Browse code

Command to set deployment hooks on deployment configs

Cesar Wong authored on 2016/05/31 22:13:31
Showing 6 changed files
... ...
@@ -1603,6 +1603,80 @@ _oc_set_probe()
1603 1603
     must_have_one_noun=()
1604 1604
 }
1605 1605
 
1606
+_oc_set_deployment-hook()
1607
+{
1608
+    last_command="oc_set_deployment-hook"
1609
+    commands=()
1610
+
1611
+    flags=()
1612
+    two_word_flags=()
1613
+    flags_with_completion=()
1614
+    flags_completion=()
1615
+
1616
+    flags+=("--all")
1617
+    flags+=("--container=")
1618
+    two_word_flags+=("-c")
1619
+    flags+=("--environment=")
1620
+    two_word_flags+=("-e")
1621
+    flags+=("--failure-policy=")
1622
+    flags+=("--filename=")
1623
+    flags_with_completion+=("--filename")
1624
+    flags_completion+=("__handle_filename_extension_flag yaml|yml|json")
1625
+    two_word_flags+=("-f")
1626
+    flags_with_completion+=("-f")
1627
+    flags_completion+=("__handle_filename_extension_flag yaml|yml|json")
1628
+    flags+=("--mid")
1629
+    flags+=("--no-headers")
1630
+    flags+=("--output=")
1631
+    two_word_flags+=("-o")
1632
+    flags+=("--output-version=")
1633
+    flags+=("--post")
1634
+    flags+=("--pre")
1635
+    flags+=("--remove")
1636
+    flags+=("--selector=")
1637
+    two_word_flags+=("-l")
1638
+    flags+=("--show-all")
1639
+    flags+=("-a")
1640
+    flags+=("--show-labels")
1641
+    flags+=("--sort-by=")
1642
+    flags+=("--template=")
1643
+    flags_with_completion+=("--template")
1644
+    flags_completion+=("_filedir")
1645
+    two_word_flags+=("-t")
1646
+    flags_with_completion+=("-t")
1647
+    flags_completion+=("_filedir")
1648
+    flags+=("--volumes=")
1649
+    two_word_flags+=("-v")
1650
+    flags+=("--api-version=")
1651
+    flags+=("--as=")
1652
+    flags+=("--certificate-authority=")
1653
+    flags_with_completion+=("--certificate-authority")
1654
+    flags_completion+=("_filedir")
1655
+    flags+=("--client-certificate=")
1656
+    flags_with_completion+=("--client-certificate")
1657
+    flags_completion+=("_filedir")
1658
+    flags+=("--client-key=")
1659
+    flags_with_completion+=("--client-key")
1660
+    flags_completion+=("_filedir")
1661
+    flags+=("--cluster=")
1662
+    flags+=("--config=")
1663
+    flags_with_completion+=("--config")
1664
+    flags_completion+=("_filedir")
1665
+    flags+=("--context=")
1666
+    flags+=("--google-json-key=")
1667
+    flags+=("--insecure-skip-tls-verify")
1668
+    flags+=("--log-flush-frequency=")
1669
+    flags+=("--match-server-version")
1670
+    flags+=("--namespace=")
1671
+    two_word_flags+=("-n")
1672
+    flags+=("--server=")
1673
+    flags+=("--token=")
1674
+    flags+=("--user=")
1675
+
1676
+    must_have_one_flag=()
1677
+    must_have_one_noun=()
1678
+}
1679
+
1606 1680
 _oc_set_triggers()
1607 1681
 {
1608 1682
     last_command="oc_set_triggers"
... ...
@@ -1751,6 +1825,7 @@ _oc_set()
1751 1751
     commands+=("env")
1752 1752
     commands+=("volumes")
1753 1753
     commands+=("probe")
1754
+    commands+=("deployment-hook")
1754 1755
     commands+=("triggers")
1755 1756
     commands+=("build-hook")
1756 1757
 
... ...
@@ -5191,6 +5191,80 @@ _openshift_cli_set_probe()
5191 5191
     must_have_one_noun=()
5192 5192
 }
5193 5193
 
5194
+_openshift_cli_set_deployment-hook()
5195
+{
5196
+    last_command="openshift_cli_set_deployment-hook"
5197
+    commands=()
5198
+
5199
+    flags=()
5200
+    two_word_flags=()
5201
+    flags_with_completion=()
5202
+    flags_completion=()
5203
+
5204
+    flags+=("--all")
5205
+    flags+=("--container=")
5206
+    two_word_flags+=("-c")
5207
+    flags+=("--environment=")
5208
+    two_word_flags+=("-e")
5209
+    flags+=("--failure-policy=")
5210
+    flags+=("--filename=")
5211
+    flags_with_completion+=("--filename")
5212
+    flags_completion+=("__handle_filename_extension_flag yaml|yml|json")
5213
+    two_word_flags+=("-f")
5214
+    flags_with_completion+=("-f")
5215
+    flags_completion+=("__handle_filename_extension_flag yaml|yml|json")
5216
+    flags+=("--mid")
5217
+    flags+=("--no-headers")
5218
+    flags+=("--output=")
5219
+    two_word_flags+=("-o")
5220
+    flags+=("--output-version=")
5221
+    flags+=("--post")
5222
+    flags+=("--pre")
5223
+    flags+=("--remove")
5224
+    flags+=("--selector=")
5225
+    two_word_flags+=("-l")
5226
+    flags+=("--show-all")
5227
+    flags+=("-a")
5228
+    flags+=("--show-labels")
5229
+    flags+=("--sort-by=")
5230
+    flags+=("--template=")
5231
+    flags_with_completion+=("--template")
5232
+    flags_completion+=("_filedir")
5233
+    two_word_flags+=("-t")
5234
+    flags_with_completion+=("-t")
5235
+    flags_completion+=("_filedir")
5236
+    flags+=("--volumes=")
5237
+    two_word_flags+=("-v")
5238
+    flags+=("--api-version=")
5239
+    flags+=("--as=")
5240
+    flags+=("--certificate-authority=")
5241
+    flags_with_completion+=("--certificate-authority")
5242
+    flags_completion+=("_filedir")
5243
+    flags+=("--client-certificate=")
5244
+    flags_with_completion+=("--client-certificate")
5245
+    flags_completion+=("_filedir")
5246
+    flags+=("--client-key=")
5247
+    flags_with_completion+=("--client-key")
5248
+    flags_completion+=("_filedir")
5249
+    flags+=("--cluster=")
5250
+    flags+=("--config=")
5251
+    flags_with_completion+=("--config")
5252
+    flags_completion+=("_filedir")
5253
+    flags+=("--context=")
5254
+    flags+=("--google-json-key=")
5255
+    flags+=("--insecure-skip-tls-verify")
5256
+    flags+=("--log-flush-frequency=")
5257
+    flags+=("--match-server-version")
5258
+    flags+=("--namespace=")
5259
+    two_word_flags+=("-n")
5260
+    flags+=("--server=")
5261
+    flags+=("--token=")
5262
+    flags+=("--user=")
5263
+
5264
+    must_have_one_flag=()
5265
+    must_have_one_noun=()
5266
+}
5267
+
5194 5268
 _openshift_cli_set_triggers()
5195 5269
 {
5196 5270
     last_command="openshift_cli_set_triggers"
... ...
@@ -5339,6 +5413,7 @@ _openshift_cli_set()
5339 5339
     commands+=("env")
5340 5340
     commands+=("volumes")
5341 5341
     commands+=("probe")
5342
+    commands+=("deployment-hook")
5342 5343
     commands+=("triggers")
5343 5344
     commands+=("build-hook")
5344 5345
 
... ...
@@ -1923,6 +1923,26 @@ Update a build hook on a build config
1923 1923
 ====
1924 1924
 
1925 1925
 
1926
+== oc set deployment-hook
1927
+Update a deployment hook on a deployment config
1928
+
1929
+====
1930
+
1931
+[options="nowrap"]
1932
+----
1933
+  # Clear pre and post hooks on a deployment config
1934
+  oc set deployment-hook dc/myapp --remove --pre --post
1935
+
1936
+  # Set the pre deployment hook to execute a db migration command for an application
1937
+  # using the data volume from the application
1938
+  oc set deployment-hook dc/myapp --pre -v data -- /var/lib/migrate-db.sh
1939
+
1940
+  # Set a mid deployment hook along with additional environment variables
1941
+  oc set deployment-hook dc/myapp --mid -v data -e VAR1=value1 -e VAR2=value2 -- /var/lib/prepare-deploy.sh
1942
+----
1943
+====
1944
+
1945
+
1926 1946
 == oc set env
1927 1947
 Update environment variables on a pod template
1928 1948
 
1929 1949
new file mode 100644
... ...
@@ -0,0 +1,418 @@
0
+package set
1
+
2
+import (
3
+	"fmt"
4
+	"io"
5
+	"os"
6
+
7
+	"github.com/spf13/cobra"
8
+	kapi "k8s.io/kubernetes/pkg/api"
9
+	"k8s.io/kubernetes/pkg/api/meta"
10
+	"k8s.io/kubernetes/pkg/api/unversioned"
11
+	kcmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
12
+	"k8s.io/kubernetes/pkg/kubectl/resource"
13
+	"k8s.io/kubernetes/pkg/runtime"
14
+
15
+	cmdutil "github.com/openshift/origin/pkg/cmd/util"
16
+	"github.com/openshift/origin/pkg/cmd/util/clientcmd"
17
+	deployapi "github.com/openshift/origin/pkg/deploy/api"
18
+)
19
+
20
+const (
21
+	deploymentHookLong = `
22
+Set or remove a deployment hook on a deployment config
23
+
24
+Deployment configs allow hooks to execute at different points in the lifecycle of the 
25
+deployment, depending on the deployment strategy. 
26
+
27
+For deployments with a Recreate strategy, a Pre, Mid, and Post hook can be specified. 
28
+The Pre hook will execute before the deployment starts. The Mid hook will execute once the 
29
+previous deployment has been scaled down to 0, but before the new one ramps up. 
30
+The Post hook will execute once the deployment has completed.
31
+
32
+For deployments with a Rolling strategy, a Pre and Post hook can be specified. 
33
+The Pre hook will execute before the deployment starts and the Post hook will execute once
34
+the deployment has completed.
35
+
36
+For each hook, a new pod will be started using one of the containers in the deployment's pod
37
+template with a specific command to execute. Additional environment variables may be specified
38
+for the hook, as well as which volumes from the pod template will be mounted on the hook pod.
39
+
40
+Each hook can have its own cancellation policy. One of: abort, retry, or ignore. Not all cancellation
41
+policies can be set on all hooks. For example, a Post hook on a rolling strategy does not support
42
+the abort policy, because at that point the deployment has already happened.
43
+`
44
+
45
+	deploymentHookExample = `  # Clear pre and post hooks on a deployment config
46
+  %[1]s deployment-hook dc/myapp --remove --pre --post
47
+
48
+  # Set the pre deployment hook to execute a db migration command for an application
49
+  # using the data volume from the application
50
+  %[1]s deployment-hook dc/myapp --pre -v data -- /var/lib/migrate-db.sh
51
+
52
+  # Set a mid deployment hook along with additional environment variables
53
+  %[1]s deployment-hook dc/myapp --mid -v data -e VAR1=value1 -e VAR2=value2 -- /var/lib/prepare-deploy.sh`
54
+)
55
+
56
+type DeploymentHookOptions struct {
57
+	Out io.Writer
58
+	Err io.Writer
59
+
60
+	Builder *resource.Builder
61
+	Infos   []*resource.Info
62
+
63
+	Encoder       runtime.Encoder
64
+	OutputVersion unversioned.GroupVersion
65
+
66
+	Filenames []string
67
+	Container string
68
+	Selector  string
69
+	All       bool
70
+
71
+	ShortOutput bool
72
+	Mapper      meta.RESTMapper
73
+
74
+	PrintObject func(runtime.Object) error
75
+
76
+	Pre    bool
77
+	Mid    bool
78
+	Post   bool
79
+	Remove bool
80
+
81
+	Command     []string
82
+	Environment []string
83
+	Volumes     []string
84
+
85
+	FailurePolicy deployapi.LifecycleHookFailurePolicy
86
+}
87
+
88
+// NewCmdDeploymentHook implements the set deployment-hook command
89
+func NewCmdDeploymentHook(fullName string, f *clientcmd.Factory, out, errOut io.Writer) *cobra.Command {
90
+	options := &DeploymentHookOptions{
91
+		Out: out,
92
+		Err: errOut,
93
+	}
94
+	cmd := &cobra.Command{
95
+		Use:     "deployment-hook DEPLOYMENTCONFIG --pre|--post|--mid -- CMD",
96
+		Short:   "Update a deployment hook on a deployment config",
97
+		Long:    deploymentHookLong,
98
+		Example: fmt.Sprintf(deploymentHookExample, fullName),
99
+		Run: func(cmd *cobra.Command, args []string) {
100
+			kcmdutil.CheckErr(options.Complete(f, cmd, args))
101
+			kcmdutil.CheckErr(options.Validate())
102
+			if err := options.Run(); err != nil {
103
+				// TODO: move me to kcmdutil
104
+				if err == cmdutil.ErrExit {
105
+					os.Exit(1)
106
+				}
107
+				kcmdutil.CheckErr(err)
108
+			}
109
+		},
110
+	}
111
+
112
+	kcmdutil.AddPrinterFlags(cmd)
113
+	cmd.Flags().StringVarP(&options.Container, "container", "c", options.Container, "The name of the container in the selected deployment config to use for the deployment hook")
114
+	cmd.Flags().StringVarP(&options.Selector, "selector", "l", options.Selector, "Selector (label query) to filter deployment configs")
115
+	cmd.Flags().BoolVar(&options.All, "all", options.All, "Select all deployment configs in the namespace")
116
+	cmd.Flags().StringSliceVarP(&options.Filenames, "filename", "f", options.Filenames, "Filename, directory, or URL to file to use to edit the resource.")
117
+
118
+	cmd.Flags().BoolVar(&options.Remove, "remove", options.Remove, "If true, remove the specified deployment hook(s).")
119
+	cmd.Flags().BoolVar(&options.Pre, "pre", options.Pre, "Set or remove a pre deployment hook")
120
+	cmd.Flags().BoolVar(&options.Mid, "mid", options.Mid, "Set or remove a mid deployment hook")
121
+	cmd.Flags().BoolVar(&options.Post, "post", options.Post, "Set or remove a post deployment hook")
122
+
123
+	cmd.Flags().StringSliceVarP(&options.Environment, "environment", "e", options.Environment, "Environment variables to use in the deployment hook pod")
124
+	cmd.Flags().StringSliceVarP(&options.Volumes, "volumes", "v", options.Volumes, "Volumes from the pod template to use in the deployment hook pod")
125
+
126
+	cmd.Flags().String("failure-policy", "ignore", "The failure policy for the deployment hook. Valid values are: abort,retry,ignore")
127
+
128
+	cmd.MarkFlagFilename("filename", "yaml", "yml", "json")
129
+
130
+	return cmd
131
+}
132
+
133
+func (o *DeploymentHookOptions) Complete(f *clientcmd.Factory, cmd *cobra.Command, args []string) error {
134
+	resources := args
135
+	if i := cmd.ArgsLenAtDash(); i != -1 {
136
+		resources = args[:i]
137
+		o.Command = args[i:]
138
+	}
139
+	if len(o.Filenames) == 0 && len(args) < 1 {
140
+		return kcmdutil.UsageError(cmd, "one or more deployment configs must be specified as <name> or dc/<name>")
141
+	}
142
+
143
+	cmdNamespace, explicit, err := f.DefaultNamespace()
144
+	if err != nil {
145
+		return err
146
+	}
147
+	clientConfig, err := f.ClientConfig()
148
+	if err != nil {
149
+		return err
150
+	}
151
+	o.OutputVersion, err = kcmdutil.OutputVersion(cmd, clientConfig.GroupVersion)
152
+	if err != nil {
153
+		return err
154
+	}
155
+
156
+	mapper, typer := f.Object(false)
157
+	o.Builder = resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), kapi.Codecs.UniversalDecoder()).
158
+		ContinueOnError().
159
+		NamespaceParam(cmdNamespace).DefaultNamespace().
160
+		FilenameParam(explicit, false, o.Filenames...).
161
+		SelectorParam(o.Selector).
162
+		ResourceNames("deploymentconfigs", resources...).
163
+		Flatten()
164
+
165
+	if o.All {
166
+		o.Builder.ResourceTypes("deploymentconfigs").SelectAllParam(o.All)
167
+	}
168
+
169
+	output := kcmdutil.GetFlagString(cmd, "output")
170
+	if len(output) != 0 {
171
+		o.PrintObject = func(obj runtime.Object) error { return f.PrintObject(cmd, mapper, obj, o.Out) }
172
+	}
173
+
174
+	o.Encoder = f.JSONEncoder()
175
+	o.ShortOutput = kcmdutil.GetFlagString(cmd, "output") == "name"
176
+	o.Mapper = mapper
177
+
178
+	failurePolicyString := kcmdutil.GetFlagString(cmd, "failure-policy")
179
+	if len(failurePolicyString) > 0 {
180
+		switch failurePolicyString {
181
+		case "abort":
182
+			o.FailurePolicy = deployapi.LifecycleHookFailurePolicyAbort
183
+		case "ignore":
184
+			o.FailurePolicy = deployapi.LifecycleHookFailurePolicyIgnore
185
+		case "retry":
186
+			o.FailurePolicy = deployapi.LifecycleHookFailurePolicyRetry
187
+		default:
188
+			return kcmdutil.UsageError(cmd, "valid values for --failure-policy are: abort, retry, ignore")
189
+		}
190
+	}
191
+
192
+	return nil
193
+}
194
+
195
+func (o *DeploymentHookOptions) Validate() error {
196
+
197
+	if o.Remove {
198
+		if len(o.Command) > 0 ||
199
+			len(o.Volumes) > 0 ||
200
+			len(o.Environment) > 0 ||
201
+			len(o.Container) > 0 {
202
+			return fmt.Errorf("--remove may not be used with any option except --pre, --mid, or --post")
203
+		}
204
+		if !o.Pre && !o.Mid && !o.Post {
205
+			return fmt.Errorf("you must specify at least one of --pre, --mid, or --post with the --remove flag")
206
+		}
207
+		return nil
208
+	}
209
+
210
+	cnt := 0
211
+	if o.Pre {
212
+		cnt++
213
+	}
214
+	if o.Mid {
215
+		cnt++
216
+	}
217
+	if o.Post {
218
+		cnt++
219
+	}
220
+	if cnt == 0 || cnt > 1 {
221
+		return fmt.Errorf("you must specify one of --pre, --mid, or --post")
222
+	}
223
+
224
+	if len(o.Command) == 0 {
225
+		return fmt.Errorf("you must specify a command for the deployment hook")
226
+	}
227
+	return nil
228
+}
229
+
230
+func (o *DeploymentHookOptions) Run() error {
231
+	infos := o.Infos
232
+	singular := len(o.Infos) <= 1
233
+	if o.Builder != nil {
234
+		loaded, err := o.Builder.Do().IntoSingular(&singular).Infos()
235
+		if err != nil {
236
+			return err
237
+		}
238
+		infos = loaded
239
+	}
240
+
241
+	patches := CalculatePatches(infos, o.Encoder, func(info *resource.Info) (bool, error) {
242
+		dc, ok := info.Object.(*deployapi.DeploymentConfig)
243
+		if !ok {
244
+			return false, nil
245
+		}
246
+		updated, err := o.updateDeploymentConfig(dc)
247
+		return updated, err
248
+	})
249
+
250
+	if singular && len(patches) == 0 {
251
+		return fmt.Errorf("%s/%s is not a deployment config or does not have an applicable strategy", infos[0].Mapping.Resource, infos[0].Name)
252
+	}
253
+
254
+	if o.PrintObject != nil {
255
+		object, err := resource.AsVersionedObject(infos, !singular, o.OutputVersion.String(), kapi.Codecs.LegacyCodec(o.OutputVersion))
256
+		if err != nil {
257
+			return err
258
+		}
259
+		return o.PrintObject(object)
260
+	}
261
+
262
+	failed := false
263
+	for _, patch := range patches {
264
+		info := patch.Info
265
+		if patch.Err != nil {
266
+			fmt.Fprintf(o.Err, "error: %s/%s %v\n", info.Mapping.Resource, info.Name, patch.Err)
267
+			continue
268
+		}
269
+
270
+		if string(patch.Patch) == "{}" || len(patch.Patch) == 0 {
271
+			fmt.Fprintf(o.Err, "info: %s %q was not changed\n", info.Mapping.Resource, info.Name)
272
+			continue
273
+		}
274
+
275
+		obj, err := resource.NewHelper(info.Client, info.Mapping).Patch(info.Namespace, info.Name, kapi.StrategicMergePatchType, patch.Patch)
276
+		if err != nil {
277
+			fmt.Fprintf(o.Err, "error: %v\n", err)
278
+			failed = true
279
+			continue
280
+		}
281
+
282
+		info.Refresh(obj, true)
283
+		kcmdutil.PrintSuccess(o.Mapper, o.ShortOutput, o.Out, info.Mapping.Resource, info.Name, "updated")
284
+	}
285
+	if failed {
286
+		return cmdutil.ErrExit
287
+	}
288
+	return nil
289
+}
290
+
291
+func (o *DeploymentHookOptions) updateDeploymentConfig(dc *deployapi.DeploymentConfig) (bool, error) {
292
+	var (
293
+		err             error
294
+		updatedRecreate bool
295
+		updatedRolling  bool
296
+	)
297
+
298
+	if dc.Spec.Strategy.RecreateParams != nil {
299
+		updatedRecreate, err = o.updateRecreateParams(dc, dc.Spec.Strategy.RecreateParams)
300
+		if err != nil {
301
+			return false, err
302
+		}
303
+	}
304
+	if dc.Spec.Strategy.RollingParams != nil {
305
+		updatedRolling, err = o.updateRollingParams(dc, dc.Spec.Strategy.RollingParams)
306
+		if err != nil {
307
+			return false, err
308
+		}
309
+	}
310
+	return updatedRecreate || updatedRolling, nil
311
+}
312
+
313
+func (o *DeploymentHookOptions) updateRecreateParams(dc *deployapi.DeploymentConfig, strategyParams *deployapi.RecreateDeploymentStrategyParams) (bool, error) {
314
+	var updated bool
315
+	if o.Remove {
316
+		if o.Pre && strategyParams.Pre != nil {
317
+			updated = true
318
+			strategyParams.Pre = nil
319
+		}
320
+		if o.Mid && strategyParams.Mid != nil {
321
+			updated = true
322
+			strategyParams.Mid = nil
323
+		}
324
+		if o.Post && strategyParams.Post != nil {
325
+			updated = true
326
+			strategyParams.Post = nil
327
+		}
328
+		return updated, nil
329
+	}
330
+	hook, err := o.lifecycleHook(dc)
331
+	if err != nil {
332
+		return true, err
333
+	}
334
+	switch {
335
+	case o.Pre:
336
+		strategyParams.Pre = hook
337
+	case o.Mid:
338
+		strategyParams.Mid = hook
339
+	case o.Post:
340
+		strategyParams.Post = hook
341
+	}
342
+	return true, nil
343
+}
344
+
345
+func (o *DeploymentHookOptions) updateRollingParams(dc *deployapi.DeploymentConfig, strategyParams *deployapi.RollingDeploymentStrategyParams) (bool, error) {
346
+	var updated bool
347
+	if o.Remove {
348
+		if o.Pre && strategyParams.Pre != nil {
349
+			updated = true
350
+			strategyParams.Pre = nil
351
+		}
352
+		if o.Post && strategyParams.Post != nil {
353
+			updated = true
354
+			strategyParams.Post = nil
355
+		}
356
+		return updated, nil
357
+	}
358
+	hook, err := o.lifecycleHook(dc)
359
+	if err != nil {
360
+		return true, err
361
+	}
362
+	switch {
363
+	case o.Pre:
364
+		strategyParams.Pre = hook
365
+	case o.Post:
366
+		strategyParams.Post = hook
367
+	}
368
+	return true, nil
369
+}
370
+
371
+func (o *DeploymentHookOptions) lifecycleHook(dc *deployapi.DeploymentConfig) (*deployapi.LifecycleHook, error) {
372
+	hook := &deployapi.LifecycleHook{
373
+		FailurePolicy: o.FailurePolicy,
374
+		ExecNewPod: &deployapi.ExecNewPodHook{
375
+			Command: o.Command,
376
+		},
377
+	}
378
+	if len(o.Container) > 0 {
379
+		found := false
380
+		for _, c := range dc.Spec.Template.Spec.Containers {
381
+			if c.Name == o.Container {
382
+				found = true
383
+				break
384
+			}
385
+		}
386
+		if !found {
387
+			fmt.Fprintf(o.Err, "warning: deployment config %q does not have a container named %q\n", dc.Name, o.Container)
388
+		}
389
+		hook.ExecNewPod.ContainerName = o.Container
390
+	}
391
+	if len(hook.ExecNewPod.ContainerName) == 0 {
392
+		hook.ExecNewPod.ContainerName = dc.Spec.Template.Spec.Containers[0].Name
393
+	}
394
+	if len(o.Environment) > 0 {
395
+		env, _, err := cmdutil.ParseEnv(o.Environment, nil)
396
+		if err != nil {
397
+			return nil, err
398
+		}
399
+		hook.ExecNewPod.Env = env
400
+	}
401
+	if len(o.Volumes) > 0 {
402
+		for _, v := range o.Volumes {
403
+			found := false
404
+			for _, podVolume := range dc.Spec.Template.Spec.Volumes {
405
+				if podVolume.Name == v {
406
+					found = true
407
+					break
408
+				}
409
+			}
410
+			if !found {
411
+				fmt.Fprintf(o.Err, "warning: deployment config %q does not have a volume named %q\n", dc.Name, v)
412
+			}
413
+		}
414
+		hook.ExecNewPod.Volumes = o.Volumes
415
+	}
416
+	return hook, nil
417
+}
... ...
@@ -36,6 +36,7 @@ func NewCmdSet(fullName string, f *clientcmd.Factory, in io.Reader, out, errout
36 36
 				NewCmdEnv(name, f, in, out),
37 37
 				NewCmdVolume(name, f, out, errout),
38 38
 				NewCmdProbe(name, f, out, errout),
39
+				NewCmdDeploymentHook(name, f, out, errout),
39 40
 			},
40 41
 		},
41 42
 		{
... ...
@@ -131,4 +131,44 @@ os::cmd::expect_success 'oc delete hpa/test-deployment-config'
131 131
 echo "autoscale: ok"
132 132
 os::test::junit::declare_suite_end
133 133
 
134
+os::test::junit::declare_suite_start "cmd/deployments/setdeploymenthook"
135
+# Validate the set deployment-hook command
136
+arg="-f test/integration/fixtures/test-deployment-config.yaml"
137
+os::cmd::expect_failure_and_text "oc set deployment-hook" "error: one or more deployment configs"
138
+os::cmd::expect_failure_and_text "oc set deployment-hook ${arg}" "error: you must specify one of --pre, --mid, or --post"
139
+os::cmd::expect_success_and_text "oc set deployment-hook ${arg} --pre  -o yaml -- echo 'hello world'" 'pre:'
140
+os::cmd::expect_success_and_text "oc set deployment-hook ${arg} --pre  -o yaml -- echo 'hello world'" 'execNewPod:'
141
+os::cmd::expect_success_and_text "oc set deployment-hook ${arg} --pre  -o yaml -- echo 'hello world'" '\- echo'
142
+os::cmd::expect_success_and_text "oc set deployment-hook ${arg} --pre  -o yaml -- echo 'hello world'" '\- hello world'
143
+os::cmd::expect_success_and_text "oc set deployment-hook ${arg} --post -o yaml -- echo 'hello world'" 'post:'
144
+os::cmd::expect_success_and_text "oc set deployment-hook ${arg} --mid  -o yaml -- echo 'hello world'" 'mid:'
145
+os::cmd::expect_success_and_text "oc set deployment-hook ${arg} --pre --failure-policy=ignore -o yaml -- echo 'hello world'" 'failurePolicy: Ignore'
146
+os::cmd::expect_success_and_text "oc set deployment-hook ${arg} --pre --failure-policy=retry  -o yaml -- echo 'hello world'" 'failurePolicy: Retry'
147
+os::cmd::expect_success_and_text "oc set deployment-hook ${arg} --pre --failure-policy=abort  -o yaml -- echo 'hello world'" 'failurePolicy: Abort'
148
+# Non-existent container
149
+os::cmd::expect_success_and_text "oc set deployment-hook ${arg} --pre --container=blah -o yaml -- echo 'hello world'" 'does not have a container named'
150
+# Non-existent volume
151
+os::cmd::expect_success_and_text "oc set deployment-hook ${arg} --pre --volumes=blah -o yaml -- echo 'hello world'" 'does not have a volume named'
152
+# Existing container
153
+os::cmd::expect_success_and_not_text "oc set deployment-hook ${arg} --pre --container=ruby-helloworld -o yaml -- echo 'hello world'" 'does not have a container named'
154
+os::cmd::expect_success_and_text "oc set deployment-hook ${arg} --pre --container=ruby-helloworld -o yaml -- echo 'hello world'" 'containerName: ruby-helloworld'
155
+# Existing volume
156
+os::cmd::expect_success_and_not_text "oc set deployment-hook ${arg} --pre --volumes=vol1 -o yaml -- echo 'hello world'" 'does not have a volume named'
157
+os::cmd::expect_success_and_text "oc set deployment-hook ${arg} --pre --volumes=vol1 -o yaml -- echo 'hello world'" '\- vol1'
158
+# Server object tests
159
+os::cmd::expect_success "oc create -f test/integration/fixtures/test-deployment-config.yaml"
160
+os::cmd::expect_failure_and_text "oc set deployment-hook dc/test-deployment-config --pre" "you must specify a command"
161
+os::cmd::expect_success_and_text "oc set deployment-hook test-deployment-config --pre -- echo 'hello world'" "updated"
162
+os::cmd::expect_success_and_text "oc set deployment-hook dc/test-deployment-config --pre -- echo 'hello world'" "was not changed"
163
+os::cmd::expect_success_and_text "oc get dc/test-deployment-config -o yaml" "pre:"
164
+os::cmd::expect_success_and_text "oc set deployment-hook dc/test-deployment-config --pre --failure-policy=abort -- echo 'test'" "updated"
165
+os::cmd::expect_success_and_text "oc get dc/test-deployment-config -o yaml" "failurePolicy: Abort"
166
+os::cmd::expect_success_and_text "oc set deployment-hook --all --pre -- echo 'all dc'" "updated"
167
+os::cmd::expect_success_and_text "oc get dc -o yaml" "all dc"
168
+os::cmd::expect_success_and_text "oc set deployment-hook dc/test-deployment-config --pre --remove" "updated"
169
+os::cmd::expect_success_and_not_text "oc get dc/test-deployment-config -o yaml" "pre:"
170
+os::cmd::expect_success "oc delete dc/test-deployment-config"
171
+echo "set deployment-hook: ok"
172
+os::test::junit::declare_suite_end
173
+
134 174
 os::test::junit::declare_suite_end