Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: Fix some bugs #2421

Merged
merged 5 commits into from
Jan 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 18 additions & 9 deletions pkg/resources/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -263,16 +263,25 @@ func UpdateDatabase(d *schema.ResourceData, meta interface{}) error {
// If replication configuration changes, need to update accounts that have permission to replicate database
if d.HasChange("replication_configuration") {
oldConfig, newConfig := d.GetChange("replication_configuration")
newAccounts := newConfig.([]interface{})[0].(map[string]interface{})["accounts"].([]interface{})
newAccountIDs := make([]sdk.AccountIdentifier, len(newAccounts))
for i, account := range newAccounts {
newAccountIDs[i] = sdk.NewAccountIdentifierFromAccountLocator(account.(string))

newAccountIDs := make([]sdk.AccountIdentifier, 0)
ignoreEditionCheck := false
if len(newConfig.([]interface{})) != 0 {
newAccounts := newConfig.([]interface{})[0].(map[string]interface{})["accounts"].([]interface{})
for _, account := range newAccounts {
newAccountIDs = append(newAccountIDs, sdk.NewAccountIdentifierFromAccountLocator(account.(string)))
}
ignoreEditionCheck = newConfig.([]interface{})[0].(map[string]interface{})["ignore_edition_check"].(bool)
}
oldAccounts := oldConfig.([]interface{})[0].(map[string]interface{})["accounts"].([]interface{})
oldAccountIDs := make([]sdk.AccountIdentifier, len(oldAccounts))
for i, account := range oldAccounts {
oldAccountIDs[i] = sdk.NewAccountIdentifierFromAccountLocator(account.(string))

oldAccountIDs := make([]sdk.AccountIdentifier, 0)
if len(oldConfig.([]interface{})) != 0 {
oldAccounts := oldConfig.([]interface{})[0].(map[string]interface{})["accounts"].([]interface{})
for _, account := range oldAccounts {
oldAccountIDs = append(oldAccountIDs, sdk.NewAccountIdentifierFromAccountLocator(account.(string)))
}
}

accountsToRemove := make([]sdk.AccountIdentifier, 0)
accountsToAdd := make([]sdk.AccountIdentifier, 0)
// Find accounts to remove
Expand All @@ -294,7 +303,7 @@ func UpdateDatabase(d *schema.ResourceData, meta interface{}) error {
ToAccounts: accountsToAdd,
},
}
if ignoreEditionCheck := d.Get("ignore_edition_check").(bool); ignoreEditionCheck {
if ignoreEditionCheck {
opts.EnableReplication.IgnoreEditionCheck = sdk.Bool(ignoreEditionCheck)
}
err := client.Databases.AlterReplication(ctx, id, opts)
Expand Down
55 changes: 52 additions & 3 deletions pkg/resources/database_acceptance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ func TestAcc_Database(t *testing.T) {
prefix := "tst-terraform" + strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))
prefix2 := "tst-terraform" + strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha))

secondaryAccountName := getSecondaryAccount(t)

resource.ParallelTest(t, resource.TestCase{
Providers: acc.TestAccProviders(),
PreCheck: func() { acc.TestAccPreCheck(t) },
Expand Down Expand Up @@ -80,11 +82,25 @@ func TestAcc_Database(t *testing.T) {
resource.TestCheckResourceAttr("snowflake_database.db", "data_retention_time_in_days", "3"),
),
},
// ADD REPLICATION
// proves https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2369 error
{
Config: dbConfigWithReplication(prefix2, secondaryAccountName),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("snowflake_database.db", "name", prefix2),
resource.TestCheckResourceAttr("snowflake_database.db", "comment", "test comment 2"),
resource.TestCheckResourceAttr("snowflake_database.db", "data_retention_time_in_days", "3"),
resource.TestCheckResourceAttr("snowflake_database.db", "replication_configuration.#", "1"),
resource.TestCheckResourceAttr("snowflake_database.db", "replication_configuration.0.accounts.#", "1"),
resource.TestCheckResourceAttr("snowflake_database.db", "replication_configuration.0.accounts.0", secondaryAccountName),
),
},
// IMPORT
{
ResourceName: "snowflake_database.db",
ImportState: true,
ImportStateVerify: true,
ResourceName: "snowflake_database.db",
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"replication_configuration"},
},
},
})
Expand Down Expand Up @@ -155,6 +171,22 @@ resource "snowflake_database" "db" {
return fmt.Sprintf(s, prefix)
}

func dbConfigWithReplication(prefix string, secondaryAccountName string) string {
s := `
resource "snowflake_database" "db" {
name = "%s"
comment = "test comment 2"
data_retention_time_in_days = 3
replication_configuration {
accounts = [
"%s"
]
}
}
`
return fmt.Sprintf(s, prefix, secondaryAccountName)
}

func dropDatabaseOutsideTerraform(t *testing.T, id string) {
t.Helper()

Expand All @@ -166,6 +198,23 @@ func dropDatabaseOutsideTerraform(t *testing.T, id string) {
require.NoError(t, err)
}

func getSecondaryAccount(t *testing.T) string {
t.Helper()

secondaryConfig, err := sdk.ProfileConfig("secondary_test_account")
require.NoError(t, err)

secondaryClient, err := sdk.NewClient(secondaryConfig)
require.NoError(t, err)

ctx := context.Background()

account, err := secondaryClient.ContextFunctions.CurrentAccount(ctx)
require.NoError(t, err)

return account
}

func testAccCheckDatabaseExistence(t *testing.T, id string, shouldExist bool) func(state *terraform.State) error {
t.Helper()
return func(state *terraform.State) error {
Expand Down
45 changes: 38 additions & 7 deletions pkg/resources/dynamic_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package resources
import (
"context"
"database/sql"
"errors"
"log"
"strings"

Expand Down Expand Up @@ -190,8 +191,7 @@ func ReadDynamicTable(d *schema.ResourceData, meta interface{}) error {
return err
}
}
text := dynamicTable.Text
if strings.Contains(text, "OR REPLACE") {
if strings.Contains(dynamicTable.Text, "OR REPLACE") {
if err := d.Set("or_replace", true); err != nil {
return err
}
Expand All @@ -200,11 +200,6 @@ func ReadDynamicTable(d *schema.ResourceData, meta interface{}) error {
return err
}
}
// trim up to " ..AS" and remove whitespace
query := strings.TrimSpace(text[strings.Index(text, "AS")+3:])
if err := d.Set("query", query); err != nil {
return err
}
if err := d.Set("cluster_by", dynamicTable.ClusterBy); err != nil {
return err
}
Expand Down Expand Up @@ -247,9 +242,45 @@ func ReadDynamicTable(d *schema.ResourceData, meta interface{}) error {
if err := d.Set("data_timestamp", dynamicTable.DataTimestamp.Format("2006-01-02T16:04:05.000 -0700")); err != nil {
return err
}

query, err := getQueryFromDDL(dynamicTable.Text)
if err != nil {
return err
}
if err := d.Set("query", query); err != nil {
return err
}

return nil
}

/*
* Previous implementation tried to match query part from the whole dynamic table DDL statement by just using `AS`.
* It was failing for table names containing `AS` (like `REASON`). It was also failing for other parts containing `AS`.
* We cannot simply match by ` AS ` because this can still be part of COMMENT or SELECT query itself.
* We have considered not setting the query at all but it was not ideal because of:
* - possible external changes to dynamic table (drop and recreate externally with different query);
* - import not 100% correct.
* We did not want to complicate the implementation too much by introducing parsers.
* One more thing worth mentioning is the whitespace that can be introduced by the user that is still returned by SHOW.
* For now, we just normalize the DDL before extraction of query.
*
* The outcome implementation matches by ` AS SELECT ` and checks the number of matches.
* If more matches are found, the error is returned to inform user about possible cause of error.
*
* Refer to issue https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2329.
*/
func getQueryFromDDL(text string) (string, error) {
normalizedDDL := normalizeQuery(text)
matchSubstring := " AS SELECT "
matches := strings.Count(strings.ToUpper(normalizedDDL), matchSubstring)
if matches != 1 {
return "", errors.New("too many matches found. There is no way of getting ONLY the 'query' used to create the dynamic table from Snowflake. We try to get it from the whole creation statement but there may be cases where it fails. Please submit the issue on Github (refer to #2329)")
}
idx := strings.Index(strings.ToUpper(normalizedDDL), " AS SELECT ")
return strings.TrimSpace(normalizedDDL[idx+4:]), nil
}

func parseTargetLag(v interface{}) sdk.TargetLag {
var result sdk.TargetLag
tl := v.([]interface{})[0].(map[string]interface{})
Expand Down
83 changes: 83 additions & 0 deletions pkg/resources/dynamic_table_acceptance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"database/sql"
"fmt"
"regexp"
"strings"
"testing"

Expand Down Expand Up @@ -284,6 +285,88 @@ func TestAcc_DynamicTable_issue2276(t *testing.T) {
})
}

// TestAcc_DynamicTable_issue2329 proves https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2329 issue.
func TestAcc_DynamicTable_issue2329(t *testing.T) {
dynamicTableName := strings.ToUpper(acctest.RandStringFromCharSet(4, acctest.CharSetAlpha) + "AS" + acctest.RandStringFromCharSet(4, acctest.CharSetAlpha))
tableName := dynamicTableName + "_table"
query := fmt.Sprintf(`select "id" from "%v"."%v"."%v"`, acc.TestDatabaseName, acc.TestSchemaName, tableName)
m := func() map[string]config.Variable {
return map[string]config.Variable{
"name": config.StringVariable(dynamicTableName),
"database": config.StringVariable(acc.TestDatabaseName),
"schema": config.StringVariable(acc.TestSchemaName),
"warehouse": config.StringVariable(acc.TestWarehouseName),
// spaces added on purpose
"query": config.StringVariable(" " + query),
"comment": config.StringVariable("Comment with AS on purpose"),
"table_name": config.StringVariable(tableName),
}
}

resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories,
PreCheck: func() { acc.TestAccPreCheck(t) },
TerraformVersionChecks: []tfversion.TerraformVersionCheck{
tfversion.RequireAbove(tfversion.Version1_5_0),
},
CheckDestroy: testAccCheckDynamicTableDestroy,
Steps: []resource.TestStep{
{
ConfigDirectory: config.TestStepDirectory(),
ConfigVariables: m(),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("snowflake_dynamic_table.dt", "name", dynamicTableName),
resource.TestCheckResourceAttr("snowflake_dynamic_table.dt", "query", query),
),
},
// No changes are expected
{
ConfigDirectory: acc.ConfigurationSameAsStepN(1),
ConfigVariables: m(),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("snowflake_dynamic_table.dt", "name", dynamicTableName),
resource.TestCheckResourceAttr("snowflake_dynamic_table.dt", "query", query),
),
},
},
})
}

// TestAcc_DynamicTable_issue2329_with_matching_comment proves https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2329 issue.
func TestAcc_DynamicTable_issue2329_with_matching_comment(t *testing.T) {
dynamicTableName := strings.ToUpper(acctest.RandStringFromCharSet(4, acctest.CharSetAlpha) + "AS" + acctest.RandStringFromCharSet(4, acctest.CharSetAlpha))
tableName := dynamicTableName + "_table"
query := fmt.Sprintf(`select "id" from "%v"."%v"."%v"`, acc.TestDatabaseName, acc.TestSchemaName, tableName)
m := func() map[string]config.Variable {
return map[string]config.Variable{
"name": config.StringVariable(dynamicTableName),
"database": config.StringVariable(acc.TestDatabaseName),
"schema": config.StringVariable(acc.TestSchemaName),
"warehouse": config.StringVariable(acc.TestWarehouseName),
"query": config.StringVariable(query),
"comment": config.StringVariable("Comment with AS SELECT on purpose"),
"table_name": config.StringVariable(tableName),
}
}

resource.Test(t, resource.TestCase{
ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories,
PreCheck: func() { acc.TestAccPreCheck(t) },
TerraformVersionChecks: []tfversion.TerraformVersionCheck{
tfversion.RequireAbove(tfversion.Version1_5_0),
},
CheckDestroy: testAccCheckDynamicTableDestroy,
Steps: []resource.TestStep{
// If we match more than one time (in this case in comment) we raise an explanation error.
{
ConfigDirectory: acc.ConfigurationDirectory("TestAcc_DynamicTable_issue2329/1"),
ConfigVariables: m(),
ExpectError: regexp.MustCompile(`too many matches found. There is no way of getting ONLY the 'query' used to create the dynamic table from Snowflake. We try to get it from the whole creation statement but there may be cases where it fails. Please submit the issue on Github \(refer to #2329\)`),
},
},
})
}

func testAccCheckDynamicTableDestroy(s *terraform.State) error {
db := acc.TestAccProvider.Meta().(*sql.DB)
client := sdk.NewClientFromDB(db)
Expand Down
2 changes: 1 addition & 1 deletion pkg/resources/oauth_integration.go
Original file line number Diff line number Diff line change
Expand Up @@ -310,7 +310,7 @@ func UpdateOAuthIntegration(d *schema.ResourceData, meta interface{}) error {

if d.HasChange("blocked_roles_list") {
runSetStatement = true
stmt.SetStringList(`BLOCKED_ROLES_LIST`, expandStringList(d.Get("blocked_roles_list").([]interface{})))
stmt.SetStringList(`BLOCKED_ROLES_LIST`, expandStringList(d.Get("blocked_roles_list").(*schema.Set).List()))
}

if d.HasChange("enabled") {
Expand Down
17 changes: 13 additions & 4 deletions pkg/resources/oauth_integration_acceptance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func TestAcc_OAuthIntegration(t *testing.T) {
CheckDestroy: nil,
Steps: []resource.TestStep{
{
Config: oauthIntegrationConfig(name, oauthClient, clientType),
Config: oauthIntegrationConfig(name, oauthClient, clientType, "SYSADMIN"),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("snowflake_oauth_integration.test", "name", name),
resource.TestCheckResourceAttr("snowflake_oauth_integration.test", "oauth_client", oauthClient),
Expand All @@ -32,6 +32,15 @@ func TestAcc_OAuthIntegration(t *testing.T) {
resource.TestCheckResourceAttr("snowflake_oauth_integration.test", "blocked_roles_list.0", "SYSADMIN"),
),
},
{
// role change proves https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2358 issue
Config: oauthIntegrationConfig(name, oauthClient, clientType, "USERADMIN"),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("snowflake_oauth_integration.test", "name", name),
resource.TestCheckResourceAttr("snowflake_oauth_integration.test", "blocked_roles_list.#", "1"),
resource.TestCheckResourceAttr("snowflake_oauth_integration.test", "blocked_roles_list.0", "USERADMIN"),
),
},
{
ResourceName: "snowflake_oauth_integration.test",
ImportState: true,
Expand All @@ -41,7 +50,7 @@ func TestAcc_OAuthIntegration(t *testing.T) {
})
}

func oauthIntegrationConfig(name, oauthClient, clientType string) string {
func oauthIntegrationConfig(name, oauthClient, clientType string, blockedRole string) string {
return fmt.Sprintf(`
resource "snowflake_oauth_integration" "test" {
name = "%s"
Expand All @@ -51,9 +60,9 @@ func oauthIntegrationConfig(name, oauthClient, clientType string) string {
enabled = true
oauth_issue_refresh_tokens = true
oauth_refresh_token_validity = 3600
blocked_roles_list = ["SYSADMIN"]
blocked_roles_list = ["%s"]
}
`, name, oauthClient, clientType)
`, name, oauthClient, clientType, blockedRole)
}

func TestAcc_OAuthIntegrationTableau(t *testing.T) {
Expand Down
27 changes: 27 additions & 0 deletions pkg/resources/testdata/TestAcc_DynamicTable_issue2329/1/test.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
resource "snowflake_table" "t" {
database = var.database
schema = var.schema
name = var.table_name
change_tracking = true
column {
name = "id"
type = "NUMBER(38,0)"
}
column {
name = "data"
type = "VARCHAR(16)"
}
}

resource "snowflake_dynamic_table" "dt" {
depends_on = [snowflake_table.t]
name = var.name
database = var.database
schema = var.schema
target_lag {
maximum_duration = "2 minutes"
}
warehouse = var.warehouse
query = var.query
comment = var.comment
}
Loading
Loading