From 2fb50d7b5beb1f361d3c761b344bab3216f6ea59 Mon Sep 17 00:00:00 2001 From: Artur Sawicki Date: Thu, 28 Nov 2024 20:25:33 +0100 Subject: [PATCH 1/3] chore: Use service user for ci (#3228) Use service user on CI - adjust config provider tests - limit the number of predefined profiles - create users with limited privileges to test different setups - extract toml builder (with follow-ups described in TODOs) - prepare user setups in TestClient (with follow-up improvements described in TODOs) Fixes: - fix the authenticator type merging (special empty value added) Misc: - bump sweepers timeout to 10m - left TODOs to check the behavior of 3-value booleans for each attribute in driver config - remove unused asserts - move some SDK client tests to testint package (TODO left for the rest) --- .github/workflows/tests.yml | 2 +- Makefile | 2 +- pkg/acceptance/asserts.go | 23 - pkg/acceptance/asserts_test.go | 73 -- pkg/acceptance/helpers/config_toml_creator.go | 162 +++++ pkg/acceptance/helpers/context_client.go | 10 + pkg/acceptance/helpers/grant_client.go | 66 ++ pkg/acceptance/helpers/ids_generator.go | 4 + pkg/acceptance/helpers/random/certs.go | 38 +- .../helpers/tmp_toml_setup_helpers.go | 69 ++ .../helpers/tmp_user_setup_helpers.go | 101 +++ pkg/acceptance/ids/non_existing.go | 8 + .../testprofiles/testing_config_profiles.go | 16 +- .../authentication_methods/auth_test.go | 8 +- .../authentication_methods/profiles.go | 6 + pkg/provider/provider.go | 14 +- pkg/provider/provider_acceptance_test.go | 633 ++++++++++-------- .../resource_monitor_acceptance_test.go | 18 +- pkg/sdk/client_integration_test.go | 55 +- pkg/sdk/config.go | 95 +-- pkg/sdk/config_test.go | 58 +- pkg/sdk/helper_test.go | 4 +- pkg/sdk/sweepers_test.go | 2 +- pkg/sdk/testint/client_integration_test.go | 64 ++ 24 files changed, 1022 insertions(+), 509 deletions(-) delete mode 100644 pkg/acceptance/asserts.go delete mode 100644 pkg/acceptance/asserts_test.go create mode 100644 pkg/acceptance/helpers/config_toml_creator.go create mode 100644 pkg/acceptance/helpers/tmp_toml_setup_helpers.go create mode 100644 pkg/acceptance/helpers/tmp_user_setup_helpers.go create mode 100644 pkg/acceptance/ids/non_existing.go create mode 100644 pkg/manual_tests/authentication_methods/profiles.go diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 824bd0cb0e..5f1d0284f4 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -49,7 +49,7 @@ jobs: - name: Create and populate .snowflake/config file id: create_config - run: mkdir -p $HOME/.snowflake && echo "${{ secrets.SNOWFLAKE_CONFIG_FILE }}" > $HOME/.snowflake/config + run: mkdir -p $HOME/.snowflake && echo "${{ secrets.ASW_TMP_SNOWFLAKE_CONFIG_FILE }}" > $HOME/.snowflake/config - name: Create and populate .snowflake/config_v097_compatible file id: create_config_v097_compatible diff --git a/Makefile b/Makefile index 66435284af..fa1448adef 100644 --- a/Makefile +++ b/Makefile @@ -57,7 +57,7 @@ sweep: ## destroy the whole architecture; USE ONLY FOR DEVELOPMENT ACCOUNTS @echo "Are you sure? [y/n]" >&2 @read -r REPLY; \ if echo "$$REPLY" | grep -qG "^[yY]$$"; then \ - TEST_SF_TF_ENABLE_SWEEP=1 go test -timeout 300s -run "^(TestSweepAll|Test_Sweeper_NukeStaleObjects)" ./pkg/sdk -v; \ + TEST_SF_TF_ENABLE_SWEEP=1 go test -timeout=10m -run "^(TestSweepAll|Test_Sweeper_NukeStaleObjects)" ./pkg/sdk -v; \ else echo "Aborting..."; \ fi; diff --git a/pkg/acceptance/asserts.go b/pkg/acceptance/asserts.go deleted file mode 100644 index 4ce547dfa9..0000000000 --- a/pkg/acceptance/asserts.go +++ /dev/null @@ -1,23 +0,0 @@ -package acceptance - -import ( - "fmt" - "strconv" - - "github.com/hashicorp/terraform-plugin-testing/helper/resource" -) - -func IsGreaterOrEqualTo(greaterOrEqualValue int) resource.CheckResourceAttrWithFunc { - return func(value string) error { - intValue, err := strconv.Atoi(value) - if err != nil { - return fmt.Errorf("unable to parse value %s as integer, err = %w", value, err) - } - - if intValue < greaterOrEqualValue { - return fmt.Errorf("expected value %d to be greater or equal to %d", intValue, greaterOrEqualValue) - } - - return nil - } -} diff --git a/pkg/acceptance/asserts_test.go b/pkg/acceptance/asserts_test.go deleted file mode 100644 index 6d7e488d7f..0000000000 --- a/pkg/acceptance/asserts_test.go +++ /dev/null @@ -1,73 +0,0 @@ -package acceptance - -import ( - "testing" - - "github.com/stretchr/testify/assert" -) - -func TestIsGreaterOrEqualTo(t *testing.T) { - testCases := []struct { - Name string - GreaterOrEqualTo int - Actual string - Error string - }{ - { - Name: "validation: smaller than expected", - GreaterOrEqualTo: 20, - Actual: "10", - Error: "expected value 10 to be greater or equal to 20", - }, - { - Name: "validation: zero actual value", - GreaterOrEqualTo: 20, - Actual: "0", - Error: "expected value 0 to be greater or equal to 20", - }, - { - Name: "validation: zero greater value", - GreaterOrEqualTo: 0, - Actual: "-10", - Error: "expected value -10 to be greater or equal to 0", - }, - { - Name: "validation: negative value", - GreaterOrEqualTo: -20, - Actual: "-30", - Error: "expected value -30 to be greater or equal to -20", - }, - { - Name: "validation: not int value", - GreaterOrEqualTo: 20, - Actual: "not_int", - Error: "unable to parse value not_int as integer, err = strconv.Atoi: parsing \"not_int\": invalid syntax", - }, - { - Name: "validation: equal value", - GreaterOrEqualTo: 20, - Actual: "20", - }, - { - Name: "validation: greater value", - GreaterOrEqualTo: 20, - Actual: "30", - }, - { - Name: "validation: greater value with expected negative value", - GreaterOrEqualTo: -20, - Actual: "30", - }, - } - - for _, testCase := range testCases { - t.Run(testCase.Name, func(t *testing.T) { - err := IsGreaterOrEqualTo(testCase.GreaterOrEqualTo)(testCase.Actual) - if testCase.Error != "" { - assert.ErrorContains(t, err, testCase.Error) - } else { - assert.NoError(t, err) - } - }) - } -} diff --git a/pkg/acceptance/helpers/config_toml_creator.go b/pkg/acceptance/helpers/config_toml_creator.go new file mode 100644 index 0000000000..5e3ca26a02 --- /dev/null +++ b/pkg/acceptance/helpers/config_toml_creator.go @@ -0,0 +1,162 @@ +package helpers + +import ( + "fmt" + "testing" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/helpers/random" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" +) + +// FullTomlConfigForServiceUser is a temporary function used to test provider configuration +// TODO [SNOW-1827309]: use toml marshaling from "github.com/pelletier/go-toml/v2" +// TODO [SNOW-1827309]: add builders for our toml config struct +func FullTomlConfigForServiceUser(t *testing.T, profile string, userId sdk.AccountObjectIdentifier, roleId sdk.AccountObjectIdentifier, warehouseId sdk.AccountObjectIdentifier, accountIdentifier sdk.AccountIdentifier, privateKey string) string { + t.Helper() + + return fmt.Sprintf(` +[%[1]s] +user = '%[2]s' +privatekey = '''%[7]s''' +role = '%[3]s' +organizationname = '%[5]s' +accountname = '%[6]s' +warehouse = '%[4]s' +clientip = '1.2.3.4' +protocol = 'https' +port = 443 +oktaurl = 'https://example.com' +clienttimeout = 10 +jwtclienttimeout = 20 +logintimeout = 30 +requesttimeout = 40 +jwtexpiretimeout = 50 +externalbrowsertimeout = 60 +maxretrycount = 1 +authenticator = 'SNOWFLAKE_JWT' +insecuremode = true +ocspfailopen = true +token = 'token' +keepsessionalive = true +disabletelemetry = true +validatedefaultparameters = true +clientrequestmfatoken = true +clientstoretemporarycredential = true +tracing = 'warning' +tmpdirpath = '.' +disablequerycontextcache = true +includeretryreason = true +disableconsolelogin = true + +[%[1]s.params] +foo = 'bar' +`, profile, userId.Name(), roleId.Name(), warehouseId.Name(), accountIdentifier.OrganizationName(), accountIdentifier.AccountName(), privateKey) +} + +// FullInvalidTomlConfigForServiceUser is a temporary function used to test provider configuration +func FullInvalidTomlConfigForServiceUser(t *testing.T, profile string) string { + t.Helper() + + privateKey, _, _, _ := random.GenerateRSAKeyPair(t, "") + return fmt.Sprintf(` +[%[1]s] +user = 'invalid' +privatekey = '''%[2]s''' +role = 'invalid' +accountname = 'invalid' +organizationname = 'invalid' +warehouse = 'invalid' +clientip = 'invalid' +protocol = 'invalid' +port = -1 +oktaurl = 'invalid' +clienttimeout = -1 +jwtclienttimeout = -1 +logintimeout = -1 +requesttimeout = -1 +jwtexpiretimeout = -1 +externalbrowsertimeout = -1 +maxretrycount = -1 +authenticator = 'snowflake' +insecuremode = true +ocspfailopen = true +token = 'token' +keepsessionalive = true +disabletelemetry = true +validatedefaultparameters = false +clientrequestmfatoken = true +clientstoretemporarycredential = true +tracing = 'invalid' +tmpdirpath = '.' +disablequerycontextcache = true +includeretryreason = true +disableconsolelogin = true + +[%[1]s.params] +foo = 'bar'`, profile, privateKey) +} + +// TomlConfigForServiceUser is a temporary function used to test provider configuration +func TomlConfigForServiceUser(t *testing.T, profile string, userId sdk.AccountObjectIdentifier, roleId sdk.AccountObjectIdentifier, warehouseId sdk.AccountObjectIdentifier, accountIdentifier sdk.AccountIdentifier, privateKey string) string { + t.Helper() + + return fmt.Sprintf(` +[%[1]s] +user = '%[2]s' +privatekey = '''%[7]s''' +role = '%[3]s' +organizationname = '%[5]s' +accountname = '%[6]s' +warehouse = '%[4]s' +authenticator = 'SNOWFLAKE_JWT' +`, profile, userId.Name(), roleId.Name(), warehouseId.Name(), accountIdentifier.OrganizationName(), accountIdentifier.AccountName(), privateKey) +} + +// TomlConfigForServiceUserWithEncryptedKey is a temporary function used to test provider configuration +func TomlConfigForServiceUserWithEncryptedKey(t *testing.T, profile string, userId sdk.AccountObjectIdentifier, roleId sdk.AccountObjectIdentifier, warehouseId sdk.AccountObjectIdentifier, accountIdentifier sdk.AccountIdentifier, privateKey string, pass string) string { + t.Helper() + + return fmt.Sprintf(` +[%[1]s] +user = '%[2]s' +privatekey = '''%[7]s''' +privatekeypassphrase = '%[8]s' +role = '%[3]s' +organizationname = '%[5]s' +accountname = '%[6]s' +warehouse = '%[4]s' +authenticator = 'SNOWFLAKE_JWT' +`, profile, userId.Name(), roleId.Name(), warehouseId.Name(), accountIdentifier.OrganizationName(), accountIdentifier.AccountName(), privateKey, pass) +} + +// TomlIncorrectConfigForServiceUser is a temporary function used to test provider configuration +func TomlIncorrectConfigForServiceUser(t *testing.T, profile string, accountIdentifier sdk.AccountIdentifier) string { + t.Helper() + + privateKey, _, _, _ := random.GenerateRSAKeyPair(t, "") + return fmt.Sprintf(` +[%[1]s] +user = 'non-existing-user' +privatekey = '''%[4]s''' +role = 'non-existing-role' +organizationname = '%[2]s' +accountname = '%[3]s' +authenticator = 'SNOWFLAKE_JWT' +`, profile, accountIdentifier.OrganizationName(), accountIdentifier.AccountName(), privateKey) +} + +// TomlConfigForLegacyServiceUser is a temporary function used to test provider configuration +func TomlConfigForLegacyServiceUser(t *testing.T, profile string, userId sdk.AccountObjectIdentifier, roleId sdk.AccountObjectIdentifier, warehouseId sdk.AccountObjectIdentifier, accountIdentifier sdk.AccountIdentifier, pass string) string { + t.Helper() + + return fmt.Sprintf(` +[%[1]s] +user = '%[2]s' +password = '%[7]s' +role = '%[3]s' +organizationname = '%[5]s' +accountname = '%[6]s' +warehouse = '%[4]s' +authenticator = 'SNOWFLAKE' +`, profile, userId.Name(), roleId.Name(), warehouseId.Name(), accountIdentifier.OrganizationName(), accountIdentifier.AccountName(), pass) +} diff --git a/pkg/acceptance/helpers/context_client.go b/pkg/acceptance/helpers/context_client.go index 6c5b4bda46..4c513135c1 100644 --- a/pkg/acceptance/helpers/context_client.go +++ b/pkg/acceptance/helpers/context_client.go @@ -33,6 +33,16 @@ func (c *ContextClient) CurrentAccount(t *testing.T) string { return currentAccount } +func (c *ContextClient) CurrentAccountId(t *testing.T) sdk.AccountIdentifier { + t.Helper() + ctx := context.Background() + + currentSessionDetails, err := c.client().CurrentSessionDetails(ctx) + require.NoError(t, err) + + return sdk.NewAccountIdentifier(currentSessionDetails.OrganizationName, currentSessionDetails.AccountName) +} + func (c *ContextClient) CurrentRole(t *testing.T) sdk.AccountObjectIdentifier { t.Helper() ctx := context.Background() diff --git a/pkg/acceptance/helpers/grant_client.go b/pkg/acceptance/helpers/grant_client.go index 4fd7791bfb..338a46969d 100644 --- a/pkg/acceptance/helpers/grant_client.go +++ b/pkg/acceptance/helpers/grant_client.go @@ -129,6 +129,72 @@ func (c *GrantClient) RevokePrivilegesOnDatabaseFromDatabaseRole( require.NoError(t, err) } +func (c *GrantClient) GrantPrivilegesOnDatabaseToAccountRole( + t *testing.T, + accountRoleId sdk.AccountObjectIdentifier, + databaseId sdk.AccountObjectIdentifier, + privileges []sdk.AccountObjectPrivilege, + withGrantOption bool, +) { + t.Helper() + c.grantPrivilegesOnAccountLevelObjectToAccountRole( + t, + accountRoleId, + &sdk.AccountRoleGrantOn{ + AccountObject: &sdk.GrantOnAccountObject{ + Database: sdk.Pointer(databaseId), + }, + }, + privileges, + withGrantOption, + ) +} + +func (c *GrantClient) GrantPrivilegesOnWarehouseToAccountRole( + t *testing.T, + accountRoleId sdk.AccountObjectIdentifier, + warehouseId sdk.AccountObjectIdentifier, + privileges []sdk.AccountObjectPrivilege, + withGrantOption bool, +) { + t.Helper() + c.grantPrivilegesOnAccountLevelObjectToAccountRole( + t, + accountRoleId, + &sdk.AccountRoleGrantOn{ + AccountObject: &sdk.GrantOnAccountObject{ + Warehouse: sdk.Pointer(warehouseId), + }, + }, + privileges, + withGrantOption, + ) +} + +func (c *GrantClient) grantPrivilegesOnAccountLevelObjectToAccountRole( + t *testing.T, + accountRoleId sdk.AccountObjectIdentifier, + accountObjectGrantOn *sdk.AccountRoleGrantOn, + privileges []sdk.AccountObjectPrivilege, + withGrantOption bool, +) { + t.Helper() + ctx := context.Background() + + err := c.client().GrantPrivilegesToAccountRole( + ctx, + &sdk.AccountRoleGrantPrivileges{ + AccountObjectPrivileges: privileges, + }, + accountObjectGrantOn, + accountRoleId, + &sdk.GrantPrivilegesToAccountRoleOptions{ + WithGrantOption: sdk.Bool(withGrantOption), + }, + ) + require.NoError(t, err) +} + func (c *GrantClient) GrantPrivilegesOnDatabaseToDatabaseRole( t *testing.T, databaseRoleId sdk.DatabaseObjectIdentifier, diff --git a/pkg/acceptance/helpers/ids_generator.go b/pkg/acceptance/helpers/ids_generator.go index 7cb9c767ec..ade93d46bc 100644 --- a/pkg/acceptance/helpers/ids_generator.go +++ b/pkg/acceptance/helpers/ids_generator.go @@ -29,6 +29,10 @@ func (c *IdsGenerator) WarehouseId() sdk.AccountObjectIdentifier { return sdk.NewAccountObjectIdentifier(c.context.warehouse) } +func (c *IdsGenerator) SnowflakeWarehouseId() sdk.AccountObjectIdentifier { + return sdk.NewAccountObjectIdentifier("SNOWFLAKE") +} + func (c *IdsGenerator) AccountIdentifierWithLocator() sdk.AccountIdentifier { return sdk.NewAccountIdentifierFromAccountLocator(c.context.client.GetAccountLocator()) } diff --git a/pkg/acceptance/helpers/random/certs.go b/pkg/acceptance/helpers/random/certs.go index e8a7697d70..f3bb1c40fb 100644 --- a/pkg/acceptance/helpers/random/certs.go +++ b/pkg/acceptance/helpers/random/certs.go @@ -47,6 +47,13 @@ func GenerateRSAPublicKey(t *testing.T) (string, string) { t.Helper() key := GenerateRSAPrivateKey(t) + return generateRSAPublicKeyFromPrivateKey(t, key) +} + +// GenerateRSAPublicKey returns an RSA public key without BEGIN and END markers, and key's hash. +func generateRSAPublicKeyFromPrivateKey(t *testing.T, key *rsa.PrivateKey) (string, string) { + t.Helper() + pub := key.Public() b, err := x509.MarshalPKIXPublicKey(pub.(*rsa.PublicKey)) require.NoError(t, err) @@ -61,6 +68,24 @@ func GenerateRSAPrivateKey(t *testing.T) *rsa.PrivateKey { return key } +// GenerateRSAKeyPair returns an RSA private key (unencrypted and encrypted), RSA public key without BEGIN and END markers, and key's hash. +func GenerateRSAKeyPair(t *testing.T, pass string) (string, string, string, string) { + t.Helper() + + privateKey := GenerateRSAPrivateKey(t) + unencryptedDer, err := x509.MarshalPKCS8PrivateKey(privateKey) + require.NoError(t, err) + privBlock := pem.Block{ + Type: "PRIVATE KEY", + Bytes: unencryptedDer, + } + unencrypted := string(pem.EncodeToMemory(&privBlock)) + encrypted := encrypt(t, privateKey, pass) + + publicKey, keyHash := generateRSAPublicKeyFromPrivateKey(t, privateKey) + return unencrypted, encrypted, publicKey, keyHash +} + // GenerateRSAPrivateKeyEncrypted returns a PEM-encoded pair of unencrypted and encrypted key with a given password func GenerateRSAPrivateKeyEncrypted(t *testing.T, password string) (unencrypted, encrypted string) { t.Helper() @@ -72,8 +97,15 @@ func GenerateRSAPrivateKeyEncrypted(t *testing.T, password string) (unencrypted, Bytes: unencryptedDer, } unencrypted = string(pem.EncodeToMemory(&privBlock)) + encrypted = encrypt(t, rsaPrivateKey, password) - encryptedDer, err := pkcs8.MarshalPrivateKey(rsaPrivateKey, []byte(password), &pkcs8.Opts{ + return +} + +func encrypt(t *testing.T, rsaPrivateKey *rsa.PrivateKey, pass string) string { + t.Helper() + + encryptedDer, err := pkcs8.MarshalPrivateKey(rsaPrivateKey, []byte(pass), &pkcs8.Opts{ Cipher: pkcs8.AES256CBC, KDFOpts: pkcs8.PBKDF2Opts{ SaltSize: 16, IterationCount: 2000, HMACHash: crypto.SHA256, @@ -84,9 +116,7 @@ func GenerateRSAPrivateKeyEncrypted(t *testing.T, password string) (unencrypted, Type: "ENCRYPTED PRIVATE KEY", Bytes: encryptedDer, } - encrypted = string(pem.EncodeToMemory(&privEncryptedBlock)) - - return + return string(pem.EncodeToMemory(&privEncryptedBlock)) } func hash(t *testing.T, b []byte) string { diff --git a/pkg/acceptance/helpers/tmp_toml_setup_helpers.go b/pkg/acceptance/helpers/tmp_toml_setup_helpers.go new file mode 100644 index 0000000000..6d4fc701ae --- /dev/null +++ b/pkg/acceptance/helpers/tmp_toml_setup_helpers.go @@ -0,0 +1,69 @@ +package helpers + +import ( + "testing" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/helpers/random" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/testhelpers" +) + +// TODO [SNOW-1827324]: add TestClient ref to each specific client, so that we enhance specific client and not the base one + +func (c *TestClient) TempTomlConfigForServiceUser(t *testing.T, serviceUser *TmpServiceUser) *TmpTomlConfig { + t.Helper() + return c.StoreTempTomlConfig(t, func(profile string) string { + return TomlConfigForServiceUser(t, profile, serviceUser.UserId, serviceUser.RoleId, serviceUser.WarehouseId, serviceUser.AccountId, serviceUser.PrivateKey) + }) +} + +func (c *TestClient) TempTomlConfigForServiceUserWithEncryptedKey(t *testing.T, serviceUser *TmpServiceUser) *TmpTomlConfig { + t.Helper() + return c.StoreTempTomlConfig(t, func(profile string) string { + return TomlConfigForServiceUserWithEncryptedKey(t, profile, serviceUser.UserId, serviceUser.RoleId, serviceUser.WarehouseId, serviceUser.AccountId, serviceUser.EncryptedPrivateKey, serviceUser.Pass) + }) +} + +func (c *TestClient) TempIncorrectTomlConfigForServiceUser(t *testing.T, serviceUser *TmpServiceUser) *TmpTomlConfig { + t.Helper() + return c.StoreTempTomlConfig(t, func(profile string) string { + return TomlIncorrectConfigForServiceUser(t, profile, serviceUser.AccountId) + }) +} + +func (c *TestClient) TempIncorrectTomlConfigForServiceUserWithEncryptedKey(t *testing.T, serviceUser *TmpServiceUser) *TmpTomlConfig { + t.Helper() + return c.StoreTempTomlConfig(t, func(profile string) string { + return TomlConfigForServiceUserWithEncryptedKey(t, profile, serviceUser.UserId, serviceUser.RoleId, serviceUser.WarehouseId, serviceUser.AccountId, serviceUser.EncryptedPrivateKey, "incorrect pass") + }) +} + +func (c *TestClient) TempTomlConfigForLegacyServiceUser(t *testing.T, legacyServiceUser *TmpLegacyServiceUser) *TmpTomlConfig { + t.Helper() + return c.StoreTempTomlConfig(t, func(profile string) string { + return TomlConfigForLegacyServiceUser(t, profile, legacyServiceUser.UserId, legacyServiceUser.RoleId, legacyServiceUser.WarehouseId, legacyServiceUser.AccountId, legacyServiceUser.Pass) + }) +} + +func (c *TestClient) TempIncorrectTomlConfigForLegacyServiceUser(t *testing.T, legacyServiceUser *TmpLegacyServiceUser) *TmpTomlConfig { + t.Helper() + return c.StoreTempTomlConfig(t, func(profile string) string { + return TomlConfigForLegacyServiceUser(t, profile, legacyServiceUser.UserId, legacyServiceUser.RoleId, legacyServiceUser.WarehouseId, legacyServiceUser.AccountId, "incorrect pass") + }) +} + +func (c *TestClient) StoreTempTomlConfig(t *testing.T, tomlProvider func(string) string) *TmpTomlConfig { + t.Helper() + + profile := random.AlphaN(6) + toml := tomlProvider(profile) + configPath := testhelpers.TestFile(t, random.AlphaN(10), []byte(toml)) + return &TmpTomlConfig{ + Profile: profile, + Path: configPath, + } +} + +type TmpTomlConfig struct { + Profile string + Path string +} diff --git a/pkg/acceptance/helpers/tmp_user_setup_helpers.go b/pkg/acceptance/helpers/tmp_user_setup_helpers.go new file mode 100644 index 0000000000..41605b5662 --- /dev/null +++ b/pkg/acceptance/helpers/tmp_user_setup_helpers.go @@ -0,0 +1,101 @@ +package helpers + +import ( + "fmt" + "testing" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/helpers/random" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" +) + +// TODO [SNOW-1827324]: add TestClient ref to each specific client, so that we enhance specific client and not the base one +// TODO [SNOW-1827324]: consider using these in other places where user is set up + +func (c *TestClient) SetUpTemporaryLegacyServiceUser(t *testing.T) *TmpLegacyServiceUser { + t.Helper() + + pass := random.Password() + tmpUser := c.setUpTmpUserWithBasicAccess(t, func(userId sdk.AccountObjectIdentifier) (*sdk.User, func()) { + return c.User.CreateUserWithOptions(t, userId, &sdk.CreateUserOptions{ObjectProperties: &sdk.UserObjectProperties{ + Type: sdk.Pointer(sdk.UserTypeLegacyService), + Password: sdk.String(pass), + }}) + }) + + return &TmpLegacyServiceUser{ + Pass: pass, + TmpUser: tmpUser, + } +} + +func (c *TestClient) SetUpTemporaryServiceUser(t *testing.T) *TmpServiceUser { + t.Helper() + + pass := random.Password() + privateKey, encryptedKey, publicKey, _ := random.GenerateRSAKeyPair(t, pass) + tmpUser := c.setUpTmpUserWithBasicAccess(t, func(userId sdk.AccountObjectIdentifier) (*sdk.User, func()) { + return c.User.CreateUserWithOptions(t, userId, &sdk.CreateUserOptions{ObjectProperties: &sdk.UserObjectProperties{ + Type: sdk.Pointer(sdk.UserTypeLegacyService), + RSAPublicKey: sdk.String(publicKey), + }}) + }) + + return &TmpServiceUser{ + PublicKey: publicKey, + PrivateKey: privateKey, + EncryptedPrivateKey: encryptedKey, + Pass: pass, + TmpUser: tmpUser, + } +} + +func (c *TestClient) setUpTmpUserWithBasicAccess(t *testing.T, userCreator func(userId sdk.AccountObjectIdentifier) (*sdk.User, func())) TmpUser { + t.Helper() + + warehouseId := c.Ids.SnowflakeWarehouseId() + accountId := c.Context.CurrentAccountId(t) + + tmpUserId := c.Ids.RandomAccountObjectIdentifier() + _, userCleanup := userCreator(tmpUserId) + t.Cleanup(userCleanup) + + tmpRole, roleCleanup := c.Role.CreateRole(t) + t.Cleanup(roleCleanup) + + tmpRoleId := tmpRole.ID() + + c.Grant.GrantPrivilegesOnDatabaseToAccountRole(t, tmpRoleId, c.Ids.DatabaseId(), []sdk.AccountObjectPrivilege{sdk.AccountObjectPrivilegeUsage}, false) + c.Grant.GrantPrivilegesOnWarehouseToAccountRole(t, tmpRoleId, warehouseId, []sdk.AccountObjectPrivilege{sdk.AccountObjectPrivilegeUsage}, false) + c.Role.GrantRoleToUser(t, tmpRoleId, tmpUserId) + + return TmpUser{ + UserId: tmpUserId, + RoleId: tmpRoleId, + WarehouseId: warehouseId, + AccountId: accountId, + } +} + +type TmpUser struct { + UserId sdk.AccountObjectIdentifier + RoleId sdk.AccountObjectIdentifier + WarehouseId sdk.AccountObjectIdentifier + AccountId sdk.AccountIdentifier +} + +func (u *TmpUser) OrgAndAccount() string { + return fmt.Sprintf("%s-%s", u.AccountId.OrganizationName(), u.AccountId.AccountName()) +} + +type TmpServiceUser struct { + PublicKey string + PrivateKey string + EncryptedPrivateKey string + Pass string + TmpUser +} + +type TmpLegacyServiceUser struct { + Pass string + TmpUser +} diff --git a/pkg/acceptance/ids/non_existing.go b/pkg/acceptance/ids/non_existing.go new file mode 100644 index 0000000000..6e1d71c6ff --- /dev/null +++ b/pkg/acceptance/ids/non_existing.go @@ -0,0 +1,8 @@ +package ids + +import "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + +// TODO [SNOW-1827335]: there are similar non existing ids in setup_test.go in integration tests, consider merging them (they use different parents - acc vs int) +var ( + NonExistingAccountObjectIdentifier = sdk.NewAccountObjectIdentifier("does_not_exist") +) diff --git a/pkg/acceptance/testprofiles/testing_config_profiles.go b/pkg/acceptance/testprofiles/testing_config_profiles.go index c671c4219c..5c04251416 100644 --- a/pkg/acceptance/testprofiles/testing_config_profiles.go +++ b/pkg/acceptance/testprofiles/testing_config_profiles.go @@ -1,16 +1,8 @@ package testprofiles const ( - Default = "default" - Secondary = "secondary_test_account" - Third = "third_test_account" - Fourth = "fourth_test_account" - IncorrectUserAndPassword = "incorrect_test_profile" - CompleteFields = "complete_fields" - CompleteFieldsInvalid = "complete_fields_invalid" - DefaultWithPasscode = "default_with_passcode" - - JwtAuth = "jwt_auth" - EncryptedJwtAuth = "encrypted_jwt_auth" - Okta = "okta" + Default = "default" + Secondary = "secondary_test_account" + Third = "third_test_account" + Fourth = "fourth_test_account" ) diff --git a/pkg/manual_tests/authentication_methods/auth_test.go b/pkg/manual_tests/authentication_methods/auth_test.go index 5089009e3a..48446fbd2a 100644 --- a/pkg/manual_tests/authentication_methods/auth_test.go +++ b/pkg/manual_tests/authentication_methods/auth_test.go @@ -5,11 +5,11 @@ import ( "testing" acc "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance" - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/testenvs" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/testprofiles" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/snowflakeenvs" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/tfversion" ) @@ -31,7 +31,7 @@ func TestAcc_Provider_OktaAuth(t *testing.T) { }, Steps: []resource.TestStep{ { - Config: providerConfigWithAuthenticator(testprofiles.Okta, sdk.AuthenticationTypeOkta), + Config: providerConfigWithAuthenticator(Okta, sdk.AuthenticationTypeOkta), }, }, }) @@ -75,11 +75,11 @@ func TestAcc_Provider_UsernamePasswordMfaAuthWithPasscode(t *testing.T) { Steps: []resource.TestStep{ // ensure MFA is checked here - accept access to keychain on your device { - Config: providerConfigWithAuthenticator(testprofiles.DefaultWithPasscode, sdk.AuthenticationTypeUsernamePasswordMfa), + Config: providerConfigWithAuthenticator(DefaultWithPasscode, sdk.AuthenticationTypeUsernamePasswordMfa), }, // check that MFA login is cached - this step should not require manual action { - Config: providerConfigWithAuthenticator(testprofiles.DefaultWithPasscode, sdk.AuthenticationTypeUsernamePasswordMfa), + Config: providerConfigWithAuthenticator(DefaultWithPasscode, sdk.AuthenticationTypeUsernamePasswordMfa), }, }, }) diff --git a/pkg/manual_tests/authentication_methods/profiles.go b/pkg/manual_tests/authentication_methods/profiles.go new file mode 100644 index 0000000000..424f7ee22e --- /dev/null +++ b/pkg/manual_tests/authentication_methods/profiles.go @@ -0,0 +1,6 @@ +package manual + +const ( + DefaultWithPasscode = "default_with_passcode" + Okta = "okta" +) diff --git a/pkg/provider/provider.go b/pkg/provider/provider.go index 94318dc77f..7e5f4c9370 100644 --- a/pkg/provider/provider.go +++ b/pkg/provider/provider.go @@ -135,8 +135,8 @@ func Provider() *schema.Provider { Type: schema.TypeString, Description: envNameFieldDescription(fmt.Sprintf("Specifies the [authentication type](https://pkg.go.dev/github.com/snowflakedb/gosnowflake#AuthType) to use when connecting to Snowflake. Valid options are: %v. Value `JWT` is deprecated and will be removed in future releases.", docs.PossibleValuesListed(sdk.AllAuthenticationTypes)), snowflakeenvs.Authenticator), Optional: true, - DefaultFunc: schema.EnvDefaultFunc(snowflakeenvs.Authenticator, string(sdk.AuthenticationTypeSnowflake)), - ValidateDiagFunc: validators.NormalizeValidation(sdk.ToAuthenticatorType), + DefaultFunc: schema.EnvDefaultFunc(snowflakeenvs.Authenticator, string(sdk.AuthenticationTypeEmpty)), + ValidateDiagFunc: validators.NormalizeValidation(sdk.ToExtendedAuthenticatorType), }, "passcode": { Type: schema.TypeString, @@ -718,13 +718,11 @@ func getDriverConfigFromTerraform(s *schema.ResourceData) (*gosnowflake.Config, handleIntAttribute(s, "port", &config.Port), // authenticator func() error { - if v, ok := s.GetOk("authenticator"); ok && v.(string) != "" { - authType, err := sdk.ToAuthenticatorType(v.(string)) - if err != nil { - return err - } - config.Authenticator = authType + authType, err := sdk.ToExtendedAuthenticatorType(s.Get("authenticator").(string)) + if err != nil { + return err } + config.Authenticator = authType return nil }(), handleStringField(s, "passcode", &config.Passcode), diff --git a/pkg/provider/provider_acceptance_test.go b/pkg/provider/provider_acceptance_test.go index 0d0daf7b67..f1ac12b158 100644 --- a/pkg/provider/provider_acceptance_test.go +++ b/pkg/provider/provider_acceptance_test.go @@ -6,35 +6,34 @@ import ( "net/url" "os" "regexp" - "strings" "testing" "time" acc "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance" - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" - "github.com/snowflakedb/gosnowflake" - "github.com/stretchr/testify/assert" + internalprovider "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/helpers" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/ids" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/testenvs" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/testprofiles" - internalprovider "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/snowflakeenvs" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/terraform" "github.com/hashicorp/terraform-plugin-testing/tfversion" + "github.com/snowflakedb/gosnowflake" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestAcc_Provider_configHierarchy(t *testing.T) { + _ = testenvs.GetOrSkipTest(t, testenvs.EnableAcceptance) + acc.TestAccPreCheck(t) t.Setenv(string(testenvs.ConfigureClientOnce), "") - user := acc.DefaultConfig(t).User - pass := acc.DefaultConfig(t).Password - account := acc.DefaultConfig(t).Account - role := acc.DefaultConfig(t).Role - host := acc.DefaultConfig(t).Host - - nonExistingUser := "non-existing-user" + tmpServiceUser := acc.TestClient().SetUpTemporaryServiceUser(t) + tmpServiceUserConfig := acc.TestClient().TempTomlConfigForServiceUser(t, tmpServiceUser) + incorrectConfig := acc.TestClient().TempIncorrectTomlConfigForServiceUser(t, tmpServiceUser) resource.Test(t, resource.TestCase{ ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, @@ -42,6 +41,7 @@ func TestAcc_Provider_configHierarchy(t *testing.T) { acc.TestAccPreCheck(t) testenvs.AssertEnvNotSet(t, snowflakeenvs.User) testenvs.AssertEnvNotSet(t, snowflakeenvs.Password) + testenvs.AssertEnvNotSet(t, snowflakeenvs.ConfigPath) }, TerraformVersionChecks: []tfversion.TerraformVersionCheck{ tfversion.RequireAbove(tfversion.Version1_5_0), @@ -49,79 +49,102 @@ func TestAcc_Provider_configHierarchy(t *testing.T) { Steps: []resource.TestStep{ // make sure that we fail for incorrect profile { - Config: providerConfig(testprofiles.IncorrectUserAndPassword), - ExpectError: regexp.MustCompile("Incorrect username or password was specified"), + PreConfig: func() { + t.Setenv(snowflakeenvs.ConfigPath, incorrectConfig.Path) + }, + Config: providerConfig(incorrectConfig.Profile), + ExpectError: regexp.MustCompile("JWT token is invalid"), + }, + // make sure that we succeed for the correct profile + { + PreConfig: func() { + t.Setenv(snowflakeenvs.ConfigPath, tmpServiceUserConfig.Path) + }, + Config: providerConfig(tmpServiceUserConfig.Profile), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.snowflake_database.t", "name", acc.TestDatabaseName), + ), }, // incorrect user in provider config should not be rewritten by profile and cause error { - Config: providerConfigWithUser(nonExistingUser, testprofiles.Default), - ExpectError: regexp.MustCompile("Incorrect username or password was specified"), + Config: providerConfigWithUserAndProfile(ids.NonExistingAccountObjectIdentifier, tmpServiceUserConfig.Profile), + ExpectError: regexp.MustCompile("JWT token is invalid"), }, - // correct user and password in provider's config should not be rewritten by a faulty config + // correct user and key in provider's config should not be rewritten by a faulty config { - Config: providerConfigWithUserAndPassword(user, pass, testprofiles.IncorrectUserAndPassword), + PreConfig: func() { + t.Setenv(snowflakeenvs.ConfigPath, incorrectConfig.Path) + }, + Config: providerConfigWithUserPrivateKeyAndProfile(tmpServiceUser.UserId, tmpServiceUser.PrivateKey, tmpServiceUser.RoleId.Name(), incorrectConfig.Profile), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("data.snowflake_database.t", "name", acc.TestDatabaseName), ), }, - // incorrect user in env variable should not be rewritten by profile and cause error + // incorrect user in env variable should not be rewritten by profile and cause error (profile authenticator is set to JWT and that's why the error is about incorrect token) { PreConfig: func() { - t.Setenv(snowflakeenvs.User, nonExistingUser) + t.Setenv(snowflakeenvs.User, ids.NonExistingAccountObjectIdentifier.Name()) + t.Setenv(snowflakeenvs.ConfigPath, tmpServiceUserConfig.Path) }, - Config: providerConfig(testprofiles.Default), - ExpectError: regexp.MustCompile("Incorrect username or password was specified"), + Config: providerConfig(tmpServiceUserConfig.Profile), + ExpectError: regexp.MustCompile("JWT token is invalid"), }, - // correct user and password in env should not be rewritten by a faulty config + // correct user and private key in env should not be rewritten by a faulty config { PreConfig: func() { - t.Setenv(snowflakeenvs.User, user) - t.Setenv(snowflakeenvs.Password, pass) + t.Setenv(snowflakeenvs.User, tmpServiceUser.UserId.Name()) + t.Setenv(snowflakeenvs.PrivateKey, tmpServiceUser.PrivateKey) + t.Setenv(snowflakeenvs.Role, tmpServiceUser.RoleId.Name()) + t.Setenv(snowflakeenvs.ConfigPath, incorrectConfig.Path) }, - Config: providerConfig(testprofiles.IncorrectUserAndPassword), + Config: providerConfigWithProfileAndAuthenticator(incorrectConfig.Profile, sdk.AuthenticationTypeJwt), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("data.snowflake_database.t", "name", acc.TestDatabaseName), ), }, // user on provider level wins (it's incorrect - env and profile ones are) { - Config: providerConfigWithUser(nonExistingUser, testprofiles.Default), - ExpectError: regexp.MustCompile("Incorrect username or password was specified"), + PreConfig: func() { + testenvs.AssertEnvSet(t, snowflakeenvs.User) + t.Setenv(snowflakeenvs.ConfigPath, tmpServiceUserConfig.Path) + }, + Config: providerConfigWithUserPrivateKeyAndProfile(ids.NonExistingAccountObjectIdentifier, tmpServiceUser.PrivateKey, tmpServiceUser.RoleId.Name(), tmpServiceUserConfig.Profile), + ExpectError: regexp.MustCompile("JWT token is invalid"), }, - // there is no config (by setting the dir to something different than .snowflake/config) + // there is no config (by setting the dir to something different from .snowflake/config) { PreConfig: func() { dir, err := os.UserHomeDir() require.NoError(t, err) t.Setenv(snowflakeenvs.ConfigPath, dir) }, - Config: providerConfigWithUserAndPassword(user, pass, testprofiles.Default), + Config: providerConfigWithUserPrivateKeyAndProfile(tmpServiceUser.UserId, tmpServiceUser.PrivateKey, tmpServiceUser.RoleId.Name(), testprofiles.Default), ExpectError: regexp.MustCompile("account is empty"), }, // provider's config should not be rewritten by env when there is no profile (incorrect user in config versus correct one in env) - proves #2242 { PreConfig: func() { testenvs.AssertEnvSet(t, snowflakeenvs.ConfigPath) - t.Setenv(snowflakeenvs.User, user) - t.Setenv(snowflakeenvs.Password, pass) - t.Setenv(snowflakeenvs.Account, account) - t.Setenv(snowflakeenvs.Role, role) - t.Setenv(snowflakeenvs.Host, host) + t.Setenv(snowflakeenvs.User, tmpServiceUser.UserId.Name()) + t.Setenv(snowflakeenvs.PrivateKey, tmpServiceUser.PrivateKey) + t.Setenv(snowflakeenvs.AccountName, tmpServiceUser.AccountId.AccountName()) + t.Setenv(snowflakeenvs.OrganizationName, tmpServiceUser.AccountId.OrganizationName()) + t.Setenv(snowflakeenvs.Role, tmpServiceUser.RoleId.Name()) }, - Config: providerConfigWithUser(nonExistingUser, testprofiles.Default), - ExpectError: regexp.MustCompile("Incorrect username or password was specified"), + Config: providerConfigWithUserAndProfile(ids.NonExistingAccountObjectIdentifier, testprofiles.Default), + ExpectError: regexp.MustCompile("JWT token is invalid"), }, // make sure the teardown is fine by using a correct env config at the end { PreConfig: func() { testenvs.AssertEnvSet(t, snowflakeenvs.ConfigPath) testenvs.AssertEnvSet(t, snowflakeenvs.User) - testenvs.AssertEnvSet(t, snowflakeenvs.Password) - testenvs.AssertEnvSet(t, snowflakeenvs.Account) + testenvs.AssertEnvSet(t, snowflakeenvs.PrivateKey) + testenvs.AssertEnvSet(t, snowflakeenvs.AccountName) + testenvs.AssertEnvSet(t, snowflakeenvs.OrganizationName) testenvs.AssertEnvSet(t, snowflakeenvs.Role) - testenvs.AssertEnvSet(t, snowflakeenvs.Host) }, - Config: emptyProviderConfig(), + Config: providerConfigWithAuthenticator(sdk.AuthenticationTypeJwt), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("data.snowflake_database.t", "name", acc.TestDatabaseName), ), @@ -131,8 +154,14 @@ func TestAcc_Provider_configHierarchy(t *testing.T) { } func TestAcc_Provider_configureClientOnceSwitching(t *testing.T) { + _ = testenvs.GetOrSkipTest(t, testenvs.EnableAcceptance) + acc.TestAccPreCheck(t) t.Setenv(string(testenvs.ConfigureClientOnce), "") + tmpServiceUser := acc.TestClient().SetUpTemporaryServiceUser(t) + tmpServiceUserConfig := acc.TestClient().TempTomlConfigForServiceUser(t, tmpServiceUser) + incorrectConfig := acc.TestClient().TempIncorrectTomlConfigForServiceUser(t, tmpServiceUser) + resource.Test(t, resource.TestCase{ ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, PreCheck: func() { @@ -146,15 +175,19 @@ func TestAcc_Provider_configureClientOnceSwitching(t *testing.T) { Steps: []resource.TestStep{ // client setup is incorrect { - Config: providerConfig(testprofiles.IncorrectUserAndPassword), - ExpectError: regexp.MustCompile("Incorrect username or password was specified"), + PreConfig: func() { + t.Setenv(snowflakeenvs.ConfigPath, incorrectConfig.Path) + }, + Config: providerConfig(incorrectConfig.Profile), + ExpectError: regexp.MustCompile("JWT token is invalid"), }, // in this step we simulate the situation when we want to use client configured once, but it was faulty last time { PreConfig: func() { t.Setenv(string(testenvs.ConfigureClientOnce), "true") + t.Setenv(snowflakeenvs.ConfigPath, tmpServiceUserConfig.Path) }, - Config: emptyProviderConfig(), + Config: providerConfig(tmpServiceUserConfig.Profile), Check: resource.ComposeTestCheckFunc( resource.TestCheckResourceAttr("data.snowflake_database.t", "name", acc.TestDatabaseName), ), @@ -164,11 +197,14 @@ func TestAcc_Provider_configureClientOnceSwitching(t *testing.T) { } func TestAcc_Provider_tomlConfig(t *testing.T) { + _ = testenvs.GetOrSkipTest(t, testenvs.EnableAcceptance) + acc.TestAccPreCheck(t) t.Setenv(string(testenvs.ConfigureClientOnce), "") - user := acc.DefaultConfig(t).User - pass := acc.DefaultConfig(t).Password - account := acc.DefaultConfig(t).Account + tmpServiceUser := acc.TestClient().SetUpTemporaryServiceUser(t) + tmpServiceUserConfig := acc.TestClient().StoreTempTomlConfig(t, func(profile string) string { + return helpers.FullTomlConfigForServiceUser(t, profile, tmpServiceUser.UserId, tmpServiceUser.RoleId, tmpServiceUser.WarehouseId, tmpServiceUser.AccountId, tmpServiceUser.PrivateKey) + }) oktaUrl, err := url.Parse("https://example.com") require.NoError(t, err) @@ -179,54 +215,54 @@ func TestAcc_Provider_tomlConfig(t *testing.T) { acc.TestAccPreCheck(t) testenvs.AssertEnvNotSet(t, snowflakeenvs.User) testenvs.AssertEnvNotSet(t, snowflakeenvs.Password) + testenvs.AssertEnvNotSet(t, snowflakeenvs.ConfigPath) + + t.Setenv(snowflakeenvs.ConfigPath, tmpServiceUserConfig.Path) }, TerraformVersionChecks: []tfversion.TerraformVersionCheck{ tfversion.RequireAbove(tfversion.Version1_5_0), }, Steps: []resource.TestStep{ { - Config: providerConfig(testprofiles.CompleteFields), + Config: providerConfig(tmpServiceUserConfig.Profile), Check: func(s *terraform.State) error { config := acc.TestAccProvider.Meta().(*internalprovider.Context).Client.GetConfig() - assert.Equal(t, &gosnowflake.Config{ - Account: account, - User: user, - Password: pass, - Warehouse: "SNOWFLAKE", - Role: "ACCOUNTADMIN", - ValidateDefaultParameters: gosnowflake.ConfigBoolTrue, - ClientIP: net.ParseIP("1.2.3.4"), - Protocol: "https", - Host: fmt.Sprintf("%s.snowflakecomputing.com", account), - Params: map[string]*string{ - "foo": sdk.Pointer("bar"), - }, - Port: 443, - Authenticator: gosnowflake.AuthTypeSnowflake, - PasscodeInPassword: false, - OktaURL: oktaUrl, - LoginTimeout: 30 * time.Second, - RequestTimeout: 40 * time.Second, - JWTExpireTimeout: 50 * time.Second, - ClientTimeout: 10 * time.Second, - JWTClientTimeout: 20 * time.Second, - ExternalBrowserTimeout: 60 * time.Second, - MaxRetryCount: 1, - Application: "terraform-provider-snowflake", - InsecureMode: true, - OCSPFailOpen: gosnowflake.OCSPFailOpenTrue, - Token: "token", - KeepSessionAlive: true, - DisableTelemetry: true, - Tracing: string(sdk.DriverLogLevelInfo), - TmpDirPath: ".", - ClientRequestMfaToken: gosnowflake.ConfigBoolTrue, - ClientStoreTemporaryCredential: gosnowflake.ConfigBoolTrue, - DisableQueryContextCache: true, - IncludeRetryReason: gosnowflake.ConfigBoolTrue, - DisableConsoleLogin: gosnowflake.ConfigBoolTrue, - }, config) - assert.Equal(t, string(sdk.DriverLogLevelInfo), gosnowflake.GetLogger().GetLogLevel()) + assert.Equal(t, tmpServiceUser.OrgAndAccount(), config.Account) + assert.Equal(t, tmpServiceUser.UserId.Name(), config.User) + assert.Equal(t, tmpServiceUser.WarehouseId.Name(), config.Warehouse) + assert.Equal(t, tmpServiceUser.RoleId.Name(), config.Role) + assert.Equal(t, gosnowflake.ConfigBoolTrue, config.ValidateDefaultParameters) + assert.Equal(t, net.ParseIP("1.2.3.4"), config.ClientIP) + assert.Equal(t, "https", config.Protocol) + assert.Equal(t, fmt.Sprintf("%s.snowflakecomputing.com", tmpServiceUser.OrgAndAccount()), config.Host) + assert.Equal(t, 443, config.Port) + assert.Equal(t, gosnowflake.AuthTypeJwt, config.Authenticator) + assert.Equal(t, false, config.PasscodeInPassword) + assert.Equal(t, oktaUrl, config.OktaURL) + assert.Equal(t, 30*time.Second, config.LoginTimeout) + assert.Equal(t, 40*time.Second, config.RequestTimeout) + assert.Equal(t, 50*time.Second, config.JWTExpireTimeout) + assert.Equal(t, 10*time.Second, config.ClientTimeout) + assert.Equal(t, 20*time.Second, config.JWTClientTimeout) + assert.Equal(t, 60*time.Second, config.ExternalBrowserTimeout) + assert.Equal(t, 1, config.MaxRetryCount) + assert.Equal(t, "terraform-provider-snowflake", config.Application) + assert.Equal(t, true, config.InsecureMode) + assert.Equal(t, gosnowflake.OCSPFailOpenTrue, config.OCSPFailOpen) + assert.Equal(t, "token", config.Token) + assert.Equal(t, true, config.KeepSessionAlive) + assert.Equal(t, true, config.DisableTelemetry) + assert.Equal(t, string(sdk.DriverLogLevelWarning), config.Tracing) + assert.Equal(t, ".", config.TmpDirPath) + assert.Equal(t, gosnowflake.ConfigBoolTrue, config.ClientRequestMfaToken) + assert.Equal(t, gosnowflake.ConfigBoolTrue, config.ClientStoreTemporaryCredential) + assert.Equal(t, true, config.DisableQueryContextCache) + assert.Equal(t, gosnowflake.ConfigBoolTrue, config.IncludeRetryReason) + assert.Equal(t, gosnowflake.ConfigBoolTrue, config.DisableConsoleLogin) + assert.Equal(t, map[string]*string{ + "foo": sdk.Pointer("bar"), + }, config.Params) + assert.Equal(t, string(sdk.DriverLogLevelWarning), gosnowflake.GetLogger().GetLogLevel()) return nil }, @@ -236,13 +272,14 @@ func TestAcc_Provider_tomlConfig(t *testing.T) { } func TestAcc_Provider_envConfig(t *testing.T) { + _ = testenvs.GetOrSkipTest(t, testenvs.EnableAcceptance) + acc.TestAccPreCheck(t) t.Setenv(string(testenvs.ConfigureClientOnce), "") - user := acc.DefaultConfig(t).User - pass := acc.DefaultConfig(t).Password - account := acc.DefaultConfig(t).Account - - accountParts := strings.SplitN(account, "-", 2) + tmpServiceUser := acc.TestClient().SetUpTemporaryServiceUser(t) + tmpServiceUserConfig := acc.TestClient().StoreTempTomlConfig(t, func(profile string) string { + return helpers.FullInvalidTomlConfigForServiceUser(t, profile) + }) oktaUrlFromEnv, err := url.Parse("https://example-env.com") require.NoError(t, err) @@ -250,9 +287,12 @@ func TestAcc_Provider_envConfig(t *testing.T) { resource.Test(t, resource.TestCase{ ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, PreCheck: func() { - acc.TestAccPreCheck(t) testenvs.AssertEnvNotSet(t, snowflakeenvs.User) testenvs.AssertEnvNotSet(t, snowflakeenvs.Password) + testenvs.AssertEnvNotSet(t, snowflakeenvs.Account) + testenvs.AssertEnvNotSet(t, snowflakeenvs.ConfigPath) + + t.Setenv(snowflakeenvs.ConfigPath, tmpServiceUserConfig.Path) }, TerraformVersionChecks: []tfversion.TerraformVersionCheck{ tfversion.RequireAbove(tfversion.Version1_5_0), @@ -260,20 +300,19 @@ func TestAcc_Provider_envConfig(t *testing.T) { Steps: []resource.TestStep{ { PreConfig: func() { - t.Setenv(snowflakeenvs.AccountName, accountParts[1]) - t.Setenv(snowflakeenvs.OrganizationName, accountParts[0]) - t.Setenv(snowflakeenvs.User, user) - t.Setenv(snowflakeenvs.Password, pass) - t.Setenv(snowflakeenvs.Warehouse, "SNOWFLAKE") + t.Setenv(snowflakeenvs.AccountName, tmpServiceUser.AccountId.AccountName()) + t.Setenv(snowflakeenvs.OrganizationName, tmpServiceUser.AccountId.OrganizationName()) + t.Setenv(snowflakeenvs.User, tmpServiceUser.UserId.Name()) + t.Setenv(snowflakeenvs.PrivateKey, tmpServiceUser.PrivateKey) + t.Setenv(snowflakeenvs.Warehouse, tmpServiceUser.WarehouseId.Name()) t.Setenv(snowflakeenvs.Protocol, "https") t.Setenv(snowflakeenvs.Port, "443") // do not set token - it should be propagated from TOML - t.Setenv(snowflakeenvs.Role, "ACCOUNTADMIN") - t.Setenv(snowflakeenvs.Authenticator, "snowflake") - t.Setenv(snowflakeenvs.ValidateDefaultParameters, "false") + t.Setenv(snowflakeenvs.Role, tmpServiceUser.RoleId.Name()) + t.Setenv(snowflakeenvs.Authenticator, "SNOWFLAKE_JWT") + t.Setenv(snowflakeenvs.ValidateDefaultParameters, "true") t.Setenv(snowflakeenvs.ClientIp, "2.2.2.2") t.Setenv(snowflakeenvs.Host, "") - t.Setenv(snowflakeenvs.Authenticator, "") t.Setenv(snowflakeenvs.Passcode, "") t.Setenv(snowflakeenvs.PasscodeInPassword, "false") t.Setenv(snowflakeenvs.OktaUrl, "https://example-env.com") @@ -292,52 +331,50 @@ func TestAcc_Provider_envConfig(t *testing.T) { t.Setenv(snowflakeenvs.DisableQueryContextCache, "false") t.Setenv(snowflakeenvs.IncludeRetryReason, "false") t.Setenv(snowflakeenvs.MaxRetryCount, "2") - t.Setenv(snowflakeenvs.DriverTracing, string(sdk.DriverLogLevelDebug)) + t.Setenv(snowflakeenvs.DriverTracing, string(sdk.DriverLogLevelWarning)) t.Setenv(snowflakeenvs.TmpDirectoryPath, "../") t.Setenv(snowflakeenvs.DisableConsoleLogin, "false") }, - Config: providerConfig(testprofiles.CompleteFieldsInvalid), + Config: providerConfig(tmpServiceUserConfig.Profile), Check: func(s *terraform.State) error { config := acc.TestAccProvider.Meta().(*internalprovider.Context).Client.GetConfig() - assert.Equal(t, &gosnowflake.Config{ - Account: account, - User: user, - Password: pass, - Warehouse: "SNOWFLAKE", - Role: "ACCOUNTADMIN", - ValidateDefaultParameters: gosnowflake.ConfigBoolFalse, - ClientIP: net.ParseIP("2.2.2.2"), - Protocol: "https", - Params: map[string]*string{ - "foo": sdk.Pointer("bar"), - }, - Host: fmt.Sprintf("%s.snowflakecomputing.com", account), - Port: 443, - Authenticator: gosnowflake.AuthTypeSnowflake, - PasscodeInPassword: false, - OktaURL: oktaUrlFromEnv, - LoginTimeout: 100 * time.Second, - RequestTimeout: 200 * time.Second, - JWTExpireTimeout: 300 * time.Second, - ClientTimeout: 400 * time.Second, - JWTClientTimeout: 500 * time.Second, - ExternalBrowserTimeout: 600 * time.Second, - MaxRetryCount: 2, - Application: "terraform-provider-snowflake", - InsecureMode: true, - OCSPFailOpen: gosnowflake.OCSPFailOpenFalse, - Token: "token", - KeepSessionAlive: true, - DisableTelemetry: true, - Tracing: string(sdk.DriverLogLevelDebug), - TmpDirPath: "../", - ClientRequestMfaToken: gosnowflake.ConfigBoolFalse, - ClientStoreTemporaryCredential: gosnowflake.ConfigBoolFalse, - DisableQueryContextCache: true, - IncludeRetryReason: gosnowflake.ConfigBoolFalse, - DisableConsoleLogin: gosnowflake.ConfigBoolFalse, - }, config) - assert.Equal(t, string(sdk.DriverLogLevelDebug), gosnowflake.GetLogger().GetLogLevel()) + + assert.Equal(t, tmpServiceUser.OrgAndAccount(), config.Account) + assert.Equal(t, tmpServiceUser.UserId.Name(), config.User) + assert.Equal(t, tmpServiceUser.WarehouseId.Name(), config.Warehouse) + assert.Equal(t, tmpServiceUser.RoleId.Name(), config.Role) + assert.Equal(t, gosnowflake.ConfigBoolTrue, config.ValidateDefaultParameters) + assert.Equal(t, net.ParseIP("2.2.2.2"), config.ClientIP) + assert.Equal(t, "https", config.Protocol) + assert.Equal(t, fmt.Sprintf("%s.snowflakecomputing.com", tmpServiceUser.OrgAndAccount()), config.Host) + assert.Equal(t, 443, config.Port) + assert.Equal(t, gosnowflake.AuthTypeJwt, config.Authenticator) + assert.Equal(t, false, config.PasscodeInPassword) + assert.Equal(t, oktaUrlFromEnv, config.OktaURL) + assert.Equal(t, 100*time.Second, config.LoginTimeout) + assert.Equal(t, 200*time.Second, config.RequestTimeout) + assert.Equal(t, 300*time.Second, config.JWTExpireTimeout) + assert.Equal(t, 400*time.Second, config.ClientTimeout) + assert.Equal(t, 500*time.Second, config.JWTClientTimeout) + assert.Equal(t, 600*time.Second, config.ExternalBrowserTimeout) + assert.Equal(t, 2, config.MaxRetryCount) + assert.Equal(t, "terraform-provider-snowflake", config.Application) + assert.Equal(t, true, config.InsecureMode) + assert.Equal(t, gosnowflake.OCSPFailOpenFalse, config.OCSPFailOpen) + assert.Equal(t, "token", config.Token) + assert.Equal(t, true, config.KeepSessionAlive) + assert.Equal(t, true, config.DisableTelemetry) + assert.Equal(t, string(sdk.DriverLogLevelWarning), config.Tracing) + assert.Equal(t, "../", config.TmpDirPath) + assert.Equal(t, gosnowflake.ConfigBoolFalse, config.ClientRequestMfaToken) + assert.Equal(t, gosnowflake.ConfigBoolFalse, config.ClientStoreTemporaryCredential) + assert.Equal(t, true, config.DisableQueryContextCache) + assert.Equal(t, gosnowflake.ConfigBoolFalse, config.IncludeRetryReason) + assert.Equal(t, gosnowflake.ConfigBoolFalse, config.DisableConsoleLogin) + assert.Equal(t, map[string]*string{ + "foo": sdk.Pointer("bar"), + }, config.Params) + assert.Equal(t, string(sdk.DriverLogLevelWarning), gosnowflake.GetLogger().GetLogLevel()) return nil }, @@ -347,14 +384,14 @@ func TestAcc_Provider_envConfig(t *testing.T) { } func TestAcc_Provider_tfConfig(t *testing.T) { + _ = testenvs.GetOrSkipTest(t, testenvs.EnableAcceptance) + acc.TestAccPreCheck(t) t.Setenv(string(testenvs.ConfigureClientOnce), "") - user := acc.DefaultConfig(t).User - pass := acc.DefaultConfig(t).Password - account := acc.DefaultConfig(t).Account - - accountParts := strings.SplitN(account, "-", 2) - orgName, accountName := accountParts[0], accountParts[1] + tmpServiceUser := acc.TestClient().SetUpTemporaryServiceUser(t) + tmpServiceUserConfig := acc.TestClient().StoreTempTomlConfig(t, func(profile string) string { + return helpers.FullInvalidTomlConfigForServiceUser(t, profile) + }) oktaUrlFromTf, err := url.Parse("https://example-tf.com") require.NoError(t, err) @@ -365,6 +402,9 @@ func TestAcc_Provider_tfConfig(t *testing.T) { acc.TestAccPreCheck(t) testenvs.AssertEnvNotSet(t, snowflakeenvs.User) testenvs.AssertEnvNotSet(t, snowflakeenvs.Password) + testenvs.AssertEnvNotSet(t, snowflakeenvs.ConfigPath) + + t.Setenv(snowflakeenvs.ConfigPath, tmpServiceUserConfig.Path) }, TerraformVersionChecks: []tfversion.TerraformVersionCheck{ tfversion.RequireAbove(tfversion.Version1_5_0), @@ -375,7 +415,7 @@ func TestAcc_Provider_tfConfig(t *testing.T) { t.Setenv(snowflakeenvs.OrganizationName, "invalid") t.Setenv(snowflakeenvs.AccountName, "invalid") t.Setenv(snowflakeenvs.User, "invalid") - t.Setenv(snowflakeenvs.Password, "invalid") + t.Setenv(snowflakeenvs.PrivateKey, "invalid") t.Setenv(snowflakeenvs.Warehouse, "invalid") t.Setenv(snowflakeenvs.Protocol, "invalid") t.Setenv(snowflakeenvs.Port, "-1") @@ -403,52 +443,50 @@ func TestAcc_Provider_tfConfig(t *testing.T) { t.Setenv(snowflakeenvs.DisableQueryContextCache, "false") t.Setenv(snowflakeenvs.IncludeRetryReason, "false") t.Setenv(snowflakeenvs.MaxRetryCount, "2") - t.Setenv(snowflakeenvs.DriverTracing, string(sdk.DriverLogLevelDebug)) + t.Setenv(snowflakeenvs.DriverTracing, "invalid") t.Setenv(snowflakeenvs.TmpDirectoryPath, "../") t.Setenv(snowflakeenvs.DisableConsoleLogin, "false") }, - Config: providerConfigAllFields(testprofiles.CompleteFieldsInvalid, orgName, accountName, user, pass), + Config: providerConfigAllFields(tmpServiceUserConfig, tmpServiceUser), Check: func(s *terraform.State) error { config := acc.TestAccProvider.Meta().(*internalprovider.Context).Client.GetConfig() - assert.Equal(t, &gosnowflake.Config{ - Account: account, - User: user, - Password: pass, - Warehouse: "SNOWFLAKE", - Role: "ACCOUNTADMIN", - ValidateDefaultParameters: gosnowflake.ConfigBoolTrue, - ClientIP: net.ParseIP("3.3.3.3"), - Protocol: "https", - Params: map[string]*string{ - "foo": sdk.Pointer("piyo"), - }, - Host: fmt.Sprintf("%s.snowflakecomputing.com", account), - Port: 443, - Authenticator: gosnowflake.AuthTypeSnowflake, - PasscodeInPassword: false, - OktaURL: oktaUrlFromTf, - LoginTimeout: 101 * time.Second, - RequestTimeout: 201 * time.Second, - JWTExpireTimeout: 301 * time.Second, - ClientTimeout: 401 * time.Second, - JWTClientTimeout: 501 * time.Second, - ExternalBrowserTimeout: 601 * time.Second, - MaxRetryCount: 3, - Application: "terraform-provider-snowflake", - InsecureMode: true, - OCSPFailOpen: gosnowflake.OCSPFailOpenTrue, - Token: "token", - KeepSessionAlive: true, - DisableTelemetry: true, - Tracing: string(sdk.DriverLogLevelInfo), - TmpDirPath: "../../", - ClientRequestMfaToken: gosnowflake.ConfigBoolTrue, - ClientStoreTemporaryCredential: gosnowflake.ConfigBoolTrue, - DisableQueryContextCache: true, - IncludeRetryReason: gosnowflake.ConfigBoolTrue, - DisableConsoleLogin: gosnowflake.ConfigBoolTrue, - }, config) - assert.Equal(t, string(sdk.DriverLogLevelInfo), gosnowflake.GetLogger().GetLogLevel()) + + assert.Equal(t, tmpServiceUser.OrgAndAccount(), config.Account) + assert.Equal(t, tmpServiceUser.UserId.Name(), config.User) + assert.Equal(t, tmpServiceUser.WarehouseId.Name(), config.Warehouse) + assert.Equal(t, tmpServiceUser.RoleId.Name(), config.Role) + assert.Equal(t, gosnowflake.ConfigBoolTrue, config.ValidateDefaultParameters) + assert.Equal(t, net.ParseIP("3.3.3.3"), config.ClientIP) + assert.Equal(t, "https", config.Protocol) + assert.Equal(t, fmt.Sprintf("%s.snowflakecomputing.com", tmpServiceUser.OrgAndAccount()), config.Host) + assert.Equal(t, 443, config.Port) + assert.Equal(t, gosnowflake.AuthTypeJwt, config.Authenticator) + assert.Equal(t, false, config.PasscodeInPassword) + assert.Equal(t, oktaUrlFromTf, config.OktaURL) + assert.Equal(t, 101*time.Second, config.LoginTimeout) + assert.Equal(t, 201*time.Second, config.RequestTimeout) + assert.Equal(t, 301*time.Second, config.JWTExpireTimeout) + assert.Equal(t, 401*time.Second, config.ClientTimeout) + assert.Equal(t, 501*time.Second, config.JWTClientTimeout) + assert.Equal(t, 601*time.Second, config.ExternalBrowserTimeout) + assert.Equal(t, 3, config.MaxRetryCount) + assert.Equal(t, "terraform-provider-snowflake", config.Application) + assert.Equal(t, true, config.InsecureMode) + assert.Equal(t, gosnowflake.OCSPFailOpenTrue, config.OCSPFailOpen) + assert.Equal(t, "token", config.Token) + assert.Equal(t, true, config.KeepSessionAlive) + assert.Equal(t, true, config.DisableTelemetry) + assert.Equal(t, string(sdk.DriverLogLevelWarning), config.Tracing) + assert.Equal(t, "../../", config.TmpDirPath) + assert.Equal(t, gosnowflake.ConfigBoolTrue, config.ClientRequestMfaToken) + assert.Equal(t, gosnowflake.ConfigBoolTrue, config.ClientStoreTemporaryCredential) + assert.Equal(t, true, config.DisableQueryContextCache) + assert.Equal(t, gosnowflake.ConfigBoolTrue, config.IncludeRetryReason) + assert.Equal(t, gosnowflake.ConfigBoolTrue, config.DisableConsoleLogin) + assert.Equal(t, map[string]*string{ + "foo": sdk.Pointer("piyo"), + }, config.Params) + assert.Equal(t, string(sdk.DriverLogLevelWarning), gosnowflake.GetLogger().GetLogLevel()) return nil }, @@ -458,8 +496,13 @@ func TestAcc_Provider_tfConfig(t *testing.T) { } func TestAcc_Provider_useNonExistentDefaultParams(t *testing.T) { + _ = testenvs.GetOrSkipTest(t, testenvs.EnableAcceptance) + acc.TestAccPreCheck(t) t.Setenv(string(testenvs.ConfigureClientOnce), "") + tmpServiceUser := acc.TestClient().SetUpTemporaryServiceUser(t) + tmpServiceUserConfig := acc.TestClient().TempTomlConfigForServiceUser(t, tmpServiceUser) + nonExisting := "NON-EXISTENT" resource.Test(t, resource.TestCase{ @@ -468,22 +511,25 @@ func TestAcc_Provider_useNonExistentDefaultParams(t *testing.T) { acc.TestAccPreCheck(t) testenvs.AssertEnvNotSet(t, snowflakeenvs.User) testenvs.AssertEnvNotSet(t, snowflakeenvs.Password) + testenvs.AssertEnvNotSet(t, snowflakeenvs.ConfigPath) + + t.Setenv(snowflakeenvs.ConfigPath, tmpServiceUserConfig.Path) }, TerraformVersionChecks: []tfversion.TerraformVersionCheck{ tfversion.RequireAbove(tfversion.Version1_5_0), }, Steps: []resource.TestStep{ { - Config: providerConfigWithRole(testprofiles.Default, nonExisting), + Config: providerConfigWithExplicitValidationAndRole(tmpServiceUserConfig.Profile, nonExisting, true), ExpectError: regexp.MustCompile("Role 'NON-EXISTENT' specified in the connect string does not exist or not authorized."), }, { - Config: providerConfigWithWarehouse(testprofiles.Default, nonExisting), + Config: providerConfigWithExplicitValidationAndWarehouse(tmpServiceUserConfig.Profile, nonExisting, true), ExpectError: regexp.MustCompile("The requested warehouse does not exist or not authorized."), }, // check that using a non-existing warehouse with disabled verification succeeds { - Config: providerConfigWithWarehouseAndDisabledValidation(testprofiles.Default, nonExisting), + Config: providerConfigWithExplicitValidationAndWarehouse(tmpServiceUserConfig.Profile, nonExisting, false), }, }, }) @@ -491,8 +537,13 @@ func TestAcc_Provider_useNonExistentDefaultParams(t *testing.T) { // prove we can use tri-value booleans, similarly to the ones in resources func TestAcc_Provider_triValueBoolean(t *testing.T) { + _ = testenvs.GetOrSkipTest(t, testenvs.EnableAcceptance) + acc.TestAccPreCheck(t) t.Setenv(string(testenvs.ConfigureClientOnce), "") + tmpServiceUser := acc.TestClient().SetUpTemporaryServiceUser(t) + tmpServiceUserConfig := acc.TestClient().TempTomlConfigForServiceUser(t, tmpServiceUser) + resource.Test(t, resource.TestCase{ PreCheck: func() { acc.TestAccPreCheck(t) @@ -509,24 +560,28 @@ func TestAcc_Provider_triValueBoolean(t *testing.T) { Config: providerConfigWithClientStoreTemporaryCredential(testprofiles.Default, `true`), }, { - // Use the default TOML config again. PreConfig: func() { - t.Setenv(snowflakeenvs.ConfigPath, "") + t.Setenv(snowflakeenvs.ConfigPath, tmpServiceUserConfig.Path) }, ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, - Config: providerConfigWithClientStoreTemporaryCredential(testprofiles.Default, `true`), + Config: providerConfigWithClientStoreTemporaryCredential(tmpServiceUserConfig.Profile, `true`), }, { ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, - Config: providerConfigWithClientStoreTemporaryCredential(testprofiles.Default, `"true"`), + Config: providerConfigWithClientStoreTemporaryCredential(tmpServiceUserConfig.Profile, `"true"`), }, }, }) } func TestAcc_Provider_sessionParameters(t *testing.T) { + _ = testenvs.GetOrSkipTest(t, testenvs.EnableAcceptance) + acc.TestAccPreCheck(t) t.Setenv(string(testenvs.ConfigureClientOnce), "") + tmpServiceUser := acc.TestClient().SetUpTemporaryServiceUser(t) + tmpServiceUserConfig := acc.TestClient().TempTomlConfigForServiceUser(t, tmpServiceUser) + resource.Test(t, resource.TestCase{ ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, PreCheck: func() { @@ -539,7 +594,10 @@ func TestAcc_Provider_sessionParameters(t *testing.T) { }, Steps: []resource.TestStep{ { - Config: providerWithParamsConfig(testprofiles.Default, 31337), + PreConfig: func() { + t.Setenv(snowflakeenvs.ConfigPath, tmpServiceUserConfig.Path) + }, + Config: providerWithParamsConfig(tmpServiceUserConfig.Profile, 31337), Check: resource.ComposeAggregateTestCheckFunc( resource.TestCheckResourceAttr("snowflake_unsafe_execute.t", "query_results.#", "1"), resource.TestCheckResourceAttr("snowflake_unsafe_execute.t", "query_results.0.value", "31337"), @@ -550,8 +608,16 @@ func TestAcc_Provider_sessionParameters(t *testing.T) { } func TestAcc_Provider_JwtAuth(t *testing.T) { + _ = testenvs.GetOrSkipTest(t, testenvs.EnableAcceptance) + acc.TestAccPreCheck(t) t.Setenv(string(testenvs.ConfigureClientOnce), "") + tmpServiceUser := acc.TestClient().SetUpTemporaryServiceUser(t) + tmpServiceUserConfig := acc.TestClient().TempTomlConfigForServiceUser(t, tmpServiceUser) + tmpIncorrectServiceUserConfig := acc.TestClient().TempIncorrectTomlConfigForServiceUser(t, tmpServiceUser) + tmpServiceUserWithEncryptedKeyConfig := acc.TestClient().TempTomlConfigForServiceUserWithEncryptedKey(t, tmpServiceUser) + tmpIncorrectServiceUserWithEncryptedKeyConfig := acc.TestClient().TempIncorrectTomlConfigForServiceUserWithEncryptedKey(t, tmpServiceUser) + resource.Test(t, resource.TestCase{ ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, PreCheck: func() { @@ -563,91 +629,135 @@ func TestAcc_Provider_JwtAuth(t *testing.T) { tfversion.RequireAbove(tfversion.Version1_5_0), }, Steps: []resource.TestStep{ + // authenticate with incorrect private key + { + PreConfig: func() { + t.Setenv(snowflakeenvs.ConfigPath, tmpIncorrectServiceUserConfig.Path) + }, + Config: providerConfigWithProfileAndAuthenticator(tmpIncorrectServiceUserConfig.Profile, sdk.AuthenticationTypeJwt), + ExpectError: regexp.MustCompile("JWT token is invalid"), + }, // authenticate with unencrypted private key { - Config: providerConfigWithAuthenticator(testprofiles.JwtAuth, sdk.AuthenticationTypeJwt), + PreConfig: func() { + t.Setenv(snowflakeenvs.ConfigPath, tmpServiceUserConfig.Path) + }, + Config: providerConfigWithProfileAndAuthenticator(tmpServiceUserConfig.Profile, sdk.AuthenticationTypeJwt), }, // authenticate with unencrypted private key with a legacy authenticator value // solves https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2983 { - Config: providerConfigWithAuthenticator(testprofiles.JwtAuth, sdk.AuthenticationTypeJwtLegacy), + Config: providerConfigWithProfileAndAuthenticator(tmpServiceUserConfig.Profile, sdk.AuthenticationTypeJwtLegacy), + }, + // check encrypted private key with incorrect password + { + PreConfig: func() { + t.Setenv(snowflakeenvs.ConfigPath, tmpIncorrectServiceUserWithEncryptedKeyConfig.Path) + }, + Config: providerConfigWithProfileAndAuthenticator(tmpIncorrectServiceUserWithEncryptedKeyConfig.Profile, sdk.AuthenticationTypeJwt), + ExpectError: regexp.MustCompile("pkcs8: incorrect password"), }, // authenticate with encrypted private key { - Config: providerConfigWithAuthenticator(testprofiles.EncryptedJwtAuth, sdk.AuthenticationTypeJwt), + PreConfig: func() { + t.Setenv(snowflakeenvs.ConfigPath, tmpServiceUserWithEncryptedKeyConfig.Path) + }, + Config: providerConfigWithProfileAndAuthenticator(tmpServiceUserWithEncryptedKeyConfig.Profile, sdk.AuthenticationTypeJwt), }, }, }) } func TestAcc_Provider_SnowflakeAuth(t *testing.T) { + _ = testenvs.GetOrSkipTest(t, testenvs.EnableAcceptance) + acc.TestAccPreCheck(t) t.Setenv(string(testenvs.ConfigureClientOnce), "") + tmpLegacyServiceUser := acc.TestClient().SetUpTemporaryLegacyServiceUser(t) + tmpLegacyServiceUserConfig := acc.TestClient().TempTomlConfigForLegacyServiceUser(t, tmpLegacyServiceUser) + incorrectLegacyServiceUserConfig := acc.TestClient().TempIncorrectTomlConfigForLegacyServiceUser(t, tmpLegacyServiceUser) + 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{ { - Config: providerConfigWithAuthenticator(testprofiles.Default, sdk.AuthenticationTypeSnowflake), + PreConfig: func() { + t.Setenv(snowflakeenvs.ConfigPath, incorrectLegacyServiceUserConfig.Path) + }, + Config: providerConfig(incorrectLegacyServiceUserConfig.Profile), + ExpectError: regexp.MustCompile("Incorrect username or password was specified"), + }, + { + PreConfig: func() { + t.Setenv(snowflakeenvs.ConfigPath, tmpLegacyServiceUserConfig.Path) + }, + Config: providerConfig(tmpLegacyServiceUserConfig.Profile), }, }, }) } func TestAcc_Provider_invalidConfigurations(t *testing.T) { + _ = testenvs.GetOrSkipTest(t, testenvs.EnableAcceptance) + acc.TestAccPreCheck(t) + t.Setenv(string(testenvs.ConfigureClientOnce), "") + + tmpServiceUser := acc.TestClient().SetUpTemporaryServiceUser(t) + tmpServiceUserConfig := acc.TestClient().TempTomlConfigForServiceUser(t, tmpServiceUser) + resource.Test(t, resource.TestCase{ ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, TerraformVersionChecks: []tfversion.TerraformVersionCheck{ tfversion.RequireAbove(tfversion.Version1_5_0), }, + PreCheck: func() { + t.Setenv(snowflakeenvs.ConfigPath, tmpServiceUserConfig.Path) + }, Steps: []resource.TestStep{ { - Config: providerConfigWithClientIp(testprofiles.Default, "invalid"), + Config: providerConfigWithClientIp(tmpServiceUserConfig.Profile, "invalid"), ExpectError: regexp.MustCompile("expected client_ip to contain a valid IP"), }, { - Config: providerConfigWithProtocol(testprofiles.Default, "invalid"), + Config: providerConfigWithProtocol(tmpServiceUserConfig.Profile, "invalid"), ExpectError: regexp.MustCompile("invalid protocol: invalid"), }, { - Config: providerConfigWithPort(testprofiles.Default, 123456789), + Config: providerConfigWithPort(tmpServiceUserConfig.Profile, 123456789), ExpectError: regexp.MustCompile(`expected "port" to be a valid port number or 0, got: 123456789`), }, { - Config: providerConfigWithAuthType(testprofiles.Default, "invalid"), + Config: providerConfigWithAuthType(tmpServiceUserConfig.Profile, "invalid"), ExpectError: regexp.MustCompile("invalid authenticator type: invalid"), }, { - Config: providerConfigWithOktaUrl(testprofiles.Default, "invalid"), + Config: providerConfigWithOktaUrl(tmpServiceUserConfig.Profile, "invalid"), ExpectError: regexp.MustCompile(`expected "okta_url" to have a host, got invalid`), }, { - Config: providerConfigWithTimeout(testprofiles.Default, "login_timeout", -1), + Config: providerConfigWithTimeout(tmpServiceUserConfig.Profile, "login_timeout", -1), ExpectError: regexp.MustCompile(`expected login_timeout to be at least \(0\), got -1`), }, { - Config: providerConfigWithTokenEndpoint(testprofiles.Default, "invalid"), + Config: providerConfigWithTokenEndpoint(tmpServiceUserConfig.Profile, "invalid"), ExpectError: regexp.MustCompile(`expected "token_endpoint" to have a host, got invalid`), }, { - Config: providerConfigWithLogLevel(testprofiles.Default, "invalid"), + Config: providerConfigWithLogLevel(tmpServiceUserConfig.Profile, "invalid"), ExpectError: regexp.MustCompile(`invalid driver log level: invalid`), }, { - Config: providerConfig("non-existing"), - // .* is used to match the error message regarding of the home user location - ExpectError: regexp.MustCompile(`profile "non-existing" not found in file .*.snowflake/config`), + Config: providerConfig("non-existing"), + ExpectError: regexp.MustCompile(fmt.Sprintf(`profile "non-existing" not found in file %s`, tmpServiceUserConfig.Path)), }, }, }) } -func providerConfigWithAuthenticator(profile string, authenticator sdk.AuthenticationType) string { +func providerConfigWithProfileAndAuthenticator(profile string, authenticator sdk.AuthenticationType) string { return fmt.Sprintf(` provider "snowflake" { profile = "%[1]s" @@ -656,10 +766,11 @@ provider "snowflake" { `, profile, authenticator) + datasourceConfig() } -func emptyProviderConfig() string { - return ` +func providerConfigWithAuthenticator(authenticator sdk.AuthenticationType) string { + return fmt.Sprintf(` provider "snowflake" { -}` + datasourceConfig() + authenticator = "%[1]s" +}`, authenticator) + datasourceConfig() } func providerConfig(profile string) string { @@ -670,22 +781,26 @@ provider "snowflake" { `, profile) + datasourceConfig() } -func providerConfigWithRole(profile, role string) string { +func providerConfigWithExplicitValidationAndRole(profile string, role string, validate bool) string { return fmt.Sprintf(` provider "snowflake" { profile = "%[1]s" role = "%[2]s" + + validate_default_parameters = "%[3]t" } -`, profile, role) + datasourceConfig() +`, profile, role, validate) + datasourceConfig() } -func providerConfigWithWarehouse(profile, warehouse string) string { +func providerConfigWithExplicitValidationAndWarehouse(profile string, warehouse string, validate bool) string { return fmt.Sprintf(` provider "snowflake" { - profile = "%[1]s" - warehouse = "%[2]s" + profile = "%[1]s" + warehouse = "%[2]s" + + validate_default_parameters = "%[3]t" } -`, profile, warehouse) + datasourceConfig() +`, profile, warehouse, validate) + datasourceConfig() } func providerConfigWithClientStoreTemporaryCredential(profile, clientStoreTemporaryCredential string) string { @@ -697,16 +812,6 @@ provider "snowflake" { `, profile, clientStoreTemporaryCredential) + datasourceConfig() } -func providerConfigWithWarehouseAndDisabledValidation(profile, warehouse string) string { - return fmt.Sprintf(` -provider "snowflake" { - profile = "%[1]s" - warehouse = "%[2]s" - validate_default_parameters = "false" -} -`, profile, warehouse) + datasourceConfig() -} - func providerConfigWithProtocol(profile, protocol string) string { return fmt.Sprintf(` provider "snowflake" { @@ -785,46 +890,27 @@ provider "snowflake" { `, profile, clientIp) + datasourceConfig() } -func providerConfigWithUser(user string, profile string) string { +func providerConfigWithUserAndProfile(userId sdk.AccountObjectIdentifier, profile string) string { return fmt.Sprintf(` provider "snowflake" { + authenticator = "SNOWFLAKE_JWT" user = "%[1]s" profile = "%[2]s" } -`, user, profile) + datasourceConfig() +`, userId.Name(), profile) + datasourceConfig() } -func providerConfigWithUserAndPassword(user string, pass string, profile string) string { +func providerConfigWithUserPrivateKeyAndProfile(userId sdk.AccountObjectIdentifier, key string, role string, profile string) string { return fmt.Sprintf(` provider "snowflake" { + authenticator = "SNOWFLAKE_JWT" user = "%[1]s" - password = "%[2]s" - profile = "%[3]s" -} -`, user, pass, profile) + datasourceConfig() -} - -func providerConfigWithNewAccountId(profile, orgName, accountName string) string { - return fmt.Sprintf(` -provider "snowflake" { - profile = "%[1]s" - account_name = "%[2]s" - organization_name = "%[3]s" -} -`, profile, accountName, orgName) + datasourceConfig() -} - -func providerConfigComplete(profile, user, password, orgName, accountName string) string { - return fmt.Sprintf(` -provider "snowflake" { - profile = "%[1]s" - user = "%[2]s" - password = "%[3]s" - organization_name = "%[4]s" - account_name = "%[5]s" - warehouse = "SNOWFLAKE" + private_key = < Date: Thu, 28 Nov 2024 20:39:09 +0100 Subject: [PATCH 2/3] chore: Revert to proper env (#3238) Revert to previous env after merge --- .github/workflows/tests.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 5f1d0284f4..824bd0cb0e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -49,7 +49,7 @@ jobs: - name: Create and populate .snowflake/config file id: create_config - run: mkdir -p $HOME/.snowflake && echo "${{ secrets.ASW_TMP_SNOWFLAKE_CONFIG_FILE }}" > $HOME/.snowflake/config + run: mkdir -p $HOME/.snowflake && echo "${{ secrets.SNOWFLAKE_CONFIG_FILE }}" > $HOME/.snowflake/config - name: Create and populate .snowflake/config_v097_compatible file id: create_config_v097_compatible From 425787c5938e88895af1157f505889611bdef398 Mon Sep 17 00:00:00 2001 From: Artur Sawicki Date: Fri, 29 Nov 2024 10:50:59 +0100 Subject: [PATCH 3/3] chore: Improve config builders (#3207) Improve how we build config models: - explore HCL v1 lib - generate provider config builder - generate datasource config builder - generate config using hcl v1 lib - support objects, lists, and sets - use provider config in provider setup tests - use datasource generated config in one test for databases datasource - use support for objects and lists in one view resource acceptance test - extract nullVariable to own file - cleanup in bettertestspoc/config directory - add all datasources to provider/datasources - ~deprecate old config builder and config variables from config methods~ - undeprecated for now because of linter (and I could not quickly find how to disable it; will do in a follow-up) - multiline values (using <