From 6dd843cdb195b5e54181ad9a057a76d674c4adaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Mon, 18 Mar 2024 11:55:00 +0100 Subject: [PATCH 1/7] rename changes after review wip wip wip wip Adjust grant ownership on pipe to be less hacky without any additional grants underneath fixes after review fixes after review fixes after review wip wip wip wip wip wip wip wip wip wip wip wip --- pkg/sdk/testint/grants_integration_test.go | 304 +++++++++++++++++++++ 1 file changed, 304 insertions(+) diff --git a/pkg/sdk/testint/grants_integration_test.go b/pkg/sdk/testint/grants_integration_test.go index 4a5e69db28..74d4c7fd9c 100644 --- a/pkg/sdk/testint/grants_integration_test.go +++ b/pkg/sdk/testint/grants_integration_test.go @@ -1374,6 +1374,310 @@ func TestInt_GrantOwnership(t *testing.T) { require.NoError(t, err) require.Equal(t, sdk.PausedPipeExecutionState, secondPipeExecutionState) }) + + t.Run("on pipe - with ownership", func(t *testing.T) { + pipe, pipeCleanup := createPipe(t, client, testDb(t), testSchema(t), random.AlphaN(20), copyStatement) + t.Cleanup(pipeCleanup) + + pipeExecutionState, err := client.SystemFunctions.PipeStatus(pipe.ID()) + require.NoError(t, err) + require.Equal(t, sdk.RunningPipeExecutionState, pipeExecutionState) + + role, roleCleanup := createRole(t, client) + t.Cleanup(roleCleanup) + + err = client.Grants.GrantOwnership( + ctx, + ownershipGrantOnPipe(pipe), + sdk.OwnershipGrantTo{ + AccountRoleName: sdk.Pointer(role.ID()), + }, + new(sdk.GrantOwnershipOptions), + ) + require.NoError(t, err) + checkOwnershipOnObjectToRole(t, ownershipGrantOnPipe(pipe), role.ID().Name()) + + currentRole, err := client.ContextFunctions.CurrentRole(ctx) + require.NoError(t, err) + + grantOwnershipToRole(t, currentRole, ownershipGrantOnPipe(pipe)) + checkOwnershipOnObjectToRole(t, ownershipGrantOnPipe(pipe), currentRole) + + pipeExecutionState, err = client.SystemFunctions.PipeStatus(pipe.ID()) + require.NoError(t, err) + require.Equal(t, sdk.PausedPipeExecutionState, pipeExecutionState) + }) + + t.Run("on pipe - with operate and monitor privileges granted", func(t *testing.T) { + role, roleCleanup := createRoleGrantedToCurrentUser(t, client) + t.Cleanup(roleCleanup) + + pipeRole, pipeRoleCleanup := createRoleGrantedToCurrentUser(t, client) + t.Cleanup(pipeRoleCleanup) + + // Role needs usage on the database and schema to later be able to remove pipe in the cleanup + grantDatabaseAndSchemaUsage(t, role) + // grantPipeRole grants the necessary privileges to a role to be able to create pipe + grantPipeRole(t, pipeRole, table, stage) + + previousRole, err := client.ContextFunctions.CurrentRole(ctx) + require.NoError(t, err) + + // Use a previously prepared role to create a pipe and grant MONITOR + OPERATE to the previously used role (ACCOUNTADMIN). + usePreviousRole := useRole(t, client, pipeRole.Name) + + pipe, pipeCleanup := createPipe(t, client, testDb(t), testSchema(t), random.AlphaN(20), copyStatement) + t.Cleanup(func() { + usePreviousRole = useRole(t, client, role.Name) + pipeCleanup() + usePreviousRole() + }) + + // Grant MONITOR and OPERATE privileges to the role. + makeAccountRoleOperableOnPipe(t, previousRole, pipe) + + usePreviousRole() + + err = client.Pipes.Alter(ctx, pipe.ID(), &sdk.AlterPipeOptions{ + Set: &sdk.PipeSet{ + PipeExecutionPaused: sdk.Bool(false), + }, + }) + require.NoError(t, err) + + pipeExecutionState, err := client.SystemFunctions.PipeStatus(pipe.ID()) + require.NoError(t, err) + require.Equal(t, sdk.RunningPipeExecutionState, pipeExecutionState) + + err = client.Grants.GrantOwnership( + ctx, + ownershipGrantOnPipe(pipe), + sdk.OwnershipGrantTo{ + AccountRoleName: sdk.Pointer(role.ID()), + }, + &sdk.GrantOwnershipOptions{ + CurrentGrants: &sdk.OwnershipCurrentGrants{ + OutboundPrivileges: sdk.Revoke, // To revoke MONITOR privilege from ACCOUNTADMIN automatically + }, + }, + ) + require.NoError(t, err) + checkOwnershipOnObjectToRole(t, ownershipGrantOnPipe(pipe), role.ID().Name()) + + usePreviousRole() + + pipeExecutionState, err = client.SystemFunctions.PipeStatus(pipe.ID()) + require.NoError(t, err) + require.Equal(t, sdk.PausedPipeExecutionState, pipeExecutionState) + }) + + t.Run("on pipe - with operate privilege granted and copy current grants option", func(t *testing.T) { + role, roleCleanup := createRoleGrantedToCurrentUser(t, client) + t.Cleanup(roleCleanup) + + pipeRole, pipeRoleCleanup := createRoleGrantedToCurrentUser(t, client) + t.Cleanup(pipeRoleCleanup) + + // Role needs usage on the database and schema to later be able to remove pipe in the cleanup + grantDatabaseAndSchemaUsage(t, role) + // grantPipeRole grants the necessary privileges to a role to be able to create pipe + grantPipeRole(t, pipeRole, table, stage) + + previousRole, err := client.ContextFunctions.CurrentRole(ctx) + require.NoError(t, err) + + // Use a previously prepared role to create a pipe and grant MONITOR + OPERATE to the previously used role (ACCOUNTADMIN). + usePreviousRole := useRole(t, client, pipeRole.Name) + + pipe, pipeCleanup := createPipe(t, client, testDb(t), testSchema(t), random.AlphaN(20), copyStatement) + t.Cleanup(func() { + usePreviousRole = useRole(t, client, role.Name) + pipeCleanup() + usePreviousRole() + }) + + // Grant MONITOR and OPERATE privileges to the role. + makeAccountRoleOperableOnPipe(t, previousRole, pipe) + + usePreviousRole() + + err = client.Pipes.Alter(ctx, pipe.ID(), &sdk.AlterPipeOptions{ + Set: &sdk.PipeSet{ + PipeExecutionPaused: sdk.Bool(false), + }, + }) + require.NoError(t, err) + + pipeExecutionState, err := client.SystemFunctions.PipeStatus(pipe.ID()) + require.NoError(t, err) + require.Equal(t, sdk.RunningPipeExecutionState, pipeExecutionState) + + err = client.Grants.GrantOwnership( + ctx, + ownershipGrantOnPipe(pipe), + sdk.OwnershipGrantTo{ + AccountRoleName: sdk.Pointer(role.ID()), + }, + &sdk.GrantOwnershipOptions{ + CurrentGrants: &sdk.OwnershipCurrentGrants{ + OutboundPrivileges: sdk.Copy, // With copy, we'll be able to resume the pipe after ownership transfer + }, + }, + ) + require.NoError(t, err) + checkOwnershipOnObjectToRole(t, ownershipGrantOnPipe(pipe), role.ID().Name()) + + usePreviousRole() + + pipeExecutionState, err = client.SystemFunctions.PipeStatus(pipe.ID()) + require.NoError(t, err) + require.Equal(t, sdk.RunningPipeExecutionState, pipeExecutionState) + }) + + t.Run("on pipe - with neither ownership nor operate", func(t *testing.T) { + role, roleCleanup := createRoleGrantedToCurrentUser(t, client) + t.Cleanup(roleCleanup) + + pipeRole, pipeRoleCleanup := createRoleGrantedToCurrentUser(t, client) + t.Cleanup(pipeRoleCleanup) + + // Role needs usage on the database and schema to later be able to remove pipe in the cleanup + grantDatabaseAndSchemaUsage(t, role) + // grantPipeRole grants the necessary privileges to a role to be able to create pipe + grantPipeRole(t, pipeRole, table, stage) + + // Use a previously prepared role to create a pipe and grant MONITOR + OPERATE to the previously used role (ACCOUNTADMIN). + usePreviousRole := useRole(t, client, pipeRole.Name) + + pipe, pipeCleanup := createPipe(t, client, testDb(t), testSchema(t), random.AlphaN(20), copyStatement) + t.Cleanup(func() { + usePreviousRole = useRole(t, client, pipeRole.Name) + pipeCleanup() + usePreviousRole() + }) + + err := client.Pipes.Alter(ctx, pipe.ID(), &sdk.AlterPipeOptions{ + Set: &sdk.PipeSet{ + PipeExecutionPaused: sdk.Bool(false), + }, + }) + require.NoError(t, err) + + pipeExecutionState, err := client.SystemFunctions.PipeStatus(pipe.ID()) + require.NoError(t, err) + require.Equal(t, sdk.RunningPipeExecutionState, pipeExecutionState) + + usePreviousRole() + + err = client.Grants.GrantOwnership( + ctx, + ownershipGrantOnPipe(pipe), + sdk.OwnershipGrantTo{ + AccountRoleName: sdk.Pointer(role.ID()), + }, + new(sdk.GrantOwnershipOptions), + ) + require.ErrorContains(t, err, fmt.Sprintf("Pipe %s not in paused state. To pause pipe run ALTER PIPE %s SET PIPE_EXECUTION_PAUSED=true", pipe.Name, pipe.Name)) + }) + + t.Run("on pipe - with neither ownership nor operate on paused pipe", func(t *testing.T) { + role, roleCleanup := createRoleGrantedToCurrentUser(t, client) + t.Cleanup(roleCleanup) + + pipeRole, pipeRoleCleanup := createRoleGrantedToCurrentUser(t, client) + t.Cleanup(pipeRoleCleanup) + + // Role needs usage on the database and schema to later be able to remove pipe in the cleanup + grantDatabaseAndSchemaUsage(t, role) + // grantPipeRole grants the necessary privileges to a role to be able to create pipe + grantPipeRole(t, pipeRole, table, stage) + + // Use a previously prepared role to create a pipe and grant MONITOR + OPERATE to the previously used role (ACCOUNTADMIN). + usePreviousRole := useRole(t, client, pipeRole.Name) + + pipe, pipeCleanup := createPipe(t, client, testDb(t), testSchema(t), random.AlphaN(20), copyStatement) + t.Cleanup(func() { + usePreviousRole = useRole(t, client, role.Name) + pipeCleanup() + usePreviousRole() + }) + + err := client.Pipes.Alter(ctx, pipe.ID(), &sdk.AlterPipeOptions{ + Set: &sdk.PipeSet{ + PipeExecutionPaused: sdk.Bool(true), + }, + }) + require.NoError(t, err) + + pipeExecutionState, err := client.SystemFunctions.PipeStatus(pipe.ID()) + require.NoError(t, err) + require.Equal(t, sdk.PausedPipeExecutionState, pipeExecutionState) + + usePreviousRole() + + err = client.Grants.GrantOwnership( + ctx, + ownershipGrantOnPipe(pipe), + sdk.OwnershipGrantTo{ + AccountRoleName: sdk.Pointer(role.ID()), + }, + new(sdk.GrantOwnershipOptions), + ) + require.NoError(t, err) + checkOwnershipOnObjectToRole(t, ownershipGrantOnPipe(pipe), role.Name) + }) + + t.Run("on all pipes", func(t *testing.T) { + pipe, pipeCleanup := createPipe(t, client, testDb(t), testSchema(t), random.AlphaN(20), copyStatement) + t.Cleanup(pipeCleanup) + + secondPipe, secondPipeCleanup := createPipe(t, client, testDb(t), testSchema(t), random.AlphaN(20), copyStatement) + t.Cleanup(secondPipeCleanup) + + pipeExecutionState, err := client.SystemFunctions.PipeStatus(pipe.ID()) + require.NoError(t, err) + require.Equal(t, sdk.RunningPipeExecutionState, pipeExecutionState) + + secondPipeExecutionState, err := client.SystemFunctions.PipeStatus(secondPipe.ID()) + require.NoError(t, err) + require.Equal(t, sdk.RunningPipeExecutionState, secondPipeExecutionState) + + role, roleCleanup := createRole(t, client) + t.Cleanup(roleCleanup) + + onAllPipesInSchema := sdk.OwnershipGrantOn{ + All: &sdk.GrantOnSchemaObjectIn{ + PluralObjectType: sdk.PluralObjectTypePipes, + InSchema: sdk.Pointer(testSchema(t).ID()), + }, + } + err = client.Grants.GrantOwnership( + ctx, + onAllPipesInSchema, + sdk.OwnershipGrantTo{ + AccountRoleName: sdk.Pointer(role.ID()), + }, + new(sdk.GrantOwnershipOptions), + ) + require.NoError(t, err) + + checkOwnershipOnObjectToRole(t, ownershipGrantOnPipe(pipe), role.ID().Name()) + checkOwnershipOnObjectToRole(t, ownershipGrantOnPipe(secondPipe), role.ID().Name()) + + currentRole, err := client.ContextFunctions.CurrentRole(ctx) + require.NoError(t, err) + grantOwnershipToRole(t, currentRole, onAllPipesInSchema) + checkOwnershipOnObjectToRole(t, ownershipGrantOnPipe(pipe), currentRole) + checkOwnershipOnObjectToRole(t, ownershipGrantOnPipe(secondPipe), currentRole) + + pipeExecutionState, err = client.SystemFunctions.PipeStatus(pipe.ID()) + require.NoError(t, err) + require.Equal(t, sdk.PausedPipeExecutionState, pipeExecutionState) + + secondPipeExecutionState, err = client.SystemFunctions.PipeStatus(secondPipe.ID()) + require.NoError(t, err) + require.Equal(t, sdk.PausedPipeExecutionState, secondPipeExecutionState) + }) } func TestInt_ShowGrants(t *testing.T) { From 008df203dbb83c200656568ddf885f1c3d98172d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Wed, 3 Apr 2024 11:41:31 +0200 Subject: [PATCH 2/7] Add grant ownership on tasks --- .../grant_ownership_acceptance_test.go | 89 +++++++++ .../TestAcc_GrantOwnership/OnAllTasks/test.tf | 31 +++ .../OnAllTasks/variables.tf | 19 ++ .../TestAcc_GrantOwnership/OnTask/test.tf | 19 ++ .../OnTask/variables.tf | 15 ++ pkg/sdk/grants_impl.go | 83 ++++++++ pkg/sdk/testint/grants_integration_test.go | 183 +++++++++++++++++- pkg/sdk/testint/helpers_test.go | 24 +++ 8 files changed, 459 insertions(+), 4 deletions(-) create mode 100644 pkg/resources/testdata/TestAcc_GrantOwnership/OnAllTasks/test.tf create mode 100644 pkg/resources/testdata/TestAcc_GrantOwnership/OnAllTasks/variables.tf create mode 100644 pkg/resources/testdata/TestAcc_GrantOwnership/OnTask/test.tf create mode 100644 pkg/resources/testdata/TestAcc_GrantOwnership/OnTask/variables.tf diff --git a/pkg/resources/grant_ownership_acceptance_test.go b/pkg/resources/grant_ownership_acceptance_test.go index e2153e59fa..e44e235531 100644 --- a/pkg/resources/grant_ownership_acceptance_test.go +++ b/pkg/resources/grant_ownership_acceptance_test.go @@ -771,6 +771,95 @@ func TestAcc_GrantOwnership_OnAllPipes(t *testing.T) { }) } +func TestAcc_GrantOwnership_OnTask(t *testing.T) { + accountRoleName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + taskName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + accountRoleFullyQualifiedName := sdk.NewAccountObjectIdentifier(accountRoleName).FullyQualifiedName() + taskFullyQualifiedName := sdk.NewSchemaObjectIdentifier(acc.TestDatabaseName, acc.TestSchemaName, taskName).FullyQualifiedName() + + configVariables := config.Variables{ + "account_role_name": config.StringVariable(accountRoleName), + "database": config.StringVariable(acc.TestDatabaseName), + "schema": config.StringVariable(acc.TestSchemaName), + "task": config.StringVariable(taskName), + } + resourceName := "snowflake_grant_ownership.test" + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + Steps: []resource.TestStep{ + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/OnTask"), + ConfigVariables: configVariables, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "account_role_name", accountRoleName), + resource.TestCheckResourceAttr(resourceName, "on.0.object_type", sdk.ObjectTypeTask.String()), + resource.TestCheckResourceAttr(resourceName, "on.0.object_name", taskFullyQualifiedName), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("ToAccountRole|%s||OnObject|TASK|%s", accountRoleFullyQualifiedName, taskFullyQualifiedName)), + checkResourceOwnershipIsGranted(&sdk.ShowGrantOptions{ + On: &sdk.ShowGrantsOn{ + Object: &sdk.Object{ + ObjectType: sdk.ObjectTypeTask, + Name: sdk.NewSchemaObjectIdentifierFromFullyQualifiedName(taskFullyQualifiedName), + }, + }, + // TODO(SNOW-999049): Fix this identifier + }, sdk.ObjectTypeTask, accountRoleName, fmt.Sprintf("%s\".\"%s\".%s", acc.TestDatabaseName, acc.TestSchemaName, taskName)), + ), + }, + }, + }) +} + +func TestAcc_GrantOwnership_OnAllTasks(t *testing.T) { + accountRoleName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + taskName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + secondTaskName := strings.ToUpper(acctest.RandStringFromCharSet(10, acctest.CharSetAlpha)) + accountRoleFullyQualifiedName := sdk.NewAccountObjectIdentifier(accountRoleName).FullyQualifiedName() + schemaFullyQualifiedName := sdk.NewDatabaseObjectIdentifier(acc.TestDatabaseName, acc.TestSchemaName).FullyQualifiedName() + + configVariables := config.Variables{ + "account_role_name": config.StringVariable(accountRoleName), + "database": config.StringVariable(acc.TestDatabaseName), + "schema": config.StringVariable(acc.TestSchemaName), + "task": config.StringVariable(taskName), + "second_task": config.StringVariable(secondTaskName), + } + resourceName := "snowflake_grant_ownership.test" + + resource.Test(t, resource.TestCase{ + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + PreCheck: func() { acc.TestAccPreCheck(t) }, + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + Steps: []resource.TestStep{ + { + ConfigDirectory: acc.ConfigurationDirectory("TestAcc_GrantOwnership/OnAllTasks"), + ConfigVariables: configVariables, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "account_role_name", accountRoleName), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf("ToAccountRole|%s|REVOKE|OnAll|TASKS|InSchema|%s", accountRoleFullyQualifiedName, schemaFullyQualifiedName)), + checkResourceOwnershipIsGranted(&sdk.ShowGrantOptions{ + To: &sdk.ShowGrantsTo{ + Role: sdk.NewAccountObjectIdentifier(accountRoleName), + }, + }, + sdk.ObjectTypeTask, accountRoleName, + // TODO(SNOW-999049): Fix this identifier + fmt.Sprintf("%s\".\"%s\".%s", acc.TestDatabaseName, acc.TestSchemaName, taskName), + fmt.Sprintf("%s\".\"%s\".%s", acc.TestDatabaseName, acc.TestSchemaName, secondTaskName), + ), + ), + }, + }, + }) +} + func createDatabaseWithRoleAsOwner(t *testing.T, roleName string, databaseName string) func() { t.Helper() client, err := sdk.NewDefaultClient() diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnAllTasks/test.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnAllTasks/test.tf new file mode 100644 index 0000000000..87598d618f --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnAllTasks/test.tf @@ -0,0 +1,31 @@ +resource "snowflake_role" "test" { + name = var.account_role_name +} + +resource "snowflake_task" "test" { + database = var.database + schema = var.schema + name = var.task + sql_statement = "SELECT CURRENT_TIMESTAMP" +} + +resource "snowflake_task" "second_test" { + database = var.database + schema = var.schema + name = var.second_task + sql_statement = "SELECT CURRENT_TIMESTAMP" +} + +resource "snowflake_grant_ownership" "test" { + depends_on = [snowflake_task.test, snowflake_task.second_test] + account_role_name = snowflake_role.test.name + + on { + all { + object_type_plural = "TASKS" + in_schema = "\"${var.database}\".\"${var.schema}\"" + } + } + + outbound_privileges = "REVOKE" +} diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnAllTasks/variables.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnAllTasks/variables.tf new file mode 100644 index 0000000000..4d2ea6b045 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnAllTasks/variables.tf @@ -0,0 +1,19 @@ +variable "account_role_name" { + type = string +} + +variable "database" { + type = string +} + +variable "schema" { + type = string +} + +variable "task" { + type = string +} + +variable "second_task" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnTask/test.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnTask/test.tf new file mode 100644 index 0000000000..435bb9d8d1 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnTask/test.tf @@ -0,0 +1,19 @@ +resource "snowflake_role" "test" { + name = var.account_role_name +} + +resource "snowflake_task" "test" { + database = var.database + schema = var.schema + name = var.task + sql_statement = "SELECT CURRENT_TIMESTAMP" +} + +resource "snowflake_grant_ownership" "test" { + account_role_name = snowflake_role.test.name + + on { + object_type = "TASK" + object_name = "\"${snowflake_task.test.database}\".\"${snowflake_task.test.schema}\".\"${snowflake_task.test.name}\"" + } +} diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnTask/variables.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnTask/variables.tf new file mode 100644 index 0000000000..aa54bc60fa --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnTask/variables.tf @@ -0,0 +1,15 @@ +variable "account_role_name" { + type = string +} + +variable "database" { + type = string +} + +variable "schema" { + type = string +} + +variable "task" { + type = string +} diff --git a/pkg/sdk/grants_impl.go b/pkg/sdk/grants_impl.go index f1611828bf..5996d78566 100644 --- a/pkg/sdk/grants_impl.go +++ b/pkg/sdk/grants_impl.go @@ -213,6 +213,10 @@ func (v *grants) GrantOwnership(ctx context.Context, on OwnershipGrantOn, to Own return v.grantOwnershipOnPipe(ctx, on.Object.Name.(SchemaObjectIdentifier), opts) } + if on.Object != nil && on.Object.ObjectType == ObjectTypeTask { + return v.grantOwnershipOnTask(ctx, on.Object.Name.(SchemaObjectIdentifier), opts) + } + // Snowflake doesn't allow bulk operations on Pipes. Because of that, when SDK user // issues "grant x on all pipes" operation, we'll go and grant specified privileges // to every Pipe one by one. @@ -237,6 +241,31 @@ func (v *grants) GrantOwnership(ctx context.Context, on OwnershipGrantOn, to Own ) } + // To grant ownership of a task in Snowflake, it (and its root) has to be suspended before + // and resume after (only if it was previously running). To simplify the logic, every task + // will be granted individually where the suspension/resume logic is applied. + if on.All != nil && on.All.PluralObjectType == PluralObjectTypeTasks { + return v.runOnAllTasks( + ctx, + on.All.InDatabase, + on.All.InSchema, + func(task Task) error { + return v.client.Grants.GrantOwnership( + ctx, + OwnershipGrantOn{ + Object: &Object{ + ObjectType: ObjectTypeTask, + Name: NewSchemaObjectIdentifier(task.DatabaseName, task.SchemaName, task.Name), + }, + }, + to, + opts, + ) + }, + ) + + } + return validateAndExec(v.client, ctx, opts) } @@ -344,6 +373,39 @@ func (v *grants) grantOwnershipOnPipe(ctx context.Context, pipeId SchemaObjectId return nil } +func (v *grants) grantOwnershipOnTask(ctx context.Context, taskId SchemaObjectIdentifier, opts *GrantOwnershipOptions) error { + // 1. Suspend task roots. + tasksToResume, err := v.client.Tasks.SuspendRootTasks(ctx, taskId, taskId) // params ?? + if err != nil { + return err + } + + // 2. Grant ownership on the task. + if err := validateAndExec(v.client, ctx, opts); err != nil { + return err + } + + // 3. Resume tasks. + if len(tasksToResume) > 0 { + err = v.client.Tasks.ResumeTasks(ctx, tasksToResume) + if err != nil { + return err + } + } + + return nil +} + +// TODO: consider (describe in the pr) +//func (v *grants) grantOwnershipOnAllTasks(ctx context.Context, opts *GrantOwnershipOptions) error { +// grant on all tasks +// 1. grant ownership on all tasks (Snowflake will suspend automatically) +// 2. go through tasks in [database|schema] +// 3. resume w if here last_suspended_reason was equal to "GRANT_OWNERSHIP" (have to resume parent first) +// or +//return nil +//} + func (v *grants) grantTemporarily(ctx context.Context, privileges *AccountRoleGrantPrivileges, on *AccountRoleGrantOn, accountRoleName AccountObjectIdentifier) (func() error, error) { return func() error { return v.client.Grants.RevokePrivilegesFromAccountRole( @@ -383,6 +445,27 @@ func (v *grants) runOnAllPipes(ctx context.Context, inDatabase *AccountObjectIde return runOnAll(pipes, command) } +func (v *grants) runOnAllTasks(ctx context.Context, inDatabase *AccountObjectIdentifier, inSchema *DatabaseObjectIdentifier, command func(Task) error) error { + var in *In + switch { + case inDatabase != nil: + in = &In{ + Database: *inDatabase, + } + case inSchema != nil: + in = &In{ + Schema: *inSchema, + } + } + + tasks, err := v.client.Tasks.Show(ctx, NewShowTaskRequest().WithIn(in)) + if err != nil { + return err + } + + return runOnAll(tasks, command) +} + func runOnAll[T any](collection []T, command func(T) error) error { var errs []error for _, element := range collection { diff --git a/pkg/sdk/testint/grants_integration_test.go b/pkg/sdk/testint/grants_integration_test.go index 74d4c7fd9c..a2de2c54f6 100644 --- a/pkg/sdk/testint/grants_integration_test.go +++ b/pkg/sdk/testint/grants_integration_test.go @@ -876,7 +876,7 @@ func TestInt_GrantOwnership(t *testing.T) { err = client.Grants.GrantPrivilegesToAccountRole( ctx, &sdk.AccountRoleGrantPrivileges{ - SchemaPrivileges: []sdk.SchemaPrivilege{sdk.SchemaPrivilegeUsage, sdk.SchemaPrivilegeCreatePipe}, + SchemaPrivileges: []sdk.SchemaPrivilege{sdk.SchemaPrivilegeUsage, sdk.SchemaPrivilegeCreatePipe, sdk.SchemaPrivilegeCreateTask}, }, &sdk.AccountRoleGrantOn{ Schema: &sdk.GrantOnSchema{ @@ -931,6 +931,27 @@ func TestInt_GrantOwnership(t *testing.T) { require.NoError(t, err) } + grantTaskRole := func(t *testing.T, role *sdk.Role) { + t.Helper() + + grantDatabaseAndSchemaUsage(t, role) + + err := client.Grants.GrantPrivilegesToAccountRole( + ctx, + &sdk.AccountRoleGrantPrivileges{ + AccountObjectPrivileges: []sdk.AccountObjectPrivilege{sdk.AccountObjectPrivilegeUsage}, + }, + &sdk.AccountRoleGrantOn{ + AccountObject: &sdk.GrantOnAccountObject{ + Warehouse: sdk.Pointer(testWarehouse(t).ID()), + }, + }, + role.ID(), + new(sdk.GrantPrivilegesToAccountRoleOptions), + ) + require.NoError(t, err) + } + makeAccountRoleOperableOnPipe := func(t *testing.T, grantingRole string, pipe *sdk.Pipe) { t.Helper() @@ -953,15 +974,23 @@ func TestInt_GrantOwnership(t *testing.T) { require.NoError(t, err) } - ownershipGrantOnPipe := func(p *sdk.Pipe) sdk.OwnershipGrantOn { + ownershipGrantOnObject := func(objectType sdk.ObjectType, identifier sdk.ObjectIdentifier) sdk.OwnershipGrantOn { return sdk.OwnershipGrantOn{ Object: &sdk.Object{ - ObjectType: sdk.ObjectTypePipe, - Name: p.ID(), + ObjectType: objectType, + Name: identifier, }, } } + ownershipGrantOnPipe := func(pipe *sdk.Pipe) sdk.OwnershipGrantOn { + return ownershipGrantOnObject(sdk.ObjectTypePipe, pipe.ID()) + } + + ownershipGrantOnTask := func(task *sdk.Task) sdk.OwnershipGrantOn { + return ownershipGrantOnObject(sdk.ObjectTypeTask, task.ID()) + } + t.Run("on schema object to database role", func(t *testing.T) { databaseRole, _ := createDatabaseRole(t, client, testDb(t)) databaseRoleId := sdk.NewDatabaseObjectIdentifier(testDb(t).Name, databaseRole.Name) @@ -1678,6 +1707,152 @@ func TestInt_GrantOwnership(t *testing.T) { require.NoError(t, err) require.Equal(t, sdk.PausedPipeExecutionState, secondPipeExecutionState) }) + + t.Run("on task - with ownership", func(t *testing.T) { + task, taskCleanup := createTask(t, client, testDb(t), testSchema(t)) + t.Cleanup(taskCleanup) + + role, roleCleanup := createRole(t, client) + t.Cleanup(roleCleanup) + + err := client.Grants.GrantOwnership( + ctx, + ownershipGrantOnTask(task), + sdk.OwnershipGrantTo{ + AccountRoleName: sdk.Pointer(role.ID()), + }, + new(sdk.GrantOwnershipOptions), + ) + require.NoError(t, err) + checkOwnershipOnObjectToRole(t, ownershipGrantOnTask(task), role.ID().Name()) + }) + + t.Run("on task - without ownership", func(t *testing.T) { + taskRole, taskRoleCleanup := createRoleGrantedToCurrentUser(t, client) + t.Cleanup(taskRoleCleanup) + + role, roleCleanup := createRoleGrantedToCurrentUser(t, client) + t.Cleanup(roleCleanup) + + // Role needs usage on the database and schema to later be able to remove task in the cleanup + grantDatabaseAndSchemaUsage(t, role) + + // grantTaskRole grants the necessary privileges to a role to be able to create task + grantTaskRole(t, taskRole) + + // Use a previously prepared role to create a task + usePreviousRole := useRole(t, client, taskRole.Name) + + taskId := sdk.NewSchemaObjectIdentifier(TestDatabaseName, TestSchemaName, random.AlphaN(20)) + withWarehouseReq := sdk.NewCreateTaskWarehouseRequest().WithWarehouse(sdk.Pointer(testWarehouse(t).ID())) + task, taskCleanup := createTaskWithRequest(t, client, sdk.NewCreateTaskRequest(taskId, "SELECT CURRENT_TIMESTAMP").WithWarehouse(withWarehouseReq)) + t.Cleanup(taskCleanup) + + usePreviousRole() + + t.Cleanup(func() { + currentRole, err := client.ContextFunctions.CurrentRole(ctx) + require.NoError(t, err) + + grantOwnershipToRole(t, currentRole, ownershipGrantOnTask(task)) + }) + + err := client.Grants.GrantOwnership( + ctx, + ownershipGrantOnTask(task), + sdk.OwnershipGrantTo{ + AccountRoleName: sdk.Pointer(role.ID()), + }, + new(sdk.GrantOwnershipOptions), + ) + require.NoError(t, err) + checkOwnershipOnObjectToRole(t, ownershipGrantOnTask(task), role.ID().Name()) + }) + + t.Run("on all tasks - with ownership", func(t *testing.T) { + task, taskCleanup := createTask(t, client, testDb(t), testSchema(t)) + t.Cleanup(taskCleanup) + + secondTask, secondTaskCleanup := createTask(t, client, testDb(t), testSchema(t)) + t.Cleanup(secondTaskCleanup) + + role, roleCleanup := createRole(t, client) + t.Cleanup(roleCleanup) + + onAllTasks := sdk.OwnershipGrantOn{ + All: &sdk.GrantOnSchemaObjectIn{ + PluralObjectType: sdk.PluralObjectTypeTasks, + InSchema: sdk.Pointer(testSchema(t).ID()), + }, + } + err := client.Grants.GrantOwnership( + ctx, + onAllTasks, + sdk.OwnershipGrantTo{ + AccountRoleName: sdk.Pointer(role.ID()), + }, + new(sdk.GrantOwnershipOptions), + ) + require.NoError(t, err) + + checkOwnershipOnObjectToRole(t, ownershipGrantOnTask(task), role.ID().Name()) + checkOwnershipOnObjectToRole(t, ownershipGrantOnTask(secondTask), role.ID().Name()) + }) + + t.Run("on all tasks - without ownership", func(t *testing.T) { + taskRole, taskRoleCleanup := createRoleGrantedToCurrentUser(t, client) + t.Cleanup(taskRoleCleanup) + + role, roleCleanup := createRoleGrantedToCurrentUser(t, client) + t.Cleanup(roleCleanup) + + // Role needs usage on the database and schema to later be able to remove task in the cleanup + grantDatabaseAndSchemaUsage(t, role) + + // grantTaskRole grants the necessary privileges to a role to be able to create task + grantTaskRole(t, taskRole) + + // Use a previously prepared role to create a task + usePreviousRole := useRole(t, client, taskRole.Name) + + taskId := sdk.NewSchemaObjectIdentifier(TestDatabaseName, TestSchemaName, random.AlphaN(20)) + withWarehouseReq := sdk.NewCreateTaskWarehouseRequest().WithWarehouse(sdk.Pointer(testWarehouse(t).ID())) + task, taskCleanup := createTaskWithRequest(t, client, sdk.NewCreateTaskRequest(taskId, "SELECT CURRENT_TIMESTAMP").WithWarehouse(withWarehouseReq)) + t.Cleanup(taskCleanup) + + secondTaskId := sdk.NewSchemaObjectIdentifier(TestDatabaseName, TestSchemaName, random.AlphaN(20)) + secondTask, secondTaskCleanup := createTaskWithRequest(t, client, sdk.NewCreateTaskRequest(secondTaskId, "SELECT CURRENT_TIMESTAMP").WithWarehouse(withWarehouseReq)) + t.Cleanup(secondTaskCleanup) + + usePreviousRole() + + t.Cleanup(func() { + currentRole, err := client.ContextFunctions.CurrentRole(ctx) + require.NoError(t, err) + + grantOwnershipToRole(t, currentRole, ownershipGrantOnTask(task)) + grantOwnershipToRole(t, currentRole, ownershipGrantOnTask(secondTask)) + }) + + onAllTasks := sdk.OwnershipGrantOn{ + All: &sdk.GrantOnSchemaObjectIn{ + PluralObjectType: sdk.PluralObjectTypeTasks, + InSchema: sdk.Pointer(testSchema(t).ID()), + }, + } + err := client.Grants.GrantOwnership( + ctx, + onAllTasks, + sdk.OwnershipGrantTo{ + AccountRoleName: sdk.Pointer(role.ID()), + }, + new(sdk.GrantOwnershipOptions), + ) + require.NoError(t, err) + + checkOwnershipOnObjectToRole(t, ownershipGrantOnTask(task), role.ID().Name()) + checkOwnershipOnObjectToRole(t, ownershipGrantOnTask(secondTask), role.ID().Name()) + }) } func TestInt_ShowGrants(t *testing.T) { diff --git a/pkg/sdk/testint/helpers_test.go b/pkg/sdk/testint/helpers_test.go index b80c372e79..cde606e889 100644 --- a/pkg/sdk/testint/helpers_test.go +++ b/pkg/sdk/testint/helpers_test.go @@ -903,3 +903,27 @@ func updateAccountParameterTemporarily(t *testing.T, client *sdk.Client, paramet require.NoError(t, err) } } + +func createTaskWithRequest(t *testing.T, client *sdk.Client, request *sdk.CreateTaskRequest) (*sdk.Task, func()) { + t.Helper() + ctx := context.Background() + + id := request.GetName() + + err := client.Tasks.Create(ctx, request) + require.NoError(t, err) + + task, err := client.Tasks.ShowByID(ctx, id) + require.NoError(t, err) + + return task, func() { + err = client.Tasks.Drop(ctx, sdk.NewDropTaskRequest(id)) + require.NoError(t, err) + } +} + +func createTask(t *testing.T, client *sdk.Client, database *sdk.Database, schema *sdk.Schema) (*sdk.Task, func()) { + t.Helper() + id := sdk.NewSchemaObjectIdentifier(database.Name, schema.Name, random.AlphaN(20)) + return createTaskWithRequest(t, client, sdk.NewCreateTaskRequest(id, "SELECT CURRENT_TIMESTAMP")) +} From cb6c278dfe7ccf948e7dc7ade4a55b9422bb50aa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Thu, 4 Apr 2024 11:16:51 +0200 Subject: [PATCH 3/7] Changes after review --- pkg/sdk/testint/grants_integration_test.go | 304 --------------------- 1 file changed, 304 deletions(-) diff --git a/pkg/sdk/testint/grants_integration_test.go b/pkg/sdk/testint/grants_integration_test.go index a2de2c54f6..a3f92c35a8 100644 --- a/pkg/sdk/testint/grants_integration_test.go +++ b/pkg/sdk/testint/grants_integration_test.go @@ -1404,310 +1404,6 @@ func TestInt_GrantOwnership(t *testing.T) { require.Equal(t, sdk.PausedPipeExecutionState, secondPipeExecutionState) }) - t.Run("on pipe - with ownership", func(t *testing.T) { - pipe, pipeCleanup := createPipe(t, client, testDb(t), testSchema(t), random.AlphaN(20), copyStatement) - t.Cleanup(pipeCleanup) - - pipeExecutionState, err := client.SystemFunctions.PipeStatus(pipe.ID()) - require.NoError(t, err) - require.Equal(t, sdk.RunningPipeExecutionState, pipeExecutionState) - - role, roleCleanup := createRole(t, client) - t.Cleanup(roleCleanup) - - err = client.Grants.GrantOwnership( - ctx, - ownershipGrantOnPipe(pipe), - sdk.OwnershipGrantTo{ - AccountRoleName: sdk.Pointer(role.ID()), - }, - new(sdk.GrantOwnershipOptions), - ) - require.NoError(t, err) - checkOwnershipOnObjectToRole(t, ownershipGrantOnPipe(pipe), role.ID().Name()) - - currentRole, err := client.ContextFunctions.CurrentRole(ctx) - require.NoError(t, err) - - grantOwnershipToRole(t, currentRole, ownershipGrantOnPipe(pipe)) - checkOwnershipOnObjectToRole(t, ownershipGrantOnPipe(pipe), currentRole) - - pipeExecutionState, err = client.SystemFunctions.PipeStatus(pipe.ID()) - require.NoError(t, err) - require.Equal(t, sdk.PausedPipeExecutionState, pipeExecutionState) - }) - - t.Run("on pipe - with operate and monitor privileges granted", func(t *testing.T) { - role, roleCleanup := createRoleGrantedToCurrentUser(t, client) - t.Cleanup(roleCleanup) - - pipeRole, pipeRoleCleanup := createRoleGrantedToCurrentUser(t, client) - t.Cleanup(pipeRoleCleanup) - - // Role needs usage on the database and schema to later be able to remove pipe in the cleanup - grantDatabaseAndSchemaUsage(t, role) - // grantPipeRole grants the necessary privileges to a role to be able to create pipe - grantPipeRole(t, pipeRole, table, stage) - - previousRole, err := client.ContextFunctions.CurrentRole(ctx) - require.NoError(t, err) - - // Use a previously prepared role to create a pipe and grant MONITOR + OPERATE to the previously used role (ACCOUNTADMIN). - usePreviousRole := useRole(t, client, pipeRole.Name) - - pipe, pipeCleanup := createPipe(t, client, testDb(t), testSchema(t), random.AlphaN(20), copyStatement) - t.Cleanup(func() { - usePreviousRole = useRole(t, client, role.Name) - pipeCleanup() - usePreviousRole() - }) - - // Grant MONITOR and OPERATE privileges to the role. - makeAccountRoleOperableOnPipe(t, previousRole, pipe) - - usePreviousRole() - - err = client.Pipes.Alter(ctx, pipe.ID(), &sdk.AlterPipeOptions{ - Set: &sdk.PipeSet{ - PipeExecutionPaused: sdk.Bool(false), - }, - }) - require.NoError(t, err) - - pipeExecutionState, err := client.SystemFunctions.PipeStatus(pipe.ID()) - require.NoError(t, err) - require.Equal(t, sdk.RunningPipeExecutionState, pipeExecutionState) - - err = client.Grants.GrantOwnership( - ctx, - ownershipGrantOnPipe(pipe), - sdk.OwnershipGrantTo{ - AccountRoleName: sdk.Pointer(role.ID()), - }, - &sdk.GrantOwnershipOptions{ - CurrentGrants: &sdk.OwnershipCurrentGrants{ - OutboundPrivileges: sdk.Revoke, // To revoke MONITOR privilege from ACCOUNTADMIN automatically - }, - }, - ) - require.NoError(t, err) - checkOwnershipOnObjectToRole(t, ownershipGrantOnPipe(pipe), role.ID().Name()) - - usePreviousRole() - - pipeExecutionState, err = client.SystemFunctions.PipeStatus(pipe.ID()) - require.NoError(t, err) - require.Equal(t, sdk.PausedPipeExecutionState, pipeExecutionState) - }) - - t.Run("on pipe - with operate privilege granted and copy current grants option", func(t *testing.T) { - role, roleCleanup := createRoleGrantedToCurrentUser(t, client) - t.Cleanup(roleCleanup) - - pipeRole, pipeRoleCleanup := createRoleGrantedToCurrentUser(t, client) - t.Cleanup(pipeRoleCleanup) - - // Role needs usage on the database and schema to later be able to remove pipe in the cleanup - grantDatabaseAndSchemaUsage(t, role) - // grantPipeRole grants the necessary privileges to a role to be able to create pipe - grantPipeRole(t, pipeRole, table, stage) - - previousRole, err := client.ContextFunctions.CurrentRole(ctx) - require.NoError(t, err) - - // Use a previously prepared role to create a pipe and grant MONITOR + OPERATE to the previously used role (ACCOUNTADMIN). - usePreviousRole := useRole(t, client, pipeRole.Name) - - pipe, pipeCleanup := createPipe(t, client, testDb(t), testSchema(t), random.AlphaN(20), copyStatement) - t.Cleanup(func() { - usePreviousRole = useRole(t, client, role.Name) - pipeCleanup() - usePreviousRole() - }) - - // Grant MONITOR and OPERATE privileges to the role. - makeAccountRoleOperableOnPipe(t, previousRole, pipe) - - usePreviousRole() - - err = client.Pipes.Alter(ctx, pipe.ID(), &sdk.AlterPipeOptions{ - Set: &sdk.PipeSet{ - PipeExecutionPaused: sdk.Bool(false), - }, - }) - require.NoError(t, err) - - pipeExecutionState, err := client.SystemFunctions.PipeStatus(pipe.ID()) - require.NoError(t, err) - require.Equal(t, sdk.RunningPipeExecutionState, pipeExecutionState) - - err = client.Grants.GrantOwnership( - ctx, - ownershipGrantOnPipe(pipe), - sdk.OwnershipGrantTo{ - AccountRoleName: sdk.Pointer(role.ID()), - }, - &sdk.GrantOwnershipOptions{ - CurrentGrants: &sdk.OwnershipCurrentGrants{ - OutboundPrivileges: sdk.Copy, // With copy, we'll be able to resume the pipe after ownership transfer - }, - }, - ) - require.NoError(t, err) - checkOwnershipOnObjectToRole(t, ownershipGrantOnPipe(pipe), role.ID().Name()) - - usePreviousRole() - - pipeExecutionState, err = client.SystemFunctions.PipeStatus(pipe.ID()) - require.NoError(t, err) - require.Equal(t, sdk.RunningPipeExecutionState, pipeExecutionState) - }) - - t.Run("on pipe - with neither ownership nor operate", func(t *testing.T) { - role, roleCleanup := createRoleGrantedToCurrentUser(t, client) - t.Cleanup(roleCleanup) - - pipeRole, pipeRoleCleanup := createRoleGrantedToCurrentUser(t, client) - t.Cleanup(pipeRoleCleanup) - - // Role needs usage on the database and schema to later be able to remove pipe in the cleanup - grantDatabaseAndSchemaUsage(t, role) - // grantPipeRole grants the necessary privileges to a role to be able to create pipe - grantPipeRole(t, pipeRole, table, stage) - - // Use a previously prepared role to create a pipe and grant MONITOR + OPERATE to the previously used role (ACCOUNTADMIN). - usePreviousRole := useRole(t, client, pipeRole.Name) - - pipe, pipeCleanup := createPipe(t, client, testDb(t), testSchema(t), random.AlphaN(20), copyStatement) - t.Cleanup(func() { - usePreviousRole = useRole(t, client, pipeRole.Name) - pipeCleanup() - usePreviousRole() - }) - - err := client.Pipes.Alter(ctx, pipe.ID(), &sdk.AlterPipeOptions{ - Set: &sdk.PipeSet{ - PipeExecutionPaused: sdk.Bool(false), - }, - }) - require.NoError(t, err) - - pipeExecutionState, err := client.SystemFunctions.PipeStatus(pipe.ID()) - require.NoError(t, err) - require.Equal(t, sdk.RunningPipeExecutionState, pipeExecutionState) - - usePreviousRole() - - err = client.Grants.GrantOwnership( - ctx, - ownershipGrantOnPipe(pipe), - sdk.OwnershipGrantTo{ - AccountRoleName: sdk.Pointer(role.ID()), - }, - new(sdk.GrantOwnershipOptions), - ) - require.ErrorContains(t, err, fmt.Sprintf("Pipe %s not in paused state. To pause pipe run ALTER PIPE %s SET PIPE_EXECUTION_PAUSED=true", pipe.Name, pipe.Name)) - }) - - t.Run("on pipe - with neither ownership nor operate on paused pipe", func(t *testing.T) { - role, roleCleanup := createRoleGrantedToCurrentUser(t, client) - t.Cleanup(roleCleanup) - - pipeRole, pipeRoleCleanup := createRoleGrantedToCurrentUser(t, client) - t.Cleanup(pipeRoleCleanup) - - // Role needs usage on the database and schema to later be able to remove pipe in the cleanup - grantDatabaseAndSchemaUsage(t, role) - // grantPipeRole grants the necessary privileges to a role to be able to create pipe - grantPipeRole(t, pipeRole, table, stage) - - // Use a previously prepared role to create a pipe and grant MONITOR + OPERATE to the previously used role (ACCOUNTADMIN). - usePreviousRole := useRole(t, client, pipeRole.Name) - - pipe, pipeCleanup := createPipe(t, client, testDb(t), testSchema(t), random.AlphaN(20), copyStatement) - t.Cleanup(func() { - usePreviousRole = useRole(t, client, role.Name) - pipeCleanup() - usePreviousRole() - }) - - err := client.Pipes.Alter(ctx, pipe.ID(), &sdk.AlterPipeOptions{ - Set: &sdk.PipeSet{ - PipeExecutionPaused: sdk.Bool(true), - }, - }) - require.NoError(t, err) - - pipeExecutionState, err := client.SystemFunctions.PipeStatus(pipe.ID()) - require.NoError(t, err) - require.Equal(t, sdk.PausedPipeExecutionState, pipeExecutionState) - - usePreviousRole() - - err = client.Grants.GrantOwnership( - ctx, - ownershipGrantOnPipe(pipe), - sdk.OwnershipGrantTo{ - AccountRoleName: sdk.Pointer(role.ID()), - }, - new(sdk.GrantOwnershipOptions), - ) - require.NoError(t, err) - checkOwnershipOnObjectToRole(t, ownershipGrantOnPipe(pipe), role.Name) - }) - - t.Run("on all pipes", func(t *testing.T) { - pipe, pipeCleanup := createPipe(t, client, testDb(t), testSchema(t), random.AlphaN(20), copyStatement) - t.Cleanup(pipeCleanup) - - secondPipe, secondPipeCleanup := createPipe(t, client, testDb(t), testSchema(t), random.AlphaN(20), copyStatement) - t.Cleanup(secondPipeCleanup) - - pipeExecutionState, err := client.SystemFunctions.PipeStatus(pipe.ID()) - require.NoError(t, err) - require.Equal(t, sdk.RunningPipeExecutionState, pipeExecutionState) - - secondPipeExecutionState, err := client.SystemFunctions.PipeStatus(secondPipe.ID()) - require.NoError(t, err) - require.Equal(t, sdk.RunningPipeExecutionState, secondPipeExecutionState) - - role, roleCleanup := createRole(t, client) - t.Cleanup(roleCleanup) - - onAllPipesInSchema := sdk.OwnershipGrantOn{ - All: &sdk.GrantOnSchemaObjectIn{ - PluralObjectType: sdk.PluralObjectTypePipes, - InSchema: sdk.Pointer(testSchema(t).ID()), - }, - } - err = client.Grants.GrantOwnership( - ctx, - onAllPipesInSchema, - sdk.OwnershipGrantTo{ - AccountRoleName: sdk.Pointer(role.ID()), - }, - new(sdk.GrantOwnershipOptions), - ) - require.NoError(t, err) - - checkOwnershipOnObjectToRole(t, ownershipGrantOnPipe(pipe), role.ID().Name()) - checkOwnershipOnObjectToRole(t, ownershipGrantOnPipe(secondPipe), role.ID().Name()) - - currentRole, err := client.ContextFunctions.CurrentRole(ctx) - require.NoError(t, err) - grantOwnershipToRole(t, currentRole, onAllPipesInSchema) - checkOwnershipOnObjectToRole(t, ownershipGrantOnPipe(pipe), currentRole) - checkOwnershipOnObjectToRole(t, ownershipGrantOnPipe(secondPipe), currentRole) - - pipeExecutionState, err = client.SystemFunctions.PipeStatus(pipe.ID()) - require.NoError(t, err) - require.Equal(t, sdk.PausedPipeExecutionState, pipeExecutionState) - - secondPipeExecutionState, err = client.SystemFunctions.PipeStatus(secondPipe.ID()) - require.NoError(t, err) - require.Equal(t, sdk.PausedPipeExecutionState, secondPipeExecutionState) - }) - t.Run("on task - with ownership", func(t *testing.T) { task, taskCleanup := createTask(t, client, testDb(t), testSchema(t)) t.Cleanup(taskCleanup) From 9a5d2740602a156b8751956201381d5c7db1bd93 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Thu, 4 Apr 2024 12:41:50 +0200 Subject: [PATCH 4/7] wip --- .../TestAcc_GrantOwnership/OnAllTasks/test.tf | 12 +- .../TestAcc_GrantOwnership/OnTask/test.tf | 6 +- pkg/sdk/grants_impl.go | 72 ++++++++--- pkg/sdk/testint/grants_integration_test.go | 120 +++++++++++++++++- tmp/grant_ownership.md.tmpl | 2 + 5 files changed, 182 insertions(+), 30 deletions(-) diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnAllTasks/test.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnAllTasks/test.tf index 87598d618f..6480a83dab 100644 --- a/pkg/resources/testdata/TestAcc_GrantOwnership/OnAllTasks/test.tf +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnAllTasks/test.tf @@ -3,16 +3,16 @@ resource "snowflake_role" "test" { } resource "snowflake_task" "test" { - database = var.database - schema = var.schema - name = var.task + database = var.database + schema = var.schema + name = var.task sql_statement = "SELECT CURRENT_TIMESTAMP" } resource "snowflake_task" "second_test" { - database = var.database - schema = var.schema - name = var.second_task + database = var.database + schema = var.schema + name = var.second_task sql_statement = "SELECT CURRENT_TIMESTAMP" } diff --git a/pkg/resources/testdata/TestAcc_GrantOwnership/OnTask/test.tf b/pkg/resources/testdata/TestAcc_GrantOwnership/OnTask/test.tf index 435bb9d8d1..58fc9840ee 100644 --- a/pkg/resources/testdata/TestAcc_GrantOwnership/OnTask/test.tf +++ b/pkg/resources/testdata/TestAcc_GrantOwnership/OnTask/test.tf @@ -3,9 +3,9 @@ resource "snowflake_role" "test" { } resource "snowflake_task" "test" { - database = var.database - schema = var.schema - name = var.task + database = var.database + schema = var.schema + name = var.task sql_statement = "SELECT CURRENT_TIMESTAMP" } diff --git a/pkg/sdk/grants_impl.go b/pkg/sdk/grants_impl.go index 5996d78566..d26185e850 100644 --- a/pkg/sdk/grants_impl.go +++ b/pkg/sdk/grants_impl.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/internal/collections" "log" "slices" @@ -263,7 +264,6 @@ func (v *grants) GrantOwnership(ctx context.Context, on OwnershipGrantOn, to Own ) }, ) - } return validateAndExec(v.client, ctx, opts) @@ -374,38 +374,74 @@ func (v *grants) grantOwnershipOnPipe(ctx context.Context, pipeId SchemaObjectId } func (v *grants) grantOwnershipOnTask(ctx context.Context, taskId SchemaObjectIdentifier, opts *GrantOwnershipOptions) error { - // 1. Suspend task roots. - tasksToResume, err := v.client.Tasks.SuspendRootTasks(ctx, taskId, taskId) // params ?? + currentGrants, err := v.client.Grants.Show(ctx, &ShowGrantOptions{ + On: &ShowGrantsOn{ + Object: &Object{ + ObjectType: ObjectTypeTask, + Name: taskId, + }, + }, + }) if err != nil { return err } - // 2. Grant ownership on the task. - if err := validateAndExec(v.client, ctx, opts); err != nil { + currentRole, err := v.client.ContextFunctions.CurrentRole(ctx) + if err != nil { return err } + currentRoleName := NewAccountObjectIdentifier(currentRole) - // 3. Resume tasks. - if len(tasksToResume) > 0 { - err = v.client.Tasks.ResumeTasks(ctx, tasksToResume) + currentTask, err := v.client.Tasks.ShowByID(ctx, taskId) + if err != nil { + return err + } + + isGrantedWithPrivilege := func(privilege string) bool { + return slices.ContainsFunc(currentGrants, func(grant Grant) bool { + return grant.GranteeName == currentRoleName && + grant.GrantedOn == ObjectTypeTask && + grant.Privilege == privilege + }) + } + canOperateOnTask := isGrantedWithPrivilege(SchemaObjectPrivilegeOperate.String()) + canExecuteTask := isGrantedWithPrivilege(GlobalPrivilegeExecuteTask.String()) + + var tasksToResume []SchemaObjectIdentifier + if canOperateOnTask { + tasksToResume, err = v.client.Tasks.SuspendRootTasks(ctx, taskId, taskId) if err != nil { return err } + } else { + log.Printf("[WARN] Insufficient privileges to operate on task: %s (OPERATE privilege). Trying to proceed with ownership transfer...", taskId.FullyQualifiedName()) + } + + if err := validateAndExec(v.client, ctx, opts); err != nil { + return err + } + + if currentTask.State == TaskStateStarted && !slices.ContainsFunc(tasksToResume, func(id SchemaObjectIdentifier) bool { + return id.FullyQualifiedName() == currentTask.ID().FullyQualifiedName() + }) { + tasksToResume = append(tasksToResume, currentTask.ID()) + } + + if len(tasksToResume) > 0 { + if canExecuteTask { + err = v.client.Tasks.ResumeTasks(ctx, tasksToResume) + if err != nil { + return err + } + } else { + tasksToResumeString := collections.Map(tasksToResume, func(id SchemaObjectIdentifier) string { return id.FullyQualifiedName() }) + log.Printf("[WARN] Insufficient privileges to resume tasks: %v (EXECUTE TASK privilege). Tasks have to be resumed manually.", tasksToResumeString) + } } return nil } -// TODO: consider (describe in the pr) -//func (v *grants) grantOwnershipOnAllTasks(ctx context.Context, opts *GrantOwnershipOptions) error { -// grant on all tasks -// 1. grant ownership on all tasks (Snowflake will suspend automatically) -// 2. go through tasks in [database|schema] -// 3. resume w if here last_suspended_reason was equal to "GRANT_OWNERSHIP" (have to resume parent first) -// or -//return nil -//} - func (v *grants) grantTemporarily(ctx context.Context, privileges *AccountRoleGrantPrivileges, on *AccountRoleGrantOn, accountRoleName AccountObjectIdentifier) (func() error, error) { return func() error { return v.client.Grants.RevokePrivilegesFromAccountRole( diff --git a/pkg/sdk/testint/grants_integration_test.go b/pkg/sdk/testint/grants_integration_test.go index a3f92c35a8..aaac569286 100644 --- a/pkg/sdk/testint/grants_integration_test.go +++ b/pkg/sdk/testint/grants_integration_test.go @@ -950,6 +950,7 @@ func TestInt_GrantOwnership(t *testing.T) { new(sdk.GrantPrivilegesToAccountRoleOptions), ) require.NoError(t, err) + } makeAccountRoleOperableOnPipe := func(t *testing.T, grantingRole string, pipe *sdk.Pipe) { @@ -1408,10 +1409,17 @@ func TestInt_GrantOwnership(t *testing.T) { task, taskCleanup := createTask(t, client, testDb(t), testSchema(t)) t.Cleanup(taskCleanup) + err := client.Tasks.Alter(ctx, sdk.NewAlterTaskRequest(task.ID()).WithResume(sdk.Bool(true))) + require.NoError(t, err) + role, roleCleanup := createRole(t, client) t.Cleanup(roleCleanup) - err := client.Grants.GrantOwnership( + task, err = client.Tasks.ShowByID(ctx, task.ID()) + require.NoError(t, err) + require.Equal(t, sdk.TaskStateStarted, task.State) + + err = client.Grants.GrantOwnership( ctx, ownershipGrantOnTask(task), sdk.OwnershipGrantTo{ @@ -1421,6 +1429,10 @@ func TestInt_GrantOwnership(t *testing.T) { ) require.NoError(t, err) checkOwnershipOnObjectToRole(t, ownershipGrantOnTask(task), role.ID().Name()) + + task, err = client.Tasks.ShowByID(ctx, task.ID()) + require.NoError(t, err) + require.Equal(t, sdk.TaskStateSuspended, task.State) }) t.Run("on task - without ownership", func(t *testing.T) { @@ -1444,6 +1456,9 @@ func TestInt_GrantOwnership(t *testing.T) { task, taskCleanup := createTaskWithRequest(t, client, sdk.NewCreateTaskRequest(taskId, "SELECT CURRENT_TIMESTAMP").WithWarehouse(withWarehouseReq)) t.Cleanup(taskCleanup) + err := client.Tasks.Alter(ctx, sdk.NewAlterTaskRequest(task.ID()).WithResume(sdk.Bool(true))) + require.NoError(t, err) + usePreviousRole() t.Cleanup(func() { @@ -1453,7 +1468,11 @@ func TestInt_GrantOwnership(t *testing.T) { grantOwnershipToRole(t, currentRole, ownershipGrantOnTask(task)) }) - err := client.Grants.GrantOwnership( + task, err = client.Tasks.ShowByID(ctx, task.ID()) + require.NoError(t, err) + require.Equal(t, sdk.TaskStateStarted, task.State) + + err = client.Grants.GrantOwnership( ctx, ownershipGrantOnTask(task), sdk.OwnershipGrantTo{ @@ -1463,6 +1482,101 @@ func TestInt_GrantOwnership(t *testing.T) { ) require.NoError(t, err) checkOwnershipOnObjectToRole(t, ownershipGrantOnTask(task), role.ID().Name()) + + task, err = client.Tasks.ShowByID(ctx, task.ID()) + require.NoError(t, err) + require.Equal(t, sdk.TaskStateSuspended, task.State) + }) + + t.Run("on task - with operate and execute task", func(t *testing.T) { + taskRole, taskRoleCleanup := createRoleGrantedToCurrentUser(t, client) + t.Cleanup(taskRoleCleanup) + + role, roleCleanup := createRoleGrantedToCurrentUser(t, client) + t.Cleanup(roleCleanup) + + // Role needs usage on the database and schema to later be able to remove task in the cleanup + grantDatabaseAndSchemaUsage(t, role) + + // grantTaskRole grants the necessary privileges to a role to be able to create task + grantTaskRole(t, taskRole) + + currentRole, err := client.ContextFunctions.CurrentRole(ctx) + require.NoError(t, err) + + err = client.Grants.GrantPrivilegesToAccountRole( + ctx, + &sdk.AccountRoleGrantPrivileges{ + GlobalPrivileges: []sdk.GlobalPrivilege{sdk.GlobalPrivilegeExecuteTask}, + }, + &sdk.AccountRoleGrantOn{ + Account: sdk.Bool(true), + }, + sdk.NewAccountObjectIdentifier(currentRole), + new(sdk.GrantPrivilegesToAccountRoleOptions), + ) + require.NoError(t, err) + + // Use a previously prepared role to create a task + usePreviousRole := useRole(t, client, taskRole.Name) + + taskId := sdk.NewSchemaObjectIdentifier(TestDatabaseName, TestSchemaName, random.AlphaN(20)) + withWarehouseReq := sdk.NewCreateTaskWarehouseRequest().WithWarehouse(sdk.Pointer(testWarehouse(t).ID())) + task, taskCleanup := createTaskWithRequest(t, client, sdk.NewCreateTaskRequest(taskId, "SELECT CURRENT_TIMESTAMP").WithWarehouse(withWarehouseReq).WithSchedule(sdk.String("60 minutes"))) + t.Cleanup(taskCleanup) + + err = client.Grants.GrantPrivilegesToAccountRole( + ctx, + &sdk.AccountRoleGrantPrivileges{ + SchemaObjectPrivileges: []sdk.SchemaObjectPrivilege{sdk.SchemaObjectPrivilegeOperate}, + }, + &sdk.AccountRoleGrantOn{ + SchemaObject: &sdk.GrantOnSchemaObject{ + SchemaObject: &sdk.Object{ + ObjectType: sdk.ObjectTypeTask, + Name: task.ID(), + }, + }, + }, + sdk.NewAccountObjectIdentifier(currentRole), + new(sdk.GrantPrivilegesToAccountRoleOptions), + ) + require.NoError(t, err) + + err = client.Tasks.Alter(ctx, sdk.NewAlterTaskRequest(task.ID()).WithResume(sdk.Bool(true))) + require.NoError(t, err) + + usePreviousRole() + + t.Cleanup(func() { + currentRole, err := client.ContextFunctions.CurrentRole(ctx) + require.NoError(t, err) + + grantOwnershipToRole(t, currentRole, ownershipGrantOnTask(task)) + }) + + currentTask, err := client.Tasks.ShowByID(ctx, task.ID()) + require.NoError(t, err) + require.Equal(t, sdk.TaskStateStarted, currentTask.State) + + err = client.Grants.GrantOwnership( + ctx, + ownershipGrantOnTask(task), + sdk.OwnershipGrantTo{ + AccountRoleName: sdk.Pointer(role.ID()), + }, + &sdk.GrantOwnershipOptions{ + CurrentGrants: &sdk.OwnershipCurrentGrants{ + OutboundPrivileges: sdk.Copy, + }, + }, + ) + require.NoError(t, err) + checkOwnershipOnObjectToRole(t, ownershipGrantOnTask(task), role.ID().Name()) + + currentTask, err = client.Tasks.ShowByID(ctx, task.ID()) + require.NoError(t, err) + require.Equal(t, sdk.TaskStateStarted, currentTask.State) }) t.Run("on all tasks - with ownership", func(t *testing.T) { @@ -1495,7 +1609,7 @@ func TestInt_GrantOwnership(t *testing.T) { checkOwnershipOnObjectToRole(t, ownershipGrantOnTask(secondTask), role.ID().Name()) }) - t.Run("on all tasks - without ownership", func(t *testing.T) { + t.Run("on all tasks - without ownership", func(t *testing.T) { taskRole, taskRoleCleanup := createRoleGrantedToCurrentUser(t, client) t.Cleanup(taskRoleCleanup) diff --git a/tmp/grant_ownership.md.tmpl b/tmp/grant_ownership.md.tmpl index aa24bbbe5c..5f5bf2f8eb 100644 --- a/tmp/grant_ownership.md.tmpl +++ b/tmp/grant_ownership.md.tmpl @@ -42,6 +42,8 @@ To transfer ownership of a pipe(s) **semi-automatically** you have to: 2. Create Terraform configuration with the `snowflake_grant_ownership` resource and perform ownership transfer with the `terraform apply`. 3. To resume the pipe(s) after ownership transfer use [PIPE_FORCE_RESUME system function](https://docs.snowflake.com/en/sql-reference/functions/system_pipe_force_resume). +## Granting ownership on tasks + ## Granting ownership on tasks {{/* TODO: In next pr */}} From 6d3fa86879cdef3bb73cde6c5c0a39687d72b0ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Mon, 8 Apr 2024 10:55:00 +0200 Subject: [PATCH 5/7] wip --- pkg/sdk/grants_impl.go | 45 +++++- pkg/sdk/testint/grants_integration_test.go | 180 +++++++++++++++++---- pkg/sdk/testint/helpers_test.go | 3 +- 3 files changed, 189 insertions(+), 39 deletions(-) diff --git a/pkg/sdk/grants_impl.go b/pkg/sdk/grants_impl.go index d26185e850..9d663a83a7 100644 --- a/pkg/sdk/grants_impl.go +++ b/pkg/sdk/grants_impl.go @@ -374,7 +374,7 @@ func (v *grants) grantOwnershipOnPipe(ctx context.Context, pipeId SchemaObjectId } func (v *grants) grantOwnershipOnTask(ctx context.Context, taskId SchemaObjectIdentifier, opts *GrantOwnershipOptions) error { - currentGrants, err := v.client.Grants.Show(ctx, &ShowGrantOptions{ + currentGrantsOnObject, err := v.client.Grants.Show(ctx, &ShowGrantOptions{ On: &ShowGrantsOn{ Object: &Object{ ObjectType: ObjectTypeTask, @@ -386,6 +386,15 @@ func (v *grants) grantOwnershipOnTask(ctx context.Context, taskId SchemaObjectId return err } + currentGrantsOnAccount, err := v.client.Grants.Show(ctx, &ShowGrantOptions{ + On: &ShowGrantsOn{ + Account: Bool(true), + }, + }) + if err != nil { + return err + } + currentRole, err := v.client.ContextFunctions.CurrentRole(ctx) if err != nil { return err @@ -397,18 +406,32 @@ func (v *grants) grantOwnershipOnTask(ctx context.Context, taskId SchemaObjectId return err } - isGrantedWithPrivilege := func(privilege string) bool { - return slices.ContainsFunc(currentGrants, func(grant Grant) bool { + currentGrantsOnTaskWarehouse, err := v.client.Grants.Show(ctx, &ShowGrantOptions{ + On: &ShowGrantsOn{ + Object: &Object{ + ObjectType: ObjectTypeWarehouse, + Name: NewAccountObjectIdentifier(currentTask.Warehouse), + }, + }, + }) + if err != nil { + return err + } + + isGrantedWithPrivilege := func(collection []Grant, grantedOn ObjectType, privilege string) bool { + return slices.ContainsFunc(collection, func(grant Grant) bool { return grant.GranteeName == currentRoleName && - grant.GrantedOn == ObjectTypeTask && + grant.GrantedOn == grantedOn && grant.Privilege == privilege }) } - canOperateOnTask := isGrantedWithPrivilege(SchemaObjectPrivilegeOperate.String()) - canExecuteTask := isGrantedWithPrivilege(GlobalPrivilegeExecuteTask.String()) + canOperateOnTask := isGrantedWithPrivilege(currentGrantsOnObject, ObjectTypeTask, SchemaObjectPrivilegeOperate.String()) + isGrantedWithWarehouseUsage := isGrantedWithPrivilege(currentGrantsOnTaskWarehouse, ObjectTypeWarehouse, AccountObjectPrivilegeUsage.String()) + canSuspendTask := canOperateOnTask || isGrantedWithPrivilege(currentGrantsOnObject, ObjectTypeTask, "OWNERSHIP") + canResumeTask := isGrantedWithWarehouseUsage || canOperateOnTask || isGrantedWithPrivilege(currentGrantsOnAccount, ObjectTypeAccount, GlobalPrivilegeExecuteTask.String()) var tasksToResume []SchemaObjectIdentifier - if canOperateOnTask { + if canSuspendTask { tasksToResume, err = v.client.Tasks.SuspendRootTasks(ctx, taskId, taskId) if err != nil { return err @@ -417,10 +440,16 @@ func (v *grants) grantOwnershipOnTask(ctx context.Context, taskId SchemaObjectId log.Printf("[WARN] Insufficient privileges to operate on task: %s (OPERATE privilege). Trying to proceed with ownership transfer...", taskId.FullyQualifiedName()) } + tasksBefore, _ := v.client.Tasks.Show(ctx, NewShowTaskRequest().WithIn(&In{Schema: NewDatabaseObjectIdentifier(taskId.databaseName, taskId.schemaName)})) + _ = tasksBefore + if err := validateAndExec(v.client, ctx, opts); err != nil { return err } + tasksAfter, _ := v.client.Tasks.Show(ctx, NewShowTaskRequest().WithIn(&In{Schema: NewDatabaseObjectIdentifier(taskId.databaseName, taskId.schemaName)})) + _ = tasksAfter + if currentTask.State == TaskStateStarted && !slices.ContainsFunc(tasksToResume, func(id SchemaObjectIdentifier) bool { return id.FullyQualifiedName() == currentTask.ID().FullyQualifiedName() }) { @@ -428,7 +457,7 @@ func (v *grants) grantOwnershipOnTask(ctx context.Context, taskId SchemaObjectId } if len(tasksToResume) > 0 { - if canExecuteTask { + if canResumeTask && ((opts.CurrentGrants != nil && opts.CurrentGrants.OutboundPrivileges == Copy) || (opts.To.AccountRoleName != nil && opts.To.AccountRoleName.Name() == currentRoleName.Name())) { err = v.client.Tasks.ResumeTasks(ctx, tasksToResume) if err != nil { return err diff --git a/pkg/sdk/testint/grants_integration_test.go b/pkg/sdk/testint/grants_integration_test.go index aaac569286..a5a98be8c6 100644 --- a/pkg/sdk/testint/grants_integration_test.go +++ b/pkg/sdk/testint/grants_integration_test.go @@ -841,16 +841,25 @@ func TestInt_GrantOwnership(t *testing.T) { require.NoError(t, err) } - grantOwnershipToRole := func(t *testing.T, roleName string, on sdk.OwnershipGrantOn) { + grantOwnershipToRole := func(t *testing.T, roleName string, on sdk.OwnershipGrantOn, outboundOpts *sdk.OwnershipCurrentGrantsOutboundPrivileges) { t.Helper() + var opts *sdk.GrantOwnershipOptions + if outboundOpts != nil { + opts = &sdk.GrantOwnershipOptions{ + CurrentGrants: &sdk.OwnershipCurrentGrants{ + OutboundPrivileges: *outboundOpts, + }, + } + } + err := client.Grants.GrantOwnership( ctx, on, sdk.OwnershipGrantTo{ AccountRoleName: sdk.Pointer(sdk.NewAccountObjectIdentifier(roleName)), }, - new(sdk.GrantOwnershipOptions), + opts, ) require.NoError(t, err) } @@ -951,6 +960,18 @@ func TestInt_GrantOwnership(t *testing.T) { ) require.NoError(t, err) + err = client.Grants.GrantPrivilegesToAccountRole( + ctx, + &sdk.AccountRoleGrantPrivileges{ + GlobalPrivileges: []sdk.GlobalPrivilege{sdk.GlobalPrivilegeExecuteTask}, + }, + &sdk.AccountRoleGrantOn{ + Account: sdk.Bool(true), + }, + role.ID(), + new(sdk.GrantPrivilegesToAccountRoleOptions), + ) + require.NoError(t, err) } makeAccountRoleOperableOnPipe := func(t *testing.T, grantingRole string, pipe *sdk.Pipe) { @@ -1126,7 +1147,7 @@ func TestInt_GrantOwnership(t *testing.T) { currentRole, err := client.ContextFunctions.CurrentRole(ctx) require.NoError(t, err) - grantOwnershipToRole(t, currentRole, ownershipGrantOnPipe(pipe)) + grantOwnershipToRole(t, currentRole, ownershipGrantOnPipe(pipe), nil) checkOwnershipOnObjectToRole(t, ownershipGrantOnPipe(pipe), currentRole) pipeExecutionState, err = client.SystemFunctions.PipeStatus(pipe.ID()) @@ -1392,7 +1413,7 @@ func TestInt_GrantOwnership(t *testing.T) { currentRole, err := client.ContextFunctions.CurrentRole(ctx) require.NoError(t, err) - grantOwnershipToRole(t, currentRole, onAllPipesInSchema) + grantOwnershipToRole(t, currentRole, onAllPipesInSchema, nil) checkOwnershipOnObjectToRole(t, ownershipGrantOnPipe(pipe), currentRole) checkOwnershipOnObjectToRole(t, ownershipGrantOnPipe(secondPipe), currentRole) @@ -1435,7 +1456,7 @@ func TestInt_GrantOwnership(t *testing.T) { require.Equal(t, sdk.TaskStateSuspended, task.State) }) - t.Run("on task - without ownership", func(t *testing.T) { + t.Run("on task - without ownership and operate", func(t *testing.T) { taskRole, taskRoleCleanup := createRoleGrantedToCurrentUser(t, client) t.Cleanup(taskRoleCleanup) @@ -1453,21 +1474,18 @@ func TestInt_GrantOwnership(t *testing.T) { taskId := sdk.NewSchemaObjectIdentifier(TestDatabaseName, TestSchemaName, random.AlphaN(20)) withWarehouseReq := sdk.NewCreateTaskWarehouseRequest().WithWarehouse(sdk.Pointer(testWarehouse(t).ID())) - task, taskCleanup := createTaskWithRequest(t, client, sdk.NewCreateTaskRequest(taskId, "SELECT CURRENT_TIMESTAMP").WithWarehouse(withWarehouseReq)) - t.Cleanup(taskCleanup) + task, taskCleanup := createTaskWithRequest(t, client, sdk.NewCreateTaskRequest(taskId, "SELECT CURRENT_TIMESTAMP").WithWarehouse(withWarehouseReq).WithSchedule(sdk.String("60 minutes"))) + t.Cleanup(func() { + usePreviousRole := useRole(t, client, taskRole.Name) + defer usePreviousRole() + taskCleanup() + }) err := client.Tasks.Alter(ctx, sdk.NewAlterTaskRequest(task.ID()).WithResume(sdk.Bool(true))) require.NoError(t, err) usePreviousRole() - t.Cleanup(func() { - currentRole, err := client.ContextFunctions.CurrentRole(ctx) - require.NoError(t, err) - - grantOwnershipToRole(t, currentRole, ownershipGrantOnTask(task)) - }) - task, err = client.Tasks.ShowByID(ctx, task.ID()) require.NoError(t, err) require.Equal(t, sdk.TaskStateStarted, task.State) @@ -1480,12 +1498,7 @@ func TestInt_GrantOwnership(t *testing.T) { }, new(sdk.GrantOwnershipOptions), ) - require.NoError(t, err) - checkOwnershipOnObjectToRole(t, ownershipGrantOnTask(task), role.ID().Name()) - - task, err = client.Tasks.ShowByID(ctx, task.ID()) - require.NoError(t, err) - require.Equal(t, sdk.TaskStateSuspended, task.State) + require.ErrorContains(t, err, "Unable to update graph with root task") // cannot suspend the root task without the ownership or operate privileges }) t.Run("on task - with operate and execute task", func(t *testing.T) { @@ -1552,7 +1565,7 @@ func TestInt_GrantOwnership(t *testing.T) { currentRole, err := client.ContextFunctions.CurrentRole(ctx) require.NoError(t, err) - grantOwnershipToRole(t, currentRole, ownershipGrantOnTask(task)) + grantOwnershipToRole(t, currentRole, ownershipGrantOnTask(task), sdk.Pointer(sdk.Revoke)) }) currentTask, err := client.Tasks.ShowByID(ctx, task.ID()) @@ -1579,23 +1592,37 @@ func TestInt_GrantOwnership(t *testing.T) { require.Equal(t, sdk.TaskStateStarted, currentTask.State) }) - t.Run("on all tasks - with ownership", func(t *testing.T) { + t.Run("on all tasks - with ownership", func(t *testing.T) { task, taskCleanup := createTask(t, client, testDb(t), testSchema(t)) t.Cleanup(taskCleanup) + err := client.Tasks.Alter(ctx, sdk.NewAlterTaskRequest(task.ID()).WithResume(sdk.Bool(true))) + require.NoError(t, err) + secondTask, secondTaskCleanup := createTask(t, client, testDb(t), testSchema(t)) t.Cleanup(secondTaskCleanup) + err = client.Tasks.Alter(ctx, sdk.NewAlterTaskRequest(secondTask.ID()).WithResume(sdk.Bool(true))) + require.NoError(t, err) + role, roleCleanup := createRole(t, client) t.Cleanup(roleCleanup) + currentTask, err := client.Tasks.ShowByID(ctx, task.ID()) + require.NoError(t, err) + require.Equal(t, sdk.TaskStateStarted, currentTask.State) + + currentSecondTask, err := client.Tasks.ShowByID(ctx, secondTask.ID()) + require.NoError(t, err) + require.Equal(t, sdk.TaskStateStarted, currentSecondTask.State) + onAllTasks := sdk.OwnershipGrantOn{ All: &sdk.GrantOnSchemaObjectIn{ PluralObjectType: sdk.PluralObjectTypeTasks, InSchema: sdk.Pointer(testSchema(t).ID()), }, } - err := client.Grants.GrantOwnership( + err = client.Grants.GrantOwnership( ctx, onAllTasks, sdk.OwnershipGrantTo{ @@ -1607,9 +1634,18 @@ func TestInt_GrantOwnership(t *testing.T) { checkOwnershipOnObjectToRole(t, ownershipGrantOnTask(task), role.ID().Name()) checkOwnershipOnObjectToRole(t, ownershipGrantOnTask(secondTask), role.ID().Name()) + + currentTask, err = client.Tasks.ShowByID(ctx, task.ID()) + require.NoError(t, err) + require.Equal(t, sdk.TaskStateSuspended, currentTask.State) + + currentSecondTask, err = client.Tasks.ShowByID(ctx, secondTask.ID()) + require.NoError(t, err) + require.Equal(t, sdk.TaskStateSuspended, currentSecondTask.State) + }) - t.Run("on all tasks - without ownership", func(t *testing.T) { + t.Run("on all tasks - with operate", func(t *testing.T) { taskRole, taskRoleCleanup := createRoleGrantedToCurrentUser(t, client) t.Cleanup(taskRoleCleanup) @@ -1622,46 +1658,130 @@ func TestInt_GrantOwnership(t *testing.T) { // grantTaskRole grants the necessary privileges to a role to be able to create task grantTaskRole(t, taskRole) + currentRole, err := client.ContextFunctions.CurrentRole(ctx) + require.NoError(t, err) + + err = client.Grants.GrantPrivilegesToAccountRole( + ctx, + &sdk.AccountRoleGrantPrivileges{ + GlobalPrivileges: []sdk.GlobalPrivilege{sdk.GlobalPrivilegeExecuteTask}, + }, + &sdk.AccountRoleGrantOn{ + Account: sdk.Bool(true), + }, + sdk.NewAccountObjectIdentifier(currentRole), + new(sdk.GrantPrivilegesToAccountRoleOptions), + ) + require.NoError(t, err) + // Use a previously prepared role to create a task usePreviousRole := useRole(t, client, taskRole.Name) taskId := sdk.NewSchemaObjectIdentifier(TestDatabaseName, TestSchemaName, random.AlphaN(20)) withWarehouseReq := sdk.NewCreateTaskWarehouseRequest().WithWarehouse(sdk.Pointer(testWarehouse(t).ID())) - task, taskCleanup := createTaskWithRequest(t, client, sdk.NewCreateTaskRequest(taskId, "SELECT CURRENT_TIMESTAMP").WithWarehouse(withWarehouseReq)) + task, taskCleanup := createTaskWithRequest(t, client, sdk.NewCreateTaskRequest(taskId, "SELECT CURRENT_TIMESTAMP").WithWarehouse(withWarehouseReq).WithSchedule(sdk.String("60 minutes"))) t.Cleanup(taskCleanup) secondTaskId := sdk.NewSchemaObjectIdentifier(TestDatabaseName, TestSchemaName, random.AlphaN(20)) - secondTask, secondTaskCleanup := createTaskWithRequest(t, client, sdk.NewCreateTaskRequest(secondTaskId, "SELECT CURRENT_TIMESTAMP").WithWarehouse(withWarehouseReq)) + secondTask, secondTaskCleanup := createTaskWithRequest(t, client, sdk.NewCreateTaskRequest(secondTaskId, "SELECT CURRENT_TIMESTAMP").WithWarehouse(withWarehouseReq).WithAfter([]sdk.SchemaObjectIdentifier{task.ID()})) t.Cleanup(secondTaskCleanup) + err = client.Grants.GrantPrivilegesToAccountRole( + ctx, + &sdk.AccountRoleGrantPrivileges{ + SchemaObjectPrivileges: []sdk.SchemaObjectPrivilege{sdk.SchemaObjectPrivilegeOperate}, + }, + &sdk.AccountRoleGrantOn{ + SchemaObject: &sdk.GrantOnSchemaObject{ + SchemaObject: &sdk.Object{ + ObjectType: sdk.ObjectTypeTask, + Name: task.ID(), + }, + }, + }, + sdk.NewAccountObjectIdentifier(currentRole), + new(sdk.GrantPrivilegesToAccountRoleOptions), + ) + require.NoError(t, err) + + err = client.Grants.GrantPrivilegesToAccountRole( + ctx, + &sdk.AccountRoleGrantPrivileges{ + SchemaObjectPrivileges: []sdk.SchemaObjectPrivilege{sdk.SchemaObjectPrivilegeOperate}, + }, + &sdk.AccountRoleGrantOn{ + SchemaObject: &sdk.GrantOnSchemaObject{ + SchemaObject: &sdk.Object{ + ObjectType: sdk.ObjectTypeTask, + Name: secondTask.ID(), + }, + }, + }, + sdk.NewAccountObjectIdentifier(currentRole), + new(sdk.GrantPrivilegesToAccountRoleOptions), + ) + require.NoError(t, err) + usePreviousRole() + err = client.Tasks.Alter(ctx, sdk.NewAlterTaskRequest(secondTask.ID()).WithResume(sdk.Bool(true))) + require.NoError(t, err) + + err = client.Tasks.Alter(ctx, sdk.NewAlterTaskRequest(task.ID()).WithResume(sdk.Bool(true))) + require.NoError(t, err) + t.Cleanup(func() { currentRole, err := client.ContextFunctions.CurrentRole(ctx) require.NoError(t, err) - grantOwnershipToRole(t, currentRole, ownershipGrantOnTask(task)) - grantOwnershipToRole(t, currentRole, ownershipGrantOnTask(secondTask)) + usePreviousRole := useRole(t, client, taskRole.Name) + grantOwnershipToRole(t, currentRole, ownershipGrantOnTask(task), nil) + grantOwnershipToRole(t, currentRole, ownershipGrantOnTask(secondTask), nil) + usePreviousRole() }) + usePreviousRole = useRole(t, client, taskRole.Name) + currentTask, err := client.Tasks.ShowByID(ctx, task.ID()) + require.NoError(t, err) + require.Equal(t, sdk.TaskStateStarted, currentTask.State) + + currentSecondTask, err := client.Tasks.ShowByID(ctx, secondTask.ID()) + require.NoError(t, err) + require.Equal(t, sdk.TaskStateStarted, currentSecondTask.State) + usePreviousRole() + onAllTasks := sdk.OwnershipGrantOn{ All: &sdk.GrantOnSchemaObjectIn{ PluralObjectType: sdk.PluralObjectTypeTasks, InSchema: sdk.Pointer(testSchema(t).ID()), }, } - err := client.Grants.GrantOwnership( + err = client.Grants.GrantOwnership( ctx, onAllTasks, sdk.OwnershipGrantTo{ AccountRoleName: sdk.Pointer(role.ID()), }, - new(sdk.GrantOwnershipOptions), + &sdk.GrantOwnershipOptions{ + CurrentGrants: &sdk.OwnershipCurrentGrants{ + OutboundPrivileges: sdk.Copy, + }, + }, ) require.NoError(t, err) checkOwnershipOnObjectToRole(t, ownershipGrantOnTask(task), role.ID().Name()) checkOwnershipOnObjectToRole(t, ownershipGrantOnTask(secondTask), role.ID().Name()) + + usePreviousRole = useRole(t, client, role.Name) + currentTask, err = client.Tasks.ShowByID(ctx, task.ID()) + require.NoError(t, err) + require.Equal(t, sdk.TaskStateStarted, currentTask.State) + + currentSecondTask, err = client.Tasks.ShowByID(ctx, secondTask.ID()) + require.NoError(t, err) + require.Equal(t, sdk.TaskStateStarted, currentSecondTask.State) + usePreviousRole() }) } diff --git a/pkg/sdk/testint/helpers_test.go b/pkg/sdk/testint/helpers_test.go index cde606e889..e8fd24d1fd 100644 --- a/pkg/sdk/testint/helpers_test.go +++ b/pkg/sdk/testint/helpers_test.go @@ -925,5 +925,6 @@ func createTaskWithRequest(t *testing.T, client *sdk.Client, request *sdk.Create func createTask(t *testing.T, client *sdk.Client, database *sdk.Database, schema *sdk.Schema) (*sdk.Task, func()) { t.Helper() id := sdk.NewSchemaObjectIdentifier(database.Name, schema.Name, random.AlphaN(20)) - return createTaskWithRequest(t, client, sdk.NewCreateTaskRequest(id, "SELECT CURRENT_TIMESTAMP")) + warehouseReq := sdk.NewCreateTaskWarehouseRequest().WithWarehouse(sdk.Pointer(testWarehouse(t).ID())) + return createTaskWithRequest(t, client, sdk.NewCreateTaskRequest(id, "SELECT CURRENT_TIMESTAMP").WithSchedule(sdk.String("60 minutes")).WithWarehouse(warehouseReq)) } From 3cf9e9c89ad668bc3d216468da192080de29ed7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Mon, 8 Apr 2024 12:59:22 +0200 Subject: [PATCH 6/7] lint fix --- pkg/sdk/grants_impl.go | 3 ++- pkg/sdk/testint/grants_integration_test.go | 1 - 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/sdk/grants_impl.go b/pkg/sdk/grants_impl.go index 9d663a83a7..99112dc9bf 100644 --- a/pkg/sdk/grants_impl.go +++ b/pkg/sdk/grants_impl.go @@ -4,10 +4,11 @@ import ( "context" "errors" "fmt" - "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/internal/collections" "log" "slices" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/internal/collections" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/logging" ) diff --git a/pkg/sdk/testint/grants_integration_test.go b/pkg/sdk/testint/grants_integration_test.go index a5a98be8c6..af40d16576 100644 --- a/pkg/sdk/testint/grants_integration_test.go +++ b/pkg/sdk/testint/grants_integration_test.go @@ -1642,7 +1642,6 @@ func TestInt_GrantOwnership(t *testing.T) { currentSecondTask, err = client.Tasks.ShowByID(ctx, secondTask.ID()) require.NoError(t, err) require.Equal(t, sdk.TaskStateSuspended, currentSecondTask.State) - }) t.Run("on all tasks - with operate", func(t *testing.T) { From 85f09f56cdc4f476f50652b03180e451999a62fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Cie=C5=9Blak?= Date: Mon, 8 Apr 2024 15:38:49 +0200 Subject: [PATCH 7/7] doc update --- docs/resources/grant_ownership.md | 248 ++++++++++++++++++ .../snowflake_grant_ownership/import.sh | 0 .../snowflake_grant_ownership/resource.tf | 0 pkg/sdk/grants_impl.go | 6 - .../resources}/grant_ownership.md.tmpl | 16 +- 5 files changed, 260 insertions(+), 10 deletions(-) rename {tmp => examples/resources}/snowflake_grant_ownership/import.sh (100%) rename {tmp => examples/resources}/snowflake_grant_ownership/resource.tf (100%) rename {tmp => templates/resources}/grant_ownership.md.tmpl (73%) diff --git a/docs/resources/grant_ownership.md b/docs/resources/grant_ownership.md index 7bd1b083c5..9c1e18ddd2 100644 --- a/docs/resources/grant_ownership.md +++ b/docs/resources/grant_ownership.md @@ -1,15 +1,204 @@ --- +# generated by https://github.com/hashicorp/terraform-plugin-docs page_title: "snowflake_grant_ownership Resource - terraform-provider-snowflake" subcategory: "" description: |- --- + +!> **Warning** We're in a process of implementing this resource, so it's not available yet. + +~> **Note** This is a preview resource. It's ready for general use. In case of any errors, please file an issue in our GitHub repository. +~> **Note** For more details about granting ownership, please visit [`GRANT OWNERSHIP` Snowflake documentation page](https://docs.snowflake.com/en/sql-reference/sql/grant-ownership). + + + + # snowflake_grant_ownership (Resource) +## Example Usage + +```terraform +################################## +### on object to account role +################################## + +resource "snowflake_role" "test" { + name = "test_role" +} + +resource "snowflake_database" "test" { + name = "test_database" +} + +resource "snowflake_schema" "test" { + name = "test_schema" + database = snowflake_database.test.name +} + +resource "snowflake_grant_ownership" "test" { + account_role_name = snowflake_role.test.name + outbound_privileges = "COPY" + on { + object_type = "SCHEMA" + object_name = "\"${snowflake_database.test.name}\".\"${snowflake_schema.test.name}\"" + } +} + +################################## +### on object to database role +################################## + +resource "snowflake_database" "test" { + name = "test_database" +} + +resource "snowflake_schema" "test" { + name = "test_schema" + database = snowflake_database.test.name +} + +resource "snowflake_database_role" "test" { + name = "test_database_role" + database = snowflake_database.test.name +} + +resource "snowflake_grant_ownership" "test" { + database_role_name = "\"${snowflake_database_role.test.database}\".\"${snowflake_database_role.test.name}\"" + outbound_privileges = "REVOKE" + on { + object_type = "SCHEMA" + object_name = "\"${snowflake_database.test.name}\".\"${snowflake_schema.test.name}\"" + } +} + +################################## +### on all tables in database to account role +################################## + +resource "snowflake_role" "test" { + name = "test_role" +} + +resource "snowflake_database" "test" { + name = "test_database" +} + +resource "snowflake_grant_ownership" "test" { + account_role_name = snowflake_role.test.name + on { + all { + plural_object_type = "TABLES" + in_database = snowflake_database.test.name + } + } +} + +################################## +### on all tables in schema to account role +################################## + +resource "snowflake_role" "test" { + name = "test_role" +} + +resource "snowflake_database" "test" { + name = "test_database" +} + +resource "snowflake_schema" "test" { + name = "test_schema" + database = snowflake_database.test.name +} + +resource "snowflake_grant_ownership" "test" { + account_role_name = snowflake_role.test.name + on { + all { + plural_object_type = "TABLES" + in_schema = "\"${snowflake_database.test.name}\".\"${snowflake_schema.test.name}\"" + } + } +} + +################################## +### on future tables in database to account role +################################## + +resource "snowflake_role" "test" { + name = "test_role" +} + +resource "snowflake_database" "test" { + name = "test_database" +} + +resource "snowflake_grant_ownership" "test" { + account_role_name = snowflake_role.test.name + on { + future { + plural_object_type = "TABLES" + in_database = snowflake_database.test.name + } + } +} + +################################## +### on future tables in schema to account role +################################## +resource "snowflake_role" "test" { + name = "test_role" +} + +resource "snowflake_database" "test" { + name = "test_database" +} + +resource "snowflake_schema" "test" { + name = "test_schema" + database = snowflake_database.test.name +} + +resource "snowflake_grant_ownership" "test" { + account_role_name = snowflake_role.test.name + on { + future { + plural_object_type = "TABLES" + in_schema = "\"${snowflake_database.test.name}\".\"${snowflake_schema.test.name}\"" + } + } +} +``` + +## Granting ownership on pipes +To transfer ownership of a pipe, there must be additional conditions met. Otherwise, additional manual work +will be needed afterward or in some cases, the ownership won't be transferred (resulting in error). + +To transfer ownership of a pipe(s) **fully automatically**, one of the following conditions has to be met: +- OPERATE and MONITOR privileges are granted to the current role on the pipe(s) and `outbound_privileges` field is set to `COPY`. +- The pipe(s) running status is paused (additional privileges and fields set are needed to pause and resume the pipe before and after ownership transfer. If it's already paused, nothing additional is needed and the pipe will remain paused after the ownership transfer). + +To transfer ownership of a pipe(s) **semi-automatically** you have to: +1. Pause the pipe(s) you want to transfer ownership of (using [ALTER PIPE](https://docs.snowflake.com/en/sql-reference/sql/alter-pipe#syntax); see PIPE_EXECUTION_PAUSED). +2. Create Terraform configuration with the `snowflake_grant_ownership` resource and perform ownership transfer with the `terraform apply`. +3. To resume the pipe(s) after ownership transfer use [PIPE_FORCE_RESUME system function](https://docs.snowflake.com/en/sql-reference/functions/system_pipe_force_resume). + +## Granting ownership on task +Granting ownership on single task requires: +- Either OWNERSHIP or OPERATE privilege to suspend the task (and its root) +- Role that will be granted ownership has to have USAGE granted on the warehouse assigned to the task, as well as EXECUTE TASK granted globally +- The outbound privileges set to `outbound_privileges = "COPY"` if you want to move grants automatically to the owner (also enables the provider to resume the task automatically) +If originally the first owner won't be granted with OPERATE, USAGE (on the warehouse), EXECUTE TASK (on the account), and outbound privileges won't be set to `COPY`, then you have to resume suspended tasks manually. + +## Granting ownership on all tasks in database/schema +Granting ownership on all tasks requires less privileges than granting ownership on one task, because it does a little bit less and requires additional work to be done after. +The only thing you have to take care of is to resume tasks after grant ownership transfer. If your tasks are managed by the Snowflake Terraform Plugin, this should +be as simple as running `terraform apply` second time (assuming the currently used role is privileged enough to be able to resume the tasks). +If your tasks are not managed by the Snowflake Terraform Plugin, you should resume them yourself manually. ## Schema @@ -62,3 +251,62 @@ Optional: - `in_database` (String) The fully qualified name of the database. - `in_schema` (String) The fully qualified name of the schema. + +## Import + +~> **Note** All the ..._name parts should be fully qualified names (where every part is quoted), e.g. for schema object it is `""."".""` + +Import is supported using the following syntax: + +`terraform import "||||"` + +where: +- role_type - string - type of granted role (either ToAccountRole or ToDatabaseRole) +- role_name - string - fully qualified identifier for either account role or database role (depending on the role_type) +- outbound_privileges_behavior - string - behavior specified for existing roles (can be either COPY or REVOKE) +- grant_type - enum +- grant_data - data dependent on grant_type + +It has varying number of parts, depending on grant_type. All the possible types are: + +### OnObject +`terraform import "|||OnObject||"` + +### OnAll (contains inner types: InDatabase | InSchema) + +#### InDatabase +`terraform import "|||OnAll||InDatabase|"` + +#### InSchema +`terraform import "|||OnAll||InSchema|"` + +### OnFuture (contains inner types: InDatabase | InSchema) + +#### InDatabase +`terraform import "|||OnFuture||InDatabase|"` + +#### InSchema +`terraform import "|||OnFuture||InSchema|"` + +### Import examples + +#### OnObject on Schema ToAccountRole +`terraform import "ToAccountRole|\"account_role\"|COPY|OnObject|SCHEMA|\"database_name\".\"schema_name\""` + +#### OnObject on Schema ToDatabaseRole +`terraform import "ToDatabaseRole|\"database_name\".\"database_role_name\"|COPY|OnObject|SCHEMA|\"database_name\".\"schema_name\""` + +#### OnObject on Table +`terraform import "ToAccountRole|\"account_role\"|COPY|OnObject|TABLE|\"database_name\".\"schema_name\".\"table_name\""` + +#### OnAll InDatabase +`terraform import "ToAccountRole|\"account_role\"|REVOKE|OnAll|TABLES|InDatabase|\"database_name\""` + +#### OnAll InSchema +`terraform import "ToAccountRole|\"account_role\"||OnAll|TABLES|InSchema|\"database_name\".\"schema_name\""` + +#### OnFuture InDatabase +`terraform import "ToAccountRole|\"account_role\"||OnFuture|TABLES|InDatabase|\"database_name\""` + +#### OnFuture InSchema +`terraform import "ToAccountRole|\"account_role\"|COPY|OnFuture|TABLES|InSchema|\"database_name\".\"schema_name\""` diff --git a/tmp/snowflake_grant_ownership/import.sh b/examples/resources/snowflake_grant_ownership/import.sh similarity index 100% rename from tmp/snowflake_grant_ownership/import.sh rename to examples/resources/snowflake_grant_ownership/import.sh diff --git a/tmp/snowflake_grant_ownership/resource.tf b/examples/resources/snowflake_grant_ownership/resource.tf similarity index 100% rename from tmp/snowflake_grant_ownership/resource.tf rename to examples/resources/snowflake_grant_ownership/resource.tf diff --git a/pkg/sdk/grants_impl.go b/pkg/sdk/grants_impl.go index 99112dc9bf..fac1c79839 100644 --- a/pkg/sdk/grants_impl.go +++ b/pkg/sdk/grants_impl.go @@ -441,16 +441,10 @@ func (v *grants) grantOwnershipOnTask(ctx context.Context, taskId SchemaObjectId log.Printf("[WARN] Insufficient privileges to operate on task: %s (OPERATE privilege). Trying to proceed with ownership transfer...", taskId.FullyQualifiedName()) } - tasksBefore, _ := v.client.Tasks.Show(ctx, NewShowTaskRequest().WithIn(&In{Schema: NewDatabaseObjectIdentifier(taskId.databaseName, taskId.schemaName)})) - _ = tasksBefore - if err := validateAndExec(v.client, ctx, opts); err != nil { return err } - tasksAfter, _ := v.client.Tasks.Show(ctx, NewShowTaskRequest().WithIn(&In{Schema: NewDatabaseObjectIdentifier(taskId.databaseName, taskId.schemaName)})) - _ = tasksAfter - if currentTask.State == TaskStateStarted && !slices.ContainsFunc(tasksToResume, func(id SchemaObjectIdentifier) bool { return id.FullyQualifiedName() == currentTask.ID().FullyQualifiedName() }) { diff --git a/tmp/grant_ownership.md.tmpl b/templates/resources/grant_ownership.md.tmpl similarity index 73% rename from tmp/grant_ownership.md.tmpl rename to templates/resources/grant_ownership.md.tmpl index 5f5bf2f8eb..1bbc3268be 100644 --- a/tmp/grant_ownership.md.tmpl +++ b/templates/resources/grant_ownership.md.tmpl @@ -42,10 +42,18 @@ To transfer ownership of a pipe(s) **semi-automatically** you have to: 2. Create Terraform configuration with the `snowflake_grant_ownership` resource and perform ownership transfer with the `terraform apply`. 3. To resume the pipe(s) after ownership transfer use [PIPE_FORCE_RESUME system function](https://docs.snowflake.com/en/sql-reference/functions/system_pipe_force_resume). -## Granting ownership on tasks - -## Granting ownership on tasks -{{/* TODO: In next pr */}} +## Granting ownership on task +Granting ownership on single task requires: +- Either OWNERSHIP or OPERATE privilege to suspend the task (and its root) +- Role that will be granted ownership has to have USAGE granted on the warehouse assigned to the task, as well as EXECUTE TASK granted globally +- The outbound privileges set to `outbound_privileges = "COPY"` if you want to move grants automatically to the owner (also enables the provider to resume the task automatically) +If originally the first owner won't be granted with OPERATE, USAGE (on the warehouse), EXECUTE TASK (on the account), and outbound privileges won't be set to `COPY`, then you have to resume suspended tasks manually. + +## Granting ownership on all tasks in database/schema +Granting ownership on all tasks requires less privileges than granting ownership on one task, because it does a little bit less and requires additional work to be done after. +The only thing you have to take care of is to resume tasks after grant ownership transfer. If your tasks are managed by the Snowflake Terraform Plugin, this should +be as simple as running `terraform apply` second time (assuming the currently used role is privileged enough to be able to resume the tasks). +If your tasks are not managed by the Snowflake Terraform Plugin, you should resume them yourself manually. {{ .SchemaMarkdown | trimspace }}