From 32c55ad4d26924ba730af854540265fd2c768852 Mon Sep 17 00:00:00 2001 From: Jakub Michalak Date: Fri, 19 Jul 2024 16:06:06 +0200 Subject: [PATCH 1/6] Fix sync password --- ...tegration_with_authorization_code_grant.md | 4 +- ...ion_integration_with_client_credentials.md | 4 +- ...hentication_integration_with_jwt_bearer.md | 4 +- docs/resources/external_oauth_integration.md | 4 +- .../oauth_integration_for_custom_clients.md | 8 +- ...th_integration_for_partner_applications.md | 8 +- docs/resources/saml2_integration.md | 8 +- docs/resources/scim_integration.md | 10 +- ...tegration_with_authorization_code_grant.go | 2 + ...ion_integration_with_client_credentials.go | 4 +- ...hentication_integration_with_jwt_bearer.go | 4 +- pkg/resources/doc_helpers.go | 2 +- pkg/resources/external_oauth_integration.go | 5 +- .../oauth_integration_for_custom_clients.go | 5 +- ...th_integration_for_partner_applications.go | 5 +- pkg/resources/saml2_integration.go | 5 +- pkg/resources/scim_integration.go | 71 +++++-- .../scim_integration_acceptance_test.go | 189 +++++++++++++++++- .../scim_integration_state_upgraders.go | 15 ++ .../completeAzure/test.tf | 8 + .../completeAzure/variables.tf | 18 ++ 21 files changed, 322 insertions(+), 61 deletions(-) create mode 100644 pkg/resources/testdata/TestAcc_ScimIntegration/completeAzure/test.tf create mode 100644 pkg/resources/testdata/TestAcc_ScimIntegration/completeAzure/variables.tf diff --git a/docs/resources/api_authentication_integration_with_authorization_code_grant.md b/docs/resources/api_authentication_integration_with_authorization_code_grant.md index d011430f6a..ea13b5071a 100644 --- a/docs/resources/api_authentication_integration_with_authorization_code_grant.md +++ b/docs/resources/api_authentication_integration_with_authorization_code_grant.md @@ -2,14 +2,14 @@ page_title: "snowflake_api_authentication_integration_with_authorization_code_grant Resource - terraform-provider-snowflake" subcategory: "" description: |- - + Resource used to manage api authentication security integration objects with authorization code grant. For more information, check security integrations documentation https://docs.snowflake.com/en/sql-reference/sql/create-security-integration-api-auth. --- !> **V1 release candidate** This resource was reworked and is a release candidate for the V1. We do not expect significant changes in it before the V1. We will welcome any feedback and adjust the resource if needed. Any errors reported will be resolved with a higher priority. We encourage checking this resource out before the V1 release. Please follow the [migration guide](https://github.com/Snowflake-Labs/terraform-provider-snowflake/blob/main/MIGRATION_GUIDE.md#v0920--v0930) to use it. # snowflake_api_authentication_integration_with_authorization_code_grant (Resource) - +Resource used to manage api authentication security integration objects with authorization code grant. For more information, check [security integrations documentation](https://docs.snowflake.com/en/sql-reference/sql/create-security-integration-api-auth). ## Example Usage diff --git a/docs/resources/api_authentication_integration_with_client_credentials.md b/docs/resources/api_authentication_integration_with_client_credentials.md index cb6a466a94..00ae798573 100644 --- a/docs/resources/api_authentication_integration_with_client_credentials.md +++ b/docs/resources/api_authentication_integration_with_client_credentials.md @@ -2,14 +2,14 @@ page_title: "snowflake_api_authentication_integration_with_client_credentials Resource - terraform-provider-snowflake" subcategory: "" description: |- - + Resource used to manage api authentication security integration objects with client credentials. For more information, check security integrations documentation https://docs.snowflake.com/en/sql-reference/sql/create-security-integration-api-auth. --- !> **V1 release candidate** This resource was reworked and is a release candidate for the V1. We do not expect significant changes in it before the V1. We will welcome any feedback and adjust the resource if needed. Any errors reported will be resolved with a higher priority. We encourage checking this resource out before the V1 release. Please follow the [migration guide](https://github.com/Snowflake-Labs/terraform-provider-snowflake/blob/main/MIGRATION_GUIDE.md#v0920--v0930) to use it. # snowflake_api_authentication_integration_with_client_credentials (Resource) - +Resource used to manage api authentication security integration objects with client credentials. For more information, check [security integrations documentation](https://docs.snowflake.com/en/sql-reference/sql/create-security-integration-api-auth). ## Example Usage diff --git a/docs/resources/api_authentication_integration_with_jwt_bearer.md b/docs/resources/api_authentication_integration_with_jwt_bearer.md index be734aab19..b58db4dd9f 100644 --- a/docs/resources/api_authentication_integration_with_jwt_bearer.md +++ b/docs/resources/api_authentication_integration_with_jwt_bearer.md @@ -2,14 +2,14 @@ page_title: "snowflake_api_authentication_integration_with_jwt_bearer Resource - terraform-provider-snowflake" subcategory: "" description: |- - + Resource used to manage api authentication security integration objects with jwt bearer. For more information, check security integrations documentation https://docs.snowflake.com/en/sql-reference/sql/create-security-integration-api-auth. --- !> **V1 release candidate** This resource was reworked and is a release candidate for the V1. We do not expect significant changes in it before the V1. We will welcome any feedback and adjust the resource if needed. Any errors reported will be resolved with a higher priority. We encourage checking this resource out before the V1 release. Please follow the [migration guide](https://github.com/Snowflake-Labs/terraform-provider-snowflake/blob/main/MIGRATION_GUIDE.md#v0920--v0930) to use it. # snowflake_api_authentication_integration_with_jwt_bearer (Resource) - +Resource used to manage api authentication security integration objects with jwt bearer. For more information, check [security integrations documentation](https://docs.snowflake.com/en/sql-reference/sql/create-security-integration-api-auth). ## Example Usage diff --git a/docs/resources/external_oauth_integration.md b/docs/resources/external_oauth_integration.md index bb4bf25f1f..616a6c71a6 100644 --- a/docs/resources/external_oauth_integration.md +++ b/docs/resources/external_oauth_integration.md @@ -2,14 +2,14 @@ page_title: "snowflake_external_oauth_integration Resource - terraform-provider-snowflake" subcategory: "" description: |- - Resource used to manage external oauth security integrations. For more information, check documentation https://docs.snowflake.com/en/sql-reference/sql/create-security-integration-oauth-external. + Resource used to manage external oauth security integration objects. For more information, check security integrations documentation https://docs.snowflake.com/en/sql-reference/sql/create-security-integration-oauth-external. --- !> **V1 release candidate** This resource was reworked and is a release candidate for the V1. We do not expect significant changes in it before the V1. We will welcome any feedback and adjust the resource if needed. Any errors reported will be resolved with a higher priority. We encourage checking this resource out before the V1 release. Please follow the [migration guide](https://github.com/Snowflake-Labs/terraform-provider-snowflake/blob/main/MIGRATION_GUIDE.md#v0920--v0930) to use it. # snowflake_external_oauth_integration (Resource) -Resource used to manage external oauth security integrations. For more information, check [documentation](https://docs.snowflake.com/en/sql-reference/sql/create-security-integration-oauth-external). +Resource used to manage external oauth security integration objects. For more information, check [security integrations documentation](https://docs.snowflake.com/en/sql-reference/sql/create-security-integration-oauth-external). ## Example Usage diff --git a/docs/resources/oauth_integration_for_custom_clients.md b/docs/resources/oauth_integration_for_custom_clients.md index 020b62a829..857fed4f11 100644 --- a/docs/resources/oauth_integration_for_custom_clients.md +++ b/docs/resources/oauth_integration_for_custom_clients.md @@ -2,14 +2,14 @@ page_title: "snowflake_oauth_integration_for_custom_clients Resource - terraform-provider-snowflake" subcategory: "" description: |- - + Resource used to manage oauth security integration for custom clients objects. For more information, check security integrations documentation https://docs.snowflake.com/en/sql-reference/sql/create-security-integration-oauth-snowflake. --- !> **V1 release candidate** This resource was reworked and is a release candidate for the V1. We do not expect significant changes in it before the V1. We will welcome any feedback and adjust the resource if needed. Any errors reported will be resolved with a higher priority. We encourage checking this resource out before the V1 release. Please follow the [migration guide](https://github.com/Snowflake-Labs/terraform-provider-snowflake/blob/main/MIGRATION_GUIDE.md#v0920--v0930) to use it. # snowflake_oauth_integration_for_custom_clients (Resource) - +Resource used to manage oauth security integration for custom clients objects. For more information, check [security integrations documentation](https://docs.snowflake.com/en/sql-reference/sql/create-security-integration-oauth-snowflake). ## Example Usage @@ -49,7 +49,7 @@ resource "snowflake_oauth_integration_for_custom_clients" "complete" { - `blocked_roles_list` (Set of String) A set of Snowflake roles that a user cannot explicitly consent to using after authenticating. - `name` (String) Specifies the name of the OAuth integration. This name follows the rules for Object Identifiers. The name should be unique among security integrations in your account. -- `oauth_client_type` (String) Specifies the type of client being registered. Snowflake supports both confidential and public clients. Valid options are: [PUBLIC CONFIDENTIAL] +- `oauth_client_type` (String) Specifies the type of client being registered. Snowflake supports both confidential and public clients. Valid options are: `PUBLIC` | `CONFIDENTIAL`. - `oauth_redirect_uri` (String) Specifies the client URI. After a user is authenticated, the web browser is redirected to this URI. ### Optional @@ -63,7 +63,7 @@ resource "snowflake_oauth_integration_for_custom_clients" "complete" { - `oauth_enforce_pkce` (String) Boolean that specifies whether Proof Key for Code Exchange (PKCE) should be required for the integration. Available options are: "true" or "false". When the value is not set in the configuration the provider will put "default" there which means to use the Snowflake default for this value. - `oauth_issue_refresh_tokens` (String) Specifies whether to allow the client to exchange a refresh token for an access token when the current access token has expired. Available options are: "true" or "false". When the value is not set in the configuration the provider will put "default" there which means to use the Snowflake default for this value. - `oauth_refresh_token_validity` (Number) Specifies how long refresh tokens should be valid (in seconds). OAUTH_ISSUE_REFRESH_TOKENS must be set to TRUE. -- `oauth_use_secondary_roles` (String) Specifies whether default secondary roles set in the user properties are activated by default in the session being opened. Valid options are: [IMPLICIT NONE] +- `oauth_use_secondary_roles` (String) Specifies whether default secondary roles set in the user properties are activated by default in the session being opened. Valid options are: `IMPLICIT` | `NONE`. - `pre_authorized_roles_list` (Set of String) A set of Snowflake roles that a user does not need to explicitly consent to using after authenticating. ### Read-Only diff --git a/docs/resources/oauth_integration_for_partner_applications.md b/docs/resources/oauth_integration_for_partner_applications.md index cc772195cd..9ebac00ebe 100644 --- a/docs/resources/oauth_integration_for_partner_applications.md +++ b/docs/resources/oauth_integration_for_partner_applications.md @@ -2,14 +2,14 @@ page_title: "snowflake_oauth_integration_for_partner_applications Resource - terraform-provider-snowflake" subcategory: "" description: |- - + Resource used to manage oauth security integration for partner applications objects. For more information, check security integrations documentation https://docs.snowflake.com/en/sql-reference/sql/create-security-integration-oauth-snowflake. --- !> **V1 release candidate** This resource was reworked and is a release candidate for the V1. We do not expect significant changes in it before the V1. We will welcome any feedback and adjust the resource if needed. Any errors reported will be resolved with a higher priority. We encourage checking this resource out before the V1 release. Please follow the [migration guide](https://github.com/Snowflake-Labs/terraform-provider-snowflake/blob/main/MIGRATION_GUIDE.md#v0920--v0930) to use it. # snowflake_oauth_integration_for_partner_applications (Resource) - +Resource used to manage oauth security integration for partner applications objects. For more information, check [security integrations documentation](https://docs.snowflake.com/en/sql-reference/sql/create-security-integration-oauth-snowflake). ## Example Usage @@ -42,7 +42,7 @@ resource "snowflake_oauth_integration_for_partner_applications" "test" { - `blocked_roles_list` (Set of String) A set of Snowflake roles that a user cannot explicitly consent to using after authenticating. - `name` (String) Specifies the name of the OAuth integration. This name follows the rules for Object Identifiers. The name should be unique among security integrations in your account. -- `oauth_client` (String) Creates an OAuth interface between Snowflake and a partner application. Valid options are: [LOOKER TABLEAU_DESKTOP TABLEAU_SERVER] +- `oauth_client` (String) Creates an OAuth interface between Snowflake and a partner application. Valid options are: `LOOKER` | `TABLEAU_DESKTOP` | `TABLEAU_SERVER`. ### Optional @@ -51,7 +51,7 @@ resource "snowflake_oauth_integration_for_partner_applications" "test" { - `oauth_issue_refresh_tokens` (String) Specifies whether to allow the client to exchange a refresh token for an access token when the current access token has expired. Available options are: "true" or "false". When the value is not set in the configuration the provider will put "default" there which means to use the Snowflake default for this value. - `oauth_redirect_uri` (String) Specifies the client URI. After a user is authenticated, the web browser is redirected to this URI. The field should be only set when OAUTH_CLIENT = LOOKER. In any other case the field should be left out empty. External changes for this field won't be detected. In case you want to apply external changes, you can re-create the resource manually using "terraform taint". - `oauth_refresh_token_validity` (Number) Specifies how long refresh tokens should be valid (in seconds). OAUTH_ISSUE_REFRESH_TOKENS must be set to TRUE. -- `oauth_use_secondary_roles` (String) Specifies whether default secondary roles set in the user properties are activated by default in the session being opened. Valid options are: [IMPLICIT NONE] +- `oauth_use_secondary_roles` (String) Specifies whether default secondary roles set in the user properties are activated by default in the session being opened. Valid options are: `IMPLICIT` | `NONE`. ### Read-Only diff --git a/docs/resources/saml2_integration.md b/docs/resources/saml2_integration.md index 7f3456d124..fc9c4d9124 100644 --- a/docs/resources/saml2_integration.md +++ b/docs/resources/saml2_integration.md @@ -2,14 +2,14 @@ page_title: "snowflake_saml2_integration Resource - terraform-provider-snowflake" subcategory: "" description: |- - + Resource used to manage saml2 security integration objects. For more information, check security integrations documentation https://docs.snowflake.com/en/sql-reference/sql/create-security-integration-saml2. --- !> **V1 release candidate** This resource was reworked and is a release candidate for the V1. We do not expect significant changes in it before the V1. We will welcome any feedback and adjust the resource if needed. Any errors reported will be resolved with a higher priority. We encourage checking this resource out before the V1 release. Please follow the [migration guide](https://github.com/Snowflake-Labs/terraform-provider-snowflake/blob/main/MIGRATION_GUIDE.md#v0920--v0930) to use it. # snowflake_saml2_integration (Resource) - +Resource used to manage saml2 security integration objects. For more information, check [security integrations documentation](https://docs.snowflake.com/en/sql-reference/sql/create-security-integration-saml2). ## Example Usage @@ -53,7 +53,7 @@ resource "snowflake_saml2_integration" "test" { - `name` (String) Specifies the name of the SAML2 integration. This name follows the rules for Object Identifiers. The name should be unique among security integrations in your account. - `saml2_issuer` (String) The string containing the IdP EntityID / Issuer. -- `saml2_provider` (String) The string describing the IdP. Valid options are: [OKTA ADFS CUSTOM]. +- `saml2_provider` (String) The string describing the IdP. Valid options are: `OKTA` | `ADFS` | `CUSTOM`. - `saml2_sso_url` (String) The string containing the IdP SSO URL, where the user should be redirected by Snowflake (the Service Provider) with a SAML AuthnRequest message. - `saml2_x509_cert` (String) The Base64 encoded IdP signing certificate on a single line without the leading -----BEGIN CERTIFICATE----- and ending -----END CERTIFICATE----- markers. @@ -66,7 +66,7 @@ resource "snowflake_saml2_integration" "test" { - `saml2_enable_sp_initiated` (String) The Boolean indicating if the Log In With button will be shown on the login page. TRUE: displays the Log in With button on the login page. FALSE: does not display the Log in With button on the login page. Available options are: "true" or "false". When the value is not set in the configuration the provider will put "default" there which means to use the Snowflake default for this value. - `saml2_force_authn` (String) The Boolean indicating whether users, during the initial authentication flow, are forced to authenticate again to access Snowflake. When set to TRUE, Snowflake sets the ForceAuthn SAML parameter to TRUE in the outgoing request from Snowflake to the identity provider. TRUE: forces users to authenticate again to access Snowflake, even if a valid session with the identity provider exists. FALSE: does not force users to authenticate again to access Snowflake. Available options are: "true" or "false". When the value is not set in the configuration the provider will put "default" there which means to use the Snowflake default for this value. - `saml2_post_logout_redirect_url` (String) The endpoint to which Snowflake redirects users after clicking the Log Out button in the classic Snowflake web interface. Snowflake terminates the Snowflake session upon redirecting to the specified endpoint. -- `saml2_requested_nameid_format` (String) The SAML NameID format allows Snowflake to set an expectation of the identifying attribute of the user (i.e. SAML Subject) in the SAML assertion from the IdP to ensure a valid authentication to Snowflake. Valid options are: [urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos urn:oasis:names:tc:SAML:2.0:nameid-format:persistent urn:oasis:names:tc:SAML:2.0:nameid-format:transient] +- `saml2_requested_nameid_format` (String) The SAML NameID format allows Snowflake to set an expectation of the identifying attribute of the user (i.e. SAML Subject) in the SAML assertion from the IdP to ensure a valid authentication to Snowflake. Valid options are: `urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified` | `urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress` | `urn:oasis:names:tc:SAML:1.1:nameid-format:X509SubjectName` | `urn:oasis:names:tc:SAML:1.1:nameid-format:WindowsDomainQualifiedName` | `urn:oasis:names:tc:SAML:2.0:nameid-format:kerberos` | `urn:oasis:names:tc:SAML:2.0:nameid-format:persistent` | `urn:oasis:names:tc:SAML:2.0:nameid-format:transient`. - `saml2_sign_request` (String) The Boolean indicating whether SAML requests are signed. TRUE: allows SAML requests to be signed. FALSE: does not allow SAML requests to be signed. Available options are: "true" or "false". When the value is not set in the configuration the provider will put "default" there which means to use the Snowflake default for this value. - `saml2_snowflake_acs_url` (String) The string containing the Snowflake Assertion Consumer Service URL to which the IdP will send its SAML authentication response back to Snowflake. This property will be set in the SAML authentication request generated by Snowflake when initiating a SAML SSO operation with the IdP. If an incorrect value is specified, Snowflake returns an error message indicating the acceptable values to use. - `saml2_snowflake_issuer_url` (String) The string containing the EntityID / Issuer for the Snowflake service provider. If an incorrect value is specified, Snowflake returns an error message indicating the acceptable values to use. diff --git a/docs/resources/scim_integration.md b/docs/resources/scim_integration.md index 43278d71f0..bfa47bf537 100644 --- a/docs/resources/scim_integration.md +++ b/docs/resources/scim_integration.md @@ -2,14 +2,14 @@ page_title: "snowflake_scim_integration Resource - terraform-provider-snowflake" subcategory: "" description: |- - + Resource used to manage scim security integration objects. For more information, check security integrations documentation https://docs.snowflake.com/en/sql-reference/sql/create-security-integration-scim. --- !> **V1 release candidate** This resource was reworked and is a release candidate for the V1. We do not expect significant changes in it before the V1. We will welcome any feedback and adjust the resource if needed. Any errors reported will be resolved with a higher priority. We encourage checking this resource out before the V1 release. Please follow the [migration guide](https://github.com/Snowflake-Labs/terraform-provider-snowflake/blob/main/MIGRATION_GUIDE.md#v0920--v0930) to use it. # snowflake_scim_integration (Resource) - +Resource used to manage scim security integration objects. For more information, check [security integrations documentation](https://docs.snowflake.com/en/sql-reference/sql/create-security-integration-scim). ## Example Usage @@ -40,14 +40,14 @@ resource "snowflake_scim_integration" "test" { - `enabled` (Boolean) Specify whether the security integration is enabled. - `name` (String) String that specifies the identifier (i.e. name) for the integration; must be unique in your account. -- `run_as_role` (String) Specify the SCIM role in Snowflake that owns any users and roles that are imported from the identity provider into Snowflake using SCIM. Provider assumes that the specified role is already provided. Valid options are: [OKTA_PROVISIONER AAD_PROVISIONER GENERIC_SCIM_PROVISIONER]. -- `scim_client` (String) Specifies the client type for the scim integration. Valid options are: [OKTA AZURE GENERIC]. +- `run_as_role` (String) Specify the SCIM role in Snowflake that owns any users and roles that are imported from the identity provider into Snowflake using SCIM. Provider assumes that the specified role is already provided. Valid options are: `OKTA_PROVISIONER` | `AAD_PROVISIONER` | `GENERIC_SCIM_PROVISIONER`. +- `scim_client` (String) Specifies the client type for the scim integration. Valid options are: `OKTA` | `AZURE` | `GENERIC`. ### Optional - `comment` (String) Specifies a comment for the integration. - `network_policy` (String) Specifies an existing network policy that controls SCIM network traffic. -- `sync_password` (String) Specifies whether to enable or disable the synchronization of a user password from an Okta SCIM client as part of the API request to Snowflake. Available options are: "true" or "false". When the value is not set in the configuration the provider will put "default" there which means to use the Snowflake default for this value. +- `sync_password` (String) Specifies whether to enable or disable the synchronization of a user password from an Okta SCIM client as part of the API request to Snowflake. This property is not supported for Azure SCIM. Available options are: "true" or "false". When the value is not set in the configuration the provider will put "default" there which means to use the Snowflake default for this value. ### Read-Only diff --git a/pkg/resources/api_authentication_integration_with_authorization_code_grant.go b/pkg/resources/api_authentication_integration_with_authorization_code_grant.go index 9f320e33c5..9c8b0cc03f 100644 --- a/pkg/resources/api_authentication_integration_with_authorization_code_grant.go +++ b/pkg/resources/api_authentication_integration_with_authorization_code_grant.go @@ -40,6 +40,8 @@ func ApiAuthenticationIntegrationWithAuthorizationCodeGrant() *schema.Resource { ReadContext: ReadContextApiAuthenticationIntegrationWithAuthorizationCodeGrant(true), UpdateContext: UpdateContextApiAuthenticationIntegrationWithAuthorizationCodeGrant, DeleteContext: DeleteContextApiAuthenticationIntegrationWithAuthorizationCodeGrant, + Description: "Resource used to manage api authentication security integration objects with authorization code grant. For more information, check [security integrations documentation](https://docs.snowflake.com/en/sql-reference/sql/create-security-integration-api-auth).", + CustomizeDiff: customdiff.All( ForceNewIfChangeToEmptyString("oauth_token_endpoint"), ForceNewIfChangeToEmptyString("oauth_authorization_endpoint"), diff --git a/pkg/resources/api_authentication_integration_with_client_credentials.go b/pkg/resources/api_authentication_integration_with_client_credentials.go index 40e3736f7f..45e25ca0cd 100644 --- a/pkg/resources/api_authentication_integration_with_client_credentials.go +++ b/pkg/resources/api_authentication_integration_with_client_credentials.go @@ -35,7 +35,9 @@ func ApiAuthenticationIntegrationWithClientCredentials() *schema.Resource { ReadContext: ReadContextApiAuthenticationIntegrationWithClientCredentials(true), UpdateContext: UpdateContextApiAuthenticationIntegrationWithClientCredentials, DeleteContext: DeleteContextApiAuthenticationIntegrationWithClientCredentials, - Schema: apiAuthClientCredentialsSchema, + Description: "Resource used to manage api authentication security integration objects with client credentials. For more information, check [security integrations documentation](https://docs.snowflake.com/en/sql-reference/sql/create-security-integration-api-auth).", + + Schema: apiAuthClientCredentialsSchema, CustomizeDiff: customdiff.All( ForceNewIfChangeToEmptyString("oauth_token_endpoint"), ForceNewIfChangeToEmptyString("oauth_client_auth_method"), diff --git a/pkg/resources/api_authentication_integration_with_jwt_bearer.go b/pkg/resources/api_authentication_integration_with_jwt_bearer.go index 73252185d5..df54815f89 100644 --- a/pkg/resources/api_authentication_integration_with_jwt_bearer.go +++ b/pkg/resources/api_authentication_integration_with_jwt_bearer.go @@ -38,7 +38,9 @@ func ApiAuthenticationIntegrationWithJwtBearer() *schema.Resource { ReadContext: ReadContextApiAuthenticationIntegrationWithJwtBearer(true), UpdateContext: UpdateContextApiAuthenticationIntegrationWithJwtBearer, DeleteContext: DeleteContextApiAuthenticationIntegrationWithJwtBearer, - Schema: apiAuthJwtBearerSchema, + Description: "Resource used to manage api authentication security integration objects with jwt bearer. For more information, check [security integrations documentation](https://docs.snowflake.com/en/sql-reference/sql/create-security-integration-api-auth).", + + Schema: apiAuthJwtBearerSchema, CustomizeDiff: customdiff.All( ForceNewIfChangeToEmptyString("oauth_token_endpoint"), ForceNewIfChangeToEmptyString("oauth_authorization_endpoint"), diff --git a/pkg/resources/doc_helpers.go b/pkg/resources/doc_helpers.go index eb966707d3..020f70ce46 100644 --- a/pkg/resources/doc_helpers.go +++ b/pkg/resources/doc_helpers.go @@ -5,7 +5,7 @@ import ( "strings" ) -func possibleValuesListed(values []string) string { +func possibleValuesListed[T ~string](values []T) string { valuesWrapped := make([]string, len(values)) for i, value := range values { valuesWrapped[i] = fmt.Sprintf("`%s`", value) diff --git a/pkg/resources/external_oauth_integration.go b/pkg/resources/external_oauth_integration.go index 8e9804acdb..2434dc284c 100644 --- a/pkg/resources/external_oauth_integration.go +++ b/pkg/resources/external_oauth_integration.go @@ -157,7 +157,9 @@ func ExternalOauthIntegration() *schema.Resource { ReadContext: ReadContextExternalOauthIntegration(true), UpdateContext: UpdateContextExternalOauthIntegration, DeleteContext: DeleteContextExternalOauthIntegration, - Schema: oauthExternalIntegrationSchema, + Description: "Resource used to manage external oauth security integration objects. For more information, check [security integrations documentation](https://docs.snowflake.com/en/sql-reference/sql/create-security-integration-oauth-external).", + + Schema: oauthExternalIntegrationSchema, CustomizeDiff: customdiff.All( ForceNewIfChangeToEmptyString("external_oauth_rsa_public_key"), ForceNewIfChangeToEmptyString("external_oauth_rsa_public_key_2"), @@ -173,7 +175,6 @@ func ExternalOauthIntegration() *schema.Resource { Importer: &schema.ResourceImporter{ StateContext: ImportExternalOauthIntegration, }, - Description: "Resource used to manage external oauth security integrations. For more information, check [documentation](https://docs.snowflake.com/en/sql-reference/sql/create-security-integration-oauth-external).", StateUpgraders: []schema.StateUpgrader{ { diff --git a/pkg/resources/oauth_integration_for_custom_clients.go b/pkg/resources/oauth_integration_for_custom_clients.go index edd3aa9161..c7b549f3a1 100644 --- a/pkg/resources/oauth_integration_for_custom_clients.go +++ b/pkg/resources/oauth_integration_for_custom_clients.go @@ -33,7 +33,7 @@ var oauthIntegrationForCustomClientsSchema = map[string]*schema.Schema{ ForceNew: true, ValidateDiagFunc: sdkValidation(sdk.ToOauthSecurityIntegrationClientTypeOption), DiffSuppressFunc: NormalizeAndCompare(sdk.ToOauthSecurityIntegrationClientTypeOption), - Description: fmt.Sprintf("Specifies the type of client being registered. Snowflake supports both confidential and public clients. Valid options are: %v", sdk.AllOauthSecurityIntegrationClientTypes), + Description: fmt.Sprintf("Specifies the type of client being registered. Snowflake supports both confidential and public clients. Valid options are: %v.", possibleValuesListed(sdk.AllOauthSecurityIntegrationClientTypes)), }, "oauth_redirect_uri": { Type: schema.TypeString, @@ -69,7 +69,7 @@ var oauthIntegrationForCustomClientsSchema = map[string]*schema.Schema{ Optional: true, ValidateDiagFunc: sdkValidation(sdk.ToOauthSecurityIntegrationUseSecondaryRolesOption), DiffSuppressFunc: NormalizeAndCompare(sdk.ToOauthSecurityIntegrationUseSecondaryRolesOption), - Description: fmt.Sprintf("Specifies whether default secondary roles set in the user properties are activated by default in the session being opened. Valid options are: %v", sdk.AllOauthSecurityIntegrationUseSecondaryRoles), + Description: fmt.Sprintf("Specifies whether default secondary roles set in the user properties are activated by default in the session being opened. Valid options are: %v.", possibleValuesListed(sdk.AllOauthSecurityIntegrationUseSecondaryRoles)), }, "pre_authorized_roles_list": { Type: schema.TypeSet, @@ -154,6 +154,7 @@ func OauthIntegrationForCustomClients() *schema.Resource { ReadContext: ReadContextOauthIntegrationForCustomClients(true), UpdateContext: UpdateContextOauthIntegrationForCustomClients, DeleteContext: DeleteContextOauthIntegrationForCustomClients, + Description: "Resource used to manage oauth security integration for custom clients objects. For more information, check [security integrations documentation](https://docs.snowflake.com/en/sql-reference/sql/create-security-integration-oauth-snowflake).", CustomizeDiff: customdiff.All( ComputedIfAnyAttributeChanged( diff --git a/pkg/resources/oauth_integration_for_partner_applications.go b/pkg/resources/oauth_integration_for_partner_applications.go index b8299d9bf3..285e8b1b16 100644 --- a/pkg/resources/oauth_integration_for_partner_applications.go +++ b/pkg/resources/oauth_integration_for_partner_applications.go @@ -32,7 +32,7 @@ var oauthIntegrationForPartnerApplicationsSchema = map[string]*schema.Schema{ Type: schema.TypeString, Required: true, ForceNew: true, - Description: fmt.Sprintf("Creates an OAuth interface between Snowflake and a partner application. Valid options are: %v", sdk.AllOauthSecurityIntegrationClients), + Description: fmt.Sprintf("Creates an OAuth interface between Snowflake and a partner application. Valid options are: %v.", possibleValuesListed(sdk.AllOauthSecurityIntegrationClients)), ValidateDiagFunc: sdkValidation(sdk.ToOauthSecurityIntegrationClientOption), DiffSuppressFunc: NormalizeAndCompare(sdk.ToOauthSecurityIntegrationClientOption), }, @@ -68,7 +68,7 @@ var oauthIntegrationForPartnerApplicationsSchema = map[string]*schema.Schema{ "oauth_use_secondary_roles": { Type: schema.TypeString, Optional: true, - Description: fmt.Sprintf("Specifies whether default secondary roles set in the user properties are activated by default in the session being opened. Valid options are: %v", sdk.AllOauthSecurityIntegrationUseSecondaryRoles), + Description: fmt.Sprintf("Specifies whether default secondary roles set in the user properties are activated by default in the session being opened. Valid options are: %v.", possibleValuesListed(sdk.AllOauthSecurityIntegrationUseSecondaryRoles)), ValidateDiagFunc: sdkValidation(sdk.ToOauthSecurityIntegrationUseSecondaryRolesOption), DiffSuppressFunc: SuppressIfAny(NormalizeAndCompare(sdk.ToOauthSecurityIntegrationUseSecondaryRolesOption), IgnoreChangeToCurrentSnowflakeValueInDescribe("oauth_use_secondary_roles")), }, @@ -115,6 +115,7 @@ func OauthIntegrationForPartnerApplications() *schema.Resource { ReadContext: ReadContextOauthIntegrationForPartnerApplications(true), UpdateContext: UpdateContextOauthIntegrationForPartnerApplications, DeleteContext: DeleteContextSecurityIntegration, + Description: "Resource used to manage oauth security integration for partner applications objects. For more information, check [security integrations documentation](https://docs.snowflake.com/en/sql-reference/sql/create-security-integration-oauth-snowflake).", CustomizeDiff: customdiff.All( ComputedIfAnyAttributeChanged( diff --git a/pkg/resources/saml2_integration.go b/pkg/resources/saml2_integration.go index b483e45330..98142d372a 100644 --- a/pkg/resources/saml2_integration.go +++ b/pkg/resources/saml2_integration.go @@ -49,7 +49,7 @@ var saml2IntegrationSchema = map[string]*schema.Schema{ Required: true, ValidateDiagFunc: sdkValidation(sdk.ToSaml2SecurityIntegrationSaml2ProviderOption), DiffSuppressFunc: NormalizeAndCompare(sdk.ToSaml2SecurityIntegrationSaml2ProviderOption), - Description: fmt.Sprintf("The string describing the IdP. Valid options are: %v.", sdk.AllSaml2SecurityIntegrationSaml2Providers), + Description: fmt.Sprintf("The string describing the IdP. Valid options are: %v.", possibleValuesListed(sdk.AllSaml2SecurityIntegrationSaml2Providers)), }, "saml2_x509_cert": { Type: schema.TypeString, @@ -83,7 +83,7 @@ var saml2IntegrationSchema = map[string]*schema.Schema{ Optional: true, ValidateDiagFunc: sdkValidation(sdk.ToSaml2SecurityIntegrationSaml2RequestedNameidFormatOption), DiffSuppressFunc: SuppressIfAny(NormalizeAndCompare(sdk.ToSaml2SecurityIntegrationSaml2RequestedNameidFormatOption), IgnoreChangeToCurrentSnowflakeValueInDescribe("saml2_requested_nameid_format")), - Description: fmt.Sprintf("The SAML NameID format allows Snowflake to set an expectation of the identifying attribute of the user (i.e. SAML Subject) in the SAML assertion from the IdP to ensure a valid authentication to Snowflake. Valid options are: %v", sdk.AllSaml2SecurityIntegrationSaml2RequestedNameidFormats), + Description: fmt.Sprintf("The SAML NameID format allows Snowflake to set an expectation of the identifying attribute of the user (i.e. SAML Subject) in the SAML assertion from the IdP to ensure a valid authentication to Snowflake. Valid options are: %v.", possibleValuesListed(sdk.AllSaml2SecurityIntegrationSaml2RequestedNameidFormats)), }, "saml2_post_logout_redirect_url": { Type: schema.TypeString, @@ -156,6 +156,7 @@ func SAML2Integration() *schema.Resource { ReadContext: ReadContextSAML2Integration(true), UpdateContext: UpdateContextSAML2Integration, DeleteContext: DeleteContextSAM2LIntegration, + Description: "Resource used to manage saml2 security integration objects. For more information, check [security integrations documentation](https://docs.snowflake.com/en/sql-reference/sql/create-security-integration-saml2).", Schema: saml2IntegrationSchema, Importer: &schema.ResourceImporter{ diff --git a/pkg/resources/scim_integration.go b/pkg/resources/scim_integration.go index 845077b20e..ecfd3d1694 100644 --- a/pkg/resources/scim_integration.go +++ b/pkg/resources/scim_integration.go @@ -36,7 +36,7 @@ var scimIntegrationSchema = map[string]*schema.Schema{ Type: schema.TypeString, Required: true, ForceNew: true, - Description: fmt.Sprintf("Specifies the client type for the scim integration. Valid options are: %v.", sdk.AsStringList(sdk.AllScimSecurityIntegrationScimClients)), + Description: fmt.Sprintf("Specifies the client type for the scim integration. Valid options are: %v.", possibleValuesListed(sdk.AllScimSecurityIntegrationScimClients)), ValidateDiagFunc: StringInSlice(sdk.AsStringList(sdk.AllScimSecurityIntegrationScimClients), true), DiffSuppressFunc: ignoreCaseAndTrimSpaceSuppressFunc, }, @@ -45,7 +45,7 @@ var scimIntegrationSchema = map[string]*schema.Schema{ Required: true, ForceNew: true, Description: fmt.Sprintf("Specify the SCIM role in Snowflake that owns any users and roles that are imported from the identity provider into Snowflake using SCIM."+ - " Provider assumes that the specified role is already provided. Valid options are: %v.", sdk.AllScimSecurityIntegrationRunAsRoles), + " Provider assumes that the specified role is already provided. Valid options are: %v.", possibleValuesListed(sdk.AllScimSecurityIntegrationRunAsRoles)), ValidateDiagFunc: StringInSlice(sdk.AsStringList(sdk.AllScimSecurityIntegrationRunAsRoles), true), DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { normalize := func(s string) string { @@ -67,7 +67,7 @@ var scimIntegrationSchema = map[string]*schema.Schema{ Default: BooleanDefault, ValidateDiagFunc: validateBooleanString, DiffSuppressFunc: IgnoreChangeToCurrentSnowflakeValueInDescribe("sync_password"), - Description: booleanStringFieldDescription("Specifies whether to enable or disable the synchronization of a user password from an Okta SCIM client as part of the API request to Snowflake."), + Description: booleanStringFieldDescription("Specifies whether to enable or disable the synchronization of a user password from an Okta SCIM client as part of the API request to Snowflake. This property is not supported for Azure SCIM."), }, "comment": { Type: schema.TypeString, @@ -94,12 +94,13 @@ var scimIntegrationSchema = map[string]*schema.Schema{ func SCIMIntegration() *schema.Resource { return &schema.Resource{ - SchemaVersion: 1, + SchemaVersion: 2, CreateContext: CreateContextSCIMIntegration, ReadContext: ReadContextSCIMIntegration(true), UpdateContext: UpdateContextSCIMIntegration, DeleteContext: DeleteContextSCIMIntegration, + Description: "Resource used to manage scim security integration objects. For more information, check [security integrations documentation](https://docs.snowflake.com/en/sql-reference/sql/create-security-integration-scim).", Schema: scimIntegrationSchema, Importer: &schema.ResourceImporter{ @@ -118,6 +119,12 @@ func SCIMIntegration() *schema.Resource { Type: cty.EmptyObject, Upgrade: v092ScimIntegrationStateUpgrader, }, + { + Version: 1, + // setting type to cty.EmptyObject is a bit hacky here but following https://developer.hashicorp.com/terraform/plugin/framework/migrating/resources/state-upgrade#sdkv2-1 would require lots of repetitive code; this should work with cty.EmptyObject + Type: cty.EmptyObject, + Upgrade: v093ScimIntegrationStateUpgrader, + }, }, } } @@ -143,10 +150,12 @@ func ImportScimIntegration(ctx context.Context, d *schema.ResourceData, meta any if err = d.Set("enabled", integration.Enabled); err != nil { return nil, err } - if scimClient, err := integration.SubType(); err == nil { - if err = d.Set("scim_client", scimClient); err != nil { - return nil, err - } + scimClient, err := integration.SubType() + if err != nil { + return nil, err + } + if err = d.Set("scim_client", scimClient); err != nil { + return nil, err } if runAsRoleProperty, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "RUN_AS_ROLE" }); err == nil { if err = d.Set("run_as_role", runAsRoleProperty.Value); err != nil { @@ -158,10 +167,16 @@ func ImportScimIntegration(ctx context.Context, d *schema.ResourceData, meta any return nil, err } } - if syncPasswordProperty, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "SYNC_PASSWORD" }); err == nil { - if err = d.Set("sync_password", syncPasswordProperty.Value); err != nil { + if scimClient == string(sdk.ScimSecurityIntegrationScimClientAzure) { + if err = d.Set("sync_password", BooleanDefault); err != nil { return nil, err } + } else { + if syncPasswordProperty, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "SYNC_PASSWORD" }); err == nil { + if err = d.Set("sync_password", syncPasswordProperty.Value); err != nil { + return nil, err + } + } } if err = d.Set("comment", integration.Comment); err != nil { return nil, err @@ -192,6 +207,15 @@ func CreateContextSCIMIntegration(ctx context.Context, d *schema.ResourceData, m } if v := d.Get("sync_password").(string); v != BooleanDefault { + if scimClient := d.Get("scim_client").(string); scimClient == string(sdk.ScimSecurityIntegrationScimClientAzure) { + return diag.Diagnostics{ + { + Severity: diag.Error, + Summary: "field `sync_password` is not supported for scim_client = \"AZURE\"", + Detail: "can not CREATE scim integration with field `sync_password` for scim_client = \"AZURE\"", + }, + } + } parsed, err := strconv.ParseBool(v) if err != nil { return diag.FromErr(err) @@ -288,17 +312,23 @@ func ReadContextSCIMIntegration(withExternalChangesMarking bool) schema.ReadCont return diag.FromErr(err) } - syncPasswordProperty, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "SYNC_PASSWORD" }) - if err != nil { - return diag.FromErr(err) - } - if err = handleExternalChangesToObjectInDescribe(d, describeMapping{"network_policy", "network_policy", networkPolicyProperty.Value, networkPolicyProperty.Value, nil}, - describeMapping{"sync_password", "sync_password", syncPasswordProperty.Value, syncPasswordProperty.Value, nil}, ); err != nil { return diag.FromErr(err) } + + if scimClient != string(sdk.ScimSecurityIntegrationScimClientAzure) { + syncPasswordProperty, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "SYNC_PASSWORD" }) + if err != nil { + return diag.FromErr(err) + } + if err = handleExternalChangesToObjectInDescribe(d, + describeMapping{"sync_password", "sync_password", syncPasswordProperty.Value, syncPasswordProperty.Value, nil}, + ); err != nil { + return diag.FromErr(err) + } + } } // These are all identity sets, needed for the case where: @@ -347,6 +377,15 @@ func UpdateContextSCIMIntegration(ctx context.Context, d *schema.ResourceData, m } if d.HasChange("sync_password") { + if scimClient := d.Get("scim_client").(string); scimClient == string(sdk.ScimSecurityIntegrationScimClientAzure) { + return diag.Diagnostics{ + { + Severity: diag.Error, + Summary: "field `sync_password` is not supported for scim_client = \"AZURE\"", + Detail: "can not SET and UNSET field `sync_password` for scim_client = \"AZURE\"", + }, + } + } if v := d.Get("sync_password").(string); v != BooleanDefault { parsed, err := strconv.ParseBool(v) if err != nil { diff --git a/pkg/resources/scim_integration_acceptance_test.go b/pkg/resources/scim_integration_acceptance_test.go index 4c3708b772..b42bcf2b56 100644 --- a/pkg/resources/scim_integration_acceptance_test.go +++ b/pkg/resources/scim_integration_acceptance_test.go @@ -208,6 +208,53 @@ func TestAcc_ScimIntegration_complete(t *testing.T) { }) } +func TestAcc_ScimIntegration_completeAzure(t *testing.T) { + networkPolicy, networkPolicyCleanup := acc.TestClient().NetworkPolicy.CreateNetworkPolicy(t) + t.Cleanup(networkPolicyCleanup) + id := acc.TestClient().Ids.RandomAccountObjectIdentifier() + role := snowflakeroles.AadProvisioner + m := func() map[string]config.Variable { + return map[string]config.Variable{ + "name": config.StringVariable(id.Name()), + "enabled": config.BoolVariable(false), + "scim_client": config.StringVariable(string(sdk.ScimSecurityIntegrationScimClientAzure)), + "network_policy_name": config.StringVariable(networkPolicy.Name), + "run_as_role": config.StringVariable(role.Name()), + "comment": config.StringVariable("foo"), + } + } + 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.ScimSecurityIntegration), + Steps: []resource.TestStep{ + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ScimIntegration/completeAzure"), + ConfigVariables: m(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("snowflake_scim_integration.test", "name", id.Name()), + resource.TestCheckResourceAttr("snowflake_scim_integration.test", "enabled", "false"), + resource.TestCheckResourceAttr("snowflake_scim_integration.test", "scim_client", string(sdk.ScimSecurityIntegrationScimClientAzure)), + resource.TestCheckResourceAttr("snowflake_scim_integration.test", "run_as_role", role.Name()), + resource.TestCheckResourceAttr("snowflake_scim_integration.test", "network_policy", sdk.NewAccountObjectIdentifier(networkPolicy.Name).Name()), // TODO(SNOW-999049): Fix during identifiers rework + resource.TestCheckResourceAttr("snowflake_scim_integration.test", "sync_password", r.BooleanDefault), + resource.TestCheckResourceAttr("snowflake_scim_integration.test", "comment", "foo"), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ScimIntegration/completeAzure"), + ConfigVariables: m(), + ResourceName: "snowflake_scim_integration.test", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func TestAcc_ScimIntegration_InvalidScimClient(t *testing.T) { id := acc.TestClient().Ids.RandomAccountObjectIdentifier() m := func() map[string]config.Variable { @@ -292,7 +339,77 @@ func TestAcc_ScimIntegration_InvalidIncomplete(t *testing.T) { }) } -func TestAcc_ScimIntegration_migrateFromVersion093EnabledTrue(t *testing.T) { +func TestAcc_ScimIntegration_InvalidCreateWithSyncPasswordWithAzure(t *testing.T) { + id := acc.TestClient().Ids.RandomAccountObjectIdentifier() + m := func() map[string]config.Variable { + return map[string]config.Variable{ + "name": config.StringVariable(id.Name()), + "scim_client": config.StringVariable(string(sdk.ScimSecurityIntegrationScimClientAzure)), + "run_as_role": config.StringVariable(snowflakeroles.AadProvisioner.Name()), + "enabled": config.BoolVariable(true), + "sync_password": config.BoolVariable(false), + "network_policy_name": config.StringVariable(""), + "comment": config.StringVariable("foo"), + } + } + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + ErrorCheck: helpers.AssertErrorContainsPartsFunc(t, []string{ + "can not CREATE scim integration with field `sync_password` for scim_client = \"AZURE\"", + }), + Steps: []resource.TestStep{ + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ScimIntegration/complete"), + ConfigVariables: m(), + }, + }, + }) +} + +func TestAcc_ScimIntegration_InvalidUpdateWithSyncPasswordWithAzure(t *testing.T) { + id := acc.TestClient().Ids.RandomAccountObjectIdentifier() + m := func(complete bool) map[string]config.Variable { + c := map[string]config.Variable{ + "name": config.StringVariable(id.Name()), + "scim_client": config.StringVariable(string(sdk.ScimSecurityIntegrationScimClientAzure)), + "run_as_role": config.StringVariable(snowflakeroles.AadProvisioner.Name()), + "enabled": config.BoolVariable(true), + } + if complete { + c["sync_password"] = config.BoolVariable(true) + c["network_policy_name"] = config.StringVariable("") + c["comment"] = config.StringVariable("foo") + } + return c + } + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + ErrorCheck: helpers.AssertErrorContainsPartsFunc(t, []string{ + "can not SET and UNSET field `sync_password` for scim_client = \"AZURE\"", + }), + Steps: []resource.TestStep{ + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ScimIntegration/basic"), + ConfigVariables: m(false), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ScimIntegration/complete"), + ConfigVariables: m(true), + }, + }, + }) +} + +func TestAcc_ScimIntegration_migrateFromVersion092EnabledTrue(t *testing.T) { id := acc.TestClient().Ids.RandomAccountObjectIdentifier() role := snowflakeroles.GenericScimProvisioner resourceName := "snowflake_scim_integration.test" @@ -310,7 +427,7 @@ func TestAcc_ScimIntegration_migrateFromVersion093EnabledTrue(t *testing.T) { Source: "Snowflake-Labs/snowflake", }, }, - Config: scimIntegrationv092(id.Name(), role.Name()), + Config: scimIntegrationv092(id.Name(), role.Name(), sdk.ScimSecurityIntegrationScimClientGeneric), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "name", id.Name()), resource.TestCheckResourceAttr(resourceName, "provisioner_role", role.Name()), @@ -318,7 +435,7 @@ func TestAcc_ScimIntegration_migrateFromVersion093EnabledTrue(t *testing.T) { }, { ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, - Config: scimIntegrationv093(id.Name(), role.Name(), true), + Config: scimIntegrationv093(id.Name(), role.Name(), true, sdk.ScimSecurityIntegrationScimClientGeneric), ConfigPlanChecks: resource.ConfigPlanChecks{ PreApply: []plancheck.PlanCheck{ planchecks.ExpectChange("snowflake_scim_integration.test", "name", tfjson.ActionUpdate, sdk.String(id.Name()), sdk.String(id.Name())), @@ -358,7 +475,7 @@ func TestAcc_ScimIntegration_migrateFromVersion092EnabledFalse(t *testing.T) { Source: "Snowflake-Labs/snowflake", }, }, - Config: scimIntegrationv092(id.Name(), role.Name()), + Config: scimIntegrationv092(id.Name(), role.Name(), sdk.ScimSecurityIntegrationScimClientGeneric), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr(resourceName, "name", id.Name()), resource.TestCheckResourceAttr(resourceName, "provisioner_role", role.Name()), @@ -366,7 +483,7 @@ func TestAcc_ScimIntegration_migrateFromVersion092EnabledFalse(t *testing.T) { }, { ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, - Config: scimIntegrationv093(id.Name(), role.Name(), false), + Config: scimIntegrationv093(id.Name(), role.Name(), false, sdk.ScimSecurityIntegrationScimClientGeneric), ConfigPlanChecks: resource.ConfigPlanChecks{ PreApply: []plancheck.PlanCheck{plancheck.ExpectNonEmptyPlan()}, }, @@ -380,7 +497,61 @@ func TestAcc_ScimIntegration_migrateFromVersion092EnabledFalse(t *testing.T) { }) } -func scimIntegrationv092(name, roleName string) string { +func TestAcc_ScimIntegration_migrateFromVersion093HandleSyncPassword(t *testing.T) { + id := acc.TestClient().Ids.RandomAccountObjectIdentifier() + role := snowflakeroles.AadProvisioner + resourceName := "snowflake_scim_integration.test" + resource.Test(t, resource.TestCase{ + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + + Steps: []resource.TestStep{ + { + ExternalProviders: map[string]resource.ExternalProvider{ + "snowflake": { + VersionConstraint: "=0.92.0", + Source: "Snowflake-Labs/snowflake", + }, + }, + Config: scimIntegrationv092(id.Name(), role.Name(), sdk.ScimSecurityIntegrationScimClientAzure), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", id.Name()), + ), + }, + { + ExternalProviders: map[string]resource.ExternalProvider{ + "snowflake": { + VersionConstraint: "=0.93.0", + Source: "Snowflake-Labs/snowflake", + }, + }, + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{plancheck.ExpectNonEmptyPlan()}, + }, + Config: scimIntegrationv093(id.Name(), role.Name(), true, sdk.ScimSecurityIntegrationScimClientAzure), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", id.Name()), + ), + ExpectError: regexp.MustCompile("invalid property 'SYNC_PASSWORD' for 'INTEGRATION"), + }, + { + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + Config: scimIntegrationv093(id.Name(), role.Name(), true, sdk.ScimSecurityIntegrationScimClientAzure), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{plancheck.ExpectEmptyPlan()}, + }, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "name", id.Name()), + resource.TestCheckResourceAttr(resourceName, "sync_password", r.BooleanDefault), + ), + }, + }, + }) +} + +func scimIntegrationv092(name, roleName string, scimClient sdk.ScimSecurityIntegrationScimClientOption) string { s := ` resource "snowflake_scim_integration" "test" { name = "%s" @@ -388,10 +559,10 @@ resource "snowflake_scim_integration" "test" { provisioner_role = "%s" } ` - return fmt.Sprintf(s, name, sdk.ScimSecurityIntegrationScimClientGeneric, roleName) + return fmt.Sprintf(s, name, scimClient, roleName) } -func scimIntegrationv093(name, roleName string, enabled bool) string { +func scimIntegrationv093(name, roleName string, enabled bool, scimClient sdk.ScimSecurityIntegrationScimClientOption) string { s := ` resource "snowflake_scim_integration" "test" { name = "%s" @@ -400,5 +571,5 @@ resource "snowflake_scim_integration" "test" { run_as_role = "%s" } ` - return fmt.Sprintf(s, name, enabled, sdk.ScimSecurityIntegrationScimClientGeneric, roleName) + return fmt.Sprintf(s, name, enabled, scimClient, roleName) } diff --git a/pkg/resources/scim_integration_state_upgraders.go b/pkg/resources/scim_integration_state_upgraders.go index 9e4c2db70e..4fb1dc942b 100644 --- a/pkg/resources/scim_integration_state_upgraders.go +++ b/pkg/resources/scim_integration_state_upgraders.go @@ -3,6 +3,9 @@ package resources import ( "context" "strconv" + "strings" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" ) func v092ScimIntegrationStateUpgrader(ctx context.Context, rawState map[string]any, meta any) (map[string]any, error) { @@ -19,3 +22,15 @@ func v092ScimIntegrationStateUpgrader(ctx context.Context, rawState map[string]a return rawState, nil } + +func v093ScimIntegrationStateUpgrader(ctx context.Context, rawState map[string]any, meta any) (map[string]any, error) { + if rawState == nil { + return rawState, nil + } + + if v, ok := rawState["scim_client"]; ok && strings.EqualFold(strings.TrimSpace(v.(string)), string(sdk.ScimSecurityIntegrationScimClientAzure)) { + rawState["sync_password"] = BooleanDefault + } + + return rawState, nil +} diff --git a/pkg/resources/testdata/TestAcc_ScimIntegration/completeAzure/test.tf b/pkg/resources/testdata/TestAcc_ScimIntegration/completeAzure/test.tf new file mode 100644 index 0000000000..ad7cd9a0cf --- /dev/null +++ b/pkg/resources/testdata/TestAcc_ScimIntegration/completeAzure/test.tf @@ -0,0 +1,8 @@ +resource "snowflake_scim_integration" "test" { + name = var.name + enabled = var.enabled + scim_client = var.scim_client + network_policy = var.network_policy_name + run_as_role = var.run_as_role + comment = var.comment +} diff --git a/pkg/resources/testdata/TestAcc_ScimIntegration/completeAzure/variables.tf b/pkg/resources/testdata/TestAcc_ScimIntegration/completeAzure/variables.tf new file mode 100644 index 0000000000..8a2d104e63 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_ScimIntegration/completeAzure/variables.tf @@ -0,0 +1,18 @@ +variable "name" { + type = string +} +variable "enabled" { + type = bool +} +variable "scim_client" { + type = string +} +variable "network_policy_name" { + type = string +} +variable "run_as_role" { + type = string +} +variable "comment" { + type = string +} From 12669523b24bd4eabff8ceafc4b496d2068614a9 Mon Sep 17 00:00:00 2001 From: Jakub Michalak Date: Mon, 22 Jul 2024 13:33:10 +0200 Subject: [PATCH 2/6] Add entries to migration guide, add sdk validations --- MIGRATION_GUIDE.md | 16 ++++++++--- pkg/resources/scim_integration.go | 27 +++++++------------ .../scim_integration_acceptance_test.go | 20 +++++++++----- pkg/sdk/security_integrations_gen_test.go | 7 +++++ .../security_integrations_validations_gen.go | 3 +++ 5 files changed, 44 insertions(+), 29 deletions(-) diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md index 020a152886..173fc50785 100644 --- a/MIGRATION_GUIDE.md +++ b/MIGRATION_GUIDE.md @@ -6,6 +6,10 @@ across different versions. ## v0.93.0 ➞ v0.94.0 +### *(breaking change)* changes in snowflake_scim_integration + +In order to fix issues in v0.93.0, when a resource has Azure scim client, `sync_password` field is now set to `default` value in the state. State will be migrated automatically. + ### *(new feature)* new snowflake_account_role resource Already existing `snowflake_role` was deprecated in favor of the new `snowflake_account_role`. The old resource got upgraded to @@ -47,10 +51,10 @@ See reference [doc](https://docs.snowflake.com/en/sql-reference/sql/create-secur ### *(new feature)* snowflake_oauth_integration_for_custom_clients and snowflake_oauth_integration_for_partner_applications resources -To enhance clarity and functionality, the new resources `snowflake_oauth_integration_for_custom_clients` and `snowflake_oauth_integration_for_partner_applications` have been introduced +To enhance clarity and functionality, the new resources `snowflake_oauth_integration_for_custom_clients` and `snowflake_oauth_integration_for_partner_applications` have been introduced to replace the previous `snowflake_oauth_integration`. Recognizing that the old resource carried multiple responsibilities within a single entity, we opted to divide it into two more specialized resources. -The newly introduced resources are aligned with the latest Snowflake documentation at the time of implementation, and adhere to our [new conventions](#general-changes). -This segregation was based on the `oauth_client` attribute, where `CUSTOM` corresponds to `snowflake_oauth_integration_for_custom_clients`, +The newly introduced resources are aligned with the latest Snowflake documentation at the time of implementation, and adhere to our [new conventions](#general-changes). +This segregation was based on the `oauth_client` attribute, where `CUSTOM` corresponds to `snowflake_oauth_integration_for_custom_clients`, while other attributes align with `snowflake_oauth_integration_for_partner_applications`. ### *(new feature)* snowflake_security_integrations datasource @@ -105,7 +109,7 @@ The fields listed below had diff suppress which removed '-' from strings. Now, t ### *(new feature)* snowflake_saml2_integration resource The new `snowflake_saml2_integration` is introduced and deprecates `snowflake_saml_integration`. It contains new fields -and follows our new conventions making it more stable. The old SAML integration wasn't changed, so no migration needed, +and follows our new conventions making it more stable. The old SAML integration wasn't changed, so no migration needed, but we recommend to eventually migrate to the newer counterpart. ### snowflake_scim_integration resource changes @@ -113,6 +117,10 @@ but we recommend to eventually migrate to the newer counterpart. Now, the `sync_password` field will set the state value to `default` whenever the value is not set in the config. This indicates that the value on the Snowflake side is set to the Snowflake default. +> [!WARNING] +> This change causes issues for Azure scim client (see [#2946](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2946)). The workaround is to remove the resource from the state with `terraform state rm`, add `sync_password = true` to the config, and import with `terraform import "snowflake_scim_integration.test" "aad_provisioning"`. After these steps, there should be no errors and no diff on this field. This behavior is fixed in v0.94 with state upgrader. + + #### *(behavior change)* Renamed fields Renamed field `provisioner_role` to `run_as_role` to align with Snowflake docs. Please rename this field in your configuration files. State will be migrated automatically. diff --git a/pkg/resources/scim_integration.go b/pkg/resources/scim_integration.go index ecfd3d1694..050349881a 100644 --- a/pkg/resources/scim_integration.go +++ b/pkg/resources/scim_integration.go @@ -167,7 +167,7 @@ func ImportScimIntegration(ctx context.Context, d *schema.ResourceData, meta any return nil, err } } - if scimClient == string(sdk.ScimSecurityIntegrationScimClientAzure) { + if strings.EqualFold(strings.TrimSpace(scimClient), string(sdk.ScimSecurityIntegrationScimClientAzure)) { if err = d.Set("sync_password", BooleanDefault); err != nil { return nil, err } @@ -207,7 +207,7 @@ func CreateContextSCIMIntegration(ctx context.Context, d *schema.ResourceData, m } if v := d.Get("sync_password").(string); v != BooleanDefault { - if scimClient := d.Get("scim_client").(string); scimClient == string(sdk.ScimSecurityIntegrationScimClientAzure) { + if scimClient := d.Get("scim_client").(string); strings.EqualFold(strings.TrimSpace(scimClient), string(sdk.ScimSecurityIntegrationScimClientAzure)) { return diag.Diagnostics{ { Severity: diag.Error, @@ -318,7 +318,7 @@ func ReadContextSCIMIntegration(withExternalChangesMarking bool) schema.ReadCont return diag.FromErr(err) } - if scimClient != string(sdk.ScimSecurityIntegrationScimClientAzure) { + if !strings.EqualFold(strings.TrimSpace(scimClient), string(sdk.ScimSecurityIntegrationScimClientAzure)) { syncPasswordProperty, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "SYNC_PASSWORD" }) if err != nil { return diag.FromErr(err) @@ -331,20 +331,11 @@ func ReadContextSCIMIntegration(withExternalChangesMarking bool) schema.ReadCont } } - // These are all identity sets, needed for the case where: - // - previous config was empty (therefore Snowflake defaults had been used) - // - new config have the same values that are already in SF - if !d.GetRawConfig().IsNull() { - if v := d.GetRawConfig().AsValueMap()["network_policy"]; !v.IsNull() { - if err = d.Set("network_policy", v.AsString()); err != nil { - return diag.FromErr(err) - } - } - if v := d.GetRawConfig().AsValueMap()["sync_password"]; !v.IsNull() { - if err = d.Set("sync_password", v.AsString()); err != nil { - return diag.FromErr(err) - } - } + if err = setStateToValuesFromConfig(d, saml2IntegrationSchema, []string{ + "network_policy", + "sync_password", + }); err != nil { + return diag.FromErr(err) } if err = d.Set(ShowOutputAttributeName, []map[string]any{schemas.SecurityIntegrationToSchema(integration)}); err != nil { @@ -377,7 +368,7 @@ func UpdateContextSCIMIntegration(ctx context.Context, d *schema.ResourceData, m } if d.HasChange("sync_password") { - if scimClient := d.Get("scim_client").(string); scimClient == string(sdk.ScimSecurityIntegrationScimClientAzure) { + if scimClient := d.Get("scim_client").(string); strings.EqualFold(strings.TrimSpace(scimClient), string(sdk.ScimSecurityIntegrationScimClientAzure)) { return diag.Diagnostics{ { Severity: diag.Error, diff --git a/pkg/resources/scim_integration_acceptance_test.go b/pkg/resources/scim_integration_acceptance_test.go index b42bcf2b56..9b35922a23 100644 --- a/pkg/resources/scim_integration_acceptance_test.go +++ b/pkg/resources/scim_integration_acceptance_test.go @@ -212,7 +212,7 @@ func TestAcc_ScimIntegration_completeAzure(t *testing.T) { networkPolicy, networkPolicyCleanup := acc.TestClient().NetworkPolicy.CreateNetworkPolicy(t) t.Cleanup(networkPolicyCleanup) id := acc.TestClient().Ids.RandomAccountObjectIdentifier() - role := snowflakeroles.AadProvisioner + role := snowflakeroles.GenericScimProvisioner m := func() map[string]config.Variable { return map[string]config.Variable{ "name": config.StringVariable(id.Name()), @@ -345,7 +345,7 @@ func TestAcc_ScimIntegration_InvalidCreateWithSyncPasswordWithAzure(t *testing.T return map[string]config.Variable{ "name": config.StringVariable(id.Name()), "scim_client": config.StringVariable(string(sdk.ScimSecurityIntegrationScimClientAzure)), - "run_as_role": config.StringVariable(snowflakeroles.AadProvisioner.Name()), + "run_as_role": config.StringVariable(snowflakeroles.GenericScimProvisioner.Name()), "enabled": config.BoolVariable(true), "sync_password": config.BoolVariable(false), "network_policy_name": config.StringVariable(""), @@ -359,7 +359,7 @@ func TestAcc_ScimIntegration_InvalidCreateWithSyncPasswordWithAzure(t *testing.T tfversion.RequireAbove(tfversion.Version1_5_0), }, ErrorCheck: helpers.AssertErrorContainsPartsFunc(t, []string{ - "can not CREATE scim integration with field `sync_password` for scim_client = \"AZURE\"", + "can not CREATE scim integration with field `sync_password`", }), Steps: []resource.TestStep{ { @@ -376,7 +376,7 @@ func TestAcc_ScimIntegration_InvalidUpdateWithSyncPasswordWithAzure(t *testing.T c := map[string]config.Variable{ "name": config.StringVariable(id.Name()), "scim_client": config.StringVariable(string(sdk.ScimSecurityIntegrationScimClientAzure)), - "run_as_role": config.StringVariable(snowflakeroles.AadProvisioner.Name()), + "run_as_role": config.StringVariable(snowflakeroles.GenericScimProvisioner.Name()), "enabled": config.BoolVariable(true), } if complete { @@ -394,7 +394,7 @@ func TestAcc_ScimIntegration_InvalidUpdateWithSyncPasswordWithAzure(t *testing.T tfversion.RequireAbove(tfversion.Version1_5_0), }, ErrorCheck: helpers.AssertErrorContainsPartsFunc(t, []string{ - "can not SET and UNSET field `sync_password` for scim_client = \"AZURE\"", + "can not SET and UNSET field `sync_password`", }), Steps: []resource.TestStep{ { @@ -499,7 +499,7 @@ func TestAcc_ScimIntegration_migrateFromVersion092EnabledFalse(t *testing.T) { func TestAcc_ScimIntegration_migrateFromVersion093HandleSyncPassword(t *testing.T) { id := acc.TestClient().Ids.RandomAccountObjectIdentifier() - role := snowflakeroles.AadProvisioner + role := snowflakeroles.GenericScimProvisioner resourceName := "snowflake_scim_integration.test" resource.Test(t, resource.TestCase{ PreCheck: func() { acc.TestAccPreCheck(t) }, @@ -508,6 +508,7 @@ func TestAcc_ScimIntegration_migrateFromVersion093HandleSyncPassword(t *testing. }, Steps: []resource.TestStep{ + // create resource with v0.92 { ExternalProviders: map[string]resource.ExternalProvider{ "snowflake": { @@ -520,6 +521,7 @@ func TestAcc_ScimIntegration_migrateFromVersion093HandleSyncPassword(t *testing. resource.TestCheckResourceAttr(resourceName, "name", id.Name()), ), }, + // migrate to v0.93 - there is a diff due to new field sync_password in state { ExternalProviders: map[string]resource.ExternalProvider{ "snowflake": { @@ -528,7 +530,10 @@ func TestAcc_ScimIntegration_migrateFromVersion093HandleSyncPassword(t *testing. }, }, ConfigPlanChecks: resource.ConfigPlanChecks{ - PreApply: []plancheck.PlanCheck{plancheck.ExpectNonEmptyPlan()}, + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectNonEmptyPlan(), + planchecks.ExpectChange(resourceName, "sync_password", tfjson.ActionUpdate, nil, sdk.String(r.BooleanDefault)), + }, }, Config: scimIntegrationv093(id.Name(), role.Name(), true, sdk.ScimSecurityIntegrationScimClientAzure), Check: resource.ComposeTestCheckFunc( @@ -536,6 +541,7 @@ func TestAcc_ScimIntegration_migrateFromVersion093HandleSyncPassword(t *testing. ), ExpectError: regexp.MustCompile("invalid property 'SYNC_PASSWORD' for 'INTEGRATION"), }, + // check with newest version - the value in state was set to boolean default, so there should be no diff { ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, Config: scimIntegrationv093(id.Name(), role.Name(), true, sdk.ScimSecurityIntegrationScimClientAzure), diff --git a/pkg/sdk/security_integrations_gen_test.go b/pkg/sdk/security_integrations_gen_test.go index 64de07e48d..ab850dca6e 100644 --- a/pkg/sdk/security_integrations_gen_test.go +++ b/pkg/sdk/security_integrations_gen_test.go @@ -415,6 +415,13 @@ func TestSecurityIntegrations_CreateScim(t *testing.T) { assertOptsInvalidJoinedErrors(t, opts, errOneOf("CreateScimSecurityIntegrationOptions", "OrReplace", "IfNotExists")) }) + t.Run("validation: conflicting SyncPassword for Azure ScimClient", func(t *testing.T) { + opts := defaultOpts() + opts.ScimClient = ScimSecurityIntegrationScimClientAzure + opts.SyncPassword = Pointer(true) + assertOptsInvalidJoinedErrors(t, opts, NewError("SyncPassword is not supported for Azure scim client")) + }) + t.Run("basic", func(t *testing.T) { opts := defaultOpts() opts.OrReplace = Pointer(true) diff --git a/pkg/sdk/security_integrations_validations_gen.go b/pkg/sdk/security_integrations_validations_gen.go index 131ae527fb..9aa903a862 100644 --- a/pkg/sdk/security_integrations_validations_gen.go +++ b/pkg/sdk/security_integrations_validations_gen.go @@ -143,6 +143,9 @@ func (opts *CreateScimSecurityIntegrationOptions) validate() error { if everyValueSet(opts.OrReplace, opts.IfNotExists) { errs = append(errs, errOneOf("CreateScimSecurityIntegrationOptions", "OrReplace", "IfNotExists")) } + if opts.ScimClient == ScimSecurityIntegrationScimClientAzure && opts.SyncPassword != nil { + errs = append(errs, NewError("SyncPassword is not supported for Azure scim client")) + } return JoinErrors(errs...) } From 90b8c4b98d59e3ed152e763433cc789691af5373 Mon Sep 17 00:00:00 2001 From: Jakub Michalak Date: Mon, 22 Jul 2024 14:34:28 +0200 Subject: [PATCH 3/6] Fix test --- pkg/resources/scim_integration_acceptance_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/resources/scim_integration_acceptance_test.go b/pkg/resources/scim_integration_acceptance_test.go index 9b35922a23..159b31e874 100644 --- a/pkg/resources/scim_integration_acceptance_test.go +++ b/pkg/resources/scim_integration_acceptance_test.go @@ -380,7 +380,7 @@ func TestAcc_ScimIntegration_InvalidUpdateWithSyncPasswordWithAzure(t *testing.T "enabled": config.BoolVariable(true), } if complete { - c["sync_password"] = config.BoolVariable(true) + c["sync_password"] = config.BoolVariable(false) c["network_policy_name"] = config.StringVariable("") c["comment"] = config.StringVariable("foo") } From e4984c159113d43b9e1fcb1b98d034965b061d2e Mon Sep 17 00:00:00 2001 From: Jakub Michalak Date: Tue, 23 Jul 2024 08:15:37 +0200 Subject: [PATCH 4/6] Fix schemas in setStateToValuesFromConfig --- pkg/resources/api_authentication_integration_common.go | 2 +- ...hentication_integration_with_authorization_code_grant.go | 2 +- ...pi_authentication_integration_with_client_credentials.go | 2 +- .../api_authentication_integration_with_jwt_bearer.go | 2 +- pkg/resources/external_oauth_integration.go | 6 +++--- pkg/resources/scim_integration.go | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pkg/resources/api_authentication_integration_common.go b/pkg/resources/api_authentication_integration_common.go index bee53e091b..aafad99b24 100644 --- a/pkg/resources/api_authentication_integration_common.go +++ b/pkg/resources/api_authentication_integration_common.go @@ -335,7 +335,7 @@ func handleApiAuthRead(d *schema.ResourceData, return err } } - if err := setStateToValuesFromConfig(d, warehouseSchema, []string{ + if err := setStateToValuesFromConfig(d, apiAuthCommonSchema, []string{ "oauth_access_token_validity", "oauth_refresh_token_validity", "oauth_client_id", diff --git a/pkg/resources/api_authentication_integration_with_authorization_code_grant.go b/pkg/resources/api_authentication_integration_with_authorization_code_grant.go index 9c8b0cc03f..538b6440da 100644 --- a/pkg/resources/api_authentication_integration_with_authorization_code_grant.go +++ b/pkg/resources/api_authentication_integration_with_authorization_code_grant.go @@ -175,7 +175,7 @@ func ReadContextApiAuthenticationIntegrationWithAuthorizationCodeGrant(withExter }); err != nil { return diag.FromErr(err) } - if err := setStateToValuesFromConfig(d, warehouseSchema, []string{ + if err := setStateToValuesFromConfig(d, apiAuthAuthorizationCodeGrantSchema, []string{ "oauth_authorization_endpoint", "oauth_allowed_scopes", "oauth_client_auth_method", diff --git a/pkg/resources/api_authentication_integration_with_client_credentials.go b/pkg/resources/api_authentication_integration_with_client_credentials.go index 45e25ca0cd..d027493ab2 100644 --- a/pkg/resources/api_authentication_integration_with_client_credentials.go +++ b/pkg/resources/api_authentication_integration_with_client_credentials.go @@ -146,7 +146,7 @@ func ReadContextApiAuthenticationIntegrationWithClientCredentials(withExternalCh }); err != nil { return diag.FromErr(err) } - if err := setStateToValuesFromConfig(d, warehouseSchema, []string{ + if err := setStateToValuesFromConfig(d, apiAuthClientCredentialsSchema, []string{ "oauth_allowed_scopes", }); err != nil { return diag.FromErr(err) diff --git a/pkg/resources/api_authentication_integration_with_jwt_bearer.go b/pkg/resources/api_authentication_integration_with_jwt_bearer.go index df54815f89..edc612f56d 100644 --- a/pkg/resources/api_authentication_integration_with_jwt_bearer.go +++ b/pkg/resources/api_authentication_integration_with_jwt_bearer.go @@ -162,7 +162,7 @@ func ReadContextApiAuthenticationIntegrationWithJwtBearer(withExternalChangesMar }); err != nil { return diag.FromErr(err) } - if err := setStateToValuesFromConfig(d, warehouseSchema, []string{ + if err := setStateToValuesFromConfig(d, apiAuthJwtBearerSchema, []string{ "oauth_authorization_endpoint", "oauth_assertion_issuer", }); err != nil { diff --git a/pkg/resources/external_oauth_integration.go b/pkg/resources/external_oauth_integration.go index 2434dc284c..37c91ded28 100644 --- a/pkg/resources/external_oauth_integration.go +++ b/pkg/resources/external_oauth_integration.go @@ -21,7 +21,7 @@ import ( var privilegedRoles = []string{"ACCOUNTADMIN", "ORGADMIN", "SECURITYADMIN"} -var oauthExternalIntegrationSchema = map[string]*schema.Schema{ +var externalOauthIntegrationSchema = map[string]*schema.Schema{ "name": { Type: schema.TypeString, Required: true, @@ -159,7 +159,7 @@ func ExternalOauthIntegration() *schema.Resource { DeleteContext: DeleteContextExternalOauthIntegration, Description: "Resource used to manage external oauth security integration objects. For more information, check [security integrations documentation](https://docs.snowflake.com/en/sql-reference/sql/create-security-integration-oauth-external).", - Schema: oauthExternalIntegrationSchema, + Schema: externalOauthIntegrationSchema, CustomizeDiff: customdiff.All( ForceNewIfChangeToEmptyString("external_oauth_rsa_public_key"), ForceNewIfChangeToEmptyString("external_oauth_rsa_public_key_2"), @@ -521,7 +521,7 @@ func ReadContextExternalOauthIntegration(withExternalChangesMarking bool) schema } } - if err = setStateToValuesFromConfig(d, warehouseSchema, []string{ + if err = setStateToValuesFromConfig(d, externalOauthIntegrationSchema, []string{ "external_oauth_jws_keys_url", "external_oauth_rsa_public_key", "external_oauth_rsa_public_key_2", diff --git a/pkg/resources/scim_integration.go b/pkg/resources/scim_integration.go index d184145361..ba78acbeac 100644 --- a/pkg/resources/scim_integration.go +++ b/pkg/resources/scim_integration.go @@ -331,7 +331,7 @@ func ReadContextSCIMIntegration(withExternalChangesMarking bool) schema.ReadCont } } - if err = setStateToValuesFromConfig(d, saml2IntegrationSchema, []string{ + if err = setStateToValuesFromConfig(d, scimIntegrationSchema, []string{ "network_policy", "sync_password", }); err != nil { From e656eba6199bd87dc11138981f3fe3fe7e5baaae Mon Sep 17 00:00:00 2001 From: Jakub Michalak Date: Tue, 23 Jul 2024 10:06:58 +0200 Subject: [PATCH 5/6] Do not return error on missing allowed_email_patterns and allowed_user_domains --- pkg/resources/saml2_integration.go | 37 +++++++++++++++++------------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/pkg/resources/saml2_integration.go b/pkg/resources/saml2_integration.go index d528c7db2e..bc484a13a9 100644 --- a/pkg/resources/saml2_integration.go +++ b/pkg/resources/saml2_integration.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "log" "reflect" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/collections" @@ -323,20 +324,22 @@ func ImportSaml2Integration(ctx context.Context, d *schema.ResourceData, meta an return property.Name == "ALLOWED_USER_DOMAINS" }) if err != nil { - return nil, fmt.Errorf("failed to find allowed user domains, err = %w", err) - } - if err := d.Set("allowed_user_domains", sdk.ParseCommaSeparatedStringArray(allowedUserDomains.Value, false)); err != nil { - return nil, err + log.Printf("[DEBUG] failed to find allowed user domains, err = %v", err) + } else { + if err := d.Set("allowed_user_domains", sdk.ParseCommaSeparatedStringArray(allowedUserDomains.Value, false)); err != nil { + return nil, err + } } allowedEmailDomains, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "ALLOWED_EMAIL_PATTERNS" }) if err != nil { - return nil, fmt.Errorf("failed to find allowed email patterns, err = %w", err) - } - if err := d.Set("allowed_email_patterns", sdk.ParseCommaSeparatedStringArray(allowedEmailDomains.Value, false)); err != nil { - return nil, err + log.Printf("[DEBUG] failed to find allowed email patterns, err = %v", err) + } else { + if err := d.Set("allowed_email_patterns", sdk.ParseCommaSeparatedStringArray(allowedEmailDomains.Value, false)); err != nil { + return nil, err + } } return []*schema.ResourceData{d}, nil @@ -533,20 +536,22 @@ func ReadContextSAML2Integration(withExternalChangesMarking bool) schema.ReadCon return property.Name == "ALLOWED_USER_DOMAINS" }) if err != nil { - return diag.FromErr(fmt.Errorf("failed to find allowed user domains, err = %w", err)) - } - if err := d.Set("allowed_user_domains", sdk.ParseCommaSeparatedStringArray(allowedUserDomains.Value, false)); err != nil { - return diag.FromErr(err) + log.Printf("[DEBUG] failed to find allowed user domains, err = %v", err) + } else { + if err := d.Set("allowed_user_domains", sdk.ParseCommaSeparatedStringArray(allowedUserDomains.Value, false)); err != nil { + return diag.FromErr(err) + } } allowedEmailDomains, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "ALLOWED_EMAIL_PATTERNS" }) if err != nil { - return diag.FromErr(fmt.Errorf("failed to find allowed email patterns, err = %w", err)) - } - if err := d.Set("allowed_email_patterns", sdk.ParseCommaSeparatedStringArray(allowedEmailDomains.Value, false)); err != nil { - return diag.FromErr(err) + log.Printf("[DEBUG] failed to find allowed email patterns, err = %v", err) + } else { + if err := d.Set("allowed_email_patterns", sdk.ParseCommaSeparatedStringArray(allowedEmailDomains.Value, false)); err != nil { + return diag.FromErr(err) + } } if err := d.Set("comment", integration.Comment); err != nil { From 76d8c3a303363f7626c3ab0b1bb8cb19f78caad1 Mon Sep 17 00:00:00 2001 From: Jakub Michalak Date: Tue, 23 Jul 2024 13:15:02 +0200 Subject: [PATCH 6/6] Extract optional fields handling --- pkg/resources/saml2_integration.go | 55 +++++++++++------------------- 1 file changed, 19 insertions(+), 36 deletions(-) diff --git a/pkg/resources/saml2_integration.go b/pkg/resources/saml2_integration.go index bc484a13a9..f1261391fc 100644 --- a/pkg/resources/saml2_integration.go +++ b/pkg/resources/saml2_integration.go @@ -320,31 +320,28 @@ func ImportSaml2Integration(ctx context.Context, d *schema.ResourceData, meta an return nil, err } - allowedUserDomains, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { - return property.Name == "ALLOWED_USER_DOMAINS" - }) - if err != nil { - log.Printf("[DEBUG] failed to find allowed user domains, err = %v", err) - } else { - if err := d.Set("allowed_user_domains", sdk.ParseCommaSeparatedStringArray(allowedUserDomains.Value, false)); err != nil { - return nil, err - } + if err := d.Set("allowed_user_domains", getOptionalListField(integrationProperties, "ALLOWED_USER_DOMAINS")); err != nil { + return nil, err } - allowedEmailDomains, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { - return property.Name == "ALLOWED_EMAIL_PATTERNS" - }) - if err != nil { - log.Printf("[DEBUG] failed to find allowed email patterns, err = %v", err) - } else { - if err := d.Set("allowed_email_patterns", sdk.ParseCommaSeparatedStringArray(allowedEmailDomains.Value, false)); err != nil { - return nil, err - } + if err := d.Set("allowed_email_patterns", getOptionalListField(integrationProperties, "ALLOWED_EMAIL_PATTERNS")); err != nil { + return nil, err } return []*schema.ResourceData{d}, nil } +func getOptionalListField(props []sdk.SecurityIntegrationProperty, propName string) []string { + found, err := collections.FindOne(props, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == propName + }) + if err != nil { + log.Printf("[DEBUG] failed to find %s in object properties, err = %v", propName, err) + return make([]string, 0) + } + return sdk.ParseCommaSeparatedStringArray(found.Value, false) +} + func CreateContextSAML2Integration(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics { client := meta.(*provider.Context).Client @@ -532,26 +529,12 @@ func ReadContextSAML2Integration(withExternalChangesMarking bool) schema.ReadCon return diag.FromErr(err) } - allowedUserDomains, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { - return property.Name == "ALLOWED_USER_DOMAINS" - }) - if err != nil { - log.Printf("[DEBUG] failed to find allowed user domains, err = %v", err) - } else { - if err := d.Set("allowed_user_domains", sdk.ParseCommaSeparatedStringArray(allowedUserDomains.Value, false)); err != nil { - return diag.FromErr(err) - } + if err := d.Set("allowed_user_domains", getOptionalListField(integrationProperties, "ALLOWED_USER_DOMAINS")); err != nil { + return diag.FromErr(err) } - allowedEmailDomains, err := collections.FindOne(integrationProperties, func(property sdk.SecurityIntegrationProperty) bool { - return property.Name == "ALLOWED_EMAIL_PATTERNS" - }) - if err != nil { - log.Printf("[DEBUG] failed to find allowed email patterns, err = %v", err) - } else { - if err := d.Set("allowed_email_patterns", sdk.ParseCommaSeparatedStringArray(allowedEmailDomains.Value, false)); err != nil { - return diag.FromErr(err) - } + if err := d.Set("allowed_email_patterns", getOptionalListField(integrationProperties, "ALLOWED_EMAIL_PATTERNS")); err != nil { + return diag.FromErr(err) } if err := d.Set("comment", integration.Comment); err != nil {