Disable all image triggers on rollback to prevent undesirable
side-effects. Provide a new deploy option `--enable-triggers` to
allow users to re-enable image triggers automatically.
... | ... |
@@ -34,6 +34,7 @@ type DeployOptions struct { |
34 | 34 |
deployLatest bool |
35 | 35 |
retryDeploy bool |
36 | 36 |
cancelDeploy bool |
37 |
+ enableTriggers bool |
|
37 | 38 |
} |
38 | 39 |
|
39 | 40 |
const ( |
... | ... |
@@ -86,6 +87,7 @@ func NewCmdDeploy(fullName string, f *clientcmd.Factory, out io.Writer) *cobra.C |
86 | 86 |
cmd.Flags().BoolVar(&options.deployLatest, "latest", false, "Start a new deployment now.") |
87 | 87 |
cmd.Flags().BoolVar(&options.retryDeploy, "retry", false, "Retry the latest failed deployment.") |
88 | 88 |
cmd.Flags().BoolVar(&options.cancelDeploy, "cancel", false, "Cancel the in-progress deployment.") |
89 |
+ cmd.Flags().BoolVar(&options.enableTriggers, "enable-triggers", false, "Enables all image triggers for the deployment config.") |
|
89 | 90 |
|
90 | 91 |
return cmd |
91 | 92 |
} |
... | ... |
@@ -181,6 +183,13 @@ func (o DeployOptions) RunDeploy() error { |
181 | 181 |
case o.cancelDeploy: |
182 | 182 |
c := &cancelDeploymentCommand{client: commandClient} |
183 | 183 |
err = c.cancel(config, o.out) |
184 |
+ case o.enableTriggers: |
|
185 |
+ t := &triggerEnabler{ |
|
186 |
+ updateConfig: func(namespace string, config *deployapi.DeploymentConfig) (*deployapi.DeploymentConfig, error) { |
|
187 |
+ return o.osClient.DeploymentConfigs(namespace).Update(config) |
|
188 |
+ }, |
|
189 |
+ } |
|
190 |
+ t.enableTriggers(config, o.out) |
|
184 | 191 |
default: |
185 | 192 |
describer := describe.NewLatestDeploymentsDescriber(o.osClient, o.kubeClient, -1) |
186 | 193 |
desc, err := describer.Describe(config.Namespace, config.Name) |
... | ... |
@@ -337,6 +346,33 @@ func (c *cancelDeploymentCommand) cancel(config *deployapi.DeploymentConfig, out |
337 | 337 |
} |
338 | 338 |
} |
339 | 339 |
|
340 |
+// triggerEnabler can enable image triggers for a config. |
|
341 |
+type triggerEnabler struct { |
|
342 |
+ // updateConfig persists config. |
|
343 |
+ updateConfig func(namespace string, config *deployapi.DeploymentConfig) (*deployapi.DeploymentConfig, error) |
|
344 |
+} |
|
345 |
+ |
|
346 |
+// enableTriggers enables all image triggers and then persists config. |
|
347 |
+func (t *triggerEnabler) enableTriggers(config *deployapi.DeploymentConfig, out io.Writer) error { |
|
348 |
+ enabled := []string{} |
|
349 |
+ for _, trigger := range config.Triggers { |
|
350 |
+ if trigger.Type == deployapi.DeploymentTriggerOnImageChange { |
|
351 |
+ trigger.ImageChangeParams.Automatic = true |
|
352 |
+ enabled = append(enabled, trigger.ImageChangeParams.From.Name) |
|
353 |
+ } |
|
354 |
+ } |
|
355 |
+ if len(enabled) == 0 { |
|
356 |
+ fmt.Fprintln(out, "no image triggers found to enable") |
|
357 |
+ return nil |
|
358 |
+ } |
|
359 |
+ _, err := t.updateConfig(config.Namespace, config) |
|
360 |
+ if err != nil { |
|
361 |
+ return err |
|
362 |
+ } |
|
363 |
+ fmt.Fprintf(out, "enabled image triggers: %s\n", strings.Join(enabled, ",")) |
|
364 |
+ return nil |
|
365 |
+} |
|
366 |
+ |
|
340 | 367 |
// deployCommandClientImpl is a pluggable deployCommandClient. |
341 | 368 |
type deployCommandClientImpl struct { |
342 | 369 |
GetDeploymentFn func(namespace, name string) (*kapi.ReplicationController, error) |
... | ... |
@@ -332,3 +332,44 @@ func TestCmdDeploy_cancelOk(t *testing.T) { |
332 | 332 |
} |
333 | 333 |
} |
334 | 334 |
} |
335 |
+ |
|
336 |
+func TestDeploy_triggerEnable(t *testing.T) { |
|
337 |
+ var updated *deployapi.DeploymentConfig |
|
338 |
+ triggerEnabler := &triggerEnabler{ |
|
339 |
+ updateConfig: func(namespace string, config *deployapi.DeploymentConfig) (*deployapi.DeploymentConfig, error) { |
|
340 |
+ updated = config |
|
341 |
+ return config, nil |
|
342 |
+ }, |
|
343 |
+ } |
|
344 |
+ |
|
345 |
+ mktrigger := func() deployapi.DeploymentTriggerPolicy { |
|
346 |
+ t := deploytest.OkImageChangeTrigger() |
|
347 |
+ t.ImageChangeParams.Automatic = false |
|
348 |
+ return t |
|
349 |
+ } |
|
350 |
+ count := 3 |
|
351 |
+ |
|
352 |
+ config := deploytest.OkDeploymentConfig(1) |
|
353 |
+ config.Triggers = []deployapi.DeploymentTriggerPolicy{} |
|
354 |
+ for i := 0; i < count; i++ { |
|
355 |
+ config.Triggers = append(config.Triggers, mktrigger()) |
|
356 |
+ } |
|
357 |
+ |
|
358 |
+ err := triggerEnabler.enableTriggers(config, ioutil.Discard) |
|
359 |
+ if err != nil { |
|
360 |
+ t.Fatalf("unexpected error: %v", err) |
|
361 |
+ } |
|
362 |
+ |
|
363 |
+ if updated == nil { |
|
364 |
+ t.Fatalf("expected an updated config") |
|
365 |
+ } |
|
366 |
+ |
|
367 |
+ if e, a := count, len(config.Triggers); e != a { |
|
368 |
+ t.Fatalf("expected %d triggers, got %d", e, a) |
|
369 |
+ } |
|
370 |
+ for _, trigger := range config.Triggers { |
|
371 |
+ if !trigger.ImageChangeParams.Automatic { |
|
372 |
+ t.Errorf("expected trigger to be enabled: %#v", trigger.ImageChangeParams) |
|
373 |
+ } |
|
374 |
+ } |
|
375 |
+} |
... | ... |
@@ -3,13 +3,16 @@ package cmd |
3 | 3 |
import ( |
4 | 4 |
"fmt" |
5 | 5 |
"io" |
6 |
+ "strings" |
|
6 | 7 |
|
7 | 8 |
kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api" |
9 |
+ kclient "github.com/GoogleCloudPlatform/kubernetes/pkg/client" |
|
8 | 10 |
kubectl "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl" |
9 | 11 |
cmdutil "github.com/GoogleCloudPlatform/kubernetes/pkg/kubectl/cmd/util" |
10 | 12 |
"github.com/spf13/cobra" |
11 | 13 |
|
12 | 14 |
latest "github.com/openshift/origin/pkg/api/latest" |
15 |
+ "github.com/openshift/origin/pkg/client" |
|
13 | 16 |
describe "github.com/openshift/origin/pkg/cmd/cli/describe" |
14 | 17 |
"github.com/openshift/origin/pkg/cmd/util/clientcmd" |
15 | 18 |
deployapi "github.com/openshift/origin/pkg/deploy/api" |
... | ... |
@@ -25,6 +28,11 @@ environment variables and volumes are included in rollbacks, so if you've |
25 | 25 |
recently updated security credentials in your environment your previous |
26 | 26 |
deployment may not have the correct values. |
27 | 27 |
|
28 |
+Any image triggers present in the rolled back configuration will be disabled |
|
29 |
+with a warning. This is to help prevent your rolled back deployment from being |
|
30 |
+replaced by a triggered deployment soon after your rollback. To re-enable the |
|
31 |
+triggers, use the 'deploy' command. |
|
32 |
+ |
|
28 | 33 |
If you would like to review the outcome of the rollback, pass '--dry-run' to print |
29 | 34 |
a human-readable representation of the updated deployment configuration instead of |
30 | 35 |
executing the rollback. This is useful if you're not quite sure what the outcome |
... | ... |
@@ -40,7 +48,7 @@ will be.` |
40 | 40 |
$ %[1]s rollback deployment-1 --output=json | %[1]s update deploymentConfigs deployment -f -` |
41 | 41 |
) |
42 | 42 |
|
43 |
-// NewCmdRollback implements the OpenShift cli rollback command |
|
43 |
+// NewCmdRollback creates a CLI rollback command. |
|
44 | 44 |
func NewCmdRollback(fullName string, f *clientcmd.Factory, out io.Writer) *cobra.Command { |
45 | 45 |
rollback := &deployapi.DeploymentConfigRollback{ |
46 | 46 |
Spec: deployapi.DeploymentConfigRollbackSpec{ |
... | ... |
@@ -54,8 +62,60 @@ func NewCmdRollback(fullName string, f *clientcmd.Factory, out io.Writer) *cobra |
54 | 54 |
Long: rollbackLong, |
55 | 55 |
Example: fmt.Sprintf(rollbackExample, fullName), |
56 | 56 |
Run: func(cmd *cobra.Command, args []string) { |
57 |
- err := RunRollback(f, out, cmd, args, rollback) |
|
57 |
+ // Validate arguments |
|
58 |
+ if len(args) == 0 || len(args[0]) == 0 { |
|
59 |
+ cmdutil.CheckErr(cmdutil.UsageError(cmd, "A deployment name is required.")) |
|
60 |
+ } |
|
61 |
+ |
|
62 |
+ // Extract arguments |
|
63 |
+ format := cmdutil.GetFlagString(cmd, "output") |
|
64 |
+ template := cmdutil.GetFlagString(cmd, "template") |
|
65 |
+ dryRun := cmdutil.GetFlagBool(cmd, "dry-run") |
|
66 |
+ |
|
67 |
+ // Get globally provided stuff |
|
68 |
+ namespace, err := f.DefaultNamespace() |
|
69 |
+ cmdutil.CheckErr(err) |
|
70 |
+ oClient, kClient, err := f.Clients() |
|
71 |
+ cmdutil.CheckErr(err) |
|
72 |
+ |
|
73 |
+ // Set up the rollback config |
|
74 |
+ rollback.Spec.From.Name = args[0] |
|
75 |
+ |
|
76 |
+ // Make a helper and generate a rolled back config |
|
77 |
+ helper := newHelper(oClient, kClient) |
|
78 |
+ config, err := helper.Generate(namespace, rollback) |
|
79 |
+ cmdutil.CheckErr(err) |
|
80 |
+ |
|
81 |
+ // If this is a dry run, print and exit |
|
82 |
+ if dryRun { |
|
83 |
+ err := helper.Describe(config, out) |
|
84 |
+ cmdutil.CheckErr(err) |
|
85 |
+ return |
|
86 |
+ } |
|
87 |
+ |
|
88 |
+ // If an output format is specified, print and exit |
|
89 |
+ if len(format) > 0 { |
|
90 |
+ err := helper.Print(config, format, template, out) |
|
91 |
+ cmdutil.CheckErr(err) |
|
92 |
+ return |
|
93 |
+ } |
|
94 |
+ |
|
95 |
+ // Perform the rollback |
|
96 |
+ rolledback, err := helper.Update(config) |
|
58 | 97 |
cmdutil.CheckErr(err) |
98 |
+ |
|
99 |
+ // Notify the user of any disabled image triggers |
|
100 |
+ fmt.Fprintf(out, "#%d rolled back to %s\n", rolledback.LatestVersion, rollback.Spec.From.Name) |
|
101 |
+ for _, trigger := range rolledback.Triggers { |
|
102 |
+ disabled := []string{} |
|
103 |
+ if trigger.Type == deployapi.DeploymentTriggerOnImageChange && !trigger.ImageChangeParams.Automatic { |
|
104 |
+ disabled = append(disabled, trigger.ImageChangeParams.From.Name) |
|
105 |
+ } |
|
106 |
+ if len(disabled) > 0 { |
|
107 |
+ reenable := fmt.Sprintf("%s deploy %s --enable-triggers", fullName, rolledback.Name) |
|
108 |
+ fmt.Fprintf(cmd.Out(), "Warning: the following images triggers were disabled: %s\n You can re-enable them with: %s\n", strings.Join(disabled, ","), reenable) |
|
109 |
+ } |
|
110 |
+ } |
|
59 | 111 |
}, |
60 | 112 |
} |
61 | 113 |
|
... | ... |
@@ -69,58 +129,60 @@ func NewCmdRollback(fullName string, f *clientcmd.Factory, out io.Writer) *cobra |
69 | 69 |
return cmd |
70 | 70 |
} |
71 | 71 |
|
72 |
-// RunRollback contains all the necessary functionality for OpenShift cli rollback command |
|
73 |
-func RunRollback(f *clientcmd.Factory, out io.Writer, cmd *cobra.Command, args []string, rollback *deployapi.DeploymentConfigRollback) error { |
|
74 |
- if len(args) == 0 || len(args[0]) == 0 { |
|
75 |
- return cmdutil.UsageError(cmd, "A deployment name is required.") |
|
72 |
+// newHelper makes a hew helper using real clients. |
|
73 |
+func newHelper(oClient client.Interface, kClient kclient.Interface) *helper { |
|
74 |
+ return &helper{ |
|
75 |
+ generateRollback: func(namespace string, config *deployapi.DeploymentConfigRollback) (*deployapi.DeploymentConfig, error) { |
|
76 |
+ return oClient.DeploymentConfigs(namespace).Rollback(config) |
|
77 |
+ }, |
|
78 |
+ describe: func(config *deployapi.DeploymentConfig) (string, error) { |
|
79 |
+ describer := describe.NewDeploymentConfigDescriberForConfig(oClient, kClient, config) |
|
80 |
+ return describer.Describe(config.Namespace, config.Name) |
|
81 |
+ }, |
|
82 |
+ updateConfig: func(namespace string, config *deployapi.DeploymentConfig) (*deployapi.DeploymentConfig, error) { |
|
83 |
+ return oClient.DeploymentConfigs(namespace).Update(config) |
|
84 |
+ }, |
|
76 | 85 |
} |
86 |
+} |
|
77 | 87 |
|
78 |
- rollback.Spec.From.Name = args[0] |
|
79 |
- |
|
80 |
- outputFormat := cmdutil.GetFlagString(cmd, "output") |
|
81 |
- outputTemplate := cmdutil.GetFlagString(cmd, "template") |
|
82 |
- dryRun := cmdutil.GetFlagBool(cmd, "dry-run") |
|
88 |
+// helper knows how to perform various rollback related tasks. |
|
89 |
+type helper struct { |
|
90 |
+ // generateRollback generates a rolled back config from the input config |
|
91 |
+ generateRollback func(namespace string, config *deployapi.DeploymentConfigRollback) (*deployapi.DeploymentConfig, error) |
|
92 |
+ // describe returns the describer output for config |
|
93 |
+ describe func(config *deployapi.DeploymentConfig) (string, error) |
|
94 |
+ // updateConfig persists config |
|
95 |
+ updateConfig func(namespace string, config *deployapi.DeploymentConfig) (*deployapi.DeploymentConfig, error) |
|
96 |
+} |
|
83 | 97 |
|
84 |
- osClient, kClient, err := f.Clients() |
|
85 |
- if err != nil { |
|
86 |
- return err |
|
87 |
- } |
|
98 |
+// Generate generates a rolled back DeploymentConfig. |
|
99 |
+func (r *helper) Generate(namespace string, config *deployapi.DeploymentConfigRollback) (*deployapi.DeploymentConfig, error) { |
|
100 |
+ return r.generateRollback(namespace, config) |
|
101 |
+} |
|
88 | 102 |
|
89 |
- namespace, err := f.DefaultNamespace() |
|
103 |
+// Describe describes a DeploymentConfig. |
|
104 |
+func (r *helper) Describe(config *deployapi.DeploymentConfig, out io.Writer) error { |
|
105 |
+ description, err := r.describe(config) |
|
90 | 106 |
if err != nil { |
91 | 107 |
return err |
92 | 108 |
} |
109 |
+ out.Write([]byte(description)) |
|
110 |
+ return nil |
|
111 |
+} |
|
93 | 112 |
|
94 |
- // Generate the rollback config |
|
95 |
- newConfig, err := osClient.DeploymentConfigs(namespace).Rollback(rollback) |
|
113 |
+// Print prints a deployment config in the specified format with the given |
|
114 |
+// template. |
|
115 |
+func (r *helper) Print(config *deployapi.DeploymentConfig, format, template string, out io.Writer) error { |
|
116 |
+ printer, _, err := kubectl.GetPrinter(format, template) |
|
96 | 117 |
if err != nil { |
97 | 118 |
return err |
98 | 119 |
} |
120 |
+ versionedPrinter := kubectl.NewVersionedPrinter(printer, kapi.Scheme, latest.Version) |
|
121 |
+ versionedPrinter.PrintObj(config, out) |
|
122 |
+ return nil |
|
123 |
+} |
|
99 | 124 |
|
100 |
- // If dry-run is specified, describe the rollback and exit |
|
101 |
- if dryRun { |
|
102 |
- describer := describe.NewDeploymentConfigDescriberForConfig(osClient, kClient, newConfig) |
|
103 |
- description, err := describer.Describe(newConfig.Namespace, newConfig.Name) |
|
104 |
- if err != nil { |
|
105 |
- return err |
|
106 |
- } |
|
107 |
- out.Write([]byte(description)) |
|
108 |
- return nil |
|
109 |
- } |
|
110 |
- |
|
111 |
- // If an output format is specified, display the rollback config JSON and exit |
|
112 |
- // WITHOUT performing a rollback. |
|
113 |
- if len(outputFormat) > 0 { |
|
114 |
- printer, _, err := kubectl.GetPrinter(outputFormat, outputTemplate) |
|
115 |
- if err != nil { |
|
116 |
- return err |
|
117 |
- } |
|
118 |
- versionedPrinter := kubectl.NewVersionedPrinter(printer, kapi.Scheme, latest.Version) |
|
119 |
- versionedPrinter.PrintObj(newConfig, out) |
|
120 |
- return nil |
|
121 |
- } |
|
122 |
- |
|
123 |
- // Apply the rollback config |
|
124 |
- _, err = osClient.DeploymentConfigs(namespace).Update(newConfig) |
|
125 |
- return err |
|
125 |
+// Update persists the given DeploymentConfig. |
|
126 |
+func (r *helper) Update(config *deployapi.DeploymentConfig) (*deployapi.DeploymentConfig, error) { |
|
127 |
+ return r.updateConfig(config.Namespace, config) |
|
126 | 128 |
} |
... | ... |
@@ -12,9 +12,14 @@ import ( |
12 | 12 |
// in a configurable way. |
13 | 13 |
type RollbackGenerator struct{} |
14 | 14 |
|
15 |
-// GenerateRollback creates a new DeploymentConfig by merging to onto from based on the options provided |
|
16 |
-// by spec. The LatestVersion of the result is unconditionally incremented, as rollback candidates are |
|
17 |
-// should be possible to be deployed manually regardless of other system behavior such as triggering. |
|
15 |
+// GenerateRollback creates a new DeploymentConfig by merging to onto from |
|
16 |
+// based on the options provided by spec. The LatestVersion of the result is |
|
17 |
+// unconditionally incremented, as rollback candidates are should be possible |
|
18 |
+// to be deployed manually regardless of other system behavior such as |
|
19 |
+// triggering. |
|
20 |
+// |
|
21 |
+// Any image change triggers on the new config are disabled to prevent |
|
22 |
+// triggered deployments from immediately replacing the rollback. |
|
18 | 23 |
func (g *RollbackGenerator) GenerateRollback(from, to *deployapi.DeploymentConfig, spec *deployapi.DeploymentConfigRollbackSpec) (*deployapi.DeploymentConfig, error) { |
19 | 24 |
rollback := &deployapi.DeploymentConfig{} |
20 | 25 |
|
... | ... |
@@ -49,6 +54,13 @@ func (g *RollbackGenerator) GenerateRollback(from, to *deployapi.DeploymentConfi |
49 | 49 |
} |
50 | 50 |
} |
51 | 51 |
|
52 |
+ // Disable any image change triggers. |
|
53 |
+ for _, trigger := range rollback.Triggers { |
|
54 |
+ if trigger.Type == deployapi.DeploymentTriggerOnImageChange { |
|
55 |
+ trigger.ImageChangeParams.Automatic = false |
|
56 |
+ } |
|
57 |
+ } |
|
58 |
+ |
|
52 | 59 |
// TODO: add a new cause? |
53 | 60 |
rollback.LatestVersion++ |
54 | 61 |
|
... | ... |
@@ -15,6 +15,7 @@ func TestGeneration(t *testing.T) { |
15 | 15 |
Type: deployapi.DeploymentStrategyTypeCustom, |
16 | 16 |
} |
17 | 17 |
from.Triggers = append(from.Triggers, deployapi.DeploymentTriggerPolicy{Type: deployapi.DeploymentTriggerOnConfigChange}) |
18 |
+ from.Triggers = append(from.Triggers, deploytest.OkImageChangeTrigger()) |
|
18 | 19 |
from.Template.ControllerTemplate.Template.Spec.Containers[0].Name = "changed" |
19 | 20 |
from.Template.ControllerTemplate.Replicas = 5 |
20 | 21 |
from.Template.ControllerTemplate.Selector = map[string]string{ |
... | ... |
@@ -64,6 +65,12 @@ func TestGeneration(t *testing.T) { |
64 | 64 |
if hasReplicationMetaDiff(from, rollback) && !spec.IncludeReplicationMeta { |
65 | 65 |
t.Fatalf("unexpected replication meta diff: from=%v, rollback=%v", from, rollback) |
66 | 66 |
} |
67 |
+ |
|
68 |
+ for i, trigger := range rollback.Triggers { |
|
69 |
+ if trigger.Type == deployapi.DeploymentTriggerOnImageChange && trigger.ImageChangeParams.Automatic { |
|
70 |
+ t.Errorf("image change trigger %d should be disabled", i) |
|
71 |
+ } |
|
72 |
+ } |
|
67 | 73 |
} |
68 | 74 |
} |
69 | 75 |
} |