Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changes/v1.17/NEW FEATURES-20260810-122653.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: NEW FEATURES
body: 'policy: Terraform Policy is now generally available. The `-policies` flag for `plan`, `apply`, and `init` no longer requires the `-allow-experimental-features` flag.'
time: 2026-08-10T12:26:53.000000+00:00
custom:
Issue: "38970"
2 changes: 1 addition & 1 deletion internal/command/apply.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ func (c *ApplyCommand) Run(rawArgs []string) int {
}

func (c *ApplyCommand) Validate(args *arguments.Apply) (diags tfdiags.Diagnostics) {
return diags.Append(validatePolicyPaths(args.PolicyPaths, c.AllowExperimentalFeatures))
return diags.Append(validatePolicyPaths(args.PolicyPaths))
}

func (c *ApplyCommand) LoadPlanFile(path string) (*planfile.WrappedPlanFile, tfdiags.Diagnostics) {
Expand Down
25 changes: 10 additions & 15 deletions internal/command/apply_policy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,8 @@ func TestApply_WithPolicyDiagnosticsJSON(t *testing.T) {
overrides.PolicyClient = policyClient
c := &ApplyCommand{
Meta: Meta{
testingOverrides: overrides,
View: view,
AllowExperimentalFeatures: true,
testingOverrides: overrides,
View: view,
},
}
resp := policy.EvaluationFromProtoResponse(
Expand Down Expand Up @@ -176,9 +175,8 @@ func TestApply_PolicyResultsJSON_WithSavedPlan(t *testing.T) {
planOverrides.PolicyClient = policy.NewTestMockClient(t)
planCmd := &PlanCommand{
Meta: Meta{
testingOverrides: planOverrides,
View: planView,
AllowExperimentalFeatures: true,
testingOverrides: planOverrides,
View: planView,
},
}
if code := planCmd.Run([]string{"-policies", td, "-no-color", "-out=planfile"}); code != 0 {
Expand All @@ -193,9 +191,8 @@ func TestApply_PolicyResultsJSON_WithSavedPlan(t *testing.T) {
applyOverrides.PolicyClient = applyPolicyClient
applyCmd := &ApplyCommand{
Meta: Meta{
testingOverrides: applyOverrides,
View: applyView,
AllowExperimentalFeatures: true,
testingOverrides: applyOverrides,
View: applyView,
},
}

Expand Down Expand Up @@ -314,9 +311,8 @@ func TestApply_WithPolicyClientStopAfterApply(t *testing.T) {
overrides.PolicyClient = policyClient
c := &ApplyCommand{
Meta: Meta{
testingOverrides: overrides,
View: view,
AllowExperimentalFeatures: true,
testingOverrides: overrides,
View: view,
},
}

Expand Down Expand Up @@ -357,9 +353,8 @@ func TestApply_WithPlanPolicyDiagnosticsJSON(t *testing.T) {
overrides.PolicyClient = policyClient
c := &ApplyCommand{
Meta: Meta{
testingOverrides: overrides,
View: view,
AllowExperimentalFeatures: true,
testingOverrides: overrides,
View: view,
},
}
p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) {
Expand Down
96 changes: 0 additions & 96 deletions internal/command/command_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@
return filepath.Join(fixtureDir, name)
}

func testPolicyFixtureDir(t *testing.T) string {

Check failure on line 143 in internal/command/command_test.go

View workflow job for this annotation

GitHub Actions / Code Consistency Checks

func testPolicyFixtureDir is unused (U1000)
t.Helper()

td := t.TempDir()
Expand Down Expand Up @@ -249,102 +249,6 @@
return testPlanFileMatchState(t, configSnap, state, plan, statemgr.SnapshotMeta{})
}

func TestPlan_PoliciesRequireExperimentalFeatures(t *testing.T) {
td := testPolicyFixtureDir(t)

p := planFixtureProvider()
view, done := testView(t)
c := &PlanCommand{
Meta: Meta{
testingOverrides: metaOverridesForProvider(p),
View: view,
},
}

code := c.Run([]string{"-policies", td, "-no-color"})
output := done(t)
if code != 1 {
t.Fatalf("expected exit code 1, got %d\n\n%s", code, output.All())
}
if got := output.Stderr(); !strings.Contains(got, "The -policies flag is only valid in experimental builds of Terraform.") {
t.Fatalf("expected policy experiment gating diagnostic, got: %s", got)
}
if strings.Contains(output.All(), "Failed to connect to policy engine") {
t.Fatalf("policy engine should not be initialized when experiments are disabled: %s", output.All())
}
}

func TestApply_PoliciesRequireExperimentalFeatures(t *testing.T) {
td := testPolicyFixtureDir(t)

p := planFixtureProvider()
view, done := testView(t)
c := &ApplyCommand{
Meta: Meta{
testingOverrides: metaOverridesForProvider(p),
View: view,
},
}

code := c.Run([]string{"-policies", td, "-no-color", "-auto-approve"})
output := done(t)
if code != 1 {
t.Fatalf("expected exit code 1, got %d\n\n%s", code, output.All())
}
if got := output.Stderr(); !strings.Contains(got, "The -policies flag is only valid in experimental builds of Terraform.") {
t.Fatalf("expected policy experiment gating diagnostic, got: %s", got)
}
if strings.Contains(output.All(), "Failed to connect to policy engine") {
t.Fatalf("policy engine should not be initialized when experiments are disabled: %s", output.All())
}
}

func TestInit_PoliciesRequireExperimentalFeatures(t *testing.T) {
td := testPolicyFixtureDir(t)

view, done := testView(t)
c := &InitCommand{
Meta: Meta{
Ui: new(cli.MockUi),
View: view,
},
}

code := c.Run([]string{"-policies", td, "-no-color"})
output := done(t)
if code != 1 {
t.Fatalf("expected exit code 1, got %d\n\n%s", code, output.All())
}
if got := output.Stderr(); !strings.Contains(got, "The -policies flag is only valid in experimental builds of Terraform.") {
t.Fatalf("expected policy experiment gating diagnostic, got: %s", got)
}
}

func TestQuery_PoliciesRequireExperimentalFeatures(t *testing.T) {
td := testPolicyFixtureDir(t)

p := queryFixtureProvider()
view, done := testView(t)
c := &QueryCommand{
Meta: Meta{
testingOverrides: metaOverridesForProvider(p),
View: view,
},
}

code := c.Run([]string{"-policies", td, "-no-color"})
output := done(t)
if code != 1 {
t.Fatalf("expected exit code 1, got %d\n\n%s", code, output.All())
}
if got := output.Stderr(); !strings.Contains(got, "The -policies flag is only valid in experimental builds of Terraform.") {
t.Fatalf("expected policy experiment gating diagnostic, got: %s", got)
}
if strings.Contains(output.All(), "Failed to connect to policy engine") {
t.Fatalf("policy engine should not be initialized when experiments are disabled: %s", output.All())
}
}

func testPlanFileMatchState(t *testing.T, configSnap *configload.Snapshot, state *states.State, plan *plans.Plan, stateMeta statemgr.SnapshotMeta) string {
t.Helper()

Expand Down
2 changes: 1 addition & 1 deletion internal/command/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -299,7 +299,7 @@ func (c *InitCommand) initBackend(ctx context.Context, root *configs.Module, ini
}

func (c *InitCommand) Validate(args *arguments.Init) (diags tfdiags.Diagnostics) {
diags = diags.Append(validatePolicyPaths(args.PolicyPaths, c.AllowExperimentalFeatures))
diags = diags.Append(validatePolicyPaths(args.PolicyPaths))
return diags
}

Expand Down
4 changes: 0 additions & 4 deletions internal/command/meta_policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,6 @@ func (c *Meta) PolicyClient(ctx context.Context, policyPaths []string, ent *poli
client.Stop()
}
}
if !c.AllowExperimentalFeatures {
log.Printf("[DEBUG] Policies are not supported without experiments enabled, skipping policy client setup")
return client, nil, closer
}
if len(policyPaths) == 0 {
log.Printf("[DEBUG] No policy paths configured, skipping policy client setup")
return client, nil, closer
Expand Down
2 changes: 1 addition & 1 deletion internal/command/plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ func (c *PlanCommand) Run(rawArgs []string) int {
}

func (c *PlanCommand) Validate(args *arguments.Plan) (diags tfdiags.Diagnostics) {
return diags.Append(validatePolicyPaths(args.PolicyPaths, c.AllowExperimentalFeatures))
return diags.Append(validatePolicyPaths(args.PolicyPaths))
}

func (c *PlanCommand) PrepareBackend(args *arguments.State, viewType arguments.ViewType) (backendrun.OperationsBackend, tfdiags.Diagnostics) {
Expand Down
47 changes: 19 additions & 28 deletions internal/command/plan_policy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,8 @@ func TestPlan_WithPolicy(t *testing.T) {
overrides.PolicyClient = policyClient
c := &PlanCommand{
Meta: Meta{
testingOverrides: overrides,
View: view,
AllowExperimentalFeatures: true,
testingOverrides: overrides,
View: view,
},
}
resp := policy.EvaluationFromProtoResponse(
Expand Down Expand Up @@ -158,9 +157,8 @@ func TestPlan_WithPolicyDiagnosticsJSON(t *testing.T) {
overrides.PolicyClient = policyClient
c := &PlanCommand{
Meta: Meta{
testingOverrides: overrides,
View: view,
AllowExperimentalFeatures: true,
testingOverrides: overrides,
View: view,
},
}
resp := policy.EvaluationFromProtoResponse(
Expand Down Expand Up @@ -355,9 +353,8 @@ func TestPlan_WithPolicyUnknown(t *testing.T) {
overrides.PolicyClient = policyClient
c := &PlanCommand{
Meta: Meta{
testingOverrides: overrides,
View: view,
AllowExperimentalFeatures: true,
testingOverrides: overrides,
View: view,
},
}

Expand Down Expand Up @@ -482,10 +479,9 @@ func TestPlan_WithPolicySuccessInfo(t *testing.T) {
policyClient := policy.NewTestMockClient(t)
overrides.PolicyClient = policyClient
meta := Meta{
testingOverrides: overrides,
View: view,
ProviderSource: providerSource,
AllowExperimentalFeatures: true,
testingOverrides: overrides,
View: view,
ProviderSource: providerSource,
}

init := &InitCommand{
Expand Down Expand Up @@ -674,9 +670,8 @@ func TestPlan_WithPolicySuccessInfoJSON(t *testing.T) {
overrides.PolicyClient = policyClient
c := &PlanCommand{
Meta: Meta{
testingOverrides: overrides,
View: view,
AllowExperimentalFeatures: true,
testingOverrides: overrides,
View: view,
},
}

Expand Down Expand Up @@ -859,9 +854,8 @@ func TestPlan_Policy_Destroy(t *testing.T) {

c := &PlanCommand{
Meta: Meta{
testingOverrides: overrides,
View: view,
AllowExperimentalFeatures: true,
testingOverrides: overrides,
View: view,
},
}

Expand Down Expand Up @@ -948,9 +942,8 @@ func TestPlan_WithPolicyClientStopAfterPlan(t *testing.T) {
overrides.PolicyClient = policyClient
c := &PlanCommand{
Meta: Meta{
testingOverrides: overrides,
View: view,
AllowExperimentalFeatures: true,
testingOverrides: overrides,
View: view,
},
}

Expand Down Expand Up @@ -991,9 +984,8 @@ func TestPlan_WithPolicySetupFailure(t *testing.T) {
// diagnostics from attempting to connect to the policy engine.
c := &PlanCommand{
Meta: Meta{
testingOverrides: overrides,
View: view,
AllowExperimentalFeatures: true,
testingOverrides: overrides,
View: view,
},
}

Expand Down Expand Up @@ -1073,9 +1065,8 @@ func TestPlan_WithPolicySetupFailureJSON(t *testing.T) {
// diagnostics from attempting to connect to the policy engine.
c := &PlanCommand{
Meta: Meta{
testingOverrides: overrides,
View: view,
AllowExperimentalFeatures: true,
testingOverrides: overrides,
View: view,
},
}

Expand Down
10 changes: 1 addition & 9 deletions internal/command/policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,7 @@ import (
"github.com/hashicorp/terraform/internal/tfdiags"
)

func validatePolicyPaths(policyPaths []string, experimental bool) (diags tfdiags.Diagnostics) {
if !experimental && len(policyPaths) > 0 {
diags = diags.Append(tfdiags.Sourceless(
tfdiags.Error,
"Failed to parse command-line flags",
"The -policies flag is only valid in experimental builds of Terraform.",
))
}

func validatePolicyPaths(policyPaths []string) (diags tfdiags.Diagnostics) {
for _, path := range policyPaths {
if _, err := os.Stat(path); err != nil {
if os.IsNotExist(err) {
Expand Down
12 changes: 0 additions & 12 deletions internal/command/policy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,18 +44,6 @@ func TestValidatePolicyPaths(t *testing.T) {
),
},
},
{
name: "existing path, experiments disallowed",
path: existingPath,
allowExperiments: false,
want: tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"Failed to parse command-line flags",
"The -policies flag is only valid in experimental builds of Terraform.",
),
},
},
}

for _, tc := range tests {
Expand Down
3 changes: 1 addition & 2 deletions internal/command/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -170,8 +170,7 @@ func (c *QueryCommand) Run(rawArgs []string) int {
}

func (c *QueryCommand) Validate(args *arguments.Query) (diags tfdiags.Diagnostics) {
// validatePolicyPaths call ejects early if -policies flag was passed for non-experimental builds
return diags.Append(validatePolicyPaths(args.PolicyPaths, c.AllowExperimentalFeatures))
return diags.Append(validatePolicyPaths(args.PolicyPaths))
}

func (c *QueryCommand) PrepareBackend(args *arguments.State, viewType arguments.ViewType) (backendrun.OperationsBackend, tfdiags.Diagnostics) {
Expand Down
12 changes: 0 additions & 12 deletions internal/command/query_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -341,18 +341,6 @@ func TestQueryCommand_Validate(t *testing.T) {
),
},
},
{
name: "experiments disallowed",
policyPaths: []string{td},
allowExperiments: false,
wantDiags: tfdiags.Diagnostics{
tfdiags.Sourceless(
tfdiags.Error,
"Failed to parse command-line flags",
"The -policies flag is only valid in experimental builds of Terraform.",
),
},
},
}

for _, tc := range tests {
Expand Down
Loading