terraform-provider-gitea/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource/testing_sets.go
dependabot[bot] 910ccdb092
Bump github.com/hashicorp/terraform-plugin-sdk/v2 from 2.26.1 to 2.27.0
Bumps [github.com/hashicorp/terraform-plugin-sdk/v2](https://github.com/hashicorp/terraform-plugin-sdk) from 2.26.1 to 2.27.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.26.1...v2.27.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] <support@github.com>
2023-07-03 20:21:30 +00:00

362 lines
13 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
// These test helpers were developed by the AWS provider team at HashiCorp.
package resource
import (
"fmt"
"regexp"
"strings"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
)
const (
sentinelIndex = "*"
)
// TestCheckTypeSetElemNestedAttrs ensures a subset map of values is stored in
// state for the given name and key combination of attributes nested under a
// list or set block. Use this TestCheckFunc in preference over non-set
// variants to simplify testing code and ensure compatibility with indicies,
// which can easily change with schema changes. State value checking is only
// recommended for testing Computed attributes and attribute defaults.
//
// For managed resources, the name parameter is a combination of the resource
// type, a period (.), and the name label. The name for the below example
// configuration would be "myprovider_thing.example".
//
// resource "myprovider_thing" "example" { ... }
//
// For data sources, the name parameter is a combination of the keyword "data",
// a period (.), the data source type, a period (.), and the name label. The
// name for the below example configuration would be
// "data.myprovider_thing.example".
//
// data "myprovider_thing" "example" { ... }
//
// The key parameter is an attribute path in Terraform CLI 0.11 and earlier
// "flatmap" syntax. Keys start with the attribute name of a top-level
// attribute. Use the sentinel value '*' to replace the element indexing into
// a list or set. The sentinel value can be used for each list or set index, if
// there are multiple lists or sets in the attribute path.
//
// The values parameter is the map of attribute names to attribute values
// expected to be nested under the list or set.
//
// You may check for unset nested attributes, however this will also match keys
// set to an empty string. Use a map with at least 1 non-empty value.
//
// map[string]string{
// "key1": "value",
// "key2": "",
// }
//
// If the values map is not granular enough, it is possible to match an element
// you were not intending to in the set. Provide the most complete mapping of
// attributes possible to be sure the unique element exists.
func TestCheckTypeSetElemNestedAttrs(name, attr string, values map[string]string) TestCheckFunc {
return func(s *terraform.State) error {
is, err := primaryInstanceState(s, name)
if err != nil {
return err
}
attrParts := strings.Split(attr, ".")
if attrParts[len(attrParts)-1] != sentinelIndex {
return fmt.Errorf("%q does not end with the special value %q", attr, sentinelIndex)
}
// account for cases where the user is trying to see if the value is unset/empty
// there may be ambiguous scenarios where a field was deliberately unset vs set
// to the empty string, this will match both, which may be a false positive.
var matchCount int
for _, v := range values {
if v != "" {
matchCount++
}
}
if matchCount == 0 {
return fmt.Errorf("%#v has no non-empty values", values)
}
if testCheckTypeSetElemNestedAttrsInState(is, attrParts, matchCount, values) {
return nil
}
return fmt.Errorf("%q no TypeSet element %q, with nested attrs %#v in state: %#v", name, attr, values, is.Attributes)
}
}
// TestMatchTypeSetElemNestedAttrs ensures a subset map of values, compared by
// regular expressions, is stored in state for the given name and key
// combination of attributes nested under a list or set block. Use this
// TestCheckFunc in preference over non-set variants to simplify testing code
// and ensure compatibility with indicies, which can easily change with schema
// changes. State value checking is only recommended for testing Computed
// attributes and attribute defaults.
//
// For managed resources, the name parameter is a combination of the resource
// type, a period (.), and the name label. The name for the below example
// configuration would be "myprovider_thing.example".
//
// resource "myprovider_thing" "example" { ... }
//
// For data sources, the name parameter is a combination of the keyword "data",
// a period (.), the data source type, a period (.), and the name label. The
// name for the below example configuration would be
// "data.myprovider_thing.example".
//
// data "myprovider_thing" "example" { ... }
//
// The key parameter is an attribute path in Terraform CLI 0.11 and earlier
// "flatmap" syntax. Keys start with the attribute name of a top-level
// attribute. Use the sentinel value '*' to replace the element indexing into
// a list or set. The sentinel value can be used for each list or set index, if
// there are multiple lists or sets in the attribute path.
//
// The values parameter is the map of attribute names to regular expressions
// for matching attribute values expected to be nested under the list or set.
//
// You may check for unset nested attributes, however this will also match keys
// set to an empty string. Use a map with at least 1 non-empty value.
//
// map[string]*regexp.Regexp{
// "key1": regexp.MustCompile(`^value`),
// "key2": regexp.MustCompile(`^$`),
// }
//
// If the values map is not granular enough, it is possible to match an element
// you were not intending to in the set. Provide the most complete mapping of
// attributes possible to be sure the unique element exists.
func TestMatchTypeSetElemNestedAttrs(name, attr string, values map[string]*regexp.Regexp) TestCheckFunc {
return func(s *terraform.State) error {
is, err := primaryInstanceState(s, name)
if err != nil {
return err
}
attrParts := strings.Split(attr, ".")
if attrParts[len(attrParts)-1] != sentinelIndex {
return fmt.Errorf("%q does not end with the special value %q", attr, sentinelIndex)
}
// account for cases where the user is trying to see if the value is unset/empty
// there may be ambiguous scenarios where a field was deliberately unset vs set
// to the empty string, this will match both, which may be a false positive.
var matchCount int
for _, v := range values {
if v != nil {
matchCount++
}
}
if matchCount == 0 {
return fmt.Errorf("%#v has no non-empty values", values)
}
if testCheckTypeSetElemNestedAttrsInState(is, attrParts, matchCount, values) {
return nil
}
return fmt.Errorf("%q no TypeSet element %q, with the regex provided, match in state: %#v", name, attr, is.Attributes)
}
}
// TestCheckTypeSetElemAttr is a TestCheckFunc that accepts a resource
// 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
// TestCheckTypeSetElemAttr ensures a specific value is stored in state for the
// given name and key combination under a list or set. Use this TestCheckFunc
// in preference over non-set variants to simplify testing code and ensure
// compatibility with indicies, which can easily change with schema changes.
// State value checking is only recommended for testing Computed attributes and
// attribute defaults.
//
// For managed resources, the name parameter is a combination of the resource
// type, a period (.), and the name label. The name for the below example
// configuration would be "myprovider_thing.example".
//
// resource "myprovider_thing" "example" { ... }
//
// For data sources, the name parameter is a combination of the keyword "data",
// a period (.), the data source type, a period (.), and the name label. The
// name for the below example configuration would be
// "data.myprovider_thing.example".
//
// data "myprovider_thing" "example" { ... }
//
// The key parameter is an attribute path in Terraform CLI 0.11 and earlier
// "flatmap" syntax. Keys start with the attribute name of a top-level
// attribute. Use the sentinel value '*' to replace the element indexing into
// a list or set. The sentinel value can be used for each list or set index, if
// there are multiple lists or sets in the attribute path.
//
// The value parameter is the stringified data to check at the given key. Use
// the following attribute type rules to set the value:
//
// - Boolean: "false" or "true".
// - Float/Integer: Stringified number, such as "1.2" or "123".
// - String: No conversion necessary.
func TestCheckTypeSetElemAttr(name, attr, value string) TestCheckFunc {
return func(s *terraform.State) error {
is, err := primaryInstanceState(s, name)
if err != nil {
return err
}
err = testCheckTypeSetElem(is, attr, value)
if err != nil {
return fmt.Errorf("%q error: %s", name, err)
}
return nil
}
}
// TestCheckTypeSetElemAttrPair ensures value equality in state between the
// first given name and key combination and the second name and key
// combination. State value checking is only recommended for testing Computed
// attributes and attribute defaults.
//
// For managed resources, the name parameter is a combination of the resource
// type, a period (.), and the name label. The name for the below example
// configuration would be "myprovider_thing.example".
//
// resource "myprovider_thing" "example" { ... }
//
// For data sources, the name parameter is a combination of the keyword "data",
// a period (.), the data source type, a period (.), and the name label. The
// name for the below example configuration would be
// "data.myprovider_thing.example".
//
// data "myprovider_thing" "example" { ... }
//
// The first and second names may use any combination of managed resources
// and/or data sources.
//
// The key parameter is an attribute path in Terraform CLI 0.11 and earlier
// "flatmap" syntax. Keys start with the attribute name of a top-level
// attribute. Use the sentinel value '*' to replace the element indexing into
// a list or set. The sentinel value can be used for each list or set index, if
// there are multiple lists or sets in the attribute path.
func TestCheckTypeSetElemAttrPair(nameFirst, keyFirst, nameSecond, keySecond string) TestCheckFunc {
return func(s *terraform.State) error {
isFirst, err := primaryInstanceState(s, nameFirst)
if err != nil {
return err
}
isSecond, err := primaryInstanceState(s, nameSecond)
if err != nil {
return err
}
vSecond, okSecond := isSecond.Attributes[keySecond]
if !okSecond {
return fmt.Errorf("%s: Attribute %q not set, cannot be checked against TypeSet", nameSecond, keySecond)
}
return testCheckTypeSetElemPair(isFirst, keyFirst, vSecond)
}
}
func testCheckTypeSetElem(is *terraform.InstanceState, attr, value string) error {
attrParts := strings.Split(attr, ".")
if attrParts[len(attrParts)-1] != sentinelIndex {
return fmt.Errorf("%q does not end with the special value %q", attr, sentinelIndex)
}
for stateKey, stateValue := range is.Attributes {
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 %q, with value %q in state: %#v", attr, value, is.Attributes)
}
func testCheckTypeSetElemPair(is *terraform.InstanceState, attr, value string) error {
attrParts := strings.Split(attr, ".")
for stateKey, stateValue := range is.Attributes {
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 %q, with value %q in state: %#v", attr, value, is.Attributes)
}
// testCheckTypeSetElemNestedAttrsInState is a helper function
// to determine if nested attributes and their values are equal to those
// in the instance state. Currently, the function accepts a "values" param of type
// map[string]string or map[string]*regexp.Regexp.
// Returns true if all attributes match, else false.
func testCheckTypeSetElemNestedAttrsInState(is *terraform.InstanceState, attrParts []string, matchCount int, values interface{}) bool {
matches := make(map[string]int)
for stateKey, stateValue := range is.Attributes {
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"
if len(stateKeyParts) < 3 || len(attrParts) > len(stateKeyParts) {
continue
}
var pathMatch bool
for i := range attrParts {
if attrParts[i] != stateKeyParts[i] && attrParts[i] != sentinelIndex {
break
}
if i == len(attrParts)-1 {
pathMatch = true
}
}
if !pathMatch {
continue
}
id := stateKeyParts[len(attrParts)-1]
nestedAttr := strings.Join(stateKeyParts[len(attrParts):], ".")
var match bool
switch t := values.(type) {
case map[string]string:
if v, keyExists := t[nestedAttr]; keyExists && v == stateValue {
match = true
}
case map[string]*regexp.Regexp:
if v, keyExists := t[nestedAttr]; keyExists && v != nil && v.MatchString(stateValue) {
match = true
}
}
if match {
matches[id] = matches[id] + 1
if matches[id] == matchCount {
return true
}
}
}
return false
}