Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
102 changes: 102 additions & 0 deletions acceptance/apps/deploy/bundle-with-appname/output.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@

=== API deploy with an explicit app name
>>> [CLI] apps deploy test-app --no-wait
{
"deployment_id": "dep-123",
Expand All @@ -10,9 +11,110 @@
}
}

=== API deploy with a workspace source path
>>> [CLI] apps deploy test-app --source-code-path /Workspace/apps/test-app --no-wait
{
"deployment_id": "dep-123",
"mode": "SNAPSHOT",
"source_code_path": "/Workspace/apps/test-app",
"status": {
"message": "Deployment pending",
"state": "PENDING"
}
}

=== API deploy with a Git source
>>> [CLI] apps deploy test-app --git-branch release --no-wait
{
"deployment_id": "dep-123",
"mode": "SNAPSHOT",
"source_code_path": "/Workspace/apps/test-app",
"status": {
"message": "Deployment pending",
"state": "PENDING"
}
}

=== API and bundle deploy flags cannot be mixed
>>> musterr [CLI] apps deploy --source-code-path /Workspace/apps/test-app --force
Error: API deploy flags --source-code-path cannot be combined with bundle deploy flags --force

>>> musterr [CLI] apps deploy --git-branch release --skip-validation
Error: API deploy flags --git-branch cannot be combined with bundle deploy flags --skip-validation

>>> musterr [CLI] apps deploy test-app --no-wait --target prod
Error: API deploy flags --no-wait cannot be combined with bundle deploy flags --target

>>> musterr [CLI] apps deploy --no-wait --var app_name=my-app
Error: API deploy flags --no-wait cannot be combined with bundle deploy flags --var

=== Every API deploy flag requires APP_NAME
>>> musterr [CLI] apps deploy --no-wait
Error: API deploy flags --no-wait require APP_NAME; provide APP_NAME or omit these flags to use bundle deploy

>>> musterr [CLI] apps deploy --timeout=1m
Error: API deploy flags --timeout require APP_NAME; provide APP_NAME or omit these flags to use bundle deploy

>>> musterr [CLI] apps deploy --source-code-path=/Workspace/apps/test-app
Error: API deploy flags --source-code-path require APP_NAME; provide APP_NAME or omit these flags to use bundle deploy

>>> musterr [CLI] apps deploy --git-branch=release
Error: API deploy flags --git-branch require APP_NAME; provide APP_NAME or omit these flags to use bundle deploy

>>> musterr [CLI] apps deploy --git-tag=v1
Error: API deploy flags --git-tag require APP_NAME; provide APP_NAME or omit these flags to use bundle deploy

>>> musterr [CLI] apps deploy --git-commit=abc
Error: API deploy flags --git-commit require APP_NAME; provide APP_NAME or omit these flags to use bundle deploy

>>> musterr [CLI] apps deploy --git-source-code-path=app
Error: API deploy flags --git-source-code-path require APP_NAME; provide APP_NAME or omit these flags to use bundle deploy

>>> musterr [CLI] apps deploy --deployment-id=dep-123
Error: API deploy flags --deployment-id require APP_NAME; provide APP_NAME or omit these flags to use bundle deploy

>>> musterr [CLI] apps deploy --mode=SNAPSHOT
Error: API deploy flags --mode require APP_NAME; provide APP_NAME or omit these flags to use bundle deploy

>>> musterr [CLI] apps deploy --json={}
Error: API deploy flags --json require APP_NAME; provide APP_NAME or omit these flags to use bundle deploy

=== Bundle flags cannot be used with APP_NAME
>>> musterr [CLI] apps deploy test-app --force
Error: bundle deploy flags --force cannot be used when APP_NAME is provided; omit APP_NAME to use bundle deploy

>>> musterr [CLI] apps deploy test-app --target prod
Error: bundle deploy flags --target cannot be used when APP_NAME is provided; omit APP_NAME to use bundle deploy

>>> musterr [CLI] apps deploy test-app -t prod
Error: bundle deploy flags --target cannot be used when APP_NAME is provided; omit APP_NAME to use bundle deploy

>>> musterr [CLI] apps deploy test-app --environment prod
Flag --environment has been deprecated, use --target flag instead
Error: bundle deploy flags --environment cannot be used when APP_NAME is provided; omit APP_NAME to use bundle deploy

>>> musterr [CLI] apps deploy test-app --var app_name=my-app
Error: bundle deploy flags --var cannot be used when APP_NAME is provided; omit APP_NAME to use bundle deploy

>>> print_requests.py //apps
{
"method": "POST",
"path": "/api/2.0/apps/test-app/deployments",
"body": {}
}
{
"method": "POST",
"path": "/api/2.0/apps/test-app/deployments",
"body": {
"source_code_path": "/Workspace/apps/test-app"
}
}
{
"method": "POST",
"path": "/api/2.0/apps/test-app/deployments",
"body": {
"git_source": {
"branch": "release"
}
}
}
30 changes: 28 additions & 2 deletions acceptance/apps/deploy/bundle-with-appname/script
Original file line number Diff line number Diff line change
@@ -1,5 +1,31 @@
# Test: apps deploy with APP_NAME in a bundle directory
# Expected: Falls back to API deploy (ignores bundle, no validation)
# Test: apps deploy API routing in a bundle directory
# Expected: explicit names use direct deploy; incompatible flags fail
title "API deploy with an explicit app name"
trace $CLI apps deploy test-app --no-wait

title "API deploy with a workspace source path"
# Scope path conversion suppression to the CLI; Python helpers need it on Windows.
MSYS_NO_PATHCONV=1 trace $CLI apps deploy test-app --source-code-path /Workspace/apps/test-app --no-wait

title "API deploy with a Git source"
trace $CLI apps deploy test-app --git-branch release --no-wait

title "API and bundle deploy flags cannot be mixed"
MSYS_NO_PATHCONV=1 trace musterr $CLI apps deploy --source-code-path /Workspace/apps/test-app --force
trace musterr $CLI apps deploy --git-branch release --skip-validation
trace musterr $CLI apps deploy test-app --no-wait --target prod
trace musterr $CLI apps deploy --no-wait --var app_name=my-app

title "Every API deploy flag requires APP_NAME"
for flag in --no-wait --timeout=1m --source-code-path=/Workspace/apps/test-app --git-branch=release --git-tag=v1 --git-commit=abc --git-source-code-path=app --deployment-id=dep-123 --mode=SNAPSHOT --json='{}'; do
MSYS_NO_PATHCONV=1 trace musterr $CLI apps deploy "$flag"
done

title "Bundle flags cannot be used with APP_NAME"
trace musterr $CLI apps deploy test-app --force
trace musterr $CLI apps deploy test-app --target prod
trace musterr $CLI apps deploy test-app -t prod
trace musterr $CLI apps deploy test-app --environment prod
trace musterr $CLI apps deploy test-app --var app_name=my-app

trace print_requests.py //apps
7 changes: 7 additions & 0 deletions acceptance/apps/deploy/no-bundle-no-args/output.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,10 @@ Usage: databricks apps deploy APP_NAME
APP_NAME is the name of the Databricks app to operate on.
Alternatively, run this command from a project directory containing
databricks.yml to auto-detect the app name.

=== API deploy flags require APP_NAME outside a bundle too
>>> musterr [CLI] apps deploy --source-code-path /Workspace/apps/test-app
Error: API deploy flags --source-code-path require APP_NAME; provide APP_NAME or omit these flags to use bundle deploy

>>> musterr [CLI] apps deploy --no-wait
Error: API deploy flags --no-wait require APP_NAME; provide APP_NAME or omit these flags to use bundle deploy
4 changes: 4 additions & 0 deletions acceptance/apps/deploy/no-bundle-no-args/script
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# Test: apps deploy without databricks.yml and no APP_NAME
# Expected: Error about missing argument
musterr $CLI apps deploy

title "API deploy flags require APP_NAME outside a bundle too"
MSYS_NO_PATHCONV=1 trace musterr $CLI apps deploy --source-code-path /Workspace/apps/test-app
trace musterr $CLI apps deploy --no-wait
87 changes: 66 additions & 21 deletions cmd/apps/deploy_bundle.go

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

High-level comment: Can we make the logic a bit simpler?

  1. Route by app name only, error instead of infer. No app name → bundle. App name → API. API-only flag with no app name → error "provide APP_NAME". This deletes requestFlagNames entirely, deletes the request-vs-control distinction, and deletes the databricks.yml name-inference path (getAppNameFromArgs in the API branch). It also makes deploy match start/stop/delete.
  2. Detect bundle flags without a name list. API set = the local-flag snapshot (already automatic). Bundle set = the override's own flags (automatic) plus the inherited ones. For the inherited three, either reference the producer's definition or annotate them at registration.

What that removes: the requestFlagNames map, the control-flag special-casing, the double validateFlags call, and the app-name inference. What it keeps: the two genuinely useful errors ("can't combine bundle + API flags", "provide APP_NAME") — which are the part that satisfies the repo's "never silently ignore a flag" rule. Result: one mental model, no hardcoded flag lists, --target and the init-order trap fixed as a side effect.

Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import (
"errors"
"fmt"
"os"
"slices"
"strings"

"github.com/databricks/cli/bundle"
"github.com/databricks/cli/bundle/config"
Expand All @@ -18,6 +20,7 @@ import (
"github.com/databricks/cli/libs/log"
"github.com/databricks/databricks-sdk-go/service/apps"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)

// ErrorWrapper is a function type for wrapping deployment errors.
Expand Down Expand Up @@ -46,6 +49,18 @@ type bundleDeployOptions struct {
skipTests bool
}

// changedFlagNames returns sorted command-line names for explicitly set flags.
func changedFlagNames(flags *pflag.FlagSet) []string {
var changed []string
flags.VisitAll(func(flag *pflag.Flag) {
if flag.Changed {
changed = append(changed, "--"+flag.Name)
}
})
slices.Sort(changed)
return changed
}

// applyDeployFlags writes the deploy flag values onto the bundle config.
// Flags that override bundle YAML are only applied when explicitly set by the user.
func applyDeployFlags(cmd *cobra.Command, b *bundle.Bundle, opts bundleDeployOptions) {
Expand All @@ -65,38 +80,62 @@ func applyDeployFlags(cmd *cobra.Command, b *bundle.Bundle, opts bundleDeployOpt
}

// BundleDeployOverrideWithWrapper creates a deploy override function that uses
// the provided error wrapper for API fallback errors.
// the provided error wrapper for API errors.
func BundleDeployOverrideWithWrapper(wrapError ErrorWrapper) func(*cobra.Command, *apps.CreateAppDeploymentRequest) {
return func(deployCmd *cobra.Command, deployReq *apps.CreateAppDeploymentRequest) {
return func(deployCmd *cobra.Command, _ *apps.CreateAppDeploymentRequest) {
var opts bundleDeployOptions

deployCmd.Flags().BoolVar(&opts.force, "force", false, "Force-override Git branch validation.")
deployCmd.Flags().BoolVar(&opts.forceLock, "force-lock", false, "Force acquisition of deployment lock.")
deployCmd.Flags().BoolVar(&opts.failOnActiveRuns, "fail-on-active-runs", false, "Fail if there are running jobs or pipelines in the deployment.")
deployCmd.Flags().StringVar(&opts.clusterId, "compute-id", "", "Override cluster in the deployment with the given compute ID.")
deployCmd.Flags().StringVarP(&opts.clusterId, "cluster-id", "c", "", "Override cluster in the deployment with the given cluster ID.")
deployCmd.Flags().BoolVar(&opts.autoApprove, "auto-approve", false, "Skip interactive approvals that might be required for deployment.")
deployCmd.Flags().MarkDeprecated("compute-id", "use --cluster-id instead")
deployCmd.Flags().BoolVar(&opts.verbose, "verbose", false, "Enable verbose output.")
deployCmd.Flags().StringVar(&opts.readPlanPath, "plan", "", "Path to a JSON plan file to apply instead of planning (direct engine only).")
// Generated API flags and API-specific overrides, including the Git source
// override, are registered before the bundle override.
apiFlags := pflag.NewFlagSet("api", pflag.ContinueOnError)
apiFlags.AddFlagSet(deployCmd.LocalNonPersistentFlags())

bundleFlags := pflag.NewFlagSet("bundle", pflag.ContinueOnError)
bundleFlags.BoolVar(&opts.force, "force", false, "Force-override Git branch validation.")
bundleFlags.BoolVar(&opts.forceLock, "force-lock", false, "Force acquisition of deployment lock.")
bundleFlags.BoolVar(&opts.failOnActiveRuns, "fail-on-active-runs", false, "Fail if there are running jobs or pipelines in the deployment.")
bundleFlags.StringVar(&opts.clusterId, "compute-id", "", "Override cluster in the deployment with the given compute ID.")
bundleFlags.StringVarP(&opts.clusterId, "cluster-id", "c", "", "Override cluster in the deployment with the given cluster ID.")
bundleFlags.BoolVar(&opts.autoApprove, "auto-approve", false, "Skip interactive approvals that might be required for deployment.")
bundleFlags.MarkDeprecated("compute-id", "use --cluster-id instead")
bundleFlags.BoolVar(&opts.verbose, "verbose", false, "Enable verbose output.")
bundleFlags.StringVar(&opts.readPlanPath, "plan", "", "Path to a JSON plan file to apply instead of planning (direct engine only).")
// Verbose flag currently only affects file sync output, it's used by the vscode extension
deployCmd.Flags().MarkHidden("verbose")
deployCmd.Flags().BoolVar(&opts.skipValidation, "skip-validation", false, "Skip project validation (build, typecheck, lint)")
deployCmd.Flags().BoolVar(&opts.skipTests, "skip-tests", true, "Skip running tests during validation")
bundleFlags.MarkHidden("verbose")
bundleFlags.BoolVar(&opts.skipValidation, "skip-validation", false, "Skip project validation (build, typecheck, lint)")
bundleFlags.BoolVar(&opts.skipTests, "skip-tests", true, "Skip running tests during validation")
deployCmd.Flags().AddFlagSet(bundleFlags)

makeArgsOptionalWithBundle(deployCmd, "deploy [APP_NAME]")
originalArgs := deployCmd.Args
deployCmd.Args = func(cmd *cobra.Command, args []string) error {
// Parent flags are available only after the command tree is assembled.
cmd.InheritedFlags().VisitAll(func(flag *pflag.Flag) {
if _, ok := flag.Annotations[root.BundleFlagAnnotation]; ok {
bundleFlags.AddFlag(flag)
}
})
apiFlagNames := changedFlagNames(apiFlags)
bundleFlagNames := changedFlagNames(bundleFlags)
if len(apiFlagNames) > 0 && len(bundleFlagNames) > 0 {
return fmt.Errorf("API deploy flags %s cannot be combined with bundle deploy flags %s", strings.Join(apiFlagNames, ", "), strings.Join(bundleFlagNames, ", "))
}
if len(args) > 0 && len(bundleFlagNames) > 0 {
return fmt.Errorf("bundle deploy flags %s cannot be used when APP_NAME is provided; omit APP_NAME to use bundle deploy", strings.Join(bundleFlagNames, ", "))
}
if len(args) == 0 && len(apiFlagNames) > 0 {
return fmt.Errorf("API deploy flags %s require APP_NAME; provide APP_NAME or omit these flags to use bundle deploy", strings.Join(apiFlagNames, ", "))
}
return originalArgs(cmd, args)
}

originalRunE := deployCmd.RunE
deployCmd.RunE = func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
b := root.TryConfigureBundle(cmd)
if b != nil {
return runBundleDeploy(cmd, opts)
}
return runBundleDeploy(cmd, opts)
}

err := originalRunE(cmd, args)
return wrapError(cmd, deployReq.AppName, err)
return wrapError(cmd, args[0], err)
}

deployCmd.Long = `Create an app deployment.
Expand All @@ -107,9 +146,12 @@ without an APP_NAME argument, this command runs an enhanced deployment pipeline:
2. Deploys the project to the workspace
3. Runs the app

When an APP_NAME argument is provided (or when not in a project directory),
When an APP_NAME argument is provided,
creates an app deployment using the API directly.

API deploy flags, including --no-wait and --timeout, require APP_NAME.
API deploy flags cannot be combined with bundle deploy flags.

Arguments:
APP_NAME: The name of the app. Required when not in a project directory.
When provided in a project directory, uses API deploy instead of project deploy.
Expand All @@ -124,6 +166,9 @@ Examples:
# Deploy a specific app using the API (even from a project directory)
databricks apps deploy my-app

# Deploy a workspace source path using the API
databricks apps deploy my-app --source-code-path /Workspace/Users/me/my-app --no-wait

# Deploy from project with validation skip
databricks apps deploy --skip-validation

Expand Down
Loading
Loading