Browse code

refactor cancelbuild cli cmd and add unit tests

Signed-off-by: Guilherme Rezende <guilhermebr@gmail.com>

Guilherme Rezende authored on 2016/09/04 08:31:06
Showing 4 changed files
... ...
@@ -61,7 +61,7 @@ func (c *FakeBuilds) Watch(opts kapi.ListOptions) (watch.Interface, error) {
61 61
 }
62 62
 
63 63
 func (c *FakeBuilds) Clone(request *buildapi.BuildRequest) (result *buildapi.Build, err error) {
64
-	action := ktestclient.NewCreateAction("buildconfigs", c.Namespace, request)
64
+	action := ktestclient.NewCreateAction("builds", c.Namespace, request)
65 65
 	action.Subresource = "clone"
66 66
 	obj, err := c.Fake.Invokes(action, &buildapi.Build{})
67 67
 	if obj == nil {
... ...
@@ -115,7 +115,7 @@ func NewCommandCLI(name, fullName string, in io.Reader, out, errout io.Writer) *
115 115
 				cmd.NewCmdRollback(fullName, f, out),
116 116
 				cmd.NewCmdNewBuild(fullName, f, in, out),
117 117
 				cmd.NewCmdStartBuild(fullName, f, in, out),
118
-				cmd.NewCmdCancelBuild(fullName, f, in, out),
118
+				cmd.NewCmdCancelBuild(cmd.CancelBuildRecommendedCommandName, fullName, f, in, out),
119 119
 				cmd.NewCmdImportImage(fullName, f, out),
120 120
 				cmd.NewCmdTag(fullName, f, out),
121 121
 			},
... ...
@@ -1,6 +1,7 @@
1 1
 package cmd
2 2
 
3 3
 import (
4
+	"errors"
4 5
 	"fmt"
5 6
 	"io"
6 7
 	"strings"
... ...
@@ -9,7 +10,7 @@ import (
9 9
 
10 10
 	"github.com/spf13/cobra"
11 11
 	kapi "k8s.io/kubernetes/pkg/api"
12
-	"k8s.io/kubernetes/pkg/api/errors"
12
+	kapierrors "k8s.io/kubernetes/pkg/api/errors"
13 13
 	"k8s.io/kubernetes/pkg/api/meta"
14 14
 	kcmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
15 15
 	"k8s.io/kubernetes/pkg/util/wait"
... ...
@@ -22,6 +23,9 @@ import (
22 22
 	"github.com/openshift/origin/pkg/cmd/util/clientcmd"
23 23
 )
24 24
 
25
+// CancelBuildRecommendedCommandName is the recommended command name.
26
+const CancelBuildRecommendedCommandName = "cancel-build"
27
+
25 28
 const (
26 29
 	cancelBuildLong = `
27 30
 Cancel running, pending, or new builds
... ...
@@ -30,21 +34,22 @@ This command requests a graceful shutdown of the build. There may be a delay bet
30 30
 the build and the time the build is terminated.`
31 31
 
32 32
 	cancelBuildExample = `  # Cancel the build with the given name
33
-  %[1]s cancel-build ruby-build-2
33
+  %[1]s %[2]s ruby-build-2
34 34
 
35 35
   # Cancel the named build and print the build logs
36
-  %[1]s cancel-build ruby-build-2 --dump-logs
36
+  %[1]s %[2]s ruby-build-2 --dump-logs
37 37
 
38 38
   # Cancel the named build and create a new one with the same parameters
39
-  %[1]s cancel-build ruby-build-2 --restart
39
+  %[1]s %[2]s ruby-build-2 --restart
40 40
 
41 41
   # Cancel multiple builds
42
-  %[1]s cancel-build ruby-build-1 ruby-build-2 ruby-build-3
42
+  %[1]s %[2]s ruby-build-1 ruby-build-2 ruby-build-3
43 43
 
44 44
   # Cancel all builds created from 'ruby-build' build configuration that are in 'new' state
45
-  %[1]s cancel-build bc/ruby-build --state=new`
45
+  %[1]s %[2]s bc/ruby-build --state=new`
46 46
 )
47 47
 
48
+// CancelBuildOptions contains all the options for running the CancelBuild cli command.
48 49
 type CancelBuildOptions struct {
49 50
 	In          io.Reader
50 51
 	Out, ErrOut io.Writer
... ...
@@ -64,18 +69,21 @@ type CancelBuildOptions struct {
64 64
 }
65 65
 
66 66
 // NewCmdCancelBuild implements the OpenShift cli cancel-build command
67
-func NewCmdCancelBuild(fullName string, f *clientcmd.Factory, in io.Reader, out io.Writer) *cobra.Command {
67
+func NewCmdCancelBuild(name, baseName string, f *clientcmd.Factory, in io.Reader, out io.Writer) *cobra.Command {
68 68
 	o := &CancelBuildOptions{}
69 69
 
70 70
 	cmd := &cobra.Command{
71
-		Use:        "cancel-build (BUILD | BUILDCONFIG)",
71
+		Use:        fmt.Sprintf("%s (BUILD | BUILDCONFIG)", name),
72 72
 		Short:      "Cancel running, pending, or new builds",
73 73
 		Long:       cancelBuildLong,
74
-		Example:    fmt.Sprintf(cancelBuildExample, fullName),
74
+		Example:    fmt.Sprintf(cancelBuildExample, baseName, name),
75 75
 		SuggestFor: []string{"builds", "stop-build"},
76 76
 		Run: func(cmd *cobra.Command, args []string) {
77
-			kcmdutil.CheckErr(o.Complete(f, in, out, cmd, args))
78
-			kcmdutil.CheckErr(o.Run())
77
+			err := o.Complete(f, cmd, args, in, out)
78
+			kcmdutil.CheckErr(err)
79
+
80
+			err = o.RunCancelBuild()
81
+			kcmdutil.CheckErr(err)
79 82
 		},
80 83
 	}
81 84
 
... ...
@@ -85,7 +93,8 @@ func NewCmdCancelBuild(fullName string, f *clientcmd.Factory, in io.Reader, out
85 85
 	return cmd
86 86
 }
87 87
 
88
-func (o *CancelBuildOptions) Complete(f *clientcmd.Factory, in io.Reader, out io.Writer, cmd *cobra.Command, args []string) error {
88
+// Complete completes all the required options.
89
+func (o *CancelBuildOptions) Complete(f *clientcmd.Factory, cmd *cobra.Command, args []string, in io.Reader, out io.Writer) error {
89 90
 	o.In = in
90 91
 	o.Out = out
91 92
 	o.ErrOut = cmd.OutOrStderr()
... ...
@@ -130,6 +139,7 @@ func (o *CancelBuildOptions) Complete(f *clientcmd.Factory, in io.Reader, out io
130 130
 		if err != nil {
131 131
 			return err
132 132
 		}
133
+
133 134
 		switch resource {
134 135
 		case buildapi.Resource("buildconfigs"):
135 136
 			list, err := buildutil.BuildConfigBuilds(o.BuildLister, o.Namespace, name, nil)
... ...
@@ -149,7 +159,8 @@ func (o *CancelBuildOptions) Complete(f *clientcmd.Factory, in io.Reader, out io
149 149
 	return nil
150 150
 }
151 151
 
152
-func (o *CancelBuildOptions) Run() error {
152
+// RunCancelBuild implements all the necessary functionality for CancelBuild.
153
+func (o *CancelBuildOptions) RunCancelBuild() error {
153 154
 	var builds []*buildapi.Build
154 155
 
155 156
 	for _, name := range o.BuildNames {
... ...
@@ -158,6 +169,7 @@ func (o *CancelBuildOptions) Run() error {
158 158
 			o.ReportError(fmt.Errorf("build %s/%s not found", o.Namespace, name))
159 159
 			continue
160 160
 		}
161
+
161 162
 		stateMatch := false
162 163
 		for _, state := range o.States {
163 164
 			if strings.ToLower(string(build.Status.Phase)) == state {
... ...
@@ -198,7 +210,7 @@ func (o *CancelBuildOptions) Run() error {
198 198
 				switch {
199 199
 				case err == nil:
200 200
 					return true, nil
201
-				case errors.IsConflict(err):
201
+				case kapierrors.IsConflict(err):
202 202
 					build, err = o.BuildClient.Get(build.Name)
203 203
 					return false, err
204 204
 				}
... ...
@@ -208,6 +220,7 @@ func (o *CancelBuildOptions) Run() error {
208 208
 				o.ReportError(fmt.Errorf("build %s/%s failed to update: %v", build.Namespace, build.Name, err))
209 209
 				return
210 210
 			}
211
+
211 212
 			// Make sure the build phase is really cancelled.
212 213
 			err = wait.Poll(500*time.Millisecond, 30*time.Second, func() (bool, error) {
213 214
 				updatedBuild, err := o.BuildClient.Get(build.Name)
... ...
@@ -220,6 +233,7 @@ func (o *CancelBuildOptions) Run() error {
220 220
 				o.ReportError(fmt.Errorf("build %s/%s failed to cancel: %v", build.Namespace, build.Name, err))
221 221
 				return
222 222
 			}
223
+
223 224
 			resource, name, _ := cmdutil.ResolveResource(buildapi.Resource("builds"), build.Name, o.Mapper)
224 225
 			kcmdutil.PrintSuccess(o.Mapper, false, o.Out, resource.Resource, name, "cancelled")
225 226
 		}(b)
... ...
@@ -240,7 +254,7 @@ func (o *CancelBuildOptions) Run() error {
240 240
 	}
241 241
 
242 242
 	if o.HasError {
243
-		return fmt.Errorf("failure during the build cancellation")
243
+		return errors.New("failure during the build cancellation")
244 244
 	}
245 245
 
246 246
 	return nil
247 247
new file mode 100644
... ...
@@ -0,0 +1,201 @@
0
+package cmd
1
+
2
+import (
3
+	"io/ioutil"
4
+	"strconv"
5
+	"strings"
6
+	"testing"
7
+
8
+	kapi "k8s.io/kubernetes/pkg/api"
9
+	"k8s.io/kubernetes/pkg/apimachinery/registered"
10
+
11
+	buildapi "github.com/openshift/origin/pkg/build/api"
12
+	"github.com/openshift/origin/pkg/client/testclient"
13
+)
14
+
15
+// TestCancelBuildDefaultFlags ensures that flags default values are set.
16
+func TestCancelBuildDefaultFlags(t *testing.T) {
17
+	o := CancelBuildOptions{}
18
+
19
+	tests := map[string]struct {
20
+		flagName   string
21
+		defaultVal string
22
+	}{
23
+		"state": {
24
+			flagName:   "state",
25
+			defaultVal: "[" + strings.Join(o.States, ",") + "]",
26
+		},
27
+		"dump-logs": {
28
+			flagName:   "dump-logs",
29
+			defaultVal: strconv.FormatBool(o.DumpLogs),
30
+		},
31
+		"restart": {
32
+			flagName:   "restart",
33
+			defaultVal: strconv.FormatBool(o.Restart),
34
+		},
35
+	}
36
+
37
+	cmd := NewCmdCancelBuild("oc", CancelBuildRecommendedCommandName, nil, nil, nil)
38
+
39
+	for _, v := range tests {
40
+		f := cmd.Flag(v.flagName)
41
+		if f == nil {
42
+			t.Fatalf("expected flag %s to be registered but found none", v.flagName)
43
+		}
44
+
45
+		if f.DefValue != v.defaultVal {
46
+			t.Errorf("expected default value of %s for %s but found %s", v.defaultVal, v.flagName, f.DefValue)
47
+		}
48
+	}
49
+}
50
+
51
+// TestCancelBuildRun ensures that RunCancelBuild command calls the right actions.
52
+func TestCancelBuildRun(t *testing.T) {
53
+	tests := map[string]struct {
54
+		opts            *CancelBuildOptions
55
+		phase           buildapi.BuildPhase
56
+		expectedActions []testAction
57
+		expectedErr     error
58
+	}{
59
+		"cancelled": {
60
+			opts: &CancelBuildOptions{
61
+				Out:       ioutil.Discard,
62
+				Namespace: "test",
63
+				States:    []string{"new", "pending", "running"},
64
+			},
65
+			phase: buildapi.BuildPhaseCancelled,
66
+			expectedActions: []testAction{
67
+				{verb: "get", resource: "builds"},
68
+			},
69
+			expectedErr: nil,
70
+		},
71
+		"complete": {
72
+			opts: &CancelBuildOptions{
73
+				Out:       ioutil.Discard,
74
+				Namespace: "test",
75
+			},
76
+			phase: buildapi.BuildPhaseComplete,
77
+			expectedActions: []testAction{
78
+				{verb: "get", resource: "builds"},
79
+			},
80
+			expectedErr: nil,
81
+		},
82
+		"new": {
83
+			opts: &CancelBuildOptions{
84
+				Out:       ioutil.Discard,
85
+				Namespace: "test",
86
+			},
87
+			phase: buildapi.BuildPhaseNew,
88
+			expectedActions: []testAction{
89
+				{verb: "get", resource: "builds"},
90
+				{verb: "update", resource: "builds"},
91
+				{verb: "get", resource: "builds"},
92
+			},
93
+			expectedErr: nil,
94
+		},
95
+		"pending": {
96
+			opts: &CancelBuildOptions{
97
+				Out:       ioutil.Discard,
98
+				Namespace: "test",
99
+			},
100
+			phase: buildapi.BuildPhaseNew,
101
+			expectedActions: []testAction{
102
+				{verb: "get", resource: "builds"},
103
+				{verb: "update", resource: "builds"},
104
+				{verb: "get", resource: "builds"},
105
+			},
106
+			expectedErr: nil,
107
+		},
108
+		"running and restart": {
109
+			opts: &CancelBuildOptions{
110
+				Out:       ioutil.Discard,
111
+				Namespace: "test",
112
+				Restart:   true,
113
+			},
114
+			phase: buildapi.BuildPhaseNew,
115
+			expectedActions: []testAction{
116
+				{verb: "get", resource: "builds"},
117
+				{verb: "update", resource: "builds"},
118
+				{verb: "get", resource: "builds"},
119
+				{verb: "create", resource: "builds"},
120
+			},
121
+			expectedErr: nil,
122
+		},
123
+	}
124
+
125
+	for _, test := range tests {
126
+		client := testclient.NewSimpleFake(genBuild(test.phase))
127
+		buildClient := NewFakeTestBuilds(client, test.opts.Namespace)
128
+
129
+		test.opts.Client = client
130
+		test.opts.BuildClient = buildClient
131
+		test.opts.ReportError = func(err error) {
132
+			test.opts.HasError = true
133
+		}
134
+		test.opts.Mapper = registered.RESTMapper()
135
+		test.opts.BuildNames = []string{"ruby-ex"}
136
+		test.opts.States = []string{"new", "pending", "running"}
137
+
138
+		if err := test.opts.RunCancelBuild(); err != test.expectedErr {
139
+			t.Fatalf("error mismatch: expected %v, got %v", test.expectedErr, err)
140
+		}
141
+
142
+		got := test.opts.Client.(*testclient.Fake).Actions()
143
+		if len(test.expectedActions) != len(got) {
144
+			t.Fatalf("action length mismatch: expected %d, got %d", len(test.expectedActions), len(got))
145
+		}
146
+
147
+		for i, action := range test.expectedActions {
148
+			if !got[i].Matches(action.verb, action.resource) {
149
+				t.Errorf("action mismatch: expected %s %s, got %s %s", action.verb, action.resource, got[i].GetVerb(), got[i].GetResource())
150
+			}
151
+		}
152
+	}
153
+
154
+}
155
+
156
+type FakeTestBuilds struct {
157
+	*testclient.FakeBuilds
158
+	Obj *buildapi.Build
159
+}
160
+
161
+func NewFakeTestBuilds(c *testclient.Fake, ns string) *FakeTestBuilds {
162
+	f := FakeTestBuilds{}
163
+	f.FakeBuilds = &testclient.FakeBuilds{}
164
+	f.Fake = c
165
+	f.Namespace = ns
166
+
167
+	return &f
168
+}
169
+
170
+func (c *FakeTestBuilds) Get(name string) (*buildapi.Build, error) {
171
+	obj, err := c.FakeBuilds.Get(name)
172
+	if c.Obj == nil {
173
+		c.Obj = obj
174
+	}
175
+
176
+	return c.Obj, err
177
+}
178
+
179
+func (c *FakeTestBuilds) Update(inObj *buildapi.Build) (*buildapi.Build, error) {
180
+	_, err := c.FakeBuilds.Update(inObj)
181
+	if inObj.Status.Cancelled == true {
182
+		inObj.Status.Phase = buildapi.BuildPhaseCancelled
183
+	}
184
+
185
+	c.Obj = inObj
186
+	return c.Obj, err
187
+}
188
+
189
+func genBuild(phase buildapi.BuildPhase) *buildapi.Build {
190
+	build := buildapi.Build{
191
+		ObjectMeta: kapi.ObjectMeta{
192
+			Name:      "ruby-ex",
193
+			Namespace: "test",
194
+		},
195
+		Status: buildapi.BuildStatus{
196
+			Phase: phase,
197
+		},
198
+	}
199
+	return &build
200
+}