/
githubmirror
/
terraform
Обзор
Документация
Войти
/
githubmirror
/
terraform
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
internal/command/plan_policy_test.go
1 097 строк
34 KB
Samsondeen
No policy evaluation should be done for pre-destroy refresh (#38856)
10 июл 2026, 18:19
Не верифицирован
10 июл 2026, 18:19
e562eb2
Код
Авторство
О чём код?
// Copyright IBM Corp. 2014, 2026 // SPDX-License-Identifier: BUSL-1.1 package command import ( "context" "os" "sync/atomic" "testing" "github.com/google/go-cmp/cmp" "github.com/hashicorp/hcl/v2" "github.com/hashicorp/terraform/internal/addrs" "github.com/hashicorp/terraform/internal/policy" "github.com/hashicorp/terraform/internal/policy/proto" "github.com/hashicorp/terraform/internal/states" ) // Tests the output of a plan that includes a policy evaluation func TestPlan_WithPolicy(t *testing.T) { td := t.TempDir() testCopyDir(t, testFixturePath("plan"), td) t.Chdir(td) policyCode := ` resource_policy "resource_type" "policy_name" { enforce_attrs { key = attr.value == "foo" } } ` if err := os.WriteFile("policy.hcl", []byte(policyCode), 0644); err != nil { t.Fatal(err) } p := planFixtureProvider() view, done := testView(t) overrides := metaOverridesForProvider(p) policyClient := policy.NewTestMockClient(t) overrides.PolicyClient = policyClient c := &PlanCommand{ Meta: Meta{ testingOverrides: overrides, View: view, AllowExperimentalFeatures: true, }, } resp := policy.EvaluationFromProtoResponse( proto.EvaluateResult_DENY_EVALUATE_RESULT, []*proto.PolicyEvaluationDetail{ { Address: "resource_policy.foo", Result: proto.EvaluateResult_DENY_EVALUATE_RESULT, File: "policy_file.tfpolicy.hcl", DefRange: &proto.Range{ Filename: "policy_file.tfpolicy.hcl", Start: &proto.Position{ Line: 1, Column: 1, }, End: &proto.Position{ Line: 2, Column: 4, }, }, EnforceResults: []*proto.EnforceBlockResult{ { Result: proto.EvaluateResult_DENY_EVALUATE_RESULT, Diagnostics: []*proto.Diagnostic{ { Severity: proto.Severity_ERROR, Summary: "policy denied", Result: &proto.DiagnosticResult{ Result: proto.EvaluateResult_DENY_EVALUATE_RESULT, }, Subject: &proto.Range{ Filename: "policy_file.tfpolicy.hcl", Start: &proto.Position{ Line: 1, Column: 1, }, End: &proto.Position{ Line: 2, Column: 4, }, }, Context: &proto.Range{ Filename: "policy_file.tfpolicy.hcl", Start: &proto.Position{ Line: 1, Column: 1, }, End: &proto.Position{ Line: 4, Column: 10, }, }, Snippet: &proto.Snippet{ Code: policyCode, StartLine: 1, HighlightStartOffset: 1, HighlightEndOffset: 100, }, }, }, }, }, }, }) policyClient.EvaluateResponse = &resp args := []string{"-policies", td} code := c.Run(append(args, "-no-color")) output := done(t) if code != 0 { t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) } expected := ` Error: policy denied on policy_file.tfpolicy.hcl line 1: 1: resource_policy "resource_type" "policy_name" { 2: enforce_attrs { 3: key = attr.value == "foo" 4: } 5: } 6: while evaluating policy for main.tf line 1: 1: resource "test_instance" "foo" { ` if diff := cmp.Diff(expected, output.Stderr()); diff != "" { t.Fatalf("unexpected output:\n%s", diff) } } func TestPlan_WithPolicyDiagnosticsJSON(t *testing.T) { td := t.TempDir() testCopyDir(t, testFixturePath("plan"), td) t.Chdir(td) policyCode := ` resource_policy "resource_type" "policy_name" { enforce_attrs { key = attr.value == "foo" } } ` if err := os.WriteFile("policy.hcl", []byte(policyCode), 0644); err != nil { t.Fatal(err) } p := planFixtureProvider() view, done := testView(t) overrides := metaOverridesForProvider(p) policyClient := policy.NewTestMockClient(t) overrides.PolicyClient = policyClient c := &PlanCommand{ Meta: Meta{ testingOverrides: overrides, View: view, AllowExperimentalFeatures: true, }, } resp := policy.EvaluationFromProtoResponse( proto.EvaluateResult_DENY_EVALUATE_RESULT, []*proto.PolicyEvaluationDetail{ { Address: "resource_policy.foo", Result: proto.EvaluateResult_DENY_EVALUATE_RESULT, File: "policy_file.tfpolicy.hcl", PolicySetEnforcement: "mandatory", DefRange: &proto.Range{ Filename: "policy_file.tfpolicy.hcl", Start: &proto.Position{ Line: 1, Column: 1, }, End: &proto.Position{ Line: 2, Column: 4, }, }, EnforceResults: []*proto.EnforceBlockResult{ { Result: proto.EvaluateResult_DENY_EVALUATE_RESULT, BlockIndex: 1, Diagnostics: []*proto.Diagnostic{ { Severity: proto.Severity_ERROR, Summary: "policy denied", Result: &proto.DiagnosticResult{ Result: proto.EvaluateResult_DENY_EVALUATE_RESULT, }, Subject: &proto.Range{ Filename: "policy_file.tfpolicy.hcl", Start: &proto.Position{ Line: 1, Column: 1, }, End: &proto.Position{ Line: 2, Column: 4, }, }, Context: &proto.Range{ Filename: "policy_file.tfpolicy.hcl", Start: &proto.Position{ Line: 1, Column: 1, }, End: &proto.Position{ Line: 4, Column: 10, }, }, Snippet: &proto.Snippet{ Code: policyCode, StartLine: 1, HighlightStartOffset: 1, HighlightEndOffset: 100, }, }, }, }, }, }, { Address: "resource_policy.bar", Result: proto.EvaluateResult_DENY_EVALUATE_RESULT, File: "policy_file.tfpolicy.hcl", PolicySetEnforcement: "mandatory", DefRange: &proto.Range{ Filename: "policy_file.tfpolicy.hcl", Start: &proto.Position{ Line: 1, Column: 1, }, End: &proto.Position{ Line: 2, Column: 4, }, }, EnforceResults: []*proto.EnforceBlockResult{ { Result: proto.EvaluateResult_DENY_EVALUATE_RESULT, BlockIndex: 2, Diagnostics: []*proto.Diagnostic{ { Severity: proto.Severity_ERROR, Summary: "policy failed for some other reason", Result: &proto.DiagnosticResult{ Result: proto.EvaluateResult_DENY_EVALUATE_RESULT, }, Subject: &proto.Range{ Filename: "policy_file.tfpolicy.hcl", Start: &proto.Position{ Line: 1, Column: 1, }, End: &proto.Position{ Line: 2, Column: 4, }, }, Context: &proto.Range{ Filename: "policy_file.tfpolicy.hcl", Start: &proto.Position{ Line: 1, Column: 1, }, End: &proto.Position{ Line: 4, Column: 10, }, }, Snippet: &proto.Snippet{ Code: policyCode, StartLine: 1, HighlightStartOffset: 1, HighlightEndOffset: 100, }, }, }, }, }, }, }) policyClient.EvaluateResponse = &resp // implicit allow, in a case where the evaluated provider matched no policy in the engine policyClient.EvaluateProviderResponse = &policy.EvaluationResponse{ Overall: policy.AllowResult, Policies: []*policy.Policy{{ Result: policy.AllowResult, PolicySetName: "some_policy_set", Address: "policy_name", Directory: "some/path/to", Filename: "provider_policy_file.tfpolicy.hcl", EnforcementLevel: "mandatory", Range: &hcl.Range{ Filename: "provider_policy_file.tfpolicy.hcl", Start: hcl.Pos{ Line: 1, Column: 1, }, End: hcl.Pos{ Line: 5, Column: 12, }, }, }, }, } args := []string{"-policies", td} code := c.Run(append(args, "-no-color", "-json")) output := done(t) if code != 0 { t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) } expected := `{"@level":"info","@message":"Terraform 1.16.0-dev","@module":"terraform.ui","terraform":"1.16.0-dev","type":"version","ui":"1.3"} {"@level":"info","@message":"data.test_data_source.a: Refreshing...","@module":"terraform.ui","hook":{"resource":{"addr":"data.test_data_source.a","module":"","resource":"data.test_data_source.a","implied_provider":"test","resource_type":"test_data_source","resource_name":"a","resource_key":null},"action":"read"},"type":"apply_start"} {"@level":"info","@message":"data.test_data_source.a: Refresh complete after 0s [id=zzzzz]","@module":"terraform.ui","hook":{"resource":{"addr":"data.test_data_source.a","module":"","resource":"data.test_data_source.a","implied_provider":"test","resource_type":"test_data_source","resource_name":"a","resource_key":null},"action":"read","id_key":"id","id_value":"zzzzz","elapsed_seconds":0},"type":"apply_complete"} {"@level":"error","@message":"Error: policy denied","@module":"terraform.ui","@policy":"true","policy_diagnostic":{"severity":"error","summary":"policy denied","detail":"","range":{"filename":"main.tf","start":{"line":1,"column":1,"byte":0},"end":{"line":1,"column":31,"byte":30}},"snippet":{"context":null,"code":"resource \"test_instance\" \"foo\" {","start_line":1,"highlight_start_offset":0,"highlight_end_offset":30,"values":[]},"policy_range":{"filename":"policy_file.tfpolicy.hcl","start":{"line":1,"column":1,"byte":0},"end":{"line":2,"column":4,"byte":0}},"policy_snippet":{"context":null,"code":"\t\tresource_policy \"resource_type\" \"policy_name\" {\n\t\t enforce_attrs {\n\t\t key = attr.value == \"foo\"\n\t\t }\n\t\t}\n\t","start_line":1,"highlight_start_offset":1,"highlight_end_offset":100,"values":null}},"policy_metadata":{"policy_set_path":"policy_file.tfpolicy.hcl","policy_name":"resource_policy.foo","file_name":"policy_file.tfpolicy.hcl","enforcement_level":"mandatory","enforce_index":1},"result":"DenyResult","target_address":"test_instance.foo","type":"policy_diagnostic"} {"@level":"error","@message":"Error: policy failed for some other reason","@module":"terraform.ui","@policy":"true","policy_diagnostic":{"severity":"error","summary":"policy failed for some other reason","detail":"","range":{"filename":"main.tf","start":{"line":1,"column":1,"byte":0},"end":{"line":1,"column":31,"byte":30}},"snippet":{"context":null,"code":"resource \"test_instance\" \"foo\" {","start_line":1,"highlight_start_offset":0,"highlight_end_offset":30,"values":[]},"policy_range":{"filename":"policy_file.tfpolicy.hcl","start":{"line":1,"column":1,"byte":0},"end":{"line":2,"column":4,"byte":0}},"policy_snippet":{"context":null,"code":"\t\tresource_policy \"resource_type\" \"policy_name\" {\n\t\t enforce_attrs {\n\t\t key = attr.value == \"foo\"\n\t\t }\n\t\t}\n\t","start_line":1,"highlight_start_offset":1,"highlight_end_offset":100,"values":null}},"policy_metadata":{"policy_set_path":"policy_file.tfpolicy.hcl","policy_name":"resource_policy.bar","file_name":"policy_file.tfpolicy.hcl","enforcement_level":"mandatory","enforce_index":2},"result":"DenyResult","target_address":"test_instance.foo","type":"policy_diagnostic"} {"@level":"info","@message":"Policy Result","@module":"terraform.ui","@policy":"true","policy_address":"resource_policy.foo","policy_metadata":{"policy_set_path":"policy_file.tfpolicy.hcl","policy_name":"resource_policy.foo","file_name":"policy_file.tfpolicy.hcl","enforcement_level":"mandatory"},"result":"DenyResult","target_address":"test_instance.foo","type":"policy_result"} {"@level":"info","@message":"Policy Result","@module":"terraform.ui","@policy":"true","policy_address":"resource_policy.bar","policy_metadata":{"policy_set_path":"policy_file.tfpolicy.hcl","policy_name":"resource_policy.bar","file_name":"policy_file.tfpolicy.hcl","enforcement_level":"mandatory"},"result":"DenyResult","target_address":"test_instance.foo","type":"policy_result"} {"@level":"info","@message":"test_instance.foo: Plan to create","@module":"terraform.ui","change":{"resource":{"addr":"test_instance.foo","module":"","resource":"test_instance.foo","implied_provider":"test","resource_type":"test_instance","resource_name":"foo","resource_key":null},"action":"create"},"type":"planned_change"} {"@level":"info","@message":"Plan: 1 to add, 0 to change, 0 to destroy.","@module":"terraform.ui","changes":{"add":1,"change":0,"import":0,"remove":0,"action_fail":0,"action_invocation":0,"operation":"plan"},"type":"change_summary"}` checkGoldenReferenceStr(t, output, expected) } func TestPlan_WithPolicyUnknown(t *testing.T) { td := t.TempDir() testCopyDir(t, testFixturePath("plan"), td) t.Chdir(td) policyCode := ` resource_policy "resource_type" "policy_name" { enforce_attrs { key = attr.value == "foo" } } ` if err := os.WriteFile("policy.hcl", []byte(policyCode), 0644); err != nil { t.Fatal(err) } p := planFixtureProvider() view, done := testView(t) overrides := metaOverridesForProvider(p) policyClient := policy.NewTestMockClient(t) overrides.PolicyClient = policyClient c := &PlanCommand{ Meta: Meta{ testingOverrides: overrides, View: view, AllowExperimentalFeatures: true, }, } resp := policy.EvaluationFromProtoResponse(proto.EvaluateResult_UNKNOWN_EVALUATE_RESULT, []*proto.PolicyEvaluationDetail{ { Result: proto.EvaluateResult_UNKNOWN_EVALUATE_RESULT, Diagnostics: []*proto.Diagnostic{ { Severity: proto.Severity_WARNING, Summary: "policy with unknowns", Result: &proto.DiagnosticResult{ Result: proto.EvaluateResult_UNKNOWN_EVALUATE_RESULT, }, Subject: &proto.Range{ Filename: "policy_file.tfpolicy.hcl", Start: &proto.Position{ Line: 1, Column: 1, }, End: &proto.Position{ Line: 2, Column: 4, }, }, Context: &proto.Range{ Filename: "policy_file.tfpolicy.hcl", Start: &proto.Position{ Line: 1, Column: 1, }, End: &proto.Position{ Line: 4, Column: 10, }, }, Snippet: &proto.Snippet{ Code: policyCode, StartLine: 1, HighlightStartOffset: 1, HighlightEndOffset: 100, }, }, }, }, }) policyClient.EvaluateResponse = &resp args := []string{"-policies", td} code := c.Run(append(args, "-no-color")) output := done(t) if code != 0 { t.Fatalf("bad: %d\n\n%s", code, output.Stdout()) } expected := `data.test_data_source.a: Reading... data.test_data_source.a: Read complete after 0s [id=zzzzz] Warning: policy with unknowns on policy_file.tfpolicy.hcl line 1: 1: resource_policy "resource_type" "policy_name" { 2: enforce_attrs { 3: key = attr.value == "foo" 4: } 5: } 6: while evaluating policy for main.tf line 1: 1: resource "test_instance" "foo" { Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # test_instance.foo will be created + resource "test_instance" "foo" { + ami = "bar" + network_interface { + description = "Main network interface" + device_index = "0" } } Plan: 1 to add, 0 to change, 0 to destroy. ───────────────────────────────────────────────────────────────────────────── Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now. ` if actual, diff := output.Stdout(), cmp.Diff(expected, output.Stdout()); diff != "" { t.Fatalf("unexpected output:\n%s. \nDiff: %s", actual, diff) } } func TestPlan_WithPolicySuccessInfo(t *testing.T) { td := t.TempDir() testCopyDir(t, testFixturePath("plan"), td) t.Chdir(td) policyCode := ` resource_policy "resource_type" "policy_name" { enforce_attrs { key = attr.value == "foo" } } ` if err := os.WriteFile("policy.hcl", []byte(policyCode), 0644); err != nil { t.Fatal(err) } providerSource := newMockProviderSource(t, map[string][]string{ "test": {"1.0.0"}, }) p := planFixtureProvider() view, done := testView(t) overrides := metaOverridesForProvider(p) policyClient := policy.NewTestMockClient(t) overrides.PolicyClient = policyClient meta := Meta{ testingOverrides: overrides, View: view, ProviderSource: providerSource, AllowExperimentalFeatures: true, } init := &InitCommand{ Meta: meta, } if code := init.Run(nil); code != 0 { output := done(t) t.Fatalf("expected status code %d but got %d: %s", 0, code, output.All()) } view, done = testView(t) meta.View = view c := &PlanCommand{ Meta: meta, } policyObj := &policy.Policy{ Result: policy.AllowResult, PolicySetName: "some_policy_set", Address: "policy_name", Directory: "some/path/to", Filename: "provider_policy_file.tfpolicy.hcl", EnforcementLevel: "mandatory", Range: &hcl.Range{ Filename: "provider_policy_file.tfpolicy.hcl", Start: hcl.Pos{ Line: 1, Column: 1, }, End: hcl.Pos{ Line: 5, Column: 12, }, }, } policyClient.EvaluateProviderFn = func(ctx context.Context, req policy.EvaluationRequest[*proto.PolicyEvaluateProviderRequest_ProviderMetadata]) policy.EvaluationResponse { if req.Meta.Version != "1.0.0" { t.Fatalf("Expected provider version to be 1.0.0") } return policy.EvaluationResponse{ Overall: policy.AllowResult, Policies: []*policy.Policy{policyObj}, Enforcements: []policy.EnforcementResult{ { Result: policy.AllowResult, Message: "Something about this enforcement", BlockIndex: 1, Snippet: &proto.Snippet{ Code: "provider_policy \"test_policy\" \"name\"", StartLine: 1, HighlightStartOffset: 1, HighlightEndOffset: 100, }, Range: &hcl.Range{ Filename: "provider_policy_file.tfpolicy.hcl", Start: hcl.Pos{ Line: 3, Column: 5, }, End: hcl.Pos{ Line: 4, Column: 10, }, }, Policy: policyObj, }, }, } } policyObj = &policy.Policy{ Result: policy.AllowResult, PolicySetName: "some_policy_set", Address: "policy_name", Directory: "some/path/to", Filename: "policy_file.tfpolicy.hcl", EnforcementLevel: "mandatory", Range: &hcl.Range{ Filename: "policy_file.tfpolicy.hcl", Start: hcl.Pos{ Line: 1, Column: 1, }, End: hcl.Pos{ Line: 5, Column: 12, }, }, } policyClient.EvaluateResponse = &policy.EvaluationResponse{ Overall: policy.AllowResult, Policies: []*policy.Policy{policyObj}, Enforcements: []policy.EnforcementResult{ { Result: policy.AllowResult, Message: "Something about this enforcement", BlockIndex: 1, Snippet: &proto.Snippet{ Code: "resource_policy \"test_policy\" \"name\"", StartLine: 1, HighlightStartOffset: 1, HighlightEndOffset: 100, }, Range: &hcl.Range{ Filename: "policy_file.tfpolicy.hcl", Start: hcl.Pos{ Line: 3, Column: 5, }, End: hcl.Pos{ Line: 4, Column: 10, }, }, Policy: policyObj, }, }, } args := []string{"-policies", td} code := c.Run(append(args, "-no-color")) output := done(t) if code != 0 { t.Fatalf("bad: %d\n\n%s", code, output.Stdout()) } expected := ` Policy Info: on provider_policy_file.tfpolicy.hcl line 3, in provider_policy "test_policy" "name" "Something about this enforcement" data.test_data_source.a: Reading... data.test_data_source.a: Read complete after 0s [id=zzzzz] Policy Info: on policy_file.tfpolicy.hcl line 3, in resource_policy "test_policy" "name" "Something about this enforcement" on main.tf line 1, in resource "test_instance" "foo" Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # test_instance.foo will be created + resource "test_instance" "foo" { + ami = "bar" + network_interface { + description = "Main network interface" + device_index = "0" } } Plan: 1 to add, 0 to change, 0 to destroy. ───────────────────────────────────────────────────────────────────────────── Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now. ` if actual, diff := output.Stdout(), cmp.Diff(expected, output.Stdout()); diff != "" { t.Fatalf("unexpected output:\n%s. \nDiff: %s", actual, diff) } } func TestPlan_WithPolicySuccessInfoJSON(t *testing.T) { td := t.TempDir() testCopyDir(t, testFixturePath("plan-policy"), td) t.Chdir(td) p := planFixtureProvider() view, done := testView(t) overrides := metaOverridesForProvider(p) policyClient := policy.NewTestMockClient(t) overrides.PolicyClient = policyClient c := &PlanCommand{ Meta: Meta{ testingOverrides: overrides, View: view, AllowExperimentalFeatures: true, }, } policyObj := &policy.Policy{ Result: policy.AllowResult, PolicySetName: "some_policy_set", Address: "policy_name", Directory: "some/path/to", Filename: "provider_policy_file.tfpolicy.hcl", EnforcementLevel: "mandatory", Range: &hcl.Range{ Filename: "provider_policy_file.tfpolicy.hcl", Start: hcl.Pos{ Line: 1, Column: 1, }, End: hcl.Pos{ Line: 5, Column: 12, }, }, } policyClient.EvaluateProviderResponse = &policy.EvaluationResponse{ Overall: policy.AllowResult, Policies: []*policy.Policy{policyObj}, Enforcements: []policy.EnforcementResult{ { Result: policy.AllowResult, Message: "Something about this enforcement", BlockIndex: 1, Snippet: &proto.Snippet{ Code: "provider_policy \"test_policy\" \"name\"", StartLine: 1, HighlightStartOffset: 1, HighlightEndOffset: 100, }, Range: &hcl.Range{ Filename: "provider_policy_file.tfpolicy.hcl", Start: hcl.Pos{ Line: 3, Column: 5, }, End: hcl.Pos{ Line: 4, Column: 10, }, }, Policy: policyObj, }, }, } policyObj = &policy.Policy{ Result: policy.AllowResult, PolicySetName: "some_policy_set", Address: "policy_name", Directory: "some/path/to", Filename: "policy_file.tfpolicy.hcl", EnforcementLevel: "mandatory", Range: &hcl.Range{ Filename: "policy_file.tfpolicy.hcl", Start: hcl.Pos{ Line: 1, Column: 1, }, End: hcl.Pos{ Line: 5, Column: 12, }, }, } policyClient.EvaluateResponse = &policy.EvaluationResponse{ Overall: policy.AllowResult, Policies: []*policy.Policy{policyObj}, Enforcements: []policy.EnforcementResult{ { Result: policy.AllowResult, Message: "Something about this enforcement", BlockIndex: 1, Snippet: &proto.Snippet{ Code: "resource_policy \"test_policy\" \"name\"", StartLine: 1, HighlightStartOffset: 1, HighlightEndOffset: 100, }, Range: &hcl.Range{ Filename: "policy_file.tfpolicy.hcl", Start: hcl.Pos{ Line: 3, Column: 5, }, End: hcl.Pos{ Line: 4, Column: 10, }, }, Policy: policyObj, }, }, } args := []string{"-policies", td} code := c.Run(append(args, "-no-color", "-json")) output := done(t) if code != 0 { t.Fatalf("bad: %d\n\n%s", code, output.Stdout()) } checkGoldenReference(t, output, "plan-policy") } func TestPlan_Policy_Destroy(t *testing.T) { td := t.TempDir() testCopyDir(t, testFixturePath("plan-policy"), td) t.Chdir(td) policyCode := ` resource_policy "resource_type" "policy_name" { enforce_attrs { key = attr.value == "foo" } } ` if err := os.WriteFile("policy.hcl", []byte(policyCode), 0644); err != nil { t.Fatal(err) } policyObj := &policy.Policy{ Result: policy.AllowResult, PolicySetName: "some_policy_set", Address: "provider_policy.example", Directory: "some/path/to", Filename: "provider_policy_file.tfpolicy.hcl", EnforcementLevel: "mandatory", Range: &hcl.Range{ Filename: "provider_policy_file.tfpolicy.hcl", Start: hcl.Pos{Line: 1, Column: 1}, End: hcl.Pos{Line: 5, Column: 12}, }, } originalState := states.BuildState(func(s *states.SyncState) { s.SetResourceInstanceCurrent( addrs.Resource{ Mode: addrs.ManagedResourceMode, Type: "test_instance", Name: "foo", }.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance), &states.ResourceInstanceObjectSrc{ AttrsJSON: []byte(`{"id":"bar"}`), Status: states.ObjectReady, }, addrs.AbsProviderConfig{ Provider: addrs.NewDefaultProvider("test"), Module: addrs.RootModule, }, ) }) statePath := testStateFile(t, originalState) p := planFixtureProvider() view, done := testView(t) overrides := metaOverridesForProvider(p) policyClient := policy.NewTestMockClient(t) overrides.PolicyClient = policyClient providerEvalCount := 0 policyClient.EvaluateProviderFn = func(ctx context.Context, req policy.EvaluationRequest[*proto.PolicyEvaluateProviderRequest_ProviderMetadata]) policy.EvaluationResponse { providerEvalCount++ return policy.EvaluationResponse{ Overall: policy.AllowResult, Policies: []*policy.Policy{policyObj}, Enforcements: []policy.EnforcementResult{{ Result: policy.AllowResult, Message: "Destroy provider enforcement message", Policy: policyObj, }}, } } c := &PlanCommand{ Meta: Meta{ testingOverrides: overrides, View: view, AllowExperimentalFeatures: true, }, } args := []string{"-destroy", "-state", statePath, "-policies", td, "-parallelism=1", "-no-color"} code := c.Run(args) output := done(t) if code != 0 { t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) } if providerEvalCount != 1 { t.Fatalf("expected exactly 1 provider policy evaluation, got %d", providerEvalCount) } expectedStdout := ` Warning: Deprecated flag: -state Use the "path" attribute within the "local" backend to specify a file for state storage data.test_data_source.a: Reading... data.test_data_source.a: Read complete after 0s [id=zzzzz] test_instance.foo: Refreshing state... [id=bar] Policy Info: in policy provider_policy.example "Destroy provider enforcement message" Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: - destroy Terraform will perform the following actions: # test_instance.foo will be destroyed - resource "test_instance" "foo" { - id = "bar" -> null } Plan: 0 to add, 0 to change, 1 to destroy. ───────────────────────────────────────────────────────────────────────────── Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now. ` if diff := cmp.Diff(expectedStdout, output.Stdout()); diff != "" { t.Fatalf("unexpected stdout output:\n%s", diff) } } func TestPlan_WithPolicyClientStopAfterPlan(t *testing.T) { td := t.TempDir() testCopyDir(t, testFixturePath("plan"), td) t.Chdir(td) policyCode := ` resource_policy "resource_type" "policy_name" { enforce_attrs { key = attr.value == "foo" } } ` if err := os.WriteFile("policy.hcl", []byte(policyCode), 0644); err != nil { t.Fatal(err) } p := planFixtureProvider() view, done := testView(t) overrides := metaOverridesForProvider(p) policyClient := policy.NewTestMockClient(t) var stopCalled atomic.Bool var policyEvaluated atomic.Bool policyClient.StopFn = func() { stopCalled.Store(true) } policyClient.EvaluateFn = func(ctx context.Context, req policy.EvaluationRequest[*proto.PolicyEvaluateResourceRequest_ResourceMetadata]) policy.EvaluationResponse { policyEvaluated.Store(true) if stopCalled.Load() { t.Fatal("policy client Stop was called before the plan finished") } return policy.EvaluationResponse{Overall: policy.AllowResult} } overrides.PolicyClient = policyClient c := &PlanCommand{ Meta: Meta{ testingOverrides: overrides, View: view, AllowExperimentalFeatures: true, }, } args := []string{"-policies", td} code := c.Run(append(args, "-no-color")) output := done(t) if code != 0 { t.Fatalf("bad: %d\n\n%s", code, output.All()) } if !policyEvaluated.Load() { t.Fatal("expected policy evaluation to be called during plan") } if !stopCalled.Load() { t.Fatal("expected policy client Stop to be called after plan") } } func TestPlan_WithPolicySetupFailure(t *testing.T) { td := t.TempDir() testCopyDir(t, testFixturePath("plan"), td) t.Chdir(td) policyCode := ` resource_policy "resource_type" "policy_name" { enforce_attrs { key = attr.value == "foo" } } ` if err := os.WriteFile("policy.hcl", []byte(policyCode), 0644); err != nil { t.Fatal(err) } p := planFixtureProvider() view, done := testView(t) overrides := metaOverridesForProvider(p) // We intentionally do not pass a policy client override here so the command // exercises the real policy client initialization path and emits any setup // diagnostics from attempting to connect to the policy engine. c := &PlanCommand{ Meta: Meta{ testingOverrides: overrides, View: view, AllowExperimentalFeatures: true, }, } args := []string{"-policies", td} code := c.Run(append(args, "-no-color")) output := done(t) // expect the operation to be a success if code != 0 { t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) } // we still display the policy output // and the plan still succeeds expectedStdOut := `data.test_data_source.a: Reading... data.test_data_source.a: Read complete after 0s [id=zzzzz] Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols: + create Terraform will perform the following actions: # test_instance.foo will be created + resource "test_instance" "foo" { + ami = "bar" + network_interface { + description = "Main network interface" + device_index = "0" } } Plan: 1 to add, 0 to change, 0 to destroy. ───────────────────────────────────────────────────────────────────────────── Note: You didn't use the -out option to save this plan, so Terraform can't guarantee to take exactly these actions if you run "terraform apply" now. ` if diff := cmp.Diff(expectedStdOut, output.Stdout()); diff != "" { t.Fatalf("unexpected stdout output:\n%s", diff) } expectedStdErr := ` Error: Failed to connect to policy engine Failed to connect to policy engine: failed to connect to plugin: exec: "tfpolicy-plugin": executable file not found in $PATH. ` if diff := cmp.Diff(expectedStdErr, output.Stderr()); diff != "" { t.Fatalf("unexpected stderr output:\n%s", diff) } } func TestPlan_WithPolicySetupFailureJSON(t *testing.T) { td := t.TempDir() testCopyDir(t, testFixturePath("plan"), td) t.Chdir(td) policyCode := ` resource_policy "resource_type" "policy_name" { enforce_attrs { key = attr.value == "foo" } } ` if err := os.WriteFile("policy.hcl", []byte(policyCode), 0644); err != nil { t.Fatal(err) } p := planFixtureProvider() view, done := testView(t) overrides := metaOverridesForProvider(p) // We intentionally do not pass a policy client override here so the command // exercises the real policy client initialization path and emits any setup // diagnostics from attempting to connect to the policy engine. c := &PlanCommand{ Meta: Meta{ testingOverrides: overrides, View: view, AllowExperimentalFeatures: true, }, } args := []string{"-policies", td} code := c.Run(append(args, "-no-color", "-json")) output := done(t) // expect the operation to be a success if code != 0 { t.Fatalf("bad: %d\n\n%s", code, output.Stderr()) } expected := `{"@level":"info","@message":"Terraform 1.15.0-dev","@module":"terraform.ui","terraform":"1.15.0-dev","type":"version","ui":"1.3"} {"@level":"error","@message":"Error: Failed to connect to policy engine","@module":"terraform.ui","@policy":"true","policy_diagnostic":{"severity":"error","summary":"Failed to connect to policy engine","detail":"Failed to connect to policy engine: failed to connect to plugin: exec: \"tfpolicy-plugin\": executable file not found in $PATH."},"policy_metadata":{},"result":"SetupErrorResult","type":"policy_diagnostic"} {"@level":"info","@message":"data.test_data_source.a: Refreshing...","@module":"terraform.ui","hook":{"resource":{"addr":"data.test_data_source.a","module":"","resource":"data.test_data_source.a","implied_provider":"test","resource_type":"test_data_source","resource_name":"a","resource_key":null},"action":"read"},"type":"apply_start"} {"@level":"info","@message":"data.test_data_source.a: Refresh complete after 0s [id=zzzzz]","@module":"terraform.ui","hook":{"resource":{"addr":"data.test_data_source.a","module":"","resource":"data.test_data_source.a","implied_provider":"test","resource_type":"test_data_source","resource_name":"a","resource_key":null},"action":"read","id_key":"id","id_value":"zzzzz","elapsed_seconds":0},"type":"apply_complete"} {"@level":"info","@message":"test_instance.foo: Plan to create","@module":"terraform.ui","change":{"resource":{"addr":"test_instance.foo","module":"","resource":"test_instance.foo","implied_provider":"test","resource_type":"test_instance","resource_name":"foo","resource_key":null},"action":"create"},"type":"planned_change"} {"@level":"info","@message":"Plan: 1 to add, 0 to change, 0 to destroy.","@module":"terraform.ui","changes":{"add":1,"change":0,"import":0,"remove":0,"action_invocation":0,"action_fail":0,"operation":"plan"},"type":"change_summary"}` checkGoldenReferenceStr(t, output, expected) }