Add variable delete command (#6929)

This commit is contained in:
Anupam Kumar 2023-03-13 09:11:55 +05:30 committed by GitHub
parent c82509587e
commit e5a0b1fe34
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 345 additions and 7 deletions

View 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
}

View 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)
})
}
}

View file

@ -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)
}
})
}

View file

@ -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
}