Browse code

deploy: add new endpoint for rolling back deploymentconfigs

Michail Kargakis authored on 2016/06/03 00:46:45
Showing 19 changed files
... ...
@@ -25,6 +25,7 @@ type DeploymentConfigInterface interface {
25 25
 	Watch(opts kapi.ListOptions) (watch.Interface, error)
26 26
 	Generate(name string) (*deployapi.DeploymentConfig, error)
27 27
 	Rollback(config *deployapi.DeploymentConfigRollback) (*deployapi.DeploymentConfig, error)
28
+	RollbackDeprecated(config *deployapi.DeploymentConfigRollback) (*deployapi.DeploymentConfig, error)
28 29
 	GetScale(name string) (*extensions.Scale, error)
29 30
 	UpdateScale(scale *extensions.Scale) (*extensions.Scale, error)
30 31
 	UpdateStatus(config *deployapi.DeploymentConfig) (*deployapi.DeploymentConfig, error)
... ...
@@ -104,6 +105,20 @@ func (c *deploymentConfigs) Rollback(config *deployapi.DeploymentConfigRollback)
104 104
 	result = &deployapi.DeploymentConfig{}
105 105
 	err = c.r.Post().
106 106
 		Namespace(c.ns).
107
+		Resource("deploymentConfigs").
108
+		Name(config.Name).
109
+		SubResource("rollback").
110
+		Body(config).
111
+		Do().
112
+		Into(result)
113
+	return
114
+}
115
+
116
+// RollbackDeprecated rolls a deploymentConfig back to a previous configuration
117
+func (c *deploymentConfigs) RollbackDeprecated(config *deployapi.DeploymentConfigRollback) (result *deployapi.DeploymentConfig, err error) {
118
+	result = &deployapi.DeploymentConfig{}
119
+	err = c.r.Post().
120
+		Namespace(c.ns).
107 121
 		Resource("deploymentConfigRollbacks").
108 122
 		Body(config).
109 123
 		Do().
... ...
@@ -71,6 +71,15 @@ func (c *FakeDeploymentConfigs) Generate(name string) (*deployapi.DeploymentConf
71 71
 }
72 72
 
73 73
 func (c *FakeDeploymentConfigs) Rollback(inObj *deployapi.DeploymentConfigRollback) (result *deployapi.DeploymentConfig, err error) {
74
+	obj, err := c.Fake.Invokes(ktestclient.NewCreateAction("deploymentconfigs/rollback", c.Namespace, inObj), inObj)
75
+	if obj == nil {
76
+		return nil, err
77
+	}
78
+
79
+	return obj.(*deployapi.DeploymentConfig), err
80
+}
81
+
82
+func (c *FakeDeploymentConfigs) RollbackDeprecated(inObj *deployapi.DeploymentConfigRollback) (result *deployapi.DeploymentConfig, err error) {
74 83
 	obj, err := c.Fake.Invokes(ktestclient.NewCreateAction("deploymentconfigrollbacks", c.Namespace, inObj), inObj)
75 84
 	if obj == nil {
76 85
 		return nil, err
... ...
@@ -227,7 +227,7 @@ func (o *RollbackOptions) Run() error {
227 227
 			IncludeReplicationMeta: o.IncludeScalingSettings,
228 228
 		},
229 229
 	}
230
-	newConfig, err := o.oc.DeploymentConfigs(o.Namespace).Rollback(rollback)
230
+	newConfig, err := o.oc.DeploymentConfigs(o.Namespace).RollbackDeprecated(rollback)
231 231
 	if err != nil {
232 232
 		return err
233 233
 	}
... ...
@@ -224,7 +224,7 @@ func GetBootstrapClusterRoles() []authorizationapi.ClusterRole {
224 224
 				authorizationapi.NewRule(read...).Groups(buildGroup).Resources("builds/log").RuleOrDie(),
225 225
 				authorizationapi.NewRule("create").Groups(buildGroup).Resources("buildconfigs/instantiate", "buildconfigs/instantiatebinary", "builds/clone").RuleOrDie(),
226 226
 
227
-				authorizationapi.NewRule(readWrite...).Groups(deployGroup).Resources("deploymentconfigs", "generatedeploymentconfigs", "deploymentconfigrollbacks", "deploymentconfigs/scale").RuleOrDie(),
227
+				authorizationapi.NewRule(readWrite...).Groups(deployGroup).Resources("deploymentconfigs", "generatedeploymentconfigs", "deploymentconfigrollbacks", "deploymentconfigs/rollback", "deploymentconfigs/scale").RuleOrDie(),
228 228
 				authorizationapi.NewRule(read...).Groups(deployGroup).Resources("deploymentconfigs/log", "deploymentconfigs/status").RuleOrDie(),
229 229
 
230 230
 				authorizationapi.NewRule(readWrite...).Groups(imageGroup).Resources("imagestreams", "imagestreammappings", "imagestreamtags", "imagestreamimages", "imagestreams/secrets").RuleOrDie(),
... ...
@@ -271,7 +271,7 @@ func GetBootstrapClusterRoles() []authorizationapi.ClusterRole {
271 271
 				authorizationapi.NewRule(read...).Groups(buildGroup).Resources("builds/log").RuleOrDie(),
272 272
 				authorizationapi.NewRule("create").Groups(buildGroup).Resources("buildconfigs/instantiate", "buildconfigs/instantiatebinary", "builds/clone").RuleOrDie(),
273 273
 
274
-				authorizationapi.NewRule(readWrite...).Groups(deployGroup).Resources("deploymentconfigs", "generatedeploymentconfigs", "deploymentconfigrollbacks", "deploymentconfigs/scale").RuleOrDie(),
274
+				authorizationapi.NewRule(readWrite...).Groups(deployGroup).Resources("deploymentconfigs", "generatedeploymentconfigs", "deploymentconfigrollbacks", "deploymentconfigs/rollback", "deploymentconfigs/scale").RuleOrDie(),
275 275
 				authorizationapi.NewRule(read...).Groups(deployGroup).Resources("deploymentconfigs/log", "deploymentconfigs/status").RuleOrDie(),
276 276
 
277 277
 				authorizationapi.NewRule(readWrite...).Groups(imageGroup).Resources("imagestreams", "imagestreammappings", "imagestreamtags", "imagestreamimages", "imagestreams/secrets").RuleOrDie(),
... ...
@@ -313,7 +313,7 @@ func GetBootstrapClusterRoles() []authorizationapi.ClusterRole {
313 313
 				authorizationapi.NewRule(read...).Groups(buildGroup).Resources("builds", "buildconfigs", "buildconfigs/webhooks").RuleOrDie(),
314 314
 				authorizationapi.NewRule(read...).Groups(buildGroup).Resources("builds/log").RuleOrDie(),
315 315
 
316
-				authorizationapi.NewRule(read...).Groups(deployGroup).Resources("deploymentconfigs", "generatedeploymentconfigs", "deploymentconfigrollbacks", "deploymentconfigs/scale").RuleOrDie(),
316
+				authorizationapi.NewRule(read...).Groups(deployGroup).Resources("deploymentconfigs", "deploymentconfigs/scale").RuleOrDie(),
317 317
 				authorizationapi.NewRule(read...).Groups(deployGroup).Resources("deploymentconfigs/log", "deploymentconfigs/status").RuleOrDie(),
318 318
 
319 319
 				authorizationapi.NewRule(read...).Groups(imageGroup).Resources("imagestreams", "imagestreammappings", "imagestreamtags", "imagestreamimages").RuleOrDie(),
... ...
@@ -518,12 +518,12 @@ func (c *MasterConfig) GetRestStorage() map[string]rest.Storage {
518 518
 		},
519 519
 	}
520 520
 	configClient, kclient := c.DeploymentConfigClients()
521
-	deployRollback := &deployrollback.RollbackGenerator{}
522 521
 	deployRollbackClient := deployrollback.Client{
523 522
 		DCFn: deployConfigRegistry.GetDeploymentConfig,
524 523
 		RCFn: clientDeploymentInterface{kclient}.GetDeployment,
525
-		GRFn: deployRollback.GenerateRollback,
524
+		GRFn: deployrollback.NewRollbackGenerator().GenerateRollback,
526 525
 	}
526
+	deployConfigRollbackStorage := deployrollback.NewREST(configClient, kclient, c.EtcdHelper.Codec())
527 527
 
528 528
 	projectStorage := projectproxy.NewREST(kclient.Namespaces(), c.ProjectAuthorizationCache, c.ProjectAuthorizationCache, c.ProjectCache)
529 529
 
... ...
@@ -568,12 +568,15 @@ func (c *MasterConfig) GetRestStorage() map[string]rest.Storage {
568 568
 		"imageStreamMappings":  imageStreamMappingStorage,
569 569
 		"imageStreamTags":      imageStreamTagStorage,
570 570
 
571
-		"deploymentConfigs":         deployConfigStorage,
572
-		"deploymentConfigs/scale":   deployConfigScaleStorage,
573
-		"deploymentConfigs/status":  deployConfigStatusStorage,
571
+		"deploymentConfigs":          deployConfigStorage,
572
+		"deploymentConfigs/scale":    deployConfigScaleStorage,
573
+		"deploymentConfigs/status":   deployConfigStatusStorage,
574
+		"deploymentConfigs/rollback": deployConfigRollbackStorage,
575
+		"deploymentConfigs/log":      deploylogregistry.NewREST(configClient, kclient, c.DeploymentLogClient(), kubeletClient),
576
+
577
+		// TODO: Deprecate these
574 578
 		"generateDeploymentConfigs": deployconfiggenerator.NewREST(deployConfigGenerator, c.EtcdHelper.Codec()),
575
-		"deploymentConfigRollbacks": deployrollback.NewREST(deployRollbackClient, c.EtcdHelper.Codec()),
576
-		"deploymentConfigs/log":     deploylogregistry.NewREST(configClient, kclient, c.DeploymentLogClient(), kubeletClient),
579
+		"deploymentConfigRollbacks": deployrollback.NewDeprecatedREST(deployRollbackClient, c.EtcdHelper.Codec()),
577 580
 
578 581
 		"processedTemplates": templateregistry.NewREST(),
579 582
 		"templates":          templateStorage,
... ...
@@ -409,6 +409,10 @@ type DeploymentConfigList struct {
409 409
 // DeploymentConfigRollback provides the input to rollback generation.
410 410
 type DeploymentConfigRollback struct {
411 411
 	unversioned.TypeMeta
412
+	// Name of the deployment config that will be rolled back.
413
+	Name string
414
+	// UpdatedAnnotations is a set of new annotations that will be added in the deployment config.
415
+	UpdatedAnnotations map[string]string
412 416
 	// Spec defines the options to rollback generation.
413 417
 	Spec DeploymentConfigRollbackSpec
414 418
 }
... ...
@@ -417,6 +421,8 @@ type DeploymentConfigRollback struct {
417 417
 type DeploymentConfigRollbackSpec struct {
418 418
 	// From points to a ReplicationController which is a deployment.
419 419
 	From kapi.ObjectReference
420
+	// Revision to rollback to. If set to 0, rollback to the last revision.
421
+	Revision int64
420 422
 	// IncludeTriggers specifies whether to include config Triggers.
421 423
 	IncludeTriggers bool
422 424
 	// IncludeTemplate specifies whether to include the PodTemplateSpec.
... ...
@@ -366,6 +366,10 @@ type DeploymentConfigList struct {
366 366
 // DeploymentConfigRollback provides the input to rollback generation.
367 367
 type DeploymentConfigRollback struct {
368 368
 	unversioned.TypeMeta `json:",inline"`
369
+	// Name of the deployment config that will be rolled back.
370
+	Name string `json:"name"`
371
+	// UpdatedAnnotations is a set of new annotations that will be added in the deployment config.
372
+	UpdatedAnnotations map[string]string `json:"updatedAnnotations,omitempty"`
369 373
 	// Spec defines the options to rollback generation.
370 374
 	Spec DeploymentConfigRollbackSpec `json:"spec"`
371 375
 }
... ...
@@ -374,6 +378,8 @@ type DeploymentConfigRollback struct {
374 374
 type DeploymentConfigRollbackSpec struct {
375 375
 	// From points to a ReplicationController which is a deployment.
376 376
 	From kapi.ObjectReference `json:"from"`
377
+	// Revision to rollback to. If set to 0, rollback to the last revision.
378
+	Revision int64 `json:"revision,omitempty"`
377 379
 	// IncludeTriggers specifies whether to include config Triggers.
378 380
 	IncludeTriggers bool `json:"includeTriggers"`
379 381
 	// IncludeTemplate specifies whether to include the PodTemplateSpec.
... ...
@@ -377,6 +377,10 @@ type DeploymentConfigList struct {
377 377
 // DeploymentConfigRollback provides the input to rollback generation.
378 378
 type DeploymentConfigRollback struct {
379 379
 	unversioned.TypeMeta `json:",inline"`
380
+	// Name of the deployment config that will be rolled back.
381
+	Name string `json:"name"`
382
+	// UpdatedAnnotations is a set of new annotations that will be added in the deployment config.
383
+	UpdatedAnnotations map[string]string `json:"updatedAnnotations,omitempty"`
380 384
 	// Spec defines the options to rollback generation.
381 385
 	Spec DeploymentConfigRollbackSpec `json:"spec"`
382 386
 }
... ...
@@ -385,6 +389,8 @@ type DeploymentConfigRollback struct {
385 385
 type DeploymentConfigRollbackSpec struct {
386 386
 	// From points to a ReplicationController which is a deployment.
387 387
 	From kapi.ObjectReference `json:"from"`
388
+	// Revision to rollback to. If set to 0, rollback to the last revision.
389
+	Revision int64 `json:"revision,omitempty"`
388 390
 	// IncludeTriggers specifies whether to include config Triggers.
389 391
 	IncludeTriggers bool `json:"includeTriggers"`
390 392
 	// IncludeTemplate specifies whether to include the PodTemplateSpec.
... ...
@@ -148,6 +148,23 @@ func ValidateDeploymentConfigStatusUpdate(newConfig *deployapi.DeploymentConfig,
148 148
 func ValidateDeploymentConfigRollback(rollback *deployapi.DeploymentConfigRollback) field.ErrorList {
149 149
 	result := field.ErrorList{}
150 150
 
151
+	if len(rollback.Name) == 0 {
152
+		result = append(result, field.Required(field.NewPath("name"), "name of the deployment config is missing"))
153
+	} else if !kvalidation.IsDNS1123Subdomain(rollback.Name) {
154
+		result = append(result, field.Invalid(field.NewPath("name"), rollback.Name, "name of the deployment config is invalid"))
155
+	}
156
+
157
+	specPath := field.NewPath("spec")
158
+	if rollback.Spec.Revision < 0 {
159
+		result = append(result, field.Invalid(specPath.Child("revision"), rollback.Spec.Revision, "must be non-negative"))
160
+	}
161
+
162
+	return result
163
+}
164
+
165
+func ValidateDeploymentConfigRollbackDeprecated(rollback *deployapi.DeploymentConfigRollback) field.ErrorList {
166
+	result := field.ErrorList{}
167
+
151 168
 	fromPath := field.NewPath("spec", "from")
152 169
 	if len(rollback.Spec.From.Name) == 0 {
153 170
 		result = append(result, field.Required(fromPath.Child("name"), ""))
... ...
@@ -726,6 +726,20 @@ func TestValidateDeploymentConfigUpdate(t *testing.T) {
726 726
 
727 727
 func TestValidateDeploymentConfigRollbackOK(t *testing.T) {
728 728
 	rollback := &api.DeploymentConfigRollback{
729
+		Name: "config",
730
+		Spec: api.DeploymentConfigRollbackSpec{
731
+			Revision: 2,
732
+		},
733
+	}
734
+
735
+	errs := ValidateDeploymentConfigRollback(rollback)
736
+	if len(errs) > 0 {
737
+		t.Errorf("Unxpected non-empty error list: %v", errs)
738
+	}
739
+}
740
+
741
+func TestValidateDeploymentConfigRollbackDeprecatedOK(t *testing.T) {
742
+	rollback := &api.DeploymentConfigRollback{
729 743
 		Spec: api.DeploymentConfigRollbackSpec{
730 744
 			From: kapi.ObjectReference{
731 745
 				Name: "deployment",
... ...
@@ -733,7 +747,7 @@ func TestValidateDeploymentConfigRollbackOK(t *testing.T) {
733 733
 		},
734 734
 	}
735 735
 
736
-	errs := ValidateDeploymentConfigRollback(rollback)
736
+	errs := ValidateDeploymentConfigRollbackDeprecated(rollback)
737 737
 	if len(errs) > 0 {
738 738
 		t.Errorf("Unxpected non-empty error list: %v", errs)
739 739
 	}
... ...
@@ -749,6 +763,59 @@ func TestValidateDeploymentConfigRollbackInvalidFields(t *testing.T) {
749 749
 		T field.ErrorType
750 750
 		F string
751 751
 	}{
752
+		"missing name": {
753
+			api.DeploymentConfigRollback{
754
+				Spec: api.DeploymentConfigRollbackSpec{
755
+					Revision: 2,
756
+				},
757
+			},
758
+			field.ErrorTypeRequired,
759
+			"name",
760
+		},
761
+		"invalid name": {
762
+			api.DeploymentConfigRollback{
763
+				Name: "*_*myconfig",
764
+				Spec: api.DeploymentConfigRollbackSpec{
765
+					Revision: 2,
766
+				},
767
+			},
768
+			field.ErrorTypeInvalid,
769
+			"name",
770
+		},
771
+		"invalid revision": {
772
+			api.DeploymentConfigRollback{
773
+				Name: "config",
774
+				Spec: api.DeploymentConfigRollbackSpec{
775
+					Revision: -1,
776
+				},
777
+			},
778
+			field.ErrorTypeInvalid,
779
+			"spec.revision",
780
+		},
781
+	}
782
+
783
+	for k, v := range errorCases {
784
+		errs := ValidateDeploymentConfigRollback(&v.D)
785
+		if len(errs) == 0 {
786
+			t.Errorf("Expected failure for scenario %q", k)
787
+		}
788
+		for i := range errs {
789
+			if errs[i].Type != v.T {
790
+				t.Errorf("%s: expected errors to have type %q: %v", k, v.T, errs[i])
791
+			}
792
+			if errs[i].Field != v.F {
793
+				t.Errorf("%s: expected errors to have field %q: %v", k, v.F, errs[i])
794
+			}
795
+		}
796
+	}
797
+}
798
+
799
+func TestValidateDeploymentConfigRollbackDeprecatedInvalidFields(t *testing.T) {
800
+	errorCases := map[string]struct {
801
+		D api.DeploymentConfigRollback
802
+		T field.ErrorType
803
+		F string
804
+	}{
752 805
 		"missing spec.from.name": {
753 806
 			api.DeploymentConfigRollback{
754 807
 				Spec: api.DeploymentConfigRollbackSpec{
... ...
@@ -773,7 +840,7 @@ func TestValidateDeploymentConfigRollbackInvalidFields(t *testing.T) {
773 773
 	}
774 774
 
775 775
 	for k, v := range errorCases {
776
-		errs := ValidateDeploymentConfigRollback(&v.D)
776
+		errs := ValidateDeploymentConfigRollbackDeprecated(&v.D)
777 777
 		if len(errs) == 0 {
778 778
 			t.Errorf("Expected failure for scenario %s", k)
779 779
 		}
... ...
@@ -5,9 +5,11 @@ import (
5 5
 
6 6
 	kapi "k8s.io/kubernetes/pkg/api"
7 7
 	kerrors "k8s.io/kubernetes/pkg/api/errors"
8
+	kclient "k8s.io/kubernetes/pkg/client/unversioned"
8 9
 	"k8s.io/kubernetes/pkg/runtime"
9 10
 	"k8s.io/kubernetes/pkg/util/validation/field"
10 11
 
12
+	"github.com/openshift/origin/pkg/client"
11 13
 	deployapi "github.com/openshift/origin/pkg/deploy/api"
12 14
 	"github.com/openshift/origin/pkg/deploy/api/validation"
13 15
 	deployutil "github.com/openshift/origin/pkg/deploy/util"
... ...
@@ -15,95 +17,82 @@ import (
15 15
 
16 16
 // REST provides a rollback generation endpoint. Only the Create method is implemented.
17 17
 type REST struct {
18
-	generator GeneratorClient
18
+	generator RollbackGenerator
19
+	dn        client.DeploymentConfigsNamespacer
20
+	rn        kclient.ReplicationControllersNamespacer
19 21
 	codec     runtime.Codec
20 22
 }
21 23
 
22
-// GeneratorClient defines a local interface to a rollback generator for testability.
23
-type GeneratorClient interface {
24
-	GenerateRollback(from, to *deployapi.DeploymentConfig, spec *deployapi.DeploymentConfigRollbackSpec) (*deployapi.DeploymentConfig, error)
25
-	GetDeployment(ctx kapi.Context, name string) (*kapi.ReplicationController, error)
26
-	GetDeploymentConfig(ctx kapi.Context, name string) (*deployapi.DeploymentConfig, error)
27
-}
28
-
29
-// Client provides an implementation of Generator client
30
-type Client struct {
31
-	GRFn func(from, to *deployapi.DeploymentConfig, spec *deployapi.DeploymentConfigRollbackSpec) (*deployapi.DeploymentConfig, error)
32
-	RCFn func(ctx kapi.Context, name string) (*kapi.ReplicationController, error)
33
-	DCFn func(ctx kapi.Context, name string) (*deployapi.DeploymentConfig, error)
34
-}
35
-
36
-// GetDeployment returns the deploymentConfig with the provided context and name
37
-func (c Client) GetDeploymentConfig(ctx kapi.Context, name string) (*deployapi.DeploymentConfig, error) {
38
-	return c.DCFn(ctx, name)
39
-}
40
-
41
-// GetDeployment returns the deployment with the provided context and name
42
-func (c Client) GetDeployment(ctx kapi.Context, name string) (*kapi.ReplicationController, error) {
43
-	return c.RCFn(ctx, name)
44
-}
45
-
46
-// GenerateRollback generates a new deploymentConfig by merging a pair of deploymentConfigs
47
-func (c Client) GenerateRollback(from, to *deployapi.DeploymentConfig, spec *deployapi.DeploymentConfigRollbackSpec) (*deployapi.DeploymentConfig, error) {
48
-	return c.GRFn(from, to, spec)
49
-}
50
-
51 24
 // NewREST safely creates a new REST.
52
-func NewREST(generator GeneratorClient, codec runtime.Codec) *REST {
25
+func NewREST(oc client.Interface, kc kclient.Interface, codec runtime.Codec) *REST {
53 26
 	return &REST{
54
-		generator: generator,
27
+		generator: NewRollbackGenerator(),
28
+		dn:        oc,
29
+		rn:        kc,
55 30
 		codec:     codec,
56 31
 	}
57 32
 }
58 33
 
59 34
 // New creates an empty DeploymentConfigRollback resource
60
-func (s *REST) New() runtime.Object {
35
+func (r *REST) New() runtime.Object {
61 36
 	return &deployapi.DeploymentConfigRollback{}
62 37
 }
63 38
 
64 39
 // Create generates a new DeploymentConfig representing a rollback.
65
-func (s *REST) Create(ctx kapi.Context, obj runtime.Object) (runtime.Object, error) {
40
+func (r *REST) Create(ctx kapi.Context, obj runtime.Object) (runtime.Object, error) {
41
+	namespace, ok := kapi.NamespaceFrom(ctx)
42
+	if !ok {
43
+		return nil, kerrors.NewBadRequest("namespace parameter required.")
44
+	}
66 45
 	rollback, ok := obj.(*deployapi.DeploymentConfigRollback)
67 46
 	if !ok {
68 47
 		return nil, kerrors.NewBadRequest(fmt.Sprintf("not a rollback spec: %#v", obj))
69 48
 	}
70 49
 
71 50
 	if errs := validation.ValidateDeploymentConfigRollback(rollback); len(errs) > 0 {
72
-		return nil, kerrors.NewInvalid(deployapi.Kind("DeploymentConfigRollback"), "", errs)
51
+		return nil, kerrors.NewInvalid(deployapi.Kind("DeploymentConfigRollback"), rollback.Name, errs)
73 52
 	}
74 53
 
75
-	// Roll back "from" the current deployment "to" a target deployment
76
-
77
-	// Find the target ("to") deployment and decode the DeploymentConfig
78
-	targetDeployment, err := s.generator.GetDeployment(ctx, rollback.Spec.From.Name)
54
+	from, err := r.dn.DeploymentConfigs(namespace).Get(rollback.Name)
79 55
 	if err != nil {
80
-		if kerrors.IsNotFound(err) {
81
-			return nil, newInvalidDeploymentError(rollback, "Deployment not found")
82
-		}
83
-		return nil, newInvalidDeploymentError(rollback, fmt.Sprintf("%v", err))
56
+		return nil, newInvalidError(rollback, fmt.Sprintf("cannot get deployment config %q: %v", rollback.Name, err))
57
+	}
58
+
59
+	switch from.Status.LatestVersion {
60
+	case 0:
61
+		return nil, newInvalidError(rollback, "cannot rollback an undeployed config")
62
+	case 1:
63
+		return nil, newInvalidError(rollback, fmt.Sprintf("no previous deployment exists for %q", deployutil.LabelForDeploymentConfig(from)))
84 64
 	}
85 65
 
86
-	to, err := deployutil.DecodeDeploymentConfig(targetDeployment, s.codec)
66
+	revision := from.Status.LatestVersion - 1
67
+	if rollback.Spec.Revision > 0 {
68
+		revision = rollback.Spec.Revision
69
+	}
70
+
71
+	// Find the target deployment and decode its config.
72
+	name := deployutil.DeploymentNameForConfigVersion(from.Name, revision)
73
+	targetDeployment, err := r.rn.ReplicationControllers(namespace).Get(name)
87 74
 	if err != nil {
88
-		return nil, newInvalidDeploymentError(rollback,
89
-			fmt.Sprintf("couldn't decode DeploymentConfig from Deployment: %v", err))
75
+		return nil, newInvalidError(rollback, err.Error())
90 76
 	}
91 77
 
92
-	// Find the current ("from") version of the target deploymentConfig
93
-	from, err := s.generator.GetDeploymentConfig(ctx, to.Name)
78
+	to, err := deployutil.DecodeDeploymentConfig(targetDeployment, r.codec)
94 79
 	if err != nil {
95
-		if kerrors.IsNotFound(err) {
96
-			return nil, newInvalidDeploymentError(rollback,
97
-				fmt.Sprintf("couldn't find a current DeploymentConfig %s/%s", targetDeployment.Namespace, to.Name))
98
-		}
99
-		return nil, newInvalidDeploymentError(rollback,
100
-			fmt.Sprintf("error finding current DeploymentConfig %s/%s: %v", targetDeployment.Namespace, to.Name, err))
80
+		return nil, newInvalidError(rollback, fmt.Sprintf("couldn't decode deployment config from deployment: %v", err))
81
+	}
82
+
83
+	if from.Annotations == nil && len(rollback.UpdatedAnnotations) > 0 {
84
+		from.Annotations = make(map[string]string)
85
+	}
86
+	for key, value := range rollback.UpdatedAnnotations {
87
+		from.Annotations[key] = value
101 88
 	}
102 89
 
103
-	return s.generator.GenerateRollback(from, to, &rollback.Spec)
90
+	return r.generator.GenerateRollback(from, to, &rollback.Spec)
104 91
 }
105 92
 
106
-func newInvalidDeploymentError(rollback *deployapi.DeploymentConfigRollback, reason string) error {
107
-	err := field.Invalid(field.NewPath("spec").Child("from").Child("name"), rollback.Spec.From.Name, reason)
108
-	return kerrors.NewInvalid(deployapi.Kind("DeploymentConfigRollback"), "", field.ErrorList{err})
93
+func newInvalidError(rollback *deployapi.DeploymentConfigRollback, reason string) error {
94
+	err := field.Invalid(field.NewPath("name"), rollback.Name, reason)
95
+	return kerrors.NewInvalid(deployapi.Kind("DeploymentConfigRollback"), rollback.Name, field.ErrorList{err})
109 96
 }
110 97
new file mode 100644
... ...
@@ -0,0 +1,109 @@
0
+package rollback
1
+
2
+import (
3
+	"fmt"
4
+
5
+	kapi "k8s.io/kubernetes/pkg/api"
6
+	kerrors "k8s.io/kubernetes/pkg/api/errors"
7
+	"k8s.io/kubernetes/pkg/runtime"
8
+	"k8s.io/kubernetes/pkg/util/validation/field"
9
+
10
+	deployapi "github.com/openshift/origin/pkg/deploy/api"
11
+	"github.com/openshift/origin/pkg/deploy/api/validation"
12
+	deployutil "github.com/openshift/origin/pkg/deploy/util"
13
+)
14
+
15
+// REST provides a rollback generation endpoint. Only the Create method is implemented.
16
+type DeprecatedREST struct {
17
+	generator GeneratorClient
18
+	codec     runtime.Codec
19
+}
20
+
21
+// GeneratorClient defines a local interface to a rollback generator for testability.
22
+type GeneratorClient interface {
23
+	GenerateRollback(from, to *deployapi.DeploymentConfig, spec *deployapi.DeploymentConfigRollbackSpec) (*deployapi.DeploymentConfig, error)
24
+	GetDeployment(ctx kapi.Context, name string) (*kapi.ReplicationController, error)
25
+	GetDeploymentConfig(ctx kapi.Context, name string) (*deployapi.DeploymentConfig, error)
26
+}
27
+
28
+// Client provides an implementation of Generator client
29
+type Client struct {
30
+	GRFn func(from, to *deployapi.DeploymentConfig, spec *deployapi.DeploymentConfigRollbackSpec) (*deployapi.DeploymentConfig, error)
31
+	RCFn func(ctx kapi.Context, name string) (*kapi.ReplicationController, error)
32
+	DCFn func(ctx kapi.Context, name string) (*deployapi.DeploymentConfig, error)
33
+}
34
+
35
+// GetDeployment returns the deploymentConfig with the provided context and name
36
+func (c Client) GetDeploymentConfig(ctx kapi.Context, name string) (*deployapi.DeploymentConfig, error) {
37
+	return c.DCFn(ctx, name)
38
+}
39
+
40
+// GetDeployment returns the deployment with the provided context and name
41
+func (c Client) GetDeployment(ctx kapi.Context, name string) (*kapi.ReplicationController, error) {
42
+	return c.RCFn(ctx, name)
43
+}
44
+
45
+// GenerateRollback generates a new deploymentConfig by merging a pair of deploymentConfigs
46
+func (c Client) GenerateRollback(from, to *deployapi.DeploymentConfig, spec *deployapi.DeploymentConfigRollbackSpec) (*deployapi.DeploymentConfig, error) {
47
+	return c.GRFn(from, to, spec)
48
+}
49
+
50
+// NewDeprecatedREST safely creates a new REST.
51
+func NewDeprecatedREST(generator GeneratorClient, codec runtime.Codec) *DeprecatedREST {
52
+	return &DeprecatedREST{
53
+		generator: generator,
54
+		codec:     codec,
55
+	}
56
+}
57
+
58
+// New creates an empty DeploymentConfigRollback resource
59
+func (s *DeprecatedREST) New() runtime.Object {
60
+	return &deployapi.DeploymentConfigRollback{}
61
+}
62
+
63
+// Create generates a new DeploymentConfig representing a rollback.
64
+func (s *DeprecatedREST) Create(ctx kapi.Context, obj runtime.Object) (runtime.Object, error) {
65
+	rollback, ok := obj.(*deployapi.DeploymentConfigRollback)
66
+	if !ok {
67
+		return nil, kerrors.NewBadRequest(fmt.Sprintf("not a rollback spec: %#v", obj))
68
+	}
69
+
70
+	if errs := validation.ValidateDeploymentConfigRollbackDeprecated(rollback); len(errs) > 0 {
71
+		return nil, kerrors.NewInvalid(deployapi.Kind("DeploymentConfigRollback"), "", errs)
72
+	}
73
+
74
+	// Roll back "from" the current deployment "to" a target deployment
75
+
76
+	// Find the target ("to") deployment and decode the DeploymentConfig
77
+	targetDeployment, err := s.generator.GetDeployment(ctx, rollback.Spec.From.Name)
78
+	if err != nil {
79
+		if kerrors.IsNotFound(err) {
80
+			return nil, newInvalidDeploymentError(rollback, "Deployment not found")
81
+		}
82
+		return nil, newInvalidDeploymentError(rollback, fmt.Sprintf("%v", err))
83
+	}
84
+
85
+	to, err := deployutil.DecodeDeploymentConfig(targetDeployment, s.codec)
86
+	if err != nil {
87
+		return nil, newInvalidDeploymentError(rollback,
88
+			fmt.Sprintf("couldn't decode DeploymentConfig from Deployment: %v", err))
89
+	}
90
+
91
+	// Find the current ("from") version of the target deploymentConfig
92
+	from, err := s.generator.GetDeploymentConfig(ctx, to.Name)
93
+	if err != nil {
94
+		if kerrors.IsNotFound(err) {
95
+			return nil, newInvalidDeploymentError(rollback,
96
+				fmt.Sprintf("couldn't find a current DeploymentConfig %s/%s", targetDeployment.Namespace, to.Name))
97
+		}
98
+		return nil, newInvalidDeploymentError(rollback,
99
+			fmt.Sprintf("error finding current DeploymentConfig %s/%s: %v", targetDeployment.Namespace, to.Name, err))
100
+	}
101
+
102
+	return s.generator.GenerateRollback(from, to, &rollback.Spec)
103
+}
104
+
105
+func newInvalidDeploymentError(rollback *deployapi.DeploymentConfigRollback, reason string) error {
106
+	err := field.Invalid(field.NewPath("spec").Child("from").Child("name"), rollback.Spec.From.Name, reason)
107
+	return kerrors.NewInvalid(deployapi.Kind("DeploymentConfigRollback"), "", field.ErrorList{err})
108
+}
0 109
new file mode 100644
... ...
@@ -0,0 +1,231 @@
0
+package rollback
1
+
2
+import (
3
+	"errors"
4
+	"fmt"
5
+	"strings"
6
+	"testing"
7
+
8
+	kapi "k8s.io/kubernetes/pkg/api"
9
+	kerrors "k8s.io/kubernetes/pkg/api/errors"
10
+
11
+	deployapi "github.com/openshift/origin/pkg/deploy/api"
12
+	_ "github.com/openshift/origin/pkg/deploy/api/install"
13
+	deploytest "github.com/openshift/origin/pkg/deploy/api/test"
14
+	deployutil "github.com/openshift/origin/pkg/deploy/util"
15
+)
16
+
17
+func TestCreateErrorDepr(t *testing.T) {
18
+	rest := DeprecatedREST{}
19
+	obj, err := rest.Create(kapi.NewDefaultContext(), &deployapi.DeploymentConfig{})
20
+
21
+	if err == nil {
22
+		t.Errorf("Expected an error")
23
+	}
24
+
25
+	if obj != nil {
26
+		t.Errorf("Unexpected non-nil object: %#v", obj)
27
+	}
28
+}
29
+
30
+func TestCreateInvalidDepr(t *testing.T) {
31
+	rest := DeprecatedREST{}
32
+	obj, err := rest.Create(kapi.NewDefaultContext(), &deployapi.DeploymentConfigRollback{})
33
+
34
+	if err == nil {
35
+		t.Errorf("Expected an error")
36
+	}
37
+
38
+	if obj != nil {
39
+		t.Errorf("Unexpected non-nil object: %#v", obj)
40
+	}
41
+}
42
+
43
+func TestCreateOkDepr(t *testing.T) {
44
+	rest := DeprecatedREST{
45
+		generator: Client{
46
+			GRFn: func(from, to *deployapi.DeploymentConfig, spec *deployapi.DeploymentConfigRollbackSpec) (*deployapi.DeploymentConfig, error) {
47
+				return &deployapi.DeploymentConfig{}, nil
48
+			},
49
+			RCFn: func(ctx kapi.Context, name string) (*kapi.ReplicationController, error) {
50
+				deployment, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(1), kapi.Codecs.LegacyCodec(deployapi.SchemeGroupVersion))
51
+				return deployment, nil
52
+			},
53
+			DCFn: func(ctx kapi.Context, name string) (*deployapi.DeploymentConfig, error) {
54
+				return deploytest.OkDeploymentConfig(1), nil
55
+			},
56
+		},
57
+		codec: kapi.Codecs.LegacyCodec(deployapi.SchemeGroupVersion),
58
+	}
59
+
60
+	obj, err := rest.Create(kapi.NewDefaultContext(), &deployapi.DeploymentConfigRollback{
61
+		Spec: deployapi.DeploymentConfigRollbackSpec{
62
+			From: kapi.ObjectReference{
63
+				Name:      "deployment",
64
+				Namespace: kapi.NamespaceDefault,
65
+			},
66
+		},
67
+	})
68
+
69
+	if err != nil {
70
+		t.Errorf("Unexpected error: %v", err)
71
+	}
72
+
73
+	if obj == nil {
74
+		t.Errorf("Expected a result obj")
75
+	}
76
+
77
+	if _, ok := obj.(*deployapi.DeploymentConfig); !ok {
78
+		t.Errorf("expected a DeploymentConfig, got a %#v", obj)
79
+	}
80
+}
81
+
82
+func TestCreateGeneratorErrorDepr(t *testing.T) {
83
+	rest := DeprecatedREST{
84
+		generator: Client{
85
+			GRFn: func(from, to *deployapi.DeploymentConfig, spec *deployapi.DeploymentConfigRollbackSpec) (*deployapi.DeploymentConfig, error) {
86
+				return nil, kerrors.NewInternalError(fmt.Errorf("something terrible happened"))
87
+			},
88
+			RCFn: func(ctx kapi.Context, name string) (*kapi.ReplicationController, error) {
89
+				deployment, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(1), kapi.Codecs.LegacyCodec(deployapi.SchemeGroupVersion))
90
+				return deployment, nil
91
+			},
92
+			DCFn: func(ctx kapi.Context, name string) (*deployapi.DeploymentConfig, error) {
93
+				return deploytest.OkDeploymentConfig(1), nil
94
+			},
95
+		},
96
+		codec: kapi.Codecs.LegacyCodec(deployapi.SchemeGroupVersion),
97
+	}
98
+
99
+	_, err := rest.Create(kapi.NewDefaultContext(), &deployapi.DeploymentConfigRollback{
100
+		Spec: deployapi.DeploymentConfigRollbackSpec{
101
+			From: kapi.ObjectReference{
102
+				Name:      "deployment",
103
+				Namespace: kapi.NamespaceDefault,
104
+			},
105
+		},
106
+	})
107
+
108
+	if err == nil || !strings.Contains(err.Error(), "something terrible happened") {
109
+		t.Errorf("Unexpected error: %v", err)
110
+	}
111
+}
112
+
113
+func TestCreateMissingDeploymentDepr(t *testing.T) {
114
+	rest := DeprecatedREST{
115
+		generator: Client{
116
+			GRFn: func(from, to *deployapi.DeploymentConfig, spec *deployapi.DeploymentConfigRollbackSpec) (*deployapi.DeploymentConfig, error) {
117
+				t.Fatal("unexpected call to generator")
118
+				return nil, errors.New("something terrible happened")
119
+			},
120
+			RCFn: func(ctx kapi.Context, name string) (*kapi.ReplicationController, error) {
121
+				return nil, kerrors.NewNotFound(kapi.Resource("replicationController"), name)
122
+			},
123
+			DCFn: func(ctx kapi.Context, name string) (*deployapi.DeploymentConfig, error) {
124
+				namespace, _ := kapi.NamespaceFrom(ctx)
125
+				t.Fatalf("unexpected call to GetDeploymentConfig(%s/%s)", namespace, name)
126
+				return nil, kerrors.NewNotFound(deployapi.Resource("deploymentConfig"), name)
127
+			},
128
+		},
129
+		codec: kapi.Codecs.LegacyCodec(deployapi.SchemeGroupVersion),
130
+	}
131
+
132
+	obj, err := rest.Create(kapi.NewDefaultContext(), &deployapi.DeploymentConfigRollback{
133
+		Spec: deployapi.DeploymentConfigRollbackSpec{
134
+			From: kapi.ObjectReference{
135
+				Name:      "deployment",
136
+				Namespace: kapi.NamespaceDefault,
137
+			},
138
+		},
139
+	})
140
+
141
+	if err == nil {
142
+		t.Errorf("Expected an error")
143
+	}
144
+
145
+	if obj != nil {
146
+		t.Error("Unexpected result obj")
147
+	}
148
+}
149
+
150
+func TestCreateInvalidDeploymentDepr(t *testing.T) {
151
+	rest := DeprecatedREST{
152
+		generator: Client{
153
+			GRFn: func(from, to *deployapi.DeploymentConfig, spec *deployapi.DeploymentConfigRollbackSpec) (*deployapi.DeploymentConfig, error) {
154
+				t.Fatal("unexpected call to generator")
155
+				return nil, errors.New("something terrible happened")
156
+			},
157
+			RCFn: func(ctx kapi.Context, name string) (*kapi.ReplicationController, error) {
158
+				// invalidate the encoded config
159
+				deployment, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(1), kapi.Codecs.LegacyCodec(deployapi.SchemeGroupVersion))
160
+				deployment.Annotations[deployapi.DeploymentEncodedConfigAnnotation] = ""
161
+				return deployment, nil
162
+			},
163
+			DCFn: func(ctx kapi.Context, name string) (*deployapi.DeploymentConfig, error) {
164
+				namespace, _ := kapi.NamespaceFrom(ctx)
165
+				t.Fatalf("unexpected call to GetDeploymentConfig(%s/%s)", namespace, name)
166
+				return nil, kerrors.NewNotFound(deployapi.Resource("deploymentConfig"), name)
167
+			},
168
+		},
169
+		codec: kapi.Codecs.LegacyCodec(deployapi.SchemeGroupVersion),
170
+	}
171
+
172
+	obj, err := rest.Create(kapi.NewDefaultContext(), &deployapi.DeploymentConfigRollback{
173
+		Spec: deployapi.DeploymentConfigRollbackSpec{
174
+			From: kapi.ObjectReference{
175
+				Name:      "deployment",
176
+				Namespace: kapi.NamespaceDefault,
177
+			},
178
+		},
179
+	})
180
+
181
+	if err == nil {
182
+		t.Errorf("Expected an error")
183
+	}
184
+
185
+	if obj != nil {
186
+		t.Error("Unexpected result obj")
187
+	}
188
+}
189
+
190
+func TestCreateMissingDeploymentConfigDepr(t *testing.T) {
191
+	rest := DeprecatedREST{
192
+		generator: Client{
193
+			GRFn: func(from, to *deployapi.DeploymentConfig, spec *deployapi.DeploymentConfigRollbackSpec) (*deployapi.DeploymentConfig, error) {
194
+				t.Fatal("unexpected call to generator")
195
+				return nil, errors.New("something terrible happened")
196
+			},
197
+			RCFn: func(ctx kapi.Context, name string) (*kapi.ReplicationController, error) {
198
+				deployment, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(1), kapi.Codecs.LegacyCodec(deployapi.SchemeGroupVersion))
199
+				return deployment, nil
200
+			},
201
+			DCFn: func(ctx kapi.Context, name string) (*deployapi.DeploymentConfig, error) {
202
+				return nil, kerrors.NewNotFound(deployapi.Resource("deploymentConfig"), name)
203
+			},
204
+		},
205
+		codec: kapi.Codecs.LegacyCodec(deployapi.SchemeGroupVersion),
206
+	}
207
+
208
+	obj, err := rest.Create(kapi.NewDefaultContext(), &deployapi.DeploymentConfigRollback{
209
+		Spec: deployapi.DeploymentConfigRollbackSpec{
210
+			From: kapi.ObjectReference{
211
+				Name:      "deployment",
212
+				Namespace: kapi.NamespaceDefault,
213
+			},
214
+		},
215
+	})
216
+
217
+	if err == nil {
218
+		t.Errorf("Expected an error")
219
+	}
220
+
221
+	if obj != nil {
222
+		t.Error("Unexpected result obj")
223
+	}
224
+}
225
+
226
+func TestNewDepr(t *testing.T) {
227
+	// :)
228
+	rest := NewDeprecatedREST(Client{}, kapi.Codecs.LegacyCodec(deployapi.SchemeGroupVersion))
229
+	rest.New()
230
+}
... ...
@@ -2,19 +2,31 @@ package rollback
2 2
 
3 3
 import (
4 4
 	"errors"
5
-	"fmt"
6 5
 	"strings"
7 6
 	"testing"
8 7
 
9 8
 	kapi "k8s.io/kubernetes/pkg/api"
10 9
 	kerrors "k8s.io/kubernetes/pkg/api/errors"
10
+	ktestclient "k8s.io/kubernetes/pkg/client/unversioned/testclient"
11
+	"k8s.io/kubernetes/pkg/runtime"
11 12
 
13
+	"github.com/openshift/origin/pkg/client/testclient"
12 14
 	deployapi "github.com/openshift/origin/pkg/deploy/api"
13 15
 	_ "github.com/openshift/origin/pkg/deploy/api/install"
14 16
 	deploytest "github.com/openshift/origin/pkg/deploy/api/test"
15 17
 	deployutil "github.com/openshift/origin/pkg/deploy/util"
16 18
 )
17 19
 
20
+var codec = kapi.Codecs.LegacyCodec(deployapi.SchemeGroupVersion)
21
+
22
+type terribleGenerator struct{}
23
+
24
+func (tg *terribleGenerator) GenerateRollback(from, to *deployapi.DeploymentConfig, spec *deployapi.DeploymentConfigRollbackSpec) (*deployapi.DeploymentConfig, error) {
25
+	return nil, kerrors.NewInternalError(errors.New("something terrible happened"))
26
+}
27
+
28
+var _ RollbackGenerator = &terribleGenerator{}
29
+
18 30
 func TestCreateError(t *testing.T) {
19 31
 	rest := REST{}
20 32
 	obj, err := rest.Create(kapi.NewDefaultContext(), &deployapi.DeploymentConfig{})
... ...
@@ -42,28 +54,20 @@ func TestCreateInvalid(t *testing.T) {
42 42
 }
43 43
 
44 44
 func TestCreateOk(t *testing.T) {
45
-	rest := REST{
46
-		generator: Client{
47
-			GRFn: func(from, to *deployapi.DeploymentConfig, spec *deployapi.DeploymentConfigRollbackSpec) (*deployapi.DeploymentConfig, error) {
48
-				return &deployapi.DeploymentConfig{}, nil
49
-			},
50
-			RCFn: func(ctx kapi.Context, name string) (*kapi.ReplicationController, error) {
51
-				deployment, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(1), kapi.Codecs.LegacyCodec(deployapi.SchemeGroupVersion))
52
-				return deployment, nil
53
-			},
54
-			DCFn: func(ctx kapi.Context, name string) (*deployapi.DeploymentConfig, error) {
55
-				return deploytest.OkDeploymentConfig(1), nil
56
-			},
57
-		},
58
-		codec: kapi.Codecs.LegacyCodec(deployapi.SchemeGroupVersion),
59
-	}
45
+	oc := &testclient.Fake{}
46
+	oc.AddReactor("get", "deploymentconfigs", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) {
47
+		return true, deploytest.OkDeploymentConfig(2), nil
48
+	})
49
+	kc := &ktestclient.Fake{}
50
+	kc.AddReactor("get", "replicationcontrollers", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) {
51
+		deployment, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(1), codec)
52
+		return true, deployment, nil
53
+	})
60 54
 
61
-	obj, err := rest.Create(kapi.NewDefaultContext(), &deployapi.DeploymentConfigRollback{
55
+	obj, err := NewREST(oc, kc, codec).Create(kapi.NewDefaultContext(), &deployapi.DeploymentConfigRollback{
56
+		Name: "config",
62 57
 		Spec: deployapi.DeploymentConfigRollbackSpec{
63
-			From: kapi.ObjectReference{
64
-				Name:      "deployment",
65
-				Namespace: kapi.NamespaceDefault,
66
-			},
58
+			Revision: 1,
67 59
 		},
68 60
 	})
69 61
 
... ...
@@ -76,33 +80,32 @@ func TestCreateOk(t *testing.T) {
76 76
 	}
77 77
 
78 78
 	if _, ok := obj.(*deployapi.DeploymentConfig); !ok {
79
-		t.Errorf("expected a DeploymentConfig, got a %#v", obj)
79
+		t.Errorf("expected a deployment config, got a %#v", obj)
80 80
 	}
81 81
 }
82 82
 
83 83
 func TestCreateGeneratorError(t *testing.T) {
84
+	oc := &testclient.Fake{}
85
+	oc.AddReactor("get", "deploymentconfigs", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) {
86
+		return true, deploytest.OkDeploymentConfig(2), nil
87
+	})
88
+	kc := &ktestclient.Fake{}
89
+	kc.AddReactor("get", "replicationcontrollers", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) {
90
+		deployment, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(1), codec)
91
+		return true, deployment, nil
92
+	})
93
+
84 94
 	rest := REST{
85
-		generator: Client{
86
-			GRFn: func(from, to *deployapi.DeploymentConfig, spec *deployapi.DeploymentConfigRollbackSpec) (*deployapi.DeploymentConfig, error) {
87
-				return nil, kerrors.NewInternalError(fmt.Errorf("something terrible happened"))
88
-			},
89
-			RCFn: func(ctx kapi.Context, name string) (*kapi.ReplicationController, error) {
90
-				deployment, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(1), kapi.Codecs.LegacyCodec(deployapi.SchemeGroupVersion))
91
-				return deployment, nil
92
-			},
93
-			DCFn: func(ctx kapi.Context, name string) (*deployapi.DeploymentConfig, error) {
94
-				return deploytest.OkDeploymentConfig(1), nil
95
-			},
96
-		},
97
-		codec: kapi.Codecs.LegacyCodec(deployapi.SchemeGroupVersion),
95
+		generator: &terribleGenerator{},
96
+		dn:        oc,
97
+		rn:        kc,
98
+		codec:     kapi.Codecs.LegacyCodec(deployapi.SchemeGroupVersion),
98 99
 	}
99 100
 
100 101
 	_, err := rest.Create(kapi.NewDefaultContext(), &deployapi.DeploymentConfigRollback{
102
+		Name: "config",
101 103
 		Spec: deployapi.DeploymentConfigRollbackSpec{
102
-			From: kapi.ObjectReference{
103
-				Name:      "deployment",
104
-				Namespace: kapi.NamespaceDefault,
105
-			},
104
+			Revision: 1,
106 105
 		},
107 106
 	})
108 107
 
... ...
@@ -112,30 +115,20 @@ func TestCreateGeneratorError(t *testing.T) {
112 112
 }
113 113
 
114 114
 func TestCreateMissingDeployment(t *testing.T) {
115
-	rest := REST{
116
-		generator: Client{
117
-			GRFn: func(from, to *deployapi.DeploymentConfig, spec *deployapi.DeploymentConfigRollbackSpec) (*deployapi.DeploymentConfig, error) {
118
-				t.Fatal("unexpected call to generator")
119
-				return nil, errors.New("something terrible happened")
120
-			},
121
-			RCFn: func(ctx kapi.Context, name string) (*kapi.ReplicationController, error) {
122
-				return nil, kerrors.NewNotFound(kapi.Resource("replicationController"), name)
123
-			},
124
-			DCFn: func(ctx kapi.Context, name string) (*deployapi.DeploymentConfig, error) {
125
-				namespace, _ := kapi.NamespaceFrom(ctx)
126
-				t.Fatalf("unexpected call to GetDeploymentConfig(%s/%s)", namespace, name)
127
-				return nil, kerrors.NewNotFound(deployapi.Resource("deploymentConfig"), name)
128
-			},
129
-		},
130
-		codec: kapi.Codecs.LegacyCodec(deployapi.SchemeGroupVersion),
131
-	}
115
+	oc := &testclient.Fake{}
116
+	oc.AddReactor("get", "deploymentconfigs", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) {
117
+		return true, deploytest.OkDeploymentConfig(2), nil
118
+	})
119
+	kc := &ktestclient.Fake{}
120
+	kc.AddReactor("get", "replicationcontrollers", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) {
121
+		deployment, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(1), codec)
122
+		return true, nil, kerrors.NewNotFound(kapi.Resource("replicationController"), deployment.Name)
123
+	})
132 124
 
133
-	obj, err := rest.Create(kapi.NewDefaultContext(), &deployapi.DeploymentConfigRollback{
125
+	obj, err := NewREST(oc, kc, codec).Create(kapi.NewDefaultContext(), &deployapi.DeploymentConfigRollback{
126
+		Name: "config",
134 127
 		Spec: deployapi.DeploymentConfigRollbackSpec{
135
-			From: kapi.ObjectReference{
136
-				Name:      "deployment",
137
-				Namespace: kapi.NamespaceDefault,
138
-			},
128
+			Revision: 1,
139 129
 		},
140 130
 	})
141 131
 
... ...
@@ -149,33 +142,22 @@ func TestCreateMissingDeployment(t *testing.T) {
149 149
 }
150 150
 
151 151
 func TestCreateInvalidDeployment(t *testing.T) {
152
-	rest := REST{
153
-		generator: Client{
154
-			GRFn: func(from, to *deployapi.DeploymentConfig, spec *deployapi.DeploymentConfigRollbackSpec) (*deployapi.DeploymentConfig, error) {
155
-				t.Fatal("unexpected call to generator")
156
-				return nil, errors.New("something terrible happened")
157
-			},
158
-			RCFn: func(ctx kapi.Context, name string) (*kapi.ReplicationController, error) {
159
-				// invalidate the encoded config
160
-				deployment, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(1), kapi.Codecs.LegacyCodec(deployapi.SchemeGroupVersion))
161
-				deployment.Annotations[deployapi.DeploymentEncodedConfigAnnotation] = ""
162
-				return deployment, nil
163
-			},
164
-			DCFn: func(ctx kapi.Context, name string) (*deployapi.DeploymentConfig, error) {
165
-				namespace, _ := kapi.NamespaceFrom(ctx)
166
-				t.Fatalf("unexpected call to GetDeploymentConfig(%s/%s)", namespace, name)
167
-				return nil, kerrors.NewNotFound(deployapi.Resource("deploymentConfig"), name)
168
-			},
169
-		},
170
-		codec: kapi.Codecs.LegacyCodec(deployapi.SchemeGroupVersion),
171
-	}
152
+	oc := &testclient.Fake{}
153
+	oc.AddReactor("get", "deploymentconfigs", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) {
154
+		return true, deploytest.OkDeploymentConfig(2), nil
155
+	})
156
+	kc := &ktestclient.Fake{}
157
+	kc.AddReactor("get", "replicationcontrollers", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) {
158
+		// invalidate the encoded config
159
+		deployment, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(1), codec)
160
+		deployment.Annotations[deployapi.DeploymentEncodedConfigAnnotation] = ""
161
+		return true, deployment, nil
162
+	})
172 163
 
173
-	obj, err := rest.Create(kapi.NewDefaultContext(), &deployapi.DeploymentConfigRollback{
164
+	obj, err := NewREST(oc, kc, codec).Create(kapi.NewDefaultContext(), &deployapi.DeploymentConfigRollback{
165
+		Name: "config",
174 166
 		Spec: deployapi.DeploymentConfigRollbackSpec{
175
-			From: kapi.ObjectReference{
176
-				Name:      "deployment",
177
-				Namespace: kapi.NamespaceDefault,
178
-			},
167
+			Revision: 1,
179 168
 		},
180 169
 	})
181 170
 
... ...
@@ -189,29 +171,21 @@ func TestCreateInvalidDeployment(t *testing.T) {
189 189
 }
190 190
 
191 191
 func TestCreateMissingDeploymentConfig(t *testing.T) {
192
-	rest := REST{
193
-		generator: Client{
194
-			GRFn: func(from, to *deployapi.DeploymentConfig, spec *deployapi.DeploymentConfigRollbackSpec) (*deployapi.DeploymentConfig, error) {
195
-				t.Fatal("unexpected call to generator")
196
-				return nil, errors.New("something terrible happened")
197
-			},
198
-			RCFn: func(ctx kapi.Context, name string) (*kapi.ReplicationController, error) {
199
-				deployment, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(1), kapi.Codecs.LegacyCodec(deployapi.SchemeGroupVersion))
200
-				return deployment, nil
201
-			},
202
-			DCFn: func(ctx kapi.Context, name string) (*deployapi.DeploymentConfig, error) {
203
-				return nil, kerrors.NewNotFound(deployapi.Resource("deploymentConfig"), name)
204
-			},
205
-		},
206
-		codec: kapi.Codecs.LegacyCodec(deployapi.SchemeGroupVersion),
207
-	}
192
+	oc := &testclient.Fake{}
193
+	oc.AddReactor("get", "deploymentconfigs", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) {
194
+		dc := deploytest.OkDeploymentConfig(2)
195
+		return true, nil, kerrors.NewNotFound(deployapi.Resource("deploymentConfig"), dc.Name)
196
+	})
197
+	kc := &ktestclient.Fake{}
198
+	kc.AddReactor("get", "replicationcontrollers", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) {
199
+		deployment, _ := deployutil.MakeDeployment(deploytest.OkDeploymentConfig(1), codec)
200
+		return true, deployment, nil
201
+	})
208 202
 
209
-	obj, err := rest.Create(kapi.NewDefaultContext(), &deployapi.DeploymentConfigRollback{
203
+	obj, err := NewREST(oc, kc, codec).Create(kapi.NewDefaultContext(), &deployapi.DeploymentConfigRollback{
204
+		Name: "config",
210 205
 		Spec: deployapi.DeploymentConfigRollbackSpec{
211
-			From: kapi.ObjectReference{
212
-				Name:      "deployment",
213
-				Namespace: kapi.NamespaceDefault,
214
-			},
206
+			Revision: 1,
215 207
 		},
216 208
 	})
217 209
 
... ...
@@ -223,9 +197,3 @@ func TestCreateMissingDeploymentConfig(t *testing.T) {
223 223
 		t.Error("Unexpected result obj")
224 224
 	}
225 225
 }
226
-
227
-func TestNew(t *testing.T) {
228
-	// :)
229
-	rest := NewREST(Client{}, kapi.Codecs.LegacyCodec(deployapi.SchemeGroupVersion))
230
-	rest.New()
231
-}
... ...
@@ -8,19 +8,28 @@ import (
8 8
 	deployapi "github.com/openshift/origin/pkg/deploy/api"
9 9
 )
10 10
 
11
-// RollbackGenerator generates a new DeploymentConfig by merging a pair of DeploymentConfigs
12
-// in a configurable way.
13
-type RollbackGenerator struct{}
11
+// RollbackGenerator generates a new deployment config by merging a pair of deployment
12
+// configs in a configurable way.
13
+type RollbackGenerator interface {
14
+	// GenerateRollback creates a new deployment config by merging to onto from
15
+	// based on the options provided by spec. The latestVersion of the result is
16
+	// unconditionally incremented, as rollback candidates should be possible
17
+	// to be deployed manually regardless of other system behavior such as
18
+	// triggering.
19
+	//
20
+	// Any image change triggers on the new config are disabled to prevent
21
+	// triggered deployments from immediately replacing the rollback.
22
+	GenerateRollback(from, to *deployapi.DeploymentConfig, spec *deployapi.DeploymentConfigRollbackSpec) (*deployapi.DeploymentConfig, error)
23
+}
24
+
25
+// NewRollbackGenerator returns a new rollback generator.
26
+func NewRollbackGenerator() RollbackGenerator {
27
+	return &rollbackGenerator{}
28
+}
29
+
30
+type rollbackGenerator struct{}
14 31
 
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.
23
-func (g *RollbackGenerator) GenerateRollback(from, to *deployapi.DeploymentConfig, spec *deployapi.DeploymentConfigRollbackSpec) (*deployapi.DeploymentConfig, error) {
32
+func (g *rollbackGenerator) GenerateRollback(from, to *deployapi.DeploymentConfig, spec *deployapi.DeploymentConfigRollbackSpec) (*deployapi.DeploymentConfig, error) {
24 33
 	rollback := &deployapi.DeploymentConfig{}
25 34
 
26 35
 	if err := kapi.Scheme.Convert(&from, &rollback); err != nil {
... ...
@@ -62,6 +71,7 @@ func (g *RollbackGenerator) GenerateRollback(from, to *deployapi.DeploymentConfi
62 62
 	}
63 63
 
64 64
 	// TODO: add a new cause?
65
+	// TODO: Instantiate instead of incrementing latestVersion
65 66
 	rollback.Status.LatestVersion++
66 67
 
67 68
 	return rollback, nil
... ...
@@ -41,7 +41,7 @@ func TestGeneration(t *testing.T) {
41 41
 		rollbackSpecs = append(rollbackSpecs, spec)
42 42
 	}
43 43
 
44
-	generator := &RollbackGenerator{}
44
+	generator := NewRollbackGenerator()
45 45
 
46 46
 	// Test every combination.
47 47
 	for _, spec := range rollbackSpecs {
... ...
@@ -97,12 +97,12 @@ echo "get: ok"
97 97
 os::test::junit::declare_suite_end
98 98
 
99 99
 os::test::junit::declare_suite_start "cmd/deployments/rollback"
100
-os::cmd::expect_success 'oc rollback database --to-version=1 -o=yaml'
101
-os::cmd::expect_success 'oc rollback dc/database --to-version=1 -o=yaml'
102
-os::cmd::expect_success 'oc rollback dc/database --to-version=1 --dry-run'
103
-os::cmd::expect_success 'oc rollback database-1 -o=yaml'
104
-os::cmd::expect_success 'oc rollback rc/database-1 -o=yaml'
105 100
 # should fail because there's no previous deployment
101
+os::cmd::expect_failure 'oc rollback database --to-version=1 -o=yaml'
102
+os::cmd::expect_failure 'oc rollback dc/database --to-version=1 -o=yaml'
103
+os::cmd::expect_failure 'oc rollback dc/database --to-version=1 --dry-run'
104
+os::cmd::expect_failure 'oc rollback database-1 -o=yaml'
105
+os::cmd::expect_failure 'oc rollback rc/database-1 -o=yaml'
106 106
 os::cmd::expect_failure 'oc rollback database -o yaml'
107 107
 echo "rollback: ok"
108 108
 os::test::junit::declare_suite_end
... ...
@@ -97,7 +97,7 @@ func TestClusterReaderCoverage(t *testing.T) {
97 97
 	// remove resources without read APIs
98 98
 	nonreadingResources := []unversioned.GroupResource{
99 99
 		buildapi.Resource("buildconfigs/instantiatebinary"), buildapi.Resource("buildconfigs/instantiate"), buildapi.Resource("builds/clone"),
100
-		deployapi.Resource("deploymentconfigrollbacks"), deployapi.Resource("generatedeploymentconfigs"),
100
+		deployapi.Resource("deploymentconfigrollbacks"), deployapi.Resource("generatedeploymentconfigs"), deployapi.Resource("deploymentconfigs/rollback"),
101 101
 		imageapi.Resource("imagestreamimports"), imageapi.Resource("imagestreammappings"),
102 102
 		extensionsapi.Resource("deployments/rollback"),
103 103
 		kapi.Resource("pods/attach"), kapi.Resource("namespaces/finalize"),
... ...
@@ -557,6 +557,7 @@ items:
557 557
     resources:
558 558
     - deploymentconfigrollbacks
559 559
     - deploymentconfigs
560
+    - deploymentconfigs/rollback
560 561
     - deploymentconfigs/scale
561 562
     - generatedeploymentconfigs
562 563
     verbs:
... ...
@@ -871,6 +872,7 @@ items:
871 871
     resources:
872 872
     - deploymentconfigrollbacks
873 873
     - deploymentconfigs
874
+    - deploymentconfigs/rollback
874 875
     - deploymentconfigs/scale
875 876
     - generatedeploymentconfigs
876 877
     verbs:
... ...
@@ -1103,10 +1105,8 @@ items:
1103 1103
     - ""
1104 1104
     attributeRestrictions: null
1105 1105
     resources:
1106
-    - deploymentconfigrollbacks
1107 1106
     - deploymentconfigs
1108 1107
     - deploymentconfigs/scale
1109
-    - generatedeploymentconfigs
1110 1108
     verbs:
1111 1109
     - get
1112 1110
     - list