/
githubmirror
/
terraform
Обзор
Документация
Войти
/
githubmirror
/
terraform
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
internal/command/apply_policy_test.go
490 строк
22 KB
James Bardin
record failed actions
06 июл 2026, 18:04
06 июл 2026, 18:04
2f83376
Код
Авторство
О чём код?
// Copyright IBM Corp. 2014, 2026 // SPDX-License-Identifier: BUSL-1.1 package command import ( "context" "os" "sync/atomic" "testing" "github.com/hashicorp/hcl/v2" "github.com/hashicorp/terraform/internal/policy" "github.com/hashicorp/terraform/internal/policy/proto" "github.com/hashicorp/terraform/internal/providers" "github.com/zclconf/go-cty/cty" ) // This tests that the apply policy diagnostics are reported. func TestApply_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 := &ApplyCommand{ 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, }, }, }, }, }, }, }) 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", "-auto-approve")) output := done(t) if code != 0 { t.Fatalf("bad: %d\n\n%s", code, output.Stdout()) } 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":"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"} {"@level":"info","@message":"test_instance.foo: Creating...","@module":"terraform.ui","hook":{"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":"apply_start"} {"@level":"info","@message":"test_instance.foo: Creation complete after 0s","@module":"terraform.ui","hook":{"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","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":"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":"Apply complete! Resources: 1 added, 0 changed, 0 destroyed.","@module":"terraform.ui","changes":{"add":1,"change":0,"import":0,"remove":0,"action_invocation":0,"action_fail":0,"operation":"apply"},"type":"change_summary"} {"@level":"info","@message":"Outputs: 0","@module":"terraform.ui","outputs":{},"type":"outputs"}` checkGoldenReferenceStr(t, output, expected) } func TestApply_PolicyResultsJSON_WithSavedPlan(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() planView, planDone := testView(t) planOverrides := metaOverridesForProvider(p) planOverrides.PolicyClient = policy.NewTestMockClient(t) planCmd := &PlanCommand{ Meta: Meta{ testingOverrides: planOverrides, View: planView, AllowExperimentalFeatures: true, }, } if code := planCmd.Run([]string{"-policies", td, "-no-color", "-out=planfile"}); code != 0 { output := planDone(t) t.Fatalf("plan failed: %d\n\n%s", code, output.All()) } _ = planDone(t) applyView, applyDone := testView(t) applyOverrides := metaOverridesForProvider(p) applyPolicyClient := policy.NewTestMockClient(t) applyOverrides.PolicyClient = applyPolicyClient applyCmd := &ApplyCommand{ Meta: Meta{ testingOverrides: applyOverrides, View: applyView, 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, }, }, }, }, }, }, }) applyPolicyClient.EvaluateResponse = &resp code := applyCmd.Run([]string{"-policies", td, "-no-color", "-json", "-auto-approve", "planfile"}) output := applyDone(t) if code != 0 { t.Fatalf("apply failed: %d\n\n%s", code, output.All()) } 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":"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":"test_instance.foo: Creating...","@module":"terraform.ui","hook":{"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":"apply_start"} {"@level":"info","@message":"test_instance.foo: Creation complete after 0s","@module":"terraform.ui","hook":{"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","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":"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":"Apply complete! Resources: 1 added, 0 changed, 0 destroyed.","@module":"terraform.ui","changes":{"add":1,"change":0,"import":0,"remove":0,"action_invocation":0,"action_fail":0,"operation":"apply"},"type":"change_summary"} {"@level":"info","@message":"Outputs: 0","@module":"terraform.ui","outputs":{},"type":"outputs"}` checkGoldenReferenceStr(t, output, expected) } func TestApply_WithPolicyClientStopAfterApply(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 apply finished") } return policy.EvaluationResponse{Overall: policy.AllowResult} } overrides.PolicyClient = policyClient c := &ApplyCommand{ Meta: Meta{ testingOverrides: overrides, View: view, AllowExperimentalFeatures: true, }, } args := []string{"-policies", td} code := c.Run(append(args, "-no-color", "-auto-approve")) 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 apply") } if !stopCalled.Load() { t.Fatal("expected policy client Stop to be called after apply") } } // This tests that the plan policy diagnostic is superceded by the apply policy evaluation. func TestApply_WithPlanPolicyDiagnosticsJSON(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 := &ApplyCommand{ Meta: Meta{ testingOverrides: overrides, View: view, AllowExperimentalFeatures: true, }, } p.PlanResourceChangeFn = func(req providers.PlanResourceChangeRequest) (resp providers.PlanResourceChangeResponse) { s := req.ProposedNewState.AsValueMap() s["id"] = cty.UnknownVal(cty.String) resp.PlannedState = cty.ObjectVal(s) return } evalRespFn := func(result proto.EvaluateResult) policy.EvaluationResponse { detail := &proto.PolicyEvaluationDetail{ Address: "resource_policy.foo", Result: 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: result, }}, } if result == proto.EvaluateResult_DENY_EVALUATE_RESULT { detail.EnforceResults[0].Diagnostics = []*proto.Diagnostic{ { Severity: proto.Severity_ERROR, Summary: "policy denied", Result: &proto.DiagnosticResult{ Result: 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, }, }, } } return policy.EvaluationFromProtoResponse(result, []*proto.PolicyEvaluationDetail{detail}) } policyClient.EvaluateFn = func(ctx context.Context, er policy.EvaluationRequest[*proto.PolicyEvaluateResourceRequest_ResourceMetadata]) policy.EvaluationResponse { // This is what is returned during the post-plan policy evaluation if !er.Attrs.Raw.GetAttr("id").IsWhollyKnown() { return evalRespFn(proto.EvaluateResult_DENY_EVALUATE_RESULT) } // This is for the post-apply policy evaluation return evalRespFn(proto.EvaluateResult_ALLOW_EVALUATE_RESULT) } // 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", "-auto-approve")) output := done(t) if code != 0 { t.Fatalf("bad: %d\n\n%s", code, output.Stdout()) } // The resulting json only contains the policy result, because the object that // had a failed policy evaluation during the plan succeeded during apply. // This can occur when more references become known. 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":"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"} {"@level":"info","@message":"test_instance.foo: Creating...","@module":"terraform.ui","hook":{"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":"apply_start"} {"@level":"info","@message":"test_instance.foo: Creation complete after 0s","@module":"terraform.ui","hook":{"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","id_key":"id","elapsed_seconds":0},"type":"apply_complete"} {"@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":"AllowResult","target_address":"test_instance.foo","type":"policy_result"} {"@level":"info","@message":"Apply complete! Resources: 1 added, 0 changed, 0 destroyed.","@module":"terraform.ui","changes":{"add":1,"change":0,"import":0,"remove":0,"action_invocation":0,"action_fail":0,"operation":"apply"},"type":"change_summary"} {"@level":"info","@message":"Outputs: 0","@module":"terraform.ui","outputs":{},"type":"outputs"}` checkGoldenReferenceStr(t, output, expected) }