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
24 changes: 21 additions & 3 deletions cmd/wfctl/infra.go
Original file line number Diff line number Diff line change
Expand Up @@ -1030,10 +1030,28 @@ func findInfraSpecByName(cfgFile, envName, name string) (interfaces.ResourceSpec
return interfaces.ResourceSpec{}, fmt.Errorf("infra resource %q not found in %s", name, cfgFile)
}

// resolveIaCProviderRef returns the iac.provider module name to dispatch to
// for a resource. Reads "iac_provider" first (canonical, disambiguates
// implementation from IaC routing), falls back to "provider" for backward
// compatibility. Resources whose plugin schema uses "provider" as the
// implementation identifier (e.g. infra.eventbus's provider="nats" |
// "kafka") should declare iac_provider explicitly. Resources where
// "provider" is itself the iac.provider module name (e.g. infra.database's
// provider="do-provider") continue to work via the fallback.
func resolveIaCProviderRef(cfg map[string]any) string {
if v, ok := cfg["iac_provider"].(string); ok && v != "" {
return v
}
if v, ok := cfg["provider"].(string); ok {
return v
}
return ""
}

func resolveProviderForSpec(cfgFile, envName string, spec interfaces.ResourceSpec) (string, map[string]any, error) {
moduleRef, _ := spec.Config["provider"].(string)
moduleRef := resolveIaCProviderRef(spec.Config)
if moduleRef == "" {
return "", nil, fmt.Errorf("infra module %q (%s): missing required 'provider' field", spec.Name, spec.Type)
return "", nil, fmt.Errorf("infra module %q (%s): missing required 'iac_provider' or 'provider' field", spec.Name, spec.Type)
}
cfg, err := config.LoadFromFile(cfgFile)
if err != nil {
Expand All @@ -1060,7 +1078,7 @@ func resolveProviderForSpec(cfgFile, envName string, spec interfaces.ResourceSpe
}
return providerType, modCfg, nil
}
return "", nil, fmt.Errorf("infra module %q references provider %q which is not declared as an iac.provider module", spec.Name, moduleRef)
return "", nil, fmt.Errorf("infra module %q references iac.provider module %q (resolved from iac_provider/provider field) which is not declared as an iac.provider module", spec.Name, moduleRef)
}

func isNoopStateStore(store infraStateStore) bool {
Expand Down
18 changes: 8 additions & 10 deletions cmd/wfctl/infra_apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -345,16 +345,14 @@ func resourceStateProviderRef(st interfaces.ResourceState) string {
if st.AppliedConfig == nil {
return ""
}
providerRef, _ := st.AppliedConfig["provider"].(string)
return providerRef
return resolveIaCProviderRef(st.AppliedConfig)
}

func resourceSpecProviderRef(spec interfaces.ResourceSpec) string {
if spec.Config == nil {
return ""
}
providerRef, _ := spec.Config["provider"].(string)
return providerRef
return resolveIaCProviderRef(spec.Config)
}

// applyWithProviderAndStore computes a diff plan for the given specs against
Expand Down Expand Up @@ -554,7 +552,7 @@ func applyWithProviderAndStore(ctx context.Context, provider interfaces.IaCProvi
for i := range specs {
if specs[i].Name == r.Name {
appliedCfg = specs[i].Config
providerRef, _ = specs[i].Config["provider"].(string)
providerRef = resolveIaCProviderRef(specs[i].Config)
dependencies = append([]string(nil), specs[i].DependsOn...)
break
}
Expand Down Expand Up @@ -1166,22 +1164,22 @@ func applyFromPrecomputedPlan(ctx context.Context, plan interfaces.IaCPlan, cfgF

for i := range plan.Actions {
action := &plan.Actions[i]
moduleRef, _ := action.Resource.Config["provider"].(string)
moduleRef := resolveIaCProviderRef(action.Resource.Config)
// delete actions from ComputePlan carry an empty Resource.Config — the
// provider ref must be recovered from the recorded current state instead.
if moduleRef == "" && action.Current != nil {
moduleRef = action.Current.ProviderRef
if moduleRef == "" {
moduleRef, _ = action.Current.AppliedConfig["provider"].(string)
moduleRef = resolveIaCProviderRef(action.Current.AppliedConfig)
}
}
if moduleRef == "" {
return runHydrated, fmt.Errorf("plan action for %q: missing 'provider' field in resource config (delete actions require a current state record)", action.Resource.Name)
return runHydrated, fmt.Errorf("plan action for %q: missing 'iac_provider' or 'provider' field in resource config (delete actions require a current state record)", action.Resource.Name)
}
if _, exists := groups[moduleRef]; !exists {
def, ok := providerDefs[moduleRef]
if !ok {
return runHydrated, fmt.Errorf("plan action for %q references provider %q which is not declared as an iac.provider module", action.Resource.Name, moduleRef)
return runHydrated, fmt.Errorf("plan action for %q references iac.provider module %q (resolved from iac_provider/provider field) which is not declared as an iac.provider module", action.Resource.Name, moduleRef)
}
groups[moduleRef] = &actionGroup{provType: def.provType, provCfg: def.provCfg}
groupOrder = append(groupOrder, moduleRef)
Expand Down Expand Up @@ -1339,7 +1337,7 @@ func applyPrecomputedPlanWithStore(ctx context.Context, plan interfaces.IaCPlan,
for i := range plan.Actions {
if plan.Actions[i].Resource.Name == r.Name {
appliedCfg = plan.Actions[i].Resource.Config
providerRef, _ = plan.Actions[i].Resource.Config["provider"].(string)
providerRef = resolveIaCProviderRef(plan.Actions[i].Resource.Config)
dependencies = append([]string(nil), plan.Actions[i].Resource.DependsOn...)
break
}
Expand Down
4 changes: 2 additions & 2 deletions cmd/wfctl/infra_apply_dryrun.go
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ func printDryRunJSON(cfgFile, envName string, plan interfaces.IaCPlan, providerG
actions := make([]DryRunAction, 0, len(plan.Actions))
for i := range plan.Actions {
a := &plan.Actions[i]
provRef, _ := a.Resource.Config["provider"].(string)
provRef := resolveIaCProviderRef(a.Resource.Config)
actions = append(actions, DryRunAction{
Action: a.Action,
ResourceName: a.Resource.Name,
Expand Down Expand Up @@ -223,7 +223,7 @@ func collectProviderGroups(cfgFile, envName string, specs []interfaces.ResourceS
if !strings.HasPrefix(spec.Type, "infra.") {
continue
}
moduleRef, _ := spec.Config["provider"].(string)
moduleRef := resolveIaCProviderRef(spec.Config)
if moduleRef == "" {
continue
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/wfctl/infra_destroy.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ func destroyInfraModules(ctx context.Context, cfgFile, envName string) error { /
moduleRef := resourceStateProviderRef(*st)
if spec, ok := specByName[st.Name]; ok {
if moduleRef == "" {
moduleRef, _ = spec.Config["provider"].(string)
moduleRef = resolveIaCProviderRef(spec.Config)
}
}

Expand Down
74 changes: 74 additions & 0 deletions cmd/wfctl/infra_iac_provider_ref_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package main

import "testing"

// TestResolveIaCProviderRef covers the disambiguation between an
// implementation-level "provider" field (e.g. infra.eventbus's provider="nats")
// and the iac.provider module reference. The canonical key is iac_provider;
// "provider" is the backward-compat fallback.
func TestResolveIaCProviderRef(t *testing.T) {
cases := []struct {
name string
cfg map[string]any
want string
}{
{
name: "iac_provider_only",
cfg: map[string]any{"iac_provider": "do-provider"},
want: "do-provider",
},
{
name: "provider_only_back_compat",
cfg: map[string]any{"provider": "do-provider"},
want: "do-provider",
},
{
name: "iac_provider_wins_over_provider",
cfg: map[string]any{
"iac_provider": "do-provider",
"provider": "nats", // implementation, ignored for IaC routing
},
want: "do-provider",
},
{
name: "empty_iac_provider_falls_back",
cfg: map[string]any{
"iac_provider": "",
"provider": "do-provider",
},
want: "do-provider",
},
{
name: "neither_set",
cfg: map[string]any{},
want: "",
},
{
name: "nil_config",
cfg: nil,
want: "",
},
{
name: "non_string_iac_provider_falls_back",
cfg: map[string]any{
"iac_provider": 42, // type mismatch, treated as missing
"provider": "do-provider",
},
want: "do-provider",
},
{
name: "non_string_provider",
cfg: map[string]any{"provider": 42},
want: "",
},
}

for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got := resolveIaCProviderRef(tc.cfg)
if got != tc.want {
t.Errorf("resolveIaCProviderRef(%v) = %q; want %q", tc.cfg, got, tc.want)
}
})
}
}
8 changes: 4 additions & 4 deletions cmd/wfctl/infra_provider_dispatch.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,18 +115,18 @@ func groupSpecsByProviderRef(specs []interfaces.ResourceSpec, defs map[string]pr
for _, spec := range specs {
var moduleRef string
if spec.Config != nil {
moduleRef, _ = spec.Config["provider"].(string)
moduleRef = resolveIaCProviderRef(spec.Config)
}
if moduleRef == "" {
return nil, nil, fmt.Errorf("infra module %q (%s): missing required 'provider' field", spec.Name, spec.Type)
return nil, nil, fmt.Errorf("infra module %q (%s): missing required 'iac_provider' or 'provider' field", spec.Name, spec.Type)
}
if _, exists := groups[moduleRef]; !exists {
def, ok := defs[moduleRef]
if !ok {
if _, isDisabled := disabled[moduleRef]; isDisabled {
return nil, nil, fmt.Errorf("infra module %q references provider %q which is disabled for environment %q", spec.Name, moduleRef, envName)
return nil, nil, fmt.Errorf("infra module %q references iac.provider %q which is disabled for environment %q", spec.Name, moduleRef, envName)
}
return nil, nil, fmt.Errorf("infra module %q references provider %q which is not declared as an iac.provider module", spec.Name, moduleRef)
return nil, nil, fmt.Errorf("infra module %q references iac.provider module %q (resolved from iac_provider/provider field) which is not declared as an iac.provider module", spec.Name, moduleRef)
}
if def.provType == "" {
return nil, nil, fmt.Errorf("provider module %q has no 'provider' type configured", moduleRef)
Expand Down
2 changes: 1 addition & 1 deletion cmd/wfctl/infra_status_drift.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ func groupStatesByProvider(states []interfaces.ResourceState, cfgFile, envName s
moduleRef := resourceStateProviderRef(*st)
if spec, ok := specByName[st.Name]; ok {
if moduleRef == "" {
moduleRef, _ = spec.Config["provider"].(string)
moduleRef = resolveIaCProviderRef(spec.Config)
}
}
if moduleRef == "" {
Expand Down
Loading