diff --git a/bundle/config/mutator/resourcemutator/apply_bundle_permissions.go b/bundle/config/mutator/resourcemutator/apply_bundle_permissions.go index 15493ae9d15..da9fcc75c22 100644 --- a/bundle/config/mutator/resourcemutator/apply_bundle_permissions.go +++ b/bundle/config/mutator/resourcemutator/apply_bundle_permissions.go @@ -108,6 +108,12 @@ func (m *bundlePermissions) Apply(ctx context.Context, b *bundle.Bundle) diag.Di return diag.FromErr(err) } + // Target permissions follow root permissions when SelectTarget merges the lists. + targetPermissionsStart := len(b.Config.Permissions) + if b.Target != nil { + targetPermissionsStart -= len(b.Target.Permissions) + } + for _, group := range b.Config.Resources.AllResources() { key := group.Description.PluralName lm, ok := levelsMap[key] @@ -124,6 +130,7 @@ func (m *bundlePermissions) Apply(ctx context.Context, b *bundle.Bundle) diag.Di permissions, key, lm, + targetPermissionsStart, )...) writePermissions(field, permissions) } @@ -187,9 +194,10 @@ func convertPermissions( resourcePermissions []resources.Permission, resourceName string, lm map[string]string, + targetPermissionsStart int, ) []resources.Permission { var permissions []resources.Permission - for _, p := range bundlePermissions { + for i, p := range bundlePermissions { level, ok := lm[string(p.Level)] // If there is no bundle permission level defined in the map, it means // it's not applicable for the resource, therefore skipping @@ -197,7 +205,13 @@ func convertPermissions( continue } - if notifyForPermissionOverlap(ctx, p, resourcePermissions, resourceName) { + if i >= targetPermissionsStart && resourceName == "jobs" && p.GroupName != "" { + // A job-level group permission takes precedence over the target-level one. + overlap, _ := isPermissionOverlap(p, resourcePermissions, resourceName) + if overlap { + continue + } + } else if notifyForPermissionOverlap(ctx, p, resourcePermissions, resourceName) { continue } diff --git a/bundle/config/mutator/resourcemutator/apply_bundle_permissions_test.go b/bundle/config/mutator/resourcemutator/apply_bundle_permissions_test.go index 610832c2b92..037f06332f2 100644 --- a/bundle/config/mutator/resourcemutator/apply_bundle_permissions_test.go +++ b/bundle/config/mutator/resourcemutator/apply_bundle_permissions_test.go @@ -9,6 +9,7 @@ import ( "github.com/databricks/cli/bundle" "github.com/databricks/cli/bundle/config" + "github.com/databricks/cli/bundle/config/mutator" "github.com/databricks/cli/bundle/config/resources" "github.com/databricks/cli/libs/diag" "github.com/databricks/cli/libs/dyn" @@ -223,6 +224,58 @@ func TestWarningOnOverlapPermission(t *testing.T) { require.Contains(t, b.Config.Resources.Jobs["job_2"].Permissions, resources.JobPermission{Level: "CAN_VIEW", GroupName: "TestGroup"}) } +func TestTargetGroupPermissionOverridesJobWithoutWarning(t *testing.T) { + for _, tc := range []struct { + name string + rootPermissions []resources.Permission + wantWarnings int + }{ + {name: "target only"}, + { + name: "root and target", + rootPermissions: []resources.Permission{ + {Level: permissions.CAN_VIEW, GroupName: "TestGroup"}, + }, + wantWarnings: 1, + }, + } { + t.Run(tc.name, func(t *testing.T) { + b := &bundle.Bundle{ + Config: config.Root{ + Permissions: tc.rootPermissions, + Targets: map[string]*config.Target{ + "dev-ci": { + Permissions: []resources.Permission{ + {Level: permissions.CAN_VIEW, GroupName: "TestGroup"}, + }, + Resources: &config.Resources{ + Jobs: map[string]*resources.Job{ + "job_1": { + JobSettings: jobs.JobSettings{Name: "job_1"}, + Permissions: []resources.JobPermission{ + {Level: "CAN_MANAGE_RUN", GroupName: "TestGroup"}, + }, + }, + }, + }, + }, + }, + }, + } + + diags := bundle.ApplySeq(t.Context(), b, mutator.SelectTarget("dev-ci"), ApplyBundlePermissions()) + require.Len(t, diags, tc.wantWarnings) + if tc.wantWarnings > 0 { + require.Equal(t, diag.Warning, diags[0].Severity) + require.Equal(t, "'jobs' already has permissions set for 'TestGroup' group", diags[0].Summary) + } + require.Equal(t, []resources.JobPermission{ + {Level: "CAN_MANAGE_RUN", GroupName: "TestGroup"}, + }, b.Config.Resources.Jobs["job_1"].Permissions) + }) + } +} + func TestAllResourcesExplicitlyDefinedForPermissionsSupport(t *testing.T) { r := config.Resources{}