Skip to content

Commit dbff17e

Browse files
committed
add removing secrets
1 parent 486aa81 commit dbff17e

File tree

4 files changed

+261
-8
lines changed

4 files changed

+261
-8
lines changed

pkg/cmd/secret/remove/remove.go

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package remove
2+
3+
import (
4+
"fmt"
5+
"net/http"
6+
7+
"github.com/MakeNowJust/heredoc"
8+
"github.com/cli/cli/api"
9+
"github.com/cli/cli/internal/ghinstance"
10+
"github.com/cli/cli/internal/ghrepo"
11+
"github.com/cli/cli/pkg/cmdutil"
12+
"github.com/cli/cli/pkg/iostreams"
13+
"github.com/spf13/cobra"
14+
)
15+
16+
type RemoveOptions struct {
17+
HttpClient func() (*http.Client, error)
18+
IO *iostreams.IOStreams
19+
BaseRepo func() (ghrepo.Interface, error)
20+
21+
SecretName string
22+
OrgName string
23+
}
24+
25+
func NewCmdRemove(f *cmdutil.Factory, runF func(*RemoveOptions) error) *cobra.Command {
26+
opts := &RemoveOptions{
27+
IO: f.IOStreams,
28+
HttpClient: f.HttpClient,
29+
}
30+
31+
cmd := &cobra.Command{
32+
Use: "remove <secret name>",
33+
Short: "Remove an organization or repository secret",
34+
Example: heredoc.Doc(`
35+
$ gh secret remove REPO_SECRET
36+
$ gh secret remove --org ORG_SECRET
37+
$ gh secret remove --org="anotherOrg" ORG_SECRET
38+
`),
39+
Args: cobra.ExactArgs(1),
40+
RunE: func(cmd *cobra.Command, args []string) error {
41+
// support `-R, --repo` override
42+
opts.BaseRepo = f.BaseRepo
43+
44+
opts.SecretName = args[0]
45+
46+
if runF != nil {
47+
return runF(opts)
48+
}
49+
50+
return removeRun(opts)
51+
},
52+
}
53+
cmd.Flags().StringVar(&opts.OrgName, "org", "", "List secrets for an organization")
54+
cmd.Flags().Lookup("org").NoOptDefVal = "@owner"
55+
56+
return cmd
57+
}
58+
59+
func removeRun(opts *RemoveOptions) error {
60+
c, err := opts.HttpClient()
61+
if err != nil {
62+
return fmt.Errorf("could not create http client: %w", err)
63+
}
64+
client := api.NewClientFromHTTP(c)
65+
66+
var baseRepo ghrepo.Interface
67+
if opts.OrgName == "" || opts.OrgName == "@owner" {
68+
baseRepo, err = opts.BaseRepo()
69+
if err != nil {
70+
return fmt.Errorf("could not determine base repo: %w", err)
71+
}
72+
}
73+
74+
host := ghinstance.OverridableDefault()
75+
if opts.OrgName == "@owner" {
76+
opts.OrgName = baseRepo.RepoOwner()
77+
host = baseRepo.RepoHost()
78+
}
79+
80+
var path string
81+
if opts.OrgName == "" {
82+
path = fmt.Sprintf("repos/%s/actions/secrets/%s", ghrepo.FullName(baseRepo), opts.SecretName)
83+
} else {
84+
path = fmt.Sprintf("orgs/%s/actions/secrets/%s", opts.OrgName, opts.SecretName)
85+
}
86+
87+
err = client.REST(host, "DELETE", path, nil, nil)
88+
if err != nil {
89+
return fmt.Errorf("failed to delete secret %s: %w", opts.SecretName, err)
90+
}
91+
92+
return nil
93+
}
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
package remove
2+
3+
import (
4+
"bytes"
5+
"fmt"
6+
"net/http"
7+
"testing"
8+
9+
"github.com/cli/cli/internal/ghrepo"
10+
"github.com/cli/cli/pkg/cmdutil"
11+
"github.com/cli/cli/pkg/httpmock"
12+
"github.com/cli/cli/pkg/iostreams"
13+
"github.com/google/shlex"
14+
"github.com/stretchr/testify/assert"
15+
)
16+
17+
func TestNewCmdRemove(t *testing.T) {
18+
tests := []struct {
19+
name string
20+
cli string
21+
wants RemoveOptions
22+
wantsErr bool
23+
}{
24+
{
25+
name: "no args",
26+
wantsErr: true,
27+
},
28+
{
29+
name: "implicit org",
30+
cli: "cool --org",
31+
wants: RemoveOptions{
32+
SecretName: "cool",
33+
OrgName: "@owner",
34+
},
35+
},
36+
{
37+
name: "explicit org",
38+
cli: "cool --org=anOrg",
39+
wants: RemoveOptions{
40+
SecretName: "cool",
41+
OrgName: "anOrg",
42+
},
43+
},
44+
}
45+
46+
for _, tt := range tests {
47+
t.Run(tt.name, func(t *testing.T) {
48+
io, _, _, _ := iostreams.Test()
49+
f := &cmdutil.Factory{
50+
IOStreams: io,
51+
}
52+
53+
argv, err := shlex.Split(tt.cli)
54+
assert.NoError(t, err)
55+
56+
var gotOpts *RemoveOptions
57+
cmd := NewCmdRemove(f, func(opts *RemoveOptions) error {
58+
gotOpts = opts
59+
return nil
60+
})
61+
cmd.SetArgs(argv)
62+
cmd.SetIn(&bytes.Buffer{})
63+
cmd.SetOut(&bytes.Buffer{})
64+
cmd.SetErr(&bytes.Buffer{})
65+
66+
_, err = cmd.ExecuteC()
67+
if tt.wantsErr {
68+
assert.Error(t, err)
69+
return
70+
}
71+
assert.NoError(t, err)
72+
73+
assert.Equal(t, tt.wants.SecretName, gotOpts.SecretName)
74+
assert.Equal(t, tt.wants.OrgName, gotOpts.OrgName)
75+
})
76+
}
77+
78+
}
79+
80+
func Test_removeRun_repo(t *testing.T) {
81+
reg := &httpmock.Registry{}
82+
83+
reg.Register(
84+
httpmock.REST("DELETE", "repos/owner/repo/actions/secrets/cool_secret"),
85+
httpmock.StatusStringResponse(204, "No Content"))
86+
87+
io, _, _, _ := iostreams.Test()
88+
89+
opts := &RemoveOptions{
90+
IO: io,
91+
HttpClient: func() (*http.Client, error) {
92+
return &http.Client{Transport: reg}, nil
93+
},
94+
BaseRepo: func() (ghrepo.Interface, error) {
95+
return ghrepo.FromFullName("owner/repo")
96+
},
97+
SecretName: "cool_secret",
98+
}
99+
100+
err := removeRun(opts)
101+
assert.NoError(t, err)
102+
103+
reg.Verify(t)
104+
}
105+
106+
func Test_removeRun_org(t *testing.T) {
107+
tests := []struct {
108+
name string
109+
opts *RemoveOptions
110+
}{
111+
{
112+
name: "implicit org",
113+
opts: &RemoveOptions{
114+
OrgName: "@owner",
115+
},
116+
},
117+
{
118+
name: "explicit org",
119+
opts: &RemoveOptions{
120+
OrgName: "UmbrellaCorporation",
121+
},
122+
},
123+
}
124+
125+
for _, tt := range tests {
126+
t.Run(tt.name, func(t *testing.T) {
127+
reg := &httpmock.Registry{}
128+
129+
impliedOrgName := "NeoUmbrella"
130+
131+
orgName := tt.opts.OrgName
132+
if orgName == "@owner" {
133+
orgName = impliedOrgName
134+
}
135+
136+
reg.Register(
137+
httpmock.REST("DELETE", fmt.Sprintf("orgs/%s/actions/secrets/tVirus", orgName)),
138+
httpmock.StatusStringResponse(204, "No Content"))
139+
140+
io, _, _, _ := iostreams.Test()
141+
142+
tt.opts.BaseRepo = func() (ghrepo.Interface, error) {
143+
return ghrepo.FromFullName(fmt.Sprintf("%s/repo", impliedOrgName))
144+
}
145+
tt.opts.HttpClient = func() (*http.Client, error) {
146+
return &http.Client{Transport: reg}, nil
147+
}
148+
tt.opts.IO = io
149+
tt.opts.SecretName = "tVirus"
150+
151+
err := removeRun(tt.opts)
152+
assert.NoError(t, err)
153+
154+
reg.Verify(t)
155+
156+
})
157+
}
158+
159+
}

pkg/cmd/secret/secret.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"github.com/spf13/cobra"
77

88
cmdList "github.com/cli/cli/pkg/cmd/secret/list"
9+
cmdRemove "github.com/cli/cli/pkg/cmd/secret/remove"
910
cmdSet "github.com/cli/cli/pkg/cmd/secret/set"
1011
)
1112

@@ -22,8 +23,9 @@ func NewCmdSecret(f *cmdutil.Factory) *cobra.Command {
2223
cmdutil.EnableRepoOverride(cmd, f)
2324

2425
cmd.AddCommand(cmdList.NewCmdList(f, nil))
26+
// TODO add success messages to these:
2527
cmd.AddCommand(cmdSet.NewCmdSet(f, nil))
26-
// TODO add delete
28+
cmd.AddCommand(cmdRemove.NewCmdRemove(f, nil))
2729

2830
return cmd
2931
}

pkg/cmd/secret/set/set_test.go

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -172,17 +172,15 @@ func Test_setRun_repo(t *testing.T) {
172172

173173
reg.Register(httpmock.REST("PUT", "repos/owner/repo/actions/secrets/cool_secret"), httpmock.StatusStringResponse(201, `{}`))
174174

175-
mockClient := func() (*http.Client, error) {
176-
return &http.Client{Transport: reg}, nil
177-
}
178-
179175
io, _, _, _ := iostreams.Test()
180176

181177
opts := &SetOptions{
178+
HttpClient: func() (*http.Client, error) {
179+
return &http.Client{Transport: reg}, nil
180+
},
182181
BaseRepo: func() (ghrepo.Interface, error) {
183182
return ghrepo.FromFullName("owner/repo")
184183
},
185-
HttpClient: mockClient,
186184
IO: io,
187185
SecretName: "cool_secret",
188186
Body: "a secret",
@@ -240,9 +238,10 @@ func Test_setRun_org(t *testing.T) {
240238
t.Run(tt.name, func(t *testing.T) {
241239
reg := &httpmock.Registry{}
242240

241+
impliedOrgName := "NeoUmbrella"
243242
orgName := tt.opts.OrgName
244243
if orgName == "@owner" {
245-
orgName = "NeoUmbrella"
244+
orgName = impliedOrgName
246245
}
247246

248247
reg.Register(httpmock.REST("GET",
@@ -261,7 +260,7 @@ func Test_setRun_org(t *testing.T) {
261260
io, _, _, _ := iostreams.Test()
262261

263262
tt.opts.BaseRepo = func() (ghrepo.Interface, error) {
264-
return ghrepo.FromFullName("NeoUmbrella/repo")
263+
return ghrepo.FromFullName(fmt.Sprintf("%s/repo", impliedOrgName))
265264
}
266265
tt.opts.HttpClient = func() (*http.Client, error) {
267266
return &http.Client{Transport: reg}, nil

0 commit comments

Comments
 (0)