Skip to content

Commit

Permalink
feat: Secrets datasource (#3131)
Browse files Browse the repository at this point in the history
<!-- Feel free to delete comments as you fill this in -->

<!-- summary of changes -->
## Changes
- added datasource for secrets
- generated documentation for secrets
- ComposeCheckDestroy function
## Test Plan
<!-- detail ways in which this PR has been tested or needs to be tested
-->
* [X] acceptance tests


## References
https://docs.snowflake.com/en/sql-reference/sql/create-secret

## TODO
Handling secret type changed externally
  • Loading branch information
sfc-gh-fbudzynski authored Oct 18, 2024
1 parent 16a812d commit 8110138
Show file tree
Hide file tree
Showing 8 changed files with 742 additions and 0 deletions.
131 changes: 131 additions & 0 deletions docs/data-sources/secrets.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
---
page_title: "snowflake_secrets Data Source - terraform-provider-snowflake"
subcategory: ""
description: |-
Datasource used to get details of filtered secrets. Filtering is aligned with the current possibilities for SHOW SECRETS https://docs.snowflake.com/en/sql-reference/sql/show-secrets query. The results of SHOW and DESCRIBE are encapsulated in one output collection secrets.
---

!> **V1 release candidate** This data source 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 data source if needed. Any errors reported will be resolved with a higher priority. We encourage checking this data source out before the V1 release. Please follow the [migration guide](https://github.com/Snowflake-Labs/terraform-provider-snowflake/blob/main/MIGRATION_GUIDE.md#v0970--v0980) to use it.

# snowflake_secrets (Data Source)

Datasource used to get details of filtered secrets. Filtering is aligned with the current possibilities for [SHOW SECRETS](https://docs.snowflake.com/en/sql-reference/sql/show-secrets) query. The results of SHOW and DESCRIBE are encapsulated in one output collection `secrets`.

## Example Usage

```terraform
# Simple usage
data "snowflake_secrets" "simple" {
}
output "simple_output" {
value = data.snowflake_secrets.simple.secrets
}
# Filtering (like)
data "snowflake_secrets" "like" {
like = "secret-name"
}
output "like_output" {
value = data.snowflake_secrets.like.secrets
}
# Filtering by prefix (like)
data "snowflake_secrets" "like_prefix" {
like = "prefix%"
}
output "like_prefix_output" {
value = data.snowflake_secrets.like_prefix.secrets
}
# Filtering (in)
data "snowflake_secrets" "in" {
in {
schema = snowflake_schema.test.fully_qualified_name
}
}
output "in_output" {
value = data.snowflake_secrets.in.secrets
}
# Without additional data (to limit the number of calls make for every found secret)
data "snowflake_secrets" "only_show" {
# with_describe is turned on by default and it calls DESCRIBE SECRET for every secret found and attaches its output to secrets.*.describe_output field
with_describe = false
}
output "only_show_output" {
value = data.snowflake_secrets.only_show.secrets
}
```

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

### Optional

- `in` (Block List, Max: 1) IN clause to filter the list of secrets (see [below for nested schema](#nestedblock--in))
- `like` (String) Filters the output with **case-insensitive** pattern, with support for SQL wildcard characters (`%` and `_`).
- `with_describe` (Boolean) Runs DESC SECRET for each secret returned by SHOW SECRETS. The output of describe is saved to the description field. By default this value is set to true.

### Read-Only

- `id` (String) The ID of this resource.
- `secrets` (List of Object) Holds the aggregated output of all secrets details queries. (see [below for nested schema](#nestedatt--secrets))

<a id="nestedblock--in"></a>
### Nested Schema for `in`

Optional:

- `account` (Boolean) Returns records for the entire account.
- `application` (String) Returns records for the specified application.
- `application_package` (String) Returns records for the specified application package.
- `database` (String) Returns records for the current database in use or for a specified database.
- `schema` (String) Returns records for the current schema in use or a specified schema. Use fully qualified name.


<a id="nestedatt--secrets"></a>
### Nested Schema for `secrets`

Read-Only:

- `describe_output` (List of Object) (see [below for nested schema](#nestedobjatt--secrets--describe_output))
- `show_output` (List of Object) (see [below for nested schema](#nestedobjatt--secrets--show_output))

<a id="nestedobjatt--secrets--describe_output"></a>
### Nested Schema for `secrets.describe_output`

Read-Only:

- `comment` (String)
- `created_on` (String)
- `database_name` (String)
- `integration_name` (String)
- `name` (String)
- `oauth_access_token_expiry_time` (String)
- `oauth_refresh_token_expiry_time` (String)
- `oauth_scopes` (Set of String)
- `owner` (String)
- `schema_name` (String)
- `secret_type` (String)
- `username` (String)


<a id="nestedobjatt--secrets--show_output"></a>
### Nested Schema for `secrets.show_output`

Read-Only:

- `comment` (String)
- `created_on` (String)
- `database_name` (String)
- `name` (String)
- `oauth_scopes` (Set of String)
- `owner` (String)
- `owner_role_type` (String)
- `schema_name` (String)
- `secret_type` (String)
46 changes: 46 additions & 0 deletions examples/data-sources/snowflake_secrets/data-source.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# Simple usage
data "snowflake_secrets" "simple" {
}

output "simple_output" {
value = data.snowflake_secrets.simple.secrets
}

# Filtering (like)
data "snowflake_secrets" "like" {
like = "secret-name"
}

output "like_output" {
value = data.snowflake_secrets.like.secrets
}

# Filtering by prefix (like)
data "snowflake_secrets" "like_prefix" {
like = "prefix%"
}

output "like_prefix_output" {
value = data.snowflake_secrets.like_prefix.secrets
}

# Filtering (in)
data "snowflake_secrets" "in" {
in {
schema = snowflake_schema.test.fully_qualified_name
}
}

output "in_output" {
value = data.snowflake_secrets.in.secrets
}

# Without additional data (to limit the number of calls make for every found secret)
data "snowflake_secrets" "only_show" {
# with_describe is turned on by default and it calls DESCRIBE SECRET for every secret found and attaches its output to secrets.*.describe_output field
with_describe = false
}

output "only_show_output" {
value = data.snowflake_secrets.only_show.secrets
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package resourceshowoutputassert

import (
"testing"

"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/bettertestspoc/assert"
)

// SecretsDatasourceShowOutput is a temporary workaround to have better show output assertions in data source acceptance tests.
func SecretsDatasourceShowOutput(t *testing.T, name string) *SecretShowOutputAssert {
t.Helper()

s := SecretShowOutputAssert{
ResourceAssert: assert.NewDatasourceAssert("data."+name, "show_output", "secrets.0."),
}
s.AddAssertion(assert.ValueSet("show_output.#", "1"))
return &s
}
13 changes: 13 additions & 0 deletions pkg/acceptance/check_destroy.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,19 @@ import (
"github.com/hashicorp/terraform-plugin-testing/terraform"
)

func ComposeCheckDestroy(t *testing.T, resources ...resources.Resource) func(*terraform.State) error {
t.Helper()

return func(s *terraform.State) error {
errs := make([]error, 0)
for _, resource := range resources {
checkFunc := CheckDestroy(t, resource)
errs = append(errs, checkFunc(s))
}
return errors.Join(errs...)
}
}

func CheckDestroy(t *testing.T, resource resources.Resource) func(*terraform.State) error {
t.Helper()
// TODO [SNOW-1653619]: use TestClient() here
Expand Down
142 changes: 142 additions & 0 deletions pkg/datasources/secrets.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
package datasources

import (
"context"

"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider"
"github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/resources"
"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/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

var secretsSchema = map[string]*schema.Schema{
"with_describe": {
Type: schema.TypeBool,
Optional: true,
Default: true,
Description: "Runs DESC SECRET for each secret returned by SHOW SECRETS. The output of describe is saved to the description field. By default this value is set to true.",
},
"in": {
Type: schema.TypeList,
Optional: true,
Description: "IN clause to filter the list of secrets",
MaxItems: 1,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
"account": {
Type: schema.TypeBool,
Optional: true,
Description: "Returns records for the entire account.",
ExactlyOneOf: []string{"in.0.account", "in.0.database", "in.0.schema", "in.0.application", "in.0.application_package"},
},
"database": {
Type: schema.TypeString,
Optional: true,
Description: "Returns records for the current database in use or for a specified database.",
ExactlyOneOf: []string{"in.0.account", "in.0.database", "in.0.schema", "in.0.application", "in.0.application_package"},
ValidateDiagFunc: resources.IsValidIdentifier[sdk.AccountObjectIdentifier](),
},
"schema": {
Type: schema.TypeString,
Optional: true,
Description: "Returns records for the current schema in use or a specified schema. Use fully qualified name.",
ExactlyOneOf: []string{"in.0.account", "in.0.database", "in.0.schema", "in.0.application", "in.0.application_package"},
ValidateDiagFunc: resources.IsValidIdentifier[sdk.DatabaseObjectIdentifier](),
},
"application": {
Type: schema.TypeString,
Optional: true,
Description: "Returns records for the specified application.",
ExactlyOneOf: []string{"in.0.account", "in.0.database", "in.0.schema", "in.0.application", "in.0.application_package"},
ValidateDiagFunc: resources.IsValidIdentifier[sdk.AccountObjectIdentifier](),
},
"application_package": {
Type: schema.TypeString,
Optional: true,
Description: "Returns records for the specified application package.",
ExactlyOneOf: []string{"in.0.account", "in.0.database", "in.0.schema", "in.0.application", "in.0.application_package"},
ValidateDiagFunc: resources.IsValidIdentifier[sdk.AccountObjectIdentifier](),
},
},
},
},
"like": {
Type: schema.TypeString,
Optional: true,
Description: "Filters the output with **case-insensitive** pattern, with support for SQL wildcard characters (`%` and `_`).",
},
"secrets": {
Type: schema.TypeList,
Computed: true,
Description: "Holds the aggregated output of all secrets details queries.",
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
resources.ShowOutputAttributeName: {
Type: schema.TypeList,
Computed: true,
Description: "Holds the output of SHOW SECRETS.",
Elem: &schema.Resource{
Schema: schemas.ShowSecretSchema,
},
},
resources.DescribeOutputAttributeName: {
Type: schema.TypeList,
Computed: true,
Description: "Holds the output of DESCRIBE SECRET.",
Elem: &schema.Resource{
Schema: schemas.DescribeSecretSchema,
},
},
},
},
},
}

func Secrets() *schema.Resource {
return &schema.Resource{
ReadContext: ReadSecrets,
Schema: secretsSchema,
Description: "Datasource used to get details of filtered secrets. Filtering is aligned with the current possibilities for [SHOW SECRETS](https://docs.snowflake.com/en/sql-reference/sql/show-secrets) query. The results of SHOW and DESCRIBE are encapsulated in one output collection `secrets`.",
}
}

func ReadSecrets(ctx context.Context, d *schema.ResourceData, meta any) diag.Diagnostics {
client := meta.(*provider.Context).Client
req := sdk.NewShowSecretRequest()

handleLike(d, &req.Like)
err := handleExtendedIn(d, &req.In)
if err != nil {
return diag.FromErr(err)
}

secrets, err := client.Secrets.Show(ctx, req)
if err != nil {
return diag.FromErr(err)
}
d.SetId("secrets_read")

flattenedSecrets := make([]map[string]any, len(secrets))
for i, secret := range secrets {
secret := secret
var secretDescriptions []map[string]any
if d.Get("with_describe").(bool) {
describeOutput, err := client.Secrets.Describe(ctx, secret.ID())
if err != nil {
return diag.FromErr(err)
}
secretDescriptions = []map[string]any{schemas.SecretDescriptionToSchema(*describeOutput)}
}

flattenedSecrets[i] = map[string]any{
resources.ShowOutputAttributeName: []map[string]any{schemas.SecretToSchema(&secret)},
resources.DescribeOutputAttributeName: secretDescriptions,
}
}
if err := d.Set("secrets", flattenedSecrets); err != nil {
return diag.FromErr(err)
}
return nil
}
Loading

0 comments on commit 8110138

Please sign in to comment.