Merge pull request #10327 from iamazeem/9897-gh-cache-delete-exit-code

[gh cache delete --all] Add `--succeed-on-no-caches` flag to return exit code 0
This commit is contained in:
Tyler McGoffin 2025-02-19 10:34:29 -08:00 committed by GitHub
commit 5aa36de302
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 100 additions and 5 deletions

View file

@ -22,8 +22,9 @@ type DeleteOptions struct {
HttpClient func() (*http.Client, error)
IO *iostreams.IOStreams
DeleteAll bool
Identifier string
DeleteAll bool
SucceedOnNoCaches bool
Identifier string
}
func NewCmdDelete(f *cmdutil.Factory, runF func(*DeleteOptions) error) *cobra.Command {
@ -33,7 +34,7 @@ func NewCmdDelete(f *cmdutil.Factory, runF func(*DeleteOptions) error) *cobra.Co
}
cmd := &cobra.Command{
Use: "delete [<cache-id>| <cache-key> | --all]",
Use: "delete [<cache-id> | <cache-key> | --all]",
Short: "Delete GitHub Actions caches",
Long: heredoc.Docf(`
Delete GitHub Actions caches.
@ -50,8 +51,11 @@ func NewCmdDelete(f *cmdutil.Factory, runF func(*DeleteOptions) error) *cobra.Co
# Delete a cache by id in a specific repo
$ gh cache delete 1234 --repo cli/cli
# Delete all caches
# Delete all caches (exit code 1 on no caches)
$ gh cache delete --all
# Delete all caches (exit code 0 on no caches)
$ gh cache delete --all --succeed-on-no-caches
`),
Args: cobra.MaximumNArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
@ -65,6 +69,10 @@ func NewCmdDelete(f *cmdutil.Factory, runF func(*DeleteOptions) error) *cobra.Co
return err
}
if !opts.DeleteAll && opts.SucceedOnNoCaches {
return cmdutil.FlagErrorf("--succeed-on-no-caches must be used in conjunction with --all")
}
if !opts.DeleteAll && len(args) == 0 {
return cmdutil.FlagErrorf("must provide either cache id, cache key, or use --all")
}
@ -82,6 +90,7 @@ func NewCmdDelete(f *cmdutil.Factory, runF func(*DeleteOptions) error) *cobra.Co
}
cmd.Flags().BoolVarP(&opts.DeleteAll, "all", "a", false, "Delete all caches")
cmd.Flags().BoolVar(&opts.SucceedOnNoCaches, "succeed-on-no-caches", false, "Return exit code 0 if no caches found. Must be used in conjunction with `--all`")
return cmd
}
@ -100,12 +109,21 @@ func deleteRun(opts *DeleteOptions) error {
var toDelete []string
if opts.DeleteAll {
opts.IO.StartProgressIndicator()
caches, err := shared.GetCaches(client, repo, shared.GetCachesOptions{Limit: -1})
opts.IO.StopProgressIndicator()
if err != nil {
return err
}
if len(caches.ActionsCaches) == 0 {
return fmt.Errorf("%s No caches to delete", opts.IO.ColorScheme().FailureIcon())
if opts.SucceedOnNoCaches {
if opts.IO.IsStdoutTTY() {
fmt.Fprintf(opts.IO.Out, "%s No caches to delete\n", opts.IO.ColorScheme().SuccessIcon())
}
return nil
} else {
return fmt.Errorf("%s No caches to delete", opts.IO.ColorScheme().FailureIcon())
}
}
for _, cache := range caches.ActionsCaches {
toDelete = append(toDelete, strconv.Itoa(cache.Id))

View file

@ -43,6 +43,21 @@ func TestNewCmdDelete(t *testing.T) {
cli: "--all",
wants: DeleteOptions{DeleteAll: true},
},
{
name: "delete all and succeed-on-no-caches flags",
cli: "--all --succeed-on-no-caches",
wants: DeleteOptions{DeleteAll: true, SucceedOnNoCaches: true},
},
{
name: "succeed-on-no-caches flag",
cli: "--succeed-on-no-caches",
wantsErr: "--succeed-on-no-caches must be used in conjunction with --all",
},
{
name: "succeed-on-no-caches flag and id argument",
cli: "--succeed-on-no-caches 123",
wantsErr: "--succeed-on-no-caches must be used in conjunction with --all",
},
{
name: "id argument and delete all flag",
cli: "1 --all",
@ -72,6 +87,7 @@ func TestNewCmdDelete(t *testing.T) {
}
assert.NoError(t, err)
assert.Equal(t, tt.wants.DeleteAll, gotOpts.DeleteAll)
assert.Equal(t, tt.wants.SucceedOnNoCaches, gotOpts.SucceedOnNoCaches)
assert.Equal(t, tt.wants.Identifier, gotOpts.Identifier)
})
}
@ -160,6 +176,19 @@ func TestDeleteRun(t *testing.T) {
tty: true,
wantStdout: "✓ Deleted 2 caches from OWNER/REPO\n",
},
{
name: "attempts to delete all caches but api errors",
opts: DeleteOptions{DeleteAll: true},
stubs: func(reg *httpmock.Registry) {
reg.Register(
httpmock.REST("GET", "repos/OWNER/REPO/actions/caches"),
httpmock.StatusStringResponse(500, ""),
)
},
tty: true,
wantErr: true,
wantErrMsg: "HTTP 500 (https://api.github.com/repos/OWNER/REPO/actions/caches?per_page=100)",
},
{
name: "displays delete error",
opts: DeleteOptions{Identifier: "123"},
@ -186,6 +215,54 @@ func TestDeleteRun(t *testing.T) {
tty: true,
wantStdout: "✓ Deleted 1 cache from OWNER/REPO\n",
},
{
name: "no caches to delete when deleting all",
opts: DeleteOptions{DeleteAll: true},
stubs: func(reg *httpmock.Registry) {
reg.Register(
httpmock.REST("GET", "repos/OWNER/REPO/actions/caches"),
httpmock.JSONResponse(shared.CachePayload{
ActionsCaches: []shared.Cache{},
TotalCount: 0,
}),
)
},
tty: false,
wantErr: true,
wantErrMsg: "X No caches to delete",
},
{
name: "no caches to delete when deleting all but succeed on no cache tty",
opts: DeleteOptions{DeleteAll: true, SucceedOnNoCaches: true},
stubs: func(reg *httpmock.Registry) {
reg.Register(
httpmock.REST("GET", "repos/OWNER/REPO/actions/caches"),
httpmock.JSONResponse(shared.CachePayload{
ActionsCaches: []shared.Cache{},
TotalCount: 0,
}),
)
},
tty: true,
wantErr: false,
wantStdout: "✓ No caches to delete\n",
},
{
name: "no caches to delete when deleting all but succeed on no cache non-tty",
opts: DeleteOptions{DeleteAll: true, SucceedOnNoCaches: true},
stubs: func(reg *httpmock.Registry) {
reg.Register(
httpmock.REST("GET", "repos/OWNER/REPO/actions/caches"),
httpmock.JSONResponse(shared.CachePayload{
ActionsCaches: []shared.Cache{},
TotalCount: 0,
}),
)
},
tty: false,
wantErr: false,
wantStdout: "",
},
}
for _, tt := range tests {