Browse code

Make bulk output more centralized

Handle more conditions consistently across all "config generator"
commands.

Simplify ipfailover to be more consistent with other flows.

Clayton Coleman authored on 2016/04/18 12:52:06
Showing 25 changed files
... ...
@@ -1346,7 +1346,6 @@ _oadm_router()
1346 1346
     flags+=("--labels=")
1347 1347
     flags+=("--latest-images")
1348 1348
     flags+=("--metrics-image=")
1349
-    flags+=("--no-headers")
1350 1349
     flags+=("--output=")
1351 1350
     two_word_flags+=("-o")
1352 1351
     flags+=("--output-version=")
... ...
@@ -1355,20 +1354,10 @@ _oadm_router()
1355 1355
     flags+=("--secrets-as-env")
1356 1356
     flags+=("--selector=")
1357 1357
     flags+=("--service-account=")
1358
-    flags+=("--show-all")
1359
-    flags+=("-a")
1360
-    flags+=("--show-labels")
1361
-    flags+=("--sort-by=")
1362 1358
     flags+=("--stats-password=")
1363 1359
     flags+=("--stats-port=")
1364 1360
     flags+=("--stats-user=")
1365 1361
     flags+=("--subdomain=")
1366
-    flags+=("--template=")
1367
-    flags_with_completion+=("--template")
1368
-    flags_completion+=("_filedir")
1369
-    two_word_flags+=("-t")
1370
-    flags_with_completion+=("-t")
1371
-    flags_completion+=("_filedir")
1372 1362
     flags+=("--type=")
1373 1363
     flags+=("--api-version=")
1374 1364
     flags+=("--certificate-authority=")
... ...
@@ -1413,11 +1402,11 @@ _oadm_ipfailover()
1413 1413
     flags+=("--credentials=")
1414 1414
     flags_with_completion+=("--credentials")
1415 1415
     flags_completion+=("__handle_filename_extension_flag kubeconfig")
1416
+    flags+=("--dry-run")
1416 1417
     flags+=("--images=")
1417 1418
     flags+=("--interface=")
1418 1419
     two_word_flags+=("-i")
1419 1420
     flags+=("--latest-images")
1420
-    flags+=("--no-headers")
1421 1421
     flags+=("--output=")
1422 1422
     two_word_flags+=("-o")
1423 1423
     flags+=("--output-version=")
... ...
@@ -1426,16 +1415,6 @@ _oadm_ipfailover()
1426 1426
     flags+=("--selector=")
1427 1427
     two_word_flags+=("-l")
1428 1428
     flags+=("--service-account=")
1429
-    flags+=("--show-all")
1430
-    flags+=("-a")
1431
-    flags+=("--show-labels")
1432
-    flags+=("--sort-by=")
1433
-    flags+=("--template=")
1434
-    flags_with_completion+=("--template")
1435
-    flags_completion+=("_filedir")
1436
-    two_word_flags+=("-t")
1437
-    flags_with_completion+=("-t")
1438
-    flags_completion+=("_filedir")
1439 1429
     flags+=("--type=")
1440 1430
     flags+=("--virtual-ips=")
1441 1431
     flags+=("--vrrp-id-offset=")
... ...
@@ -1489,7 +1468,6 @@ _oadm_registry()
1489 1489
     flags+=("--labels=")
1490 1490
     flags+=("--latest-images")
1491 1491
     flags+=("--mount-host=")
1492
-    flags+=("--no-headers")
1493 1492
     flags+=("--output=")
1494 1493
     two_word_flags+=("-o")
1495 1494
     flags+=("--output-version=")
... ...
@@ -1497,16 +1475,6 @@ _oadm_registry()
1497 1497
     flags+=("--replicas=")
1498 1498
     flags+=("--selector=")
1499 1499
     flags+=("--service-account=")
1500
-    flags+=("--show-all")
1501
-    flags+=("-a")
1502
-    flags+=("--show-labels")
1503
-    flags+=("--sort-by=")
1504
-    flags+=("--template=")
1505
-    flags_with_completion+=("--template")
1506
-    flags_completion+=("_filedir")
1507
-    two_word_flags+=("-t")
1508
-    flags_with_completion+=("-t")
1509
-    flags_completion+=("_filedir")
1510 1500
     flags+=("--tls-certificate=")
1511 1501
     flags+=("--tls-key=")
1512 1502
     flags+=("--type=")
... ...
@@ -458,13 +458,9 @@ _oc_new-app()
458 458
     flags+=("--list")
459 459
     flags+=("-L")
460 460
     flags+=("--name=")
461
-    flags+=("--no-headers")
462 461
     flags+=("--no-install")
463 462
     flags+=("--output=")
464 463
     two_word_flags+=("-o")
465
-    flags+=("--output-template=")
466
-    flags_with_completion+=("--output-template")
467
-    flags_completion+=("_filedir")
468 464
     flags+=("--output-version=")
469 465
     flags+=("--param=")
470 466
     two_word_flags+=("-p")
... ...
@@ -751,24 +747,13 @@ _oc_new-build()
751 751
     flags+=("--labels=")
752 752
     two_word_flags+=("-l")
753 753
     flags+=("--name=")
754
-    flags+=("--no-headers")
755 754
     flags+=("--no-output")
756 755
     flags+=("--output=")
757 756
     two_word_flags+=("-o")
758 757
     flags+=("--output-version=")
759
-    flags+=("--show-all")
760
-    flags+=("-a")
761
-    flags+=("--show-labels")
762
-    flags+=("--sort-by=")
763 758
     flags+=("--source-image=")
764 759
     flags+=("--source-image-path=")
765 760
     flags+=("--strategy=")
766
-    flags+=("--template=")
767
-    flags_with_completion+=("--template")
768
-    flags_completion+=("_filedir")
769
-    two_word_flags+=("-t")
770
-    flags_with_completion+=("-t")
771
-    flags_completion+=("_filedir")
772 761
     flags+=("--to=")
773 762
     flags+=("--to-docker")
774 763
     flags+=("--api-version=")
... ...
@@ -4191,7 +4176,6 @@ _oc_adm_router()
4191 4191
     flags+=("--labels=")
4192 4192
     flags+=("--latest-images")
4193 4193
     flags+=("--metrics-image=")
4194
-    flags+=("--no-headers")
4195 4194
     flags+=("--output=")
4196 4195
     two_word_flags+=("-o")
4197 4196
     flags+=("--output-version=")
... ...
@@ -4200,20 +4184,10 @@ _oc_adm_router()
4200 4200
     flags+=("--secrets-as-env")
4201 4201
     flags+=("--selector=")
4202 4202
     flags+=("--service-account=")
4203
-    flags+=("--show-all")
4204
-    flags+=("-a")
4205
-    flags+=("--show-labels")
4206
-    flags+=("--sort-by=")
4207 4203
     flags+=("--stats-password=")
4208 4204
     flags+=("--stats-port=")
4209 4205
     flags+=("--stats-user=")
4210 4206
     flags+=("--subdomain=")
4211
-    flags+=("--template=")
4212
-    flags_with_completion+=("--template")
4213
-    flags_completion+=("_filedir")
4214
-    two_word_flags+=("-t")
4215
-    flags_with_completion+=("-t")
4216
-    flags_completion+=("_filedir")
4217 4207
     flags+=("--type=")
4218 4208
     flags+=("--api-version=")
4219 4209
     flags+=("--certificate-authority=")
... ...
@@ -4258,11 +4232,11 @@ _oc_adm_ipfailover()
4258 4258
     flags+=("--credentials=")
4259 4259
     flags_with_completion+=("--credentials")
4260 4260
     flags_completion+=("__handle_filename_extension_flag kubeconfig")
4261
+    flags+=("--dry-run")
4261 4262
     flags+=("--images=")
4262 4263
     flags+=("--interface=")
4263 4264
     two_word_flags+=("-i")
4264 4265
     flags+=("--latest-images")
4265
-    flags+=("--no-headers")
4266 4266
     flags+=("--output=")
4267 4267
     two_word_flags+=("-o")
4268 4268
     flags+=("--output-version=")
... ...
@@ -4271,16 +4245,6 @@ _oc_adm_ipfailover()
4271 4271
     flags+=("--selector=")
4272 4272
     two_word_flags+=("-l")
4273 4273
     flags+=("--service-account=")
4274
-    flags+=("--show-all")
4275
-    flags+=("-a")
4276
-    flags+=("--show-labels")
4277
-    flags+=("--sort-by=")
4278
-    flags+=("--template=")
4279
-    flags_with_completion+=("--template")
4280
-    flags_completion+=("_filedir")
4281
-    two_word_flags+=("-t")
4282
-    flags_with_completion+=("-t")
4283
-    flags_completion+=("_filedir")
4284 4274
     flags+=("--type=")
4285 4275
     flags+=("--virtual-ips=")
4286 4276
     flags+=("--vrrp-id-offset=")
... ...
@@ -4334,7 +4298,6 @@ _oc_adm_registry()
4334 4334
     flags+=("--labels=")
4335 4335
     flags+=("--latest-images")
4336 4336
     flags+=("--mount-host=")
4337
-    flags+=("--no-headers")
4338 4337
     flags+=("--output=")
4339 4338
     two_word_flags+=("-o")
4340 4339
     flags+=("--output-version=")
... ...
@@ -4342,16 +4305,6 @@ _oc_adm_registry()
4342 4342
     flags+=("--replicas=")
4343 4343
     flags+=("--selector=")
4344 4344
     flags+=("--service-account=")
4345
-    flags+=("--show-all")
4346
-    flags+=("-a")
4347
-    flags+=("--show-labels")
4348
-    flags+=("--sort-by=")
4349
-    flags+=("--template=")
4350
-    flags_with_completion+=("--template")
4351
-    flags_completion+=("_filedir")
4352
-    two_word_flags+=("-t")
4353
-    flags_with_completion+=("-t")
4354
-    flags_completion+=("_filedir")
4355 4345
     flags+=("--tls-certificate=")
4356 4346
     flags+=("--tls-key=")
4357 4347
     flags+=("--type=")
... ...
@@ -1921,7 +1921,6 @@ _openshift_admin_router()
1921 1921
     flags+=("--labels=")
1922 1922
     flags+=("--latest-images")
1923 1923
     flags+=("--metrics-image=")
1924
-    flags+=("--no-headers")
1925 1924
     flags+=("--output=")
1926 1925
     two_word_flags+=("-o")
1927 1926
     flags+=("--output-version=")
... ...
@@ -1930,20 +1929,10 @@ _openshift_admin_router()
1930 1930
     flags+=("--secrets-as-env")
1931 1931
     flags+=("--selector=")
1932 1932
     flags+=("--service-account=")
1933
-    flags+=("--show-all")
1934
-    flags+=("-a")
1935
-    flags+=("--show-labels")
1936
-    flags+=("--sort-by=")
1937 1933
     flags+=("--stats-password=")
1938 1934
     flags+=("--stats-port=")
1939 1935
     flags+=("--stats-user=")
1940 1936
     flags+=("--subdomain=")
1941
-    flags+=("--template=")
1942
-    flags_with_completion+=("--template")
1943
-    flags_completion+=("_filedir")
1944
-    two_word_flags+=("-t")
1945
-    flags_with_completion+=("-t")
1946
-    flags_completion+=("_filedir")
1947 1937
     flags+=("--type=")
1948 1938
     flags+=("--api-version=")
1949 1939
     flags+=("--certificate-authority=")
... ...
@@ -1988,11 +1977,11 @@ _openshift_admin_ipfailover()
1988 1988
     flags+=("--credentials=")
1989 1989
     flags_with_completion+=("--credentials")
1990 1990
     flags_completion+=("__handle_filename_extension_flag kubeconfig")
1991
+    flags+=("--dry-run")
1991 1992
     flags+=("--images=")
1992 1993
     flags+=("--interface=")
1993 1994
     two_word_flags+=("-i")
1994 1995
     flags+=("--latest-images")
1995
-    flags+=("--no-headers")
1996 1996
     flags+=("--output=")
1997 1997
     two_word_flags+=("-o")
1998 1998
     flags+=("--output-version=")
... ...
@@ -2001,16 +1990,6 @@ _openshift_admin_ipfailover()
2001 2001
     flags+=("--selector=")
2002 2002
     two_word_flags+=("-l")
2003 2003
     flags+=("--service-account=")
2004
-    flags+=("--show-all")
2005
-    flags+=("-a")
2006
-    flags+=("--show-labels")
2007
-    flags+=("--sort-by=")
2008
-    flags+=("--template=")
2009
-    flags_with_completion+=("--template")
2010
-    flags_completion+=("_filedir")
2011
-    two_word_flags+=("-t")
2012
-    flags_with_completion+=("-t")
2013
-    flags_completion+=("_filedir")
2014 2004
     flags+=("--type=")
2015 2005
     flags+=("--virtual-ips=")
2016 2006
     flags+=("--vrrp-id-offset=")
... ...
@@ -2064,7 +2043,6 @@ _openshift_admin_registry()
2064 2064
     flags+=("--labels=")
2065 2065
     flags+=("--latest-images")
2066 2066
     flags+=("--mount-host=")
2067
-    flags+=("--no-headers")
2068 2067
     flags+=("--output=")
2069 2068
     two_word_flags+=("-o")
2070 2069
     flags+=("--output-version=")
... ...
@@ -2072,16 +2050,6 @@ _openshift_admin_registry()
2072 2072
     flags+=("--replicas=")
2073 2073
     flags+=("--selector=")
2074 2074
     flags+=("--service-account=")
2075
-    flags+=("--show-all")
2076
-    flags+=("-a")
2077
-    flags+=("--show-labels")
2078
-    flags+=("--sort-by=")
2079
-    flags+=("--template=")
2080
-    flags_with_completion+=("--template")
2081
-    flags_completion+=("_filedir")
2082
-    two_word_flags+=("-t")
2083
-    flags_with_completion+=("-t")
2084
-    flags_completion+=("_filedir")
2085 2075
     flags+=("--tls-certificate=")
2086 2076
     flags+=("--tls-key=")
2087 2077
     flags+=("--type=")
... ...
@@ -4008,13 +3976,9 @@ _openshift_cli_new-app()
4008 4008
     flags+=("--list")
4009 4009
     flags+=("-L")
4010 4010
     flags+=("--name=")
4011
-    flags+=("--no-headers")
4012 4011
     flags+=("--no-install")
4013 4012
     flags+=("--output=")
4014 4013
     two_word_flags+=("-o")
4015
-    flags+=("--output-template=")
4016
-    flags_with_completion+=("--output-template")
4017
-    flags_completion+=("_filedir")
4018 4014
     flags+=("--output-version=")
4019 4015
     flags+=("--param=")
4020 4016
     two_word_flags+=("-p")
... ...
@@ -4301,24 +4265,13 @@ _openshift_cli_new-build()
4301 4301
     flags+=("--labels=")
4302 4302
     two_word_flags+=("-l")
4303 4303
     flags+=("--name=")
4304
-    flags+=("--no-headers")
4305 4304
     flags+=("--no-output")
4306 4305
     flags+=("--output=")
4307 4306
     two_word_flags+=("-o")
4308 4307
     flags+=("--output-version=")
4309
-    flags+=("--show-all")
4310
-    flags+=("-a")
4311
-    flags+=("--show-labels")
4312
-    flags+=("--sort-by=")
4313 4308
     flags+=("--source-image=")
4314 4309
     flags+=("--source-image-path=")
4315 4310
     flags+=("--strategy=")
4316
-    flags+=("--template=")
4317
-    flags_with_completion+=("--template")
4318
-    flags_completion+=("_filedir")
4319
-    two_word_flags+=("-t")
4320
-    flags_with_completion+=("-t")
4321
-    flags_completion+=("_filedir")
4322 4311
     flags+=("--to=")
4323 4312
     flags+=("--to-docker")
4324 4313
     flags+=("--api-version=")
... ...
@@ -7741,7 +7694,6 @@ _openshift_cli_adm_router()
7741 7741
     flags+=("--labels=")
7742 7742
     flags+=("--latest-images")
7743 7743
     flags+=("--metrics-image=")
7744
-    flags+=("--no-headers")
7745 7744
     flags+=("--output=")
7746 7745
     two_word_flags+=("-o")
7747 7746
     flags+=("--output-version=")
... ...
@@ -7750,20 +7702,10 @@ _openshift_cli_adm_router()
7750 7750
     flags+=("--secrets-as-env")
7751 7751
     flags+=("--selector=")
7752 7752
     flags+=("--service-account=")
7753
-    flags+=("--show-all")
7754
-    flags+=("-a")
7755
-    flags+=("--show-labels")
7756
-    flags+=("--sort-by=")
7757 7753
     flags+=("--stats-password=")
7758 7754
     flags+=("--stats-port=")
7759 7755
     flags+=("--stats-user=")
7760 7756
     flags+=("--subdomain=")
7761
-    flags+=("--template=")
7762
-    flags_with_completion+=("--template")
7763
-    flags_completion+=("_filedir")
7764
-    two_word_flags+=("-t")
7765
-    flags_with_completion+=("-t")
7766
-    flags_completion+=("_filedir")
7767 7757
     flags+=("--type=")
7768 7758
     flags+=("--api-version=")
7769 7759
     flags+=("--certificate-authority=")
... ...
@@ -7808,11 +7750,11 @@ _openshift_cli_adm_ipfailover()
7808 7808
     flags+=("--credentials=")
7809 7809
     flags_with_completion+=("--credentials")
7810 7810
     flags_completion+=("__handle_filename_extension_flag kubeconfig")
7811
+    flags+=("--dry-run")
7811 7812
     flags+=("--images=")
7812 7813
     flags+=("--interface=")
7813 7814
     two_word_flags+=("-i")
7814 7815
     flags+=("--latest-images")
7815
-    flags+=("--no-headers")
7816 7816
     flags+=("--output=")
7817 7817
     two_word_flags+=("-o")
7818 7818
     flags+=("--output-version=")
... ...
@@ -7821,16 +7763,6 @@ _openshift_cli_adm_ipfailover()
7821 7821
     flags+=("--selector=")
7822 7822
     two_word_flags+=("-l")
7823 7823
     flags+=("--service-account=")
7824
-    flags+=("--show-all")
7825
-    flags+=("-a")
7826
-    flags+=("--show-labels")
7827
-    flags+=("--sort-by=")
7828
-    flags+=("--template=")
7829
-    flags_with_completion+=("--template")
7830
-    flags_completion+=("_filedir")
7831
-    two_word_flags+=("-t")
7832
-    flags_with_completion+=("-t")
7833
-    flags_completion+=("_filedir")
7834 7824
     flags+=("--type=")
7835 7825
     flags+=("--virtual-ips=")
7836 7826
     flags+=("--vrrp-id-offset=")
... ...
@@ -7884,7 +7816,6 @@ _openshift_cli_adm_registry()
7884 7884
     flags+=("--labels=")
7885 7885
     flags+=("--latest-images")
7886 7886
     flags+=("--mount-host=")
7887
-    flags+=("--no-headers")
7888 7887
     flags+=("--output=")
7889 7888
     two_word_flags+=("-o")
7890 7889
     flags+=("--output-version=")
... ...
@@ -7892,16 +7823,6 @@ _openshift_cli_adm_registry()
7892 7892
     flags+=("--replicas=")
7893 7893
     flags+=("--selector=")
7894 7894
     flags+=("--service-account=")
7895
-    flags+=("--show-all")
7896
-    flags+=("-a")
7897
-    flags+=("--show-labels")
7898
-    flags+=("--sort-by=")
7899
-    flags+=("--template=")
7900
-    flags_with_completion+=("--template")
7901
-    flags_completion+=("_filedir")
7902
-    two_word_flags+=("-t")
7903
-    flags_with_completion+=("-t")
7904
-    flags_completion+=("_filedir")
7905 7895
     flags+=("--tls-certificate=")
7906 7896
     flags+=("--tls-key=")
7907 7897
     flags+=("--type=")
... ...
@@ -14620,11 +14541,11 @@ _openshift_ex_ipfailover()
14620 14620
     flags+=("--credentials=")
14621 14621
     flags_with_completion+=("--credentials")
14622 14622
     flags_completion+=("__handle_filename_extension_flag kubeconfig")
14623
+    flags+=("--dry-run")
14623 14624
     flags+=("--images=")
14624 14625
     flags+=("--interface=")
14625 14626
     two_word_flags+=("-i")
14626 14627
     flags+=("--latest-images")
14627
-    flags+=("--no-headers")
14628 14628
     flags+=("--output=")
14629 14629
     two_word_flags+=("-o")
14630 14630
     flags+=("--output-version=")
... ...
@@ -14633,16 +14554,6 @@ _openshift_ex_ipfailover()
14633 14633
     flags+=("--selector=")
14634 14634
     two_word_flags+=("-l")
14635 14635
     flags+=("--service-account=")
14636
-    flags+=("--show-all")
14637
-    flags+=("-a")
14638
-    flags+=("--show-labels")
14639
-    flags+=("--sort-by=")
14640
-    flags+=("--template=")
14641
-    flags_with_completion+=("--template")
14642
-    flags_completion+=("_filedir")
14643
-    two_word_flags+=("-t")
14644
-    flags_with_completion+=("-t")
14645
-    flags_completion+=("_filedir")
14646 14636
     flags+=("--type=")
14647 14637
     flags+=("--virtual-ips=")
14648 14638
     flags+=("--vrrp-id-offset=")
... ...
@@ -10,7 +10,6 @@ import (
10 10
 	"k8s.io/kubernetes/pkg/api/validation"
11 11
 	"k8s.io/kubernetes/pkg/serviceaccount"
12 12
 	"k8s.io/kubernetes/pkg/util/sets"
13
-
14 13
 	// uservalidation "github.com/openshift/origin/pkg/user/api/validation"
15 14
 )
16 15
 
... ...
@@ -56,7 +56,7 @@ func NewCommandAdmin(name, fullName string, out io.Writer, errout io.Writer) *co
56 56
 			Message: "Install Commands:",
57 57
 			Commands: []*cobra.Command{
58 58
 				router.NewCmdRouter(f, fullName, "router", out),
59
-				exipfailover.NewCmdIPFailoverConfig(f, fullName, "ipfailover", out),
59
+				exipfailover.NewCmdIPFailoverConfig(f, fullName, "ipfailover", out, errout),
60 60
 				registry.NewCmdRegistry(f, fullName, "registry", out),
61 61
 			},
62 62
 		},
... ...
@@ -67,6 +67,8 @@ NOTE: This command is intended to simplify the tasks of setting up a Docker regi
67 67
 )
68 68
 
69 69
 type RegistryConfig struct {
70
+	Action configcmd.BulkAction
71
+
70 72
 	Name           string
71 73
 	Type           string
72 74
 	ImageTemplate  variable.ImageTemplate
... ...
@@ -89,8 +91,6 @@ type RegistryConfig struct {
89 89
 // randomSecretSize is the number of random bytes to generate.
90 90
 const randomSecretSize = 32
91 91
 
92
-var errExit = fmt.Errorf("exit")
93
-
94 92
 const (
95 93
 	defaultLabel = "docker-registry=default"
96 94
 	defaultPort  = 5000
... ...
@@ -128,7 +128,7 @@ func NewCmdRegistry(f *clientcmd.Factory, parentName, name string, out io.Writer
128 128
 		Example: fmt.Sprintf(registryExample, parentName, name),
129 129
 		Run: func(cmd *cobra.Command, args []string) {
130 130
 			err := RunCmdRegistry(f, cmd, out, cfg, args)
131
-			if err != errExit {
131
+			if err != cmdutil.ErrExit {
132 132
 				kcmdutil.CheckErr(err)
133 133
 			} else {
134 134
 				os.Exit(1)
... ...
@@ -144,7 +144,6 @@ func NewCmdRegistry(f *clientcmd.Factory, parentName, name string, out io.Writer
144 144
 	cmd.Flags().StringVar(&cfg.Labels, "labels", cfg.Labels, "A set of labels to uniquely identify the registry and its components.")
145 145
 	cmd.Flags().StringVar(&cfg.Volume, "volume", cfg.Volume, "The volume path to use for registry storage; defaults to /registry which is the default for origin-docker-registry.")
146 146
 	cmd.Flags().StringVar(&cfg.HostMount, "mount-host", cfg.HostMount, "If set, the registry volume will be created as a host-mount at this path.")
147
-	cmd.Flags().BoolVar(&cfg.DryRun, "dry-run", cfg.DryRun, "Check if the registry exists instead of creating.")
148 147
 	cmd.Flags().Bool("create", false, "deprecated; this is now the default behavior")
149 148
 	cmd.Flags().StringVar(&cfg.Credentials, "credentials", "", "Path to a .kubeconfig file that will contain the credentials the registry should use to contact the master.")
150 149
 	cmd.Flags().StringVar(&cfg.ServiceAccount, "service-account", cfg.ServiceAccount, "Name of the service account to use to run the registry pod.")
... ...
@@ -158,7 +157,8 @@ func NewCmdRegistry(f *clientcmd.Factory, parentName, name string, out io.Writer
158 158
 	// Deprecate credentials
159 159
 	cmd.Flags().MarkDeprecated("credentials", "use --service-account to specify the service account the registry will use to make API calls")
160 160
 
161
-	kcmdutil.AddPrinterFlags(cmd)
161
+	cfg.Action.BindForOutput(cmd.Flags())
162
+	cmd.Flags().String("output-version", "", "The preferred API versions of the output objects")
162 163
 
163 164
 	return cmd
164 165
 }
... ...
@@ -215,22 +215,27 @@ func RunCmdRegistry(f *clientcmd.Factory, cmd *cobra.Command, out io.Writer, cfg
215 215
 		return fmt.Errorf("error getting client: %v", err)
216 216
 	}
217 217
 
218
-	_, output, err := kcmdutil.PrinterForCommand(cmd)
219
-	if err != nil {
220
-		return fmt.Errorf("unable to configure printer: %v", err)
221
-	}
218
+	cfg.Action.Bulk.Mapper = clientcmd.ResourceMapper(f)
219
+	cfg.Action.Out, cfg.Action.ErrOut = out, cmd.Out()
220
+	cfg.Action.Bulk.Op = configcmd.Create
222 221
 
223 222
 	var clusterIP string
224
-	generate := output
225 223
 
226
-	service, err := kClient.Services(namespace).Get(name)
227
-	if err != nil {
228
-		if !errors.IsNotFound(err) && !generate {
229
-			return fmt.Errorf("can't check for existing docker-registry %q: %v", name, err)
224
+	output := cfg.Action.ShouldPrint()
225
+	generate := output
226
+	if !generate {
227
+		service, err := kClient.Services(namespace).Get(name)
228
+		if err != nil {
229
+			if !errors.IsNotFound(err) && !generate {
230
+				return fmt.Errorf("can't check for existing docker-registry %q: %v", name, err)
231
+			}
232
+			if !output && cfg.Action.DryRun {
233
+				return fmt.Errorf("Docker registry %q service does not exist", name)
234
+			}
235
+			generate = true
236
+		} else {
237
+			clusterIP = service.Spec.ClusterIP
230 238
 		}
231
-		generate = true
232
-	} else {
233
-		clusterIP = service.Spec.ClusterIP
234 239
 	}
235 240
 
236 241
 	if !generate {
... ...
@@ -238,10 +243,6 @@ func RunCmdRegistry(f *clientcmd.Factory, cmd *cobra.Command, out io.Writer, cfg
238 238
 		return nil
239 239
 	}
240 240
 
241
-	if cfg.DryRun && !output {
242
-		return fmt.Errorf("docker-registry %q does not exist (no service).", name)
243
-	}
244
-
245 241
 	// create new registry
246 242
 	secretEnv := app.Environment{}
247 243
 	switch {
... ...
@@ -404,7 +405,7 @@ func RunCmdRegistry(f *clientcmd.Factory, cmd *cobra.Command, out io.Writer, cfg
404 404
 	// TODO: label all created objects with the same label
405 405
 	list := &kapi.List{Items: objects}
406 406
 
407
-	if output {
407
+	if cfg.Action.ShouldPrint() {
408 408
 		mapper, _ := f.Object(false)
409 409
 		fn := cmdutil.VersionedPrintObject(f.PrintObject, cmd, mapper, out)
410 410
 		if err := fn(list); err != nil {
... ...
@@ -413,16 +414,8 @@ func RunCmdRegistry(f *clientcmd.Factory, cmd *cobra.Command, out io.Writer, cfg
413 413
 		return nil
414 414
 	}
415 415
 
416
-	mapper, typer := f.Factory.Object(false)
417
-	bulk := configcmd.Bulk{
418
-		Mapper:            mapper,
419
-		Typer:             typer,
420
-		RESTClientFactory: f.Factory.ClientForMapping,
421
-
422
-		After: configcmd.NewPrintNameOrErrorAfter(mapper, kcmdutil.GetFlagString(cmd, "output") == "name", "created", out, cmd.Out()),
423
-	}
424
-	if errs := bulk.Create(list, namespace); len(errs) != 0 {
425
-		return errExit
416
+	if errs := cfg.Action.WithMessage(fmt.Sprintf("Creating registry %s", cfg.Name), "created").Run(list, namespace); len(errs) > 0 {
417
+		return cmdutil.ErrExit
426 418
 	}
427 419
 	return nil
428 420
 }
... ...
@@ -86,6 +86,8 @@ var defaultCertificatePath = path.Join(defaultCertificateDir, "tls.crt")
86 86
 // launch a router, including general parameters, type of router, and
87 87
 // type-specific parameters.
88 88
 type RouterConfig struct {
89
+	Action configcmd.BulkAction
90
+
89 91
 	// Name is the router name, set as an argument
90 92
 	Name string
91 93
 
... ...
@@ -247,7 +249,6 @@ func NewCmdRouter(f *clientcmd.Factory, parentName, name string, out io.Writer)
247 247
 	cmd.Flags().StringVar(&cfg.Ports, "ports", cfg.Ports, "A comma delimited list of ports or port pairs to expose on the router pod. The default is set for HAProxy. Port pairs are applied to the service and to host ports (if specified).")
248 248
 	cmd.Flags().IntVar(&cfg.Replicas, "replicas", cfg.Replicas, "The replication factor of the router; commonly 2 when high availability is desired.")
249 249
 	cmd.Flags().StringVar(&cfg.Labels, "labels", cfg.Labels, "A set of labels to uniquely identify the router and its components.")
250
-	cmd.Flags().BoolVar(&cfg.DryRun, "dry-run", cfg.DryRun, "Exit with code 1 if the specified router does not exist.")
251 250
 	cmd.Flags().BoolVar(&cfg.SecretsAsEnv, "secrets-as-env", cfg.SecretsAsEnv, "Use environment variables for master secrets.")
252 251
 	cmd.Flags().Bool("create", false, "deprecated; this is now the default behavior")
253 252
 	cmd.Flags().StringVar(&cfg.Credentials, "credentials", "", "Path to a .kubeconfig file that will contain the credentials the router should use to contact the master.")
... ...
@@ -276,7 +277,8 @@ func NewCmdRouter(f *clientcmd.Factory, parentName, name string, out io.Writer)
276 276
 	// Deprecate credentials
277 277
 	cmd.Flags().MarkDeprecated("credentials", "use --service-account to specify the service account the router will use to make API calls")
278 278
 
279
-	kcmdutil.AddPrinterFlags(cmd)
279
+	cfg.Action.BindForOutput(cmd.Flags())
280
+	cmd.Flags().String("output-version", "", "The preferred API versions of the output objects")
280 281
 
281 282
 	return cmd
282 283
 }
... ...
@@ -528,19 +530,26 @@ func RunCmdRouter(f *clientcmd.Factory, cmd *cobra.Command, out io.Writer, cfg *
528 528
 		return fmt.Errorf("error getting client: %v", err)
529 529
 	}
530 530
 
531
-	_, output, err := kcmdutil.PrinterForCommand(cmd)
532
-	if err != nil {
533
-		return fmt.Errorf("unable to configure printer: %v", err)
534
-	}
531
+	cfg.Action.Bulk.Mapper = clientcmd.ResourceMapper(f)
532
+	cfg.Action.Out, cfg.Action.ErrOut = out, cmd.Out()
533
+	cfg.Action.Bulk.Op = configcmd.Create
535 534
 
535
+	var clusterIP string
536
+
537
+	output := cfg.Action.ShouldPrint()
536 538
 	generate := output
537 539
 	if !generate {
538
-		_, err = kClient.Services(namespace).Get(name)
540
+		service, err := kClient.Services(namespace).Get(name)
539 541
 		if err != nil {
540 542
 			if !errors.IsNotFound(err) {
541 543
 				return fmt.Errorf("can't check for existing router %q: %v", name, err)
542 544
 			}
545
+			if !output && cfg.Action.DryRun {
546
+				return fmt.Errorf("Router %q service does not exist", name)
547
+			}
543 548
 			generate = true
549
+		} else {
550
+			clusterIP = service.Spec.ClusterIP
544 551
 		}
545 552
 	}
546 553
 	if !generate {
... ...
@@ -548,17 +557,13 @@ func RunCmdRouter(f *clientcmd.Factory, cmd *cobra.Command, out io.Writer, cfg *
548 548
 		return nil
549 549
 	}
550 550
 
551
-	if cfg.DryRun && !output {
552
-		return fmt.Errorf("router %q does not exist (no service)", name)
553
-	}
554
-
555 551
 	if len(cfg.ServiceAccount) == 0 {
556 552
 		return fmt.Errorf("you must specify a service account for the router with --service-account")
557 553
 	}
558 554
 
559 555
 	if err := validateServiceAccount(kClient, namespace, cfg.ServiceAccount, cfg.HostNetwork, cfg.HostPorts); err != nil {
560 556
 		err = fmt.Errorf("router could not be created; %v", err)
561
-		if !output {
557
+		if !cfg.Action.ShouldPrint() {
562 558
 			return err
563 559
 		}
564 560
 		fmt.Fprintf(cmd.Out(), "error: %v\n", err)
... ...
@@ -604,7 +609,7 @@ func RunCmdRouter(f *clientcmd.Factory, cmd *cobra.Command, out io.Writer, cfg *
604 604
 
605 605
 	if len(cfg.StatsPassword) == 0 {
606 606
 		cfg.StatsPassword = generateStatsPassword()
607
-		if !output {
607
+		if !cfg.Action.ShouldPrint() {
608 608
 			fmt.Fprintf(cmd.Out(), "info: password for stats user %s has been set to %s\n", cfg.StatsUsername, cfg.StatsPassword)
609 609
 		}
610 610
 	}
... ...
@@ -735,6 +740,7 @@ func RunCmdRouter(f *clientcmd.Factory, cmd *cobra.Command, out io.Writer, cfg *
735 735
 	for i := range objects {
736 736
 		switch t := objects[i].(type) {
737 737
 		case *kapi.Service:
738
+			t.Spec.ClusterIP = clusterIP
738 739
 			for j, servicePort := range t.Spec.Ports {
739 740
 				for _, targetPort := range ports {
740 741
 					if targetPort.ContainerPort == servicePort.Port && targetPort.HostPort != 0 {
... ...
@@ -747,7 +753,7 @@ func RunCmdRouter(f *clientcmd.Factory, cmd *cobra.Command, out io.Writer, cfg *
747 747
 	// TODO: label all created objects with the same label - router=<name>
748 748
 	list := &kapi.List{Items: objects}
749 749
 
750
-	if output {
750
+	if cfg.Action.ShouldPrint() {
751 751
 		mapper, _ := f.Object(false)
752 752
 		fn := cmdutil.VersionedPrintObject(f.PrintObject, cmd, mapper, out)
753 753
 		if err := fn(list); err != nil {
... ...
@@ -756,15 +762,7 @@ func RunCmdRouter(f *clientcmd.Factory, cmd *cobra.Command, out io.Writer, cfg *
756 756
 		return defaultOutputErr
757 757
 	}
758 758
 
759
-	mapper, typer := f.Factory.Object(false)
760
-	bulk := configcmd.Bulk{
761
-		Mapper:            mapper,
762
-		Typer:             typer,
763
-		RESTClientFactory: f.Factory.ClientForMapping,
764
-
765
-		After: configcmd.NewPrintNameOrErrorAfter(mapper, kcmdutil.GetFlagString(cmd, "output") == "name", "created", out, cmd.Out()),
766
-	}
767
-	if errs := bulk.Create(list, namespace); len(errs) != 0 {
759
+	if errs := cfg.Action.WithMessage(fmt.Sprintf("Creating router %s", cfg.Name), "created").Run(list, namespace); len(errs) > 0 {
768 760
 		return cmdutil.ErrExit
769 761
 	}
770 762
 	return nil
... ...
@@ -23,7 +23,6 @@ import (
23 23
 	kcmd "k8s.io/kubernetes/pkg/kubectl/cmd"
24 24
 	kcmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
25 25
 	"k8s.io/kubernetes/pkg/kubectl/resource"
26
-	"k8s.io/kubernetes/pkg/labels"
27 26
 	"k8s.io/kubernetes/pkg/runtime"
28 27
 	"k8s.io/kubernetes/pkg/util/errors"
29 28
 	"k8s.io/kubernetes/pkg/util/sets"
... ...
@@ -119,6 +118,7 @@ To search templates, image streams, and Docker images that match the arguments p
119 119
 )
120 120
 
121 121
 type NewAppOptions struct {
122
+	Action configcmd.BulkAction
122 123
 	Config *newcmd.AppConfig
123 124
 
124 125
 	CommandPath string
... ...
@@ -175,16 +175,9 @@ func NewCmdNewApplication(commandName string, f *clientcmd.Factory, out io.Write
175 175
 	cmd.Flags().BoolVar(&config.AllowMissingImageStreamTags, "allow-missing-imagestream-tags", false, "If true, indicates that image stream tags that don't exist should still be used.")
176 176
 	cmd.Flags().BoolVar(&config.AllowSecretUse, "grant-install-rights", false, "If true, a component that requires access to your account may use your token to install software into your project. Only grant images you trust the right to run with your token.")
177 177
 	cmd.Flags().BoolVar(&config.SkipGeneration, "no-install", false, "Do not attempt to run images that describe themselves as being installable")
178
-	cmd.Flags().BoolVar(&config.DryRun, "dry-run", false, "If true, do not actually create resources.")
179 178
 
180
-	// TODO AddPrinterFlags disabled so that it doesn't conflict with our own "template" flag.
181
-	// Need a better solution.
182
-	// kcmdutil.AddPrinterFlags(cmd)
183
-	cmd.Flags().StringP("output", "o", "", "Output format. One of: json|yaml|template|templatefile.")
184
-	cmd.Flags().String("output-version", "", "Output the formatted object with the given version (default api-version).")
185
-	cmd.Flags().Bool("no-headers", false, "When using the default output, don't print headers.")
186
-	cmd.Flags().String("output-template", "", "Template string or path to template file to use when -o=template or -o=templatefile.  The template format is golang templates [http://golang.org/pkg/text/template/#pkg-overview]")
187
-	cmd.MarkFlagFilename("output-template")
179
+	options.Action.BindForOutput(cmd.Flags())
180
+	cmd.Flags().String("output-version", "", "The preferred API versions of the output objects")
188 181
 
189 182
 	return cmd
190 183
 }
... ...
@@ -203,6 +196,15 @@ func (o *NewAppOptions) Complete(commandName string, f *clientcmd.Factory, c *co
203 203
 	}
204 204
 	o.Config.ErrOut = o.ErrOut
205 205
 
206
+	o.Action.Out, o.Action.ErrOut = o.Out, o.ErrOut
207
+	o.Action.Bulk.Mapper = clientcmd.ResourceMapper(f)
208
+	o.Action.Bulk.Op = configcmd.Create
209
+	// Retry is used to support previous versions of the API server that will
210
+	// consider the presence of an unknown trigger type to be an error.
211
+	o.Action.Bulk.Retry = retryBuildConfig
212
+
213
+	o.Config.DryRun = o.Action.DryRun
214
+
206 215
 	o.CommandPath = c.CommandPath()
207 216
 	o.CommandName = commandName
208 217
 	mapper, _ := f.Object(false)
... ...
@@ -221,8 +223,6 @@ func (o *NewAppOptions) Complete(commandName string, f *clientcmd.Factory, c *co
221 221
 func (o *NewAppOptions) Run() error {
222 222
 	config := o.Config
223 223
 	out := o.Out
224
-	output := o.Output
225
-	shortOutput := output == "name"
226 224
 
227 225
 	if config.Querying() {
228 226
 		result, err := config.RunQuery()
... ...
@@ -230,7 +230,7 @@ func (o *NewAppOptions) Run() error {
230 230
 			return handleRunError(err, o.CommandName, o.CommandPath)
231 231
 		}
232 232
 
233
-		if len(output) != 0 {
233
+		if o.Action.ShouldPrint() {
234 234
 			return o.PrintObject(result.List)
235 235
 		}
236 236
 
... ...
@@ -258,44 +258,25 @@ func (o *NewAppOptions) Run() error {
258 258
 		return err
259 259
 	}
260 260
 
261
-	indent := "    "
262
-	switch {
263
-	case shortOutput:
264
-		indent = ""
265
-	case len(output) != 0:
261
+	if o.Action.ShouldPrint() {
266 262
 		return o.PrintObject(result.List)
267
-	case !result.GeneratedJobs:
268
-		if len(config.Labels) > 0 {
269
-			fmt.Fprintf(out, "--> Creating resources with label %s ...\n", labels.SelectorFromSet(config.Labels).String())
270
-		} else {
271
-			fmt.Fprintf(out, "--> Creating resources ...\n")
272
-		}
273
-	}
274
-	if config.DryRun {
275
-		fmt.Fprintf(out, "--> Success (DRY RUN)\n")
276
-		return nil
277 263
 	}
278 264
 
279
-	var afterFn configcmd.AfterFunc
280
-	switch {
281
-	// only print success if we don't have installables
282
-	case !result.GeneratedJobs:
283
-		afterFn = configcmd.NewPrintNameOrErrorAfterIndent(config.Mapper, shortOutput, "created", out, config.ErrOut, indent)
284
-	default:
285
-		afterFn = configcmd.NewPrintErrorAfter(config.Mapper, config.ErrOut)
286
-		afterFn = configcmd.HaltOnError(afterFn)
265
+	if result.GeneratedJobs {
266
+		o.Action.Compact()
287 267
 	}
288 268
 
289
-	if err := createObjects(config, afterFn, result); err != nil {
290
-		return err
269
+	if errs := o.Action.WithMessage(configcmd.CreateMessage(config.Labels), "created").Run(result.List, result.Namespace); len(errs) > 0 {
270
+		return cmdutil.ErrExit
291 271
 	}
292 272
 
293
-	if !shortOutput && !result.GeneratedJobs {
294
-		fmt.Fprintf(out, "--> Success\n")
273
+	if !o.Action.Verbose() || o.Action.DryRun {
274
+		return nil
295 275
 	}
296 276
 
297 277
 	hasMissingRepo := false
298 278
 	installing := []*kapi.Pod{}
279
+	indent := o.Action.DefaultIndent()
299 280
 	for _, item := range result.List.Items {
300 281
 		switch t := item.(type) {
301 282
 		case *kapi.Pod:
... ...
@@ -327,10 +308,6 @@ func (o *NewAppOptions) Run() error {
327 327
 		}
328 328
 	}
329 329
 
330
-	if shortOutput {
331
-		return nil
332
-	}
333
-
334 330
 	switch {
335 331
 	case len(installing) == 1:
336 332
 		jobInput := installing[0].Annotations[newcmd.GeneratedForJobFor]
... ...
@@ -574,23 +551,6 @@ func retryBuildConfig(info *resource.Info, err error) runtime.Object {
574 574
 	return nil
575 575
 }
576 576
 
577
-func createObjects(config *newcmd.AppConfig, after configcmd.AfterFunc, result *newcmd.AppResult) error {
578
-	bulk := configcmd.Bulk{
579
-		Mapper:            config.Mapper,
580
-		Typer:             config.Typer,
581
-		RESTClientFactory: config.ClientMapper.ClientForMapping,
582
-
583
-		After: after,
584
-		// Retry is used to support previous versions of the API server that will
585
-		// consider the presence of an unknown trigger type to be an error.
586
-		Retry: retryBuildConfig,
587
-	}
588
-	if errs := bulk.Create(result.List, result.Namespace); len(errs) != 0 {
589
-		return cmdutil.ErrExit
590
-	}
591
-	return nil
592
-}
593
-
594 577
 func handleRunError(err error, commandName, commandPath string) error {
595 578
 	if err == nil {
596 579
 		return nil
... ...
@@ -20,7 +20,6 @@ import (
20 20
 	configcmd "github.com/openshift/origin/pkg/config/cmd"
21 21
 	newapp "github.com/openshift/origin/pkg/generate/app"
22 22
 	newcmd "github.com/openshift/origin/pkg/generate/app/cmd"
23
-	"k8s.io/kubernetes/pkg/labels"
24 23
 )
25 24
 
26 25
 const (
... ...
@@ -80,6 +79,7 @@ on the Docker Hub.
80 80
 )
81 81
 
82 82
 type NewBuildOptions struct {
83
+	Action configcmd.BulkAction
83 84
 	Config *newcmd.AppConfig
84 85
 
85 86
 	CommandPath string
... ...
@@ -131,11 +131,12 @@ func NewCmdNewBuild(fullName string, f *clientcmd.Factory, in io.Reader, out io.
131 131
 	cmd.Flags().BoolVar(&config.AllowMissingImages, "allow-missing-images", false, "If true, indicates that referenced Docker images that cannot be found locally or in a registry should still be used.")
132 132
 	cmd.Flags().BoolVar(&config.AllowMissingImageStreamTags, "allow-missing-imagestream-tags", false, "If true, indicates that image stream tags that don't exist should still be used.")
133 133
 	cmd.Flags().StringVar(&config.ContextDir, "context-dir", "", "Context directory to be used for the build.")
134
-	cmd.Flags().BoolVar(&config.DryRun, "dry-run", false, "If true, do not actually create resources.")
135 134
 	cmd.Flags().BoolVar(&config.NoOutput, "no-output", false, "If true, the build output will not be pushed anywhere.")
136 135
 	cmd.Flags().StringVar(&config.SourceImage, "source-image", "", "Specify an image to use as source for the build.  You must also specify --source-image-path.")
137 136
 	cmd.Flags().StringVar(&config.SourceImagePath, "source-image-path", "", "Specify the file or directory to copy from the source image and its destination in the build directory. Format: [source]:[destination-dir].")
138
-	kcmdutil.AddPrinterFlags(cmd)
137
+
138
+	options.Action.BindForOutput(cmd.Flags())
139
+	cmd.Flags().String("output-version", "", "The preferred API versions of the output objects")
139 140
 
140 141
 	return cmd
141 142
 }
... ...
@@ -154,6 +155,15 @@ func (o *NewBuildOptions) Complete(fullName string, f *clientcmd.Factory, c *cob
154 154
 	}
155 155
 	o.Config.ErrOut = o.ErrOut
156 156
 
157
+	o.Action.Out, o.Action.ErrOut = o.Out, o.ErrOut
158
+	o.Action.Bulk.Mapper = clientcmd.ResourceMapper(f)
159
+	o.Action.Bulk.Op = configcmd.Create
160
+	// Retry is used to support previous versions of the API server that will
161
+	// consider the presence of an unknown trigger type to be an error.
162
+	o.Action.Bulk.Retry = retryBuildConfig
163
+
164
+	o.Config.DryRun = o.Action.DryRun
165
+
157 166
 	o.CommandPath = c.CommandPath()
158 167
 	o.CommandName = fullName
159 168
 	mapper, _ := f.Object(false)
... ...
@@ -179,8 +189,6 @@ func (o *NewBuildOptions) Complete(fullName string, f *clientcmd.Factory, c *cob
179 179
 func (o *NewBuildOptions) Run() error {
180 180
 	config := o.Config
181 181
 	out := o.Out
182
-	output := o.Output
183
-	shortOutput := output == "name"
184 182
 
185 183
 	result, err := config.Run()
186 184
 	if err != nil {
... ...
@@ -198,34 +206,19 @@ func (o *NewBuildOptions) Run() error {
198 198
 		return err
199 199
 	}
200 200
 
201
-	indent := "    "
202
-	switch {
203
-	case shortOutput:
204
-		indent = ""
205
-	case len(output) != 0:
201
+	if o.Action.ShouldPrint() {
206 202
 		return o.PrintObject(result.List)
207
-	default:
208
-		if len(config.Labels) > 0 {
209
-			fmt.Fprintf(out, "--> Creating resources with label %s ...\n", labels.SelectorFromSet(config.Labels).String())
210
-		} else {
211
-			fmt.Fprintf(out, "--> Creating resources ...\n")
212
-		}
213
-	}
214
-	if config.DryRun {
215
-		fmt.Fprintf(out, "--> Success (DRY RUN)\n")
216
-		return nil
217 203
 	}
218 204
 
219
-	afterFn := configcmd.NewPrintNameOrErrorAfterIndent(config.Mapper, shortOutput, "created", out, config.ErrOut, indent)
220
-	if err := createObjects(config, afterFn, result); err != nil {
221
-		return err
205
+	if errs := o.Action.WithMessage(configcmd.CreateMessage(config.Labels), "created").Run(result.List, result.Namespace); len(errs) > 0 {
206
+		return cmdutil.ErrExit
222 207
 	}
223 208
 
224
-	if shortOutput {
209
+	if !o.Action.Verbose() || o.Action.DryRun {
225 210
 		return nil
226 211
 	}
227 212
 
228
-	fmt.Fprintf(out, "--> Success\n")
213
+	indent := o.Action.DefaultIndent()
229 214
 	for _, item := range result.List.Items {
230 215
 		switch t := item.(type) {
231 216
 		case *buildapi.BuildConfig:
... ...
@@ -3,12 +3,16 @@ package ipfailover
3 3
 import (
4 4
 	"fmt"
5 5
 	"io"
6
+	"os"
6 7
 
7 8
 	"github.com/spf13/cobra"
8
-	cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
9 9
 
10
+	kcmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
11
+
12
+	cmdutil "github.com/openshift/origin/pkg/cmd/util"
10 13
 	"github.com/openshift/origin/pkg/cmd/util/clientcmd"
11 14
 	"github.com/openshift/origin/pkg/cmd/util/variable"
15
+	configcmd "github.com/openshift/origin/pkg/config/cmd"
12 16
 	"github.com/openshift/origin/pkg/ipfailover"
13 17
 	"github.com/openshift/origin/pkg/ipfailover/keepalived"
14 18
 )
... ...
@@ -45,8 +49,12 @@ value that matches the number of nodes for the given labeled selector.`
45 45
   $ %[1]s %[2]s ipf-alt --selector="hagroup=us-west-ha" --virtual-ips="1.2.3.4" -o yaml --images=myrepo/myipfailover:mytag`
46 46
 )
47 47
 
48
-func NewCmdIPFailoverConfig(f *clientcmd.Factory, parentName, name string, out io.Writer) *cobra.Command {
48
+func NewCmdIPFailoverConfig(f *clientcmd.Factory, parentName, name string, out, errout io.Writer) *cobra.Command {
49 49
 	options := &ipfailover.IPFailoverConfigCmdOptions{
50
+		Action: configcmd.BulkAction{
51
+			Out:    out,
52
+			ErrOut: errout,
53
+		},
50 54
 		ImageTemplate:    variable.NewDefaultImageTemplate(),
51 55
 		Selector:         ipfailover.DefaultSelector,
52 56
 		ServicePort:      ipfailover.DefaultServicePort,
... ...
@@ -62,9 +70,11 @@ func NewCmdIPFailoverConfig(f *clientcmd.Factory, parentName, name string, out i
62 62
 		Long:    ipFailover_long,
63 63
 		Example: fmt.Sprintf(ipFailover_example, parentName, name),
64 64
 		Run: func(cmd *cobra.Command, args []string) {
65
-			options.ShortOutput = cmdutil.GetFlagString(cmd, "output") == "name"
66
-			err := processCommand(f, options, cmd, args, out)
67
-			cmdutil.CheckErr(err)
65
+			err := Run(f, options, cmd, args)
66
+			if err == cmdutil.ErrExit {
67
+				os.Exit(1)
68
+			}
69
+			kcmdutil.CheckErr(err)
68 70
 		},
69 71
 	}
70 72
 
... ...
@@ -87,7 +97,9 @@ func NewCmdIPFailoverConfig(f *clientcmd.Factory, parentName, name string, out i
87 87
 	// autocompletion hints
88 88
 	cmd.MarkFlagFilename("credentials", "kubeconfig")
89 89
 
90
-	cmdutil.AddPrinterFlags(cmd)
90
+	options.Action.BindForOutput(cmd.Flags())
91
+	cmd.Flags().String("output-version", "", "The preferred API versions of the output objects")
92
+
91 93
 	return cmd
92 94
 }
93 95
 
... ...
@@ -108,7 +120,7 @@ func getConfigurationName(args []string) (string, error) {
108 108
 }
109 109
 
110 110
 //  Get the configurator based on the ipfailover type.
111
-func getConfigurator(name string, f *clientcmd.Factory, options *ipfailover.IPFailoverConfigCmdOptions, out io.Writer) (*ipfailover.Configurator, error) {
111
+func getPlugin(name string, f *clientcmd.Factory, options *ipfailover.IPFailoverConfigCmdOptions) (ipfailover.IPFailoverConfiguratorPlugin, error) {
112 112
 	//  Currently, the only supported plugin is keepalived (default).
113 113
 	plugin, err := keepalived.NewIPFailoverConfiguratorPlugin(name, f, options)
114 114
 
... ...
@@ -124,59 +136,45 @@ func getConfigurator(name string, f *clientcmd.Factory, options *ipfailover.IPFa
124 124
 		return nil, fmt.Errorf("IPFailoverConfigurator %q plugin error: %v", options.Type, err)
125 125
 	}
126 126
 
127
-	return ipfailover.NewConfigurator(name, plugin, out), nil
127
+	return plugin, nil
128 128
 }
129 129
 
130
-//  Preview the configuration if required - returns true|false and errors.
131
-func previewConfiguration(c *ipfailover.Configurator, cmd *cobra.Command, out io.Writer) (bool, error) {
132
-	p, output, err := cmdutil.PrinterForCommand(cmd)
130
+// Run runs the ipfailover command.
131
+func Run(f *clientcmd.Factory, options *ipfailover.IPFailoverConfigCmdOptions, cmd *cobra.Command, args []string) error {
132
+	name, err := getConfigurationName(args)
133 133
 	if err != nil {
134
-		return true, fmt.Errorf("Error configuring printer: %v", err)
135
-	}
136
-
137
-	// Check if we are outputting info.
138
-	if !output {
139
-		return false, nil
134
+		return err
140 135
 	}
141 136
 
142
-	configList, err := c.Generate()
143
-	if err != nil {
144
-		return true, fmt.Errorf("Error generating config: %v", err)
145
-	}
137
+	options.Action.Bulk.Mapper = clientcmd.ResourceMapper(f)
138
+	options.Action.Bulk.Op = configcmd.Create
146 139
 
147
-	if err := p.PrintObj(configList, out); err != nil {
148
-		return true, fmt.Errorf("Unable to print object: %v", err)
140
+	if err := ipfailover.ValidateCmdOptions(options); err != nil {
141
+		return err
149 142
 	}
150 143
 
151
-	return true, nil
152
-}
153
-
154
-//  Process the ipfailover command.
155
-func processCommand(f *clientcmd.Factory, options *ipfailover.IPFailoverConfigCmdOptions, cmd *cobra.Command, args []string, out io.Writer) error {
156
-	name, err := getConfigurationName(args)
144
+	p, err := getPlugin(name, f, options)
157 145
 	if err != nil {
158 146
 		return err
159 147
 	}
160 148
 
161
-	c, err := getConfigurator(name, f, options, out)
149
+	list, err := p.Generate()
162 150
 	if err != nil {
163 151
 		return err
164 152
 	}
165 153
 
166
-	//  First up, validate all the command line options.
167
-	if err := ipfailover.ValidateCmdOptions(options, c); err != nil {
168
-		return err
154
+	if options.Action.ShouldPrint() {
155
+		mapper, _ := f.Object(false)
156
+		return cmdutil.VersionedPrintObject(f.PrintObject, cmd, mapper, options.Action.Out)(list)
169 157
 	}
170 158
 
171
-	//  Check if we are just previewing the config.
172
-	previewFlag, err := previewConfiguration(c, cmd, out)
173
-	if previewFlag {
159
+	namespace, _, err := f.DefaultNamespace()
160
+	if err != nil {
174 161
 		return err
175 162
 	}
176 163
 
177
-	if options.Create {
178
-		return c.Create()
164
+	if errs := options.Action.WithMessage(fmt.Sprintf("Creating IP failover %s", name), "created").Run(list, namespace); len(errs) > 0 {
165
+		return cmdutil.ErrExit
179 166
 	}
180
-
181 167
 	return nil
182 168
 }
... ...
@@ -139,6 +139,7 @@ func NewCommandOpenShift(name string) *cobra.Command {
139 139
 
140 140
 func newExperimentalCommand(name, fullName string) *cobra.Command {
141 141
 	out := os.Stdout
142
+	errout := os.Stderr
142 143
 
143 144
 	experimental := &cobra.Command{
144 145
 		Use:   name,
... ...
@@ -154,7 +155,7 @@ func newExperimentalCommand(name, fullName string) *cobra.Command {
154 154
 	f := clientcmd.New(experimental.PersistentFlags())
155 155
 
156 156
 	experimental.AddCommand(validate.NewCommandValidate(validate.ValidateRecommendedName, fullName+" "+validate.ValidateRecommendedName, out))
157
-	experimental.AddCommand(exipfailover.NewCmdIPFailoverConfig(f, fullName, "ipfailover", out))
157
+	experimental.AddCommand(exipfailover.NewCmdIPFailoverConfig(f, fullName, "ipfailover", out, errout))
158 158
 	experimental.AddCommand(buildchain.NewCmdBuildChain(name, fullName+" "+buildchain.BuildChainRecommendedCommandName, f, out))
159 159
 	deprecatedDiag := diagnostics.NewCmdDiagnostics(diagnostics.DiagnosticsRecommendedName, fullName+" "+diagnostics.DiagnosticsRecommendedName, out)
160 160
 	deprecatedDiag.Deprecated = fmt.Sprintf(`use "oadm %[1]s" to run diagnostics instead.`, diagnostics.DiagnosticsRecommendedName)
... ...
@@ -478,6 +478,15 @@ func getPorts(spec api.PodSpec) []string {
478 478
 	return result
479 479
 }
480 480
 
481
+func ResourceMapper(f *Factory) *resource.Mapper {
482
+	mapper, typer := f.Object(false)
483
+	return &resource.Mapper{
484
+		RESTMapper:   mapper,
485
+		ObjectTyper:  typer,
486
+		ClientMapper: resource.ClientMapperFunc(f.ClientForMapping),
487
+	}
488
+}
489
+
481 490
 // UpdateObjectEnvironment update the environment variables in object specification.
482 491
 func (f *Factory) UpdateObjectEnvironment(obj runtime.Object, fn func(*[]api.EnvVar) error) (bool, error) {
483 492
 	switch t := obj.(type) {
... ...
@@ -4,23 +4,85 @@ import (
4 4
 	"fmt"
5 5
 	"io"
6 6
 
7
+	"github.com/spf13/pflag"
8
+
7 9
 	kapi "k8s.io/kubernetes/pkg/api"
8 10
 	"k8s.io/kubernetes/pkg/api/meta"
11
+	"k8s.io/kubernetes/pkg/api/unversioned"
9 12
 	cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util"
10 13
 	"k8s.io/kubernetes/pkg/kubectl/resource"
14
+	"k8s.io/kubernetes/pkg/labels"
11 15
 	"k8s.io/kubernetes/pkg/runtime"
12 16
 )
13 17
 
18
+type Runner interface {
19
+	Run(list *kapi.List, namespace string) []error
20
+}
21
+
14 22
 // AfterFunc takes an info and an error, and returns true if processing should stop.
15 23
 type AfterFunc func(*resource.Info, error) bool
16 24
 
25
+// OpFunc takes the provided info and attempts to perform the operation
26
+type OpFunc func(info *resource.Info, namespace string, obj runtime.Object) (runtime.Object, error)
27
+
28
+// RetryFunc can retry the operation a single time by returning a non-nil object.
29
+// TODO: make this a more general retry "loop" function rather than one time.
30
+type RetryFunc func(info *resource.Info, err error) runtime.Object
31
+
32
+// Mapper is an interface testability that is equivalent to resource.Mapper
33
+type Mapper interface {
34
+	meta.RESTMapper
35
+	InfoForObject(obj runtime.Object, preferredGVKs []unversioned.GroupVersionKind) (*resource.Info, error)
36
+}
37
+
17 38
 // Bulk provides helpers for iterating over a list of items
18 39
 type Bulk struct {
19
-	Mapper            meta.RESTMapper
20
-	Typer             runtime.ObjectTyper
21
-	RESTClientFactory func(mapping *meta.RESTMapping) (resource.RESTClient, error)
22
-	After             AfterFunc
23
-	Retry             func(info *resource.Info, err error) runtime.Object
40
+	Mapper Mapper
41
+
42
+	Op    OpFunc
43
+	After AfterFunc
44
+	Retry RetryFunc
45
+}
46
+
47
+// Create attempts to create each item generically, gathering all errors in the
48
+// event a failure occurs. The contents of list will be updated to include the
49
+// version from the server.
50
+func (b *Bulk) Run(list *kapi.List, namespace string) []error {
51
+	after := b.After
52
+	if after == nil {
53
+		after = func(*resource.Info, error) bool { return false }
54
+	}
55
+
56
+	errs := []error{}
57
+	for i, item := range list.Items {
58
+		info, err := b.Mapper.InfoForObject(item, nil)
59
+		if err != nil {
60
+			errs = append(errs, err)
61
+			if after(info, err) {
62
+				break
63
+			}
64
+			continue
65
+		}
66
+		obj, err := b.Op(info, namespace, item)
67
+		if err != nil && b.Retry != nil {
68
+			if obj := b.Retry(info, err); obj != nil {
69
+				obj, err = b.Op(info, namespace, obj)
70
+			}
71
+		}
72
+		if err != nil {
73
+			errs = append(errs, err)
74
+			if after(info, err) {
75
+				break
76
+			}
77
+			continue
78
+		}
79
+		info.Refresh(obj, true)
80
+		list.Items[i] = obj
81
+		if after(info, nil) {
82
+			break
83
+		}
84
+	}
85
+	return errs
24 86
 }
25 87
 
26 88
 func NewPrintNameOrErrorAfter(mapper meta.RESTMapper, short bool, operation string, out, errs io.Writer) AfterFunc {
... ...
@@ -57,47 +119,112 @@ func HaltOnError(fn AfterFunc) AfterFunc {
57 57
 	}
58 58
 }
59 59
 
60
-func encodeAndCreate(info *resource.Info, namespace string, obj runtime.Object) (runtime.Object, error) {
60
+// Create is the default create operation for a generic resource.
61
+func Create(info *resource.Info, namespace string, obj runtime.Object) (runtime.Object, error) {
61 62
 	return resource.NewHelper(info.Client, info.Mapping).Create(namespace, false, obj)
62 63
 }
63 64
 
64
-// Create attempts to create each item generically, gathering all errors in the
65
-// event a failure occurs. The contents of list will be updated to include the
66
-// version from the server.
67
-func (b *Bulk) Create(list *kapi.List, namespace string) []error {
68
-	resourceMapper := &resource.Mapper{ObjectTyper: b.Typer, RESTMapper: b.Mapper, ClientMapper: resource.ClientMapperFunc(b.RESTClientFactory)}
69
-	after := b.After
70
-	if after == nil {
71
-		after = func(*resource.Info, error) bool { return false }
65
+func NoOp(info *resource.Info, namespace string, obj runtime.Object) (runtime.Object, error) {
66
+	return info.Object, nil
67
+}
68
+
69
+func labelSuffix(set map[string]string) string {
70
+	if len(set) == 0 {
71
+		return ""
72 72
 	}
73
+	return fmt.Sprintf(" with label %s", labels.SelectorFromSet(set).String())
74
+}
73 75
 
74
-	errs := []error{}
75
-	for i, item := range list.Items {
76
-		info, err := resourceMapper.InfoForObject(item, nil)
77
-		if err != nil {
78
-			errs = append(errs, err)
79
-			if after(info, err) {
80
-				break
81
-			}
82
-			continue
83
-		}
84
-		obj, err := encodeAndCreate(info, namespace, item)
85
-		if err != nil && b.Retry != nil {
86
-			if obj := b.Retry(info, err); obj != nil {
87
-				obj, err = encodeAndCreate(info, namespace, obj)
88
-			}
89
-		}
90
-		if err != nil {
91
-			errs = append(errs, err)
92
-			if after(info, err) {
93
-				break
94
-			}
95
-			continue
76
+func CreateMessage(labels map[string]string) string {
77
+	return fmt.Sprintf("Creating resources%s", labelSuffix(labels))
78
+}
79
+
80
+type BulkAction struct {
81
+	// required setup
82
+	Bulk        Bulk
83
+	Out, ErrOut io.Writer
84
+
85
+	// flags
86
+	Output      string
87
+	DryRun      bool
88
+	StopOnError bool
89
+
90
+	// output modifiers
91
+	Action string
92
+}
93
+
94
+// BindForAction sets flags on this action for when setting -o should only change how the operation results are displayed.
95
+// Passing -o is changing the default output format.
96
+func (b *BulkAction) BindForAction(flags *pflag.FlagSet) {
97
+	flags.StringVarP(&b.Output, "output", "o", "", "Output mode. Use \"-o name\" for shorter output (resource/name).")
98
+	flags.BoolVar(&b.DryRun, "dry-run", false, "If true, show the result of the operation without performing it.")
99
+}
100
+
101
+// BindForOutput sets flags on this action for when setting -o will not execute the action (the point of the action is
102
+// primarily to generate the output). Passing -o is asking for output, not execution.
103
+func (b *BulkAction) BindForOutput(flags *pflag.FlagSet) {
104
+	flags.StringVarP(&b.Output, "output", "o", "", "Output results as yaml or json instead of executing, or use name for succint output (resource/name).")
105
+	flags.BoolVar(&b.DryRun, "dry-run", false, "If true, show the result of the operation without performing it.")
106
+}
107
+
108
+// Compact sets the output to a minimal set
109
+func (b *BulkAction) Compact() {
110
+	b.Output = "compact"
111
+}
112
+
113
+// ShouldPrint returns true if an external printer should handle this action instead of execution.
114
+func (b *BulkAction) ShouldPrint() bool {
115
+	return b.Output != "" && b.Output != "name" && b.Output != "compact"
116
+}
117
+
118
+func (b *BulkAction) Verbose() bool {
119
+	return b.Output == ""
120
+}
121
+
122
+func (b *BulkAction) DefaultIndent() string {
123
+	if b.Verbose() {
124
+		return "    "
125
+	}
126
+	return ""
127
+}
128
+
129
+func (b BulkAction) WithMessage(action, individual string) Runner {
130
+	b.Action = action
131
+	switch {
132
+	// TODO: this should be b printer
133
+	case b.Output == "":
134
+		b.Bulk.After = NewPrintNameOrErrorAfterIndent(b.Bulk.Mapper, false, individual, b.Out, b.ErrOut, b.DefaultIndent())
135
+	// TODO: needs to be unified with the name printer (incremental vs exact execution), possibly by creating b synthetic printer?
136
+	case b.Output == "name":
137
+		b.Bulk.After = NewPrintNameOrErrorAfterIndent(b.Bulk.Mapper, true, individual, b.Out, b.ErrOut, b.DefaultIndent())
138
+	default:
139
+		b.Bulk.After = NewPrintErrorAfter(b.Bulk.Mapper, b.ErrOut)
140
+		if b.StopOnError {
141
+			b.Bulk.After = HaltOnError(b.Bulk.After)
96 142
 		}
97
-		info.Refresh(obj, true)
98
-		list.Items[i] = obj
99
-		if after(info, nil) {
100
-			break
143
+	}
144
+	return &b
145
+}
146
+
147
+func (b *BulkAction) Run(list *kapi.List, namespace string) []error {
148
+	run := b.Bulk
149
+
150
+	if b.Verbose() {
151
+		fmt.Fprintf(b.Out, "--> %s ...\n", b.Action)
152
+	}
153
+
154
+	var modifier string
155
+	if b.DryRun {
156
+		run.Op = NoOp
157
+		modifier = " (DRY RUN)"
158
+	}
159
+
160
+	errs := run.Run(list, namespace)
161
+	if b.Verbose() {
162
+		if len(errs) == 0 {
163
+			fmt.Fprintf(b.Out, "--> Success%s\n", modifier)
164
+		} else {
165
+			fmt.Fprintf(b.Out, "--> Failed%s\n", modifier)
101 166
 		}
102 167
 	}
103 168
 	return errs
104 169
new file mode 100644
... ...
@@ -0,0 +1,163 @@
0
+package cmd
1
+
2
+import (
3
+	"bytes"
4
+	"fmt"
5
+	"reflect"
6
+	"testing"
7
+
8
+	kapi "k8s.io/kubernetes/pkg/api"
9
+	"k8s.io/kubernetes/pkg/api/meta"
10
+	"k8s.io/kubernetes/pkg/api/unversioned"
11
+	"k8s.io/kubernetes/pkg/kubectl/resource"
12
+	"k8s.io/kubernetes/pkg/runtime"
13
+)
14
+
15
+type bulkTester struct {
16
+	meta.RESTMapper
17
+
18
+	mapping *meta.RESTMapping
19
+	err     error
20
+	opErr   error
21
+
22
+	infos    []runtime.Object
23
+	recorded []runtime.Object
24
+}
25
+
26
+func (bt *bulkTester) ResourceSingularizer(resource string) (string, error) {
27
+	return resource, nil
28
+}
29
+
30
+func (bt *bulkTester) InfoForObject(obj runtime.Object, preferredGVKs []unversioned.GroupVersionKind) (*resource.Info, error) {
31
+	bt.infos = append(bt.infos, obj)
32
+	return &resource.Info{Object: obj, Mapping: bt.mapping}, bt.err
33
+}
34
+
35
+func (bt *bulkTester) Record(info *resource.Info, namespace string, obj runtime.Object) (runtime.Object, error) {
36
+	bt.recorded = append(bt.recorded, obj)
37
+	return obj, bt.opErr
38
+}
39
+
40
+func TestBulk(t *testing.T) {
41
+	bt := &bulkTester{
42
+		mapping: &meta.RESTMapping{
43
+			MetadataAccessor: meta.NewAccessor(),
44
+		},
45
+	}
46
+	b := Bulk{Mapper: bt, Op: bt.Record}
47
+
48
+	in := &kapi.Pod{}
49
+	if errs := b.Run(&kapi.List{Items: []runtime.Object{in}}, "test_namespace"); len(errs) > 0 {
50
+		t.Fatal(errs)
51
+	}
52
+	if !reflect.DeepEqual(bt.infos, []runtime.Object{in}) {
53
+		t.Fatalf("unexpected: %#v", bt.infos)
54
+	}
55
+	if !reflect.DeepEqual(bt.recorded, []runtime.Object{in}) {
56
+		t.Fatalf("unexpected: %#v", bt.recorded)
57
+	}
58
+}
59
+
60
+func TestBulkInfoError(t *testing.T) {
61
+	bt := &bulkTester{
62
+		mapping: &meta.RESTMapping{
63
+			MetadataAccessor: meta.NewAccessor(),
64
+		},
65
+		err: fmt.Errorf("error1"),
66
+	}
67
+	b := Bulk{Mapper: bt, Op: bt.Record}
68
+
69
+	in := &kapi.Pod{}
70
+	if errs := b.Run(&kapi.List{Items: []runtime.Object{in}}, "test_namespace"); len(errs) != 1 || errs[0] != bt.err {
71
+		t.Fatal(errs)
72
+	}
73
+	if !reflect.DeepEqual(bt.infos, []runtime.Object{in}) {
74
+		t.Fatalf("unexpected: %#v", bt.infos)
75
+	}
76
+	if !reflect.DeepEqual(bt.recorded, []runtime.Object(nil)) {
77
+		t.Fatalf("unexpected: %#v", bt.recorded)
78
+	}
79
+}
80
+
81
+func TestBulkOpError(t *testing.T) {
82
+	bt := &bulkTester{
83
+		mapping: &meta.RESTMapping{
84
+			MetadataAccessor: meta.NewAccessor(),
85
+		},
86
+		opErr: fmt.Errorf("error1"),
87
+	}
88
+	b := Bulk{Mapper: bt, Op: bt.Record}
89
+
90
+	in := &kapi.Pod{}
91
+	if errs := b.Run(&kapi.List{Items: []runtime.Object{in}}, "test_namespace"); len(errs) != 1 || errs[0] != bt.opErr {
92
+		t.Fatal(errs)
93
+	}
94
+	if !reflect.DeepEqual(bt.infos, []runtime.Object{in}) {
95
+		t.Fatalf("unexpected: %#v", bt.infos)
96
+	}
97
+	if !reflect.DeepEqual(bt.recorded, []runtime.Object{in}) {
98
+		t.Fatalf("unexpected: %#v", bt.recorded)
99
+	}
100
+}
101
+
102
+func TestBulkAction(t *testing.T) {
103
+	bt := &bulkTester{
104
+		mapping: &meta.RESTMapping{
105
+			MetadataAccessor: meta.NewAccessor(),
106
+		},
107
+	}
108
+	out, err := &bytes.Buffer{}, &bytes.Buffer{}
109
+	bulk := Bulk{Mapper: bt, Op: bt.Record}
110
+	b := &BulkAction{Bulk: bulk, Output: "", Out: out, ErrOut: err}
111
+	b2 := b.WithMessage("test1", "test2")
112
+
113
+	in := &kapi.Pod{ObjectMeta: kapi.ObjectMeta{Name: "obj1"}}
114
+	if errs := b2.Run(&kapi.List{Items: []runtime.Object{in}}, "test_namespace"); len(errs) != 0 {
115
+		t.Fatal(errs)
116
+	}
117
+	if !reflect.DeepEqual(bt.infos, []runtime.Object{in}) {
118
+		t.Fatalf("unexpected: %#v", bt.infos)
119
+	}
120
+	if !reflect.DeepEqual(bt.recorded, []runtime.Object{in}) {
121
+		t.Fatalf("unexpected: %#v", bt.recorded)
122
+	}
123
+	if out.String() != `--> test1 ...
124
+    "obj1" test2
125
+--> Success
126
+` {
127
+		t.Fatalf("unexpected: %s", out.String())
128
+	}
129
+	if err.String() != `` {
130
+		t.Fatalf("unexpected: %s", err.String())
131
+	}
132
+}
133
+
134
+func TestBulkActionCompact(t *testing.T) {
135
+	bt := &bulkTester{
136
+		mapping: &meta.RESTMapping{
137
+			MetadataAccessor: meta.NewAccessor(),
138
+		},
139
+	}
140
+	out, err := &bytes.Buffer{}, &bytes.Buffer{}
141
+	bulk := Bulk{Mapper: bt, Op: bt.Record}
142
+	b := &BulkAction{Bulk: bulk, Output: "", Out: out, ErrOut: err}
143
+	b.Compact()
144
+	b2 := b.WithMessage("test1", "test2")
145
+
146
+	in := &kapi.Pod{ObjectMeta: kapi.ObjectMeta{Name: "obj1"}}
147
+	if errs := b2.Run(&kapi.List{Items: []runtime.Object{in}}, "test_namespace"); len(errs) != 0 {
148
+		t.Fatal(errs)
149
+	}
150
+	if !reflect.DeepEqual(bt.infos, []runtime.Object{in}) {
151
+		t.Fatalf("unexpected: %#v", bt.infos)
152
+	}
153
+	if !reflect.DeepEqual(bt.recorded, []runtime.Object{in}) {
154
+		t.Fatalf("unexpected: %#v", bt.recorded)
155
+	}
156
+	if out.String() != `` {
157
+		t.Fatalf("unexpected: %s", out.String())
158
+	}
159
+	if err.String() != `` {
160
+		t.Fatalf("unexpected: %s", err.String())
161
+	}
162
+}
0 163
deleted file mode 100644
... ...
@@ -1,29 +0,0 @@
1
-package ipfailover
2
-
3
-import (
4
-	"io"
5
-
6
-	"github.com/golang/glog"
7
-	kapi "k8s.io/kubernetes/pkg/api"
8
-)
9
-
10
-type Configurator struct {
11
-	Name   string
12
-	Plugin IPFailoverConfiguratorPlugin
13
-	Writer io.Writer
14
-}
15
-
16
-func NewConfigurator(name string, plugin IPFailoverConfiguratorPlugin, out io.Writer) *Configurator {
17
-	glog.V(4).Infof("Creating IP failover configurator: %s", name)
18
-	return &Configurator{Name: name, Plugin: plugin, Writer: out}
19
-}
20
-
21
-func (c *Configurator) Generate() (*kapi.List, error) {
22
-	glog.V(4).Infof("Generating IP failover configuration: %s", c.Name)
23
-	return c.Plugin.Generate()
24
-}
25
-
26
-func (c *Configurator) Create() error {
27
-	glog.V(4).Infof("Creating IP failover configuration: %s", c.Name)
28
-	return c.Plugin.Create(c.Writer)
29
-}
30 1
deleted file mode 100644
... ...
@@ -1,63 +0,0 @@
1
-package ipfailover
2
-
3
-import (
4
-	"fmt"
5
-	"testing"
6
-)
7
-
8
-type ConfiguratorCallback func(name string, c *Configurator) error
9
-
10
-func runConfiguratorCallCountTest(t *testing.T, name string, idx int, err error, expectation int, cb ConfiguratorCallback) {
11
-	testUnitName := fmt.Sprintf("%s-%d", name, idx)
12
-	plugin := MakeMockPlugin(testUnitName, err)
13
-	c := NewConfigurator(testUnitName, plugin, nil)
14
-	cb(name, c)
15
-	callCount := GetCallCount(testUnitName)
16
-	if callCount != expectation {
17
-		t.Errorf("Configurator test %q:%d failed - got call count %d, expected %d", name, idx, callCount, expectation)
18
-	}
19
-}
20
-
21
-func RunConfiguratorTest(t *testing.T, name string, cb ConfiguratorCallback) {
22
-	idx := 0
23
-	expectedErrors := []error{nil, fmt.Errorf("error-test-%s", name)}
24
-	for _, err := range expectedErrors {
25
-		for _, val := range []int{1, 1, 2, 3, 5, 8} {
26
-			idx = idx + 1
27
-			runConfiguratorCallCountTest(t, name, idx, err, val, func(n string, c *Configurator) error {
28
-				for cnt := 0; cnt < val; cnt++ {
29
-					reterr := cb(n, c)
30
-					if nil != err && nil == reterr {
31
-						t.Errorf("Configurator test %q failed - got no error, expected %v", name, err)
32
-					}
33
-				}
34
-				return nil
35
-			})
36
-		}
37
-	}
38
-}
39
-
40
-func TestNewConfigurator(t *testing.T) {
41
-	plugin := &MockPlugin{}
42
-	c := NewConfigurator("test-configurator", plugin, nil)
43
-	if nil == c {
44
-		t.Errorf("TestNewConfigurator failed - got nil, expected a new configurator instance")
45
-	}
46
-}
47
-
48
-func TestConfiguratorGenerate(t *testing.T) {
49
-	cb := func(n string, c *Configurator) error {
50
-		_, err := c.Generate()
51
-		return err
52
-	}
53
-
54
-	RunConfiguratorTest(t, "TestConfiguratorGenerate", cb)
55
-}
56
-
57
-func TestConfiguratorCreate(t *testing.T) {
58
-	cb := func(n string, c *Configurator) error {
59
-		return c.Create()
60
-	}
61
-
62
-	RunConfiguratorTest(t, "TestConfiguratorCreate", cb)
63
-}
... ...
@@ -1,18 +1,9 @@
1 1
 package ipfailover
2 2
 
3 3
 import (
4
-	"io"
5
-
6 4
 	kapi "k8s.io/kubernetes/pkg/api"
7
-
8
-	deployapi "github.com/openshift/origin/pkg/deploy/api"
9 5
 )
10 6
 
11 7
 type IPFailoverConfiguratorPlugin interface {
12
-	GetWatchPort() (int, error)
13
-	GetSelector() (map[string]string, error)
14
-	GetNamespace() (string, error)
15
-	GetDeploymentConfig() (*deployapi.DeploymentConfig, error)
16 8
 	Generate() (*kapi.List, error)
17
-	Create(out io.Writer) error
18 9
 }
... ...
@@ -2,8 +2,6 @@ package keepalived
2 2
 
3 3
 import (
4 4
 	"fmt"
5
-	"io"
6
-	"os"
7 5
 	"strings"
8 6
 
9 7
 	"github.com/golang/glog"
... ...
@@ -12,7 +10,6 @@ import (
12 12
 	"k8s.io/kubernetes/pkg/runtime"
13 13
 
14 14
 	"github.com/openshift/origin/pkg/cmd/util/clientcmd"
15
-	configcmd "github.com/openshift/origin/pkg/config/cmd"
16 15
 	deployapi "github.com/openshift/origin/pkg/deploy/api"
17 16
 	"github.com/openshift/origin/pkg/generate/app"
18 17
 	"github.com/openshift/origin/pkg/ipfailover"
... ...
@@ -128,33 +125,3 @@ func (p *KeepalivedPlugin) Generate() (*kapi.List, error) {
128 128
 
129 129
 	return configList, nil
130 130
 }
131
-
132
-// Create the config and services associated with this IP Failover configuration.
133
-func (p *KeepalivedPlugin) Create(out io.Writer) error {
134
-	namespace, err := p.GetNamespace()
135
-	if err != nil {
136
-		return fmt.Errorf("error getting Namespace: %v", err)
137
-	}
138
-
139
-	mapper, typer := p.Factory.Factory.Object(false)
140
-	bulk := configcmd.Bulk{
141
-		Mapper:            mapper,
142
-		Typer:             typer,
143
-		RESTClientFactory: p.Factory.Factory.ClientForMapping,
144
-
145
-		After: configcmd.NewPrintNameOrErrorAfter(mapper, p.Options.ShortOutput, "created", out, os.Stderr),
146
-	}
147
-
148
-	configList, err := p.Generate()
149
-	if err != nil {
150
-		return fmt.Errorf("error generating config: %v", err)
151
-	}
152
-
153
-	if errs := bulk.Create(configList, namespace); len(errs) != 0 {
154
-		return fmt.Errorf("error creating config: %+v", errs)
155
-	}
156
-
157
-	glog.V(4).Infof("Created KeepAlived IP Failover DeploymentConfig: %q", p.Name)
158
-
159
-	return nil
160
-}
161 131
deleted file mode 100644
... ...
@@ -1,166 +0,0 @@
1
-package ipfailover
2
-
3
-import (
4
-	"fmt"
5
-	"io"
6
-	"os"
7
-	"testing"
8
-
9
-	deployapi "github.com/openshift/origin/pkg/deploy/api"
10
-	kapi "k8s.io/kubernetes/pkg/api"
11
-
12
-	"github.com/openshift/origin/pkg/cmd/util/clientcmd"
13
-)
14
-
15
-var callCounterMap = make(map[string]int, 0)
16
-
17
-func GetCallCount(name string) int {
18
-	counter, ok := callCounterMap[name]
19
-	if ok {
20
-		return counter
21
-	}
22
-
23
-	return 0
24
-}
25
-
26
-func IncrementCallCount(name string) {
27
-	callCounterMap[name] = GetCallCount(name) + 1
28
-}
29
-
30
-type MockPlugin struct {
31
-	Name             string
32
-	Factory          *clientcmd.Factory
33
-	Options          *IPFailoverConfigCmdOptions
34
-	DeploymentConfig *deployapi.DeploymentConfig
35
-	TestError        error
36
-}
37
-
38
-func (p MockPlugin) GetWatchPort() (int, error) {
39
-	IncrementCallCount(p.Name)
40
-	return p.Options.WatchPort, p.TestError
41
-}
42
-
43
-func (p MockPlugin) GetSelector() (map[string]string, error) {
44
-	IncrementCallCount(p.Name)
45
-	return map[string]string{DefaultName: p.Name}, p.TestError
46
-}
47
-
48
-func (p MockPlugin) GetNamespace() (string, error) {
49
-	IncrementCallCount(p.Name)
50
-	return "mock", p.TestError
51
-}
52
-
53
-func (p MockPlugin) GetDeploymentConfig() (*deployapi.DeploymentConfig, error) {
54
-	IncrementCallCount(p.Name)
55
-	return p.DeploymentConfig, p.TestError
56
-}
57
-
58
-func (p MockPlugin) Generate() (*kapi.List, error) {
59
-	IncrementCallCount(p.Name)
60
-	return &kapi.List{}, p.TestError
61
-}
62
-
63
-func (p MockPlugin) Create(out io.Writer) error {
64
-	IncrementCallCount(p.Name)
65
-	return p.TestError
66
-}
67
-
68
-func MakeMockPlugin(name string, err error) *MockPlugin {
69
-	return &MockPlugin{
70
-		Name:             name,
71
-		Options:          &IPFailoverConfigCmdOptions{},
72
-		DeploymentConfig: &deployapi.DeploymentConfig{},
73
-		TestError:        err,
74
-	}
75
-}
76
-
77
-type PluginCallback func(name string, p IPFailoverConfiguratorPlugin) error
78
-
79
-func runPluginCallCountTest(t *testing.T, name string, idx int, err error, expectation int, cb PluginCallback) {
80
-	testUnitName := fmt.Sprintf("%s-%d", name, idx)
81
-	plugin := MakeMockPlugin(testUnitName, err)
82
-	cb(testUnitName, *plugin)
83
-	callCount := GetCallCount(testUnitName)
84
-	if callCount != expectation {
85
-		t.Errorf("Plugin test %q:%d failed - got call count %d, expected %d", name, idx, callCount, expectation)
86
-	}
87
-}
88
-
89
-func TestNewPlugin(t *testing.T) {
90
-	plugin := MakeMockPlugin("NewPlugin", nil)
91
-	if nil == plugin {
92
-		t.Errorf("Test for NewPlugin failed - got nil, expected a new plugin instance")
93
-	}
94
-}
95
-
96
-func RunPluginInterfaceTest(t *testing.T, name string, cb PluginCallback) {
97
-	idx := 0
98
-	expectedErrors := []error{nil, fmt.Errorf("error-test-%s", name)}
99
-	for _, err := range expectedErrors {
100
-		for _, val := range []int{1, 1, 2, 3, 5, 8} {
101
-			idx = idx + 1
102
-			runPluginCallCountTest(t, name, idx, err, val, func(n string, p IPFailoverConfiguratorPlugin) error {
103
-				for cnt := 0; cnt < val; cnt++ {
104
-					reterr := cb(n, p)
105
-					if nil != err && nil == reterr {
106
-						t.Errorf("Test %q failed - got no error, expected %v", name, err)
107
-					}
108
-				}
109
-				return nil
110
-			})
111
-		}
112
-	}
113
-}
114
-
115
-func TestPluginGetWatchPort(t *testing.T) {
116
-	cb := func(n string, p IPFailoverConfiguratorPlugin) error {
117
-		_, err := p.GetWatchPort()
118
-		return err
119
-	}
120
-
121
-	RunPluginInterfaceTest(t, "PluginGetWatchPort", cb)
122
-}
123
-
124
-func TestPluginGetSelector(t *testing.T) {
125
-	cb := func(n string, p IPFailoverConfiguratorPlugin) error {
126
-		_, err := p.GetSelector()
127
-		return err
128
-	}
129
-
130
-	RunPluginInterfaceTest(t, "PluginGetSelector", cb)
131
-}
132
-
133
-func TestPluginGetNamespace(t *testing.T) {
134
-	cb := func(n string, p IPFailoverConfiguratorPlugin) error {
135
-		_, err := p.GetNamespace()
136
-		return err
137
-	}
138
-
139
-	RunPluginInterfaceTest(t, "PluginGetNamespace", cb)
140
-}
141
-
142
-func TestPluginGetDeploymentConfig(t *testing.T) {
143
-	cb := func(n string, p IPFailoverConfiguratorPlugin) error {
144
-		_, err := p.GetDeploymentConfig()
145
-		return err
146
-	}
147
-
148
-	RunPluginInterfaceTest(t, "PluginGetDeploymentConfig", cb)
149
-}
150
-
151
-func TestPluginGenerate(t *testing.T) {
152
-	cb := func(n string, p IPFailoverConfiguratorPlugin) error {
153
-		_, err := p.Generate()
154
-		return err
155
-	}
156
-
157
-	RunPluginInterfaceTest(t, "PluginGenerate", cb)
158
-}
159
-
160
-func TestPluginCreate(t *testing.T) {
161
-	cb := func(n string, p IPFailoverConfiguratorPlugin) error {
162
-		return p.Create(os.Stderr)
163
-	}
164
-
165
-	RunPluginInterfaceTest(t, "PluginCreateStderr", cb)
166
-}
... ...
@@ -2,6 +2,7 @@ package ipfailover
2 2
 
3 3
 import (
4 4
 	"github.com/openshift/origin/pkg/cmd/util/variable"
5
+	configcmd "github.com/openshift/origin/pkg/config/cmd"
5 6
 )
6 7
 
7 8
 const (
... ...
@@ -26,6 +27,8 @@ const (
26 26
 
27 27
 // IPFailoverConfigCmdOptions are options supported by the IP Failover admin command.
28 28
 type IPFailoverConfigCmdOptions struct {
29
+	Action configcmd.BulkAction
30
+
29 31
 	Type           string
30 32
 	ImageTemplate  variable.ImageTemplate
31 33
 	Credentials    string
... ...
@@ -40,6 +43,4 @@ type IPFailoverConfigCmdOptions struct {
40 40
 	WatchPort        int
41 41
 	VRRPIDOffset     int
42 42
 	Replicas         int
43
-
44
-	ShortOutput bool
45 43
 }
... ...
@@ -76,16 +76,6 @@ func ValidateVirtualIPs(vips string) error {
76 76
 }
77 77
 
78 78
 // ValidateCmdOptions validates command line operations.
79
-func ValidateCmdOptions(options *IPFailoverConfigCmdOptions, c *Configurator) error {
80
-	dc, err := c.Plugin.GetDeploymentConfig()
81
-	if err != nil {
82
-		return err
83
-	}
84
-
85
-	//  If creating deployment, check deployment config doesn't exist.
86
-	if options.Create && dc != nil {
87
-		return fmt.Errorf("IP Failover config %q exists\n", c.Name)
88
-	}
89
-
79
+func ValidateCmdOptions(options *IPFailoverConfigCmdOptions) error {
90 80
 	return ValidateVirtualIPs(options.VirtualIPs)
91 81
 }
... ...
@@ -2,8 +2,6 @@ package ipfailover
2 2
 
3 3
 import (
4 4
 	"testing"
5
-
6
-	deployapi "github.com/openshift/origin/pkg/deploy/api"
7 5
 )
8 6
 
9 7
 func TestValidateIPAddress(t *testing.T) {
... ...
@@ -84,64 +82,6 @@ func TestValidateVirtualIPs(t *testing.T) {
84 84
 	}
85 85
 }
86 86
 
87
-func getMockConfigurator(options *IPFailoverConfigCmdOptions, dc *deployapi.DeploymentConfig) *Configurator {
88
-	p := &MockPlugin{
89
-		Name:             "mock",
90
-		Options:          options,
91
-		DeploymentConfig: dc,
92
-	}
93
-	return NewConfigurator("mock-plugin", p, nil)
94
-}
95
-
96
-func TestValidateCmdOptionsForCreate(t *testing.T) {
97
-	tests := []struct {
98
-		Name             string
99
-		Create           bool
100
-		DeploymentConfig *deployapi.DeploymentConfig
101
-		ErrorExpectation bool
102
-	}{
103
-		{
104
-			Name:             "create-with-no-service",
105
-			Create:           true,
106
-			ErrorExpectation: false,
107
-		},
108
-		{
109
-			Name:             "create-with-service",
110
-			Create:           true,
111
-			DeploymentConfig: &deployapi.DeploymentConfig{},
112
-			ErrorExpectation: true,
113
-		},
114
-		{
115
-			Name:             "no-create-option-and-service",
116
-			ErrorExpectation: false,
117
-		},
118
-		{
119
-			Name:             "no-create-option-with-service",
120
-			DeploymentConfig: &deployapi.DeploymentConfig{},
121
-			ErrorExpectation: false,
122
-		},
123
-	}
124
-
125
-	for _, tc := range tests {
126
-		options := &IPFailoverConfigCmdOptions{Create: tc.Create}
127
-		plugin := &MockPlugin{
128
-			Name:             "mock",
129
-			Options:          options,
130
-			DeploymentConfig: tc.DeploymentConfig,
131
-		}
132
-		c := NewConfigurator(tc.Name, plugin, nil)
133
-
134
-		err := ValidateCmdOptions(options, c)
135
-		if err != nil && !tc.ErrorExpectation {
136
-			t.Errorf("Test case %q got an error: %v where none was expected.",
137
-				tc.Name, err)
138
-		}
139
-		if nil == err && tc.ErrorExpectation {
140
-			t.Errorf("Test case %q got no error - expected an error.", tc.Name)
141
-		}
142
-	}
143
-}
144
-
145 87
 func TestValidateCmdOptionsVIPs(t *testing.T) {
146 88
 	validVIPs := []string{"", "1.1.1.1-1,2.2.2.2", "4.4.4.4-8",
147 89
 		"1.1.1.1-7,2.2.2.2,3.3.3.3-5",
... ...
@@ -152,8 +92,7 @@ func TestValidateCmdOptionsVIPs(t *testing.T) {
152 152
 
153 153
 	for _, vips := range validVIPs {
154 154
 		options := &IPFailoverConfigCmdOptions{VirtualIPs: vips}
155
-		c := getMockConfigurator(options, nil)
156
-		if err := ValidateCmdOptions(options, c); err != nil {
155
+		if err := ValidateCmdOptions(options); err != nil {
157 156
 			t.Errorf("Test command options valid vips=%q got error %s expected: no error.",
158 157
 				vips, err)
159 158
 		}
... ...
@@ -169,8 +108,7 @@ func TestValidateCmdOptionsVIPs(t *testing.T) {
169 169
 
170 170
 	for _, vips := range invalidVIPs {
171 171
 		options := &IPFailoverConfigCmdOptions{VirtualIPs: vips}
172
-		c := getMockConfigurator(options, nil)
173
-		if err := ValidateCmdOptions(options, c); err == nil {
172
+		if err := ValidateCmdOptions(options); err == nil {
174 173
 			t.Errorf("Test command options invalid vips=%q got no error expected: error.", vips)
175 174
 		}
176 175
 	}
... ...
@@ -142,16 +142,19 @@ func (r *REST) Create(ctx kapi.Context, obj runtime.Object) (runtime.Object, err
142 142
 	}
143 143
 
144 144
 	bulk := configcmd.Bulk{
145
-		Mapper: restMapper,
146
-		Typer:  kapi.Scheme,
147
-		RESTClientFactory: func(mapping *meta.RESTMapping) (resource.RESTClient, error) {
148
-			if latest.OriginKind(mapping.GroupVersionKind) {
149
-				return r.openshiftClient, nil
150
-			}
151
-			return r.kubeClient, nil
145
+		Mapper: &resource.Mapper{
146
+			RESTMapper:  restMapper,
147
+			ObjectTyper: kapi.Scheme,
148
+			ClientMapper: resource.ClientMapperFunc(func(mapping *meta.RESTMapping) (resource.RESTClient, error) {
149
+				if latest.OriginKind(mapping.GroupVersionKind) {
150
+					return r.openshiftClient, nil
151
+				}
152
+				return r.kubeClient, nil
153
+			}),
152 154
 		},
155
+		Op: configcmd.Create,
153 156
 	}
154
-	if err := utilerrors.NewAggregate(bulk.Create(objectsToCreate, projectName)); err != nil {
157
+	if err := utilerrors.NewAggregate(bulk.Run(objectsToCreate, projectName)); err != nil {
155 158
 		return nil, kapierror.NewInternalError(err)
156 159
 	}
157 160
 
... ...
@@ -68,4 +68,16 @@ os::cmd::expect_success_and_text 'oc get dc/router -o yaml' 'readinessProbe'
68 68
 # only when using hostnetwork should we force the probes to use localhost
69 69
 os::cmd::expect_success_and_not_text "oadm router -o yaml --credentials=${KUBECONFIG} --host-network=false" 'host: localhost'
70 70
 echo "router: ok"
71
-os::test::junit::declare_suite_end
72 71
\ No newline at end of file
72
+
73
+# test ipfailover
74
+os::cmd::expect_success_and_text 'oadm ipfailover --dry-run' 'Creating IP failover'
75
+os::cmd::expect_success_and_text 'oadm ipfailover --dry-run' 'Success \(DRY RUN\)'
76
+os::cmd::expect_success_and_text 'oadm ipfailover --dry-run -o yaml' 'name: ipfailover'
77
+os::cmd::expect_success_and_text 'oadm ipfailover --dry-run -o name' 'deploymentconfig/ipfailover'
78
+# TODO add tests for normal ipfailover creation
79
+# os::cmd::expect_success_and_text 'oadm ipfailover' 'deploymentconfig "ipfailover" created'
80
+# os::cmd::expect_failure_and_text 'oadm ipfailover' 'Error from server: deploymentconfig "ipfailover" already exists'
81
+# os::cmd::expect_success_and_text 'oadm ipfailover -o name --dry-run | xargs oc delete' 'deleted'
82
+echo "ipfailover: ok"
83
+
84
+os::test::junit::declare_suite_end
... ...
@@ -94,11 +94,12 @@ var _ = g.Describe("[builds][Slow] can use private repositories as build input",
94 94
 			testGitAuth(gitServerFixture, sourceURLTemplate, func() string {
95 95
 				g.By(fmt.Sprintf("creating a new secret for the gitserver by calling oc secrets new-basicauth %s --username=%s --password=%s --cacert=%s",
96 96
 					sourceSecretName, gitUserName, gitPassword, caCertPath))
97
-				err := oc.Run("secrets").
98
-					Args("new-basicauth", sourceSecretName,
97
+				err := oc.Run("secrets").Args(
98
+					"new-basicauth", sourceSecretName,
99 99
 					fmt.Sprintf("--username=%s", gitUserName),
100 100
 					fmt.Sprintf("--password=%s", gitPassword),
101
-					fmt.Sprintf("--ca-cert=%s", caCertPath)).Execute()
101
+					fmt.Sprintf("--ca-cert=%s", caCertPath),
102
+				).Execute()
102 103
 				o.Expect(err).NotTo(o.HaveOccurred())
103 104
 				return sourceSecretName
104 105
 			})