diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..f24a3441eb --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +# https://git-scm.com/docs/gitattributes +# https://docs.github.com/en/repositories/working-with-files/managing-files/customizing-how-changed-files-appear-on-github + +# Ignore vendor directory in language statistics +vendor/** linguist-generated=true diff --git a/github/acc_test.go b/github/acc_test.go index 7f0e3dce94..87bac1d698 100644 --- a/github/acc_test.go +++ b/github/acc_test.go @@ -11,8 +11,8 @@ import ( "testing" "github.com/google/go-github/v82/github" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) type testMode string diff --git a/github/data_source_github_actions_environment_public_key_test.go b/github/data_source_github_actions_environment_public_key_test.go index dda4bf3e05..6bfbdbec2d 100644 --- a/github/data_source_github_actions_environment_public_key_test.go +++ b/github/data_source_github_actions_environment_public_key_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubActionsEnvironmentPublicKeyDataSource(t *testing.T) { diff --git a/github/data_source_github_actions_environment_secrets_test.go b/github/data_source_github_actions_environment_secrets_test.go index 17c26fe1ca..4d9a826342 100644 --- a/github/data_source_github_actions_environment_secrets_test.go +++ b/github/data_source_github_actions_environment_secrets_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubActionsEnvironmentSecretsDataSource(t *testing.T) { diff --git a/github/data_source_github_actions_environment_variables_test.go b/github/data_source_github_actions_environment_variables_test.go index b41170bac9..c1fcdb6770 100644 --- a/github/data_source_github_actions_environment_variables_test.go +++ b/github/data_source_github_actions_environment_variables_test.go @@ -5,8 +5,8 @@ import ( "strings" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubActionsEnvironmentVariablesDataSource(t *testing.T) { diff --git a/github/data_source_github_actions_organization_oidc_subject_claim_customization_template_test.go b/github/data_source_github_actions_organization_oidc_subject_claim_customization_template_test.go index a72d201208..232f3650ab 100644 --- a/github/data_source_github_actions_organization_oidc_subject_claim_customization_template_test.go +++ b/github/data_source_github_actions_organization_oidc_subject_claim_customization_template_test.go @@ -3,7 +3,7 @@ package github import ( "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubActionsOrganizationOIDCSubjectClaimCustomizationTemplateDataSource(t *testing.T) { diff --git a/github/data_source_github_actions_organization_public_key_test.go b/github/data_source_github_actions_organization_public_key_test.go index 05cbb70671..519674a2e7 100644 --- a/github/data_source_github_actions_organization_public_key_test.go +++ b/github/data_source_github_actions_organization_public_key_test.go @@ -3,7 +3,7 @@ package github import ( "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubActionsOrganizationPublicKeyDataSource(t *testing.T) { diff --git a/github/data_source_github_actions_organization_registration_token_test.go b/github/data_source_github_actions_organization_registration_token_test.go index 92be4997b3..3de08ac8fe 100644 --- a/github/data_source_github_actions_organization_registration_token_test.go +++ b/github/data_source_github_actions_organization_registration_token_test.go @@ -3,7 +3,7 @@ package github import ( "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubActionsOrganizationRegistrationTokenDataSource(t *testing.T) { diff --git a/github/data_source_github_actions_organization_secrets_test.go b/github/data_source_github_actions_organization_secrets_test.go index 2217dbe539..93efd76961 100644 --- a/github/data_source_github_actions_organization_secrets_test.go +++ b/github/data_source_github_actions_organization_secrets_test.go @@ -5,8 +5,8 @@ import ( "strings" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubActionsOrganizationSecretsDataSource(t *testing.T) { diff --git a/github/data_source_github_actions_organization_variables_test.go b/github/data_source_github_actions_organization_variables_test.go index 53501498c1..e2bb495b42 100644 --- a/github/data_source_github_actions_organization_variables_test.go +++ b/github/data_source_github_actions_organization_variables_test.go @@ -5,8 +5,8 @@ import ( "strings" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubActionsOrganizationVariablesDataSource(t *testing.T) { diff --git a/github/data_source_github_actions_public_key_test.go b/github/data_source_github_actions_public_key_test.go index cd05217215..afcc960f50 100644 --- a/github/data_source_github_actions_public_key_test.go +++ b/github/data_source_github_actions_public_key_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubActionsPublicKeyDataSource(t *testing.T) { diff --git a/github/data_source_github_actions_registration_token_test.go b/github/data_source_github_actions_registration_token_test.go index c4c53cb892..b3e93a2f65 100644 --- a/github/data_source_github_actions_registration_token_test.go +++ b/github/data_source_github_actions_registration_token_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubActionsRegistrationTokenDataSource(t *testing.T) { diff --git a/github/data_source_github_actions_repository_oidc_subject_claim_customization_template_test.go b/github/data_source_github_actions_repository_oidc_subject_claim_customization_template_test.go index 6fec7776b4..b547ba1037 100644 --- a/github/data_source_github_actions_repository_oidc_subject_claim_customization_template_test.go +++ b/github/data_source_github_actions_repository_oidc_subject_claim_customization_template_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubActionsRepositoryOIDCSubjectClaimCustomizationTemplateDataSource(t *testing.T) { diff --git a/github/data_source_github_actions_secrets_test.go b/github/data_source_github_actions_secrets_test.go index c07a81be04..e810d4a999 100644 --- a/github/data_source_github_actions_secrets_test.go +++ b/github/data_source_github_actions_secrets_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubActionsSecretsDataSource(t *testing.T) { diff --git a/github/data_source_github_actions_variables_test.go b/github/data_source_github_actions_variables_test.go index 370fa38e52..915a292a9f 100644 --- a/github/data_source_github_actions_variables_test.go +++ b/github/data_source_github_actions_variables_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubActionsVariablesDataSource(t *testing.T) { diff --git a/github/data_source_github_app_test.go b/github/data_source_github_app_test.go index 5130b7042a..f996c78105 100644 --- a/github/data_source_github_app_test.go +++ b/github/data_source_github_app_test.go @@ -3,7 +3,7 @@ package github import ( "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubAppDataSource(t *testing.T) { diff --git a/github/data_source_github_branch_protection_rules_test.go b/github/data_source_github_branch_protection_rules_test.go index df7138b636..f0c2ce5946 100644 --- a/github/data_source_github_branch_protection_rules_test.go +++ b/github/data_source_github_branch_protection_rules_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubBranchProtectionRulesDataSource(t *testing.T) { diff --git a/github/data_source_github_branch_test.go b/github/data_source_github_branch_test.go index 8be51e858e..6958629294 100644 --- a/github/data_source_github_branch_test.go +++ b/github/data_source_github_branch_test.go @@ -5,8 +5,8 @@ import ( "regexp" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubBranchDataSource(t *testing.T) { diff --git a/github/data_source_github_codespaces_organization_public_key_test.go b/github/data_source_github_codespaces_organization_public_key_test.go index 15ca95ff65..7d8afeaadc 100644 --- a/github/data_source_github_codespaces_organization_public_key_test.go +++ b/github/data_source_github_codespaces_organization_public_key_test.go @@ -3,7 +3,7 @@ package github import ( "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubCodespacesOrganizationPublicKeyDataSource(t *testing.T) { diff --git a/github/data_source_github_codespaces_organization_secrets_test.go b/github/data_source_github_codespaces_organization_secrets_test.go index 88ff440bde..b340978f04 100644 --- a/github/data_source_github_codespaces_organization_secrets_test.go +++ b/github/data_source_github_codespaces_organization_secrets_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubCodespacesOrganizationSecretsDataSource(t *testing.T) { diff --git a/github/data_source_github_codespaces_public_key_test.go b/github/data_source_github_codespaces_public_key_test.go index 85dd76394d..5848b8f3c6 100644 --- a/github/data_source_github_codespaces_public_key_test.go +++ b/github/data_source_github_codespaces_public_key_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubCodespacesPublicKeyDataSource(t *testing.T) { diff --git a/github/data_source_github_codespaces_secrets_test.go b/github/data_source_github_codespaces_secrets_test.go index c71a90270f..0bd8e37479 100644 --- a/github/data_source_github_codespaces_secrets_test.go +++ b/github/data_source_github_codespaces_secrets_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubCodespacesSecretsDataSource(t *testing.T) { diff --git a/github/data_source_github_codespaces_user_public_key_test.go b/github/data_source_github_codespaces_user_public_key_test.go index 44fdd34cd1..62c9f47506 100644 --- a/github/data_source_github_codespaces_user_public_key_test.go +++ b/github/data_source_github_codespaces_user_public_key_test.go @@ -3,7 +3,7 @@ package github import ( "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubCodespacesUserPublicKeyDataSource(t *testing.T) { diff --git a/github/data_source_github_codespaces_user_secrets_test.go b/github/data_source_github_codespaces_user_secrets_test.go index 6db857dd70..483f82a83b 100644 --- a/github/data_source_github_codespaces_user_secrets_test.go +++ b/github/data_source_github_codespaces_user_secrets_test.go @@ -5,8 +5,8 @@ import ( "strings" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubCodespacesUserSecretsDataSource(t *testing.T) { diff --git a/github/data_source_github_collaborators_test.go b/github/data_source_github_collaborators_test.go index d7733331a1..16f80acec3 100644 --- a/github/data_source_github_collaborators_test.go +++ b/github/data_source_github_collaborators_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubCollaboratorsDataSource(t *testing.T) { diff --git a/github/data_source_github_dependabot_organization_public_key_test.go b/github/data_source_github_dependabot_organization_public_key_test.go index 2e6ac3ef04..8df4aa5a43 100644 --- a/github/data_source_github_dependabot_organization_public_key_test.go +++ b/github/data_source_github_dependabot_organization_public_key_test.go @@ -3,7 +3,7 @@ package github import ( "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubDependabotOrganizationPublicKeyDataSource(t *testing.T) { diff --git a/github/data_source_github_dependabot_organization_secrets_test.go b/github/data_source_github_dependabot_organization_secrets_test.go index d70aac505c..6b72579982 100644 --- a/github/data_source_github_dependabot_organization_secrets_test.go +++ b/github/data_source_github_dependabot_organization_secrets_test.go @@ -5,8 +5,8 @@ import ( "strings" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubDependabotOrganizationSecretsDataSource(t *testing.T) { diff --git a/github/data_source_github_dependabot_public_key_test.go b/github/data_source_github_dependabot_public_key_test.go index 6da9b92e5d..24ae804bb9 100644 --- a/github/data_source_github_dependabot_public_key_test.go +++ b/github/data_source_github_dependabot_public_key_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubDependabotPublicKeyDataSource(t *testing.T) { diff --git a/github/data_source_github_dependabot_secrets_test.go b/github/data_source_github_dependabot_secrets_test.go index 694d0c422b..646aea04da 100644 --- a/github/data_source_github_dependabot_secrets_test.go +++ b/github/data_source_github_dependabot_secrets_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubDependabotSecretsDataSource(t *testing.T) { diff --git a/github/data_source_github_enterprise_test.go b/github/data_source_github_enterprise_test.go index e9729ee61d..7db4b65000 100644 --- a/github/data_source_github_enterprise_test.go +++ b/github/data_source_github_enterprise_test.go @@ -4,7 +4,7 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubEnterpriseDataSource(t *testing.T) { diff --git a/github/data_source_github_ip_ranges_test.go b/github/data_source_github_ip_ranges_test.go index a7e6295a9b..6cb519c465 100644 --- a/github/data_source_github_ip_ranges_test.go +++ b/github/data_source_github_ip_ranges_test.go @@ -3,7 +3,7 @@ package github import ( "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubIpRangesDataSource(t *testing.T) { diff --git a/github/data_source_github_issue_labels_test.go b/github/data_source_github_issue_labels_test.go index c03b48493c..bd996d3f27 100644 --- a/github/data_source_github_issue_labels_test.go +++ b/github/data_source_github_issue_labels_test.go @@ -5,8 +5,8 @@ import ( "regexp" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubIssueLabelsDataSource(t *testing.T) { diff --git a/github/data_source_github_membership_test.go b/github/data_source_github_membership_test.go index 4077d3fcd6..108aa01b83 100644 --- a/github/data_source_github_membership_test.go +++ b/github/data_source_github_membership_test.go @@ -5,7 +5,7 @@ import ( "regexp" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubMembershipDataSource(t *testing.T) { diff --git a/github/data_source_github_organization_custom_role_test.go b/github/data_source_github_organization_custom_role_test.go index 414f5e7dde..c3a7048c93 100644 --- a/github/data_source_github_organization_custom_role_test.go +++ b/github/data_source_github_organization_custom_role_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubOrganizationCustomRoleDataSource(t *testing.T) { diff --git a/github/data_source_github_organization_external_identities_test.go b/github/data_source_github_organization_external_identities_test.go index 823734a134..b071ad0655 100644 --- a/github/data_source_github_organization_external_identities_test.go +++ b/github/data_source_github_organization_external_identities_test.go @@ -3,7 +3,7 @@ package github import ( "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubOrganizationExternalIdentities(t *testing.T) { diff --git a/github/data_source_github_organization_ip_allow_list_test.go b/github/data_source_github_organization_ip_allow_list_test.go index 2647ec6ea5..8bc2157bac 100644 --- a/github/data_source_github_organization_ip_allow_list_test.go +++ b/github/data_source_github_organization_ip_allow_list_test.go @@ -3,7 +3,7 @@ package github import ( "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubOrganizationIpAllowListDataSource(t *testing.T) { diff --git a/github/data_source_github_organization_repository_role_test.go b/github/data_source_github_organization_repository_role_test.go index c323b7461d..3367d51356 100644 --- a/github/data_source_github_organization_repository_role_test.go +++ b/github/data_source_github_organization_repository_role_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubOrganizationRepositoryRoleDataSource(t *testing.T) { diff --git a/github/data_source_github_organization_repository_roles_test.go b/github/data_source_github_organization_repository_roles_test.go index 5167762593..466722a068 100644 --- a/github/data_source_github_organization_repository_roles_test.go +++ b/github/data_source_github_organization_repository_roles_test.go @@ -3,7 +3,7 @@ package github import ( "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccDataSourceGithubOrganizationRepositoryRoles(t *testing.T) { diff --git a/github/data_source_github_organization_role_teams_test.go b/github/data_source_github_organization_role_teams_test.go index 2d75289e6f..c37db7eb05 100644 --- a/github/data_source_github_organization_role_teams_test.go +++ b/github/data_source_github_organization_role_teams_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccDataSourceGithubOrganizationRoleTeams(t *testing.T) { diff --git a/github/data_source_github_organization_role_test.go b/github/data_source_github_organization_role_test.go index e284c5ac9b..249a1fef77 100644 --- a/github/data_source_github_organization_role_test.go +++ b/github/data_source_github_organization_role_test.go @@ -5,7 +5,7 @@ import ( "strconv" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccDataSourceGithubOrganizationRole(t *testing.T) { diff --git a/github/data_source_github_organization_role_users_test.go b/github/data_source_github_organization_role_users_test.go index 652daf078e..4a831b245e 100644 --- a/github/data_source_github_organization_role_users_test.go +++ b/github/data_source_github_organization_role_users_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccDataSourceGithubOrganizationRoleUsers(t *testing.T) { diff --git a/github/data_source_github_organization_roles_test.go b/github/data_source_github_organization_roles_test.go index fc764e0704..15264c10c0 100644 --- a/github/data_source_github_organization_roles_test.go +++ b/github/data_source_github_organization_roles_test.go @@ -3,7 +3,7 @@ package github import ( "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccDataSourceGithubOrganizationRoles(t *testing.T) { diff --git a/github/data_source_github_organization_security_managers_test.go b/github/data_source_github_organization_security_managers_test.go index f0aeddd791..271a31dded 100644 --- a/github/data_source_github_organization_security_managers_test.go +++ b/github/data_source_github_organization_security_managers_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccDataSourceGithubOrganizationSecurityManagers(t *testing.T) { diff --git a/github/data_source_github_organization_team_sync_groups_test.go b/github/data_source_github_organization_team_sync_groups_test.go index 2c27319a81..a0779c84fb 100644 --- a/github/data_source_github_organization_team_sync_groups_test.go +++ b/github/data_source_github_organization_team_sync_groups_test.go @@ -3,7 +3,7 @@ package github import ( "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubOrganizationTeamSyncGroupsDataSource_existing(t *testing.T) { diff --git a/github/data_source_github_organization_teams_test.go b/github/data_source_github_organization_teams_test.go index 31d082283c..ebd1a0fe67 100644 --- a/github/data_source_github_organization_teams_test.go +++ b/github/data_source_github_organization_teams_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubOrganizationTeamsDataSource(t *testing.T) { diff --git a/github/data_source_github_organization_test.go b/github/data_source_github_organization_test.go index f9d4f0a5c1..474c78b7f4 100644 --- a/github/data_source_github_organization_test.go +++ b/github/data_source_github_organization_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubOrganizationDataSource(t *testing.T) { diff --git a/github/data_source_github_organization_webhooks_test.go b/github/data_source_github_organization_webhooks_test.go index 11e8465e10..7d25d690dd 100644 --- a/github/data_source_github_organization_webhooks_test.go +++ b/github/data_source_github_organization_webhooks_test.go @@ -3,7 +3,7 @@ package github import ( "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubOrganizationWebhooksDataSource(t *testing.T) { diff --git a/github/data_source_github_ref_test.go b/github/data_source_github_ref_test.go index 9af723c874..2e117cb510 100644 --- a/github/data_source_github_ref_test.go +++ b/github/data_source_github_ref_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubRefDataSource(t *testing.T) { diff --git a/github/data_source_github_release_asset_test.go b/github/data_source_github_release_asset_test.go index 134ee87668..019cdb6afe 100644 --- a/github/data_source_github_release_asset_test.go +++ b/github/data_source_github_release_asset_test.go @@ -4,7 +4,7 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubReleaseAssetDataSource(t *testing.T) { diff --git a/github/data_source_github_release_test.go b/github/data_source_github_release_test.go index f695bbfe55..20efff6a99 100644 --- a/github/data_source_github_release_test.go +++ b/github/data_source_github_release_test.go @@ -6,7 +6,7 @@ import ( "strconv" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubReleaseDataSource(t *testing.T) { diff --git a/github/data_source_github_repositories_test.go b/github/data_source_github_repositories_test.go index cf3afcab0f..f8ab3a4f85 100644 --- a/github/data_source_github_repositories_test.go +++ b/github/data_source_github_repositories_test.go @@ -5,7 +5,7 @@ import ( "regexp" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubRepositoriesDataSource(t *testing.T) { diff --git a/github/data_source_github_repository_autolink_references_test.go b/github/data_source_github_repository_autolink_references_test.go index 54261ec959..8039f4ef39 100644 --- a/github/data_source_github_repository_autolink_references_test.go +++ b/github/data_source_github_repository_autolink_references_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubRepositoryAutolinkReferencesDataSource(t *testing.T) { diff --git a/github/data_source_github_repository_branches_test.go b/github/data_source_github_repository_branches_test.go index 55cfd86cbb..5c1263b882 100644 --- a/github/data_source_github_repository_branches_test.go +++ b/github/data_source_github_repository_branches_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubRepositoryBranchesDataSource(t *testing.T) { diff --git a/github/data_source_github_repository_custom_properties_test.go b/github/data_source_github_repository_custom_properties_test.go index 84b7cf3016..de2f82acc3 100644 --- a/github/data_source_github_repository_custom_properties_test.go +++ b/github/data_source_github_repository_custom_properties_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubRepositoryCustomPropertiesDataSource(t *testing.T) { diff --git a/github/data_source_github_repository_deploy_keys_test.go b/github/data_source_github_repository_deploy_keys_test.go index dab6d610bd..28e2d266cc 100644 --- a/github/data_source_github_repository_deploy_keys_test.go +++ b/github/data_source_github_repository_deploy_keys_test.go @@ -7,8 +7,8 @@ import ( "strings" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubRepositoryDeployKeysDataSource(t *testing.T) { diff --git a/github/data_source_github_repository_deployment_branch_policies_test.go b/github/data_source_github_repository_deployment_branch_policies_test.go index 2c1e1a251d..8ee470b88c 100644 --- a/github/data_source_github_repository_deployment_branch_policies_test.go +++ b/github/data_source_github_repository_deployment_branch_policies_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubRepositoryDeploymentBranchPolicies(t *testing.T) { diff --git a/github/data_source_github_repository_environment_deployment_policies_test.go b/github/data_source_github_repository_environment_deployment_policies_test.go index a85b608a01..efb91a1951 100644 --- a/github/data_source_github_repository_environment_deployment_policies_test.go +++ b/github/data_source_github_repository_environment_deployment_policies_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubRepositoryEnvironmentDeploymentPolicies(t *testing.T) { diff --git a/github/data_source_github_repository_environments_test.go b/github/data_source_github_repository_environments_test.go index e7171011a9..0e819b3c09 100644 --- a/github/data_source_github_repository_environments_test.go +++ b/github/data_source_github_repository_environments_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubRepositoryEnvironmentsDataSource(t *testing.T) { diff --git a/github/data_source_github_repository_file_test.go b/github/data_source_github_repository_file_test.go index 27bba16c29..abb53ab289 100644 --- a/github/data_source_github_repository_file_test.go +++ b/github/data_source_github_repository_file_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubRepositoryFileDataSource(t *testing.T) { diff --git a/github/data_source_github_repository_milestone_test.go b/github/data_source_github_repository_milestone_test.go index 485110d9d2..bb0018f3ea 100644 --- a/github/data_source_github_repository_milestone_test.go +++ b/github/data_source_github_repository_milestone_test.go @@ -4,9 +4,9 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubRepositoryMilestoneDataSource(t *testing.T) { diff --git a/github/data_source_github_repository_pull_request_test.go b/github/data_source_github_repository_pull_request_test.go index bacdc008a1..d3818cfa2a 100644 --- a/github/data_source_github_repository_pull_request_test.go +++ b/github/data_source_github_repository_pull_request_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubRepositoryPullRequestDataSource(t *testing.T) { diff --git a/github/data_source_github_repository_pull_requests_test.go b/github/data_source_github_repository_pull_requests_test.go index 7bf4ce6098..4579382470 100644 --- a/github/data_source_github_repository_pull_requests_test.go +++ b/github/data_source_github_repository_pull_requests_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubRepositoryPullRequestsDataSource(t *testing.T) { diff --git a/github/data_source_github_repository_teams_test.go b/github/data_source_github_repository_teams_test.go index 4decdfd3a0..a4f1cb123e 100644 --- a/github/data_source_github_repository_teams_test.go +++ b/github/data_source_github_repository_teams_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubRepositoryTeamsDataSource(t *testing.T) { diff --git a/github/data_source_github_repository_test.go b/github/data_source_github_repository_test.go index c9d1e5aaf5..515c639aab 100644 --- a/github/data_source_github_repository_test.go +++ b/github/data_source_github_repository_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccDataSourceGithubRepository(t *testing.T) { diff --git a/github/data_source_github_repository_webhooks_test.go b/github/data_source_github_repository_webhooks_test.go index da1dd83277..c521278f1d 100644 --- a/github/data_source_github_repository_webhooks_test.go +++ b/github/data_source_github_repository_webhooks_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubRepositoryWebhooksDataSource(t *testing.T) { diff --git a/github/data_source_github_rest_api_test.go b/github/data_source_github_rest_api_test.go index 030d2d7865..d1024cb6ad 100644 --- a/github/data_source_github_rest_api_test.go +++ b/github/data_source_github_rest_api_test.go @@ -5,8 +5,8 @@ import ( "regexp" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubRestApiDataSource(t *testing.T) { diff --git a/github/data_source_github_ssh_keys_test.go b/github/data_source_github_ssh_keys_test.go index 372045dea4..0957b1e229 100644 --- a/github/data_source_github_ssh_keys_test.go +++ b/github/data_source_github_ssh_keys_test.go @@ -3,7 +3,7 @@ package github import ( "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubSshKeysDataSource(t *testing.T) { diff --git a/github/data_source_github_team_test.go b/github/data_source_github_team_test.go index 7967a26ec6..09c1da4a9c 100644 --- a/github/data_source_github_team_test.go +++ b/github/data_source_github_team_test.go @@ -5,8 +5,8 @@ import ( "regexp" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubTeamDataSource(t *testing.T) { diff --git a/github/data_source_github_tree_test.go b/github/data_source_github_tree_test.go index 394c079fb6..c7fb797e00 100644 --- a/github/data_source_github_tree_test.go +++ b/github/data_source_github_tree_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubTreeDataSource(t *testing.T) { diff --git a/github/data_source_github_user_external_identity_test.go b/github/data_source_github_user_external_identity_test.go index 8a48c7cd68..8c9be701fd 100644 --- a/github/data_source_github_user_external_identity_test.go +++ b/github/data_source_github_user_external_identity_test.go @@ -3,7 +3,7 @@ package github import ( "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubUserExternalIdentity(t *testing.T) { diff --git a/github/data_source_github_user_test.go b/github/data_source_github_user_test.go index 1c2cf63f62..31b6f48204 100644 --- a/github/data_source_github_user_test.go +++ b/github/data_source_github_user_test.go @@ -5,7 +5,7 @@ import ( "regexp" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubUserDataSource(t *testing.T) { diff --git a/github/data_source_github_users_test.go b/github/data_source_github_users_test.go index 3d60e7bcdc..905b8f5a32 100644 --- a/github/data_source_github_users_test.go +++ b/github/data_source_github_users_test.go @@ -4,7 +4,7 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) // TODO: this is failing. diff --git a/github/provider_test.go b/github/provider_test.go index caca4fdb7d..4f022ce4d4 100644 --- a/github/provider_test.go +++ b/github/provider_test.go @@ -5,8 +5,8 @@ import ( "regexp" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" ) func TestProvider(t *testing.T) { diff --git a/github/resource_github_actions_environment_secret_test.go b/github/resource_github_actions_environment_secret_test.go index 6b73af8a6c..193c8c2209 100644 --- a/github/resource_github_actions_environment_secret_test.go +++ b/github/resource_github_actions_environment_secret_test.go @@ -8,9 +8,10 @@ import ( "testing" "github.com/google/go-github/v82/github" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/terraform" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubActionsEnvironmentSecret(t *testing.T) { diff --git a/github/resource_github_actions_environment_variable_test.go b/github/resource_github_actions_environment_variable_test.go index cdfe4adda5..f0275871dc 100644 --- a/github/resource_github_actions_environment_variable_test.go +++ b/github/resource_github_actions_environment_variable_test.go @@ -8,9 +8,9 @@ import ( "testing" "github.com/google/go-github/v82/github" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" ) func TestAccGithubActionsEnvironmentVariable(t *testing.T) { diff --git a/github/resource_github_actions_hosted_runner_test.go b/github/resource_github_actions_hosted_runner_test.go index 22e8805d33..ff510d84aa 100644 --- a/github/resource_github_actions_hosted_runner_test.go +++ b/github/resource_github_actions_hosted_runner_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubActionsHostedRunner(t *testing.T) { diff --git a/github/resource_github_actions_organization_oidc_subject_claim_customization_template_test.go b/github/resource_github_actions_organization_oidc_subject_claim_customization_template_test.go index 3fd221fa73..ee9b786de9 100644 --- a/github/resource_github_actions_organization_oidc_subject_claim_customization_template_test.go +++ b/github/resource_github_actions_organization_oidc_subject_claim_customization_template_test.go @@ -4,7 +4,7 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubActionsOrganizationOIDCSubjectClaimCustomizationTemplate(t *testing.T) { diff --git a/github/resource_github_actions_organization_permissions_test.go b/github/resource_github_actions_organization_permissions_test.go index 11253569d2..8aa3b9516e 100644 --- a/github/resource_github_actions_organization_permissions_test.go +++ b/github/resource_github_actions_organization_permissions_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubActionsOrganizationPermissions(t *testing.T) { diff --git a/github/resource_github_actions_organization_secret_repositories_test.go b/github/resource_github_actions_organization_secret_repositories_test.go index 508ef5dd49..276798c569 100644 --- a/github/resource_github_actions_organization_secret_repositories_test.go +++ b/github/resource_github_actions_organization_secret_repositories_test.go @@ -5,8 +5,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubActionsOrganizationSecretRepositories(t *testing.T) { diff --git a/github/resource_github_actions_organization_secret_repository_test.go b/github/resource_github_actions_organization_secret_repository_test.go index ef0ff0e00e..d7dec7d116 100644 --- a/github/resource_github_actions_organization_secret_repository_test.go +++ b/github/resource_github_actions_organization_secret_repository_test.go @@ -5,8 +5,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubActionsOrganizationSecretRepository(t *testing.T) { diff --git a/github/resource_github_actions_organization_secret_test.go b/github/resource_github_actions_organization_secret_test.go index da469c53ef..782802b214 100644 --- a/github/resource_github_actions_organization_secret_test.go +++ b/github/resource_github_actions_organization_secret_test.go @@ -8,9 +8,9 @@ import ( "testing" "github.com/google/go-github/v82/github" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" ) func TestAccGithubActionsOrganizationSecret(t *testing.T) { diff --git a/github/resource_github_actions_organization_variable_repositories_test.go b/github/resource_github_actions_organization_variable_repositories_test.go index 323c2363ce..39c1a84ecf 100644 --- a/github/resource_github_actions_organization_variable_repositories_test.go +++ b/github/resource_github_actions_organization_variable_repositories_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubActionsOrganizationVariableRepositories(t *testing.T) { diff --git a/github/resource_github_actions_organization_variable_repository_test.go b/github/resource_github_actions_organization_variable_repository_test.go index 30c0ec68dd..e5d99ec490 100644 --- a/github/resource_github_actions_organization_variable_repository_test.go +++ b/github/resource_github_actions_organization_variable_repository_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubActionsOrganizationVariableRepository(t *testing.T) { diff --git a/github/resource_github_actions_organization_variable_test.go b/github/resource_github_actions_organization_variable_test.go index cd99944fa4..bbc32793aa 100644 --- a/github/resource_github_actions_organization_variable_test.go +++ b/github/resource_github_actions_organization_variable_test.go @@ -7,9 +7,9 @@ import ( "testing" "github.com/google/go-github/v82/github" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" ) func TestAccGithubActionsOrganizationVariable(t *testing.T) { diff --git a/github/resource_github_actions_organization_workflow_permissions_test.go b/github/resource_github_actions_organization_workflow_permissions_test.go index f1487068d6..8af9b10f1c 100644 --- a/github/resource_github_actions_organization_workflow_permissions_test.go +++ b/github/resource_github_actions_organization_workflow_permissions_test.go @@ -4,7 +4,7 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubActionsOrganizationWorkflowPermissions(t *testing.T) { diff --git a/github/resource_github_actions_repository_access_level_test.go b/github/resource_github_actions_repository_access_level_test.go index 2661cc2dba..e370a889f2 100644 --- a/github/resource_github_actions_repository_access_level_test.go +++ b/github/resource_github_actions_repository_access_level_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubActionsRepositoryAccessLevel(t *testing.T) { diff --git a/github/resource_github_actions_repository_oidc_subject_claim_customization_template_test.go b/github/resource_github_actions_repository_oidc_subject_claim_customization_template_test.go index ed29828432..89f359a770 100644 --- a/github/resource_github_actions_repository_oidc_subject_claim_customization_template_test.go +++ b/github/resource_github_actions_repository_oidc_subject_claim_customization_template_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubActionsRepositoryOIDCSubjectClaimCustomizationTemplate(t *testing.T) { diff --git a/github/resource_github_actions_repository_permissions_test.go b/github/resource_github_actions_repository_permissions_test.go index caada9479b..a64352a57c 100644 --- a/github/resource_github_actions_repository_permissions_test.go +++ b/github/resource_github_actions_repository_permissions_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubActionsRepositoryPermissions(t *testing.T) { diff --git a/github/resource_github_actions_runner_group_test.go b/github/resource_github_actions_runner_group_test.go index 094897739b..a399e5f140 100644 --- a/github/resource_github_actions_runner_group_test.go +++ b/github/resource_github_actions_runner_group_test.go @@ -4,11 +4,11 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-plugin-testing/terraform" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubActionsRunnerGroup(t *testing.T) { diff --git a/github/resource_github_actions_secret_test.go b/github/resource_github_actions_secret_test.go index 99f812a4cc..3c47224af9 100644 --- a/github/resource_github_actions_secret_test.go +++ b/github/resource_github_actions_secret_test.go @@ -7,9 +7,9 @@ import ( "testing" "github.com/google/go-github/v82/github" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" ) func TestAccGithubActionsSecret(t *testing.T) { diff --git a/github/resource_github_actions_variable_test.go b/github/resource_github_actions_variable_test.go index 0612c7268b..dc732f4296 100644 --- a/github/resource_github_actions_variable_test.go +++ b/github/resource_github_actions_variable_test.go @@ -7,9 +7,10 @@ import ( "testing" "github.com/google/go-github/v82/github" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/terraform" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubActionsVariable(t *testing.T) { diff --git a/github/resource_github_app_installation_repositories_test.go b/github/resource_github_app_installation_repositories_test.go index 8c07b8f13a..e0c2e3c915 100644 --- a/github/resource_github_app_installation_repositories_test.go +++ b/github/resource_github_app_installation_repositories_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubAppInstallationRepositories(t *testing.T) { diff --git a/github/resource_github_app_installation_repository_test.go b/github/resource_github_app_installation_repository_test.go index 70954a02e4..46f11e71c8 100644 --- a/github/resource_github_app_installation_repository_test.go +++ b/github/resource_github_app_installation_repository_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubAppInstallationRepository(t *testing.T) { diff --git a/github/resource_github_branch_default_test.go b/github/resource_github_branch_default_test.go index be6539949b..47855e32b4 100644 --- a/github/resource_github_branch_default_test.go +++ b/github/resource_github_branch_default_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubBranchDefault(t *testing.T) { diff --git a/github/resource_github_branch_protection_test.go b/github/resource_github_branch_protection_test.go index a45cd47927..6fa000258d 100644 --- a/github/resource_github_branch_protection_test.go +++ b/github/resource_github_branch_protection_test.go @@ -7,9 +7,9 @@ import ( "regexp" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" ) func TestAccGithubBranchProtectionV4(t *testing.T) { diff --git a/github/resource_github_branch_protection_v3_test.go b/github/resource_github_branch_protection_v3_test.go index 1cd60f6d18..6ef6f8cb82 100644 --- a/github/resource_github_branch_protection_v3_test.go +++ b/github/resource_github_branch_protection_v3_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubBranchProtectionV3_required_pull_request_reviews(t *testing.T) { diff --git a/github/resource_github_branch_test.go b/github/resource_github_branch_test.go index dfa5fbcc78..b79af29781 100644 --- a/github/resource_github_branch_test.go +++ b/github/resource_github_branch_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubBranch(t *testing.T) { diff --git a/github/resource_github_codespaces_organization_secret_repositories_test.go b/github/resource_github_codespaces_organization_secret_repositories_test.go index 07d3ee9cb9..9dae401bc5 100644 --- a/github/resource_github_codespaces_organization_secret_repositories_test.go +++ b/github/resource_github_codespaces_organization_secret_repositories_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubCodespacesOrganizationSecretRepositories(t *testing.T) { diff --git a/github/resource_github_codespaces_organization_secret_test.go b/github/resource_github_codespaces_organization_secret_test.go index 6c71f046cb..42f39fa66c 100644 --- a/github/resource_github_codespaces_organization_secret_test.go +++ b/github/resource_github_codespaces_organization_secret_test.go @@ -5,7 +5,7 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubCodespacesOrganizationSecret(t *testing.T) { diff --git a/github/resource_github_codespaces_secret_test.go b/github/resource_github_codespaces_secret_test.go index 55bf8e9665..46719ae9a7 100644 --- a/github/resource_github_codespaces_secret_test.go +++ b/github/resource_github_codespaces_secret_test.go @@ -6,9 +6,9 @@ import ( "strings" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubCodespacesSecret(t *testing.T) { diff --git a/github/resource_github_codespaces_user_secret_test.go b/github/resource_github_codespaces_user_secret_test.go index aae9a4b0cc..fec0494325 100644 --- a/github/resource_github_codespaces_user_secret_test.go +++ b/github/resource_github_codespaces_user_secret_test.go @@ -6,7 +6,7 @@ import ( "strings" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubCodespacesUserSecret(t *testing.T) { diff --git a/github/resource_github_dependabot_organization_secret_repositories_test.go b/github/resource_github_dependabot_organization_secret_repositories_test.go index fd66da858a..c3191d4c65 100644 --- a/github/resource_github_dependabot_organization_secret_repositories_test.go +++ b/github/resource_github_dependabot_organization_secret_repositories_test.go @@ -5,8 +5,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubDependabotOrganizationSecretRepositories(t *testing.T) { diff --git a/github/resource_github_dependabot_organization_secret_repository_test.go b/github/resource_github_dependabot_organization_secret_repository_test.go index 08280dab4e..04771315bc 100644 --- a/github/resource_github_dependabot_organization_secret_repository_test.go +++ b/github/resource_github_dependabot_organization_secret_repository_test.go @@ -5,8 +5,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubDependabotOrganizationSecretRepository(t *testing.T) { diff --git a/github/resource_github_dependabot_organization_secret_test.go b/github/resource_github_dependabot_organization_secret_test.go index ef55e98d22..733c6150d2 100644 --- a/github/resource_github_dependabot_organization_secret_test.go +++ b/github/resource_github_dependabot_organization_secret_test.go @@ -8,9 +8,9 @@ import ( "testing" "github.com/google/go-github/v82/github" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" ) func TestAccGithubDependabotOrganizationSecret(t *testing.T) { diff --git a/github/resource_github_dependabot_secret_test.go b/github/resource_github_dependabot_secret_test.go index 04db8fa79c..cc6e66c363 100644 --- a/github/resource_github_dependabot_secret_test.go +++ b/github/resource_github_dependabot_secret_test.go @@ -7,9 +7,10 @@ import ( "testing" "github.com/google/go-github/v82/github" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/terraform" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubDependabotSecret(t *testing.T) { diff --git a/github/resource_github_emu_group_mapping_test.go b/github/resource_github_emu_group_mapping_test.go index 209572a27e..aeee3f8e32 100644 --- a/github/resource_github_emu_group_mapping_test.go +++ b/github/resource_github_emu_group_mapping_test.go @@ -6,9 +6,9 @@ import ( "strconv" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" ) func TestAccGithubEMUGroupMapping(t *testing.T) { diff --git a/github/resource_github_enterprise_actions_permissions_test.go b/github/resource_github_enterprise_actions_permissions_test.go index 1baa99175e..4511865293 100644 --- a/github/resource_github_enterprise_actions_permissions_test.go +++ b/github/resource_github_enterprise_actions_permissions_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubActionsEnterprisePermissions(t *testing.T) { diff --git a/github/resource_github_enterprise_actions_runner_group_test.go b/github/resource_github_enterprise_actions_runner_group_test.go index 3695b870d1..fec43eb55d 100644 --- a/github/resource_github_enterprise_actions_runner_group_test.go +++ b/github/resource_github_enterprise_actions_runner_group_test.go @@ -4,9 +4,9 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubActionsEnterpriseRunnerGroup(t *testing.T) { diff --git a/github/resource_github_enterprise_actions_workflow_permissions_test.go b/github/resource_github_enterprise_actions_workflow_permissions_test.go index 7e8f90d2fc..4ef51d55ed 100644 --- a/github/resource_github_enterprise_actions_workflow_permissions_test.go +++ b/github/resource_github_enterprise_actions_workflow_permissions_test.go @@ -4,7 +4,7 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubEnterpriseActionsWorkflowPermissions(t *testing.T) { diff --git a/github/resource_github_enterprise_organization_test.go b/github/resource_github_enterprise_organization_test.go index dffc609653..94e7b33040 100644 --- a/github/resource_github_enterprise_organization_test.go +++ b/github/resource_github_enterprise_organization_test.go @@ -9,8 +9,8 @@ import ( "testing" "github.com/google/go-github/v82/github" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestIsSAMLEnforcementError(t *testing.T) { diff --git a/github/resource_github_enterprise_security_analysis_settings_test.go b/github/resource_github_enterprise_security_analysis_settings_test.go index 5dc3cf29a6..30033ee6ed 100644 --- a/github/resource_github_enterprise_security_analysis_settings_test.go +++ b/github/resource_github_enterprise_security_analysis_settings_test.go @@ -4,7 +4,7 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubEnterpriseSecurityAnalysisSettings(t *testing.T) { diff --git a/github/resource_github_etag_acc_test.go b/github/resource_github_etag_acc_test.go index 42353af166..0cb16fe4e0 100644 --- a/github/resource_github_etag_acc_test.go +++ b/github/resource_github_etag_acc_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) // TestAccGithubRepositoryEtagPresent tests that etag field is populated. diff --git a/github/resource_github_issue_label_test.go b/github/resource_github_issue_label_test.go index 65858f83c7..7bf1178aa3 100644 --- a/github/resource_github_issue_label_test.go +++ b/github/resource_github_issue_label_test.go @@ -5,8 +5,8 @@ import ( "strings" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubIssueLabel(t *testing.T) { diff --git a/github/resource_github_issue_labels_test.go b/github/resource_github_issue_labels_test.go index 109b4b4fbc..9465eb0d68 100644 --- a/github/resource_github_issue_labels_test.go +++ b/github/resource_github_issue_labels_test.go @@ -5,8 +5,8 @@ import ( "strings" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubIssueLabels(t *testing.T) { diff --git a/github/resource_github_issue_test.go b/github/resource_github_issue_test.go index 3741dd15a6..57d830d839 100644 --- a/github/resource_github_issue_test.go +++ b/github/resource_github_issue_test.go @@ -4,10 +4,10 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-plugin-testing/terraform" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubIssue(t *testing.T) { diff --git a/github/resource_github_membership_test.go b/github/resource_github_membership_test.go index 1f6ddbfdec..60ce5db031 100644 --- a/github/resource_github_membership_test.go +++ b/github/resource_github_membership_test.go @@ -7,8 +7,8 @@ import ( "testing" "github.com/google/go-github/v82/github" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" ) func TestAccGithubMembership(t *testing.T) { diff --git a/github/resource_github_organization_custom_properties_test.go b/github/resource_github_organization_custom_properties_test.go index d517e66a74..c4f55d752d 100644 --- a/github/resource_github_organization_custom_properties_test.go +++ b/github/resource_github_organization_custom_properties_test.go @@ -5,7 +5,7 @@ import ( "regexp" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubOrganizationCustomPropertiesValidation(t *testing.T) { diff --git a/github/resource_github_organization_custom_role_test.go b/github/resource_github_organization_custom_role_test.go index 25af7af271..15a8284a0e 100644 --- a/github/resource_github_organization_custom_role_test.go +++ b/github/resource_github_organization_custom_role_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubOrganizationCustomRole(t *testing.T) { diff --git a/github/resource_github_organization_project_test.go b/github/resource_github_organization_project_test.go index 3fcf0f5583..c1828e2ec0 100644 --- a/github/resource_github_organization_project_test.go +++ b/github/resource_github_organization_project_test.go @@ -8,8 +8,8 @@ package github // "testing" // "github.com/google/go-github/v82/github" -// "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" -// "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +// "github.com/hashicorp/terraform-plugin-testing/helper/resource" +// "github.com/hashicorp/terraform-plugin-testing/terraform" // ) // func TestAccGithubOrganizationProject_basic(t *testing.T) { diff --git a/github/resource_github_organization_repository_role_test.go b/github/resource_github_organization_repository_role_test.go index ad1c413c1a..72116c55a3 100644 --- a/github/resource_github_organization_repository_role_test.go +++ b/github/resource_github_organization_repository_role_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubOrganizationRepositoryRole(t *testing.T) { diff --git a/github/resource_github_organization_role_team_assignment_test.go b/github/resource_github_organization_role_team_assignment_test.go index 3afeedcd2d..9465c34084 100644 --- a/github/resource_github_organization_role_team_assignment_test.go +++ b/github/resource_github_organization_role_team_assignment_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubOrganizationRoleTeamAssignment(t *testing.T) { diff --git a/github/resource_github_organization_role_team_test.go b/github/resource_github_organization_role_team_test.go index 5dd02ba280..5c1f7e6897 100644 --- a/github/resource_github_organization_role_team_test.go +++ b/github/resource_github_organization_role_team_test.go @@ -5,8 +5,8 @@ import ( "strconv" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubOrganizationRoleTeam(t *testing.T) { diff --git a/github/resource_github_organization_role_test.go b/github/resource_github_organization_role_test.go index 66999da5a1..efc3c28b98 100644 --- a/github/resource_github_organization_role_test.go +++ b/github/resource_github_organization_role_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubOrganizationRole(t *testing.T) { diff --git a/github/resource_github_organization_role_user_test.go b/github/resource_github_organization_role_user_test.go index 3786c3c0b5..4b9628bf0c 100644 --- a/github/resource_github_organization_role_user_test.go +++ b/github/resource_github_organization_role_user_test.go @@ -5,7 +5,7 @@ import ( "strconv" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubOrganizationRoleUser(t *testing.T) { diff --git a/github/resource_github_organization_ruleset_test.go b/github/resource_github_organization_ruleset_test.go index 539a2511cf..6f8dd502c5 100644 --- a/github/resource_github_organization_ruleset_test.go +++ b/github/resource_github_organization_ruleset_test.go @@ -5,9 +5,9 @@ import ( "regexp" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubOrganizationRuleset(t *testing.T) { diff --git a/github/resource_github_organization_security_manager_test.go b/github/resource_github_organization_security_manager_test.go index e8611c133b..e65286274a 100644 --- a/github/resource_github_organization_security_manager_test.go +++ b/github/resource_github_organization_security_manager_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubOrganizationSecurityManager(t *testing.T) { diff --git a/github/resource_github_organization_settings_test.go b/github/resource_github_organization_settings_test.go index 5706dedea3..742f779b3e 100644 --- a/github/resource_github_organization_settings_test.go +++ b/github/resource_github_organization_settings_test.go @@ -4,7 +4,7 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubOrganizationSettings(t *testing.T) { diff --git a/github/resource_github_organization_webhook_test.go b/github/resource_github_organization_webhook_test.go index 77c44b9a09..d3ceed7e40 100644 --- a/github/resource_github_organization_webhook_test.go +++ b/github/resource_github_organization_webhook_test.go @@ -5,8 +5,8 @@ import ( "strings" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubOrganizationWebhook(t *testing.T) { diff --git a/github/resource_github_project_card_test.go b/github/resource_github_project_card_test.go index bd802b5b55..ee5c808b07 100644 --- a/github/resource_github_project_card_test.go +++ b/github/resource_github_project_card_test.go @@ -4,10 +4,10 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-plugin-testing/terraform" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubProjectCard(t *testing.T) { diff --git a/github/resource_github_project_column_test.go b/github/resource_github_project_column_test.go index f610d5cd82..d4497b148a 100644 --- a/github/resource_github_project_column_test.go +++ b/github/resource_github_project_column_test.go @@ -7,8 +7,8 @@ package github // "testing" // "github.com/google/go-github/v82/github" -// "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" -// "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" +// "github.com/hashicorp/terraform-plugin-testing/helper/resource" +// "github.com/hashicorp/terraform-plugin-testing/terraform" // ) // func TestAccGithubProjectColumn_basic(t *testing.T) { diff --git a/github/resource_github_release_test.go b/github/resource_github_release_test.go index 4b2694272b..70d2b409aa 100644 --- a/github/resource_github_release_test.go +++ b/github/resource_github_release_test.go @@ -5,10 +5,10 @@ import ( "log" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-plugin-testing/terraform" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubReleaseResource(t *testing.T) { diff --git a/github/resource_github_repository_autolink_reference_test.go b/github/resource_github_repository_autolink_reference_test.go index d057808cf9..e97b14abf6 100644 --- a/github/resource_github_repository_autolink_reference_test.go +++ b/github/resource_github_repository_autolink_reference_test.go @@ -5,8 +5,8 @@ import ( "regexp" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubRepositoryAutolinkReference(t *testing.T) { diff --git a/github/resource_github_repository_collaborator_test.go b/github/resource_github_repository_collaborator_test.go index 43597984b1..7a3d8a0a8d 100644 --- a/github/resource_github_repository_collaborator_test.go +++ b/github/resource_github_repository_collaborator_test.go @@ -5,8 +5,8 @@ import ( "os" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubRepositoryCollaborator(t *testing.T) { diff --git a/github/resource_github_repository_collaborators_test.go b/github/resource_github_repository_collaborators_test.go index b3bcbbf212..43e31b3b8d 100644 --- a/github/resource_github_repository_collaborators_test.go +++ b/github/resource_github_repository_collaborators_test.go @@ -6,9 +6,9 @@ import ( "testing" "github.com/google/go-github/v82/github" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" ) func TestAccGithubRepositoryCollaborators(t *testing.T) { diff --git a/github/resource_github_repository_custom_property_test.go b/github/resource_github_repository_custom_property_test.go index 5efe6fd8e7..9fcb28b496 100644 --- a/github/resource_github_repository_custom_property_test.go +++ b/github/resource_github_repository_custom_property_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubRepositoryCustomProperty(t *testing.T) { diff --git a/github/resource_github_repository_dependabot_security_updates_test.go b/github/resource_github_repository_dependabot_security_updates_test.go index 6b42a602c7..b656902f86 100644 --- a/github/resource_github_repository_dependabot_security_updates_test.go +++ b/github/resource_github_repository_dependabot_security_updates_test.go @@ -5,8 +5,8 @@ import ( "strings" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubRepositoryDependabotSecurityUpdates(t *testing.T) { diff --git a/github/resource_github_repository_deploy_key_test.go b/github/resource_github_repository_deploy_key_test.go index 9dda76697c..973725836f 100644 --- a/github/resource_github_repository_deploy_key_test.go +++ b/github/resource_github_repository_deploy_key_test.go @@ -10,9 +10,9 @@ import ( "strings" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" ) func TestSuppressDeployKeyDiff(t *testing.T) { diff --git a/github/resource_github_repository_deployment_branch_policy_test.go b/github/resource_github_repository_deployment_branch_policy_test.go index 403911619d..49b13fc5ac 100644 --- a/github/resource_github_repository_deployment_branch_policy_test.go +++ b/github/resource_github_repository_deployment_branch_policy_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubRepositoryDeploymentBranchPolicy(t *testing.T) { diff --git a/github/resource_github_repository_environment_deployment_policy_test.go b/github/resource_github_repository_environment_deployment_policy_test.go index 73c962ff50..57508cffa2 100644 --- a/github/resource_github_repository_environment_deployment_policy_test.go +++ b/github/resource_github_repository_environment_deployment_policy_test.go @@ -5,9 +5,9 @@ import ( "regexp" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" ) func TestAccGithubRepositoryEnvironmentDeploymentPolicy(t *testing.T) { diff --git a/github/resource_github_repository_environment_test.go b/github/resource_github_repository_environment_test.go index db4ee9cdea..3ea4e60d18 100644 --- a/github/resource_github_repository_environment_test.go +++ b/github/resource_github_repository_environment_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubRepositoryEnvironment(t *testing.T) { diff --git a/github/resource_github_repository_file_test.go b/github/resource_github_repository_file_test.go index 8dba39abba..57343b6dd8 100644 --- a/github/resource_github_repository_file_test.go +++ b/github/resource_github_repository_file_test.go @@ -6,8 +6,8 @@ import ( "strings" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubRepositoryFile(t *testing.T) { diff --git a/github/resource_github_repository_milestone_test.go b/github/resource_github_repository_milestone_test.go index 30346b94f7..82cd4221d2 100644 --- a/github/resource_github_repository_milestone_test.go +++ b/github/resource_github_repository_milestone_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubRepositoryMilestone(t *testing.T) { diff --git a/github/resource_github_repository_project_test.go b/github/resource_github_repository_project_test.go index 8ea8d8ce1e..fcf6b8fa26 100644 --- a/github/resource_github_repository_project_test.go +++ b/github/resource_github_repository_project_test.go @@ -5,8 +5,8 @@ import ( "regexp" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubRepositoryProject(t *testing.T) { diff --git a/github/resource_github_repository_pull_request_test.go b/github/resource_github_repository_pull_request_test.go index 816abd8a07..71e1ad2ebc 100644 --- a/github/resource_github_repository_pull_request_test.go +++ b/github/resource_github_repository_pull_request_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubRepositoryPullRequest(t *testing.T) { diff --git a/github/resource_github_repository_ruleset_test.go b/github/resource_github_repository_ruleset_test.go index 3cfc6d6ae0..a10affa289 100644 --- a/github/resource_github_repository_ruleset_test.go +++ b/github/resource_github_repository_ruleset_test.go @@ -6,9 +6,9 @@ import ( "regexp" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" ) func TestAccGithubRepositoryRuleset(t *testing.T) { diff --git a/github/resource_github_repository_test.go b/github/resource_github_repository_test.go index bd34c6f803..1589c84bf1 100644 --- a/github/resource_github_repository_test.go +++ b/github/resource_github_repository_test.go @@ -8,10 +8,10 @@ import ( "github.com/hashicorp/go-cty/cty" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-plugin-testing/terraform" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubRepository(t *testing.T) { diff --git a/github/resource_github_repository_topics_test.go b/github/resource_github_repository_topics_test.go index 69f9a0f216..b6ef36ab30 100644 --- a/github/resource_github_repository_topics_test.go +++ b/github/resource_github_repository_topics_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubRepositoryTopics(t *testing.T) { diff --git a/github/resource_github_repository_webhook_test.go b/github/resource_github_repository_webhook_test.go index 11eb1f0ce4..7ec97ab083 100644 --- a/github/resource_github_repository_webhook_test.go +++ b/github/resource_github_repository_webhook_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubRepositoryWebhook(t *testing.T) { diff --git a/github/resource_github_team_members_test.go b/github/resource_github_team_members_test.go index b00a3709cd..132b80ca25 100644 --- a/github/resource_github_team_members_test.go +++ b/github/resource_github_team_members_test.go @@ -5,9 +5,9 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" ) func TestAccGithubTeamMembers(t *testing.T) { diff --git a/github/resource_github_team_membership_test.go b/github/resource_github_team_membership_test.go index 02fa2f313c..d2248d8816 100644 --- a/github/resource_github_team_membership_test.go +++ b/github/resource_github_team_membership_test.go @@ -7,9 +7,9 @@ import ( "testing" "github.com/google/go-github/v82/github" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" ) func TestAccGithubTeamMembership(t *testing.T) { diff --git a/github/resource_github_team_repository_test.go b/github/resource_github_team_repository_test.go index 81baeb68d6..2c3c9ef597 100644 --- a/github/resource_github_team_repository_test.go +++ b/github/resource_github_team_repository_test.go @@ -5,8 +5,8 @@ import ( "strings" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubTeamRepository(t *testing.T) { diff --git a/github/resource_github_team_settings_test.go b/github/resource_github_team_settings_test.go index 053440a33f..09b46a0711 100644 --- a/github/resource_github_team_settings_test.go +++ b/github/resource_github_team_settings_test.go @@ -6,8 +6,8 @@ import ( "strings" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubTeamSettings(t *testing.T) { diff --git a/github/resource_github_team_sync_group_mapping_test.go b/github/resource_github_team_sync_group_mapping_test.go index a03fbce056..51ff3e5338 100644 --- a/github/resource_github_team_sync_group_mapping_test.go +++ b/github/resource_github_team_sync_group_mapping_test.go @@ -7,9 +7,9 @@ import ( "github.com/google/go-github/v82/github" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" ) func TestAccGithubTeamSyncGroupMapping_basic(t *testing.T) { diff --git a/github/resource_github_team_test.go b/github/resource_github_team_test.go index eb34895d32..42e3cc4f5d 100644 --- a/github/resource_github_team_test.go +++ b/github/resource_github_team_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubTeam(t *testing.T) { diff --git a/github/resource_github_user_gpg_key_test.go b/github/resource_github_user_gpg_key_test.go index 08b6feeaed..ab3c4b706d 100644 --- a/github/resource_github_user_gpg_key_test.go +++ b/github/resource_github_user_gpg_key_test.go @@ -7,7 +7,7 @@ import ( "strings" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubUserGpgKey(t *testing.T) { diff --git a/github/resource_github_user_invitation_accepter_test.go b/github/resource_github_user_invitation_accepter_test.go index 79a8a3ae9a..67d18ddd64 100644 --- a/github/resource_github_user_invitation_accepter_test.go +++ b/github/resource_github_user_invitation_accepter_test.go @@ -5,9 +5,9 @@ import ( "regexp" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" ) func TestAccGithubUserInvitationAccepter(t *testing.T) { diff --git a/github/resource_github_user_ssh_key_test.go b/github/resource_github_user_ssh_key_test.go index 80ab315b79..f21ac61239 100644 --- a/github/resource_github_user_ssh_key_test.go +++ b/github/resource_github_user_ssh_key_test.go @@ -8,8 +8,8 @@ import ( "strings" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" "golang.org/x/crypto/ssh" ) diff --git a/github/resource_github_workflow_repository_permissions_test.go b/github/resource_github_workflow_repository_permissions_test.go index ddf83333af..2ecbff09c7 100644 --- a/github/resource_github_workflow_repository_permissions_test.go +++ b/github/resource_github_workflow_repository_permissions_test.go @@ -4,8 +4,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" ) func TestAccGithubWorkflowRepositoryPermissions(t *testing.T) { diff --git a/github/resource_organization_block_test.go b/github/resource_organization_block_test.go index 0f54247d86..2541f05fb2 100644 --- a/github/resource_organization_block_test.go +++ b/github/resource_organization_block_test.go @@ -5,8 +5,8 @@ import ( "fmt" "testing" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" ) func TestAccOrganizationBlock_basic(t *testing.T) { diff --git a/go.mod b/go.mod index 63b7d23f87..0c244c5967 100644 --- a/go.mod +++ b/go.mod @@ -9,6 +9,7 @@ require ( github.com/hashicorp/go-cty v1.5.0 github.com/hashicorp/terraform-plugin-log v0.10.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.38.1 + github.com/hashicorp/terraform-plugin-testing v1.14.0 github.com/shurcooL/githubv4 v0.0.0-20221126192849-0b5c4c7994eb golang.org/x/crypto v0.47.0 golang.org/x/oauth2 v0.34.0 @@ -35,8 +36,8 @@ require ( github.com/hashicorp/hc-install v0.9.2 // indirect github.com/hashicorp/hcl/v2 v2.24.0 // indirect github.com/hashicorp/logutils v1.0.0 // indirect - github.com/hashicorp/terraform-exec v0.23.1 // indirect - github.com/hashicorp/terraform-json v0.27.1 // indirect + github.com/hashicorp/terraform-exec v0.24.0 // indirect + github.com/hashicorp/terraform-json v0.27.2 // indirect github.com/hashicorp/terraform-plugin-go v0.29.0 // indirect github.com/hashicorp/terraform-registry-address v0.4.0 // indirect github.com/hashicorp/terraform-svchost v0.1.1 // indirect diff --git a/go.sum b/go.sum index b7b2cb72cc..b2bd137805 100644 --- a/go.sum +++ b/go.sum @@ -86,16 +86,18 @@ github.com/hashicorp/hcl/v2 v2.24.0 h1:2QJdZ454DSsYGoaE6QheQZjtKZSUs9Nh2izTWiwQx github.com/hashicorp/hcl/v2 v2.24.0/go.mod h1:oGoO1FIQYfn/AgyOhlg9qLC6/nOJPX3qGbkZpYAcqfM= 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.23.1 h1:diK5NSSDXDKqHEOIQefBMu9ny+FhzwlwV0xgUTB7VTo= -github.com/hashicorp/terraform-exec v0.23.1/go.mod h1:e4ZEg9BJDRaSalGm2z8vvrPONt0XWG0/tXpmzYTf+dM= -github.com/hashicorp/terraform-json v0.27.1 h1:zWhEracxJW6lcjt/JvximOYyc12pS/gaKSy/wzzE7nY= -github.com/hashicorp/terraform-json v0.27.1/go.mod h1:GzPLJ1PLdUG5xL6xn1OXWIjteQRT2CNT9o/6A9mi9hE= +github.com/hashicorp/terraform-exec v0.24.0 h1:mL0xlk9H5g2bn0pPF6JQZk5YlByqSqrO5VoaNtAf8OE= +github.com/hashicorp/terraform-exec v0.24.0/go.mod h1:lluc/rDYfAhYdslLJQg3J0oDqo88oGQAdHR+wDqFvo4= +github.com/hashicorp/terraform-json v0.27.2 h1:BwGuzM6iUPqf9JYM/Z4AF1OJ5VVJEEzoKST/tRDBJKU= +github.com/hashicorp/terraform-json v0.27.2/go.mod h1:GzPLJ1PLdUG5xL6xn1OXWIjteQRT2CNT9o/6A9mi9hE= github.com/hashicorp/terraform-plugin-go v0.29.0 h1:1nXKl/nSpaYIUBU1IG/EsDOX0vv+9JxAltQyDMpq5mU= github.com/hashicorp/terraform-plugin-go v0.29.0/go.mod h1:vYZbIyvxyy0FWSmDHChCqKvI40cFTDGSb3D8D70i9GM= github.com/hashicorp/terraform-plugin-log v0.10.0 h1:eu2kW6/QBVdN4P3Ju2WiB2W3ObjkAsyfBsL3Wh1fj3g= github.com/hashicorp/terraform-plugin-log v0.10.0/go.mod h1:/9RR5Cv2aAbrqcTSdNmY1NRHP4E3ekrXRGjqORpXyB0= github.com/hashicorp/terraform-plugin-sdk/v2 v2.38.1 h1:mlAq/OrMlg04IuJT7NpefI1wwtdpWudnEmjuQs04t/4= github.com/hashicorp/terraform-plugin-sdk/v2 v2.38.1/go.mod h1:GQhpKVvvuwzD79e8/NZ+xzj+ZpWovdPAe8nfV/skwNU= +github.com/hashicorp/terraform-plugin-testing v1.14.0 h1:5t4VKrjOJ0rg0sVuSJ86dz5K7PHsMO6OKrHFzDBerWA= +github.com/hashicorp/terraform-plugin-testing v1.14.0/go.mod h1:1qfWkecyYe1Do2EEOK/5/WnTyvC8wQucUkkhiGLg5nk= github.com/hashicorp/terraform-registry-address v0.4.0 h1:S1yCGomj30Sao4l5BMPjTGZmCNzuv7/GDTDX99E9gTk= github.com/hashicorp/terraform-registry-address v0.4.0/go.mod h1:LRS1Ay0+mAiRkUyltGT+UHWkIqTFvigGn/LbMshfflE= github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ= diff --git a/vendor/github.com/hashicorp/terraform-exec/internal/version/version.go b/vendor/github.com/hashicorp/terraform-exec/internal/version/version.go index 72f2e1f7be..d360cb7525 100644 --- a/vendor/github.com/hashicorp/terraform-exec/internal/version/version.go +++ b/vendor/github.com/hashicorp/terraform-exec/internal/version/version.go @@ -3,7 +3,7 @@ package version -const version = "0.23.1" +const version = "0.24.0" // ModuleVersion returns the current version of the github.com/hashicorp/terraform-exec Go module. // This is a function to allow for future possible enhancement using debug.BuildInfo. diff --git a/vendor/github.com/hashicorp/terraform-exec/tfexec/cmd.go b/vendor/github.com/hashicorp/terraform-exec/tfexec/cmd.go index cd5a7e28bb..47e0e68766 100644 --- a/vendor/github.com/hashicorp/terraform-exec/tfexec/cmd.go +++ b/vendor/github.com/hashicorp/terraform-exec/tfexec/cmd.go @@ -12,12 +12,14 @@ import ( "fmt" "io" "io/ioutil" + "iter" "os" "os/exec" "runtime" "strings" "github.com/hashicorp/terraform-exec/internal/version" + tfjson "github.com/hashicorp/terraform-json" ) const ( @@ -216,6 +218,72 @@ func (tf *Terraform) runTerraformCmdJSON(ctx context.Context, cmd *exec.Cmd, v i return dec.Decode(v) } +func (tf *Terraform) runTerraformCmdJSONLog(ctx context.Context, cmd *exec.Cmd) iter.Seq[NextMessage] { + pr, pw := io.Pipe() + tf.SetStdout(pw) + + emitter := newLogMsgEmitter(pr) + + go func() { + err := tf.runTerraformCmd(ctx, cmd) + emitter.done <- errors.Join(err, pw.Close()) + }() + + return func(yield func(msg NextMessage) bool) { + for { + nextMsg := emitter.NextMessage() + ok := yield(nextMsg) + if !ok || nextMsg.Msg == nil { + return + } + } + } +} + +func newLogMsgEmitter(stdoutReader io.ReadCloser) *logMsgEmitter { + return &logMsgEmitter{ + scanner: bufio.NewScanner(stdoutReader), + stdoutReader: stdoutReader, + done: make(chan error, 1), + } +} + +type logMsgEmitter struct { + scanner *bufio.Scanner + stdoutReader io.Closer + done chan error +} + +type NextMessage struct { + Msg tfjson.LogMsg + Err error +} + +// NextMessage returns next decoded message, if any, along with any errors. +// Stdout reader is closed when the last message is received. +// +// Error returned can be related to decoding of the message, the Terraform command +// or closing of stdout reader. +// +// Any error coming from Terraform (such as wrong configuration syntax) is +// represented as LogMsg of Level [tfjson.Error]. +func (e *logMsgEmitter) NextMessage() NextMessage { + if e.scanner.Scan() { + msg, err := tfjson.UnmarshalLogMessage(e.scanner.Bytes()) + return NextMessage{ + Msg: msg, + Err: err, + } + } + + err := <-e.done + err = errors.Join(err, e.scanner.Err(), e.stdoutReader.Close()) + return NextMessage{ + Msg: nil, + Err: err, + } +} + // mergeUserAgent does some minor deduplication to ensure we aren't // just using the same append string over and over. func mergeUserAgent(uas ...string) string { diff --git a/vendor/github.com/hashicorp/terraform-exec/tfexec/options.go b/vendor/github.com/hashicorp/terraform-exec/tfexec/options.go index 339bf39ec9..0129bd5c69 100644 --- a/vendor/github.com/hashicorp/terraform-exec/tfexec/options.go +++ b/vendor/github.com/hashicorp/terraform-exec/tfexec/options.go @@ -184,6 +184,14 @@ func FromModule(source string) *FromModuleOption { return &FromModuleOption{source} } +type GenerateConfigOutOption struct { + path string +} + +func GenerateConfigOut(path string) *GenerateConfigOutOption { + return &GenerateConfigOutOption{path} +} + type GetOption struct { get bool } diff --git a/vendor/github.com/hashicorp/terraform-exec/tfexec/query.go b/vendor/github.com/hashicorp/terraform-exec/tfexec/query.go new file mode 100644 index 0000000000..04b515f342 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-exec/tfexec/query.go @@ -0,0 +1,127 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package tfexec + +import ( + "context" + "fmt" + "iter" + "os/exec" +) + +type queryConfig struct { + dir string + generateConfig string + reattachInfo ReattachInfo + vars []string + varFiles []string +} + +var defaultQueryOptions = queryConfig{} + +// QueryOption represents options used in the Query method. +type QueryOption interface { + configureQuery(*queryConfig) +} + +func (opt *DirOption) configureQuery(conf *queryConfig) { + conf.dir = opt.path +} + +func (opt *GenerateConfigOutOption) configureQuery(conf *queryConfig) { + conf.generateConfig = opt.path +} + +func (opt *ReattachOption) configureQuery(conf *queryConfig) { + conf.reattachInfo = opt.info +} + +func (opt *VarFileOption) configureQuery(conf *queryConfig) { + conf.varFiles = append(conf.varFiles, opt.path) +} + +func (opt *VarOption) configureQuery(conf *queryConfig) { + conf.vars = append(conf.vars, opt.assignment) +} + +// QueryJSON executes `terraform query` with the specified options as well as the +// `-json` flag and waits for it to complete. +// +// Using the `-json` flag will result in +// [machine-readable](https://developer.hashicorp.com/terraform/internals/machine-readable-ui) +// JSON being written to the supplied `io.Writer`. +// +// The returned error is nil if `terraform query` has been executed and exits +// with 0. +// +// QueryJSON is likely to be removed in a future major version in favour of +// query returning JSON by default. +func (tf *Terraform) QueryJSON(ctx context.Context, opts ...QueryOption) (iter.Seq[NextMessage], error) { + err := tf.compatible(ctx, tf1_14_0, nil) + if err != nil { + return nil, fmt.Errorf("terraform query -json was added in 1.14.0: %w", err) + } + + queryCmd, err := tf.queryJSONCmd(ctx, opts...) + if err != nil { + return nil, err + } + + return tf.runTerraformCmdJSONLog(ctx, queryCmd), nil +} + +func (tf *Terraform) queryJSONCmd(ctx context.Context, opts ...QueryOption) (*exec.Cmd, error) { + c := defaultQueryOptions + + for _, o := range opts { + o.configureQuery(&c) + } + + args, err := tf.buildQueryArgs(ctx, c) + if err != nil { + return nil, err + } + + args = append(args, "-json") + + return tf.buildQueryCmd(ctx, c, args) +} + +func (tf *Terraform) buildQueryArgs(ctx context.Context, c queryConfig) ([]string, error) { + args := []string{"query", "-no-color"} + + if c.generateConfig != "" { + args = append(args, "-generate-config-out="+c.generateConfig) + } + + for _, vf := range c.varFiles { + args = append(args, "-var-file="+vf) + } + + if c.vars != nil { + for _, v := range c.vars { + args = append(args, "-var", v) + } + } + + return args, nil +} + +func (tf *Terraform) buildQueryCmd(ctx context.Context, c queryConfig, args []string) (*exec.Cmd, error) { + // optional positional argument + if c.dir != "" { + args = append(args, c.dir) + } + + mergeEnv := map[string]string{} + if c.reattachInfo != nil { + reattachStr, err := c.reattachInfo.marshalString() + if err != nil { + return nil, err + } + mergeEnv[reattachEnvVar] = reattachStr + } + + return tf.buildTerraformCmd(ctx, mergeEnv, args...), nil +} diff --git a/vendor/github.com/hashicorp/terraform-exec/tfexec/version.go b/vendor/github.com/hashicorp/terraform-exec/tfexec/version.go index 87addd1ec5..40b9301ea2 100644 --- a/vendor/github.com/hashicorp/terraform-exec/tfexec/version.go +++ b/vendor/github.com/hashicorp/terraform-exec/tfexec/version.go @@ -34,6 +34,8 @@ var ( tf1_4_0 = version.Must(version.NewVersion("1.4.0")) tf1_6_0 = version.Must(version.NewVersion("1.6.0")) tf1_9_0 = version.Must(version.NewVersion("1.9.0")) + tf1_13_0 = version.Must(version.NewVersion("1.13.0")) + tf1_14_0 = version.Must(version.NewVersion("1.14.0")) ) // Version returns structured output from the terraform version command including both the Terraform CLI version diff --git a/vendor/github.com/hashicorp/terraform-json/logging_types.go b/vendor/github.com/hashicorp/terraform-json/logging_types.go index 3e712a0fdd..6f9008c4d1 100644 --- a/vendor/github.com/hashicorp/terraform-json/logging_types.go +++ b/vendor/github.com/hashicorp/terraform-json/logging_types.go @@ -3,6 +3,7 @@ package tfjson import ( + "bytes" "encoding/json" ) @@ -29,31 +30,36 @@ var allLogMessageTypes = []any{ } func unmarshalByType(t LogMessageType, b []byte) (LogMsg, error) { + d := json.NewDecoder(bytes.NewReader(b)) + + // decode numbers as json.Number to avoid losing precision + d.UseNumber() + switch t { // generic case MessageTypeVersion: v := VersionLogMessage{} - return v, json.Unmarshal(b, &v) + return v, d.Decode(&v) case MessageTypeLog: v := LogMessage{} - return v, json.Unmarshal(b, &v) + return v, d.Decode(&v) case MessageTypeDiagnostic: v := DiagnosticLogMessage{} - return v, json.Unmarshal(b, &v) + return v, d.Decode(&v) // query case MessageListStart: v := ListStartMessage{} - return v, json.Unmarshal(b, &v) + return v, d.Decode(&v) case MessageListResourceFound: v := ListResourceFoundMessage{} - return v, json.Unmarshal(b, &v) + return v, d.Decode(&v) case MessageListComplete: v := ListCompleteMessage{} - return v, json.Unmarshal(b, &v) + return v, d.Decode(&v) } v := UnknownLogMessage{} - return v, json.Unmarshal(b, &v) + return v, d.Decode(&v) } diff --git a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource/aliases.go b/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource/aliases.go deleted file mode 100644 index 275137d812..0000000000 --- a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource/aliases.go +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package resource - -import ( - "context" - "time" - - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/id" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry" -) - -// Deprecated: Use helper/id package instead. This is required for migrating acceptance -// testing to terraform-plugin-testing. -const UniqueIdPrefix = id.UniqueIdPrefix - -// Helper for a resource to generate a unique identifier w/ default prefix -// -// Deprecated: Use helper/id package instead. This is required for migrating acceptance -// testing to terraform-plugin-testing. -func UniqueId() string { - return id.UniqueId() -} - -// Deprecated: Use helper/id package instead. This is required for migrating acceptance -// testing to terraform-plugin-testing. -const UniqueIDSuffixLength = id.UniqueIDSuffixLength - -// Helper for a resource to generate a unique identifier w/ given prefix -// -// After the prefix, the ID consists of an incrementing 26 digit value (to match -// previous timestamp output). After the prefix, the ID consists of a timestamp -// and an incrementing 8 hex digit value The timestamp means that multiple IDs -// created with the same prefix will sort in the order of their creation, even -// across multiple terraform executions, as long as the clock is not turned back -// between calls, and as long as any given terraform execution generates fewer -// than 4 billion IDs. -// -// Deprecated: Use helper/id package instead. This is required for migrating acceptance -// testing to terraform-plugin-testing. -func PrefixedUniqueId(prefix string) string { - return id.PrefixedUniqueId(prefix) -} - -// Deprecated: Use helper/retry package instead. This is required for migrating acceptance -// testing to terraform-plugin-testing. -type NotFoundError = retry.NotFoundError - -// UnexpectedStateError is returned when Refresh returns a state that's neither in Target nor Pending -// -// Deprecated: Use helper/retry package instead. This is required for migrating acceptance -// testing to terraform-plugin-testing. -type UnexpectedStateError = retry.UnexpectedStateError - -// TimeoutError is returned when WaitForState times out -// -// Deprecated: Use helper/retry package instead. This is required for migrating acceptance -// testing to terraform-plugin-testing. -type TimeoutError = retry.TimeoutError - -// StateRefreshFunc is a function type used for StateChangeConf that is -// responsible for refreshing the item being watched for a state change. -// -// It returns three results. `result` is any object that will be returned -// as the final object after waiting for state change. This allows you to -// return the final updated object, for example an EC2 instance after refreshing -// it. A nil result represents not found. -// -// `state` is the latest state of that object. And `err` is any error that -// may have happened while refreshing the state. -// -// Deprecated: Use helper/retry package instead. This is required for migrating acceptance -// testing to terraform-plugin-testing. -type StateRefreshFunc = retry.StateRefreshFunc - -// StateChangeConf is the configuration struct used for `WaitForState`. -// -// Deprecated: Use helper/retry package instead. This is required for migrating acceptance -// testing to terraform-plugin-testing. -type StateChangeConf = retry.StateChangeConf - -// RetryFunc is the function retried until it succeeds. -// -// Deprecated: Use helper/retry package instead. This is required for migrating acceptance -// testing to terraform-plugin-testing. -type RetryFunc = retry.RetryFunc - -// RetryContext is a basic wrapper around StateChangeConf that will just retry -// a function until it no longer returns an error. -// -// Cancellation from the passed in context will propagate through to the -// underlying StateChangeConf -// -// Deprecated: Use helper/retry package instead. This is required for migrating acceptance -// testing to terraform-plugin-testing. -func RetryContext(ctx context.Context, timeout time.Duration, f RetryFunc) error { - return retry.RetryContext(ctx, timeout, f) -} - -// Retry is a basic wrapper around StateChangeConf that will just retry -// a function until it no longer returns an error. -// -// Deprecated: Use helper/retry package instead. This is required for migrating acceptance -// testing to terraform-plugin-testing. -func Retry(timeout time.Duration, f RetryFunc) error { - return retry.Retry(timeout, f) -} - -// RetryError is the required return type of RetryFunc. It forces client code -// to choose whether or not a given error is retryable. -// -// Deprecated: Use helper/retry package instead. This is required for migrating acceptance -// testing to terraform-plugin-testing. -type RetryError = retry.RetryError - -// RetryableError is a helper to create a RetryError that's retryable from a -// given error. To prevent logic errors, will return an error when passed a -// nil error. -// -// Deprecated: Use helper/retry package instead. This is required for migrating acceptance -// testing to terraform-plugin-testing. -func RetryableError(err error) *RetryError { - r := retry.RetryableError(err) - - return &RetryError{ - Err: r.Err, - Retryable: r.Retryable, - } -} - -// NonRetryableError is a helper to create a RetryError that's _not_ retryable -// from a given error. To prevent logic errors, will return an error when -// passed a nil error. -// -// Deprecated: Use helper/retry package instead. This is required for migrating acceptance -// testing to terraform-plugin-testing. -func NonRetryableError(err error) *RetryError { - r := retry.NonRetryableError(err) - - return &RetryError{ - Err: r.Err, - Retryable: r.Retryable, - } -} diff --git a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource/testing_new_config.go b/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource/testing_new_config.go deleted file mode 100644 index a52008768f..0000000000 --- a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource/testing_new_config.go +++ /dev/null @@ -1,244 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package resource - -import ( - "context" - "errors" - "fmt" - - tfjson "github.com/hashicorp/terraform-json" - testing "github.com/mitchellh/go-testing-interface" - - "github.com/hashicorp/terraform-plugin-sdk/v2/internal/logging" - "github.com/hashicorp/terraform-plugin-sdk/v2/internal/plugintest" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" -) - -func testStepNewConfig(ctx context.Context, t testing.T, c TestCase, wd *plugintest.WorkingDir, step TestStep, providers *providerFactories) error { - t.Helper() - - err := wd.SetConfig(ctx, step.mergedConfig(ctx, c)) - if err != nil { - return fmt.Errorf("Error setting config: %w", err) - } - - // require a refresh before applying - // failing to do this will result in data sources not being updated - err = runProviderCommand(ctx, t, func() error { - return wd.Refresh(ctx) - }, wd, providers) - if err != nil { - return fmt.Errorf("Error running pre-apply refresh: %w", err) - } - - // If this step is a PlanOnly step, skip over this first Plan and - // subsequent Apply, and use the follow-up Plan that checks for - // permadiffs - if !step.PlanOnly { - logging.HelperResourceDebug(ctx, "Running Terraform CLI plan and apply") - - // Plan! - err := runProviderCommand(ctx, t, func() error { - if step.Destroy { - return wd.CreateDestroyPlan(ctx) - } - return wd.CreatePlan(ctx) - }, wd, providers) - if err != nil { - return fmt.Errorf("Error running pre-apply plan: %w", err) - } - - // We need to keep a copy of the state prior to destroying such - // that the destroy steps can verify their behavior in the - // check function - var stateBeforeApplication *terraform.State - err = runProviderCommand(ctx, t, func() error { - stateBeforeApplication, err = getState(ctx, t, wd) - if err != nil { - return err - } - return nil - }, wd, providers) - if err != nil { - return fmt.Errorf("Error retrieving pre-apply state: %w", err) - } - - // Apply the diff, creating real resources - err = runProviderCommand(ctx, t, func() error { - return wd.Apply(ctx) - }, wd, providers) - if err != nil { - if step.Destroy { - return fmt.Errorf("Error running destroy: %w", err) - } - return fmt.Errorf("Error running apply: %w", err) - } - - // Get the new state - var state *terraform.State - err = runProviderCommand(ctx, t, func() error { - state, err = getState(ctx, t, wd) - if err != nil { - return err - } - return nil - }, wd, providers) - if err != nil { - return fmt.Errorf("Error retrieving state after apply: %w", err) - } - - // Run any configured checks - if step.Check != nil { - logging.HelperResourceTrace(ctx, "Using TestStep Check") - - state.IsBinaryDrivenTest = true - if step.Destroy { - if err := step.Check(stateBeforeApplication); err != nil { - return fmt.Errorf("Check failed: %w", err) - } - } else { - if err := step.Check(state); err != nil { - return fmt.Errorf("Check failed: %w", err) - } - } - } - } - - // Test for perpetual diffs by performing a plan, a refresh, and another plan - logging.HelperResourceDebug(ctx, "Running Terraform CLI plan to check for perpetual differences") - - // do a plan - err = runProviderCommand(ctx, t, func() error { - if step.Destroy { - return wd.CreateDestroyPlan(ctx) - } - return wd.CreatePlan(ctx) - }, wd, providers) - if err != nil { - return fmt.Errorf("Error running post-apply plan: %w", err) - } - - var plan *tfjson.Plan - err = runProviderCommand(ctx, t, func() error { - var err error - plan, err = wd.SavedPlan(ctx) - return err - }, wd, providers) - if err != nil { - return fmt.Errorf("Error retrieving post-apply plan: %w", err) - } - - if !planIsEmpty(plan) && !step.ExpectNonEmptyPlan { - var stdout string - err = runProviderCommand(ctx, t, func() error { - var err error - stdout, err = wd.SavedPlanRawStdout(ctx) - return err - }, wd, providers) - if err != nil { - return fmt.Errorf("Error retrieving formatted plan output: %w", err) - } - return fmt.Errorf("After applying this test step, the plan was not empty.\nstdout:\n\n%s", stdout) - } - - // do a refresh - if !step.Destroy || (step.Destroy && !step.PreventPostDestroyRefresh) { - err := runProviderCommand(ctx, t, func() error { - return wd.Refresh(ctx) - }, wd, providers) - if err != nil { - return fmt.Errorf("Error running post-apply refresh: %w", err) - } - } - - // do another plan - err = runProviderCommand(ctx, t, func() error { - if step.Destroy { - return wd.CreateDestroyPlan(ctx) - } - return wd.CreatePlan(ctx) - }, wd, providers) - if err != nil { - return fmt.Errorf("Error running second post-apply plan: %w", err) - } - - err = runProviderCommand(ctx, t, func() error { - var err error - plan, err = wd.SavedPlan(ctx) - return err - }, wd, providers) - if err != nil { - return fmt.Errorf("Error retrieving second post-apply plan: %w", err) - } - - // check if plan is empty - if !planIsEmpty(plan) && !step.ExpectNonEmptyPlan { - var stdout string - err = runProviderCommand(ctx, t, func() error { - var err error - stdout, err = wd.SavedPlanRawStdout(ctx) - return err - }, wd, providers) - if err != nil { - return fmt.Errorf("Error retrieving formatted second plan output: %w", err) - } - return fmt.Errorf("After applying this test step and performing a `terraform refresh`, the plan was not empty.\nstdout\n\n%s", stdout) - } else if step.ExpectNonEmptyPlan && planIsEmpty(plan) { - return errors.New("Expected a non-empty plan, but got an empty plan") - } - - // ID-ONLY REFRESH - // If we've never checked an id-only refresh and our state isn't - // empty, find the first resource and test it. - if c.IDRefreshName != "" { - logging.HelperResourceTrace(ctx, "Using TestCase IDRefreshName") - - var state *terraform.State - - err = runProviderCommand(ctx, t, func() error { - state, err = getState(ctx, t, wd) - if err != nil { - return err - } - return nil - }, wd, providers) - - if err != nil { - return err - } - - if state.Empty() { - return nil - } - - var idRefreshCheck *terraform.ResourceState - - // Find the first non-nil resource in the state - for _, m := range state.Modules { - if len(m.Resources) > 0 { - if v, ok := m.Resources[c.IDRefreshName]; ok { - idRefreshCheck = v - } - - break - } - } - - // If we have an instance to check for refreshes, do it - // immediately. We do it in the middle of another test - // because it shouldn't affect the overall state (refresh - // is read-only semantically) and we want to fail early if - // this fails. If refresh isn't read-only, then this will have - // caught a different bug. - if idRefreshCheck != nil { - if err := testIDRefresh(ctx, t, c, wd, step, idRefreshCheck, providers); err != nil { - return fmt.Errorf( - "[ERROR] Test: ID-only test failed: %s", err) - } - } - } - - return nil -} diff --git a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource/testing_new_import_state.go b/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource/testing_new_import_state.go deleted file mode 100644 index 4ddf56c5b2..0000000000 --- a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource/testing_new_import_state.go +++ /dev/null @@ -1,281 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package resource - -import ( - "context" - "fmt" - "reflect" - "strings" - - "github.com/google/go-cmp/cmp" - "github.com/mitchellh/go-testing-interface" - - "github.com/hashicorp/terraform-plugin-sdk/v2/internal/logging" - "github.com/hashicorp/terraform-plugin-sdk/v2/internal/plugintest" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" -) - -func testStepNewImportState(ctx context.Context, t testing.T, helper *plugintest.Helper, wd *plugintest.WorkingDir, step TestStep, cfg string, providers *providerFactories) error { - t.Helper() - - if step.ResourceName == "" { - t.Fatal("ResourceName is required for an import state test") - } - - // get state from check sequence - var state *terraform.State - var err error - err = runProviderCommand(ctx, t, func() error { - state, err = getState(ctx, t, wd) - if err != nil { - return err - } - return nil - }, wd, providers) - if err != nil { - t.Fatalf("Error getting state: %s", err) - } - - // Determine the ID to import - var importId string - switch { - case step.ImportStateIdFunc != nil: - logging.HelperResourceTrace(ctx, "Using TestStep ImportStateIdFunc for import identifier") - - var err error - - logging.HelperResourceDebug(ctx, "Calling TestStep ImportStateIdFunc") - - importId, err = step.ImportStateIdFunc(state) - - if err != nil { - t.Fatal(err) - } - - logging.HelperResourceDebug(ctx, "Called TestStep ImportStateIdFunc") - case step.ImportStateId != "": - logging.HelperResourceTrace(ctx, "Using TestStep ImportStateId for import identifier") - - importId = step.ImportStateId - default: - logging.HelperResourceTrace(ctx, "Using resource identifier for import identifier") - - resource, err := testResource(step, state) - if err != nil { - t.Fatal(err) - } - importId = resource.Primary.ID - } - - if step.ImportStateIdPrefix != "" { - logging.HelperResourceTrace(ctx, "Prepending TestStep ImportStateIdPrefix for import identifier") - - importId = step.ImportStateIdPrefix + importId - } - - logging.HelperResourceTrace(ctx, fmt.Sprintf("Using import identifier: %s", importId)) - - // Create working directory for import tests - if step.Config == "" { - logging.HelperResourceTrace(ctx, "Using prior TestStep Config for import") - - step.Config = cfg - if step.Config == "" { - t.Fatal("Cannot import state with no specified config") - } - } - - var importWd *plugintest.WorkingDir - - // Use the same working directory to persist the state from import - if step.ImportStatePersist { - importWd = wd - } else { - importWd = helper.RequireNewWorkingDir(ctx, t) - defer importWd.Close() - } - - err = importWd.SetConfig(ctx, step.Config) - if err != nil { - t.Fatalf("Error setting test config: %s", err) - } - - logging.HelperResourceDebug(ctx, "Running Terraform CLI init and import") - - if !step.ImportStatePersist { - err = runProviderCommand(ctx, t, func() error { - return importWd.Init(ctx) - }, importWd, providers) - if err != nil { - t.Fatalf("Error running init: %s", err) - } - } - - err = runProviderCommand(ctx, t, func() error { - return importWd.Import(ctx, step.ResourceName, importId) - }, importWd, providers) - if err != nil { - return err - } - - var importState *terraform.State - err = runProviderCommand(ctx, t, func() error { - importState, err = getState(ctx, t, importWd) - if err != nil { - return err - } - return nil - }, importWd, providers) - if err != nil { - t.Fatalf("Error getting state: %s", err) - } - - // Go through the imported state and verify - if step.ImportStateCheck != nil { - logging.HelperResourceTrace(ctx, "Using TestStep ImportStateCheck") - - var states []*terraform.InstanceState - for address, r := range importState.RootModule().Resources { - if strings.HasPrefix(address, "data.") { - continue - } - - if r.Primary == nil { - continue - } - - is := r.Primary.DeepCopy() - is.Ephemeral.Type = r.Type // otherwise the check function cannot see the type - states = append(states, is) - } - - logging.HelperResourceDebug(ctx, "Calling TestStep ImportStateCheck") - - if err := step.ImportStateCheck(states); err != nil { - t.Fatal(err) - } - - logging.HelperResourceDebug(ctx, "Called TestStep ImportStateCheck") - } - - // Verify that all the states match - if step.ImportStateVerify { - logging.HelperResourceTrace(ctx, "Using TestStep ImportStateVerify") - - // Ensure that we do not match against data sources as they - // cannot be imported and are not what we want to verify. - // Mode is not present in ResourceState so we use the - // stringified ResourceStateKey for comparison. - newResources := make(map[string]*terraform.ResourceState) - for k, v := range importState.RootModule().Resources { - if !strings.HasPrefix(k, "data.") { - newResources[k] = v - } - } - oldResources := make(map[string]*terraform.ResourceState) - for k, v := range state.RootModule().Resources { - if !strings.HasPrefix(k, "data.") { - oldResources[k] = v - } - } - - for _, r := range newResources { - // Find the existing resource - var oldR *terraform.ResourceState - for _, r2 := range oldResources { - - if r2.Primary != nil && r2.Primary.ID == r.Primary.ID && r2.Type == r.Type && r2.Provider == r.Provider { - oldR = r2 - break - } - } - if oldR == nil || oldR.Primary == nil { - t.Fatalf( - "Failed state verification, resource with ID %s not found", - r.Primary.ID) - } - - // don't add empty flatmapped containers, so we can more easily - // compare the attributes - skipEmpty := func(k, v string) bool { - if strings.HasSuffix(k, ".#") || strings.HasSuffix(k, ".%") { - if v == "0" { - return true - } - } - return false - } - - // Compare their attributes - actual := make(map[string]string) - for k, v := range r.Primary.Attributes { - if skipEmpty(k, v) { - continue - } - actual[k] = v - } - - expected := make(map[string]string) - for k, v := range oldR.Primary.Attributes { - if skipEmpty(k, v) { - continue - } - expected[k] = v - } - - // Remove fields we're ignoring - for _, v := range step.ImportStateVerifyIgnore { - for k := range actual { - if strings.HasPrefix(k, v) { - delete(actual, k) - } - } - for k := range expected { - if strings.HasPrefix(k, v) { - delete(expected, k) - } - } - } - - // timeouts are only _sometimes_ added to state. To - // account for this, just don't compare timeouts at - // all. - for k := range actual { - if strings.HasPrefix(k, "timeouts.") { - delete(actual, k) - } - if k == "timeouts" { - delete(actual, k) - } - } - for k := range expected { - if strings.HasPrefix(k, "timeouts.") { - delete(expected, k) - } - if k == "timeouts" { - delete(expected, k) - } - } - - if !reflect.DeepEqual(actual, expected) { - // Determine only the different attributes - // go-cmp tries to show surrounding identical map key/value for - // context of differences, which may be confusing. - for k, v := range expected { - if av, ok := actual[k]; ok && v == av { - delete(expected, k) - delete(actual, k) - } - } - - if diff := cmp.Diff(expected, actual); diff != "" { - return fmt.Errorf("ImportStateVerify attributes not equivalent. Difference is shown below. The - symbol indicates attributes missing after import.\n\n%s", diff) - } - } - } - } - - return nil -} diff --git a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource/teststep_providers.go b/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource/teststep_providers.go deleted file mode 100644 index 9b759bde03..0000000000 --- a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource/teststep_providers.go +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package resource - -import ( - "context" - "fmt" - "regexp" - "strings" -) - -var configProviderBlockRegex = regexp.MustCompile(`provider "?[a-zA-Z0-9_-]+"? {`) - -// configHasProviderBlock returns true if the Config has declared a provider -// configuration block, e.g. provider "examplecloud" {...} -func (s TestStep) configHasProviderBlock(_ context.Context) bool { - return configProviderBlockRegex.MatchString(s.Config) -} - -// configHasTerraformBlock returns true if the Config has declared a terraform -// configuration block, e.g. terraform {...} -func (s TestStep) configHasTerraformBlock(_ context.Context) bool { - return strings.Contains(s.Config, "terraform {") -} - -// mergedConfig prepends any necessary terraform configuration blocks to the -// TestStep Config. -// -// If there are ExternalProviders configurations in either the TestCase or -// TestStep, the terraform configuration block should be included with the -// step configuration to prevent errors with providers outside the -// registry.terraform.io hostname or outside the hashicorp namespace. -func (s TestStep) mergedConfig(ctx context.Context, testCase TestCase) string { - var config strings.Builder - - // Prevent issues with existing configurations containing the terraform - // configuration block. - if s.configHasTerraformBlock(ctx) { - config.WriteString(s.Config) - - return config.String() - } - - if testCase.hasProviders(ctx) { - config.WriteString(testCase.providerConfig(ctx, s.configHasProviderBlock(ctx))) - } else { - config.WriteString(s.providerConfig(ctx, s.configHasProviderBlock(ctx))) - } - - config.WriteString(s.Config) - - return config.String() -} - -// providerConfig takes the list of providers in a TestStep and returns a -// config with only empty provider blocks. This is useful for Import, where no -// config is provided, but the providers must be defined. -func (s TestStep) providerConfig(_ context.Context, skipProviderBlock bool) string { - var providerBlocks, requiredProviderBlocks strings.Builder - - for name, externalProvider := range s.ExternalProviders { - if !skipProviderBlock { - providerBlocks.WriteString(fmt.Sprintf("provider %q {}\n", name)) - } - - if externalProvider.Source == "" && externalProvider.VersionConstraint == "" { - continue - } - - requiredProviderBlocks.WriteString(fmt.Sprintf(" %s = {\n", name)) - - if externalProvider.Source != "" { - requiredProviderBlocks.WriteString(fmt.Sprintf(" source = %q\n", externalProvider.Source)) - } - - if externalProvider.VersionConstraint != "" { - requiredProviderBlocks.WriteString(fmt.Sprintf(" version = %q\n", externalProvider.VersionConstraint)) - } - - requiredProviderBlocks.WriteString(" }\n") - } - - if requiredProviderBlocks.Len() > 0 { - return fmt.Sprintf(` -terraform { - required_providers { -%[1]s - } -} - -%[2]s -`, strings.TrimSuffix(requiredProviderBlocks.String(), "\n"), providerBlocks.String()) - } - - return providerBlocks.String() -} diff --git a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource/teststep_validate.go b/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource/teststep_validate.go deleted file mode 100644 index 7dbf883b50..0000000000 --- a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource/teststep_validate.go +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package resource - -import ( - "context" - "fmt" - - "github.com/hashicorp/terraform-plugin-sdk/v2/internal/logging" -) - -// testStepValidateRequest contains data for the (TestStep).validate() method. -type testStepValidateRequest struct { - // StepNumber is the index of the TestStep in the TestCase.Steps. - StepNumber int - - // TestCaseHasProviders is enabled if the TestCase has set any of - // ExternalProviders, ProtoV5ProviderFactories, ProtoV6ProviderFactories, - // or ProviderFactories. - TestCaseHasProviders bool -} - -// hasProviders returns true if the TestStep has set any of the -// ExternalProviders, ProtoV5ProviderFactories, ProtoV6ProviderFactories, or -// ProviderFactories fields. -func (s TestStep) hasProviders(_ context.Context) bool { - if len(s.ExternalProviders) > 0 { - return true - } - - if len(s.ProtoV5ProviderFactories) > 0 { - return true - } - - if len(s.ProtoV6ProviderFactories) > 0 { - return true - } - - if len(s.ProviderFactories) > 0 { - return true - } - - return false -} - -// validate ensures the TestStep is valid based on the following criteria: -// -// - Config or ImportState or RefreshState is set. -// - Config and RefreshState are not both set. -// - RefreshState and Destroy are not both set. -// - RefreshState is not the first TestStep. -// - Providers are not specified (ExternalProviders, -// ProtoV5ProviderFactories, ProtoV6ProviderFactories, ProviderFactories) -// if specified at the TestCase level. -// - Providers are specified (ExternalProviders, ProtoV5ProviderFactories, -// ProtoV6ProviderFactories, ProviderFactories) if not specified at the -// TestCase level. -// - No overlapping ExternalProviders and ProviderFactories entries -// - ResourceName is not empty when ImportState is true, ImportStateIdFunc -// is not set, and ImportStateId is not set. -func (s TestStep) validate(ctx context.Context, req testStepValidateRequest) error { - ctx = logging.TestStepNumberContext(ctx, req.StepNumber) - - logging.HelperResourceTrace(ctx, "Validating TestStep") - - if s.Config == "" && !s.ImportState && !s.RefreshState { - err := fmt.Errorf("TestStep missing Config or ImportState or RefreshState") - logging.HelperResourceError(ctx, "TestStep validation error", map[string]interface{}{logging.KeyError: err}) - return err - } - - if s.Config != "" && s.RefreshState { - err := fmt.Errorf("TestStep cannot have Config and RefreshState") - logging.HelperResourceError(ctx, "TestStep validation error", map[string]interface{}{logging.KeyError: err}) - return err - } - - if s.RefreshState && s.Destroy { - err := fmt.Errorf("TestStep cannot have RefreshState and Destroy") - logging.HelperResourceError(ctx, "TestStep validation error", map[string]interface{}{logging.KeyError: err}) - return err - } - - if s.RefreshState && req.StepNumber == 1 { - err := fmt.Errorf("TestStep cannot have RefreshState as first step") - logging.HelperResourceError(ctx, "TestStep validation error", map[string]interface{}{logging.KeyError: err}) - return err - } - - if s.ImportState && s.RefreshState { - err := fmt.Errorf("TestStep cannot have ImportState and RefreshState in same step") - logging.HelperResourceError(ctx, "TestStep validation error", map[string]interface{}{logging.KeyError: err}) - return err - } - - for name := range s.ExternalProviders { - if _, ok := s.ProviderFactories[name]; ok { - err := fmt.Errorf("TestStep provider %q set in both ExternalProviders and ProviderFactories", name) - logging.HelperResourceError(ctx, "TestStep validation error", map[string]interface{}{logging.KeyError: err}) - return err - } - } - - hasProviders := s.hasProviders(ctx) - - if req.TestCaseHasProviders && hasProviders { - err := fmt.Errorf("Providers must only be specified either at the TestCase or TestStep level") - logging.HelperResourceError(ctx, "TestStep validation error", map[string]interface{}{logging.KeyError: err}) - return err - } - - if !req.TestCaseHasProviders && !hasProviders { - err := fmt.Errorf("Providers must be specified at the TestCase level or in all TestStep") - logging.HelperResourceError(ctx, "TestStep validation error", map[string]interface{}{logging.KeyError: err}) - return err - } - - if s.ImportState { - if s.ImportStateId == "" && s.ImportStateIdFunc == nil && s.ResourceName == "" { - err := fmt.Errorf("TestStep ImportState must be specified with ImportStateId, ImportStateIdFunc, or ResourceName") - logging.HelperResourceError(ctx, "TestStep validation error", map[string]interface{}{logging.KeyError: err}) - return err - } - } - - return nil -} diff --git a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/internal/plugintest/util.go b/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/internal/plugintest/util.go deleted file mode 100644 index 0d4bbe5266..0000000000 --- a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/internal/plugintest/util.go +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) HashiCorp, Inc. -// SPDX-License-Identifier: MPL-2.0 - -package plugintest - -import ( - "fmt" - "os" - "path/filepath" -) - -func symlinkFile(src string, dest string) error { - err := os.Symlink(src, dest) - - if err != nil { - return fmt.Errorf("unable to symlink %q to %q: %w", src, dest, err) - } - - srcInfo, err := os.Stat(src) - - if err != nil { - return fmt.Errorf("unable to stat %q: %w", src, err) - } - - err = os.Chmod(dest, srcInfo.Mode()) - - if err != nil { - return fmt.Errorf("unable to set %q permissions: %w", dest, err) - } - - return nil -} - -// symlinkDirectoriesOnly finds only the first-level child directories in srcDir -// and symlinks them into destDir. -// Unlike symlinkDir, this is done non-recursively in order to limit the number -// of file descriptors used. -func symlinkDirectoriesOnly(srcDir string, destDir string) error { - srcInfo, err := os.Stat(srcDir) - if err != nil { - return fmt.Errorf("unable to stat source directory %q: %w", srcDir, err) - } - - err = os.MkdirAll(destDir, srcInfo.Mode()) - if err != nil { - return fmt.Errorf("unable to make destination directory %q: %w", destDir, err) - } - - dirEntries, err := os.ReadDir(srcDir) - - if err != nil { - return fmt.Errorf("unable to read source directory %q: %w", srcDir, err) - } - - for _, dirEntry := range dirEntries { - if !dirEntry.IsDir() { - continue - } - - srcPath := filepath.Join(srcDir, dirEntry.Name()) - destPath := filepath.Join(destDir, dirEntry.Name()) - err := symlinkFile(srcPath, destPath) - - if err != nil { - return fmt.Errorf("unable to symlink directory %q to %q: %w", srcPath, destPath, err) - } - } - - return nil -} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/LICENSE b/vendor/github.com/hashicorp/terraform-plugin-testing/LICENSE new file mode 100644 index 0000000000..07c599410b --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/LICENSE @@ -0,0 +1,375 @@ +Copyright (c) 2014 HashiCorp, Inc. + +Mozilla Public License Version 2.0 +================================== + +1. Definitions +-------------- + +1.1. "Contributor" + means each individual or legal entity that creates, contributes to + the creation of, or owns Covered Software. + +1.2. "Contributor Version" + means the combination of the Contributions of others (if any) used + by a Contributor and that particular Contributor's Contribution. + +1.3. "Contribution" + means Covered Software of a particular Contributor. + +1.4. "Covered Software" + means Source Code Form to which the initial Contributor has attached + the notice in Exhibit A, the Executable Form of such Source Code + Form, and Modifications of such Source Code Form, in each case + including portions thereof. + +1.5. "Incompatible With Secondary Licenses" + means + + (a) that the initial Contributor has attached the notice described + in Exhibit B to the Covered Software; or + + (b) that the Covered Software was made available under the terms of + version 1.1 or earlier of the License, but not also under the + terms of a Secondary License. + +1.6. "Executable Form" + means any form of the work other than Source Code Form. + +1.7. "Larger Work" + means a work that combines Covered Software with other material, in + a separate file or files, that is not Covered Software. + +1.8. "License" + means this document. + +1.9. "Licensable" + means having the right to grant, to the maximum extent possible, + whether at the time of the initial grant or subsequently, any and + all of the rights conveyed by this License. + +1.10. "Modifications" + means any of the following: + + (a) any file in Source Code Form that results from an addition to, + deletion from, or modification of the contents of Covered + Software; or + + (b) any new file in Source Code Form that contains any Covered + Software. + +1.11. "Patent Claims" of a Contributor + means any patent claim(s), including without limitation, method, + process, and apparatus claims, in any patent Licensable by such + Contributor that would be infringed, but for the grant of the + License, by the making, using, selling, offering for sale, having + made, import, or transfer of either its Contributions or its + Contributor Version. + +1.12. "Secondary License" + means either the GNU General Public License, Version 2.0, the GNU + Lesser General Public License, Version 2.1, the GNU Affero General + Public License, Version 3.0, or any later versions of those + licenses. + +1.13. "Source Code Form" + means the form of the work preferred for making modifications. + +1.14. "You" (or "Your") + means an individual or a legal entity exercising rights under this + License. For legal entities, "You" includes any entity that + controls, is controlled by, or is under common control with You. For + purposes of this definition, "control" means (a) the power, direct + or indirect, to cause the direction or management of such entity, + whether by contract or otherwise, or (b) ownership of more than + fifty percent (50%) of the outstanding shares or beneficial + ownership of such entity. + +2. License Grants and Conditions +-------------------------------- + +2.1. Grants + +Each Contributor hereby grants You a world-wide, royalty-free, +non-exclusive license: + +(a) under intellectual property rights (other than patent or trademark) + Licensable by such Contributor to use, reproduce, make available, + modify, display, perform, distribute, and otherwise exploit its + Contributions, either on an unmodified basis, with Modifications, or + as part of a Larger Work; and + +(b) under Patent Claims of such Contributor to make, use, sell, offer + for sale, have made, import, and otherwise transfer either its + Contributions or its Contributor Version. + +2.2. Effective Date + +The licenses granted in Section 2.1 with respect to any Contribution +become effective for each Contribution on the date the Contributor first +distributes such Contribution. + +2.3. Limitations on Grant Scope + +The licenses granted in this Section 2 are the only rights granted under +this License. No additional rights or licenses will be implied from the +distribution or licensing of Covered Software under this License. +Notwithstanding Section 2.1(b) above, no patent license is granted by a +Contributor: + +(a) for any code that a Contributor has removed from Covered Software; + or + +(b) for infringements caused by: (i) Your and any other third party's + modifications of Covered Software, or (ii) the combination of its + Contributions with other software (except as part of its Contributor + Version); or + +(c) under Patent Claims infringed by Covered Software in the absence of + its Contributions. + +This License does not grant any rights in the trademarks, service marks, +or logos of any Contributor (except as may be necessary to comply with +the notice requirements in Section 3.4). + +2.4. Subsequent Licenses + +No Contributor makes additional grants as a result of Your choice to +distribute the Covered Software under a subsequent version of this +License (see Section 10.2) or under the terms of a Secondary License (if +permitted under the terms of Section 3.3). + +2.5. Representation + +Each Contributor represents that the Contributor believes its +Contributions are its original creation(s) or it has sufficient rights +to grant the rights to its Contributions conveyed by this License. + +2.6. Fair Use + +This License is not intended to limit any rights You have under +applicable copyright doctrines of fair use, fair dealing, or other +equivalents. + +2.7. Conditions + +Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted +in Section 2.1. + +3. Responsibilities +------------------- + +3.1. Distribution of Source Form + +All distribution of Covered Software in Source Code Form, including any +Modifications that You create or to which You contribute, must be under +the terms of this License. You must inform recipients that the Source +Code Form of the Covered Software is governed by the terms of this +License, and how they can obtain a copy of this License. You may not +attempt to alter or restrict the recipients' rights in the Source Code +Form. + +3.2. Distribution of Executable Form + +If You distribute Covered Software in Executable Form then: + +(a) such Covered Software must also be made available in Source Code + Form, as described in Section 3.1, and You must inform recipients of + the Executable Form how they can obtain a copy of such Source Code + Form by reasonable means in a timely manner, at a charge no more + than the cost of distribution to the recipient; and + +(b) You may distribute such Executable Form under the terms of this + License, or sublicense it under different terms, provided that the + license for the Executable Form does not attempt to limit or alter + the recipients' rights in the Source Code Form under this License. + +3.3. Distribution of a Larger Work + +You may create and distribute a Larger Work under terms of Your choice, +provided that You also comply with the requirements of this License for +the Covered Software. If the Larger Work is a combination of Covered +Software with a work governed by one or more Secondary Licenses, and the +Covered Software is not Incompatible With Secondary Licenses, this +License permits You to additionally distribute such Covered Software +under the terms of such Secondary License(s), so that the recipient of +the Larger Work may, at their option, further distribute the Covered +Software under the terms of either this License or such Secondary +License(s). + +3.4. Notices + +You may not remove or alter the substance of any license notices +(including copyright notices, patent notices, disclaimers of warranty, +or limitations of liability) contained within the Source Code Form of +the Covered Software, except that You may alter any license notices to +the extent required to remedy known factual inaccuracies. + +3.5. Application of Additional Terms + +You may choose to offer, and to charge a fee for, warranty, support, +indemnity or liability obligations to one or more recipients of Covered +Software. However, You may do so only on Your own behalf, and not on +behalf of any Contributor. You must make it absolutely clear that any +such warranty, support, indemnity, or liability obligation is offered by +You alone, and You hereby agree to indemnify every Contributor for any +liability incurred by such Contributor as a result of warranty, support, +indemnity or liability terms You offer. You may include additional +disclaimers of warranty and limitations of liability specific to any +jurisdiction. + +4. Inability to Comply Due to Statute or Regulation +--------------------------------------------------- + +If it is impossible for You to comply with any of the terms of this +License with respect to some or all of the Covered Software due to +statute, judicial order, or regulation then You must: (a) comply with +the terms of this License to the maximum extent possible; and (b) +describe the limitations and the code they affect. Such description must +be placed in a text file included with all distributions of the Covered +Software under this License. Except to the extent prohibited by statute +or regulation, such description must be sufficiently detailed for a +recipient of ordinary skill to be able to understand it. + +5. Termination +-------------- + +5.1. The rights granted under this License will terminate automatically +if You fail to comply with any of its terms. However, if You become +compliant, then the rights granted under this License from a particular +Contributor are reinstated (a) provisionally, unless and until such +Contributor explicitly and finally terminates Your grants, and (b) on an +ongoing basis, if such Contributor fails to notify You of the +non-compliance by some reasonable means prior to 60 days after You have +come back into compliance. Moreover, Your grants from a particular +Contributor are reinstated on an ongoing basis if such Contributor +notifies You of the non-compliance by some reasonable means, this is the +first time You have received notice of non-compliance with this License +from such Contributor, and You become compliant prior to 30 days after +Your receipt of the notice. + +5.2. If You initiate litigation against any entity by asserting a patent +infringement claim (excluding declaratory judgment actions, +counter-claims, and cross-claims) alleging that a Contributor Version +directly or indirectly infringes any patent, then the rights granted to +You by any and all Contributors for the Covered Software under Section +2.1 of this License shall terminate. + +5.3. In the event of termination under Sections 5.1 or 5.2 above, all +end user license agreements (excluding distributors and resellers) which +have been validly granted by You or Your distributors under this License +prior to termination shall survive termination. + +************************************************************************ +* * +* 6. Disclaimer of Warranty * +* ------------------------- * +* * +* Covered Software is provided under this License on an "as is" * +* basis, without warranty of any kind, either expressed, implied, or * +* statutory, including, without limitation, warranties that the * +* Covered Software is free of defects, merchantable, fit for a * +* particular purpose or non-infringing. The entire risk as to the * +* quality and performance of the Covered Software is with You. * +* Should any Covered Software prove defective in any respect, You * +* (not any Contributor) assume the cost of any necessary servicing, * +* repair, or correction. This disclaimer of warranty constitutes an * +* essential part of this License. No use of any Covered Software is * +* authorized under this License except under this disclaimer. * +* * +************************************************************************ + +************************************************************************ +* * +* 7. Limitation of Liability * +* -------------------------- * +* * +* Under no circumstances and under no legal theory, whether tort * +* (including negligence), contract, or otherwise, shall any * +* Contributor, or anyone who distributes Covered Software as * +* permitted above, be liable to You for any direct, indirect, * +* special, incidental, or consequential damages of any character * +* including, without limitation, damages for lost profits, loss of * +* goodwill, work stoppage, computer failure or malfunction, or any * +* and all other commercial damages or losses, even if such party * +* shall have been informed of the possibility of such damages. This * +* limitation of liability shall not apply to liability for death or * +* personal injury resulting from such party's negligence to the * +* extent applicable law prohibits such limitation. Some * +* jurisdictions do not allow the exclusion or limitation of * +* incidental or consequential damages, so this exclusion and * +* limitation may not apply to You. * +* * +************************************************************************ + +8. Litigation +------------- + +Any litigation relating to this License may be brought only in the +courts of a jurisdiction where the defendant maintains its principal +place of business and such litigation shall be governed by laws of that +jurisdiction, without reference to its conflict-of-law provisions. +Nothing in this Section shall prevent a party's ability to bring +cross-claims or counter-claims. + +9. Miscellaneous +---------------- + +This License represents the complete agreement concerning the subject +matter hereof. If any provision of this License is held to be +unenforceable, such provision shall be reformed only to the extent +necessary to make it enforceable. Any law or regulation which provides +that the language of a contract shall be construed against the drafter +shall not be used to construe this License against a Contributor. + +10. Versions of the License +--------------------------- + +10.1. New Versions + +Mozilla Foundation is the license steward. Except as provided in Section +10.3, no one other than the license steward has the right to modify or +publish new versions of this License. Each version will be given a +distinguishing version number. + +10.2. Effect of New Versions + +You may distribute the Covered Software under the terms of the version +of the License under which You originally received the Covered Software, +or under the terms of any subsequent version published by the license +steward. + +10.3. Modified Versions + +If you create software not governed by this License, and you want to +create a new license for such software, you may create and use a +modified version of this License if you rename the license and remove +any references to the name of the license steward (except to note that +such modified license differs from this License). + +10.4. Distributing Source Code Form that is Incompatible With Secondary +Licenses + +If You choose to distribute Source Code Form that is Incompatible With +Secondary Licenses under the terms of this version of the License, the +notice described in Exhibit B of this License must be attached. + +Exhibit A - Source Code Form License Notice +------------------------------------------- + + This Source Code Form is subject to the terms of the Mozilla Public + License, v. 2.0. If a copy of the MPL was not distributed with this + file, You can obtain one at http://mozilla.org/MPL/2.0/. + +If it is not possible or desirable to put the notice in a particular +file, then You may include the notice in a location (such as a LICENSE +file in a relevant directory) where a recipient would be likely to look +for such a notice. + +You may add additional accurate notices of copyright ownership. + +Exhibit B - "Incompatible With Secondary Licenses" Notice +--------------------------------------------------------- + + This Source Code Form is "Incompatible With Secondary Licenses", as + defined by the Mozilla Public License, v. 2.0. diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/compare/doc.go b/vendor/github.com/hashicorp/terraform-plugin-testing/compare/doc.go new file mode 100644 index 0000000000..feb4a4c005 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/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/vendor/github.com/hashicorp/terraform-plugin-testing/compare/value_comparer.go b/vendor/github.com/hashicorp/terraform-plugin-testing/compare/value_comparer.go new file mode 100644 index 0000000000..af635898b8 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/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/vendor/github.com/hashicorp/terraform-plugin-testing/compare/values_differ.go b/vendor/github.com/hashicorp/terraform-plugin-testing/compare/values_differ.go new file mode 100644 index 0000000000..24bd2ae22c --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/compare/values_differ.go @@ -0,0 +1,31 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package compare + +import ( + "fmt" + "reflect" +) + +var _ ValueComparer = valuesDiffer{} + +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 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]) + } + } + + 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/vendor/github.com/hashicorp/terraform-plugin-testing/compare/values_same.go b/vendor/github.com/hashicorp/terraform-plugin-testing/compare/values_same.go new file mode 100644 index 0000000000..46ee13f312 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/compare/values_same.go @@ -0,0 +1,31 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package compare + +import ( + "fmt" + "reflect" +) + +var _ ValueComparer = valuesSame{} + +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 !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]) + } + } + + 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/vendor/github.com/hashicorp/terraform-plugin-testing/config/config.go b/vendor/github.com/hashicorp/terraform-plugin-testing/config/config.go new file mode 100644 index 0000000000..4a663610fd --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/config/config.go @@ -0,0 +1,38 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package config + +// TestStepConfigFunc is the callback type used with acceptance tests to +// specify a string which either identifies a directory containing +// Terraform configuration files, or a file that contains Terraform +// configuration. +type TestStepConfigFunc func(TestStepConfigRequest) string + +// TestStepConfigRequest defines the request supplied to types +// implementing TestStepConfigFunc. StepNumber is one-based +// and is used in the predefined helper functions: +// +// - [config.TestStepDirectory] +// - [config.TestStepFile]. +// +// TestName is used in the predefined helper functions: +// +// - [config.TestNameDirectory] +// - [config.TestStepDirectory] +// - [config.TestNameFile] +// - [config.TestStepFile] +type TestStepConfigRequest struct { + StepNumber int + TestName string +} + +// Exec executes TestStepConfigFunc if it is not nil, otherwise an +// empty string is returned. +func (f TestStepConfigFunc) Exec(req TestStepConfigRequest) string { + if f != nil { + return f(req) + } + + return "" +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/config/constraints.go b/vendor/github.com/hashicorp/terraform-plugin-testing/config/constraints.go new file mode 100644 index 0000000000..3842cc0cc0 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/config/constraints.go @@ -0,0 +1,27 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package config + +// anyFloat is a constraint that permits any floating-point type. This type +// definition is copied rather than depending on x/exp/constraints since the +// dependency is otherwise unneeded, the definition is relatively trivial and +// static, and the Go language maintainers are not sure if/where these will live +// in the standard library. +// +// Reference: https://github.com/golang/go/issues/61914 +type anyFloat interface { + ~float32 | ~float64 +} + +// anyInteger is a constraint that permits any integer type. This type +// definition is copied rather than depending on x/exp/constraints since the +// dependency is otherwise unneeded, the definition is relatively trivial and +// static, and the Go language maintainers are not sure if/where these will live +// in the standard library. +// +// Reference: https://github.com/golang/go/issues/61914 +type anyInteger interface { + ~int | ~int8 | ~int16 | ~int32 | ~int64 | + ~uint | ~uint8 | ~uint16 | ~uint32 | ~uint64 | ~uintptr +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/config/directory.go b/vendor/github.com/hashicorp/terraform-plugin-testing/config/directory.go new file mode 100644 index 0000000000..c3c9ab0c0f --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/config/directory.go @@ -0,0 +1,63 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package config + +import ( + "path/filepath" + "strconv" +) + +// StaticDirectory returns the supplied directory. +func StaticDirectory(directory string) func(TestStepConfigRequest) string { + return func(_ TestStepConfigRequest) string { + return directory + } +} + +// TestNameDirectory returns the name of the test prefixed with +// "testdata". +// +// For example, given test code: +// +// func TestExampleCloudThing_basic(t *testing.T) { +// resource.Test(t, resource.TestCase{ +// Steps: []resource.TestStep{ +// { +// ConfigDirectory: config.TestNameDirectory(), +// }, +// }, +// }) +// } +// +// The testing configurations will be expected in the +// testdata/TestExampleCloudThing_basic/ directory. +func TestNameDirectory() func(TestStepConfigRequest) string { + return func(req TestStepConfigRequest) string { + return filepath.Join("testdata", req.TestName) + } +} + +// TestStepDirectory returns the name of the test suffixed with the +// test step number and prefixed with "testdata". +// +// For example, given test code: +// +// func TestExampleCloudThing_basic(t *testing.T) { +// resource.Test(t, resource.TestCase{ +// Steps: []resource.TestStep{ +// { +// ConfigDirectory: config.TestStepDirectory(), +// }, +// }, +// }) +// } +// +// The testing configurations will be expected in the +// testdata/TestExampleCloudThing_basic/1 directory as +// TestStepConfigRequest.StepNumber is one-based. +func TestStepDirectory() func(TestStepConfigRequest) string { + return func(req TestStepConfigRequest) string { + return filepath.Join("testdata", req.TestName, strconv.Itoa(req.StepNumber)) + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/config/doc.go b/vendor/github.com/hashicorp/terraform-plugin-testing/config/doc.go new file mode 100644 index 0000000000..e85d5f81c9 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/config/doc.go @@ -0,0 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// Package config implements functionality for supporting native +// Terraform configuration and variables for testing purposes. +package config diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/config/file.go b/vendor/github.com/hashicorp/terraform-plugin-testing/config/file.go new file mode 100644 index 0000000000..1974c40651 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/config/file.go @@ -0,0 +1,63 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package config + +import ( + "path/filepath" + "strconv" +) + +// StaticFile returns the supplied file. +func StaticFile(file string) func(TestStepConfigRequest) string { + return func(_ TestStepConfigRequest) string { + return file + } +} + +// TestNameFile returns the name of the test suffixed with the supplied +// file and prefixed with "testdata". +// +// For example, given test code: +// +// func TestExampleCloudThing_basic(t *testing.T) { +// resource.Test(t, resource.TestCase{ +// Steps: []resource.TestStep{ +// { +// ConfigFile: config.TestNameFile("test.tf"), +// }, +// }, +// }) +// } +// +// The testing configuration will be expected in the +// testdata/TestExampleCloudThing_basic/test.tf file. +func TestNameFile(file string) func(TestStepConfigRequest) string { + return func(req TestStepConfigRequest) string { + return filepath.Join("testdata", req.TestName, file) + } +} + +// TestStepFile returns the name of the test suffixed with the test +// step number and the supplied file, and prefixed with "testdata". +// +// For example, given test code: +// +// func TestExampleCloudThing_basic(t *testing.T) { +// resource.Test(t, resource.TestCase{ +// Steps: []resource.TestStep{ +// { +// ConfigFile: config.TestStepFile("test.tf"), +// }, +// }, +// }) +// } +// +// The testing configuration will be expected in the +// testdata/TestExampleCloudThing_basic/1/test.tf file +// as TestStepConfigRequest.StepNumber is one-based. +func TestStepFile(file string) func(TestStepConfigRequest) string { + return func(req TestStepConfigRequest) string { + return filepath.Join("testdata", req.TestName, strconv.Itoa(req.StepNumber), file) + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/config/variable.go b/vendor/github.com/hashicorp/terraform-plugin-testing/config/variable.go new file mode 100644 index 0000000000..fa109a2a5c --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/config/variable.go @@ -0,0 +1,324 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package config + +import ( + "encoding/json" + "errors" + "fmt" + "os" + "path/filepath" + "reflect" +) + +const autoTFVarsJson = "terraform-plugin-testing.auto.tfvars.json" + +// Variable interface is an alias to json.Marshaler. +type Variable interface { + json.Marshaler +} + +// Variables is a type holding a key-value map of variable names +// to types implementing the Variable interface. +type Variables map[string]Variable + +// Write creates a file in the destination supplied +// containing JSON encoded Variables. +func (v Variables) Write(dest string) error { + if len(v) == 0 { + return nil + } + + b, err := json.Marshal(v) + + if err != nil { + return fmt.Errorf("cannot marshal variables: %s", err) + } + + outFilename := filepath.Join(dest, autoTFVarsJson) + + err = os.WriteFile(outFilename, b, 0600) + + if err != nil { + return fmt.Errorf("cannot write variables file: %s", err) + } + + return nil +} + +var _ Variable = boolVariable{} + +// boolVariable supports JSON encoding of a bool. +type boolVariable struct { + value bool +} + +// MarshalJSON returns the JSON encoding of boolVariable. +func (v boolVariable) MarshalJSON() ([]byte, error) { + return json.Marshal(v.value) +} + +// BoolVariable returns boolVariable which implements Variable. +func BoolVariable(value bool) boolVariable { + return boolVariable{ + value: value, + } +} + +var _ Variable = floatVariable{} + +// floatVariable supports JSON encoding of any floating-point type. +type floatVariable struct { + value any +} + +// MarshalJSON returns the JSON encoding of floatVariable. +func (v floatVariable) MarshalJSON() ([]byte, error) { + return json.Marshal(v.value) +} + +// FloatVariable returns floatVariable which implements Variable. +func FloatVariable[T anyFloat](value T) floatVariable { + return floatVariable{ + value: value, + } +} + +var _ Variable = integerVariable{} + +// integerVariable supports JSON encoding of any integer type. +type integerVariable struct { + value any +} + +// MarshalJSON returns the JSON encoding of integerVariable. +func (v integerVariable) MarshalJSON() ([]byte, error) { + return json.Marshal(v.value) +} + +// IntegerVariable returns integerVariable which implements Variable. +func IntegerVariable[T anyInteger](value T) integerVariable { + return integerVariable{ + value: value, + } +} + +var _ Variable = listVariable{} + +// listVariable supports JSON encoding of slice of Variable. +type listVariable struct { + value []Variable +} + +// MarshalJSON returns the JSON encoding of listVariable. +// Every Variable within a listVariable must be the same +// underlying type. +func (v listVariable) MarshalJSON() ([]byte, error) { + if !typesEq(v.value) { + return nil, errors.New("lists must contain the same type") + } + + return json.Marshal(v.value) +} + +// ListVariable returns listVariable which implements Variable. +func ListVariable(value ...Variable) listVariable { + return listVariable{ + value: value, + } +} + +var _ Variable = mapVariable{} + +// mapVariable supports JSON encoding of a key-value map of +// string to Variable. +type mapVariable struct { + value map[string]Variable +} + +// MarshalJSON returns the JSON encoding of mapVariable. +// Every Variable in a mapVariable must be the same +// underlying type. +func (v mapVariable) MarshalJSON() ([]byte, error) { + var variables []Variable + + for _, variable := range v.value { + variables = append(variables, variable) + } + + if !typesEq(variables) { + return nil, errors.New("maps must contain the same type") + } + + return json.Marshal(v.value) +} + +// MapVariable returns mapVariable which implements Variable. +func MapVariable(value map[string]Variable) mapVariable { + return mapVariable{ + value: value, + } +} + +var _ Variable = objectVariable{} + +// objectVariable supports JSON encoding of a key-value +// map of string to Variable in which each Variable +// can be a different underlying type. +type objectVariable struct { + value map[string]Variable +} + +// MarshalJSON returns the JSON encoding of objectVariable. +func (v objectVariable) MarshalJSON() ([]byte, error) { + b, err := json.Marshal(v.value) + + if err != nil { + innerErr := err + + // Unwrap is used here to expose the initial error, for example + // "maps must contain the same type" whilst removing any errors + // related to the implementation (i.e., the usage of + // encoding/json in this instance. + for errors.Unwrap(innerErr) != nil { + innerErr = errors.Unwrap(err) + } + + return nil, innerErr + } + + return b, nil +} + +// ObjectVariable returns objectVariable which implements Variable. +func ObjectVariable(value map[string]Variable) objectVariable { + return objectVariable{ + value: value, + } +} + +var _ Variable = setVariable{} + +// setVariable supports JSON encoding of a slice of Variable. +type setVariable struct { + value []Variable +} + +// MarshalJSON returns the JSON encoding of setVariable. +// Every Variable in a setVariable must be the same +// underlying type. +func (v setVariable) MarshalJSON() ([]byte, error) { + for kx, x := range v.value { + for ky := kx + 1; ky < len(v.value); ky++ { + y := v.value[ky] + + if _, ok := x.(setVariable); !ok { + continue + } + + if _, ok := y.(setVariable); !ok { + continue + } + + if reflect.DeepEqual(x, y) { + return nil, errors.New("sets must contain unique elements") + } + } + } + + if !typesEq(v.value) { + return nil, errors.New("sets must contain the same type") + } + + return json.Marshal(v.value) +} + +// SetVariable returns setVariable which implements Variable. +func SetVariable(value ...Variable) setVariable { + return setVariable{ + value: value, + } +} + +var _ Variable = stringVariable{} + +// stringVariable supports JSON encoding of a string. +type stringVariable struct { + value string +} + +// MarshalJSON returns the JSON encoding of stringVariable. +func (v stringVariable) MarshalJSON() ([]byte, error) { + return json.Marshal(v.value) +} + +// StringVariable returns stringVariable which implements Variable. +func StringVariable(value string) stringVariable { + return stringVariable{ + value: value, + } +} + +var _ Variable = tupleVariable{} + +// tupleVariable supports JSON encoding of a slice of Variable +// in which each element in the slice can be a different +// underlying type. +type tupleVariable struct { + value []Variable +} + +// MarshalJSON returns the JSON encoding of tupleVariable. +func (v tupleVariable) MarshalJSON() ([]byte, error) { + return json.Marshal(v.value) +} + +// TupleVariable returns tupleVariable which implements Variable. +func TupleVariable(value ...Variable) tupleVariable { + return tupleVariable{ + value: value, + } +} + +// typesEq verifies that every element in the supplied slice of Variable +// is the same underlying type. +func typesEq(variables []Variable) bool { + var t reflect.Type + + for _, variable := range variables { + switch x := variable.(type) { + case listVariable: + if !typesEq(x.value) { + return false + } + case mapVariable: + var vars []Variable + + for _, v := range x.value { + vars = append(vars, v) + } + + if !typesEq(vars) { + return false + } + case setVariable: + if !typesEq(x.value) { + return false + } + } + + typeOfVariable := reflect.TypeOf(variable) + + if t == nil { + t = typeOfVariable + continue + } + + if t != typeOfVariable { + return false + } + } + + return true +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest/random.go b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/acctest/random.go similarity index 76% rename from vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest/random.go rename to vendor/github.com/hashicorp/terraform-plugin-testing/helper/acctest/random.go index 382163cbc2..102c85ddd8 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest/random.go +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/acctest/random.go @@ -34,9 +34,9 @@ func RandomWithPrefix(name string) string { return fmt.Sprintf("%s-%d", name, RandInt()) } -// RandIntRange returns a random integer between minVal (inclusive) and maxVal (exclusive) -func RandIntRange(minVal int, maxVal int) int { - return rand.Intn(maxVal-minVal) + minVal +// RandIntRange returns a random integer between minInt (inclusive) and maxInt (exclusive) +func RandIntRange(minInt int, maxInt int) int { + return rand.Intn(maxInt-minInt) + minInt } // RandString generates a random alphanumeric string of the length specified @@ -122,10 +122,8 @@ func RandTLSCert(orgName string) (string, string, error) { } // RandIpAddress returns a random IP address in the specified CIDR block. -// The prefix length must be less than 31. func RandIpAddress(s string) (string, error) { prefix, err := netip.ParsePrefix(s) - if err != nil { return "", err } @@ -134,47 +132,36 @@ func RandIpAddress(s string) (string, error) { return prefix.Addr().String(), nil } - prefixSizeExponent := uint(prefix.Addr().BitLen() - prefix.Bits()) - - if prefix.Addr().Is4() && prefixSizeExponent > 31 { - return "", fmt.Errorf("CIDR range is too large: %d", prefixSizeExponent) + // base address as byte slice + prefixBytes, err := prefix.Masked().Addr().MarshalBinary() + if err != nil { + return "", err } - // Prevent panics with rand.Int63n(). - if prefix.Addr().Is6() && prefixSizeExponent > 63 { - return "", fmt.Errorf("CIDR range is too large: %d", prefixSizeExponent) + // inverse mask (ones in the host bits) as byte slice + inverseMaskBytes, err := inverseMask(prefix.Bits(), len(prefixBytes)) + if err != nil { + return "", err } - // Calculate max random integer based on the prefix. - // Bit shift 1< byteLen*8 { + return nil, fmt.Errorf("cannot fit a %d-bit mask into %d bytes", bits, byteLen) + } + + iBits := (byteLen * 8) - bits + var result []byte + for iBits > 0 { + b := uint8((1 << iBits) - 1) + result = append([]byte{b}, result...) + iBits -= 8 + } + + return append(make([]byte, byteLen-len(result)), result...), nil +} + const ( // CharSetAlphaNum is the alphanumeric character set for use with // RandStringFromCharSet diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/additional_cli_options.go b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/additional_cli_options.go new file mode 100644 index 0000000000..36dc0f89a8 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/additional_cli_options.go @@ -0,0 +1,29 @@ +// 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 + + // NoRefresh will pass the `-refresh=false` flag to the plan command. + NoRefresh bool +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource/environment_variables.go b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/environment_variables.go similarity index 77% rename from vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource/environment_variables.go rename to vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/environment_variables.go index 981908799e..2f8967fc66 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource/environment_variables.go +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/environment_variables.go @@ -32,4 +32,14 @@ const ( // type Config field includes a provider source, such as the terraform // configuration block required_providers attribute. EnvTfAccProviderNamespace = "TF_ACC_PROVIDER_NAMESPACE" + + // This is an undocumented compatibility flag. When this is set, a + // `Config`-mode test step will invoke a refresh before successful + // completion. + // + // This is a compatibility measure for test cases that have different -- + // but semantically-equal -- state representations in their test steps. + // When comparing two states, the testing framework is not aware of + // semantic equality or set equality. + EnvTfAccRefreshAfterApply = "TF_ACC_REFRESH_AFTER_APPLY" ) diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/error.go b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/error.go new file mode 100644 index 0000000000..3c990e35fc --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/error.go @@ -0,0 +1,132 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package resource + +import ( + "fmt" + "strings" + "time" +) + +// NotFoundError represents when a StateRefreshFunc returns a nil result +// during a StateChangeConf waiter method and that StateChangeConf is +// configured for specific targets. +// +// Deprecated: Copy this type to the provider codebase or use +// github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry.NotFoundError. +type NotFoundError struct { + LastError error + LastRequest interface{} + LastResponse interface{} + Message string + Retries int +} + +// Error returns the Message string, if non-empty, or a string indicating +// the resource could not be found. +// +// Deprecated: Copy this method to the provider codebase or use +// github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry.NotFoundError. +func (e *NotFoundError) Error() string { + if e.Message != "" { + return e.Message + } + + if e.Retries > 0 { + return fmt.Sprintf("couldn't find resource (%d retries)", e.Retries) + } + + return "couldn't find resource" +} + +// Unwrap returns the LastError, compatible with errors.Unwrap. +// +// Deprecated: Copy this method to the provider codebase or use +// github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry.NotFoundError. +func (e *NotFoundError) Unwrap() error { + return e.LastError +} + +// UnexpectedStateError is returned when Refresh returns a state that's neither in Target nor Pending +// +// Deprecated: Copy this type to the provider codebase or use +// github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry.UnexpectedStateError. +type UnexpectedStateError struct { + LastError error + State string + ExpectedState []string +} + +// Error returns a string with the unexpected state value, the desired target, +// and any last error. +// +// Deprecated: Copy this method to the provider codebase or use +// github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry.UnexpectedStateError. +func (e *UnexpectedStateError) Error() string { + return fmt.Sprintf( + "unexpected state '%s', wanted target '%s'. last error: %s", + e.State, + strings.Join(e.ExpectedState, ", "), + e.LastError, + ) +} + +// Unwrap returns the LastError, compatible with errors.Unwrap. +// +// Deprecated: Copy this method to the provider codebase or use +// github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry.UnexpectedStateError. +func (e *UnexpectedStateError) Unwrap() error { + return e.LastError +} + +// TimeoutError is returned when WaitForState times out +// +// Deprecated: Copy this type to the provider codebase or use +// github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry.TimeoutError. +type TimeoutError struct { + LastError error + LastState string + Timeout time.Duration + ExpectedState []string +} + +// Error returns a string with any information available. +// +// Deprecated: Copy this method to the provider codebase or use +// github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry.TimeoutError. +func (e *TimeoutError) Error() string { + expectedState := "resource to be gone" + if len(e.ExpectedState) > 0 { + expectedState = fmt.Sprintf("state to become '%s'", strings.Join(e.ExpectedState, ", ")) + } + + extraInfo := make([]string, 0) + if e.LastState != "" { + extraInfo = append(extraInfo, fmt.Sprintf("last state: '%s'", e.LastState)) + } + if e.Timeout > 0 { + extraInfo = append(extraInfo, fmt.Sprintf("timeout: %s", e.Timeout.String())) + } + + suffix := "" + if len(extraInfo) > 0 { + suffix = fmt.Sprintf(" (%s)", strings.Join(extraInfo, ", ")) + } + + if e.LastError != nil { + return fmt.Sprintf("timeout while waiting for %s%s: %s", + expectedState, suffix, e.LastError) + } + + return fmt.Sprintf("timeout while waiting for %s%s", + expectedState, suffix) +} + +// Unwrap returns the LastError, compatible with errors.Unwrap. +// +// Deprecated: Copy this method to the provider codebase or use +// github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry.TimeoutError. +func (e *TimeoutError) Unwrap() error { + return e.LastError +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/id/id.go b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/id.go similarity index 70% rename from vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/id/id.go rename to vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/id.go index 0491252c85..81c1f2e8e8 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/id/id.go +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/id.go @@ -1,7 +1,7 @@ // Copyright (c) HashiCorp, Inc. // SPDX-License-Identifier: MPL-2.0 -package id +package resource import ( "fmt" @@ -10,6 +10,11 @@ import ( "time" ) +// UniqueIdPrefix is a string prefix automatically added to return values of +// the UniqueId function. +// +// Deprecated: Copy this value to the provider codebase or use +// github.com/hashicorp/terraform-plugin-sdk/v2/helper/id.UniquePrefix. const UniqueIdPrefix = `terraform-` // idCounter is a monotonic counter for generating ordered unique ids. @@ -17,6 +22,9 @@ var idMutex sync.Mutex var idCounter uint32 // Helper for a resource to generate a unique identifier w/ default prefix +// +// Deprecated: Copy this function to the provider codebase or use +// github.com/hashicorp/terraform-plugin-sdk/v2/helper/id.Unique. func UniqueId() string { return PrefixedUniqueId(UniqueIdPrefix) } @@ -24,6 +32,9 @@ func UniqueId() string { // UniqueIDSuffixLength is the string length of the suffix generated by // PrefixedUniqueId. This can be used by length validation functions to // ensure prefixes are the correct length for the target field. +// +// Deprecated: Copy this value to the provider codebase or use +// github.com/hashicorp/terraform-plugin-sdk/v2/helper/id.UniqueSuffixLength. const UniqueIDSuffixLength = 26 // Helper for a resource to generate a unique identifier w/ given prefix @@ -35,6 +46,9 @@ const UniqueIDSuffixLength = 26 // across multiple terraform executions, as long as the clock is not turned back // between calls, and as long as any given terraform execution generates fewer // than 4 billion IDs. +// +// Deprecated: Copy this function to the provider codebase or use +// github.com/hashicorp/terraform-plugin-sdk/v2/helper/id.PrefixedUnique. func PrefixedUniqueId(prefix string) string { // Be precise to 4 digits of fractional seconds, but remove the dot before the // fractional seconds. diff --git a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource/json.go b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/json.go similarity index 100% rename from vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource/json.go rename to vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/json.go diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/plan_checks.go b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/plan_checks.go new file mode 100644 index 0000000000..c64c02ba8f --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/plan_checks.go @@ -0,0 +1,28 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package resource + +import ( + "context" + "errors" + + tfjson "github.com/hashicorp/terraform-json" + "github.com/hashicorp/terraform-plugin-testing/plancheck" + "github.com/mitchellh/go-testing-interface" +) + +func runPlanChecks(ctx context.Context, t testing.T, plan *tfjson.Plan, planChecks []plancheck.PlanCheck) error { + t.Helper() + + var result []error + + for _, planCheck := range planChecks { + resp := plancheck.CheckPlanResponse{} + planCheck.CheckPlan(ctx, plancheck.CheckPlanRequest{Plan: plan}, &resp) + + result = append(result, resp.Error) + } + + return errors.Join(result...) +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource/plugin.go b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/plugin.go similarity index 88% rename from vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource/plugin.go rename to vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/plugin.go index 14d869466b..86c3b77baf 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource/plugin.go +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/plugin.go @@ -13,13 +13,15 @@ import ( "github.com/hashicorp/go-hclog" "github.com/hashicorp/terraform-exec/tfexec" + tfjson "github.com/hashicorp/terraform-json" "github.com/hashicorp/terraform-plugin-go/tfprotov5" "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/v2/internal/logging" - "github.com/hashicorp/terraform-plugin-sdk/v2/internal/plugintest" "github.com/hashicorp/terraform-plugin-sdk/v2/plugin" - testing "github.com/mitchellh/go-testing-interface" + "github.com/mitchellh/go-testing-interface" + + "github.com/hashicorp/terraform-plugin-testing/internal/logging" + "github.com/hashicorp/terraform-plugin-testing/internal/plugintest" ) // protov5ProviderFactory is a function which is called to start a protocol @@ -112,7 +114,42 @@ type providerFactories struct { protov6 protov6ProviderFactories } -func runProviderCommand(ctx context.Context, t testing.T, f func() error, wd *plugintest.WorkingDir, factories *providerFactories) error { +func runProviderCommandApplyRefreshOnly(ctx context.Context, t testing.T, wd *plugintest.WorkingDir, factories *providerFactories) error { + t.Helper() + + fn := func() error { + return wd.Apply(ctx, tfexec.Refresh(true), tfexec.RefreshOnly(true)) + } + return runProviderCommand(ctx, t, wd, factories, fn) +} + +func runProviderCommandCreatePlan(ctx context.Context, t testing.T, wd *plugintest.WorkingDir, factories *providerFactories) error { + t.Helper() + + fn := func() error { + return wd.CreatePlan(ctx) + } + return runProviderCommand(ctx, t, wd, factories, fn) +} + +func runProviderCommandSavedPlan(ctx context.Context, t testing.T, wd *plugintest.WorkingDir, factories *providerFactories) (*tfjson.Plan, error) { + t.Helper() + + var plan *tfjson.Plan + fn := func() error { + var err error + plan, err = wd.SavedPlan(ctx) + return err + } + err := runProviderCommand(ctx, t, wd, factories, fn) + if err != nil { + return nil, err + } + + return plan, nil +} + +func runProviderCommand(ctx context.Context, t testing.T, wd *plugintest.WorkingDir, factories *providerFactories, f func() error) error { // don't point to this as a test failure location // point to whatever called it t.Helper() @@ -177,14 +214,14 @@ func runProviderCommand(ctx context.Context, t testing.T, f func() error, wd *pl providerName = strings.TrimPrefix(providerName, "terraform-provider-") providerAddress := getProviderAddr(providerName) - logging.HelperResourceDebug(ctx, "Creating sdkv2 provider instance", map[string]interface{}{logging.KeyProviderAddress: providerAddress}) + logging.HelperResourceTrace(ctx, "Creating sdkv2 provider instance", map[string]interface{}{logging.KeyProviderAddress: providerAddress}) provider, err := factory() if err != nil { return fmt.Errorf("unable to create provider %q from factory: %w", providerName, err) } - logging.HelperResourceDebug(ctx, "Created sdkv2 provider instance", map[string]interface{}{logging.KeyProviderAddress: providerAddress}) + logging.HelperResourceTrace(ctx, "Created sdkv2 provider instance", map[string]interface{}{logging.KeyProviderAddress: providerAddress}) // keep track of the running factory, so we can make sure it's // shut down. @@ -214,14 +251,14 @@ func runProviderCommand(ctx context.Context, t testing.T, f func() error, wd *pl ProviderAddr: providerAddress, } - logging.HelperResourceDebug(ctx, "Starting sdkv2 provider instance server", map[string]interface{}{logging.KeyProviderAddress: providerAddress}) + logging.HelperResourceTrace(ctx, "Starting sdkv2 provider instance server", map[string]interface{}{logging.KeyProviderAddress: providerAddress}) config, closeCh, err := plugin.DebugServe(ctx, opts) if err != nil { return fmt.Errorf("unable to serve provider %q: %w", providerName, err) } - logging.HelperResourceDebug(ctx, "Started sdkv2 provider instance server", map[string]interface{}{logging.KeyProviderAddress: providerAddress}) + logging.HelperResourceTrace(ctx, "Started sdkv2 provider instance server", map[string]interface{}{logging.KeyProviderAddress: providerAddress}) tfexecConfig := tfexec.ReattachConfig{ Protocol: config.Protocol, @@ -271,14 +308,14 @@ func runProviderCommand(ctx context.Context, t testing.T, f func() error, wd *pl } } - logging.HelperResourceDebug(ctx, "Creating tfprotov5 provider instance", map[string]interface{}{logging.KeyProviderAddress: providerAddress}) + logging.HelperResourceTrace(ctx, "Creating tfprotov5 provider instance", map[string]interface{}{logging.KeyProviderAddress: providerAddress}) provider, err := factory() if err != nil { return fmt.Errorf("unable to create provider %q from factory: %w", providerName, err) } - logging.HelperResourceDebug(ctx, "Created tfprotov5 provider instance", map[string]interface{}{logging.KeyProviderAddress: providerAddress}) + logging.HelperResourceTrace(ctx, "Created tfprotov5 provider instance", map[string]interface{}{logging.KeyProviderAddress: providerAddress}) // keep track of the running factory, so we can make sure it's // shut down. @@ -302,14 +339,14 @@ func runProviderCommand(ctx context.Context, t testing.T, f func() error, wd *pl ProviderAddr: providerAddress, } - logging.HelperResourceDebug(ctx, "Starting tfprotov5 provider instance server", map[string]interface{}{logging.KeyProviderAddress: providerAddress}) + logging.HelperResourceTrace(ctx, "Starting tfprotov5 provider instance server", map[string]interface{}{logging.KeyProviderAddress: providerAddress}) config, closeCh, err := plugin.DebugServe(ctx, opts) if err != nil { return fmt.Errorf("unable to serve provider %q: %w", providerName, err) } - logging.HelperResourceDebug(ctx, "Started tfprotov5 provider instance server", map[string]interface{}{logging.KeyProviderAddress: providerAddress}) + logging.HelperResourceTrace(ctx, "Started tfprotov5 provider instance server", map[string]interface{}{logging.KeyProviderAddress: providerAddress}) tfexecConfig := tfexec.ReattachConfig{ Protocol: config.Protocol, @@ -360,14 +397,14 @@ func runProviderCommand(ctx context.Context, t testing.T, f func() error, wd *pl } } - logging.HelperResourceDebug(ctx, "Creating tfprotov6 provider instance", map[string]interface{}{logging.KeyProviderAddress: providerAddress}) + logging.HelperResourceTrace(ctx, "Creating tfprotov6 provider instance", map[string]interface{}{logging.KeyProviderAddress: providerAddress}) provider, err := factory() if err != nil { return fmt.Errorf("unable to create provider %q from factory: %w", providerName, err) } - logging.HelperResourceDebug(ctx, "Created tfprotov6 provider instance", map[string]interface{}{logging.KeyProviderAddress: providerAddress}) + logging.HelperResourceTrace(ctx, "Created tfprotov6 provider instance", map[string]interface{}{logging.KeyProviderAddress: providerAddress}) // keep track of the running factory, so we can make sure it's // shut down. @@ -387,14 +424,14 @@ func runProviderCommand(ctx context.Context, t testing.T, f func() error, wd *pl ProviderAddr: providerAddress, } - logging.HelperResourceDebug(ctx, "Starting tfprotov6 provider instance server", map[string]interface{}{logging.KeyProviderAddress: providerAddress}) + logging.HelperResourceTrace(ctx, "Starting tfprotov6 provider instance server", map[string]interface{}{logging.KeyProviderAddress: providerAddress}) config, closeCh, err := plugin.DebugServe(ctx, opts) if err != nil { return fmt.Errorf("unable to serve provider %q: %w", providerName, err) } - logging.HelperResourceDebug(ctx, "Started tfprotov6 provider instance server", map[string]interface{}{logging.KeyProviderAddress: providerAddress}) + logging.HelperResourceTrace(ctx, "Started tfprotov6 provider instance server", map[string]interface{}{logging.KeyProviderAddress: providerAddress}) tfexecConfig := tfexec.ReattachConfig{ Protocol: config.Protocol, @@ -440,7 +477,7 @@ func runProviderCommand(ctx context.Context, t testing.T, f func() error, wd *pl } logging.HelperResourceTrace(ctx, "Called wrapped Terraform CLI command") - logging.HelperResourceDebug(ctx, "Stopping providers") + logging.HelperResourceTrace(ctx, "Stopping providers") // cancel the servers so they'll return. Otherwise, this closeCh won't // get closed, and we'll hang here. diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/query/query_checks.go b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/query/query_checks.go new file mode 100644 index 0000000000..79a5b9813a --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/query/query_checks.go @@ -0,0 +1,96 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package query + +import ( + "context" + "errors" + "fmt" + + tfjson "github.com/hashicorp/terraform-json" + "github.com/mitchellh/go-testing-interface" + + "github.com/hashicorp/terraform-plugin-testing/querycheck" + "github.com/hashicorp/terraform-plugin-testing/querycheck/queryfilter" +) + +func RunQueryChecks(ctx context.Context, t testing.T, query []tfjson.LogMsg, queryChecks []querycheck.QueryResultCheck) error { + t.Helper() + + var result []error + + if query == nil { + result = append(result, fmt.Errorf("no query results found")) + } + + found := make([]tfjson.ListResourceFoundData, 0) + summary := tfjson.ListCompleteData{} + + for _, msg := range query { + switch v := msg.(type) { + case tfjson.ListResourceFoundMessage: + found = append(found, v.ListResourceFound) + case tfjson.ListCompleteMessage: + summary = v.ListComplete + // TODO diagnostics and errors? + default: + continue + } + } + + var reqQueryData []tfjson.ListResourceFoundData + for _, queryCheck := range queryChecks { + reqQueryData = found + if filterCheck, ok := queryCheck.(querycheck.QueryResultCheckWithFilters); ok { + filtered, err := runQueryFilters(ctx, filterCheck, reqQueryData) + if err != nil { + return err + } + reqQueryData = filtered + } + resp := querycheck.CheckQueryResponse{} + queryCheck.CheckQuery(ctx, querycheck.CheckQueryRequest{ + Query: reqQueryData, + QuerySummary: &summary, + }, &resp) + + result = append(result, resp.Error) + } + + return errors.Join(result...) +} + +func runQueryFilters(ctx context.Context, filterCheck querycheck.QueryResultCheckWithFilters, queryResults []tfjson.ListResourceFoundData) ([]tfjson.ListResourceFoundData, error) { + filters := filterCheck.QueryFilters(ctx) + filteredResults := make([]tfjson.ListResourceFoundData, 0) + + // If there are no filters, just return the original results + if len(filters) == 0 { + return queryResults, nil + } + + for _, result := range queryResults { + keepResult := false + + for _, filter := range filters { + + resp := queryfilter.FilterQueryResponse{} + filter.Filter(ctx, queryfilter.FilterQueryRequest{QueryItem: result}, &resp) + + if resp.Include { + keepResult = true + } + + if resp.Error != nil { + return nil, resp.Error + } + } + + if keepResult { + filteredResults = append(filteredResults, result) + } + } + + return filteredResults, nil +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/state.go b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/state.go new file mode 100644 index 0000000000..9ed1132703 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/state.go @@ -0,0 +1,292 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package resource + +import ( + "context" + "log" + "time" +) + +var refreshGracePeriod = 30 * time.Second + +// StateRefreshFunc is a function type used for StateChangeConf that is +// responsible for refreshing the item being watched for a state change. +// +// It returns three results. `result` is any object that will be returned +// as the final object after waiting for state change. This allows you to +// return the final updated object, for example an EC2 instance after refreshing +// it. A nil result represents not found. +// +// `state` is the latest state of that object. And `err` is any error that +// may have happened while refreshing the state. +// +// Deprecated: Copy this type to the provider codebase or use +// github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry.StateRefreshFunc. +type StateRefreshFunc func() (result interface{}, state string, err error) + +// StateChangeConf is the configuration struct used for `WaitForState`. +// +// Deprecated: Copy this type to the provider codebase or use +// github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry.StateChangeConf. +type StateChangeConf struct { + Delay time.Duration // Wait this time before starting checks + Pending []string // States that are "allowed" and will continue trying + Refresh StateRefreshFunc // Refreshes the current state + Target []string // Target state + Timeout time.Duration // The amount of time to wait before timeout + MinTimeout time.Duration // Smallest time to wait before refreshes + PollInterval time.Duration // Override MinTimeout/backoff and only poll this often + NotFoundChecks int // Number of times to allow not found (nil result from Refresh) + + // This is to work around inconsistent APIs + ContinuousTargetOccurence int // Number of times the Target state has to occur continuously +} + +// WaitForStateContext watches an object and waits for it to achieve the state +// specified in the configuration using the specified Refresh() func, +// waiting the number of seconds specified in the timeout configuration. +// +// If the Refresh function returns an error, exit immediately with that error. +// +// If the Refresh function returns a state other than the Target state or one +// listed in Pending, return immediately with an error. +// +// If the Timeout is exceeded before reaching the Target state, return an +// error. +// +// Otherwise, the result is the result of the first call to the Refresh function to +// reach the target state. +// +// # Cancellation from the passed in context will cancel the refresh loop +// +// Deprecated: Copy this method to the provider codebase or use +// github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry.StateChangeConf. +func (conf *StateChangeConf) WaitForStateContext(ctx context.Context) (interface{}, error) { + log.Printf("[DEBUG] Waiting for state to become: %s", conf.Target) + + notfoundTick := 0 + targetOccurence := 0 + + // Set a default for times to check for not found + if conf.NotFoundChecks == 0 { + conf.NotFoundChecks = 20 + } + + if conf.ContinuousTargetOccurence == 0 { + conf.ContinuousTargetOccurence = 1 + } + + type Result struct { + Result interface{} + State string + Error error + Done bool + } + + // Read every result from the refresh loop, waiting for a positive result.Done. + resCh := make(chan Result, 1) + // cancellation channel for the refresh loop + cancelCh := make(chan struct{}) + + result := Result{} + + go func() { + defer close(resCh) + + select { + case <-time.After(conf.Delay): + case <-cancelCh: + return + } + + // start with 0 delay for the first loop + var wait time.Duration + + for { + // store the last result + resCh <- result + + // wait and watch for cancellation + select { + case <-cancelCh: + return + case <-time.After(wait): + // first round had no wait + if wait == 0 { + wait = 100 * time.Millisecond + } + } + + res, currentState, err := conf.Refresh() + result = Result{ + Result: res, + State: currentState, + Error: err, + } + + if err != nil { + resCh <- result + return + } + + // If we're waiting for the absence of a thing, then return + if res == nil && len(conf.Target) == 0 { + targetOccurence++ + if conf.ContinuousTargetOccurence == targetOccurence { + result.Done = true + resCh <- result + return + } + continue + } + + if res == nil { + // If we didn't find the resource, check if we have been + // not finding it for awhile, and if so, report an error. + notfoundTick++ + if notfoundTick > conf.NotFoundChecks { + result.Error = &NotFoundError{ + LastError: err, + Retries: notfoundTick, + } + resCh <- result + return + } + } else { + // Reset the counter for when a resource isn't found + notfoundTick = 0 + found := false + + for _, allowed := range conf.Target { + if currentState == allowed { + found = true + targetOccurence++ + if conf.ContinuousTargetOccurence == targetOccurence { + result.Done = true + resCh <- result + return + } + continue + } + } + + for _, allowed := range conf.Pending { + if currentState == allowed { + found = true + targetOccurence = 0 + break + } + } + + if !found && len(conf.Pending) > 0 { + result.Error = &UnexpectedStateError{ + LastError: err, + State: result.State, + ExpectedState: conf.Target, + } + resCh <- result + return + } + } + + // Wait between refreshes using exponential backoff, except when + // waiting for the target state to reoccur. + if targetOccurence == 0 { + wait *= 2 + } + + // If a poll interval has been specified, choose that interval. + // Otherwise bound the default value. + if conf.PollInterval > 0 && conf.PollInterval < 180*time.Second { + wait = conf.PollInterval + } else { + if wait < conf.MinTimeout { + wait = conf.MinTimeout + } else if wait > 10*time.Second { + wait = 10 * time.Second + } + } + + log.Printf("[TRACE] Waiting %s before next try", wait) + } + }() + + // store the last value result from the refresh loop + lastResult := Result{} + + timeout := time.After(conf.Timeout) + for { + select { + case r, ok := <-resCh: + // channel closed, so return the last result + if !ok { + return lastResult.Result, lastResult.Error + } + + // we reached the intended state + if r.Done { + return r.Result, r.Error + } + + // still waiting, store the last result + lastResult = r + case <-ctx.Done(): + close(cancelCh) + return nil, ctx.Err() + case <-timeout: + log.Printf("[WARN] WaitForState timeout after %s", conf.Timeout) + log.Printf("[WARN] WaitForState starting %s refresh grace period", refreshGracePeriod) + + // cancel the goroutine and start our grace period timer + close(cancelCh) + timeout := time.After(refreshGracePeriod) + + // we need a for loop and a label to break on, because we may have + // an extra response value to read, but still want to wait for the + // channel to close. + forSelect: + for { + select { + case r, ok := <-resCh: + if r.Done { + // the last refresh loop reached the desired state + return r.Result, r.Error + } + + if !ok { + // the goroutine returned + break forSelect + } + + // target state not reached, save the result for the + // TimeoutError and wait for the channel to close + lastResult = r + case <-ctx.Done(): + log.Println("[ERROR] Context cancelation detected, abandoning grace period") + break forSelect + case <-timeout: + log.Println("[ERROR] WaitForState exceeded refresh grace period") + break forSelect + } + } + + return nil, &TimeoutError{ + LastError: lastResult.Error, + LastState: lastResult.State, + Timeout: conf.Timeout, + ExpectedState: conf.Target, + } + } + } +} + +// WaitForState watches an object and waits for it to achieve the state +// specified in the configuration using the specified Refresh() func, +// waiting the number of seconds specified in the timeout configuration. +// +// Deprecated: Please use WaitForStateContext to ensure proper plugin shutdown +func (conf *StateChangeConf) WaitForState() (interface{}, error) { + return conf.WaitForStateContext(context.Background()) +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/state_checks.go b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/state_checks.go new file mode 100644 index 0000000000..66c850eae4 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/state_checks.go @@ -0,0 +1,29 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package resource + +import ( + "context" + "errors" + + tfjson "github.com/hashicorp/terraform-json" + "github.com/mitchellh/go-testing-interface" + + "github.com/hashicorp/terraform-plugin-testing/statecheck" +) + +func runStateChecks(ctx context.Context, t testing.T, state *tfjson.State, stateChecks []statecheck.StateCheck) error { + t.Helper() + + var result []error + + for _, stateCheck := range stateChecks { + resp := statecheck.CheckStateResponse{} + stateCheck.CheckState(ctx, statecheck.CheckStateRequest{State: state}, &resp) + + result = append(result, resp.Error) + } + + return errors.Join(result...) +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource/state_shim.go b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/state_shim.go similarity index 72% rename from vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource/state_shim.go rename to vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/state_shim.go index 43c809f2ba..2cd8407ff5 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource/state_shim.go +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/state_shim.go @@ -10,9 +10,10 @@ import ( tfjson "github.com/hashicorp/terraform-json" - "github.com/hashicorp/terraform-plugin-sdk/v2/internal/addrs" - "github.com/hashicorp/terraform-plugin-sdk/v2/internal/tfdiags" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-plugin-testing/terraform" + + "github.com/hashicorp/terraform-plugin-testing/internal/addrs" + "github.com/hashicorp/terraform-plugin-testing/internal/tfdiags" ) type shimmedState struct { @@ -20,7 +21,7 @@ type shimmedState struct { } func shimStateFromJson(jsonState *tfjson.State) (*terraform.State, error) { - state := terraform.NewState() + state := terraform.NewState() //nolint:staticcheck // legacy usage state.TFVersion = jsonState.TerraformVersion if jsonState.Values == nil { @@ -61,24 +62,45 @@ func shimOutputState(so *tfjson.StateOutput) (*terraform.OutputState, error) { os.Value = v return os, nil } + switch firstElem := v[0].(type) { case string: elements := make([]interface{}, len(v)) for i, el := range v { - elements[i] = el.(string) + strElement, ok := el.(string) + // If the type of the element doesn't match the first elem, it's a tuple, return the original value + if !ok { + os.Value = v + return os, nil + } + elements[i] = strElement } os.Value = elements case bool: elements := make([]interface{}, len(v)) for i, el := range v { - elements[i] = el.(bool) + boolElement, ok := el.(bool) + // If the type of the element doesn't match the first elem, it's a tuple, return the original value + if !ok { + os.Value = v + return os, nil + } + + elements[i] = boolElement } os.Value = elements // unmarshalled number from JSON will always be json.Number case json.Number: elements := make([]interface{}, len(v)) for i, el := range v { - elements[i] = el.(json.Number) + numberElement, ok := el.(json.Number) + // If the type of the element doesn't match the first elem, it's a tuple, return the original value + if !ok { + os.Value = v + return os, nil + } + + elements[i] = numberElement } os.Value = elements case []interface{}: @@ -120,7 +142,7 @@ func (ss *shimmedState) shimStateModule(sm *tfjson.StateModule) error { } } - mod := ss.state.AddModule(path) + mod := ss.state.AddModule(path) //nolint:staticcheck // legacy usage for _, res := range sm.Resources { resourceState, err := shimResourceState(res) if err != nil { @@ -189,15 +211,36 @@ func shimResourceState(res *tfjson.StateResource) (*terraform.ResourceState, err } attributes := sf.Flatmap() - if _, ok := attributes["id"]; !ok { - return nil, fmt.Errorf("no %q found in attributes", "id") + // The instance state identifier was a Terraform versions 0.11 and earlier + // concept which helped core and the then SDK determine if the resource + // should be removed and as an identifier value in the human readable + // output. This concept unfortunately carried over to the testing logic when + // the testing logic was mostly changed to use the public, machine-readable + // JSON interface with Terraform, rather than reusing prior internal logic + // from Terraform. Using the "id" attribute value for this identifier was + // the default implementation and therefore those older versions of + // Terraform required the attribute. This is no longer necessary after + // Terraform versions 0.12 and later. + // + // If the "id" attribute is not found, set the instance state identifier to + // a synthetic value that can hopefully lead someone encountering the value + // to these comments. The prior logic used to raise an error if the + // attribute was not present, but this value should now only be present in + // legacy logic of this Go module, such as unintentionally exported logic in + // the terraform package, and not encountered during normal testing usage. + // + // Reference: https://github.com/hashicorp/terraform-plugin-testing/issues/84 + instanceStateID, ok := attributes["id"] + + if !ok { + instanceStateID = "id-attribute-not-set" } return &terraform.ResourceState{ Provider: res.ProviderName, Type: res.Type, Primary: &terraform.InstanceState{ - ID: attributes["id"], + ID: instanceStateID, Attributes: attributes, Meta: map[string]interface{}{ "schema_version": int(res.SchemaVersion), diff --git a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource/testcase_providers.go b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/testcase_providers.go similarity index 100% rename from vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource/testcase_providers.go rename to vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/testcase_providers.go diff --git a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource/testcase_validate.go b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/testcase_validate.go similarity index 66% rename from vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource/testcase_validate.go rename to vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/testcase_validate.go index 8eb85a14ab..6640f8c84c 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource/testcase_validate.go +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/testcase_validate.go @@ -7,9 +7,18 @@ import ( "context" "fmt" - "github.com/hashicorp/terraform-plugin-sdk/v2/internal/logging" + "github.com/mitchellh/go-testing-interface" + + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/internal/logging" + "github.com/hashicorp/terraform-plugin-testing/internal/teststep" ) +// hasProviders returns true if the TestCase has ExternalProviders set. +func (c TestCase) hasExternalProviders(_ context.Context) bool { + return len(c.ExternalProviders) > 0 +} + // hasProviders returns true if the TestCase has set any of the // ExternalProviders, ProtoV5ProviderFactories, ProtoV6ProviderFactories, // ProviderFactories, or Providers fields. @@ -42,7 +51,7 @@ func (c TestCase) hasProviders(_ context.Context) bool { // - No overlapping ExternalProviders and Providers entries // - No overlapping ExternalProviders and ProviderFactories entries // - TestStep validations performed by the (TestStep).validate() method. -func (c TestCase) validate(ctx context.Context) error { +func (c TestCase) validate(ctx context.Context, t testing.T) error { logging.HelperResourceTrace(ctx, "Validating TestCase") if len(c.Steps) == 0 { @@ -65,13 +74,30 @@ func (c TestCase) validate(ctx context.Context) error { } } + testCaseHasExternalProviders := c.hasExternalProviders(ctx) testCaseHasProviders := c.hasProviders(ctx) for stepIndex, step := range c.Steps { stepNumber := stepIndex + 1 // Use 1-based index for humans + + configRequest := teststep.PrepareConfigurationRequest{ + Directory: step.ConfigDirectory, + File: step.ConfigFile, + Raw: step.Config, + TestStepConfigRequest: config.TestStepConfigRequest{ + StepNumber: stepNumber, + TestName: t.Name(), + }, + }.Exec() + + stepConfiguration := teststep.Configuration(configRequest) + stepValidateReq := testStepValidateRequest{ - StepNumber: stepNumber, - TestCaseHasProviders: testCaseHasProviders, + StepConfiguration: stepConfiguration, + StepNumber: stepNumber, + TestCaseHasExternalProviders: testCaseHasExternalProviders, + TestCaseHasProviders: testCaseHasProviders, + TestName: t.Name(), } err := step.validate(ctx, stepValidateReq) diff --git a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource/testing.go b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/testing.go similarity index 66% rename from vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource/testing.go rename to vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/testing.go index ac575ed43c..4ba1961bf1 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource/testing.go +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/testing.go @@ -15,16 +15,24 @@ import ( "strings" "time" + "github.com/hashicorp/terraform-plugin-testing/querycheck" + "github.com/mitchellh/go-testing-interface" "github.com/hashicorp/terraform-plugin-go/tfprotov5" "github.com/hashicorp/terraform-plugin-go/tfprotov6" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" - "github.com/hashicorp/terraform-plugin-sdk/v2/internal/addrs" - "github.com/hashicorp/terraform-plugin-sdk/v2/internal/logging" - "github.com/hashicorp/terraform-plugin-sdk/v2/internal/plugintest" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/plancheck" + "github.com/hashicorp/terraform-plugin-testing/statecheck" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/hashicorp/terraform-plugin-testing/tfversion" + + "github.com/hashicorp/terraform-plugin-testing/internal/addrs" + "github.com/hashicorp/terraform-plugin-testing/internal/logging" + "github.com/hashicorp/terraform-plugin-testing/internal/plugintest" ) // flagSweep is a flag available when running tests on the command line. It @@ -157,7 +165,7 @@ func runSweepers(regions []string, sweepers map[string]*Sweeper, allowFailures b log.Printf("Sweeper Tests for region (%s) ran successfully:\n", region) for sweeper, sweeperErr := range regionSweeperRunList { if sweeperErr == nil { - fmt.Printf("\t- %s\n", sweeper) + log.Printf("\t- %s\n", sweeper) } else { regionSweeperErrorFound = true } @@ -168,7 +176,7 @@ func runSweepers(regions []string, sweepers map[string]*Sweeper, allowFailures b log.Printf("Sweeper Tests for region (%s) ran unsuccessfully:\n", region) for sweeper, sweeperErr := range regionSweeperRunList { if sweeperErr != nil { - fmt.Printf("\t- %s: %s\n", sweeper, sweeperErr) + log.Printf("\t- %s: %s\n", sweeper, sweeperErr) } } } @@ -319,6 +327,12 @@ type TestCase struct { // acceptance tests, such as verifying that keys are setup. PreCheck func() + // TerraformVersionChecks is a list of checks to run against + // the Terraform CLI version which is running the testing. + // Each check is executed in order, respecting the first skip + // or fail response, unless the Any() meta check is also used. + TerraformVersionChecks []tfversion.TerraformVersionCheck + // ProviderFactories can be specified for the providers that are valid. // // This can also be specified at the TestStep level to enable per-step @@ -396,6 +410,11 @@ type TestCase struct { // ErrorCheck allows providers the option to handle errors such as skipping // tests based on certain errors. + // + // This functionality is only intended for provider-controlled error + // messaging. While in certain scenarios this can also catch testing logic + // error messages, those messages are not protected by compatibility + // promises. ErrorCheck ErrorCheckFunc // Steps are the apply sequences done within the context of the @@ -415,6 +434,19 @@ type TestCase struct { // IDRefreshIgnore is a list of configuration keys that will be ignored // during ID-only refresh testing. IDRefreshIgnore []string + + // WorkingDir sets the base directory where testing files used by the testing + // module are generated. If WorkingDir is unset, a randomized, temporary + // directory is used. + // + // Use the TF_ACC_PERSIST_WORKING_DIR environment variable, conventionally + // 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 @@ -424,6 +456,37 @@ type ExternalProvider struct { Source string // the provider source } +type ImportStateKind byte + +const ( + // ImportCommandWithID tests import by using the ID string with the `terraform import` command + ImportCommandWithID ImportStateKind = iota + + // ImportBlockWithID tests import by using the ID string in an import configuration block with the `terraform plan` command + ImportBlockWithID + + // ImportBlockWithResourceIdentity imports the state using an import block with a resource identity + ImportBlockWithResourceIdentity +) + +// plannable reports whether this kind indicates the use of plannable import blocks +func (kind ImportStateKind) plannable() bool { + return kind == ImportBlockWithID || kind == ImportBlockWithResourceIdentity +} + +// resourceIdentity reports whether this kind indicates the use of resource identity in import blocks +func (kind ImportStateKind) resourceIdentity() bool { + return kind == ImportBlockWithResourceIdentity +} + +func (kind ImportStateKind) String() string { + return map[ImportStateKind]string{ + ImportCommandWithID: "ImportCommandWithID", + ImportBlockWithID: "ImportBlockWithID", + ImportBlockWithResourceIdentity: "ImportBlockWithResourceIdentity", + }[kind] +} + // TestStep is a single apply sequence of a test, done within the // context of a state. // @@ -470,12 +533,70 @@ type TestStep struct { // Config a string of the configuration to give to Terraform. If this // is set, then the TestCase will execute this step with the same logic - // as a `terraform apply`. + // as a `terraform apply`. If both Config and ConfigDirectory are set + // an error will be returned. // // JSON Configuration Syntax can be used and is assumed whenever Config // contains valid JSON. + // + // Only one of Config, ConfigDirectory or ConfigFile can be set + // otherwise an error will be returned. Config string + // ConfigDirectory is a function which returns a function that + // accepts config.TestStepProviderConfig and returns a string + // representing a directory that contains Terraform + // configuration files. + // + // There are helper functions in the [config] package that can be used, + // such as: + // + // - [config.StaticDirectory] + // - [config.TestNameDirectory] + // - [config.TestStepDirectory] + // + // When running Terraform operations for the test, Terraform will + // be executed with copies of the files of this directory as its + // working directory. Only one of Config, ConfigDirectory or + // ConfigFile can be set otherwise an error will be returned. + ConfigDirectory config.TestStepConfigFunc + + // ConfigFile is a function which returns a function that + // accepts config.TestStepProviderConfig and returns a string + // representing a file that contains Terraform configuration. + // + // There are helper functions in the [config] package that can be used, + // such as: + // + // - [config.StaticFile] + // - [config.TestNameFile] + // - [config.TestStepFile] + // + // When running Terraform operations for the test, Terraform will + // be executed with a copy of the file as its working directory. + // Only one of Config, ConfigDirectory or ConfigFile can be set + // otherwise an error will be returned. + ConfigFile config.TestStepConfigFunc + + // ImportStateConfigExact indicates that the test framework should use the exact + // content of the Config, ConfigFile, or ConfigDirectory inputs and should + // not modify it at test run time. + // + // The default is false. At test run time, the test framework will generate + // specific kinds of configuration, such as import blocks, and append them + // to the given Config, ConfigFile, or ConfigDirectory inputs. Using this + // default improves test readability and removes duplication of setup. + ImportStateConfigExact bool + + // ConfigVariables is a map defining variables for use in conjunction + // with Terraform configuration. If this map is populated then it + // will be used to assemble an *.auto.tfvars.json which will be + // written into the working directory. Any variables that are + // defined within the Terraform configuration that have a matching + // variable definition in *.auto.tfvars.json will have their value + // substituted when the acceptance test is executed. + ConfigVariables config.Variables + // Check is called after the Config is applied. Use this step to // make your own API calls to check the status of things, and to // inspect the format of the ResourceState itself. @@ -496,8 +617,35 @@ type TestStep struct { // ExpectError allows the construction of test cases that we expect to fail // with an error. The specified regexp must match against the error for the // test to pass. + // + // This functionality is only intended for provider-controlled error + // messaging. While in certain scenarios this can also catch testing logic + // error messages, those messages are not protected by compatibility + // promises. ExpectError *regexp.Regexp + // ConfigPlanChecks allows assertions to be made against the plan file at different points of a Config (apply) test using a plan check. + // Custom plan checks can be created by implementing the [PlanCheck] interface, or by using a PlanCheck implementation from the provided [plancheck] package + // + // [PlanCheck]: https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/plancheck#PlanCheck + // [plancheck]: https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/plancheck + ConfigPlanChecks ConfigPlanChecks + + // RefreshPlanChecks allows assertions to be made against the plan file at different points of a Refresh test using a plan check. + // Custom plan checks can be created by implementing the [PlanCheck] interface, or by using a PlanCheck implementation from the provided [plancheck] package + // + // [PlanCheck]: https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/plancheck#PlanCheck + // [plancheck]: https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/plancheck + RefreshPlanChecks RefreshPlanChecks + + // ConfigStateChecks allow assertions to be made against the state file during a Config (apply) test using a state check. + // Custom state checks can be created by implementing the [statecheck.StateCheck] interface, or by using a StateCheck implementation from the provided [statecheck] package. + ConfigStateChecks []statecheck.StateCheck + + // QueryResultChecks allow assertions to be made against a collection of found resources that were returned by a query using a query check. + // Custom query checks can be created by implementing the [querycheck.QueryResultCheck] interface, or by using a QueryResultCheck implementation from the provided [querycheck] package. + QueryResultChecks []querycheck.QueryResultCheck + // PlanOnly can be set to only run `plan` with this configuration, and not // actually apply it. This is useful for ensuring config changes result in // no-op plans @@ -523,6 +671,10 @@ type TestStep struct { // SkipFunc is called after PreConfig but before applying the Config. SkipFunc func() (bool, error) + // PostApplyFunc is called after the Config is applied and after all plan/apply checks are run. + // This can be used to perform assertions against API values that are not stored in Terraform state. + PostApplyFunc func() + //--------------------------------------------------------------- // ImportState testing //--------------------------------------------------------------- @@ -532,6 +684,13 @@ type TestStep struct { // ID of that resource. ImportState bool + // ImportStateKind controls the method of import that is used in combination with the other import-related fields on the TestStep struct. + // + // - By default, ImportCommandWithID is used, which tests import by using the ID string with the `terraform import` command. This was the original behavior prior to introducing the ImportStateKind field. + // - ImportBlockWithID tests import by using the ID string in an import configuration block with the `terraform plan` command. + // - ImportBlockWithResourceIdentity imports the state using an import configuration block with a resource identity. + ImportStateKind ImportStateKind + // ImportStateId is the ID to perform an ImportState operation with. // This is optional. If it isn't set, then the resource ID is automatically // determined by inspecting the state for ResourceName's ID. @@ -565,15 +724,36 @@ type TestStep struct { // Terraform version specific logic in provider testing. ImportStateCheck ImportStateCheckFunc + // ImportPlanChecks allows assertions to be made against the plan file at different points of a plannable import test using a plan check. + // Custom plan checks can be created by implementing the [PlanCheck] interface, or by using a PlanCheck implementation from the provided [plancheck] package + // + // [PlanCheck]: https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/plancheck#PlanCheck + // [plancheck]: https://pkg.go.dev/github.com/hashicorp/terraform-plugin-testing/plancheck + ImportPlanChecks ImportPlanChecks + // ImportStateVerify, if true, will also check that the state values // that are finally put into the state after import match for all the // IDs returned by the Import. Note that this checks for strict equality // and does not respect DiffSuppressFunc or CustomizeDiff. // + // By default, the prior resource state and import resource state are + // matched by the "id" attribute. If the "id" attribute is not implemented + // or another attribute more uniquely identifies the resource, set the + // ImportStateVerifyIdentifierAttribute field to adjust the attribute for + // matching. + // + // If certain attributes cannot be correctly imported, set the + // ImportStateVerifyIgnore field. + ImportStateVerify bool + + // ImportStateVerifyIdentifierAttribute is the resource attribute for + // matching the prior resource state and import resource state during import + // verification. By default, the "id" attribute is used. + ImportStateVerifyIdentifierAttribute string + // ImportStateVerifyIgnore is a list of prefixes of fields that should // not be verified to be equal. These can be set to ephemeral fields or // fields that can't be refreshed and don't matter. - ImportStateVerify bool ImportStateVerifyIgnore []string // ImportStatePersist, if true, will update the persisted state with the @@ -665,6 +845,38 @@ type TestStep struct { // for performing import testing where the prior TestStep configuration // contained a provider outside the one under test. ExternalProviders map[string]ExternalProvider + + // If true, the test step will run the query command + Query bool +} + +// ConfigPlanChecks defines the different points in a Config TestStep when plan checks can be run. +type ConfigPlanChecks struct { + // PreApply runs all plan checks in the slice. This occurs before the apply of a Config test is run. This slice cannot be populated + // with TestStep.PlanOnly, as there is no PreApply plan run with that flag set. All errors by plan checks in this slice are aggregated, reported, and will result in a test failure. + PreApply []plancheck.PlanCheck + + // PostApplyPreRefresh runs all plan checks in the slice. This occurs after the apply and before the refresh of a Config test is run. + // All errors by plan checks in this slice are aggregated, reported, and will result in a test failure. + PostApplyPreRefresh []plancheck.PlanCheck + + // PostApplyPostRefresh runs all plan checks in the slice. This occurs after the apply and refresh of a Config test are run. + // All errors by plan checks in this slice are aggregated, reported, and will result in a test failure. + PostApplyPostRefresh []plancheck.PlanCheck +} + +// ImportPlanChecks defines the different points in an Import TestStep when plan checks can be run. +type ImportPlanChecks struct { + // PreApply runs all plan checks in the slice. This occurs after the plan of an Import test is computed. This slice cannot be populated + // with TestStep.PlanOnly, as there is no PreApply plan run with that flag set. All errors by plan checks in this slice are aggregated, reported, and will result in a test failure. + PreApply []plancheck.PlanCheck +} + +// RefreshPlanChecks defines the different points in a Refresh TestStep when plan checks can be run. +type RefreshPlanChecks struct { + // PostRefresh runs all plan checks in the slice. This occurs after the refresh of the Refresh test is run. + // All errors by plan checks in this slice are aggregated, reported, and will result in a test failure. + PostRefresh []plancheck.PlanCheck } // ParallelTest performs an acceptance test on a resource, allowing concurrency @@ -687,11 +899,6 @@ func ParallelTest(t testing.T, c TestCase) { // set to some non-empty value. This is to avoid test cases surprising // a user by creating real resources. // -// Tests will fail unless the verbose flag (`go test -v`, or explicitly -// the "-test.v" flag) is set. Because some acceptance tests take quite -// long, we require the verbose flag so users are able to see progress -// output. -// // Use the ParallelTest() function to automatically set (*testing.T).Parallel() // to enable testing concurrency. Use the UnitTest() function to automatically // set the TestCase type IsUnitTest field. @@ -718,7 +925,7 @@ func Test(t testing.T, c TestCase) { ctx := context.Background() ctx = logging.InitTestContext(ctx, t) - err := c.validate(ctx) + err := c.validate(ctx, t) if err != nil { logging.HelperResourceError(ctx, @@ -775,6 +982,13 @@ func Test(t testing.T, c TestCase) { } }(helper) + // Run the TerraformVersionChecks if we have it. + // This is done after creating the helper because a working directory is required + // to retrieve the Terraform version. + if c.TerraformVersionChecks != nil { + runTFVersionChecks(ctx, t, helper.TerraformVersion(), c.TerraformVersionChecks) + } + runNewTest(ctx, t, c, helper) logging.HelperResourceDebug(ctx, "Finished TestCase") @@ -792,17 +1006,17 @@ func UnitTest(t testing.T, c TestCase) { Test(t, c) } -func testResource(c TestStep, state *terraform.State) (*terraform.ResourceState, error) { +func testResource(name string, state *terraform.State) (*terraform.ResourceState, error) { for _, m := range state.Modules { if len(m.Resources) > 0 { - if v, ok := m.Resources[c.ResourceName]; ok { + if v, ok := m.Resources[name]; ok { return v, nil } } } return nil, fmt.Errorf( - "Resource specified by ResourceName couldn't be found: %s", c.ResourceName) + "Resource specified by ResourceName couldn't be found: %s", name) } // ComposeTestCheckFunc lets you compose multiple TestCheckFuncs into @@ -812,12 +1026,12 @@ func testResource(c TestStep, state *terraform.State) (*terraform.ResourceState, // into smaller pieces more easily. // // ComposeTestCheckFunc returns immediately on the first TestCheckFunc error. -// To aggregate all errors, use ComposeAggregateTestCheckFunc instead. +// To aggregrate all errors, use ComposeAggregateTestCheckFunc instead. func ComposeTestCheckFunc(fs ...TestCheckFunc) TestCheckFunc { return func(s *terraform.State) error { for i, f := range fs { if err := f(s); err != nil { - return fmt.Errorf("Check %d/%d error: %s", i+1, len(fs), err) + return fmt.Errorf("Check %d/%d error: %w", i+1, len(fs), err) } } @@ -831,7 +1045,7 @@ func ComposeTestCheckFunc(fs ...TestCheckFunc) TestCheckFunc { // As a user testing their provider, this lets you decompose your checks // into smaller pieces more easily. // -// Unlike ComposeTestCheckFunc, ComposeAggregateTestCheckFunc runs _all_ of the +// Unlike ComposeTestCheckFunc, ComposeAggergateTestCheckFunc runs _all_ of the // TestCheckFuncs and aggregates failures. func ComposeAggregateTestCheckFunc(fs ...TestCheckFunc) TestCheckFunc { return func(s *terraform.State) error { @@ -889,6 +1103,44 @@ func ComposeAggregateTestCheckFunc(fs ...TestCheckFunc) TestCheckFunc { // attributes using the special key syntax, checking a list, map, or set // attribute directly is not supported. Use TestCheckResourceAttr with // the special .# or .% key syntax for those situations instead. +// +// An experimental interface exists to potentially replace the +// TestCheckResourceAttrSet functionality in the future and feedback +// would be appreciated. This example performs the same check as +// TestCheckResourceAttrSet with that experimental interface, by +// using [ExpectKnownValue] with [knownvalue.NotNull]: +// +// package example_test +// +// import ( +// "testing" +// +// "github.com/hashicorp/terraform-plugin-testing/helper/resource" +// "github.com/hashicorp/terraform-plugin-testing/knownvalue" +// "github.com/hashicorp/terraform-plugin-testing/statecheck" +// "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +// ) +// +// func TestExpectKnownValue_CheckState_AttributeFound(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" {}`, +// ConfigStateChecks: []statecheck.StateCheck{ +// statecheck.ExpectKnownValue( +// "test_resource.one", +// tfjsonpath.New("computed_attribute"), +// knownvalue.NotNull(), +// ), +// }, +// }, +// }, +// }) +// } func TestCheckResourceAttrSet(name, key string) TestCheckFunc { return checkIfIndexesIntoTypeSet(key, func(s *terraform.State) error { is, err := primaryInstanceState(s, name) @@ -902,6 +1154,14 @@ func TestCheckResourceAttrSet(name, key string) TestCheckFunc { // TestCheckModuleResourceAttrSet - as per TestCheckResourceAttrSet but with // support for non-root modules +// +// Deprecated: This functionality is deprecated without replacement. The +// terraform-plugin-testing Go module is intended for provider testing, which +// should always be possible within the root module of a configuration. This +// functionality is a carryover of when this code was used within Terraform +// core to test both providers and modules. Modern testing implementations to +// verify interactions between modules should be tested in Terraform core or +// using tooling outside this Go module. func TestCheckModuleResourceAttrSet(mp []string, name string, key string) TestCheckFunc { mpt := addrs.Module(mp).UnkeyedInstanceShim() return checkIfIndexesIntoTypeSet(key, func(s *terraform.State) error { @@ -980,6 +1240,44 @@ func testCheckResourceAttrSet(is *terraform.InstanceState, name string, key stri // - Boolean: "false" or "true". // - Float/Integer: Stringified number, such as "1.2" or "123". // - String: No conversion necessary. +// +// An experimental interface exists to potentially replace the +// TestCheckResourceAttr functionality in the future and feedback +// would be appreciated. This example performs the same check as +// TestCheckResourceAttr with that experimental interface, by +// using [statecheck.ExpectKnownValue]: +// +// package example_test +// +// import ( +// "testing" +// +// "github.com/hashicorp/terraform-plugin-testing/helper/resource" +// "github.com/hashicorp/terraform-plugin-testing/knownvalue" +// "github.com/hashicorp/terraform-plugin-testing/statecheck" +// "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +// ) +// +// func TestExpectKnownValue_CheckState_Bool(t *testing.T) { +// t.Parallel() +// +// resource.Test(t, resource.TestCase{ +// // Provider definition omitted. +// Steps: []resource.TestStep{ +// { +// // Example resource containing a computed boolean attribute named "computed_attribute" +// Config: `resource "test_resource" "one" {}`, +// ConfigStateChecks: []statecheck.StateCheck{ +// statecheck.ExpectKnownValue( +// "test_resource.one", +// tfjsonpath.New("computed_attribute"), +// knownvalue.Bool(true), +// ), +// }, +// }, +// }, +// }) +// } func TestCheckResourceAttr(name, key, value string) TestCheckFunc { return checkIfIndexesIntoTypeSet(key, func(s *terraform.State) error { is, err := primaryInstanceState(s, name) @@ -993,6 +1291,14 @@ func TestCheckResourceAttr(name, key, value string) TestCheckFunc { // TestCheckModuleResourceAttr - as per TestCheckResourceAttr but with // support for non-root modules +// +// Deprecated: This functionality is deprecated without replacement. The +// terraform-plugin-testing Go module is intended for provider testing, which +// should always be possible within the root module of a configuration. This +// functionality is a carryover of when this code was used within Terraform +// core to test both providers and modules. Modern testing implementations to +// verify interactions between modules should be tested in Terraform core or +// using tooling outside this Go module. func TestCheckModuleResourceAttr(mp []string, name string, key string, value string) TestCheckFunc { mpt := addrs.Module(mp).UnkeyedInstanceShim() return checkIfIndexesIntoTypeSet(key, func(s *terraform.State) error { @@ -1055,6 +1361,43 @@ func testCheckResourceAttr(is *terraform.InstanceState, name string, key string, // when using TestCheckResourceAttrWith and a value is found for the given name and key. // // When this function returns an error, TestCheckResourceAttrWith will fail the check. +// +// An experimental interface exists to potentially replace the +// CheckResourceAttrWithFunc functionality in the future and feedback +// would be appreciated. This example performs the same check as +// TestCheckResourceAttrWith with that experimental interface, by +// using [statecheck.ExpectKnownValue] in combination with +// [knownvalue.StringRegexp]: +// +// package example_test +// +// import ( +// "testing" +// +// "github.com/hashicorp/terraform-plugin-testing/helper/resource" +// "github.com/hashicorp/terraform-plugin-testing/statecheck" +// "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +// ) +// +// func TestExpectKnownValue_CheckState_String_Custom(t *testing.T) { +// t.Parallel() +// +// resource.Test(t, resource.TestCase{ +// // Provider definition omitted. +// Steps: []resource.TestStep{ +// { +// // Example resource containing a computed string attribute named "computed_attribute" +// Config: `resource "test_resource" "one" {}`, +// ConfigStateChecks: []statecheck.StateCheck{ +// statecheck.ExpectKnownValue( +// "test_resource.one", +// tfjsonpath.New("computed_attribute"), +// knownvalue.StringRegexp(regexp.MustCompile("str")), +// }, +// }, +// }, +// }) +// } type CheckResourceAttrWithFunc func(value string) error // TestCheckResourceAttrWith ensures a value stored in state for the @@ -1092,6 +1435,43 @@ type CheckResourceAttrWithFunc func(value string) error // and it's provided with the attribute value to apply a custom checking logic, // if it was found in the state. The function must return an error for the // check to fail, or `nil` to succeed. +// +// An experimental interface exists to potentially replace the +// TestCheckResourceAttrWith functionality in the future and feedback +// would be appreciated. This example performs the same check as +// TestCheckResourceAttrWith with that experimental interface, by +// using [statecheck.ExpectKnownValue] in combination with +// [knownvalue.StringRegexp]: +// +// package example_test +// +// import ( +// "testing" +// +// "github.com/hashicorp/terraform-plugin-testing/helper/resource" +// "github.com/hashicorp/terraform-plugin-testing/statecheck" +// "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +// ) +// +// func TestExpectKnownValue_CheckState_String_Custom(t *testing.T) { +// t.Parallel() +// +// resource.Test(t, resource.TestCase{ +// // Provider definition omitted. +// Steps: []resource.TestStep{ +// { +// // Example resource containing a computed string attribute named "computed_attribute" +// Config: `resource "test_resource" "one" {}`, +// ConfigStateChecks: []statecheck.StateCheck{ +// statecheck.ExpectKnownValue( +// "test_resource.one", +// tfjsonpath.New("computed_attribute"), +// knownvalue.StringRegexp(regexp.MustCompile("str")), +// }, +// }, +// }, +// }) +// } func TestCheckResourceAttrWith(name, key string, checkValueFunc CheckResourceAttrWithFunc) TestCheckFunc { return checkIfIndexesIntoTypeSet(key, func(s *terraform.State) error { is, err := primaryInstanceState(s, name) @@ -1144,6 +1524,43 @@ func TestCheckResourceAttrWith(name, key string, checkValueFunc CheckResourceAtt // attributes using the special key syntax, checking a list, map, or set // attribute directly is not supported. Use TestCheckResourceAttr with // the special .# or .% key syntax for those situations instead. +// +// An experimental interface exists to potentially replace the +// TestCheckNoResourceAttr functionality in the future and feedback +// would be appreciated. This example performs the same check as +// TestCheckNoResourceAttr with that experimental interface, by +// using [statecheck.ExpectKnownValue] with [knownvalue.Null]: +// +// package example_test +// +// import ( +// "testing" +// +// "github.com/hashicorp/terraform-plugin-testing/helper/resource" +// "github.com/hashicorp/terraform-plugin-testing/statecheck" +// "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +// ) +// +// func TestExpectKnownValue_CheckState_AttributeNull(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" that has a null value +// Config: `resource "test_resource" "one" {}`, +// ConfigStateChecks: []statecheck.StateCheck{ +// statecheck.ExpectKnownValue( +// "test_resource.one", +// tfjsonpath.New("computed_attribute"), +// knownvalue.Null(), +// ), +// }, +// }, +// }, +// }) +// } func TestCheckNoResourceAttr(name, key string) TestCheckFunc { return checkIfIndexesIntoTypeSet(key, func(s *terraform.State) error { is, err := primaryInstanceState(s, name) @@ -1157,6 +1574,14 @@ func TestCheckNoResourceAttr(name, key string) TestCheckFunc { // TestCheckModuleNoResourceAttr - as per TestCheckNoResourceAttr but with // support for non-root modules +// +// Deprecated: This functionality is deprecated without replacement. The +// terraform-plugin-testing Go module is intended for provider testing, which +// should always be possible within the root module of a configuration. This +// functionality is a carryover of when this code was used within Terraform +// core to test both providers and modules. Modern testing implementations to +// verify interactions between modules should be tested in Terraform core or +// using tooling outside this Go module. func TestCheckModuleNoResourceAttr(mp []string, name string, key string) TestCheckFunc { mpt := addrs.Module(mp).UnkeyedInstanceShim() return checkIfIndexesIntoTypeSet(key, func(s *terraform.State) error { @@ -1240,6 +1665,43 @@ func testCheckNoResourceAttr(is *terraform.InstanceState, name string, key strin // using the regexp.MustCompile() function, which will automatically ensure the // regular expression is supported by the Go regular expression handlers during // compilation. +// +// An experimental interface exists to potentially replace the +// TestMatchResourceAttr functionality in the future and feedback +// would be appreciated. This example performs the same check as +// TestMatchResourceAttr with that experimental interface, by +// using [statecheck.ExpectKnownValue] in combination with +// [knownvalue.StringRegexp]: +// +// package example_test +// +// import ( +// "testing" +// +// "github.com/hashicorp/terraform-plugin-testing/helper/resource" +// "github.com/hashicorp/terraform-plugin-testing/statecheck" +// "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +// ) +// +// func TestExpectKnownValue_CheckState_String_Custom(t *testing.T) { +// t.Parallel() +// +// resource.Test(t, resource.TestCase{ +// // Provider definition omitted. +// Steps: []resource.TestStep{ +// { +// // Example resource containing a computed string attribute named "computed_attribute" +// Config: `resource "test_resource" "one" {}`, +// ConfigStateChecks: []statecheck.StateCheck{ +// statecheck.ExpectKnownValue( +// "test_resource.one", +// tfjsonpath.New("computed_attribute"), +// knownvalue.StringRegexp(regexp.MustCompile("str")), +// }, +// }, +// }, +// }) +// } func TestMatchResourceAttr(name, key string, r *regexp.Regexp) TestCheckFunc { return checkIfIndexesIntoTypeSet(key, func(s *terraform.State) error { is, err := primaryInstanceState(s, name) @@ -1253,6 +1715,14 @@ func TestMatchResourceAttr(name, key string, r *regexp.Regexp) TestCheckFunc { // TestModuleMatchResourceAttr - as per TestMatchResourceAttr but with // support for non-root modules +// +// Deprecated: This functionality is deprecated without replacement. The +// terraform-plugin-testing Go module is intended for provider testing, which +// should always be possible within the root module of a configuration. This +// functionality is a carryover of when this code was used within Terraform +// core to test both providers and modules. Modern testing implementations to +// verify interactions between modules should be tested in Terraform core or +// using tooling outside this Go module. func TestModuleMatchResourceAttr(mp []string, name string, key string, r *regexp.Regexp) TestCheckFunc { mpt := addrs.Module(mp).UnkeyedInstanceShim() return checkIfIndexesIntoTypeSet(key, func(s *terraform.State) error { @@ -1292,6 +1762,14 @@ func TestCheckResourceAttrPtr(name string, key string, value *string) TestCheckF // TestCheckModuleResourceAttrPtr - as per TestCheckResourceAttrPtr but with // support for non-root modules +// +// Deprecated: This functionality is deprecated without replacement. The +// terraform-plugin-testing Go module is intended for provider testing, which +// should always be possible within the root module of a configuration. This +// functionality is a carryover of when this code was used within Terraform +// core to test both providers and modules. Modern testing implementations to +// verify interactions between modules should be tested in Terraform core or +// using tooling outside this Go module. func TestCheckModuleResourceAttrPtr(mp []string, name string, key string, value *string) TestCheckFunc { return func(s *terraform.State) error { return TestCheckModuleResourceAttr(mp, name, key, *value)(s) @@ -1349,6 +1827,14 @@ func TestCheckResourceAttrPair(nameFirst, keyFirst, nameSecond, keySecond string // TestCheckModuleResourceAttrPair - as per TestCheckResourceAttrPair but with // support for non-root modules +// +// Deprecated: This functionality is deprecated without replacement. The +// terraform-plugin-testing Go module is intended for provider testing, which +// should always be possible within the root module of a configuration. This +// functionality is a carryover of when this code was used within Terraform +// core to test both providers and modules. Modern testing implementations to +// verify interactions between modules should be tested in Terraform core or +// using tooling outside this Go module. func TestCheckModuleResourceAttrPair(mpFirst []string, nameFirst string, keyFirst string, mpSecond []string, nameSecond string, keySecond string) TestCheckFunc { mptFirst := addrs.Module(mpFirst).UnkeyedInstanceShim() mptSecond := addrs.Module(mpSecond).UnkeyedInstanceShim() @@ -1417,6 +1903,100 @@ func testCheckResourceAttrPair(isFirst *terraform.InstanceState, nameFirst strin } // TestCheckOutput checks an output in the Terraform configuration +// +// An experimental interface exists to potentially replace the +// TestCheckOutput functionality in the future and feedback +// would be appreciated. This example performs the same check as +// TestCheckOutput with that experimental interface, by +// using [statecheck.ExpectKnownOutputValue]: +// +// package example_test +// +// import ( +// "testing" +// +// "github.com/hashicorp/terraform-plugin-testing/helper/resource" +// "github.com/hashicorp/terraform-plugin-testing/knownvalue" +// "github.com/hashicorp/terraform-plugin-testing/statecheck" +// "github.com/hashicorp/terraform-plugin-testing/tfversion" +// ) +// +// func TestExpectKnownOutputValue_CheckState_Bool(t *testing.T) { +// t.Parallel() +// +// resource.Test(t, resource.TestCase{ +// TerraformVersionChecks: []tfversion.TerraformVersionCheck{ +// tfversion.SkipBelow(tfversion.Version1_8_0), +// }, +// // Provider definition omitted. +// Steps: []resource.TestStep{ +// { +// // Example provider containing a provider-defined function named "bool" +// Config: `output "test" { +// value = provider::example::bool(true) +// }`, +// ConfigStateChecks: []statecheck.StateCheck{ +// statecheck.ExpectKnownOutputValue("test", knownvalue.Bool(true)), +// }, +// }, +// }, +// }) +// } +// +// An experimental interface exists to potentially replace the +// TestCheckOutput functionality in the future and feedback +// would be appreciated. This example performs the same check as +// TestCheckOutput with that experimental interface, by using +// [statecheck.ExpectKnownOutputValueAtPath]: +// +// package example_test +// +// import ( +// "testing" +// +// "github.com/hashicorp/terraform-plugin-testing/helper/resource" +// "github.com/hashicorp/terraform-plugin-testing/knownvalue" +// "github.com/hashicorp/terraform-plugin-testing/statecheck" +// "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +// ) +// +// func TestExpectKnownOutputValueAtPath_CheckState_Bool(t *testing.T) { +// t.Parallel() +// +// resource.Test(t, resource.TestCase{ +// // Provider definition omitted. +// Steps: []resource.TestStep{ +// { +// // Example resource containing a computed boolean attribute named "computed_attribute" +// Config: `resource "test_resource" "one" {} +// +// // Generally, it is not necessary to use an output to test a resource attribute, +// // the resource attribute should be tested directly instead, by inspecting the +// // value of the resource attribute. For instance: +// // +// // ConfigStateChecks: []statecheck.StateCheck{ +// // statecheck.ExpectKnownValue( +// // "test_resource.one", +// // tfjsonpath.New("computed_attribute"), +// // knownvalue.Bool(true), +// // ), +// // }, +// // +// // This is only shown as an example. +// output test_resource_one_output { +// value = test_resource.one +// }`, +// ConfigStateChecks: []statecheck.StateCheck{ +// statecheck.ExpectKnownOutputValueAtPath( +// "test_resource_one_output", +// tfjsonpath.New("computed_attribute"), +// knownvalue.Bool(true), +// ), +// }, +// }, +// }, +// }) +// } func TestCheckOutput(name, value string) TestCheckFunc { return func(s *terraform.State) error { ms := s.RootModule() @@ -1437,6 +2017,64 @@ func TestCheckOutput(name, value string) TestCheckFunc { } } +// TestMatchOutput ensures a value matching a regular expression is +// stored in state for the given name. State value checking is only +// recommended for testing Computed attributes and attribute defaults. +// +// An experimental interface exists to potentially replace the +// TestMatchOutput functionality in the future and feedback +// would be appreciated. This example performs the same check as +// TestMatchOutput with that experimental interface, by using +// [statecheck.ExpectKnownOutputValueAtPath] in combination with +// [knownvalue.StringRegexp]: +// +// package example_test +// +// import ( +// "testing" +// +// "github.com/hashicorp/terraform-plugin-testing/helper/resource" +// "github.com/hashicorp/terraform-plugin-testing/statecheck" +// "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +// ) +// +// func TestExpectKnownOutputValueAtPath_CheckState_String_Custom(t *testing.T) { +// t.Parallel() +// +// resource.Test(t, resource.TestCase{ +// // Provider definition omitted. +// Steps: []resource.TestStep{ +// { +// // Example resource containing a computed string attribute named "computed_attribute" +// Config: `resource "test_resource" "one" {} +// +// // Generally, it is not necessary to use an output to test a resource attribute, +// // the resource attribute should be tested directly instead, by inspecting the +// // value of the resource attribute. For instance: +// // +// // ConfigStateChecks: []statecheck.StateCheck{ +// // statecheck.ExpectKnownValue( +// // "test_resource.one", +// // tfjsonpath.New("computed_attribute"), +// // knownvalue.StringRegexp(regexp.MustCompile("str")), +// // ), +// // }, +// // +// // This is only shown as an example. +// output test_resource_one_output { +// value = test_resource.one +// }`, +// ConfigStateChecks: []statecheck.StateCheck{ +// statecheck.ExpectKnownOutputValueAtPath( +// "test_resource_one_output", +// tfjsonpath.New("computed_attribute"), +// knownvalue.StringRegexp(regexp.MustCompile("str"), +// ), +// }, +// }, +// }, +// }) +// } func TestMatchOutput(name string, r *regexp.Regexp) TestCheckFunc { return func(s *terraform.State) error { ms := s.RootModule() @@ -1445,7 +2083,12 @@ func TestMatchOutput(name string, r *regexp.Regexp) TestCheckFunc { return fmt.Errorf("Not found: %s", name) } - if !r.MatchString(rs.Value.(string)) { + valStr, ok := rs.Value.(string) + if !ok { + return fmt.Errorf("unexpected type %T for resource value", rs.Value) + } + + if !r.MatchString(valStr) { return fmt.Errorf( "Output '%s': %#v didn't match %q", name, @@ -1476,7 +2119,7 @@ func modulePrimaryInstanceState(ms *terraform.ModuleState, name string) (*terraf // modulePathPrimaryInstanceState returns the primary instance state for the // given resource name in a given module path. func modulePathPrimaryInstanceState(s *terraform.State, mp addrs.ModuleInstance, name string) (*terraform.InstanceState, error) { - ms := s.ModuleByPath(mp) + ms := s.ModuleByPath(mp) //nolint:staticcheck // legacy usage if ms == nil { return nil, fmt.Errorf("No module found at: %s", mp) } @@ -1487,7 +2130,7 @@ func modulePathPrimaryInstanceState(s *terraform.State, mp addrs.ModuleInstance, // primaryInstanceState returns the primary instance state for the given // resource name in the root module. func primaryInstanceState(s *terraform.State, name string) (*terraform.InstanceState, error) { - ms := s.RootModule() + ms := s.RootModule() //nolint:staticcheck // legacy usage return modulePrimaryInstanceState(ms, name) } @@ -1506,7 +2149,7 @@ func indexesIntoTypeSet(key string) bool { func checkIfIndexesIntoTypeSet(key string, f TestCheckFunc) TestCheckFunc { return func(s *terraform.State) error { err := f(s) - if err != nil && s.IsBinaryDrivenTest && indexesIntoTypeSet(key) { + if err != nil && indexesIntoTypeSet(key) { return fmt.Errorf("Error in test check: %s\nTest check address %q likely indexes into TypeSet\nThis is currently not possible in the SDK", err, key) } return err @@ -1516,7 +2159,7 @@ func checkIfIndexesIntoTypeSet(key string, f TestCheckFunc) TestCheckFunc { func checkIfIndexesIntoTypeSetPair(keyFirst, keySecond string, f TestCheckFunc) TestCheckFunc { return func(s *terraform.State) error { err := f(s) - if err != nil && s.IsBinaryDrivenTest && (indexesIntoTypeSet(keyFirst) || indexesIntoTypeSet(keySecond)) { + if err != nil && (indexesIntoTypeSet(keyFirst) || indexesIntoTypeSet(keySecond)) { return fmt.Errorf("Error in test check: %s\nTest check address %q or %q likely indexes into TypeSet\nThis is currently not possible in the SDK", err, keyFirst, keySecond) } return err diff --git a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource/testing_config.go b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/testing_config.go similarity index 78% rename from vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource/testing_config.go rename to vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/testing_config.go index f56c885be3..57bc0d8ce3 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource/testing_config.go +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/testing_config.go @@ -7,8 +7,9 @@ import ( "context" "fmt" - "github.com/hashicorp/terraform-plugin-sdk/v2/internal/logging" - "github.com/hashicorp/terraform-plugin-sdk/v2/internal/plugintest" + "github.com/hashicorp/terraform-plugin-testing/internal/logging" + + "github.com/hashicorp/terraform-plugin-testing/internal/plugintest" ) func testStepTaint(ctx context.Context, step TestStep, wd *plugintest.WorkingDir) error { diff --git a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource/testing_new.go b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/testing_new.go similarity index 53% rename from vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource/testing_new.go rename to vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/testing_new.go index 14b247306a..0faf05892f 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource/testing_new.go +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/testing_new.go @@ -6,24 +6,30 @@ package resource import ( "context" "fmt" + "os" + "path/filepath" "reflect" + "strconv" "strings" "github.com/google/go-cmp/cmp" + "github.com/hashicorp/go-version" tfjson "github.com/hashicorp/terraform-json" "github.com/mitchellh/go-testing-interface" - "github.com/hashicorp/terraform-plugin-sdk/v2/internal/logging" - "github.com/hashicorp/terraform-plugin-sdk/v2/internal/plugintest" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/internal/logging" + "github.com/hashicorp/terraform-plugin-testing/internal/plugintest" + "github.com/hashicorp/terraform-plugin-testing/internal/teststep" + "github.com/hashicorp/terraform-plugin-testing/terraform" ) func runPostTestDestroy(ctx context.Context, t testing.T, c TestCase, wd *plugintest.WorkingDir, providers *providerFactories, statePreDestroy *terraform.State) error { t.Helper() - err := runProviderCommand(ctx, t, func() error { + err := runProviderCommand(ctx, t, wd, providers, func() error { return wd.Destroy(ctx) - }, wd, providers) + }) if err != nil { return err } @@ -45,7 +51,7 @@ func runPostTestDestroy(ctx context.Context, t testing.T, c TestCase, wd *plugin func runNewTest(ctx context.Context, t testing.T, c TestCase, helper *plugintest.Helper) { t.Helper() - wd := helper.RequireNewWorkingDir(ctx, t) + wd := helper.RequireNewWorkingDir(ctx, t, c.WorkingDir) ctx = logging.TestTerraformPathContext(ctx, wd.GetHelper().TerraformExecPath()) ctx = logging.TestWorkingDirectoryContext(ctx, wd.GetHelper().WorkingDirectory()) @@ -57,15 +63,17 @@ func runNewTest(ctx context.Context, t testing.T, c TestCase, helper *plugintest } defer func() { + t.Helper() + var statePreDestroy *terraform.State var err error - err = runProviderCommand(ctx, t, func() error { - statePreDestroy, err = getState(ctx, t, wd) + err = runProviderCommand(ctx, t, wd, providers, func() error { + _, statePreDestroy, err = getState(ctx, t, wd) if err != nil { return err } return nil - }, wd, providers) + }) if err != nil { logging.HelperResourceError(ctx, "Error retrieving state, there may be dangling resources", @@ -89,8 +97,16 @@ func runNewTest(ctx context.Context, t testing.T, c TestCase, helper *plugintest wd.Close() }() + // Return value from c.ProviderConfig() is assigned to Raw as this was previously being + // passed to wd.SetConfig() when the second argument accept a configuration string. if c.hasProviders(ctx) { - err := wd.SetConfig(ctx, c.providerConfig(ctx, false)) + config := teststep.Configuration( + teststep.ConfigurationRequest{ + Raw: teststep.Pointer(c.providerConfig(ctx, false)), + }, + ) + + err := wd.SetConfig(ctx, config, nil) if err != nil { logging.HelperResourceError(ctx, @@ -100,9 +116,9 @@ func runNewTest(ctx context.Context, t testing.T, c TestCase, helper *plugintest t.Fatalf("TestCase error setting provider configuration: %s", err) } - err = runProviderCommand(ctx, t, func() error { + err = runProviderCommand(ctx, t, wd, providers, func() error { return wd.Init(ctx) - }, wd, providers) + }) if err != nil { logging.HelperResourceError(ctx, @@ -113,14 +129,30 @@ func runNewTest(ctx context.Context, t testing.T, c TestCase, helper *plugintest } } - logging.HelperResourceDebug(ctx, "Starting TestSteps") - // use this to track last step successfully applied // acts as default for import tests - var appliedCfg string + var appliedCfg teststep.Config + var stepNumber int for stepIndex, step := range c.Steps { - stepNumber := stepIndex + 1 // 1-based indexing for humans + if stepNumber > 0 { + copyWorkingDir(ctx, t, stepNumber, wd) + } + + stepNumber = stepIndex + 1 // 1-based indexing for humans + + configRequest := teststep.PrepareConfigurationRequest{ + Directory: step.ConfigDirectory, + File: step.ConfigFile, + Raw: step.Config, + TestStepConfigRequest: config.TestStepConfigRequest{ + StepNumber: stepNumber, + TestName: t.Name(), + }, + }.Exec() + + cfg := teststep.Configuration(configRequest) + ctx = logging.TestStepNumberContext(ctx, stepNumber) logging.HelperResourceDebug(ctx, "Starting TestStep") @@ -152,7 +184,7 @@ func runNewTest(ctx context.Context, t testing.T, c TestCase, helper *plugintest } } - if step.Config != "" && !step.Destroy && len(step.Taint) > 0 { + if cfg != nil && !step.Destroy && len(step.Taint) > 0 { err := testStepTaint(ctx, step, wd) if err != nil { @@ -164,16 +196,67 @@ func runNewTest(ctx context.Context, t testing.T, c TestCase, helper *plugintest } } - if step.hasProviders(ctx) { + hasProviders, err := step.hasProviders(ctx, stepIndex, t.Name()) + + if err != nil { + logging.HelperResourceError(ctx, + "TestStep error checking for providers", + map[string]interface{}{logging.KeyError: err}, + ) + t.Fatalf("TestStep %d/%d error checking for providers: %s", stepNumber, len(c.Steps), err) + } + + if hasProviders { providers = &providerFactories{ legacy: sdkProviderFactories(c.ProviderFactories).merge(step.ProviderFactories), protov5: protov5ProviderFactories(c.ProtoV5ProviderFactories).merge(step.ProtoV5ProviderFactories), protov6: protov6ProviderFactories(c.ProtoV6ProviderFactories).merge(step.ProtoV6ProviderFactories), } - providerCfg := step.providerConfig(ctx, step.configHasProviderBlock(ctx)) + var hasProviderBlock bool - err := wd.SetConfig(ctx, providerCfg) + if cfg != nil { + hasProviderBlock, err = cfg.HasProviderBlock(ctx) + + if err != nil { + logging.HelperResourceError(ctx, + "TestStep error determining whether configuration contains provider block", + map[string]interface{}{logging.KeyError: err}, + ) + t.Fatalf("TestStep %d/%d error determining whether configuration contains provider block: %s", stepNumber, len(c.Steps), err) + } + } + + var testStepConfig teststep.Config + + rawCfg, err := step.providerConfig(ctx, hasProviderBlock, helper.TerraformVersion()) + + if err != nil { + logging.HelperResourceError(ctx, + "TestStep error generating provider configuration", + map[string]interface{}{logging.KeyError: err}, + ) + t.Fatalf("TestStep %d/%d error generating provider configuration: %s", stepNumber, len(c.Steps), err) + } + + // Return value from step.providerConfig() is assigned to Raw as this was previously being + // passed to wd.SetConfig() directly when the second argument to wd.SetConfig() accepted a + // configuration string. + confRequest := teststep.PrepareConfigurationRequest{ + Directory: step.ConfigDirectory, + File: step.ConfigFile, + Raw: rawCfg, + TestStepConfigRequest: config.TestStepConfigRequest{ + StepNumber: stepNumber, + TestName: t.Name(), + }, + }.Exec() + + testStepConfig = teststep.Configuration(confRequest) + + if !step.Query { + err = wd.SetConfig(ctx, testStepConfig, step.ConfigVariables) + } if err != nil { logging.HelperResourceError(ctx, @@ -183,15 +266,9 @@ func runNewTest(ctx context.Context, t testing.T, c TestCase, helper *plugintest t.Fatalf("TestStep %d/%d error setting test provider configuration: %s", stepNumber, len(c.Steps), err) } - err = runProviderCommand( - ctx, - t, - func() error { - return wd.Init(ctx) - }, - wd, - providers, - ) + err = runProviderCommand(ctx, t, wd, providers, func() error { + return wd.Init(ctx) + }) if err != nil { logging.HelperResourceError(ctx, @@ -206,7 +283,7 @@ func runNewTest(ctx context.Context, t testing.T, c TestCase, helper *plugintest if step.ImportState { logging.HelperResourceTrace(ctx, "TestStep is ImportState mode") - err := testStepNewImportState(ctx, t, helper, wd, step, appliedCfg, providers) + err := testStepNewImportState(ctx, t, helper, wd, step, appliedCfg, providers, stepNumber) if step.ExpectError != nil { logging.HelperResourceDebug(ctx, "Checking TestStep ExpectError") if err == nil { @@ -281,10 +358,46 @@ func runNewTest(ctx context.Context, t testing.T, c TestCase, helper *plugintest continue } - if step.Config != "" { + if step.Query { + logging.HelperResourceTrace(ctx, "TestStep is Query mode") + + err := testStepNewQuery(ctx, t, wd, step, providers) + + if step.ExpectError != nil { + logging.HelperResourceDebug(ctx, "Checking TestStep ExpectError") + if err == nil { + logging.HelperResourceError(ctx, "Error running query: expected an error but got none") + t.Fatalf("Step %d/%d error running query: expected an error but got none", stepNumber, len(c.Steps)) + } + if !step.ExpectError.MatchString(err.Error()) { + logging.HelperResourceError(ctx, fmt.Sprintf("Error running query: expected an error with pattern (%s)", step.ExpectError.String()), + map[string]interface{}{logging.KeyError: err}, + ) + t.Fatalf("Step %d/%d error running query, expected an error with pattern (%s), no match on: %s", stepNumber, len(c.Steps), step.ExpectError.String(), err) + } + } else { + if err != nil && c.ErrorCheck != nil { + logging.HelperResourceDebug(ctx, "Calling TestCase ErrorCheck") + err = c.ErrorCheck(err) + logging.HelperResourceDebug(ctx, "Called TestCase ErrorCheck") + } + if err != nil { + logging.HelperResourceError(ctx, "Error running query", + map[string]interface{}{logging.KeyError: err}, + ) + t.Fatalf("Step %d/%d error running query checks: %s", stepNumber, len(c.Steps), err) + } + } + + logging.HelperResourceDebug(ctx, "Finished TestStep") + + continue + } + + if cfg != nil { logging.HelperResourceTrace(ctx, "TestStep is Config mode") - err := testStepNewConfig(ctx, t, c, wd, step, providers) + err := testStepNewConfig(ctx, t, c, wd, step, providers, stepIndex, helper) if step.ExpectError != nil { logging.HelperResourceDebug(ctx, "Checking TestStep ExpectError") @@ -318,7 +431,53 @@ func runNewTest(ctx context.Context, t testing.T, c TestCase, helper *plugintest } } - appliedCfg = step.mergedConfig(ctx, c) + var hasTerraformBlock bool + var hasProviderBlock bool + + if cfg != nil { + hasTerraformBlock, err = cfg.HasTerraformBlock(ctx) + + if err != nil { + logging.HelperResourceError(ctx, + "Error determining whether configuration contains terraform block", + map[string]interface{}{logging.KeyError: err}, + ) + t.Fatalf("Error determining whether configuration contains terraform block: %s", err) + } + + hasProviderBlock, err = cfg.HasProviderBlock(ctx) + + if err != nil { + logging.HelperResourceError(ctx, + "Error determining whether configuration contains provider block", + map[string]interface{}{logging.KeyError: err}, + ) + t.Fatalf("Error determining whether configuration contains provider block: %s", err) + } + } + + mergedConfig, err := step.mergedConfig(ctx, c, hasTerraformBlock, hasProviderBlock, helper.TerraformVersion()) + + if err != nil { + logging.HelperResourceError(ctx, + "Error generating merged configuration", + map[string]interface{}{logging.KeyError: err}, + ) + t.Fatalf("Error generating merged configuration: %s", err) + } + + // Preserve the step config for future test steps to use (import state) + confRequest := teststep.PrepareConfigurationRequest{ + Directory: step.ConfigDirectory, + File: step.ConfigFile, + Raw: mergedConfig, + TestStepConfigRequest: config.TestStepConfigRequest{ + StepNumber: stepNumber, + TestName: t.Name(), + }, + }.Exec() + + appliedCfg = teststep.Configuration(confRequest) logging.HelperResourceDebug(ctx, "Finished TestStep") @@ -327,27 +486,31 @@ func runNewTest(ctx context.Context, t testing.T, c TestCase, helper *plugintest t.Fatalf("Step %d/%d, unsupported test mode", stepNumber, len(c.Steps)) } + + if stepNumber > 0 { + copyWorkingDir(ctx, t, stepNumber, wd) + } } -func getState(ctx context.Context, t testing.T, wd *plugintest.WorkingDir) (*terraform.State, error) { +func getState(ctx context.Context, t testing.T, wd *plugintest.WorkingDir) (*tfjson.State, *terraform.State, error) { t.Helper() jsonState, err := wd.State(ctx) if err != nil { - return nil, err + return nil, nil, err } state, err := shimStateFromJson(jsonState) if err != nil { t.Fatal(err) } - return state, nil + return jsonState, state, nil } func stateIsEmpty(state *terraform.State) bool { - return state.Empty() || !state.HasResources() + return state.Empty() || !state.HasResources() //nolint:staticcheck // legacy usage } -func planIsEmpty(plan *tfjson.Plan) bool { +func planIsEmpty(plan *tfjson.Plan, tfVersion *version.Version) bool { for _, rc := range plan.ResourceChanges { for _, a := range rc.Change.Actions { if a != tfjson.ActionNoop { @@ -355,43 +518,112 @@ func planIsEmpty(plan *tfjson.Plan) bool { } } } + + if tfVersion.LessThan(expectNonEmptyPlanOutputChangesMinTFVersion) { + return true + } + + for _, change := range plan.OutputChanges { + if !change.Actions.NoOp() { + return false + } + } + return true } -func testIDRefresh(ctx context.Context, t testing.T, c TestCase, wd *plugintest.WorkingDir, step TestStep, r *terraform.ResourceState, providers *providerFactories) error { +func testIDRefresh(ctx context.Context, t testing.T, c TestCase, wd *plugintest.WorkingDir, step TestStep, r *terraform.ResourceState, providers *providerFactories, stepIndex int, helper *plugintest.Helper) error { t.Helper() // Build the state. The state is just the resource with an ID. There // are no attributes. We only set what is needed to perform a refresh. - state := terraform.NewState() + state := terraform.NewState() //nolint:staticcheck // legacy usage state.RootModule().Resources = make(map[string]*terraform.ResourceState) state.RootModule().Resources[c.IDRefreshName] = &terraform.ResourceState{} + configRequest := teststep.PrepareConfigurationRequest{ + Directory: step.ConfigDirectory, + File: step.ConfigFile, + Raw: step.Config, + TestStepConfigRequest: config.TestStepConfigRequest{ + StepNumber: stepIndex + 1, + TestName: t.Name(), + }, + }.Exec() + + cfg := teststep.Configuration(configRequest) + + var hasProviderBlock bool + + if cfg != nil { + var err error + + hasProviderBlock, err = cfg.HasProviderBlock(ctx) + + if err != nil { + logging.HelperResourceError(ctx, + "Error determining whether configuration contains provider block for import test config", + map[string]interface{}{logging.KeyError: err}, + ) + t.Fatalf("Error determining whether configuration contains provider block for import test config: %s", err) + } + } + + // Return value from c.ProviderConfig() is assigned to Raw as this was previously being + // passed to wd.SetConfig() when the second argument accept a configuration string. + testStepConfig := teststep.Configuration( + teststep.ConfigurationRequest{ + Raw: teststep.Pointer(c.providerConfig(ctx, hasProviderBlock)), + }, + ) + // Temporarily set the config to a minimal provider config for the refresh // test. After the refresh we can reset it. - err := wd.SetConfig(ctx, c.providerConfig(ctx, step.configHasProviderBlock(ctx))) + err := wd.SetConfig(ctx, testStepConfig, step.ConfigVariables) if err != nil { t.Fatalf("Error setting import test config: %s", err) } + + rawCfg, err := step.providerConfig(ctx, hasProviderBlock, helper.TerraformVersion()) + + if err != nil { + t.Fatalf("Error generating import provider config: %s", err) + } + defer func() { - err = wd.SetConfig(ctx, step.Config) + t.Helper() + + confRequest := teststep.PrepareConfigurationRequest{ + Directory: step.ConfigDirectory, + File: step.ConfigFile, + Raw: rawCfg, + TestStepConfigRequest: config.TestStepConfigRequest{ + StepNumber: stepIndex + 1, + TestName: t.Name(), + }, + }.Exec() + + testStepConfigDefer := teststep.Configuration(confRequest) + + err = wd.SetConfig(ctx, testStepConfigDefer, step.ConfigVariables) + if err != nil { t.Fatalf("Error resetting test config: %s", err) } }() // Refresh! - err = runProviderCommand(ctx, t, func() error { + err = runProviderCommand(ctx, t, wd, providers, func() error { err = wd.Refresh(ctx) if err != nil { t.Fatalf("Error running terraform refresh: %s", err) } - state, err = getState(ctx, t, wd) + _, state, err = getState(ctx, t, wd) if err != nil { return err } return nil - }, wd, providers) + }) if err != nil { return err } @@ -441,3 +673,27 @@ func testIDRefresh(ctx context.Context, t testing.T, c TestCase, wd *plugintest. return nil } + +func copyWorkingDir(ctx context.Context, t testing.T, stepNumber int, wd *plugintest.WorkingDir) { + if os.Getenv(plugintest.EnvTfAccPersistWorkingDir) == "" { + return + } + + workingDir := wd.GetHelper().WorkingDirectory() + + dest := filepath.Join(workingDir, fmt.Sprintf("%s%s", "step_", strconv.Itoa(stepNumber))) + + baseDir := wd.BaseDir() + rootBaseDir := strings.TrimPrefix(baseDir, workingDir) + + err := plugintest.CopyDir(workingDir, dest, rootBaseDir) + if err != nil { + logging.HelperResourceError(ctx, + "Unexpected error copying working directory files", + map[string]interface{}{logging.KeyError: err}, + ) + t.Fatalf("TestStep %d/%d error copying working directory files: %s", stepNumber, err) + } + + t.Logf("Working directory and files have been copied to: %s", dest) +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/testing_new_config.go b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/testing_new_config.go new file mode 100644 index 0000000000..1145c8e4e7 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/testing_new_config.go @@ -0,0 +1,475 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package resource + +import ( + "context" + "errors" + "fmt" + "os" + + "github.com/hashicorp/terraform-exec/tfexec" + tfjson "github.com/hashicorp/terraform-json" + "github.com/mitchellh/go-testing-interface" + + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/internal/teststep" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/hashicorp/terraform-plugin-testing/tfversion" + + "github.com/hashicorp/terraform-plugin-testing/internal/logging" + "github.com/hashicorp/terraform-plugin-testing/internal/plugintest" +) + +// expectNonEmptyPlanOutputChangesMinTFVersion is used to keep compatibility for +// Terraform 0.12 and 0.13 after enabling ExpectNonEmptyPlan to check output +// changes. Those older versions will always show outputs being created. +var expectNonEmptyPlanOutputChangesMinTFVersion = tfversion.Version0_14_0 + +func testStepNewConfig(ctx context.Context, t testing.T, c TestCase, wd *plugintest.WorkingDir, step TestStep, providers *providerFactories, stepIndex int, helper *plugintest.Helper) error { + t.Helper() + + // When `refreshAfterApply` is true, a `Config`-mode test step will invoke + // a refresh before successful completion. This is a compatibility measure + // for test cases that have different -- but semantically-equal -- state + // representations in their test steps. When comparing two states, the + // testing framework is not aware of semantic equality or set equality. + _, refreshAfterApply := os.LookupEnv(EnvTfAccRefreshAfterApply) + + configRequest := teststep.PrepareConfigurationRequest{ + Directory: step.ConfigDirectory, + File: step.ConfigFile, + Raw: step.Config, + TestStepConfigRequest: config.TestStepConfigRequest{ + StepNumber: stepIndex + 1, + TestName: t.Name(), + }, + }.Exec() + + cfg := teststep.Configuration(configRequest) + + var hasTerraformBlock bool + var hasProviderBlock bool + + if cfg != nil { + var err error + + hasTerraformBlock, err = cfg.HasTerraformBlock(ctx) + + if err != nil { + logging.HelperResourceError(ctx, + "Error determining whether configuration contains terraform block", + map[string]interface{}{logging.KeyError: err}, + ) + t.Fatalf("Error determining whether configuration contains terraform block: %s", err) + } + + hasProviderBlock, err = cfg.HasProviderBlock(ctx) + + if err != nil { + logging.HelperResourceError(ctx, + "Error determining whether configuration contains provider block", + map[string]interface{}{logging.KeyError: err}, + ) + t.Fatalf("Error determining whether configuration contains provider block: %s", err) + } + } + + mergedConfig, err := step.mergedConfig(ctx, c, hasTerraformBlock, hasProviderBlock, helper.TerraformVersion()) + + if err != nil { + logging.HelperResourceError(ctx, + "Error generating merged configuration", + map[string]interface{}{logging.KeyError: err}, + ) + t.Fatalf("Error generating merged configuration: %s", err) + } + + confRequest := teststep.PrepareConfigurationRequest{ + Directory: step.ConfigDirectory, + File: step.ConfigFile, + Raw: mergedConfig, + TestStepConfigRequest: config.TestStepConfigRequest{ + StepNumber: stepIndex + 1, + TestName: t.Name(), + }, + }.Exec() + + testStepConfig := teststep.Configuration(confRequest) + + err = wd.SetConfig(ctx, testStepConfig, step.ConfigVariables) + if err != nil { + return fmt.Errorf("Error setting config: %w", err) + } + + // If this step is a PlanOnly step, skip over this first Plan and + // subsequent Apply, and use the follow-up Plan that checks for + // permadiffs + if !step.PlanOnly { + logging.HelperResourceDebug(ctx, "Running Terraform CLI plan and apply") + + // Plan! + err := runProviderCommand(ctx, t, wd, providers, func() error { + var opts []tfexec.PlanOption + if step.Destroy { + opts = append(opts, tfexec.Destroy(true)) + } + + if c.AdditionalCLIOptions != nil { + if c.AdditionalCLIOptions.Plan.AllowDeferral { + opts = append(opts, tfexec.AllowDeferral(true)) + } + if c.AdditionalCLIOptions.Plan.NoRefresh { + opts = append(opts, tfexec.Refresh(false)) + } + } + + return wd.CreatePlan(ctx, opts...) + }) + if err != nil { + return fmt.Errorf("Error running pre-apply plan: %w", err) + } + + // Run pre-apply plan checks + if len(step.ConfigPlanChecks.PreApply) > 0 { + var plan *tfjson.Plan + err = runProviderCommand(ctx, t, wd, providers, func() error { + var err error + plan, err = wd.SavedPlan(ctx) + return err + }) + if err != nil { + return fmt.Errorf("Error retrieving pre-apply plan: %w", err) + } + + err = runPlanChecks(ctx, t, plan, step.ConfigPlanChecks.PreApply) + if err != nil { + return fmt.Errorf("Pre-apply plan check(s) failed:\n%w", err) + } + } + + // We need to keep a copy of the state prior to destroying such + // that the destroy steps can verify their behavior in the + // check function + var stateBeforeApplication *terraform.State + + if step.Check != nil && step.Destroy { + // Refresh the state before shimming it for destroy checks later. + // This re-implements previously existing test step logic for the + // specific situation that a provider developer has applied a + // resource with a previous schema version and is destroying it with + // a provider that has a newer schema version. Without this refresh + // the shim logic will return an error such as: + // + // Failed to marshal state to json: schema version 0 for null_resource.test in state does not match version 1 from the provider + err := runProviderCommand(ctx, t, wd, providers, func() error { + return wd.Refresh(ctx) + }) + + if err != nil { + return fmt.Errorf("Error running pre-apply refresh: %w", err) + } + + err = runProviderCommand(ctx, t, wd, providers, func() error { + _, stateBeforeApplication, err = getState(ctx, t, wd) + if err != nil { + return err + } + return nil + }) + + if err != nil { + return fmt.Errorf("Error retrieving pre-apply state: %w", err) + } + } + + // Apply the diff, creating real resources + err = runProviderCommand(ctx, t, wd, providers, func() error { + var opts []tfexec.ApplyOption + + if c.AdditionalCLIOptions != nil && c.AdditionalCLIOptions.Apply.AllowDeferral { + opts = append(opts, tfexec.AllowDeferral(true)) + } + + return wd.Apply(ctx, opts...) + }) + if err != nil { + if step.Destroy { + return fmt.Errorf("Error running destroy: %w", err) + } + return fmt.Errorf("Error running apply: %w", err) + } + + // Run any configured checks + if step.Check != nil { + logging.HelperResourceTrace(ctx, "Using TestStep Check") + + if step.Destroy { + if err := step.Check(stateBeforeApplication); err != nil { + return fmt.Errorf("Check failed: %w", err) + } + } else { + var state *terraform.State + + err := runProviderCommand(ctx, t, wd, providers, func() error { + _, state, err = getState(ctx, t, wd) + if err != nil { + return err + } + return nil + }) + + if err != nil { + return fmt.Errorf("Error retrieving state after apply: %w", err) + } + + if err := step.Check(state); err != nil { + return fmt.Errorf("Check failed: %w", err) + } + } + } + + // Run state checks + if len(step.ConfigStateChecks) > 0 { + var state *tfjson.State + + err = runProviderCommand(ctx, t, wd, providers, func() error { + var err error + state, err = wd.State(ctx) + return err + }) + + if err != nil { + return fmt.Errorf("Error retrieving post-apply, post-refresh state: %w", err) + } + + err = runStateChecks(ctx, t, state, step.ConfigStateChecks) + if err != nil { + return fmt.Errorf("Post-apply refresh state check(s) failed:\n%w", err) + } + } + } + + // Test for perpetual diffs by performing a plan, a refresh, and another plan + logging.HelperResourceDebug(ctx, "Running Terraform CLI plan to check for perpetual differences") + + // do a plan + err = runProviderCommand(ctx, t, wd, providers, func() error { + opts := []tfexec.PlanOption{ + tfexec.Refresh(false), + } + if step.Destroy { + opts = append(opts, tfexec.Destroy(true)) + } + + if c.AdditionalCLIOptions != nil { + if c.AdditionalCLIOptions.Plan.AllowDeferral { + opts = append(opts, tfexec.AllowDeferral(true)) + } + if c.AdditionalCLIOptions.Plan.NoRefresh { + opts = append(opts, tfexec.Refresh(false)) + } + } + + return wd.CreatePlan(ctx, opts...) + }) + if err != nil { + if step.PlanOnly { + return fmt.Errorf("Error running non-refresh plan: %w", err) + } + + return fmt.Errorf("Error running post-apply non-refresh plan: %w", err) + } + + var plan *tfjson.Plan + err = runProviderCommand(ctx, t, wd, providers, func() error { + var err error + plan, err = wd.SavedPlan(ctx) + return err + }) + if err != nil { + if step.PlanOnly { + return fmt.Errorf("Error reading saved non-refresh plan: %w", err) + } + + return fmt.Errorf("Error reading saved post-apply non-refresh plan: %w", err) + } + + // Run post-apply, pre-refresh plan checks + if len(step.ConfigPlanChecks.PostApplyPreRefresh) > 0 { + err = runPlanChecks(ctx, t, plan, step.ConfigPlanChecks.PostApplyPreRefresh) + if err != nil { + if step.PlanOnly { + return fmt.Errorf("Non-refresh plan checks(s) failed:\n%w", err) + } + + return fmt.Errorf("Post-apply, pre-refresh plan check(s) failed:\n%w", err) + } + } + + if !planIsEmpty(plan, helper.TerraformVersion()) && !step.ExpectNonEmptyPlan { + var stdout string + err = runProviderCommand(ctx, t, wd, providers, func() error { + var err error + stdout, err = wd.SavedPlanRawStdout(ctx) + return err + }) + if err != nil { + return fmt.Errorf("Error reading saved human-readable non-refresh plan output: %w", err) + } + + if step.PlanOnly { + return fmt.Errorf("The non-refresh plan was not empty.\nstdout:\n\n%s", stdout) + } + + return fmt.Errorf("After applying this test step, the non-refresh plan was not empty.\nstdout:\n\n%s", stdout) + } + + // do another plan + err = runProviderCommand(ctx, t, wd, providers, func() error { + var opts []tfexec.PlanOption + if step.Destroy { + opts = append(opts, tfexec.Destroy(true)) + + if step.PreventPostDestroyRefresh { + opts = append(opts, tfexec.Refresh(false)) + } + } + + if c.AdditionalCLIOptions != nil { + if c.AdditionalCLIOptions.Plan.AllowDeferral { + opts = append(opts, tfexec.AllowDeferral(true)) + } + if c.AdditionalCLIOptions.Plan.NoRefresh { + opts = append(opts, tfexec.Refresh(false)) + } + } + + return wd.CreatePlan(ctx, opts...) + }) + if err != nil { + if step.PlanOnly { + return fmt.Errorf("Error running refresh plan: %w", err) + } + + return fmt.Errorf("Error running post-apply refresh plan: %w", err) + } + + err = runProviderCommand(ctx, t, wd, providers, func() error { + var err error + plan, err = wd.SavedPlan(ctx) + return err + }) + if err != nil { + if step.PlanOnly { + return fmt.Errorf("Error reading refresh plan: %w", err) + } + + return fmt.Errorf("Error reading post-apply refresh plan: %w", err) + } + + // Run post-apply, post-refresh plan checks + if len(step.ConfigPlanChecks.PostApplyPostRefresh) > 0 { + err = runPlanChecks(ctx, t, plan, step.ConfigPlanChecks.PostApplyPostRefresh) + if err != nil { + return fmt.Errorf("Post-apply refresh plan check(s) failed:\n%w", err) + } + } + + // check if plan is empty + if !planIsEmpty(plan, helper.TerraformVersion()) && !step.ExpectNonEmptyPlan { + var stdout string + err = runProviderCommand(ctx, t, wd, providers, func() error { + var err error + stdout, err = wd.SavedPlanRawStdout(ctx) + return err + }) + if err != nil { + return fmt.Errorf("Error reading human-readable refresh plan output: %w", err) + } + + if step.PlanOnly { + return fmt.Errorf("The refresh plan was not empty.\nstdout\n\n%s", stdout) + } + + return fmt.Errorf("After applying this test step, the refresh plan was not empty.\nstdout\n\n%s", stdout) + } else if step.ExpectNonEmptyPlan && planIsEmpty(plan, helper.TerraformVersion()) { + return errors.New("Expected a non-empty plan, but got an empty refresh plan") + } + + // ID-ONLY REFRESH + // If we've never checked an id-only refresh and our state isn't + // empty, find the first resource and test it. + if c.IDRefreshName != "" { + logging.HelperResourceTrace(ctx, "Using TestCase IDRefreshName") + + var state *terraform.State + + err = runProviderCommand(ctx, t, wd, providers, func() error { + _, state, err = getState(ctx, t, wd) + if err != nil { + return err + } + return nil + }) + + if err != nil { + return err + } + + //nolint:staticcheck // legacy usage + if state.Empty() { + return nil + } + + var idRefreshCheck *terraform.ResourceState + + // Find the first non-nil resource in the state + for _, m := range state.Modules { + if len(m.Resources) > 0 { + if v, ok := m.Resources[c.IDRefreshName]; ok { + idRefreshCheck = v + } + + break + } + } + + // If we have an instance to check for refreshes, do it + // immediately. We do it in the middle of another test + // because it shouldn't affect the overall state (refresh + // is read-only semantically) and we want to fail early if + // this fails. If refresh isn't read-only, then this will have + // caught a different bug. + if idRefreshCheck != nil { + fmt.Println("Not Writing by testing ID Refresh") + if err := testIDRefresh(ctx, t, c, wd, step, idRefreshCheck, providers, stepIndex, helper); err != nil { + return fmt.Errorf( + "[ERROR] Test: ID-only test failed: %s", err) + } + } + } + + if step.PostApplyFunc != nil { + logging.HelperResourceDebug(ctx, "Calling TestCase PostApplyFunc") + step.PostApplyFunc() + logging.HelperResourceDebug(ctx, "Called TestCase PostApplyFunc") + } + + if refreshAfterApply && !step.Destroy && !step.PlanOnly { + if len(c.Steps) > stepIndex+1 { + // If the next step is a refresh, then we have no need to refresh here + if !c.Steps[stepIndex+1].RefreshState { + // Log a searchable message to easily determine when this is no longer being used + logging.HelperResourceDebug(ctx, EnvTfAccRefreshAfterApply+": running apply -refresh-only -refresh=true") + err := runProviderCommandApplyRefreshOnly(ctx, t, wd, providers) + if err != nil { + return fmt.Errorf("Error running apply refresh-only: %w", err) + } + } + } + } + + return nil +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/testing_new_import_state.go b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/testing_new_import_state.go new file mode 100644 index 0000000000..d2d71311cc --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/testing_new_import_state.go @@ -0,0 +1,571 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package resource + +import ( + "context" + "encoding/json" + "fmt" + "reflect" + "strings" + + "github.com/hashicorp/go-version" + + tfjson "github.com/hashicorp/terraform-json" + + "github.com/google/go-cmp/cmp" + "github.com/mitchellh/go-testing-interface" + + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/internal/logging" + "github.com/hashicorp/terraform-plugin-testing/internal/plugintest" + "github.com/hashicorp/terraform-plugin-testing/internal/teststep" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/hashicorp/terraform-plugin-testing/tfversion" +) + +func testStepNewImportState(ctx context.Context, t testing.T, helper *plugintest.Helper, testCaseWorkingDir *plugintest.WorkingDir, step TestStep, priorStepCfg teststep.Config, providers *providerFactories, stepNumber int) error { + t.Helper() + + // step.ImportStateKind implicitly defaults to the zero-value (ImportCommandWithID) for backward compatibility + kind := step.ImportStateKind + importStatePersist := step.ImportStatePersist + + if err := importStatePreconditions(t, helper, step); err != nil { + return err + } + + resourceName := step.ResourceName + if resourceName == "" { + t.Fatal("ResourceName is required for an import state test") + } + + // get state from check sequence + var state *terraform.State + var stateJSON *tfjson.State + var err error + + err = runProviderCommand(ctx, t, testCaseWorkingDir, providers, func() error { + stateJSON, state, err = getState(ctx, t, testCaseWorkingDir) + if err != nil { + return err + } + return nil + }) + if err != nil { + t.Fatalf("Error getting state: %s", err) + } + + // Determine the ID to import + var importId string + switch { + case step.ImportStateIdFunc != nil: + logging.HelperResourceTrace(ctx, "Using TestStep ImportStateIdFunc for import identifier") + + var err error + + logging.HelperResourceDebug(ctx, "Calling TestStep ImportStateIdFunc") + + importId, err = step.ImportStateIdFunc(state) + + if err != nil { + t.Fatal(err) + } + + logging.HelperResourceDebug(ctx, "Called TestStep ImportStateIdFunc") + case step.ImportStateId != "": + logging.HelperResourceTrace(ctx, "Using TestStep ImportStateId for import identifier") + + importId = step.ImportStateId + default: + logging.HelperResourceTrace(ctx, "Using resource identifier for import identifier") + + resource, err := testResource(resourceName, state) + if err != nil { + t.Fatal(err) + } + importId = resource.Primary.ID + } + + if step.ImportStateIdPrefix != "" { + logging.HelperResourceTrace(ctx, "Prepending TestStep ImportStateIdPrefix for import identifier") + + importId = step.ImportStateIdPrefix + importId + } + + logging.HelperResourceTrace(ctx, fmt.Sprintf("Using import identifier: %s", importId)) + + var priorIdentityValues map[string]any + + if kind.plannable() && kind.resourceIdentity() { + priorIdentityValues = identityValuesFromStateValues(stateJSON.Values, resourceName) + if len(priorIdentityValues) == 0 { + return fmt.Errorf("importing resource %s: expected prior state to have resource identity values, got none", resourceName) + } + } + + testStepConfigRequest := config.TestStepConfigRequest{ + StepNumber: stepNumber, + TestName: t.Name(), + } + testStepConfig := teststep.Configuration(teststep.PrepareConfigurationRequest{ + Directory: step.ConfigDirectory, + File: step.ConfigFile, + Raw: step.Config, + TestStepConfigRequest: testStepConfigRequest, + }.Exec()) + + // If the current import state test step doesn't have configuration, use the prior test step config + if testStepConfig == nil { + if priorStepCfg == nil { + t.Fatal("Cannot import state with no specified config") + } + + logging.HelperResourceTrace(ctx, "Using prior TestStep Config for import") + + testStepConfig = priorStepCfg + } + + switch { + case step.ImportStateConfigExact: + break + + case kind.plannable() && kind.resourceIdentity(): + testStepConfig = appendImportBlockWithIdentity(testStepConfig, resourceName, priorIdentityValues) + + case kind.plannable(): + testStepConfig = appendImportBlock(testStepConfig, resourceName, importId) + } + + var workingDir *plugintest.WorkingDir + if importStatePersist { + workingDir = testCaseWorkingDir + } else { + workingDir = helper.RequireNewWorkingDir(ctx, t, "") + defer workingDir.Close() + } + + err = workingDir.SetConfig(ctx, testStepConfig, step.ConfigVariables) + if err != nil { + t.Fatalf("Error setting test config: %s", err) + } + + if kind.plannable() { + if stepNumber > 1 { + err = workingDir.CopyState(ctx, testCaseWorkingDir.StateFilePath()) + if err != nil { + t.Fatalf("copying state: %s", err) + } + + err = runProviderCommand(ctx, t, workingDir, providers, func() error { + return workingDir.RemoveResource(ctx, resourceName) + }) + if err != nil { + t.Fatalf("removing resource %s from copied state: %s", resourceName, err) + } + } + } + + if !importStatePersist { + err = runProviderCommand(ctx, t, workingDir, providers, func() error { + return workingDir.Init(ctx) + }) + if err != nil { + t.Fatalf("Error running init: %s", err) + } + } + + if kind.plannable() { + return testImportBlock(ctx, t, workingDir, providers, resourceName, step, priorIdentityValues) + } else { + return testImportCommand(ctx, t, workingDir, providers, resourceName, importId, step, state) + } +} + +func testImportBlock(ctx context.Context, t testing.T, workingDir *plugintest.WorkingDir, providers *providerFactories, resourceName string, step TestStep, priorIdentityValues map[string]any) error { + kind := step.ImportStateKind + + err := runProviderCommandCreatePlan(ctx, t, workingDir, providers) + if err != nil { + return fmt.Errorf("generating plan with import config: %s", err) + } + + plan, err := runProviderCommandSavedPlan(ctx, t, workingDir, providers) + if err != nil { + return fmt.Errorf("reading generated plan with import config: %s", err) + } + + logging.HelperResourceDebug(ctx, fmt.Sprintf("ImportBlockWithId: %d resource changes", len(plan.ResourceChanges))) + + // Verify reasonable things about the plan + var resourceChangeUnderTest *tfjson.ResourceChange + + if len(plan.ResourceChanges) == 0 { + return fmt.Errorf("importing resource %s: expected a resource change, got no changes", resourceName) + } + + for _, change := range plan.ResourceChanges { + if change.Address == resourceName { + resourceChangeUnderTest = change + } + } + + if resourceChangeUnderTest == nil || resourceChangeUnderTest.Change == nil || resourceChangeUnderTest.Change.Actions == nil { + return fmt.Errorf("importing resource %s: expected a resource change, got no changes", resourceName) + } + + change := resourceChangeUnderTest.Change + actions := change.Actions + importing := change.Importing + + switch { + case importing == nil: + return fmt.Errorf("importing resource %s: expected an import operation, got %q action with plan \nstdout:\n\n%s", resourceChangeUnderTest.Address, actions, savedPlanRawStdout(ctx, t, workingDir, providers)) + // By default we want to ensure there isn't a proposed plan after importing, but for some resources this is unavoidable. + // An example would be importing a resource that cannot read it's entire value back from the remote API. + case !step.ExpectNonEmptyPlan && !actions.NoOp(): + return fmt.Errorf("importing resource %s: expected a no-op import operation, got %q action with plan \nstdout:\n\n%s", resourceChangeUnderTest.Address, actions, savedPlanRawStdout(ctx, t, workingDir, providers)) + } + + if err := runPlanChecks(ctx, t, plan, step.ImportPlanChecks.PreApply); err != nil { + return err + } + + if kind.resourceIdentity() { + newIdentityValues := identityValuesFromStateValues(plan.PlannedValues, resourceName) + if !cmp.Equal(priorIdentityValues, newIdentityValues) { + return fmt.Errorf("importing resource %s: expected identity values %v, got %v", resourceName, priorIdentityValues, newIdentityValues) + } + } + + return nil +} + +func testImportCommand(ctx context.Context, t testing.T, workingDir *plugintest.WorkingDir, providers *providerFactories, resourceName string, importId string, step TestStep, state *terraform.State) error { + err := runProviderCommand(ctx, t, workingDir, providers, func() error { + return workingDir.Import(ctx, resourceName, importId) + }) + if err != nil { + return err + } + + var importState *terraform.State + err = runProviderCommand(ctx, t, workingDir, providers, func() error { + _, importState, err = getState(ctx, t, workingDir) + if err != nil { + return err + } + return nil + }) + if err != nil { + t.Fatalf("Error getting state: %s", err) + } + + logging.HelperResourceDebug(ctx, fmt.Sprintf("State after import: %d resources in the root module", len(importState.RootModule().Resources))) + + // Go through the imported state and verify + if step.ImportStateCheck != nil { + logging.HelperResourceTrace(ctx, "Using TestStep ImportStateCheck") + runImportStateCheckFunction(ctx, t, importState, step) + } + + // Verify that all the states match + if step.ImportStateVerify { + logging.HelperResourceTrace(ctx, "Using TestStep ImportStateVerify") + + // Ensure that we do not match against data sources as they + // cannot be imported and are not what we want to verify. + // Mode is not present in ResourceState so we use the + // stringified ResourceStateKey for comparison. + newResources := make(map[string]*terraform.ResourceState) + for k, v := range importState.RootModule().Resources { + if !strings.HasPrefix(k, "data.") { + newResources[k] = v + } + } + oldResources := make(map[string]*terraform.ResourceState) + for k, v := range state.RootModule().Resources { + if !strings.HasPrefix(k, "data.") { + oldResources[k] = v + } + } + + identifierAttribute := step.ImportStateVerifyIdentifierAttribute + + if identifierAttribute == "" { + identifierAttribute = "id" + } + + for _, r := range newResources { + rIdentifier, ok := r.Primary.Attributes[identifierAttribute] + + if !ok { + t.Fatalf("ImportStateVerify: New resource missing identifier attribute %q, ensure attribute value is properly set or use ImportStateVerifyIdentifierAttribute to choose different attribute", identifierAttribute) + } + + // Find the existing resource + var oldR *terraform.ResourceState + for _, r2 := range oldResources { + if r2.Primary == nil || r2.Type != r.Type || r2.Provider != r.Provider { + continue + } + + r2Identifier, ok := r2.Primary.Attributes[identifierAttribute] + + if !ok { + t.Fatalf("ImportStateVerify: Old resource missing identifier attribute %q, ensure attribute value is properly set or use ImportStateVerifyIdentifierAttribute to choose different attribute", identifierAttribute) + } + + if r2Identifier == rIdentifier { + oldR = r2 + break + } + } + if oldR == nil || oldR.Primary == nil { + t.Fatalf( + "Failed state verification, resource with ID %s not found", + rIdentifier) + } + + // don't add empty flatmapped containers, so we can more easily + // compare the attributes + skipEmpty := func(k, v string) bool { + if strings.HasSuffix(k, ".#") || strings.HasSuffix(k, ".%") { + if v == "0" { + return true + } + } + return false + } + + // Compare their attributes + actual := make(map[string]string) + for k, v := range r.Primary.Attributes { + if skipEmpty(k, v) { + continue + } + actual[k] = v + } + + expected := make(map[string]string) + for k, v := range oldR.Primary.Attributes { + if skipEmpty(k, v) { + continue + } + expected[k] = v + } + + // Remove fields we're ignoring + for _, v := range step.ImportStateVerifyIgnore { + for k := range actual { + if strings.HasPrefix(k, v) { + delete(actual, k) + } + } + for k := range expected { + if strings.HasPrefix(k, v) { + delete(expected, k) + } + } + } + + // timeouts are only _sometimes_ added to state. To + // account for this, just don't compare timeouts at + // all. + for k := range actual { + if strings.HasPrefix(k, "timeouts.") { + delete(actual, k) + } + if k == "timeouts" { + delete(actual, k) + } + } + for k := range expected { + if strings.HasPrefix(k, "timeouts.") { + delete(expected, k) + } + if k == "timeouts" { + delete(expected, k) + } + } + + if !reflect.DeepEqual(actual, expected) { + // Determine only the different attributes + // go-cmp tries to show surrounding identical map key/value for + // context of differences, which may be confusing. + for k, v := range expected { + if av, ok := actual[k]; ok && v == av { + delete(expected, k) + delete(actual, k) + } + } + + if diff := cmp.Diff(expected, actual); diff != "" { + return fmt.Errorf("ImportStateVerify attributes not equivalent. Difference is shown below. The - symbol indicates attributes missing after import.\n\n%s", diff) + } + } + } + } + + return nil +} + +func appendImportBlock(config teststep.Config, resourceName string, importID string) teststep.Config { + return config.Append( + fmt.Sprintf(``+"\n"+ + `import {`+"\n"+ + ` to = %s`+"\n"+ + ` id = %q`+"\n"+ + `}`, + resourceName, importID)) +} + +func appendImportBlockWithIdentity(config teststep.Config, resourceName string, identityValues map[string]any) teststep.Config { + configBuilder := strings.Builder{} + configBuilder.WriteString(fmt.Sprintf(``+"\n"+ + `import {`+"\n"+ + ` to = %s`+"\n"+ + ` identity = {`+"\n", + resourceName)) + + for k, v := range identityValues { + // It's valid for identity attributes to be null, we can just omit it from config + if v == nil { + continue + } + + switch v := v.(type) { + case bool: + configBuilder.WriteString(fmt.Sprintf(` %q = %t`+"\n", k, v)) + + case []any: + var quotedV []string + for _, v := range v { + quotedV = append(quotedV, fmt.Sprintf(`%q`, v)) + } + configBuilder.WriteString(fmt.Sprintf(` %q = [%s]`+"\n", k, strings.Join(quotedV, ", "))) + + case json.Number: + configBuilder.WriteString(fmt.Sprintf(` %q = %s`+"\n", k, v)) + + case string: + configBuilder.WriteString(fmt.Sprintf(` %q = %q`+"\n", k, v)) + + default: + panic(fmt.Sprintf("unexpected type %T for identity value %q", v, k)) + } + } + + configBuilder.WriteString(` }` + "\n") + configBuilder.WriteString(`}` + "\n") + + return config.Append(configBuilder.String()) +} + +func importStatePreconditions(t testing.T, helper *plugintest.Helper, step TestStep) error { + t.Helper() + + kind := step.ImportStateKind + versionUnderTest := *helper.TerraformVersion().Core() + resourceIdentityMinimumVersion := version.Must(version.NewVersion("1.12.0")) + + // Instead of calling [t.Fatal], we return an error. This package's unit tests can use [TestStep.ExpectError] to match + // on the error message. An alternative, [plugintest.TestExpectTFatal], does not have access to logged error messages, + // so it is open to false positives on this complex code path. + // + // Multiple cases may match, so check the most specific cases first + switch { + case kind.resourceIdentity() && versionUnderTest.LessThan(resourceIdentityMinimumVersion): + return fmt.Errorf( + `ImportState steps using resource identity require Terraform 1.12.0 or later. Either ` + + `upgrade the Terraform version running the test or add a ` + "`TerraformVersionChecks`" + ` to ` + + `the test case to skip this test.` + "\n\n" + + `https://developer.hashicorp.com/terraform/plugin/testing/acceptance-tests/tfversion-checks#skip-version-checks`) + + case kind.plannable() && versionUnderTest.LessThan(tfversion.Version1_5_0): + return fmt.Errorf( + `ImportState steps using plannable import blocks require Terraform 1.5.0 or later. Either ` + + `upgrade the Terraform version running the test or add a ` + "`TerraformVersionChecks`" + ` to ` + + `the test case to skip this test.` + "\n\n" + + `https://developer.hashicorp.com/terraform/plugin/testing/acceptance-tests/tfversion-checks#skip-version-checks`) + + case kind.plannable() && step.ImportStatePersist: + return fmt.Errorf(`ImportStatePersist is not supported with plannable import blocks`) + + case kind.plannable() && step.ImportStateVerify: + return fmt.Errorf(`ImportStateVerify is not supported with plannable import blocks`) + } + + return nil +} + +func resourcesFromState(stateValues *tfjson.StateValues) []*tfjson.StateResource { + if stateValues == nil || stateValues.RootModule == nil { + return []*tfjson.StateResource{} + } + + return stateValues.RootModule.Resources +} + +func identityValuesFromStateValues(stateValues *tfjson.StateValues, resourceName string) map[string]any { + var resource *tfjson.StateResource + resources := resourcesFromState(stateValues) + + for _, r := range resources { + if r.Address == resourceName { + resource = r + break + } + } + + if resource == nil || len(resource.IdentityValues) == 0 { + return map[string]any{} + } + + return resource.IdentityValues +} + +func runImportStateCheckFunction(ctx context.Context, t testing.T, importState *terraform.State, step TestStep) { + t.Helper() + + var states []*terraform.InstanceState + for address, r := range importState.RootModule().Resources { + if strings.HasPrefix(address, "data.") { + continue + } + + if r.Primary == nil { + continue + } + + is := r.Primary.DeepCopy() //nolint:staticcheck // legacy usage + is.Ephemeral.Type = r.Type // otherwise the check function cannot see the type + states = append(states, is) + } + + logging.HelperResourceTrace(ctx, "Calling TestStep ImportStateCheck") + + if err := step.ImportStateCheck(states); err != nil { + t.Fatal(err) + } + + logging.HelperResourceTrace(ctx, "Called TestStep ImportStateCheck") +} + +func savedPlanRawStdout(ctx context.Context, t testing.T, wd *plugintest.WorkingDir, providers *providerFactories) string { + t.Helper() + + var stdout string + + err := runProviderCommand(ctx, t, wd, providers, func() error { + var err error + stdout, err = wd.SavedPlanRawStdout(ctx) + return err + }) + + if err != nil { + return fmt.Sprintf("error retrieving formatted plan output: %s", err) + } + return stdout +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/testing_new_query.go b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/testing_new_query.go new file mode 100644 index 0000000000..c8961f9433 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/testing_new_query.go @@ -0,0 +1,47 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package resource + +import ( + "context" + "fmt" + + tfjson "github.com/hashicorp/terraform-json" + "github.com/mitchellh/go-testing-interface" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource/query" + "github.com/hashicorp/terraform-plugin-testing/internal/plugintest" + "github.com/hashicorp/terraform-plugin-testing/internal/teststep" +) + +func testStepNewQuery(ctx context.Context, t testing.T, wd *plugintest.WorkingDir, step TestStep, providers *providerFactories) error { + t.Helper() + + queryConfigRequest := teststep.ConfigurationRequest{ + Raw: &step.Config, + } + err := wd.SetQuery(ctx, teststep.Configuration(queryConfigRequest), step.ConfigVariables) + if err != nil { + return fmt.Errorf("Error setting query config: %w", err) + } + + err = runProviderCommand(ctx, t, wd, providers, func() error { + return wd.Init(ctx) + }) + if err != nil { + t.Fatalf("Error getting init: %s", err) + } + + var queryOut []tfjson.LogMsg + err = runProviderCommand(ctx, t, wd, providers, func() error { + var err error + queryOut, err = wd.Query(ctx) + return err + }) + if err != nil { + return err + } + + return query.RunQueryChecks(ctx, t, queryOut, step.QueryResultChecks) +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource/testing_new_refresh_state.go b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/testing_new_refresh_state.go similarity index 57% rename from vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource/testing_new_refresh_state.go rename to vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/testing_new_refresh_state.go index 627190a9d1..b1971a289f 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource/testing_new_refresh_state.go +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/testing_new_refresh_state.go @@ -10,9 +10,10 @@ import ( tfjson "github.com/hashicorp/terraform-json" "github.com/mitchellh/go-testing-interface" - "github.com/hashicorp/terraform-plugin-sdk/v2/internal/logging" - "github.com/hashicorp/terraform-plugin-sdk/v2/internal/plugintest" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-plugin-testing/terraform" + + "github.com/hashicorp/terraform-plugin-testing/internal/logging" + "github.com/hashicorp/terraform-plugin-testing/internal/plugintest" ) func testStepNewRefreshState(ctx context.Context, t testing.T, wd *plugintest.WorkingDir, step TestStep, providers *providerFactories) error { @@ -20,32 +21,32 @@ func testStepNewRefreshState(ctx context.Context, t testing.T, wd *plugintest.Wo var err error // Explicitly ensure prior state exists before refresh. - err = runProviderCommand(ctx, t, func() error { - _, err = getState(ctx, t, wd) + err = runProviderCommand(ctx, t, wd, providers, func() error { + _, _, err = getState(ctx, t, wd) if err != nil { return err } return nil - }, wd, providers) + }) if err != nil { t.Fatalf("Error getting state: %s", err) } - err = runProviderCommand(ctx, t, func() error { + err = runProviderCommand(ctx, t, wd, providers, func() error { return wd.Refresh(ctx) - }, wd, providers) + }) if err != nil { return err } var refreshState *terraform.State - err = runProviderCommand(ctx, t, func() error { - refreshState, err = getState(ctx, t, wd) + err = runProviderCommand(ctx, t, wd, providers, func() error { + _, refreshState, err = getState(ctx, t, wd) if err != nil { return err } return nil - }, wd, providers) + }) if err != nil { t.Fatalf("Error getting state: %s", err) } @@ -62,30 +63,38 @@ func testStepNewRefreshState(ctx context.Context, t testing.T, wd *plugintest.Wo } // do a plan - err = runProviderCommand(ctx, t, func() error { + err = runProviderCommand(ctx, t, wd, providers, func() error { return wd.CreatePlan(ctx) - }, wd, providers) + }) if err != nil { - return fmt.Errorf("Error running post-apply plan: %w", err) + return fmt.Errorf("Error running post-refresh plan: %w", err) } var plan *tfjson.Plan - err = runProviderCommand(ctx, t, func() error { + err = runProviderCommand(ctx, t, wd, providers, func() error { var err error plan, err = wd.SavedPlan(ctx) return err - }, wd, providers) + }) if err != nil { - return fmt.Errorf("Error retrieving post-apply plan: %w", err) + return fmt.Errorf("Error retrieving post-refresh plan: %w", err) + } + + // Run post-refresh plan checks + if len(step.RefreshPlanChecks.PostRefresh) > 0 { + err = runPlanChecks(ctx, t, plan, step.RefreshPlanChecks.PostRefresh) + if err != nil { + return fmt.Errorf("Post-refresh plan check(s) failed:\n%w", err) + } } - if !planIsEmpty(plan) && !step.ExpectNonEmptyPlan { + if !planIsEmpty(plan, wd.GetHelper().TerraformVersion()) && !step.ExpectNonEmptyPlan { var stdout string - err = runProviderCommand(ctx, t, func() error { + err = runProviderCommand(ctx, t, wd, providers, func() error { var err error stdout, err = wd.SavedPlanRawStdout(ctx) return err - }, wd, providers) + }) if err != nil { return fmt.Errorf("Error retrieving formatted plan output: %w", err) } diff --git a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource/testing_sets.go b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/testing_sets.go similarity index 73% rename from vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource/testing_sets.go rename to vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/testing_sets.go index 45cce95732..8511222043 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource/testing_sets.go +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/testing_sets.go @@ -10,7 +10,7 @@ import ( "regexp" "strings" - "github.com/hashicorp/terraform-plugin-sdk/v2/terraform" + "github.com/hashicorp/terraform-plugin-testing/terraform" ) const ( @@ -20,7 +20,7 @@ const ( // 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 indices, +// 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. // @@ -57,6 +57,46 @@ const ( // 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. +// +// An experimental interface exists to potentially replace the +// TestCheckTypeSetElemNestedAttrs functionality in the future and feedback +// would be appreciated. This example performs the same check as +// TestCheckTypeSetElemNestedAttrs with that experimental interface, by using +// [statecheck.ExpectKnownValue] in combination with [knownvalue.SetPartial]: +// +// package example_test +// +// import ( +// "testing" +// +// "github.com/hashicorp/terraform-plugin-testing/helper/resource" +// "github.com/hashicorp/terraform-plugin-testing/knownvalue" +// "github.com/hashicorp/terraform-plugin-testing/statecheck" +// "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +// ) +// +// func TestExpectKnownValue_CheckState_SetPartial(t *testing.T) { +// t.Parallel() +// +// resource.Test(t, resource.TestCase{ +// // Provider definition omitted. +// Steps: []resource.TestStep{ +// { +// // Example resource containing a computed set attribute named "computed_attribute" +// Config: `resource "test_resource" "one" {}`, +// ConfigStateChecks: []statecheck.StateCheck{ +// statecheck.ExpectKnownValue( +// "test_resource.one", +// tfjsonpath.New("computed_attribute"), +// knownvalue.SetPartial([]knownvalue.Check{ +// knownvalue.StringExact("value2"), +// }), +// ), +// }, +// }, +// }, +// }) +// } func TestCheckTypeSetElemNestedAttrs(name, attr string, values map[string]string) TestCheckFunc { return func(s *terraform.State) error { is, err := primaryInstanceState(s, name) @@ -92,7 +132,7 @@ func TestCheckTypeSetElemNestedAttrs(name, attr string, values map[string]string // 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 indices, which can easily change with schema +// 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. // @@ -129,6 +169,56 @@ func TestCheckTypeSetElemNestedAttrs(name, attr string, values map[string]string // 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. +// +// 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. +// +// An experimental interface exists to potentially replace the +// TestMatchTypeSetElemNestedAttrs functionality in the future and feedback +// would be appreciated. This example performs the same check as +// TestMatchTypeSetElemNestedAttrs with that experimental interface, by using +// [statecheck.ExpectKnownValue] in combination with [knownvalue.SetExact], +// with a nested [knownvalue.StringRegexp]: +// +// package example_test +// +// import ( +// "testing" +// +// "github.com/hashicorp/terraform-plugin-testing/helper/resource" +// "github.com/hashicorp/terraform-plugin-testing/knownvalue" +// "github.com/hashicorp/terraform-plugin-testing/statecheck" +// "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +// ) +// +// func TestExpectKnownValue_CheckState_SetNestedBlock_Custom(t *testing.T) { +// t.Parallel() +// +// resource.Test(t, resource.TestCase{ +// // Provider definition omitted. +// Steps: []resource.TestStep{ +// { +// // Example resource containing a set nested block name "block" which contains a computed string attribute named "computed_attribute" +// Config: `resource "test_resource" "one" {}`, +// ConfigStateChecks: []statecheck.StateCheck{ +// statecheck.ExpectKnownValue( +// "test_resource.one", +// tfjsonpath.New("block"), +// knownvalue.SetExact([]knownvalue.Check{ +// knownvalue.MapExact(map[string]knownvalue.Check{ +// "computed_attribute": knownvalue.StringRegexp(regexp.MustCompile("str")), +// }), +// knownvalue.MapExact(map[string]knownvalue.Check{ +// "computed_attribute": knownvalue.StringRegexp(regexp.MustCompile("rts")), +// }), +// }), +// ), +// }, +// }, +// }, +// }) +// } func TestMatchTypeSetElemNestedAttrs(name, attr string, values map[string]*regexp.Regexp) TestCheckFunc { return func(s *terraform.State) error { is, err := primaryInstanceState(s, name) @@ -171,7 +261,7 @@ func TestMatchTypeSetElemNestedAttrs(name, attr string, values map[string]*regex // 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 indices, which can easily change with schema changes. +// compatibility with indicies, which can easily change with schema changes. // State value checking is only recommended for testing Computed attributes and // attribute defaults. // @@ -200,6 +290,47 @@ func TestMatchTypeSetElemNestedAttrs(name, attr string, values map[string]*regex // - Boolean: "false" or "true". // - Float/Integer: Stringified number, such as "1.2" or "123". // - String: No conversion necessary. +// +// An experimental interface exists to potentially replace the +// TestCheckTypeSetElemAttr functionality in the future and feedback +// would be appreciated. This example performs the same check as +// TestCheckTypeSetElemAttr with that experimental interface, by using +// [statecheck.ExpectKnownValue] in combination with [knownvalue.SetExact]: +// +// package example_test +// +// import ( +// "testing" +// +// "github.com/hashicorp/terraform-plugin-testing/helper/resource" +// "github.com/hashicorp/terraform-plugin-testing/knownvalue" +// "github.com/hashicorp/terraform-plugin-testing/statecheck" +// "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +// ) +// +// func TestExpectKnownValue_CheckState_Set(t *testing.T) { +// t.Parallel() +// +// resource.Test(t, resource.TestCase{ +// // Provider definition omitted. +// Steps: []resource.TestStep{ +// { +// // Example resource containing a computed set attribute named "computed_attribute" +// Config: `resource "test_resource" "one" {}`, +// ConfigStateChecks: []statecheck.StateCheck{ +// statecheck.ExpectKnownValue( +// "test_resource.one", +// tfjsonpath.New("computed_attribute"), +// knownvalue.SetExact([]knownvalue.Check{ +// knownvalue.StringExact("value2"), +// knownvalue.StringExact("value1"), +// }), +// ), +// }, +// }, +// }, +// }) +// } func TestCheckTypeSetElemAttr(name, attr, value string) TestCheckFunc { return func(s *terraform.State) error { is, err := primaryInstanceState(s, name) diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/teststep_providers.go b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/teststep_providers.go new file mode 100644 index 0000000000..bd9f43b861 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/teststep_providers.go @@ -0,0 +1,258 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package resource + +import ( + "context" + "encoding/json" + "fmt" + "strings" + + "github.com/hashicorp/go-version" +) + +// tfBlockMinReqTFVersion is used to prevent errors arising from +// adding required providers to the terraform block when Terraform +// is any version prior to v1.0.0 +const tfBlockMinReqTFVersion = "1.0.0" + +// mergedConfig prepends any necessary terraform configuration blocks to the +// TestStep Config. +// +// If there are ExternalProviders configurations in either the TestCase or +// TestStep, the terraform configuration block should be included with the +// step configuration to prevent errors with providers outside the +// registry.terraform.io hostname or outside the hashicorp namespace. +// This is only necessary when using TestStep.Config. +// +// When TestStep.ConfigDirectory is used, the expectation is that the +// Terraform configuration files will specify a terraform configuration +// block and/or provider blocks as necessary. +func (s TestStep) mergedConfig(ctx context.Context, testCase TestCase, configHasTerraformBlock, configHasProviderBlock bool, tfVersion *version.Version) (string, error) { + var config strings.Builder + + // Prevent issues with existing configurations containing the terraform + // configuration block. + if configHasTerraformBlock { + config.WriteString(s.Config) + + return config.String(), nil + } + + if testCase.hasProviders(ctx) { + cfg, err := s.providerConfigTestCase(ctx, configHasProviderBlock, testCase, tfVersion) + + if err != nil { + return "", err + } + + config.WriteString(cfg) + } else { + cfg, err := s.providerConfig(ctx, configHasProviderBlock, tfVersion) + + if err != nil { + return "", err + } + + config.WriteString(cfg) + } + + config.WriteString(s.Config) + + return config.String(), nil +} + +// providerConfig takes the list of providers in a TestStep and returns a +// config with only empty provider blocks. This is useful for Import, where no +// config is provided, but the providers must be defined. +func (s TestStep) providerConfig(_ context.Context, skipProviderBlock bool, tfVersion *version.Version) (string, error) { + var providerBlocks, requiredProviderBlocks strings.Builder + + for name, externalProvider := range s.ExternalProviders { + if !skipProviderBlock { + providerBlocks.WriteString(fmt.Sprintf("provider %q {}\n", name)) + } + + if externalProvider.Source == "" && externalProvider.VersionConstraint == "" { + continue + } + + requiredProviderBlocks.WriteString(fmt.Sprintf(" %s = {\n", name)) + + if externalProvider.Source != "" { + requiredProviderBlocks.WriteString(fmt.Sprintf(" source = %q\n", externalProvider.Source)) + } + + if externalProvider.VersionConstraint != "" { + requiredProviderBlocks.WriteString(fmt.Sprintf(" version = %q\n", externalProvider.VersionConstraint)) + } + + requiredProviderBlocks.WriteString(" }\n") + } + + minReqVersion, err := version.NewVersion(tfBlockMinReqTFVersion) + + if err != nil { + return "", err + } + + for name := range s.ProviderFactories { + if tfVersion.LessThan(minReqVersion) { + break + } + + requiredProviderBlocks.WriteString(addTerraformBlockSource(name, s.Config)) + } + + for name := range s.ProtoV5ProviderFactories { + if tfVersion.LessThan(minReqVersion) { + break + } + + requiredProviderBlocks.WriteString(addTerraformBlockSource(name, s.Config)) + } + + for name := range s.ProtoV6ProviderFactories { + if tfVersion.LessThan(minReqVersion) { + break + } + + requiredProviderBlocks.WriteString(addTerraformBlockSource(name, s.Config)) + } + + if requiredProviderBlocks.Len() > 0 { + return fmt.Sprintf(` +terraform { + required_providers { +%[1]s + } +} + +%[2]s +`, strings.TrimSuffix(requiredProviderBlocks.String(), "\n"), providerBlocks.String()), nil + } + + return providerBlocks.String(), nil +} + +func (s TestStep) providerConfigTestCase(_ context.Context, skipProviderBlock bool, testCase TestCase, tfVersion *version.Version) (string, error) { + var providerBlocks, requiredProviderBlocks strings.Builder + + providerNames := make(map[string]struct{}, len(testCase.Providers)) + + for name := range testCase.Providers { + providerNames[name] = struct{}{} + } + + for name := range testCase.ProviderFactories { + delete(providerNames, name) + } + + // [BF] The Providers field handling predates the logic being moved to this + // method. It's not entirely clear to me at this time why this field + // is being used and not the others, but leaving it here just in case + // it does have a special purpose that wasn't being unit tested prior. + for name := range providerNames { + providerBlocks.WriteString(fmt.Sprintf("provider %q {}\n", name)) + + requiredProviderBlocks.WriteString(fmt.Sprintf(" %s = {\n", name)) + + requiredProviderBlocks.WriteString(" }\n") + } + + for name, externalProvider := range testCase.ExternalProviders { + if !skipProviderBlock { + providerBlocks.WriteString(fmt.Sprintf("provider %q {}\n", name)) + } + + if externalProvider.Source == "" && externalProvider.VersionConstraint == "" { + continue + } + + requiredProviderBlocks.WriteString(fmt.Sprintf(" %s = {\n", name)) + + if externalProvider.Source != "" { + requiredProviderBlocks.WriteString(fmt.Sprintf(" source = %q\n", externalProvider.Source)) + } + + if externalProvider.VersionConstraint != "" { + requiredProviderBlocks.WriteString(fmt.Sprintf(" version = %q\n", externalProvider.VersionConstraint)) + } + + requiredProviderBlocks.WriteString(" }\n") + } + + minReqVersion, err := version.NewVersion(tfBlockMinReqTFVersion) + + if err != nil { + return "", err + } + + for name := range testCase.ProviderFactories { + if tfVersion.LessThan(minReqVersion) { + break + } + + providerFactoryBlocks := addTerraformBlockSource(name, s.Config) + + if len(providerFactoryBlocks) > 0 { + requiredProviderBlocks.WriteString(providerFactoryBlocks) + } + } + + for name := range testCase.ProtoV5ProviderFactories { + if tfVersion.LessThan(minReqVersion) { + break + } + + protov5ProviderFactoryBlocks := addTerraformBlockSource(name, s.Config) + + if len(protov5ProviderFactoryBlocks) > 0 { + requiredProviderBlocks.WriteString(protov5ProviderFactoryBlocks) + } + } + + for name := range testCase.ProtoV6ProviderFactories { + if tfVersion.LessThan(minReqVersion) { + break + } + + protov6ProviderFactoryBlocks := addTerraformBlockSource(name, s.Config) + + if len(protov6ProviderFactoryBlocks) > 0 { + requiredProviderBlocks.WriteString(addTerraformBlockSource(name, s.Config)) + } + } + + if requiredProviderBlocks.Len() > 0 { + return fmt.Sprintf(` +terraform { + required_providers { +%[1]s + } +} + +%[2]s +`, strings.TrimSuffix(requiredProviderBlocks.String(), "\n"), providerBlocks.String()), nil + } + + return providerBlocks.String(), nil +} + +func addTerraformBlockSource(name, config string) string { + var js json.RawMessage + + // Do not process JSON. + if err := json.Unmarshal([]byte(config), &js); err == nil { + return "" + } + + var providerBlocks strings.Builder + + providerBlocks.WriteString(fmt.Sprintf(" %s = {\n", name)) + providerBlocks.WriteString(fmt.Sprintf(" source = %q\n", getProviderAddr(name))) + providerBlocks.WriteString(" }\n") + + return providerBlocks.String() +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/teststep_validate.go b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/teststep_validate.go new file mode 100644 index 0000000000..8590ca466a --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/teststep_validate.go @@ -0,0 +1,245 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package resource + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/internal/logging" + "github.com/hashicorp/terraform-plugin-testing/internal/teststep" +) + +// testStepValidateRequest contains data for the (TestStep).validate() method. +type testStepValidateRequest struct { + // StepConfiguration contains the TestStep configuration derived from + // TestStep.Config, TestStep.ConfigDirectory, or TestStep.ConfigFile. + StepConfiguration teststep.Config + + // StepNumber is the index of the TestStep in the TestCase.Steps. + StepNumber int + + // TestCaseHasExternalProviders is enabled if the TestCase has + // ExternalProviders. + TestCaseHasExternalProviders bool + + // TestCaseHasProviders is enabled if the TestCase has set any of + // ExternalProviders, ProtoV5ProviderFactories, ProtoV6ProviderFactories, + // or ProviderFactories. + TestCaseHasProviders bool + + // TestName is the name of the test. + TestName string +} + +// hasExternalProviders returns true if the TestStep has +// ExternalProviders set. +func (s TestStep) hasExternalProviders() bool { + return len(s.ExternalProviders) > 0 +} + +// hasProviders returns true if the TestStep has set any of the +// ExternalProviders, ProtoV5ProviderFactories, ProtoV6ProviderFactories, or +// ProviderFactories fields. It will also return true if ConfigDirectory or +// Config contain terraform configuration which specify a provider block. +func (s TestStep) hasProviders(ctx context.Context, stepIndex int, testName string) (bool, error) { + if len(s.ExternalProviders) > 0 { + return true, nil + } + + if len(s.ProtoV5ProviderFactories) > 0 { + return true, nil + } + + if len(s.ProtoV6ProviderFactories) > 0 { + return true, nil + } + + if len(s.ProviderFactories) > 0 { + return true, nil + } + + configRequest := teststep.PrepareConfigurationRequest{ + Directory: s.ConfigDirectory, + File: s.ConfigFile, + TestStepConfigRequest: config.TestStepConfigRequest{ + StepNumber: stepIndex + 1, + TestName: testName, + }, + }.Exec() + + cfg := teststep.Configuration(configRequest) + + var cfgHasProviders bool + + if cfg != nil { + var err error + + cfgHasProviders, err = cfg.HasProviderBlock(ctx) + + if err != nil { + return false, err + } + } + + if cfgHasProviders { + return true, nil + } + + return false, nil +} + +// validate ensures the TestStep is valid based on the following criteria: +// +// - Config or ImportState or RefreshState is set. +// - Config and RefreshState are not both set. +// - RefreshState and Destroy are not both set. +// - RefreshState is not the first TestStep. +// - Providers are not specified (ExternalProviders, +// ProtoV5ProviderFactories, ProtoV6ProviderFactories, ProviderFactories) +// if specified at the TestCase level. +// - Providers are specified (ExternalProviders, ProtoV5ProviderFactories, +// ProtoV6ProviderFactories, ProviderFactories) if not specified at the +// TestCase level. +// - No overlapping ExternalProviders and ProviderFactories entries +// - ResourceName is not empty when ImportState is true, ImportStateIdFunc +// is not set, and ImportStateId is not set. +// - ConfigPlanChecks (PreApply, PostApplyPreRefresh, PostApplyPostRefresh) are only set when Config is set. +// - ConfigPlanChecks.PreApply are only set when PlanOnly is false. +// - RefreshPlanChecks (PostRefresh) are only set when RefreshState is set. +func (s TestStep) validate(ctx context.Context, req testStepValidateRequest) error { + ctx = logging.TestStepNumberContext(ctx, req.StepNumber) + + logging.HelperResourceTrace(ctx, "Validating TestStep") + + if req.StepConfiguration == nil && !s.ImportState && !s.RefreshState { + err := fmt.Errorf("TestStep missing Config or ConfigDirectory or ConfigFile or ImportState or RefreshState") + logging.HelperResourceError(ctx, "TestStep validation error", map[string]interface{}{logging.KeyError: err}) + return err + } + + if req.StepConfiguration != nil && s.RefreshState { + err := fmt.Errorf("TestStep cannot have Config or ConfigDirectory or ConfigFile and RefreshState") + logging.HelperResourceError(ctx, "TestStep validation error", map[string]interface{}{logging.KeyError: err}) + return err + } + + if s.RefreshState && s.Destroy { + err := fmt.Errorf("TestStep cannot have RefreshState and Destroy") + logging.HelperResourceError(ctx, "TestStep validation error", map[string]interface{}{logging.KeyError: err}) + return err + } + + if s.RefreshState && req.StepNumber == 1 { + err := fmt.Errorf("TestStep cannot have RefreshState as first step") + logging.HelperResourceError(ctx, "TestStep validation error", map[string]interface{}{logging.KeyError: err}) + return err + } + + if s.ImportState && s.RefreshState { + err := fmt.Errorf("TestStep cannot have ImportState and RefreshState in same step") + logging.HelperResourceError(ctx, "TestStep validation error", map[string]interface{}{logging.KeyError: err}) + return err + } + + for name := range s.ExternalProviders { + if _, ok := s.ProviderFactories[name]; ok { + err := fmt.Errorf("TestStep provider %q set in both ExternalProviders and ProviderFactories", name) + logging.HelperResourceError(ctx, "TestStep validation error", map[string]interface{}{logging.KeyError: err}) + return err + } + } + + if req.TestCaseHasExternalProviders && req.StepConfiguration != nil && req.StepConfiguration.HasConfigurationFiles() { + err := fmt.Errorf("Providers must only be specified within the terraform configuration files when using TestStep.Config") + logging.HelperResourceError(ctx, "TestStep validation error", map[string]interface{}{logging.KeyError: err}) + return err + } + + if s.hasExternalProviders() && req.StepConfiguration != nil && req.StepConfiguration.HasConfigurationFiles() { + err := fmt.Errorf("Providers must only be specified within the terraform configuration files when using TestStep.Config") + logging.HelperResourceError(ctx, "TestStep validation error", map[string]interface{}{logging.KeyError: err}) + return err + } + + // We need a 0-based step index for consistency + hasProviders, err := s.hasProviders(ctx, req.StepNumber-1, req.TestName) + + if err != nil { + logging.HelperResourceError(ctx, "TestStep error checking for providers", map[string]interface{}{logging.KeyError: err}) + return err + } + + if req.TestCaseHasProviders && hasProviders { + err := fmt.Errorf("Providers must only be specified either at the TestCase or TestStep level") + logging.HelperResourceError(ctx, "TestStep validation error", map[string]interface{}{logging.KeyError: err}) + return err + } + + var cfgHasProviderBlock bool + + if req.StepConfiguration != nil { + cfgHasProviderBlock, err = req.StepConfiguration.HasProviderBlock(ctx) + + if err != nil { + logging.HelperResourceError(ctx, "TestStep error checking for if configuration has provider block", map[string]interface{}{logging.KeyError: err}) + return err + } + } + + if !req.TestCaseHasProviders && !hasProviders && !cfgHasProviderBlock { + err := fmt.Errorf("Providers must be specified at the TestCase level, or in all TestStep, or in TestStep.ConfigDirectory or TestStep.ConfigFile") + logging.HelperResourceError(ctx, "TestStep validation error", map[string]interface{}{logging.KeyError: err}) + return err + } + + if s.ImportState { + if s.ImportStateId == "" && s.ImportStateIdFunc == nil && s.ResourceName == "" { + err := fmt.Errorf("TestStep ImportState must be specified with ImportStateId, ImportStateIdFunc, or ResourceName") + logging.HelperResourceError(ctx, "TestStep validation error", map[string]interface{}{logging.KeyError: err}) + return err + } + } + + if len(s.ConfigPlanChecks.PreApply) > 0 { + if req.StepConfiguration == nil { + err := fmt.Errorf("TestStep ConfigPlanChecks.PreApply must only be specified with Config, ConfigDirectory or ConfigFile") + logging.HelperResourceError(ctx, "TestStep validation error", map[string]interface{}{logging.KeyError: err}) + return err + } + + if s.PlanOnly { + err := fmt.Errorf("TestStep ConfigPlanChecks.PreApply cannot be run with PlanOnly") + logging.HelperResourceError(ctx, "TestStep validation error", map[string]interface{}{logging.KeyError: err}) + return err + } + } + + if len(s.ConfigPlanChecks.PostApplyPreRefresh) > 0 && req.StepConfiguration == nil { + err := fmt.Errorf("TestStep ConfigPlanChecks.PostApplyPreRefresh must only be specified with Config, ConfigDirectory or ConfigFile") + logging.HelperResourceError(ctx, "TestStep validation error", map[string]interface{}{logging.KeyError: err}) + return err + } + + if len(s.ConfigPlanChecks.PostApplyPostRefresh) > 0 && req.StepConfiguration == nil { + err := fmt.Errorf("TestStep ConfigPlanChecks.PostApplyPostRefresh must only be specified with Config, ConfigDirectory or ConfigFile") + logging.HelperResourceError(ctx, "TestStep validation error", map[string]interface{}{logging.KeyError: err}) + return err + } + + if len(s.RefreshPlanChecks.PostRefresh) > 0 && !s.RefreshState { + err := fmt.Errorf("TestStep RefreshPlanChecks.PostRefresh must only be specified with RefreshState") + logging.HelperResourceError(ctx, "TestStep validation error", map[string]interface{}{logging.KeyError: err}) + return err + } + + if len(s.ConfigStateChecks) > 0 && req.StepConfiguration == nil { + err := fmt.Errorf("TestStep ConfigStateChecks must only be specified with Config, ConfigDirectory or ConfigFile") + logging.HelperResourceError(ctx, "TestStep validation error", map[string]interface{}{logging.KeyError: err}) + return err + } + + return nil +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/tfversion_checks.go b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/tfversion_checks.go new file mode 100644 index 0000000000..1bec0abd62 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/tfversion_checks.go @@ -0,0 +1,31 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package resource + +import ( + "context" + + "github.com/hashicorp/go-version" + "github.com/mitchellh/go-testing-interface" + + "github.com/hashicorp/terraform-plugin-testing/tfversion" +) + +func runTFVersionChecks(ctx context.Context, t testing.T, terraformVersion *version.Version, terraformVersionChecks []tfversion.TerraformVersionCheck) { + t.Helper() + + for _, tfVersionCheck := range terraformVersionChecks { + resp := tfversion.CheckTerraformVersionResponse{} + tfVersionCheck.CheckTerraformVersion(ctx, tfversion.CheckTerraformVersionRequest{TerraformVersion: terraformVersion}, &resp) + + if resp.Error != nil { + t.Fatalf(resp.Error.Error()) + } + + if resp.Skip != "" { + t.Skip(resp.Skip) + } + } + +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/wait.go b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/wait.go new file mode 100644 index 0000000000..332791bc91 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/helper/resource/wait.go @@ -0,0 +1,135 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package resource + +import ( + "context" + "errors" + "sync" + "time" +) + +// RetryContext is a basic wrapper around StateChangeConf that will just retry +// a function until it no longer returns an error. +// +// Cancellation from the passed in context will propagate through to the +// underlying StateChangeConf +// +// Deprecated: Copy this function to the provider codebase or use +// github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry.RetryContext. +func RetryContext(ctx context.Context, timeout time.Duration, f RetryFunc) error { + // These are used to pull the error out of the function; need a mutex to + // avoid a data race. + var resultErr error + var resultErrMu sync.Mutex + + c := &StateChangeConf{ + Pending: []string{"retryableerror"}, + Target: []string{"success"}, + Timeout: timeout, + MinTimeout: 500 * time.Millisecond, + Refresh: func() (interface{}, string, error) { + rerr := f() + + resultErrMu.Lock() + defer resultErrMu.Unlock() + + if rerr == nil { + resultErr = nil + return 42, "success", nil + } + + resultErr = rerr.Err + + if rerr.Retryable { + return 42, "retryableerror", nil + } + return nil, "quit", rerr.Err + }, + } + + _, waitErr := c.WaitForStateContext(ctx) + + // Need to acquire the lock here to be able to avoid race using resultErr as + // the return value + resultErrMu.Lock() + defer resultErrMu.Unlock() + + // resultErr may be nil because the wait timed out and resultErr was never + // set; this is still an error + if resultErr == nil { + return waitErr + } + // resultErr takes precedence over waitErr if both are set because it is + // more likely to be useful + return resultErr +} + +// Retry is a basic wrapper around StateChangeConf that will just retry +// a function until it no longer returns an error. +// +// Deprecated: Please use RetryContext to ensure proper plugin shutdown +func Retry(timeout time.Duration, f RetryFunc) error { + return RetryContext(context.Background(), timeout, f) +} + +// RetryFunc is the function retried until it succeeds. +// +// Deprecated: Copy this type to the provider codebase or use +// github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry.RetryFunc. +type RetryFunc func() *RetryError + +// RetryError is the required return type of RetryFunc. It forces client code +// to choose whether or not a given error is retryable. +// +// Deprecated: Copy this type to the provider codebase or use +// github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry.RetryError. +type RetryError struct { + Err error + Retryable bool +} + +// Unwrap returns the Err, compatible with errors.Unwrap. +// +// Deprecated: Copy this method to the provider codebase or use +// github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry.RetryError. +func (e *RetryError) Unwrap() error { + return e.Err +} + +// RetryableError is a helper to create a RetryError that's retryable from a +// given error. To prevent logic errors, will return an error when passed a +// nil error. +// +// Deprecated: Copy this function to the provider codebase or use +// github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry.RetryableError. +func RetryableError(err error) *RetryError { + if err == nil { + return &RetryError{ + Err: errors.New("empty retryable error received. " + + "This is a bug with the Terraform provider and should be " + + "reported as a GitHub issue in the provider repository."), + Retryable: false, + } + } + return &RetryError{Err: err, Retryable: true} +} + +// NonRetryableError is a helper to create a RetryError that's _not_ retryable +// from a given error. To prevent logic errors, will return an error when +// passed a nil error. +// +// Deprecated: Copy this function to the provider codebase or use +// github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry.NonRetryableError. +func NonRetryableError(err error) *RetryError { + if err == nil { + return &RetryError{ + Err: errors.New("empty non-retryable error received. " + + "This is a bug with the Terraform provider and should be " + + "reported as a GitHub issue in the provider repository."), + Retryable: false, + } + } + return &RetryError{Err: err, Retryable: false} +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/internal/addrs/doc.go b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/addrs/doc.go new file mode 100644 index 0000000000..0d29d9f456 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/addrs/doc.go @@ -0,0 +1,20 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// Package addrs contains types that represent "addresses", which are +// references to specific objects within a Terraform configuration or +// state. +// +// All addresses have string representations based on HCL traversal syntax +// which should be used in the user-interface, and also in-memory +// representations that can be used internally. +// +// For object types that exist within Terraform modules a pair of types is +// used. The "local" part of the address is represented by a type, and then +// an absolute path to that object in the context of its module is represented +// by a type of the same name with an "Abs" prefix added, for "absolute". +// +// All types within this package should be treated as immutable, even if this +// is not enforced by the Go compiler. It is always an implementation error +// to modify an address object in-place after it is initially constructed. +package addrs diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/internal/addrs/instance_key.go b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/addrs/instance_key.go new file mode 100644 index 0000000000..56700fc057 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/addrs/instance_key.go @@ -0,0 +1,50 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package addrs + +import ( + "fmt" +) + +// instanceKey represents the key of an instance within an object that +// contains multiple instances due to using "count" or "for_each" arguments +// in configuration. +// +// intKey and stringKey are the two implementations of this type. No other +// implementations are allowed. The single instance of an object that _isn't_ +// using "count" or "for_each" is represented by NoKey, which is a nil +// InstanceKey. +type instanceKey interface { + instanceKeySigil() + String() string +} + +// NoKey represents the absence of an instanceKey, for the single instance +// of a configuration object that does not use "count" or "for_each" at all. +var NoKey instanceKey + +// intKey is the InstanceKey representation representing integer indices, as +// used when the "count" argument is specified or if for_each is used with +// a sequence type. +type intKey int + +func (k intKey) instanceKeySigil() { +} + +func (k intKey) String() string { + return fmt.Sprintf("[%d]", int(k)) +} + +// stringKey is the InstanceKey representation representing string indices, as +// used when the "for_each" argument is specified with a map or object type. +type stringKey string + +func (k stringKey) instanceKeySigil() { +} + +func (k stringKey) String() string { + // FIXME: This isn't _quite_ right because Go's quoted string syntax is + // slightly different than HCL's, but we'll accept it for now. + return fmt.Sprintf("[%q]", string(k)) +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/internal/addrs/module.go b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/addrs/module.go new file mode 100644 index 0000000000..8dbbb469d4 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/addrs/module.go @@ -0,0 +1,16 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package addrs + +// Module is an address for a module call within configuration. This is +// the static counterpart of ModuleInstance, representing a traversal through +// the static module call tree in configuration and does not take into account +// the potentially-multiple instances of a module that might be created by +// "count" and "for_each" arguments within those calls. +// +// This type should be used only in very specialized cases when working with +// the static module call tree. Type ModuleInstance is appropriate in more cases. +// +// Although Module is a slice, it should be treated as immutable after creation. +type Module []string diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/internal/addrs/module_instance.go b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/addrs/module_instance.go new file mode 100644 index 0000000000..e43fd3e362 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/addrs/module_instance.go @@ -0,0 +1,242 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package addrs + +import ( + "bytes" + "fmt" + + "github.com/hashicorp/hcl/v2" + "github.com/hashicorp/hcl/v2/hclsyntax" + "github.com/zclconf/go-cty/cty" + "github.com/zclconf/go-cty/cty/gocty" + + "github.com/hashicorp/terraform-plugin-testing/internal/tfdiags" +) + +// ModuleInstance is an address for a particular module instance within the +// dynamic module tree. This is an extension of the static traversals +// represented by type Module that deals with the possibility of a single +// module call producing multiple instances via the "count" and "for_each" +// arguments. +// +// Although ModuleInstance is a slice, it should be treated as immutable after +// creation. +type ModuleInstance []ModuleInstanceStep + +func parseModuleInstance(traversal hcl.Traversal) (ModuleInstance, tfdiags.Diagnostics) { + mi, remain, diags := parseModuleInstancePrefix(traversal) + if len(remain) != 0 { + if len(remain) == len(traversal) { + diags = append(diags, tfdiags.Diag( + tfdiags.Error, + "Invalid module instance address", + "A module instance address must begin with \"module.\".", + )) + } else { + diags = append(diags, tfdiags.Diag( + tfdiags.Error, + "Invalid module instance address", + "The module instance address is followed by additional invalid content.", + )) + } + } + return mi, diags +} + +// ParseModuleInstanceStr is a helper wrapper around ParseModuleInstance +// that takes a string and parses it with the HCL native syntax traversal parser +// before interpreting it. +// +// This should be used only in specialized situations since it will cause the +// created references to not have any meaningful source location information. +// If a reference string is coming from a source that should be identified in +// error messages then the caller should instead parse it directly using a +// suitable function from the HCL API and pass the traversal itself to +// ParseProviderConfigCompact. +// +// Error diagnostics are returned if either the parsing fails or the analysis +// of the traversal fails. There is no way for the caller to distinguish the +// two kinds of diagnostics programmatically. If error diagnostics are returned +// then the returned address is invalid. +func ParseModuleInstanceStr(str string) (ModuleInstance, tfdiags.Diagnostics) { + var diags tfdiags.Diagnostics + + traversal, parseDiags := hclsyntax.ParseTraversalAbs([]byte(str), "", hcl.Pos{Line: 1, Column: 1}) + for _, err := range parseDiags.Errs() { + // ignore warnings, they don't matter in this case + diags = append(diags, tfdiags.FromError(err)) + } + if parseDiags.HasErrors() { + return nil, diags + } + + addr, addrDiags := parseModuleInstance(traversal) + diags = append(diags, addrDiags...) + return addr, diags +} + +func parseModuleInstancePrefix(traversal hcl.Traversal) (ModuleInstance, hcl.Traversal, tfdiags.Diagnostics) { + remain := traversal + var mi ModuleInstance + var diags tfdiags.Diagnostics + + for len(remain) > 0 { + var next string + switch tt := remain[0].(type) { + case hcl.TraverseRoot: + next = tt.Name + case hcl.TraverseAttr: + next = tt.Name + default: + diags = append(diags, tfdiags.Diag( + tfdiags.Error, + "Invalid address operator", + "Module address prefix must be followed by dot and then a name.", + )) + } + + if next != "module" { + break + } + + remain = remain[1:] + // If we have the prefix "module" then we should be followed by an + // module call name, as an attribute, and then optionally an index step + // giving the instance key. + if len(remain) == 0 { + diags = append(diags, tfdiags.Diag( + tfdiags.Error, + "Invalid address operator", + "Prefix \"module.\" must be followed by a module name.", + )) + break + } + + var moduleName string + switch tt := remain[0].(type) { + case hcl.TraverseAttr: + moduleName = tt.Name + default: + diags = append(diags, tfdiags.Diag( + tfdiags.Error, + "Invalid address operator", + "Prefix \"module.\" must be followed by a module name.", + )) + } + remain = remain[1:] + step := ModuleInstanceStep{ + Name: moduleName, + } + + if len(remain) > 0 { + if idx, ok := remain[0].(hcl.TraverseIndex); ok { + remain = remain[1:] + + switch idx.Key.Type() { + case cty.String: + step.InstanceKey = stringKey(idx.Key.AsString()) + case cty.Number: + var idxInt int + err := gocty.FromCtyValue(idx.Key, &idxInt) + if err == nil { + step.InstanceKey = intKey(idxInt) + } else { + diags = append(diags, tfdiags.Diag( + tfdiags.Error, + "Invalid address operator", + fmt.Sprintf("Invalid module index: %s.", err), + )) + } + default: + // Should never happen, because no other types are allowed in traversal indices. + diags = append(diags, tfdiags.Diag( + tfdiags.Error, + "Invalid address operator", + "Invalid module key: must be either a string or an integer.", + )) + } + } + } + + mi = append(mi, step) + } + + var retRemain hcl.Traversal + if len(remain) > 0 { + retRemain = make(hcl.Traversal, len(remain)) + copy(retRemain, remain) + // The first element here might be either a TraverseRoot or a + // TraverseAttr, depending on whether we had a module address on the + // front. To make life easier for callers, we'll normalize to always + // start with a TraverseRoot. + if tt, ok := retRemain[0].(hcl.TraverseAttr); ok { + retRemain[0] = hcl.TraverseRoot{ + Name: tt.Name, + SrcRange: tt.SrcRange, + } + } + } + + return mi, retRemain, diags +} + +// UnkeyedInstanceShim is a shim method for converting a Module address to the +// equivalent ModuleInstance address that assumes that no modules have +// keyed instances. +// +// This is a temporary allowance for the fact that Terraform does not presently +// support "count" and "for_each" on modules, and thus graph building code that +// derives graph nodes from configuration must just assume unkeyed modules +// in order to construct the graph. At a later time when "count" and "for_each" +// support is added for modules, all callers of this method will need to be +// reworked to allow for keyed module instances. +func (m Module) UnkeyedInstanceShim() ModuleInstance { + path := make(ModuleInstance, len(m)) + for i, name := range m { + path[i] = ModuleInstanceStep{Name: name} + } + return path +} + +// ModuleInstanceStep is a single traversal step through the dynamic module +// tree. It is used only as part of ModuleInstance. +type ModuleInstanceStep struct { + Name string + InstanceKey instanceKey +} + +// RootModuleInstance is the module instance address representing the root +// module, which is also the zero value of ModuleInstance. +var RootModuleInstance ModuleInstance + +// Child returns the address of a child module instance of the receiver, +// identified by the given name and key. +func (m ModuleInstance) Child(name string, key instanceKey) ModuleInstance { + ret := make(ModuleInstance, 0, len(m)+1) + ret = append(ret, m...) + return append(ret, ModuleInstanceStep{ + Name: name, + InstanceKey: key, + }) +} + +// String returns a string representation of the receiver, in the format used +// within e.g. user-provided resource addresses. +// +// The address of the root module has the empty string as its representation. +func (m ModuleInstance) String() string { + var buf bytes.Buffer + sep := "" + for _, step := range m { + buf.WriteString(sep) + buf.WriteString("module.") + buf.WriteString(step.Name) + if step.InstanceKey != NoKey { + buf.WriteString(step.InstanceKey.String()) + } + sep = "." + } + return buf.String() +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/internal/configs/configschema/coerce_value.go b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/configs/configschema/coerce_value.go new file mode 100644 index 0000000000..d12ff8cced --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/configs/configschema/coerce_value.go @@ -0,0 +1,253 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package configschema + +import ( + "fmt" + + "github.com/hashicorp/go-cty/cty" + "github.com/hashicorp/go-cty/cty/convert" +) + +// CoerceValue attempts to force the given value to conform to the type +// implied by the receiever. +// +// This is useful in situations where a configuration must be derived from +// an already-decoded value. It is always better to decode directly from +// configuration where possible since then source location information is +// still available to produce diagnostics, but in special situations this +// function allows a compatible result to be obtained even if the +// configuration objects are not available. +// +// If the given value cannot be converted to conform to the receiving schema +// then an error is returned describing one of possibly many problems. This +// error may be a cty.PathError indicating a position within the nested +// data structure where the problem applies. +func (b *Block) CoerceValue(in cty.Value) (cty.Value, error) { + var path cty.Path + return b.coerceValue(in, path) +} + +func (b *Block) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) { + switch { + case in.IsNull(): + return cty.NullVal(b.ImpliedType()), nil + case !in.IsKnown(): + return cty.UnknownVal(b.ImpliedType()), nil + } + + ty := in.Type() + if !ty.IsObjectType() { + return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("an object is required") + } + + for name := range ty.AttributeTypes() { + if _, defined := b.Attributes[name]; defined { + continue + } + if _, defined := b.BlockTypes[name]; defined { + continue + } + return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("unexpected attribute %q", name) + } + + attrs := make(map[string]cty.Value) + + for name, attrS := range b.Attributes { + var val cty.Value + switch { + case ty.HasAttribute(name): + val = in.GetAttr(name) + case attrS.Computed || attrS.Optional: + val = cty.NullVal(attrS.Type) + default: + return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("attribute %q is required", name) + } + + val, err := attrS.coerceValue(val, append(path, cty.GetAttrStep{Name: name})) + if err != nil { + return cty.UnknownVal(b.ImpliedType()), err + } + + attrs[name] = val + } + for typeName, blockS := range b.BlockTypes { + switch blockS.Nesting { + + case NestingSingle, NestingGroup: + switch { + case ty.HasAttribute(typeName): + var err error + val := in.GetAttr(typeName) + attrs[typeName], err = blockS.coerceValue(val, append(path, cty.GetAttrStep{Name: typeName})) + if err != nil { + return cty.UnknownVal(b.ImpliedType()), err + } + default: + attrs[typeName] = blockS.EmptyValue() + } + + case NestingList: + switch { + case ty.HasAttribute(typeName): + coll := in.GetAttr(typeName) + + switch { + case coll.IsNull(): + attrs[typeName] = cty.NullVal(cty.List(blockS.ImpliedType())) + continue + case !coll.IsKnown(): + attrs[typeName] = cty.UnknownVal(cty.List(blockS.ImpliedType())) + continue + } + + if !coll.CanIterateElements() { + return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("must be a list") + } + l := coll.LengthInt() + + if l == 0 { + attrs[typeName] = cty.ListValEmpty(blockS.ImpliedType()) + continue + } + elems := make([]cty.Value, 0, l) + { + path = append(path, cty.GetAttrStep{Name: typeName}) + for it := coll.ElementIterator(); it.Next(); { + var err error + idx, val := it.Element() + val, err = blockS.coerceValue(val, append(path, cty.IndexStep{Key: idx})) + if err != nil { + return cty.UnknownVal(b.ImpliedType()), err + } + elems = append(elems, val) + } + } + attrs[typeName] = cty.ListVal(elems) + default: + attrs[typeName] = cty.ListValEmpty(blockS.ImpliedType()) + } + + case NestingSet: + switch { + case ty.HasAttribute(typeName): + coll := in.GetAttr(typeName) + + switch { + case coll.IsNull(): + attrs[typeName] = cty.NullVal(cty.Set(blockS.ImpliedType())) + continue + case !coll.IsKnown(): + attrs[typeName] = cty.UnknownVal(cty.Set(blockS.ImpliedType())) + continue + } + + if !coll.CanIterateElements() { + return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("must be a set") + } + l := coll.LengthInt() + + if l == 0 { + attrs[typeName] = cty.SetValEmpty(blockS.ImpliedType()) + continue + } + elems := make([]cty.Value, 0, l) + { + path = append(path, cty.GetAttrStep{Name: typeName}) + for it := coll.ElementIterator(); it.Next(); { + var err error + idx, val := it.Element() + val, err = blockS.coerceValue(val, append(path, cty.IndexStep{Key: idx})) + if err != nil { + return cty.UnknownVal(b.ImpliedType()), err + } + elems = append(elems, val) + } + } + attrs[typeName] = cty.SetVal(elems) + default: + attrs[typeName] = cty.SetValEmpty(blockS.ImpliedType()) + } + + case NestingMap: + switch { + case ty.HasAttribute(typeName): + coll := in.GetAttr(typeName) + + switch { + case coll.IsNull(): + attrs[typeName] = cty.NullVal(cty.Map(blockS.ImpliedType())) + continue + case !coll.IsKnown(): + attrs[typeName] = cty.UnknownVal(cty.Map(blockS.ImpliedType())) + continue + } + + if !coll.CanIterateElements() { + return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("must be a map") + } + l := coll.LengthInt() + if l == 0 { + attrs[typeName] = cty.MapValEmpty(blockS.ImpliedType()) + continue + } + elems := make(map[string]cty.Value) + { + path = append(path, cty.GetAttrStep{Name: typeName}) + for it := coll.ElementIterator(); it.Next(); { + var err error + key, val := it.Element() + if key.Type() != cty.String || key.IsNull() || !key.IsKnown() { + return cty.UnknownVal(b.ImpliedType()), path.NewErrorf("must be a map") + } + val, err = blockS.coerceValue(val, append(path, cty.IndexStep{Key: key})) + if err != nil { + return cty.UnknownVal(b.ImpliedType()), err + } + elems[key.AsString()] = val + } + } + + // If the attribute values here contain any DynamicPseudoTypes, + // the concrete type must be an object. + useObject := false + switch { + case coll.Type().IsObjectType(): + useObject = true + default: + // It's possible that we were given a map, and need to coerce it to an object + ety := coll.Type().ElementType() + for _, v := range elems { + if !v.Type().Equals(ety) { + useObject = true + break + } + } + } + + if useObject { + attrs[typeName] = cty.ObjectVal(elems) + } else { + attrs[typeName] = cty.MapVal(elems) + } + default: + attrs[typeName] = cty.MapValEmpty(blockS.ImpliedType()) + } + + default: + // should never happen because above is exhaustive + panic(fmt.Errorf("unsupported nesting mode %#v", blockS.Nesting)) + } + } + + return cty.ObjectVal(attrs), nil +} + +func (a *Attribute) coerceValue(in cty.Value, path cty.Path) (cty.Value, error) { + val, err := convert.Convert(in, a.Type) + if err != nil { + return cty.UnknownVal(a.Type), path.NewError(err) + } + return val, nil +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/internal/configs/configschema/doc.go b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/configs/configschema/doc.go new file mode 100644 index 0000000000..d96be9c7f0 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/configs/configschema/doc.go @@ -0,0 +1,17 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// Package configschema contains types for describing the expected structure +// of a configuration block whose shape is not known until runtime. +// +// For example, this is used to describe the expected contents of a resource +// configuration block, which is defined by the corresponding provider plugin +// and thus not compiled into Terraform core. +// +// A configschema primarily describes the shape of configuration, but it is +// also suitable for use with other structures derived from the configuration, +// such as the cached state of a resource or a resource diff. +// +// This package should not be confused with the package helper/schema, which +// is the higher-level helper library used to implement providers themselves. +package configschema diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/internal/configs/configschema/empty_value.go b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/configs/configschema/empty_value.go new file mode 100644 index 0000000000..cc1107fa0b --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/configs/configschema/empty_value.go @@ -0,0 +1,62 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package configschema + +import ( + "github.com/hashicorp/go-cty/cty" +) + +// EmptyValue returns the "empty value" for the receiving block, which for +// a block type is a non-null object where all of the attribute values are +// the empty values of the block's attributes and nested block types. +// +// In other words, it returns the value that would be returned if an empty +// block were decoded against the receiving schema, assuming that no required +// attribute or block constraints were honored. +func (b *Block) EmptyValue() cty.Value { + vals := make(map[string]cty.Value) + for name, attrS := range b.Attributes { + vals[name] = attrS.EmptyValue() + } + for name, blockS := range b.BlockTypes { + vals[name] = blockS.EmptyValue() + } + return cty.ObjectVal(vals) +} + +// EmptyValue returns the "empty value" for the receiving attribute, which is +// the value that would be returned if there were no definition of the attribute +// at all, ignoring any required constraint. +func (a *Attribute) EmptyValue() cty.Value { + return cty.NullVal(a.Type) +} + +// EmptyValue returns the "empty value" for when there are zero nested blocks +// present of the receiving type. +func (b *NestedBlock) EmptyValue() cty.Value { + switch b.Nesting { + case NestingSingle: + return cty.NullVal(b.Block.ImpliedType()) + case NestingGroup: + return b.Block.EmptyValue() + case NestingList: + if ty := b.Block.ImpliedType(); ty.HasDynamicTypes() { + return cty.EmptyTupleVal + } else { + return cty.ListValEmpty(ty) + } + case NestingMap: + if ty := b.Block.ImpliedType(); ty.HasDynamicTypes() { + return cty.EmptyObjectVal + } else { + return cty.MapValEmpty(ty) + } + case NestingSet: + return cty.SetValEmpty(b.Block.ImpliedType()) + default: + // Should never get here because the above is intended to be exhaustive, + // but we'll be robust and return a result nonetheless. + return cty.NullVal(cty.DynamicPseudoType) + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/internal/configs/configschema/implied_type.go b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/configs/configschema/implied_type.go new file mode 100644 index 0000000000..4de413519f --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/configs/configschema/implied_type.go @@ -0,0 +1,71 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package configschema + +import ( + "github.com/hashicorp/go-cty/cty" +) + +// ImpliedType returns the cty.Type that would result from decoding a +// configuration block using the receiving block schema. +// +// ImpliedType always returns a result, even if the given schema is +// inconsistent. +func (b *Block) ImpliedType() cty.Type { + if b == nil { + return cty.EmptyObject + } + + atys := make(map[string]cty.Type) + + for name, attrS := range b.Attributes { + atys[name] = attrS.Type + } + + for name, blockS := range b.BlockTypes { + if _, exists := atys[name]; exists { + panic("invalid schema, blocks and attributes cannot have the same name") + } + + childType := blockS.Block.ImpliedType() + + switch blockS.Nesting { + case NestingSingle, NestingGroup: + atys[name] = childType + case NestingList: + // We prefer to use a list where possible, since it makes our + // implied type more complete, but if there are any + // dynamically-typed attributes inside we must use a tuple + // instead, which means our type _constraint_ must be + // cty.DynamicPseudoType to allow the tuple type to be decided + // separately for each value. + if childType.HasDynamicTypes() { + atys[name] = cty.DynamicPseudoType + } else { + atys[name] = cty.List(childType) + } + case NestingSet: + if childType.HasDynamicTypes() { + panic("can't use cty.DynamicPseudoType inside a block type with NestingSet") + } + atys[name] = cty.Set(childType) + case NestingMap: + // We prefer to use a map where possible, since it makes our + // implied type more complete, but if there are any + // dynamically-typed attributes inside we must use an object + // instead, which means our type _constraint_ must be + // cty.DynamicPseudoType to allow the tuple type to be decided + // separately for each value. + if childType.HasDynamicTypes() { + atys[name] = cty.DynamicPseudoType + } else { + atys[name] = cty.Map(childType) + } + default: + panic("invalid nesting type") + } + } + + return cty.Object(atys) +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/internal/configs/configschema/nestingmode_string.go b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/configs/configschema/nestingmode_string.go new file mode 100644 index 0000000000..febe743e11 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/configs/configschema/nestingmode_string.go @@ -0,0 +1,28 @@ +// Code generated by "stringer -type=NestingMode"; DO NOT EDIT. + +package configschema + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[nestingModeInvalid-0] + _ = x[NestingSingle-1] + _ = x[NestingGroup-2] + _ = x[NestingList-3] + _ = x[NestingSet-4] + _ = x[NestingMap-5] +} + +const _NestingMode_name = "nestingModeInvalidNestingSingleNestingGroupNestingListNestingSetNestingMap" + +var _NestingMode_index = [...]uint8{0, 18, 31, 43, 54, 64, 74} + +func (i NestingMode) String() string { + if i < 0 || i >= NestingMode(len(_NestingMode_index)-1) { + return "NestingMode(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _NestingMode_name[_NestingMode_index[i]:_NestingMode_index[i+1]] +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/internal/configs/configschema/schema.go b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/configs/configschema/schema.go new file mode 100644 index 0000000000..fafe3fa91c --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/configs/configschema/schema.go @@ -0,0 +1,161 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package configschema + +import ( + "github.com/hashicorp/go-cty/cty" +) + +// StringKind represents the format a string is in. +type StringKind int + +const ( + // StringPlain indicates a string is plain-text and requires no processing for display. + StringPlain StringKind = iota + // StringMarkdown indicates a string is in markdown format and may + // require additional processing to display. + StringMarkdown +) + +// Block represents a configuration block. +// +// "Block" here is a logical grouping construct, though it happens to map +// directly onto the physical block syntax of Terraform's native configuration +// syntax. It may be a more a matter of convention in other syntaxes, such as +// JSON. +// +// When converted to a value, a Block always becomes an instance of an object +// type derived from its defined attributes and nested blocks +type Block struct { + // Attributes describes any attributes that may appear directly inside + // the block. + Attributes map[string]*Attribute + + // BlockTypes describes any nested block types that may appear directly + // inside the block. + BlockTypes map[string]*NestedBlock + + // Description and DescriptionKind contain a user facing description of the block + // and the format of that string. + Description string + DescriptionKind StringKind + + // Deprecated indicates whether the block has been marked as deprecated in the + // provider and usage should be discouraged. + Deprecated bool +} + +// Attribute represents a configuration attribute, within a block. +type Attribute struct { + // Type is a type specification that the attribute's value must conform to. + Type cty.Type + + // Description is an English-language description of the purpose and + // usage of the attribute. A description should be concise and use only + // one or two sentences, leaving full definition to longer-form + // documentation defined elsewhere. + Description string + DescriptionKind StringKind + + // Required, if set to true, specifies that an omitted or null value is + // not permitted. + Required bool + + // Optional, if set to true, specifies that an omitted or null value is + // permitted. This field conflicts with Required. + Optional bool + + // Computed, if set to true, specifies that the value comes from the + // provider rather than from configuration. If combined with Optional, + // then the config may optionally provide an overridden value. + Computed bool + + // Sensitive, if set to true, indicates that an attribute may contain + // sensitive information. + // + // At present nothing is done with this information, but callers are + // encouraged to set it where appropriate so that it may be used in the + // future to help Terraform mask sensitive information. (Terraform + // currently achieves this in a limited sense via other mechanisms.) + Sensitive bool + + // Deprecated indicates whether the attribute has been marked as deprecated in the + // provider and usage should be discouraged. + Deprecated bool +} + +// NestedBlock represents the embedding of one block within another. +type NestedBlock struct { + // Block is the description of the block that's nested. + Block + + // Nesting provides the nesting mode for the child block, which determines + // how many instances of the block are allowed, how many labels it expects, + // and how the resulting data will be converted into a data structure. + Nesting NestingMode + + // MinItems and MaxItems set, for the NestingList and NestingSet nesting + // modes, lower and upper limits on the number of child blocks allowed + // of the given type. If both are left at zero, no limit is applied. + // + // As a special case, both values can be set to 1 for NestingSingle in + // order to indicate that a particular single block is required. + // + // These fields are ignored for other nesting modes and must both be left + // at zero. + MinItems, MaxItems int +} + +// NestingMode is an enumeration of modes for nesting blocks inside other +// blocks. +type NestingMode int + +// This code was previously generated with a go:generate directive calling: +// go run golang.org/x/tools/cmd/stringer -type=NestingMode +// However, it is now considered frozen and the tooling dependency has been +// removed. The String method can be manually updated if necessary. + +const ( + nestingModeInvalid NestingMode = iota + + // NestingSingle indicates that only a single instance of a given + // block type is permitted, with no labels, and its content should be + // provided directly as an object value. + NestingSingle + + // NestingGroup is similar to NestingSingle in that it calls for only a + // single instance of a given block type with no labels, but it additionally + // guarantees that its result will never be null, even if the block is + // absent, and instead the nested attributes and blocks will be treated + // as absent in that case. (Any required attributes or blocks within the + // nested block are not enforced unless the block is explicitly present + // in the configuration, so they are all effectively optional when the + // block is not present.) + // + // This is useful for the situation where a remote API has a feature that + // is always enabled but has a group of settings related to that feature + // that themselves have default values. By using NestingGroup instead of + // NestingSingle in that case, generated plans will show the block as + // present even when not present in configuration, thus allowing any + // default values within to be displayed to the user. + NestingGroup + + // NestingList indicates that multiple blocks of the given type are + // permitted, with no labels, and that their corresponding objects should + // be provided in a list. + NestingList + + // NestingSet indicates that multiple blocks of the given type are + // permitted, with no labels, and that their corresponding objects should + // be provided in a set. + NestingSet + + // NestingMap indicates that multiple blocks of the given type are + // permitted, each with a single label, and that their corresponding + // objects should be provided in a map whose keys are the labels. + // + // It's an error, therefore, to use the same label value on multiple + // blocks. + NestingMap +) diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/internal/configs/hcl2shim/flatmap.go b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/configs/hcl2shim/flatmap.go new file mode 100644 index 0000000000..2bad034de9 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/configs/hcl2shim/flatmap.go @@ -0,0 +1,426 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package hcl2shim + +import ( + "fmt" + "strconv" + "strings" + + "github.com/hashicorp/go-cty/cty" + "github.com/hashicorp/go-cty/cty/convert" +) + +// FlatmapValueFromHCL2 converts a value from HCL2 (really, from the cty dynamic +// types library that HCL2 uses) to a map compatible with what would be +// produced by the "flatmap" package. +// +// The type of the given value informs the structure of the resulting map. +// The value must be of an object type or this function will panic. +// +// Flatmap values can only represent maps when they are of primitive types, +// so the given value must not have any maps of complex types or the result +// is undefined. +func FlatmapValueFromHCL2(v cty.Value) map[string]string { + if v.IsNull() { + return nil + } + + if !v.Type().IsObjectType() { + panic(fmt.Sprintf("HCL2ValueFromFlatmap called on %#v", v.Type())) + } + + m := make(map[string]string) + flatmapValueFromHCL2Map(m, "", v) + return m +} + +func flatmapValueFromHCL2Value(m map[string]string, key string, val cty.Value) { + ty := val.Type() + switch { + case ty.IsPrimitiveType() || ty == cty.DynamicPseudoType: + flatmapValueFromHCL2Primitive(m, key, val) + case ty.IsObjectType() || ty.IsMapType(): + flatmapValueFromHCL2Map(m, key+".", val) + case ty.IsTupleType() || ty.IsListType() || ty.IsSetType(): + flatmapValueFromHCL2Seq(m, key+".", val) + default: + panic(fmt.Sprintf("cannot encode %s to flatmap", ty.FriendlyName())) + } +} + +func flatmapValueFromHCL2Primitive(m map[string]string, key string, val cty.Value) { + if !val.IsKnown() { + m[key] = UnknownVariableValue + return + } + if val.IsNull() { + // Omit entirely + return + } + + var err error + val, err = convert.Convert(val, cty.String) + if err != nil { + // Should not be possible, since all primitive types can convert to string. + panic(fmt.Sprintf("invalid primitive encoding to flatmap: %s", err)) + } + m[key] = val.AsString() +} + +func flatmapValueFromHCL2Map(m map[string]string, prefix string, val cty.Value) { + if val.IsNull() { + // Omit entirely + return + } + if !val.IsKnown() { + switch { + case val.Type().IsObjectType(): + // Whole objects can't be unknown in flatmap, so instead we'll + // just write all of the attribute values out as unknown. + for name, aty := range val.Type().AttributeTypes() { + flatmapValueFromHCL2Value(m, prefix+name, cty.UnknownVal(aty)) + } + default: + m[prefix+"%"] = UnknownVariableValue + } + return + } + + valLen := 0 + for it := val.ElementIterator(); it.Next(); { + ak, av := it.Element() + name := ak.AsString() + flatmapValueFromHCL2Value(m, prefix+name, av) + valLen++ + } + if !val.Type().IsObjectType() { // objects don't have an explicit count included, since their attribute count is fixed + m[prefix+"%"] = strconv.Itoa(valLen) + } +} + +func flatmapValueFromHCL2Seq(m map[string]string, prefix string, val cty.Value) { + if val.IsNull() { + // Omit entirely + return + } + if !val.IsKnown() { + m[prefix+"#"] = UnknownVariableValue + return + } + + // For sets this won't actually generate exactly what helper/schema would've + // generated, because we don't have access to the set key function it + // would've used. However, in practice it doesn't actually matter what the + // keys are as long as they are unique, so we'll just generate sequential + // indexes for them as if it were a list. + // + // An important implication of this, however, is that the set ordering will + // not be consistent across mutations and so different keys may be assigned + // to the same value when round-tripping. Since this shim is intended to + // be short-lived and not used for round-tripping, we accept this. + i := 0 + for it := val.ElementIterator(); it.Next(); { + _, av := it.Element() + key := prefix + strconv.Itoa(i) + flatmapValueFromHCL2Value(m, key, av) + i++ + } + m[prefix+"#"] = strconv.Itoa(i) +} + +// HCL2ValueFromFlatmap converts a map compatible with what would be produced +// by the "flatmap" package to a HCL2 (really, the cty dynamic types library +// that HCL2 uses) object type. +// +// The intended result type must be provided in order to guide how the +// map contents are decoded. This must be an object type or this function +// will panic. +// +// Flatmap values can only represent maps when they are of primitive types, +// so the given type must not have any maps of complex types or the result +// is undefined. +// +// The result may contain null values if the given map does not contain keys +// for all of the different key paths implied by the given type. +func HCL2ValueFromFlatmap(m map[string]string, ty cty.Type) (cty.Value, error) { + if m == nil { + return cty.NullVal(ty), nil + } + if !ty.IsObjectType() { + panic(fmt.Sprintf("HCL2ValueFromFlatmap called on %#v", ty)) + } + + return hcl2ValueFromFlatmapObject(m, "", ty.AttributeTypes()) +} + +func hcl2ValueFromFlatmapValue(m map[string]string, key string, ty cty.Type) (cty.Value, error) { + var val cty.Value + var err error + switch { + case ty.IsPrimitiveType(): + val, err = hcl2ValueFromFlatmapPrimitive(m, key, ty) + case ty.IsObjectType(): + val, err = hcl2ValueFromFlatmapObject(m, key+".", ty.AttributeTypes()) + case ty.IsTupleType(): + val, err = hcl2ValueFromFlatmapTuple(m, key+".", ty.TupleElementTypes()) + case ty.IsMapType(): + val, err = hcl2ValueFromFlatmapMap(m, key+".", ty) + case ty.IsListType(): + val, err = hcl2ValueFromFlatmapList(m, key+".", ty) + case ty.IsSetType(): + val, err = hcl2ValueFromFlatmapSet(m, key+".", ty) + default: + err = fmt.Errorf("cannot decode %s from flatmap", ty.FriendlyName()) + } + + if err != nil { + return cty.DynamicVal, err + } + return val, nil +} + +func hcl2ValueFromFlatmapPrimitive(m map[string]string, key string, ty cty.Type) (cty.Value, error) { + rawVal, exists := m[key] + if !exists { + return cty.NullVal(ty), nil + } + if rawVal == UnknownVariableValue { + return cty.UnknownVal(ty), nil + } + + var err error + val := cty.StringVal(rawVal) + val, err = convert.Convert(val, ty) + if err != nil { + // This should never happen for _valid_ input, but flatmap data might + // be tampered with by the user and become invalid. + return cty.DynamicVal, fmt.Errorf("invalid value for %q in state: %s", key, err) + } + + return val, nil +} + +func hcl2ValueFromFlatmapObject(m map[string]string, prefix string, atys map[string]cty.Type) (cty.Value, error) { + vals := make(map[string]cty.Value) + for name, aty := range atys { + val, err := hcl2ValueFromFlatmapValue(m, prefix+name, aty) + if err != nil { + return cty.DynamicVal, err + } + vals[name] = val + } + return cty.ObjectVal(vals), nil +} + +func hcl2ValueFromFlatmapTuple(m map[string]string, prefix string, etys []cty.Type) (cty.Value, error) { + var vals []cty.Value + + // if the container is unknown, there is no count string + listName := strings.TrimRight(prefix, ".") + if m[listName] == UnknownVariableValue { + return cty.UnknownVal(cty.Tuple(etys)), nil + } + + countStr, exists := m[prefix+"#"] + if !exists { + return cty.NullVal(cty.Tuple(etys)), nil + } + if countStr == UnknownVariableValue { + return cty.UnknownVal(cty.Tuple(etys)), nil + } + + count, err := strconv.Atoi(countStr) + if err != nil { + return cty.DynamicVal, fmt.Errorf("invalid count value for %q in state: %s", prefix, err) + } + if count != len(etys) { + return cty.DynamicVal, fmt.Errorf("wrong number of values for %q in state: got %d, but need %d", prefix, count, len(etys)) + } + + vals = make([]cty.Value, len(etys)) + for i, ety := range etys { + key := prefix + strconv.Itoa(i) + val, err := hcl2ValueFromFlatmapValue(m, key, ety) + if err != nil { + return cty.DynamicVal, err + } + vals[i] = val + } + return cty.TupleVal(vals), nil +} + +func hcl2ValueFromFlatmapMap(m map[string]string, prefix string, ty cty.Type) (cty.Value, error) { + vals := make(map[string]cty.Value) + ety := ty.ElementType() + + // if the container is unknown, there is no count string + listName := strings.TrimRight(prefix, ".") + if m[listName] == UnknownVariableValue { + return cty.UnknownVal(ty), nil + } + + // We actually don't really care about the "count" of a map for our + // purposes here, but we do need to check if it _exists_ in order to + // recognize the difference between null (not set at all) and empty. + if strCount, exists := m[prefix+"%"]; !exists { + return cty.NullVal(ty), nil + } else if strCount == UnknownVariableValue { + return cty.UnknownVal(ty), nil + } + + for fullKey := range m { + if !strings.HasPrefix(fullKey, prefix) { + continue + } + + // The flatmap format doesn't allow us to distinguish between keys + // that contain periods and nested objects, so by convention a + // map is only ever of primitive type in flatmap, and we just assume + // that the remainder of the raw key (dots and all) is the key we + // want in the result value. + key := fullKey[len(prefix):] + if key == "%" { + // Ignore the "count" key + continue + } + + val, err := hcl2ValueFromFlatmapValue(m, fullKey, ety) + if err != nil { + return cty.DynamicVal, err + } + vals[key] = val + } + + if len(vals) == 0 { + return cty.MapValEmpty(ety), nil + } + return cty.MapVal(vals), nil +} + +func hcl2ValueFromFlatmapList(m map[string]string, prefix string, ty cty.Type) (cty.Value, error) { + var vals []cty.Value + + // if the container is unknown, there is no count string + listName := strings.TrimRight(prefix, ".") + if m[listName] == UnknownVariableValue { + return cty.UnknownVal(ty), nil + } + + countStr, exists := m[prefix+"#"] + if !exists { + return cty.NullVal(ty), nil + } + if countStr == UnknownVariableValue { + return cty.UnknownVal(ty), nil + } + + count, err := strconv.Atoi(countStr) + if err != nil { + return cty.DynamicVal, fmt.Errorf("invalid count value for %q in state: %s", prefix, err) + } + + ety := ty.ElementType() + if count == 0 { + return cty.ListValEmpty(ety), nil + } + + vals = make([]cty.Value, count) + for i := 0; i < count; i++ { + key := prefix + strconv.Itoa(i) + val, err := hcl2ValueFromFlatmapValue(m, key, ety) + if err != nil { + return cty.DynamicVal, err + } + vals[i] = val + } + + return cty.ListVal(vals), nil +} + +func hcl2ValueFromFlatmapSet(m map[string]string, prefix string, ty cty.Type) (cty.Value, error) { + var vals []cty.Value + ety := ty.ElementType() + + // if the container is unknown, there is no count string + listName := strings.TrimRight(prefix, ".") + if m[listName] == UnknownVariableValue { + return cty.UnknownVal(ty), nil + } + + strCount, exists := m[prefix+"#"] + if !exists { + return cty.NullVal(ty), nil + } else if strCount == UnknownVariableValue { + return cty.UnknownVal(ty), nil + } + + // Keep track of keys we've seen, se we don't add the same set value + // multiple times. The cty.Set will normally de-duplicate values, but we may + // have unknown values that would not show as equivalent. + seen := map[string]bool{} + + for fullKey := range m { + if !strings.HasPrefix(fullKey, prefix) { + continue + } + subKey := fullKey[len(prefix):] + if subKey == "#" { + // Ignore the "count" key + continue + } + key := fullKey + if dot := strings.IndexByte(subKey, '.'); dot != -1 { + key = fullKey[:dot+len(prefix)] + } + + if seen[key] { + continue + } + + seen[key] = true + + // The flatmap format doesn't allow us to distinguish between keys + // that contain periods and nested objects, so by convention a + // map is only ever of primitive type in flatmap, and we just assume + // that the remainder of the raw key (dots and all) is the key we + // want in the result value. + + val, err := hcl2ValueFromFlatmapValue(m, key, ety) + if err != nil { + return cty.DynamicVal, err + } + vals = append(vals, val) + } + + if len(vals) == 0 && strCount == "1" { + // An empty set wouldn't be represented in the flatmap, so this must be + // a single empty object since the count is actually 1. + // Add an appropriately typed null value to the set. + var val cty.Value + switch { + case ety.IsMapType(): + val = cty.MapValEmpty(ety) + case ety.IsListType(): + val = cty.ListValEmpty(ety) + case ety.IsSetType(): + val = cty.SetValEmpty(ety) + case ety.IsObjectType(): + // TODO: cty.ObjectValEmpty + objectMap := map[string]cty.Value{} + for attr, ty := range ety.AttributeTypes() { + objectMap[attr] = cty.NullVal(ty) + } + val = cty.ObjectVal(objectMap) + default: + val = cty.NullVal(ety) + } + vals = append(vals, val) + + } else if len(vals) == 0 { + return cty.SetValEmpty(ety), nil + } + + return cty.SetVal(vals), nil +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/internal/configs/hcl2shim/paths.go b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/configs/hcl2shim/paths.go new file mode 100644 index 0000000000..628a8bf686 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/configs/hcl2shim/paths.go @@ -0,0 +1,279 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package hcl2shim + +import ( + "fmt" + "reflect" + "strconv" + "strings" + + "github.com/hashicorp/go-cty/cty" +) + +// RequiresReplace takes a list of flatmapped paths from a +// InstanceDiff.Attributes along with the corresponding cty.Type, and returns +// the list of the cty.Paths that are flagged as causing the resource +// replacement (RequiresNew). +// This will filter out redundant paths, paths that refer to flatmapped indexes +// (e.g. "#", "%"), and will return any changes within a set as the path to the +// set itself. +func RequiresReplace(attrs []string, ty cty.Type) ([]cty.Path, error) { + var paths []cty.Path + + for _, attr := range attrs { + p, err := requiresReplacePath(attr, ty) + if err != nil { + return nil, err + } + + paths = append(paths, p) + } + + // now trim off any trailing paths that aren't GetAttrSteps, since only an + // attribute itself can require replacement + paths = trimPaths(paths) + + // There may be redundant paths due to set elements or index attributes + // Do some ugly n^2 filtering, but these are always fairly small sets. + for i := 0; i < len(paths)-1; i++ { + for j := i + 1; j < len(paths); j++ { + if reflect.DeepEqual(paths[i], paths[j]) { + // swap the tail and slice it off + paths[j], paths[len(paths)-1] = paths[len(paths)-1], paths[j] + paths = paths[:len(paths)-1] + j-- + } + } + } + + return paths, nil +} + +// trimPaths removes any trailing steps that aren't of type GetAttrSet, since +// only an attribute itself can require replacement +func trimPaths(paths []cty.Path) []cty.Path { + var trimmed []cty.Path + for _, path := range paths { + path = trimPath(path) + if len(path) > 0 { + trimmed = append(trimmed, path) + } + } + return trimmed +} + +func trimPath(path cty.Path) cty.Path { + for len(path) > 0 { + _, isGetAttr := path[len(path)-1].(cty.GetAttrStep) + if isGetAttr { + break + } + path = path[:len(path)-1] + } + return path +} + +// requiresReplacePath takes a key from a flatmap along with the cty.Type +// describing the structure, and returns the cty.Path that would be used to +// reference the nested value in the data structure. +// This is used specifically to record the RequiresReplace attributes from a +// ResourceInstanceDiff. +func requiresReplacePath(k string, ty cty.Type) (cty.Path, error) { + if k == "" { + return nil, nil + } + if !ty.IsObjectType() { + panic(fmt.Sprintf("requires replace path on non-object type: %#v", ty)) + } + + path, err := pathFromFlatmapKeyObject(k, ty.AttributeTypes()) + if err != nil { + return path, fmt.Errorf("[%s] %s", k, err) + } + return path, nil +} + +func pathSplit(p string) (string, string) { + parts := strings.SplitN(p, ".", 2) + head := parts[0] + rest := "" + if len(parts) > 1 { + rest = parts[1] + } + return head, rest +} + +func pathFromFlatmapKeyObject(key string, atys map[string]cty.Type) (cty.Path, error) { + k, rest := pathSplit(key) + + path := cty.Path{cty.GetAttrStep{Name: k}} + + ty, ok := atys[k] + if !ok { + return path, fmt.Errorf("attribute %q not found", k) + } + + if rest == "" { + return path, nil + } + + p, err := pathFromFlatmapKeyValue(rest, ty) + if err != nil { + return path, err + } + + return append(path, p...), nil +} + +func pathFromFlatmapKeyValue(key string, ty cty.Type) (cty.Path, error) { + var path cty.Path + var err error + + switch { + case ty.IsPrimitiveType(): + err = fmt.Errorf("invalid step %q with type %#v", key, ty) + case ty.IsObjectType(): + path, err = pathFromFlatmapKeyObject(key, ty.AttributeTypes()) + case ty.IsTupleType(): + path, err = pathFromFlatmapKeyTuple(key, ty.TupleElementTypes()) + case ty.IsMapType(): + path, err = pathFromFlatmapKeyMap(key, ty) + case ty.IsListType(): + path, err = pathFromFlatmapKeyList(key, ty) + case ty.IsSetType(): + path, err = pathFromFlatmapKeySet(key, ty) + default: + err = fmt.Errorf("unrecognized type: %s", ty.FriendlyName()) + } + + if err != nil { + return path, err + } + + return path, nil +} + +func pathFromFlatmapKeyTuple(key string, etys []cty.Type) (cty.Path, error) { + var path cty.Path + var err error + + k, rest := pathSplit(key) + + // we don't need to convert the index keys to paths + if k == "#" { + return path, nil + } + + idx, err := strconv.Atoi(k) + if err != nil { + return path, err + } + + path = cty.Path{cty.IndexStep{Key: cty.NumberIntVal(int64(idx))}} + + if idx >= len(etys) { + return path, fmt.Errorf("index %s out of range in %#v", key, etys) + } + + if rest == "" { + return path, nil + } + + ty := etys[idx] + + p, err := pathFromFlatmapKeyValue(rest, ty.ElementType()) + if err != nil { + return path, err + } + + return append(path, p...), nil +} + +func pathFromFlatmapKeyMap(key string, ty cty.Type) (cty.Path, error) { + var path cty.Path + var err error + + k, rest := key, "" + if !ty.ElementType().IsPrimitiveType() { + k, rest = pathSplit(key) + } + + // we don't need to convert the index keys to paths + if k == "%" { + return path, nil + } + + path = cty.Path{cty.IndexStep{Key: cty.StringVal(k)}} + + if rest == "" { + return path, nil + } + + p, err := pathFromFlatmapKeyValue(rest, ty.ElementType()) + if err != nil { + return path, err + } + + return append(path, p...), nil +} + +func pathFromFlatmapKeyList(key string, ty cty.Type) (cty.Path, error) { + var path cty.Path + var err error + + k, rest := pathSplit(key) + + // we don't need to convert the index keys to paths + if key == "#" { + return path, nil + } + + idx, err := strconv.Atoi(k) + if err != nil { + return path, err + } + + path = cty.Path{cty.IndexStep{Key: cty.NumberIntVal(int64(idx))}} + + if rest == "" { + return path, nil + } + + p, err := pathFromFlatmapKeyValue(rest, ty.ElementType()) + if err != nil { + return path, err + } + + return append(path, p...), nil +} + +func pathFromFlatmapKeySet(key string, ty cty.Type) (cty.Path, error) { + // once we hit a set, we can't return consistent paths, so just mark the + // set as a whole changed. + return nil, nil +} + +// FlatmapKeyFromPath returns the flatmap equivalent of the given cty.Path for +// use in generating legacy style diffs. +func FlatmapKeyFromPath(path cty.Path) string { + var parts []string + + for _, step := range path { + switch step := step.(type) { + case cty.GetAttrStep: + parts = append(parts, step.Name) + case cty.IndexStep: + switch ty := step.Key.Type(); { + case ty == cty.String: + parts = append(parts, step.Key.AsString()) + case ty == cty.Number: + i, _ := step.Key.AsBigFloat().Int64() + parts = append(parts, strconv.Itoa(int(i))) + } + } + } + + return strings.Join(parts, ".") +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/internal/configs/hcl2shim/values.go b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/configs/hcl2shim/values.go new file mode 100644 index 0000000000..191f1bc753 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/configs/hcl2shim/values.go @@ -0,0 +1,233 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package hcl2shim + +import ( + "fmt" + "math/big" + + "github.com/hashicorp/go-cty/cty" + + "github.com/hashicorp/terraform-plugin-testing/internal/configs/configschema" +) + +// UnknownVariableValue is a sentinel value that can be used +// to denote that the value of a variable is unknown at this time. +// RawConfig uses this information to build up data about +// unknown keys. +const UnknownVariableValue = "74D93920-ED26-11E3-AC10-0800200C9A66" + +// ConfigValueFromHCL2Block is like ConfigValueFromHCL2 but it works only for +// known object values and uses the provided block schema to perform some +// additional normalization to better mimic the shape of value that the old +// HCL1/HIL-based codepaths would've produced. +// +// In particular, it discards the collections that we use to represent nested +// blocks (other than NestingSingle) if they are empty, which better mimics +// the HCL1 behavior because HCL1 had no knowledge of the schema and so didn't +// know that an unspecified block _could_ exist. +// +// The given object value must conform to the schema's implied type or this +// function will panic or produce incorrect results. +// +// This is primarily useful for the final transition from new-style values to +// terraform.ResourceConfig before calling to a legacy provider, since +// helper/schema (the old provider SDK) is particularly sensitive to these +// subtle differences within its validation code. +func ConfigValueFromHCL2Block(v cty.Value, schema *configschema.Block) map[string]interface{} { + if v.IsNull() { + return nil + } + if !v.IsKnown() { + panic("ConfigValueFromHCL2Block used with unknown value") + } + if !v.Type().IsObjectType() { + panic(fmt.Sprintf("ConfigValueFromHCL2Block used with non-object value %#v", v)) + } + + atys := v.Type().AttributeTypes() + ret := make(map[string]interface{}) + + for name := range schema.Attributes { + if _, exists := atys[name]; !exists { + continue + } + + av := v.GetAttr(name) + if av.IsNull() { + // Skip nulls altogether, to better mimic how HCL1 would behave + continue + } + ret[name] = ConfigValueFromHCL2(av) + } + + for name, blockS := range schema.BlockTypes { + if _, exists := atys[name]; !exists { + continue + } + bv := v.GetAttr(name) + if !bv.IsKnown() { + ret[name] = UnknownVariableValue + continue + } + if bv.IsNull() { + continue + } + + switch blockS.Nesting { + + case configschema.NestingSingle, configschema.NestingGroup: + ret[name] = ConfigValueFromHCL2Block(bv, &blockS.Block) + + case configschema.NestingList, configschema.NestingSet: + l := bv.LengthInt() + if l == 0 { + // skip empty collections to better mimic how HCL1 would behave + continue + } + + elems := make([]interface{}, 0, l) + for it := bv.ElementIterator(); it.Next(); { + _, ev := it.Element() + if !ev.IsKnown() { + elems = append(elems, UnknownVariableValue) + continue + } + elems = append(elems, ConfigValueFromHCL2Block(ev, &blockS.Block)) + } + ret[name] = elems + + case configschema.NestingMap: + if bv.LengthInt() == 0 { + // skip empty collections to better mimic how HCL1 would behave + continue + } + + elems := make(map[string]interface{}) + for it := bv.ElementIterator(); it.Next(); { + ek, ev := it.Element() + if !ev.IsKnown() { + elems[ek.AsString()] = UnknownVariableValue + continue + } + elems[ek.AsString()] = ConfigValueFromHCL2Block(ev, &blockS.Block) + } + ret[name] = elems + } + } + + return ret +} + +// ConfigValueFromHCL2 converts a value from HCL2 (really, from the cty dynamic +// types library that HCL2 uses) to a value type that matches what would've +// been produced from the HCL-based interpolator for an equivalent structure. +// +// This function will transform a cty null value into a Go nil value, which +// isn't a possible outcome of the HCL/HIL-based decoder and so callers may +// need to detect and reject any null values. +func ConfigValueFromHCL2(v cty.Value) interface{} { + if !v.IsKnown() { + return UnknownVariableValue + } + if v.IsNull() { + return nil + } + + switch v.Type() { + case cty.Bool: + return v.True() // like HCL.BOOL + case cty.String: + return v.AsString() // like HCL token.STRING or token.HEREDOC + case cty.Number: + // We can't match HCL _exactly_ here because it distinguishes between + // int and float values, but we'll get as close as we can by using + // an int if the number is exactly representable, and a float if not. + // The conversion to float will force precision to that of a float64, + // which is potentially losing information from the specific number + // given, but no worse than what HCL would've done in its own conversion + // to float. + + f := v.AsBigFloat() + if i, acc := f.Int64(); acc == big.Exact { + // if we're on a 32-bit system and the number is too big for 32-bit + // int then we'll fall through here and use a float64. + const MaxInt = int(^uint(0) >> 1) + const MinInt = -MaxInt - 1 + if i <= int64(MaxInt) && i >= int64(MinInt) { + return int(i) // Like HCL token.NUMBER + } + } + + f64, _ := f.Float64() + return f64 // like HCL token.FLOAT + } + + if v.Type().IsListType() || v.Type().IsSetType() || v.Type().IsTupleType() { + l := make([]interface{}, 0, v.LengthInt()) + it := v.ElementIterator() + for it.Next() { + _, ev := it.Element() + l = append(l, ConfigValueFromHCL2(ev)) + } + return l + } + + if v.Type().IsMapType() || v.Type().IsObjectType() { + l := make(map[string]interface{}) + it := v.ElementIterator() + for it.Next() { + ek, ev := it.Element() + cv := ConfigValueFromHCL2(ev) + if cv != nil { + l[ek.AsString()] = cv + } + } + return l + } + + // If we fall out here then we have some weird type that we haven't + // accounted for. This should never happen unless the caller is using + // capsule types, and we don't currently have any such types defined. + panic(fmt.Errorf("can't convert %#v to config value", v)) +} + +// HCL2ValueFromConfigValue is the opposite of configValueFromHCL2: it takes +// a value as would be returned from the old interpolator and turns it into +// a cty.Value so it can be used within, for example, an HCL2 EvalContext. +func HCL2ValueFromConfigValue(v interface{}) cty.Value { + if v == nil { + return cty.NullVal(cty.DynamicPseudoType) + } + if v == UnknownVariableValue { + return cty.DynamicVal + } + + switch tv := v.(type) { + case bool: + return cty.BoolVal(tv) + case string: + return cty.StringVal(tv) + case int: + return cty.NumberIntVal(int64(tv)) + case float64: + return cty.NumberFloatVal(tv) + case []interface{}: + vals := make([]cty.Value, len(tv)) + for i, ev := range tv { + vals[i] = HCL2ValueFromConfigValue(ev) + } + return cty.TupleVal(vals) + case map[string]interface{}: + vals := map[string]cty.Value{} + for k, ev := range tv { + vals[k] = HCL2ValueFromConfigValue(ev) + } + return cty.ObjectVal(vals) + default: + // HCL/HIL should never generate anything that isn't caught by + // the above, so if we get here something has gone very wrong. + panic(fmt.Errorf("can't convert %#v to cty.Value", v)) + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/internal/configs/hcl2shim/values_equiv.go b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/configs/hcl2shim/values_equiv.go new file mode 100644 index 0000000000..6b2be2239d --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/configs/hcl2shim/values_equiv.go @@ -0,0 +1,217 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package hcl2shim + +import ( + "github.com/hashicorp/go-cty/cty" +) + +// ValuesSDKEquivalent returns true if both of the given values seem equivalent +// as far as the legacy SDK diffing code would be concerned. +// +// Since SDK diffing is a fuzzy, inexact operation, this function is also +// fuzzy and inexact. It will err on the side of returning false if it +// encounters an ambiguous situation. Ambiguity is most common in the presence +// of sets because in practice it is impossible to exactly correlate +// nonequal-but-equivalent set elements because they have no identity separate +// from their value. +// +// This must be used _only_ for comparing values for equivalence within the +// SDK planning code. It is only meaningful to compare the "prior state" +// provided by Terraform Core with the "planned new state" produced by the +// legacy SDK code via shims. In particular it is not valid to use this +// function with their the config value or the "proposed new state" value +// because they contain only the subset of data that Terraform Core itself is +// able to determine. +func ValuesSDKEquivalent(a, b cty.Value) bool { + if a == cty.NilVal || b == cty.NilVal { + // We don't generally expect nils to appear, but we'll allow them + // for robustness since the data structures produced by legacy SDK code + // can sometimes be non-ideal. + return a == b // equivalent if they are _both_ nil + } + if a.RawEquals(b) { + // Easy case. We use RawEquals because we want two unknowns to be + // considered equal here, whereas "Equals" would return unknown. + return true + } + if !a.IsKnown() || !b.IsKnown() { + // Two unknown values are equivalent regardless of type. A known is + // never equivalent to an unknown. + return a.IsKnown() == b.IsKnown() + } + if aZero, bZero := valuesSDKEquivalentIsNullOrZero(a), valuesSDKEquivalentIsNullOrZero(b); aZero || bZero { + // Two null/zero values are equivalent regardless of type. A non-zero is + // never equivalent to a zero. + return aZero == bZero + } + + // If we get down here then we are guaranteed that both a and b are known, + // non-null values. + + aTy := a.Type() + bTy := b.Type() + switch { + case aTy.IsSetType() && bTy.IsSetType(): + return valuesSDKEquivalentSets(a, b) + case aTy.IsListType() && bTy.IsListType(): + return valuesSDKEquivalentSequences(a, b) + case aTy.IsTupleType() && bTy.IsTupleType(): + return valuesSDKEquivalentSequences(a, b) + case aTy.IsMapType() && bTy.IsMapType(): + return valuesSDKEquivalentMappings(a, b) + case aTy.IsObjectType() && bTy.IsObjectType(): + return valuesSDKEquivalentMappings(a, b) + case aTy == cty.Number && bTy == cty.Number: + return valuesSDKEquivalentNumbers(a, b) + default: + // We've now covered all the interesting cases, so anything that falls + // down here cannot be equivalent. + return false + } +} + +// valuesSDKEquivalentIsNullOrZero returns true if the given value is either +// null or is the "zero value" (in the SDK/Go sense) for its type. +func valuesSDKEquivalentIsNullOrZero(v cty.Value) bool { + if v == cty.NilVal { + return true + } + + ty := v.Type() + switch { + case !v.IsKnown(): + return false + case v.IsNull(): + return true + + // After this point, v is always known and non-null + case ty.IsListType() || ty.IsSetType() || ty.IsMapType() || ty.IsObjectType() || ty.IsTupleType(): + return v.LengthInt() == 0 + case ty == cty.String: + return v.RawEquals(cty.StringVal("")) + case ty == cty.Number: + return v.RawEquals(cty.Zero) + case ty == cty.Bool: + return v.RawEquals(cty.False) + default: + // The above is exhaustive, but for robustness we'll consider anything + // else to _not_ be zero unless it is null. + return false + } +} + +// valuesSDKEquivalentSets returns true only if each of the elements in a can +// be correlated with at least one equivalent element in b and vice-versa. +// This is a fuzzy operation that prefers to signal non-equivalence if it cannot +// be certain that all elements are accounted for. +func valuesSDKEquivalentSets(a, b cty.Value) bool { + if aLen, bLen := a.LengthInt(), b.LengthInt(); aLen != bLen { + return false + } + + // Our methodology here is a little tricky, to deal with the fact that + // it's impossible to directly correlate two non-equal set elements because + // they don't have identities separate from their values. + // The approach is to count the number of equivalent elements each element + // of a has in b and vice-versa, and then return true only if each element + // in both sets has at least one equivalent. + as := a.AsValueSlice() + bs := b.AsValueSlice() + aeqs := make([]bool, len(as)) + beqs := make([]bool, len(bs)) + for ai, av := range as { + for bi, bv := range bs { + if ValuesSDKEquivalent(av, bv) { + aeqs[ai] = true + beqs[bi] = true + } + } + } + + for _, eq := range aeqs { + if !eq { + return false + } + } + for _, eq := range beqs { + if !eq { + return false + } + } + return true +} + +// valuesSDKEquivalentSequences decides equivalence for two sequence values +// (lists or tuples). +func valuesSDKEquivalentSequences(a, b cty.Value) bool { + as := a.AsValueSlice() + bs := b.AsValueSlice() + if len(as) != len(bs) { + return false + } + + for i := range as { + if !ValuesSDKEquivalent(as[i], bs[i]) { + return false + } + } + return true +} + +// valuesSDKEquivalentMappings decides equivalence for two mapping values +// (maps or objects). +func valuesSDKEquivalentMappings(a, b cty.Value) bool { + as := a.AsValueMap() + bs := b.AsValueMap() + if len(as) != len(bs) { + return false + } + + for k, av := range as { + bv, ok := bs[k] + if !ok { + return false + } + if !ValuesSDKEquivalent(av, bv) { + return false + } + } + return true +} + +// valuesSDKEquivalentNumbers decides equivalence for two number values based +// on the fact that the SDK uses int and float64 representations while +// cty (and thus Terraform Core) uses big.Float, and so we expect to lose +// precision in the round-trip. +// +// This does _not_ attempt to allow for an epsilon difference that may be +// caused by accumulated innacuracy in a float calculation, under the +// expectation that providers generally do not actually do compuations on +// floats and instead just pass string representations of them on verbatim +// to remote APIs. A remote API _itself_ may introduce inaccuracy, but that's +// a problem for the provider itself to deal with, based on its knowledge of +// the remote system, e.g. using DiffSuppressFunc. +func valuesSDKEquivalentNumbers(a, b cty.Value) bool { + if a.RawEquals(b) { + return true // easy + } + + af := a.AsBigFloat() + bf := b.AsBigFloat() + + if af.IsInt() != bf.IsInt() { + return false + } + if af.IsInt() && bf.IsInt() { + return false // a.RawEquals(b) test above is good enough for integers + } + + // The SDK supports only int and float64, so if it's not an integer + // we know that only a float64-level of precision can possibly be + // significant. + af64, _ := af.Float64() + bf64, _ := bf.Float64() + return af64 == bf64 +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/internal/logging/context.go b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/logging/context.go new file mode 100644 index 0000000000..35f4160618 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/logging/context.go @@ -0,0 +1,62 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package logging + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-log/tfsdklog" + helperlogging "github.com/hashicorp/terraform-plugin-sdk/v2/helper/logging" + testing "github.com/mitchellh/go-testing-interface" +) + +// InitTestContext registers the terraform-plugin-log/tfsdklog test sink, +// configures the standard library log package, and creates SDK logger +// contexts. The incoming context is expected to be devoid of logging setup. +// +// The standard library log package handling is important as provider code +// under test may be using that package or another logging library outside of +// terraform-plugin-log. +func InitTestContext(ctx context.Context, t testing.T) context.Context { + helperlogging.SetOutput(t) + + ctx = tfsdklog.ContextWithTestLogging(ctx, t.Name()) + ctx = tfsdklog.NewRootSDKLogger(ctx, tfsdklog.WithLevelFromEnv(EnvTfLogSdk)) + ctx = tfsdklog.NewSubsystem(ctx, SubsystemHelperResource, + // All calls are through the HelperResource* helper functions + tfsdklog.WithAdditionalLocationOffset(1), + tfsdklog.WithLevelFromEnv(EnvTfLogSdkHelperResource), + ) + ctx = TestNameContext(ctx, t.Name()) + + return ctx +} + +// TestNameContext adds the current test name to loggers. +func TestNameContext(ctx context.Context, testName string) context.Context { + ctx = tfsdklog.SubsystemSetField(ctx, SubsystemHelperResource, KeyTestName, testName) + + return ctx +} + +// TestStepNumberContext adds the current test step number to loggers. +func TestStepNumberContext(ctx context.Context, stepNumber int) context.Context { + ctx = tfsdklog.SubsystemSetField(ctx, SubsystemHelperResource, KeyTestStepNumber, stepNumber) + + return ctx +} + +// TestTerraformPathContext adds the current test Terraform CLI path to loggers. +func TestTerraformPathContext(ctx context.Context, terraformPath string) context.Context { + ctx = tfsdklog.SubsystemSetField(ctx, SubsystemHelperResource, KeyTestTerraformPath, terraformPath) + + return ctx +} + +// TestWorkingDirectoryContext adds the current test working directory to loggers. +func TestWorkingDirectoryContext(ctx context.Context, workingDirectory string) context.Context { + ctx = tfsdklog.SubsystemSetField(ctx, SubsystemHelperResource, KeyTestWorkingDirectory, workingDirectory) + + return ctx +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/internal/logging/environment_variables.go b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/logging/environment_variables.go new file mode 100644 index 0000000000..846cf67dd9 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/logging/environment_variables.go @@ -0,0 +1,22 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package logging + +// Environment variables. +const ( + // EnvTfLogSdk is an environment variable that sets the logging level of + // the root SDK logger, while the provider is under test. In "production" + // usage, this environment variable is handled by terraform-plugin-go. + // + // Terraform CLI's logging must be explicitly turned on before this + // environment varable can be used to reduce the SDK logging levels. It + // cannot be used to show only SDK logging unless all other logging levels + // are turned off. + EnvTfLogSdk = "TF_LOG_SDK" + + // EnvTfLogSdkHelperResource is an environment variable that sets the logging + // level of SDK helper/resource loggers. Infers root SDK logging level, if + // unset. + EnvTfLogSdkHelperResource = "TF_LOG_SDK_HELPER_RESOURCE" +) diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/internal/logging/helper_resource.go b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/logging/helper_resource.go new file mode 100644 index 0000000000..1b1459f246 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/logging/helper_resource.go @@ -0,0 +1,35 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package logging + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-log/tfsdklog" +) + +const ( + // SubsystemHelperResource is the tfsdklog subsystem name for helper/resource. + SubsystemHelperResource = "helper_resource" +) + +// HelperResourceTrace emits a helper/resource subsystem log at TRACE level. +func HelperResourceTrace(ctx context.Context, msg string, additionalFields ...map[string]interface{}) { + tfsdklog.SubsystemTrace(ctx, SubsystemHelperResource, msg, additionalFields...) +} + +// HelperResourceDebug emits a helper/resource subsystem log at DEBUG level. +func HelperResourceDebug(ctx context.Context, msg string, additionalFields ...map[string]interface{}) { + tfsdklog.SubsystemDebug(ctx, SubsystemHelperResource, msg, additionalFields...) +} + +// HelperResourceWarn emits a helper/resource subsystem log at WARN level. +func HelperResourceWarn(ctx context.Context, msg string, additionalFields ...map[string]interface{}) { + tfsdklog.SubsystemWarn(ctx, SubsystemHelperResource, msg, additionalFields...) +} + +// HelperResourceError emits a helper/resource subsystem log at ERROR level. +func HelperResourceError(ctx context.Context, msg string, additionalFields ...map[string]interface{}) { + tfsdklog.SubsystemError(ctx, SubsystemHelperResource, msg, additionalFields...) +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/internal/logging/keys.go b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/logging/keys.go new file mode 100644 index 0000000000..983fde437a --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/logging/keys.go @@ -0,0 +1,63 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package logging + +// Structured logging keys. +// +// Practitioners or tooling reading logs may be depending on these keys, so be +// conscious of that when changing them. +// +// Refer to the terraform-plugin-go logging keys as well, which should be +// equivalent to these when possible. +const ( + // Attribute path representation, which is typically in flatmap form such + // as parent.0.child in this project. + KeyAttributePath = "tf_attribute_path" + + // The type of data source being operated on, such as "archive_file" + KeyDataSourceType = "tf_data_source_type" + + // Underlying Go error string when logging an error. + KeyError = "error" + + // The full address of the provider, such as + // registry.terraform.io/hashicorp/random + KeyProviderAddress = "tf_provider_addr" + + // The type of resource being operated on, such as "random_pet" + KeyResourceType = "tf_resource_type" + + // The name of the test being executed. + KeyTestName = "test_name" + + // The TestStep number of the test being executed. Starts at 1. + KeyTestStepNumber = "test_step_number" + + // Terraform configuration used during acceptance testing Terraform operations. + KeyTestTerraformConfiguration = "test_terraform_configuration" + + // The Terraform CLI logging level (TF_LOG) used for an acceptance test. + KeyTestTerraformLogLevel = "test_terraform_log_level" + + // The Terraform CLI logging level (TF_LOG_CORE) used for an acceptance test. + KeyTestTerraformLogCoreLevel = "test_terraform_log_core_level" + + // The Terraform CLI logging level (TF_LOG_PROVIDER) used for an acceptance test. + KeyTestTerraformLogProviderLevel = "test_terraform_log_provider_level" + + // The path to the Terraform CLI logging file used for an acceptance test. + // + // This should match where the rest of the acceptance test logs are going + // already, but is provided for troubleshooting in case it does not. + KeyTestTerraformLogPath = "test_terraform_log_path" + + // The path to the Terraform CLI used for an acceptance test. + KeyTestTerraformPath = "test_terraform_path" + + // Terraform plan output generated during a TestStep. + KeyTestTerraformPlan = "test_terraform_plan" + + // The working directory of the acceptance test. + KeyTestWorkingDirectory = "test_working_directory" +) diff --git a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/internal/plugintest/config.go b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/plugintest/config.go similarity index 97% rename from vendor/github.com/hashicorp/terraform-plugin-sdk/v2/internal/plugintest/config.go rename to vendor/github.com/hashicorp/terraform-plugin-testing/internal/plugintest/config.go index d3cb35bcec..b63a55e4ed 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/internal/plugintest/config.go +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/plugintest/config.go @@ -16,7 +16,8 @@ import ( "github.com/hashicorp/hc-install/product" "github.com/hashicorp/hc-install/releases" "github.com/hashicorp/hc-install/src" - "github.com/hashicorp/terraform-plugin-sdk/v2/internal/logging" + + "github.com/hashicorp/terraform-plugin-testing/internal/logging" ) // Config is used to configure the test helper. In most normal test programs diff --git a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/internal/plugintest/doc.go b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/plugintest/doc.go similarity index 100% rename from vendor/github.com/hashicorp/terraform-plugin-sdk/v2/internal/plugintest/doc.go rename to vendor/github.com/hashicorp/terraform-plugin-testing/internal/plugintest/doc.go diff --git a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/internal/plugintest/environment_variables.go b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/plugintest/environment_variables.go similarity index 91% rename from vendor/github.com/hashicorp/terraform-plugin-sdk/v2/internal/plugintest/environment_variables.go rename to vendor/github.com/hashicorp/terraform-plugin-testing/internal/plugintest/environment_variables.go index 6df86f89f8..361de8f1e0 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/internal/plugintest/environment_variables.go +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/plugintest/environment_variables.go @@ -108,4 +108,12 @@ const ( // will be installed if a binary is not found at the given path. No version // checks are performed against an existing binary. EnvTfAccTerraformPath = "TF_ACC_TERRAFORM_PATH" + + // EnvTfAccPersistWorkingDir environment variable enables persisting + // the working directory and the files generated during execution of + // TestStep(s). Default is disabled, in which case the working directory + // and the files it contains are deleted at the end of each acceptance + // test. Can be set to any value to persist the working directory and + // its contents, however "1" is conventional. + EnvTfAccPersistWorkingDir = "TF_ACC_PERSIST_WORKING_DIR" ) diff --git a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/internal/plugintest/guard.go b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/plugintest/guard.go similarity index 100% rename from vendor/github.com/hashicorp/terraform-plugin-sdk/v2/internal/plugintest/guard.go rename to vendor/github.com/hashicorp/terraform-plugin-testing/internal/plugintest/guard.go diff --git a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/internal/plugintest/helper.go b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/plugintest/helper.go similarity index 90% rename from vendor/github.com/hashicorp/terraform-plugin-sdk/v2/internal/plugintest/helper.go rename to vendor/github.com/hashicorp/terraform-plugin-testing/internal/plugintest/helper.go index f961788721..3c9772cfc4 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/internal/plugintest/helper.go +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/plugintest/helper.go @@ -10,8 +10,10 @@ import ( "os" "strings" + "github.com/hashicorp/go-version" "github.com/hashicorp/terraform-exec/tfexec" - "github.com/hashicorp/terraform-plugin-sdk/v2/internal/logging" + + "github.com/hashicorp/terraform-plugin-testing/internal/logging" ) // AutoInitProviderHelper is the main entrypoint for testing provider plugins @@ -41,6 +43,7 @@ type Helper struct { // for tests that use fixture files. sourceDir string terraformExec string + terraformVer *version.Version // execTempDir is created during DiscoverConfig to store any downloaded // binaries @@ -77,11 +80,23 @@ func InitHelper(ctx context.Context, config *Config) (*Helper, error) { return nil, fmt.Errorf("failed to create temporary directory for test helper: %s", err) } + tf, err := tfexec.NewTerraform(baseDir, config.TerraformExec) + if err != nil { + return nil, fmt.Errorf("unable to create terraform-exec instance: %w", err) + } + + tfVersion, _, err := tf.Version(ctx, false) + + if err != nil { + return nil, fmt.Errorf("error calling terraform version command: %w", err) + } + return &Helper{ baseDir: baseDir, sourceDir: config.SourceDir, terraformExec: config.TerraformExec, execTempDir: config.execTempDir, + terraformVer: tfVersion, }, nil } @@ -91,12 +106,17 @@ func InitHelper(ctx context.Context, config *Config) (*Helper, error) { // Call this before returning from TestMain to minimize the amount of detritus // left behind in the filesystem after the tests complete. func (h *Helper) Close() error { + if os.Getenv(EnvTfAccPersistWorkingDir) != "" { + return nil + } + if h.execTempDir != "" { err := os.RemoveAll(h.execTempDir) if err != nil { return err } } + return os.RemoveAll(h.baseDir) } @@ -106,8 +126,15 @@ func (h *Helper) Close() error { // If the working directory object is not itself closed by the time the test // program exits, the Close method on the helper itself will attempt to // delete it. -func (h *Helper) NewWorkingDir(ctx context.Context, t TestControl) (*WorkingDir, error) { - dir, err := os.MkdirTemp(h.baseDir, "work") +func (h *Helper) NewWorkingDir(ctx context.Context, t TestControl, wd string) (*WorkingDir, error) { + workingDir := h.baseDir + + if wd != "" { + workingDir = wd + h.baseDir = wd + } + + dir, err := os.MkdirTemp(workingDir, "work") if err != nil { return nil, err } @@ -266,10 +293,10 @@ func (h *Helper) NewWorkingDir(ctx context.Context, t TestControl) (*WorkingDir, // RequireNewWorkingDir is a variant of NewWorkingDir that takes a TestControl // object and will immediately fail the running test if the creation of the // working directory fails. -func (h *Helper) RequireNewWorkingDir(ctx context.Context, t TestControl) *WorkingDir { +func (h *Helper) RequireNewWorkingDir(ctx context.Context, t TestControl, workingDir string) *WorkingDir { t.Helper() - wd, err := h.NewWorkingDir(ctx, t) + wd, err := h.NewWorkingDir(ctx, t, workingDir) if err != nil { t := testingT{t} t.Fatalf("failed to create new working directory: %s", err) @@ -288,3 +315,8 @@ func (h *Helper) WorkingDirectory() string { func (h *Helper) TerraformExecPath() string { return h.terraformExec } + +// TerraformVersion returns the Terraform CLI version being used when running tests. +func (h *Helper) TerraformVersion() *version.Version { + return h.terraformVer +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/internal/plugintest/util.go b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/plugintest/util.go new file mode 100644 index 0000000000..be187a01b9 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/plugintest/util.go @@ -0,0 +1,186 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package plugintest + +import ( + "fmt" + "io" + "io/fs" + "os" + "path" + "path/filepath" + "strings" + "testing" +) + +func symlinkFile(src string, dest string) error { + err := os.Symlink(src, dest) + + if err != nil { + return fmt.Errorf("unable to symlink %q to %q: %w", src, dest, err) + } + + srcInfo, err := os.Stat(src) + + if err != nil { + return fmt.Errorf("unable to stat %q: %w", src, err) + } + + err = os.Chmod(dest, srcInfo.Mode()) + + if err != nil { + return fmt.Errorf("unable to set %q permissions: %w", dest, err) + } + + return nil +} + +// symlinkDirectoriesOnly finds only the first-level child directories in srcDir +// and symlinks them into destDir. +// Unlike symlinkDir, this is done non-recursively in order to limit the number +// of file descriptors used. +func symlinkDirectoriesOnly(srcDir string, destDir string) error { + srcInfo, err := os.Stat(srcDir) + if err != nil { + return fmt.Errorf("unable to stat source directory %q: %w", srcDir, err) + } + + err = os.MkdirAll(destDir, srcInfo.Mode()) + if err != nil { + return fmt.Errorf("unable to make destination directory %q: %w", destDir, err) + } + + dirEntries, err := os.ReadDir(srcDir) + + if err != nil { + return fmt.Errorf("unable to read source directory %q: %w", srcDir, err) + } + + for _, dirEntry := range dirEntries { + if !dirEntry.IsDir() { + continue + } + + srcPath := filepath.Join(srcDir, dirEntry.Name()) + destPath := filepath.Join(destDir, dirEntry.Name()) + err := symlinkFile(srcPath, destPath) + + if err != nil { + return fmt.Errorf("unable to symlink directory %q to %q: %w", srcPath, destPath, err) + } + } + + return nil +} + +// CopyFile copies a single file from src to dest. +func CopyFile(src, dest string) error { + var srcFileInfo os.FileInfo + + srcFile, err := os.Open(src) + if err != nil { + return fmt.Errorf("unable to open file: %w", err) + } + defer srcFile.Close() + + destFile, err := os.Create(dest) + if err != nil { + return fmt.Errorf("unable to create file: %w", err) + } + defer destFile.Close() + + if _, err = io.Copy(destFile, srcFile); err != nil { + return fmt.Errorf("unable to copy: %w", err) + } + + if srcFileInfo, err = os.Stat(src); err != nil { + return fmt.Errorf("unable to stat: %w", err) + } + + return os.Chmod(dest, srcFileInfo.Mode()) +} + +// CopyDir recursively copies directories and files +// from src to dest. +func CopyDir(src, dest, baseDirName string) error { + srcInfo, err := os.Stat(src) + if err != nil { + return fmt.Errorf("unable to stat: %w", err) + } + + if err = os.MkdirAll(dest, srcInfo.Mode()); err != nil { + return fmt.Errorf("unable to create dir: %w", err) + } + + dirEntries, err := os.ReadDir(src) + if err != nil { + return fmt.Errorf("unable to read dir: %w", err) + } + + for _, dirEntry := range dirEntries { + srcFilepath := path.Join(src, dirEntry.Name()) + destFilepath := path.Join(dest, dirEntry.Name()) + + if !strings.Contains(srcFilepath, baseDirName) { + continue + } + + fi, err := dirEntry.Info() + + if err != nil { + return fmt.Errorf("unable to get dir entry info: %w", err) + } + + if dirEntry.IsDir() || fi.Mode()&fs.ModeSymlink == fs.ModeSymlink { + if err = CopyDir(srcFilepath, destFilepath, baseDirName); err != nil { + return fmt.Errorf("unable to copy directory: %w", err) + } + } else { + if err = CopyFile(srcFilepath, destFilepath); err != nil { + return fmt.Errorf("unable to copy file: %w", err) + } + } + } + + return nil +} + +// TestExpectTFatal provides a wrapper for logic which should call +// (*testing.T).Fatal() or (*testing.T).Fatalf(). +// +// Since we do not want the wrapping test to fail when an expected test error +// occurs, it is required that the testLogic passed in uses +// github.com/mitchellh/go-testing-interface.RuntimeT instead of the real +// *testing.T. +// +// If Fatal() or Fatalf() is not called in the logic, the real (*testing.T).Fatal() will +// be called to fail the test. +func TestExpectTFatal(t *testing.T, testLogic func()) { + t.Helper() + + var recoverIface interface{} + + func() { + defer func() { + recoverIface = recover() + }() + + testLogic() + }() + + if recoverIface == nil { + t.Fatalf("expected t.Fatal(), got none") + } + + recoverStr, ok := recoverIface.(string) + + if !ok { + t.Fatalf("expected string from recover(), got: %v (%T)", recoverIface, recoverIface) + } + + // this string is hardcoded in github.com/mitchellh/go-testing-interface + if !strings.HasPrefix(recoverStr, "testing.T failed, see logs for output") { + t.Fatalf("expected t.Fatal(), got: %s", recoverStr) + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/internal/plugintest/working_dir.go b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/plugintest/working_dir.go similarity index 62% rename from vendor/github.com/hashicorp/terraform-plugin-sdk/v2/internal/plugintest/working_dir.go rename to vendor/github.com/hashicorp/terraform-plugin-testing/internal/plugintest/working_dir.go index 05b0284420..e7fe6b2469 100644 --- a/vendor/github.com/hashicorp/terraform-plugin-sdk/v2/internal/plugintest/working_dir.go +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/plugintest/working_dir.go @@ -5,21 +5,23 @@ package plugintest import ( "context" - "encoding/json" "fmt" + "io" "os" "path/filepath" "github.com/hashicorp/terraform-exec/tfexec" tfjson "github.com/hashicorp/terraform-json" - "github.com/hashicorp/terraform-plugin-sdk/v2/internal/logging" + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/internal/logging" + "github.com/hashicorp/terraform-plugin-testing/internal/teststep" ) const ( - ConfigFileName = "terraform_plugin_test.tf" - ConfigFileNameJSON = ConfigFileName + ".json" - PlanFileName = "tfplan" + ConfigFileName = "terraform_plugin_test.tf" + PlanFileName = "tfplan" + QueryFileName = "terraform_plugin_test.tfquery.hcl" ) // WorkingDir represents a distinct working directory that can be used for @@ -36,6 +38,10 @@ type WorkingDir struct { // was stored; empty until SetConfig is called. configFilename string + // queryFilename is the full filename where the latest query configuration + // was stored; empty until SetQuery is called. + queryFilename string + // tf is the instance of tfexec.Terraform used for running Terraform commands tf *tfexec.Terraform @@ -47,10 +53,19 @@ type WorkingDir struct { reattachInfo tfexec.ReattachInfo } +// BaseDir returns the path to the root of the working directory tree. +func (wd *WorkingDir) BaseDir() string { + return wd.baseDir +} + // Close deletes the directories and files created to represent the receiving // working directory. After this method is called, the working directory object // is invalid and may no longer be used. func (wd *WorkingDir) Close() error { + if os.Getenv(EnvTfAccPersistWorkingDir) != "" { + return nil + } + return os.RemoveAll(wd.baseDir) } @@ -73,29 +88,145 @@ func (wd *WorkingDir) GetHelper() *Helper { // This must be called at least once before any call to Init, Plan, Apply, or // Destroy to establish the configuration. Any previously-set configuration is // discarded and any saved plan is cleared. -func (wd *WorkingDir) SetConfig(ctx context.Context, cfg string) error { +func (wd *WorkingDir) SetConfig(ctx context.Context, cfg teststep.Config, vars config.Variables) error { + // Remove old config and variables files first + d, err := os.Open(wd.baseDir) + + if err != nil { + return err + } + + defer d.Close() + + fi, err := d.Readdir(-1) + + if err != nil { + return err + } + + for _, file := range fi { + if file.Mode().IsRegular() { + if filepath.Ext(file.Name()) == ".tf" || filepath.Ext(file.Name()) == ".json" || filepath.Ext(file.Name()) == ".tfquery.hcl" { + err = os.Remove(filepath.Join(d.Name(), file.Name())) + + if err != nil && !os.IsNotExist(err) { + return err + } + } + } + } + logging.HelperResourceTrace(ctx, "Setting Terraform configuration", map[string]any{logging.KeyTestTerraformConfiguration: cfg}) outFilename := filepath.Join(wd.baseDir, ConfigFileName) - rmFilename := filepath.Join(wd.baseDir, ConfigFileNameJSON) - bCfg := []byte(cfg) - if json.Valid(bCfg) { - outFilename, rmFilename = rmFilename, outFilename + + // This file has to be written otherwise wd.Init() will return an error. + err = os.WriteFile(outFilename, nil, 0700) + + if err != nil { + return err + } + + // wd.configFilename must be set otherwise wd.Init() will return an error. + wd.configFilename = outFilename + + // Write configuration + if cfg != nil { + err = cfg.Write(ctx, wd.baseDir) + + if err != nil { + return err + } + } + + //Write configuration variables + err = vars.Write(wd.baseDir) + + if err != nil { + return err } - if err := os.Remove(rmFilename); err != nil && !os.IsNotExist(err) { - return fmt.Errorf("unable to remove %q: %w", rmFilename, err) + + // Changing configuration invalidates any saved plan. + err = wd.ClearPlan(ctx) + + if err != nil { + return err } - err := os.WriteFile(outFilename, bCfg, 0700) + + return nil +} + +// SetQuery sets a new query configuration for the working directory. +// +// This must be called at least once before any call to Init or Query Destroy +// to establish the query configuration. Any previously-set configuration is +// discarded and any saved plan is cleared. +func (wd *WorkingDir) SetQuery(ctx context.Context, cfg teststep.Config, vars config.Variables) error { + // Remove old config and variables files first + d, err := os.Open(wd.baseDir) + if err != nil { return err } + + defer d.Close() + + fi, err := d.Readdir(-1) + + if err != nil { + return err + } + + for _, file := range fi { + if file.Mode().IsRegular() { + if filepath.Ext(file.Name()) == ".warioform" || filepath.Ext(file.Name()) == ".json" || filepath.Ext(file.Name()) == ".tfquery.hcl" { + err = os.Remove(filepath.Join(d.Name(), file.Name())) + + if err != nil && !os.IsNotExist(err) { + return err + } + } + } + } + + logging.HelperResourceTrace(ctx, "Setting Terraform query configuration", map[string]any{logging.KeyTestTerraformConfiguration: cfg}) + + outFilename := filepath.Join(wd.baseDir, QueryFileName) + + // This file has to be written otherwise wd.Init() will return an error. + err = os.WriteFile(outFilename, nil, 0700) + + if err != nil { + return err + } + + // wd.configFilename must be set otherwise wd.Init() will return an error. + wd.queryFilename = outFilename wd.configFilename = outFilename + // Write configuration + if cfg != nil { + err = cfg.WriteQuery(ctx, wd.baseDir) + + if err != nil { + return err + } + } + + //Write configuration variables + err = vars.Write(wd.baseDir) + + if err != nil { + return err + } + // Changing configuration invalidates any saved plan. err = wd.ClearPlan(ctx) + if err != nil { return err } + return nil } @@ -122,6 +253,40 @@ func (wd *WorkingDir) ClearState(ctx context.Context) error { return nil } +func (wd *WorkingDir) CopyState(ctx context.Context, src string) error { + srcState, err := os.Open(src) + if err != nil { + return fmt.Errorf("failed to open statefile for read: %w", err) + } + + defer srcState.Close() + + dstState, err := os.Create(filepath.Join(wd.baseDir, "terraform.tfstate")) + if err != nil { + return fmt.Errorf("failed to open statefile for write: %w", err) + } + + defer dstState.Close() + + buf := make([]byte, 1024) + for { + n, err := srcState.Read(buf) + if err != nil { + if err == io.EOF { + break + } + return fmt.Errorf("failed to read from statefile: %w", err) + } + + _, err = dstState.Write(buf[:n]) + if err != nil { + return fmt.Errorf("failed to write to statefile: %w", err) + } + } + + return nil +} + // ClearPlan deletes any saved plan present in the working directory. func (wd *WorkingDir) ClearPlan(ctx context.Context) error { logging.HelperResourceTrace(ctx, "Clearing Terraform plan") @@ -171,10 +336,13 @@ func (wd *WorkingDir) planFilename() string { // CreatePlan runs "terraform plan" to create a saved plan file, which if successful // will then be used for the next call to Apply. -func (wd *WorkingDir) CreatePlan(ctx context.Context) error { +func (wd *WorkingDir) CreatePlan(ctx context.Context, opts ...tfexec.PlanOption) error { logging.HelperResourceTrace(ctx, "Calling Terraform CLI plan command") - hasChanges, err := wd.tf.Plan(context.Background(), tfexec.Reattach(wd.reattachInfo), tfexec.Refresh(false), tfexec.Out(PlanFileName)) + opts = append(opts, tfexec.Reattach(wd.reattachInfo)) + opts = append(opts, tfexec.Out(PlanFileName)) + + hasChanges, err := wd.tf.Plan(context.Background(), opts...) logging.HelperResourceTrace(ctx, "Called Terraform CLI plan command") @@ -199,42 +367,13 @@ func (wd *WorkingDir) CreatePlan(ctx context.Context) error { return nil } -// CreateDestroyPlan runs "terraform plan -destroy" to create a saved plan -// file, which if successful will then be used for the next call to Apply. -func (wd *WorkingDir) CreateDestroyPlan(ctx context.Context) error { - logging.HelperResourceTrace(ctx, "Calling Terraform CLI plan -destroy command") - - hasChanges, err := wd.tf.Plan(context.Background(), tfexec.Reattach(wd.reattachInfo), tfexec.Refresh(false), tfexec.Out(PlanFileName), tfexec.Destroy(true)) - - logging.HelperResourceTrace(ctx, "Called Terraform CLI plan -destroy command") - - if err != nil { - return err - } - - if !hasChanges { - logging.HelperResourceTrace(ctx, "Created destroy plan with no changes") - - return nil - } - - stdout, err := wd.SavedPlanRawStdout(ctx) - - if err != nil { - return fmt.Errorf("error retrieving formatted plan output: %w", err) - } - - logging.HelperResourceTrace(ctx, "Created destroy plan with changes", map[string]any{logging.KeyTestTerraformPlan: stdout}) - - return nil -} - // Apply runs "terraform apply". If CreatePlan has previously completed // 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)) } @@ -270,6 +409,17 @@ func (wd *WorkingDir) HasSavedPlan() bool { return err == nil } +// RemoveResource removes a resource from state. +func (wd *WorkingDir) RemoveResource(ctx context.Context, address string) error { + logging.HelperResourceTrace(ctx, "Calling Terraform CLI state rm command") + + err := wd.tf.StateRm(context.Background(), address) + + logging.HelperResourceTrace(ctx, "Called Terraform CLI state rm command") + + return err +} + // SavedPlan returns an object describing the current saved plan file, if any. // // If no plan is saved or if the plan file cannot be read, SavedPlan returns @@ -281,7 +431,7 @@ func (wd *WorkingDir) SavedPlan(ctx context.Context) (*tfjson.Plan, error) { logging.HelperResourceTrace(ctx, "Calling Terraform CLI show command for JSON plan") - plan, err := wd.tf.ShowPlanFile(context.Background(), wd.planFilename(), tfexec.Reattach(wd.reattachInfo)) + plan, err := wd.tf.ShowPlanFile(context.Background(), wd.planFilename(), tfexec.Reattach(wd.reattachInfo), tfexec.JSONNumber(true)) logging.HelperResourceTrace(ctx, "Calling Terraform CLI show command for JSON plan") @@ -324,6 +474,10 @@ func (wd *WorkingDir) State(ctx context.Context) (*tfjson.State, error) { return state, err } +func (wd *WorkingDir) StateFilePath() string { + return filepath.Join(wd.baseDir, "terraform.tfstate") +} + // Import runs terraform import func (wd *WorkingDir) Import(ctx context.Context, resource, id string) error { logging.HelperResourceTrace(ctx, "Calling Terraform CLI import command") @@ -369,3 +523,43 @@ func (wd *WorkingDir) Schemas(ctx context.Context) (*tfjson.ProviderSchemas, err return providerSchemas, err } + +func (wd *WorkingDir) Query(ctx context.Context) ([]tfjson.LogMsg, error) { + var messages []tfjson.LogMsg + var diags []tfjson.LogMsg + + logging.HelperResourceTrace(ctx, "Calling Terraform CLI providers query command") + + args := []tfexec.QueryOption{tfexec.Reattach(wd.reattachInfo)} + + logs, err := wd.tf.QueryJSON(context.Background(), args...) + + if err != nil { + return nil, fmt.Errorf("running terraform query command: %w", err) + } + + for msg := range logs { + if msg.Msg == nil { + continue + } + + if msg.Err != nil { + return nil, fmt.Errorf("retrieving message: %w", msg.Err) + } + + if msg.Msg.Level() == tfjson.Error { + // TODO reimplement missing .tf config error + diags = append(diags, msg.Msg) + continue + } + messages = append(messages, msg.Msg) + } + + if len(diags) > 0 { + return nil, fmt.Errorf("running terraform query command returned diagnostics: %+v", diags) + } + + logging.HelperResourceTrace(ctx, "Called Terraform CLI providers query command") + + return messages, nil +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/internal/teststep/config.go b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/teststep/config.go new file mode 100644 index 0000000000..5df477f085 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/teststep/config.go @@ -0,0 +1,269 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package teststep + +import ( + "context" + "fmt" + "io" + "os" + "path/filepath" + "regexp" + "strings" + + "github.com/hashicorp/terraform-plugin-testing/config" +) + +const ( + rawConfigFileName = "terraform_plugin_test.tf" + rawConfigFileNameJSON = rawConfigFileName + ".json" + rawQueryConfigFileName = "terraform_plugin_test.tfquery.hcl" +) + +var ( + // Expected to match: + // provider "example" { + // provider "example"{ + // provider example { + // provider example{ + // provider"example"{ + providerConfigBlockRegex = regexp.MustCompile(`provider(\s*"[a-zA-Z0-9_-]+"\s*|\s+[a-zA-Z0-9_-]+\s*){`) + // Expected to match: + // terraform { + // terraform{ + terraformConfigBlockRegex = regexp.MustCompile(`terraform\s*{`) +) + +// Config defines an interface implemented by all types +// that represent Terraform configuration: +// +// - [config.configurationDirectory] +// - [config.configurationFile] +// - [config.configurationString] +type Config interface { + HasConfigurationFiles() bool + HasProviderBlock(context.Context) (bool, error) + HasTerraformBlock(context.Context) (bool, error) + Write(context.Context, string) error + Append(string) Config + WriteQuery(context.Context, string) error +} + +// PrepareConfigurationRequest is used to simplify the generation of +// a ConfigurationRequest which is required when calling the +// Configuration func. +type PrepareConfigurationRequest struct { + Directory config.TestStepConfigFunc + File config.TestStepConfigFunc + Raw string + TestStepConfigRequest config.TestStepConfigRequest +} + +// Exec returns a Configuration request which is required when +// calling the Configuration func. +func (p PrepareConfigurationRequest) Exec() ConfigurationRequest { + directory := Pointer(p.Directory.Exec(p.TestStepConfigRequest)) + file := Pointer(p.File.Exec(p.TestStepConfigRequest)) + raw := Pointer(p.Raw) + + return ConfigurationRequest{ + Directory: directory, + File: file, + Raw: raw, + } +} + +// ConfigurationRequest is used by the Configuration func to determine +// the underlying type to instantiate. +type ConfigurationRequest struct { + Directory *string + File *string + Raw *string +} + +// Validate ensures that only one of Directory, File or Raw are non-empty. +func (c ConfigurationRequest) Validate() error { + var configSet []string + + if c.Directory != nil && *c.Directory != "" { + configSet = append(configSet, "directory") + } + + if c.File != nil && *c.File != "" { + configSet = append(configSet, "file") + } + + if c.Raw != nil && *c.Raw != "" { + configSet = append(configSet, "raw") + } + + if len(configSet) > 1 { + configSetStr := strings.Join(configSet, `, `) + + i := strings.LastIndex(configSetStr, ", ") + + if i != -1 { + configSetStr = configSetStr[:i] + " and " + configSetStr[i+len(", "):] + } + + return fmt.Errorf(`%s are populated, only one of "directory", "file", or "raw" is allowed`, configSetStr) + } + + return nil +} + +// Configuration uses the supplied ConfigurationRequest to determine +// which of the types that implement Config to instantiate. If none +// of the fields in ConfigurationRequest are populated nil is returned. +func Configuration(req ConfigurationRequest) Config { + if req.Directory != nil && *req.Directory != "" { + return configurationDirectory{ + directory: *req.Directory, + } + } + + if req.File != nil && *req.File != "" { + return configurationFile{ + file: *req.File, + } + } + + if req.Raw != nil && *req.Raw != "" { + return configurationString{ + raw: *req.Raw, + } + } + + return nil +} + +// copyFiles accepts a path to a directory and a destination. Only +// files in the path directory are copied, any nested directories +// are ignored. +func copyFiles(path string, dstPath string) error { + infos, err := os.ReadDir(path) + + if err != nil { + return err + } + + for _, info := range infos { + srcPath := filepath.Join(path, info.Name()) + + if info.IsDir() { + continue + } else { + _, err = copyFile(srcPath, dstPath) + + if err != nil { + return err + } + } + + } + return nil +} + +// copyFile accepts a path to a file and a destination, +// copying the file from path to destination. +func copyFile(path string, dstPath string) (string, error) { + srcF, err := os.Open(path) + + if err != nil { + return "", err + } + + defer srcF.Close() + + di, err := os.Stat(dstPath) + + if err != nil { + return "", err + } + + if di.IsDir() { + _, file := filepath.Split(path) + dstPath = filepath.Join(dstPath, file) + } + + dstF, err := os.Create(dstPath) + + if err != nil { + return "", err + } + + defer dstF.Close() + + if _, err := io.Copy(dstF, srcF); err != nil { + return "", err + } + + return dstPath, nil +} + +// appendToFile accepts a path to a file and a string, +// appending the file from path to destination. +func appendToFile(path string, content string) error { + f, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, os.ModeAppend) + if err != nil { + return err + } + defer f.Close() + + if _, err := io.WriteString(f, content); err != nil { + return err + } + + return nil +} + +// filesContains accepts a string representing a directory and a +// regular expression. For each file that is found within the +// directory fileContains func is called. Any nested directories +// within the directory specified by dir are ignored. +func filesContains(dir string, find *regexp.Regexp) (bool, error) { + dirEntries, err := os.ReadDir(dir) + + if err != nil { + return false, err + } + + for _, dirEntry := range dirEntries { + if dirEntry.IsDir() { + continue + } + + path := filepath.Join(dir, dirEntry.Name()) + + contains, err := fileContains(path, find) + + if err != nil { + return false, err + } + + if contains { + return true, nil + } + } + + return false, nil +} + +// fileContains accepts a path and a regular expression. The +// file is read and the supplied regular expression is used +// to determine whether the file contains the specified string. +func fileContains(path string, find *regexp.Regexp) (bool, error) { + f, err := os.ReadFile(path) + + if err != nil { + return false, err + } + + return find.MatchString(string(f)), nil +} + +// Pointer returns a pointer to any type. +func Pointer[T any](in T) *T { + return &in +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/internal/teststep/directory.go b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/teststep/directory.go new file mode 100644 index 0000000000..1afc45a2df --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/teststep/directory.go @@ -0,0 +1,132 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package teststep + +import ( + "context" + "fmt" + "hash/crc32" + "os" + "path/filepath" +) + +var _ Config = configurationDirectory{} + +type configurationDirectory struct { + directory string + + // appendedConfig is a map of filenames to content + appendedConfig map[string]string +} + +// HasConfigurationFiles is used during validation to ensure that +// ExternalProviders are not declared at the TestCase or TestStep +// level when using TestStep.ConfigDirectory. +func (c configurationDirectory) HasConfigurationFiles() bool { + return true +} + +// HasProviderBlock returns true if the Config has declared a provider +// configuration block, e.g. provider "examplecloud" {...} +func (c configurationDirectory) HasProviderBlock(ctx context.Context) (bool, error) { + configDirectory := c.directory + + if !filepath.IsAbs(configDirectory) { + pwd, err := os.Getwd() + + if err != nil { + return false, err + } + + configDirectory = filepath.Join(pwd, configDirectory) + } + + contains, err := filesContains(configDirectory, providerConfigBlockRegex) + + if err != nil { + return false, err + } + + return contains, nil +} + +// HasTerraformBlock returns true if the Config has declared a terraform +// configuration block, e.g. terraform {...} +func (c configurationDirectory) HasTerraformBlock(ctx context.Context) (bool, error) { + configDirectory := c.directory + + if !filepath.IsAbs(configDirectory) { + pwd, err := os.Getwd() + + if err != nil { + return false, err + } + + configDirectory = filepath.Join(pwd, configDirectory) + } + + contains, err := filesContains(configDirectory, terraformConfigBlockRegex) + + if err != nil { + return false, err + } + + return contains, nil +} + +func (c configurationDirectory) WriteQuery(ctx context.Context, dest string) error { + panic("WriteQuery not supported for configurationDirectory") +} + +// Write copies all files from directory to destination. +func (c configurationDirectory) Write(ctx context.Context, dest string) error { + configDirectory := c.directory + + if !filepath.IsAbs(configDirectory) { + pwd, err := os.Getwd() + + if err != nil { + return err + } + + configDirectory = filepath.Join(pwd, configDirectory) + } + + err := copyFiles(configDirectory, dest) + if err != nil { + return err + } + + err = c.writeAppendedConfig(dest) + if err != nil { + return err + } + + return nil +} + +func (c configurationDirectory) Append(config string) Config { + if c.appendedConfig == nil { + c.appendedConfig = make(map[string]string) + } + + checksum := crc32.Checksum([]byte(config), crc32.IEEETable) + filename := fmt.Sprintf("terraform_plugin_test_%d.tf", checksum) + + c.appendedConfig[filename] = config + return c +} + +func (c configurationDirectory) writeAppendedConfig(dstPath string) error { + for filename, config := range c.appendedConfig { + outFilename := filepath.Join(dstPath, filename) + + err := os.WriteFile(outFilename, []byte(config), 0700) + if err != nil { + return err + } + } + + return nil +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/internal/teststep/file.go b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/teststep/file.go new file mode 100644 index 0000000000..a4862b05d4 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/teststep/file.go @@ -0,0 +1,112 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package teststep + +import ( + "context" + "os" + "path/filepath" +) + +var _ Config = configurationFile{} + +type configurationFile struct { + file string + appendedConfig string +} + +// HasConfigurationFiles is used during validation to ensure that +// ExternalProviders are not declared at the TestCase or TestStep +// level when using TestStep.ConfigFile. +func (c configurationFile) HasConfigurationFiles() bool { + return true +} + +// HasProviderBlock returns true if the Config has declared a provider +// configuration block, e.g. provider "examplecloud" {...} +func (c configurationFile) HasProviderBlock(ctx context.Context) (bool, error) { + configFile := c.file + + if !filepath.IsAbs(configFile) { + pwd, err := os.Getwd() + + if err != nil { + return false, err + } + + configFile = filepath.Join(pwd, configFile) + } + + contains, err := fileContains(configFile, providerConfigBlockRegex) + + if err != nil { + return false, err + } + + return contains, nil +} + +// HasTerraformBlock returns true if the Config has declared a terraform +// configuration block, e.g. terraform {...} +func (c configurationFile) HasTerraformBlock(ctx context.Context) (bool, error) { + configFile := c.file + + if !filepath.IsAbs(configFile) { + pwd, err := os.Getwd() + + if err != nil { + return false, err + } + + configFile = filepath.Join(pwd, configFile) + } + + contains, err := fileContains(configFile, terraformConfigBlockRegex) + + if err != nil { + return false, err + } + + return contains, nil +} + +func (c configurationFile) WriteQuery(ctx context.Context, dest string) error { + panic("WriteQuery not supported for configurationFile") +} + +// Write copies file from c.file to destination. +func (c configurationFile) Write(ctx context.Context, dest string) error { + configFile := c.file + + if !filepath.IsAbs(configFile) { + pwd, err := os.Getwd() + + if err != nil { + return err + } + + configFile = filepath.Join(pwd, configFile) + } + + destPath, err := copyFile(configFile, dest) + if err != nil { + return err + } + + if len(c.appendedConfig) > 0 { + err := appendToFile(destPath, c.appendedConfig) + if err != nil { + return err + } + } + + return nil +} + +func (c configurationFile) Append(config string) Config { + return configurationFile{ + file: c.file, + appendedConfig: config, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/internal/teststep/string.go b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/teststep/string.go new file mode 100644 index 0000000000..ccb8fb3e85 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/teststep/string.go @@ -0,0 +1,82 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package teststep + +import ( + "context" + "encoding/json" + "fmt" + "os" + "path/filepath" + "strings" +) + +var _ Config = configurationString{} + +type configurationString struct { + raw string +} + +// HasConfigurationFiles is used during validation to allow declaration +// of ExternalProviders at the TestCase or TestStep level when using +// TestStep.Config. +func (c configurationString) HasConfigurationFiles() bool { + return false +} + +// HasProviderBlock returns true if the Config has declared a provider +// configuration block, e.g. provider "examplecloud" {...} +func (c configurationString) HasProviderBlock(ctx context.Context) (bool, error) { + return providerConfigBlockRegex.MatchString(c.raw), nil +} + +// HasTerraformBlock returns true if the Config has declared a terraform +// configuration block, e.g. terraform {...} +func (c configurationString) HasTerraformBlock(ctx context.Context) (bool, error) { + return terraformConfigBlockRegex.MatchString(c.raw), nil +} + +// Write creates a file and writes c.raw into it. +func (c configurationString) Write(ctx context.Context, dest string) error { + outFilename := filepath.Join(dest, rawConfigFileName) + rmFilename := filepath.Join(dest, rawConfigFileNameJSON) + + bCfg := []byte(c.raw) + + if json.Valid(bCfg) { + outFilename, rmFilename = rmFilename, outFilename + } + + if err := os.Remove(rmFilename); err != nil && !os.IsNotExist(err) { + return fmt.Errorf("unable to remove %q: %w", rmFilename, err) + } + + err := os.WriteFile(outFilename, bCfg, 0700) + + if err != nil { + return err + } + + return nil +} + +// WriteQuery creates a file and writes c.raw into it. +func (c configurationString) WriteQuery(ctx context.Context, dest string) error { + outFilename := filepath.Join(dest, rawQueryConfigFileName) + + bCfg := []byte(c.raw) + + err := os.WriteFile(outFilename, bCfg, 0700) + if err != nil { + return err + } + + return nil +} + +func (c configurationString) Append(config string) Config { + return configurationString{ + raw: strings.Join([]string{c.raw, config}, "\n"), + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/internal/tfdiags/config_traversals.go b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/tfdiags/config_traversals.go new file mode 100644 index 0000000000..6208117cbf --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/tfdiags/config_traversals.go @@ -0,0 +1,59 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package tfdiags + +import ( + "bytes" + "fmt" + "strconv" + + "github.com/hashicorp/go-cty/cty" +) + +// FormatCtyPath is a helper function to produce a user-friendly string +// representation of a cty.Path. The result uses a syntax similar to the +// HCL expression language in the hope of it being familiar to users. +func FormatCtyPath(path cty.Path) string { + var buf bytes.Buffer + for _, step := range path { + switch ts := step.(type) { + case cty.GetAttrStep: + fmt.Fprintf(&buf, ".%s", ts.Name) + case cty.IndexStep: + buf.WriteByte('[') + key := ts.Key + keyTy := key.Type() + switch { + case key.IsNull(): + buf.WriteString("null") + case !key.IsKnown(): + buf.WriteString("(not yet known)") + case keyTy == cty.Number: + bf := key.AsBigFloat() + buf.WriteString(bf.Text('g', -1)) + case keyTy == cty.String: + buf.WriteString(strconv.Quote(key.AsString())) + default: + buf.WriteString("...") + } + buf.WriteByte(']') + } + } + return buf.String() +} + +// FormatError is a helper function to produce a user-friendly string +// representation of certain special error types that we might want to +// include in diagnostic messages. +// +// This currently has special behavior only for cty.PathError, where a +// non-empty path is rendered in a HCL-like syntax as context. +func FormatError(err error) string { + perr, ok := err.(cty.PathError) + if !ok || len(perr.Path) == 0 { + return err.Error() + } + + return fmt.Sprintf("%s: %s", FormatCtyPath(perr.Path), perr.Error()) +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/internal/tfdiags/contextual.go b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/tfdiags/contextual.go new file mode 100644 index 0000000000..a9b5c7e83e --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/tfdiags/contextual.go @@ -0,0 +1,84 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package tfdiags + +import ( + "github.com/hashicorp/go-cty/cty" +) + +// AttributeValue returns a diagnostic about an attribute value in an implied current +// configuration context. This should be returned only from functions whose +// interface specifies a clear configuration context that this will be +// resolved in. +// +// The given path is relative to the implied configuration context. To describe +// a top-level attribute, it should be a single-element cty.Path with a +// cty.GetAttrStep. It's assumed that the path is returning into a structure +// that would be produced by our conventions in the configschema package; it +// may return unexpected results for structures that can't be represented by +// configschema. +// +// Since mapping attribute paths back onto configuration is an imprecise +// operation (e.g. dynamic block generation may cause the same block to be +// evaluated multiple times) the diagnostic detail should include the attribute +// name and other context required to help the user understand what is being +// referenced in case the identified source range is not unique. +// +// The returned attribute will not have source location information until +// context is applied to the containing diagnostics using diags.InConfigBody. +// After context is applied, the source location is the value assigned to the +// named attribute, or the containing body's "missing item range" if no +// value is present. +func AttributeValue(severity Severity, summary, detail string, attrPath cty.Path) Diagnostic { + return &attributeDiagnostic{ + diagnosticBase: diagnosticBase{ + severity: severity, + summary: summary, + detail: detail, + }, + attrPath: attrPath, + } +} + +// GetAttribute extracts an attribute cty.Path from a diagnostic if it contains +// one. Normally this is not accessed directly, and instead the config body is +// added to the Diagnostic to create a more complete message for the user. In +// some cases however, we may want to know just the name of the attribute that +// generated the Diagnostic message. +// This returns a nil cty.Path if it does not exist in the Diagnostic. +func GetAttribute(d Diagnostic) cty.Path { + if d, ok := d.(*attributeDiagnostic); ok { + return d.attrPath + } + return nil +} + +type attributeDiagnostic struct { + diagnosticBase + attrPath cty.Path +} + +// WholeContainingBody returns a diagnostic about the body that is an implied +// current configuration context. This should be returned only from +// functions whose interface specifies a clear configuration context that this +// will be resolved in. +// +// The returned attribute will not have source location information until +// context is applied to the containing diagnostics using diags.InConfigBody. +// After context is applied, the source location is currently the missing item +// range of the body. In future, this may change to some other suitable +// part of the containing body. +func WholeContainingBody(severity Severity, summary, detail string) Diagnostic { + return &wholeBodyDiagnostic{ + diagnosticBase: diagnosticBase{ + severity: severity, + summary: summary, + detail: detail, + }, + } +} + +type wholeBodyDiagnostic struct { + diagnosticBase +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/internal/tfdiags/diagnostic.go b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/tfdiags/diagnostic.go new file mode 100644 index 0000000000..547271346a --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/tfdiags/diagnostic.go @@ -0,0 +1,26 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package tfdiags + +type Diagnostic interface { + Severity() Severity + Description() Description +} + +type Severity rune + +// This code was previously generated with a go:generate directive calling: +// go run golang.org/x/tools/cmd/stringer -type=Severity +// However, it is now considered frozen and the tooling dependency has been +// removed. The String method can be manually updated if necessary. + +const ( + Error Severity = 'E' + Warning Severity = 'W' +) + +type Description struct { + Summary string + Detail string +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/internal/tfdiags/diagnostic_base.go b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/tfdiags/diagnostic_base.go new file mode 100644 index 0000000000..505692ce51 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/tfdiags/diagnostic_base.go @@ -0,0 +1,34 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package tfdiags + +// diagnosticBase can be embedded in other diagnostic structs to get +// default implementations of Severity and Description. This type also +// has default implementations of Source that return no source +// location or expression-related information, so embedders should generally +// override those method to return more useful results where possible. +type diagnosticBase struct { + severity Severity + summary string + detail string +} + +func (d diagnosticBase) Severity() Severity { + return d.severity +} + +func (d diagnosticBase) Description() Description { + return Description{ + Summary: d.summary, + Detail: d.detail, + } +} + +func Diag(sev Severity, summary, detail string) Diagnostic { + return &diagnosticBase{ + severity: sev, + summary: summary, + detail: detail, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/internal/tfdiags/diagnostics.go b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/tfdiags/diagnostics.go new file mode 100644 index 0000000000..4fc99c1bb7 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/tfdiags/diagnostics.go @@ -0,0 +1,196 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package tfdiags + +import ( + "bytes" + "fmt" + "sort" +) + +// Diagnostics is a list of diagnostics. Diagnostics is intended to be used +// where a Go "error" might normally be used, allowing richer information +// to be conveyed (more context, support for warnings). +// +// A nil Diagnostics is a valid, empty diagnostics list, thus allowing +// heap allocation to be avoided in the common case where there are no +// diagnostics to report at all. +type Diagnostics []Diagnostic + +// HasErrors returns true if any of the diagnostics in the list have +// a severity of Error. +func (diags Diagnostics) HasErrors() bool { + for _, diag := range diags { + if diag.Severity() == Error { + return true + } + } + return false +} + +// Err flattens a diagnostics list into a single Go error, or to nil +// if the diagnostics list does not include any error-level diagnostics. +// +// This can be used to smuggle diagnostics through an API that deals in +// native errors, but unfortunately it will lose naked warnings (warnings +// that aren't accompanied by at least one error) since such APIs have no +// mechanism through which to report these. +// +// return result, diags.Error() +func (diags Diagnostics) Err() error { + if !diags.HasErrors() { + return nil + } + return diagnosticsAsError{diags} +} + +// ErrWithWarnings is similar to Err except that it will also return a non-nil +// error if the receiver contains only warnings. +// +// In the warnings-only situation, the result is guaranteed to be of dynamic +// type NonFatalError, allowing diagnostics-aware callers to type-assert +// and unwrap it, treating it as non-fatal. +// +// This should be used only in contexts where the caller is able to recognize +// and handle NonFatalError. For normal callers that expect a lack of errors +// to be signaled by nil, use just Diagnostics.Err. +func (diags Diagnostics) ErrWithWarnings() error { + if len(diags) == 0 { + return nil + } + if diags.HasErrors() { + return diags.Err() + } + return NonFatalError{diags} +} + +// NonFatalErr is similar to Err except that it always returns either nil +// (if there are no diagnostics at all) or NonFatalError. +// +// This allows diagnostics to be returned over an error return channel while +// being explicit that the diagnostics should not halt processing. +// +// This should be used only in contexts where the caller is able to recognize +// and handle NonFatalError. For normal callers that expect a lack of errors +// to be signaled by nil, use just Diagnostics.Err. +func (diags Diagnostics) NonFatalErr() error { + if len(diags) == 0 { + return nil + } + return NonFatalError{diags} +} + +type diagnosticsAsError struct { + Diagnostics +} + +func (dae diagnosticsAsError) Error() string { + diags := dae.Diagnostics + switch { + case len(diags) == 0: + // should never happen, since we don't create this wrapper if + // there are no diagnostics in the list. + return "no errors" + case len(diags) == 1: + desc := diags[0].Description() + if desc.Detail == "" { + return desc.Summary + } + return fmt.Sprintf("%s: %s", desc.Summary, desc.Detail) + default: + var ret bytes.Buffer + fmt.Fprintf(&ret, "%d problems:\n", len(diags)) + for _, diag := range dae.Diagnostics { + desc := diag.Description() + if desc.Detail == "" { + fmt.Fprintf(&ret, "\n- %s", desc.Summary) + } else { + fmt.Fprintf(&ret, "\n- %s: %s", desc.Summary, desc.Detail) + } + } + return ret.String() + } +} + +// WrappedErrors is an implementation of errwrap.Wrapper so that an error-wrapped +// diagnostics object can be picked apart by errwrap-aware code. +func (dae diagnosticsAsError) WrappedErrors() []error { + var errs []error + for _, diag := range dae.Diagnostics { + if wrapper, isErr := diag.(nativeError); isErr { + errs = append(errs, wrapper.err) + } + } + return errs +} + +// NonFatalError is a special error type, returned by +// Diagnostics.ErrWithWarnings and Diagnostics.NonFatalErr, +// that indicates that the wrapped diagnostics should be treated as non-fatal. +// Callers can conditionally type-assert an error to this type in order to +// detect the non-fatal scenario and handle it in a different way. +type NonFatalError struct { + Diagnostics +} + +func (woe NonFatalError) Error() string { + diags := woe.Diagnostics + switch { + case len(diags) == 0: + // should never happen, since we don't create this wrapper if + // there are no diagnostics in the list. + return "no errors or warnings" + case len(diags) == 1: + desc := diags[0].Description() + if desc.Detail == "" { + return desc.Summary + } + return fmt.Sprintf("%s: %s", desc.Summary, desc.Detail) + default: + var ret bytes.Buffer + if diags.HasErrors() { + fmt.Fprintf(&ret, "%d problems:\n", len(diags)) + } else { + fmt.Fprintf(&ret, "%d warnings:\n", len(diags)) + } + for _, diag := range woe.Diagnostics { + desc := diag.Description() + if desc.Detail == "" { + fmt.Fprintf(&ret, "\n- %s", desc.Summary) + } else { + fmt.Fprintf(&ret, "\n- %s: %s", desc.Summary, desc.Detail) + } + } + return ret.String() + } +} + +// sortDiagnostics is an implementation of sort.Interface +type sortDiagnostics []Diagnostic + +var _ sort.Interface = sortDiagnostics(nil) + +func (sd sortDiagnostics) Len() int { + return len(sd) +} + +func (sd sortDiagnostics) Less(i, j int) bool { + iD, jD := sd[i], sd[j] + iSev, jSev := iD.Severity(), jD.Severity() + + switch { + case iSev != jSev: + return iSev == Warning + default: + // The remaining properties do not have a defined ordering, so + // we'll leave it unspecified. Since we use sort.Stable in + // the caller of this, the ordering of remaining items will + // be preserved. + return false + } +} + +func (sd sortDiagnostics) Swap(i, j int) { + sd[i], sd[j] = sd[j], sd[i] +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/internal/tfdiags/doc.go b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/tfdiags/doc.go new file mode 100644 index 0000000000..23be0a8bec --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/tfdiags/doc.go @@ -0,0 +1,19 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// Package tfdiags is a utility package for representing errors and +// warnings in a manner that allows us to produce good messages for the +// user. +// +// "diag" is short for "diagnostics", and is meant as a general word for +// feedback to a user about potential or actual problems. +// +// A design goal for this package is for it to be able to provide rich +// messaging where possible but to also be pragmatic about dealing with +// generic errors produced by system components that _can't_ provide +// such rich messaging. As a consequence, the main types in this package -- +// Diagnostics and Diagnostic -- are designed so that they can be "smuggled" +// over an error channel and then be unpacked at the other end, so that +// error diagnostics (at least) can transit through APIs that are not +// aware of this package. +package tfdiags diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/internal/tfdiags/error.go b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/tfdiags/error.go new file mode 100644 index 0000000000..f7c9c65d38 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/tfdiags/error.go @@ -0,0 +1,27 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package tfdiags + +// nativeError is a Diagnostic implementation that wraps a normal Go error +type nativeError struct { + err error +} + +var _ Diagnostic = nativeError{} + +func (e nativeError) Severity() Severity { + return Error +} + +func (e nativeError) Description() Description { + return Description{ + Summary: FormatError(e.err), + } +} + +func FromError(err error) Diagnostic { + return &nativeError{ + err: err, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/internal/tfdiags/severity_string.go b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/tfdiags/severity_string.go new file mode 100644 index 0000000000..78a721068c --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/tfdiags/severity_string.go @@ -0,0 +1,29 @@ +// Code generated by "stringer -type=Severity"; DO NOT EDIT. + +package tfdiags + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[Error-69] + _ = x[Warning-87] +} + +const ( + _Severity_name_0 = "Error" + _Severity_name_1 = "Warning" +) + +func (i Severity) String() string { + switch { + case i == 69: + return _Severity_name_0 + case i == 87: + return _Severity_name_1 + default: + return "Severity(" + strconv.FormatInt(int64(i), 10) + ")" + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/internal/tfdiags/simple_warning.go b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/tfdiags/simple_warning.go new file mode 100644 index 0000000000..0c90c47889 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/internal/tfdiags/simple_warning.go @@ -0,0 +1,23 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package tfdiags + +type simpleWarning string + +var _ Diagnostic = simpleWarning("") + +// SimpleWarning constructs a simple (summary-only) warning diagnostic. +func SimpleWarning(msg string) Diagnostic { + return simpleWarning(msg) +} + +func (e simpleWarning) Severity() Severity { + return Warning +} + +func (e simpleWarning) Description() Description { + return Description{ + Summary: string(e), + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/bool.go b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/bool.go new file mode 100644 index 0000000000..fba10ee863 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/bool.go @@ -0,0 +1,44 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package knownvalue + +import ( + "fmt" + "strconv" +) + +var _ Check = boolValue{} + +type boolValue struct { + value bool +} + +// CheckValue determines whether the passed value is of type bool, and +// contains a matching bool value. +func (v boolValue) CheckValue(other any) error { + otherVal, ok := other.(bool) + + if !ok { + return fmt.Errorf("expected bool value for Bool check, got: %T", other) + } + + if otherVal != v.value { + return fmt.Errorf("expected value %t for Bool check, got: %t", v.value, otherVal) + } + + return nil +} + +// String returns the string representation of the bool value. +func (v boolValue) String() string { + return strconv.FormatBool(v.value) +} + +// Bool returns a Check for asserting equality between the +// supplied bool and the value passed to the CheckValue method. +func Bool(value bool) boolValue { + return boolValue{ + value: value, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/bool_func.go b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/bool_func.go new file mode 100644 index 0000000000..15135c10f1 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/bool_func.go @@ -0,0 +1,39 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package knownvalue + +import "fmt" + +var _ Check = boolFunc{} + +type boolFunc struct { + checkFunc func(v bool) error +} + +// CheckValue determines whether the passed value is of type bool, and +// returns no error from the provided check function +func (v boolFunc) CheckValue(other any) error { + val, ok := other.(bool) + + if !ok { + return fmt.Errorf("expected bool value for BoolFunc check, got: %T", other) + } + + return v.checkFunc(val) +} + +// String returns the bool representation of the value. +func (v boolFunc) String() string { + // Validation is up the the implementer of the function, so there are no + // bool literal or regex comparers to print here + return "BoolFunc" +} + +// BoolFunc returns a Check for passing the bool value in state +// to the provided check function +func BoolFunc(fn func(v bool) error) boolFunc { + return boolFunc{ + checkFunc: fn, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/check.go b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/check.go new file mode 100644 index 0000000000..cef532c94b --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/check.go @@ -0,0 +1,14 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package knownvalue + +// Check defines an interface that is implemented to determine whether type and value match. Individual +// implementations determine how the match is performed (e.g., exact match, partial match). +type Check interface { + // CheckValue should assert the given known value against any expectations. Use the error + // return to signal unexpected values or implementation errors. + CheckValue(value any) error + // String should return a string representation of the type and value. + String() string +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/doc.go b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/doc.go new file mode 100644 index 0000000000..4041c3e935 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/doc.go @@ -0,0 +1,5 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// Package knownvalue contains the known value interface, and types implementing the known value interface. +package knownvalue diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/float32.go b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/float32.go new file mode 100644 index 0000000000..ee02fdcb1a --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/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/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/float32_func.go b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/float32_func.go new file mode 100644 index 0000000000..5d3b55fcd6 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/float32_func.go @@ -0,0 +1,48 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package knownvalue + +import ( + "encoding/json" + "fmt" + "strconv" +) + +var _ Check = float32Func{} + +type float32Func struct { + checkFunc func(v float32) error +} + +// CheckValue determines whether the passed value is of type float32, and +// returns no error from the provided check function +func (v float32Func) CheckValue(other any) error { + jsonNum, ok := other.(json.Number) + + if !ok { + return fmt.Errorf("expected json.Number value for Float32Func 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 Float32Func check: %s", err) + } + + return v.checkFunc(float32(otherVal)) +} + +// String returns the float32 representation of the value. +func (v float32Func) String() string { + // Validation is up the the implementer of the function, so there are no + // float32 literal or regex comparers to print here + return "Float32Func" +} + +// Float32Func returns a Check for passing the float32 value in state +// to the provided check function +func Float32Func(fn func(v float32) error) float32Func { + return float32Func{ + checkFunc: fn, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/float64.go b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/float64.go new file mode 100644 index 0000000000..bacdaa6faf --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/float64.go @@ -0,0 +1,51 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package knownvalue + +import ( + "encoding/json" + "fmt" + "strconv" +) + +var _ Check = float64Exact{} + +type float64Exact struct { + value float64 +} + +// CheckValue determines whether the passed value is of type float64, and +// contains a matching float64 value. +func (v float64Exact) CheckValue(other any) error { + jsonNum, ok := other.(json.Number) + + if !ok { + return fmt.Errorf("expected json.Number value for Float64Exact check, got: %T", other) + } + + otherVal, err := jsonNum.Float64() + + if err != nil { + return fmt.Errorf("expected json.Number to be parseable as float64 value for Float64Exact check: %s", err) + } + + if otherVal != v.value { + return fmt.Errorf("expected value %s for Float64Exact check, got: %s", v.String(), strconv.FormatFloat(otherVal, 'f', -1, 64)) + } + + return nil +} + +// String returns the string representation of the float64 value. +func (v float64Exact) String() string { + return strconv.FormatFloat(v.value, 'f', -1, 64) +} + +// Float64Exact returns a Check for asserting equality between the +// supplied float64 and the value passed to the CheckValue method. +func Float64Exact(value float64) float64Exact { + return float64Exact{ + value: value, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/float64_func.go b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/float64_func.go new file mode 100644 index 0000000000..56f17e0c16 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/float64_func.go @@ -0,0 +1,48 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package knownvalue + +import ( + "encoding/json" + "fmt" + "strconv" +) + +var _ Check = float64Func{} + +type float64Func struct { + checkFunc func(v float64) error +} + +// CheckValue determines whether the passed value is of type float64, and +// returns no error from the provided check function +func (v float64Func) CheckValue(other any) error { + jsonNum, ok := other.(json.Number) + + if !ok { + return fmt.Errorf("expected json.Number value for Float64Func check, got: %T", other) + } + + otherVal, err := strconv.ParseFloat(string(jsonNum), 64) + if err != nil { + return fmt.Errorf("expected json.Number to be parseable as float64 value for Float64Func check: %s", err) + } + + return v.checkFunc(otherVal) +} + +// String returns the float64 representation of the value. +func (v float64Func) String() string { + // Validation is up the the implementer of the function, so there are no + // float64 literal or regex comparers to print here + return "Float64Func" +} + +// Float64Func returns a Check for passing the float64 value in state +// to the provided check function +func Float64Func(fn func(v float64) error) float64Func { + return float64Func{ + checkFunc: fn, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/int32.go b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/int32.go new file mode 100644 index 0000000000..49dd30bb3c --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/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/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/int32_func.go b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/int32_func.go new file mode 100644 index 0000000000..587f7aae62 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/int32_func.go @@ -0,0 +1,48 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package knownvalue + +import ( + "encoding/json" + "fmt" + "strconv" +) + +var _ Check = int32Func{} + +type int32Func struct { + checkFunc func(v int32) error +} + +// CheckValue determines whether the passed value is of type int32, and +// returns no error from the provided check function +func (v int32Func) CheckValue(other any) error { + jsonNum, ok := other.(json.Number) + + if !ok { + return fmt.Errorf("expected json.Number value for Int32Func 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 Int32Func check: %s", err) + } + + return v.checkFunc(int32(otherVal)) +} + +// String returns the int32 representation of the value. +func (v int32Func) String() string { + // Validation is up the the implementer of the function, so there are no + // int32 literal or regex comparers to print here + return "Int32Func" +} + +// Int32Func returns a Check for passing the int32 value in state +// to the provided check function +func Int32Func(fn func(v int32) error) int32Func { + return int32Func{ + checkFunc: fn, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/int64.go b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/int64.go new file mode 100644 index 0000000000..19f8036228 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/int64.go @@ -0,0 +1,51 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package knownvalue + +import ( + "encoding/json" + "fmt" + "strconv" +) + +var _ Check = int64Exact{} + +type int64Exact struct { + value int64 +} + +// CheckValue determines whether the passed value is of type int64, and +// contains a matching int64 value. +func (v int64Exact) CheckValue(other any) error { + jsonNum, ok := other.(json.Number) + + if !ok { + return fmt.Errorf("expected json.Number value for Int64Exact check, got: %T", other) + } + + otherVal, err := jsonNum.Int64() + + if err != nil { + return fmt.Errorf("expected json.Number to be parseable as int64 value for Int64Exact check: %s", err) + } + + if otherVal != v.value { + return fmt.Errorf("expected value %d for Int64Exact check, got: %d", v.value, otherVal) + } + + return nil +} + +// String returns the string representation of the int64 value. +func (v int64Exact) String() string { + return strconv.FormatInt(v.value, 10) +} + +// Int64Exact returns a Check for asserting equality between the +// supplied int64 and the value passed to the CheckValue method. +func Int64Exact(value int64) int64Exact { + return int64Exact{ + value: value, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/int64_func.go b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/int64_func.go new file mode 100644 index 0000000000..1269355c49 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/int64_func.go @@ -0,0 +1,48 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package knownvalue + +import ( + "encoding/json" + "fmt" + "strconv" +) + +var _ Check = int64Func{} + +type int64Func struct { + checkFunc func(v int64) error +} + +// CheckValue determines whether the passed value is of type int64, and +// returns no error from the provided check function +func (v int64Func) CheckValue(other any) error { + jsonNum, ok := other.(json.Number) + + if !ok { + return fmt.Errorf("expected json.Number value for Int64Func check, got: %T", other) + } + + otherVal, err := strconv.ParseInt(string(jsonNum), 10, 64) + if err != nil { + return fmt.Errorf("expected json.Number to be parseable as int64 value for Int64Func check: %s", err) + } + + return v.checkFunc(otherVal) +} + +// String returns the int64 representation of the value. +func (v int64Func) String() string { + // Validation is up the the implementer of the function, so there are no + // int64 literal or regex comparers to print here + return "Int64Func" +} + +// Int64Func returns a Check for passing the int64 value in state +// to the provided check function +func Int64Func(fn func(v int64) error) int64Func { + return int64Func{ + checkFunc: fn, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/list.go b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/list.go new file mode 100644 index 0000000000..5001752412 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/list.go @@ -0,0 +1,67 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package knownvalue + +import ( + "fmt" +) + +var _ Check = listExact{} + +type listExact struct { + value []Check +} + +// CheckValue determines whether the passed value is of type []any, and +// contains matching slice entries in the same sequence. +func (v listExact) CheckValue(other any) error { + otherVal, ok := other.([]any) + + if !ok { + return fmt.Errorf("expected []any value for ListExact check, got: %T", other) + } + + if len(otherVal) != len(v.value) { + expectedElements := "elements" + actualElements := "elements" + + if len(v.value) == 1 { + expectedElements = "element" + } + + if len(otherVal) == 1 { + actualElements = "element" + } + + return fmt.Errorf("expected %d %s for ListExact check, got %d %s", len(v.value), expectedElements, len(otherVal), actualElements) + } + + for i := 0; i < len(v.value); i++ { + if err := v.value[i].CheckValue(otherVal[i]); err != nil { + return fmt.Errorf("list element index %d: %s", i, err) + } + } + + return nil +} + +// String returns the string representation of the value. +func (v listExact) String() string { + var listVals []string + + for _, val := range v.value { + listVals = append(listVals, val.String()) + } + + return fmt.Sprintf("%s", listVals) +} + +// ListExact returns a Check for asserting equality between the +// supplied []Check and the value passed to the CheckValue method. +// This is an order-dependent check. +func ListExact(value []Check) listExact { + return listExact{ + value: value, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/list_partial.go b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/list_partial.go new file mode 100644 index 0000000000..7d6e7ee20e --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/list_partial.go @@ -0,0 +1,89 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package knownvalue + +import ( + "bytes" + "fmt" + "sort" + "strings" +) + +var _ Check = listPartial{} + +type listPartial struct { + value map[int]Check +} + +// CheckValue determines whether the passed value is of type []any, and +// contains matching slice entries in the same sequence. +func (v listPartial) CheckValue(other any) error { + otherVal, ok := other.([]any) + + if !ok { + return fmt.Errorf("expected []any value for ListPartial check, got: %T", other) + } + + var keys []int + + for k := range v.value { + keys = append(keys, k) + } + + sort.SliceStable(keys, func(i, j int) bool { + return keys[i] < keys[j] + }) + + for _, k := range keys { + if len(otherVal) <= k { + return fmt.Errorf("missing element index %d for ListPartial check", k) + } + + if err := v.value[k].CheckValue(otherVal[k]); err != nil { + return fmt.Errorf("list element %d: %s", k, err) + } + } + + return nil +} + +// String returns the string representation of the value. +func (v listPartial) String() string { + var b bytes.Buffer + + b.WriteString("[") + + var keys []int + + var listVals []string + + for k := range v.value { + keys = append(keys, k) + } + + sort.SliceStable(keys, func(i, j int) bool { + return keys[i] < keys[j] + }) + + for _, k := range keys { + listVals = append(listVals, fmt.Sprintf("%d:%s", k, v.value[k])) + } + + b.WriteString(strings.Join(listVals, " ")) + + b.WriteString("]") + + return b.String() +} + +// ListPartial returns a Check for asserting partial equality between the +// supplied map[int]Check and the value passed to the CheckValue method. The +// map keys represent the zero-ordered element indices within the list that is +// being checked. Only the elements at the indices defined within the +// supplied map[int]Check are checked. +func ListPartial(value map[int]Check) listPartial { + return listPartial{ + value: value, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/list_size.go b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/list_size.go new file mode 100644 index 0000000000..d95192310e --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/list_size.go @@ -0,0 +1,55 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package knownvalue + +import ( + "fmt" + "strconv" +) + +var _ Check = listSizeExact{} + +type listSizeExact struct { + size int +} + +// CheckValue verifies that the passed value is a list, map, object, +// or set, and contains a matching number of elements. +func (v listSizeExact) CheckValue(other any) error { + otherVal, ok := other.([]any) + + if !ok { + return fmt.Errorf("expected []any value for ListSizeExact check, got: %T", other) + } + + if len(otherVal) != v.size { + expectedElements := "elements" + actualElements := "elements" + + if v.size == 1 { + expectedElements = "element" + } + + if len(otherVal) == 1 { + actualElements = "element" + } + + return fmt.Errorf("expected %d %s for ListSizeExact check, got %d %s", v.size, expectedElements, len(otherVal), actualElements) + } + + return nil +} + +// String returns the string representation of the value. +func (v listSizeExact) String() string { + return strconv.FormatInt(int64(v.size), 10) +} + +// ListSizeExact returns a Check for asserting that +// a list has size elements. +func ListSizeExact(size int) listSizeExact { + return listSizeExact{ + size: size, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/map.go b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/map.go new file mode 100644 index 0000000000..f4027c9df4 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/map.go @@ -0,0 +1,93 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package knownvalue + +import ( + "fmt" + "sort" +) + +var _ Check = mapExact{} + +type mapExact struct { + value map[string]Check +} + +// CheckValue determines whether the passed value is of type map[string]any, and +// contains matching map entries. +func (v mapExact) CheckValue(other any) error { + otherVal, ok := other.(map[string]any) + + if !ok { + return fmt.Errorf("expected map[string]any value for MapExact check, got: %T", other) + } + + if len(otherVal) != len(v.value) { + expectedElements := "elements" + actualElements := "elements" + + if len(v.value) == 1 { + expectedElements = "element" + } + + if len(otherVal) == 1 { + actualElements = "element" + } + + return fmt.Errorf("expected %d %s for MapExact check, got %d %s", len(v.value), expectedElements, len(otherVal), actualElements) + } + + var keys []string + + for k := range v.value { + keys = append(keys, k) + } + + sort.SliceStable(keys, func(i, j int) bool { + return keys[i] < keys[j] + }) + + for _, k := range keys { + otherValItem, ok := otherVal[k] + + if !ok { + return fmt.Errorf("missing element %s for MapExact check", k) + } + + if err := v.value[k].CheckValue(otherValItem); err != nil { + return fmt.Errorf("%s map element: %s", k, err) + } + } + + return nil +} + +// String returns the string representation of the value. +func (v mapExact) String() string { + var keys []string + + for k := range v.value { + keys = append(keys, k) + } + + sort.SliceStable(keys, func(i, j int) bool { + return keys[i] < keys[j] + }) + + mapVals := make(map[string]string, len(keys)) + + for _, k := range keys { + mapVals[k] = v.value[k].String() + } + + return fmt.Sprintf("%v", mapVals) +} + +// MapExact returns a Check for asserting equality between the +// supplied map[string]Check and the value passed to the CheckValue method. +func MapExact(value map[string]Check) mapExact { + return mapExact{ + value: value, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/map_partial.go b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/map_partial.go new file mode 100644 index 0000000000..860b2adff2 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/map_partial.go @@ -0,0 +1,80 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package knownvalue + +import ( + "fmt" + "sort" +) + +var _ Check = mapPartial{} + +type mapPartial struct { + value map[string]Check +} + +// CheckValue determines whether the passed value is of type map[string]any, and +// contains matching map entries. +func (v mapPartial) CheckValue(other any) error { + otherVal, ok := other.(map[string]any) + + if !ok { + return fmt.Errorf("expected map[string]any value for MapPartial check, got: %T", other) + } + + var keys []string + + for k := range v.value { + keys = append(keys, k) + } + + sort.SliceStable(keys, func(i, j int) bool { + return keys[i] < keys[j] + }) + + for _, k := range keys { + otherValItem, ok := otherVal[k] + + if !ok { + return fmt.Errorf("missing element %s for MapPartial check", k) + } + + if err := v.value[k].CheckValue(otherValItem); err != nil { + return fmt.Errorf("%s map element: %s", k, err) + } + } + + return nil +} + +// String returns the string representation of the value. +func (v mapPartial) String() string { + var keys []string + + for k := range v.value { + keys = append(keys, k) + } + + sort.SliceStable(keys, func(i, j int) bool { + return keys[i] < keys[j] + }) + + mapVals := make(map[string]string, len(keys)) + + for _, k := range keys { + mapVals[k] = v.value[k].String() + } + + return fmt.Sprintf("%v", mapVals) +} + +// MapPartial returns a Check for asserting partial equality between the +// supplied map[string]Check and the value passed to the CheckValue method. Only +// the elements at the map keys defined within the supplied map[string]Check are +// checked. +func MapPartial(value map[string]Check) mapPartial { + return mapPartial{ + value: value, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/map_size.go b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/map_size.go new file mode 100644 index 0000000000..c0966823c1 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/map_size.go @@ -0,0 +1,55 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package knownvalue + +import ( + "fmt" + "strconv" +) + +var _ Check = mapSizeExact{} + +type mapSizeExact struct { + size int +} + +// CheckValue verifies that the passed value is a list, map, object, +// or set, and contains a matching number of elements. +func (v mapSizeExact) CheckValue(other any) error { + otherVal, ok := other.(map[string]any) + + if !ok { + return fmt.Errorf("expected map[string]any value for MapSizeExact check, got: %T", other) + } + + if len(otherVal) != v.size { + expectedElements := "elements" + actualElements := "elements" + + if v.size == 1 { + expectedElements = "element" + } + + if len(otherVal) == 1 { + actualElements = "element" + } + + return fmt.Errorf("expected %d %s for MapSizeExact check, got %d %s", v.size, expectedElements, len(otherVal), actualElements) + } + + return nil +} + +// String returns the string representation of the value. +func (v mapSizeExact) String() string { + return strconv.Itoa(v.size) +} + +// MapSizeExact returns a Check for asserting that +// a map has size elements. +func MapSizeExact(size int) mapSizeExact { + return mapSizeExact{ + size: size, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/not_null.go b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/not_null.go new file mode 100644 index 0000000000..d7ae68904e --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/not_null.go @@ -0,0 +1,32 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package knownvalue + +import ( + "fmt" +) + +var _ Check = notNull{} + +type notNull struct{} + +// CheckValue determines whether the passed value is nil. +func (v notNull) CheckValue(other any) error { + if other == nil { + return fmt.Errorf("expected non-nil value for NotNull check, got: %T", other) + } + + return nil +} + +// String returns the string representation of notNull. +func (v notNull) String() string { + return "not-null" +} + +// NotNull returns a Check for asserting the value passed +// to the CheckValue method is not nil. +func NotNull() notNull { + return notNull{} +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/null.go b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/null.go new file mode 100644 index 0000000000..24e6b7e2b9 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/null.go @@ -0,0 +1,32 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package knownvalue + +import ( + "fmt" +) + +var _ Check = null{} + +type null struct{} + +// CheckValue determines whether the passed value is nil. +func (v null) CheckValue(other any) error { + if other != nil { + return fmt.Errorf("expected nil value for Null check, got: %T", other) + } + + return nil +} + +// String returns the string representation of null. +func (v null) String() string { + return "null" +} + +// Null returns a Check for asserting the value passed +// to the CheckValue method is nil. +func Null() null { + return null{} +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/number.go b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/number.go new file mode 100644 index 0000000000..d101b1f68c --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/number.go @@ -0,0 +1,56 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package knownvalue + +import ( + "encoding/json" + "fmt" + "math/big" +) + +var _ Check = numberExact{} + +type numberExact struct { + value *big.Float +} + +// CheckValue determines whether the passed value is of type *big.Float, and +// contains a matching *big.Float value. +func (v numberExact) CheckValue(other any) error { + if v.value == nil { + return fmt.Errorf("value in NumberExact check is nil") + } + + jsonNum, ok := other.(json.Number) + + if !ok { + return fmt.Errorf("expected json.Number value for NumberExact check, got: %T", other) + } + + otherVal, _, err := big.ParseFloat(jsonNum.String(), 10, 512, big.ToNearestEven) + + if err != nil { + return fmt.Errorf("expected json.Number to be parseable as big.Float value for NumberExact check: %s", err) + } + + if v.value.Cmp(otherVal) != 0 { + return fmt.Errorf("expected value %s for NumberExact check, got: %s", v.String(), otherVal.Text('f', -1)) + } + + return nil +} + +// String returns the string representation of the *big.Float value. +func (v numberExact) String() string { + return v.value.Text('f', -1) +} + +// NumberExact returns a Check for asserting equality between the +// supplied *big.Float and the value passed to the CheckValue method. +// The CheckValue method uses 512-bit precision to perform this assertion. +func NumberExact(value *big.Float) numberExact { + return numberExact{ + value: value, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/number_func.go b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/number_func.go new file mode 100644 index 0000000000..75cce49388 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/number_func.go @@ -0,0 +1,48 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package knownvalue + +import ( + "encoding/json" + "fmt" + "math/big" +) + +var _ Check = numberFunc{} + +type numberFunc struct { + checkFunc func(v *big.Float) error +} + +// CheckValue determines whether the passed value is of type int64, and +// returns no error from the provided check function +func (v numberFunc) CheckValue(other any) error { + jsonNum, ok := other.(json.Number) + + if !ok { + return fmt.Errorf("expected json.Number value for NumberFunc check, got: %T", other) + } + + otherVal, _, err := big.ParseFloat(jsonNum.String(), 10, 512, big.ToNearestEven) + if err != nil { + return fmt.Errorf("expected json.Number to be parseable as big.Float value for NumberFunc check: %s", err) + } + + return v.checkFunc(otherVal) +} + +// String returns the int64 representation of the value. +func (v numberFunc) String() string { + // Validation is up the the implementer of the function, so there are no + // int64 literal or regex comparers to print here + return "NumberFunc" +} + +// NumberFunc returns a Check for passing the int64 value in state +// to the provided check function +func NumberFunc(fn func(v *big.Float) error) numberFunc { + return numberFunc{ + checkFunc: fn, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/object.go b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/object.go new file mode 100644 index 0000000000..cc97542c47 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/object.go @@ -0,0 +1,116 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package knownvalue + +import ( + "fmt" + "maps" + "slices" + "sort" +) + +var _ Check = objectExact{} + +type objectExact struct { + value map[string]Check +} + +// CheckValue determines whether the passed value is of type map[string]any, and +// contains matching object entries. +func (v objectExact) CheckValue(other any) error { + otherVal, ok := other.(map[string]any) + + if !ok { + return fmt.Errorf("expected map[string]any value for ObjectExact check, got: %T", other) + } + + if len(otherVal) != len(v.value) { + deltaMsg := "" + if len(otherVal) > len(v.value) { + deltaMsg = createDeltaString(otherVal, v.value, "actual value has extra attribute(s): ") + } else { + deltaMsg = createDeltaString(v.value, otherVal, "actual value is missing attribute(s): ") + } + + return fmt.Errorf("expected %d attribute(s) for ObjectExact check, got %d attribute(s): %s", len(v.value), len(otherVal), deltaMsg) + } + + var keys []string + + for k := range v.value { + keys = append(keys, k) + } + + sort.SliceStable(keys, func(i, j int) bool { + return keys[i] < keys[j] + }) + + for _, k := range keys { + otherValItem, ok := otherVal[k] + + if !ok { + return fmt.Errorf("missing attribute %s for ObjectExact check", k) + } + + if err := v.value[k].CheckValue(otherValItem); err != nil { + return fmt.Errorf("%s object attribute: %s", k, err) + } + } + + return nil +} + +// String returns the string representation of the value. +func (v objectExact) String() string { + var keys []string + + for k := range v.value { + keys = append(keys, k) + } + + sort.SliceStable(keys, func(i, j int) bool { + return keys[i] < keys[j] + }) + + mapVals := make(map[string]string, len(keys)) + + for _, k := range keys { + mapVals[k] = v.value[k].String() + } + + return fmt.Sprintf("%v", mapVals) +} + +// ObjectExact returns a Check for asserting equality between the supplied +// map[string]Check and the value passed to the CheckValue method. The map +// keys represent object attribute names. +func ObjectExact(value map[string]Check) objectExact { + return objectExact{ + value: value, + } +} + +// createDeltaString prints the map keys that are present in mapA and not present in mapB +func createDeltaString[T any, V any](mapA map[string]T, mapB map[string]V, msgPrefix string) string { + deltaMsg := "" + + deltaMap := make(map[string]T, len(mapA)) + maps.Copy(deltaMap, mapA) + for key := range mapB { + delete(deltaMap, key) + } + + deltaKeys := slices.Sorted(maps.Keys(deltaMap)) + + for i, k := range deltaKeys { + if i == 0 { + deltaMsg += msgPrefix + } else { + deltaMsg += ", " + } + deltaMsg += fmt.Sprintf("%q", k) + } + + return deltaMsg +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/object_partial.go b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/object_partial.go new file mode 100644 index 0000000000..775ab4c340 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/object_partial.go @@ -0,0 +1,80 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package knownvalue + +import ( + "fmt" + "sort" +) + +var _ Check = objectPartial{} + +type objectPartial struct { + value map[string]Check +} + +// CheckValue determines whether the passed value is of type map[string]any, and +// contains matching map entries. +func (v objectPartial) CheckValue(other any) error { + otherVal, ok := other.(map[string]any) + + if !ok { + return fmt.Errorf("expected map[string]any value for ObjectPartial check, got: %T", other) + } + + var keys []string + + for k := range v.value { + keys = append(keys, k) + } + + sort.SliceStable(keys, func(i, j int) bool { + return keys[i] < keys[j] + }) + + for _, k := range keys { + otherValItem, ok := otherVal[k] + + if !ok { + return fmt.Errorf("missing attribute %s for ObjectPartial check", k) + } + + if err := v.value[k].CheckValue(otherValItem); err != nil { + return fmt.Errorf("%s object attribute: %s", k, err) + } + } + + return nil +} + +// String returns the string representation of the value. +func (v objectPartial) String() string { + var keys []string + + for k := range v.value { + keys = append(keys, k) + } + + sort.SliceStable(keys, func(i, j int) bool { + return keys[i] < keys[j] + }) + + mapVals := make(map[string]string, len(keys)) + + for _, k := range keys { + mapVals[k] = v.value[k].String() + } + + return fmt.Sprintf("%v", mapVals) +} + +// ObjectPartial returns a Check for asserting partial equality between the +// supplied map[string]Check and the value passed to the CheckValue method. The map +// keys represent object attribute names. Only the object attributes defined by the +// map keys within the supplied map[string]Check are checked. +func ObjectPartial(value map[string]Check) objectPartial { + return objectPartial{ + value: value, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/set.go b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/set.go new file mode 100644 index 0000000000..206f26698f --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/set.go @@ -0,0 +1,86 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package knownvalue + +import ( + "fmt" +) + +var _ Check = setExact{} + +type setExact struct { + value []Check +} + +// CheckValue determines whether the passed value is of type []any, and +// contains matching slice entries independent of the sequence. +func (v setExact) CheckValue(other any) error { + otherVal, ok := other.([]any) + + if !ok { + return fmt.Errorf("expected []any value for SetExact check, got: %T", other) + } + + if len(otherVal) != len(v.value) { + expectedElements := "elements" + actualElements := "elements" + + if len(v.value) == 1 { + expectedElements = "element" + } + + if len(otherVal) == 1 { + actualElements = "element" + } + + return fmt.Errorf("expected %d %s for SetExact check, got %d %s", len(v.value), expectedElements, len(otherVal), actualElements) + } + + otherValCopy := make([]any, len(otherVal)) + + copy(otherValCopy, otherVal) + + for i := 0; i < len(v.value); i++ { + err := fmt.Errorf("missing value %s for SetExact check", v.value[i].String()) + + for j := 0; j < len(otherValCopy); j++ { + checkValueErr := v.value[i].CheckValue(otherValCopy[j]) + + if checkValueErr == nil { + otherValCopy[j] = otherValCopy[len(otherValCopy)-1] + otherValCopy = otherValCopy[:len(otherValCopy)-1] + + err = nil + + break + } + } + + if err != nil { + return err + } + } + + return nil +} + +// String returns the string representation of the value. +func (v setExact) String() string { + var setVals []string + + for _, val := range v.value { + setVals = append(setVals, val.String()) + } + + return fmt.Sprintf("%s", setVals) +} + +// SetExact returns a Check for asserting equality between the +// supplied []Check and the value passed to the CheckValue method. +// This is an order-independent check. +func SetExact(value []Check) setExact { + return setExact{ + value: value, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/set_partial.go b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/set_partial.go new file mode 100644 index 0000000000..dcbcd2ff10 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/set_partial.go @@ -0,0 +1,72 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package knownvalue + +import ( + "fmt" +) + +var _ Check = setPartial{} + +type setPartial struct { + value []Check +} + +// CheckValue determines whether the passed value is of type []any, and +// contains matching slice entries in any sequence. +func (v setPartial) CheckValue(other any) error { + otherVal, ok := other.([]any) + + if !ok { + return fmt.Errorf("expected []any value for SetPartial check, got: %T", other) + } + + otherValCopy := make([]any, len(otherVal)) + + copy(otherValCopy, otherVal) + + for i := 0; i < len(v.value); i++ { + err := fmt.Errorf("missing value %s for SetPartial check", v.value[i].String()) + + for j := 0; j < len(otherValCopy); j++ { + checkValueErr := v.value[i].CheckValue(otherValCopy[j]) + + if checkValueErr == nil { + otherValCopy[j] = otherValCopy[len(otherValCopy)-1] + otherValCopy = otherValCopy[:len(otherValCopy)-1] + + err = nil + + break + } + } + + if err != nil { + return err + } + } + + return nil +} + +// String returns the string representation of the value. +func (v setPartial) String() string { + var setVals []string + + for _, val := range v.value { + setVals = append(setVals, val.String()) + } + + return fmt.Sprintf("%s", setVals) +} + +// SetPartial returns a Check for asserting partial equality between the +// supplied []Check and the value passed to the CheckValue method. Only the +// elements defined within the supplied []Check are checked. This is an +// order-independent check. +func SetPartial(value []Check) setPartial { + return setPartial{ + value: value, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/set_size.go b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/set_size.go new file mode 100644 index 0000000000..aa3cce1706 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/set_size.go @@ -0,0 +1,55 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package knownvalue + +import ( + "fmt" + "strconv" +) + +var _ Check = setSizeExact{} + +type setSizeExact struct { + size int +} + +// CheckValue verifies that the passed value is a list, map, object, +// or set, and contains a matching number of elements. +func (v setSizeExact) CheckValue(other any) error { + otherVal, ok := other.([]any) + + if !ok { + return fmt.Errorf("expected []any value for SetElementExact check, got: %T", other) + } + + if len(otherVal) != v.size { + expectedElements := "elements" + actualElements := "elements" + + if v.size == 1 { + expectedElements = "element" + } + + if len(otherVal) == 1 { + actualElements = "element" + } + + return fmt.Errorf("expected %d %s for SetElementExact check, got %d %s", v.size, expectedElements, len(otherVal), actualElements) + } + + return nil +} + +// String returns the string representation of the value. +func (v setSizeExact) String() string { + return strconv.FormatInt(int64(v.size), 10) +} + +// SetSizeExact returns a Check for asserting that +// a set has size elements. +func SetSizeExact(size int) setSizeExact { + return setSizeExact{ + size: size, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/string.go b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/string.go new file mode 100644 index 0000000000..63d03a5071 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/string.go @@ -0,0 +1,41 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package knownvalue + +import "fmt" + +var _ Check = stringExact{} + +type stringExact struct { + value string +} + +// CheckValue determines whether the passed value is of type string, and +// contains a matching sequence of bytes. +func (v stringExact) CheckValue(other any) error { + otherVal, ok := other.(string) + + if !ok { + return fmt.Errorf("expected string value for StringExact check, got: %T", other) + } + + if otherVal != v.value { + return fmt.Errorf("expected value %s for StringExact check, got: %s", v.value, otherVal) + } + + return nil +} + +// String returns the string representation of the value. +func (v stringExact) String() string { + return v.value +} + +// StringExact returns a Check for asserting equality between the +// supplied string and a value passed to the CheckValue method. +func StringExact(value string) stringExact { + return stringExact{ + value: value, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/string_func.go b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/string_func.go new file mode 100644 index 0000000000..e1dc3f0b96 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/string_func.go @@ -0,0 +1,39 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package knownvalue + +import "fmt" + +var _ Check = stringFunc{} + +type stringFunc struct { + checkFunc func(v string) error +} + +// CheckValue determines whether the passed value is of type string, and +// returns no error from the provided check function +func (v stringFunc) CheckValue(value any) error { + val, ok := value.(string) + + if !ok { + return fmt.Errorf("expected string value for StringFunc check, got: %T", value) + } + + return v.checkFunc(val) +} + +// String returns the string representation of the value. +func (v stringFunc) String() string { + // Validation is up the the implementer of the function, so there are no + // string literal or regex comparers to print here + return "StringFunc" +} + +// StringFunc returns a Check for passing the string value in state +// to the provided check function +func StringFunc(fn func(v string) error) stringFunc { + return stringFunc{ + checkFunc: fn, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/string_regexp.go b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/string_regexp.go new file mode 100644 index 0000000000..782e297479 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/string_regexp.go @@ -0,0 +1,45 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package knownvalue + +import ( + "fmt" + "regexp" +) + +var _ Check = stringRegexp{} + +type stringRegexp struct { + regex *regexp.Regexp +} + +// CheckValue determines whether the passed value is of type string, and +// contains a sequence of bytes that match the regular expression supplied +// to StringRegexp. +func (v stringRegexp) CheckValue(other any) error { + otherVal, ok := other.(string) + + if !ok { + return fmt.Errorf("expected string value for StringRegexp check, got: %T", other) + } + + if !v.regex.MatchString(otherVal) { + return fmt.Errorf("expected regex match %s for StringRegexp check, got: %s", v.regex.String(), otherVal) + } + + return nil +} + +// String returns the string representation of the value. +func (v stringRegexp) String() string { + return v.regex.String() +} + +// StringRegexp returns a Check for asserting equality between the +// supplied regular expression and a value passed to the CheckValue method. +func StringRegexp(regex *regexp.Regexp) stringRegexp { + return stringRegexp{ + regex: regex, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/tuple.go b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/tuple.go new file mode 100644 index 0000000000..c034bba734 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/tuple.go @@ -0,0 +1,67 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package knownvalue + +import ( + "fmt" +) + +var _ Check = tupleExact{} + +type tupleExact struct { + value []Check +} + +// CheckValue determines whether the passed value is of type []any, and +// contains matching slice entries in the same sequence. +func (v tupleExact) CheckValue(other any) error { + otherVal, ok := other.([]any) + + if !ok { + return fmt.Errorf("expected []any value for TupleExact check, got: %T", other) + } + + if len(otherVal) != len(v.value) { + expectedElements := "elements" + actualElements := "elements" + + if len(v.value) == 1 { + expectedElements = "element" + } + + if len(otherVal) == 1 { + actualElements = "element" + } + + return fmt.Errorf("expected %d %s for TupleExact check, got %d %s", len(v.value), expectedElements, len(otherVal), actualElements) + } + + for i := 0; i < len(v.value); i++ { + if err := v.value[i].CheckValue(otherVal[i]); err != nil { + return fmt.Errorf("tuple element index %d: %s", i, err) + } + } + + return nil +} + +// String returns the string representation of the value. +func (v tupleExact) String() string { + var tupleVals []string + + for _, val := range v.value { + tupleVals = append(tupleVals, val.String()) + } + + return fmt.Sprintf("%s", tupleVals) +} + +// TupleExact returns a Check for asserting equality between the +// supplied []Check and the value passed to the CheckValue method. +// This is an order-dependent check. +func TupleExact(value []Check) tupleExact { + return tupleExact{ + value: value, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/tuple_partial.go b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/tuple_partial.go new file mode 100644 index 0000000000..cfd73b2d9b --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/tuple_partial.go @@ -0,0 +1,89 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package knownvalue + +import ( + "bytes" + "fmt" + "sort" + "strings" +) + +var _ Check = tuplePartial{} + +type tuplePartial struct { + value map[int]Check +} + +// CheckValue determines whether the passed value is of type []any, and +// contains matching slice entries in the same sequence. +func (v tuplePartial) CheckValue(other any) error { + otherVal, ok := other.([]any) + + if !ok { + return fmt.Errorf("expected []any value for TuplePartial check, got: %T", other) + } + + var keys []int + + for k := range v.value { + keys = append(keys, k) + } + + sort.SliceStable(keys, func(i, j int) bool { + return keys[i] < keys[j] + }) + + for _, k := range keys { + if len(otherVal) <= k { + return fmt.Errorf("missing element index %d for TuplePartial check", k) + } + + if err := v.value[k].CheckValue(otherVal[k]); err != nil { + return fmt.Errorf("tuple element %d: %s", k, err) + } + } + + return nil +} + +// String returns the string representation of the value. +func (v tuplePartial) String() string { + var b bytes.Buffer + + b.WriteString("[") + + var keys []int + + var tupleVals []string + + for k := range v.value { + keys = append(keys, k) + } + + sort.SliceStable(keys, func(i, j int) bool { + return keys[i] < keys[j] + }) + + for _, k := range keys { + tupleVals = append(tupleVals, fmt.Sprintf("%d:%s", k, v.value[k])) + } + + b.WriteString(strings.Join(tupleVals, " ")) + + b.WriteString("]") + + return b.String() +} + +// TuplePartial returns a Check for asserting partial equality between the +// supplied map[int]Check and the value passed to the CheckValue method. The +// map keys represent the zero-ordered element indices within the tuple that is +// being checked. Only the elements at the indices defined within the +// supplied map[int]Check are checked. +func TuplePartial(value map[int]Check) tuplePartial { + return tuplePartial{ + value: value, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/tuple_size.go b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/tuple_size.go new file mode 100644 index 0000000000..a2c2dce3d3 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/knownvalue/tuple_size.go @@ -0,0 +1,55 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package knownvalue + +import ( + "fmt" + "strconv" +) + +var _ Check = tupleSizeExact{} + +type tupleSizeExact struct { + size int +} + +// CheckValue verifies that the passed value is a tuple, map, object, +// or set, and contains a matching number of elements. +func (v tupleSizeExact) CheckValue(other any) error { + otherVal, ok := other.([]any) + + if !ok { + return fmt.Errorf("expected []any value for TupleSizeExact check, got: %T", other) + } + + if len(otherVal) != v.size { + expectedElements := "elements" + actualElements := "elements" + + if v.size == 1 { + expectedElements = "element" + } + + if len(otherVal) == 1 { + actualElements = "element" + } + + return fmt.Errorf("expected %d %s for TupleSizeExact check, got %d %s", v.size, expectedElements, len(otherVal), actualElements) + } + + return nil +} + +// String returns the string representation of the value. +func (v tupleSizeExact) String() string { + return strconv.FormatInt(int64(v.size), 10) +} + +// TupleSizeExact returns a Check for asserting that +// a tuple has size elements. +func TupleSizeExact(size int) tupleSizeExact { + return tupleSizeExact{ + size: size, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/plancheck/deferred_reason.go b/vendor/github.com/hashicorp/terraform-plugin-testing/plancheck/deferred_reason.go new file mode 100644 index 0000000000..4787a8c3e1 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/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/vendor/github.com/hashicorp/terraform-plugin-testing/plancheck/doc.go b/vendor/github.com/hashicorp/terraform-plugin-testing/plancheck/doc.go new file mode 100644 index 0000000000..eceda6faaf --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/plancheck/doc.go @@ -0,0 +1,5 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// Package plancheck contains the plan check interface, request/response structs, and common plan check implementations. +package plancheck diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/plancheck/expect_deferred_change.go b/vendor/github.com/hashicorp/terraform-plugin-testing/plancheck/expect_deferred_change.go new file mode 100644 index 0000000000..14310ca31c --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/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/vendor/github.com/hashicorp/terraform-plugin-testing/plancheck/expect_empty_plan.go b/vendor/github.com/hashicorp/terraform-plugin-testing/plancheck/expect_empty_plan.go new file mode 100644 index 0000000000..8df2e281ef --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/plancheck/expect_empty_plan.go @@ -0,0 +1,39 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package plancheck + +import ( + "context" + "errors" + "fmt" +) + +var _ PlanCheck = expectEmptyPlan{} + +type expectEmptyPlan struct{} + +// CheckPlan implements the plan check logic. +func (e expectEmptyPlan) CheckPlan(ctx context.Context, req CheckPlanRequest, resp *CheckPlanResponse) { + var result []error + + for output, change := range req.Plan.OutputChanges { + if !change.Actions.NoOp() { + result = append(result, fmt.Errorf("expected empty plan, but output %q has planned action(s): %v", output, change.Actions)) + } + } + + for _, rc := range req.Plan.ResourceChanges { + if !rc.Change.Actions.NoOp() { + result = append(result, fmt.Errorf("expected empty plan, but %s has planned action(s): %v", rc.Address, rc.Change.Actions)) + } + } + + resp.Error = errors.Join(result...) +} + +// ExpectEmptyPlan returns a plan check that asserts that there are no output or resource changes in the plan. +// All output and resource changes found will be aggregated and returned in a plan check error. +func ExpectEmptyPlan() PlanCheck { + return expectEmptyPlan{} +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/plancheck/expect_known_output_value.go b/vendor/github.com/hashicorp/terraform-plugin-testing/plancheck/expect_known_output_value.go new file mode 100644 index 0000000000..bc954c8ed5 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/plancheck/expect_known_output_value.go @@ -0,0 +1,68 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package plancheck + +import ( + "context" + "fmt" + + tfjson "github.com/hashicorp/terraform-json" + + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +// Resource Plan Check +var _ PlanCheck = expectKnownOutputValue{} + +type expectKnownOutputValue struct { + outputAddress string + knownValue knownvalue.Check +} + +// CheckPlan implements the plan check logic. +func (e expectKnownOutputValue) CheckPlan(ctx context.Context, req CheckPlanRequest, resp *CheckPlanResponse) { + var change *tfjson.Change + + if req.Plan == nil { + resp.Error = fmt.Errorf("plan is nil") + } + + for address, oc := range req.Plan.OutputChanges { + if e.outputAddress == address { + change = oc + + break + } + } + + if change == nil { + resp.Error = fmt.Errorf("%s - Output not found in plan", e.outputAddress) + + return + } + + result, err := tfjsonpath.Traverse(change.After, tfjsonpath.Path{}) + + if err != nil { + resp.Error = err + + return + } + + if err := e.knownValue.CheckValue(result); err != nil { + resp.Error = fmt.Errorf("error checking value for output at path: %s, err: %s", e.outputAddress, err) + + return + } +} + +// ExpectKnownOutputValue returns a plan check that asserts that the specified value +// has a known type, and value. +func ExpectKnownOutputValue(outputAddress string, knownValue knownvalue.Check) PlanCheck { + return expectKnownOutputValue{ + outputAddress: outputAddress, + knownValue: knownValue, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/plancheck/expect_known_output_value_at_path.go b/vendor/github.com/hashicorp/terraform-plugin-testing/plancheck/expect_known_output_value_at_path.go new file mode 100644 index 0000000000..42f07a1515 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/plancheck/expect_known_output_value_at_path.go @@ -0,0 +1,71 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package plancheck + +import ( + "context" + "fmt" + + tfjson "github.com/hashicorp/terraform-json" + + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +// Resource Plan Check +var _ PlanCheck = expectKnownOutputValueAtPath{} + +type expectKnownOutputValueAtPath struct { + outputAddress string + outputPath tfjsonpath.Path + knownValue knownvalue.Check +} + +// CheckPlan implements the plan check logic. +func (e expectKnownOutputValueAtPath) CheckPlan(ctx context.Context, req CheckPlanRequest, resp *CheckPlanResponse) { + var change *tfjson.Change + + if req.Plan == nil { + resp.Error = fmt.Errorf("plan is nil") + } + + for address, oc := range req.Plan.OutputChanges { + if e.outputAddress == address { + change = oc + + break + } + } + + if change == nil { + resp.Error = fmt.Errorf("%s - Output not found in plan", e.outputAddress) + + return + } + + result, err := tfjsonpath.Traverse(change.After, e.outputPath) + + if err != nil { + resp.Error = err + + return + } + + if err := e.knownValue.CheckValue(result); err != nil { + resp.Error = fmt.Errorf("error checking value for output at path: %s.%s, err: %s", e.outputAddress, e.outputPath.String(), err) + + return + } +} + +// ExpectKnownOutputValueAtPath returns a plan check that asserts that the specified output at the given path +// has a known type and value. Prior to Terraform v1.3.0 a planned output is marked as fully unknown +// if any attribute is unknown. +func ExpectKnownOutputValueAtPath(outputAddress string, outputPath tfjsonpath.Path, knownValue knownvalue.Check) PlanCheck { + return expectKnownOutputValueAtPath{ + outputAddress: outputAddress, + outputPath: outputPath, + knownValue: knownValue, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/plancheck/expect_known_value.go b/vendor/github.com/hashicorp/terraform-plugin-testing/plancheck/expect_known_value.go new file mode 100644 index 0000000000..ae6ea6d8de --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/plancheck/expect_known_value.go @@ -0,0 +1,70 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package plancheck + +import ( + "context" + "fmt" + + tfjson "github.com/hashicorp/terraform-json" + + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +// Resource Plan Check +var _ PlanCheck = expectKnownValue{} + +type expectKnownValue struct { + resourceAddress string + attributePath tfjsonpath.Path + knownValue knownvalue.Check +} + +// CheckPlan implements the plan check logic. +func (e expectKnownValue) CheckPlan(ctx context.Context, req CheckPlanRequest, resp *CheckPlanResponse) { + var rc *tfjson.ResourceChange + + if req.Plan == nil { + resp.Error = fmt.Errorf("plan is nil") + } + + for _, resourceChange := range req.Plan.ResourceChanges { + if e.resourceAddress == resourceChange.Address { + rc = resourceChange + + break + } + } + + if rc == nil { + resp.Error = fmt.Errorf("%s - Resource not found in plan", e.resourceAddress) + + return + } + + result, err := tfjsonpath.Traverse(rc.Change.After, e.attributePath) + + if err != nil { + resp.Error = err + + return + } + + if err := e.knownValue.CheckValue(result); err != nil { + resp.Error = fmt.Errorf("error checking value for attribute at path: %s.%s, err: %s", e.resourceAddress, e.attributePath.String(), err) + + return + } +} + +// ExpectKnownValue returns a plan check that asserts that the specified attribute at the given resource +// has a known type and value. +func ExpectKnownValue(resourceAddress string, attributePath tfjsonpath.Path, knownValue knownvalue.Check) PlanCheck { + return expectKnownValue{ + resourceAddress: resourceAddress, + attributePath: attributePath, + knownValue: knownValue, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/plancheck/expect_no_deferred_changes.go b/vendor/github.com/hashicorp/terraform-plugin-testing/plancheck/expect_no_deferred_changes.go new file mode 100644 index 0000000000..726ea6802f --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/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/vendor/github.com/hashicorp/terraform-plugin-testing/plancheck/expect_non_empty_plan.go b/vendor/github.com/hashicorp/terraform-plugin-testing/plancheck/expect_non_empty_plan.go new file mode 100644 index 0000000000..482321ecea --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/plancheck/expect_non_empty_plan.go @@ -0,0 +1,35 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package plancheck + +import ( + "context" + "errors" +) + +var _ PlanCheck = expectNonEmptyPlan{} + +type expectNonEmptyPlan struct{} + +// CheckPlan implements the plan check logic. +func (e expectNonEmptyPlan) CheckPlan(ctx context.Context, req CheckPlanRequest, resp *CheckPlanResponse) { + for _, change := range req.Plan.OutputChanges { + if !change.Actions.NoOp() { + return + } + } + + for _, rc := range req.Plan.ResourceChanges { + if !rc.Change.Actions.NoOp() { + return + } + } + + resp.Error = errors.New("expected a non-empty plan, but got an empty plan") +} + +// ExpectNonEmptyPlan returns a plan check that asserts there is at least one output or resource change in the plan. +func ExpectNonEmptyPlan() PlanCheck { + return expectNonEmptyPlan{} +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/plancheck/expect_null_output_value.go b/vendor/github.com/hashicorp/terraform-plugin-testing/plancheck/expect_null_output_value.go new file mode 100644 index 0000000000..92fbf89dd6 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/plancheck/expect_null_output_value.go @@ -0,0 +1,74 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package plancheck + +import ( + "context" + "fmt" + + tfjson "github.com/hashicorp/terraform-json" + + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +var _ PlanCheck = expectNullOutputValue{} + +type expectNullOutputValue struct { + outputAddress string +} + +// CheckPlan implements the plan check logic. +func (e expectNullOutputValue) CheckPlan(ctx context.Context, req CheckPlanRequest, resp *CheckPlanResponse) { + var change *tfjson.Change + + for address, oc := range req.Plan.OutputChanges { + if e.outputAddress == address { + change = oc + + break + } + } + + if change == nil { + resp.Error = fmt.Errorf("%s - Output not found in plan OutputChanges", e.outputAddress) + + return + } + + var result any + var err error + + switch { + case change.Actions.Create(): + result, err = tfjsonpath.Traverse(change.After, tfjsonpath.Path{}) + default: + result, err = tfjsonpath.Traverse(change.Before, tfjsonpath.Path{}) + } + + if err != nil { + resp.Error = err + + return + } + + if result != nil { + resp.Error = fmt.Errorf("attribute at path is not null") + + return + } +} + +// ExpectNullOutputValue returns a plan check that asserts that the specified output has a null value. +// +// Due to implementation differences between the terraform-plugin-sdk and the terraform-plugin-framework, representation of null +// values may differ. For example, terraform-plugin-sdk based providers may have less precise representations of null values, such +// as marking whole maps as null rather than individual element values. +// +// Deprecated: Use [plancheck.ExpectKnownOutputValue] with [knownvalue.Null] instead. +// ExpectNullOutputValue will be removed in the next major version release. +func ExpectNullOutputValue(outputAddress string) PlanCheck { + return expectNullOutputValue{ + outputAddress: outputAddress, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/plancheck/expect_null_output_value_at_path.go b/vendor/github.com/hashicorp/terraform-plugin-testing/plancheck/expect_null_output_value_at_path.go new file mode 100644 index 0000000000..69f237d5f7 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/plancheck/expect_null_output_value_at_path.go @@ -0,0 +1,76 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package plancheck + +import ( + "context" + "fmt" + + tfjson "github.com/hashicorp/terraform-json" + + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +var _ PlanCheck = expectNullOutputValueAtPath{} + +type expectNullOutputValueAtPath struct { + outputAddress string + valuePath tfjsonpath.Path +} + +// CheckPlan implements the plan check logic. +func (e expectNullOutputValueAtPath) CheckPlan(ctx context.Context, req CheckPlanRequest, resp *CheckPlanResponse) { + var change *tfjson.Change + + for address, oc := range req.Plan.OutputChanges { + if e.outputAddress == address { + change = oc + + break + } + } + + if change == nil { + resp.Error = fmt.Errorf("%s - Output not found in plan OutputChanges", e.outputAddress) + + return + } + + var result any + var err error + + switch { + case change.Actions.Create(): + result, err = tfjsonpath.Traverse(change.After, e.valuePath) + default: + result, err = tfjsonpath.Traverse(change.Before, e.valuePath) + } + + if err != nil { + resp.Error = err + + return + } + + if result != nil { + resp.Error = fmt.Errorf("attribute at path is not null") + + return + } +} + +// ExpectNullOutputValueAtPath returns a plan check that asserts that the specified output has a null value. +// +// Due to implementation differences between the terraform-plugin-sdk and the terraform-plugin-framework, representation of null +// values may differ. For example, terraform-plugin-sdk based providers may have less precise representations of null values, such +// as marking whole maps as null rather than individual element values. +// +// Deprecated: Use [plancheck.ExpectKnownOutputValueAtPath] with [knownvalue.Null] instead. +// ExpectNullOutputValueAtPath will be removed in the next major version release. +func ExpectNullOutputValueAtPath(outputAddress string, valuePath tfjsonpath.Path) PlanCheck { + return expectNullOutputValueAtPath{ + outputAddress: outputAddress, + valuePath: valuePath, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/plancheck/expect_resource_action.go b/vendor/github.com/hashicorp/terraform-plugin-testing/plancheck/expect_resource_action.go new file mode 100644 index 0000000000..37a2336d2f --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/plancheck/expect_resource_action.go @@ -0,0 +1,90 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package plancheck + +import ( + "context" + "fmt" +) + +var _ PlanCheck = expectResourceAction{} + +type expectResourceAction struct { + resourceAddress string + actionType ResourceActionType +} + +// CheckPlan implements the plan check logic. +func (e expectResourceAction) CheckPlan(ctx context.Context, req CheckPlanRequest, resp *CheckPlanResponse) { + foundResource := false + + for _, rc := range req.Plan.ResourceChanges { + if e.resourceAddress != rc.Address { + continue + } + + switch e.actionType { + case ResourceActionNoop: + if !rc.Change.Actions.NoOp() { + resp.Error = fmt.Errorf("'%s' - expected %s, got action(s): %v", rc.Address, e.actionType, rc.Change.Actions) + return + } + case ResourceActionCreate: + if !rc.Change.Actions.Create() { + resp.Error = fmt.Errorf("'%s' - expected %s, got action(s): %v", rc.Address, e.actionType, rc.Change.Actions) + return + } + case ResourceActionRead: + if !rc.Change.Actions.Read() { + resp.Error = fmt.Errorf("'%s' - expected %s, got action(s): %v", rc.Address, e.actionType, rc.Change.Actions) + return + } + case ResourceActionUpdate: + if !rc.Change.Actions.Update() { + resp.Error = fmt.Errorf("'%s' - expected %s, got action(s): %v", rc.Address, e.actionType, rc.Change.Actions) + return + } + case ResourceActionDestroy: + if !rc.Change.Actions.Delete() { + resp.Error = fmt.Errorf("'%s' - expected %s, got action(s): %v", rc.Address, e.actionType, rc.Change.Actions) + return + } + case ResourceActionDestroyBeforeCreate: + if !rc.Change.Actions.DestroyBeforeCreate() { + resp.Error = fmt.Errorf("'%s' - expected %s, got action(s): %v", rc.Address, e.actionType, rc.Change.Actions) + return + } + case ResourceActionCreateBeforeDestroy: + if !rc.Change.Actions.CreateBeforeDestroy() { + resp.Error = fmt.Errorf("'%s' - expected %s, got action(s): %v", rc.Address, e.actionType, rc.Change.Actions) + return + } + case ResourceActionReplace: + if !rc.Change.Actions.Replace() { + resp.Error = fmt.Errorf("%s - expected %s, got action(s): %v", rc.Address, e.actionType, rc.Change.Actions) + return + } + default: + resp.Error = fmt.Errorf("%s - unexpected ResourceActionType: %s", rc.Address, e.actionType) + return + } + + foundResource = true + break + } + + if !foundResource { + resp.Error = fmt.Errorf("%s - Resource not found in plan ResourceChanges", e.resourceAddress) + return + } +} + +// ExpectResourceAction returns a plan check that asserts that a given resource will have a specific resource change type in the plan. +// Valid actionType are an enum of type plancheck.ResourceActionType, examples: NoOp, DestroyBeforeCreate, Update (in-place), etc. +func ExpectResourceAction(resourceAddress string, actionType ResourceActionType) PlanCheck { + return expectResourceAction{ + resourceAddress: resourceAddress, + actionType: actionType, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/plancheck/expect_sensitive_value.go b/vendor/github.com/hashicorp/terraform-plugin-testing/plancheck/expect_sensitive_value.go new file mode 100644 index 0000000000..b6c3a51946 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/plancheck/expect_sensitive_value.go @@ -0,0 +1,61 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package plancheck + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +var _ PlanCheck = expectSensitiveValue{} + +type expectSensitiveValue struct { + resourceAddress string + attributePath tfjsonpath.Path +} + +// CheckPlan implements the plan check logic. +func (e expectSensitiveValue) CheckPlan(ctx context.Context, req CheckPlanRequest, resp *CheckPlanResponse) { + + for _, rc := range req.Plan.ResourceChanges { + if e.resourceAddress != rc.Address { + continue + } + + result, err := tfjsonpath.Traverse(rc.Change.AfterSensitive, e.attributePath) + if err != nil { + resp.Error = err + return + } + + isSensitive, ok := result.(bool) + if !ok { + resp.Error = fmt.Errorf("invalid path: the path value cannot be asserted as bool") + return + } + + if !isSensitive { + resp.Error = fmt.Errorf("attribute at path is not sensitive") + return + } + + return + } + + resp.Error = fmt.Errorf("%s - Resource not found in plan ResourceChanges", e.resourceAddress) +} + +// ExpectSensitiveValue returns a plan check that asserts that the specified attribute at the given resource has a sensitive value. +// +// Due to implementation differences between the terraform-plugin-sdk and the terraform-plugin-framework, representation of sensitive +// values may differ. For example, terraform-plugin-sdk based providers may have less precise representations of sensitive values, such +// as marking whole maps as sensitive rather than individual element values. +func ExpectSensitiveValue(resourceAddress string, attributePath tfjsonpath.Path) PlanCheck { + return expectSensitiveValue{ + resourceAddress: resourceAddress, + attributePath: attributePath, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/plancheck/expect_unknown_output_value.go b/vendor/github.com/hashicorp/terraform-plugin-testing/plancheck/expect_unknown_output_value.go new file mode 100644 index 0000000000..a104b48876 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/plancheck/expect_unknown_output_value.go @@ -0,0 +1,82 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package plancheck + +import ( + "context" + "fmt" + + tfjson "github.com/hashicorp/terraform-json" + + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +var _ PlanCheck = expectUnknownOutputValue{} + +type expectUnknownOutputValue struct { + outputAddress string +} + +// CheckPlan implements the plan check logic. +func (e expectUnknownOutputValue) CheckPlan(ctx context.Context, req CheckPlanRequest, resp *CheckPlanResponse) { + var change *tfjson.Change + + for address, oc := range req.Plan.OutputChanges { + if e.outputAddress == address { + change = oc + + break + } + } + + if change == nil { + resp.Error = fmt.Errorf("%s - Output not found in plan OutputChanges", e.outputAddress) + + return + } + + result, err := tfjsonpath.Traverse(change.AfterUnknown, tfjsonpath.Path{}) + if err != nil { + // If we find the output in the known values, return a more explicit message + knownVal, knownErr := tfjsonpath.Traverse(change.After, tfjsonpath.Path{}) + if knownErr == nil { + resp.Error = fmt.Errorf("Expected unknown value at output %q, but found known value: \"%v\"", e.outputAddress, knownVal) + return + } + + resp.Error = err + return + } + + isUnknown, ok := result.(bool) + + if !ok { + resp.Error = fmt.Errorf("invalid path: the path value cannot be asserted as bool") + + return + } + + if !isUnknown { + // The output should have a known value, look first to return a more explicit message + knownVal, knownErr := tfjsonpath.Traverse(change.After, tfjsonpath.Path{}) + if knownErr == nil { + resp.Error = fmt.Errorf("Expected unknown value at output %q, but found known value: \"%v\"", e.outputAddress, knownVal) + return + } + resp.Error = fmt.Errorf("Expected unknown value at output %q, but found known value", e.outputAddress) + + return + } +} + +// ExpectUnknownOutputValue returns a plan check that asserts that the specified output has an unknown value. +// +// Due to implementation differences between the terraform-plugin-sdk and the terraform-plugin-framework, representation of unknown +// values may differ. For example, terraform-plugin-sdk based providers may have less precise representations of unknown values, such +// as marking whole maps as unknown rather than individual element values. +func ExpectUnknownOutputValue(outputAddress string) PlanCheck { + return expectUnknownOutputValue{ + outputAddress: outputAddress, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/plancheck/expect_unknown_output_value_at_path.go b/vendor/github.com/hashicorp/terraform-plugin-testing/plancheck/expect_unknown_output_value_at_path.go new file mode 100644 index 0000000000..9c54e09b83 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/plancheck/expect_unknown_output_value_at_path.go @@ -0,0 +1,85 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package plancheck + +import ( + "context" + "fmt" + + tfjson "github.com/hashicorp/terraform-json" + + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +var _ PlanCheck = expectUnknownOutputValueAtPath{} + +type expectUnknownOutputValueAtPath struct { + outputAddress string + valuePath tfjsonpath.Path +} + +// CheckPlan implements the plan check logic. +func (e expectUnknownOutputValueAtPath) CheckPlan(ctx context.Context, req CheckPlanRequest, resp *CheckPlanResponse) { + var change *tfjson.Change + + for address, oc := range req.Plan.OutputChanges { + if e.outputAddress == address { + change = oc + + break + } + } + + if change == nil { + resp.Error = fmt.Errorf("%s - Output not found in plan OutputChanges", e.outputAddress) + + return + } + + result, err := tfjsonpath.Traverse(change.AfterUnknown, e.valuePath) + if err != nil { + // If we find the output in the known values, return a more explicit message + knownVal, knownErr := tfjsonpath.Traverse(change.After, e.valuePath) + if knownErr == nil { + resp.Error = fmt.Errorf("Expected unknown value at output %q path %q, but found known value: \"%v\"", e.outputAddress, e.valuePath.String(), knownVal) + return + } + + resp.Error = err + + return + } + + isUnknown, ok := result.(bool) + + if !ok { + resp.Error = fmt.Errorf("invalid path: the path value cannot be asserted as bool") + + return + } + + if !isUnknown { + // The output should have a known value, look first to return a more explicit message + knownVal, knownErr := tfjsonpath.Traverse(change.After, e.valuePath) + if knownErr == nil { + resp.Error = fmt.Errorf("Expected unknown value at output %q path %q, but found known value: \"%v\"", e.outputAddress, e.valuePath.String(), knownVal) + return + } + resp.Error = fmt.Errorf("Expected unknown value at output %q path %q, but found known value", e.outputAddress, e.valuePath.String()) + + return + } +} + +// ExpectUnknownOutputValueAtPath returns a plan check that asserts that the specified output has an unknown value. +// +// Due to implementation differences between the terraform-plugin-sdk and the terraform-plugin-framework, representation of unknown +// values may differ. For example, terraform-plugin-sdk based providers may have less precise representations of unknown values, such +// as marking whole maps as unknown rather than individual element values. +func ExpectUnknownOutputValueAtPath(outputAddress string, valuePath tfjsonpath.Path) PlanCheck { + return expectUnknownOutputValueAtPath{ + outputAddress: outputAddress, + valuePath: valuePath, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/plancheck/expect_unknown_value.go b/vendor/github.com/hashicorp/terraform-plugin-testing/plancheck/expect_unknown_value.go new file mode 100644 index 0000000000..91ec3beb21 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/plancheck/expect_unknown_value.go @@ -0,0 +1,75 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package plancheck + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +var _ PlanCheck = expectUnknownValue{} + +type expectUnknownValue struct { + resourceAddress string + attributePath tfjsonpath.Path +} + +// CheckPlan implements the plan check logic. +func (e expectUnknownValue) CheckPlan(ctx context.Context, req CheckPlanRequest, resp *CheckPlanResponse) { + + for _, rc := range req.Plan.ResourceChanges { + if e.resourceAddress != rc.Address { + continue + } + + result, err := tfjsonpath.Traverse(rc.Change.AfterUnknown, e.attributePath) + if err != nil { + // If we find the attribute in the known values, return a more explicit message + knownVal, knownErr := tfjsonpath.Traverse(rc.Change.After, e.attributePath) + if knownErr == nil { + resp.Error = fmt.Errorf("Expected unknown value at %q, but found known value: \"%v\"", e.attributePath.String(), knownVal) + return + } + + resp.Error = err + return + } + + isUnknown, ok := result.(bool) + if !ok { + resp.Error = fmt.Errorf("invalid path: the path value cannot be asserted as bool") + return + } + + if !isUnknown { + // The attribute should have a known value, look first to return a more explicit message + knownVal, knownErr := tfjsonpath.Traverse(rc.Change.After, e.attributePath) + if knownErr == nil { + resp.Error = fmt.Errorf("Expected unknown value at %q, but found known value: \"%v\"", e.attributePath.String(), knownVal) + return + } + + resp.Error = fmt.Errorf("Expected unknown value at %q, but found known value", e.attributePath.String()) + return + } + + return + } + + resp.Error = fmt.Errorf("%s - Resource not found in plan ResourceChanges", e.resourceAddress) +} + +// ExpectUnknownValue returns a plan check that asserts that the specified attribute at the given resource has an unknown value. +// +// Due to implementation differences between the terraform-plugin-sdk and the terraform-plugin-framework, representation of unknown +// values may differ. For example, terraform-plugin-sdk based providers may have less precise representations of unknown values, such +// as marking whole maps as unknown rather than individual element values. +func ExpectUnknownValue(resourceAddress string, attributePath tfjsonpath.Path) PlanCheck { + return expectUnknownValue{ + resourceAddress: resourceAddress, + attributePath: attributePath, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/plancheck/plan_check.go b/vendor/github.com/hashicorp/terraform-plugin-testing/plancheck/plan_check.go new file mode 100644 index 0000000000..b6ec0d1997 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/plancheck/plan_check.go @@ -0,0 +1,30 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package plancheck + +import ( + "context" + + tfjson "github.com/hashicorp/terraform-json" +) + +// PlanCheck defines an interface for implementing test logic that checks a plan file and then returns an error +// if the plan file does not match what is expected. +type PlanCheck interface { + // CheckPlan should perform the plan check. + CheckPlan(context.Context, CheckPlanRequest, *CheckPlanResponse) +} + +// CheckPlanRequest is a request for an invoke of the CheckPlan function. +type CheckPlanRequest struct { + // Plan represents a parsed plan file, retrieved via the `terraform show -json` command. + Plan *tfjson.Plan +} + +// CheckPlanResponse is a response to an invoke of the CheckPlan function. +type CheckPlanResponse struct { + // Error is used to report the failure of a plan check assertion and is combined with other PlanCheck errors + // to be reported as a test failure. + Error error +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/plancheck/resource_action.go b/vendor/github.com/hashicorp/terraform-plugin-testing/plancheck/resource_action.go new file mode 100644 index 0000000000..ff4afebcdb --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/plancheck/resource_action.go @@ -0,0 +1,50 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package plancheck + +// ResourceActionType is a string enum type that routes to a specific terraform-json.Actions function for asserting resource changes. +// - https://pkg.go.dev/github.com/hashicorp/terraform-json#Actions +// +// More information about expected resource behavior can be found at: https://developer.hashicorp.com/terraform/language/resources/behavior +type ResourceActionType string + +const ( + // ResourceActionNoop occurs when a resource is not planned to change (no-op). + // - Routes to: https://pkg.go.dev/github.com/hashicorp/terraform-json#Actions.NoOp + ResourceActionNoop ResourceActionType = "NoOp" + + // ResourceActionCreate occurs when a resource is planned to be created. + // - Routes to: https://pkg.go.dev/github.com/hashicorp/terraform-json#Actions.Create + ResourceActionCreate ResourceActionType = "Create" + + // ResourceActionRead occurs when a data source is planned to be read during the apply stage (data sources are read during plan stage when possible). + // See the data source documentation for more information on this behavior: https://developer.hashicorp.com/terraform/language/data-sources#data-resource-behavior + // - Routes to: https://pkg.go.dev/github.com/hashicorp/terraform-json#Actions.Read + ResourceActionRead ResourceActionType = "Read" + + // ResourceActionUpdate occurs when a resource is planned to be updated in-place. + // - Routes to: https://pkg.go.dev/github.com/hashicorp/terraform-json#Actions.Update + ResourceActionUpdate ResourceActionType = "Update" + + // ResourceActionDestroy occurs when a resource is planned to be deleted. + // - Routes to: https://pkg.go.dev/github.com/hashicorp/terraform-json#Actions.Delete + ResourceActionDestroy ResourceActionType = "Destroy" + + // ResourceActionDestroyBeforeCreate occurs when a resource is planned to be deleted and then re-created. This is the default + // behavior when terraform must change a resource argument that cannot be updated in-place due to remote API limitations. + // - Routes to: https://pkg.go.dev/github.com/hashicorp/terraform-json#Actions.DestroyBeforeCreate + ResourceActionDestroyBeforeCreate ResourceActionType = "DestroyBeforeCreate" + + // ResourceActionCreateBeforeDestroy occurs when a resource is planned to be created and then deleted. This is opt-in behavior that + // is enabled with the [create_before_destroy] meta-argument. + // - Routes to: https://pkg.go.dev/github.com/hashicorp/terraform-json#Actions.CreateBeforeDestroy + // + // [create_before_destroy]: https://developer.hashicorp.com/terraform/language/meta-arguments/lifecycle#create_before_destroy + ResourceActionCreateBeforeDestroy ResourceActionType = "CreateBeforeDestroy" + + // ResourceActionReplace can be used to verify a resource is planned to be deleted and re-created (where the order of delete and create actions are not important). + // This action matches both ResourceActionDestroyBeforeCreate and ResourceActionCreateBeforeDestroy. + // - Routes to: https://pkg.go.dev/github.com/hashicorp/terraform-json#Actions.Replace + ResourceActionReplace ResourceActionType = "Replace" +) diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/doc.go b/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/doc.go new file mode 100644 index 0000000000..67aa0ceded --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/doc.go @@ -0,0 +1,5 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// Package querycheck contains the query check interface, request/response structs, and common query check implementations. +package querycheck diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/expect_identity.go b/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/expect_identity.go new file mode 100644 index 0000000000..3943909913 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/expect_identity.go @@ -0,0 +1,105 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package querycheck + +import ( + "context" + "errors" + "fmt" + "sort" + "strings" + + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/statecheck" +) + +var _ QueryResultCheck = expectIdentity{} + +type expectIdentity struct { + listResourceAddress string + check map[string]knownvalue.Check +} + +// CheckQuery implements the query check logic. +func (e expectIdentity) CheckQuery(_ context.Context, req CheckQueryRequest, resp *CheckQueryResponse) { + for _, res := range req.Query { + var errCollection []error + + if e.listResourceAddress != strings.TrimPrefix(res.Address, "list.") { + continue + } + + if len(res.Identity) != len(e.check) { + deltaMsg := "" + if len(res.Identity) > len(e.check) { + deltaMsg = statecheck.CreateDeltaString(res.Identity, e.check, "actual identity has extra attribute(s): ") + } else { + deltaMsg = statecheck.CreateDeltaString(e.check, res.Identity, "actual identity is missing attribute(s): ") + } + + resp.Error = fmt.Errorf("%s - Expected %d attribute(s) in the actual identity object, got %d attribute(s): %s", e.listResourceAddress, len(e.check), len(res.Identity), deltaMsg) + return + } + + var keys []string + + for k := range e.check { + keys = append(keys, k) + } + + sort.SliceStable(keys, func(i, j int) bool { + return keys[i] < keys[j] + }) + + for _, k := range keys { + actualIdentityVal, ok := res.Identity[k] + + if !ok { + resp.Error = fmt.Errorf("%s - missing attribute %q in actual identity object", e.listResourceAddress, k) + return + } + + if err := e.check[k].CheckValue(actualIdentityVal); err != nil { + errCollection = append(errCollection, fmt.Errorf("%s - %q identity attribute: %s", e.listResourceAddress, k, err)) + } + } + + if errCollection == nil { + return + } + } + + var errCollection []error + errCollection = append(errCollection, fmt.Errorf("an identity with the following attributes was not found")) + + var keys []string + + for k := range e.check { + keys = append(keys, k) + } + + sort.SliceStable(keys, func(i, j int) bool { + return keys[i] < keys[j] + }) + + // wrap errors for each check + for _, attr := range keys { + check := e.check[attr] + errCollection = append(errCollection, fmt.Errorf("attribute %q: %s", attr, check)) + } + + errCollection = append(errCollection, fmt.Errorf("address: %s\n", e.listResourceAddress)) + resp.Error = errors.Join(errCollection...) +} + +// ExpectIdentity returns a query check that asserts that the identity at the given resource matches a known object, where each +// map key represents an identity attribute name. The identity in query must exactly match the given object. +// +// This query check can only be used with managed resources that support resource identity and query. Query is only supported in Terraform v1.14+ +func ExpectIdentity(resourceAddress string, identity map[string]knownvalue.Check) QueryResultCheck { + return expectIdentity{ + listResourceAddress: resourceAddress, + check: identity, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/expect_no_identity.go b/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/expect_no_identity.go new file mode 100644 index 0000000000..f5d92f1f34 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/expect_no_identity.go @@ -0,0 +1,89 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package querycheck + +import ( + "context" + "errors" + "fmt" + "sort" + "strings" + + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/statecheck" +) + +var _ QueryResultCheck = expectNoIdentity{} + +type expectNoIdentity struct { + listResourceAddress string + check map[string]knownvalue.Check +} + +// CheckQuery implements the query check logic. +func (e expectNoIdentity) CheckQuery(_ context.Context, req CheckQueryRequest, resp *CheckQueryResponse) { + for _, res := range req.Query { + var errCollection []error + + if e.listResourceAddress != strings.TrimPrefix(res.Address, "list.") { + continue + } + + if len(res.Identity) != len(e.check) { + deltaMsg := "" + if len(res.Identity) > len(e.check) { + deltaMsg = statecheck.CreateDeltaString(res.Identity, e.check, "actual identity has extra attribute(s): ") + } else { + deltaMsg = statecheck.CreateDeltaString(e.check, res.Identity, "actual identity is missing attribute(s): ") + } + + resp.Error = fmt.Errorf("%s - Expected %d attribute(s) in the actual identity object, got %d attribute(s): %s", e.listResourceAddress, len(e.check), len(res.Identity), deltaMsg) + return + } + + var keys []string + + for k := range e.check { + keys = append(keys, k) + } + + sort.SliceStable(keys, func(i, j int) bool { + return keys[i] < keys[j] + }) + + for _, k := range keys { + actualIdentityVal, ok := res.Identity[k] + + if !ok { + resp.Error = fmt.Errorf("%s - missing attribute %q in actual identity object", e.listResourceAddress, k) + return + } + + if err := e.check[k].CheckValue(actualIdentityVal); err != nil { + errCollection = append(errCollection, fmt.Errorf("%s - %q identity attribute: %s", e.listResourceAddress, k, err)) + } + } + + if errCollection == nil { + errs := []error{fmt.Errorf("an unexpected identity matching the given attributes was found")} + // wrap errors for each check + for attr, check := range e.check { + errs = append(errs, fmt.Errorf("attribute %q: %s", attr, check)) + } + errs = append(errs, fmt.Errorf("address: %s\n", e.listResourceAddress)) + resp.Error = errors.Join(errs...) + } + } +} + +// ExpectNoIdentity returns a query check that asserts that the identity at the given resource does not match a known object, where each +// map key represents an identity attribute name. The identity in query must exactly match the given object. +// +// This query check can only be used with managed resources that support resource identity and query. Query is only supported in Terraform v1.14+ +func ExpectNoIdentity(resourceAddress string, identity map[string]knownvalue.Check) QueryResultCheck { + return expectNoIdentity{ + listResourceAddress: resourceAddress, + check: identity, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/expect_resource_display_name.go b/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/expect_resource_display_name.go new file mode 100644 index 0000000000..89f3f00c7f --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/expect_resource_display_name.go @@ -0,0 +1,70 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package querycheck + +import ( + "context" + "fmt" + "strings" + + tfjson "github.com/hashicorp/terraform-json" + + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/querycheck/queryfilter" +) + +var _ QueryResultCheck = expectResourceDisplayName{} +var _ QueryResultCheckWithFilters = expectResourceDisplayName{} + +type expectResourceDisplayName struct { + listResourceAddress string + filter queryfilter.QueryFilter + displayName knownvalue.Check +} + +func (e expectResourceDisplayName) QueryFilters(ctx context.Context) []queryfilter.QueryFilter { + if e.filter == nil { + return []queryfilter.QueryFilter{} + } + + return []queryfilter.QueryFilter{ + e.filter, + } +} + +func (e expectResourceDisplayName) CheckQuery(_ context.Context, req CheckQueryRequest, resp *CheckQueryResponse) { + listRes := make([]tfjson.ListResourceFoundData, 0) + for _, result := range req.Query { + if strings.TrimPrefix(result.Address, "list.") == e.listResourceAddress { + listRes = append(listRes, result) + } + } + + if len(listRes) == 0 { + resp.Error = fmt.Errorf("%s - no query results found after filtering", e.listResourceAddress) + return + } + + if len(listRes) > 1 { + resp.Error = fmt.Errorf("%s - more than 1 query result found after filtering", e.listResourceAddress) + return + } + res := listRes[0] + if err := e.displayName.CheckValue(res.DisplayName); err != nil { + resp.Error = fmt.Errorf("error checking value for display name %s, err: %s", e.displayName.String(), err) + return + } + +} + +// ExpectResourceDisplayName returns a query check that asserts that a resource with a given display name exists within the returned results of the query. +// +// This query check can only be used with managed resources that support query. Query is only supported in Terraform v1.14+ +func ExpectResourceDisplayName(listResourceAddress string, filter queryfilter.QueryFilter, displayName knownvalue.Check) QueryResultCheck { + return expectResourceDisplayName{ + listResourceAddress: listResourceAddress, + filter: filter, + displayName: displayName, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/expect_resource_known_values.go b/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/expect_resource_known_values.go new file mode 100644 index 0000000000..8185d8f049 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/expect_resource_known_values.go @@ -0,0 +1,93 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package querycheck + +import ( + "context" + "fmt" + tfjson "github.com/hashicorp/terraform-json" + "strings" + + "github.com/hashicorp/terraform-plugin-testing/querycheck/queryfilter" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +var _ QueryResultCheck = expectResourceKnownValues{} +var _ QueryResultCheckWithFilters = expectResourceKnownValues{} + +type expectResourceKnownValues struct { + listResourceAddress string + filter queryfilter.QueryFilter + knownValueChecks []KnownValueCheck +} + +func (e expectResourceKnownValues) QueryFilters(ctx context.Context) []queryfilter.QueryFilter { + if e.filter == nil { + return []queryfilter.QueryFilter{} + } + + return []queryfilter.QueryFilter{ + e.filter, + } +} + +func (e expectResourceKnownValues) CheckQuery(_ context.Context, req CheckQueryRequest, resp *CheckQueryResponse) { + listRes := make([]tfjson.ListResourceFoundData, 0) + var diags []error + for _, res := range req.Query { + if e.listResourceAddress == strings.TrimPrefix(res.Address, "list.") { + listRes = append(listRes, res) + } + } + + if len(listRes) == 0 { + resp.Error = fmt.Errorf("%s - no query results found after filtering", e.listResourceAddress) + return + } + + if len(listRes) > 1 { + resp.Error = fmt.Errorf("%s - more than 1 query result found after filtering", e.listResourceAddress) + return + } + + res := listRes[0] + + if res.ResourceObject == nil { + resp.Error = fmt.Errorf("%s - no resource object was returned, ensure `include_resource` has been set to `true` in the list resource config`", e.listResourceAddress) + return + } + + for _, c := range e.knownValueChecks { + resource, err := tfjsonpath.Traverse(res.ResourceObject, c.Path) + if err != nil { + resp.Error = err + return + } + + if err := c.KnownValue.CheckValue(resource); err != nil { + diags = append(diags, fmt.Errorf("error checking value for attribute at path: %s for resource with identity %s, err: %s", c.Path.String(), e.filter, err)) + } + } + + if diags != nil { + var diagsStr string + for _, diag := range diags { + diagsStr += diag.Error() + "; " + } + resp.Error = fmt.Errorf("the following errors were found while checking values: %s", diagsStr) + return + } +} + +// ExpectResourceKnownValues returns a query check which asserts that a resource object identified by a query filter +// passes the given query checks. +// +// This query check can only be used with managed resources that support resource identity and query. Query is only supported in Terraform v1.14+ +func ExpectResourceKnownValues(listResourceAddress string, filter queryfilter.QueryFilter, knownValues []KnownValueCheck) QueryResultCheck { + return expectResourceKnownValues{ + listResourceAddress: listResourceAddress, + filter: filter, + knownValueChecks: knownValues, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/expect_result_length_atleast.go b/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/expect_result_length_atleast.go new file mode 100644 index 0000000000..6b3dbde8ab --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/expect_result_length_atleast.go @@ -0,0 +1,39 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package querycheck + +import ( + "context" + "fmt" +) + +var _ QueryResultCheck = expectLengthAtLeast{} + +type expectLengthAtLeast struct { + resourceAddress string + check int +} + +// CheckQuery implements the query check logic. +func (e expectLengthAtLeast) CheckQuery(_ context.Context, req CheckQueryRequest, resp *CheckQueryResponse) { + if req.QuerySummary == nil { + resp.Error = fmt.Errorf("no completed query information available") + return + } + + if req.QuerySummary.Total < e.check { + resp.Error = fmt.Errorf("Query result of at least length %v - expected but got %v.", e.check, req.QuerySummary.Total) + return + } +} + +// ExpectLengthAtLeast returns a query check that asserts that the length of the query result is at least the given value. +// +// This query check can only be used with managed resources that support query. Query is only supported in Terraform v1.14+ +func ExpectLengthAtLeast(resourceAddress string, length int) QueryResultCheck { + return expectLengthAtLeast{ + resourceAddress: resourceAddress, + check: length, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/expect_result_length_exact.go b/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/expect_result_length_exact.go new file mode 100644 index 0000000000..f10546ac25 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/expect_result_length_exact.go @@ -0,0 +1,39 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package querycheck + +import ( + "context" + "fmt" +) + +var _ QueryResultCheck = expectLength{} + +type expectLength struct { + resourceAddress string + check int +} + +// CheckQuery implements the query check logic. +func (e expectLength) CheckQuery(_ context.Context, req CheckQueryRequest, resp *CheckQueryResponse) { + if req.QuerySummary == nil { + resp.Error = fmt.Errorf("no query summary information available") + return + } + + if e.check != req.QuerySummary.Total { + resp.Error = fmt.Errorf("number of found resources %v - expected but got %v.", e.check, req.QuerySummary.Total) + return + } +} + +// ExpectLength returns a query check that asserts that the length of the query result is exactly the given value. +// +// This query check can only be used with managed resources that support query. Query is only supported in Terraform v1.14+ +func ExpectLength(resourceAddress string, length int) QueryResultCheck { + return expectLength{ + resourceAddress: resourceAddress, + check: length, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/known_value.go b/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/known_value.go new file mode 100644 index 0000000000..67179ec29b --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/known_value.go @@ -0,0 +1,19 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package querycheck + +import ( + "github.com/hashicorp/terraform-plugin-testing/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +// KnownValueCheck represents a check of a known value at a specific JSON path +// and is used to specify multiple known value checks to assert against a +// single resource object returned by a query. +type KnownValueCheck struct { + // Path specifies the JSON path to check within the resource object. + Path tfjsonpath.Path + // KnownValue specifies the expected known value check to perform at the given path. + KnownValue knownvalue.Check +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/query_check.go b/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/query_check.go new file mode 100644 index 0000000000..9728f11d93 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/query_check.go @@ -0,0 +1,43 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package querycheck + +import ( + "context" + + tfjson "github.com/hashicorp/terraform-json" + + "github.com/hashicorp/terraform-plugin-testing/querycheck/queryfilter" +) + +// QueryResultCheck defines an interface for implementing test logic to apply an assertion against a collection of found +// resources that were returned by a query. It returns an error if the query results do not match what is expected. +type QueryResultCheck interface { + // CheckQuery should perform the query check. + CheckQuery(context.Context, CheckQueryRequest, *CheckQueryResponse) +} + +// QueryResultCheckWithFilters is an interface type that extends QueryResultCheck to include declarative query filters. +type QueryResultCheckWithFilters interface { + QueryResultCheck + + // QueryFilters should return a slice of queryfilter.QueryFilter that will be applied to the check. + QueryFilters(context.Context) []queryfilter.QueryFilter +} + +// CheckQueryRequest is a request for an invoke of the CheckQuery function. +type CheckQueryRequest struct { + // Query represents the parsed log messages relating to found resources returned by the `terraform query -json` command. + Query []tfjson.ListResourceFoundData + + // QuerySummary contains a summary of the completed query operation + QuerySummary *tfjson.ListCompleteData +} + +// CheckQueryResponse is a response to an invoke of the CheckQuery function. +type CheckQueryResponse struct { + // Error is used to report the failure of a query check assertion and is combined with other QueryResultCheck errors + // to be reported as a test failure. + Error error +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/queryfilter/filter.go b/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/queryfilter/filter.go new file mode 100644 index 0000000000..716ccc2a6f --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/queryfilter/filter.go @@ -0,0 +1,32 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package queryfilter + +import ( + "context" + + tfjson "github.com/hashicorp/terraform-json" +) + +// QueryFilter defines an interface for implementing declarative filtering logic to apply to query results before +// the results are passed to a query check request. +type QueryFilter interface { + Filter(context.Context, FilterQueryRequest, *FilterQueryResponse) +} + +// FilterQueryRequest is a request to a filter function. +type FilterQueryRequest struct { + // QueryItem represents a single parsed log message relating to a found resource returned by the `terraform query -json` command. + QueryItem tfjson.ListResourceFoundData +} + +// FilterQueryResponse is a response to a filter function. +type FilterQueryResponse struct { + // Include indicates whether the QueryItem should be included in CheckQueryRequest.Query + Include bool + + // Error is used to report the failure of filtering and is combined with other QueryFilter errors + // to be reported as a test failure. + Error error +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/queryfilter/filter_by_display_name.go b/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/queryfilter/filter_by_display_name.go new file mode 100644 index 0000000000..025559b7ce --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/queryfilter/filter_by_display_name.go @@ -0,0 +1,29 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package queryfilter + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-testing/knownvalue" +) + +type filterByDisplayName struct { + displayNameCheck knownvalue.Check +} + +func (f filterByDisplayName) Filter(ctx context.Context, req FilterQueryRequest, resp *FilterQueryResponse) { + if err := f.displayNameCheck.CheckValue(req.QueryItem.DisplayName); err == nil { + resp.Include = true + return + } +} + +// ByDisplayNameExact returns a query filter that only includes query items that match +// the specified display name. +func ByDisplayName(displayNameCheck knownvalue.Check) QueryFilter { + return filterByDisplayName{ + displayNameCheck: displayNameCheck, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/queryfilter/filter_by_resource_identity.go b/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/queryfilter/filter_by_resource_identity.go new file mode 100644 index 0000000000..119da9b3e1 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/querycheck/queryfilter/filter_by_resource_identity.go @@ -0,0 +1,59 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package queryfilter + +import ( + "context" + "sort" + + "github.com/hashicorp/terraform-plugin-testing/knownvalue" +) + +type filterByResourceIdentity struct { + identity map[string]knownvalue.Check +} + +func (f filterByResourceIdentity) Filter(ctx context.Context, req FilterQueryRequest, resp *FilterQueryResponse) { + if len(req.QueryItem.Identity) != len(f.identity) { + resp.Include = false + return + } + + var keys []string + + for k := range f.identity { + keys = append(keys, k) + } + + sort.SliceStable(keys, func(i, j int) bool { + return keys[i] < keys[j] + }) + + for _, k := range keys { + actualIdentityVal, ok := req.QueryItem.Identity[k] + + if !ok { + resp.Include = false + return + } + + if err := f.identity[k].CheckValue(actualIdentityVal); err != nil { + resp.Include = false + return + } + } + + resp.Include = true +} + +// ByResourceIdentity returns a query filter that only includes query items that match +// the given resource identity. +// +// Errors thrown by the given known value checks are only used to filter out non-matching query +// items and are otherwise ignored. +func ByResourceIdentity(identity map[string]knownvalue.Check) QueryFilter { + return filterByResourceIdentity{ + identity: identity, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/statecheck/compare_value.go b/vendor/github.com/hashicorp/terraform-plugin-testing/statecheck/compare_value.go new file mode 100644 index 0000000000..68a6ef9d58 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/statecheck/compare_value.go @@ -0,0 +1,114 @@ +// 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 values retrieved from state using the +// supplied value comparer. +func CompareValue(comparer compare.ValueComparer) *compareValue { + return &compareValue{ + comparer: comparer, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/statecheck/compare_value_collection.go b/vendor/github.com/hashicorp/terraform-plugin-testing/statecheck/compare_value_collection.go new file mode 100644 index 0000000000..7a06c60107 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/statecheck/compare_value_collection.go @@ -0,0 +1,223 @@ +// 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 + } + + 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 + } + + x, err := tfjsonpath.Traverse(t, 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 + } + + 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 { + resp.Error = err + + return + } + + // Verify resultOne is a collection. + switch t := resultOne.(type) { + case []any, map[string]any: + // Collection found. + 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) + + 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/vendor/github.com/hashicorp/terraform-plugin-testing/statecheck/compare_value_pairs.go b/vendor/github.com/hashicorp/terraform-plugin-testing/statecheck/compare_value_pairs.go new file mode 100644 index 0000000000..8db67c5625 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/statecheck/compare_value_pairs.go @@ -0,0 +1,111 @@ +// 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 + } +} + +// 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, + attributePathOne: attributePathOne, + resourceAddressTwo: resourceAddressTwo, + attributePathTwo: attributePathTwo, + comparer: comparer, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/statecheck/doc.go b/vendor/github.com/hashicorp/terraform-plugin-testing/statecheck/doc.go new file mode 100644 index 0000000000..eba32447d2 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/statecheck/doc.go @@ -0,0 +1,5 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// Package statecheck contains the state check interface, request/response structs, and common state check implementations. +package statecheck diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/statecheck/expect_identity.go b/vendor/github.com/hashicorp/terraform-plugin-testing/statecheck/expect_identity.go new file mode 100644 index 0000000000..a89a06e6d9 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/statecheck/expect_identity.go @@ -0,0 +1,138 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package statecheck + +import ( + "context" + "fmt" + "maps" + "slices" + "sort" + + tfjson "github.com/hashicorp/terraform-json" + + "github.com/hashicorp/terraform-plugin-testing/knownvalue" +) + +var _ StateCheck = expectIdentity{} + +type expectIdentity struct { + resourceAddress string + identity map[string]knownvalue.Check +} + +// CheckState implements the state check logic. +func (e expectIdentity) 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 + } + + if resource.IdentitySchemaVersion == nil || len(resource.IdentityValues) == 0 { + resp.Error = fmt.Errorf("%s - Identity not found in state. Either the resource does not support identity or the Terraform version running the test does not support identity. (must be v1.12+)", e.resourceAddress) + + return + } + + if len(resource.IdentityValues) != len(e.identity) { + deltaMsg := "" + if len(resource.IdentityValues) > len(e.identity) { + deltaMsg = CreateDeltaString(resource.IdentityValues, e.identity, "actual identity has extra attribute(s): ") + } else { + deltaMsg = CreateDeltaString(e.identity, resource.IdentityValues, "actual identity is missing attribute(s): ") + } + + resp.Error = fmt.Errorf("%s - Expected %d attribute(s) in the actual identity object, got %d attribute(s): %s", e.resourceAddress, len(e.identity), len(resource.IdentityValues), deltaMsg) + return + } + + var keys []string + + for k := range e.identity { + keys = append(keys, k) + } + + sort.SliceStable(keys, func(i, j int) bool { + return keys[i] < keys[j] + }) + + for _, k := range keys { + actualIdentityVal, ok := resource.IdentityValues[k] + + if !ok { + resp.Error = fmt.Errorf("%s - missing attribute %q in actual identity object", e.resourceAddress, k) + return + } + + if err := e.identity[k].CheckValue(actualIdentityVal); err != nil { + resp.Error = fmt.Errorf("%s - %q identity attribute: %s", e.resourceAddress, k, err) + return + } + } +} + +// ExpectIdentity returns a state check that asserts that the identity at the given resource matches a known object, where each +// map key represents an identity attribute name. The identity in state must exactly match the given object and any missing/extra +// attributes will raise a diagnostic. +// +// This state check can only be used with managed resources that support resource identity. Resource identity is only supported in Terraform v1.12+ +func ExpectIdentity(resourceAddress string, identity map[string]knownvalue.Check) StateCheck { + return expectIdentity{ + resourceAddress: resourceAddress, + identity: identity, + } +} + +// CreateDeltaString prints the map keys that are present in mapA and not present in mapB +func CreateDeltaString[T any, V any](mapA map[string]T, mapB map[string]V, msgPrefix string) string { + deltaMsg := "" + + deltaMap := make(map[string]T, len(mapA)) + maps.Copy(deltaMap, mapA) + for key := range mapB { + delete(deltaMap, key) + } + + deltaKeys := slices.Sorted(maps.Keys(deltaMap)) + + for i, k := range deltaKeys { + if i == 0 { + deltaMsg += msgPrefix + } else { + deltaMsg += ", " + } + deltaMsg += fmt.Sprintf("%q", k) + } + + return deltaMsg +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/statecheck/expect_identity_value.go b/vendor/github.com/hashicorp/terraform-plugin-testing/statecheck/expect_identity_value.go new file mode 100644 index 0000000000..22da58ea82 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/statecheck/expect_identity_value.go @@ -0,0 +1,91 @@ +// 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/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +var _ StateCheck = expectIdentityValue{} + +type expectIdentityValue struct { + resourceAddress string + attributePath tfjsonpath.Path + identityValue knownvalue.Check +} + +// CheckState implements the state check logic. +func (e expectIdentityValue) 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 + } + + if resource.IdentitySchemaVersion == nil || len(resource.IdentityValues) == 0 { + resp.Error = fmt.Errorf("%s - Identity not found in state. Either the resource does not support identity or the Terraform version running the test does not support identity. (must be v1.12+)", e.resourceAddress) + + return + } + + result, err := tfjsonpath.Traverse(resource.IdentityValues, e.attributePath) + + if err != nil { + resp.Error = err + + return + } + + if err := e.identityValue.CheckValue(result); err != nil { + resp.Error = fmt.Errorf("error checking identity value for attribute at path: %s.%s, err: %s", e.resourceAddress, e.attributePath.String(), err) + + return + } +} + +// ExpectIdentityValue returns a state check that asserts that the specified identity attribute at the given resource +// matches a known value. This state check can only be used with managed resources that support resource identity. +// +// Resource identity is only supported in Terraform v1.12+ +func ExpectIdentityValue(resourceAddress string, attributePath tfjsonpath.Path, identityValue knownvalue.Check) StateCheck { + return expectIdentityValue{ + resourceAddress: resourceAddress, + attributePath: attributePath, + identityValue: identityValue, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/statecheck/expect_identity_value_matches_state.go b/vendor/github.com/hashicorp/terraform-plugin-testing/statecheck/expect_identity_value_matches_state.go new file mode 100644 index 0000000000..1e3c6ea14f --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/statecheck/expect_identity_value_matches_state.go @@ -0,0 +1,97 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package statecheck + +import ( + "context" + "fmt" + "reflect" + + tfjson "github.com/hashicorp/terraform-json" + + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +var _ StateCheck = expectIdentityValueMatchesState{} + +type expectIdentityValueMatchesState struct { + resourceAddress string + attributePath tfjsonpath.Path +} + +// CheckState implements the state check logic. +func (e expectIdentityValueMatchesState) 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 + } + + if resource.IdentitySchemaVersion == nil || len(resource.IdentityValues) == 0 { + resp.Error = fmt.Errorf("%s - Identity not found in state. Either the resource does not support identity or the Terraform version running the test does not support identity. (must be v1.12+)", e.resourceAddress) + + return + } + + identityResult, err := tfjsonpath.Traverse(resource.IdentityValues, e.attributePath) + + if err != nil { + resp.Error = err + + return + } + + stateResult, err := tfjsonpath.Traverse(resource.AttributeValues, e.attributePath) + + if err != nil { + resp.Error = err + + return + } + + if !reflect.DeepEqual(identityResult, stateResult) { + resp.Error = fmt.Errorf("expected identity and state value at path to match, but they differ: %s.%s, identity value: %v, state value: %v", e.resourceAddress, e.attributePath.String(), identityResult, stateResult) + + return + } +} + +// ExpectIdentityValueMatchesState returns a state check that asserts that the specified identity attribute at the given resource +// matches the same attribute in state. This is useful when an identity attribute is in sync with a state attribute of the same path. +// +// This state check can only be used with managed resources that support resource identity. Resource identity is only supported in Terraform v1.12+ +func ExpectIdentityValueMatchesState(resourceAddress string, attributePath tfjsonpath.Path) StateCheck { + return expectIdentityValueMatchesState{ + resourceAddress: resourceAddress, + attributePath: attributePath, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/statecheck/expect_identity_value_matches_state_at_path.go b/vendor/github.com/hashicorp/terraform-plugin-testing/statecheck/expect_identity_value_matches_state_at_path.go new file mode 100644 index 0000000000..2572439980 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/statecheck/expect_identity_value_matches_state_at_path.go @@ -0,0 +1,106 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package statecheck + +import ( + "context" + "fmt" + "reflect" + + tfjson "github.com/hashicorp/terraform-json" + + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +var _ StateCheck = expectIdentityValueMatchesStateAtPath{} + +type expectIdentityValueMatchesStateAtPath struct { + resourceAddress string + identityAttrPath tfjsonpath.Path + stateAttrPath tfjsonpath.Path +} + +// CheckState implements the state check logic. +func (e expectIdentityValueMatchesStateAtPath) 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 + } + + if resource.IdentitySchemaVersion == nil || len(resource.IdentityValues) == 0 { + resp.Error = fmt.Errorf("%s - Identity not found in state. Either the resource does not support identity or the Terraform version running the test does not support identity. (must be v1.12+)", e.resourceAddress) + + return + } + + identityResult, err := tfjsonpath.Traverse(resource.IdentityValues, e.identityAttrPath) + + if err != nil { + resp.Error = err + + return + } + + stateResult, err := tfjsonpath.Traverse(resource.AttributeValues, e.stateAttrPath) + + if err != nil { + resp.Error = err + + return + } + + if !reflect.DeepEqual(identityResult, stateResult) { + resp.Error = fmt.Errorf( + "expected identity (%[1]s.%[2]s) and state value (%[1]s.%[3]s) to match, but they differ: identity value: %[4]v, state value: %[5]v", + e.resourceAddress, + e.identityAttrPath.String(), + e.stateAttrPath.String(), + identityResult, + stateResult, + ) + + return + } +} + +// ExpectIdentityValueMatchesStateAtPath returns a state check that asserts that the specified identity attribute at the given resource +// matches the specified attribute in state. This is useful when an identity attribute is in sync with a state attribute of a different path. +// +// This state check can only be used with managed resources that support resource identity. Resource identity is only supported in Terraform v1.12+ +func ExpectIdentityValueMatchesStateAtPath(resourceAddress string, identityAttrPath, stateAttrPath tfjsonpath.Path) StateCheck { + return expectIdentityValueMatchesStateAtPath{ + resourceAddress: resourceAddress, + identityAttrPath: identityAttrPath, + stateAttrPath: stateAttrPath, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/statecheck/expect_known_output_value.go b/vendor/github.com/hashicorp/terraform-plugin-testing/statecheck/expect_known_output_value.go new file mode 100644 index 0000000000..951a364005 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/statecheck/expect_known_output_value.go @@ -0,0 +1,76 @@ +// 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/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +// Resource State Check +var _ StateCheck = expectKnownOutputValue{} + +type expectKnownOutputValue struct { + outputAddress string + knownValue knownvalue.Check +} + +// CheckState implements the state check logic. +func (e expectKnownOutputValue) CheckState(ctx context.Context, req CheckStateRequest, resp *CheckStateResponse) { + var output *tfjson.StateOutput + + 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 + } + + for address, oc := range req.State.Values.Outputs { + if e.outputAddress == address { + output = oc + + break + } + } + + if output == nil { + resp.Error = fmt.Errorf("%s - Output not found in state", e.outputAddress) + + return + } + + result, err := tfjsonpath.Traverse(output.Value, tfjsonpath.Path{}) + + if err != nil { + resp.Error = err + + return + } + + if err := e.knownValue.CheckValue(result); err != nil { + resp.Error = fmt.Errorf("error checking value for output at path: %s, err: %s", e.outputAddress, err) + + return + } +} + +// ExpectKnownOutputValue returns a state check that asserts that the specified value +// has a known type, and value. +func ExpectKnownOutputValue(outputAddress string, knownValue knownvalue.Check) StateCheck { + return expectKnownOutputValue{ + outputAddress: outputAddress, + knownValue: knownValue, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/statecheck/expect_known_output_value_at_path.go b/vendor/github.com/hashicorp/terraform-plugin-testing/statecheck/expect_known_output_value_at_path.go new file mode 100644 index 0000000000..1cdfcea796 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/statecheck/expect_known_output_value_at_path.go @@ -0,0 +1,78 @@ +// 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/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +// Resource State Check +var _ StateCheck = expectKnownOutputValueAtPath{} + +type expectKnownOutputValueAtPath struct { + outputAddress string + outputPath tfjsonpath.Path + knownValue knownvalue.Check +} + +// CheckState implements the state check logic. +func (e expectKnownOutputValueAtPath) CheckState(ctx context.Context, req CheckStateRequest, resp *CheckStateResponse) { + var output *tfjson.StateOutput + + 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 + } + + for address, oc := range req.State.Values.Outputs { + if e.outputAddress == address { + output = oc + + break + } + } + + if output == nil { + resp.Error = fmt.Errorf("%s - Output not found in state", e.outputAddress) + + return + } + + result, err := tfjsonpath.Traverse(output.Value, e.outputPath) + + if err != nil { + resp.Error = err + + return + } + + if err := e.knownValue.CheckValue(result); err != nil { + resp.Error = fmt.Errorf("error checking value for output at path: %s.%s, err: %s", e.outputAddress, e.outputPath.String(), err) + + return + } +} + +// ExpectKnownOutputValueAtPath returns a state check that asserts that the specified output at the given path +// has a known type and value. +func ExpectKnownOutputValueAtPath(outputAddress string, outputPath tfjsonpath.Path, knownValue knownvalue.Check) StateCheck { + return expectKnownOutputValueAtPath{ + outputAddress: outputAddress, + outputPath: outputPath, + knownValue: knownValue, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/statecheck/expect_known_value.go b/vendor/github.com/hashicorp/terraform-plugin-testing/statecheck/expect_known_value.go new file mode 100644 index 0000000000..aca58c4d27 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/statecheck/expect_known_value.go @@ -0,0 +1,84 @@ +// 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/knownvalue" + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +// Resource State Check +var _ StateCheck = expectKnownValue{} + +type expectKnownValue struct { + resourceAddress string + attributePath tfjsonpath.Path + knownValue knownvalue.Check +} + +// CheckState implements the state check logic. +func (e expectKnownValue) 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 err := e.knownValue.CheckValue(result); err != nil { + resp.Error = fmt.Errorf("error checking value for attribute at path: %s.%s, err: %s", e.resourceAddress, e.attributePath.String(), err) + + return + } +} + +// ExpectKnownValue returns a state check that asserts that the specified attribute at the given resource +// has a known type and value. +func ExpectKnownValue(resourceAddress string, attributePath tfjsonpath.Path, knownValue knownvalue.Check) StateCheck { + return expectKnownValue{ + resourceAddress: resourceAddress, + attributePath: attributePath, + knownValue: knownValue, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/statecheck/expect_sensitive_value.go b/vendor/github.com/hashicorp/terraform-plugin-testing/statecheck/expect_sensitive_value.go new file mode 100644 index 0000000000..ea46711197 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/statecheck/expect_sensitive_value.go @@ -0,0 +1,101 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package statecheck + +import ( + "context" + "encoding/json" + "fmt" + + tfjson "github.com/hashicorp/terraform-json" + + "github.com/hashicorp/terraform-plugin-testing/tfjsonpath" +) + +var _ StateCheck = expectSensitiveValue{} + +type expectSensitiveValue struct { + resourceAddress string + attributePath tfjsonpath.Path +} + +// CheckState implements the state check logic. +func (e expectSensitiveValue) 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 + } + + var data map[string]any + + err := json.Unmarshal(resource.SensitiveValues, &data) + + if err != nil { + resp.Error = fmt.Errorf("could not unmarshal SensitiveValues: %s", err) + + return + } + + result, err := tfjsonpath.Traverse(data, e.attributePath) + + if err != nil { + resp.Error = err + + return + } + + isSensitive, ok := result.(bool) + if !ok { + resp.Error = fmt.Errorf("invalid path: the path value cannot be asserted as bool") + + return + } + + if !isSensitive { + resp.Error = fmt.Errorf("attribute at path is not sensitive") + + return + } +} + +// ExpectSensitiveValue returns a state check that asserts that the specified attribute at the given resource has a sensitive value. +// +// Due to implementation differences between the terraform-plugin-sdk and the terraform-plugin-framework, representation of sensitive +// values may differ. For example, terraform-plugin-sdk based providers may have less precise representations of sensitive values, such +// as marking whole maps as sensitive rather than individual element values. +func ExpectSensitiveValue(resourceAddress string, attributePath tfjsonpath.Path) StateCheck { + return expectSensitiveValue{ + resourceAddress: resourceAddress, + attributePath: attributePath, + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/statecheck/state_check.go b/vendor/github.com/hashicorp/terraform-plugin-testing/statecheck/state_check.go new file mode 100644 index 0000000000..cfd2da6b1c --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/statecheck/state_check.go @@ -0,0 +1,30 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package statecheck + +import ( + "context" + + tfjson "github.com/hashicorp/terraform-json" +) + +// StateCheck defines an interface for implementing test logic that checks a state file and then returns an error +// if the state file does not match what is expected. +type StateCheck interface { + // CheckState should perform the state check. + CheckState(context.Context, CheckStateRequest, *CheckStateResponse) +} + +// CheckStateRequest is a request for an invoke of the CheckState function. +type CheckStateRequest struct { + // State represents a parsed state file, retrieved via the `terraform show -json` command. + State *tfjson.State +} + +// CheckStateResponse is a response to an invoke of the CheckState function. +type CheckStateResponse struct { + // Error is used to report the failure of a state check assertion and is combined with other StateCheck errors + // to be reported as a test failure. + Error error +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/terraform/diff.go b/vendor/github.com/hashicorp/terraform-plugin-testing/terraform/diff.go new file mode 100644 index 0000000000..f70e46e4d0 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/terraform/diff.go @@ -0,0 +1,1055 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package terraform + +import ( + "fmt" + "log" + "reflect" + "regexp" + "sort" + "strconv" + "strings" + "sync" + + "github.com/hashicorp/go-cty/cty" + + "github.com/hashicorp/terraform-plugin-testing/internal/configs/configschema" + "github.com/hashicorp/terraform-plugin-testing/internal/configs/hcl2shim" +) + +// diffChangeType is an enum with the kind of changes a diff has planned. +type diffChangeType byte + +const ( + diffInvalid diffChangeType = iota //nolint:deadcode,varcheck + diffNone + diffCreate + diffUpdate + diffDestroy + diffDestroyCreate +) + +// multiVal matches the index key to a flatmapped set, list or map +var multiVal = regexp.MustCompile(`\.(#|%)$`) + +// InstanceDiff is the diff of a resource from some state to another. +// +// Deprecated: This type is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +type InstanceDiff struct { + mu sync.Mutex + Attributes map[string]*ResourceAttrDiff + Destroy bool + DestroyDeposed bool + DestroyTainted bool + + RawConfig cty.Value + RawState cty.Value + RawPlan cty.Value + + // Meta is a simple K/V map that is stored in a diff and persisted to + // plans but otherwise is completely ignored by Terraform core. It is + // meant to be used for additional data a resource may want to pass through. + // The value here must only contain Go primitives and collections. + Meta map[string]interface{} +} + +// Deprecated: This method is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +func (d *InstanceDiff) Lock() { d.mu.Lock() } + +// Deprecated: This method is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +func (d *InstanceDiff) Unlock() { d.mu.Unlock() } + +// ApplyToValue merges the receiver into the given base value, returning a +// new value that incorporates the planned changes. The given value must +// conform to the given schema, or this method will panic. +// +// This method is intended for shimming old subsystems that still use this +// legacy diff type to work with the new-style types. +// +// Deprecated: This method is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +func (d *InstanceDiff) ApplyToValue(base cty.Value, schema *configschema.Block) (cty.Value, error) { + // Create an InstanceState attributes from our existing state. + // We can use this to more easily apply the diff changes. + attrs := hcl2shim.FlatmapValueFromHCL2(base) + applied, err := d.Apply(attrs, schema) + if err != nil { + return base, err + } + + val, err := hcl2shim.HCL2ValueFromFlatmap(applied, schema.ImpliedType()) + if err != nil { + return base, err + } + + return schema.CoerceValue(val) +} + +// Apply applies the diff to the provided flatmapped attributes, +// returning the new instance attributes. +// +// This method is intended for shimming old subsystems that still use this +// legacy diff type to work with the new-style types. +// +// Deprecated: This method is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +func (d *InstanceDiff) Apply(attrs map[string]string, schema *configschema.Block) (map[string]string, error) { + // We always build a new value here, even if the given diff is "empty", + // because we might be planning to create a new instance that happens + // to have no attributes set, and so we want to produce an empty object + // rather than just echoing back the null old value. + if attrs == nil { + attrs = map[string]string{} + } + + // Rather applying the diff to mutate the attrs, we'll copy new values into + // here to avoid the possibility of leaving stale values. + result := map[string]string{} + + if d.Destroy || d.DestroyDeposed || d.DestroyTainted { + return result, nil + } + + return d.applyBlockDiff(nil, attrs, schema) +} + +func (d *InstanceDiff) applyBlockDiff(path []string, attrs map[string]string, schema *configschema.Block) (map[string]string, error) { + result := map[string]string{} + name := "" + if len(path) > 0 { + name = path[len(path)-1] + } + + // localPrefix is used to build the local result map + localPrefix := "" + if name != "" { + localPrefix = name + "." + } + + // iterate over the schema rather than the attributes, so we can handle + // different block types separately from plain attributes + for n, attrSchema := range schema.Attributes { + var err error + newAttrs, err := d.applyAttrDiff(append(path, n), attrs, attrSchema) + + if err != nil { + return result, err + } + + for k, v := range newAttrs { + result[localPrefix+k] = v + } + } + + blockPrefix := strings.Join(path, ".") + if blockPrefix != "" { + blockPrefix += "." + } + for n, block := range schema.BlockTypes { + // we need to find the set of all keys that traverse this block + candidateKeys := map[string]bool{} + blockKey := blockPrefix + n + "." + localBlockPrefix := localPrefix + n + "." + + // we can only trust the diff for sets, since the path changes, so don't + // count existing values as candidate keys. If it turns out we're + // keeping the attributes, we will catch it down below with "keepBlock" + // after we check the set count. + if block.Nesting != configschema.NestingSet { + for k := range attrs { + if strings.HasPrefix(k, blockKey) { + nextDot := strings.Index(k[len(blockKey):], ".") + if nextDot < 0 { + continue + } + nextDot += len(blockKey) + candidateKeys[k[len(blockKey):nextDot]] = true + } + } + } + + for k, diff := range d.Attributes { + // helper/schema should not insert nil diff values, but don't panic + // if it does. + if diff == nil { + continue + } + + if strings.HasPrefix(k, blockKey) { + nextDot := strings.Index(k[len(blockKey):], ".") + if nextDot < 0 { + continue + } + + if diff.NewRemoved { + continue + } + + nextDot += len(blockKey) + candidateKeys[k[len(blockKey):nextDot]] = true + } + } + + // check each set candidate to see if it was removed. + // we need to do this, because when entire sets are removed, they may + // have the wrong key, and ony show diffs going to "" + if block.Nesting == configschema.NestingSet { + for k := range candidateKeys { + indexPrefix := strings.Join(append(path, n, k), ".") + "." + keep := false + // now check each set element to see if it's a new diff, or one + // that we're dropping. Since we're only applying the "New" + // portion of the set, we can ignore diffs that only contain "Old" + for attr, diff := range d.Attributes { + // helper/schema should not insert nil diff values, but don't panic + // if it does. + if diff == nil { + continue + } + + if !strings.HasPrefix(attr, indexPrefix) { + continue + } + + // check for empty "count" keys + if (strings.HasSuffix(attr, ".#") || strings.HasSuffix(attr, ".%")) && diff.New == "0" { + continue + } + + // removed items don't count either + if diff.NewRemoved { + continue + } + + // this must be a diff to keep + keep = true + break + } + if !keep { + delete(candidateKeys, k) + } + } + } + + for k := range candidateKeys { + newAttrs, err := d.applyBlockDiff(append(path, n, k), attrs, &block.Block) + if err != nil { + return result, err + } + + for attr, v := range newAttrs { + result[localBlockPrefix+attr] = v + } + } + + keepBlock := true + // check this block's count diff directly first, since we may not + // have candidates because it was removed and only set to "0" + if diff, ok := d.Attributes[blockKey+"#"]; ok { + if diff.New == "0" || diff.NewRemoved { + keepBlock = false + } + } + + // if there was no diff at all, then we need to keep the block attributes + if len(candidateKeys) == 0 && keepBlock { + for k, v := range attrs { + if strings.HasPrefix(k, blockKey) { + // we need the key relative to this block, so remove the + // entire prefix, then re-insert the block name. + localKey := localBlockPrefix + k[len(blockKey):] + result[localKey] = v + } + } + } + + countAddr := strings.Join(append(path, n, "#"), ".") + if countDiff, ok := d.Attributes[countAddr]; ok { + if countDiff.NewComputed { + result[localBlockPrefix+"#"] = hcl2shim.UnknownVariableValue + } else { + result[localBlockPrefix+"#"] = countDiff.New + + // While sets are complete, list are not, and we may not have all the + // information to track removals. If the list was truncated, we need to + // remove the extra items from the result. + if block.Nesting == configschema.NestingList && + countDiff.New != "" && countDiff.New != hcl2shim.UnknownVariableValue { + length, _ := strconv.Atoi(countDiff.New) + for k := range result { + if !strings.HasPrefix(k, localBlockPrefix) { + continue + } + + index := k[len(localBlockPrefix):] + nextDot := strings.Index(index, ".") + if nextDot < 1 { + continue + } + index = index[:nextDot] + i, err := strconv.Atoi(index) + if err != nil { + // this shouldn't happen since we added these + // ourself, but make note of it just in case. + log.Printf("[ERROR] bad list index in %q: %s", k, err) + continue + } + if i >= length { + delete(result, k) + } + } + } + } + } else if origCount, ok := attrs[countAddr]; ok && keepBlock { + result[localBlockPrefix+"#"] = origCount + } else { + result[localBlockPrefix+"#"] = countFlatmapContainerValues(localBlockPrefix+"#", result) + } + } + + return result, nil +} + +func (d *InstanceDiff) applyAttrDiff(path []string, attrs map[string]string, attrSchema *configschema.Attribute) (map[string]string, error) { + ty := attrSchema.Type + switch { + case ty.IsListType(), ty.IsTupleType(), ty.IsMapType(): + return d.applyCollectionDiff(path, attrs, attrSchema) + case ty.IsSetType(): + return d.applySetDiff(path, attrs, attrSchema) + default: + return d.applySingleAttrDiff(path, attrs, attrSchema) + } +} + +func (d *InstanceDiff) applySingleAttrDiff(path []string, attrs map[string]string, attrSchema *configschema.Attribute) (map[string]string, error) { + currentKey := strings.Join(path, ".") + + attr := path[len(path)-1] + + result := map[string]string{} + diff := d.Attributes[currentKey] + old, exists := attrs[currentKey] + + if diff != nil && diff.NewComputed { + result[attr] = hcl2shim.UnknownVariableValue + return result, nil + } + + // "id" must exist and not be an empty string, or it must be unknown. + // This only applied to top-level "id" fields. + if attr == "id" && len(path) == 1 { + if old == "" { + result[attr] = hcl2shim.UnknownVariableValue + } else { + result[attr] = old + } + return result, nil + } + + // attribute diffs are sometimes missed, so assume no diff means keep the + // old value + if diff == nil { + if exists { + result[attr] = old + } else { + // We need required values, so set those with an empty value. It + // must be set in the config, since if it were missing it would have + // failed validation. + if attrSchema.Required { + // we only set a missing string here, since bool or number types + // would have distinct zero value which shouldn't have been + // lost. + if attrSchema.Type == cty.String { + result[attr] = "" + } + } + } + return result, nil + } + + // check for missmatched diff values + if exists && + old != diff.Old && + old != hcl2shim.UnknownVariableValue && + diff.Old != hcl2shim.UnknownVariableValue { + return result, fmt.Errorf("diff apply conflict for %s: diff expects %q, but prior value has %q", attr, diff.Old, old) + } + + if diff.NewRemoved { + // don't set anything in the new value + return map[string]string{}, nil + } + + if diff.Old == diff.New && diff.New == "" { + // this can only be a valid empty string + if attrSchema.Type == cty.String { + result[attr] = "" + } + return result, nil + } + + if attrSchema.Computed && diff.NewComputed { + result[attr] = hcl2shim.UnknownVariableValue + return result, nil + } + + result[attr] = diff.New + + return result, nil +} + +func (d *InstanceDiff) applyCollectionDiff(path []string, attrs map[string]string, attrSchema *configschema.Attribute) (map[string]string, error) { + result := map[string]string{} + + prefix := "" + if len(path) > 1 { + prefix = strings.Join(path[:len(path)-1], ".") + "." + } + + name := "" + if len(path) > 0 { + name = path[len(path)-1] + } + + currentKey := prefix + name + + // check the index first for special handling + for k, diff := range d.Attributes { + // check the index value, which can be set, and 0 + if k == currentKey+".#" || k == currentKey+".%" || k == currentKey { + if diff.NewRemoved { + return result, nil + } + + if diff.NewComputed { + result[k[len(prefix):]] = hcl2shim.UnknownVariableValue + return result, nil + } + + // do what the diff tells us to here, so that it's consistent with applies + if diff.New == "0" { + result[k[len(prefix):]] = "0" + return result, nil + } + } + } + + // collect all the keys from the diff and the old state + noDiff := true + keys := map[string]bool{} + for k := range d.Attributes { + if !strings.HasPrefix(k, currentKey+".") { + continue + } + noDiff = false + keys[k] = true + } + + noAttrs := true + for k := range attrs { + if !strings.HasPrefix(k, currentKey+".") { + continue + } + noAttrs = false + keys[k] = true + } + + // If there's no diff and no attrs, then there's no value at all. + // This prevents an unexpected zero-count attribute in the attributes. + if noDiff && noAttrs { + return result, nil + } + + idx := "#" + if attrSchema.Type.IsMapType() { + idx = "%" + } + + for k := range keys { + // generate an schema placeholder for the values + elSchema := &configschema.Attribute{ + Type: attrSchema.Type.ElementType(), + } + + res, err := d.applySingleAttrDiff(append(path, k[len(currentKey)+1:]), attrs, elSchema) + if err != nil { + return result, err + } + + for k, v := range res { + result[name+"."+k] = v + } + } + + // Just like in nested list blocks, for simple lists we may need to fill in + // missing empty strings. + countKey := name + "." + idx + count := result[countKey] + length, _ := strconv.Atoi(count) + + if count != "" && count != hcl2shim.UnknownVariableValue && + attrSchema.Type.Equals(cty.List(cty.String)) { + // insert empty strings into missing indexes + for i := 0; i < length; i++ { + key := fmt.Sprintf("%s.%d", name, i) + if _, ok := result[key]; !ok { + result[key] = "" + } + } + } + + // now check for truncation in any type of list + if attrSchema.Type.IsListType() { + for key := range result { + if key == countKey { + continue + } + + if len(key) <= len(name)+1 { + // not sure what this is, but don't panic + continue + } + + index := key[len(name)+1:] + + // It is possible to have nested sets or maps, so look for another dot + dot := strings.Index(index, ".") + if dot > 0 { + index = index[:dot] + } + + // This shouldn't have any more dots, since the element type is only string. + num, err := strconv.Atoi(index) + if err != nil { + log.Printf("[ERROR] bad list index in %q: %s", currentKey, err) + continue + } + + if num >= length { + delete(result, key) + } + } + } + + // Fill in the count value if it wasn't present in the diff for some reason, + // or if there is no count at all. + _, countDiff := d.Attributes[countKey] + if result[countKey] == "" || (!countDiff && len(keys) != len(result)) { + result[countKey] = countFlatmapContainerValues(countKey, result) + } + + return result, nil +} + +func (d *InstanceDiff) applySetDiff(path []string, attrs map[string]string, attrSchema *configschema.Attribute) (map[string]string, error) { + // We only need this special behavior for sets of object. + if !attrSchema.Type.ElementType().IsObjectType() { + // The normal collection apply behavior will work okay for this one, then. + return d.applyCollectionDiff(path, attrs, attrSchema) + } + + // When we're dealing with a set of an object type we actually want to + // use our normal _block type_ apply behaviors, so we'll construct ourselves + // a synthetic schema that treats the object type as a block type and + // then delegate to our block apply method. + synthSchema := &configschema.Block{ + Attributes: make(map[string]*configschema.Attribute), + } + + for name, ty := range attrSchema.Type.ElementType().AttributeTypes() { + // We can safely make everything into an attribute here because in the + // event that there are nested set attributes we'll end up back in + // here again recursively and can then deal with the next level of + // expansion. + synthSchema.Attributes[name] = &configschema.Attribute{ + Type: ty, + Optional: true, + } + } + + parentPath := path[:len(path)-1] + childName := path[len(path)-1] + containerSchema := &configschema.Block{ + BlockTypes: map[string]*configschema.NestedBlock{ + childName: { + Nesting: configschema.NestingSet, + Block: *synthSchema, + }, + }, + } + + return d.applyBlockDiff(parentPath, attrs, containerSchema) +} + +// countFlatmapContainerValues returns the number of values in the flatmapped container +// (set, map, list) indexed by key. The key argument is expected to include the +// trailing ".#", or ".%". +func countFlatmapContainerValues(key string, attrs map[string]string) string { + if len(key) < 3 || !(strings.HasSuffix(key, ".#") || strings.HasSuffix(key, ".%")) { + panic(fmt.Sprintf("invalid index value %q", key)) + } + + prefix := key[:len(key)-1] + items := map[string]int{} + + for k := range attrs { + if k == key { + continue + } + if !strings.HasPrefix(k, prefix) { + continue + } + + suffix := k[len(prefix):] + dot := strings.Index(suffix, ".") + if dot > 0 { + suffix = suffix[:dot] + } + + items[suffix]++ + } + return strconv.Itoa(len(items)) +} + +// ResourceAttrDiff is the diff of a single attribute of a resource. +// +// Deprecated: This type is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +type ResourceAttrDiff struct { + Old string // Old Value + New string // New Value + NewComputed bool // True if new value is computed (unknown currently) + NewRemoved bool // True if this attribute is being removed + NewExtra interface{} // Extra information for the provider + RequiresNew bool // True if change requires new resource + Sensitive bool // True if the data should not be displayed in UI output + Type diffAttrType +} + +func (d *ResourceAttrDiff) GoString() string { + return fmt.Sprintf("*%#v", *d) +} + +// DiffAttrType is an enum type that says whether a resource attribute +// diff is an input attribute (comes from the configuration) or an +// output attribute (comes as a result of applying the configuration). An +// example input would be "ami" for AWS and an example output would be +// "private_ip". +type diffAttrType byte + +// Deprecated: This function is unintentionally exported by this Go module and +// not supported for external consumption. It will be removed in the next major +// version. +func NewInstanceDiff() *InstanceDiff { + return &InstanceDiff{Attributes: make(map[string]*ResourceAttrDiff)} +} + +// ChangeType returns the diffChangeType represented by the diff +// for this single instance. +// +// Deprecated: This method is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +func (d *InstanceDiff) ChangeType() diffChangeType { + if d.Empty() { + return diffNone + } + + if d.RequiresNew() && (d.GetDestroy() || d.GetDestroyTainted()) { + return diffDestroyCreate + } + + if d.GetDestroy() || d.GetDestroyDeposed() { + return diffDestroy + } + + if d.RequiresNew() { + return diffCreate + } + + return diffUpdate +} + +// Empty returns true if this diff encapsulates no changes. +// +// Deprecated: This method is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +func (d *InstanceDiff) Empty() bool { + if d == nil { + return true + } + + d.mu.Lock() + defer d.mu.Unlock() + return !d.Destroy && + !d.DestroyTainted && + !d.DestroyDeposed && + len(d.Attributes) == 0 +} + +// Equal compares two diffs for exact equality. +// +// This is different from the Same comparison that is supported which +// checks for operation equality taking into account computed values. Equal +// instead checks for exact equality. +// TODO: investigate why removing this unused method causes panic in tests +func (d *InstanceDiff) Equal(d2 *InstanceDiff) bool { + // If one is nil, they must both be nil + if d == nil || d2 == nil { + return d == d2 + } + + // Use DeepEqual + return reflect.DeepEqual(d, d2) +} + +func (d *InstanceDiff) GoString() string { + return fmt.Sprintf("*%#v", InstanceDiff{ + Attributes: d.Attributes, + Destroy: d.Destroy, + DestroyTainted: d.DestroyTainted, + DestroyDeposed: d.DestroyDeposed, + }) +} + +// RequiresNew returns true if the diff requires the creation of a new +// resource (implying the destruction of the old). +// +// Deprecated: This method is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +func (d *InstanceDiff) RequiresNew() bool { + if d == nil { + return false + } + + d.mu.Lock() + defer d.mu.Unlock() + + return d.requiresNew() +} + +func (d *InstanceDiff) requiresNew() bool { + if d == nil { + return false + } + + if d.DestroyTainted { + return true + } + + for _, rd := range d.Attributes { + if rd != nil && rd.RequiresNew { + return true + } + } + + return false +} + +// Deprecated: This method is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +func (d *InstanceDiff) GetDestroyDeposed() bool { + d.mu.Lock() + defer d.mu.Unlock() + + return d.DestroyDeposed +} + +// Deprecated: This method is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +func (d *InstanceDiff) GetDestroyTainted() bool { + d.mu.Lock() + defer d.mu.Unlock() + + return d.DestroyTainted +} + +// Deprecated: This method is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +func (d *InstanceDiff) GetDestroy() bool { + d.mu.Lock() + defer d.mu.Unlock() + + return d.Destroy +} + +// Deprecated: This method is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +func (d *InstanceDiff) GetAttribute(key string) (*ResourceAttrDiff, bool) { + d.mu.Lock() + defer d.mu.Unlock() + + attr, ok := d.Attributes[key] + return attr, ok +} + +// Safely copies the Attributes map +// +// Deprecated: This method is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +func (d *InstanceDiff) CopyAttributes() map[string]*ResourceAttrDiff { + d.mu.Lock() + defer d.mu.Unlock() + + attrs := make(map[string]*ResourceAttrDiff) + for k, v := range d.Attributes { + attrs[k] = v + } + + return attrs +} + +// Same checks whether or not two InstanceDiff's are the "same". When +// we say "same", it is not necessarily exactly equal. Instead, it is +// just checking that the same attributes are changing, a destroy +// isn't suddenly happening, etc. +// +// Deprecated: This method is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +func (d *InstanceDiff) Same(d2 *InstanceDiff) (bool, string) { + // we can safely compare the pointers without a lock + switch { + case d == nil && d2 == nil: + return true, "" + case d == nil || d2 == nil: + return false, "one nil" + case d == d2: + return true, "" + } + + d.mu.Lock() + defer d.mu.Unlock() + + // If we're going from requiring new to NOT requiring new, then we have + // to see if all required news were computed. If so, it is allowed since + // computed may also mean "same value and therefore not new". + oldNew := d.requiresNew() + newNew := d2.RequiresNew() + if oldNew && !newNew { + oldNew = false + + // This section builds a list of ignorable attributes for requiresNew + // by removing off any elements of collections going to zero elements. + // For collections going to zero, they may not exist at all in the + // new diff (and hence RequiresNew == false). + ignoreAttrs := make(map[string]struct{}) + for k, diffOld := range d.Attributes { + if !strings.HasSuffix(k, ".%") && !strings.HasSuffix(k, ".#") { + continue + } + + // This case is in here as a protection measure. The bug that this + // code originally fixed (GH-11349) didn't have to deal with computed + // so I'm not 100% sure what the correct behavior is. Best to leave + // the old behavior. + if diffOld.NewComputed { + continue + } + + // We're looking for the case a map goes to exactly 0. + if diffOld.New != "0" { + continue + } + + // Found it! Ignore all of these. The prefix here is stripping + // off the "%" so it is just "k." + prefix := k[:len(k)-1] + for k2 := range d.Attributes { + if strings.HasPrefix(k2, prefix) { + ignoreAttrs[k2] = struct{}{} + } + } + } + + for k, rd := range d.Attributes { + if _, ok := ignoreAttrs[k]; ok { + continue + } + + // If the field is requires new and NOT computed, then what + // we have is a diff mismatch for sure. We set that the old + // diff does REQUIRE a ForceNew. + if rd != nil && rd.RequiresNew && !rd.NewComputed { + oldNew = true + break + } + } + } + + if oldNew != newNew { + return false, fmt.Sprintf( + "diff RequiresNew; old: %t, new: %t", oldNew, newNew) + } + + // Verify that destroy matches. The second boolean here allows us to + // have mismatching Destroy if we're moving from RequiresNew true + // to false above. Therefore, the second boolean will only pass if + // we're moving from Destroy: true to false as well. + if d.Destroy != d2.GetDestroy() && d.requiresNew() == oldNew { + return false, fmt.Sprintf( + "diff: Destroy; old: %t, new: %t", d.Destroy, d2.GetDestroy()) + } + + // Go through the old diff and make sure the new diff has all the + // same attributes. To start, build up the check map to be all the keys. + checkOld := make(map[string]struct{}) + checkNew := make(map[string]struct{}) + for k := range d.Attributes { + checkOld[k] = struct{}{} + } + for k := range d2.CopyAttributes() { + checkNew[k] = struct{}{} + } + + // Make an ordered list so we are sure the approximated hashes are left + // to process at the end of the loop + keys := make([]string, 0, len(d.Attributes)) + for k := range d.Attributes { + keys = append(keys, k) + } + sort.StringSlice(keys).Sort() + + for _, k := range keys { + diffOld := d.Attributes[k] + + if _, ok := checkOld[k]; !ok { + // We're not checking this key for whatever reason (see where + // check is modified). + continue + } + + // Remove this key since we'll never hit it again + delete(checkOld, k) + delete(checkNew, k) + + _, ok := d2.GetAttribute(k) + if !ok { + // If there's no new attribute, and the old diff expected the attribute + // to be removed, that's just fine. + if diffOld.NewRemoved { + continue + } + + // If the last diff was a computed value then the absence of + // that value is allowed since it may mean the value ended up + // being the same. + if diffOld.NewComputed { + ok = true + } + + // No exact match, but maybe this is a set containing computed + // values. So check if there is an approximate hash in the key + // and if so, try to match the key. + if strings.Contains(k, "~") { + parts := strings.Split(k, ".") + parts2 := append([]string(nil), parts...) + + re := regexp.MustCompile(`^~\d+$`) + for i, part := range parts { + if re.MatchString(part) { + // we're going to consider this the base of a + // computed hash, and remove all longer matching fields + ok = true + + parts2[i] = `\d+` + parts2 = parts2[:i+1] + break + } + } + + re, err := regexp.Compile("^" + strings.Join(parts2, `\.`)) + if err != nil { + return false, fmt.Sprintf("regexp failed to compile; err: %#v", err) + } + + for k2 := range checkNew { + if re.MatchString(k2) { + delete(checkNew, k2) + } + } + } + + // This is a little tricky, but when a diff contains a computed + // list, set, or map that can only be interpolated after the apply + // command has created the dependent resources, it could turn out + // that the result is actually the same as the existing state which + // would remove the key from the diff. + if diffOld.NewComputed && (strings.HasSuffix(k, ".#") || strings.HasSuffix(k, ".%")) { + ok = true + } + + // Similarly, in a RequiresNew scenario, a list that shows up in the plan + // diff can disappear from the apply diff, which is calculated from an + // empty state. + if d.requiresNew() && (strings.HasSuffix(k, ".#") || strings.HasSuffix(k, ".%")) { + ok = true + } + + if !ok { + return false, fmt.Sprintf("attribute mismatch: %s", k) + } + } + + // search for the suffix of the base of a [computed] map, list or set. + match := multiVal.FindStringSubmatch(k) + + if diffOld.NewComputed && len(match) == 2 { + matchLen := len(match[1]) + + // This is a computed list, set, or map, so remove any keys with + // this prefix from the check list. + kprefix := k[:len(k)-matchLen] + for k2 := range checkOld { + if strings.HasPrefix(k2, kprefix) { + delete(checkOld, k2) + } + } + for k2 := range checkNew { + if strings.HasPrefix(k2, kprefix) { + delete(checkNew, k2) + } + } + } + + // We don't compare the values because we can't currently actually + // guarantee to generate the same value two two diffs created from + // the same state+config: we have some pesky interpolation functions + // that do not behave as pure functions (uuid, timestamp) and so they + // can be different each time a diff is produced. + // FIXME: Re-organize our config handling so that we don't re-evaluate + // expressions when we produce a second comparison diff during + // apply (for EvalCompareDiff). + } + + // Check for leftover attributes + if len(checkNew) > 0 { + extras := make([]string, 0, len(checkNew)) + for attr := range checkNew { + extras = append(extras, attr) + } + return false, + fmt.Sprintf("extra attributes: %s", strings.Join(extras, ", ")) + } + + return true, "" +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/terraform/instancetype.go b/vendor/github.com/hashicorp/terraform-plugin-testing/terraform/instancetype.go new file mode 100644 index 0000000000..1871445819 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/terraform/instancetype.go @@ -0,0 +1,19 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package terraform + +// This code was previously generated with a go:generate directive calling: +// go run golang.org/x/tools/cmd/stringer -type=instanceType instancetype.go +// However, it is now considered frozen and the tooling dependency has been +// removed. The String method can be manually updated if necessary. + +// instanceType is an enum of the various types of instances store in the State +type instanceType int + +const ( + typeInvalid instanceType = iota + typePrimary + typeTainted + typeDeposed +) diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/terraform/instancetype_string.go b/vendor/github.com/hashicorp/terraform-plugin-testing/terraform/instancetype_string.go new file mode 100644 index 0000000000..782ef90c05 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/terraform/instancetype_string.go @@ -0,0 +1,26 @@ +// Code generated by "stringer -type=instanceType instancetype.go"; DO NOT EDIT. + +package terraform + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[typeInvalid-0] + _ = x[typePrimary-1] + _ = x[typeTainted-2] + _ = x[typeDeposed-3] +} + +const _instanceType_name = "typeInvalidtypePrimarytypeTaintedtypeDeposed" + +var _instanceType_index = [...]uint8{0, 11, 22, 33, 44} + +func (i instanceType) String() string { + if i < 0 || i >= instanceType(len(_instanceType_index)-1) { + return "instanceType(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _instanceType_name[_instanceType_index[i]:_instanceType_index[i+1]] +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/terraform/resource.go b/vendor/github.com/hashicorp/terraform-plugin-testing/terraform/resource.go new file mode 100644 index 0000000000..e5887b5366 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/terraform/resource.go @@ -0,0 +1,362 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package terraform + +import ( + "fmt" + "reflect" + "sort" + "strconv" + "strings" + + "github.com/hashicorp/go-cty/cty" + + "github.com/hashicorp/terraform-plugin-testing/internal/configs/configschema" + "github.com/hashicorp/terraform-plugin-testing/internal/configs/hcl2shim" +) + +// InstanceInfo is used to hold information about the instance and/or +// resource being modified. +// +// Deprecated: This type is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +type InstanceInfo struct { + // Id is a unique name to represent this instance. This is not related + // to InstanceState.ID in any way. + Id string + + // ModulePath is the complete path of the module containing this + // instance. + ModulePath []string + + // Type is the resource type of this instance + Type string +} + +// ResourceConfig is a legacy type that was formerly used to represent +// interpolatable configuration blocks. It is now only used to shim to old +// APIs that still use this type, via NewResourceConfigShimmed. +// +// Deprecated: This type is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +type ResourceConfig struct { + ComputedKeys []string + Raw map[string]interface{} + Config map[string]interface{} +} + +// NewResourceConfigRaw constructs a ResourceConfig whose content is exactly +// the given value. +// +// The given value may contain hcl2shim.UnknownVariableValue to signal that +// something is computed, but it must not contain unprocessed interpolation +// sequences as we might've seen in Terraform v0.11 and prior. +// +// Deprecated: This function is unintentionally exported by this Go module and +// not supported for external consumption. It will be removed in the next major +// version. Use real Terraform configuration instead. +func NewResourceConfigRaw(raw map[string]interface{}) *ResourceConfig { + v := hcl2shim.HCL2ValueFromConfigValue(raw) + + // This is a little weird but we round-trip the value through the hcl2shim + // package here for two reasons: firstly, because that reduces the risk + // of it including something unlike what NewResourceConfigShimmed would + // produce, and secondly because it creates a copy of "raw" just in case + // something is relying on the fact that in the old world the raw and + // config maps were always distinct, and thus you could in principle mutate + // one without affecting the other. (I sure hope nobody was doing that, though!) + //nolint:forcetypeassert + cfg := hcl2shim.ConfigValueFromHCL2(v).(map[string]interface{}) + + return &ResourceConfig{ + Raw: raw, + Config: cfg, + + ComputedKeys: newResourceConfigShimmedComputedKeys(v, ""), + } +} + +// NewResourceConfigShimmed wraps a cty.Value of object type in a legacy +// ResourceConfig object, so that it can be passed to older APIs that expect +// this wrapping. +// +// The returned ResourceConfig is already interpolated and cannot be +// re-interpolated. It is, therefore, useful only to functions that expect +// an already-populated ResourceConfig which they then treat as read-only. +// +// If the given value is not of an object type that conforms to the given +// schema then this function will panic. +// +// Deprecated: This function is unintentionally exported by this Go module and +// not supported for external consumption. It will be removed in the next major +// version. +func NewResourceConfigShimmed(val cty.Value, schema *configschema.Block) *ResourceConfig { + if !val.Type().IsObjectType() { + panic(fmt.Errorf("NewResourceConfigShimmed given %#v; an object type is required", val.Type())) + } + ret := &ResourceConfig{} + + legacyVal := hcl2shim.ConfigValueFromHCL2Block(val, schema) + if legacyVal != nil { + ret.Config = legacyVal + + // Now we need to walk through our structure and find any unknown values, + // producing the separate list ComputedKeys to represent these. We use the + // schema here so that we can preserve the expected invariant + // that an attribute is always either wholly known or wholly unknown, while + // a child block can be partially unknown. + ret.ComputedKeys = newResourceConfigShimmedComputedKeys(val, "") + } else { + ret.Config = make(map[string]interface{}) + } + ret.Raw = ret.Config + + return ret +} + +// Record the any config values in ComputedKeys. This field had been unused in +// helper/schema, but in the new protocol we're using this so that the SDK can +// now handle having an unknown collection. The legacy diff code doesn't +// properly handle the unknown, because it can't be expressed in the same way +// between the config and diff. +func newResourceConfigShimmedComputedKeys(val cty.Value, path string) []string { + var ret []string + ty := val.Type() + + if val.IsNull() { + return ret + } + + if !val.IsKnown() { + // we shouldn't have an entirely unknown resource, but prevent empty + // strings just in case + if len(path) > 0 { + ret = append(ret, path) + } + return ret + } + + if path != "" { + path += "." + } + switch { + case ty.IsListType(), ty.IsTupleType(), ty.IsSetType(): + i := 0 + for it := val.ElementIterator(); it.Next(); i++ { + _, subVal := it.Element() + keys := newResourceConfigShimmedComputedKeys(subVal, fmt.Sprintf("%s%d", path, i)) + ret = append(ret, keys...) + } + + case ty.IsMapType(), ty.IsObjectType(): + for it := val.ElementIterator(); it.Next(); { + subK, subVal := it.Element() + keys := newResourceConfigShimmedComputedKeys(subVal, fmt.Sprintf("%s%s", path, subK.AsString())) + ret = append(ret, keys...) + } + } + + return ret +} + +// DeepCopy performs a deep copy of the configuration. This makes it safe +// to modify any of the structures that are part of the resource config without +// affecting the original configuration. +// +// Deprecated: This method is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +func (c *ResourceConfig) DeepCopy() *ResourceConfig { + // DeepCopying a nil should return a nil to avoid panics + if c == nil { + return nil + } + + copied := &ResourceConfig{} + + if c.ComputedKeys != nil { + copied.ComputedKeys = make([]string, len(c.ComputedKeys)) + + copy(copied.ComputedKeys, c.ComputedKeys) + } + + if c.Config != nil { + copied.Config = make(map[string]any, len(c.Config)) + + for key, value := range c.Config { + copied.Config[key] = value + } + } + + if c.Raw != nil { + copied.Raw = make(map[string]any, len(c.Raw)) + + for key, value := range c.Raw { + copied.Raw[key] = value + } + } + + return copied +} + +// Equal checks the equality of two resource configs. +func (c *ResourceConfig) Equal(c2 *ResourceConfig) bool { + // If either are nil, then they're only equal if they're both nil + if c == nil || c2 == nil { + return c == c2 + } + + // Sort the computed keys so they're deterministic + sort.Strings(c.ComputedKeys) + sort.Strings(c2.ComputedKeys) + + // Two resource configs if their exported properties are equal. + // We don't compare "raw" because it is never used again after + // initialization and for all intents and purposes they are equal + // if the exported properties are equal. + check := [][2]interface{}{ + {c.ComputedKeys, c2.ComputedKeys}, + {c.Raw, c2.Raw}, + {c.Config, c2.Config}, + } + for _, pair := range check { + if !reflect.DeepEqual(pair[0], pair[1]) { + return false + } + } + + return true +} + +// Get looks up a configuration value by key and returns the value. +// +// The second return value is true if the get was successful. Get will +// return the raw value if the key is computed, so you should pair this +// with IsComputed. +// +// Deprecated: This method is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +func (c *ResourceConfig) Get(k string) (interface{}, bool) { + // We aim to get a value from the configuration. If it is computed, + // then we return the pure raw value. + source := c.Config + if c.IsComputed(k) { + source = c.Raw + } + + return c.get(k, source) +} + +// GetRaw looks up a configuration value by key and returns the value, +// from the raw, uninterpolated config. +// +// The second return value is true if the get was successful. Get will +// not succeed if the value is being computed. +// +// Deprecated: This method is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +func (c *ResourceConfig) GetRaw(k string) (interface{}, bool) { + return c.get(k, c.Raw) +} + +// IsComputed returns whether the given key is computed or not. +// +// Deprecated: This method is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +func (c *ResourceConfig) IsComputed(k string) bool { + // The next thing we do is check the config if we get a computed + // value out of it. + v, ok := c.get(k, c.Config) + if !ok { + return false + } + + // If value is nil, then it isn't computed + if v == nil { + return false + } + + // Test if the value contains an unknown value + return unknownValueWalk(reflect.ValueOf(v)) +} + +func (c *ResourceConfig) get( + k string, raw map[string]interface{}) (interface{}, bool) { + parts := strings.Split(k, ".") + if len(parts) == 1 && parts[0] == "" { + parts = nil + } + + var current interface{} = raw + var previous interface{} = nil + for i, part := range parts { + if current == nil { + return nil, false + } + + cv := reflect.ValueOf(current) + switch cv.Kind() { + case reflect.Map: + previous = current + v := cv.MapIndex(reflect.ValueOf(part)) + if !v.IsValid() { + if i > 0 && i != (len(parts)-1) { + tryKey := strings.Join(parts[i:], ".") + v := cv.MapIndex(reflect.ValueOf(tryKey)) + if !v.IsValid() { + return nil, false + } + + return v.Interface(), true + } + + return nil, false + } + + current = v.Interface() + case reflect.Slice: + previous = current + + if part == "#" { + // If any value in a list is computed, this whole thing + // is computed and we can't read any part of it. + for i := 0; i < cv.Len(); i++ { + if v := cv.Index(i).Interface(); v == hcl2shim.UnknownVariableValue { + return v, true + } + } + + current = cv.Len() + } else { + i, err := strconv.ParseInt(part, 0, 0) + if err != nil { + return nil, false + } + if int(i) < 0 || int(i) >= cv.Len() { + return nil, false + } + current = cv.Index(int(i)).Interface() + } + case reflect.String: + // This happens when map keys contain "." and have a common + // prefix so were split as path components above. + actualKey := strings.Join(parts[i-1:], ".") + if prevMap, ok := previous.(map[string]interface{}); ok { + v, ok := prevMap[actualKey] + return v, ok + } + + return nil, false + default: + panic(fmt.Sprintf("Unknown kind: %s", cv.Kind())) + } + } + + return current, true +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/terraform/resource_address.go b/vendor/github.com/hashicorp/terraform-plugin-testing/terraform/resource_address.go new file mode 100644 index 0000000000..8d92fbb5e4 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/terraform/resource_address.go @@ -0,0 +1,229 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package terraform + +import ( + "fmt" + "reflect" + "regexp" + "strconv" + "strings" +) + +// resourceAddress is a way of identifying an individual resource (or, +// eventually, a subset of resources) within the state. It is used for Targets. +type resourceAddress struct { + // Addresses a resource falling somewhere in the module path + // When specified alone, addresses all resources within a module path + Path []string + + // Addresses a specific resource that occurs in a list + Index int + + InstanceType instanceType + InstanceTypeSet bool + Name string + Type string + Mode ResourceMode // significant only if InstanceTypeSet +} + +// String outputs the address that parses into this address. +func (r *resourceAddress) String() string { + var result []string + for _, p := range r.Path { + result = append(result, "module", p) + } + + switch r.Mode { + case ManagedResourceMode: + // nothing to do + case DataResourceMode: + result = append(result, "data") + default: + panic(fmt.Errorf("unsupported resource mode %s", r.Mode)) + } + + if r.Type != "" { + result = append(result, r.Type) + } + + if r.Name != "" { + name := r.Name + if r.InstanceTypeSet { + switch r.InstanceType { + case typePrimary: + name += ".primary" + case typeDeposed: + name += ".deposed" + case typeTainted: + name += ".tainted" + } + } + + if r.Index >= 0 { + name += fmt.Sprintf("[%d]", r.Index) + } + result = append(result, name) + } + + return strings.Join(result, ".") +} + +func parseResourceAddress(s string) (*resourceAddress, error) { + matches, err := tokenizeResourceAddress(s) + if err != nil { + return nil, err + } + mode := ManagedResourceMode + if matches["data_prefix"] != "" { + mode = DataResourceMode + } + resourceIndex, err := parseResourceIndex(matches["index"]) + if err != nil { + return nil, err + } + instanceType, err := parseInstanceType(matches["instance_type"]) + if err != nil { + return nil, err + } + path := parseResourcePath(matches["path"]) + + // not allowed to say "data." without a type following + if mode == DataResourceMode && matches["type"] == "" { + return nil, fmt.Errorf( + "invalid resource address %q: must target specific data instance", + s, + ) + } + + return &resourceAddress{ + Path: path, + Index: resourceIndex, + InstanceType: instanceType, + InstanceTypeSet: matches["instance_type"] != "", + Name: matches["name"], + Type: matches["type"], + Mode: mode, + }, nil +} + +// Less returns true if and only if the receiver should be sorted before +// the given address when presenting a list of resource addresses to +// an end-user. +// +// This sort uses lexicographic sorting for most components, but uses +// numeric sort for indices, thus causing index 10 to sort after +// index 9, rather than after index 1. +func (addr *resourceAddress) Less(other *resourceAddress) bool { + + switch { + + case len(addr.Path) != len(other.Path): + return len(addr.Path) < len(other.Path) + + case !reflect.DeepEqual(addr.Path, other.Path): + // If the two paths are the same length but don't match, we'll just + // cheat and compare the string forms since it's easier than + // comparing all of the path segments in turn, and lexicographic + // comparison is correct for the module path portion. + addrStr := addr.String() + otherStr := other.String() + return addrStr < otherStr + + case addr.Mode != other.Mode: + return addr.Mode == DataResourceMode + + case addr.Type != other.Type: + return addr.Type < other.Type + + case addr.Name != other.Name: + return addr.Name < other.Name + + case addr.Index != other.Index: + // Since "Index" is -1 for an un-indexed address, this also conveniently + // sorts unindexed addresses before indexed ones, should they both + // appear for some reason. + return addr.Index < other.Index + + case addr.InstanceTypeSet != other.InstanceTypeSet: + return !addr.InstanceTypeSet + + case addr.InstanceType != other.InstanceType: + // InstanceType is actually an enum, so this is just an arbitrary + // sort based on the enum numeric values, and thus not particularly + // meaningful. + return addr.InstanceType < other.InstanceType + + default: + return false + + } +} + +func parseResourceIndex(s string) (int, error) { + if s == "" { + return -1, nil + } + return strconv.Atoi(s) +} + +func parseResourcePath(s string) []string { + if s == "" { + return nil + } + parts := strings.Split(s, ".") + path := make([]string, 0, len(parts)) + for _, s := range parts { + // Due to the limitations of the regexp match below, the path match has + // some noise in it we have to filter out :| + if s == "" || s == "module" { + continue + } + path = append(path, s) + } + return path +} + +func parseInstanceType(s string) (instanceType, error) { + switch s { + case "", "primary": + return typePrimary, nil + case "deposed": + return typeDeposed, nil + case "tainted": + return typeTainted, nil + default: + return typeInvalid, fmt.Errorf("Unexpected value for instanceType field: %q", s) + } +} + +func tokenizeResourceAddress(s string) (map[string]string, error) { + // Example of portions of the regexp below using the + // string "aws_instance.web.tainted[1]" + re := regexp.MustCompile(`\A` + + // "module.foo.module.bar" (optional) + `(?P(?:module\.(?P[^.]+)\.?)*)` + + // possibly "data.", if targeting is a data resource + `(?P(?:data\.)?)` + + // "aws_instance.web" (optional when module path specified) + `(?:(?P[^.]+)\.(?P[^.[]+))?` + + // "tainted" (optional, omission implies: "primary") + `(?:\.(?P\w+))?` + + // "1" (optional, omission implies: "0") + `(?:\[(?P\d+)\])?` + + `\z`) + + groupNames := re.SubexpNames() + rawMatches := re.FindAllStringSubmatch(s, -1) + if len(rawMatches) != 1 { + return nil, fmt.Errorf("invalid resource address %q", s) + } + + matches := make(map[string]string) + for i, m := range rawMatches[0] { + matches[groupNames[i]] = m + } + + return matches, nil +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/terraform/resource_mode.go b/vendor/github.com/hashicorp/terraform-plugin-testing/terraform/resource_mode.go new file mode 100644 index 0000000000..2d7b10bcff --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/terraform/resource_mode.go @@ -0,0 +1,18 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package terraform + +// This code was previously generated with a go:generate directive calling: +// go run golang.org/x/tools/cmd/stringer -type=ResourceMode -output=resource_mode_string.go resource_mode.go +// However, it is now considered frozen and the tooling dependency has been +// removed. The String method can be manually updated if necessary. + +// ResourceMode is deprecated, use addrs.ResourceMode instead. +// It has been preserved for backwards compatibility. +type ResourceMode int + +const ( + ManagedResourceMode ResourceMode = iota + DataResourceMode +) diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/terraform/resource_mode_string.go b/vendor/github.com/hashicorp/terraform-plugin-testing/terraform/resource_mode_string.go new file mode 100644 index 0000000000..ba84346a21 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/terraform/resource_mode_string.go @@ -0,0 +1,24 @@ +// Code generated by "stringer -type=ResourceMode -output=resource_mode_string.go resource_mode.go"; DO NOT EDIT. + +package terraform + +import "strconv" + +func _() { + // An "invalid array index" compiler error signifies that the constant values have changed. + // Re-run the stringer command to generate them again. + var x [1]struct{} + _ = x[ManagedResourceMode-0] + _ = x[DataResourceMode-1] +} + +const _ResourceMode_name = "ManagedResourceModeDataResourceMode" + +var _ResourceMode_index = [...]uint8{0, 19, 35} + +func (i ResourceMode) String() string { + if i < 0 || i >= ResourceMode(len(_ResourceMode_index)-1) { + return "ResourceMode(" + strconv.FormatInt(int64(i), 10) + ")" + } + return _ResourceMode_name[_ResourceMode_index[i]:_ResourceMode_index[i+1]] +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/terraform/resource_provider.go b/vendor/github.com/hashicorp/terraform-plugin-testing/terraform/resource_provider.go new file mode 100644 index 0000000000..6de283544b --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/terraform/resource_provider.go @@ -0,0 +1,37 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package terraform + +// ResourceType is a type of resource that a resource provider can manage. +// +// Deprecated: This type is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +type ResourceType struct { + Name string // Name of the resource, example "instance" (no provider prefix) + Importable bool // Whether this resource supports importing + + // SchemaAvailable is set if the provider supports the ProviderSchema, + // ResourceTypeSchema and DataSourceSchema methods. Although it is + // included on each resource type, it's actually a provider-wide setting + // that's smuggled here only because that avoids a breaking change to + // the plugin protocol. + SchemaAvailable bool +} + +// DataSource is a data source that a resource provider implements. +// +// Deprecated: This type is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +type DataSource struct { + Name string + + // SchemaAvailable is set if the provider supports the ProviderSchema, + // ResourceTypeSchema and DataSourceSchema methods. Although it is + // included on each resource type, it's actually a provider-wide setting + // that's smuggled here only because that avoids a breaking change to + // the plugin protocol. + SchemaAvailable bool +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/terraform/schemas.go b/vendor/github.com/hashicorp/terraform-plugin-testing/terraform/schemas.go new file mode 100644 index 0000000000..1cec2eb6fe --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/terraform/schemas.go @@ -0,0 +1,37 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package terraform + +import ( + "github.com/hashicorp/terraform-plugin-testing/internal/configs/configschema" +) + +// ProviderSchema represents the schema for a provider's own configuration +// and the configuration for some or all of its resources and data sources. +// +// The completeness of this structure depends on how it was constructed. +// When constructed for a configuration, it will generally include only +// resource types and data sources used by that configuration. +// +// Deprecated: This type is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +type ProviderSchema struct { + Provider *configschema.Block + ResourceTypes map[string]*configschema.Block + DataSources map[string]*configschema.Block + + ResourceTypeSchemaVersions map[string]uint64 +} + +// ProviderSchemaRequest is used to describe to a ResourceProvider which +// aspects of schema are required, when calling the GetSchema method. +// +// Deprecated: This type is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +type ProviderSchemaRequest struct { + ResourceTypes []string + DataSources []string +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/terraform/state.go b/vendor/github.com/hashicorp/terraform-plugin-testing/terraform/state.go new file mode 100644 index 0000000000..68267757a6 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/terraform/state.go @@ -0,0 +1,1876 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package terraform + +import ( + "bufio" + "bytes" + "encoding/json" + "errors" + "fmt" + "log" + "os" + "reflect" + "sort" + "strconv" + "strings" + "sync" + + "github.com/hashicorp/go-cty/cty" + "github.com/hashicorp/go-uuid" + + "github.com/hashicorp/terraform-plugin-testing/internal/addrs" + "github.com/hashicorp/terraform-plugin-testing/internal/configs/hcl2shim" +) + +const ( + // StateVersion is the current version for our state file + stateVersion = 3 +) + +// rootModulePath is the path of the root module +var rootModulePath = []string{"root"} + +// normalizeModulePath transforms a legacy module path (which may or may not +// have a redundant "root" label at the start of it) into an +// addrs.ModuleInstance representing the same module. +// +// For legacy reasons, different parts of Terraform disagree about whether the +// root module has the path []string{} or []string{"root"}, and so this +// function accepts both and trims off the "root". An implication of this is +// that it's not possible to actually have a module call in the root module +// that is itself named "root", since that would be ambiguous. +// +// normalizeModulePath takes a raw module path and returns a path that +// has the rootModulePath prepended to it. If I could go back in time I +// would've never had a rootModulePath (empty path would be root). We can +// still fix this but thats a big refactor that my branch doesn't make sense +// for. Instead, this function normalizes paths. +func normalizeModulePath(p []string) addrs.ModuleInstance { + // FIXME: Remove this once everyone is using addrs.ModuleInstance. + + if len(p) > 0 && p[0] == "root" { + p = p[1:] + } + + ret := make(addrs.ModuleInstance, len(p)) + for i, name := range p { + // For now we don't actually support modules with multiple instances + // identified by keys, so we just treat every path element as a + // step with no key. + ret[i] = addrs.ModuleInstanceStep{ + Name: name, + } + } + return ret +} + +// State keeps track of a snapshot state-of-the-world that Terraform +// can use to keep track of what real world resources it is actually +// managing. +type State struct { + // Version is the state file protocol version. + Version int `json:"version"` + + // TFVersion is the version of Terraform that wrote this state. + TFVersion string `json:"terraform_version,omitempty"` + + // Serial is incremented on any operation that modifies + // the State file. It is used to detect potentially conflicting + // updates. + Serial int64 `json:"serial"` + + // Lineage is set when a new, blank state is created and then + // never updated. This allows us to determine whether the serials + // of two states can be meaningfully compared. + // Apart from the guarantee that collisions between two lineages + // are very unlikely, this value is opaque and external callers + // should only compare lineage strings byte-for-byte for equality. + Lineage string `json:"lineage"` + + // Remote is used to track the metadata required to + // pull and push state files from a remote storage endpoint. + // + // Deprecated: This field is unintentionally exported by this Go module and + // external consumption is not supported. It will be removed in the next + // major version. + Remote *RemoteState `json:"remote,omitempty"` + + // Backend tracks the configuration for the backend in use with + // this state. This is used to track any changes in the backend + // configuration. + // + // Deprecated: This field is unintentionally exported by this Go module and + // external consumption is not supported. It will be removed in the next + // major version. + Backend *BackendState `json:"backend,omitempty"` + + // Modules contains all the modules in a breadth-first order + Modules []*ModuleState `json:"modules"` + + mu sync.Mutex + + // IsBinaryDrivenTest is a special flag that assists with a binary driver + // heuristic, it should not be set externally + // + // Deprecated: This field is unintentionally exported by this Go module and + // external consumption is not supported. It will be removed in the next + // major version. + IsBinaryDrivenTest bool +} + +// Deprecated: This method is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +func (s *State) Lock() { s.mu.Lock() } + +// Deprecated: This method is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +func (s *State) Unlock() { s.mu.Unlock() } + +// NewState is used to initialize a blank state +// +// Deprecated: This method is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +func NewState() *State { + s := &State{} + s.init() + return s +} + +// Children returns the ModuleStates that are direct children of +// the given path. If the path is "root", for example, then children +// returned might be "root.child", but not "root.child.grandchild". +// +// Deprecated: This method is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +func (s *State) Children(path []string) []*ModuleState { + s.Lock() + defer s.Unlock() + // TODO: test + + return s.children(path) +} + +func (s *State) children(path []string) []*ModuleState { + result := make([]*ModuleState, 0) + for _, m := range s.Modules { + if m == nil { + continue + } + + if len(m.Path) != len(path)+1 { + continue + } + if !reflect.DeepEqual(path, m.Path[:len(path)]) { + continue + } + + result = append(result, m) + } + + return result +} + +// AddModule adds the module with the given path to the state. +// +// This should be the preferred method to add module states since it +// allows us to optimize lookups later as well as control sorting. +// +// Deprecated: This method is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +func (s *State) AddModule(path addrs.ModuleInstance) *ModuleState { + s.Lock() + defer s.Unlock() + + return s.addModule(path) +} + +func (s *State) addModule(path addrs.ModuleInstance) *ModuleState { + // check if the module exists first + m := s.moduleByPath(path) + if m != nil { + return m + } + + // Lower the new-style address into a legacy-style address. + // This requires that none of the steps have instance keys, which is + // true for all addresses at the time of implementing this because + // "count" and "for_each" are not yet implemented for modules. + // For the purposes of state, the legacy address format also includes + // a redundant extra prefix element "root". It is important to include + // this because the "prune" method will remove any module that has a + // path length less than one, and other parts of the state code will + // trim off the first element indiscriminately. + legacyPath := make([]string, len(path)+1) + legacyPath[0] = "root" + for i, step := range path { + if step.InstanceKey != addrs.NoKey { + // FIXME: Once the rest of Terraform is ready to use count and + // for_each, remove all of this and just write the addrs.ModuleInstance + // value itself into the ModuleState. + panic("state cannot represent modules with count or for_each keys") + } + + legacyPath[i+1] = step.Name + } + + m = &ModuleState{Path: legacyPath} + m.init() + s.Modules = append(s.Modules, m) + s.sort() + return m +} + +// ModuleByPath is used to lookup the module state for the given path. +// This should be the preferred lookup mechanism as it allows for future +// lookup optimizations. +// +// Deprecated: This method is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +func (s *State) ModuleByPath(path addrs.ModuleInstance) *ModuleState { + if s == nil { + return nil + } + s.Lock() + defer s.Unlock() + + return s.moduleByPath(path) +} + +func (s *State) moduleByPath(path addrs.ModuleInstance) *ModuleState { + for _, mod := range s.Modules { + if mod == nil { + continue + } + if mod.Path == nil { + panic("missing module path") + } + modPath := normalizeModulePath(mod.Path) + if modPath.String() == path.String() { + return mod + } + } + return nil +} + +// Empty returns true if the state is empty. +// +// Deprecated: This method is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +func (s *State) Empty() bool { + if s == nil { + return true + } + s.Lock() + defer s.Unlock() + + return len(s.Modules) == 0 +} + +// HasResources returns true if the state contains any resources. +// +// This is similar to !s.Empty, but returns true also in the case where the +// state has modules but all of them are devoid of resources. +// +// Deprecated: This method is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +func (s *State) HasResources() bool { + if s.Empty() { + return false + } + + for _, mod := range s.Modules { + if len(mod.Resources) > 0 { + return true + } + } + + return false +} + +// IsRemote returns true if State represents a state that exists and is +// remote. +// +// Deprecated: This method is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +func (s *State) IsRemote() bool { + if s == nil { + return false + } + s.Lock() + defer s.Unlock() + + if s.Remote == nil { + return false + } + if s.Remote.Type == "" { + return false + } + + return true +} + +// Validate validates the integrity of this state file. +// +// Certain properties of the statefile are expected by Terraform in order +// to behave properly. The core of Terraform will assume that once it +// receives a State structure that it has been validated. This validation +// check should be called to ensure that. +// +// If this returns an error, then the user should be notified. The error +// response will include detailed information on the nature of the error. +// +// Deprecated: This method is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +func (s *State) Validate() error { + s.Lock() + defer s.Unlock() + + var result []error + + // !!!! FOR DEVELOPERS !!!! + // + // Any errors returned from this Validate function will BLOCK TERRAFORM + // from loading a state file. Therefore, this should only contain checks + // that are only resolvable through manual intervention. + // + // !!!! FOR DEVELOPERS !!!! + + // Make sure there are no duplicate module states. We open a new + // block here so we can use basic variable names and future validations + // can do the same. + { + found := make(map[string]struct{}) + for _, ms := range s.Modules { + if ms == nil { + continue + } + + key := strings.Join(ms.Path, ".") + if _, ok := found[key]; ok { + result = append(result, fmt.Errorf( + strings.TrimSpace(stateValidateErrMultiModule), key)) + continue + } + + found[key] = struct{}{} + } + } + + return errors.Join(result...) +} + +// Remove removes the item in the state at the given address, returning +// any errors that may have occurred. +// +// If the address references a module state or resource, it will delete +// all children as well. To check what will be deleted, use a StateFilter +// first. +// +// Deprecated: This method is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +func (s *State) Remove(addr ...string) error { + s.Lock() + defer s.Unlock() + + // Filter out what we need to delete + filter := &stateFilter{State: s} + results, err := filter.filter(addr...) + if err != nil { + return err + } + + // If we have no results, just exit early, we're not going to do anything. + // While what happens below is fairly fast, this is an important early + // exit since the prune below might modify the state more and we don't + // want to modify the state if we don't have to. + if len(results) == 0 { + return nil + } + + // Go through each result and grab what we need + removed := make(map[interface{}]struct{}) + for _, r := range results { + // Convert the path to our own type + path := append([]string{"root"}, r.Path...) + + // If we removed this already, then ignore + if _, ok := removed[r.Value]; ok { + continue + } + + // If we removed the parent already, then ignore + if r.Parent != nil { + if _, ok := removed[r.Parent.Value]; ok { + continue + } + } + + // Add this to the removed list + removed[r.Value] = struct{}{} + + switch v := r.Value.(type) { + case *ModuleState: + s.removeModule(v) + case *ResourceState: + s.removeResource(path, v) + case *InstanceState: + //nolint:forcetypeassert + s.removeInstance(r.Parent.Value.(*ResourceState), v) + default: + return fmt.Errorf("unknown type to delete: %T", r.Value) + } + } + + // Prune since the removal functions often do the bare minimum to + // remove a thing and may leave around dangling empty modules, resources, + // etc. Prune will clean that all up. + s.prune() + + return nil +} + +func (s *State) removeModule(v *ModuleState) { + for i, m := range s.Modules { + if m == v { + s.Modules, s.Modules[len(s.Modules)-1] = append(s.Modules[:i], s.Modules[i+1:]...), nil + return + } + } +} + +func (s *State) removeResource(path []string, v *ResourceState) { + // Get the module this resource lives in. If it doesn't exist, we're done. + mod := s.moduleByPath(normalizeModulePath(path)) + if mod == nil { + return + } + + // Find this resource. This is a O(N) lookup when if we had the key + // it could be O(1) but even with thousands of resources this shouldn't + // matter right now. We can easily up performance here when the time comes. + for k, r := range mod.Resources { + if r == v { + // Found it + delete(mod.Resources, k) + return + } + } +} + +func (s *State) removeInstance(r *ResourceState, v *InstanceState) { + // Go through the resource and find the instance that matches this + // (if any) and remove it. + + // Check primary + if r.Primary == v { + r.Primary = nil + return + } +} + +// RootModule returns the ModuleState for the root module +func (s *State) RootModule() *ModuleState { + root := s.ModuleByPath(addrs.RootModuleInstance) + if root == nil { + panic("missing root module") + } + return root +} + +// Equal tests if one state is equal to another. +// +// Deprecated: This method is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +func (s *State) Equal(other *State) bool { + // If one is nil, we do a direct check + if s == nil || other == nil { + return s == other + } + + s.Lock() + defer s.Unlock() + return s.equal(other) +} + +func (s *State) equal(other *State) bool { + if s == nil || other == nil { + return s == other + } + + // If the versions are different, they're certainly not equal + if s.Version != other.Version { + return false + } + + // If any of the modules are not equal, then this state isn't equal + if len(s.Modules) != len(other.Modules) { + return false + } + for _, m := range s.Modules { + // This isn't very optimal currently but works. + otherM := other.moduleByPath(normalizeModulePath(m.Path)) + if otherM == nil { + return false + } + + // If they're not equal, then we're not equal! + if !m.Equal(otherM) { + return false + } + } + + return true +} + +// Deprecated: This type is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +type StateAgeComparison int + +const ( + StateAgeEqual StateAgeComparison = 0 + StateAgeReceiverNewer StateAgeComparison = 1 + StateAgeReceiverOlder StateAgeComparison = -1 +) + +// CompareAges compares one state with another for which is "older". +// +// This is a simple check using the state's serial, and is thus only as +// reliable as the serial itself. In the normal case, only one state +// exists for a given combination of lineage/serial, but Terraform +// does not guarantee this and so the result of this method should be +// used with care. +// +// Returns an integer that is negative if the receiver is older than +// the argument, positive if the converse, and zero if they are equal. +// An error is returned if the two states are not of the same lineage, +// in which case the integer returned has no meaning. +// +// Deprecated: This method is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +func (s *State) CompareAges(other *State) (StateAgeComparison, error) { + // nil states are "older" than actual states + switch { + case s != nil && other == nil: + return StateAgeReceiverNewer, nil + case s == nil && other != nil: + return StateAgeReceiverOlder, nil + case s == nil && other == nil: + return StateAgeEqual, nil + } + + if !s.SameLineage(other) { + return StateAgeEqual, fmt.Errorf( + "can't compare two states of differing lineage", + ) + } + + s.Lock() + defer s.Unlock() + + switch { + case s.Serial < other.Serial: + return StateAgeReceiverOlder, nil + case s.Serial > other.Serial: + return StateAgeReceiverNewer, nil + default: + return StateAgeEqual, nil + } +} + +// SameLineage returns true only if the state given in argument belongs +// to the same "lineage" of states as the receiver. +// +// Deprecated: This method is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +func (s *State) SameLineage(other *State) bool { + s.Lock() + defer s.Unlock() + + // If one of the states has no lineage then it is assumed to predate + // this concept, and so we'll accept it as belonging to any lineage + // so that a lineage string can be assigned to newer versions + // without breaking compatibility with older versions. + if s.Lineage == "" || other.Lineage == "" { + return true + } + + return s.Lineage == other.Lineage +} + +// DeepCopy performs a deep copy of the state structure and returns +// a new structure. +// +// Deprecated: This method is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +func (s *State) DeepCopy() *State { + if s == nil { + return nil + } + + copied := &State{ + IsBinaryDrivenTest: s.IsBinaryDrivenTest, + Lineage: s.Lineage, + Serial: s.Serial, + TFVersion: s.TFVersion, + Version: s.Version, + } + + if s.Backend != nil { + copied.Backend = &BackendState{ + Hash: s.Backend.Hash, + ConfigRaw: s.Backend.ConfigRaw, + Type: s.Backend.Type, + } + } + + // Best effort single level copy is fine; this is method is not used by this + // Go module and its already deprecated. + if s.Modules != nil { + copied.Modules = make([]*ModuleState, len(s.Modules)) + + copy(copied.Modules, s.Modules) + } + + if s.Remote != nil { + copied.Remote = &RemoteState{ + Type: s.Remote.Type, + } + + if s.Remote.Config != nil { + copied.Remote.Config = make(map[string]string, len(s.Remote.Config)) + + for key, value := range s.Remote.Config { + copied.Remote.Config[key] = value + } + } + } + + return copied +} + +// Deprecated: This method is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +func (s *State) Init() { + s.Lock() + defer s.Unlock() + s.init() +} + +func (s *State) init() { + if s.Version == 0 { + s.Version = stateVersion + } + + if s.moduleByPath(addrs.RootModuleInstance) == nil { + s.addModule(addrs.RootModuleInstance) + } + s.ensureHasLineage() + + for _, mod := range s.Modules { + if mod != nil { + mod.init() + } + } + + if s.Remote != nil { + s.Remote.init() + } + +} + +// Deprecated: This method is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +func (s *State) EnsureHasLineage() { + s.Lock() + defer s.Unlock() + + s.ensureHasLineage() +} + +func (s *State) ensureHasLineage() { + if s.Lineage == "" { + lineage, err := uuid.GenerateUUID() + if err != nil { + panic(fmt.Errorf("Failed to generate lineage: %v", err)) + } + s.Lineage = lineage + if os.Getenv("TF_ACC") == "" || os.Getenv("TF_ACC_STATE_LINEAGE") == "1" { + log.Printf("[DEBUG] New state was assigned lineage %q\n", s.Lineage) + } + } else { + if os.Getenv("TF_ACC") == "" || os.Getenv("TF_ACC_STATE_LINEAGE") == "1" { + log.Printf("[TRACE] Preserving existing state lineage %q\n", s.Lineage) + } + } +} + +// AddModuleState insert this module state and override any existing ModuleState +// +// Deprecated: This method is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +func (s *State) AddModuleState(mod *ModuleState) { + mod.init() + s.Lock() + defer s.Unlock() + + s.addModuleState(mod) +} + +func (s *State) addModuleState(mod *ModuleState) { + for i, m := range s.Modules { + if reflect.DeepEqual(m.Path, mod.Path) { + s.Modules[i] = mod + return + } + } + + s.Modules = append(s.Modules, mod) + s.sort() +} + +// prune is used to remove any resources that are no longer required +func (s *State) prune() { + if s == nil { + return + } + + // Filter out empty modules. + // A module is always assumed to have a path, and it's length isn't always + // bounds checked later on. Modules may be "emptied" during destroy, but we + // never want to store those in the state. + for i := 0; i < len(s.Modules); i++ { + if s.Modules[i] == nil || len(s.Modules[i].Path) == 0 { + s.Modules = append(s.Modules[:i], s.Modules[i+1:]...) + i-- + } + } + + for _, mod := range s.Modules { + mod.prune() + } + if s.Remote != nil && s.Remote.Empty() { + s.Remote = nil + } +} + +// sort sorts the modules +func (s *State) sort() { + sort.Sort(moduleStateSort(s.Modules)) + + // Allow modules to be sorted + for _, m := range s.Modules { + if m != nil { + m.sort() + } + } +} + +func (s *State) String() string { + if s == nil { + return "" + } + s.Lock() + defer s.Unlock() + + var buf bytes.Buffer + for _, m := range s.Modules { + mStr := m.String() + + // If we're the root module, we just write the output directly. + if reflect.DeepEqual(m.Path, rootModulePath) { + buf.WriteString(mStr + "\n") + continue + } + + buf.WriteString(fmt.Sprintf("module.%s:\n", strings.Join(m.Path[1:], "."))) + + s := bufio.NewScanner(strings.NewReader(mStr)) + for s.Scan() { + text := s.Text() + if text != "" { + text = " " + text + } + + buf.WriteString(fmt.Sprintf("%s\n", text)) + } + } + + return strings.TrimSpace(buf.String()) +} + +// BackendState stores the configuration to connect to a remote backend. +// +// Deprecated: This type is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +type BackendState struct { + Type string `json:"type"` // Backend type + ConfigRaw json.RawMessage `json:"config"` // Backend raw config + Hash uint64 `json:"hash"` // Hash of portion of configuration from config files +} + +// RemoteState is used to track the information about a remote +// state store that we push/pull state to. +// +// Deprecated: This type is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +type RemoteState struct { + // Type controls the client we use for the remote state + Type string `json:"type"` + + // Config is used to store arbitrary configuration that + // is type specific + Config map[string]string `json:"config"` + + mu sync.Mutex +} + +// Deprecated: This method is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +func (s *RemoteState) Lock() { s.mu.Lock() } + +// Deprecated: This method is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +func (s *RemoteState) Unlock() { s.mu.Unlock() } + +func (r *RemoteState) init() { + r.Lock() + defer r.Unlock() + + if r.Config == nil { + r.Config = make(map[string]string) + } +} + +// Deprecated: This method is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +func (r *RemoteState) Empty() bool { + if r == nil { + return true + } + r.Lock() + defer r.Unlock() + + return r.Type == "" +} + +// OutputState is used to track the state relevant to a single output. +type OutputState struct { + // Sensitive describes whether the output is considered sensitive, + // which may lead to masking the value on screen in some cases. + Sensitive bool `json:"sensitive"` + // Type describes the structure of Value. Valid values are "string", + // "map" and "list" + Type string `json:"type"` + // Value contains the value of the output, in the structure described + // by the Type field. + Value interface{} `json:"value"` + + mu sync.Mutex +} + +// Deprecated: This method is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +func (s *OutputState) Lock() { s.mu.Lock() } + +// Deprecated: This method is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +func (s *OutputState) Unlock() { s.mu.Unlock() } + +func (s *OutputState) String() string { + return fmt.Sprintf("%#v", s.Value) +} + +// Equal compares two OutputState structures for equality. nil values are +// considered equal. +// +// Deprecated: This method is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +func (s *OutputState) Equal(other *OutputState) bool { + if s == nil && other == nil { + return true + } + + if s == nil || other == nil { + return false + } + s.Lock() + defer s.Unlock() + + if s.Type != other.Type { + return false + } + + if s.Sensitive != other.Sensitive { + return false + } + + if !reflect.DeepEqual(s.Value, other.Value) { + return false + } + + return true +} + +// ModuleState is used to track all the state relevant to a single +// module. Previous to Terraform 0.3, all state belonged to the "root" +// module. +type ModuleState struct { + // Path is the import path from the root module. Modules imports are + // always disjoint, so the path represents amodule tree + Path []string `json:"path"` + + // Locals are kept only transiently in-memory, because we can always + // re-compute them. + Locals map[string]interface{} `json:"-"` + + // Outputs declared by the module and maintained for each module + // even though only the root module technically needs to be kept. + // This allows operators to inspect values at the boundaries. + Outputs map[string]*OutputState `json:"outputs"` + + // Resources is a mapping of the logically named resource to + // the state of the resource. Each resource may actually have + // N instances underneath, although a user only needs to think + // about the 1:1 case. + Resources map[string]*ResourceState `json:"resources"` + + // Dependencies are a list of things that this module relies on + // existing to remain intact. For example: an module may depend + // on a VPC ID given by an aws_vpc resource. + // + // Terraform uses this information to build valid destruction + // orders and to warn the user if they're destroying a module that + // another resource depends on. + // + // Things can be put into this list that may not be managed by + // Terraform. If Terraform doesn't find a matching ID in the + // overall state, then it assumes it isn't managed and doesn't + // worry about it. + Dependencies []string `json:"depends_on"` + + mu sync.Mutex +} + +// Deprecated: This method is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +func (s *ModuleState) Lock() { s.mu.Lock() } + +// Deprecated: This method is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +func (s *ModuleState) Unlock() { s.mu.Unlock() } + +// Equal tests whether one module state is equal to another. +func (m *ModuleState) Equal(other *ModuleState) bool { + m.Lock() + defer m.Unlock() + + // Paths must be equal + if !reflect.DeepEqual(m.Path, other.Path) { + return false + } + + // Outputs must be equal + if len(m.Outputs) != len(other.Outputs) { + return false + } + for k, v := range m.Outputs { + if !other.Outputs[k].Equal(v) { + return false + } + } + + // Dependencies must be equal. This sorts these in place but + // this shouldn't cause any problems. + sort.Strings(m.Dependencies) + sort.Strings(other.Dependencies) + if len(m.Dependencies) != len(other.Dependencies) { + return false + } + for i, d := range m.Dependencies { + if other.Dependencies[i] != d { + return false + } + } + + // Resources must be equal + if len(m.Resources) != len(other.Resources) { + return false + } + for k, r := range m.Resources { + otherR, ok := other.Resources[k] + if !ok { + return false + } + + if !r.Equal(otherR) { + return false + } + } + + return true +} + +func (m *ModuleState) init() { + m.Lock() + defer m.Unlock() + + if m.Path == nil { + m.Path = []string{} + } + if m.Outputs == nil { + m.Outputs = make(map[string]*OutputState) + } + if m.Resources == nil { + m.Resources = make(map[string]*ResourceState) + } + + if m.Dependencies == nil { + m.Dependencies = make([]string, 0) + } + + for _, rs := range m.Resources { + rs.init() + } +} + +// prune is used to remove any resources that are no longer required +func (m *ModuleState) prune() { + m.Lock() + defer m.Unlock() + + for k, v := range m.Resources { + if v == nil || (v.Primary == nil || v.Primary.ID == "") && len(v.Deposed) == 0 { + delete(m.Resources, k) + continue + } + + v.prune() + } + + for k, v := range m.Outputs { + if v.Value == hcl2shim.UnknownVariableValue { + delete(m.Outputs, k) + } + } + + m.Dependencies = uniqueStrings(m.Dependencies) +} + +func (m *ModuleState) sort() { + for _, v := range m.Resources { + v.sort() + } +} + +func (m *ModuleState) String() string { + m.Lock() + defer m.Unlock() + + var buf bytes.Buffer + + if len(m.Resources) == 0 { + buf.WriteString("") + } + + names := make([]string, 0, len(m.Resources)) + for name := range m.Resources { + names = append(names, name) + } + + sort.Sort(resourceNameSort(names)) + + for _, k := range names { + rs := m.Resources[k] + var id string + if rs.Primary != nil { + id = rs.Primary.ID + } + if id == "" { + id = "" + } + + taintStr := "" + if rs.Primary.Tainted { + taintStr = " (tainted)" + } + + deposedStr := "" + if len(rs.Deposed) > 0 { + deposedStr = fmt.Sprintf(" (%d deposed)", len(rs.Deposed)) + } + + buf.WriteString(fmt.Sprintf("%s:%s%s\n", k, taintStr, deposedStr)) + buf.WriteString(fmt.Sprintf(" ID = %s\n", id)) + if rs.Provider != "" { + buf.WriteString(fmt.Sprintf(" provider = %s\n", rs.Provider)) + } + + var attributes map[string]string + if rs.Primary != nil { + attributes = rs.Primary.Attributes + } + attrKeys := make([]string, 0, len(attributes)) + for ak := range attributes { + if ak == "id" { + continue + } + + attrKeys = append(attrKeys, ak) + } + + sort.Strings(attrKeys) + + for _, ak := range attrKeys { + av := attributes[ak] + buf.WriteString(fmt.Sprintf(" %s = %s\n", ak, av)) + } + + for idx, t := range rs.Deposed { + taintStr := "" + if t.Tainted { + taintStr = " (tainted)" + } + buf.WriteString(fmt.Sprintf(" Deposed ID %d = %s%s\n", idx+1, t.ID, taintStr)) + } + + if len(rs.Dependencies) > 0 { + buf.WriteString("\n Dependencies:\n") + for _, dep := range rs.Dependencies { + buf.WriteString(fmt.Sprintf(" %s\n", dep)) + } + } + } + + if len(m.Outputs) > 0 { + buf.WriteString("\nOutputs:\n\n") + + ks := make([]string, 0, len(m.Outputs)) + for k := range m.Outputs { + ks = append(ks, k) + } + + sort.Strings(ks) + + for _, k := range ks { + v := m.Outputs[k] + switch vTyped := v.Value.(type) { + case string: + buf.WriteString(fmt.Sprintf("%s = %s\n", k, vTyped)) + case []interface{}: + buf.WriteString(fmt.Sprintf("%s = %s\n", k, vTyped)) + case map[string]interface{}: + var mapKeys []string + for key := range vTyped { + mapKeys = append(mapKeys, key) + } + sort.Strings(mapKeys) + + var mapBuf bytes.Buffer + mapBuf.WriteString("{") + for _, key := range mapKeys { + mapBuf.WriteString(fmt.Sprintf("%s:%s ", key, vTyped[key])) + } + mapBuf.WriteString("}") + + buf.WriteString(fmt.Sprintf("%s = %s\n", k, mapBuf.String())) + } + } + } + + return buf.String() +} + +// ResourceStateKey is a structured representation of the key used for the +// ModuleState.Resources mapping +type ResourceStateKey struct { + Name string + Type string + Mode ResourceMode + Index int +} + +// Equal determines whether two ResourceStateKeys are the same +func (rsk *ResourceStateKey) Equal(other *ResourceStateKey) bool { + if rsk == nil || other == nil { + return false + } + if rsk.Mode != other.Mode { + return false + } + if rsk.Type != other.Type { + return false + } + if rsk.Name != other.Name { + return false + } + if rsk.Index != other.Index { + return false + } + return true +} + +func (rsk *ResourceStateKey) String() string { + if rsk == nil { + return "" + } + var prefix string + switch rsk.Mode { + case ManagedResourceMode: + prefix = "" + case DataResourceMode: + prefix = "data." + default: + panic(fmt.Errorf("unknown resource mode %s", rsk.Mode)) + } + if rsk.Index == -1 { + return fmt.Sprintf("%s%s.%s", prefix, rsk.Type, rsk.Name) + } + return fmt.Sprintf("%s%s.%s.%d", prefix, rsk.Type, rsk.Name, rsk.Index) +} + +// ParseResourceStateKey accepts a key in the format used by +// ModuleState.Resources and returns a resource name and resource index. In the +// state, a resource has the format "type.name.index" or "type.name". In the +// latter case, the index is returned as -1. +func parseResourceStateKey(k string) (*ResourceStateKey, error) { + parts := strings.Split(k, ".") + mode := ManagedResourceMode + if len(parts) > 0 && parts[0] == "data" { + mode = DataResourceMode + // Don't need the constant "data" prefix for parsing + // now that we've figured out the mode. + parts = parts[1:] + } + if len(parts) < 2 || len(parts) > 3 { + return nil, fmt.Errorf("Malformed resource state key: %s", k) + } + rsk := &ResourceStateKey{ + Mode: mode, + Type: parts[0], + Name: parts[1], + Index: -1, + } + if len(parts) == 3 { + index, err := strconv.Atoi(parts[2]) + if err != nil { + return nil, fmt.Errorf("Malformed resource state key index: %s", k) + } + rsk.Index = index + } + return rsk, nil +} + +// ResourceState holds the state of a resource that is used so that +// a provider can find and manage an existing resource as well as for +// storing attributes that are used to populate variables of child +// resources. +// +// Attributes has attributes about the created resource that are +// queryable in interpolation: "${type.id.attr}" +// +// Extra is just extra data that a provider can return that we store +// for later, but is not exposed in any way to the user. +type ResourceState struct { + // This is filled in and managed by Terraform, and is the resource + // type itself such as "mycloud_instance". If a resource provider sets + // this value, it won't be persisted. + Type string `json:"type"` + + // Dependencies are a list of things that this resource relies on + // existing to remain intact. For example: an AWS instance might + // depend on a subnet (which itself might depend on a VPC, and so + // on). + // + // Terraform uses this information to build valid destruction + // orders and to warn the user if they're destroying a resource that + // another resource depends on. + // + // Things can be put into this list that may not be managed by + // Terraform. If Terraform doesn't find a matching ID in the + // overall state, then it assumes it isn't managed and doesn't + // worry about it. + Dependencies []string `json:"depends_on"` + + // Primary is the current active instance for this resource. + // It can be replaced but only after a successful creation. + // This is the instances on which providers will act. + Primary *InstanceState `json:"primary"` + + // Deposed is used in the mechanics of CreateBeforeDestroy: the existing + // Primary is Deposed to get it out of the way for the replacement Primary to + // be created by Apply. If the replacement Primary creates successfully, the + // Deposed instance is cleaned up. + // + // If there were problems creating the replacement Primary, the Deposed + // instance and the (now tainted) replacement Primary will be swapped so the + // tainted replacement will be cleaned up instead. + // + // An instance will remain in the Deposed list until it is successfully + // destroyed and purged. + Deposed []*InstanceState `json:"deposed"` + + // Provider is used when a resource is connected to a provider with an alias. + // If this string is empty, the resource is connected to the default provider, + // e.g. "aws_instance" goes with the "aws" provider. + // If the resource block contained a "provider" key, that value will be set here. + Provider string `json:"provider"` + + mu sync.Mutex +} + +// Deprecated: This method is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +func (s *ResourceState) Lock() { s.mu.Lock() } + +// Deprecated: This method is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +func (s *ResourceState) Unlock() { s.mu.Unlock() } + +// Equal tests whether two ResourceStates are equal. +func (s *ResourceState) Equal(other *ResourceState) bool { + s.Lock() + defer s.Unlock() + + if s.Type != other.Type { + return false + } + + if s.Provider != other.Provider { + return false + } + + // Dependencies must be equal + sort.Strings(s.Dependencies) + sort.Strings(other.Dependencies) + if len(s.Dependencies) != len(other.Dependencies) { + return false + } + for i, d := range s.Dependencies { + if other.Dependencies[i] != d { + return false + } + } + + // States must be equal + return s.Primary.Equal(other.Primary) +} + +func (s *ResourceState) init() { + s.Lock() + defer s.Unlock() + + if s.Primary == nil { + s.Primary = &InstanceState{} + } + s.Primary.init() + + if s.Dependencies == nil { + s.Dependencies = []string{} + } + + if s.Deposed == nil { + s.Deposed = make([]*InstanceState, 0) + } +} + +// prune is used to remove any instances that are no longer required +func (s *ResourceState) prune() { + s.Lock() + defer s.Unlock() + + n := len(s.Deposed) + for i := 0; i < n; i++ { + inst := s.Deposed[i] + if inst == nil || inst.ID == "" { + copy(s.Deposed[i:], s.Deposed[i+1:]) + s.Deposed[n-1] = nil + n-- + i-- + } + } + s.Deposed = s.Deposed[:n] + + s.Dependencies = uniqueStrings(s.Dependencies) +} + +func (s *ResourceState) sort() { + s.Lock() + defer s.Unlock() + + sort.Strings(s.Dependencies) +} + +func (s *ResourceState) String() string { + s.Lock() + defer s.Unlock() + + var buf bytes.Buffer + buf.WriteString(fmt.Sprintf("Type = %s", s.Type)) + return buf.String() +} + +// InstanceState is used to track the unique state information belonging +// to a given instance. +type InstanceState struct { + // A unique ID for this resource. This is opaque to Terraform + // and is only meant as a lookup mechanism for the providers. + ID string `json:"id"` + + // Attributes are basic information about the resource. Any keys here + // are accessible in variable format within Terraform configurations: + // ${resourcetype.name.attribute}. + Attributes map[string]string `json:"attributes"` + + // Ephemeral is used to store any state associated with this instance + // that is necessary for the Terraform run to complete, but is not + // persisted to a state file. + Ephemeral EphemeralState `json:"-"` + + // Meta is a simple K/V map that is persisted to the State but otherwise + // ignored by Terraform core. It's meant to be used for accounting by + // external client code. The value here must only contain Go primitives + // and collections. + Meta map[string]interface{} `json:"meta"` + + ProviderMeta cty.Value + + RawConfig cty.Value + RawState cty.Value + RawPlan cty.Value + + // Tainted is used to mark a resource for recreation. + Tainted bool `json:"tainted"` + + mu sync.Mutex +} + +// Deprecated: This method is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +func (s *InstanceState) Lock() { s.mu.Lock() } + +// Deprecated: This method is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +func (s *InstanceState) Unlock() { s.mu.Unlock() } + +func (s *InstanceState) init() { + s.Lock() + defer s.Unlock() + + if s.Attributes == nil { + s.Attributes = make(map[string]string) + } + if s.Meta == nil { + s.Meta = make(map[string]interface{}) + } + s.Ephemeral.init() +} + +// NewInstanceStateShimmedFromValue is a shim method to lower a new-style +// object value representing the attributes of an instance object into the +// legacy InstanceState representation. +// +// This is for shimming to old components only and should not be used in new code. +// +// Deprecated: This function is unintentionally exported by this Go module and +// not supported for external consumption. It will be removed in the next major +// version. +func NewInstanceStateShimmedFromValue(state cty.Value, schemaVersion int) *InstanceState { + attrs := hcl2shim.FlatmapValueFromHCL2(state) + return &InstanceState{ + ID: attrs["id"], + Attributes: attrs, + Meta: map[string]interface{}{ + "schema_version": schemaVersion, + }, + } +} + +// AttrsAsObjectValue shims from the legacy InstanceState representation to +// a new-style cty object value representation of the state attributes, using +// the given type for guidance. +// +// The given type must be the implied type of the schema of the resource type +// of the object whose state is being converted, or the result is undefined. +// +// This is for shimming from old components only and should not be used in +// new code. +// +// Deprecated: This method is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +func (s *InstanceState) AttrsAsObjectValue(ty cty.Type) (cty.Value, error) { + if s == nil { + // if the state is nil, we need to construct a complete cty.Value with + // null attributes, rather than a single cty.NullVal(ty) + s = &InstanceState{} + } + + if s.Attributes == nil { + s.Attributes = map[string]string{} + } + + // make sure ID is included in the attributes. The InstanceState.ID value + // takes precedence. + if s.ID != "" { + s.Attributes["id"] = s.ID + } + + return hcl2shim.HCL2ValueFromFlatmap(s.Attributes, ty) +} + +// Copy all the Fields from another InstanceState +// +// Deprecated: This method is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +func (s *InstanceState) Set(from *InstanceState) { + s.Lock() + defer s.Unlock() + + from.Lock() + defer from.Unlock() + + s.ID = from.ID + s.Attributes = from.Attributes + s.Ephemeral = from.Ephemeral + s.Meta = from.Meta + s.Tainted = from.Tainted +} + +// Deprecated: This method is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +func (s *InstanceState) DeepCopy() *InstanceState { + if s == nil { + return nil + } + + copied := &InstanceState{ + Ephemeral: EphemeralState{ + ConnInfo: s.Ephemeral.ConnInfo, + Type: s.Ephemeral.Type, + }, + ID: s.ID, + ProviderMeta: s.ProviderMeta, + RawConfig: s.RawConfig, + RawPlan: s.RawPlan, + RawState: s.RawState, + Tainted: s.Tainted, + } + + if s.Attributes != nil { + copied.Attributes = make(map[string]string, len(s.Attributes)) + + for k, v := range s.Attributes { + copied.Attributes[k] = v + } + } + + // Best effort single level copy is fine; this is not used by this Go module + // and its already deprecated. + if s.Meta != nil { + copied.Meta = make(map[string]any, len(s.Meta)) + + for k, v := range s.Meta { + copied.Meta[k] = v + } + } + + return copied +} + +// Deprecated: This method is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +func (s *InstanceState) Empty() bool { + if s == nil { + return true + } + s.Lock() + defer s.Unlock() + + return s.ID == "" +} + +func (s *InstanceState) Equal(other *InstanceState) bool { + // Short circuit some nil checks + if s == nil || other == nil { + return s == other + } + s.Lock() + defer s.Unlock() + + // IDs must be equal + if s.ID != other.ID { + return false + } + + // Attributes must be equal + if len(s.Attributes) != len(other.Attributes) { + return false + } + for k, v := range s.Attributes { + otherV, ok := other.Attributes[k] + if !ok { + return false + } + + if v != otherV { + return false + } + } + + // Meta must be equal + if len(s.Meta) != len(other.Meta) { + return false + } + if s.Meta != nil && other.Meta != nil { + // We only do the deep check if both are non-nil. If one is nil + // we treat it as equal since their lengths are both zero (check + // above). + // + // Since this can contain numeric values that may change types during + // serialization, let's compare the serialized values. + sMeta, err := json.Marshal(s.Meta) + if err != nil { + // marshaling primitives shouldn't ever error out + panic(err) + } + otherMeta, err := json.Marshal(other.Meta) + if err != nil { + panic(err) + } + + if !bytes.Equal(sMeta, otherMeta) { + return false + } + } + + if s.Tainted != other.Tainted { + return false + } + + return true +} + +// MergeDiff takes a ResourceDiff and merges the attributes into +// this resource state in order to generate a new state. This new +// state can be used to provide updated attribute lookups for +// variable interpolation. +// +// If the diff attribute requires computing the value, and hence +// won't be available until apply, the value is replaced with the +// computeID. +// +// Deprecated: This method is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +func (s *InstanceState) MergeDiff(d *InstanceDiff) *InstanceState { + result := s.DeepCopy() + if result == nil { + result = new(InstanceState) + } + result.init() + + if s != nil { + s.Lock() + defer s.Unlock() + for k, v := range s.Attributes { + result.Attributes[k] = v + } + } + if d != nil { + for k, diff := range d.CopyAttributes() { + if diff.NewRemoved { + delete(result.Attributes, k) + continue + } + if diff.NewComputed { + result.Attributes[k] = hcl2shim.UnknownVariableValue + continue + } + + result.Attributes[k] = diff.New + } + } + + return result +} + +func (s *InstanceState) String() string { + notCreated := "" + + if s == nil { + return notCreated + } + + s.Lock() + defer s.Unlock() + + var buf bytes.Buffer + + if s.ID == "" { + return notCreated + } + + buf.WriteString(fmt.Sprintf("ID = %s\n", s.ID)) + + attributes := s.Attributes + attrKeys := make([]string, 0, len(attributes)) + for ak := range attributes { + if ak == "id" { + continue + } + + attrKeys = append(attrKeys, ak) + } + sort.Strings(attrKeys) + + for _, ak := range attrKeys { + av := attributes[ak] + buf.WriteString(fmt.Sprintf("%s = %s\n", ak, av)) + } + + buf.WriteString(fmt.Sprintf("Tainted = %t\n", s.Tainted)) + + return buf.String() +} + +// EphemeralState is used for transient state that is only kept in-memory +// +// Deprecated: This type is unintentionally exported by this Go module and not +// supported for external consumption. It will be removed in the next major +// version. +type EphemeralState struct { + // ConnInfo is used for the providers to export information which is + // used to connect to the resource for provisioning. For example, + // this could contain SSH or WinRM credentials. + ConnInfo map[string]string `json:"-"` + + // Type is used to specify the resource type for this instance. This is only + // required for import operations (as documented). If the documentation + // doesn't state that you need to set this, then don't worry about + // setting it. + Type string `json:"-"` +} + +func (e *EphemeralState) init() { + if e.ConnInfo == nil { + e.ConnInfo = make(map[string]string) + } +} + +// resourceNameSort implements the sort.Interface to sort name parts lexically for +// strings and numerically for integer indexes. +type resourceNameSort []string + +func (r resourceNameSort) Len() int { return len(r) } +func (r resourceNameSort) Swap(i, j int) { r[i], r[j] = r[j], r[i] } + +func (r resourceNameSort) Less(i, j int) bool { + iParts := strings.Split(r[i], ".") + jParts := strings.Split(r[j], ".") + + end := len(iParts) + if len(jParts) < end { + end = len(jParts) + } + + for idx := 0; idx < end; idx++ { + if iParts[idx] == jParts[idx] { + continue + } + + // sort on the first non-matching part + iInt, iIntErr := strconv.Atoi(iParts[idx]) + jInt, jIntErr := strconv.Atoi(jParts[idx]) + + switch { + case iIntErr == nil && jIntErr == nil: + // sort numerically if both parts are integers + return iInt < jInt + case iIntErr == nil: + // numbers sort before strings + return true + case jIntErr == nil: + return false + default: + return iParts[idx] < jParts[idx] + } + } + + return r[i] < r[j] +} + +// moduleStateSort implements sort.Interface to sort module states +type moduleStateSort []*ModuleState + +func (s moduleStateSort) Len() int { + return len(s) +} + +func (s moduleStateSort) Less(i, j int) bool { + a := s[i] + b := s[j] + + // If either is nil, then the nil one is "less" than + if a == nil || b == nil { + return a == nil + } + + // If the lengths are different, then the shorter one always wins + if len(a.Path) != len(b.Path) { + return len(a.Path) < len(b.Path) + } + + // Otherwise, compare lexically + return strings.Join(a.Path, ".") < strings.Join(b.Path, ".") +} + +func (s moduleStateSort) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} + +const stateValidateErrMultiModule = ` +Multiple modules with the same path: %s + +This means that there are multiple entries in the "modules" field +in your state file that point to the same module. This will cause Terraform +to behave in unexpected and error prone ways and is invalid. Please back up +and modify your state file manually to resolve this. +` diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/terraform/state_filter.go b/vendor/github.com/hashicorp/terraform-plugin-testing/terraform/state_filter.go new file mode 100644 index 0000000000..caf2c79674 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/terraform/state_filter.go @@ -0,0 +1,273 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package terraform + +import ( + "fmt" + "sort" +) + +// stateFilter is responsible for filtering and searching a state. +// +// This is a separate struct from State rather than a method on State +// because StateFilter might create sidecar data structures to optimize +// filtering on the state. +// +// If you change the State, the filter created is invalid and either +// Reset should be called or a new one should be allocated. StateFilter +// will not watch State for changes and do this for you. If you filter after +// changing the State without calling Reset, the behavior is not defined. +type stateFilter struct { + State *State +} + +// Filter takes the addresses specified by fs and finds all the matches. +// The values of fs are resource addressing syntax that can be parsed by +// parseResourceAddress. +func (f *stateFilter) filter(fs ...string) ([]*stateFilterResult, error) { + // Parse all the addresses + var as []*resourceAddress + + if len(fs) == 0 { + // If we weren't given any filters, then we list all + as = []*resourceAddress{{Index: -1}} + } else { + as = make([]*resourceAddress, len(fs)) + } + + for i, v := range fs { + a, err := parseResourceAddress(v) + if err != nil { + return nil, fmt.Errorf("Error parsing address '%s': %s", v, err) + } + + as[i] = a + } + + // Filter each of the address. We keep track of this in a map to + // strip duplicates. + resultSet := make(map[string]*stateFilterResult) + for _, a := range as { + for _, r := range f.filterSingle(a) { + resultSet[r.String()] = r + } + } + + // Make the result list + results := make([]*stateFilterResult, 0, len(resultSet)) + for _, v := range resultSet { + results = append(results, v) + } + + // Sort them and return + sort.Sort(stateFilterResultSlice(results)) + return results, nil +} + +func (f *stateFilter) filterSingle(a *resourceAddress) []*stateFilterResult { + // The slice to keep track of results + var results []*stateFilterResult + + // Go through modules first. + modules := make([]*ModuleState, 0, len(f.State.Modules)) + for _, m := range f.State.Modules { + if f.relevant(a, m) { + modules = append(modules, m) + + // Only add the module to the results if we haven't specified a type. + // We also ignore the root module. + if a.Type == "" && len(m.Path) > 1 { + results = append(results, &stateFilterResult{ + Path: m.Path[1:], + Address: (&resourceAddress{Path: m.Path[1:]}).String(), + Value: m, + }) + } + } + } + + // With the modules set, go through all the resources within + // the modules to find relevant resources. + for _, m := range modules { + for n, r := range m.Resources { + // The name in the state contains valuable information. Parse. + key, err := parseResourceStateKey(n) + if err != nil { + // If we get an error parsing, then just ignore it + // out of the state. + continue + } + + // Older states and test fixtures often don't contain the + // type directly on the ResourceState. We add this so StateFilter + // is a bit more robust. + if r.Type == "" { + r.Type = key.Type + } + + if f.relevant(a, r) { + if a.Name != "" && a.Name != key.Name { + // Name doesn't match + continue + } + + if a.Index >= 0 && key.Index != a.Index { + // Index doesn't match + continue + } + + if a.Name != "" && a.Name != key.Name { + continue + } + + // Build the address for this resource + addr := &resourceAddress{ + Path: m.Path[1:], + Name: key.Name, + Type: key.Type, + Index: key.Index, + } + + // Add the resource level result + resourceResult := &stateFilterResult{ + Path: addr.Path, + Address: addr.String(), + Value: r, + } + if !a.InstanceTypeSet { + results = append(results, resourceResult) + } + + // Add the instances + if r.Primary != nil { + addr.InstanceType = typePrimary + addr.InstanceTypeSet = false + results = append(results, &stateFilterResult{ + Path: addr.Path, + Address: addr.String(), + Parent: resourceResult, + Value: r.Primary, + }) + } + + for _, instance := range r.Deposed { + if f.relevant(a, instance) { + addr.InstanceType = typeDeposed + addr.InstanceTypeSet = true + results = append(results, &stateFilterResult{ + Path: addr.Path, + Address: addr.String(), + Parent: resourceResult, + Value: instance, + }) + } + } + } + } + } + + return results +} + +// relevant checks for relevance of this address against the given value. +func (f *stateFilter) relevant(addr *resourceAddress, raw interface{}) bool { + switch v := raw.(type) { + case *ModuleState: + path := v.Path[1:] + + if len(addr.Path) > len(path) { + // Longer path in address means there is no way we match. + return false + } + + // Check for a prefix match + for i, p := range addr.Path { + if path[i] != p { + // Any mismatches don't match. + return false + } + } + + return true + case *ResourceState: + if addr.Type == "" { + // If we have no resource type, then we're interested in all! + return true + } + + // If the type doesn't match we fail immediately + if v.Type != addr.Type { + return false + } + + return true + default: + // If we don't know about it, let's just say no + return false + } +} + +// stateFilterResult is a single result from a filter operation. Filter +// can match multiple things within a state (module, resource, instance, etc.) +// and this unifies that. +type stateFilterResult struct { + // Module path of the result + Path []string + + // Address is the address that can be used to reference this exact result. + Address string + + // Parent, if non-nil, is a parent of this result. For instances, the + // parent would be a resource. For resources, the parent would be + // a module. For modules, this is currently nil. + Parent *stateFilterResult + + // Value is the actual value. This must be type switched on. It can be + // any data structures that `State` can hold: `ModuleState`, + // `ResourceState`, `InstanceState`. + Value interface{} +} + +func (r *stateFilterResult) String() string { + return fmt.Sprintf("%T: %s", r.Value, r.Address) +} + +func (r *stateFilterResult) sortedType() int { + switch r.Value.(type) { + case *ModuleState: + return 0 + case *ResourceState: + return 1 + case *InstanceState: + return 2 + default: + return 50 + } +} + +// stateFilterResultSlice is a slice of results that implements +// sort.Interface. The sorting goal is what is most appealing to +// human output. +type stateFilterResultSlice []*stateFilterResult + +func (s stateFilterResultSlice) Len() int { return len(s) } +func (s stateFilterResultSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } +func (s stateFilterResultSlice) Less(i, j int) bool { + a, b := s[i], s[j] + + // if these address contain an index, we want to sort by index rather than name + addrA, errA := parseResourceAddress(a.Address) + addrB, errB := parseResourceAddress(b.Address) + if errA == nil && errB == nil && addrA.Name == addrB.Name && addrA.Index != addrB.Index { + return addrA.Index < addrB.Index + } + + // If the addresses are different it is just lexographic sorting + if a.Address != b.Address { + return a.Address < b.Address + } + + // Addresses are the same, which means it matters on the type + return a.sortedType() < b.sortedType() +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/terraform/unknown_value_walk.go b/vendor/github.com/hashicorp/terraform-plugin-testing/terraform/unknown_value_walk.go new file mode 100644 index 0000000000..66e129549e --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/terraform/unknown_value_walk.go @@ -0,0 +1,85 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package terraform + +import ( + "reflect" + + "github.com/hashicorp/terraform-plugin-testing/internal/configs/hcl2shim" +) + +// unknownValueWalk is a reimplementation of the prior walk() logic from +// github.com/mitchellh/reflectwalk and the only walker implemented in this +// module that checked values for hcl2shim.UnknownVariableValue. +// +// Using reflection instead of known logic here is a Go anti-pattern, however +// this logic will be removed in the next major version, so the reflection +// approach is preserved to minimize reimplementation effort. +func unknownValueWalk(v reflect.Value) bool { + for { + switch v.Kind() { + case reflect.Interface: + v = v.Elem() + + continue + case reflect.Pointer: + v = reflect.Indirect(v) + + continue + } + + break + } + + switch v.Kind() { + case reflect.Bool, + reflect.Complex128, + reflect.Complex64, + reflect.Float32, + reflect.Float64, + reflect.Int, + reflect.Int16, + reflect.Int32, + reflect.Int64, + reflect.Int8, + reflect.Uint, + reflect.Uint16, + reflect.Uint32, + reflect.Uint64, + reflect.Uint8, + reflect.Uintptr, + reflect.String: + value := v.Interface() + + return value == hcl2shim.UnknownVariableValue + case reflect.Map: + for _, k := range v.MapKeys() { + value := v.MapIndex(k) + + if foundUnknown := unknownValueWalk(value); foundUnknown { + return true + } + } + case reflect.Array, reflect.Slice: + for index := 0; index < v.Len(); index++ { + value := v.Index(index) + + if foundUnknown := unknownValueWalk(value); foundUnknown { + return true + } + } + case reflect.Struct: + for index := 0; index < v.Type().NumField(); index++ { + value := v.FieldByIndex([]int{index}) + + if foundUnknown := unknownValueWalk(value); foundUnknown { + return true + } + } + default: + panic("unsupported reflect type: " + v.Kind().String()) + } + + return false +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/terraform/util.go b/vendor/github.com/hashicorp/terraform-plugin-testing/terraform/util.go new file mode 100644 index 0000000000..6353ad27d9 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/terraform/util.go @@ -0,0 +1,25 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package terraform + +import ( + "sort" +) + +// deduplicate a slice of strings +func uniqueStrings(s []string) []string { + if len(s) < 2 { + return s + } + + sort.Strings(s) + result := make([]string, 1, len(s)) + result[0] = s[0] + for i := 1; i < len(s); i++ { + if s[i] != result[len(result)-1] { + result = append(result, s[i]) + } + } + return result +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/tfjsonpath/doc.go b/vendor/github.com/hashicorp/terraform-plugin-testing/tfjsonpath/doc.go new file mode 100644 index 0000000000..4b1a4923b9 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/tfjsonpath/doc.go @@ -0,0 +1,6 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// Package tfjsonpath implements terraform-json path functionality, which defines +// traversals into Terraform JSON data, for testing purposes. +package tfjsonpath diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/tfjsonpath/path.go b/vendor/github.com/hashicorp/terraform-plugin-testing/tfjsonpath/path.go new file mode 100644 index 0000000000..c29ae2608e --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/tfjsonpath/path.go @@ -0,0 +1,135 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package tfjsonpath + +import ( + "fmt" + "strings" +) + +// Path represents exact traversal steps specifying a value inside +// Terraform JSON data. These steps always start from a MapStep with a key +// specifying the name of a top-level JSON object or array. +// +// The [terraform-json] library serves as the de facto documentation +// for JSON format of Terraform data. +// +// Use the New() function to create a Path with an initial AtMapKey() step. +// Path functionality follows a builder pattern, which allows for chaining method +// calls to construct a full path. The available traversal steps after Path +// creation are: +// +// - AtSliceIndex(): Step into a slice at a specific 0-based index +// - AtMapKey(): Step into a map at a specific key +// +// For example, to represent the first element of a JSON array +// underneath a "some_array" property of this JSON value: +// +// { +// "some_array": [true] +// } +// +// The path code would be represented by: +// +// tfjsonpath.New("some_array").AtSliceIndex(0) +// +// [terraform-json]: (https://pkg.go.dev/github.com/hashicorp/terraform-json) +type Path struct { + steps []step +} + +// New creates a new path with an initial MapStep or SliceStep. +func New[T int | string](firstStep T) Path { + switch t := any(firstStep).(type) { + case int: + return Path{ + steps: []step{ + SliceStep(t), + }, + } + case string: + return Path{ + steps: []step{ + MapStep(t), + }, + } + } + + // Unreachable code + return Path{} +} + +// AtSliceIndex returns a copied Path with a new SliceStep at the end. +func (s Path) AtSliceIndex(index int) Path { + newSteps := append(s.steps, SliceStep(index)) + s.steps = newSteps + return s +} + +// AtMapKey returns a copied Path with a new MapStep at the end. +func (s Path) AtMapKey(key string) Path { + newSteps := append(s.steps, MapStep(key)) + s.steps = newSteps + return s +} + +// String returns a string representation of the Path. +func (s Path) String() string { + var pathStr []string + + for _, step := range s.steps { + pathStr = append(pathStr, fmt.Sprintf("%v", step)) + } + + return strings.Join(pathStr, ".") +} + +// Traverse returns the element found when traversing the given +// object using the specified Path. The object is an unmarshalled +// JSON object representing Terraform data. +// +// Traverse returns an error if the value specified by the Path +// is not found in the given object or if the given object does not +// conform to format of Terraform JSON data. +func Traverse(object any, attrPath Path) (any, error) { + result := object + + var steps []string + + for _, step := range attrPath.steps { + switch s := step.(type) { + case MapStep: + steps = append(steps, string(s)) + + mapObj, ok := result.(map[string]any) + + if !ok { + return nil, fmt.Errorf("path not found: cannot convert object at MapStep %s to map[string]any", strings.Join(steps, ".")) + } + + result, ok = mapObj[string(s)] + + if !ok { + return nil, fmt.Errorf("path not found: specified key %s not found in map at %s", string(s), strings.Join(steps, ".")) + } + + case SliceStep: + steps = append(steps, fmt.Sprint(s)) + + sliceObj, ok := result.([]any) + + if !ok { + return nil, fmt.Errorf("path not found: cannot convert object at SliceStep %s to []any", strings.Join(steps, ".")) + } + + if int(s) >= len(sliceObj) { + return nil, fmt.Errorf("path not found: SliceStep index %s is out of range with slice length %d", strings.Join(steps, "."), len(sliceObj)) + } + + result = sliceObj[s] + } + } + + return result, nil +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/tfjsonpath/step.go b/vendor/github.com/hashicorp/terraform-plugin-testing/tfjsonpath/step.go new file mode 100644 index 0000000000..5a779640d4 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/tfjsonpath/step.go @@ -0,0 +1,14 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package tfjsonpath + +// step represents a traversal type indicating the underlying Go type +// representation for a Terraform JSON value. +type step any + +// MapStep represents a traversal for map[string]any +type MapStep string + +// SliceStep represents a traversal for []any +type SliceStep int diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/tfversion/all.go b/vendor/github.com/hashicorp/terraform-plugin-testing/tfversion/all.go new file mode 100644 index 0000000000..78e1f17801 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/tfversion/all.go @@ -0,0 +1,45 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package tfversion + +import ( + "context" +) + +// All will return the first non-nil error or non-empty skip message +// if any of the given checks return a non-nil error or non-empty skip message. +// Otherwise, it will return a nil error and empty skip message (run the test) +// +// Use of All is only necessary when used in conjunction with Any as the +// TerraformVersionChecks field automatically applies a logical AND. +func All(terraformVersionChecks ...TerraformVersionCheck) TerraformVersionCheck { + return allCheck{ + terraformVersionChecks: terraformVersionChecks, + } +} + +// allCheck implements the TerraformVersionCheck interface +type allCheck struct { + terraformVersionChecks []TerraformVersionCheck +} + +// CheckTerraformVersion satisfies the TerraformVersionCheck interface. +func (a allCheck) CheckTerraformVersion(ctx context.Context, req CheckTerraformVersionRequest, resp *CheckTerraformVersionResponse) { + + for _, subCheck := range a.terraformVersionChecks { + checkResp := CheckTerraformVersionResponse{} + + subCheck.CheckTerraformVersion(ctx, CheckTerraformVersionRequest{TerraformVersion: req.TerraformVersion}, &checkResp) + + if checkResp.Error != nil { + resp.Error = checkResp.Error + return + } + + if checkResp.Skip != "" { + resp.Skip = checkResp.Skip + return + } + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/tfversion/any.go b/vendor/github.com/hashicorp/terraform-plugin-testing/tfversion/any.go new file mode 100644 index 0000000000..2fee9cb10d --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/tfversion/any.go @@ -0,0 +1,54 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package tfversion + +import ( + "context" + "errors" + "strings" +) + +// Any will return a nil error and empty skip message (run the test) +// if any of the given checks return a nil error and empty skip message. +// Otherwise, it will return all errors and fail the test if any of the given +// checks return a non-nil error, or it will return all skip messages +// and skip (pass) the test. +func Any(terraformVersionChecks ...TerraformVersionCheck) TerraformVersionCheck { + return anyCheck{ + terraformVersionChecks: terraformVersionChecks, + } +} + +// anyCheck implements the TerraformVersionCheck interface +type anyCheck struct { + terraformVersionChecks []TerraformVersionCheck +} + +// CheckTerraformVersion satisfies the TerraformVersionCheck interface. +func (a anyCheck) CheckTerraformVersion(ctx context.Context, req CheckTerraformVersionRequest, resp *CheckTerraformVersionResponse) { + var joinedErrors []error + strBuilder := strings.Builder{} + + for _, subCheck := range a.terraformVersionChecks { + checkResp := CheckTerraformVersionResponse{} + + subCheck.CheckTerraformVersion(ctx, CheckTerraformVersionRequest{TerraformVersion: req.TerraformVersion}, &checkResp) + + if checkResp.Error == nil && checkResp.Skip == "" { + resp.Error = nil + resp.Skip = "" + return + } + + joinedErrors = append(joinedErrors, checkResp.Error) + + if checkResp.Skip != "" { + strBuilder.WriteString(checkResp.Skip) + strBuilder.WriteString("\n") + } + } + + resp.Error = errors.Join(joinedErrors...) + resp.Skip = strings.TrimSpace(strBuilder.String()) +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/tfversion/doc.go b/vendor/github.com/hashicorp/terraform-plugin-testing/tfversion/doc.go new file mode 100644 index 0000000000..d73b474d25 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/tfversion/doc.go @@ -0,0 +1,5 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// Package tfversion contains the Terraform version check interface, request/response structs, and common version check implementations. +package tfversion diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/tfversion/require_above.go b/vendor/github.com/hashicorp/terraform-plugin-testing/tfversion/require_above.go new file mode 100644 index 0000000000..bea04c67a3 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/tfversion/require_above.go @@ -0,0 +1,54 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package tfversion + +import ( + "context" + "fmt" + + "github.com/hashicorp/go-version" +) + +// RequireAbove will fail the test if the Terraform CLI +// version is exclusively below the given version. For example, if given +// version.Must(version.NewVersion("1.8.0")), then 1.7.x or +// any other prior versions will fail the test. +// +// Prereleases of Terraform CLI (whether alpha, beta, or rc) are considered +// equal to a given patch version. For example, if given +// version.Must(version.NewVersion("1.8.0")), then 1.8.0-rc1 will run, not fail, +// the test. Terraform prereleases are considered as potential candidates for +// the upcoming version and therefore are treated as semantically equal for +// testing. If failing prereleases of the same patch release is desired, give a +// higher prerelease version. For example, if given +// version.Must(version.NewVersion("1.8.0-rc2")), then 1.8.0-rc1 will fail the +// test. +func RequireAbove(minimumVersion *version.Version) TerraformVersionCheck { + return requireAboveCheck{ + minimumVersion: minimumVersion, + } +} + +// requireAboveCheck implements the TerraformVersionCheck interface +type requireAboveCheck struct { + minimumVersion *version.Version +} + +// CheckTerraformVersion satisfies the TerraformVersionCheck interface. +func (r requireAboveCheck) CheckTerraformVersion(ctx context.Context, req CheckTerraformVersionRequest, resp *CheckTerraformVersionResponse) { + var terraformVersion *version.Version + + // If given a prerelease version, check the Terraform CLI version directly, + // otherwise use the core version so that prereleases are treated as equal. + if r.minimumVersion.Prerelease() != "" { + terraformVersion = req.TerraformVersion + } else { + terraformVersion = req.TerraformVersion.Core() + } + + if terraformVersion.LessThan(r.minimumVersion) { + resp.Error = fmt.Errorf("expected Terraform CLI version above %s but detected version is %s", + r.minimumVersion, req.TerraformVersion) + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/tfversion/require_below.go b/vendor/github.com/hashicorp/terraform-plugin-testing/tfversion/require_below.go new file mode 100644 index 0000000000..1b1f2cfb23 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/tfversion/require_below.go @@ -0,0 +1,54 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package tfversion + +import ( + "context" + "fmt" + + "github.com/hashicorp/go-version" +) + +// RequireBelow will fail the test if the Terraform CLI +// version is inclusively above the given version. For example, if given +// version.Must(version.NewVersion("1.8.0")), then versions 1.8.x and +// above will fail the test. +// +// Prereleases of Terraform CLI (whether alpha, beta, or rc) are considered +// equal to a given patch version. For example, if given +// version.Must(version.NewVersion("1.8.0")), then 1.8.0-rc1 will fail, not run, +// the test. Terraform prereleases are considered as potential candidates for +// the upcoming version and therefore are treated as semantically equal for +// testing purposes. If failing prereleases of the same patch release is +// desired, give a lower prerelease version. For example, if given +// version.Must(version.NewVersion("1.8.0-rc1")), then 1.8.0-rc2 will fail the +// test. +func RequireBelow(maximumVersion *version.Version) TerraformVersionCheck { + return requireBelowCheck{ + maximumVersion: maximumVersion, + } +} + +// requireBelowCheck implements the TerraformVersionCheck interface +type requireBelowCheck struct { + maximumVersion *version.Version +} + +// CheckTerraformVersion satisfies the TerraformVersionCheck interface. +func (s requireBelowCheck) CheckTerraformVersion(ctx context.Context, req CheckTerraformVersionRequest, resp *CheckTerraformVersionResponse) { + var terraformVersion *version.Version + + // If given a prerelease version, check the Terraform CLI version directly, + // otherwise use the core version so that prereleases are treated as equal. + if s.maximumVersion.Prerelease() != "" { + terraformVersion = req.TerraformVersion + } else { + terraformVersion = req.TerraformVersion.Core() + } + + if terraformVersion.GreaterThanOrEqual(s.maximumVersion) { + resp.Error = fmt.Errorf("expected Terraform CLI version below %s but detected version is %s", + s.maximumVersion, req.TerraformVersion) + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/tfversion/require_between.go b/vendor/github.com/hashicorp/terraform-plugin-testing/tfversion/require_between.go new file mode 100644 index 0000000000..ac0150773c --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/tfversion/require_between.go @@ -0,0 +1,67 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package tfversion + +import ( + "context" + "fmt" + + "github.com/hashicorp/go-version" +) + +// RequireBetween will fail the test if the Terraform CLI +// version is outside the given minimum (exclusive) and maximum (inclusive). +// For example, if given a minimum version of version.Must(version.NewVersion("1.7.0")) +// and a maximum version of version.Must(version.NewVersion("1.8.0")), then 1.6.x or +// any other prior versions and versions greater than or equal to 1.8.0 will fail the test. +// +// Prereleases of Terraform CLI (whether alpha, beta, or rc) are considered +// equal to a given patch version. For example, if given a minimum version of +// version.Must(version.NewVersion("1.8.0")), then 1.8.0-rc1 will run, not fail, +// the test. Terraform prereleases are considered as potential candidates for +// the upcoming version and therefore are treated as semantically equal for +// testing purposes. If failing prereleases of the same patch release is +// desired, give a higher prerelease version. For example, if given a minimum +// version of version.Must(version.NewVersion("1.8.0-rc2")), then 1.8.0-rc1 will +// fail the test. +func RequireBetween(minimumVersion, maximumVersion *version.Version) TerraformVersionCheck { + return requireBetweenCheck{ + minimumVersion: minimumVersion, + maximumVersion: maximumVersion, + } +} + +// requireBetweenCheck implements the TerraformVersionCheck interface +type requireBetweenCheck struct { + minimumVersion *version.Version + maximumVersion *version.Version +} + +// CheckTerraformVersion satisfies the TerraformVersionCheck interface. +func (s requireBetweenCheck) CheckTerraformVersion(ctx context.Context, req CheckTerraformVersionRequest, resp *CheckTerraformVersionResponse) { + var maxTerraformVersion, minTerraformVersion *version.Version + + // If given a prerelease maximum version, check the Terraform CLI version + // directly, otherwise use the core version so that prereleases are treated + // as equal. + if s.maximumVersion.Prerelease() != "" { + maxTerraformVersion = req.TerraformVersion + } else { + maxTerraformVersion = req.TerraformVersion.Core() + } + + // If given a prerelease minimum version, check the Terraform CLI version + // directly, otherwise use the core version so that prereleases are treated + // as equal. + if s.minimumVersion.Prerelease() != "" { + minTerraformVersion = req.TerraformVersion + } else { + minTerraformVersion = req.TerraformVersion.Core() + } + + if minTerraformVersion.LessThan(s.minimumVersion) || maxTerraformVersion.GreaterThanOrEqual(s.maximumVersion) { + resp.Error = fmt.Errorf("expected Terraform CLI version between %s and %s but detected version is %s", + s.minimumVersion, s.maximumVersion, req.TerraformVersion) + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/tfversion/require_not.go b/vendor/github.com/hashicorp/terraform-plugin-testing/tfversion/require_not.go new file mode 100644 index 0000000000..2addb28140 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/tfversion/require_not.go @@ -0,0 +1,51 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package tfversion + +import ( + "context" + "fmt" + + "github.com/hashicorp/go-version" +) + +// RequireNot will fail the test if the Terraform CLI +// version matches the given version. +// +// Prereleases of Terraform CLI (whether alpha, beta, or rc) are considered +// equal to a given patch version. For example, if given +// version.Must(version.NewVersion("1.8.0")), then 1.8.0-rc1 will fail, not run, +// the test. Terraform prereleases are considered as potential candidates for +// the upcoming version and therefore are treated as semantically equal for +// testing purposes. If running prereleases of the same patch release is +// desired, give a different prerelease version. For example, if given +// version.Must(version.NewVersion("1.8.0-rc2")), then 1.8.0-rc1 will +// run the test. +func RequireNot(version *version.Version) TerraformVersionCheck { + return requireNotCheck{ + version: version, + } +} + +// requireNotCheck implements the TerraformVersionCheck interface +type requireNotCheck struct { + version *version.Version +} + +// CheckTerraformVersion satisfies the TerraformVersionCheck interface. +func (s requireNotCheck) CheckTerraformVersion(ctx context.Context, req CheckTerraformVersionRequest, resp *CheckTerraformVersionResponse) { + var terraformVersion *version.Version + + // If given a prerelease version, check the Terraform CLI version directly, + // otherwise use the core version so that prereleases are treated as equal. + if s.version.Prerelease() != "" { + terraformVersion = req.TerraformVersion + } else { + terraformVersion = req.TerraformVersion.Core() + } + + if terraformVersion.Equal(s.version) { + resp.Error = fmt.Errorf("unexpected Terraform CLI version: %s", s.version) + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/tfversion/skip_above.go b/vendor/github.com/hashicorp/terraform-plugin-testing/tfversion/skip_above.go new file mode 100644 index 0000000000..b56918c65c --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/tfversion/skip_above.go @@ -0,0 +1,54 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package tfversion + +import ( + "context" + "fmt" + + "github.com/hashicorp/go-version" +) + +// SkipAbove will skip (pass) the test if the Terraform CLI +// version is exclusively above the given version. For example, if given +// version.Must(version.NewVersion("1.8.0")), then 1.8.x or +// any other later versions will skip the test. +// +// Prereleases of Terraform CLI (whether alpha, beta, or rc) are considered +// equal to a given patch version. For example, if given +// version.Must(version.NewVersion("1.8.0")), then 1.8.0-rc1 will run, not skip, +// the test. Terraform prereleases are considered as potential candidates for +// the upcoming version and therefore are treated as semantically equal for +// testing. If skipping prereleases of the same patch release is desired, give a +// lower prerelease version. For example, if given +// version.Must(version.NewVersion("1.8.0-rc1")), then 1.8.0-rc2 will skip the +// test. +func SkipAbove(maximumVersion *version.Version) TerraformVersionCheck { + return skipAboveCheck{ + maximumVersion: maximumVersion, + } +} + +// skipAboveCheck implements the TerraformVersionCheck interface +type skipAboveCheck struct { + maximumVersion *version.Version +} + +// CheckTerraformVersion satisfies the TerraformVersionCheck interface. +func (s skipAboveCheck) CheckTerraformVersion(ctx context.Context, req CheckTerraformVersionRequest, resp *CheckTerraformVersionResponse) { + var terraformVersion *version.Version + + // If given a prerelease version, check the Terraform CLI version directly, + // otherwise use the core version so that prereleases are treated as equal. + if s.maximumVersion.Prerelease() != "" { + terraformVersion = req.TerraformVersion + } else { + terraformVersion = req.TerraformVersion.Core() + } + + if terraformVersion.GreaterThan(s.maximumVersion) { + resp.Skip = fmt.Sprintf("Terraform CLI version %s is above maximum version %s: skipping test", + req.TerraformVersion, s.maximumVersion) + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/tfversion/skip_below.go b/vendor/github.com/hashicorp/terraform-plugin-testing/tfversion/skip_below.go new file mode 100644 index 0000000000..15cb8a4989 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/tfversion/skip_below.go @@ -0,0 +1,54 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package tfversion + +import ( + "context" + "fmt" + + "github.com/hashicorp/go-version" +) + +// SkipBelow will skip (pass) the test if the Terraform CLI +// version is exclusively below the given version. For example, if given +// version.Must(version.NewVersion("1.8.0")), then 1.7.x or +// any other prior versions will skip the test. +// +// Prereleases of Terraform CLI (whether alpha, beta, or rc) are considered +// equal to a given patch version. For example, if given +// version.Must(version.NewVersion("1.8.0")), then 1.8.0-rc1 will run, not skip, +// the test. Terraform prereleases are considered as potential candidates for +// the upcoming version and therefore are treated as important for testing to +// run. If skipping prereleases of the same patch release is desired, give a +// higher prerelease version. For example, if given +// version.Must(version.NewVersion("1.8.0-rc2")), then 1.8.0-rc1 will skip the +// test. +func SkipBelow(minimumVersion *version.Version) TerraformVersionCheck { + return skipBelowCheck{ + minimumVersion: minimumVersion, + } +} + +// skipBelowCheck implements the TerraformVersionCheck interface +type skipBelowCheck struct { + minimumVersion *version.Version +} + +// CheckTerraformVersion satisfies the TerraformVersionCheck interface. +func (s skipBelowCheck) CheckTerraformVersion(ctx context.Context, req CheckTerraformVersionRequest, resp *CheckTerraformVersionResponse) { + var terraformVersion *version.Version + + // If given a prerelease version, check the Terraform CLI version directly, + // otherwise use the core version so that prereleases are treated as equal. + if s.minimumVersion.Prerelease() != "" { + terraformVersion = req.TerraformVersion + } else { + terraformVersion = req.TerraformVersion.Core() + } + + if terraformVersion.LessThan(s.minimumVersion) { + resp.Skip = fmt.Sprintf("Terraform CLI version %s is below minimum version %s: skipping test", + req.TerraformVersion, s.minimumVersion) + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/tfversion/skip_between.go b/vendor/github.com/hashicorp/terraform-plugin-testing/tfversion/skip_between.go new file mode 100644 index 0000000000..555ff79cc5 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/tfversion/skip_between.go @@ -0,0 +1,67 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package tfversion + +import ( + "context" + "fmt" + + "github.com/hashicorp/go-version" +) + +// SkipBetween will skip the test if the Terraform CLI +// version is between the given minimum (inclusive) and maximum (exclusive). +// For example, if given a minimum version of version.Must(version.NewVersion("1.7.0")) +// and a maximum version of version.Must(version.NewVersion("1.8.0")), then versions 1.7.x +// will skip the test. +// +// Prereleases of Terraform CLI (whether alpha, beta, or rc) are considered +// equal to a given patch version. For example, if given a minimum version of +// version.Must(version.NewVersion("1.8.0")), then 1.8.0-rc1 will skip, not run, +// the test. Terraform prereleases are considered as potential candidates for +// the upcoming version and therefore are treated as semantically equal for +// testing purposes. If running prereleases of the same patch release is +// desired, give a higher prerelease version. For example, if given a minimum +// version of version.Must(version.NewVersion("1.8.0-rc2")), then 1.8.0-rc1 will +// run the test. +func SkipBetween(minimumVersion, maximumVersion *version.Version) TerraformVersionCheck { + return skipBetweenCheck{ + minimumVersion: minimumVersion, + maximumVersion: maximumVersion, + } +} + +// skipBetweenCheck implements the TerraformVersionCheck interface +type skipBetweenCheck struct { + minimumVersion *version.Version + maximumVersion *version.Version +} + +// CheckTerraformVersion satisfies the TerraformVersionCheck interface. +func (s skipBetweenCheck) CheckTerraformVersion(ctx context.Context, req CheckTerraformVersionRequest, resp *CheckTerraformVersionResponse) { + var maxTerraformVersion, minTerraformVersion *version.Version + + // If given a prerelease maximum version, check the Terraform CLI version + // directly, otherwise use the core version so that prereleases are treated + // as equal. + if s.maximumVersion.Prerelease() != "" { + maxTerraformVersion = req.TerraformVersion + } else { + maxTerraformVersion = req.TerraformVersion.Core() + } + + // If given a prerelease minimum version, check the Terraform CLI version + // directly, otherwise use the core version so that prereleases are treated + // as equal. + if s.minimumVersion.Prerelease() != "" { + minTerraformVersion = req.TerraformVersion + } else { + minTerraformVersion = req.TerraformVersion.Core() + } + + if minTerraformVersion.GreaterThanOrEqual(s.minimumVersion) && maxTerraformVersion.LessThan(s.maximumVersion) { + resp.Skip = fmt.Sprintf("Terraform CLI version %s is between %s and %s: skipping test.", + req.TerraformVersion, s.minimumVersion, s.maximumVersion) + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/tfversion/skip_if.go b/vendor/github.com/hashicorp/terraform-plugin-testing/tfversion/skip_if.go new file mode 100644 index 0000000000..e5b7d96fc2 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/tfversion/skip_if.go @@ -0,0 +1,51 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package tfversion + +import ( + "context" + "fmt" + + "github.com/hashicorp/go-version" +) + +// SkipIf will skip (pass) the test if the Terraform CLI +// version matches the given version. +// +// Prereleases of Terraform CLI (whether alpha, beta, or rc) are considered +// equal to a given patch version. For example, if given +// version.Must(version.NewVersion("1.8.0")), then 1.8.0-rc1 will skip, not run, +// the test. Terraform prereleases are considered as potential candidates for +// the upcoming version and therefore are treated as semantically equal for +// testing purposes. If running prereleases of the same patch release is +// desired, give a different prerelease version. For example, if given +// version.Must(version.NewVersion("1.8.0-rc2")), then 1.8.0-rc1 will +// run the test. +func SkipIf(version *version.Version) TerraformVersionCheck { + return skipIfCheck{ + version: version, + } +} + +// skipIfCheck implements the TerraformVersionCheck interface +type skipIfCheck struct { + version *version.Version +} + +// CheckTerraformVersion satisfies the TerraformVersionCheck interface. +func (s skipIfCheck) CheckTerraformVersion(ctx context.Context, req CheckTerraformVersionRequest, resp *CheckTerraformVersionResponse) { + var terraformVersion *version.Version + + // If given a prerelease version, check the Terraform CLI version directly, + // otherwise use the core version so that prereleases are treated as equal. + if s.version.Prerelease() != "" { + terraformVersion = req.TerraformVersion + } else { + terraformVersion = req.TerraformVersion.Core() + } + + if terraformVersion.Equal(s.version) { + resp.Skip = fmt.Sprintf("Terraform CLI version is %s: skipping test.", s.version) + } +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/tfversion/skip_if_not_alpha.go b/vendor/github.com/hashicorp/terraform-plugin-testing/tfversion/skip_if_not_alpha.go new file mode 100644 index 0000000000..413ee1b435 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/tfversion/skip_if_not_alpha.go @@ -0,0 +1,31 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package tfversion + +import ( + "context" + "fmt" + "strings" +) + +// SkipIfNotAlpha will skip (pass) the test if the Terraform CLI +// version is not an alpha prerelease (for example, 1.10.0-alpha20241023). +// +// Alpha builds of Terraform include experimental features, so this version check +// can be used for acceptance testing of experimental features, such as deferred actions. +func SkipIfNotAlpha() TerraformVersionCheck { + return skipIfNotAlphaCheck{} +} + +// skipIfNotAlphaCheck implements the TerraformVersionCheck interface +type skipIfNotAlphaCheck struct{} + +// CheckTerraformVersion satisfies the TerraformVersionCheck interface. +func (s skipIfNotAlphaCheck) CheckTerraformVersion(ctx context.Context, req CheckTerraformVersionRequest, resp *CheckTerraformVersionResponse) { + if strings.Contains(req.TerraformVersion.Prerelease(), "alpha") { + return + } + + resp.Skip = fmt.Sprintf("Terraform CLI version %s is not an alpha build: skipping test.", req.TerraformVersion) +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/tfversion/skip_if_not_prerelease.go b/vendor/github.com/hashicorp/terraform-plugin-testing/tfversion/skip_if_not_prerelease.go new file mode 100644 index 0000000000..c49400b295 --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/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/vendor/github.com/hashicorp/terraform-plugin-testing/tfversion/version_check.go b/vendor/github.com/hashicorp/terraform-plugin-testing/tfversion/version_check.go new file mode 100644 index 0000000000..554ec2247d --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/tfversion/version_check.go @@ -0,0 +1,39 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package tfversion + +import ( + "context" + + "github.com/hashicorp/go-version" +) + +// TerraformVersionCheck is the interface for writing check logic against the Terraform CLI version. +// The Terraform CLI version is determined by the binary selected by the TF_ACC_TERRAFORM_PATH environment +// variable value, installed by the TF_ACC_TERRAFORM_VERSION value, or already existing based on the PATH environment +// variable. This logic is executed at the beginning of the TestCase before any TestStep is executed. +// +// This package contains some built-in functionality that implements the interface, otherwise consumers can use this +// interface for implementing their own custom logic. +type TerraformVersionCheck interface { + // CheckTerraformVersion should implement the logic to either pass, error (failing the test), or skip (passing the test). + CheckTerraformVersion(context.Context, CheckTerraformVersionRequest, *CheckTerraformVersionResponse) +} + +// CheckTerraformVersionRequest is the request received for the CheckTerraformVersion method of the +// TerraformVersionCheck interface. The response of that method is CheckTerraformVersionResponse. +type CheckTerraformVersionRequest struct { + // TerraformVersion is the version associated with the selected Terraform CLI binary. + TerraformVersion *version.Version +} + +// CheckTerraformVersionResponse is the response returned for the CheckTerraformVersion method of the +// TerraformVersionCheck interface. The request of that method is CheckTerraformVersionRequest. +type CheckTerraformVersionResponse struct { + // Error will result in failing the test with a given error message. + Error error + + // Skip will result in passing the test with a given skip message. + Skip string +} diff --git a/vendor/github.com/hashicorp/terraform-plugin-testing/tfversion/versions.go b/vendor/github.com/hashicorp/terraform-plugin-testing/tfversion/versions.go new file mode 100644 index 0000000000..dc5cc7dfea --- /dev/null +++ b/vendor/github.com/hashicorp/terraform-plugin-testing/tfversion/versions.go @@ -0,0 +1,44 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +package tfversion + +import "github.com/hashicorp/go-version" + +// Common use version variables to simplify provider testing implementations. +// This list is not intended to be exhaustive of all Terraform versions, +// however these should at least include cases where Terraform +// introduced new configuration language features. +var ( + // Version0_12_26 is the first Terraform CLI version supported + // by the testing code. + Version0_12_26 *version.Version = version.Must(version.NewVersion("0.12.26")) + + // Major versions + + Version1_0_0 *version.Version = version.Must(version.NewVersion("1.0.0")) + Version2_0_0 *version.Version = version.Must(version.NewVersion("2.0.0")) + + // Minor versions + + Version0_13_0 *version.Version = version.Must(version.NewVersion("0.13.0")) + Version0_14_0 *version.Version = version.Must(version.NewVersion("0.14.0")) + Version0_15_0 *version.Version = version.Must(version.NewVersion("0.15.0")) + Version1_1_0 *version.Version = version.Must(version.NewVersion("1.1.0")) + Version1_2_0 *version.Version = version.Must(version.NewVersion("1.2.0")) + Version1_3_0 *version.Version = version.Must(version.NewVersion("1.3.0")) + Version1_4_0 *version.Version = version.Must(version.NewVersion("1.4.0")) + // Version1_4_6 fixed inclusion of sensitive values in `terraform show -json` output. + // Reference: https://github.com/hashicorp/terraform/releases/tag/v1.4.6 + Version1_4_6 *version.Version = version.Must(version.NewVersion("1.4.6")) + Version1_5_0 *version.Version = version.Must(version.NewVersion("1.5.0")) + Version1_6_0 *version.Version = version.Must(version.NewVersion("1.6.0")) + Version1_7_0 *version.Version = version.Must(version.NewVersion("1.7.0")) + Version1_8_0 *version.Version = version.Must(version.NewVersion("1.8.0")) + Version1_9_0 *version.Version = version.Must(version.NewVersion("1.9.0")) + Version1_10_0 *version.Version = version.Must(version.NewVersion("1.10.0")) + Version1_11_0 *version.Version = version.Must(version.NewVersion("1.11.0")) + Version1_12_0 *version.Version = version.Must(version.NewVersion("1.12.0")) + Version1_13_0 *version.Version = version.Must(version.NewVersion("1.13.0")) + Version1_14_0 *version.Version = version.Must(version.NewVersion("1.14.0")) +) diff --git a/vendor/modules.txt b/vendor/modules.txt index 6470cee748..d423414c91 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -134,11 +134,11 @@ github.com/hashicorp/hcl/v2/hclsyntax # github.com/hashicorp/logutils v1.0.0 ## explicit github.com/hashicorp/logutils -# github.com/hashicorp/terraform-exec v0.23.1 +# github.com/hashicorp/terraform-exec v0.24.0 ## explicit; go 1.23.0 github.com/hashicorp/terraform-exec/internal/version github.com/hashicorp/terraform-exec/tfexec -# github.com/hashicorp/terraform-json v0.27.1 +# github.com/hashicorp/terraform-json v0.27.2 ## explicit; go 1.21 github.com/hashicorp/terraform-json # github.com/hashicorp/terraform-plugin-go v0.29.0 @@ -171,11 +171,8 @@ github.com/hashicorp/terraform-plugin-log/tfsdklog # github.com/hashicorp/terraform-plugin-sdk/v2 v2.38.1 ## explicit; go 1.24.0 github.com/hashicorp/terraform-plugin-sdk/v2/diag -github.com/hashicorp/terraform-plugin-sdk/v2/helper/acctest github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff -github.com/hashicorp/terraform-plugin-sdk/v2/helper/id github.com/hashicorp/terraform-plugin-sdk/v2/helper/logging -github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema github.com/hashicorp/terraform-plugin-sdk/v2/helper/structure @@ -187,11 +184,32 @@ github.com/hashicorp/terraform-plugin-sdk/v2/internal/helper/hashcode github.com/hashicorp/terraform-plugin-sdk/v2/internal/logging github.com/hashicorp/terraform-plugin-sdk/v2/internal/plans/objchange github.com/hashicorp/terraform-plugin-sdk/v2/internal/plugin/convert -github.com/hashicorp/terraform-plugin-sdk/v2/internal/plugintest github.com/hashicorp/terraform-plugin-sdk/v2/internal/tfdiags github.com/hashicorp/terraform-plugin-sdk/v2/meta github.com/hashicorp/terraform-plugin-sdk/v2/plugin github.com/hashicorp/terraform-plugin-sdk/v2/terraform +# github.com/hashicorp/terraform-plugin-testing v1.14.0 +## explicit; go 1.24.0 +github.com/hashicorp/terraform-plugin-testing/compare +github.com/hashicorp/terraform-plugin-testing/config +github.com/hashicorp/terraform-plugin-testing/helper/acctest +github.com/hashicorp/terraform-plugin-testing/helper/resource +github.com/hashicorp/terraform-plugin-testing/helper/resource/query +github.com/hashicorp/terraform-plugin-testing/internal/addrs +github.com/hashicorp/terraform-plugin-testing/internal/configs/configschema +github.com/hashicorp/terraform-plugin-testing/internal/configs/hcl2shim +github.com/hashicorp/terraform-plugin-testing/internal/logging +github.com/hashicorp/terraform-plugin-testing/internal/plugintest +github.com/hashicorp/terraform-plugin-testing/internal/teststep +github.com/hashicorp/terraform-plugin-testing/internal/tfdiags +github.com/hashicorp/terraform-plugin-testing/knownvalue +github.com/hashicorp/terraform-plugin-testing/plancheck +github.com/hashicorp/terraform-plugin-testing/querycheck +github.com/hashicorp/terraform-plugin-testing/querycheck/queryfilter +github.com/hashicorp/terraform-plugin-testing/statecheck +github.com/hashicorp/terraform-plugin-testing/terraform +github.com/hashicorp/terraform-plugin-testing/tfjsonpath +github.com/hashicorp/terraform-plugin-testing/tfversion # github.com/hashicorp/terraform-registry-address v0.4.0 ## explicit; go 1.23.0 github.com/hashicorp/terraform-registry-address