Skip to content

Commit fef8768

Browse files
committed
Fix deploy scripts: use SCIM API for SP admin group check, add verbose logging
The `databricks groups get admins` CLI command requires a numeric group ID, not the display name, causing the SP admin check to silently fail. Replaced with direct SCIM v2 REST API calls. Also extracted auth token/host resolution to a shared step and added detailed progress messages throughout post-deploy. Co-authored-by: Isaac
1 parent d49b102 commit fef8768

2 files changed

Lines changed: 132 additions & 61 deletions

File tree

deploy.ps1

Lines changed: 63 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -112,40 +112,57 @@ Write-Host ""
112112
databricks bundle run $AppName --profile $profile
113113

114114
# Get app metadata
115+
Write-Host ""
116+
Write-Host " Fetching app metadata..."
115117
$appData = $null
116118
$integrationId = $null
117119
$spId = $null
120+
$spName = $null
118121
try {
119122
$appData = databricks apps get $DeployedAppName --profile $profile -o json 2>$null | ConvertFrom-Json
120123
$integrationId = $appData.oauth2_app_integration_id
121124
$spId = $appData.service_principal_id
125+
$spName = $appData.service_principal_name
126+
} catch {
127+
Write-Host " Warning: Could not fetch app metadata." -ForegroundColor Yellow
128+
}
129+
Write-Host " App integration ID: $(if ($integrationId) { $integrationId } else { 'not found' })"
130+
Write-Host " Service Principal: $(if ($spName) { $spName } else { 'unknown' }) (ID: $(if ($spId) { $spId } else { 'not found' }))"
131+
132+
# Get auth token + workspace host for API calls
133+
$token = $null
134+
$wsHost = $null
135+
try {
136+
$tokenJson = databricks auth token --profile $profile 2>$null | ConvertFrom-Json
137+
$token = $tokenJson.access_token
138+
$wsHost = (python3 -c "
139+
from databricks.sdk.core import Config
140+
c = Config(profile='$profile')
141+
print((c.host or '').rstrip('/'))
142+
" 2>$null).Trim()
122143
} catch {}
123144

124145
# Upload app thumbnail
125146
if (Test-Path $Screenshot) {
126147
Write-Host ""
127148
Write-Host " Uploading app thumbnail..."
128-
try {
129-
$tokenJson = databricks auth token --profile $profile 2>$null | ConvertFrom-Json
130-
$token = $tokenJson.access_token
131-
$wsHost = (python3 -c "
132-
from databricks.sdk.core import Config
133-
c = Config(profile='$profile')
134-
print((c.host or '').rstrip('/'))
135-
" 2>$null).Trim()
136-
if ($token -and $wsHost) {
149+
if ($token -and $wsHost) {
150+
try {
137151
$b64 = [Convert]::ToBase64String([IO.File]::ReadAllBytes($Screenshot))
138152
$body = "{`"encoded_thumbnail`": `"$b64`"}"
139153
Invoke-RestMethod -Uri "$wsHost/api/2.0/apps/$DeployedAppName/thumbnail" `
140154
-Method Post -Headers @{ Authorization = "Bearer $token"; "Content-Type" = "application/json" } `
141155
-Body $body 2>$null | Out-Null
142156
Write-Host " Thumbnail uploaded." -ForegroundColor Green
143-
} else {
144-
Write-Host " Warning: Could not upload thumbnail (missing token or host)." -ForegroundColor Yellow
157+
} catch {
158+
Write-Host " Warning: Could not upload thumbnail." -ForegroundColor Yellow
145159
}
146-
} catch {
147-
Write-Host " Warning: Could not upload thumbnail." -ForegroundColor Yellow
160+
} else {
161+
Write-Host " Warning: Could not upload thumbnail (missing token or host)." -ForegroundColor Yellow
148162
}
163+
} else {
164+
Write-Host ""
165+
Write-Host " Skipping thumbnail upload ($Screenshot not found)."
149166
}
150167

151168
# Configure OBO scopes (requires an account-level profile)
@@ -179,7 +196,7 @@ else {
179196
}
180197
else {
181198
Write-Host " Using account profile: $accountProfile"
182-
Write-Host " Integration ID: $integrationId"
199+
Write-Host " Updating integration $integrationId with all-apis scope..."
183200

184201
try {
185202
$integJson = databricks account custom-app-integration get $integrationId --profile $accountProfile -o json 2>$null | ConvertFrom-Json
@@ -191,35 +208,54 @@ else {
191208
$updateJson = "{`"scopes`": $OboScopes, `"redirect_urls`": $redirectUrls}"
192209
try {
193210
databricks account custom-app-integration update $integrationId --profile $accountProfile --json $updateJson 2>$null
194-
Write-Host " OBO scopes configured successfully: all-apis enabled." -ForegroundColor Green
211+
Write-Host " OBO scopes configured successfully." -ForegroundColor Green
195212
} catch {
196213
Write-Host " Warning: Could not update OBO scopes. Configure them manually in the Databricks Apps UI." -ForegroundColor Yellow
197214
}
198215
}
199216
}
200217

201218
# Add Service Principal to admins group
202-
if ($spId) {
203-
$spInAdmins = $false
219+
Write-Host ""
220+
Write-Host " Checking Service Principal admin group membership..."
221+
222+
if (-not $spId) {
223+
Write-Host " Warning: No Service Principal ID found - skipping admins group check." -ForegroundColor Yellow
224+
}
225+
elseif (-not $token -or -not $wsHost) {
226+
Write-Host " Warning: No auth token or host - skipping admins group check." -ForegroundColor Yellow
227+
}
228+
else {
229+
# Use SCIM API to find admins group and check membership
204230
try {
205-
$adminsJson = databricks groups get admins --profile $profile -o json 2>$null | ConvertFrom-Json
206-
$spInAdmins = ($adminsJson.members | Where-Object { $_.value -eq "$spId" }).Count -gt 0
207-
} catch {}
231+
$adminsScim = Invoke-RestMethod -Uri "$wsHost/api/2.0/preview/scim/v2/Groups?filter=displayName+eq+%22admins%22" `
232+
-Headers @{ Authorization = "Bearer $token" } 2>$null
233+
$adminsGroup = $adminsScim.Resources | Select-Object -First 1
234+
$adminsGroupId = $adminsGroup.id
235+
$spInAdmins = ($adminsGroup.members | Where-Object { "$($_.value)" -eq "$spId" }).Count -gt 0
236+
} catch {
237+
$adminsGroupId = $null
238+
$spInAdmins = $false
239+
}
208240

209-
if ($spInAdmins) {
210-
Write-Host ""
211-
Write-Host " App Service Principal (ID: $spId) is already in the admins group."
241+
if (-not $adminsGroupId) {
242+
Write-Host " Warning: Could not find admins group via SCIM API." -ForegroundColor Yellow
243+
}
244+
elseif ($spInAdmins) {
245+
Write-Host " $spName (ID: $spId) is already in the admins group."
212246
}
213247
else {
214-
Write-Host ""
215-
Write-Host " The app's Service Principal (ID: $spId) is NOT in the admins group."
248+
Write-Host " $spName (ID: $spId) is NOT in the admins group."
216249
Write-Host " Adding it to admins allows the SP to access all workspace objects."
217250
$addToAdmins = Read-Host " Add Service Principal to admins group? [y/N]"
218251

219252
if ($addToAdmins -match '^[Yy]$') {
220-
$patchJson = '{"Operations": [{"op": "add", "value": {"members": [{"value": "' + $spId + '"}]}}], "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"]}'
253+
Write-Host " Adding SP to admins group (SCIM group ID: $adminsGroupId)..."
221254
try {
222-
databricks groups patch admins --profile $profile --json $patchJson 2>$null
255+
$patchBody = "{`"Operations`": [{`"op`": `"add`", `"path`": `"members`", `"value`": [{`"value`": `"$spId`"}]}], `"schemas`": [`"urn:ietf:params:scim:api:messages:2.0:PatchOp`"]}"
256+
Invoke-RestMethod -Uri "$wsHost/api/2.0/preview/scim/v2/Groups/$adminsGroupId" `
257+
-Method Patch -Headers @{ Authorization = "Bearer $token"; "Content-Type" = "application/scim+json" } `
258+
-Body $patchBody 2>$null | Out-Null
223259
Write-Host " Service Principal added to admins group." -ForegroundColor Green
224260
} catch {
225261
Write-Host " Warning: Could not add SP to admins group. Add it manually via Settings -> Identity and access -> Groups -> admins." -ForegroundColor Yellow

deploy.sh

Lines changed: 69 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -117,23 +117,28 @@ echo ""
117117
databricks bundle run "$APP_NAME" --profile "$profile"
118118

119119
# ── Get app metadata ─────────────────────────────────────────────────
120-
app_json=$(databricks apps get "$DEPLOYED_APP_NAME" --profile "$profile" -o json 2>/dev/null || echo "{}")
120+
echo ""
121+
echo " Fetching app metadata..."
122+
app_json=$(databricks apps get "$DEPLOYED_APP_NAME" --profile "$profile" -o json 2>&1) || { echo " Warning: Could not fetch app metadata."; app_json="{}"; }
121123
integration_id=$(echo "$app_json" | python3 -c "import sys,json; print(json.load(sys.stdin).get('oauth2_app_integration_id',''))" 2>/dev/null)
122124
sp_id=$(echo "$app_json" | python3 -c "import sys,json; print(json.load(sys.stdin).get('service_principal_id',''))" 2>/dev/null)
125+
sp_name=$(echo "$app_json" | python3 -c "import sys,json; print(json.load(sys.stdin).get('service_principal_name',''))" 2>/dev/null)
126+
echo " App integration ID: ${integration_id:-not found}"
127+
echo " Service Principal: ${sp_name:-unknown} (ID: ${sp_id:-not found})"
123128

124-
# ── Upload app thumbnail ─────────────────────────────────────────────
125-
if [[ -f "$SCREENSHOT" ]]; then
126-
echo ""
127-
echo " Uploading app thumbnail..."
128-
_token=$(databricks auth token --profile "$profile" 2>/dev/null \
129-
| python3 -c "import sys,json; print(json.load(sys.stdin).get('access_token',''))" 2>/dev/null)
130-
_ws_host=$(echo "$app_json" | python3 -c "import sys,json; d=json.load(sys.stdin); u=d.get('url',''); print(u.split('.')[0].replace('https://','').rsplit('-',1)[0] if u else '')" 2>/dev/null)
131-
# Derive workspace host from the profile
132-
_ws_host=$(python3 -c "
129+
# ── Get auth token + workspace host for API calls ────────────────────
130+
_token=$(databricks auth token --profile "$profile" 2>/dev/null \
131+
| python3 -c "import sys,json; print(json.load(sys.stdin).get('access_token',''))" 2>/dev/null)
132+
_ws_host=$(python3 -c "
133133
from databricks.sdk.core import Config
134134
c = Config(profile='${profile}')
135135
print((c.host or '').rstrip('/'))
136136
" 2>/dev/null)
137+
138+
# ── Upload app thumbnail ─────────────────────────────────────────────
139+
if [[ -f "$SCREENSHOT" ]]; then
140+
echo ""
141+
echo " Uploading app thumbnail..."
137142
if [[ -n "$_token" && -n "$_ws_host" ]]; then
138143
_b64=$(base64 -i "$SCREENSHOT")
139144
_resp=$(curl -s -X POST "${_ws_host}/api/2.0/apps/${DEPLOYED_APP_NAME}/thumbnail" \
@@ -144,6 +149,9 @@ print((c.host or '').rstrip('/'))
144149
else
145150
echo " Warning: Could not upload thumbnail (missing token or host)."
146151
fi
152+
else
153+
echo ""
154+
echo " Skipping thumbnail upload (${SCREENSHOT} not found)."
147155
fi
148156

149157
# ── Configure OBO scopes (requires an account-level profile) ──────────
@@ -173,7 +181,7 @@ else
173181
echo " OBO scopes not configured. Add them in the Databricks Apps UI → Configure → Add scope."
174182
else
175183
echo " Using account profile: ${account_profile}"
176-
echo " Integration ID: ${integration_id}"
184+
echo " Updating integration ${integration_id} with all-apis scope..."
177185

178186
redirect_urls=$(databricks account custom-app-integration get "$integration_id" \
179187
--profile "$account_profile" -o json 2>/dev/null \
@@ -182,40 +190,67 @@ else
182190
databricks account custom-app-integration update "$integration_id" \
183191
--profile "$account_profile" \
184192
--json "{\"scopes\": ${OBO_SCOPES}, \"redirect_urls\": ${redirect_urls:-"[]"}}" 2>/dev/null \
185-
&& echo " OBO scopes configured successfully: all-apis enabled." \
193+
&& echo " OBO scopes configured successfully." \
186194
|| echo " Warning: Could not update OBO scopes. Configure them manually in the Databricks Apps UI."
187195
fi
188196
fi
189197

190198
# ── Add Service Principal to admins group ─────────────────────────────
191-
if [[ -n "$sp_id" ]]; then
192-
# Check if the SP is already in the admins group
193-
admins_json=$(databricks groups get admins --profile "$profile" -o json 2>/dev/null || echo "{}")
194-
sp_in_admins=$(echo "$admins_json" | python3 -c "
199+
echo ""
200+
echo " Checking Service Principal admin group membership..."
201+
202+
if [[ -z "$sp_id" ]]; then
203+
echo " Warning: No Service Principal ID found — skipping admins group check."
204+
elif [[ -z "$_token" || -z "$_ws_host" ]]; then
205+
echo " Warning: No auth token or host — skipping admins group check."
206+
else
207+
# Use SCIM API to find the admins group and check membership
208+
admins_scim=$(curl -s "${_ws_host}/api/2.0/preview/scim/v2/Groups?filter=displayName+eq+%22admins%22" \
209+
-H "Authorization: Bearer ${_token}" 2>&1)
210+
211+
admins_group_id=$(echo "$admins_scim" | python3 -c "
195212
import sys, json
196-
data = json.load(sys.stdin)
197-
members = data.get('members', [])
213+
d = json.load(sys.stdin)
214+
groups = d.get('Resources', [])
215+
print(groups[0]['id'] if groups else '')
216+
" 2>/dev/null)
217+
218+
if [[ -z "$admins_group_id" ]]; then
219+
echo " Warning: Could not find admins group via SCIM API."
220+
else
221+
sp_in_admins=$(echo "$admins_scim" | python3 -c "
222+
import sys, json
223+
d = json.load(sys.stdin)
224+
members = d.get('Resources', [{}])[0].get('members', [])
198225
sp_id = '${sp_id}'
199226
print('yes' if any(str(m.get('value')) == sp_id for m in members) else 'no')
200227
" 2>/dev/null)
201228

202-
if [[ "$sp_in_admins" == "yes" ]]; then
203-
echo ""
204-
echo " App Service Principal (ID: ${sp_id}) is already in the admins group."
205-
else
206-
echo ""
207-
echo " The app's Service Principal (ID: ${sp_id}) is NOT in the admins group."
208-
echo " Adding it to admins allows the SP to access all workspace objects."
209-
printf " Add Service Principal to admins group? [y/N] "
210-
read -r add_to_admins
211-
212-
if [[ "$add_to_admins" =~ ^[Yy]$ ]]; then
213-
databricks groups patch admins --profile "$profile" \
214-
--json "{\"Operations\": [{\"op\": \"add\", \"value\": {\"members\": [{\"value\": \"${sp_id}\"}]}}], \"schemas\": [\"urn:ietf:params:scim:api:messages:2.0:PatchOp\"]}" 2>/dev/null \
215-
&& echo " Service Principal added to admins group." \
216-
|| echo " Warning: Could not add SP to admins group. Add it manually via Settings → Identity and access → Groups → admins."
229+
if [[ "$sp_in_admins" == "yes" ]]; then
230+
echo " ${sp_name} (ID: ${sp_id}) is already in the admins group."
217231
else
218-
echo " Skipped. The SP may not be able to access all workspace objects."
232+
echo " ${sp_name} (ID: ${sp_id}) is NOT in the admins group."
233+
echo " Adding it to admins allows the SP to access all workspace objects."
234+
printf " Add Service Principal to admins group? [y/N] "
235+
read -r add_to_admins
236+
237+
if [[ "$add_to_admins" =~ ^[Yy]$ ]]; then
238+
echo " Adding SP to admins group (SCIM group ID: ${admins_group_id})..."
239+
_patch_resp=$(curl -s -X PATCH "${_ws_host}/api/2.0/preview/scim/v2/Groups/${admins_group_id}" \
240+
-H "Authorization: Bearer ${_token}" \
241+
-H "Content-Type: application/scim+json" \
242+
-d "{\"Operations\": [{\"op\": \"add\", \"path\": \"members\", \"value\": [{\"value\": \"${sp_id}\"}]}], \"schemas\": [\"urn:ietf:params:scim:api:messages:2.0:PatchOp\"]}" 2>&1)
243+
# Check for error
244+
_err=$(echo "$_patch_resp" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('detail',''))" 2>/dev/null)
245+
if [[ -z "$_err" ]]; then
246+
echo " Service Principal added to admins group."
247+
else
248+
echo " Warning: Could not add SP to admins group: ${_err}"
249+
echo " Add it manually via Settings → Identity and access → Groups → admins."
250+
fi
251+
else
252+
echo " Skipped. The SP may not be able to access all workspace objects."
253+
fi
219254
fi
220255
fi
221256
fi

0 commit comments

Comments
 (0)