github cloudposse/atmos v1.222.0-rc.1

pre-release6 hours ago
feat: terminal steps - tty/interactive fields and exec step type @osterman (#2602) ## what

Terminal steps for custom commands and workflows — three related capabilities:

  • interactive: true — attach host stdin and let the step own Ctrl-C. Atmos suspends its SIGINT-exit handler while the step runs (new pkg/signals suspension registry consulted by the main.go signal handler).
  • tty: true — allocate a pseudo-terminal (reusing pkg/terminal/pty, same engine as atmos devcontainer attach). The command sees a real TTY; secret masking is applied to PTY output. With interactive: true, the host terminal switches to raw mode so Ctrl-C flows through the PTY to the child.
  • type: exec — replace the Atmos process entirely (shell exec semantics): execve of the system shell on Unix (env, working directory, and terminal inherited natively; ATMOS_SHLVL unchanged), spawn-and-propagate-exit-code emulation on Windows. Validated to be the final step; tty/interactive/retry/timeout/output are rejected on exec steps.

Architecture: all logic lives in narrow packages — pkg/process (RunShellStep routing, RunShellSession, ReplaceShellSession), pkg/schema (validation), pkg/signals (interrupt suspension). cmd/ and internal/exec contain only inline switch-case call sites; pkg/runner and the step handler share the same routing.

Also fixes in pkg/terminal/pty found along the way:

  • stdin copier no longer blocks completion (it's detached, docker-CLI pattern)
  • session teardown is bounded: when grandchildren (e.g. aws ssm's session-manager-plugin) keep the PTY slave open after the child exits, output drains on a 1s deadline instead of hanging with the terminal in raw mode
  • DisableStdinForward for -t-without--i semantics

why

Custom commands had no way to hand the terminal to an interactive process:

commands:
  - name: ssh
    steps:
      - type: shell
        command: "exec aws ssm start-session --target {{ .Arguments.instance_id }}"

ran the SSM session as a piped, masked subprocess: full-screen rendering broke, and Ctrl-C inside the session killed Atmos itself (global SIGINT handler exits 130), killing the orphaned session with SIGPIPE.

With this change:

commands:
  - name: ssh
    steps:
      - type: shell
        tty: true
        interactive: true
        command: "aws ssm start-session --target {{ .Arguments.instance_id }}"

behaves like docker run -it (supervised: masking preserved, more steps can follow), and:

      - type: exec
        command: "aws ssm start-session --target {{ .Arguments.instance_id }}"

hands the process over entirely (launcher: native job control, zero proxy overhead, must be the last step).

references

  • Reported in SweetOps Slack (SSM session via custom command gets a mangled terminal and dies with SIGPIPE on Ctrl-C); teardown hang + raw-terminal-after-exit reproduced live on this PR and fixed
  • Docs: Interactive and TTY Steps

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added tty and interactive options for shell steps, and introduced exec step type for process replacement.
  • Behavior
    • Improved terminal/TTY handling, including Ctrl-C ownership, PTY session behavior, and more reliable exit-code propagation.
    • “Silent” exit codes now skip themed error rendering.
  • Validation
    • Enforced exec steps must be the final step and disallow incompatible fields; improved error hints.
  • Documentation
    • Updated workflow/CLI docs and added a blog post with usage guidance.
  • Tests / Fixes
    • Expanded coverage for shell sessions, PTY stdin forwarding/teardown, exec replacement, signals, and schema validation.
feat(secrets): declarative secrets management with !secret, CRUD CLI, and masking @osterman (#1911) ## what

Implements the Secrets Management PRD end to end — a GitOps-friendly, multi-cloud secrets workflow built on top of the existing store registry (not a parallel backend). Secrets are declared in stack config (committed to git) and their values live in a cloud secret backend or a SOPS-encrypted file, managed with a Vercel-like CLI and resolved at runtime with a new !secret YAML function.

Stores (pkg/store)

  • StoreConfig gains secret: true (subsystem membership) and kind (cloud/thing) with legacy type mapping; !store against a secret: true store is now an error ("use !secret").
  • New DeletableStore / StatusStore / SecretAwareStore interfaces; AWS SSM writes SecureString when used as a secret backend and gains Delete/Has.
  • New store backends: AWS Secrets Manager and HashiCorp Vault (KV v2). Registry refactored to a table-driven builder map; kindtype compatibility.

Secrets core (pkg/secrets)

  • service, declaration registry, resolver, validator, kinds, and a leaf pkg/secrets/providers/ subpackage with a store-adapter (track 1) and a native SOPS provider (track 2: age/aws-kms/gcp-kms/gpg).
  • SOPS providers can be defined in atmos.yaml, globally in a stack (secrets: top-level merges into every component), or under a single component.

!secret + masking (the headline)

  • !secret NAME [| path ...] [| default ...] wired into the live YAML pipeline, with automatic masker registration.
  • Mask-without-retrieval: inspection commands (describe, list) resolve !secret to <MASKED> without contacting the backend when masking is on (the default) — so you can inspect a stack with no cloud credentials. Value-producing commands (secret get, terraform plan/apply) always retrieve; --mask/ATMOS_MASK only controls redaction of display output.
  • Sensitive Terraform outputs (sensitive = true) auto-register with the masker as they flow through !terraform.output / atmos.Component() / describe.

CLI (cmd/secret)

init, set (alias add), get, delete (alias rm), list, pull, push, import, validate.

Stack processing

secrets is now a first-class inheritable component section, plus a global stack-level secrets: block that merges into every component.

Docs + example

  • Full Docusaurus docs: atmos secret overview + all 9 subcommands, secrets configuration page, !secret function page; blog post (with an embedded example) and a roadmap milestone.
  • examples/sops-secrets/ — fully self-contained, age-encrypted, no cloud credentials. Bundled atmos test custom command (.atmos.d/test.yaml) proves the full lifecycle end to end (set → encrypted-at-rest → get → list → validate → masked-without-credentials inspection → reveal-needs-key).

why

There was no unified way to manage human-provisioned secrets in Atmos — stores were designed for machine-written Terraform outputs, and the historical workaround (Chamber) was AWS-only. This adds explicit, declarative secret registration so a secret must be declared before it can be set, read, or resolved, and makes "inspect a stack" decoupled from "authenticate to the secret backend."

references

  • PRD: docs/prd/secrets-management.md and docs/prd/secrets-masking/
  • Example: examples/sops-secrets/ (run atmos test)

notes / follow-ups

  • Fixed a pre-existing init-ordering bug where the global --mask=false flag did not disable the early-initialized I/O masker (only ATMOS_MASK=false did). io.ReconcileMasking() now reconciles the masker after flags are parsed, so --mask=false and ATMOS_MASK=false behave identically.
  • pkg/store backend implementations could be moved into a pkg/store/providers/ subpackage (mirroring pkg/secrets/providers/) — deferred to a dedicated follow-up PR since it touches ~30 external call-sites.
  • Base-component (metadata.component) inheritance of the secrets section is not wired yet (component-level + import: + global-stack inheritance all work).

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • First-class secrets management CLI: secret init, set/add, get, delete/rm, list, pull, push, import, validate, plus exec, shell, and keygen.
    • SOPS-backed secrets with collision-safe placement and secure inspection by default.
    • Terraform integration: secret values are injected via TF_VAR_*, and secret-bearing values are omitted from generated varfiles by default (with optional shell export).
  • Documentation
    • Added PRDs/blog and updated secrets examples and masking guidance.
  • Bug Fixes
    • Masking behavior now consistently follows --mask across inspection-style commands.
  • Tests
    • Expanded unit/integration coverage for secrets, masking, Terraform, and providers.
  • Chores
    • Updated license inventory and CI/test reliability tweaks.

Don't miss a new atmos release

NewReleases is sending notifications on new releases.