Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions pkg/config/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,17 @@ func processEnvVars(atmosConfig *schema.AtmosConfiguration) error {
}
}

ciCommentsEnabled := os.Getenv("ATMOS_CI_COMMENTS_ENABLED")
if len(ciCommentsEnabled) > 0 {
log.Debug(foundEnvVarMessage, "ATMOS_CI_COMMENTS_ENABLED", ciCommentsEnabled)
enabled, err := strconv.ParseBool(ciCommentsEnabled)
if err != nil {
log.Warn("Invalid boolean value for ENV variable; using default.", "ATMOS_CI_COMMENTS_ENABLED", ciCommentsEnabled)
} else {
atmosConfig.CI.Comments.Enabled = enabled
}
}

return nil
}

Expand Down
128 changes: 128 additions & 0 deletions pkg/config/utils_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,37 @@ func Test_processEnvVars(t *testing.T) {
expectError: true,
errorMessage: "strconv.ParseBool: parsing \"not-a-boolean\": invalid syntax",
},
{
name: "ATMOS_CI_COMMENTS_ENABLED=true enables CI comments",
envVars: map[string]string{
"ATMOS_CI_COMMENTS_ENABLED": "true",
},
expectedConfig: schema.AtmosConfiguration{
CI: schema.CIConfig{
Comments: schema.CICommentsConfig{Enabled: true},
},
},
expectError: false,
},
{
name: "ATMOS_CI_COMMENTS_ENABLED=false disables CI comments",
envVars: map[string]string{
"ATMOS_CI_COMMENTS_ENABLED": "false",
},
expectedConfig: schema.AtmosConfiguration{
CI: schema.CIConfig{
Comments: schema.CICommentsConfig{Enabled: false},
},
},
expectError: false,
},
{
name: "ATMOS_CI_COMMENTS_ENABLED invalid value logs warning without error",
envVars: map[string]string{
"ATMOS_CI_COMMENTS_ENABLED": "not-a-bool",
},
expectError: false,
},
}

for _, tt := range tests {
Expand Down Expand Up @@ -335,6 +366,103 @@ func Test_processEnvVars(t *testing.T) {
}
}

func TestProcessEnvVars_CICommentsEnabled(t *testing.T) {
tests := []struct {
name string
envVal string
initial bool
want bool
}{
{
name: "env var true overrides false yaml default",
envVal: "true",
initial: false,
want: true,
},
{
name: "env var false overrides true yaml setting",
envVal: "false",
initial: true,
want: false,
},
{
name: "env var 1 enables CI comments",
envVal: "1",
initial: false,
want: true,
},
{
name: "env var 0 disables CI comments",
envVal: "0",
initial: true,
want: false,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
t.Setenv("ATMOS_CI_COMMENTS_ENABLED", tt.envVal)

config := &schema.AtmosConfiguration{
Schemas: make(map[string]interface{}),
CI: schema.CIConfig{
Comments: schema.CICommentsConfig{Enabled: tt.initial},
},
}

err := processEnvVars(config)
assert.NoError(t, err)
assert.Equal(t, tt.want, config.CI.Comments.Enabled)
})
}

t.Run("env var not set leaves yaml value unchanged", func(t *testing.T) {
// Empty string is treated as unset — env var is not processed.
t.Setenv("ATMOS_CI_COMMENTS_ENABLED", "")

config := &schema.AtmosConfiguration{
Schemas: make(map[string]interface{}),
CI: schema.CIConfig{
Comments: schema.CICommentsConfig{Enabled: true},
},
}

err := processEnvVars(config)
assert.NoError(t, err)
assert.True(t, config.CI.Comments.Enabled)
})

t.Run("invalid env var value logs warning and leaves value unchanged (true)", func(t *testing.T) {
t.Setenv("ATMOS_CI_COMMENTS_ENABLED", "not-a-bool")

config := &schema.AtmosConfiguration{
Schemas: make(map[string]interface{}),
CI: schema.CIConfig{
Comments: schema.CICommentsConfig{Enabled: true},
},
}

err := processEnvVars(config)
assert.NoError(t, err)
assert.True(t, config.CI.Comments.Enabled)
})

t.Run("invalid env var value logs warning and leaves value unchanged (false)", func(t *testing.T) {
t.Setenv("ATMOS_CI_COMMENTS_ENABLED", "not-a-bool")

config := &schema.AtmosConfiguration{
Schemas: make(map[string]interface{}),
CI: schema.CIConfig{
Comments: schema.CICommentsConfig{Enabled: false},
},
}

err := processEnvVars(config)
assert.NoError(t, err)
assert.False(t, config.CI.Comments.Enabled)
})
}

func TestFindAllStackConfigsInPathsForStack(t *testing.T) {
stacksPath := "../../tests/fixtures/scenarios/stack-templates-2"

Expand Down
58 changes: 58 additions & 0 deletions website/blog/2026-04-10-ci-comments-env-var.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
---
slug: ci-comments-env-var
title: "Toggle CI PR Comments with an Environment Variable"
authors: [atmos]
tags: [enhancement]
date: 2026-04-10T00:00:00.000Z
---

You can now enable or disable CI PR comments per-pipeline using the `ATMOS_CI_COMMENTS_ENABLED` environment variable — no config file changes needed.

<!--truncate-->

## What Changed

A new `ATMOS_CI_COMMENTS_ENABLED` environment variable overrides the `ci.comments.enabled` setting in `atmos.yaml`. When set, it takes precedence over the YAML configuration.

```
ATMOS_CI_COMMENTS_ENABLED=true # enable PR comments
ATMOS_CI_COMMENTS_ENABLED=false # disable PR comments
```

The variable accepts any standard boolean value (`true`, `false`, `1`, `0`). Invalid values log a warning and leave the YAML setting unchanged.

## Why This Matters

Some CI workflows need different PR comment behavior depending on the context. For example:

- **Scheduled drift-detection runs** should suppress PR comments (there's no PR to comment on)
- **PR preview pipelines** should post plan summaries as PR comments
- **Staging deploys** triggered by merge should skip comments

Previously, you had to maintain separate `atmos.yaml` files or use conditional YAML templating to toggle comments. Now it's a single environment variable.

## How to Use It

Set the variable in your CI workflow:

```yaml
# Post plan summaries as PR comments
- name: atmos terraform plan
env:
ATMOS_CI_COMMENTS_ENABLED: "true"
run: |
atmos terraform plan "$COMPONENT" --stack "$STACK"

# Suppress comments for scheduled runs
- name: atmos terraform plan (drift detection)
env:
ATMOS_CI_COMMENTS_ENABLED: "false"
run: |
atmos terraform plan "$COMPONENT" --stack "$STACK"
```

The environment variable follows the same precedence as other `ATMOS_*` variables: CLI flags > environment variables > config files > defaults.

## Get Involved

Found an issue or have a feature request? Open an issue on [GitHub](https://github.com/cloudposse/atmos/issues).
8 changes: 8 additions & 0 deletions website/docs/cli/configuration/ci/comments.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ ci:
**Requires:** `pull-requests: write` permission (GitHub) or API token with MR notes scope (GitLab)

**Default:** `true`

**Environment variable:** `ATMOS_CI_COMMENTS_ENABLED` — overrides this setting when set.
</dd>

<dt>`ci.comments.behavior`</dt>
Expand All @@ -52,6 +54,12 @@ ci:
</dd>
</dl>

## Environment Variables

| Variable | Description |
|----------|-------------|
| `ATMOS_CI_COMMENTS_ENABLED` | Override `ci.comments.enabled` (`true`/`false`). Takes precedence over the YAML configuration when set. |

## GitHub Actions Permissions

PR comments require the `pull-requests: write` permission:
Expand Down
1 change: 1 addition & 0 deletions website/docs/cli/configuration/ci/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ Override with the `--ci` flag or `ci.enabled` configuration.
| Variable | Description |
|----------|-------------|
| `ATMOS_CI` | Explicitly enable CI mode (`true`/`false`) |
| `ATMOS_CI_COMMENTS_ENABLED` | Override `ci.comments.enabled` (`true`/`false`) |
| `CI` | Standard CI environment variable (set by most CI providers) |
| `GITHUB_ACTIONS` | Set by GitHub Actions runner |
| `GITHUB_TOKEN` / `GH_TOKEN` | GitHub API token (required for status checks and PR comments) |
Expand Down
3 changes: 2 additions & 1 deletion website/src/data/roadmap.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading