Skip to content
Closed
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
13 changes: 1 addition & 12 deletions internal/command/deploy.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,18 +58,7 @@ func (d *deployCommand) run(ctx context.Context, ns *docker.Namespace, cmd *cobr
AutoUpdate: true,
})

progress := func(p docker.DeployProgress) {
switch p.Stage {
case docker.DeployStageDownloading:
fmt.Printf("Downloading: %d%%\n", p.Percentage)
case docker.DeployStageStarting:
fmt.Println("Starting...")
case docker.DeployStageFinished:
fmt.Println("Finished")
}
}

if err := app.Deploy(ctx, progress); err != nil {
if err := app.Deploy(ctx, printDeployProgress); err != nil {
if cleanupErr := app.Destroy(context.Background(), true); cleanupErr != nil {
slog.Error("Failed to clean up after deploy failure", "app", name, "error", cleanupErr)
}
Expand Down
11 changes: 11 additions & 0 deletions internal/command/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,14 @@ func namespaceFlag(cmd *cobra.Command) string {
namespace, _ := cmd.Root().PersistentFlags().GetString("namespace")
return namespace
}

func printDeployProgress(p docker.DeployProgress) {
switch p.Stage {
case docker.DeployStageDownloading:
fmt.Printf("Downloading: %d%%\n", p.Percentage)
case docker.DeployStageStarting:
fmt.Println("Starting...")
case docker.DeployStageFinished:
fmt.Println("Finished")
}
}
38 changes: 34 additions & 4 deletions internal/command/update.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package command

import (
"context"
"fmt"

"github.com/spf13/cobra"

"github.com/basecamp/once/internal/docker"
"github.com/basecamp/once/internal/version"
)

Expand All @@ -13,12 +17,38 @@ type updateCommand struct {
func newUpdateCommand() *updateCommand {
u := &updateCommand{}
u.cmd = &cobra.Command{
Use: "update",
Short: "Update once to the latest version",
Args: cobra.NoArgs,
Use: "update [app]",
Short: "Update once to the latest version, or update a specific application",
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
return version.NewUpdater().UpdateBinary()
if len(args) == 0 {
return version.NewUpdater().UpdateBinary()
}
return WithNamespace(u.run)(cmd, args)
},
}
return u
}

// Private

func (u *updateCommand) run(ctx context.Context, ns *docker.Namespace, cmd *cobra.Command, args []string) error {
appName := args[0]

var changed bool
err := withApplication(ns, appName, "updating", func(app *docker.Application) error {
var err error
changed, err = app.Update(ctx, printDeployProgress)
return err
})
Comment on lines +38 to +43
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

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

This updates the app regardless of whether it's currently running. Since app.Update deploys a new container and re-registers with the proxy, once update <app> could unintentionally start an app that was previously stopped. If the intention is to match the background runner logic, add a guard (e.g., require app.Running or otherwise avoid deploying when the app is stopped) and return a clear message/error when the app isn't running.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

I don't think it's a problem, if you use this command I assume you plan to deploy the app

if err != nil {
return err
}

if changed {
fmt.Printf("Updated %s\n", appName)
} else {
fmt.Printf("%s is already up to date\n", appName)
}
return nil
}