diff --git a/docs/resources/user_password_policy_attachment.md b/docs/resources/user_password_policy_attachment.md new file mode 100644 index 0000000000..28d19855b6 --- /dev/null +++ b/docs/resources/user_password_policy_attachment.md @@ -0,0 +1,53 @@ +--- +# generated by https://github.com/hashicorp/terraform-plugin-docs +page_title: "snowflake_user_password_policy_attachment Resource - terraform-provider-snowflake" +subcategory: "" +description: |- + Specifies the password policy to use for a certain user. +--- + +# snowflake_user_password_policy_attachment (Resource) + +Specifies the password policy to use for a certain user. + +## Example Usage + +```terraform +resource "snowflake_user" "user" { + name = "USER_NAME" +} +resource "snowflake_password_policy" "pp" { + database = "prod" + schema = "security" + name = "default_policy" +} + +resource "snowflake_user_password_policy_attachment" "ppa" { + password_policy_database = snowflake_password_policy.pp.database + password_policy_schema = snowflake_password_policy.pp.schema + password_policy_name = snowflake_password_policy.pp.name + user_name = snowflake_user.user.name +} +``` + + +## Schema + +### Required + +- `password_policy_database` (String) Database name where the password policy is stored +- `password_policy_name` (String) Non-qualified name of the password policy +- `password_policy_schema` (String) Schema name where the password policy is stored +- `user_name` (String) User name of the user you want to attach the password policy to + +### Read-Only + +- `id` (String) The ID of this resource. + +## Import + +Import is supported using the following syntax: + +```shell +terraform import snowflake_user_password_policy_attachment.example "MY_DATABASE|MY_SCHEMA|PASSWORD_POLICY_NAME|USER_NAME" +``` diff --git a/examples/resources/snowflake_user_password_policy_attachment/import.sh b/examples/resources/snowflake_user_password_policy_attachment/import.sh new file mode 100644 index 0000000000..196afce86b --- /dev/null +++ b/examples/resources/snowflake_user_password_policy_attachment/import.sh @@ -0,0 +1 @@ +terraform import snowflake_user_password_policy_attachment.example "MY_DATABASE|MY_SCHEMA|PASSWORD_POLICY_NAME|USER_NAME" diff --git a/examples/resources/snowflake_user_password_policy_attachment/resource.tf b/examples/resources/snowflake_user_password_policy_attachment/resource.tf new file mode 100644 index 0000000000..b718dcc5da --- /dev/null +++ b/examples/resources/snowflake_user_password_policy_attachment/resource.tf @@ -0,0 +1,15 @@ +resource "snowflake_user" "user" { + name = "USER_NAME" +} +resource "snowflake_password_policy" "pp" { + database = "prod" + schema = "security" + name = "default_policy" +} + +resource "snowflake_user_password_policy_attachment" "ppa" { + password_policy_database = snowflake_password_policy.pp.database + password_policy_schema = snowflake_password_policy.pp.schema + password_policy_name = snowflake_password_policy.pp.name + user_name = snowflake_user.user.name +} diff --git a/pkg/provider/provider.go b/pkg/provider/provider.go index 92a0e0dc49..0208fd3bde 100644 --- a/pkg/provider/provider.go +++ b/pkg/provider/provider.go @@ -484,6 +484,7 @@ func getResources() map[string]*schema.Resource { "snowflake_unsafe_execute": resources.UnsafeExecute(), "snowflake_user": resources.User(), "snowflake_user_ownership_grant": resources.UserOwnershipGrant(), + "snowflake_user_password_policy_attachment": resources.UserPasswordPolicyAttachment(), "snowflake_user_public_keys": resources.UserPublicKeys(), "snowflake_view": resources.View(), "snowflake_warehouse": resources.Warehouse(), diff --git a/pkg/resources/password_policy.go b/pkg/resources/password_policy.go index 2f30635ae6..19279b24ab 100644 --- a/pkg/resources/password_policy.go +++ b/pkg/resources/password_policy.go @@ -428,7 +428,7 @@ func UpdatePasswordPolicy(d *schema.ResourceData, meta interface{}) error { d.SetId(helpers.EncodeSnowflakeID(newID)) } - return nil + return ReadPasswordPolicy(d, meta) } // DeletePasswordPolicy implements schema.DeleteFunc. diff --git a/pkg/resources/user_password_policy_attachment.go b/pkg/resources/user_password_policy_attachment.go new file mode 100644 index 0000000000..68a9ee6869 --- /dev/null +++ b/pkg/resources/user_password_policy_attachment.go @@ -0,0 +1,148 @@ +package resources + +import ( + "context" + "database/sql" + "fmt" + "strings" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +var userPasswordPolicyAttachmentSchema = map[string]*schema.Schema{ + "user_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "User name of the user you want to attach the password policy to", + }, + "password_policy_database": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "Database name where the password policy is stored", + }, + "password_policy_schema": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "Schema name where the password policy is stored", + }, + "password_policy_name": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + Description: "Non-qualified name of the password policy", + }, +} + +func UserPasswordPolicyAttachment() *schema.Resource { + return &schema.Resource{ + Description: "Specifies the password policy to use for a certain user.", + + Create: CreateUserPasswordPolicyAttachment, + Read: ReadUserPasswordPolicyAttachment, + Delete: DeleteUserPasswordPolicyAttachment, + + Schema: userPasswordPolicyAttachmentSchema, + + Importer: &schema.ResourceImporter{ + StateContext: schema.ImportStatePassthroughContext, + }, + } +} + +func CreateUserPasswordPolicyAttachment(d *schema.ResourceData, meta interface{}) error { + db := meta.(*sql.DB) + client := sdk.NewClientFromDB(db) + ctx := context.Background() + + userName := sdk.NewAccountObjectIdentifierFromFullyQualifiedName(d.Get("user_name").(string)) + passwordPolicy := sdk.NewSchemaObjectIdentifier( + d.Get("password_policy_database").(string), + d.Get("password_policy_schema").(string), + d.Get("password_policy_name").(string), + ) + + err := client.Users.Alter(ctx, userName, &sdk.AlterUserOptions{ + Set: &sdk.UserSet{ + PasswordPolicy: &passwordPolicy, + }, + }) + if err != nil { + return err + } + d.SetId(fmt.Sprintf(`%s|%s`, helpers.EncodeSnowflakeID(passwordPolicy), helpers.EncodeSnowflakeID(userName))) + + return ReadUserPasswordPolicyAttachment(d, meta) +} + +func ReadUserPasswordPolicyAttachment(d *schema.ResourceData, meta interface{}) error { + parts := strings.Split(d.Id(), helpers.IDDelimiter) + if len(parts) != 4 { + // Note: this exception handling is particularly useful when importing + return fmt.Errorf("id should be in the format 'database|schema|password_policy|user_name', but I got '%s'", d.Id()) + } + // Note: there is no alphanumeric id for an attachment, so we retrieve the password policies attached to a certain user. + userName := sdk.NewAccountObjectIdentifierFromFullyQualifiedName(parts[3]) + db := meta.(*sql.DB) + client := sdk.NewClientFromDB(db) + ctx := context.Background() + policyReferences, err := client.PolicyReferences.GetForEntity(ctx, &sdk.GetForEntityPolicyReferenceRequest{ + // Note: I cannot insert both single and double quotes in the SDK, so for now I need to do this + RefEntityName: userName.FullyQualifiedName(), + RefEntityDomain: "user", + }) + if err != nil { + return err + } + + // Note: this should never happen, but just in case: so far, Snowflake only allows one Password Policy per user. + if len(policyReferences) > 1 { + return fmt.Errorf("internal error: multiple policy references attached to a user. This should never happen") + } + + // Note: this means the resource has been deleted outside of Terraform. + if len(policyReferences) == 0 { + d.SetId("") + return nil + } + if err := d.Set("password_policy_database", sdk.NewAccountIdentifierFromFullyQualifiedName(policyReferences[0].PolicyDb).Name()); err != nil { + return err + } + if err := d.Set("password_policy_schema", sdk.NewAccountIdentifierFromFullyQualifiedName(policyReferences[0].PolicySchema).Name()); err != nil { + return err + } + if err := d.Set("password_policy_name", sdk.NewAccountIdentifierFromFullyQualifiedName(policyReferences[0].PolicyName).Name()); err != nil { + return err + } + if err := d.Set("user_name", helpers.EncodeSnowflakeID(userName)); err != nil { + return err + } + return err +} + +// DeleteAccountPasswordPolicyAttachment implements schema.DeleteFunc. +func DeleteUserPasswordPolicyAttachment(d *schema.ResourceData, meta interface{}) error { + db := meta.(*sql.DB) + client := sdk.NewClientFromDB(db) + ctx := context.Background() + + userName := sdk.NewAccountObjectIdentifierFromFullyQualifiedName(d.Get("user_name").(string)) + + err := client.Users.Alter(ctx, userName, &sdk.AlterUserOptions{ + Unset: &sdk.UserUnset{ + PasswordPolicy: sdk.Bool(true), + }, + }) + + d.SetId("") + + if err != nil { + return err + } + + return nil +} diff --git a/pkg/resources/user_password_policy_attachment_acceptance_test.go b/pkg/resources/user_password_policy_attachment_acceptance_test.go new file mode 100644 index 0000000000..96a2f1d3a2 --- /dev/null +++ b/pkg/resources/user_password_policy_attachment_acceptance_test.go @@ -0,0 +1,100 @@ +package resources_test + +import ( + "context" + "database/sql" + "fmt" + "strings" + "testing" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + + acc "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance" + "github.com/hashicorp/terraform-plugin-testing/helper/acctest" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" +) + +func TestAcc_UserPasswordPolicyAttachment(t *testing.T) { + prefix := "tst-terraform" + strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + prefix2 := "tst-terraform" + strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + CheckDestroy: testAccCheckUserPasswordPolicyAttachmentDestroy, + Steps: []resource.TestStep{ + // CREATE + { + Config: userPasswordPolicyAttachmentConfig("USER", acc.TestDatabaseName, acc.TestSchemaName, prefix), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("snowflake_user_password_policy_attachment.ppa", "id"), + ), + Destroy: false, + }, + // UPDATE + { + Config: userPasswordPolicyAttachmentConfig(fmt.Sprintf("USER_%s", prefix), acc.TestDatabaseName, acc.TestSchemaName, prefix2), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("snowflake_user_password_policy_attachment.ppa", "id"), + resource.TestCheckResourceAttr("snowflake_user_password_policy_attachment.ppa", "user_name", fmt.Sprintf("USER_%s", prefix)), + ), + }, + // IMPORT + { + ResourceName: "snowflake_user_password_policy_attachment.ppa", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccCheckUserPasswordPolicyAttachmentDestroy(s *terraform.State) error { + db := acc.TestAccProvider.Meta().(*sql.DB) + client := sdk.NewClientFromDB(db) + ctx := context.Background() + for _, rs := range s.RootModule().Resources { + // Note: I leverage the fact that the state during the test is specific to the test case, so there should only be there resources created in this test + if rs.Type != "snowflake_user_password_policy_attachment" { + continue + } + userName := sdk.NewAccountObjectIdentifierFromFullyQualifiedName(rs.Primary.Attributes["user_name"]) + policyReferences, err := client.PolicyReferences.GetForEntity(ctx, &sdk.GetForEntityPolicyReferenceRequest{ + RefEntityName: userName.FullyQualifiedName(), + RefEntityDomain: "user", + }) + if err != nil { + if strings.Contains(err.Error(), "does not exist or not authorized") { + // Note: this can happen if the Policy Reference or the User have been deleted as well; in this case, just ignore the error + continue + } + return err + } + if len(policyReferences) > 0 { + return fmt.Errorf("User Password Policy attachment %v still exists", policyReferences[0].PolicyName) + } + } + return nil +} + +func userPasswordPolicyAttachmentConfig(userName, databaseName, schemaName, prefix string) string { + s := ` +resource "snowflake_user" "user" { + name = "%s" +} +resource "snowflake_password_policy" "pp" { + database = "%s" + schema = "%s" + name = "pp_%v" +} + +resource "snowflake_user_password_policy_attachment" "ppa" { + password_policy_database = snowflake_password_policy.pp.database + password_policy_schema = snowflake_password_policy.pp.schema + password_policy_name = snowflake_password_policy.pp.name + user_name = snowflake_user.user.name +} +` + return fmt.Sprintf(s, userName, databaseName, schemaName, prefix) +} diff --git a/pkg/sdk/client.go b/pkg/sdk/client.go index 42c9738a60..f816d82748 100644 --- a/pkg/sdk/client.go +++ b/pkg/sdk/client.go @@ -63,6 +63,7 @@ type Client struct { Parameters Parameters PasswordPolicies PasswordPolicies Pipes Pipes + PolicyReferences PolicyReferences Procedures Procedures ResourceMonitors ResourceMonitors Roles Roles @@ -215,6 +216,7 @@ func (c *Client) initialize() { c.Parameters = ¶meters{client: c} c.PasswordPolicies = &passwordPolicies{client: c} c.Pipes = &pipes{client: c} + c.PolicyReferences = &policyReference{client: c} c.Procedures = &procedures{client: c} c.ReplicationFunctions = &replicationFunctions{client: c} c.ResourceMonitors = &resourceMonitors{client: c} diff --git a/pkg/sdk/policy_references.go b/pkg/sdk/policy_references.go new file mode 100644 index 0000000000..9cac3db020 --- /dev/null +++ b/pkg/sdk/policy_references.go @@ -0,0 +1,112 @@ +package sdk + +import ( + "context" + "database/sql" +) + +type PolicyReferences interface { + GetForEntity(ctx context.Context, request *GetForEntityPolicyReferenceRequest) ([]PolicyReference, error) +} + +type getForEntityPolicyReferenceOptions struct { + select_ bool `ddl:"static" sql:"SELECT"` + asterisk bool `ddl:"static" sql:"*"` + from bool `ddl:"static" sql:"FROM"` + tableFunction *tableFunction `ddl:"keyword"` +} + +type tableFunction struct { + table *bool `ddl:"keyword" sql:"TABLE"` + policyReferenceFunction *policyReferenceFunction `ddl:"list,parentheses,no_comma"` +} + +type policyReferenceFunction struct { + functionFullyQualifiedName *bool `ddl:"keyword" sql:"SNOWFLAKE.INFORMATION_SCHEMA.POLICY_REFERENCES"` + arguments *policyReferenceFunctionArguments `ddl:"list,parentheses"` +} +type policyReferenceFunctionArguments struct { + refEntityName []ObjectIdentifier `ddl:"parameter,single_quotes,arrow_equals" sql:"ref_entity_name"` + refEntityDomain *string `ddl:"parameter,single_quotes,arrow_equals" sql:"ref_entity_domain"` +} + +type PolicyReference struct { + PolicyDb string + PolicySchema string + PolicyName string + PolicyKind string + RefDatabaseName string + RefSchemaName string + RefEntityName string + RefEntityDomain string + RefColumnName string + RefArgColumnNames string + TagDatabase string + TagSchema string + TagName string + PolicyStatus string +} + +type policyReferenceDBRow struct { + PolicyDb sql.NullString `db:"POLICY_DB"` + PolicySchema sql.NullString `db:"POLICY_SCHEMA"` + PolicyName sql.NullString `db:"POLICY_NAME"` + PolicyKind sql.NullString `db:"POLICY_KIND"` + RefDatabaseName sql.NullString `db:"REF_DATABASE_NAME"` + RefSchemaName sql.NullString `db:"REF_SCHEMA_NAME"` + RefEntityName sql.NullString `db:"REF_ENTITY_NAME"` + RefEntityDomain sql.NullString `db:"REF_ENTITY_DOMAIN"` + RefColumnName sql.NullString `db:"REF_COLUMN_NAME"` + RefArgColumnNames sql.NullString `db:"REF_ARG_COLUMN_NAMES"` + TagDatabase sql.NullString `db:"TAG_DATABASE"` + TagSchema sql.NullString `db:"TAG_SCHEMA"` + TagName sql.NullString `db:"TAG_NAME"` + PolicyStatus sql.NullString `db:"POLICY_STATUS"` +} + +func (row policyReferenceDBRow) convert() *PolicyReference { + policyReference := PolicyReference{} + if row.PolicyDb.Valid { + policyReference.PolicyDb = row.PolicyDb.String + } + if row.PolicySchema.Valid { + policyReference.PolicySchema = row.PolicySchema.String + } + if row.PolicyName.Valid { + policyReference.PolicyName = row.PolicyName.String + } + if row.PolicyKind.Valid { + policyReference.PolicyKind = row.PolicyKind.String + } + if row.RefDatabaseName.Valid { + policyReference.RefDatabaseName = row.RefDatabaseName.String + } + if row.RefSchemaName.Valid { + policyReference.RefSchemaName = row.RefSchemaName.String + } + if row.RefEntityName.Valid { + policyReference.RefEntityName = row.RefEntityName.String + } + if row.RefEntityDomain.Valid { + policyReference.RefEntityDomain = row.RefEntityDomain.String + } + if row.RefColumnName.Valid { + policyReference.RefColumnName = row.RefColumnName.String + } + if row.RefArgColumnNames.Valid { + policyReference.RefArgColumnNames = row.RefArgColumnNames.String + } + if row.TagDatabase.Valid { + policyReference.TagDatabase = row.TagDatabase.String + } + if row.TagSchema.Valid { + policyReference.TagSchema = row.TagSchema.String + } + if row.TagName.Valid { + policyReference.TagName = row.TagName.String + } + if row.PolicyStatus.Valid { + policyReference.PolicyStatus = row.PolicyStatus.String + } + return &policyReference +} diff --git a/pkg/sdk/policy_references_dto.go b/pkg/sdk/policy_references_dto.go new file mode 100644 index 0000000000..416df3f490 --- /dev/null +++ b/pkg/sdk/policy_references_dto.go @@ -0,0 +1,8 @@ +package sdk + +var _ optionsProvider[getForEntityPolicyReferenceOptions] = new(GetForEntityPolicyReferenceRequest) + +type GetForEntityPolicyReferenceRequest struct { + RefEntityName string + RefEntityDomain string +} diff --git a/pkg/sdk/policy_references_impl.go b/pkg/sdk/policy_references_impl.go new file mode 100644 index 0000000000..751aba9389 --- /dev/null +++ b/pkg/sdk/policy_references_impl.go @@ -0,0 +1,34 @@ +package sdk + +import "context" + +type policyReference struct { + client *Client +} + +func (v *policyReference) GetForEntity(ctx context.Context, request *GetForEntityPolicyReferenceRequest) ([]PolicyReference, error) { + opts := request.toOpts() + dbRows, err := validateAndQuery[policyReferenceDBRow](v.client, ctx, opts) + if err != nil { + return nil, err + } + + resultList := convertRows[policyReferenceDBRow, PolicyReference](dbRows) + + return resultList, nil +} + +func (request *GetForEntityPolicyReferenceRequest) toOpts() *getForEntityPolicyReferenceOptions { + return &getForEntityPolicyReferenceOptions{ + tableFunction: &tableFunction{ + table: Bool(true), + policyReferenceFunction: &policyReferenceFunction{ + functionFullyQualifiedName: Bool(true), + arguments: &policyReferenceFunctionArguments{ + refEntityName: []ObjectIdentifier{NewObjectIdentifierFromFullyQualifiedName(request.RefEntityName)}, + refEntityDomain: String(request.RefEntityDomain), + }, + }, + }, + } +} diff --git a/pkg/sdk/policy_references_test.go b/pkg/sdk/policy_references_test.go new file mode 100644 index 0000000000..f25ea82368 --- /dev/null +++ b/pkg/sdk/policy_references_test.go @@ -0,0 +1,143 @@ +package sdk + +import ( + "strings" + "testing" +) + +func TestPolicyReferencesGetForEntity(t *testing.T) { + userName := NewAccountObjectIdentifierFromFullyQualifiedName("USER") + + t.Run("validation: missing refEntityName", func(t *testing.T) { + opts := &getForEntityPolicyReferenceOptions{ + tableFunction: &tableFunction{ + table: Bool(true), + policyReferenceFunction: &policyReferenceFunction{ + functionFullyQualifiedName: Bool(true), + arguments: &policyReferenceFunctionArguments{ + refEntityName: nil, + refEntityDomain: String("user"), + }, + }, + }, + } + assertOptsInvalidJoinedErrors(t, opts, errNotSet("getForEntityPolicyReferenceOptions", "refEntityName")) + }) + + t.Run("validation: missing refEntityDomain", func(t *testing.T) { + opts := &getForEntityPolicyReferenceOptions{ + tableFunction: &tableFunction{ + table: Bool(true), + policyReferenceFunction: &policyReferenceFunction{ + functionFullyQualifiedName: Bool(true), + arguments: &policyReferenceFunctionArguments{ + refEntityName: []ObjectIdentifier{userName}, + refEntityDomain: nil, + }, + }, + }, + } + assertOptsInvalidJoinedErrors(t, opts, errNotSet("getForEntityPolicyReferenceOptions", "refEntityDomain")) + }) + + t.Run("validation: domain: user", func(t *testing.T) { + opts := &getForEntityPolicyReferenceOptions{ + tableFunction: &tableFunction{ + table: Bool(true), + policyReferenceFunction: &policyReferenceFunction{ + functionFullyQualifiedName: Bool(true), + arguments: &policyReferenceFunctionArguments{ + refEntityName: []ObjectIdentifier{userName}, + refEntityDomain: String("user"), + }, + }, + }, + } + assertOptsValidAndSQLEquals(t, opts, "SELECT * FROM TABLE (SNOWFLAKE.INFORMATION_SCHEMA.POLICY_REFERENCES (ref_entity_name => '%s', ref_entity_domain => 'user'))", strings.ReplaceAll(userName.FullyQualifiedName(), `"`, `\"`)) + }) + + tableName := NewSchemaObjectIdentifier("db", "schema", "table") + t.Run("validation: domain: table", func(t *testing.T) { + opts := &getForEntityPolicyReferenceOptions{ + tableFunction: &tableFunction{ + table: Bool(true), + policyReferenceFunction: &policyReferenceFunction{ + functionFullyQualifiedName: Bool(true), + arguments: &policyReferenceFunctionArguments{ + refEntityName: []ObjectIdentifier{tableName}, + refEntityDomain: String("table"), + }, + }, + }, + } + assertOptsValidAndSQLEquals(t, opts, "SELECT * FROM TABLE (SNOWFLAKE.INFORMATION_SCHEMA.POLICY_REFERENCES (ref_entity_name => '%s', ref_entity_domain => 'table'))", strings.ReplaceAll(tableName.FullyQualifiedName(), `"`, `\"`)) + }) + + accountName := NewAccountObjectIdentifier("account") + t.Run("validation: domain: account", func(t *testing.T) { + opts := &getForEntityPolicyReferenceOptions{ + tableFunction: &tableFunction{ + table: Bool(true), + policyReferenceFunction: &policyReferenceFunction{ + functionFullyQualifiedName: Bool(true), + arguments: &policyReferenceFunctionArguments{ + refEntityName: []ObjectIdentifier{accountName}, + refEntityDomain: String("account"), + }, + }, + }, + } + assertOptsValidAndSQLEquals(t, opts, "SELECT * FROM TABLE (SNOWFLAKE.INFORMATION_SCHEMA.POLICY_REFERENCES (ref_entity_name => '%s', ref_entity_domain => 'account'))", strings.ReplaceAll(accountName.FullyQualifiedName(), `"`, `\"`)) + }) + + integrationName := NewAccountObjectIdentifier("integration") + t.Run("validation: domain: integration", func(t *testing.T) { + opts := &getForEntityPolicyReferenceOptions{ + tableFunction: &tableFunction{ + table: Bool(true), + policyReferenceFunction: &policyReferenceFunction{ + functionFullyQualifiedName: Bool(true), + arguments: &policyReferenceFunctionArguments{ + refEntityName: []ObjectIdentifier{integrationName}, + refEntityDomain: String("integration"), + }, + }, + }, + } + assertOptsValidAndSQLEquals(t, opts, "SELECT * FROM TABLE (SNOWFLAKE.INFORMATION_SCHEMA.POLICY_REFERENCES (ref_entity_name => '%s', ref_entity_domain => 'integration'))", strings.ReplaceAll(integrationName.FullyQualifiedName(), `"`, `\"`)) + }) + + tagName := NewSchemaObjectIdentifier("db", "schema", "tag") + t.Run("validation: domain: tag", func(t *testing.T) { + opts := &getForEntityPolicyReferenceOptions{ + tableFunction: &tableFunction{ + table: Bool(true), + policyReferenceFunction: &policyReferenceFunction{ + functionFullyQualifiedName: Bool(true), + arguments: &policyReferenceFunctionArguments{ + refEntityName: []ObjectIdentifier{tagName}, + refEntityDomain: String("tag"), + }, + }, + }, + } + assertOptsValidAndSQLEquals(t, opts, "SELECT * FROM TABLE (SNOWFLAKE.INFORMATION_SCHEMA.POLICY_REFERENCES (ref_entity_name => '%s', ref_entity_domain => 'tag'))", strings.ReplaceAll(tagName.FullyQualifiedName(), `"`, `\"`)) + }) + + viewName := NewSchemaObjectIdentifier("db", "schema", "view") + t.Run("validation: domain: integration", func(t *testing.T) { + opts := &getForEntityPolicyReferenceOptions{ + tableFunction: &tableFunction{ + table: Bool(true), + policyReferenceFunction: &policyReferenceFunction{ + functionFullyQualifiedName: Bool(true), + arguments: &policyReferenceFunctionArguments{ + refEntityName: []ObjectIdentifier{viewName}, + refEntityDomain: String("view"), + }, + }, + }, + } + assertOptsValidAndSQLEquals(t, opts, "SELECT * FROM TABLE (SNOWFLAKE.INFORMATION_SCHEMA.POLICY_REFERENCES (ref_entity_name => '%s', ref_entity_domain => 'view'))", strings.ReplaceAll(viewName.FullyQualifiedName(), `"`, `\"`)) + }) +} diff --git a/pkg/sdk/policy_references_validation.go b/pkg/sdk/policy_references_validation.go new file mode 100644 index 0000000000..e92ed6f7d9 --- /dev/null +++ b/pkg/sdk/policy_references_validation.go @@ -0,0 +1,21 @@ +package sdk + +import ( + "errors" +) + +var _ validatable = new(getForEntityPolicyReferenceOptions) + +func (opts *getForEntityPolicyReferenceOptions) validate() error { + if opts == nil { + return errors.Join(ErrNilOptions) + } + var errs []error + if opts.tableFunction.policyReferenceFunction.arguments.refEntityDomain == nil { + errs = append(errs, errNotSet("getForEntityPolicyReferenceOptions", "refEntityDomain")) + } + if opts.tableFunction.policyReferenceFunction.arguments.refEntityName == nil { + errs = append(errs, errNotSet("getForEntityPolicyReferenceOptions", "refEntityName")) + } + return errors.Join(errs...) +} diff --git a/pkg/sdk/users.go b/pkg/sdk/users.go index 6b610a788b..b7f937c2d5 100644 --- a/pkg/sdk/users.go +++ b/pkg/sdk/users.go @@ -347,11 +347,11 @@ func (opts *RemoveDelegatedAuthorization) validate() error { } type UserSet struct { - PasswordPolicy *string `ddl:"parameter" sql:"PASSWORD POLICY"` - SessionPolicy *string `ddl:"parameter" sql:"SESSION POLICY"` - ObjectProperties *UserObjectProperties `ddl:"keyword"` - ObjectParameters *UserObjectParameters `ddl:"keyword"` - SessionParameters *SessionParameters `ddl:"keyword"` + PasswordPolicy *SchemaObjectIdentifier `ddl:"identifier" sql:"PASSWORD POLICY"` + SessionPolicy *string `ddl:"parameter" sql:"SESSION POLICY"` + ObjectProperties *UserObjectProperties `ddl:"keyword"` + ObjectParameters *UserObjectParameters `ddl:"keyword"` + SessionParameters *SessionParameters `ddl:"keyword"` } func (opts *UserSet) validate() error { diff --git a/pkg/sdk/users_test.go b/pkg/sdk/users_test.go index bfe837bfdd..67a5334a9b 100644 --- a/pkg/sdk/users_test.go +++ b/pkg/sdk/users_test.go @@ -63,14 +63,14 @@ func TestUserAlter(t *testing.T) { }) t.Run("with setting a policy", func(t *testing.T) { - passwordPolicy := "PASSWORD_POLICY1" + passwordPolicy := NewSchemaObjectIdentifier("db", "schema", "PASSWORD_POLICY1") opts := &AlterUserOptions{ name: id, Set: &UserSet{ - PasswordPolicy: String(passwordPolicy), + PasswordPolicy: &passwordPolicy, }, } - assertOptsValidAndSQLEquals(t, opts, "ALTER USER %s SET PASSWORD POLICY = %s", id.FullyQualifiedName(), passwordPolicy) + assertOptsValidAndSQLEquals(t, opts, "ALTER USER %s SET PASSWORD POLICY %s", id.FullyQualifiedName(), passwordPolicy.FullyQualifiedName()) }) t.Run("with setting tags", func(t *testing.T) {