-
Notifications
You must be signed in to change notification settings - Fork 180
AIR CLI Integration: Scaffold experimental AIR CLI command package #5564
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
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
|
|
||
| === run | ||
| >>> [CLI] experimental air run | ||
| Error: `air run` is not implemented yet | ||
|
|
||
| Exit code: 1 | ||
|
|
||
| === status | ||
| >>> [CLI] experimental air status 123 | ||
| Error: `air status` is not implemented yet | ||
|
|
||
| Exit code: 1 | ||
|
|
||
| === list | ||
| >>> [CLI] experimental air list | ||
| Error: `air list` is not implemented yet | ||
|
|
||
| Exit code: 1 | ||
|
|
||
| === logs | ||
| >>> [CLI] experimental air logs 123 | ||
| Error: `air logs` is not implemented yet | ||
|
|
||
| Exit code: 1 | ||
|
|
||
| === cancel | ||
| >>> [CLI] experimental air cancel 123 | ||
| Error: `air cancel` is not implemented yet | ||
|
|
||
| Exit code: 1 | ||
|
|
||
| === register-image | ||
| >>> [CLI] experimental air register-image my-image:latest | ||
| Error: `air register-image` is not implemented yet | ||
|
|
||
| Exit code: 1 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| # Each stub must fail with "not implemented"; errcode records the exit code. | ||
|
|
||
| title "run" | ||
| errcode trace $CLI experimental air run | ||
|
|
||
| title "status" | ||
| errcode trace $CLI experimental air status 123 | ||
|
|
||
| title "list" | ||
| errcode trace $CLI experimental air list | ||
|
|
||
| title "logs" | ||
| errcode trace $CLI experimental air logs 123 | ||
|
|
||
| title "cancel" | ||
| errcode trace $CLI experimental air cancel 123 | ||
|
|
||
| title "register-image" | ||
| errcode trace $CLI experimental air register-image my-image:latest | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| Local = true | ||
|
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.
|
||
| Cloud = false | ||
|
|
||
| # Stubs fail locally before any API call, so no server stubs needed. | ||
| [EnvMatrix] | ||
| DATABRICKS_BUNDLE_ENGINE = [] | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| package air | ||
|
|
||
| import ( | ||
| "fmt" | ||
|
|
||
| "github.com/spf13/cobra" | ||
| ) | ||
|
|
||
| // New returns the root command for the experimental AI runtime CLI. | ||
| // | ||
| // Milestone 0: scaffolds the command group with every subcommand registered as a | ||
| // stub (not yet implemented), pending the port from the Python `air` CLI. | ||
| func New() *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "air", | ||
| Short: "Run and manage AI runtime training workloads", | ||
| Long: `Run and manage AI runtime training workloads on Databricks serverless GPU compute. | ||
|
|
||
| This command set is the Go port of the standalone Python "air" CLI. It is | ||
| experimental and may change in future versions.`, | ||
| } | ||
|
|
||
| cmd.AddCommand(newRunCommand()) | ||
| cmd.AddCommand(newStatusCommand()) | ||
| cmd.AddCommand(newListCommand()) | ||
| cmd.AddCommand(newLogsCommand()) | ||
| cmd.AddCommand(newCancelCommand()) | ||
| cmd.AddCommand(newRegisterImageCommand()) | ||
|
|
||
| return cmd | ||
| } | ||
|
|
||
| // notImplemented returns the placeholder error used by milestone-0 stubs. | ||
| func notImplemented(name string) error { | ||
| return fmt.Errorf("`air %s` is not implemented yet", name) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| package air | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| "github.com/stretchr/testify/assert" | ||
| ) | ||
|
|
||
| // TestNewRegistersAllSubcommands asserts the `ai` command wires up every | ||
|
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. Typo: |
||
| // expected subcommand, so none is accidentally dropped from New. | ||
| func TestNewRegistersAllSubcommands(t *testing.T) { | ||
| registered := make(map[string]bool) | ||
| for _, c := range New().Commands() { | ||
| registered[c.Name()] = true | ||
| } | ||
|
|
||
| want := []string{"run", "status", "list", "logs", "cancel", "register-image"} | ||
| for _, name := range want { | ||
| assert.True(t, registered[name], "subcommand %q is not registered", name) | ||
| } | ||
| assert.Len(t, registered, len(want), "unexpected number of subcommands") | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| package air | ||
|
|
||
| import ( | ||
| "github.com/spf13/cobra" | ||
| ) | ||
|
|
||
| func newCancelCommand() *cobra.Command { | ||
| var ( | ||
| all bool | ||
| yes bool | ||
| ) | ||
|
|
||
| cmd := &cobra.Command{ | ||
| Use: "cancel [RUN_ID...]", | ||
| Args: cobra.ArbitraryArgs, | ||
|
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.
|
||
| Short: "Cancel one or more runs", | ||
| Long: `Cancel one or more runs by ID, or cancel all of your active runs with --all.`, | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| return notImplemented("cancel") | ||
| }, | ||
| } | ||
|
|
||
| cmd.Flags().BoolVar(&all, "all", false, "Cancel all of your active runs") | ||
| cmd.Flags().BoolVarP(&yes, "yes", "y", false, "Skip the confirmation prompt") | ||
|
|
||
| return cmd | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| package air | ||
|
|
||
| import ( | ||
| "github.com/databricks/cli/cmd/root" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
|
|
||
| func newListCommand() *cobra.Command { | ||
| var ( | ||
| limit int | ||
| active bool | ||
| allUsers bool | ||
| filters []string | ||
| ) | ||
|
|
||
| cmd := &cobra.Command{ | ||
| Use: "list", | ||
| Args: root.NoArgs, | ||
| Short: "List recent runs", | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| return notImplemented("list") | ||
| }, | ||
| } | ||
|
|
||
| cmd.Flags().IntVar(&limit, "limit", 20, "Maximum number of runs to show") | ||
| cmd.Flags().BoolVar(&active, "active", false, "Show only active runs") | ||
| cmd.Flags().BoolVar(&allUsers, "all-users", false, "Show runs from all users") | ||
| cmd.Flags().StringArrayVar(&filters, "filter", nil, "Filter runs, e.g. experiment=foo* (repeatable)") | ||
|
|
||
| return cmd | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| package air | ||
|
|
||
| import ( | ||
| "github.com/databricks/cli/cmd/root" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
|
|
||
| func newLogsCommand() *cobra.Command { | ||
| var ( | ||
| node int | ||
| lines int | ||
| retry int | ||
| downloadTo string | ||
| review bool | ||
| ) | ||
|
|
||
| cmd := &cobra.Command{ | ||
| Use: "logs RUN_ID", | ||
| Args: root.ExactArgs(1), | ||
| Short: "Stream or fetch logs for a run", | ||
| Long: `Stream logs from an active run, or fetch logs from a completed run.`, | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| return notImplemented("logs") | ||
| }, | ||
| } | ||
|
|
||
| cmd.Flags().IntVar(&node, "node", 0, "Fetch logs from this node") | ||
| cmd.Flags().IntVar(&lines, "lines", 10000, "For completed runs, print the last N lines") | ||
| cmd.Flags().IntVar(&retry, "retry", -1, "View logs from a specific retry attempt (default: latest)") | ||
|
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. pflag appends its own marker for non-zero defaults, so the help renders a double default (checked against a local build): Reword to fold the sentinel in, something like "View logs from a specific retry attempt; -1 means latest". |
||
| cmd.Flags().StringVar(&downloadTo, "download-to", "", "Download all logs to this directory instead of printing") | ||
| cmd.Flags().BoolVar(&review, "review", false, "Download logs from all nodes and filter for error signatures") | ||
|
|
||
| return cmd | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| package air | ||
|
|
||
| import ( | ||
| "github.com/databricks/cli/cmd/root" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
|
|
||
| func newRegisterImageCommand() *cobra.Command { | ||
| var ( | ||
| scope string | ||
| key string | ||
| interactiveAuth bool | ||
| tagPolicy string | ||
| timeoutMinutes int | ||
| ) | ||
|
|
||
| cmd := &cobra.Command{ | ||
| Use: "register-image IMAGE_URL", | ||
| Args: root.ExactArgs(1), | ||
| Short: "Mirror a Docker image into the workspace registry", | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| return notImplemented("register-image") | ||
| }, | ||
| } | ||
|
|
||
| cmd.Flags().StringVar(&scope, "scope", "", "Databricks secret scope holding registry credentials") | ||
| cmd.Flags().StringVar(&key, "key", "", "Databricks secret key holding registry credentials") | ||
| cmd.Flags().BoolVar(&interactiveAuth, "interactive-authenticate", false, "Prompt for registry credentials and store them as a secret") | ||
| cmd.Flags().StringVar(&tagPolicy, "tag-policy", "auto", "Image resolution policy: auto or latest") | ||
| cmd.Flags().IntVar(&timeoutMinutes, "timeout-minutes", 60, "Timeout to wait for the image to become available") | ||
|
|
||
| return cmd | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,36 @@ | ||
| package air | ||
|
|
||
| import ( | ||
| "github.com/databricks/cli/cmd/root" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
|
|
||
| func newRunCommand() *cobra.Command { | ||
| var ( | ||
| file string | ||
| watch bool | ||
| overrides []string | ||
| dryRun bool | ||
| idempotencyKey string | ||
| ) | ||
|
|
||
| cmd := &cobra.Command{ | ||
| Use: "run", | ||
| Args: root.NoArgs, | ||
| Short: "Submit a training workload from a YAML config", | ||
| Long: `Submit a training workload to Databricks serverless GPU compute. | ||
|
|
||
| The workload is described by a YAML config file (see --file).`, | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| return notImplemented("run") | ||
| }, | ||
| } | ||
|
|
||
| cmd.Flags().StringVarP(&file, "file", "f", "", "Path to the workload YAML config") | ||
| cmd.Flags().BoolVar(&watch, "watch", false, "Stream logs until the run completes") | ||
| cmd.Flags().StringArrayVar(&overrides, "override", nil, "Override a YAML field, e.g. compute.num_accelerators=8 (repeatable)") | ||
| cmd.Flags().BoolVar(&dryRun, "dry-run", false, "Validate the config without submitting") | ||
| cmd.Flags().StringVar(&idempotencyKey, "idempotency-key", "", "Return the existing run if this key was already used") | ||
|
|
||
| return cmd | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| package air | ||
|
|
||
| import ( | ||
| "github.com/databricks/cli/cmd/root" | ||
| "github.com/spf13/cobra" | ||
| ) | ||
|
|
||
| func newStatusCommand() *cobra.Command { | ||
| cmd := &cobra.Command{ | ||
| Use: "status RUN_ID", | ||
| Args: root.ExactArgs(1), | ||
| Short: "Show status and configuration for a run", | ||
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| return notImplemented("status") | ||
| }, | ||
| } | ||
|
|
||
| return cmd | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| package air | ||
|
|
||
| import ( | ||
| "testing" | ||
|
|
||
| "github.com/spf13/cobra" | ||
| "github.com/stretchr/testify/assert" | ||
| "github.com/stretchr/testify/require" | ||
| ) | ||
|
|
||
| // TestStubCommandsReturnNotImplemented asserts each unimplemented subcommand | ||
| // fails with a "not implemented" error. Drop a command here once it lands. | ||
| func TestStubCommandsReturnNotImplemented(t *testing.T) { | ||
| stubs := map[string]*cobra.Command{ | ||
| "run": newRunCommand(), | ||
| "status": newStatusCommand(), | ||
| "list": newListCommand(), | ||
| "logs": newLogsCommand(), | ||
| "cancel": newCancelCommand(), | ||
| "register-image": newRegisterImageCommand(), | ||
| } | ||
|
|
||
| for name, cmd := range stubs { | ||
| t.Run(name, func(t *testing.T) { | ||
| require.NotNil(t, cmd.RunE, "command should define RunE") | ||
| err := cmd.RunE(cmd, nil) | ||
| assert.ErrorContains(t, err, "not implemented") | ||
|
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 passes even if a stub returns the wrong name, i.e. if assert.EqualError(t, err, fmt.Sprintf("`air %s` is not implemented yet", name)) |
||
| }) | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| package experimental | ||
|
|
||
| import ( | ||
| "github.com/databricks/cli/cmd/experimental/air" | ||
|
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. The established layout for experimental features is One catch to take care of in the same PR: |
||
| aitoolscmd "github.com/databricks/cli/experimental/aitools/cmd" | ||
| geniecmd "github.com/databricks/cli/experimental/genie/cmd" | ||
| postgrescmd "github.com/databricks/cli/experimental/postgres/cmd" | ||
|
|
@@ -22,6 +23,7 @@ These commands provide early access to new features that are still under | |
| development. They may change or be removed in future versions without notice.`, | ||
| } | ||
|
|
||
| cmd.AddCommand(air.New()) | ||
| cmd.AddCommand(aitoolscmd.NewAitoolsCmd()) | ||
| cmd.AddCommand(geniecmd.NewGenieCmd()) | ||
| cmd.AddCommand(postgrescmd.New()) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,7 +20,7 @@ def main(): | |
| # files are silently missed until the file is staged or committed. | ||
| out = subprocess.check_output( | ||
| ["git", "grep", "--no-color", "-E", "^" + EMBED, "--", "*.go"], | ||
| text=True, | ||
| universal_newlines=True, | ||
|
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 change doesn't belong in this PR and the rationale doesn't hold up. This script isn't part of the acceptance harness, it's invoked by the Taskfile ( Please drop this file from the PR (one PR = one change). If |
||
| ) | ||
| paths = set() | ||
| for line in out.splitlines(): | ||
|
|
||
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.
The PR's stated goal is making the command tree reviewable, so let's pin it in the acceptance output. Add a help case and the tree, short descriptions and flags all land in
output.txt, then any future change shows up as a diff: