Skip to content

Commit 2f0ab68

Browse files
extract: add extract bool and string value functions
This introduces a new state check that extracts an underlying attribute value by traversing the state with tfjsonpath using a specified path.
1 parent aae37aa commit 2f0ab68

File tree

4 files changed

+385
-0
lines changed

4 files changed

+385
-0
lines changed

statecheck/extract_bool_value.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package statecheck
5+
6+
import (
7+
"context"
8+
"fmt"
9+
10+
tfjson "github.com/hashicorp/terraform-json"
11+
"github.com/hashicorp/terraform-plugin-testing/tfjsonpath"
12+
)
13+
14+
var _ StateCheck = extractBoolValue{}
15+
16+
type extractBoolValue struct {
17+
resourceAddress string
18+
attributePath tfjsonpath.Path
19+
targetVar *bool
20+
}
21+
22+
func (e extractBoolValue) CheckState(ctx context.Context, req CheckStateRequest, resp *CheckStateResponse) {
23+
var resource *tfjson.StateResource
24+
25+
if req.State == nil {
26+
resp.Error = fmt.Errorf("state is nil")
27+
28+
return
29+
}
30+
31+
if req.State.Values == nil {
32+
resp.Error = fmt.Errorf("state does not contain any state values")
33+
34+
return
35+
}
36+
37+
if req.State.Values.RootModule == nil {
38+
resp.Error = fmt.Errorf("state does not contain a root module")
39+
40+
return
41+
}
42+
43+
for _, r := range req.State.Values.RootModule.Resources {
44+
if e.resourceAddress == r.Address {
45+
resource = r
46+
47+
break
48+
}
49+
}
50+
51+
if resource == nil {
52+
resp.Error = fmt.Errorf("%s - Resource not found in state", e.resourceAddress)
53+
54+
return
55+
}
56+
57+
result, err := tfjsonpath.Traverse(resource.AttributeValues, e.attributePath)
58+
if err != nil {
59+
resp.Error = err
60+
61+
return
62+
}
63+
64+
if result == nil {
65+
resp.Error = fmt.Errorf("nil: result for attribute '%s' in '%s'", e.attributePath, e.resourceAddress)
66+
67+
return
68+
}
69+
70+
switch t := result.(type) {
71+
case bool:
72+
*e.targetVar = t
73+
default:
74+
resp.Error = fmt.Errorf("invalid type for attribute '%s' in '%s'. Expected: bool, Got: %T", e.attributePath, e.resourceAddress, t)
75+
76+
return
77+
}
78+
}
79+
80+
func ExtractBoolValue(resourceAddress string, attributePath tfjsonpath.Path, targetVar *bool) StateCheck {
81+
return extractBoolValue{
82+
resourceAddress: resourceAddress,
83+
attributePath: attributePath,
84+
targetVar: targetVar,
85+
}
86+
}
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package statecheck_test
5+
6+
import (
7+
"fmt"
8+
"regexp"
9+
"testing"
10+
11+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
12+
13+
r "github.com/hashicorp/terraform-plugin-testing/helper/resource"
14+
"github.com/hashicorp/terraform-plugin-testing/statecheck"
15+
"github.com/hashicorp/terraform-plugin-testing/tfjsonpath"
16+
)
17+
18+
func TestExtractBoolValue_Basic(t *testing.T) {
19+
t.Parallel()
20+
21+
// targetVar will be set to the extracted value.
22+
var targetVar bool
23+
24+
r.Test(t, r.TestCase{
25+
ProviderFactories: map[string]func() (*schema.Provider, error){
26+
"test": func() (*schema.Provider, error) { //nolint:unparam // required signature
27+
return testProvider(), nil
28+
},
29+
},
30+
Steps: []r.TestStep{
31+
{
32+
Config: `resource "test_resource" "one" {
33+
bool_attribute = true
34+
}
35+
`,
36+
ConfigStateChecks: []statecheck.StateCheck{
37+
statecheck.ExtractBoolValue(
38+
"test_resource.one",
39+
tfjsonpath.New("bool_attribute"),
40+
&targetVar,
41+
),
42+
},
43+
},
44+
},
45+
})
46+
47+
t.Run("CheckTargetVar", func(t *testing.T) {
48+
if err := testAccAssertBoolEquals(true, targetVar); err != nil {
49+
t.Errorf("Error in testAccAssertBoolEquals: %v", err)
50+
}
51+
})
52+
}
53+
54+
func TestExtractBoolValue_KnownValueWrongType(t *testing.T) {
55+
t.Parallel()
56+
57+
// targetVar will be set to the extracted value.
58+
var targetVar bool
59+
60+
r.Test(t, r.TestCase{
61+
ProviderFactories: map[string]func() (*schema.Provider, error){
62+
"test": func() (*schema.Provider, error) { //nolint:unparam // required signature
63+
return testProvider(), nil
64+
},
65+
},
66+
Steps: []r.TestStep{
67+
{
68+
Config: `resource "test_resource" "one" {
69+
float_attribute = 1.23
70+
}
71+
`,
72+
ConfigStateChecks: []statecheck.StateCheck{
73+
statecheck.ExtractBoolValue(
74+
"test_resource.one",
75+
tfjsonpath.New("float_attribute"),
76+
&targetVar,
77+
),
78+
},
79+
ExpectError: regexp.MustCompile(`invalid type for attribute \'float_attribute\' in \'test_resource\.one\'. Expected: bool, Got: json\.Number`),
80+
},
81+
},
82+
})
83+
}
84+
85+
func TestExtractBoolValue_Null(t *testing.T) {
86+
t.Parallel()
87+
88+
// targetVar will be set to the extracted value.
89+
var targetVar bool
90+
91+
r.Test(t, r.TestCase{
92+
ProviderFactories: map[string]func() (*schema.Provider, error){
93+
"test": func() (*schema.Provider, error) { //nolint:unparam // required signature
94+
return testProvider(), nil
95+
},
96+
},
97+
Steps: []r.TestStep{
98+
{
99+
Config: `resource "test_resource" "one" {
100+
bool_attribute = null
101+
}
102+
`,
103+
ConfigStateChecks: []statecheck.StateCheck{
104+
statecheck.ExtractBoolValue(
105+
"test_resource.one",
106+
tfjsonpath.New("bool_attribute"),
107+
&targetVar,
108+
),
109+
},
110+
ExpectError: regexp.MustCompile(`nil: result for attribute \'bool_attribute\' in \'test_resource.one\'`),
111+
},
112+
},
113+
})
114+
}
115+
116+
func TestExtractBoolValue_ResourceNotFound(t *testing.T) {
117+
t.Parallel()
118+
119+
// targetVar will be set to the extracted value.
120+
var targetVar bool
121+
122+
r.Test(t, r.TestCase{
123+
ProviderFactories: map[string]func() (*schema.Provider, error){
124+
"test": func() (*schema.Provider, error) { //nolint:unparam // required signature
125+
return testProvider(), nil
126+
},
127+
},
128+
Steps: []r.TestStep{
129+
{
130+
Config: `resource "test_resource" "one" {
131+
bool_attribute = true
132+
}
133+
`,
134+
ConfigStateChecks: []statecheck.StateCheck{
135+
statecheck.ExtractBoolValue(
136+
"test_resource.two",
137+
tfjsonpath.New("bool_attribute"),
138+
&targetVar,
139+
),
140+
},
141+
ExpectError: regexp.MustCompile("test_resource.two - Resource not found in state"),
142+
},
143+
},
144+
})
145+
}
146+
147+
// testAccAssertBoolEquals compares the expected and target bool values.
148+
func testAccAssertBoolEquals(expected bool, targetVar bool) error {
149+
if targetVar != expected {
150+
return fmt.Errorf("expected targetVar to be %v, got %v", expected, targetVar)
151+
}
152+
return nil
153+
}

statecheck/extract_string_value.go

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package statecheck
5+
6+
import (
7+
"context"
8+
"fmt"
9+
10+
tfjson "github.com/hashicorp/terraform-json"
11+
"github.com/hashicorp/terraform-plugin-testing/tfjsonpath"
12+
)
13+
14+
var _ StateCheck = extractStringValue{}
15+
16+
type extractStringValue struct {
17+
resourceAddress string
18+
attributePath tfjsonpath.Path
19+
targetVar *string
20+
}
21+
22+
func (e extractStringValue) CheckState(ctx context.Context, req CheckStateRequest, resp *CheckStateResponse) {
23+
var resource *tfjson.StateResource
24+
25+
if req.State == nil {
26+
resp.Error = fmt.Errorf("state is nil")
27+
28+
return
29+
}
30+
31+
if req.State.Values == nil {
32+
resp.Error = fmt.Errorf("state does not contain any state values")
33+
34+
return
35+
}
36+
37+
if req.State.Values.RootModule == nil {
38+
resp.Error = fmt.Errorf("state does not contain a root module")
39+
40+
return
41+
}
42+
43+
for _, r := range req.State.Values.RootModule.Resources {
44+
if e.resourceAddress == r.Address {
45+
resource = r
46+
47+
break
48+
}
49+
}
50+
51+
if resource == nil {
52+
resp.Error = fmt.Errorf("%s - Resource not found in state", e.resourceAddress)
53+
54+
return
55+
}
56+
57+
result, err := tfjsonpath.Traverse(resource.AttributeValues, e.attributePath)
58+
if err != nil {
59+
resp.Error = err
60+
61+
return
62+
}
63+
64+
if result == nil {
65+
resp.Error = fmt.Errorf("nil: result for attribute '%s' in '%s'", e.attributePath, e.resourceAddress)
66+
67+
return
68+
}
69+
70+
switch t := result.(type) {
71+
case string:
72+
*e.targetVar = t
73+
return
74+
default:
75+
resp.Error = fmt.Errorf("invalid type for attribute '%s' in '%s'. Expected: string, Got: %T", e.attributePath, e.resourceAddress, t)
76+
77+
return
78+
}
79+
}
80+
81+
func ExtractStringValue(resourceAddress string, attributePath tfjsonpath.Path, targetVar *string) StateCheck {
82+
return extractStringValue{
83+
resourceAddress: resourceAddress,
84+
attributePath: attributePath,
85+
targetVar: targetVar,
86+
}
87+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
4+
package statecheck_test
5+
6+
import (
7+
"fmt"
8+
"testing"
9+
10+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
11+
12+
r "github.com/hashicorp/terraform-plugin-testing/helper/resource"
13+
"github.com/hashicorp/terraform-plugin-testing/statecheck"
14+
"github.com/hashicorp/terraform-plugin-testing/tfjsonpath"
15+
)
16+
17+
func TestExtractStringValue_Basic(t *testing.T) {
18+
t.Parallel()
19+
20+
// targetVar will be set to the extracted value.
21+
var targetVar string
22+
23+
r.Test(t, r.TestCase{
24+
ProviderFactories: map[string]func() (*schema.Provider, error){
25+
"test": func() (*schema.Provider, error) { //nolint:unparam // required signature
26+
return testProvider(), nil
27+
},
28+
},
29+
Steps: []r.TestStep{
30+
{
31+
Config: `resource "test_resource" "one" {
32+
string_attribute = "str"
33+
}
34+
`,
35+
ConfigStateChecks: []statecheck.StateCheck{
36+
statecheck.ExtractStringValue(
37+
"test_resource.one",
38+
tfjsonpath.New("string_attribute"),
39+
&targetVar,
40+
),
41+
},
42+
},
43+
},
44+
})
45+
46+
t.Run("CheckTargetVar", func(t *testing.T) {
47+
if err := testAccAssertStringEquals("str", targetVar); err != nil {
48+
t.Errorf("Error in testAccAssertBoolEquals: %v", err)
49+
}
50+
})
51+
}
52+
53+
// testAccAssertStringEquals compares the expected and target string values.
54+
func testAccAssertStringEquals(expected string, targetVar string) error {
55+
if targetVar != expected {
56+
return fmt.Errorf("expected targetVar to be %v, got %v", expected, targetVar)
57+
}
58+
return nil
59+
}

0 commit comments

Comments
 (0)