Skip to content

Commit 203a7ad

Browse files
committed
Add CLI and backend policy wiring for plan apply and query
1 parent 91c960d commit 203a7ad

40 files changed

Lines changed: 2008 additions & 64 deletions

internal/backend/backendrun/local_run.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@
44
package backendrun
55

66
import (
7+
"context"
8+
79
"github.com/hashicorp/terraform/internal/configs"
810
"github.com/hashicorp/terraform/internal/plans"
11+
"github.com/hashicorp/terraform/internal/policy"
912
"github.com/hashicorp/terraform/internal/states"
1013
"github.com/hashicorp/terraform/internal/states/statemgr"
1114
"github.com/hashicorp/terraform/internal/terraform"
@@ -29,7 +32,11 @@ type Local interface {
2932
// backend's implementations of this to understand what this actually
3033
// does, because this operation has no well-defined contract aside from
3134
// "whatever it already does".
32-
LocalRun(*Operation) (*LocalRun, statemgr.Full, tfdiags.Diagnostics)
35+
LocalRun(context.Context, *Operation) (*LocalRun, statemgr.Full, tfdiags.Diagnostics)
36+
37+
// Finish should be called when the local run has completed executing and
38+
// the resources should be cleaned up.
39+
Finish()
3340
}
3441

3542
// LocalRun represents the assortment of objects that we can collect or
@@ -77,4 +84,17 @@ type LocalRun struct {
7784
//
7885
// This is nil when we're not applying a saved plan.
7986
Plan *plans.Plan
87+
88+
// PolicyClient is an optional argument that enables policy evaluations
89+
// during the run.
90+
PolicyClient policy.Client
91+
}
92+
93+
func (lr *LocalRun) Finish() {
94+
if lr == nil {
95+
return
96+
}
97+
if lr.PolicyClient != nil {
98+
lr.PolicyClient.Stop()
99+
}
80100
}

internal/backend/backendrun/operation.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"github.com/hashicorp/terraform/internal/depsfile"
1919
"github.com/hashicorp/terraform/internal/plans"
2020
"github.com/hashicorp/terraform/internal/plans/planfile"
21+
"github.com/hashicorp/terraform/internal/policy"
2122
"github.com/hashicorp/terraform/internal/states"
2223
"github.com/hashicorp/terraform/internal/terraform"
2324
"github.com/hashicorp/terraform/internal/tfdiags"
@@ -165,6 +166,11 @@ type Operation struct {
165166

166167
// Query is true if the operation should be a query operation
167168
Query bool
169+
170+
// PolicyPaths will trigger Terraform to load policies from the specified
171+
// paths.
172+
PolicyPaths []string
173+
PolicyClient policy.Client
168174
}
169175

170176
// HasConfig returns true if and only if the operation has a ConfigDir value

internal/backend/local/backend.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import (
1313
"sort"
1414
"sync"
1515

16+
"github.com/zclconf/go-cty/cty"
17+
1618
"github.com/hashicorp/terraform/internal/backend"
1719
"github.com/hashicorp/terraform/internal/backend/backendrun"
1820
"github.com/hashicorp/terraform/internal/command/views"
@@ -21,7 +23,6 @@ import (
2123
"github.com/hashicorp/terraform/internal/states/statemgr"
2224
"github.com/hashicorp/terraform/internal/terraform"
2325
"github.com/hashicorp/terraform/internal/tfdiags"
24-
"github.com/zclconf/go-cty/cty"
2526
)
2627

2728
const (
@@ -113,6 +114,10 @@ func NewWithBackend(backend backend.Backend) *Local {
113114
}
114115
}
115116

117+
func (b *Local) Finish() {
118+
// nothing to do
119+
}
120+
116121
func (b *Local) ConfigSchema() *configschema.Block {
117122
if b.Backend != nil {
118123
return b.Backend.ConfigSchema()

internal/backend/local/backend_apply.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,8 @@ func (b *Local) opApply(
5959

6060
// Get our context
6161
lr, _, opState, contextDiags := b.localRun(op)
62+
defer lr.Finish()
63+
6264
diags = diags.Append(contextDiags)
6365
if contextDiags.HasErrors() {
6466
op.ReportResult(runningOp, diags)
@@ -94,6 +96,8 @@ func (b *Local) opApply(
9496
combinedPlanApply := false
9597
// If we weren't given a plan, then we refresh/plan
9698
if op.PlanFile == nil {
99+
// set the policy client to nil for the plan preceding apply
100+
lr.PlanOpts.PolicyClient = nil
97101
combinedPlanApply = true
98102
// Perform the plan
99103
log.Printf("[INFO] backend/local: apply calling Plan")
@@ -110,6 +114,8 @@ func (b *Local) opApply(
110114
if plan != nil && (len(plan.Changes.Resources) != 0 || len(plan.Changes.Outputs) != 0) {
111115
op.View.Plan(plan, schemas)
112116
}
117+
// Report all policy results that may have accumulated during the plan
118+
op.View.PolicyResults(plan.PolicyResults)
113119
op.ReportResult(runningOp, diags)
114120
return
115121
}
@@ -420,14 +426,21 @@ func (b *Local) opApply(
420426
// Start the apply in a goroutine so that we can be interrupted.
421427
var applyState *states.State
422428
var applyDiags tfdiags.Diagnostics
429+
430+
// We use a new store for the apply policy results, as objects that failed during the plan policy
431+
// evaluation may have updated data which yields a different policy evaluation result.
432+
policyResults := plans.NewPolicyResults()
423433
doneCh := make(chan struct{})
424434
go func() {
425435
defer logging.PanicHandler()
426436
defer close(doneCh)
427437

428438
log.Printf("[INFO] backend/local: apply calling Apply")
429439
applyState, applyDiags = lr.Core.Apply(plan, lr.Config, &terraform.ApplyOpts{
430-
SetVariables: applyTimeValues,
440+
SetVariables: applyTimeValues,
441+
Locks: providerLocksSnapshot(op.DependencyLocks),
442+
PolicyClient: lr.PolicyClient,
443+
PolicyResults: policyResults,
431444
})
432445
}()
433446

@@ -436,6 +449,9 @@ func (b *Local) opApply(
436449
}
437450
diags = diags.Append(applyDiags)
438451

452+
// Print the policy results we found during apply
453+
op.View.PolicyResults(policyResults)
454+
439455
// Even on error with an empty state, the state value should not be nil.
440456
// Return early here to prevent corrupting any existing state.
441457
if diags.HasErrors() && applyState == nil {

internal/backend/local/backend_local.go

Lines changed: 31 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@ import (
1414
"github.com/hashicorp/hcl/v2"
1515
"github.com/zclconf/go-cty/cty"
1616

17+
"github.com/hashicorp/terraform/internal/addrs"
1718
"github.com/hashicorp/terraform/internal/backend/backendrun"
1819
"github.com/hashicorp/terraform/internal/command/arguments"
1920
"github.com/hashicorp/terraform/internal/configs"
2021
"github.com/hashicorp/terraform/internal/configs/configload"
22+
"github.com/hashicorp/terraform/internal/depsfile"
2123
"github.com/hashicorp/terraform/internal/lang"
2224
"github.com/hashicorp/terraform/internal/plans/planfile"
2325
"github.com/hashicorp/terraform/internal/states/statemgr"
@@ -26,7 +28,7 @@ import (
2628
)
2729

2830
// backendrun.Local implementation.
29-
func (b *Local) LocalRun(op *backendrun.Operation) (*backendrun.LocalRun, statemgr.Full, tfdiags.Diagnostics) {
31+
func (b *Local) LocalRun(ctx context.Context, op *backendrun.Operation) (*backendrun.LocalRun, statemgr.Full, tfdiags.Diagnostics) {
3032
// Make sure the type is invalid. We use this as a way to know not
3133
// to ask for input/validate. We're modifying this through a pointer,
3234
// so we're mutating an object that belongs to the caller here, which
@@ -35,7 +37,7 @@ func (b *Local) LocalRun(op *backendrun.Operation) (*backendrun.LocalRun, statem
3537
// happens to do.
3638
op.Type = backendrun.OperationTypeInvalid
3739

38-
op.StateLocker = op.StateLocker.WithContext(context.Background())
40+
op.StateLocker = op.StateLocker.WithContext(ctx)
3941

4042
lr, _, stateMgr, diags := b.localRun(op)
4143
return lr, stateMgr, diags
@@ -70,8 +72,6 @@ func (b *Local) localRun(op *backendrun.Operation) (*backendrun.LocalRun, *confi
7072
return nil, nil, nil, diags
7173
}
7274

73-
ret := &backendrun.LocalRun{}
74-
7575
// Initialize our context options
7676
var coreOpts terraform.ContextOpts
7777
if v := b.ContextOpts; v != nil {
@@ -80,11 +80,16 @@ func (b *Local) localRun(op *backendrun.Operation) (*backendrun.LocalRun, *confi
8080
coreOpts.UIInput = op.UIIn
8181
coreOpts.Hooks = op.Hooks
8282

83+
// the run must be closed now
84+
ret := &backendrun.LocalRun{
85+
PolicyClient: op.PolicyClient,
86+
}
87+
8388
var ctxDiags tfdiags.Diagnostics
8489
var configSnap *configload.Snapshot
8590
if op.PlanFile.IsCloud() {
8691
diags = diags.Append(fmt.Errorf("error: using a saved cloud plan when executing Terraform locally is not supported"))
87-
return nil, nil, nil, diags
92+
return ret, nil, nil, diags
8893
}
8994

9095
if lp, ok := op.PlanFile.Local(); ok {
@@ -100,7 +105,7 @@ func (b *Local) localRun(op *backendrun.Operation) (*backendrun.LocalRun, *confi
100105
ret, configSnap, ctxDiags = b.localRunForPlanFile(op, lp, ret, &coreOpts, stateMeta)
101106
if ctxDiags.HasErrors() {
102107
diags = diags.Append(ctxDiags)
103-
return nil, nil, nil, diags
108+
return ret, nil, nil, diags
104109
}
105110

106111
// Write sources into the cache of the main loader so that they are
@@ -112,7 +117,7 @@ func (b *Local) localRun(op *backendrun.Operation) (*backendrun.LocalRun, *confi
112117
}
113118
diags = diags.Append(ctxDiags)
114119
if diags.HasErrors() {
115-
return nil, nil, nil, diags
120+
return ret, nil, nil, diags
116121
}
117122

118123
// If we have an operation, then we automatically do the input/validate
@@ -126,7 +131,7 @@ func (b *Local) localRun(op *backendrun.Operation) (*backendrun.LocalRun, *confi
126131
inputDiags := ret.Core.Input(ret.Config, mode)
127132
diags = diags.Append(inputDiags)
128133
if inputDiags.HasErrors() {
129-
return nil, nil, nil, diags
134+
return ret, nil, nil, diags
130135
}
131136
}
132137

@@ -149,7 +154,7 @@ func (b *Local) localRunDirect(op *backendrun.Operation, run *backendrun.LocalRu
149154
rootMod, configDiags := op.ConfigLoader.LoadRootModule(op.ConfigDir)
150155
diags = diags.Append(configDiags)
151156
if configDiags.HasErrors() {
152-
return nil, nil, diags
157+
return run, nil, diags
153158
}
154159

155160
var rawVariables map[string]arguments.UnparsedVariableValue
@@ -170,7 +175,7 @@ func (b *Local) localRunDirect(op *backendrun.Operation, run *backendrun.LocalRu
170175
variables, varDiags := backendrun.ParseVariableValues(rawVariables, rootMod.Variables)
171176
diags = diags.Append(varDiags)
172177
if diags.HasErrors() {
173-
return nil, nil, diags
178+
return run, nil, diags
174179
}
175180

176181
planOpts := &terraform.PlanOpts{
@@ -183,6 +188,8 @@ func (b *Local) localRunDirect(op *backendrun.Operation, run *backendrun.LocalRu
183188
GenerateConfigPath: op.GenerateConfigOut,
184189
DeferralAllowed: op.DeferralAllowed,
185190
Query: op.Query,
191+
Locks: providerLocksSnapshot(op.DependencyLocks),
192+
PolicyClient: run.PolicyClient,
186193
}
187194
run.PlanOpts = planOpts
188195

@@ -193,7 +200,7 @@ func (b *Local) localRunDirect(op *backendrun.Operation, run *backendrun.LocalRu
193200
tfCtx, moreDiags := terraform.NewContext(coreOpts)
194201
diags = diags.Append(moreDiags)
195202
if moreDiags.HasErrors() {
196-
return nil, nil, diags
203+
return run, nil, diags
197204
}
198205
run.Core = tfCtx
199206

@@ -261,7 +268,7 @@ func (b *Local) localRunForPlanFile(op *backendrun.Operation, pf *planfile.Reade
261268
errSummary,
262269
fmt.Sprintf("Failed to read configuration snapshot from plan file: %s.", err),
263270
))
264-
return nil, snap, diags
271+
return run, snap, diags
265272
}
266273
loader := configload.NewLoaderFromSnapshot(snap)
267274
loader.AllowLanguageExperiments(op.ConfigLoader.AllowsLanguageExperiments())
@@ -299,7 +306,7 @@ func (b *Local) localRunForPlanFile(op *backendrun.Operation, pf *planfile.Reade
299306
errSummary,
300307
fmt.Sprintf("Failed to read prior state snapshot from plan file: %s.", err),
301308
))
302-
return nil, snap, diags
309+
return run, snap, diags
303310
}
304311

305312
if currentStateMeta != nil {
@@ -343,7 +350,7 @@ func (b *Local) localRunForPlanFile(op *backendrun.Operation, pf *planfile.Reade
343350
errSummary,
344351
fmt.Sprintf("Failed to read plan from plan file: %s.", err),
345352
))
346-
return nil, snap, diags
353+
return run, snap, diags
347354
}
348355
// When we're applying a saved plan, we populate Plan instead of PlanOpts,
349356
// because a plan object incorporates the subset of data from PlanOps that
@@ -377,7 +384,7 @@ func (b *Local) localRunForPlanFile(op *backendrun.Operation, pf *planfile.Reade
377384
tfCtx, moreDiags := terraform.NewContext(coreOpts)
378385
diags = diags.Append(moreDiags)
379386
if moreDiags.HasErrors() {
380-
return nil, nil, diags
387+
return run, nil, diags
381388
}
382389
run.Core = tfCtx
383390

@@ -596,3 +603,12 @@ func (v unparsedTestVariableValue) ParseVariableValue(mode configs.VariableParsi
596603
SourceRange: tfdiags.SourceRangeFromHCL(v.Expr.Range()),
597604
}, diags
598605
}
606+
607+
// providerLocksSnapshot returns a read-only snapshot of provider locks for
608+
// use during graph walks. Returns nil if locks is nil.
609+
func providerLocksSnapshot(locks *depsfile.Locks) map[addrs.Provider]*depsfile.ProviderLock {
610+
if locks == nil {
611+
return nil
612+
}
613+
return locks.AllProviders()
614+
}

internal/backend/local/backend_local_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ func TestLocalRun(t *testing.T) {
4848
StateLocker: stateLocker,
4949
}
5050

51-
_, _, diags := b.LocalRun(op)
51+
_, _, diags := b.LocalRun(context.Background(), op)
5252
if diags.HasErrors() {
5353
t.Fatalf("unexpected error: %s", diags.Err().Error())
5454
}
@@ -79,7 +79,7 @@ func TestLocalRun_error(t *testing.T) {
7979
StateLocker: stateLocker,
8080
}
8181

82-
_, _, diags := b.LocalRun(op)
82+
_, _, diags := b.LocalRun(context.Background(), op)
8383
if !diags.HasErrors() {
8484
t.Fatal("unexpected success")
8585
}
@@ -114,7 +114,7 @@ func TestLocalRun_cloudPlan(t *testing.T) {
114114
StateLocker: stateLocker,
115115
}
116116

117-
_, _, diags := b.LocalRun(op)
117+
_, _, diags := b.LocalRun(context.Background(), op)
118118
if !diags.HasErrors() {
119119
t.Fatal("unexpected success")
120120
}
@@ -201,7 +201,7 @@ func TestLocalRun_stalePlan(t *testing.T) {
201201
StateLocker: stateLocker,
202202
}
203203

204-
_, _, diags := b.LocalRun(op)
204+
_, _, diags := b.LocalRun(context.Background(), op)
205205
if !diags.HasErrors() {
206206
t.Fatal("unexpected success")
207207
}

internal/backend/local/backend_plan.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ func (b *Local) opPlan(
9090

9191
// Set up backend and get our context
9292
lr, configSnap, opState, ctxDiags := b.localRun(op)
93+
defer lr.Finish()
94+
9395
diags = diags.Append(ctxDiags)
9496
if ctxDiags.HasErrors() {
9597
op.ReportResult(runningOp, diags)
@@ -211,6 +213,9 @@ func (b *Local) opPlan(
211213
return
212214
}
213215

216+
// set the config sources of the plan
217+
plan.ConfigSources = op.ConfigLoader.Sources()
218+
214219
// Write out any generated config, before we render the plan.
215220
wroteConfig, moreDiags := maybeWriteGeneratedConfig(plan, op.GenerateConfigOut)
216221
diags = diags.Append(moreDiags)
@@ -221,6 +226,9 @@ func (b *Local) opPlan(
221226

222227
op.View.Plan(plan, schemas)
223228

229+
// Report all policy results that may have accumulated during the plan
230+
op.View.PolicyResults(plan.PolicyResults)
231+
224232
// If we've accumulated any diagnostics along the way then we'll show them
225233
// here just before we show the summary and next steps. This can potentially
226234
// include errors, because we intentionally try to show a partial plan

internal/backend/local/backend_refresh.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ func (b *Local) opRefresh(
4949

5050
// Get our context
5151
lr, _, opState, contextDiags := b.localRun(op)
52+
defer lr.Finish()
53+
5254
diags = diags.Append(contextDiags)
5355
if contextDiags.HasErrors() {
5456
op.ReportResult(runningOp, diags)

0 commit comments

Comments
 (0)