-
Notifications
You must be signed in to change notification settings - Fork 233
apps: route deploy to API mode on request flags and reject incompatible flags #6776
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,15 @@ | ||
| # 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 and API flags use direct deploy; incompatible flags fail | ||
| title "API deploy with an explicit app name" | ||
| trace $CLI apps deploy test-app --no-wait | ||
|
|
||
| title "API flags infer the app name from the bundle" | ||
| trace $CLI apps deploy --source-code-path /Workspace/apps/inferred --no-wait | ||
|
|
||
| title "API and bundle deploy flags cannot be mixed" | ||
| trace musterr $CLI apps deploy --source-code-path /Workspace/apps/inferred --force | ||
|
|
||
| title "API control flags do not select API mode" | ||
| trace musterr $CLI apps deploy --no-wait | ||
|
|
||
| trace print_requests.py //apps |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,6 +5,8 @@ import ( | |
| "errors" | ||
| "fmt" | ||
| "os" | ||
| "slices" | ||
| "strings" | ||
|
|
||
| "github.com/databricks/cli/bundle" | ||
| "github.com/databricks/cli/bundle/config" | ||
|
|
@@ -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. | ||
|
|
@@ -46,6 +49,18 @@ type bundleDeployOptions struct { | |
| skipTests bool | ||
| } | ||
|
|
||
| // changedFlagNames returns sorted command-line names for explicitly set flags. | ||
| func changedFlagNames(cmd *cobra.Command, names map[string]struct{}) []string { | ||
| var changed []string | ||
| for name := range names { | ||
| if cmd.Flags().Changed(name) { | ||
| changed = append(changed, "--"+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) { | ||
|
|
@@ -67,8 +82,30 @@ 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. | ||
| 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 | ||
| flagNames := func(flags *pflag.FlagSet) map[string]struct{} { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can we extract the full flag validation logic as a separate function, to keep the code easier to read? all the logic: including collecting the flags and validating them against the known flags |
||
| names := make(map[string]struct{}) | ||
| flags.VisitAll(func(flag *pflag.Flag) { | ||
| names[flag.Name] = struct{}{} | ||
| }) | ||
| return names | ||
| } | ||
| // Generated API flags and API-specific overrides, including the Git source | ||
| // override, are registered before the bundle override. | ||
| apiFlagNames := flagNames(deployCmd.Flags()) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This only works because |
||
| // Control flags must not bypass the bundle pipeline just because they are | ||
| // implemented by the generated API command. | ||
| requestFlagNames := map[string]struct{}{ | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is there a way to avoid hardcoding those flags here? Can't we use some existing references? If not, can we move this definition to the actual producer side, so that it's easier to keep the map up to date? |
||
| "deployment-id": {}, | ||
| "git-branch": {}, | ||
| "git-commit": {}, | ||
| "git-source-code-path": {}, | ||
| "git-tag": {}, | ||
| "json": {}, | ||
| "mode": {}, | ||
| "source-code-path": {}, | ||
| } | ||
|
|
||
| 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.") | ||
|
|
@@ -83,20 +120,65 @@ func BundleDeployOverrideWithWrapper(wrapError ErrorWrapper) func(*cobra.Command | |
| 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") | ||
| bundleFlagNames := flagNames(deployCmd.Flags()) | ||
| for name := range apiFlagNames { | ||
| delete(bundleFlagNames, name) | ||
| } | ||
| // --var is inherited from the apps command after this override runs. | ||
| bundleFlagNames["var"] = struct{}{} | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. My agent reported that the
Anyway, that's similar issue as in the hardcoded flag map above - it's an error prone approach and it might get out of sync easily. Can we do something about it? |
||
| validateFlags := func(cmd *cobra.Command, args []string) error { | ||
| apiFlags := changedFlagNames(cmd, apiFlagNames) | ||
| bundleFlags := changedFlagNames(cmd, bundleFlagNames) | ||
| requestFlags := changedFlagNames(cmd, requestFlagNames) | ||
| if len(apiFlags) > 0 && len(bundleFlags) > 0 { | ||
| return fmt.Errorf("API deploy flags %s cannot be combined with bundle deploy flags %s", strings.Join(apiFlags, ", "), strings.Join(bundleFlags, ", ")) | ||
| } | ||
| if len(args) > 0 && len(bundleFlags) > 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(bundleFlags, ", ")) | ||
| } | ||
| if len(args) == 0 && len(apiFlags) > 0 && len(requestFlags) == 0 { | ||
| return fmt.Errorf("API deploy flags %s do not select API mode; provide APP_NAME or an API request flag such as --source-code-path, or omit them to use bundle deploy", strings.Join(apiFlags, ", ")) | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| makeArgsOptionalWithBundle(deployCmd, "deploy [APP_NAME]") | ||
|
|
||
| originalPreRunE := deployCmd.PreRunE | ||
| deployCmd.PreRunE = func(cmd *cobra.Command, args []string) error { | ||
| if err := validateFlags(cmd, args); err != nil { | ||
| return err | ||
| } | ||
| if originalPreRunE != nil { | ||
| return originalPreRunE(cmd, args) | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| originalRunE := deployCmd.RunE | ||
| deployCmd.RunE = func(cmd *cobra.Command, args []string) error { | ||
| if len(args) == 0 { | ||
| if err := validateFlags(cmd, args); err != nil { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why is it run twice? |
||
| return err | ||
| } | ||
| requestFlags := changedFlagNames(cmd, requestFlagNames) | ||
|
|
||
| if len(args) == 0 && len(requestFlags) == 0 { | ||
| b := root.TryConfigureBundle(cmd) | ||
| if b != nil { | ||
| return runBundleDeploy(cmd, opts) | ||
| } | ||
| } | ||
|
|
||
| if len(args) == 0 { | ||
| appName, _, err := getAppNameFromArgs(cmd, args) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| args = []string{appName} | ||
| } | ||
|
|
||
| err := originalRunE(cmd, args) | ||
| return wrapError(cmd, deployReq.AppName, err) | ||
| return wrapError(cmd, args[0], err) | ||
| } | ||
|
|
||
| deployCmd.Long = `Create an app deployment. | ||
|
|
@@ -110,6 +192,11 @@ without an APP_NAME argument, this command runs an enhanced deployment pipeline: | |
| When an APP_NAME argument is provided (or when not in a project directory), | ||
| creates an app deployment using the API directly. | ||
|
|
||
| When an API request flag is provided without APP_NAME in a project directory, | ||
| the app name is inferred from databricks.yml and the API is used directly. | ||
| Control flags such as --no-wait and --timeout do not select API mode by themselves. | ||
| 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. | ||
|
|
@@ -124,6 +211,9 @@ Examples: | |
| # Deploy a specific app using the API (even from a project directory) | ||
| databricks apps deploy my-app | ||
|
|
||
| # Infer the app name and deploy a workspace source path using the API | ||
| databricks apps deploy --source-code-path /Workspace/Users/me/my-app --no-wait | ||
|
|
||
| # Deploy from project with validation skip | ||
| databricks apps deploy --skip-validation | ||
|
|
||
|
|
||
There was a problem hiding this comment.
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?
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.