Skip to content

Commit b69113d

Browse files
ostermanclaudeaknysh
authored
Support explicit CI git checkout and bundle Docker CLI (#2812)
* feat(ci): support explicit git clone checkout * fix(docs): satisfy skill editorconfig * chore(ci): validate editorconfig before commit * fix(ci): install atmos for pre-commit validation * fix(ci): use validate-capable atmos release * test(docker): expect bundled docker cli * fix(ci): mirror Docker Hub pulls through Google * fix(test): resolve CI toolchain cache binaries * fix(pre-commit): remove Go build hook * test(git-clone): cover CI-mode resolution edge cases Patch coverage was 82.71% (below the 85% target) with the gaps concentrated in parseCloneFlags/resolveCICloneMode's flag-parse-error path, the unset-ATMOS_CI-and-unset-flag default path, and root.go's gitCloneBootstrapCIMode explicit --ci=true and invalid-ATMOS_CI branches. * fix(tests): close TOCTOU race in Terraform/OpenTofu binary resolution TestExecuteTerraform_Version and TestYamlFuncTerraformOutput each resolved the tofu/terraform binary via RequireTofu/RequireTerraform, then made a second, independent exec.LookPath call before isolating the binary. On the Windows acceptance job's concurrent packages, another test can mutate the shared toolchain cache directory between those two lookups, turning a binary that just existed into a spurious "not found" failure. RequireTerraformPath/RequireTofuPath now resolve the path once and hand it back directly, removing the redundant lookup at each call site. * fix(tests): fail loudly instead of returning an empty path on lookup miss requireExecutablePath (backing RequireTerraformPath/RequireTofuPath) silently returned "" when ATMOS_TEST_SKIP_PRECONDITION_CHECKS=true (set for every CI acceptance job) and the binary wasn't found, instead of surfacing the miss. Callers that need the resolved path then crashed on the empty string with a confusing "open : file not found" error rather than a clear diagnostic. RequireExecutable is decoupled back to its original full-bypass behavior (a true no-op when checks are disabled) since its callers only need a boolean gate, not a path -- only the path-returning variants now hard-fail on a genuine lookup miss. * fix(test): isolate toolchain install-path from the real shared cache dir Several pkg/toolchain tests (TestRunInstall_WithLatestKeyword, TestRunInstall_WithCanonicalFormat, TestRunInstall_WithValidToolSpec, TestRunInstall_WithSetAsDefault, TestRunInstallWithNoArgs, TestRunInstall_Reinstall, TestRunUninstall, TestRunUninstall_InvalidToolSpecFormat, TestUninstallAllVersionsOfTool) called SetAtmosConfig() without setting Toolchain.InstallPath. NewInstaller()'s GetInstallPath() falls back to the real, shared XDG toolchain cache directory whenever InstallPath is empty -- the exact directory CI's "atmos toolchain install --default" step populates and that the whole acceptance suite depends on for hashicorp/terraform and opentofu/opentofu binaries for the rest of the run. TestRunInstall_WithLatestKeyword and TestRunInstall_WithCanonicalFormat in particular perform real network installs of real "terraform" binaries into that shared path, racing with every other concurrently-running package's test process reading tofu/terraform off PATH -- almost certainly the root cause of the intermittent "'tofu' not found in PATH" hard failures seen on the Windows acceptance job (round 3 of this investigation had already ruled out a stale-PATH-visibility theory for this specific failure instance). Fixed by pointing Toolchain.InstallPath at each test's own t.TempDir(), matching the isolation pattern already used by the rest of the toolchain test suite. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(test): isolate remaining toolchain-path leak, widen resolution retry TestBuildToolRow/TestBuildToolRow_NotInstalled resolved NewInstaller()'s binDir to the real shared toolchain cache dir (no InstallPath override), same class of issue fixed in fdbb83f -- read-only here, but still worth isolating for hygiene and to rule them out as a contributor. The Windows tofu-not-found flake recurred in CI even after that fix, so widen requireExecutablePath's retry window from 2s to 15s as a defensive backstop: some residual contention under the acceptance job's concurrent package load isn't yet fully identified, and a longer bounded poll is safer than risking another silent failure while that's tracked down. * test(ci): add lookup forensics for the Windows toolchain-vanishing flake The Windows acceptance job has a recurring failure where an installed toolchain binary (tofu/terraform) vanishes from PATH mid-suite for the full retry window. Root-cause candidates are all writer-side windows in the shared toolchain cache (uninstall RemoveAll, onedir rename-aside, in-place flat-install extraction) but four rounds of static analysis couldn't pin which one fires in CI. requireExecutablePath now dumps per-PATH-entry forensics on failure: whether each toolchain dir exists, its contents, target presence, and whether the installer's ".lock" sibling is held. The next CI failure becomes direct evidence of the mechanism (dir deleted vs tree renamed aside vs writer mid-flight vs PATH corruption) instead of another guess. * fix(test): stop telemetry test deleting the shared Atmos cache root Root cause of the recurring Windows acceptance flake, confirmed by the lookup forensics added in a68e195: the failure dump showed EVERY toolchain directory (terraform, opentofu, helm, helmfile) gone at once -- the whole <cache>/atmos tree had been deleted mid-run. TestPrintTelemetryDisclosureOnlyOnce "cleaned up" by os.RemoveAll on filepath.Dir(cfg.GetCacheFilePath()) -- the real shared <cache>/atmos root -- twice (setup + defer). Ever since the toolchain install root moved underneath that same root (<cache>/atmos/toolchain, #2579), this deleted every CI-provisioned tool out from under the concurrently running package test binaries. Whichever terraform/tofu-dependent test happened to run during the window failed with "executable file not found in %PATH%"; the tests package healed itself via TestMain re-provisioning, which is why only internal/exec kept failing. All four disclosure tests now isolate the cache via a per-test ATMOS_XDG_CACHE_HOME/XDG_CACHE_HOME redirect instead of deleting anything shared. This also fixes the sibling tests that "cleaned" ./.atmos -- a directory the disclosure code never used -- while actually polluting the real user-level cache.yaml. * docs(fixes): record telemetry-test shared-cache-root fix * refactor(cmd): remove hand-rolled argv parsing for CI git-clone bootstrap cmd/root.go hand-parsed os.Args to decide whether a config-init failure was a no-argument `atmos git clone` bootstrapping in an empty CI workspace, duplicating cmd/git's own --ci/ATMOS_CI resolution (resolveCICloneMode) with real drift: root's literal "--ci=true"/ "--ci=false" string matching missed values pflag's ParseBool accepts (e.g. --ci=TRUE, ATMOS_CI=1). Replace ~120 lines of argv sniffing (isCIGitCloneBootstrapRequested, gitCloneBootstrapCIMode, stripRootFlagsForBootstrapCheck, and friends) with cmd/git.CICloneBootstrapRequested(cmd, args): a Cobra-identity + already-parsed-flag check, callable only from PersistentPreRun onward (where Cobra has parsed the target command's flags), delegating the actual --ci/ATMOS_CI precedence to the single implementation (resolveCICloneMode) cmd/git's own RunE already uses. This also fixes a load-bearing bug uncovered while tracing the old code: the pre-dispatch call site (handleConfigInitErrorWithArgs, run from Execute() before RootCmd.ExecuteC()) was the ONLY place that set CI.Enabled on the config cmd/git actually reads (the package-level `atmosConfig`, aliased into cmd/git via SetAtmosConfig before PersistentPreRun runs) -- PersistentPreRun's own bootstrap branch wrote to a local `tmpConfig` that is discarded at the end of the function, so for the canonical empty-workspace (cfg.NotFound) case it never did anything. The pre-dispatch call site is deleted entirely; the load-bearing write is added directly to PersistentPreRun's NotFound branch instead, which now writes both the package-level atmosConfig (load-bearing) and the local tmpConfig (used by the rest of this invocation). Malformed (as opposed to missing) atmos.yaml encountered during CI bootstrap continues to be tolerated via PersistentPreRun's existing non-NotFound bootstrap branch, now also routed through the shared helper. * fix(ci): exclude docs.docker.com from link check (connection resets) CI's Check Markdown Links job failed on docs/prd/ecr-authentication.md (unrelated to this PR's diff) with "Connection reset by peer" against docs.docker.com. The URL is live (curl: 301 -> 200 outside CI); this matches the repo's existing precedent for CDN-fronted hosts that intermittently reset connections from CI runners (taskfile.dev, geminicli.com, otelic.com, etc.). * fix(test): address CodeRabbit findings on PR #2812 Fix three CodeRabbit-flagged issues: - pkg/toolchain/install_test.go, list_test.go: several tests set Toolchain.InstallPath to a per-test t.TempDir() (needed to keep real installs off the shared XDG toolchain cache) but only partially restored atmosConfig afterward, leaving InstallPath pointed at a directory that TempDir cleanup had already deleted. Save/restore the full prior atmosConfig instead of reconstructing a partial one. - cmd/root_test.go: TestApplyCIGitCloneBootstrap_AllowsBootstrap and TestApplyCIGitCloneBootstrap_NoCIProviderDetected didn't pin ATMOS_CI, so an ambient ATMOS_CI=false in the developer/CI environment could make resolveCICloneMode resolve to "disabled" and fail the test non-deterministically. Pin ATMOS_CI explicitly, mirroring the existing guard in cmd/git/bootstrap_test.go. - .github/workflows/test.yml: the Docker Hub mirror configuration step used jq's `unique` to append mirror.gcr.io to registry-mirrors, which sorts the array and can silently reorder any pre-existing mirrors (Docker tries mirrors in listed order). Only append when absent, preserving existing order. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(git): use CI context branch name, not raw ref, in no-arg checkout Add a real (non-stubbed) native-ci job cloning a local fixture repo to exercise the CI git-clone bootstrap path end-to-end, which immediately surfaced a pre-existing bug: runCICheckout passed the raw ref (e.g. refs/heads/main) as `git clone --branch`, which rejects full ref paths. Use the CI context's already-parsed branch name instead, and document the config-init tolerance mechanism in docs/prd/git-ops.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * revert(ci): drop the ci-bootstrap-clone native-ci job The synthetic fixture-repo redirection (overriding GITHUB_SERVER_URL/ GITHUB_REPOSITORY at the step level) worked in a local shell but not in a real Actions run: GitHub Actions silently ignores GITHUB_-prefixed env overrides, so the job actually cloned the real cloudposse/atmos repo and failed a later assertion meant for the fixture. That's too curve-fitted to the local repro to be a real test of native CI in CI, and not fixable by adjusting the script further, so drop it rather than keep a job that looks like coverage but silently tests the wrong thing. The runCICheckout branch/ref fix itself is unaffected — it was found and verified via a local (non-CI) reproduction and is covered by the corrected unit test. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * docs(fix-log): fix missing comma per CodeRabbit review Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> * fix(test): recognize packer-only manifest error in build tolerance check TestPackerBuildCmdWithDirectoryTemplate failed on Windows CI (job 91001781247) with "Failed loading manifest ... EOF" -- text packer's own manifest post-processor produces, never Atmos. The packerRan heuristic didn't recognize this specific string, so it fell through to a real test failure even though packer clearly ran and failed for environmental (credential) reasons, same as the test already tolerates for other packer error shapes. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com> Co-authored-by: Andriy Knysh <aknysh@users.noreply.github.com>
1 parent 0c77c7b commit b69113d

25 files changed

Lines changed: 775 additions & 198 deletions

.github/workflows/pre-commit.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ on:
55
types: [opened, synchronize, reopened]
66

77
env:
8+
# Released binary used by hooks that invoke Atmos. This must not build source
9+
# during pre-commit, which runs in a worktree-sensitive lifecycle event.
10+
ATMOS_BOOTSTRAP_VERSION: "1.224.1"
811
# Ensure pre-commit uses the right Python version
912
PYTHON_VERSION: "3.11"
1013
# Skip hooks that are already running in other CI jobs to avoid redundant work
@@ -35,6 +38,11 @@ jobs:
3538
# Fetch full history for proper diff checking
3639
fetch-depth: 0
3740

41+
- name: Set up Atmos
42+
uses: ./.github/actions/setup-atmos-bootstrap
43+
with:
44+
atmos-version: ${{ env.ATMOS_BOOTSTRAP_VERSION }}
45+
3846
- name: Set up Go
3947
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
4048
with:

.github/workflows/test.yml

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -591,6 +591,22 @@ jobs:
591591
timeout-minutes: 45
592592
uses: ./.github/actions/setup-colima
593593

594+
- name: Configure Docker Hub mirror on Linux
595+
if: matrix.flavor.target == 'linux'
596+
shell: bash
597+
run: |
598+
set -euo pipefail
599+
sudo install -d -m 0755 /etc/docker
600+
if sudo test -s /etc/docker/daemon.json; then
601+
sudo jq '."registry-mirrors" = ((."registry-mirrors" // []) as $mirrors | if ($mirrors | index("https://mirror.gcr.io")) == null then $mirrors + ["https://mirror.gcr.io"] else $mirrors end)' \
602+
/etc/docker/daemon.json | sudo tee /etc/docker/daemon.json.tmp >/dev/null
603+
sudo mv /etc/docker/daemon.json.tmp /etc/docker/daemon.json
604+
else
605+
printf '%s\n' '{"registry-mirrors":["https://mirror.gcr.io"]}' | sudo tee /etc/docker/daemon.json >/dev/null
606+
fi
607+
sudo systemctl restart docker
608+
docker info --format '{{json .RegistryConfig.Mirrors}}' | grep -F 'https://mirror.gcr.io'
609+
594610
- name: Write a default AWS profile to the AWS config file
595611
run: |
596612
mkdir -p ~/.aws

.pre-commit-config.yaml

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,6 @@ repos:
1010
args: [-w]
1111
exclude: ^(vendor/|tests/test-cases/|tests/testdata/|tests/snapshots/)
1212

13-
- id: go-build-mod
14-
name: Check Go build
15-
description: Ensure code compiles before commit
16-
1713
- id: go-mod-tidy
1814
name: Tidy go.mod
1915
description: Ensure go.mod and go.sum are clean
@@ -67,6 +63,17 @@ repos:
6763
pass_filenames: false
6864
always_run: true
6965

66+
# Keep local commits aligned with the affected-file validation job.
67+
# Pre-commit must not compile Atmos: use an already installed binary.
68+
# Exclude Go to match CI's raw-string limitation.
69+
- id: atmos-validate-editorconfig
70+
name: Validate EditorConfig
71+
description: Validate affected non-Go files against EditorConfig rules
72+
entry: atmos validate --affected --exclude 'tests/fixtures/**' --exclude '**/*.go' --format rich
73+
language: system
74+
pass_filenames: false
75+
always_run: true
76+
7077

7178
# General file hygiene
7279
# NOTE: trailing-whitespace, end-of-file-fixer, and check-added-large-files hardcode

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ RUN set -ex; \
1919
# Update the package list
2020
apt-get update; \
2121
# Install runtime dependencies required by Atmos-managed tools.
22-
apt-get -y install --no-install-recommends curl git ca-certificates python3; \
22+
apt-get -y install --no-install-recommends curl git ca-certificates docker.io python3; \
2323
# Install the Cloud Posse Debian repository
2424
curl -1sLf 'https://dl.cloudsmith.io/public/cloudposse/packages/cfg/setup/bash.deb.sh' | bash -x; \
2525
# Install OpenTofu

agent-skills/skills/atmos-modernization/SKILL.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ the umbrella term for replacing legacy patterns with supported, current patterns
1919
| `settings.depends_on` | `dependencies.components` |
2020
| `cloudposse/github-action-atmos*` wrapper actions | Native CI with direct `atmos` commands |
2121
| `cloudposse/github-action-setup-atmos` as default | GitHub Actions container `ghcr.io/cloudposse/atmos:<version>` |
22+
| `apt-get install docker.io` in an Atmos container job | Remove it: the official Atmos image already ships with `docker.io` |
2223
| GitHub Actions `concurrency` around jobs or workflows that invoke `atmos` | An explicit promotion workflow or deployment controller — environments and merge queues are approval/merge-order controls, not deployment-order guarantees; a concurrency group evicts its pending run regardless of `cancel-in-progress` |
2324
| `hashicorp/setup-terraform` / `opentofu/setup-opentofu` in Atmos jobs | Atmos `dependencies.tools` and toolchain |
2425
| Manual `atmos toolchain install <tool>` preinstall steps for Atmos-owned tools | Declarative `dependencies.tools` at the owning component, workflow, hook, or custom command |
@@ -88,6 +89,9 @@ jobs:
8889
- run: atmos terraform plan vpc -s prod
8990
```
9091
92+
The official Atmos image includes `docker.io`, so do not add an `apt-get install docker.io` step
93+
to containerized Atmos jobs. Docker-backed commands use the runner-provided Docker daemon/socket.
94+
9195
Use `atmos describe affected --format=matrix` for PR matrices and `atmos list instances
9296
--format=matrix` for full estate operations.
9397

cmd/docker_and_action_regression_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ func TestDockerfileInstallsPython3Runtime(t *testing.T) {
1212
content, err := os.ReadFile("../Dockerfile")
1313
require.NoError(t, err)
1414

15-
assert.Contains(t, string(content), "--no-install-recommends curl git ca-certificates python3")
15+
assert.Contains(t, string(content), "--no-install-recommends curl git ca-certificates docker.io python3")
1616
assert.NotContains(t, string(content), "python3-pip")
1717
assert.NotContains(t, string(content), "python3-venv")
1818
}

cmd/git/bootstrap.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package git
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
6+
"github.com/cloudposse/atmos/pkg/ci"
7+
"github.com/cloudposse/atmos/pkg/flags"
8+
)
9+
10+
// CICloneBootstrapRequested reports whether the invoked command is a
11+
// no-argument `atmos git clone` running under a detected CI provider that has
12+
// not explicitly opted out of CI checkout (--ci=false / ATMOS_CI=false).
13+
//
14+
// RootCmd's config-init-error path (cmd/root.go) uses this to tolerate a
15+
// missing or malformed atmos.yaml: the CI bootstrap clone runs in an empty
16+
// workspace (e.g. replacing actions/checkout) where no atmos.yaml can exist
17+
// yet. This must only be called after Cobra has parsed cmd's flags (true from
18+
// PersistentPreRun onward), since it reads the real --ci flag via
19+
// resolveCICloneMode instead of re-parsing os.Args by hand.
20+
func CICloneBootstrapRequested(cmd *cobra.Command, args []string) bool {
21+
if !isCloneCommand(cmd) {
22+
return false
23+
}
24+
25+
// --all bulk-clones every configured repository -- never the single-repo
26+
// CI bootstrap case, even with zero positional args.
27+
if all, _ := cmd.Flags().GetBool(flagAll); all {
28+
return false
29+
}
30+
31+
// A native-arg separator (`clone -- --no-tags`) signals a deliberate,
32+
// hand-crafted invocation, not the zero-argument auto-bootstrap case.
33+
positional, separated := flags.SplitArgsAtDash(cmd, args)
34+
if len(positional) != 0 || len(separated) != 0 {
35+
return false
36+
}
37+
38+
if ci.Detect() == nil {
39+
return false
40+
}
41+
42+
// resolveCICloneMode returns ciCloneModeAuto even when --ci/ATMOS_CI is
43+
// malformed (its error is for the caller to report); treating that the
44+
// same as "auto" here defers the actual error to parseCloneFlags in the
45+
// command's own RunE, where it belongs.
46+
mode, _ := resolveCICloneMode(cmd)
47+
return mode != ciCloneModeDisabled
48+
}
49+
50+
// isCloneCommand reports whether cmd is the `atmos git clone` leaf, checked
51+
// by name/parent rather than pointer identity so callers can exercise this
52+
// with a lightweight test command tree instead of the package's real
53+
// singletons.
54+
func isCloneCommand(cmd *cobra.Command) bool {
55+
return cmd != nil &&
56+
cmd.Name() == cloneCmd.Name() &&
57+
cmd.Parent() != nil &&
58+
cmd.Parent().Name() == gitCmd.Name()
59+
}

cmd/git/bootstrap_test.go

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
package git
2+
3+
import (
4+
"os"
5+
"testing"
6+
7+
"github.com/spf13/cobra"
8+
"github.com/stretchr/testify/assert"
9+
"github.com/stretchr/testify/require"
10+
// The GitHub CI provider is already blank-imported by clone.go, registering
11+
// it for ci.Detect() package-wide.
12+
)
13+
14+
// newBootstrapTestCommand builds a lightweight "git clone" command tree
15+
// (mirroring the real gitCmd/cloneCmd parent/name relationship) with the same
16+
// --ci/--all bool flags the real clone command registers, then parses rawArgs
17+
// through it exactly as Cobra would before invoking PersistentPreRun/RunE.
18+
// Returns the clone leaf and the resulting positional args.
19+
func newBootstrapTestCommand(t *testing.T, rawArgs []string) (*cobra.Command, []string) {
20+
t.Helper()
21+
22+
root := &cobra.Command{Use: "atmos"}
23+
git := &cobra.Command{Use: gitCmd.Name()}
24+
clone := &cobra.Command{Use: cloneCmd.Name()}
25+
clone.Flags().Bool(flagCI, false, "")
26+
clone.Flags().Bool(flagAll, false, "")
27+
root.AddCommand(git)
28+
git.AddCommand(clone)
29+
30+
require.NoError(t, clone.ParseFlags(rawArgs))
31+
return clone, clone.Flags().Args()
32+
}
33+
34+
func withCleanATMOSCIEnv(t *testing.T, value string) {
35+
t.Helper()
36+
if value == "" {
37+
original, wasSet := os.LookupEnv("ATMOS_CI")
38+
os.Unsetenv("ATMOS_CI")
39+
t.Cleanup(func() {
40+
if wasSet {
41+
_ = os.Setenv("ATMOS_CI", original)
42+
return
43+
}
44+
os.Unsetenv("ATMOS_CI")
45+
})
46+
return
47+
}
48+
t.Setenv("ATMOS_CI", value)
49+
}
50+
51+
func TestCICloneBootstrapRequested(t *testing.T) {
52+
tests := []struct {
53+
name string
54+
rawArgs []string
55+
ciDetected bool
56+
atmosCIEnv string
57+
wantRequest bool
58+
}{
59+
{
60+
name: "no CI provider detected",
61+
rawArgs: nil,
62+
ciDetected: false,
63+
wantRequest: false,
64+
},
65+
{
66+
name: "CI detected, no args, auto mode",
67+
rawArgs: nil,
68+
ciDetected: true,
69+
wantRequest: true,
70+
},
71+
{
72+
name: "explicit --ci=false opts out",
73+
rawArgs: []string{"--ci=false"},
74+
ciDetected: true,
75+
wantRequest: false,
76+
},
77+
{
78+
name: "ATMOS_CI=false opts out",
79+
rawArgs: nil,
80+
ciDetected: true,
81+
atmosCIEnv: "false",
82+
wantRequest: false,
83+
},
84+
{
85+
name: "ATMOS_CI uppercase TRUE is accepted (no drift vs pflag ParseBool)",
86+
rawArgs: nil,
87+
ciDetected: true,
88+
atmosCIEnv: "TRUE",
89+
wantRequest: true,
90+
},
91+
{
92+
name: "positional argument disqualifies bootstrap",
93+
rawArgs: []string{"my-repo"},
94+
ciDetected: true,
95+
wantRequest: false,
96+
},
97+
{
98+
name: "native args after -- disqualify bootstrap",
99+
rawArgs: []string{"--", "--no-tags"},
100+
ciDetected: true,
101+
wantRequest: false,
102+
},
103+
{
104+
name: "--all disqualifies bootstrap",
105+
rawArgs: []string{"--all"},
106+
ciDetected: true,
107+
wantRequest: false,
108+
},
109+
{
110+
name: "invalid ATMOS_CI value defers to auto (RunE reports the real error)",
111+
rawArgs: nil,
112+
ciDetected: true,
113+
atmosCIEnv: "sometimes",
114+
wantRequest: true,
115+
},
116+
}
117+
118+
for _, tt := range tests {
119+
t.Run(tt.name, func(t *testing.T) {
120+
if tt.ciDetected {
121+
t.Setenv("GITHUB_ACTIONS", "true")
122+
} else {
123+
t.Setenv("GITHUB_ACTIONS", "false")
124+
}
125+
withCleanATMOSCIEnv(t, tt.atmosCIEnv)
126+
127+
cmd, args := newBootstrapTestCommand(t, tt.rawArgs)
128+
assert.Equal(t, tt.wantRequest, CICloneBootstrapRequested(cmd, args))
129+
})
130+
}
131+
}
132+
133+
func TestCICloneBootstrapRequested_WrongCommand(t *testing.T) {
134+
t.Setenv("GITHUB_ACTIONS", "true")
135+
withCleanATMOSCIEnv(t, "")
136+
137+
root := &cobra.Command{Use: "atmos"}
138+
terraform := &cobra.Command{Use: "terraform"}
139+
plan := &cobra.Command{Use: "plan"}
140+
root.AddCommand(terraform)
141+
terraform.AddCommand(plan)
142+
143+
assert.False(t, CICloneBootstrapRequested(plan, nil))
144+
assert.False(t, CICloneBootstrapRequested(nil, nil))
145+
}

0 commit comments

Comments
 (0)