-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
feat: Add support for validating Output Changes
#1459
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -25,6 +25,9 @@ type PlanStruct struct { | |
// A map that maps full resource addresses (e.g., module.foo.null_resource.test) to the planned actions terraform | ||
// will take on that resource. | ||
ResourceChangesMap map[string]*tfjson.ResourceChange | ||
|
||
// A map that maps the output name to the planned values of that output | ||
OutputChangesMap map[string]*tfjson.Change | ||
} | ||
|
||
// ParsePlanJSON takes in the json string representation of the terraform plan and returns a go struct representation | ||
|
@@ -36,11 +39,22 @@ func ParsePlanJSON(jsonStr string) (*PlanStruct, error) { | |
return nil, err | ||
} | ||
|
||
plan.OutputChangesMap = parseOutputChanges(plan) | ||
plan.ResourcePlannedValuesMap = parsePlannedValues(plan) | ||
plan.ResourceChangesMap = parseResourceChanges(plan) | ||
return plan, nil | ||
} | ||
|
||
// parseOutputChanges takes a plan and returns a maps that maps output names to the planned changes for that output. | ||
// If there are no changes, this returns an empty map instead of erroring | ||
func parseOutputChanges(plan *PlanStruct) map[string]*tfjson.Change { | ||
out := map[string]*tfjson.Change{} | ||
for output_name, change := range plan.RawPlan.OutputChanges { | ||
out[output_name] = change | ||
} | ||
return out | ||
} | ||
|
||
// parseResourceChanges takes a plan and returns a map that maps resource addresses to the planned changes for that | ||
// resource. If there are no changes, this returns an empty map instead of erroring. | ||
func parseResourceChanges(plan *PlanStruct) map[string]*tfjson.ResourceChange { | ||
|
@@ -91,6 +105,18 @@ func parseModulePlannedValues(module *tfjson.StateModule) map[string]*tfjson.Sta | |
return out | ||
} | ||
|
||
// AssertOutputChangesMapKeyExists checks if the given key exists in the map, failing the test if it does not. | ||
func AssertOutputChangesMapKeyExists(t testing.TestingT, plan *PlanStruct, keyQuery string) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
_, hasKey := plan.OutputChangesMap[keyQuery] | ||
assert.Truef(t, hasKey, "Given output changes map does not have key %s", keyQuery) | ||
} | ||
|
||
// RequireOutputChangesMapKeyExists checks if the given key exists in the map, failing and halting the test if it does not. | ||
func RequireOutputChangesMapKeyExists(t testing.TestingT, plan *PlanStruct, keyQuery string) { | ||
_, hasKey := plan.OutputChangesMap[keyQuery] | ||
require.Truef(t, hasKey, "Given output changes map does not have key %s", keyQuery) | ||
} | ||
|
||
// AssertPlannedValuesMapKeyExists checks if the given key exists in the map, failing the test if it does not. | ||
func AssertPlannedValuesMapKeyExists(t testing.TestingT, plan *PlanStruct, keyQuery string) { | ||
_, hasKey := plan.ResourcePlannedValuesMap[keyQuery] | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,6 +4,7 @@ import ( | |
"testing" | ||
|
||
http_helper "github.com/gruntwork-io/terratest/modules/http-helper" | ||
tfjson "github.com/hashicorp/terraform-json" | ||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
@@ -12,10 +13,10 @@ const ( | |
// NOTE: We pull down the json files from github during test runtime as opposed to checking it in as these source | ||
// files are licensed under MPL and we want to avoid a dual license scenario where some source files in terratest | ||
// are licensed under a different license. | ||
basicJsonUrl = "https://raw.githubusercontent.com/hashicorp/terraform-json/v0.8.0/testdata/basic/plan.json" | ||
deepModuleJsonUrl = "https://raw.githubusercontent.com/hashicorp/terraform-json/v0.8.0/testdata/deep_module/plan.json" | ||
basicJsonUrl = "https://raw.githubusercontent.com/hashicorp/terraform-json/v0.13.0/testdata/basic/plan.json" | ||
deepModuleJsonUrl = "https://raw.githubusercontent.com/hashicorp/terraform-json/v0.13.0/testdata/deep_module/plan.json" | ||
|
||
changesJsonUrl = "https://raw.githubusercontent.com/hashicorp/terraform-json/v0.8.0/testdata/has_changes/plan.json" | ||
changesJsonUrl = "https://raw.githubusercontent.com/hashicorp/terraform-json/v0.13.0/testdata/has_changes/plan.json" | ||
) | ||
|
||
func TestPlannedValuesMapWithBasicJson(t *testing.T) { | ||
|
@@ -77,5 +78,26 @@ func TestResourceChangesJson(t *testing.T) { | |
barChanges := plan.ResourceChangesMap["null_resource.bar"] | ||
require.NotNil(t, barChanges.Change) | ||
assert.Equal(t, barChanges.Change.After.(map[string]interface{})["triggers"].(map[string]interface{})["foo_id"].(string), "424881806176056736") | ||
} | ||
|
||
func TestOutputChangesJson(t *testing.T) { | ||
t.Parallel() | ||
|
||
// Retrieve test data from the terraform-json project. | ||
_, jsonData := http_helper.HttpGet(t, changesJsonUrl, nil) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can the status code also be verified? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure... I straight copied this func from |
||
plan, err := ParsePlanJSON(jsonData) | ||
require.NoError(t, err) | ||
|
||
// Spot check a few changes to make sure the right address was registered | ||
RequireOutputChangesMapKeyExists(t, plan, "foo") | ||
fooChanges := plan.OutputChangesMap["foo"] | ||
require.NotNil(t, fooChanges) | ||
assert.Equal(t, fooChanges.Actions, tfjson.Actions{"create"}) | ||
assert.Equal(t, fooChanges.After, "bar") | ||
|
||
RequireOutputChangesMapKeyExists(t, plan, "map") | ||
mapChanges := plan.OutputChangesMap["map"] | ||
require.NotNil(t, mapChanges) | ||
assert.Equal(t, mapChanges.Actions, tfjson.Actions{"create"}) | ||
assert.Equal(t, mapChanges.After.(map[string]interface{}), map[string]interface{}{"foo": "bar", "number": float64(42)}) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use camel case instead of snake case