Skip to content

Commit 0cd9be4

Browse files
committed
Add init-time policy evaluation
1 parent 49366dd commit 0cd9be4

25 files changed

Lines changed: 853 additions & 112 deletions

File tree

internal/command/arguments/init.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,10 @@ type Init struct {
7979
// TODO(SarahFrench/radeksimko): Remove this once the feature is no longer
8080
// experimental
8181
EnablePssExperiment bool
82+
83+
// PolicyPath contains an optional path to any defined policies that should
84+
// be applied for this plan operation.
85+
PolicyPaths []string
8286
}
8387

8488
// ParseInit processes CLI arguments, returning an Init value and errors.
@@ -112,6 +116,7 @@ func ParseInit(args []string, experimentsEnabled bool) (*Init, tfdiags.Diagnosti
112116
cmdFlags.BoolVar(&init.Json, "json", false, "json")
113117
cmdFlags.Var(&init.BackendConfig, "backend-config", "")
114118
cmdFlags.Var(&init.PluginPath, "plugin-dir", "plugin directory")
119+
cmdFlags.Var((*FlagStringSlice)(&init.PolicyPaths), "policies", "policies")
115120

116121
// Used for enabling experimental code that's invoked before configuration is parsed.
117122
cmdFlags.BoolVar(&init.EnablePssExperiment, "enable-pluggable-state-storage-experiment", false, "Enable the pluggable state storage experiment")

internal/command/command_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,7 @@ func testModuleWithSnapshot(t *testing.T, name string) (*configs.Config, *config
160160
// sources only this ultimately just records all of the module paths
161161
// in a JSON file so that we can load them below.
162162
inst := initwd.NewModuleInstaller(loader.ModulesDir(), loader, registry.NewClient(nil, nil), nil)
163-
_, instDiags := inst.InstallModules(context.Background(), dir, "tests", true, false, initwd.ModuleInstallHooksImpl{})
163+
_, instDiags := inst.InstallModules(context.Background(), dir, "tests", true, false)
164164
if instDiags.HasErrors() {
165165
t.Fatal(instDiags.Err())
166166
}

internal/command/graph_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -227,7 +227,7 @@ func TestGraph_resourcesOnly(t *testing.T) {
227227
t.Fatal(err)
228228
}
229229
inst := initwd.NewModuleInstaller(".terraform/modules", loader, registry.NewClient(nil, nil), nil)
230-
_, instDiags := inst.InstallModules(context.Background(), ".", "tests", true, false, initwd.ModuleInstallHooksImpl{})
230+
_, instDiags := inst.InstallModules(context.Background(), ".", "tests", true, false)
231231
if instDiags.HasErrors() {
232232
t.Fatal(instDiags.Err())
233233
}

internal/command/hook_module_install.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,13 @@ type view interface {
1515
Log(message string, params ...any)
1616
}
1717
type uiModuleInstallHooks struct {
18-
initwd.ModuleInstallHooksImpl
18+
initwd.ModuleInstallHookImpl
1919
Ui cli.Ui
2020
ShowLocalPaths bool
2121
View view
2222
}
2323

24-
var _ initwd.ModuleInstallHooks = uiModuleInstallHooks{}
24+
var _ initwd.ModuleInstallHook = uiModuleInstallHooks{}
2525

2626
func (h uiModuleInstallHooks) Download(modulePath, packageAddr string, v *version.Version) {
2727
if v != nil {

internal/command/init.go

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ import (
3333
"github.com/hashicorp/terraform/internal/getproviders"
3434
"github.com/hashicorp/terraform/internal/getproviders/providerreqs"
3535
"github.com/hashicorp/terraform/internal/getproviders/reattach"
36+
"github.com/hashicorp/terraform/internal/initwd"
37+
"github.com/hashicorp/terraform/internal/plans"
38+
"github.com/hashicorp/terraform/internal/policy"
3639
"github.com/hashicorp/terraform/internal/providercache"
3740
"github.com/hashicorp/terraform/internal/states"
3841
"github.com/hashicorp/terraform/internal/tfdiags"
@@ -76,7 +79,7 @@ func (c *InitCommand) Run(args []string) int {
7679
return c.run(initArgs, view)
7780
}
7881

79-
func (c *InitCommand) getModules(ctx context.Context, path, testsDir string, earlyRoot *configs.Module, upgrade bool, view views.Init) (output bool, abort bool, diags tfdiags.Diagnostics) {
82+
func (c *InitCommand) getModules(ctx context.Context, path, testsDir string, earlyRoot *configs.Module, upgrade bool, view views.Init, policyClient policy.Client) (output bool, abort bool, policyResults *plans.PolicyResults, diags tfdiags.Diagnostics) {
8083
testModules := false // We can also have modules buried in test files.
8184
for _, file := range earlyRoot.Tests {
8285
for _, run := range file.Runs {
@@ -88,7 +91,7 @@ func (c *InitCommand) getModules(ctx context.Context, path, testsDir string, ear
8891

8992
if len(earlyRoot.ModuleCalls) == 0 && !testModules {
9093
// Nothing to do
91-
return false, false, nil
94+
return false, false, nil, nil
9295
}
9396

9497
ctx, span := tracer.Start(ctx, "install modules", trace.WithAttributes(
@@ -102,13 +105,24 @@ func (c *InitCommand) getModules(ctx context.Context, path, testsDir string, ear
102105
view.Output(views.InitializingModulesMessage)
103106
}
104107

105-
hooks := uiModuleInstallHooks{
108+
uiHook := uiModuleInstallHooks{
106109
Ui: c.Ui,
107110
ShowLocalPaths: true,
108111
View: view,
109112
}
113+
hooks := []initwd.ModuleInstallHook{uiHook}
114+
var policyHook *policyModuleInstallHook
115+
if policyClient != nil {
116+
policyResults = plans.NewPolicyResults()
117+
policyHook = &policyModuleInstallHook{
118+
client: policyClient,
119+
rootModule: earlyRoot,
120+
policyResults: policyResults,
121+
}
122+
hooks = append(hooks, policyHook)
123+
}
110124

111-
installAbort, installDiags := c.installModules(ctx, path, testsDir, upgrade, false, hooks)
125+
installAbort, installDiags := c.installModules(ctx, path, testsDir, upgrade, false, hooks...)
112126
diags = diags.Append(installDiags)
113127

114128
// At this point, installModules may have generated error diags or been
@@ -128,7 +142,7 @@ func (c *InitCommand) getModules(ctx context.Context, path, testsDir string, ear
128142
}
129143
}
130144

131-
return true, installAbort, diags
145+
return true, installAbort, policyResults, diags
132146
}
133147

134148
func (c *InitCommand) initCloud(ctx context.Context, root *configs.Module, extraConfig arguments.FlagNameValueSlice, viewType arguments.ViewType, view views.Init) (be backend.Backend, output bool, diags tfdiags.Diagnostics) {
@@ -364,7 +378,7 @@ the backend configuration is present and valid.
364378
// The method downloads any missing providers that aren't already downloaded and then returns
365379
// dependency lock data based on the configuration.
366380
// The dependency lock file itself isn't updated here.
367-
func (c *InitCommand) getProvidersFromConfig(ctx context.Context, config *configs.Config, upgrade bool, pluginDirs []string, flagLockfile string, view views.Init) (output bool, resultingLocks *depsfile.Locks, diags tfdiags.Diagnostics) {
381+
func (c *InitCommand) getProvidersFromConfig(ctx context.Context, config *configs.Config, upgrade bool, pluginDirs []string, flagLockfile string, view views.Init, installerHook *providerInstallerHook) (output bool, resultingLocks *depsfile.Locks, diags tfdiags.Diagnostics) {
368382
ctx, span := tracer.Start(ctx, "install providers from config")
369383
defer span.End()
370384

@@ -422,6 +436,7 @@ func (c *InitCommand) getProvidersFromConfig(ctx context.Context, config *config
422436

423437
evts := c.prepareInstallerEvents(ctx, reqs, &diags, inst, view, views.InitializingProviderPluginFromConfigMessage, views.ReusingPreviousVersionInfo)
424438
ctx = evts.OnContext(ctx)
439+
inst.SetHook(installerHook)
425440

426441
mode := providercache.InstallNewProvidersOnly
427442
if upgrade {
@@ -443,20 +458,19 @@ func (c *InitCommand) getProvidersFromConfig(ctx context.Context, config *config
443458

444459
// Determine which required providers are already downloaded, and download any
445460
// new providers or newer versions of providers
446-
configLocks, err := inst.EnsureProviderVersions(ctx, previousLocks, reqs, mode)
461+
configLocks, installErr := inst.EnsureProviderVersions(ctx, previousLocks, reqs, mode)
447462
if ctx.Err() == context.Canceled {
448463
diags = diags.Append(fmt.Errorf("Provider installation was canceled by an interrupt signal."))
449-
view.Diagnostics(diags)
464+
view.Diagnostics(diags) // TODO: Why is the output viewed here?
450465
return true, nil, diags
451466
}
452-
if err != nil {
453-
// The errors captured in "err" should be redundant with what we
467+
if installErr != nil {
468+
// The errors captured in "installErr" should be redundant with what we
454469
// received via the InstallerEvents callbacks above, so we'll
455470
// just return those as long as we have some.
456471
if !diags.HasErrors() {
457-
diags = diags.Append(err)
472+
diags = diags.Append(installErr)
458473
}
459-
460474
return true, nil, diags
461475
}
462476

@@ -469,7 +483,7 @@ func (c *InitCommand) getProvidersFromConfig(ctx context.Context, config *config
469483
// The calling code is assumed to have already called getProvidersFromConfig, which is used to
470484
// supply the configLocks argument.
471485
// The dependency lock file itself isn't updated here.
472-
func (c *InitCommand) getProvidersFromState(ctx context.Context, state *states.State, configReqs providerreqs.Requirements, configLocks *depsfile.Locks, upgrade bool, pluginDirs []string, flagLockfile string, view views.Init) (output bool, resultingLocks *depsfile.Locks, diags tfdiags.Diagnostics) {
486+
func (c *InitCommand) getProvidersFromState(ctx context.Context, state *states.State, configReqs providerreqs.Requirements, configLocks *depsfile.Locks, upgrade bool, pluginDirs []string, flagLockfile string, view views.Init, installerHook *providerInstallerHook) (output bool, resultingLocks *depsfile.Locks, diags tfdiags.Diagnostics) {
473487
ctx, span := tracer.Start(ctx, "install providers from state")
474488
defer span.End()
475489

@@ -547,6 +561,7 @@ func (c *InitCommand) getProvidersFromState(ctx context.Context, state *states.S
547561
// are shimming our vt100 output to the legacy console API on Windows.
548562
evts := c.prepareInstallerEvents(ctx, reqs, &diags, inst, view, views.InitializingProviderPluginFromStateMessage, views.ReusingVersionIdentifiedFromConfig)
549563
ctx = evts.OnContext(ctx)
564+
inst.SetHook(installerHook)
550565

551566
mode := providercache.InstallNewProvidersOnly
552567

0 commit comments

Comments
 (0)