mirror of
https://github.com/hashicorp/terraform.git
synced 2026-03-22 02:20:07 -04:00
147 lines
5.2 KiB
Go
147 lines
5.2 KiB
Go
// Copyright IBM Corp. 2014, 2026
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package graph
|
|
|
|
import (
|
|
"fmt"
|
|
"log"
|
|
"path/filepath"
|
|
|
|
"github.com/hashicorp/hcl/v2"
|
|
|
|
"github.com/hashicorp/terraform/internal/addrs"
|
|
"github.com/hashicorp/terraform/internal/configs"
|
|
"github.com/hashicorp/terraform/internal/lang"
|
|
"github.com/hashicorp/terraform/internal/moduletest"
|
|
"github.com/hashicorp/terraform/internal/plans"
|
|
"github.com/hashicorp/terraform/internal/providers"
|
|
"github.com/hashicorp/terraform/internal/terraform"
|
|
"github.com/hashicorp/terraform/internal/tfdiags"
|
|
)
|
|
|
|
// testPlan defines how to execute a run block representing a plan command
|
|
//
|
|
// See also: (n *NodeTestRun).testApply
|
|
func (n *NodeTestRun) testPlan(ctx *EvalContext, variables terraform.InputValues, providers map[addrs.RootProviderConfig]providers.Interface, waiter *operationWaiter) {
|
|
file, run := n.File(), n.run
|
|
config := run.ModuleConfig
|
|
|
|
// FilterVariablesToModule only returns warnings, so we don't check the
|
|
// returned diags for errors.
|
|
setVariables, testOnlyVariables, setVariableDiags := FilterVariablesToModule(run.ModuleConfig, variables)
|
|
run.Diagnostics = run.Diagnostics.Append(setVariableDiags)
|
|
|
|
// ignore diags because validate has covered it
|
|
tfCtx, _ := terraform.NewContext(n.opts.ContextOpts)
|
|
|
|
// execute the terraform plan operation
|
|
planScope, plan, originalDiags := plan(ctx, tfCtx, file.Config, run.Config, run.ModuleConfig, setVariables, providers, waiter)
|
|
// We exclude the diagnostics that are expected to fail from the plan
|
|
// diagnostics, and if an expected failure is not found, we add a new error diagnostic.
|
|
planDiags := moduletest.ValidateExpectedFailures(run.Config, originalDiags)
|
|
|
|
if ctx.Verbose() {
|
|
// in verbose mode, we still add all the original diagnostics for
|
|
// display.
|
|
run.Diagnostics = run.Diagnostics.Append(originalDiags)
|
|
} else {
|
|
run.Diagnostics = run.Diagnostics.Append(planDiags)
|
|
}
|
|
|
|
if planDiags.HasErrors() {
|
|
run.Status = moduletest.Error
|
|
return
|
|
}
|
|
|
|
if ctx.Verbose() {
|
|
schemas, diags := tfCtx.Schemas(config, plan.PriorState)
|
|
|
|
// If we're going to fail to render the plan, let's not fail the overall
|
|
// test. It can still have succeeded. So we'll add the diagnostics, but
|
|
// still report the test status as a success.
|
|
if diags.HasErrors() {
|
|
// This is very unlikely.
|
|
diags = diags.Append(tfdiags.Sourceless(
|
|
tfdiags.Warning,
|
|
"Failed to print verbose output",
|
|
fmt.Sprintf("Terraform failed to print the verbose output for %s, other diagnostics will contain more details as to why.", filepath.Join(file.Name, run.Name))))
|
|
} else {
|
|
run.Verbose = &moduletest.Verbose{
|
|
Plan: plan,
|
|
State: nil, // We don't have a state to show in plan mode.
|
|
Config: config,
|
|
Providers: schemas.Providers,
|
|
Provisioners: schemas.Provisioners,
|
|
}
|
|
}
|
|
|
|
run.Diagnostics = run.Diagnostics.Append(diags)
|
|
}
|
|
|
|
// Evaluate the run block directly in the graph context to validate the assertions
|
|
// of the run. We also pass in all the
|
|
// previous contexts so this run block can refer to outputs from
|
|
// previous run blocks.
|
|
status, outputVals, moreDiags := ctx.EvaluateRun(run.Config, run.ModuleConfig.Module, planScope, testOnlyVariables)
|
|
run.Status = run.Status.Merge(status)
|
|
run.Diagnostics = run.Diagnostics.Append(moreDiags)
|
|
run.Outputs = outputVals
|
|
}
|
|
|
|
func plan(ctx *EvalContext, tfCtx *terraform.Context, file *configs.TestFile, run *configs.TestRun, module *configs.Config, variables terraform.InputValues, providers map[addrs.RootProviderConfig]providers.Interface, waiter *operationWaiter) (*lang.Scope, *plans.Plan, tfdiags.Diagnostics) {
|
|
log.Printf("[TRACE] TestFileRunner: called plan for %s", run.Name)
|
|
|
|
var diags tfdiags.Diagnostics
|
|
|
|
targets, targetDiags := moduletest.GetRunTargets(run)
|
|
diags = diags.Append(targetDiags)
|
|
|
|
replaces, replaceDiags := moduletest.GetRunReplaces(run)
|
|
diags = diags.Append(replaceDiags)
|
|
|
|
references, referenceDiags := moduletest.GetRunReferences(run)
|
|
diags = diags.Append(referenceDiags)
|
|
|
|
state, err := ctx.LoadState(run)
|
|
if err != nil {
|
|
diags = diags.Append(&hcl.Diagnostic{
|
|
Severity: hcl.DiagError,
|
|
Summary: "Failed to load state",
|
|
Detail: fmt.Sprintf("Could not retrieve state for run %s: %s.", run.Name, err),
|
|
Subject: run.Backend.DeclRange.Ptr(),
|
|
})
|
|
}
|
|
|
|
if diags.HasErrors() {
|
|
return nil, nil, diags
|
|
}
|
|
|
|
planOpts := &terraform.PlanOpts{
|
|
Mode: func() plans.Mode {
|
|
switch run.Options.Mode {
|
|
case configs.RefreshOnlyTestMode:
|
|
return plans.RefreshOnlyMode
|
|
default:
|
|
return plans.NormalMode
|
|
}
|
|
}(),
|
|
Targets: targets,
|
|
ForceReplace: replaces,
|
|
SkipRefresh: !run.Options.Refresh,
|
|
SetVariables: variables,
|
|
ExternalReferences: references,
|
|
ExternalProviders: providers,
|
|
Overrides: ctx.GetOverrides(run.Name),
|
|
DeferralAllowed: ctx.deferralAllowed,
|
|
AllowRootEphemeralOutputs: true,
|
|
}
|
|
|
|
waiter.update(tfCtx, moduletest.Running, nil)
|
|
log.Printf("[DEBUG] TestFileRunner: starting plan for %s", run.Name)
|
|
plan, scope, planDiags := tfCtx.PlanAndEval(module, state, planOpts)
|
|
log.Printf("[DEBUG] TestFileRunner: completed plan for %s", run.Name)
|
|
diags = diags.Append(planDiags)
|
|
|
|
return scope, plan, diags
|
|
}
|