X Tutup
Skip to content

Commit 248ee42

Browse files
committed
gh workflow {enable, disable}
1 parent 5aac191 commit 248ee42

File tree

10 files changed

+1085
-71
lines changed

10 files changed

+1085
-71
lines changed
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
package disable
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"net/http"
7+
8+
"github.com/cli/cli/api"
9+
"github.com/cli/cli/internal/ghrepo"
10+
"github.com/cli/cli/pkg/cmd/workflow/shared"
11+
"github.com/cli/cli/pkg/cmdutil"
12+
"github.com/cli/cli/pkg/iostreams"
13+
"github.com/spf13/cobra"
14+
)
15+
16+
type DisableOptions struct {
17+
HttpClient func() (*http.Client, error)
18+
IO *iostreams.IOStreams
19+
BaseRepo func() (ghrepo.Interface, error)
20+
21+
Selector string
22+
Prompt bool
23+
}
24+
25+
func NewCmdDisable(f *cmdutil.Factory, runF func(*DisableOptions) error) *cobra.Command {
26+
opts := &DisableOptions{
27+
IO: f.IOStreams,
28+
HttpClient: f.HttpClient,
29+
}
30+
31+
cmd := &cobra.Command{
32+
Use: "disable [<workflow ID> | <workflow name>]",
33+
Short: "Disable a workflow",
34+
Args: cobra.MaximumNArgs(1),
35+
Hidden: true,
36+
RunE: func(cmd *cobra.Command, args []string) error {
37+
// support `-R, --repo` override
38+
opts.BaseRepo = f.BaseRepo
39+
40+
if len(args) > 0 {
41+
opts.Selector = args[0]
42+
} else if !opts.IO.CanPrompt() {
43+
return &cmdutil.FlagError{Err: errors.New("workflow ID or name required when not running interactively")}
44+
} else {
45+
opts.Prompt = true
46+
}
47+
48+
if runF != nil {
49+
return runF(opts)
50+
}
51+
return runDisable(opts)
52+
},
53+
}
54+
55+
return cmd
56+
}
57+
58+
func runDisable(opts *DisableOptions) error {
59+
c, err := opts.HttpClient()
60+
if err != nil {
61+
return fmt.Errorf("could not build http client: %w", err)
62+
}
63+
client := api.NewClientFromHTTP(c)
64+
65+
repo, err := opts.BaseRepo()
66+
if err != nil {
67+
return fmt.Errorf("could not determine base repo: %w", err)
68+
}
69+
70+
states := []shared.WorkflowState{shared.Active}
71+
workflow, err := shared.ResolveWorkflow(
72+
opts.IO, client, repo, opts.Prompt, opts.Selector, states)
73+
if err != nil {
74+
var fae shared.FilteredAllError
75+
if errors.As(err, &fae) {
76+
return errors.New("there are no enabled workflows to disable")
77+
}
78+
return err
79+
}
80+
81+
path := fmt.Sprintf("repos/%s/actions/workflows/%d/disable", ghrepo.FullName(repo), workflow.ID)
82+
err = client.REST(repo.RepoHost(), "PUT", path, nil, nil)
83+
if err != nil {
84+
return fmt.Errorf("failed to disable workflow: %w", err)
85+
}
86+
87+
if opts.IO.CanPrompt() {
88+
cs := opts.IO.ColorScheme()
89+
fmt.Fprintf(opts.IO.Out, "%s Disabled %s\n", cs.SuccessIcon(), cs.Bold(workflow.Name))
90+
}
91+
92+
return nil
93+
}
Lines changed: 298 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,298 @@
1+
package disable
2+
3+
import (
4+
"bytes"
5+
"io/ioutil"
6+
"net/http"
7+
"testing"
8+
9+
"github.com/cli/cli/internal/ghrepo"
10+
"github.com/cli/cli/pkg/cmd/workflow/shared"
11+
"github.com/cli/cli/pkg/cmdutil"
12+
"github.com/cli/cli/pkg/httpmock"
13+
"github.com/cli/cli/pkg/iostreams"
14+
"github.com/cli/cli/pkg/prompt"
15+
"github.com/google/shlex"
16+
"github.com/stretchr/testify/assert"
17+
)
18+
19+
func TestNewCmdDisable(t *testing.T) {
20+
tests := []struct {
21+
name string
22+
cli string
23+
tty bool
24+
wants DisableOptions
25+
wantsErr bool
26+
}{
27+
{
28+
name: "blank tty",
29+
tty: true,
30+
wants: DisableOptions{
31+
Prompt: true,
32+
},
33+
},
34+
{
35+
name: "blank nontty",
36+
wantsErr: true,
37+
},
38+
{
39+
name: "arg tty",
40+
cli: "123",
41+
tty: true,
42+
wants: DisableOptions{
43+
Selector: "123",
44+
},
45+
},
46+
{
47+
name: "arg nontty",
48+
cli: "123",
49+
wants: DisableOptions{
50+
Selector: "123",
51+
},
52+
},
53+
}
54+
55+
for _, tt := range tests {
56+
t.Run(tt.name, func(t *testing.T) {
57+
io, _, _, _ := iostreams.Test()
58+
io.SetStdinTTY(tt.tty)
59+
io.SetStdoutTTY(tt.tty)
60+
61+
f := &cmdutil.Factory{
62+
IOStreams: io,
63+
}
64+
65+
argv, err := shlex.Split(tt.cli)
66+
assert.NoError(t, err)
67+
68+
var gotOpts *DisableOptions
69+
cmd := NewCmdDisable(f, func(opts *DisableOptions) error {
70+
gotOpts = opts
71+
return nil
72+
})
73+
cmd.SetArgs(argv)
74+
cmd.SetIn(&bytes.Buffer{})
75+
cmd.SetOut(ioutil.Discard)
76+
cmd.SetErr(ioutil.Discard)
77+
78+
_, err = cmd.ExecuteC()
79+
if tt.wantsErr {
80+
assert.Error(t, err)
81+
return
82+
}
83+
84+
assert.NoError(t, err)
85+
86+
assert.Equal(t, tt.wants.Selector, gotOpts.Selector)
87+
assert.Equal(t, tt.wants.Prompt, gotOpts.Prompt)
88+
})
89+
}
90+
}
91+
92+
func TestDisableRun(t *testing.T) {
93+
tests := []struct {
94+
name string
95+
opts *DisableOptions
96+
httpStubs func(*httpmock.Registry)
97+
askStubs func(*prompt.AskStubber)
98+
tty bool
99+
wantOut string
100+
wantErrOut string
101+
wantErr bool
102+
}{
103+
{
104+
name: "tty no arg",
105+
opts: &DisableOptions{
106+
Prompt: true,
107+
},
108+
tty: true,
109+
httpStubs: func(reg *httpmock.Registry) {
110+
reg.Register(
111+
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows"),
112+
httpmock.JSONResponse(shared.WorkflowsPayload{
113+
Workflows: []shared.Workflow{
114+
shared.AWorkflow,
115+
shared.DisabledWorkflow,
116+
shared.AnotherWorkflow,
117+
},
118+
}))
119+
reg.Register(
120+
httpmock.REST("PUT", "repos/OWNER/REPO/actions/workflows/789/disable"),
121+
httpmock.StatusStringResponse(204, "{}"))
122+
},
123+
askStubs: func(as *prompt.AskStubber) {
124+
as.StubOne(1)
125+
},
126+
wantOut: "✓ Disabled another workflow\n",
127+
},
128+
{
129+
name: "tty name arg",
130+
opts: &DisableOptions{
131+
Selector: "a workflow",
132+
},
133+
tty: true,
134+
httpStubs: func(reg *httpmock.Registry) {
135+
reg.Register(
136+
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/a workflow"),
137+
httpmock.StatusStringResponse(404, "not found"))
138+
reg.Register(
139+
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows"),
140+
httpmock.JSONResponse(shared.WorkflowsPayload{
141+
Workflows: []shared.Workflow{
142+
shared.AWorkflow,
143+
shared.DisabledWorkflow,
144+
shared.AnotherWorkflow,
145+
},
146+
}))
147+
reg.Register(
148+
httpmock.REST("PUT", "repos/OWNER/REPO/actions/workflows/123/disable"),
149+
httpmock.StatusStringResponse(204, "{}"))
150+
},
151+
wantOut: "✓ Disabled a workflow\n",
152+
},
153+
{
154+
name: "tty name arg nonunique",
155+
opts: &DisableOptions{
156+
Selector: "another workflow",
157+
},
158+
tty: true,
159+
httpStubs: func(reg *httpmock.Registry) {
160+
reg.Register(
161+
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/another workflow"),
162+
httpmock.StatusStringResponse(404, "not found"))
163+
reg.Register(
164+
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows"),
165+
httpmock.JSONResponse(shared.WorkflowsPayload{
166+
Workflows: []shared.Workflow{
167+
shared.AWorkflow,
168+
shared.DisabledWorkflow,
169+
shared.AnotherWorkflow,
170+
shared.YetAnotherWorkflow,
171+
shared.AnotherDisabledWorkflow,
172+
},
173+
}))
174+
reg.Register(
175+
httpmock.REST("PUT", "repos/OWNER/REPO/actions/workflows/1011/disable"),
176+
httpmock.StatusStringResponse(204, "{}"))
177+
},
178+
askStubs: func(as *prompt.AskStubber) {
179+
as.StubOne(1)
180+
},
181+
wantOut: "✓ Disabled another workflow\n",
182+
},
183+
{
184+
name: "tty ID arg",
185+
opts: &DisableOptions{
186+
Selector: "123",
187+
},
188+
tty: true,
189+
httpStubs: func(reg *httpmock.Registry) {
190+
reg.Register(
191+
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
192+
httpmock.JSONResponse(shared.AWorkflow))
193+
reg.Register(
194+
httpmock.REST("PUT", "repos/OWNER/REPO/actions/workflows/123/disable"),
195+
httpmock.StatusStringResponse(204, "{}"))
196+
},
197+
wantOut: "✓ Disabled a workflow\n",
198+
},
199+
{
200+
name: "nontty ID arg",
201+
opts: &DisableOptions{
202+
Selector: "123",
203+
},
204+
httpStubs: func(reg *httpmock.Registry) {
205+
reg.Register(
206+
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/123"),
207+
httpmock.JSONResponse(shared.AWorkflow))
208+
reg.Register(
209+
httpmock.REST("PUT", "repos/OWNER/REPO/actions/workflows/123/disable"),
210+
httpmock.StatusStringResponse(204, "{}"))
211+
},
212+
},
213+
{
214+
name: "nontty name arg",
215+
opts: &DisableOptions{
216+
Selector: "a workflow",
217+
},
218+
httpStubs: func(reg *httpmock.Registry) {
219+
reg.Register(
220+
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/a workflow"),
221+
httpmock.StatusStringResponse(404, "not found"))
222+
reg.Register(
223+
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows"),
224+
httpmock.JSONResponse(shared.WorkflowsPayload{
225+
Workflows: []shared.Workflow{
226+
shared.AWorkflow,
227+
shared.DisabledWorkflow,
228+
shared.AnotherWorkflow,
229+
shared.AnotherDisabledWorkflow,
230+
shared.UniqueDisabledWorkflow,
231+
},
232+
}))
233+
reg.Register(
234+
httpmock.REST("PUT", "repos/OWNER/REPO/actions/workflows/123/disable"),
235+
httpmock.StatusStringResponse(204, "{}"))
236+
},
237+
},
238+
{
239+
name: "nontty name arg nonunique",
240+
opts: &DisableOptions{
241+
Selector: "another workflow",
242+
},
243+
httpStubs: func(reg *httpmock.Registry) {
244+
reg.Register(
245+
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows/another workflow"),
246+
httpmock.StatusStringResponse(404, "not found"))
247+
reg.Register(
248+
httpmock.REST("GET", "repos/OWNER/REPO/actions/workflows"),
249+
httpmock.JSONResponse(shared.WorkflowsPayload{
250+
Workflows: []shared.Workflow{
251+
shared.AWorkflow,
252+
shared.DisabledWorkflow,
253+
shared.AnotherWorkflow,
254+
shared.YetAnotherWorkflow,
255+
shared.AnotherDisabledWorkflow,
256+
shared.UniqueDisabledWorkflow,
257+
},
258+
}))
259+
},
260+
wantErr: true,
261+
wantErrOut: "could not resolve to a unique workflow; found: another.yml yetanother.yml",
262+
},
263+
}
264+
265+
for _, tt := range tests {
266+
reg := &httpmock.Registry{}
267+
tt.httpStubs(reg)
268+
tt.opts.HttpClient = func() (*http.Client, error) {
269+
return &http.Client{Transport: reg}, nil
270+
}
271+
272+
io, _, stdout, _ := iostreams.Test()
273+
io.SetStdoutTTY(tt.tty)
274+
io.SetStdinTTY(tt.tty)
275+
tt.opts.IO = io
276+
tt.opts.BaseRepo = func() (ghrepo.Interface, error) {
277+
return ghrepo.FromFullName("OWNER/REPO")
278+
}
279+
280+
as, teardown := prompt.InitAskStubber()
281+
defer teardown()
282+
if tt.askStubs != nil {
283+
tt.askStubs(as)
284+
}
285+
286+
t.Run(tt.name, func(t *testing.T) {
287+
err := runDisable(tt.opts)
288+
if tt.wantErr {
289+
assert.Error(t, err)
290+
assert.Equal(t, tt.wantErrOut, err.Error())
291+
return
292+
}
293+
assert.NoError(t, err)
294+
assert.Equal(t, tt.wantOut, stdout.String())
295+
reg.Verify(t)
296+
})
297+
}
298+
}

0 commit comments

Comments
 (0)
X Tutup