Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
9d14f46
feat(ci): support explicit git clone checkout
osterman Jul 27, 2026
3315a62
fix(docs): satisfy skill editorconfig
osterman Jul 27, 2026
9a1d717
chore(ci): validate editorconfig before commit
osterman Jul 27, 2026
785edb4
fix(ci): install atmos for pre-commit validation
osterman Jul 27, 2026
df8510f
fix(ci): use validate-capable atmos release
osterman Jul 27, 2026
a8a68c1
test(docker): expect bundled docker cli
osterman Jul 27, 2026
3d6efee
fix(ci): mirror Docker Hub pulls through Google
osterman Jul 27, 2026
f996805
fix(test): resolve CI toolchain cache binaries
osterman Jul 28, 2026
df2cb8f
fix(pre-commit): remove Go build hook
osterman Jul 28, 2026
57110b7
test(git-clone): cover CI-mode resolution edge cases
osterman Jul 28, 2026
5ddaec9
Merge origin/main into osterman/support-ci-git-clone
osterman Jul 28, 2026
0cfc8dd
fix(tests): close TOCTOU race in Terraform/OpenTofu binary resolution
osterman Jul 28, 2026
3f217e6
fix(tests): fail loudly instead of returning an empty path on lookup …
osterman Jul 29, 2026
9e9c60b
Merge branch 'main' into osterman/support-ci-git-clone
osterman Jul 29, 2026
1436770
Merge branch 'main' into osterman/support-ci-git-clone
osterman Jul 29, 2026
8ea8ecd
Merge branch 'main' into osterman/support-ci-git-clone
osterman Jul 29, 2026
b41e99a
Merge origin/main into osterman/support-ci-git-clone
osterman Jul 29, 2026
6dca8da
Merge branch 'main' into osterman/support-ci-git-clone
osterman Jul 29, 2026
fdbb83f
fix(test): isolate toolchain install-path from the real shared cache dir
osterman Jul 29, 2026
c0181b8
fix(test): isolate remaining toolchain-path leak, widen resolution retry
osterman Jul 29, 2026
a62452a
Merge branch 'main' into osterman/support-ci-git-clone
osterman Jul 29, 2026
9343ec5
Merge branch 'main' into osterman/support-ci-git-clone
osterman Jul 29, 2026
a68e195
test(ci): add lookup forensics for the Windows toolchain-vanishing flake
osterman Jul 29, 2026
96666c3
fix(test): stop telemetry test deleting the shared Atmos cache root
osterman Jul 30, 2026
4193f0a
docs(fixes): record telemetry-test shared-cache-root fix
osterman Jul 30, 2026
2682303
Merge branch 'main' into osterman/support-ci-git-clone
osterman Jul 30, 2026
1bd63df
refactor(cmd): remove hand-rolled argv parsing for CI git-clone boots…
osterman Jul 30, 2026
285bc51
fix(ci): exclude docs.docker.com from link check (connection resets)
osterman Jul 30, 2026
4357390
fix(test): address CodeRabbit findings on PR #2812
osterman Jul 30, 2026
6fa52e2
fix(git): use CI context branch name, not raw ref, in no-arg checkout
osterman Jul 30, 2026
91e3572
revert(ci): drop the ci-bootstrap-clone native-ci job
osterman Jul 30, 2026
bb2d6a3
docs(fix-log): fix missing comma per CodeRabbit review
osterman Jul 30, 2026
87fa547
Merge branch 'main' into osterman/support-ci-git-clone
osterman Jul 30, 2026
f0c83bc
Merge remote-tracking branch 'origin/main' into osterman/support-ci-g…
osterman Jul 30, 2026
076c295
fix(test): recognize packer-only manifest error in build tolerance check
osterman Jul 30, 2026
258ecbf
Merge branch 'main' into osterman/support-ci-git-clone
aknysh Jul 31, 2026
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
8 changes: 8 additions & 0 deletions .github/workflows/pre-commit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ on:
types: [opened, synchronize, reopened]

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

- name: Set up Atmos
uses: ./.github/actions/setup-atmos-bootstrap
with:
atmos-version: ${{ env.ATMOS_BOOTSTRAP_VERSION }}

- name: Set up Go
uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0
with:
Expand Down
16 changes: 16 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -591,6 +591,22 @@ jobs:
timeout-minutes: 45
uses: ./.github/actions/setup-colima

- name: Configure Docker Hub mirror on Linux
if: matrix.flavor.target == 'linux'
shell: bash
run: |
set -euo pipefail
sudo install -d -m 0755 /etc/docker
if sudo test -s /etc/docker/daemon.json; then
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)' \
/etc/docker/daemon.json | sudo tee /etc/docker/daemon.json.tmp >/dev/null
sudo mv /etc/docker/daemon.json.tmp /etc/docker/daemon.json
Comment thread
coderabbitai[bot] marked this conversation as resolved.
else
printf '%s\n' '{"registry-mirrors":["https://mirror.gcr.io"]}' | sudo tee /etc/docker/daemon.json >/dev/null
fi
sudo systemctl restart docker
docker info --format '{{json .RegistryConfig.Mirrors}}' | grep -F 'https://mirror.gcr.io'

- name: Write a default AWS profile to the AWS config file
run: |
mkdir -p ~/.aws
Expand Down
15 changes: 11 additions & 4 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,6 @@ repos:
args: [-w]
exclude: ^(vendor/|tests/test-cases/|tests/testdata/|tests/snapshots/)

- id: go-build-mod
name: Check Go build
description: Ensure code compiles before commit

- id: go-mod-tidy
name: Tidy go.mod
description: Ensure go.mod and go.sum are clean
Expand Down Expand Up @@ -67,6 +63,17 @@ repos:
pass_filenames: false
always_run: true

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


# General file hygiene
# NOTE: trailing-whitespace, end-of-file-fixer, and check-added-large-files hardcode
Expand Down
2 changes: 1 addition & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ RUN set -ex; \
# Update the package list
apt-get update; \
# Install runtime dependencies required by Atmos-managed tools.
apt-get -y install --no-install-recommends curl git ca-certificates python3; \
apt-get -y install --no-install-recommends curl git ca-certificates docker.io python3; \
# Install the Cloud Posse Debian repository
curl -1sLf 'https://dl.cloudsmith.io/public/cloudposse/packages/cfg/setup/bash.deb.sh' | bash -x; \
# Install OpenTofu
Expand Down
4 changes: 4 additions & 0 deletions agent-skills/skills/atmos-modernization/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ the umbrella term for replacing legacy patterns with supported, current patterns
| `settings.depends_on` | `dependencies.components` |
| `cloudposse/github-action-atmos*` wrapper actions | Native CI with direct `atmos` commands |
| `cloudposse/github-action-setup-atmos` as default | GitHub Actions container `ghcr.io/cloudposse/atmos:<version>` |
| `apt-get install docker.io` in an Atmos container job | Remove it: the official Atmos image already ships with `docker.io` |
| 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` |
| `hashicorp/setup-terraform` / `opentofu/setup-opentofu` in Atmos jobs | Atmos `dependencies.tools` and toolchain |
| Manual `atmos toolchain install <tool>` preinstall steps for Atmos-owned tools | Declarative `dependencies.tools` at the owning component, workflow, hook, or custom command |
Expand Down Expand Up @@ -88,6 +89,9 @@ jobs:
- run: atmos terraform plan vpc -s prod
```

The official Atmos image includes `docker.io`, so do not add an `apt-get install docker.io` step
to containerized Atmos jobs. Docker-backed commands use the runner-provided Docker daemon/socket.

Use `atmos describe affected --format=matrix` for PR matrices and `atmos list instances
--format=matrix` for full estate operations.

Expand Down
2 changes: 1 addition & 1 deletion cmd/docker_and_action_regression_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ func TestDockerfileInstallsPython3Runtime(t *testing.T) {
content, err := os.ReadFile("../Dockerfile")
require.NoError(t, err)

assert.Contains(t, string(content), "--no-install-recommends curl git ca-certificates python3")
assert.Contains(t, string(content), "--no-install-recommends curl git ca-certificates docker.io python3")
assert.NotContains(t, string(content), "python3-pip")
assert.NotContains(t, string(content), "python3-venv")
}
Expand Down
59 changes: 59 additions & 0 deletions cmd/git/bootstrap.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package git

import (
"github.com/spf13/cobra"

"github.com/cloudposse/atmos/pkg/ci"
"github.com/cloudposse/atmos/pkg/flags"
)

// CICloneBootstrapRequested reports whether the invoked command is a
// no-argument `atmos git clone` running under a detected CI provider that has
// not explicitly opted out of CI checkout (--ci=false / ATMOS_CI=false).
//
// RootCmd's config-init-error path (cmd/root.go) uses this to tolerate a
// missing or malformed atmos.yaml: the CI bootstrap clone runs in an empty
// workspace (e.g. replacing actions/checkout) where no atmos.yaml can exist
// yet. This must only be called after Cobra has parsed cmd's flags (true from
// PersistentPreRun onward), since it reads the real --ci flag via
// resolveCICloneMode instead of re-parsing os.Args by hand.
func CICloneBootstrapRequested(cmd *cobra.Command, args []string) bool {
if !isCloneCommand(cmd) {
return false
}

// --all bulk-clones every configured repository -- never the single-repo
// CI bootstrap case, even with zero positional args.
if all, _ := cmd.Flags().GetBool(flagAll); all {
return false
}

// A native-arg separator (`clone -- --no-tags`) signals a deliberate,
// hand-crafted invocation, not the zero-argument auto-bootstrap case.
positional, separated := flags.SplitArgsAtDash(cmd, args)
if len(positional) != 0 || len(separated) != 0 {
return false
}

if ci.Detect() == nil {
return false
}

// resolveCICloneMode returns ciCloneModeAuto even when --ci/ATMOS_CI is
// malformed (its error is for the caller to report); treating that the
// same as "auto" here defers the actual error to parseCloneFlags in the
// command's own RunE, where it belongs.
mode, _ := resolveCICloneMode(cmd)
return mode != ciCloneModeDisabled
}

// isCloneCommand reports whether cmd is the `atmos git clone` leaf, checked
// by name/parent rather than pointer identity so callers can exercise this
// with a lightweight test command tree instead of the package's real
// singletons.
func isCloneCommand(cmd *cobra.Command) bool {
return cmd != nil &&
cmd.Name() == cloneCmd.Name() &&
cmd.Parent() != nil &&
cmd.Parent().Name() == gitCmd.Name()
}
145 changes: 145 additions & 0 deletions cmd/git/bootstrap_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
package git

import (
"os"
"testing"

"github.com/spf13/cobra"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
// The GitHub CI provider is already blank-imported by clone.go, registering
// it for ci.Detect() package-wide.
)

// newBootstrapTestCommand builds a lightweight "git clone" command tree
// (mirroring the real gitCmd/cloneCmd parent/name relationship) with the same
// --ci/--all bool flags the real clone command registers, then parses rawArgs
// through it exactly as Cobra would before invoking PersistentPreRun/RunE.
// Returns the clone leaf and the resulting positional args.
func newBootstrapTestCommand(t *testing.T, rawArgs []string) (*cobra.Command, []string) {
t.Helper()

root := &cobra.Command{Use: "atmos"}
git := &cobra.Command{Use: gitCmd.Name()}
clone := &cobra.Command{Use: cloneCmd.Name()}
clone.Flags().Bool(flagCI, false, "")
clone.Flags().Bool(flagAll, false, "")
root.AddCommand(git)
git.AddCommand(clone)

require.NoError(t, clone.ParseFlags(rawArgs))
return clone, clone.Flags().Args()
}

func withCleanATMOSCIEnv(t *testing.T, value string) {
t.Helper()
if value == "" {
original, wasSet := os.LookupEnv("ATMOS_CI")
os.Unsetenv("ATMOS_CI")
t.Cleanup(func() {
if wasSet {
_ = os.Setenv("ATMOS_CI", original)
return
}
os.Unsetenv("ATMOS_CI")
})
return
}
t.Setenv("ATMOS_CI", value)
}

func TestCICloneBootstrapRequested(t *testing.T) {
tests := []struct {
name string
rawArgs []string
ciDetected bool
atmosCIEnv string
wantRequest bool
}{
{
name: "no CI provider detected",
rawArgs: nil,
ciDetected: false,
wantRequest: false,
},
{
name: "CI detected, no args, auto mode",
rawArgs: nil,
ciDetected: true,
wantRequest: true,
},
{
name: "explicit --ci=false opts out",
rawArgs: []string{"--ci=false"},
ciDetected: true,
wantRequest: false,
},
{
name: "ATMOS_CI=false opts out",
rawArgs: nil,
ciDetected: true,
atmosCIEnv: "false",
wantRequest: false,
},
{
name: "ATMOS_CI uppercase TRUE is accepted (no drift vs pflag ParseBool)",
rawArgs: nil,
ciDetected: true,
atmosCIEnv: "TRUE",
wantRequest: true,
},
{
name: "positional argument disqualifies bootstrap",
rawArgs: []string{"my-repo"},
ciDetected: true,
wantRequest: false,
},
{
name: "native args after -- disqualify bootstrap",
rawArgs: []string{"--", "--no-tags"},
ciDetected: true,
wantRequest: false,
},
{
name: "--all disqualifies bootstrap",
rawArgs: []string{"--all"},
ciDetected: true,
wantRequest: false,
},
{
name: "invalid ATMOS_CI value defers to auto (RunE reports the real error)",
rawArgs: nil,
ciDetected: true,
atmosCIEnv: "sometimes",
wantRequest: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if tt.ciDetected {
t.Setenv("GITHUB_ACTIONS", "true")
} else {
t.Setenv("GITHUB_ACTIONS", "false")
}
withCleanATMOSCIEnv(t, tt.atmosCIEnv)

cmd, args := newBootstrapTestCommand(t, tt.rawArgs)
assert.Equal(t, tt.wantRequest, CICloneBootstrapRequested(cmd, args))
})
}
}

func TestCICloneBootstrapRequested_WrongCommand(t *testing.T) {
t.Setenv("GITHUB_ACTIONS", "true")
withCleanATMOSCIEnv(t, "")

root := &cobra.Command{Use: "atmos"}
terraform := &cobra.Command{Use: "terraform"}
plan := &cobra.Command{Use: "plan"}
root.AddCommand(terraform)
terraform.AddCommand(plan)

assert.False(t, CICloneBootstrapRequested(plan, nil))
assert.False(t, CICloneBootstrapRequested(nil, nil))
}
Loading
Loading