terraform/internal/command/plan_policy_test.go

909 lines
29 KiB
Go

// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: BUSL-1.1
package command
import (
"context"
"fmt"
"os"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/hcl/v2"
"github.com/hashicorp/terraform/internal/policy"
"github.com/hashicorp/terraform/internal/policy/proto"
)
// 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.15.0-dev","@module":"terraform.ui","terraform":"1.15.0-dev","type":"version","ui":"1.2"}
{"@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","implied_provider":"test","module":"","resource":"test_instance.foo","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,"operation":"plan"},"type":"change_summary"}
{"@level":"error","@message":"Error: policy denied","@module":"terraform.ui","@policy":"true","target_address":"test_instance.foo","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":{"enforce_index":1,"policy_set_path":"policy_file.tfpolicy.hcl","policy_name":"resource_policy.foo","file_name":"policy_file.tfpolicy.hcl","enforcement_level":"mandatory"},"result":"DenyResult","type":"policy_diagnostic"}
{"@level":"error","@message":"Error: policy failed for some other reason","@module":"terraform.ui","@policy":"true","target_address":"test_instance.foo","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":{"enforce_index":2,"policy_set_path":"policy_file.tfpolicy.hcl","policy_name":"resource_policy.bar","file_name":"policy_file.tfpolicy.hcl","enforcement_level":"mandatory"},"result":"DenyResult","type":"policy_diagnostic"}
{"@level":"info","@message":"Policy Result","@module":"terraform.ui","@policy":"true","target_address":"test_instance.foo","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","type":"policy_result"}
{"@level":"info","@message":"Policy Result","@module":"terraform.ui","@policy":"true","target_address":"test_instance.foo","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","type":"policy_result"}`
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]
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.
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" {
─────────────────────────────────────────────────────────────────────────────
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 := `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.
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"
Policy Info:
on provider_policy_file.tfpolicy.hcl line 3, in provider_policy "test_policy" "name"
"Something about this enforcement"
─────────────────────────────────────────────────────────────────────────────
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_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
expectedOut := `
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.
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(expectedOut, output.All()); diff != "" {
t.Fatalf("unexpected 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,"operation":"plan"},"type":"change_summary"}`
fmt.Println(output.Stdout())
checkGoldenReferenceStr(t, output, expected)
}