Skip to content

Commit

Permalink
feat: Unsafe execute v1 readiness (#3266)
Browse files Browse the repository at this point in the history
### Changes
- Deprecated unsafe execute resource
- Add new snowflake_execute resource
- Adjust tests to use the new resource
- Add more tests for other corner cases
- Make sure the new resource is importable
  • Loading branch information
sfc-gh-jcieslak authored Dec 12, 2024
1 parent 13401d5 commit c4f1e8f
Show file tree
Hide file tree
Showing 27 changed files with 632 additions and 231 deletions.
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/01-bug.yml
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ body:
- resource:database_role
- resource:dynamic_table
- resource:email_notification_integration
- resource:execute
- resource:external_function
- resource:external_oauth_integration
- resource:external_table
Expand Down
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/02-general-usage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ body:
- resource:database_role
- resource:dynamic_table
- resource:email_notification_integration
- resource:execute
- resource:external_function
- resource:external_oauth_integration
- resource:external_table
Expand Down
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/03-documentation.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ body:
- resource:database_role
- resource:dynamic_table
- resource:email_notification_integration
- resource:execute
- resource:external_function
- resource:external_oauth_integration
- resource:external_table
Expand Down
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/04-feature-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ body:
- resource:database_role
- resource:dynamic_table
- resource:email_notification_integration
- resource:execute
- resource:external_function
- resource:external_oauth_integration
- resource:external_table
Expand Down
10 changes: 10 additions & 0 deletions MIGRATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,16 @@ During resource deleting, provider now uses `UNSET` instead of `SET` with the de
#### *(behavior change)* changes in `key` field
The value of `key` field is now case-insensitive and is validated. The list of supported values is available in the resource documentation.

### unsafe_execute resource deprecation / new execute resource

The `snowflake_unsafe_execute` gets deprecated in favor of the new resource `snowflake_execute`.
The `snowflake_execute` was build on top of `snowflake_unsafe_execute` with a few improvements.
The unsafe version will be removed with the v1 release, so please migrate to the `snowflake_execute` resource.

For no downtime migration, follow our [guide](https://github.com/Snowflake-Labs/terraform-provider-snowflake/blob/main/docs/technical-documentation/resource_migration.md).
When importing, remember that the given resource id has to be unique (using UUIDs is recommended).
Also, because of the nature of the resource, first apply after importing is necessary to "copy" values from the configuration to the state.

### snowflake_oauth_integration_for_partner_applications and snowflake_oauth_integration_for_custom_clients resource changes
#### *(behavior change)* `blocked_roles_list` field is no longer required

Expand Down
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -367,6 +367,7 @@ provider "snowflake" {
- [snowflake_saml_integration](./docs/resources/saml_integration) - use [snowflake_saml2_integration](./docs/resources/saml2_integration) instead
- [snowflake_stream](./docs/resources/stream)
- [snowflake_tag_masking_policy_association](./docs/resources/tag_masking_policy_association)
- [snowflake_unsafe_execute](./docs/resources/unsafe_execute) - use [snowflake_execute](./docs/resources/execute) instead

## Currently deprecated datasources

Expand Down
128 changes: 128 additions & 0 deletions docs/resources/execute.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
---
# generated by https://github.com/hashicorp/terraform-plugin-docs
page_title: "snowflake_execute Resource - terraform-provider-snowflake"
subcategory: ""
description: |-
Resource allowing execution of ANY SQL statement.
---

# snowflake_execute (Resource)

!> **Warning** This is a dangerous resource that allows executing **ANY** SQL statement. It may destroy resources if used incorrectly. It may behave incorrectly combined with other resources. Use at your own risk.

~> **Note** It can be theoretically used to manage resource that are not supported by the provider. This is risky and may brake other resources if used incorrectly.

~> **Note** Use `query` parameter with caution. It will fetch **ALL** the results returned by the query provided. Try to limit the number of results by writing query with filters. Query failure does not stop resource creation; it simply results in `query_results` being empty.

Resource allowing execution of ANY SQL statement.

## Example Usage

```terraform
##################################
### simple use cases
##################################
# create and destroy resource
resource "snowflake_execute" "test" {
execute = "CREATE DATABASE ABC"
revert = "DROP DATABASE ABC"
}
# create and destroy resource using qualified name
resource "snowflake_execute" "test" {
execute = "CREATE DATABASE \"abc\""
revert = "DROP DATABASE \"abc\""
}
# with query
resource "snowflake_execute" "test" {
execute = "CREATE DATABASE ABC"
revert = "DROP DATABASE ABC"
query = "SHOW DATABASES LIKE '%ABC%'"
}
##################################
### grants example
##################################
# grant and revoke privilege USAGE to ROLE on database
resource "snowflake_execute" "test" {
execute = "GRANT USAGE ON DATABASE ABC TO ROLE XYZ"
revert = "REVOKE USAGE ON DATABASE ABC FROM ROLE XYZ"
}
# grant and revoke with for_each
variable "database_grants" {
type = list(object({
database_name = string
role_id = string
privileges = list(string)
}))
}
resource "snowflake_execute" "test" {
for_each = { for index, db_grant in var.database_grants : index => db_grant }
execute = "GRANT ${join(",", each.value.privileges)} ON DATABASE ${each.value.database_name} TO ROLE ${each.value.role_id}"
revert = "REVOKE ${join(",", each.value.privileges)} ON DATABASE ${each.value.database_name} FROM ROLE ${each.value.role_id}"
}
##################################
### fixing bad configuration
##################################
# bad revert
# 1 - resource created with a bad revert; it is constructed, revert is not validated before destroy happens
resource "snowflake_execute" "test" {
execute = "CREATE DATABASE ABC"
revert = "SELECT 1"
}
# 2 - fix the revert first; resource won't be recreated
resource "snowflake_execute" "test" {
execute = "CREATE DATABASE ABC"
revert = "DROP DATABASE ABC"
}
# bad query
# 1 - resource will be created; query_results will be empty
resource "snowflake_execute" "test" {
execute = "CREATE DATABASE ABC"
revert = "DROP DATABASE ABC"
query = "bad query"
}
# 2 - fix the query; query_results will be calculated; resource won't be recreated
resource "snowflake_execute" "test" {
execute = "CREATE DATABASE ABC"
revert = "DROP DATABASE ABC"
query = "SHOW DATABASES LIKE '%ABC%'"
}
```
-> **Note** Instead of using fully_qualified_name, you can reference objects managed outside Terraform by constructing a correct ID, consult [identifiers guide](https://registry.terraform.io/providers/Snowflake-Labs/snowflake/latest/docs/guides/identifiers#new-computed-fully-qualified-name-field-in-resources).
<!-- TODO(SNOW-1634854): include an example showing both methods-->

<!-- schema generated by tfplugindocs -->
## Schema

### Required

- `execute` (String) SQL statement to execute. Forces recreation of resource on change.
- `revert` (String) SQL statement to revert the execute statement. Invoked when resource is being destroyed.

### Optional

- `query` (String) Optional SQL statement to do a read. Invoked on every resource refresh and every time it is changed.

### Read-Only

- `id` (String) The ID of this resource.
- `query_results` (List of Map of String) List of key-value maps (text to text) retrieved after executing read query. Will be empty if the query results in an error.

## Import

Import is supported using the following syntax:

```shell
terraform import snowflake_execute.example '<random_uuid>'
```
6 changes: 3 additions & 3 deletions docs/resources/unsafe_execute.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ description: |-

!> **Warning** This is a dangerous resource that allows executing **ANY** SQL statement. It may destroy resources if used incorrectly. It may behave incorrectly combined with other resources. Use at your own risk.

~> **Note** This resource will be included in the V1 (check [here](https://github.com/Snowflake-Labs/terraform-provider-snowflake/blob/main/v1-preparations/ESSENTIAL_GA_OBJECTS.MD)) but may be slightly modified before. Design decisions and changes will be listed in the [migration guide](https://github.com/Snowflake-Labs/terraform-provider-snowflake/blob/main/MIGRATION_GUIDE.md#migration-guide).

~> **Note** It can be theoretically used to manage resource that are not supported by the provider. This is risky and may brake other resources if used incorrectly.

~> **Note** Use `query` parameter with caution. It will fetch **ALL** the results returned by the query provided. Try to limit the number of results by writing query with filters. Query failure does not stop resource creation; it simply results in `query_results` being empty.

~> **Deprecation** This resource is deprecated and will be removed in a future major version release. Please use [snowflake_execute](./execute) instead. <deprecation>

Experimental resource allowing execution of ANY SQL statement. It may destroy resources if used incorrectly. It may behave incorrectly combined with other resources. Use at your own risk.

## Example Usage
Expand Down Expand Up @@ -139,7 +139,7 @@ resource "snowflake_unsafe_execute" "test" {

### Optional

- `query` (String) Optional SQL statement to do a read. Invoked after creation and every time it is changed.
- `query` (String) Optional SQL statement to do a read. Invoked on every resource refresh and every time it is changed.

### Read-Only

Expand Down
1 change: 1 addition & 0 deletions examples/additional/deprecated_resources.MD
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
- [snowflake_saml_integration](./docs/resources/saml_integration) - use [snowflake_saml2_integration](./docs/resources/saml2_integration) instead
- [snowflake_stream](./docs/resources/stream)
- [snowflake_tag_masking_policy_association](./docs/resources/tag_masking_policy_association)
- [snowflake_unsafe_execute](./docs/resources/unsafe_execute) - use [snowflake_execute](./docs/resources/execute) instead
1 change: 1 addition & 0 deletions examples/resources/snowflake_execute/import.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
terraform import snowflake_execute.example '<random_uuid>'
79 changes: 79 additions & 0 deletions examples/resources/snowflake_execute/resource.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
##################################
### simple use cases
##################################

# create and destroy resource
resource "snowflake_execute" "test" {
execute = "CREATE DATABASE ABC"
revert = "DROP DATABASE ABC"
}

# create and destroy resource using qualified name
resource "snowflake_execute" "test" {
execute = "CREATE DATABASE \"abc\""
revert = "DROP DATABASE \"abc\""
}

# with query
resource "snowflake_execute" "test" {
execute = "CREATE DATABASE ABC"
revert = "DROP DATABASE ABC"
query = "SHOW DATABASES LIKE '%ABC%'"
}

##################################
### grants example
##################################

# grant and revoke privilege USAGE to ROLE on database
resource "snowflake_execute" "test" {
execute = "GRANT USAGE ON DATABASE ABC TO ROLE XYZ"
revert = "REVOKE USAGE ON DATABASE ABC FROM ROLE XYZ"
}

# grant and revoke with for_each
variable "database_grants" {
type = list(object({
database_name = string
role_id = string
privileges = list(string)
}))
}

resource "snowflake_execute" "test" {
for_each = { for index, db_grant in var.database_grants : index => db_grant }
execute = "GRANT ${join(",", each.value.privileges)} ON DATABASE ${each.value.database_name} TO ROLE ${each.value.role_id}"
revert = "REVOKE ${join(",", each.value.privileges)} ON DATABASE ${each.value.database_name} FROM ROLE ${each.value.role_id}"
}

##################################
### fixing bad configuration
##################################

# bad revert
# 1 - resource created with a bad revert; it is constructed, revert is not validated before destroy happens
resource "snowflake_execute" "test" {
execute = "CREATE DATABASE ABC"
revert = "SELECT 1"
}

# 2 - fix the revert first; resource won't be recreated
resource "snowflake_execute" "test" {
execute = "CREATE DATABASE ABC"
revert = "DROP DATABASE ABC"
}

# bad query
# 1 - resource will be created; query_results will be empty
resource "snowflake_execute" "test" {
execute = "CREATE DATABASE ABC"
revert = "DROP DATABASE ABC"
query = "bad query"
}

# 2 - fix the query; query_results will be calculated; resource won't be recreated
resource "snowflake_execute" "test" {
execute = "CREATE DATABASE ABC"
revert = "DROP DATABASE ABC"
query = "SHOW DATABASES LIKE '%ABC%'"
}
1 change: 1 addition & 0 deletions pkg/provider/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,7 @@ func getResources() map[string]*schema.Resource {
"snowflake_database_role": resources.DatabaseRole(),
"snowflake_dynamic_table": resources.DynamicTable(),
"snowflake_email_notification_integration": resources.EmailNotificationIntegration(),
"snowflake_execute": resources.Execute(),
"snowflake_external_function": resources.ExternalFunction(),
"snowflake_external_oauth_integration": resources.ExternalOauthIntegration(),
"snowflake_external_table": resources.ExternalTable(),
Expand Down
10 changes: 5 additions & 5 deletions pkg/provider/provider_acceptance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -623,10 +623,10 @@ func TestAcc_Provider_sessionParameters(t *testing.T) {
"statement_timeout_in_seconds": tfconfig.IntegerVariable(31337),
},
),
)) + unsafeExecuteShowSessionParameter(),
)) + executeShowSessionParameter(),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr("snowflake_unsafe_execute.t", "query_results.#", "1"),
resource.TestCheckResourceAttr("snowflake_unsafe_execute.t", "query_results.0.value", "31337"),
resource.TestCheckResourceAttr("snowflake_execute.t", "query_results.#", "1"),
resource.TestCheckResourceAttr("snowflake_execute.t", "query_results.0.value", "31337"),
),
},
},
Expand Down Expand Up @@ -803,9 +803,9 @@ func datasourceModel() config.DatasourceModel {
return datasourcemodel.Database("t", acc.TestDatabaseName)
}

func unsafeExecuteShowSessionParameter() string {
func executeShowSessionParameter() string {
return `
resource snowflake_unsafe_execute "t" {
resource snowflake_execute "t" {
execute = "SELECT 1"
query = "SHOW PARAMETERS LIKE 'STATEMENT_TIMEOUT_IN_SECONDS' IN SESSION"
revert = "SELECT 1"
Expand Down
1 change: 1 addition & 0 deletions pkg/provider/resources/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const (
DatabaseRole resource = "snowflake_database_role"
DynamicTable resource = "snowflake_dynamic_table"
EmailNotificationIntegration resource = "snowflake_email_notification_integration"
Execute resource = "snowflake_execute"
ExternalFunction resource = "snowflake_external_function"
ExternalTable resource = "snowflake_external_table"
ExternalOauthSecurityIntegration resource = "snowflake_external_oauth_security_integration"
Expand Down
Loading

0 comments on commit c4f1e8f

Please sign in to comment.