Browse code

Added image repository reference From field to STIBuildStrategy

Matej 'Yin' Gagyi authored on 2015/02/11 23:24:05
Showing 21 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
... ...
@@ -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}"
... ...
@@ -130,7 +130,7 @@ trap "cleanup" EXIT
130 130
 function wait_for_app() {
131 131
 	echo "[INFO] Waiting for app in namespace $1"
132 132
 	echo "[INFO] Waiting for database pod to start"
133
-	wait_for_command "osc get -n $1 pods -l name=database | grep -i Running" $((30*TIME_SEC))
133
+	wait_for_command "osc get -n $1 pods -l name=database | grep -i Running" $((60*TIME_SEC))
134 134
 
135 135
 	echo "[INFO] Waiting for database service to start"
136 136
 	wait_for_command "osc get -n $1 services | grep database" $((20*TIME_SEC))
... ...
@@ -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
+}