Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
17 changes: 12 additions & 5 deletions cmd/wfctl/infra.go
Original file line number Diff line number Diff line change
Expand Up @@ -441,13 +441,20 @@ func extractDependsOn(cfg map[string]any) []string {
// populating Size and DependsOn from the resolved Config. Used by both the
// --env and no-env paths so field extraction never diverges.
func resourceSpecFromResolvedModule(r *config.ResolvedModule) interfaces.ResourceSpec {
cfg := cloneMap(r.Config)
if r.Protected {
if cfg == nil {
cfg = map[string]any{}
}
cfg["protected"] = true
}
spec := interfaces.ResourceSpec{
Name: r.Name,
Type: r.Type,
Config: r.Config,
DependsOn: extractDependsOn(r.Config),
Config: cfg,
DependsOn: extractDependsOn(cfg),
}
if size, ok := r.Config["size"].(string); ok {
if size, ok := cfg["size"].(string); ok {
spec.Size = interfaces.Size(size)
}
return spec
Expand Down Expand Up @@ -531,7 +538,7 @@ func parseInfraResourceSpecs(cfgFile string) ([]interfaces.ResourceSpec, error)
if !isInfraType(m.Type) {
continue
}
r := &config.ResolvedModule{Name: m.Name, Type: m.Type, Config: config.ExpandEnvInMapPreservingVars(m.Config, infraPreserveKeys, secretVars)}
r := &config.ResolvedModule{Name: m.Name, Type: m.Type, Protected: m.Protected, Config: config.ExpandEnvInMapPreservingVars(m.Config, infraPreserveKeys, secretVars)}
specs = append(specs, resourceSpecFromResolvedModule(r))
}
return specs, nil
Expand Down Expand Up @@ -578,7 +585,7 @@ func planResourcesForEnv(path, envName string) ([]*config.ResolvedModule, error)
continue
}
if envName == "" {
out = append(out, &config.ResolvedModule{Name: m.Name, Type: m.Type, Config: config.ExpandEnvInMapPreservingVars(m.Config, infraPreserveKeys, secretVars)})
out = append(out, &config.ResolvedModule{Name: m.Name, Type: m.Type, Protected: m.Protected, Config: config.ExpandEnvInMapPreservingVars(m.Config, infraPreserveKeys, secretVars)})
continue
}
resolved, ok := m.ResolveForEnv(envName)
Expand Down
27 changes: 27 additions & 0 deletions cmd/wfctl/infra_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,33 @@ modules:
}
}

func TestParseInfraResourceSpecs_TopLevelProtectedBecomesConfig(t *testing.T) {
yaml := `
modules:
- name: app
type: infra.container_service
protected: true
config:
image: ghcr.io/example/app:latest
`
f, err := writeTempYAML(t, yaml)
if err != nil {
t.Fatal(err)
}
defer os.Remove(f)

specs, err := parseInfraResourceSpecs(f)
if err != nil {
t.Fatal(err)
}
if len(specs) != 1 {
t.Fatalf("expected 1 spec, got %d", len(specs))
}
if got, _ := specs[0].Config["protected"].(bool); !got {
t.Fatalf("spec.Config[protected] = %#v, want true", specs[0].Config["protected"])
}
}

// --- formatPlanTable tests ---

func TestFormatPlanTable_ShowsAllActions(t *testing.T) {
Expand Down
1 change: 1 addition & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ type ModuleConfig struct {
Name string `json:"name" yaml:"name"`
Type string `json:"type" yaml:"type"`
Satisfies []string `json:"satisfies,omitempty" yaml:"satisfies,omitempty"`
Protected bool `json:"protected,omitempty" yaml:"protected,omitempty"`
Config map[string]any `json:"config,omitempty" yaml:"config,omitempty"`
DependsOn []string `json:"dependsOn,omitempty" yaml:"dependsOn,omitempty"`
Branches map[string]string `json:"branches,omitempty" yaml:"branches,omitempty"`
Expand Down
18 changes: 10 additions & 8 deletions config/module_resolve_env.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@ import "strings"

// ResolvedModule is the effective module config for a specific environment.
type ResolvedModule struct {
Name string
Type string
Provider string
Region string
Config map[string]any
Name string
Type string
Provider string
Region string
Protected bool
Config map[string]any
}

// ResolveForEnv returns the effective module config for envName.
Expand All @@ -19,9 +20,10 @@ type ResolvedModule struct {
// construction (which reads only Config) picks them up.
func (m *ModuleConfig) ResolveForEnv(envName string) (*ResolvedModule, bool) {
resolved := &ResolvedModule{
Name: m.Name,
Type: m.Type,
Config: cloneMap(m.Config),
Name: m.Name,
Type: m.Type,
Protected: m.Protected,
Config: cloneMap(m.Config),
}
setRegionFromConfig(resolved)

Expand Down
16 changes: 16 additions & 0 deletions config/module_resolve_env_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,22 @@ func TestResolveForEnv_NoEnvironments_ReturnsTopLevel(t *testing.T) {
}
}

func TestResolveForEnv_PreservesTopLevelProtected(t *testing.T) {
m := &ModuleConfig{
Name: "db",
Type: "infra.database",
Protected: true,
Config: map[string]any{"size": "small"},
}
resolved, ok := m.ResolveForEnv("prod")
if !ok {
t.Fatal("want ok=true")
}
if !resolved.Protected {
t.Fatal("want Protected=true")
}
}

func TestResolveForEnv_OverridesMerge(t *testing.T) {
m := &ModuleConfig{
Name: "db",
Expand Down
Loading