From 6289a12717301db5393809c4a93a45be4bbb8014 Mon Sep 17 00:00:00 2001 From: Ilya Kuznetsov Date: Wed, 17 Jun 2026 18:05:00 +0000 Subject: [PATCH] Render deployment_id and version_id in bundle summary Co-authored-by: Isaac --- .../bundle/dms/plan-and-summary/output.txt | 25 ++++++++ acceptance/bundle/dms/plan-and-summary/script | 3 + bundle/render/render_text_output.go | 25 +++++++- bundle/render/render_text_output_test.go | 2 +- cmd/bundle/summary.go | 62 +++++++++++++++++-- 5 files changed, 111 insertions(+), 6 deletions(-) diff --git a/acceptance/bundle/dms/plan-and-summary/output.txt b/acceptance/bundle/dms/plan-and-summary/output.txt index dd6913e3795..f23c2bffcaa 100644 --- a/acceptance/bundle/dms/plan-and-summary/output.txt +++ b/acceptance/bundle/dms/plan-and-summary/output.txt @@ -15,12 +15,21 @@ Target: default Workspace: User: [USERNAME] Path: /Workspace/Users/[USERNAME]/.bundle/plan-summary-test/default +Deployment: + ID: [UUID] + Version: 1 Resources: Jobs: test_job: Name: test-job URL: [DATABRICKS_URL]/jobs/[NUMID]?w=[NUMID] +>>> [CLI] bundle summary -o json +{ + "deployment_id": "[UUID]", + "version_id": "1" +} + >>> print_requests.py --get //bundle ^//workspace-files ^//import-file { "method": "POST", @@ -103,3 +112,19 @@ Resources: }, "body": null } +{ + "method": "GET", + "path": "/api/2.0/bundle/deployments/[UUID]" +} +{ + "method": "GET", + "path": "/api/2.0/bundle/deployments/[UUID]/resources", + "q": { + "page_size": "1000" + }, + "body": null +} +{ + "method": "GET", + "path": "/api/2.0/bundle/deployments/[UUID]" +} diff --git a/acceptance/bundle/dms/plan-and-summary/script b/acceptance/bundle/dms/plan-and-summary/script index a662938cca9..f3e9be8bb46 100644 --- a/acceptance/bundle/dms/plan-and-summary/script +++ b/acceptance/bundle/dms/plan-and-summary/script @@ -7,6 +7,9 @@ trace $CLI bundle plan # Summary should show the deployment ID and read state from DMS. trace $CLI bundle summary +# Summary in JSON mode exposes the deployment metadata for programmatic use. +trace $CLI bundle summary -o json | jq .deployment + # Print metadata service requests from plan and summary. # Both should include ListResources calls. trace print_requests.py --get //bundle ^//workspace-files ^//import-file diff --git a/bundle/render/render_text_output.go b/bundle/render/render_text_output.go index b1f0c6442d1..ba44de2203b 100644 --- a/bundle/render/render_text_output.go +++ b/bundle/render/render_text_output.go @@ -45,6 +45,13 @@ const resourcesTemplate = `Resources: {{- end }} ` +const deploymentTemplate = `Deployment: + ID: {{ .DeploymentId | bold }} +{{- if .VersionId }} + Version: {{ .VersionId | bold }} +{{- end }} +` + type ResourceGroup struct { GroupName string Resources []ResourceInfo @@ -136,7 +143,7 @@ func RenderDiagnosticsSummary(ctx context.Context, out io.Writer, b *bundle.Bund return nil } -func RenderSummary(ctx context.Context, out io.Writer, b *bundle.Bundle) error { +func RenderSummary(ctx context.Context, out io.Writer, b *bundle.Bundle, deploymentID, versionID string) error { if b == nil { return nil } @@ -144,6 +151,12 @@ func RenderSummary(ctx context.Context, out io.Writer, b *bundle.Bundle) error { return err } + if deploymentID != "" { + if err := renderDeploymentTemplate(ctx, out, deploymentID, versionID); err != nil { + return fmt.Errorf("failed to render deployment template: %w", err) + } + } + var resourceGroups []ResourceGroup for _, group := range b.Config.Resources.AllResources() { @@ -171,6 +184,16 @@ func RenderSummary(ctx context.Context, out io.Writer, b *bundle.Bundle) error { return nil } +// renderDeploymentTemplate renders the bundle's deployment metadata service +// identifiers (deployment_id and the current version_id). +func renderDeploymentTemplate(ctx context.Context, out io.Writer, deploymentID, versionID string) error { + t := template.Must(template.New("deployment").Funcs(cmdio.RenderFuncMap(ctx)).Parse(deploymentTemplate)) + return t.Execute(out, map[string]any{ + "DeploymentId": deploymentID, + "VersionId": versionID, + }) +} + // Helper function to sort and render resource groups using the template func renderResourcesTemplate(ctx context.Context, out io.Writer, resourceGroups []ResourceGroup) error { // Sort everything to ensure consistent output diff --git a/bundle/render/render_text_output_test.go b/bundle/render/render_text_output_test.go index b797b423ad5..a1980563f46 100644 --- a/bundle/render/render_text_output_test.go +++ b/bundle/render/render_text_output_test.go @@ -340,7 +340,7 @@ func TestRenderSummary(t *testing.T) { } writer := &bytes.Buffer{} - err := RenderSummary(ctx, writer, b) + err := RenderSummary(ctx, writer, b, "", "") require.NoError(t, err) expectedSummary := `Name: test-bundle diff --git a/cmd/bundle/summary.go b/cmd/bundle/summary.go index b3a55a607cc..303118242bc 100644 --- a/cmd/bundle/summary.go +++ b/cmd/bundle/summary.go @@ -1,12 +1,17 @@ package bundle import ( + "context" + "encoding/json" + "fmt" + "github.com/databricks/cli/bundle" "github.com/databricks/cli/bundle/render" "github.com/databricks/cli/cmd/bundle/utils" "github.com/databricks/cli/cmd/root" "github.com/databricks/cli/libs/flags" "github.com/databricks/cli/libs/logdiag" + "github.com/databricks/cli/libs/tmpdms" "github.com/spf13/cobra" ) @@ -51,11 +56,60 @@ Useful after deployment to see what was created and where to find it.`, } func showSummary(cmd *cobra.Command, b *bundle.Bundle) error { - if root.OutputType(cmd) == flags.OutputText { - return render.RenderSummary(cmd.Context(), cmd.OutOrStdout(), b) + ctx := cmd.Context() + + // When the deployment metadata service is in use, surface the bundle's + // deployment_id and current version_id so callers (e.g. the workspace UI) + // can link to the deployment metadata. Both are empty otherwise. + versionID, err := currentDeploymentVersion(ctx, b) + if err != nil { + return err + } + + switch root.OutputType(cmd) { + case flags.OutputText: + return render.RenderSummary(ctx, cmd.OutOrStdout(), b, b.DeploymentID, versionID) + case flags.OutputJSON: + return renderSummaryJSON(cmd, b, versionID) + } + return nil +} + +// currentDeploymentVersion returns the latest version_id recorded for the +// bundle's deployment, or "" when the deployment metadata service is not in use. +func currentDeploymentVersion(ctx context.Context, b *bundle.Bundle) (string, error) { + if b.DeploymentID == "" { + return "", nil + } + svc, err := tmpdms.NewDeploymentMetadataAPI(b.WorkspaceClient(ctx)) + if err != nil { + return "", fmt.Errorf("failed to create metadata service client: %w", err) + } + dep, err := svc.GetDeployment(ctx, tmpdms.GetDeploymentRequest{DeploymentID: b.DeploymentID}) + if err != nil { + return "", fmt.Errorf("failed to get deployment: %w", err) + } + return dep.LastVersionID, nil +} + +// renderSummaryJSON marshals the bundle configuration and, when the deployment +// metadata service is in use, adds a top-level "deployment" object carrying the +// deployment_id and version_id. +func renderSummaryJSON(cmd *cobra.Command, b *bundle.Bundle, versionID string) error { + value := b.Config.Value().AsAny() + if b.DeploymentID != "" { + // The config root is always a mapping for a loaded bundle. + value.(map[string]any)["deployment"] = map[string]string{ + "deployment_id": b.DeploymentID, + "version_id": versionID, + } } - if root.OutputType(cmd) == flags.OutputJSON { - return renderJsonOutput(cmd, b) + buf, err := json.MarshalIndent(value, "", " ") + if err != nil { + return err } + out := cmd.OutOrStdout() + _, _ = out.Write(buf) + _, _ = out.Write([]byte{'\n'}) return nil }