#4258 Add sub command to delete asset from release (#4416)

* #4258 Add sub command to delete asset from release

* Add just a bit of polish

Co-authored-by: Sam Coe <samcoe@users.noreply.github.com>
This commit is contained in:
Adarsh K Kumar 2022-01-21 19:19:36 +05:30 committed by GitHub
parent 4340c65ad9
commit bb3070aaa0
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 302 additions and 0 deletions

View file

@ -0,0 +1,127 @@
package deleteasset
import (
"fmt"
"net/http"
"github.com/AlecAivazis/survey/v2"
"github.com/cli/cli/v2/api"
"github.com/cli/cli/v2/internal/ghrepo"
"github.com/cli/cli/v2/pkg/cmd/release/shared"
"github.com/cli/cli/v2/pkg/cmdutil"
"github.com/cli/cli/v2/pkg/iostreams"
"github.com/cli/cli/v2/pkg/prompt"
"github.com/spf13/cobra"
)
type DeleteAssetOptions struct {
HttpClient func() (*http.Client, error)
IO *iostreams.IOStreams
BaseRepo func() (ghrepo.Interface, error)
TagName string
SkipConfirm bool
AssetName string
}
func NewCmdDeleteAsset(f *cmdutil.Factory, runF func(*DeleteAssetOptions) error) *cobra.Command {
opts := &DeleteAssetOptions{
IO: f.IOStreams,
HttpClient: f.HttpClient,
}
cmd := &cobra.Command{
Use: "delete-asset <tag> <asset-name>",
Short: "Delete an asset from a release",
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
// support `-R, --repo` override
opts.BaseRepo = f.BaseRepo
opts.TagName = args[0]
opts.AssetName = args[1]
if runF != nil {
return runF(opts)
}
return deleteAssetRun(opts)
},
}
cmd.Flags().BoolVarP(&opts.SkipConfirm, "yes", "y", false, "Skip the confirmation prompt")
return cmd
}
func deleteAssetRun(opts *DeleteAssetOptions) error {
httpClient, err := opts.HttpClient()
if err != nil {
return err
}
baseRepo, err := opts.BaseRepo()
if err != nil {
return err
}
release, err := shared.FetchRelease(httpClient, baseRepo, opts.TagName)
if err != nil {
return err
}
if !opts.SkipConfirm && opts.IO.CanPrompt() {
var confirmed bool
err := prompt.SurveyAskOne(&survey.Confirm{
Message: fmt.Sprintf("Delete asset %s in release %s in %s?", opts.AssetName, release.TagName, ghrepo.FullName(baseRepo)),
Default: true,
}, &confirmed)
if err != nil {
return err
}
if !confirmed {
return cmdutil.CancelError
}
}
var assetURL string
for _, a := range release.Assets {
if a.Name == opts.AssetName {
assetURL = a.APIURL
break
}
}
if assetURL == "" {
return fmt.Errorf("asset %s not found in release %s", opts.AssetName, release.TagName)
}
err = deleteAsset(httpClient, assetURL)
if err != nil {
return err
}
if !opts.IO.IsStdoutTTY() || !opts.IO.IsStderrTTY() {
return nil
}
cs := opts.IO.ColorScheme()
fmt.Fprintf(opts.IO.ErrOut, "%s Deleted asset %s from release %s\n", cs.SuccessIconWithColor(cs.Red), opts.AssetName, release.TagName)
return nil
}
func deleteAsset(httpClient *http.Client, assetURL string) error {
req, err := http.NewRequest("DELETE", assetURL, nil)
if err != nil {
return err
}
resp, err := httpClient.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode > 299 {
return api.HandleHTTPError(resp)
}
return nil
}

View file

@ -0,0 +1,173 @@
package deleteasset
import (
"bytes"
"io/ioutil"
"net/http"
"testing"
"github.com/cli/cli/v2/internal/ghrepo"
"github.com/cli/cli/v2/pkg/cmdutil"
"github.com/cli/cli/v2/pkg/httpmock"
"github.com/cli/cli/v2/pkg/iostreams"
"github.com/google/shlex"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func Test_NewCmdDeleteAsset(t *testing.T) {
tests := []struct {
name string
args string
isTTY bool
want DeleteAssetOptions
wantErr string
}{
{
name: "tag and asset arguments",
args: "v1.2.3 test-asset",
isTTY: true,
want: DeleteAssetOptions{
TagName: "v1.2.3",
SkipConfirm: false,
AssetName: "test-asset",
},
},
{
name: "skip confirm",
args: "v1.2.3 test-asset -y",
isTTY: true,
want: DeleteAssetOptions{
TagName: "v1.2.3",
SkipConfirm: true,
AssetName: "test-asset",
},
},
{
name: "no arguments",
args: "",
isTTY: true,
wantErr: "accepts 2 arg(s), received 0",
},
{
name: "one arguments",
args: "v1.2.3",
isTTY: true,
wantErr: "accepts 2 arg(s), received 1",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
io, _, _, _ := iostreams.Test()
io.SetStdoutTTY(tt.isTTY)
io.SetStdinTTY(tt.isTTY)
io.SetStderrTTY(tt.isTTY)
f := &cmdutil.Factory{
IOStreams: io,
}
var opts *DeleteAssetOptions
cmd := NewCmdDeleteAsset(f, func(o *DeleteAssetOptions) error {
opts = o
return nil
})
argv, err := shlex.Split(tt.args)
require.NoError(t, err)
cmd.SetArgs(argv)
cmd.SetIn(&bytes.Buffer{})
cmd.SetOut(ioutil.Discard)
cmd.SetErr(ioutil.Discard)
_, err = cmd.ExecuteC()
if tt.wantErr != "" {
require.EqualError(t, err, tt.wantErr)
return
} else {
require.NoError(t, err)
}
assert.Equal(t, tt.want.TagName, opts.TagName)
assert.Equal(t, tt.want.SkipConfirm, opts.SkipConfirm)
assert.Equal(t, tt.want.AssetName, opts.AssetName)
})
}
}
func Test_deleteAssetRun(t *testing.T) {
tests := []struct {
name string
isTTY bool
opts DeleteAssetOptions
wantErr string
wantStdout string
wantStderr string
}{
{
name: "skipping confirmation",
isTTY: true,
opts: DeleteAssetOptions{
TagName: "v1.2.3",
SkipConfirm: true,
AssetName: "test-asset",
},
wantStdout: ``,
wantStderr: "✓ Deleted asset test-asset from release v1.2.3\n",
},
{
name: "non-interactive",
isTTY: false,
opts: DeleteAssetOptions{
TagName: "v1.2.3",
SkipConfirm: false,
AssetName: "test-asset",
},
wantStdout: ``,
wantStderr: ``,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
io, _, stdout, stderr := iostreams.Test()
io.SetStdoutTTY(tt.isTTY)
io.SetStdinTTY(tt.isTTY)
io.SetStderrTTY(tt.isTTY)
fakeHTTP := &httpmock.Registry{}
fakeHTTP.Register(httpmock.REST("GET", "repos/OWNER/REPO/releases/tags/v1.2.3"), httpmock.StringResponse(`{
"tag_name": "v1.2.3",
"draft": false,
"url": "https://api.github.com/repos/OWNER/REPO/releases/23456",
"assets": [
{
"url": "https://api.github.com/repos/OWNER/REPO/releases/assets/1",
"id": 1,
"name": "test-asset"
}
]
}`))
fakeHTTP.Register(httpmock.REST("DELETE", "repos/OWNER/REPO/releases/assets/1"), httpmock.StatusStringResponse(204, ""))
tt.opts.IO = io
tt.opts.HttpClient = func() (*http.Client, error) {
return &http.Client{Transport: fakeHTTP}, nil
}
tt.opts.BaseRepo = func() (ghrepo.Interface, error) {
return ghrepo.FromFullName("OWNER/REPO")
}
err := deleteAssetRun(&tt.opts)
if tt.wantErr != "" {
require.EqualError(t, err, tt.wantErr)
return
} else {
require.NoError(t, err)
}
assert.Equal(t, tt.wantStdout, stdout.String())
assert.Equal(t, tt.wantStderr, stderr.String())
})
}
}

View file

@ -3,6 +3,7 @@ package release
import (
cmdCreate "github.com/cli/cli/v2/pkg/cmd/release/create"
cmdDelete "github.com/cli/cli/v2/pkg/cmd/release/delete"
cmdDeleteAsset "github.com/cli/cli/v2/pkg/cmd/release/delete-asset"
cmdDownload "github.com/cli/cli/v2/pkg/cmd/release/download"
cmdList "github.com/cli/cli/v2/pkg/cmd/release/list"
cmdUpload "github.com/cli/cli/v2/pkg/cmd/release/upload"
@ -24,6 +25,7 @@ func NewCmdRelease(f *cmdutil.Factory) *cobra.Command {
cmd.AddCommand(cmdCreate.NewCmdCreate(f, nil))
cmd.AddCommand(cmdDelete.NewCmdDelete(f, nil))
cmd.AddCommand(cmdDeleteAsset.NewCmdDeleteAsset(f, nil))
cmd.AddCommand(cmdDownload.NewCmdDownload(f, nil))
cmd.AddCommand(cmdList.NewCmdList(f, nil))
cmd.AddCommand(cmdView.NewCmdView(f, nil))