From 6b38339708afed2ddcf3b063367ccff90236f387 Mon Sep 17 00:00:00 2001 From: Mike Sul Date: Mon, 16 Mar 2026 12:38:59 +0100 Subject: [PATCH 1/4] fix(pull): fix import ordering Signed-off-by: Mike Sul --- cmd/composectl/cmd/pull.go | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/cmd/composectl/cmd/pull.go b/cmd/composectl/cmd/pull.go index d5c89ca..ef33bee 100644 --- a/cmd/composectl/cmd/pull.go +++ b/cmd/composectl/cmd/pull.go @@ -3,14 +3,15 @@ package composectl import ( "encoding/json" "fmt" + "os" + "sync/atomic" + "time" + "github.com/containerd/containerd/platforms" "github.com/foundriesio/composeapp/pkg/compose" v1 "github.com/foundriesio/composeapp/pkg/compose/v1" "github.com/moby/term" "github.com/spf13/cobra" - "os" - "sync/atomic" - "time" ) var ( From 77f0737e4912b1989f203d5391609b8ce36fafbf Mon Sep 17 00:00:00 2001 From: Mike Sul Date: Mon, 16 Mar 2026 12:58:44 +0100 Subject: [PATCH 2/4] refact(pull): use private structure for parameters Signed-off-by: Mike Sul --- cmd/composectl/cmd/pull.go | 36 +++++++++++++++++++++++------------- 1 file changed, 23 insertions(+), 13 deletions(-) diff --git a/cmd/composectl/cmd/pull.go b/cmd/composectl/cmd/pull.go index ef33bee..b7814e5 100644 --- a/cmd/composectl/cmd/pull.go +++ b/cmd/composectl/cmd/pull.go @@ -14,10 +14,15 @@ import ( "github.com/spf13/cobra" ) +type ( + pullOptions struct { + UsageWatermark uint + SrcStorePath string + PrintUsageStat bool + } +) + var ( - pullUsageWatermark *uint - pullSrcStorePath *string - pullPrintUsageStat *bool exitCodeInsufficientSpace int = 100 ) @@ -27,31 +32,36 @@ func init() { Short: "pull []", Long: ``, Args: cobra.MinimumNArgs(1), - Run: pullApps, } + opts := pullOptions{} + + pullCmd.Flags().UintVarP(&opts.UsageWatermark, "storage-usage-watermark", "u", 80, "The maximum allowed storage usage in percentage") + pullCmd.Flags().StringVarP(&opts.SrcStorePath, "source-store-path", "l", "", "A path to the source store root directory") + pullCmd.Flags().BoolVarP(&opts.PrintUsageStat, "print-usage-stat", "p", false, "A flag to enable/disable usage statistic output to stderr") + pullCmd.Run = func(cmd *cobra.Command, args []string) { + pullApps(cmd, args, &opts) + } + rootCmd.AddCommand(pullCmd) - pullUsageWatermark = pullCmd.Flags().UintP("storage-usage-watermark", "u", 80, "The maximum allowed storage usage in percentage") - pullSrcStorePath = pullCmd.Flags().StringP("source-store-path", "l", "", "A path to the source store root directory") - pullPrintUsageStat = pullCmd.Flags().BoolP("print-usage-stat", "p", false, "A flag to enable/disable usage statistic output to stderr") } -func pullApps(cmd *cobra.Command, args []string) { +func pullApps(cmd *cobra.Command, args []string, opts *pullOptions) { if len(args) > 1 { fmt.Printf("Pulling %d apps to %s\n", len(args), config.StoreRoot) } else { fmt.Printf("Pulling %s to %s\n", args[0], config.StoreRoot) } - srcBlobProvider, cs, err := getAppStoreAndDstBlobProvider(*pullSrcStorePath, false) + srcBlobProvider, cs, err := getAppStoreAndDstBlobProvider(opts.SrcStorePath, false) DieNotNil(err) - cr, ui, apps, err := checkApps(cmd.Context(), args, srcBlobProvider, *pullUsageWatermark, - *pullSrcStorePath, false, false) + cr, ui, apps, err := checkApps(cmd.Context(), args, srcBlobProvider, opts.UsageWatermark, + opts.SrcStorePath, false, false) DieNotNil(err, "failed to check apps status") if len(cr.MissingBlobs) > 0 { ui.Print() if ui.Required > ui.Available { - if *pullPrintUsageStat { + if opts.PrintUsageStat { if b, err := json.Marshal(ui); err == nil { fmt.Fprintln(os.Stderr, string(b)) } @@ -64,7 +74,7 @@ func pullApps(cmd *cobra.Command, args []string) { err := compose.FetchBlobs(cmd.Context(), config, cr.MissingBlobs, compose.WithProgressPollInterval(1000), compose.WithFetchProgress(getFetchProgressHandler()), - compose.WithSourcePath(*pullSrcStorePath)) + compose.WithSourcePath(opts.SrcStorePath)) DieNotNil(err, "failed to fetch blobs") fmt.Println("\n\nApp blobs pull completed at " + time.Now().UTC().Format("15:04:05 02 Jan 2006")) } From 52c2261b5b3b1aa9d8982303b6699a8d85ca8b56 Mon Sep 17 00:00:00 2001 From: Mike Sul Date: Mon, 16 Mar 2026 13:08:10 +0100 Subject: [PATCH 3/4] feat(pull): add flag for a quick check of blobs Add a new flag to enable/disable quick check of app blobs present on a local host. By default, the quick check is disabled and blob hashes are verified. If enabled, then, only blobs presence and their sizes are checked, no hashes are verified. This flag makes the `pull` command consistent with the `check` command - check blob hashes by default, and allow a quick check through a CLI flag "--quick". Signed-off-by: Mike Sul --- cmd/composectl/cmd/pull.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cmd/composectl/cmd/pull.go b/cmd/composectl/cmd/pull.go index b7814e5..9e5f41a 100644 --- a/cmd/composectl/cmd/pull.go +++ b/cmd/composectl/cmd/pull.go @@ -19,6 +19,7 @@ type ( UsageWatermark uint SrcStorePath string PrintUsageStat bool + Quick bool } ) @@ -38,6 +39,7 @@ func init() { pullCmd.Flags().UintVarP(&opts.UsageWatermark, "storage-usage-watermark", "u", 80, "The maximum allowed storage usage in percentage") pullCmd.Flags().StringVarP(&opts.SrcStorePath, "source-store-path", "l", "", "A path to the source store root directory") pullCmd.Flags().BoolVarP(&opts.PrintUsageStat, "print-usage-stat", "p", false, "A flag to enable/disable usage statistic output to stderr") + pullCmd.Flags().BoolVar(&opts.Quick, "quick", false, "Skip checking hash of app blobs; verify only their presence and size") pullCmd.Run = func(cmd *cobra.Command, args []string) { pullApps(cmd, args, &opts) } @@ -56,7 +58,7 @@ func pullApps(cmd *cobra.Command, args []string, opts *pullOptions) { DieNotNil(err) cr, ui, apps, err := checkApps(cmd.Context(), args, srcBlobProvider, opts.UsageWatermark, - opts.SrcStorePath, false, false) + opts.SrcStorePath, false, opts.Quick) DieNotNil(err, "failed to check apps status") if len(cr.MissingBlobs) > 0 { ui.Print() From 1a9676022d1e18a2dea8494a9365b9798f02b6ab Mon Sep 17 00:00:00 2001 From: Mike Sul Date: Mon, 16 Mar 2026 14:38:24 +0100 Subject: [PATCH 4/4] refact(check): check watermark value Check if the specified watermark value is in the allowed range [20,99] in the `check` and `pull` commands. Set the min and max allowed values to 20 and 99, respectively; the same values we set in fioup. Signed-off-by: Mike Sul --- cmd/composectl/cmd/check.go | 17 +++++++++++++---- cmd/composectl/cmd/pull.go | 4 +++- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/cmd/composectl/cmd/check.go b/cmd/composectl/cmd/check.go index 42d9753..76eede9 100644 --- a/cmd/composectl/cmd/check.go +++ b/cmd/composectl/cmd/check.go @@ -46,8 +46,9 @@ type ( ) const ( - MinUsageWatermark = 20 - MaxUsageWatermark = 95 + MinUsageWatermark = 20 + MaxUsageWatermark = 99 + DefaultUsageWatermark = 95 ) func init() { @@ -58,8 +59,8 @@ func init() { Args: cobra.MinimumNArgs(1), } opts := checkOptions{} - opts.UsageWatermark = checkCmd.Flags().UintP("storage-usage-watermark", "u", 80, - "The maximum allowed storage usage in percentage") + opts.UsageWatermark = checkCmd.Flags().UintP("storage-usage-watermark", "u", DefaultUsageWatermark, + fmt.Sprintf("The maximum allowed storage usage in percentage in range %d-%d", MinUsageWatermark, MaxUsageWatermark)) opts.SrcStorePath = checkCmd.Flags().StringP("source-store-path", "l", "", "A path to the source store root directory") opts.Locally = checkCmd.Flags().BoolP("local", "", false, @@ -76,12 +77,20 @@ func init() { fmt.Fprintf(os.Stderr, "unsupported `--format` value: %s\n", opts.Format) os.Exit(1) } + checkWatermark(*opts.UsageWatermark) checkAppsCmd(cmd, args, &opts) } rootCmd.AddCommand(checkCmd) } +func checkWatermark(watermark uint) { + if watermark < MinUsageWatermark || watermark > MaxUsageWatermark { + DieNotNilWithCode(fmt.Errorf("invalid `--storage-usage-watermark` value: %d; should be between %d and %d", + watermark, MinUsageWatermark, MaxUsageWatermark), 1, "invalid argument") + } +} + func checkAppsCmd(cmd *cobra.Command, args []string, opts *checkOptions) { var quietCheck bool if opts.Format == "json" { diff --git a/cmd/composectl/cmd/pull.go b/cmd/composectl/cmd/pull.go index 9e5f41a..5df6b6b 100644 --- a/cmd/composectl/cmd/pull.go +++ b/cmd/composectl/cmd/pull.go @@ -36,11 +36,13 @@ func init() { } opts := pullOptions{} - pullCmd.Flags().UintVarP(&opts.UsageWatermark, "storage-usage-watermark", "u", 80, "The maximum allowed storage usage in percentage") + pullCmd.Flags().UintVarP(&opts.UsageWatermark, "storage-usage-watermark", "u", DefaultUsageWatermark, + fmt.Sprintf("The maximum allowed storage usage in percentage in range %d-%d", MinUsageWatermark, MaxUsageWatermark)) pullCmd.Flags().StringVarP(&opts.SrcStorePath, "source-store-path", "l", "", "A path to the source store root directory") pullCmd.Flags().BoolVarP(&opts.PrintUsageStat, "print-usage-stat", "p", false, "A flag to enable/disable usage statistic output to stderr") pullCmd.Flags().BoolVar(&opts.Quick, "quick", false, "Skip checking hash of app blobs; verify only their presence and size") pullCmd.Run = func(cmd *cobra.Command, args []string) { + checkWatermark(opts.UsageWatermark) pullApps(cmd, args, &opts) }