| // Copyright (c) HashiCorp, Inc. |
| // SPDX-License-Identifier: BUSL-1.1 |
| |
| package stackruntime |
| |
| import ( |
| "context" |
| "fmt" |
| "sync/atomic" |
| |
| "github.com/hashicorp/terraform/internal/addrs" |
| "github.com/hashicorp/terraform/internal/depsfile" |
| "github.com/hashicorp/terraform/internal/providers" |
| "github.com/hashicorp/terraform/internal/stacks/stackaddrs" |
| "github.com/hashicorp/terraform/internal/stacks/stackconfig" |
| "github.com/hashicorp/terraform/internal/stacks/stackplan" |
| "github.com/hashicorp/terraform/internal/stacks/stackruntime/internal/stackeval" |
| "github.com/hashicorp/terraform/internal/stacks/stackstate" |
| "github.com/hashicorp/terraform/internal/tfdiags" |
| ) |
| |
| // Apply performs the changes described in a previously-generated plan, |
| // aiming to make the real system converge with the desired state and |
| // then emit a series of patches that the caller must make to the |
| // current state to represent what has changed. |
| // |
| // Apply does not return a result directly because it emits results in a |
| // streaming fashion using channels provided in the given [ApplyResponse]. |
| // |
| // Callers must not modify any values reachable directly or indirectly |
| // through resp after passing it to this function, aside from the implicit |
| // modifications to the internal state of channels caused by reading them. |
| func Apply(ctx context.Context, req *ApplyRequest, resp *ApplyResponse) { |
| resp.Complete = false // We'll reset this to true only if we actually succeed |
| |
| var seenAnyErrors atomic.Bool |
| outp := stackeval.ApplyOutput{ |
| AnnounceAppliedChange: func(ctx context.Context, change stackstate.AppliedChange) { |
| resp.AppliedChanges <- change |
| }, |
| AnnounceDiagnostics: func(ctx context.Context, diags tfdiags.Diagnostics) { |
| for _, diag := range diags { |
| if diag.Severity() == tfdiags.Error { |
| seenAnyErrors.Store(true) // never becomes false again |
| } |
| resp.Diagnostics <- diag |
| } |
| }, |
| } |
| |
| // Whatever return path we take, we must close our channels to allow |
| // a caller to see that the operation is complete. |
| defer func() { |
| close(resp.Diagnostics) |
| close(resp.AppliedChanges) // MUST be the last channel to close |
| }() |
| |
| main, err := stackeval.ApplyPlan( |
| ctx, |
| req.Config, |
| req.Plan, |
| stackeval.ApplyOpts{ |
| InputVariableValues: req.InputValues, |
| ProviderFactories: req.ProviderFactories, |
| ExperimentsAllowed: req.ExperimentsAllowed, |
| DependencyLocks: req.DependencyLocks, |
| }, |
| outp, |
| ) |
| if err != nil { |
| // An error here means that the apply wasn't even able to _start_, |
| // typically because the request itself was invalid. We'll announce |
| // that as a diagnostic and then halt, though if we get here then |
| // it's most likely a bug in the caller rather than end-user error. |
| resp.Diagnostics <- tfdiags.Sourceless( |
| tfdiags.Error, |
| "Invalid apply request", |
| fmt.Sprintf("Cannot begin the apply phase: %s.", err), |
| ) |
| return |
| } |
| |
| if !seenAnyErrors.Load() { |
| resp.Complete = true |
| } |
| |
| cleanupDiags := main.DoCleanup(ctx) |
| for _, diag := range cleanupDiags { |
| // cleanup diagnostics don't stop the apply from being "complete", |
| // since this should include only transient operational errors such |
| // as failing to terminate a provider plugin. |
| resp.Diagnostics <- diag |
| } |
| |
| } |
| |
| // ApplyRequest represents the inputs to an [Apply] call. |
| type ApplyRequest struct { |
| Config *stackconfig.Config |
| Plan *stackplan.Plan |
| |
| InputValues map[stackaddrs.InputVariable]ExternalInputValue |
| ProviderFactories map[addrs.Provider]providers.Factory |
| |
| ExperimentsAllowed bool |
| DependencyLocks depsfile.Locks |
| } |
| |
| // ApplyResponse is used by [Apply] to describe the results of applying. |
| // |
| // [Apply] produces streaming results throughout its execution, and so it |
| // communicates with the caller by writing to provided channels during its work |
| // and then modifying other fields in this structure before returning. Callers |
| // MUST NOT access any non-channel fields of ApplyResponse until the |
| // AppliedChanges channel has been closed to signal the completion of the |
| // apply process. |
| type ApplyResponse struct { |
| // [Apply] will set this field to true if the apply ran to completion |
| // without encountering any errors, or set this to false if not. |
| // |
| // A caller might react to Complete: true by creating one follow-up plan |
| // just to confirm that everything has converged and then, if so, consider |
| // all of the configuration versions that contributed to this plan to now |
| // be converged. If unsuccessful, none of the contributing configurations |
| // are known to be converged and the operator will need to decide whether |
| // to immediately try creating a new plan (if they think the error was |
| // transient) or push a new configuration update to correct the problem. |
| // |
| // If this field is false after applying is complete then it's likely that |
| // at least some of the planned side-effects already occurred, and so |
| // it's important to still handle anything that was written to the |
| // AppliedChanges channel to partially update the state with the subset |
| // of changes that were completed. |
| // |
| // The initial value of this field is ignored; there's no reason to set |
| // it to anything other than the zero value. |
| Complete bool |
| |
| // AppliedChanges is the channel that will be sent each individual |
| // applied change, in no predictable order, during the apply |
| // operation. |
| // |
| // Callers MUST provide a non-nil channel and read from it from |
| // another Goroutine throughout the apply operation, or apply |
| // progress will be blocked. Callers that read slowly should provide |
| // a buffered channel to reduce the backpressure they exert on the |
| // apply process. |
| // |
| // The apply operation will close this channel before it returns. |
| // AppliedChanges is guaranteed to be the last channel to close |
| // (i.e. after Diagnostics is closed) so callers can use the close |
| // signal of this channel alone to mark that the apply process is |
| // over, but if Diagnostics is a buffered channel they must take |
| // care to deplete its buffer afterwards to avoid losing diagnostics |
| // delivered near the end of the apply process. |
| AppliedChanges chan<- stackstate.AppliedChange |
| |
| // Diagnostics is the channel that will be sent any diagnostics |
| // that arise during the apply process, in no particular order. |
| // |
| // In particular note that there's no guarantee that the diagnostics |
| // for applying changes to a particular object will be emitted in close |
| // proximity to an AppliedChanges write for that same object. Diagnostics |
| // and applied changes are totally decoupled, since diagnostics might be |
| // collected up and emitted later as a large batch if the runtime |
| // needs to perform aggregate operations such as deduplication on |
| // the diagnostics before exposing them. |
| // |
| // Callers MUST provide a non-nil channel and read from it from |
| // another Goroutine throughout the plan operation, or apply |
| // progress will be blocked. Callers that read slowly should provide |
| // a buffered channel to reduce the backpressure they exert on the |
| // apply process. |
| // |
| // The apply operation will close this channel before it returns, but |
| // callers should use the close event of AppliedChanges as the definitive |
| // signal that planning is complete. |
| Diagnostics chan<- tfdiags.Diagnostic |
| } |