Skip to content

Commit 62cd88d

Browse files
committed
Improve oc cancel-build command
1 parent b33f606 commit 62cd88d

File tree

5 files changed

+192
-94
lines changed

5 files changed

+192
-94
lines changed

contrib/completions/bash/oc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -863,7 +863,9 @@ _oc_cancel-build()
863863
flags_completion=()
864864

865865
flags+=("--dump-logs")
866+
flags+=("--from=")
866867
flags+=("--restart")
868+
flags+=("--state=")
867869
flags+=("--api-version=")
868870
flags+=("--certificate-authority=")
869871
flags_with_completion+=("--certificate-authority")

contrib/completions/bash/openshift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4412,7 +4412,9 @@ _openshift_cli_cancel-build()
44124412
flags_completion=()
44134413

44144414
flags+=("--dump-logs")
4415+
flags+=("--from=")
44154416
flags+=("--restart")
4417+
flags+=("--state=")
44164418
flags+=("--api-version=")
44174419
flags+=("--certificate-authority=")
44184420
flags_with_completion+=("--certificate-authority")

pkg/build/util/util.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"k8s.io/kubernetes/pkg/labels"
1010

1111
buildapi "github.com/openshift/origin/pkg/build/api"
12+
"github.com/openshift/origin/pkg/client"
1213
)
1314

1415
const (
@@ -90,6 +91,30 @@ func BuildConfigSelectorDeprecated(name string) labels.Selector {
9091
return labels.Set{buildapi.BuildConfigLabelDeprecated: name}.AsSelector()
9192
}
9293

94+
type buildFilter func(buildapi.Build) bool
95+
96+
// BuildConfigBuilds return a list of builds for the given build config.
97+
// Optionally you can specify a filter function to select only builds that
98+
// matches your criteria.
99+
func BuildConfigBuilds(c client.BuildInterface, name string, filterFunc buildFilter) (*buildapi.BuildList, error) {
100+
result, err := c.List(kapi.ListOptions{
101+
LabelSelector: BuildConfigSelector(name),
102+
})
103+
if err != nil {
104+
return nil, err
105+
}
106+
if filterFunc == nil {
107+
return result, nil
108+
}
109+
filteredList := &buildapi.BuildList{TypeMeta: result.TypeMeta, ListMeta: result.ListMeta}
110+
for _, b := range result.Items {
111+
if filterFunc(b) {
112+
filteredList.Items = append(filteredList.Items, b)
113+
}
114+
}
115+
return filteredList, nil
116+
}
117+
93118
// ConfigNameForBuild returns the name of the build config from a
94119
// build name.
95120
func ConfigNameForBuild(build *buildapi.Build) string {

pkg/cmd/cli/cli.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ func NewCommandCLI(name, fullName string, in io.Reader, out, errout io.Writer) *
104104
cmd.NewCmdRollback(fullName, f, out),
105105
cmd.NewCmdNewBuild(fullName, f, in, out),
106106
cmd.NewCmdStartBuild(fullName, f, in, out),
107-
cmd.NewCmdCancelBuild(fullName, f, out),
107+
cmd.NewCmdCancelBuild(fullName, f, in, out),
108108
cmd.NewCmdImportImage(fullName, f, out),
109109
cmd.NewCmdTag(fullName, f, out),
110110
},

pkg/cmd/cli/cmd/cancelbuild.go

Lines changed: 162 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -3,166 +3,235 @@ package cmd
33
import (
44
"fmt"
55
"io"
6+
"strings"
7+
"sync"
8+
"time"
69

7-
"github.com/golang/glog"
810
"github.com/spf13/cobra"
911
kapi "k8s.io/kubernetes/pkg/api"
1012
"k8s.io/kubernetes/pkg/api/errors"
1113
kcmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
12-
"k8s.io/kubernetes/pkg/kubectl/resource"
14+
"k8s.io/kubernetes/pkg/util/wait"
1315

1416
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"
1519
"github.com/openshift/origin/pkg/cmd/util/clientcmd"
1620
)
1721

1822
const (
1923
cancelBuildLong = `
20-
Cancels a pending or running build
24+
Cancels a pending, running or new builds
2125
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
2327
the build and the time the build is terminated.`
2428

2529
cancelBuildExample = ` # Cancel the build with the given name
26-
$ %[1]s cancel-build 1da32cvq
30+
$ %[1]s cancel-build ruby-build-2
2731
2832
# 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
3034
3135
# 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`
3343
)
3444

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+
3560
// 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+
3764
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",
4067
Long: cancelBuildLong,
4168
Example: fmt.Sprintf(cancelBuildExample, fullName),
4269
SuggestFor: []string{"builds", "stop-build"},
4370
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())
4673
},
4774
}
4875

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.")
5280
return cmd
5381
}
5482

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()
6087

61-
buildName := args[0]
6288
namespace, _, err := f.DefaultNamespace()
6389
if err != nil {
6490
return err
6591
}
6692

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+
}
70105
}
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()
79108
if err != nil {
80109
return err
81110
}
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)
85118
}
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")
88122
}
89123

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)
94136
}
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)
96147
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)
98153
} 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)
100155
}
101156
}
102157

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()
108166
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
110169
}
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))
115172
}
116-
break
117173
}
118-
fmt.Fprintf(out, "Build %s was cancelled.\n", build.Name)
119174

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()
123196

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)
132206
}
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")
148207
}
208+
149209
return nil
150210
}
151211

152212
// 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:
156216
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:
164219
return false
165220
}
166-
167221
return true
168222
}
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

Comments
 (0)