-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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.
- Loading branch information
1 parent
aae37aa
commit 2f0ab68
Showing
4 changed files
with
385 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
// 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/tfjsonpath" | ||
) | ||
|
||
var _ StateCheck = extractBoolValue{} | ||
|
||
type extractBoolValue struct { | ||
resourceAddress string | ||
attributePath tfjsonpath.Path | ||
targetVar *bool | ||
} | ||
|
||
func (e extractBoolValue) 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 | ||
} | ||
|
||
for _, r := range req.State.Values.RootModule.Resources { | ||
if e.resourceAddress == r.Address { | ||
resource = r | ||
|
||
break | ||
} | ||
} | ||
|
||
if resource == nil { | ||
resp.Error = fmt.Errorf("%s - Resource not found in state", e.resourceAddress) | ||
|
||
return | ||
} | ||
|
||
result, err := tfjsonpath.Traverse(resource.AttributeValues, e.attributePath) | ||
if err != nil { | ||
resp.Error = err | ||
|
||
return | ||
} | ||
|
||
if result == nil { | ||
resp.Error = fmt.Errorf("nil: result for attribute '%s' in '%s'", e.attributePath, e.resourceAddress) | ||
|
||
return | ||
} | ||
|
||
switch t := result.(type) { | ||
case bool: | ||
*e.targetVar = t | ||
default: | ||
resp.Error = fmt.Errorf("invalid type for attribute '%s' in '%s'. Expected: bool, Got: %T", e.attributePath, e.resourceAddress, t) | ||
|
||
return | ||
} | ||
} | ||
|
||
func ExtractBoolValue(resourceAddress string, attributePath tfjsonpath.Path, targetVar *bool) StateCheck { | ||
return extractBoolValue{ | ||
resourceAddress: resourceAddress, | ||
attributePath: attributePath, | ||
targetVar: targetVar, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,153 @@ | ||
// Copyright (c) HashiCorp, Inc. | ||
// SPDX-License-Identifier: MPL-2.0 | ||
|
||
package statecheck_test | ||
|
||
import ( | ||
"fmt" | ||
"regexp" | ||
"testing" | ||
|
||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" | ||
|
||
r "github.com/hashicorp/terraform-plugin-testing/helper/resource" | ||
"github.com/hashicorp/terraform-plugin-testing/statecheck" | ||
"github.com/hashicorp/terraform-plugin-testing/tfjsonpath" | ||
) | ||
|
||
func TestExtractBoolValue_Basic(t *testing.T) { | ||
t.Parallel() | ||
|
||
// targetVar will be set to the extracted value. | ||
var targetVar bool | ||
|
||
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{ | ||
statecheck.ExtractBoolValue( | ||
"test_resource.one", | ||
tfjsonpath.New("bool_attribute"), | ||
&targetVar, | ||
), | ||
}, | ||
}, | ||
}, | ||
}) | ||
|
||
t.Run("CheckTargetVar", func(t *testing.T) { | ||
if err := testAccAssertBoolEquals(true, targetVar); err != nil { | ||
t.Errorf("Error in testAccAssertBoolEquals: %v", err) | ||
} | ||
}) | ||
} | ||
|
||
func TestExtractBoolValue_KnownValueWrongType(t *testing.T) { | ||
t.Parallel() | ||
|
||
// targetVar will be set to the extracted value. | ||
var targetVar bool | ||
|
||
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" { | ||
float_attribute = 1.23 | ||
} | ||
`, | ||
ConfigStateChecks: []statecheck.StateCheck{ | ||
statecheck.ExtractBoolValue( | ||
"test_resource.one", | ||
tfjsonpath.New("float_attribute"), | ||
&targetVar, | ||
), | ||
}, | ||
ExpectError: regexp.MustCompile(`invalid type for attribute \'float_attribute\' in \'test_resource\.one\'. Expected: bool, Got: json\.Number`), | ||
}, | ||
}, | ||
}) | ||
} | ||
|
||
func TestExtractBoolValue_Null(t *testing.T) { | ||
t.Parallel() | ||
|
||
// targetVar will be set to the extracted value. | ||
var targetVar bool | ||
|
||
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 = null | ||
} | ||
`, | ||
ConfigStateChecks: []statecheck.StateCheck{ | ||
statecheck.ExtractBoolValue( | ||
"test_resource.one", | ||
tfjsonpath.New("bool_attribute"), | ||
&targetVar, | ||
), | ||
}, | ||
ExpectError: regexp.MustCompile(`nil: result for attribute \'bool_attribute\' in \'test_resource.one\'`), | ||
}, | ||
}, | ||
}) | ||
} | ||
|
||
func TestExtractBoolValue_ResourceNotFound(t *testing.T) { | ||
t.Parallel() | ||
|
||
// targetVar will be set to the extracted value. | ||
var targetVar bool | ||
|
||
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{ | ||
statecheck.ExtractBoolValue( | ||
"test_resource.two", | ||
tfjsonpath.New("bool_attribute"), | ||
&targetVar, | ||
), | ||
}, | ||
ExpectError: regexp.MustCompile("test_resource.two - Resource not found in state"), | ||
}, | ||
}, | ||
}) | ||
} | ||
|
||
// testAccAssertBoolEquals compares the expected and target bool values. | ||
func testAccAssertBoolEquals(expected bool, targetVar bool) error { | ||
if targetVar != expected { | ||
return fmt.Errorf("expected targetVar to be %v, got %v", expected, targetVar) | ||
} | ||
return nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
// 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/tfjsonpath" | ||
) | ||
|
||
var _ StateCheck = extractStringValue{} | ||
|
||
type extractStringValue struct { | ||
resourceAddress string | ||
attributePath tfjsonpath.Path | ||
targetVar *string | ||
} | ||
|
||
func (e extractStringValue) 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 | ||
} | ||
|
||
for _, r := range req.State.Values.RootModule.Resources { | ||
if e.resourceAddress == r.Address { | ||
resource = r | ||
|
||
break | ||
} | ||
} | ||
|
||
if resource == nil { | ||
resp.Error = fmt.Errorf("%s - Resource not found in state", e.resourceAddress) | ||
|
||
return | ||
} | ||
|
||
result, err := tfjsonpath.Traverse(resource.AttributeValues, e.attributePath) | ||
if err != nil { | ||
resp.Error = err | ||
|
||
return | ||
} | ||
|
||
if result == nil { | ||
resp.Error = fmt.Errorf("nil: result for attribute '%s' in '%s'", e.attributePath, e.resourceAddress) | ||
|
||
return | ||
} | ||
|
||
switch t := result.(type) { | ||
case string: | ||
*e.targetVar = t | ||
return | ||
default: | ||
resp.Error = fmt.Errorf("invalid type for attribute '%s' in '%s'. Expected: string, Got: %T", e.attributePath, e.resourceAddress, t) | ||
|
||
return | ||
} | ||
} | ||
|
||
func ExtractStringValue(resourceAddress string, attributePath tfjsonpath.Path, targetVar *string) StateCheck { | ||
return extractStringValue{ | ||
resourceAddress: resourceAddress, | ||
attributePath: attributePath, | ||
targetVar: targetVar, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
// Copyright (c) HashiCorp, Inc. | ||
// SPDX-License-Identifier: MPL-2.0 | ||
|
||
package statecheck_test | ||
|
||
import ( | ||
"fmt" | ||
"testing" | ||
|
||
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" | ||
|
||
r "github.com/hashicorp/terraform-plugin-testing/helper/resource" | ||
"github.com/hashicorp/terraform-plugin-testing/statecheck" | ||
"github.com/hashicorp/terraform-plugin-testing/tfjsonpath" | ||
) | ||
|
||
func TestExtractStringValue_Basic(t *testing.T) { | ||
t.Parallel() | ||
|
||
// targetVar will be set to the extracted value. | ||
var targetVar string | ||
|
||
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" | ||
} | ||
`, | ||
ConfigStateChecks: []statecheck.StateCheck{ | ||
statecheck.ExtractStringValue( | ||
"test_resource.one", | ||
tfjsonpath.New("string_attribute"), | ||
&targetVar, | ||
), | ||
}, | ||
}, | ||
}, | ||
}) | ||
|
||
t.Run("CheckTargetVar", func(t *testing.T) { | ||
if err := testAccAssertStringEquals("str", targetVar); err != nil { | ||
t.Errorf("Error in testAccAssertBoolEquals: %v", err) | ||
} | ||
}) | ||
} | ||
|
||
// testAccAssertStringEquals compares the expected and target string values. | ||
func testAccAssertStringEquals(expected string, targetVar string) error { | ||
if targetVar != expected { | ||
return fmt.Errorf("expected targetVar to be %v, got %v", expected, targetVar) | ||
} | ||
return nil | ||
} |