mirror of
https://github.com/hashicorp/terraform.git
synced 2026-02-19 02:39:17 -05:00
168 lines
6.7 KiB
Go
168 lines
6.7 KiB
Go
// 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
|