Skip to content

Commit

Permalink
fix: Connection and secret-datasource tests (#3177)
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
* fix secrets datasource acceptance tests
* added tests for `ParseCommaSeparatedAccountIdentifierArray`
* added customDiff and tests for re-creating connection resources
* adjusted migration-guide and documentation for connection resources
* added 'TestAccPreCheck' to acceptance tests

## Test Plan
<!-- detail ways in which this PR has been tested or needs to be tested
-->
* [x] unit tests
* [x] integration tests
* [x] acceptance tests
* [x] acceptance tests for secrets-datasource

## References
<!-- issues documentation links, etc  -->
*
#3162

---------

Co-authored-by: Jan Cieślak <jan.cieslak@snowflake.com>
  • Loading branch information
sfc-gh-fbudzynski and sfc-gh-jcieslak authored Nov 8, 2024
1 parent 2942374 commit 167de4b
Show file tree
Hide file tree
Showing 15 changed files with 205 additions and 72 deletions.
17 changes: 12 additions & 5 deletions MIGRATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,17 +19,24 @@ Added a new datasource enabling querying and filtering connections. Notes:

### *(new feature)* connection resources

Added a new resources for managing connections. We decided to split connection into two separate resources based on whether the connection is primary or a replica (secondary). i.e.:
Added a new resources for managing connections. We decided to split connection into two separate resources based on whether the connection is a primary or replicated (secondary). i.e.:

- `snowflake_connection` is used as primary connection, with ability to enable failover to other accounts.
- `snowflake_secondary_connection` is used as replica (secondary) connection.
- `snowflake_primary_connection` is used to manage primary connection, with ability to enable failover to other accounts.
- `snowflake_secondary_connection` is used to manage replicated (secondary) connection.

In order to promote secondary_connection to primary, resources need to be migrated (check [resource migration](https://github.com/Snowflake-Labs/terraform-provider-snowflake/blob/main/docs/technical-documentation/resource_migration.md)) or re-created and imported using the following SQL statements on Snowflake Worksheet:
To promote `snowflake_secondary_connection` to `snowflake_primary_connection`, resources need to be removed from the state, altered manually using:
```
ALTER CONNECTION <name> PRIMARY;
```
and then imported again, now as `snowflake_primary_connection`.

To demote `snowflake_primary_connection` back to `snowflake_secondary_connection`, resources need to be removed from the state, re-created manually using:
```
CREATE CONNECTION <name> AS REPLICA OF <organization_name>.<account_name>.<connection_name>;
ALTER CONNECTION <name> PRIMARY;
```
and then imported as `snowflake_secondary_connection`.

For guidance on removing and importing resources into the state check [resource migration](https://github.com/Snowflake-Labs/terraform-provider-snowflake/blob/main/docs/technical-documentation/resource_migration.md).

See reference [docs](https://docs.snowflake.com/en/sql-reference/sql/create-connection).

Expand Down
14 changes: 11 additions & 3 deletions docs/resources/primary_connection.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
page_title: "snowflake_primary_connection Resource - terraform-provider-snowflake"
subcategory: ""
description: |-
Resource used to manage primary (not replicated) connections. For more information, check connection documentation https://docs.snowflake.com/en/sql-reference/sql/create-connection.html.
Resource used to manage primary connections. For managing replicated connection check resource snowflakesecondaryconnection ./secondary_connection. For more information, check connection documentation https://docs.snowflake.com/en/sql-reference/sql/create-connection.html.
---

!> **V1 release candidate** This resource is a release candidate for the V1. It is on the list of remaining GA objects for 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#v0970--v0980) to use it.

# snowflake_primary_connection (Resource)

Resource used to manage primary (not replicated) connections. For more information, check [connection documentation](https://docs.snowflake.com/en/sql-reference/sql/create-connection.html).
Resource used to manage primary connections. For managing replicated connection check resource [snowflake_secondary_connection](./secondary_connection). For more information, check [connection documentation](https://docs.snowflake.com/en/sql-reference/sql/create-connection.html).

## Example Usage

Expand All @@ -28,7 +28,14 @@ resource "snowflake_primary_connection" "complete" {
]
}
```
-> **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).

-> **Note** Instead of using fully_qualified_name, you can reference objects managed outside Terraform by constructing a correct ID, consult [identifiers guide](./docs/guides/identifiers#new-computed-fully-qualified-name-field-in-resources).

-> **Note** To demote [`snowflake_primary_connection`](./primary_connection) to [`snowflake_secondary_connection`](./secondary_connection), resources need to be migrated manually. For guidance on removing and importing resources into the state check [resource migration](https://github.com/Snowflake-Labs/terraform-provider-snowflake/blob/main/docs/technical-documentation/resource_migration.md). Remove the resource from the state, then recreate it in manually using:
```
CREATE CONNECTION <name> AS REPLICA OF <organization_name>.<account_name>.<connection_name>;
```
and then import it as the `snowflake_secondary_connection`.
<!-- TODO(SNOW-1634854): include an example showing both methods-->

<!-- schema generated by tfplugindocs -->
Expand All @@ -47,6 +54,7 @@ resource "snowflake_primary_connection" "complete" {

- `fully_qualified_name` (String) Fully qualified name of the resource. For more information, see [object name resolution](https://docs.snowflake.com/en/sql-reference/name-resolution).
- `id` (String) The ID of this resource.
- `is_primary` (Boolean) Indicates if the connection is primary. When Terraform detects that the connection is not primary, the resource is recreated.
- `show_output` (List of Object) Outputs the result of `SHOW CONNECTIONS` for the given connection. (see [below for nested schema](#nestedatt--show_output))

<a id="nestedatt--show_output"></a>
Expand Down
15 changes: 11 additions & 4 deletions docs/resources/secondary_connection.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@
page_title: "snowflake_secondary_connection Resource - terraform-provider-snowflake"
subcategory: ""
description: |-
Resource used to manage secondary connections. To promote secondary connection to primary check migraton guide https://github.com/Snowflake-Labs/terraform-provider-snowflake/blob/main/MIGRATION_GUIDE.md#connection-resources. For more information, check connection documentation https://docs.snowflake.com/en/sql-reference/sql/create-connection.html.
Resource used to manage secondary (replicated) connections. To manage primary connection check resource snowflakeprimaryconnection ./primary_connection. For more information, check connection documentation https://docs.snowflake.com/en/sql-reference/sql/create-connection.html.
---

!> **V1 release candidate** This resource is a release candidate for the V1. It is on the list of remaining GA objects for 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#v0970--v0980) to use it.

# snowflake_secondary_connection (Resource)

Resource used to manage secondary connections. To promote secondary connection to primary check [migraton guide](https://github.com/Snowflake-Labs/terraform-provider-snowflake/blob/main/MIGRATION_GUIDE.md#connection-resources). For more information, check [connection documentation](https://docs.snowflake.com/en/sql-reference/sql/create-connection.html).
Resource used to manage secondary (replicated) connections. To manage primary connection check resource [snowflake_primary_connection](./primary_connection). For more information, check [connection documentation](https://docs.snowflake.com/en/sql-reference/sql/create-connection.html).

## Example Usage

Expand All @@ -27,7 +27,14 @@ resource "snowflake_secondary_connection" "complete" {
comment = "my complete secondary connection"
}
```
-> **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).

-> **Note** Instead of using fully_qualified_name, you can reference objects managed outside Terraform by constructing a correct ID, consult [identifiers guide](../guides/identifiers#new-computed-fully-qualified-name-field-in-resources).

-> **Note** To promote [`snowflake_secondary_connection`](./secondary_connection) to [`snowflake_primary_connection`](./primary_connection), resources need to be migrated manually. For guidance on removing and importing resources into the state check [resource migration](https://github.com/Snowflake-Labs/terraform-provider-snowflake/blob/main/docs/technical-documentation/resource_migration.md). Remove the resource from the state, then promote it manually using:
```
ALTER CONNECTION <name> PRIMARY;
```
and then import it as the `snowflake_primary_connection`.
<!-- TODO(SNOW-1634854): include an example showing both methods-->

<!-- schema generated by tfplugindocs -->
Expand All @@ -46,7 +53,7 @@ resource "snowflake_secondary_connection" "complete" {

- `fully_qualified_name` (String) Fully qualified name of the resource. For more information, see [object name resolution](https://docs.snowflake.com/en/sql-reference/name-resolution).
- `id` (String) The ID of this resource.
- `is_primary` (Boolean) Indicates if the connection has been changed to primary. If change is detected, the secondary connection will be recreated.
- `is_primary` (Boolean) Indicates if the connection primary status has been changed. If change is detected, resource will be recreated.
- `show_output` (List of Object) Outputs the result of `SHOW CONNECTIONS` for the given connection. (see [below for nested schema](#nestedatt--show_output))

<a id="nestedatt--show_output"></a>
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

49 changes: 26 additions & 23 deletions pkg/datasources/secrets_acceptance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ func TestAcc_Secrets_WithClientCredentials(t *testing.T) {

secretModel := model.SecretWithClientCredentials("test", integrationId.Name(), id.DatabaseName(), id.SchemaName(), id.Name(), []string{"username", "test_scope"})

dataSecretsClientCredentials := accConfig.FromModel(t, secretModel) + secretsData(secretWithClientCredentials)
dataSecretsClientCredentials := accConfig.FromModel(t, secretModel) + secretsData(secretWithClientCredentials, id.DatabaseId().FullyQualifiedName())

dsName := "data.snowflake_secrets.test"
resource.Test(t, resource.TestCase{
Expand Down Expand Up @@ -97,7 +97,7 @@ func TestAcc_Secrets_WithAuthorizationCodeGrant(t *testing.T) {

secretModel := model.SecretWithAuthorizationCodeGrant("test", integrationId.Name(), id.DatabaseName(), id.SchemaName(), id.Name(), "test_token", time.Now().Add(24*time.Hour).Format(time.DateTime)).WithComment("test_comment")

dataSecretsAuthorizationCode := accConfig.FromModel(t, secretModel) + secretsData(secretWithAuthorizationCodeGrant)
dataSecretsAuthorizationCode := accConfig.FromModel(t, secretModel) + secretsData(secretWithAuthorizationCodeGrant, id.DatabaseId().FullyQualifiedName())

dsName := "data.snowflake_secrets.test"
resource.Test(t, resource.TestCase{
Expand Down Expand Up @@ -138,7 +138,7 @@ func TestAcc_Secrets_WithBasicAuthentication(t *testing.T) {
id := acc.TestClient().Ids.RandomSchemaObjectIdentifier()

secretModel := model.SecretWithBasicAuthentication("test", id.DatabaseName(), id.Name(), "test_passwd", id.SchemaName(), "test_username")
dataSecretsAuthorizationCode := accConfig.FromModel(t, secretModel) + secretsData(secretWithBasicAuthentication)
dataSecretsAuthorizationCode := accConfig.FromModel(t, secretModel) + secretsData(secretWithBasicAuthentication, id.DatabaseId().FullyQualifiedName())

dsName := "data.snowflake_secrets.test"
resource.Test(t, resource.TestCase{
Expand Down Expand Up @@ -179,7 +179,7 @@ func TestAcc_Secrets_WithGenericString(t *testing.T) {

secretModel := model.SecretWithGenericString("test", id.DatabaseName(), id.Name(), id.SchemaName(), "test_secret_string")

dataSecretsAuthorizationCode := accConfig.FromModel(t, secretModel) + secretsData(secretWithGenericString)
dataSecretsAuthorizationCode := accConfig.FromModel(t, secretModel) + secretsData(secretWithGenericString, id.DatabaseId().FullyQualifiedName())

dsName := "data.snowflake_secrets.test"
resource.Test(t, resource.TestCase{
Expand Down Expand Up @@ -216,11 +216,14 @@ func TestAcc_Secrets_WithGenericString(t *testing.T) {
})
}

func secretsData(secretResourceName string) string {
func secretsData(secretResourceName string, inDatabaseName string) string {
return fmt.Sprintf(`
data "snowflake_secrets" "test" {
depends_on = [%s.test]
}`, secretResourceName)
in {
database = %s
}
}`, secretResourceName, inDatabaseName)
}

func TestAcc_Secrets_Filtering(t *testing.T) {
Expand Down Expand Up @@ -309,6 +312,23 @@ func TestAcc_Secrets_Filtering(t *testing.T) {
})
}

func TestAcc_Secrets_EmptyIn(t *testing.T) {
resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories,
PreCheck: func() { acc.TestAccPreCheck(t) },
TerraformVersionChecks: []tfversion.TerraformVersionCheck{
tfversion.RequireAbove(tfversion.Version1_5_0),
},
CheckDestroy: nil,
Steps: []resource.TestStep{
{
Config: secretDatasourceEmptyIn(),
ExpectError: regexp.MustCompile("Invalid combination of arguments"),
},
},
})
}

func datasourceWithLikeMultipleSecretTypes(like string) string {
return fmt.Sprintf(`
data "snowflake_secrets" "test" {
Expand Down Expand Up @@ -340,23 +360,6 @@ func secretDatasourceInAccountWithLike(prefix string) string {
`, prefix)
}

func TestAcc_Secrets_EmptyIn(t *testing.T) {
resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories,
PreCheck: func() { acc.TestAccPreCheck(t) },
TerraformVersionChecks: []tfversion.TerraformVersionCheck{
tfversion.RequireAbove(tfversion.Version1_5_0),
},
CheckDestroy: nil,
Steps: []resource.TestStep{
{
Config: secretDatasourceEmptyIn(),
ExpectError: regexp.MustCompile("Invalid combination of arguments"),
},
},
})
}

func secretDatasourceEmptyIn() string {
return `
data "snowflake_secrets" "test" {
Expand Down
16 changes: 9 additions & 7 deletions pkg/resources/custom_diffs.go
Original file line number Diff line number Diff line change
Expand Up @@ -271,13 +271,15 @@ func RecreateWhenStreamIsStale() schema.CustomizeDiffFunc {
}
}

// TODO: [SNOW-1763442] unable to test now, as there is no test accounts with different regions
// RecreateWhenSecondaryConnectionChangedExternally detects if the secondary connection was promoted externally to serve as primary.
// If so, it sets the `is_primary` field to `false` which is our desired value for secondary_connection
func RecreateWhenSecondaryConnectionPromotedExternally() schema.CustomizeDiffFunc {
return func(_ context.Context, diff *schema.ResourceDiff, _ any) error {
if _, newValue := diff.GetChange("is_primary"); newValue.(bool) {
return diff.SetNew("is_primary", false)
// RecreateWhenResourceBoolFieldChangedExternally recreates a resource when wantValue is different than value in boolField.
func RecreateWhenResourceBoolFieldChangedExternally(boolField string, wantValue bool) schema.CustomizeDiffFunc {
return func(_ context.Context, diff *schema.ResourceDiff, _ interface{}) error {
if n := diff.Get(boolField); n != nil {
logging.DebugLogger.Printf("[DEBUG] new external value for %v: %v\n", boolField, n.(bool))

if n.(bool) != wantValue {
return errors.Join(diff.SetNew(boolField, wantValue), diff.ForceNew(boolField))
}
}
return nil
}
Expand Down
44 changes: 36 additions & 8 deletions pkg/resources/custom_diffs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"strings"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff"

"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/sdk"
Expand Down Expand Up @@ -1082,24 +1084,50 @@ func Test_RecreateWhenSecretTypeChangedExternallyForOAuth2(t *testing.T) {
}
}

func Test_RecreateWhenSecondaryConnectionChangedExternally(t *testing.T) {
func Test_RecreateWhenResourceBoolFieldChangedExternally(t *testing.T) {
tests := []struct {
name string
expectedIsPrimary string
stateValue map[string]string
name string
isPrimary bool
stateValue map[string]string
wantForceNew bool
}{
{
name: "changed from is_primary from false to true",
expectedIsPrimary: "false",
name: "changed is_primary from false to true",
isPrimary: false,
stateValue: map[string]string{
"is_primary": "true",
},
wantForceNew: true,
},
{
name: "changed is_primary from true to false",
isPrimary: true,
stateValue: map[string]string{
"is_primary": "false",
},
wantForceNew: true,
},
{
name: "no change in is_primary - true to true",
isPrimary: true,
stateValue: map[string]string{
"is_primary": "true",
},
wantForceNew: false,
},
{
name: "no change in is_primary - false to false",
isPrimary: false,
stateValue: map[string]string{
"is_primary": "false",
},
wantForceNew: false,
},
}
for _, tt := range tests {
tt := tt
t.Run(tt.name, func(t *testing.T) {
customDiff := resources.RecreateWhenSecondaryConnectionPromotedExternally()
customDiff := resources.RecreateWhenResourceBoolFieldChangedExternally("is_primary", tt.isPrimary)
testProvider := createProviderWithCustomSchemaAndCustomDiff(t,
map[string]*schema.Schema{
"is_primary": {
Expand All @@ -1114,7 +1142,7 @@ func Test_RecreateWhenSecondaryConnectionChangedExternally(t *testing.T) {
tt.stateValue,
map[string]any{},
)
assert.Equal(t, tt.expectedIsPrimary, diff.Attributes["is_primary"].New)
assert.Equal(t, tt.wantForceNew, diff.RequiresNew())
})
}
}
Loading

0 comments on commit 167de4b

Please sign in to comment.