-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Introduce
tfjsonpath
Package and Sensitive and Unknown Plan Checks (#…
…154) * Implement `tfjsonpath` package * Implement `ExpectSensitiveValue` plan check * Implement `ExpectUnknownValue` plan check * Update website documentation * Resolve linting errors * Add copyright headers * Add test for `null` values * Reword bool assertion error * Add Changie Entries * Correct documentation typos Co-authored-by: Brian Flad <[email protected]> * Add error for when resource is not found * Add additional wording to attribute path subsections * Resolve linting errors --------- Co-authored-by: Brian Flad <[email protected]>
- Loading branch information
Showing
14 changed files
with
1,907 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
kind: FEATURES | ||
body: 'tfjsonpath: Introduced new `tfjsonpath` package which contains methods that | ||
allow traversal of Terraform JSON data' | ||
time: 2023-07-20T16:34:47.373683-04:00 | ||
custom: | ||
Issue: "154" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
kind: FEATURES | ||
body: 'plancheck: Added `ExpectUnknownValue` built-in plan check, which asserts that | ||
a given attribute has an unknown value' | ||
time: 2023-07-20T16:36:27.361538-04:00 | ||
custom: | ||
Issue: "154" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
kind: FEATURES | ||
body: 'plancheck: Added `ExpectSensitiveValue` built-in plan check, which asserts | ||
that a given attribute has a sensitive value' | ||
time: 2023-07-20T16:38:28.94511-04:00 | ||
custom: | ||
Issue: "154" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
// Copyright (c) HashiCorp, Inc. | ||
// SPDX-License-Identifier: MPL-2.0 | ||
|
||
package plancheck | ||
|
||
import ( | ||
"context" | ||
"fmt" | ||
|
||
"github.com/hashicorp/terraform-plugin-testing/tfjsonpath" | ||
) | ||
|
||
var _ PlanCheck = expectSensitiveValue{} | ||
|
||
type expectSensitiveValue struct { | ||
resourceAddress string | ||
attributePath tfjsonpath.Path | ||
} | ||
|
||
// CheckPlan implements the plan check logic. | ||
func (e expectSensitiveValue) CheckPlan(ctx context.Context, req CheckPlanRequest, resp *CheckPlanResponse) { | ||
|
||
for _, rc := range req.Plan.ResourceChanges { | ||
if e.resourceAddress != rc.Address { | ||
continue | ||
} | ||
|
||
result, err := tfjsonpath.Traverse(rc.Change.AfterSensitive, e.attributePath) | ||
if err != nil { | ||
resp.Error = err | ||
return | ||
} | ||
|
||
isSensitive, ok := result.(bool) | ||
if !ok { | ||
resp.Error = fmt.Errorf("invalid path: the path value cannot be asserted as bool") | ||
return | ||
} | ||
|
||
if !isSensitive { | ||
resp.Error = fmt.Errorf("attribute at path is not sensitive") | ||
return | ||
} | ||
|
||
return | ||
} | ||
|
||
resp.Error = fmt.Errorf("%s - Resource not found in plan ResourceChanges", e.resourceAddress) | ||
} | ||
|
||
// ExpectSensitiveValue returns a plan check that asserts that the specified attribute at the given resource has a sensitive value. | ||
// | ||
// Due to implementation differences between the terraform-plugin-sdk and the terraform-plugin-framework, representation of sensitive | ||
// values may differ. For example, terraform-plugin-sdk based providers may have less precise representations of sensitive values, such | ||
// as marking whole maps as sensitive rather than individual element values. | ||
func ExpectSensitiveValue(resourceAddress string, attributePath tfjsonpath.Path) PlanCheck { | ||
return expectSensitiveValue{ | ||
resourceAddress: resourceAddress, | ||
attributePath: attributePath, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,300 @@ | ||
// Copyright (c) HashiCorp, Inc. | ||
// SPDX-License-Identifier: MPL-2.0 | ||
|
||
package plancheck_test | ||
|
||
import ( | ||
"context" | ||
"regexp" | ||
"testing" | ||
|
||
"github.com/hashicorp/terraform-plugin-sdk/v2/diag" | ||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" | ||
|
||
r "github.com/hashicorp/terraform-plugin-testing/helper/resource" | ||
"github.com/hashicorp/terraform-plugin-testing/plancheck" | ||
"github.com/hashicorp/terraform-plugin-testing/tfjsonpath" | ||
) | ||
|
||
func Test_ExpectSensitiveValue_SensitiveStringAttribute(t *testing.T) { | ||
t.Parallel() | ||
|
||
r.UnitTest(t, r.TestCase{ | ||
ProviderFactories: map[string]func() (*schema.Provider, error){ | ||
"test": func() (*schema.Provider, error) { //nolint:unparam // required signature | ||
return testProviderSensitive(), nil | ||
}, | ||
}, | ||
Steps: []r.TestStep{ | ||
{ | ||
Config: ` | ||
resource "test_resource" "one" { | ||
sensitive_string_attribute = "test" | ||
} | ||
`, | ||
ConfigPlanChecks: r.ConfigPlanChecks{ | ||
PreApply: []plancheck.PlanCheck{ | ||
plancheck.ExpectSensitiveValue("test_resource.one", | ||
tfjsonpath.New("sensitive_string_attribute")), | ||
}, | ||
}, | ||
}, | ||
}, | ||
}) | ||
} | ||
|
||
func Test_ExpectSensitiveValue_SensitiveListAttribute(t *testing.T) { | ||
t.Parallel() | ||
|
||
r.UnitTest(t, r.TestCase{ | ||
ProviderFactories: map[string]func() (*schema.Provider, error){ | ||
"test": func() (*schema.Provider, error) { //nolint:unparam // required signature | ||
return testProviderSensitive(), nil | ||
}, | ||
}, | ||
Steps: []r.TestStep{ | ||
{ | ||
Config: ` | ||
resource "test_resource" "one" { | ||
sensitive_list_attribute = ["value1"] | ||
} | ||
`, | ||
ConfigPlanChecks: r.ConfigPlanChecks{ | ||
PreApply: []plancheck.PlanCheck{ | ||
plancheck.ExpectSensitiveValue("test_resource.one", | ||
tfjsonpath.New("sensitive_list_attribute")), | ||
}, | ||
}, | ||
}, | ||
}, | ||
}) | ||
} | ||
|
||
func Test_ExpectSensitiveValue_SensitiveSetAttribute(t *testing.T) { | ||
t.Parallel() | ||
|
||
r.UnitTest(t, r.TestCase{ | ||
ProviderFactories: map[string]func() (*schema.Provider, error){ | ||
"test": func() (*schema.Provider, error) { //nolint:unparam // required signature | ||
return testProviderSensitive(), nil | ||
}, | ||
}, | ||
Steps: []r.TestStep{ | ||
{ | ||
Config: ` | ||
resource "test_resource" "one" { | ||
sensitive_set_attribute = ["value1"] | ||
} | ||
`, | ||
ConfigPlanChecks: r.ConfigPlanChecks{ | ||
PreApply: []plancheck.PlanCheck{ | ||
plancheck.ExpectSensitiveValue("test_resource.one", | ||
tfjsonpath.New("sensitive_set_attribute")), | ||
}, | ||
}, | ||
}, | ||
}, | ||
}) | ||
} | ||
|
||
func Test_ExpectSensitiveValue_SensitiveMapAttribute(t *testing.T) { | ||
t.Parallel() | ||
|
||
r.UnitTest(t, r.TestCase{ | ||
ProviderFactories: map[string]func() (*schema.Provider, error){ | ||
"test": func() (*schema.Provider, error) { //nolint:unparam // required signature | ||
return testProviderSensitive(), nil | ||
}, | ||
}, | ||
Steps: []r.TestStep{ | ||
{ | ||
Config: ` | ||
resource "test_resource" "one" { | ||
sensitive_map_attribute = { | ||
key1 = "value1", | ||
} | ||
} | ||
`, | ||
ConfigPlanChecks: r.ConfigPlanChecks{ | ||
PreApply: []plancheck.PlanCheck{ | ||
plancheck.ExpectSensitiveValue("test_resource.one", | ||
tfjsonpath.New("sensitive_map_attribute")), | ||
}, | ||
}, | ||
}, | ||
}, | ||
}) | ||
} | ||
|
||
func Test_ExpectSensitiveValue_ListNestedBlock_SensitiveAttribute(t *testing.T) { | ||
t.Parallel() | ||
|
||
r.UnitTest(t, r.TestCase{ | ||
ProviderFactories: map[string]func() (*schema.Provider, error){ | ||
"test": func() (*schema.Provider, error) { //nolint:unparam // required signature | ||
return testProviderSensitive(), nil | ||
}, | ||
}, | ||
Steps: []r.TestStep{ | ||
{ | ||
Config: ` | ||
resource "test_resource" "one" { | ||
list_nested_block_sensitive_attribute { | ||
sensitive_list_nested_block_attribute = "sensitive-test" | ||
list_nested_block_attribute = "test" | ||
} | ||
} | ||
`, | ||
ConfigPlanChecks: r.ConfigPlanChecks{ | ||
PreApply: []plancheck.PlanCheck{ | ||
plancheck.ExpectSensitiveValue("test_resource.one", | ||
tfjsonpath.New("list_nested_block_sensitive_attribute").AtSliceIndex(0). | ||
AtMapKey("sensitive_list_nested_block_attribute")), | ||
}, | ||
}, | ||
}, | ||
}, | ||
}) | ||
} | ||
|
||
func Test_ExpectSensitiveValue_SetNestedBlock_SensitiveAttribute(t *testing.T) { | ||
t.Parallel() | ||
|
||
r.UnitTest(t, r.TestCase{ | ||
ProviderFactories: map[string]func() (*schema.Provider, error){ | ||
"test": func() (*schema.Provider, error) { //nolint:unparam // required signature | ||
return testProviderSensitive(), nil | ||
}, | ||
}, | ||
Steps: []r.TestStep{ | ||
{ | ||
Config: ` | ||
resource "test_resource" "one" { | ||
set_nested_block_sensitive_attribute { | ||
sensitive_set_nested_block_attribute = "sensitive-test" | ||
set_nested_block_attribute = "test" | ||
} | ||
} | ||
`, | ||
ConfigPlanChecks: r.ConfigPlanChecks{ | ||
PreApply: []plancheck.PlanCheck{ | ||
plancheck.ExpectSensitiveValue("test_resource.one", | ||
tfjsonpath.New("set_nested_block_sensitive_attribute")), | ||
}, | ||
}, | ||
}, | ||
}, | ||
}) | ||
} | ||
|
||
func Test_ExpectSensitiveValue_ExpectError_ResourceNotFound(t *testing.T) { | ||
t.Parallel() | ||
|
||
r.UnitTest(t, r.TestCase{ | ||
ProviderFactories: map[string]func() (*schema.Provider, error){ | ||
"test": func() (*schema.Provider, error) { //nolint:unparam // required signature | ||
return testProviderSensitive(), nil | ||
}, | ||
}, | ||
Steps: []r.TestStep{ | ||
{ | ||
Config: ` | ||
resource "test_resource" "one" {} | ||
`, | ||
ConfigPlanChecks: r.ConfigPlanChecks{ | ||
PreApply: []plancheck.PlanCheck{ | ||
plancheck.ExpectSensitiveValue("test_resource.two", tfjsonpath.New("set_attribute")), | ||
}, | ||
}, | ||
ExpectError: regexp.MustCompile(`test_resource.two - Resource not found in plan ResourceChanges`), | ||
}, | ||
}, | ||
}) | ||
} | ||
|
||
func testProviderSensitive() *schema.Provider { | ||
return &schema.Provider{ | ||
ResourcesMap: map[string]*schema.Resource{ | ||
"test_resource": { | ||
CreateContext: func(_ context.Context, d *schema.ResourceData, _ interface{}) diag.Diagnostics { | ||
d.SetId("test") | ||
return nil | ||
}, | ||
UpdateContext: func(_ context.Context, _ *schema.ResourceData, _ interface{}) diag.Diagnostics { | ||
return nil | ||
}, | ||
DeleteContext: func(_ context.Context, _ *schema.ResourceData, _ interface{}) diag.Diagnostics { | ||
return nil | ||
}, | ||
ReadContext: func(_ context.Context, _ *schema.ResourceData, _ interface{}) diag.Diagnostics { | ||
return nil | ||
}, | ||
Schema: map[string]*schema.Schema{ | ||
"sensitive_string_attribute": { | ||
Sensitive: true, | ||
Optional: true, | ||
Type: schema.TypeString, | ||
}, | ||
"sensitive_list_attribute": { | ||
Sensitive: true, | ||
Type: schema.TypeList, | ||
Elem: &schema.Schema{ | ||
Type: schema.TypeString, | ||
}, | ||
Optional: true, | ||
}, | ||
"sensitive_set_attribute": { | ||
Sensitive: true, | ||
Type: schema.TypeSet, | ||
Elem: &schema.Schema{ | ||
Type: schema.TypeString, | ||
}, | ||
Optional: true, | ||
}, | ||
"sensitive_map_attribute": { | ||
Sensitive: true, | ||
Type: schema.TypeMap, | ||
Elem: &schema.Schema{ | ||
Type: schema.TypeString, | ||
}, | ||
Optional: true, | ||
}, | ||
"list_nested_block_sensitive_attribute": { | ||
Type: schema.TypeList, | ||
Optional: true, | ||
Elem: &schema.Resource{ | ||
Schema: map[string]*schema.Schema{ | ||
"list_nested_block_attribute": { | ||
Type: schema.TypeString, | ||
Optional: true, | ||
}, | ||
"sensitive_list_nested_block_attribute": { | ||
Sensitive: true, | ||
Type: schema.TypeString, | ||
Optional: true, | ||
}, | ||
}, | ||
}, | ||
}, | ||
"set_nested_block_sensitive_attribute": { | ||
Type: schema.TypeSet, | ||
Optional: true, | ||
Elem: &schema.Resource{ | ||
Schema: map[string]*schema.Schema{ | ||
"set_nested_block_attribute": { | ||
Type: schema.TypeString, | ||
Optional: true, | ||
}, | ||
"sensitive_set_nested_block_attribute": { | ||
Sensitive: true, | ||
Type: schema.TypeString, | ||
Optional: true, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
}, | ||
} | ||
} |
Oops, something went wrong.