Skip to content

Commit 14aeb2a

Browse files
authored
Fix "bundle summary" to not lose warning messages (#3123)
## Changes - Merge rendering logic of "bundle validate" and "bundle summary" into one function. - Now "bundle summary" logs all warning diagnostics to stderr like "bundle validate" does. ## Why Not rendering warnings is a bug IMO. These are not just validation issues but could be real issues that are only related to summary and don't show up in "bundle validate". This is not the first time "bundle summary" differs from "bundle validate" (#2990) so in this PR I go beyond fixing and merge two code paths.
1 parent a68ea56 commit 14aeb2a

File tree

10 files changed

+139
-84
lines changed

10 files changed

+139
-84
lines changed

NEXT_CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@
1010
* Fixed an issue where running `databricks auth login` would remove the `cluster_id` field from profiles in `.databrickscfg`. The login process now preserves the `cluster_id` field. ([#2988](https://github.com/databricks/cli/pull/2988))
1111

1212
### Bundles
13+
- "bundle summary" now prints diagnostic warnings to stderr ([#3123](https://github.com/databricks/cli/pull/3123))
1314

1415
### API Changes

acceptance/bundle/deploy/mlops-stacks/output.txt

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,14 @@ This directory contains an ML project based on the default
2121
[Databricks MLOps Stacks](https://github.com/databricks/mlops-stacks),
2222

2323
>>> [CLI] bundle summary
24+
Warning: expected a string value, found null
25+
at targets.dev.workspace.host
26+
in databricks.yml:34:12
27+
28+
Warning: unknown field: description
29+
at resources.experiments.experiment
30+
in resources/ml-artifacts-resource.yml:21:7
31+
2432
Name: project_name_[UNIQUE_NAME]
2533
Target: dev
2634
Workspace:
@@ -75,14 +83,31 @@ Warning: unknown field: description
7583

7684

7785
>>> [CLI] bundle summary -o json
86+
Warning: expected a string value, found null
87+
at targets.dev.workspace.host
88+
in databricks.yml:34:12
89+
90+
Warning: unknown field: description
91+
at resources.experiments.experiment
92+
in resources/ml-artifacts-resource.yml:21:7
93+
7894
{
7995
"experiment_id": "[NUMID]",
8096
"model_id": "[dev [USERNAME]] dev-project_name_[UNIQUE_NAME]-model",
8197
"inference_job_id": "[NUMID]",
8298
"training_job_id": "[NUMID]"
8399
}
84100

85-
=== Assert the batch inference job actually exists{
101+
=== Assert the batch inference job actually exists
102+
Warning: expected a string value, found null
103+
at targets.dev.workspace.host
104+
in databricks.yml:34:12
105+
106+
Warning: unknown field: description
107+
at resources.experiments.experiment
108+
in resources/ml-artifacts-resource.yml:21:7
109+
110+
{
86111
"name": "[dev [USERNAME]] dev-project_name_[UNIQUE_NAME]-batch-inference-job"
87112
}
88113

acceptance/bundle/deploy/mlops-stacks/script

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,6 @@ trace $CLI bundle deploy
3131

3232
trace $CLI bundle summary -o json | jq -r '{experiment_id: .resources.experiments.experiment.id, model_id: .resources.models.model.id, inference_job_id: .resources.jobs.batch_inference_job.id, training_job_id: .resources.jobs.model_training_job.id}'
3333

34-
title "Assert the batch inference job actually exists"
34+
title "Assert the batch inference job actually exists\n"
3535
JOB_ID=$($CLI bundle summary -o json | jq -r '.resources.jobs.batch_inference_job.id')
3636
$CLI jobs get "${JOB_ID}" | jq '{name: .settings.name}'

acceptance/bundle/python/restricted-execution/output.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ value_from_env
1616
>>> musterr uv run [UV_ARGS] -q [CLI] bundle summary
1717
Error: Running Python code is not allowed when DATABRICKS_BUNDLE_RESTRICTED_CODE_EXECUTION is set
1818

19+
1920
Exit code (musterr): 1
2021

2122
>>> musterr cat envs.txt

acceptance/bundle/variables/host/output.txt

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,4 +50,17 @@ Workspace:
5050

5151
Found 1 error and 1 warning
5252

53+
Exit code: 1
54+
55+
>>> errcode [CLI] bundle summary
56+
Warning: Variable interpolation is not supported for fields that configure authentication
57+
at workspace.host
58+
in [TEST_TMP_DIR]/databricks.yml:10:9
59+
60+
Interpolation is not supported for the field workspace.host. Please set
61+
the DATABRICKS_HOST environment variable if you wish to configure this field at runtime.
62+
63+
Error: failed during request visitor: parse "https://${var.host}": invalid character "{" in host name
64+
65+
5366
Exit code: 1
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
trace errcode $CLI bundle validate -o json
22
trace errcode $CLI bundle validate
3+
trace errcode $CLI bundle summary
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[[Repls]]
2+
Old = '\\'
3+
New = '/'

bundle/render/render_text_output.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,9 @@ func RenderDiagnostics(out io.Writer, b *bundle.Bundle, diags diag.Diagnostics,
187187
}
188188

189189
func RenderSummary(ctx context.Context, out io.Writer, b *bundle.Bundle) error {
190+
if b == nil {
191+
panic("internal error: b must not be nil")
192+
}
190193
if err := renderSummaryHeaderTemplate(out, b); err != nil {
191194
return err
192195
}

cmd/bundle/summary.go

Lines changed: 43 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,18 @@
11
package bundle
22

33
import (
4-
"encoding/json"
54
"errors"
6-
"fmt"
75
"os"
86
"path/filepath"
97

108
"github.com/databricks/cli/bundle"
119
"github.com/databricks/cli/bundle/config/mutator"
1210
"github.com/databricks/cli/bundle/deploy/terraform"
1311
"github.com/databricks/cli/bundle/phases"
14-
"github.com/databricks/cli/bundle/render"
1512
"github.com/databricks/cli/bundle/statemgmt"
1613
"github.com/databricks/cli/cmd/bundle/utils"
1714
"github.com/databricks/cli/cmd/root"
18-
"github.com/databricks/cli/libs/flags"
15+
"github.com/databricks/cli/libs/diag"
1916
"github.com/spf13/cobra"
2017
)
2118

@@ -33,66 +30,57 @@ func newSummaryCommand() *cobra.Command {
3330
cmd.Flags().MarkHidden("include-locations")
3431

3532
cmd.RunE = func(cmd *cobra.Command, args []string) error {
36-
ctx := cmd.Context()
37-
b, diags := utils.ConfigureBundleWithVariables(cmd)
38-
if err := diags.Error(); err != nil {
39-
return diags.Error()
40-
}
41-
42-
diags = phases.Initialize(ctx, b)
43-
if err := diags.Error(); err != nil {
44-
return err
45-
}
33+
b, diags := prepareBundleForSummary(cmd, forcePull, includeLocations)
34+
return renderBundle(cmd, b, diags, true)
35+
}
4636

47-
cacheDir, err := terraform.Dir(ctx, b)
48-
if err != nil {
49-
return err
50-
}
51-
_, stateFileErr := os.Stat(filepath.Join(cacheDir, b.StateFilename()))
52-
_, configFileErr := os.Stat(filepath.Join(cacheDir, terraform.TerraformConfigFileName))
53-
noCache := errors.Is(stateFileErr, os.ErrNotExist) || errors.Is(configFileErr, os.ErrNotExist)
37+
return cmd
38+
}
5439

55-
if forcePull || noCache {
56-
diags = bundle.ApplySeq(ctx, b,
57-
statemgmt.StatePull(),
58-
terraform.Interpolate(),
59-
terraform.Write(),
60-
)
61-
if err := diags.Error(); err != nil {
62-
return err
63-
}
64-
}
40+
func prepareBundleForSummary(cmd *cobra.Command, forcePull, includeLocations bool) (*bundle.Bundle, diag.Diagnostics) {
41+
ctx := cmd.Context()
42+
b, diags := utils.ConfigureBundleWithVariables(cmd)
43+
if err := diags.Error(); err != nil {
44+
return nil, diags
45+
}
6546

66-
diags = bundle.ApplySeq(ctx, b,
67-
terraform.Load(),
68-
mutator.InitializeURLs(),
69-
)
47+
diags = diags.Extend(phases.Initialize(ctx, b))
48+
if err := diags.Error(); err != nil {
49+
return nil, diags
50+
}
7051

71-
// Include location information in the output if the flag is set.
72-
if includeLocations {
73-
diags = diags.Extend(bundle.Apply(ctx, b, mutator.PopulateLocations()))
74-
}
52+
cacheDir, err := terraform.Dir(ctx, b)
53+
if err != nil {
54+
return nil, diags
55+
}
56+
_, stateFileErr := os.Stat(filepath.Join(cacheDir, b.StateFilename()))
57+
_, configFileErr := os.Stat(filepath.Join(cacheDir, terraform.TerraformConfigFileName))
58+
noCache := errors.Is(stateFileErr, os.ErrNotExist) || errors.Is(configFileErr, os.ErrNotExist)
7559

60+
if forcePull || noCache {
61+
diags = diags.Extend(bundle.ApplySeq(ctx, b,
62+
statemgmt.StatePull(),
63+
terraform.Interpolate(),
64+
terraform.Write(),
65+
))
7666
if err := diags.Error(); err != nil {
77-
return err
67+
return nil, diags
7868
}
69+
}
7970

80-
switch root.OutputType(cmd) {
81-
case flags.OutputText:
82-
return render.RenderSummary(ctx, cmd.OutOrStdout(), b)
83-
case flags.OutputJSON:
84-
buf, err := json.MarshalIndent(b.Config.Value().AsAny(), "", " ")
85-
if err != nil {
86-
return err
87-
}
88-
_, _ = cmd.OutOrStdout().Write(buf)
89-
_, _ = cmd.OutOrStdout().Write([]byte{'\n'})
90-
default:
91-
return fmt.Errorf("unknown output type %s", root.OutputType(cmd))
92-
}
71+
diags = diags.Extend(bundle.ApplySeq(ctx, b,
72+
terraform.Load(),
73+
mutator.InitializeURLs(),
74+
))
9375

94-
return nil
76+
// Include location information in the output if the flag is set.
77+
if includeLocations {
78+
diags = diags.Extend(bundle.Apply(ctx, b, mutator.PopulateLocations()))
9579
}
9680

97-
return cmd
81+
if err := diags.Error(); err != nil {
82+
return nil, diags
83+
}
84+
85+
return b, diags
9886
}

cmd/bundle/validate.go

Lines changed: 47 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"github.com/databricks/cli/bundle/render"
1313
"github.com/databricks/cli/cmd/bundle/utils"
1414
"github.com/databricks/cli/cmd/root"
15+
"github.com/databricks/cli/libs/diag"
1516
"github.com/databricks/cli/libs/flags"
1617
"github.com/spf13/cobra"
1718
)
@@ -63,41 +64,60 @@ func newValidateCommand() *cobra.Command {
6364
diags = diags.Extend(bundle.Apply(ctx, b, mutator.PopulateLocations()))
6465
}
6566

66-
switch root.OutputType(cmd) {
67-
case flags.OutputText:
68-
renderOpts := render.RenderOptions{RenderSummaryTable: true}
69-
err := render.RenderDiagnostics(cmd.OutOrStdout(), b, diags, renderOpts)
70-
if err != nil {
71-
return fmt.Errorf("failed to render output: %w", err)
72-
}
73-
74-
if diags.HasError() {
75-
return root.ErrAlreadyPrinted
76-
}
67+
return renderBundle(cmd, b, diags, false)
68+
}
7769

78-
return nil
79-
case flags.OutputJSON:
80-
renderOpts := render.RenderOptions{RenderSummaryTable: false}
81-
err1 := render.RenderDiagnostics(cmd.ErrOrStderr(), b, diags, renderOpts)
82-
err2 := renderJsonOutput(cmd, b)
70+
return cmd
71+
}
8372

73+
// This function is used to render results both for "bundle validate" and "bundle summary".
74+
// In JSON mode, there is no difference in rendering between these two (but there is a difference in how we prepare the bundle).
75+
// In non-JSON mode both "bundle validate" and "bundle summary" will print diagnostics to stderr but "bundle validate"
76+
// will also print "summary" message via RenderSummaryTable option.
77+
func renderBundle(cmd *cobra.Command, b *bundle.Bundle, diags diag.Diagnostics, withBundleSummary bool) error {
78+
ctx := cmd.Context()
79+
switch root.OutputType(cmd) {
80+
case flags.OutputText:
81+
// Confusingly RenderSummaryTable relates to "Validation OK" and related messages, it has nothing
82+
// to do with "bundle summary" command and we don't want to show it in bundle summary command.
83+
renderOpts := render.RenderOptions{RenderSummaryTable: !withBundleSummary}
84+
err1 := render.RenderDiagnostics(cmd.OutOrStdout(), b, diags, renderOpts)
85+
if b != nil && withBundleSummary {
86+
// Now RenderSummary actually related to "bundle summary"
87+
err2 := render.RenderSummary(ctx, cmd.OutOrStdout(), b)
8488
if err2 != nil {
8589
return err2
8690
}
91+
}
8792

88-
if err1 != nil {
89-
return err1
90-
}
93+
if err1 != nil {
94+
return err1
95+
}
9196

92-
if diags.HasError() {
93-
return root.ErrAlreadyPrinted
94-
}
97+
if diags.HasError() {
98+
return root.ErrAlreadyPrinted
99+
}
100+
101+
return nil
102+
case flags.OutputJSON:
103+
renderOpts := render.RenderOptions{RenderSummaryTable: false}
104+
err1 := render.RenderDiagnostics(cmd.ErrOrStderr(), b, diags, renderOpts)
105+
err2 := renderJsonOutput(cmd, b)
95106

96-
return nil
97-
default:
98-
return fmt.Errorf("unknown output type %s", root.OutputType(cmd))
107+
if err2 != nil {
108+
return err2
99109
}
100-
}
101110

102-
return cmd
111+
if err1 != nil {
112+
return err1
113+
}
114+
115+
if diags.HasError() {
116+
return root.ErrAlreadyPrinted
117+
}
118+
119+
return nil
120+
default:
121+
return fmt.Errorf("unknown output type %s", root.OutputType(cmd))
122+
}
103123
}

0 commit comments

Comments
 (0)