From 55258e7a726321cb0300c2fd761e105050195ba8 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Tue, 30 Apr 2024 14:34:48 +0100 Subject: [PATCH 01/61] compare: Add compare package and implementations for ValuesSame, ValuesSameAny, ValuesDiffer, ValuesDifferAny, and ValuesDifferAll --- compare/compare_values.go | 8 +++++ compare/values_differ.go | 18 ++++++++++ compare/values_differ_all.go | 22 ++++++++++++ compare/values_differ_all_test.go | 52 ++++++++++++++++++++++++++++ compare/values_differ_any.go | 30 ++++++++++++++++ compare/values_differ_any_test.go | 48 ++++++++++++++++++++++++++ compare/values_differ_test.go | 48 ++++++++++++++++++++++++++ compare/values_same.go | 18 ++++++++++ compare/values_same_any.go | 26 ++++++++++++++ compare/values_same_any_test.go | 48 ++++++++++++++++++++++++++ compare/values_same_test.go | 57 +++++++++++++++++++++++++++++++ 11 files changed, 375 insertions(+) create mode 100644 compare/compare_values.go create mode 100644 compare/values_differ.go create mode 100644 compare/values_differ_all.go create mode 100644 compare/values_differ_all_test.go create mode 100644 compare/values_differ_any.go create mode 100644 compare/values_differ_any_test.go create mode 100644 compare/values_differ_test.go create mode 100644 compare/values_same.go create mode 100644 compare/values_same_any.go create mode 100644 compare/values_same_any_test.go create mode 100644 compare/values_same_test.go diff --git a/compare/compare_values.go b/compare/compare_values.go new file mode 100644 index 000000000..6e226267e --- /dev/null +++ b/compare/compare_values.go @@ -0,0 +1,8 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package compare + +type ValueComparer interface { + CompareValues(values ...any) error +} diff --git a/compare/values_differ.go b/compare/values_differ.go new file mode 100644 index 000000000..cde002ba9 --- /dev/null +++ b/compare/values_differ.go @@ -0,0 +1,18 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package compare + +import "fmt" + +type ValuesDiffer struct{} + +func (v ValuesDiffer) CompareValues(values ...any) error { + for i := 1; i < len(values); i++ { + if values[i-1] == values[i] { + return fmt.Errorf("expected values to differ, but they are the same: %v == %v", values[i-1], values[i]) + } + } + + return nil +} diff --git a/compare/values_differ_all.go b/compare/values_differ_all.go new file mode 100644 index 000000000..f585d4e20 --- /dev/null +++ b/compare/values_differ_all.go @@ -0,0 +1,22 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package compare + +import "fmt" + +type ValuesDifferAll struct{} + +func (v ValuesDifferAll) CompareValues(values ...any) error { + vals := map[any]struct{}{} + + for i := 0; i < len(values); i++ { + if _, ok := vals[values[i]]; ok { + return fmt.Errorf("expected values to differ, but value is duplicated: %v", values[i]) + } + + vals[values[i]] = struct{}{} + } + + return nil +} diff --git a/compare/values_differ_all_test.go b/compare/values_differ_all_test.go new file mode 100644 index 000000000..03fa7f637 --- /dev/null +++ b/compare/values_differ_all_test.go @@ -0,0 +1,52 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package compare_test + +import ( + "fmt" + "testing" + + "github.com/google/go-cmp/cmp" + + "github.com/hashicorp/terraform-plugin-testing/compare" +) + +func TestValuesDifferAll_CompareValues(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + in []any + expectedError error + }{ + "nil": {}, + "single-value": { + in: []any{"str"}, + }, + "non-matching-values": { + in: []any{"str", "other_str", "another_str"}, + }, + "matching-values": { + in: []any{"str", "str", "str"}, + expectedError: fmt.Errorf("expected values to differ, but value is duplicated: str"), + }, + "non-sequential-matching-values": { + in: []any{"str", "other_str", "str"}, + expectedError: fmt.Errorf("expected values to differ, but value is duplicated: str"), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + err := compare.ValuesDifferAll{}.CompareValues(testCase.in...) + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} diff --git a/compare/values_differ_any.go b/compare/values_differ_any.go new file mode 100644 index 000000000..f43192883 --- /dev/null +++ b/compare/values_differ_any.go @@ -0,0 +1,30 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package compare + +import "fmt" + +type ValuesDifferAny struct{} + +func (v ValuesDifferAny) CompareValues(values ...any) error { + if len(values) < 2 { + return nil + } + + vals := map[any]int{} + + for i := 0; i < len(values); i++ { + vals[values[i]]++ + } + + if len(vals) < 2 { + for k, v := range vals { + if v > 1 { + return fmt.Errorf("expected values to differ, but value is duplicated: %v", k) + } + } + } + + return nil +} diff --git a/compare/values_differ_any_test.go b/compare/values_differ_any_test.go new file mode 100644 index 000000000..43b2825ab --- /dev/null +++ b/compare/values_differ_any_test.go @@ -0,0 +1,48 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package compare_test + +import ( + "fmt" + "testing" + + "github.com/google/go-cmp/cmp" + + "github.com/hashicorp/terraform-plugin-testing/compare" +) + +func TestValuesDifferAny_CompareValues(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + in []any + expectedError error + }{ + "nil": {}, + "single-value": { + in: []any{"str"}, + }, + "non-matching-sequential-values": { + in: []any{"str", "other_str", "str"}, + }, + "matching-values": { + in: []any{"str", "str", "str"}, + expectedError: fmt.Errorf("expected values to differ, but value is duplicated: str"), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + err := compare.ValuesDifferAny{}.CompareValues(testCase.in...) + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} diff --git a/compare/values_differ_test.go b/compare/values_differ_test.go new file mode 100644 index 000000000..aecbe20ef --- /dev/null +++ b/compare/values_differ_test.go @@ -0,0 +1,48 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package compare_test + +import ( + "fmt" + "testing" + + "github.com/google/go-cmp/cmp" + + "github.com/hashicorp/terraform-plugin-testing/compare" +) + +func TestValuesDiffer_CompareValues(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + in []any + expectedError error + }{ + "nil": {}, + "single-value": { + in: []any{"str"}, + }, + "non-matching-sequential-values": { + in: []any{"str", "other_str", "str"}, + }, + "matching-values": { + in: []any{"str", "other_str", "other_str"}, + expectedError: fmt.Errorf("expected values to differ, but they are the same: other_str == other_str"), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + err := compare.ValuesDiffer{}.CompareValues(testCase.in...) + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} diff --git a/compare/values_same.go b/compare/values_same.go new file mode 100644 index 000000000..219d641ba --- /dev/null +++ b/compare/values_same.go @@ -0,0 +1,18 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package compare + +import "fmt" + +type ValuesSame struct{} + +func (v ValuesSame) CompareValues(values ...any) error { + for i := 1; i < len(values); i++ { + if values[i-1] != values[i] { + return fmt.Errorf("expected values to be the same, but they differ: %v != %v", values[i-1], values[i]) + } + } + + return nil +} diff --git a/compare/values_same_any.go b/compare/values_same_any.go new file mode 100644 index 000000000..651b1a620 --- /dev/null +++ b/compare/values_same_any.go @@ -0,0 +1,26 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package compare + +import "fmt" + +type ValuesSameAny struct{} + +func (v ValuesSameAny) CompareValues(values ...any) error { + if len(values) < 2 { + return nil + } + + vals := map[any]struct{}{} + + for i := 0; i < len(values); i++ { + vals[values[i]] = struct{}{} + } + + if len(vals) == len(values) { + return fmt.Errorf("expected at least two values to be the same, but all values differ") + } + + return nil +} diff --git a/compare/values_same_any_test.go b/compare/values_same_any_test.go new file mode 100644 index 000000000..4c9086e21 --- /dev/null +++ b/compare/values_same_any_test.go @@ -0,0 +1,48 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package compare_test + +import ( + "fmt" + "testing" + + "github.com/google/go-cmp/cmp" + + "github.com/hashicorp/terraform-plugin-testing/compare" +) + +func TestValuesSameAny_CompareValues(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + in []any + expectedError error + }{ + "nil": {}, + "single-value": { + in: []any{"str"}, + }, + "non-sequential-matching-values": { + in: []any{"str", "other_str", "str"}, + }, + "non-matching-values": { + in: []any{"str", "other_str", "another_str"}, + expectedError: fmt.Errorf("expected at least two values to be the same, but all values differ"), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + err := compare.ValuesSameAny{}.CompareValues(testCase.in...) + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} diff --git a/compare/values_same_test.go b/compare/values_same_test.go new file mode 100644 index 000000000..e43c32776 --- /dev/null +++ b/compare/values_same_test.go @@ -0,0 +1,57 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package compare_test + +import ( + "fmt" + "testing" + + "github.com/google/go-cmp/cmp" + + "github.com/hashicorp/terraform-plugin-testing/compare" +) + +func TestValuesSame_CompareValues(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + in []any + expectedError error + }{ + "nil": {}, + "single-value": { + in: []any{"str"}, + }, + "matching-values": { + in: []any{"str", "str", "str"}, + }, + "non-matching-values": { + in: []any{"str", "str", "other_str"}, + expectedError: fmt.Errorf("expected values to be the same, but they differ: str != other_str"), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + err := compare.ValuesSame{}.CompareValues(testCase.in...) + + if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +// equateErrorMessage reports errors to be equal if both are nil +// or both have the same message. +var equateErrorMessage = cmp.Comparer(func(x, y error) bool { + if x == nil || y == nil { + return x == nil && y == nil + } + return x.Error() == y.Error() +}) From 8e71907e1e6fd53215711c5f554d2f91b7cff808 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Tue, 30 Apr 2024 14:37:19 +0100 Subject: [PATCH 02/61] statecheck: Add CompareValue, CompareValueContains, and CompareValuePairs state checks --- statecheck/compare_value.go | 113 +++++ statecheck/compare_value_contains.go | 150 +++++++ statecheck/compare_value_contains_test.go | 308 ++++++++++++++ statecheck/compare_value_pairs.go | 109 +++++ statecheck/compare_value_pairs_test.go | 142 +++++++ statecheck/compare_value_test.go | 493 ++++++++++++++++++++++ 6 files changed, 1315 insertions(+) create mode 100644 statecheck/compare_value.go create mode 100644 statecheck/compare_value_contains.go create mode 100644 statecheck/compare_value_contains_test.go create mode 100644 statecheck/compare_value_pairs.go create mode 100644 statecheck/compare_value_pairs_test.go create mode 100644 statecheck/compare_value_test.go diff --git a/statecheck/compare_value.go b/statecheck/compare_value.go new file mode 100644 index 000000000..2105bf890 --- /dev/null +++ b/statecheck/compare_value.go @@ -0,0 +1,113 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package statecheck + +import ( + "context" + "fmt" + + tfjson "github.com/hashicorp/terraform-json" + + "github.com/hashicorp/terraform-plugin-testing/compare" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +// Resource State Check +var _ StateCheck = &compareValue{} + +type compareValue struct { + resourceAddresses []string + attributePaths []tfjsonpath.Path + stateValues []any + comparer compare.ValueComparer +} + +func (e *compareValue) AddStateValue(resourceAddress string, attributePath tfjsonpath.Path) StateCheck { + e.resourceAddresses = append(e.resourceAddresses, resourceAddress) + e.attributePaths = append(e.attributePaths, attributePath) + + return e +} + +// CheckState implements the state check logic. +func (e *compareValue) CheckState(ctx context.Context, req CheckStateRequest, resp *CheckStateResponse) { + var resource *tfjson.StateResource + + if req.State == nil { + resp.Error = fmt.Errorf("state is nil") + + return + } + + if req.State.Values == nil { + resp.Error = fmt.Errorf("state does not contain any state values") + + return + } + + if req.State.Values.RootModule == nil { + resp.Error = fmt.Errorf("state does not contain a root module") + + return + } + + // All calls to AddStateValue occur before any TestStep is run, populating the resourceAddresses + // and attributePaths slices. The stateValues slice is populated during execution of each TestStep. + // Each call to CheckState happens sequentially during each TestStep. + // The currentIndex is reflective of the current state value being checked. + currentIndex := len(e.stateValues) + + if len(e.resourceAddresses) <= currentIndex { + resp.Error = fmt.Errorf("resource addresses index out of bounds: %d", currentIndex) + + return + } + + resourceAddress := e.resourceAddresses[currentIndex] + + for _, r := range req.State.Values.RootModule.Resources { + if resourceAddress == r.Address { + resource = r + + break + } + } + + if resource == nil { + resp.Error = fmt.Errorf("%s - Resource not found in state", resourceAddress) + + return + } + + if len(e.attributePaths) <= currentIndex { + resp.Error = fmt.Errorf("attribute paths index out of bounds: %d", currentIndex) + + return + } + + attributePath := e.attributePaths[currentIndex] + + result, err := tfjsonpath.Traverse(resource.AttributeValues, attributePath) + + if err != nil { + resp.Error = err + + return + } + + e.stateValues = append(e.stateValues, result) + + err = e.comparer.CompareValues(e.stateValues...) + + if err != nil { + resp.Error = err + } +} + +// CompareValue returns a state check that compares sequential values retrieved from state. +func CompareValue(comparer compare.ValueComparer) *compareValue { + return &compareValue{ + comparer: comparer, + } +} diff --git a/statecheck/compare_value_contains.go b/statecheck/compare_value_contains.go new file mode 100644 index 000000000..a6ae2e000 --- /dev/null +++ b/statecheck/compare_value_contains.go @@ -0,0 +1,150 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package statecheck + +import ( + "context" + "fmt" + "sort" + + tfjson "github.com/hashicorp/terraform-json" + + "github.com/hashicorp/terraform-plugin-testing/compare" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +// Resource State Check +var _ StateCheck = &compareValueContains{} + +type compareValueContains struct { + resourceAddressOne string + attributePathOne tfjsonpath.Path + resourceAddressTwo string + attributePathTwo tfjsonpath.Path + comparer compare.ValueComparer +} + +// CheckState implements the state check logic. +func (e *compareValueContains) CheckState(ctx context.Context, req CheckStateRequest, resp *CheckStateResponse) { + var resourceOne *tfjson.StateResource + var resourceTwo *tfjson.StateResource + + if req.State == nil { + resp.Error = fmt.Errorf("state is nil") + + return + } + + if req.State.Values == nil { + resp.Error = fmt.Errorf("state does not contain any state values") + + return + } + + if req.State.Values.RootModule == nil { + resp.Error = fmt.Errorf("state does not contain a root module") + + return + } + + for _, r := range req.State.Values.RootModule.Resources { + if e.resourceAddressOne == r.Address { + resourceOne = r + + break + } + } + + if resourceOne == nil { + resp.Error = fmt.Errorf("%s - Resource not found in state", e.resourceAddressOne) + + return + } + + resultOne, err := tfjsonpath.Traverse(resourceOne.AttributeValues, e.attributePathOne) + + if err != nil { + resp.Error = err + + return + } + + for _, r := range req.State.Values.RootModule.Resources { + if e.resourceAddressTwo == r.Address { + resourceTwo = r + + break + } + } + + if resourceTwo == nil { + resp.Error = fmt.Errorf("%s - Resource not found in state", e.resourceAddressTwo) + + return + } + + resultTwo, err := tfjsonpath.Traverse(resourceTwo.AttributeValues, e.attributePathTwo) + + if err != nil { + resp.Error = err + + return + } + + if listOrSet, ok := resultOne.([]any); ok { + var errs []error + + for _, v := range listOrSet { + errs = append(errs, e.comparer.CompareValues(v, resultTwo)) + } + + for _, err = range errs { + if err == nil { + return + } + } + + resp.Error = err + + return + } + + if mapOrObject, ok := resultOne.(map[string]any); ok { + keys := make([]string, 0, len(mapOrObject)) + + for k := range mapOrObject { + keys = append(keys, k) + } + + sort.Strings(keys) + + var errs []error + + for _, key := range keys { + errs = append(errs, e.comparer.CompareValues(mapOrObject[key], resultTwo)) + } + + for _, err = range errs { + if err == nil { + return + } + } + + resp.Error = err + + return + } + + resp.Error = fmt.Errorf("expected []any or map[string]any value for CompareValueContains check, got: %T", resultOne) +} + +func CompareValueContains(resourceAddressOne string, attributePathOne tfjsonpath.Path, resourceAddressTwo string, attributePathTwo tfjsonpath.Path, comparer compare.ValueComparer) StateCheck { + return &compareValueContains{ + resourceAddressOne: resourceAddressOne, + attributePathOne: attributePathOne, + resourceAddressTwo: resourceAddressTwo, + attributePathTwo: attributePathTwo, + comparer: comparer, + } +} diff --git a/statecheck/compare_value_contains_test.go b/statecheck/compare_value_contains_test.go new file mode 100644 index 000000000..2785c7621 --- /dev/null +++ b/statecheck/compare_value_contains_test.go @@ -0,0 +1,308 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package statecheck_test + +import ( + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/hashicorp/terraform-plugin-testing/compare" + r "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +func TestCompareValueContains_CheckState_Map_ValuesSame_DifferError(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + string_attribute = "str" + } + + resource "test_resource" "two" { + map_attribute = { + "a": "str2", + "b": "str3", + } + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValueContains( + "test_resource.two", + tfjsonpath.New("map_attribute"), + "test_resource.one", + tfjsonpath.New("string_attribute"), + compare.ValuesSame{}, + ), + }, + ExpectError: regexp.MustCompile("expected values to be the same, but they differ: str3 != str"), + }, + }, + }) +} + +func TestCompareValueContains_CheckState_Map_ValuesSame(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + string_attribute = "str" + } + + resource "test_resource" "two" { + map_attribute = { + "a": "str2", + "b": resource.test_resource.one.string_attribute, + } + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValueContains( + "test_resource.two", + tfjsonpath.New("map_attribute"), + "test_resource.one", + tfjsonpath.New("string_attribute"), + compare.ValuesSame{}, + ), + }, + }, + }, + }) +} + +func TestCompareValueContains_CheckState_Map_ValuesDiffer_SameError(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + string_attribute = "str" + } + + resource "test_resource" "two" { + map_attribute = { + "a": resource.test_resource.one.string_attribute, + "b": resource.test_resource.one.string_attribute, + } + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValueContains( + "test_resource.two", + tfjsonpath.New("map_attribute"), + "test_resource.one", + tfjsonpath.New("string_attribute"), + compare.ValuesDiffer{}, + ), + }, + ExpectError: regexp.MustCompile("expected values to differ, but they are the same: str == str"), + }, + }, + }) +} + +func TestCompareValueContains_CheckState_Map_ValuesDiffer(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + string_attribute = "str" + } + + resource "test_resource" "two" { + map_attribute = { + "a": resource.test_resource.one.string_attribute, + "b": "str2", + } + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValueContains( + "test_resource.two", + tfjsonpath.New("map_attribute"), + "test_resource.one", + tfjsonpath.New("string_attribute"), + compare.ValuesDiffer{}, + ), + }, + }, + }, + }) +} + +func TestCompareValueContains_CheckState_Set_ValuesSame_DifferError(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + string_attribute = "str" + } + + resource "test_resource" "two" { + set_attribute = [ + "str2", + "str3" + ] + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValueContains( + "test_resource.two", + tfjsonpath.New("set_attribute"), + "test_resource.one", + tfjsonpath.New("string_attribute"), + compare.ValuesSame{}, + ), + }, + ExpectError: regexp.MustCompile("expected values to be the same, but they differ: str3 != str"), + }, + }, + }) +} + +func TestCompareValueContains_CheckState_Set_ValuesSame(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + string_attribute = "str" + } + + resource "test_resource" "two" { + set_attribute = [ + "str2", + resource.test_resource.one.string_attribute + ] + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValueContains( + "test_resource.two", + tfjsonpath.New("set_attribute"), + "test_resource.one", + tfjsonpath.New("string_attribute"), + compare.ValuesSame{}, + ), + }, + }, + }, + }) +} + +func TestCompareValueContains_CheckState_Set_ValuesDiffer_SameError(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + string_attribute = "str" + } + + resource "test_resource" "two" { + set_attribute = [ + resource.test_resource.one.string_attribute, + resource.test_resource.one.string_attribute, + ] + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValueContains( + "test_resource.two", + tfjsonpath.New("set_attribute"), + "test_resource.one", + tfjsonpath.New("string_attribute"), + compare.ValuesDiffer{}, + ), + }, + ExpectError: regexp.MustCompile("expected values to differ, but they are the same: str == str"), + }, + }, + }) +} + +func TestCompareValueContains_CheckState_Set_ValuesDiffer(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + string_attribute = "str" + } + + resource "test_resource" "two" { + set_attribute = [ + resource.test_resource.one.string_attribute, + "str2" + ] + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValueContains( + "test_resource.two", + tfjsonpath.New("set_attribute"), + "test_resource.one", + tfjsonpath.New("string_attribute"), + compare.ValuesDiffer{}, + ), + }, + }, + }, + }) +} diff --git a/statecheck/compare_value_pairs.go b/statecheck/compare_value_pairs.go new file mode 100644 index 000000000..b5d5352c5 --- /dev/null +++ b/statecheck/compare_value_pairs.go @@ -0,0 +1,109 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package statecheck + +import ( + "context" + "fmt" + + tfjson "github.com/hashicorp/terraform-json" + + "github.com/hashicorp/terraform-plugin-testing/compare" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +// Resource State Check +var _ StateCheck = &compareValuePairs{} + +type compareValuePairs struct { + resourceAddressOne string + attributePathOne tfjsonpath.Path + resourceAddressTwo string + attributePathTwo tfjsonpath.Path + comparer compare.ValueComparer +} + +// CheckState implements the state check logic. +func (e *compareValuePairs) CheckState(ctx context.Context, req CheckStateRequest, resp *CheckStateResponse) { + var resourceOne *tfjson.StateResource + var resourceTwo *tfjson.StateResource + + if req.State == nil { + resp.Error = fmt.Errorf("state is nil") + + return + } + + if req.State.Values == nil { + resp.Error = fmt.Errorf("state does not contain any state values") + + return + } + + if req.State.Values.RootModule == nil { + resp.Error = fmt.Errorf("state does not contain a root module") + + return + } + + for _, r := range req.State.Values.RootModule.Resources { + if e.resourceAddressOne == r.Address { + resourceOne = r + + break + } + } + + if resourceOne == nil { + resp.Error = fmt.Errorf("%s - Resource not found in state", e.resourceAddressOne) + + return + } + + resultOne, err := tfjsonpath.Traverse(resourceOne.AttributeValues, e.attributePathOne) + + if err != nil { + resp.Error = err + + return + } + + for _, r := range req.State.Values.RootModule.Resources { + if e.resourceAddressTwo == r.Address { + resourceTwo = r + + break + } + } + + if resourceTwo == nil { + resp.Error = fmt.Errorf("%s - Resource not found in state", e.resourceAddressTwo) + + return + } + + resultTwo, err := tfjsonpath.Traverse(resourceTwo.AttributeValues, e.attributePathTwo) + + if err != nil { + resp.Error = err + + return + } + + err = e.comparer.CompareValues(resultOne, resultTwo) + + if err != nil { + resp.Error = err + } +} + +func CompareValuePairs(resourceAddressOne string, attributePathOne tfjsonpath.Path, resourceAddressTwo string, attributePathTwo tfjsonpath.Path, comparer compare.ValueComparer) StateCheck { + return &compareValuePairs{ + resourceAddressOne: resourceAddressOne, + attributePathOne: attributePathOne, + resourceAddressTwo: resourceAddressTwo, + attributePathTwo: attributePathTwo, + comparer: comparer, + } +} diff --git a/statecheck/compare_value_pairs_test.go b/statecheck/compare_value_pairs_test.go new file mode 100644 index 000000000..96bae8052 --- /dev/null +++ b/statecheck/compare_value_pairs_test.go @@ -0,0 +1,142 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package statecheck_test + +import ( + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/hashicorp/terraform-plugin-testing/compare" + r "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +func TestCompareValuePairs_CheckState_ValuesSame_DifferError(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + bool_attribute = true + float_attribute = 1.234 + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValuePairs( + "test_resource.one", + tfjsonpath.New("bool_attribute"), + "test_resource.one", + tfjsonpath.New("float_attribute"), + compare.ValuesSame{}, + ), + }, + ExpectError: regexp.MustCompile("expected values to be the same, but they differ: true != 1.234"), + }, + }, + }) +} + +func TestCompareValuePairs_CheckState_ValuesSame(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + bool_attribute = true + } + + resource "test_resource" "two" { + bool_attribute = true + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValuePairs( + "test_resource.one", + tfjsonpath.New("bool_attribute"), + "test_resource.two", + tfjsonpath.New("bool_attribute"), + compare.ValuesSame{}, + ), + }, + }, + }, + }) +} + +func TestCompareValuePairs_CheckState_ValuesDiffer_SameError(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + bool_attribute = true + } + + resource "test_resource" "two" { + bool_attribute = true + }`, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValuePairs( + "test_resource.one", + tfjsonpath.New("bool_attribute"), + "test_resource.two", + tfjsonpath.New("bool_attribute"), + compare.ValuesDiffer{}, + ), + }, + ExpectError: regexp.MustCompile("expected values to differ, but they are the same: true == true"), + }, + }, + }) +} + +func TestCompareValuePairs_CheckState_ValuesDiffer(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + bool_attribute = true + float_attribute = 1.234 + }`, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValuePairs( + "test_resource.one", + tfjsonpath.New("bool_attribute"), + "test_resource.one", + tfjsonpath.New("float_attribute"), + compare.ValuesDiffer{}, + ), + }, + }, + }, + }) +} diff --git a/statecheck/compare_value_test.go b/statecheck/compare_value_test.go new file mode 100644 index 000000000..3cce5057d --- /dev/null +++ b/statecheck/compare_value_test.go @@ -0,0 +1,493 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package statecheck_test + +import ( + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/hashicorp/terraform-plugin-testing/compare" + r "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +func TestCompareValue_CheckState_Bool_ValuesSame_ValueDiffersError(t *testing.T) { + t.Parallel() + + boolValuesDiffer := statecheck.CompareValue(compare.ValuesSame{}) + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + bool_attribute = true + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + boolValuesDiffer.AddStateValue( + "test_resource.one", + tfjsonpath.New("bool_attribute"), + ), + }, + }, + { + Config: `resource "test_resource" "one" { + bool_attribute = true + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + boolValuesDiffer.AddStateValue( + "test_resource.one", + tfjsonpath.New("bool_attribute"), + ), + }, + }, + { + Config: `resource "test_resource" "one" { + bool_attribute = false + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + boolValuesDiffer.AddStateValue( + "test_resource.one", + tfjsonpath.New("bool_attribute"), + ), + }, + ExpectError: regexp.MustCompile(`expected values to be the same, but they differ: true != false`), + }, + }, + }) +} + +func TestCompareValue_CheckState_Bool_ValuesSame(t *testing.T) { + t.Parallel() + + boolValuesDiffer := statecheck.CompareValue(compare.ValuesSame{}) + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + bool_attribute = true + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + boolValuesDiffer.AddStateValue( + "test_resource.one", + tfjsonpath.New("bool_attribute"), + ), + }, + }, + { + Config: `resource "test_resource" "one" { + bool_attribute = true + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + boolValuesDiffer.AddStateValue( + "test_resource.one", + tfjsonpath.New("bool_attribute"), + ), + }, + }, + }, + }) +} + +func TestCompareValue_CheckState_Bool_ValuesSameAny_ValueDiffersError(t *testing.T) { + t.Parallel() + + boolValuesDiffer := statecheck.CompareValue(compare.ValuesSameAny{}) + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + bool_attribute = true + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + boolValuesDiffer.AddStateValue( + "test_resource.one", + tfjsonpath.New("bool_attribute"), + ), + }, + }, + { + Config: `resource "test_resource" "one" { + bool_attribute = false + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + boolValuesDiffer.AddStateValue( + "test_resource.one", + tfjsonpath.New("bool_attribute"), + ), + }, + ExpectError: regexp.MustCompile(`expected at least two values to be the same, but all values differ`), + }, + }, + }) +} + +func TestCompareValue_CheckState_Bool_ValuesSameAny(t *testing.T) { + t.Parallel() + + boolValuesDiffer := statecheck.CompareValue(compare.ValuesSameAny{}) + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + bool_attribute = true + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + boolValuesDiffer.AddStateValue( + "test_resource.one", + tfjsonpath.New("bool_attribute"), + ), + }, + }, + { + Config: `resource "test_resource" "one" { + bool_attribute = true + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + boolValuesDiffer.AddStateValue( + "test_resource.one", + tfjsonpath.New("bool_attribute"), + ), + }, + }, + { + Config: `resource "test_resource" "one" { + bool_attribute = false + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + boolValuesDiffer.AddStateValue( + "test_resource.one", + tfjsonpath.New("bool_attribute"), + ), + }, + }, + }, + }) +} + +func TestCompareValue_CheckState_Bool_ValuesDiffer_ValueSameError(t *testing.T) { + t.Parallel() + + boolValuesDiffer := statecheck.CompareValue(compare.ValuesDiffer{}) + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + bool_attribute = true + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + boolValuesDiffer.AddStateValue( + "test_resource.one", + tfjsonpath.New("bool_attribute"), + ), + }, + }, + { + Config: `resource "test_resource" "one" { + bool_attribute = false + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + boolValuesDiffer.AddStateValue( + "test_resource.one", + tfjsonpath.New("bool_attribute"), + ), + }, + }, + { + Config: `resource "test_resource" "one" { + bool_attribute = false + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + boolValuesDiffer.AddStateValue( + "test_resource.one", + tfjsonpath.New("bool_attribute"), + ), + }, + ExpectError: regexp.MustCompile(`expected values to differ, but they are the same: false == false`), + }, + }, + }) +} + +func TestCompareValue_CheckState_Bool_ValuesDiffer(t *testing.T) { + t.Parallel() + + boolValuesDiffer := statecheck.CompareValue(compare.ValuesDiffer{}) + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + bool_attribute = true + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + boolValuesDiffer.AddStateValue( + "test_resource.one", + tfjsonpath.New("bool_attribute"), + ), + }, + }, + { + Config: `resource "test_resource" "one" { + bool_attribute = false + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + boolValuesDiffer.AddStateValue( + "test_resource.one", + tfjsonpath.New("bool_attribute"), + ), + }, + }, + { + Config: `resource "test_resource" "one" { + bool_attribute = true + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + boolValuesDiffer.AddStateValue( + "test_resource.one", + tfjsonpath.New("bool_attribute"), + ), + }, + }, + }, + }) +} + +func TestCompareValue_CheckState_Bool_ValuesDifferAll_ValuesSameError(t *testing.T) { + t.Parallel() + + boolValuesDiffer := statecheck.CompareValue(compare.ValuesDifferAll{}) + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + bool_attribute = true + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + boolValuesDiffer.AddStateValue( + "test_resource.one", + tfjsonpath.New("bool_attribute"), + ), + }, + }, + { + Config: `resource "test_resource" "one" { + bool_attribute = false + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + boolValuesDiffer.AddStateValue( + "test_resource.one", + tfjsonpath.New("bool_attribute"), + ), + }, + }, + { + Config: `resource "test_resource" "one" { + bool_attribute = true + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + boolValuesDiffer.AddStateValue( + "test_resource.one", + tfjsonpath.New("bool_attribute"), + ), + }, + ExpectError: regexp.MustCompile(`expected values to differ, but value is duplicated: true`), + }, + }, + }) +} + +func TestCompareValue_CheckState_Bool_ValuesDifferAll(t *testing.T) { + t.Parallel() + + boolValuesDiffer := statecheck.CompareValue(compare.ValuesDifferAll{}) + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + bool_attribute = true + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + boolValuesDiffer.AddStateValue( + "test_resource.one", + tfjsonpath.New("bool_attribute"), + ), + }, + }, + { + Config: `resource "test_resource" "one" { + bool_attribute = false + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + boolValuesDiffer.AddStateValue( + "test_resource.one", + tfjsonpath.New("bool_attribute"), + ), + }, + }, + }, + }) +} + +func TestCompareValue_CheckState_Bool_ValuesDifferAny_ValuesSameError(t *testing.T) { + t.Parallel() + + boolValuesDiffer := statecheck.CompareValue(compare.ValuesDifferAny{}) + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + bool_attribute = true + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + boolValuesDiffer.AddStateValue( + "test_resource.one", + tfjsonpath.New("bool_attribute"), + ), + }, + }, + { + Config: `resource "test_resource" "one" { + bool_attribute = true + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + boolValuesDiffer.AddStateValue( + "test_resource.one", + tfjsonpath.New("bool_attribute"), + ), + }, + ExpectError: regexp.MustCompile(`expected values to differ, but value is duplicated: true`), + }, + }, + }) +} + +func TestCompareValue_CheckState_Bool_ValuesDifferAny(t *testing.T) { + t.Parallel() + + boolValuesDiffer := statecheck.CompareValue(compare.ValuesDifferAny{}) + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + bool_attribute = true + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + boolValuesDiffer.AddStateValue( + "test_resource.one", + tfjsonpath.New("bool_attribute"), + ), + }, + }, + { + Config: `resource "test_resource" "one" { + bool_attribute = false + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + boolValuesDiffer.AddStateValue( + "test_resource.one", + tfjsonpath.New("bool_attribute"), + ), + }, + }, + { + Config: `resource "test_resource" "one" { + bool_attribute = false + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + boolValuesDiffer.AddStateValue( + "test_resource.one", + tfjsonpath.New("bool_attribute"), + ), + }, + }, + }, + }) +} From 6f29fb4a19adf0ca12a4b26d04010e3b4c43601b Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Tue, 30 Apr 2024 17:05:03 +0100 Subject: [PATCH 03/61] statecheck: Fixing references in tests --- statecheck/compare_value_contains_test.go | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/statecheck/compare_value_contains_test.go b/statecheck/compare_value_contains_test.go index 2785c7621..a8682905f 100644 --- a/statecheck/compare_value_contains_test.go +++ b/statecheck/compare_value_contains_test.go @@ -70,7 +70,7 @@ func TestCompareValueContains_CheckState_Map_ValuesSame(t *testing.T) { resource "test_resource" "two" { map_attribute = { "a": "str2", - "b": resource.test_resource.one.string_attribute, + "b": test_resource.one.string_attribute, } } `, @@ -105,8 +105,8 @@ func TestCompareValueContains_CheckState_Map_ValuesDiffer_SameError(t *testing.T resource "test_resource" "two" { map_attribute = { - "a": resource.test_resource.one.string_attribute, - "b": resource.test_resource.one.string_attribute, + "a": test_resource.one.string_attribute, + "b": test_resource.one.string_attribute, } } `, @@ -142,7 +142,7 @@ func TestCompareValueContains_CheckState_Map_ValuesDiffer(t *testing.T) { resource "test_resource" "two" { map_attribute = { - "a": resource.test_resource.one.string_attribute, + "a": test_resource.one.string_attribute, "b": "str2", } } @@ -216,7 +216,7 @@ func TestCompareValueContains_CheckState_Set_ValuesSame(t *testing.T) { resource "test_resource" "two" { set_attribute = [ "str2", - resource.test_resource.one.string_attribute + test_resource.one.string_attribute ] } `, @@ -251,8 +251,8 @@ func TestCompareValueContains_CheckState_Set_ValuesDiffer_SameError(t *testing.T resource "test_resource" "two" { set_attribute = [ - resource.test_resource.one.string_attribute, - resource.test_resource.one.string_attribute, + test_resource.one.string_attribute, + test_resource.one.string_attribute, ] } `, @@ -288,7 +288,7 @@ func TestCompareValueContains_CheckState_Set_ValuesDiffer(t *testing.T) { resource "test_resource" "two" { set_attribute = [ - resource.test_resource.one.string_attribute, + test_resource.one.string_attribute, "str2" ] } From 171c9a150a483196cef4b37c17daae399c453787 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Wed, 1 May 2024 14:39:27 +0100 Subject: [PATCH 04/61] Adding go docs and constructor to compare package types --- compare/compare_values.go | 8 -------- compare/value_comparer.go | 13 +++++++++++++ compare/values_differ.go | 14 ++++++++++++-- compare/values_differ_all.go | 14 ++++++++++++-- compare/values_differ_all_test.go | 2 +- compare/values_differ_any.go | 14 ++++++++++++-- compare/values_differ_any_test.go | 2 +- compare/values_differ_test.go | 2 +- compare/values_same.go | 14 ++++++++++++-- compare/values_same_any.go | 14 ++++++++++++-- compare/values_same_any_test.go | 2 +- compare/values_same_test.go | 2 +- statecheck/compare_value_contains_test.go | 16 ++++++++-------- statecheck/compare_value_pairs_test.go | 8 ++++---- statecheck/compare_value_test.go | 20 ++++++++++---------- 15 files changed, 100 insertions(+), 45 deletions(-) delete mode 100644 compare/compare_values.go create mode 100644 compare/value_comparer.go diff --git a/compare/compare_values.go b/compare/compare_values.go deleted file mode 100644 index 6e226267e..000000000 --- a/compare/compare_values.go +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package compare - -type ValueComparer interface { - CompareValues(values ...any) error -} diff --git a/compare/value_comparer.go b/compare/value_comparer.go new file mode 100644 index 000000000..af635898b --- /dev/null +++ b/compare/value_comparer.go @@ -0,0 +1,13 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package compare + +// ValueComparer defines an interface that is implemented to run comparison logic on multiple values. Individual +// implementations determine how the comparison is performed (e.g., values differ, values equal). +type ValueComparer interface { + // CompareValues should assert the given known values against any expectations. + // Values are always ordered in the order they were added. Use the error + // return to signal unexpected values or implementation errors. + CompareValues(values ...any) error +} diff --git a/compare/values_differ.go b/compare/values_differ.go index cde002ba9..9dade264f 100644 --- a/compare/values_differ.go +++ b/compare/values_differ.go @@ -5,9 +5,13 @@ package compare import "fmt" -type ValuesDiffer struct{} +var _ ValueComparer = valuesDiffer{} -func (v ValuesDiffer) CompareValues(values ...any) error { +type valuesDiffer struct{} + +// CompareValues determines whether each value in the sequence of the supplied values +// differs from the preceding value. +func (v valuesDiffer) CompareValues(values ...any) error { for i := 1; i < len(values); i++ { if values[i-1] == values[i] { return fmt.Errorf("expected values to differ, but they are the same: %v == %v", values[i-1], values[i]) @@ -16,3 +20,9 @@ func (v ValuesDiffer) CompareValues(values ...any) error { return nil } + +// ValuesDiffer returns a ValueComparer for asserting that each value in the sequence of +// the values supplied to the CompareValues method differs from the preceding value. +func ValuesDiffer() valuesDiffer { + return valuesDiffer{} +} diff --git a/compare/values_differ_all.go b/compare/values_differ_all.go index f585d4e20..c378bbfca 100644 --- a/compare/values_differ_all.go +++ b/compare/values_differ_all.go @@ -5,9 +5,13 @@ package compare import "fmt" -type ValuesDifferAll struct{} +var _ ValueComparer = valuesDifferAll{} -func (v ValuesDifferAll) CompareValues(values ...any) error { +type valuesDifferAll struct{} + +// CompareValues determines whether each value in the supplied values +// is unique. +func (v valuesDifferAll) CompareValues(values ...any) error { vals := map[any]struct{}{} for i := 0; i < len(values); i++ { @@ -20,3 +24,9 @@ func (v ValuesDifferAll) CompareValues(values ...any) error { return nil } + +// ValuesDifferAll returns a ValueComparer for asserting that each value in the +// values supplied to the CompareValues method is unique. +func ValuesDifferAll() valuesDifferAll { + return valuesDifferAll{} +} diff --git a/compare/values_differ_all_test.go b/compare/values_differ_all_test.go index 03fa7f637..61dbce126 100644 --- a/compare/values_differ_all_test.go +++ b/compare/values_differ_all_test.go @@ -42,7 +42,7 @@ func TestValuesDifferAll_CompareValues(t *testing.T) { t.Run(name, func(t *testing.T) { t.Parallel() - err := compare.ValuesDifferAll{}.CompareValues(testCase.in...) + err := compare.ValuesDifferAll().CompareValues(testCase.in...) if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { t.Errorf("unexpected difference: %s", diff) diff --git a/compare/values_differ_any.go b/compare/values_differ_any.go index f43192883..6e46db341 100644 --- a/compare/values_differ_any.go +++ b/compare/values_differ_any.go @@ -5,9 +5,13 @@ package compare import "fmt" -type ValuesDifferAny struct{} +var _ ValueComparer = valuesDifferAny{} -func (v ValuesDifferAny) CompareValues(values ...any) error { +type valuesDifferAny struct{} + +// CompareValues determines whether any value in the supplied values +// is unique. +func (v valuesDifferAny) CompareValues(values ...any) error { if len(values) < 2 { return nil } @@ -28,3 +32,9 @@ func (v ValuesDifferAny) CompareValues(values ...any) error { return nil } + +// ValuesDifferAny returns a ValueComparer for asserting that any value in the +// values supplied to the CompareValues method is unique. +func ValuesDifferAny() valuesDifferAny { + return valuesDifferAny{} +} diff --git a/compare/values_differ_any_test.go b/compare/values_differ_any_test.go index 43b2825ab..ce3e17de4 100644 --- a/compare/values_differ_any_test.go +++ b/compare/values_differ_any_test.go @@ -38,7 +38,7 @@ func TestValuesDifferAny_CompareValues(t *testing.T) { t.Run(name, func(t *testing.T) { t.Parallel() - err := compare.ValuesDifferAny{}.CompareValues(testCase.in...) + err := compare.ValuesDifferAny().CompareValues(testCase.in...) if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { t.Errorf("unexpected difference: %s", diff) diff --git a/compare/values_differ_test.go b/compare/values_differ_test.go index aecbe20ef..13b0d6e4c 100644 --- a/compare/values_differ_test.go +++ b/compare/values_differ_test.go @@ -38,7 +38,7 @@ func TestValuesDiffer_CompareValues(t *testing.T) { t.Run(name, func(t *testing.T) { t.Parallel() - err := compare.ValuesDiffer{}.CompareValues(testCase.in...) + err := compare.ValuesDiffer().CompareValues(testCase.in...) if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { t.Errorf("unexpected difference: %s", diff) diff --git a/compare/values_same.go b/compare/values_same.go index 219d641ba..19210c44e 100644 --- a/compare/values_same.go +++ b/compare/values_same.go @@ -5,9 +5,13 @@ package compare import "fmt" -type ValuesSame struct{} +var _ ValueComparer = valuesSame{} -func (v ValuesSame) CompareValues(values ...any) error { +type valuesSame struct{} + +// CompareValues determines whether each value in the sequence of the supplied values +// is the same as the preceding value. +func (v valuesSame) CompareValues(values ...any) error { for i := 1; i < len(values); i++ { if values[i-1] != values[i] { return fmt.Errorf("expected values to be the same, but they differ: %v != %v", values[i-1], values[i]) @@ -16,3 +20,9 @@ func (v ValuesSame) CompareValues(values ...any) error { return nil } + +// ValuesSame returns a ValueComparer for asserting that each value in the sequence of +// the values supplied to the CompareValues method is the same as the preceding value. +func ValuesSame() valuesSame { + return valuesSame{} +} diff --git a/compare/values_same_any.go b/compare/values_same_any.go index 651b1a620..bbc784853 100644 --- a/compare/values_same_any.go +++ b/compare/values_same_any.go @@ -5,9 +5,13 @@ package compare import "fmt" -type ValuesSameAny struct{} +var _ ValueComparer = valuesSameAny{} -func (v ValuesSameAny) CompareValues(values ...any) error { +type valuesSameAny struct{} + +// CompareValues determines whether any value in the supplied values +// matches any other value. +func (v valuesSameAny) CompareValues(values ...any) error { if len(values) < 2 { return nil } @@ -24,3 +28,9 @@ func (v ValuesSameAny) CompareValues(values ...any) error { return nil } + +// ValuesSameAny returns a ValueComparer for asserting whether any value in the +// values supplied to the CompareValues method is the same as any other value. +func ValuesSameAny() valuesSameAny { + return valuesSameAny{} +} diff --git a/compare/values_same_any_test.go b/compare/values_same_any_test.go index 4c9086e21..16363fbef 100644 --- a/compare/values_same_any_test.go +++ b/compare/values_same_any_test.go @@ -38,7 +38,7 @@ func TestValuesSameAny_CompareValues(t *testing.T) { t.Run(name, func(t *testing.T) { t.Parallel() - err := compare.ValuesSameAny{}.CompareValues(testCase.in...) + err := compare.ValuesSameAny().CompareValues(testCase.in...) if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { t.Errorf("unexpected difference: %s", diff) diff --git a/compare/values_same_test.go b/compare/values_same_test.go index e43c32776..452f3280a 100644 --- a/compare/values_same_test.go +++ b/compare/values_same_test.go @@ -38,7 +38,7 @@ func TestValuesSame_CompareValues(t *testing.T) { t.Run(name, func(t *testing.T) { t.Parallel() - err := compare.ValuesSame{}.CompareValues(testCase.in...) + err := compare.ValuesSame().CompareValues(testCase.in...) if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { t.Errorf("unexpected difference: %s", diff) diff --git a/statecheck/compare_value_contains_test.go b/statecheck/compare_value_contains_test.go index a8682905f..61f25f7f9 100644 --- a/statecheck/compare_value_contains_test.go +++ b/statecheck/compare_value_contains_test.go @@ -43,7 +43,7 @@ func TestCompareValueContains_CheckState_Map_ValuesSame_DifferError(t *testing.T tfjsonpath.New("map_attribute"), "test_resource.one", tfjsonpath.New("string_attribute"), - compare.ValuesSame{}, + compare.ValuesSame(), ), }, ExpectError: regexp.MustCompile("expected values to be the same, but they differ: str3 != str"), @@ -80,7 +80,7 @@ func TestCompareValueContains_CheckState_Map_ValuesSame(t *testing.T) { tfjsonpath.New("map_attribute"), "test_resource.one", tfjsonpath.New("string_attribute"), - compare.ValuesSame{}, + compare.ValuesSame(), ), }, }, @@ -116,7 +116,7 @@ func TestCompareValueContains_CheckState_Map_ValuesDiffer_SameError(t *testing.T tfjsonpath.New("map_attribute"), "test_resource.one", tfjsonpath.New("string_attribute"), - compare.ValuesDiffer{}, + compare.ValuesDiffer(), ), }, ExpectError: regexp.MustCompile("expected values to differ, but they are the same: str == str"), @@ -153,7 +153,7 @@ func TestCompareValueContains_CheckState_Map_ValuesDiffer(t *testing.T) { tfjsonpath.New("map_attribute"), "test_resource.one", tfjsonpath.New("string_attribute"), - compare.ValuesDiffer{}, + compare.ValuesDiffer(), ), }, }, @@ -189,7 +189,7 @@ func TestCompareValueContains_CheckState_Set_ValuesSame_DifferError(t *testing.T tfjsonpath.New("set_attribute"), "test_resource.one", tfjsonpath.New("string_attribute"), - compare.ValuesSame{}, + compare.ValuesSame(), ), }, ExpectError: regexp.MustCompile("expected values to be the same, but they differ: str3 != str"), @@ -226,7 +226,7 @@ func TestCompareValueContains_CheckState_Set_ValuesSame(t *testing.T) { tfjsonpath.New("set_attribute"), "test_resource.one", tfjsonpath.New("string_attribute"), - compare.ValuesSame{}, + compare.ValuesSame(), ), }, }, @@ -262,7 +262,7 @@ func TestCompareValueContains_CheckState_Set_ValuesDiffer_SameError(t *testing.T tfjsonpath.New("set_attribute"), "test_resource.one", tfjsonpath.New("string_attribute"), - compare.ValuesDiffer{}, + compare.ValuesDiffer(), ), }, ExpectError: regexp.MustCompile("expected values to differ, but they are the same: str == str"), @@ -299,7 +299,7 @@ func TestCompareValueContains_CheckState_Set_ValuesDiffer(t *testing.T) { tfjsonpath.New("set_attribute"), "test_resource.one", tfjsonpath.New("string_attribute"), - compare.ValuesDiffer{}, + compare.ValuesDiffer(), ), }, }, diff --git a/statecheck/compare_value_pairs_test.go b/statecheck/compare_value_pairs_test.go index 96bae8052..4df478832 100644 --- a/statecheck/compare_value_pairs_test.go +++ b/statecheck/compare_value_pairs_test.go @@ -37,7 +37,7 @@ func TestCompareValuePairs_CheckState_ValuesSame_DifferError(t *testing.T) { tfjsonpath.New("bool_attribute"), "test_resource.one", tfjsonpath.New("float_attribute"), - compare.ValuesSame{}, + compare.ValuesSame(), ), }, ExpectError: regexp.MustCompile("expected values to be the same, but they differ: true != 1.234"), @@ -71,7 +71,7 @@ func TestCompareValuePairs_CheckState_ValuesSame(t *testing.T) { tfjsonpath.New("bool_attribute"), "test_resource.two", tfjsonpath.New("bool_attribute"), - compare.ValuesSame{}, + compare.ValuesSame(), ), }, }, @@ -103,7 +103,7 @@ func TestCompareValuePairs_CheckState_ValuesDiffer_SameError(t *testing.T) { tfjsonpath.New("bool_attribute"), "test_resource.two", tfjsonpath.New("bool_attribute"), - compare.ValuesDiffer{}, + compare.ValuesDiffer(), ), }, ExpectError: regexp.MustCompile("expected values to differ, but they are the same: true == true"), @@ -133,7 +133,7 @@ func TestCompareValuePairs_CheckState_ValuesDiffer(t *testing.T) { tfjsonpath.New("bool_attribute"), "test_resource.one", tfjsonpath.New("float_attribute"), - compare.ValuesDiffer{}, + compare.ValuesDiffer(), ), }, }, diff --git a/statecheck/compare_value_test.go b/statecheck/compare_value_test.go index 3cce5057d..b6c16e13a 100644 --- a/statecheck/compare_value_test.go +++ b/statecheck/compare_value_test.go @@ -18,7 +18,7 @@ import ( func TestCompareValue_CheckState_Bool_ValuesSame_ValueDiffersError(t *testing.T) { t.Parallel() - boolValuesDiffer := statecheck.CompareValue(compare.ValuesSame{}) + boolValuesDiffer := statecheck.CompareValue(compare.ValuesSame()) r.Test(t, r.TestCase{ ProviderFactories: map[string]func() (*schema.Provider, error){ @@ -71,7 +71,7 @@ func TestCompareValue_CheckState_Bool_ValuesSame_ValueDiffersError(t *testing.T) func TestCompareValue_CheckState_Bool_ValuesSame(t *testing.T) { t.Parallel() - boolValuesDiffer := statecheck.CompareValue(compare.ValuesSame{}) + boolValuesDiffer := statecheck.CompareValue(compare.ValuesSame()) r.Test(t, r.TestCase{ ProviderFactories: map[string]func() (*schema.Provider, error){ @@ -111,7 +111,7 @@ func TestCompareValue_CheckState_Bool_ValuesSame(t *testing.T) { func TestCompareValue_CheckState_Bool_ValuesSameAny_ValueDiffersError(t *testing.T) { t.Parallel() - boolValuesDiffer := statecheck.CompareValue(compare.ValuesSameAny{}) + boolValuesDiffer := statecheck.CompareValue(compare.ValuesSameAny()) r.Test(t, r.TestCase{ ProviderFactories: map[string]func() (*schema.Provider, error){ @@ -152,7 +152,7 @@ func TestCompareValue_CheckState_Bool_ValuesSameAny_ValueDiffersError(t *testing func TestCompareValue_CheckState_Bool_ValuesSameAny(t *testing.T) { t.Parallel() - boolValuesDiffer := statecheck.CompareValue(compare.ValuesSameAny{}) + boolValuesDiffer := statecheck.CompareValue(compare.ValuesSameAny()) r.Test(t, r.TestCase{ ProviderFactories: map[string]func() (*schema.Provider, error){ @@ -204,7 +204,7 @@ func TestCompareValue_CheckState_Bool_ValuesSameAny(t *testing.T) { func TestCompareValue_CheckState_Bool_ValuesDiffer_ValueSameError(t *testing.T) { t.Parallel() - boolValuesDiffer := statecheck.CompareValue(compare.ValuesDiffer{}) + boolValuesDiffer := statecheck.CompareValue(compare.ValuesDiffer()) r.Test(t, r.TestCase{ ProviderFactories: map[string]func() (*schema.Provider, error){ @@ -257,7 +257,7 @@ func TestCompareValue_CheckState_Bool_ValuesDiffer_ValueSameError(t *testing.T) func TestCompareValue_CheckState_Bool_ValuesDiffer(t *testing.T) { t.Parallel() - boolValuesDiffer := statecheck.CompareValue(compare.ValuesDiffer{}) + boolValuesDiffer := statecheck.CompareValue(compare.ValuesDiffer()) r.Test(t, r.TestCase{ ProviderFactories: map[string]func() (*schema.Provider, error){ @@ -309,7 +309,7 @@ func TestCompareValue_CheckState_Bool_ValuesDiffer(t *testing.T) { func TestCompareValue_CheckState_Bool_ValuesDifferAll_ValuesSameError(t *testing.T) { t.Parallel() - boolValuesDiffer := statecheck.CompareValue(compare.ValuesDifferAll{}) + boolValuesDiffer := statecheck.CompareValue(compare.ValuesDifferAll()) r.Test(t, r.TestCase{ ProviderFactories: map[string]func() (*schema.Provider, error){ @@ -362,7 +362,7 @@ func TestCompareValue_CheckState_Bool_ValuesDifferAll_ValuesSameError(t *testing func TestCompareValue_CheckState_Bool_ValuesDifferAll(t *testing.T) { t.Parallel() - boolValuesDiffer := statecheck.CompareValue(compare.ValuesDifferAll{}) + boolValuesDiffer := statecheck.CompareValue(compare.ValuesDifferAll()) r.Test(t, r.TestCase{ ProviderFactories: map[string]func() (*schema.Provider, error){ @@ -402,7 +402,7 @@ func TestCompareValue_CheckState_Bool_ValuesDifferAll(t *testing.T) { func TestCompareValue_CheckState_Bool_ValuesDifferAny_ValuesSameError(t *testing.T) { t.Parallel() - boolValuesDiffer := statecheck.CompareValue(compare.ValuesDifferAny{}) + boolValuesDiffer := statecheck.CompareValue(compare.ValuesDifferAny()) r.Test(t, r.TestCase{ ProviderFactories: map[string]func() (*schema.Provider, error){ @@ -443,7 +443,7 @@ func TestCompareValue_CheckState_Bool_ValuesDifferAny_ValuesSameError(t *testing func TestCompareValue_CheckState_Bool_ValuesDifferAny(t *testing.T) { t.Parallel() - boolValuesDiffer := statecheck.CompareValue(compare.ValuesDifferAny{}) + boolValuesDiffer := statecheck.CompareValue(compare.ValuesDifferAny()) r.Test(t, r.TestCase{ ProviderFactories: map[string]func() (*schema.Provider, error){ From 38b95179a95c43b888cb6e50ef84ea472fc4fb66 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Wed, 1 May 2024 15:01:58 +0100 Subject: [PATCH 05/61] Adding go docs for CompareValue state checks --- statecheck/compare_value.go | 3 ++- statecheck/compare_value_contains.go | 3 +++ statecheck/compare_value_pairs.go | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/statecheck/compare_value.go b/statecheck/compare_value.go index 2105bf890..68a6ef9d5 100644 --- a/statecheck/compare_value.go +++ b/statecheck/compare_value.go @@ -105,7 +105,8 @@ func (e *compareValue) CheckState(ctx context.Context, req CheckStateRequest, re } } -// CompareValue returns a state check that compares sequential values retrieved from state. +// CompareValue returns a state check that compares values retrieved from state using the +// supplied value comparer. func CompareValue(comparer compare.ValueComparer) *compareValue { return &compareValue{ comparer: comparer, diff --git a/statecheck/compare_value_contains.go b/statecheck/compare_value_contains.go index a6ae2e000..c32ba6dfd 100644 --- a/statecheck/compare_value_contains.go +++ b/statecheck/compare_value_contains.go @@ -139,6 +139,9 @@ func (e *compareValueContains) CheckState(ctx context.Context, req CheckStateReq resp.Error = fmt.Errorf("expected []any or map[string]any value for CompareValueContains check, got: %T", resultOne) } +// CompareValueContains returns a state check that compares the values in state for the list, map, object +// or set defined by the first given resource address and path with the value in state for the second +// given resource address and path using the supplied value comparer. func CompareValueContains(resourceAddressOne string, attributePathOne tfjsonpath.Path, resourceAddressTwo string, attributePathTwo tfjsonpath.Path, comparer compare.ValueComparer) StateCheck { return &compareValueContains{ resourceAddressOne: resourceAddressOne, diff --git a/statecheck/compare_value_pairs.go b/statecheck/compare_value_pairs.go index b5d5352c5..8db67c562 100644 --- a/statecheck/compare_value_pairs.go +++ b/statecheck/compare_value_pairs.go @@ -98,6 +98,8 @@ func (e *compareValuePairs) CheckState(ctx context.Context, req CheckStateReques } } +// CompareValuePairs returns a state check that compares the value in state for the first given resource address and +// path with the value in state for the second given resource address and path using the supplied value comparer. func CompareValuePairs(resourceAddressOne string, attributePathOne tfjsonpath.Path, resourceAddressTwo string, attributePathTwo tfjsonpath.Path, comparer compare.ValueComparer) StateCheck { return &compareValuePairs{ resourceAddressOne: resourceAddressOne, From 973e8082e578e45148c941e6da3e339be9bb74f4 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Wed, 1 May 2024 17:11:56 +0100 Subject: [PATCH 06/61] Refactoring ValueComparer implementations to handle slice and map of interface values --- compare/values_differ.go | 7 +++++-- compare/values_differ_all.go | 15 ++++++++------- compare/values_differ_all_test.go | 20 +++++++++++++++++--- compare/values_differ_any.go | 20 ++++++++++---------- compare/values_differ_any_test.go | 27 ++++++++++++++++++++++++--- compare/values_differ_test.go | 16 +++++++++++++++- compare/values_same.go | 7 +++++-- compare/values_same_any.go | 19 ++++++++++--------- compare/values_same_any_test.go | 18 +++++++++++++++++- compare/values_same_test.go | 18 +++++++++++++++++- 10 files changed, 128 insertions(+), 39 deletions(-) diff --git a/compare/values_differ.go b/compare/values_differ.go index 9dade264f..24bd2ae22 100644 --- a/compare/values_differ.go +++ b/compare/values_differ.go @@ -3,7 +3,10 @@ package compare -import "fmt" +import ( + "fmt" + "reflect" +) var _ ValueComparer = valuesDiffer{} @@ -13,7 +16,7 @@ type valuesDiffer struct{} // differs from the preceding value. func (v valuesDiffer) CompareValues(values ...any) error { for i := 1; i < len(values); i++ { - if values[i-1] == values[i] { + if reflect.DeepEqual(values[i-1], values[i]) { return fmt.Errorf("expected values to differ, but they are the same: %v == %v", values[i-1], values[i]) } } diff --git a/compare/values_differ_all.go b/compare/values_differ_all.go index c378bbfca..f821b4742 100644 --- a/compare/values_differ_all.go +++ b/compare/values_differ_all.go @@ -3,7 +3,10 @@ package compare -import "fmt" +import ( + "fmt" + "reflect" +) var _ ValueComparer = valuesDifferAll{} @@ -12,14 +15,12 @@ type valuesDifferAll struct{} // CompareValues determines whether each value in the supplied values // is unique. func (v valuesDifferAll) CompareValues(values ...any) error { - vals := map[any]struct{}{} - for i := 0; i < len(values); i++ { - if _, ok := vals[values[i]]; ok { - return fmt.Errorf("expected values to differ, but value is duplicated: %v", values[i]) + for j := i + 1; j < len(values); j++ { + if reflect.DeepEqual(values[i], values[j]) { + return fmt.Errorf("expected values to differ, but value is duplicated: %v", values[i]) + } } - - vals[values[i]] = struct{}{} } return nil diff --git a/compare/values_differ_all_test.go b/compare/values_differ_all_test.go index 61dbce126..aa38a903c 100644 --- a/compare/values_differ_all_test.go +++ b/compare/values_differ_all_test.go @@ -26,14 +26,28 @@ func TestValuesDifferAll_CompareValues(t *testing.T) { "non-matching-values": { in: []any{"str", "other_str", "another_str"}, }, - "matching-values": { - in: []any{"str", "str", "str"}, + "matching-values-string": { + in: []any{"str", "str"}, expectedError: fmt.Errorf("expected values to differ, but value is duplicated: str"), }, - "non-sequential-matching-values": { + "non-sequential-matching-values-string": { in: []any{"str", "other_str", "str"}, expectedError: fmt.Errorf("expected values to differ, but value is duplicated: str"), }, + "matching-values-slice": { + in: []any{ + []any{"str"}, + []any{"str"}, + }, + expectedError: fmt.Errorf("expected values to differ, but value is duplicated: [str]"), + }, + "matching-values-map": { + in: []any{ + map[string]any{"a": "str"}, + map[string]any{"a": "str"}, + }, + expectedError: fmt.Errorf("expected values to differ, but value is duplicated: map[a:str]"), + }, } for name, testCase := range testCases { diff --git a/compare/values_differ_any.go b/compare/values_differ_any.go index 6e46db341..086d549f4 100644 --- a/compare/values_differ_any.go +++ b/compare/values_differ_any.go @@ -3,7 +3,10 @@ package compare -import "fmt" +import ( + "fmt" + "reflect" +) var _ ValueComparer = valuesDifferAny{} @@ -16,21 +19,18 @@ func (v valuesDifferAny) CompareValues(values ...any) error { return nil } - vals := map[any]int{} + var val any for i := 0; i < len(values); i++ { - vals[values[i]]++ - } - - if len(vals) < 2 { - for k, v := range vals { - if v > 1 { - return fmt.Errorf("expected values to differ, but value is duplicated: %v", k) + val = values[i] + for j := 1; j < len(values); j++ { + if !reflect.DeepEqual(values[i], values[j]) { + return nil } } } - return nil + return fmt.Errorf("expected values to differ, but value is duplicated: %v", val) } // ValuesDifferAny returns a ValueComparer for asserting that any value in the diff --git a/compare/values_differ_any_test.go b/compare/values_differ_any_test.go index ce3e17de4..5e66baaf5 100644 --- a/compare/values_differ_any_test.go +++ b/compare/values_differ_any_test.go @@ -23,13 +23,34 @@ func TestValuesDifferAny_CompareValues(t *testing.T) { "single-value": { in: []any{"str"}, }, - "non-matching-sequential-values": { + "non-matching-sequential-values-string": { in: []any{"str", "other_str", "str"}, }, - "matching-values": { - in: []any{"str", "str", "str"}, + "non-matching-sequential-values-slice": { + in: []any{ + []any{"str"}, + []any{"other_str"}, + []any{"str"}, + }, + }, + "matching-values-string": { + in: []any{"str", "str"}, expectedError: fmt.Errorf("expected values to differ, but value is duplicated: str"), }, + "matching-values-slice": { + in: []any{ + []any{"str"}, + []any{"str"}, + }, + expectedError: fmt.Errorf("expected values to differ, but value is duplicated: [str]"), + }, + "matching-values-map": { + in: []any{ + map[string]any{"a": "str"}, + map[string]any{"a": "str"}, + }, + expectedError: fmt.Errorf("expected values to differ, but value is duplicated: map[a:str]"), + }, } for name, testCase := range testCases { diff --git a/compare/values_differ_test.go b/compare/values_differ_test.go index 13b0d6e4c..35653f339 100644 --- a/compare/values_differ_test.go +++ b/compare/values_differ_test.go @@ -26,10 +26,24 @@ func TestValuesDiffer_CompareValues(t *testing.T) { "non-matching-sequential-values": { in: []any{"str", "other_str", "str"}, }, - "matching-values": { + "matching-values-string": { in: []any{"str", "other_str", "other_str"}, expectedError: fmt.Errorf("expected values to differ, but they are the same: other_str == other_str"), }, + "matching-values-slice": { + in: []any{ + []any{"other_str"}, + []any{"other_str"}, + }, + expectedError: fmt.Errorf("expected values to differ, but they are the same: [other_str] == [other_str]"), + }, + "matching-values-map": { + in: []any{ + map[string]any{"a": "other_str"}, + map[string]any{"a": "other_str"}, + }, + expectedError: fmt.Errorf("expected values to differ, but they are the same: map[a:other_str] == map[a:other_str]"), + }, } for name, testCase := range testCases { diff --git a/compare/values_same.go b/compare/values_same.go index 19210c44e..46ee13f31 100644 --- a/compare/values_same.go +++ b/compare/values_same.go @@ -3,7 +3,10 @@ package compare -import "fmt" +import ( + "fmt" + "reflect" +) var _ ValueComparer = valuesSame{} @@ -13,7 +16,7 @@ type valuesSame struct{} // is the same as the preceding value. func (v valuesSame) CompareValues(values ...any) error { for i := 1; i < len(values); i++ { - if values[i-1] != values[i] { + if !reflect.DeepEqual(values[i-1], values[i]) { return fmt.Errorf("expected values to be the same, but they differ: %v != %v", values[i-1], values[i]) } } diff --git a/compare/values_same_any.go b/compare/values_same_any.go index bbc784853..3190c51d6 100644 --- a/compare/values_same_any.go +++ b/compare/values_same_any.go @@ -3,7 +3,10 @@ package compare -import "fmt" +import ( + "fmt" + "reflect" +) var _ ValueComparer = valuesSameAny{} @@ -16,17 +19,15 @@ func (v valuesSameAny) CompareValues(values ...any) error { return nil } - vals := map[any]struct{}{} - for i := 0; i < len(values); i++ { - vals[values[i]] = struct{}{} - } - - if len(vals) == len(values) { - return fmt.Errorf("expected at least two values to be the same, but all values differ") + for j := i + 1; j < len(values); j++ { + if reflect.DeepEqual(values[i], values[j]) { + return nil + } + } } - return nil + return fmt.Errorf("expected at least two values to be the same, but all values differ") } // ValuesSameAny returns a ValueComparer for asserting whether any value in the diff --git a/compare/values_same_any_test.go b/compare/values_same_any_test.go index 16363fbef..4d43a404f 100644 --- a/compare/values_same_any_test.go +++ b/compare/values_same_any_test.go @@ -26,10 +26,26 @@ func TestValuesSameAny_CompareValues(t *testing.T) { "non-sequential-matching-values": { in: []any{"str", "other_str", "str"}, }, - "non-matching-values": { + "non-matching-values-string": { in: []any{"str", "other_str", "another_str"}, expectedError: fmt.Errorf("expected at least two values to be the same, but all values differ"), }, + "non-matching-values-slice": { + in: []any{ + []any{"str"}, + []any{"other_str"}, + []any{"another_str"}, + }, + expectedError: fmt.Errorf("expected at least two values to be the same, but all values differ"), + }, + "non-matching-values-map": { + in: []any{ + map[string]any{"a": "str"}, + map[string]any{"a": "other_str"}, + map[string]any{"a": "another_str"}, + }, + expectedError: fmt.Errorf("expected at least two values to be the same, but all values differ"), + }, } for name, testCase := range testCases { diff --git a/compare/values_same_test.go b/compare/values_same_test.go index 452f3280a..dde2ee6ea 100644 --- a/compare/values_same_test.go +++ b/compare/values_same_test.go @@ -26,10 +26,26 @@ func TestValuesSame_CompareValues(t *testing.T) { "matching-values": { in: []any{"str", "str", "str"}, }, - "non-matching-values": { + "non-matching-values-string": { in: []any{"str", "str", "other_str"}, expectedError: fmt.Errorf("expected values to be the same, but they differ: str != other_str"), }, + "non-matching-values-slice": { + in: []any{ + []any{"str"}, + []any{"str"}, + []any{"other_str"}, + }, + expectedError: fmt.Errorf("expected values to be the same, but they differ: [str] != [other_str]"), + }, + "non-matching-values-map": { + in: []any{ + map[string]any{"a": "str"}, + map[string]any{"a": "str"}, + map[string]any{"a": "other_str"}, + }, + expectedError: fmt.Errorf("expected values to be the same, but they differ: map[a:str] != map[a:other_str]"), + }, } for name, testCase := range testCases { From 1ce7f29e7c4674e155f2de271863c0e3e325e653 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Fri, 3 May 2024 15:30:37 +0100 Subject: [PATCH 07/61] Refactoring CompareValueCollection to extract nested values --- statecheck/compare_value_collection.go | 209 ++++++ statecheck/compare_value_collection_test.go | 665 ++++++++++++++++++++ statecheck/compare_value_contains.go | 153 ----- statecheck/compare_value_contains_test.go | 308 --------- statecheck/expect_known_value_test.go | 20 + 5 files changed, 894 insertions(+), 461 deletions(-) create mode 100644 statecheck/compare_value_collection.go create mode 100644 statecheck/compare_value_collection_test.go delete mode 100644 statecheck/compare_value_contains.go delete mode 100644 statecheck/compare_value_contains_test.go diff --git a/statecheck/compare_value_collection.go b/statecheck/compare_value_collection.go new file mode 100644 index 000000000..4b16522d3 --- /dev/null +++ b/statecheck/compare_value_collection.go @@ -0,0 +1,209 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package statecheck + +import ( + "context" + "errors" + "fmt" + "sort" + + tfjson "github.com/hashicorp/terraform-json" + + "github.com/hashicorp/terraform-plugin-testing/compare" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +// Resource State Check +var _ StateCheck = &compareValueCollection{} + +type compareValueCollection struct { + resourceAddressOne string + collectionPath []tfjsonpath.Path + resourceAddressTwo string + attributePath tfjsonpath.Path + comparer compare.ValueComparer +} + +func walkCollectionPath(obj any, paths []tfjsonpath.Path, results []any) ([]any, error) { + switch t := obj.(type) { + case []any: + for _, v := range t { + if len(paths) == 0 { + results = append(results, v) + continue + } + + if len(paths) == 0 { + break + } + + x, err := tfjsonpath.Traverse(v, paths[0]) + + if err != nil { + return results, err + } + + results, err = walkCollectionPath(x, paths[1:], results) + + if err != nil { + return results, err + } + } + case map[string]any: + keys := make([]string, 0, len(t)) + + for k := range t { + keys = append(keys, k) + } + + sort.Strings(keys) + + for _, key := range keys { + if len(paths) == 0 { + results = append(results, t[key]) + continue + } + + if len(paths) == 0 { + break + } + + x, err := tfjsonpath.Traverse(t[key], paths[0]) + + if err != nil { + return results, err + } + + results, err = walkCollectionPath(x, paths[1:], results) + + if err != nil { + return results, err + } + } + default: + results = append(results, obj) + } + + return results, nil +} + +// CheckState implements the state check logic. +func (e *compareValueCollection) CheckState(ctx context.Context, req CheckStateRequest, resp *CheckStateResponse) { + var resourceOne *tfjson.StateResource + var resourceTwo *tfjson.StateResource + + if req.State == nil { + resp.Error = fmt.Errorf("state is nil") + + return + } + + if req.State.Values == nil { + resp.Error = fmt.Errorf("state does not contain any state values") + + return + } + + if req.State.Values.RootModule == nil { + resp.Error = fmt.Errorf("state does not contain a root module") + + return + } + + for _, r := range req.State.Values.RootModule.Resources { + if e.resourceAddressOne == r.Address { + resourceOne = r + + break + } + } + + if resourceOne == nil { + resp.Error = fmt.Errorf("%s - Resource not found in state", e.resourceAddressOne) + + return + } + + resultOne, err := tfjsonpath.Traverse(resourceOne.AttributeValues, e.collectionPath[0]) + + if err != nil { + resp.Error = err + + return + } + + var results []any + + results, err = walkCollectionPath(resultOne, e.collectionPath[1:], results) + + if err != nil { + resp.Error = err + + return + } + + for _, r := range req.State.Values.RootModule.Resources { + if e.resourceAddressTwo == r.Address { + resourceTwo = r + + break + } + } + + if resourceTwo == nil { + resp.Error = fmt.Errorf("%s - Resource not found in state", e.resourceAddressTwo) + + return + } + + resultTwo, err := tfjsonpath.Traverse(resourceTwo.AttributeValues, e.attributePath) + + if err != nil { + resp.Error = err + + return + } + + var errs []error + + for _, v := range results { + switch resultTwo.(type) { + case []any: + errs = append(errs, e.comparer.CompareValues([]any{v}, resultTwo)) + default: + errs = append(errs, e.comparer.CompareValues(v, resultTwo)) + } + } + + for _, err = range errs { + if err == nil { + return + } + } + + errMsgs := map[string]struct{}{} + + for _, err = range errs { + if _, ok := errMsgs[err.Error()]; ok { + continue + } + + resp.Error = errors.Join(resp.Error, err) + + errMsgs[err.Error()] = struct{}{} + } +} + +// CompareValueCollection returns a state check that iterates over each element in a collection and compares the value of each element +// with the value of an attribute using the given value comparer. +func CompareValueCollection(resourceAddressOne string, collectionPath []tfjsonpath.Path, resourceAddressTwo string, attributePath tfjsonpath.Path, comparer compare.ValueComparer) StateCheck { + return &compareValueCollection{ + resourceAddressOne: resourceAddressOne, + collectionPath: collectionPath, + resourceAddressTwo: resourceAddressTwo, + attributePath: attributePath, + comparer: comparer, + } +} diff --git a/statecheck/compare_value_collection_test.go b/statecheck/compare_value_collection_test.go new file mode 100644 index 000000000..a2d8bee2d --- /dev/null +++ b/statecheck/compare_value_collection_test.go @@ -0,0 +1,665 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package statecheck_test + +import ( + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/hashicorp/terraform-plugin-testing/compare" + r "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +func TestCompareValueCollection_CheckState_Map_ValuesSame_DifferError(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + string_attribute = "str" + } + + resource "test_resource" "two" { + map_attribute = { + "a": "str2", + "b": "str3", + } + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValueCollection( + "test_resource.two", + []tfjsonpath.Path{ + tfjsonpath.New("map_attribute"), + }, + "test_resource.one", + tfjsonpath.New("string_attribute"), + compare.ValuesSame(), + ), + }, + ExpectError: regexp.MustCompile("expected values to be the same, but they differ: str2 != str\nexpected values to be the same, but they differ: str3 != str"), + }, + }, + }) +} + +func TestCompareValueCollection_CheckState_Map_ValuesSame(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + string_attribute = "str" + } + + resource "test_resource" "two" { + map_attribute = { + "a": "str2", + "b": test_resource.one.string_attribute, + } + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValueCollection( + "test_resource.two", + []tfjsonpath.Path{ + tfjsonpath.New("map_attribute"), + }, + "test_resource.one", + tfjsonpath.New("string_attribute"), + compare.ValuesSame(), + ), + }, + }, + }, + }) +} + +func TestCompareValueCollection_CheckState_Map_ValuesDiffer_SameError(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + string_attribute = "str" + } + + resource "test_resource" "two" { + map_attribute = { + "a": test_resource.one.string_attribute, + "b": test_resource.one.string_attribute, + } + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValueCollection( + "test_resource.two", + []tfjsonpath.Path{ + tfjsonpath.New("map_attribute"), + }, + "test_resource.one", + tfjsonpath.New("string_attribute"), + compare.ValuesDiffer(), + ), + }, + ExpectError: regexp.MustCompile("expected values to differ, but they are the same: str == str"), + }, + }, + }) +} + +func TestCompareValueCollection_CheckState_Map_ValuesDiffer(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + string_attribute = "str" + } + + resource "test_resource" "two" { + map_attribute = { + "a": test_resource.one.string_attribute, + "b": "str2", + } + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValueCollection( + "test_resource.two", + []tfjsonpath.Path{ + tfjsonpath.New("map_attribute"), + }, + "test_resource.one", + tfjsonpath.New("string_attribute"), + compare.ValuesDiffer(), + ), + }, + }, + }, + }) +} + +func TestCompareValueCollection_CheckState_Set_ValuesSame_DifferError(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + string_attribute = "str" + } + + resource "test_resource" "two" { + set_attribute = [ + "str2", + "str3" + ] + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValueCollection( + "test_resource.two", + []tfjsonpath.Path{ + tfjsonpath.New("set_attribute"), + }, + "test_resource.one", + tfjsonpath.New("string_attribute"), + compare.ValuesSame(), + ), + }, + ExpectError: regexp.MustCompile("expected values to be the same, but they differ: str3 != str"), + }, + }, + }) +} + +func TestCompareValueCollection_CheckState_Set_ValuesSame(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + string_attribute = "str" + } + + resource "test_resource" "two" { + set_attribute = [ + "str2", + test_resource.one.string_attribute + ] + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValueCollection( + "test_resource.two", + []tfjsonpath.Path{ + tfjsonpath.New("set_attribute"), + }, + "test_resource.one", + tfjsonpath.New("string_attribute"), + compare.ValuesSame(), + ), + }, + }, + }, + }) +} + +func TestCompareValueCollection_CheckState_Set_ValuesDiffer_SameError(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + string_attribute = "str" + } + + resource "test_resource" "two" { + set_attribute = [ + test_resource.one.string_attribute, + test_resource.one.string_attribute, + ] + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValueCollection( + "test_resource.two", + []tfjsonpath.Path{ + tfjsonpath.New("set_attribute"), + }, + "test_resource.one", + tfjsonpath.New("string_attribute"), + compare.ValuesDiffer(), + ), + }, + ExpectError: regexp.MustCompile("expected values to differ, but they are the same: str == str"), + }, + }, + }) +} + +func TestCompareValueCollection_CheckState_Set_ValuesDiffer(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + string_attribute = "str" + } + + resource "test_resource" "two" { + set_attribute = [ + test_resource.one.string_attribute, + "str2" + ] + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValueCollection( + "test_resource.two", + []tfjsonpath.Path{ + tfjsonpath.New("set_attribute"), + }, + "test_resource.one", + tfjsonpath.New("string_attribute"), + compare.ValuesDiffer(), + ), + }, + }, + }, + }) +} + +func TestCompareValueCollection_CheckState_SetNestedBlock_String_ValuesSame_DifferError(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + string_attribute = "str" + } + + resource "test_resource" "two" { + set_nested_block { + set_nested_block_attribute = "str1" + } + set_nested_block { + set_nested_block_attribute = "str2" + } + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValueCollection( + "test_resource.two", + []tfjsonpath.Path{ + tfjsonpath.New("set_nested_block"), + tfjsonpath.New("set_nested_block_attribute"), + }, + "test_resource.one", + tfjsonpath.New("string_attribute"), + compare.ValuesSame(), + ), + }, + ExpectError: regexp.MustCompile("expected values to be the same, but they differ: str1 != str\nexpected values to be the same, but they differ: str2 != str"), + }, + }, + }) +} + +func TestCompareValueCollection_CheckState_SetNestedBlock_String_ValuesSame(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + string_attribute = "str" + } + + resource "test_resource" "two" { + set_nested_block { + set_nested_block_attribute = "str1" + } + set_nested_block { + set_nested_block_attribute = "str" + } + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValueCollection( + "test_resource.two", + []tfjsonpath.Path{ + tfjsonpath.New("set_nested_block"), + tfjsonpath.New("set_nested_block_attribute"), + }, + "test_resource.one", + tfjsonpath.New("string_attribute"), + compare.ValuesSame(), + ), + }, + }, + }, + }) +} + +func TestCompareValueCollection_CheckState_SetNestedBlock_SetNestedBlock_ValuesSame(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + set_nested_block { + set_nested_block_attribute = "str" + } + } + + resource "test_resource" "two" { + set_nested_nested_block { + set_nested_block { + set_nested_block_attribute = "str1" + } + set_nested_block { + set_nested_block_attribute = "str" + } + } + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValueCollection( + "test_resource.two", + []tfjsonpath.Path{ + tfjsonpath.New("set_nested_nested_block"), + tfjsonpath.New("set_nested_block"), + }, + "test_resource.one", + tfjsonpath.New("set_nested_block"), + compare.ValuesSame(), + ), + }, + }, + }, + }) +} + +func TestCompareValueCollection_CheckState_SetNestedBlockBlock_SetNestedBlockBlock_ValuesSame(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + set_nested_nested_block { + set_nested_block { + set_nested_block_attribute = "str1" + } + set_nested_block { + set_nested_block_attribute = "str" + } + } + } + + resource "test_resource" "two" { + set_nested_nested_block { + set_nested_block { + set_nested_block_attribute = "str2" + } + set_nested_block { + set_nested_block_attribute = "st3" + } + } + set_nested_nested_block { + set_nested_block { + set_nested_block_attribute = "str1" + } + set_nested_block { + set_nested_block_attribute = "str" + } + } + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValueCollection( + "test_resource.two", + []tfjsonpath.Path{ + tfjsonpath.New("set_nested_nested_block"), + }, + "test_resource.one", + tfjsonpath.New("set_nested_nested_block"), + compare.ValuesSame(), + ), + }, + }, + }, + }) +} + +func TestCompareValueCollection_CheckState_SetNested_ValuesDiffer(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + set_nested_nested_block { + set_nested_block { + set_nested_block_attribute = "str_x" + } + set_nested_block { + set_nested_block_attribute = "str_y" + } + } + } + + resource "test_resource" "two" { + set_nested_nested_block { + set_nested_block { + set_nested_block_attribute = "str_c" + } + set_nested_block { + set_nested_block_attribute = "str_d" + } + } + set_nested_nested_block { + set_nested_block { + set_nested_block_attribute = "str_a" + } + set_nested_block { + set_nested_block_attribute = "str_b" + } + } + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValueCollection( + "test_resource.two", + []tfjsonpath.Path{ + tfjsonpath.New("set_nested_nested_block"), + tfjsonpath.New("set_nested_block"), + tfjsonpath.New("set_nested_block_attribute"), + }, + "test_resource.one", + tfjsonpath.New("set_nested_nested_block").AtSliceIndex(0).AtMapKey("set_nested_block").AtSliceIndex(0).AtMapKey("set_nested_block_attribute"), + compare.ValuesDiffer(), + ), + statecheck.CompareValueCollection( + "test_resource.two", + []tfjsonpath.Path{ + tfjsonpath.New("set_nested_nested_block"), + tfjsonpath.New("set_nested_block"), + }, + "test_resource.one", + tfjsonpath.New("set_nested_nested_block").AtSliceIndex(0).AtMapKey("set_nested_block").AtSliceIndex(0), + compare.ValuesDiffer(), + ), + statecheck.CompareValueCollection( + "test_resource.two", + []tfjsonpath.Path{ + tfjsonpath.New("set_nested_nested_block"), + }, + "test_resource.one", + tfjsonpath.New("set_nested_nested_block"), + compare.ValuesDiffer(), + ), + }, + }, + }, + }) +} + +func TestCompareValueCollection_CheckState_SetNested_ValuesSame(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + set_nested_nested_block { + set_nested_block { + set_nested_block_attribute = "str_a" + } + set_nested_block { + set_nested_block_attribute = "str_b" + } + } + } + + resource "test_resource" "two" { + set_nested_nested_block { + set_nested_block { + set_nested_block_attribute = "str_c" + } + set_nested_block { + set_nested_block_attribute = "str_d" + } + } + set_nested_nested_block { + set_nested_block { + set_nested_block_attribute = "str_a" + } + set_nested_block { + set_nested_block_attribute = "str_b" + } + } + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValueCollection( + "test_resource.two", + []tfjsonpath.Path{ + tfjsonpath.New("set_nested_nested_block"), + tfjsonpath.New("set_nested_block"), + tfjsonpath.New("set_nested_block_attribute"), + }, + "test_resource.one", + tfjsonpath.New("set_nested_nested_block").AtSliceIndex(0).AtMapKey("set_nested_block").AtSliceIndex(0).AtMapKey("set_nested_block_attribute"), + compare.ValuesSame(), + ), + statecheck.CompareValueCollection( + "test_resource.two", + []tfjsonpath.Path{ + tfjsonpath.New("set_nested_nested_block"), + tfjsonpath.New("set_nested_block"), + }, + "test_resource.one", + tfjsonpath.New("set_nested_nested_block").AtSliceIndex(0).AtMapKey("set_nested_block").AtSliceIndex(0), + compare.ValuesSame(), + ), + statecheck.CompareValueCollection( + "test_resource.two", + []tfjsonpath.Path{ + tfjsonpath.New("set_nested_nested_block"), + }, + "test_resource.one", + tfjsonpath.New("set_nested_nested_block"), + compare.ValuesSame(), + ), + }, + }, + }, + }) +} diff --git a/statecheck/compare_value_contains.go b/statecheck/compare_value_contains.go deleted file mode 100644 index c32ba6dfd..000000000 --- a/statecheck/compare_value_contains.go +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package statecheck - -import ( - "context" - "fmt" - "sort" - - tfjson "github.com/hashicorp/terraform-json" - - "github.com/hashicorp/terraform-plugin-testing/compare" - "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" -) - -// Resource State Check -var _ StateCheck = &compareValueContains{} - -type compareValueContains struct { - resourceAddressOne string - attributePathOne tfjsonpath.Path - resourceAddressTwo string - attributePathTwo tfjsonpath.Path - comparer compare.ValueComparer -} - -// CheckState implements the state check logic. -func (e *compareValueContains) CheckState(ctx context.Context, req CheckStateRequest, resp *CheckStateResponse) { - var resourceOne *tfjson.StateResource - var resourceTwo *tfjson.StateResource - - if req.State == nil { - resp.Error = fmt.Errorf("state is nil") - - return - } - - if req.State.Values == nil { - resp.Error = fmt.Errorf("state does not contain any state values") - - return - } - - if req.State.Values.RootModule == nil { - resp.Error = fmt.Errorf("state does not contain a root module") - - return - } - - for _, r := range req.State.Values.RootModule.Resources { - if e.resourceAddressOne == r.Address { - resourceOne = r - - break - } - } - - if resourceOne == nil { - resp.Error = fmt.Errorf("%s - Resource not found in state", e.resourceAddressOne) - - return - } - - resultOne, err := tfjsonpath.Traverse(resourceOne.AttributeValues, e.attributePathOne) - - if err != nil { - resp.Error = err - - return - } - - for _, r := range req.State.Values.RootModule.Resources { - if e.resourceAddressTwo == r.Address { - resourceTwo = r - - break - } - } - - if resourceTwo == nil { - resp.Error = fmt.Errorf("%s - Resource not found in state", e.resourceAddressTwo) - - return - } - - resultTwo, err := tfjsonpath.Traverse(resourceTwo.AttributeValues, e.attributePathTwo) - - if err != nil { - resp.Error = err - - return - } - - if listOrSet, ok := resultOne.([]any); ok { - var errs []error - - for _, v := range listOrSet { - errs = append(errs, e.comparer.CompareValues(v, resultTwo)) - } - - for _, err = range errs { - if err == nil { - return - } - } - - resp.Error = err - - return - } - - if mapOrObject, ok := resultOne.(map[string]any); ok { - keys := make([]string, 0, len(mapOrObject)) - - for k := range mapOrObject { - keys = append(keys, k) - } - - sort.Strings(keys) - - var errs []error - - for _, key := range keys { - errs = append(errs, e.comparer.CompareValues(mapOrObject[key], resultTwo)) - } - - for _, err = range errs { - if err == nil { - return - } - } - - resp.Error = err - - return - } - - resp.Error = fmt.Errorf("expected []any or map[string]any value for CompareValueContains check, got: %T", resultOne) -} - -// CompareValueContains returns a state check that compares the values in state for the list, map, object -// or set defined by the first given resource address and path with the value in state for the second -// given resource address and path using the supplied value comparer. -func CompareValueContains(resourceAddressOne string, attributePathOne tfjsonpath.Path, resourceAddressTwo string, attributePathTwo tfjsonpath.Path, comparer compare.ValueComparer) StateCheck { - return &compareValueContains{ - resourceAddressOne: resourceAddressOne, - attributePathOne: attributePathOne, - resourceAddressTwo: resourceAddressTwo, - attributePathTwo: attributePathTwo, - comparer: comparer, - } -} diff --git a/statecheck/compare_value_contains_test.go b/statecheck/compare_value_contains_test.go deleted file mode 100644 index 61f25f7f9..000000000 --- a/statecheck/compare_value_contains_test.go +++ /dev/null @@ -1,308 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package statecheck_test - -import ( - "regexp" - "testing" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - - "github.com/hashicorp/terraform-plugin-testing/compare" - r "github.com/hashicorp/terraform-plugin-testing/helper/resource" - "github.com/hashicorp/terraform-plugin-testing/statecheck" - "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" -) - -func TestCompareValueContains_CheckState_Map_ValuesSame_DifferError(t *testing.T) { - t.Parallel() - - r.Test(t, r.TestCase{ - ProviderFactories: map[string]func() (*schema.Provider, error){ - "test": func() (*schema.Provider, error) { //nolint:unparam // required signature - return testProvider(), nil - }, - }, - Steps: []r.TestStep{ - { - Config: `resource "test_resource" "one" { - string_attribute = "str" - } - - resource "test_resource" "two" { - map_attribute = { - "a": "str2", - "b": "str3", - } - } - `, - ConfigStateChecks: []statecheck.StateCheck{ - statecheck.CompareValueContains( - "test_resource.two", - tfjsonpath.New("map_attribute"), - "test_resource.one", - tfjsonpath.New("string_attribute"), - compare.ValuesSame(), - ), - }, - ExpectError: regexp.MustCompile("expected values to be the same, but they differ: str3 != str"), - }, - }, - }) -} - -func TestCompareValueContains_CheckState_Map_ValuesSame(t *testing.T) { - t.Parallel() - - r.Test(t, r.TestCase{ - ProviderFactories: map[string]func() (*schema.Provider, error){ - "test": func() (*schema.Provider, error) { //nolint:unparam // required signature - return testProvider(), nil - }, - }, - Steps: []r.TestStep{ - { - Config: `resource "test_resource" "one" { - string_attribute = "str" - } - - resource "test_resource" "two" { - map_attribute = { - "a": "str2", - "b": test_resource.one.string_attribute, - } - } - `, - ConfigStateChecks: []statecheck.StateCheck{ - statecheck.CompareValueContains( - "test_resource.two", - tfjsonpath.New("map_attribute"), - "test_resource.one", - tfjsonpath.New("string_attribute"), - compare.ValuesSame(), - ), - }, - }, - }, - }) -} - -func TestCompareValueContains_CheckState_Map_ValuesDiffer_SameError(t *testing.T) { - t.Parallel() - - r.Test(t, r.TestCase{ - ProviderFactories: map[string]func() (*schema.Provider, error){ - "test": func() (*schema.Provider, error) { //nolint:unparam // required signature - return testProvider(), nil - }, - }, - Steps: []r.TestStep{ - { - Config: `resource "test_resource" "one" { - string_attribute = "str" - } - - resource "test_resource" "two" { - map_attribute = { - "a": test_resource.one.string_attribute, - "b": test_resource.one.string_attribute, - } - } - `, - ConfigStateChecks: []statecheck.StateCheck{ - statecheck.CompareValueContains( - "test_resource.two", - tfjsonpath.New("map_attribute"), - "test_resource.one", - tfjsonpath.New("string_attribute"), - compare.ValuesDiffer(), - ), - }, - ExpectError: regexp.MustCompile("expected values to differ, but they are the same: str == str"), - }, - }, - }) -} - -func TestCompareValueContains_CheckState_Map_ValuesDiffer(t *testing.T) { - t.Parallel() - - r.Test(t, r.TestCase{ - ProviderFactories: map[string]func() (*schema.Provider, error){ - "test": func() (*schema.Provider, error) { //nolint:unparam // required signature - return testProvider(), nil - }, - }, - Steps: []r.TestStep{ - { - Config: `resource "test_resource" "one" { - string_attribute = "str" - } - - resource "test_resource" "two" { - map_attribute = { - "a": test_resource.one.string_attribute, - "b": "str2", - } - } - `, - ConfigStateChecks: []statecheck.StateCheck{ - statecheck.CompareValueContains( - "test_resource.two", - tfjsonpath.New("map_attribute"), - "test_resource.one", - tfjsonpath.New("string_attribute"), - compare.ValuesDiffer(), - ), - }, - }, - }, - }) -} - -func TestCompareValueContains_CheckState_Set_ValuesSame_DifferError(t *testing.T) { - t.Parallel() - - r.Test(t, r.TestCase{ - ProviderFactories: map[string]func() (*schema.Provider, error){ - "test": func() (*schema.Provider, error) { //nolint:unparam // required signature - return testProvider(), nil - }, - }, - Steps: []r.TestStep{ - { - Config: `resource "test_resource" "one" { - string_attribute = "str" - } - - resource "test_resource" "two" { - set_attribute = [ - "str2", - "str3" - ] - } - `, - ConfigStateChecks: []statecheck.StateCheck{ - statecheck.CompareValueContains( - "test_resource.two", - tfjsonpath.New("set_attribute"), - "test_resource.one", - tfjsonpath.New("string_attribute"), - compare.ValuesSame(), - ), - }, - ExpectError: regexp.MustCompile("expected values to be the same, but they differ: str3 != str"), - }, - }, - }) -} - -func TestCompareValueContains_CheckState_Set_ValuesSame(t *testing.T) { - t.Parallel() - - r.Test(t, r.TestCase{ - ProviderFactories: map[string]func() (*schema.Provider, error){ - "test": func() (*schema.Provider, error) { //nolint:unparam // required signature - return testProvider(), nil - }, - }, - Steps: []r.TestStep{ - { - Config: `resource "test_resource" "one" { - string_attribute = "str" - } - - resource "test_resource" "two" { - set_attribute = [ - "str2", - test_resource.one.string_attribute - ] - } - `, - ConfigStateChecks: []statecheck.StateCheck{ - statecheck.CompareValueContains( - "test_resource.two", - tfjsonpath.New("set_attribute"), - "test_resource.one", - tfjsonpath.New("string_attribute"), - compare.ValuesSame(), - ), - }, - }, - }, - }) -} - -func TestCompareValueContains_CheckState_Set_ValuesDiffer_SameError(t *testing.T) { - t.Parallel() - - r.Test(t, r.TestCase{ - ProviderFactories: map[string]func() (*schema.Provider, error){ - "test": func() (*schema.Provider, error) { //nolint:unparam // required signature - return testProvider(), nil - }, - }, - Steps: []r.TestStep{ - { - Config: `resource "test_resource" "one" { - string_attribute = "str" - } - - resource "test_resource" "two" { - set_attribute = [ - test_resource.one.string_attribute, - test_resource.one.string_attribute, - ] - } - `, - ConfigStateChecks: []statecheck.StateCheck{ - statecheck.CompareValueContains( - "test_resource.two", - tfjsonpath.New("set_attribute"), - "test_resource.one", - tfjsonpath.New("string_attribute"), - compare.ValuesDiffer(), - ), - }, - ExpectError: regexp.MustCompile("expected values to differ, but they are the same: str == str"), - }, - }, - }) -} - -func TestCompareValueContains_CheckState_Set_ValuesDiffer(t *testing.T) { - t.Parallel() - - r.Test(t, r.TestCase{ - ProviderFactories: map[string]func() (*schema.Provider, error){ - "test": func() (*schema.Provider, error) { //nolint:unparam // required signature - return testProvider(), nil - }, - }, - Steps: []r.TestStep{ - { - Config: `resource "test_resource" "one" { - string_attribute = "str" - } - - resource "test_resource" "two" { - set_attribute = [ - test_resource.one.string_attribute, - "str2" - ] - } - `, - ConfigStateChecks: []statecheck.StateCheck{ - statecheck.CompareValueContains( - "test_resource.two", - tfjsonpath.New("set_attribute"), - "test_resource.one", - tfjsonpath.New("string_attribute"), - compare.ValuesDiffer(), - ), - }, - }, - }, - }) -} diff --git a/statecheck/expect_known_value_test.go b/statecheck/expect_known_value_test.go index f02b8cd09..2eb56d4e4 100644 --- a/statecheck/expect_known_value_test.go +++ b/statecheck/expect_known_value_test.go @@ -1622,6 +1622,26 @@ func testProvider() *schema.Provider { }, }, }, + "set_nested_nested_block": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "set_nested_block": { + Type: schema.TypeSet, + Optional: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "set_nested_block_attribute": { + Type: schema.TypeString, + Optional: true, + }, + }, + }, + }, + }, + }, + }, "string_attribute": { Optional: true, Type: schema.TypeString, From e652edc42e8b4dd130747d1cd82b20d2b6f412e6 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Tue, 7 May 2024 11:35:13 +0100 Subject: [PATCH 08/61] Adding further test coverage for CompareValueCollection --- statecheck/compare_value_collection.go | 15 + statecheck/compare_value_collection_test.go | 1317 ++++++++++++++++--- 2 files changed, 1148 insertions(+), 184 deletions(-) diff --git a/statecheck/compare_value_collection.go b/statecheck/compare_value_collection.go index 4b16522d3..e0632f651 100644 --- a/statecheck/compare_value_collection.go +++ b/statecheck/compare_value_collection.go @@ -134,6 +134,21 @@ func (e *compareValueCollection) CheckState(ctx context.Context, req CheckStateR return } + // Verify resultOne is a collection. + switch t := resultOne.(type) { + case []any, map[string]any: + default: + var pathStr string + + for _, v := range e.collectionPath { + pathStr += fmt.Sprintf(".%s", v.String()) + } + + resp.Error = fmt.Errorf("%s%s is not a collection type: %T", e.resourceAddressOne, pathStr, t) + + return + } + var results []any results, err = walkCollectionPath(resultOne, e.collectionPath[1:], results) diff --git a/statecheck/compare_value_collection_test.go b/statecheck/compare_value_collection_test.go index a2d8bee2d..fffbc7a92 100644 --- a/statecheck/compare_value_collection_test.go +++ b/statecheck/compare_value_collection_test.go @@ -15,7 +15,7 @@ import ( "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" ) -func TestCompareValueCollection_CheckState_Map_ValuesSame_DifferError(t *testing.T) { +func TestCompareValueCollection_CheckState_Bool_Error_NotCollection(t *testing.T) { t.Parallel() r.Test(t, r.TestCase{ @@ -27,34 +27,31 @@ func TestCompareValueCollection_CheckState_Map_ValuesSame_DifferError(t *testing Steps: []r.TestStep{ { Config: `resource "test_resource" "one" { - string_attribute = "str" + bool_attribute = true } resource "test_resource" "two" { - map_attribute = { - "a": "str2", - "b": "str3", - } + bool_attribute = true } `, ConfigStateChecks: []statecheck.StateCheck{ statecheck.CompareValueCollection( "test_resource.two", []tfjsonpath.Path{ - tfjsonpath.New("map_attribute"), + tfjsonpath.New("bool_attribute"), }, "test_resource.one", - tfjsonpath.New("string_attribute"), + tfjsonpath.New("bool_attribute"), compare.ValuesSame(), ), }, - ExpectError: regexp.MustCompile("expected values to be the same, but they differ: str2 != str\nexpected values to be the same, but they differ: str3 != str"), + ExpectError: regexp.MustCompile("test_resource.two.bool_attribute is not a collection type: bool"), }, }, }) } -func TestCompareValueCollection_CheckState_Map_ValuesSame(t *testing.T) { +func TestCompareValueCollection_CheckState_Float_Error_NotCollection(t *testing.T) { t.Parallel() r.Test(t, r.TestCase{ @@ -66,33 +63,31 @@ func TestCompareValueCollection_CheckState_Map_ValuesSame(t *testing.T) { Steps: []r.TestStep{ { Config: `resource "test_resource" "one" { - string_attribute = "str" + float_attribute = 1.234 } resource "test_resource" "two" { - map_attribute = { - "a": "str2", - "b": test_resource.one.string_attribute, - } + float_attribute = 1.234 } `, ConfigStateChecks: []statecheck.StateCheck{ statecheck.CompareValueCollection( "test_resource.two", []tfjsonpath.Path{ - tfjsonpath.New("map_attribute"), + tfjsonpath.New("float_attribute"), }, "test_resource.one", - tfjsonpath.New("string_attribute"), + tfjsonpath.New("float_attribute"), compare.ValuesSame(), ), }, + ExpectError: regexp.MustCompile("test_resource.two.float_attribute is not a collection type: json.Number"), }, }, }) } -func TestCompareValueCollection_CheckState_Map_ValuesDiffer_SameError(t *testing.T) { +func TestCompareValueCollection_CheckState_Int_Error_NotCollection(t *testing.T) { t.Parallel() r.Test(t, r.TestCase{ @@ -104,34 +99,31 @@ func TestCompareValueCollection_CheckState_Map_ValuesDiffer_SameError(t *testing Steps: []r.TestStep{ { Config: `resource "test_resource" "one" { - string_attribute = "str" + int_attribute = 1234 } resource "test_resource" "two" { - map_attribute = { - "a": test_resource.one.string_attribute, - "b": test_resource.one.string_attribute, - } + int_attribute = 1234 } `, ConfigStateChecks: []statecheck.StateCheck{ statecheck.CompareValueCollection( "test_resource.two", []tfjsonpath.Path{ - tfjsonpath.New("map_attribute"), + tfjsonpath.New("int_attribute"), }, "test_resource.one", - tfjsonpath.New("string_attribute"), - compare.ValuesDiffer(), + tfjsonpath.New("int_attribute"), + compare.ValuesSame(), ), }, - ExpectError: regexp.MustCompile("expected values to differ, but they are the same: str == str"), + ExpectError: regexp.MustCompile("test_resource.two.int_attribute is not a collection type: json.Number"), }, }, }) } -func TestCompareValueCollection_CheckState_Map_ValuesDiffer(t *testing.T) { +func TestCompareValueCollection_CheckState_List_ValuesSame_ErrorDiffer(t *testing.T) { t.Parallel() r.Test(t, r.TestCase{ @@ -147,29 +139,30 @@ func TestCompareValueCollection_CheckState_Map_ValuesDiffer(t *testing.T) { } resource "test_resource" "two" { - map_attribute = { - "a": test_resource.one.string_attribute, - "b": "str2", - } + list_attribute = [ + "str2", + "str3", + ] } `, ConfigStateChecks: []statecheck.StateCheck{ statecheck.CompareValueCollection( "test_resource.two", []tfjsonpath.Path{ - tfjsonpath.New("map_attribute"), + tfjsonpath.New("list_attribute"), }, "test_resource.one", tfjsonpath.New("string_attribute"), - compare.ValuesDiffer(), + compare.ValuesSame(), ), }, + ExpectError: regexp.MustCompile("expected values to be the same, but they differ: str2 != str\nexpected values to be the same, but they differ: str3 != str"), }, }, }) } -func TestCompareValueCollection_CheckState_Set_ValuesSame_DifferError(t *testing.T) { +func TestCompareValueCollection_CheckState_List_ValuesSame(t *testing.T) { t.Parallel() r.Test(t, r.TestCase{ @@ -185,9 +178,9 @@ func TestCompareValueCollection_CheckState_Set_ValuesSame_DifferError(t *testing } resource "test_resource" "two" { - set_attribute = [ + list_attribute = [ "str2", - "str3" + "str", ] } `, @@ -195,20 +188,19 @@ func TestCompareValueCollection_CheckState_Set_ValuesSame_DifferError(t *testing statecheck.CompareValueCollection( "test_resource.two", []tfjsonpath.Path{ - tfjsonpath.New("set_attribute"), + tfjsonpath.New("list_attribute"), }, "test_resource.one", tfjsonpath.New("string_attribute"), compare.ValuesSame(), ), }, - ExpectError: regexp.MustCompile("expected values to be the same, but they differ: str3 != str"), }, }, }) } -func TestCompareValueCollection_CheckState_Set_ValuesSame(t *testing.T) { +func TestCompareValueCollection_CheckState_List_ValuesDiffer_ErrorSame(t *testing.T) { t.Parallel() r.Test(t, r.TestCase{ @@ -224,9 +216,9 @@ func TestCompareValueCollection_CheckState_Set_ValuesSame(t *testing.T) { } resource "test_resource" "two" { - set_attribute = [ - "str2", - test_resource.one.string_attribute + list_attribute = [ + "str", + "str", ] } `, @@ -234,19 +226,20 @@ func TestCompareValueCollection_CheckState_Set_ValuesSame(t *testing.T) { statecheck.CompareValueCollection( "test_resource.two", []tfjsonpath.Path{ - tfjsonpath.New("set_attribute"), + tfjsonpath.New("list_attribute"), }, "test_resource.one", tfjsonpath.New("string_attribute"), - compare.ValuesSame(), + compare.ValuesDiffer(), ), }, + ExpectError: regexp.MustCompile("expected values to differ, but they are the same: str == str"), }, }, }) } -func TestCompareValueCollection_CheckState_Set_ValuesDiffer_SameError(t *testing.T) { +func TestCompareValueCollection_CheckState_List_ValuesDiffer(t *testing.T) { t.Parallel() r.Test(t, r.TestCase{ @@ -262,9 +255,9 @@ func TestCompareValueCollection_CheckState_Set_ValuesDiffer_SameError(t *testing } resource "test_resource" "two" { - set_attribute = [ - test_resource.one.string_attribute, - test_resource.one.string_attribute, + list_attribute = [ + "str", + "str2", ] } `, @@ -272,20 +265,19 @@ func TestCompareValueCollection_CheckState_Set_ValuesDiffer_SameError(t *testing statecheck.CompareValueCollection( "test_resource.two", []tfjsonpath.Path{ - tfjsonpath.New("set_attribute"), + tfjsonpath.New("list_attribute"), }, "test_resource.one", tfjsonpath.New("string_attribute"), compare.ValuesDiffer(), ), }, - ExpectError: regexp.MustCompile("expected values to differ, but they are the same: str == str"), }, }, }) } -func TestCompareValueCollection_CheckState_Set_ValuesDiffer(t *testing.T) { +func TestCompareValueCollection_CheckState_ListNestedBlock_ValuesSame_ErrorDiffer(t *testing.T) { t.Parallel() r.Test(t, r.TestCase{ @@ -301,29 +293,33 @@ func TestCompareValueCollection_CheckState_Set_ValuesDiffer(t *testing.T) { } resource "test_resource" "two" { - set_attribute = [ - test_resource.one.string_attribute, - "str2" - ] + list_nested_block { + list_nested_block_attribute = "str2" + } + list_nested_block { + list_nested_block_attribute = "str3" + } } `, ConfigStateChecks: []statecheck.StateCheck{ statecheck.CompareValueCollection( "test_resource.two", []tfjsonpath.Path{ - tfjsonpath.New("set_attribute"), + tfjsonpath.New("list_nested_block"), + tfjsonpath.New("list_nested_block_attribute"), }, "test_resource.one", tfjsonpath.New("string_attribute"), - compare.ValuesDiffer(), + compare.ValuesSame(), ), }, + ExpectError: regexp.MustCompile("expected values to be the same, but they differ: str2 != str\nexpected values to be the same, but they differ: str3 != str"), }, }, }) } -func TestCompareValueCollection_CheckState_SetNestedBlock_String_ValuesSame_DifferError(t *testing.T) { +func TestCompareValueCollection_CheckState_ListNestedBlock_ValuesSame(t *testing.T) { t.Parallel() r.Test(t, r.TestCase{ @@ -339,11 +335,11 @@ func TestCompareValueCollection_CheckState_SetNestedBlock_String_ValuesSame_Diff } resource "test_resource" "two" { - set_nested_block { - set_nested_block_attribute = "str1" + list_nested_block { + list_nested_block_attribute = "str2" } - set_nested_block { - set_nested_block_attribute = "str2" + list_nested_block { + list_nested_block_attribute = "str" } } `, @@ -351,21 +347,20 @@ func TestCompareValueCollection_CheckState_SetNestedBlock_String_ValuesSame_Diff statecheck.CompareValueCollection( "test_resource.two", []tfjsonpath.Path{ - tfjsonpath.New("set_nested_block"), - tfjsonpath.New("set_nested_block_attribute"), + tfjsonpath.New("list_nested_block"), + tfjsonpath.New("list_nested_block_attribute"), }, "test_resource.one", tfjsonpath.New("string_attribute"), compare.ValuesSame(), ), }, - ExpectError: regexp.MustCompile("expected values to be the same, but they differ: str1 != str\nexpected values to be the same, but they differ: str2 != str"), }, }, }) } -func TestCompareValueCollection_CheckState_SetNestedBlock_String_ValuesSame(t *testing.T) { +func TestCompareValueCollection_CheckState_ListNestedBlock_ValuesDiffer_ErrorSame(t *testing.T) { t.Parallel() r.Test(t, r.TestCase{ @@ -381,11 +376,11 @@ func TestCompareValueCollection_CheckState_SetNestedBlock_String_ValuesSame(t *t } resource "test_resource" "two" { - set_nested_block { - set_nested_block_attribute = "str1" + list_nested_block { + list_nested_block_attribute = "str" } - set_nested_block { - set_nested_block_attribute = "str" + list_nested_block { + list_nested_block_attribute = "str" } } `, @@ -393,20 +388,21 @@ func TestCompareValueCollection_CheckState_SetNestedBlock_String_ValuesSame(t *t statecheck.CompareValueCollection( "test_resource.two", []tfjsonpath.Path{ - tfjsonpath.New("set_nested_block"), - tfjsonpath.New("set_nested_block_attribute"), + tfjsonpath.New("list_nested_block"), + tfjsonpath.New("list_nested_block_attribute"), }, "test_resource.one", tfjsonpath.New("string_attribute"), - compare.ValuesSame(), + compare.ValuesDiffer(), ), }, + ExpectError: regexp.MustCompile("expected values to differ, but they are the same: str == str"), }, }, }) } -func TestCompareValueCollection_CheckState_SetNestedBlock_SetNestedBlock_ValuesSame(t *testing.T) { +func TestCompareValueCollection_CheckState_ListNestedBlock_ValuesDiffer(t *testing.T) { t.Parallel() r.Test(t, r.TestCase{ @@ -418,19 +414,15 @@ func TestCompareValueCollection_CheckState_SetNestedBlock_SetNestedBlock_ValuesS Steps: []r.TestStep{ { Config: `resource "test_resource" "one" { - set_nested_block { - set_nested_block_attribute = "str" - } + string_attribute = "str" } resource "test_resource" "two" { - set_nested_nested_block { - set_nested_block { - set_nested_block_attribute = "str1" - } - set_nested_block { - set_nested_block_attribute = "str" - } + list_nested_block { + list_nested_block_attribute = "str2" + } + list_nested_block { + list_nested_block_attribute = "str3" } } `, @@ -438,12 +430,12 @@ func TestCompareValueCollection_CheckState_SetNestedBlock_SetNestedBlock_ValuesS statecheck.CompareValueCollection( "test_resource.two", []tfjsonpath.Path{ - tfjsonpath.New("set_nested_nested_block"), - tfjsonpath.New("set_nested_block"), + tfjsonpath.New("list_nested_block"), + tfjsonpath.New("list_nested_block_attribute"), }, "test_resource.one", - tfjsonpath.New("set_nested_block"), - compare.ValuesSame(), + tfjsonpath.New("string_attribute"), + compare.ValuesDiffer(), ), }, }, @@ -451,7 +443,7 @@ func TestCompareValueCollection_CheckState_SetNestedBlock_SetNestedBlock_ValuesS }) } -func TestCompareValueCollection_CheckState_SetNestedBlockBlock_SetNestedBlockBlock_ValuesSame(t *testing.T) { +func TestCompareValueCollection_CheckState_Map_ValuesSame_ErrorDiffer(t *testing.T) { t.Parallel() r.Test(t, r.TestCase{ @@ -463,32 +455,13 @@ func TestCompareValueCollection_CheckState_SetNestedBlockBlock_SetNestedBlockBlo Steps: []r.TestStep{ { Config: `resource "test_resource" "one" { - set_nested_nested_block { - set_nested_block { - set_nested_block_attribute = "str1" - } - set_nested_block { - set_nested_block_attribute = "str" - } - } + string_attribute = "str" } resource "test_resource" "two" { - set_nested_nested_block { - set_nested_block { - set_nested_block_attribute = "str2" - } - set_nested_block { - set_nested_block_attribute = "st3" - } - } - set_nested_nested_block { - set_nested_block { - set_nested_block_attribute = "str1" - } - set_nested_block { - set_nested_block_attribute = "str" - } + map_attribute = { + "a": "str2", + "b": "str3", } } `, @@ -496,19 +469,20 @@ func TestCompareValueCollection_CheckState_SetNestedBlockBlock_SetNestedBlockBlo statecheck.CompareValueCollection( "test_resource.two", []tfjsonpath.Path{ - tfjsonpath.New("set_nested_nested_block"), + tfjsonpath.New("map_attribute"), }, "test_resource.one", - tfjsonpath.New("set_nested_nested_block"), + tfjsonpath.New("string_attribute"), compare.ValuesSame(), ), }, + ExpectError: regexp.MustCompile("expected values to be the same, but they differ: str2 != str\nexpected values to be the same, but they differ: str3 != str"), }, }, }) } -func TestCompareValueCollection_CheckState_SetNested_ValuesDiffer(t *testing.T) { +func TestCompareValueCollection_CheckState_Map_ValuesSame(t *testing.T) { t.Parallel() r.Test(t, r.TestCase{ @@ -520,32 +494,13 @@ func TestCompareValueCollection_CheckState_SetNested_ValuesDiffer(t *testing.T) Steps: []r.TestStep{ { Config: `resource "test_resource" "one" { - set_nested_nested_block { - set_nested_block { - set_nested_block_attribute = "str_x" - } - set_nested_block { - set_nested_block_attribute = "str_y" - } - } + string_attribute = "str" } resource "test_resource" "two" { - set_nested_nested_block { - set_nested_block { - set_nested_block_attribute = "str_c" - } - set_nested_block { - set_nested_block_attribute = "str_d" - } - } - set_nested_nested_block { - set_nested_block { - set_nested_block_attribute = "str_a" - } - set_nested_block { - set_nested_block_attribute = "str_b" - } + map_attribute = { + "a": "str2", + "b": "str", } } `, @@ -553,31 +508,87 @@ func TestCompareValueCollection_CheckState_SetNested_ValuesDiffer(t *testing.T) statecheck.CompareValueCollection( "test_resource.two", []tfjsonpath.Path{ - tfjsonpath.New("set_nested_nested_block"), - tfjsonpath.New("set_nested_block"), - tfjsonpath.New("set_nested_block_attribute"), + tfjsonpath.New("map_attribute"), }, "test_resource.one", - tfjsonpath.New("set_nested_nested_block").AtSliceIndex(0).AtMapKey("set_nested_block").AtSliceIndex(0).AtMapKey("set_nested_block_attribute"), - compare.ValuesDiffer(), + tfjsonpath.New("string_attribute"), + compare.ValuesSame(), ), + }, + }, + }, + }) +} + +func TestCompareValueCollection_CheckState_Map_ValuesDiffer_ErrorSame(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + string_attribute = "str" + } + + resource "test_resource" "two" { + map_attribute = { + "a": "str", + "b": "str", + } + } + `, + ConfigStateChecks: []statecheck.StateCheck{ statecheck.CompareValueCollection( "test_resource.two", []tfjsonpath.Path{ - tfjsonpath.New("set_nested_nested_block"), - tfjsonpath.New("set_nested_block"), + tfjsonpath.New("map_attribute"), }, "test_resource.one", - tfjsonpath.New("set_nested_nested_block").AtSliceIndex(0).AtMapKey("set_nested_block").AtSliceIndex(0), + tfjsonpath.New("string_attribute"), compare.ValuesDiffer(), ), + }, + ExpectError: regexp.MustCompile("expected values to differ, but they are the same: str == str"), + }, + }, + }) +} + +func TestCompareValueCollection_CheckState_Map_ValuesDiffer(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + string_attribute = "str" + } + + resource "test_resource" "two" { + map_attribute = { + "a": "str", + "b": "str2", + } + } + `, + ConfigStateChecks: []statecheck.StateCheck{ statecheck.CompareValueCollection( "test_resource.two", []tfjsonpath.Path{ - tfjsonpath.New("set_nested_nested_block"), + tfjsonpath.New("map_attribute"), }, "test_resource.one", - tfjsonpath.New("set_nested_nested_block"), + tfjsonpath.New("string_attribute"), compare.ValuesDiffer(), ), }, @@ -586,7 +597,7 @@ func TestCompareValueCollection_CheckState_SetNested_ValuesDiffer(t *testing.T) }) } -func TestCompareValueCollection_CheckState_SetNested_ValuesSame(t *testing.T) { +func TestCompareValueCollection_CheckState_Set_ValuesSame_ErrorDiffer(t *testing.T) { t.Parallel() r.Test(t, r.TestCase{ @@ -598,57 +609,959 @@ func TestCompareValueCollection_CheckState_SetNested_ValuesSame(t *testing.T) { Steps: []r.TestStep{ { Config: `resource "test_resource" "one" { - set_nested_nested_block { - set_nested_block { - set_nested_block_attribute = "str_a" - } - set_nested_block { - set_nested_block_attribute = "str_b" - } - } + string_attribute = "str" } resource "test_resource" "two" { - set_nested_nested_block { - set_nested_block { - set_nested_block_attribute = "str_c" - } - set_nested_block { - set_nested_block_attribute = "str_d" - } - } - set_nested_nested_block { - set_nested_block { - set_nested_block_attribute = "str_a" - } - set_nested_block { - set_nested_block_attribute = "str_b" - } - } + set_attribute = [ + "str2", + "str3" + ] } `, ConfigStateChecks: []statecheck.StateCheck{ statecheck.CompareValueCollection( "test_resource.two", []tfjsonpath.Path{ - tfjsonpath.New("set_nested_nested_block"), - tfjsonpath.New("set_nested_block"), - tfjsonpath.New("set_nested_block_attribute"), + tfjsonpath.New("set_attribute"), }, "test_resource.one", - tfjsonpath.New("set_nested_nested_block").AtSliceIndex(0).AtMapKey("set_nested_block").AtSliceIndex(0).AtMapKey("set_nested_block_attribute"), + tfjsonpath.New("string_attribute"), compare.ValuesSame(), ), - statecheck.CompareValueCollection( - "test_resource.two", - []tfjsonpath.Path{ - tfjsonpath.New("set_nested_nested_block"), - tfjsonpath.New("set_nested_block"), - }, + }, + ExpectError: regexp.MustCompile("expected values to be the same, but they differ: str2 != str\nexpected values to be the same, but they differ: str3 != str"), + }, + }, + }) +} + +func TestCompareValueCollection_CheckState_Set_ValuesSame(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + string_attribute = "str" + } + + resource "test_resource" "two" { + set_attribute = [ + "str2", + "str" + ] + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValueCollection( + "test_resource.two", + []tfjsonpath.Path{ + tfjsonpath.New("set_attribute"), + }, + "test_resource.one", + tfjsonpath.New("string_attribute"), + compare.ValuesSame(), + ), + }, + }, + }, + }) +} + +func TestCompareValueCollection_CheckState_Set_ValuesDiffer_ErrorSame(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + string_attribute = "str" + } + + resource "test_resource" "two" { + set_attribute = [ + "str", + "str", + ] + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValueCollection( + "test_resource.two", + []tfjsonpath.Path{ + tfjsonpath.New("set_attribute"), + }, + "test_resource.one", + tfjsonpath.New("string_attribute"), + compare.ValuesDiffer(), + ), + }, + ExpectError: regexp.MustCompile("expected values to differ, but they are the same: str == str"), + }, + }, + }) +} + +func TestCompareValueCollection_CheckState_Set_ValuesDiffer(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + string_attribute = "str" + } + + resource "test_resource" "two" { + set_attribute = [ + "str", + "str2" + ] + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValueCollection( + "test_resource.two", + []tfjsonpath.Path{ + tfjsonpath.New("set_attribute"), + }, + "test_resource.one", + tfjsonpath.New("string_attribute"), + compare.ValuesDiffer(), + ), + }, + }, + }, + }) +} + +func TestCompareValueCollection_CheckState_SetNestedBlock_ValuesSame_ErrorDiffer(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + string_attribute = "str" + } + + resource "test_resource" "two" { + set_nested_block { + set_nested_block_attribute = "str2" + } + set_nested_block { + set_nested_block_attribute = "str3" + } + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValueCollection( + "test_resource.two", + []tfjsonpath.Path{ + tfjsonpath.New("set_nested_block"), + tfjsonpath.New("set_nested_block_attribute"), + }, + "test_resource.one", + tfjsonpath.New("string_attribute"), + compare.ValuesSame(), + ), + }, + ExpectError: regexp.MustCompile("expected values to be the same, but they differ: str2 != str\nexpected values to be the same, but they differ: str3 != str"), + }, + }, + }) +} + +func TestCompareValueCollection_CheckState_SetNestedBlock_ValuesSame(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + string_attribute = "str" + } + + resource "test_resource" "two" { + set_nested_block { + set_nested_block_attribute = "str2" + } + set_nested_block { + set_nested_block_attribute = "str" + } + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValueCollection( + "test_resource.two", + []tfjsonpath.Path{ + tfjsonpath.New("set_nested_block"), + tfjsonpath.New("set_nested_block_attribute"), + }, + "test_resource.one", + tfjsonpath.New("string_attribute"), + compare.ValuesSame(), + ), + }, + }, + }, + }) +} + +func TestCompareValueCollection_CheckState_SetNestedBlock_ValuesDiffer_ErrorSame(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + string_attribute = "str" + } + + resource "test_resource" "two" { + set_nested_block { + set_nested_block_attribute = "str" + } + set_nested_block { + set_nested_block_attribute = "str" + } + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValueCollection( + "test_resource.two", + []tfjsonpath.Path{ + tfjsonpath.New("set_nested_block"), + tfjsonpath.New("set_nested_block_attribute"), + }, + "test_resource.one", + tfjsonpath.New("string_attribute"), + compare.ValuesDiffer(), + ), + }, + ExpectError: regexp.MustCompile("expected values to differ, but they are the same: str == str"), + }, + }, + }) +} + +func TestCompareValueCollection_CheckState_SetNestedBlock_ValuesDiffer(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + string_attribute = "str" + } + + resource "test_resource" "two" { + set_nested_block { + set_nested_block_attribute = "str2" + } + set_nested_block { + set_nested_block_attribute = "str3" + } + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValueCollection( + "test_resource.two", + []tfjsonpath.Path{ + tfjsonpath.New("set_nested_block"), + tfjsonpath.New("set_nested_block_attribute"), + }, + "test_resource.one", + tfjsonpath.New("string_attribute"), + compare.ValuesDiffer(), + ), + }, + }, + }, + }) +} + +func TestCompareValueCollection_CheckState_SetNestedNestedBlock_ValuesDiffer_ErrorSameAttribute(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + set_nested_nested_block { + set_nested_block { + set_nested_block_attribute = "str" + } + } + } + + resource "test_resource" "two" { + set_nested_nested_block { + set_nested_block { + set_nested_block_attribute = "str" + } + } + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValueCollection( + "test_resource.two", + []tfjsonpath.Path{ + tfjsonpath.New("set_nested_nested_block"), + tfjsonpath.New("set_nested_block"), + tfjsonpath.New("set_nested_block_attribute"), + }, + "test_resource.one", + tfjsonpath.New("set_nested_nested_block").AtSliceIndex(0).AtMapKey("set_nested_block").AtSliceIndex(0).AtMapKey("set_nested_block_attribute"), + compare.ValuesDiffer(), + ), + }, + ExpectError: regexp.MustCompile("expected values to differ, but they are the same: str == str"), + }, + }, + }) +} + +func TestCompareValueCollection_CheckState_SetNestedNestedBlock_ValuesDiffer_ErrorSameNestedBlock(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + set_nested_nested_block { + set_nested_block { + set_nested_block_attribute = "str" + } + } + } + + resource "test_resource" "two" { + set_nested_nested_block { + set_nested_block { + set_nested_block_attribute = "str" + } + } + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValueCollection( + "test_resource.two", + []tfjsonpath.Path{ + tfjsonpath.New("set_nested_nested_block"), + tfjsonpath.New("set_nested_block"), + }, + "test_resource.one", + tfjsonpath.New("set_nested_nested_block").AtSliceIndex(0).AtMapKey("set_nested_block").AtSliceIndex(0), + compare.ValuesDiffer(), + ), + }, + ExpectError: regexp.MustCompile(`expected values to differ, but they are the same: map\[set_nested_block_attribute:str\] == map\[set_nested_block_attribute:str\]`), + }, + }, + }) +} + +func TestCompareValueCollection_CheckState_SetNestedNestedBlock_ValuesDiffer_ErrorSameNestedNestedBlock(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + set_nested_nested_block { + set_nested_block { + set_nested_block_attribute = "str" + } + } + } + + resource "test_resource" "two" { + set_nested_nested_block { + set_nested_block { + set_nested_block_attribute = "str" + } + } + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValueCollection( + "test_resource.two", + []tfjsonpath.Path{ + tfjsonpath.New("set_nested_nested_block"), + }, + "test_resource.one", + tfjsonpath.New("set_nested_nested_block"), + compare.ValuesDiffer(), + ), + }, + ExpectError: regexp.MustCompile(`expected values to differ, but they are the same: \[map\[set_nested_block:\[map\[set_nested_block_attribute:str\]\]\]\] == \[map\[set_nested_block:\[map\[set_nested_block_attribute:str\]\]\]\]`), + }, + }, + }) +} + +func TestCompareValueCollection_CheckState_SetNestedNestedBlock_ValuesDifferAttribute(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + set_nested_nested_block { + set_nested_block { + set_nested_block_attribute = "str1" + } + set_nested_block { + set_nested_block_attribute = "str2" + } + } + } + + resource "test_resource" "two" { + set_nested_nested_block { + set_nested_block { + set_nested_block_attribute = "str3" + } + set_nested_block { + set_nested_block_attribute = "str4" + } + } + set_nested_nested_block { + set_nested_block { + set_nested_block_attribute = "str5" + } + set_nested_block { + set_nested_block_attribute = "str6" + } + } + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValueCollection( + "test_resource.two", + []tfjsonpath.Path{ + tfjsonpath.New("set_nested_nested_block"), + tfjsonpath.New("set_nested_block"), + tfjsonpath.New("set_nested_block_attribute"), + }, + "test_resource.one", + tfjsonpath.New("set_nested_nested_block").AtSliceIndex(0).AtMapKey("set_nested_block").AtSliceIndex(0).AtMapKey("set_nested_block_attribute"), + compare.ValuesDiffer(), + ), + }, + }, + }, + }) +} + +func TestCompareValueCollection_CheckState_SetNestedNestedBlock_ValuesDifferNestedBlock(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + set_nested_nested_block { + set_nested_block { + set_nested_block_attribute = "str1" + } + set_nested_block { + set_nested_block_attribute = "str2" + } + } + } + + resource "test_resource" "two" { + set_nested_nested_block { + set_nested_block { + set_nested_block_attribute = "str3" + } + set_nested_block { + set_nested_block_attribute = "str4" + } + } + set_nested_nested_block { + set_nested_block { + set_nested_block_attribute = "str5" + } + set_nested_block { + set_nested_block_attribute = "str6" + } + } + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValueCollection( + "test_resource.two", + []tfjsonpath.Path{ + tfjsonpath.New("set_nested_nested_block"), + tfjsonpath.New("set_nested_block"), + }, + "test_resource.one", + tfjsonpath.New("set_nested_nested_block").AtSliceIndex(0).AtMapKey("set_nested_block").AtSliceIndex(0), + compare.ValuesDiffer(), + ), + }, + }, + }, + }) +} + +func TestCompareValueCollection_CheckState_SetNestedNestedBlock_ValuesDifferNestedNestedBlock(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + set_nested_nested_block { + set_nested_block { + set_nested_block_attribute = "str1" + } + set_nested_block { + set_nested_block_attribute = "str2" + } + } + } + + resource "test_resource" "two" { + set_nested_nested_block { + set_nested_block { + set_nested_block_attribute = "str3" + } + set_nested_block { + set_nested_block_attribute = "str4" + } + } + set_nested_nested_block { + set_nested_block { + set_nested_block_attribute = "str5" + } + set_nested_block { + set_nested_block_attribute = "str6" + } + } + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValueCollection( + "test_resource.two", + []tfjsonpath.Path{ + tfjsonpath.New("set_nested_nested_block"), + }, + "test_resource.one", + tfjsonpath.New("set_nested_nested_block"), + compare.ValuesDiffer(), + ), + }, + }, + }, + }) +} + +func TestCompareValueCollection_CheckState_SetNested_ValuesSame_ErrorAttribute(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + set_nested_nested_block { + set_nested_block { + set_nested_block_attribute = "str_a" + } + set_nested_block { + set_nested_block_attribute = "str_b" + } + } + } + + resource "test_resource" "two" { + set_nested_nested_block { + set_nested_block { + set_nested_block_attribute = "str_c" + } + set_nested_block { + set_nested_block_attribute = "str_d" + } + } + set_nested_nested_block { + set_nested_block { + set_nested_block_attribute = "str_e" + } + set_nested_block { + set_nested_block_attribute = "str_f" + } + } + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValueCollection( + "test_resource.two", + []tfjsonpath.Path{ + tfjsonpath.New("set_nested_nested_block"), + tfjsonpath.New("set_nested_block"), + tfjsonpath.New("set_nested_block_attribute"), + }, + "test_resource.one", + tfjsonpath.New("set_nested_nested_block").AtSliceIndex(0).AtMapKey("set_nested_block").AtSliceIndex(0).AtMapKey("set_nested_block_attribute"), + compare.ValuesSame(), + ), + }, + ExpectError: regexp.MustCompile("expected values to be the same, but they differ: str_c != str_a\nexpected values to be the same, but they differ: str_d != str_a\nexpected values to be the same, but they differ: str_e != str_a\nexpected values to be the same, but they differ: str_f != str_a"), + }, + }, + }) +} + +func TestCompareValueCollection_CheckState_SetNested_ValuesSame_ErrorNestedBlock(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + set_nested_nested_block { + set_nested_block { + set_nested_block_attribute = "str_a" + } + set_nested_block { + set_nested_block_attribute = "str_b" + } + } + } + + resource "test_resource" "two" { + set_nested_nested_block { + set_nested_block { + set_nested_block_attribute = "str_c" + } + set_nested_block { + set_nested_block_attribute = "str_d" + } + } + set_nested_nested_block { + set_nested_block { + set_nested_block_attribute = "str_e" + } + set_nested_block { + set_nested_block_attribute = "str_f" + } + } + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValueCollection( + "test_resource.two", + []tfjsonpath.Path{ + tfjsonpath.New("set_nested_nested_block"), + tfjsonpath.New("set_nested_block"), + }, + "test_resource.one", + tfjsonpath.New("set_nested_nested_block").AtSliceIndex(0).AtMapKey("set_nested_block").AtSliceIndex(0), + compare.ValuesSame(), + ), + }, + ExpectError: regexp.MustCompile(`expected values to be the same, but they differ: map\[set_nested_block_attribute:str_c\] != map\[set_nested_block_attribute:str_a\]\nexpected values to be the same, but they differ: map\[set_nested_block_attribute:str_d\] != map\[set_nested_block_attribute:str_a\]\nexpected values to be the same, but they differ: map\[set_nested_block_attribute:str_e\] != map\[set_nested_block_attribute:str_a\]\nexpected values to be the same, but they differ: map\[set_nested_block_attribute:str_f\] != map\[set_nested_block_attribute:str_a\]`), + }, + }, + }) +} + +func TestCompareValueCollection_CheckState_SetNested_ValuesSame_ErrorNestedNestedBlock(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + set_nested_nested_block { + set_nested_block { + set_nested_block_attribute = "str_a" + } + set_nested_block { + set_nested_block_attribute = "str_b" + } + } + } + + resource "test_resource" "two" { + set_nested_nested_block { + set_nested_block { + set_nested_block_attribute = "str_c" + } + set_nested_block { + set_nested_block_attribute = "str_d" + } + } + set_nested_nested_block { + set_nested_block { + set_nested_block_attribute = "str_e" + } + set_nested_block { + set_nested_block_attribute = "str_f" + } + } + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValueCollection( + "test_resource.two", + []tfjsonpath.Path{ + tfjsonpath.New("set_nested_nested_block"), + }, + "test_resource.one", + tfjsonpath.New("set_nested_nested_block"), + compare.ValuesSame(), + ), + }, + ExpectError: regexp.MustCompile(`expected values to be the same, but they differ: \[map\[set_nested_block:\[map\[set_nested_block_attribute:str_c\] map\[set_nested_block_attribute:str_d\]\]\]\] != \[map\[set_nested_block:\[map\[set_nested_block_attribute:str_a\] map\[set_nested_block_attribute:str_b\]\]\]\]\nexpected values to be the same, but they differ: \[map\[set_nested_block:\[map\[set_nested_block_attribute:str_e\] map\[set_nested_block_attribute:str_f\]\]\]\] != \[map\[set_nested_block:\[map\[set_nested_block_attribute:str_a\] map\[set_nested_block_attribute:str_b\]\]\]\]`), + }, + }, + }) +} + +func TestCompareValueCollection_CheckState_SetNested_ValuesSameAttribute(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + set_nested_nested_block { + set_nested_block { + set_nested_block_attribute = "str_a" + } + set_nested_block { + set_nested_block_attribute = "str_b" + } + } + } + + resource "test_resource" "two" { + set_nested_nested_block { + set_nested_block { + set_nested_block_attribute = "str_c" + } + set_nested_block { + set_nested_block_attribute = "str_d" + } + } + set_nested_nested_block { + set_nested_block { + set_nested_block_attribute = "str_a" + } + set_nested_block { + set_nested_block_attribute = "str_b" + } + } + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValueCollection( + "test_resource.two", + []tfjsonpath.Path{ + tfjsonpath.New("set_nested_nested_block"), + tfjsonpath.New("set_nested_block"), + tfjsonpath.New("set_nested_block_attribute"), + }, + "test_resource.one", + tfjsonpath.New("set_nested_nested_block").AtSliceIndex(0).AtMapKey("set_nested_block").AtSliceIndex(0).AtMapKey("set_nested_block_attribute"), + compare.ValuesSame(), + ), + }, + }, + }, + }) +} + +func TestCompareValueCollection_CheckState_SetNested_ValuesSameNestedBlock(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + set_nested_nested_block { + set_nested_block { + set_nested_block_attribute = "str_a" + } + set_nested_block { + set_nested_block_attribute = "str_b" + } + } + } + + resource "test_resource" "two" { + set_nested_nested_block { + set_nested_block { + set_nested_block_attribute = "str_c" + } + set_nested_block { + set_nested_block_attribute = "str_d" + } + } + set_nested_nested_block { + set_nested_block { + set_nested_block_attribute = "str_a" + } + set_nested_block { + set_nested_block_attribute = "str_b" + } + } + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValueCollection( + "test_resource.two", + []tfjsonpath.Path{ + tfjsonpath.New("set_nested_nested_block"), + tfjsonpath.New("set_nested_block"), + }, "test_resource.one", tfjsonpath.New("set_nested_nested_block").AtSliceIndex(0).AtMapKey("set_nested_block").AtSliceIndex(0), compare.ValuesSame(), ), + }, + }, + }, + }) +} + +func TestCompareValueCollection_CheckState_SetNested_ValuesSameNestedNestedBlock(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + set_nested_nested_block { + set_nested_block { + set_nested_block_attribute = "str_a" + } + set_nested_block { + set_nested_block_attribute = "str_b" + } + } + } + + resource "test_resource" "two" { + set_nested_nested_block { + set_nested_block { + set_nested_block_attribute = "str_c" + } + set_nested_block { + set_nested_block_attribute = "str_d" + } + } + set_nested_nested_block { + set_nested_block { + set_nested_block_attribute = "str_a" + } + set_nested_block { + set_nested_block_attribute = "str_b" + } + } + } + `, + ConfigStateChecks: []statecheck.StateCheck{ statecheck.CompareValueCollection( "test_resource.two", []tfjsonpath.Path{ @@ -663,3 +1576,39 @@ func TestCompareValueCollection_CheckState_SetNested_ValuesSame(t *testing.T) { }, }) } + +func TestCompareValueCollection_CheckState_String_Error_NotCollection(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + string_attribute = "str" + } + + resource "test_resource" "two" { + string_attribute = "str" + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValueCollection( + "test_resource.two", + []tfjsonpath.Path{ + tfjsonpath.New("string_attribute"), + }, + "test_resource.one", + tfjsonpath.New("string_attribute"), + compare.ValuesSame(), + ), + }, + ExpectError: regexp.MustCompile("test_resource.two.string_attribute is not a collection type: string"), + }, + }, + }) +} From a5c6e026d9499412edc89d71eb7323da89d05515 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Tue, 7 May 2024 13:49:35 +0100 Subject: [PATCH 09/61] Adding compare pkg doc --- compare/doc.go | 5 +++++ statecheck/compare_value_collection.go | 1 + 2 files changed, 6 insertions(+) create mode 100644 compare/doc.go diff --git a/compare/doc.go b/compare/doc.go new file mode 100644 index 000000000..feb4a4c00 --- /dev/null +++ b/compare/doc.go @@ -0,0 +1,5 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// Package compare contains the value comparer interface, and types implementing the value comparer interface. +package compare diff --git a/statecheck/compare_value_collection.go b/statecheck/compare_value_collection.go index e0632f651..4bbc08d1a 100644 --- a/statecheck/compare_value_collection.go +++ b/statecheck/compare_value_collection.go @@ -137,6 +137,7 @@ func (e *compareValueCollection) CheckState(ctx context.Context, req CheckStateR // Verify resultOne is a collection. switch t := resultOne.(type) { case []any, map[string]any: + // Collection found. default: var pathStr string From 235278c58bb8a70a6da3526ab21b74d38d82c2be Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Wed, 8 May 2024 14:25:58 +0100 Subject: [PATCH 10/61] Removing ValuesDifferAll, ValuesDifferAny, ValuesSameAny --- compare/values_differ_all.go | 33 ---- compare/values_differ_all_test.go | 66 ------- compare/values_differ_any.go | 40 ----- compare/values_differ_any_test.go | 69 -------- compare/values_same_any.go | 37 ---- compare/values_same_any_test.go | 64 ------- statecheck/compare_value_test.go | 279 ------------------------------ 7 files changed, 588 deletions(-) delete mode 100644 compare/values_differ_all.go delete mode 100644 compare/values_differ_all_test.go delete mode 100644 compare/values_differ_any.go delete mode 100644 compare/values_differ_any_test.go delete mode 100644 compare/values_same_any.go delete mode 100644 compare/values_same_any_test.go diff --git a/compare/values_differ_all.go b/compare/values_differ_all.go deleted file mode 100644 index f821b4742..000000000 --- a/compare/values_differ_all.go +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package compare - -import ( - "fmt" - "reflect" -) - -var _ ValueComparer = valuesDifferAll{} - -type valuesDifferAll struct{} - -// CompareValues determines whether each value in the supplied values -// is unique. -func (v valuesDifferAll) CompareValues(values ...any) error { - for i := 0; i < len(values); i++ { - for j := i + 1; j < len(values); j++ { - if reflect.DeepEqual(values[i], values[j]) { - return fmt.Errorf("expected values to differ, but value is duplicated: %v", values[i]) - } - } - } - - return nil -} - -// ValuesDifferAll returns a ValueComparer for asserting that each value in the -// values supplied to the CompareValues method is unique. -func ValuesDifferAll() valuesDifferAll { - return valuesDifferAll{} -} diff --git a/compare/values_differ_all_test.go b/compare/values_differ_all_test.go deleted file mode 100644 index aa38a903c..000000000 --- a/compare/values_differ_all_test.go +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package compare_test - -import ( - "fmt" - "testing" - - "github.com/google/go-cmp/cmp" - - "github.com/hashicorp/terraform-plugin-testing/compare" -) - -func TestValuesDifferAll_CompareValues(t *testing.T) { - t.Parallel() - - testCases := map[string]struct { - in []any - expectedError error - }{ - "nil": {}, - "single-value": { - in: []any{"str"}, - }, - "non-matching-values": { - in: []any{"str", "other_str", "another_str"}, - }, - "matching-values-string": { - in: []any{"str", "str"}, - expectedError: fmt.Errorf("expected values to differ, but value is duplicated: str"), - }, - "non-sequential-matching-values-string": { - in: []any{"str", "other_str", "str"}, - expectedError: fmt.Errorf("expected values to differ, but value is duplicated: str"), - }, - "matching-values-slice": { - in: []any{ - []any{"str"}, - []any{"str"}, - }, - expectedError: fmt.Errorf("expected values to differ, but value is duplicated: [str]"), - }, - "matching-values-map": { - in: []any{ - map[string]any{"a": "str"}, - map[string]any{"a": "str"}, - }, - expectedError: fmt.Errorf("expected values to differ, but value is duplicated: map[a:str]"), - }, - } - - for name, testCase := range testCases { - name, testCase := name, testCase - - t.Run(name, func(t *testing.T) { - t.Parallel() - - err := compare.ValuesDifferAll().CompareValues(testCase.in...) - - if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { - t.Errorf("unexpected difference: %s", diff) - } - }) - } -} diff --git a/compare/values_differ_any.go b/compare/values_differ_any.go deleted file mode 100644 index 086d549f4..000000000 --- a/compare/values_differ_any.go +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package compare - -import ( - "fmt" - "reflect" -) - -var _ ValueComparer = valuesDifferAny{} - -type valuesDifferAny struct{} - -// CompareValues determines whether any value in the supplied values -// is unique. -func (v valuesDifferAny) CompareValues(values ...any) error { - if len(values) < 2 { - return nil - } - - var val any - - for i := 0; i < len(values); i++ { - val = values[i] - for j := 1; j < len(values); j++ { - if !reflect.DeepEqual(values[i], values[j]) { - return nil - } - } - } - - return fmt.Errorf("expected values to differ, but value is duplicated: %v", val) -} - -// ValuesDifferAny returns a ValueComparer for asserting that any value in the -// values supplied to the CompareValues method is unique. -func ValuesDifferAny() valuesDifferAny { - return valuesDifferAny{} -} diff --git a/compare/values_differ_any_test.go b/compare/values_differ_any_test.go deleted file mode 100644 index 5e66baaf5..000000000 --- a/compare/values_differ_any_test.go +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package compare_test - -import ( - "fmt" - "testing" - - "github.com/google/go-cmp/cmp" - - "github.com/hashicorp/terraform-plugin-testing/compare" -) - -func TestValuesDifferAny_CompareValues(t *testing.T) { - t.Parallel() - - testCases := map[string]struct { - in []any - expectedError error - }{ - "nil": {}, - "single-value": { - in: []any{"str"}, - }, - "non-matching-sequential-values-string": { - in: []any{"str", "other_str", "str"}, - }, - "non-matching-sequential-values-slice": { - in: []any{ - []any{"str"}, - []any{"other_str"}, - []any{"str"}, - }, - }, - "matching-values-string": { - in: []any{"str", "str"}, - expectedError: fmt.Errorf("expected values to differ, but value is duplicated: str"), - }, - "matching-values-slice": { - in: []any{ - []any{"str"}, - []any{"str"}, - }, - expectedError: fmt.Errorf("expected values to differ, but value is duplicated: [str]"), - }, - "matching-values-map": { - in: []any{ - map[string]any{"a": "str"}, - map[string]any{"a": "str"}, - }, - expectedError: fmt.Errorf("expected values to differ, but value is duplicated: map[a:str]"), - }, - } - - for name, testCase := range testCases { - name, testCase := name, testCase - - t.Run(name, func(t *testing.T) { - t.Parallel() - - err := compare.ValuesDifferAny().CompareValues(testCase.in...) - - if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { - t.Errorf("unexpected difference: %s", diff) - } - }) - } -} diff --git a/compare/values_same_any.go b/compare/values_same_any.go deleted file mode 100644 index 3190c51d6..000000000 --- a/compare/values_same_any.go +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package compare - -import ( - "fmt" - "reflect" -) - -var _ ValueComparer = valuesSameAny{} - -type valuesSameAny struct{} - -// CompareValues determines whether any value in the supplied values -// matches any other value. -func (v valuesSameAny) CompareValues(values ...any) error { - if len(values) < 2 { - return nil - } - - for i := 0; i < len(values); i++ { - for j := i + 1; j < len(values); j++ { - if reflect.DeepEqual(values[i], values[j]) { - return nil - } - } - } - - return fmt.Errorf("expected at least two values to be the same, but all values differ") -} - -// ValuesSameAny returns a ValueComparer for asserting whether any value in the -// values supplied to the CompareValues method is the same as any other value. -func ValuesSameAny() valuesSameAny { - return valuesSameAny{} -} diff --git a/compare/values_same_any_test.go b/compare/values_same_any_test.go deleted file mode 100644 index 4d43a404f..000000000 --- a/compare/values_same_any_test.go +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package compare_test - -import ( - "fmt" - "testing" - - "github.com/google/go-cmp/cmp" - - "github.com/hashicorp/terraform-plugin-testing/compare" -) - -func TestValuesSameAny_CompareValues(t *testing.T) { - t.Parallel() - - testCases := map[string]struct { - in []any - expectedError error - }{ - "nil": {}, - "single-value": { - in: []any{"str"}, - }, - "non-sequential-matching-values": { - in: []any{"str", "other_str", "str"}, - }, - "non-matching-values-string": { - in: []any{"str", "other_str", "another_str"}, - expectedError: fmt.Errorf("expected at least two values to be the same, but all values differ"), - }, - "non-matching-values-slice": { - in: []any{ - []any{"str"}, - []any{"other_str"}, - []any{"another_str"}, - }, - expectedError: fmt.Errorf("expected at least two values to be the same, but all values differ"), - }, - "non-matching-values-map": { - in: []any{ - map[string]any{"a": "str"}, - map[string]any{"a": "other_str"}, - map[string]any{"a": "another_str"}, - }, - expectedError: fmt.Errorf("expected at least two values to be the same, but all values differ"), - }, - } - - for name, testCase := range testCases { - name, testCase := name, testCase - - t.Run(name, func(t *testing.T) { - t.Parallel() - - err := compare.ValuesSameAny().CompareValues(testCase.in...) - - if diff := cmp.Diff(err, testCase.expectedError, equateErrorMessage); diff != "" { - t.Errorf("unexpected difference: %s", diff) - } - }) - } -} diff --git a/statecheck/compare_value_test.go b/statecheck/compare_value_test.go index b6c16e13a..0035178d4 100644 --- a/statecheck/compare_value_test.go +++ b/statecheck/compare_value_test.go @@ -108,99 +108,6 @@ func TestCompareValue_CheckState_Bool_ValuesSame(t *testing.T) { }) } -func TestCompareValue_CheckState_Bool_ValuesSameAny_ValueDiffersError(t *testing.T) { - t.Parallel() - - boolValuesDiffer := statecheck.CompareValue(compare.ValuesSameAny()) - - r.Test(t, r.TestCase{ - ProviderFactories: map[string]func() (*schema.Provider, error){ - "test": func() (*schema.Provider, error) { //nolint:unparam // required signature - return testProvider(), nil - }, - }, - Steps: []r.TestStep{ - { - Config: `resource "test_resource" "one" { - bool_attribute = true - } - `, - ConfigStateChecks: []statecheck.StateCheck{ - boolValuesDiffer.AddStateValue( - "test_resource.one", - tfjsonpath.New("bool_attribute"), - ), - }, - }, - { - Config: `resource "test_resource" "one" { - bool_attribute = false - } - `, - ConfigStateChecks: []statecheck.StateCheck{ - boolValuesDiffer.AddStateValue( - "test_resource.one", - tfjsonpath.New("bool_attribute"), - ), - }, - ExpectError: regexp.MustCompile(`expected at least two values to be the same, but all values differ`), - }, - }, - }) -} - -func TestCompareValue_CheckState_Bool_ValuesSameAny(t *testing.T) { - t.Parallel() - - boolValuesDiffer := statecheck.CompareValue(compare.ValuesSameAny()) - - r.Test(t, r.TestCase{ - ProviderFactories: map[string]func() (*schema.Provider, error){ - "test": func() (*schema.Provider, error) { //nolint:unparam // required signature - return testProvider(), nil - }, - }, - Steps: []r.TestStep{ - { - Config: `resource "test_resource" "one" { - bool_attribute = true - } - `, - ConfigStateChecks: []statecheck.StateCheck{ - boolValuesDiffer.AddStateValue( - "test_resource.one", - tfjsonpath.New("bool_attribute"), - ), - }, - }, - { - Config: `resource "test_resource" "one" { - bool_attribute = true - } - `, - ConfigStateChecks: []statecheck.StateCheck{ - boolValuesDiffer.AddStateValue( - "test_resource.one", - tfjsonpath.New("bool_attribute"), - ), - }, - }, - { - Config: `resource "test_resource" "one" { - bool_attribute = false - } - `, - ConfigStateChecks: []statecheck.StateCheck{ - boolValuesDiffer.AddStateValue( - "test_resource.one", - tfjsonpath.New("bool_attribute"), - ), - }, - }, - }, - }) -} - func TestCompareValue_CheckState_Bool_ValuesDiffer_ValueSameError(t *testing.T) { t.Parallel() @@ -305,189 +212,3 @@ func TestCompareValue_CheckState_Bool_ValuesDiffer(t *testing.T) { }, }) } - -func TestCompareValue_CheckState_Bool_ValuesDifferAll_ValuesSameError(t *testing.T) { - t.Parallel() - - boolValuesDiffer := statecheck.CompareValue(compare.ValuesDifferAll()) - - r.Test(t, r.TestCase{ - ProviderFactories: map[string]func() (*schema.Provider, error){ - "test": func() (*schema.Provider, error) { //nolint:unparam // required signature - return testProvider(), nil - }, - }, - Steps: []r.TestStep{ - { - Config: `resource "test_resource" "one" { - bool_attribute = true - } - `, - ConfigStateChecks: []statecheck.StateCheck{ - boolValuesDiffer.AddStateValue( - "test_resource.one", - tfjsonpath.New("bool_attribute"), - ), - }, - }, - { - Config: `resource "test_resource" "one" { - bool_attribute = false - } - `, - ConfigStateChecks: []statecheck.StateCheck{ - boolValuesDiffer.AddStateValue( - "test_resource.one", - tfjsonpath.New("bool_attribute"), - ), - }, - }, - { - Config: `resource "test_resource" "one" { - bool_attribute = true - } - `, - ConfigStateChecks: []statecheck.StateCheck{ - boolValuesDiffer.AddStateValue( - "test_resource.one", - tfjsonpath.New("bool_attribute"), - ), - }, - ExpectError: regexp.MustCompile(`expected values to differ, but value is duplicated: true`), - }, - }, - }) -} - -func TestCompareValue_CheckState_Bool_ValuesDifferAll(t *testing.T) { - t.Parallel() - - boolValuesDiffer := statecheck.CompareValue(compare.ValuesDifferAll()) - - r.Test(t, r.TestCase{ - ProviderFactories: map[string]func() (*schema.Provider, error){ - "test": func() (*schema.Provider, error) { //nolint:unparam // required signature - return testProvider(), nil - }, - }, - Steps: []r.TestStep{ - { - Config: `resource "test_resource" "one" { - bool_attribute = true - } - `, - ConfigStateChecks: []statecheck.StateCheck{ - boolValuesDiffer.AddStateValue( - "test_resource.one", - tfjsonpath.New("bool_attribute"), - ), - }, - }, - { - Config: `resource "test_resource" "one" { - bool_attribute = false - } - `, - ConfigStateChecks: []statecheck.StateCheck{ - boolValuesDiffer.AddStateValue( - "test_resource.one", - tfjsonpath.New("bool_attribute"), - ), - }, - }, - }, - }) -} - -func TestCompareValue_CheckState_Bool_ValuesDifferAny_ValuesSameError(t *testing.T) { - t.Parallel() - - boolValuesDiffer := statecheck.CompareValue(compare.ValuesDifferAny()) - - r.Test(t, r.TestCase{ - ProviderFactories: map[string]func() (*schema.Provider, error){ - "test": func() (*schema.Provider, error) { //nolint:unparam // required signature - return testProvider(), nil - }, - }, - Steps: []r.TestStep{ - { - Config: `resource "test_resource" "one" { - bool_attribute = true - } - `, - ConfigStateChecks: []statecheck.StateCheck{ - boolValuesDiffer.AddStateValue( - "test_resource.one", - tfjsonpath.New("bool_attribute"), - ), - }, - }, - { - Config: `resource "test_resource" "one" { - bool_attribute = true - } - `, - ConfigStateChecks: []statecheck.StateCheck{ - boolValuesDiffer.AddStateValue( - "test_resource.one", - tfjsonpath.New("bool_attribute"), - ), - }, - ExpectError: regexp.MustCompile(`expected values to differ, but value is duplicated: true`), - }, - }, - }) -} - -func TestCompareValue_CheckState_Bool_ValuesDifferAny(t *testing.T) { - t.Parallel() - - boolValuesDiffer := statecheck.CompareValue(compare.ValuesDifferAny()) - - r.Test(t, r.TestCase{ - ProviderFactories: map[string]func() (*schema.Provider, error){ - "test": func() (*schema.Provider, error) { //nolint:unparam // required signature - return testProvider(), nil - }, - }, - Steps: []r.TestStep{ - { - Config: `resource "test_resource" "one" { - bool_attribute = true - } - `, - ConfigStateChecks: []statecheck.StateCheck{ - boolValuesDiffer.AddStateValue( - "test_resource.one", - tfjsonpath.New("bool_attribute"), - ), - }, - }, - { - Config: `resource "test_resource" "one" { - bool_attribute = false - } - `, - ConfigStateChecks: []statecheck.StateCheck{ - boolValuesDiffer.AddStateValue( - "test_resource.one", - tfjsonpath.New("bool_attribute"), - ), - }, - }, - { - Config: `resource "test_resource" "one" { - bool_attribute = false - } - `, - ConfigStateChecks: []statecheck.StateCheck{ - boolValuesDiffer.AddStateValue( - "test_resource.one", - tfjsonpath.New("bool_attribute"), - ), - }, - }, - }, - }) -} From dcfee1339c4559196f4a086c9b2cc94e2a8f9dba Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Thu, 9 May 2024 11:41:06 +0100 Subject: [PATCH 11/61] Adding website docs for value comparers and compare value state checks --- website/data/plugin-testing-nav-data.json | 9 + .../state-checks/resource.mdx | 220 +++++++++++++++++- .../value-comparers/index.mdx | 132 +++++++++++ 3 files changed, 357 insertions(+), 4 deletions(-) create mode 100644 website/docs/plugin/testing/acceptance-tests/value-comparers/index.mdx diff --git a/website/data/plugin-testing-nav-data.json b/website/data/plugin-testing-nav-data.json index bac221d3f..e27db47d8 100644 --- a/website/data/plugin-testing-nav-data.json +++ b/website/data/plugin-testing-nav-data.json @@ -132,6 +132,15 @@ } ] }, + { + "title": "Value Comparers", + "routes": [ + { + "title": "Overview", + "path": "acceptance-tests/value-comparers" + } + ] + }, { "title": "Sweepers", "path": "acceptance-tests/sweepers" diff --git a/website/docs/plugin/testing/acceptance-tests/state-checks/resource.mdx b/website/docs/plugin/testing/acceptance-tests/state-checks/resource.mdx index a3e94cbbe..d11db8f63 100644 --- a/website/docs/plugin/testing/acceptance-tests/state-checks/resource.mdx +++ b/website/docs/plugin/testing/acceptance-tests/state-checks/resource.mdx @@ -9,10 +9,222 @@ description: >- The `terraform-plugin-testing` module provides a package [`statecheck`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/statecheck) with built-in managed resource, and data source state checks for common use-cases: -| Check | Description | -|-----------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| [`ExpectKnownValue`](/terraform/plugin/testing/acceptance-tests/state-checks/resource#expectknownvalue-state-check) | Asserts the specified attribute at the given managed resource, or data source, has the specified type, and value. | -| [`ExpectSensitiveValue`](/terraform/plugin/testing/acceptance-tests/state-checks/resource#expectsensitivevalue-state-check) | Asserts the specified attribute at the given managed resource, or data source, has a sensitive value. | +| Check | Description | +|---------------------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| [`CompareValue`](/terraform/plugin/testing/acceptance-tests/state-checks/resource#comparevalue-state-check) | Compares sequential values of the specified attribute at the given managed resource, or data source, using the supplied [value comparer](/terraform/plugin/testing/acceptance-tests/value-comparers). | +| [`CompareValueCollection`](/terraform/plugin/testing/acceptance-tests/state-checks/resource#comparevaluecollection-state-check) | Compares each item in the specified collection (e.g., list, set) attribute, with the second specified attribute at the given managed resources, or data sources, using the supplied [value comparer](/terraform/plugin/testing/acceptance-tests/value-comparers). | +| [`CompareValuePairs`](/terraform/plugin/testing/acceptance-tests/state-checks/resource#comparevaluecollection-state-check) | Compares the specified attributes at the given managed resources, or data sources, using the supplied [value comparer](/terraform/plugin/testing/acceptance-tests/value-comparers). | +| [`ExpectKnownValue`](/terraform/plugin/testing/acceptance-tests/state-checks/resource#expectknownvalue-state-check) | Asserts the specified attribute at the given managed resource, or data source, has the specified type, and value. | +| [`ExpectSensitiveValue`](/terraform/plugin/testing/acceptance-tests/state-checks/resource#expectsensitivevalue-state-check) | Asserts the specified attribute at the given managed resource, or data source, has a sensitive value. | + +## `CompareValue` State Check + +The intended usage of [`statecheck.CompareValue(comparer)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/statecheck#CompareValue) state check is to retrieve a specific resource attribute value from state during sequential test steps, and to compare these values using the supplied value comparer. + +Refer to [Value Comparers](/terraform/plugin/testing/acceptance-tests/value-comparers) for details, and examples of the available [compare.ValueComparer](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/compare#ValueComparer) types that can be used with the `CompareValue` state check. + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/compare" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +func TestCompareValue_CheckState_ValuesSame(t *testing.T) { + t.Parallel() + + compareValuesSame := statecheck.CompareValue(compare.ValuesSame()) + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigStateChecks: []statecheck.StateCheck{ + compareValuesSame.AddStateValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + ), + }, + }, + { + // Example resource containing a computed attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigStateChecks: []statecheck.StateCheck{ + compareValuesSame.AddStateValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + ), + }, + }, + }, + }) +} +``` + +## `CompareValueCollection` State Check + +The [`statecheck.CompareValueCollection(resourceAddressOne, collectionPath, resourceAddressTwo, attributePath, comparer)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/statecheck#CompareValueCollection) state check retrieves a specific collection (e.g., list, set) resource attribute, and a second resource attribute from state, and compares each of the items in the collection with the second attribute using the supplied value comparer. + +Refer to [Value Comparers](/terraform/plugin/testing/acceptance-tests/value-comparers) for details, and examples of the available [compare.ValueComparer](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/compare#ValueComparer) types that can be used with the `CompareValueCollection` state check. + +The following example illustrates how a `CompareValue` state check can be used to determine whether an attribute value appears in a collection attribute. Note that this is for illustrative purposes only, `CompareValue` should only be used for checking computed values. + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/compare" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +func TestCompareValueCollection_CheckState_ValuesSame(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // The following is for illustrative purposes. State checking + // should only be used for computed attributes + Config: `resource "test_resource" "one" { + string_attribute = "str" + } + + resource "test_resource" "two" { + list_attribute = [ + "str2", + "str", + ] + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValueCollection( + "test_resource.two", + []tfjsonpath.Path{ + tfjsonpath.New("list_attribute"), + }, + "test_resource.one", + tfjsonpath.New("string_attribute"), + compare.ValuesSame(), + ), + }, + }, + }, + }) +} +``` + +The following example illustrates how a `CompareValue` state check can be used to determine whether an object attribute value appears in a collection (e.g., list) attribute containing objects. Note that this is for illustrative purposes only, `CompareValue` should only be used for checking computed values. + + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/compare" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +func TestCompareValueCollection_CheckState_ValuesSame(t *testing.T) { + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // The following is for illustrative purposes. State checking + // should only be used for computed attributes + Config: `resource "test_resource" "one" { + list_nested_attribute = [ + { + a = false + b = "two" + }, + { + a = true + b = "four" + } + ] + single_nested_attribute = { + a = true + b = "four" + } + }`, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValueCollection( + "test_resource.one", + []tfjsonpath.Path{ + tfjsonpath.New("list_nested_attribute"), + tfjsonpath.New("b"), + }, + "test_resource.one", + tfjsonpath.New("single_nested_attribute").AtMapKey("b"), + compare.ValuesSame(), + ), + }, + }, + }, + }) +} +``` + +## `CompareValuePairs` State Check + +The [`statecheck.CompareValuePairs(resourceAddressOne, attributePathOne, resourceAddressTwo, attributePathTwo, comparer)`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/statecheck#CompareValuePairs) state check provides a basis for retrieving a pair of attribute values, and comparing them using the supplied value comparer. + +Refer to [Value Comparers](/terraform/plugin/testing/acceptance-tests/value-comparers) for details, and examples of the available [compare.ValueComparer](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/compare#ValueComparer) types that can be used with the `CompareValuePairs` state check. + +```go +package statecheck_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-testing/compare" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +func TestCompareValuePairs_CheckState_ValuesSame(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed attribute named "computed_attribute" + Config: `resource "test_resource" "one" {} + + resource "test_resource" "two" {} + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValuePairs( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + "test_resource.two", + tfjsonpath.New("computed_attribute"), + compare.ValuesSame(), + ), + }, + }, + }, + }) +} +``` ## `ExpectKnownValue` State Check diff --git a/website/docs/plugin/testing/acceptance-tests/value-comparers/index.mdx b/website/docs/plugin/testing/acceptance-tests/value-comparers/index.mdx new file mode 100644 index 000000000..2714e302d --- /dev/null +++ b/website/docs/plugin/testing/acceptance-tests/value-comparers/index.mdx @@ -0,0 +1,132 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Value Comparers' +description: >- + How to use value comparers in the testing module. + Value comparers define a comparison for a resource attribute, or output value for use in State Checks. +--- + +# Value Comparers + +Value Comparers are for use in conjunction with [State Checks](/terraform/plugin/testing/acceptance-tests/state-checks), which leverage the [terraform-json](https://pkg.go.dev/github.com/hashicorp/terraform-json) representation of Terraform state. + +## Usage + +Example uses in the testing module include: + +- The [`CompareValue()`](/terraform/plugin/testing/acceptance-tests/state-checks/resource#comparevalue-state-check), [`CompareValueCollection()`](/terraform/plugin/testing/acceptance-tests/state-checks/resource#comparevaluecollection-state-check) and [`CompareValuePairs()`](/terraform/plugin/testing/acceptance-tests/state-checks/resource#comparevaluepairs-state-check) [built-in state checks](/terraform/plugin/testing/acceptance-tests/state-checks) use value comparers for comparing specific resource attribute, or output values. + +## Using a Value Comparer + +The value comparer types are implemented within the `terraform-plugin-testing` module in the [`compare` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/compare). Value comparers are instantiated by calling the relevant constructor function. + +```go +compare.ValuesDiffer() +``` + +The value comparer types implement the [`ValueComparer` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/compare#ValueComparer). The `CompareValues()` method accepts a variadic argument of type `any`, which allows for comparison of arbitrary data structures. + +## Values Differ Comparer Type + +The [ValuesDiffer](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/compare#ValuesDiffer) value comparer verifies that each value in the sequence of values supplied to the `CompareValues()` method differs from the preceding value. + +Example usage of [ValuesDiffer](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/compare#ValuesDiffer) in a [CompareValue](/terraform/plugin/testing/acceptance-tests/state-checks/resource#comparevalue-state-check) state check. + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/hashicorp/terraform-plugin-testing/compare" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +func TestCompareValue_CheckState_ValuesDiffer(t *testing.T) { + t.Parallel() + + compareValuesDiffer := statecheck.CompareValue(compare.ValuesDiffer()) + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigStateChecks: []statecheck.StateCheck{ + compareValuesDiffer.AddStateValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + ), + }, + }, + { + // Example resource containing a computed attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigStateChecks: []statecheck.StateCheck{ + compareValuesDiffer.AddStateValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + ), + }, + }, + }, + }) +} +``` + +## Values Same Comparer Type + +The [ValuesSame](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/compare#ValuesSame) value comparer verifies that each value in the sequence of values supplied to the `CompareValues()` method is the same as the preceding value. + +Example usage of [ValuesSame](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/compare#ValuesSame) in a [CompareValue](/terraform/plugin/testing/acceptance-tests/state-checks/resource#comparevalue-state-check) state check. + +```go +package example_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/hashicorp/terraform-plugin-testing/compare" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +func TestCompareValue_CheckState_ValuesSame(t *testing.T) { + t.Parallel() + + compareValuesSame := statecheck.CompareValue(compare.ValuesSame()) + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigStateChecks: []statecheck.StateCheck{ + compareValuesSame.AddStateValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + ), + }, + }, + { + // Example resource containing a computed attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigStateChecks: []statecheck.StateCheck{ + compareValuesSame.AddStateValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + ), + }, + }, + }, + }) +} +``` From a772710352c0a0f864cf7691f0bacf07390e8005 Mon Sep 17 00:00:00 2001 From: Benjamin Bennett Date: Thu, 9 May 2024 11:41:42 +0100 Subject: [PATCH 12/61] Amending docs for known value checks --- .../testing/acceptance-tests/known-value-checks/index.mdx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/website/docs/plugin/testing/acceptance-tests/known-value-checks/index.mdx b/website/docs/plugin/testing/acceptance-tests/known-value-checks/index.mdx index 62db10d5b..b18088a94 100644 --- a/website/docs/plugin/testing/acceptance-tests/known-value-checks/index.mdx +++ b/website/docs/plugin/testing/acceptance-tests/known-value-checks/index.mdx @@ -2,18 +2,19 @@ page_title: 'Plugin Development - Acceptance Testing: Known Values' description: >- How to use known values in the testing module. - Known values define an expected type, and value for a resource attribute, or output value in a Terraform plan for use in Plan Checks. + Known values define an expected type, and value for a resource attribute, or output value in a Terraform plan or state for use in Plan Checks or State Checks. --- # Known Value Checks -Known Value Checks are for use in conjunction with [Plan Checks](/terraform/plugin/testing/acceptance-tests/plan-checks), which leverage the [terraform-json](https://pkg.go.dev/github.com/hashicorp/terraform-json) representation of a Terraform plan. +Known Value Checks are for use in conjunction with [Plan Checks](/terraform/plugin/testing/acceptance-tests/plan-checks), and [State Checks](/terraform/plugin/testing/acceptance-tests/state-checks) which leverage the [terraform-json](https://pkg.go.dev/github.com/hashicorp/terraform-json) representation of a Terraform plan. ## Usage Example uses in the testing module include: -- The [`ExpectknownValue()`](/terraform/plugin/testing/acceptance-tests/plan-checks/resource#example-using-plancheck-expectknownvalue), [`ExpectKnownOutputValue()`](/terraform/plugin/testing/acceptance-tests/plan-checks/output#example-using-plancheck-expectknownoutputvalue) and [`ExpectKnownOutputValueAtPath()`](/terraform/plugin/testing/acceptance-tests/plan-checks/output#example-using-plancheck-expectknownoutputvalueatpath) [built-in plan checks](/terraform/plugin/testing/acceptance-tests/plan-checks) use known value checks for asserting whether a specific resource attribute, or output value has a particular type, and value. +- **Plan Checks**: The [`ExpectknownValue()`](/terraform/plugin/testing/acceptance-tests/plan-checks/resource#expectknownvalue-plan-check), [`ExpectKnownOutputValue()`](/terraform/plugin/testing/acceptance-tests/plan-checks/output#expectknownoutputvalue-plan-check) and [`ExpectKnownOutputValueAtPath()`](/terraform/plugin/testing/acceptance-tests/plan-checks/output#expectknownoutputvalueatpath-plan-check) [built-in plan checks](/terraform/plugin/testing/acceptance-tests/plan-checks) use known value checks for asserting whether a specific resource attribute, or output value has a particular type, and value. +- **State Checks**: The [`ExpectknownValue()`](/terraform/plugin/testing/acceptance-tests/state-checks/resource#expectknownvalue-state-check), [`ExpectKnownOutputValue()`](/terraform/plugin/testing/acceptance-tests/state-checks/output#expectknownoutputvalue-state-check) and [`ExpectKnownOutputValueAtPath()`](/terraform/plugin/testing/acceptance-tests/state-checks/output#expectknownoutputvalueatpath-state-check) [built-in state checks](/terraform/plugin/testing/acceptance-tests/state-checks) use known value checks for asserting whether a specific resource attribute, or output value has a particular type, and value. ## Using a Known Value Check From 6d797182d57ac08b49dc93860fcf3834bdae41f4 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 9 May 2024 09:03:02 +0100 Subject: [PATCH 13/61] build(deps): Bump hashicorp/setup-terraform from 3.1.0 to 3.1.1 (#335) Bumps [hashicorp/setup-terraform](https://github.com/hashicorp/setup-terraform) from 3.1.0 to 3.1.1. - [Release notes](https://github.com/hashicorp/setup-terraform/releases) - [Changelog](https://github.com/hashicorp/setup-terraform/blob/main/CHANGELOG.md) - [Commits](https://github.com/hashicorp/setup-terraform/compare/97f030cf6dc0b4f5e0da352c7bca9cca34579800...651471c36a6092792c552e8b1bef71e592b462d8) --- updated-dependencies: - dependency-name: hashicorp/setup-terraform dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/ci-go.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci-go.yml b/.github/workflows/ci-go.yml index d99321e36..d6108dd71 100644 --- a/.github/workflows/ci-go.yml +++ b/.github/workflows/ci-go.yml @@ -34,7 +34,7 @@ jobs: - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 with: go-version: ${{ matrix.go-version }} - - uses: hashicorp/setup-terraform@97f030cf6dc0b4f5e0da352c7bca9cca34579800 # v3.1.0 + - uses: hashicorp/setup-terraform@651471c36a6092792c552e8b1bef71e592b462d8 # v3.1.1 with: terraform_version: ${{ matrix.terraform }} terraform_wrapper: false From 0dfcfff0c9ac2963436c8c72fc2ca8b9c3c3e908 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 10 May 2024 09:01:41 -0400 Subject: [PATCH 14/61] build(deps): Bump hashicorp/setup-copywrite from 1.1.2 to 1.1.3 (#336) Bumps [hashicorp/setup-copywrite](https://github.com/hashicorp/setup-copywrite) from 1.1.2 to 1.1.3. - [Release notes](https://github.com/hashicorp/setup-copywrite/releases) - [Commits](https://github.com/hashicorp/setup-copywrite/compare/867a1a2a064a0626db322392806428f7dc59cb3e...32638da2d4e81d56a0764aa1547882fc4d209636) --- updated-dependencies: - dependency-name: hashicorp/setup-copywrite dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/compliance.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/compliance.yml b/.github/workflows/compliance.yml index 7317d6dac..fb4959f8f 100644 --- a/.github/workflows/compliance.yml +++ b/.github/workflows/compliance.yml @@ -12,6 +12,6 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - - uses: hashicorp/setup-copywrite@867a1a2a064a0626db322392806428f7dc59cb3e # v1.1.2 + - uses: hashicorp/setup-copywrite@32638da2d4e81d56a0764aa1547882fc4d209636 # v1.1.3 - run: copywrite headers --plan - run: copywrite license --plan From 773e336ed2d47a214f515341894283031d161a85 Mon Sep 17 00:00:00 2001 From: "hashicorp-tsccr[bot]" <129506189+hashicorp-tsccr[bot]@users.noreply.github.com> Date: Mon, 13 May 2024 09:13:47 -0400 Subject: [PATCH 15/61] Result of tsccr-helper -log-level=info gha update -latest . (#337) Co-authored-by: hashicorp-tsccr[bot] --- .github/workflows/ci-github-actions.yml | 2 +- .github/workflows/ci-go.yml | 6 +++--- .github/workflows/ci-goreleaser.yml | 2 +- .github/workflows/release.yml | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci-github-actions.yml b/.github/workflows/ci-github-actions.yml index a731bf6a2..afedc05af 100644 --- a/.github/workflows/ci-github-actions.yml +++ b/.github/workflows/ci-github-actions.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 + - uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 with: go-version-file: 'go.mod' - run: go install github.com/rhysd/actionlint/cmd/actionlint@latest diff --git a/.github/workflows/ci-go.yml b/.github/workflows/ci-go.yml index d6108dd71..b2b7bed43 100644 --- a/.github/workflows/ci-go.yml +++ b/.github/workflows/ci-go.yml @@ -17,11 +17,11 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 + - uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 with: go-version-file: 'go.mod' - run: go mod download - - uses: golangci/golangci-lint-action@82d40c283aeb1f2b6595839195e95c2d6a49081b # v5.0.0 + - uses: golangci/golangci-lint-action@38e1018663fa5173f3968ea0777460d3de38f256 # v5.3.0 test: name: test (Go ${{ matrix.go-version }} / TF ${{ matrix.terraform }}) runs-on: ubuntu-latest @@ -31,7 +31,7 @@ jobs: terraform: ${{ fromJSON(vars.TF_VERSIONS_PROTOCOL_V5) }} steps: - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 + - uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 with: go-version: ${{ matrix.go-version }} - uses: hashicorp/setup-terraform@651471c36a6092792c552e8b1bef71e592b462d8 # v3.1.1 diff --git a/.github/workflows/ci-goreleaser.yml b/.github/workflows/ci-goreleaser.yml index 666dc7c2e..320e554f2 100644 --- a/.github/workflows/ci-goreleaser.yml +++ b/.github/workflows/ci-goreleaser.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 - - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 + - uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 with: go-version-file: 'go.mod' - uses: goreleaser/goreleaser-action@7ec5c2b0c6cdda6e8bbb49444bc797dd33d74dd8 # v5.0.0 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 92dc9c73e..07b350bc1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -84,7 +84,7 @@ jobs: ref: ${{ inputs.versionNumber }} fetch-depth: 0 - - uses: actions/setup-go@0c52d547c9bc32b1aa3301fd7a9cb496313a4491 # v5.0.0 + - uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 with: go-version-file: 'go.mod' From 1e21a907be44b084a76ba6c8eaf206527ff80f1e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 15 May 2024 10:38:33 -0400 Subject: [PATCH 16/61] build(deps): Bump github.com/hashicorp/terraform-json (#338) Bumps [github.com/hashicorp/terraform-json](https://github.com/hashicorp/terraform-json) from 0.21.0 to 0.22.0. - [Release notes](https://github.com/hashicorp/terraform-json/releases) - [Commits](https://github.com/hashicorp/terraform-json/compare/v0.21.0...v0.22.0) --- updated-dependencies: - dependency-name: github.com/hashicorp/terraform-json dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 5f2d2703c..dc78c40cd 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/hashicorp/hcl/v2 v2.20.1 github.com/hashicorp/logutils v1.0.0 github.com/hashicorp/terraform-exec v0.20.0 - github.com/hashicorp/terraform-json v0.21.0 + github.com/hashicorp/terraform-json v0.22.0 github.com/hashicorp/terraform-plugin-go v0.22.2 github.com/hashicorp/terraform-plugin-log v0.9.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.33.0 diff --git a/go.sum b/go.sum index 15b110e75..e1f2e37fe 100644 --- a/go.sum +++ b/go.sum @@ -70,8 +70,8 @@ github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/terraform-exec v0.20.0 h1:DIZnPsqzPGuUnq6cH8jWcPunBfY+C+M8JyYF3vpnuEo= github.com/hashicorp/terraform-exec v0.20.0/go.mod h1:ckKGkJWbsNqFKV1itgMnE0hY9IYf1HoiekpuN0eWoDw= -github.com/hashicorp/terraform-json v0.21.0 h1:9NQxbLNqPbEMze+S6+YluEdXgJmhQykRyRNd+zTI05U= -github.com/hashicorp/terraform-json v0.21.0/go.mod h1:qdeBs11ovMzo5puhrRibdD6d2Dq6TyE/28JiU4tIQxk= +github.com/hashicorp/terraform-json v0.22.0 h1:cTZejr05cbovFmQ05MeJ43CPrn/+1hUBG7KDTcRwBc0= +github.com/hashicorp/terraform-json v0.22.0/go.mod h1:JbWSQCLFSXFFhg42T7l9iJwdGXBYV8fmmD6o/ML4p3A= github.com/hashicorp/terraform-plugin-go v0.22.2 h1:5o8uveu6eZUf5J7xGPV0eY0TPXg3qpmwX9sce03Bxnc= github.com/hashicorp/terraform-plugin-go v0.22.2/go.mod h1:drq8Snexp9HsbFZddvyLHN6LuWHHndSQg+gV+FPkcIM= github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= From 1333beab72694b476f9f9dcadc481ae1e6f9d82f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 May 2024 10:53:25 -0400 Subject: [PATCH 17/61] build(deps): Bump github.com/hashicorp/terraform-json (#339) Bumps [github.com/hashicorp/terraform-json](https://github.com/hashicorp/terraform-json) from 0.22.0 to 0.22.1. - [Release notes](https://github.com/hashicorp/terraform-json/releases) - [Commits](https://github.com/hashicorp/terraform-json/compare/v0.22.0...v0.22.1) --- updated-dependencies: - dependency-name: github.com/hashicorp/terraform-json dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index dc78c40cd..9bf007909 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/hashicorp/hcl/v2 v2.20.1 github.com/hashicorp/logutils v1.0.0 github.com/hashicorp/terraform-exec v0.20.0 - github.com/hashicorp/terraform-json v0.22.0 + github.com/hashicorp/terraform-json v0.22.1 github.com/hashicorp/terraform-plugin-go v0.22.2 github.com/hashicorp/terraform-plugin-log v0.9.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.33.0 diff --git a/go.sum b/go.sum index e1f2e37fe..8687485ce 100644 --- a/go.sum +++ b/go.sum @@ -70,8 +70,8 @@ github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/terraform-exec v0.20.0 h1:DIZnPsqzPGuUnq6cH8jWcPunBfY+C+M8JyYF3vpnuEo= github.com/hashicorp/terraform-exec v0.20.0/go.mod h1:ckKGkJWbsNqFKV1itgMnE0hY9IYf1HoiekpuN0eWoDw= -github.com/hashicorp/terraform-json v0.22.0 h1:cTZejr05cbovFmQ05MeJ43CPrn/+1hUBG7KDTcRwBc0= -github.com/hashicorp/terraform-json v0.22.0/go.mod h1:JbWSQCLFSXFFhg42T7l9iJwdGXBYV8fmmD6o/ML4p3A= +github.com/hashicorp/terraform-json v0.22.1 h1:xft84GZR0QzjPVWs4lRUwvTcPnegqlyS7orfb5Ltvec= +github.com/hashicorp/terraform-json v0.22.1/go.mod h1:JbWSQCLFSXFFhg42T7l9iJwdGXBYV8fmmD6o/ML4p3A= github.com/hashicorp/terraform-plugin-go v0.22.2 h1:5o8uveu6eZUf5J7xGPV0eY0TPXg3qpmwX9sce03Bxnc= github.com/hashicorp/terraform-plugin-go v0.22.2/go.mod h1:drq8Snexp9HsbFZddvyLHN6LuWHHndSQg+gV+FPkcIM= github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= From 56d08103c18297eb69dfd4840e774c0f67de9d0f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 17 May 2024 11:24:04 -0400 Subject: [PATCH 18/61] build(deps): Bump github.com/hashicorp/terraform-exec (#341) Bumps [github.com/hashicorp/terraform-exec](https://github.com/hashicorp/terraform-exec) from 0.20.0 to 0.21.0. - [Release notes](https://github.com/hashicorp/terraform-exec/releases) - [Changelog](https://github.com/hashicorp/terraform-exec/blob/main/CHANGELOG.md) - [Commits](https://github.com/hashicorp/terraform-exec/compare/v0.20.0...v0.21.0) --- updated-dependencies: - dependency-name: github.com/hashicorp/terraform-exec dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 9bf007909..c234fa239 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/hashicorp/hc-install v0.6.4 github.com/hashicorp/hcl/v2 v2.20.1 github.com/hashicorp/logutils v1.0.0 - github.com/hashicorp/terraform-exec v0.20.0 + github.com/hashicorp/terraform-exec v0.21.0 github.com/hashicorp/terraform-json v0.22.1 github.com/hashicorp/terraform-plugin-go v0.22.2 github.com/hashicorp/terraform-plugin-log v0.9.0 diff --git a/go.sum b/go.sum index 8687485ce..5ce8cbdeb 100644 --- a/go.sum +++ b/go.sum @@ -68,8 +68,8 @@ github.com/hashicorp/hcl/v2 v2.20.1 h1:M6hgdyz7HYt1UN9e61j+qKJBqR3orTWbI1HKBJEdx github.com/hashicorp/hcl/v2 v2.20.1/go.mod h1:TZDqQ4kNKCbh1iJp99FdPiUaVDDUPivbqxZulxDYqL4= github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/terraform-exec v0.20.0 h1:DIZnPsqzPGuUnq6cH8jWcPunBfY+C+M8JyYF3vpnuEo= -github.com/hashicorp/terraform-exec v0.20.0/go.mod h1:ckKGkJWbsNqFKV1itgMnE0hY9IYf1HoiekpuN0eWoDw= +github.com/hashicorp/terraform-exec v0.21.0 h1:uNkLAe95ey5Uux6KJdua6+cv8asgILFVWkd/RG0D2XQ= +github.com/hashicorp/terraform-exec v0.21.0/go.mod h1:1PPeMYou+KDUSSeRE9szMZ/oHf4fYUmB923Wzbq1ICg= github.com/hashicorp/terraform-json v0.22.1 h1:xft84GZR0QzjPVWs4lRUwvTcPnegqlyS7orfb5Ltvec= github.com/hashicorp/terraform-json v0.22.1/go.mod h1:JbWSQCLFSXFFhg42T7l9iJwdGXBYV8fmmD6o/ML4p3A= github.com/hashicorp/terraform-plugin-go v0.22.2 h1:5o8uveu6eZUf5J7xGPV0eY0TPXg3qpmwX9sce03Bxnc= From 91740d7b460de6859fab57b382f97b7b832427e9 Mon Sep 17 00:00:00 2001 From: Austin Valle Date: Fri, 17 May 2024 11:42:12 -0400 Subject: [PATCH 19/61] all: Add deferred action testing support (plan checks, version check, and CLI options) (#331) * sloppy first commit! * add version checks and tests * add changelogs * update terraform-plugin-go * spelling fix * update `terraform-json` * switch to pointer bool value --- .../ENHANCEMENTS-20240503-161709.yaml | 6 + .../unreleased/FEATURES-20240503-161531.yaml | 6 + .../unreleased/FEATURES-20240503-161802.yaml | 6 + go.mod | 4 +- go.sum | 8 +- helper/resource/additional_cli_options.go | 26 +++ helper/resource/testing.go | 4 + helper/resource/testing_new_config.go | 23 +- internal/plugintest/working_dir.go | 3 +- .../testsdk/providerserver/providerserver.go | 13 ++ .../providerserver/providerserver_protov5.go | 14 ++ internal/testing/testsdk/resource/resource.go | 1 + plancheck/deferred_reason.go | 21 ++ plancheck/expect_deferred_change.go | 49 +++++ plancheck/expect_deferred_change_test.go | 169 +++++++++++++++ plancheck/expect_no_deferred_changes.go | 52 +++++ plancheck/expect_no_deferred_changes_test.go | 198 ++++++++++++++++++ tfversion/skip_if_not_prerelease.go | 28 +++ tfversion/skip_if_not_prerelease_test.go | 94 +++++++++ 19 files changed, 717 insertions(+), 8 deletions(-) create mode 100644 .changes/unreleased/ENHANCEMENTS-20240503-161709.yaml create mode 100644 .changes/unreleased/FEATURES-20240503-161531.yaml create mode 100644 .changes/unreleased/FEATURES-20240503-161802.yaml create mode 100644 helper/resource/additional_cli_options.go create mode 100644 plancheck/deferred_reason.go create mode 100644 plancheck/expect_deferred_change.go create mode 100644 plancheck/expect_deferred_change_test.go create mode 100644 plancheck/expect_no_deferred_changes.go create mode 100644 plancheck/expect_no_deferred_changes_test.go create mode 100644 tfversion/skip_if_not_prerelease.go create mode 100644 tfversion/skip_if_not_prerelease_test.go diff --git a/.changes/unreleased/ENHANCEMENTS-20240503-161709.yaml b/.changes/unreleased/ENHANCEMENTS-20240503-161709.yaml new file mode 100644 index 000000000..dff8d4adf --- /dev/null +++ b/.changes/unreleased/ENHANCEMENTS-20240503-161709.yaml @@ -0,0 +1,6 @@ +kind: ENHANCEMENTS +body: 'helper/resource: Added `(TestCase).AdditionalCLIOptions` with `AllowDeferral` + option for plan and apply commands.' +time: 2024-05-03T16:17:09.64792-04:00 +custom: + Issue: "331" diff --git a/.changes/unreleased/FEATURES-20240503-161531.yaml b/.changes/unreleased/FEATURES-20240503-161531.yaml new file mode 100644 index 000000000..edd8c036f --- /dev/null +++ b/.changes/unreleased/FEATURES-20240503-161531.yaml @@ -0,0 +1,6 @@ +kind: FEATURES +body: 'plancheck: Added `ExpectDeferredChange` and `ExpectNoDeferredChanges` checks + for experimental deferred action support.' +time: 2024-05-03T16:15:31.03438-04:00 +custom: + Issue: "331" diff --git a/.changes/unreleased/FEATURES-20240503-161802.yaml b/.changes/unreleased/FEATURES-20240503-161802.yaml new file mode 100644 index 000000000..a06f5f78c --- /dev/null +++ b/.changes/unreleased/FEATURES-20240503-161802.yaml @@ -0,0 +1,6 @@ +kind: FEATURES +body: 'tfversion: Added `SkipIfNotPrerelease` version check for testing experimental + features of prerelease Terraform builds.' +time: 2024-05-03T16:18:02.132794-04:00 +custom: + Issue: "331" diff --git a/go.mod b/go.mod index c234fa239..a805261e6 100644 --- a/go.mod +++ b/go.mod @@ -15,7 +15,7 @@ require ( github.com/hashicorp/logutils v1.0.0 github.com/hashicorp/terraform-exec v0.21.0 github.com/hashicorp/terraform-json v0.22.1 - github.com/hashicorp/terraform-plugin-go v0.22.2 + github.com/hashicorp/terraform-plugin-go v0.23.0 github.com/hashicorp/terraform-plugin-log v0.9.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.33.0 github.com/mitchellh/go-testing-interface v1.14.1 @@ -56,5 +56,5 @@ require ( google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect google.golang.org/grpc v1.63.2 // indirect - google.golang.org/protobuf v1.33.0 // indirect + google.golang.org/protobuf v1.34.0 // indirect ) diff --git a/go.sum b/go.sum index 5ce8cbdeb..6d381066e 100644 --- a/go.sum +++ b/go.sum @@ -72,8 +72,8 @@ github.com/hashicorp/terraform-exec v0.21.0 h1:uNkLAe95ey5Uux6KJdua6+cv8asgILFVW github.com/hashicorp/terraform-exec v0.21.0/go.mod h1:1PPeMYou+KDUSSeRE9szMZ/oHf4fYUmB923Wzbq1ICg= github.com/hashicorp/terraform-json v0.22.1 h1:xft84GZR0QzjPVWs4lRUwvTcPnegqlyS7orfb5Ltvec= github.com/hashicorp/terraform-json v0.22.1/go.mod h1:JbWSQCLFSXFFhg42T7l9iJwdGXBYV8fmmD6o/ML4p3A= -github.com/hashicorp/terraform-plugin-go v0.22.2 h1:5o8uveu6eZUf5J7xGPV0eY0TPXg3qpmwX9sce03Bxnc= -github.com/hashicorp/terraform-plugin-go v0.22.2/go.mod h1:drq8Snexp9HsbFZddvyLHN6LuWHHndSQg+gV+FPkcIM= +github.com/hashicorp/terraform-plugin-go v0.23.0 h1:AALVuU1gD1kPb48aPQUjug9Ir/125t+AAurhqphJ2Co= +github.com/hashicorp/terraform-plugin-go v0.23.0/go.mod h1:1E3Cr9h2vMlahWMbsSEcNrOCxovCZhOOIXjFHbjc/lQ= github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow= github.com/hashicorp/terraform-plugin-sdk/v2 v2.33.0 h1:qHprzXy/As0rxedphECBEQAh3R4yp6pKksKHcqZx5G8= @@ -200,8 +200,8 @@ google.golang.org/grpc v1.63.2 h1:MUeiw1B2maTVZthpU5xvASfTh3LDbxHd6IJ6QQVU+xM= google.golang.org/grpc v1.63.2/go.mod h1:WAX/8DgncnokcFUldAxq7GeB5DXHDbMF+lLvDomNkRA= google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= -google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +google.golang.org/protobuf v1.34.0 h1:Qo/qEd2RZPCf2nKuorzksSknv0d3ERwp1vFG38gSmH4= +google.golang.org/protobuf v1.34.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/helper/resource/additional_cli_options.go b/helper/resource/additional_cli_options.go new file mode 100644 index 000000000..62578edef --- /dev/null +++ b/helper/resource/additional_cli_options.go @@ -0,0 +1,26 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package resource + +// AdditionalCLIOptions allows an intentionally limited set of options to be passed +// to the Terraform CLI when executing test steps. +type AdditionalCLIOptions struct { + // Apply represents options to be passed to the `terraform apply` command. + Apply ApplyOptions + + // Plan represents options to be passed to the `terraform plan` command. + Plan PlanOptions +} + +// ApplyOptions represents options to be passed to the `terraform apply` command. +type ApplyOptions struct { + // AllowDeferral will pass the experimental `-allow-deferral` flag to the apply command. + AllowDeferral bool +} + +// PlanOptions represents options to be passed to the `terraform plan` command. +type PlanOptions struct { + // AllowDeferral will pass the experimental `-allow-deferral` flag to the plan command. + AllowDeferral bool +} diff --git a/helper/resource/testing.go b/helper/resource/testing.go index f98abda39..9e1961a46 100644 --- a/helper/resource/testing.go +++ b/helper/resource/testing.go @@ -441,6 +441,10 @@ type TestCase struct { // set to "1", to persist any working directory files. Otherwise, this directory is // automatically cleaned up at the end of the TestCase. WorkingDir string + + // AdditionalCLIOptions allows an intentionally limited set of options to be passed + // to the Terraform CLI when executing test steps. + AdditionalCLIOptions *AdditionalCLIOptions } // ExternalProvider holds information about third-party providers that should diff --git a/helper/resource/testing_new_config.go b/helper/resource/testing_new_config.go index 9747eaf6e..1456f7fba 100644 --- a/helper/resource/testing_new_config.go +++ b/helper/resource/testing_new_config.go @@ -107,6 +107,11 @@ func testStepNewConfig(ctx context.Context, t testing.T, c TestCase, wd *plugint if step.Destroy { opts = append(opts, tfexec.Destroy(true)) } + + if c.AdditionalCLIOptions != nil && c.AdditionalCLIOptions.Plan.AllowDeferral { + opts = append(opts, tfexec.AllowDeferral(true)) + } + return wd.CreatePlan(ctx, opts...) }, wd, providers) if err != nil { @@ -168,7 +173,13 @@ func testStepNewConfig(ctx context.Context, t testing.T, c TestCase, wd *plugint // Apply the diff, creating real resources err = runProviderCommand(ctx, t, func() error { - return wd.Apply(ctx) + var opts []tfexec.ApplyOption + + if c.AdditionalCLIOptions != nil && c.AdditionalCLIOptions.Apply.AllowDeferral { + opts = append(opts, tfexec.AllowDeferral(true)) + } + + return wd.Apply(ctx, opts...) }, wd, providers) if err != nil { if step.Destroy { @@ -238,6 +249,11 @@ func testStepNewConfig(ctx context.Context, t testing.T, c TestCase, wd *plugint if step.Destroy { opts = append(opts, tfexec.Destroy(true)) } + + if c.AdditionalCLIOptions != nil && c.AdditionalCLIOptions.Plan.AllowDeferral { + opts = append(opts, tfexec.AllowDeferral(true)) + } + return wd.CreatePlan(ctx, opts...) }, wd, providers) if err != nil { @@ -302,6 +318,11 @@ func testStepNewConfig(ctx context.Context, t testing.T, c TestCase, wd *plugint opts = append(opts, tfexec.Refresh(false)) } } + + if c.AdditionalCLIOptions != nil && c.AdditionalCLIOptions.Plan.AllowDeferral { + opts = append(opts, tfexec.AllowDeferral(true)) + } + return wd.CreatePlan(ctx, opts...) }, wd, providers) if err != nil { diff --git a/internal/plugintest/working_dir.go b/internal/plugintest/working_dir.go index 2c8888d72..0cff33408 100644 --- a/internal/plugintest/working_dir.go +++ b/internal/plugintest/working_dir.go @@ -257,8 +257,9 @@ func (wd *WorkingDir) CreatePlan(ctx context.Context, opts ...tfexec.PlanOption) // successfully and the saved plan has not been cleared in the meantime then // this will apply the saved plan. Otherwise, it will implicitly create a new // plan and apply it. -func (wd *WorkingDir) Apply(ctx context.Context) error { +func (wd *WorkingDir) Apply(ctx context.Context, opts ...tfexec.ApplyOption) error { args := []tfexec.ApplyOption{tfexec.Reattach(wd.reattachInfo), tfexec.Refresh(false)} + args = append(args, opts...) if wd.HasSavedPlan() { args = append(args, tfexec.DirOrPlan(PlanFileName)) } diff --git a/internal/testing/testsdk/providerserver/providerserver.go b/internal/testing/testsdk/providerserver/providerserver.go index 1e0dd017d..4cc3f84ad 100644 --- a/internal/testing/testsdk/providerserver/providerserver.go +++ b/internal/testing/testsdk/providerserver/providerserver.go @@ -58,6 +58,18 @@ type ProviderServer struct { Provider provider.Provider } +func (s ProviderServer) CallFunction(ctx context.Context, req *tfprotov6.CallFunctionRequest) (*tfprotov6.CallFunctionResponse, error) { + return &tfprotov6.CallFunctionResponse{}, nil +} + +func (s ProviderServer) GetFunctions(ctx context.Context, req *tfprotov6.GetFunctionsRequest) (*tfprotov6.GetFunctionsResponse, error) { + return &tfprotov6.GetFunctionsResponse{}, nil +} + +func (s ProviderServer) MoveResourceState(ctx context.Context, req *tfprotov6.MoveResourceStateRequest) (*tfprotov6.MoveResourceStateResponse, error) { + return &tfprotov6.MoveResourceStateResponse{}, nil +} + func (s ProviderServer) GetMetadata(ctx context.Context, request *tfprotov6.GetMetadataRequest) (*tfprotov6.GetMetadataResponse, error) { resp := &tfprotov6.GetMetadataResponse{ ServerCapabilities: &tfprotov6.ServerCapabilities{ @@ -448,6 +460,7 @@ func (s ProviderServer) PlanResourceChange(ctx context.Context, req *tfprotov6.P resp.Diagnostics = planResp.Diagnostics resp.RequiresReplace = planResp.RequiresReplace + resp.Deferred = planResp.Deferred if len(resp.Diagnostics) > 0 { return resp, nil diff --git a/internal/testing/testsdk/providerserver/providerserver_protov5.go b/internal/testing/testsdk/providerserver/providerserver_protov5.go index 1c945f68f..4e6452b67 100644 --- a/internal/testing/testsdk/providerserver/providerserver_protov5.go +++ b/internal/testing/testsdk/providerserver/providerserver_protov5.go @@ -36,6 +36,20 @@ type Protov5ProviderServer struct { Provider provider.Protov5Provider } +// CallFunction implements tfprotov5.ProviderServer. +func (s Protov5ProviderServer) CallFunction(ctx context.Context, req *tfprotov5.CallFunctionRequest) (*tfprotov5.CallFunctionResponse, error) { + return &tfprotov5.CallFunctionResponse{}, nil +} + +// GetFunctions implements tfprotov5.ProviderServer. +func (s Protov5ProviderServer) GetFunctions(ctx context.Context, req *tfprotov5.GetFunctionsRequest) (*tfprotov5.GetFunctionsResponse, error) { + return &tfprotov5.GetFunctionsResponse{}, nil +} + +func (s Protov5ProviderServer) MoveResourceState(ctx context.Context, req *tfprotov5.MoveResourceStateRequest) (*tfprotov5.MoveResourceStateResponse, error) { + return &tfprotov5.MoveResourceStateResponse{}, nil +} + func (s Protov5ProviderServer) GetMetadata(ctx context.Context, request *tfprotov5.GetMetadataRequest) (*tfprotov5.GetMetadataResponse, error) { return &tfprotov5.GetMetadataResponse{}, nil } diff --git a/internal/testing/testsdk/resource/resource.go b/internal/testing/testsdk/resource/resource.go index f053b135f..5fea34468 100644 --- a/internal/testing/testsdk/resource/resource.go +++ b/internal/testing/testsdk/resource/resource.go @@ -55,6 +55,7 @@ type PlanChangeRequest struct { } type PlanChangeResponse struct { + Deferred *tfprotov6.Deferred Diagnostics []*tfprotov6.Diagnostic PlannedState tftypes.Value RequiresReplace []*tftypes.AttributePath diff --git a/plancheck/deferred_reason.go b/plancheck/deferred_reason.go new file mode 100644 index 000000000..4787a8c3e --- /dev/null +++ b/plancheck/deferred_reason.go @@ -0,0 +1,21 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package plancheck + +// DeferredReason is a string stored in the plan file which indicates why Terraform +// is deferring a change for a resource. +type DeferredReason string + +const ( + // DeferredReasonResourceConfigUnknown is used to indicate that the resource configuration + // is partially unknown and the real values need to be known before the change can be planned. + DeferredReasonResourceConfigUnknown DeferredReason = "resource_config_unknown" + + // DeferredReasonProviderConfigUnknown is used to indicate that the provider configuration + // is partially unknown and the real values need to be known before the change can be planned. + DeferredReasonProviderConfigUnknown DeferredReason = "provider_config_unknown" + + // DeferredReasonAbsentPrereq is used to indicate that a hard dependency has not been satisfied. + DeferredReasonAbsentPrereq DeferredReason = "absent_prereq" +) diff --git a/plancheck/expect_deferred_change.go b/plancheck/expect_deferred_change.go new file mode 100644 index 000000000..14310ca31 --- /dev/null +++ b/plancheck/expect_deferred_change.go @@ -0,0 +1,49 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package plancheck + +import ( + "context" + "fmt" +) + +var _ PlanCheck = expectDeferredChange{} + +type expectDeferredChange struct { + resourceAddress string + reason DeferredReason +} + +// CheckPlan implements the plan check logic. +func (e expectDeferredChange) CheckPlan(ctx context.Context, req CheckPlanRequest, resp *CheckPlanResponse) { + foundResource := false + + for _, dc := range req.Plan.DeferredChanges { + if dc.ResourceChange == nil || e.resourceAddress != dc.ResourceChange.Address { + continue + } + + if e.reason != DeferredReason(dc.Reason) { + resp.Error = fmt.Errorf("'%s' - expected %q, got deferred reason: %q", dc.ResourceChange.Address, e.reason, dc.Reason) + return + } + + foundResource = true + break + } + + if !foundResource { + resp.Error = fmt.Errorf("%s - No deferred changes found for resource", e.resourceAddress) + return + } +} + +// ExpectDeferredChange returns a plan check that asserts that a given resource will have a +// deferred change in the plan with the given reason. +func ExpectDeferredChange(resourceAddress string, reason DeferredReason) PlanCheck { + return expectDeferredChange{ + resourceAddress: resourceAddress, + reason: reason, + } +} diff --git a/plancheck/expect_deferred_change_test.go b/plancheck/expect_deferred_change_test.go new file mode 100644 index 000000000..05baa0671 --- /dev/null +++ b/plancheck/expect_deferred_change_test.go @@ -0,0 +1,169 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package plancheck_test + +import ( + "context" + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-go/tftypes" + r "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/internal/testing/testprovider" + "github.com/hashicorp/terraform-plugin-testing/internal/testing/testsdk/providerserver" + "github.com/hashicorp/terraform-plugin-testing/internal/testing/testsdk/resource" + "github.com/hashicorp/terraform-plugin-testing/plancheck" + "github.com/hashicorp/terraform-plugin-testing/tfversion" +) + +func Test_ExpectDeferredChange_Reason_Match(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_9_0), + tfversion.SkipIfNotPrerelease(), + }, + AdditionalCLIOptions: &r.AdditionalCLIOptions{ + Plan: r.PlanOptions{AllowDeferral: true}, + Apply: r.ApplyOptions{AllowDeferral: true}, + }, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "test": providerserver.NewProviderServer(testprovider.Provider{ + Resources: map[string]testprovider.Resource{ + "test_resource": { + PlanChangeFunc: func(ctx context.Context, req resource.PlanChangeRequest, resp *resource.PlanChangeResponse) { + resp.Deferred = &tfprotov6.Deferred{ + Reason: tfprotov6.DeferredReasonResourceConfigUnknown, + } + }, + SchemaResponse: &resource.SchemaResponse{ + Schema: &tfprotov6.Schema{ + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "id", + Type: tftypes.String, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + }), + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "test" {}`, + ConfigPlanChecks: r.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectDeferredChange("test_resource.test", plancheck.DeferredReasonResourceConfigUnknown), + }, + }, + }, + }, + }) +} + +func Test_ExpectDeferredChange_Reason_NoMatch(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_9_0), + tfversion.SkipIfNotPrerelease(), + }, + AdditionalCLIOptions: &r.AdditionalCLIOptions{ + Plan: r.PlanOptions{AllowDeferral: true}, + Apply: r.ApplyOptions{AllowDeferral: true}, + }, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "test": providerserver.NewProviderServer(testprovider.Provider{ + Resources: map[string]testprovider.Resource{ + "test_resource": { + PlanChangeFunc: func(ctx context.Context, req resource.PlanChangeRequest, resp *resource.PlanChangeResponse) { + resp.Deferred = &tfprotov6.Deferred{ + Reason: tfprotov6.DeferredReasonAbsentPrereq, + } + }, + SchemaResponse: &resource.SchemaResponse{ + Schema: &tfprotov6.Schema{ + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "id", + Type: tftypes.String, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + }), + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "test" {}`, + ConfigPlanChecks: r.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectDeferredChange("test_resource.test", plancheck.DeferredReasonProviderConfigUnknown), + }, + }, + ExpectError: regexp.MustCompile(`expected "provider_config_unknown", got deferred reason: "absent_prereq"`), + }, + }, + }) +} + +func Test_ExpectDeferredChange_NoDeferredChanges(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_9_0), + tfversion.SkipIfNotPrerelease(), + }, + AdditionalCLIOptions: &r.AdditionalCLIOptions{ + Plan: r.PlanOptions{AllowDeferral: true}, + Apply: r.ApplyOptions{AllowDeferral: true}, + }, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "test": providerserver.NewProviderServer(testprovider.Provider{ + Resources: map[string]testprovider.Resource{ + "test_resource": { + SchemaResponse: &resource.SchemaResponse{ + Schema: &tfprotov6.Schema{ + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "id", + Type: tftypes.String, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + }), + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "test" {}`, + ConfigPlanChecks: r.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectDeferredChange("test_resource.test", plancheck.DeferredReasonProviderConfigUnknown), + }, + }, + ExpectError: regexp.MustCompile(`No deferred changes found for resource`), + }, + }, + }) +} diff --git a/plancheck/expect_no_deferred_changes.go b/plancheck/expect_no_deferred_changes.go new file mode 100644 index 000000000..726ea6802 --- /dev/null +++ b/plancheck/expect_no_deferred_changes.go @@ -0,0 +1,52 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package plancheck + +import ( + "context" + "errors" + "fmt" +) + +var _ PlanCheck = expectNoDeferredChanges{} + +type expectNoDeferredChanges struct{} + +// CheckPlan implements the plan check logic. +func (e expectNoDeferredChanges) CheckPlan(ctx context.Context, req CheckPlanRequest, resp *CheckPlanResponse) { + if len(req.Plan.DeferredChanges) == 0 { + return + } + + var result []error + for _, deferred := range req.Plan.DeferredChanges { + resourceAddress := "unknown" + if deferred.ResourceChange != nil { + resourceAddress = deferred.ResourceChange.Address + } + + result = append(result, fmt.Errorf("expected no deferred changes, but resource %q is deferred with reason: %q", resourceAddress, deferred.Reason)) + } + + resp.Error = errors.Join(result...) + if resp.Error != nil { + return + } + + if req.Plan.Complete == nil { + resp.Error = errors.New("expected plan to be marked as complete, but complete field was not set in plan (nil). This indicates that the plan was created with a version of Terraform older than 1.8, which does not support the complete field.") + return + } + + if !*req.Plan.Complete { + resp.Error = errors.New("expected plan to be marked as complete, but complete was \"false\", indicating that at least one more plan/apply round is needed to converge.") + return + } +} + +// ExpectNoDeferredChanges returns a plan check that asserts that there are no deferred changes +// for any resources in the plan. +func ExpectNoDeferredChanges() PlanCheck { + return expectNoDeferredChanges{} +} diff --git a/plancheck/expect_no_deferred_changes_test.go b/plancheck/expect_no_deferred_changes_test.go new file mode 100644 index 000000000..b3b5bfc23 --- /dev/null +++ b/plancheck/expect_no_deferred_changes_test.go @@ -0,0 +1,198 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package plancheck_test + +import ( + "context" + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-go/tftypes" + r "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/internal/testing/testprovider" + "github.com/hashicorp/terraform-plugin-testing/internal/testing/testsdk/providerserver" + "github.com/hashicorp/terraform-plugin-testing/internal/testing/testsdk/resource" + "github.com/hashicorp/terraform-plugin-testing/plancheck" + "github.com/hashicorp/terraform-plugin-testing/tfversion" +) + +func Test_ExpectNoDeferredChange(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_9_0), + tfversion.SkipIfNotPrerelease(), + }, + AdditionalCLIOptions: &r.AdditionalCLIOptions{ + Plan: r.PlanOptions{AllowDeferral: true}, + Apply: r.ApplyOptions{AllowDeferral: true}, + }, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "test": providerserver.NewProviderServer(testprovider.Provider{ + Resources: map[string]testprovider.Resource{ + "test_resource": { + SchemaResponse: &resource.SchemaResponse{ + Schema: &tfprotov6.Schema{ + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "id", + Type: tftypes.String, + Required: true, + }, + }, + }, + }, + }, + }, + }, + }), + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "test" { + id = "hello" + }`, + ConfigPlanChecks: r.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectNoDeferredChanges(), + }, + }, + }, + }, + }) +} + +func Test_ExpectNoDeferredChange_OneDeferral(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_9_0), + tfversion.SkipIfNotPrerelease(), + }, + AdditionalCLIOptions: &r.AdditionalCLIOptions{ + Plan: r.PlanOptions{AllowDeferral: true}, + Apply: r.ApplyOptions{AllowDeferral: true}, + }, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "test": providerserver.NewProviderServer(testprovider.Provider{ + Resources: map[string]testprovider.Resource{ + "test_resource": { + PlanChangeFunc: func(ctx context.Context, req resource.PlanChangeRequest, resp *resource.PlanChangeResponse) { + resp.Deferred = &tfprotov6.Deferred{ + Reason: tfprotov6.DeferredReasonResourceConfigUnknown, + } + }, + SchemaResponse: &resource.SchemaResponse{ + Schema: &tfprotov6.Schema{ + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "id", + Type: tftypes.String, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + }), + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "test" {}`, + ConfigPlanChecks: r.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectNoDeferredChanges(), + }, + }, + ExpectError: regexp.MustCompile(`expected no deferred changes, but resource "test_resource.test" is deferred with reason: "resource_config_unknown"`), + }, + }, + }) +} + +func Test_ExpectNoDeferredChange_MultipleDeferrals(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_9_0), + tfversion.SkipIfNotPrerelease(), + }, + AdditionalCLIOptions: &r.AdditionalCLIOptions{ + Plan: r.PlanOptions{AllowDeferral: true}, + Apply: r.ApplyOptions{AllowDeferral: true}, + }, + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "test": providerserver.NewProviderServer(testprovider.Provider{ + Resources: map[string]testprovider.Resource{ + "test_resource_one": { + PlanChangeFunc: func(ctx context.Context, req resource.PlanChangeRequest, resp *resource.PlanChangeResponse) { + resp.Deferred = &tfprotov6.Deferred{ + Reason: tfprotov6.DeferredReasonResourceConfigUnknown, + } + }, + SchemaResponse: &resource.SchemaResponse{ + Schema: &tfprotov6.Schema{ + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "id", + Type: tftypes.String, + Computed: true, + }, + }, + }, + }, + }, + }, + "test_resource_two": { + PlanChangeFunc: func(ctx context.Context, req resource.PlanChangeRequest, resp *resource.PlanChangeResponse) { + resp.Deferred = &tfprotov6.Deferred{ + Reason: tfprotov6.DeferredReasonAbsentPrereq, + } + }, + SchemaResponse: &resource.SchemaResponse{ + Schema: &tfprotov6.Schema{ + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "id", + Type: tftypes.String, + Computed: true, + }, + }, + }, + }, + }, + }, + }, + }), + }, + Steps: []r.TestStep{ + { + Config: ` + resource "test_resource_one" "test" {} + + resource "test_resource_two" "test" {} + `, + ConfigPlanChecks: r.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectNoDeferredChanges(), + }, + }, + ExpectError: regexp.MustCompile( + `expected no deferred changes, but resource "test_resource_one.test" is deferred with reason: "resource_config_unknown"\n` + + `expected no deferred changes, but resource "test_resource_two.test" is deferred with reason: "absent_prereq"`, + ), + }, + }, + }) +} diff --git a/tfversion/skip_if_not_prerelease.go b/tfversion/skip_if_not_prerelease.go new file mode 100644 index 000000000..c49400b29 --- /dev/null +++ b/tfversion/skip_if_not_prerelease.go @@ -0,0 +1,28 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package tfversion + +import ( + "context" + "fmt" +) + +// SkipIfNotPrerelease will skip (pass) the test if the Terraform CLI +// version does not include prerelease information. This will include builds +// of Terraform that are from source. (e.g. 1.8.0-dev) +func SkipIfNotPrerelease() TerraformVersionCheck { + return skipIfNotPrereleaseCheck{} +} + +// skipIfNotPrereleaseCheck implements the TerraformVersionCheck interface +type skipIfNotPrereleaseCheck struct{} + +// CheckTerraformVersion satisfies the TerraformVersionCheck interface. +func (s skipIfNotPrereleaseCheck) CheckTerraformVersion(ctx context.Context, req CheckTerraformVersionRequest, resp *CheckTerraformVersionResponse) { + if req.TerraformVersion.Prerelease() != "" { + return + } + + resp.Skip = fmt.Sprintf("Terraform CLI version %s is not a prerelease build: skipping test.", req.TerraformVersion) +} diff --git a/tfversion/skip_if_not_prerelease_test.go b/tfversion/skip_if_not_prerelease_test.go new file mode 100644 index 000000000..545017ed0 --- /dev/null +++ b/tfversion/skip_if_not_prerelease_test.go @@ -0,0 +1,94 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package tfversion_test + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + + r "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/internal/testing/testprovider" + "github.com/hashicorp/terraform-plugin-testing/internal/testing/testsdk/providerserver" + "github.com/hashicorp/terraform-plugin-testing/tfversion" + + testinginterface "github.com/mitchellh/go-testing-interface" +) + +func Test_SkipIfNotPrerelease_SkipTest_Stable(t *testing.T) { //nolint:paralleltest + t.Setenv("TF_ACC_TERRAFORM_PATH", "") + t.Setenv("TF_ACC_TERRAFORM_VERSION", "1.8.0") + + r.UnitTest(t, r.TestCase{ + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "test": func() (tfprotov6.ProviderServer, error) { //nolint:unparam // required signature + return nil, nil + }, + }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipIfNotPrerelease(), + }, + Steps: []r.TestStep{ + { + Config: `//non-empty config`, + }, + }, + }) +} + +func Test_SkipIfNotPrerelease_RunTest_Alpha(t *testing.T) { //nolint:paralleltest + t.Setenv("TF_ACC_TERRAFORM_PATH", "") + t.Setenv("TF_ACC_TERRAFORM_VERSION", "1.9.0-alpha20240501") + + r.UnitTest(&testinginterface.RuntimeT{}, r.TestCase{ + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "test": providerserver.NewProviderServer(testprovider.Provider{}), + }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipIfNotPrerelease(), + }, + Steps: []r.TestStep{ + { + Config: `//non-empty config`, + }, + }, + }) +} + +func Test_SkipIfNotPrerelease_RunTest_Beta1(t *testing.T) { //nolint:paralleltest + t.Setenv("TF_ACC_TERRAFORM_PATH", "") + t.Setenv("TF_ACC_TERRAFORM_VERSION", "1.8.0-beta1") + + r.UnitTest(&testinginterface.RuntimeT{}, r.TestCase{ + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "test": providerserver.NewProviderServer(testprovider.Provider{}), + }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipIfNotPrerelease(), + }, + Steps: []r.TestStep{ + { + Config: `//non-empty config`, + }, + }, + }) +} +func Test_SkipIfNotPrerelease_RunTest_RC(t *testing.T) { //nolint:paralleltest + t.Setenv("TF_ACC_TERRAFORM_PATH", "") + t.Setenv("TF_ACC_TERRAFORM_VERSION", "1.8.0-rc2") + + r.UnitTest(&testinginterface.RuntimeT{}, r.TestCase{ + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "test": providerserver.NewProviderServer(testprovider.Provider{}), + }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipIfNotPrerelease(), + }, + Steps: []r.TestStep{ + { + Config: `//non-empty config`, + }, + }, + }) +} From bf8eb7b2e1ee7521bbe279dcb83b78093c1693b0 Mon Sep 17 00:00:00 2001 From: hc-github-team-tf-provider-devex Date: Fri, 17 May 2024 15:49:06 +0000 Subject: [PATCH 20/61] Update changelog --- .changes/1.8.0.md | 19 +++++++++++++++++++ .../unreleased/BUG FIXES-20240325-125136.yaml | 5 ----- .../unreleased/BUG FIXES-20240328-180800.yaml | 5 ----- .../ENHANCEMENTS-20240315-185051.yaml | 5 ----- .../ENHANCEMENTS-20240325-120539.yaml | 6 ------ .../ENHANCEMENTS-20240327-171628.yaml | 7 ------- .../ENHANCEMENTS-20240503-161709.yaml | 6 ------ .../unreleased/FEATURES-20240503-161531.yaml | 6 ------ .../unreleased/FEATURES-20240503-161802.yaml | 6 ------ CHANGELOG.md | 19 +++++++++++++++++++ 10 files changed, 38 insertions(+), 46 deletions(-) create mode 100644 .changes/1.8.0.md delete mode 100644 .changes/unreleased/BUG FIXES-20240325-125136.yaml delete mode 100644 .changes/unreleased/BUG FIXES-20240328-180800.yaml delete mode 100644 .changes/unreleased/ENHANCEMENTS-20240315-185051.yaml delete mode 100644 .changes/unreleased/ENHANCEMENTS-20240325-120539.yaml delete mode 100644 .changes/unreleased/ENHANCEMENTS-20240327-171628.yaml delete mode 100644 .changes/unreleased/ENHANCEMENTS-20240503-161709.yaml delete mode 100644 .changes/unreleased/FEATURES-20240503-161531.yaml delete mode 100644 .changes/unreleased/FEATURES-20240503-161802.yaml diff --git a/.changes/1.8.0.md b/.changes/1.8.0.md new file mode 100644 index 000000000..10e813247 --- /dev/null +++ b/.changes/1.8.0.md @@ -0,0 +1,19 @@ +## 1.8.0 (May 17, 2024) + +FEATURES: + +* plancheck: Added `ExpectDeferredChange` and `ExpectNoDeferredChanges` checks for experimental deferred action support. ([#331](https://github.com/hashicorp/terraform-plugin-testing/issues/331)) +* tfversion: Added `SkipIfNotPrerelease` version check for testing experimental features of prerelease Terraform builds. ([#331](https://github.com/hashicorp/terraform-plugin-testing/issues/331)) + +ENHANCEMENTS: + +* helper/acctest: Improve scope of IPv4/IPv6 random address generation in RandIpAddress() ([#305](https://github.com/hashicorp/terraform-plugin-testing/issues/305)) +* knownvalue: Add `TupleExact`, `TuplePartial` and `TupleSizeExact` checks for dynamic value testing. ([#312](https://github.com/hashicorp/terraform-plugin-testing/issues/312)) +* tfversion: Ensured Terraform CLI prerelease versions are considered semantically equal to patch versions in built-in checks to match the Terraform CLI versioning policy ([#303](https://github.com/hashicorp/terraform-plugin-testing/issues/303)) +* helper/resource: Added `(TestCase).AdditionalCLIOptions` with `AllowDeferral` option for plan and apply commands. ([#331](https://github.com/hashicorp/terraform-plugin-testing/issues/331)) + +BUG FIXES: + +* helper/resource: Fix panic in output state shimming when a tuple is present. ([#310](https://github.com/hashicorp/terraform-plugin-testing/issues/310)) +* tfversion: Fixed `RequireBelow` ignoring equal versioning to fail a test ([#303](https://github.com/hashicorp/terraform-plugin-testing/issues/303)) + diff --git a/.changes/unreleased/BUG FIXES-20240325-125136.yaml b/.changes/unreleased/BUG FIXES-20240325-125136.yaml deleted file mode 100644 index 086a66c5f..000000000 --- a/.changes/unreleased/BUG FIXES-20240325-125136.yaml +++ /dev/null @@ -1,5 +0,0 @@ -kind: BUG FIXES -body: 'helper/resource: Fix panic in output state shimming when a tuple is present.' -time: 2024-03-25T12:51:36.841767-04:00 -custom: - Issue: "310" diff --git a/.changes/unreleased/BUG FIXES-20240328-180800.yaml b/.changes/unreleased/BUG FIXES-20240328-180800.yaml deleted file mode 100644 index 8539e6088..000000000 --- a/.changes/unreleased/BUG FIXES-20240328-180800.yaml +++ /dev/null @@ -1,5 +0,0 @@ -kind: BUG FIXES -body: 'tfversion: Fixed `RequireBelow` ignoring equal versioning to fail a test' -time: 2024-03-28T18:08:00.236612-04:00 -custom: - Issue: "303" diff --git a/.changes/unreleased/ENHANCEMENTS-20240315-185051.yaml b/.changes/unreleased/ENHANCEMENTS-20240315-185051.yaml deleted file mode 100644 index 22f0fec2b..000000000 --- a/.changes/unreleased/ENHANCEMENTS-20240315-185051.yaml +++ /dev/null @@ -1,5 +0,0 @@ -kind: ENHANCEMENTS -body: 'helper/acctest: Improve scope of IPv4/IPv6 random address generation in RandIpAddress()' -time: 2024-03-15T18:50:51.668365-04:00 -custom: - Issue: "305" diff --git a/.changes/unreleased/ENHANCEMENTS-20240325-120539.yaml b/.changes/unreleased/ENHANCEMENTS-20240325-120539.yaml deleted file mode 100644 index ad24b19a9..000000000 --- a/.changes/unreleased/ENHANCEMENTS-20240325-120539.yaml +++ /dev/null @@ -1,6 +0,0 @@ -kind: ENHANCEMENTS -body: 'knownvalue: Add `TupleExact`, `TuplePartial` and `TupleSizeExact` checks for - dynamic value testing.' -time: 2024-03-25T12:05:39.777695-04:00 -custom: - Issue: "312" diff --git a/.changes/unreleased/ENHANCEMENTS-20240327-171628.yaml b/.changes/unreleased/ENHANCEMENTS-20240327-171628.yaml deleted file mode 100644 index 32a6292ca..000000000 --- a/.changes/unreleased/ENHANCEMENTS-20240327-171628.yaml +++ /dev/null @@ -1,7 +0,0 @@ -kind: ENHANCEMENTS -body: 'tfversion: Ensured Terraform CLI prerelease versions are considered - semantically equal to patch versions in built-in checks to match the Terraform CLI - versioning policy' -time: 2024-03-27T17:16:28.49466-04:00 -custom: - Issue: "303" diff --git a/.changes/unreleased/ENHANCEMENTS-20240503-161709.yaml b/.changes/unreleased/ENHANCEMENTS-20240503-161709.yaml deleted file mode 100644 index dff8d4adf..000000000 --- a/.changes/unreleased/ENHANCEMENTS-20240503-161709.yaml +++ /dev/null @@ -1,6 +0,0 @@ -kind: ENHANCEMENTS -body: 'helper/resource: Added `(TestCase).AdditionalCLIOptions` with `AllowDeferral` - option for plan and apply commands.' -time: 2024-05-03T16:17:09.64792-04:00 -custom: - Issue: "331" diff --git a/.changes/unreleased/FEATURES-20240503-161531.yaml b/.changes/unreleased/FEATURES-20240503-161531.yaml deleted file mode 100644 index edd8c036f..000000000 --- a/.changes/unreleased/FEATURES-20240503-161531.yaml +++ /dev/null @@ -1,6 +0,0 @@ -kind: FEATURES -body: 'plancheck: Added `ExpectDeferredChange` and `ExpectNoDeferredChanges` checks - for experimental deferred action support.' -time: 2024-05-03T16:15:31.03438-04:00 -custom: - Issue: "331" diff --git a/.changes/unreleased/FEATURES-20240503-161802.yaml b/.changes/unreleased/FEATURES-20240503-161802.yaml deleted file mode 100644 index a06f5f78c..000000000 --- a/.changes/unreleased/FEATURES-20240503-161802.yaml +++ /dev/null @@ -1,6 +0,0 @@ -kind: FEATURES -body: 'tfversion: Added `SkipIfNotPrerelease` version check for testing experimental - features of prerelease Terraform builds.' -time: 2024-05-03T16:18:02.132794-04:00 -custom: - Issue: "331" diff --git a/CHANGELOG.md b/CHANGELOG.md index 47b8e8b1f..ab9b8a1ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,22 @@ +## 1.8.0 (May 17, 2024) + +FEATURES: + +* plancheck: Added `ExpectDeferredChange` and `ExpectNoDeferredChanges` checks for experimental deferred action support. ([#331](https://github.com/hashicorp/terraform-plugin-testing/issues/331)) +* tfversion: Added `SkipIfNotPrerelease` version check for testing experimental features of prerelease Terraform builds. ([#331](https://github.com/hashicorp/terraform-plugin-testing/issues/331)) + +ENHANCEMENTS: + +* helper/acctest: Improve scope of IPv4/IPv6 random address generation in RandIpAddress() ([#305](https://github.com/hashicorp/terraform-plugin-testing/issues/305)) +* knownvalue: Add `TupleExact`, `TuplePartial` and `TupleSizeExact` checks for dynamic value testing. ([#312](https://github.com/hashicorp/terraform-plugin-testing/issues/312)) +* tfversion: Ensured Terraform CLI prerelease versions are considered semantically equal to patch versions in built-in checks to match the Terraform CLI versioning policy ([#303](https://github.com/hashicorp/terraform-plugin-testing/issues/303)) +* helper/resource: Added `(TestCase).AdditionalCLIOptions` with `AllowDeferral` option for plan and apply commands. ([#331](https://github.com/hashicorp/terraform-plugin-testing/issues/331)) + +BUG FIXES: + +* helper/resource: Fix panic in output state shimming when a tuple is present. ([#310](https://github.com/hashicorp/terraform-plugin-testing/issues/310)) +* tfversion: Fixed `RequireBelow` ignoring equal versioning to fail a test ([#303](https://github.com/hashicorp/terraform-plugin-testing/issues/303)) + ## 1.7.0 (March 05, 2024) NOTES: From e5cc909ea198f82cdad25e35cbc8179e7bde92a4 Mon Sep 17 00:00:00 2001 From: Service Account - Terraform Provider DevEx <100357958+hc-github-team-tf-provider-devex@users.noreply.github.com> Date: Fri, 17 May 2024 15:10:07 -0400 Subject: [PATCH 21/61] [CI] Update issue comment triage workflow file --- .github/workflows/issue-comment-triage.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .github/workflows/issue-comment-triage.yml diff --git a/.github/workflows/issue-comment-triage.yml b/.github/workflows/issue-comment-triage.yml new file mode 100644 index 000000000..6c1ddccb6 --- /dev/null +++ b/.github/workflows/issue-comment-triage.yml @@ -0,0 +1,14 @@ +# DO NOT EDIT - This GitHub Workflow is managed by automation +# https://github.com/hashicorp/terraform-devex-repos +name: Issue Comment Triage + +on: + issue_comment: + types: [created] + +jobs: + issue_comment_triage: + runs-on: ubuntu-latest + steps: + - name: 'Remove stale and waiting-response on comment' + run: gh issue edit ${{ github.event.issue.number }} --remove-label stale,waiting-response From 252df65897ed7f307d48bc6c3454f52b281fdee3 Mon Sep 17 00:00:00 2001 From: Service Account - Terraform Provider DevEx <100357958+hc-github-team-tf-provider-devex@users.noreply.github.com> Date: Fri, 17 May 2024 15:23:01 -0400 Subject: [PATCH 22/61] [CI] Update issue comment triage workflow file --- .github/workflows/issue-comment-triage.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/issue-comment-triage.yml b/.github/workflows/issue-comment-triage.yml index 6c1ddccb6..556d36de1 100644 --- a/.github/workflows/issue-comment-triage.yml +++ b/.github/workflows/issue-comment-triage.yml @@ -9,6 +9,8 @@ on: jobs: issue_comment_triage: runs-on: ubuntu-latest + env: + GH_TOKEN: ${{ github.token }} steps: - name: 'Remove stale and waiting-response on comment' run: gh issue edit ${{ github.event.issue.number }} --remove-label stale,waiting-response From ee740e8e0e81927676808405e4f7b3f33644193b Mon Sep 17 00:00:00 2001 From: Service Account - Terraform Provider DevEx <100357958+hc-github-team-tf-provider-devex@users.noreply.github.com> Date: Fri, 17 May 2024 15:48:01 -0400 Subject: [PATCH 23/61] [CI] Update issue comment triage workflow file --- .github/workflows/issue-comment-triage.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/issue-comment-triage.yml b/.github/workflows/issue-comment-triage.yml index 556d36de1..0f2af3d08 100644 --- a/.github/workflows/issue-comment-triage.yml +++ b/.github/workflows/issue-comment-triage.yml @@ -13,4 +13,4 @@ jobs: GH_TOKEN: ${{ github.token }} steps: - name: 'Remove stale and waiting-response on comment' - run: gh issue edit ${{ github.event.issue.number }} --remove-label stale,waiting-response + run: gh issue edit ${{ github.event.issue.html_url }} --remove-label stale,waiting-response From 46b3d5c6485730e672a00f57eaf267df98730a10 Mon Sep 17 00:00:00 2001 From: Service Account - Terraform Provider DevEx <100357958+hc-github-team-tf-provider-devex@users.noreply.github.com> Date: Fri, 17 May 2024 16:11:10 -0400 Subject: [PATCH 24/61] [CI] Update issue comment triage workflow file --- .github/workflows/issue-comment-triage.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/issue-comment-triage.yml b/.github/workflows/issue-comment-triage.yml index 0f2af3d08..00017cdfb 100644 --- a/.github/workflows/issue-comment-triage.yml +++ b/.github/workflows/issue-comment-triage.yml @@ -10,7 +10,12 @@ jobs: issue_comment_triage: runs-on: ubuntu-latest env: + # issue_comment events are triggered by comments on issues and pull requests. Checking the + # value of github.event.issue.pull_request tells us whether the issue is an issue or is + # actually a pull request, allowing us to dynamically set the gh subcommand: + # https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#issue_comment-on-issues-only-or-pull-requests-only + COMMAND: ${{ github.event.issue.pull_request && 'pr' || 'issue' }} GH_TOKEN: ${{ github.token }} steps: - - name: 'Remove stale and waiting-response on comment' - run: gh issue edit ${{ github.event.issue.html_url }} --remove-label stale,waiting-response + - name: 'Remove waiting-response on comment' + run: gh ${{ env.COMMAND }} edit ${{ github.event.issue.html_url }} --remove-label waiting-response From 2c791e23b8f6e82043086d8497072b855bba9008 Mon Sep 17 00:00:00 2001 From: "hashicorp-tsccr[bot]" <129506189+hashicorp-tsccr[bot]@users.noreply.github.com> Date: Mon, 20 May 2024 08:13:18 -0400 Subject: [PATCH 25/61] Result of tsccr-helper -log-level=info gha update -latest . (#342) Co-authored-by: hashicorp-tsccr[bot] --- .github/workflows/ci-go.yml | 2 +- .github/workflows/ci-goreleaser.yml | 2 +- .github/workflows/release.yml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci-go.yml b/.github/workflows/ci-go.yml index b2b7bed43..266198a12 100644 --- a/.github/workflows/ci-go.yml +++ b/.github/workflows/ci-go.yml @@ -21,7 +21,7 @@ jobs: with: go-version-file: 'go.mod' - run: go mod download - - uses: golangci/golangci-lint-action@38e1018663fa5173f3968ea0777460d3de38f256 # v5.3.0 + - uses: golangci/golangci-lint-action@a4f60bb28d35aeee14e6880718e0c85ff1882e64 # v6.0.1 test: name: test (Go ${{ matrix.go-version }} / TF ${{ matrix.terraform }}) runs-on: ubuntu-latest diff --git a/.github/workflows/ci-goreleaser.yml b/.github/workflows/ci-goreleaser.yml index 320e554f2..62e7ec344 100644 --- a/.github/workflows/ci-goreleaser.yml +++ b/.github/workflows/ci-goreleaser.yml @@ -18,6 +18,6 @@ jobs: - uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 with: go-version-file: 'go.mod' - - uses: goreleaser/goreleaser-action@7ec5c2b0c6cdda6e8bbb49444bc797dd33d74dd8 # v5.0.0 + - uses: goreleaser/goreleaser-action@5742e2a039330cbb23ebf35f046f814d4c6ff811 # v5.1.0 with: args: check diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 07b350bc1..5a66e7ef2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -93,7 +93,7 @@ jobs: cd .changes sed -e "1{/# /d;}" -e "2{/^$/d;}" ${{ needs.changelog-version.outputs.version }}.md > /tmp/release-notes.txt - - uses: goreleaser/goreleaser-action@7ec5c2b0c6cdda6e8bbb49444bc797dd33d74dd8 # v5.0.0 + - uses: goreleaser/goreleaser-action@5742e2a039330cbb23ebf35f046f814d4c6ff811 # v5.1.0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: From e309ffba913eedc3ba4b2f62bc13180b6402cfaf Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 20 May 2024 09:33:24 -0400 Subject: [PATCH 26/61] build(deps): Bump github.com/hashicorp/terraform-plugin-sdk/v2 (#343) Bumps [github.com/hashicorp/terraform-plugin-sdk/v2](https://github.com/hashicorp/terraform-plugin-sdk) from 2.33.0 to 2.34.0. - [Release notes](https://github.com/hashicorp/terraform-plugin-sdk/releases) - [Changelog](https://github.com/hashicorp/terraform-plugin-sdk/blob/main/CHANGELOG.md) - [Commits](https://github.com/hashicorp/terraform-plugin-sdk/compare/v2.33.0...v2.34.0) --- updated-dependencies: - dependency-name: github.com/hashicorp/terraform-plugin-sdk/v2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index a805261e6..0acca0806 100644 --- a/go.mod +++ b/go.mod @@ -17,7 +17,7 @@ require ( github.com/hashicorp/terraform-json v0.22.1 github.com/hashicorp/terraform-plugin-go v0.23.0 github.com/hashicorp/terraform-plugin-log v0.9.0 - github.com/hashicorp/terraform-plugin-sdk/v2 v2.33.0 + github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 github.com/mitchellh/go-testing-interface v1.14.1 github.com/zclconf/go-cty v1.14.4 golang.org/x/crypto v0.23.0 diff --git a/go.sum b/go.sum index 6d381066e..81b95b2b2 100644 --- a/go.sum +++ b/go.sum @@ -76,8 +76,8 @@ github.com/hashicorp/terraform-plugin-go v0.23.0 h1:AALVuU1gD1kPb48aPQUjug9Ir/12 github.com/hashicorp/terraform-plugin-go v0.23.0/go.mod h1:1E3Cr9h2vMlahWMbsSEcNrOCxovCZhOOIXjFHbjc/lQ= github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0= github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow= -github.com/hashicorp/terraform-plugin-sdk/v2 v2.33.0 h1:qHprzXy/As0rxedphECBEQAh3R4yp6pKksKHcqZx5G8= -github.com/hashicorp/terraform-plugin-sdk/v2 v2.33.0/go.mod h1:H+8tjs9TjV2w57QFVSMBQacf8k/E1XwLXGCARgViC6A= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 h1:kJiWGx2kiQVo97Y5IOGR4EMcZ8DtMswHhUuFibsCQQE= +github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0/go.mod h1:sl/UoabMc37HA6ICVMmGO+/0wofkVIRxf+BMb/dnoIg= github.com/hashicorp/terraform-registry-address v0.2.3 h1:2TAiKJ1A3MAkZlH1YI/aTVcLZRu7JseiXNRHbOAyoTI= github.com/hashicorp/terraform-registry-address v0.2.3/go.mod h1:lFHA76T8jfQteVfT7caREqguFrW3c4MFSPhZB7HHgUM= github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ= From e8b691c95f4b7b80b02a085c4e7c256b29d0a33b Mon Sep 17 00:00:00 2001 From: Austin Valle Date: Mon, 20 May 2024 14:35:49 -0400 Subject: [PATCH 27/61] workflows: Delete old issue label remove workflow (#344) Replaced by `.github/workflows/issue-comment-triage.yml` --- .github/workflows/issue-comment-created.yml | 15 --------------- 1 file changed, 15 deletions(-) delete mode 100644 .github/workflows/issue-comment-created.yml diff --git a/.github/workflows/issue-comment-created.yml b/.github/workflows/issue-comment-created.yml deleted file mode 100644 index 0c4f898c4..000000000 --- a/.github/workflows/issue-comment-created.yml +++ /dev/null @@ -1,15 +0,0 @@ -name: Issue Comment Created Triage - -on: - issue_comment: - types: [created] - -jobs: - issue_comment_triage: - runs-on: ubuntu-latest - steps: - - uses: actions-ecosystem/action-remove-labels@2ce5d41b4b6aa8503e285553f75ed56e0a40bae0 # v1.3.0 - with: - labels: | - stale - waiting-response From 32ef3f16d514fab220155c6d4de8d512c00e126a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 21 May 2024 12:39:41 -0400 Subject: [PATCH 28/61] build(deps): Bump github.com/hashicorp/hc-install from 0.6.4 to 0.7.0 (#345) updated-dependencies: - dependency-name: github.com/hashicorp/hc-install dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 0acca0806..b9e3c2874 100644 --- a/go.mod +++ b/go.mod @@ -10,7 +10,7 @@ require ( github.com/hashicorp/go-hclog v1.6.3 github.com/hashicorp/go-uuid v1.0.3 github.com/hashicorp/go-version v1.6.0 - github.com/hashicorp/hc-install v0.6.4 + github.com/hashicorp/hc-install v0.7.0 github.com/hashicorp/hcl/v2 v2.20.1 github.com/hashicorp/logutils v1.0.0 github.com/hashicorp/terraform-exec v0.21.0 @@ -48,7 +48,7 @@ require ( github.com/vmihailenco/msgpack v4.0.4+incompatible // indirect github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect - golang.org/x/mod v0.16.0 // indirect + golang.org/x/mod v0.17.0 // indirect golang.org/x/net v0.23.0 // indirect golang.org/x/sys v0.20.0 // indirect golang.org/x/text v0.15.0 // indirect diff --git a/go.sum b/go.sum index 81b95b2b2..0376f52e8 100644 --- a/go.sum +++ b/go.sum @@ -62,8 +62,8 @@ github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/C github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/hc-install v0.6.4 h1:QLqlM56/+SIIGvGcfFiwMY3z5WGXT066suo/v9Km8e0= -github.com/hashicorp/hc-install v0.6.4/go.mod h1:05LWLy8TD842OtgcfBbOT0WMoInBMUSHjmDx10zuBIA= +github.com/hashicorp/hc-install v0.7.0 h1:Uu9edVqjKQxxuD28mR5TikkKDd/p55S8vzPC1659aBk= +github.com/hashicorp/hc-install v0.7.0/go.mod h1:ELmmzZlGnEcqoUMKUuykHaPCIR1sYLYX+KSggWSKZuA= github.com/hashicorp/hcl/v2 v2.20.1 h1:M6hgdyz7HYt1UN9e61j+qKJBqR3orTWbI1HKBJEdxtc= github.com/hashicorp/hcl/v2 v2.20.1/go.mod h1:TZDqQ4kNKCbh1iJp99FdPiUaVDDUPivbqxZulxDYqL4= github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= @@ -146,8 +146,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= -golang.org/x/mod v0.16.0 h1:QX4fJ0Rr5cPQCF7O9lh9Se4pmwfwskqZfq5moyldzic= -golang.org/x/mod v0.16.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= From d7b7078b2314e0b08aac60fbe565d229b8c2c7ec Mon Sep 17 00:00:00 2001 From: Service Account - Terraform Provider DevEx <100357958+hc-github-team-tf-provider-devex@users.noreply.github.com> Date: Wed, 22 May 2024 13:47:53 -0400 Subject: [PATCH 29/61] [CI] Update lock workflow file --- .github/workflows/lock.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml index edbbb9904..55cd282a1 100644 --- a/.github/workflows/lock.yml +++ b/.github/workflows/lock.yml @@ -1,13 +1,17 @@ +# DO NOT EDIT - This GitHub Workflow is managed by automation +# https://github.com/hashicorp/terraform-devex-repos name: 'Lock Threads' on: schedule: - - cron: '50 1 * * *' + - cron: '29 22 * * *' jobs: lock: runs-on: ubuntu-latest steps: + # NOTE: When TSCCR updates the GitHub action version, update the template workflow file to avoid drift: + # https://github.com/hashicorp/terraform-devex-repos/blob/main/modules/repo/workflows/lock.tftpl - uses: dessant/lock-threads@1bf7ec25051fe7c00bdd17e6a7cf3d7bfb7dc771 # v5.0.1 with: github-token: ${{ github.token }} From 0d32cfa383a478be046347fc0c92a768365fce00 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 24 May 2024 12:10:53 -0400 Subject: [PATCH 30/61] build(deps): Bump github.com/hashicorp/go-version from 1.6.0 to 1.7.0 (#346) Bumps [github.com/hashicorp/go-version](https://github.com/hashicorp/go-version) from 1.6.0 to 1.7.0. - [Release notes](https://github.com/hashicorp/go-version/releases) - [Changelog](https://github.com/hashicorp/go-version/blob/main/CHANGELOG.md) - [Commits](https://github.com/hashicorp/go-version/compare/v1.6.0...v1.7.0) --- updated-dependencies: - dependency-name: github.com/hashicorp/go-version dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index b9e3c2874..9085d2f0f 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320 github.com/hashicorp/go-hclog v1.6.3 github.com/hashicorp/go-uuid v1.0.3 - github.com/hashicorp/go-version v1.6.0 + github.com/hashicorp/go-version v1.7.0 github.com/hashicorp/hc-install v0.7.0 github.com/hashicorp/hcl/v2 v2.20.1 github.com/hashicorp/logutils v1.0.0 diff --git a/go.sum b/go.sum index 0376f52e8..77c6ff287 100644 --- a/go.sum +++ b/go.sum @@ -60,8 +60,8 @@ github.com/hashicorp/go-plugin v1.6.0/go.mod h1:lBS5MtSSBZk0SHc66KACcjjlU6WzEVP/ github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.6.0 h1:feTTfFNnjP967rlCxM/I9g701jU+RN74YKx2mOkIeek= -github.com/hashicorp/go-version v1.6.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= +github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/hc-install v0.7.0 h1:Uu9edVqjKQxxuD28mR5TikkKDd/p55S8vzPC1659aBk= github.com/hashicorp/hc-install v0.7.0/go.mod h1:ELmmzZlGnEcqoUMKUuykHaPCIR1sYLYX+KSggWSKZuA= github.com/hashicorp/hcl/v2 v2.20.1 h1:M6hgdyz7HYt1UN9e61j+qKJBqR3orTWbI1HKBJEdxtc= From cd40e58b93ca4b580f280366fd4fcebdffade2d1 Mon Sep 17 00:00:00 2001 From: "hashicorp-tsccr[bot]" <129506189+hashicorp-tsccr[bot]@users.noreply.github.com> Date: Tue, 28 May 2024 10:39:21 -0400 Subject: [PATCH 31/61] Result of tsccr-helper -log-level=info gha update -latest . (#348) Co-authored-by: hashicorp-tsccr[bot] --- .github/workflows/ci-changie.yml | 2 +- .github/workflows/ci-github-actions.yml | 2 +- .github/workflows/ci-go.yml | 4 ++-- .github/workflows/ci-goreleaser.yml | 2 +- .github/workflows/compliance.yml | 2 +- .github/workflows/release.yml | 6 +++--- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci-changie.yml b/.github/workflows/ci-changie.yml index bfc5cf9a5..e1c0f40a3 100644 --- a/.github/workflows/ci-changie.yml +++ b/.github/workflows/ci-changie.yml @@ -15,7 +15,7 @@ jobs: check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - uses: miniscruff/changie-action@6dcc2533cac0495148ed4046c438487e4dceaa23 # v2.0.0 with: version: latest diff --git a/.github/workflows/ci-github-actions.yml b/.github/workflows/ci-github-actions.yml index afedc05af..e6d3310cf 100644 --- a/.github/workflows/ci-github-actions.yml +++ b/.github/workflows/ci-github-actions.yml @@ -13,7 +13,7 @@ jobs: actionlint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 with: go-version-file: 'go.mod' diff --git a/.github/workflows/ci-go.yml b/.github/workflows/ci-go.yml index 266198a12..8aebe79c5 100644 --- a/.github/workflows/ci-go.yml +++ b/.github/workflows/ci-go.yml @@ -16,7 +16,7 @@ jobs: golangci-lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 with: go-version-file: 'go.mod' @@ -30,7 +30,7 @@ jobs: go-version: [ '1.22', '1.21' ] terraform: ${{ fromJSON(vars.TF_VERSIONS_PROTOCOL_V5) }} steps: - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 with: go-version: ${{ matrix.go-version }} diff --git a/.github/workflows/ci-goreleaser.yml b/.github/workflows/ci-goreleaser.yml index 62e7ec344..b55e1d728 100644 --- a/.github/workflows/ci-goreleaser.yml +++ b/.github/workflows/ci-goreleaser.yml @@ -14,7 +14,7 @@ jobs: check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 with: go-version-file: 'go.mod' diff --git a/.github/workflows/compliance.yml b/.github/workflows/compliance.yml index fb4959f8f..8db9cfd12 100644 --- a/.github/workflows/compliance.yml +++ b/.github/workflows/compliance.yml @@ -11,7 +11,7 @@ jobs: copywrite: runs-on: ubuntu-latest steps: - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 - uses: hashicorp/setup-copywrite@32638da2d4e81d56a0764aa1547882fc4d209636 # v1.1.3 - run: copywrite headers --plan - run: copywrite license --plan diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5a66e7ef2..444a25239 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -25,7 +25,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: fetch-depth: 0 # Avoid persisting GITHUB_TOKEN credentials as they take priority over our service account PAT for `git push` operations @@ -54,7 +54,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: fetch-depth: 0 # Default input is the SHA that initially triggered the workflow. As we created a new commit in the previous job, @@ -79,7 +79,7 @@ jobs: contents: write # Needed for goreleaser to create GitHub release issues: write # Needed for goreleaser to close associated milestone steps: - - uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b # v4.1.4 + - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 with: ref: ${{ inputs.versionNumber }} fetch-depth: 0 From 7905bc7d7e2e08a450c792785de856ef8ea2c793 Mon Sep 17 00:00:00 2001 From: Service Account - Terraform Provider DevEx <100357958+hc-github-team-tf-provider-devex@users.noreply.github.com> Date: Tue, 4 Jun 2024 10:09:23 -0400 Subject: [PATCH 32/61] [CI] terraform-devex-repos automation --- .github/workflows/add-content-to-project.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/add-content-to-project.yml b/.github/workflows/add-content-to-project.yml index 35166f2ac..eeb91e1e3 100644 --- a/.github/workflows/add-content-to-project.yml +++ b/.github/workflows/add-content-to-project.yml @@ -10,7 +10,7 @@ on: # Calling it multiple times will be idempotent. # # See https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ - # to see the reasoning behind using `pull_request_target` instead of `pull_request` + # to see the reasoning behind using `pull_request_target` instead of `pull_request` types: [opened, reopened, ready_for_review] jobs: From 80a2d897ae545ad123bf8fc6503fba72e3fc1863 Mon Sep 17 00:00:00 2001 From: Service Account - Terraform Provider DevEx <100357958+hc-github-team-tf-provider-devex@users.noreply.github.com> Date: Tue, 4 Jun 2024 10:09:44 -0400 Subject: [PATCH 33/61] [CI] terraform-devex-repos automation From 26f93f06b41f07c5d0a44ae0078ba1f010629fb1 Mon Sep 17 00:00:00 2001 From: Service Account - Terraform Provider DevEx <100357958+hc-github-team-tf-provider-devex@users.noreply.github.com> Date: Tue, 4 Jun 2024 10:09:46 -0400 Subject: [PATCH 34/61] [CI] terraform-devex-repos automation --- .github/workflows/send-to-jira.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/send-to-jira.yml b/.github/workflows/send-to-jira.yml index cee84f686..6cf5ae5ad 100644 --- a/.github/workflows/send-to-jira.yml +++ b/.github/workflows/send-to-jira.yml @@ -34,4 +34,3 @@ jobs: description: "${{ github.event.issue.html_url || github.event.pull_request.html_url }} \n Synced by Github Actions, tagged by ${{ github.actor }}" # customfield_10091 is Team (R&D) fields: '{"customfield_10091": ["TF-DevEx"], "labels": ["Github"]}' - From 64094f27c690ad3de5bf014ba63cdedadeaa02d5 Mon Sep 17 00:00:00 2001 From: Service Account - Terraform Provider DevEx <100357958+hc-github-team-tf-provider-devex@users.noreply.github.com> Date: Tue, 4 Jun 2024 13:07:34 -0400 Subject: [PATCH 35/61] [CI] terraform-devex-repos automation --- .github/workflows/send-to-jira.yml | 36 ------------------------------ 1 file changed, 36 deletions(-) delete mode 100644 .github/workflows/send-to-jira.yml diff --git a/.github/workflows/send-to-jira.yml b/.github/workflows/send-to-jira.yml deleted file mode 100644 index 6cf5ae5ad..000000000 --- a/.github/workflows/send-to-jira.yml +++ /dev/null @@ -1,36 +0,0 @@ -on: - issues: - types: [labeled] - -name: Jira Sync - -jobs: - sync: - runs-on: ubuntu-latest - name: Jira sync - steps: - - - name: Login - uses: atlassian/gajira-login@45fd029b9f1d6d8926c6f04175aa80c0e42c9026 # v3.0.1 - if: github.event.label.name == 'tf-devex-triage' - env: - JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} - JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} - JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} - - name: Search for existing issue - id: search - if: github.event.label.name == 'tf-devex-triage' - uses: tomhjp/gh-action-jira-search@04700b457f317c3e341ce90da5a3ff4ce058f2fa # v0.2.2 - with: - jql: 'project="TFECO" and "Team (R&D)[Labels]"="TF-DevEx" and description ~ "${{ github.event.issue.html_url || github.event.pull_request.html_url }}" and labels in (Github)' - - - name: Create task in DevEx board - if: github.event.label.name == 'tf-devex-triage' && !steps.search.outputs.issue - uses: atlassian/gajira-create@59e177c4f6451399df5b4911c2211104f171e669 # v3.0.1 - with: - project: TFECO - issuetype: "Task" - summary: "[GH] ${{ github.event.issue.title || github.event.pull_request.title }}" - description: "${{ github.event.issue.html_url || github.event.pull_request.html_url }} \n Synced by Github Actions, tagged by ${{ github.actor }}" - # customfield_10091 is Team (R&D) - fields: '{"customfield_10091": ["TF-DevEx"], "labels": ["Github"]}' From 9b71c36e45ec1b19464a26b7844379e405d5c715 Mon Sep 17 00:00:00 2001 From: Service Account - Terraform Provider DevEx <100357958+hc-github-team-tf-provider-devex@users.noreply.github.com> Date: Tue, 4 Jun 2024 13:08:05 -0400 Subject: [PATCH 36/61] [CI] terraform-devex-repos automation --- .github/workflows/jira-sync.yml | 39 --------------------------------- 1 file changed, 39 deletions(-) delete mode 100644 .github/workflows/jira-sync.yml diff --git a/.github/workflows/jira-sync.yml b/.github/workflows/jira-sync.yml deleted file mode 100644 index 737d56a5d..000000000 --- a/.github/workflows/jira-sync.yml +++ /dev/null @@ -1,39 +0,0 @@ -on: - issues: - types: [closed, deleted, reopened] - pull_request_target: - types: [closed, reopened] - -name: Jira Sync - -jobs: - sync: - runs-on: ubuntu-latest - name: Jira sync - steps: - - - name: Login - uses: atlassian/gajira-login@45fd029b9f1d6d8926c6f04175aa80c0e42c9026 # v3.0.1 - if: contains(github.event.pull_request.labels.*.name, 'tf-devex-triage') || contains(github.event.issue.labels.*.name, 'tf-devex-triage') - env: - JIRA_BASE_URL: ${{ secrets.JIRA_BASE_URL }} - JIRA_USER_EMAIL: ${{ secrets.JIRA_USER_EMAIL }} - JIRA_API_TOKEN: ${{ secrets.JIRA_API_TOKEN }} - - name: Search for existing issue - id: search - if: contains(github.event.pull_request.labels.*.name, 'tf-devex-triage') || contains(github.event.issue.labels.*.name, 'tf-devex-triage') - uses: tomhjp/gh-action-jira-search@04700b457f317c3e341ce90da5a3ff4ce058f2fa # v0.2.2 - with: - jql: 'project="TFECO" and "Team (R&D)[Labels]"="TF-DevEx" and description ~ "${{ github.event.issue.html_url || github.event.pull_request.html_url }}" and labels in (Github)' - - name: Close task - if: ( github.event.action == 'closed' || github.event.action == 'deleted' ) && steps.search.outputs.issue - uses: atlassian/gajira-transition@38fc9cd61b03d6a53dd35fcccda172fe04b36de3 # v3.0.1 - with: - issue: ${{ steps.search.outputs.issue }} - transition: "Closed" - - name: Reopen task - if: github.event.action == 'reopened' && steps.search.outputs.issue - uses: atlassian/gajira-transition@38fc9cd61b03d6a53dd35fcccda172fe04b36de3 # v3.0.1 - with: - issue: ${{ steps.search.outputs.issue }} - transition: "To Do" From 5f42c0813d418b18e28a61c7c69f53622e039f35 Mon Sep 17 00:00:00 2001 From: Service Account - Terraform Provider DevEx <100357958+hc-github-team-tf-provider-devex@users.noreply.github.com> Date: Tue, 4 Jun 2024 13:08:37 -0400 Subject: [PATCH 37/61] [CI] terraform-devex-repos automation --- .github/workflows/add-content-to-project.yml | 40 -------------------- 1 file changed, 40 deletions(-) delete mode 100644 .github/workflows/add-content-to-project.yml diff --git a/.github/workflows/add-content-to-project.yml b/.github/workflows/add-content-to-project.yml deleted file mode 100644 index eeb91e1e3..000000000 --- a/.github/workflows/add-content-to-project.yml +++ /dev/null @@ -1,40 +0,0 @@ -# Based on https://github.com/leonsteinhaeuser/project-beta-automations - -name: "Add Issues/PRs to TF Provider DevEx team board" - -on: - issues: - types: [opened, reopened] - pull_request_target: - # NOTE: The way content is added to project board is equivalent to an "upsert". - # Calling it multiple times will be idempotent. - # - # See https://securitylab.github.com/research/github-actions-preventing-pwn-requests/ - # to see the reasoning behind using `pull_request_target` instead of `pull_request` - types: [opened, reopened, ready_for_review] - -jobs: - add-content-to-project: - name: "Add Content to project" - runs-on: ubuntu-latest - steps: - - name: "Set Issue to 'Priority = Triage Next'" - uses: leonsteinhaeuser/project-beta-automations@939000fb1900c9fc4f7b5058a09d9f833ebc6859 # v2.2.1 - if: github.event_name == 'issues' - with: - gh_token: ${{ secrets.TF_DEVEX_PROJECT_GITHUB_TOKEN }} - organization: "hashicorp" - project_id: 99 #< https://github.com/orgs/hashicorp/projects/99 - resource_node_id: ${{ github.event.issue.node_id }} - operation_mode: custom_field - custom_field_values: '[{\"name\":\"Priority\",\"type\":\"single_select\",\"value\":\"Triage Next\"}]' - - name: "Set Pull Request to 'Priority = Triage Next'" - uses: leonsteinhaeuser/project-beta-automations@939000fb1900c9fc4f7b5058a09d9f833ebc6859 # v2.2.1 - if: github.event_name == 'pull_request_target' - with: - gh_token: ${{ secrets.TF_DEVEX_PROJECT_GITHUB_TOKEN }} - organization: "hashicorp" - project_id: 99 #< https://github.com/orgs/hashicorp/projects/99 - resource_node_id: ${{ github.event.pull_request.node_id }} - operation_mode: custom_field - custom_field_values: '[{\"name\":\"Priority\",\"type\":\"single_select\",\"value\":\"Triage Next\"}]' From d67d49429d24c92bb21364268be14afcde051f61 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 5 Jun 2024 10:57:30 -0400 Subject: [PATCH 38/61] build(deps): Bump golang.org/x/crypto from 0.23.0 to 0.24.0 (#350) Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.23.0 to 0.24.0. - [Commits](https://github.com/golang/crypto/compare/v0.23.0...v0.24.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 11 ++++++----- go.sum | 28 ++++++++++++++-------------- 2 files changed, 20 insertions(+), 19 deletions(-) diff --git a/go.mod b/go.mod index 9085d2f0f..a571d5781 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 github.com/mitchellh/go-testing-interface v1.14.1 github.com/zclconf/go-cty v1.14.4 - golang.org/x/crypto v0.23.0 + golang.org/x/crypto v0.24.0 ) require ( @@ -49,10 +49,11 @@ require ( github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect golang.org/x/mod v0.17.0 // indirect - golang.org/x/net v0.23.0 // indirect - golang.org/x/sys v0.20.0 // indirect - golang.org/x/text v0.15.0 // indirect - golang.org/x/tools v0.13.0 // indirect + golang.org/x/net v0.25.0 // indirect + golang.org/x/sync v0.7.0 // indirect + golang.org/x/sys v0.21.0 // indirect + golang.org/x/text v0.16.0 // indirect + golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/appengine v1.6.8 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20240227224415-6ceb2ff114de // indirect google.golang.org/grpc v1.63.2 // indirect diff --git a/go.sum b/go.sum index 77c6ff287..922f7a138 100644 --- a/go.sum +++ b/go.sum @@ -143,8 +143,8 @@ github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b h1:FosyBZYxY3 github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.23.0 h1:dIJU/v2J8Mdglj/8rJ6UUOM3Zc9zLZxVZwwxMooUSAI= -golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= @@ -152,13 +152,13 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= -golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= -golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.25.0 h1:d/OCCoBEUq33pjydKrGQhw7IlUPI2Oylr+8qLx49kac= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ= -golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -171,24 +171,24 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.20.0 h1:Od9JTbYCk261bKm4M/mw7AklTlFYIa0bIp9BgSm1S8Y= -golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.20.0 h1:VnkxpohqXaOBYJtBmEppKUG6mXpi+4O6purfc2+sMhw= -golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= +golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= -golang.org/x/text v0.15.0 h1:h1V/4gjBv8v9cjcR6+AR5+/cIYK5N/WAgiv4xlsEtAk= -golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= -golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d h1:vU5i/LfpvrRCpgM/VPfJLg5KjxD3E+hfT1SH+d9zLwg= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= From 3871ae0c61fb9fe01e9c6d3927e6a5b9080cbdcb Mon Sep 17 00:00:00 2001 From: Service Account - Terraform Provider DevEx <100357958+hc-github-team-tf-provider-devex@users.noreply.github.com> Date: Fri, 7 Jun 2024 16:21:55 -0400 Subject: [PATCH 39/61] [CI] terraform-devex-repos automation From 3f5d56a441ac642888bd6df9ae69f4946d8ccef9 Mon Sep 17 00:00:00 2001 From: Service Account - Terraform Provider DevEx <100357958+hc-github-team-tf-provider-devex@users.noreply.github.com> Date: Fri, 7 Jun 2024 16:22:11 -0400 Subject: [PATCH 40/61] [CI] terraform-devex-repos automation --- .github/workflows/ci-changie.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-changie.yml b/.github/workflows/ci-changie.yml index e1c0f40a3..57219686b 100644 --- a/.github/workflows/ci-changie.yml +++ b/.github/workflows/ci-changie.yml @@ -1,3 +1,6 @@ +# DO NOT EDIT - This GitHub Workflow is managed by automation +# https://github.com/hashicorp/terraform-devex-repos + # Continuous integration handling for changie name: ci-changie @@ -15,9 +18,10 @@ jobs: check: runs-on: ubuntu-latest steps: + # Ensure terraform-devex-repos is updated on version changes. - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + # Ensure terraform-devex-repos is updated on version changes. - uses: miniscruff/changie-action@6dcc2533cac0495148ed4046c438487e4dceaa23 # v2.0.0 with: version: latest args: batch patch --dry-run - From b62ea3ca231c590ba6dc42925cf5dcc97b327e12 Mon Sep 17 00:00:00 2001 From: Service Account - Terraform Provider DevEx <100357958+hc-github-team-tf-provider-devex@users.noreply.github.com> Date: Fri, 7 Jun 2024 16:23:03 -0400 Subject: [PATCH 41/61] [CI] terraform-devex-repos automation --- .changie.yaml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.changie.yaml b/.changie.yaml index 00b125957..1beba3233 100644 --- a/.changie.yaml +++ b/.changie.yaml @@ -1,6 +1,5 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - +# DO NOT EDIT - This GitHub Workflow is managed by automation +# https://github.com/hashicorp/terraform-devex-repos changesDir: .changes unreleasedDir: unreleased changelogPath: CHANGELOG.md From 835a83701d7a361950c98b491bfb13fa9f5e81ca Mon Sep 17 00:00:00 2001 From: Service Account - Terraform Provider DevEx <100357958+hc-github-team-tf-provider-devex@users.noreply.github.com> Date: Mon, 10 Jun 2024 16:08:09 -0400 Subject: [PATCH 42/61] [CI] Update lock workflow file --- .github/workflows/lock.yml | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/.github/workflows/lock.yml b/.github/workflows/lock.yml index 55cd282a1..199e2e086 100644 --- a/.github/workflows/lock.yml +++ b/.github/workflows/lock.yml @@ -15,13 +15,7 @@ jobs: - uses: dessant/lock-threads@1bf7ec25051fe7c00bdd17e6a7cf3d7bfb7dc771 # v5.0.1 with: github-token: ${{ github.token }} - issue-comment: > - I'm going to lock this issue because it has been closed for _30 days_ ⏳. This helps our maintainers find and focus on the active issues. - - If you have found a problem that seems similar to this, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further. issue-inactive-days: '30' - pr-comment: > - I'm going to lock this pull request because it has been closed for _30 days_ ⏳. This helps our maintainers find and focus on the active contributions. - - If you have found a problem that seems related to this change, please open a new issue and complete the issue template so we can capture all the details necessary to investigate further. + issue-lock-reason: resolved pr-inactive-days: '30' + pr-lock-reason: resolved From 751342d7436a305b9597f0b2741a4d9fd2a6a3bb Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 21 Jun 2024 10:20:38 -0400 Subject: [PATCH 43/61] build(deps): Bump github.com/hashicorp/hcl/v2 from 2.20.1 to 2.21.0 (#352) Bumps [github.com/hashicorp/hcl/v2](https://github.com/hashicorp/hcl) from 2.20.1 to 2.21.0. - [Release notes](https://github.com/hashicorp/hcl/releases) - [Changelog](https://github.com/hashicorp/hcl/blob/main/CHANGELOG.md) - [Commits](https://github.com/hashicorp/hcl/compare/v2.20.1...v2.21.0) --- updated-dependencies: - dependency-name: github.com/hashicorp/hcl/v2 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index a571d5781..309ba908d 100644 --- a/go.mod +++ b/go.mod @@ -11,7 +11,7 @@ require ( github.com/hashicorp/go-uuid v1.0.3 github.com/hashicorp/go-version v1.7.0 github.com/hashicorp/hc-install v0.7.0 - github.com/hashicorp/hcl/v2 v2.20.1 + github.com/hashicorp/hcl/v2 v2.21.0 github.com/hashicorp/logutils v1.0.0 github.com/hashicorp/terraform-exec v0.21.0 github.com/hashicorp/terraform-json v0.22.1 diff --git a/go.sum b/go.sum index 922f7a138..b6abf0ec1 100644 --- a/go.sum +++ b/go.sum @@ -64,8 +64,8 @@ github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKe github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/hc-install v0.7.0 h1:Uu9edVqjKQxxuD28mR5TikkKDd/p55S8vzPC1659aBk= github.com/hashicorp/hc-install v0.7.0/go.mod h1:ELmmzZlGnEcqoUMKUuykHaPCIR1sYLYX+KSggWSKZuA= -github.com/hashicorp/hcl/v2 v2.20.1 h1:M6hgdyz7HYt1UN9e61j+qKJBqR3orTWbI1HKBJEdxtc= -github.com/hashicorp/hcl/v2 v2.20.1/go.mod h1:TZDqQ4kNKCbh1iJp99FdPiUaVDDUPivbqxZulxDYqL4= +github.com/hashicorp/hcl/v2 v2.21.0 h1:lve4q/o/2rqwYOgUg3y3V2YPyD1/zkCLGjIV74Jit14= +github.com/hashicorp/hcl/v2 v2.21.0/go.mod h1:62ZYHrXgPoX8xBnzl8QzbWq4dyDsDtfCRgIq1rbJEvA= github.com/hashicorp/logutils v1.0.0 h1:dLEQVugN8vlakKOUE3ihGLTZJRB4j+M2cdTm/ORI65Y= github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= github.com/hashicorp/terraform-exec v0.21.0 h1:uNkLAe95ey5Uux6KJdua6+cv8asgILFVWkd/RG0D2XQ= @@ -139,8 +139,8 @@ github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/zclconf/go-cty v1.14.4 h1:uXXczd9QDGsgu0i/QFR/hzI5NYCHLf6NQw/atrbnhq8= github.com/zclconf/go-cty v1.14.4/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE= -github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b h1:FosyBZYxY34Wul7O/MSKey3txpPYyCqVO5ZyceuQJEI= -github.com/zclconf/go-cty-debug v0.0.0-20191215020915-b22d67c1ba0b/go.mod h1:ZRKQfBXbGkpdV6QMzT3rU1kSTAnfu1dO8dPKjYprgj8= +github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6ZMSMNJFMOjqrGHynW3DIBuR2H9j0ug+Mo= +github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= From 1d6261e2c7519aa28d18c74953dfec60c702b5f1 Mon Sep 17 00:00:00 2001 From: "hashicorp-tsccr[bot]" <129506189+hashicorp-tsccr[bot]@users.noreply.github.com> Date: Fri, 21 Jun 2024 10:51:26 -0400 Subject: [PATCH 44/61] SEC-090: Automated trusted workflow pinning (2024-06-17) (#351) * Result of tsccr-helper -log-level=info gha update -latest . * Add version to .goreleaser file --------- Co-authored-by: hashicorp-tsccr[bot] Co-authored-by: Austin Valle --- .github/workflows/ci-goreleaser.yml | 2 +- .github/workflows/release.yml | 2 +- .goreleaser.yml | 4 +--- 3 files changed, 3 insertions(+), 5 deletions(-) diff --git a/.github/workflows/ci-goreleaser.yml b/.github/workflows/ci-goreleaser.yml index b55e1d728..390e6d02c 100644 --- a/.github/workflows/ci-goreleaser.yml +++ b/.github/workflows/ci-goreleaser.yml @@ -18,6 +18,6 @@ jobs: - uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 with: go-version-file: 'go.mod' - - uses: goreleaser/goreleaser-action@5742e2a039330cbb23ebf35f046f814d4c6ff811 # v5.1.0 + - uses: goreleaser/goreleaser-action@286f3b13b1b49da4ac219696163fb8c1c93e1200 # v6.0.0 with: args: check diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 444a25239..5dfe00221 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -93,7 +93,7 @@ jobs: cd .changes sed -e "1{/# /d;}" -e "2{/^$/d;}" ${{ needs.changelog-version.outputs.version }}.md > /tmp/release-notes.txt - - uses: goreleaser/goreleaser-action@5742e2a039330cbb23ebf35f046f814d4c6ff811 # v5.1.0 + - uses: goreleaser/goreleaser-action@286f3b13b1b49da4ac219696163fb8c1c93e1200 # v6.0.0 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: diff --git a/.goreleaser.yml b/.goreleaser.yml index 33424ec46..91d69e904 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -1,6 +1,4 @@ -# Copyright (c) HashiCorp, Inc. -# SPDX-License-Identifier: MPL-2.0 - +version: 2 project_name: terraform-plugin-testing builds: - skip: true From ad5287aef6ac61e5d3ebf9e538ed7ec4e774a237 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 25 Jun 2024 17:15:10 -0400 Subject: [PATCH 45/61] build(deps): Bump github.com/hashicorp/copywrite in /tools (#355) Bumps [github.com/hashicorp/copywrite](https://github.com/hashicorp/copywrite) from 0.18.0 to 0.19.0. - [Release notes](https://github.com/hashicorp/copywrite/releases) - [Changelog](https://github.com/hashicorp/copywrite/blob/main/.goreleaser.yaml) - [Commits](https://github.com/hashicorp/copywrite/compare/v0.18.0...v0.19.0) --- updated-dependencies: - dependency-name: github.com/hashicorp/copywrite dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- tools/go.mod | 2 +- tools/go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tools/go.mod b/tools/go.mod index ba85d85e0..4a4affd20 100644 --- a/tools/go.mod +++ b/tools/go.mod @@ -2,7 +2,7 @@ module tools go 1.21 -require github.com/hashicorp/copywrite v0.18.0 +require github.com/hashicorp/copywrite v0.19.0 require ( github.com/AlecAivazis/survey/v2 v2.3.6 // indirect diff --git a/tools/go.sum b/tools/go.sum index b903a2483..0d4023903 100644 --- a/tools/go.sum +++ b/tools/go.sum @@ -147,8 +147,8 @@ github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542 h1:2VTzZjLZBgl62/EtslC github.com/h2non/parth v0.0.0-20190131123155-b4df798d6542/go.mod h1:Ow0tF8D4Kplbc8s8sSb3V2oUCygFHVp8gC3Dn6U4MNI= github.com/hashicorp/consul/api v1.13.0/go.mod h1:ZlVrynguJKcYr54zGaDbaL3fOvKC9m72FhPvA8T35KQ= github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= -github.com/hashicorp/copywrite v0.18.0 h1:6f3aBDyQLBXhD6cdGSnsEM37vCDi3JJrkbR9HPBJf5c= -github.com/hashicorp/copywrite v0.18.0/go.mod h1:6wvQH+ICDoD2bpjO1RJ6fi+h3aY5NeLEM12oTkEtFoc= +github.com/hashicorp/copywrite v0.19.0 h1:f9LVxTDBfFYeQmdBpOsZ+HWknXonI8ZwubbO/RwyuCo= +github.com/hashicorp/copywrite v0.19.0/go.mod h1:6wvQH+ICDoD2bpjO1RJ6fi+h3aY5NeLEM12oTkEtFoc= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= From acc1c0b5942e60c54d5cbd08b409a9f5b5994f38 Mon Sep 17 00:00:00 2001 From: "hashicorp-tsccr[bot]" <129506189+hashicorp-tsccr[bot]@users.noreply.github.com> Date: Wed, 26 Jun 2024 14:55:05 -0400 Subject: [PATCH 46/61] Result of tsccr-helper -log-level=info gha update -latest . (#354) Co-authored-by: hashicorp-tsccr[bot] Co-authored-by: Austin Valle --- .github/workflows/ci-changie.yml | 2 +- .github/workflows/ci-github-actions.yml | 2 +- .github/workflows/ci-go.yml | 4 ++-- .github/workflows/ci-goreleaser.yml | 2 +- .github/workflows/compliance.yml | 2 +- .github/workflows/release.yml | 6 +++--- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/ci-changie.yml b/.github/workflows/ci-changie.yml index 57219686b..de5554dda 100644 --- a/.github/workflows/ci-changie.yml +++ b/.github/workflows/ci-changie.yml @@ -19,7 +19,7 @@ jobs: runs-on: ubuntu-latest steps: # Ensure terraform-devex-repos is updated on version changes. - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 # Ensure terraform-devex-repos is updated on version changes. - uses: miniscruff/changie-action@6dcc2533cac0495148ed4046c438487e4dceaa23 # v2.0.0 with: diff --git a/.github/workflows/ci-github-actions.yml b/.github/workflows/ci-github-actions.yml index e6d3310cf..cf96ddbdc 100644 --- a/.github/workflows/ci-github-actions.yml +++ b/.github/workflows/ci-github-actions.yml @@ -13,7 +13,7 @@ jobs: actionlint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 with: go-version-file: 'go.mod' diff --git a/.github/workflows/ci-go.yml b/.github/workflows/ci-go.yml index 8aebe79c5..2af391c85 100644 --- a/.github/workflows/ci-go.yml +++ b/.github/workflows/ci-go.yml @@ -16,7 +16,7 @@ jobs: golangci-lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 with: go-version-file: 'go.mod' @@ -30,7 +30,7 @@ jobs: go-version: [ '1.22', '1.21' ] terraform: ${{ fromJSON(vars.TF_VERSIONS_PROTOCOL_V5) }} steps: - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 with: go-version: ${{ matrix.go-version }} diff --git a/.github/workflows/ci-goreleaser.yml b/.github/workflows/ci-goreleaser.yml index 390e6d02c..398bd1558 100644 --- a/.github/workflows/ci-goreleaser.yml +++ b/.github/workflows/ci-goreleaser.yml @@ -14,7 +14,7 @@ jobs: check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 # v5.0.1 with: go-version-file: 'go.mod' diff --git a/.github/workflows/compliance.yml b/.github/workflows/compliance.yml index 8db9cfd12..51d71992e 100644 --- a/.github/workflows/compliance.yml +++ b/.github/workflows/compliance.yml @@ -11,7 +11,7 @@ jobs: copywrite: runs-on: ubuntu-latest steps: - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 - uses: hashicorp/setup-copywrite@32638da2d4e81d56a0764aa1547882fc4d209636 # v1.1.3 - run: copywrite headers --plan - run: copywrite license --plan diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 5dfe00221..3a6ec9c87 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -25,7 +25,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: fetch-depth: 0 # Avoid persisting GITHUB_TOKEN credentials as they take priority over our service account PAT for `git push` operations @@ -54,7 +54,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: fetch-depth: 0 # Default input is the SHA that initially triggered the workflow. As we created a new commit in the previous job, @@ -79,7 +79,7 @@ jobs: contents: write # Needed for goreleaser to create GitHub release issues: write # Needed for goreleaser to close associated milestone steps: - - uses: actions/checkout@a5ac7e51b41094c92402da3b24376905380afc29 # v4.1.6 + - uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7 with: ref: ${{ inputs.versionNumber }} fetch-depth: 0 From 082330e5e6178ee7e23bc9da97e51454b77d505e Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Mon, 1 Jul 2024 12:57:15 -0400 Subject: [PATCH 47/61] knownvalue: Add `Int32Exact` and `Float32Exact` checks (#356) * Add `float32exact` and `int32exact` types * Update website documentation * Add changelog entries * Update changelog wording --- .../ENHANCEMENTS-20240626-163240.yaml | 5 ++ .../ENHANCEMENTS-20240626-163311.yaml | 5 ++ knownvalue/float32.go | 51 +++++++++++++ knownvalue/float32_test.go | 75 +++++++++++++++++++ knownvalue/int32.go | 51 +++++++++++++ knownvalue/int32_test.go | 75 +++++++++++++++++++ website/data/plugin-testing-nav-data.json | 8 ++ .../known-value-checks/custom.mdx | 2 + .../known-value-checks/float32.mdx | 42 +++++++++++ .../known-value-checks/index.mdx | 2 + .../known-value-checks/int32.mdx | 42 +++++++++++ .../known-value-checks/int64.mdx | 2 +- .../testing/acceptance-tests/tfjson-paths.mdx | 2 + 13 files changed, 361 insertions(+), 1 deletion(-) create mode 100644 .changes/unreleased/ENHANCEMENTS-20240626-163240.yaml create mode 100644 .changes/unreleased/ENHANCEMENTS-20240626-163311.yaml create mode 100644 knownvalue/float32.go create mode 100644 knownvalue/float32_test.go create mode 100644 knownvalue/int32.go create mode 100644 knownvalue/int32_test.go create mode 100644 website/docs/plugin/testing/acceptance-tests/known-value-checks/float32.mdx create mode 100644 website/docs/plugin/testing/acceptance-tests/known-value-checks/int32.mdx diff --git a/.changes/unreleased/ENHANCEMENTS-20240626-163240.yaml b/.changes/unreleased/ENHANCEMENTS-20240626-163240.yaml new file mode 100644 index 000000000..7f31ac2ea --- /dev/null +++ b/.changes/unreleased/ENHANCEMENTS-20240626-163240.yaml @@ -0,0 +1,5 @@ +kind: ENHANCEMENTS +body: 'knownvalue: Add `Int32Exact` check for int32 value testing.' +time: 2024-06-26T16:32:40.387821-04:00 +custom: + Issue: "356" diff --git a/.changes/unreleased/ENHANCEMENTS-20240626-163311.yaml b/.changes/unreleased/ENHANCEMENTS-20240626-163311.yaml new file mode 100644 index 000000000..b6c29f5ba --- /dev/null +++ b/.changes/unreleased/ENHANCEMENTS-20240626-163311.yaml @@ -0,0 +1,5 @@ +kind: ENHANCEMENTS +body: 'knownvalue: Add `Float32Exact` check for float32 value testing.' +time: 2024-06-26T16:33:11.495969-04:00 +custom: + Issue: "356" diff --git a/knownvalue/float32.go b/knownvalue/float32.go new file mode 100644 index 000000000..ee02fdcb1 --- /dev/null +++ b/knownvalue/float32.go @@ -0,0 +1,51 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package knownvalue + +import ( + "encoding/json" + "fmt" + "strconv" +) + +var _ Check = float32Exact{} + +type float32Exact struct { + value float32 +} + +// CheckValue determines whether the passed value is of type float32, and +// contains a matching float32 value. +func (v float32Exact) CheckValue(other any) error { + jsonNum, ok := other.(json.Number) + + if !ok { + return fmt.Errorf("expected json.Number value for Float32Exact check, got: %T", other) + } + + otherVal, err := strconv.ParseFloat(string(jsonNum), 32) + + if err != nil { + return fmt.Errorf("expected json.Number to be parseable as float32 value for Float32Exact check: %s", err) + } + + if float32(otherVal) != v.value { + return fmt.Errorf("expected value %s for Float32Exact check, got: %s", v.String(), strconv.FormatFloat(otherVal, 'f', -1, 32)) + } + + return nil +} + +// String returns the string representation of the float32 value. +func (v float32Exact) String() string { + return strconv.FormatFloat(float64(v.value), 'f', -1, 32) +} + +// Float32Exact returns a Check for asserting equality between the +// supplied float32 and the value passed to the CheckValue method. +func Float32Exact(value float32) float32Exact { + return float32Exact{ + value: value, + } +} diff --git a/knownvalue/float32_test.go b/knownvalue/float32_test.go new file mode 100644 index 000000000..4b2ef14c4 --- /dev/null +++ b/knownvalue/float32_test.go @@ -0,0 +1,75 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package knownvalue_test + +import ( + "encoding/json" + "fmt" + "testing" + + "github.com/google/go-cmp/cmp" + + "github.com/hashicorp/terraform-plugin-testing/knownvalue" +) + +func TestFloat32Value_CheckValue(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + self knownvalue.Check + other any + expectedError error + }{ + "zero-nil": { + self: knownvalue.Float32Exact(0), + expectedError: fmt.Errorf("expected json.Number value for Float32Exact check, got: "), + }, + "zero-other": { + self: knownvalue.Float32Exact(0), + other: json.Number("0.0"), // checking against the underlying value field zero-value + }, + "nil": { + self: knownvalue.Float32Exact(1.234), + expectedError: fmt.Errorf("expected json.Number value for Float32Exact check, got: "), + }, + "wrong-type": { + self: knownvalue.Float32Exact(1.234), + other: json.Number("str"), + expectedError: fmt.Errorf("expected json.Number to be parseable as float32 value for Float32Exact check: strconv.ParseFloat: parsing \"str\": invalid syntax"), + }, + "not-equal": { + self: knownvalue.Float32Exact(1.234), + other: json.Number("4.321"), + expectedError: fmt.Errorf("expected value 1.234 for Float32Exact check, got: 4.321"), + }, + "equal": { + self: knownvalue.Float32Exact(1.234), + other: json.Number("1.234"), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.self.CheckValue(testCase.other) + + if diff := cmp.Diff(got, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestFloat32Value_String(t *testing.T) { + t.Parallel() + + got := knownvalue.Float32Exact(1.234567890123e+03).String() + + if diff := cmp.Diff(got, "1234.5679"); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } +} diff --git a/knownvalue/int32.go b/knownvalue/int32.go new file mode 100644 index 000000000..49dd30bb3 --- /dev/null +++ b/knownvalue/int32.go @@ -0,0 +1,51 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package knownvalue + +import ( + "encoding/json" + "fmt" + "strconv" +) + +var _ Check = int32Exact{} + +type int32Exact struct { + value int32 +} + +// CheckValue determines whether the passed value is of type int32, and +// contains a matching int32 value. +func (v int32Exact) CheckValue(other any) error { + jsonNum, ok := other.(json.Number) + + if !ok { + return fmt.Errorf("expected json.Number value for Int32Exact check, got: %T", other) + } + + otherVal, err := strconv.ParseInt(string(jsonNum), 10, 32) + + if err != nil { + return fmt.Errorf("expected json.Number to be parseable as int32 value for Int32Exact check: %s", err) + } + + if int32(otherVal) != v.value { + return fmt.Errorf("expected value %d for Int32Exact check, got: %d", v.value, otherVal) + } + + return nil +} + +// String returns the string representation of the int32 value. +func (v int32Exact) String() string { + return strconv.FormatInt(int64(v.value), 10) +} + +// Int32Exact returns a Check for asserting equality between the +// supplied int32 and the value passed to the CheckValue method. +func Int32Exact(value int32) int32Exact { + return int32Exact{ + value: value, + } +} diff --git a/knownvalue/int32_test.go b/knownvalue/int32_test.go new file mode 100644 index 000000000..b153dee29 --- /dev/null +++ b/knownvalue/int32_test.go @@ -0,0 +1,75 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package knownvalue_test + +import ( + "encoding/json" + "fmt" + "testing" + + "github.com/google/go-cmp/cmp" + + "github.com/hashicorp/terraform-plugin-testing/knownvalue" +) + +func TestInt32Value_CheckValue(t *testing.T) { + t.Parallel() + + testCases := map[string]struct { + self knownvalue.Check + other any + expectedError error + }{ + "zero-nil": { + self: knownvalue.Int32Exact(0), + expectedError: fmt.Errorf("expected json.Number value for Int32Exact check, got: "), + }, + "zero-other": { + self: knownvalue.Int32Exact(0), + other: json.Number("0"), // checking against the underlying value field zero-value + }, + "nil": { + self: knownvalue.Int32Exact(1234), + expectedError: fmt.Errorf("expected json.Number value for Int32Exact check, got: "), + }, + "wrong-type": { + self: knownvalue.Int32Exact(1234), + other: json.Number("str"), + expectedError: fmt.Errorf("expected json.Number to be parseable as int32 value for Int32Exact check: strconv.ParseInt: parsing \"str\": invalid syntax"), + }, + "not-equal": { + self: knownvalue.Int32Exact(1234), + other: json.Number("4321"), + expectedError: fmt.Errorf("expected value 1234 for Int32Exact check, got: 4321"), + }, + "equal": { + self: knownvalue.Int32Exact(1234), + other: json.Number("1234"), + }, + } + + for name, testCase := range testCases { + name, testCase := name, testCase + + t.Run(name, func(t *testing.T) { + t.Parallel() + + got := testCase.self.CheckValue(testCase.other) + + if diff := cmp.Diff(got, testCase.expectedError, equateErrorMessage); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } + }) + } +} + +func TestInt32Value_String(t *testing.T) { + t.Parallel() + + got := knownvalue.Int32Exact(123456789).String() + + if diff := cmp.Diff(got, "123456789"); diff != "" { + t.Errorf("unexpected difference: %s", diff) + } +} diff --git a/website/data/plugin-testing-nav-data.json b/website/data/plugin-testing-nav-data.json index e27db47d8..be9cff643 100644 --- a/website/data/plugin-testing-nav-data.json +++ b/website/data/plugin-testing-nav-data.json @@ -86,10 +86,18 @@ "title": "Custom", "path": "acceptance-tests/known-value-checks/custom" }, + { + "title": "Float32", + "path": "acceptance-tests/known-value-checks/float32" + }, { "title": "Float64", "path": "acceptance-tests/known-value-checks/float64" }, + { + "title": "Int32", + "path": "acceptance-tests/known-value-checks/int32" + }, { "title": "Int64", "path": "acceptance-tests/known-value-checks/int64" diff --git a/website/docs/plugin/testing/acceptance-tests/known-value-checks/custom.mdx b/website/docs/plugin/testing/acceptance-tests/known-value-checks/custom.mdx index b577eb8cc..ef92d4e80 100644 --- a/website/docs/plugin/testing/acceptance-tests/known-value-checks/custom.mdx +++ b/website/docs/plugin/testing/acceptance-tests/known-value-checks/custom.mdx @@ -63,7 +63,9 @@ The `other` parameter passed to the `CheckValue` method is one of the following Refer to the following built-in known value checks for implementations that handle the different types that can be passed to the `CheckValue` method in the `other` parameter: * [Bool](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Bool) +* [Float32Exact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Float32Exact) * [Float64Exact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Float64Exact) +* [Int32Exact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Int32Exact) * [Int64Exact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Int64Exact) * [ListExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#ListExact) * [MapExact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#MapExact) diff --git a/website/docs/plugin/testing/acceptance-tests/known-value-checks/float32.mdx b/website/docs/plugin/testing/acceptance-tests/known-value-checks/float32.mdx new file mode 100644 index 000000000..26f5a77fc --- /dev/null +++ b/website/docs/plugin/testing/acceptance-tests/known-value-checks/float32.mdx @@ -0,0 +1,42 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Known Values' +description: >- + Float32 Value Checks for use with Plan Checks. +--- + +# Float32 Known Value Checks + +The known value checks that are available for float32 values are: + +* [Float32Exact](/terraform/plugin/testing/acceptance-tests/known-value-checks/float32#float32exact-check) + +## `Float32Exact` Check + +The [Float32Exact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Float32Exact) check tests that a resource attribute, or output value has an exactly matching float32 value. + +Example usage of [Float32Exact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Float32Exact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. + +```go +func TestExpectKnownValue_CheckPlan_Float32(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed float32 attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + knownvalue.Float32Exact(1.23), + ), + }, + }, + }, + }, + }) +} +``` diff --git a/website/docs/plugin/testing/acceptance-tests/known-value-checks/index.mdx b/website/docs/plugin/testing/acceptance-tests/known-value-checks/index.mdx index b18088a94..c27775b93 100644 --- a/website/docs/plugin/testing/acceptance-tests/known-value-checks/index.mdx +++ b/website/docs/plugin/testing/acceptance-tests/known-value-checks/index.mdx @@ -40,7 +40,9 @@ The following table shows the correspondence between [knownvalue.Check](https:// | Known Value Check Type | Framework Attribute Type | SDKv2 Attribute Type | |------------------------------------------------------------------------------------------------------|---------------------------|----------------------| | [Bool Known Value Checks](/terraform/plugin/testing/acceptance-tests/known-value-checks/bool) | `schema.BoolAttribute` | `schema.TypeBool` | +| [Float32 Known Value Checks](/terraform/plugin/testing/acceptance-tests/known-value-checks/float32) | `schema.Float32Attribute` | `schema.TypeFloat` | | [Float64 Known Value Checks](/terraform/plugin/testing/acceptance-tests/known-value-checks/float64) | `schema.Float64Attribute` | `schema.TypeFloat` | +| [Int32 Known Value Checks](/terraform/plugin/testing/acceptance-tests/known-value-checks/int32) | `schema.Int32Attribute` | `schema.TypeInt` | | [Int64 Known Value Checks](/terraform/plugin/testing/acceptance-tests/known-value-checks/int64) | `schema.Int64Attribute` | `schema.TypeInt` | | [List Known Value Checks](/terraform/plugin/testing/acceptance-tests/known-value-checks/list) | `schema.ListAttribute` | `schema.TypeList` | | [Map Known Value Checks](/terraform/plugin/testing/acceptance-tests/known-value-checks/map) | `schema.MapAttribute` | `schema.TypeMap` | diff --git a/website/docs/plugin/testing/acceptance-tests/known-value-checks/int32.mdx b/website/docs/plugin/testing/acceptance-tests/known-value-checks/int32.mdx new file mode 100644 index 000000000..69f1bd7ab --- /dev/null +++ b/website/docs/plugin/testing/acceptance-tests/known-value-checks/int32.mdx @@ -0,0 +1,42 @@ +--- +page_title: 'Plugin Development - Acceptance Testing: Known Values' +description: >- + Int32 Value Checks for use with Plan Checks. +--- + +# Int32 Known Value Checks + +The known value checks that are available for int32 values are: + +* [Int32Exact](/terraform/plugin/testing/acceptance-tests/known-value-checks/int32#int32exact-check) + +## `Int32Exact` Check + +The [Int32Exact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Int32Exact) check tests that a resource attribute, or output value has an exactly matching int32 value. + +Example usage of [Int32Exact](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/knownvalue#Int32Exact) in an [ExpectKnownValue](/terraform/plugin/testing/acceptance-tests/plan-checks/resource) plan check. + +```go +func TestExpectKnownValue_CheckPlan_Int32(t *testing.T) { + t.Parallel() + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + // Example resource containing a computed int32 attribute named "computed_attribute" + Config: `resource "test_resource" "one" {}`, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectKnownValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + knownvalue.Int32Exact(123), + ), + }, + }, + }, + }, + }) +} +``` diff --git a/website/docs/plugin/testing/acceptance-tests/known-value-checks/int64.mdx b/website/docs/plugin/testing/acceptance-tests/known-value-checks/int64.mdx index af647e701..55cc46192 100644 --- a/website/docs/plugin/testing/acceptance-tests/known-value-checks/int64.mdx +++ b/website/docs/plugin/testing/acceptance-tests/known-value-checks/int64.mdx @@ -8,7 +8,7 @@ description: >- The known value checks that are available for int64 values are: -* [Int64Exact](/terraform/plugin/testing/acceptance-tests/known-value-checks/float64#int64exact-check) +* [Int64Exact](/terraform/plugin/testing/acceptance-tests/known-value-checks/int64#int64exact-check) ## `Int64Exact` Check diff --git a/website/docs/plugin/testing/acceptance-tests/tfjson-paths.mdx b/website/docs/plugin/testing/acceptance-tests/tfjson-paths.mdx index e169026d2..a40e90f52 100644 --- a/website/docs/plugin/testing/acceptance-tests/tfjson-paths.mdx +++ b/website/docs/plugin/testing/acceptance-tests/tfjson-paths.mdx @@ -64,7 +64,9 @@ The following table shows the different [`tfjsonpath.Path` type](https://pkg.go. | Framework Attribute Type | SDKv2 Attribute Type | Child Path Method | |---------------------------|----------------------|-------------------| | `schema.BoolAttribute` | `schema.TypeBool` | N/A | +| `schema.Float32Attribute` | `schema.TypeFloat` | N/A | | `schema.Float64Attribute` | `schema.TypeFloat` | N/A | +| `schema.Int32Attribute` | `schema.TypeInt` | N/A | | `schema.Int64Attribute` | `schema.TypeInt` | N/A | | `schema.ListAttribute` | `schema.TypeList` | `AtSliceIndex()` | | `schema.MapAttribute` | `schema.TypeMap` | `AtMapKey()` | From ee0e39639edbce635383678295153c3fd05c9e04 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 5 Jul 2024 16:44:32 -0400 Subject: [PATCH 48/61] build(deps): Bump golang.org/x/crypto from 0.24.0 to 0.25.0 (#358) Bumps [golang.org/x/crypto](https://github.com/golang/crypto) from 0.24.0 to 0.25.0. - [Commits](https://github.com/golang/crypto/compare/v0.24.0...v0.25.0) --- updated-dependencies: - dependency-name: golang.org/x/crypto dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/go.mod b/go.mod index 309ba908d..83494db6a 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0 github.com/mitchellh/go-testing-interface v1.14.1 github.com/zclconf/go-cty v1.14.4 - golang.org/x/crypto v0.24.0 + golang.org/x/crypto v0.25.0 ) require ( @@ -51,7 +51,7 @@ require ( golang.org/x/mod v0.17.0 // indirect golang.org/x/net v0.25.0 // indirect golang.org/x/sync v0.7.0 // indirect - golang.org/x/sys v0.21.0 // indirect + golang.org/x/sys v0.22.0 // indirect golang.org/x/text v0.16.0 // indirect golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d // indirect google.golang.org/appengine v1.6.8 // indirect diff --git a/go.sum b/go.sum index b6abf0ec1..5feb4a5b2 100644 --- a/go.sum +++ b/go.sum @@ -143,8 +143,8 @@ github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940 h1:4r45xpDWB6 github.com/zclconf/go-cty-debug v0.0.0-20240509010212-0d6042c53940/go.mod h1:CmBdvvj3nqzfzJ6nTCIwDTPZ56aVGvDrmztiO5g3qrM= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= -golang.org/x/crypto v0.24.0 h1:mnl8DM0o513X8fdIkmyFE/5hTYxbwYOjDS/+rK6qpRI= -golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= +golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.17.0 h1:zY54UmvipHiNd+pm+m0x9KhZ9hl1/7QNMyxXbc6ICqA= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= @@ -171,12 +171,12 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.21.0 h1:rF+pYz3DAGSQAxAu1CbC7catZg4ebC4UIeIhKxBZvws= -golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= +golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/term v0.21.0 h1:WVXCp+/EBEHOj53Rvu+7KiT/iElMrO8ACK16SMZ3jaA= -golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= +golang.org/x/term v0.22.0 h1:BbsgPEJULsl2fV/AT3v15Mjva5yXKQDyKf+TbDz7QJk= +golang.org/x/term v0.22.0/go.mod h1:F3qCibpT5AMpCRfhfT53vVJwhLtIVHhB9XDjfFvnMI4= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= From 0cec1c679d14280eb34109583a0fbde9c3ea3ed5 Mon Sep 17 00:00:00 2001 From: hc-github-team-tf-provider-devex Date: Tue, 9 Jul 2024 15:39:09 +0000 Subject: [PATCH 49/61] Update changelog --- .changes/1.9.0.md | 7 +++++++ .changes/unreleased/ENHANCEMENTS-20240626-163240.yaml | 5 ----- .changes/unreleased/ENHANCEMENTS-20240626-163311.yaml | 5 ----- CHANGELOG.md | 7 +++++++ 4 files changed, 14 insertions(+), 10 deletions(-) create mode 100644 .changes/1.9.0.md delete mode 100644 .changes/unreleased/ENHANCEMENTS-20240626-163240.yaml delete mode 100644 .changes/unreleased/ENHANCEMENTS-20240626-163311.yaml diff --git a/.changes/1.9.0.md b/.changes/1.9.0.md new file mode 100644 index 000000000..dcbca5418 --- /dev/null +++ b/.changes/1.9.0.md @@ -0,0 +1,7 @@ +## 1.9.0 (July 09, 2024) + +ENHANCEMENTS: + +* knownvalue: Add `Int32Exact` check for int32 value testing. ([#356](https://github.com/hashicorp/terraform-plugin-testing/issues/356)) +* knownvalue: Add `Float32Exact` check for float32 value testing. ([#356](https://github.com/hashicorp/terraform-plugin-testing/issues/356)) + diff --git a/.changes/unreleased/ENHANCEMENTS-20240626-163240.yaml b/.changes/unreleased/ENHANCEMENTS-20240626-163240.yaml deleted file mode 100644 index 7f31ac2ea..000000000 --- a/.changes/unreleased/ENHANCEMENTS-20240626-163240.yaml +++ /dev/null @@ -1,5 +0,0 @@ -kind: ENHANCEMENTS -body: 'knownvalue: Add `Int32Exact` check for int32 value testing.' -time: 2024-06-26T16:32:40.387821-04:00 -custom: - Issue: "356" diff --git a/.changes/unreleased/ENHANCEMENTS-20240626-163311.yaml b/.changes/unreleased/ENHANCEMENTS-20240626-163311.yaml deleted file mode 100644 index b6c29f5ba..000000000 --- a/.changes/unreleased/ENHANCEMENTS-20240626-163311.yaml +++ /dev/null @@ -1,5 +0,0 @@ -kind: ENHANCEMENTS -body: 'knownvalue: Add `Float32Exact` check for float32 value testing.' -time: 2024-06-26T16:33:11.495969-04:00 -custom: - Issue: "356" diff --git a/CHANGELOG.md b/CHANGELOG.md index ab9b8a1ea..97eeed1ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,10 @@ +## 1.9.0 (July 09, 2024) + +ENHANCEMENTS: + +* knownvalue: Add `Int32Exact` check for int32 value testing. ([#356](https://github.com/hashicorp/terraform-plugin-testing/issues/356)) +* knownvalue: Add `Float32Exact` check for float32 value testing. ([#356](https://github.com/hashicorp/terraform-plugin-testing/issues/356)) + ## 1.8.0 (May 17, 2024) FEATURES: From c13ca7b56f5020824a4127255f67f7a4e584c1d7 Mon Sep 17 00:00:00 2001 From: Austin Valle Date: Fri, 17 May 2024 11:42:12 -0400 Subject: [PATCH 50/61] all: Add deferred action testing support (plan checks, version check, and CLI options) (#331) * sloppy first commit! * add version checks and tests * add changelogs * update terraform-plugin-go * spelling fix * update `terraform-json` * switch to pointer bool value --- .changes/unreleased/ENHANCEMENTS-20240503-161709.yaml | 6 ++++++ .changes/unreleased/FEATURES-20240503-161531.yaml | 6 ++++++ .changes/unreleased/FEATURES-20240503-161802.yaml | 6 ++++++ 3 files changed, 18 insertions(+) create mode 100644 .changes/unreleased/ENHANCEMENTS-20240503-161709.yaml create mode 100644 .changes/unreleased/FEATURES-20240503-161531.yaml create mode 100644 .changes/unreleased/FEATURES-20240503-161802.yaml diff --git a/.changes/unreleased/ENHANCEMENTS-20240503-161709.yaml b/.changes/unreleased/ENHANCEMENTS-20240503-161709.yaml new file mode 100644 index 000000000..dff8d4adf --- /dev/null +++ b/.changes/unreleased/ENHANCEMENTS-20240503-161709.yaml @@ -0,0 +1,6 @@ +kind: ENHANCEMENTS +body: 'helper/resource: Added `(TestCase).AdditionalCLIOptions` with `AllowDeferral` + option for plan and apply commands.' +time: 2024-05-03T16:17:09.64792-04:00 +custom: + Issue: "331" diff --git a/.changes/unreleased/FEATURES-20240503-161531.yaml b/.changes/unreleased/FEATURES-20240503-161531.yaml new file mode 100644 index 000000000..edd8c036f --- /dev/null +++ b/.changes/unreleased/FEATURES-20240503-161531.yaml @@ -0,0 +1,6 @@ +kind: FEATURES +body: 'plancheck: Added `ExpectDeferredChange` and `ExpectNoDeferredChanges` checks + for experimental deferred action support.' +time: 2024-05-03T16:15:31.03438-04:00 +custom: + Issue: "331" diff --git a/.changes/unreleased/FEATURES-20240503-161802.yaml b/.changes/unreleased/FEATURES-20240503-161802.yaml new file mode 100644 index 000000000..a06f5f78c --- /dev/null +++ b/.changes/unreleased/FEATURES-20240503-161802.yaml @@ -0,0 +1,6 @@ +kind: FEATURES +body: 'tfversion: Added `SkipIfNotPrerelease` version check for testing experimental + features of prerelease Terraform builds.' +time: 2024-05-03T16:18:02.132794-04:00 +custom: + Issue: "331" From 167bc62df5bc02c408b1c93773fecbc72bdcf8c0 Mon Sep 17 00:00:00 2001 From: hc-github-team-tf-provider-devex Date: Fri, 17 May 2024 15:49:06 +0000 Subject: [PATCH 51/61] Update changelog --- .changes/unreleased/ENHANCEMENTS-20240503-161709.yaml | 6 ------ .changes/unreleased/FEATURES-20240503-161531.yaml | 6 ------ .changes/unreleased/FEATURES-20240503-161802.yaml | 6 ------ 3 files changed, 18 deletions(-) delete mode 100644 .changes/unreleased/ENHANCEMENTS-20240503-161709.yaml delete mode 100644 .changes/unreleased/FEATURES-20240503-161531.yaml delete mode 100644 .changes/unreleased/FEATURES-20240503-161802.yaml diff --git a/.changes/unreleased/ENHANCEMENTS-20240503-161709.yaml b/.changes/unreleased/ENHANCEMENTS-20240503-161709.yaml deleted file mode 100644 index dff8d4adf..000000000 --- a/.changes/unreleased/ENHANCEMENTS-20240503-161709.yaml +++ /dev/null @@ -1,6 +0,0 @@ -kind: ENHANCEMENTS -body: 'helper/resource: Added `(TestCase).AdditionalCLIOptions` with `AllowDeferral` - option for plan and apply commands.' -time: 2024-05-03T16:17:09.64792-04:00 -custom: - Issue: "331" diff --git a/.changes/unreleased/FEATURES-20240503-161531.yaml b/.changes/unreleased/FEATURES-20240503-161531.yaml deleted file mode 100644 index edd8c036f..000000000 --- a/.changes/unreleased/FEATURES-20240503-161531.yaml +++ /dev/null @@ -1,6 +0,0 @@ -kind: FEATURES -body: 'plancheck: Added `ExpectDeferredChange` and `ExpectNoDeferredChanges` checks - for experimental deferred action support.' -time: 2024-05-03T16:15:31.03438-04:00 -custom: - Issue: "331" diff --git a/.changes/unreleased/FEATURES-20240503-161802.yaml b/.changes/unreleased/FEATURES-20240503-161802.yaml deleted file mode 100644 index a06f5f78c..000000000 --- a/.changes/unreleased/FEATURES-20240503-161802.yaml +++ /dev/null @@ -1,6 +0,0 @@ -kind: FEATURES -body: 'tfversion: Added `SkipIfNotPrerelease` version check for testing experimental - features of prerelease Terraform builds.' -time: 2024-05-03T16:18:02.132794-04:00 -custom: - Issue: "331" From 829c50a7daa11f10518f84f55f059b9547038cc0 Mon Sep 17 00:00:00 2001 From: Service Account - Terraform Provider DevEx <100357958+hc-github-team-tf-provider-devex@users.noreply.github.com> Date: Tue, 4 Jun 2024 10:09:44 -0400 Subject: [PATCH 52/61] [CI] terraform-devex-repos automation From a1da161ca0d85346b4444a84be0e8dce62526d30 Mon Sep 17 00:00:00 2001 From: Service Account - Terraform Provider DevEx <100357958+hc-github-team-tf-provider-devex@users.noreply.github.com> Date: Fri, 7 Jun 2024 16:21:55 -0400 Subject: [PATCH 53/61] [CI] terraform-devex-repos automation From 2c3375e55c530648a72b07b53480651631043e09 Mon Sep 17 00:00:00 2001 From: Selena Goods Date: Mon, 1 Jul 2024 12:57:15 -0400 Subject: [PATCH 54/61] knownvalue: Add `Int32Exact` and `Float32Exact` checks (#356) * Add `float32exact` and `int32exact` types * Update website documentation * Add changelog entries * Update changelog wording --- .changes/unreleased/ENHANCEMENTS-20240626-163240.yaml | 5 +++++ .changes/unreleased/ENHANCEMENTS-20240626-163311.yaml | 5 +++++ 2 files changed, 10 insertions(+) create mode 100644 .changes/unreleased/ENHANCEMENTS-20240626-163240.yaml create mode 100644 .changes/unreleased/ENHANCEMENTS-20240626-163311.yaml diff --git a/.changes/unreleased/ENHANCEMENTS-20240626-163240.yaml b/.changes/unreleased/ENHANCEMENTS-20240626-163240.yaml new file mode 100644 index 000000000..7f31ac2ea --- /dev/null +++ b/.changes/unreleased/ENHANCEMENTS-20240626-163240.yaml @@ -0,0 +1,5 @@ +kind: ENHANCEMENTS +body: 'knownvalue: Add `Int32Exact` check for int32 value testing.' +time: 2024-06-26T16:32:40.387821-04:00 +custom: + Issue: "356" diff --git a/.changes/unreleased/ENHANCEMENTS-20240626-163311.yaml b/.changes/unreleased/ENHANCEMENTS-20240626-163311.yaml new file mode 100644 index 000000000..b6c29f5ba --- /dev/null +++ b/.changes/unreleased/ENHANCEMENTS-20240626-163311.yaml @@ -0,0 +1,5 @@ +kind: ENHANCEMENTS +body: 'knownvalue: Add `Float32Exact` check for float32 value testing.' +time: 2024-06-26T16:33:11.495969-04:00 +custom: + Issue: "356" From 07a62c3ba4ae03b92886073b1bd8220fac4e2c96 Mon Sep 17 00:00:00 2001 From: hc-github-team-tf-provider-devex Date: Tue, 9 Jul 2024 15:39:09 +0000 Subject: [PATCH 55/61] Update changelog --- .changes/unreleased/ENHANCEMENTS-20240626-163240.yaml | 5 ----- .changes/unreleased/ENHANCEMENTS-20240626-163311.yaml | 5 ----- 2 files changed, 10 deletions(-) delete mode 100644 .changes/unreleased/ENHANCEMENTS-20240626-163240.yaml delete mode 100644 .changes/unreleased/ENHANCEMENTS-20240626-163311.yaml diff --git a/.changes/unreleased/ENHANCEMENTS-20240626-163240.yaml b/.changes/unreleased/ENHANCEMENTS-20240626-163240.yaml deleted file mode 100644 index 7f31ac2ea..000000000 --- a/.changes/unreleased/ENHANCEMENTS-20240626-163240.yaml +++ /dev/null @@ -1,5 +0,0 @@ -kind: ENHANCEMENTS -body: 'knownvalue: Add `Int32Exact` check for int32 value testing.' -time: 2024-06-26T16:32:40.387821-04:00 -custom: - Issue: "356" diff --git a/.changes/unreleased/ENHANCEMENTS-20240626-163311.yaml b/.changes/unreleased/ENHANCEMENTS-20240626-163311.yaml deleted file mode 100644 index b6c29f5ba..000000000 --- a/.changes/unreleased/ENHANCEMENTS-20240626-163311.yaml +++ /dev/null @@ -1,5 +0,0 @@ -kind: ENHANCEMENTS -body: 'knownvalue: Add `Float32Exact` check for float32 value testing.' -time: 2024-06-26T16:33:11.495969-04:00 -custom: - Issue: "356" From 546be85402147356ba23270e1688340d9af9e2cf Mon Sep 17 00:00:00 2001 From: Austin Valle Date: Tue, 16 Jul 2024 15:23:50 -0400 Subject: [PATCH 56/61] quick doc fix --- .../testing/acceptance-tests/known-value-checks/index.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/docs/plugin/testing/acceptance-tests/known-value-checks/index.mdx b/website/docs/plugin/testing/acceptance-tests/known-value-checks/index.mdx index c27775b93..569f6b5b6 100644 --- a/website/docs/plugin/testing/acceptance-tests/known-value-checks/index.mdx +++ b/website/docs/plugin/testing/acceptance-tests/known-value-checks/index.mdx @@ -13,8 +13,8 @@ Known Value Checks are for use in conjunction with [Plan Checks](/terraform/plug Example uses in the testing module include: -- **Plan Checks**: The [`ExpectknownValue()`](/terraform/plugin/testing/acceptance-tests/plan-checks/resource#expectknownvalue-plan-check), [`ExpectKnownOutputValue()`](/terraform/plugin/testing/acceptance-tests/plan-checks/output#expectknownoutputvalue-plan-check) and [`ExpectKnownOutputValueAtPath()`](/terraform/plugin/testing/acceptance-tests/plan-checks/output#expectknownoutputvalueatpath-plan-check) [built-in plan checks](/terraform/plugin/testing/acceptance-tests/plan-checks) use known value checks for asserting whether a specific resource attribute, or output value has a particular type, and value. -- **State Checks**: The [`ExpectknownValue()`](/terraform/plugin/testing/acceptance-tests/state-checks/resource#expectknownvalue-state-check), [`ExpectKnownOutputValue()`](/terraform/plugin/testing/acceptance-tests/state-checks/output#expectknownoutputvalue-state-check) and [`ExpectKnownOutputValueAtPath()`](/terraform/plugin/testing/acceptance-tests/state-checks/output#expectknownoutputvalueatpath-state-check) [built-in state checks](/terraform/plugin/testing/acceptance-tests/state-checks) use known value checks for asserting whether a specific resource attribute, or output value has a particular type, and value. +- **Plan Checks**: The [`ExpectKnownValue()`](/terraform/plugin/testing/acceptance-tests/plan-checks/resource#expectknownvalue-plan-check), [`ExpectKnownOutputValue()`](/terraform/plugin/testing/acceptance-tests/plan-checks/output#expectknownoutputvalue-plan-check) and [`ExpectKnownOutputValueAtPath()`](/terraform/plugin/testing/acceptance-tests/plan-checks/output#expectknownoutputvalueatpath-plan-check) [built-in plan checks](/terraform/plugin/testing/acceptance-tests/plan-checks) use known value checks for asserting whether a specific resource attribute, or output value has a particular type, and value. +- **State Checks**: The [`ExpectKnownValue()`](/terraform/plugin/testing/acceptance-tests/state-checks/resource#expectknownvalue-state-check), [`ExpectKnownOutputValue()`](/terraform/plugin/testing/acceptance-tests/state-checks/output#expectknownoutputvalue-state-check) and [`ExpectKnownOutputValueAtPath()`](/terraform/plugin/testing/acceptance-tests/state-checks/output#expectknownoutputvalueatpath-state-check) [built-in state checks](/terraform/plugin/testing/acceptance-tests/state-checks) use known value checks for asserting whether a specific resource attribute, or output value has a particular type, and value. ## Using a Known Value Check From eff8125988a1cb26aa3ae12dc4453cd2ac45cc81 Mon Sep 17 00:00:00 2001 From: Austin Valle Date: Wed, 17 Jul 2024 09:34:57 -0400 Subject: [PATCH 57/61] added invalid test case --- statecheck/compare_value_test.go | 34 ++++++++++++++++++++++++++++---- 1 file changed, 30 insertions(+), 4 deletions(-) diff --git a/statecheck/compare_value_test.go b/statecheck/compare_value_test.go index 0035178d4..81f6c8e0d 100644 --- a/statecheck/compare_value_test.go +++ b/statecheck/compare_value_test.go @@ -15,7 +15,33 @@ import ( "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" ) -func TestCompareValue_CheckState_Bool_ValuesSame_ValueDiffersError(t *testing.T) { +func TestCompareValue_CheckState_NoStateValues(t *testing.T) { + t.Parallel() + + boolValuesDiffer := statecheck.CompareValue(compare.ValuesSame()) + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + bool_attribute = true + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + boolValuesDiffer, + }, + ExpectError: regexp.MustCompile(`resource addresses index out of bounds: 0`), + }, + }, + }) +} + +func TestCompareValue_CheckState_ValuesSame_ValueDiffersError(t *testing.T) { t.Parallel() boolValuesDiffer := statecheck.CompareValue(compare.ValuesSame()) @@ -68,7 +94,7 @@ func TestCompareValue_CheckState_Bool_ValuesSame_ValueDiffersError(t *testing.T) }) } -func TestCompareValue_CheckState_Bool_ValuesSame(t *testing.T) { +func TestCompareValue_CheckState_ValuesSame(t *testing.T) { t.Parallel() boolValuesDiffer := statecheck.CompareValue(compare.ValuesSame()) @@ -108,7 +134,7 @@ func TestCompareValue_CheckState_Bool_ValuesSame(t *testing.T) { }) } -func TestCompareValue_CheckState_Bool_ValuesDiffer_ValueSameError(t *testing.T) { +func TestCompareValue_CheckState_ValuesDiffer_ValueSameError(t *testing.T) { t.Parallel() boolValuesDiffer := statecheck.CompareValue(compare.ValuesDiffer()) @@ -161,7 +187,7 @@ func TestCompareValue_CheckState_Bool_ValuesDiffer_ValueSameError(t *testing.T) }) } -func TestCompareValue_CheckState_Bool_ValuesDiffer(t *testing.T) { +func TestCompareValue_CheckState_ValuesDiffer(t *testing.T) { t.Parallel() boolValuesDiffer := statecheck.CompareValue(compare.ValuesDiffer()) From b57949f2f71cb62de22130c4f9a2f5c1a5457646 Mon Sep 17 00:00:00 2001 From: Austin Valle Date: Wed, 17 Jul 2024 15:52:05 -0400 Subject: [PATCH 58/61] adjust compare value collection to work with nested attributes --- statecheck/compare_value_collection.go | 16 +- statecheck/compare_value_collection_test.go | 365 ++++++++++++++++++++ statecheck/compare_value_test.go | 1 + 3 files changed, 373 insertions(+), 9 deletions(-) diff --git a/statecheck/compare_value_collection.go b/statecheck/compare_value_collection.go index 4bbc08d1a..7a06c6010 100644 --- a/statecheck/compare_value_collection.go +++ b/statecheck/compare_value_collection.go @@ -35,10 +35,6 @@ func walkCollectionPath(obj any, paths []tfjsonpath.Path, results []any) ([]any, continue } - if len(paths) == 0 { - break - } - x, err := tfjsonpath.Traverse(v, paths[0]) if err != nil { @@ -66,11 +62,7 @@ func walkCollectionPath(obj any, paths []tfjsonpath.Path, results []any) ([]any, continue } - if len(paths) == 0 { - break - } - - x, err := tfjsonpath.Traverse(t[key], paths[0]) + x, err := tfjsonpath.Traverse(t, paths[0]) if err != nil { return results, err @@ -126,6 +118,12 @@ func (e *compareValueCollection) CheckState(ctx context.Context, req CheckStateR return } + if len(e.collectionPath) == 0 { + resp.Error = fmt.Errorf("%s - No collection path was provided", e.resourceAddressOne) + + return + } + resultOne, err := tfjsonpath.Traverse(resourceOne.AttributeValues, e.collectionPath[0]) if err != nil { diff --git a/statecheck/compare_value_collection_test.go b/statecheck/compare_value_collection_test.go index fffbc7a92..5145bc65d 100644 --- a/statecheck/compare_value_collection_test.go +++ b/statecheck/compare_value_collection_test.go @@ -7,10 +7,15 @@ import ( "regexp" "testing" + "github.com/hashicorp/terraform-plugin-go/tfprotov6" + "github.com/hashicorp/terraform-plugin-go/tftypes" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "github.com/hashicorp/terraform-plugin-testing/compare" r "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/internal/testing/testprovider" + "github.com/hashicorp/terraform-plugin-testing/internal/testing/testsdk/providerserver" + "github.com/hashicorp/terraform-plugin-testing/internal/testing/testsdk/resource" "github.com/hashicorp/terraform-plugin-testing/statecheck" "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" ) @@ -162,6 +167,44 @@ func TestCompareValueCollection_CheckState_List_ValuesSame_ErrorDiffer(t *testin }) } +func TestCompareValueCollection_CheckState_EmptyCollectionPath(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + ProviderFactories: map[string]func() (*schema.Provider, error){ + "test": func() (*schema.Provider, error) { //nolint:unparam // required signature + return testProvider(), nil + }, + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + string_attribute = "str" + } + + resource "test_resource" "two" { + list_attribute = [ + "str2", + "str", + ] + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValueCollection( + "test_resource.two", + // Empty path is invalid + []tfjsonpath.Path{}, + "test_resource.one", + tfjsonpath.New("string_attribute"), + compare.ValuesSame(), + ), + }, + ExpectError: regexp.MustCompile("test_resource.two - No collection path was provided"), + }, + }, + }) +} + func TestCompareValueCollection_CheckState_List_ValuesSame(t *testing.T) { t.Parallel() @@ -1612,3 +1655,325 @@ func TestCompareValueCollection_CheckState_String_Error_NotCollection(t *testing }, }) } + +func TestCompareValueCollection_CheckState_ListNestedAttribute_ValuesSame(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "test": providerserver.NewProviderServer(testprovider.Provider{ + Resources: map[string]testprovider.Resource{ + "test_resource": { + SchemaResponse: &resource.SchemaResponse{ + Schema: &tfprotov6.Schema{ + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "str_attr", + Type: tftypes.String, + Optional: true, + }, + { + Name: "nested_attr", + NestedType: &tfprotov6.SchemaObject{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "str_attr", + Type: tftypes.String, + Optional: true, + }, + }, + Nesting: tfprotov6.SchemaObjectNestingModeList, + }, + Optional: true, + }, + }, + }, + }, + }, + }, + }, + }), + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + str_attr = "str2" + } + resource "test_resource" "two" { + nested_attr = [ + { + str_attr = "str1" + }, + { + str_attr = "str2" + } + ] + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValueCollection( + "test_resource.two", + []tfjsonpath.Path{ + tfjsonpath.New("nested_attr"), + tfjsonpath.New("str_attr"), + }, + "test_resource.one", + tfjsonpath.New("str_attr"), + compare.ValuesSame(), + ), + }, + }, + }, + }) +} + +func TestCompareValueCollection_CheckState_ListNestedAttribute_ValuesSame_ErrorDiff(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "test": providerserver.NewProviderServer(testprovider.Provider{ + Resources: map[string]testprovider.Resource{ + "test_resource": { + SchemaResponse: &resource.SchemaResponse{ + Schema: &tfprotov6.Schema{ + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "str_attr", + Type: tftypes.String, + Optional: true, + }, + { + Name: "nested_attr", + NestedType: &tfprotov6.SchemaObject{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "str_attr", + Type: tftypes.String, + Optional: true, + }, + }, + Nesting: tfprotov6.SchemaObjectNestingModeList, + }, + Optional: true, + }, + }, + }, + }, + }, + }, + }, + }), + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + str_attr = "str1" + } + resource "test_resource" "two" { + nested_attr = [ + { + str_attr = "str2" + }, + { + str_attr = "str3" + } + ] + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValueCollection( + "test_resource.two", + []tfjsonpath.Path{ + tfjsonpath.New("nested_attr"), + tfjsonpath.New("str_attr"), + }, + "test_resource.one", + tfjsonpath.New("str_attr"), + compare.ValuesSame(), + ), + }, + ExpectError: regexp.MustCompile("expected values to be the same, but they differ: str2 != str1\nexpected values to be the same, but they differ: str3 != str1"), + }, + }, + }) +} + +func TestCompareValueCollection_CheckState_DoubleListNestedAttribute_ValuesSame(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "test": providerserver.NewProviderServer(testprovider.Provider{ + Resources: map[string]testprovider.Resource{ + "test_resource": { + SchemaResponse: &resource.SchemaResponse{ + Schema: &tfprotov6.Schema{ + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "str_attr", + Type: tftypes.String, + Optional: true, + }, + { + Name: "nested_attr", + NestedType: &tfprotov6.SchemaObject{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "double_nested_attr", + NestedType: &tfprotov6.SchemaObject{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "str_attr", + Type: tftypes.String, + Optional: true, + }, + }, + Nesting: tfprotov6.SchemaObjectNestingModeSingle, + }, + Optional: true, + }, + }, + Nesting: tfprotov6.SchemaObjectNestingModeList, + }, + Optional: true, + }, + }, + }, + }, + }, + }, + }, + }), + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + str_attr = "str2" + } + resource "test_resource" "two" { + nested_attr = [ + { + double_nested_attr = { + str_attr = "str1" + } + }, + { + double_nested_attr = { + str_attr = "str2" + } + } + ] + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValueCollection( + "test_resource.two", + []tfjsonpath.Path{ + tfjsonpath.New("nested_attr"), + tfjsonpath.New("double_nested_attr"), + tfjsonpath.New("str_attr"), + }, + "test_resource.one", + tfjsonpath.New("str_attr"), + compare.ValuesSame(), + ), + }, + }, + }, + }) +} + +func TestCompareValueCollection_CheckState_DoubleListNestedAttribute_ValuesSame_ErrorDiff(t *testing.T) { + t.Parallel() + + r.Test(t, r.TestCase{ + + ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ + "test": providerserver.NewProviderServer(testprovider.Provider{ + Resources: map[string]testprovider.Resource{ + "test_resource": { + SchemaResponse: &resource.SchemaResponse{ + Schema: &tfprotov6.Schema{ + Block: &tfprotov6.SchemaBlock{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "str_attr", + Type: tftypes.String, + Optional: true, + }, + { + Name: "nested_attr", + NestedType: &tfprotov6.SchemaObject{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "double_nested_attr", + NestedType: &tfprotov6.SchemaObject{ + Attributes: []*tfprotov6.SchemaAttribute{ + { + Name: "str_attr", + Type: tftypes.String, + Optional: true, + }, + }, + Nesting: tfprotov6.SchemaObjectNestingModeSingle, + }, + Optional: true, + }, + }, + Nesting: tfprotov6.SchemaObjectNestingModeList, + }, + Optional: true, + }, + }, + }, + }, + }, + }, + }, + }), + }, + Steps: []r.TestStep{ + { + Config: `resource "test_resource" "one" { + str_attr = "str1" + } + resource "test_resource" "two" { + nested_attr = [ + { + double_nested_attr = { + str_attr = "str2" + } + }, + { + double_nested_attr = { + str_attr = "str3" + } + } + ] + } + `, + ConfigStateChecks: []statecheck.StateCheck{ + statecheck.CompareValueCollection( + "test_resource.two", + []tfjsonpath.Path{ + tfjsonpath.New("nested_attr"), + tfjsonpath.New("double_nested_attr"), + tfjsonpath.New("str_attr"), + }, + "test_resource.one", + tfjsonpath.New("str_attr"), + compare.ValuesSame(), + ), + }, + ExpectError: regexp.MustCompile("expected values to be the same, but they differ: str2 != str1\nexpected values to be the same, but they differ: str3 != str1"), + }, + }, + }) +} diff --git a/statecheck/compare_value_test.go b/statecheck/compare_value_test.go index 81f6c8e0d..271692425 100644 --- a/statecheck/compare_value_test.go +++ b/statecheck/compare_value_test.go @@ -33,6 +33,7 @@ func TestCompareValue_CheckState_NoStateValues(t *testing.T) { } `, ConfigStateChecks: []statecheck.StateCheck{ + // No state values have been added boolValuesDiffer, }, ExpectError: regexp.MustCompile(`resource addresses index out of bounds: 0`), From d745fb0c9f3e970d77f3885dd49bbaf9adb9d7de Mon Sep 17 00:00:00 2001 From: Austin Valle Date: Wed, 17 Jul 2024 16:50:05 -0400 Subject: [PATCH 59/61] add changelogs --- .changes/unreleased/FEATURES-20240717-155731.yaml | 6 ++++++ .changes/unreleased/FEATURES-20240717-160116.yaml | 6 ++++++ .changes/unreleased/FEATURES-20240717-160331.yaml | 7 +++++++ .changes/unreleased/FEATURES-20240717-164418.yaml | 7 +++++++ .changes/unreleased/NOTES-20240717-155810.yaml | 6 ++++++ .changes/unreleased/NOTES-20240717-164911.yaml | 7 +++++++ 6 files changed, 39 insertions(+) create mode 100644 .changes/unreleased/FEATURES-20240717-155731.yaml create mode 100644 .changes/unreleased/FEATURES-20240717-160116.yaml create mode 100644 .changes/unreleased/FEATURES-20240717-160331.yaml create mode 100644 .changes/unreleased/FEATURES-20240717-164418.yaml create mode 100644 .changes/unreleased/NOTES-20240717-155810.yaml create mode 100644 .changes/unreleased/NOTES-20240717-164911.yaml diff --git a/.changes/unreleased/FEATURES-20240717-155731.yaml b/.changes/unreleased/FEATURES-20240717-155731.yaml new file mode 100644 index 000000000..6f06b4eb9 --- /dev/null +++ b/.changes/unreleased/FEATURES-20240717-155731.yaml @@ -0,0 +1,6 @@ +kind: FEATURES +body: 'compare: Introduced new `compare` package, which contains interfaces and implementations + for value comparisons in state checks.' +time: 2024-07-17T15:57:31.637692-04:00 +custom: + Issue: "330" diff --git a/.changes/unreleased/FEATURES-20240717-160116.yaml b/.changes/unreleased/FEATURES-20240717-160116.yaml new file mode 100644 index 000000000..6783377d1 --- /dev/null +++ b/.changes/unreleased/FEATURES-20240717-160116.yaml @@ -0,0 +1,6 @@ +kind: FEATURES +body: 'statecheck: Added `CompareValue` state check, which compares sequential values of the + specified attribute at the given managed resource, or data source, using the supplied value comparer.' +time: 2024-07-17T16:01:16.194665-04:00 +custom: + Issue: "330" diff --git a/.changes/unreleased/FEATURES-20240717-160331.yaml b/.changes/unreleased/FEATURES-20240717-160331.yaml new file mode 100644 index 000000000..1187e32a3 --- /dev/null +++ b/.changes/unreleased/FEATURES-20240717-160331.yaml @@ -0,0 +1,7 @@ +kind: FEATURES +body: 'statecheck: Added `CompareValueCollection` state check, which compares each item in + the specified collection (e.g., list, set) attribute, with the second specified + attribute at the given managed resources, or data sources, using the supplied value comparer.' +time: 2024-07-17T16:03:31.77827-04:00 +custom: + Issue: "330" diff --git a/.changes/unreleased/FEATURES-20240717-164418.yaml b/.changes/unreleased/FEATURES-20240717-164418.yaml new file mode 100644 index 000000000..312311b1f --- /dev/null +++ b/.changes/unreleased/FEATURES-20240717-164418.yaml @@ -0,0 +1,7 @@ +kind: FEATURES +body: 'statecheck: Added `CompareValuePairs` state check, which compares the + specified attributes at the given managed resources, or data sources, using + the supplied value comparer.' +time: 2024-07-17T16:44:18.612874-04:00 +custom: + Issue: "330" diff --git a/.changes/unreleased/NOTES-20240717-155810.yaml b/.changes/unreleased/NOTES-20240717-155810.yaml new file mode 100644 index 000000000..a8edd41c4 --- /dev/null +++ b/.changes/unreleased/NOTES-20240717-155810.yaml @@ -0,0 +1,6 @@ +kind: NOTES +body: 'compare: The `compare` package is considered experimental and may be altered + or removed in a subsequent release' +time: 2024-07-17T15:58:10.435384-04:00 +custom: + Issue: "330" diff --git a/.changes/unreleased/NOTES-20240717-164911.yaml b/.changes/unreleased/NOTES-20240717-164911.yaml new file mode 100644 index 000000000..bc3af510b --- /dev/null +++ b/.changes/unreleased/NOTES-20240717-164911.yaml @@ -0,0 +1,7 @@ +kind: NOTES +body: 'statecheck: `CompareValue`, `CompareValueCollection`, and `CompareValuePairs` + state checks are considered experimental and may be altered or removed in a subsequent + release.' +time: 2024-07-17T16:49:11.296585-04:00 +custom: + Issue: "330" From d61aac9ee8d2fbef20397eda2dfc2e0d3248a3b5 Mon Sep 17 00:00:00 2001 From: Austin Valle Date: Wed, 17 Jul 2024 16:56:52 -0400 Subject: [PATCH 60/61] add tfversion skip for protov6 --- statecheck/compare_value_collection_test.go | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/statecheck/compare_value_collection_test.go b/statecheck/compare_value_collection_test.go index 5145bc65d..31cdc9ec0 100644 --- a/statecheck/compare_value_collection_test.go +++ b/statecheck/compare_value_collection_test.go @@ -18,6 +18,7 @@ import ( "github.com/hashicorp/terraform-plugin-testing/internal/testing/testsdk/resource" "github.com/hashicorp/terraform-plugin-testing/statecheck" "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" + "github.com/hashicorp/terraform-plugin-testing/tfversion" ) func TestCompareValueCollection_CheckState_Bool_Error_NotCollection(t *testing.T) { @@ -1660,7 +1661,9 @@ func TestCompareValueCollection_CheckState_ListNestedAttribute_ValuesSame(t *tes t.Parallel() r.Test(t, r.TestCase{ - + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_0_0), // Nested attributes only available in protocol version 6 + }, ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ "test": providerserver.NewProviderServer(testprovider.Provider{ Resources: map[string]testprovider.Resource{ @@ -1733,7 +1736,9 @@ func TestCompareValueCollection_CheckState_ListNestedAttribute_ValuesSame_ErrorD t.Parallel() r.Test(t, r.TestCase{ - + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_0_0), // Nested attributes only available in protocol version 6 + }, ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ "test": providerserver.NewProviderServer(testprovider.Provider{ Resources: map[string]testprovider.Resource{ @@ -1807,7 +1812,9 @@ func TestCompareValueCollection_CheckState_DoubleListNestedAttribute_ValuesSame( t.Parallel() r.Test(t, r.TestCase{ - + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_0_0), // Nested attributes only available in protocol version 6 + }, ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ "test": providerserver.NewProviderServer(testprovider.Provider{ Resources: map[string]testprovider.Resource{ @@ -1894,7 +1901,9 @@ func TestCompareValueCollection_CheckState_DoubleListNestedAttribute_ValuesSame_ t.Parallel() r.Test(t, r.TestCase{ - + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.SkipBelow(tfversion.Version1_0_0), // Nested attributes only available in protocol version 6 + }, ProtoV6ProviderFactories: map[string]func() (tfprotov6.ProviderServer, error){ "test": providerserver.NewProviderServer(testprovider.Provider{ Resources: map[string]testprovider.Resource{ From bcdd810e0d349b42e698f186d342ba87fb8b42a8 Mon Sep 17 00:00:00 2001 From: Austin Valle Date: Wed, 7 Aug 2024 11:57:51 -0400 Subject: [PATCH 61/61] update example for value comparers --- .../testing/acceptance-tests/testcase.mdx | 2 +- .../value-comparers/index.mdx | 215 +++++++++++------- website/docs/plugin/testing/index.mdx | 4 - 3 files changed, 134 insertions(+), 87 deletions(-) diff --git a/website/docs/plugin/testing/acceptance-tests/testcase.mdx b/website/docs/plugin/testing/acceptance-tests/testcase.mdx index 57ddd888f..77ba53078 100644 --- a/website/docs/plugin/testing/acceptance-tests/testcase.mdx +++ b/website/docs/plugin/testing/acceptance-tests/testcase.mdx @@ -157,7 +157,7 @@ but before any test steps are executed. The [Terraform Version Checks](/terrafor are generic checks that check logic against the Terraform CLI version and can immediately pass or fail a test before any test steps are executed. -The tfversion package provides built-in checks for common scenarios. +The [`tfversion`](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/tfversion) package provides built-in checks for common scenarios. **Example usage:** diff --git a/website/docs/plugin/testing/acceptance-tests/value-comparers/index.mdx b/website/docs/plugin/testing/acceptance-tests/value-comparers/index.mdx index 2714e302d..3ef5e19d6 100644 --- a/website/docs/plugin/testing/acceptance-tests/value-comparers/index.mdx +++ b/website/docs/plugin/testing/acceptance-tests/value-comparers/index.mdx @@ -7,25 +7,70 @@ description: >- # Value Comparers -Value Comparers are for use in conjunction with [State Checks](/terraform/plugin/testing/acceptance-tests/state-checks), which leverage the [terraform-json](https://pkg.go.dev/github.com/hashicorp/terraform-json) representation of Terraform state. - -## Usage + -Example uses in the testing module include: - -- The [`CompareValue()`](/terraform/plugin/testing/acceptance-tests/state-checks/resource#comparevalue-state-check), [`CompareValueCollection()`](/terraform/plugin/testing/acceptance-tests/state-checks/resource#comparevaluecollection-state-check) and [`CompareValuePairs()`](/terraform/plugin/testing/acceptance-tests/state-checks/resource#comparevaluepairs-state-check) [built-in state checks](/terraform/plugin/testing/acceptance-tests/state-checks) use value comparers for comparing specific resource attribute, or output values. +Value Comparers are for use in conjunction with [State Checks](/terraform/plugin/testing/acceptance-tests/state-checks), which leverage the [terraform-json](https://pkg.go.dev/github.com/hashicorp/terraform-json) representation of Terraform state. -## Using a Value Comparer + -The value comparer types are implemented within the `terraform-plugin-testing` module in the [`compare` package](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/compare). Value comparers are instantiated by calling the relevant constructor function. +Value comparers can be used to assert a resource or data source attribute value across multiple [Test Steps](/terraform/plugin/testing/acceptance-tests/teststep), like asserting that a randomly generated resource attribute doesn't change after multiple apply steps. This is done by creating the value comparer, typically before the test case is defined, using the relevant constructor function: +```go +func TestExample(t *testing.T) { + // Create the value comparer so we can add state values to it during the test steps + compareValuesDiffer := statecheck.CompareValue(compare.ValuesDiffer()) + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + // .. test steps omitted + }, + }) +} +``` +Once the value comparer is created, state values can be added in `TestStep.ConfigStateChecks`: ```go -compare.ValuesDiffer() +func TestExample(t *testing.T) { + // Create the value comparer so we can add state values to it during the test steps + compareValuesDiffer := statecheck.CompareValue(compare.ValuesDiffer()) + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + Config: `resource "test_resource" "one" {}`, + ConfigStateChecks: []statecheck.StateCheck{ + // Add the current state value of "computed_attribute" to the value comparer. + // Since there are no other state values at this point, no assertion is made. + compareValuesDiffer.AddStateValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + ), + }, + }, + { + Config: `resource "test_resource" "one" {}`, + ConfigStateChecks: []statecheck.StateCheck{ + // Add the current state value of "computed_attribute" to the value comparer. + // Since there is an existing state value in the value comparer at this point, + // if the two values are equal, the test will produce an error. + compareValuesDiffer.AddStateValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + ), + }, + }, + }, + }) +} ``` -The value comparer types implement the [`ValueComparer` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/compare#ValueComparer). The `CompareValues()` method accepts a variadic argument of type `any`, which allows for comparison of arbitrary data structures. +The value comparer implementation (defined by the [`ValueComparer` interface](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/compare#ValueComparer)) determines what assertion occurs when a state value is added. The built-in value comparers are: +- [`CompareValue()`](/terraform/plugin/testing/acceptance-tests/state-checks/resource#comparevalue-state-check) +- [`CompareValueCollection()`](/terraform/plugin/testing/acceptance-tests/state-checks/resource#comparevaluecollection-state-check) +- [`CompareValuePairs()`](/terraform/plugin/testing/acceptance-tests/state-checks/resource#comparevaluepairs-state-check) -## Values Differ Comparer Type +## Values Differ The [ValuesDiffer](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/compare#ValuesDiffer) value comparer verifies that each value in the sequence of values supplied to the `CompareValues()` method differs from the preceding value. @@ -35,50 +80,53 @@ Example usage of [ValuesDiffer](https://pkg.go.dev/github.com/hashicorp/terrafor package example_test import ( - "testing" + "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-plugin-testing/compare" - "github.com/hashicorp/terraform-plugin-testing/helper/resource" - "github.com/hashicorp/terraform-plugin-testing/statecheck" - "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" + "github.com/hashicorp/terraform-plugin-testing/compare" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" ) func TestCompareValue_CheckState_ValuesDiffer(t *testing.T) { - t.Parallel() - - compareValuesDiffer := statecheck.CompareValue(compare.ValuesDiffer()) - - resource.Test(t, resource.TestCase{ - // Provider definition omitted. - Steps: []resource.TestStep{ - { - // Example resource containing a computed attribute named "computed_attribute" - Config: `resource "test_resource" "one" {}`, - ConfigStateChecks: []statecheck.StateCheck{ - compareValuesDiffer.AddStateValue( - "test_resource.one", - tfjsonpath.New("computed_attribute"), - ), - }, - }, - { - // Example resource containing a computed attribute named "computed_attribute" - Config: `resource "test_resource" "one" {}`, - ConfigStateChecks: []statecheck.StateCheck{ - compareValuesDiffer.AddStateValue( - "test_resource.one", - tfjsonpath.New("computed_attribute"), - ), - }, - }, - }, - }) + t.Parallel() + + compareValuesDiffer := statecheck.CompareValue(compare.ValuesDiffer()) + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + Config: `resource "test_resource" "one" {}`, + ConfigStateChecks: []statecheck.StateCheck{ + // Add the current state value of "computed_attribute" to the value comparer. + // Since there are no other state values at this point, no assertion is made. + compareValuesDiffer.AddStateValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + ), + }, + }, + { + // Add the current state value of "computed_attribute" to the value comparer. + // Since there is an existing state value in the value comparer at this point, + // if the two values are equal, the test will produce an error. + Config: `resource "test_resource" "one" {}`, + ConfigStateChecks: []statecheck.StateCheck{ + compareValuesDiffer.AddStateValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + ), + }, + }, + }, + }) } ``` -## Values Same Comparer Type +## Values Same The [ValuesSame](https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/compare#ValuesSame) value comparer verifies that each value in the sequence of values supplied to the `CompareValues()` method is the same as the preceding value. @@ -88,45 +136,48 @@ Example usage of [ValuesSame](https://pkg.go.dev/github.com/hashicorp/terraform- package example_test import ( - "testing" + "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-plugin-testing/compare" - "github.com/hashicorp/terraform-plugin-testing/helper/resource" - "github.com/hashicorp/terraform-plugin-testing/statecheck" - "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" + "github.com/hashicorp/terraform-plugin-testing/compare" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" ) func TestCompareValue_CheckState_ValuesSame(t *testing.T) { - t.Parallel() - - compareValuesSame := statecheck.CompareValue(compare.ValuesSame()) - - resource.Test(t, resource.TestCase{ - // Provider definition omitted. - Steps: []resource.TestStep{ - { - // Example resource containing a computed attribute named "computed_attribute" - Config: `resource "test_resource" "one" {}`, - ConfigStateChecks: []statecheck.StateCheck{ - compareValuesSame.AddStateValue( - "test_resource.one", - tfjsonpath.New("computed_attribute"), - ), - }, - }, - { - // Example resource containing a computed attribute named "computed_attribute" - Config: `resource "test_resource" "one" {}`, - ConfigStateChecks: []statecheck.StateCheck{ - compareValuesSame.AddStateValue( - "test_resource.one", - tfjsonpath.New("computed_attribute"), - ), - }, - }, - }, - }) + t.Parallel() + + compareValuesSame := statecheck.CompareValue(compare.ValuesSame()) + + resource.Test(t, resource.TestCase{ + // Provider definition omitted. + Steps: []resource.TestStep{ + { + Config: `resource "test_resource" "one" {}`, + ConfigStateChecks: []statecheck.StateCheck{ + // Add the current state value of "computed_attribute" to the value comparer. + // Since there are no other state values at this point, no assertion is made. + compareValuesSame.AddStateValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + ), + }, + }, + { + Config: `resource "test_resource" "one" {}`, + ConfigStateChecks: []statecheck.StateCheck{ + // Add the current state value of "computed_attribute" to the value comparer. + // Since there is an existing state value in the value comparer at this point, + // if the two values are not equal, the test will produce an error. + compareValuesSame.AddStateValue( + "test_resource.one", + tfjsonpath.New("computed_attribute"), + ), + }, + }, + }, + }) } ``` diff --git a/website/docs/plugin/testing/index.mdx b/website/docs/plugin/testing/index.mdx index 2ed9cb366..d994c3ad7 100644 --- a/website/docs/plugin/testing/index.mdx +++ b/website/docs/plugin/testing/index.mdx @@ -37,10 +37,6 @@ plugins, **it is highly recommended to use an account dedicated to testing, to ensure no infrastructure is created in error in any environment that cannot be completely and safely destroyed.** -HashiCorp runs nightly acceptance tests of providers found in the [Terraform -Providers GitHub Organization](https://github.com/terraform-providers) to ensure -each Provider is working correctly. - For a given plugin, Acceptance Tests can be run from the root of the project by using a common make task: