forked from openshift/origin
-
Notifications
You must be signed in to change notification settings - Fork 1
/
buildhook.go
277 lines (228 loc) · 8.27 KB
/
buildhook.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
package set
import (
"fmt"
"io"
"os"
"github.com/spf13/cobra"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/kubernetes/pkg/kubectl/cmd/templates"
kcmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
"k8s.io/kubernetes/pkg/kubectl/resource"
buildapi "github.com/openshift/origin/pkg/build/apis/build"
"github.com/openshift/origin/pkg/cmd/util/clientcmd"
)
var (
buildHookLong = templates.LongDesc(`
Set or remove a build hook on a build config
Build hooks allow behavior to be injected into the build process.
A post-commit build hook is executed after a build has committed an image but before the
image has been pushed to a registry. It can be used to execute tests on the image and verify
it before it is made available in a registry or for any other logic that is needed to execute
before the image is pushed to the registry. A new container with the recently built image is
launched with the build hook command. If the command or script run by the build hook returns a
non-zero exit code, the resulting image will not be pushed to the registry.
The command for a build hook may be specified as a shell script (with the --script argument),
as a new entrypoint command on the image with the --command argument, or as a set of
arguments to the image's entrypoint (default).`)
buildHookExample = templates.Examples(`
# Clear post-commit hook on a build config
%[1]s build-hook bc/mybuild --post-commit --remove
# Set the post-commit hook to execute a test suite using a new entrypoint
%[1]s build-hook bc/mybuild --post-commit --command -- /bin/bash -c /var/lib/test-image.sh
# Set the post-commit hook to execute a shell script
%[1]s build-hook bc/mybuild --post-commit --script="/var/lib/test-image.sh param1 param2 && /var/lib/done.sh"
# Set the post-commit hook as a set of arguments to the default image entrypoint
%[1]s build-hook bc/mybuild --post-commit -- arg1 arg2`)
)
type BuildHookOptions struct {
Out io.Writer
Err io.Writer
Builder *resource.Builder
Infos []*resource.Info
Encoder runtime.Encoder
Filenames []string
Selector string
All bool
Output string
Cmd *cobra.Command
Local bool
ShortOutput bool
Mapper meta.RESTMapper
PrintObject func([]*resource.Info) error
Script string
Entrypoint bool
Remove bool
PostCommit bool
Command []string
}
// NewCmdBuildHook implements the set build-hook command
func NewCmdBuildHook(fullName string, f *clientcmd.Factory, out, errOut io.Writer) *cobra.Command {
options := &BuildHookOptions{
Out: out,
Err: errOut,
}
cmd := &cobra.Command{
Use: "build-hook BUILDCONFIG --post-commit [--command] [--script] -- CMD",
Short: "Update a build hook on a build config",
Long: buildHookLong,
Example: fmt.Sprintf(buildHookExample, fullName),
Run: func(cmd *cobra.Command, args []string) {
kcmdutil.CheckErr(options.Complete(f, cmd, args))
kcmdutil.CheckErr(options.Validate())
if err := options.Run(); err != nil {
// TODO: move me to kcmdutil
if err == kcmdutil.ErrExit {
os.Exit(1)
}
kcmdutil.CheckErr(err)
}
},
}
kcmdutil.AddPrinterFlags(cmd)
cmd.Flags().StringVarP(&options.Selector, "selector", "l", options.Selector, "Selector (label query) to filter build configs")
cmd.Flags().BoolVar(&options.All, "all", options.All, "If true, select all build configs in the namespace")
cmd.Flags().StringSliceVarP(&options.Filenames, "filename", "f", options.Filenames, "Filename, directory, or URL to file to use to edit the resource.")
cmd.Flags().BoolVar(&options.PostCommit, "post-commit", options.PostCommit, "If true, set the post-commit build hook on a build config")
cmd.Flags().BoolVar(&options.Entrypoint, "command", options.Entrypoint, "If true, set the entrypoint of the hook container to the given command")
cmd.Flags().StringVar(&options.Script, "script", options.Script, "Specify a script to run for the build-hook")
cmd.Flags().BoolVar(&options.Remove, "remove", options.Remove, "If true, remove the build hook.")
cmd.Flags().BoolVar(&options.Local, "local", false, "If true, set image will NOT contact api-server but run locally.")
cmd.MarkFlagFilename("filename", "yaml", "yml", "json")
kcmdutil.AddDryRunFlag(cmd)
return cmd
}
func (o *BuildHookOptions) Complete(f *clientcmd.Factory, cmd *cobra.Command, args []string) error {
resources := args
if i := cmd.ArgsLenAtDash(); i != -1 {
resources = args[:i]
o.Command = args[i:]
}
if len(o.Filenames) == 0 && len(args) < 1 {
return kcmdutil.UsageErrorf(cmd, "one or more build configs must be specified as <name> or <resource>/<name>")
}
cmdNamespace, explicit, err := f.DefaultNamespace()
if err != nil {
return err
}
o.Cmd = cmd
mapper, _ := f.Object()
o.Builder = f.NewBuilder(!o.Local).
ContinueOnError().
NamespaceParam(cmdNamespace).DefaultNamespace().
FilenameParam(explicit, &resource.FilenameOptions{Recursive: false, Filenames: o.Filenames}).
SelectorParam(o.Selector).
ResourceNames("buildconfigs", resources...).
Flatten()
if !o.Local {
o.Builder = o.Builder.
SelectorParam(o.Selector).
ResourceNames("buildconfigs", resources...)
if o.All {
o.Builder.ResourceTypes("buildconfigs").SelectAllParam(o.All)
}
}
o.Output = kcmdutil.GetFlagString(cmd, "output")
o.PrintObject = func(infos []*resource.Info) error {
return f.PrintResourceInfos(cmd, o.Local, infos, o.Out)
}
o.Encoder = f.JSONEncoder()
o.ShortOutput = kcmdutil.GetFlagString(cmd, "output") == "name"
o.Mapper = mapper
return nil
}
func (o *BuildHookOptions) Validate() error {
if !o.PostCommit {
return fmt.Errorf("you must specify a type of hook to set")
}
if o.Remove {
if len(o.Command) > 0 {
return fmt.Errorf("--remove may not be used with any other option")
}
return nil
}
if len(o.Script) > 0 && o.Entrypoint {
return fmt.Errorf("--script and --command cannot be specified together")
}
if len(o.Script) > 0 && len(o.Command) > 0 {
return fmt.Errorf("a command cannot be specified when using the --script argument")
}
if len(o.Command) == 0 && len(o.Script) == 0 {
return fmt.Errorf("you must specify either a script or command for the build hook")
}
return nil
}
func (o *BuildHookOptions) Run() error {
infos := o.Infos
singleItemImplied := len(o.Infos) <= 1
if o.Builder != nil {
loaded, err := o.Builder.Do().IntoSingleItemImplied(&singleItemImplied).Infos()
if err != nil {
return err
}
infos = loaded
}
patches := CalculatePatches(infos, o.Encoder, func(info *resource.Info) (bool, error) {
bc, ok := info.Object.(*buildapi.BuildConfig)
if !ok {
return false, nil
}
o.updateBuildConfig(bc)
return true, nil
})
if singleItemImplied && len(patches) == 0 {
return fmt.Errorf("%s/%s is not a build config", infos[0].Mapping.Resource, infos[0].Name)
}
if len(o.Output) > 0 || o.Local || kcmdutil.GetDryRunFlag(o.Cmd) {
return o.PrintObject(infos)
}
failed := false
for _, patch := range patches {
info := patch.Info
if patch.Err != nil {
fmt.Fprintf(o.Err, "error: %s/%s %v\n", info.Mapping.Resource, info.Name, patch.Err)
continue
}
if string(patch.Patch) == "{}" || len(patch.Patch) == 0 {
fmt.Fprintf(o.Err, "info: %s %q was not changed\n", info.Mapping.Resource, info.Name)
continue
}
obj, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, types.StrategicMergePatchType, patch.Patch)
if err != nil {
fmt.Fprintf(o.Err, "error: %v\n", err)
failed = true
continue
}
info.Refresh(obj, true)
kcmdutil.PrintSuccess(o.Mapper, o.ShortOutput, o.Out, info.Mapping.Resource, info.Name, false, "updated")
}
if failed {
return kcmdutil.ErrExit
}
return nil
}
func (o *BuildHookOptions) updateBuildConfig(bc *buildapi.BuildConfig) {
if o.Remove {
bc.Spec.PostCommit.Args = nil
bc.Spec.PostCommit.Command = nil
bc.Spec.PostCommit.Script = ""
return
}
switch {
case len(o.Script) > 0:
bc.Spec.PostCommit.Args = nil
bc.Spec.PostCommit.Command = nil
bc.Spec.PostCommit.Script = o.Script
case o.Entrypoint:
bc.Spec.PostCommit.Command = o.Command[0:1]
if len(o.Command) > 1 {
bc.Spec.PostCommit.Args = o.Command[1:]
}
bc.Spec.PostCommit.Script = ""
default:
bc.Spec.PostCommit.Command = nil
bc.Spec.PostCommit.Args = o.Command
bc.Spec.PostCommit.Script = ""
}
}