Add variable delete command (#6929)
This commit is contained in:
parent
c82509587e
commit
e5a0b1fe34
4 changed files with 345 additions and 7 deletions
135
pkg/cmd/variable/delete/delete.go
Normal file
135
pkg/cmd/variable/delete/delete.go
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
package delete
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/v2/api"
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
"github.com/cli/cli/v2/internal/ghrepo"
|
||||
"github.com/cli/cli/v2/pkg/cmd/variable/shared"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
"github.com/cli/cli/v2/pkg/iostreams"
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
type DeleteOptions struct {
|
||||
HttpClient func() (*http.Client, error)
|
||||
IO *iostreams.IOStreams
|
||||
Config func() (config.Config, error)
|
||||
BaseRepo func() (ghrepo.Interface, error)
|
||||
|
||||
VariableName string
|
||||
OrgName string
|
||||
EnvName string
|
||||
Application string
|
||||
}
|
||||
|
||||
func NewCmdDelete(f *cmdutil.Factory, runF func(*DeleteOptions) error) *cobra.Command {
|
||||
opts := &DeleteOptions{
|
||||
IO: f.IOStreams,
|
||||
Config: f.Config,
|
||||
HttpClient: f.HttpClient,
|
||||
}
|
||||
|
||||
cmd := &cobra.Command{
|
||||
Use: "delete <variable-name>",
|
||||
Short: "Delete variables",
|
||||
Long: heredoc.Doc(`
|
||||
Delete a variable on one of the following levels:
|
||||
- repository (default): available to Actions runs or Dependabot in a repository
|
||||
- environment: available to Actions runs for a deployment environment in a repository
|
||||
- organization: available to Actions runs or Dependabot within an organization
|
||||
`),
|
||||
Args: cobra.ExactArgs(1),
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
// support `-R, --repo` override
|
||||
opts.BaseRepo = f.BaseRepo
|
||||
|
||||
if err := cmdutil.MutuallyExclusive("specify only one of `--org` or `--env`", opts.OrgName != "", opts.EnvName != ""); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
opts.VariableName = args[0]
|
||||
|
||||
if runF != nil {
|
||||
return runF(opts)
|
||||
}
|
||||
|
||||
return removeRun(opts)
|
||||
},
|
||||
Aliases: []string{
|
||||
"remove",
|
||||
},
|
||||
}
|
||||
cmd.Flags().StringVarP(&opts.OrgName, "org", "o", "", "Delete a variable for an organization")
|
||||
cmd.Flags().StringVarP(&opts.EnvName, "env", "e", "", "Delete a variable for an environment")
|
||||
|
||||
return cmd
|
||||
}
|
||||
|
||||
func removeRun(opts *DeleteOptions) error {
|
||||
c, err := opts.HttpClient()
|
||||
if err != nil {
|
||||
return fmt.Errorf("could not create http client: %w", err)
|
||||
}
|
||||
client := api.NewClientFromHTTP(c)
|
||||
|
||||
orgName := opts.OrgName
|
||||
envName := opts.EnvName
|
||||
|
||||
variableEntity, err := shared.GetVariableEntity(orgName, envName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var baseRepo ghrepo.Interface
|
||||
if variableEntity == shared.Repository || variableEntity == shared.Environment {
|
||||
baseRepo, err = opts.BaseRepo()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
var path string
|
||||
switch variableEntity {
|
||||
case shared.Organization:
|
||||
path = fmt.Sprintf("orgs/%s/actions/variables/%s", orgName, opts.VariableName)
|
||||
case shared.Environment:
|
||||
path = fmt.Sprintf("repositories/%s/environments/%s/variables/%s", ghrepo.FullName(baseRepo), envName, opts.VariableName)
|
||||
case shared.Repository:
|
||||
path = fmt.Sprintf("repos/%s/actions/variables/%s", ghrepo.FullName(baseRepo), opts.VariableName)
|
||||
}
|
||||
|
||||
cfg, err := opts.Config()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
host, _ := cfg.Authentication().DefaultHost()
|
||||
|
||||
err = client.REST(host, "DELETE", path, nil, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to delete variable %s: %w", opts.VariableName, err)
|
||||
}
|
||||
|
||||
if opts.IO.IsStdoutTTY() {
|
||||
var target string
|
||||
switch variableEntity {
|
||||
case shared.Organization:
|
||||
target = orgName
|
||||
case shared.Repository, shared.Environment:
|
||||
target = ghrepo.FullName(baseRepo)
|
||||
}
|
||||
|
||||
cs := opts.IO.ColorScheme()
|
||||
if envName != "" {
|
||||
fmt.Fprintf(opts.IO.Out, "%s Deleted variable %s from %s environment on %s\n", cs.SuccessIconWithColor(cs.Red), opts.VariableName, envName, target)
|
||||
} else {
|
||||
fmt.Fprintf(opts.IO.Out, "%s Deleted Actions variable %s from %s\n", cs.SuccessIconWithColor(cs.Red), opts.VariableName, target)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
203
pkg/cmd/variable/delete/delete_test.go
Normal file
203
pkg/cmd/variable/delete/delete_test.go
Normal file
|
|
@ -0,0 +1,203 @@
|
|||
package delete
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"net/http"
|
||||
"testing"
|
||||
|
||||
"github.com/cli/cli/v2/internal/config"
|
||||
"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"
|
||||
)
|
||||
|
||||
func TestNewCmdDelete(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
cli string
|
||||
wants DeleteOptions
|
||||
wantsErr bool
|
||||
}{
|
||||
{
|
||||
name: "no args",
|
||||
wantsErr: true,
|
||||
},
|
||||
{
|
||||
name: "repo",
|
||||
cli: "cool",
|
||||
wants: DeleteOptions{
|
||||
VariableName: "cool",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "org",
|
||||
cli: "cool --org anOrg",
|
||||
wants: DeleteOptions{
|
||||
VariableName: "cool",
|
||||
OrgName: "anOrg",
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "env",
|
||||
cli: "cool --env anEnv",
|
||||
wants: DeleteOptions{
|
||||
VariableName: "cool",
|
||||
EnvName: "anEnv",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ios, _, _, _ := iostreams.Test()
|
||||
f := &cmdutil.Factory{
|
||||
IOStreams: ios,
|
||||
}
|
||||
|
||||
argv, err := shlex.Split(tt.cli)
|
||||
assert.NoError(t, err)
|
||||
|
||||
var gotOpts *DeleteOptions
|
||||
cmd := NewCmdDelete(f, func(opts *DeleteOptions) error {
|
||||
gotOpts = opts
|
||||
return nil
|
||||
})
|
||||
cmd.SetArgs(argv)
|
||||
cmd.SetIn(&bytes.Buffer{})
|
||||
cmd.SetOut(&bytes.Buffer{})
|
||||
cmd.SetErr(&bytes.Buffer{})
|
||||
|
||||
_, err = cmd.ExecuteC()
|
||||
if tt.wantsErr {
|
||||
assert.Error(t, err)
|
||||
return
|
||||
}
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.Equal(t, tt.wants.VariableName, gotOpts.VariableName)
|
||||
assert.Equal(t, tt.wants.OrgName, gotOpts.OrgName)
|
||||
assert.Equal(t, tt.wants.EnvName, gotOpts.EnvName)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_removeRun_repo(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
opts *DeleteOptions
|
||||
wantPath string
|
||||
}{
|
||||
{
|
||||
name: "Actions",
|
||||
opts: &DeleteOptions{
|
||||
Application: "actions",
|
||||
VariableName: "cool_variable",
|
||||
},
|
||||
wantPath: "repos/owner/repo/actions/variables/cool_variable",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
reg := &httpmock.Registry{}
|
||||
|
||||
reg.Register(
|
||||
httpmock.REST("DELETE", tt.wantPath),
|
||||
httpmock.StatusStringResponse(204, "No Content"))
|
||||
|
||||
ios, _, _, _ := iostreams.Test()
|
||||
|
||||
tt.opts.IO = ios
|
||||
tt.opts.HttpClient = func() (*http.Client, error) {
|
||||
return &http.Client{Transport: reg}, nil
|
||||
}
|
||||
tt.opts.Config = func() (config.Config, error) {
|
||||
return config.NewBlankConfig(), nil
|
||||
}
|
||||
tt.opts.BaseRepo = func() (ghrepo.Interface, error) {
|
||||
return ghrepo.FromFullName("owner/repo")
|
||||
}
|
||||
|
||||
err := removeRun(tt.opts)
|
||||
assert.NoError(t, err)
|
||||
|
||||
reg.Verify(t)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_removeRun_env(t *testing.T) {
|
||||
reg := &httpmock.Registry{}
|
||||
|
||||
reg.Register(
|
||||
httpmock.REST("DELETE", "repositories/owner/repo/environments/development/variables/cool_variable"),
|
||||
httpmock.StatusStringResponse(204, "No Content"))
|
||||
|
||||
ios, _, _, _ := iostreams.Test()
|
||||
|
||||
opts := &DeleteOptions{
|
||||
IO: ios,
|
||||
HttpClient: func() (*http.Client, error) {
|
||||
return &http.Client{Transport: reg}, nil
|
||||
},
|
||||
Config: func() (config.Config, error) {
|
||||
return config.NewBlankConfig(), nil
|
||||
},
|
||||
BaseRepo: func() (ghrepo.Interface, error) {
|
||||
return ghrepo.FromFullName("owner/repo")
|
||||
},
|
||||
VariableName: "cool_variable",
|
||||
EnvName: "development",
|
||||
}
|
||||
|
||||
err := removeRun(opts)
|
||||
assert.NoError(t, err)
|
||||
|
||||
reg.Verify(t)
|
||||
}
|
||||
|
||||
func Test_removeRun_org(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
opts *DeleteOptions
|
||||
wantPath string
|
||||
}{
|
||||
{
|
||||
name: "org",
|
||||
opts: &DeleteOptions{
|
||||
OrgName: "UmbrellaCorporation",
|
||||
},
|
||||
wantPath: "orgs/UmbrellaCorporation/actions/variables/tVirus",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
reg := &httpmock.Registry{}
|
||||
|
||||
reg.Register(
|
||||
httpmock.REST("DELETE", tt.wantPath),
|
||||
httpmock.StatusStringResponse(204, "No Content"))
|
||||
|
||||
ios, _, _, _ := iostreams.Test()
|
||||
|
||||
tt.opts.Config = func() (config.Config, error) {
|
||||
return config.NewBlankConfig(), nil
|
||||
}
|
||||
tt.opts.BaseRepo = func() (ghrepo.Interface, error) {
|
||||
return ghrepo.FromFullName("owner/repo")
|
||||
}
|
||||
tt.opts.HttpClient = func() (*http.Client, error) {
|
||||
return &http.Client{Transport: reg}, nil
|
||||
}
|
||||
tt.opts.IO = ios
|
||||
tt.opts.VariableName = "tVirus"
|
||||
|
||||
err := removeRun(tt.opts)
|
||||
assert.NoError(t, err)
|
||||
|
||||
reg.Verify(t)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -89,6 +89,7 @@ func Test_listRun(t *testing.T) {
|
|||
tty: true,
|
||||
opts: &ListOptions{},
|
||||
wantOut: []string{
|
||||
"NAME VALUE UPDATED AT",
|
||||
"VARIABLE_ONE one Updated 1988-10-11",
|
||||
"VARIABLE_TWO two Updated 2020-12-04",
|
||||
"VARIABLE_THREE three Updated 1975-11-30",
|
||||
|
|
@ -111,6 +112,7 @@ func Test_listRun(t *testing.T) {
|
|||
OrgName: "UmbrellaCorporation",
|
||||
},
|
||||
wantOut: []string{
|
||||
"NAME VALUE UPDATED AT VISIBILITY",
|
||||
"VARIABLE_ONE org_one Updated 1988-10-11 Visible to all repositories",
|
||||
"VARIABLE_TWO org_two Updated 2020-12-04 Visible to private repositories",
|
||||
"VARIABLE_THREE org_three Updated 1975-11-30 Visible to 2 selected reposito...",
|
||||
|
|
@ -135,6 +137,7 @@ func Test_listRun(t *testing.T) {
|
|||
EnvName: "Development",
|
||||
},
|
||||
wantOut: []string{
|
||||
"NAME VALUE UPDATED AT",
|
||||
"VARIABLE_ONE one Updated 1988-10-11",
|
||||
"VARIABLE_TWO two Updated 2020-12-04",
|
||||
"VARIABLE_THREE three Updated 1975-11-30",
|
||||
|
|
@ -239,13 +242,8 @@ func Test_listRun(t *testing.T) {
|
|||
|
||||
reg.Verify(t)
|
||||
outputLines := strings.Split(stdout.String(), "\n")
|
||||
idx := 0
|
||||
if strings.Contains(outputLines[0], "NAME") {
|
||||
idx = 1
|
||||
}
|
||||
for _, l := range tt.wantOut {
|
||||
assert.Equal(t, outputLines[idx], l)
|
||||
idx++
|
||||
for i, l := range tt.wantOut {
|
||||
assert.Equal(t, outputLines[i], l)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package variable
|
|||
|
||||
import (
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
cmdDelete "github.com/cli/cli/v2/pkg/cmd/variable/delete"
|
||||
cmdList "github.com/cli/cli/v2/pkg/cmd/variable/list"
|
||||
cmdSet "github.com/cli/cli/v2/pkg/cmd/variable/set"
|
||||
"github.com/cli/cli/v2/pkg/cmdutil"
|
||||
|
|
@ -22,5 +23,6 @@ func NewCmdVariable(f *cmdutil.Factory) *cobra.Command {
|
|||
|
||||
cmd.AddCommand(cmdSet.NewCmdSet(f, nil))
|
||||
cmd.AddCommand(cmdList.NewCmdList(f, nil))
|
||||
cmd.AddCommand(cmdDelete.NewCmdDelete(f, nil))
|
||||
return cmd
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue