Browse code

Merge pull request #9349 from kargakis/instantiate-endpoint

Merged by openshift-bot

OpenShift Bot authored on 2016/10/05 00:55:30
Showing 45 changed files
... ...
@@ -230,6 +230,19 @@ message DeploymentLogOptions {
230 230
   optional int64 version = 10;
231 231
 }
232 232
 
233
+// DeploymentRequest is a request to a deployment config for a new deployment.
234
+message DeploymentRequest {
235
+  // Name of the deployment config for requesting a new deployment.
236
+  optional string name = 1;
237
+
238
+  // Latest will update the deployment config with the latest state from all triggers.
239
+  optional bool latest = 2;
240
+
241
+  // Force will try to force a new deployment to run. If the deployment config is paused,
242
+  // then setting this to true will return an Invalid error.
243
+  optional bool force = 3;
244
+}
245
+
233 246
 // DeploymentStrategy describes how to perform a deployment.
234 247
 message DeploymentStrategy {
235 248
   // Type is the name of a deployment strategy.
... ...
@@ -6258,6 +6258,67 @@
6258 6258
     ]
6259 6259
    },
6260 6260
    {
6261
+    "path": "/oapi/v1/namespaces/{namespace}/deploymentconfigs/{name}/instantiate",
6262
+    "description": "OpenShift REST API, version v1",
6263
+    "operations": [
6264
+     {
6265
+      "type": "v1.DeploymentRequest",
6266
+      "method": "POST",
6267
+      "summary": "create instantiate of a DeploymentRequest",
6268
+      "nickname": "createNamespacedDeploymentRequestInstantiate",
6269
+      "parameters": [
6270
+       {
6271
+        "type": "string",
6272
+        "paramType": "query",
6273
+        "name": "pretty",
6274
+        "description": "If 'true', then the output is pretty printed.",
6275
+        "required": false,
6276
+        "allowMultiple": false
6277
+       },
6278
+       {
6279
+        "type": "v1.DeploymentRequest",
6280
+        "paramType": "body",
6281
+        "name": "body",
6282
+        "description": "",
6283
+        "required": true,
6284
+        "allowMultiple": false
6285
+       },
6286
+       {
6287
+        "type": "string",
6288
+        "paramType": "path",
6289
+        "name": "namespace",
6290
+        "description": "object name and auth scope, such as for teams and projects",
6291
+        "required": true,
6292
+        "allowMultiple": false
6293
+       },
6294
+       {
6295
+        "type": "string",
6296
+        "paramType": "path",
6297
+        "name": "name",
6298
+        "description": "name of the DeploymentRequest",
6299
+        "required": true,
6300
+        "allowMultiple": false
6301
+       }
6302
+      ],
6303
+      "responseMessages": [
6304
+       {
6305
+        "code": 200,
6306
+        "message": "OK",
6307
+        "responseModel": "v1.DeploymentRequest"
6308
+       }
6309
+      ],
6310
+      "produces": [
6311
+       "application/json",
6312
+       "application/yaml",
6313
+       "application/vnd.kubernetes.protobuf"
6314
+      ],
6315
+      "consumes": [
6316
+       "*/*"
6317
+      ]
6318
+     }
6319
+    ]
6320
+   },
6321
+   {
6261 6322
     "path": "/oapi/v1/namespaces/{namespace}/deploymentconfigs/{name}/log",
6262 6323
     "description": "OpenShift REST API, version v1",
6263 6324
     "operations": [
... ...
@@ -24620,6 +24681,37 @@
24620 24620
      }
24621 24621
     }
24622 24622
    },
24623
+   "v1.DeploymentRequest": {
24624
+    "id": "v1.DeploymentRequest",
24625
+    "description": "DeploymentRequest is a request to a deployment config for a new deployment.",
24626
+    "required": [
24627
+     "name",
24628
+     "latest",
24629
+     "force"
24630
+    ],
24631
+    "properties": {
24632
+     "kind": {
24633
+      "type": "string",
24634
+      "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: http://releases.k8s.io/release-1.4/docs/devel/api-conventions.md#types-kinds"
24635
+     },
24636
+     "apiVersion": {
24637
+      "type": "string",
24638
+      "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: http://releases.k8s.io/release-1.4/docs/devel/api-conventions.md#resources"
24639
+     },
24640
+     "name": {
24641
+      "type": "string",
24642
+      "description": "Name of the deployment config for requesting a new deployment."
24643
+     },
24644
+     "latest": {
24645
+      "type": "boolean",
24646
+      "description": "Latest will update the deployment config with the latest state from all triggers."
24647
+     },
24648
+     "force": {
24649
+      "type": "boolean",
24650
+      "description": "Force will try to force a new deployment to run. If the deployment config is paused, then setting this to true will return an Invalid error."
24651
+     }
24652
+    }
24653
+   },
24623 24654
    "v1.DeploymentLog": {
24624 24655
     "id": "v1.DeploymentLog",
24625 24656
     "description": "DeploymentLog represents the logs for a deployment",
... ...
@@ -32199,6 +32199,64 @@
32199 32199
      }
32200 32200
     ]
32201 32201
    },
32202
+   "/oapi/v1/namespaces/{namespace}/deploymentconfigs/{name}/instantiate": {
32203
+    "post": {
32204
+     "description": "create instantiate of a DeploymentRequest",
32205
+     "consumes": [
32206
+      "*/*"
32207
+     ],
32208
+     "produces": [
32209
+      "application/json",
32210
+      "application/yaml",
32211
+      "application/vnd.kubernetes.protobuf"
32212
+     ],
32213
+     "schemes": [
32214
+      "https"
32215
+     ],
32216
+     "operationId": "createNamespacedDeploymentRequestInstantiate",
32217
+     "responses": {
32218
+      "200": {
32219
+       "description": "OK",
32220
+       "schema": {
32221
+        "$ref": "#/definitions/v1.DeploymentRequest"
32222
+       }
32223
+      }
32224
+     }
32225
+    },
32226
+    "parameters": [
32227
+     {
32228
+      "name": "body",
32229
+      "in": "body",
32230
+      "required": true,
32231
+      "schema": {
32232
+       "$ref": "#/definitions/v1.DeploymentRequest"
32233
+      }
32234
+     },
32235
+     {
32236
+      "uniqueItems": true,
32237
+      "type": "string",
32238
+      "description": "name of the DeploymentRequest",
32239
+      "name": "name",
32240
+      "in": "path",
32241
+      "required": true
32242
+     },
32243
+     {
32244
+      "uniqueItems": true,
32245
+      "type": "string",
32246
+      "description": "object name and auth scope, such as for teams and projects",
32247
+      "name": "namespace",
32248
+      "in": "path",
32249
+      "required": true
32250
+     },
32251
+     {
32252
+      "uniqueItems": true,
32253
+      "type": "string",
32254
+      "description": "If 'true', then the output is pretty printed.",
32255
+      "name": "pretty",
32256
+      "in": "query"
32257
+     }
32258
+    ]
32259
+   },
32202 32260
    "/oapi/v1/namespaces/{namespace}/deploymentconfigs/{name}/log": {
32203 32261
     "get": {
32204 32262
      "description": "read log of the specified DeploymentLog",
... ...
@@ -46200,6 +46258,36 @@
46200 46200
      }
46201 46201
     }
46202 46202
    },
46203
+   "v1.DeploymentRequest": {
46204
+    "description": "DeploymentRequest is a request to a deployment config for a new deployment.",
46205
+    "required": [
46206
+     "name",
46207
+     "latest",
46208
+     "force"
46209
+    ],
46210
+    "properties": {
46211
+     "apiVersion": {
46212
+      "description": "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: http://releases.k8s.io/release-1.4/docs/devel/api-conventions.md#resources",
46213
+      "type": "string"
46214
+     },
46215
+     "force": {
46216
+      "description": "Force will try to force a new deployment to run. If the deployment config is paused, then setting this to true will return an Invalid error.",
46217
+      "type": "boolean"
46218
+     },
46219
+     "kind": {
46220
+      "description": "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: http://releases.k8s.io/release-1.4/docs/devel/api-conventions.md#types-kinds",
46221
+      "type": "string"
46222
+     },
46223
+     "latest": {
46224
+      "description": "Latest will update the deployment config with the latest state from all triggers.",
46225
+      "type": "boolean"
46226
+     },
46227
+     "name": {
46228
+      "description": "Name of the deployment config for requesting a new deployment.",
46229
+      "type": "string"
46230
+     }
46231
+    }
46232
+   },
46203 46233
    "v1.DeploymentStrategy": {
46204 46234
     "description": "DeploymentStrategy describes how to perform a deployment.",
46205 46235
     "properties": {
... ...
@@ -64,6 +64,7 @@ func registerAll() {
64 64
 	Validator.MustRegister(&deployapi.DeploymentConfig{}, deployvalidation.ValidateDeploymentConfig, deployvalidation.ValidateDeploymentConfigUpdate)
65 65
 	Validator.MustRegister(&deployapi.DeploymentConfigRollback{}, deployvalidation.ValidateDeploymentConfigRollback, nil)
66 66
 	Validator.MustRegister(&deployapi.DeploymentLogOptions{}, deployvalidation.ValidateDeploymentLogOptions, nil)
67
+	Validator.MustRegister(&deployapi.DeploymentRequest{}, deployvalidation.ValidateDeploymentRequest, nil)
67 68
 	Validator.MustRegister(&extensions.Scale{}, extvalidation.ValidateScale, nil)
68 69
 
69 70
 	Validator.MustRegister(&imageapi.Image{}, imagevalidation.ValidateImage, imagevalidation.ValidateImageUpdate)
... ...
@@ -30,6 +30,7 @@ type DeploymentConfigInterface interface {
30 30
 	GetScale(name string) (*extensions.Scale, error)
31 31
 	UpdateScale(scale *extensions.Scale) (*extensions.Scale, error)
32 32
 	UpdateStatus(config *deployapi.DeploymentConfig) (*deployapi.DeploymentConfig, error)
33
+	Instantiate(request *deployapi.DeploymentRequest) (*deployapi.DeploymentConfig, error)
33 34
 }
34 35
 
35 36
 // deploymentConfigs implements DeploymentConfigsNamespacer interface
... ...
@@ -155,6 +156,13 @@ func (c *deploymentConfigs) UpdateStatus(deploymentConfig *deployapi.DeploymentC
155 155
 	return
156 156
 }
157 157
 
158
+// Instantiate instantiates a new build from build config returning new object or an error
159
+func (c *deploymentConfigs) Instantiate(request *deployapi.DeploymentRequest) (*deployapi.DeploymentConfig, error) {
160
+	result := &deployapi.DeploymentConfig{}
161
+	err := c.r.Post().Namespace(c.ns).Resource("deploymentConfigs").Name(request.Name).SubResource("instantiate").Body(request).Do().Into(result)
162
+	return result, err
163
+}
164
+
158 165
 type updateConfigFunc func(d *deployapi.DeploymentConfig)
159 166
 
160 167
 // UpdateConfigWithRetries will try to update a deployment config and ignore any update conflicts.
... ...
@@ -114,3 +114,13 @@ func (c *FakeDeploymentConfigs) UpdateStatus(inObj *deployapi.DeploymentConfig)
114 114
 
115 115
 	return obj.(*deployapi.DeploymentConfig), err
116 116
 }
117
+
118
+func (c *FakeDeploymentConfigs) Instantiate(inObj *deployapi.DeploymentRequest) (*deployapi.DeploymentConfig, error) {
119
+	deployment := &deployapi.DeploymentConfig{ObjectMeta: kapi.ObjectMeta{Name: inObj.Name}}
120
+	obj, err := c.Fake.Invokes(ktestclient.NewUpdateAction("deploymentconfigs/instantiate", c.Namespace, deployment), deployment)
121
+	if obj == nil {
122
+		return nil, err
123
+	}
124
+
125
+	return obj.(*deployapi.DeploymentConfig), err
126
+}
... ...
@@ -245,8 +245,19 @@ func (o DeployOptions) deploy(config *deployapi.DeploymentConfig) error {
245 245
 		return err
246 246
 	}
247 247
 
248
-	config.Status.LatestVersion++
249
-	dc, err := o.osClient.DeploymentConfigs(config.Namespace).Update(config)
248
+	request := &deployapi.DeploymentRequest{
249
+		Name:   config.Name,
250
+		Latest: false,
251
+		Force:  true,
252
+	}
253
+
254
+	dc, err := o.osClient.DeploymentConfigs(config.Namespace).Instantiate(request)
255
+	// Pre 1.4 servers don't support the instantiate endpoint. Fallback to incrementing
256
+	// latestVersion on them.
257
+	if kerrors.IsNotFound(err) || kerrors.IsForbidden(err) {
258
+		config.Status.LatestVersion++
259
+		dc, err = o.osClient.DeploymentConfigs(config.Namespace).Update(config)
260
+	}
250 261
 	if err != nil {
251 262
 		return err
252 263
 	}
... ...
@@ -40,14 +40,14 @@ func TestCmdDeploy_latestOk(t *testing.T) {
40 40
 	}
41 41
 	for _, status := range validStatusList {
42 42
 		config := deploytest.OkDeploymentConfig(1)
43
-		var updatedConfig *deployapi.DeploymentConfig
43
+		updatedConfig := config
44 44
 
45 45
 		osClient := &tc.Fake{}
46 46
 		osClient.AddReactor("get", "deploymentconfigs", func(action ktc.Action) (handled bool, ret runtime.Object, err error) {
47 47
 			return true, config, nil
48 48
 		})
49
-		osClient.AddReactor("update", "deploymentconfigs", func(action ktc.Action) (handled bool, ret runtime.Object, err error) {
50
-			updatedConfig = action.(ktc.UpdateAction).GetObject().(*deployapi.DeploymentConfig)
49
+		osClient.AddReactor("update", "deploymentconfigs/instantiate", func(action ktc.Action) (handled bool, ret runtime.Object, err error) {
50
+			updatedConfig.Status.LatestVersion++
51 51
 			return true, updatedConfig, nil
52 52
 		})
53 53
 
... ...
@@ -62,10 +62,7 @@ func TestCmdDeploy_latestOk(t *testing.T) {
62 62
 			t.Fatalf("unexpected error: %v", err)
63 63
 		}
64 64
 
65
-		if updatedConfig == nil {
66
-			t.Fatalf("expected updated config")
67
-		}
68
-		if exp, got := updatedConfig.Status.LatestVersion, int64(2); exp != got {
65
+		if exp, got := int64(2), updatedConfig.Status.LatestVersion; exp != got {
69 66
 			t.Fatalf("expected deployment config version: %d, got: %d", exp, got)
70 67
 		}
71 68
 	}
... ...
@@ -51,6 +51,7 @@ var DescriberCoverageExceptions = []reflect.Type{
51 51
 	reflect.TypeOf(&deployapi.DeploymentConfigRollback{}),             // normal users don't ever look at these
52 52
 	reflect.TypeOf(&deployapi.DeploymentLog{}),                        // normal users don't ever look at these
53 53
 	reflect.TypeOf(&deployapi.DeploymentLogOptions{}),                 // normal users don't ever look at these
54
+	reflect.TypeOf(&deployapi.DeploymentRequest{}),                    // normal users don't ever look at these
54 55
 	reflect.TypeOf(&imageapi.DockerImage{}),                           // not a top level resource
55 56
 	reflect.TypeOf(&imageapi.ImageStreamImport{}),                     // normal users don't ever look at these
56 57
 	reflect.TypeOf(&oauthapi.OAuthAccessToken{}),                      // normal users don't ever look at these
... ...
@@ -30,6 +30,7 @@ var PrinterCoverageExceptions = []reflect.Type{
30 30
 	reflect.TypeOf(&imageapi.ImageStreamImport{}),     // normal users don't ever look at these
31 31
 	reflect.TypeOf(&buildapi.BuildLog{}),              // just a marker type
32 32
 	reflect.TypeOf(&buildapi.BuildLogOptions{}),       // just a marker type
33
+	reflect.TypeOf(&deployapi.DeploymentRequest{}),    // normal users don't ever look at these
33 34
 	reflect.TypeOf(&deployapi.DeploymentLog{}),        // just a marker type
34 35
 	reflect.TypeOf(&deployapi.DeploymentLogOptions{}), // just a marker type
35 36
 
... ...
@@ -251,7 +251,8 @@ func GetBootstrapClusterRoles() []authorizationapi.ClusterRole {
251 251
 				// access to jenkins.  multiple values to ensure that covers relationships
252 252
 				authorizationapi.NewRule("admin", "edit", "view").Groups(buildapi.FutureGroupName).Resources("jenkins").RuleOrDie(),
253 253
 
254
-				authorizationapi.NewRule(readWrite...).Groups(deployGroup).Resources("deploymentconfigs", "generatedeploymentconfigs", "deploymentconfigrollbacks", "deploymentconfigs/rollback", "deploymentconfigs/scale").RuleOrDie(),
254
+				authorizationapi.NewRule(readWrite...).Groups(deployGroup).Resources("deploymentconfigs", "generatedeploymentconfigs", "deploymentconfigs/scale").RuleOrDie(),
255
+				authorizationapi.NewRule("create").Groups(deployGroup).Resources("deploymentconfigrollbacks", "deploymentconfigs/rollback", "deploymentconfigs/instantiate").RuleOrDie(),
255 256
 				authorizationapi.NewRule(read...).Groups(deployGroup).Resources("deploymentconfigs/log", "deploymentconfigs/status").RuleOrDie(),
256 257
 
257 258
 				authorizationapi.NewRule(readWrite...).Groups(imageGroup).Resources("imagestreams", "imagestreammappings", "imagestreamtags", "imagestreamimages", "imagestreams/secrets").RuleOrDie(),
... ...
@@ -305,7 +306,8 @@ func GetBootstrapClusterRoles() []authorizationapi.ClusterRole {
305 305
 				// access to jenkins.  multiple values to ensure that covers relationships
306 306
 				authorizationapi.NewRule("edit", "view").Groups(buildapi.FutureGroupName).Resources("jenkins").RuleOrDie(),
307 307
 
308
-				authorizationapi.NewRule(readWrite...).Groups(deployGroup).Resources("deploymentconfigs", "generatedeploymentconfigs", "deploymentconfigrollbacks", "deploymentconfigs/rollback", "deploymentconfigs/scale").RuleOrDie(),
308
+				authorizationapi.NewRule(readWrite...).Groups(deployGroup).Resources("deploymentconfigs", "generatedeploymentconfigs", "deploymentconfigs/scale").RuleOrDie(),
309
+				authorizationapi.NewRule("create").Groups(deployGroup).Resources("deploymentconfigrollbacks", "deploymentconfigs/rollback", "deploymentconfigs/instantiate").RuleOrDie(),
309 310
 				authorizationapi.NewRule(read...).Groups(deployGroup).Resources("deploymentconfigs/log", "deploymentconfigs/status").RuleOrDie(),
310 311
 
311 312
 				authorizationapi.NewRule(readWrite...).Groups(imageGroup).Resources("imagestreams", "imagestreammappings", "imagestreamtags", "imagestreamimages", "imagestreams/secrets").RuleOrDie(),
... ...
@@ -53,6 +53,7 @@ import (
53 53
 	deployconfigetcd "github.com/openshift/origin/pkg/deploy/registry/deployconfig/etcd"
54 54
 	deploylogregistry "github.com/openshift/origin/pkg/deploy/registry/deploylog"
55 55
 	deployconfiggenerator "github.com/openshift/origin/pkg/deploy/registry/generator"
56
+	deployconfiginstantiate "github.com/openshift/origin/pkg/deploy/registry/instantiate"
56 57
 	deployrollback "github.com/openshift/origin/pkg/deploy/registry/rollback"
57 58
 	"github.com/openshift/origin/pkg/dockerregistry"
58 59
 	"github.com/openshift/origin/pkg/image/importer"
... ...
@@ -498,7 +499,17 @@ func (c *MasterConfig) GetRestStorage() map[string]rest.Storage {
498 498
 	checkStorageErr(err)
499 499
 	buildConfigRegistry := buildconfigregistry.NewRegistry(buildConfigStorage)
500 500
 
501
-	deployConfigStorage, deployConfigStatusStorage, deployConfigScaleStorage, err := deployconfigetcd.NewREST(c.RESTOptionsGetter, c.DeploymentConfigScaleClient())
501
+	deployConfigStorage, deployConfigStatusStorage, deployConfigScaleStorage, err := deployconfigetcd.NewREST(c.RESTOptionsGetter)
502
+
503
+	dcInstantiateOriginClient, dcInstantiateKubeClient := c.DeploymentConfigInstantiateClients()
504
+	dcInstantiateStorage := deployconfiginstantiate.NewREST(
505
+		*deployConfigStorage.Store,
506
+		dcInstantiateOriginClient,
507
+		dcInstantiateKubeClient,
508
+		c.ExternalVersionCodec,
509
+		c.AdmissionControl,
510
+	)
511
+
502 512
 	checkStorageErr(err)
503 513
 	deployConfigRegistry := deployconfigregistry.NewRegistry(deployConfigStorage)
504 514
 
... ...
@@ -661,11 +672,12 @@ func (c *MasterConfig) GetRestStorage() map[string]rest.Storage {
661 661
 		"imageStreamMappings":  imageStreamMappingStorage,
662 662
 		"imageStreamTags":      imageStreamTagStorage,
663 663
 
664
-		"deploymentConfigs":          deployConfigStorage,
665
-		"deploymentConfigs/scale":    deployConfigScaleStorage,
666
-		"deploymentConfigs/status":   deployConfigStatusStorage,
667
-		"deploymentConfigs/rollback": deployConfigRollbackStorage,
668
-		"deploymentConfigs/log":      deploylogregistry.NewREST(configClient, kclient, c.DeploymentLogClient(), kubeletClient),
664
+		"deploymentConfigs":             deployConfigStorage,
665
+		"deploymentConfigs/scale":       deployConfigScaleStorage,
666
+		"deploymentConfigs/status":      deployConfigStatusStorage,
667
+		"deploymentConfigs/rollback":    deployConfigRollbackStorage,
668
+		"deploymentConfigs/log":         deploylogregistry.NewREST(configClient, kclient, c.DeploymentLogClient(), kubeletClient),
669
+		"deploymentConfigs/instantiate": dcInstantiateStorage,
669 670
 
670 671
 		// TODO: Deprecate these
671 672
 		"generateDeploymentConfigs": deployconfiggenerator.NewREST(deployConfigGenerator, c.ExternalVersionCodec),
... ...
@@ -787,8 +799,7 @@ func initReadinessCheckRoute(root *restful.WebService, path string, readyFunc fu
787 787
 		Produces(restful.MIME_JSON))
788 788
 }
789 789
 
790
-// initHealthCheckRoute initializes an HTTP endpoint for health checking.
791
-// OpenShift is deemed healthy if the API server can respond with an OK messages
790
+// initMetricsRoute initializes an HTTP endpoint for metrics.
792 791
 func initMetricsRoute(root *restful.WebService, path string) {
793 792
 	h := prometheus.Handler()
794 793
 	root.Route(root.GET(path).To(func(req *restful.Request, resp *restful.Response) {
... ...
@@ -898,9 +898,9 @@ func (c *MasterConfig) ImageImportControllerClient() *osclient.Client {
898 898
 	return c.PrivilegedLoopbackOpenShiftClient
899 899
 }
900 900
 
901
-// DeploymentConfigScaleClient returns the client used by the Scale subresource registry
902
-func (c *MasterConfig) DeploymentConfigScaleClient() *kclient.Client {
903
-	return c.PrivilegedLoopbackKubernetesClient
901
+// DeploymentConfigInstantiateClients returns the clients used by the instantiate endpoint.
902
+func (c *MasterConfig) DeploymentConfigInstantiateClients() (*osclient.Client, *kclient.Client) {
903
+	return c.PrivilegedLoopbackOpenShiftClient, c.PrivilegedLoopbackKubernetesClient
904 904
 }
905 905
 
906 906
 // DeploymentControllerClients returns the deployment controller client objects
... ...
@@ -922,13 +922,8 @@ func (c *MasterConfig) DeploymentConfigControllerClients() (*osclient.Client, *k
922 922
 	return c.PrivilegedLoopbackOpenShiftClient, c.PrivilegedLoopbackKubernetesClient
923 923
 }
924 924
 
925
-// DeploymentTriggerControllerClients returns the deploymentConfig trigger controller client objects
926
-func (c *MasterConfig) DeploymentTriggerControllerClients() (*osclient.Client, *kclient.Client) {
927
-	return c.PrivilegedLoopbackOpenShiftClient, c.PrivilegedLoopbackKubernetesClient
928
-}
929
-
930
-// DeploymentImageChangeTriggerControllerClient returns the deploymentConfig image change controller client object
931
-func (c *MasterConfig) DeploymentImageChangeTriggerControllerClient() *osclient.Client {
925
+// DeploymentTriggerControllerClient returns the deploymentConfig trigger controller client object
926
+func (c *MasterConfig) DeploymentTriggerControllerClient() *osclient.Client {
932 927
 	return c.PrivilegedLoopbackOpenShiftClient
933 928
 }
934 929
 
... ...
@@ -15,6 +15,7 @@ import (
15 15
 	"github.com/openshift/origin/pkg/api/validation"
16 16
 	otestclient "github.com/openshift/origin/pkg/client/testclient"
17 17
 	"github.com/openshift/origin/pkg/controller/shared"
18
+	deployapi "github.com/openshift/origin/pkg/deploy/api"
18 19
 	quotaapi "github.com/openshift/origin/pkg/quota/api"
19 20
 	"github.com/openshift/origin/pkg/quota/controller/clusterquotamapping"
20 21
 	"github.com/openshift/origin/pkg/util/restoptions"
... ...
@@ -25,6 +26,7 @@ import (
25 25
 var KnownUpdateValidationExceptions = []reflect.Type{
26 26
 	reflect.TypeOf(&extapi.Scale{}),                         // scale operation uses the ValidateScale() function for both create and update
27 27
 	reflect.TypeOf(&quotaapi.AppliedClusterResourceQuota{}), // this only retrieved, never created.  its a virtual projection of ClusterResourceQuota
28
+	reflect.TypeOf(&deployapi.DeploymentRequest{}),          // request for deployments already use ValidateDeploymentRequest()
28 29
 }
29 30
 
30 31
 // TestValidationRegistration makes sure that any RESTStorage that allows create or update has the correct validation register.
... ...
@@ -43,7 +43,6 @@ import (
43 43
 	deploycontroller "github.com/openshift/origin/pkg/deploy/controller/deployment"
44 44
 	deployconfigcontroller "github.com/openshift/origin/pkg/deploy/controller/deploymentconfig"
45 45
 	triggercontroller "github.com/openshift/origin/pkg/deploy/controller/generictrigger"
46
-	imagechangecontroller "github.com/openshift/origin/pkg/deploy/controller/imagechange"
47 46
 	"github.com/openshift/origin/pkg/dns"
48 47
 	imagecontroller "github.com/openshift/origin/pkg/image/controller"
49 48
 	projectcontroller "github.com/openshift/origin/pkg/project/controller"
... ...
@@ -356,19 +355,9 @@ func (c *MasterConfig) RunDeploymentConfigController() {
356 356
 func (c *MasterConfig) RunDeploymentTriggerController() {
357 357
 	dcInfomer := c.Informers.DeploymentConfigs().Informer()
358 358
 	streamInformer := c.Informers.ImageStreams().Informer()
359
-	osclient, kclient := c.DeploymentTriggerControllerClients()
359
+	osclient := c.DeploymentTriggerControllerClient()
360 360
 
361
-	controller := triggercontroller.NewDeploymentTriggerController(dcInfomer, streamInformer, osclient, kclient, c.ExternalVersionCodec)
362
-	go controller.Run(5, utilwait.NeverStop)
363
-}
364
-
365
-// RunDeploymentImageChangeTriggerController starts the image change trigger controller process.
366
-func (c *MasterConfig) RunDeploymentImageChangeTriggerController() {
367
-	dcInfomer := c.Informers.DeploymentConfigs().Informer()
368
-	streamInformer := c.Informers.ImageStreams().Informer()
369
-	osclient, _ := c.DeploymentTriggerControllerClients()
370
-
371
-	controller := imagechangecontroller.NewImageChangeController(dcInfomer, streamInformer, osclient)
361
+	controller := triggercontroller.NewDeploymentTriggerController(dcInfomer, streamInformer, osclient, c.ExternalVersionCodec)
372 362
 	go controller.Run(5, utilwait.NeverStop)
373 363
 }
374 364
 
... ...
@@ -713,7 +713,6 @@ func startControllers(oc *origin.MasterConfig, kc *kubernetes.MasterConfig) erro
713 713
 	oc.RunDeploymentController()
714 714
 	oc.RunDeploymentConfigController()
715 715
 	oc.RunDeploymentTriggerController()
716
-	oc.RunDeploymentImageChangeTriggerController()
717 716
 	oc.RunImageImportController()
718 717
 	oc.RunOriginNamespaceController()
719 718
 	oc.RunSDNController()
... ...
@@ -49,6 +49,13 @@ func ScaleFromConfig(dc *DeploymentConfig) *extensions.Scale {
49 49
 	}
50 50
 }
51 51
 
52
+// RequestForConfig builds a new deployment request for a deployment config.
53
+func RequestForConfig(dc *DeploymentConfig) *DeploymentRequest {
54
+	return &DeploymentRequest{
55
+		Name: dc.Name,
56
+	}
57
+}
58
+
52 59
 // TemplateImage is a structure for helping a caller iterate over a PodSpec
53 60
 type TemplateImage struct {
54 61
 	Image string
... ...
@@ -32,6 +32,7 @@ func addKnownTypes(scheme *runtime.Scheme) error {
32 32
 		&DeploymentConfig{},
33 33
 		&DeploymentConfigList{},
34 34
 		&DeploymentConfigRollback{},
35
+		&DeploymentRequest{},
35 36
 		&DeploymentLog{},
36 37
 		&DeploymentLogOptions{},
37 38
 	)
... ...
@@ -41,5 +42,6 @@ func addKnownTypes(scheme *runtime.Scheme) error {
41 41
 func (obj *DeploymentConfig) GetObjectKind() unversioned.ObjectKind         { return &obj.TypeMeta }
42 42
 func (obj *DeploymentConfigList) GetObjectKind() unversioned.ObjectKind     { return &obj.TypeMeta }
43 43
 func (obj *DeploymentConfigRollback) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
44
+func (obj *DeploymentRequest) GetObjectKind() unversioned.ObjectKind        { return &obj.TypeMeta }
44 45
 func (obj *DeploymentLog) GetObjectKind() unversioned.ObjectKind            { return &obj.TypeMeta }
45 46
 func (obj *DeploymentLogOptions) GetObjectKind() unversioned.ObjectKind     { return &obj.TypeMeta }
... ...
@@ -1,12 +1,15 @@
1 1
 package test
2 2
 
3 3
 import (
4
+	"testing"
5
+
4 6
 	kapi "k8s.io/kubernetes/pkg/api"
5 7
 	"k8s.io/kubernetes/pkg/api/resource"
6 8
 	"k8s.io/kubernetes/pkg/apis/autoscaling"
7 9
 	"k8s.io/kubernetes/pkg/util/sets"
8 10
 
9 11
 	deployapi "github.com/openshift/origin/pkg/deploy/api"
12
+	deployv1 "github.com/openshift/origin/pkg/deploy/api/v1"
10 13
 	imageapi "github.com/openshift/origin/pkg/image/api"
11 14
 )
12 15
 
... ...
@@ -238,6 +241,29 @@ func OkHPAForDeploymentConfig(config *deployapi.DeploymentConfig, min, max int)
238 238
 	}
239 239
 }
240 240
 
241
+func OkStreamForConfig(config *deployapi.DeploymentConfig) *imageapi.ImageStream {
242
+	for _, t := range config.Spec.Triggers {
243
+		if t.Type != deployapi.DeploymentTriggerOnImageChange {
244
+			continue
245
+		}
246
+
247
+		ref := t.ImageChangeParams.From
248
+		name, tag, _ := imageapi.SplitImageStreamTag(ref.Name)
249
+
250
+		return &imageapi.ImageStream{
251
+			ObjectMeta: kapi.ObjectMeta{
252
+				Name:      name,
253
+				Namespace: ref.Namespace,
254
+			},
255
+			Status: imageapi.ImageStreamStatus{
256
+				Tags: map[string]imageapi.TagEventList{
257
+					tag: {
258
+						Items: []imageapi.TagEvent{{DockerImageReference: t.ImageChangeParams.LastTriggeredImage}}}}},
259
+		}
260
+	}
261
+	return nil
262
+}
263
+
241 264
 func RemoveTriggerTypes(config *deployapi.DeploymentConfig, triggerTypes ...deployapi.DeploymentTriggerType) {
242 265
 	types := sets.NewString()
243 266
 	for _, triggerType := range triggerTypes {
... ...
@@ -254,3 +280,17 @@ func RemoveTriggerTypes(config *deployapi.DeploymentConfig, triggerTypes ...depl
254 254
 
255 255
 	config.Spec.Triggers = remaining
256 256
 }
257
+
258
+func RoundTripConfig(t *testing.T, config *deployapi.DeploymentConfig) *deployapi.DeploymentConfig {
259
+	versioned, err := kapi.Scheme.ConvertToVersion(config, deployv1.SchemeGroupVersion)
260
+	if err != nil {
261
+		t.Errorf("unexpected conversion error: %v", err)
262
+		return nil
263
+	}
264
+	defaulted, err := kapi.Scheme.ConvertToVersion(versioned, deployapi.SchemeGroupVersion)
265
+	if err != nil {
266
+		t.Errorf("unexpected conversion error: %v", err)
267
+		return nil
268
+	}
269
+	return defaulted.(*deployapi.DeploymentConfig)
270
+}
... ...
@@ -445,6 +445,18 @@ type DeploymentConfigRollbackSpec struct {
445 445
 	IncludeStrategy bool
446 446
 }
447 447
 
448
+// DeploymentRequest is a request to a deployment config for a new deployment.
449
+type DeploymentRequest struct {
450
+	unversioned.TypeMeta
451
+	// Name of the deployment config for requesting a new deployment.
452
+	Name string
453
+	// Latest will update the deployment config with the latest state from all triggers.
454
+	Latest bool
455
+	// Force will try to force a new deployment to run. If the deployment config is paused,
456
+	// then setting this to true will return an Invalid error.
457
+	Force bool
458
+}
459
+
448 460
 // DeploymentLog represents the logs for a deployment
449 461
 type DeploymentLog struct {
450 462
 	unversioned.TypeMeta
... ...
@@ -21,6 +21,7 @@
21 21
 		DeploymentDetails
22 22
 		DeploymentLog
23 23
 		DeploymentLogOptions
24
+		DeploymentRequest
24 25
 		DeploymentStrategy
25 26
 		DeploymentTriggerImageChangeParams
26 27
 		DeploymentTriggerPolicies
... ...
@@ -113,51 +114,55 @@ func (m *DeploymentLogOptions) Reset()                    { *m = DeploymentLogOp
113 113
 func (*DeploymentLogOptions) ProtoMessage()               {}
114 114
 func (*DeploymentLogOptions) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{11} }
115 115
 
116
+func (m *DeploymentRequest) Reset()                    { *m = DeploymentRequest{} }
117
+func (*DeploymentRequest) ProtoMessage()               {}
118
+func (*DeploymentRequest) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{12} }
119
+
116 120
 func (m *DeploymentStrategy) Reset()                    { *m = DeploymentStrategy{} }
117 121
 func (*DeploymentStrategy) ProtoMessage()               {}
118
-func (*DeploymentStrategy) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{12} }
122
+func (*DeploymentStrategy) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{13} }
119 123
 
120 124
 func (m *DeploymentTriggerImageChangeParams) Reset()      { *m = DeploymentTriggerImageChangeParams{} }
121 125
 func (*DeploymentTriggerImageChangeParams) ProtoMessage() {}
122 126
 func (*DeploymentTriggerImageChangeParams) Descriptor() ([]byte, []int) {
123
-	return fileDescriptorGenerated, []int{13}
127
+	return fileDescriptorGenerated, []int{14}
124 128
 }
125 129
 
126 130
 func (m *DeploymentTriggerPolicies) Reset()      { *m = DeploymentTriggerPolicies{} }
127 131
 func (*DeploymentTriggerPolicies) ProtoMessage() {}
128 132
 func (*DeploymentTriggerPolicies) Descriptor() ([]byte, []int) {
129
-	return fileDescriptorGenerated, []int{14}
133
+	return fileDescriptorGenerated, []int{15}
130 134
 }
131 135
 
132 136
 func (m *DeploymentTriggerPolicy) Reset()      { *m = DeploymentTriggerPolicy{} }
133 137
 func (*DeploymentTriggerPolicy) ProtoMessage() {}
134 138
 func (*DeploymentTriggerPolicy) Descriptor() ([]byte, []int) {
135
-	return fileDescriptorGenerated, []int{15}
139
+	return fileDescriptorGenerated, []int{16}
136 140
 }
137 141
 
138 142
 func (m *ExecNewPodHook) Reset()                    { *m = ExecNewPodHook{} }
139 143
 func (*ExecNewPodHook) ProtoMessage()               {}
140
-func (*ExecNewPodHook) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{16} }
144
+func (*ExecNewPodHook) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{17} }
141 145
 
142 146
 func (m *LifecycleHook) Reset()                    { *m = LifecycleHook{} }
143 147
 func (*LifecycleHook) ProtoMessage()               {}
144
-func (*LifecycleHook) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{17} }
148
+func (*LifecycleHook) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{18} }
145 149
 
146 150
 func (m *RecreateDeploymentStrategyParams) Reset()      { *m = RecreateDeploymentStrategyParams{} }
147 151
 func (*RecreateDeploymentStrategyParams) ProtoMessage() {}
148 152
 func (*RecreateDeploymentStrategyParams) Descriptor() ([]byte, []int) {
149
-	return fileDescriptorGenerated, []int{18}
153
+	return fileDescriptorGenerated, []int{19}
150 154
 }
151 155
 
152 156
 func (m *RollingDeploymentStrategyParams) Reset()      { *m = RollingDeploymentStrategyParams{} }
153 157
 func (*RollingDeploymentStrategyParams) ProtoMessage() {}
154 158
 func (*RollingDeploymentStrategyParams) Descriptor() ([]byte, []int) {
155
-	return fileDescriptorGenerated, []int{19}
159
+	return fileDescriptorGenerated, []int{20}
156 160
 }
157 161
 
158 162
 func (m *TagImageHook) Reset()                    { *m = TagImageHook{} }
159 163
 func (*TagImageHook) ProtoMessage()               {}
160
-func (*TagImageHook) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{20} }
164
+func (*TagImageHook) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{21} }
161 165
 
162 166
 func init() {
163 167
 	proto.RegisterType((*CustomDeploymentStrategyParams)(nil), "github.com.openshift.origin.pkg.deploy.api.v1.CustomDeploymentStrategyParams")
... ...
@@ -172,6 +177,7 @@ func init() {
172 172
 	proto.RegisterType((*DeploymentDetails)(nil), "github.com.openshift.origin.pkg.deploy.api.v1.DeploymentDetails")
173 173
 	proto.RegisterType((*DeploymentLog)(nil), "github.com.openshift.origin.pkg.deploy.api.v1.DeploymentLog")
174 174
 	proto.RegisterType((*DeploymentLogOptions)(nil), "github.com.openshift.origin.pkg.deploy.api.v1.DeploymentLogOptions")
175
+	proto.RegisterType((*DeploymentRequest)(nil), "github.com.openshift.origin.pkg.deploy.api.v1.DeploymentRequest")
175 176
 	proto.RegisterType((*DeploymentStrategy)(nil), "github.com.openshift.origin.pkg.deploy.api.v1.DeploymentStrategy")
176 177
 	proto.RegisterType((*DeploymentTriggerImageChangeParams)(nil), "github.com.openshift.origin.pkg.deploy.api.v1.DeploymentTriggerImageChangeParams")
177 178
 	proto.RegisterType((*DeploymentTriggerPolicies)(nil), "github.com.openshift.origin.pkg.deploy.api.v1.DeploymentTriggerPolicies")
... ...
@@ -749,6 +755,44 @@ func (m *DeploymentLogOptions) MarshalTo(data []byte) (int, error) {
749 749
 	return i, nil
750 750
 }
751 751
 
752
+func (m *DeploymentRequest) Marshal() (data []byte, err error) {
753
+	size := m.Size()
754
+	data = make([]byte, size)
755
+	n, err := m.MarshalTo(data)
756
+	if err != nil {
757
+		return nil, err
758
+	}
759
+	return data[:n], nil
760
+}
761
+
762
+func (m *DeploymentRequest) MarshalTo(data []byte) (int, error) {
763
+	var i int
764
+	_ = i
765
+	var l int
766
+	_ = l
767
+	data[i] = 0xa
768
+	i++
769
+	i = encodeVarintGenerated(data, i, uint64(len(m.Name)))
770
+	i += copy(data[i:], m.Name)
771
+	data[i] = 0x10
772
+	i++
773
+	if m.Latest {
774
+		data[i] = 1
775
+	} else {
776
+		data[i] = 0
777
+	}
778
+	i++
779
+	data[i] = 0x18
780
+	i++
781
+	if m.Force {
782
+		data[i] = 1
783
+	} else {
784
+		data[i] = 0
785
+	}
786
+	i++
787
+	return i, nil
788
+}
789
+
752 790
 func (m *DeploymentStrategy) Marshal() (data []byte, err error) {
753 791
 	size := m.Size()
754 792
 	data = make([]byte, size)
... ...
@@ -1441,6 +1485,16 @@ func (m *DeploymentLogOptions) Size() (n int) {
1441 1441
 	return n
1442 1442
 }
1443 1443
 
1444
+func (m *DeploymentRequest) Size() (n int) {
1445
+	var l int
1446
+	_ = l
1447
+	l = len(m.Name)
1448
+	n += 1 + l + sovGenerated(uint64(l))
1449
+	n += 2
1450
+	n += 2
1451
+	return n
1452
+}
1453
+
1444 1454
 func (m *DeploymentStrategy) Size() (n int) {
1445 1455
 	var l int
1446 1456
 	_ = l
... ...
@@ -1815,6 +1869,18 @@ func (this *DeploymentLogOptions) String() string {
1815 1815
 	}, "")
1816 1816
 	return s
1817 1817
 }
1818
+func (this *DeploymentRequest) String() string {
1819
+	if this == nil {
1820
+		return "nil"
1821
+	}
1822
+	s := strings.Join([]string{`&DeploymentRequest{`,
1823
+		`Name:` + fmt.Sprintf("%v", this.Name) + `,`,
1824
+		`Latest:` + fmt.Sprintf("%v", this.Latest) + `,`,
1825
+		`Force:` + fmt.Sprintf("%v", this.Force) + `,`,
1826
+		`}`,
1827
+	}, "")
1828
+	return s
1829
+}
1818 1830
 func (this *DeploymentStrategy) String() string {
1819 1831
 	if this == nil {
1820 1832
 		return "nil"
... ...
@@ -3913,6 +3979,125 @@ func (m *DeploymentLogOptions) Unmarshal(data []byte) error {
3913 3913
 	}
3914 3914
 	return nil
3915 3915
 }
3916
+func (m *DeploymentRequest) Unmarshal(data []byte) error {
3917
+	l := len(data)
3918
+	iNdEx := 0
3919
+	for iNdEx < l {
3920
+		preIndex := iNdEx
3921
+		var wire uint64
3922
+		for shift := uint(0); ; shift += 7 {
3923
+			if shift >= 64 {
3924
+				return ErrIntOverflowGenerated
3925
+			}
3926
+			if iNdEx >= l {
3927
+				return io.ErrUnexpectedEOF
3928
+			}
3929
+			b := data[iNdEx]
3930
+			iNdEx++
3931
+			wire |= (uint64(b) & 0x7F) << shift
3932
+			if b < 0x80 {
3933
+				break
3934
+			}
3935
+		}
3936
+		fieldNum := int32(wire >> 3)
3937
+		wireType := int(wire & 0x7)
3938
+		if wireType == 4 {
3939
+			return fmt.Errorf("proto: DeploymentRequest: wiretype end group for non-group")
3940
+		}
3941
+		if fieldNum <= 0 {
3942
+			return fmt.Errorf("proto: DeploymentRequest: illegal tag %d (wire type %d)", fieldNum, wire)
3943
+		}
3944
+		switch fieldNum {
3945
+		case 1:
3946
+			if wireType != 2 {
3947
+				return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType)
3948
+			}
3949
+			var stringLen uint64
3950
+			for shift := uint(0); ; shift += 7 {
3951
+				if shift >= 64 {
3952
+					return ErrIntOverflowGenerated
3953
+				}
3954
+				if iNdEx >= l {
3955
+					return io.ErrUnexpectedEOF
3956
+				}
3957
+				b := data[iNdEx]
3958
+				iNdEx++
3959
+				stringLen |= (uint64(b) & 0x7F) << shift
3960
+				if b < 0x80 {
3961
+					break
3962
+				}
3963
+			}
3964
+			intStringLen := int(stringLen)
3965
+			if intStringLen < 0 {
3966
+				return ErrInvalidLengthGenerated
3967
+			}
3968
+			postIndex := iNdEx + intStringLen
3969
+			if postIndex > l {
3970
+				return io.ErrUnexpectedEOF
3971
+			}
3972
+			m.Name = string(data[iNdEx:postIndex])
3973
+			iNdEx = postIndex
3974
+		case 2:
3975
+			if wireType != 0 {
3976
+				return fmt.Errorf("proto: wrong wireType = %d for field Latest", wireType)
3977
+			}
3978
+			var v int
3979
+			for shift := uint(0); ; shift += 7 {
3980
+				if shift >= 64 {
3981
+					return ErrIntOverflowGenerated
3982
+				}
3983
+				if iNdEx >= l {
3984
+					return io.ErrUnexpectedEOF
3985
+				}
3986
+				b := data[iNdEx]
3987
+				iNdEx++
3988
+				v |= (int(b) & 0x7F) << shift
3989
+				if b < 0x80 {
3990
+					break
3991
+				}
3992
+			}
3993
+			m.Latest = bool(v != 0)
3994
+		case 3:
3995
+			if wireType != 0 {
3996
+				return fmt.Errorf("proto: wrong wireType = %d for field Force", wireType)
3997
+			}
3998
+			var v int
3999
+			for shift := uint(0); ; shift += 7 {
4000
+				if shift >= 64 {
4001
+					return ErrIntOverflowGenerated
4002
+				}
4003
+				if iNdEx >= l {
4004
+					return io.ErrUnexpectedEOF
4005
+				}
4006
+				b := data[iNdEx]
4007
+				iNdEx++
4008
+				v |= (int(b) & 0x7F) << shift
4009
+				if b < 0x80 {
4010
+					break
4011
+				}
4012
+			}
4013
+			m.Force = bool(v != 0)
4014
+		default:
4015
+			iNdEx = preIndex
4016
+			skippy, err := skipGenerated(data[iNdEx:])
4017
+			if err != nil {
4018
+				return err
4019
+			}
4020
+			if skippy < 0 {
4021
+				return ErrInvalidLengthGenerated
4022
+			}
4023
+			if (iNdEx + skippy) > l {
4024
+				return io.ErrUnexpectedEOF
4025
+			}
4026
+			iNdEx += skippy
4027
+		}
4028
+	}
4029
+
4030
+	if iNdEx > l {
4031
+		return io.ErrUnexpectedEOF
4032
+	}
4033
+	return nil
4034
+}
3916 4035
 func (m *DeploymentStrategy) Unmarshal(data []byte) error {
3917 4036
 	l := len(data)
3918 4037
 	iNdEx := 0
... ...
@@ -5631,147 +5816,150 @@ var (
5631 5631
 )
5632 5632
 
5633 5633
 var fileDescriptorGenerated = []byte{
5634
-	// 2271 bytes of a gzipped FileDescriptorProto
5635
-	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xb4, 0x59, 0xcd, 0x6f, 0x24, 0x57,
5636
-	0x11, 0x77, 0x7b, 0xc6, 0xf6, 0xcc, 0x1b, 0x7f, 0xbe, 0xfd, 0x9a, 0x4c, 0x90, 0x6d, 0x35, 0x49,
5637
-	0xb4, 0x28, 0xd9, 0x1e, 0xad, 0x01, 0x29, 0xd9, 0xb0, 0x01, 0x8f, 0xe3, 0xcd, 0x3a, 0x8c, 0xd7,
5638
-	0xce, 0xb3, 0x77, 0x03, 0x41, 0x08, 0xb5, 0x67, 0x9e, 0x67, 0x3b, 0x9e, 0xee, 0x37, 0xf4, 0xc7,
5639
-	0xec, 0xce, 0x89, 0x95, 0x38, 0x20, 0x84, 0x90, 0x10, 0x48, 0x08, 0x89, 0x4b, 0x4e, 0x5c, 0x90,
5640
-	0xf8, 0x13, 0x38, 0x70, 0x61, 0x39, 0x11, 0x6e, 0x48, 0xa0, 0x55, 0x58, 0xae, 0xf0, 0x0f, 0x70,
5641
-	0xa2, 0xde, 0x57, 0x7f, 0x4c, 0xcf, 0x38, 0x3b, 0xb6, 0x73, 0x18, 0xc9, 0xfd, 0xaa, 0xea, 0x57,
5642
-	0xf5, 0xaa, 0xea, 0xd5, 0xab, 0x7a, 0x46, 0xb7, 0x3b, 0x4e, 0xf8, 0x30, 0x3a, 0xb2, 0x5a, 0xcc,
5643
-	0xad, 0xb3, 0x1e, 0xf5, 0x82, 0x87, 0xce, 0x71, 0x58, 0x67, 0xbe, 0xd3, 0x71, 0xbc, 0x7a, 0xef,
5644
-	0xa4, 0x53, 0x6f, 0xd3, 0x5e, 0x97, 0x0d, 0xea, 0x76, 0xcf, 0xa9, 0xf7, 0x6f, 0xd6, 0x3b, 0xd4,
5645
-	0xa3, 0xbe, 0x1d, 0xd2, 0xb6, 0xd5, 0xf3, 0x59, 0xc8, 0xf0, 0x8d, 0x44, 0xdc, 0x8a, 0xc5, 0x2d,
5646
-	0x29, 0x6e, 0x81, 0xb8, 0x25, 0xc5, 0x2d, 0x10, 0xb7, 0xfa, 0x37, 0x6b, 0x29, 0xf6, 0x7a, 0x87,
5647
-	0x75, 0x58, 0x5d, 0xa0, 0x1c, 0x45, 0xc7, 0xe2, 0x4b, 0x7c, 0x88, 0xbf, 0x24, 0x7a, 0xed, 0xeb,
5648
-	0x27, 0x6f, 0x06, 0x96, 0xc3, 0xea, 0x27, 0xd1, 0x11, 0xf5, 0x3d, 0x1a, 0xd2, 0x40, 0x98, 0xc4,
5649
-	0x6d, 0x89, 0xbc, 0x3e, 0xf5, 0x03, 0x87, 0x79, 0xb4, 0x3d, 0x6c, 0x54, 0xed, 0x8d, 0xf1, 0x62,
5650
-	0xf9, 0x2d, 0xd4, 0x6e, 0x8f, 0xe5, 0x0e, 0xea, 0xf4, 0x71, 0x08, 0x7b, 0x02, 0x2d, 0x01, 0x48,
5651
-	0x1e, 0xd1, 0xd0, 0xce, 0x8b, 0xdf, 0x18, 0x2d, 0xee, 0x47, 0x5e, 0xe8, 0xb8, 0x34, 0xc7, 0x7e,
5652
-	0x73, 0x34, 0x7b, 0x14, 0x3a, 0xdd, 0xba, 0xe3, 0x85, 0x41, 0xe8, 0x0f, 0x8b, 0x98, 0x7f, 0x31,
5653
-	0xd0, 0xea, 0x56, 0x14, 0x84, 0xcc, 0x7d, 0x57, 0x38, 0xd3, 0xa5, 0x5e, 0x78, 0x10, 0x72, 0x8e,
5654
-	0xce, 0x60, 0xdf, 0xf6, 0x6d, 0x37, 0xc0, 0x5f, 0x46, 0x33, 0x8e, 0x6b, 0x77, 0x68, 0xd5, 0x58,
5655
-	0x37, 0xae, 0x97, 0x1b, 0x0b, 0x4f, 0x9f, 0xad, 0x4d, 0x3d, 0x7f, 0xb6, 0x36, 0xb3, 0xc3, 0x17,
5656
-	0x89, 0xa4, 0xe1, 0xef, 0xa1, 0x0a, 0xf5, 0xfa, 0x8e, 0xcf, 0x3c, 0x8e, 0x50, 0x9d, 0x5e, 0x2f,
5657
-	0x5c, 0xaf, 0x6c, 0xbc, 0x62, 0x49, 0x83, 0xac, 0xc4, 0x20, 0x11, 0x37, 0x19, 0x30, 0x6b, 0xdb,
5658
-	0xeb, 0x3f, 0xb0, 0xfd, 0xc6, 0x25, 0x05, 0x58, 0xd9, 0x4e, 0x00, 0x48, 0x1a, 0x0d, 0xbf, 0x8a,
5659
-	0xe6, 0x20, 0xa8, 0xae, 0xed, 0xb5, 0xab, 0x05, 0x00, 0x2e, 0x37, 0x2a, 0xc0, 0x3e, 0xb7, 0x25,
5660
-	0x97, 0x88, 0xa6, 0x99, 0x7f, 0x35, 0xd0, 0x52, 0xb2, 0x8b, 0x2d, 0x3b, 0x0a, 0x28, 0x7e, 0x0b,
5661
-	0x15, 0xc3, 0x41, 0x4f, 0xdb, 0xfe, 0xaa, 0x52, 0x55, 0x3c, 0x84, 0xb5, 0xff, 0x3d, 0x5b, 0xbb,
5662
-	0x92, 0xb0, 0x1f, 0x42, 0x5a, 0x75, 0xa8, 0xcf, 0x09, 0x44, 0x88, 0xe0, 0x27, 0x06, 0x9a, 0x17,
5663
-	0x9b, 0x53, 0x24, 0xd8, 0x94, 0x01, 0x9b, 0x7a, 0xdf, 0x9a, 0x28, 0x2d, 0xad, 0x21, 0x8b, 0x76,
5664
-	0x52, 0x88, 0x8d, 0x65, 0xb0, 0x65, 0x3e, 0xbd, 0x42, 0x32, 0x1a, 0x4d, 0x0f, 0xbd, 0x7c, 0x8a,
5665
-	0x38, 0xde, 0x43, 0xc5, 0x63, 0x9f, 0xb9, 0x62, 0x73, 0x95, 0x8d, 0x1b, 0xa7, 0x7b, 0x7b, 0xef,
5666
-	0xe8, 0x63, 0xda, 0x0a, 0x09, 0x3d, 0xa6, 0x3e, 0xf5, 0x5a, 0xb4, 0x31, 0xaf, 0x7d, 0x71, 0x07,
5667
-	0x20, 0x88, 0x00, 0x32, 0xff, 0x34, 0x8d, 0x96, 0x53, 0x0a, 0x99, 0x77, 0xec, 0x74, 0xf0, 0x77,
5668
-	0x50, 0xc9, 0x85, 0xec, 0x6c, 0xdb, 0xa1, 0xad, 0x34, 0x5d, 0x7f, 0x11, 0x4d, 0xbb, 0x20, 0xd3,
5669
-	0xc0, 0x4a, 0x09, 0x4a, 0xd6, 0x48, 0x8c, 0x86, 0x29, 0x2a, 0x06, 0x3d, 0xda, 0x52, 0x8e, 0xdd,
5670
-	0x3a, 0xbb, 0x63, 0x85, 0xa1, 0x07, 0x00, 0x95, 0xec, 0x8a, 0x7f, 0x11, 0x01, 0x8f, 0x5d, 0x34,
5671
-	0x1b, 0x84, 0x76, 0x18, 0x05, 0x90, 0x3d, 0x5c, 0xd1, 0xf6, 0x79, 0x15, 0x09, 0xb0, 0xc6, 0xa2,
5672
-	0x52, 0x35, 0x2b, 0xbf, 0x89, 0x52, 0x62, 0xfe, 0xc3, 0x40, 0x97, 0x87, 0x45, 0x9a, 0x4e, 0x10,
5673
-	0xe2, 0xef, 0xe7, 0x1c, 0x59, 0x3f, 0xc5, 0x91, 0xa9, 0x22, 0x64, 0x71, 0x71, 0xe1, 0xcf, 0x65,
5674
-	0xa5, 0xb3, 0xa4, 0x57, 0x52, 0xde, 0x6c, 0xc3, 0x39, 0x0d, 0xa9, 0x1b, 0xa8, 0xc3, 0xf7, 0xcd,
5675
-	0x73, 0xee, 0x32, 0x75, 0xd0, 0x39, 0x2a, 0x91, 0xe0, 0xe6, 0x27, 0x05, 0x54, 0x1d, 0x66, 0x25,
5676
-	0xac, 0xdb, 0x3d, 0xb2, 0x5b, 0x27, 0x78, 0x1d, 0x15, 0x3d, 0xdb, 0xd5, 0xa7, 0x2d, 0x8e, 0xc5,
5677
-	0x3d, 0x58, 0x23, 0x82, 0x82, 0x7f, 0x6f, 0x20, 0x1c, 0xf5, 0xda, 0xbc, 0x02, 0x6d, 0x7a, 0x1e,
5678
-	0x03, 0x8f, 0xf1, 0x02, 0xa8, 0x4c, 0xfe, 0xc1, 0x39, 0x4d, 0xd6, 0x76, 0x58, 0xf7, 0x73, 0x1a,
5679
-	0xb6, 0xbd, 0xd0, 0x1f, 0x34, 0x6a, 0xca, 0x22, 0x9c, 0x67, 0x20, 0x23, 0xcc, 0x82, 0xcc, 0x91,
5680
-	0x09, 0x2a, 0xf3, 0xe6, 0xdb, 0x17, 0x64, 0xde, 0xb8, 0x44, 0xad, 0x6d, 0xa3, 0x6b, 0x63, 0x2c,
5681
-	0xc7, 0xcb, 0xa8, 0x70, 0x42, 0x07, 0xd2, 0xb1, 0x84, 0xff, 0x89, 0x2f, 0xa3, 0x99, 0xbe, 0xdd,
5682
-	0x8d, 0xa8, 0x38, 0x3d, 0x65, 0x22, 0x3f, 0x6e, 0x4d, 0xbf, 0x69, 0x98, 0x7f, 0x2c, 0xa0, 0x2f,
5683
-	0x9d, 0xa6, 0xfb, 0xc2, 0xeb, 0x06, 0x7e, 0x03, 0x95, 0x7c, 0xda, 0x77, 0x78, 0xb6, 0x0a, 0x73,
5684
-	0x0a, 0x49, 0xa2, 0x12, 0xb5, 0x4e, 0x62, 0x0e, 0xbc, 0x89, 0x96, 0x1c, 0xaf, 0xd5, 0x8d, 0xda,
5685
-	0xba, 0x90, 0xc9, 0x83, 0x59, 0x6a, 0x5c, 0x53, 0x42, 0x4b, 0x3b, 0x59, 0x32, 0x19, 0xe6, 0x4f,
5686
-	0x43, 0x50, 0xb7, 0xd7, 0x05, 0x97, 0x55, 0x8b, 0xa3, 0x21, 0x14, 0x99, 0x0c, 0xf3, 0xe3, 0x07,
5687
-	0xe8, 0xaa, 0x5a, 0x22, 0xe0, 0x2b, 0xa7, 0x25, 0xbc, 0xcd, 0x8f, 0x54, 0x75, 0x46, 0x20, 0xad,
5688
-	0x2a, 0xa4, 0xab, 0x3b, 0x23, 0xb9, 0xc8, 0x18, 0xe9, 0x94, 0x69, 0xfa, 0x1e, 0xad, 0xce, 0x8e,
5689
-	0x34, 0x4d, 0x93, 0xc9, 0x30, 0xbf, 0xf9, 0xdb, 0xd9, 0x7c, 0x05, 0x11, 0x81, 0x63, 0xa8, 0x14,
5690
-	0x68, 0x50, 0x19, 0xbc, 0xcd, 0x33, 0xe7, 0xa4, 0xd6, 0x96, 0x84, 0x2a, 0x36, 0x28, 0x56, 0x82,
5691
-	0x7d, 0x54, 0x0a, 0x75, 0x8c, 0x64, 0x95, 0xbe, 0x7b, 0x66, 0x85, 0x2a, 0x78, 0xfb, 0x0c, 0xdc,
5692
-	0xe5, 0xd0, 0xa0, 0x31, 0xcf, 0x75, 0xc6, 0x21, 0x8e, 0xf5, 0xc8, 0x64, 0x12, 0x3e, 0x95, 0x79,
5693
-	0x31, 0x93, 0x4e, 0x26, 0xb9, 0x4e, 0x62, 0x0e, 0xdc, 0x44, 0x97, 0x75, 0x62, 0xdd, 0x85, 0x9a,
5694
-	0xc8, 0xfc, 0x41, 0xd3, 0x71, 0x9d, 0x50, 0xa4, 0xc3, 0x4c, 0xa3, 0x0a, 0x52, 0x97, 0xc9, 0x08,
5695
-	0x3a, 0x19, 0x29, 0xc5, 0x0b, 0x18, 0xa4, 0x7f, 0xa8, 0x52, 0x20, 0x4e, 0xf5, 0x43, 0x58, 0x23,
5696
-	0x82, 0x82, 0x5f, 0x43, 0xb3, 0x3d, 0x7e, 0x11, 0xb7, 0x55, 0x54, 0xe3, 0x5b, 0x60, 0x5f, 0xac,
5697
-	0x12, 0x45, 0xc5, 0x3f, 0x82, 0x50, 0xd1, 0x2e, 0x9c, 0x1c, 0xe6, 0x57, 0xe7, 0x44, 0x75, 0xfb,
5698
-	0xe0, 0x02, 0xee, 0x37, 0xeb, 0x40, 0x61, 0xca, 0x7a, 0x96, 0x84, 0x4e, 0x2d, 0x93, 0x58, 0x29,
5699
-	0xfe, 0x10, 0x42, 0xa7, 0xcf, 0x46, 0xe9, 0x45, 0x0e, 0xfa, 0x3e, 0x6b, 0xeb, 0xc3, 0x21, 0x2b,
5700
-	0x94, 0x88, 0x8f, 0x3e, 0x3f, 0x31, 0x18, 0x4f, 0x70, 0xd7, 0xf1, 0x08, 0xb5, 0xdb, 0x83, 0x03,
5701
-	0xda, 0x62, 0x5e, 0x3b, 0xa8, 0x96, 0x85, 0xb3, 0xe3, 0x04, 0xdf, 0xcd, 0x92, 0xc9, 0x30, 0x7f,
5702
-	0xed, 0x6d, 0xb4, 0x90, 0xd9, 0xc8, 0x44, 0xe5, 0xed, 0x57, 0x45, 0x74, 0x75, 0xf4, 0x95, 0x8c,
5703
-	0x01, 0x97, 0x9b, 0x18, 0x84, 0x0f, 0xe4, 0xd5, 0x29, 0x00, 0x0b, 0x8d, 0x2b, 0xca, 0xb0, 0x85,
5704
-	0x66, 0x9a, 0x48, 0xb2, 0xbc, 0xf8, 0x7d, 0x84, 0xd9, 0x51, 0x40, 0xfd, 0x3e, 0x6d, 0xbf, 0x27,
5705
-	0xbb, 0xe4, 0xa4, 0x9c, 0xc5, 0x17, 0xc7, 0x5e, 0x8e, 0x83, 0x8c, 0x90, 0x9a, 0x30, 0x87, 0xc1,
5706
-	0xa3, 0xea, 0xf2, 0xd1, 0x44, 0x95, 0xbe, 0xb1, 0x47, 0xef, 0x67, 0xc9, 0x64, 0x98, 0x1f, 0xbf,
5707
-	0x87, 0x56, 0xec, 0xbe, 0xed, 0x74, 0xed, 0xa3, 0x2e, 0x8d, 0x41, 0x66, 0x04, 0xc8, 0x4b, 0x0a,
5708
-	0x64, 0x65, 0x73, 0x98, 0x81, 0xe4, 0x65, 0xf0, 0x2e, 0xba, 0x14, 0x79, 0x79, 0xa8, 0x59, 0x01,
5709
-	0xf5, 0xb2, 0x82, 0xba, 0x74, 0x3f, 0xcf, 0x42, 0x46, 0xc9, 0xe1, 0x0e, 0x9a, 0x6b, 0x43, 0x55,
5710
-	0x74, 0xba, 0x01, 0x9c, 0x02, 0x9e, 0x84, 0xdf, 0x3a, 0xf3, 0x29, 0x78, 0x57, 0xe2, 0xc8, 0xe6,
5711
-	0x5f, 0x7d, 0x10, 0x8d, 0x6e, 0xfe, 0xce, 0x40, 0x2b, 0x39, 0x5e, 0xfc, 0x15, 0x34, 0xe7, 0xd2,
5712
-	0x20, 0x48, 0xa6, 0x97, 0x25, 0xb5, 0x83, 0xb9, 0x5d, 0xb9, 0x4c, 0x34, 0x1d, 0x1f, 0xa3, 0xd9,
5713
-	0x16, 0x3f, 0xba, 0xba, 0x19, 0x79, 0xe7, 0x7c, 0x7d, 0x7e, 0x52, 0x18, 0xc4, 0x27, 0xb4, 0x87,
5714
-	0x12, 0xdd, 0x5c, 0x42, 0x0b, 0x09, 0x6b, 0x93, 0x75, 0xcc, 0x9f, 0x17, 0xd3, 0xd5, 0x1e, 0x56,
5715
-	0xf6, 0x7a, 0xb2, 0xfb, 0xa8, 0xa3, 0x32, 0x1c, 0x17, 0xd8, 0x08, 0xa4, 0x95, 0x32, 0x7f, 0x45,
5716
-	0x81, 0x96, 0xb7, 0x34, 0x81, 0x24, 0x3c, 0xbc, 0x36, 0x1d, 0xc3, 0x3d, 0xcf, 0x1e, 0x89, 0xac,
5717
-	0x4d, 0xd5, 0xa6, 0x3b, 0x62, 0x95, 0x28, 0x2a, 0xcf, 0xce, 0x1e, 0x2f, 0x7f, 0x2c, 0xd2, 0x37,
5718
-	0x6f, 0x9c, 0x9d, 0xfb, 0x6a, 0x9d, 0xc4, 0x1c, 0xf8, 0x6b, 0x68, 0x3e, 0x80, 0x1b, 0x8a, 0xea,
5719
-	0xc3, 0x5e, 0x94, 0x17, 0x3c, 0x1f, 0x5d, 0x0e, 0x52, 0xeb, 0x24, 0xc3, 0x05, 0x53, 0x43, 0x59,
5720
-	0x7c, 0x1f, 0xc2, 0xa0, 0x2a, 0x12, 0xb1, 0xb2, 0xf1, 0xfa, 0x0b, 0x76, 0xbb, 0x5c, 0xa4, 0xb1,
5721
-	0xc0, 0x77, 0x79, 0xa0, 0x11, 0x48, 0x02, 0x86, 0x37, 0x10, 0xe2, 0xd3, 0x2f, 0x74, 0xdb, 0x6e,
5722
-	0x2f, 0x50, 0x55, 0x38, 0x9e, 0x33, 0x0e, 0x63, 0x0a, 0x49, 0x71, 0xe1, 0xd7, 0x51, 0x99, 0x27,
5723
-	0x44, 0x13, 0xdc, 0x24, 0x13, 0xb1, 0x20, 0x15, 0x1c, 0xea, 0x45, 0x92, 0xd0, 0xb1, 0x85, 0x50,
5724
-	0x97, 0xdf, 0x06, 0x8d, 0x01, 0x58, 0x28, 0x6a, 0x67, 0xa1, 0xb1, 0xc8, 0xc1, 0x9b, 0xf1, 0x2a,
5725
-	0x49, 0x71, 0x70, 0xb7, 0x7b, 0xec, 0x91, 0x0d, 0x97, 0x4e, 0x39, 0xeb, 0xf6, 0x7b, 0xec, 0x43,
5726
-	0x58, 0x25, 0x8a, 0xca, 0xc7, 0x58, 0xb5, 0xc9, 0x2a, 0x12, 0xa0, 0x22, 0x93, 0x75, 0x35, 0xd2,
5727
-	0x34, 0xf3, 0x6f, 0x73, 0x08, 0xe7, 0xaf, 0x69, 0x7c, 0x2b, 0x33, 0xc9, 0xbe, 0x36, 0x34, 0xc9,
5728
-	0x5e, 0xcd, 0x4b, 0xa4, 0x46, 0xd9, 0x1f, 0xc3, 0x28, 0xdb, 0x12, 0x53, 0xbe, 0x9c, 0xe9, 0xd5,
5729
-	0x5d, 0xbe, 0x3b, 0x61, 0x8a, 0x9f, 0xfe, 0x50, 0x20, 0x53, 0x62, 0x2b, 0xa5, 0x86, 0x64, 0x94,
5730
-	0xe2, 0x9f, 0x19, 0x68, 0xd1, 0xa7, 0x2d, 0x9f, 0x82, 0x90, 0xb2, 0x43, 0x36, 0xd6, 0x7b, 0x13,
5731
-	0xda, 0x41, 0x14, 0xc8, 0x58, 0x4b, 0x30, 0x58, 0xb2, 0x48, 0x32, 0xaa, 0xc8, 0x90, 0x6a, 0xfc,
5732
-	0x13, 0x03, 0x2d, 0xf8, 0x70, 0x1e, 0x1c, 0xaf, 0xa3, 0x8c, 0x29, 0x0a, 0x63, 0xee, 0x4d, 0x6a,
5733
-	0x8c, 0xc4, 0x18, 0x6b, 0xcb, 0x0a, 0xbf, 0x78, 0x48, 0x5a, 0x11, 0xc9, 0xea, 0xc5, 0x2d, 0x54,
5734
-	0xf6, 0x69, 0xc0, 0x22, 0xbf, 0x45, 0x03, 0x75, 0x54, 0x36, 0x4e, 0xbf, 0xaa, 0x89, 0x62, 0x27,
5735
-	0xf4, 0x87, 0x91, 0xe3, 0x53, 0xae, 0x35, 0x48, 0x6a, 0x83, 0xa6, 0x42, 0x52, 0xc7, 0xb8, 0x38,
5736
-	0x42, 0xb3, 0x50, 0x99, 0x69, 0x97, 0x9f, 0x98, 0xc2, 0x19, 0x62, 0x9f, 0xdf, 0x9f, 0xd5, 0x14,
5737
-	0x78, 0xb2, 0x13, 0x89, 0x73, 0x5e, 0x2e, 0x12, 0xa5, 0x0c, 0xff, 0xd4, 0x40, 0x15, 0x3b, 0x35,
5738
-	0xe8, 0xc9, 0x56, 0x88, 0x9c, 0x5f, 0x79, 0x6e, 0xb6, 0x8b, 0x9f, 0x91, 0xd2, 0x43, 0x5d, 0x5a,
5739
-	0x77, 0xed, 0x2d, 0x54, 0x49, 0x99, 0x3c, 0x49, 0xcf, 0x51, 0x7b, 0x07, 0x2d, 0x9f, 0x6b, 0x24,
5740
-	0xfb, 0xc3, 0x34, 0x32, 0x73, 0x9d, 0xb0, 0x78, 0xcb, 0xd9, 0x7a, 0x68, 0x7b, 0x1d, 0x9d, 0x93,
5741
-	0x50, 0xf1, 0xed, 0x08, 0x0e, 0x0c, 0xa8, 0x69, 0x09, 0xe0, 0x52, 0x12, 0xd5, 0x4d, 0x4d, 0x20,
5742
-	0x09, 0x0f, 0x14, 0x85, 0xc5, 0xb8, 0xfc, 0xf3, 0x29, 0x5b, 0x5e, 0x5e, 0x65, 0x79, 0x00, 0xb6,
5743
-	0x32, 0x14, 0x32, 0xc4, 0x19, 0x4f, 0x81, 0x85, 0x8b, 0x9a, 0x02, 0xa1, 0x81, 0xea, 0xda, 0x81,
5744
-	0xde, 0x1d, 0x6d, 0x8b, 0xfd, 0x89, 0x53, 0x55, 0x4e, 0x1a, 0xa8, 0x66, 0x8e, 0x83, 0x8c, 0x90,
5745
-	0x32, 0x7f, 0x69, 0xa0, 0x97, 0xc6, 0x8e, 0x0e, 0xf8, 0x44, 0x3f, 0x75, 0x18, 0x22, 0x9d, 0xee,
5746
-	0x5c, 0xc8, 0x4c, 0x32, 0x18, 0xfd, 0xe2, 0x71, 0xab, 0xf4, 0x9b, 0x4f, 0xd6, 0xa6, 0x9e, 0xfc,
5747
-	0x73, 0x7d, 0xca, 0xfc, 0xaf, 0x81, 0xae, 0x8d, 0x91, 0x3d, 0xcf, 0x43, 0xe3, 0xaf, 0xa1, 0x75,
5748
-	0x71, 0x86, 0x73, 0x41, 0x95, 0xe8, 0x0f, 0xce, 0xbb, 0xb5, 0x5c, 0x92, 0x35, 0xae, 0xf0, 0x5e,
5749
-	0x30, 0xb7, 0x4c, 0xf2, 0x26, 0x98, 0x9f, 0x41, 0xc1, 0xde, 0x7e, 0x4c, 0x5b, 0xf7, 0xe8, 0x23,
5750
-	0x18, 0x0e, 0xee, 0x32, 0x76, 0x92, 0x7e, 0x8a, 0x35, 0xc6, 0x3f, 0xc5, 0xe2, 0x2d, 0x54, 0xa0,
5751
-	0x5e, 0x7f, 0xa2, 0x67, 0xe0, 0x8a, 0x72, 0x59, 0x01, 0xbe, 0x09, 0x97, 0xe6, 0xdd, 0x7c, 0x26,
5752
-	0x65, 0x45, 0xa6, 0x96, 0x93, 0x6e, 0x3e, 0x93, 0xdf, 0x24, 0xcb, 0x2b, 0x2e, 0x5b, 0xd6, 0x8d,
5753
-	0xf8, 0x91, 0x28, 0x26, 0x86, 0x3e, 0x90, 0x4b, 0x44, 0xd3, 0xcc, 0x3f, 0x4f, 0xa3, 0x85, 0xa6,
5754
-	0x73, 0x4c, 0x5b, 0x83, 0x56, 0x97, 0x8a, 0x1d, 0x7e, 0x17, 0x2d, 0x1c, 0x43, 0x2b, 0x10, 0xf9,
5755
-	0x54, 0x46, 0x56, 0x45, 0xf4, 0xab, 0x5a, 0xeb, 0x9d, 0x34, 0x11, 0x42, 0x5b, 0xcb, 0x88, 0x67,
5756
-	0xa8, 0x24, 0x8b, 0x84, 0x5d, 0x84, 0x68, 0xec, 0x4e, 0x15, 0xe0, 0xdb, 0x13, 0x06, 0x38, 0x1b,
5757
-	0x0f, 0xd9, 0x97, 0x24, 0x6b, 0x24, 0xa5, 0x00, 0x77, 0x79, 0xd3, 0xd3, 0x11, 0x91, 0x0e, 0xc4,
5758
-	0xc3, 0x79, 0x65, 0xe3, 0xed, 0x09, 0xb5, 0x1d, 0x2a, 0x79, 0xa1, 0x2b, 0x2e, 0x45, 0x7a, 0x55,
5759
-	0x74, 0x4d, 0xea, 0x4f, 0xf3, 0x3f, 0xd3, 0x68, 0xfd, 0xf3, 0x2e, 0x66, 0x5e, 0xaf, 0x78, 0x57,
5760
-	0xc6, 0xa2, 0x50, 0x77, 0x93, 0x72, 0x42, 0x13, 0xf5, 0xea, 0x30, 0x43, 0x21, 0x43, 0x9c, 0x30,
5761
-	0xd0, 0x16, 0xa0, 0x27, 0x55, 0x6e, 0xfb, 0xc6, 0x84, 0x1b, 0xc9, 0x04, 0xa9, 0x31, 0xc7, 0xf3,
5762
-	0x0c, 0x5a, 0x5d, 0xc2, 0x11, 0x39, 0xb0, 0xeb, 0xb4, 0x55, 0x1d, 0xbc, 0x00, 0xe0, 0x5d, 0xa7,
5763
-	0x4d, 0x38, 0x22, 0xfe, 0x08, 0x15, 0x7b, 0x2c, 0x08, 0x55, 0x63, 0x71, 0x3e, 0xe4, 0x12, 0xaf,
5764
-	0x26, 0xfb, 0x8c, 0xbf, 0x43, 0x70, 0x4c, 0xf3, 0x79, 0x11, 0xad, 0x7d, 0x4e, 0xeb, 0x81, 0x77,
5765
-	0x60, 0x96, 0x13, 0x73, 0xe2, 0x3e, 0xf5, 0x1d, 0xd6, 0xce, 0xba, 0xfc, 0x9a, 0x98, 0xe3, 0xf2,
5766
-	0x64, 0x32, 0x4a, 0x06, 0xdf, 0xe6, 0xaf, 0x5a, 0x21, 0x8c, 0xb9, 0x76, 0x57, 0xc3, 0xc8, 0xc9,
5767
-	0xf8, 0x92, 0x7c, 0xd1, 0xca, 0x90, 0xc8, 0x30, 0xef, 0x88, 0xb8, 0x17, 0x5e, 0x38, 0xee, 0x1f,
5768
-	0xa3, 0x45, 0xd7, 0x7e, 0x9c, 0x9a, 0x38, 0x95, 0x3f, 0xad, 0x31, 0x65, 0x85, 0xff, 0xbb, 0xcb,
5769
-	0x92, 0xff, 0xee, 0xb2, 0xc0, 0xb0, 0x3d, 0x1f, 0xbc, 0x02, 0x5e, 0x92, 0xba, 0x76, 0x33, 0x48,
5770
-	0x64, 0x08, 0x59, 0xfc, 0xaf, 0xc3, 0x7e, 0x7c, 0x10, 0xf9, 0x1d, 0x3d, 0xb4, 0x4c, 0xaa, 0x45,
5771
-	0xbc, 0x9a, 0xec, 0x2a, 0x0c, 0x12, 0xa3, 0xe9, 0xec, 0x9d, 0xbb, 0xf0, 0xec, 0xd5, 0x49, 0x56,
5772
-	0xfa, 0x02, 0x92, 0x0c, 0x6e, 0xa6, 0xf9, 0x74, 0x09, 0xc8, 0x97, 0x64, 0x63, 0x82, 0x92, 0xbc,
5773
-	0x83, 0xa6, 0x43, 0xa6, 0xce, 0xef, 0x84, 0xed, 0x06, 0x52, 0x0a, 0xa6, 0x0f, 0x19, 0x01, 0x90,
5774
-	0xc6, 0x2b, 0x4f, 0xff, 0xb5, 0x3a, 0xf5, 0x29, 0xfc, 0xfe, 0x0e, 0xbf, 0x27, 0xcf, 0x57, 0x8d,
5775
-	0xa7, 0xf0, 0xfb, 0x14, 0x7e, 0x9f, 0xc1, 0xef, 0x17, 0xff, 0x5e, 0x9d, 0xfa, 0x68, 0xba, 0x7f,
5776
-	0xf3, 0xff, 0x01, 0x00, 0x00, 0xff, 0xff, 0x87, 0xa8, 0x15, 0xd6, 0x80, 0x1e, 0x00, 0x00,
5634
+	// 2309 bytes of a gzipped FileDescriptorProto
5635
+	0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xb4, 0x59, 0xcd, 0x6f, 0x63, 0x49,
5636
+	0x11, 0x8f, 0x63, 0x27, 0xb1, 0xdb, 0xf9, 0xec, 0xf9, 0xf2, 0x7a, 0x51, 0x12, 0x3d, 0x76, 0x57,
5637
+	0x83, 0x76, 0xe7, 0x59, 0x13, 0x40, 0xda, 0x9d, 0x65, 0x16, 0xe2, 0x6c, 0xb2, 0x93, 0xc5, 0x99,
5638
+	0x64, 0x3b, 0x99, 0x59, 0x58, 0x84, 0xd0, 0x8b, 0xdd, 0xf6, 0xbc, 0x8d, 0xdf, 0x6b, 0xf3, 0x3e,
5639
+	0x3c, 0xe3, 0x13, 0x23, 0x38, 0x20, 0x84, 0x90, 0x10, 0x48, 0x08, 0x89, 0xcb, 0x9e, 0xb8, 0x20,
5640
+	0xf1, 0x27, 0x70, 0xe0, 0xc2, 0x70, 0x62, 0xb9, 0x21, 0x81, 0x46, 0xc3, 0x70, 0x85, 0x7f, 0x80,
5641
+	0x13, 0xd5, 0x5f, 0xef, 0xc3, 0xcf, 0xce, 0x8c, 0x93, 0x70, 0xb0, 0x94, 0xd7, 0x55, 0xf5, 0xab,
5642
+	0xea, 0xaa, 0xea, 0xea, 0xaa, 0x0e, 0xba, 0xdd, 0xb1, 0x83, 0x07, 0xe1, 0xb1, 0xd9, 0x64, 0x4e,
5643
+	0x8d, 0xf5, 0xa8, 0xeb, 0x3f, 0xb0, 0xdb, 0x41, 0x8d, 0x79, 0x76, 0xc7, 0x76, 0x6b, 0xbd, 0x93,
5644
+	0x4e, 0xad, 0x45, 0x7b, 0x5d, 0x36, 0xa8, 0x59, 0x3d, 0xbb, 0xd6, 0xbf, 0x59, 0xeb, 0x50, 0x97,
5645
+	0x7a, 0x56, 0x40, 0x5b, 0x66, 0xcf, 0x63, 0x01, 0xc3, 0x37, 0x62, 0x71, 0x33, 0x12, 0x37, 0xa5,
5646
+	0xb8, 0x09, 0xe2, 0xa6, 0x14, 0x37, 0x41, 0xdc, 0xec, 0xdf, 0xac, 0x26, 0xd8, 0x6b, 0x1d, 0xd6,
5647
+	0x61, 0x35, 0x81, 0x72, 0x1c, 0xb6, 0xc5, 0x97, 0xf8, 0x10, 0x7f, 0x49, 0xf4, 0xea, 0x57, 0x4f,
5648
+	0xde, 0xf6, 0x4d, 0x9b, 0xd5, 0x4e, 0xc2, 0x63, 0xea, 0xb9, 0x34, 0xa0, 0xbe, 0x30, 0x89, 0xdb,
5649
+	0x12, 0xba, 0x7d, 0xea, 0xf9, 0x36, 0x73, 0x69, 0x6b, 0xd8, 0xa8, 0xea, 0x5b, 0xe3, 0xc5, 0xb2,
5650
+	0x5b, 0xa8, 0xde, 0x1e, 0xcb, 0xed, 0xd7, 0xe8, 0xa3, 0x00, 0xf6, 0x04, 0x5a, 0x7c, 0x90, 0x3c,
5651
+	0xa6, 0x81, 0x95, 0x15, 0xbf, 0x31, 0x5a, 0xdc, 0x0b, 0xdd, 0xc0, 0x76, 0x68, 0x86, 0xfd, 0xe6,
5652
+	0x68, 0xf6, 0x30, 0xb0, 0xbb, 0x35, 0xdb, 0x0d, 0xfc, 0xc0, 0x1b, 0x16, 0x31, 0xfe, 0x9c, 0x43,
5653
+	0xab, 0x5b, 0xa1, 0x1f, 0x30, 0xe7, 0x7d, 0xe1, 0x4c, 0x87, 0xba, 0xc1, 0x61, 0xc0, 0x39, 0x3a,
5654
+	0x83, 0x03, 0xcb, 0xb3, 0x1c, 0x1f, 0x7f, 0x11, 0xcd, 0xd8, 0x8e, 0xd5, 0xa1, 0x95, 0xdc, 0x7a,
5655
+	0xee, 0x7a, 0xa9, 0xbe, 0xf0, 0xe4, 0xe9, 0xda, 0xd4, 0xf3, 0xa7, 0x6b, 0x33, 0xbb, 0x7c, 0x91,
5656
+	0x48, 0x1a, 0xfe, 0x0e, 0x2a, 0x53, 0xb7, 0x6f, 0x7b, 0xcc, 0xe5, 0x08, 0x95, 0xe9, 0xf5, 0xfc,
5657
+	0xf5, 0xf2, 0xc6, 0x6b, 0xa6, 0x34, 0xc8, 0x8c, 0x0d, 0x12, 0x71, 0x93, 0x01, 0x33, 0xb7, 0xdd,
5658
+	0xfe, 0x7d, 0xcb, 0xab, 0x5f, 0x52, 0x80, 0xe5, 0xed, 0x18, 0x80, 0x24, 0xd1, 0xf0, 0xeb, 0x68,
5659
+	0x0e, 0x82, 0xea, 0x58, 0x6e, 0xab, 0x92, 0x07, 0xe0, 0x52, 0xbd, 0x0c, 0xec, 0x73, 0x5b, 0x72,
5660
+	0x89, 0x68, 0x9a, 0xf1, 0x97, 0x1c, 0x5a, 0x8a, 0x77, 0xb1, 0x65, 0x85, 0x3e, 0xc5, 0xef, 0xa0,
5661
+	0x42, 0x30, 0xe8, 0x69, 0xdb, 0x5f, 0x57, 0xaa, 0x0a, 0x47, 0xb0, 0xf6, 0xdf, 0xa7, 0x6b, 0x57,
5662
+	0x62, 0xf6, 0x23, 0x48, 0xab, 0x0e, 0xf5, 0x38, 0x81, 0x08, 0x11, 0xfc, 0x38, 0x87, 0xe6, 0xc5,
5663
+	0xe6, 0x14, 0x09, 0x36, 0x95, 0x83, 0x4d, 0x7d, 0x68, 0x4e, 0x94, 0x96, 0xe6, 0x90, 0x45, 0xbb,
5664
+	0x09, 0xc4, 0xfa, 0x32, 0xd8, 0x32, 0x9f, 0x5c, 0x21, 0x29, 0x8d, 0x86, 0x8b, 0x5e, 0x3d, 0x45,
5665
+	0x1c, 0xef, 0xa3, 0x42, 0xdb, 0x63, 0x8e, 0xd8, 0x5c, 0x79, 0xe3, 0xc6, 0xe9, 0xde, 0xde, 0x3f,
5666
+	0xfe, 0x94, 0x36, 0x03, 0x42, 0xdb, 0xd4, 0xa3, 0x6e, 0x93, 0xd6, 0xe7, 0xb5, 0x2f, 0x76, 0x00,
5667
+	0x82, 0x08, 0x20, 0xe3, 0x8f, 0xd3, 0x68, 0x39, 0xa1, 0x90, 0xb9, 0x6d, 0xbb, 0x83, 0xbf, 0x85,
5668
+	0x8a, 0x0e, 0x64, 0x67, 0xcb, 0x0a, 0x2c, 0xa5, 0xe9, 0xfa, 0xcb, 0x68, 0xda, 0x03, 0x99, 0x3a,
5669
+	0x56, 0x4a, 0x50, 0xbc, 0x46, 0x22, 0x34, 0x4c, 0x51, 0xc1, 0xef, 0xd1, 0xa6, 0x72, 0xec, 0xd6,
5670
+	0xd9, 0x1d, 0x2b, 0x0c, 0x3d, 0x04, 0xa8, 0x78, 0x57, 0xfc, 0x8b, 0x08, 0x78, 0xec, 0xa0, 0x59,
5671
+	0x3f, 0xb0, 0x82, 0xd0, 0x87, 0xec, 0xe1, 0x8a, 0xb6, 0xcf, 0xab, 0x48, 0x80, 0xd5, 0x17, 0x95,
5672
+	0xaa, 0x59, 0xf9, 0x4d, 0x94, 0x12, 0xe3, 0xef, 0x39, 0x74, 0x79, 0x58, 0xa4, 0x61, 0xfb, 0x01,
5673
+	0xfe, 0x6e, 0xc6, 0x91, 0xb5, 0x53, 0x1c, 0x99, 0x28, 0x42, 0x26, 0x17, 0x17, 0xfe, 0x5c, 0x56,
5674
+	0x3a, 0x8b, 0x7a, 0x25, 0xe1, 0xcd, 0x16, 0x9c, 0xd3, 0x80, 0x3a, 0xbe, 0x3a, 0x7c, 0x5f, 0x3f,
5675
+	0xe7, 0x2e, 0x13, 0x07, 0x9d, 0xa3, 0x12, 0x09, 0x6e, 0x7c, 0x96, 0x47, 0x95, 0x61, 0x56, 0xc2,
5676
+	0xba, 0xdd, 0x63, 0xab, 0x79, 0x82, 0xd7, 0x51, 0xc1, 0xb5, 0x1c, 0x7d, 0xda, 0xa2, 0x58, 0xdc,
5677
+	0x85, 0x35, 0x22, 0x28, 0xf8, 0x77, 0x39, 0x84, 0xc3, 0x5e, 0x8b, 0x57, 0xa0, 0x4d, 0xd7, 0x65,
5678
+	0xe0, 0x31, 0x5e, 0x00, 0x95, 0xc9, 0xdf, 0x3b, 0xa7, 0xc9, 0xda, 0x0e, 0xf3, 0x5e, 0x46, 0xc3,
5679
+	0xb6, 0x1b, 0x78, 0x83, 0x7a, 0x55, 0x59, 0x84, 0xb3, 0x0c, 0x64, 0x84, 0x59, 0x90, 0x39, 0x32,
5680
+	0x41, 0x65, 0xde, 0x7c, 0xf3, 0x82, 0xcc, 0x1b, 0x97, 0xa8, 0xd5, 0x6d, 0x74, 0x6d, 0x8c, 0xe5,
5681
+	0x78, 0x19, 0xe5, 0x4f, 0xe8, 0x40, 0x3a, 0x96, 0xf0, 0x3f, 0xf1, 0x65, 0x34, 0xd3, 0xb7, 0xba,
5682
+	0x21, 0x15, 0xa7, 0xa7, 0x44, 0xe4, 0xc7, 0xad, 0xe9, 0xb7, 0x73, 0xc6, 0x1f, 0xf2, 0xe8, 0x0b,
5683
+	0xa7, 0xe9, 0xbe, 0xf0, 0xba, 0x81, 0xdf, 0x42, 0x45, 0x8f, 0xf6, 0x6d, 0x9e, 0xad, 0xc2, 0x9c,
5684
+	0x7c, 0x9c, 0xa8, 0x44, 0xad, 0x93, 0x88, 0x03, 0x6f, 0xa2, 0x25, 0xdb, 0x6d, 0x76, 0xc3, 0x96,
5685
+	0x2e, 0x64, 0xf2, 0x60, 0x16, 0xeb, 0xd7, 0x94, 0xd0, 0xd2, 0x6e, 0x9a, 0x4c, 0x86, 0xf9, 0x93,
5686
+	0x10, 0xd4, 0xe9, 0x75, 0xc1, 0x65, 0x95, 0xc2, 0x68, 0x08, 0x45, 0x26, 0xc3, 0xfc, 0xf8, 0x3e,
5687
+	0xba, 0xaa, 0x96, 0x08, 0xf8, 0xca, 0x6e, 0x0a, 0x6f, 0xf3, 0x23, 0x55, 0x99, 0x11, 0x48, 0xab,
5688
+	0x0a, 0xe9, 0xea, 0xee, 0x48, 0x2e, 0x32, 0x46, 0x3a, 0x61, 0x9a, 0xbe, 0x47, 0x2b, 0xb3, 0x23,
5689
+	0x4d, 0xd3, 0x64, 0x32, 0xcc, 0x6f, 0xfc, 0x66, 0x36, 0x5b, 0x41, 0x44, 0xe0, 0x18, 0x2a, 0xfa,
5690
+	0x1a, 0x54, 0x06, 0x6f, 0xf3, 0xcc, 0x39, 0xa9, 0xb5, 0xc5, 0xa1, 0x8a, 0x0c, 0x8a, 0x94, 0x60,
5691
+	0x0f, 0x15, 0x03, 0x1d, 0x23, 0x59, 0xa5, 0xef, 0x9c, 0x59, 0xa1, 0x0a, 0xde, 0x01, 0x03, 0x77,
5692
+	0xd9, 0xd4, 0xaf, 0xcf, 0x73, 0x9d, 0x51, 0x88, 0x23, 0x3d, 0x32, 0x99, 0x84, 0x4f, 0x65, 0x5e,
5693
+	0xcc, 0x24, 0x93, 0x49, 0xae, 0x93, 0x88, 0x03, 0x37, 0xd0, 0x65, 0x9d, 0x58, 0x77, 0xa0, 0x26,
5694
+	0x32, 0x6f, 0xd0, 0xb0, 0x1d, 0x3b, 0x10, 0xe9, 0x30, 0x53, 0xaf, 0x80, 0xd4, 0x65, 0x32, 0x82,
5695
+	0x4e, 0x46, 0x4a, 0xf1, 0x02, 0x06, 0xe9, 0x1f, 0xa8, 0x14, 0x88, 0x52, 0xfd, 0x08, 0xd6, 0x88,
5696
+	0xa0, 0xe0, 0x37, 0xd0, 0x6c, 0x8f, 0x5f, 0xc4, 0x2d, 0x15, 0xd5, 0xe8, 0x16, 0x38, 0x10, 0xab,
5697
+	0x44, 0x51, 0xf1, 0x0f, 0x20, 0x54, 0xb4, 0x0b, 0x27, 0x87, 0x79, 0x95, 0x39, 0x51, 0xdd, 0x3e,
5698
+	0xba, 0x80, 0xfb, 0xcd, 0x3c, 0x54, 0x98, 0xb2, 0x9e, 0xc5, 0xa1, 0x53, 0xcb, 0x24, 0x52, 0x8a,
5699
+	0x3f, 0x86, 0xd0, 0xe9, 0xb3, 0x51, 0x7c, 0x99, 0x83, 0x7e, 0xc0, 0x5a, 0xfa, 0x70, 0xc8, 0x0a,
5700
+	0x25, 0xe2, 0xa3, 0xcf, 0x4f, 0x04, 0xc6, 0x13, 0xdc, 0xb1, 0x5d, 0x42, 0xad, 0xd6, 0xe0, 0x90,
5701
+	0x36, 0x99, 0xdb, 0xf2, 0x2b, 0x25, 0xe1, 0xec, 0x28, 0xc1, 0xf7, 0xd2, 0x64, 0x32, 0xcc, 0x5f,
5702
+	0x7d, 0x17, 0x2d, 0xa4, 0x36, 0x32, 0x51, 0x79, 0xfb, 0x65, 0x01, 0x5d, 0x1d, 0x7d, 0x25, 0x63,
5703
+	0xc0, 0xe5, 0x26, 0xfa, 0xc1, 0x7d, 0x79, 0x75, 0x0a, 0xc0, 0x7c, 0xfd, 0x8a, 0x32, 0x6c, 0xa1,
5704
+	0x91, 0x24, 0x92, 0x34, 0x2f, 0xfe, 0x10, 0x61, 0x76, 0xec, 0x53, 0xaf, 0x4f, 0x5b, 0x1f, 0xc8,
5705
+	0x2e, 0x39, 0x2e, 0x67, 0xd1, 0xc5, 0xb1, 0x9f, 0xe1, 0x20, 0x23, 0xa4, 0x26, 0xcc, 0x61, 0xf0,
5706
+	0xa8, 0xba, 0x7c, 0x34, 0x51, 0xa5, 0x6f, 0xe4, 0xd1, 0x7b, 0x69, 0x32, 0x19, 0xe6, 0xc7, 0x1f,
5707
+	0xa0, 0x15, 0xab, 0x6f, 0xd9, 0x5d, 0xeb, 0xb8, 0x4b, 0x23, 0x90, 0x19, 0x01, 0xf2, 0x8a, 0x02,
5708
+	0x59, 0xd9, 0x1c, 0x66, 0x20, 0x59, 0x19, 0xbc, 0x87, 0x2e, 0x85, 0x6e, 0x16, 0x6a, 0x56, 0x40,
5709
+	0xbd, 0xaa, 0xa0, 0x2e, 0xdd, 0xcb, 0xb2, 0x90, 0x51, 0x72, 0xb8, 0x83, 0xe6, 0x5a, 0x50, 0x15,
5710
+	0xed, 0xae, 0x0f, 0xa7, 0x80, 0x27, 0xe1, 0x37, 0xce, 0x7c, 0x0a, 0xde, 0x97, 0x38, 0xb2, 0xf9,
5711
+	0x57, 0x1f, 0x44, 0xa3, 0x1b, 0xbf, 0xcd, 0xa1, 0x95, 0x0c, 0x2f, 0xfe, 0x12, 0x9a, 0x73, 0xa8,
5712
+	0xef, 0xc7, 0xd3, 0xcb, 0x92, 0xda, 0xc1, 0xdc, 0x9e, 0x5c, 0x26, 0x9a, 0x8e, 0xdb, 0x68, 0xb6,
5713
+	0xc9, 0x8f, 0xae, 0x6e, 0x46, 0xde, 0x3b, 0x5f, 0x9f, 0x1f, 0x17, 0x06, 0xf1, 0x09, 0xed, 0xa1,
5714
+	0x44, 0x37, 0x96, 0xd0, 0x42, 0xcc, 0xda, 0x60, 0x1d, 0xe3, 0x67, 0x85, 0x64, 0xb5, 0x87, 0x95,
5715
+	0xfd, 0x9e, 0xec, 0x3e, 0x6a, 0xa8, 0x04, 0xc7, 0x05, 0x36, 0x02, 0x69, 0xa5, 0xcc, 0x5f, 0x51,
5716
+	0xa0, 0xa5, 0x2d, 0x4d, 0x20, 0x31, 0x0f, 0xaf, 0x4d, 0x6d, 0xb8, 0xe7, 0xd9, 0x43, 0x91, 0xb5,
5717
+	0x89, 0xda, 0xb4, 0x23, 0x56, 0x89, 0xa2, 0xf2, 0xec, 0xec, 0xf1, 0xf2, 0xc7, 0x42, 0x7d, 0xf3,
5718
+	0x46, 0xd9, 0x79, 0xa0, 0xd6, 0x49, 0xc4, 0x81, 0xbf, 0x82, 0xe6, 0x7d, 0xb8, 0xa1, 0xa8, 0x3e,
5719
+	0xec, 0x05, 0x79, 0xc1, 0xf3, 0xd1, 0xe5, 0x30, 0xb1, 0x4e, 0x52, 0x5c, 0x30, 0x35, 0x94, 0xc4,
5720
+	0xf7, 0x11, 0x0c, 0xaa, 0x22, 0x11, 0xcb, 0x1b, 0x6f, 0xbe, 0x64, 0xb7, 0xcb, 0x45, 0xea, 0x0b,
5721
+	0x7c, 0x97, 0x87, 0x1a, 0x81, 0xc4, 0x60, 0x78, 0x03, 0x21, 0x3e, 0xfd, 0x42, 0xb7, 0xed, 0xf4,
5722
+	0x7c, 0x55, 0x85, 0xa3, 0x39, 0xe3, 0x28, 0xa2, 0x90, 0x04, 0x17, 0x7e, 0x13, 0x95, 0x78, 0x42,
5723
+	0x34, 0xc0, 0x4d, 0x32, 0x11, 0xf3, 0x52, 0xc1, 0x91, 0x5e, 0x24, 0x31, 0x1d, 0x9b, 0x08, 0x75,
5724
+	0xf9, 0x6d, 0x50, 0x1f, 0x80, 0x85, 0xa2, 0x76, 0xe6, 0xeb, 0x8b, 0x1c, 0xbc, 0x11, 0xad, 0x92,
5725
+	0x04, 0x07, 0x77, 0xbb, 0xcb, 0x1e, 0x5a, 0x70, 0xe9, 0x94, 0xd2, 0x6e, 0xbf, 0xcb, 0x3e, 0x86,
5726
+	0x55, 0xa2, 0xa8, 0x7c, 0x8c, 0x55, 0x9b, 0xac, 0x20, 0x01, 0x2a, 0x32, 0x59, 0x57, 0x23, 0x4d,
5727
+	0x33, 0x7e, 0x98, 0xca, 0x64, 0x42, 0xbf, 0x1f, 0xf2, 0x7b, 0xe7, 0xc5, 0xad, 0x35, 0x98, 0x21,
5728
+	0x0b, 0xda, 0x70, 0xf4, 0x65, 0xd5, 0x23, 0x8a, 0xca, 0xe7, 0xf9, 0x36, 0xf3, 0x9a, 0x54, 0x85,
5729
+	0x3e, 0x6a, 0xf3, 0x77, 0xf8, 0x22, 0x91, 0x34, 0xe3, 0xaf, 0x73, 0x08, 0x67, 0x7b, 0x05, 0x7c,
5730
+	0x2b, 0x35, 0x4e, 0xbf, 0x31, 0x34, 0x4e, 0x5f, 0xcd, 0x4a, 0x24, 0xe6, 0xe9, 0x1f, 0xc1, 0x3c,
5731
+	0xdd, 0x14, 0x4f, 0x0d, 0xf2, 0x61, 0x41, 0x35, 0x14, 0x7b, 0x13, 0x9e, 0xb3, 0xd3, 0x5f, 0x2b,
5732
+	0x64, 0x5e, 0x6e, 0x25, 0xd4, 0x90, 0x94, 0x52, 0xfc, 0xd3, 0x1c, 0x5a, 0xf4, 0x68, 0xd3, 0xa3,
5733
+	0x20, 0xa4, 0xec, 0x90, 0xdd, 0xfd, 0xfe, 0x84, 0x76, 0x10, 0x05, 0x32, 0xd6, 0x12, 0x0c, 0x96,
5734
+	0x2c, 0x92, 0x94, 0x2a, 0x32, 0xa4, 0x1a, 0xff, 0x38, 0x87, 0x16, 0x3c, 0x38, 0x94, 0xb6, 0xdb,
5735
+	0x51, 0xc6, 0x14, 0x84, 0x31, 0x77, 0x27, 0x35, 0x46, 0x62, 0x8c, 0xb5, 0x65, 0x85, 0xdf, 0x7e,
5736
+	0x24, 0xa9, 0x88, 0xa4, 0xf5, 0xe2, 0x26, 0x2a, 0x79, 0xd4, 0x67, 0x21, 0x04, 0xdf, 0x57, 0xe7,
5737
+	0x75, 0xe3, 0xf4, 0x7e, 0x81, 0x28, 0x76, 0x9e, 0xa1, 0xb6, 0x47, 0xb9, 0x56, 0x3f, 0x2e, 0x50,
5738
+	0x9a, 0x0a, 0x27, 0x2b, 0xc2, 0xc5, 0x21, 0x4f, 0xd1, 0x63, 0xda, 0xe5, 0xc7, 0x36, 0x7f, 0x86,
5739
+	0xd8, 0x67, 0xf7, 0x67, 0x36, 0x04, 0x9e, 0x6c, 0x87, 0x12, 0x19, 0xcf, 0x17, 0x89, 0x52, 0x86,
5740
+	0x7f, 0x92, 0x43, 0x65, 0x2b, 0x31, 0x6d, 0xca, 0x7e, 0x8c, 0x9c, 0x5f, 0x79, 0x66, 0xc0, 0x8c,
5741
+	0xde, 0xb2, 0x92, 0x93, 0x65, 0x52, 0x77, 0xf5, 0x1d, 0x54, 0x4e, 0x98, 0x3c, 0x49, 0xe3, 0x53,
5742
+	0x7d, 0x0f, 0x2d, 0x9f, 0x6b, 0x2e, 0xfc, 0xfd, 0x34, 0x32, 0x32, 0xed, 0xb8, 0x78, 0x50, 0xda,
5743
+	0x7a, 0x60, 0xb9, 0x1d, 0x9d, 0x93, 0x70, 0xed, 0x58, 0x21, 0x1c, 0x18, 0x50, 0xd3, 0x14, 0xc0,
5744
+	0xc5, 0x38, 0xaa, 0x9b, 0x9a, 0x40, 0x62, 0x1e, 0x28, 0x0a, 0x8b, 0xd1, 0x1d, 0xc4, 0xeb, 0x91,
5745
+	0xbc, 0x41, 0x4b, 0xf2, 0x00, 0x6c, 0xa5, 0x28, 0x64, 0x88, 0x33, 0x1a, 0x45, 0xf3, 0x17, 0x35,
5746
+	0x8a, 0x42, 0x17, 0xd7, 0xb5, 0x7c, 0xbd, 0x3b, 0xda, 0x12, 0xfb, 0x13, 0xa7, 0xaa, 0x14, 0x77,
5747
+	0x71, 0x8d, 0x0c, 0x07, 0x19, 0x21, 0x65, 0xfc, 0x22, 0x87, 0x5e, 0x19, 0x3b, 0xbf, 0xe0, 0x13,
5748
+	0xfd, 0xde, 0x92, 0x13, 0xe9, 0xb4, 0x73, 0x21, 0x83, 0xd1, 0x60, 0xf4, 0xb3, 0xcb, 0xad, 0xe2,
5749
+	0xaf, 0x3f, 0x5b, 0x9b, 0x7a, 0xfc, 0x8f, 0xf5, 0x29, 0xe3, 0x3f, 0x39, 0x74, 0x6d, 0x8c, 0xec,
5750
+	0x79, 0x5e, 0x3b, 0x7f, 0x05, 0xb7, 0x8e, 0x3d, 0x9c, 0x0b, 0xaa, 0x44, 0x7f, 0x74, 0xde, 0xad,
5751
+	0x65, 0x92, 0xac, 0x7e, 0x85, 0x37, 0xa4, 0x99, 0x65, 0x92, 0x35, 0xc1, 0x78, 0x06, 0x05, 0x7b,
5752
+	0xfb, 0x11, 0x6d, 0xde, 0xa5, 0x0f, 0x61, 0x42, 0xb9, 0xc3, 0xd8, 0x49, 0xf2, 0x3d, 0x38, 0x37,
5753
+	0xfe, 0x3d, 0x18, 0x6f, 0xa1, 0x3c, 0x75, 0xfb, 0x13, 0xbd, 0x45, 0x97, 0x95, 0xcb, 0xf2, 0xf0,
5754
+	0x4d, 0xb8, 0x34, 0x1f, 0x29, 0x52, 0x29, 0x2b, 0x32, 0xb5, 0x14, 0x8f, 0x14, 0xa9, 0xfc, 0x26,
5755
+	0x69, 0x5e, 0x71, 0xe3, 0xb3, 0x6e, 0xc8, 0x8f, 0x44, 0x21, 0x36, 0xf4, 0xbe, 0x5c, 0x22, 0x9a,
5756
+	0x66, 0xfc, 0x69, 0x1a, 0x2d, 0x34, 0xec, 0x36, 0x6d, 0x0e, 0x9a, 0x5d, 0x2a, 0x76, 0xf8, 0x6d,
5757
+	0xb4, 0xd0, 0x86, 0x7e, 0x24, 0xf4, 0xa8, 0x8c, 0xac, 0x8a, 0xe8, 0x97, 0xb5, 0xd6, 0x9d, 0x24,
5758
+	0x11, 0x42, 0x5b, 0x4d, 0x89, 0xa7, 0xa8, 0x24, 0x8d, 0x84, 0x1d, 0x84, 0x68, 0xe4, 0x4e, 0x15,
5759
+	0xe0, 0xdb, 0x13, 0x06, 0x38, 0x1d, 0x0f, 0xd9, 0x1c, 0xc5, 0x6b, 0x24, 0xa1, 0x00, 0x77, 0x79,
5760
+	0xe7, 0xd5, 0x11, 0x91, 0xf6, 0xc5, 0xeb, 0x7d, 0x79, 0xe3, 0xdd, 0x09, 0xb5, 0x1d, 0x29, 0x79,
5761
+	0xa1, 0x2b, 0x2a, 0x45, 0x7a, 0x55, 0xb4, 0x6e, 0xea, 0x4f, 0xe3, 0xdf, 0xd3, 0x68, 0xfd, 0x45,
5762
+	0x17, 0x33, 0xaf, 0x57, 0xbc, 0x35, 0x64, 0x61, 0xa0, 0x5b, 0x5a, 0x39, 0x26, 0x8a, 0x7a, 0x75,
5763
+	0x94, 0xa2, 0x90, 0x21, 0x4e, 0x98, 0xaa, 0xf3, 0xd0, 0x18, 0x2b, 0xb7, 0x7d, 0x6d, 0xc2, 0x8d,
5764
+	0xa4, 0x82, 0x54, 0x9f, 0xe3, 0x79, 0x06, 0xfd, 0x36, 0xe1, 0x88, 0x1c, 0xd8, 0xb1, 0x5b, 0xaa,
5765
+	0x0e, 0x5e, 0x00, 0xf0, 0x9e, 0xdd, 0x22, 0x1c, 0x11, 0x7f, 0x82, 0x0a, 0x3d, 0xe6, 0x07, 0xaa,
5766
+	0xb1, 0x38, 0x1f, 0x72, 0x91, 0x57, 0x93, 0x03, 0xc6, 0x1f, 0x43, 0x38, 0xa6, 0xf1, 0xbc, 0x80,
5767
+	0xd6, 0x5e, 0xd0, 0x7a, 0xe0, 0x5d, 0x18, 0x28, 0xc5, 0xb0, 0x7a, 0x40, 0x3d, 0x9b, 0xb5, 0xd2,
5768
+	0x2e, 0xbf, 0x26, 0x86, 0xc9, 0x2c, 0x99, 0x8c, 0x92, 0xc1, 0xb7, 0xf9, 0xd3, 0x5a, 0x00, 0xb3,
5769
+	0xb6, 0xd5, 0xd5, 0x30, 0x72, 0x3c, 0xbf, 0x24, 0x9f, 0xd5, 0x52, 0x24, 0x32, 0xcc, 0x3b, 0x22,
5770
+	0xee, 0xf9, 0x97, 0x8e, 0xfb, 0xa7, 0x68, 0xd1, 0xb1, 0x1e, 0x25, 0xc6, 0x5e, 0xe5, 0x4f, 0x73,
5771
+	0x4c, 0x59, 0xe1, 0xff, 0x73, 0x33, 0xe5, 0xff, 0xdc, 0x4c, 0x30, 0x6c, 0xdf, 0x03, 0xaf, 0x80,
5772
+	0x97, 0xa4, 0xae, 0xbd, 0x14, 0x12, 0x19, 0x42, 0x16, 0xff, 0x70, 0xb1, 0x1e, 0x1d, 0x86, 0x5e,
5773
+	0x47, 0x4f, 0x4e, 0x93, 0x6a, 0x11, 0x4f, 0x37, 0x7b, 0x0a, 0x83, 0x44, 0x68, 0x3a, 0x7b, 0xe7,
5774
+	0x2e, 0x3c, 0x7b, 0x75, 0x92, 0x15, 0xff, 0x0f, 0x49, 0x06, 0x37, 0xd3, 0x7c, 0xb2, 0x04, 0x64,
5775
+	0x4b, 0x72, 0x6e, 0x82, 0x92, 0xbc, 0x8b, 0xa6, 0x03, 0xa6, 0xce, 0xef, 0x84, 0xed, 0x06, 0x52,
5776
+	0x0a, 0xa6, 0x8f, 0x18, 0x01, 0x90, 0xfa, 0x6b, 0x4f, 0xfe, 0xb9, 0x3a, 0xf5, 0x39, 0xfc, 0xfe,
5777
+	0x06, 0xbf, 0xc7, 0xcf, 0x57, 0x73, 0x4f, 0xe0, 0xf7, 0x39, 0xfc, 0x9e, 0xc1, 0xef, 0xe7, 0xff,
5778
+	0x5a, 0x9d, 0xfa, 0x64, 0xba, 0x7f, 0xf3, 0x7f, 0x01, 0x00, 0x00, 0xff, 0xff, 0xcc, 0x26, 0x6d,
5779
+	0xfd, 0x05, 0x1f, 0x00, 0x00,
5777 5780
 }
... ...
@@ -230,6 +230,19 @@ message DeploymentLogOptions {
230 230
   optional int64 version = 10;
231 231
 }
232 232
 
233
+// DeploymentRequest is a request to a deployment config for a new deployment.
234
+message DeploymentRequest {
235
+  // Name of the deployment config for requesting a new deployment.
236
+  optional string name = 1;
237
+
238
+  // Latest will update the deployment config with the latest state from all triggers.
239
+  optional bool latest = 2;
240
+
241
+  // Force will try to force a new deployment to run. If the deployment config is paused,
242
+  // then setting this to true will return an Invalid error.
243
+  optional bool force = 3;
244
+}
245
+
233 246
 // DeploymentStrategy describes how to perform a deployment.
234 247
 message DeploymentStrategy {
235 248
   // Type is the name of a deployment strategy.
... ...
@@ -21,6 +21,7 @@ func addKnownTypes(scheme *runtime.Scheme) error {
21 21
 		&DeploymentConfig{},
22 22
 		&DeploymentConfigList{},
23 23
 		&DeploymentConfigRollback{},
24
+		&DeploymentRequest{},
24 25
 		&DeploymentLog{},
25 26
 		&DeploymentLogOptions{},
26 27
 	)
... ...
@@ -30,5 +31,6 @@ func addKnownTypes(scheme *runtime.Scheme) error {
30 30
 func (obj *DeploymentConfig) GetObjectKind() unversioned.ObjectKind         { return &obj.TypeMeta }
31 31
 func (obj *DeploymentConfigList) GetObjectKind() unversioned.ObjectKind     { return &obj.TypeMeta }
32 32
 func (obj *DeploymentConfigRollback) GetObjectKind() unversioned.ObjectKind { return &obj.TypeMeta }
33
+func (obj *DeploymentRequest) GetObjectKind() unversioned.ObjectKind        { return &obj.TypeMeta }
33 34
 func (obj *DeploymentLog) GetObjectKind() unversioned.ObjectKind            { return &obj.TypeMeta }
34 35
 func (obj *DeploymentLogOptions) GetObjectKind() unversioned.ObjectKind     { return &obj.TypeMeta }
... ...
@@ -149,6 +149,17 @@ func (DeploymentLogOptions) SwaggerDoc() map[string]string {
149 149
 	return map_DeploymentLogOptions
150 150
 }
151 151
 
152
+var map_DeploymentRequest = map[string]string{
153
+	"":       "DeploymentRequest is a request to a deployment config for a new deployment.",
154
+	"name":   "Name of the deployment config for requesting a new deployment.",
155
+	"latest": "Latest will update the deployment config with the latest state from all triggers.",
156
+	"force":  "Force will try to force a new deployment to run. If the deployment config is paused, then setting this to true will return an Invalid error.",
157
+}
158
+
159
+func (DeploymentRequest) SwaggerDoc() map[string]string {
160
+	return map_DeploymentRequest
161
+}
162
+
152 163
 var map_DeploymentStrategy = map[string]string{
153 164
 	"":               "DeploymentStrategy describes how to perform a deployment.",
154 165
 	"type":           "Type is the name of a deployment strategy.",
... ...
@@ -421,6 +421,18 @@ type DeploymentConfigRollbackSpec struct {
421 421
 	IncludeStrategy bool `json:"includeStrategy" protobuf:"varint,6,opt,name=includeStrategy"`
422 422
 }
423 423
 
424
+// DeploymentRequest is a request to a deployment config for a new deployment.
425
+type DeploymentRequest struct {
426
+	unversioned.TypeMeta `json:",inline"`
427
+	// Name of the deployment config for requesting a new deployment.
428
+	Name string `json:"name" protobuf:"bytes,1,opt,name=name"`
429
+	// Latest will update the deployment config with the latest state from all triggers.
430
+	Latest bool `json:"latest" protobuf:"varint,2,opt,name=latest"`
431
+	// Force will try to force a new deployment to run. If the deployment config is paused,
432
+	// then setting this to true will return an Invalid error.
433
+	Force bool `json:"force" protobuf:"varint,3,opt,name=force"`
434
+}
435
+
424 436
 // DeploymentLog represents the logs for a deployment
425 437
 type DeploymentLog struct {
426 438
 	unversioned.TypeMeta `json:",inline"`
... ...
@@ -44,6 +44,8 @@ func RegisterConversions(scheme *runtime.Scheme) error {
44 44
 		Convert_api_DeploymentLog_To_v1_DeploymentLog,
45 45
 		Convert_v1_DeploymentLogOptions_To_api_DeploymentLogOptions,
46 46
 		Convert_api_DeploymentLogOptions_To_v1_DeploymentLogOptions,
47
+		Convert_v1_DeploymentRequest_To_api_DeploymentRequest,
48
+		Convert_api_DeploymentRequest_To_v1_DeploymentRequest,
47 49
 		Convert_v1_DeploymentStrategy_To_api_DeploymentStrategy,
48 50
 		Convert_api_DeploymentStrategy_To_v1_DeploymentStrategy,
49 51
 		Convert_v1_DeploymentTriggerImageChangeParams_To_api_DeploymentTriggerImageChangeParams,
... ...
@@ -543,6 +545,34 @@ func Convert_api_DeploymentLogOptions_To_v1_DeploymentLogOptions(in *api.Deploym
543 543
 	return autoConvert_api_DeploymentLogOptions_To_v1_DeploymentLogOptions(in, out, s)
544 544
 }
545 545
 
546
+func autoConvert_v1_DeploymentRequest_To_api_DeploymentRequest(in *DeploymentRequest, out *api.DeploymentRequest, s conversion.Scope) error {
547
+	if err := pkg_api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil {
548
+		return err
549
+	}
550
+	out.Name = in.Name
551
+	out.Latest = in.Latest
552
+	out.Force = in.Force
553
+	return nil
554
+}
555
+
556
+func Convert_v1_DeploymentRequest_To_api_DeploymentRequest(in *DeploymentRequest, out *api.DeploymentRequest, s conversion.Scope) error {
557
+	return autoConvert_v1_DeploymentRequest_To_api_DeploymentRequest(in, out, s)
558
+}
559
+
560
+func autoConvert_api_DeploymentRequest_To_v1_DeploymentRequest(in *api.DeploymentRequest, out *DeploymentRequest, s conversion.Scope) error {
561
+	if err := pkg_api.Convert_unversioned_TypeMeta_To_unversioned_TypeMeta(&in.TypeMeta, &out.TypeMeta, s); err != nil {
562
+		return err
563
+	}
564
+	out.Name = in.Name
565
+	out.Latest = in.Latest
566
+	out.Force = in.Force
567
+	return nil
568
+}
569
+
570
+func Convert_api_DeploymentRequest_To_v1_DeploymentRequest(in *api.DeploymentRequest, out *DeploymentRequest, s conversion.Scope) error {
571
+	return autoConvert_api_DeploymentRequest_To_v1_DeploymentRequest(in, out, s)
572
+}
573
+
546 574
 func autoConvert_v1_DeploymentStrategy_To_api_DeploymentStrategy(in *DeploymentStrategy, out *api.DeploymentStrategy, s conversion.Scope) error {
547 575
 	SetDefaults_DeploymentStrategy(in)
548 576
 	out.Type = api.DeploymentStrategyType(in.Type)
... ...
@@ -33,6 +33,7 @@ func RegisterDeepCopies(scheme *runtime.Scheme) error {
33 33
 		conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_DeploymentDetails, InType: reflect.TypeOf(&DeploymentDetails{})},
34 34
 		conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_DeploymentLog, InType: reflect.TypeOf(&DeploymentLog{})},
35 35
 		conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_DeploymentLogOptions, InType: reflect.TypeOf(&DeploymentLogOptions{})},
36
+		conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_DeploymentRequest, InType: reflect.TypeOf(&DeploymentRequest{})},
36 37
 		conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_DeploymentStrategy, InType: reflect.TypeOf(&DeploymentStrategy{})},
37 38
 		conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_DeploymentTriggerImageChangeParams, InType: reflect.TypeOf(&DeploymentTriggerImageChangeParams{})},
38 39
 		conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_v1_DeploymentTriggerPolicy, InType: reflect.TypeOf(&DeploymentTriggerPolicy{})},
... ...
@@ -321,6 +322,18 @@ func DeepCopy_v1_DeploymentLogOptions(in interface{}, out interface{}, c *conver
321 321
 	}
322 322
 }
323 323
 
324
+func DeepCopy_v1_DeploymentRequest(in interface{}, out interface{}, c *conversion.Cloner) error {
325
+	{
326
+		in := in.(*DeploymentRequest)
327
+		out := out.(*DeploymentRequest)
328
+		out.TypeMeta = in.TypeMeta
329
+		out.Name = in.Name
330
+		out.Latest = in.Latest
331
+		out.Force = in.Force
332
+		return nil
333
+	}
334
+}
335
+
324 336
 func DeepCopy_v1_DeploymentStrategy(in interface{}, out interface{}, c *conversion.Cloner) error {
325 337
 	{
326 338
 		in := in.(*DeploymentStrategy)
... ...
@@ -502,6 +502,31 @@ func IsValidPercent(percent string) bool {
502 502
 
503 503
 const isNegativeErrorMsg string = `must be non-negative`
504 504
 
505
+func ValidateDeploymentRequest(req *deployapi.DeploymentRequest) field.ErrorList {
506
+	allErrs := field.ErrorList{}
507
+
508
+	if len(req.Name) == 0 {
509
+		allErrs = append(allErrs, field.Invalid(field.NewPath("name"), req.Name, "name of the deployment config is missing"))
510
+	} else if len(kvalidation.IsDNS1123Subdomain(req.Name)) != 0 {
511
+		allErrs = append(allErrs, field.Invalid(field.NewPath("name"), req.Name, "name of the deployment config is invalid"))
512
+	}
513
+
514
+	return allErrs
515
+}
516
+
517
+func ValidateRequestForDeploymentConfig(req *deployapi.DeploymentRequest, config *deployapi.DeploymentConfig) field.ErrorList {
518
+	allErrs := ValidateDeploymentRequest(req)
519
+
520
+	if config.Spec.Paused {
521
+		// TODO: Enable deployment requests for paused deployment configs
522
+		// See https://github.com/openshift/origin/issues/9903
523
+		details := fmt.Sprintf("deployment config %q is paused - unpause to request a new deployment", config.Name)
524
+		allErrs = append(allErrs, field.Invalid(field.NewPath("spec").Child("paused"), config.Spec.Paused, details))
525
+	}
526
+
527
+	return allErrs
528
+}
529
+
505 530
 func ValidateDeploymentLogOptions(opts *deployapi.DeploymentLogOptions) field.ErrorList {
506 531
 	allErrs := field.ErrorList{}
507 532
 
... ...
@@ -33,6 +33,7 @@ func RegisterDeepCopies(scheme *runtime.Scheme) error {
33 33
 		conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_DeploymentDetails, InType: reflect.TypeOf(&DeploymentDetails{})},
34 34
 		conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_DeploymentLog, InType: reflect.TypeOf(&DeploymentLog{})},
35 35
 		conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_DeploymentLogOptions, InType: reflect.TypeOf(&DeploymentLogOptions{})},
36
+		conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_DeploymentRequest, InType: reflect.TypeOf(&DeploymentRequest{})},
36 37
 		conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_DeploymentStrategy, InType: reflect.TypeOf(&DeploymentStrategy{})},
37 38
 		conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_DeploymentTriggerImageChangeParams, InType: reflect.TypeOf(&DeploymentTriggerImageChangeParams{})},
38 39
 		conversion.GeneratedDeepCopyFunc{Fn: DeepCopy_api_DeploymentTriggerPolicy, InType: reflect.TypeOf(&DeploymentTriggerPolicy{})},
... ...
@@ -322,6 +323,18 @@ func DeepCopy_api_DeploymentLogOptions(in interface{}, out interface{}, c *conve
322 322
 	}
323 323
 }
324 324
 
325
+func DeepCopy_api_DeploymentRequest(in interface{}, out interface{}, c *conversion.Cloner) error {
326
+	{
327
+		in := in.(*DeploymentRequest)
328
+		out := out.(*DeploymentRequest)
329
+		out.TypeMeta = in.TypeMeta
330
+		out.Name = in.Name
331
+		out.Latest = in.Latest
332
+		out.Force = in.Force
333
+		return nil
334
+	}
335
+}
336
+
325 337
 func DeepCopy_api_DeploymentStrategy(in interface{}, out interface{}, c *conversion.Cloner) error {
326 338
 	{
327 339
 		in := in.(*DeploymentStrategy)
... ...
@@ -1,10 +1,6 @@
1 1
 package generictrigger
2 2
 
3 3
 import (
4
-	"fmt"
5
-
6
-	kapi "k8s.io/kubernetes/pkg/api"
7
-	kclient "k8s.io/kubernetes/pkg/client/unversioned"
8 4
 	"k8s.io/kubernetes/pkg/runtime"
9 5
 	utilruntime "k8s.io/kubernetes/pkg/util/runtime"
10 6
 	"k8s.io/kubernetes/pkg/util/workqueue"
... ...
@@ -13,7 +9,6 @@ import (
13 13
 	osclient "github.com/openshift/origin/pkg/client"
14 14
 	oscache "github.com/openshift/origin/pkg/client/cache"
15 15
 	deployapi "github.com/openshift/origin/pkg/deploy/api"
16
-	deployutil "github.com/openshift/origin/pkg/deploy/util"
17 16
 )
18 17
 
19 18
 // DeploymentTriggerController processes all triggers for a deployment config
... ...
@@ -21,8 +16,6 @@ import (
21 21
 type DeploymentTriggerController struct {
22 22
 	// dn is used to update deployment configs.
23 23
 	dn osclient.DeploymentConfigsNamespacer
24
-	// rn is used for getting the latest deployment for a config.
25
-	rn kclient.ReplicationControllersNamespacer
26 24
 
27 25
 	// queue contains deployment configs that need to be synced.
28 26
 	queue workqueue.RateLimitingInterface
... ...
@@ -36,186 +29,19 @@ type DeploymentTriggerController struct {
36 36
 	codec runtime.Codec
37 37
 }
38 38
 
39
-// fatalError is an error which can't be retried.
40
-type fatalError string
41
-
42
-func (e fatalError) Error() string {
43
-	return fmt.Sprintf("fatal error handling configuration: %s", string(e))
44
-}
45
-
46 39
 // Handle processes deployment triggers for a deployment config.
47 40
 func (c *DeploymentTriggerController) Handle(config *deployapi.DeploymentConfig) error {
48 41
 	if len(config.Spec.Triggers) == 0 || config.Spec.Paused {
49 42
 		return nil
50 43
 	}
51 44
 
52
-	// Try to decode this deployment config from the encoded annotation found in
53
-	// its latest deployment.
54
-	decoded, err := c.decodeFromLatest(config)
55
-	if err != nil {
56
-		return err
57
-	}
58
-
59
-	canTrigger, causes := canTrigger(config, decoded)
60
-
61
-	// Return if we cannot trigger a new deployment.
62
-	if !canTrigger {
63
-		return nil
64
-	}
65
-
66
-	copied, err := deployutil.DeploymentConfigDeepCopy(config)
67
-	if err != nil {
68
-		return err
69
-	}
70
-
71
-	return c.update(copied, causes)
72
-}
73
-
74
-// decodeFromLatest will try to return the decoded version of the current deploymentconfig found
75
-// in the annotations of its latest deployment. If there is no previous deploymentconfig (ie.
76
-// latestVersion == 0), the returned deploymentconfig will be the same.
77
-func (c *DeploymentTriggerController) decodeFromLatest(config *deployapi.DeploymentConfig) (*deployapi.DeploymentConfig, error) {
78
-	if config.Status.LatestVersion == 0 {
79
-		return config, nil
80
-	}
81
-
82
-	latestDeploymentName := deployutil.LatestDeploymentNameForConfig(config)
83
-	deployment, err := c.rn.ReplicationControllers(config.Namespace).Get(latestDeploymentName)
84
-	if err != nil {
85
-		// If there's no deployment for the latest config, we have no basis of
86
-		// comparison. It's the responsibility of the deployment config controller
87
-		// to make the deployment for the config, so return early.
88
-		return nil, fmt.Errorf("couldn't retrieve deployment for deployment config %q: %v", deployutil.LabelForDeploymentConfig(config), err)
89
-	}
90
-
91
-	latest, err := deployutil.DecodeDeploymentConfig(deployment, c.codec)
92
-	if err != nil {
93
-		return nil, fatalError(err.Error())
94
-	}
95
-	return latest, nil
96
-}
97
-
98
-// canTrigger is used by the trigger controller to determine if the provided config can trigger
99
-// a deployment.
100
-//
101
-// Image change triggers are processed first. It is required for all of them to point to images
102
-// that exist. Otherwise, this controller will wait for the images to land and be updated in the
103
-// triggers that point to them by the image change controller.
104
-//
105
-// Config change triggers are processed last. If all images are resolved and an automatic trigger
106
-// was updated, then it should be possible to trigger a new deployment without a config change
107
-// trigger. Otherwise, if a config change trigger exists and the config is not deployed yet or it
108
-// has a podtemplate change, then the controller should trigger a new deployment (assuming all
109
-// image change triggers can trigger).
110
-func canTrigger(config, decoded *deployapi.DeploymentConfig) (bool, []deployapi.DeploymentCause) {
111
-	if decoded == nil {
112
-		// The decoded deployment config will never be nil here but a sanity check
113
-		// never hurts.
114
-		return false, nil
115
-	}
116
-	ictCount, resolved, canTriggerByImageChange := 0, 0, false
117
-	var causes []deployapi.DeploymentCause
118
-
119
-	// IMAGE CHANGE TRIGGERS
120
-	for _, t := range config.Spec.Triggers {
121
-		if t.Type != deployapi.DeploymentTriggerOnImageChange {
122
-			continue
123
-		}
124
-		ictCount++
125
-
126
-		// If this is the initial deployment then we need to wait for the image change controller
127
-		// to resolve the image inside the pod template.
128
-		lastTriggered := t.ImageChangeParams.LastTriggeredImage
129
-		if len(lastTriggered) == 0 {
130
-			continue
131
-		}
132
-		resolved++
133
-
134
-		// Non-automatic triggers should not be able to trigger deployments.
135
-		if !t.ImageChangeParams.Automatic {
136
-			continue
137
-		}
138
-
139
-		// We need stronger checks in order to validate that this template
140
-		// change is an image change. Look at the deserialized config's
141
-		// triggers and compare with the present trigger. Initial deployments
142
-		// should always trigger since there is no previous config to compare to.
143
-		if config.Status.LatestVersion > 0 {
144
-			if !triggeredByDifferentImage(*t.ImageChangeParams, *decoded) {
145
-				continue
146
-			}
147
-		}
148
-
149
-		canTriggerByImageChange = true
150
-		causes = append(causes, deployapi.DeploymentCause{
151
-			Type: deployapi.DeploymentTriggerOnImageChange,
152
-			ImageTrigger: &deployapi.DeploymentCauseImageTrigger{
153
-				From: kapi.ObjectReference{
154
-					Name:      t.ImageChangeParams.From.Name,
155
-					Namespace: t.ImageChangeParams.From.Namespace,
156
-					Kind:      "ImageStreamTag",
157
-				},
158
-			},
159
-		})
160
-	}
161
-
162
-	// We need to wait for all images to resolve before triggering a new deployment.
163
-	if ictCount != resolved {
164
-		return false, nil
45
+	request := &deployapi.DeploymentRequest{
46
+		Name:   config.Name,
47
+		Latest: true,
48
+		Force:  false,
165 49
 	}
166 50
 
167
-	// CONFIG CHANGE TRIGGERS
168
-	canTriggerByConfigChange := false
169
-	// Our deployment config has a config change trigger and no image change has triggered.
170
-	// If an image change had happened, it would be enough to start a new deployment without
171
-	// caring about the config change trigger.
172
-	if deployutil.HasChangeTrigger(config) && !canTriggerByImageChange {
173
-		// This is the initial deployment or the config has a template change. We need to
174
-		// kick a new deployment.
175
-		if config.Status.LatestVersion == 0 || !kapi.Semantic.DeepEqual(config.Spec.Template, decoded.Spec.Template) {
176
-			canTriggerByConfigChange = true
177
-			causes = []deployapi.DeploymentCause{{Type: deployapi.DeploymentTriggerOnConfigChange}}
178
-		}
179
-	}
180
-
181
-	return canTriggerByConfigChange || canTriggerByImageChange, causes
182
-}
183
-
184
-// triggeredByDifferentImage compares the provided image change parameters with those found in the
185
-// previous deployment config (the one we decoded from the annotations of its latest deployment)
186
-// and returns whether the two deployment configs have been triggered by a different image change.
187
-func triggeredByDifferentImage(ictParams deployapi.DeploymentTriggerImageChangeParams, previous deployapi.DeploymentConfig) bool {
188
-	for _, t := range previous.Spec.Triggers {
189
-		if t.Type != deployapi.DeploymentTriggerOnImageChange {
190
-			continue
191
-		}
192
-
193
-		if t.ImageChangeParams.From.Name != ictParams.From.Name ||
194
-			t.ImageChangeParams.From.Namespace != ictParams.From.Namespace {
195
-			continue
196
-		}
197
-		if t.ImageChangeParams.LastTriggeredImage != ictParams.LastTriggeredImage {
198
-			glog.V(4).Infof("Deployment config %q triggered by different image: %s -> %s", previous.Name, t.ImageChangeParams.LastTriggeredImage, ictParams.LastTriggeredImage)
199
-			return true
200
-		}
201
-		return false
202
-	}
203
-	return false
204
-}
205
-
206
-// update increments the latestVersion of the provided deployment config so the deployment config
207
-// controller can run a new deployment and also updates the details of the deployment config.
208
-func (c *DeploymentTriggerController) update(config *deployapi.DeploymentConfig, causes []deployapi.DeploymentCause) error {
209
-	config.Status.LatestVersion++
210
-	config.Status.Details = new(deployapi.DeploymentDetails)
211
-	config.Status.Details.Causes = causes
212
-	switch causes[0].Type {
213
-	case deployapi.DeploymentTriggerOnConfigChange:
214
-		config.Status.Details.Message = "caused by a config change"
215
-	case deployapi.DeploymentTriggerOnImageChange:
216
-		config.Status.Details.Message = "caused by an image change"
217
-	}
218
-	_, err := c.dn.DeploymentConfigs(config.Namespace).UpdateStatus(config)
51
+	_, err := c.dn.DeploymentConfigs(config.Namespace).Instantiate(request)
219 52
 	return err
220 53
 }
221 54
 
... ...
@@ -9,7 +9,6 @@ import (
9 9
 	ktestclient "k8s.io/kubernetes/pkg/client/unversioned/testclient"
10 10
 	"k8s.io/kubernetes/pkg/controller/framework"
11 11
 	"k8s.io/kubernetes/pkg/runtime"
12
-	"k8s.io/kubernetes/pkg/util/diff"
13 12
 	"k8s.io/kubernetes/pkg/watch"
14 13
 
15 14
 	"github.com/openshift/origin/pkg/client/testclient"
... ...
@@ -17,34 +16,31 @@ import (
17 17
 	_ "github.com/openshift/origin/pkg/deploy/api/install"
18 18
 	testapi "github.com/openshift/origin/pkg/deploy/api/test"
19 19
 	deployv1 "github.com/openshift/origin/pkg/deploy/api/v1"
20
-	deployutil "github.com/openshift/origin/pkg/deploy/util"
21 20
 	imageapi "github.com/openshift/origin/pkg/image/api"
22 21
 )
23 22
 
24 23
 var (
25 24
 	codec      = kapi.Codecs.LegacyCodec(deployv1.SchemeGroupVersion)
26
-	mock       = &testclient.Fake{}
27 25
 	dcInformer = framework.NewSharedIndexInformer(
28 26
 		&cache.ListWatch{
29 27
 			ListFunc: func(options kapi.ListOptions) (runtime.Object, error) {
30
-				return mock.DeploymentConfigs(kapi.NamespaceAll).List(options)
28
+				return (&testclient.Fake{}).DeploymentConfigs(kapi.NamespaceAll).List(options)
31 29
 			},
32 30
 			WatchFunc: func(options kapi.ListOptions) (watch.Interface, error) {
33
-				return mock.DeploymentConfigs(kapi.NamespaceAll).Watch(options)
31
+				return (&testclient.Fake{}).DeploymentConfigs(kapi.NamespaceAll).Watch(options)
34 32
 			},
35 33
 		},
36 34
 		&deployapi.DeploymentConfig{},
37 35
 		2*time.Minute,
38 36
 		cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc},
39 37
 	)
40
-
41 38
 	streamInformer = framework.NewSharedIndexInformer(
42 39
 		&cache.ListWatch{
43 40
 			ListFunc: func(options kapi.ListOptions) (runtime.Object, error) {
44
-				return mock.ImageStreams(kapi.NamespaceAll).List(options)
41
+				return (&testclient.Fake{}).ImageStreams(kapi.NamespaceAll).List(options)
45 42
 			},
46 43
 			WatchFunc: func(options kapi.ListOptions) (watch.Interface, error) {
47
-				return mock.ImageStreams(kapi.NamespaceAll).Watch(options)
44
+				return (&testclient.Fake{}).ImageStreams(kapi.NamespaceAll).Watch(options)
48 45
 			},
49 46
 		},
50 47
 		&imageapi.ImageStream{},
... ...
@@ -53,13 +49,12 @@ var (
53 53
 	)
54 54
 )
55 55
 
56
-// TestHandle_newConfigNoTriggers ensures that a change to a config with no
57
-// triggers doesn't result in a new config version bump.
58
-func TestHandle_newConfigNoTriggers(t *testing.T) {
56
+// TestHandle_noTriggers ensures that a change to a config with no
57
+// triggers doesn't result in a config instantiation.
58
+func TestHandle_noTriggers(t *testing.T) {
59 59
 	fake := &testclient.Fake{}
60
-	kFake := &ktestclient.Fake{}
61 60
 
62
-	controller := NewDeploymentTriggerController(dcInformer, streamInformer, fake, kFake, codec)
61
+	controller := NewDeploymentTriggerController(dcInformer, streamInformer, fake, codec)
63 62
 
64 63
 	config := testapi.OkDeploymentConfig(1)
65 64
 	config.Namespace = kapi.NamespaceDefault
... ...
@@ -68,596 +63,71 @@ func TestHandle_newConfigNoTriggers(t *testing.T) {
68 68
 		t.Fatalf("unexpected error: %v", err)
69 69
 	}
70 70
 	if len(fake.Actions()) > 0 {
71
-		t.Fatalf("unexpected actions by the Origin client: %v", fake.Actions())
72
-	}
73
-	if len(kFake.Actions()) > 0 {
74
-		t.Fatalf("unexpected actions by the Kube client: %v", kFake.Actions())
71
+		t.Fatalf("unexpected actions: %v", fake.Actions())
75 72
 	}
76 73
 }
77 74
 
78
-// TestHandle_newConfigTriggers ensures that the creation of a new config
79
-// (with version 0) with a config change trigger results in a version bump and
80
-// cause update for initial deployment.
81
-func TestHandle_newConfigTriggers(t *testing.T) {
82
-	var updated *deployapi.DeploymentConfig
83
-
75
+// TestHandle_pausedConfig ensures that a paused config will not be instantiated.
76
+func TestHandle_pausedConfig(t *testing.T) {
84 77
 	fake := &testclient.Fake{}
85
-	fake.AddReactor("update", "deploymentconfigs/status", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) {
86
-		updated = action.(ktestclient.UpdateAction).GetObject().(*deployapi.DeploymentConfig)
87
-		return true, updated, nil
88
-	})
89
-	kFake := &ktestclient.Fake{}
90 78
 
91
-	controller := NewDeploymentTriggerController(dcInformer, streamInformer, fake, kFake, codec)
79
+	controller := NewDeploymentTriggerController(dcInformer, streamInformer, fake, codec)
92 80
 
93
-	config := testapi.OkDeploymentConfig(0)
81
+	config := testapi.OkDeploymentConfig(1)
94 82
 	config.Namespace = kapi.NamespaceDefault
95
-	config.Spec.Triggers = []deployapi.DeploymentTriggerPolicy{testapi.OkConfigChangeTrigger()}
83
+	config.Spec.Paused = true
96 84
 	if err := controller.Handle(config); err != nil {
97 85
 		t.Fatalf("unexpected error: %v", err)
98 86
 	}
99
-	if updated == nil {
100
-		t.Fatalf("expected config to be updated")
101
-	}
102
-	if e, a := int64(1), updated.Status.LatestVersion; e != a {
103
-		t.Fatalf("expected update to latestversion=%d, got %d", e, a)
104
-	}
105
-	if updated.Status.Details == nil {
106
-		t.Fatalf("expected config change details to be set")
107
-	} else if updated.Status.Details.Causes == nil {
108
-		t.Fatalf("expected config change causes to be set")
109
-	} else if updated.Status.Details.Causes[0].Type != deployapi.DeploymentTriggerOnConfigChange {
110
-		t.Fatalf("expected config change cause to be set to config change trigger, got %s", updated.Status.Details.Causes[0].Type)
87
+	if len(fake.Actions()) > 0 {
88
+		t.Fatalf("unexpected actions: %v", fake.Actions())
111 89
 	}
112 90
 }
113 91
 
114
-// TestHandle_changeWithTemplateDiff ensures that a pod template change to a
115
-// config with a config change trigger results in a version bump and cause
116
-// update.
117
-func TestHandle_changeWithTemplateDiff(t *testing.T) {
118
-	scenarios := []struct {
119
-		name           string
120
-		modify         func(*deployapi.DeploymentConfig)
121
-		changeExpected bool
122
-	}{
123
-		{
124
-			name:           "container name change",
125
-			changeExpected: true,
126
-			modify: func(config *deployapi.DeploymentConfig) {
127
-				config.Spec.Template.Spec.Containers[1].Name = "modified"
128
-			},
129
-		},
130
-		{
131
-			name:           "template label change",
132
-			changeExpected: true,
133
-			modify: func(config *deployapi.DeploymentConfig) {
134
-				config.Spec.Template.Labels["newkey"] = "value"
135
-			},
136
-		},
137
-		{
138
-			name:           "no diff",
139
-			changeExpected: false,
140
-			modify:         func(config *deployapi.DeploymentConfig) {},
141
-		},
142
-	}
143
-
144
-	for _, s := range scenarios {
145
-		t.Logf("running scenario: %s", s.name)
146
-		fake := &testclient.Fake{}
147
-		kFake := &ktestclient.Fake{}
148
-
149
-		config := testapi.OkDeploymentConfig(1)
150
-		config.Namespace = kapi.NamespaceDefault
151
-		config.Spec.Triggers = []deployapi.DeploymentTriggerPolicy{testapi.OkConfigChangeTrigger()}
152
-
153
-		versioned, err := kapi.Scheme.ConvertToVersion(config, deployv1.SchemeGroupVersion)
154
-		if err != nil {
155
-			t.Errorf("unexpected conversion error: %v", err)
156
-			continue
157
-		}
158
-		defaulted, err := kapi.Scheme.ConvertToVersion(versioned, deployapi.SchemeGroupVersion)
159
-		if err != nil {
160
-			t.Errorf("unexpected conversion error: %v", err)
161
-			continue
162
-		}
163
-		config = defaulted.(*deployapi.DeploymentConfig)
164
-
165
-		deployment, _ := deployutil.MakeDeployment(config, kapi.Codecs.LegacyCodec(deployv1.SchemeGroupVersion))
166
-		var updated *deployapi.DeploymentConfig
167
-
168
-		fake.PrependReactor("update", "deploymentconfigs/status", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) {
169
-			updated = action.(ktestclient.UpdateAction).GetObject().(*deployapi.DeploymentConfig)
170
-			return true, updated, nil
171
-		})
172
-		kFake.PrependReactor("get", "replicationcontrollers", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) {
173
-			return true, deployment, nil
174
-		})
175
-
176
-		controller := NewDeploymentTriggerController(dcInformer, streamInformer, fake, kFake, codec)
177
-
178
-		s.modify(config)
179
-		if err := controller.Handle(config); err != nil {
180
-			t.Errorf("unexpected error: %v", err)
181
-			continue
182
-		}
92
+// TestHandle_configChangeTrigger ensures that a config with a config change
93
+// trigger will be reconciled.
94
+func TestHandle_configChangeTrigger(t *testing.T) {
95
+	updated := false
183 96
 
184
-		if !s.changeExpected {
185
-			if updated != nil {
186
-				t.Errorf("unexpected update to version %d: %s", updated.Status.LatestVersion, diff.ObjectReflectDiff(config, updated))
187
-			}
188
-			continue
189
-		}
190
-
191
-		// changeExpected == true
192
-		if updated == nil {
193
-			t.Errorf("expected config to be updated")
194
-			continue
195
-		}
196
-		if e, a := int64(2), updated.Status.LatestVersion; e != a {
197
-			t.Errorf("expected update to latestversion=%d, got %d", e, a)
198
-			continue
199
-		}
200
-
201
-		if updated.Status.Details == nil {
202
-			t.Errorf("expected config change details to be set")
203
-		} else if updated.Status.Details.Causes == nil {
204
-			t.Errorf("expected config change causes to be set")
205
-		} else if updated.Status.Details.Causes[0].Type != deployapi.DeploymentTriggerOnConfigChange {
206
-			t.Errorf("expected config change cause to be set to config change trigger, got %s", updated.Status.Details.Causes[0].Type)
207
-		}
208
-	}
209
-}
210
-
211
-// TestHandle_waitForImageController tests an initial deployment with unresolved image. The config
212
-// change controller should never increment latestVersion, thus trigger a deployment for this config.
213
-func TestHandle_waitForImageController(t *testing.T) {
214 97
 	fake := &testclient.Fake{}
215
-	kFake := &ktestclient.Fake{}
216
-
217
-	fake.PrependReactor("update", "deploymentconfigs/status", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) {
218
-		t.Fatalf("an update should never run before the template image is resolved")
98
+	fake.AddReactor("update", "deploymentconfigs/instantiate", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) {
99
+		updated = true
219 100
 		return true, nil, nil
220 101
 	})
221 102
 
222
-	controller := NewDeploymentTriggerController(dcInformer, streamInformer, fake, kFake, codec)
103
+	controller := NewDeploymentTriggerController(dcInformer, streamInformer, fake, codec)
223 104
 
224 105
 	config := testapi.OkDeploymentConfig(0)
225 106
 	config.Namespace = kapi.NamespaceDefault
226
-	config.Spec.Triggers = []deployapi.DeploymentTriggerPolicy{testapi.OkConfigChangeTrigger(), testapi.OkImageChangeTrigger()}
227
-
107
+	config.Spec.Triggers = []deployapi.DeploymentTriggerPolicy{testapi.OkConfigChangeTrigger()}
228 108
 	if err := controller.Handle(config); err != nil {
229 109
 		t.Fatalf("unexpected error: %v", err)
230 110
 	}
231
-}
232
-
233
-// TestHandle_automaticImageUpdates tests automatic and non-automatic updates
234
-// from image change triggers.
235
-func TestHandle_automaticImageUpdates(t *testing.T) {
236
-	tests := []struct {
237
-		name           string
238
-		auto           bool
239
-		canTrigger     bool
240
-		version        int64
241
-		expectedUpdate bool
242
-	}{
243
-		{
244
-			name:           "initial deployment with unresolved image (auto: true)",
245
-			auto:           true,
246
-			canTrigger:     false,
247
-			version:        0,
248
-			expectedUpdate: false,
249
-		},
250
-		{
251
-			name:           "initial deployment with unresolved image (auto: false)",
252
-			auto:           false,
253
-			canTrigger:     false,
254
-			version:        0,
255
-			expectedUpdate: false,
256
-		},
257
-		{
258
-			name:           "initial deployment with resolved image (auto: true)",
259
-			auto:           true,
260
-			canTrigger:     true,
261
-			version:        0,
262
-			expectedUpdate: true,
263
-		},
264
-		{
265
-			name:           "initial deployment with resolved image (auto: false)",
266
-			auto:           false,
267
-			canTrigger:     true,
268
-			version:        0,
269
-			expectedUpdate: true,
270
-		},
271
-	}
272
-
273
-	for _, test := range tests {
274
-		updated := false
275
-
276
-		fake := &testclient.Fake{}
277
-		kFake := &ktestclient.Fake{}
278
-		fake.PrependReactor("update", "deploymentconfigs/status", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) {
279
-			updated = true
280
-			return true, nil, nil
281
-		})
282
-		kFake.PrependReactor("get", "replicationcontrollers", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) {
283
-			// This will always return no template difference. We test template differences in TestHandle_changeWithTemplateDiff
284
-			config := testapi.OkDeploymentConfig(0)
285
-			deployment, _ := deployutil.MakeDeployment(config, kapi.Codecs.LegacyCodec(deployv1.SchemeGroupVersion))
286
-			return true, deployment, nil
287
-		})
288
-
289
-		controller := NewDeploymentTriggerController(dcInformer, streamInformer, fake, kFake, codec)
290
-
291
-		config := testapi.OkDeploymentConfig(test.version)
292
-		config.Namespace = kapi.NamespaceDefault
293
-		ict := testapi.OkImageChangeTrigger()
294
-		ict.ImageChangeParams.Automatic = test.auto
295
-		if test.canTrigger {
296
-			ict.ImageChangeParams.LastTriggeredImage = testapi.DockerImageReference
297
-		}
298
-		config.Spec.Triggers = []deployapi.DeploymentTriggerPolicy{testapi.OkConfigChangeTrigger(), ict}
299
-
300
-		if err := controller.Handle(config); err != nil {
301
-			t.Errorf("%s: unexpected error: %v", test.name, err)
302
-			continue
303
-		}
304
-
305
-		if test.expectedUpdate != updated {
306
-			t.Errorf("%s: expected update: %t, got update: %t", test.name, test.expectedUpdate, updated)
307
-		}
111
+	if !updated {
112
+		t.Fatalf("expected config to be instantiated")
308 113
 	}
309 114
 }
310 115
 
311
-func TestCanTrigger(t *testing.T) {
312
-	tests := []struct {
313
-		name string
314
-
315
-		config  *deployapi.DeploymentConfig
316
-		decoded *deployapi.DeploymentConfig
116
+// TestHandle_imageChangeTrigger ensures that a config with an image change
117
+// trigger will be reconciled.
118
+func TestHandle_imageChangeTrigger(t *testing.T) {
119
+	updated := false
317 120
 
318
-		expected       bool
319
-		expectedCauses []deployapi.DeploymentCause
320
-	}{
321
-		{
322
-			name: "nil decoded config",
323
-
324
-			config:  testapi.OkDeploymentConfig(1),
325
-			decoded: nil,
326
-
327
-			expected:       false,
328
-			expectedCauses: nil,
329
-		},
330
-		{
331
-			name: "no trigger",
332
-
333
-			config: &deployapi.DeploymentConfig{
334
-				Spec: deployapi.DeploymentConfigSpec{
335
-					Template: testapi.OkPodTemplateChanged(),
336
-				},
337
-				Status: testapi.OkDeploymentConfigStatus(1),
338
-			},
339
-			decoded: &deployapi.DeploymentConfig{
340
-				Spec: deployapi.DeploymentConfigSpec{
341
-					Template: testapi.OkPodTemplate(),
342
-				},
343
-				Status: testapi.OkDeploymentConfigStatus(1),
344
-			},
345
-
346
-			expected:       false,
347
-			expectedCauses: nil,
348
-		},
349
-		{
350
-			name: "config change trigger only",
351
-
352
-			config: &deployapi.DeploymentConfig{
353
-				Spec: deployapi.DeploymentConfigSpec{
354
-					Template: testapi.OkPodTemplateChanged(),
355
-					Triggers: []deployapi.DeploymentTriggerPolicy{
356
-						testapi.OkConfigChangeTrigger(),
357
-					},
358
-				},
359
-				Status: testapi.OkDeploymentConfigStatus(1),
360
-			},
361
-			decoded: &deployapi.DeploymentConfig{
362
-				Spec: deployapi.DeploymentConfigSpec{
363
-					Template: testapi.OkPodTemplate(),
364
-					Triggers: []deployapi.DeploymentTriggerPolicy{
365
-						testapi.OkConfigChangeTrigger(),
366
-					},
367
-				},
368
-				Status: testapi.OkDeploymentConfigStatus(1),
369
-			},
370
-
371
-			expected:       true,
372
-			expectedCauses: testapi.OkConfigChangeDetails().Causes,
373
-		},
374
-		{
375
-			name: "config change trigger only [no change][initial]",
376
-
377
-			config: &deployapi.DeploymentConfig{
378
-				Spec: deployapi.DeploymentConfigSpec{
379
-					Template: testapi.OkPodTemplate(),
380
-					Triggers: []deployapi.DeploymentTriggerPolicy{
381
-						testapi.OkConfigChangeTrigger(),
382
-					},
383
-				},
384
-				Status: testapi.OkDeploymentConfigStatus(0),
385
-			},
386
-			decoded: &deployapi.DeploymentConfig{
387
-				Spec: deployapi.DeploymentConfigSpec{
388
-					Template: testapi.OkPodTemplate(),
389
-					Triggers: []deployapi.DeploymentTriggerPolicy{
390
-						testapi.OkConfigChangeTrigger(),
391
-					},
392
-				},
393
-				Status: testapi.OkDeploymentConfigStatus(0),
394
-			},
395
-
396
-			expected:       true,
397
-			expectedCauses: testapi.OkConfigChangeDetails().Causes,
398
-		},
399
-		{
400
-			name: "config change trigger only [no change]",
401
-
402
-			config: &deployapi.DeploymentConfig{
403
-				Spec: deployapi.DeploymentConfigSpec{
404
-					Template: testapi.OkPodTemplate(),
405
-					Triggers: []deployapi.DeploymentTriggerPolicy{
406
-						testapi.OkConfigChangeTrigger(),
407
-					},
408
-				},
409
-				Status: testapi.OkDeploymentConfigStatus(1),
410
-			},
411
-			decoded: &deployapi.DeploymentConfig{
412
-				Spec: deployapi.DeploymentConfigSpec{
413
-					Template: testapi.OkPodTemplate(),
414
-					Triggers: []deployapi.DeploymentTriggerPolicy{
415
-						testapi.OkConfigChangeTrigger(),
416
-					},
417
-				},
418
-				Status: testapi.OkDeploymentConfigStatus(1),
419
-			},
420
-
421
-			expected:       false,
422
-			expectedCauses: nil,
423
-		},
424
-		{
425
-			name: "image change trigger only [automatic=false]",
426
-
427
-			config: &deployapi.DeploymentConfig{
428
-				Spec: deployapi.DeploymentConfigSpec{
429
-					Template: testapi.OkPodTemplateChanged(), // Irrelevant change
430
-					Triggers: []deployapi.DeploymentTriggerPolicy{
431
-						testapi.OkNonAutomaticICT(), // Image still to be resolved but it's false anyway
432
-					},
433
-				},
434
-			},
435
-			decoded: &deployapi.DeploymentConfig{
436
-				Spec: deployapi.DeploymentConfigSpec{
437
-					Template: testapi.OkPodTemplate(),
438
-					Triggers: []deployapi.DeploymentTriggerPolicy{
439
-						testapi.OkNonAutomaticICT(),
440
-					},
441
-				},
442
-			},
443
-
444
-			expected:       false,
445
-			expectedCauses: nil,
446
-		},
447
-		{
448
-			name: "image change trigger only [automatic=false][image triggered]",
449
-
450
-			config: &deployapi.DeploymentConfig{
451
-				Spec: deployapi.DeploymentConfigSpec{
452
-					Template: testapi.OkPodTemplateChanged(), // Image has been updated in the template but automatic=false
453
-					Triggers: []deployapi.DeploymentTriggerPolicy{
454
-						testapi.OkTriggeredNonAutomatic(),
455
-					},
456
-				},
457
-			},
458
-			decoded: &deployapi.DeploymentConfig{
459
-				Spec: deployapi.DeploymentConfigSpec{
460
-					Template: testapi.OkPodTemplate(),
461
-					Triggers: []deployapi.DeploymentTriggerPolicy{
462
-						testapi.OkNonAutomaticICT(),
463
-					},
464
-				},
465
-			},
466
-
467
-			expected:       false,
468
-			expectedCauses: nil,
469
-		},
470
-		{
471
-			name: "image change trigger only [automatic=true]",
472
-
473
-			config: &deployapi.DeploymentConfig{
474
-				Spec: deployapi.DeploymentConfigSpec{
475
-					Template: testapi.OkPodTemplateChanged(),
476
-					Triggers: []deployapi.DeploymentTriggerPolicy{
477
-						testapi.OkTriggeredImageChange(),
478
-					},
479
-				},
480
-			},
481
-			decoded: &deployapi.DeploymentConfig{
482
-				Spec: deployapi.DeploymentConfigSpec{
483
-					Template: testapi.OkPodTemplate(),
484
-					Triggers: []deployapi.DeploymentTriggerPolicy{
485
-						testapi.OkImageChangeTrigger(),
486
-					},
487
-				},
488
-			},
489
-
490
-			expected:       true,
491
-			expectedCauses: testapi.OkImageChangeDetails().Causes,
492
-		},
493
-		{
494
-			name: "image change trigger only [automatic=true][no change]",
495
-
496
-			config: &deployapi.DeploymentConfig{
497
-				Spec: deployapi.DeploymentConfigSpec{
498
-					Template: testapi.OkPodTemplate(),
499
-					Triggers: []deployapi.DeploymentTriggerPolicy{
500
-						testapi.OkImageChangeTrigger(),
501
-					},
502
-				},
503
-			},
504
-			decoded: &deployapi.DeploymentConfig{
505
-				Spec: deployapi.DeploymentConfigSpec{
506
-					Template: testapi.OkPodTemplate(),
507
-					Triggers: []deployapi.DeploymentTriggerPolicy{
508
-						testapi.OkImageChangeTrigger(),
509
-					},
510
-				},
511
-			},
512
-
513
-			expected:       false,
514
-			expectedCauses: nil,
515
-		},
516
-		{
517
-			name: "config change and image change trigger [automatic=false][initial][image resolved]",
518
-
519
-			config: &deployapi.DeploymentConfig{
520
-				Spec: deployapi.DeploymentConfigSpec{
521
-					Template: testapi.OkPodTemplateChanged(),
522
-					Triggers: []deployapi.DeploymentTriggerPolicy{
523
-						testapi.OkConfigChangeTrigger(),
524
-						testapi.OkTriggeredNonAutomatic(),
525
-					},
526
-				},
527
-				Status: testapi.OkDeploymentConfigStatus(0),
528
-			},
529
-			decoded: &deployapi.DeploymentConfig{
530
-				Spec: deployapi.DeploymentConfigSpec{
531
-					Template: testapi.OkPodTemplate(),
532
-					Triggers: []deployapi.DeploymentTriggerPolicy{
533
-						testapi.OkConfigChangeTrigger(),
534
-						testapi.OkNonAutomaticICT(),
535
-					},
536
-				},
537
-				Status: testapi.OkDeploymentConfigStatus(0),
538
-			},
539
-
540
-			expected:       true,
541
-			expectedCauses: testapi.OkConfigChangeDetails().Causes,
542
-		},
543
-		{
544
-			name: "config change and image change trigger [automatic=false][initial]",
545
-
546
-			config: &deployapi.DeploymentConfig{
547
-				Spec: deployapi.DeploymentConfigSpec{
548
-					Template: testapi.OkPodTemplate(),
549
-					Triggers: []deployapi.DeploymentTriggerPolicy{
550
-						testapi.OkConfigChangeTrigger(),
551
-						testapi.OkNonAutomaticICT(), // Image is not resolved yet
552
-					},
553
-				},
554
-				Status: testapi.OkDeploymentConfigStatus(0),
555
-			},
556
-			decoded: &deployapi.DeploymentConfig{
557
-				Spec: deployapi.DeploymentConfigSpec{
558
-					Template: testapi.OkPodTemplate(),
559
-					Triggers: []deployapi.DeploymentTriggerPolicy{
560
-						testapi.OkConfigChangeTrigger(),
561
-						testapi.OkNonAutomaticICT(),
562
-					},
563
-				},
564
-				Status: testapi.OkDeploymentConfigStatus(0),
565
-			},
566
-
567
-			expected:       false,
568
-			expectedCauses: nil,
569
-		},
570
-		{
571
-			name: "config change and image change trigger [automatic=true][initial]",
572
-
573
-			config: &deployapi.DeploymentConfig{
574
-				Spec: deployapi.DeploymentConfigSpec{
575
-					Template: testapi.OkPodTemplateChanged(), // Pod template has changed but the image in the template is yet to be updated
576
-					Triggers: []deployapi.DeploymentTriggerPolicy{
577
-						testapi.OkConfigChangeTrigger(),
578
-						testapi.OkImageChangeTrigger(),
579
-					},
580
-				},
581
-				Status: testapi.OkDeploymentConfigStatus(0),
582
-			},
583
-			decoded: &deployapi.DeploymentConfig{
584
-				Spec: deployapi.DeploymentConfigSpec{
585
-					Template: testapi.OkPodTemplate(),
586
-					Triggers: []deployapi.DeploymentTriggerPolicy{
587
-						testapi.OkConfigChangeTrigger(),
588
-						testapi.OkImageChangeTrigger(),
589
-					},
590
-				},
591
-				Status: testapi.OkDeploymentConfigStatus(0),
592
-			},
593
-
594
-			expected:       false,
595
-			expectedCauses: nil,
596
-		},
597
-		{
598
-			name: "config change and image change trigger [automatic=true][initial][image triggered]",
599
-
600
-			config: &deployapi.DeploymentConfig{
601
-				Spec: deployapi.DeploymentConfigSpec{
602
-					Template: testapi.OkPodTemplateChanged(),
603
-					Triggers: []deployapi.DeploymentTriggerPolicy{
604
-						testapi.OkConfigChangeTrigger(),
605
-						testapi.OkTriggeredImageChange(),
606
-					},
607
-				},
608
-				Status: testapi.OkDeploymentConfigStatus(0),
609
-			},
610
-			decoded: &deployapi.DeploymentConfig{
611
-				Spec: deployapi.DeploymentConfigSpec{
612
-					Template: testapi.OkPodTemplate(),
613
-					Triggers: []deployapi.DeploymentTriggerPolicy{
614
-						testapi.OkConfigChangeTrigger(),
615
-						testapi.OkImageChangeTrigger(),
616
-					},
617
-				},
618
-				Status: testapi.OkDeploymentConfigStatus(0),
619
-			},
620
-
621
-			expected:       true,
622
-			expectedCauses: testapi.OkImageChangeDetails().Causes,
623
-		},
624
-		{
625
-			name: "config change and image change trigger [automatic=true][no change]",
121
+	fake := &testclient.Fake{}
122
+	fake.AddReactor("update", "deploymentconfigs/instantiate", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) {
123
+		updated = true
124
+		return true, nil, nil
125
+	})
626 126
 
627
-			config: &deployapi.DeploymentConfig{
628
-				Spec: deployapi.DeploymentConfigSpec{
629
-					Template: testapi.OkPodTemplate(),
630
-					Triggers: []deployapi.DeploymentTriggerPolicy{
631
-						testapi.OkConfigChangeTrigger(),
632
-						testapi.OkImageChangeTrigger(),
633
-					},
634
-				},
635
-				Status: testapi.OkDeploymentConfigStatus(1),
636
-			},
637
-			decoded: &deployapi.DeploymentConfig{
638
-				Spec: deployapi.DeploymentConfigSpec{
639
-					Template: testapi.OkPodTemplate(),
640
-					Triggers: []deployapi.DeploymentTriggerPolicy{
641
-						testapi.OkConfigChangeTrigger(),
642
-						testapi.OkImageChangeTrigger(),
643
-					},
644
-				},
645
-				Status: testapi.OkDeploymentConfigStatus(1),
646
-			},
127
+	controller := NewDeploymentTriggerController(dcInformer, streamInformer, fake, codec)
647 128
 
648
-			expected:       false,
649
-			expectedCauses: nil,
650
-		},
129
+	config := testapi.OkDeploymentConfig(0)
130
+	config.Namespace = kapi.NamespaceDefault
131
+	config.Spec.Triggers = []deployapi.DeploymentTriggerPolicy{testapi.OkImageChangeTrigger()}
132
+	if err := controller.Handle(config); err != nil {
133
+		t.Fatalf("unexpected error: %v", err)
651 134
 	}
652
-
653
-	for _, test := range tests {
654
-		got, gotCauses := canTrigger(test.config, test.decoded)
655
-		if test.expected != got {
656
-			t.Errorf("%s: expected to trigger: %t, got: %t", test.name, test.expected, got)
657
-			continue
658
-		}
659
-		if !kapi.Semantic.DeepEqual(test.expectedCauses, gotCauses) {
660
-			t.Errorf("%s: expected causes:\n%#v\ngot:\n%#v", test.name, test.expectedCauses, gotCauses)
661
-		}
135
+	if !updated {
136
+		t.Fatalf("expected config to be instantiated")
662 137
 	}
663 138
 }
... ...
@@ -5,7 +5,6 @@ import (
5 5
 
6 6
 	"github.com/golang/glog"
7 7
 
8
-	kclient "k8s.io/kubernetes/pkg/client/unversioned"
9 8
 	kcontroller "k8s.io/kubernetes/pkg/controller"
10 9
 	"k8s.io/kubernetes/pkg/controller/framework"
11 10
 	"k8s.io/kubernetes/pkg/runtime"
... ...
@@ -29,10 +28,9 @@ const (
29 29
 )
30 30
 
31 31
 // NewDeploymentTriggerController returns a new DeploymentTriggerController.
32
-func NewDeploymentTriggerController(dcInformer, streamInformer framework.SharedIndexInformer, oc osclient.Interface, kc kclient.Interface, codec runtime.Codec) *DeploymentTriggerController {
32
+func NewDeploymentTriggerController(dcInformer, streamInformer framework.SharedIndexInformer, oc osclient.Interface, codec runtime.Codec) *DeploymentTriggerController {
33 33
 	c := &DeploymentTriggerController{
34 34
 		dn: oc,
35
-		rn: kc,
36 35
 
37 36
 		queue: workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()),
38 37
 
... ...
@@ -46,11 +44,10 @@ func NewDeploymentTriggerController(dcInformer, streamInformer framework.SharedI
46 46
 	})
47 47
 	c.dcStoreSynced = dcInformer.HasSynced
48 48
 
49
-	// TODO: Re-enable in https://github.com/openshift/origin/pull/9349
50
-	// streamInformer.AddEventHandler(framework.ResourceEventHandlerFuncs{
51
-	// AddFunc:    c.addImageStream,
52
-	// UpdateFunc: c.updateImageStream,
53
-	// })
49
+	streamInformer.AddEventHandler(framework.ResourceEventHandlerFuncs{
50
+		AddFunc:    c.addImageStream,
51
+		UpdateFunc: c.updateImageStream,
52
+	})
54 53
 
55 54
 	return c
56 55
 }
... ...
@@ -92,6 +89,9 @@ func (c *DeploymentTriggerController) waitForSyncedStore(ready chan<- struct{},
92 92
 
93 93
 func (c *DeploymentTriggerController) addDeploymentConfig(obj interface{}) {
94 94
 	dc := obj.(*deployapi.DeploymentConfig)
95
+	if len(dc.Spec.Triggers) == 0 || dc.Spec.Paused {
96
+		return
97
+	}
95 98
 	c.enqueueDeploymentConfig(dc)
96 99
 }
97 100
 
... ...
@@ -103,6 +103,10 @@ func (c *DeploymentTriggerController) updateDeploymentConfig(old, cur interface{
103 103
 		return
104 104
 	}
105 105
 
106
+	if len(newDc.Spec.Triggers) == 0 || newDc.Spec.Paused {
107
+		return
108
+	}
109
+
106 110
 	c.enqueueDeploymentConfig(newDc)
107 111
 }
108 112
 
109 113
deleted file mode 100644
... ...
@@ -1,161 +0,0 @@
1
-package imagechange
2
-
3
-import (
4
-	"fmt"
5
-
6
-	"github.com/golang/glog"
7
-
8
-	utilruntime "k8s.io/kubernetes/pkg/util/runtime"
9
-	"k8s.io/kubernetes/pkg/util/sets"
10
-	"k8s.io/kubernetes/pkg/util/workqueue"
11
-
12
-	"github.com/openshift/origin/pkg/client"
13
-	oscache "github.com/openshift/origin/pkg/client/cache"
14
-	deployapi "github.com/openshift/origin/pkg/deploy/api"
15
-	deployutil "github.com/openshift/origin/pkg/deploy/util"
16
-	imageapi "github.com/openshift/origin/pkg/image/api"
17
-)
18
-
19
-// ImageChangeController increments the version of a deployment config which has an image
20
-// change trigger when a tag update to a triggered ImageStream is detected.
21
-//
22
-// Use the ImageChangeControllerFactory to create this controller.
23
-type ImageChangeController struct {
24
-	dn client.DeploymentConfigsNamespacer
25
-
26
-	// queue contains deployment configs that need to be synced.
27
-	queue workqueue.RateLimitingInterface
28
-
29
-	// streamLister provides a local cache for image streams.
30
-	streamLister oscache.StoreToImageStreamLister
31
-	// dcLister provides a local cache for deployment configs.
32
-	dcLister oscache.StoreToDeploymentConfigLister
33
-
34
-	// streamStoreSynced makes sure the stream store is synced before reconcling any image stream.
35
-	streamStoreSynced func() bool
36
-	// dcStoreSynced makes sure the dc store is synced before reconcling any image stream.
37
-	dcStoreSynced func() bool
38
-}
39
-
40
-// fatalError is an error which can't be retried.
41
-type fatalError string
42
-
43
-func (e fatalError) Error() string {
44
-	return fmt.Sprintf("fatal error handling image stream: %s", string(e))
45
-}
46
-
47
-// Handle processes image change triggers associated with imagestream.
48
-func (c *ImageChangeController) Handle(stream *imageapi.ImageStream) error {
49
-	configs, err := c.dcLister.GetConfigsForImageStream(stream)
50
-	if err != nil {
51
-		return fmt.Errorf("couldn't get list of deployment configs while handling image stream %q: %v", imageapi.LabelForStream(stream), err)
52
-	}
53
-
54
-	// Find any configs which should be updated based on the new image state
55
-	var configsToUpdate []*deployapi.DeploymentConfig
56
-	for n, config := range configs {
57
-		glog.V(4).Infof("Detecting image changes for deployment config %q", deployutil.LabelForDeploymentConfig(config))
58
-		hasImageChange := false
59
-
60
-		for j := range config.Spec.Triggers {
61
-			// because config can be copied during this loop, make sure we load from config for subsequent loops
62
-			trigger := config.Spec.Triggers[j]
63
-			params := trigger.ImageChangeParams
64
-
65
-			// Only automatic image change triggers should fire
66
-			if trigger.Type != deployapi.DeploymentTriggerOnImageChange {
67
-				continue
68
-			}
69
-
70
-			// All initial deployments should have their images resolved in order to
71
-			// be able to work and not try to pull non-existent images from DockerHub.
72
-			// Deployments with automatic set to false that have been deployed at least
73
-			// once shouldn't have their images updated.
74
-			if (!params.Automatic || config.Spec.Paused) && len(params.LastTriggeredImage) > 0 {
75
-				continue
76
-			}
77
-
78
-			// Check if the image stream matches the trigger
79
-			if !triggerMatchesImage(config, params, stream) {
80
-				continue
81
-			}
82
-
83
-			_, tag, ok := imageapi.SplitImageStreamTag(params.From.Name)
84
-			if !ok {
85
-				glog.Warningf("Invalid image stream tag %q in %q", params.From.Name, deployutil.LabelForDeploymentConfig(config))
86
-				continue
87
-			}
88
-
89
-			// Find the latest tag event for the trigger tag
90
-			latestEvent := imageapi.LatestTaggedImage(stream, tag)
91
-			if latestEvent == nil {
92
-				glog.V(5).Infof("Couldn't find latest tag event for tag %q in image stream %q", tag, imageapi.LabelForStream(stream))
93
-				continue
94
-			}
95
-
96
-			// Ensure a change occurred
97
-			if len(latestEvent.DockerImageReference) == 0 || latestEvent.DockerImageReference == params.LastTriggeredImage {
98
-				glog.V(4).Infof("No image changes for deployment config %q were detected", deployutil.LabelForDeploymentConfig(config))
99
-				continue
100
-			}
101
-
102
-			names := sets.NewString(params.ContainerNames...)
103
-			for i := range config.Spec.Template.Spec.Containers {
104
-				container := &config.Spec.Template.Spec.Containers[i]
105
-				if !names.Has(container.Name) {
106
-					continue
107
-				}
108
-
109
-				if !hasImageChange {
110
-					// create a copy prior to mutation
111
-					result, err := deployutil.DeploymentConfigDeepCopy(configs[n])
112
-					if err != nil {
113
-						utilruntime.HandleError(err)
114
-						continue
115
-					}
116
-					configs[n] = result
117
-					container = &configs[n].Spec.Template.Spec.Containers[i]
118
-					params = configs[n].Spec.Triggers[j].ImageChangeParams
119
-				}
120
-
121
-				// Update the image
122
-				container.Image = latestEvent.DockerImageReference
123
-				// Log the last triggered image ID
124
-				params.LastTriggeredImage = latestEvent.DockerImageReference
125
-				hasImageChange = true
126
-			}
127
-		}
128
-
129
-		if hasImageChange {
130
-			configsToUpdate = append(configsToUpdate, configs[n])
131
-		}
132
-	}
133
-
134
-	// Attempt to regenerate all configs which may contain image updates
135
-	anyFailed := false
136
-	for _, config := range configsToUpdate {
137
-		if _, err := c.dn.DeploymentConfigs(config.Namespace).Update(config); err != nil {
138
-			utilruntime.HandleError(err)
139
-			anyFailed = true
140
-		} else {
141
-			glog.V(4).Infof("Updated deployment config %q for trigger on image stream %q",
142
-				deployutil.LabelForDeploymentConfig(config), imageapi.LabelForStream(stream))
143
-		}
144
-	}
145
-
146
-	if anyFailed {
147
-		return fmt.Errorf("couldn't update some deployment configs for trigger on image stream %q", imageapi.LabelForStream(stream))
148
-	}
149
-
150
-	return nil
151
-}
152
-
153
-// triggerMatchesImage decides whether a given trigger for config matches the provided image stream.
154
-func triggerMatchesImage(config *deployapi.DeploymentConfig, params *deployapi.DeploymentTriggerImageChangeParams, stream *imageapi.ImageStream) bool {
155
-	namespace := params.From.Namespace
156
-	if len(namespace) == 0 {
157
-		namespace = config.Namespace
158
-	}
159
-	name, _, ok := imageapi.SplitImageStreamTag(params.From.Name)
160
-	return stream.Namespace == namespace && stream.Name == name && ok
161
-}
162 1
deleted file mode 100644
... ...
@@ -1,242 +0,0 @@
1
-package imagechange
2
-
3
-import (
4
-	"flag"
5
-	"testing"
6
-	"time"
7
-
8
-	kapi "k8s.io/kubernetes/pkg/api"
9
-	"k8s.io/kubernetes/pkg/client/cache"
10
-	ktestclient "k8s.io/kubernetes/pkg/client/unversioned/testclient"
11
-	"k8s.io/kubernetes/pkg/controller/framework"
12
-	"k8s.io/kubernetes/pkg/runtime"
13
-
14
-	oscache "github.com/openshift/origin/pkg/client/cache"
15
-	"github.com/openshift/origin/pkg/client/testclient"
16
-	deployapi "github.com/openshift/origin/pkg/deploy/api"
17
-	testapi "github.com/openshift/origin/pkg/deploy/api/test"
18
-	imageapi "github.com/openshift/origin/pkg/image/api"
19
-)
20
-
21
-var (
22
-	dcInformer = framework.NewSharedIndexInformer(
23
-		&cache.ListWatch{},
24
-		&deployapi.DeploymentConfig{},
25
-		2*time.Minute,
26
-		cache.Indexers{oscache.ImageStreamReferenceIndex: oscache.ImageStreamReferenceIndexFunc},
27
-	)
28
-	streamInformer = framework.NewSharedIndexInformer(
29
-		&cache.ListWatch{},
30
-		&imageapi.ImageStream{},
31
-		2*time.Minute,
32
-		cache.Indexers{},
33
-	)
34
-)
35
-
36
-func init() {
37
-	flag.Set("v", "5")
38
-}
39
-
40
-func makeStream(name, tag, dir, image string) *imageapi.ImageStream {
41
-	return &imageapi.ImageStream{
42
-		ObjectMeta: kapi.ObjectMeta{Name: name, Namespace: kapi.NamespaceDefault},
43
-		Status: imageapi.ImageStreamStatus{
44
-			Tags: map[string]imageapi.TagEventList{
45
-				tag: {
46
-					Items: []imageapi.TagEvent{
47
-						{
48
-							DockerImageReference: dir,
49
-							Image:                image,
50
-						},
51
-					},
52
-				},
53
-			},
54
-		},
55
-	}
56
-}
57
-
58
-// TestHandle_changeForNonAutomaticTag ensures that an image update for which
59
-// there is a matching trigger results in a no-op due to the trigger's
60
-// automatic flag being set to false.
61
-func TestHandle_changeForNonAutomaticTag(t *testing.T) {
62
-	fake := &testclient.Fake{}
63
-	fake.AddReactor("update", "deploymentconfigs", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) {
64
-		t.Fatalf("unexpected deploymentconfig update")
65
-		return true, nil, nil
66
-	})
67
-
68
-	controller := NewImageChangeController(dcInformer, streamInformer, fake)
69
-
70
-	config := testapi.OkDeploymentConfig(1)
71
-	config.Namespace = kapi.NamespaceDefault
72
-	config.Spec.Triggers[0].ImageChangeParams.Automatic = false
73
-	// The image has been resolved at least once before.
74
-	config.Spec.Triggers[0].ImageChangeParams.LastTriggeredImage = testapi.DockerImageReference
75
-	controller.dcLister.Add(config)
76
-
77
-	// verify no-op
78
-	tagUpdate := makeStream(testapi.ImageStreamName, imageapi.DefaultImageTag, testapi.DockerImageReference, testapi.ImageID)
79
-
80
-	if err := controller.Handle(tagUpdate); err != nil {
81
-		t.Fatalf("unexpected err: %v", err)
82
-	}
83
-
84
-	if len(fake.Actions()) > 0 {
85
-		t.Fatalf("unexpected actions: %v", fake.Actions())
86
-	}
87
-}
88
-
89
-// TestHandle_changeForInitialNonAutomaticDeployment ensures that an image update for which
90
-// there is a matching trigger will actually update the deployment config if it hasn't been
91
-// deployed before.
92
-func TestHandle_changeForInitialNonAutomaticDeployment(t *testing.T) {
93
-	fake := &testclient.Fake{}
94
-
95
-	controller := NewImageChangeController(dcInformer, streamInformer, fake)
96
-
97
-	config := testapi.OkDeploymentConfig(0)
98
-	config.Namespace = kapi.NamespaceDefault
99
-	config.Spec.Triggers[0].ImageChangeParams.Automatic = false
100
-	config.Spec.Triggers[0].ImageChangeParams.From.Namespace = kapi.NamespaceDefault
101
-	controller.dcLister.Indexer.Add(config)
102
-
103
-	tagUpdate := makeStream(testapi.ImageStreamName, imageapi.DefaultImageTag, testapi.DockerImageReference, testapi.ImageID)
104
-
105
-	if err := controller.Handle(tagUpdate); err != nil {
106
-		t.Fatalf("unexpected err: %v", err)
107
-	}
108
-
109
-	actions := fake.Actions()
110
-	if len(actions) != 1 {
111
-		t.Fatalf("unexpected amount of actions: expected 1, got %d (%v)", len(actions), actions)
112
-	}
113
-	if !actions[0].Matches("update", "deploymentconfigs") {
114
-		t.Fatalf("unexpected action: %v", actions[0])
115
-	}
116
-}
117
-
118
-// TestHandle_changeForUnregisteredTag ensures that an image update for which
119
-// there is a matching trigger results in a no-op due to the tag specified on
120
-// the trigger not matching the tags defined on the image stream.
121
-func TestHandle_changeForUnregisteredTag(t *testing.T) {
122
-	fake := &testclient.Fake{}
123
-	fake.AddReactor("update", "deploymentconfigs", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) {
124
-		t.Fatalf("unexpected deploymentconfig update")
125
-		return true, nil, nil
126
-	})
127
-
128
-	controller := NewImageChangeController(dcInformer, streamInformer, fake)
129
-
130
-	config := testapi.OkDeploymentConfig(0)
131
-	controller.dcLister.Add(&config)
132
-
133
-	// verify no-op
134
-	tagUpdate := makeStream(testapi.ImageStreamName, "unrecognized", testapi.DockerImageReference, testapi.ImageID)
135
-
136
-	if err := controller.Handle(tagUpdate); err != nil {
137
-		t.Fatalf("unexpected err: %v", err)
138
-	}
139
-
140
-	if len(fake.Actions()) > 0 {
141
-		t.Fatalf("unexpected actions: %v", fake.Actions())
142
-	}
143
-}
144
-
145
-// TestHandle_matchScenarios comprehensively tests trigger definitions against
146
-// image stream updates to ensure that the image change triggers match (or don't
147
-// match) properly.
148
-func TestHandle_matchScenarios(t *testing.T) {
149
-	tests := []struct {
150
-		name string
151
-
152
-		param   *deployapi.DeploymentTriggerImageChangeParams
153
-		matches bool
154
-	}{
155
-		{
156
-			name: "automatic=true, initial trigger",
157
-
158
-			param: &deployapi.DeploymentTriggerImageChangeParams{
159
-				Automatic:          true,
160
-				ContainerNames:     []string{"container1"},
161
-				From:               kapi.ObjectReference{Namespace: kapi.NamespaceDefault, Name: imageapi.JoinImageStreamTag(testapi.ImageStreamName, imageapi.DefaultImageTag)},
162
-				LastTriggeredImage: "",
163
-			},
164
-			matches: true,
165
-		},
166
-		{
167
-			name: "automatic=false, initial trigger",
168
-
169
-			param: &deployapi.DeploymentTriggerImageChangeParams{
170
-				Automatic:          false,
171
-				ContainerNames:     []string{"container1"},
172
-				From:               kapi.ObjectReference{Namespace: kapi.NamespaceDefault, Name: imageapi.JoinImageStreamTag(testapi.ImageStreamName, imageapi.DefaultImageTag)},
173
-				LastTriggeredImage: "",
174
-			},
175
-			matches: true,
176
-		},
177
-		{
178
-			name: "(no-op) automatic=false, already triggered",
179
-
180
-			param: &deployapi.DeploymentTriggerImageChangeParams{
181
-				Automatic:          false,
182
-				ContainerNames:     []string{"container1"},
183
-				From:               kapi.ObjectReference{Namespace: kapi.NamespaceDefault, Name: imageapi.JoinImageStreamTag(testapi.ImageStreamName, imageapi.DefaultImageTag)},
184
-				LastTriggeredImage: testapi.DockerImageReference,
185
-			},
186
-			matches: false,
187
-		},
188
-		{
189
-			name: "(no-op) automatic=true, image is already deployed",
190
-
191
-			param: &deployapi.DeploymentTriggerImageChangeParams{
192
-				Automatic:          true,
193
-				ContainerNames:     []string{"container1"},
194
-				From:               kapi.ObjectReference{Name: imageapi.JoinImageStreamTag(testapi.ImageStreamName, imageapi.DefaultImageTag)},
195
-				LastTriggeredImage: testapi.DockerImageReference,
196
-			},
197
-			matches: false,
198
-		},
199
-		{
200
-			name: "(no-op) trigger doesn't match the stream",
201
-
202
-			param: &deployapi.DeploymentTriggerImageChangeParams{
203
-				Automatic:          true,
204
-				ContainerNames:     []string{"container1"},
205
-				From:               kapi.ObjectReference{Namespace: kapi.NamespaceDefault, Name: imageapi.JoinImageStreamTag("other-stream", imageapi.DefaultImageTag)},
206
-				LastTriggeredImage: "",
207
-			},
208
-			matches: false,
209
-		},
210
-	}
211
-
212
-	for _, test := range tests {
213
-		updated := false
214
-
215
-		fake := &testclient.Fake{}
216
-		fake.AddReactor("update", "deploymentconfigs", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) {
217
-			if !test.matches {
218
-				t.Fatal("unexpected deploymentconfig update")
219
-			}
220
-			updated = true
221
-			return true, nil, nil
222
-		})
223
-
224
-		controller := NewImageChangeController(dcInformer, streamInformer, fake)
225
-
226
-		config := testapi.OkDeploymentConfig(1)
227
-		config.Namespace = kapi.NamespaceDefault
228
-		config.Spec.Triggers = []deployapi.DeploymentTriggerPolicy{{Type: deployapi.DeploymentTriggerOnImageChange, ImageChangeParams: test.param}}
229
-		controller.dcLister.Add(config)
230
-
231
-		t.Logf("running test %q", test.name)
232
-		stream := makeStream(testapi.ImageStreamName, imageapi.DefaultImageTag, testapi.DockerImageReference, testapi.ImageID)
233
-		if err := controller.Handle(stream); err != nil {
234
-			t.Fatalf("unexpected error: %v", err)
235
-		}
236
-
237
-		// assert updates occurred
238
-		if test.matches && !updated {
239
-			t.Fatal("expected an update")
240
-		}
241
-	}
242
-}
243 1
deleted file mode 100644
... ...
@@ -1,184 +0,0 @@
1
-package imagechange
2
-
3
-import (
4
-	"time"
5
-
6
-	"github.com/golang/glog"
7
-
8
-	kcontroller "k8s.io/kubernetes/pkg/controller"
9
-	"k8s.io/kubernetes/pkg/controller/framework"
10
-	utilruntime "k8s.io/kubernetes/pkg/util/runtime"
11
-	"k8s.io/kubernetes/pkg/util/wait"
12
-	"k8s.io/kubernetes/pkg/util/workqueue"
13
-
14
-	osclient "github.com/openshift/origin/pkg/client"
15
-	deployapi "github.com/openshift/origin/pkg/deploy/api"
16
-	deployutil "github.com/openshift/origin/pkg/deploy/util"
17
-	imageapi "github.com/openshift/origin/pkg/image/api"
18
-)
19
-
20
-const (
21
-	// We must avoid creating processing image stream until the deployment config and image
22
-	// stream stores have synced.
23
-	storeSyncedPollPeriod = 100 * time.Millisecond
24
-	// MaxRetries is the number of times an image stream will be retried before it is dropped
25
-	// out of the queue.
26
-	MaxRetries = 5
27
-)
28
-
29
-// NewImageChangeController returns a new ImageChangeController.
30
-func NewImageChangeController(dcInformer, streamInformer framework.SharedIndexInformer, oc osclient.Interface) *ImageChangeController {
31
-	c := &ImageChangeController{
32
-		dn: oc,
33
-
34
-		queue: workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()),
35
-	}
36
-
37
-	c.streamLister.Indexer = streamInformer.GetIndexer()
38
-	streamInformer.AddEventHandler(framework.ResourceEventHandlerFuncs{
39
-		AddFunc:    c.addImageStream,
40
-		UpdateFunc: c.updateImageStream,
41
-	})
42
-	c.streamStoreSynced = streamInformer.HasSynced
43
-
44
-	c.dcLister.Indexer = dcInformer.GetIndexer()
45
-	dcInformer.AddEventHandler(framework.ResourceEventHandlerFuncs{
46
-		AddFunc:    c.addDeploymentConfig,
47
-		UpdateFunc: c.updateDeploymentConfig,
48
-	})
49
-	c.dcStoreSynced = dcInformer.HasSynced
50
-
51
-	return c
52
-}
53
-
54
-// Run begins watching and syncing.
55
-func (c *ImageChangeController) Run(workers int, stopCh <-chan struct{}) {
56
-	defer utilruntime.HandleCrash()
57
-
58
-	// Wait for the dc store to sync before starting any work in this controller.
59
-	ready := make(chan struct{})
60
-	go c.waitForSyncedStore(ready, stopCh)
61
-	select {
62
-	case <-ready:
63
-	case <-stopCh:
64
-		return
65
-	}
66
-
67
-	for i := 0; i < workers; i++ {
68
-		go wait.Until(c.worker, time.Second, stopCh)
69
-	}
70
-	<-stopCh
71
-	glog.Infof("Shutting down image change controller")
72
-	c.queue.ShutDown()
73
-}
74
-
75
-func (c *ImageChangeController) waitForSyncedStore(ready chan<- struct{}, stopCh <-chan struct{}) {
76
-	defer utilruntime.HandleCrash()
77
-
78
-	for !c.streamStoreSynced() || !c.dcStoreSynced() {
79
-		glog.V(4).Infof("Waiting for the image stream and deployment config caches to sync before starting the image change controller workers")
80
-		select {
81
-		case <-time.After(storeSyncedPollPeriod):
82
-		case <-stopCh:
83
-			return
84
-		}
85
-	}
86
-	close(ready)
87
-}
88
-
89
-func (c *ImageChangeController) addImageStream(obj interface{}) {
90
-	stream := obj.(*imageapi.ImageStream)
91
-	c.enqueueImageStream(stream)
92
-}
93
-
94
-func (c *ImageChangeController) updateImageStream(old, cur interface{}) {
95
-	// A periodic relist will send update events for all known streams.
96
-	newStream := cur.(*imageapi.ImageStream)
97
-	oldStream := old.(*imageapi.ImageStream)
98
-	if newStream.ResourceVersion == oldStream.ResourceVersion {
99
-		return
100
-	}
101
-
102
-	c.enqueueImageStream(newStream)
103
-}
104
-
105
-// addDeploymentConfig is used for making sure that new deployment configs with triggers pointing
106
-// to existing images will be deployed as soon as they are created.
107
-func (c *ImageChangeController) addDeploymentConfig(obj interface{}) {
108
-	dc := obj.(*deployapi.DeploymentConfig)
109
-	for _, stream := range c.streamLister.GetStreamsForConfig(dc) {
110
-		glog.V(4).Infof("Reconciling stream %q for config %q\n", imageapi.LabelForStream(stream), deployutil.LabelForDeploymentConfig(dc))
111
-		c.enqueueImageStream(stream)
112
-	}
113
-}
114
-
115
-// updateDeploymentConfig is used for making sure that deployment configs with new triggers
116
-// pointing to existing images will be deployed as soon as they are updated.
117
-func (c *ImageChangeController) updateDeploymentConfig(old, cur interface{}) {
118
-	// A periodic relist will send update events for all known configs.
119
-	newDc := cur.(*deployapi.DeploymentConfig)
120
-	oldDc := old.(*deployapi.DeploymentConfig)
121
-	if newDc.ResourceVersion == oldDc.ResourceVersion {
122
-		return
123
-	}
124
-
125
-	for _, stream := range c.streamLister.GetStreamsForConfig(newDc) {
126
-		glog.V(4).Infof("Reconciling stream %q for config %q\n", imageapi.LabelForStream(stream), deployutil.LabelForDeploymentConfig(newDc))
127
-		c.enqueueImageStream(stream)
128
-	}
129
-}
130
-
131
-func (c *ImageChangeController) enqueueImageStream(stream *imageapi.ImageStream) {
132
-	key, err := kcontroller.KeyFunc(stream)
133
-	if err != nil {
134
-		glog.Errorf("Couldn't get key for object %+v: %v", stream, err)
135
-		return
136
-	}
137
-	c.queue.Add(key)
138
-}
139
-
140
-func (c *ImageChangeController) worker() {
141
-	for {
142
-		if quit := c.work(); quit {
143
-			return
144
-		}
145
-	}
146
-}
147
-
148
-func (c *ImageChangeController) work() bool {
149
-	key, quit := c.queue.Get()
150
-	if quit {
151
-		return true
152
-	}
153
-	defer c.queue.Done(key)
154
-
155
-	stream, err := c.getByKey(key.(string))
156
-	if err != nil {
157
-		glog.Error(err.Error())
158
-	}
159
-
160
-	if stream == nil {
161
-		return false
162
-	}
163
-
164
-	if err := c.Handle(stream); err != nil {
165
-		utilruntime.HandleError(err)
166
-	}
167
-
168
-	return false
169
-}
170
-
171
-func (c *ImageChangeController) getByKey(key string) (*imageapi.ImageStream, error) {
172
-	obj, exists, err := c.streamLister.Indexer.GetByKey(key)
173
-	if err != nil {
174
-		glog.Infof("Unable to retrieve image stream %q from store: %v", key, err)
175
-		c.queue.Add(key)
176
-		return nil, err
177
-	}
178
-	if !exists {
179
-		glog.Infof("Image stream %q has been deleted", key)
180
-		return nil, nil
181
-	}
182
-
183
-	return obj.(*imageapi.ImageStream), nil
184
-}
... ...
@@ -7,7 +7,7 @@ import (
7 7
 	"k8s.io/kubernetes/pkg/api/errors"
8 8
 	"k8s.io/kubernetes/pkg/api/rest"
9 9
 	"k8s.io/kubernetes/pkg/apis/extensions"
10
-	kclient "k8s.io/kubernetes/pkg/client/unversioned"
10
+	extvalidation "k8s.io/kubernetes/pkg/apis/extensions/validation"
11 11
 	"k8s.io/kubernetes/pkg/fields"
12 12
 	"k8s.io/kubernetes/pkg/labels"
13 13
 	"k8s.io/kubernetes/pkg/registry/generic"
... ...
@@ -18,7 +18,6 @@ import (
18 18
 	"github.com/openshift/origin/pkg/deploy/api"
19 19
 	"github.com/openshift/origin/pkg/deploy/registry/deployconfig"
20 20
 	"github.com/openshift/origin/pkg/util/restoptions"
21
-	extvalidation "k8s.io/kubernetes/pkg/apis/extensions/validation"
22 21
 )
23 22
 
24 23
 // REST contains the REST storage for DeploymentConfig objects.
... ...
@@ -29,7 +28,7 @@ type REST struct {
29 29
 // NewREST returns a deploymentConfigREST containing the REST storage for DeploymentConfig objects,
30 30
 // a statusREST containing the REST storage for changing the status of a DeploymentConfig,
31 31
 // and a scaleREST containing the REST storage for the Scale subresources of DeploymentConfigs.
32
-func NewREST(optsGetter restoptions.Getter, rcNamespacer kclient.ReplicationControllersNamespacer) (*REST, *StatusREST, *ScaleREST, error) {
32
+func NewREST(optsGetter restoptions.Getter) (*REST, *StatusREST, *ScaleREST, error) {
33 33
 	store := &registry.Store{
34 34
 		NewFunc:           func() runtime.Object { return &api.DeploymentConfig{} },
35 35
 		NewListFunc:       func() runtime.Object { return &api.DeploymentConfigList{} },
... ...
@@ -54,18 +53,14 @@ func NewREST(optsGetter restoptions.Getter, rcNamespacer kclient.ReplicationCont
54 54
 	statusStore := *store
55 55
 	statusStore.UpdateStrategy = deployconfig.StatusStrategy
56 56
 	statusREST := &StatusREST{store: &statusStore}
57
-	scaleREST := &ScaleREST{
58
-		registry:     deployconfig.NewRegistry(deploymentConfigREST),
59
-		rcNamespacer: rcNamespacer,
60
-	}
57
+	scaleREST := &ScaleREST{registry: deployconfig.NewRegistry(deploymentConfigREST)}
61 58
 
62 59
 	return deploymentConfigREST, statusREST, scaleREST, nil
63 60
 }
64 61
 
65 62
 // ScaleREST contains the REST storage for the Scale subresource of DeploymentConfigs.
66 63
 type ScaleREST struct {
67
-	registry     deployconfig.Registry
68
-	rcNamespacer kclient.ReplicationControllersNamespacer
64
+	registry deployconfig.Registry
69 65
 }
70 66
 
71 67
 // ScaleREST implements Patcher
... ...
@@ -3,7 +3,6 @@ package etcd
3 3
 import (
4 4
 	"testing"
5 5
 
6
-	"k8s.io/kubernetes/pkg/client/unversioned/testclient"
7 6
 	"k8s.io/kubernetes/pkg/fields"
8 7
 	"k8s.io/kubernetes/pkg/labels"
9 8
 	"k8s.io/kubernetes/pkg/registry/registrytest"
... ...
@@ -18,7 +17,7 @@ import (
18 18
 
19 19
 func newStorage(t *testing.T) (*REST, *etcdtesting.EtcdTestServer) {
20 20
 	etcdStorage, server := registrytest.NewEtcdStorage(t, "")
21
-	storage, _, _, err := NewREST(restoptions.NewSimpleGetter(etcdStorage), testclient.NewSimpleFake())
21
+	storage, _, _, err := NewREST(restoptions.NewSimpleGetter(etcdStorage))
22 22
 	if err != nil {
23 23
 		t.Fatal(err)
24 24
 	}
... ...
@@ -68,6 +68,8 @@ func (strategy) PrepareForUpdate(ctx kapi.Context, obj, old runtime.Object) {
68 68
 		newDc.Status.LatestVersion = newVersion
69 69
 	}
70 70
 
71
+	// TODO: Disallow lastTriggeredImage updates from this update path.
72
+
71 73
 	// Any changes to the spec or labels, increment the generation number, any changes
72 74
 	// to the status should reflect the generation number of the corresponding object
73 75
 	// (should be handled by the controller).
74 76
new file mode 100644
... ...
@@ -0,0 +1,295 @@
0
+package instantiate
1
+
2
+import (
3
+	"fmt"
4
+
5
+	"github.com/golang/glog"
6
+
7
+	"k8s.io/kubernetes/pkg/admission"
8
+	kapi "k8s.io/kubernetes/pkg/api"
9
+	"k8s.io/kubernetes/pkg/api/errors"
10
+	"k8s.io/kubernetes/pkg/api/rest"
11
+	"k8s.io/kubernetes/pkg/api/unversioned"
12
+	kclient "k8s.io/kubernetes/pkg/client/unversioned"
13
+	"k8s.io/kubernetes/pkg/registry/generic/registry"
14
+	"k8s.io/kubernetes/pkg/runtime"
15
+	utilerrors "k8s.io/kubernetes/pkg/util/errors"
16
+	"k8s.io/kubernetes/pkg/util/sets"
17
+
18
+	"github.com/openshift/origin/pkg/client"
19
+	deployapi "github.com/openshift/origin/pkg/deploy/api"
20
+	"github.com/openshift/origin/pkg/deploy/api/validation"
21
+	deployutil "github.com/openshift/origin/pkg/deploy/util"
22
+	imageapi "github.com/openshift/origin/pkg/image/api"
23
+)
24
+
25
+func NewREST(store registry.Store, oc client.Interface, kc kclient.Interface, decoder runtime.Decoder, admission admission.Interface) *REST {
26
+	store.UpdateStrategy = Strategy
27
+	return &REST{store: &store, isn: oc, rn: kc, decoder: decoder, admit: admission}
28
+}
29
+
30
+// REST implements the Creater interface.
31
+var _ = rest.Creater(&REST{})
32
+
33
+type REST struct {
34
+	store   *registry.Store
35
+	isn     client.ImageStreamsNamespacer
36
+	rn      kclient.ReplicationControllersNamespacer
37
+	decoder runtime.Decoder
38
+	admit   admission.Interface
39
+}
40
+
41
+func (s *REST) New() runtime.Object {
42
+	return &deployapi.DeploymentRequest{}
43
+}
44
+
45
+// Create instantiates a deployment config
46
+func (r *REST) Create(ctx kapi.Context, obj runtime.Object) (runtime.Object, error) {
47
+	req, ok := obj.(*deployapi.DeploymentRequest)
48
+	if !ok {
49
+		return nil, errors.NewBadRequest(fmt.Sprintf("wrong object passed for requesting a new deployment: %#v", obj))
50
+	}
51
+
52
+	configObj, err := r.store.Get(ctx, req.Name)
53
+	if err != nil {
54
+		return nil, err
55
+	}
56
+	config := configObj.(*deployapi.DeploymentConfig)
57
+	old := config
58
+
59
+	if errs := validation.ValidateRequestForDeploymentConfig(req, config); len(errs) > 0 {
60
+		return nil, errors.NewInvalid(deployapi.Kind("DeploymentRequest"), req.Name, errs)
61
+	}
62
+
63
+	// We need to process the deployment config before we can determine if it is possible to trigger
64
+	// a deployment.
65
+	if req.Latest {
66
+		if err := processTriggers(config, r.isn, req.Force); err != nil {
67
+			return nil, err
68
+		}
69
+	}
70
+
71
+	canTrigger, causes, err := canTrigger(config, r.rn, r.decoder, req.Force)
72
+	if err != nil {
73
+		return nil, err
74
+	}
75
+	// If we cannot trigger then there is nothing to do here.
76
+	if !canTrigger {
77
+		return &unversioned.Status{
78
+			Message: fmt.Sprintf("deployment config %q cannot be instantiated", config.Name),
79
+			Code:    int32(204),
80
+		}, nil
81
+	}
82
+	glog.V(4).Infof("New deployment for %q caused by %#v", config.Name, causes)
83
+
84
+	config.Status.Details = new(deployapi.DeploymentDetails)
85
+	config.Status.Details.Causes = causes
86
+	switch causes[0].Type {
87
+	case deployapi.DeploymentTriggerOnConfigChange:
88
+		config.Status.Details.Message = "config change"
89
+	case deployapi.DeploymentTriggerOnImageChange:
90
+		config.Status.Details.Message = "image change"
91
+	case deployapi.DeploymentTriggerManual:
92
+		config.Status.Details.Message = "manual change"
93
+	}
94
+	config.Status.LatestVersion++
95
+
96
+	userInfo, _ := kapi.UserFrom(ctx)
97
+	attrs := admission.NewAttributesRecord(config, old, deployapi.Kind("DeploymentConfig").WithVersion(""), config.Namespace, config.Name, deployapi.Resource("DeploymentConfig").WithVersion(""), "", admission.Update, userInfo)
98
+	if err := r.admit.Admit(attrs); err != nil {
99
+		return nil, err
100
+	}
101
+
102
+	updated, _, err := r.store.Update(ctx, config.Name, rest.DefaultUpdatedObjectInfo(config, kapi.Scheme))
103
+	return updated, err
104
+}
105
+
106
+// processTriggers will go over all deployment triggers that require processing and update
107
+// the deployment config accordingly. This contains the work that the image change controller
108
+// had been doing up to the point we got the /instantiate endpoint.
109
+func processTriggers(config *deployapi.DeploymentConfig, isn client.ImageStreamsNamespacer, force bool) error {
110
+	errs := []error{}
111
+
112
+	// Process any image change triggers.
113
+	for _, trigger := range config.Spec.Triggers {
114
+		if trigger.Type != deployapi.DeploymentTriggerOnImageChange {
115
+			continue
116
+		}
117
+
118
+		params := trigger.ImageChangeParams
119
+
120
+		// Forced deployments should always try to resolve the images in the template.
121
+		// On the other hand, paused deployments or non-automatic triggers shouldn't.
122
+		if !force && (config.Spec.Paused || !params.Automatic) {
123
+			continue
124
+		}
125
+
126
+		// Tag references are already validated
127
+		name, tag, _ := imageapi.SplitImageStreamTag(params.From.Name)
128
+		stream, err := isn.ImageStreams(params.From.Namespace).Get(name)
129
+		if err != nil {
130
+			if !errors.IsNotFound(err) {
131
+				errs = append(errs, err)
132
+			}
133
+			continue
134
+		}
135
+
136
+		// Find the latest tag event for the trigger reference.
137
+		latestEvent := imageapi.LatestTaggedImage(stream, tag)
138
+		if latestEvent == nil {
139
+			continue
140
+		}
141
+
142
+		// Ensure a change occurred
143
+		latestRef := latestEvent.DockerImageReference
144
+		if len(latestRef) == 0 || latestRef == params.LastTriggeredImage {
145
+			continue
146
+		}
147
+
148
+		// Update containers
149
+		names := sets.NewString(params.ContainerNames...)
150
+		for i := range config.Spec.Template.Spec.Containers {
151
+			container := &config.Spec.Template.Spec.Containers[i]
152
+			if !names.Has(container.Name) {
153
+				continue
154
+			}
155
+
156
+			if container.Image != latestRef {
157
+				// Update the image
158
+				container.Image = latestRef
159
+				// Log the last triggered image ID
160
+				params.LastTriggeredImage = latestRef
161
+			}
162
+		}
163
+	}
164
+
165
+	if err := utilerrors.NewAggregate(errs); err != nil {
166
+		return errors.NewInternalError(err)
167
+	}
168
+
169
+	return nil
170
+}
171
+
172
+// canTrigger determines if we can trigger a new deployment for config based on the various deployment triggers.
173
+func canTrigger(
174
+	config *deployapi.DeploymentConfig,
175
+	rn kclient.ReplicationControllersNamespacer,
176
+	decoder runtime.Decoder,
177
+	force bool,
178
+) (bool, []deployapi.DeploymentCause, error) {
179
+
180
+	decoded, err := decodeFromLatestDeployment(config, rn, decoder)
181
+	if err != nil {
182
+		return false, nil, err
183
+	}
184
+
185
+	ictCount, resolved, canTriggerByImageChange := 0, 0, false
186
+	var causes []deployapi.DeploymentCause
187
+
188
+	for _, t := range config.Spec.Triggers {
189
+		if t.Type != deployapi.DeploymentTriggerOnImageChange {
190
+			continue
191
+		}
192
+		ictCount++
193
+
194
+		// If the image is yet to be resolved then we cannot process this trigger.
195
+		lastTriggered := t.ImageChangeParams.LastTriggeredImage
196
+		if len(lastTriggered) == 0 {
197
+			continue
198
+		}
199
+		resolved++
200
+
201
+		// Non-automatic triggers should not be able to trigger deployments.
202
+		if !t.ImageChangeParams.Automatic {
203
+			continue
204
+		}
205
+
206
+		// We need stronger checks in order to validate that this template
207
+		// change is an image change. Look at the deserialized config's
208
+		// triggers and compare with the present trigger. Initial deployments
209
+		// should always trigger - there is no previous config to use for the
210
+		// comparison.
211
+		if config.Status.LatestVersion > 0 && !triggeredByDifferentImage(*t.ImageChangeParams, *decoded) {
212
+			continue
213
+		}
214
+
215
+		canTriggerByImageChange = true
216
+		causes = append(causes, deployapi.DeploymentCause{
217
+			Type: deployapi.DeploymentTriggerOnImageChange,
218
+			ImageTrigger: &deployapi.DeploymentCauseImageTrigger{
219
+				From: kapi.ObjectReference{
220
+					Name:      t.ImageChangeParams.From.Name,
221
+					Namespace: t.ImageChangeParams.From.Namespace,
222
+					Kind:      "ImageStreamTag",
223
+				},
224
+			},
225
+		})
226
+	}
227
+
228
+	if ictCount != resolved {
229
+		err = errors.NewBadRequest(fmt.Sprintf("cannot trigger a deployment for %q because it contains unresolved images", config.Name))
230
+		return false, nil, err
231
+	}
232
+
233
+	if force {
234
+		return true, []deployapi.DeploymentCause{{Type: deployapi.DeploymentTriggerManual}}, nil
235
+	}
236
+
237
+	canTriggerByConfigChange := false
238
+	if deployutil.HasChangeTrigger(config) && // Our deployment config has a config change trigger
239
+		len(causes) == 0 && // and no other trigger has triggered.
240
+		(config.Status.LatestVersion == 0 || // Either it's the initial deployment
241
+			!kapi.Semantic.DeepEqual(config.Spec.Template, decoded.Spec.Template)) /* or a config change happened so we need to trigger */ {
242
+
243
+		canTriggerByConfigChange = true
244
+		causes = []deployapi.DeploymentCause{{Type: deployapi.DeploymentTriggerOnConfigChange}}
245
+	}
246
+
247
+	return canTriggerByConfigChange || canTriggerByImageChange, causes, nil
248
+}
249
+
250
+// decodeFromLatestDeployment will try to return the decoded version of the current deploymentconfig
251
+// found in the annotations of its latest deployment. If there is no previous deploymentconfig (ie.
252
+// latestVersion == 0), the returned deploymentconfig will be the same.
253
+func decodeFromLatestDeployment(config *deployapi.DeploymentConfig, rn kclient.ReplicationControllersNamespacer, decoder runtime.Decoder) (*deployapi.DeploymentConfig, error) {
254
+	if config.Status.LatestVersion == 0 {
255
+		return config, nil
256
+	}
257
+
258
+	latestDeploymentName := deployutil.LatestDeploymentNameForConfig(config)
259
+	deployment, err := rn.ReplicationControllers(config.Namespace).Get(latestDeploymentName)
260
+	if err != nil {
261
+		// If there's no deployment for the latest config, we have no basis of
262
+		// comparison. It's the responsibility of the deployment config controller
263
+		// to make the deployment for the config, so return early.
264
+		return nil, err
265
+	}
266
+	decoded, err := deployutil.DecodeDeploymentConfig(deployment, decoder)
267
+	if err != nil {
268
+		return nil, errors.NewBadRequest(err.Error())
269
+	}
270
+	return decoded, nil
271
+}
272
+
273
+// triggeredByDifferentImage compares the provided image change parameters with those found in the
274
+// previous deployment config (the one we decoded from the annotations of its latest deployment)
275
+// and returns whether the two deployment configs have been triggered by a different image change.
276
+func triggeredByDifferentImage(ictParams deployapi.DeploymentTriggerImageChangeParams, previous deployapi.DeploymentConfig) bool {
277
+	for _, t := range previous.Spec.Triggers {
278
+		if t.Type != deployapi.DeploymentTriggerOnImageChange {
279
+			continue
280
+		}
281
+
282
+		if t.ImageChangeParams.From.Name != ictParams.From.Name ||
283
+			t.ImageChangeParams.From.Namespace != ictParams.From.Namespace {
284
+			continue
285
+		}
286
+
287
+		if t.ImageChangeParams.LastTriggeredImage != ictParams.LastTriggeredImage {
288
+			glog.V(4).Infof("Deployment config %q triggered by different image: %s -> %s", previous.Name, t.ImageChangeParams.LastTriggeredImage, ictParams.LastTriggeredImage)
289
+			return true
290
+		}
291
+		return false
292
+	}
293
+	return false
294
+}
0 295
new file mode 100644
... ...
@@ -0,0 +1,692 @@
0
+package instantiate
1
+
2
+import (
3
+	"testing"
4
+
5
+	kapi "k8s.io/kubernetes/pkg/api"
6
+	"k8s.io/kubernetes/pkg/api/errors"
7
+	ktestclient "k8s.io/kubernetes/pkg/client/unversioned/testclient"
8
+	"k8s.io/kubernetes/pkg/runtime"
9
+
10
+	"github.com/openshift/origin/pkg/client/testclient"
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
+	deployv1 "github.com/openshift/origin/pkg/deploy/api/v1"
15
+	deployutil "github.com/openshift/origin/pkg/deploy/util"
16
+	imageapi "github.com/openshift/origin/pkg/image/api"
17
+)
18
+
19
+var codec = kapi.Codecs.LegacyCodec(deployv1.SchemeGroupVersion)
20
+
21
+// TestProcess_changeForNonAutomaticTag ensures that an image update for which
22
+// there is a matching trigger results in a no-op due to the trigger's
23
+// automatic flag being set to false or updates the config if forced.
24
+func TestProcess_changeForNonAutomaticTag(t *testing.T) {
25
+	tests := []struct {
26
+		name  string
27
+		force bool
28
+
29
+		expected    bool
30
+		expectedErr bool
31
+	}{
32
+		{
33
+			name:  "normal update",
34
+			force: false,
35
+
36
+			expected:    false,
37
+			expectedErr: false,
38
+		},
39
+		{
40
+			name:  "forced update",
41
+			force: true,
42
+
43
+			expected:    true,
44
+			expectedErr: false,
45
+		},
46
+	}
47
+
48
+	for _, test := range tests {
49
+		config := deploytest.OkDeploymentConfig(1)
50
+		config.Namespace = kapi.NamespaceDefault
51
+		config.Spec.Triggers[0].ImageChangeParams.Automatic = false
52
+		// The image has been resolved at least once before.
53
+		config.Spec.Triggers[0].ImageChangeParams.LastTriggeredImage = deploytest.DockerImageReference
54
+
55
+		stream := deploytest.OkStreamForConfig(config)
56
+		config.Spec.Triggers[0].ImageChangeParams.LastTriggeredImage = "someotherresolveddockerimagereference"
57
+
58
+		fake := &testclient.Fake{}
59
+		fake.AddReactor("get", "imagestreams", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) {
60
+			if !test.expected {
61
+				t.Errorf("unexpected imagestream call")
62
+			}
63
+			return true, stream, nil
64
+		})
65
+
66
+		image := config.Spec.Template.Spec.Containers[0].Image
67
+
68
+		// Force equals to false; we shouldn't update the config anyway
69
+		err := processTriggers(config, fake, test.force)
70
+		if err == nil && test.expectedErr {
71
+			t.Errorf("%s: expected an error", test.name)
72
+			continue
73
+		}
74
+		if err != nil && !test.expectedErr {
75
+			t.Errorf("%s: unexpected error: %v", test.name, err)
76
+			continue
77
+		}
78
+		if test.expected && config.Spec.Template.Spec.Containers[0].Image == image {
79
+			t.Errorf("%s: expected an image update but got none", test.name)
80
+		} else if !test.expected && config.Spec.Template.Spec.Containers[0].Image != image {
81
+			t.Errorf("%s: didn't expect an image update but got %s", test.name, image)
82
+		}
83
+	}
84
+}
85
+
86
+// TestProcess_changeForUnregisteredTag ensures that an image update for which
87
+// there is a matching trigger results in a no-op due to the tag specified on
88
+// the trigger not matching the tags defined on the image stream.
89
+func TestProcess_changeForUnregisteredTag(t *testing.T) {
90
+	config := deploytest.OkDeploymentConfig(0)
91
+	stream := deploytest.OkStreamForConfig(config)
92
+	// The image has been resolved at least once before.
93
+	config.Spec.Triggers[0].ImageChangeParams.From.Name = imageapi.JoinImageStreamTag(stream.Name, "unrelatedtag")
94
+
95
+	fake := &testclient.Fake{}
96
+	fake.AddReactor("get", "imagestreams", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) {
97
+		return true, stream, nil
98
+	})
99
+
100
+	image := config.Spec.Template.Spec.Containers[0].Image
101
+
102
+	// verify no-op; should be the same for force=true and force=false
103
+	if err := processTriggers(config, fake, false); err != nil {
104
+		t.Fatalf("unexpected error: %v", err)
105
+	}
106
+	if image != config.Spec.Template.Spec.Containers[0].Image {
107
+		t.Fatalf("unexpected image update: %#v", config.Spec.Template.Spec.Containers[0].Image)
108
+	}
109
+
110
+	if err := processTriggers(config, fake, true); err != nil {
111
+		t.Fatalf("unexpected error when forced: %v", err)
112
+	}
113
+	if image != config.Spec.Template.Spec.Containers[0].Image {
114
+		t.Fatalf("unexpected image update when forced: %#v", config.Spec.Template.Spec.Containers[0].Image)
115
+	}
116
+}
117
+
118
+// TestProcess_matchScenarios comprehensively tests trigger definitions against
119
+// image stream updates to ensure that the image change triggers match (or don't
120
+// match) properly.
121
+func TestProcess_matchScenarios(t *testing.T) {
122
+	tests := []struct {
123
+		name string
124
+
125
+		param    *deployapi.DeploymentTriggerImageChangeParams
126
+		notFound bool
127
+
128
+		expected bool
129
+	}{
130
+		{
131
+			name: "automatic=true, initial trigger, explicit namespace",
132
+
133
+			param: &deployapi.DeploymentTriggerImageChangeParams{
134
+				Automatic:          true,
135
+				ContainerNames:     []string{"container1"},
136
+				From:               kapi.ObjectReference{Namespace: kapi.NamespaceDefault, Name: imageapi.JoinImageStreamTag(deploytest.ImageStreamName, imageapi.DefaultImageTag)},
137
+				LastTriggeredImage: "",
138
+			},
139
+
140
+			expected: true,
141
+		},
142
+		{
143
+			name: "automatic=true, initial trigger, implicit namespace",
144
+
145
+			param: &deployapi.DeploymentTriggerImageChangeParams{
146
+				Automatic:          true,
147
+				ContainerNames:     []string{"container1"},
148
+				From:               kapi.ObjectReference{Name: imageapi.JoinImageStreamTag(deploytest.ImageStreamName, imageapi.DefaultImageTag)},
149
+				LastTriggeredImage: "",
150
+			},
151
+
152
+			expected: true,
153
+		},
154
+		{
155
+			name: "automatic=false, initial trigger",
156
+
157
+			param: &deployapi.DeploymentTriggerImageChangeParams{
158
+				Automatic:          false,
159
+				ContainerNames:     []string{"container1"},
160
+				From:               kapi.ObjectReference{Namespace: kapi.NamespaceDefault, Name: imageapi.JoinImageStreamTag(deploytest.ImageStreamName, imageapi.DefaultImageTag)},
161
+				LastTriggeredImage: "",
162
+			},
163
+
164
+			expected: false,
165
+		},
166
+		{
167
+			name: "(no-op) automatic=false, already triggered",
168
+
169
+			param: &deployapi.DeploymentTriggerImageChangeParams{
170
+				Automatic:          false,
171
+				ContainerNames:     []string{"container1"},
172
+				From:               kapi.ObjectReference{Namespace: kapi.NamespaceDefault, Name: imageapi.JoinImageStreamTag(deploytest.ImageStreamName, imageapi.DefaultImageTag)},
173
+				LastTriggeredImage: deploytest.DockerImageReference,
174
+			},
175
+
176
+			expected: false,
177
+		},
178
+		{
179
+			name: "(no-op) automatic=true, image is already deployed",
180
+
181
+			param: &deployapi.DeploymentTriggerImageChangeParams{
182
+				Automatic:          true,
183
+				ContainerNames:     []string{"container1"},
184
+				From:               kapi.ObjectReference{Name: imageapi.JoinImageStreamTag(deploytest.ImageStreamName, imageapi.DefaultImageTag)},
185
+				LastTriggeredImage: deploytest.DockerImageReference,
186
+			},
187
+
188
+			expected: false,
189
+		},
190
+		{
191
+			name: "(no-op) trigger doesn't match the stream",
192
+
193
+			param: &deployapi.DeploymentTriggerImageChangeParams{
194
+				Automatic:          true,
195
+				ContainerNames:     []string{"container1"},
196
+				From:               kapi.ObjectReference{Namespace: kapi.NamespaceDefault, Name: imageapi.JoinImageStreamTag("other-stream", imageapi.DefaultImageTag)},
197
+				LastTriggeredImage: "",
198
+			},
199
+			notFound: true,
200
+
201
+			expected: false,
202
+		},
203
+	}
204
+
205
+	for i := range tests {
206
+		test := tests[i]
207
+		t.Logf("running test %q", test.name)
208
+
209
+		fake := &testclient.Fake{}
210
+		fake.AddReactor("get", "imagestreams", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) {
211
+			if test.notFound {
212
+				name := action.(ktestclient.GetAction).GetName()
213
+				return true, nil, errors.NewNotFound(imageapi.Resource("ImageStream"), name)
214
+			}
215
+			stream := fakeStream(deploytest.ImageStreamName, imageapi.DefaultImageTag, deploytest.DockerImageReference, deploytest.ImageID)
216
+			return true, stream, nil
217
+		})
218
+
219
+		config := deploytest.OkDeploymentConfig(1)
220
+		config.Namespace = kapi.NamespaceDefault
221
+		config.Spec.Triggers = []deployapi.DeploymentTriggerPolicy{
222
+			{
223
+				Type:              deployapi.DeploymentTriggerOnImageChange,
224
+				ImageChangeParams: test.param,
225
+			},
226
+		}
227
+
228
+		image := config.Spec.Template.Spec.Containers[0].Image
229
+
230
+		err := processTriggers(config, fake, false)
231
+		if err != nil {
232
+			t.Errorf("unexpected error: %v", err)
233
+			continue
234
+		}
235
+		if test.expected && config.Spec.Template.Spec.Containers[0].Image == image {
236
+			t.Errorf("%s: expected an image update but got none", test.name)
237
+		} else if !test.expected && config.Spec.Template.Spec.Containers[0].Image != image {
238
+			t.Errorf("%s: didn't expect an image update but got %s", test.name, image)
239
+		}
240
+	}
241
+}
242
+
243
+func fakeStream(name, tag, dir, image string) *imageapi.ImageStream {
244
+	return &imageapi.ImageStream{
245
+		ObjectMeta: kapi.ObjectMeta{Name: name, Namespace: kapi.NamespaceDefault},
246
+		Status: imageapi.ImageStreamStatus{
247
+			Tags: map[string]imageapi.TagEventList{
248
+				tag: {
249
+					Items: []imageapi.TagEvent{
250
+						{
251
+							DockerImageReference: dir,
252
+							Image:                image,
253
+						},
254
+					},
255
+				},
256
+			},
257
+		},
258
+	}
259
+}
260
+
261
+func TestCanTrigger(t *testing.T) {
262
+	tests := []struct {
263
+		name string
264
+
265
+		config  *deployapi.DeploymentConfig
266
+		decoded *deployapi.DeploymentConfig
267
+		force   bool
268
+
269
+		expected       bool
270
+		expectedCauses []deployapi.DeploymentCause
271
+		expectedErr    bool
272
+	}{
273
+		{
274
+			name: "no trigger [w/ podtemplate change]",
275
+
276
+			config: &deployapi.DeploymentConfig{
277
+				ObjectMeta: kapi.ObjectMeta{Name: "config"},
278
+				Spec: deployapi.DeploymentConfigSpec{
279
+					Triggers: []deployapi.DeploymentTriggerPolicy{},
280
+					Template: deploytest.OkPodTemplateChanged(),
281
+				},
282
+				Status: deploytest.OkDeploymentConfigStatus(1),
283
+			},
284
+			decoded: &deployapi.DeploymentConfig{
285
+				ObjectMeta: kapi.ObjectMeta{Name: "config"},
286
+				Spec: deployapi.DeploymentConfigSpec{
287
+					Triggers: []deployapi.DeploymentTriggerPolicy{},
288
+					Template: deploytest.OkPodTemplate(),
289
+				},
290
+				Status: deploytest.OkDeploymentConfigStatus(1),
291
+			},
292
+			force: false,
293
+
294
+			expected:       false,
295
+			expectedCauses: nil,
296
+		},
297
+		{
298
+			name: "forced updated",
299
+
300
+			config: &deployapi.DeploymentConfig{
301
+				ObjectMeta: kapi.ObjectMeta{Name: "config"},
302
+				Spec: deployapi.DeploymentConfigSpec{
303
+					Template: deploytest.OkPodTemplateChanged(),
304
+				},
305
+				Status: deploytest.OkDeploymentConfigStatus(1),
306
+			},
307
+			decoded: &deployapi.DeploymentConfig{
308
+				ObjectMeta: kapi.ObjectMeta{Name: "config"},
309
+				Spec: deployapi.DeploymentConfigSpec{
310
+					Template: deploytest.OkPodTemplate(),
311
+				},
312
+				Status: deploytest.OkDeploymentConfigStatus(1),
313
+			},
314
+			force: true,
315
+
316
+			expected:       true,
317
+			expectedCauses: []deployapi.DeploymentCause{{Type: deployapi.DeploymentTriggerManual}},
318
+		},
319
+		{
320
+			name: "config change trigger only [w/ podtemplate change]",
321
+
322
+			config: &deployapi.DeploymentConfig{
323
+				ObjectMeta: kapi.ObjectMeta{Name: "config"},
324
+				Spec: deployapi.DeploymentConfigSpec{
325
+					Template: deploytest.OkPodTemplateChanged(),
326
+					Triggers: []deployapi.DeploymentTriggerPolicy{
327
+						deploytest.OkConfigChangeTrigger(),
328
+					},
329
+				},
330
+				Status: deploytest.OkDeploymentConfigStatus(1),
331
+			},
332
+			decoded: &deployapi.DeploymentConfig{
333
+				ObjectMeta: kapi.ObjectMeta{Name: "config"},
334
+				Spec: deployapi.DeploymentConfigSpec{
335
+					Template: deploytest.OkPodTemplate(),
336
+					Triggers: []deployapi.DeploymentTriggerPolicy{
337
+						deploytest.OkConfigChangeTrigger(),
338
+					},
339
+				},
340
+				Status: deploytest.OkDeploymentConfigStatus(1),
341
+			},
342
+			force: false,
343
+
344
+			expected:       true,
345
+			expectedCauses: deploytest.OkConfigChangeDetails().Causes,
346
+		},
347
+		{
348
+			name: "config change trigger only [no change][initial]",
349
+
350
+			config: &deployapi.DeploymentConfig{
351
+				ObjectMeta: kapi.ObjectMeta{Name: "config"},
352
+				Spec: deployapi.DeploymentConfigSpec{
353
+					Template: deploytest.OkPodTemplate(),
354
+					Triggers: []deployapi.DeploymentTriggerPolicy{
355
+						deploytest.OkConfigChangeTrigger(),
356
+					},
357
+				},
358
+				Status: deploytest.OkDeploymentConfigStatus(0),
359
+			},
360
+			decoded: &deployapi.DeploymentConfig{
361
+				ObjectMeta: kapi.ObjectMeta{Name: "config"},
362
+				Spec: deployapi.DeploymentConfigSpec{
363
+					Template: deploytest.OkPodTemplate(),
364
+					Triggers: []deployapi.DeploymentTriggerPolicy{
365
+						deploytest.OkConfigChangeTrigger(),
366
+					},
367
+				},
368
+				Status: deploytest.OkDeploymentConfigStatus(0),
369
+			},
370
+			force: false,
371
+
372
+			expected:       true,
373
+			expectedCauses: deploytest.OkConfigChangeDetails().Causes,
374
+		},
375
+		{
376
+			name: "config change trigger only [no change]",
377
+
378
+			config: &deployapi.DeploymentConfig{
379
+				ObjectMeta: kapi.ObjectMeta{Name: "config"},
380
+				Spec: deployapi.DeploymentConfigSpec{
381
+					Template: deploytest.OkPodTemplate(),
382
+					Triggers: []deployapi.DeploymentTriggerPolicy{
383
+						deploytest.OkConfigChangeTrigger(),
384
+					},
385
+				},
386
+				Status: deploytest.OkDeploymentConfigStatus(1),
387
+			},
388
+			decoded: &deployapi.DeploymentConfig{
389
+				ObjectMeta: kapi.ObjectMeta{Name: "config"},
390
+				Spec: deployapi.DeploymentConfigSpec{
391
+					Template: deploytest.OkPodTemplate(),
392
+					Triggers: []deployapi.DeploymentTriggerPolicy{
393
+						deploytest.OkConfigChangeTrigger(),
394
+					},
395
+				},
396
+				Status: deploytest.OkDeploymentConfigStatus(1),
397
+			},
398
+			force: false,
399
+
400
+			expected:       false,
401
+			expectedCauses: nil,
402
+		},
403
+		{
404
+			name: "image change trigger only [automatic=false][w/ podtemplate change]",
405
+
406
+			config: &deployapi.DeploymentConfig{
407
+				ObjectMeta: kapi.ObjectMeta{Name: "config"},
408
+				Spec: deployapi.DeploymentConfigSpec{
409
+					Template: deploytest.OkPodTemplateChanged(), // Irrelevant change
410
+					Triggers: []deployapi.DeploymentTriggerPolicy{
411
+						deploytest.OkNonAutomaticICT(), // Image still to be resolved but it's false anyway
412
+					},
413
+				},
414
+				Status: deploytest.OkDeploymentConfigStatus(1),
415
+			},
416
+			decoded: &deployapi.DeploymentConfig{
417
+				ObjectMeta: kapi.ObjectMeta{Name: "config"},
418
+				Spec: deployapi.DeploymentConfigSpec{
419
+					Template: deploytest.OkPodTemplate(),
420
+					Triggers: []deployapi.DeploymentTriggerPolicy{
421
+						deploytest.OkNonAutomaticICT(),
422
+					},
423
+				},
424
+				Status: deploytest.OkDeploymentConfigStatus(1),
425
+			},
426
+			force: false,
427
+
428
+			expected:       false,
429
+			expectedCauses: nil,
430
+			expectedErr:    true,
431
+		},
432
+		{
433
+			name: "image change trigger only [automatic=false][w/ image change]",
434
+
435
+			config: &deployapi.DeploymentConfig{
436
+				ObjectMeta: kapi.ObjectMeta{Name: "config"},
437
+				Spec: deployapi.DeploymentConfigSpec{
438
+					Template: deploytest.OkPodTemplateChanged(), // Image has been updated in the template but automatic=false
439
+					Triggers: []deployapi.DeploymentTriggerPolicy{
440
+						deploytest.OkTriggeredNonAutomatic(),
441
+					},
442
+				},
443
+				Status: deploytest.OkDeploymentConfigStatus(1),
444
+			},
445
+			decoded: &deployapi.DeploymentConfig{
446
+				ObjectMeta: kapi.ObjectMeta{Name: "config"},
447
+				Spec: deployapi.DeploymentConfigSpec{
448
+					Template: deploytest.OkPodTemplate(),
449
+					Triggers: []deployapi.DeploymentTriggerPolicy{
450
+						deploytest.OkNonAutomaticICT(),
451
+					},
452
+				},
453
+				Status: deploytest.OkDeploymentConfigStatus(1),
454
+			},
455
+			force: false,
456
+
457
+			expected:       false,
458
+			expectedCauses: nil,
459
+		},
460
+		{
461
+			name: "image change trigger only [automatic=true][w/ image change]",
462
+
463
+			config: &deployapi.DeploymentConfig{
464
+				ObjectMeta: kapi.ObjectMeta{Name: "config"},
465
+				Spec: deployapi.DeploymentConfigSpec{
466
+					Template: deploytest.OkPodTemplateChanged(),
467
+					Triggers: []deployapi.DeploymentTriggerPolicy{
468
+						deploytest.OkTriggeredImageChange(),
469
+					},
470
+				},
471
+				Status: deploytest.OkDeploymentConfigStatus(1),
472
+			},
473
+			decoded: &deployapi.DeploymentConfig{
474
+				ObjectMeta: kapi.ObjectMeta{Name: "config"},
475
+				Spec: deployapi.DeploymentConfigSpec{
476
+					Template: deploytest.OkPodTemplate(),
477
+					Triggers: []deployapi.DeploymentTriggerPolicy{
478
+						deploytest.OkImageChangeTrigger(),
479
+					},
480
+				},
481
+				Status: deploytest.OkDeploymentConfigStatus(1),
482
+			},
483
+			force: false,
484
+
485
+			expected:       true,
486
+			expectedCauses: deploytest.OkImageChangeDetails().Causes,
487
+		},
488
+		{
489
+			name: "image change trigger only [automatic=true][no change]",
490
+
491
+			config: &deployapi.DeploymentConfig{
492
+				ObjectMeta: kapi.ObjectMeta{Name: "config"},
493
+				Spec: deployapi.DeploymentConfigSpec{
494
+					Template: deploytest.OkPodTemplateChanged(),
495
+					Triggers: []deployapi.DeploymentTriggerPolicy{
496
+						deploytest.OkTriggeredImageChange(),
497
+					},
498
+				},
499
+				Status: deploytest.OkDeploymentConfigStatus(1),
500
+			},
501
+			decoded: &deployapi.DeploymentConfig{
502
+				ObjectMeta: kapi.ObjectMeta{Name: "config"},
503
+				Spec: deployapi.DeploymentConfigSpec{
504
+					Template: deploytest.OkPodTemplateChanged(),
505
+					Triggers: []deployapi.DeploymentTriggerPolicy{
506
+						deploytest.OkTriggeredImageChange(),
507
+					},
508
+				},
509
+				Status: deploytest.OkDeploymentConfigStatus(1),
510
+			},
511
+			force: false,
512
+
513
+			expected:       false,
514
+			expectedCauses: nil,
515
+		},
516
+		{
517
+			name: "config change and image change trigger [automatic=false][initial][w/ image change]",
518
+
519
+			config: &deployapi.DeploymentConfig{
520
+				ObjectMeta: kapi.ObjectMeta{Name: "config"},
521
+				Spec: deployapi.DeploymentConfigSpec{
522
+					Template: deploytest.OkPodTemplateChanged(),
523
+					Triggers: []deployapi.DeploymentTriggerPolicy{
524
+						deploytest.OkConfigChangeTrigger(),
525
+						deploytest.OkTriggeredNonAutomatic(),
526
+					},
527
+				},
528
+				Status: deploytest.OkDeploymentConfigStatus(0),
529
+			},
530
+			decoded: &deployapi.DeploymentConfig{
531
+				ObjectMeta: kapi.ObjectMeta{Name: "config"},
532
+				Spec: deployapi.DeploymentConfigSpec{
533
+					Template: deploytest.OkPodTemplate(),
534
+					Triggers: []deployapi.DeploymentTriggerPolicy{
535
+						deploytest.OkConfigChangeTrigger(),
536
+						deploytest.OkNonAutomaticICT(),
537
+					},
538
+				},
539
+				Status: deploytest.OkDeploymentConfigStatus(0),
540
+			},
541
+			force: false,
542
+
543
+			expected:       true,
544
+			expectedCauses: deploytest.OkConfigChangeDetails().Causes,
545
+		},
546
+		{
547
+			name: "config change and image change trigger [automatic=false][initial][no change]",
548
+
549
+			config: &deployapi.DeploymentConfig{
550
+				ObjectMeta: kapi.ObjectMeta{Name: "config"},
551
+				Spec: deployapi.DeploymentConfigSpec{
552
+					Template: deploytest.OkPodTemplate(),
553
+					Triggers: []deployapi.DeploymentTriggerPolicy{
554
+						deploytest.OkConfigChangeTrigger(),
555
+						deploytest.OkNonAutomaticICT(), // Image is not resolved yet
556
+					},
557
+				},
558
+				Status: deploytest.OkDeploymentConfigStatus(0),
559
+			},
560
+			decoded: &deployapi.DeploymentConfig{
561
+				ObjectMeta: kapi.ObjectMeta{Name: "config"},
562
+				Spec: deployapi.DeploymentConfigSpec{
563
+					Template: deploytest.OkPodTemplate(),
564
+					Triggers: []deployapi.DeploymentTriggerPolicy{
565
+						deploytest.OkConfigChangeTrigger(),
566
+						deploytest.OkNonAutomaticICT(),
567
+					},
568
+				},
569
+				Status: deploytest.OkDeploymentConfigStatus(0),
570
+			},
571
+			force: false,
572
+
573
+			expected:       false,
574
+			expectedCauses: nil,
575
+			expectedErr:    true,
576
+		},
577
+		{
578
+			name: "config change and image change trigger [automatic=true][initial][w/ podtemplate change]",
579
+
580
+			config: &deployapi.DeploymentConfig{
581
+				ObjectMeta: kapi.ObjectMeta{Name: "config"},
582
+				Spec: deployapi.DeploymentConfigSpec{
583
+					Template: deploytest.OkPodTemplateChanged(), // Pod template has changed but the image in the template is yet to be updated
584
+					Triggers: []deployapi.DeploymentTriggerPolicy{
585
+						deploytest.OkConfigChangeTrigger(),
586
+						deploytest.OkImageChangeTrigger(),
587
+					},
588
+				},
589
+				Status: deploytest.OkDeploymentConfigStatus(0),
590
+			},
591
+			decoded: &deployapi.DeploymentConfig{
592
+				ObjectMeta: kapi.ObjectMeta{Name: "config"},
593
+				Spec: deployapi.DeploymentConfigSpec{
594
+					Template: deploytest.OkPodTemplate(),
595
+					Triggers: []deployapi.DeploymentTriggerPolicy{
596
+						deploytest.OkConfigChangeTrigger(),
597
+						deploytest.OkImageChangeTrigger(),
598
+					},
599
+				},
600
+				Status: deploytest.OkDeploymentConfigStatus(0),
601
+			},
602
+			force: false,
603
+
604
+			expected:       false,
605
+			expectedCauses: nil,
606
+			expectedErr:    true,
607
+		},
608
+		{
609
+			name: "config change and image change trigger [automatic=true][initial][w/ image change]",
610
+
611
+			config: &deployapi.DeploymentConfig{
612
+				ObjectMeta: kapi.ObjectMeta{Name: "config"},
613
+				Spec: deployapi.DeploymentConfigSpec{
614
+					Template: deploytest.OkPodTemplateChanged(),
615
+					Triggers: []deployapi.DeploymentTriggerPolicy{
616
+						deploytest.OkConfigChangeTrigger(),
617
+						deploytest.OkTriggeredImageChange(),
618
+					},
619
+				},
620
+				Status: deploytest.OkDeploymentConfigStatus(0),
621
+			},
622
+			decoded: &deployapi.DeploymentConfig{
623
+				ObjectMeta: kapi.ObjectMeta{Name: "config"},
624
+				Spec: deployapi.DeploymentConfigSpec{
625
+					Template: deploytest.OkPodTemplate(),
626
+					Triggers: []deployapi.DeploymentTriggerPolicy{
627
+						deploytest.OkConfigChangeTrigger(),
628
+						deploytest.OkImageChangeTrigger(),
629
+					},
630
+				},
631
+				Status: deploytest.OkDeploymentConfigStatus(0),
632
+			},
633
+			force: false,
634
+
635
+			expected:       true,
636
+			expectedCauses: deploytest.OkImageChangeDetails().Causes,
637
+		},
638
+		{
639
+			name: "config change and image change trigger [automatic=true][no change]",
640
+
641
+			config: &deployapi.DeploymentConfig{
642
+				ObjectMeta: kapi.ObjectMeta{Name: "config"},
643
+				Spec: deployapi.DeploymentConfigSpec{
644
+					Template: deploytest.OkPodTemplateChanged(),
645
+					Triggers: []deployapi.DeploymentTriggerPolicy{
646
+						deploytest.OkConfigChangeTrigger(),
647
+						deploytest.OkTriggeredImageChange(),
648
+					},
649
+				},
650
+				Status: deploytest.OkDeploymentConfigStatus(1),
651
+			},
652
+			force: false,
653
+
654
+			expected:       false,
655
+			expectedCauses: nil,
656
+		},
657
+	}
658
+
659
+	for _, test := range tests {
660
+		t.Logf("running scenario %q", test.name)
661
+
662
+		fake := &ktestclient.Fake{}
663
+		fake.AddReactor("get", "replicationcontrollers", func(action ktestclient.Action) (handled bool, ret runtime.Object, err error) {
664
+			config := test.decoded
665
+			if config == nil {
666
+				config = test.config
667
+			}
668
+			config = deploytest.RoundTripConfig(t, config)
669
+			deployment, _ := deployutil.MakeDeployment(config, codec)
670
+			return true, deployment, nil
671
+		})
672
+
673
+		test.config = deploytest.RoundTripConfig(t, test.config)
674
+
675
+		got, gotCauses, err := canTrigger(test.config, fake, codec, test.force)
676
+		if err != nil && !test.expectedErr {
677
+			t.Errorf("unexpected error: %v", err)
678
+			continue
679
+		}
680
+		if err == nil && test.expectedErr {
681
+			t.Errorf("expected an error")
682
+			continue
683
+		}
684
+		if test.expected != got {
685
+			t.Errorf("expected to trigger: %t, got: %t", test.expected, got)
686
+		}
687
+		if !kapi.Semantic.DeepEqual(test.expectedCauses, gotCauses) {
688
+			t.Errorf("expected causes:\n%#v\ngot:\n%#v", test.expectedCauses, gotCauses)
689
+		}
690
+	}
691
+}
0 692
new file mode 100644
... ...
@@ -0,0 +1,73 @@
0
+package instantiate
1
+
2
+import (
3
+	"reflect"
4
+
5
+	kapi "k8s.io/kubernetes/pkg/api"
6
+	"k8s.io/kubernetes/pkg/runtime"
7
+	"k8s.io/kubernetes/pkg/util/validation/field"
8
+
9
+	"github.com/openshift/origin/pkg/deploy/api"
10
+	"github.com/openshift/origin/pkg/deploy/api/validation"
11
+)
12
+
13
+type strategy struct {
14
+	runtime.ObjectTyper
15
+}
16
+
17
+var Strategy = strategy{kapi.Scheme}
18
+
19
+func (strategy) NamespaceScoped() bool {
20
+	return true
21
+}
22
+
23
+func (strategy) AllowCreateOnUpdate() bool {
24
+	return false
25
+}
26
+
27
+func (strategy) AllowUnconditionalUpdate() bool {
28
+	return false
29
+}
30
+
31
+func (strategy) GenerateName(base string) string {
32
+	return base
33
+}
34
+
35
+// PrepareForCreate is a no-op for the instantiate endpoint.
36
+func (strategy) PrepareForCreate(ctx kapi.Context, obj runtime.Object) {
37
+}
38
+
39
+// PrepareForUpdate clears fields that are not allowed to be set by the instantiate endpoint.
40
+func (strategy) PrepareForUpdate(ctx kapi.Context, obj, old runtime.Object) {
41
+	newDc := obj.(*api.DeploymentConfig)
42
+	oldDc := old.(*api.DeploymentConfig)
43
+
44
+	// Allow the status fields that need to be updated in every instantiation.
45
+	oldStatus := oldDc.Status
46
+	oldStatus.LatestVersion = newDc.Status.LatestVersion
47
+	oldStatus.Details = newDc.Status.Details
48
+	newDc.Status = oldStatus
49
+
50
+	if !reflect.DeepEqual(oldDc.Spec, newDc.Spec) || newDc.Status.LatestVersion != oldDc.Status.LatestVersion {
51
+		newDc.Generation = oldDc.Generation + 1
52
+	}
53
+}
54
+
55
+// Canonicalize normalizes the object after validation.
56
+func (strategy) Canonicalize(obj runtime.Object) {
57
+}
58
+
59
+// CheckGracefulDelete allows a deployment config to be gracefully deleted.
60
+func (strategy) CheckGracefulDelete(obj runtime.Object, options *kapi.DeleteOptions) bool {
61
+	return false
62
+}
63
+
64
+// Validate is a no-op for the instantiate endpoint.
65
+func (strategy) Validate(ctx kapi.Context, obj runtime.Object) field.ErrorList {
66
+	return validation.ValidateDeploymentConfig(obj.(*api.DeploymentConfig))
67
+}
68
+
69
+// ValidateUpdate is the default update validation for the instantiate endpoint.
70
+func (strategy) ValidateUpdate(ctx kapi.Context, obj, old runtime.Object) field.ErrorList {
71
+	return validation.ValidateDeploymentConfigUpdate(obj.(*api.DeploymentConfig), old.(*api.DeploymentConfig))
72
+}
... ...
@@ -417,8 +417,8 @@ var _ = g.Describe("deploymentconfigs", func() {
417 417
 			g.By(fmt.Sprintf("checking the history for substrings\n%s", out))
418 418
 			o.Expect(out).To(o.ContainSubstring("deploymentconfigs \"deployment-simple\""))
419 419
 			o.Expect(out).To(o.ContainSubstring("REVISION	STATUS		CAUSE"))
420
-			o.Expect(out).To(o.ContainSubstring("1		Complete	caused by a config change"))
421
-			o.Expect(out).To(o.ContainSubstring("2		Complete	caused by a config change"))
420
+			o.Expect(out).To(o.ContainSubstring("1		Complete	config change"))
421
+			o.Expect(out).To(o.ContainSubstring("2		Complete	config change"))
422 422
 		})
423 423
 	})
424 424
 
... ...
@@ -448,10 +448,10 @@ var _ = g.Describe("deploymentconfigs", func() {
448 448
 				generation = strings.Trim(generation, "\"")
449 449
 				g.By(fmt.Sprintf("checking the generation for %s: %s", resource, generation))
450 450
 
451
-				return strings.Contains(generation, "1") && strings.Contains(version, "1"), nil
451
+				return strings.Contains(generation, "2") && strings.Contains(version, "1"), nil
452 452
 			})
453 453
 			if err == wait.ErrWaitTimeout {
454
-				err = fmt.Errorf("expected generation: 1, got: %s, expected latestVersion: 1, got: %s", generation, version)
454
+				err = fmt.Errorf("expected generation: 2, got: %s, expected latestVersion: 1, got: %s", generation, version)
455 455
 			}
456 456
 			o.Expect(err).NotTo(o.HaveOccurred())
457 457
 
... ...
@@ -470,10 +470,10 @@ var _ = g.Describe("deploymentconfigs", func() {
470 470
 				generation = strings.Trim(generation, "\"")
471 471
 				g.By(fmt.Sprintf("checking the generation for %s: %s", resource, generation))
472 472
 
473
-				return strings.Contains(generation, "2"), nil
473
+				return strings.Contains(generation, "3"), nil
474 474
 			})
475 475
 			if err == wait.ErrWaitTimeout {
476
-				err = fmt.Errorf("expected generation: 2, got: %s", generation)
476
+				err = fmt.Errorf("expected generation: 3, got: %s", generation)
477 477
 			}
478 478
 			o.Expect(err).NotTo(o.HaveOccurred())
479 479
 
... ...
@@ -497,10 +497,10 @@ var _ = g.Describe("deploymentconfigs", func() {
497 497
 				generation = strings.Trim(generation, "\"")
498 498
 				g.By(fmt.Sprintf("checking the generation for %s: %s", resource, generation))
499 499
 
500
-				return strings.Contains(generation, "3") && strings.Contains(version, "2"), nil
500
+				return strings.Contains(generation, "4") && strings.Contains(version, "2"), nil
501 501
 			})
502 502
 			if err == wait.ErrWaitTimeout {
503
-				err = fmt.Errorf("expected generation: 3, got: %s, expected latestVersion: 2, got: %s", generation, version)
503
+				err = fmt.Errorf("expected generation: 4, got: %s, expected latestVersion: 2, got: %s", generation, version)
504 504
 			}
505 505
 			o.Expect(err).NotTo(o.HaveOccurred())
506 506
 
... ...
@@ -156,7 +156,8 @@ func TestClusterReaderCoverage(t *testing.T) {
156 156
 	// remove resources without read APIs
157 157
 	nonreadingResources := []unversioned.GroupResource{
158 158
 		buildapi.Resource("buildconfigs/instantiatebinary"), buildapi.Resource("buildconfigs/instantiate"), buildapi.Resource("builds/clone"),
159
-		deployapi.Resource("deploymentconfigrollbacks"), deployapi.Resource("generatedeploymentconfigs"), deployapi.Resource("deploymentconfigs/rollback"),
159
+		deployapi.Resource("deploymentconfigrollbacks"), deployapi.Resource("generatedeploymentconfigs"),
160
+		deployapi.Resource("deploymentconfigs/rollback"), deployapi.Resource("deploymentconfigs/instantiate"),
160 161
 		imageapi.Resource("imagestreamimports"), imageapi.Resource("imagestreammappings"),
161 162
 		extensionsapi.Resource("deployments/rollback"),
162 163
 		kapi.Resource("pods/attach"), kapi.Resource("namespaces/finalize"),
... ...
@@ -48,11 +48,7 @@ func TestTriggers_manual(t *testing.T) {
48 48
 
49 49
 	config := deploytest.OkDeploymentConfig(0)
50 50
 	config.Namespace = namespace
51
-	config.Spec.Triggers = []deployapi.DeploymentTriggerPolicy{
52
-		{
53
-			Type: deployapi.DeploymentTriggerManual,
54
-		},
55
-	}
51
+	config.Spec.Triggers = []deployapi.DeploymentTriggerPolicy{{Type: deployapi.DeploymentTriggerManual}}
56 52
 
57 53
 	dc, err := osClient.DeploymentConfigs(namespace).Create(config)
58 54
 	if err != nil {
... ...
@@ -65,25 +61,27 @@ func TestTriggers_manual(t *testing.T) {
65 65
 	}
66 66
 	defer rcWatch.Stop()
67 67
 
68
-	retryErr := kclient.RetryOnConflict(wait.Backoff{Steps: maxUpdateRetries}, func() error {
69
-		config, err := osClient.DeploymentConfigs(namespace).Generate(config.Name)
70
-		if err != nil {
71
-			return err
72
-		}
73
-		if config.Status.LatestVersion != 1 {
74
-			t.Fatalf("Generated deployment should have version 1: %#v", config)
75
-		}
76
-		t.Logf("config(1): %#v", config)
77
-		updatedConfig, err := osClient.DeploymentConfigs(namespace).Update(config)
78
-		if err != nil {
79
-			return err
80
-		}
81
-		t.Logf("config(2): %#v", updatedConfig)
82
-		return nil
83
-	})
84
-	if retryErr != nil {
85
-		t.Fatal(err)
68
+	request := &deployapi.DeploymentRequest{
69
+		Name:   config.Name,
70
+		Latest: false,
71
+		Force:  true,
72
+	}
73
+	config, err = osClient.DeploymentConfigs(namespace).Instantiate(request)
74
+	if err != nil {
75
+		t.Fatalf("Couldn't instantiate deployment config %q: %v", config.Name, err)
76
+	}
77
+	if config.Status.LatestVersion != 1 {
78
+		t.Fatal("Instantiated deployment config should have version 1")
79
+	}
80
+	if config.Status.Details == nil || len(config.Status.Details.Causes) == 0 {
81
+		t.Fatal("Instantiated deployment config should have a cause of deployment")
86 82
 	}
83
+	gotType := config.Status.Details.Causes[0].Type
84
+	if gotType != deployapi.DeploymentTriggerManual {
85
+		t.Fatalf("Instantiated deployment config should have a %q cause of deployment instead of %q",
86
+			deployapi.DeploymentTriggerManual, gotType)
87
+	}
88
+
87 89
 	event := <-rcWatch.ResultChan()
88 90
 	if e, a := watchapi.Added, event.Type; e != a {
89 91
 		t.Fatalf("expected watch event type %s, got %s", e, a)
... ...
@@ -205,7 +203,7 @@ waitForNewConfig:
205 205
 }
206 206
 
207 207
 // TestTriggers_imageChange_nonAutomatic ensures that a deployment config with a non-automatic
208
-// trigger will have its image updated without starting a new deployment.
208
+// trigger will have its image updated when a deployment is started manually.
209 209
 func TestTriggers_imageChange_nonAutomatic(t *testing.T) {
210 210
 	testutil.RequireEtcd(t)
211 211
 	defer testutil.DumpEtcdOnFailure(t)
... ...
@@ -221,18 +219,18 @@ func TestTriggers_imageChange_nonAutomatic(t *testing.T) {
221 221
 	if err != nil {
222 222
 		t.Fatalf("error getting cluster admin client config: %v", err)
223 223
 	}
224
-	openshiftProjectAdminClient, err := testserver.CreateNewProject(openshiftClusterAdminClient, *openshiftClusterAdminClientConfig, testutil.Namespace(), "bob")
224
+	oc, err := testserver.CreateNewProject(openshiftClusterAdminClient, *openshiftClusterAdminClientConfig, testutil.Namespace(), "bob")
225 225
 	if err != nil {
226 226
 		t.Fatalf("error creating project: %v", err)
227 227
 	}
228 228
 
229 229
 	imageStream := &imageapi.ImageStream{ObjectMeta: kapi.ObjectMeta{Name: deploytest.ImageStreamName}}
230 230
 
231
-	if imageStream, err = openshiftProjectAdminClient.ImageStreams(testutil.Namespace()).Create(imageStream); err != nil {
231
+	if imageStream, err = oc.ImageStreams(testutil.Namespace()).Create(imageStream); err != nil {
232 232
 		t.Fatalf("Couldn't create imagestream: %v", err)
233 233
 	}
234 234
 
235
-	imageWatch, err := openshiftProjectAdminClient.ImageStreams(testutil.Namespace()).Watch(kapi.ListOptions{})
235
+	imageWatch, err := oc.ImageStreams(testutil.Namespace()).Watch(kapi.ListOptions{})
236 236
 	if err != nil {
237 237
 		t.Fatalf("Couldn't subscribe to imagestreams: %v", err)
238 238
 	}
... ...
@@ -252,32 +250,36 @@ func TestTriggers_imageChange_nonAutomatic(t *testing.T) {
252 252
 			DockerImageReference: pullSpec,
253 253
 		},
254 254
 	}
255
-	updated := ""
256 255
 
257 256
 	createTagEvent := func(mapping *imageapi.ImageStreamMapping) {
258
-		if err := openshiftProjectAdminClient.ImageStreamMappings(testutil.Namespace()).Create(mapping); err != nil {
257
+		if err := oc.ImageStreamMappings(testutil.Namespace()).Create(mapping); err != nil {
259 258
 			t.Fatalf("unexpected error: %v", err)
260 259
 		}
261 260
 
262 261
 		t.Log("Waiting for image stream mapping to be reflected in the image stream status...")
263 262
 
263
+		timeout := time.After(time.Minute)
264
+
264 265
 		for {
265 266
 			select {
266 267
 			case event := <-imageWatch.ResultChan():
267 268
 				stream := event.Object.(*imageapi.ImageStream)
268 269
 				tagEventList, ok := stream.Status.Tags[imageapi.DefaultImageTag]
269
-				if ok {
270
-					if updated != tagEventList.Items[0].DockerImageReference {
271
-						updated = tagEventList.Items[0].DockerImageReference
272
-						return
273
-					}
270
+				if ok && len(tagEventList.Items) > 0 && tagEventList.Items[0].DockerImageReference == mapping.Image.DockerImageReference {
271
+					t.Logf("imagestream %q now has status with tags: %#v", stream.Name, stream.Status.Tags)
272
+					return
274 273
 				}
275
-				t.Logf("Still waiting for latest tag status update on imagestream %q", stream.Name)
274
+				if len(tagEventList.Items) > 0 {
275
+					t.Logf("want: %s, got: %s", mapping.Image.DockerImageReference, tagEventList.Items[0].DockerImageReference)
276
+				}
277
+				t.Logf("Still waiting for latest tag status update on imagestream %q with tags: %#v", stream.Name, tagEventList)
278
+			case <-timeout:
279
+				t.Fatalf("timed out waiting for image stream %q to be updated", imageStream.Name)
276 280
 			}
277 281
 		}
278 282
 	}
279 283
 
280
-	configWatch, err := openshiftProjectAdminClient.DeploymentConfigs(testutil.Namespace()).Watch(kapi.ListOptions{})
284
+	configWatch, err := oc.DeploymentConfigs(testutil.Namespace()).Watch(kapi.ListOptions{})
281 285
 	if err != nil {
282 286
 		t.Fatalf("Couldn't subscribe to deploymentconfigs: %v", err)
283 287
 	}
... ...
@@ -287,18 +289,20 @@ func TestTriggers_imageChange_nonAutomatic(t *testing.T) {
287 287
 	config.Namespace = testutil.Namespace()
288 288
 	config.Spec.Triggers = []deployapi.DeploymentTriggerPolicy{deploytest.OkImageChangeTrigger()}
289 289
 	config.Spec.Triggers[0].ImageChangeParams.Automatic = false
290
-	if config, err = openshiftProjectAdminClient.DeploymentConfigs(testutil.Namespace()).Create(config); err != nil {
290
+	if config, err = oc.DeploymentConfigs(testutil.Namespace()).Create(config); err != nil {
291 291
 		t.Fatalf("Couldn't create deploymentconfig: %v", err)
292 292
 	}
293 293
 
294 294
 	createTagEvent(mapping)
295 295
 
296 296
 	var newConfig *deployapi.DeploymentConfig
297
-	t.Log("Waiting for the initial deploymentconfig update in response to the imagestream update")
297
+	t.Log("Waiting for the first imagestream update - no deployment should run")
298 298
 
299
-	timeout := time.After(30 * time.Second)
299
+	timeout := time.After(20 * time.Second)
300 300
 
301
-	// This is the initial deployment with automatic=false in its ICT - it should be updated to pullSpec
301
+	// Deployment config with automatic=false in its ICT - no deployment should trigger.
302
+	// We don't really care about the initial update since it's not going to be deployed
303
+	// anyway.
302 304
 out:
303 305
 	for {
304 306
 		select {
... ...
@@ -313,21 +317,21 @@ out:
313 313
 				t.Fatalf("unexpected latestVersion update - the config has no config change trigger")
314 314
 			}
315 315
 
316
-			if e, a := updated, newConfig.Spec.Template.Spec.Containers[0].Image; e == a {
317
-				break out
318
-			}
319 316
 		case <-timeout:
320
-			t.Fatalf("timed out waiting for the image update to happen")
317
+			break out
321 318
 		}
322 319
 	}
323 320
 
324
-	t.Log("Waiting for the second imagestream update - it shouldn't update the deploymentconfig")
321
+	t.Log("Waiting for the second imagestream update - no deployment should run")
325 322
 
326 323
 	// Subsequent updates to the image shouldn't update the pod template image
327 324
 	mapping.Image.Name = "sha256:thisupdatedimageshouldneverlandinthepodtemplate"
328 325
 	mapping.Image.DockerImageReference = fmt.Sprintf("registry:8080/%s/%s@%s", testutil.Namespace(), deploytest.ImageStreamName, mapping.Image.Name)
329 326
 	createTagEvent(mapping)
330 327
 
328
+	timeout = time.After(20 * time.Second)
329
+
330
+loop:
331 331
 	for {
332 332
 		select {
333 333
 		case event := <-configWatch.ResultChan():
... ...
@@ -341,13 +345,36 @@ out:
341 341
 				t.Fatalf("unexpected latestVersion update - the config has no config change trigger")
342 342
 			}
343 343
 
344
-			if e, a := updated, newConfig.Spec.Template.Spec.Containers[0].Image; e == a {
345
-				t.Fatalf("unexpected image update, expected initial image to be the same: %#v", newConfig)
346
-			}
347 344
 		case <-timeout:
348
-			return
345
+			break loop
349 346
 		}
350 347
 	}
348
+
349
+	t.Log("Instantiate the deployment config - the latest image should be picked up and a new deployment should run")
350
+	request := &deployapi.DeploymentRequest{
351
+		Name:   config.Name,
352
+		Latest: true,
353
+		Force:  true,
354
+	}
355
+	if _, err = oc.DeploymentConfigs(config.Namespace).Instantiate(request); err != nil {
356
+		t.Fatalf("Couldn't instantiate deployment config %q: %v", config.Name, err)
357
+	}
358
+	config, err = oc.DeploymentConfigs(config.Namespace).Get(config.Name)
359
+	if err != nil {
360
+		t.Fatalf("Unexpected error: %v", err)
361
+	}
362
+	if exp, got := mapping.Image.DockerImageReference, config.Spec.Template.Spec.Containers[0].Image; exp != got {
363
+		t.Fatalf("Expected image %q instead of %q to be updated in deployment config %q", exp, got, config.Name)
364
+	}
365
+	if exp, got := int64(1), config.Status.LatestVersion; exp != got {
366
+		t.Fatalf("Expected latestVersion for deployment config %q to be %d, got %d", config.Name, exp, got)
367
+	}
368
+	if config.Status.Details == nil || len(config.Status.Details.Causes) == 0 {
369
+		t.Fatalf("Expected a cause of deployment for deployment config %q", config.Name)
370
+	}
371
+	if gotType, expectedType := config.Status.Details.Causes[0].Type, deployapi.DeploymentTriggerManual; gotType != expectedType {
372
+		t.Fatalf("Instantiated deployment config should have a %q cause of deployment instead of %q", expectedType, gotType)
373
+	}
351 374
 }
352 375
 
353 376
 // TestTriggers_MultipleICTs ensures that a deployment config with more than one ImageChange trigger
... ...
@@ -462,18 +489,18 @@ out:
462 462
 			}
463 463
 			container := newConfig.Spec.Template.Spec.Containers[0]
464 464
 			if e, a := updatedPullSpec, container.Image; e == a {
465
-				break out
465
+				t.Fatalf("unexpected image update: %#v", newConfig)
466 466
 			}
467 467
 
468 468
 		case <-timeout:
469
-			t.Fatalf("timed out waiting for the first image update to happen")
469
+			break out
470 470
 		}
471 471
 	}
472 472
 
473 473
 	t.Log("Should trigger a new deployment in response to the second imagestream update")
474
-	updatedImage = "sampleImage"
475
-	updatedPullSpec = "samplePullSpec"
476
-	createTagEvent(secondImageStream.Name, imageapi.DefaultImageTag, updatedImage, updatedPullSpec)
474
+	secondImage := "sampleImage"
475
+	secondPullSpec := "samplePullSpec"
476
+	createTagEvent(secondImageStream.Name, imageapi.DefaultImageTag, secondImage, secondPullSpec)
477 477
 	for {
478 478
 	inner:
479 479
 		select {
... ...
@@ -489,13 +516,20 @@ out:
489 489
 				break inner
490 490
 			case newConfig.Status.LatestVersion > 1:
491 491
 				t.Fatalf("unexpected latestVersion %d for %#v", newConfig.Status.LatestVersion, newConfig)
492
+			default:
493
+				// Keep on
492 494
 			}
493 495
 
494
-			container := newConfig.Spec.Template.Spec.Containers[1]
496
+			container := newConfig.Spec.Template.Spec.Containers[0]
495 497
 			if e, a := updatedPullSpec, container.Image; e != a {
496 498
 				t.Fatalf("unexpected image for pod template container %q; expected %q, got %q", container.Name, e, a)
497 499
 			}
498 500
 
501
+			container = newConfig.Spec.Template.Spec.Containers[1]
502
+			if e, a := secondPullSpec, container.Image; e != a {
503
+				t.Fatalf("unexpected image for pod template container %q; expected %q, got %q", container.Name, e, a)
504
+			}
505
+
499 506
 			return
500 507
 
501 508
 		case <-timeout:
... ...
@@ -543,7 +577,8 @@ func TestTriggers_configChange(t *testing.T) {
543 543
 	defer rcWatch.Stop()
544 544
 
545 545
 	// submit the initial deployment config
546
-	if _, err := osClient.DeploymentConfigs(namespace).Create(config); err != nil {
546
+	config, err = osClient.DeploymentConfigs(namespace).Create(config)
547
+	if err != nil {
547 548
 		t.Fatalf("Couldn't create DeploymentConfig: %v", err)
548 549
 	}
549 550
 
... ...
@@ -559,28 +594,50 @@ func TestTriggers_configChange(t *testing.T) {
559 559
 		t.Fatalf("Expected deployment annotated with deploymentConfig '%s', got '%s'", e, a)
560 560
 	}
561 561
 
562
-	assertEnvVarEquals("ENV1", "VAL1", deployment, t)
563
-
562
+	// before we update the config, we need to update the state of the existing deployment
563
+	// this is required to be done manually since the deployment and deployer pod controllers are not run in this test
564
+	// get this live or conflicts will never end up resolved
564 565
 	retryErr := kclient.RetryOnConflict(wait.Backoff{Steps: maxUpdateRetries}, func() error {
565
-		// before we update the config, we need to update the state of the existing deployment
566
-		// this is required to be done manually since the deployment and deployer pod controllers are not run in this test
567
-		// get this live or conflicts will never end up resolved
568 566
 		liveDeployment, err := kubeClient.ReplicationControllers(deployment.Namespace).Get(deployment.Name)
569 567
 		if err != nil {
570 568
 			return err
571 569
 		}
570
+
572 571
 		liveDeployment.Annotations[deployapi.DeploymentStatusAnnotation] = string(deployapi.DeploymentStatusComplete)
572
+
573 573
 		// update the deployment
574
-		if _, err := kubeClient.ReplicationControllers(namespace).Update(liveDeployment); err != nil {
574
+		_, err = kubeClient.ReplicationControllers(namespace).Update(liveDeployment)
575
+		return err
576
+	})
577
+	if retryErr != nil {
578
+		t.Fatal(retryErr)
579
+	}
580
+
581
+	event = <-rcWatch.ResultChan()
582
+	if e, a := watchapi.Modified, event.Type; e != a {
583
+		t.Fatalf("expected watch event type %s, got %s", e, a)
584
+	}
585
+
586
+	assertEnvVarEquals("ENV1", "VAL1", deployment, t)
587
+
588
+	// Update the config with a new environment variable and observe a new deployment
589
+	// coming up.
590
+	retryErr = kclient.RetryOnConflict(wait.Backoff{Steps: maxUpdateRetries}, func() error {
591
+		latest, err := osClient.DeploymentConfigs(namespace).Get(config.Name)
592
+		if err != nil {
575 593
 			return err
576 594
 		}
577 595
 
578
-		event = <-rcWatch.ResultChan()
579
-		if e, a := watchapi.Modified, event.Type; e != a {
580
-			t.Fatalf("expected watch event type %s, got %s", e, a)
596
+		for i, e := range latest.Spec.Template.Spec.Containers[0].Env {
597
+			if e.Name == "ENV1" {
598
+				latest.Spec.Template.Spec.Containers[0].Env[i].Value = "UPDATED"
599
+				break
600
+			}
581 601
 		}
582 602
 
583
-		return nil
603
+		// update the config
604
+		_, err = osClient.DeploymentConfigs(namespace).Update(latest)
605
+		return err
584 606
 	})
585 607
 	if retryErr != nil {
586 608
 		t.Fatal(retryErr)
... ...
@@ -657,9 +657,7 @@ items:
657 657
     - ""
658 658
     attributeRestrictions: null
659 659
     resources:
660
-    - deploymentconfigrollbacks
661 660
     - deploymentconfigs
662
-    - deploymentconfigs/rollback
663 661
     - deploymentconfigs/scale
664 662
     - generatedeploymentconfigs
665 663
     verbs:
... ...
@@ -675,6 +673,15 @@ items:
675 675
     - ""
676 676
     attributeRestrictions: null
677 677
     resources:
678
+    - deploymentconfigrollbacks
679
+    - deploymentconfigs/instantiate
680
+    - deploymentconfigs/rollback
681
+    verbs:
682
+    - create
683
+  - apiGroups:
684
+    - ""
685
+    attributeRestrictions: null
686
+    resources:
678 687
     - deploymentconfigs/log
679 688
     - deploymentconfigs/status
680 689
     verbs:
... ...
@@ -1008,9 +1015,7 @@ items:
1008 1008
     - ""
1009 1009
     attributeRestrictions: null
1010 1010
     resources:
1011
-    - deploymentconfigrollbacks
1012 1011
     - deploymentconfigs
1013
-    - deploymentconfigs/rollback
1014 1012
     - deploymentconfigs/scale
1015 1013
     - generatedeploymentconfigs
1016 1014
     verbs:
... ...
@@ -1026,6 +1031,15 @@ items:
1026 1026
     - ""
1027 1027
     attributeRestrictions: null
1028 1028
     resources:
1029
+    - deploymentconfigrollbacks
1030
+    - deploymentconfigs/instantiate
1031
+    - deploymentconfigs/rollback
1032
+    verbs:
1033
+    - create
1034
+  - apiGroups:
1035
+    - ""
1036
+    attributeRestrictions: null
1037
+    resources:
1029 1038
     - deploymentconfigs/log
1030 1039
     - deploymentconfigs/status
1031 1040
     verbs: