|
4 | 4 | "context" |
5 | 5 | "crypto/sha256" |
6 | 6 | "encoding/json" |
| 7 | + "errors" |
7 | 8 | "fmt" |
8 | 9 | "os" |
9 | 10 | "sort" |
@@ -108,11 +109,13 @@ func ComputePlan(ctx context.Context, p interfaces.IaCProvider, desired []interf |
108 | 109 | hash = configHash(spec.Config) |
109 | 110 | } |
110 | 111 | if rs, exists := currentMap[spec.Name]; !exists { |
111 | | - creates = append(creates, interfaces.PlanAction{ |
112 | | - Action: "create", |
113 | | - Resource: spec, |
114 | | - ResolvedConfigHash: hash, |
115 | | - }) |
| 112 | + create, err := classifyCreate(ctx, p, spec, hash) |
| 113 | + if err != nil { |
| 114 | + return interfaces.IaCPlan{}, err |
| 115 | + } |
| 116 | + if create != nil { |
| 117 | + creates = append(creates, *create) |
| 118 | + } |
116 | 119 | } else { |
117 | 120 | candidates = append(candidates, modCandidate{ |
118 | 121 | spec: spec, |
@@ -350,6 +353,64 @@ func classifyModification(ctx context.Context, p interfaces.IaCProvider, spec in |
350 | 353 | return nil |
351 | 354 | } |
352 | 355 |
|
| 356 | +// classifyCreate decides whether a desired resource absent from local state |
| 357 | +// should produce a create action. Drivers that opt in to ResourceAdoptionLocator |
| 358 | +// get one chance to locate/read an external resource and Diff it before create. |
| 359 | +// Nil provider, nil driver, or non-adoption drivers preserve legacy create |
| 360 | +// behavior. |
| 361 | +func classifyCreate(ctx context.Context, p interfaces.IaCProvider, spec interfaces.ResourceSpec, hash string) (*interfaces.PlanAction, error) { |
| 362 | + create := &interfaces.PlanAction{ |
| 363 | + Action: "create", |
| 364 | + Resource: spec, |
| 365 | + ResolvedConfigHash: hash, |
| 366 | + } |
| 367 | + if p == nil { |
| 368 | + return create, nil |
| 369 | + } |
| 370 | + driver := resourceDriverForCreate(p, spec.Type) |
| 371 | + if driver == nil { |
| 372 | + return create, nil |
| 373 | + } |
| 374 | + locator, ok := driver.(interfaces.ResourceAdoptionLocator) |
| 375 | + if !ok { |
| 376 | + return create, nil |
| 377 | + } |
| 378 | + ref, ok, err := locator.AdoptionRef(spec) |
| 379 | + if err != nil { |
| 380 | + return nil, fmt.Errorf("provider.AdoptionRef(%q/%q): %w", spec.Type, spec.Name, err) |
| 381 | + } |
| 382 | + if !ok { |
| 383 | + return create, nil |
| 384 | + } |
| 385 | + current, err := driver.Read(ctx, ref) |
| 386 | + if err != nil { |
| 387 | + if errors.Is(err, interfaces.ErrResourceNotFound) { |
| 388 | + return create, nil |
| 389 | + } |
| 390 | + return nil, fmt.Errorf("provider.Read(%q/%q): %w", spec.Type, spec.Name, err) |
| 391 | + } |
| 392 | + if current == nil { |
| 393 | + return create, nil |
| 394 | + } |
| 395 | + diff, err := driver.Diff(ctx, spec, current) |
| 396 | + if err != nil { |
| 397 | + return nil, fmt.Errorf("provider.Diff(%q/%q): %w", spec.Type, spec.Name, err) |
| 398 | + } |
| 399 | + if diff == nil || (!diff.NeedsUpdate && !diff.NeedsReplace && !hasForceNew(diff.Changes)) { |
| 400 | + return nil, nil |
| 401 | + } |
| 402 | + create.Changes = diff.Changes |
| 403 | + return create, nil |
| 404 | +} |
| 405 | + |
| 406 | +func resourceDriverForCreate(p interfaces.IaCProvider, resourceType string) interfaces.ResourceDriver { |
| 407 | + driver, err := p.ResourceDriver(resourceType) |
| 408 | + if err != nil { |
| 409 | + return nil |
| 410 | + } |
| 411 | + return driver |
| 412 | +} |
| 413 | + |
353 | 414 | // resourceStateToOutput converts the persisted ResourceState into the |
354 | 415 | // *interfaces.ResourceOutput shape that ResourceDriver.Diff expects. |
355 | 416 | // Sensitive map is not reconstructed here — drivers that need it should |
|
0 commit comments