Skip to content

Commit c3ab183

Browse files
ostermanclaudeaknysh
authored
feat(container): persistent container component kind + compositions (#2645)
* feat(container): add persistent container component kind + compositions Add a stack-scoped, Atmos-native `container` component kind: one component is one persistent service. Atmos owns the image artifact (build/push/pull) and a long-running named container lifecycle (list/up/ps/logs/exec/restart/stop/rm/down), discovered by labels derived from the canonical component instance address (`<stack>/container/<component>`) rather than local state files. - pkg/container: identity (canonical labels, RuntimeName, DiscoveryFilter) + named lifecycle (Up/Down/FindInstance), sharing the sanitizer with sandbox.go. - pkg/component/container: provider + executor for the verbs; first-class config (`image`/`build`/`run` reuse the workflow container-step structs, NOT `vars`); build-before-start; abstract components rejected. - cmd/container + cmd/composition: thin CommandProvider command groups. - `atmos container list` shows per-instance running state (label discovery). The generic `atmos list components` lists containers as a component type uniformly, without container-specific status (consistent with terraform/ansible). - compositions: first-class `composition:` membership + `compositions:` section; hard error on undeclared membership; `atmos composition validate` soft report. - Custom-component fallback now runs metadata.inherits inheritance + generic deep-merge of all top-level keys, so container honors catalog/abstract defaults. - describe component auto-detect + describe/list type whitelist extended for container (and the pre-existing ansible gap in list components). - Examples (container-component, compositions), docs, JSON schema, and a new atmos-core-component-development contributor skill. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(container): address review feedback and fix CI failures Apply CodeRabbit review fixes for PR #2645 (container component type + compositions) and fix the failing Acceptance Tests. First-class image/build contract: - ValidateComponent and its tests check top-level image/build (not vars.*) - executor error text and verb help/usage docs use first-class keys Correctness/robustness: - lifecycle.Up joins the cleanup error when Start fails (no orphan masking) - ensureImage only builds on a genuine missing image; export and reuse IsImageMissingError so transport/auth/daemon errors surface - composition + container resolve stack via viper (flag > env > config) - container component completion filters by stack and container type - scope FParseErrWhitelist to `exec` only so other verbs catch typos - customComponentInheritsBases errors on malformed metadata.inherits Schema: - container_component_manifest accepts !include via oneOf - compositions require non-empty, unique services - classify ContainerSectionName in the schema-coverage ratchet Tests/docs: - assert port-binding values (not just length); add composition executor unit tests and a negative-path inherits test; docstrings on new exports - doc fixes (comma, explicit ps/logs/exec commands, Next Steps link) CI: - regenerate TestCLICommands/atmos_non-existent snapshot to include the new composition and container commands Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * refactor(container): unify all labels under tools.atmos.* namespace Replace the two legacy label namespaces (com.cloudposse.atmos.* for container-component instances and com.atmos.* for workflow sandboxes, container steps, and devcontainers) with a single tools.atmos.* namespace so the label vocabulary is consistent repo-wide. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(container): bulk lifecycle ops, log tailing, start verb, polish Add multi-component and interactive operation to the container component lifecycle, mirroring docker compose, plus several UX fixes. Bulk operation: - build/push/pull/up/start/restart/stop/rm/down accept no component and operate on --all (optionally --stack-scoped) or via an interactive picker (stack, then multi-select of components). Continue-on-error with an aggregated summary; teardown verbs (down/stop/rm) run in reverse order. - Reuse describeStacks + collectContainerInstances for target discovery; per-target dispatch reuses the existing single-component executors. logs --all / --follow: - logs takes an optional component plus --all/--follow/--tail. Multi-follow streams concurrently with a colored, centered, uppercased per-component label (log-level-badge style via pkg/ui/theme.ComponentLabelStyle), built on pkg/io.NewLinePrefixWriterRaw; degrades to [NAME] without color. - Ctrl-C stops gracefully: suspends Atmos's global interrupt-exit (pkg/signals) and prints a friendly closing line instead of exiting 130. start verb: - Add `start` to resume an existing stopped container in place (inverse of stop; unlike up it never creates/recreates), restoring up/down + start/stop symmetry. ps + list: - `ps` no longer requires a component; with none it lists all components' running state (like `list`, optionally --stack-filtered). - Fix list/ps column alignment: render the colored status dot outside tabwriter (its ANSI codes inflated the byte-counted column width). Other: - Spinners on lifecycle ops (build/push/pull/up/start/restart/stop/rm/down), matching the devcontainer UX. - Fix the --stack flag being dropped for single-component invocations by rebinding the executing command's flags to Viper in runVerb. - Give --tail a no-opt default so bare `--tail` does not swallow the next flag. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(container): push every build tag (multi-registry push in one op) `atmos container push <component>` (and `push --all`) now pushes every entry in `build.tags` instead of only the single top-level `image`. Because the build already applies each tag to the image locally, listing registry-qualified tags in `build.tags` ships the image to multiple registries in one operation. Pushes run in order and fail fast; with no `build.tags`, push falls back to `image` (unchanged behavior). - pkg/component/container/config.go: ContainerSpec.PushRefs() returns the deduped, order-preserving build tags, else the single image. - pkg/component/container/executor.go: ExecutePush fans out over PushRefs() (per-ref spinner, fail-fast, dry-run lists each ref). - docs: dedicated "Push to multiple registries" section in the container usage page, plus Usage/Configuration/Examples mentions. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * feat(container): first-class healthcheck and restart policies Add Docker-Compose-style `run.healthcheck` and `run.restart` as first-class container component properties, so users no longer need the raw `run.run_args` passthrough. Health status is now surfaced in `atmos container ps`/`list`. - `restart: { policy, max_retries }` -> docker/podman --restart=<policy>[:<n>] - `healthcheck:` mirrors Compose (test string/list with NONE|CMD|CMD-SHELL, interval/timeout/retries/start_period/start_interval/disable) -> --health-* / --no-healthcheck - ps/list gain a HEALTH column, parsed for free from the existing ps .Status string (no extra runtime call) in both docker and podman paths - restart policy and healthcheck durations validated up front with friendly errors instead of opaque runtime failures - JSON manifest schema, the container-component example, and the usage docs document the new settings Adding the Health field to container.Info / instanceRow grew those structs, so a few existing by-value range/param copies are switched to indexing/pointers to satisfy gocritic (sandbox.go, podman.go, logs.go). Plumbing mirrors the existing ports/mounts flow: schema.ContainerRunStep -> ContainerSpec mappers/ValidateRun -> NamedConfig -> CreateConfig -> addHealthAndRestart flag emitter. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(container): address CodeRabbit review feedback on PR #2645 Apply still-valid CodeRabbit findings; skip the rest with rationale. Correctness/robustness: - container: IsContainerRunning tokenizes status (no false match on "not running"); identity labels are now authoritative over caller labels in buildNamedCreateConfig (label-based discovery stays intact) - container: CommandArgs tokenizes run.command with google/shlex so quoted args survive in ExecuteUp; errors on malformed quoting - container logs: only "no running container" is skippable (new ErrNoRunningContainer sentinel); real discovery failures aggregate and surface; follow errors wrap a static sentinel - ui: guard negative index in FormatComponentLabel and ComponentLabelStyle to avoid a negative-modulus panic - stacks: non-string metadata.inherits items now error instead of being silently dropped - schema: compositions accepts !include like peer sections Tests/docs/cleanup: - fail-loud fixture check; "not running" regression case; both-direction label isolation; ShellOptions IO propagation; ShellOptions test names; table-driven composition arg test; push stub Shell fails loudly; pin mockgen to v0.6.0; correct stale describe_stacks comment; e.g., Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * docs(changelog): add container components blog post and roadmap milestone PR #2645 is labeled `minor`, so the release-docs gate requires a changelog entry and a roadmap update for the user-facing container component kind + compositions feature. - Add website/blog/2026-06-23-container-components.mdx (slug: container-components) - Mark the "Container components with component-instance lifecycle" and "Atmos compositions as system boundaries" milestones as shipped in the Container Composition & Local Development initiative (pr: 2645, changelog: container-components); recompute progress 29% -> 57% Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Andriy Knysh <aknysh@users.noreply.github.com>
1 parent 2c6912a commit c3ab183

103 files changed

Lines changed: 9003 additions & 217 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.

.claude/skills/atmos-components

Lines changed: 0 additions & 1 deletion
This file was deleted.
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
---
2+
name: atmos-core-component-development
3+
description: "Atmos CORE contributor guide for adding/modifying a component TYPE in the Go codebase (terraform/helmfile/packer/ansible/container): the component registry & provider, the CLI command group, the describe/list type whitelist, custom-component inheritance & deep-merge, schema, and tests. NOT for authoring user components in stacks (that is the atmos-components skill)."
4+
metadata:
5+
copyright: Copyright Cloud Posse, LLC 2026
6+
version: "1.0.0"
7+
---
8+
9+
# Atmos Core: Component Type Development
10+
11+
Use this skill when **developing Atmos itself** — adding or modifying a component **type** (kind) in the
12+
Go codebase. This is contributor/core guidance, distinct from the user-facing `atmos-components` skill
13+
(which documents authoring terraform/helmfile/container components in stacks).
14+
15+
Start from `docs/developing-component-plugins.md` (the component-plugin development guide). The notes
16+
below capture the non-obvious wiring learned while adding the `container` component type.
17+
18+
## The component provider (pkg/component)
19+
20+
A component type is a `ComponentProvider` (`pkg/component/provider.go`) registered via `init()` with
21+
`component.Register(...)` (`pkg/component/registry.go`). Reference impls:
22+
23+
- `pkg/component/ansible/` — typed-config built-in style.
24+
- `pkg/component/mock/`, `pkg/component/custom/` — the `Plugins`-map plugin style.
25+
- `pkg/component/container/` — provider + `cmd`/lifecycle split.
26+
27+
Layout per the guide: `config.go` (typed `Config` + `parseConfig`), `<type>.go` (provider + `init`),
28+
`executor.go` (verb implementations), `<type>_test.go` (>90% coverage). Wire a blank import into
29+
`cmd/root.go` so `init()` runs.
30+
31+
Reusable error sentinels live in `errors/errors.go`: `ErrComponentExecutionFailed`,
32+
`ErrComponentConfigInvalid`, `ErrComponentValidationFailed`, `ErrComponentTypeEmpty`.
33+
34+
## First-class component config (NOT `vars`)
35+
36+
Per-instance config that is **not arbitrary template data** must be first-class top-level sections
37+
(siblings of `metadata`/`env`/`composition`), NOT nested under `vars`. For container, the config reuses
38+
the workflow container-step structs (`schema.ContainerBuildStep`/`ContainerRunStep`/`ContainerMount`/
39+
`ContainerPort` in `pkg/schema/workflow.go`) for consistency. Decode a YAML-derived `map[string]any`
40+
into those structs with mapstructure using `TagName: "yaml"` so snake_case keys (`build_args`,
41+
`read_only`) map.
42+
43+
## The CLI command group (cmd/<type>)
44+
45+
Mirror `cmd/ansible/`: a base `cobra.Command` registered through the command registry
46+
(`cmd/internal` `CommandProvider`), persistent flags via `flags.NewStandardParser()` (NEVER
47+
`viper.BindEnv`/`BindPFlag`), one thin file per verb dispatching to
48+
`component.MustGetProvider(<type>).Execute(&component.ExecutionContext{...})`. Wire a blank import into
49+
`cmd/root.go`.
50+
51+
## CRITICAL: the describe/list type whitelist
52+
53+
A new top-level `components.<type>` is **dropped** (stack renders `{}`, "component not found") unless
54+
the type is added to several hardcoded lists. Grep `AnsibleSectionName` / `"ansible"` across
55+
`internal/exec` + `pkg/list/extract` and mirror every hit:
56+
57+
1. `pkg/config/const.go``XComponentType` / `XSectionName` consts.
58+
2. `internal/exec/describe_stacks_component_processor.go` — the `typeEntries` list AND
59+
`componentsSectionHasComponents`.
60+
3. `internal/exec/describe_stacks.go``getComponentBasePath` switch.
61+
4. `internal/exec/describe_component.go` — the `detectComponentType` auto-detect order (a loop over
62+
`[terraform, helmfile, packer, ansible, container]`); a type missing here makes `atmos describe
63+
component <name>` fail even when the lifecycle works.
64+
5. `pkg/list/extract/components.go` — THREE hardcoded type lists (per-stack `extractComponentType` ×2,
65+
unique `extractUniqueComponentType`).
66+
67+
Verify with `atmos describe stacks` (stack with only the new type must be non-empty) and
68+
`atmos describe component <name> -s <stack>`.
69+
70+
## Inheritance & deep-merge for custom types
71+
72+
Built-in types (terraform/helmfile/packer/ansible) get full inheritance via the processComponent
73+
pipeline. Other types ride the **custom-component fallback** in
74+
`internal/exec/stack_processor_process_stacks.go`. That fallback now resolves `metadata.inherits` and
75+
**generic-deep-merges all top-level keys** (`resolveCustomComponentInheritance`), so custom types honor
76+
catalog/abstract defaults. Gotchas:
77+
78+
- Strip `metadata.type`/`inherits`/`component` from a base before merging, or an abstract base poisons
79+
the concrete component (`sanitizeBaseForInheritance`).
80+
- Reject `metadata.type: abstract` for execution and filter it from listings.
81+
- Use the **native merge** (`pkg/merge`) — it already incorporates the slice-truncation and
82+
permissive-type-mismatch fixes in `docs/fixes/2026-03-19-*` and `docs/fixes/2026-03-24-*`.
83+
- Component-level config *sections* (vars/settings/env/hooks/secrets/...) that need per-section
84+
inheritance still require the section whitelist plumbing (see `docs/errors.md` / the merge helpers).
85+
86+
## Schema, docs, tests
87+
88+
- JSON schema: `pkg/datafetcher/schema/atmos/manifest/1.0.json` — add `<type>_components` +
89+
`<type>_component_manifest` definitions and the `components.<type>` property.
90+
- Docs: `website/docs/components/components-overview.mdx` (Component Types table + directory diagram),
91+
`website/docs/components/<type>.mdx`, and `website/docs/cli/commands/<type>/usage.mdx`.
92+
- Tests: provider unit tests with a mockgen `Runtime`/dependency, `cmd.NewTestKit` for the command,
93+
inheritance + abstract + graceful-empty cases. Regenerate affected `--help` golden snapshots with
94+
`-regenerate-snapshots` (never hand-edit).
95+
- Gate: `./custom-gcl run --new-from-rev=origin/<base> ./pkg/component/<type>/... ./internal/exec/...`.

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

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -96,11 +96,11 @@ Containers follow the naming convention: `atmos-devcontainer.{name}.{instance}`
9696
All Atmos devcontainers are labeled for management:
9797

9898
```text
99-
com.atmos.type=devcontainer
100-
com.atmos.devcontainer.name={name}
101-
com.atmos.devcontainer.instance={instance}
102-
com.atmos.workspace={workspace-path}
103-
com.atmos.created={timestamp}
99+
tools.atmos.type=devcontainer
100+
tools.atmos.devcontainer.name={name}
101+
tools.atmos.devcontainer.instance={instance}
102+
tools.atmos.workspace={workspace-path}
103+
tools.atmos.created={timestamp}
104104
```
105105

106106
### Runtime Auto-Detection

cmd/composition/composition.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package composition
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
"github.com/spf13/viper"
6+
7+
"github.com/cloudposse/atmos/cmd/internal"
8+
pkgcomposition "github.com/cloudposse/atmos/pkg/composition"
9+
cfg "github.com/cloudposse/atmos/pkg/config"
10+
"github.com/cloudposse/atmos/pkg/flags"
11+
"github.com/cloudposse/atmos/pkg/flags/compat"
12+
"github.com/cloudposse/atmos/pkg/schema"
13+
)
14+
15+
var compositionParser *flags.StandardParser
16+
17+
// compositionCmd is the base command for composition operations.
18+
var compositionCmd = &cobra.Command{
19+
Use: "composition",
20+
Short: "Inspect compositions that group component instances into systems",
21+
Long: "Operate on compositions — named groupings of component instances (services) that form a system.",
22+
RunE: func(cmd *cobra.Command, _ []string) error {
23+
return cmd.Usage()
24+
},
25+
}
26+
27+
// The validate subcommand reports a composition's fulfilled and not-provided services.
28+
var validateCmd = &cobra.Command{
29+
Use: "validate <composition>",
30+
Short: "Report a composition's fulfilled and not-provided services for a stack",
31+
Long: "Show which of a composition's declared services are fulfilled by components in a stack and which are not provided there.",
32+
Args: cobra.ExactArgs(1),
33+
RunE: func(cmd *cobra.Command, args []string) error {
34+
info := buildConfigAndStacksInfo(cmd)
35+
return pkgcomposition.ExecuteValidate(cmd.Context(), &info, args[0])
36+
},
37+
}
38+
39+
func init() {
40+
compositionParser = flags.NewStandardParser(flags.WithFlagRegistry(flags.CommonFlags()))
41+
compositionParser.RegisterPersistentFlags(compositionCmd)
42+
if err := compositionParser.BindToViper(viper.GetViper()); err != nil {
43+
panic(err)
44+
}
45+
46+
compositionCmd.AddCommand(validateCmd)
47+
internal.Register(&CompositionCommandProvider{})
48+
}
49+
50+
// CompositionCommandProvider implements the CommandProvider interface.
51+
type CompositionCommandProvider struct{}
52+
53+
// GetCommand returns the composition command.
54+
func (c *CompositionCommandProvider) GetCommand() *cobra.Command { return compositionCmd }
55+
56+
// GetName returns the command name.
57+
func (c *CompositionCommandProvider) GetName() string { return "composition" }
58+
59+
// GetGroup returns the command group for help organization.
60+
func (c *CompositionCommandProvider) GetGroup() string { return "Core Stack Commands" }
61+
62+
// GetAliases returns command aliases.
63+
func (c *CompositionCommandProvider) GetAliases() []internal.CommandAlias { return nil }
64+
65+
// GetFlagsBuilder returns the flags builder for this command.
66+
func (c *CompositionCommandProvider) GetFlagsBuilder() flags.Builder { return nil }
67+
68+
// GetPositionalArgsBuilder returns the positional args builder for this command.
69+
func (c *CompositionCommandProvider) GetPositionalArgsBuilder() *flags.PositionalArgsBuilder {
70+
return nil
71+
}
72+
73+
// GetCompatibilityFlags returns compatibility flags for this command.
74+
func (c *CompositionCommandProvider) GetCompatibilityFlags() map[string]compat.CompatibilityFlag {
75+
return nil
76+
}
77+
78+
// IsExperimental returns whether this command is experimental.
79+
func (c *CompositionCommandProvider) IsExperimental() bool { return false }
80+
81+
// buildConfigAndStacksInfo creates a ConfigAndStacksInfo from global + stack flags.
82+
func buildConfigAndStacksInfo(cmd *cobra.Command) schema.ConfigAndStacksInfo {
83+
globalFlags := flags.ParseGlobalFlags(cmd, viper.GetViper())
84+
info := schema.ConfigAndStacksInfo{
85+
AtmosBasePath: globalFlags.BasePath,
86+
AtmosConfigFilesFromArg: globalFlags.Config,
87+
AtmosConfigDirsFromArg: globalFlags.ConfigPath,
88+
Identity: cfg.NormalizeIdentityValue(globalFlags.Identity.Value()),
89+
ProfilesFromArg: globalFlags.Profile,
90+
}
91+
// Resolve the stack via viper so the full precedence chain is honored
92+
// (flag > ATMOS_STACK env > config), not just the directly-set Cobra flag.
93+
if stack := viper.GetViper().GetString("stack"); stack != "" {
94+
info.Stack = stack
95+
}
96+
return info
97+
}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package composition
2+
3+
import (
4+
"testing"
5+
6+
"github.com/spf13/viper"
7+
"github.com/stretchr/testify/assert"
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
func TestCompositionCommandProvider(t *testing.T) {
12+
provider := &CompositionCommandProvider{}
13+
14+
cmd := provider.GetCommand()
15+
require.NotNil(t, cmd)
16+
assert.Equal(t, "composition", cmd.Use)
17+
assert.Equal(t, "composition", provider.GetName())
18+
assert.Equal(t, "Core Stack Commands", provider.GetGroup())
19+
assert.Nil(t, provider.GetAliases())
20+
assert.Nil(t, provider.GetFlagsBuilder())
21+
assert.Nil(t, provider.GetPositionalArgsBuilder())
22+
assert.Nil(t, provider.GetCompatibilityFlags())
23+
assert.False(t, provider.IsExperimental())
24+
}
25+
26+
func TestCompositionCommandStructure(t *testing.T) {
27+
subcommands := compositionCmd.Commands()
28+
names := make([]string, len(subcommands))
29+
for i, c := range subcommands {
30+
names[i] = c.Name()
31+
}
32+
assert.Contains(t, names, "validate")
33+
}
34+
35+
func TestValidateRequiresExactlyOneArg(t *testing.T) {
36+
// validate <composition> takes exactly one positional argument.
37+
tests := []struct {
38+
name string
39+
args []string
40+
wantErr bool
41+
}{
42+
{name: "no args", args: []string{}, wantErr: true},
43+
{name: "exactly one arg", args: []string{"storefront"}, wantErr: false},
44+
{name: "too many args", args: []string{"a", "b"}, wantErr: true},
45+
}
46+
for _, tt := range tests {
47+
t.Run(tt.name, func(t *testing.T) {
48+
err := validateCmd.Args(validateCmd, tt.args)
49+
if tt.wantErr {
50+
require.Error(t, err)
51+
return
52+
}
53+
require.NoError(t, err)
54+
})
55+
}
56+
}
57+
58+
func TestBuildConfigAndStacksInfo_ResolvesStack(t *testing.T) {
59+
// Stack is resolved via viper so the full precedence chain is honored.
60+
v := viper.GetViper()
61+
orig := v.GetString("stack")
62+
t.Cleanup(func() { v.Set("stack", orig) })
63+
64+
v.Set("stack", "dev")
65+
info := buildConfigAndStacksInfo(compositionCmd)
66+
assert.Equal(t, "dev", info.Stack)
67+
68+
v.Set("stack", "")
69+
info = buildConfigAndStacksInfo(compositionCmd)
70+
assert.Empty(t, info.Stack)
71+
}

cmd/container/completions.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
package container
2+
3+
import (
4+
"github.com/spf13/cobra"
5+
"github.com/spf13/viper"
6+
7+
e "github.com/cloudposse/atmos/internal/exec"
8+
cfg "github.com/cloudposse/atmos/pkg/config"
9+
"github.com/cloudposse/atmos/pkg/flags"
10+
l "github.com/cloudposse/atmos/pkg/list"
11+
"github.com/cloudposse/atmos/pkg/perf"
12+
"github.com/cloudposse/atmos/pkg/schema"
13+
)
14+
15+
// globalInfoForCompletion builds a minimal ConfigAndStacksInfo from global flags
16+
// so completion honors config-selection flags.
17+
func globalInfoForCompletion(cmd *cobra.Command) schema.ConfigAndStacksInfo {
18+
globalFlags := flags.ParseGlobalFlags(cmd, viper.GetViper())
19+
return schema.ConfigAndStacksInfo{
20+
AtmosBasePath: globalFlags.BasePath,
21+
AtmosConfigFilesFromArg: globalFlags.Config,
22+
AtmosConfigDirsFromArg: globalFlags.ConfigPath,
23+
ProfilesFromArg: globalFlags.Profile,
24+
}
25+
}
26+
27+
// stackFlagCompletion provides completion values for the --stack flag.
28+
func stackFlagCompletion(cmd *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
29+
defer perf.Track(nil, "container.stackFlagCompletion")()
30+
31+
atmosConfig, err := cfg.InitCliConfig(globalInfoForCompletion(cmd), true)
32+
if err != nil {
33+
return nil, cobra.ShellCompDirectiveNoFileComp
34+
}
35+
stacksMap, err := e.ExecuteDescribeStacks(&atmosConfig, "", nil, nil, nil, false, false, false, false, nil, nil)
36+
if err != nil {
37+
return nil, cobra.ShellCompDirectiveNoFileComp
38+
}
39+
stacks, err := l.FilterAndListStacks(stacksMap, "")
40+
if err != nil {
41+
return nil, cobra.ShellCompDirectiveNoFileComp
42+
}
43+
return stacks, cobra.ShellCompDirectiveNoFileComp
44+
}
45+
46+
// componentArgCompletion provides completion values for the component argument.
47+
func componentArgCompletion(cmd *cobra.Command, args []string, _ string) ([]string, cobra.ShellCompDirective) {
48+
defer perf.Track(nil, "container.componentArgCompletion")()
49+
50+
if len(args) > 0 {
51+
return nil, cobra.ShellCompDirectiveNoFileComp
52+
}
53+
// Honor the --stack flag and restrict suggestions to container components so
54+
// completion never offers non-container or wrong-stack components.
55+
stack := ""
56+
if stackFlag := cmd.Flag("stack"); stackFlag != nil {
57+
stack = stackFlag.Value.String()
58+
}
59+
atmosConfig, err := cfg.InitCliConfig(globalInfoForCompletion(cmd), true)
60+
if err != nil {
61+
return nil, cobra.ShellCompDirectiveNoFileComp
62+
}
63+
stacksMap, err := e.ExecuteDescribeStacks(&atmosConfig, stack, nil, []string{cfg.ContainerComponentType}, nil, false, false, false, false, nil, nil)
64+
if err != nil {
65+
return nil, cobra.ShellCompDirectiveNoFileComp
66+
}
67+
components, err := l.FilterAndListComponents(stack, stacksMap)
68+
if err != nil {
69+
return nil, cobra.ShellCompDirectiveNoFileComp
70+
}
71+
return components, cobra.ShellCompDirectiveNoFileComp
72+
}
73+
74+
// RegisterContainerCompletions registers completion functions for the container
75+
// command. Every subcommand takes a component argument.
76+
func RegisterContainerCompletions(cmd *cobra.Command) {
77+
defer perf.Track(nil, "container.RegisterContainerCompletions")()
78+
79+
for _, subCmd := range cmd.Commands() {
80+
subCmd.ValidArgsFunction = componentArgCompletion
81+
}
82+
}

0 commit comments

Comments
 (0)