Browse code

Added image repository reference From field to STIBuildStrategy

Matej 'Yin' Gagyi authored on 2015/02/11 23:24:05
Showing 22 changed files
... ...
@@ -87,6 +87,7 @@ func TestExampleObjectSchemas(t *testing.T) {
87 87
 			"test-route":             &routeapi.Route{},
88 88
 			"test-service":           &kapi.Service{},
89 89
 			"test-buildcli":          &kapi.List{},
90
+			"test-buildcli-beta2":    &kapi.List{},
90 91
 		},
91 92
 		"../test/templates/fixtures": {
92 93
 			"crunchydata-pod": nil, // Explicitly fails validation, but should pass transformation
... ...
@@ -177,18 +177,25 @@ This section covers how to perform all the steps of building, deploying, and upd
177 177
 
178 178
 8. Confirm the registry is accessible (you may need to run this more than once):
179 179
 
180
-        $ curl `osc get service docker-registry --template="{{ .portalIP}}:{{ .port }}"`
180
+        $ export DOCKER_REGISTRY=`osc get service docker-registry --template="{{ .portalIP}}:{{ .port }}"`
181
+		$ curl $DOCKER_REGISTRY
181 182
 
182 183
     You should see:
183 184
 
184 185
         "docker-registry server (dev) (v0.9.0)"
185 186
 
186 187
 
187
-9. Create a new project in OpenShift. This creates a namespace `test` to contain the builds and app that we will generate below.
188
+9. Push builder image to private docker-registry:
189
+
190
+		$ docker pull openshift/ruby-20-centos7:latest
191
+		$ docker tag -f openshift/ruby-20-centos7:latest ${DOCKER_REGISTRY}/test/ruby-20-centos7:latest
192
+		$ docker push ${DOCKER_REGISTRY}/test/ruby-20-centos7:latest
193
+
194
+10. Create a new project in OpenShift. This creates a namespace `test` to contain the builds and app that we will generate below.
188 195
 
189 196
         $ openshift ex new-project test --display-name="OpenShift 3 Sample" --description="This is an example project to demonstrate OpenShift v3" --admin=anypassword:test-admin
190 197
 
191
-10. *Optional:* View the OpenShift web console in your browser by browsing to `https://<host>:8444`.  Login using the user `test-admin` and any password.
198
+11. *Optional:* View the OpenShift web console in your browser by browsing to `https://<host>:8444`.  Login using the user `test-admin` and any password.
192 199
 
193 200
     * You will need to have the browser accept the certificate at
194 201
       `https://<host>:8443` before the console can consult the OpenShift
... ...
@@ -199,7 +206,7 @@ This section covers how to perform all the steps of building, deploying, and upd
199 199
       and run builds.
200 200
 
201 201
 
202
-11. *Optional:* Fork the [ruby sample repository](https://github.com/openshift/ruby-hello-world)
202
+12. *Optional:* Fork the [ruby sample repository](https://github.com/openshift/ruby-hello-world)
203 203
     to an OpenShift-visible git account that you control, preferably
204 204
     somewhere that can also reach your OpenShift server with a webhook.
205 205
     A github.com account is an obvious place for this, but an in-house
... ...
@@ -212,7 +219,7 @@ This section covers how to perform all the steps of building, deploying, and upd
212 212
     Without your own fork, you can still run the initial build from
213 213
     OpenShift's public repository, just not a changed build.
214 214
 
215
-12. *Optional:* Add the following webhook under the settings in your new GitHub repository:
215
+13. *Optional:* Add the following webhook under the settings in your new GitHub repository:
216 216
 
217 217
         $ https://<host>:8443/osapi/v1beta1/buildConfigHooks/ruby-sample-build/secret101/github?namespace=test
218 218
 
... ...
@@ -223,12 +230,12 @@ This section covers how to perform all the steps of building, deploying, and upd
223 223
     instance as the certificate chain generated is not publicly verified.
224 224
 
225 225
 	
226
-13. Edit application-template-stibuild.json which will define the sample application
226
+14. Edit application-template-stibuild.json which will define the sample application
227 227
 
228 228
  * Update the BuildConfig's sourceURI (git://github.com/openshift/ruby-hello-world.git) to point to your forked repository.
229 229
    *Note:* You can skip this step if you did not create a forked repository.
230 230
 
231
-14. Submit the application template for processing (generating shared parameters requested in the template)
231
+15. Submit the application template for processing (generating shared parameters requested in the template)
232 232
     and then request creation of the processed template:
233 233
 
234 234
         $ osc process -n test -f application-template-stibuild.json | osc create -n test -f -
... ...
@@ -247,13 +254,13 @@ This section covers how to perform all the steps of building, deploying, and upd
247 247
     Note that no build has actually occurred yet, so at this time there
248 248
     is no image to deploy and no application to visit.
249 249
 
250
-15. Trigger an initial build of your application
250
+16. Trigger an initial build of your application
251 251
  * If you setup the GitHub webhook, push a change to app.rb in your ruby sample repository.
252 252
  * Otherwise you can request a new build by running:
253 253
 
254 254
             $ osc start-build -n test ruby-sample-build
255 255
 
256
-16. Monitor the builds and wait for the status to go to "complete" (this can take a few minutes):
256
+17. Monitor the builds and wait for the status to go to "complete" (this can take a few minutes):
257 257
 
258 258
         $ osc get -n test builds
259 259
 
... ...
@@ -283,7 +290,7 @@ This section covers how to perform all the steps of building, deploying, and upd
283 283
     automatically trigger a deployment of the application, creating a
284 284
     pod each for the frontend (your Ruby code) and backend.
285 285
 
286
-17. Wait for the application's frontend pod and database pods to be started (this can take a few minutes):
286
+18. Wait for the application's frontend pod and database pods to be started (this can take a few minutes):
287 287
 
288 288
         $ osc get -n test pods
289 289
 
... ...
@@ -294,7 +301,7 @@ This section covers how to perform all the steps of building, deploying, and upd
294 294
         1b978f62-605f-11e4-b0db-3c970e3bf0b7                mysql                                                                                                             localhost.localdomain/   deploymentConfig=,deploymentID=database,name=database,replicationController=1b960e56-605f-11e4-b0db-3c970e3bf0b7,template=ruby-helloworld-sample             Running
295 295
         4a792f55-605f-11e4-b0db-3c970e3bf0b7                172.30.17.3:5001/openshift/origin-ruby-sample:9477bdb99a409b9c747e699361ae7934fd83bb4092627e2ee35f9f0b0869885b   localhost.localdomain/   deploymentConfig=frontend,deploymentID=frontend-1,name=frontend,replicationController=4a749831-605f-11e4-b0db-3c970e3bf0b7,template=ruby-helloworld-sample   Running
296 296
 
297
-18. Determine the IP for the frontend service:
297
+19. Determine the IP for the frontend service:
298 298
 
299 299
         $ osc get -n test services
300 300
 
... ...
@@ -310,7 +317,7 @@ This section covers how to perform all the steps of building, deploying, and upd
310 310
 
311 311
     *Note:* you can also get this information from the web console.
312 312
 
313
-19. Confirm the application is now accessible via the frontend service on port 5432.  Go to http://172.30.17.4:5432 (or whatever IP address was reported above) in your browser if you're running this locally; otherwise you can use curl to see the HTML, or port forward the address to your local workstation to visit it.
313
+20. Confirm the application is now accessible via the frontend service on port 5432.  Go to http://172.30.17.4:5432 (or whatever IP address was reported above) in your browser if you're running this locally; otherwise you can use curl to see the HTML, or port forward the address to your local workstation to visit it.
314 314
 
315 315
 	- - -
316 316
 	**VAGRANT USERS:**
... ...
@@ -323,14 +330,14 @@ This section covers how to perform all the steps of building, deploying, and upd
323 323
 
324 324
     You should see a welcome page and a form that allows you to query and update key/value pairs.  The keys are stored in the database container running in the database pod.
325 325
 
326
-20. Make a change to your ruby sample main.html file, commit, and push it via git.
326
+21. Make a change to your ruby sample main.html file, commit, and push it via git.
327 327
 
328 328
  * If you do not have the webhook enabled, you'll have to manually trigger another build:
329 329
 
330 330
             $ osc start-build -n test ruby-sample-build
331 331
 
332 332
 
333
-21. Repeat step 16 (waiting for the build to complete).  Once the build is complete, refreshing your browser should show your changes.
333
+22. Repeat step 17 (waiting for the build to complete).  Once the build is complete, refreshing your browser should show your changes.
334 334
 
335 335
 Congratulations, you've successfully deployed and updated an application on OpenShift.
336 336
 
... ...
@@ -329,12 +329,14 @@ openshift ex new-project recreated-project --admin="anypassword:createuser2"
329 329
 osc describe policybinding master -n recreated-project | grep anypassword:createuser2
330 330
 echo "ex new-project: ok"
331 331
 
332
+# Test running a router
332 333
 [ ! "$(openshift ex router | grep 'does not exist')"]
333 334
 [ "$(openshift ex router -o yaml --credentials="${OPENSHIFTCONFIG}" | grep 'openshift/origin-haproxy-')" ]
334 335
 openshift ex router --create --credentials="${OPENSHIFTCONFIG}"
335 336
 [ "$(openshift ex router | grep 'service exists')" ]
336 337
 echo "ex router: ok"
337 338
 
339
+# Test running a registry
338 340
 [ ! "$(openshift ex registry | grep 'does not exist')"]
339 341
 [ "$(openshift ex registry -o yaml --credentials="${OPENSHIFTCONFIG}" | grep 'openshift/origin-docker-registry')" ]
340 342
 openshift ex registry --create --credentials="${OPENSHIFTCONFIG}"
... ...
@@ -232,11 +232,11 @@ echo "[INFO] Installing the registry"
232 232
 # TODO: add --images="${USE_IMAGES}" when the Docker registry is built alongside OpenShift
233 233
 openshift ex registry --create --credentials="${CERT_DIR}/openshift-registry/.kubeconfig" --mount-host="/tmp/openshift.local.registry" --images='openshift/origin-${component}:latest'
234 234
 
235
-echo "[INFO] Pre-pulling and pushing centos7"
236
-docker pull centos:centos7
235
+echo "[INFO] Pre-pulling and pushing ruby-20-centos7"
236
+docker pull openshift/ruby-20-centos7:latest
237 237
 # TODO: remove after this becomes part of the build
238 238
 docker pull openshift/origin-docker-registry
239
-echo "[INFO] Pulled centos7"
239
+echo "[INFO] Pulled ruby-20-centos7"
240 240
 
241 241
 echo "[INFO] Waiting for Docker registry pod to start"
242 242
 # TODO: simplify when #4702 is fixed upstream
... ...
@@ -250,9 +250,9 @@ wait_for_url_timed "http://${DOCKER_REGISTRY}" "[INFO] Docker registry says: " $
250 250
 
251 251
 [ "$(dig @${API_HOST} "docker-registry.default.local." A)" ]
252 252
 
253
-docker tag -f centos:centos7 ${DOCKER_REGISTRY}/cached/centos:centos7
254
-docker push ${DOCKER_REGISTRY}/cached/centos:centos7
255
-echo "[INFO] Pushed centos7"
253
+docker tag -f openshift/ruby-20-centos7:latest ${DOCKER_REGISTRY}/test/ruby-20-centos7:latest
254
+docker push ${DOCKER_REGISTRY}/test/ruby-20-centos7:latest
255
+echo "[INFO] Pushed ruby-20-centos7"
256 256
 
257 257
 # Process template and create
258 258
 echo "[INFO] Submitting application template json for processing..."
... ...
@@ -48,7 +48,7 @@ type BuildParameters struct {
48 48
 	Revision *SourceRevision `json:"revision,omitempty"`
49 49
 
50 50
 	// Strategy defines how to perform a build.
51
-	Strategy BuildStrategy `json:"strategy,omitempty"`
51
+	Strategy BuildStrategy `json:"strategy"`
52 52
 
53 53
 	// Output describes the Docker image the Strategy should produce.
54 54
 	Output BuildOutput `json:"output,omitempty"`
... ...
@@ -142,7 +142,7 @@ type SourceControlUser struct {
142 142
 // BuildStrategy contains the details of how to perform a build.
143 143
 type BuildStrategy struct {
144 144
 	// Type is the kind of build strategy.
145
-	Type BuildStrategyType `json:"type,omitempty"`
145
+	Type BuildStrategyType `json:"type"`
146 146
 
147 147
 	// DockerStrategy holds the parameters to the Docker build strategy.
148 148
 	DockerStrategy *DockerBuildStrategy `json:"dockerStrategy,omitempty"`
... ...
@@ -209,8 +209,16 @@ type DockerBuildStrategy struct {
209 209
 // STIBuildStrategy defines input parameters specific to an STI build.
210 210
 type STIBuildStrategy struct {
211 211
 	// Image is the image used to execute the build.
212
+	// Only valid if From is not present.
212 213
 	Image string `json:"image,omitempty"`
213 214
 
215
+	// From is reference to an image repository from where the docker image should be pulled
216
+	From *kapi.ObjectReference `json:"from,omitempty"`
217
+
218
+	// Tag is the name of image repository tag to be used as the build image, it only
219
+	// applies when From is specified.
220
+	Tag string `json:"tag,omitempty`
221
+
214 222
 	// Additional environment variables you want to pass into a builder container
215 223
 	Env []kapi.EnvVar `json:"env,omitempty"`
216 224
 
... ...
@@ -53,11 +53,27 @@ func init() {
53 53
 		func(in *newer.STIBuildStrategy, out *STIBuildStrategy, s conversion.Scope) error {
54 54
 			out.BuilderImage = in.Image
55 55
 			out.Image = in.Image
56
+			if in.From != nil {
57
+				out.From = &kapi.ObjectReference{
58
+					Name:      in.From.Name,
59
+					Namespace: in.From.Namespace,
60
+					Kind:      "ImageRepository",
61
+				}
62
+			}
63
+			out.Tag = in.Tag
56 64
 			out.Scripts = in.Scripts
57 65
 			out.Clean = !in.Incremental
58 66
 			return s.Convert(&in.Env, &out.Env, 0)
59 67
 		},
60 68
 		func(in *STIBuildStrategy, out *newer.STIBuildStrategy, s conversion.Scope) error {
69
+			if in.From != nil {
70
+				out.From = &api.ObjectReference{
71
+					Name:      in.From.Name,
72
+					Namespace: in.From.Namespace,
73
+					Kind:      "ImageRepository",
74
+				}
75
+			}
76
+			out.Tag = in.Tag
61 77
 			out.Scripts = in.Scripts
62 78
 			out.Incremental = !in.Clean
63 79
 			if len(in.Image) != 0 {
... ...
@@ -26,6 +26,12 @@ func TestSTIBuildStrategyConversion(t *testing.T) {
26 26
 	if actual.Image != oldVersion.BuilderImage {
27 27
 		t.Errorf("expected %v, actual %v", oldVersion.BuilderImage, actual.Image)
28 28
 	}
29
+	if actual.From != nil {
30
+		t.Errorf("expected %v, actual %v", nil, actual.From)
31
+	}
32
+	if actual.Tag != oldVersion.Tag {
33
+		t.Errorf("expected %v, actual %v", oldVersion.Tag, actual.Tag)
34
+	}
29 35
 	if actual.Incremental == oldVersion.Clean {
30 36
 		t.Errorf("expected %v, actual %v", oldVersion.Clean, actual.Incremental)
31 37
 	}
... ...
@@ -48,7 +48,7 @@ type BuildParameters struct {
48 48
 	Revision *SourceRevision `json:"revision,omitempty"`
49 49
 
50 50
 	// Strategy defines how to perform a build.
51
-	Strategy BuildStrategy `json:"strategy,omitempty"`
51
+	Strategy BuildStrategy `json:"strategy"`
52 52
 
53 53
 	// Output describes the Docker image the Strategy should produce.
54 54
 	Output BuildOutput `json:"output,omitempty"`
... ...
@@ -142,7 +142,7 @@ type SourceControlUser struct {
142 142
 // BuildStrategy contains the details of how to perform a build.
143 143
 type BuildStrategy struct {
144 144
 	// Type is the kind of build strategy.
145
-	Type BuildStrategyType `json:"type,omitempty"`
145
+	Type BuildStrategyType `json:"type"`
146 146
 
147 147
 	// DockerStrategy holds the parameters to the Docker build strategy.
148 148
 	DockerStrategy *DockerBuildStrategy `json:"dockerStrategy,omitempty"`
... ...
@@ -214,8 +214,17 @@ type STIBuildStrategy struct {
214 214
 	BuilderImage string `json:"builderImage,omitempty"`
215 215
 
216 216
 	// Image is the image used to execute the build.
217
+	// For BuildConfigs, From takes precedence.
217 218
 	Image string `json:"image,omitempty"`
218 219
 
220
+	// Tag is the name of image repository tag to be used as the build image, it only
221
+	// applies when From is specified.
222
+	Tag string `json:"tag,omitempty"`
223
+
224
+	// From is reference to an image repository from where the docker image should be pulled
225
+	// Only allowed in BuildConfigs, Builds use the Image field exclusively.
226
+	From *kapi.ObjectReference `json:"from,omitempty"`
227
+
219 228
 	// Additional environment variables you want to pass into a builder container
220 229
 	Env []kapi.EnvVar `json:"env,omitempty"`
221 230
 
... ...
@@ -144,23 +144,22 @@ func validateBuildConfigOutput(output *buildapi.BuildOutput) errs.ValidationErro
144 144
 func validateStrategy(strategy *buildapi.BuildStrategy) errs.ValidationErrorList {
145 145
 	allErrs := errs.ValidationErrorList{}
146 146
 
147
-	if len(strategy.Type) == 0 {
147
+	switch {
148
+	case len(strategy.Type) == 0:
148 149
 		allErrs = append(allErrs, errs.NewFieldRequired("type"))
149
-	}
150 150
 
151
-	switch strategy.Type {
152
-	case buildapi.STIBuildStrategyType:
151
+	case strategy.Type == buildapi.STIBuildStrategyType:
153 152
 		if strategy.STIStrategy == nil {
154 153
 			allErrs = append(allErrs, errs.NewFieldRequired("stiStrategy"))
155 154
 		} else {
156 155
 			allErrs = append(allErrs, validateSTIStrategy(strategy.STIStrategy).Prefix("stiStrategy")...)
157 156
 		}
158
-	case buildapi.DockerBuildStrategyType:
157
+	case strategy.Type == buildapi.DockerBuildStrategyType:
159 158
 		// DockerStrategy is currently optional, initialize it to a default state if it's not set.
160 159
 		if strategy.DockerStrategy == nil {
161 160
 			strategy.DockerStrategy = &buildapi.DockerBuildStrategy{}
162 161
 		}
163
-	case buildapi.CustomBuildStrategyType:
162
+	case strategy.Type == buildapi.CustomBuildStrategyType:
164 163
 		if strategy.CustomStrategy == nil {
165 164
 			allErrs = append(allErrs, errs.NewFieldRequired("customStrategy"))
166 165
 		} else {
... ...
@@ -178,8 +177,11 @@ func validateStrategy(strategy *buildapi.BuildStrategy) errs.ValidationErrorList
178 178
 
179 179
 func validateSTIStrategy(strategy *buildapi.STIBuildStrategy) errs.ValidationErrorList {
180 180
 	allErrs := errs.ValidationErrorList{}
181
-	if len(strategy.Image) == 0 {
182
-		allErrs = append(allErrs, errs.NewFieldRequired("image"))
181
+	if (strategy.From == nil || len(strategy.From.Name) == 0) && len(strategy.Image) == 0 {
182
+		allErrs = append(allErrs, errs.NewFieldRequired("from"))
183
+	}
184
+	if (strategy.From != nil && len(strategy.From.Name) != 0) && len(strategy.Image) != 0 {
185
+		allErrs = append(allErrs, errs.NewFieldInvalid("image", strategy.Image, "only one of 'image' and 'from' may be set"))
183 186
 	}
184 187
 	return allErrs
185 188
 }
... ...
@@ -76,7 +76,9 @@ func TestBuildConfigValidationSuccess(t *testing.T) {
76 76
 				Type:           buildapi.DockerBuildStrategyType,
77 77
 				DockerStrategy: &buildapi.DockerBuildStrategy{},
78 78
 			},
79
-			Output: buildapi.BuildOutput{},
79
+			Output: buildapi.BuildOutput{
80
+				DockerImageReference: "repository/data",
81
+			},
80 82
 		},
81 83
 	}
82 84
 	if result := ValidateBuildConfig(buildConfig); len(result) > 0 {
... ...
@@ -275,7 +277,7 @@ func TestValidateBuildParameters(t *testing.T) {
275 275
 			},
276 276
 		},
277 277
 		{
278
-			string(errs.ValidationErrorTypeRequired) + "strategy.stiStrategy.image",
278
+			string(errs.ValidationErrorTypeInvalid) + "strategy.type",
279 279
 			&buildapi.BuildParameters{
280 280
 				Source: buildapi.BuildSource{
281 281
 					Type: buildapi.BuildSourceGit,
... ...
@@ -283,15 +285,70 @@ func TestValidateBuildParameters(t *testing.T) {
283 283
 						URI: "http://github.com/my/repository",
284 284
 					},
285 285
 				},
286
+				Strategy: buildapi.BuildStrategy{Type: "classic-joke"},
286 287
 				Output: buildapi.BuildOutput{
287 288
 					DockerImageReference: "repository/data",
288 289
 				},
290
+			},
291
+		},
292
+		{
293
+			string(errs.ValidationErrorTypeRequired) + "strategy.type",
294
+			&buildapi.BuildParameters{
295
+				Source: buildapi.BuildSource{
296
+					Type: buildapi.BuildSourceGit,
297
+					Git: &buildapi.GitBuildSource{
298
+						URI: "http://github.com/my/repository",
299
+					},
300
+				},
301
+				Strategy: buildapi.BuildStrategy{},
302
+				Output: buildapi.BuildOutput{
303
+					DockerImageReference: "repository/data",
304
+				},
305
+			},
306
+		},
307
+		// invalid because both image and from are specified in the
308
+		// sti strategy definition
309
+		{
310
+			string(errs.ValidationErrorTypeInvalid) + "strategy.stiStrategy.image",
311
+			&buildapi.BuildParameters{
312
+				Source: buildapi.BuildSource{
313
+					Type: buildapi.BuildSourceGit,
314
+					Git: &buildapi.GitBuildSource{
315
+						URI: "http://github.com/my/repository",
316
+					},
317
+				},
289 318
 				Strategy: buildapi.BuildStrategy{
290 319
 					Type: buildapi.STIBuildStrategyType,
291 320
 					STIStrategy: &buildapi.STIBuildStrategy{
292
-						Image: "",
321
+						Image: "image",
322
+						From: &kapi.ObjectReference{
323
+							Name: "reponame",
324
+						},
325
+					},
326
+				},
327
+				Output: buildapi.BuildOutput{
328
+					DockerImageReference: "repository/data",
329
+				},
330
+			},
331
+		},
332
+		// invalid because neither image nor from are specified in the
333
+		// sti strategy definition
334
+		{
335
+			string(errs.ValidationErrorTypeRequired) + "strategy.stiStrategy.from",
336
+			&buildapi.BuildParameters{
337
+				Source: buildapi.BuildSource{
338
+					Type: buildapi.BuildSourceGit,
339
+					Git: &buildapi.GitBuildSource{
340
+						URI: "http://github.com/my/repository",
293 341
 					},
294 342
 				},
343
+				Strategy: buildapi.BuildStrategy{
344
+					Type:        buildapi.STIBuildStrategyType,
345
+					STIStrategy: &buildapi.STIBuildStrategy{},
346
+				},
347
+				Output: buildapi.BuildOutput{
348
+					DockerImageReference: "repository/data",
349
+				},
295 350
 			},
296 351
 		},
297 352
 	}
... ...
@@ -309,6 +366,61 @@ func TestValidateBuildParameters(t *testing.T) {
309 309
 	}
310 310
 }
311 311
 
312
+func TestValidateBuildParametersSuccess(t *testing.T) {
313
+	testCases := []struct {
314
+		*buildapi.BuildParameters
315
+	}{
316
+		{
317
+			&buildapi.BuildParameters{
318
+				Source: buildapi.BuildSource{
319
+					Type: buildapi.BuildSourceGit,
320
+					Git: &buildapi.GitBuildSource{
321
+						URI: "http://github.com/my/repository",
322
+					},
323
+				},
324
+				Strategy: buildapi.BuildStrategy{
325
+					Type: buildapi.STIBuildStrategyType,
326
+					STIStrategy: &buildapi.STIBuildStrategy{
327
+						Image: "repository/builder-image",
328
+					},
329
+				},
330
+				Output: buildapi.BuildOutput{
331
+					DockerImageReference: "repository/data",
332
+				},
333
+			},
334
+		},
335
+		{
336
+			&buildapi.BuildParameters{
337
+				Source: buildapi.BuildSource{
338
+					Type: buildapi.BuildSourceGit,
339
+					Git: &buildapi.GitBuildSource{
340
+						URI: "http://github.com/my/repository",
341
+					},
342
+				},
343
+				Strategy: buildapi.BuildStrategy{
344
+					Type: buildapi.STIBuildStrategyType,
345
+					STIStrategy: &buildapi.STIBuildStrategy{
346
+						From: &kapi.ObjectReference{
347
+							Name: "reponame",
348
+						},
349
+					},
350
+				},
351
+				Output: buildapi.BuildOutput{
352
+					DockerImageReference: "repository/data",
353
+				},
354
+			},
355
+		},
356
+	}
357
+
358
+	for _, config := range testCases {
359
+		errors := validateBuildParameters(config.BuildParameters)
360
+		if len(errors) != 0 {
361
+			t.Errorf("Unexpected validation error: %v", errors)
362
+		}
363
+	}
364
+
365
+}
366
+
312 367
 func TestValidateTrigger(t *testing.T) {
313 368
 	tests := map[string]struct {
314 369
 		trigger  buildapi.BuildTriggerPolicy
... ...
@@ -8,6 +8,7 @@ import (
8 8
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/client/cache"
9 9
 	"github.com/GoogleCloudPlatform/kubernetes/pkg/util"
10 10
 
11
+	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
11 12
 	buildapi "github.com/openshift/origin/pkg/build/api"
12 13
 	buildclient "github.com/openshift/origin/pkg/build/client"
13 14
 	buildutil "github.com/openshift/origin/pkg/build/util"
... ...
@@ -37,7 +38,8 @@ type ImageChangeController struct {
37 37
 // HandleImageRepo processes the next ImageRepository event.
38 38
 func (c *ImageChangeController) HandleImageRepo(repo *imageapi.ImageRepository) error {
39 39
 	glog.V(4).Infof("Build image change controller detected imagerepo change %s", repo.Status.DockerImageRepository)
40
-	subs := make(map[string]string)
40
+	imageSubs := make(map[string]string)
41
+	repoSubs := make(map[kapi.ObjectReference]string)
41 42
 
42 43
 	// TODO: this is inefficient
43 44
 	for _, bc := range c.BuildConfigStore.List() {
... ...
@@ -73,7 +75,8 @@ func (c *ImageChangeController) HandleImageRepo(repo *imageapi.ImageRepository)
73 73
 				next = latest.DockerImageReference
74 74
 			}
75 75
 			if len(last) == 0 || next != last {
76
-				subs[change.Image] = latest.DockerImageReference
76
+				imageSubs[change.Image] = latest.DockerImageReference
77
+				repoSubs[change.From] = latest.DockerImageReference
77 78
 				change.LastTriggeredImageID = next
78 79
 				shouldBuild = true
79 80
 			}
... ...
@@ -81,7 +84,7 @@ func (c *ImageChangeController) HandleImageRepo(repo *imageapi.ImageRepository)
81 81
 
82 82
 		if shouldBuild {
83 83
 			glog.V(4).Infof("Running build for buildConfig %s in namespace %s", config.Name, config.Namespace)
84
-			b := buildutil.GenerateBuildFromConfig(config, nil, subs)
84
+			b := buildutil.GenerateBuildFromConfig(config, nil, imageSubs, repoSubs)
85 85
 			if err := c.BuildCreator.Create(config.Namespace, b); err != nil {
86 86
 				return fmt.Errorf("error starting build for buildConfig %s: %v", config.Name, err)
87 87
 			}
... ...
@@ -255,6 +255,37 @@ func mockBuildConfig() *api.BuildConfig {
255 255
 	}
256 256
 }
257 257
 
258
+func mockBuildConfigImageRef() *api.BuildConfig {
259
+	return &api.BuildConfig{
260
+		ObjectMeta: kapi.ObjectMeta{
261
+			Name:      "data-build",
262
+			Namespace: kapi.NamespaceDefault,
263
+			Labels: map[string]string{
264
+				"name": "data-build",
265
+			},
266
+		},
267
+		Parameters: api.BuildParameters{
268
+			Source: api.BuildSource{
269
+				Type: api.BuildSourceGit,
270
+				Git: &api.GitBuildSource{
271
+					URI: "http://my.build.com/the/build/Dockerfile",
272
+				},
273
+			},
274
+			Strategy: api.BuildStrategy{
275
+				Type: api.STIBuildStrategyType,
276
+				STIStrategy: &api.STIBuildStrategy{
277
+					From: &kapi.ObjectReference{
278
+						Name: "builder/image",
279
+					},
280
+				},
281
+			},
282
+			Output: api.BuildOutput{
283
+				DockerImageReference: "repository/data-build",
284
+			},
285
+		},
286
+	}
287
+}
288
+
258 289
 func TestUpdateBuildConfig(t *testing.T) {
259 290
 	mockRegistry := test.BuildConfigRegistry{}
260 291
 	storage := REST{&mockRegistry}
... ...
@@ -290,69 +321,143 @@ func TestUpdateBuildConfigError(t *testing.T) {
290 290
 func TestBuildConfigRESTValidatesCreate(t *testing.T) {
291 291
 	mockRegistry := test.BuildConfigRegistry{}
292 292
 	storage := REST{&mockRegistry}
293
-	failureCases := map[string]api.BuildConfig{
293
+	failureCases := map[string]struct {
294
+		expectSuccess bool
295
+		data          api.BuildConfig
296
+	}{
294 297
 		"blank sourceURI": {
295
-			ObjectMeta: kapi.ObjectMeta{Name: "abc"},
296
-			Parameters: api.BuildParameters{
297
-				Source: api.BuildSource{
298
-					Type: api.BuildSourceGit,
299
-					Git: &api.GitBuildSource{
300
-						URI: "",
298
+			false,
299
+			api.BuildConfig{
300
+				ObjectMeta: kapi.ObjectMeta{Name: "abc"},
301
+				Parameters: api.BuildParameters{
302
+					Source: api.BuildSource{
303
+						Type: api.BuildSourceGit,
304
+						Git: &api.GitBuildSource{
305
+							URI: "",
306
+						},
301 307
 					},
302
-				},
303
-				Strategy: api.BuildStrategy{
304
-					Type: api.STIBuildStrategyType,
305
-					STIStrategy: &api.STIBuildStrategy{
306
-						Image: "builder/image",
308
+					Strategy: api.BuildStrategy{
309
+						Type: api.STIBuildStrategyType,
310
+						STIStrategy: &api.STIBuildStrategy{
311
+							From: &kapi.ObjectReference{
312
+								Name: "builder/image",
313
+							},
314
+						},
315
+					},
316
+					Output: api.BuildOutput{
317
+						DockerImageReference: "data/image",
307 318
 					},
308
-				},
309
-				Output: api.BuildOutput{
310
-					DockerImageReference: "data/image",
311 319
 				},
312 320
 			},
313 321
 		},
314 322
 		"blank DockerImageReference": {
315
-			ObjectMeta: kapi.ObjectMeta{Name: "abc"},
316
-			Parameters: api.BuildParameters{
317
-				Source: api.BuildSource{
318
-					Type: api.BuildSourceGit,
319
-					Git: &api.GitBuildSource{
320
-						URI: "http://github.com/test/source",
323
+			true,
324
+			api.BuildConfig{
325
+				ObjectMeta: kapi.ObjectMeta{Name: "abc"},
326
+				Parameters: api.BuildParameters{
327
+					Source: api.BuildSource{
328
+						Type: api.BuildSourceGit,
329
+						Git: &api.GitBuildSource{
330
+							URI: "http://github.com/test/source",
331
+						},
332
+					},
333
+					Strategy: api.BuildStrategy{
334
+						Type: api.STIBuildStrategyType,
335
+						STIStrategy: &api.STIBuildStrategy{
336
+							From: &kapi.ObjectReference{
337
+								Name: "builder/image",
338
+							},
339
+						},
340
+					},
341
+					Output: api.BuildOutput{
342
+						DockerImageReference: "",
321 343
 					},
322
-				},
323
-				Output: api.BuildOutput{
324
-					DockerImageReference: "",
325 344
 				},
326 345
 			},
327 346
 		},
328
-		"blank Image": {
329
-			ObjectMeta: kapi.ObjectMeta{Name: "abc"},
330
-			Parameters: api.BuildParameters{
331
-				Source: api.BuildSource{
332
-					Type: api.BuildSourceGit,
333
-					Git: &api.GitBuildSource{
334
-						URI: "http://github.com/test/source",
347
+		"blank From.Name and blank Image": {
348
+			false,
349
+			api.BuildConfig{
350
+				ObjectMeta: kapi.ObjectMeta{Name: "abc"},
351
+				Parameters: api.BuildParameters{
352
+					Source: api.BuildSource{
353
+						Type: api.BuildSourceGit,
354
+						Git: &api.GitBuildSource{
355
+							URI: "http://github.com/test/source",
356
+						},
357
+					},
358
+					Strategy: api.BuildStrategy{
359
+						Type:        api.STIBuildStrategyType,
360
+						STIStrategy: &api.STIBuildStrategy{},
361
+					},
362
+					Output: api.BuildOutput{
363
+						DockerImageReference: "data/image",
335 364
 					},
336 365
 				},
337
-				Strategy: api.BuildStrategy{
338
-					Type: api.STIBuildStrategyType,
339
-					STIStrategy: &api.STIBuildStrategy{
340
-						Image: "",
366
+			},
367
+		},
368
+		"blank From.Name and Image present": {
369
+			true,
370
+			api.BuildConfig{
371
+				ObjectMeta: kapi.ObjectMeta{Name: "abc"},
372
+				Parameters: api.BuildParameters{
373
+					Source: api.BuildSource{
374
+						Type: api.BuildSourceGit,
375
+						Git: &api.GitBuildSource{
376
+							URI: "http://github.com/test/source",
377
+						},
378
+					},
379
+					Strategy: api.BuildStrategy{
380
+						Type: api.STIBuildStrategyType,
381
+						STIStrategy: &api.STIBuildStrategy{
382
+							Image: "builder/image",
383
+						},
384
+					},
385
+					Output: api.BuildOutput{
386
+						DockerImageReference: "data/image",
341 387
 					},
342 388
 				},
343
-				Output: api.BuildOutput{
344
-					DockerImageReference: "data/image",
389
+			},
390
+		},
391
+		"blank Image and From.Name present": {
392
+			true,
393
+			api.BuildConfig{
394
+				ObjectMeta: kapi.ObjectMeta{Name: "abc"},
395
+				Parameters: api.BuildParameters{
396
+					Source: api.BuildSource{
397
+						Type: api.BuildSourceGit,
398
+						Git: &api.GitBuildSource{
399
+							URI: "http://github.com/test/source",
400
+						},
401
+					},
402
+					Strategy: api.BuildStrategy{
403
+						Type: api.STIBuildStrategyType,
404
+						STIStrategy: &api.STIBuildStrategy{
405
+							From: &kapi.ObjectReference{
406
+								Name: "builder/image",
407
+							},
408
+						},
409
+					},
410
+					Output: api.BuildOutput{
411
+						DockerImageReference: "data/image",
412
+					},
345 413
 				},
346 414
 			},
347 415
 		},
348 416
 	}
349
-	for desc, failureCase := range failureCases {
350
-		c, err := storage.Create(kapi.NewDefaultContext(), &failureCase)
351
-		if c != nil {
352
-			t.Errorf("%s: Expected nil object", desc)
353
-		}
354
-		if !errors.IsInvalid(err) {
355
-			t.Errorf("%s: Expected to get an invalid resource error, got %v", desc, err)
417
+	for desc, testCase := range failureCases {
418
+		c, err := storage.Create(kapi.NewDefaultContext(), &testCase.data)
419
+		if testCase.expectSuccess {
420
+			if c == nil {
421
+				t.Errorf("%s: Expected success, got error: %s", desc, err)
422
+			}
423
+		} else {
424
+			if c != nil {
425
+				t.Errorf("%s: Expected nil object", desc)
426
+			}
427
+			if !errors.IsInvalid(err) {
428
+				t.Errorf("%s: Expected to get an invalid resource error, got %v", desc, err)
429
+			}
356 430
 		}
357 431
 	}
358 432
 }
... ...
@@ -360,83 +465,175 @@ func TestBuildConfigRESTValidatesCreate(t *testing.T) {
360 360
 func TestBuildRESTValidatesUpdate(t *testing.T) {
361 361
 	mockRegistry := test.BuildConfigRegistry{}
362 362
 	storage := REST{&mockRegistry}
363
-	failureCases := map[string]api.BuildConfig{
363
+	failureCases := map[string]struct {
364
+		expectSuccess bool
365
+		data          api.BuildConfig
366
+	}{
364 367
 		"empty ID": {
365
-			ObjectMeta: kapi.ObjectMeta{Name: ""},
366
-			Parameters: api.BuildParameters{
367
-				Source: api.BuildSource{
368
-					Type: api.BuildSourceGit,
369
-					Git: &api.GitBuildSource{
370
-						URI: "http://github.com/test/source",
371
-					},
368
+			false,
369
+			api.BuildConfig{
370
+				ObjectMeta: kapi.ObjectMeta{
371
+					Name:      "",
372
+					Namespace: kapi.NamespaceDefault,
372 373
 				},
373
-				Output: api.BuildOutput{
374
-					DockerImageReference: "data/image",
374
+				Parameters: api.BuildParameters{
375
+					Source: api.BuildSource{
376
+						Type: api.BuildSourceGit,
377
+						Git: &api.GitBuildSource{
378
+							URI: "http://github.com/test/source",
379
+						},
380
+					},
381
+					Output: api.BuildOutput{
382
+						DockerImageReference: "data/image",
383
+					},
375 384
 				},
376 385
 			},
377 386
 		},
378 387
 		"blank sourceURI": {
379
-			ObjectMeta: kapi.ObjectMeta{Name: "abc"},
380
-			Parameters: api.BuildParameters{
381
-				Source: api.BuildSource{
382
-					Type: api.BuildSourceGit,
383
-					Git: &api.GitBuildSource{
384
-						URI: "",
385
-					},
388
+			false,
389
+			api.BuildConfig{
390
+				ObjectMeta: kapi.ObjectMeta{
391
+					Name:      "abc",
392
+					Namespace: kapi.NamespaceDefault,
386 393
 				},
387
-				Strategy: api.BuildStrategy{
388
-					Type: api.STIBuildStrategyType,
389
-					STIStrategy: &api.STIBuildStrategy{
390
-						Image: "builder/image",
394
+				Parameters: api.BuildParameters{
395
+					Source: api.BuildSource{
396
+						Type: api.BuildSourceGit,
397
+						Git: &api.GitBuildSource{
398
+							URI: "",
399
+						},
400
+					},
401
+					Strategy: api.BuildStrategy{
402
+						Type: api.STIBuildStrategyType,
403
+						STIStrategy: &api.STIBuildStrategy{
404
+							From: &kapi.ObjectReference{
405
+								Name: "builder/image",
406
+							},
407
+						},
408
+					},
409
+					Output: api.BuildOutput{
410
+						DockerImageReference: "data/image",
391 411
 					},
392
-				},
393
-				Output: api.BuildOutput{
394
-					DockerImageReference: "data/image",
395 412
 				},
396 413
 			},
397 414
 		},
398 415
 		"blank DockerImageReference": {
399
-			ObjectMeta: kapi.ObjectMeta{Name: "abc"},
400
-			Parameters: api.BuildParameters{
401
-				Source: api.BuildSource{
402
-					Type: api.BuildSourceGit,
403
-					Git: &api.GitBuildSource{
404
-						URI: "http://github.com/test/source",
405
-					},
416
+			false,
417
+			api.BuildConfig{
418
+				ObjectMeta: kapi.ObjectMeta{
419
+					Name:      "abc",
420
+					Namespace: kapi.NamespaceDefault,
406 421
 				},
407
-				Output: api.BuildOutput{
408
-					DockerImageReference: "",
422
+				Parameters: api.BuildParameters{
423
+					Source: api.BuildSource{
424
+						Type: api.BuildSourceGit,
425
+						Git: &api.GitBuildSource{
426
+							URI: "http://github.com/test/source",
427
+						},
428
+					},
429
+					Output: api.BuildOutput{
430
+						DockerImageReference: "",
431
+					},
409 432
 				},
410 433
 			},
411 434
 		},
412
-		"blank Image on STIBuildType": {
413
-			ObjectMeta: kapi.ObjectMeta{Name: "abc"},
414
-			Parameters: api.BuildParameters{
415
-				Source: api.BuildSource{
416
-					Type: api.BuildSourceGit,
417
-					Git: &api.GitBuildSource{
418
-						URI: "http://github.com/test/source",
435
+		"blank From.Name and blank Image": {
436
+			false,
437
+			api.BuildConfig{
438
+				ObjectMeta: kapi.ObjectMeta{
439
+					Name:      "abc",
440
+					Namespace: kapi.NamespaceDefault,
441
+				},
442
+				Parameters: api.BuildParameters{
443
+					Source: api.BuildSource{
444
+						Type: api.BuildSourceGit,
445
+						Git: &api.GitBuildSource{
446
+							URI: "http://github.com/test/source",
447
+						},
448
+					},
449
+					Strategy: api.BuildStrategy{
450
+						Type: api.STIBuildStrategyType,
451
+						STIStrategy: &api.STIBuildStrategy{
452
+							From: &kapi.ObjectReference{
453
+								Name: "",
454
+							},
455
+						},
456
+					},
457
+					Output: api.BuildOutput{
458
+						DockerImageReference: "data/image",
419 459
 					},
420 460
 				},
421
-				Strategy: api.BuildStrategy{
422
-					Type: api.STIBuildStrategyType,
423
-					STIStrategy: &api.STIBuildStrategy{
424
-						Image: "",
461
+			},
462
+		},
463
+		"blank From.Name and Image present": {
464
+			true,
465
+			api.BuildConfig{
466
+				ObjectMeta: kapi.ObjectMeta{
467
+					Name:      "abc",
468
+					Namespace: kapi.NamespaceDefault,
469
+				},
470
+				Parameters: api.BuildParameters{
471
+					Source: api.BuildSource{
472
+						Type: api.BuildSourceGit,
473
+						Git: &api.GitBuildSource{
474
+							URI: "http://github.com/test/source",
475
+						},
476
+					},
477
+					Strategy: api.BuildStrategy{
478
+						Type: api.STIBuildStrategyType,
479
+						STIStrategy: &api.STIBuildStrategy{
480
+							Image: "builder/image",
481
+						},
482
+					},
483
+					Output: api.BuildOutput{
484
+						DockerImageReference: "data/image",
425 485
 					},
426 486
 				},
427
-				Output: api.BuildOutput{
428
-					DockerImageReference: "data/image",
487
+			},
488
+		},
489
+		"blank Image and From.Name present": {
490
+			true,
491
+			api.BuildConfig{
492
+				ObjectMeta: kapi.ObjectMeta{
493
+					Name:      "abc",
494
+					Namespace: kapi.NamespaceDefault,
495
+				},
496
+				Parameters: api.BuildParameters{
497
+					Source: api.BuildSource{
498
+						Type: api.BuildSourceGit,
499
+						Git: &api.GitBuildSource{
500
+							URI: "http://github.com/test/source",
501
+						},
502
+					},
503
+					Strategy: api.BuildStrategy{
504
+						Type: api.STIBuildStrategyType,
505
+						STIStrategy: &api.STIBuildStrategy{
506
+							From: &kapi.ObjectReference{
507
+								Name: "imagerepo",
508
+							},
509
+						},
510
+					},
511
+					Output: api.BuildOutput{
512
+						DockerImageReference: "data/image",
513
+					},
429 514
 				},
430 515
 			},
431 516
 		},
432 517
 	}
433
-	for desc, failureCase := range failureCases {
434
-		c, created, err := storage.Update(kapi.NewDefaultContext(), &failureCase)
435
-		if c != nil || created {
436
-			t.Errorf("%s: Expected nil object", desc)
437
-		}
438
-		if !errors.IsInvalid(err) {
439
-			t.Errorf("%s: Expected to get an invalid resource error, got %v", desc, err)
518
+
519
+	for desc, testCase := range failureCases {
520
+		c, created, err := storage.Update(kapi.NewDefaultContext(), &testCase.data)
521
+		if testCase.expectSuccess {
522
+			if c == nil {
523
+				t.Errorf("%s: Expected success, error: %s", desc, err)
524
+			}
525
+		} else {
526
+			if c != nil || created {
527
+				t.Errorf("%s: Expected nil object", desc)
528
+			}
529
+			if !errors.IsInvalid(err) {
530
+				t.Errorf("%s: Expected to get an invalid resource error, got %v", desc, err)
531
+			}
440 532
 		}
441 533
 	}
442 534
 }
... ...
@@ -485,5 +682,4 @@ func checkExpectedNamespaceError(t *testing.T, err error) {
485 485
 			t.Errorf("Expected '"+expectedError+"' error, got '%v'", err.Error())
486 486
 		}
487 487
 	}
488
-
489 488
 }
... ...
@@ -110,6 +110,63 @@ func TestEtcdCreateBuild(t *testing.T) {
110 110
 			Strategy: api.BuildStrategy{
111 111
 				Type: api.STIBuildStrategyType,
112 112
 				STIStrategy: &api.STIBuildStrategy{
113
+					From: &kapi.ObjectReference{
114
+						Name: "builder/image",
115
+					},
116
+				},
117
+			},
118
+			Output: api.BuildOutput{
119
+				DockerImageReference: "repository/dataBuild",
120
+			},
121
+		},
122
+		Status:  api.BuildStatusPending,
123
+		PodName: "-the-pod-id",
124
+	})
125
+	if err != nil {
126
+		t.Fatalf("unexpected error: %v", err)
127
+	}
128
+
129
+	resp, err := fakeClient.Get(makeTestDefaultBuildKey("foo"), false, false)
130
+	if err != nil {
131
+		t.Fatalf("Unexpected error %v", err)
132
+	}
133
+	var build api.Build
134
+	err = latest.Codec.DecodeInto([]byte(resp.Node.Value), &build)
135
+	if err != nil {
136
+		t.Errorf("unexpected error: %v", err)
137
+	}
138
+
139
+	if build.Name != "foo" {
140
+		t.Errorf("Unexpected build: %#v %s", build, resp.Node.Value)
141
+	}
142
+}
143
+
144
+func TestEtcdCreateBuildUsingImage(t *testing.T) {
145
+	fakeClient := tools.NewFakeEtcdClient(t)
146
+	fakeClient.TestIndex = true
147
+	fakeClient.Data[makeTestDefaultBuildKey("foo")] = tools.EtcdResponseWithError{
148
+		R: &etcd.Response{
149
+			Node: nil,
150
+		},
151
+		E: tools.EtcdErrorNotFound,
152
+	}
153
+	registry := NewTestEtcd(fakeClient)
154
+	err := registry.CreateBuild(kapi.NewDefaultContext(), &api.Build{
155
+		ObjectMeta: kapi.ObjectMeta{
156
+			Name: "foo",
157
+			Labels: map[string]string{
158
+				"name": "dataBuild",
159
+			},
160
+		},
161
+		Parameters: api.BuildParameters{
162
+			Source: api.BuildSource{
163
+				Git: &api.GitBuildSource{
164
+					URI: "http://my.build.com/the/build/Dockerfile",
165
+				},
166
+			},
167
+			Strategy: api.BuildStrategy{
168
+				Type: api.STIBuildStrategyType,
169
+				STIStrategy: &api.STIBuildStrategy{
113 170
 					Image: "builder/image",
114 171
 				},
115 172
 			},
... ...
@@ -332,6 +389,61 @@ func TestEtcdCreateBuildConfig(t *testing.T) {
332 332
 			Strategy: api.BuildStrategy{
333 333
 				Type: api.STIBuildStrategyType,
334 334
 				STIStrategy: &api.STIBuildStrategy{
335
+					From: &kapi.ObjectReference{
336
+						Name: "builder/image",
337
+					},
338
+				},
339
+			},
340
+			Output: api.BuildOutput{
341
+				DockerImageReference: "repository/dataBuild",
342
+			},
343
+		},
344
+	})
345
+	if err != nil {
346
+		t.Fatalf("unexpected error: %v", err)
347
+	}
348
+
349
+	resp, err := fakeClient.Get(makeTestDefaultBuildConfigKey("foo"), false, false)
350
+	if err != nil {
351
+		t.Fatalf("Unexpected error %v", err)
352
+	}
353
+	var buildConfig api.BuildConfig
354
+	err = latest.Codec.DecodeInto([]byte(resp.Node.Value), &buildConfig)
355
+	if err != nil {
356
+		t.Errorf("unexpected error: %v", err)
357
+	}
358
+
359
+	if buildConfig.Name != "foo" {
360
+		t.Errorf("Unexpected buildConfig: %#v %s", buildConfig, resp.Node.Value)
361
+	}
362
+}
363
+
364
+func TestEtcdCreateBuildConfigUsingImage(t *testing.T) {
365
+	fakeClient := tools.NewFakeEtcdClient(t)
366
+	fakeClient.TestIndex = true
367
+	fakeClient.Data[makeTestDefaultBuildConfigKey("foo")] = tools.EtcdResponseWithError{
368
+		R: &etcd.Response{
369
+			Node: nil,
370
+		},
371
+		E: tools.EtcdErrorNotFound,
372
+	}
373
+	registry := NewTestEtcd(fakeClient)
374
+	err := registry.CreateBuildConfig(kapi.NewDefaultContext(), &api.BuildConfig{
375
+		ObjectMeta: kapi.ObjectMeta{
376
+			Name: "foo",
377
+			Labels: map[string]string{
378
+				"name": "dataBuildConfig",
379
+			},
380
+		},
381
+		Parameters: api.BuildParameters{
382
+			Source: api.BuildSource{
383
+				Git: &api.GitBuildSource{
384
+					URI: "http://my.build.com/the/build/Dockerfile",
385
+				},
386
+			},
387
+			Strategy: api.BuildStrategy{
388
+				Type: api.STIBuildStrategyType,
389
+				STIStrategy: &api.STIBuildStrategy{
335 390
 					Image: "builder/image",
336 391
 				},
337 392
 			},
... ...
@@ -1,6 +1,7 @@
1 1
 package util
2 2
 
3 3
 import (
4
+	"fmt"
4 5
 	"github.com/golang/glog"
5 6
 
6 7
 	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
... ...
@@ -13,7 +14,8 @@ import (
13 13
 // GenerateBuildFromConfig creates a new build based on a given BuildConfig. Optionally a SourceRevision for the new
14 14
 // build can be specified.  Also optionally a list of image names to be substituted can be supplied.  Values in the BuildConfig
15 15
 // that have a substitution provided will be replaced in the resulting Build
16
-func GenerateBuildFromConfig(bc *buildapi.BuildConfig, r *buildapi.SourceRevision, imageSubstitutions map[string]string) (build *buildapi.Build) {
16
+func GenerateBuildFromConfig(bc *buildapi.BuildConfig, ref *buildapi.SourceRevision, imageSubstitutions map[string]string,
17
+	imageRepoSubstitutions map[kapi.ObjectReference]string) (build *buildapi.Build) {
17 18
 	// Need to copy the buildConfig here so that it doesn't share pointers with
18 19
 	// the build object which could be (will be) modified later.
19 20
 	obj, _ := kapi.Scheme.Copy(bc)
... ...
@@ -24,7 +26,7 @@ func GenerateBuildFromConfig(bc *buildapi.BuildConfig, r *buildapi.SourceRevisio
24 24
 			Source:   bcCopy.Parameters.Source,
25 25
 			Strategy: bcCopy.Parameters.Strategy,
26 26
 			Output:   bcCopy.Parameters.Output,
27
-			Revision: r,
27
+			Revision: ref,
28 28
 		},
29 29
 		ObjectMeta: kapi.ObjectMeta{
30 30
 			Labels: bcCopy.Labels,
... ...
@@ -39,6 +41,14 @@ func GenerateBuildFromConfig(bc *buildapi.BuildConfig, r *buildapi.SourceRevisio
39 39
 		glog.V(4).Infof("Substituting %s for %s", newImage, originalImage)
40 40
 		SubstituteImageReferences(b, originalImage, newImage)
41 41
 	}
42
+	for imageRepo, newImage := range imageRepoSubstitutions {
43
+		if len(imageRepo.Namespace) != 0 {
44
+			glog.V(4).Infof("Substituting repository %s for %s/%s", newImage, imageRepo.Namespace, imageRepo.Name)
45
+		} else {
46
+			glog.V(4).Infof("Substituting repository %s for %s", newImage, imageRepo.Name)
47
+		}
48
+		SubstituteImageRepoReferences(b, imageRepo, newImage)
49
+	}
42 50
 	return b
43 51
 }
44 52
 
... ...
@@ -62,10 +72,65 @@ func GenerateBuildFromBuild(build *buildapi.Build) *buildapi.Build {
62 62
 // the image tag from the corresponding image repo rather than the image field from the buildconfig
63 63
 // as the base image for the build.
64 64
 func GenerateBuildWithImageTag(config *buildapi.BuildConfig, revision *buildapi.SourceRevision, imageRepoGetter osclient.ImageRepositoryNamespaceGetter) (*buildapi.Build, error) {
65
-
66
-	imageSubstitutions := make(map[string]string)
65
+	var build *buildapi.Build
66
+	var err error
67 67
 	glog.V(4).Infof("Generating tagged build for config %s", config.Name)
68 68
 
69
+	switch {
70
+	case config.Parameters.Strategy.Type == buildapi.STIBuildStrategyType:
71
+		if config.Parameters.Strategy.STIStrategy.From != nil && config.Parameters.Strategy.STIStrategy.From.Name != "" {
72
+			build, err = GenerateBuildUsingObjectReference(config, revision, imageRepoGetter)
73
+		} else {
74
+			build, err = GenerateBuildUsingImageTriggerTag(config, revision, imageRepoGetter)
75
+		}
76
+	case config.Parameters.Strategy.Type == buildapi.DockerBuildStrategyType:
77
+		build, err = GenerateBuildUsingImageTriggerTag(config, revision, imageRepoGetter)
78
+	case config.Parameters.Strategy.Type == buildapi.CustomBuildStrategyType:
79
+		build, err = GenerateBuildUsingImageTriggerTag(config, revision, imageRepoGetter)
80
+	default:
81
+		return nil, fmt.Errorf("Build strategy type must be set")
82
+	}
83
+	return build, err
84
+}
85
+
86
+// GenerateBuildUsingObjectReference examines the ImageRepo referenced by the BuildConfig and resolves it to
87
+// an imagespec, it then returns a Build object that uses that imagespec.
88
+func GenerateBuildUsingObjectReference(config *buildapi.BuildConfig, revision *buildapi.SourceRevision, imageRepoGetter osclient.ImageRepositoryNamespaceGetter) (*buildapi.Build, error) {
89
+	imageRepoSubstitutions := make(map[kapi.ObjectReference]string)
90
+	from := config.Parameters.Strategy.STIStrategy.From
91
+	namespace := from.Namespace
92
+	if len(namespace) == 0 {
93
+		namespace = config.Namespace
94
+	}
95
+	tag := config.Parameters.Strategy.STIStrategy.Tag
96
+	if len(tag) == 0 {
97
+		tag = buildapi.DefaultImageTag
98
+	}
99
+
100
+	imageRepo, err := imageRepoGetter.GetByNamespace(namespace, from.Name)
101
+	if err != nil {
102
+		return nil, err
103
+	}
104
+	if imageRepo == nil || imageRepo.Status.DockerImageRepository == "" {
105
+		return nil, fmt.Errorf("Docker Image Repository %s missing in namespace %s", from.Name, namespace)
106
+	}
107
+	glog.V(4).Infof("Found image repo %s", imageRepo.Name)
108
+	latest, err := imageapi.LatestTaggedImage(imageRepo, tag)
109
+	if err == nil {
110
+		glog.V(4).Infof("Using image %s for image repository %s in namespace %s", latest.DockerImageReference, from.Name, from.Namespace)
111
+		imageRepoSubstitutions[*from] = latest.DockerImageReference
112
+	} else {
113
+		return nil, fmt.Errorf("Docker Image Repository %s has no tag %s", from.Name, tag)
114
+	}
115
+	glog.V(4).Infof("Generating build from config for build config %s", config.Name)
116
+	return GenerateBuildFromConfig(config, revision, nil, imageRepoSubstitutions), nil
117
+}
118
+
119
+// GenerateBuildUsingTriggerTag examines the ImageChangeTriggers associated with the BuildConfig
120
+// and uses them to determine the current imagespec that should be used to run this build, it then
121
+// returns a Build object that uses that imagespec.
122
+func GenerateBuildUsingImageTriggerTag(config *buildapi.BuildConfig, revision *buildapi.SourceRevision, imageRepoGetter osclient.ImageRepositoryNamespaceGetter) (*buildapi.Build, error) {
123
+	imageSubstitutions := make(map[string]string)
69 124
 	for _, trigger := range config.Triggers {
70 125
 		if trigger.Type != buildapi.ImageChangeBuildTriggerType {
71 126
 			continue
... ...
@@ -111,7 +176,7 @@ func GenerateBuildWithImageTag(config *buildapi.BuildConfig, revision *buildapi.
111 111
 		imageSubstitutions[icTrigger.Image] = imageRef
112 112
 	}
113 113
 	glog.V(4).Infof("Generating build from config for build config %s", config.Name)
114
-	build := GenerateBuildFromConfig(config, revision, imageSubstitutions)
114
+	build := GenerateBuildFromConfig(config, revision, imageSubstitutions, nil)
115 115
 	return build, nil
116 116
 }
117 117
 
... ...
@@ -124,6 +189,7 @@ func SubstituteImageReferences(build *buildapi.Build, oldImage string, newImage
124 124
 		build.Parameters.Strategy.DockerStrategy.Image = newImage
125 125
 	case build.Parameters.Strategy.Type == buildapi.STIBuildStrategyType &&
126 126
 		build.Parameters.Strategy.STIStrategy != nil &&
127
+		(build.Parameters.Strategy.STIStrategy.From == nil || build.Parameters.Strategy.STIStrategy.From.Name == "") &&
127 128
 		build.Parameters.Strategy.STIStrategy.Image == oldImage:
128 129
 		build.Parameters.Strategy.STIStrategy.Image = newImage
129 130
 	case build.Parameters.Strategy.Type == buildapi.CustomBuildStrategyType:
... ...
@@ -155,3 +221,18 @@ func SubstituteImageReferences(build *buildapi.Build, oldImage string, newImage
155 155
 		}
156 156
 	}
157 157
 }
158
+
159
+// SubstituteImageRepoReferences uses references to an image repository to set an actual image name
160
+// It also clears the ImageRepo reference from the BuildStrategy, if one was set.  The imagereference
161
+// field will be used explicitly.
162
+func SubstituteImageRepoReferences(build *buildapi.Build, imageRepo kapi.ObjectReference, newImage string) {
163
+	switch {
164
+	case build.Parameters.Strategy.Type == buildapi.STIBuildStrategyType &&
165
+		build.Parameters.Strategy.STIStrategy != nil &&
166
+		build.Parameters.Strategy.STIStrategy.From != nil &&
167
+		build.Parameters.Strategy.STIStrategy.From.Name == imageRepo.Name &&
168
+		build.Parameters.Strategy.STIStrategy.From.Namespace == imageRepo.Namespace:
169
+		build.Parameters.Strategy.STIStrategy.Image = newImage
170
+		build.Parameters.Strategy.STIStrategy.From = nil
171
+	}
172
+}
... ...
@@ -22,6 +22,7 @@ const (
22 22
 
23 23
 	imageRepoName          = "testRepo"
24 24
 	unmatchedImageRepoName = "unmatchedRepo"
25
+	imageRepoNamespace     = "testns"
25 26
 )
26 27
 
27 28
 func TestGenerateBuildFromConfig(t *testing.T) {
... ...
@@ -52,7 +53,7 @@ func TestGenerateBuildFromConfig(t *testing.T) {
52 52
 			Commit: "abcd",
53 53
 		},
54 54
 	}
55
-	build := GenerateBuildFromConfig(bc, revision, nil)
55
+	build := GenerateBuildFromConfig(bc, revision, nil, nil)
56 56
 	if !reflect.DeepEqual(source, build.Parameters.Source) {
57 57
 		t.Errorf("Build source does not match BuildConfig source")
58 58
 	}
... ...
@@ -73,7 +74,7 @@ func TestGenerateBuildFromConfig(t *testing.T) {
73 73
 	}
74 74
 }
75 75
 
76
-func TestGenerateBuildWithImageTag(t *testing.T) {
76
+func TestGenerateBuildWithImageTagForDockerStrategy(t *testing.T) {
77 77
 	source := mockSource()
78 78
 	strategy := mockDockerStrategy()
79 79
 	strategy.DockerStrategy.Image = originalImage
... ...
@@ -241,6 +242,82 @@ func TestGenerateBuildWithImageTagUnmatchedTag(t *testing.T) {
241 241
 	}
242 242
 }
243 243
 
244
+func TestGenerateBuildWithImageTagForSTIStrategyImage(t *testing.T) {
245
+	source := mockSource()
246
+	strategy := mockSTIStrategyForImage()
247
+	output := mockOutput()
248
+	imageRepoGetter := &mockImageRepositoryNamespaceGetter{"", imageRepoName}
249
+
250
+	bc := &buildapi.BuildConfig{
251
+		ObjectMeta: kapi.ObjectMeta{
252
+			Name: "test-build-config",
253
+		},
254
+		Parameters: buildapi.BuildParameters{
255
+			Source: source,
256
+			Revision: &buildapi.SourceRevision{
257
+				Type: buildapi.BuildSourceGit,
258
+				Git: &buildapi.GitSourceRevision{
259
+					Commit: "1234",
260
+				},
261
+			},
262
+			Strategy: strategy,
263
+			Output:   output,
264
+		},
265
+		Triggers: []buildapi.BuildTriggerPolicy{
266
+			{
267
+				Type: buildapi.ImageChangeBuildTriggerType,
268
+				ImageChange: &buildapi.ImageChangeTrigger{
269
+					Image: originalImage,
270
+					From: kapi.ObjectReference{
271
+						Name: imageRepoName,
272
+					},
273
+					Tag: tagName,
274
+				},
275
+			},
276
+		},
277
+	}
278
+
279
+	build, err := GenerateBuildWithImageTag(bc, nil, imageRepoGetter)
280
+	if err != nil {
281
+		t.Errorf("Unexpected error %v", err)
282
+	}
283
+	if build.Parameters.Strategy.STIStrategy.Image != newImage {
284
+		t.Errorf("STI base image value %s does not match expected value %s", build.Parameters.Strategy.STIStrategy.Image, newImage)
285
+	}
286
+}
287
+
288
+func TestGenerateBuildWithImageTagForSTIStrategyImageRepository(t *testing.T) {
289
+	source := mockSource()
290
+	strategy := mockSTIStrategyForImageRepository()
291
+	output := mockOutput()
292
+	imageRepoGetter := &mockImageRepositoryNamespaceGetter{imageRepoNamespace, imageRepoName}
293
+
294
+	bc := &buildapi.BuildConfig{
295
+		ObjectMeta: kapi.ObjectMeta{
296
+			Name: "test-build-config",
297
+		},
298
+		Parameters: buildapi.BuildParameters{
299
+			Source: source,
300
+			Revision: &buildapi.SourceRevision{
301
+				Type: buildapi.BuildSourceGit,
302
+				Git: &buildapi.GitSourceRevision{
303
+					Commit: "1234",
304
+				},
305
+			},
306
+			Strategy: strategy,
307
+			Output:   output,
308
+		},
309
+	}
310
+
311
+	build, err := GenerateBuildWithImageTag(bc, nil, imageRepoGetter)
312
+	if err != nil {
313
+		t.Errorf("Unexpected error %v", err)
314
+	}
315
+	if build.Parameters.Strategy.STIStrategy.Image != newImage {
316
+		t.Errorf("STI base image value %s does not match expected value %s", build.Parameters.Strategy.STIStrategy.Image, newImage)
317
+	}
318
+}
319
+
244 320
 func TestGenerateBuildFromBuild(t *testing.T) {
245 321
 	source := mockSource()
246 322
 	strategy := mockDockerStrategy()
... ...
@@ -276,7 +353,7 @@ func TestSubstituteImageDockerNil(t *testing.T) {
276 276
 	strategy := mockDockerStrategy()
277 277
 	output := mockOutput()
278 278
 	bc := mockBuildConfig(source, strategy, output)
279
-	build := GenerateBuildFromConfig(bc, nil, nil)
279
+	build := GenerateBuildFromConfig(bc, nil, nil, nil)
280 280
 
281 281
 	// Docker build with nil base image
282 282
 	// base image should still be nil
... ...
@@ -291,7 +368,7 @@ func TestSubstituteImageDockerMatch(t *testing.T) {
291 291
 	strategy := mockDockerStrategy()
292 292
 	output := mockOutput()
293 293
 	bc := mockBuildConfig(source, strategy, output)
294
-	build := GenerateBuildFromConfig(bc, nil, nil)
294
+	build := GenerateBuildFromConfig(bc, nil, nil, nil)
295 295
 
296 296
 	// Docker build with a matched base image
297 297
 	// base image should be replaced.
... ...
@@ -310,7 +387,7 @@ func TestSubstituteImageDockerMismatch(t *testing.T) {
310 310
 	strategy := mockDockerStrategy()
311 311
 	output := mockOutput()
312 312
 	bc := mockBuildConfig(source, strategy, output)
313
-	build := GenerateBuildFromConfig(bc, nil, nil)
313
+	build := GenerateBuildFromConfig(bc, nil, nil, nil)
314 314
 
315 315
 	// Docker build with an unmatched base image
316 316
 	// base image should not be replaced.
... ...
@@ -322,10 +399,10 @@ func TestSubstituteImageDockerMismatch(t *testing.T) {
322 322
 
323 323
 func TestSubstituteImageSTIMatch(t *testing.T) {
324 324
 	source := mockSource()
325
-	strategy := mockSTIStrategy()
325
+	strategy := mockSTIStrategyForImage()
326 326
 	output := mockOutput()
327 327
 	bc := mockBuildConfig(source, strategy, output)
328
-	build := GenerateBuildFromConfig(bc, nil, nil)
328
+	build := GenerateBuildFromConfig(bc, nil, nil, nil)
329 329
 
330 330
 	// STI build with a matched base image
331 331
 	// base image should be replaced
... ...
@@ -336,15 +413,33 @@ func TestSubstituteImageSTIMatch(t *testing.T) {
336 336
 	if bc.Parameters.Strategy.STIStrategy.Image != originalImage {
337 337
 		t.Errorf("STI BuildConfig was updated when Build was modified")
338 338
 	}
339
+}
340
+
341
+func TestSubstituteImageRepositorySTIMatch(t *testing.T) {
342
+	source := mockSource()
343
+	strategy := mockSTIStrategyForImageRepository()
344
+	repoRef := *strategy.STIStrategy.From
345
+	output := mockOutput()
346
+	bc := mockBuildConfig(source, strategy, output)
347
+	build := GenerateBuildFromConfig(bc, nil, nil, nil)
339 348
 
349
+	// STI build with a matched base image
350
+	// base image should be replaced
351
+	SubstituteImageRepoReferences(build, repoRef, newImage)
352
+	if build.Parameters.Strategy.STIStrategy.Image != newImage {
353
+		t.Errorf("Base image name was not substituted in sti strategy")
354
+	}
355
+	if bc.Parameters.Strategy.STIStrategy.Image != "" {
356
+		t.Errorf("STI BuildConfig was updated when Build was modified")
357
+	}
340 358
 }
341 359
 
342 360
 func TestSubstituteImageSTIMismatch(t *testing.T) {
343 361
 	source := mockSource()
344
-	strategy := mockSTIStrategy()
362
+	strategy := mockSTIStrategyForImage()
345 363
 	output := mockOutput()
346 364
 	bc := mockBuildConfig(source, strategy, output)
347
-	build := GenerateBuildFromConfig(bc, nil, nil)
365
+	build := GenerateBuildFromConfig(bc, nil, nil, nil)
348 366
 
349 367
 	// STI build with an unmatched base image
350 368
 	// base image should not be replaced
... ...
@@ -354,12 +449,30 @@ func TestSubstituteImageSTIMismatch(t *testing.T) {
354 354
 	}
355 355
 }
356 356
 
357
+func TestSubstituteImageRepositorySTIMismatch(t *testing.T) {
358
+	source := mockSource()
359
+	strategy := mockSTIStrategyForImage()
360
+	output := mockOutput()
361
+	bc := mockBuildConfig(source, strategy, output)
362
+	imageRepoRef := kapi.ObjectReference{
363
+		Name: "unmatched",
364
+	}
365
+	build := GenerateBuildFromConfig(bc, nil, nil, nil)
366
+
367
+	// STI build with an unmatched image repository
368
+	// base image should not be set
369
+	SubstituteImageRepoReferences(build, imageRepoRef, "dummy")
370
+	if build.Parameters.Strategy.STIStrategy.Image == "dummy" {
371
+		t.Errorf("Base image name was improperly set in STI strategy")
372
+	}
373
+}
374
+
357 375
 func TestSubstituteImageCustomAllMatch(t *testing.T) {
358 376
 	source := mockSource()
359 377
 	strategy := mockCustomStrategy()
360 378
 	output := mockOutput()
361 379
 	bc := mockBuildConfig(source, strategy, output)
362
-	build := GenerateBuildFromConfig(bc, nil, nil)
380
+	build := GenerateBuildFromConfig(bc, nil, nil, nil)
363 381
 
364 382
 	// Full custom build with a Image and a well defined environment variable image value,
365 383
 	// both should be replaced.  Additional environment variables should not be touched.
... ...
@@ -393,7 +506,7 @@ func TestSubstituteImageCustomAllMismatch(t *testing.T) {
393 393
 	strategy := mockCustomStrategy()
394 394
 	output := mockOutput()
395 395
 	bc := mockBuildConfig(source, strategy, output)
396
-	build := GenerateBuildFromConfig(bc, nil, nil)
396
+	build := GenerateBuildFromConfig(bc, nil, nil, nil)
397 397
 
398 398
 	// Full custom build with base image that is not matched
399 399
 	// Base image name should be unchanged
... ...
@@ -408,7 +521,7 @@ func TestSubstituteImageCustomBaseMatchEnvMismatch(t *testing.T) {
408 408
 	strategy := mockCustomStrategy()
409 409
 	output := mockOutput()
410 410
 	bc := mockBuildConfig(source, strategy, output)
411
-	build := GenerateBuildFromConfig(bc, nil, nil)
411
+	build := GenerateBuildFromConfig(bc, nil, nil, nil)
412 412
 
413 413
 	// Full custom build with a Image and a well defined environment variable image value that does not match the new image
414 414
 	// Only base image should be replaced.  Environment variables should not be touched.
... ...
@@ -435,7 +548,7 @@ func TestSubstituteImageCustomBaseMatchEnvMissing(t *testing.T) {
435 435
 	strategy := mockCustomStrategy()
436 436
 	output := mockOutput()
437 437
 	bc := mockBuildConfig(source, strategy, output)
438
-	build := GenerateBuildFromConfig(bc, nil, nil)
438
+	build := GenerateBuildFromConfig(bc, nil, nil, nil)
439 439
 
440 440
 	// Custom build with a base Image but no image environment variable.
441 441
 	// base image should be replaced, new image environment variable should be added,
... ...
@@ -462,7 +575,7 @@ func TestSubstituteImageCustomBaseMatchEnvNil(t *testing.T) {
462 462
 	strategy := mockCustomStrategy()
463 463
 	output := mockOutput()
464 464
 	bc := mockBuildConfig(source, strategy, output)
465
-	build := GenerateBuildFromConfig(bc, nil, nil)
465
+	build := GenerateBuildFromConfig(bc, nil, nil, nil)
466 466
 
467 467
 	// Custom build with a base Image but no environment variables
468 468
 	// base image should be replaced, new image environment variable should be added
... ...
@@ -497,7 +610,7 @@ func mockDockerStrategy() buildapi.BuildStrategy {
497 497
 	}
498 498
 }
499 499
 
500
-func mockSTIStrategy() buildapi.BuildStrategy {
500
+func mockSTIStrategyForImage() buildapi.BuildStrategy {
501 501
 	return buildapi.BuildStrategy{
502 502
 		Type: buildapi.STIBuildStrategyType,
503 503
 		STIStrategy: &buildapi.STIBuildStrategy{
... ...
@@ -506,6 +619,19 @@ func mockSTIStrategy() buildapi.BuildStrategy {
506 506
 	}
507 507
 }
508 508
 
509
+func mockSTIStrategyForImageRepository() buildapi.BuildStrategy {
510
+	return buildapi.BuildStrategy{
511
+		Type: buildapi.STIBuildStrategyType,
512
+		STIStrategy: &buildapi.STIBuildStrategy{
513
+			From: &kapi.ObjectReference{
514
+				Name:      imageRepoName,
515
+				Namespace: imageRepoNamespace,
516
+			},
517
+			Tag: tagName,
518
+		},
519
+	}
520
+}
521
+
509 522
 func mockCustomStrategy() buildapi.BuildStrategy {
510 523
 	return buildapi.BuildStrategy{
511 524
 		Type: buildapi.CustomBuildStrategyType,
... ...
@@ -15,13 +15,28 @@ import (
15 15
 type okImageRepositoryNamespaceGetter struct{}
16 16
 
17 17
 func (m *okImageRepositoryNamespaceGetter) GetByNamespace(namespace, name string) (*imageapi.ImageRepository, error) {
18
-	return nil, nil
18
+	return &imageapi.ImageRepository{
19
+		Status: imageapi.ImageRepositoryStatus{
20
+			DockerImageRepository: "repository/image",
21
+		},
22
+		Tags: map[string]string{
23
+			"latest": "latest",
24
+		},
25
+	}, nil
19 26
 }
20 27
 
21 28
 type okBuildConfigGetter struct{}
22 29
 
23 30
 func (*okBuildConfigGetter) Get(namespace, name string) (*api.BuildConfig, error) {
24 31
 	return &api.BuildConfig{
32
+		Parameters: api.BuildParameters{
33
+			Strategy: api.BuildStrategy{
34
+				Type: "STI",
35
+				STIStrategy: &api.STIBuildStrategy{
36
+					Image: "repository/builder-image",
37
+				},
38
+			},
39
+		},
25 40
 		Triggers: []api.BuildTriggerPolicy{
26 41
 			{
27 42
 				Type: api.GithubWebHookBuildTriggerType,
... ...
@@ -234,7 +249,14 @@ func (i *testBuildConfigInterface) Get(namespace, name string) (*api.BuildConfig
234 234
 func TestInvokeWebhookOk(t *testing.T) {
235 235
 	var buildRequest *api.Build
236 236
 	buildConfig := &api.BuildConfig{
237
-		Parameters: api.BuildParameters{},
237
+		Parameters: api.BuildParameters{
238
+			Strategy: api.BuildStrategy{
239
+				Type: "STI",
240
+				STIStrategy: &api.STIBuildStrategy{
241
+					Image: "repository/builder-image",
242
+				},
243
+			},
244
+		},
238 245
 	}
239 246
 
240 247
 	server := httptest.NewServer(NewController(
... ...
@@ -8,9 +8,19 @@ import (
8 8
 	"strings"
9 9
 	"testing"
10 10
 
11
+	kapi "github.com/GoogleCloudPlatform/kubernetes/pkg/api"
11 12
 	"github.com/openshift/origin/pkg/build/api"
12 13
 )
13 14
 
15
+var mockBuildStrategy api.BuildStrategy = api.BuildStrategy{
16
+	Type: "STI",
17
+	STIStrategy: &api.STIBuildStrategy{
18
+		From: &kapi.ObjectReference{
19
+			Name: "repository/image",
20
+		},
21
+	},
22
+}
23
+
14 24
 func GivenRequest(method string) *http.Request {
15 25
 	req, _ := http.NewRequest(method, "http://someurl.com", nil)
16 26
 	return req
... ...
@@ -106,7 +116,7 @@ func TestExtractWithEmptyPayload(t *testing.T) {
106 106
 					Ref: "master",
107 107
 				},
108 108
 			},
109
-			Strategy: api.BuildStrategy{},
109
+			Strategy: mockBuildStrategy,
110 110
 		},
111 111
 	}
112 112
 	plugin := New()
... ...
@@ -140,7 +150,7 @@ func TestExtractWithUnmatchedRefGitPayload(t *testing.T) {
140 140
 					Ref: "asdfkasdfasdfasdfadsfkjhkhkh",
141 141
 				},
142 142
 			},
143
-			Strategy: api.BuildStrategy{},
143
+			Strategy: mockBuildStrategy,
144 144
 		},
145 145
 	}
146 146
 	plugin := New()
... ...
@@ -175,7 +185,7 @@ func TestExtractWithGitPayload(t *testing.T) {
175 175
 					Ref: "master",
176 176
 				},
177 177
 			},
178
-			Strategy: api.BuildStrategy{},
178
+			Strategy: mockBuildStrategy,
179 179
 		},
180 180
 	}
181 181
 	plugin := New()
... ...
@@ -16,7 +16,14 @@ import (
16 16
 type okImageRepositoryNamespaceGetter struct{}
17 17
 
18 18
 func (m *okImageRepositoryNamespaceGetter) GetByNamespace(namespace, name string) (*imageapi.ImageRepository, error) {
19
-	return nil, nil
19
+	return &imageapi.ImageRepository{
20
+		Status: imageapi.ImageRepositoryStatus{
21
+			DockerImageRepository: "repository/image",
22
+		},
23
+		Tags: map[string]string{
24
+			"latest": "latest",
25
+		},
26
+	}, nil
20 27
 }
21 28
 
22 29
 type okBuildConfigGetter struct{}
... ...
@@ -38,10 +45,18 @@ func (c *okBuildConfigGetter) Get(namespace, name string) (*api.BuildConfig, err
38 38
 					URI: "git://github.com/my/repo.git",
39 39
 				},
40 40
 			},
41
+			Strategy: mockBuildStrategy,
41 42
 		},
42 43
 	}, nil
43 44
 }
44 45
 
46
+var mockBuildStrategy api.BuildStrategy = api.BuildStrategy{
47
+	Type: "STI",
48
+	STIStrategy: &api.STIBuildStrategy{
49
+		Image: "repository/image",
50
+	},
51
+}
52
+
45 53
 type okBuildCreator struct{}
46 54
 
47 55
 func (c *okBuildCreator) Create(namespace string, build *api.Build) error {
... ...
@@ -207,6 +222,7 @@ func setup(t *testing.T, filename, eventType string) *testContext {
207 207
 						URI: "git://github.com/my/repo.git",
208 208
 					},
209 209
 				},
210
+				Strategy: mockBuildStrategy,
210 211
 			},
211 212
 		},
212 213
 		path: "/foobar",
... ...
@@ -78,7 +78,54 @@ func (d *BuildDescriber) DescribeUser(out *tabwriter.Writer, label string, u bui
78 78
 	}
79 79
 }
80 80
 
81
-func (d *BuildDescriber) DescribeParameters(p buildapi.BuildParameters, out *tabwriter.Writer) {
81
+func (d *BuildDescriber) Describe(namespace, name string) (string, error) {
82
+	c := d.Builds(namespace)
83
+	build, err := c.Get(name)
84
+	if err != nil {
85
+		return "", err
86
+	}
87
+	return tabbedString(func(out *tabwriter.Writer) error {
88
+		formatMeta(out, build.ObjectMeta)
89
+		formatString(out, "Status", bold(build.Status))
90
+		if build.StartTimestamp != nil {
91
+			formatString(out, "Started", build.StartTimestamp.Time)
92
+		}
93
+		if build.CompletionTimestamp != nil {
94
+			formatString(out, "Finished", build.CompletionTimestamp.Time)
95
+		}
96
+		// Create the time object with second-level precision so we don't get
97
+		// output like "duration: 1.2724395728934s"
98
+		t := util.Now().Rfc3339Copy()
99
+		if build.StartTimestamp != nil && build.CompletionTimestamp != nil {
100
+			// time a build ran from pod creation to build finish or cancel
101
+			formatString(out, "Duration", build.CompletionTimestamp.Sub(build.StartTimestamp.Rfc3339Copy().Time))
102
+		} else if build.CompletionTimestamp != nil && build.Status == buildapi.BuildStatusCancelled {
103
+			// time a build waited for its pod before ultimately being canceled before that pod was created
104
+			formatString(out, "Duration", fmt.Sprintf("waited for %s", build.CompletionTimestamp.Sub(build.CreationTimestamp.Rfc3339Copy().Time)))
105
+		} else if build.CompletionTimestamp != nil && build.Status != buildapi.BuildStatusCancelled {
106
+			// for some reason we never saw the pod enter the running state, so we don't know when it
107
+			// "started", so instead print out the time from creation to completion.
108
+			formatString(out, "Duration", build.CompletionTimestamp.Sub(build.CreationTimestamp.Rfc3339Copy().Time))
109
+		} else if build.StartTimestamp == nil && build.Status != buildapi.BuildStatusCancelled {
110
+			// time a new build has been waiting for its pod to be created so it can run
111
+			formatString(out, "Duration", fmt.Sprintf("waiting for %s", t.Sub(build.CreationTimestamp.Rfc3339Copy().Time)))
112
+		} else if build.CompletionTimestamp == nil {
113
+			// time a still running build has been running in a pod
114
+			formatString(out, "Duration", fmt.Sprintf("running for %s", t.Sub(build.StartTimestamp.Rfc3339Copy().Time)))
115
+		}
116
+		formatString(out, "Build Pod", build.PodName)
117
+		describeBuildParameters(build.Parameters, out)
118
+		return nil
119
+	})
120
+}
121
+
122
+// BuildConfigDescriber generates information about a buildConfig
123
+type BuildConfigDescriber struct {
124
+	client.Interface
125
+	host string
126
+}
127
+
128
+func describeBuildParameters(p buildapi.BuildParameters, out *tabwriter.Writer) {
82 129
 	formatString(out, "Strategy", p.Strategy.Type)
83 130
 	switch p.Strategy.Type {
84 131
 	case buildapi.DockerBuildStrategyType:
... ...
@@ -89,10 +136,7 @@ func (d *BuildDescriber) DescribeParameters(p buildapi.BuildParameters, out *tab
89 89
 			formatString(out, "Image", p.Strategy.DockerStrategy.Image)
90 90
 		}
91 91
 	case buildapi.STIBuildStrategyType:
92
-		formatString(out, "Image", p.Strategy.STIStrategy.Image)
93
-		if p.Strategy.STIStrategy.Incremental {
94
-			formatString(out, "Incremental Build", "yes")
95
-		}
92
+		describeSTIStrategy(p.Strategy.STIStrategy, out)
96 93
 	case buildapi.CustomBuildStrategyType:
97 94
 		formatString(out, "Image", p.Strategy.CustomStrategy.Image)
98 95
 		if p.Strategy.CustomStrategy.ExposeDockerSocket {
... ...
@@ -122,62 +166,36 @@ func (d *BuildDescriber) DescribeParameters(p buildapi.BuildParameters, out *tab
122 122
 
123 123
 	formatString(out, "Output Spec", p.Output.DockerImageReference)
124 124
 	if p.Revision != nil && p.Revision.Type == buildapi.BuildSourceGit && p.Revision.Git != nil {
125
+		buildDescriber := &BuildDescriber{}
126
+
125 127
 		formatString(out, "Git Commit", p.Revision.Git.Commit)
126
-		d.DescribeUser(out, "Revision Author", p.Revision.Git.Author)
127
-		d.DescribeUser(out, "Revision Committer", p.Revision.Git.Committer)
128
+		buildDescriber.DescribeUser(out, "Revision Author", p.Revision.Git.Author)
129
+		buildDescriber.DescribeUser(out, "Revision Committer", p.Revision.Git.Committer)
128 130
 		if len(p.Revision.Git.Message) > 0 {
129 131
 			formatString(out, "Revision Message", p.Revision.Git.Message)
130 132
 		}
131 133
 	}
132 134
 }
133 135
 
134
-func (d *BuildDescriber) Describe(namespace, name string) (string, error) {
135
-	c := d.Builds(namespace)
136
-	build, err := c.Get(name)
137
-	if err != nil {
138
-		return "", err
139
-	}
140
-
141
-	return tabbedString(func(out *tabwriter.Writer) error {
142
-		formatMeta(out, build.ObjectMeta)
143
-		formatString(out, "Status", bold(build.Status))
144
-		if build.StartTimestamp != nil {
145
-			formatString(out, "Started", build.StartTimestamp.Time)
146
-		}
147
-		if build.CompletionTimestamp != nil {
148
-			formatString(out, "Finished", build.CompletionTimestamp.Time)
136
+func describeSTIStrategy(s *buildapi.STIBuildStrategy, out *tabwriter.Writer) {
137
+	if s.From != nil && s.From.Name != "" {
138
+		if s.From.Namespace != "" {
139
+			formatString(out, "Image Repository", fmt.Sprintf("%s/%s", s.From.Name, s.From.Namespace))
140
+		} else {
141
+			formatString(out, "Image Repository", s.From.Name)
149 142
 		}
150
-		// Create the time object with second-level precision so we don't get
151
-		// output like "duration: 1.2724395728934s"
152
-		t := util.Now().Rfc3339Copy()
153
-		if build.StartTimestamp != nil && build.CompletionTimestamp != nil {
154
-			// time a build ran from pod creation to build finish or cancel
155
-			formatString(out, "Duration", build.CompletionTimestamp.Sub(build.StartTimestamp.Rfc3339Copy().Time))
156
-		} else if build.CompletionTimestamp != nil && build.Status == buildapi.BuildStatusCancelled {
157
-			// time a build waited for its pod before ultimately being canceled before that pod was created
158
-			formatString(out, "Duration", fmt.Sprintf("waited for %s", build.CompletionTimestamp.Sub(build.CreationTimestamp.Rfc3339Copy().Time)))
159
-		} else if build.CompletionTimestamp != nil && build.Status != buildapi.BuildStatusCancelled {
160
-			// for some reason we never saw the pod enter the running state, so we don't know when it
161
-			// "started", so instead print out the time from creation to completion.
162
-			formatString(out, "Duration", build.CompletionTimestamp.Sub(build.CreationTimestamp.Rfc3339Copy().Time))
163
-		} else if build.StartTimestamp == nil && build.Status != buildapi.BuildStatusCancelled {
164
-			// time a new build has been waiting for its pod to be created so it can run
165
-			formatString(out, "Duration", fmt.Sprintf("waiting for %s", t.Sub(build.CreationTimestamp.Rfc3339Copy().Time)))
166
-		} else if build.CompletionTimestamp == nil {
167
-			// time a still running build has been running in a pod
168
-			formatString(out, "Duration", fmt.Sprintf("running for %s", t.Sub(build.StartTimestamp.Rfc3339Copy().Time)))
143
+		if s.Tag != "" {
144
+			formatString(out, "Image Repository Tag", s.Tag)
169 145
 		}
170
-
171
-		formatString(out, "Build Pod", build.PodName)
172
-		d.DescribeParameters(build.Parameters, out)
173
-		return nil
174
-	})
175
-}
176
-
177
-// BuildConfigDescriber generates information about a buildConfig
178
-type BuildConfigDescriber struct {
179
-	client.Interface
180
-	host string
146
+	} else {
147
+		formatString(out, "Builder Image", s.Image)
148
+	}
149
+	if s.Scripts != "" {
150
+		formatString(out, "Scripts", s.Scripts)
151
+	}
152
+	if s.Incremental {
153
+		formatString(out, "Incremental Build", "yes")
154
+	}
181 155
 }
182 156
 
183 157
 // DescribeTriggers generates information about the triggers associated with a buildconfig
... ...
@@ -209,11 +227,9 @@ func (d *BuildConfigDescriber) Describe(namespace, name string) (string, error)
209 209
 		return "", err
210 210
 	}
211 211
 
212
-	buildDescriber := &BuildDescriber{}
213
-
214 212
 	return tabbedString(func(out *tabwriter.Writer) error {
215 213
 		formatMeta(out, buildConfig.ObjectMeta)
216
-		buildDescriber.DescribeParameters(buildConfig.Parameters, out)
214
+		describeBuildParameters(buildConfig.Parameters, out)
217 215
 		d.DescribeTriggers(buildConfig, d.host, out)
218 216
 		return nil
219 217
 	})
220 218
new file mode 100644
... ...
@@ -0,0 +1,107 @@
0
+{
1
+  "apiVersion": "v1beta1",
2
+  "items": [
3
+    {
4
+      "apiVersion": "v1beta1",
5
+      "dockerImageRepository": "openshift/ruby-20-centos",
6
+      "kind": "ImageRepository",
7
+      "metadata": {
8
+        "name": "ruby-20-centos-buildcli"
9
+      },
10
+      "tags": {
11
+        "valid": "success"
12
+      }
13
+    },
14
+    {
15
+      "apiVersion": "v1beta1",
16
+      "kind": "BuildConfig",
17
+      "labels": {
18
+        "name": "ruby-sample-build"
19
+      },
20
+      "metadata": {
21
+        "name": "ruby-sample-build-validtag"
22
+      },
23
+      "parameters": {
24
+        "output": {
25
+          "to": {
26
+            "name": "origin-ruby-sample"
27
+          }
28
+        },
29
+        "source": {
30
+          "git": {
31
+            "uri": "git://github.com/openshift/ruby-hello-world.git"
32
+          },
33
+          "type": "Git"
34
+        },
35
+        "strategy": {
36
+          "stiStrategy": {
37
+            "from": {
38
+              "name": "ruby-20-centos-buildcli"
39
+            },
40
+            "tag": "valid",
41
+            "scripts": "https://raw.githubusercontent.com/openshift/ruby-20-centos/master/.sti/bin"
42
+          },
43
+          "type": "STI"
44
+        }
45
+      },
46
+      "triggers": [
47
+        {
48
+          "imageChange": {
49
+            "from": {
50
+              "name": "ruby-20-centos-buildcli"
51
+            },
52
+            "image": "openshift/ruby-20-centos",
53
+            "tag": "valid"
54
+          },
55
+          "type": "imageChange"
56
+        }
57
+      ]
58
+    },
59
+    {
60
+      "apiVersion": "v1beta1",
61
+      "kind": "BuildConfig",
62
+      "labels": {
63
+        "name": "ruby-sample-build"
64
+      },
65
+      "metadata": {
66
+        "name": "ruby-sample-build-invalidtag"
67
+      },
68
+      "parameters": {
69
+        "output": {
70
+          "to": {
71
+            "name": "origin-ruby-sample"
72
+          }
73
+        },
74
+        "source": {
75
+          "git": {
76
+            "uri": "git://github.com/openshift/ruby-hello-world.git"
77
+          },
78
+          "type": "Git"
79
+        },
80
+        "strategy": {
81
+          "stiStrategy": {
82
+	    "from": {
83
+	      "name": "ruby-20-centos-buildcli"
84
+	    },
85
+            "tag": "invalid",
86
+            "scripts": "https://raw.githubusercontent.com/openshift/ruby-20-centos/master/.sti/bin"
87
+          },
88
+          "type": "STI"
89
+        }
90
+      },
91
+      "triggers": [
92
+        {
93
+          "imageChange": {
94
+            "from": {
95
+              "name": "ruby-20-centos-buildcli"
96
+            },
97
+            "image": "openshift/ruby-20-centos",
98
+            "tag": "invalid"
99
+          },
100
+          "type": "imageChange"
101
+        }
102
+      ]
103
+    }
104
+  ],
105
+  "kind": "List"
106
+}
... ...
@@ -106,13 +106,6 @@ func TestSimpleImageChangeBuildTrigger(t *testing.T) {
106 106
 	}); err != nil {
107 107
 		t.Fatalf("unexpected error: %v", err)
108 108
 	}
109
-	/*
110
-		// update the image tag to ref-2 in the imagerepo so we get another build event using that tag.
111
-		imageRepo.Tags["latest"] = "ref-2"
112
-		if _, err = openshift.Client.ImageRepositories(testutil.Namespace()).Update(imageRepo); err != nil {
113
-			t.Fatalf("Error updating imageRepo: %v", err)
114
-		}
115
-	*/
116 109
 	event = <-watch.ResultChan()
117 110
 	if e, a := watchapi.Added, event.Type; e != a {
118 111
 		t.Fatalf("expected watch event type %s, got %s", e, a)
... ...
@@ -135,7 +128,126 @@ func TestSimpleImageChangeBuildTrigger(t *testing.T) {
135 135
 		t.Fatalf("Expected build with label %s=%s from build config got %s=%s", "testlabel", "testvalue", "testlabel", newBuild.Labels["testlabel"])
136 136
 	}
137 137
 
138
-	event = <-watch2.ResultChan()
138
+	<-watch2.ResultChan()
139
+	updatedConfig, err = openshift.Client.BuildConfigs(testutil.Namespace()).Get(config.Name)
140
+	if err != nil {
141
+		t.Fatalf("Couldn't get BuildConfig: %v", err)
142
+	}
143
+	if updatedConfig.Triggers[0].ImageChange.LastTriggeredImageID != "ref-2-random" {
144
+		t.Errorf("unexpected trigger id: %#v", updatedConfig.Triggers[0].ImageChange)
145
+	}
146
+}
147
+
148
+func TestSimpleImageChangeBuildTriggerFromRef(t *testing.T) {
149
+	testutil.DeleteAllEtcdKeys()
150
+	openshift := NewTestOpenshift(t)
151
+	defer openshift.Close()
152
+
153
+	imageRepo := &imageapi.ImageRepository{
154
+		ObjectMeta:            kapi.ObjectMeta{Name: "test-image-trigger-repo"},
155
+		DockerImageRepository: "registry:8080/openshift/test-image-trigger",
156
+		Tags: map[string]string{
157
+			"latest": "latest",
158
+		},
159
+	}
160
+
161
+	config := imageChangeBuildConfigFromRef()
162
+
163
+	created, err := openshift.Client.BuildConfigs(testutil.Namespace()).Create(config)
164
+	if err != nil {
165
+		t.Fatalf("Couldn't create BuildConfig: %v", err)
166
+	}
167
+
168
+	watch, err := openshift.Client.Builds(testutil.Namespace()).Watch(labels.Everything(), fields.Everything(), created.ResourceVersion)
169
+	if err != nil {
170
+		t.Fatalf("Couldn't subscribe to Builds %v", err)
171
+	}
172
+	defer watch.Stop()
173
+
174
+	watch2, err := openshift.Client.BuildConfigs(testutil.Namespace()).Watch(labels.Everything(), fields.Everything(), created.ResourceVersion)
175
+	if err != nil {
176
+		t.Fatalf("Couldn't subscribe to BuildConfigs %v", err)
177
+	}
178
+	defer watch2.Stop()
179
+
180
+	imageRepo, err = openshift.Client.ImageRepositories(testutil.Namespace()).Create(imageRepo)
181
+	if err != nil {
182
+		t.Fatalf("Couldn't create ImageRepository: %v", err)
183
+	}
184
+
185
+	// wait for initial build event from the creation of the imagerepo with tag latest
186
+	event := <-watch.ResultChan()
187
+	if e, a := watchapi.Added, event.Type; e != a {
188
+		t.Fatalf("expected watch event type %s, got %s", e, a)
189
+	}
190
+	newBuild := event.Object.(*buildapi.Build)
191
+	if newBuild.Parameters.Strategy.STIStrategy.Image != "registry:8080/openshift/test-image-trigger:latest" {
192
+		i, _ := openshift.Client.ImageRepositories(testutil.Namespace()).Get(imageRepo.Name)
193
+		bc, _ := openshift.Client.BuildConfigs(testutil.Namespace()).Get(config.Name)
194
+		t.Fatalf("Expected build with base image %s, got %s\n, imagerepo is %v\ntrigger is %s\n", "registry:8080/openshift/test-image-trigger:latest", newBuild.Parameters.Strategy.STIStrategy.Image, i, bc.Triggers[0].ImageChange)
195
+	}
196
+	event = <-watch.ResultChan()
197
+	if e, a := watchapi.Modified, event.Type; e != a {
198
+		t.Fatalf("expected watch event type %s, got %s", e, a)
199
+	}
200
+	newBuild = event.Object.(*buildapi.Build)
201
+	if newBuild.Parameters.Output.DockerImageReference != "registry:8080/openshift/test-image-trigger:outputtag" {
202
+		t.Fatalf("Expected build with output image %s, got %s", "registry:8080/openshift/test-image-trigger:outputtag", newBuild.Parameters.Output.DockerImageReference)
203
+	}
204
+	if newBuild.Labels["testlabel"] != "testvalue" {
205
+		t.Fatalf("Expected build with label %s=%s from build config got %s=%s", "testlabel", "testvalue", "testlabel", newBuild.Labels["testlabel"])
206
+	}
207
+
208
+	// wait for build config to be updated
209
+	<-watch2.ResultChan()
210
+	updatedConfig, err := openshift.Client.BuildConfigs(testutil.Namespace()).Get(config.Name)
211
+	if err != nil {
212
+		t.Fatalf("Couldn't get BuildConfig: %v", err)
213
+	}
214
+	// the first tag did not have an image id, so the last trigger field is the pull spec
215
+	if updatedConfig.Triggers[0].ImageChange.LastTriggeredImageID != "registry:8080/openshift/test-image-trigger:latest" {
216
+		t.Errorf("Expected imageID equal to pull spec, got %s", updatedConfig.Triggers[0].ImageChange)
217
+	}
218
+
219
+	// trigger a build by posting a new image
220
+	if err := openshift.Client.ImageRepositoryMappings(testutil.Namespace()).Create(&imageapi.ImageRepositoryMapping{
221
+		ObjectMeta: kapi.ObjectMeta{
222
+			Namespace: testutil.Namespace(),
223
+			Name:      imageRepo.Name,
224
+		},
225
+		Tag: "latest",
226
+		Image: imageapi.Image{
227
+			ObjectMeta: kapi.ObjectMeta{
228
+				Name: "ref-2-random",
229
+			},
230
+			DockerImageReference: "registry:8080/openshift/test-image-trigger:ref-2",
231
+		},
232
+	}); err != nil {
233
+		t.Fatalf("unexpected error: %v", err)
234
+	}
235
+	event = <-watch.ResultChan()
236
+	if e, a := watchapi.Added, event.Type; e != a {
237
+		t.Fatalf("expected watch event type %s, got %s", e, a)
238
+	}
239
+	newBuild = event.Object.(*buildapi.Build)
240
+	if newBuild.Parameters.Strategy.STIStrategy.Image != "registry:8080/openshift/test-image-trigger:ref-2" {
241
+		i, _ := openshift.Client.ImageRepositories(testutil.Namespace()).Get(imageRepo.Name)
242
+		bc, _ := openshift.Client.BuildConfigs(testutil.Namespace()).Get(config.Name)
243
+		t.Fatalf("Expected build with base image %s, got %s\n, imagerepo is %v\trigger is %s\n", "registry:8080/openshift/test-image-trigger:ref-2", newBuild.Parameters.Strategy.STIStrategy.Image, i, bc.Triggers[0].ImageChange)
244
+	}
245
+	event = <-watch.ResultChan()
246
+	if e, a := watchapi.Modified, event.Type; e != a {
247
+		t.Fatalf("expected watch event type %s, got %s", e, a)
248
+	}
249
+	newBuild = event.Object.(*buildapi.Build)
250
+	if newBuild.Parameters.Output.DockerImageReference != "registry:8080/openshift/test-image-trigger:outputtag" {
251
+		t.Fatalf("Expected build with output image %s, got %s", "registry:8080/openshift/test-image-trigger:outputtag", newBuild.Parameters.Output.DockerImageReference)
252
+	}
253
+	if newBuild.Labels["testlabel"] != "testvalue" {
254
+		t.Fatalf("Expected build with label %s=%s from build config got %s=%s", "testlabel", "testvalue", "testlabel", newBuild.Labels["testlabel"])
255
+	}
256
+
257
+	<-watch2.ResultChan()
139 258
 	updatedConfig, err = openshift.Client.BuildConfigs(testutil.Namespace()).Get(config.Name)
140 259
 	if err != nil {
141 260
 		t.Fatalf("Couldn't get BuildConfig: %v", err)
... ...
@@ -187,3 +299,48 @@ func imageChangeBuildConfig() *buildapi.BuildConfig {
187 187
 	}
188 188
 	return buildcfg
189 189
 }
190
+
191
+func imageChangeBuildConfigFromRef() *buildapi.BuildConfig {
192
+	buildcfg := &buildapi.BuildConfig{
193
+		ObjectMeta: kapi.ObjectMeta{
194
+			Name:   "test-build-cfg",
195
+			Labels: map[string]string{"testlabel": "testvalue"},
196
+		},
197
+		Parameters: buildapi.BuildParameters{
198
+			Source: buildapi.BuildSource{
199
+				Type: "Git",
200
+				Git: &buildapi.GitBuildSource{
201
+					URI: "git://github.com/openshift/ruby-hello-world.git",
202
+				},
203
+				ContextDir: "contextimage",
204
+			},
205
+			Strategy: buildapi.BuildStrategy{
206
+				Type: buildapi.STIBuildStrategyType,
207
+				STIStrategy: &buildapi.STIBuildStrategy{
208
+					From: &kapi.ObjectReference{
209
+						Name: "test-image-trigger-repo",
210
+					},
211
+				},
212
+			},
213
+			Output: buildapi.BuildOutput{
214
+				To: &kapi.ObjectReference{
215
+					Name: "test-image-trigger-repo",
216
+				},
217
+				Tag: "outputtag",
218
+			},
219
+		},
220
+		Triggers: []buildapi.BuildTriggerPolicy{
221
+			{
222
+				Type: buildapi.ImageChangeBuildTriggerType,
223
+				ImageChange: &buildapi.ImageChangeTrigger{
224
+					Image: "registry:8080/openshift/test-image-trigger",
225
+					From: kapi.ObjectReference{
226
+						Name: "test-image-trigger-repo",
227
+					},
228
+					Tag: "latest",
229
+				},
230
+			},
231
+		},
232
+	}
233
+	return buildcfg
234
+}
... ...
@@ -92,6 +92,48 @@ func TestWebhookGithubPushWithImageTag(t *testing.T) {
92 92
 	}
93 93
 }
94 94
 
95
+func TestWebhookGithubPushWithImageTagRef(t *testing.T) {
96
+	testutil.DeleteAllEtcdKeys()
97
+	openshift := NewTestBuildOpenshift(t)
98
+	defer openshift.Close()
99
+
100
+	// create imagerepo
101
+	imageRepo := &imageapi.ImageRepository{
102
+		ObjectMeta: kapi.ObjectMeta{Name: "imageRepo"},
103
+		Tags:       map[string]string{"validTag": "success"},
104
+	}
105
+	if _, err := openshift.Client.ImageRepositories(testutil.Namespace()).Create(imageRepo); err != nil {
106
+		t.Fatalf("Unexpected error: %v", err)
107
+	}
108
+
109
+	// create buildconfig
110
+	buildConfig := mockBuildConfigRefParms("originalImage", "imageRepo", "validTag")
111
+
112
+	if _, err := openshift.Client.BuildConfigs(testutil.Namespace()).Create(buildConfig); err != nil {
113
+		t.Fatalf("Unexpected error: %v", err)
114
+	}
115
+
116
+	watch, err := openshift.Client.Builds(testutil.Namespace()).Watch(labels.Everything(), fields.Everything(), "0")
117
+	if err != nil {
118
+		t.Fatalf("Couldn't subscribe to builds: %v", err)
119
+	}
120
+	defer watch.Stop()
121
+
122
+	// trigger build event sending push notification
123
+	postFile("push", "pushevent.json", openshift.server.URL+openshift.whPrefix+"pushbuild/secret101/github?namespace="+testutil.Namespace(), http.StatusOK, t)
124
+
125
+	event := <-watch.ResultChan()
126
+	actual := event.Object.(*buildapi.Build)
127
+
128
+	if actual.Status != buildapi.BuildStatusNew {
129
+		t.Errorf("Expected %s, got %s", buildapi.BuildStatusNew, actual.Status)
130
+	}
131
+
132
+	if actual.Parameters.Strategy.STIStrategy.Image != "registry:3000/integration/imageRepo:success" {
133
+		t.Errorf("Expected %s, got %s", "registry:3000/integration-test/imageRepo:success", actual.Parameters.Strategy.STIStrategy.Image)
134
+	}
135
+}
136
+
95 137
 func TestWebhookGithubPushWithImageTagUnmatched(t *testing.T) {
96 138
 	testutil.DeleteAllEtcdKeys()
97 139
 	openshift := NewTestBuildOpenshift(t)
... ...
@@ -272,3 +314,50 @@ func mockBuildConfigParms(imageName, imageRepo, imageTag string) *buildapi.Build
272 272
 		},
273 273
 	}
274 274
 }
275
+
276
+func mockBuildConfigRefParms(imageName, imageRepo, imageTag string) *buildapi.BuildConfig {
277
+	return &buildapi.BuildConfig{
278
+		ObjectMeta: kapi.ObjectMeta{
279
+			Name: "pushbuild",
280
+		},
281
+		Triggers: []buildapi.BuildTriggerPolicy{
282
+			{
283
+				Type: buildapi.GithubWebHookBuildTriggerType,
284
+				GithubWebHook: &buildapi.WebHookTrigger{
285
+					Secret: "secret101",
286
+				},
287
+			},
288
+			{
289
+				Type: buildapi.ImageChangeBuildTriggerType,
290
+				ImageChange: &buildapi.ImageChangeTrigger{
291
+					Image: imageName,
292
+					From: kapi.ObjectReference{
293
+						Name: imageRepo,
294
+					},
295
+					Tag: imageTag,
296
+				},
297
+			},
298
+		},
299
+		Parameters: buildapi.BuildParameters{
300
+			Source: buildapi.BuildSource{
301
+				Type: buildapi.BuildSourceGit,
302
+				Git: &buildapi.GitBuildSource{
303
+					URI: "http://my.docker/build",
304
+				},
305
+				ContextDir: "context",
306
+			},
307
+			Strategy: buildapi.BuildStrategy{
308
+				Type: buildapi.STIBuildStrategyType,
309
+				STIStrategy: &buildapi.STIBuildStrategy{
310
+					From: &kapi.ObjectReference{
311
+						Name: imageRepo,
312
+					},
313
+					Tag: imageTag,
314
+				},
315
+			},
316
+			Output: buildapi.BuildOutput{
317
+				DockerImageReference: "namespace/builtimage",
318
+			},
319
+		},
320
+	}
321
+}