Skip to content

Commit c5dd3dd

Browse files
authored
Introduce new bundle variable: ${workspace.current_user.domain_friendly_name} (#3623)
## Why <!-- Why are these changes needed? Provide the context that the reviewer might be missing. For example, were there any decisions behind the change that are not reflected in the code itself? --> `${workspace.current_user.dns_name}` will allow bundle authors to prefix apps and database instances resources with caller's usernames (useful for development targets). Currently `short_name` does not always fit this purpose because it can contain characters that apps / database instances do not allow in the names of resources. ## Tests <!-- How have you tested the changes? --> - Existing tests - New unit test with transformation examples - Manual check that `dns_name` can be used as a bundle variable <!-- If your PR needs to be included in the release notes for next release, add a separate entry in NEXT_CHANGELOG.md as part of your PR. -->
1 parent edabd9b commit c5dd3dd

File tree

8 files changed

+156
-3
lines changed

8 files changed

+156
-3
lines changed

NEXT_CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@
1010

1111
### Bundles
1212
* Add new Lakeflow Pipelines support for bundle generate ([#3568](https://github.com/databricks/cli/pull/3568))
13+
* Introduce new bundle variable: `${workspace.current_user.domain_friendly_name}` ([#3623](https://github.com/databricks/cli/pull/3623))
1314

1415
### API Changes

acceptance/bundle/deployment/bind/alert/out.test.toml

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

acceptance/bundle/validate/job-references/output.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@
5757
"workspace": {
5858
"artifact_path": "/Workspace/Users/[USERNAME]/.bundle/foobar/default/artifacts",
5959
"current_user": {
60+
"domain_friendly_name": "[USERNAME]",
6061
"id": "[USERID]",
6162
"short_name": "[USERNAME]",
6263
"userName": "[USERNAME]"

bundle/config/mutator/populate_current_user.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,9 @@ func (m *populateCurrentUser) Apply(ctx context.Context, b *bundle.Bundle) diag.
3333
}
3434

3535
b.Config.Workspace.CurrentUser = &config.User{
36-
ShortName: iamutil.GetShortUserName(me),
37-
User: me,
36+
ShortName: iamutil.GetShortUserName(me),
37+
DomainFriendlyName: iamutil.GetShortUserDomainFriendlyName(me),
38+
User: me,
3839
}
3940

4041
// Configure tagging object now that we know we have a valid client.

bundle/config/workspace.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ type User struct {
7272
// A short name for the user, based on the user's UserName.
7373
ShortName string `json:"short_name,omitempty" bundle:"readonly"`
7474

75+
// A short name for the user that is stripped off of non-alphanumeric character
76+
// Can be used as a prefix for resources that use their name as part of their URL (e.g. Apps, Database Instances)
77+
DomainFriendlyName string `json:"domain_friendly_name,omitempty" bundle:"readonly"`
78+
7579
*iam.User
7680
}
7781

libs/iamutil/user.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,13 @@ func GetShortUserName(user *iam.User) string {
1818
local, _, _ := strings.Cut(name, "@")
1919
return textutil.NormalizeString(local)
2020
}
21+
22+
// GetShortUserDomainFriendlyName returns a dns-friendly for of the user name based on short name
23+
// We replace all non-alphanumeric characters with a dash (following Databricks Apps' naming requirements)
24+
func GetShortUserDomainFriendlyName(user *iam.User) string {
25+
name := GetShortUserName(user)
26+
return strings.ToLower(textutil.Chain(
27+
textutil.NormalizeMarks(),
28+
textutil.ReplaceNotIn(textutil.Alphanumeric, '-'),
29+
).TransformString(name))
30+
}

libs/iamutil/user_test.go

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,3 +111,126 @@ func TestGetShortUserName(t *testing.T) {
111111
assert.Equal(t, tt.expected, GetShortUserName(tt.user))
112112
}
113113
}
114+
115+
func TestGetShortUserDomainFriendlyName(t *testing.T) {
116+
tests := []struct {
117+
name string
118+
user *iam.User
119+
expected string
120+
}{
121+
{
122+
name: "basic email with dots",
123+
user: &iam.User{
124+
UserName: "[email protected]",
125+
},
126+
expected: "test-user-1234",
127+
},
128+
{
129+
name: "email with underscores",
130+
user: &iam.User{
131+
UserName: "[email protected]",
132+
},
133+
expected: "test-user",
134+
},
135+
{
136+
name: "email with special characters",
137+
user: &iam.User{
138+
UserName: "test$#%[email protected]",
139+
},
140+
expected: "test-user",
141+
},
142+
{
143+
name: "service principal with display name",
144+
user: &iam.User{
145+
UserName: `1706906c-c0a2-4c25-9f57-3a7aa3cb8123`,
146+
DisplayName: "my_service_principal",
147+
},
148+
expected: "my-service-principal",
149+
},
150+
{
151+
name: "uppercase letters should be lowercased",
152+
user: &iam.User{
153+
UserName: "[email protected]",
154+
},
155+
expected: "john-doe",
156+
},
157+
{
158+
name: "mixed case with numbers",
159+
user: &iam.User{
160+
UserName: "[email protected]",
161+
},
162+
expected: "user123-test",
163+
},
164+
{
165+
name: "unicode characters with normalization",
166+
user: &iam.User{
167+
UserName: "tést.ü[email protected]",
168+
},
169+
expected: "test-user",
170+
},
171+
{
172+
name: "complex special characters",
173+
user: &iam.User{
174+
UserName: "user+tag&[email protected]",
175+
},
176+
expected: "user-tag-more",
177+
},
178+
{
179+
name: "service principal with mixed case display name",
180+
user: &iam.User{
181+
UserName: `1706906c-c0a2-4c25-9f57-3a7aa3cb8123`,
182+
DisplayName: "My_Service_Principal",
183+
},
184+
expected: "my-service-principal",
185+
},
186+
{
187+
name: "email with multiple consecutive special chars",
188+
user: &iam.User{
189+
UserName: "[email protected]",
190+
},
191+
expected: "test-user-name",
192+
},
193+
{
194+
name: "chinese characters in email",
195+
user: &iam.User{
196+
UserName: "用户.测试@example.com",
197+
},
198+
expected: "-----",
199+
},
200+
{
201+
name: "japanese characters in email",
202+
user: &iam.User{
203+
UserName: "ユーザー.テスト@example.com",
204+
},
205+
expected: "--------",
206+
},
207+
{
208+
name: "emoji in email",
209+
user: &iam.User{
210+
UserName: "user😀.test🚀@example.com",
211+
},
212+
expected: "user-test",
213+
},
214+
{
215+
name: "mixed latin and chinese characters",
216+
user: &iam.User{
217+
UserName: "john.李明@company.com",
218+
},
219+
expected: "john---",
220+
},
221+
{
222+
name: "service principal with emoji display name",
223+
user: &iam.User{
224+
UserName: "1706906c-c0a2-4c25-9f57-3a7aa3cb8123",
225+
DisplayName: "bot🤖_service",
226+
},
227+
expected: "bot-service",
228+
},
229+
}
230+
231+
for _, tt := range tests {
232+
t.Run(tt.name, func(t *testing.T) {
233+
assert.Equal(t, tt.expected, GetShortUserDomainFriendlyName(tt.user))
234+
})
235+
}
236+
}

libs/textutil/latin.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,16 @@ var Latin1 = &unicode.RangeTable{
99
},
1010
LatinOffset: 1,
1111
}
12+
13+
// Range table for alphanumeric characters (ASCII letters and digits only).
14+
var Alphanumeric = &unicode.RangeTable{
15+
R16: []unicode.Range16{
16+
// ASCII digits 0-9
17+
{0x0030, 0x0039, 1},
18+
// ASCII uppercase letters A-Z
19+
{0x0041, 0x005A, 1},
20+
// ASCII lowercase letters a-z
21+
{0x0061, 0x007A, 1},
22+
},
23+
LatinOffset: 3,
24+
}

0 commit comments

Comments
 (0)