diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md index bbd93e1eeb..9790aa2f10 100644 --- a/MIGRATION_GUIDE.md +++ b/MIGRATION_GUIDE.md @@ -20,6 +20,14 @@ They are all described in short in the [changes before v1 doc](./v1-preparations ### old grant resources removal Following the [announcement](https://github.com/Snowflake-Labs/terraform-provider-snowflake/discussions/2736) we have removed the old grant resources. The two resources [snowflake_role_ownership_grant](https://registry.terraform.io/providers/Snowflake-Labs/snowflake/latest/docs/resources/role_ownership_grant) and [snowflake_user_ownership_grant](https://registry.terraform.io/providers/Snowflake-Labs/snowflake/latest/docs/resources/user_ownership_grant) were not listed in the announcement, but they were also marked as deprecated ones. We are removing them too to conclude the grants redesign saga. +### *(new feature)* Api authentication resources +Added new api authentication resources, i.e.: +- `snowflake_api_authentication_integration_with_authorization_code_grant` +- `snowflake_api_authentication_integration_with_client_credentials` +- `snowflake_api_authentication_integration_with_jwt_bearer` + +See reference [doc](https://docs.snowflake.com/en/sql-reference/sql/create-security-integration-api-auth). + ### *(new feature)* snowflake_security_integrations datasource Added a new datasource enabling querying and filtering all types of security integrations. Notes: - all results are stored in `security_integrations` field. @@ -129,10 +137,10 @@ All the field changes in comparison to the previous database resource are: - removed: the field is removed from `snowflake_shared_database` as it doesn't have any effect on shared databases. - `from_database` - database cloning was entirely removed and is not possible by any of the new database resources. - `from_share` - the parameter was moved to the dedicated resource for databases created from shares `snowflake_shared_database`. Right now, it's a text field instead of a map. Additionally, instead of legacy account identifier format we're expecting the new one that with share looks like this: `..`. For more information on account identifiers, visit the [official documentation](https://docs.snowflake.com/en/user-guide/admin-account-identifier). -- p, +- p, - `from_replication` - the parameter was moved to the dedicated resource for databases created from primary databases `snowflake_secondary_database` - `replication_configuration` - renamed: was renamed to `configuration` and is only available in the `snowflake_database`. Its internal schema changed that instead of list of accounts, we expect a list of nested objects with accounts for which replication (and optionally failover) should be enabled. More information about converting between both versions [here](#resource-renamed-snowflake_database---snowflake_database_old). Additionally, instead of legacy account identifier format we're expecting the new one that looks like this: `.` (it will be automatically migrated to the recommended format by the state upgrader). For more information on account identifiers, visit the [official documentation](https://docs.snowflake.com/en/user-guide/admin-account-identifier). -- `data_retention_time_in_days` +- `data_retention_time_in_days` - in `snowflake_shared_database` - removed: the field is removed from `snowflake_shared_database` as it doesn't have any effect on shared databases. - in `snowflake_database` and `snowflake_secondary_database` @@ -213,7 +221,7 @@ The only difference would be that instead of writing/generating new configuratio - `pattern` was replaced by `like` field. - Additional filtering options added (`limit`). - Added missing fields returned by SHOW DATABASES and enclosed its output in `show_output` field. -- Added outputs from **DESC DATABASE** and **SHOW PARAMETERS IN DATABASE** (they can be turned off by declaring `with_describe = false` and `with_parameters = false`, **they're turned on by default**). +- Added outputs from **DESC DATABASE** and **SHOW PARAMETERS IN DATABASE** (they can be turned off by declaring `with_describe = false` and `with_parameters = false`, **they're turned on by default**). The additional parameters call **DESC DATABASE** (with `with_describe` turned on) and **SHOW PARAMETERS IN DATABASE** (with `with_parameters` turned on) **per database** returned by **SHOW DATABASES**. The outputs of both commands are held in `databases` entry, where **DESC DATABASE** is saved in the `describe_output` field, and **SHOW PARAMETERS IN DATABASE** in the `parameters` field. It's important to limit the records and calls to Snowflake to the minimum. That's why we recommend assessing which information you need from the data source and then providing strong filters and turning off additional fields for better plan performance. @@ -237,7 +245,7 @@ resource "snowflake_tag_masking_policy_association" "name" { masking_policy_id = snowflake_masking_policy.example_masking_policy.id } ``` - + After ```terraform resource "snowflake_tag_masking_policy_association" "name" { diff --git a/docs/resources/api_authentication_integration_with_authorization_code_grant.md b/docs/resources/api_authentication_integration_with_authorization_code_grant.md new file mode 100644 index 0000000000..d011430f6a --- /dev/null +++ b/docs/resources/api_authentication_integration_with_authorization_code_grant.md @@ -0,0 +1,235 @@ +--- +page_title: "snowflake_api_authentication_integration_with_authorization_code_grant Resource - terraform-provider-snowflake" +subcategory: "" +description: |- + +--- + +!> **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) + + + +## Example Usage + +```terraform +# basic resource +resource "snowflake_api_authentication_integration_with_authorization_code_grant" "test" { + enabled = true + name = "test" + oauth_client_id = "sn-oauth-134o9erqfedlc" + oauth_client_secret = "eb9vaXsrcEvrFdfcvCaoijhilj4fc" +} +# resource with all fields set +resource "snowflake_api_authentication_integration_with_authorization_code_grant" "test" { + comment = "comment" + enabled = true + name = "test" + oauth_access_token_validity = 42 + oauth_allowed_scopes = ["useraccount"] + oauth_authorization_endpoint = "https://example.com" + oauth_client_auth_method = "CLIENT_SECRET_POST" + oauth_client_id = "sn-oauth-134o9erqfedlc" + oauth_client_secret = "eb9vaXsrcEvrFdfcvCaoijhilj4fc" + oauth_refresh_token_validity = 42 + oauth_token_endpoint = "https://example.com" +} +``` + + +## Schema + +### Required + +- `enabled` (Boolean) Specifies whether this security integration is enabled or disabled. +- `name` (String) Specifies the identifier (i.e. name) for the integration. This value must be unique in your account. +- `oauth_client_id` (String) Specifies the client ID for the OAuth application in the external service. +- `oauth_client_secret` (String) Specifies the client secret for the OAuth application in the ServiceNow instance from the previous step. The connector uses this to request an access token from the ServiceNow instance. + +### Optional + +- `comment` (String) Specifies a comment for the integration. +- `oauth_access_token_validity` (Number) Specifies the default lifetime of the OAuth access token (in seconds) issued by an OAuth server. +- `oauth_allowed_scopes` (Set of String) Specifies a list of scopes to use when making a request from the OAuth by a role with USAGE on the integration during the OAuth client credentials flow. +- `oauth_authorization_endpoint` (String) Specifies the URL for authenticating to the external service. If removed from the config, the resource is recreated. +- `oauth_client_auth_method` (String) Specifies that POST is used as the authentication method to the external service. If removed from the config, the resource is recreated. Valid values are (case-insensitive): `CLIENT_SECRET_POST`. +- `oauth_refresh_token_validity` (Number) Specifies the value to determine the validity of the refresh token obtained from the OAuth server. +- `oauth_token_endpoint` (String) Specifies the token endpoint used by the client to obtain an access token by presenting its authorization grant or refresh token. The token endpoint is used with every authorization grant except for the implicit grant type (since an access token is issued directly). If removed from the config, the resource is recreated. + +### Read-Only + +- `describe_output` (List of Object) Outputs the result of `DESCRIBE SECURITY INTEGRATIONS` for the given security integration. (see [below for nested schema](#nestedatt--describe_output)) +- `id` (String) The ID of this resource. +- `show_output` (List of Object) Outputs the result of `SHOW SECURITY INTEGRATIONS` for the given security integration. (see [below for nested schema](#nestedatt--show_output)) + + +### Nested Schema for `describe_output` + +Read-Only: + +- `auth_type` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--auth_type)) +- `comment` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--comment)) +- `enabled` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--enabled)) +- `oauth_access_token_validity` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--oauth_access_token_validity)) +- `oauth_allowed_scopes` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--oauth_allowed_scopes)) +- `oauth_authorization_endpoint` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--oauth_authorization_endpoint)) +- `oauth_client_auth_method` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--oauth_client_auth_method)) +- `oauth_client_id` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--oauth_client_id)) +- `oauth_grant` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--oauth_grant)) +- `oauth_refresh_token_validity` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--oauth_refresh_token_validity)) +- `oauth_token_endpoint` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--oauth_token_endpoint)) +- `parent_integration` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--parent_integration)) + + +### Nested Schema for `describe_output.auth_type` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.comment` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.enabled` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.oauth_access_token_validity` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.oauth_allowed_scopes` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.oauth_authorization_endpoint` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.oauth_client_auth_method` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.oauth_client_id` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.oauth_grant` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.oauth_refresh_token_validity` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.oauth_token_endpoint` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.parent_integration` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + + +### Nested Schema for `show_output` + +Read-Only: + +- `category` (String) +- `comment` (String) +- `created_on` (String) +- `enabled` (Boolean) +- `integration_type` (String) +- `name` (String) + +## Import + +Import is supported using the following syntax: + +```shell +terraform import snowflake_api_authentication_integration_with_authorization_code_grant.example "name" +``` diff --git a/docs/resources/api_authentication_integration_with_client_credentials.md b/docs/resources/api_authentication_integration_with_client_credentials.md new file mode 100644 index 0000000000..cb6a466a94 --- /dev/null +++ b/docs/resources/api_authentication_integration_with_client_credentials.md @@ -0,0 +1,232 @@ +--- +page_title: "snowflake_api_authentication_integration_with_client_credentials Resource - terraform-provider-snowflake" +subcategory: "" +description: |- + +--- + +!> **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) + + + +## Example Usage + +```terraform +# basic resource +resource "snowflake_api_authentication_integration_with_client_credentials" "test" { + enabled = true + name = "test" + oauth_client_id = "sn-oauth-134o9erqfedlc" + oauth_client_secret = "eb9vaXsrcEvrFdfcvCaoijhilj4fc" +} +# resource with all fields set +resource "snowflake_api_authentication_integration_with_client_credentials" "test" { + comment = "comment" + enabled = true + name = "test" + oauth_access_token_validity = 42 + oauth_allowed_scopes = ["useraccount"] + oauth_client_auth_method = "CLIENT_SECRET_POST" + oauth_client_id = "sn-oauth-134o9erqfedlc" + oauth_client_secret = "eb9vaXsrcEvrFdfcvCaoijhilj4fc" + oauth_token_endpoint = "https://example.com" +} +``` + + +## Schema + +### Required + +- `enabled` (Boolean) Specifies whether this security integration is enabled or disabled. +- `name` (String) Specifies the identifier (i.e. name) for the integration. This value must be unique in your account. +- `oauth_client_id` (String) Specifies the client ID for the OAuth application in the external service. +- `oauth_client_secret` (String) Specifies the client secret for the OAuth application in the ServiceNow instance from the previous step. The connector uses this to request an access token from the ServiceNow instance. + +### Optional + +- `comment` (String) Specifies a comment for the integration. +- `oauth_access_token_validity` (Number) Specifies the default lifetime of the OAuth access token (in seconds) issued by an OAuth server. +- `oauth_allowed_scopes` (Set of String) Specifies a list of scopes to use when making a request from the OAuth by a role with USAGE on the integration during the OAuth client credentials flow. +- `oauth_client_auth_method` (String) Specifies that POST is used as the authentication method to the external service. If removed from the config, the resource is recreated. Valid values are (case-insensitive): `CLIENT_SECRET_POST`. +- `oauth_refresh_token_validity` (Number) Specifies the value to determine the validity of the refresh token obtained from the OAuth server. +- `oauth_token_endpoint` (String) Specifies the token endpoint used by the client to obtain an access token by presenting its authorization grant or refresh token. The token endpoint is used with every authorization grant except for the implicit grant type (since an access token is issued directly). If removed from the config, the resource is recreated. + +### Read-Only + +- `describe_output` (List of Object) Outputs the result of `DESCRIBE SECURITY INTEGRATIONS` for the given security integration. (see [below for nested schema](#nestedatt--describe_output)) +- `id` (String) The ID of this resource. +- `show_output` (List of Object) Outputs the result of `SHOW SECURITY INTEGRATIONS` for the given security integration. (see [below for nested schema](#nestedatt--show_output)) + + +### Nested Schema for `describe_output` + +Read-Only: + +- `auth_type` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--auth_type)) +- `comment` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--comment)) +- `enabled` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--enabled)) +- `oauth_access_token_validity` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--oauth_access_token_validity)) +- `oauth_allowed_scopes` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--oauth_allowed_scopes)) +- `oauth_authorization_endpoint` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--oauth_authorization_endpoint)) +- `oauth_client_auth_method` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--oauth_client_auth_method)) +- `oauth_client_id` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--oauth_client_id)) +- `oauth_grant` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--oauth_grant)) +- `oauth_refresh_token_validity` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--oauth_refresh_token_validity)) +- `oauth_token_endpoint` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--oauth_token_endpoint)) +- `parent_integration` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--parent_integration)) + + +### Nested Schema for `describe_output.auth_type` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.comment` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.enabled` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.oauth_access_token_validity` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.oauth_allowed_scopes` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.oauth_authorization_endpoint` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.oauth_client_auth_method` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.oauth_client_id` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.oauth_grant` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.oauth_refresh_token_validity` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.oauth_token_endpoint` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.parent_integration` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + + +### Nested Schema for `show_output` + +Read-Only: + +- `category` (String) +- `comment` (String) +- `created_on` (String) +- `enabled` (Boolean) +- `integration_type` (String) +- `name` (String) + +## Import + +Import is supported using the following syntax: + +```shell +terraform import snowflake_api_authentication_integration_with_client_credentials.example "name" +``` diff --git a/docs/resources/api_authentication_integration_with_jwt_bearer.md b/docs/resources/api_authentication_integration_with_jwt_bearer.md new file mode 100644 index 0000000000..be734aab19 --- /dev/null +++ b/docs/resources/api_authentication_integration_with_jwt_bearer.md @@ -0,0 +1,236 @@ +--- +page_title: "snowflake_api_authentication_integration_with_jwt_bearer Resource - terraform-provider-snowflake" +subcategory: "" +description: |- + +--- + +!> **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) + + + +## Example Usage + +```terraform +# basic resource +resource "snowflake_api_authentication_integration_with_jwt_bearer" "test" { + enabled = true + name = "test" + oauth_client_id = "sn-oauth-134o9erqfedlc" + oauth_client_secret = "eb9vaXsrcEvrFdfcvCaoijhilj4fc" + oauth_assertion_issuer = "issuer" +} +# resource with all fields set +resource "snowflake_api_authentication_integration_with_jwt_bearer" "test" { + comment = "comment" + enabled = true + name = "test" + oauth_access_token_validity = 42 + oauth_authorization_endpoint = "https://example.com" + oauth_client_auth_method = "CLIENT_SECRET_POST" + oauth_client_id = "sn-oauth-134o9erqfedlc" + oauth_client_secret = "eb9vaXsrcEvrFdfcvCaoijhilj4fc" + oauth_refresh_token_validity = 42 + oauth_token_endpoint = "https://example.com" + oauth_assertion_issuer = "issuer" +} +``` + + +## Schema + +### Required + +- `enabled` (Boolean) Specifies whether this security integration is enabled or disabled. +- `name` (String) Specifies the identifier (i.e. name) for the integration. This value must be unique in your account. +- `oauth_assertion_issuer` (String) +- `oauth_client_id` (String) Specifies the client ID for the OAuth application in the external service. +- `oauth_client_secret` (String) Specifies the client secret for the OAuth application in the ServiceNow instance from the previous step. The connector uses this to request an access token from the ServiceNow instance. + +### Optional + +- `comment` (String) Specifies a comment for the integration. +- `oauth_access_token_validity` (Number) Specifies the default lifetime of the OAuth access token (in seconds) issued by an OAuth server. +- `oauth_authorization_endpoint` (String) Specifies the URL for authenticating to the external service. +- `oauth_client_auth_method` (String) Specifies that POST is used as the authentication method to the external service. If removed from the config, the resource is recreated. Valid values are (case-insensitive): `CLIENT_SECRET_POST`. +- `oauth_refresh_token_validity` (Number) Specifies the value to determine the validity of the refresh token obtained from the OAuth server. +- `oauth_token_endpoint` (String) Specifies the token endpoint used by the client to obtain an access token by presenting its authorization grant or refresh token. The token endpoint is used with every authorization grant except for the implicit grant type (since an access token is issued directly). If removed from the config, the resource is recreated. + +### Read-Only + +- `describe_output` (List of Object) Outputs the result of `DESCRIBE SECURITY INTEGRATIONS` for the given security integration. (see [below for nested schema](#nestedatt--describe_output)) +- `id` (String) The ID of this resource. +- `show_output` (List of Object) Outputs the result of `SHOW SECURITY INTEGRATIONS` for the given security integration. (see [below for nested schema](#nestedatt--show_output)) + + +### Nested Schema for `describe_output` + +Read-Only: + +- `auth_type` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--auth_type)) +- `comment` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--comment)) +- `enabled` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--enabled)) +- `oauth_access_token_validity` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--oauth_access_token_validity)) +- `oauth_allowed_scopes` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--oauth_allowed_scopes)) +- `oauth_authorization_endpoint` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--oauth_authorization_endpoint)) +- `oauth_client_auth_method` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--oauth_client_auth_method)) +- `oauth_client_id` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--oauth_client_id)) +- `oauth_grant` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--oauth_grant)) +- `oauth_refresh_token_validity` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--oauth_refresh_token_validity)) +- `oauth_token_endpoint` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--oauth_token_endpoint)) +- `parent_integration` (List of Object) (see [below for nested schema](#nestedobjatt--describe_output--parent_integration)) + + +### Nested Schema for `describe_output.auth_type` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.comment` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.enabled` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.oauth_access_token_validity` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.oauth_allowed_scopes` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.oauth_authorization_endpoint` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.oauth_client_auth_method` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.oauth_client_id` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.oauth_grant` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.oauth_refresh_token_validity` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.oauth_token_endpoint` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + +### Nested Schema for `describe_output.parent_integration` + +Read-Only: + +- `default` (String) +- `name` (String) +- `type` (String) +- `value` (String) + + + + +### Nested Schema for `show_output` + +Read-Only: + +- `category` (String) +- `comment` (String) +- `created_on` (String) +- `enabled` (Boolean) +- `integration_type` (String) +- `name` (String) + +## Import + +Import is supported using the following syntax: + +```shell +terraform import snowflake_api_authentication_integration_with_jwt_bearer.example "name" +``` diff --git a/examples/resources/snowflake_api_authentication_integration_with_authorization_code_grant/import.sh b/examples/resources/snowflake_api_authentication_integration_with_authorization_code_grant/import.sh new file mode 100644 index 0000000000..c641594f3b --- /dev/null +++ b/examples/resources/snowflake_api_authentication_integration_with_authorization_code_grant/import.sh @@ -0,0 +1 @@ +terraform import snowflake_api_authentication_integration_with_authorization_code_grant.example "name" diff --git a/examples/resources/snowflake_api_authentication_integration_with_authorization_code_grant/resource.tf b/examples/resources/snowflake_api_authentication_integration_with_authorization_code_grant/resource.tf new file mode 100644 index 0000000000..c6f0047f70 --- /dev/null +++ b/examples/resources/snowflake_api_authentication_integration_with_authorization_code_grant/resource.tf @@ -0,0 +1,21 @@ +# basic resource +resource "snowflake_api_authentication_integration_with_authorization_code_grant" "test" { + enabled = true + name = "test" + oauth_client_id = "sn-oauth-134o9erqfedlc" + oauth_client_secret = "eb9vaXsrcEvrFdfcvCaoijhilj4fc" +} +# resource with all fields set +resource "snowflake_api_authentication_integration_with_authorization_code_grant" "test" { + comment = "comment" + enabled = true + name = "test" + oauth_access_token_validity = 42 + oauth_allowed_scopes = ["useraccount"] + oauth_authorization_endpoint = "https://example.com" + oauth_client_auth_method = "CLIENT_SECRET_POST" + oauth_client_id = "sn-oauth-134o9erqfedlc" + oauth_client_secret = "eb9vaXsrcEvrFdfcvCaoijhilj4fc" + oauth_refresh_token_validity = 42 + oauth_token_endpoint = "https://example.com" +} diff --git a/examples/resources/snowflake_api_authentication_integration_with_client_credentials/import.sh b/examples/resources/snowflake_api_authentication_integration_with_client_credentials/import.sh new file mode 100644 index 0000000000..d3454c9a27 --- /dev/null +++ b/examples/resources/snowflake_api_authentication_integration_with_client_credentials/import.sh @@ -0,0 +1 @@ +terraform import snowflake_api_authentication_integration_with_client_credentials.example "name" diff --git a/examples/resources/snowflake_api_authentication_integration_with_client_credentials/resource.tf b/examples/resources/snowflake_api_authentication_integration_with_client_credentials/resource.tf new file mode 100644 index 0000000000..6e246b8a68 --- /dev/null +++ b/examples/resources/snowflake_api_authentication_integration_with_client_credentials/resource.tf @@ -0,0 +1,19 @@ +# basic resource +resource "snowflake_api_authentication_integration_with_client_credentials" "test" { + enabled = true + name = "test" + oauth_client_id = "sn-oauth-134o9erqfedlc" + oauth_client_secret = "eb9vaXsrcEvrFdfcvCaoijhilj4fc" +} +# resource with all fields set +resource "snowflake_api_authentication_integration_with_client_credentials" "test" { + comment = "comment" + enabled = true + name = "test" + oauth_access_token_validity = 42 + oauth_allowed_scopes = ["useraccount"] + oauth_client_auth_method = "CLIENT_SECRET_POST" + oauth_client_id = "sn-oauth-134o9erqfedlc" + oauth_client_secret = "eb9vaXsrcEvrFdfcvCaoijhilj4fc" + oauth_token_endpoint = "https://example.com" +} diff --git a/examples/resources/snowflake_api_authentication_integration_with_jwt_bearer/import.sh b/examples/resources/snowflake_api_authentication_integration_with_jwt_bearer/import.sh new file mode 100644 index 0000000000..b1cb40660a --- /dev/null +++ b/examples/resources/snowflake_api_authentication_integration_with_jwt_bearer/import.sh @@ -0,0 +1 @@ +terraform import snowflake_api_authentication_integration_with_jwt_bearer.example "name" diff --git a/examples/resources/snowflake_api_authentication_integration_with_jwt_bearer/resource.tf b/examples/resources/snowflake_api_authentication_integration_with_jwt_bearer/resource.tf new file mode 100644 index 0000000000..c46ab6c5f4 --- /dev/null +++ b/examples/resources/snowflake_api_authentication_integration_with_jwt_bearer/resource.tf @@ -0,0 +1,22 @@ +# basic resource +resource "snowflake_api_authentication_integration_with_jwt_bearer" "test" { + enabled = true + name = "test" + oauth_client_id = "sn-oauth-134o9erqfedlc" + oauth_client_secret = "eb9vaXsrcEvrFdfcvCaoijhilj4fc" + oauth_assertion_issuer = "issuer" +} +# resource with all fields set +resource "snowflake_api_authentication_integration_with_jwt_bearer" "test" { + comment = "comment" + enabled = true + name = "test" + oauth_access_token_validity = 42 + oauth_authorization_endpoint = "https://example.com" + oauth_client_auth_method = "CLIENT_SECRET_POST" + oauth_client_id = "sn-oauth-134o9erqfedlc" + oauth_client_secret = "eb9vaXsrcEvrFdfcvCaoijhilj4fc" + oauth_refresh_token_validity = 42 + oauth_token_endpoint = "https://example.com" + oauth_assertion_issuer = "issuer" +} diff --git a/pkg/provider/provider.go b/pkg/provider/provider.go index 8c98fdd90a..9ccba1cafc 100644 --- a/pkg/provider/provider.go +++ b/pkg/provider/provider.go @@ -421,70 +421,73 @@ func Provider() *schema.Provider { func getResources() map[string]*schema.Resource { return map[string]*schema.Resource{ - "snowflake_account": resources.Account(), - "snowflake_account_password_policy_attachment": resources.AccountPasswordPolicyAttachment(), - "snowflake_account_parameter": resources.AccountParameter(), - "snowflake_alert": resources.Alert(), - "snowflake_api_integration": resources.APIIntegration(), - "snowflake_cortex_search_service": resources.CortexSearchService(), - "snowflake_database_old": resources.DatabaseOld(), - "snowflake_database": resources.Database(), - "snowflake_database_role": resources.DatabaseRole(), - "snowflake_dynamic_table": resources.DynamicTable(), - "snowflake_email_notification_integration": resources.EmailNotificationIntegration(), - "snowflake_external_function": resources.ExternalFunction(), - "snowflake_external_oauth_integration": resources.ExternalOauthIntegration(), - "snowflake_external_table": resources.ExternalTable(), - "snowflake_failover_group": resources.FailoverGroup(), - "snowflake_file_format": resources.FileFormat(), - "snowflake_function": resources.Function(), - "snowflake_grant_account_role": resources.GrantAccountRole(), - "snowflake_grant_application_role": resources.GrantApplicationRole(), - "snowflake_grant_database_role": resources.GrantDatabaseRole(), - "snowflake_grant_ownership": resources.GrantOwnership(), - "snowflake_grant_privileges_to_account_role": resources.GrantPrivilegesToAccountRole(), - "snowflake_grant_privileges_to_database_role": resources.GrantPrivilegesToDatabaseRole(), - "snowflake_grant_privileges_to_share": resources.GrantPrivilegesToShare(), - "snowflake_managed_account": resources.ManagedAccount(), - "snowflake_masking_policy": resources.MaskingPolicy(), - "snowflake_materialized_view": resources.MaterializedView(), - "snowflake_network_policy": resources.NetworkPolicy(), - "snowflake_network_policy_attachment": resources.NetworkPolicyAttachment(), - "snowflake_network_rule": resources.NetworkRule(), - "snowflake_notification_integration": resources.NotificationIntegration(), - "snowflake_oauth_integration": resources.OAuthIntegration(), - "snowflake_object_parameter": resources.ObjectParameter(), - "snowflake_password_policy": resources.PasswordPolicy(), - "snowflake_pipe": resources.Pipe(), - "snowflake_procedure": resources.Procedure(), - "snowflake_resource_monitor": resources.ResourceMonitor(), - "snowflake_role": resources.Role(), - "snowflake_row_access_policy": resources.RowAccessPolicy(), - "snowflake_saml_integration": resources.SAMLIntegration(), - "snowflake_saml2_integration": resources.SAML2Integration(), - "snowflake_schema": resources.Schema(), - "snowflake_scim_integration": resources.SCIMIntegration(), - "snowflake_secondary_database": resources.SecondaryDatabase(), - "snowflake_sequence": resources.Sequence(), - "snowflake_session_parameter": resources.SessionParameter(), - "snowflake_share": resources.Share(), - "snowflake_shared_database": resources.SharedDatabase(), - "snowflake_stage": resources.Stage(), - "snowflake_storage_integration": resources.StorageIntegration(), - "snowflake_stream": resources.Stream(), - "snowflake_table": resources.Table(), - "snowflake_table_column_masking_policy_application": resources.TableColumnMaskingPolicyApplication(), - "snowflake_table_constraint": resources.TableConstraint(), - "snowflake_tag": resources.Tag(), - "snowflake_tag_association": resources.TagAssociation(), - "snowflake_tag_masking_policy_association": resources.TagMaskingPolicyAssociation(), - "snowflake_task": resources.Task(), - "snowflake_unsafe_execute": resources.UnsafeExecute(), - "snowflake_user": resources.User(), - "snowflake_user_password_policy_attachment": resources.UserPasswordPolicyAttachment(), - "snowflake_user_public_keys": resources.UserPublicKeys(), - "snowflake_view": resources.View(), - "snowflake_warehouse": resources.Warehouse(), + "snowflake_account": resources.Account(), + "snowflake_account_password_policy_attachment": resources.AccountPasswordPolicyAttachment(), + "snowflake_account_parameter": resources.AccountParameter(), + "snowflake_alert": resources.Alert(), + "snowflake_api_authentication_integration_with_authorization_code_grant": resources.ApiAuthenticationIntegrationWithAuthorizationCodeGrant(), + "snowflake_api_authentication_integration_with_client_credentials": resources.ApiAuthenticationIntegrationWithClientCredentials(), + "snowflake_api_authentication_integration_with_jwt_bearer": resources.ApiAuthenticationIntegrationWithJwtBearer(), + "snowflake_api_integration": resources.APIIntegration(), + "snowflake_cortex_search_service": resources.CortexSearchService(), + "snowflake_database_old": resources.DatabaseOld(), + "snowflake_database": resources.Database(), + "snowflake_database_role": resources.DatabaseRole(), + "snowflake_dynamic_table": resources.DynamicTable(), + "snowflake_email_notification_integration": resources.EmailNotificationIntegration(), + "snowflake_external_function": resources.ExternalFunction(), + "snowflake_external_oauth_integration": resources.ExternalOauthIntegration(), + "snowflake_external_table": resources.ExternalTable(), + "snowflake_failover_group": resources.FailoverGroup(), + "snowflake_file_format": resources.FileFormat(), + "snowflake_function": resources.Function(), + "snowflake_grant_account_role": resources.GrantAccountRole(), + "snowflake_grant_application_role": resources.GrantApplicationRole(), + "snowflake_grant_database_role": resources.GrantDatabaseRole(), + "snowflake_grant_ownership": resources.GrantOwnership(), + "snowflake_grant_privileges_to_account_role": resources.GrantPrivilegesToAccountRole(), + "snowflake_grant_privileges_to_database_role": resources.GrantPrivilegesToDatabaseRole(), + "snowflake_grant_privileges_to_share": resources.GrantPrivilegesToShare(), + "snowflake_managed_account": resources.ManagedAccount(), + "snowflake_masking_policy": resources.MaskingPolicy(), + "snowflake_materialized_view": resources.MaterializedView(), + "snowflake_network_policy": resources.NetworkPolicy(), + "snowflake_network_policy_attachment": resources.NetworkPolicyAttachment(), + "snowflake_network_rule": resources.NetworkRule(), + "snowflake_notification_integration": resources.NotificationIntegration(), + "snowflake_oauth_integration": resources.OAuthIntegration(), + "snowflake_object_parameter": resources.ObjectParameter(), + "snowflake_password_policy": resources.PasswordPolicy(), + "snowflake_pipe": resources.Pipe(), + "snowflake_procedure": resources.Procedure(), + "snowflake_resource_monitor": resources.ResourceMonitor(), + "snowflake_role": resources.Role(), + "snowflake_row_access_policy": resources.RowAccessPolicy(), + "snowflake_saml_integration": resources.SAMLIntegration(), + "snowflake_saml2_integration": resources.SAML2Integration(), + "snowflake_schema": resources.Schema(), + "snowflake_scim_integration": resources.SCIMIntegration(), + "snowflake_secondary_database": resources.SecondaryDatabase(), + "snowflake_sequence": resources.Sequence(), + "snowflake_session_parameter": resources.SessionParameter(), + "snowflake_share": resources.Share(), + "snowflake_shared_database": resources.SharedDatabase(), + "snowflake_stage": resources.Stage(), + "snowflake_storage_integration": resources.StorageIntegration(), + "snowflake_stream": resources.Stream(), + "snowflake_table": resources.Table(), + "snowflake_table_column_masking_policy_application": resources.TableColumnMaskingPolicyApplication(), + "snowflake_table_constraint": resources.TableConstraint(), + "snowflake_tag": resources.Tag(), + "snowflake_tag_association": resources.TagAssociation(), + "snowflake_tag_masking_policy_association": resources.TagMaskingPolicyAssociation(), + "snowflake_task": resources.Task(), + "snowflake_unsafe_execute": resources.UnsafeExecute(), + "snowflake_user": resources.User(), + "snowflake_user_password_policy_attachment": resources.UserPasswordPolicyAttachment(), + "snowflake_user_public_keys": resources.UserPublicKeys(), + "snowflake_view": resources.View(), + "snowflake_warehouse": resources.Warehouse(), } } diff --git a/pkg/resources/api_authentication_integration_common.go b/pkg/resources/api_authentication_integration_common.go new file mode 100644 index 0000000000..bfb01a1814 --- /dev/null +++ b/pkg/resources/api_authentication_integration_common.go @@ -0,0 +1,356 @@ +package resources + +import ( + "fmt" + "strconv" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/collections" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/schemas" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation" +) + +var apiAuthCommonSchema = map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "Specifies the identifier (i.e. name) for the integration. This value must be unique in your account.", + }, + "enabled": { + Type: schema.TypeBool, + Required: true, + Description: "Specifies whether this security integration is enabled or disabled.", + }, + "oauth_client_id": { + Type: schema.TypeString, + Required: true, + Description: "Specifies the client ID for the OAuth application in the external service.", + }, + "oauth_client_secret": { + Type: schema.TypeString, + Required: true, + Description: "Specifies the client secret for the OAuth application in the ServiceNow instance from the previous step. The connector uses this to request an access token from the ServiceNow instance.", + }, + "oauth_token_endpoint": { + Type: schema.TypeString, + Optional: true, + Description: "Specifies the token endpoint used by the client to obtain an access token by presenting its authorization grant or refresh token. The token endpoint is used with every authorization grant except for the implicit grant type (since an access token is issued directly). If removed from the config, the resource is recreated.", + }, + "oauth_client_auth_method": { + Type: schema.TypeString, + Optional: true, + ValidateDiagFunc: sdkValidation(sdk.ToApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption), + DiffSuppressFunc: SuppressIfAny(NormalizeAndCompare(sdk.ToApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption), IgnoreChangeToCurrentSnowflakeValueInDescribe("oauth_client_auth_method")), + Description: fmt.Sprintf("Specifies that POST is used as the authentication method to the external service. If removed from the config, the resource is recreated. Valid values are (case-insensitive): %s.", possibleValuesListed(sdk.AsStringList(sdk.AllApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption))), + }, + "oauth_access_token_validity": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IntAtLeast(0), + Default: IntDefault, + Description: "Specifies the default lifetime of the OAuth access token (in seconds) issued by an OAuth server.", + DiffSuppressFunc: IgnoreChangeToCurrentSnowflakeValueInDescribe("oauth_access_token_validity"), + }, + "oauth_refresh_token_validity": { + Type: schema.TypeInt, + Optional: true, + ValidateFunc: validation.IntAtLeast(1), + Description: "Specifies the value to determine the validity of the refresh token obtained from the OAuth server.", + DiffSuppressFunc: IgnoreChangeToCurrentSnowflakeValueInDescribe("oauth_refresh_token_validity"), + }, + "comment": { + Type: schema.TypeString, + Optional: true, + Description: "Specifies a comment for the integration.", + }, + ShowOutputAttributeName: { + Type: schema.TypeList, + Computed: true, + Description: "Outputs the result of `SHOW SECURITY INTEGRATIONS` for the given security integration.", + Elem: &schema.Resource{ + Schema: schemas.ShowSecurityIntegrationSchema, + }, + }, + DescribeOutputAttributeName: { + Type: schema.TypeList, + Computed: true, + Description: "Outputs the result of `DESCRIBE SECURITY INTEGRATIONS` for the given security integration.", + Elem: &schema.Resource{ + Schema: schemas.DescribeApiAuthSecurityIntegrationSchema, + }, + }, +} + +type commonApiAuthSet struct { + enabled *bool + oauthClientId *string + oauthClientSecret *string + comment *string + oauthAccessTokenValidity *int + oauthClientAuthMethod *sdk.ApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption + oauthTokenEndpoint *string + oauthRefreshTokenValidity *int +} + +type commonApiAuthUnset struct { + comment *bool +} + +func handleApiAuthUpdate(d *schema.ResourceData) (commonApiAuthSet, commonApiAuthUnset, error) { + set, unset := commonApiAuthSet{}, commonApiAuthUnset{} + + if d.HasChange("enabled") { + // required field + set.enabled = sdk.Pointer(d.Get("enabled").(bool)) + } + + if d.HasChange("oauth_client_id") { + // required field + set.oauthClientId = sdk.Pointer(d.Get("oauth_client_id").(string)) + } + + if d.HasChange("oauth_client_secret") { + // required field + set.oauthClientSecret = sdk.Pointer(d.Get("oauth_client_secret").(string)) + } + + if d.HasChange("comment") { + if v, ok := d.GetOk("comment"); ok { + set.comment = sdk.Pointer(v.(string)) + } else { + unset.comment = sdk.Pointer(true) + } + } + + if d.HasChange("oauth_access_token_validity") { + if v := d.Get("oauth_access_token_validity").(int); v != IntDefault { + set.oauthAccessTokenValidity = sdk.Pointer(v) + } else { + // TODO(SNOW-1515781): use UNSET + set.oauthAccessTokenValidity = sdk.Pointer(0) + } + } + + if d.HasChange("oauth_client_auth_method") { + v := d.Get("oauth_client_auth_method").(string) + if len(v) > 0 { + value, err := sdk.ToApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption(v) + if err != nil { + return commonApiAuthSet{}, commonApiAuthUnset{}, err + } + set.oauthClientAuthMethod = sdk.Pointer(value) + } + // else: force new + } + + if d.HasChange("oauth_refresh_token_validity") { + if v, ok := d.GetOk("oauth_refresh_token_validity"); ok { + set.oauthRefreshTokenValidity = sdk.Pointer(v.(int)) + } else { + // TODO(SNOW-1515781): use UNSET + set.oauthRefreshTokenValidity = sdk.Pointer(7776000) + } + } + + if d.HasChange("oauth_token_endpoint") { + if v, ok := d.GetOk("oauth_token_endpoint"); ok { + set.oauthTokenEndpoint = sdk.Pointer(v.(string)) + } + // else: force new + } + return set, unset, nil +} + +type commonApiAuthCreate struct { + name string + enabled bool + oauthClientId string + oauthClientSecret string + comment *string + oauthAccessTokenValidity *int + oauthClientAuthMethod *sdk.ApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption + oauthTokenEndpoint *string + oauthRefreshTokenValidity *int +} + +func handleApiAuthCreate(d *schema.ResourceData) (commonApiAuthCreate, error) { + create := commonApiAuthCreate{ + enabled: d.Get("enabled").(bool), + name: d.Get("name").(string), + oauthClientId: d.Get("oauth_client_id").(string), + oauthClientSecret: d.Get("oauth_client_secret").(string), + } + if v, ok := d.GetOk("comment"); ok { + create.comment = sdk.Pointer(v.(string)) + } + + if v := d.Get("oauth_access_token_validity").(int); v != IntDefault { + create.oauthAccessTokenValidity = sdk.Pointer(v) + } + if v, ok := d.GetOk("oauth_refresh_token_validity"); ok { + create.oauthRefreshTokenValidity = sdk.Pointer(v.(int)) + } + if v, ok := d.GetOk("oauth_token_endpoint"); ok { + create.oauthTokenEndpoint = sdk.Pointer(v.(string)) + } + if v, ok := d.GetOk("oauth_client_auth_method"); ok { + value, err := sdk.ToApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption(v.(string)) + if err != nil { + return commonApiAuthCreate{}, err + } + create.oauthClientAuthMethod = sdk.Pointer(value) + } + + return create, nil +} + +func handleApiAuthImport(d *schema.ResourceData, integration *sdk.SecurityIntegration, + properties []sdk.SecurityIntegrationProperty, +) error { + if err := d.Set("name", integration.Name); err != nil { + return err + } + if err := d.Set("enabled", integration.Enabled); err != nil { + return err + } + if err := d.Set("comment", integration.Comment); err != nil { + return err + } + + oauthAccessTokenValidity, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "OAUTH_ACCESS_TOKEN_VALIDITY" + }) + if err == nil { + value, err := strconv.Atoi(oauthAccessTokenValidity.Value) + if err != nil { + return err + } + if err = d.Set("oauth_access_token_validity", value); err != nil { + return err + } + } + oauthRefreshTokenValidity, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "OAUTH_REFRESH_TOKEN_VALIDITY" + }) + if err == nil { + value, err := strconv.Atoi(oauthRefreshTokenValidity.Value) + if err != nil { + return err + } + if err = d.Set("oauth_refresh_token_validity", value); err != nil { + return err + } + } + oauthClientId, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_CLIENT_ID" }) + if err == nil { + if err = d.Set("oauth_client_id", oauthClientId.Value); err != nil { + return err + } + } + oauthClientAuthMethod, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "OAUTH_CLIENT_AUTH_METHOD" + }) + if err == nil { + if err = d.Set("oauth_client_auth_method", oauthClientAuthMethod.Value); err != nil { + return err + } + } + oauthTokenEndpoint, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_TOKEN_ENDPOINT" }) + if err == nil { + if err = d.Set("oauth_token_endpoint", oauthTokenEndpoint.Value); err != nil { + return err + } + } + + return nil +} + +func handleApiAuthRead(d *schema.ResourceData, + integration *sdk.SecurityIntegration, + properties []sdk.SecurityIntegrationProperty, + withExternalChangesMarking bool, + extraFieldsDescribeMappings []describeMapping, +) error { + if err := d.Set("name", integration.Name); err != nil { + return err + } + if err := d.Set("comment", integration.Comment); err != nil { + return err + } + if err := d.Set("enabled", integration.Enabled); err != nil { + return err + } + if withExternalChangesMarking { + oauthAccessTokenValidity, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "OAUTH_ACCESS_TOKEN_VALIDITY" + }) + if err != nil { + return err + } + + oauthRefreshTokenValidity, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "OAUTH_REFRESH_TOKEN_VALIDITY" + }) + if err != nil { + return err + } + + oauthClientId, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_CLIENT_ID" }) + if err != nil { + return err + } + + oauthClientAuthMethod, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "OAUTH_CLIENT_AUTH_METHOD" + }) + if err != nil { + return err + } + + oauthTokenEndpoint, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_TOKEN_ENDPOINT" }) + if err != nil { + return err + } + + oauthAccessTokenValidityInt, err := strconv.Atoi(oauthAccessTokenValidity.Value) + if err != nil { + return err + } + oauthRefreshTokenValidityInt, err := strconv.Atoi(oauthRefreshTokenValidity.Value) + if err != nil { + return err + } + + if err = handleExternalChangesToObjectInDescribe(d, + append(extraFieldsDescribeMappings, + describeMapping{"oauth_access_token_validity", "oauth_access_token_validity", oauthAccessTokenValidity.Value, oauthAccessTokenValidityInt, nil}, + describeMapping{"oauth_refresh_token_validity", "oauth_refresh_token_validity", oauthRefreshTokenValidity.Value, oauthRefreshTokenValidityInt, nil}, + describeMapping{"oauth_client_id", "oauth_client_id", oauthClientId.Value, oauthClientId.Value, nil}, + describeMapping{"oauth_client_auth_method", "oauth_client_auth_method", oauthClientAuthMethod.Value, oauthClientAuthMethod.Value, nil}, + describeMapping{"oauth_token_endpoint", "oauth_token_endpoint", oauthTokenEndpoint.Value, oauthTokenEndpoint.Value, nil}, + )..., + ); err != nil { + return err + } + } + if err := setStateToValuesFromConfig(d, warehouseSchema, []string{ + "oauth_access_token_validity", + "oauth_refresh_token_validity", + "oauth_client_id", + "oauth_client_auth_method", + "oauth_token_endpoint", + }); err != nil { + return err + } + + if err := d.Set(ShowOutputAttributeName, []map[string]any{schemas.SecurityIntegrationToSchema(integration)}); err != nil { + return err + } + + if err := d.Set(DescribeOutputAttributeName, []map[string]any{schemas.ApiAuthSecurityIntegrationPropertiesToSchema(properties)}); err != nil { + return err + } + return nil +} diff --git a/pkg/resources/api_authentication_integration_with_authorization_code_grant.go b/pkg/resources/api_authentication_integration_with_authorization_code_grant.go new file mode 100644 index 0000000000..8bad64fd68 --- /dev/null +++ b/pkg/resources/api_authentication_integration_with_authorization_code_grant.go @@ -0,0 +1,251 @@ +package resources + +import ( + "context" + "errors" + "fmt" + "reflect" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/collections" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/logging" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +var apiAuthAuthorizationCodeGrantSchema = func() map[string]*schema.Schema { + apiAuthAuthorizationCodeGrant := map[string]*schema.Schema{ + "oauth_authorization_endpoint": { + Type: schema.TypeString, + Optional: true, + Description: "Specifies the URL for authenticating to the external service. If removed from the config, the resource is recreated.", + }, + "oauth_allowed_scopes": { + Type: schema.TypeSet, + Elem: &schema.Schema{Type: schema.TypeString}, + Optional: true, + Description: "Specifies a list of scopes to use when making a request from the OAuth by a role with USAGE on the integration during the OAuth client credentials flow.", + }, + } + return MergeMaps(apiAuthCommonSchema, apiAuthAuthorizationCodeGrant) +}() + +func ApiAuthenticationIntegrationWithAuthorizationCodeGrant() *schema.Resource { + return &schema.Resource{ + CreateContext: CreateContextApiAuthenticationIntegrationWithAuthorizationCodeGrant, + ReadContext: ReadContextApiAuthenticationIntegrationWithAuthorizationCodeGrant(true), + UpdateContext: UpdateContextApiAuthenticationIntegrationWithAuthorizationCodeGrant, + DeleteContext: DeleteContextApiAuthenticationIntegrationWithAuthorizationCodeGrant, + CustomizeDiff: customdiff.All( + ForceNewIfChangeToEmptyString("oauth_token_endpoint"), + ForceNewIfChangeToEmptyString("oauth_authorization_endpoint"), + ForceNewIfChangeToEmptyString("oauth_client_auth_method"), + ComputedIfAnyAttributeChanged(ShowOutputAttributeName, "enabled", "comment"), + ComputedIfAnyAttributeChanged(DescribeOutputAttributeName, "enabled", "comment", "oauth_access_token_validity", "oauth_refresh_token_validity", + "oauth_client_id", "oauth_client_auth_method", "oauth_authorization_endpoint", + "oauth_token_endpoint", "oauth_allowed_scopes"), + ), + Schema: apiAuthAuthorizationCodeGrantSchema, + Importer: &schema.ResourceImporter{ + StateContext: ImportApiAuthenticationWithAuthorizationCodeGrant, + }, + } +} + +func ImportApiAuthenticationWithAuthorizationCodeGrant(ctx context.Context, d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) { + logging.DebugLogger.Printf("[DEBUG] Starting api auth integration with authorization code grant import") + client := meta.(*provider.Context).Client + id := helpers.DecodeSnowflakeID(d.Id()).(sdk.AccountObjectIdentifier) + + integration, err := client.SecurityIntegrations.ShowByID(ctx, id) + if err != nil { + return nil, err + } + + properties, err := client.SecurityIntegrations.Describe(ctx, id) + if err != nil { + return nil, err + } + + if err := handleApiAuthImport(d, integration, properties); err != nil { + return nil, err + } + oauthAuthorizationEndpoint, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "OAUTH_AUTHORIZATION_ENDPOINT" + }) + if err == nil { + if err = d.Set("oauth_authorization_endpoint", oauthAuthorizationEndpoint.Value); err != nil { + return nil, err + } + } + oauthAllowedScopes, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_ALLOWED_SCOPES" }) + if err == nil { + if err = d.Set("oauth_allowed_scopes", sdk.ParseCommaSeparatedStringArray(oauthAllowedScopes.Value, false)); err != nil { + return nil, err + } + } + + return []*schema.ResourceData{d}, nil +} + +func CreateContextApiAuthenticationIntegrationWithAuthorizationCodeGrant(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*provider.Context).Client + commonCreate, err := handleApiAuthCreate(d) + if err != nil { + return diag.FromErr(err) + } + id := sdk.NewAccountObjectIdentifier(commonCreate.name) + req := sdk.NewCreateApiAuthenticationWithAuthorizationCodeGrantFlowSecurityIntegrationRequest(id, commonCreate.enabled, commonCreate.oauthClientId, commonCreate.oauthClientSecret) + req.WithOauthGrantAuthorizationCode(true) + req.Comment = commonCreate.comment + req.OauthAccessTokenValidity = commonCreate.oauthAccessTokenValidity + req.OauthRefreshTokenValidity = commonCreate.oauthRefreshTokenValidity + req.OauthTokenEndpoint = commonCreate.oauthTokenEndpoint + req.OauthClientAuthMethod = commonCreate.oauthClientAuthMethod + + if v, ok := d.GetOk("oauth_authorization_endpoint"); ok { + req.WithOauthAuthorizationEndpoint(v.(string)) + } + + if v, ok := d.GetOk("oauth_allowed_scopes"); ok { + elems := expandStringList(v.(*schema.Set).List()) + allowedScopes := make([]sdk.AllowedScope, len(elems)) + for i := range elems { + allowedScopes[i] = sdk.AllowedScope{Scope: elems[i]} + } + req.WithOauthAllowedScopes(allowedScopes) + } + + if err := client.SecurityIntegrations.CreateApiAuthenticationWithAuthorizationCodeGrantFlow(ctx, req); err != nil { + return diag.FromErr(err) + } + + d.SetId(helpers.EncodeSnowflakeID(id)) + return ReadContextApiAuthenticationIntegrationWithAuthorizationCodeGrant(false)(ctx, d, meta) +} + +func ReadContextApiAuthenticationIntegrationWithAuthorizationCodeGrant(withExternalChangesMarking bool) schema.ReadContextFunc { + return func(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*provider.Context).Client + id := helpers.DecodeSnowflakeID(d.Id()).(sdk.AccountObjectIdentifier) + + integration, err := client.SecurityIntegrations.ShowByID(ctx, id) + if err != nil { + if errors.Is(err, sdk.ErrObjectNotFound) { + d.SetId("") + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Warning, + Summary: "Failed to query security integration. Marking the resource as removed.", + Detail: fmt.Sprintf("Security integration name: %s, Err: %s", id.FullyQualifiedName(), err), + }, + } + } + return diag.FromErr(err) + } + properties, err := client.SecurityIntegrations.Describe(ctx, id) + if err != nil { + return diag.FromErr(err) + } + + if c := integration.Category; c != sdk.SecurityIntegrationCategory { + return diag.FromErr(fmt.Errorf("expected %v to be a %s integration, got %v", id, sdk.SecurityIntegrationCategory, c)) + } + oauthAuthorizationEndpoint, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "OAUTH_AUTHORIZATION_ENDPOINT" + }) + if err != nil { + return diag.FromErr(err) + } + + oauthAllowedScopes, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_ALLOWED_SCOPES" }) + if err != nil { + return diag.FromErr(err) + } + + if err := handleApiAuthRead(d, integration, properties, withExternalChangesMarking, []describeMapping{ + {"oauth_authorization_endpoint", "oauth_authorization_endpoint", oauthAuthorizationEndpoint.Value, oauthAuthorizationEndpoint.Value, nil}, + {"oauth_allowed_scopes", "oauth_allowed_scopes", oauthAllowedScopes.Value, sdk.ParseCommaSeparatedStringArray(oauthAllowedScopes.Value, false), nil}, + }); err != nil { + return diag.FromErr(err) + } + if err := setStateToValuesFromConfig(d, warehouseSchema, []string{ + "oauth_authorization_endpoint", + "oauth_allowed_scopes", + "oauth_client_auth_method", + "oauth_token_endpoint", + }); err != nil { + return diag.FromErr(err) + } + return nil + } +} + +func UpdateContextApiAuthenticationIntegrationWithAuthorizationCodeGrant(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*provider.Context).Client + id := helpers.DecodeSnowflakeID(d.Id()).(sdk.AccountObjectIdentifier) + commonSet, commonUnset, err := handleApiAuthUpdate(d) + if err != nil { + return diag.FromErr(err) + } + set := &sdk.ApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationSetRequest{ + Enabled: commonSet.enabled, + OauthTokenEndpoint: commonSet.oauthTokenEndpoint, + OauthClientAuthMethod: commonSet.oauthClientAuthMethod, + OauthClientId: commonSet.oauthClientId, + OauthClientSecret: commonSet.oauthClientSecret, + OauthAccessTokenValidity: commonSet.oauthAccessTokenValidity, + OauthRefreshTokenValidity: commonSet.oauthRefreshTokenValidity, + Comment: commonSet.comment, + } + unset := &sdk.ApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationUnsetRequest{ + Comment: commonUnset.comment, + } + if d.HasChange("oauth_authorization_endpoint") { + set.WithOauthAuthorizationEndpoint(d.Get("oauth_authorization_endpoint").(string)) + } + + if d.HasChange("oauth_allowed_scopes") { + elems := expandStringList(d.Get("oauth_allowed_scopes").(*schema.Set).List()) + allowedScopes := make([]sdk.AllowedScope, len(elems)) + for i := range elems { + allowedScopes[i] = sdk.AllowedScope{Scope: elems[i]} + } + set.WithOauthAllowedScopes(allowedScopes) + } + + if !reflect.DeepEqual(*set, sdk.ApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationSetRequest{}) { + if err := client.SecurityIntegrations.AlterApiAuthenticationWithAuthorizationCodeGrantFlow(ctx, sdk.NewAlterApiAuthenticationWithAuthorizationCodeGrantFlowSecurityIntegrationRequest(id).WithSet(*set)); err != nil { + return diag.FromErr(err) + } + } + if !reflect.DeepEqual(*unset, sdk.ApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationUnsetRequest{}) { + if err := client.SecurityIntegrations.AlterApiAuthenticationWithAuthorizationCodeGrantFlow(ctx, sdk.NewAlterApiAuthenticationWithAuthorizationCodeGrantFlowSecurityIntegrationRequest(id).WithUnset(*unset)); err != nil { + return diag.FromErr(err) + } + } + return ReadContextApiAuthenticationIntegrationWithAuthorizationCodeGrant(false)(ctx, d, meta) +} + +func DeleteContextApiAuthenticationIntegrationWithAuthorizationCodeGrant(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + id := helpers.DecodeSnowflakeID(d.Id()).(sdk.AccountObjectIdentifier) + client := meta.(*provider.Context).Client + + err := client.SecurityIntegrations.Drop(ctx, sdk.NewDropSecurityIntegrationRequest(sdk.NewAccountObjectIdentifier(id.Name())).WithIfExists(true)) + if err != nil { + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "Error deleting integration", + Detail: fmt.Sprintf("id %v err = %v", id.Name(), err), + }, + } + } + + d.SetId("") + return nil +} diff --git a/pkg/resources/api_authentication_integration_with_authorization_code_grant_acceptance_test.go b/pkg/resources/api_authentication_integration_with_authorization_code_grant_acceptance_test.go new file mode 100644 index 0000000000..ee2caa26a1 --- /dev/null +++ b/pkg/resources/api_authentication_integration_with_authorization_code_grant_acceptance_test.go @@ -0,0 +1,253 @@ +package resources_test + +import ( + "testing" + + acc "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/helpers" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/importchecks" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/tfversion" +) + +func TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant_basic(t *testing.T) { + id := acc.TestClient().Ids.RandomAccountObjectIdentifier() + m := func(complete bool) map[string]config.Variable { + c := map[string]config.Variable{ + "enabled": config.BoolVariable(true), + "name": config.StringVariable(id.Name()), + "oauth_client_id": config.StringVariable("foo"), + "oauth_client_secret": config.StringVariable("foo"), + } + if complete { + c["comment"] = config.StringVariable("foo") + c["oauth_access_token_validity"] = config.IntegerVariable(42) + c["oauth_authorization_endpoint"] = config.StringVariable("https://example.com") + c["oauth_client_auth_method"] = config.StringVariable(string(sdk.ApiAuthenticationSecurityIntegrationOauthClientAuthMethodClientSecretPost)) + c["oauth_refresh_token_validity"] = config.IntegerVariable(12345) + c["oauth_token_endpoint"] = config.StringVariable("https://example.com") + c["oauth_allowed_scopes"] = config.SetVariable(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), + }, + Steps: []resource.TestStep{ + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/basic"), + ConfigVariables: m(false), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "enabled", "true"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "name", id.Name()), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_client_id", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_client_secret", "foo"), + + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "show_output.#", "1"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "show_output.0.name", id.Name()), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "show_output.0.integration_type", "API_AUTHENTICATION"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "show_output.0.category", "SECURITY"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "show_output.0.enabled", "true"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "show_output.0.comment", ""), + resource.TestCheckResourceAttrSet("snowflake_api_authentication_integration_with_authorization_code_grant.test", "show_output.0.created_on"), + + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.#", "1"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.enabled.0.value", "true"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.oauth_access_token_validity.0.value", "0"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.oauth_refresh_token_validity.0.value", "0"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.oauth_client_id.0.value", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.oauth_client_auth_method.0.value", ""), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.oauth_authorization_endpoint.0.value", ""), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.oauth_token_endpoint.0.value", ""), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.oauth_allowed_scopes.0.value", ""), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.oauth_grant.0.value", sdk.ApiAuthenticationSecurityIntegrationOauthGrantAuthorizationCode), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.parent_integration.0.value", ""), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.auth_type.0.value", "OAUTH2"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.comment.0.value", ""), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/basic"), + ConfigVariables: m(false), + ResourceName: "snowflake_api_authentication_integration_with_authorization_code_grant.test", + ImportState: true, + ImportStateCheck: importchecks.ComposeImportStateCheck( + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "name", id.Name()), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "enabled", "true"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "oauth_client_id", "foo"), + + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "show_output.#", "1"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "show_output.0.name", id.Name()), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "show_output.0.integration_type", "API_AUTHENTICATION"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "show_output.0.category", "SECURITY"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "show_output.0.enabled", "true"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "show_output.0.comment", ""), + + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.#", "1"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.enabled.0.value", "true"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.oauth_access_token_validity.0.value", "0"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.oauth_refresh_token_validity.0.value", "0"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.oauth_client_id.0.value", "foo"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.oauth_client_auth_method.0.value", ""), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.oauth_token_endpoint.0.value", ""), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.oauth_allowed_scopes.0.value", ""), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.oauth_grant.0.value", sdk.ApiAuthenticationSecurityIntegrationOauthGrantAuthorizationCode), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.parent_integration.0.value", ""), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.auth_type.0.value", "OAUTH2"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.comment.0.value", ""), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/complete"), + ConfigVariables: m(true), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "comment", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "enabled", "true"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "name", id.Name()), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_access_token_validity", "42"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_authorization_endpoint", "https://example.com"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_client_auth_method", string(sdk.ApiAuthenticationSecurityIntegrationOauthClientAuthMethodClientSecretPost)), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_client_id", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_client_secret", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_refresh_token_validity", "12345"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_token_endpoint", "https://example.com"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_allowed_scopes.#", "1"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_allowed_scopes.0", "foo"), + + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "show_output.#", "1"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "show_output.0.name", id.Name()), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "show_output.0.integration_type", "API_AUTHENTICATION"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "show_output.0.category", "SECURITY"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "show_output.0.enabled", "true"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "show_output.0.comment", "foo"), + resource.TestCheckResourceAttrSet("snowflake_api_authentication_integration_with_authorization_code_grant.test", "show_output.0.created_on"), + + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.#", "1"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.enabled.0.value", "true"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.oauth_access_token_validity.0.value", "42"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.oauth_refresh_token_validity.0.value", "12345"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.oauth_client_id.0.value", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.oauth_client_auth_method.0.value", string(sdk.ApiAuthenticationSecurityIntegrationOauthClientAuthMethodClientSecretPost)), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.oauth_authorization_endpoint.0.value", "https://example.com"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.oauth_token_endpoint.0.value", "https://example.com"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.oauth_allowed_scopes.0.value", "[foo]"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.oauth_grant.0.value", sdk.ApiAuthenticationSecurityIntegrationOauthGrantAuthorizationCode), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.parent_integration.0.value", ""), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.auth_type.0.value", "OAUTH2"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.comment.0.value", "foo")), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/basic"), + ConfigVariables: m(true), + ResourceName: "snowflake_api_authentication_integration_with_authorization_code_grant.test", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"oauth_client_secret"}, + }, + // unset + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/basic"), + ConfigVariables: m(false), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "comment", ""), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "enabled", "true"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "name", id.Name()), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_access_token_validity", "-1"), + resource.TestCheckNoResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_authorization_endpoint"), + resource.TestCheckNoResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_client_auth_method"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_client_id", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_client_secret", "foo"), + resource.TestCheckNoResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_refresh_token_validity"), + resource.TestCheckNoResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_token_endpoint"), + ), + }, + }, + }) +} + +func TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant_complete(t *testing.T) { + id := acc.TestClient().Ids.RandomAccountObjectIdentifier() + m := func() map[string]config.Variable { + return map[string]config.Variable{ + "comment": config.StringVariable("foo"), + "enabled": config.BoolVariable(true), + "name": config.StringVariable(id.Name()), + "oauth_access_token_validity": config.IntegerVariable(42), + "oauth_authorization_endpoint": config.StringVariable("https://example.com"), + "oauth_client_auth_method": config.StringVariable(string(sdk.ApiAuthenticationSecurityIntegrationOauthClientAuthMethodClientSecretPost)), + "oauth_client_id": config.StringVariable("foo"), + "oauth_client_secret": config.StringVariable("foo"), + "oauth_refresh_token_validity": config.IntegerVariable(12345), + "oauth_token_endpoint": config.StringVariable("https://example.com"), + "oauth_allowed_scopes": config.SetVariable(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), + }, + Steps: []resource.TestStep{ + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/complete"), + ConfigVariables: m(), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "comment", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "enabled", "true"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "name", id.Name()), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_access_token_validity", "42"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_authorization_endpoint", "https://example.com"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_client_auth_method", string(sdk.ApiAuthenticationSecurityIntegrationOauthClientAuthMethodClientSecretPost)), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_client_id", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_client_secret", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_refresh_token_validity", "12345"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_token_endpoint", "https://example.com"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_allowed_scopes.#", "1"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "oauth_allowed_scopes.0", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_authorization_code_grant.test", "describe_output.0.oauth_allowed_scopes.0.value", "[foo]"), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/complete"), + ConfigVariables: m(), + ResourceName: "snowflake_api_authentication_integration_with_authorization_code_grant.test", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"oauth_client_secret"}, + }, + }, + }) +} + +func TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant_invalidIncomplete(t *testing.T) { + m := func() map[string]config.Variable { + return map[string]config.Variable{ + "name": 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{ + `The argument "oauth_client_secret" is required, but no definition was found.`, + `The argument "oauth_client_id" is required, but no definition was found.`, + `The argument "enabled" is required, but no definition was found.`, + }), + Steps: []resource.TestStep{ + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/invalid"), + ConfigVariables: m(), + }, + }, + }) +} diff --git a/pkg/resources/api_authentication_integration_with_client_credentials.go b/pkg/resources/api_authentication_integration_with_client_credentials.go new file mode 100644 index 0000000000..862c2ed21f --- /dev/null +++ b/pkg/resources/api_authentication_integration_with_client_credentials.go @@ -0,0 +1,219 @@ +package resources + +import ( + "context" + "errors" + "fmt" + "reflect" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/collections" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/logging" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +var apiAuthClientCredentialsSchema = func() map[string]*schema.Schema { + apiAuthClientCredentials := map[string]*schema.Schema{ + "oauth_allowed_scopes": { + Type: schema.TypeSet, + Elem: &schema.Schema{Type: schema.TypeString}, + Optional: true, + Description: "Specifies a list of scopes to use when making a request from the OAuth by a role with USAGE on the integration during the OAuth client credentials flow.", + }, + } + return MergeMaps(apiAuthCommonSchema, apiAuthClientCredentials) +}() + +func ApiAuthenticationIntegrationWithClientCredentials() *schema.Resource { + return &schema.Resource{ + CreateContext: CreateContextApiAuthenticationIntegrationWithClientCredentials, + ReadContext: ReadContextApiAuthenticationIntegrationWithClientCredentials(true), + UpdateContext: UpdateContextApiAuthenticationIntegrationWithClientCredentials, + DeleteContext: DeleteContextApiAuthenticationIntegrationWithClientCredentials, + Schema: apiAuthClientCredentialsSchema, + CustomizeDiff: customdiff.All( + ForceNewIfChangeToEmptyString("oauth_token_endpoint"), + ForceNewIfChangeToEmptyString("oauth_client_auth_method"), + ComputedIfAnyAttributeChanged(ShowOutputAttributeName, "enabled", "comment"), + ComputedIfAnyAttributeChanged(DescribeOutputAttributeName, "enabled", "comment", "oauth_access_token_validity", "oauth_refresh_token_validity", + "oauth_client_id", "oauth_client_auth_method", "oauth_token_endpoint", "oauth_allowed_scopes"), + ), + Importer: &schema.ResourceImporter{ + StateContext: ImportApiAuthenticationWithClientCredentials, + }, + } +} + +func ImportApiAuthenticationWithClientCredentials(ctx context.Context, d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) { + logging.DebugLogger.Printf("[DEBUG] Starting api auth integration with client credentials import") + client := meta.(*provider.Context).Client + id := helpers.DecodeSnowflakeID(d.Id()).(sdk.AccountObjectIdentifier) + + integration, err := client.SecurityIntegrations.ShowByID(ctx, id) + if err != nil { + return nil, err + } + + properties, err := client.SecurityIntegrations.Describe(ctx, id) + if err != nil { + return nil, err + } + if err := handleApiAuthImport(d, integration, properties); err != nil { + return nil, err + } + oauthAllowedScopes, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_ALLOWED_SCOPES" }) + if err == nil { + if err = d.Set("oauth_allowed_scopes", sdk.ParseCommaSeparatedStringArray(oauthAllowedScopes.Value, false)); err != nil { + return nil, err + } + } + + return []*schema.ResourceData{d}, nil +} + +func CreateContextApiAuthenticationIntegrationWithClientCredentials(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*provider.Context).Client + commonCreate, err := handleApiAuthCreate(d) + if err != nil { + return diag.FromErr(err) + } + id := sdk.NewAccountObjectIdentifier(commonCreate.name) + req := sdk.NewCreateApiAuthenticationWithClientCredentialsFlowSecurityIntegrationRequest(id, commonCreate.enabled, commonCreate.oauthClientId, commonCreate.oauthClientSecret) + req.WithOauthGrantClientCredentials(true) + req.Comment = commonCreate.comment + req.OauthAccessTokenValidity = commonCreate.oauthAccessTokenValidity + req.OauthRefreshTokenValidity = commonCreate.oauthRefreshTokenValidity + req.OauthTokenEndpoint = commonCreate.oauthTokenEndpoint + req.OauthClientAuthMethod = commonCreate.oauthClientAuthMethod + + if v, ok := d.GetOk("oauth_allowed_scopes"); ok { + elems := expandStringList(v.(*schema.Set).List()) + allowedScopes := make([]sdk.AllowedScope, len(elems)) + for i := range elems { + allowedScopes[i] = sdk.AllowedScope{Scope: elems[i]} + } + req.WithOauthAllowedScopes(allowedScopes) + } + + if err := client.SecurityIntegrations.CreateApiAuthenticationWithClientCredentialsFlow(ctx, req); err != nil { + return diag.FromErr(err) + } + + d.SetId(helpers.EncodeSnowflakeID(id)) + return ReadContextApiAuthenticationIntegrationWithClientCredentials(false)(ctx, d, meta) +} + +func ReadContextApiAuthenticationIntegrationWithClientCredentials(withExternalChangesMarking bool) schema.ReadContextFunc { + return func(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*provider.Context).Client + id := helpers.DecodeSnowflakeID(d.Id()).(sdk.AccountObjectIdentifier) + integration, err := client.SecurityIntegrations.ShowByID(ctx, id) + if err != nil { + if errors.Is(err, sdk.ErrObjectNotFound) { + d.SetId("") + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Warning, + Summary: "Failed to query security integration. Marking the resource as removed.", + Detail: fmt.Sprintf("Security integration name: %s, Err: %s", id.FullyQualifiedName(), err), + }, + } + } + return diag.FromErr(err) + } + properties, err := client.SecurityIntegrations.Describe(ctx, id) + if err != nil { + return diag.FromErr(err) + } + + if c := integration.Category; c != sdk.SecurityIntegrationCategory { + return diag.FromErr(fmt.Errorf("expected %v to be a %s integration, got %v", id, sdk.SecurityIntegrationCategory, c)) + } + oauthAllowedScopes, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_ALLOWED_SCOPES" }) + if err != nil { + return diag.FromErr(err) + } + + if err := handleApiAuthRead(d, integration, properties, withExternalChangesMarking, []describeMapping{ + {"oauth_allowed_scopes", "oauth_allowed_scopes", oauthAllowedScopes.Value, sdk.ParseCommaSeparatedStringArray(oauthAllowedScopes.Value, false), nil}, + }); err != nil { + return diag.FromErr(err) + } + if err := setStateToValuesFromConfig(d, warehouseSchema, []string{ + "oauth_allowed_scopes", + }); err != nil { + return diag.FromErr(err) + } + + return nil + } +} + +func UpdateContextApiAuthenticationIntegrationWithClientCredentials(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*provider.Context).Client + id := helpers.DecodeSnowflakeID(d.Id()).(sdk.AccountObjectIdentifier) + + commonSet, commonUnset, err := handleApiAuthUpdate(d) + if err != nil { + return diag.FromErr(err) + } + set := &sdk.ApiAuthenticationWithClientCredentialsFlowIntegrationSetRequest{ + Enabled: commonSet.enabled, + OauthTokenEndpoint: commonSet.oauthTokenEndpoint, + OauthClientAuthMethod: commonSet.oauthClientAuthMethod, + OauthClientId: commonSet.oauthClientId, + OauthClientSecret: commonSet.oauthClientSecret, + OauthAccessTokenValidity: commonSet.oauthAccessTokenValidity, + OauthRefreshTokenValidity: commonSet.oauthRefreshTokenValidity, + Comment: commonSet.comment, + } + unset := &sdk.ApiAuthenticationWithClientCredentialsFlowIntegrationUnsetRequest{ + Comment: commonUnset.comment, + } + + if d.HasChange("oauth_allowed_scopes") { + elems := expandStringList(d.Get("oauth_allowed_scopes").(*schema.Set).List()) + allowedScopes := make([]sdk.AllowedScope, len(elems)) + for i := range elems { + allowedScopes[i] = sdk.AllowedScope{Scope: elems[i]} + } + set.WithOauthAllowedScopes(allowedScopes) + } + + if !reflect.DeepEqual(*set, sdk.ApiAuthenticationWithClientCredentialsFlowIntegrationSetRequest{}) { + if err := client.SecurityIntegrations.AlterApiAuthenticationWithClientCredentialsFlow(ctx, sdk.NewAlterApiAuthenticationWithClientCredentialsFlowSecurityIntegrationRequest(id).WithSet(*set)); err != nil { + return diag.FromErr(err) + } + } + if !reflect.DeepEqual(*unset, sdk.ApiAuthenticationWithClientCredentialsFlowIntegrationUnsetRequest{}) { + if err := client.SecurityIntegrations.AlterApiAuthenticationWithClientCredentialsFlow(ctx, sdk.NewAlterApiAuthenticationWithClientCredentialsFlowSecurityIntegrationRequest(id).WithUnset(*unset)); err != nil { + return diag.FromErr(err) + } + } + + return ReadContextApiAuthenticationIntegrationWithClientCredentials(false)(ctx, d, meta) +} + +func DeleteContextApiAuthenticationIntegrationWithClientCredentials(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + id := helpers.DecodeSnowflakeID(d.Id()).(sdk.AccountObjectIdentifier) + client := meta.(*provider.Context).Client + + err := client.SecurityIntegrations.Drop(ctx, sdk.NewDropSecurityIntegrationRequest(sdk.NewAccountObjectIdentifier(id.Name())).WithIfExists(true)) + if err != nil { + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "Error deleting integration", + Detail: fmt.Sprintf("id %v err = %v", id.Name(), err), + }, + } + } + + d.SetId("") + return nil +} diff --git a/pkg/resources/api_authentication_integration_with_client_credentials_acceptance_test.go b/pkg/resources/api_authentication_integration_with_client_credentials_acceptance_test.go new file mode 100644 index 0000000000..1609e678ab --- /dev/null +++ b/pkg/resources/api_authentication_integration_with_client_credentials_acceptance_test.go @@ -0,0 +1,246 @@ +package resources_test + +import ( + "testing" + + acc "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/helpers" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/importchecks" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/tfversion" +) + +func TestAcc_ApiAuthenticationIntegrationWithClientCredentials_basic(t *testing.T) { + id := acc.TestClient().Ids.RandomAccountObjectIdentifier() + m := func(complete bool) map[string]config.Variable { + c := map[string]config.Variable{ + "enabled": config.BoolVariable(true), + "name": config.StringVariable(id.Name()), + "oauth_client_id": config.StringVariable("foo"), + "oauth_client_secret": config.StringVariable("foo"), + } + if complete { + c["comment"] = config.StringVariable("foo") + c["oauth_access_token_validity"] = config.IntegerVariable(42) + c["oauth_client_auth_method"] = config.StringVariable(string(sdk.ApiAuthenticationSecurityIntegrationOauthClientAuthMethodClientSecretPost)) + c["oauth_refresh_token_validity"] = config.IntegerVariable(12345) + c["oauth_token_endpoint"] = config.StringVariable("https://example.com") + c["oauth_allowed_scopes"] = config.SetVariable(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), + }, + Steps: []resource.TestStep{ + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithClientCredentials/basic"), + ConfigVariables: m(false), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "enabled", "true"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "name", id.Name()), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_client_id", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_client_secret", "foo"), + + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "show_output.#", "1"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "show_output.0.name", id.Name()), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "show_output.0.integration_type", "API_AUTHENTICATION"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "show_output.0.category", "SECURITY"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "show_output.0.enabled", "true"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "show_output.0.comment", ""), + resource.TestCheckResourceAttrSet("snowflake_api_authentication_integration_with_client_credentials.test", "show_output.0.created_on"), + + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.#", "1"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.enabled.0.value", "true"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.oauth_access_token_validity.0.value", "0"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.oauth_refresh_token_validity.0.value", "0"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.oauth_client_id.0.value", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.oauth_client_auth_method.0.value", ""), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.oauth_token_endpoint.0.value", ""), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.oauth_allowed_scopes.0.value", ""), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.oauth_grant.0.value", sdk.ApiAuthenticationSecurityIntegrationOauthGrantClientCredentials), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.parent_integration.0.value", ""), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.auth_type.0.value", "OAUTH2"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.comment.0.value", ""), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithClientCredentials/basic"), + ConfigVariables: m(false), + ResourceName: "snowflake_api_authentication_integration_with_client_credentials.test", + ImportState: true, + ImportStateCheck: importchecks.ComposeImportStateCheck( + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "name", id.Name()), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "enabled", "true"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "oauth_client_id", "foo"), + + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "show_output.#", "1"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "show_output.0.name", id.Name()), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "show_output.0.integration_type", "API_AUTHENTICATION"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "show_output.0.category", "SECURITY"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "show_output.0.enabled", "true"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "show_output.0.comment", ""), + + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.#", "1"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.enabled.0.value", "true"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.oauth_access_token_validity.0.value", "0"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.oauth_refresh_token_validity.0.value", "0"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.oauth_client_id.0.value", "foo"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.oauth_client_auth_method.0.value", ""), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.oauth_token_endpoint.0.value", ""), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.oauth_allowed_scopes.0.value", ""), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.oauth_grant.0.value", sdk.ApiAuthenticationSecurityIntegrationOauthGrantClientCredentials), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.parent_integration.0.value", ""), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.auth_type.0.value", "OAUTH2"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.comment.0.value", ""), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithClientCredentials/complete"), + ConfigVariables: m(true), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "comment", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "enabled", "true"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "name", id.Name()), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_access_token_validity", "42"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_client_auth_method", string(sdk.ApiAuthenticationSecurityIntegrationOauthClientAuthMethodClientSecretPost)), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_client_id", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_client_secret", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_refresh_token_validity", "12345"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_token_endpoint", "https://example.com"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_allowed_scopes.#", "1"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_allowed_scopes.0", "foo"), + + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "show_output.#", "1"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "show_output.0.name", id.Name()), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "show_output.0.integration_type", "API_AUTHENTICATION"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "show_output.0.category", "SECURITY"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "show_output.0.enabled", "true"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "show_output.0.comment", "foo"), + resource.TestCheckResourceAttrSet("snowflake_api_authentication_integration_with_client_credentials.test", "show_output.0.created_on"), + + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.#", "1"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.enabled.0.value", "true"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.oauth_access_token_validity.0.value", "42"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.oauth_refresh_token_validity.0.value", "12345"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.oauth_client_id.0.value", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.oauth_client_auth_method.0.value", string(sdk.ApiAuthenticationSecurityIntegrationOauthClientAuthMethodClientSecretPost)), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.oauth_token_endpoint.0.value", "https://example.com"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.oauth_allowed_scopes.0.value", "[foo]"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.oauth_grant.0.value", sdk.ApiAuthenticationSecurityIntegrationOauthGrantClientCredentials), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.parent_integration.0.value", ""), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.auth_type.0.value", "OAUTH2"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.comment.0.value", "foo")), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithClientCredentials/basic"), + ConfigVariables: m(true), + ResourceName: "snowflake_api_authentication_integration_with_client_credentials.test", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"oauth_client_secret"}, + }, + // unset + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithClientCredentials/basic"), + ConfigVariables: m(false), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "comment", ""), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "enabled", "true"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "name", id.Name()), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_access_token_validity", "-1"), + resource.TestCheckNoResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_client_auth_method"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_client_id", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_client_secret", "foo"), + resource.TestCheckNoResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_refresh_token_validity"), + resource.TestCheckNoResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_token_endpoint"), + ), + }, + }, + }) +} + +func TestAcc_ApiAuthenticationIntegrationWithClientCredentials_complete(t *testing.T) { + id := acc.TestClient().Ids.RandomAccountObjectIdentifier() + m := func() map[string]config.Variable { + return map[string]config.Variable{ + "comment": config.StringVariable("foo"), + "enabled": config.BoolVariable(true), + "name": config.StringVariable(id.Name()), + "oauth_access_token_validity": config.IntegerVariable(42), + "oauth_client_auth_method": config.StringVariable(string(sdk.ApiAuthenticationSecurityIntegrationOauthClientAuthMethodClientSecretPost)), + "oauth_client_id": config.StringVariable("foo"), + "oauth_client_secret": config.StringVariable("foo"), + "oauth_refresh_token_validity": config.IntegerVariable(12345), + "oauth_token_endpoint": config.StringVariable("https://example.com"), + "oauth_allowed_scopes": config.SetVariable(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), + }, + Steps: []resource.TestStep{ + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithClientCredentials/complete"), + ConfigVariables: m(), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "comment", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "enabled", "true"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "name", id.Name()), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_access_token_validity", "42"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_client_auth_method", string(sdk.ApiAuthenticationSecurityIntegrationOauthClientAuthMethodClientSecretPost)), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_client_id", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_client_secret", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_refresh_token_validity", "12345"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_token_endpoint", "https://example.com"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_allowed_scopes.#", "1"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "oauth_allowed_scopes.0", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_client_credentials.test", "describe_output.0.oauth_allowed_scopes.0.value", "[foo]"), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithClientCredentials/complete"), + ConfigVariables: m(), + ResourceName: "snowflake_api_authentication_integration_with_client_credentials.test", + ImportState: true, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"oauth_client_secret"}, + }, + }, + }) +} + +func TestAcc_ApiAuthenticationIntegrationWithClientCredentials_invalidIncomplete(t *testing.T) { + m := func() map[string]config.Variable { + return map[string]config.Variable{ + "name": 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{ + `The argument "oauth_client_secret" is required, but no definition was found.`, + `The argument "oauth_client_id" is required, but no definition was found.`, + `The argument "enabled" is required, but no definition was found.`, + }), + Steps: []resource.TestStep{ + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithClientCredentials/invalid"), + ConfigVariables: m(), + }, + }, + }) +} diff --git a/pkg/resources/api_authentication_integration_with_jwt_bearer.go b/pkg/resources/api_authentication_integration_with_jwt_bearer.go new file mode 100644 index 0000000000..e93d40ced9 --- /dev/null +++ b/pkg/resources/api_authentication_integration_with_jwt_bearer.go @@ -0,0 +1,226 @@ +package resources + +import ( + "context" + "errors" + "fmt" + "reflect" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/collections" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/logging" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + + "github.com/hashicorp/terraform-plugin-sdk/v2/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +var apiAuthJwtBearerSchema = func() map[string]*schema.Schema { + apiAuthJwtBearer := map[string]*schema.Schema{ + "oauth_authorization_endpoint": { + Type: schema.TypeString, + Optional: true, + Description: "Specifies the URL for authenticating to the external service.", + }, + "oauth_assertion_issuer": { + Type: schema.TypeString, + Required: true, + }, + } + return MergeMaps(apiAuthCommonSchema, apiAuthJwtBearer) +}() + +func ApiAuthenticationIntegrationWithJwtBearer() *schema.Resource { + return &schema.Resource{ + CreateContext: CreateContextApiAuthenticationIntegrationWithJwtBearer, + ReadContext: ReadContextApiAuthenticationIntegrationWithJwtBearer(true), + UpdateContext: UpdateContextApiAuthenticationIntegrationWithJwtBearer, + DeleteContext: DeleteContextApiAuthenticationIntegrationWithJwtBearer, + Schema: apiAuthJwtBearerSchema, + CustomizeDiff: customdiff.All( + ForceNewIfChangeToEmptyString("oauth_token_endpoint"), + ForceNewIfChangeToEmptyString("oauth_authorization_endpoint"), + ForceNewIfChangeToEmptyString("oauth_client_auth_method"), + ComputedIfAnyAttributeChanged(ShowOutputAttributeName, "enabled", "comment"), + ComputedIfAnyAttributeChanged(DescribeOutputAttributeName, "enabled", "comment", "oauth_access_token_validity", "oauth_refresh_token_validity", + "oauth_client_id", "oauth_client_auth_method", "oauth_authorization_endpoint", + "oauth_token_endpoint", "oauth_assertion_issuer"), + ), + Importer: &schema.ResourceImporter{ + StateContext: ImportApiAuthenticationWithJwtBearer, + }, + } +} + +func ImportApiAuthenticationWithJwtBearer(ctx context.Context, d *schema.ResourceData, meta any) ([]*schema.ResourceData, error) { + logging.DebugLogger.Printf("[DEBUG] Starting api auth integration with jwt bearer import") + client := meta.(*provider.Context).Client + id := helpers.DecodeSnowflakeID(d.Id()).(sdk.AccountObjectIdentifier) + + integration, err := client.SecurityIntegrations.ShowByID(ctx, id) + if err != nil { + return nil, err + } + + properties, err := client.SecurityIntegrations.Describe(ctx, id) + if err != nil { + return nil, err + } + if err := handleApiAuthImport(d, integration, properties); err != nil { + return nil, err + } + oauthAuthorizationEndpoint, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "OAUTH_AUTHORIZATION_ENDPOINT" + }) + if err == nil { + if err = d.Set("oauth_authorization_endpoint", oauthAuthorizationEndpoint.Value); err != nil { + return nil, err + } + } + oauthAssertionIssuer, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_ASSERTION_ISSUER" }) + if err == nil { + if err = d.Set("oauth_assertion_issuer", oauthAssertionIssuer.Value); err != nil { + return nil, err + } + } + + return []*schema.ResourceData{d}, nil +} + +func CreateContextApiAuthenticationIntegrationWithJwtBearer(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*provider.Context).Client + commonCreate, err := handleApiAuthCreate(d) + if err != nil { + return diag.FromErr(err) + } + id := sdk.NewAccountObjectIdentifier(commonCreate.name) + req := sdk.NewCreateApiAuthenticationWithJwtBearerFlowSecurityIntegrationRequest(id, commonCreate.enabled, d.Get("oauth_assertion_issuer").(string), commonCreate.oauthClientId, commonCreate.oauthClientSecret) + req.WithOauthGrantJwtBearer(true) + req.Comment = commonCreate.comment + req.OauthAccessTokenValidity = commonCreate.oauthAccessTokenValidity + req.OauthRefreshTokenValidity = commonCreate.oauthRefreshTokenValidity + req.OauthTokenEndpoint = commonCreate.oauthTokenEndpoint + req.OauthClientAuthMethod = commonCreate.oauthClientAuthMethod + + if v, ok := d.GetOk("oauth_authorization_endpoint"); ok { + req.WithOauthAuthorizationEndpoint(v.(string)) + } + + if err := client.SecurityIntegrations.CreateApiAuthenticationWithJwtBearerFlow(ctx, req); err != nil { + return diag.FromErr(err) + } + + d.SetId(helpers.EncodeSnowflakeID(id)) + return ReadContextApiAuthenticationIntegrationWithJwtBearer(false)(ctx, d, meta) +} + +func ReadContextApiAuthenticationIntegrationWithJwtBearer(withExternalChangesMarking bool) schema.ReadContextFunc { + return func(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*provider.Context).Client + id := helpers.DecodeSnowflakeID(d.Id()).(sdk.AccountObjectIdentifier) + + integration, err := client.SecurityIntegrations.ShowByID(ctx, id) + if err != nil { + if errors.Is(err, sdk.ErrObjectNotFound) { + d.SetId("") + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Warning, + Summary: "Failed to query security integration. Marking the resource as removed.", + Detail: fmt.Sprintf("Security integration name: %s, Err: %s", id.FullyQualifiedName(), err), + }, + } + } + return diag.FromErr(err) + } + properties, err := client.SecurityIntegrations.Describe(ctx, id) + if err != nil { + return diag.FromErr(err) + } + + if c := integration.Category; c != sdk.SecurityIntegrationCategory { + return diag.FromErr(fmt.Errorf("expected %v to be a %s integration, got %v", id, sdk.SecurityIntegrationCategory, c)) + } + oauthAuthorizationEndpoint, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { + return property.Name == "OAUTH_AUTHORIZATION_ENDPOINT" + }) + if err != nil { + return diag.FromErr(err) + } + + oauthAssertionIssuer, err := collections.FindOne(properties, func(property sdk.SecurityIntegrationProperty) bool { return property.Name == "OAUTH_ASSERTION_ISSUER" }) + if err != nil { + return diag.FromErr(err) + } + if err := handleApiAuthRead(d, integration, properties, withExternalChangesMarking, []describeMapping{ + {"oauth_authorization_endpoint", "oauth_authorization_endpoint", oauthAuthorizationEndpoint.Value, oauthAuthorizationEndpoint.Value, nil}, + {"oauth_assertion_issuer", "oauth_assertion_issuer", oauthAssertionIssuer.Value, oauthAssertionIssuer.Value, nil}, + }); err != nil { + return diag.FromErr(err) + } + if err := setStateToValuesFromConfig(d, warehouseSchema, []string{ + "oauth_authorization_endpoint", + "oauth_assertion_issuer", + }); err != nil { + return diag.FromErr(err) + } + return nil + } +} + +func UpdateContextApiAuthenticationIntegrationWithJwtBearer(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*provider.Context).Client + id := helpers.DecodeSnowflakeID(d.Id()).(sdk.AccountObjectIdentifier) + commonSet, commonUnset, err := handleApiAuthUpdate(d) + if err != nil { + return diag.FromErr(err) + } + set := &sdk.ApiAuthenticationWithJwtBearerFlowIntegrationSetRequest{ + Enabled: commonSet.enabled, + OauthTokenEndpoint: commonSet.oauthTokenEndpoint, + OauthClientAuthMethod: commonSet.oauthClientAuthMethod, + OauthClientId: commonSet.oauthClientId, + OauthClientSecret: commonSet.oauthClientSecret, + OauthAccessTokenValidity: commonSet.oauthAccessTokenValidity, + OauthRefreshTokenValidity: commonSet.oauthRefreshTokenValidity, + Comment: commonSet.comment, + } + unset := &sdk.ApiAuthenticationWithJwtBearerFlowIntegrationUnsetRequest{ + Comment: commonUnset.comment, + } + if d.HasChange("oauth_authorization_endpoint") { + set.WithOauthAuthorizationEndpoint(d.Get("oauth_authorization_endpoint").(string)) + } + if !reflect.DeepEqual(*set, sdk.ApiAuthenticationWithJwtBearerFlowIntegrationSetRequest{}) { + if err := client.SecurityIntegrations.AlterApiAuthenticationWithJwtBearerFlow(ctx, sdk.NewAlterApiAuthenticationWithJwtBearerFlowSecurityIntegrationRequest(id).WithSet(*set)); err != nil { + return diag.FromErr(err) + } + } + if !reflect.DeepEqual(*unset, sdk.ApiAuthenticationWithJwtBearerFlowIntegrationUnsetRequest{}) { + if err := client.SecurityIntegrations.AlterApiAuthenticationWithJwtBearerFlow(ctx, sdk.NewAlterApiAuthenticationWithJwtBearerFlowSecurityIntegrationRequest(id).WithUnset(*unset)); err != nil { + return diag.FromErr(err) + } + } + return ReadContextApiAuthenticationIntegrationWithJwtBearer(false)(ctx, d, meta) +} + +func DeleteContextApiAuthenticationIntegrationWithJwtBearer(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + id := helpers.DecodeSnowflakeID(d.Id()).(sdk.AccountObjectIdentifier) + client := meta.(*provider.Context).Client + + err := client.SecurityIntegrations.Drop(ctx, sdk.NewDropSecurityIntegrationRequest(sdk.NewAccountObjectIdentifier(id.Name())).WithIfExists(true)) + if err != nil { + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Error, + Summary: "Error deleting integration", + Detail: fmt.Sprintf("id %v err = %v", id.Name(), err), + }, + } + } + + d.SetId("") + return nil +} diff --git a/pkg/resources/api_authentication_integration_with_jwt_bearer_acceptance_test.go b/pkg/resources/api_authentication_integration_with_jwt_bearer_acceptance_test.go new file mode 100644 index 0000000000..9f4d24d8ff --- /dev/null +++ b/pkg/resources/api_authentication_integration_with_jwt_bearer_acceptance_test.go @@ -0,0 +1,213 @@ +package resources_test + +import ( + "testing" + + acc "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/helpers" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/importchecks" + + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/tfversion" +) + +func TestAcc_ApiAuthenticationIntegrationWithJwtBearer_basic(t *testing.T) { + // TODO [SNOW-1452191]: unskip + t.Skip("Skip because of the error: Invalid value specified for property 'OAUTH_CLIENT_SECRET'") + id := acc.TestClient().Ids.RandomAccountObjectIdentifier() + m := func(complete bool) map[string]config.Variable { + c := map[string]config.Variable{ + "enabled": config.BoolVariable(true), + "name": config.StringVariable(id.Name()), + "oauth_client_id": config.StringVariable("foo"), + "oauth_client_secret": config.StringVariable("foo"), + } + if complete { + c["comment"] = config.StringVariable("foo") + c["oauth_access_token_validity"] = config.IntegerVariable(42) + c["oauth_authorization_endpoint"] = config.StringVariable("foo") + c["oauth_client_auth_method"] = config.StringVariable("foo") + c["oauth_refresh_token_validity"] = config.IntegerVariable(42) + c["oauth_token_endpoint"] = 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), + }, + Steps: []resource.TestStep{ + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithJwtBearer/basic"), + ConfigVariables: m(false), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "enabled", "true"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "name", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "oauth_client_id", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "oauth_client_secret", "foo"), + resource.TestCheckResourceAttrSet("snowflake_api_authentication_integration_with_jwt_bearer.test", "created_on"), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithJwtBearer/basic"), + ConfigVariables: m(false), + ResourceName: "snowflake_api_authentication_integration_with_jwt_bearer.test", + ImportState: true, + ImportStateCheck: importchecks.ComposeImportStateCheck( + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "name", id.Name()), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "enabled", "true"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "oauth_client_id", "foo"), + + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "show_output.#", "1"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "show_output.0.name", id.Name()), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "show_output.0.integration_type", "API_AUTHENTICATION"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "show_output.0.category", "SECURITY"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "show_output.0.enabled", "true"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "show_output.0.comment", ""), + + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.#", "1"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.enabled.0.value", "true"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.oauth_access_token_validity.0.value", "0"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.oauth_refresh_token_validity.0.value", "0"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.oauth_client_id.0.value", "foo"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.oauth_client_auth_method.0.value", ""), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.oauth_token_endpoint.0.value", ""), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.oauth_allowed_scopes.0.value", ""), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.oauth_grant.0.value", ""), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.parent_integration.0.value", ""), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.auth_type.0.value", "OAUTH2"), + importchecks.TestCheckResourceAttrInstanceState(id.Name(), "describe_output.0.comment.0.value", ""), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithJwtBearer/complete"), + ConfigVariables: m(true), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "comment", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "created_on", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "enabled", "true"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "name", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "oauth_access_token_validity", "42"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "oauth_authorization_endpoint", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "oauth_client_auth_method", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "oauth_client_id", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "oauth_client_secret", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "oauth_refresh_token_validity", "42"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "oauth_token_endpoint", "foo"), + resource.TestCheckResourceAttrSet("snowflake_api_authentication_integration_with_jwt_bearer.test", "created_on"), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithJwtBearer/basic"), + ConfigVariables: m(true), + ResourceName: "snowflake_api_authentication_integration_with_jwt_bearer.test", + ImportState: true, + ImportStateVerify: true, + }, + // unset + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithJwtBearer/basic"), + ConfigVariables: m(false), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "comment", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "created_on", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "enabled", "true"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "name", id.Name()), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "oauth_access_token_validity", "42"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "oauth_authorization_endpoint", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "oauth_client_auth_method", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "oauth_client_id", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "oauth_client_secret", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "oauth_refresh_token_validity", "42"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "oauth_token_endpoint", "foo"), + resource.TestCheckResourceAttrSet("snowflake_api_authentication_integration_with_jwt_bearer.test", "created_on"), + ), + }, + }, + }) +} + +func TestAcc_ApiAuthenticationIntegrationWithJwtBearer_complete(t *testing.T) { + // TODO [SNOW-1452191]: unskip + t.Skip("Skip because of the error: Invalid value specified for property 'OAUTH_CLIENT_SECRET'") + id := acc.TestClient().Ids.RandomAccountObjectIdentifier() + m := func() map[string]config.Variable { + return map[string]config.Variable{ + "comment": config.StringVariable("foo"), + "enabled": config.BoolVariable(true), + "name": config.StringVariable(id.Name()), + "oauth_access_token_validity": config.IntegerVariable(42), + "oauth_authorization_endpoint": config.StringVariable("foo"), + "oauth_client_auth_method": config.StringVariable("foo"), + "oauth_client_id": config.StringVariable("foo"), + "oauth_client_secret": config.StringVariable("foo"), + "oauth_refresh_token_validity": config.IntegerVariable(42), + "oauth_token_endpoint": 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), + }, + Steps: []resource.TestStep{ + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithJwtBearer/complete"), + ConfigVariables: m(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "comment", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "created_on", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "enabled", "true"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "name", id.Name()), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "oauth_access_token_validity", "42"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "oauth_authorization_endpoint", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "oauth_client_auth_method", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "oauth_client_id", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "oauth_client_secret", "foo"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "oauth_refresh_token_validity", "42"), + resource.TestCheckResourceAttr("snowflake_api_authentication_integration_with_jwt_bearer.test", "oauth_token_endpoint", "foo"), + resource.TestCheckResourceAttrSet("snowflake_api_authentication_integration_with_jwt_bearer.test", "created_on"), + ), + }, + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithJwtBearer/complete"), + ConfigVariables: m(), + ResourceName: "snowflake_api_authentication_integration_with_jwt_bearer.test", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAcc_ApiAuthenticationIntegrationWithJwtBearer_invalidIncomplete(t *testing.T) { + m := func() map[string]config.Variable { + return map[string]config.Variable{ + "name": 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{ + `The argument "enabled" is required, but no definition was found.`, + // this one is trimmed because of inconsistent \n behavior in error message + `The argument "oauth_assertion_issuer" is required, but no definition`, + `The argument "oauth_client_id" is required, but no definition was found.`, + `The argument "oauth_client_secret" is required, but no definition was found.`, + }), + Steps: []resource.TestStep{ + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_ApiAuthenticationIntegrationWithJwtBearer/invalid"), + ConfigVariables: m(), + }, + }, + }) +} diff --git a/pkg/resources/common.go b/pkg/resources/common.go index 3f6380258d..a2a33a7c1d 100644 --- a/pkg/resources/common.go +++ b/pkg/resources/common.go @@ -4,6 +4,7 @@ import ( "strings" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" + "github.com/hashicorp/go-cty/cty" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) @@ -52,3 +53,11 @@ func suppressQuoting(_, oldValue, newValue string, _ *schema.ResourceData) bool return oldWithoutQuotes == newWithoutQuotes } } + +func ctyValToSliceString(valueElems []cty.Value) []string { + elems := make([]string, len(valueElems)) + for i, v := range valueElems { + elems[i] = v.AsString() + } + return elems +} diff --git a/pkg/resources/database_acceptance_test.go b/pkg/resources/database_acceptance_test.go index 3ca0128b94..d79e3d4797 100644 --- a/pkg/resources/database_acceptance_test.go +++ b/pkg/resources/database_acceptance_test.go @@ -1138,7 +1138,7 @@ func databaseStateUpgraderWithReplicationOld(id sdk.AccountObjectIdentifier, ena resource "snowflake_database" "test" { name = "%s" replication_configuration { - accounts = ["%s"] + accounts = ["%s"] ignore_edition_check = true } } diff --git a/pkg/resources/saml2_integration.go b/pkg/resources/saml2_integration.go index 259c8847df..6b03ef3c75 100644 --- a/pkg/resources/saml2_integration.go +++ b/pkg/resources/saml2_integration.go @@ -320,7 +320,7 @@ func ImportSaml2Integration(ctx context.Context, d *schema.ResourceData, meta an 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)); err != nil { + if err := d.Set("allowed_user_domains", sdk.ParseCommaSeparatedStringArray(allowedUserDomains.Value, false)); err != nil { return nil, err } @@ -330,7 +330,7 @@ func ImportSaml2Integration(ctx context.Context, d *schema.ResourceData, meta an 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)); err != nil { + if err := d.Set("allowed_email_patterns", sdk.ParseCommaSeparatedStringArray(allowedEmailDomains.Value, false)); err != nil { return nil, err } @@ -525,7 +525,7 @@ func ReadContextSAML2Integration(withExternalChangesMarking bool) schema.ReadCon 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)); err != nil { + if err := d.Set("allowed_user_domains", sdk.ParseCommaSeparatedStringArray(allowedUserDomains.Value, false)); err != nil { return diag.FromErr(err) } @@ -535,7 +535,7 @@ func ReadContextSAML2Integration(withExternalChangesMarking bool) schema.ReadCon 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)); err != nil { + if err := d.Set("allowed_email_patterns", sdk.ParseCommaSeparatedStringArray(allowedEmailDomains.Value, false)); err != nil { return diag.FromErr(err) } diff --git a/pkg/resources/show_and_describe_handlers.go b/pkg/resources/show_and_describe_handlers.go index 91a0fc9cb1..0e90c9e169 100644 --- a/pkg/resources/show_and_describe_handlers.go +++ b/pkg/resources/show_and_describe_handlers.go @@ -106,6 +106,10 @@ func setStateToValuesFromConfig(d *schema.ResourceData, resourceSchema map[strin if err := d.Set(field, v.AsString()); err != nil { return err } + case schema.TypeSet: + if err := d.Set(field, ctyValToSliceString(v.AsValueSlice())); err != nil { + return err + } default: log.Printf("[DEBUG] field %s has unsupported schema type %v not found", field, schemaField.Type) } diff --git a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/basic/test.tf b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/basic/test.tf new file mode 100644 index 0000000000..daf6b9b893 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/basic/test.tf @@ -0,0 +1,6 @@ +resource "snowflake_api_authentication_integration_with_authorization_code_grant" "test" { + enabled = var.enabled + name = var.name + oauth_client_id = var.oauth_client_id + oauth_client_secret = var.oauth_client_secret +} diff --git a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/basic/variables.tf b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/basic/variables.tf new file mode 100644 index 0000000000..34a32daf3e --- /dev/null +++ b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/basic/variables.tf @@ -0,0 +1,13 @@ + +variable "enabled" { + type = bool +} +variable "name" { + type = string +} +variable "oauth_client_id" { + type = string +} +variable "oauth_client_secret" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/complete/test.tf b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/complete/test.tf new file mode 100644 index 0000000000..0346d55f28 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/complete/test.tf @@ -0,0 +1,13 @@ +resource "snowflake_api_authentication_integration_with_authorization_code_grant" "test" { + comment = var.comment + enabled = var.enabled + name = var.name + oauth_access_token_validity = var.oauth_access_token_validity + oauth_authorization_endpoint = var.oauth_authorization_endpoint + oauth_client_auth_method = var.oauth_client_auth_method + oauth_client_id = var.oauth_client_id + oauth_client_secret = var.oauth_client_secret + oauth_refresh_token_validity = var.oauth_refresh_token_validity + oauth_token_endpoint = var.oauth_token_endpoint + oauth_allowed_scopes = var.oauth_allowed_scopes +} diff --git a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/complete/variables.tf b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/complete/variables.tf new file mode 100644 index 0000000000..06e9998ff5 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/complete/variables.tf @@ -0,0 +1,33 @@ +variable "comment" { + type = string +} +variable "enabled" { + type = bool +} +variable "name" { + type = string +} +variable "oauth_access_token_validity" { + type = number +} +variable "oauth_authorization_endpoint" { + type = string +} +variable "oauth_client_auth_method" { + type = string +} +variable "oauth_client_id" { + type = string +} +variable "oauth_client_secret" { + type = string +} +variable "oauth_refresh_token_validity" { + type = number +} +variable "oauth_token_endpoint" { + type = string +} +variable "oauth_allowed_scopes" { + type = set(string) +} diff --git a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/invalid/test.tf b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/invalid/test.tf new file mode 100644 index 0000000000..13f7028ac0 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/invalid/test.tf @@ -0,0 +1,3 @@ +resource "snowflake_api_authentication_integration_with_authorization_code_grant" "test" { + name = var.name +} diff --git a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/invalid/variables.tf b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/invalid/variables.tf new file mode 100644 index 0000000000..77e5cc9698 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithAuthorizationCodeGrant/invalid/variables.tf @@ -0,0 +1,3 @@ +variable "name" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/basic/test.tf b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/basic/test.tf new file mode 100644 index 0000000000..fad468b71f --- /dev/null +++ b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/basic/test.tf @@ -0,0 +1,6 @@ +resource "snowflake_api_authentication_integration_with_client_credentials" "test" { + enabled = var.enabled + name = var.name + oauth_client_id = var.oauth_client_id + oauth_client_secret = var.oauth_client_secret +} diff --git a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/basic/variables.tf b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/basic/variables.tf new file mode 100644 index 0000000000..f0aca04e61 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/basic/variables.tf @@ -0,0 +1,12 @@ +variable "enabled" { + type = bool +} +variable "name" { + type = string +} +variable "oauth_client_id" { + type = string +} +variable "oauth_client_secret" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/complete/test.tf b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/complete/test.tf new file mode 100644 index 0000000000..44c4b6c513 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/complete/test.tf @@ -0,0 +1,12 @@ +resource "snowflake_api_authentication_integration_with_client_credentials" "test" { + comment = var.comment + enabled = var.enabled + name = var.name + oauth_access_token_validity = var.oauth_access_token_validity + oauth_refresh_token_validity = var.oauth_refresh_token_validity + oauth_client_auth_method = var.oauth_client_auth_method + oauth_client_id = var.oauth_client_id + oauth_client_secret = var.oauth_client_secret + oauth_token_endpoint = var.oauth_token_endpoint + oauth_allowed_scopes = var.oauth_allowed_scopes +} diff --git a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/complete/variables.tf b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/complete/variables.tf new file mode 100644 index 0000000000..4ccccac436 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/complete/variables.tf @@ -0,0 +1,30 @@ +variable "comment" { + type = string +} +variable "enabled" { + type = bool +} +variable "name" { + type = string +} +variable "oauth_access_token_validity" { + type = number +} +variable "oauth_refresh_token_validity" { + type = number +} +variable "oauth_client_auth_method" { + type = string +} +variable "oauth_client_id" { + type = string +} +variable "oauth_client_secret" { + type = string +} +variable "oauth_token_endpoint" { + type = string +} +variable "oauth_allowed_scopes" { + type = set(string) +} diff --git a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/invalid/test.tf b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/invalid/test.tf new file mode 100644 index 0000000000..30a66f6c15 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/invalid/test.tf @@ -0,0 +1,3 @@ +resource "snowflake_api_authentication_integration_with_client_credentials" "test" { + name = var.name +} diff --git a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/invalid/variables.tf b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/invalid/variables.tf new file mode 100644 index 0000000000..77e5cc9698 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithClientCredentials/invalid/variables.tf @@ -0,0 +1,3 @@ +variable "name" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/basic/test.tf b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/basic/test.tf new file mode 100644 index 0000000000..25b0e145af --- /dev/null +++ b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/basic/test.tf @@ -0,0 +1,7 @@ +resource "snowflake_api_authentication_integration_with_jwt_bearer" "test" { + name = var.name + enabled = var.enabled + oauth_client_id = var.oauth_client_id + oauth_client_secret = var.oauth_client_secret + oauth_assertion_issuer = var.oauth_assertion_issuer +} diff --git a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/basic/variables.tf b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/basic/variables.tf new file mode 100644 index 0000000000..879a48ea65 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/basic/variables.tf @@ -0,0 +1,15 @@ +variable "enabled" { + type = bool +} +variable "name" { + type = string +} +variable "oauth_client_id" { + type = string +} +variable "oauth_client_secret" { + type = string +} +variable "oauth_assertion_issuer" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/complete/test.tf b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/complete/test.tf new file mode 100644 index 0000000000..f514dd7e23 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/complete/test.tf @@ -0,0 +1,13 @@ +resource "snowflake_api_authentication_integration_with_jwt_bearer" "test" { + comment = var.comment + enabled = var.enabled + name = var.name + oauth_access_token_validity = var.oauth_access_token_validity + oauth_authorization_endpoint = var.oauth_authorization_endpoint + oauth_client_auth_method = var.oauth_client_auth_method + oauth_client_id = var.oauth_client_id + oauth_client_secret = var.oauth_client_secret + oauth_refresh_token_validity = var.oauth_refresh_token_validity + oauth_token_endpoint = var.oauth_token_endpoint + oauth_assertion_issuer = var.oauth_assertion_issuer +} diff --git a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/complete/variables.tf b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/complete/variables.tf new file mode 100644 index 0000000000..10920ad938 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/complete/variables.tf @@ -0,0 +1,33 @@ +variable "comment" { + type = string +} +variable "enabled" { + type = bool +} +variable "name" { + type = string +} +variable "oauth_access_token_validity" { + type = number +} +variable "oauth_authorization_endpoint" { + type = string +} +variable "oauth_client_auth_method" { + type = string +} +variable "oauth_client_id" { + type = string +} +variable "oauth_client_secret" { + type = string +} +variable "oauth_refresh_token_validity" { + type = number +} +variable "oauth_token_endpoint" { + type = string +} +variable "oauth_assertion_issuer" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/invalid/test.tf b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/invalid/test.tf new file mode 100644 index 0000000000..2090469c34 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/invalid/test.tf @@ -0,0 +1,3 @@ +resource "snowflake_api_authentication_integration_with_jwt_bearer" "test" { + name = var.name +} diff --git a/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/invalid/variables.tf b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/invalid/variables.tf new file mode 100644 index 0000000000..77e5cc9698 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_ApiAuthenticationIntegrationWithJwtBearer/invalid/variables.tf @@ -0,0 +1,3 @@ +variable "name" { + type = string +} diff --git a/pkg/schemas/api_authentication_security_integration.go b/pkg/schemas/api_authentication_security_integration.go new file mode 100644 index 0000000000..913c99574e --- /dev/null +++ b/pkg/schemas/api_authentication_security_integration.go @@ -0,0 +1,54 @@ +package schemas + +import ( + "log" + "strings" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +// DescribeApiAuthSecurityIntegrationSchema represents output of DESCRIBE query for the single SecurityIntegration. +var DescribeApiAuthSecurityIntegrationSchema = map[string]*schema.Schema{ + "enabled": DescribePropertyListSchema, + "oauth_access_token_validity": DescribePropertyListSchema, + "oauth_refresh_token_validity": DescribePropertyListSchema, + "oauth_client_id": DescribePropertyListSchema, + "oauth_client_auth_method": DescribePropertyListSchema, + "oauth_authorization_endpoint": DescribePropertyListSchema, + "oauth_token_endpoint": DescribePropertyListSchema, + "oauth_allowed_scopes": DescribePropertyListSchema, + "oauth_grant": DescribePropertyListSchema, + "parent_integration": DescribePropertyListSchema, + "auth_type": DescribePropertyListSchema, + "comment": DescribePropertyListSchema, +} + +var _ = DescribeApiAuthSecurityIntegrationSchema + +func ApiAuthSecurityIntegrationPropertiesToSchema(securityIntegrationProperties []sdk.SecurityIntegrationProperty) map[string]any { + securityIntegrationSchema := make(map[string]any) + for _, securityIntegrationProperty := range securityIntegrationProperties { + securityIntegrationProperty := securityIntegrationProperty + switch securityIntegrationProperty.Name { + case "ENABLED", + "OAUTH_ACCESS_TOKEN_VALIDITY", + "OAUTH_REFRESH_TOKEN_VALIDITY", + "OAUTH_CLIENT_ID", + "OAUTH_CLIENT_AUTH_METHOD", + "OAUTH_AUTHORIZATION_ENDPOINT", + "OAUTH_TOKEN_ENDPOINT", + "OAUTH_ALLOWED_SCOPES", + "OAUTH_GRANT", + "PARENT_INTEGRATION", + "AUTH_TYPE", + "COMMENT": + securityIntegrationSchema[strings.ToLower(securityIntegrationProperty.Name)] = []map[string]any{SecurityIntegrationPropertyToSchema(&securityIntegrationProperty)} + default: + log.Printf("unknown field from DESCRIBE: %v", securityIntegrationProperty.Name) + } + } + return securityIntegrationSchema +} + +var _ = ApiAuthSecurityIntegrationPropertiesToSchema diff --git a/pkg/sdk/parsers.go b/pkg/sdk/parsers.go index 8e415373f3..26dc8b4a07 100644 --- a/pkg/sdk/parsers.go +++ b/pkg/sdk/parsers.go @@ -22,18 +22,18 @@ func ParseTimestampWithOffset(s string, dateTimeFormat string) (string, error) { // 1. The list is enclosed by [] brackets, and they shouldn't be a part of any item's value // 2. Items are separated by commas, and they shouldn't be a part of any item's value // 3. Items can have as many spaces in between, but after separation they will be trimmed and shouldn't be a part of any item's value -func ParseCommaSeparatedStringArray(value string) []string { - if strings.HasPrefix(value, "[") && strings.HasSuffix(value, "]") { - if value == "[]" { - return make([]string, 0) - } - list := strings.Trim(value, "[]") - listItems := strings.Split(list, ",") - trimmedListItems := make([]string, len(listItems)) - for i, item := range listItems { - trimmedListItems[i] = strings.TrimSpace(item) +func ParseCommaSeparatedStringArray(value string, trimQuotes bool) []string { + value = strings.Trim(value, "[]") + if value == "" { + return make([]string, 0) + } + listItems := strings.Split(value, ",") + trimmedListItems := make([]string, len(listItems)) + for i, item := range listItems { + trimmedListItems[i] = strings.TrimSpace(item) + if trimQuotes { + trimmedListItems[i] = strings.Trim(trimmedListItems[i], "'") } - return trimmedListItems } - return make([]string, 0) + return trimmedListItems } diff --git a/pkg/sdk/parsers_test.go b/pkg/sdk/parsers_test.go index 6ff3a59c16..c68ce19e47 100644 --- a/pkg/sdk/parsers_test.go +++ b/pkg/sdk/parsers_test.go @@ -8,9 +8,10 @@ import ( func TestParseCommaSeparatedStringArray(t *testing.T) { testCases := []struct { - Name string - Value string - Result []string + Name string + Value string + TrimQuotes bool + Result []string }{ { Name: "empty list", @@ -27,6 +28,18 @@ func TestParseCommaSeparatedStringArray(t *testing.T) { Value: "[one]", Result: []string{"one"}, }, + { + Name: "one element in list - with quotes", + Value: "['one']", + TrimQuotes: true, + Result: []string{"one"}, + }, + { + Name: "multiple elements in list - with quotes", + Value: "['one', 'two', 'three']", + TrimQuotes: true, + Result: []string{"one", "two", "three"}, + }, { Name: "multiple elements in list", Value: "[one, two, three]", @@ -45,13 +58,19 @@ func TestParseCommaSeparatedStringArray(t *testing.T) { { Name: "list without brackets", Value: "one,two,three", - Result: []string{}, + Result: []string{"one", "two", "three"}, + }, + { + Name: "list without brackets - with quotes", + Value: "'one','two','three'", + TrimQuotes: true, + Result: []string{"one", "two", "three"}, }, } for _, tc := range testCases { t.Run(tc.Name, func(t *testing.T) { - assert.Equal(t, tc.Result, ParseCommaSeparatedStringArray(tc.Value)) + assert.Equal(t, tc.Result, ParseCommaSeparatedStringArray(tc.Value, tc.TrimQuotes)) }) } } diff --git a/pkg/sdk/security_integrations_def.go b/pkg/sdk/security_integrations_def.go index db276281ee..f0f03ec6c3 100644 --- a/pkg/sdk/security_integrations_def.go +++ b/pkg/sdk/security_integrations_def.go @@ -9,7 +9,12 @@ import ( //go:generate go run ./poc/main.go -const SecurityIntegrationCategory = "SECURITY" +const ( + SecurityIntegrationCategory = "SECURITY" + ApiAuthenticationSecurityIntegrationOauthGrantAuthorizationCode = "AUTHORIZATION_CODE" + ApiAuthenticationSecurityIntegrationOauthGrantClientCredentials = "CLIENT_CREDENTIALS" //nolint:gosec + ApiAuthenticationSecurityIntegrationOauthGrantJwtBearer = "JWT_BEARER" +) type ApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption string @@ -17,6 +22,20 @@ const ( ApiAuthenticationSecurityIntegrationOauthClientAuthMethodClientSecretPost ApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption = "CLIENT_SECRET_POST" ) +var AllApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption = []ApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption{ + ApiAuthenticationSecurityIntegrationOauthClientAuthMethodClientSecretPost, +} + +func ToApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption(s string) (ApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption, error) { + s = strings.ToUpper(s) + switch s { + case string(ApiAuthenticationSecurityIntegrationOauthClientAuthMethodClientSecretPost): + return ApiAuthenticationSecurityIntegrationOauthClientAuthMethodClientSecretPost, nil + default: + return "", fmt.Errorf("invalid ApiAuthenticationSecurityIntegrationOauthClientAuthMethodOption: %s", s) + } +} + type ExternalOauthSecurityIntegrationTypeOption string const ( @@ -325,9 +344,10 @@ var apiAuthCodeGrantFlowIntegrationSetDef = g.NewQueryStruct("ApiAuthenticationW OptionalSQL("OAUTH_GRANT = AUTHORIZATION_CODE"). OptionalNumberAssignment("OAUTH_ACCESS_TOKEN_VALIDITY", g.ParameterOptions()). OptionalNumberAssignment("OAUTH_REFRESH_TOKEN_VALIDITY", g.ParameterOptions()). + ListAssignment("OAUTH_ALLOWED_SCOPES", "AllowedScope", g.ParameterOptions().Parentheses()). OptionalComment(). WithValidation(g.AtLeastOneValueSet, "Enabled", "OauthAuthorizationEndpoint", "OauthTokenEndpoint", "OauthClientAuthMethod", "OauthClientId", "OauthClientSecret", "OauthGrantAuthorizationCode", - "OauthAccessTokenValidity", "OauthRefreshTokenValidity", "Comment") + "OauthAccessTokenValidity", "OauthRefreshTokenValidity", "OauthAllowedScopes", "Comment") var apiAuthCodeGrantFlowIntegrationUnsetDef = g.NewQueryStruct("ApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationUnset"). OptionalSQL("ENABLED"). @@ -544,7 +564,8 @@ var SecurityIntegrationsDef = g.NewInterface( TextAssignment("OAUTH_CLIENT_SECRET", g.ParameterOptions().Required().SingleQuotes()). OptionalSQL("OAUTH_GRANT = AUTHORIZATION_CODE"). OptionalNumberAssignment("OAUTH_ACCESS_TOKEN_VALIDITY", g.ParameterOptions()). - OptionalNumberAssignment("OAUTH_REFRESH_TOKEN_VALIDITY", g.ParameterOptions()) + OptionalNumberAssignment("OAUTH_REFRESH_TOKEN_VALIDITY", g.ParameterOptions()). + ListAssignment("OAUTH_ALLOWED_SCOPES", "AllowedScope", g.ParameterOptions().Parentheses()) }), ). CustomOperation( diff --git a/pkg/sdk/security_integrations_dto_builders_gen.go b/pkg/sdk/security_integrations_dto_builders_gen.go index 12bbc1ebd1..97efc1b065 100644 --- a/pkg/sdk/security_integrations_dto_builders_gen.go +++ b/pkg/sdk/security_integrations_dto_builders_gen.go @@ -117,6 +117,11 @@ func (s *CreateApiAuthenticationWithAuthorizationCodeGrantFlowSecurityIntegratio return s } +func (s *CreateApiAuthenticationWithAuthorizationCodeGrantFlowSecurityIntegrationRequest) WithOauthAllowedScopes(OauthAllowedScopes []AllowedScope) *CreateApiAuthenticationWithAuthorizationCodeGrantFlowSecurityIntegrationRequest { + s.OauthAllowedScopes = OauthAllowedScopes + return s +} + func (s *CreateApiAuthenticationWithAuthorizationCodeGrantFlowSecurityIntegrationRequest) WithComment(Comment string) *CreateApiAuthenticationWithAuthorizationCodeGrantFlowSecurityIntegrationRequest { s.Comment = &Comment return s @@ -747,6 +752,11 @@ func (s *ApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationSetRequest) W return s } +func (s *ApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationSetRequest) WithOauthAllowedScopes(OauthAllowedScopes []AllowedScope) *ApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationSetRequest { + s.OauthAllowedScopes = OauthAllowedScopes + return s +} + func (s *ApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationSetRequest) WithComment(Comment string) *ApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationSetRequest { s.Comment = &Comment return s diff --git a/pkg/sdk/security_integrations_dto_gen.go b/pkg/sdk/security_integrations_dto_gen.go index 4280f89064..7e427facb7 100644 --- a/pkg/sdk/security_integrations_dto_gen.go +++ b/pkg/sdk/security_integrations_dto_gen.go @@ -53,6 +53,7 @@ type CreateApiAuthenticationWithAuthorizationCodeGrantFlowSecurityIntegrationReq OauthGrantAuthorizationCode *bool OauthAccessTokenValidity *int OauthRefreshTokenValidity *int + OauthAllowedScopes []AllowedScope Comment *string } @@ -241,6 +242,7 @@ type ApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationSetRequest struct OauthGrantAuthorizationCode *bool OauthAccessTokenValidity *int OauthRefreshTokenValidity *int + OauthAllowedScopes []AllowedScope Comment *string } diff --git a/pkg/sdk/security_integrations_gen.go b/pkg/sdk/security_integrations_gen.go index ceb452d972..b3a5c8801f 100644 --- a/pkg/sdk/security_integrations_gen.go +++ b/pkg/sdk/security_integrations_gen.go @@ -51,7 +51,6 @@ type CreateApiAuthenticationWithClientCredentialsFlowSecurityIntegrationOptions OauthAllowedScopes []AllowedScope `ddl:"parameter,parentheses" sql:"OAUTH_ALLOWED_SCOPES"` Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` } - type AllowedScope struct { Scope string `ddl:"keyword,single_quotes"` } @@ -74,6 +73,7 @@ type CreateApiAuthenticationWithAuthorizationCodeGrantFlowSecurityIntegrationOpt OauthGrantAuthorizationCode *bool `ddl:"keyword" sql:"OAUTH_GRANT = AUTHORIZATION_CODE"` OauthAccessTokenValidity *int `ddl:"parameter" sql:"OAUTH_ACCESS_TOKEN_VALIDITY"` OauthRefreshTokenValidity *int `ddl:"parameter" sql:"OAUTH_REFRESH_TOKEN_VALIDITY"` + OauthAllowedScopes []AllowedScope `ddl:"parameter,parentheses" sql:"OAUTH_ALLOWED_SCOPES"` Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` } @@ -123,27 +123,21 @@ type CreateExternalOauthSecurityIntegrationOptions struct { ExternalOauthScopeMappingAttribute *string `ddl:"parameter,single_quotes" sql:"EXTERNAL_OAUTH_SCOPE_MAPPING_ATTRIBUTE"` Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` } - type AllowedRolesList struct { AllowedRolesList []AccountObjectIdentifier `ddl:"list,must_parentheses"` } - type BlockedRolesList struct { BlockedRolesList []AccountObjectIdentifier `ddl:"list,must_parentheses"` } - type JwsKeysUrl struct { JwsKeyUrl string `ddl:"keyword,single_quotes"` } - type AudienceList struct { AudienceList []AudienceListItem `ddl:"list,must_parentheses"` } - type AudienceListItem struct { Item string `ddl:"keyword,single_quotes"` } - type TokenUserMappingClaim struct { Claim string `ddl:"keyword,single_quotes"` } @@ -165,7 +159,6 @@ type CreateOauthForPartnerApplicationsSecurityIntegrationOptions struct { BlockedRolesList *BlockedRolesList `ddl:"parameter,parentheses" sql:"BLOCKED_ROLES_LIST"` Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` } - type PreAuthorizedRolesList struct { PreAuthorizedRolesList []AccountObjectIdentifier `ddl:"list,must_parentheses"` } @@ -221,11 +214,9 @@ type CreateSaml2SecurityIntegrationOptions struct { Saml2SnowflakeAcsUrl *string `ddl:"parameter,single_quotes" sql:"SAML2_SNOWFLAKE_ACS_URL"` Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` } - type UserDomain struct { Domain string `ddl:"keyword,single_quotes"` } - type EmailPattern struct { Pattern string `ddl:"keyword,single_quotes"` } @@ -257,7 +248,6 @@ type AlterApiAuthenticationWithClientCredentialsFlowSecurityIntegrationOptions s Set *ApiAuthenticationWithClientCredentialsFlowIntegrationSet `ddl:"list,no_parentheses" sql:"SET"` Unset *ApiAuthenticationWithClientCredentialsFlowIntegrationUnset `ddl:"list,no_parentheses" sql:"UNSET"` } - type ApiAuthenticationWithClientCredentialsFlowIntegrationSet struct { Enabled *bool `ddl:"parameter" sql:"ENABLED"` OauthTokenEndpoint *string `ddl:"parameter,single_quotes" sql:"OAUTH_TOKEN_ENDPOINT"` @@ -270,7 +260,6 @@ type ApiAuthenticationWithClientCredentialsFlowIntegrationSet struct { OauthAllowedScopes []AllowedScope `ddl:"parameter,parentheses" sql:"OAUTH_ALLOWED_SCOPES"` Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` } - type ApiAuthenticationWithClientCredentialsFlowIntegrationUnset struct { Enabled *bool `ddl:"keyword" sql:"ENABLED"` Comment *bool `ddl:"keyword" sql:"COMMENT"` @@ -287,7 +276,6 @@ type AlterApiAuthenticationWithAuthorizationCodeGrantFlowSecurityIntegrationOpti Set *ApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationSet `ddl:"list,no_parentheses" sql:"SET"` Unset *ApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationUnset `ddl:"list,no_parentheses" sql:"UNSET"` } - type ApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationSet struct { Enabled *bool `ddl:"parameter" sql:"ENABLED"` OauthAuthorizationEndpoint *string `ddl:"parameter,single_quotes" sql:"OAUTH_AUTHORIZATION_ENDPOINT"` @@ -298,9 +286,9 @@ type ApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationSet struct { OauthGrantAuthorizationCode *bool `ddl:"keyword" sql:"OAUTH_GRANT = AUTHORIZATION_CODE"` OauthAccessTokenValidity *int `ddl:"parameter" sql:"OAUTH_ACCESS_TOKEN_VALIDITY"` OauthRefreshTokenValidity *int `ddl:"parameter" sql:"OAUTH_REFRESH_TOKEN_VALIDITY"` + OauthAllowedScopes []AllowedScope `ddl:"parameter,parentheses" sql:"OAUTH_ALLOWED_SCOPES"` Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` } - type ApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationUnset struct { Enabled *bool `ddl:"keyword" sql:"ENABLED"` Comment *bool `ddl:"keyword" sql:"COMMENT"` @@ -317,7 +305,6 @@ type AlterApiAuthenticationWithJwtBearerFlowSecurityIntegrationOptions struct { Set *ApiAuthenticationWithJwtBearerFlowIntegrationSet `ddl:"list,no_parentheses" sql:"SET"` Unset *ApiAuthenticationWithJwtBearerFlowIntegrationUnset `ddl:"list,no_parentheses" sql:"UNSET"` } - type ApiAuthenticationWithJwtBearerFlowIntegrationSet struct { Enabled *bool `ddl:"parameter" sql:"ENABLED"` OauthAuthorizationEndpoint *string `ddl:"parameter,single_quotes" sql:"OAUTH_AUTHORIZATION_ENDPOINT"` @@ -330,7 +317,6 @@ type ApiAuthenticationWithJwtBearerFlowIntegrationSet struct { OauthRefreshTokenValidity *int `ddl:"parameter" sql:"OAUTH_REFRESH_TOKEN_VALIDITY"` Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` } - type ApiAuthenticationWithJwtBearerFlowIntegrationUnset struct { Enabled *bool `ddl:"keyword" sql:"ENABLED"` Comment *bool `ddl:"keyword" sql:"COMMENT"` @@ -347,7 +333,6 @@ type AlterExternalOauthSecurityIntegrationOptions struct { Set *ExternalOauthIntegrationSet `ddl:"list,no_parentheses" sql:"SET"` Unset *ExternalOauthIntegrationUnset `ddl:"list,no_parentheses" sql:"UNSET"` } - type ExternalOauthIntegrationSet struct { Enabled *bool `ddl:"parameter" sql:"ENABLED"` ExternalOauthType *ExternalOauthSecurityIntegrationTypeOption `ddl:"parameter" sql:"EXTERNAL_OAUTH_TYPE"` @@ -364,7 +349,6 @@ type ExternalOauthIntegrationSet struct { ExternalOauthScopeDelimiter *string `ddl:"parameter,single_quotes" sql:"EXTERNAL_OAUTH_SCOPE_DELIMITER"` Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` } - type ExternalOauthIntegrationUnset struct { Enabled *bool `ddl:"keyword" sql:"ENABLED"` ExternalOauthAudienceList *bool `ddl:"keyword" sql:"EXTERNAL_OAUTH_AUDIENCE_LIST"` @@ -381,7 +365,6 @@ type AlterOauthForPartnerApplicationsSecurityIntegrationOptions struct { Set *OauthForPartnerApplicationsIntegrationSet `ddl:"list,no_parentheses" sql:"SET"` Unset *OauthForPartnerApplicationsIntegrationUnset `ddl:"list,no_parentheses" sql:"UNSET"` } - type OauthForPartnerApplicationsIntegrationSet struct { Enabled *bool `ddl:"parameter" sql:"ENABLED"` OauthIssueRefreshTokens *bool `ddl:"parameter" sql:"OAUTH_ISSUE_REFRESH_TOKENS"` @@ -391,7 +374,6 @@ type OauthForPartnerApplicationsIntegrationSet struct { BlockedRolesList *BlockedRolesList `ddl:"parameter,parentheses" sql:"BLOCKED_ROLES_LIST"` Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` } - type OauthForPartnerApplicationsIntegrationUnset struct { Enabled *bool `ddl:"keyword" sql:"ENABLED"` OauthUseSecondaryRoles *bool `ddl:"keyword" sql:"OAUTH_USE_SECONDARY_ROLES"` @@ -408,7 +390,6 @@ type AlterOauthForCustomClientsSecurityIntegrationOptions struct { Set *OauthForCustomClientsIntegrationSet `ddl:"list,no_parentheses" sql:"SET"` Unset *OauthForCustomClientsIntegrationUnset `ddl:"list,no_parentheses" sql:"UNSET"` } - type OauthForCustomClientsIntegrationSet struct { Enabled *bool `ddl:"parameter" sql:"ENABLED"` OauthRedirectUri *string `ddl:"parameter,single_quotes" sql:"OAUTH_REDIRECT_URI"` @@ -424,7 +405,6 @@ type OauthForCustomClientsIntegrationSet struct { OauthClientRsaPublicKey2 *string `ddl:"parameter,single_quotes" sql:"OAUTH_CLIENT_RSA_PUBLIC_KEY_2"` Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` } - type OauthForCustomClientsIntegrationUnset struct { Enabled *bool `ddl:"keyword" sql:"ENABLED"` NetworkPolicy *bool `ddl:"keyword" sql:"NETWORK_POLICY"` @@ -445,7 +425,6 @@ type AlterSaml2SecurityIntegrationOptions struct { Unset *Saml2IntegrationUnset `ddl:"list,no_parentheses" sql:"UNSET"` RefreshSaml2SnowflakePrivateKey *bool `ddl:"keyword" sql:"REFRESH SAML2_SNOWFLAKE_PRIVATE_KEY"` } - type Saml2IntegrationSet struct { Enabled *bool `ddl:"parameter" sql:"ENABLED"` Saml2Issuer *string `ddl:"parameter,single_quotes" sql:"SAML2_ISSUER"` @@ -465,7 +444,6 @@ type Saml2IntegrationSet struct { Saml2SnowflakeAcsUrl *string `ddl:"parameter,single_quotes" sql:"SAML2_SNOWFLAKE_ACS_URL"` Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` } - type Saml2IntegrationUnset struct { Saml2ForceAuthn *bool `ddl:"keyword" sql:"SAML2_FORCE_AUTHN"` Saml2RequestedNameidFormat *bool `ddl:"keyword" sql:"SAML2_REQUESTED_NAMEID_FORMAT"` @@ -484,14 +462,12 @@ type AlterScimSecurityIntegrationOptions struct { Set *ScimIntegrationSet `ddl:"list,no_parentheses" sql:"SET"` Unset *ScimIntegrationUnset `ddl:"list,no_parentheses" sql:"UNSET"` } - type ScimIntegrationSet struct { Enabled *bool `ddl:"parameter" sql:"ENABLED"` NetworkPolicy *AccountObjectIdentifier `ddl:"identifier,equals" sql:"NETWORK_POLICY"` SyncPassword *bool `ddl:"parameter" sql:"SYNC_PASSWORD"` Comment *StringAllowEmpty `ddl:"parameter" sql:"COMMENT"` } - type ScimIntegrationUnset struct { Enabled *bool `ddl:"keyword" sql:"ENABLED"` NetworkPolicy *bool `ddl:"keyword" sql:"NETWORK_POLICY"` @@ -512,14 +488,12 @@ type DescribeSecurityIntegrationOptions struct { securityIntegration bool `ddl:"static" sql:"SECURITY INTEGRATION"` name AccountObjectIdentifier `ddl:"identifier"` } - type securityIntegrationDescRow struct { Property string `db:"property"` PropertyType string `db:"property_type"` PropertyValue string `db:"property_value"` PropertyDefault string `db:"property_default"` } - type SecurityIntegrationProperty struct { Name string Type string @@ -541,7 +515,6 @@ type ShowSecurityIntegrationOptions struct { securityIntegrations bool `ddl:"static" sql:"SECURITY INTEGRATIONS"` Like *Like `ddl:"keyword" sql:"LIKE"` } - type securityIntegrationShowRow struct { Name string `db:"name"` Type string `db:"type"` @@ -550,7 +523,6 @@ type securityIntegrationShowRow struct { Comment sql.NullString `db:"comment"` CreatedOn time.Time `db:"created_on"` } - type SecurityIntegration struct { Name string IntegrationType string diff --git a/pkg/sdk/security_integrations_gen_test.go b/pkg/sdk/security_integrations_gen_test.go index 02fdb1f773..f867465a44 100644 --- a/pkg/sdk/security_integrations_gen_test.go +++ b/pkg/sdk/security_integrations_gen_test.go @@ -95,10 +95,11 @@ func TestSecurityIntegrations_CreateApiAuthenticationWithAuthorizationCodeGrantF opts.OauthGrantAuthorizationCode = Pointer(true) opts.OauthAccessTokenValidity = Pointer(42) opts.OauthRefreshTokenValidity = Pointer(42) + opts.OauthAllowedScopes = []AllowedScope{{Scope: "bar"}} opts.Comment = Pointer("foo") assertOptsValidAndSQLEquals(t, opts, "CREATE SECURITY INTEGRATION IF NOT EXISTS %s TYPE = API_AUTHENTICATION AUTH_TYPE = OAUTH2 ENABLED = true OAUTH_AUTHORIZATION_ENDPOINT = 'foo'"+ " OAUTH_TOKEN_ENDPOINT = 'foo' OAUTH_CLIENT_AUTH_METHOD = CLIENT_SECRET_POST OAUTH_CLIENT_ID = 'foo' OAUTH_CLIENT_SECRET = 'bar' OAUTH_GRANT = AUTHORIZATION_CODE"+ - " OAUTH_ACCESS_TOKEN_VALIDITY = 42 OAUTH_REFRESH_TOKEN_VALIDITY = 42 COMMENT = 'foo'", id.FullyQualifiedName()) + " OAUTH_ACCESS_TOKEN_VALIDITY = 42 OAUTH_REFRESH_TOKEN_VALIDITY = 42 OAUTH_ALLOWED_SCOPES = ('bar') COMMENT = 'foo'", id.FullyQualifiedName()) }) } @@ -261,7 +262,6 @@ func TestSecurityIntegrations_CreateOauthForCustomClients(t *testing.T) { roleID, role2ID, npID := randomAccountObjectIdentifier(), randomAccountObjectIdentifier(), randomAccountObjectIdentifier() opts.IfNotExists = Bool(true) opts.OauthClientType = OauthSecurityIntegrationClientTypePublic - opts.OauthRedirectUri = "uri" opts.Enabled = Pointer(true) opts.OauthAllowNonTlsRedirectUri = Pointer(true) opts.OauthEnforcePkce = Pointer(true) @@ -570,7 +570,7 @@ func TestSecurityIntegrations_AlterApiAuthenticationWithAuthorizationCodeFlow(t opts := defaultOpts() opts.Set = &ApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationSet{} assertOptsInvalidJoinedErrors(t, opts, errAtLeastOneOf("AlterApiAuthenticationWithAuthorizationCodeGrantFlowSecurityIntegrationOptions.Set", "Enabled", "OauthAuthorizationEndpoint", "OauthTokenEndpoint", - "OauthClientAuthMethod", "OauthClientId", "OauthClientSecret", "OauthGrantAuthorizationCode", "OauthAccessTokenValidity", "OauthRefreshTokenValidity", "Comment")) + "OauthClientAuthMethod", "OauthClientId", "OauthClientSecret", "OauthGrantAuthorizationCode", "OauthAccessTokenValidity", "OauthRefreshTokenValidity", "OauthAllowedScopes", "Comment")) }) t.Run("validation: at least one of the fields [opts.Unset.*] should be set", func(t *testing.T) { @@ -598,11 +598,12 @@ func TestSecurityIntegrations_AlterApiAuthenticationWithAuthorizationCodeFlow(t OauthGrantAuthorizationCode: Pointer(true), OauthAccessTokenValidity: Pointer(42), OauthRefreshTokenValidity: Pointer(42), + OauthAllowedScopes: []AllowedScope{{Scope: "bar"}}, Comment: Pointer("foo"), } assertOptsValidAndSQLEquals(t, opts, "ALTER SECURITY INTEGRATION %s SET ENABLED = true, OAUTH_TOKEN_ENDPOINT = 'foo', OAUTH_CLIENT_AUTH_METHOD = CLIENT_SECRET_POST,"+ " OAUTH_CLIENT_ID = 'foo', OAUTH_CLIENT_SECRET = 'foo', OAUTH_GRANT = AUTHORIZATION_CODE, OAUTH_ACCESS_TOKEN_VALIDITY = 42,"+ - " OAUTH_REFRESH_TOKEN_VALIDITY = 42, COMMENT = 'foo'", id.FullyQualifiedName()) + " OAUTH_REFRESH_TOKEN_VALIDITY = 42, OAUTH_ALLOWED_SCOPES = ('bar'), COMMENT = 'foo'", id.FullyQualifiedName()) }) t.Run("all options - unset", func(t *testing.T) { diff --git a/pkg/sdk/security_integrations_impl_gen.go b/pkg/sdk/security_integrations_impl_gen.go index c2244cb30d..f77857865f 100644 --- a/pkg/sdk/security_integrations_impl_gen.go +++ b/pkg/sdk/security_integrations_impl_gen.go @@ -161,6 +161,7 @@ func (r *CreateApiAuthenticationWithAuthorizationCodeGrantFlowSecurityIntegratio OauthGrantAuthorizationCode: r.OauthGrantAuthorizationCode, OauthAccessTokenValidity: r.OauthAccessTokenValidity, OauthRefreshTokenValidity: r.OauthRefreshTokenValidity, + OauthAllowedScopes: r.OauthAllowedScopes, Comment: r.Comment, } return opts @@ -206,21 +207,25 @@ func (r *CreateExternalOauthSecurityIntegrationRequest) toOpts() *CreateExternal ExternalOauthScopeMappingAttribute: r.ExternalOauthScopeMappingAttribute, Comment: r.Comment, } + if r.ExternalOauthBlockedRolesList != nil { opts.ExternalOauthBlockedRolesList = &BlockedRolesList{ BlockedRolesList: r.ExternalOauthBlockedRolesList.BlockedRolesList, } } + if r.ExternalOauthAllowedRolesList != nil { opts.ExternalOauthAllowedRolesList = &AllowedRolesList{ AllowedRolesList: r.ExternalOauthAllowedRolesList.AllowedRolesList, } } + if r.ExternalOauthAudienceList != nil { opts.ExternalOauthAudienceList = &AudienceList{ AudienceList: r.ExternalOauthAudienceList.AudienceList, } } + return opts } @@ -238,11 +243,13 @@ func (r *CreateOauthForPartnerApplicationsSecurityIntegrationRequest) toOpts() * Comment: r.Comment, } + if r.BlockedRolesList != nil { opts.BlockedRolesList = &BlockedRolesList{ BlockedRolesList: r.BlockedRolesList.BlockedRolesList, } } + return opts } @@ -265,16 +272,19 @@ func (r *CreateOauthForCustomClientsSecurityIntegrationRequest) toOpts() *Create OauthClientRsaPublicKey2: r.OauthClientRsaPublicKey2, Comment: r.Comment, } + if r.PreAuthorizedRolesList != nil { opts.PreAuthorizedRolesList = &PreAuthorizedRolesList{ PreAuthorizedRolesList: r.PreAuthorizedRolesList.PreAuthorizedRolesList, } } + if r.BlockedRolesList != nil { opts.BlockedRolesList = &BlockedRolesList{ BlockedRolesList: r.BlockedRolesList.BlockedRolesList, } } + return opts } @@ -326,6 +336,7 @@ func (r *AlterApiAuthenticationWithClientCredentialsFlowSecurityIntegrationReque SetTags: r.SetTags, UnsetTags: r.UnsetTags, } + if r.Set != nil { opts.Set = &ApiAuthenticationWithClientCredentialsFlowIntegrationSet{ Enabled: r.Set.Enabled, @@ -340,12 +351,14 @@ func (r *AlterApiAuthenticationWithClientCredentialsFlowSecurityIntegrationReque Comment: r.Set.Comment, } } + if r.Unset != nil { opts.Unset = &ApiAuthenticationWithClientCredentialsFlowIntegrationUnset{ Enabled: r.Unset.Enabled, Comment: r.Unset.Comment, } } + return opts } @@ -356,6 +369,7 @@ func (r *AlterApiAuthenticationWithAuthorizationCodeGrantFlowSecurityIntegration SetTags: r.SetTags, UnsetTags: r.UnsetTags, } + if r.Set != nil { opts.Set = &ApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationSet{ Enabled: r.Set.Enabled, @@ -367,15 +381,18 @@ func (r *AlterApiAuthenticationWithAuthorizationCodeGrantFlowSecurityIntegration OauthGrantAuthorizationCode: r.Set.OauthGrantAuthorizationCode, OauthAccessTokenValidity: r.Set.OauthAccessTokenValidity, OauthRefreshTokenValidity: r.Set.OauthRefreshTokenValidity, + OauthAllowedScopes: r.Set.OauthAllowedScopes, Comment: r.Set.Comment, } } + if r.Unset != nil { opts.Unset = &ApiAuthenticationWithAuthorizationCodeGrantFlowIntegrationUnset{ Enabled: r.Unset.Enabled, Comment: r.Unset.Comment, } } + return opts } @@ -386,6 +403,7 @@ func (r *AlterApiAuthenticationWithJwtBearerFlowSecurityIntegrationRequest) toOp SetTags: r.SetTags, UnsetTags: r.UnsetTags, } + if r.Set != nil { opts.Set = &ApiAuthenticationWithJwtBearerFlowIntegrationSet{ Enabled: r.Set.Enabled, @@ -400,12 +418,14 @@ func (r *AlterApiAuthenticationWithJwtBearerFlowSecurityIntegrationRequest) toOp Comment: r.Set.Comment, } } + if r.Unset != nil { opts.Unset = &ApiAuthenticationWithJwtBearerFlowIntegrationUnset{ Enabled: r.Unset.Enabled, Comment: r.Unset.Comment, } } + return opts } @@ -416,6 +436,7 @@ func (r *AlterExternalOauthSecurityIntegrationRequest) toOpts() *AlterExternalOa SetTags: r.SetTags, UnsetTags: r.UnsetTags, } + if r.Set != nil { opts.Set = &ExternalOauthIntegrationSet{ Enabled: r.Set.Enabled, @@ -432,28 +453,33 @@ func (r *AlterExternalOauthSecurityIntegrationRequest) toOpts() *AlterExternalOa ExternalOauthScopeDelimiter: r.Set.ExternalOauthScopeDelimiter, Comment: r.Set.Comment, } + if r.Set.ExternalOauthBlockedRolesList != nil { opts.Set.ExternalOauthBlockedRolesList = &BlockedRolesList{ BlockedRolesList: r.Set.ExternalOauthBlockedRolesList.BlockedRolesList, } } + if r.Set.ExternalOauthAllowedRolesList != nil { opts.Set.ExternalOauthAllowedRolesList = &AllowedRolesList{ AllowedRolesList: r.Set.ExternalOauthAllowedRolesList.AllowedRolesList, } } + if r.Set.ExternalOauthAudienceList != nil { opts.Set.ExternalOauthAudienceList = &AudienceList{ AudienceList: r.Set.ExternalOauthAudienceList.AudienceList, } } } + if r.Unset != nil { opts.Unset = &ExternalOauthIntegrationUnset{ Enabled: r.Unset.Enabled, ExternalOauthAudienceList: r.Unset.ExternalOauthAudienceList, } } + return opts } @@ -464,6 +490,7 @@ func (r *AlterOauthForPartnerApplicationsSecurityIntegrationRequest) toOpts() *A SetTags: r.SetTags, UnsetTags: r.UnsetTags, } + if r.Set != nil { opts.Set = &OauthForPartnerApplicationsIntegrationSet{ Enabled: r.Set.Enabled, @@ -474,18 +501,21 @@ func (r *AlterOauthForPartnerApplicationsSecurityIntegrationRequest) toOpts() *A Comment: r.Set.Comment, } + if r.Set.BlockedRolesList != nil { opts.Set.BlockedRolesList = &BlockedRolesList{ BlockedRolesList: r.Set.BlockedRolesList.BlockedRolesList, } } } + if r.Unset != nil { opts.Unset = &OauthForPartnerApplicationsIntegrationUnset{ Enabled: r.Unset.Enabled, OauthUseSecondaryRoles: r.Unset.OauthUseSecondaryRoles, } } + return opts } @@ -496,6 +526,7 @@ func (r *AlterOauthForCustomClientsSecurityIntegrationRequest) toOpts() *AlterOa SetTags: r.SetTags, UnsetTags: r.UnsetTags, } + if r.Set != nil { opts.Set = &OauthForCustomClientsIntegrationSet{ Enabled: r.Set.Enabled, @@ -511,17 +542,20 @@ func (r *AlterOauthForCustomClientsSecurityIntegrationRequest) toOpts() *AlterOa OauthClientRsaPublicKey2: r.Set.OauthClientRsaPublicKey2, Comment: r.Set.Comment, } + if r.Set.PreAuthorizedRolesList != nil { opts.Set.PreAuthorizedRolesList = &PreAuthorizedRolesList{ PreAuthorizedRolesList: r.Set.PreAuthorizedRolesList.PreAuthorizedRolesList, } } + if r.Set.BlockedRolesList != nil { opts.Set.BlockedRolesList = &BlockedRolesList{ BlockedRolesList: r.Set.BlockedRolesList.BlockedRolesList, } } } + if r.Unset != nil { opts.Unset = &OauthForCustomClientsIntegrationUnset{ Enabled: r.Unset.Enabled, @@ -531,6 +565,7 @@ func (r *AlterOauthForCustomClientsSecurityIntegrationRequest) toOpts() *AlterOa OauthUseSecondaryRoles: r.Unset.OauthUseSecondaryRoles, } } + return opts } @@ -543,6 +578,7 @@ func (r *AlterSaml2SecurityIntegrationRequest) toOpts() *AlterSaml2SecurityInteg RefreshSaml2SnowflakePrivateKey: r.RefreshSaml2SnowflakePrivateKey, } + if r.Set != nil { opts.Set = &Saml2IntegrationSet{ Enabled: r.Set.Enabled, @@ -564,6 +600,7 @@ func (r *AlterSaml2SecurityIntegrationRequest) toOpts() *AlterSaml2SecurityInteg Comment: r.Set.Comment, } } + if r.Unset != nil { opts.Unset = &Saml2IntegrationUnset{ Saml2ForceAuthn: r.Unset.Saml2ForceAuthn, @@ -572,6 +609,7 @@ func (r *AlterSaml2SecurityIntegrationRequest) toOpts() *AlterSaml2SecurityInteg Comment: r.Unset.Comment, } } + return opts } @@ -582,6 +620,7 @@ func (r *AlterScimSecurityIntegrationRequest) toOpts() *AlterScimSecurityIntegra SetTags: r.SetTags, UnsetTags: r.UnsetTags, } + if r.Set != nil { opts.Set = &ScimIntegrationSet{ Enabled: r.Set.Enabled, @@ -590,6 +629,7 @@ func (r *AlterScimSecurityIntegrationRequest) toOpts() *AlterScimSecurityIntegra Comment: r.Set.Comment, } } + if r.Unset != nil { opts.Unset = &ScimIntegrationUnset{ Enabled: r.Unset.Enabled, @@ -597,6 +637,7 @@ func (r *AlterScimSecurityIntegrationRequest) toOpts() *AlterScimSecurityIntegra SyncPassword: r.Unset.SyncPassword, } } + return opts } diff --git a/pkg/sdk/security_integrations_validations_gen.go b/pkg/sdk/security_integrations_validations_gen.go index 2e6129b157..7c9b21606d 100644 --- a/pkg/sdk/security_integrations_validations_gen.go +++ b/pkg/sdk/security_integrations_validations_gen.go @@ -182,8 +182,8 @@ func (opts *AlterApiAuthenticationWithAuthorizationCodeGrantFlowSecurityIntegrat errs = append(errs, errExactlyOneOf("AlterApiAuthenticationWithAuthorizationCodeGrantFlowSecurityIntegrationOptions", "Set", "Unset", "SetTags", "UnsetTags")) } if valueSet(opts.Set) { - if !anyValueSet(opts.Set.Enabled, opts.Set.OauthAuthorizationEndpoint, opts.Set.OauthTokenEndpoint, opts.Set.OauthClientAuthMethod, opts.Set.OauthClientId, opts.Set.OauthClientSecret, opts.Set.OauthGrantAuthorizationCode, opts.Set.OauthAccessTokenValidity, opts.Set.OauthRefreshTokenValidity, opts.Set.Comment) { - errs = append(errs, errAtLeastOneOf("AlterApiAuthenticationWithAuthorizationCodeGrantFlowSecurityIntegrationOptions.Set", "Enabled", "OauthAuthorizationEndpoint", "OauthTokenEndpoint", "OauthClientAuthMethod", "OauthClientId", "OauthClientSecret", "OauthGrantAuthorizationCode", "OauthAccessTokenValidity", "OauthRefreshTokenValidity", "Comment")) + if !anyValueSet(opts.Set.Enabled, opts.Set.OauthAuthorizationEndpoint, opts.Set.OauthTokenEndpoint, opts.Set.OauthClientAuthMethod, opts.Set.OauthClientId, opts.Set.OauthClientSecret, opts.Set.OauthGrantAuthorizationCode, opts.Set.OauthAccessTokenValidity, opts.Set.OauthRefreshTokenValidity, opts.Set.OauthAllowedScopes, opts.Set.Comment) { + errs = append(errs, errAtLeastOneOf("AlterApiAuthenticationWithAuthorizationCodeGrantFlowSecurityIntegrationOptions.Set", "Enabled", "OauthAuthorizationEndpoint", "OauthTokenEndpoint", "OauthClientAuthMethod", "OauthClientId", "OauthClientSecret", "OauthGrantAuthorizationCode", "OauthAccessTokenValidity", "OauthRefreshTokenValidity", "OauthAllowedScopes", "Comment")) } } if valueSet(opts.Unset) { diff --git a/templates/resources/api_authentication_integration_with_authorization_code_grant.md.tmpl b/templates/resources/api_authentication_integration_with_authorization_code_grant.md.tmpl new file mode 100644 index 0000000000..e7f66bbf91 --- /dev/null +++ b/templates/resources/api_authentication_integration_with_authorization_code_grant.md.tmpl @@ -0,0 +1,32 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "" +description: |- +{{ if gt (len (split .Description "")) 1 -}} +{{ index (split .Description "") 1 | plainmarkdown | trimspace | prefixlines " " }} +{{- else -}} +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +{{- end }} +--- + +!> **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. + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +{{ if .HasExample -}} +## Example Usage + +{{ tffile (printf "examples/resources/%s/resource.tf" .Name)}} +{{- end }} + +{{ .SchemaMarkdown | trimspace }} +{{- if .HasImport }} + +## Import + +Import is supported using the following syntax: + +{{ codefile "shell" (printf "examples/resources/%s/import.sh" .Name)}} +{{- end }} diff --git a/templates/resources/api_authentication_integration_with_client_credentials.md.tmpl b/templates/resources/api_authentication_integration_with_client_credentials.md.tmpl new file mode 100644 index 0000000000..e7f66bbf91 --- /dev/null +++ b/templates/resources/api_authentication_integration_with_client_credentials.md.tmpl @@ -0,0 +1,32 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "" +description: |- +{{ if gt (len (split .Description "")) 1 -}} +{{ index (split .Description "") 1 | plainmarkdown | trimspace | prefixlines " " }} +{{- else -}} +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +{{- end }} +--- + +!> **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. + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +{{ if .HasExample -}} +## Example Usage + +{{ tffile (printf "examples/resources/%s/resource.tf" .Name)}} +{{- end }} + +{{ .SchemaMarkdown | trimspace }} +{{- if .HasImport }} + +## Import + +Import is supported using the following syntax: + +{{ codefile "shell" (printf "examples/resources/%s/import.sh" .Name)}} +{{- end }} diff --git a/templates/resources/api_authentication_integration_with_jwt_bearer.md.tmpl b/templates/resources/api_authentication_integration_with_jwt_bearer.md.tmpl new file mode 100644 index 0000000000..e7f66bbf91 --- /dev/null +++ b/templates/resources/api_authentication_integration_with_jwt_bearer.md.tmpl @@ -0,0 +1,32 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "" +description: |- +{{ if gt (len (split .Description "")) 1 -}} +{{ index (split .Description "") 1 | plainmarkdown | trimspace | prefixlines " " }} +{{- else -}} +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +{{- end }} +--- + +!> **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. + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +{{ if .HasExample -}} +## Example Usage + +{{ tffile (printf "examples/resources/%s/resource.tf" .Name)}} +{{- end }} + +{{ .SchemaMarkdown | trimspace }} +{{- if .HasImport }} + +## Import + +Import is supported using the following syntax: + +{{ codefile "shell" (printf "examples/resources/%s/import.sh" .Name)}} +{{- end }} diff --git a/v1-preparations/CHANGES_BEFORE_V1.md b/v1-preparations/CHANGES_BEFORE_V1.md index 3ec832d168..bc661c4f10 100644 --- a/v1-preparations/CHANGES_BEFORE_V1.md +++ b/v1-preparations/CHANGES_BEFORE_V1.md @@ -4,14 +4,14 @@ This document is a supplement to all the resource changes described in the [migr ## Default values For any resource that went through the rework as part of the [resource preparation for V1](https://github.com/Snowflake-Labs/terraform-provider-snowflake/blob/main/ROADMAP.md#preparing-essential-ga-objects-for-the-provider-v1), -the behaviour for default values may change from the previous one. +the behaviour for default values may change from the previous one. -In the past, the provider copied defaults from Snowflake, creating a tight coupling between them. +In the past, the provider copied defaults from Snowflake, creating a tight coupling between them. However, this approach posed a challenge as the defaults on the Snowflake side could change and vary between accounts based on their configurations. Now, whenever the value is not specified in the configuration, we let the Snowflake fill out the default value for a given field -(if there is one). Using such defaults may lead to non-idempotent cases where the same configuration may -create a resource with slightly different configuration in Snowflake (depending on the Snowflake Edition and Version, +(if there is one). Using such defaults may lead to non-idempotent cases where the same configuration may +create a resource with slightly different configuration in Snowflake (depending on the Snowflake Edition and Version, current account configuration, and most-likely other factors). That is why we recommend setting optional fields where you want to ensure that the specified value has been set on the Snowflake side. diff --git a/v1-preparations/ESSENTIAL_GA_OBJECTS.MD b/v1-preparations/ESSENTIAL_GA_OBJECTS.MD index 8bd98b2ba9..99173a1c05 100644 --- a/v1-preparations/ESSENTIAL_GA_OBJECTS.MD +++ b/v1-preparations/ESSENTIAL_GA_OBJECTS.MD @@ -9,7 +9,7 @@ Status is one of: - 👨‍💻 - in progress
Known issues lists open issues touching the given object. Note that some of these issues may be already fixed in the -newer provider versions. We will address these while working on the given object. +newer provider versions. We will address these while working on the given object. | Object Type | Status | Known issues | |--------------------------|:------:|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| @@ -20,7 +20,7 @@ newer provider versions. We will address these while working on the given object | RESOURCE MONITOR | ❌ | [#1990](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1990), [#1832](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1832), [#1821](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1821), [#1754](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1754), [#1716](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1716), [#1714](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1714), [#1624](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1624), [#1500](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1500), [#1175](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1175) | | ROLE | 👨‍💻 | - | | SECURITY INTEGRATION | 👨‍💻 | [#2855](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2855), [#2719](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2719), [#2568](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2568), [#2177](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2177), [#1851](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1851), [#1773](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1773), [#1741](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1741), [#1637](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1637), [#1503](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1503), [#1498](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1498), [#1421](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1421), [#1224](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1224) | -| USER | 👨‍💻 | [#2817](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2817), [#2662](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2662), [#1572](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1572), [#1535](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1535), [#1155](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1155) | +| USER | 👨‍💻 | [#2902](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2902), [#2817](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2817), [#2662](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2662), [#1572](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1572), [#1535](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1535), [#1155](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1155) | | WAREHOUSE | 🚀 | issues in the older versions: [resources](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues?q=label%3Aresource%3Awarehouse+) and [datasources](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues?q=label%3Adata_source%3Awarehouses+) | | FUNCTION | ❌ | [2859](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2859), [#2735](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2735), [#2426](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2426), [#1479](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1479), [#1393](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1393), [#1208](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1208), [#1079](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1079) | | MASKING POLICY | ❌ | [#2236](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2236), [#2035](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2035), [#1799](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1799), [#1764](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1764), [#1656](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1656), [#1444](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1444), [#1422](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1422), [#1097](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1097) | @@ -35,3 +35,4 @@ newer provider versions. We will address these while working on the given object | TASK | ❌ | [#1419](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1419), [#1250](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1250), [#1194](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1194), [#1088](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1088) | | VIEW | ❌ | [#2430](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2430), [#2085](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2085), [#2055](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2055), [#2031](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2031), [#1526](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1526), [#1253](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1253), [#1049](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1049) | | snowflake_unsafe_execute | ❌ | - | +