From 2ce24074d91200a1146fa9eb129560a1945c6923 Mon Sep 17 00:00:00 2001 From: Florent Date: Sun, 22 Mar 2026 23:03:31 +0100 Subject: [PATCH 1/2] Allow updating a specific app via once update --- internal/command/update.go | 51 +++++++++++++++++++++++++++++++++----- 1 file changed, 45 insertions(+), 6 deletions(-) diff --git a/internal/command/update.go b/internal/command/update.go index 8e1a187..c0aaac7 100644 --- a/internal/command/update.go +++ b/internal/command/update.go @@ -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" ) @@ -13,12 +17,47 @@ type updateCommand struct { func newUpdateCommand() *updateCommand { u := &updateCommand{} u.cmd = &cobra.Command{ - Use: "update", - Short: "Update once to the latest version", - Args: cobra.NoArgs, - RunE: func(cmd *cobra.Command, args []string) error { - return version.NewUpdater().UpdateBinary() - }, + Use: "update [app]", + Short: "Update once to the latest version, or update a specific application", + Args: cobra.MaximumNArgs(1), + RunE: WithNamespace(u.run), } return u } + +// Private + +func (u *updateCommand) run(ctx context.Context, ns *docker.Namespace, cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return version.NewUpdater().UpdateBinary() + } + + appName := args[0] + 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") + } + } + + var changed bool + err := withApplication(ns, appName, "updating", func(app *docker.Application) error { + var err error + changed, err = app.Update(ctx, progress) + return err + }) + 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 +} From 2ee4bada4e28a75a06cd703ec9d02ae22f65a500 Mon Sep 17 00:00:00 2001 From: Florent Date: Sun, 22 Mar 2026 23:43:11 +0100 Subject: [PATCH 2/2] Extract shared deploy progress helper and avoid unnecessary Docker init on self-update --- internal/command/deploy.go | 13 +------------ internal/command/root.go | 11 +++++++++++ internal/command/update.go | 23 +++++++---------------- 3 files changed, 19 insertions(+), 28 deletions(-) diff --git a/internal/command/deploy.go b/internal/command/deploy.go index baaaabd..b93b480 100644 --- a/internal/command/deploy.go +++ b/internal/command/deploy.go @@ -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) } diff --git a/internal/command/root.go b/internal/command/root.go index 44b1b1e..a05ef06 100644 --- a/internal/command/root.go +++ b/internal/command/root.go @@ -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") + } +} diff --git a/internal/command/update.go b/internal/command/update.go index c0aaac7..edbadd2 100644 --- a/internal/command/update.go +++ b/internal/command/update.go @@ -20,7 +20,12 @@ func newUpdateCommand() *updateCommand { Use: "update [app]", Short: "Update once to the latest version, or update a specific application", Args: cobra.MaximumNArgs(1), - RunE: WithNamespace(u.run), + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return version.NewUpdater().UpdateBinary() + } + return WithNamespace(u.run)(cmd, args) + }, } return u } @@ -28,26 +33,12 @@ func newUpdateCommand() *updateCommand { // Private func (u *updateCommand) run(ctx context.Context, ns *docker.Namespace, cmd *cobra.Command, args []string) error { - if len(args) == 0 { - return version.NewUpdater().UpdateBinary() - } - appName := args[0] - 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") - } - } var changed bool err := withApplication(ns, appName, "updating", func(app *docker.Application) error { var err error - changed, err = app.Update(ctx, progress) + changed, err = app.Update(ctx, printDeployProgress) return err }) if err != nil {