Skip to content

feat(deployment): Run commands on deployments via quick actions and exec#3

Merged
nfebe merged 5 commits into
mainfrom
feat/deployment-quick-actions
Jun 15, 2026
Merged

feat(deployment): Run commands on deployments via quick actions and exec#3
nfebe merged 5 commits into
mainfrom
feat/deployment-quick-actions

Conversation

@nfebe

@nfebe nfebe commented Jun 15, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds CLI support for running operator commands against a deployment, which previously had to be done outside the CLI. The agent already exposed the quick-action and container-exec endpoints; this surfaces them.

Commands

  • flatrun deployment actions NAME — list a deployment's quick actions
  • flatrun deployment action NAME ACTION_ID — run a quick action (named, reusable command) in its service container
  • flatrun deployment exec NAME [SERVICE] -- COMMAND [ARGS...] — run an ad-hoc command in a service container
  • flatrun container exec CONTAINER_ID -- COMMAND [ARGS...] — run an ad-hoc command in a container by ID

Behavior

  • The command to run must follow --; the service is chosen positionally or with --service.
  • A single-service deployment resolves automatically; a multi-service deployment must be told which service to use rather than guessing.
  • On a non-zero exit, the command's captured output is printed and the CLI exits non-zero, so failures are diagnosable (not just the exit status).
  • Runs are non-interactive and honor the deployment's protected-mode rules.

Notes

  • Works for any image (bin/rails db:migrate, npx prisma migrate deploy, php artisan migrate, …).
  • Docs (docs/reference/commands.md) and CHANGELOG.md updated; VERSION bumped to 0.2.0.

Testing

gofmt, go vet, go build, and go test ./... all pass; new unit tests cover the client request shapes, service resolution (positional/flag/ambiguous), -- handling, and output-on-failure.

nfebe added 4 commits June 15, 2026 19:36
Operators previously had no way to run a command such as a database
migration or cache rebuild on a deployment from the CLI.

Two commands are added: one lists the quick actions defined on a
deployment, and one runs a chosen action inside its service container and
prints the command output. Actions remain subject to the deployment's
protected-mode rules. This lets continuous deployment pipelines run tasks
like migrations as an explicit step after shipping a release.
Quick actions cover named, reusable commands, but there was no way to run
a one-off command on a deployment without first defining it.

Adds an exec command that runs an arbitrary command in a deployment's
service container (selecting the service when there is more than one) and
prints its output, plus an equivalent that targets a container by ID. Both
run non-interactively and honor the deployment's protected-mode rules, so a
pipeline can run something like a database migration as a single step.
A failed command previously surfaced only its exit status (for example
"exit status 127"), hiding the container's own output and making failures
hard to diagnose.

The captured output is now printed alongside the error whenever an exec or
quick action exits non-zero, so the underlying message (a missing binary, a
migration error, a stack trace) is visible. The command still exits
non-zero so pipelines continue to fail.
Running exec on a deployment with more than one service silently picked
the first one, so a command meant for the app could land in an unrelated
container (for example a database) and fail confusingly.

Exec now resolves the service automatically only when a deployment has a
single running service; with more than one it lists them and stops until a
service is named. The service can be given positionally or with --service,
and the command to run must follow `--` so it is never mistaken for a
service or flag.
@sourceant

sourceant Bot commented Jun 15, 2026

Copy link
Copy Markdown

Code Review Summary

This PR introduces the ability to run quick actions and ad-hoc commands within deployment containers via the CLI. It adds deployment actions, deployment action, and exec commands for both deployments and specific containers.

🚀 Key Improvements

  • Surfaces existing agent functionality (quick-actions and exec) to the CLI.
  • Implemented a robust argument parser for handling the -- separator for ad-hoc commands.
  • Added logic to automatically resolve the target service in single-service deployments.
  • Enhanced error reporting to include container output on non-zero exit codes.

💡 Minor Suggestions

  • Ensure all internal functions in root.go receive and respect context.Context to allow for clean interruptions.

@sourceant sourceant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Review complete. See the overview comment for a summary.

Comment thread internal/command/root.go
return 2
}

data, err := client.GetDeployment(context.Background(), name)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

It is safer to use the context passed to the function rather than context.Background(), ensuring that cancellation signals (like SIGINT) are respected during the API call.

Suggested change
data, err := client.GetDeployment(context.Background(), name)
data, err := client.GetDeployment(ctx, name)

Comment thread internal/command/root.go
return execContainer(client, containerID, command, opts, stdout, stderr)
}

func execContainer(client *flatrun.Client, containerID string, command []string, opts globalOptions, stdout, stderr io.Writer) int {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Passing a context to the API request allows for proper timeout and cancellation management, which is critical for long-running 'exec' commands.

Suggested change
func execContainer(client *flatrun.Client, containerID string, command []string, opts globalOptions, stdout, stderr io.Writer) int {
func execContainer(ctx context.Context, client *flatrun.Client, containerID string, command []string, opts globalOptions, stdout, stderr io.Writer) int {
data, err := client.ContainerExec(ctx, containerID, flatrun.ExecRequest{

The README command overview did not mention the new ways to run commands
on a deployment, so add quick action and exec usage alongside the existing
deployment commands.

@sourceant sourceant Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Review complete. See the overview comment for a summary.

Comment thread internal/command/root.go
var parsed struct {
Output string `json:"output"`
}
if json.Unmarshal([]byte(apiErr.Body), &parsed) != nil {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

If json.Unmarshal fails, the error is swallowed and an empty string is returned. While this prevents a crash, it might be helpful to log a debug message or handle the error if the API structure is expected to be stable.

Suggested change
if json.Unmarshal([]byte(apiErr.Body), &parsed) != nil {
if err := json.Unmarshal([]byte(apiErr.Body), &parsed); err != nil {
return ""
}

@nfebe nfebe merged commit 1b0533a into main Jun 15, 2026
7 checks passed
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.

1 participant