2023-09-27 20:00:36 -04:00
|
|
|
// Copyright IBM Corp. 2014, 2026
|
|
|
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
|
|
2023-09-21 15:05:15 -04:00
|
|
|
package stackruntime
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"fmt"
|
|
|
|
|
"sync/atomic"
|
|
|
|
|
|
|
|
|
|
"github.com/hashicorp/terraform/internal/addrs"
|
2024-06-27 08:55:13 -04:00
|
|
|
"github.com/hashicorp/terraform/internal/depsfile"
|
2023-09-21 15:05:15 -04:00
|
|
|
"github.com/hashicorp/terraform/internal/providers"
|
2024-06-12 21:47:31 -04:00
|
|
|
"github.com/hashicorp/terraform/internal/stacks/stackaddrs"
|
2023-09-21 15:05:15 -04:00
|
|
|
"github.com/hashicorp/terraform/internal/stacks/stackconfig"
|
2024-07-12 22:20:34 -04:00
|
|
|
"github.com/hashicorp/terraform/internal/stacks/stackplan"
|
2023-09-21 15:05:15 -04:00
|
|
|
"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
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
}
|
|
|
|
|
|
2023-09-25 16:50:55 -04:00
|
|
|
// 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
|
|
|
|
|
}()
|
|
|
|
|
|
2023-09-21 15:05:15 -04:00
|
|
|
main, err := stackeval.ApplyPlan(
|
|
|
|
|
ctx,
|
|
|
|
|
req.Config,
|
2024-07-12 22:20:34 -04:00
|
|
|
req.Plan,
|
2023-09-21 15:05:15 -04:00
|
|
|
stackeval.ApplyOpts{
|
2024-06-18 22:26:13 -04:00
|
|
|
InputVariableValues: req.InputValues,
|
|
|
|
|
ProviderFactories: req.ProviderFactories,
|
|
|
|
|
ExperimentsAllowed: req.ExperimentsAllowed,
|
2024-06-27 08:55:13 -04:00
|
|
|
DependencyLocks: req.DependencyLocks,
|
2023-09-21 15:05:15 -04:00
|
|
|
},
|
|
|
|
|
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 {
|
2024-07-12 22:20:34 -04:00
|
|
|
Config *stackconfig.Config
|
|
|
|
|
Plan *stackplan.Plan
|
2023-09-21 15:05:15 -04:00
|
|
|
|
2024-06-12 21:47:31 -04:00
|
|
|
InputValues map[stackaddrs.InputVariable]ExternalInputValue
|
2023-09-21 15:05:15 -04:00
|
|
|
ProviderFactories map[addrs.Provider]providers.Factory
|
2024-02-09 21:12:52 -05:00
|
|
|
|
|
|
|
|
ExperimentsAllowed bool
|
2024-06-27 08:55:13 -04:00
|
|
|
DependencyLocks depsfile.Locks
|
2023-09-21 15:05:15 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 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
|
|
|
|
|
}
|