diff --git a/bundle/config/mutator/resourcemutator/apply_presets.go b/bundle/config/mutator/resourcemutator/apply_presets.go index bb84240d7cc..e464c8fcce3 100644 --- a/bundle/config/mutator/resourcemutator/apply_presets.go +++ b/bundle/config/mutator/resourcemutator/apply_presets.go @@ -208,7 +208,7 @@ func (m *applyPresets) Apply(ctx context.Context, b *bundle.Bundle) diag.Diagnos if s == nil { continue } - s.Name = normalizePrefix(prefix) + s.Name + s.Name = normalizeSchemaPrefix(prefix, b) + s.Name // HTTP API for schemas doesn't yet support tags. It's only supported in // the Databricks UI and via the SQL API. } @@ -387,3 +387,24 @@ func normalizePrefix(prefix string) string { return textutil.NormalizeString(prefix) + suffix } + +func normalizeSchemaPrefix(prefix string, b *bundle.Bundle) string { + user := b.Config.Workspace.CurrentUser + // Preserve the existing normalization for prefixes generated by development mode. + if b.Config.Bundle.Mode == config.Development && user != nil && prefix == "[dev "+user.ShortName+"] " { + return normalizePrefix(prefix) + } + + prefix = strings.ReplaceAll(prefix, "[", "") + prefix = strings.Trim(prefix, " ") + suffix := "" + if strings.HasSuffix(prefix, "]") { + suffix = "_" + } + + parts := strings.Split(prefix, "_") + for i := range parts { + parts[i] = textutil.NormalizeString(parts[i]) + } + return strings.Join(parts, "_") + suffix +} diff --git a/bundle/config/mutator/resourcemutator/apply_presets_test.go b/bundle/config/mutator/resourcemutator/apply_presets_test.go index c97db137b26..cfa3df95c5a 100644 --- a/bundle/config/mutator/resourcemutator/apply_presets_test.go +++ b/bundle/config/mutator/resourcemutator/apply_presets_test.go @@ -38,3 +38,54 @@ func TestApplyPresetsSkipsNilQualityMonitors(t *testing.T) { require.NoError(t, diags.Error()) assert.Nil(t, b.Config.Resources.QualityMonitors["monitor"].Schedule) } + +func TestApplyPresetsSchemaPrefixPreservesUnderscores(t *testing.T) { + tests := []struct { + name string + prefix string + expected string + }{ + {"trailing underscore", "my_user_", "my_user_qc"}, + {"repeated underscores", "my__user__", "my__user__qc"}, + {"bracketed underscores", "[qa__team] ", "qa__team_qc"}, + {"bracketed prefix", "[prod] ", "prod_qc"}, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + b := &bundle.Bundle{ + Config: config.Root{ + Presets: config.Presets{NamePrefix: test.prefix}, + Resources: config.Resources{ + Schemas: map[string]*resources.Schema{ + "qc": {CreateSchema: catalog.CreateSchema{CatalogName: "my_catalog", Name: "qc"}}, + }, + }, + }, + } + + diags := bundle.Apply(t.Context(), b, ApplyPresets()) + require.NoError(t, diags.Error()) + assert.Equal(t, test.expected, b.Config.Resources.Schemas["qc"].Name) + }) + } + + b := &bundle.Bundle{ + Config: config.Root{ + Bundle: config.Bundle{Mode: config.Development}, + Presets: config.Presets{Tags: map[string]string{"dev": "my__user"}}, + Workspace: config.Workspace{ + CurrentUser: &config.User{ShortName: "my__user"}, + }, + Resources: config.Resources{ + Schemas: map[string]*resources.Schema{ + "qc": {CreateSchema: catalog.CreateSchema{CatalogName: "my_catalog", Name: "qc"}}, + }, + }, + }, + } + + diags := bundle.ApplySeq(t.Context(), b, ApplyTargetMode(), ApplyPresets()) + require.NoError(t, diags.Error()) + assert.Equal(t, "dev_my_user_qc", b.Config.Resources.Schemas["qc"].Name) +}