Skip to content

apps: route deploy to API mode on request flags and reject incompatible flags - #6776

Draft
atilafassina wants to merge 2 commits into
mainfrom
deploy-fix
Draft

atilafassina wants to merge 2 commits into
mainfrom
deploy-fix

Conversation

@atilafassina

@atilafassina atilafassina commented Sep 21, 2026

Copy link
Copy Markdown
Contributor

apps deploy routing was purely positional: len(args) == 0 meant bundle deploy, anything else meant API deploy. This silently ignored API request flags like --source-code-path when no app name was given inside a bundle directory — running the full bundle pipeline instead.

This PR:

  • Routes to API deploy when an API request flag is set (inferring the app name from databricks.yml when no APP_NAME is given)
  • Rejects incompatible flag combinations early with actionable errors:
    • API request flags + bundle flags → error
    • APP_NAME + bundle flags → error
    • API control flags (--no-wait, --timeout) alone → error (they tune a call but don't select a mode)

Notes

  • requestFlagNames is an explicit allowlist distinguishing API request flags (which select API mode) from API control flags (which don't). New request fields added to the generated command must be added here.

atilafassina and others added 2 commits September 21, 2026 14:07
…le flags

`apps deploy` previously routed purely on whether an APP_NAME argument was
present: no arg meant bundle deploy, so an API request flag such as
`--source-code-path` given without an app name was silently ignored while the
bundle pipeline ran.

Route to API deploy whenever an API request flag is set (inferring the app
name from databricks.yml), and reject incompatible input early:
- API deploy flags cannot be combined with bundle deploy flags
- bundle deploy flags cannot be used together with an APP_NAME argument
- API control flags (--no-wait, --timeout) do not select API mode on their own

Add unit coverage for the rejected combinations and extend the
bundle-with-appname acceptance test.

Co-authored-by: Isaac <no-reply@databricks.com>
Git Bash on Windows converts leading-/ paths to Windows filesystem paths
before the CLI binary sees them, so --source-code-path /Workspace/apps/inferred
became C:/Program Files/Git/Workspace/apps/inferred. Set MSYS_NO_PATHCONV=1
to disable the conversion.

Co-authored-by: Isaac <no-reply@databricks.com>
@eng-dev-ecosystem-bot

eng-dev-ecosystem-bot commented Sep 21, 2026

Copy link
Copy Markdown
Collaborator

Integration test report

Commit: 7fa9510

Run: 35620236827

Env ✅​pass 🙈​skip Time
✅​ aws linux 276 16 5:10
✅​ aws windows 278 14 3:53
✅​ azure linux 275 16 4:40
✅​ azure windows 277 14 3:35
✅​ gcp linux 276 16 5:37
✅​ gcp windows 278 14 3:46
Top 3 slowest tests (at least 2 minutes):
duration env testname
3:32 azure windows TestAccept
3:26 aws windows TestAccept
3:05 gcp windows TestAccept

Comment thread cmd/apps/deploy_bundle.go
apiFlagNames := flagNames(deployCmd.Flags())
// Control flags must not bypass the bundle pipeline just because they are
// implemented by the generated API command.
requestFlagNames := map[string]struct{}{

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.

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?

Comment thread cmd/apps/deploy_bundle.go
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{} {

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.

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

Comment thread cmd/apps/deploy_bundle.go
delete(bundleFlagNames, name)
}
// --var is inherited from the apps command after this override runs.
bundleFlagNames["var"] = struct{}{}

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.

My agent reported that the --target is missing:

Verified facts:

  • --target / -t (and deprecated --environment / -e) is registered as a persistent flag on the root command (cmd/root/bundle.go:260, via initTargetFlag at root.go:52). Persistent-on-root = inherited by every subcommand, including apps deploy.
  • It's a bundle-only flag — it selects the bundle target and feeds TryConfigureBundle / configureBundle. Meaningless in the raw API path (no bundle there).

Why the PR misses it: the override builds apiFlagNames / bundleFlagNames by snapshotting deployCmd.Flags() at construction time. Inherited flags from root/parent aren't merged into that snapshot yet, so target is in neither set. changedFlagNames only loops over snapshot names, so --target is never checked. Result: apps deploy my-app --target prod → 0 api-flags, 0 bundle-flags → no "can't combine" error → routes to API deploy → --target silently ignored.

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?

Comment thread cmd/apps/deploy_bundle.go
originalRunE := deployCmd.RunE
deployCmd.RunE = func(cmd *cobra.Command, args []string) error {
if len(args) == 0 {
if err := validateFlags(cmd, args); err != nil {

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.

Why is it run twice?

Comment thread cmd/apps/deploy_bundle.go
}
// Generated API flags and API-specific overrides, including the Git source
// override, are registered before the bundle override.
apiFlagNames := flagNames(deployCmd.Flags())

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.

This only works because git_flags.go sorts before overrides.go, so its init() registers the git flags before this snapshot runs.
Can we avoid that? The easier way would be to use PreRunE/RunE where all the flags would be present.

Comment thread 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.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants