diff --git a/cmd/arduino-app-cli/app/app.go b/cmd/arduino-app-cli/app/app.go index 3d6b6341..b1966106 100644 --- a/cmd/arduino-app-cli/app/app.go +++ b/cmd/arduino-app-cli/app/app.go @@ -40,6 +40,7 @@ func NewAppCmd(cfg config.Configuration) *cobra.Command { appCmd.AddCommand(newListCmd(cfg)) appCmd.AddCommand(newPsCmd()) appCmd.AddCommand(newMonitorCmd(cfg)) + appCmd.AddCommand(newCacheCleanCmd(cfg)) return appCmd } diff --git a/cmd/arduino-app-cli/app/clean.go b/cmd/arduino-app-cli/app/clean.go new file mode 100644 index 00000000..a7189205 --- /dev/null +++ b/cmd/arduino-app-cli/app/clean.go @@ -0,0 +1,68 @@ +package app + +import ( + "context" + "fmt" + + "github.com/spf13/cobra" + + "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" + "github.com/arduino/arduino-app-cli/internal/orchestrator/config" +) + +func newCacheCleanCmd(cfg config.Configuration) *cobra.Command { + var forceClean bool + appCmd := &cobra.Command{ + Use: "clean-cache", + Short: "Delete app cache", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) == 0 { + return cmd.Help() + } + app, err := Load(args[0]) + if err != nil { + return err + } + 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, 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{ + 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/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..ea623791 --- /dev/null +++ b/internal/orchestrator/cache.go @@ -0,0 +1,40 @@ +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, + 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() +}