diff --git a/pkg/acceptance/helpers/account_client.go b/pkg/acceptance/helpers/account_client.go index bf78e6a289..5bcef72bec 100644 --- a/pkg/acceptance/helpers/account_client.go +++ b/pkg/acceptance/helpers/account_client.go @@ -2,19 +2,27 @@ package helpers import ( "context" + "fmt" + "strings" "testing" + "time" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/helpers/random" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/snowflakeroles" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/snowflakedb/gosnowflake" "github.com/stretchr/testify/require" ) type AccountClient struct { context *TestClientContext + ids *IdsGenerator } -func NewAccountClient(context *TestClientContext) *AccountClient { +func NewAccountClient(context *TestClientContext, idsGenerator *IdsGenerator) *AccountClient { return &AccountClient{ context: context, + ids: idsGenerator, } } @@ -42,3 +50,100 @@ func (c *AccountClient) GetAccountIdentifier(t *testing.T) sdk.AccountIdentifier t.Fatal("could not find the account identifier for the locator") return sdk.AccountIdentifier{} } + +func (c *AccountClient) Create(t *testing.T) (*sdk.Account, func()) { + t.Helper() + id := c.ids.RandomAccountObjectIdentifier() + name := c.ids.Alpha() + email := random.Email() + privateKey := random.GenerateRSAPrivateKey(t) + publicKey, _ := random.GenerateRSAPublicKeyFromPrivateKey(t, privateKey) + + return c.CreateWithRequest(t, id, &sdk.CreateAccountOptions{ + AdminName: name, + AdminRSAPublicKey: sdk.String(publicKey), + Email: email, + Edition: sdk.EditionStandard, + }) +} + +func (c *AccountClient) CreateWithRequest(t *testing.T, id sdk.AccountObjectIdentifier, opts *sdk.CreateAccountOptions) (*sdk.Account, func()) { + t.Helper() + err := c.client().Create(context.Background(), id, opts) + require.NoError(t, err) + + account, err := c.client().ShowByID(context.Background(), id) + require.NoError(t, err) + + return account, c.DropFunc(t, id) +} + +func (c *AccountClient) Alter(t *testing.T, opts *sdk.AlterAccountOptions) { + t.Helper() + err := c.client().Alter(context.Background(), opts) + require.NoError(t, err) +} + +func (c *AccountClient) DropFunc(t *testing.T, id sdk.AccountObjectIdentifier) func() { + t.Helper() + return func() { + require.NoError(t, c.Drop(t, id)) + } +} + +func (c *AccountClient) Drop(t *testing.T, id sdk.AccountObjectIdentifier) error { + t.Helper() + ctx := context.Background() + + return c.client().Drop(ctx, id, 3, &sdk.DropAccountOptions{IfExists: sdk.Bool(true)}) +} + +type Region struct { + SnowflakeRegion string `db:"snowflake_region"` + Cloud string `db:"cloud"` + Region string `db:"region"` + DisplayName string `db:"display_name"` +} + +func (c *AccountClient) ShowRegions(t *testing.T) []Region { + t.Helper() + + var regions []Region + err := c.context.client.QueryForTests(context.Background(), ®ions, "SHOW REGIONS") + require.NoError(t, err) + + return regions +} + +func (c *AccountClient) CreateAndLogIn(t *testing.T) (*sdk.Account, *sdk.Client, func()) { + t.Helper() + id := c.ids.RandomAccountObjectIdentifier() + name := c.ids.Alpha() + privateKey := random.GenerateRSAPrivateKey(t) + publicKey, _ := random.GenerateRSAPublicKeyFromPrivateKey(t, privateKey) + email := random.Email() + + account, accountCleanup := c.CreateWithRequest(t, id, &sdk.CreateAccountOptions{ + AdminName: name, + AdminRSAPublicKey: sdk.String(publicKey), + AdminUserType: sdk.Pointer(sdk.UserTypeService), + Email: email, + Edition: sdk.EditionStandard, + }) + + var client *sdk.Client + require.Eventually(t, func() bool { + newClient, err := sdk.NewClient(&gosnowflake.Config{ + Account: fmt.Sprintf("%s-%s", account.OrganizationName, account.AccountName), + User: name, + Host: strings.TrimPrefix(*account.AccountLocatorURL, `https://`), + Authenticator: gosnowflake.AuthTypeJwt, + PrivateKey: privateKey, + Role: snowflakeroles.Accountadmin.Name(), + }) + client = newClient + return err == nil + }, 2*time.Minute, time.Second*15) + + return account, client, accountCleanup +} diff --git a/pkg/acceptance/helpers/context_client.go b/pkg/acceptance/helpers/context_client.go index 4c513135c1..13410d4eae 100644 --- a/pkg/acceptance/helpers/context_client.go +++ b/pkg/acceptance/helpers/context_client.go @@ -43,6 +43,16 @@ func (c *ContextClient) CurrentAccountId(t *testing.T) sdk.AccountIdentifier { return sdk.NewAccountIdentifier(currentSessionDetails.OrganizationName, currentSessionDetails.AccountName) } +func (c *ContextClient) CurrentAccountName(t *testing.T) string { + t.Helper() + ctx := context.Background() + + currentAccount, err := c.client().CurrentAccountName(ctx) + require.NoError(t, err) + + return currentAccount +} + func (c *ContextClient) CurrentRole(t *testing.T) sdk.AccountObjectIdentifier { t.Helper() ctx := context.Background() diff --git a/pkg/acceptance/helpers/packages_policy_client.go b/pkg/acceptance/helpers/packages_policy_client.go new file mode 100644 index 0000000000..085498dede --- /dev/null +++ b/pkg/acceptance/helpers/packages_policy_client.go @@ -0,0 +1,37 @@ +package helpers + +import ( + "context" + "fmt" + "testing" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/stretchr/testify/require" +) + +type PackagesPolicyClient struct { + context *TestClientContext + ids *IdsGenerator +} + +func NewPackagesPolicyClient(context *TestClientContext, idsGenerator *IdsGenerator) *PackagesPolicyClient { + return &PackagesPolicyClient{ + context: context, + ids: idsGenerator, + } +} + +func (c *PackagesPolicyClient) Create(t *testing.T) (sdk.SchemaObjectIdentifier, func()) { + t.Helper() + + // TODO(SNOW-1348357): Replace raw SQL with SDK + + id := c.ids.RandomSchemaObjectIdentifier() + _, err := c.context.client.ExecForTests(context.Background(), fmt.Sprintf("CREATE PACKAGES POLICY %s LANGUAGE PYTHON", id.FullyQualifiedName())) + require.NoError(t, err) + + return id, func() { + _, err = c.context.client.ExecForTests(context.Background(), fmt.Sprintf("DROP PACKAGES POLICY IF EXISTS %s", id.FullyQualifiedName())) + require.NoError(t, err) + } +} diff --git a/pkg/acceptance/helpers/random/certs.go b/pkg/acceptance/helpers/random/certs.go index f3bb1c40fb..b314a0cbfa 100644 --- a/pkg/acceptance/helpers/random/certs.go +++ b/pkg/acceptance/helpers/random/certs.go @@ -47,11 +47,11 @@ func GenerateRSAPublicKey(t *testing.T) (string, string) { t.Helper() key := GenerateRSAPrivateKey(t) - return generateRSAPublicKeyFromPrivateKey(t, key) + 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) { +// GenerateRSAPublicKeyFromPrivateKey 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() @@ -82,7 +82,7 @@ func GenerateRSAKeyPair(t *testing.T, pass string) (string, string, string, stri unencrypted := string(pem.EncodeToMemory(&privBlock)) encrypted := encrypt(t, privateKey, pass) - publicKey, keyHash := generateRSAPublicKeyFromPrivateKey(t, privateKey) + publicKey, keyHash := GenerateRSAPublicKeyFromPrivateKey(t, privateKey) return unencrypted, encrypted, publicKey, keyHash } diff --git a/pkg/acceptance/helpers/test_client.go b/pkg/acceptance/helpers/test_client.go index 8c2a3cccb1..d7d115b9ec 100644 --- a/pkg/acceptance/helpers/test_client.go +++ b/pkg/acceptance/helpers/test_client.go @@ -42,6 +42,7 @@ type TestClient struct { NetworkPolicy *NetworkPolicyClient NetworkRule *NetworkRuleClient NotificationIntegration *NotificationIntegrationClient + PackagesPolicy *PackagesPolicyClient Parameter *ParameterClient PasswordPolicy *PasswordPolicyClient Pipe *PipeClient @@ -82,7 +83,7 @@ func NewTestClient(c *sdk.Client, database string, schema string, warehouse stri Ids: idsGenerator, - Account: NewAccountClient(context), + Account: NewAccountClient(context, idsGenerator), AggregationPolicy: NewAggregationPolicyClient(context, idsGenerator), Alert: NewAlertClient(context, idsGenerator), ApiIntegration: NewApiIntegrationClient(context, idsGenerator), @@ -115,6 +116,7 @@ func NewTestClient(c *sdk.Client, database string, schema string, warehouse stri NetworkPolicy: NewNetworkPolicyClient(context, idsGenerator), NetworkRule: NewNetworkRuleClient(context, idsGenerator), NotificationIntegration: NewNotificationIntegrationClient(context, idsGenerator), + PackagesPolicy: NewPackagesPolicyClient(context, idsGenerator), Parameter: NewParameterClient(context), PasswordPolicy: NewPasswordPolicyClient(context, idsGenerator), Pipe: NewPipeClient(context, idsGenerator), diff --git a/pkg/acceptance/testenvs/testing_environment_variables.go b/pkg/acceptance/testenvs/testing_environment_variables.go index 997d8cd8a8..07a69e6a34 100644 --- a/pkg/acceptance/testenvs/testing_environment_variables.go +++ b/pkg/acceptance/testenvs/testing_environment_variables.go @@ -13,9 +13,8 @@ type env string const ( BusinessCriticalAccount env = "SNOWFLAKE_BUSINESS_CRITICAL_ACCOUNT" - TestAccountCreate env = "TEST_SF_TF_TEST_ACCOUNT_CREATE" - TestFailoverGroups env = "TEST_SF_TF_TEST_FAILOVER_GROUPS" - ResourceMonitorNotifyUsers env = "TEST_SF_TF_RESOURCE_MONITOR_NOTIFY_USERS" + TestAccountCreate env = "TEST_SF_TF_TEST_ACCOUNT_CREATE" + TestFailoverGroups env = "TEST_SF_TF_TEST_FAILOVER_GROUPS" AwsExternalBucketUrl env = "TEST_SF_TF_AWS_EXTERNAL_BUCKET_URL" AwsExternalKeyId env = "TEST_SF_TF_AWS_EXTERNAL_KEY_ID" @@ -24,7 +23,7 @@ const ( AzureExternalBucketUrl env = "TEST_SF_TF_AZURE_EXTERNAL_BUCKET_URL" AzureExternalTenantId env = "TEST_SF_TF_AZURE_EXTERNAL_TENANT_ID" AzureExternalSasToken env = "TEST_SF_TF_AZURE_EXTERNAL_SAS_TOKEN" // #nosec G101 - GcsExternalBuckerUrl env = "TEST_SF_TF_GCS_EXTERNAL_BUCKET_URL" + GcsExternalBucketUrl env = "TEST_SF_TF_GCS_EXTERNAL_BUCKET_URL" EnableObjectRenamingTest env = "TEST_SF_TF_ENABLE_OBJECT_RENAMING" SkipManagedAccountTest env = "TEST_SF_TF_SKIP_MANAGED_ACCOUNT_TEST" diff --git a/pkg/datasources/accounts.go b/pkg/datasources/accounts.go index 87c706152c..d5aff6abec 100644 --- a/pkg/datasources/accounts.go +++ b/pkg/datasources/accounts.go @@ -148,7 +148,7 @@ func ReadAccounts(ctx context.Context, d *schema.ResourceData, meta any) diag.Di m["account_name"] = account.AccountName m["region_group"] = account.RegionGroup m["snowflake_region"] = account.SnowflakeRegion - m["edition"] = string(account.Edition) + m["edition"] = string(*account.Edition) m["account_url"] = account.AccountURL m["created_on"] = account.CreatedOn.String() m["comment"] = account.Comment diff --git a/pkg/schemas/account_gen.go b/pkg/schemas/account_gen.go index 17d7ee2836..715e1fb9cf 100644 --- a/pkg/schemas/account_gen.go +++ b/pkg/schemas/account_gen.go @@ -83,7 +83,7 @@ func AccountToSchema(account *sdk.Account) map[string]any { accountSchema["account_name"] = account.AccountName accountSchema["region_group"] = account.RegionGroup accountSchema["snowflake_region"] = account.SnowflakeRegion - accountSchema["edition"] = string(account.Edition) + accountSchema["edition"] = account.Edition accountSchema["account_url"] = account.AccountURL accountSchema["created_on"] = account.CreatedOn.String() accountSchema["comment"] = account.Comment diff --git a/pkg/sdk/accounts.go b/pkg/sdk/accounts.go index f238b10324..00557cba7a 100644 --- a/pkg/sdk/accounts.go +++ b/pkg/sdk/accounts.go @@ -4,8 +4,9 @@ import ( "context" "database/sql" "errors" - "fmt" "time" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/collections" ) var ( @@ -21,6 +22,7 @@ type Accounts interface { ShowByID(ctx context.Context, id AccountObjectIdentifier) (*Account, error) Drop(ctx context.Context, id AccountObjectIdentifier, gracePeriodInDays int, opts *DropAccountOptions) error Undrop(ctx context.Context, id AccountObjectIdentifier) error + ShowParameters(ctx context.Context) ([]*Parameter, error) } var _ Accounts = (*accounts)(nil) @@ -47,14 +49,16 @@ type CreateAccountOptions struct { AdminName string `ddl:"parameter,single_quotes" sql:"ADMIN_NAME"` AdminPassword *string `ddl:"parameter,single_quotes" sql:"ADMIN_PASSWORD"` AdminRSAPublicKey *string `ddl:"parameter,single_quotes" sql:"ADMIN_RSA_PUBLIC_KEY"` + AdminUserType *UserType `ddl:"parameter" sql:"ADMIN_USER_TYPE"` FirstName *string `ddl:"parameter,single_quotes" sql:"FIRST_NAME"` LastName *string `ddl:"parameter,single_quotes" sql:"LAST_NAME"` Email string `ddl:"parameter,single_quotes" sql:"EMAIL"` MustChangePassword *bool `ddl:"parameter" sql:"MUST_CHANGE_PASSWORD"` Edition AccountEdition `ddl:"parameter" sql:"EDITION"` - RegionGroup *string `ddl:"parameter,single_quotes" sql:"REGION_GROUP"` - Region *string `ddl:"parameter,single_quotes" sql:"REGION"` + RegionGroup *string `ddl:"parameter" sql:"REGION_GROUP"` + Region *string `ddl:"parameter" sql:"REGION"` Comment *string `ddl:"parameter,single_quotes" sql:"COMMENT"` + Polaris *bool `ddl:"parameter" sql:"POLARIS"` } func (opts *CreateAccountOptions) validate() error { @@ -90,12 +94,13 @@ type AlterAccountOptions struct { alter bool `ddl:"static" sql:"ALTER"` account bool `ddl:"static" sql:"ACCOUNT"` - Set *AccountSet `ddl:"keyword" sql:"SET"` - Unset *AccountUnset `ddl:"list,no_parentheses" sql:"UNSET"` - SetTag []TagAssociation `ddl:"keyword" sql:"SET TAG"` - UnsetTag []ObjectIdentifier `ddl:"keyword" sql:"UNSET TAG"` - Rename *AccountRename `ddl:"-"` - Drop *AccountDrop `ddl:"-"` + Set *AccountSet `ddl:"keyword" sql:"SET"` + Unset *AccountUnset `ddl:"list,no_parentheses" sql:"UNSET"` + SetTag []TagAssociation `ddl:"keyword" sql:"SET TAG"` + UnsetTag []ObjectIdentifier `ddl:"keyword" sql:"UNSET TAG"` + SetIsOrgAdmin *AccountSetIsOrgAdmin `ddl:"-"` + Rename *AccountRename `ddl:"-"` + Drop *AccountDrop `ddl:"-"` } func (opts *AlterAccountOptions) validate() error { @@ -103,8 +108,8 @@ func (opts *AlterAccountOptions) validate() error { return errors.Join(ErrNilOptions) } var errs []error - if !exactlyOneValueSet(opts.Set, opts.Unset, opts.SetTag, opts.UnsetTag, opts.Drop, opts.Rename) { - errs = append(errs, errExactlyOneOf("CreateAccountOptions", "Set", "Unset", "SetTag", "UnsetTag", "Drop", "Rename")) + if !exactlyOneValueSet(opts.Set, opts.Unset, opts.SetTag, opts.UnsetTag, opts.Drop, opts.Rename, opts.SetIsOrgAdmin) { + errs = append(errs, errExactlyOneOf("CreateAccountOptions", "Set", "Unset", "SetTag", "UnsetTag", "Drop", "Rename", "SetIsOrgAdmin")) } if valueSet(opts.Set) { if err := opts.Set.validate(); err != nil { @@ -164,15 +169,20 @@ func (opts *AccountLevelParameters) validate() error { type AccountSet struct { Parameters *AccountLevelParameters `ddl:"list,no_parentheses"` ResourceMonitor AccountObjectIdentifier `ddl:"identifier,equals" sql:"RESOURCE_MONITOR"` + PackagesPolicy SchemaObjectIdentifier `ddl:"identifier" sql:"PACKAGES POLICY"` PasswordPolicy SchemaObjectIdentifier `ddl:"identifier" sql:"PASSWORD POLICY"` SessionPolicy SchemaObjectIdentifier `ddl:"identifier" sql:"SESSION POLICY"` AuthenticationPolicy SchemaObjectIdentifier `ddl:"identifier" sql:"AUTHENTICATION POLICY"` + Force *bool `ddl:"keyword" sql:"FORCE"` } func (opts *AccountSet) validate() error { var errs []error - if !exactlyOneValueSet(opts.Parameters, opts.ResourceMonitor, opts.PasswordPolicy, opts.SessionPolicy, opts.AuthenticationPolicy) { - errs = append(errs, errExactlyOneOf("AccountSet", "Parameters", "ResourceMonitor", "PasswordPolicy", "SessionPolicy", "AuthenticationPolicy")) + if !exactlyOneValueSet(opts.Parameters, opts.ResourceMonitor, opts.PackagesPolicy, opts.PasswordPolicy, opts.SessionPolicy, opts.AuthenticationPolicy) { + errs = append(errs, errExactlyOneOf("AccountSet", "Parameters", "ResourceMonitor", "PackagesPolicy", "PasswordPolicy", "SessionPolicy", "AuthenticationPolicy")) + } + if valueSet(opts.Force) && !valueSet(opts.PackagesPolicy) { + errs = append(errs, NewError("force can only be set with PackagesPolicy field")) } if valueSet(opts.Parameters) { if err := opts.Parameters.validate(); err != nil { @@ -198,15 +208,17 @@ func (opts *AccountLevelParametersUnset) validate() error { type AccountUnset struct { Parameters *AccountLevelParametersUnset `ddl:"list,no_parentheses"` + PackagesPolicy *bool `ddl:"keyword" sql:"PACKAGES POLICY"` PasswordPolicy *bool `ddl:"keyword" sql:"PASSWORD POLICY"` SessionPolicy *bool `ddl:"keyword" sql:"SESSION POLICY"` AuthenticationPolicy *bool `ddl:"keyword" sql:"AUTHENTICATION POLICY"` + ResourceMonitor *bool `ddl:"keyword" sql:"RESOURCE_MONITOR"` } func (opts *AccountUnset) validate() error { var errs []error - if !exactlyOneValueSet(opts.Parameters, opts.PasswordPolicy, opts.SessionPolicy, opts.AuthenticationPolicy) { - errs = append(errs, errExactlyOneOf("AccountUnset", "Parameters", "PasswordPolicy", "SessionPolicy", "AuthenticationPolicy")) + if !exactlyOneValueSet(opts.Parameters, opts.PackagesPolicy, opts.PasswordPolicy, opts.SessionPolicy, opts.AuthenticationPolicy, opts.ResourceMonitor) { + errs = append(errs, errExactlyOneOf("AccountUnset", "Parameters", "PackagesPolicy", "PasswordPolicy", "SessionPolicy", "AuthenticationPolicy", "ResourceMonitor")) } if valueSet(opts.Parameters) { if err := opts.Parameters.validate(); err != nil { @@ -216,6 +228,11 @@ func (opts *AccountUnset) validate() error { return errors.Join(errs...) } +type AccountSetIsOrgAdmin struct { + Name AccountObjectIdentifier `ddl:"identifier"` + OrgAdmin bool `ddl:"parameter" sql:"SET IS_ORG_ADMIN"` +} + type AccountRename struct { Name AccountObjectIdentifier `ddl:"identifier"` NewName AccountObjectIdentifier `ddl:"identifier" sql:"RENAME TO"` @@ -234,8 +251,9 @@ func (opts *AccountRename) validate() error { } type AccountDrop struct { - Name AccountObjectIdentifier `ddl:"identifier"` - OldURL *bool `ddl:"keyword" sql:"DROP OLD URL"` + Name AccountObjectIdentifier `ddl:"identifier"` + OldUrl *bool `ddl:"keyword" sql:"DROP OLD URL"` + OldOrganizationUrl *bool `ddl:"keyword" sql:"DROP OLD ORGANIZATION URL"` } func (opts *AccountDrop) validate() error { @@ -243,13 +261,8 @@ func (opts *AccountDrop) validate() error { if !ValidObjectIdentifier(opts.Name) { errs = append(errs, ErrInvalidObjectIdentifier) } - if valueSet(opts.OldURL) { - // TODO: Should this really be validated to be true ? - if !*opts.OldURL { - errs = append(errs, fmt.Errorf("OldURL must be true")) - } - } else { - errs = append(errs, errNotSet("AccountDrop", "OldURL")) + if !exactlyOneValueSet(opts.OldUrl, opts.OldOrganizationUrl) { + errs = append(errs, errExactlyOneOf("AccountDrop", "OldUrl", "OldOrganizationUrl")) } return errors.Join(errs...) } @@ -265,6 +278,7 @@ func (c *accounts) Alter(ctx context.Context, opts *AlterAccountOptions) error { type ShowAccountOptions struct { show bool `ddl:"static" sql:"SHOW"` accounts bool `ddl:"static" sql:"ACCOUNTS"` + History *bool `ddl:"keyword" sql:"HISTORY"` Like *Like `ddl:"keyword" sql:"LIKE"` } @@ -278,20 +292,34 @@ func (opts *ShowAccountOptions) validate() error { type Account struct { OrganizationName string AccountName string - RegionGroup string SnowflakeRegion string - Edition AccountEdition - AccountURL string - CreatedOn time.Time - Comment string + RegionGroup *string // shows only for organizations that span multiple region groups + Edition *AccountEdition + AccountURL *string + CreatedOn *time.Time + Comment *string AccountLocator string - AccountLocatorURL string - ManagedAccounts int - ConsumptionBillingEntityName string - MarketplaceConsumerBillingEntityName string - MarketplaceProviderBillingEntityName string - OldAccountURL string - IsOrgAdmin bool + AccountLocatorURL *string + ManagedAccounts *int + ConsumptionBillingEntityName *string + MarketplaceConsumerBillingEntityName *string + MarketplaceProviderBillingEntityName *string + OldAccountURL *string + IsOrgAdmin *bool + AccountOldUrlSavedOn *time.Time + AccountOldUrlLastUsed *time.Time + OrganizationOldUrl *string + OrganizationOldUrlSavedOn *time.Time + OrganizationOldUrlLastUsed *time.Time + IsEventsAccount *bool + IsOrganizationAccount bool + // Available only with the History keyword set + DroppedOn *time.Time + ScheduledDeletionTime *time.Time + RestoredOn *time.Time + MovedToOrganization *string + MovedOn *string + OrganizationUrlExpirationOn *time.Time } func (v *Account) ID() AccountObjectIdentifier { @@ -307,48 +335,113 @@ type accountDBRow struct { AccountName string `db:"account_name"` RegionGroup sql.NullString `db:"region_group"` SnowflakeRegion string `db:"snowflake_region"` - Edition string `db:"edition"` - AccountURL string `db:"account_url"` - CreatedOn time.Time `db:"created_on"` + Edition sql.NullString `db:"edition"` + AccountURL sql.NullString `db:"account_url"` + CreatedOn sql.NullTime `db:"created_on"` Comment sql.NullString `db:"comment"` AccountLocator string `db:"account_locator"` - AccountLocatorURL string `db:"account_locator_url"` - AccountOldURLSavedOn sql.NullString `db:"account_old_url_saved_on"` - ManagedAccounts int `db:"managed_accounts"` - ConsumptionBillingEntityName string `db:"consumption_billing_entity_name"` + AccountLocatorURL sql.NullString `db:"account_locator_url"` + ManagedAccounts sql.NullInt32 `db:"managed_accounts"` + ConsumptionBillingEntityName sql.NullString `db:"consumption_billing_entity_name"` MarketplaceConsumerBillingEntityName sql.NullString `db:"marketplace_consumer_billing_entity_name"` MarketplaceProviderBillingEntityName sql.NullString `db:"marketplace_provider_billing_entity_name"` - OldAccountURL string `db:"old_account_url"` - IsOrgAdmin bool `db:"is_org_admin"` + OldAccountURL sql.NullString `db:"old_account_url"` + IsOrgAdmin sql.NullBool `db:"is_org_admin"` + AccountOldUrlSavedOn sql.NullTime `db:"account_old_url_saved_on"` + AccountOldUrlLastUsed sql.NullTime `db:"account_old_url_last_used"` + OrganizationOldUrl sql.NullString `db:"organization_old_url"` + OrganizationOldUrlSavedOn sql.NullTime `db:"organization_old_url_saved_on"` + OrganizationOldUrlLastUsed sql.NullTime `db:"organization_old_url_last_used"` + IsEventsAccount sql.NullBool `db:"is_events_account"` + IsOrganizationAccount bool `db:"is_organization_account"` + // Available only with the History keyword set + DroppedOn sql.NullTime `db:"dropped_on"` + ScheduledDeletionTime sql.NullTime `db:"scheduled_deletion_time"` + RestoredOn sql.NullTime `db:"restored_on"` + MovedToOrganization sql.NullString `db:"moved_to_organization"` + MovedOn sql.NullString `db:"moved_on"` + OrganizationUrlExpirationOn sql.NullTime `db:"organization_URL_expiration_on"` } func (row accountDBRow) convert() *Account { acc := &Account{ - OrganizationName: row.OrganizationName, - AccountName: row.AccountName, - RegionGroup: "", - SnowflakeRegion: row.SnowflakeRegion, - Edition: AccountEdition(row.Edition), - AccountURL: row.AccountURL, - CreatedOn: row.CreatedOn, - Comment: row.Comment.String, - AccountLocator: row.AccountLocator, - AccountLocatorURL: row.AccountLocatorURL, - ManagedAccounts: row.ManagedAccounts, - ConsumptionBillingEntityName: row.ConsumptionBillingEntityName, - MarketplaceConsumerBillingEntityName: "", - MarketplaceProviderBillingEntityName: "", - OldAccountURL: row.OldAccountURL, - IsOrgAdmin: row.IsOrgAdmin, + OrganizationName: row.OrganizationName, + AccountName: row.AccountName, + SnowflakeRegion: row.SnowflakeRegion, + AccountLocator: row.AccountLocator, + IsOrganizationAccount: row.IsOrganizationAccount, + } + if row.RegionGroup.Valid { + acc.RegionGroup = &row.RegionGroup.String + } + if row.Edition.Valid { + acc.Edition = Pointer(AccountEdition(row.Edition.String)) + } + if row.AccountURL.Valid { + acc.AccountURL = &row.AccountURL.String + } + if row.CreatedOn.Valid { + acc.CreatedOn = &row.CreatedOn.Time + } + if row.Comment.Valid { + acc.Comment = &row.Comment.String + } + if row.AccountLocatorURL.Valid { + acc.AccountLocatorURL = &row.AccountLocatorURL.String + } + if row.ManagedAccounts.Valid { + acc.ManagedAccounts = Int(int(row.ManagedAccounts.Int32)) + } + if row.ConsumptionBillingEntityName.Valid { + acc.ConsumptionBillingEntityName = &row.ConsumptionBillingEntityName.String + } + if row.OldAccountURL.Valid { + acc.OldAccountURL = &row.OldAccountURL.String + } + if row.IsOrgAdmin.Valid { + acc.IsOrgAdmin = &row.IsOrgAdmin.Bool + } + if row.OrganizationOldUrl.Valid { + acc.OrganizationOldUrl = &row.OrganizationOldUrl.String + } + if row.IsEventsAccount.Valid { + acc.IsEventsAccount = &row.IsEventsAccount.Bool } if row.MarketplaceConsumerBillingEntityName.Valid { - acc.MarketplaceConsumerBillingEntityName = row.MarketplaceConsumerBillingEntityName.String + acc.MarketplaceConsumerBillingEntityName = &row.MarketplaceConsumerBillingEntityName.String } if row.MarketplaceProviderBillingEntityName.Valid { - acc.MarketplaceProviderBillingEntityName = row.MarketplaceProviderBillingEntityName.String + acc.MarketplaceProviderBillingEntityName = &row.MarketplaceProviderBillingEntityName.String } - if row.RegionGroup.Valid { - acc.SnowflakeRegion = row.RegionGroup.String + if row.AccountOldUrlSavedOn.Valid { + acc.AccountOldUrlSavedOn = &row.AccountOldUrlSavedOn.Time + } + if row.AccountOldUrlLastUsed.Valid { + acc.AccountOldUrlLastUsed = &row.AccountOldUrlLastUsed.Time + } + if row.OrganizationOldUrlSavedOn.Valid { + acc.OrganizationOldUrlSavedOn = &row.OrganizationOldUrlSavedOn.Time + } + if row.OrganizationOldUrlLastUsed.Valid { + acc.OrganizationOldUrlLastUsed = &row.OrganizationOldUrlLastUsed.Time + } + if row.DroppedOn.Valid { + acc.DroppedOn = &row.DroppedOn.Time + } + if row.ScheduledDeletionTime.Valid { + acc.ScheduledDeletionTime = &row.ScheduledDeletionTime.Time + } + if row.RestoredOn.Valid { + acc.RestoredOn = &row.RestoredOn.Time + } + if row.MovedToOrganization.Valid { + acc.MovedToOrganization = &row.MovedToOrganization.String + } + if row.MovedOn.Valid { + acc.MovedOn = &row.MovedOn.String + } + if row.OrganizationUrlExpirationOn.Valid { + acc.OrganizationUrlExpirationOn = &row.OrganizationUrlExpirationOn.Time } return acc } @@ -372,12 +465,9 @@ func (c *accounts) ShowByID(ctx context.Context, id AccountObjectIdentifier) (*A if err != nil { return nil, err } - for _, account := range accounts { - if account.AccountName == id.Name() || account.AccountLocator == id.Name() { - return &account, nil - } - } - return nil, ErrObjectNotExistOrAuthorized + return collections.FindFirst(accounts, func(account Account) bool { + return account.AccountName == id.Name() + }) } // DropAccountOptions is based on https://docs.snowflake.com/en/sql-reference/sql/drop-account. @@ -397,9 +487,6 @@ func (opts *DropAccountOptions) validate() error { if !ValidObjectIdentifier(opts.name) { errs = append(errs, ErrInvalidObjectIdentifier) } - if !validateIntGreaterThanOrEqual(opts.gracePeriodInDays, 3) { - errs = append(errs, errIntValue("DropAccountOptions", "gracePeriodInDays", IntErrGreaterOrEqual, 3)) - } return errors.Join(errs...) } @@ -430,3 +517,11 @@ func (c *accounts) Undrop(ctx context.Context, id AccountObjectIdentifier) error _, err = c.client.exec(ctx, sql) return err } + +func (c *accounts) ShowParameters(ctx context.Context) ([]*Parameter, error) { + return c.client.Parameters.ShowParameters(ctx, &ShowParametersOptions{ + In: &ParametersIn{ + Account: Bool(true), + }, + }) +} diff --git a/pkg/sdk/accounts_test.go b/pkg/sdk/accounts_test.go index 3168e02d98..d275c82883 100644 --- a/pkg/sdk/accounts_test.go +++ b/pkg/sdk/accounts_test.go @@ -1,26 +1,34 @@ package sdk import ( + "fmt" "testing" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/helpers/random" ) func TestAccountCreate(t *testing.T) { t.Run("simplest case", func(t *testing.T) { + id := randomAccountObjectIdentifier() + password := random.Password() opts := &CreateAccountOptions{ - name: NewAccountObjectIdentifier("newaccount"), + name: id, AdminName: "someadmin", - AdminPassword: String("v3rys3cr3t"), + AdminPassword: String(password), Email: "admin@example.com", Edition: EditionBusinessCritical, } - assertOptsValidAndSQLEquals(t, opts, `CREATE ACCOUNT "newaccount" ADMIN_NAME = 'someadmin' ADMIN_PASSWORD = 'v3rys3cr3t' EMAIL = 'admin@example.com' EDITION = BUSINESS_CRITICAL`) + assertOptsValidAndSQLEquals(t, opts, `CREATE ACCOUNT %s ADMIN_NAME = 'someadmin' ADMIN_PASSWORD = '%s' EMAIL = 'admin@example.com' EDITION = BUSINESS_CRITICAL`, id.FullyQualifiedName(), password) }) t.Run("every option", func(t *testing.T) { + id := randomAccountObjectIdentifier() + key := random.Password() opts := &CreateAccountOptions{ - name: NewAccountObjectIdentifier("newaccount"), + name: id, AdminName: "someadmin", - AdminRSAPublicKey: String("s3cr3tk3y"), + AdminRSAPublicKey: String(key), + AdminUserType: Pointer(UserTypeService), FirstName: String("Ad"), LastName: String("Min"), Email: "admin@example.com", @@ -29,15 +37,18 @@ func TestAccountCreate(t *testing.T) { RegionGroup: String("groupid"), Region: String("regionid"), Comment: String("Test account"), + Polaris: Bool(true), } - assertOptsValidAndSQLEquals(t, opts, `CREATE ACCOUNT "newaccount" ADMIN_NAME = 'someadmin' ADMIN_RSA_PUBLIC_KEY = 's3cr3tk3y' FIRST_NAME = 'Ad' LAST_NAME = 'Min' EMAIL = 'admin@example.com' MUST_CHANGE_PASSWORD = true EDITION = BUSINESS_CRITICAL REGION_GROUP = 'groupid' REGION = 'regionid' COMMENT = 'Test account'`) + assertOptsValidAndSQLEquals(t, opts, `CREATE ACCOUNT %s ADMIN_NAME = 'someadmin' ADMIN_RSA_PUBLIC_KEY = '%s' ADMIN_USER_TYPE = SERVICE FIRST_NAME = 'Ad' LAST_NAME = 'Min' EMAIL = 'admin@example.com' MUST_CHANGE_PASSWORD = true EDITION = BUSINESS_CRITICAL REGION_GROUP = groupid REGION = regionid COMMENT = 'Test account' POLARIS = true`, id.FullyQualifiedName(), key) }) t.Run("static password", func(t *testing.T) { + id := randomAccountObjectIdentifier() + password := random.Password() opts := &CreateAccountOptions{ - name: NewAccountObjectIdentifier("newaccount"), + name: id, AdminName: "someadmin", - AdminPassword: String("v3rys3cr3t"), + AdminPassword: String(password), FirstName: String("Ad"), LastName: String("Min"), Email: "admin@example.com", @@ -47,7 +58,7 @@ func TestAccountCreate(t *testing.T) { Region: String("regionid"), Comment: String("Test account"), } - assertOptsValidAndSQLEquals(t, opts, `CREATE ACCOUNT "newaccount" ADMIN_NAME = 'someadmin' ADMIN_PASSWORD = 'v3rys3cr3t' FIRST_NAME = 'Ad' LAST_NAME = 'Min' EMAIL = 'admin@example.com' MUST_CHANGE_PASSWORD = false EDITION = BUSINESS_CRITICAL REGION_GROUP = 'groupid' REGION = 'regionid' COMMENT = 'Test account'`) + assertOptsValidAndSQLEquals(t, opts, `CREATE ACCOUNT %s ADMIN_NAME = 'someadmin' ADMIN_PASSWORD = '%s' FIRST_NAME = 'Ad' LAST_NAME = 'Min' EMAIL = 'admin@example.com' MUST_CHANGE_PASSWORD = false EDITION = BUSINESS_CRITICAL REGION_GROUP = groupid REGION = regionid COMMENT = 'Test account'`, id.FullyQualifiedName(), password) }) } @@ -56,7 +67,7 @@ func TestAccountAlter(t *testing.T) { opts := &AlterAccountOptions{ Set: &AccountSet{}, } - assertOptsInvalidJoinedErrors(t, opts, errExactlyOneOf("AccountSet", "Parameters", "ResourceMonitor", "PasswordPolicy", "SessionPolicy", "AuthenticationPolicy")) + assertOptsInvalidJoinedErrors(t, opts, errExactlyOneOf("AccountSet", "Parameters", "ResourceMonitor", "PackagesPolicy", "PasswordPolicy", "SessionPolicy", "AuthenticationPolicy")) }) t.Run("validation: exactly one value set in AccountSet - multiple set", func(t *testing.T) { @@ -67,14 +78,14 @@ func TestAccountAlter(t *testing.T) { AuthenticationPolicy: randomSchemaObjectIdentifier(), }, } - assertOptsInvalidJoinedErrors(t, opts, errExactlyOneOf("AccountSet", "Parameters", "ResourceMonitor", "PasswordPolicy", "SessionPolicy", "AuthenticationPolicy")) + assertOptsInvalidJoinedErrors(t, opts, errExactlyOneOf("AccountSet", "Parameters", "ResourceMonitor", "PackagesPolicy", "PasswordPolicy", "SessionPolicy", "AuthenticationPolicy")) }) t.Run("validation: exactly one value set in AccountUnset - nothing set", func(t *testing.T) { opts := &AlterAccountOptions{ Unset: &AccountUnset{}, } - assertOptsInvalidJoinedErrors(t, opts, errExactlyOneOf("AccountUnset", "Parameters", "PasswordPolicy", "SessionPolicy", "AuthenticationPolicy")) + assertOptsInvalidJoinedErrors(t, opts, errExactlyOneOf("AccountUnset", "Parameters", "PackagesPolicy", "PasswordPolicy", "SessionPolicy", "AuthenticationPolicy", "ResourceMonitor")) }) t.Run("validation: exactly one value set in AccountUnset - multiple set", func(t *testing.T) { @@ -85,7 +96,7 @@ func TestAccountAlter(t *testing.T) { AuthenticationPolicy: Bool(true), }, } - assertOptsInvalidJoinedErrors(t, opts, errExactlyOneOf("AccountUnset", "Parameters", "PasswordPolicy", "SessionPolicy", "AuthenticationPolicy")) + assertOptsInvalidJoinedErrors(t, opts, errExactlyOneOf("AccountUnset", "Parameters", "PackagesPolicy", "PasswordPolicy", "SessionPolicy", "AuthenticationPolicy", "ResourceMonitor")) }) t.Run("with set params", func(t *testing.T) { @@ -138,6 +149,38 @@ func TestAccountAlter(t *testing.T) { assertOptsValidAndSQLEquals(t, opts, `ALTER ACCOUNT SET RESOURCE_MONITOR = "mymonitor"`) }) + t.Run("with set packages policy", func(t *testing.T) { + id := randomSchemaObjectIdentifier() + opts := &AlterAccountOptions{ + Set: &AccountSet{ + PackagesPolicy: id, + }, + } + assertOptsValidAndSQLEquals(t, opts, `ALTER ACCOUNT SET PACKAGES POLICY %s`, id.FullyQualifiedName()) + }) + + t.Run("with set packages policy with force", func(t *testing.T) { + id := randomSchemaObjectIdentifier() + opts := &AlterAccountOptions{ + Set: &AccountSet{ + PackagesPolicy: id, + Force: Bool(true), + }, + } + assertOptsValidAndSQLEquals(t, opts, `ALTER ACCOUNT SET PACKAGES POLICY %s FORCE`, id.FullyQualifiedName()) + }) + + t.Run("validate: force with other policy than packages", func(t *testing.T) { + id := randomSchemaObjectIdentifier() + opts := &AlterAccountOptions{ + Set: &AccountSet{ + PasswordPolicy: id, + Force: Bool(true), + }, + } + assertOptsInvalidJoinedErrors(t, opts, fmt.Errorf("force can only be set with PackagesPolicy field")) + }) + t.Run("with set password policy", func(t *testing.T) { id := randomSchemaObjectIdentifier() opts := &AlterAccountOptions{ @@ -168,6 +211,15 @@ func TestAccountAlter(t *testing.T) { assertOptsValidAndSQLEquals(t, opts, `ALTER ACCOUNT SET AUTHENTICATION POLICY %s`, id.FullyQualifiedName()) }) + t.Run("with unset packages policy", func(t *testing.T) { + opts := &AlterAccountOptions{ + Unset: &AccountUnset{ + PackagesPolicy: Bool(true), + }, + } + assertOptsValidAndSQLEquals(t, opts, `ALTER ACCOUNT UNSET PACKAGES POLICY`) + }) + t.Run("with unset password policy", func(t *testing.T) { opts := &AlterAccountOptions{ Unset: &AccountUnset{ @@ -195,6 +247,15 @@ func TestAccountAlter(t *testing.T) { assertOptsValidAndSQLEquals(t, opts, `ALTER ACCOUNT UNSET AUTHENTICATION POLICY`) }) + t.Run("with unset resource monitor", func(t *testing.T) { + opts := &AlterAccountOptions{ + Unset: &AccountUnset{ + ResourceMonitor: Bool(true), + }, + } + assertOptsValidAndSQLEquals(t, opts, `ALTER ACCOUNT UNSET RESOURCE_MONITOR`) + }) + t.Run("with set tag", func(t *testing.T) { tagId1 := randomSchemaObjectIdentifier() tagId2 := randomSchemaObjectIdentifierInSchema(tagId1.SchemaId()) @@ -223,9 +284,20 @@ func TestAccountAlter(t *testing.T) { assertOptsValidAndSQLEquals(t, opts, `ALTER ACCOUNT UNSET TAG %s`, id.FullyQualifiedName()) }) + t.Run("set is_org_admin", func(t *testing.T) { + id := randomAccountObjectIdentifier() + opts := &AlterAccountOptions{ + SetIsOrgAdmin: &AccountSetIsOrgAdmin{ + Name: id, + OrgAdmin: true, + }, + } + assertOptsValidAndSQLEquals(t, opts, `ALTER ACCOUNT %s SET IS_ORG_ADMIN = true`, id.FullyQualifiedName()) + }) + t.Run("rename", func(t *testing.T) { - oldName := NewAccountObjectIdentifier("oldname") - newName := NewAccountObjectIdentifier("newname") + oldName := randomAccountObjectIdentifier() + newName := randomAccountObjectIdentifier() opts := &AlterAccountOptions{ Rename: &AccountRename{ Name: oldName, @@ -233,18 +305,77 @@ func TestAccountAlter(t *testing.T) { SaveOldURL: Bool(false), }, } - assertOptsValidAndSQLEquals(t, opts, `ALTER ACCOUNT "oldname" RENAME TO "newname" SAVE_OLD_URL = false`) + assertOptsValidAndSQLEquals(t, opts, `ALTER ACCOUNT %s RENAME TO %s SAVE_OLD_URL = false`, oldName.FullyQualifiedName(), newName.FullyQualifiedName()) + }) + + t.Run("validation: drop no url set", func(t *testing.T) { + id := randomAccountObjectIdentifier() + opts := &AlterAccountOptions{ + Drop: &AccountDrop{ + Name: id, + }, + } + assertOptsInvalidJoinedErrors(t, opts, errExactlyOneOf("AccountDrop", "OldUrl", "OldOrganizationUrl")) + }) + + t.Run("validation: drop all url options set", func(t *testing.T) { + id := randomAccountObjectIdentifier() + opts := &AlterAccountOptions{ + Drop: &AccountDrop{ + Name: id, + OldUrl: Bool(true), + OldOrganizationUrl: Bool(true), + }, + } + assertOptsInvalidJoinedErrors(t, opts, errExactlyOneOf("AccountDrop", "OldUrl", "OldOrganizationUrl")) }) t.Run("drop old url", func(t *testing.T) { - oldName := NewAccountObjectIdentifier("oldname") + id := randomAccountObjectIdentifier() opts := &AlterAccountOptions{ Drop: &AccountDrop{ - Name: oldName, - OldURL: Bool(true), + Name: id, + OldUrl: Bool(true), }, } - assertOptsValidAndSQLEquals(t, opts, `ALTER ACCOUNT "oldname" DROP OLD URL`) + assertOptsValidAndSQLEquals(t, opts, `ALTER ACCOUNT %s DROP OLD URL`, id.FullyQualifiedName()) + }) + + t.Run("drop organization old url", func(t *testing.T) { + id := randomAccountObjectIdentifier() + opts := &AlterAccountOptions{ + Drop: &AccountDrop{ + Name: id, + OldOrganizationUrl: Bool(true), + }, + } + assertOptsValidAndSQLEquals(t, opts, `ALTER ACCOUNT %s DROP OLD ORGANIZATION URL`, id.FullyQualifiedName()) + }) +} + +func TestAccountDrop(t *testing.T) { + t.Run("validate: empty options", func(t *testing.T) { + opts := &DropAccountOptions{} + assertOptsInvalidJoinedErrors(t, opts, ErrInvalidObjectIdentifier) + }) + + t.Run("minimal", func(t *testing.T) { + id := randomAccountObjectIdentifier() + opts := &DropAccountOptions{ + name: id, + gracePeriodInDays: 10, + } + assertOptsValidAndSQLEquals(t, opts, `DROP ACCOUNT %s GRACE_PERIOD_IN_DAYS = 10`, id.FullyQualifiedName()) + }) + + t.Run("if exists", func(t *testing.T) { + id := randomAccountObjectIdentifier() + opts := &DropAccountOptions{ + name: id, + IfExists: Bool(true), + gracePeriodInDays: 10, + } + assertOptsValidAndSQLEquals(t, opts, `DROP ACCOUNT IF EXISTS %s GRACE_PERIOD_IN_DAYS = 10`, id.FullyQualifiedName()) }) } @@ -254,6 +385,16 @@ func TestAccountShow(t *testing.T) { assertOptsValidAndSQLEquals(t, opts, `SHOW ACCOUNTS`) }) + t.Run("with history and like", func(t *testing.T) { + opts := &ShowAccountOptions{ + History: Bool(true), + Like: &Like{ + Pattern: String("myaccount"), + }, + } + assertOptsValidAndSQLEquals(t, opts, `SHOW ACCOUNTS HISTORY LIKE 'myaccount'`) + }) + t.Run("with like", func(t *testing.T) { opts := &ShowAccountOptions{ Like: &Like{ diff --git a/pkg/sdk/testint/accounts_integration_test.go b/pkg/sdk/testint/accounts_integration_test.go index 3192bd27d1..c4c864e445 100644 --- a/pkg/sdk/testint/accounts_integration_test.go +++ b/pkg/sdk/testint/accounts_integration_test.go @@ -1,291 +1,698 @@ package testint import ( - "log" "testing" - "time" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/testenvs" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/helpers" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/helpers/random" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/collections" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/snowflakeroles" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" - "github.com/avast/retry-go" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -func TestInt_AccountShow(t *testing.T) { +// TODO(SNOW-1342761): Adjust the tests, so they can be run in their own pipeline +// For now, those tests should be run manually. The account/admin user running those tests is required to: +// - Be privileged with ORGADMIN and ACCOUNTADMIN roles. +// - Shouldn't be any of the "main" accounts/admin users, because those tests alter the current account. + +func TestInt_Account(t *testing.T) { + testenvs.GetOrSkipTest(t, testenvs.TestAccountCreate) + client := testClient(t) ctx := testContext(t) - ok := testClientHelper().Context.IsRoleInSession(t, snowflakeroles.Orgadmin) - if !ok { - t.Skip("ORGADMIN role is not in current session") - } - currentAccount := testClientHelper().Context.CurrentAccount(t) - opts := &sdk.ShowAccountOptions{ - Like: &sdk.Like{ - Pattern: sdk.String(currentAccount), - }, + currentAccountName := testClientHelper().Context.CurrentAccountName(t) + + assertAccountQueriedByOrgAdmin := func(t *testing.T, account sdk.Account, accountName string) { + t.Helper() + assert.NotEmpty(t, account.OrganizationName) + assert.Equal(t, accountName, account.AccountName) + assert.Nil(t, account.RegionGroup) + assert.NotEmpty(t, account.SnowflakeRegion) + assert.Equal(t, sdk.EditionEnterprise, *account.Edition) + assert.NotEmpty(t, *account.AccountURL) + assert.NotEmpty(t, *account.CreatedOn) + assert.Equal(t, "SNOWFLAKE", *account.Comment) + assert.NotEmpty(t, account.AccountLocator) + assert.NotEmpty(t, *account.AccountLocatorURL) + assert.Zero(t, *account.ManagedAccounts) + assert.NotEmpty(t, *account.ConsumptionBillingEntityName) + assert.Nil(t, account.MarketplaceConsumerBillingEntityName) + assert.NotNil(t, account.MarketplaceProviderBillingEntityName) + assert.Empty(t, *account.OldAccountURL) + assert.True(t, *account.IsOrgAdmin) + assert.Nil(t, account.AccountOldUrlSavedOn) + assert.Nil(t, account.AccountOldUrlLastUsed) + assert.Empty(t, *account.OrganizationOldUrl) + assert.Nil(t, account.OrganizationOldUrlSavedOn) + assert.Nil(t, account.OrganizationOldUrlLastUsed) + assert.False(t, *account.IsEventsAccount) + assert.False(t, account.IsOrganizationAccount) } - accounts, err := client.Accounts.Show(ctx, opts) - require.NoError(t, err) - assert.NotEmpty(t, accounts) - assert.Equal(t, 1, len(accounts)) - assert.Contains(t, []string{accounts[0].AccountLocator, accounts[0].AccountName}, currentAccount) -} -func TestInt_AccountShowByID(t *testing.T) { - client := testClient(t) - ctx := testContext(t) - ok := testClientHelper().Context.IsRoleInSession(t, snowflakeroles.Orgadmin) - if !ok { - t.Skip("ORGADMIN role is not in current session") + assertAccountQueriedByAccountAdmin := func(t *testing.T, account sdk.Account, accountName string) { + t.Helper() + assert.NotEmpty(t, account.OrganizationName) + assert.Equal(t, accountName, account.AccountName) + assert.NotEmpty(t, account.SnowflakeRegion) + assert.NotEmpty(t, account.AccountLocator) + assert.False(t, account.IsOrganizationAccount) + assert.Nil(t, account.RegionGroup) + assert.Nil(t, account.Edition) + assert.Nil(t, account.AccountURL) + assert.Nil(t, account.CreatedOn) + assert.Nil(t, account.Comment) + assert.Nil(t, account.AccountLocatorURL) + assert.Nil(t, account.ManagedAccounts) + assert.Nil(t, account.ConsumptionBillingEntityName) + assert.Nil(t, account.MarketplaceConsumerBillingEntityName) + assert.Nil(t, account.MarketplaceProviderBillingEntityName) + assert.Nil(t, account.OldAccountURL) + assert.Nil(t, account.IsOrgAdmin) + assert.Nil(t, account.IsOrgAdmin) + assert.Nil(t, account.AccountOldUrlSavedOn) + assert.Nil(t, account.AccountOldUrlLastUsed) + assert.Nil(t, account.OrganizationOldUrl) + assert.Nil(t, account.OrganizationOldUrlSavedOn) + assert.Nil(t, account.OrganizationOldUrlLastUsed) + assert.Nil(t, account.IsEventsAccount) } - _, err := client.Accounts.ShowByID(ctx, NonExistingAccountObjectIdentifier) - require.ErrorIs(t, err, sdk.ErrObjectNotExistOrAuthorized) -} -func TestInt_AccountCreate(t *testing.T) { - client := testClient(t) - ctx := testContext(t) - ok := testClientHelper().Context.IsRoleInSession(t, snowflakeroles.Orgadmin) - if !ok { - t.Skip("ORGADMIN role is not in current session") + assertHistoryAccount := func(t *testing.T, account sdk.Account, accountName string) { + t.Helper() + assertAccountQueriedByOrgAdmin(t, account, currentAccountName) + assert.Nil(t, account.DroppedOn) + assert.Nil(t, account.ScheduledDeletionTime) + assert.Nil(t, account.RestoredOn) + assert.Empty(t, account.MovedToOrganization) + assert.Nil(t, account.MovedOn) + assert.Nil(t, account.OrganizationUrlExpirationOn) } - t.Run("complete case", func(t *testing.T) { - accountID := testClientHelper().Ids.RandomAccountObjectIdentifier() + + t.Run("create: minimal", func(t *testing.T) { + id := testClientHelper().Ids.RandomAccountObjectIdentifier() + name := testClientHelper().Ids.Alpha() + password := random.Password() + email := random.Email() + + err := client.Accounts.Create(ctx, id, &sdk.CreateAccountOptions{ + AdminName: name, + AdminPassword: sdk.String(password), + Email: email, + Edition: sdk.EditionStandard, + }) + require.NoError(t, err) + t.Cleanup(testClientHelper().Account.DropFunc(t, id)) + + acc, err := client.Accounts.ShowByID(ctx, id) + require.NoError(t, err) + require.Equal(t, id, acc.ID()) + }) + + t.Run("create: user type service", func(t *testing.T) { + id := testClientHelper().Ids.RandomAccountObjectIdentifier() + name := testClientHelper().Ids.Alpha() + key, _ := random.GenerateRSAPublicKey(t) + email := random.Email() + + err := client.Accounts.Create(ctx, id, &sdk.CreateAccountOptions{ + AdminName: name, + AdminRSAPublicKey: sdk.String(key), + AdminUserType: sdk.Pointer(sdk.UserTypeService), + Email: email, + Edition: sdk.EditionStandard, + }) + require.NoError(t, err) + t.Cleanup(testClientHelper().Account.DropFunc(t, id)) + + acc, err := client.Accounts.ShowByID(ctx, id) + require.NoError(t, err) + require.Equal(t, id, acc.ID()) + }) + + t.Run("create: user type legacy service", func(t *testing.T) { + id := testClientHelper().Ids.RandomAccountObjectIdentifier() + name := testClientHelper().Ids.Alpha() + password := random.Password() + email := random.Email() + + err := client.Accounts.Create(ctx, id, &sdk.CreateAccountOptions{ + AdminName: name, + AdminPassword: sdk.String(password), + AdminUserType: sdk.Pointer(sdk.UserTypeLegacyService), + Email: email, + Edition: sdk.EditionStandard, + }) + require.NoError(t, err) + t.Cleanup(testClientHelper().Account.DropFunc(t, id)) + + acc, err := client.Accounts.ShowByID(ctx, id) + require.NoError(t, err) + require.Equal(t, id, acc.ID()) + }) + + t.Run("create: complete", func(t *testing.T) { + id := testClientHelper().Ids.RandomAccountObjectIdentifier() + name := testClientHelper().Ids.Alpha() + password := random.Password() + email := random.Email() region := testClientHelper().Context.CurrentRegion(t) + regions := testClientHelper().Account.ShowRegions(t) + currentRegion, err := collections.FindFirst(regions, func(r helpers.Region) bool { + return r.SnowflakeRegion == region + }) + require.NoError(t, err) + comment := random.Comment() + + err = client.Accounts.Create(ctx, id, &sdk.CreateAccountOptions{ + AdminName: name, + AdminPassword: sdk.String(password), + FirstName: sdk.String("firstName"), + LastName: sdk.String("lastName"), + Email: email, + MustChangePassword: sdk.Bool(true), + Edition: sdk.EditionStandard, + RegionGroup: sdk.String("PUBLIC"), + Region: sdk.String(currentRegion.SnowflakeRegion), + Comment: sdk.String(comment), + // TODO(SNOW-1844776): with polaris Snowflake returns an error saying: "invalid property polaris for account" + // Polaris: sdk.Bool(true), + }) + require.NoError(t, err) + t.Cleanup(testClientHelper().Account.DropFunc(t, id)) + + acc, err := client.Accounts.ShowByID(ctx, id) + require.NoError(t, err) + require.Equal(t, id, acc.ID()) + }) + + t.Run("alter: set / unset is org admin", func(t *testing.T) { + account, accountCleanup := testClientHelper().Account.Create(t) + t.Cleanup(accountCleanup) - opts := &sdk.CreateAccountOptions{ - AdminName: "someadmin", - AdminPassword: sdk.String(random.Password()), - FirstName: sdk.String("Ad"), - LastName: sdk.String("Min"), - Email: "admin@example.com", - MustChangePassword: sdk.Bool(false), - Edition: sdk.EditionBusinessCritical, - Comment: sdk.String("Please delete me!"), - Region: sdk.String(region), - } - err := client.Accounts.Create(ctx, accountID, opts) - require.NoError(t, err) - - var account *sdk.Account - err = retry.Do( - func() error { - account, err = client.Accounts.ShowByID(ctx, accountID) - return err + require.Equal(t, false, *account.IsOrgAdmin) + + err := client.Accounts.Alter(ctx, &sdk.AlterAccountOptions{ + SetIsOrgAdmin: &sdk.AccountSetIsOrgAdmin{ + Name: account.ID(), + OrgAdmin: true, + }, + }) + require.NoError(t, err) + + acc, err := client.Accounts.ShowByID(ctx, account.ID()) + require.NoError(t, err) + require.Equal(t, true, *acc.IsOrgAdmin) + + err = client.Accounts.Alter(ctx, &sdk.AlterAccountOptions{ + SetIsOrgAdmin: &sdk.AccountSetIsOrgAdmin{ + Name: account.ID(), + OrgAdmin: false, }, - retry.OnRetry(func(n uint, err error) { - log.Printf("[DEBUG] Retrying client.Accounts.ShowByID: #%d", n+1) - }), - retry.Delay(1*time.Second), - retry.Attempts(3), - ) - require.NoError(t, err) - assert.Equal(t, accountID.Name(), account.AccountName) - assert.Equal(t, sdk.EditionBusinessCritical, account.Edition) - assert.Equal(t, "Please delete me!", account.Comment) - assert.Equal(t, region, account.SnowflakeRegion) - - // rename - newAccountID := testClientHelper().Ids.RandomAccountObjectIdentifier() - alterOpts := &sdk.AlterAccountOptions{ + }) + require.NoError(t, err) + + acc, err = client.Accounts.ShowByID(ctx, account.ID()) + require.NoError(t, err) + require.Equal(t, false, *acc.IsOrgAdmin) + }) + + t.Run("alter: rename", func(t *testing.T) { + oldAccount, oldAccountCleanup := testClientHelper().Account.Create(t) + t.Cleanup(oldAccountCleanup) + + newName := testClientHelper().Ids.RandomAccountObjectIdentifier() + t.Cleanup(testClientHelper().Account.DropFunc(t, newName)) + + err := client.Accounts.Alter(ctx, &sdk.AlterAccountOptions{ Rename: &sdk.AccountRename{ - Name: accountID, - NewName: newAccountID, - SaveOldURL: sdk.Bool(true), + Name: oldAccount.ID(), + NewName: newName, }, - } - err = client.Accounts.Alter(ctx, alterOpts) + }) require.NoError(t, err) - err = retry.Do( - func() error { - account, err = client.Accounts.ShowByID(ctx, newAccountID) - return err + _, err = client.Accounts.ShowByID(ctx, oldAccount.ID()) + require.ErrorIs(t, err, collections.ErrObjectNotFound) + + newAccount, err := client.Accounts.ShowByID(ctx, newName) + require.NoError(t, err) + require.NotNil(t, newAccount) + require.NotEqual(t, oldAccount.AccountURL, newAccount.AccountURL) + require.Equal(t, oldAccount.AccountURL, newAccount.OldAccountURL) + }) + + t.Run("alter: rename with new url", func(t *testing.T) { + account, accountCleanup := testClientHelper().Account.Create(t) + t.Cleanup(accountCleanup) + + newName := testClientHelper().Ids.RandomAccountObjectIdentifier() + t.Cleanup(testClientHelper().Account.DropFunc(t, newName)) + + err := client.Accounts.Alter(ctx, &sdk.AlterAccountOptions{ + Rename: &sdk.AccountRename{ + Name: account.ID(), + NewName: newName, + SaveOldURL: sdk.Bool(false), }, - retry.OnRetry(func(n uint, err error) { - log.Printf("[DEBUG] Retrying client.Accounts.ShowByID: #%d", n+1) - }), - retry.Delay(1*time.Second), - retry.Attempts(3), - ) + }) require.NoError(t, err) - assert.Equal(t, newAccountID.Name(), account.AccountName) - // drop old url - alterOpts = &sdk.AlterAccountOptions{ + _, err = client.Accounts.ShowByID(ctx, account.ID()) + require.ErrorIs(t, err, collections.ErrObjectNotFound) + + acc, err := client.Accounts.ShowByID(ctx, newName) + require.NoError(t, err) + require.NotEqual(t, account.AccountURL, acc.AccountURL) + require.Empty(t, acc.OldAccountURL) + }) + + t.Run("alter: drop url when there's no old url", func(t *testing.T) { + account, accountCleanup := testClientHelper().Account.Create(t) + t.Cleanup(accountCleanup) + + err := client.Accounts.Alter(ctx, &sdk.AlterAccountOptions{ Drop: &sdk.AccountDrop{ - Name: newAccountID, - OldURL: sdk.Bool(true), + Name: account.ID(), + OldUrl: sdk.Bool(true), }, - } - err = client.Accounts.Alter(ctx, alterOpts) + }) + require.ErrorContains(t, err, "The account has no old url") + }) + + t.Run("alter: drop url after rename", func(t *testing.T) { + account, accountCleanup := testClientHelper().Account.Create(t) + t.Cleanup(accountCleanup) + + newName := testClientHelper().Ids.RandomAccountObjectIdentifier() + t.Cleanup(testClientHelper().Account.DropFunc(t, newName)) + + err := client.Accounts.Alter(ctx, &sdk.AlterAccountOptions{ + Rename: &sdk.AccountRename{ + Name: account.ID(), + NewName: newName, + }, + }) require.NoError(t, err) - _, err = client.Accounts.ShowByID(ctx, newAccountID) + + acc, err := client.Accounts.ShowByID(ctx, newName) require.NoError(t, err) + require.NotEmpty(t, acc.OldAccountURL) - // drop account - err = client.Accounts.Drop(ctx, newAccountID, 3, &sdk.DropAccountOptions{ - IfExists: sdk.Bool(true), + err = client.Accounts.Alter(ctx, &sdk.AlterAccountOptions{ + Drop: &sdk.AccountDrop{ + Name: newName, + OldUrl: sdk.Bool(true), + }, }) require.NoError(t, err) - // check if account is dropped - _, err = client.Accounts.ShowByID(ctx, newAccountID) + acc, err = client.Accounts.ShowByID(ctx, newName) + require.NoError(t, err) + require.Empty(t, acc.OldAccountURL) + }) + + // TODO(SNOW-1844776): This cannot be tested as it requires capabilities of moving accounts between organizations. + + t.Run("drop: without options", func(t *testing.T) { + err := client.Accounts.Drop(ctx, sdk.NewAccountObjectIdentifier("non-existing-account"), 3, &sdk.DropAccountOptions{}) require.Error(t, err) - // undrop account - err = client.Accounts.Undrop(ctx, newAccountID) + account, accountCleanup := testClientHelper().Account.Create(t) + t.Cleanup(accountCleanup) + + err = client.Accounts.Drop(ctx, account.ID(), 3, &sdk.DropAccountOptions{}) require.NoError(t, err) - // check if account is undropped - _, err = client.Accounts.ShowByID(ctx, newAccountID) + _, err = client.Accounts.ShowByID(ctx, account.ID()) + require.ErrorIs(t, err, collections.ErrObjectNotFound) + }) + + t.Run("drop: with if exists", func(t *testing.T) { + err := client.Accounts.Drop(ctx, sdk.NewAccountObjectIdentifier("non-existing-account"), 3, &sdk.DropAccountOptions{IfExists: sdk.Bool(true)}) require.NoError(t, err) - // drop account again - err = client.Accounts.Drop(ctx, newAccountID, 3, nil) + account, accountCleanup := testClientHelper().Account.Create(t) + t.Cleanup(accountCleanup) + + err = client.Accounts.Drop(ctx, account.ID(), 3, &sdk.DropAccountOptions{IfExists: sdk.Bool(true)}) require.NoError(t, err) - // check if account is dropped - _, err = client.Accounts.ShowByID(ctx, newAccountID) - require.Error(t, err) + _, err = client.Accounts.ShowByID(ctx, account.ID()) + require.ErrorIs(t, err, collections.ErrObjectNotFound) + }) + + t.Run("undrop", func(t *testing.T) { + account, accountCleanup := testClientHelper().Account.Create(t) + t.Cleanup(accountCleanup) + + require.NoError(t, testClientHelper().Account.Drop(t, account.ID())) + + err := client.Accounts.Undrop(ctx, account.ID()) + require.NoError(t, err) + + acc, err := client.Accounts.ShowByID(ctx, account.ID()) + require.NoError(t, err) + require.Equal(t, account.ID(), acc.ID()) + }) + + t.Run("show: with like", func(t *testing.T) { + currentAccount := testClientHelper().Context.CurrentAccount(t) + accounts, err := client.Accounts.Show(ctx, &sdk.ShowAccountOptions{ + Like: &sdk.Like{ + Pattern: sdk.String(currentAccount), + }, + }) + require.NoError(t, err) + assert.Equal(t, 1, len(accounts)) + assertAccountQueriedByOrgAdmin(t, accounts[0], currentAccountName) + }) + + t.Run("show: with history", func(t *testing.T) { + currentAccount := testClientHelper().Context.CurrentAccount(t) + accounts, err := client.Accounts.Show(ctx, &sdk.ShowAccountOptions{ + History: sdk.Bool(true), + Like: &sdk.Like{ + Pattern: sdk.String(currentAccount), + }, + }) + require.NoError(t, err) + assert.Equal(t, 1, len(accounts)) + assertHistoryAccount(t, accounts[0], currentAccountName) + }) + + t.Run("show: with accountadmin role", func(t *testing.T) { + err := client.Roles.Use(ctx, sdk.NewUseRoleRequest(snowflakeroles.Accountadmin)) + require.NoError(t, err) + t.Cleanup(func() { + err = client.Roles.Use(ctx, sdk.NewUseRoleRequest(snowflakeroles.Orgadmin)) + require.NoError(t, err) + }) + + currentAccount := testClientHelper().Context.CurrentAccount(t) + accounts, err := client.Accounts.Show(ctx, &sdk.ShowAccountOptions{ + Like: &sdk.Like{ + Pattern: sdk.String(currentAccount), + }, + }) + require.NoError(t, err) + assert.Equal(t, 1, len(accounts)) + assertAccountQueriedByAccountAdmin(t, accounts[0], currentAccountName) }) } -func TestInt_AccountAlter(t *testing.T) { +func TestInt_Account_SelfAlter(t *testing.T) { + t.Skip("TODO(SNOW-1844776): Adjust the test so that self alters will be done on newly created account - not the main test one") + testenvs.GetOrSkipTest(t, testenvs.TestAccountCreate) + + // This client should be operating on a different account than the "main" one (because it will be altered here). + // Cannot use a newly created account because ORGADMIN role is necessary, + // and it is propagated only after some time (e.g., 1 hour) making it hard to automate. client := testClient(t) ctx := testContext(t) - ok := testClientHelper().Context.IsRoleInSession(t, snowflakeroles.Accountadmin) - if !ok { - t.Skip("ACCOUNTADMIN role is not in current session") + t.Cleanup(testClientHelper().Role.UseRole(t, snowflakeroles.Accountadmin)) + + assertParameterIsDefault := func(t *testing.T, parameters []*sdk.Parameter, parameterKey string) { + t.Helper() + param, err := collections.FindFirst(parameters, func(parameter *sdk.Parameter) bool { return parameter.Key == parameterKey }) + require.NoError(t, err) + require.NotNil(t, param) + require.Equal(t, (*param).Default, (*param).Value) + require.Equal(t, sdk.ParameterType(""), (*param).Level) } - t.Run("set and unset params", func(t *testing.T) { - opts := &sdk.AlterAccountOptions{ + + assertParameterValueSetOnAccount := func(t *testing.T, parameters []*sdk.Parameter, parameterKey string, parameterValue string) { + t.Helper() + param, err := collections.FindFirst(parameters, func(parameter *sdk.Parameter) bool { return parameter.Key == parameterKey }) + require.NoError(t, err) + require.NotNil(t, param) + require.Equal(t, parameterValue, (*param).Value) + require.Equal(t, sdk.ParameterTypeAccount, (*param).Level) + } + + t.Run("set / unset parameters", func(t *testing.T) { + parameters, err := client.Accounts.ShowParameters(ctx) + require.NoError(t, err) + require.NotEmpty(t, parameters) + + assertParameterIsDefault(t, parameters, string(sdk.AccountParameterMinDataRetentionTimeInDays)) + assertParameterIsDefault(t, parameters, string(sdk.AccountParameterJSONIndent)) + assertParameterIsDefault(t, parameters, string(sdk.AccountParameterUserTaskTimeoutMs)) + assertParameterIsDefault(t, parameters, string(sdk.AccountParameterEnableUnredactedQuerySyntaxError)) + + err = client.Accounts.Alter(ctx, &sdk.AlterAccountOptions{ Set: &sdk.AccountSet{ Parameters: &sdk.AccountLevelParameters{ AccountParameters: &sdk.AccountParameters{ - ClientEncryptionKeySize: sdk.Int(128), - PreventUnloadToInternalStages: sdk.Bool(true), + MinDataRetentionTimeInDays: sdk.Int(15), // default is 0 }, SessionParameters: &sdk.SessionParameters{ - JSONIndent: sdk.Int(16), + JSONIndent: sdk.Int(8), // default is 2 }, ObjectParameters: &sdk.ObjectParameters{ - MaxDataExtensionTimeInDays: sdk.Int(30), + UserTaskTimeoutMs: sdk.Int(100), // default is 3600000 + }, + UserParameters: &sdk.UserParameters{ + EnableUnredactedQuerySyntaxError: sdk.Bool(true), // default is false }, }, }, - } - err := client.Accounts.Alter(ctx, opts) - require.NoError(t, err) - p, err := client.Parameters.ShowAccountParameter(ctx, sdk.AccountParameterClientEncryptionKeySize) - require.NoError(t, err) - assert.Equal(t, 128, sdk.ToInt(p.Value)) - p, err = client.Parameters.ShowAccountParameter(ctx, sdk.AccountParameterPreventUnloadToInternalStages) - require.NoError(t, err) - assert.Equal(t, true, sdk.ToBool(p.Value)) - p, err = client.Parameters.ShowAccountParameter(ctx, sdk.AccountParameterJSONIndent) + }) require.NoError(t, err) - assert.Equal(t, 16, sdk.ToInt(p.Value)) - p, err = client.Parameters.ShowAccountParameter(ctx, sdk.AccountParameterMaxDataExtensionTimeInDays) + + parameters, err = client.Accounts.ShowParameters(ctx) require.NoError(t, err) - assert.Equal(t, 30, sdk.ToInt(p.Value)) + require.NotEmpty(t, parameters) - opts = &sdk.AlterAccountOptions{ + assertParameterValueSetOnAccount(t, parameters, string(sdk.AccountParameterMinDataRetentionTimeInDays), "15") + assertParameterValueSetOnAccount(t, parameters, string(sdk.AccountParameterJSONIndent), "8") + assertParameterValueSetOnAccount(t, parameters, string(sdk.AccountParameterUserTaskTimeoutMs), "100") + assertParameterValueSetOnAccount(t, parameters, string(sdk.AccountParameterEnableUnredactedQuerySyntaxError), "true") + + err = client.Accounts.Alter(ctx, &sdk.AlterAccountOptions{ Unset: &sdk.AccountUnset{ Parameters: &sdk.AccountLevelParametersUnset{ AccountParameters: &sdk.AccountParametersUnset{ - ClientEncryptionKeySize: sdk.Bool(true), - PreventUnloadToInternalStages: sdk.Bool(true), + MinDataRetentionTimeInDays: sdk.Bool(true), }, SessionParameters: &sdk.SessionParametersUnset{ JSONIndent: sdk.Bool(true), }, ObjectParameters: &sdk.ObjectParametersUnset{ - MaxDataExtensionTimeInDays: sdk.Bool(true), + UserTaskTimeoutMs: sdk.Bool(true), + }, + UserParameters: &sdk.UserParametersUnset{ + EnableUnredactedQuerySyntaxError: sdk.Bool(true), }, }, }, - } - err = client.Accounts.Alter(ctx, opts) + }) + require.NoError(t, err) + + parameters, err = client.Accounts.ShowParameters(ctx) require.NoError(t, err) + require.NotEmpty(t, parameters) + + assertParameterIsDefault(t, parameters, string(sdk.AccountParameterMinDataRetentionTimeInDays)) + assertParameterIsDefault(t, parameters, string(sdk.AccountParameterJSONIndent)) + assertParameterIsDefault(t, parameters, string(sdk.AccountParameterUserTaskTimeoutMs)) + assertParameterIsDefault(t, parameters, string(sdk.AccountParameterEnableUnredactedQuerySyntaxError)) }) - t.Run("set resource monitor", func(t *testing.T) { - resourceMonitorTest, resourceMonitorCleanup := testClientHelper().ResourceMonitor.CreateResourceMonitor(t) + assertPolicySet := func(t *testing.T, id sdk.SchemaObjectIdentifier) { + t.Helper() + + policies, err := testClientHelper().PolicyReferences.GetPolicyReferences(t, sdk.NewAccountObjectIdentifier(client.GetAccountLocator()), sdk.PolicyEntityDomainAccount) + require.NoError(t, err) + _, err = collections.FindFirst(policies, func(reference sdk.PolicyReference) bool { + return reference.PolicyName == id.Name() + }) + require.NoError(t, err) + } + + assertPolicyNotSet := func(t *testing.T) { + t.Helper() + + policies, err := testClientHelper().PolicyReferences.GetPolicyReferences(t, sdk.NewAccountObjectIdentifier(client.GetAccountLocator()), sdk.PolicyEntityDomainAccount) + require.Len(t, policies, 0) + require.NoError(t, err) + } + + t.Run("set / unset resource monitor", func(t *testing.T) { + resourceMonitor, resourceMonitorCleanup := testClientHelper().ResourceMonitor.CreateResourceMonitor(t) t.Cleanup(resourceMonitorCleanup) - opts := &sdk.AlterAccountOptions{ + + require.Nil(t, resourceMonitor.Level) + err := client.Accounts.Alter(ctx, &sdk.AlterAccountOptions{ Set: &sdk.AccountSet{ - ResourceMonitor: resourceMonitorTest.ID(), + ResourceMonitor: resourceMonitor.ID(), }, - } - err := client.Accounts.Alter(ctx, opts) + }) require.NoError(t, err) + + resourceMonitor, err = testClientHelper().ResourceMonitor.Show(t, resourceMonitor.ID()) + require.NoError(t, err) + require.NotNil(t, resourceMonitor.Level) + require.Equal(t, sdk.ResourceMonitorLevelAccount, *resourceMonitor.Level) + + err = client.Accounts.Alter(ctx, &sdk.AlterAccountOptions{ + Unset: &sdk.AccountUnset{ + ResourceMonitor: sdk.Bool(true), + }, + }) + require.NoError(t, err) + + resourceMonitor, err = testClientHelper().ResourceMonitor.Show(t, resourceMonitor.ID()) + require.NoError(t, err) + require.Nil(t, resourceMonitor.Level) }) - t.Run("set and unset password policy", func(t *testing.T) { - passwordPolicyTest, passwordPolicyCleanup := testClientHelper().PasswordPolicy.CreatePasswordPolicy(t) + t.Run("set / unset policies", func(t *testing.T) { + authPolicy, authPolicyCleanup := testClientHelper().AuthenticationPolicy.Create(t) + t.Cleanup(authPolicyCleanup) + + passwordPolicy, passwordPolicyCleanup := testClientHelper().PasswordPolicy.CreatePasswordPolicy(t) t.Cleanup(passwordPolicyCleanup) - opts := &sdk.AlterAccountOptions{ + + sessionPolicy, sessionPolicyCleanup := testClientHelper().SessionPolicy.CreateSessionPolicy(t) + t.Cleanup(sessionPolicyCleanup) + + packagesPolicyId, packagesPolicyCleanup := testClientHelper().PackagesPolicy.Create(t) + t.Cleanup(packagesPolicyCleanup) + + err := client.Accounts.Alter(ctx, &sdk.AlterAccountOptions{ Set: &sdk.AccountSet{ - PasswordPolicy: passwordPolicyTest.ID(), + PackagesPolicy: packagesPolicyId, }, - } - err := client.Accounts.Alter(ctx, opts) + }) require.NoError(t, err) + t.Cleanup(func() { + assert.NoError(t, client.Accounts.Alter(ctx, &sdk.AlterAccountOptions{ + Unset: &sdk.AccountUnset{ + PackagesPolicy: sdk.Bool(true), + }, + })) + }) - // now unset - opts = &sdk.AlterAccountOptions{ - Unset: &sdk.AccountUnset{ - PasswordPolicy: sdk.Bool(true), + err = client.Accounts.Alter(ctx, &sdk.AlterAccountOptions{ + Set: &sdk.AccountSet{ + PasswordPolicy: passwordPolicy.ID(), }, - } - err = client.Accounts.Alter(ctx, opts) + }) require.NoError(t, err) - }) + t.Cleanup(func() { + assert.NoError(t, client.Accounts.Alter(ctx, &sdk.AlterAccountOptions{ + Unset: &sdk.AccountUnset{ + PasswordPolicy: sdk.Bool(true), + }, + })) + }) - t.Run("set and unset session policy", func(t *testing.T) { - sessionPolicyTest, sessionPolicyCleanup := testClientHelper().SessionPolicy.CreateSessionPolicy(t) - t.Cleanup(sessionPolicyCleanup) + err = client.Accounts.Alter(ctx, &sdk.AlterAccountOptions{ + Set: &sdk.AccountSet{ + SessionPolicy: sessionPolicy.ID(), + }, + }) + require.NoError(t, err) + t.Cleanup(func() { + assert.NoError(t, client.Accounts.Alter(ctx, &sdk.AlterAccountOptions{ + Unset: &sdk.AccountUnset{ + SessionPolicy: sdk.Bool(true), + }, + })) + }) - opts := &sdk.AlterAccountOptions{ + err = client.Accounts.Alter(ctx, &sdk.AlterAccountOptions{ Set: &sdk.AccountSet{ - SessionPolicy: sessionPolicyTest.ID(), + AuthenticationPolicy: authPolicy.ID(), + }, + }) + require.NoError(t, err) + t.Cleanup(func() { + assert.NoError(t, client.Accounts.Alter(ctx, &sdk.AlterAccountOptions{ + Unset: &sdk.AccountUnset{ + AuthenticationPolicy: sdk.Bool(true), + }, + })) + }) + + assertPolicySet(t, authPolicy.ID()) + assertPolicySet(t, passwordPolicy.ID()) + assertPolicySet(t, sessionPolicy.ID()) + assertPolicySet(t, packagesPolicyId) + + err = client.Accounts.Alter(ctx, &sdk.AlterAccountOptions{ + Unset: &sdk.AccountUnset{ + PackagesPolicy: sdk.Bool(true), + }, + }) + require.NoError(t, err) + + err = client.Accounts.Alter(ctx, &sdk.AlterAccountOptions{ + Unset: &sdk.AccountUnset{ + PasswordPolicy: sdk.Bool(true), }, - } - err := client.Accounts.Alter(ctx, opts) + }) require.NoError(t, err) - // now unset - opts = &sdk.AlterAccountOptions{ + err = client.Accounts.Alter(ctx, &sdk.AlterAccountOptions{ Unset: &sdk.AccountUnset{ SessionPolicy: sdk.Bool(true), }, - } - err = client.Accounts.Alter(ctx, opts) + }) + require.NoError(t, err) + + err = client.Accounts.Alter(ctx, &sdk.AlterAccountOptions{ + Unset: &sdk.AccountUnset{ + AuthenticationPolicy: sdk.Bool(true), + }, + }) require.NoError(t, err) + + assertPolicyNotSet(t) }) - t.Run("set and unset authentication policy", func(t *testing.T) { - t.Skipf("Skipping the test for now TODO: add ticket number") - authenticationPolicyTest, authenticationPolicyCleanup := testClientHelper().AuthenticationPolicy.Create(t) - t.Cleanup(authenticationPolicyCleanup) - opts := &sdk.AlterAccountOptions{ + t.Run("force new packages policy", func(t *testing.T) { + packagesPolicyId, packagesPolicyCleanup := testClientHelper().PackagesPolicy.Create(t) + t.Cleanup(packagesPolicyCleanup) + + newPackagesPolicyId, newPackagesPolicyCleanup := testClientHelper().PackagesPolicy.Create(t) + t.Cleanup(newPackagesPolicyCleanup) + + err := client.Accounts.Alter(ctx, &sdk.AlterAccountOptions{ Set: &sdk.AccountSet{ - AuthenticationPolicy: authenticationPolicyTest.ID(), + PackagesPolicy: packagesPolicyId, }, - } - err := client.Accounts.Alter(ctx, opts) + }) require.NoError(t, err) + assertPolicySet(t, packagesPolicyId) + t.Cleanup(func() { + err = client.Accounts.Alter(ctx, &sdk.AlterAccountOptions{ + Unset: &sdk.AccountUnset{ + PackagesPolicy: sdk.Bool(true), + }, + }) + require.NoError(t, err) + assertPolicyNotSet(t) + }) - // now unset - opts = &sdk.AlterAccountOptions{ - Unset: &sdk.AccountUnset{ - AuthenticationPolicy: sdk.Bool(true), + err = client.Accounts.Alter(ctx, &sdk.AlterAccountOptions{ + Set: &sdk.AccountSet{ + PackagesPolicy: newPackagesPolicyId, }, - } - err = client.Accounts.Alter(ctx, opts) + }) + require.Error(t, err) + + err = client.Accounts.Alter(ctx, &sdk.AlterAccountOptions{ + Set: &sdk.AccountSet{ + PackagesPolicy: newPackagesPolicyId, + Force: sdk.Bool(true), + }, + }) require.NoError(t, err) + assertPolicySet(t, newPackagesPolicyId) }) } diff --git a/pkg/sdk/testint/external_volumes_gen_integration_test.go b/pkg/sdk/testint/external_volumes_gen_integration_test.go index 83f663b188..4c40c1228f 100644 --- a/pkg/sdk/testint/external_volumes_gen_integration_test.go +++ b/pkg/sdk/testint/external_volumes_gen_integration_test.go @@ -20,7 +20,7 @@ func TestInt_ExternalVolumes(t *testing.T) { awsKmsKeyId := testenvs.GetOrSkipTest(t, testenvs.AwsExternalKeyId) awsExternalId := "123456789" - gcsBaseUrl := testenvs.GetOrSkipTest(t, testenvs.GcsExternalBuckerUrl) + gcsBaseUrl := testenvs.GetOrSkipTest(t, testenvs.GcsExternalBucketUrl) gcsKmsKeyId := "123456789" azureBaseUrl := testenvs.GetOrSkipTest(t, testenvs.AzureExternalBucketUrl) diff --git a/pkg/sdk/testint/setup_test.go b/pkg/sdk/testint/setup_test.go index 83f696faca..ee949d3d67 100644 --- a/pkg/sdk/testint/setup_test.go +++ b/pkg/sdk/testint/setup_test.go @@ -9,6 +9,8 @@ import ( "testing" "time" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/snowflakeroles" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/helpers" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/helpers/random" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance/testenvs" @@ -128,6 +130,15 @@ func (itc *integrationTestContext) initialize() error { itc.client = c itc.ctx = context.Background() + // TODO(SNOW-1842271): Adjust test setup to work properly with Accountadmin role for object tests and Orgadmin for account tests + if os.Getenv(string(testenvs.TestAccountCreate)) != "" { + err = c.Sessions.UseRole(context.Background(), snowflakeroles.Accountadmin) + if err != nil { + return err + } + defer func() { _ = c.Sessions.UseRole(context.Background(), snowflakeroles.Orgadmin) }() + } + db, dbCleanup, err := createDb(itc.client, itc.ctx, false) itc.databaseCleanup = dbCleanup if err != nil { @@ -198,13 +209,16 @@ func (itc *integrationTestContext) initialize() error { return err } - err = helpers.EnsureScimProvisionerRolesExist(itc.client, itc.ctx) - if err != nil { - return err - } - err = helpers.EnsureScimProvisionerRolesExist(itc.secondaryClient, itc.secondaryCtx) - if err != nil { - return err + // TODO(SNOW-1842271): Adjust test setup to work properly with Accountadmin role for object tests and Orgadmin for account tests + if os.Getenv(string(testenvs.TestAccountCreate)) == "" { + err = helpers.EnsureScimProvisionerRolesExist(itc.client, itc.ctx) + if err != nil { + return err + } + err = helpers.EnsureScimProvisionerRolesExist(itc.secondaryClient, itc.secondaryCtx) + if err != nil { + return err + } } return nil diff --git a/pkg/sdk/testint/stages_gen_integration_test.go b/pkg/sdk/testint/stages_gen_integration_test.go index 5bb3a94198..ff2a5a94e9 100644 --- a/pkg/sdk/testint/stages_gen_integration_test.go +++ b/pkg/sdk/testint/stages_gen_integration_test.go @@ -19,7 +19,7 @@ func TestInt_Stages(t *testing.T) { awsBucketUrl := testenvs.GetOrSkipTest(t, testenvs.AwsExternalBucketUrl) awsKeyId := testenvs.GetOrSkipTest(t, testenvs.AwsExternalKeyId) awsSecretKey := testenvs.GetOrSkipTest(t, testenvs.AwsExternalSecretKey) - gcsBucketUrl := testenvs.GetOrSkipTest(t, testenvs.GcsExternalBuckerUrl) + gcsBucketUrl := testenvs.GetOrSkipTest(t, testenvs.GcsExternalBucketUrl) azureBucketUrl := testenvs.GetOrSkipTest(t, testenvs.AzureExternalBucketUrl) azureSasToken := testenvs.GetOrSkipTest(t, testenvs.AzureExternalSasToken) diff --git a/pkg/sdk/testint/storage_integration_gen_integration_test.go b/pkg/sdk/testint/storage_integration_gen_integration_test.go index 1ff3aab9d9..ddbd082d5b 100644 --- a/pkg/sdk/testint/storage_integration_gen_integration_test.go +++ b/pkg/sdk/testint/storage_integration_gen_integration_test.go @@ -18,7 +18,7 @@ func TestInt_StorageIntegrations(t *testing.T) { awsBucketUrl := testenvs.GetOrSkipTest(t, testenvs.AwsExternalBucketUrl) awsRoleARN := testenvs.GetOrSkipTest(t, testenvs.AwsExternalRoleArn) - gcsBucketUrl := testenvs.GetOrSkipTest(t, testenvs.GcsExternalBuckerUrl) + gcsBucketUrl := testenvs.GetOrSkipTest(t, testenvs.GcsExternalBucketUrl) azureBucketUrl := testenvs.GetOrSkipTest(t, testenvs.AzureExternalBucketUrl) azureTenantId := testenvs.GetOrSkipTest(t, testenvs.AzureExternalTenantId)