# ci.summary

The `ci.summary` function appends Markdown to the job summary of the current run. Locally, it renders the Markdown
as formatted text on stderr.

## Usage

```python
ci.summary(markdown = "", template = "", data = None)
```

## Arguments

- **`markdown`**

  The Markdown to append, as a string. Each call adds to the summary rather than replacing it, so a script can
  build the summary in several steps. Required unless `template` is given, and it cannot be combined with
  `template` or `data`, even when it is an empty string.
- **`template`**

  (Optional) The name of a [template file](#templates) to render into the summary, instead of passing
  `markdown`. It is a file name under [`ci.templates.base_path`](/cli/configuration/ci/templates), or an absolute
  path. There is no default template, so `data` always needs a `template`.
- **`data`**

  (Optional) A dict that becomes the template context. Keys are read in the template as `.key`. Passing `data`
  without `template` fails with an argument error that says `template=` is required.

## Templates

A script can keep the layout of a summary in a template file and pass only the values:

- A call with `template=` renders that file, with `data` as the context. A call with only `markdown` appends the text
  as written.
- There is no configured default template. The [`ci.summary.template`](/cli/configuration/ci/summary) key belongs to
  the summaries Atmos writes for commands, and scripts never read it.
- The name is resolved against [`ci.templates.base_path`](/cli/configuration/ci/templates). A relative `base_path`
  resolves against the Atmos `base_path`. When no file has exactly that name, the name with a `.md` suffix is tried.
- Templates use Go template syntax with the same functions as the built-in plan templates. The `data` dict is the
  template context.
- A key that the template reads but `data` does not contain fails the render and names the key, so a typo does not
  write `<no value>` into the summary.

See [script report templates](/cli/configuration/ci/templates#script-report-templates) for the configuration.

## Returns

The return value is `None`.

## Behavior

- On GitHub Actions, the text is appended to the job summary file that the runner provides. The summary appears on
  the run page.
- Locally, the Markdown is rendered as terminal Markdown on stderr.
- The text is masked before it leaves the process, so secrets that Atmos knows about do not reach the summary.
- In CI the call is gated by [`ci.summary.enabled`](/cli/configuration/ci/summary). When it is off, the call warns,
  renders locally, and the script continues. See [gating](/functions/automation/ci#gating).

## Examples

### Report a short result

```python
ci.summary("## Deploy\n\nDeployed **api** to `prod` in 42 seconds.")
```

### Build a table

```python
rows = ["| Stack | Result |", "| --- | --- |"]
for stack, result in {"dev": "ok", "prod": "ok"}.items():
    rows.append("| " + stack + " | " + result + " |")
ci.summary("\n".join(rows))
```

### Render a summary from a template

```text title=".atmos/ci/templates/deploy.md"
## Deploy to {{ .stack }}

{{ .count }} services deployed.
{{ range .services }}
- {{ . }}
{{ end }}
```

```python
ci.summary(template = "deploy.md", data = {"stack": "prod", "count": 2, "services": ["api", "web"]})
```

## Errors

- A missing or non-string `markdown` fails with an argument error that names `ci.summary`.
- Passing `markdown` together with `template` or `data` (an empty string counts), a `data` value that is not a dict, or
  `data` without `template` fails with an argument error.
- A template that does not exist fails the script with an error that names the resolved path and the value of
  `ci.templates.base_path`. A template that reads a key missing from `data`, or that fails to render, fails the script.
- A failure to write the summary fails the script. A gated-off call never fails.

## Related

- [`ci`](/functions/automation/ci) describes the module and the shared behavior rules.
- [CI job summaries](/cli/configuration/ci/summary) configures the native summaries that Atmos writes for commands.
- [`ci.comment`](/functions/automation/ci.comment) posts Markdown to the pull request instead.
