* #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:
parent
4340c65ad9
commit
bb3070aaa0
3 changed files with 302 additions and 0 deletions
127
pkg/cmd/release/delete-asset/delete_asset.go
Normal file
127
pkg/cmd/release/delete-asset/delete_asset.go
Normal 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
|
||||
}
|
||||
173
pkg/cmd/release/delete-asset/delete_asset_test.go
Normal file
173
pkg/cmd/release/delete-asset/delete_asset_test.go
Normal 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())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -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))
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue