diff --git a/docs/resources/grant_application_role.md b/docs/resources/grant_application_role.md new file mode 100644 index 0000000000..cf23749b39 --- /dev/null +++ b/docs/resources/grant_application_role.md @@ -0,0 +1,66 @@ +--- +page_title: "snowflake_grant_application_role Resource - terraform-provider-snowflake" +subcategory: "" +description: |- + +--- + +# snowflake_grant_application_role (Resource) + + + +## Example Usage + +```terraform +locals { + application_role_identifier = "\"my_appplication\".\"app_role_1\"" +} + +################################## +### grant application role to account role +################################## + + +resource "snowflake_role" "role" { + name = "my_role" +} + +resource "snowflake_grant_application_role" "g" { + application_role_name = local.application_role_identifier + parent_account_role_name = snowflake_role.role.name +} + +################################## +### grant application role to application +################################## + +resource "snowflake_grant_application_role" "g" { + application_role_name = local.application_role_identifier + application_name = "my_second_application" +} +``` + + +## Schema + +### Required + +- `application_role_name` (String) Specifies the identifier for the application role to grant. + +### Optional + +- `application_name` (String) The fully qualified name of the application on which application role will be granted. +- `parent_account_role_name` (String) The fully qualified name of the account role on which application role will be granted. + +### Read-Only + +- `id` (String) The ID of this resource. + +## Import + +Import is supported using the following syntax: + +```shell +# format is application_role_name (string) | object_type (ACCOUNT_ROLE|APPLICATION) | grantee_name (string) +terraform import "\"my_application\".\"app_role_1\"|ACCOUNT_ROLE|\"my_role\"" +``` diff --git a/examples/resources/snowflake_grant_application_role/import.sh b/examples/resources/snowflake_grant_application_role/import.sh new file mode 100644 index 0000000000..ad0caa05b1 --- /dev/null +++ b/examples/resources/snowflake_grant_application_role/import.sh @@ -0,0 +1,2 @@ +# format is application_role_name (string) | object_type (ACCOUNT_ROLE|APPLICATION) | grantee_name (string) +terraform import "\"my_application\".\"app_role_1\"|ACCOUNT_ROLE|\"my_role\"" diff --git a/examples/resources/snowflake_grant_application_role/resource.tf b/examples/resources/snowflake_grant_application_role/resource.tf new file mode 100644 index 0000000000..354352e105 --- /dev/null +++ b/examples/resources/snowflake_grant_application_role/resource.tf @@ -0,0 +1,26 @@ +locals { + application_role_identifier = "\"my_appplication\".\"app_role_1\"" +} + +################################## +### grant application role to account role +################################## + + +resource "snowflake_role" "role" { + name = "my_role" +} + +resource "snowflake_grant_application_role" "g" { + application_role_name = local.application_role_identifier + parent_account_role_name = snowflake_role.role.name +} + +################################## +### grant application role to application +################################## + +resource "snowflake_grant_application_role" "g" { + application_role_name = local.application_role_identifier + application_name = "my_second_application" +} diff --git a/pkg/acceptance/check_destroy.go b/pkg/acceptance/check_destroy.go index eee566d005..989e26683d 100644 --- a/pkg/acceptance/check_destroy.go +++ b/pkg/acceptance/check_destroy.go @@ -9,6 +9,7 @@ import ( "testing" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/helpers" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/provider/resources" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" "github.com/hashicorp/terraform-plugin-testing/terraform" @@ -363,3 +364,34 @@ func CheckUserPasswordPolicyAttachmentDestroy(t *testing.T) func(*terraform.Stat return nil } } + +func TestAccCheckGrantApplicationRoleDestroy(s *terraform.State) error { + client := TestAccProvider.Meta().(*provider.Context).Client + for _, rs := range s.RootModule().Resources { + if rs.Type != "snowflake_grant_application_role" { + continue + } + ctx := context.Background() + id := rs.Primary.ID + ids := strings.Split(id, "|") + applicationRoleName := ids[0] + objectType := ids[1] + parentRoleName := ids[2] + grants, err := client.Grants.Show(ctx, &sdk.ShowGrantOptions{ + Of: &sdk.ShowGrantsOf{ + ApplicationRole: sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(applicationRoleName), + }, + }) + if err != nil { + continue + } + for _, grant := range grants { + if grant.GrantedTo == sdk.ObjectType(objectType) { + if grant.GranteeName.FullyQualifiedName() == parentRoleName { + return fmt.Errorf("application role grant %v still exists", grant) + } + } + } + } + return nil +} diff --git a/pkg/acceptance/helpers/application_client.go b/pkg/acceptance/helpers/application_client.go index 6c2a84c2b8..2d5334c43b 100644 --- a/pkg/acceptance/helpers/application_client.go +++ b/pkg/acceptance/helpers/application_client.go @@ -27,7 +27,6 @@ func (c *ApplicationClient) client() sdk.Applications { func (c *ApplicationClient) CreateApplication(t *testing.T, packageId sdk.AccountObjectIdentifier, version string) (*sdk.Application, func()) { t.Helper() ctx := context.Background() - id := c.ids.RandomAccountObjectIdentifier() err := c.client().Create(ctx, sdk.NewCreateApplicationRequest(id, packageId).WithVersion(sdk.NewApplicationVersionRequest().WithVersionAndPatch(sdk.NewVersionAndPatchRequest(version, nil)))) require.NoError(t, err) diff --git a/pkg/provider/provider.go b/pkg/provider/provider.go index 4cdb8878c2..bb24fb52fd 100644 --- a/pkg/provider/provider.go +++ b/pkg/provider/provider.go @@ -467,6 +467,7 @@ func getResources() map[string]*schema.Resource { "snowflake_file_format": resources.FileFormat(), "snowflake_function": resources.Function(), "snowflake_grant_account_role": resources.GrantAccountRole(), + "snowflake_grant_application_role": resources.GrantApplicationRole(), "snowflake_grant_database_role": resources.GrantDatabaseRole(), "snowflake_grant_ownership": resources.GrantOwnership(), "snowflake_grant_privileges_to_role": resources.GrantPrivilegesToRole(), diff --git a/pkg/resources/grant_application_role.go b/pkg/resources/grant_application_role.go new file mode 100644 index 0000000000..98f0668de1 --- /dev/null +++ b/pkg/resources/grant_application_role.go @@ -0,0 +1,250 @@ +package resources + +import ( + "context" + "errors" + "fmt" + "strings" + + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/internal/provider" + + "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/diag" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" +) + +var grantApplicationRoleSchema = map[string]*schema.Schema{ + "application_role_name": { + Type: schema.TypeString, + Required: true, + Description: "Specifies the identifier for the application role to grant.", + ForceNew: true, + ValidateDiagFunc: IsValidIdentifier[sdk.DatabaseObjectIdentifier](), + }, + "parent_account_role_name": { + Type: schema.TypeString, + Optional: true, + Description: "The fully qualified name of the account role on which application role will be granted.", + ForceNew: true, + ValidateDiagFunc: IsValidIdentifier[sdk.AccountObjectIdentifier](), + ExactlyOneOf: []string{ + "parent_account_role_name", + "application_name", + }, + }, + "application_name": { + Type: schema.TypeString, + Optional: true, + Description: "The fully qualified name of the application on which application role will be granted.", + ForceNew: true, + ValidateDiagFunc: IsValidIdentifier[sdk.AccountObjectIdentifier](), + ExactlyOneOf: []string{ + "parent_account_role_name", + "application_name", + }, + }, +} + +func GrantApplicationRole() *schema.Resource { + return &schema.Resource{ + CreateContext: CreateContextGrantApplicationRole, + ReadContext: ReadContextGrantApplicationRole, + DeleteContext: DeleteContextGrantApplicationRole, + Schema: grantApplicationRoleSchema, + Importer: &schema.ResourceImporter{ + StateContext: func(ctx context.Context, d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { + parts := strings.Split(d.Id(), helpers.IDDelimiter) + if len(parts) != 3 { + return nil, fmt.Errorf("invalid ID specified: %v, expected ||", d.Id()) + } + if err := d.Set("application_role_name", parts[0]); err != nil { + return nil, err + } + switch parts[1] { + case "ACCOUNT_ROLE": + if err := d.Set("parent_account_role_name", sdk.NewAccountObjectIdentifier(parts[2]).FullyQualifiedName()); err != nil { + return nil, err + } + case "APPLICATION": + if err := d.Set("application_name", sdk.NewAccountObjectIdentifier(parts[2]).FullyQualifiedName()); err != nil { + return nil, err + } + default: + return nil, fmt.Errorf("invalid object type specified: %v, expected ACCOUNT_ROLE, APPLICATION", parts[1]) + } + + return []*schema.ResourceData{d}, nil + }, + }, + } +} + +func CreateContextGrantApplicationRole(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*provider.Context).Client + name := d.Get("application_role_name").(string) + applicationRoleIdentifier := sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(name) + // format of snowflakeResourceID is || + var snowflakeResourceID string + if parentRoleName, ok := d.GetOk("parent_account_role_name"); ok && parentRoleName.(string) != "" { + parentRoleIdentifier := sdk.NewAccountObjectIdentifierFromFullyQualifiedName(parentRoleName.(string)) + snowflakeResourceID = strings.Join([]string{applicationRoleIdentifier.FullyQualifiedName(), "ACCOUNT_ROLE", parentRoleIdentifier.FullyQualifiedName()}, helpers.IDDelimiter) + req := sdk.NewGrantApplicationRoleRequest(applicationRoleIdentifier).WithTo(*sdk.NewKindOfRoleRequest().WithRoleName(&parentRoleIdentifier)) + if err := client.ApplicationRoles.Grant(ctx, req); err != nil { + return diag.FromErr(err) + } + } else if applicationName, ok := d.GetOk("application_name"); ok && applicationName.(string) != "" { + applicationIdentifier := sdk.NewAccountObjectIdentifierFromFullyQualifiedName(applicationName.(string)) + snowflakeResourceID = strings.Join([]string{applicationRoleIdentifier.FullyQualifiedName(), sdk.ObjectTypeApplication.String(), applicationIdentifier.FullyQualifiedName()}, helpers.IDDelimiter) + req := sdk.NewGrantApplicationRoleRequest(applicationRoleIdentifier).WithTo(*sdk.NewKindOfRoleRequest().WithApplicationName(&applicationIdentifier)) + if err := client.ApplicationRoles.Grant(ctx, req); err != nil { + return diag.FromErr(err) + } + } + d.SetId(snowflakeResourceID) + return ReadContextGrantApplicationRole(ctx, d, meta) +} + +func ReadContextGrantApplicationRole(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*provider.Context).Client + parts := strings.Split(d.Id(), helpers.IDDelimiter) + applicationRoleName := parts[0] + applicationRoleIdentifier := sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(applicationRoleName) + objectTypeString := parts[1] + if objectTypeString == "ACCOUNT_ROLE" { + objectTypeString = "ROLE" + } + + // first check if either the target account role or application exists + targetIdentifier := parts[2] + objectType := sdk.ObjectType(objectTypeString) + switch objectType { + case sdk.ObjectTypeRole: + { + if _, err := client.Roles.ShowByID(ctx, sdk.NewAccountObjectIdentifierFromFullyQualifiedName(targetIdentifier)); err != nil && errors.Is(err, sdk.ErrObjectNotFound) { + d.SetId("") + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Warning, + Summary: "Failed to retrieve account role. Marking the resource as removed.", + Detail: fmt.Sprintf("Id: %s", d.Id()), + }, + } + } + } + case sdk.ObjectTypeApplication: + if _, err := client.Applications.ShowByID(ctx, sdk.NewAccountObjectIdentifierFromFullyQualifiedName(targetIdentifier)); err != nil && errors.Is(err, sdk.ErrObjectNotFound) { + d.SetId("") + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Warning, + Summary: "Failed to retrieve application. Marking the resource as removed.", + Detail: fmt.Sprintf("Id: %s", d.Id()), + }, + } + } + } + // then check if application role exists + grants, err := client.Grants.Show(ctx, &sdk.ShowGrantOptions{ + Of: &sdk.ShowGrantsOf{ + ApplicationRole: applicationRoleIdentifier, + }, + }) + if err != nil { + if errors.Is(err, sdk.ErrObjectNotExistOrAuthorized) { + d.SetId("") + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Warning, + Summary: "Failed to retrieve application role. Marking the resource as removed.", + Detail: fmt.Sprintf("Id: %s", d.Id()), + }, + } + } else { + return diag.FromErr(err) + } + } + + // finally check if the grant of the application role to the target (account role / application) exists + var found bool + for _, grant := range grants { + if grant.GrantedTo == objectType { + if grant.GrantedTo == sdk.ObjectTypeRole || grant.GrantedTo == sdk.ObjectTypeApplication { + if grant.GranteeName.FullyQualifiedName() == targetIdentifier { + found = true + break + } + } else { + /* + note that grantee_name is not saved as a valid identifier in the + SHOW GRANTS OF APPLICATION ROLE command + for example, "ABC"."test_parent_role" is saved as ABC."test_parent_role" + or "ABC"."test_parent_role" is saved as ABC.test_parent_role + and our internal mapper thereby fails to parse it correctly, returning "ABC."test_parent_role" + so this funny string replacement is needed to make it work + */ + s := grant.GranteeName.FullyQualifiedName() + if !strings.Contains(s, "\"") { + parts := strings.Split(s, ".") + s = sdk.NewDatabaseObjectIdentifier(parts[0], parts[1]).FullyQualifiedName() + } else { + parts := strings.Split(s, "\".\"") + if len(parts) < 2 { + parts = strings.Split(s, "\".") + if len(parts) < 2 { + parts = strings.Split(s, ".\"") + if len(parts) < 2 { + s = strings.Trim(s, "\"") + parts = strings.Split(s, ".") + } + } + } + s = sdk.NewDatabaseObjectIdentifier(parts[0], parts[1]).FullyQualifiedName() + } + if s == targetIdentifier { + found = true + break + } + } + } + } + if !found { + d.SetId("") + return diag.Diagnostics{ + diag.Diagnostic{ + Severity: diag.Warning, + Summary: "Application role grant not found. Marking the resource as removed.", + Detail: fmt.Sprintf("Id: %s", d.Id()), + }, + } + } + + return nil +} + +func DeleteContextGrantApplicationRole(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + client := meta.(*provider.Context).Client + + parts := strings.Split(d.Id(), "|") + id := sdk.NewDatabaseObjectIdentifierFromFullyQualifiedName(parts[0]) + objectType := parts[1] + granteeName := parts[2] + switch objectType { + case "ACCOUNT_ROLE": + applicationRoleName := sdk.NewAccountObjectIdentifierFromFullyQualifiedName(granteeName) + if err := client.ApplicationRoles.Revoke(ctx, sdk.NewRevokeApplicationRoleRequest(id).WithFrom(*sdk.NewKindOfRoleRequest().WithRoleName(&applicationRoleName))); err != nil { + if errors.Is(err, sdk.ErrObjectNotExistOrAuthorized) { + return diag.FromErr(err) + } + } + case "APPLICATION": + applicationName := sdk.NewAccountObjectIdentifierFromFullyQualifiedName(granteeName) + if err := client.ApplicationRoles.Revoke(ctx, sdk.NewRevokeApplicationRoleRequest(id).WithFrom(*sdk.NewKindOfRoleRequest().WithApplicationName(&applicationName))); err != nil { + if errors.Is(err, sdk.ErrObjectNotExistOrAuthorized) { + return diag.FromErr(err) + } + } + } + d.SetId("") + return nil +} diff --git a/pkg/resources/grant_application_role_acceptance_test.go b/pkg/resources/grant_application_role_acceptance_test.go new file mode 100644 index 0000000000..bd0ba31674 --- /dev/null +++ b/pkg/resources/grant_application_role_acceptance_test.go @@ -0,0 +1,121 @@ +package resources_test + +import ( + "fmt" + "testing" + + acc "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/acceptance" + "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" + + "github.com/hashicorp/terraform-plugin-testing/config" + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/tfversion" +) + +// TODO [SNOW-1431726]: Move to helpers +func createApp(t *testing.T) *sdk.Application { + t.Helper() + + stage, cleanupStage := acc.TestClient().Stage.CreateStage(t) + t.Cleanup(cleanupStage) + + acc.TestClient().Stage.PutOnStage(t, stage.ID(), "TestAcc_GrantApplicationRole/manifest.yml") + acc.TestClient().Stage.PutOnStage(t, stage.ID(), "TestAcc_GrantApplicationRole/setup.sql") + + applicationPackage, cleanupApplicationPackage := acc.TestClient().ApplicationPackage.CreateApplicationPackage(t) + t.Cleanup(cleanupApplicationPackage) + + acc.TestClient().ApplicationPackage.AddApplicationPackageVersion(t, applicationPackage.ID(), stage.ID(), "v1") + + application, cleanupApplication := acc.TestClient().Application.CreateApplication(t, applicationPackage.ID(), "v1") + t.Cleanup(cleanupApplication) + return application +} + +func TestAcc_GrantApplicationRole_accountRole(t *testing.T) { + parentRole, cleanupParentRole := acc.TestClient().Role.CreateRole(t) + t.Cleanup(cleanupParentRole) + resourceName := "snowflake_grant_application_role.g" + applicationRoleName := "app_role_1" + + acc.TestAccPreCheck(t) + app := createApp(t) + applicationRoleNameFullyQualified := fmt.Sprintf("\"%s\".\"%s\"", app.Name, applicationRoleName) + m := func() map[string]config.Variable { + return map[string]config.Variable{ + "parent_account_role_name": config.StringVariable(parentRole.Name), + "application_name": config.StringVariable(app.Name), + } + } + resource.Test(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + CheckDestroy: acc.TestAccCheckGrantApplicationRoleDestroy, + Steps: []resource.TestStep{ + { + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + ConfigDirectory: config.StaticDirectory("testdata/TestAcc_GrantApplicationRole/account_role"), + ConfigVariables: m(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "application_role_name", applicationRoleNameFullyQualified), + resource.TestCheckResourceAttr(resourceName, "parent_account_role_name", fmt.Sprintf("\"%s\"", parentRole.Name)), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf(`"%v"."%v"|ACCOUNT_ROLE|"%v"`, app.Name, applicationRoleName, parentRole.Name)), + ), + }, + // test import + { + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + ConfigDirectory: config.StaticDirectory("testdata/TestAcc_GrantApplicationRole/account_role"), + ConfigVariables: m(), + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAcc_GrantApplicationRole_application(t *testing.T) { + resourceName := "snowflake_grant_application_role.g" + applicationRoleName := "app_role_1" + + acc.TestAccPreCheck(t) + app := createApp(t) + app2 := createApp(t) + + m := func() map[string]config.Variable { + return map[string]config.Variable{ + "application_name": config.StringVariable(app.Name), + "application_name2": config.StringVariable(app2.Name), + } + } + applicationRoleNameFullyQualified := fmt.Sprintf("\"%s\".\"%s\"", app.Name, applicationRoleName) + resource.Test(t, resource.TestCase{ + TerraformVersionChecks: []tfversion.TerraformVersionCheck{ + tfversion.RequireAbove(tfversion.Version1_5_0), + }, + CheckDestroy: acc.TestAccCheckGrantApplicationRoleDestroy, + Steps: []resource.TestStep{ + { + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + ConfigDirectory: config.StaticDirectory("testdata/TestAcc_GrantApplicationRole/application"), + ConfigVariables: m(), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr(resourceName, "application_role_name", applicationRoleNameFullyQualified), + resource.TestCheckResourceAttr(resourceName, "application_name", fmt.Sprintf("\"%s\"", app2.Name)), + resource.TestCheckResourceAttr(resourceName, "id", fmt.Sprintf(`"%v"."%v"|APPLICATION|"%v"`, app.Name, applicationRoleName, app2.Name)), + ), + }, + // test import + { + ProtoV6ProviderFactories: acc.TestAccProtoV6ProviderFactories, + ConfigDirectory: config.StaticDirectory("testdata/TestAcc_GrantApplicationRole/application"), + ConfigVariables: m(), + ResourceName: resourceName, + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} diff --git a/pkg/resources/testdata/TestAcc_GrantApplicationRole/account_role/test.tf b/pkg/resources/testdata/TestAcc_GrantApplicationRole/account_role/test.tf new file mode 100644 index 0000000000..9c30ace9ec --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantApplicationRole/account_role/test.tf @@ -0,0 +1,8 @@ +locals { + application_role_identifier = "\"${var.application_name}\".\"app_role_1\"" +} + +resource "snowflake_grant_application_role" "g" { + application_role_name = local.application_role_identifier + parent_account_role_name = "\"${var.parent_account_role_name}\"" +} diff --git a/pkg/resources/testdata/TestAcc_GrantApplicationRole/account_role/variables.tf b/pkg/resources/testdata/TestAcc_GrantApplicationRole/account_role/variables.tf new file mode 100644 index 0000000000..4bf89421b8 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantApplicationRole/account_role/variables.tf @@ -0,0 +1,7 @@ +variable "parent_account_role_name" { + type = string +} + +variable "application_name" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_GrantApplicationRole/application/test.tf b/pkg/resources/testdata/TestAcc_GrantApplicationRole/application/test.tf new file mode 100644 index 0000000000..2013b03833 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantApplicationRole/application/test.tf @@ -0,0 +1,8 @@ +locals { + application_role_identifier = "\"${var.application_name}\".\"app_role_1\"" +} + +resource "snowflake_grant_application_role" "g" { + application_role_name = local.application_role_identifier + application_name = "\"${var.application_name2}\"" +} diff --git a/pkg/resources/testdata/TestAcc_GrantApplicationRole/application/variables.tf b/pkg/resources/testdata/TestAcc_GrantApplicationRole/application/variables.tf new file mode 100644 index 0000000000..386d3898ee --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantApplicationRole/application/variables.tf @@ -0,0 +1,7 @@ +variable "application_name" { + type = string +} + +variable "application_name2" { + type = string +} diff --git a/pkg/resources/testdata/TestAcc_GrantApplicationRole/manifest.yml b/pkg/resources/testdata/TestAcc_GrantApplicationRole/manifest.yml new file mode 100644 index 0000000000..83fb797263 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantApplicationRole/manifest.yml @@ -0,0 +1,5 @@ +manifest_version: 1 # required +version: + name: application_roles_test_app + label: "v1.0" + comment: "This application is used by Snowflake Terraform Provider for application role integration tests" diff --git a/pkg/resources/testdata/TestAcc_GrantApplicationRole/setup.sql b/pkg/resources/testdata/TestAcc_GrantApplicationRole/setup.sql new file mode 100644 index 0000000000..1c951878b2 --- /dev/null +++ b/pkg/resources/testdata/TestAcc_GrantApplicationRole/setup.sql @@ -0,0 +1,2 @@ +create application role "app_role_1" comment = 'some comment'; +create application role "app_role_2" comment = 'some comment2'; diff --git a/pkg/sdk/application_roles_def.go b/pkg/sdk/application_roles_def.go index 326ef3ab42..b26ea5da7d 100644 --- a/pkg/sdk/application_roles_def.go +++ b/pkg/sdk/application_roles_def.go @@ -4,29 +4,60 @@ import g "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk/poc/gen //go:generate go run ./poc/main.go +var applicationRoleKindOfRole = g.NewQueryStruct("KindOfRole"). + OptionalIdentifier("RoleName", g.KindOfT[AccountObjectIdentifier](), g.IdentifierOptions().SQL("ROLE")). + OptionalIdentifier("ApplicationRoleName", g.KindOfT[DatabaseObjectIdentifier](), g.IdentifierOptions().SQL("APPLICATION ROLE")). + OptionalIdentifier("ApplicationName", g.KindOfT[AccountObjectIdentifier](), g.IdentifierOptions().SQL("APPLICATION")). + WithValidation(g.ExactlyOneValueSet, "RoleName", "ApplicationRoleName", "ApplicationName") + var ApplicationRolesDef = g.NewInterface( "ApplicationRoles", "ApplicationRole", g.KindOfT[DatabaseObjectIdentifier](), -). - ShowOperation( - "https://docs.snowflake.com/en/sql-reference/sql/show-application-roles", - g.DbStruct("applicationRoleDbRow"). - Field("created_on", "time.Time"). - Field("name", "string"). - Field("owner", "string"). - Field("comment", "string"). - Field("owner_role_type", "string"), - g.PlainStruct("ApplicationRole"). - Field("CreatedOn", "time.Time"). - Field("Name", "string"). - Field("Owner", "string"). - Field("Comment", "string"). - Field("OwnerRoleType", "string"), - g.NewQueryStruct("ShowApplicationRoles"). - Show(). - SQL("APPLICATION ROLES IN APPLICATION"). - Identifier("ApplicationName", g.KindOfT[AccountObjectIdentifier](), g.IdentifierOptions()). - OptionalLimitFrom(). - WithValidation(g.ValidIdentifier, "ApplicationName"), - ) +).CustomOperation( + "Grant", + "https://docs.snowflake.com/en/sql-reference/sql/grant-application-role", + g.NewQueryStruct("GrantApplicationRole"). + Grant(). + SQL("APPLICATION ROLE"). + Name(). + QueryStructField( + "To", + applicationRoleKindOfRole, + g.KeywordOptions().SQL("TO"), + ). + WithValidation(g.ValidIdentifier, "name"), +).CustomOperation( + "Revoke", + "https://docs.snowflake.com/en/sql-reference/sql/revoke-application-role", + g.NewQueryStruct("RevokeApplicationRole"). + Revoke(). + SQL("APPLICATION ROLE"). + Name(). + QueryStructField( + "From", + applicationRoleKindOfRole, + g.KeywordOptions().SQL("FROM"), + ). + WithValidation(g.ValidIdentifier, "name"), +).ShowOperation( + "https://docs.snowflake.com/en/sql-reference/sql/show-application-roles", + g.DbStruct("applicationRoleDbRow"). + Field("created_on", "time.Time"). + Field("name", "string"). + Field("owner", "string"). + Field("comment", "string"). + Field("owner_role_type", "string"), + g.PlainStruct("ApplicationRole"). + Field("CreatedOn", "time.Time"). + Field("Name", "string"). + Field("Owner", "string"). + Field("Comment", "string"). + Field("OwnerRoleType", "string"), + g.NewQueryStruct("ShowApplicationRoles"). + Show(). + SQL("APPLICATION ROLES IN APPLICATION"). + Identifier("ApplicationName", g.KindOfT[AccountObjectIdentifier](), g.IdentifierOptions()). + OptionalLimitFrom(). + WithValidation(g.ValidIdentifier, "ApplicationName"), +).ShowByIdOperation() diff --git a/pkg/sdk/application_roles_dto_builders_gen.go b/pkg/sdk/application_roles_dto_builders_gen.go index 0b3b1411f3..5864cf1723 100644 --- a/pkg/sdk/application_roles_dto_builders_gen.go +++ b/pkg/sdk/application_roles_dto_builders_gen.go @@ -4,6 +4,51 @@ package sdk import () +func NewGrantApplicationRoleRequest( + name DatabaseObjectIdentifier, +) *GrantApplicationRoleRequest { + s := GrantApplicationRoleRequest{} + s.name = name + return &s +} + +func (s *GrantApplicationRoleRequest) WithTo(To KindOfRoleRequest) *GrantApplicationRoleRequest { + s.To = To + return s +} + +func NewKindOfRoleRequest() *KindOfRoleRequest { + return &KindOfRoleRequest{} +} + +func (s *KindOfRoleRequest) WithRoleName(RoleName *AccountObjectIdentifier) *KindOfRoleRequest { + s.RoleName = RoleName + return s +} + +func (s *KindOfRoleRequest) WithApplicationRoleName(ApplicationRoleName *DatabaseObjectIdentifier) *KindOfRoleRequest { + s.ApplicationRoleName = ApplicationRoleName + return s +} + +func (s *KindOfRoleRequest) WithApplicationName(ApplicationName *AccountObjectIdentifier) *KindOfRoleRequest { + s.ApplicationName = ApplicationName + return s +} + +func NewRevokeApplicationRoleRequest( + name DatabaseObjectIdentifier, +) *RevokeApplicationRoleRequest { + s := RevokeApplicationRoleRequest{} + s.name = name + return &s +} + +func (s *RevokeApplicationRoleRequest) WithFrom(From KindOfRoleRequest) *RevokeApplicationRoleRequest { + s.From = From + return s +} + func NewShowApplicationRoleRequest() *ShowApplicationRoleRequest { return &ShowApplicationRoleRequest{} } @@ -17,13 +62,3 @@ func (s *ShowApplicationRoleRequest) WithLimit(Limit *LimitFrom) *ShowApplicatio s.Limit = Limit return s } - -func NewShowByIDApplicationRoleRequest( - name DatabaseObjectIdentifier, - ApplicationName AccountObjectIdentifier, -) *ShowByIDApplicationRoleRequest { - s := ShowByIDApplicationRoleRequest{} - s.name = name - s.ApplicationName = ApplicationName - return &s -} diff --git a/pkg/sdk/application_roles_dto_gen.go b/pkg/sdk/application_roles_dto_gen.go index c5345d9a68..62183f8678 100644 --- a/pkg/sdk/application_roles_dto_gen.go +++ b/pkg/sdk/application_roles_dto_gen.go @@ -2,14 +2,29 @@ package sdk //go:generate go run ./dto-builder-generator/main.go -var _ optionsProvider[ShowApplicationRoleOptions] = new(ShowApplicationRoleRequest) +var ( + _ optionsProvider[GrantApplicationRoleOptions] = new(GrantApplicationRoleRequest) + _ optionsProvider[RevokeApplicationRoleOptions] = new(RevokeApplicationRoleRequest) + _ optionsProvider[ShowApplicationRoleOptions] = new(ShowApplicationRoleRequest) +) + +type GrantApplicationRoleRequest struct { + name DatabaseObjectIdentifier // required + To KindOfRoleRequest +} + +type KindOfRoleRequest struct { + RoleName *AccountObjectIdentifier + ApplicationRoleName *DatabaseObjectIdentifier + ApplicationName *AccountObjectIdentifier +} + +type RevokeApplicationRoleRequest struct { + name DatabaseObjectIdentifier // required + From KindOfRoleRequest +} type ShowApplicationRoleRequest struct { ApplicationName AccountObjectIdentifier Limit *LimitFrom } - -type ShowByIDApplicationRoleRequest struct { - name DatabaseObjectIdentifier // required - ApplicationName AccountObjectIdentifier // required -} diff --git a/pkg/sdk/application_roles_gen.go b/pkg/sdk/application_roles_gen.go index 148498054e..a05ad5a65b 100644 --- a/pkg/sdk/application_roles_gen.go +++ b/pkg/sdk/application_roles_gen.go @@ -12,8 +12,32 @@ import ( // by applying debug_mode parameter to the application, but it's a hacky solution and even with that you're limited with GRANT and REVOKE options. // That's why we're only exposing SHOW operations, because only they are the only allowed operations to be called from the program context. type ApplicationRoles interface { + Grant(ctx context.Context, request *GrantApplicationRoleRequest) error + Revoke(ctx context.Context, request *RevokeApplicationRoleRequest) error Show(ctx context.Context, request *ShowApplicationRoleRequest) ([]ApplicationRole, error) - ShowByID(ctx context.Context, request *ShowByIDApplicationRoleRequest) (*ApplicationRole, error) + ShowByID(ctx context.Context, applicationName AccountObjectIdentifier, id DatabaseObjectIdentifier) (*ApplicationRole, error) +} + +// GrantApplicationRoleOptions is based on https://docs.snowflake.com/en/sql-reference/sql/grant-application-role. +type GrantApplicationRoleOptions struct { + grant bool `ddl:"static" sql:"GRANT"` + applicationRole bool `ddl:"static" sql:"APPLICATION ROLE"` + name DatabaseObjectIdentifier `ddl:"identifier"` + To KindOfRole `ddl:"keyword" sql:"TO"` +} + +type KindOfRole struct { + RoleName *AccountObjectIdentifier `ddl:"identifier" sql:"ROLE"` + ApplicationRoleName *DatabaseObjectIdentifier `ddl:"identifier" sql:"APPLICATION ROLE"` + ApplicationName *AccountObjectIdentifier `ddl:"identifier" sql:"APPLICATION"` +} + +// RevokeApplicationRoleOptions is based on https://docs.snowflake.com/en/sql-reference/sql/revoke-application-role. +type RevokeApplicationRoleOptions struct { + revoke bool `ddl:"static" sql:"REVOKE"` + applicationRole bool `ddl:"static" sql:"APPLICATION ROLE"` + name DatabaseObjectIdentifier `ddl:"identifier"` + From KindOfRole `ddl:"keyword" sql:"FROM"` } // ShowApplicationRoleOptions is based on https://docs.snowflake.com/en/sql-reference/sql/show-application-roles. diff --git a/pkg/sdk/application_roles_gen_test.go b/pkg/sdk/application_roles_gen_test.go index e257a1711c..e4f466eaea 100644 --- a/pkg/sdk/application_roles_gen_test.go +++ b/pkg/sdk/application_roles_gen_test.go @@ -2,6 +2,113 @@ package sdk import "testing" +func TestApplicationRoles_Grant(t *testing.T) { + id := randomDatabaseObjectIdentifier() + + // Minimal valid GrantApplicationRoleOptions + defaultOpts := func() *GrantApplicationRoleOptions { + return &GrantApplicationRoleOptions{ + name: id, + } + } + + t.Run("validation: nil options", func(t *testing.T) { + var opts *GrantApplicationRoleOptions = nil + assertOptsInvalidJoinedErrors(t, opts, ErrNilOptions) + }) + + t.Run("validation: valid identifier for [opts.name]", func(t *testing.T) { + opts := defaultOpts() + opts.name = NewDatabaseObjectIdentifier("", "") + assertOptsInvalidJoinedErrors(t, opts, ErrInvalidObjectIdentifier) + }) + + t.Run("validation: exactly one field from [GrantApplicationRoleOptions.To.RoleName GrantApplicationRoleOptions.To.ApplicationRoleName GrantApplicationRoleOptions.To.ApplicationName] should be present", func(t *testing.T) { + opts := defaultOpts() + assertOptsInvalidJoinedErrors(t, opts, errExactlyOneOf("GrantApplicationRoleOptions.To", "RoleName", "ApplicationRoleName", "ApplicationName")) + }) + + t.Run("grant to role", func(t *testing.T) { + roleId := randomAccountObjectIdentifier() + + opts := defaultOpts() + opts.To = KindOfRole{ + RoleName: &roleId, + } + assertOptsValidAndSQLEquals(t, opts, `GRANT APPLICATION ROLE %s TO ROLE %s`, id.FullyQualifiedName(), roleId.FullyQualifiedName()) + }) + + t.Run("grant to application role", func(t *testing.T) { + appRoleId := randomDatabaseObjectIdentifier() + + opts := defaultOpts() + opts.To = KindOfRole{ + ApplicationRoleName: &appRoleId, + } + assertOptsValidAndSQLEquals(t, opts, `GRANT APPLICATION ROLE %s TO APPLICATION ROLE %s`, id.FullyQualifiedName(), appRoleId.FullyQualifiedName()) + }) + + t.Run("grant to application", func(t *testing.T) { + appId := randomAccountObjectIdentifier() + opts := defaultOpts() + opts.To = KindOfRole{ + ApplicationName: &appId, + } + assertOptsValidAndSQLEquals(t, opts, `GRANT APPLICATION ROLE %s TO APPLICATION %s`, id.FullyQualifiedName(), appId.FullyQualifiedName()) + }) +} + +func TestApplicationRoles_Revoke(t *testing.T) { + id := randomDatabaseObjectIdentifier() + + // Minimal valid RevokeApplicationRoleOptions + defaultOpts := func() *RevokeApplicationRoleOptions { + return &RevokeApplicationRoleOptions{ + name: id, + } + } + + t.Run("validation: nil options", func(t *testing.T) { + var opts *RevokeApplicationRoleOptions = nil + assertOptsInvalidJoinedErrors(t, opts, ErrNilOptions) + }) + + t.Run("validation: valid identifier for [opts.name]", func(t *testing.T) { + opts := defaultOpts() + opts.name = NewDatabaseObjectIdentifier("", "") + assertOptsInvalidJoinedErrors(t, opts, ErrInvalidObjectIdentifier) + }) + + t.Run("revoke from role", func(t *testing.T) { + roleId := randomAccountObjectIdentifier() + + opts := defaultOpts() + opts.From = KindOfRole{ + RoleName: &roleId, + } + assertOptsValidAndSQLEquals(t, opts, `REVOKE APPLICATION ROLE %s FROM ROLE %s`, id.FullyQualifiedName(), roleId.FullyQualifiedName()) + }) + + t.Run("revoke from application role", func(t *testing.T) { + appRoleId := randomDatabaseObjectIdentifier() + + opts := defaultOpts() + opts.From = KindOfRole{ + ApplicationRoleName: &appRoleId, + } + assertOptsValidAndSQLEquals(t, opts, `REVOKE APPLICATION ROLE %s FROM APPLICATION ROLE %s`, id.FullyQualifiedName(), appRoleId.FullyQualifiedName()) + }) + + t.Run("revoke from application", func(t *testing.T) { + appId := randomAccountObjectIdentifier() + opts := defaultOpts() + opts.From = KindOfRole{ + ApplicationName: &appId, + } + assertOptsValidAndSQLEquals(t, opts, `REVOKE APPLICATION ROLE %s FROM APPLICATION %s`, id.FullyQualifiedName(), appId.FullyQualifiedName()) + }) +} + func TestApplicationRoles_Show(t *testing.T) { appId := randomAccountObjectIdentifier() @@ -20,7 +127,7 @@ func TestApplicationRoles_Show(t *testing.T) { t.Run("validation: valid identifier for [opts.ApplicationName]", func(t *testing.T) { opts := defaultOpts() opts.ApplicationName = NewAccountObjectIdentifier("") - assertOptsInvalid(t, opts, errInvalidIdentifier("ShowApplicationRoleOptions", "ApplicationName")) + assertOptsInvalidJoinedErrors(t, opts, ErrInvalidObjectIdentifier) }) t.Run("all options", func(t *testing.T) { diff --git a/pkg/sdk/application_roles_impl_gen.go b/pkg/sdk/application_roles_impl_gen.go index 81fc6bc062..31c304fe38 100644 --- a/pkg/sdk/application_roles_impl_gen.go +++ b/pkg/sdk/application_roles_impl_gen.go @@ -12,6 +12,16 @@ type applicationRoles struct { client *Client } +func (v *applicationRoles) Grant(ctx context.Context, request *GrantApplicationRoleRequest) error { + opts := request.toOpts() + return validateAndExec(v.client, ctx, opts) +} + +func (v *applicationRoles) Revoke(ctx context.Context, request *RevokeApplicationRoleRequest) error { + opts := request.toOpts() + return validateAndExec(v.client, ctx, opts) +} + func (v *applicationRoles) Show(ctx context.Context, request *ShowApplicationRoleRequest) ([]ApplicationRole, error) { opts := request.toOpts() dbRows, err := validateAndQuery[applicationRoleDbRow](v.client, ctx, opts) @@ -22,23 +32,43 @@ func (v *applicationRoles) Show(ctx context.Context, request *ShowApplicationRol return resultList, nil } -func (v *applicationRoles) ShowByID(ctx context.Context, request *ShowByIDApplicationRoleRequest) (*ApplicationRole, error) { - appRoles, err := v.client.ApplicationRoles.Show(ctx, NewShowApplicationRoleRequest().WithApplicationName(request.ApplicationName)) +func (v *applicationRoles) ShowByID(ctx context.Context, applicationName AccountObjectIdentifier, id DatabaseObjectIdentifier) (*ApplicationRole, error) { + request := NewShowApplicationRoleRequest().WithApplicationName(applicationName) + applicationRoles, err := v.Show(ctx, request) if err != nil { return nil, err } - return collections.FindOne(appRoles, func(role ApplicationRole) bool { return role.Name == request.name.Name() }) + return collections.FindOne(applicationRoles, func(r ApplicationRole) bool { return r.Name == id.Name() }) +} + +func (r *GrantApplicationRoleRequest) toOpts() *GrantApplicationRoleOptions { + opts := &GrantApplicationRoleOptions{ + name: r.name, + } + opts.To = KindOfRole{ + RoleName: r.To.RoleName, + ApplicationRoleName: r.To.ApplicationRoleName, + ApplicationName: r.To.ApplicationName, + } + return opts +} + +func (r *RevokeApplicationRoleRequest) toOpts() *RevokeApplicationRoleOptions { + opts := &RevokeApplicationRoleOptions{ + name: r.name, + } + opts.From = KindOfRole{ + RoleName: r.From.RoleName, + ApplicationRoleName: r.From.ApplicationRoleName, + ApplicationName: r.From.ApplicationName, + } + return opts } func (r *ShowApplicationRoleRequest) toOpts() *ShowApplicationRoleOptions { opts := &ShowApplicationRoleOptions{ ApplicationName: r.ApplicationName, - } - if r.Limit != nil { - opts.Limit = &LimitFrom{ - Rows: r.Limit.Rows, - From: r.Limit.From, - } + Limit: r.Limit, } return opts } diff --git a/pkg/sdk/application_roles_validations_gen.go b/pkg/sdk/application_roles_validations_gen.go index 8f0be97d6a..d4e1318daf 100644 --- a/pkg/sdk/application_roles_validations_gen.go +++ b/pkg/sdk/application_roles_validations_gen.go @@ -1,16 +1,50 @@ package sdk -import "errors" +var ( + _ validatable = new(GrantApplicationRoleOptions) + _ validatable = new(RevokeApplicationRoleOptions) + _ validatable = new(ShowApplicationRoleOptions) +) -var _ validatable = new(ShowApplicationRoleOptions) +func (opts *GrantApplicationRoleOptions) validate() error { + if opts == nil { + return ErrNilOptions + } + var errs []error + if !ValidObjectIdentifier(opts.name) { + errs = append(errs, ErrInvalidObjectIdentifier) + } + if valueSet(opts.To) { + if !exactlyOneValueSet(opts.To.RoleName, opts.To.ApplicationRoleName, opts.To.ApplicationName) { + errs = append(errs, errExactlyOneOf("GrantApplicationRoleOptions.To", "RoleName", "ApplicationRoleName", "ApplicationName")) + } + } + return JoinErrors(errs...) +} + +func (opts *RevokeApplicationRoleOptions) validate() error { + if opts == nil { + return ErrNilOptions + } + var errs []error + if !ValidObjectIdentifier(opts.name) { + errs = append(errs, ErrInvalidObjectIdentifier) + } + if valueSet(opts.From) { + if !exactlyOneValueSet(opts.From.RoleName, opts.From.ApplicationRoleName, opts.From.ApplicationName) { + errs = append(errs, errExactlyOneOf("RevokeApplicationRoleOptions.From", "RoleName", "ApplicationRoleName", "ApplicationName")) + } + } + return JoinErrors(errs...) +} func (opts *ShowApplicationRoleOptions) validate() error { if opts == nil { - return errors.Join(ErrNilOptions) + return ErrNilOptions } var errs []error if !ValidObjectIdentifier(opts.ApplicationName) { - errs = append(errs, errInvalidIdentifier("ShowApplicationRoleOptions", "ApplicationName")) + errs = append(errs, ErrInvalidObjectIdentifier) } - return errors.Join(errs...) + return JoinErrors(errs...) } diff --git a/pkg/sdk/testint/application_roles_gen_integration_test.go b/pkg/sdk/testint/application_roles_gen_integration_test.go index 1eeee4c830..af51a50dd7 100644 --- a/pkg/sdk/testint/application_roles_gen_integration_test.go +++ b/pkg/sdk/testint/application_roles_gen_integration_test.go @@ -2,7 +2,6 @@ package testint import ( "context" - "fmt" "testing" "github.com/Snowflake-Labs/terraform-provider-snowflake/pkg/sdk" @@ -21,20 +20,28 @@ import ( // - we're ready to query application roles we have just created func TestInt_ApplicationRoles(t *testing.T) { client := testClient(t) + ctx := testContext(t) - stage, cleanupStage := testClientHelper().Stage.CreateStage(t) - t.Cleanup(cleanupStage) + createApp := func(t *testing.T) *sdk.Application { + t.Helper() + + stage, cleanupStage := testClientHelper().Stage.CreateStage(t) + t.Cleanup(cleanupStage) - testClientHelper().Stage.PutOnStage(t, stage.ID(), "manifest.yml") - testClientHelper().Stage.PutOnStage(t, stage.ID(), "setup.sql") + testClientHelper().Stage.PutOnStage(t, stage.ID(), "manifest.yml") + testClientHelper().Stage.PutOnStage(t, stage.ID(), "setup.sql") - applicationPackage, cleanupApplicationPackage := testClientHelper().ApplicationPackage.CreateApplicationPackage(t) - t.Cleanup(cleanupApplicationPackage) + applicationPackage, cleanupApplicationPackage := testClientHelper().ApplicationPackage.CreateApplicationPackage(t) + t.Cleanup(cleanupApplicationPackage) - testClientHelper().ApplicationPackage.AddApplicationPackageVersion(t, applicationPackage.ID(), stage.ID(), "v1") + testClientHelper().ApplicationPackage.AddApplicationPackageVersion(t, applicationPackage.ID(), stage.ID(), "v1") + + application, cleanupApplication := testClientHelper().Application.CreateApplication(t, applicationPackage.ID(), "v1") + t.Cleanup(cleanupApplication) + return application + } - application, cleanupApplication := testClientHelper().Application.CreateApplication(t, applicationPackage.ID(), "v1") - t.Cleanup(cleanupApplication) + application := createApp(t) assertApplicationRole := func(t *testing.T, appRole *sdk.ApplicationRole, name string, comment string) { t.Helper() @@ -53,19 +60,27 @@ func TestInt_ApplicationRoles(t *testing.T) { assertApplicationRole(t, appRole, name, comment) } + assertGrant := func(t *testing.T, grants []sdk.Grant, privilege sdk.ObjectPrivilege, on, to sdk.ObjectType) { + t.Helper() + require.NotEmpty(t, grants) + assert.NotEmpty(t, grants[0].CreatedOn) + assert.Equal(t, privilege.String(), grants[0].Privilege) + assert.Equal(t, on, grants[0].GrantedOn) + assert.Equal(t, to, grants[0].GrantedTo) + } + t.Run("show by id - same name in different schemas", func(t *testing.T) { name := "app_role_1" id := sdk.NewDatabaseObjectIdentifier(application.Name, name) ctx := context.Background() - appRole, err := client.ApplicationRoles.ShowByID(ctx, sdk.NewShowByIDApplicationRoleRequest(id, sdk.NewAccountObjectIdentifier(application.Name))) + appRole, err := client.ApplicationRoles.ShowByID(ctx, sdk.NewAccountObjectIdentifier(application.Name), id) require.NoError(t, err) assertApplicationRole(t, appRole, name, "some comment") }) t.Run("Show", func(t *testing.T) { - ctx := context.Background() req := sdk.NewShowApplicationRoleRequest(). WithApplicationName(application.ID()). WithLimit(&sdk.LimitFrom{ @@ -78,6 +93,55 @@ func TestInt_ApplicationRoles(t *testing.T) { assertApplicationRoles(t, appRoles, "app_role_2", "some comment2") }) + t.Run("show grants to application - grant to role", func(t *testing.T) { + role, cleanupRole := testClientHelper().Role.CreateRole(t) + t.Cleanup(cleanupRole) + + id := sdk.NewDatabaseObjectIdentifier(application.Name, "app_role_1") + // grant the application role to the role + kindOfRole := sdk.NewKindOfRoleRequest().WithRoleName(sdk.Pointer(role.ID())) + gr := sdk.NewGrantApplicationRoleRequest(id).WithTo(*kindOfRole) + err := client.ApplicationRoles.Grant(ctx, gr) + require.NoError(t, err) + + grants, err := client.Grants.Show(ctx, &sdk.ShowGrantOptions{To: &sdk.ShowGrantsTo{Role: role.ID()}}) + require.NoError(t, err) + assertGrant(t, grants, sdk.ObjectPrivilegeUsage, sdk.ObjectTypeApplicationRole, sdk.ObjectTypeRole) + + // revoke the application role from the role + rr := sdk.NewRevokeApplicationRoleRequest(id).WithFrom(*kindOfRole) + err = client.ApplicationRoles.Revoke(ctx, rr) + require.NoError(t, err) + + grants, err = client.Grants.Show(ctx, &sdk.ShowGrantOptions{To: &sdk.ShowGrantsTo{Role: role.ID()}}) + require.NoError(t, err) + require.Empty(t, grants) + }) + + t.Run("show grants to application - grant to application", func(t *testing.T) { + application2 := createApp(t) + + id := sdk.NewDatabaseObjectIdentifier(application.Name, "app_role_1") + // grant the application role to the application + kindOfRole := sdk.NewKindOfRoleRequest().WithApplicationName(sdk.Pointer(application2.ID())) + gr := sdk.NewGrantApplicationRoleRequest(id).WithTo(*kindOfRole) + err := client.ApplicationRoles.Grant(ctx, gr) + require.NoError(t, err) + + grants, err := client.Grants.Show(ctx, &sdk.ShowGrantOptions{To: &sdk.ShowGrantsTo{Application: application2.ID()}}) + require.NoError(t, err) + assertGrant(t, grants, sdk.ObjectPrivilegeUsage, sdk.ObjectTypeApplicationRole, sdk.ObjectTypeApplication) + + // revoke the application role from the role + rr := sdk.NewRevokeApplicationRoleRequest(id).WithFrom(*kindOfRole) + err = client.ApplicationRoles.Revoke(ctx, rr) + require.NoError(t, err) + + grants, err = client.Grants.Show(ctx, &sdk.ShowGrantOptions{To: &sdk.ShowGrantsTo{Application: application2.ID()}}) + require.NoError(t, err) + require.Empty(t, grants) + }) + t.Run("show grants to application role", func(t *testing.T) { name := "app_role_1" id := sdk.NewDatabaseObjectIdentifier(application.Name, name) @@ -89,12 +153,7 @@ func TestInt_ApplicationRoles(t *testing.T) { } grants, err := client.Grants.Show(ctx, opts) require.NoError(t, err) - - require.NotEmpty(t, grants) - require.NotEmpty(t, grants[0].CreatedOn) - require.Equal(t, sdk.ObjectPrivilegeUsage.String(), grants[0].Privilege) - require.Equal(t, sdk.ObjectTypeDatabase, grants[0].GrantedOn) - require.Equal(t, sdk.ObjectTypeApplicationRole, grants[0].GrantedTo) + assertGrant(t, grants, sdk.ObjectPrivilegeUsage, sdk.ObjectTypeDatabase, sdk.ObjectTypeApplicationRole) }) t.Run("show grants of application role", func(t *testing.T) { @@ -114,39 +173,4 @@ func TestInt_ApplicationRoles(t *testing.T) { require.Equal(t, sdk.ObjectTypeRole, grants[0].GrantedTo) require.Equal(t, application.ID(), grants[0].GrantedBy) }) - - t.Run("show grants to application", func(t *testing.T) { - // Need second app to be able to grant application role to it. Cannot grant to parent application (098806 (0A000): Cannot grant an APPLICATION ROLE to the parent APPLICATION). - stage2, cleanupStage2 := testClientHelper().Stage.CreateStage(t) - t.Cleanup(cleanupStage2) - - testClientHelper().Stage.PutOnStage(t, stage2.ID(), "manifest2.yml") - testClientHelper().Stage.PutOnStage(t, stage2.ID(), "setup.sql") - - application2, cleanupApplication2 := testClientHelper().Application.CreateApplication(t, applicationPackage.ID(), "v1") - t.Cleanup(cleanupApplication2) - name := "app_role_1" - id := sdk.NewDatabaseObjectIdentifier(application.Name, name) - ctx := context.Background() - - _, err := client.ExecForTests(ctx, fmt.Sprintf(`GRANT APPLICATION ROLE %s TO APPLICATION %s`, id.FullyQualifiedName(), application2.ID().FullyQualifiedName())) - require.NoError(t, err) - defer func() { - _, err := client.ExecForTests(ctx, fmt.Sprintf(`REVOKE APPLICATION ROLE %s FROM APPLICATION %s`, id.FullyQualifiedName(), application2.ID().FullyQualifiedName())) - require.NoError(t, err) - }() - - opts := new(sdk.ShowGrantOptions) - opts.To = &sdk.ShowGrantsTo{ - Application: application2.ID(), - } - grants, err := client.Grants.Show(ctx, opts) - require.NoError(t, err) - - require.NotEmpty(t, grants) - require.NotEmpty(t, grants[0].CreatedOn) - require.Equal(t, sdk.ObjectPrivilegeUsage.String(), grants[0].Privilege) - require.Equal(t, sdk.ObjectTypeApplicationRole, grants[0].GrantedOn) - require.Equal(t, sdk.ObjectTypeApplication, grants[0].GrantedTo) - }) } diff --git a/v1-preparations/REMAINING_GA_OBJECTS.MD b/v1-preparations/REMAINING_GA_OBJECTS.MD index d1da369e68..439fd1f9af 100644 --- a/v1-preparations/REMAINING_GA_OBJECTS.MD +++ b/v1-preparations/REMAINING_GA_OBJECTS.MD @@ -36,7 +36,7 @@ Known issues lists open issues touching the given object. Note that some of thes | NETWORK RULE | ❌ | [#2593](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2593), [#2482](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2482) | | PACKAGES POLICY | ❌ | - | | PASSWORD POLICY | ❌ | [#2213](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2213), [#2162](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2162) | -| PIPE | ❌ | [#2075](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2075), [#1781](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1781), [#1707](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1707), [#1478](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1478), [#533](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/533) | +| PIPE | ❌ | [#2785](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2785), [#2075](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2075), [#1781](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1781), [#1707](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1707), [#1478](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/1478), [#533](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/533) | | SECRET | ❌ | [#2545](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2545) | | SEQUENCE | ❌ | [#2589](https://github.com/Snowflake-Labs/terraform-provider-snowflake/issues/2589) | -| SESSION POLICY | ❌ | - | \ No newline at end of file +| SESSION POLICY | ❌ | - |