From 4f254213ccf00c4365f9c92c129c4a21534f52ea Mon Sep 17 00:00:00 2001 From: Alessio Perugini Date: Mon, 10 Nov 2025 20:42:34 +0100 Subject: [PATCH 1/2] feat: implement app cache deletion Introduce a `cache clean ` command to clean app cache. This implementation also tries to stop the related app if running. In case we do not care about that we can simply rm -rf `.cache/*`. Closes #52 --- cmd/arduino-app-cli/cache/cache.go | 18 +++++++++ cmd/arduino-app-cli/cache/clean.go | 59 ++++++++++++++++++++++++++++++ cmd/arduino-app-cli/main.go | 2 + go.mod | 5 ++- go.sum | 10 +++-- internal/orchestrator/cache.go | 17 +++++++++ 6 files changed, 105 insertions(+), 6 deletions(-) create mode 100644 cmd/arduino-app-cli/cache/cache.go create mode 100644 cmd/arduino-app-cli/cache/clean.go create mode 100644 internal/orchestrator/cache.go diff --git a/cmd/arduino-app-cli/cache/cache.go b/cmd/arduino-app-cli/cache/cache.go new file mode 100644 index 00000000..b1823498 --- /dev/null +++ b/cmd/arduino-app-cli/cache/cache.go @@ -0,0 +1,18 @@ +package cache + +import ( + "github.com/spf13/cobra" + + "github.com/arduino/arduino-app-cli/internal/orchestrator/config" +) + +func NewCacheCmd(cfg config.Configuration) *cobra.Command { + appCmd := &cobra.Command{ + Use: "cache", + Short: "Manage Arduino App cache", + } + + appCmd.AddCommand(newCacheCleanCmd(cfg)) + + return appCmd +} diff --git a/cmd/arduino-app-cli/cache/clean.go b/cmd/arduino-app-cli/cache/clean.go new file mode 100644 index 00000000..c30616ce --- /dev/null +++ b/cmd/arduino-app-cli/cache/clean.go @@ -0,0 +1,59 @@ +package cache + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" + + cmdApp "github.com/arduino/arduino-app-cli/cmd/arduino-app-cli/app" + "github.com/arduino/arduino-app-cli/cmd/arduino-app-cli/completion" + "github.com/arduino/arduino-app-cli/cmd/feedback" + "github.com/arduino/arduino-app-cli/internal/orchestrator" + "github.com/arduino/arduino-app-cli/internal/orchestrator/app" + "github.com/arduino/arduino-app-cli/internal/orchestrator/config" +) + +func newCacheCleanCmd(cfg config.Configuration) *cobra.Command { + appCmd := &cobra.Command{ + Use: "clean", + Short: "Delete app cache", + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return cmd.Help() + } + app, err := cmdApp.Load(args[0]) + if err != nil { + return err + } + return cacheCleanHandler(cmd.Context(), app) + }, + ValidArgsFunction: completion.ApplicationNames(cfg), + } + + return appCmd +} + +func cacheCleanHandler(ctx context.Context, app app.ArduinoApp) error { + if err := orchestrator.CleanAppCache(ctx, app); err != nil { + feedback.Fatal(err.Error(), feedback.ErrGeneric) + } + feedback.PrintResult(cacheCleanResult{ + AppName: app.Name, + Path: app.ProvisioningStateDir().String(), + }) + return nil +} + +type cacheCleanResult struct { + AppName string `json:"appName"` + Path string `json:"path"` +} + +func (r cacheCleanResult) String() string { + return fmt.Sprintf("✓ Cache of %q App cleaned", r.AppName) +} + +func (r cacheCleanResult) Data() interface{} { + return r +} diff --git a/cmd/arduino-app-cli/main.go b/cmd/arduino-app-cli/main.go index e859aae2..c84ee776 100644 --- a/cmd/arduino-app-cli/main.go +++ b/cmd/arduino-app-cli/main.go @@ -26,6 +26,7 @@ import ( "github.com/arduino/arduino-app-cli/cmd/arduino-app-cli/app" "github.com/arduino/arduino-app-cli/cmd/arduino-app-cli/brick" + "github.com/arduino/arduino-app-cli/cmd/arduino-app-cli/cache" "github.com/arduino/arduino-app-cli/cmd/arduino-app-cli/completion" "github.com/arduino/arduino-app-cli/cmd/arduino-app-cli/config" "github.com/arduino/arduino-app-cli/cmd/arduino-app-cli/daemon" @@ -78,6 +79,7 @@ func run(configuration cfg.Configuration) error { config.NewConfigCmd(configuration), system.NewSystemCmd(configuration), version.NewVersionCmd(Version), + cache.NewCacheCmd(configuration), ) ctx := context.Background() diff --git a/go.mod b/go.mod index be40aceb..083d8282 100644 --- a/go.mod +++ b/go.mod @@ -129,7 +129,7 @@ require ( github.com/fsnotify/fsnotify v1.9.0 // indirect github.com/fvbommel/sortorder v1.1.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect - github.com/getkin/kin-openapi v0.132.0 // indirect + github.com/getkin/kin-openapi v0.133.0 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.6.2 // indirect github.com/go-git/go-git/v5 v5.16.2 // indirect @@ -206,7 +206,7 @@ require ( github.com/morikuni/aec v1.0.0 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f // indirect - github.com/oapi-codegen/oapi-codegen/v2 v2.5.0 // indirect + github.com/oapi-codegen/oapi-codegen/v2 v2.5.1 // indirect github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 // indirect github.com/oasdiff/yaml3 v0.0.0-20250309153720-d2182401db90 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect @@ -257,6 +257,7 @@ require ( github.com/ulikunitz/xz v0.5.15 // indirect github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect github.com/vmware-labs/yaml-jsonpath v0.3.2 // indirect + github.com/woodsbury/decimal128 v1.3.0 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xanzy/ssh-agent v0.3.3 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect diff --git a/go.sum b/go.sum index 98b1009f..1f1f015e 100644 --- a/go.sum +++ b/go.sum @@ -310,8 +310,8 @@ github.com/fvbommel/sortorder v1.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQ github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= -github.com/getkin/kin-openapi v0.132.0 h1:3ISeLMsQzcb5v26yeJrBcdTCEQTag36ZjaGk7MIRUwk= -github.com/getkin/kin-openapi v0.132.0/go.mod h1:3OlG51PCYNsPByuiMB0t4fjnNlIDnaEDsjiKUV8nL58= +github.com/getkin/kin-openapi v0.133.0 h1:pJdmNohVIJ97r4AUFtEXRXwESr8b0bD721u/Tz6k8PQ= +github.com/getkin/kin-openapi v0.133.0/go.mod h1:boAciF6cXk5FhPqe/NQeBTeenbjqU4LhWBf09ILVvWE= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gliderlabs/ssh v0.3.8 h1:a4YXD1V7xMF9g5nTkdfnja3Sxy1PVDCj1Zg4Wb8vY6c= github.com/gliderlabs/ssh v0.3.8/go.mod h1:xYoytBv1sV0aL3CavoDuJIQNURXkkfPA/wxQ1pL1fAU= @@ -676,8 +676,8 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/oapi-codegen/oapi-codegen/v2 v2.5.0 h1:iJvF8SdB/3/+eGOXEpsWkD8FQAHj6mqkb6Fnsoc8MFU= -github.com/oapi-codegen/oapi-codegen/v2 v2.5.0/go.mod h1:fwlMxUEMuQK5ih9aymrxKPQqNm2n8bdLk1ppjH+lr9w= +github.com/oapi-codegen/oapi-codegen/v2 v2.5.1 h1:5vHNY1uuPBRBWqB2Dp0G7YB03phxLQZupZTIZaeorjc= +github.com/oapi-codegen/oapi-codegen/v2 v2.5.1/go.mod h1:ro0npU1BWkcGpCgGD9QwPp44l5OIZ94tB3eabnT7DjQ= github.com/oapi-codegen/runtime v1.1.1 h1:EXLHh0DXIJnWhdRPN2w4MXAzFyE4CskzhNLUmtpMYro= github.com/oapi-codegen/runtime v1.1.1/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg= github.com/oasdiff/yaml v0.0.0-20250309154309-f31be36b4037 h1:G7ERwszslrBzRxj//JalHPu/3yz+De2J+4aLtSRlHiY= @@ -909,6 +909,8 @@ github.com/warthog618/go-gpiocdev v0.9.1 h1:pwHPaqjJfhCipIQl78V+O3l9OKHivdRDdmgX github.com/warthog618/go-gpiocdev v0.9.1/go.mod h1:dN3e3t/S2aSNC+hgigGE/dBW8jE1ONk9bDSEYfoPyl8= github.com/warthog618/go-gpiosim v0.1.1 h1:MRAEv+T+itmw+3GeIGpQJBfanUVyg0l3JCTwHtwdre4= github.com/warthog618/go-gpiosim v0.1.1/go.mod h1:YXsnB+I9jdCMY4YAlMSRrlts25ltjmuIsrnoUrBLdqU= +github.com/woodsbury/decimal128 v1.3.0 h1:8pffMNWIlC0O5vbyHWFZAt5yWvWcrHA+3ovIIjVWss0= +github.com/woodsbury/decimal128 v1.3.0/go.mod h1:C5UTmyTjW3JftjUFzOVhC20BEQa2a4ZKOB5I6Zjb+ds= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= diff --git a/internal/orchestrator/cache.go b/internal/orchestrator/cache.go new file mode 100644 index 00000000..7bd4824b --- /dev/null +++ b/internal/orchestrator/cache.go @@ -0,0 +1,17 @@ +package orchestrator + +import ( + "context" + + "github.com/arduino/arduino-app-cli/internal/orchestrator/app" +) + +// CleanAppCache removes the `.cache` folder. If it detects that the app is running +// it tries to stop it first. +func CleanAppCache(ctx context.Context, app app.ArduinoApp) error { + if app.AppComposeFilePath().Exist() { + // We try to remove docker related resources at best effort + _ = StopAndDestroyApp(ctx, app) + } + return app.ProvisioningStateDir().RemoveAll() +} From ac57bdc7f57c5a53b8f4886bac236595a2e20692 Mon Sep 17 00:00:00 2001 From: Alessio Perugini Date: Tue, 11 Nov 2025 21:49:11 +0100 Subject: [PATCH 2/2] feature: add `--force` flag Add `--force` flag to forcefully terminate the app in case it's running, and then clean the related cache. --- cmd/arduino-app-cli/cache/clean.go | 15 ++++++++++++--- internal/orchestrator/cache.go | 27 +++++++++++++++++++++++++-- 2 files changed, 37 insertions(+), 5 deletions(-) diff --git a/cmd/arduino-app-cli/cache/clean.go b/cmd/arduino-app-cli/cache/clean.go index c30616ce..71be9fac 100644 --- a/cmd/arduino-app-cli/cache/clean.go +++ b/cmd/arduino-app-cli/cache/clean.go @@ -8,6 +8,7 @@ import ( cmdApp "github.com/arduino/arduino-app-cli/cmd/arduino-app-cli/app" "github.com/arduino/arduino-app-cli/cmd/arduino-app-cli/completion" + "github.com/arduino/arduino-app-cli/cmd/arduino-app-cli/internal/servicelocator" "github.com/arduino/arduino-app-cli/cmd/feedback" "github.com/arduino/arduino-app-cli/internal/orchestrator" "github.com/arduino/arduino-app-cli/internal/orchestrator/app" @@ -15,6 +16,7 @@ import ( ) func newCacheCleanCmd(cfg config.Configuration) *cobra.Command { + var forceClean bool appCmd := &cobra.Command{ Use: "clean", Short: "Delete app cache", @@ -26,16 +28,23 @@ func newCacheCleanCmd(cfg config.Configuration) *cobra.Command { if err != nil { return err } - return cacheCleanHandler(cmd.Context(), app) + return cacheCleanHandler(cmd.Context(), app, forceClean) }, ValidArgsFunction: completion.ApplicationNames(cfg), } + appCmd.Flags().BoolVarP(&forceClean, "force", "", false, "Forcefully clean the cache even if the app is running") return appCmd } -func cacheCleanHandler(ctx context.Context, app app.ArduinoApp) error { - if err := orchestrator.CleanAppCache(ctx, app); err != nil { +func cacheCleanHandler(ctx context.Context, app app.ArduinoApp, forceClean bool) error { + err := orchestrator.CleanAppCache( + ctx, + servicelocator.GetDockerClient(), + app, + orchestrator.CleanAppCacheRequest{ForceClean: forceClean}, + ) + if err != nil { feedback.Fatal(err.Error(), feedback.ErrGeneric) } feedback.PrintResult(cacheCleanResult{ diff --git a/internal/orchestrator/cache.go b/internal/orchestrator/cache.go index 7bd4824b..ea623791 100644 --- a/internal/orchestrator/cache.go +++ b/internal/orchestrator/cache.go @@ -2,16 +2,39 @@ package orchestrator import ( "context" + "errors" + + "github.com/docker/cli/cli/command" "github.com/arduino/arduino-app-cli/internal/orchestrator/app" ) +type CleanAppCacheRequest struct { + ForceClean bool +} + +var ErrCleanCacheRunningApp = errors.New("cannot remove cache of a running app") + // CleanAppCache removes the `.cache` folder. If it detects that the app is running // it tries to stop it first. -func CleanAppCache(ctx context.Context, app app.ArduinoApp) error { - if app.AppComposeFilePath().Exist() { +func CleanAppCache( + ctx context.Context, + docker command.Cli, + app app.ArduinoApp, + req CleanAppCacheRequest, +) error { + runningApp, err := getRunningApp(ctx, docker.Client()) + if err != nil { + return err + } + if runningApp.FullPath.EqualsTo(app.FullPath) { + return ErrCleanCacheRunningApp + } + + if req.ForceClean && app.AppComposeFilePath().Exist() { // We try to remove docker related resources at best effort _ = StopAndDestroyApp(ctx, app) } + return app.ProvisioningStateDir().RemoveAll() }