Skip to content

Commit 7fc9c3e

Browse files
ostermanclaudeatmos-pro[bot]aknysh
authored
feat(secrets): declarative secrets management with !secret, CRUD CLI, and masking (#1911)
* docs: Add secrets management PRD Adds comprehensive PRD for GitOps-friendly, multi-cloud secrets management in Atmos with Vercel-like developer experience. Covers declarative secret declarations, CRUD CLI commands (init, add, get, rm, list, pull, push, validate), integration with existing store and auth infrastructure, and support for AWS SSM, ASM, SOPS, Vault, Azure Key Vault, and GCP Secret Manager backends. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com> * docs: Use set/get/delete as primary commands with add/rm aliases 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * docs: Fix wording - secrets use dedicated CRUD commands, not standalone CLI * docs: Clarify store lifecycle - populated by Terraform outputs * docs: Add atmos secret import command for env file import Adds import command that creates declarations and sets values from .env/JSON/YAML files. Includes prior art research from Doppler, Chamber, and Vercel. Key differences: - push: requires pre-declared secrets (fails on undeclared) - import: creates declarations as needed (bootstrap workflow) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * docs: Fix import command - warns on undeclared, doesn't create declarations * docs: Use providers pattern consistent with auth in secrets PRD Rename all occurrences of "backend/backends" to "provider/providers" and restructure defaults to match auth patterns from auth-default-settings.md. Changes: - `secrets.default_backend` → `secrets.defaults.provider` - `secrets.backends` → `secrets.providers` - `backend:` → `provider:` in secret declarations - Backend → Provider in headings and descriptions - Add references to I/O handling strategy and auth defaults PRDs 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * docs: Use spec instead of options for provider config consistency Rename provider configuration field from `options` to `spec` for consistency with the logs PRD (atmos-logs.md) pattern. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * docs(prd): require io masking for list/describe output * docs(prd): Add sensitive terraform output handling PRD Addresses gap in secrets management PRD for machine-generated sensitive outputs flowing between components via !terraform.output and atmos.Component(). Terraform already provides sensitive metadata; Atmos I/O masking layer is ready — this PRD wires them together. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs(prd): Move sensitive terraform outputs PRD to secrets-masking/ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs(prd): Fix broken relative links after move to secrets-masking/ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs(prd): Promote store sensitivity awareness from future to in-scope Store sensitivity (SSM SecureString, retrieval-side masking) is part of the same pipeline as sensitive terraform outputs, not a separate concern. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs(prd): Extract store sensitivity into separate PRD Store sensitivity awareness (SecureString, retrieval-side masking) is a distinct concern from the terraform output pipeline. Split into its own PRD at secrets-masking/store-sensitivity.md. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> * docs(prd): Unify secrets backend on stores, collapse masking flag, address review Refine the secrets-management PRD set following PR #1911 review: - Secret stores via a top-level `secret: true` flag on existing store types (not a `type: secrets` wrapper); `!store` is refused on them, `!secret` is the only accessor. Two-track backend model: store-backed (SSM/ASM/Vault/KV/GSM) reuses the store registry; SOPS stays a native non-store provider. - Secrets declared in stack config only ("global" = shared import), so every secret keeps a real (stack, component, key) coordinate. - Collapse the proposed `--secrets` flag into the existing `--mask`: inspection commands skip retrieval (no credentials) when `--mask=true`; value-producing commands always retrieve. - Auth identity integration: top-level `identity:` on stores resolved via pkg/auth (supersedes read/write_role_arn); precedence with inheritance of the component instance's effective identity; SOPS-KMS-only identity. Address CodeRabbit review comments: - Register sensitive structured (map/list) outputs via a recursive helper, not just strings (sensitive-terraform-outputs, store-sensitivity). - Gate raw secret exposure on `--mask=false` with a mandatory warning. - Define per-provider sensitivity-metadata source (tags/labels) so GetWithSensitivity is deterministic, with a fail-safe fallback. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(prd): Fix broken Deployments PRD link in secrets-management The link checker failed because the relative link [Deployments PRD](docs/prd/deployments/problem-statement.md) resolved to the doubled path docs/prd/docs/prd/deployments/problem-statement.md, and that PRD only lives on the origin/deployments-prd branch (not in this branch). Reference it as an inline-code path with a branch annotation, matching the existing citation in the References section. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(prd): Address CodeRabbit review on secrets PRDs Round-2 review feedback on PR #1911: - secrets-management.md: document identity-resolution observability (INFO log of the resolved identity + its pinning source, --dry-run surfacing, and an --explain flag for the full 4-level precedence chain) in the identity notes and Phase 2 deliverables, so the precedence cascade is debuggable and auditable. - secrets-management.md: add a language ("text") to unlanguaged fenced blocks (MD040) flagged by markdownlint, including the precedence diagram. - store-sensitivity.md: explain why the reserved sensitivity key separator differs (colon for AWS Secrets Manager tags vs hyphen for Azure/GCP, since GCP label keys disallow colons). - store-sensitivity.md: clarify that the SSM String fallback to sensitive=false is intentional — the Type field is authoritative and SecureString has always been the mechanism for sensitive data. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(secrets): declarative secrets management with !secret, CRUD CLI, and masking Implements the Secrets Management PRD end to end: a GitOps-friendly, multi-cloud secrets workflow built on the existing store registry. Stores (pkg/store): - StoreConfig gains `secret: true` (subsystem membership) and `kind` (cloud/thing) with legacy `type` mapping; `!store` against a secret store is an error. - New DeletableStore/StatusStore/SecretAwareStore interfaces; SSM writes SecureString when secret + adds Delete/Has. - New backends: AWS Secrets Manager and HashiCorp Vault (KV v2). - Registry refactored to a table-driven builder map. Core (pkg/secrets): service, declaration registry, resolver, validator, kinds, and a leaf providers subpackage (pkg/secrets/providers) with a store-adapter (track 1) and a native SOPS provider (track 2). SOPS providers may be defined in atmos.yaml, globally in a stack, or under a component. !secret + masking (pkg/io, internal/exec): - !secret wired into live YAML dispatch with path/default modifiers and auto-masking. - Inspection commands (describe/list) resolve !secret to <MASKED> WITHOUT retrieval (no credentials); value-producing paths always retrieve. Adds io.MaskingEnabled() and recursive io.RegisterSecretValue(). - Sensitive Terraform outputs (sensitive=true) auto-register with the masker. CLI (cmd/secret): init, set (add), get, delete (rm), list, pull, push, import, validate. Stack processing: `secrets` is now a first-class inheritable component section that also merges a global stack-level `secrets:` block into every component. Docs + example: full Docusaurus docs (overview, 9 subcommands, config, !secret), blog post (with embedded example), roadmap milestone, and examples/sops-secrets with a runnable `atmos test` custom command proving the full lifecycle (age-encrypted, no cloud creds). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(secrets): make `atmos test` custom command re-invoke the same atmos binary Custom command steps called a bare `atmos`, which resolves via PATH to a possibly stale or absent binary — so `./build/atmos test` ran the steps against a different (old) atmos that lacked the `secret` command and `!secret` function. - Inject ATMOS_CLI_PATH (os.Executable()) into custom command step environments so steps can re-invoke the SAME running atmos binary. - examples/sops-secrets `atmos test` now uses "${ATMOS_CLI_PATH:-atmos}" for all atmos calls, so `./build/atmos test` passes end-to-end without atmos on PATH. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(secrets): render `secret list` via pkg/list; add --help usage examples - `atmos secret list` now renders through the pkg/list pipeline (column/sort/ renderer) instead of a hand-rolled table: theme-aware on TTY, delimited when piped, with a `--format` flag (table/json/yaml/csv/tsv). - Add embedded `cmd/markdown/atmos_secret*_usage.md` usage examples so `atmos secret [sub] --help` shows worked examples (parent + all 9 subcommands). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(io): make --mask=false reliably disable masking (init-ordering bug) The global masker is created the first time any output is produced, which can happen before CLI flags are parsed — at which point viper.GetBool("mask") returns the flag's default (true), not --mask=false. Because Initialize() is guarded by sync.Once, the later PersistentPreRun call was a no-op and the masker kept masking enabled, so --mask=false had no effect (only ATMOS_MASK=false, readable at early-init time, worked). - Add Masker.SetEnabled and io.ReconcileMasking() which re-reads the resolved masking config (flag -> env -> atmos.yaml -> default) and updates the global masker after flags are parsed. - Call io.ReconcileMasking() in root PersistentPreRun after Initialize(). - Read masker enabled state under the lock in Mask() (race-safe with SetEnabled). - Regression tests for the reconcile behavior and SetEnabled. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(secrets): clarify import routing; silence experimental notice in example test; structured test steps - `atmos secret import`: clarify (help + import.mdx + usage example) that each key is written to its OWN declared backend (the store:/sops: of its secrets.vars entry); there is no single destination and mixed backends are routed automatically. - examples/sops-secrets `atmos test`: set ATMOS_EXPERIMENTAL=silence in the command env so the experimental notice doesn't clutter the proof output; adopt structured custom-command step types (style/toast) and re-invoke the same binary via $ATMOS_CLI_PATH. - README: note that masking toggles via the --mask flag or ATMOS_MASK env var. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(secrets): add 1Password store backend with full CRUD 1Password is now a first-class Track-1 secret store (pkg/store/onepassword_*), flowing through the existing secrets subsystem (store: declarations, !secret, the atmos secret CLI) with no dispatch changes. - Dual backend, auto-selected by 1Password's own env convention: native SDK (OP_SERVICE_ACCOUNT_TOKEN, local dev) and Connect REST (OP_CONNECT_HOST/TOKEN, CI/cloud). No `op` CLI required. - Reference-based addressing: each declared secret carries a Go-templated op://vault/item/field reference (atmos_stack/atmos_component + sprig), via a new generic `reference` field on secret declarations (pkg/schema, pkg/secrets). - Full CRUD: get/set/delete/list/validate. set upserts the field (creating the item as an API Credential if missing); delete removes the field and the item once empty. Idempotent deletes. - `secret: true` is implied for type: onepassword (ApplySecretDefaults). - Pinned onepassword-sdk-go to v0.3.1: v0.4.0+ fails to compile under CGO_ENABLED=0 (desktop-integration guard); fix is on the SDK's main but untagged. Includes ongoing secrets-management work on this branch (SOPS age_key_file, secret exec/shell/env, auth, pkg/shell, docs, snapshots). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(auth): pin keyring storage contract before pkg/keyring extraction Add characterization tests that lock the exact on-disk/in-keychain layout (realm-scoped key strings, zalando service/account args, credential-envelope JSON shape, realm isolation) so the upcoming extraction of the keyring backends into a generic pkg/keyring package is provably non-breaking for existing users' stored credentials. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * refactor(keyring): extract generic pkg/keyring; refactor auth onto it Add pkg/keyring: a credential-agnostic key->string-value secret store with system (OS keychain), file (encrypted), memory, and noop backends. Backend selection lives in keyring.New; fallback policy is left to callers so a durable-write store can fail loudly instead of silently using noop. Refactor pkg/auth/credentials to a single keyringCredentialStore over pkg/keyring, keeping the credential-specific layer (realm-scoped keys, typed credentialEnvelope, expiry) and identical on-disk/keychain layout. The storage-contract characterization tests pass unchanged, proving no breakage for existing stored credentials. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(secrets): add GitHub Actions secrets store, `secret get --raw`, and OIDC alignment diagnostics GitHub Actions secrets store (kind: github/actions): a "native CI" backend that writes/lists/deletes secrets via the GitHub API (sealed-box encryption with golang.org/x/crypto/nacl/box) and reads values from the runner environment, gated by CI detection (options.ci.enabled). Flows through the existing store-backed secrets provider; registered as secret-by-default. - `secret get --raw`/`-r`: print the raw value with no trailing newline (text only) for piping (e.g. `| pbcopy`); mutually exclusive with `--format=json|env`. - Enriched github/actions read errors naming the configured environment and the `secrets.NAME` -> `env:` mapping requirement. - New stdlib-only pkg/github/oidc leaf: decode GitHub Actions OIDC token claims (repository/environment) to warn (never fail) on repo/environment mismatch between the runner and the store config. - Docs: stores.mdx (github/actions backend + OIDC alignment), secret get.mdx (--raw). This checkpoint also includes coherent secrets-management work from parallel sessions on the same branch: keychain store backend (pkg/store/keychain_store.go), completion of the pkg/keyring extraction (drop perf.Track to avoid the perf->store->keyring import cycle, with a matching lintroller exclusion), and SOPS keyring-sourced age identities. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(secrets): SOPS in-process key generation + age key from a store Close the SOPS onboarding cliff (Atmos could encrypt/decrypt but never generate an age key) and let a vault source its key from a store. Key generation (generic, registry-pattern): - `atmos secret keygen [VAULT]` dispatches to any provider implementing the new backend-agnostic `KeyGenerator` capability; backends that don't support it report a friendly "not implemented" message. The SOPS (age) provider generates an identity in-process (filippo.io/age) and records each half where SOPS already looks — private to the vault's key source (its key file, the sops-standard keys file, or a configured store), public recipient to a `.sops.yaml` creation rule (yaml.Node merge preserving other rules). Sinks mirror the vault's configured source; KMS/GPG kinds report not-supported. - `atmos secret init` auto-offers keygen for any key-generating vault missing a key. Age key from a store: - `spec.age_key: { store, path, value }` (back-compat with bare-string `age_key` and `age_key_file`). The provider reads — and keygen writes — the key via a provider-owned store triple, so a `keychain` (or other) store can hold the private key. Precedence: value > store > file > SOPS_AGE_KEY_FILE/SOPS_AGE_KEY. Also: net-new branch lint cleanup (godot, err113, unparam, add-constant, function-result-limit, hugeParam, file-length) across the secrets/keygen, keychain store, GitHub Actions store, and terraform CI-hook code. Docs: secret/keygen.mdx; secrets.mdx. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * [autocommit] formatting fixes * ci: raise build job timeout to 30m to avoid Windows cache-save cancellation The `Build (windows)` job was being cancelled at exactly 15 minutes. The build, version check, and artifact upload all succeed, but the post-job Go cache save (tar + zstd of GOMODCACHE/GOCACHE via setup-go) takes ~6 minutes on the Windows runner. With the larger dependency set, total job time crossed the 15-minute limit and the run was cancelled (not a build/test failure). Raise `timeout-minutes` for the build job from 15 to 30 to give headroom for the build plus the slow Windows cache save. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * docs(secrets): user-voice blog rewrite, PRD reconciliation, and roadmap/homepage features Blog (2026-06-05-secrets-management.mdx): - Rewrite in a user-facing voice (Vercel/Heroku framing, declared registry, per-environment provisioning), no internal jargon or hyperbole. - Add the identity+secrets differentiator (SSO/OIDC/roles), the components-auto-inject vs `secret exec`/`shell` distinction, and a note that this was a most-requested feature we deliberately took time to get right. Example README (examples/sops-secrets): trim to an embed-friendly length and reframe "End-to-end proof" as "Give it a spin". PRD reconciliation (docs/prd/secrets-management.md): document features that shipped after the original PRD — `atmos secret keygen` (in-process SOPS key generation; age key to a file or a store), `atmos secret exec`/`shell`, `secret get --raw`, the secret-by-default kinds concept, and the 1Password / 1Password Connect, keychain, and GitHub Actions backends. Roadmap + homepage: feature Secrets Management. Replace the "Server-Side Commits" featured card in roadmap.js and the "Smart Scaffolding" card on the homepage with Secrets Management. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(utils): add !secret tag to TestAtmosYamlTagsMap_ContainsAllTags The !secret YAML function (AtmosYamlFuncSecret) was registered in both AtmosYamlTags and atmosYamlTagsMap (27 tags) but the test's expectedTags list still enumerated only 26, failing the len() equality assertion and breaking the Acceptance Tests (linux + macos) CI jobs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(secrets): cover stack-scoped vs component-scoped SOPS storage Add two white-box tests for the sopsProvider that lock in how the spec.file template determines secret scope: - StackScopedSharing: a template without {{ .atmos_component }} makes all components in a stack share one encrypted file, so a same-named key written by a second component overwrites the first (stack-scoped). - ComponentScopedIsolation: a template with {{ .atmos_component }} gives each component its own file with independent values, and a sibling component cannot read another's key (component-scoped, both directions). Adds a newAgeProviderWithFile helper mirroring newAgeProvider; runs fully in-process (no sops binary, no fixtures, no cloud creds). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(secrets): add secrets to stack JSON schema + section-coverage drift guard The secrets feature shipped without updating the stack JSON Schema, so the component-level `secrets:` block (additionalProperties:false) was rejected by editors and not validated by `atmos validate stacks`. Model `secrets` in all three manifest schema copies (website, tests/fixtures, embedded), mirroring how `auth` is wired, and fix the pre-existing `auth` drift in the embedded schema (was only on the terraform component manifest). Add a drift guard so this can't silently recur: - schema_section_coverage_test.go: AST-parses pkg/config/const.go, requires every *SectionName constant to be classified as a manifest section (must be in the schema) or non-manifest. Unclassified constants fail the build; pre-existing gaps are tracked in an explicit knownSchemaGaps allowlist. - schema_secrets_validation_test.go: validates real component/stack-level secrets configs against the embedded schema and rejects malformed ones. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(secrets): enforce XOR backend (store|sops) in secret_declaration schema The secret_declaration schema documented "exactly one backend reference (store or sops) is set" but accepted both or neither, leaking invalid configs past schema validation into runtime ambiguity (both silently resolves to sops; neither only surfaces ErrNoBackend at use time). Add a nested oneOf enforcing the XOR across all three synchronized schema copies (embedded, website, test fixture), plus negative-path test cases for the both-backends and no-backend rejections. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(secrets): skip Unix perm assertion on Windows in keys-file test TestAppendIdentityToKeysFile asserted the keys file is mode 0o600, which fails on Windows: Go does not honor Unix permission bits there and reports 0o666 for any writable file. Guard the perm assertion with runtime.GOOS != "windows", matching the existing convention in pkg/auth/cloud/gcp/files_test.go. The implementation already creates the file 0o600; only the assertion was platform-specific. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * ci(codecov): use PyPI CLI to bypass broken Keybase GPG key import Codecov uploads started failing CI with "Could not verify signature. Please contact Codecov if problem continues". The action wrapper fetches Codecov's GPG public key from https://keybase.io/codecovsecurity/pgp_keys.asc to verify the downloaded CLI binary, but that URL is currently returning 404 ("SELF-SIGNED PUBLIC KEY NOT FOUND"). The empty import yields "gpg: no valid OpenPGP data found", so the signature check has no key and the step exits 1 (amplified by our fail_ci_if_error: true). This is a live Codecov/Keybase-side outage hitting many repos, not a change in ours. Add use_pypi: true so the Codecov CLI is installed from PyPI instead of cli.codecov.io, avoiding the broken keyserver fetch and restoring uploads. Repros on both v5.5.4 and v6.0.1 (verification path unchanged), so a major bump does not help. Safe to remove once Codecov restores the Keybase key. Ref: codecov/codecov-action#1955 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(secrets): raise coverage to ~80%+ across secrets packages Patch coverage on the secrets-management branch was failing the Codecov gate. This brings the new secrets code over the 80% line: cmd/secret 18.3% -> 80.4% pkg/secrets 64.5% -> 90.8% pkg/secrets/providers 79.1% -> 81.6% pkg/store 55.7% -> 80.7% cmd/secret: add a minimal, behavior-preserving DI seam (deps.go) so the command handlers are unit-testable. A `secretService` interface (which *secrets.Service satisfies structurally) plus overridable package-level seam vars (loadServiceFn, loadServiceAndConfigFn, promptForValueFn, confirmActionFn, runCommandFn, startShellFn) let tests inject a fake without constructing real config/auth. No exported API or runtime behavior changed; the handlers now call the seam vars. Added a fakeSecretService and per-handler/helper tests. pkg/secrets, pkg/secrets/providers, pkg/store: tests only. Added validator_test, uncovered service methods (VaultsMissingKeys, GenerateKeyForVault, DeleteAll, Reset, IsDeclared), resolver helpers, and store/provider branches. Covered the SDK-wrapper clients via same-package httptest/fake injection below the wrapper (real go-github client against httptest with NaCl-box round-trip; fake connect.Client for 1Password Connect). Verified: build, vet, custom golangci-lint (--new-from-rev, 0 issues), full affected-package suite with -shuffle=on, and the examples/sops-secrets end-to-end `atmos test` proof. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(secrets): address CodeRabbit review nits on PR #1911 - import_test: use t.TempDir() path instead of hardcoded Unix path - push_test: assert imported value (not just key) in JSON format test - exec_handler_test: drop Unix binary tokens (env/false) for portable placeholder - set_test: close pipe reader in cleanup to avoid FD leak - resolver_helpers_test: add compile-time sentinel for schema.ConfigAndStacksInfo fields - onepassword_client_test: split fake vault maps so title-fallback branch is actually exercised Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(secrets): raise patch coverage to ~89% across secrets/store/keyring - Refactor 1Password sdkClient behind an opSDKAPI interface with a production adapter; add an in-memory fake (sdkClient body 0 -> ~95%). - Introduce accessible-mode huh testing for masked prompts: confirm prompts via a plain reader, masked/password inputs via a creack/pty pair (Windows-skipped). Covers cmd/secret/prompt.go and pkg/keyring newPasswordPrompt for the first time. - Cover sops.go / sops_keygen.go error paths, oidc.go via httptest, and the AWS Secrets Manager lazy-init/identity paths via the existing fake. - Cover shared.go loaders (loadService/loadServiceAndConfig/buildAuthManager) with a minimal in-temp-dir atmos fixture, plus runSecretKeygen via a config seam and the missing parseScope component branch. - Add package-level test seams (restored via t.Cleanup): runForm (cmd/secret), stdinIsTerminal/runPasswordForm (keyring), loadKeygenConfig (keygen). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(secrets): add scope resolution, SOPS collision detection, and enumerate command Adds scope tagging/derivation for `secrets:` declarations (scope.go), SOPS-backed collision detection guarding the advanced spec.file template path (collision.go), and a `secret enumerate` command (enumerate.go). Includes supporting updates across cmd/secret, pkg/secrets, stack processing, schemas, PRD, and website docs, plus expanded test coverage. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(ui): left-align Yes/No buttons in confirmation prompts huh's Confirm field defaults buttonAlignment to lipgloss.Center, which is a per-field option rather than a theme property. Add a shared uiutils.NewAtmosConfirm() constructor that bakes in left alignment and route all confirmation prompt call sites through it so the Yes/No buttons line up flush-left under the title and footer. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(secrets): keep resolved secrets out of Terraform varfiles on disk Resolved !secret values were serialized verbatim into the generated *.terraform.tfvars.json varfile and left orphaned on disk in plaintext. Secret-bearing variables are now detected via the masker (independent of the --mask display flag) and routed to TF_VAR_<name> environment variables at runtime instead of the varfile. Detection is value-based, so secrets composed into larger strings or nested in maps/lists are caught too. - pkg/terraform/tfvars: Partition(vars, isSecret) + SecretEnv(secret) - pkg/io: Masker.ContainsSecret + global io.ContainsSecret - exec: strip secrets from varfile, inject TF_VAR_* into ComponentEnvList - terraform shell / generate varfile: opt-in --with-secrets (ATMOS_WITH_SECRETS) - generate varfiles (batch): always strips secrets - generate varfile: emit UI success with relative path; "(with secrets)" suffix - tests, PRD, blog, and CLI docs Bundles in-progress secrets coverage tests and stores/secrets doc edits. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(secrets): cover enumerate/list/scope paths to lift patch coverage Raise patch coverage on the changed secrets files above the 80% Codecov target by exercising the remaining 0%/low-coverage functions: - enumerateSecretScopes: drive the real config + describe-stacks pipeline in-process (0% -> 90.9%); covers the file Codecov flagged at 6.49%. - runSecretList: single-scope, load-error, and enumerated paths (70% -> 85%). - sopsProvider.SupportsScope: scope acceptance/rejection (0% -> 100%). - checkScopeSupported: rejection branch via a fake provider, since no real backend rejects a valid scope (66.7% -> 100%). Tests only; no production changes. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(secrets): cover store-hook writes to an encrypted keychain store Add a credential-free, server-free end-to-end test proving an after-terraform-apply store hook writes a Terraform output into an encrypted-at-rest secrets store (the keychain `file` backend). The test deploys a component, then asserts the hook's write round-trips through the store API and that the on-disk keyring file is encrypted (the plaintext never appears). Isolation is env-driven: XDG_DATA_HOME points the keyring file at a temp dir and ATMOS_KEYRING_PASSWORD drives the file backend non-interactively. Skips gracefully when no terraform binary is available. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * feat(secrets): guard non-encrypting secret stores, propagate custom-component env, add helmfile SOPS fixture - store: reject `secret: true` on backends that cannot encrypt at rest (Redis, Artifactory) via ErrSecretBackendNotEncrypted, so secrets are never persisted in plaintext. - cmd: export a custom component's resolved `env` section (including resolved `!secret` values) into the step subprocess environment, mirroring the built-in terraform/helmfile/packer providers. - tests: add a self-contained SOPS-encrypted helmfile secrets scenario fixture and helmfile_secrets integration test (fixture age key). - docs/examples: document the secret-store encryption requirement, custom-command env propagation, and component secrets usage. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * fix(ci): exclude flaky tldp.org link and use t.Setenv in keychain test - link-check: exclude tldp.org from lychee — its exit-codes reference returns intermittent 502 Bad Gateway in CI, matching the existing exclusions for the gnu.org/openbsd/LSB exit-code references. - test: replace os.Unsetenv("GITHUB_ACTIONS") with t.Setenv(..., "") in the keychain integration test for test-scoped, auto-restored isolation (CodeRabbit review on PR #1911). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * test(cmd): capture custom-component step env via Go-native helper, not platform binaries Addresses CodeRabbit review on PR #1911 for the component-env-export test: - Replace the platform-specific `env` / `cmd /c set` step command (and its runtime.GOOS branch) with the test binary itself in env-dump helper mode: TestMain writes the step subprocess environment to the file named by _ATMOS_TEST_DUMP_ENV and exits, mirroring the existing _ATMOS_TEST_EXIT_ONE helper. This satisfies the repo rule against platform-specific test binaries. - Stop logging the full captured environment (which could leak CI tokens); log only the asserted DEPLOY_REGION/APP_VERSION keys. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> * [autocommit] formatting fixes * feat(secrets): add scope: global and terraform-import-style secret adoption One secret value shared by many consumers is a scope problem, not an addressing problem. Complete the scope ladder (instance -> stack -> global): a global declaration's coordinate omits both the stack and component segments ({prefix}/{NAME}), so every importer of a shared catalog fragment converges on the same backend path by construction. An explicit `scope: global` survives the positional stamp at either declaration position; SOPS declares no global support yet (file placement is scope-keyed), gated by SupportsScope. Migration from legacy `!store <store> <stack> <component> <key>` paths is a one-shot CLI adoption instead of permanent config vocabulary: `atmos secret import NAME --from-stack=... --from-component=...` copies the value from its legacy coordinate into the declaration's computed coordinate (like `terraform import`, the source is never modified or deleted). --from-store defaults to the declaration's own store, --from-key to the secret name, and the segments are raw path strings transcribed from the old expression. `import` keeps its FILE mode unchanged and becomes the general surface for bringing existing secrets under management. Store key builders (SSM, ASM, GSM, keychain) now omit empty stack/component segments instead of rejecting them, so scoped coordinates compose cleanly; key-only validation remains. list/enumerate dedupe global rows to one `*/*` row; set's success message names the shared blast radius for stack/global scopes. Both manifest JSON schemas gain the scope enum (the website copy was missing `scope` entirely). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> * docs(secrets): warn against committing SOPS age private keys in demo/fixture dirs Add README.md to both the sops-secrets example and the secrets-helmfile test fixture explaining that the committed age private key is a throwaway for self-contained demos only, and that real projects must keep the key out of the repo (SOPS_AGE_KEY_FILE / OS keychain) and commit only the encrypted file. Add a matching "DEMO KEY ONLY" warning header to each keys.txt, and trim the unused DATADOG_API_KEY/REDIS_URL entries from the example's dev.enc.yaml. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Haiku 4.5 <noreply@anthropic.com> Co-authored-by: atmos-pro[bot] <173522224+atmos-pro[bot]@users.noreply.github.com> Co-authored-by: Andriy Knysh <aknysh@users.noreply.github.com>
1 parent 6a90ec1 commit 7fc9c3e

308 files changed

Lines changed: 28199 additions & 1889 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/test.yml

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,13 @@ jobs:
4141
# ensure the code builds...
4242
build:
4343
name: Build (${{ matrix.target }})
44-
# windows-latest compiles this large dependency tree far slower than the
45-
# Linux (self-hosted, s3-cache) and macOS runners, and a cold Go build cache
46-
# after dependency changes pushes it past a tight limit. Give it headroom.
47-
timeout-minutes: 25
44+
# windows-latest is the slowest target: it compiles this large dependency
45+
# tree far slower than the Linux (self-hosted, s3-cache) and macOS runners,
46+
# and on top of that the post-job Go cache save (tar + zstd of
47+
# GOMODCACHE/GOCACHE) takes several minutes. A cold cache after dependency
48+
# changes plus that save pushes the job past a tight limit and the run gets
49+
# cancelled. Give the slowest target enough headroom for build + cache save.
50+
timeout-minutes: 30
4851
strategy:
4952
fail-fast: false
5053
matrix:
@@ -284,9 +287,13 @@ jobs:
284287
flags: unittests
285288
verbose: true
286289
# Install the Codecov CLI from PyPI instead of cli.codecov.io. The
287-
# default path fetches Codecov's GPG public key from Keybase, which is
288-
# currently returning 404 ("SELF-SIGNED PUBLIC KEY NOT FOUND").
289-
# See codecov/codecov-action#1955. Safe to remove once Codecov restores the key.
290+
# default path fetches Codecov's GPG public key from Keybase
291+
# (https://keybase.io/codecovsecurity/pgp_keys.asc) to verify the CLI
292+
# binary; that URL is currently returning 404 ("SELF-SIGNED PUBLIC KEY
293+
# NOT FOUND"), so the import yields "no valid OpenPGP data found" and
294+
# the run fails with "Could not verify signature". The PyPI path avoids
295+
# the broken keyserver fetch. See codecov/codecov-action#1955. Safe to
296+
# remove once Codecov restores the Keybase key.
290297
use_pypi: true
291298

292299
docker:

.golangci.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,10 @@ linters:
6464
- "!**/pkg/auth/identities/**"
6565
- "!**/pkg/auth/cloud/**"
6666
- "!**/pkg/auth/factory/**"
67+
# pkg/store is the cloud-backend storage layer (SSM, Secrets Manager, Vault,
68+
# Azure Key Vault, GCP Secret Manager); it legitimately uses cloud SDKs and is
69+
# not provider-agnostic auth code.
70+
- "!**/pkg/store/**"
6771
- "!**/pkg/auth/types/aws_credentials.go"
6872
- "!**/pkg/auth/types/github_oidc_credentials.go"
6973
- "!**/pkg/ai/**"

NOTICE

Lines changed: 121 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,14 @@ APACHE 2.0 LICENSED DEPENDENCIES
4141
License: Apache-2.0
4242
URL: https://github.com/googleapis/google-cloud-go/blob/iam/v1.11.0/iam/LICENSE
4343

44+
- cloud.google.com/go/kms
45+
License: Apache-2.0
46+
URL: https://github.com/googleapis/google-cloud-go/blob/kms/v1.31.0/kms/LICENSE
47+
48+
- cloud.google.com/go/longrunning
49+
License: Apache-2.0
50+
URL: https://github.com/googleapis/google-cloud-go/blob/longrunning/v1.0.0/longrunning/LICENSE
51+
4452
- cloud.google.com/go/monitoring
4553
License: Apache-2.0
4654
URL: https://github.com/googleapis/google-cloud-go/blob/monitoring/v1.29.0/monitoring/LICENSE
@@ -169,6 +177,10 @@ APACHE 2.0 LICENSED DEPENDENCIES
169177
License: Apache-2.0
170178
URL: https://github.com/aws/aws-sdk-go-v2/blob/service/internal/s3shared/v1.19.23/service/internal/s3shared/LICENSE.txt
171179

180+
- github.com/aws/aws-sdk-go-v2/service/kms
181+
License: Apache-2.0
182+
URL: https://github.com/aws/aws-sdk-go-v2/blob/service/kms/v1.51.1/service/kms/LICENSE.txt
183+
172184
- github.com/aws/aws-sdk-go-v2/service/organizations
173185
License: Apache-2.0
174186
URL: https://github.com/aws/aws-sdk-go-v2/blob/service/organizations/v1.51.3/service/organizations/LICENSE.txt
@@ -273,6 +285,10 @@ APACHE 2.0 LICENSED DEPENDENCIES
273285
License: Apache-2.0
274286
URL: https://github.com/docker/libkv/blob/458977154600/LICENSE.code
275287

288+
- github.com/dylibso/observe-sdk/go
289+
License: Apache-2.0
290+
URL: https://github.com/dylibso/observe-sdk/blob/9145d8ad07e1/go/LICENSE
291+
276292
- github.com/envoyproxy/go-control-plane/envoy
277293
License: Apache-2.0
278294
URL: https://github.com/envoyproxy/go-control-plane/blob/envoy/v1.37.0/envoy/LICENSE
@@ -281,6 +297,10 @@ APACHE 2.0 LICENSED DEPENDENCIES
281297
License: Apache-2.0
282298
URL: https://github.com/envoyproxy/protoc-gen-validate/blob/v1.3.3/LICENSE
283299

300+
- github.com/getsops/gopgagent
301+
License: Apache-2.0
302+
URL: https://github.com/getsops/gopgagent/blob/7044f28e491e/LICENSE
303+
284304
- github.com/go-git/go-billy/v5
285305
License: Apache-2.0
286306
URL: https://github.com/go-git/go-billy/blob/v5.9.0/LICENSE
@@ -321,6 +341,10 @@ APACHE 2.0 LICENSED DEPENDENCIES
321341
License: Apache-2.0
322342
URL: https://github.com/google/s2a-go/blob/v0.1.9/LICENSE.md
323343

344+
- github.com/google/shlex
345+
License: Apache-2.0
346+
URL: https://github.com/google/shlex/blob/e7afc7fbc510/COPYING
347+
324348
- github.com/google/wire
325349
License: Apache-2.0
326350
URL: https://github.com/google/wire/blob/v0.7.0/LICENSE
@@ -409,6 +433,10 @@ APACHE 2.0 LICENSED DEPENDENCIES
409433
License: Apache-2.0
410434
URL: https://github.com/opencontainers/image-spec/blob/v1.1.1/LICENSE
411435

436+
- github.com/opentracing/opentracing-go
437+
License: Apache-2.0
438+
URL: https://github.com/opentracing/opentracing-go/blob/v1.2.0/LICENSE
439+
412440
- github.com/petermattis/goid
413441
License: Apache-2.0
414442
URL: https://github.com/petermattis/goid/blob/df67b199bc81/LICENSE
@@ -461,6 +489,18 @@ APACHE 2.0 LICENSED DEPENDENCIES
461489
License: Apache-2.0
462490
URL: https://github.com/spiffe/go-spiffe/blob/v2.6.0/LICENSE
463491

492+
- github.com/tetratelabs/wabin
493+
License: Apache-2.0
494+
URL: https://github.com/tetratelabs/wabin/blob/f6f874872834/LICENSE
495+
496+
- github.com/tetratelabs/wazero
497+
License: Apache-2.0
498+
URL: https://github.com/tetratelabs/wazero/blob/v1.11.0/LICENSE
499+
500+
- github.com/tjfoc/gmsm
501+
License: Apache-2.0
502+
URL: https://github.com/tjfoc/gmsm/blob/v1.4.1/LICENSE
503+
464504
- github.com/tliron/commonlog
465505
License: Apache-2.0
466506
URL: https://github.com/tliron/commonlog/blob/v0.2.21/LICENSE
@@ -473,6 +513,14 @@ APACHE 2.0 LICENSED DEPENDENCIES
473513
License: Apache-2.0
474514
URL: https://github.com/tliron/go-kutil/blob/v0.4.0/LICENSE
475515

516+
- github.com/uber/jaeger-client-go
517+
License: Apache-2.0
518+
URL: https://github.com/uber/jaeger-client-go/blob/v2.30.0/LICENSE
519+
520+
- github.com/uber/jaeger-lib/metrics
521+
License: Apache-2.0
522+
URL: https://github.com/uber/jaeger-lib/blob/v2.4.1/LICENSE
523+
476524
- github.com/wk8/go-ordered-map/v2
477525
License: Apache-2.0
478526
URL: https://github.com/wk8/go-ordered-map/blob/v2.1.8/LICENSE
@@ -541,6 +589,10 @@ APACHE 2.0 LICENSED DEPENDENCIES
541589
License: Apache-2.0
542590
URL: https://github.com/open-telemetry/opentelemetry-go/blob/trace/v1.43.0/trace/LICENSE
543591

592+
- go.opentelemetry.io/proto/otlp
593+
License: Apache-2.0
594+
URL: https://github.com/open-telemetry/opentelemetry-proto-go/blob/otlp/v1.10.0/otlp/LICENSE
595+
544596
- go.uber.org/mock/gomock
545597
License: Apache-2.0
546598
URL: https://github.com/uber/mock/blob/v0.6.0/LICENSE
@@ -634,6 +686,18 @@ BSD LICENSED DEPENDENCIES
634686
License: BSD-3-Clause
635687
URL: https://github.com/imdario/mergo/blob/v1.0.2/LICENSE
636688

689+
- filippo.io/age
690+
License: BSD-3-Clause
691+
URL: https://github.com/FiloSottile/age/blob/v1.3.1/LICENSE
692+
693+
- filippo.io/edwards25519
694+
License: BSD-3-Clause
695+
URL: https://github.com/FiloSottile/edwards25519/blob/v1.2.0/LICENSE
696+
697+
- filippo.io/hpke
698+
License: BSD-3-Clause
699+
URL: https://github.com/FiloSottile/hpke/blob/v0.4.0/LICENSE
700+
637701
- github.com/ProtonMail/go-crypto
638702
License: BSD-3-Clause
639703
URL: https://github.com/ProtonMail/go-crypto/blob/v1.4.1/LICENSE
@@ -694,6 +758,10 @@ BSD LICENSED DEPENDENCIES
694758
License: BSD-2-Clause
695759
URL: https://github.com/emirpasic/gods/blob/v1.18.1/LICENSE
696760

761+
- github.com/extism/go-sdk
762+
License: BSD-3-Clause
763+
URL: https://github.com/extism/go-sdk/blob/v1.7.1/LICENSE
764+
697765
- github.com/fsnotify/fsnotify
698766
License: BSD-3-Clause
699767
URL: https://github.com/fsnotify/fsnotify/blob/v1.10.1/LICENSE
@@ -762,6 +830,14 @@ BSD LICENSED DEPENDENCIES
762830
License: BSD-3-Clause
763831
URL: https://github.com/hashicorp/golang-lru/blob/v2.0.7/simplelru/LICENSE_list
764832

833+
- github.com/huaweicloud/huaweicloud-sdk-go-v3
834+
License: BSD-2-Clause
835+
URL: https://github.com/huaweicloud/huaweicloud-sdk-go-v3/blob/v0.1.195/LICENSE
836+
837+
- github.com/ianlancetaylor/demangle
838+
License: BSD-3-Clause
839+
URL: https://github.com/ianlancetaylor/demangle/blob/96ee0021ea0f/LICENSE
840+
765841
- github.com/klauspost/compress/internal/snapref
766842
License: BSD-3-Clause
767843
URL: https://github.com/klauspost/compress/blob/v1.18.6/internal/snapref/LICENSE
@@ -818,6 +894,10 @@ BSD LICENSED DEPENDENCIES
818894
License: BSD-3-Clause
819895
URL: https://github.com/rogpeppe/go-internal/blob/v1.14.1/LICENSE
820896

897+
- github.com/russross/blackfriday/v2
898+
License: BSD-2-Clause
899+
URL: https://github.com/russross/blackfriday/blob/v2.1.0/LICENSE.txt
900+
821901
- github.com/spf13/pflag
822902
License: BSD-3-Clause
823903
URL: https://github.com/spf13/pflag/blob/v1.0.10/LICENSE
@@ -947,6 +1027,14 @@ MOZILLA PUBLIC LICENSE (MPL) 2.0 DEPENDENCIES
9471027
License: MPL-2.0
9481028
URL: https://github.com/cyphar/filepath-securejoin/blob/v0.6.1/COPYING.md
9491029

1030+
- github.com/getsops/sops/v3
1031+
License: MPL-2.0
1032+
URL: https://github.com/getsops/sops/blob/v3.13.1/LICENSE
1033+
1034+
- github.com/getsops/sops/v3/shamir
1035+
License: MPL-2.0
1036+
URL: https://github.com/getsops/sops/blob/v3.13.1/shamir/LICENSE
1037+
9501038
- github.com/gosimple/slug
9511039
License: MPL-2.0
9521040
URL: https://github.com/gosimple/slug/blob/v1.15.0/LICENSE
@@ -1072,6 +1160,14 @@ MIT LICENSED DEPENDENCIES
10721160
License: MIT
10731161
URL: https://github.com/alessio/shellescape/blob/v1.6.0/LICENSE
10741162

1163+
- github.com/1Password/connect-sdk-go
1164+
License: MIT
1165+
URL: https://github.com/1Password/connect-sdk-go/blob/v1.5.3/LICENSE
1166+
1167+
- github.com/1password/onepassword-sdk-go
1168+
License: MIT
1169+
URL: https://github.com/1password/onepassword-sdk-go/blob/v0.3.1/LICENSE
1170+
10751171
- github.com/99designs/keyring
10761172
License: MIT
10771173
URL: https://github.com/99designs/keyring/blob/v1.2.2/LICENSE
@@ -1100,6 +1196,10 @@ MIT LICENSED DEPENDENCIES
11001196
License: MIT
11011197
URL: https://github.com/Azure/azure-sdk-for-go/blob/sdk/resourcemanager/resources/armsubscriptions/v1.3.0/sdk/resourcemanager/resources/armsubscriptions/LICENSE.txt
11021198

1199+
- github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys
1200+
License: MIT
1201+
URL: https://github.com/Azure/azure-sdk-for-go/blob/sdk/security/keyvault/azkeys/v1.4.0/sdk/security/keyvault/azkeys/LICENSE.txt
1202+
11031203
- github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets
11041204
License: MIT
11051205
URL: https://github.com/Azure/azure-sdk-for-go/blob/sdk/security/keyvault/azsecrets/v1.4.0/sdk/security/keyvault/azsecrets/LICENSE.txt
@@ -1200,6 +1300,10 @@ MIT LICENSED DEPENDENCIES
12001300
License: MIT
12011301
URL: https://github.com/bgentry/go-netrc/blob/9fd32a8b3d3d/LICENSE
12021302

1303+
- github.com/blang/semver
1304+
License: MIT
1305+
URL: https://github.com/blang/semver/blob/v3.5.1/LICENSE
1306+
12031307
- github.com/bmatcuk/doublestar/v4
12041308
License: MIT
12051309
URL: https://github.com/bmatcuk/doublestar/blob/v4.10.0/LICENSE
@@ -1296,6 +1400,10 @@ MIT LICENSED DEPENDENCIES
12961400
License: MIT
12971401
URL: https://github.com/common-nighthawk/go-figure/blob/734e95fb86be/LICENSE
12981402

1403+
- github.com/cpuguy83/go-md2man/v2/md2man
1404+
License: MIT
1405+
URL: https://github.com/cpuguy83/go-md2man/blob/v2.0.7/LICENSE.md
1406+
12991407
- github.com/creack/pty
13001408
License: MIT
13011409
URL: https://github.com/creack/pty/blob/v1.1.24/LICENSE
@@ -1408,6 +1516,10 @@ MIT LICENSED DEPENDENCIES
14081516
License: MIT
14091517
URL: https://github.com/gookit/color/blob/v1.6.1/LICENSE
14101518

1519+
- github.com/goware/prefixer
1520+
License: MIT
1521+
URL: https://github.com/goware/prefixer/blob/395022866408/LICENSE
1522+
14111523
- github.com/gsterjov/go-libsecret
14121524
License: MIT
14131525
URL: https://github.com/gsterjov/go-libsecret/blob/a6f4afe4910c/LICENSE
@@ -1494,7 +1606,7 @@ MIT LICENSED DEPENDENCIES
14941606

14951607
- github.com/json-iterator/go
14961608
License: MIT
1497-
URL: https://github.com/json-iterator/go/blob/v1.1.12/LICENSE
1609+
URL: https://github.com/json-iterator/go/blob/71ac16282d12/LICENSE
14981610

14991611
- github.com/jwalton/go-supportscolor
15001612
License: MIT
@@ -1556,6 +1668,10 @@ MIT LICENSED DEPENDENCIES
15561668
License: MIT
15571669
URL: https://github.com/lestrrat-go/option/blob/v2.0.0/LICENSE
15581670

1671+
- github.com/lib/pq
1672+
License: MIT
1673+
URL: https://github.com/lib/pq/blob/v1.12.3/LICENSE
1674+
15591675
- github.com/lrstanley/bubblezone
15601676
License: MIT
15611677
URL: https://github.com/lrstanley/bubblezone/blob/v1.0.0/LICENSE
@@ -1752,6 +1868,10 @@ MIT LICENSED DEPENDENCIES
17521868
License: MIT
17531869
URL: https://github.com/ugorji/go/blob/codec/v1.3.1/codec/LICENSE
17541870

1871+
- github.com/urfave/cli
1872+
License: MIT
1873+
URL: https://github.com/urfave/cli/blob/v1.22.17/LICENSE
1874+
17551875
- github.com/valyala/fastjson
17561876
License: MIT
17571877
URL: https://github.com/valyala/fastjson/blob/v1.6.10/LICENSE

0 commit comments

Comments
 (0)