From 649b8397987d13fa53c67729a81dc7fceef218a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Filip=20Budzy=C5=84ski?= Date: Thu, 24 Oct 2024 10:31:51 +0200 Subject: [PATCH] fix: handle external change of secret type (#3141) ## Changes * Added secret_type computed filed to secret resources * Added CustomDiff for handling external change of secret_type * Refactored show_and_describe_handlers for flat describe_output and show_output * Tested external change of secret type for each resource ## Test Plan * [x] acceptance tests ## References * https://github.com/Snowflake-Labs/terraform-provider-snowflake/pull/3110#discussion_r1793543864 * https://github.com/Snowflake-Labs/terraform-provider-snowflake/pull/3110#discussion_r1802006700 --- .../secret_with_authorization_code_grant.md | 1 + .../secret_with_basic_authentication.md | 1 + .../secret_with_client_credentials.md | 1 + docs/resources/secret_with_generic_string.md | 1 + .../resourceassert/gen/resource_schema_def.go | 16 + ...h_authorization_code_grant_resource_ext.go | 2 +- ...h_authorization_code_grant_resource_gen.go | 52 +-- ..._with_basic_authentication_resource_gen.go | 10 + ...et_with_client_credentials_resource_gen.go | 10 + ...secret_with_generic_string_resource_gen.go | 10 + ...ith_authorization_code_grant_model_gen.go} | 13 +- ...ret_with_basic_authentication_model_gen.go | 11 + ...ecret_with_client_credentials_model_ext.go | 13 + ...cret_with_client_credentials_model_gen.go} | 14 +- .../secret_with_generic_string_model_gen.go | 11 + pkg/datasources/secrets_acceptance_test.go | 16 +- pkg/resources/custom_diffs.go | 32 ++ pkg/resources/custom_diffs_test.go | 296 ++++++++++++++++++ pkg/resources/masking_policy.go | 2 +- .../oauth_integration_for_custom_clients.go | 2 +- ...th_integration_for_partner_applications.go | 2 +- pkg/resources/resource_monitor.go | 14 +- pkg/resources/saml2_integration.go | 2 +- pkg/resources/schema.go | 4 +- pkg/resources/secret_common.go | 12 +- .../secret_with_basic_authentication.go | 8 +- ...th_basic_authentication_acceptance_test.go | 57 +++- pkg/resources/secret_with_generic_string.go | 6 +- ...ret_with_generic_string_acceptance_test.go | 57 +++- ...ret_with_oauth_authorization_code_grant.go | 33 +- ...uthorization_code_grant_acceptance_test.go | 151 ++++++++- .../secret_with_oauth_client_credentials.go | 6 +- ...auth_client_credentials_acceptance_test.go | 131 +++++++- pkg/resources/show_and_describe_handlers.go | 51 ++- pkg/resources/stream_on_external_table.go | 2 +- pkg/resources/stream_on_table.go | 2 +- pkg/resources/user.go | 4 +- pkg/resources/view.go | 8 +- pkg/resources/warehouse.go | 20 +- pkg/sdk/secrets_def.go | 39 ++- .../testint/secrets_gen_integration_test.go | 30 +- 41 files changed, 984 insertions(+), 169 deletions(-) rename pkg/acceptance/bettertestspoc/config/model/{secret_with_oauth_authorization_code_grant_model_gen.go => secret_with_authorization_code_grant_model_gen.go} (92%) create mode 100644 pkg/acceptance/bettertestspoc/config/model/secret_with_client_credentials_model_ext.go rename pkg/acceptance/bettertestspoc/config/model/{secret_with_oauth_client_credentials_model_gen.go => secret_with_client_credentials_model_gen.go} (91%) diff --git a/docs/resources/secret_with_authorization_code_grant.md b/docs/resources/secret_with_authorization_code_grant.md index 5042b86916..ff13b763fc 100644 --- a/docs/resources/secret_with_authorization_code_grant.md +++ b/docs/resources/secret_with_authorization_code_grant.md @@ -59,6 +59,7 @@ resource "snowflake_secret_with_authorization_code_grant" "test" { - `describe_output` (List of Object) Outputs the result of `DESCRIBE SECRET` for the given secret. (see [below for nested schema](#nestedatt--describe_output)) - `fully_qualified_name` (String) Fully qualified name of the resource. For more information, see [object name resolution](https://docs.snowflake.com/en/sql-reference/name-resolution). - `id` (String) The ID of this resource. +- `secret_type` (String) Specifies a type for the secret. This field is used for checking external changes and recreating the resources if needed. - `show_output` (List of Object) Outputs the result of `SHOW SECRETS` for the given secret. (see [below for nested schema](#nestedatt--show_output)) diff --git a/docs/resources/secret_with_basic_authentication.md b/docs/resources/secret_with_basic_authentication.md index 6192ae72be..e7078451d4 100644 --- a/docs/resources/secret_with_basic_authentication.md +++ b/docs/resources/secret_with_basic_authentication.md @@ -56,6 +56,7 @@ resource "snowflake_secret_with_basic_authentication" "test" { - `describe_output` (List of Object) Outputs the result of `DESCRIBE SECRET` for the given secret. (see [below for nested schema](#nestedatt--describe_output)) - `fully_qualified_name` (String) Fully qualified name of the resource. For more information, see [object name resolution](https://docs.snowflake.com/en/sql-reference/name-resolution). - `id` (String) The ID of this resource. +- `secret_type` (String) Specifies a type for the secret. This field is used for checking external changes and recreating the resources if needed. - `show_output` (List of Object) Outputs the result of `SHOW SECRETS` for the given secret. (see [below for nested schema](#nestedatt--show_output)) diff --git a/docs/resources/secret_with_client_credentials.md b/docs/resources/secret_with_client_credentials.md index 8d2c2e5895..d827292373 100644 --- a/docs/resources/secret_with_client_credentials.md +++ b/docs/resources/secret_with_client_credentials.md @@ -56,6 +56,7 @@ resource "snowflake_secret_with_client_credentials" "test" { - `describe_output` (List of Object) Outputs the result of `DESCRIBE SECRET` for the given secret. (see [below for nested schema](#nestedatt--describe_output)) - `fully_qualified_name` (String) Fully qualified name of the resource. For more information, see [object name resolution](https://docs.snowflake.com/en/sql-reference/name-resolution). - `id` (String) The ID of this resource. +- `secret_type` (String) Specifies a type for the secret. This field is used for checking external changes and recreating the resources if needed. - `show_output` (List of Object) Outputs the result of `SHOW SECRETS` for the given secret. (see [below for nested schema](#nestedatt--show_output)) diff --git a/docs/resources/secret_with_generic_string.md b/docs/resources/secret_with_generic_string.md index 2a2783a71e..3b1579c6ce 100644 --- a/docs/resources/secret_with_generic_string.md +++ b/docs/resources/secret_with_generic_string.md @@ -53,6 +53,7 @@ resource "snowflake_secret_with_generic_string" "test" { - `describe_output` (List of Object) Outputs the result of `DESCRIBE SECRET` for the given secret. (see [below for nested schema](#nestedatt--describe_output)) - `fully_qualified_name` (String) Fully qualified name of the resource. For more information, see [object name resolution](https://docs.snowflake.com/en/sql-reference/name-resolution). - `id` (String) The ID of this resource. +- `secret_type` (String) Specifies a type for the secret. This field is used for checking external changes and recreating the resources if needed. - `show_output` (List of Object) Outputs the result of `SHOW SECRETS` for the given secret. (see [below for nested schema](#nestedatt--show_output)) diff --git a/pkg/acceptance/bettertestspoc/assert/resourceassert/gen/resource_schema_def.go b/pkg/acceptance/bettertestspoc/assert/resourceassert/gen/resource_schema_def.go index 9103013a55..bd7e5f8413 100644 --- a/pkg/acceptance/bettertestspoc/assert/resourceassert/gen/resource_schema_def.go +++ b/pkg/acceptance/bettertestspoc/assert/resourceassert/gen/resource_schema_def.go @@ -73,6 +73,22 @@ var allResourceSchemaDefs = []ResourceSchemaDef{ name: "StreamOnExternalTable", schema: resources.StreamOnExternalTable().Schema, }, + { + name: "SecretWithAuthorizationCodeGrant", + schema: resources.SecretWithAuthorizationCodeGrant().Schema, + }, + { + name: "SecretWithBasicAuthentication", + schema: resources.SecretWithBasicAuthentication().Schema, + }, + { + name: "SecretWithClientCredentials", + schema: resources.SecretWithClientCredentials().Schema, + }, + { + name: "SecretWithGenericString", + schema: resources.SecretWithGenericString().Schema, + }, { name: "StreamOnDirectoryTable", schema: resources.StreamOnDirectoryTable().Schema, diff --git a/pkg/acceptance/bettertestspoc/assert/resourceassert/secret_with_authorization_code_grant_resource_ext.go b/pkg/acceptance/bettertestspoc/assert/resourceassert/secret_with_authorization_code_grant_resource_ext.go index 0044880efd..e5da2b69e7 100644 --- a/pkg/acceptance/bettertestspoc/assert/resourceassert/secret_with_authorization_code_grant_resource_ext.go +++ b/pkg/acceptance/bettertestspoc/assert/resourceassert/secret_with_authorization_code_grant_resource_ext.go @@ -4,7 +4,7 @@ import ( "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/bettertestspoc/assert" ) -func (s *SecretWithAuthorizationCodeResourceAssert) HasOauthRefreshTokenExpiryTimeNotEmpty() *SecretWithAuthorizationCodeResourceAssert { +func (s *SecretWithAuthorizationCodeGrantResourceAssert) HasOauthRefreshTokenExpiryTimeNotEmpty() *SecretWithAuthorizationCodeGrantResourceAssert { s.AddAssertion(assert.ValuePresent("oauth_refresh_token_expiry_time")) return s } diff --git a/pkg/acceptance/bettertestspoc/assert/resourceassert/secret_with_authorization_code_grant_resource_gen.go b/pkg/acceptance/bettertestspoc/assert/resourceassert/secret_with_authorization_code_grant_resource_gen.go index 610faae14e..45684561ab 100644 --- a/pkg/acceptance/bettertestspoc/assert/resourceassert/secret_with_authorization_code_grant_resource_gen.go +++ b/pkg/acceptance/bettertestspoc/assert/resourceassert/secret_with_authorization_code_grant_resource_gen.go @@ -8,22 +8,22 @@ import ( "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/bettertestspoc/assert" ) -type SecretWithAuthorizationCodeResourceAssert struct { +type SecretWithAuthorizationCodeGrantResourceAssert struct { *assert.ResourceAssert } -func SecretWithAuthorizationCodeResource(t *testing.T, name string) *SecretWithAuthorizationCodeResourceAssert { +func SecretWithAuthorizationCodeGrantResource(t *testing.T, name string) *SecretWithAuthorizationCodeGrantResourceAssert { t.Helper() - return &SecretWithAuthorizationCodeResourceAssert{ + return &SecretWithAuthorizationCodeGrantResourceAssert{ ResourceAssert: assert.NewResourceAssert(name, "resource"), } } -func ImportedSecretWithAuthorizationCodeResource(t *testing.T, id string) *SecretWithAuthorizationCodeResourceAssert { +func ImportedSecretWithAuthorizationCodeGrantResource(t *testing.T, id string) *SecretWithAuthorizationCodeGrantResourceAssert { t.Helper() - return &SecretWithAuthorizationCodeResourceAssert{ + return &SecretWithAuthorizationCodeGrantResourceAssert{ ResourceAssert: assert.NewImportedResourceAssert(id, "imported resource"), } } @@ -32,86 +32,96 @@ func ImportedSecretWithAuthorizationCodeResource(t *testing.T, id string) *Secre // Attribute value string checks // /////////////////////////////////// -func (s *SecretWithAuthorizationCodeResourceAssert) HasApiAuthenticationString(expected string) *SecretWithAuthorizationCodeResourceAssert { +func (s *SecretWithAuthorizationCodeGrantResourceAssert) HasApiAuthenticationString(expected string) *SecretWithAuthorizationCodeGrantResourceAssert { s.AddAssertion(assert.ValueSet("api_authentication", expected)) return s } -func (s *SecretWithAuthorizationCodeResourceAssert) HasCommentString(expected string) *SecretWithAuthorizationCodeResourceAssert { +func (s *SecretWithAuthorizationCodeGrantResourceAssert) HasCommentString(expected string) *SecretWithAuthorizationCodeGrantResourceAssert { s.AddAssertion(assert.ValueSet("comment", expected)) return s } -func (s *SecretWithAuthorizationCodeResourceAssert) HasDatabaseString(expected string) *SecretWithAuthorizationCodeResourceAssert { +func (s *SecretWithAuthorizationCodeGrantResourceAssert) HasDatabaseString(expected string) *SecretWithAuthorizationCodeGrantResourceAssert { s.AddAssertion(assert.ValueSet("database", expected)) return s } -func (s *SecretWithAuthorizationCodeResourceAssert) HasFullyQualifiedNameString(expected string) *SecretWithAuthorizationCodeResourceAssert { +func (s *SecretWithAuthorizationCodeGrantResourceAssert) HasFullyQualifiedNameString(expected string) *SecretWithAuthorizationCodeGrantResourceAssert { s.AddAssertion(assert.ValueSet("fully_qualified_name", expected)) return s } -func (s *SecretWithAuthorizationCodeResourceAssert) HasNameString(expected string) *SecretWithAuthorizationCodeResourceAssert { +func (s *SecretWithAuthorizationCodeGrantResourceAssert) HasNameString(expected string) *SecretWithAuthorizationCodeGrantResourceAssert { s.AddAssertion(assert.ValueSet("name", expected)) return s } -func (s *SecretWithAuthorizationCodeResourceAssert) HasOauthRefreshTokenString(expected string) *SecretWithAuthorizationCodeResourceAssert { +func (s *SecretWithAuthorizationCodeGrantResourceAssert) HasOauthRefreshTokenString(expected string) *SecretWithAuthorizationCodeGrantResourceAssert { s.AddAssertion(assert.ValueSet("oauth_refresh_token", expected)) return s } -func (s *SecretWithAuthorizationCodeResourceAssert) HasOauthRefreshTokenExpiryTimeString(expected string) *SecretWithAuthorizationCodeResourceAssert { +func (s *SecretWithAuthorizationCodeGrantResourceAssert) HasOauthRefreshTokenExpiryTimeString(expected string) *SecretWithAuthorizationCodeGrantResourceAssert { s.AddAssertion(assert.ValueSet("oauth_refresh_token_expiry_time", expected)) return s } -func (s *SecretWithAuthorizationCodeResourceAssert) HasSchemaString(expected string) *SecretWithAuthorizationCodeResourceAssert { +func (s *SecretWithAuthorizationCodeGrantResourceAssert) HasSchemaString(expected string) *SecretWithAuthorizationCodeGrantResourceAssert { s.AddAssertion(assert.ValueSet("schema", expected)) return s } +func (s *SecretWithAuthorizationCodeGrantResourceAssert) HasSecretTypeString(expected string) *SecretWithAuthorizationCodeGrantResourceAssert { + s.AddAssertion(assert.ValueSet("secret_type", expected)) + return s +} + //////////////////////////// // Attribute empty checks // //////////////////////////// -func (s *SecretWithAuthorizationCodeResourceAssert) HasNoApiAuthentication() *SecretWithAuthorizationCodeResourceAssert { +func (s *SecretWithAuthorizationCodeGrantResourceAssert) HasNoApiAuthentication() *SecretWithAuthorizationCodeGrantResourceAssert { s.AddAssertion(assert.ValueNotSet("api_authentication")) return s } -func (s *SecretWithAuthorizationCodeResourceAssert) HasNoComment() *SecretWithAuthorizationCodeResourceAssert { +func (s *SecretWithAuthorizationCodeGrantResourceAssert) HasNoComment() *SecretWithAuthorizationCodeGrantResourceAssert { s.AddAssertion(assert.ValueNotSet("comment")) return s } -func (s *SecretWithAuthorizationCodeResourceAssert) HasNoDatabase() *SecretWithAuthorizationCodeResourceAssert { +func (s *SecretWithAuthorizationCodeGrantResourceAssert) HasNoDatabase() *SecretWithAuthorizationCodeGrantResourceAssert { s.AddAssertion(assert.ValueNotSet("database")) return s } -func (s *SecretWithAuthorizationCodeResourceAssert) HasNoFullyQualifiedName() *SecretWithAuthorizationCodeResourceAssert { +func (s *SecretWithAuthorizationCodeGrantResourceAssert) HasNoFullyQualifiedName() *SecretWithAuthorizationCodeGrantResourceAssert { s.AddAssertion(assert.ValueNotSet("fully_qualified_name")) return s } -func (s *SecretWithAuthorizationCodeResourceAssert) HasNoName() *SecretWithAuthorizationCodeResourceAssert { +func (s *SecretWithAuthorizationCodeGrantResourceAssert) HasNoName() *SecretWithAuthorizationCodeGrantResourceAssert { s.AddAssertion(assert.ValueNotSet("name")) return s } -func (s *SecretWithAuthorizationCodeResourceAssert) HasNoOauthRefreshToken() *SecretWithAuthorizationCodeResourceAssert { +func (s *SecretWithAuthorizationCodeGrantResourceAssert) HasNoOauthRefreshToken() *SecretWithAuthorizationCodeGrantResourceAssert { s.AddAssertion(assert.ValueNotSet("oauth_refresh_token")) return s } -func (s *SecretWithAuthorizationCodeResourceAssert) HasNoOauthRefreshTokenExpiryTime() *SecretWithAuthorizationCodeResourceAssert { +func (s *SecretWithAuthorizationCodeGrantResourceAssert) HasNoOauthRefreshTokenExpiryTime() *SecretWithAuthorizationCodeGrantResourceAssert { s.AddAssertion(assert.ValueNotSet("oauth_refresh_token_expiry_time")) return s } -func (s *SecretWithAuthorizationCodeResourceAssert) HasNoSchema() *SecretWithAuthorizationCodeResourceAssert { +func (s *SecretWithAuthorizationCodeGrantResourceAssert) HasNoSchema() *SecretWithAuthorizationCodeGrantResourceAssert { s.AddAssertion(assert.ValueNotSet("schema")) return s } + +func (s *SecretWithAuthorizationCodeGrantResourceAssert) HasNoSecretType() *SecretWithAuthorizationCodeGrantResourceAssert { + s.AddAssertion(assert.ValueNotSet("secret_type")) + return s +} diff --git a/pkg/acceptance/bettertestspoc/assert/resourceassert/secret_with_basic_authentication_resource_gen.go b/pkg/acceptance/bettertestspoc/assert/resourceassert/secret_with_basic_authentication_resource_gen.go index 186aed6035..5c638a4a09 100644 --- a/pkg/acceptance/bettertestspoc/assert/resourceassert/secret_with_basic_authentication_resource_gen.go +++ b/pkg/acceptance/bettertestspoc/assert/resourceassert/secret_with_basic_authentication_resource_gen.go @@ -62,6 +62,11 @@ func (s *SecretWithBasicAuthenticationResourceAssert) HasSchemaString(expected s return s } +func (s *SecretWithBasicAuthenticationResourceAssert) HasSecretTypeString(expected string) *SecretWithBasicAuthenticationResourceAssert { + s.AddAssertion(assert.ValueSet("secret_type", expected)) + return s +} + func (s *SecretWithBasicAuthenticationResourceAssert) HasUsernameString(expected string) *SecretWithBasicAuthenticationResourceAssert { s.AddAssertion(assert.ValueSet("username", expected)) return s @@ -101,6 +106,11 @@ func (s *SecretWithBasicAuthenticationResourceAssert) HasNoSchema() *SecretWithB return s } +func (s *SecretWithBasicAuthenticationResourceAssert) HasNoSecretType() *SecretWithBasicAuthenticationResourceAssert { + s.AddAssertion(assert.ValueNotSet("secret_type")) + return s +} + func (s *SecretWithBasicAuthenticationResourceAssert) HasNoUsername() *SecretWithBasicAuthenticationResourceAssert { s.AddAssertion(assert.ValueNotSet("username")) return s diff --git a/pkg/acceptance/bettertestspoc/assert/resourceassert/secret_with_client_credentials_resource_gen.go b/pkg/acceptance/bettertestspoc/assert/resourceassert/secret_with_client_credentials_resource_gen.go index 12a36fa46d..68aaded5df 100644 --- a/pkg/acceptance/bettertestspoc/assert/resourceassert/secret_with_client_credentials_resource_gen.go +++ b/pkg/acceptance/bettertestspoc/assert/resourceassert/secret_with_client_credentials_resource_gen.go @@ -67,6 +67,11 @@ func (s *SecretWithClientCredentialsResourceAssert) HasSchemaString(expected str return s } +func (s *SecretWithClientCredentialsResourceAssert) HasSecretTypeString(expected string) *SecretWithClientCredentialsResourceAssert { + s.AddAssertion(assert.ValueSet("secret_type", expected)) + return s +} + //////////////////////////// // Attribute empty checks // //////////////////////////// @@ -105,3 +110,8 @@ func (s *SecretWithClientCredentialsResourceAssert) HasNoSchema() *SecretWithCli s.AddAssertion(assert.ValueNotSet("schema")) return s } + +func (s *SecretWithClientCredentialsResourceAssert) HasNoSecretType() *SecretWithClientCredentialsResourceAssert { + s.AddAssertion(assert.ValueNotSet("secret_type")) + return s +} diff --git a/pkg/acceptance/bettertestspoc/assert/resourceassert/secret_with_generic_string_resource_gen.go b/pkg/acceptance/bettertestspoc/assert/resourceassert/secret_with_generic_string_resource_gen.go index d2d682aa1f..259dd6dab0 100644 --- a/pkg/acceptance/bettertestspoc/assert/resourceassert/secret_with_generic_string_resource_gen.go +++ b/pkg/acceptance/bettertestspoc/assert/resourceassert/secret_with_generic_string_resource_gen.go @@ -62,6 +62,11 @@ func (s *SecretWithGenericStringResourceAssert) HasSecretStringString(expected s return s } +func (s *SecretWithGenericStringResourceAssert) HasSecretTypeString(expected string) *SecretWithGenericStringResourceAssert { + s.AddAssertion(assert.ValueSet("secret_type", expected)) + return s +} + //////////////////////////// // Attribute empty checks // //////////////////////////// @@ -95,3 +100,8 @@ func (s *SecretWithGenericStringResourceAssert) HasNoSecretString() *SecretWithG s.AddAssertion(assert.ValueNotSet("secret_string")) return s } + +func (s *SecretWithGenericStringResourceAssert) HasNoSecretType() *SecretWithGenericStringResourceAssert { + s.AddAssertion(assert.ValueNotSet("secret_type")) + return s +} diff --git a/pkg/acceptance/bettertestspoc/config/model/secret_with_oauth_authorization_code_grant_model_gen.go b/pkg/acceptance/bettertestspoc/config/model/secret_with_authorization_code_grant_model_gen.go similarity index 92% rename from pkg/acceptance/bettertestspoc/config/model/secret_with_oauth_authorization_code_grant_model_gen.go rename to pkg/acceptance/bettertestspoc/config/model/secret_with_authorization_code_grant_model_gen.go index 212740cc5d..144a4fc212 100644 --- a/pkg/acceptance/bettertestspoc/config/model/secret_with_oauth_authorization_code_grant_model_gen.go +++ b/pkg/acceptance/bettertestspoc/config/model/secret_with_authorization_code_grant_model_gen.go @@ -18,6 +18,7 @@ type SecretWithAuthorizationCodeGrantModel struct { OauthRefreshToken tfconfig.Variable `json:"oauth_refresh_token,omitempty"` OauthRefreshTokenExpiryTime tfconfig.Variable `json:"oauth_refresh_token_expiry_time,omitempty"` Schema tfconfig.Variable `json:"schema,omitempty"` + SecretType tfconfig.Variable `json:"secret_type,omitempty"` *config.ResourceModelMeta } @@ -49,9 +50,9 @@ func SecretWithAuthorizationCodeGrantWithDefaultMeta( apiAuthentication string, database string, name string, + schema string, oauthRefreshToken string, oauthRefreshTokenExpiryTime string, - schema string, ) *SecretWithAuthorizationCodeGrantModel { s := &SecretWithAuthorizationCodeGrantModel{ResourceModelMeta: config.DefaultMeta(resources.SecretWithAuthorizationCodeGrant)} s.WithApiAuthentication(apiAuthentication) @@ -107,6 +108,11 @@ func (s *SecretWithAuthorizationCodeGrantModel) WithSchema(schema string) *Secre return s } +func (s *SecretWithAuthorizationCodeGrantModel) WithSecretType(secretType string) *SecretWithAuthorizationCodeGrantModel { + s.SecretType = tfconfig.StringVariable(secretType) + return s +} + ////////////////////////////////////////// // below it's possible to set any value // ////////////////////////////////////////// @@ -150,3 +156,8 @@ func (s *SecretWithAuthorizationCodeGrantModel) WithSchemaValue(value tfconfig.V s.Schema = value return s } + +func (s *SecretWithAuthorizationCodeGrantModel) WithSecretTypeValue(value tfconfig.Variable) *SecretWithAuthorizationCodeGrantModel { + s.SecretType = value + return s +} diff --git a/pkg/acceptance/bettertestspoc/config/model/secret_with_basic_authentication_model_gen.go b/pkg/acceptance/bettertestspoc/config/model/secret_with_basic_authentication_model_gen.go index 975b3c7ff7..b3c0f4ee83 100644 --- a/pkg/acceptance/bettertestspoc/config/model/secret_with_basic_authentication_model_gen.go +++ b/pkg/acceptance/bettertestspoc/config/model/secret_with_basic_authentication_model_gen.go @@ -16,6 +16,7 @@ type SecretWithBasicAuthenticationModel struct { Name tfconfig.Variable `json:"name,omitempty"` Password tfconfig.Variable `json:"password,omitempty"` Schema tfconfig.Variable `json:"schema,omitempty"` + SecretType tfconfig.Variable `json:"secret_type,omitempty"` Username tfconfig.Variable `json:"username,omitempty"` *config.ResourceModelMeta @@ -92,6 +93,11 @@ func (s *SecretWithBasicAuthenticationModel) WithSchema(schema string) *SecretWi return s } +func (s *SecretWithBasicAuthenticationModel) WithSecretType(secretType string) *SecretWithBasicAuthenticationModel { + s.SecretType = tfconfig.StringVariable(secretType) + return s +} + func (s *SecretWithBasicAuthenticationModel) WithUsername(username string) *SecretWithBasicAuthenticationModel { s.Username = tfconfig.StringVariable(username) return s @@ -131,6 +137,11 @@ func (s *SecretWithBasicAuthenticationModel) WithSchemaValue(value tfconfig.Vari return s } +func (s *SecretWithBasicAuthenticationModel) WithSecretTypeValue(value tfconfig.Variable) *SecretWithBasicAuthenticationModel { + s.SecretType = value + return s +} + func (s *SecretWithBasicAuthenticationModel) WithUsernameValue(value tfconfig.Variable) *SecretWithBasicAuthenticationModel { s.Username = value return s diff --git a/pkg/acceptance/bettertestspoc/config/model/secret_with_client_credentials_model_ext.go b/pkg/acceptance/bettertestspoc/config/model/secret_with_client_credentials_model_ext.go new file mode 100644 index 0000000000..6555f0f1cf --- /dev/null +++ b/pkg/acceptance/bettertestspoc/config/model/secret_with_client_credentials_model_ext.go @@ -0,0 +1,13 @@ +package model + +import tfconfig "github.com/hashicorp/terraform-plugin-testing/config" + +func (s *SecretWithClientCredentialsModel) WithOauthScopes(oauthScopes []string) *SecretWithClientCredentialsModel { + oauthScopesStringVariables := make([]tfconfig.Variable, len(oauthScopes)) + for i, v := range oauthScopes { + oauthScopesStringVariables[i] = tfconfig.StringVariable(v) + } + + s.OauthScopes = tfconfig.SetVariable(oauthScopesStringVariables...) + return s +} diff --git a/pkg/acceptance/bettertestspoc/config/model/secret_with_oauth_client_credentials_model_gen.go b/pkg/acceptance/bettertestspoc/config/model/secret_with_client_credentials_model_gen.go similarity index 91% rename from pkg/acceptance/bettertestspoc/config/model/secret_with_oauth_client_credentials_model_gen.go rename to pkg/acceptance/bettertestspoc/config/model/secret_with_client_credentials_model_gen.go index 9e093b48d0..bd69bffbb2 100644 --- a/pkg/acceptance/bettertestspoc/config/model/secret_with_oauth_client_credentials_model_gen.go +++ b/pkg/acceptance/bettertestspoc/config/model/secret_with_client_credentials_model_gen.go @@ -17,6 +17,7 @@ type SecretWithClientCredentialsModel struct { Name tfconfig.Variable `json:"name,omitempty"` OauthScopes tfconfig.Variable `json:"oauth_scopes,omitempty"` Schema tfconfig.Variable `json:"schema,omitempty"` + SecretType tfconfig.Variable `json:"secret_type,omitempty"` *config.ResourceModelMeta } @@ -94,6 +95,11 @@ func (s *SecretWithClientCredentialsModel) WithSchema(schema string) *SecretWith return s } +func (s *SecretWithClientCredentialsModel) WithSecretType(secretType string) *SecretWithClientCredentialsModel { + s.SecretType = tfconfig.StringVariable(secretType) + return s +} + ////////////////////////////////////////// // below it's possible to set any value // ////////////////////////////////////////// @@ -132,12 +138,8 @@ func (s *SecretWithClientCredentialsModel) WithSchemaValue(value tfconfig.Variab s.Schema = value return s } -func (s *SecretWithClientCredentialsModel) WithOauthScopes(oauthScopes []string) *SecretWithClientCredentialsModel { - oauthScopesStringVariables := make([]tfconfig.Variable, len(oauthScopes)) - for i, v := range oauthScopes { - oauthScopesStringVariables[i] = tfconfig.StringVariable(v) - } - s.OauthScopes = tfconfig.SetVariable(oauthScopesStringVariables...) +func (s *SecretWithClientCredentialsModel) WithSecretTypeValue(value tfconfig.Variable) *SecretWithClientCredentialsModel { + s.SecretType = value return s } diff --git a/pkg/acceptance/bettertestspoc/config/model/secret_with_generic_string_model_gen.go b/pkg/acceptance/bettertestspoc/config/model/secret_with_generic_string_model_gen.go index e2f441ef3f..c1cbe7d05e 100644 --- a/pkg/acceptance/bettertestspoc/config/model/secret_with_generic_string_model_gen.go +++ b/pkg/acceptance/bettertestspoc/config/model/secret_with_generic_string_model_gen.go @@ -16,6 +16,7 @@ type SecretWithGenericStringModel struct { Name tfconfig.Variable `json:"name,omitempty"` Schema tfconfig.Variable `json:"schema,omitempty"` SecretString tfconfig.Variable `json:"secret_string,omitempty"` + SecretType tfconfig.Variable `json:"secret_type,omitempty"` *config.ResourceModelMeta } @@ -87,6 +88,11 @@ func (s *SecretWithGenericStringModel) WithSecretString(secretString string) *Se return s } +func (s *SecretWithGenericStringModel) WithSecretType(secretType string) *SecretWithGenericStringModel { + s.SecretType = tfconfig.StringVariable(secretType) + return s +} + ////////////////////////////////////////// // below it's possible to set any value // ////////////////////////////////////////// @@ -120,3 +126,8 @@ func (s *SecretWithGenericStringModel) WithSecretStringValue(value tfconfig.Vari s.SecretString = value return s } + +func (s *SecretWithGenericStringModel) WithSecretTypeValue(value tfconfig.Variable) *SecretWithGenericStringModel { + s.SecretType = value + return s +} diff --git a/pkg/datasources/secrets_acceptance_test.go b/pkg/datasources/secrets_acceptance_test.go index 28627337b2..41d454a29a 100644 --- a/pkg/datasources/secrets_acceptance_test.go +++ b/pkg/datasources/secrets_acceptance_test.go @@ -62,7 +62,7 @@ func TestAcc_Secrets_WithClientCredentials(t *testing.T) { HasDatabaseName(id.DatabaseName()). HasSchemaName(id.SchemaName()). HasComment(""). - HasSecretType(sdk.SecretTypeOAuth2), + HasSecretType(string(sdk.SecretTypeOAuth2)), assert.Check(resource.TestCheckResourceAttr(dsName, "secrets.0.show_output.0.oauth_scopes.#", "2")), assert.Check(resource.TestCheckTypeSetElemAttr(dsName, "secrets.0.show_output.0.oauth_scopes.*", "username")), assert.Check(resource.TestCheckTypeSetElemAttr(dsName, "secrets.0.show_output.0.oauth_scopes.*", "test_scope")), @@ -70,7 +70,7 @@ func TestAcc_Secrets_WithClientCredentials(t *testing.T) { assert.Check(resource.TestCheckResourceAttr(dsName, "secrets.0.describe_output.0.name", id.Name())), assert.Check(resource.TestCheckResourceAttr(dsName, "secrets.0.describe_output.0.database_name", id.DatabaseName())), assert.Check(resource.TestCheckResourceAttr(dsName, "secrets.0.describe_output.0.schema_name", id.SchemaName())), - assert.Check(resource.TestCheckResourceAttr(dsName, "secrets.0.describe_output.0.secret_type", sdk.SecretTypeOAuth2)), + assert.Check(resource.TestCheckResourceAttr(dsName, "secrets.0.describe_output.0.secret_type", string(sdk.SecretTypeOAuth2))), assert.Check(resource.TestCheckResourceAttr(dsName, "secrets.0.describe_output.0.username", "")), assert.Check(resource.TestCheckResourceAttr(dsName, "secrets.0.describe_output.0.comment", "")), assert.Check(resource.TestCheckResourceAttr(dsName, "secrets.0.describe_output.0.oauth_scopes.#", "2")), @@ -117,13 +117,13 @@ func TestAcc_Secrets_WithAuthorizationCodeGrant(t *testing.T) { HasDatabaseName(id.DatabaseName()). HasSchemaName(id.SchemaName()). HasComment("test_comment"). - HasSecretType(sdk.SecretTypeOAuth2), + HasSecretType(string(sdk.SecretTypeOAuth2)), assert.Check(resource.TestCheckResourceAttr(dsName, "secrets.0.show_output.0.oauth_scopes.#", "0")), assert.Check(resource.TestCheckResourceAttr(dsName, "secrets.0.describe_output.0.name", id.Name())), assert.Check(resource.TestCheckResourceAttr(dsName, "secrets.0.describe_output.0.database_name", id.DatabaseName())), assert.Check(resource.TestCheckResourceAttr(dsName, "secrets.0.describe_output.0.schema_name", id.SchemaName())), - assert.Check(resource.TestCheckResourceAttr(dsName, "secrets.0.describe_output.0.secret_type", sdk.SecretTypeOAuth2)), + assert.Check(resource.TestCheckResourceAttr(dsName, "secrets.0.describe_output.0.secret_type", string(sdk.SecretTypeOAuth2))), assert.Check(resource.TestCheckResourceAttr(dsName, "secrets.0.describe_output.0.username", "")), assert.Check(resource.TestCheckResourceAttr(dsName, "secrets.0.describe_output.0.comment", "test_comment")), assert.Check(resource.TestCheckResourceAttr(dsName, "secrets.0.describe_output.0.oauth_scopes.#", "0")), @@ -158,13 +158,13 @@ func TestAcc_Secrets_WithBasicAuthentication(t *testing.T) { HasDatabaseName(id.DatabaseName()). HasSchemaName(id.SchemaName()). HasComment(""). - HasSecretType(sdk.SecretTypePassword), + HasSecretType(string(sdk.SecretTypePassword)), assert.Check(resource.TestCheckResourceAttr(dsName, "secrets.0.show_output.0.oauth_scopes.#", "0")), assert.Check(resource.TestCheckResourceAttr(dsName, "secrets.0.describe_output.0.name", id.Name())), assert.Check(resource.TestCheckResourceAttr(dsName, "secrets.0.describe_output.0.database_name", id.DatabaseName())), assert.Check(resource.TestCheckResourceAttr(dsName, "secrets.0.describe_output.0.schema_name", id.SchemaName())), - assert.Check(resource.TestCheckResourceAttr(dsName, "secrets.0.describe_output.0.secret_type", sdk.SecretTypePassword)), + assert.Check(resource.TestCheckResourceAttr(dsName, "secrets.0.describe_output.0.secret_type", string(sdk.SecretTypePassword))), assert.Check(resource.TestCheckResourceAttr(dsName, "secrets.0.describe_output.0.username", "test_username")), assert.Check(resource.TestCheckResourceAttr(dsName, "secrets.0.describe_output.0.comment", "")), assert.Check(resource.TestCheckResourceAttr(dsName, "secrets.0.describe_output.0.oauth_scopes.#", "0")), @@ -199,14 +199,14 @@ func TestAcc_Secrets_WithGenericString(t *testing.T) { HasDatabaseName(id.DatabaseName()). HasSchemaName(id.SchemaName()). HasComment(""). - HasSecretType(sdk.SecretTypeGenericString), + HasSecretType(string(sdk.SecretTypeGenericString)), assert.Check(resource.TestCheckResourceAttr(dsName, "secrets.0.show_output.0.oauth_scopes.#", "0")), assert.Check(resource.TestCheckResourceAttrSet(dsName, "secrets.0.describe_output.0.created_on")), assert.Check(resource.TestCheckResourceAttr(dsName, "secrets.0.describe_output.0.name", id.Name())), assert.Check(resource.TestCheckResourceAttr(dsName, "secrets.0.describe_output.0.database_name", id.DatabaseName())), assert.Check(resource.TestCheckResourceAttr(dsName, "secrets.0.describe_output.0.schema_name", id.SchemaName())), - assert.Check(resource.TestCheckResourceAttr(dsName, "secrets.0.describe_output.0.secret_type", sdk.SecretTypeGenericString)), + assert.Check(resource.TestCheckResourceAttr(dsName, "secrets.0.describe_output.0.secret_type", string(sdk.SecretTypeGenericString))), assert.Check(resource.TestCheckResourceAttr(dsName, "secrets.0.describe_output.0.username", "")), assert.Check(resource.TestCheckResourceAttr(dsName, "secrets.0.describe_output.0.comment", "")), assert.Check(resource.TestCheckResourceAttr(dsName, "secrets.0.describe_output.0.oauth_scopes.#", "0")), diff --git a/pkg/resources/custom_diffs.go b/pkg/resources/custom_diffs.go index 4d41624539..f884fc7e9b 100644 --- a/pkg/resources/custom_diffs.go +++ b/pkg/resources/custom_diffs.go @@ -196,6 +196,38 @@ func RecreateWhenUserTypeChangedExternally(userType sdk.UserType) schema.Customi } } +func RecreateWhenSecretTypeChangedExternally(secretType sdk.SecretType) schema.CustomizeDiffFunc { + return func(_ context.Context, diff *schema.ResourceDiff, _ interface{}) error { + if n := diff.Get("secret_type"); n != nil { + logging.DebugLogger.Printf("[DEBUG] new external value for secret type %s\n", n.(string)) + + diffSecretType, _ := sdk.ToSecretType(n.(string)) + if acceptableSecretTypes, ok := sdk.AcceptableSecretTypes[secretType]; ok && !slices.Contains(acceptableSecretTypes, diffSecretType) { + return errors.Join(diff.SetNew("secret_type", ""), diff.ForceNew("secret_type")) + } + // both client_credentials and authorization_code_grant secrets have the same type: "OAUTH2" + // to detect the external type change we need to check fields that are required in one, but should be absent in the other + // we will check if the 'oauth_refresh_token_expiry_time' is present in the describe_output + // since it is required in authorization_code_grant flow and should be empty in client_credentials flow + if diffSecretType == sdk.SecretTypeOAuth2 { + var isRefreshTokenExpiryTimeEmpty bool + rt := diff.Get("describe_output.0.oauth_refresh_token_expiry_time").(string) + + switch secretType { + case sdk.SecretTypeOAuth2AuthorizationCodeGrant: + isRefreshTokenExpiryTimeEmpty = rt == "" + case sdk.SecretTypeOAuth2ClientCredentials: + isRefreshTokenExpiryTimeEmpty = rt != "" + } + if isRefreshTokenExpiryTimeEmpty { + return errors.Join(diff.SetNew("secret_type", ""), diff.ForceNew("secret_type")) + } + } + } + return nil + } +} + // RecreateWhenStreamIsStale detects when the stream is stale, and sets a `false` value for `stale` field. // This means that the provider can detect that change in `stale` from `true` to `false`, where `false` is our desired state. func RecreateWhenStreamIsStale() schema.CustomizeDiffFunc { diff --git a/pkg/resources/custom_diffs_test.go b/pkg/resources/custom_diffs_test.go index b1e3772d6b..8942eca24c 100644 --- a/pkg/resources/custom_diffs_test.go +++ b/pkg/resources/custom_diffs_test.go @@ -143,6 +143,18 @@ func createProviderWithNamedPropertyAndCustomDiff(t *testing.T, propertyName str } } +func createProviderWithCustomSchemaAndCustomDiff(t *testing.T, customSchema map[string]*schema.Schema, customDiffFunc schema.CustomizeDiffFunc) *schema.Provider { + t.Helper() + return &schema.Provider{ + ResourcesMap: map[string]*schema.Resource{ + "test": { + Schema: customSchema, + CustomizeDiff: customDiffFunc, + }, + }, + } +} + func calculateDiff(t *testing.T, providerConfig *schema.Provider, rawConfigValue cty.Value, stateValue map[string]any) *terraform.InstanceDiff { t.Helper() diff, err := providerConfig.ResourcesMap["test"].Diff( @@ -786,3 +798,287 @@ func Test_RecreateWhenUserTypeChangedExternally(t *testing.T) { }) } } + +func Test_RecreateWhenSecretTypeChangedExternally(t *testing.T) { + tests := []struct { + name string + secretType sdk.SecretType + stateValue map[string]string + wantForceNew bool + }{ + // password type + { + name: "password - nothing in state", + secretType: sdk.SecretTypePassword, + stateValue: map[string]string{}, + wantForceNew: true, + }, + { + name: "password - empty value in state", + secretType: sdk.SecretTypePassword, + stateValue: map[string]string{ + "secret_type": "", + }, + wantForceNew: true, + }, + { + name: "password - password in state", + secretType: sdk.SecretTypePassword, + stateValue: map[string]string{ + "secret_type": "PASSWORD", + }, + wantForceNew: false, + }, + { + name: "password - password in state lowercased", + secretType: sdk.SecretTypePassword, + stateValue: map[string]string{ + "secret_type": "password", + }, + wantForceNew: false, + }, + { + name: "password - oauth2 in state", + secretType: sdk.SecretTypePassword, + stateValue: map[string]string{ + "secret_type": "OAUTH2", + }, + wantForceNew: true, + }, + { + name: "password - generic_string in state", + secretType: sdk.SecretTypePassword, + stateValue: map[string]string{ + "secret_type": "GENERIC_STRING", + }, + wantForceNew: true, + }, + { + name: "password - oauth2 in state lowercased", + secretType: sdk.SecretTypePassword, + stateValue: map[string]string{ + "secret_type": "oauth2", + }, + wantForceNew: true, + }, + // generic string type + { + name: "generic_string - nothing in state", + secretType: sdk.SecretTypeGenericString, + stateValue: map[string]string{}, + wantForceNew: true, + }, + { + name: "generic_string - empty value in state", + secretType: sdk.SecretTypeGenericString, + stateValue: map[string]string{ + "secret_type": "", + }, + wantForceNew: true, + }, + { + name: "generic_string - generic_string in state", + secretType: sdk.SecretTypeGenericString, + stateValue: map[string]string{ + "secret_type": "generic_string", + }, + wantForceNew: false, + }, + { + name: "generic_string - generic_string in state lowercased", + secretType: sdk.SecretTypeGenericString, + stateValue: map[string]string{ + "secret_type": "generic_string", + }, + wantForceNew: false, + }, + { + name: "generic_string - oauth2 in state", + secretType: sdk.SecretTypeGenericString, + stateValue: map[string]string{ + "secret_type": "OAUTH2", + }, + wantForceNew: true, + }, + { + name: "generic_string - password in state", + secretType: sdk.SecretTypeGenericString, + stateValue: map[string]string{ + "secret_type": "PASSWORD", + }, + wantForceNew: true, + }, + { + name: "generic_string - oauth2 in state lowercased", + secretType: sdk.SecretTypeGenericString, + stateValue: map[string]string{ + "secret_type": "oauth2", + }, + wantForceNew: true, + }, + // oauth2 authorization code grant type + { + name: "oauth2 authorization code grant - nothing in state", + secretType: sdk.SecretTypeOAuth2AuthorizationCodeGrant, + stateValue: map[string]string{}, + wantForceNew: true, + }, + { + name: "oauth2 authorization code grant - empty value in state", + secretType: sdk.SecretTypeOAuth2AuthorizationCodeGrant, + stateValue: map[string]string{ + "secret_type": "", + }, + wantForceNew: true, + }, + { + name: "oauth2 authorization code grant - password in state", + secretType: sdk.SecretTypeOAuth2AuthorizationCodeGrant, + stateValue: map[string]string{ + "secret_type": "PASSWORD", + }, + wantForceNew: true, + }, + { + name: "oauth2 authorization code grant - generic_string in state", + secretType: sdk.SecretTypeOAuth2AuthorizationCodeGrant, + stateValue: map[string]string{ + "secret_type": "GENERIC_STRING", + }, + wantForceNew: true, + }, + // oauth2 client credentials type + { + name: "oauth2 client credentials - nothing in state", + secretType: sdk.SecretTypeOAuth2ClientCredentials, + stateValue: map[string]string{}, + wantForceNew: true, + }, + { + name: "oauth2 client credentials - empty value in state", + secretType: sdk.SecretTypeOAuth2ClientCredentials, + stateValue: map[string]string{ + "secret_type": "", + }, + wantForceNew: true, + }, + { + name: "oauth2 client credentials - password in state", + secretType: sdk.SecretTypeOAuth2ClientCredentials, + stateValue: map[string]string{ + "secret_type": "PASSWORD", + }, + wantForceNew: true, + }, + { + name: "oauth2 client credentials - generic_string in state", + secretType: sdk.SecretTypeOAuth2ClientCredentials, + stateValue: map[string]string{ + "secret_type": "GENERIC_STRING", + }, + wantForceNew: true, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + customDiff := resources.RecreateWhenSecretTypeChangedExternally(tt.secretType) + testProvider := createProviderWithNamedPropertyAndCustomDiff(t, "secret_type", &schema.Schema{ + Type: schema.TypeString, + Computed: true, + }, customDiff) + diff := calculateDiffFromAttributes( + t, + testProvider, + tt.stateValue, + map[string]any{}, + ) + assert.Equal(t, tt.wantForceNew, diff.RequiresNew()) + }) + } +} + +func Test_RecreateWhenSecretTypeChangedExternallyForOAuth2(t *testing.T) { + tests := []struct { + name string + secretType sdk.SecretType + stateValue map[string]string + wantForceNew bool + }{ + // config - authorization code + // external change - drop and recreate with the same id but as oauth2 with client credentials + { + name: "oauth2 authorization code - oauth2 client credentials in state", + secretType: sdk.SecretTypeOAuth2AuthorizationCodeGrant, + stateValue: map[string]string{ + "secret_type": "OAUTH2", + "describe_output.0.oauth_refresh_token_expiry_time": "", + }, + wantForceNew: true, + }, + // config - client credentials + // external change - drop and recreate with the same id but as oauth2 with authorization code grant + { + name: "oauth2 client credentials - oauth2 authorization code in state", + secretType: sdk.SecretTypeOAuth2ClientCredentials, + stateValue: map[string]string{ + "secret_type": "OAUTH2", + "describe_output.0.oauth_refresh_token_expiry_time": "some test date here", + }, + wantForceNew: true, + }, + // no external change + { + name: "oauth2 authorization code - oauth2 authorization code in state", + secretType: sdk.SecretTypeOAuth2AuthorizationCodeGrant, + stateValue: map[string]string{ + "secret_type": "OAUTH2", + "describe_output.0.oauth_refresh_token_expiry_time": "some test date here", + }, + wantForceNew: false, + }, + // no external change + { + name: "oauth2 client credentials - oauth2 client credentials code in state", + secretType: sdk.SecretTypeOAuth2ClientCredentials, + stateValue: map[string]string{ + "secret_type": "OAUTH2", + "describe_output.0.oauth_refresh_token_expiry_time": "", + }, + wantForceNew: false, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + customDiff := resources.RecreateWhenSecretTypeChangedExternally(tt.secretType) + testProvider := createProviderWithCustomSchemaAndCustomDiff(t, + map[string]*schema.Schema{ + "secret_type": { + Type: schema.TypeString, + Computed: true, + }, + "describe_output": { + Type: schema.TypeList, + Computed: true, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "oauth_refresh_token_expiry_time": { + Type: schema.TypeString, + Computed: true, + }, + }, + }, + }, + }, + customDiff) + diff := calculateDiffFromAttributes( + t, + testProvider, + tt.stateValue, + map[string]any{}, + ) + assert.Equal(t, tt.wantForceNew, diff.RequiresNew()) + }) + } +} diff --git a/pkg/resources/masking_policy.go b/pkg/resources/masking_policy.go index 36739fde0f..965945e708 100644 --- a/pkg/resources/masking_policy.go +++ b/pkg/resources/masking_policy.go @@ -282,7 +282,7 @@ func ReadMaskingPolicy(withExternalChangesMarking bool) schema.ReadContextFunc { if withExternalChangesMarking { if err = handleExternalChangesToObjectInShow(d, - showMapping{"exempt_other_policies", "exempt_other_policies", maskingPolicy.ExemptOtherPolicies, booleanStringFromBool(maskingPolicy.ExemptOtherPolicies), nil}, + outputMapping{"exempt_other_policies", "exempt_other_policies", maskingPolicy.ExemptOtherPolicies, booleanStringFromBool(maskingPolicy.ExemptOtherPolicies), nil}, ); err != nil { return diag.FromErr(err) } diff --git a/pkg/resources/oauth_integration_for_custom_clients.go b/pkg/resources/oauth_integration_for_custom_clients.go index 4462651bf2..9669a37b53 100644 --- a/pkg/resources/oauth_integration_for_custom_clients.go +++ b/pkg/resources/oauth_integration_for_custom_clients.go @@ -456,7 +456,7 @@ func ReadContextOauthIntegrationForCustomClients(withExternalChangesMarking bool if withExternalChangesMarking { if err = handleExternalChangesToObjectInShow(d, - showMapping{"enabled", "enabled", integration.Enabled, booleanStringFromBool(integration.Enabled), nil}, + outputMapping{"enabled", "enabled", integration.Enabled, booleanStringFromBool(integration.Enabled), nil}, ); err != nil { return diag.FromErr(err) } diff --git a/pkg/resources/oauth_integration_for_partner_applications.go b/pkg/resources/oauth_integration_for_partner_applications.go index d92d9d4dee..12c18c62ae 100644 --- a/pkg/resources/oauth_integration_for_partner_applications.go +++ b/pkg/resources/oauth_integration_for_partner_applications.go @@ -336,7 +336,7 @@ func ReadContextOauthIntegrationForPartnerApplications(withExternalChangesMarkin if withExternalChangesMarking { if err = handleExternalChangesToObjectInShow(d, - showMapping{"enabled", "enabled", integration.Enabled, booleanStringFromBool(integration.Enabled), nil}, + outputMapping{"enabled", "enabled", integration.Enabled, booleanStringFromBool(integration.Enabled), nil}, ); err != nil { return diag.FromErr(err) } diff --git a/pkg/resources/resource_monitor.go b/pkg/resources/resource_monitor.go index f2b8d63db0..ae8da17fc2 100644 --- a/pkg/resources/resource_monitor.go +++ b/pkg/resources/resource_monitor.go @@ -267,13 +267,13 @@ func ReadResourceMonitor(withExternalChangesMarking bool) schema.ReadContextFunc if withExternalChangesMarking { if err = handleExternalChangesToObjectInShow(d, - showMapping{"credit_quota", "credit_quota", resourceMonitor.CreditQuota, resourceMonitor.CreditQuota, nil}, - showMapping{"frequency", "frequency", string(resourceMonitor.Frequency), resourceMonitor.Frequency, nil}, - showMapping{"start_time", "start_timestamp", resourceMonitor.StartTime, resourceMonitor.StartTime, nil}, - showMapping{"end_time", "end_timestamp", resourceMonitor.EndTime, resourceMonitor.EndTime, nil}, - showMapping{"notify_at", "notify_triggers", resourceMonitor.NotifyAt, resourceMonitor.NotifyAt, nil}, - showMapping{"suspend_at", "suspend_trigger", resourceMonitor.SuspendAt, resourceMonitor.SuspendAt, nil}, - showMapping{"suspend_immediately_at", "suspend_immediate_trigger", resourceMonitor.SuspendImmediateAt, resourceMonitor.SuspendImmediateAt, nil}, + outputMapping{"credit_quota", "credit_quota", resourceMonitor.CreditQuota, resourceMonitor.CreditQuota, nil}, + outputMapping{"frequency", "frequency", string(resourceMonitor.Frequency), resourceMonitor.Frequency, nil}, + outputMapping{"start_time", "start_timestamp", resourceMonitor.StartTime, resourceMonitor.StartTime, nil}, + outputMapping{"end_time", "end_timestamp", resourceMonitor.EndTime, resourceMonitor.EndTime, nil}, + outputMapping{"notify_at", "notify_triggers", resourceMonitor.NotifyAt, resourceMonitor.NotifyAt, nil}, + outputMapping{"suspend_at", "suspend_trigger", resourceMonitor.SuspendAt, resourceMonitor.SuspendAt, nil}, + outputMapping{"suspend_immediately_at", "suspend_immediate_trigger", resourceMonitor.SuspendImmediateAt, resourceMonitor.SuspendImmediateAt, nil}, ); err != nil { return diag.FromErr(err) } diff --git a/pkg/resources/saml2_integration.go b/pkg/resources/saml2_integration.go index 6712fd23fe..a921b96b7e 100644 --- a/pkg/resources/saml2_integration.go +++ b/pkg/resources/saml2_integration.go @@ -554,7 +554,7 @@ func ReadContextSAML2Integration(withExternalChangesMarking bool) schema.ReadCon if withExternalChangesMarking { if err = handleExternalChangesToObjectInShow(d, - showMapping{"enabled", "enabled", integration.Enabled, booleanStringFromBool(integration.Enabled), nil}, + outputMapping{"enabled", "enabled", integration.Enabled, booleanStringFromBool(integration.Enabled), nil}, ); err != nil { return diag.FromErr(err) } diff --git a/pkg/resources/schema.go b/pkg/resources/schema.go index de3c4e2e48..ff9d85efdb 100644 --- a/pkg/resources/schema.go +++ b/pkg/resources/schema.go @@ -270,10 +270,10 @@ func ReadContextSchema(withExternalChangesMarking bool) schema.ReadContextFunc { if withExternalChangesMarking { if err = handleExternalChangesToObjectInShow(d, - showMapping{"options", "is_transient", schema.IsTransient(), booleanStringFromBool(schema.IsTransient()), func(x any) any { + outputMapping{"options", "is_transient", schema.IsTransient(), booleanStringFromBool(schema.IsTransient()), func(x any) any { return slices.Contains(sdk.ParseCommaSeparatedStringArray(x.(string), false), "TRANSIENT") }}, - showMapping{"options", "with_managed_access", schema.IsManagedAccess(), booleanStringFromBool(schema.IsManagedAccess()), func(x any) any { + outputMapping{"options", "with_managed_access", schema.IsManagedAccess(), booleanStringFromBool(schema.IsManagedAccess()), func(x any) any { return slices.Contains(sdk.ParseCommaSeparatedStringArray(x.(string), false), "MANAGED ACCESS") }}, ); err != nil { diff --git a/pkg/resources/secret_common.go b/pkg/resources/secret_common.go index 95ceebc949..5739ceebcb 100644 --- a/pkg/resources/secret_common.go +++ b/pkg/resources/secret_common.go @@ -33,6 +33,11 @@ var secretCommonSchema = map[string]*schema.Schema{ ForceNew: true, DiffSuppressFunc: suppressIdentifierQuoting, }, + "secret_type": { + Type: schema.TypeString, + Computed: true, + Description: "Specifies a type for the secret. This field is used for checking external changes and recreating the resources if needed.", + }, "comment": { Type: schema.TypeString, Optional: true, @@ -64,8 +69,13 @@ func handleSecretImport(d *schema.ResourceData) error { return nil } -func handleSecretRead(d *schema.ResourceData, id sdk.SchemaObjectIdentifier, secret *sdk.Secret, secretDescription *sdk.SecretDetails) error { +func handleSecretRead(d *schema.ResourceData, + id sdk.SchemaObjectIdentifier, + secret *sdk.Secret, + secretDescription *sdk.SecretDetails, +) error { return errors.Join( + d.Set("secret_type", secret.SecretType), d.Set(FullyQualifiedNameAttributeName, id.FullyQualifiedName()), d.Set("comment", secret.Comment), d.Set(ShowOutputAttributeName, []map[string]any{schemas.SecretToSchema(secret)}), diff --git a/pkg/resources/secret_with_basic_authentication.go b/pkg/resources/secret_with_basic_authentication.go index e4ec6f42fe..595fc6831d 100644 --- a/pkg/resources/secret_with_basic_authentication.go +++ b/pkg/resources/secret_with_basic_authentication.go @@ -42,9 +42,9 @@ func SecretWithBasicAuthentication() *schema.Resource { Description: "Resource used to manage secret objects with Basic Authentication. For more information, check [secret documentation](https://docs.snowflake.com/en/sql-reference/sql/create-secret).", CustomizeDiff: customdiff.All( - ComputedIfAnyAttributeChanged(secretBasicAuthenticationSchema, ShowOutputAttributeName, "name", "comment"), - ComputedIfAnyAttributeChanged(secretBasicAuthenticationSchema, DescribeOutputAttributeName, "name", "username"), - ComputedIfAnyAttributeChanged(secretBasicAuthenticationSchema, FullyQualifiedNameAttributeName, "name"), + ComputedIfAnyAttributeChanged(secretBasicAuthenticationSchema, ShowOutputAttributeName, "comment"), + ComputedIfAnyAttributeChanged(secretBasicAuthenticationSchema, DescribeOutputAttributeName, "username"), + RecreateWhenSecretTypeChangedExternally(sdk.SecretTypePassword), ), Schema: secretBasicAuthenticationSchema, @@ -128,6 +128,7 @@ func ReadContextSecretWithBasicAuthentication(ctx context.Context, d *schema.Res }, } } + secretDescription, err := client.Secrets.Describe(ctx, id) if err != nil { return diag.FromErr(err) @@ -138,6 +139,7 @@ func ReadContextSecretWithBasicAuthentication(ctx context.Context, d *schema.Res if err = d.Set("username", secretDescription.Username); err != nil { return diag.FromErr(err) } + return nil } diff --git a/pkg/resources/secret_with_basic_authentication_acceptance_test.go b/pkg/resources/secret_with_basic_authentication_acceptance_test.go index 5c0aa401cb..3e3b09de19 100644 --- a/pkg/resources/secret_with_basic_authentication_acceptance_test.go +++ b/pkg/resources/secret_with_basic_authentication_acceptance_test.go @@ -57,7 +57,7 @@ func TestAcc_SecretWithBasicAuthentication_BasicFlow(t *testing.T) { resourceshowoutputassert.SecretShowOutput(t, secretName). HasName(name). HasDatabaseName(id.DatabaseName()). - HasSecretType(sdk.SecretTypePassword). + HasSecretType(string(sdk.SecretTypePassword)). HasSchemaName(id.SchemaName()). HasComment(""), ), @@ -67,7 +67,7 @@ func TestAcc_SecretWithBasicAuthentication_BasicFlow(t *testing.T) { resource.TestCheckResourceAttr(secretName, "describe_output.0.name", name), resource.TestCheckResourceAttr(secretName, "describe_output.0.database_name", id.DatabaseName()), resource.TestCheckResourceAttr(secretName, "describe_output.0.schema_name", id.SchemaName()), - resource.TestCheckResourceAttr(secretName, "describe_output.0.secret_type", sdk.SecretTypePassword), + resource.TestCheckResourceAttr(secretName, "describe_output.0.secret_type", string(sdk.SecretTypePassword)), resource.TestCheckResourceAttr(secretName, "describe_output.0.username", "foo"), resource.TestCheckResourceAttr(secretName, "describe_output.0.comment", ""), resource.TestCheckResourceAttr(secretName, "describe_output.0.oauth_access_token_expiry_time", ""), @@ -91,7 +91,7 @@ func TestAcc_SecretWithBasicAuthentication_BasicFlow(t *testing.T) { HasCommentString(comment), resourceshowoutputassert.SecretShowOutput(t, secretName). - HasSecretType(sdk.SecretTypePassword). + HasSecretType(string(sdk.SecretTypePassword)). HasComment(comment), ), @@ -225,3 +225,54 @@ func TestAcc_SecretWithBasicAuthentication_CreateWithEmptyCredentials(t *testing }, }) } + +func TestAcc_SecretWithBasicAuthentication_ExternalSecretTypeChange(t *testing.T) { + id := acc.TestClient().Ids.RandomSchemaObjectIdentifier() + name := id.Name() + secretModel := model.SecretWithBasicAuthentication("s", id.DatabaseName(), name, "test_pswd", id.SchemaName(), "test_usr") + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + CheckDestroy: acc.CheckDestroy(t, resources.SecretWithBasicAuthentication), + Steps: []resource.TestStep{ + // create + { + Config: config.FromModel(t, secretModel), + Check: resource.ComposeTestCheckFunc( + assert.AssertThat(t, + resourceassert.SecretWithBasicAuthenticationResource(t, secretModel.ResourceReference()). + HasSecretTypeString(string(sdk.SecretTypePassword)), + resourceshowoutputassert.SecretShowOutput(t, secretModel.ResourceReference()). + HasSecretType(string(sdk.SecretTypePassword)), + ), + ), + }, + // create or replace with different secret type + { + PreConfig: func() { + acc.TestClient().Secret.DropFunc(t, id)() + _, cleanup := acc.TestClient().Secret.CreateWithGenericString(t, id, "test_secret_string") + t.Cleanup(cleanup) + }, + Config: config.FromModel(t, secretModel), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(secretModel.ResourceReference(), plancheck.ResourceActionDestroyBeforeCreate), + }, + }, + Check: resource.ComposeTestCheckFunc( + assert.AssertThat(t, + resourceassert.SecretWithBasicAuthenticationResource(t, secretModel.ResourceReference()). + HasSecretTypeString(string(sdk.SecretTypePassword)), + resourceshowoutputassert.SecretShowOutput(t, secretModel.ResourceReference()). + HasSecretType(string(sdk.SecretTypePassword)), + ), + ), + }, + }, + }) +} diff --git a/pkg/resources/secret_with_generic_string.go b/pkg/resources/secret_with_generic_string.go index 42c2fc6f92..ca9dfe55d6 100644 --- a/pkg/resources/secret_with_generic_string.go +++ b/pkg/resources/secret_with_generic_string.go @@ -36,9 +36,9 @@ func SecretWithGenericString() *schema.Resource { Description: "Resource used to manage secret objects with Generic String. For more information, check [secret documentation](https://docs.snowflake.com/en/sql-reference/sql/create-secret).", CustomizeDiff: customdiff.All( - ComputedIfAnyAttributeChanged(secretGenericStringSchema, DescribeOutputAttributeName, "name"), - ComputedIfAnyAttributeChanged(secretGenericStringSchema, ShowOutputAttributeName, "name", "comment"), - ComputedIfAnyAttributeChanged(secretGenericStringSchema, FullyQualifiedNameAttributeName, "name"), + ComputedIfAnyAttributeChanged(secretGenericStringSchema, ShowOutputAttributeName, "comment"), + ComputedIfAnyAttributeChanged(secretGenericStringSchema, DescribeOutputAttributeName), + RecreateWhenSecretTypeChangedExternally(sdk.SecretTypeGenericString), ), Schema: secretGenericStringSchema, diff --git a/pkg/resources/secret_with_generic_string_acceptance_test.go b/pkg/resources/secret_with_generic_string_acceptance_test.go index d2eb8a54cf..8f57a8924d 100644 --- a/pkg/resources/secret_with_generic_string_acceptance_test.go +++ b/pkg/resources/secret_with_generic_string_acceptance_test.go @@ -54,7 +54,7 @@ func TestAcc_SecretWithGenericString_BasicFlow(t *testing.T) { resourceshowoutputassert.SecretShowOutput(t, secretModel.ResourceReference()). HasName(name). HasDatabaseName(id.DatabaseName()). - HasSecretType(sdk.SecretTypeGenericString). + HasSecretType(string(sdk.SecretTypeGenericString)). HasSchemaName(id.SchemaName()). HasComment(""), ), @@ -64,7 +64,7 @@ func TestAcc_SecretWithGenericString_BasicFlow(t *testing.T) { resource.TestCheckResourceAttr(secretName, "describe_output.0.name", name), resource.TestCheckResourceAttr(secretName, "describe_output.0.database_name", id.DatabaseName()), resource.TestCheckResourceAttr(secretName, "describe_output.0.schema_name", id.SchemaName()), - resource.TestCheckResourceAttr(secretName, "describe_output.0.secret_type", sdk.SecretTypeGenericString), + resource.TestCheckResourceAttr(secretName, "describe_output.0.secret_type", string(sdk.SecretTypeGenericString)), resource.TestCheckResourceAttr(secretName, "describe_output.0.username", ""), resource.TestCheckResourceAttr(secretName, "describe_output.0.comment", ""), resource.TestCheckResourceAttr(secretName, "describe_output.0.oauth_access_token_expiry_time", ""), @@ -90,7 +90,7 @@ func TestAcc_SecretWithGenericString_BasicFlow(t *testing.T) { HasCommentString(comment), resourceshowoutputassert.SecretShowOutput(t, secretModel.ResourceReference()). - HasSecretType(sdk.SecretTypeGenericString). + HasSecretType(string(sdk.SecretTypeGenericString)). HasComment(comment), ), @@ -185,3 +185,54 @@ func TestAcc_SecretWithGenericString_BasicFlow(t *testing.T) { }, }) } + +func TestAcc_SecretWithGenericString_ExternalSecretTypeChange(t *testing.T) { + id := acc.TestClient().Ids.RandomSchemaObjectIdentifier() + name := id.Name() + secretModel := model.SecretWithGenericString("s", id.DatabaseName(), name, id.SchemaName(), "test_usr") + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + CheckDestroy: acc.CheckDestroy(t, resources.SecretWithGenericString), + Steps: []resource.TestStep{ + // create + { + Config: config.FromModel(t, secretModel), + Check: resource.ComposeTestCheckFunc( + assert.AssertThat(t, + resourceassert.SecretWithGenericStringResource(t, secretModel.ResourceReference()). + HasSecretTypeString(string(sdk.SecretTypeGenericString)), + resourceshowoutputassert.SecretShowOutput(t, secretModel.ResourceReference()). + HasSecretType(string(sdk.SecretTypeGenericString)), + ), + ), + }, + // create or replace with different secret type + { + PreConfig: func() { + acc.TestClient().Secret.DropFunc(t, id)() + _, cleanup := acc.TestClient().Secret.CreateWithBasicAuthenticationFlow(t, id, "test_pswd", "test_usr") + t.Cleanup(cleanup) + }, + Config: config.FromModel(t, secretModel), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(secretModel.ResourceReference(), plancheck.ResourceActionDestroyBeforeCreate), + }, + }, + Check: resource.ComposeTestCheckFunc( + assert.AssertThat(t, + resourceassert.SecretWithGenericStringResource(t, secretModel.ResourceReference()). + HasSecretTypeString(string(sdk.SecretTypeGenericString)), + resourceshowoutputassert.SecretShowOutput(t, secretModel.ResourceReference()). + HasSecretType(string(sdk.SecretTypeGenericString)), + ), + ), + }, + }, + }) +} diff --git a/pkg/resources/secret_with_oauth_authorization_code_grant.go b/pkg/resources/secret_with_oauth_authorization_code_grant.go index 97025a1bc8..1797d51e8c 100644 --- a/pkg/resources/secret_with_oauth_authorization_code_grant.go +++ b/pkg/resources/secret_with_oauth_authorization_code_grant.go @@ -54,9 +54,9 @@ func SecretWithAuthorizationCodeGrant() *schema.Resource { }, CustomizeDiff: customdiff.All( - ComputedIfAnyAttributeChanged(secretAuthorizationCodeGrantSchema, DescribeOutputAttributeName, "name", "oauth_refresh_token_expiry_time", "api_authentication"), - ComputedIfAnyAttributeChanged(secretAuthorizationCodeGrantSchema, ShowOutputAttributeName, "name", "comment"), - ComputedIfAnyAttributeChanged(secretAuthorizationCodeGrantSchema, FullyQualifiedNameAttributeName, "name"), + ComputedIfAnyAttributeChanged(secretAuthorizationCodeGrantSchema, ShowOutputAttributeName, "comment"), + ComputedIfAnyAttributeChanged(secretAuthorizationCodeGrantSchema, DescribeOutputAttributeName, "oauth_refresh_token_expiry_time", "api_authentication"), + RecreateWhenSecretTypeChangedExternally(sdk.SecretTypeOAuth2AuthorizationCodeGrant), ), } } @@ -141,32 +141,27 @@ func ReadContextSecretWithAuthorizationCodeGrant(withExternalChangesMarking bool }, } } + secretDescription, err := client.Secrets.Describe(ctx, id) if err != nil { return diag.FromErr(err) } - if withExternalChangesMarking { - if err = handleExternalValueChangesToObjectInDescribe(d, - describeMapping{"oauth_refresh_token_expiry_time", "oauth_refresh_token_expiry_time", secretDescription.OauthRefreshTokenExpiryTime.String(), secretDescription.OauthRefreshTokenExpiryTime.String(), nil}, + // if secret type is changed externally, we wont be able to read oauth_refresh_token_expiry_time value (since it will not be provided) + // in any other case, there should be oauth_refresh_token_expiry_time value since it is required + if withExternalChangesMarking && secretDescription.OauthRefreshTokenExpiryTime != nil { + if err = handleExternalChangesToObjectInFlatDescribe(d, + outputMapping{"oauth_refresh_token_expiry_time", "oauth_refresh_token_expiry_time", secretDescription.OauthRefreshTokenExpiryTime.String(), secretDescription.OauthRefreshTokenExpiryTime.String(), nil}, ); err != nil { return diag.FromErr(err) } } - if err = setStateToValuesFromConfig(d, secretAuthorizationCodeGrantSchema, []string{"oauth_refresh_token_expiry_time"}); err != nil { - return diag.FromErr(err) - } - - if err = d.Set("api_authentication", secretDescription.IntegrationName); err != nil { - return diag.FromErr(err) - } - - if err := handleSecretRead(d, id, secret, secretDescription); err != nil { - return diag.FromErr(err) - } - - return nil + return diag.FromErr(errors.Join( + handleSecretRead(d, id, secret, secretDescription), + setStateToValuesFromConfig(d, secretAuthorizationCodeGrantSchema, []string{"oauth_refresh_token_expiry_time"}), + d.Set("api_authentication", secretDescription.IntegrationName), + )) } } diff --git a/pkg/resources/secret_with_oauth_authorization_code_grant_acceptance_test.go b/pkg/resources/secret_with_oauth_authorization_code_grant_acceptance_test.go index b8f6373a46..82d8c163aa 100644 --- a/pkg/resources/secret_with_oauth_authorization_code_grant_acceptance_test.go +++ b/pkg/resources/secret_with_oauth_authorization_code_grant_acceptance_test.go @@ -56,7 +56,7 @@ func TestAcc_SecretWithAuthorizationCodeGrant_BasicFlow(t *testing.T) { Config: config.FromModel(t, secretModel), Check: resource.ComposeTestCheckFunc( assert.AssertThat(t, - resourceassert.SecretWithAuthorizationCodeResource(t, secretModel.ResourceReference()). + resourceassert.SecretWithAuthorizationCodeGrantResource(t, secretModel.ResourceReference()). HasNameString(name). HasDatabaseString(id.DatabaseName()). HasSchemaString(id.SchemaName()). @@ -68,7 +68,7 @@ func TestAcc_SecretWithAuthorizationCodeGrant_BasicFlow(t *testing.T) { resourceshowoutputassert.SecretShowOutput(t, secretModel.ResourceReference()). HasName(name). HasDatabaseName(id.DatabaseName()). - HasSecretType(sdk.SecretTypeOAuth2). + HasSecretType(string(sdk.SecretTypeOAuth2)). HasSchemaName(id.SchemaName()). HasComment(""), ), @@ -78,7 +78,7 @@ func TestAcc_SecretWithAuthorizationCodeGrant_BasicFlow(t *testing.T) { resource.TestCheckResourceAttr(secretName, "describe_output.0.name", name), resource.TestCheckResourceAttr(secretName, "describe_output.0.database_name", id.DatabaseName()), resource.TestCheckResourceAttr(secretName, "describe_output.0.schema_name", id.SchemaName()), - resource.TestCheckResourceAttr(secretName, "describe_output.0.secret_type", sdk.SecretTypeOAuth2), + resource.TestCheckResourceAttr(secretName, "describe_output.0.secret_type", string(sdk.SecretTypeOAuth2)), resource.TestCheckResourceAttr(secretName, "describe_output.0.integration_name", integrationId.Name()), resource.TestCheckResourceAttr(secretName, "describe_output.0.username", ""), resource.TestCheckResourceAttr(secretName, "describe_output.0.oauth_access_token_expiry_time", ""), @@ -99,7 +99,7 @@ func TestAcc_SecretWithAuthorizationCodeGrant_BasicFlow(t *testing.T) { }, Check: resource.ComposeTestCheckFunc( assert.AssertThat(t, - resourceassert.SecretWithAuthorizationCodeResource(t, secretName). + resourceassert.SecretWithAuthorizationCodeGrantResource(t, secretName). HasNameString(name). HasDatabaseString(id.DatabaseName()). HasSchemaString(id.SchemaName()). @@ -109,7 +109,7 @@ func TestAcc_SecretWithAuthorizationCodeGrant_BasicFlow(t *testing.T) { HasCommentString(comment), resourceshowoutputassert.SecretShowOutput(t, secretModel.ResourceReference()). - HasSecretType(sdk.SecretTypeOAuth2). + HasSecretType(string(sdk.SecretTypeOAuth2)). HasComment(comment), ), resource.TestCheckResourceAttrSet(secretName, "describe_output.0.oauth_refresh_token_expiry_time"), @@ -138,7 +138,7 @@ func TestAcc_SecretWithAuthorizationCodeGrant_BasicFlow(t *testing.T) { }, Check: resource.ComposeTestCheckFunc( assert.AssertThat(t, - resourceassert.SecretWithAuthorizationCodeResource(t, secretName). + resourceassert.SecretWithAuthorizationCodeGrantResource(t, secretName). HasNameString(name). HasDatabaseString(id.DatabaseName()). HasSchemaString(id.SchemaName()). @@ -157,7 +157,7 @@ func TestAcc_SecretWithAuthorizationCodeGrant_BasicFlow(t *testing.T) { ImportStateVerify: true, ImportStateVerifyIgnore: []string{"oauth_refresh_token"}, ImportStateCheck: assert.AssertThatImport(t, - resourceassert.ImportedSecretWithAuthorizationCodeResource(t, helpers.EncodeResourceIdentifier(id)). + resourceassert.ImportedSecretWithAuthorizationCodeGrantResource(t, helpers.EncodeResourceIdentifier(id)). HasNameString(id.Name()). HasDatabaseString(id.DatabaseName()). HasSchemaString(id.SchemaName()). @@ -203,7 +203,7 @@ func TestAcc_SecretWithAuthorizationCodeGrant_DifferentTimeFormats(t *testing.T) Config: config.FromModel(t, secretModelDateOnly), Check: resource.ComposeTestCheckFunc( assert.AssertThat(t, - resourceassert.SecretWithAuthorizationCodeResource(t, secretModelDateOnly.ResourceReference()). + resourceassert.SecretWithAuthorizationCodeGrantResource(t, secretModelDateOnly.ResourceReference()). HasOauthRefreshTokenExpiryTimeString(refreshTokenExpiryDateOnly), assert.Check(resource.TestCheckResourceAttrSet(secretModelDateOnly.ResourceReference(), "describe_output.0.oauth_refresh_token_expiry_time")), ), @@ -214,7 +214,7 @@ func TestAcc_SecretWithAuthorizationCodeGrant_DifferentTimeFormats(t *testing.T) Config: config.FromModel(t, secretModelWithoutSeconds), Check: resource.ComposeTestCheckFunc( assert.AssertThat(t, - resourceassert.SecretWithAuthorizationCodeResource(t, secretModelWithoutSeconds.ResourceReference()). + resourceassert.SecretWithAuthorizationCodeGrantResource(t, secretModelWithoutSeconds.ResourceReference()). HasOauthRefreshTokenExpiryTimeString(refreshTokenExpiryWithoutSeconds), assert.Check(resource.TestCheckResourceAttrSet(secretModelWithoutSeconds.ResourceReference(), "describe_output.0.oauth_refresh_token_expiry_time")), ), @@ -225,7 +225,7 @@ func TestAcc_SecretWithAuthorizationCodeGrant_DifferentTimeFormats(t *testing.T) Config: config.FromModel(t, secretModelDateTime), Check: resource.ComposeTestCheckFunc( assert.AssertThat(t, - resourceassert.SecretWithAuthorizationCodeResource(t, secretModelDateTime.ResourceReference()). + resourceassert.SecretWithAuthorizationCodeGrantResource(t, secretModelDateTime.ResourceReference()). HasOauthRefreshTokenExpiryTimeString(refreshTokenExpiryDateTime), assert.Check(resource.TestCheckResourceAttrSet(secretModelDateTime.ResourceReference(), "describe_output.0.oauth_refresh_token_expiry_time")), ), @@ -236,7 +236,7 @@ func TestAcc_SecretWithAuthorizationCodeGrant_DifferentTimeFormats(t *testing.T) Config: config.FromModel(t, secretModelWithPDT), Check: resource.ComposeTestCheckFunc( assert.AssertThat(t, - resourceassert.SecretWithAuthorizationCodeResource(t, secretModelWithPDT.ResourceReference()). + resourceassert.SecretWithAuthorizationCodeGrantResource(t, secretModelWithPDT.ResourceReference()). HasOauthRefreshTokenExpiryTimeString(refreshTokenExpiryWithPDT), assert.Check(resource.TestCheckResourceAttrSet(secretModelWithPDT.ResourceReference(), "describe_output.0.oauth_refresh_token_expiry_time")), ), @@ -246,7 +246,7 @@ func TestAcc_SecretWithAuthorizationCodeGrant_DifferentTimeFormats(t *testing.T) }) } -func TestAcc_SecretWithAuthorizationCodeGrant_ExternalChange(t *testing.T) { +func TestAcc_SecretWithAuthorizationCodeGrant_ExternalRefreshTokenExpiryTimeChange(t *testing.T) { id := acc.TestClient().Ids.RandomSchemaObjectIdentifier() name := id.Name() comment := random.Comment() @@ -275,7 +275,7 @@ func TestAcc_SecretWithAuthorizationCodeGrant_ExternalChange(t *testing.T) { Config: config.FromModel(t, secretModel), Check: resource.ComposeTestCheckFunc( assert.AssertThat(t, - resourceassert.SecretWithAuthorizationCodeResource(t, secretModel.ResourceReference()). + resourceassert.SecretWithAuthorizationCodeGrantResource(t, secretModel.ResourceReference()). HasNameString(name). HasDatabaseString(id.DatabaseName()). HasSchemaString(id.SchemaName()). @@ -287,7 +287,7 @@ func TestAcc_SecretWithAuthorizationCodeGrant_ExternalChange(t *testing.T) { resourceshowoutputassert.SecretShowOutput(t, secretModel.ResourceReference()). HasName(name). HasDatabaseName(id.DatabaseName()). - HasSecretType(sdk.SecretTypeOAuth2). + HasSecretType(string(sdk.SecretTypeOAuth2)). HasSchemaName(id.SchemaName()). HasComment(comment), ), @@ -314,7 +314,7 @@ func TestAcc_SecretWithAuthorizationCodeGrant_ExternalChange(t *testing.T) { }, Check: resource.ComposeTestCheckFunc( assert.AssertThat(t, - resourceassert.SecretWithAuthorizationCodeResource(t, secretModel.ResourceReference()). + resourceassert.SecretWithAuthorizationCodeGrantResource(t, secretModel.ResourceReference()). HasOauthRefreshTokenExpiryTimeString(refreshTokenExpiryDateTime), assert.Check(resource.TestCheckResourceAttrSet(secretModel.ResourceReference(), "describe_output.0.oauth_refresh_token_expiry_time")), ), @@ -323,3 +323,124 @@ func TestAcc_SecretWithAuthorizationCodeGrant_ExternalChange(t *testing.T) { }, }) } + +func TestAcc_SecretWithAuthorizationCodeGrant_ExternalSecretTypeChange(t *testing.T) { + id := acc.TestClient().Ids.RandomSchemaObjectIdentifier() + name := id.Name() + + integrationId := acc.TestClient().Ids.RandomAccountObjectIdentifier() + _, apiIntegrationCleanup := acc.TestClient().SecurityIntegration.CreateApiAuthenticationClientCredentialsWithRequest(t, + sdk.NewCreateApiAuthenticationWithClientCredentialsFlowSecurityIntegrationRequest(integrationId, true, "foo", "foo"), + ) + t.Cleanup(apiIntegrationCleanup) + + secretModel := model.SecretWithAuthorizationCodeGrant("s", integrationId.Name(), id.DatabaseName(), id.SchemaName(), name, "test_refresh_token", time.Now().Add(24*time.Hour).Format(time.DateOnly)) + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + CheckDestroy: acc.CheckDestroy(t, resources.SecretWithAuthorizationCodeGrant), + Steps: []resource.TestStep{ + // create + { + Config: config.FromModel(t, secretModel), + Check: resource.ComposeTestCheckFunc( + assert.AssertThat(t, + resourceassert.SecretWithAuthorizationCodeGrantResource(t, secretModel.ResourceReference()). + HasSecretTypeString(string(sdk.SecretTypeOAuth2)), + resourceshowoutputassert.SecretShowOutput(t, secretModel.ResourceReference()). + HasSecretType(string(sdk.SecretTypeOAuth2)), + ), + ), + }, + // create or replace with different secret type + { + PreConfig: func() { + acc.TestClient().Secret.DropFunc(t, id)() + _, cleanup := acc.TestClient().Secret.CreateWithBasicAuthenticationFlow(t, id, "test_pswd", "test_usr") + t.Cleanup(cleanup) + }, + Config: config.FromModel(t, secretModel), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(secretModel.ResourceReference(), plancheck.ResourceActionDestroyBeforeCreate), + }, + }, + Check: resource.ComposeTestCheckFunc( + assert.AssertThat(t, + resourceassert.SecretWithAuthorizationCodeGrantResource(t, secretModel.ResourceReference()). + HasSecretTypeString(string(sdk.SecretTypeOAuth2)), + resourceshowoutputassert.SecretShowOutput(t, secretModel.ResourceReference()). + HasSecretType(string(sdk.SecretTypeOAuth2)), + ), + ), + }, + }, + }) +} + +func TestAcc_SecretWithAuthorizationCodeGrant_ExternalSecretTypeChangeToOAuthClientCredentials(t *testing.T) { + id := acc.TestClient().Ids.RandomSchemaObjectIdentifier() + name := id.Name() + + integrationId := acc.TestClient().Ids.RandomAccountObjectIdentifier() + _, apiIntegrationCleanup := acc.TestClient().SecurityIntegration.CreateApiAuthenticationClientCredentialsWithRequest(t, + sdk.NewCreateApiAuthenticationWithClientCredentialsFlowSecurityIntegrationRequest(integrationId, true, "test_client_id", "test_client_secret"). + WithOauthAllowedScopes([]sdk.AllowedScope{{Scope: "foo"}, {Scope: "bar"}, {Scope: "test"}}), + ) + t.Cleanup(apiIntegrationCleanup) + + secretModel := model.SecretWithAuthorizationCodeGrant("s", integrationId.Name(), id.DatabaseName(), id.SchemaName(), name, "test_refresh_token", time.Now().Add(24*time.Hour).Format(time.DateOnly)) + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + CheckDestroy: acc.CheckDestroy(t, resources.SecretWithClientCredentials), + Steps: []resource.TestStep{ + // create + { + Config: config.FromModel(t, secretModel), + Check: resource.ComposeTestCheckFunc( + assert.AssertThat(t, + resourceassert.SecretWithAuthorizationCodeGrantResource(t, secretModel.ResourceReference()). + HasSecretTypeString(string(sdk.SecretTypeOAuth2)), + resourceshowoutputassert.SecretShowOutput(t, secretModel.ResourceReference()). + HasSecretType(string(sdk.SecretTypeOAuth2)), + ), + resource.TestCheckResourceAttrSet(secretModel.ResourceReference(), "describe_output.0.oauth_refresh_token_expiry_time"), + resource.TestCheckResourceAttr(secretModel.ResourceReference(), "describe_output.0.oauth_scopes.#", "0"), + ), + }, + // create or replace with same secret type, but different create flow + { + PreConfig: func() { + acc.TestClient().Secret.DropFunc(t, id)() + _, cleanup := acc.TestClient().Secret.CreateWithOAuthClientCredentialsFlow(t, id, integrationId, []sdk.ApiIntegrationScope{}) + t.Cleanup(cleanup) + }, + Config: config.FromModel(t, secretModel), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(secretModel.ResourceReference(), plancheck.ResourceActionDestroyBeforeCreate), + }, + }, + Check: resource.ComposeTestCheckFunc( + assert.AssertThat(t, + resourceassert.SecretWithAuthorizationCodeGrantResource(t, secretModel.ResourceReference()). + HasSecretTypeString(string(sdk.SecretTypeOAuth2)), + resourceshowoutputassert.SecretShowOutput(t, secretModel.ResourceReference()). + HasSecretType(string(sdk.SecretTypeOAuth2)), + ), + resource.TestCheckResourceAttrSet(secretModel.ResourceReference(), "describe_output.0.oauth_refresh_token_expiry_time"), + resource.TestCheckResourceAttr(secretModel.ResourceReference(), "describe_output.0.oauth_scopes.#", "0"), + ), + }, + }, + }) +} diff --git a/pkg/resources/secret_with_oauth_client_credentials.go b/pkg/resources/secret_with_oauth_client_credentials.go index 1a387efe0e..866083688d 100644 --- a/pkg/resources/secret_with_oauth_client_credentials.go +++ b/pkg/resources/secret_with_oauth_client_credentials.go @@ -43,9 +43,9 @@ func SecretWithClientCredentials() *schema.Resource { Description: "Resource used to manage secret objects with OAuth Client Credentials. For more information, check [secret documentation](https://docs.snowflake.com/en/sql-reference/sql/create-secret).", CustomizeDiff: customdiff.All( - ComputedIfAnyAttributeChanged(secretClientCredentialsSchema, DescribeOutputAttributeName, "name", "oauth_scopes", "api_authentication"), - ComputedIfAnyAttributeChanged(secretClientCredentialsSchema, ShowOutputAttributeName, "name", "comment"), - ComputedIfAnyAttributeChanged(secretClientCredentialsSchema, FullyQualifiedNameAttributeName, "name"), + ComputedIfAnyAttributeChanged(secretClientCredentialsSchema, DescribeOutputAttributeName, "oauth_scopes", "api_authentication"), + ComputedIfAnyAttributeChanged(secretClientCredentialsSchema, ShowOutputAttributeName, "comment"), + RecreateWhenSecretTypeChangedExternally(sdk.SecretTypeOAuth2ClientCredentials), ), Schema: secretClientCredentialsSchema, diff --git a/pkg/resources/secret_with_oauth_client_credentials_acceptance_test.go b/pkg/resources/secret_with_oauth_client_credentials_acceptance_test.go index 265f479d1c..69a46330f2 100644 --- a/pkg/resources/secret_with_oauth_client_credentials_acceptance_test.go +++ b/pkg/resources/secret_with_oauth_client_credentials_acceptance_test.go @@ -2,6 +2,7 @@ package resources_test import ( "testing" + "time" acc "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/bettertestspoc/assert" @@ -64,7 +65,7 @@ func TestAcc_SecretWithClientCredentials_BasicFlow(t *testing.T) { resourceshowoutputassert.SecretShowOutput(t, secretModel.ResourceReference()). HasName(name). HasDatabaseName(id.DatabaseName()). - HasSecretType(sdk.SecretTypeOAuth2). + HasSecretType(string(sdk.SecretTypeOAuth2)). HasSchemaName(id.SchemaName()), ), resource.TestCheckResourceAttr(secretName, "oauth_scopes.#", "2"), @@ -107,7 +108,7 @@ func TestAcc_SecretWithClientCredentials_BasicFlow(t *testing.T) { assert.Check(resource.TestCheckTypeSetElemAttr(secretName, "oauth_scopes.*", "test")), resourceshowoutputassert.SecretShowOutput(t, secretModel.ResourceReference()). - HasSecretType(sdk.SecretTypeOAuth2). + HasSecretType(string(sdk.SecretTypeOAuth2)). HasComment(newComment), ), @@ -297,3 +298,129 @@ func TestAcc_SecretWithClientCredentials_EmptyScopesList(t *testing.T) { }, }) } + +func TestAcc_SecretWithClientCredentials_ExternalSecretTypeChange(t *testing.T) { + id := acc.TestClient().Ids.RandomSchemaObjectIdentifier() + name := id.Name() + + integrationId := acc.TestClient().Ids.RandomAccountObjectIdentifier() + _, apiIntegrationCleanup := acc.TestClient().SecurityIntegration.CreateApiAuthenticationClientCredentialsWithRequest(t, + sdk.NewCreateApiAuthenticationWithClientCredentialsFlowSecurityIntegrationRequest(integrationId, true, "test_client_id", "test_client_secret"). + WithOauthAllowedScopes([]sdk.AllowedScope{{Scope: "foo"}, {Scope: "bar"}, {Scope: "test"}}), + ) + t.Cleanup(apiIntegrationCleanup) + + secretModel := model.SecretWithClientCredentials("s", integrationId.Name(), id.DatabaseName(), id.SchemaName(), name, []string{"foo", "bar"}) + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + CheckDestroy: acc.CheckDestroy(t, resources.SecretWithClientCredentials), + Steps: []resource.TestStep{ + // create + { + Config: config.FromModel(t, secretModel), + Check: resource.ComposeTestCheckFunc( + assert.AssertThat(t, + resourceassert.SecretWithClientCredentialsResource(t, secretModel.ResourceReference()). + HasSecretTypeString(string(sdk.SecretTypeOAuth2)), + resourceshowoutputassert.SecretShowOutput(t, secretModel.ResourceReference()). + HasSecretType(string(sdk.SecretTypeOAuth2)), + ), + ), + }, + // create or replace with different secret type + { + PreConfig: func() { + acc.TestClient().Secret.DropFunc(t, id)() + _, cleanup := acc.TestClient().Secret.CreateWithGenericString(t, id, "test_secret_string") + t.Cleanup(cleanup) + }, + Config: config.FromModel(t, secretModel), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(secretModel.ResourceReference(), plancheck.ResourceActionDestroyBeforeCreate), + }, + }, + Check: resource.ComposeTestCheckFunc( + assert.AssertThat(t, + resourceassert.SecretWithClientCredentialsResource(t, secretModel.ResourceReference()). + HasSecretTypeString(string(sdk.SecretTypeOAuth2)), + resourceshowoutputassert.SecretShowOutput(t, secretModel.ResourceReference()). + HasSecretType(string(sdk.SecretTypeOAuth2)), + ), + ), + }, + }, + }) +} + +func TestAcc_SecretWithClientCredentials_ExternalSecretTypeChangeToOAuthAuthCodeGrant(t *testing.T) { + id := acc.TestClient().Ids.RandomSchemaObjectIdentifier() + name := id.Name() + + integrationId := acc.TestClient().Ids.RandomAccountObjectIdentifier() + _, apiIntegrationCleanup := acc.TestClient().SecurityIntegration.CreateApiAuthenticationClientCredentialsWithRequest(t, + sdk.NewCreateApiAuthenticationWithClientCredentialsFlowSecurityIntegrationRequest(integrationId, true, "test_client_id", "test_client_secret"). + WithOauthAllowedScopes([]sdk.AllowedScope{{Scope: "foo"}, {Scope: "bar"}, {Scope: "test"}}), + ) + t.Cleanup(apiIntegrationCleanup) + + secretModel := model.SecretWithClientCredentials("s", integrationId.Name(), id.DatabaseName(), id.SchemaName(), name, []string{"foo", "bar"}) + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + CheckDestroy: acc.CheckDestroy(t, resources.SecretWithClientCredentials), + Steps: []resource.TestStep{ + // create + { + Config: config.FromModel(t, secretModel), + Check: resource.ComposeTestCheckFunc( + assert.AssertThat(t, + resourceassert.SecretWithClientCredentialsResource(t, secretModel.ResourceReference()). + HasSecretTypeString(string(sdk.SecretTypeOAuth2)), + resourceshowoutputassert.SecretShowOutput(t, secretModel.ResourceReference()). + HasSecretType(string(sdk.SecretTypeOAuth2)), + ), + resource.TestCheckResourceAttr(secretModel.ResourceReference(), "describe_output.0.oauth_scopes.#", "2"), + resource.TestCheckTypeSetElemAttr(secretModel.ResourceReference(), "describe_output.0.oauth_scopes.*", "foo"), + resource.TestCheckTypeSetElemAttr(secretModel.ResourceReference(), "describe_output.0.oauth_scopes.*", "bar"), + resource.TestCheckResourceAttr(secretModel.ResourceReference(), "describe_output.0.oauth_refresh_token_expiry_time", ""), + ), + }, + // create or replace with the same secret type but different flow + { + PreConfig: func() { + acc.TestClient().Secret.DropFunc(t, id)() + _, cleanup := acc.TestClient().Secret.CreateWithOAuthAuthorizationCodeFlow(t, id, integrationId, "test_refresh_token", time.Now().Add(24*time.Hour).Format(time.DateOnly)) + t.Cleanup(cleanup) + }, + Config: config.FromModel(t, secretModel), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction(secretModel.ResourceReference(), plancheck.ResourceActionDestroyBeforeCreate), + }, + }, + Check: resource.ComposeTestCheckFunc( + assert.AssertThat(t, + resourceassert.SecretWithClientCredentialsResource(t, secretModel.ResourceReference()). + HasSecretTypeString(string(sdk.SecretTypeOAuth2)), + resourceshowoutputassert.SecretShowOutput(t, secretModel.ResourceReference()). + HasSecretType(string(sdk.SecretTypeOAuth2)), + ), + resource.TestCheckResourceAttr(secretModel.ResourceReference(), "describe_output.0.oauth_scopes.#", "2"), + resource.TestCheckTypeSetElemAttr(secretModel.ResourceReference(), "describe_output.0.oauth_scopes.*", "foo"), + resource.TestCheckTypeSetElemAttr(secretModel.ResourceReference(), "describe_output.0.oauth_scopes.*", "bar"), + resource.TestCheckResourceAttr(secretModel.ResourceReference(), "describe_output.0.oauth_refresh_token_expiry_time", ""), + ), + }, + }, + }) +} diff --git a/pkg/resources/show_and_describe_handlers.go b/pkg/resources/show_and_describe_handlers.go index b915b82345..fbbf8e49ca 100644 --- a/pkg/resources/show_and_describe_handlers.go +++ b/pkg/resources/show_and_describe_handlers.go @@ -13,14 +13,13 @@ const ( RelatedParametersAttributeName = "related_parameters" ) -// handleExternalChangesToObjectInShow assumes that show output is kept in ShowOutputAttributeName attribute -func handleExternalChangesToObjectInShow(d *schema.ResourceData, mappings ...showMapping) error { - if showOutput, ok := d.GetOk(ShowOutputAttributeName); ok { - showOutputList := showOutput.([]any) - if len(showOutputList) == 1 { - result := showOutputList[0].(map[string]any) +func handleExternalChangesToObject(d *schema.ResourceData, outputAttributeName string, mappings ...outputMapping) error { + if output, ok := d.GetOk(outputAttributeName); ok { + outputList := output.([]any) + if len(outputList) == 1 { + result := outputList[0].(map[string]any) for _, mapping := range mappings { - valueToCompareFrom := result[mapping.nameInShow] + valueToCompareFrom := result[mapping.nameInOutput] if mapping.normalizeFunc != nil { valueToCompareFrom = mapping.normalizeFunc(valueToCompareFrom) } @@ -35,36 +34,26 @@ func handleExternalChangesToObjectInShow(d *schema.ResourceData, mappings ...sho return nil } -type showMapping struct { - nameInShow string +// handleExternalChangesToObjectInShow assumes that show output is kept in ShowOutputAttributeName attribute +func handleExternalChangesToObjectInShow(d *schema.ResourceData, mappings ...outputMapping) error { + return handleExternalChangesToObject(d, ShowOutputAttributeName, mappings...) +} + +// handleExternalChangesToObjectInFlatDescribe assumes that describe output is kept in DescribeOutputAttributeName attribute +// It is to be used with flat - show like describe_output schemas +// To handle external changes to describe with properties like collections use `handleExternalChangesToObjectInDescribe()` +func handleExternalChangesToObjectInFlatDescribe(d *schema.ResourceData, mappings ...outputMapping) error { + return handleExternalChangesToObject(d, DescribeOutputAttributeName, mappings...) +} + +type outputMapping struct { + nameInOutput string nameInConfig string valueToCompare any valueToSet any normalizeFunc func(any) any } -// handleExternalValueChangesToObjectInDescribe assumes that describe output is kept in DescribeOutputAttributeName attribute -func handleExternalValueChangesToObjectInDescribe(d *schema.ResourceData, mappings ...describeMapping) error { - if descOutput, ok := d.GetOk(DescribeOutputAttributeName); ok { - descOutputList := descOutput.([]any) - if len(descOutputList) == 1 { - result := descOutputList[0].(map[string]any) - for _, mapping := range mappings { - valueToCompareFrom := result[mapping.nameInDescribe] - if mapping.normalizeFunc != nil { - valueToCompareFrom = mapping.normalizeFunc(valueToCompareFrom) - } - if valueToCompareFrom != mapping.valueToCompare { - if err := d.Set(mapping.nameInConfig, mapping.valueToSet); err != nil { - return err - } - } - } - } - } - return nil -} - // handleExternalChangesToObjectInDescribe assumes that show output is kept in DescribeOutputAttributeName attribute func handleExternalChangesToObjectInDescribe(d *schema.ResourceData, mappings ...describeMapping) error { if describeOutput, ok := d.GetOk(DescribeOutputAttributeName); ok { diff --git a/pkg/resources/stream_on_external_table.go b/pkg/resources/stream_on_external_table.go index 07e002e51a..9262e3595e 100644 --- a/pkg/resources/stream_on_external_table.go +++ b/pkg/resources/stream_on_external_table.go @@ -170,7 +170,7 @@ func ReadStreamOnExternalTable(withExternalChangesMarking bool) schema.ReadConte mode = *stream.Mode } if err = handleExternalChangesToObjectInShow(d, - showMapping{"mode", "insert_only", string(mode), booleanStringFromBool(stream.IsInsertOnly()), nil}, + outputMapping{"mode", "insert_only", string(mode), booleanStringFromBool(stream.IsInsertOnly()), nil}, ); err != nil { return diag.FromErr(err) } diff --git a/pkg/resources/stream_on_table.go b/pkg/resources/stream_on_table.go index ca02cec50f..4fdcceba23 100644 --- a/pkg/resources/stream_on_table.go +++ b/pkg/resources/stream_on_table.go @@ -178,7 +178,7 @@ func ReadStreamOnTable(withExternalChangesMarking bool) schema.ReadContextFunc { mode = *stream.Mode } if err = handleExternalChangesToObjectInShow(d, - showMapping{"mode", "append_only", string(mode), booleanStringFromBool(stream.IsAppendOnly()), nil}, + outputMapping{"mode", "append_only", string(mode), booleanStringFromBool(stream.IsAppendOnly()), nil}, ); err != nil { return diag.FromErr(err) } diff --git a/pkg/resources/user.go b/pkg/resources/user.go index 29cf25a441..cb4a0cfa6a 100644 --- a/pkg/resources/user.go +++ b/pkg/resources/user.go @@ -460,7 +460,7 @@ func GetReadUserFunc(userType sdk.UserType, withExternalChangesMarking bool) sch } if withExternalChangesMarking { - showMappings := []showMapping{ + showMappings := []outputMapping{ {"login_name", "login_name", u.LoginName, u.LoginName, nil}, {"display_name", "display_name", u.DisplayName, u.DisplayName, nil}, {"disabled", "disabled", u.Disabled, fmt.Sprintf("%t", u.Disabled), nil}, @@ -468,7 +468,7 @@ func GetReadUserFunc(userType sdk.UserType, withExternalChangesMarking bool) sch {"default_secondary_roles", "default_secondary_roles_option", u.DefaultSecondaryRoles, u.GetSecondaryRolesOption(), nil}, } if userType == sdk.UserTypePerson || userType == sdk.UserTypeLegacyService { - showMappings = append(showMappings, showMapping{"must_change_password", "must_change_password", u.MustChangePassword, fmt.Sprintf("%t", u.MustChangePassword), nil}) + showMappings = append(showMappings, outputMapping{"must_change_password", "must_change_password", u.MustChangePassword, fmt.Sprintf("%t", u.MustChangePassword), nil}) } if err = handleExternalChangesToObjectInShow(d, showMappings...); err != nil { return diag.FromErr(err) diff --git a/pkg/resources/view.go b/pkg/resources/view.go index ea3b4793ee..7c5b6d680f 100644 --- a/pkg/resources/view.go +++ b/pkg/resources/view.go @@ -610,14 +610,14 @@ func ReadView(withExternalChangesMarking bool) schema.ReadContextFunc { } if withExternalChangesMarking { if err = handleExternalChangesToObjectInShow(d, - showMapping{"is_secure", "is_secure", view.IsSecure, booleanStringFromBool(view.IsSecure), nil}, - showMapping{"text", "is_recursive", view.IsRecursive(), booleanStringFromBool(view.IsRecursive()), func(x any) any { + outputMapping{"is_secure", "is_secure", view.IsSecure, booleanStringFromBool(view.IsSecure), nil}, + outputMapping{"text", "is_recursive", view.IsRecursive(), booleanStringFromBool(view.IsRecursive()), func(x any) any { return strings.Contains(x.(string), "RECURSIVE") }}, - showMapping{"text", "is_temporary", view.IsTemporary(), booleanStringFromBool(view.IsTemporary()), func(x any) any { + outputMapping{"text", "is_temporary", view.IsTemporary(), booleanStringFromBool(view.IsTemporary()), func(x any) any { return strings.Contains(x.(string), "TEMPORARY") }}, - showMapping{"change_tracking", "change_tracking", view.IsChangeTracking(), booleanStringFromBool(view.IsChangeTracking()), func(x any) any { + outputMapping{"change_tracking", "change_tracking", view.IsChangeTracking(), booleanStringFromBool(view.IsChangeTracking()), func(x any) any { return x.(string) == "ON" }}, ); err != nil { diff --git a/pkg/resources/warehouse.go b/pkg/resources/warehouse.go index a2e4c43e36..15222e5a61 100644 --- a/pkg/resources/warehouse.go +++ b/pkg/resources/warehouse.go @@ -391,16 +391,16 @@ func GetReadWarehouseFunc(withExternalChangesMarking bool) schema.ReadContextFun if withExternalChangesMarking { if err = handleExternalChangesToObjectInShow(d, - showMapping{"type", "warehouse_type", string(w.Type), w.Type, nil}, - showMapping{"size", "warehouse_size", string(w.Size), w.Size, nil}, - showMapping{"max_cluster_count", "max_cluster_count", w.MaxClusterCount, w.MaxClusterCount, nil}, - showMapping{"min_cluster_count", "min_cluster_count", w.MinClusterCount, w.MinClusterCount, nil}, - showMapping{"scaling_policy", "scaling_policy", string(w.ScalingPolicy), w.ScalingPolicy, nil}, - showMapping{"auto_suspend", "auto_suspend", w.AutoSuspend, w.AutoSuspend, nil}, - showMapping{"auto_resume", "auto_resume", w.AutoResume, fmt.Sprintf("%t", w.AutoResume), nil}, - showMapping{"resource_monitor", "resource_monitor", w.ResourceMonitor.Name(), w.ResourceMonitor.Name(), nil}, - showMapping{"enable_query_acceleration", "enable_query_acceleration", w.EnableQueryAcceleration, fmt.Sprintf("%t", w.EnableQueryAcceleration), nil}, - showMapping{"query_acceleration_max_scale_factor", "query_acceleration_max_scale_factor", w.QueryAccelerationMaxScaleFactor, w.QueryAccelerationMaxScaleFactor, nil}, + outputMapping{"type", "warehouse_type", string(w.Type), w.Type, nil}, + outputMapping{"size", "warehouse_size", string(w.Size), w.Size, nil}, + outputMapping{"max_cluster_count", "max_cluster_count", w.MaxClusterCount, w.MaxClusterCount, nil}, + outputMapping{"min_cluster_count", "min_cluster_count", w.MinClusterCount, w.MinClusterCount, nil}, + outputMapping{"scaling_policy", "scaling_policy", string(w.ScalingPolicy), w.ScalingPolicy, nil}, + outputMapping{"auto_suspend", "auto_suspend", w.AutoSuspend, w.AutoSuspend, nil}, + outputMapping{"auto_resume", "auto_resume", w.AutoResume, fmt.Sprintf("%t", w.AutoResume), nil}, + outputMapping{"resource_monitor", "resource_monitor", w.ResourceMonitor.Name(), w.ResourceMonitor.Name(), nil}, + outputMapping{"enable_query_acceleration", "enable_query_acceleration", w.EnableQueryAcceleration, fmt.Sprintf("%t", w.EnableQueryAcceleration), nil}, + outputMapping{"query_acceleration_max_scale_factor", "query_acceleration_max_scale_factor", w.QueryAccelerationMaxScaleFactor, w.QueryAccelerationMaxScaleFactor, nil}, ); err != nil { return diag.FromErr(err) } diff --git a/pkg/sdk/secrets_def.go b/pkg/sdk/secrets_def.go index 44c80475c6..5ac89932a4 100644 --- a/pkg/sdk/secrets_def.go +++ b/pkg/sdk/secrets_def.go @@ -2,17 +2,50 @@ package sdk import ( "fmt" + "strings" g "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/poc/generator" ) //go:generate go run ./poc/main.go + +type ( + SecretType string +) + +func ToSecretType(s string) (SecretType, error) { + switch strings.ToUpper(s) { + case string(SecretTypePassword): + return SecretTypePassword, nil + case string(SecretTypeOAuth2): + return SecretTypeOAuth2, nil + case string(SecretTypeGenericString): + return SecretTypeGenericString, nil + case string(SecretTypeOAuth2ClientCredentials): + return SecretTypeOAuth2ClientCredentials, nil + case string(SecretTypeOAuth2AuthorizationCodeGrant): + return SecretTypeOAuth2AuthorizationCodeGrant, nil + default: + return "", fmt.Errorf("invalid secret type: %s", s) + } +} + const ( - SecretTypePassword = "PASSWORD" - SecretTypeOAuth2 = "OAUTH2" - SecretTypeGenericString = "GENERIC_STRING" + SecretTypePassword SecretType = "PASSWORD" + SecretTypeOAuth2 SecretType = "OAUTH2" + SecretTypeGenericString SecretType = "GENERIC_STRING" + SecretTypeOAuth2ClientCredentials SecretType = "OAUTH2_CLIENT_CREDENTIALS" // #nosec G101 + SecretTypeOAuth2AuthorizationCodeGrant SecretType = "OAUTH2_AUTHORIZATION_CODE_GRANT" // #nosec G101 ) +var AcceptableSecretTypes = map[SecretType][]SecretType{ + SecretTypePassword: {SecretTypePassword}, + SecretTypeOAuth2: {SecretTypeOAuth2}, + SecretTypeGenericString: {SecretTypeGenericString}, + SecretTypeOAuth2ClientCredentials: {SecretTypeOAuth2ClientCredentials, SecretTypeOAuth2}, + SecretTypeOAuth2AuthorizationCodeGrant: {SecretTypeOAuth2ClientCredentials, SecretTypeOAuth2}, +} + var secretDbRow = g.DbStruct("secretDBRow"). Field("created_on", "time.Time"). Field("name", "string"). diff --git a/pkg/sdk/testint/secrets_gen_integration_test.go b/pkg/sdk/testint/secrets_gen_integration_test.go index fa8042701c..26a12edad0 100644 --- a/pkg/sdk/testint/secrets_gen_integration_test.go +++ b/pkg/sdk/testint/secrets_gen_integration_test.go @@ -75,7 +75,7 @@ func TestInt_Secrets(t *testing.T) { objectassert.Secret(t, id). HasName(id.Name()). HasComment("a"). - HasSecretType(sdk.SecretTypeOAuth2). + HasSecretType(string(sdk.SecretTypeOAuth2)). HasOauthScopes([]string{"foo", "bar"}). HasDatabaseName(id.DatabaseName()). HasSchemaName(id.SchemaName()), @@ -87,7 +87,7 @@ func TestInt_Secrets(t *testing.T) { assertSecretDetails(details, secretDetails{ Name: id.Name(), Comment: sdk.String("a"), - SecretType: sdk.SecretTypeOAuth2, + SecretType: string(sdk.SecretTypeOAuth2), OauthScopes: []string{"foo", "bar"}, IntegrationName: sdk.String(integrationId.Name()), }) @@ -114,7 +114,7 @@ func TestInt_Secrets(t *testing.T) { assertSecretDetails(details, secretDetails{ Name: id.Name(), - SecretType: sdk.SecretTypeOAuth2, + SecretType: string(sdk.SecretTypeOAuth2), IntegrationName: sdk.String(integrationId.Name()), }) }) @@ -164,7 +164,7 @@ func TestInt_Secrets(t *testing.T) { objectassert.Secret(t, id). HasName(id.Name()). HasComment("a"). - HasSecretType(sdk.SecretTypeOAuth2). + HasSecretType(string(sdk.SecretTypeOAuth2)). HasDatabaseName(id.DatabaseName()). HasSchemaName(id.SchemaName()), ) @@ -174,7 +174,7 @@ func TestInt_Secrets(t *testing.T) { assertSecretDetails(details, secretDetails{ Name: id.Name(), - SecretType: sdk.SecretTypeOAuth2, + SecretType: string(sdk.SecretTypeOAuth2), Comment: sdk.String("a"), OauthRefreshTokenExpiryTime: stringDateToSnowflakeTimeFormat(time.DateOnly, refreshTokenExpiryTime), IntegrationName: sdk.String(integrationId.Name()), @@ -196,7 +196,7 @@ func TestInt_Secrets(t *testing.T) { assertSecretDetails(details, secretDetails{ Name: id.Name(), - SecretType: sdk.SecretTypeOAuth2, + SecretType: string(sdk.SecretTypeOAuth2), OauthRefreshTokenExpiryTime: stringDateToSnowflakeTimeFormat(time.DateTime, refreshTokenWithTime), IntegrationName: sdk.String(integrationId.Name()), }) @@ -220,7 +220,7 @@ func TestInt_Secrets(t *testing.T) { objectassert.SecretFromObject(t, secret). HasName(id.Name()). HasComment(comment). - HasSecretType(sdk.SecretTypePassword). + HasSecretType(string(sdk.SecretTypePassword)). HasDatabaseName(id.DatabaseName()). HasSchemaName(id.SchemaName()), ) @@ -231,7 +231,7 @@ func TestInt_Secrets(t *testing.T) { assertSecretDetails(details, secretDetails{ Name: id.Name(), Comment: sdk.String(comment), - SecretType: sdk.SecretTypePassword, + SecretType: string(sdk.SecretTypePassword), Username: sdk.String("foo"), }) }) @@ -250,7 +250,7 @@ func TestInt_Secrets(t *testing.T) { assertSecretDetails(details, secretDetails{ Name: id.Name(), - SecretType: sdk.SecretTypePassword, + SecretType: string(sdk.SecretTypePassword), Username: sdk.String(""), }) }) @@ -273,7 +273,7 @@ func TestInt_Secrets(t *testing.T) { objectassert.Secret(t, id). HasName(id.Name()). HasComment(comment). - HasSecretType(sdk.SecretTypeGenericString). + HasSecretType(string(sdk.SecretTypeGenericString)). HasDatabaseName(id.DatabaseName()). HasSchemaName(id.SchemaName()), ) @@ -290,7 +290,7 @@ func TestInt_Secrets(t *testing.T) { assertions.AssertThat(t, objectassert.Secret(t, id). HasName(id.Name()). - HasSecretType(sdk.SecretTypeGenericString). + HasSecretType(string(sdk.SecretTypeGenericString)). HasDatabaseName(id.DatabaseName()). HasSchemaName(id.SchemaName()), ) @@ -320,7 +320,7 @@ func TestInt_Secrets(t *testing.T) { assertSecretDetails(details, secretDetails{ Name: id.Name(), - SecretType: sdk.SecretTypeOAuth2, + SecretType: string(sdk.SecretTypeOAuth2), Comment: sdk.String(comment), OauthScopes: []string{"foo", "bar"}, IntegrationName: sdk.String(integrationId.Name()), @@ -367,7 +367,7 @@ func TestInt_Secrets(t *testing.T) { assertSecretDetails(details, secretDetails{ Name: id.Name(), - SecretType: sdk.SecretTypeOAuth2, + SecretType: string(sdk.SecretTypeOAuth2), Comment: sdk.String(comment), OauthRefreshTokenExpiryTime: stringDateToSnowflakeTimeFormat(time.DateOnly, alteredRefreshTokenExpiryTime), IntegrationName: sdk.String(integrationId.Name()), @@ -414,7 +414,7 @@ func TestInt_Secrets(t *testing.T) { // Cannot check password property since show and describe on secret do not have access to it assertSecretDetails(details, secretDetails{ Name: id.Name(), - SecretType: sdk.SecretTypePassword, + SecretType: string(sdk.SecretTypePassword), Comment: sdk.String(comment), Username: sdk.String("bar"), }) @@ -712,7 +712,7 @@ func TestInt_Secrets(t *testing.T) { assertSecretDetails(details, secretDetails{ Name: id.Name(), - SecretType: sdk.SecretTypeGenericString, + SecretType: string(sdk.SecretTypeGenericString), }) }) }