Skip to content

Commit 4c1cc1b

Browse files
committed
fix(iac): let drivers suppress new-resource creates
1 parent 644200a commit 4c1cc1b

2 files changed

Lines changed: 78 additions & 5 deletions

File tree

platform/differ.go

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -108,11 +108,13 @@ func ComputePlan(ctx context.Context, p interfaces.IaCProvider, desired []interf
108108
hash = configHash(spec.Config)
109109
}
110110
if rs, exists := currentMap[spec.Name]; !exists {
111-
creates = append(creates, interfaces.PlanAction{
112-
Action: "create",
113-
Resource: spec,
114-
ResolvedConfigHash: hash,
115-
})
111+
create, err := classifyCreate(ctx, p, spec, hash)
112+
if err != nil {
113+
return interfaces.IaCPlan{}, err
114+
}
115+
if create != nil {
116+
creates = append(creates, *create)
117+
}
116118
} else {
117119
candidates = append(candidates, modCandidate{
118120
spec: spec,
@@ -350,6 +352,37 @@ func classifyModification(ctx context.Context, p interfaces.IaCProvider, spec in
350352
return nil
351353
}
352354

355+
// classifyCreate decides whether a desired resource absent from local state
356+
// should produce a create action. If a provider driver is available, it gets
357+
// one Diff call against nil current so externally-existing resources can be
358+
// treated as no-op. Nil provider / nil driver preserves legacy create behavior.
359+
func classifyCreate(ctx context.Context, p interfaces.IaCProvider, spec interfaces.ResourceSpec, hash string) (*interfaces.PlanAction, error) {
360+
create := &interfaces.PlanAction{
361+
Action: "create",
362+
Resource: spec,
363+
ResolvedConfigHash: hash,
364+
}
365+
if p == nil {
366+
return create, nil
367+
}
368+
driver, err := p.ResourceDriver(spec.Type)
369+
if err != nil {
370+
return nil, fmt.Errorf("provider.ResourceDriver(%q): %w", spec.Type, err)
371+
}
372+
if driver == nil {
373+
return create, nil
374+
}
375+
diff, err := driver.Diff(ctx, spec, nil)
376+
if err != nil {
377+
return nil, fmt.Errorf("provider.Diff(%q/%q): %w", spec.Type, spec.Name, err)
378+
}
379+
if diff == nil || (!diff.NeedsUpdate && !diff.NeedsReplace && !hasForceNew(diff.Changes)) {
380+
return nil, nil
381+
}
382+
create.Changes = diff.Changes
383+
return create, nil
384+
}
385+
353386
// resourceStateToOutput converts the persisted ResourceState into the
354387
// *interfaces.ResourceOutput shape that ResourceDriver.Diff expects.
355388
// Sensitive map is not reconstructed here — drivers that need it should

platform/differ_test.go

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,46 @@ func TestDiffer_NewResource(t *testing.T) {
5858
}
5959
}
6060

61+
func TestComputePlan_NewResource_DriverNoopDiffSkipsCreate(t *testing.T) {
62+
desired := []interfaces.ResourceSpec{
63+
{Name: "delegation", Type: "infra.dns_delegation", Config: map[string]any{"domain": "example.com"}},
64+
}
65+
driver := &fakeDriver{diff: &interfaces.DiffResult{NeedsUpdate: false}}
66+
provider := &fakeProvider{driver: driver}
67+
68+
plan, err := platform.ComputePlan(context.Background(), provider, desired, nil)
69+
if err != nil {
70+
t.Fatalf("unexpected error: %v", err)
71+
}
72+
73+
if len(plan.Actions) != 0 {
74+
t.Fatalf("expected no actions for driver no-op new resource, got %+v", plan.Actions)
75+
}
76+
if got := driver.diffCallCount.Load(); got != 1 {
77+
t.Fatalf("Diff call count = %d, want 1", got)
78+
}
79+
}
80+
81+
func TestComputePlan_NewResource_DriverUpdateDiffCreates(t *testing.T) {
82+
desired := []interfaces.ResourceSpec{
83+
{Name: "delegation", Type: "infra.dns_delegation", Config: map[string]any{"domain": "example.com"}},
84+
}
85+
driver := &fakeDriver{diff: &interfaces.DiffResult{NeedsUpdate: true}}
86+
provider := &fakeProvider{driver: driver}
87+
88+
plan, err := platform.ComputePlan(context.Background(), provider, desired, nil)
89+
if err != nil {
90+
t.Fatalf("unexpected error: %v", err)
91+
}
92+
93+
if len(plan.Actions) != 1 || plan.Actions[0].Action != "create" {
94+
t.Fatalf("expected one create action, got %+v", plan.Actions)
95+
}
96+
if got := driver.diffCallCount.Load(); got != 1 {
97+
t.Fatalf("Diff call count = %d, want 1", got)
98+
}
99+
}
100+
61101
func TestDiffer_DeletedResource(t *testing.T) {
62102
desired := []interfaces.ResourceSpec{}
63103
current := []interfaces.ResourceState{

0 commit comments

Comments
 (0)