blob: 2481fbd527e073f53acdf5d66ebd814bf71eda64 [file] [log] [blame]
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package stackruntime
import (
"context"
"time"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/depsfile"
"github.com/hashicorp/terraform/internal/plans"
"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"
)
// Plan evaluates the given configuration to calculate a desired state,
// updates the given prior state to match the current state of real
// infrastructure, and then compares the desired state with the updated prior
// state to produce a proposed set of changes that should reduce the number
// of differences between the two.
//
// Plan does not return a result directly because it emits results in a
// streaming fashion using channels provided in the given [PlanResponse].
//
// 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 Plan(ctx context.Context, req *PlanRequest, resp *PlanResponse) {
// 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.PlannedChanges) // MUST be the last channel to close
}()
var errored bool
planTimestamp := time.Now().UTC()
if req.ForcePlanTimestamp != nil {
planTimestamp = *req.ForcePlanTimestamp
}
main := stackeval.NewForPlanning(req.Config, req.PrevState, stackeval.PlanOpts{
PlanningMode: req.PlanMode,
InputVariableValues: req.InputValues,
ProviderFactories: req.ProviderFactories,
DependencyLocks: req.DependencyLocks,
PlanTimestamp: planTimestamp,
})
main.AllowLanguageExperiments(req.ExperimentsAllowed)
main.PlanAll(ctx, stackeval.PlanOutput{
AnnouncePlannedChange: func(ctx context.Context, change stackplan.PlannedChange) {
resp.PlannedChanges <- change
},
AnnounceDiagnostics: func(ctx context.Context, diags tfdiags.Diagnostics) {
for _, diag := range diags {
if diag.Severity() == tfdiags.Error {
errored = true
}
resp.Diagnostics <- diag
}
},
})
cleanupDiags := main.DoCleanup(ctx)
for _, diag := range cleanupDiags {
// cleanup diagnostics don't stop a plan from being applyable, because
// the cleanup process should not affect the content of and validity
// of the plan. This should only include transient operational errors
// such as failing to terminate a provider plugin.
resp.Diagnostics <- diag
}
// An overall stack plan is applyable if it has no error diagnostics.
resp.Applyable = !errored
// Before we return we'll emit one more special planned change just to
// remember in the raw plan sequence whether we considered this plan to be
// applyable, so we don't need to rely on the caller to remember
// resp.Applyable separately.
resp.PlannedChanges <- &stackplan.PlannedChangeApplyable{
Applyable: resp.Applyable,
}
}
// PlanRequest represents the inputs to a [Plan] call.
type PlanRequest struct {
PlanMode plans.Mode
Config *stackconfig.Config
PrevState *stackstate.State
InputValues map[stackaddrs.InputVariable]ExternalInputValue
ProviderFactories map[addrs.Provider]providers.Factory
DependencyLocks depsfile.Locks
// ForcePlanTimestamp, if not nil, will force the plantimestamp function
// to return the given value instead of whatever real time the plan
// operation started. This is for testing purposes only.
ForcePlanTimestamp *time.Time
ExperimentsAllowed bool
}
// PlanResponse is used by [Plan] to describe the results of planning.
//
// [Plan] 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 fields of PlanResponse until the PlannedChanges
// channel has been closed to signal the completion of the planning process.
type PlanResponse struct {
// [Plan] will set this field to true if the plan ran to completion and
// is valid enough to be applied, or set this to false if not.
//
// The initial value of this field is ignored; there's no reason to set
// it to anything other than the zero value.
Applyable bool
// PlannedChanges is the channel that will be sent each individual
// planned change, in no predictable order, during the planning
// operation.
//
// Callers MUST provide a non-nil channel and read from it from
// another Goroutine throughout the plan operation, or planning
// progress will be blocked. Callers that read slowly should provide
// a buffered channel to reduce the backpressure they exert on the
// planning process.
//
// The plan operation will close this channel before it returns
// PlannedChanges 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 plan 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 planning process.
PlannedChanges chan<- stackplan.PlannedChange
// Diagnostics is the channel that will be sent any diagnostics
// that arise during the planning process, in no particular order.
//
// In particular note that there's no guarantee that the diagnostics
// for planning a particular object will be emitted in close proximity
// to a PlannedChanges write for that same object. Diagnostics and
// planned 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 planning
// progress will be blocked. Callers that read slowly should provide
// a buffered channel to reduce the backpressure they exert on the
// planning process.
//
// The plan operation will close this channel before it returns, but
// callers should use the close event of PlannedChanges as the definitive
// signal that planning is complete.
Diagnostics chan<- tfdiags.Diagnostic
}
type ExternalInputValue = stackeval.ExternalInputValue