@@ -3,166 +3,235 @@ package cmd
3
3
import (
4
4
"fmt"
5
5
"io"
6
+ "strings"
7
+ "sync"
8
+ "time"
6
9
7
- "github.com/golang/glog"
8
10
"github.com/spf13/cobra"
9
11
kapi "k8s.io/kubernetes/pkg/api"
10
12
"k8s.io/kubernetes/pkg/api/errors"
11
13
kcmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
12
- "k8s.io/kubernetes/pkg/kubectl/resource "
14
+ "k8s.io/kubernetes/pkg/util/wait "
13
15
14
16
buildapi "github.com/openshift/origin/pkg/build/api"
17
+ buildutil "github.com/openshift/origin/pkg/build/util"
18
+ osclient "github.com/openshift/origin/pkg/client"
15
19
"github.com/openshift/origin/pkg/cmd/util/clientcmd"
16
20
)
17
21
18
22
const (
19
23
cancelBuildLong = `
20
- Cancels a pending or running build
24
+ Cancels a pending, running or new builds
21
25
22
- This command requests a graceful shutdown of the running build. There may be a delay between requesting
26
+ This command requests a graceful shutdown of the build. There may be a delay between requesting
23
27
the build and the time the build is terminated.`
24
28
25
29
cancelBuildExample = ` # Cancel the build with the given name
26
- $ %[1]s cancel-build 1da32cvq
30
+ $ %[1]s cancel-build ruby-build-2
27
31
28
32
# Cancel the named build and print the build logs
29
- $ %[1]s cancel-build 1da32cvq --dump-logs
33
+ $ %[1]s cancel-build ruby-build-2 --dump-logs
30
34
31
35
# Cancel the named build and create a new one with the same parameters
32
- $ %[1]s cancel-build 1da32cvq --restart`
36
+ $ %[1]s cancel-build ruby-build-2 --restart
37
+
38
+ # Cancel multiple builds
39
+ $ %[1]s cancel-build ruby-build-1 ruby-build-2 ruby-build-3
40
+
41
+ # Cancel all builds created from 'ruby-build' build configuration that are in 'new' state
42
+ $ %[1]s cancel-build --from=ruby-build --state=new`
33
43
)
34
44
45
+ type CancelBuildOptions struct {
46
+ In io.Reader
47
+ Out , ErrOut io.Writer
48
+
49
+ DumpLogs bool
50
+ Restart bool
51
+ State string
52
+ From string
53
+ Namespace string
54
+ BuildNames []string
55
+
56
+ Client osclient.Interface
57
+ BuildClient osclient.BuildInterface
58
+ }
59
+
35
60
// NewCmdCancelBuild implements the OpenShift cli cancel-build command
36
- func NewCmdCancelBuild (fullName string , f * clientcmd.Factory , out io.Writer ) * cobra.Command {
61
+ func NewCmdCancelBuild (fullName string , f * clientcmd.Factory , in io.Reader , out io.Writer ) * cobra.Command {
62
+ o := & CancelBuildOptions {}
63
+
37
64
cmd := & cobra.Command {
38
- Use : "cancel-build BUILD" ,
39
- Short : "Cancel a pending or running build " ,
65
+ Use : "cancel-build ( BUILD | --from=BUILDCONFIG) " ,
66
+ Short : "Cancel a pending, running or new builds " ,
40
67
Long : cancelBuildLong ,
41
68
Example : fmt .Sprintf (cancelBuildExample , fullName ),
42
69
SuggestFor : []string {"builds" , "stop-build" },
43
70
Run : func (cmd * cobra.Command , args []string ) {
44
- err := RunCancelBuild ( f , out , cmd , args )
45
- kcmdutil .CheckErr (err )
71
+ kcmdutil . CheckErr ( o . Complete ( f , in , out , cmd , args ) )
72
+ kcmdutil .CheckErr (o . Run () )
46
73
},
47
74
}
48
75
49
- cmd .Flags ().Bool ("dump-logs" , false , "Specify if the build logs for the cancelled build should be shown." )
50
- cmd .Flags ().Bool ("restart" , false , "Specify if a new build should be created after the current build is cancelled." )
51
- //cmdutil.AddOutputFlagsForMutation(cmd)
76
+ cmd .Flags ().StringVar (& o .From , "from" , o .From , "Specify the name of build configuration to cancel builds from" )
77
+ cmd .Flags ().StringVar (& o .State , "state" , o .State , "Specify the state of the builds to be cancelled" )
78
+ cmd .Flags ().BoolVar (& o .DumpLogs , "dump-logs" , o .DumpLogs , "Specify if the build logs for the cancelled build should be shown." )
79
+ cmd .Flags ().BoolVar (& o .Restart , "restart" , o .Restart , "Specify if a new build should be created after the current build is cancelled." )
52
80
return cmd
53
81
}
54
82
55
- // RunCancelBuild contains all the necessary functionality for the OpenShift cli cancel-build command
56
- func RunCancelBuild (f * clientcmd.Factory , out io.Writer , cmd * cobra.Command , args []string ) error {
57
- if len (args ) == 0 || len (args [0 ]) == 0 {
58
- return kcmdutil .UsageError (cmd , "You must specify the name of a build to cancel." )
59
- }
83
+ func (o * CancelBuildOptions ) Complete (f * clientcmd.Factory , in io.Reader , out io.Writer , cmd * cobra.Command , args []string ) error {
84
+ o .In = in
85
+ o .Out = out
86
+ o .ErrOut = cmd .Out ()
60
87
61
- buildName := args [0 ]
62
88
namespace , _ , err := f .DefaultNamespace ()
63
89
if err != nil {
64
90
return err
65
91
}
66
92
67
- client , _ , err := f .Clients ()
68
- if err != nil {
69
- return err
93
+ switch {
94
+ case len (o .From ) > 0 :
95
+ if len (args ) > 0 {
96
+ return kcmdutil .UsageError (cmd , "The '--from' flag is incompatible with arguments" )
97
+ }
98
+ case len (args ) > 0 :
99
+ if len (o .State ) > 0 {
100
+ return kcmdutil .UsageError (cmd , "The '--state' flag must be used only with the '--from' flag" )
101
+ }
102
+ for _ , name := range args {
103
+ o .BuildNames = append (o .BuildNames , strings .TrimSpace (name ))
104
+ }
70
105
}
71
- buildClient := client .Builds (namespace )
72
-
73
- mapper , typer := f .Object ()
74
- obj , err := resource .NewBuilder (mapper , typer , resource .ClientMapperFunc (f .ClientForMapping ), kapi .Codecs .UniversalDecoder ()).
75
- NamespaceParam (namespace ).
76
- ResourceNames ("builds" , buildName ).
77
- SingleResourceType ().
78
- Do ().Object ()
106
+
107
+ client , _ , err := f .Clients ()
79
108
if err != nil {
80
109
return err
81
110
}
82
- build , ok := obj .(* buildapi.Build )
83
- if ! ok {
84
- return fmt .Errorf ("%q is not a valid build" , buildName )
111
+
112
+ o .Namespace = namespace
113
+ o .Client = client
114
+ o .BuildClient = client .Builds (namespace )
115
+
116
+ if len (o .State ) > 0 && ! isStateCancellable (o .State ) {
117
+ return kcmdutil .UsageError (cmd , "The '--state' flag has invalid value. Must be 'new', 'pending' or 'running'" , o .State )
85
118
}
86
- if ! isBuildCancellable (build , out ) {
87
- return nil
119
+
120
+ if len (o .From ) == 0 && (len (args ) == 0 || len (args [0 ]) == 0 ) {
121
+ return kcmdutil .UsageError (cmd , "You must specify the name of a build to cancel or '--from' flag" )
88
122
}
89
123
90
- // Print build logs before cancelling build.
91
- if kcmdutil .GetFlagBool (cmd , "dump-logs" ) {
92
- opts := buildapi.BuildLogOptions {
93
- NoWait : true ,
124
+ if len (o .From ) > 0 {
125
+ list , err := buildutil .BuildConfigBuilds (o .BuildClient , o .From , func (b buildapi.Build ) bool {
126
+ if len (o .State ) > 0 && strings .ToLower (string (b .Status .Phase )) != o .State {
127
+ return false
128
+ }
129
+ return isBuildCancellable (& b )
130
+ })
131
+ if err != nil {
132
+ return err
133
+ }
134
+ for _ , b := range list .Items {
135
+ o .BuildNames = append (o .BuildNames , b .Name )
94
136
}
95
- response , err := client .BuildLogs (namespace ).Get (build .Name , opts ).Do ().Raw ()
137
+ }
138
+
139
+ return nil
140
+ }
141
+
142
+ func (o * CancelBuildOptions ) Run () error {
143
+ var builds []* buildapi.Build
144
+
145
+ for _ , name := range o .BuildNames {
146
+ build , err := o .BuildClient .Get (name )
96
147
if err != nil {
97
- glog .Errorf ("Could not fetch build logs for %s: %v" , build .Name , err )
148
+ fmt .Fprintf (o .ErrOut , "Build %s/%s not found\n " , o .Namespace , name )
149
+ continue
150
+ }
151
+ if isBuildCancellable (build ) {
152
+ builds = append (builds , build )
98
153
} else {
99
- glog . Infof ( "Build logs for %s: \n %v " , build . Name , string ( response ) )
154
+ fmt . Fprintf ( o . ErrOut , "Build %s/%s is %s and cannot be cancelled \n " , o . Namespace , name , build . Status . Phase )
100
155
}
101
156
}
102
157
103
- // Mark build to be cancelled.
104
- for {
105
- build .Status .Cancelled = true
106
- if _ , err = buildClient .Update (build ); err != nil && errors .IsConflict (err ) {
107
- build , err = buildClient .Get (build .Name )
158
+ if o .DumpLogs {
159
+ for _ , b := range builds {
160
+ // Do not attempt to get logs from build that was not scheduled.
161
+ if b .Status .Phase == buildapi .BuildPhaseNew {
162
+ continue
163
+ }
164
+ opts := buildapi.BuildLogOptions {NoWait : true }
165
+ response , err := o .Client .BuildLogs (o .Namespace ).Get (b .Name , opts ).Do ().Raw ()
108
166
if err != nil {
109
- return err
167
+ fmt .Fprintf (o .ErrOut , "Unable to fetch logs for %s/%s: %v" , b .Namespace , b .Name , err )
168
+ continue
110
169
}
111
- continue
112
- }
113
- if err != nil {
114
- return err
170
+ fmt .Fprintf (o .Out , "==== Build %s/%s logs ====\n " , b .Namespace , b .Name )
171
+ fmt .Fprint (o .Out , string (response ))
115
172
}
116
- break
117
173
}
118
- fmt .Fprintf (out , "Build %s was cancelled.\n " , build .Name )
119
174
120
- // mapper, typer := f.Object()
121
- // resourceMapper := &resource.Mapper{ObjectTyper: typer, RESTMapper: mapper, ClientMapper: f.ClientMapperForCommand()}
122
- // shortOutput := kcmdutil.GetFlagString(cmd, "output") == "name"
175
+ var wg sync.WaitGroup
176
+ for _ , b := range builds {
177
+ wg .Add (1 )
178
+ go func (build * buildapi.Build ) {
179
+ defer wg .Done ()
180
+ err := wait .Poll (500 * time .Millisecond , 30 * time .Second , func () (bool , error ) {
181
+ build .Status .Cancelled = true
182
+ _ , err := o .BuildClient .Update (build )
183
+ if err != nil && errors .IsConflict (err ) {
184
+ return false , nil
185
+ }
186
+ return true , err
187
+ })
188
+ if err != nil {
189
+ fmt .Fprintf (o .ErrOut , "Build %s/%s failed to cancel: %v\n " , build .Namespace , build .Name , err )
190
+ return
191
+ }
192
+ fmt .Fprintf (o .ErrOut , "%s cancelled\n " , build .Name )
193
+ }(b )
194
+ }
195
+ wg .Wait ()
123
196
124
- // Create a new build with the same configuration.
125
- if kcmdutil .GetFlagBool (cmd , "restart" ) {
126
- request := & buildapi.BuildRequest {
127
- ObjectMeta : kapi.ObjectMeta {Name : build .Name },
128
- }
129
- newBuild , err := client .Builds (namespace ).Clone (request )
130
- if err != nil {
131
- return err
197
+ if o .Restart {
198
+ for _ , b := range builds {
199
+ request := & buildapi.BuildRequest {ObjectMeta : kapi.ObjectMeta {Name : b .Name }}
200
+ build , err := o .BuildClient .Clone (request )
201
+ if err != nil {
202
+ fmt .Fprintf (o .ErrOut , "Build %s/%s failed to restart: %v\n " , b .Namespace , b .Name , err )
203
+ continue
204
+ }
205
+ fmt .Fprintf (o .Out , "%s (restarted %s)\n " , build .Name , b .Name )
132
206
}
133
- fmt .Fprintf (out , "Restarted build %s.\n " , build .Name )
134
- fmt .Fprintf (out , "%s\n " , newBuild .Name )
135
- // fmt.Fprintf(out, "%s\n", newBuild.Name)
136
- // info, err := resourceMapper.InfoForObject(newBuild)
137
- // if err != nil {
138
- // return err
139
- // }
140
- //kcmdutil.PrintSuccess(mapper, shortOutput, out, info.Mapping.Resource, info.Name, "restarted")
141
- } else {
142
- fmt .Fprintf (out , "%s\n " , build .Name )
143
- // info, err := resourceMapper.InfoForObject(build)
144
- // if err != nil {
145
- // return err
146
- // }
147
- // kcmdutil.PrintSuccess(mapper, shortOutput, out, info.Mapping.Resource, info.Name, "cancelled")
148
207
}
208
+
149
209
return nil
150
210
}
151
211
152
212
// isBuildCancellable checks if another cancellation event was triggered, and if the build status is correct.
153
- func isBuildCancellable (build * buildapi.Build , out io. Writer ) bool {
154
- if build . Status . Cancelled {
155
- fmt . Fprintf ( out , "A cancellation event was already triggered for the build %s. \n " , build . Name )
213
+ func isBuildCancellable (build * buildapi.Build ) bool {
214
+ switch {
215
+ case build . Status . Cancelled :
156
216
return false
157
- }
158
-
159
- if build .Status .Phase != buildapi .BuildPhaseNew &&
160
- build .Status .Phase != buildapi .BuildPhasePending &&
161
- build .Status .Phase != buildapi .BuildPhaseRunning {
162
-
163
- fmt .Fprintf (out , "A build can be cancelled only if it has new/pending/running status.\n " )
217
+ case build .Status .Phase != buildapi .BuildPhaseNew && build .Status .Phase != buildapi .BuildPhasePending &&
218
+ build .Status .Phase != buildapi .BuildPhaseRunning :
164
219
return false
165
220
}
166
-
167
221
return true
168
222
}
223
+
224
+ // isStateCancellable validates the state provided by the '--state' flag.
225
+ func isStateCancellable (state string ) bool {
226
+ cancellablePhases := []string {
227
+ string (buildapi .BuildPhaseNew ),
228
+ string (buildapi .BuildPhasePending ),
229
+ string (buildapi .BuildPhaseRunning ),
230
+ }
231
+ for _ , p := range cancellablePhases {
232
+ if state == strings .ToLower (p ) {
233
+ return true
234
+ }
235
+ }
236
+ return false
237
+ }
0 commit comments