Skip to content

Commit

Permalink
Implementation supports fullpath with sentinel
Browse files Browse the repository at this point in the history
  • Loading branch information
appilon committed Jun 9, 2020
1 parent 2740243 commit fd1ed5f
Show file tree
Hide file tree
Showing 3 changed files with 71 additions and 68 deletions.
101 changes: 52 additions & 49 deletions aws/internal/test/testing.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,104 +8,107 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/terraform"
)

const (
sentinelIndex = "*"
)

// TestCheckTypeSetElemNestedAttrs is a resource.TestCheckFunc that accepts a resource
// name, an attribute name and depth which targets a TypeSet, as well as a value
// map to verify. The function verifies that the TypeSet attribute exists, and that
// an element matches all the values in the map.
// name, an attribute path, which should use the sentinel value '*' for indexing
// into a TypeSet. The function verifies that an element matches the whole value
// map.
//
// Use this function over SDK provided TestCheckFunctions when validating a
// TypeSet where its elements are a nested object with their own attrs/values.
//
// Please note, if the provided value map is not granular enough, there exists
// the possibility you match an element you were not intending to, in the TypeSet.
// Provide a full mapping of attributes to be sure the unique element exists.
func TestCheckTypeSetElemNestedAttrs(resourceName, attrName string, depth int, values map[string]string) resource.TestCheckFunc {
func TestCheckTypeSetElemNestedAttrs(res, attr string, values map[string]string) resource.TestCheckFunc {
return func(s *terraform.State) error {
ms := s.RootModule()
rs, ok := ms.Resources[resourceName]
rs, ok := ms.Resources[res]
if !ok {
return fmt.Errorf("Not found: %s in %s", resourceName, ms.Path)
return fmt.Errorf("Not found: %s in %s", res, ms.Path)
}

is := rs.Primary
if is == nil {
return fmt.Errorf("No primary instance: %s in %s", resourceName, ms.Path)
return fmt.Errorf("No primary instance: %s in %s", res, ms.Path)
}

matches := make(map[string]int)

attrParts := strings.Split(attr, ".")
for stateKey, stateValue := range is.Attributes {
parts := strings.Split(stateKey, ".")
stateKeyParts := strings.Split(stateKey, ".")
// a Set/List item with nested attrs would have a flatmap address of
// at least length 3
// foo.0.name = "bar"
d := len(parts) - 3
if d < 0 {
if len(stateKeyParts) < 3 {
continue
}
attr := parts[d]
if attr == attrName && d == depth-1 {
// ensure this is a Set/List
if _, exists := is.Attributes[strings.Join(parts[:d+1], ".")+".#"]; !exists {
return fmt.Errorf("%q attr %q is not TypeSet", resourceName, attrName)
var pathMatch bool
for i := range attrParts {
if attrParts[i] != stateKeyParts[i] && attrParts[i] != sentinelIndex {
break
}
elementId := parts[d+1]
nestedAttr := strings.Join(parts[d+2:], ".")
// check if the nestedAttr exists in the passed values map
// if it does, and matches, increment the matches count
if v, exists := values[nestedAttr]; exists && stateValue == v {
matches[elementId] = matches[elementId] + 1
// exit if there is an element that is a full match
if matches[elementId] == len(values) {
return nil
}
if i == len(attrParts)-1 {
pathMatch = true
}
}
if !pathMatch {
continue
}
elementId := stateKeyParts[len(attrParts)-1]
nestedAttr := strings.Join(stateKeyParts[len(attrParts):], ".")
if v, keyExists := values[nestedAttr]; keyExists && v == stateValue {
matches[elementId] = matches[elementId] + 1
if matches[elementId] == len(values) {
return nil
}
}
}

return fmt.Errorf("No TypeSet element in %q with attr name %q at depth %d, with nested attrs %#v in state: %#v", resourceName, attrName, depth, values, is.Attributes)
return fmt.Errorf("%q no TypeSet element %q, with nested attrs %#v in state: %#v", res, attr, values, is.Attributes)
}
}

// TestCheckTypeSetElemAttr is a resource.TestCheckFunc that accepts a resource
// name, an attribute name and depth which targets a TypeSet, as well as a value
// to verify. The function verifies that the TypeSet attribute exists, and that
// an element matches the passed value.
// name, an attribute path, which should use the sentinel value '*' for indexing
// into a TypeSet. The function verifies that an element matches the provided
// value.
//
// Use this function over SDK provided TestCheckFunctions when validating a
// TypeSet where its elements are a simple value
func TestCheckTypeSetElemAttr(resourceName, attrName string, depth int, value string) resource.TestCheckFunc {
func TestCheckTypeSetElemAttr(res, attr, value string) resource.TestCheckFunc {
return func(s *terraform.State) error {
ms := s.RootModule()
rs, ok := ms.Resources[resourceName]
rs, ok := ms.Resources[res]
if !ok {
return fmt.Errorf("Not found: %s in %s", resourceName, ms.Path)
return fmt.Errorf("Not found: %s in %s", res, ms.Path)
}

is := rs.Primary
if is == nil {
return fmt.Errorf("No primary instance: %s in %s", resourceName, ms.Path)
return fmt.Errorf("No primary instance: %s in %s", res, ms.Path)
}

attrParts := strings.Split(attr, ".")
for stateKey, stateValue := range is.Attributes {
parts := strings.Split(stateKey, ".")
// a Set/List item would have a flatmap address of at least length 2
// foo.0 = "bar"
d := len(parts) - 2
if d < 0 {
continue
}
attr := parts[d]
if attr == attrName && d == depth-1 && stateValue == value {
// ensure this is a Set/List
if _, exists := is.Attributes[strings.Join(parts[:d+1], ".")+".#"]; !exists {
return fmt.Errorf("%q attr %q is not TypeSet", resourceName, attrName)
} else {
return nil
if stateValue == value {
stateKeyParts := strings.Split(stateKey, ".")
if len(stateKeyParts) == len(attrParts) {
for i := range attrParts {
if attrParts[i] != stateKeyParts[i] && attrParts[i] != sentinelIndex {
break
}
if i == len(attrParts)-1 {
return nil
}
}
}
}
}

return fmt.Errorf("No TypeSet element in %q with attr name %q at depth %d, with value %q in state: %#v", resourceName, attrName, depth, value, is.Attributes)
return fmt.Errorf("%q no TypeSet element %q, with value %q in state: %#v", res, attr, value, is.Attributes)
}
}
16 changes: 8 additions & 8 deletions aws/resource_aws_apigatewayv2_api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -390,12 +390,12 @@ func TestAccAWSAPIGatewayV2Api_CorsConfiguration(t *testing.T) {
resource.TestCheckResourceAttr(resourceName, "cors_configuration.#", "1"),
resource.TestCheckResourceAttr(resourceName, "cors_configuration.0.allow_credentials", "false"),
resource.TestCheckResourceAttr(resourceName, "cors_configuration.0.allow_headers.#", "1"),
test.TestCheckTypeSetElemAttr(resourceName, "allow_headers", 3, "Authorization"),
test.TestCheckTypeSetElemAttr(resourceName, "cors_configuration.0.allow_headers.*", "Authorization"),
resource.TestCheckResourceAttr(resourceName, "cors_configuration.0.allow_methods.#", "2"),
test.TestCheckTypeSetElemAttr(resourceName, "allow_methods", 3, "GET"),
test.TestCheckTypeSetElemAttr(resourceName, "allow_methods", 3, "put"),
test.TestCheckTypeSetElemAttr(resourceName, "cors_configuration.0.allow_methods.*", "GET"),
test.TestCheckTypeSetElemAttr(resourceName, "cors_configuration.0.allow_methods.*", "put"),
resource.TestCheckResourceAttr(resourceName, "cors_configuration.0.allow_origins.#", "1"),
test.TestCheckTypeSetElemAttr(resourceName, "allow_origins", 3, "https://www.example.com"),
test.TestCheckTypeSetElemAttr(resourceName, "cors_configuration.0.allow_origins.*", "https://www.example.com"),
resource.TestCheckResourceAttr(resourceName, "cors_configuration.0.expose_headers.#", "0"),
resource.TestCheckResourceAttr(resourceName, "cors_configuration.0.max_age", "0"),
resource.TestCheckResourceAttr(resourceName, "description", ""),
Expand Down Expand Up @@ -423,12 +423,12 @@ func TestAccAWSAPIGatewayV2Api_CorsConfiguration(t *testing.T) {
resource.TestCheckResourceAttr(resourceName, "cors_configuration.0.allow_credentials", "true"),
resource.TestCheckResourceAttr(resourceName, "cors_configuration.0.allow_headers.#", "0"),
resource.TestCheckResourceAttr(resourceName, "cors_configuration.0.allow_methods.#", "1"),
test.TestCheckTypeSetElemAttr(resourceName, "allow_methods", 3, "*"),
test.TestCheckTypeSetElemAttr(resourceName, "cors_configuration.0.allow_methods.*", "*"),
resource.TestCheckResourceAttr(resourceName, "cors_configuration.0.allow_origins.#", "2"),
test.TestCheckTypeSetElemAttr(resourceName, "allow_origins", 3, "HTTP://WWW.EXAMPLE.ORG"),
test.TestCheckTypeSetElemAttr(resourceName, "allow_origins", 3, "https://example.io"),
test.TestCheckTypeSetElemAttr(resourceName, "cors_configuration.0.allow_origins.*", "HTTP://WWW.EXAMPLE.ORG"),
test.TestCheckTypeSetElemAttr(resourceName, "cors_configuration.0.allow_origins.*", "https://example.io"),
resource.TestCheckResourceAttr(resourceName, "cors_configuration.0.expose_headers.#", "1"),
test.TestCheckTypeSetElemAttr(resourceName, "expose_headers", 3, "X-Api-Id"),
test.TestCheckTypeSetElemAttr(resourceName, "cors_configuration.0.expose_headers.*", "X-Api-Id"),
resource.TestCheckResourceAttr(resourceName, "cors_configuration.0.max_age", "500"),
resource.TestCheckResourceAttr(resourceName, "description", ""),
testAccMatchResourceAttrRegionalARN(resourceName, "execution_arn", "execute-api", regexp.MustCompile(`.+`)),
Expand Down
22 changes: 11 additions & 11 deletions aws/resource_aws_db_parameter_group_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,15 +91,15 @@ func TestAccAWSDBParameterGroup_basic(t *testing.T) {
testAccCheckAWSDBParameterGroupAttributes(&v, groupName),
resource.TestCheckResourceAttr(resourceName, "name", groupName),
resource.TestCheckResourceAttr(resourceName, "family", "mysql5.6"),
test.TestCheckTypeSetElemNestedAttrs(resourceName, "parameter", 1, map[string]string{
test.TestCheckTypeSetElemNestedAttrs(resourceName, "parameter.*", map[string]string{
"name": "character_set_results",
"value": "utf8",
}),
test.TestCheckTypeSetElemNestedAttrs(resourceName, "parameter", 1, map[string]string{
test.TestCheckTypeSetElemNestedAttrs(resourceName, "parameter.*", map[string]string{
"name": "character_set_server",
"value": "utf8",
}),
test.TestCheckTypeSetElemNestedAttrs(resourceName, "parameter", 1, map[string]string{
test.TestCheckTypeSetElemNestedAttrs(resourceName, "parameter.*", map[string]string{
"name": "character_set_client",
"value": "utf8",
}),
Expand All @@ -118,23 +118,23 @@ func TestAccAWSDBParameterGroup_basic(t *testing.T) {
testAccCheckAWSDBParameterGroupAttributes(&v, groupName),
resource.TestCheckResourceAttr(resourceName, "name", groupName),
resource.TestCheckResourceAttr(resourceName, "family", "mysql5.6"),
test.TestCheckTypeSetElemNestedAttrs(resourceName, "parameter", 1, map[string]string{
test.TestCheckTypeSetElemNestedAttrs(resourceName, "parameter.*", map[string]string{
"name": "collation_connection",
"value": "utf8_unicode_ci",
}),
test.TestCheckTypeSetElemNestedAttrs(resourceName, "parameter", 1, map[string]string{
test.TestCheckTypeSetElemNestedAttrs(resourceName, "parameter.*", map[string]string{
"name": "character_set_results",
"value": "utf8",
}),
test.TestCheckTypeSetElemNestedAttrs(resourceName, "parameter", 1, map[string]string{
test.TestCheckTypeSetElemNestedAttrs(resourceName, "parameter.*", map[string]string{
"name": "character_set_server",
"value": "utf8",
}),
test.TestCheckTypeSetElemNestedAttrs(resourceName, "parameter", 1, map[string]string{
test.TestCheckTypeSetElemNestedAttrs(resourceName, "parameter.*", map[string]string{
"name": "collation_server",
"value": "utf8_unicode_ci",
}),
test.TestCheckTypeSetElemNestedAttrs(resourceName, "parameter", 1, map[string]string{
test.TestCheckTypeSetElemNestedAttrs(resourceName, "parameter.*", map[string]string{
"name": "character_set_client",
"value": "utf8",
}),
Expand All @@ -149,15 +149,15 @@ func TestAccAWSDBParameterGroup_basic(t *testing.T) {
testAccCheckAWSDBParameterNotUserDefined(resourceName, "collation_connection"),
testAccCheckAWSDBParameterNotUserDefined(resourceName, "collation_server"),
resource.TestCheckResourceAttr(resourceName, "parameter.#", "3"),
test.TestCheckTypeSetElemNestedAttrs(resourceName, "parameter", 1, map[string]string{
test.TestCheckTypeSetElemNestedAttrs(resourceName, "parameter.*", map[string]string{
"name": "character_set_results",
"value": "utf8",
}),
test.TestCheckTypeSetElemNestedAttrs(resourceName, "parameter", 1, map[string]string{
test.TestCheckTypeSetElemNestedAttrs(resourceName, "parameter.*", map[string]string{
"name": "character_set_server",
"value": "utf8",
}),
test.TestCheckTypeSetElemNestedAttrs(resourceName, "parameter", 1, map[string]string{
test.TestCheckTypeSetElemNestedAttrs(resourceName, "parameter.*", map[string]string{
"name": "character_set_client",
"value": "utf8",
}),
Expand Down

0 comments on commit fd1ed5f

Please sign in to comment.