Browse code

new-build support for image source

also change to support an array of input images

Cesar Wong authored on 2015/12/17 06:00:43
Showing 39 changed files
... ...
@@ -14704,9 +14704,12 @@
14704 14704
       "$ref": "v1.GitBuildSource",
14705 14705
       "description": "optional information about git build source"
14706 14706
      },
14707
-     "image": {
14708
-      "$ref": "v1.ImageSource",
14709
-      "description": "optional image build source.  EXPERIMENTAL: This will be changing to an array of images in the near future and no migration/compatibility will be provided.  Use at your own risk."
14707
+     "images": {
14708
+      "type": "array",
14709
+      "items": {
14710
+       "$ref": "v1.ImageSource"
14711
+      },
14712
+      "description": "optional images for build source."
14710 14713
      },
14711 14714
      "contextDir": {
14712 14715
       "type": "string",
... ...
@@ -1000,6 +1000,8 @@ _oc_new-build()
1000 1000
     flags+=("--show-all")
1001 1001
     flags+=("-a")
1002 1002
     flags+=("--sort-by=")
1003
+    flags+=("--source-image=")
1004
+    flags+=("--source-image-path=")
1003 1005
     flags+=("--strategy=")
1004 1006
     flags+=("--template=")
1005 1007
     two_word_flags+=("-t")
... ...
@@ -5995,6 +5995,8 @@ _openshift_cli_new-build()
5995 5995
     flags+=("--show-all")
5996 5996
     flags+=("-a")
5997 5997
     flags+=("--sort-by=")
5998
+    flags+=("--source-image=")
5999
+    flags+=("--source-image-path=")
5998 6000
     flags+=("--strategy=")
5999 6001
     flags+=("--template=")
6000 6002
     two_word_flags+=("-t")
... ...
@@ -590,6 +590,9 @@ Create a new build configuration
590 590
 
591 591
   # Create a build config from a remote repository and inject the npmrc into a build
592 592
   $ oc new-build https://github.com/openshift/ruby-hello-world --build-secret npmrc:.npmrc
593
+  
594
+  # Create a build config that gets its input from a remote repository and another Docker image
595
+  $ oc new-build https://github.com/openshift/ruby-hello-world --image-source=openshift/jenkins-1-centos7 --image-source-path=/var/lib/jenkins:/tmp
593 596
 ----
594 597
 ====
595 598
 
... ...
@@ -966,13 +966,15 @@ func deepCopy_api_BuildSource(in buildapi.BuildSource, out *buildapi.BuildSource
966 966
 	} else {
967 967
 		out.Git = nil
968 968
 	}
969
-	if in.Image != nil {
970
-		out.Image = new(buildapi.ImageSource)
971
-		if err := deepCopy_api_ImageSource(*in.Image, out.Image, c); err != nil {
972
-			return err
969
+	if in.Images != nil {
970
+		out.Images = make([]buildapi.ImageSource, len(in.Images))
971
+		for i := range in.Images {
972
+			if err := deepCopy_api_ImageSource(in.Images[i], &out.Images[i], c); err != nil {
973
+				return err
974
+			}
973 975
 		}
974 976
 	} else {
975
-		out.Image = nil
977
+		out.Images = nil
976 978
 	}
977 979
 	out.ContextDir = in.ContextDir
978 980
 	if in.SourceSecret != nil {
... ...
@@ -1418,13 +1418,15 @@ func autoconvert_api_BuildSource_To_v1_BuildSource(in *buildapi.BuildSource, out
1418 1418
 	} else {
1419 1419
 		out.Git = nil
1420 1420
 	}
1421
-	if in.Image != nil {
1422
-		out.Image = new(apiv1.ImageSource)
1423
-		if err := convert_api_ImageSource_To_v1_ImageSource(in.Image, out.Image, s); err != nil {
1424
-			return err
1421
+	if in.Images != nil {
1422
+		out.Images = make([]apiv1.ImageSource, len(in.Images))
1423
+		for i := range in.Images {
1424
+			if err := convert_api_ImageSource_To_v1_ImageSource(&in.Images[i], &out.Images[i], s); err != nil {
1425
+				return err
1426
+			}
1425 1427
 		}
1426 1428
 	} else {
1427
-		out.Image = nil
1429
+		out.Images = nil
1428 1430
 	}
1429 1431
 	out.ContextDir = in.ContextDir
1430 1432
 	if in.SourceSecret != nil {
... ...
@@ -2210,13 +2212,15 @@ func autoconvert_v1_BuildSource_To_api_BuildSource(in *apiv1.BuildSource, out *b
2210 2210
 	} else {
2211 2211
 		out.Git = nil
2212 2212
 	}
2213
-	if in.Image != nil {
2214
-		out.Image = new(buildapi.ImageSource)
2215
-		if err := convert_v1_ImageSource_To_api_ImageSource(in.Image, out.Image, s); err != nil {
2216
-			return err
2213
+	if in.Images != nil {
2214
+		out.Images = make([]buildapi.ImageSource, len(in.Images))
2215
+		for i := range in.Images {
2216
+			if err := convert_v1_ImageSource_To_api_ImageSource(&in.Images[i], &out.Images[i], s); err != nil {
2217
+				return err
2218
+			}
2217 2219
 		}
2218 2220
 	} else {
2219
-		out.Image = nil
2221
+		out.Images = nil
2220 2222
 	}
2221 2223
 	out.ContextDir = in.ContextDir
2222 2224
 	if in.SourceSecret != nil {
... ...
@@ -991,13 +991,15 @@ func deepCopy_v1_BuildSource(in apiv1.BuildSource, out *apiv1.BuildSource, c *co
991 991
 	} else {
992 992
 		out.Git = nil
993 993
 	}
994
-	if in.Image != nil {
995
-		out.Image = new(apiv1.ImageSource)
996
-		if err := deepCopy_v1_ImageSource(*in.Image, out.Image, c); err != nil {
997
-			return err
994
+	if in.Images != nil {
995
+		out.Images = make([]apiv1.ImageSource, len(in.Images))
996
+		for i := range in.Images {
997
+			if err := deepCopy_v1_ImageSource(in.Images[i], &out.Images[i], c); err != nil {
998
+				return err
999
+			}
998 1000
 		}
999 1001
 	} else {
1000
-		out.Image = nil
1002
+		out.Images = nil
1001 1003
 	}
1002 1004
 	out.ContextDir = in.ContextDir
1003 1005
 	if in.SourceSecret != nil {
... ...
@@ -1427,13 +1427,15 @@ func autoconvert_api_BuildSource_To_v1beta3_BuildSource(in *buildapi.BuildSource
1427 1427
 	} else {
1428 1428
 		out.Git = nil
1429 1429
 	}
1430
-	if in.Image != nil {
1431
-		out.Image = new(apiv1beta3.ImageSource)
1432
-		if err := convert_api_ImageSource_To_v1beta3_ImageSource(in.Image, out.Image, s); err != nil {
1433
-			return err
1430
+	if in.Images != nil {
1431
+		out.Images = make([]apiv1beta3.ImageSource, len(in.Images))
1432
+		for i := range in.Images {
1433
+			if err := convert_api_ImageSource_To_v1beta3_ImageSource(&in.Images[i], &out.Images[i], s); err != nil {
1434
+				return err
1435
+			}
1434 1436
 		}
1435 1437
 	} else {
1436
-		out.Image = nil
1438
+		out.Images = nil
1437 1439
 	}
1438 1440
 	out.ContextDir = in.ContextDir
1439 1441
 	if in.SourceSecret != nil {
... ...
@@ -2219,13 +2221,15 @@ func autoconvert_v1beta3_BuildSource_To_api_BuildSource(in *apiv1beta3.BuildSour
2219 2219
 	} else {
2220 2220
 		out.Git = nil
2221 2221
 	}
2222
-	if in.Image != nil {
2223
-		out.Image = new(buildapi.ImageSource)
2224
-		if err := convert_v1beta3_ImageSource_To_api_ImageSource(in.Image, out.Image, s); err != nil {
2225
-			return err
2222
+	if in.Images != nil {
2223
+		out.Images = make([]buildapi.ImageSource, len(in.Images))
2224
+		for i := range in.Images {
2225
+			if err := convert_v1beta3_ImageSource_To_api_ImageSource(&in.Images[i], &out.Images[i], s); err != nil {
2226
+				return err
2227
+			}
2226 2228
 		}
2227 2229
 	} else {
2228
-		out.Image = nil
2230
+		out.Images = nil
2229 2231
 	}
2230 2232
 	out.ContextDir = in.ContextDir
2231 2233
 	if in.SourceSecret != nil {
... ...
@@ -999,13 +999,15 @@ func deepCopy_v1beta3_BuildSource(in apiv1beta3.BuildSource, out *apiv1beta3.Bui
999 999
 	} else {
1000 1000
 		out.Git = nil
1001 1001
 	}
1002
-	if in.Image != nil {
1003
-		out.Image = new(apiv1beta3.ImageSource)
1004
-		if err := deepCopy_v1beta3_ImageSource(*in.Image, out.Image, c); err != nil {
1005
-			return err
1002
+	if in.Images != nil {
1003
+		out.Images = make([]apiv1beta3.ImageSource, len(in.Images))
1004
+		for i := range in.Images {
1005
+			if err := deepCopy_v1beta3_ImageSource(in.Images[i], &out.Images[i], c); err != nil {
1006
+				return err
1007
+			}
1006 1008
 		}
1007 1009
 	} else {
1008
-		out.Image = nil
1010
+		out.Images = nil
1009 1011
 	}
1010 1012
 	out.ContextDir = in.ContextDir
1011 1013
 	if in.SourceSecret != nil {
... ...
@@ -188,10 +188,8 @@ type BuildSource struct {
188 188
 	// Git contains optional information about git build source
189 189
 	Git *GitBuildSource
190 190
 
191
-	// Image describes an image to be used to provide source for the build
192
-	// EXPERIMENTAL.  This will be changing to an array of images in the near future
193
-	// and no migration/compatibility will be provided.  Use at your own risk.
194
-	Image *ImageSource
191
+	// Images describes a set of images to be used to provide source for the build
192
+	Images []ImageSource
195 193
 
196 194
 	// ContextDir specifies the sub-directory where the source code for the application exists.
197 195
 	// This allows to have buildable sources in directory other than root of
... ...
@@ -160,10 +160,8 @@ type BuildSource struct {
160 160
 	// Git contains optional information about git build source
161 161
 	Git *GitBuildSource `json:"git,omitempty" description:"optional information about git build source"`
162 162
 
163
-	// Image describes an image to be used to provide source for the build
164
-	// EXPERIMENTAL.  This will be changing to an array of images in the near future
165
-	// and no migration/compatibility will be provided.  Use at your own risk.
166
-	Image *ImageSource `json:"image,omitempty" description:"optional image build source.  EXPERIMENTAL: This will be changing to an array of images in the near future and no migration/compatibility will be provided.  Use at your own risk."`
163
+	// Images describes a set of images to be used to provide source for the build
164
+	Images []ImageSource `json:"images,omitempty" description:"optional images for build source."`
167 165
 
168 166
 	// ContextDir specifies the sub-directory where the source code for the application exists.
169 167
 	// This allows to have buildable sources in directory other than root of
... ...
@@ -156,10 +156,8 @@ type BuildSource struct {
156 156
 	// Git contains optional information about git build source.
157 157
 	Git *GitBuildSource `json:"git,omitempty"`
158 158
 
159
-	// Image describes an image to be used to provide source for the build
160
-	// EXPERIMENTAL.  This will be changing to an array of images in the near future
161
-	// and no migration/compatibility will be provided.  Use at your own risk.
162
-	Image *ImageSource `json:"image,omitempty" description:"optional image build source.  EXPERIMENTAL: This will be changing to an array of images in the near future and no migration/compatibility will be provided.  Use at your own risk."`
159
+	// Images describes a set of images to be used to provide source for the build
160
+	Images []ImageSource `json:"images,omitempty" description:"optional images for build source."`
163 161
 
164 162
 	// Specify the sub-directory where the source code for the application exists.
165 163
 	// This allows to have buildable sources in directory other than root of
... ...
@@ -159,8 +159,10 @@ func validateSource(input *buildapi.BuildSource, isCustomStrategy, isDockerStrat
159 159
 	if input.Dockerfile != nil {
160 160
 		allErrs = append(allErrs, validateDockerfile(*input.Dockerfile)...)
161 161
 	}
162
-	if input.Image != nil {
163
-		allErrs = append(allErrs, validateImageSource(input.Image).Prefix("image")...)
162
+	if input.Images != nil {
163
+		for i, image := range input.Images {
164
+			allErrs = append(allErrs, validateImageSource(image).PrefixIndex(i).Prefix("images")...)
165
+		}
164 166
 	}
165 167
 
166 168
 	allErrs = append(allErrs, validateSecrets(input.Secrets, isDockerStrategy).Prefix("secrets")...)
... ...
@@ -244,7 +246,7 @@ func validateSecrets(secrets []buildapi.SecretBuildSource, isDockerStrategy bool
244 244
 	return allErrs
245 245
 }
246 246
 
247
-func validateImageSource(imageSource *buildapi.ImageSource) fielderrors.ValidationErrorList {
247
+func validateImageSource(imageSource buildapi.ImageSource) fielderrors.ValidationErrorList {
248 248
 	allErrs := fielderrors.ValidationErrorList{}
249 249
 	allErrs = append(allErrs, validateFromImageReference(&imageSource.From).Prefix("from")...)
250 250
 	if imageSource.PullSecret != nil {
... ...
@@ -253,9 +255,32 @@ func validateImageSource(imageSource *buildapi.ImageSource) fielderrors.Validati
253 253
 	if len(imageSource.Paths) == 0 {
254 254
 		allErrs = append(allErrs, fielderrors.NewFieldRequired("paths"))
255 255
 	}
256
+	for i, path := range imageSource.Paths {
257
+		allErrs = append(allErrs, validateImageSourcePath(path).PrefixIndex(i).Prefix("paths")...)
258
+
259
+	}
256 260
 	return allErrs
257 261
 }
258 262
 
263
+func validateImageSourcePath(imagePath buildapi.ImageSourcePath) fielderrors.ValidationErrorList {
264
+	allErrs := fielderrors.ValidationErrorList{}
265
+	if len(imagePath.SourcePath) == 0 {
266
+		allErrs = append(allErrs, fielderrors.NewFieldRequired("sourcePath"))
267
+	}
268
+	if len(imagePath.DestinationDir) == 0 {
269
+		allErrs = append(allErrs, fielderrors.NewFieldRequired("destinationDir"))
270
+	}
271
+	if !filepath.IsAbs(imagePath.SourcePath) {
272
+		allErrs = append(allErrs, fielderrors.NewFieldInvalid("sourcePath", imagePath.SourcePath, "must be an absolute path"))
273
+	}
274
+	if filepath.IsAbs(imagePath.DestinationDir) {
275
+		allErrs = append(allErrs, fielderrors.NewFieldInvalid("destinationDir", imagePath.DestinationDir, "must be a relative path"))
276
+	}
277
+	if strings.HasPrefix(path.Clean(imagePath.DestinationDir), "..") {
278
+		allErrs = append(allErrs, fielderrors.NewFieldInvalid("destinationDir", imagePath.DestinationDir, "destination dir cannot start with '..'"))
279
+	}
280
+	return allErrs
281
+}
259 282
 func validateBinarySource(source *buildapi.BinaryBuildSource) fielderrors.ValidationErrorList {
260 283
 	allErrs := fielderrors.ValidationErrorList{}
261 284
 	if len(source.AsFile) != 0 {
... ...
@@ -785,15 +785,29 @@ func TestValidateSource(t *testing.T) {
785 785
 		{
786 786
 			ok: true,
787 787
 			source: &buildapi.BuildSource{
788
-				Image: &buildapi.ImageSource{
789
-					From: kapi.ObjectReference{
790
-						Kind: "ImageStreamTag",
791
-						Name: "my-image:latest",
788
+				Images: []buildapi.ImageSource{
789
+					{
790
+						From: kapi.ObjectReference{
791
+							Kind: "ImageStreamTag",
792
+							Name: "my-image:latest",
793
+						},
794
+						Paths: []buildapi.ImageSourcePath{
795
+							{
796
+								SourcePath:     "/some/path",
797
+								DestinationDir: "test/dir",
798
+							},
799
+						},
792 800
 					},
793
-					Paths: []buildapi.ImageSourcePath{
794
-						{
795
-							SourcePath:     "/some/path",
796
-							DestinationDir: "test/dir",
801
+					{
802
+						From: kapi.ObjectReference{
803
+							Kind: "ImageStreamTag",
804
+							Name: "my-image:latest",
805
+						},
806
+						Paths: []buildapi.ImageSourcePath{
807
+							{
808
+								SourcePath:     "/some/path",
809
+								DestinationDir: "test/dir",
810
+							},
797 811
 						},
798 812
 					},
799 813
 				},
... ...
@@ -802,52 +816,121 @@ func TestValidateSource(t *testing.T) {
802 802
 		// 16
803 803
 		{
804 804
 			t:    fielderrors.ValidationErrorTypeRequired,
805
-			path: "image.paths",
805
+			path: "images[0].paths",
806 806
 			source: &buildapi.BuildSource{
807
-				Image: &buildapi.ImageSource{
808
-					From: kapi.ObjectReference{
809
-						Kind: "ImageStreamTag",
810
-						Name: "my-image:latest",
807
+				Images: []buildapi.ImageSource{
808
+					{
809
+						From: kapi.ObjectReference{
810
+							Kind: "ImageStreamTag",
811
+							Name: "my-image:latest",
812
+						},
811 813
 					},
812 814
 				},
813 815
 			},
814 816
 		},
815
-		// 17
817
+		// 17 - destinationdir is not relative.
816 818
 		{
817 819
 			t:    fielderrors.ValidationErrorTypeInvalid,
818
-			path: "image.from.kind",
820
+			path: "images[0].paths[0].destinationDir",
819 821
 			source: &buildapi.BuildSource{
820
-				Image: &buildapi.ImageSource{
821
-					From: kapi.ObjectReference{
822
-						Kind: "InvalidKind",
823
-						Name: "my-image:latest",
824
-					},
825
-					Paths: []buildapi.ImageSourcePath{
826
-						{
827
-							SourcePath:     "/some/path",
828
-							DestinationDir: "test/dir",
822
+				Images: []buildapi.ImageSource{
823
+					{
824
+						From: kapi.ObjectReference{
825
+							Kind: "ImageStreamTag",
826
+							Name: "my-image:latest",
827
+						},
828
+						Paths: []buildapi.ImageSourcePath{
829
+							{
830
+								SourcePath:     "/some/path",
831
+								DestinationDir: "/test/dir",
832
+							},
829 833
 						},
830 834
 					},
831 835
 				},
832 836
 			},
833 837
 		},
834
-		// 18
838
+		// 18 - sourcepath is not absolute.
835 839
 		{
836
-			t:    fielderrors.ValidationErrorTypeRequired,
837
-			path: "image.pullSecret.name",
840
+			t:    fielderrors.ValidationErrorTypeInvalid,
841
+			path: "images[0].paths[0].sourcePath",
838 842
 			source: &buildapi.BuildSource{
839
-				Image: &buildapi.ImageSource{
840
-					From: kapi.ObjectReference{
841
-						Kind: "DockerImage",
842
-						Name: "my-image:latest",
843
+				Images: []buildapi.ImageSource{
844
+					{
845
+						From: kapi.ObjectReference{
846
+							Kind: "ImageStreamTag",
847
+							Name: "my-image:latest",
848
+						},
849
+						Paths: []buildapi.ImageSourcePath{
850
+							{
851
+								SourcePath:     "some/path",
852
+								DestinationDir: "test/dir",
853
+							},
854
+						},
843 855
 					},
844
-					PullSecret: &kapi.LocalObjectReference{
845
-						Name: "",
856
+				},
857
+			},
858
+		},
859
+		// 19 - destinationdir backsteps above basedir
860
+		{
861
+			t:    fielderrors.ValidationErrorTypeInvalid,
862
+			path: "images[0].paths[0].destinationDir",
863
+			source: &buildapi.BuildSource{
864
+				Images: []buildapi.ImageSource{
865
+					{
866
+						From: kapi.ObjectReference{
867
+							Kind: "ImageStreamTag",
868
+							Name: "my-image:latest",
869
+						},
870
+						Paths: []buildapi.ImageSourcePath{
871
+							{
872
+								SourcePath:     "/some/path",
873
+								DestinationDir: "test/../../dir",
874
+							},
875
+						},
846 876
 					},
847
-					Paths: []buildapi.ImageSourcePath{
848
-						{
849
-							SourcePath:     "/some/path",
850
-							DestinationDir: "test/dir",
877
+				},
878
+			},
879
+		},
880
+		// 20
881
+		{
882
+			t:    fielderrors.ValidationErrorTypeInvalid,
883
+			path: "images[0].from.kind",
884
+			source: &buildapi.BuildSource{
885
+				Images: []buildapi.ImageSource{
886
+					{
887
+						From: kapi.ObjectReference{
888
+							Kind: "InvalidKind",
889
+							Name: "my-image:latest",
890
+						},
891
+						Paths: []buildapi.ImageSourcePath{
892
+							{
893
+								SourcePath:     "/some/path",
894
+								DestinationDir: "test/dir",
895
+							},
896
+						},
897
+					},
898
+				},
899
+			},
900
+		},
901
+		// 21
902
+		{
903
+			t:    fielderrors.ValidationErrorTypeRequired,
904
+			path: "images[0].pullSecret.name",
905
+			source: &buildapi.BuildSource{
906
+				Images: []buildapi.ImageSource{
907
+					{
908
+						From: kapi.ObjectReference{
909
+							Kind: "DockerImage",
910
+							Name: "my-image:latest",
911
+						},
912
+						PullSecret: &kapi.LocalObjectReference{
913
+							Name: "",
914
+						},
915
+						Paths: []buildapi.ImageSourcePath{
916
+							{
917
+								SourcePath:     "/some/path",
918
+								DestinationDir: "test/dir",
919
+							},
851 920
 						},
852 921
 					},
853 922
 				},
... ...
@@ -22,7 +22,7 @@ const (
22 22
 	defaultRegistryServer = "https://index.docker.io/v1/"
23 23
 	PushAuthType          = "PUSH_DOCKERCFG_PATH"
24 24
 	PullAuthType          = "PULL_DOCKERCFG_PATH"
25
-	PullSourceAuthType    = "PULL_SOURCE_DOCKERCFG_PATH"
25
+	PullSourceAuthType    = "PULL_SOURCE_DOCKERCFG_PATH_"
26 26
 )
27 27
 
28 28
 // Helper contains all the valid config options for reading the local dockercfg file
... ...
@@ -39,6 +39,7 @@ type DockerClient interface {
39 39
 	DownloadFromContainer(id string, opts docker.DownloadFromContainerOptions) error
40 40
 	PullImage(opts docker.PullImageOptions, auth docker.AuthConfiguration) error
41 41
 	RemoveContainer(opts docker.RemoveContainerOptions) error
42
+	InspectImage(name string) (*docker.Image, error)
42 43
 }
43 44
 
44 45
 // pushImage pushes a docker image to the registry specified in its tag.
... ...
@@ -46,7 +46,9 @@ func (d *FakeDocker) PullImage(opts docker.PullImageOptions, auth docker.AuthCon
46 46
 func (d *FakeDocker) RemoveContainer(opts docker.RemoveContainerOptions) error {
47 47
 	return nil
48 48
 }
49
-
49
+func (d *FakeDocker) InspectImage(name string) (*docker.Image, error) {
50
+	return nil, nil
51
+}
50 52
 func TestDockerPush(t *testing.T) {
51 53
 	verifyFunc := func(opts docker.PushImageOptions, auth docker.AuthConfiguration) error {
52 54
 		if opts.Name != "test/image" {
... ...
@@ -65,10 +65,22 @@ func fetchSource(dockerClient DockerClient, dir string, build *api.Build, urlTim
65 65
 		}
66 66
 	}
67 67
 
68
+	forcePull := false
69
+	switch {
70
+	case build.Spec.Strategy.SourceStrategy != nil:
71
+		forcePull = build.Spec.Strategy.SourceStrategy.ForcePull
72
+	case build.Spec.Strategy.DockerStrategy != nil:
73
+		forcePull = build.Spec.Strategy.DockerStrategy.ForcePull
74
+	case build.Spec.Strategy.CustomStrategy != nil:
75
+		forcePull = build.Spec.Strategy.CustomStrategy.ForcePull
76
+	}
68 77
 	// extract source from an Image if specified
69
-	if build.Spec.Source.Image != nil {
70
-		// fetch image source
71
-		err := extractSourceFromImage(dockerClient, build.Spec.Source.Image.From.Name, dir, build.Spec.Source.Image.Paths)
78
+	for i, image := range build.Spec.Source.Images {
79
+		imageSecretIndex := i
80
+		if image.PullSecret == nil {
81
+			imageSecretIndex = -1
82
+		}
83
+		err := extractSourceFromImage(dockerClient, image.From.Name, dir, imageSecretIndex, image.Paths, forcePull)
72 84
 		if err != nil {
73 85
 			return nil, err
74 86
 		}
... ...
@@ -256,16 +268,43 @@ func copyImageSource(dockerClient DockerClient, containerID, sourceDir, destDir
256 256
 	return tarHelper.ExtractTarStreamWithLogging(destDir, file, tarOutput)
257 257
 }
258 258
 
259
-func extractSourceFromImage(dockerClient DockerClient, image, buildDir string, paths []api.ImageSourcePath) error {
259
+func extractSourceFromImage(dockerClient DockerClient, image, buildDir string, imageSecretIndex int, paths []api.ImageSourcePath, forcePull bool) error {
260 260
 	glog.V(4).Infof("Extracting image source from %s", image)
261 261
 
262
-	// Pre-pull image if a secret is specified
263
-	pullSecret := os.Getenv(dockercfg.PullSourceAuthType)
264
-	if len(pullSecret) > 0 {
265
-		dockerAuth, present := dockercfg.NewHelper().GetDockerAuth(image, dockercfg.PullSourceAuthType)
266
-		if present {
267
-			dockerClient.PullImage(docker.PullImageOptions{Repository: image}, dockerAuth)
262
+	dockerAuth := docker.AuthConfiguration{}
263
+	if imageSecretIndex != -1 {
264
+		pullSecret := os.Getenv(fmt.Sprintf("%s%d", dockercfg.PullSourceAuthType, imageSecretIndex))
265
+		if len(pullSecret) > 0 {
266
+			authPresent := false
267
+			dockerAuth, authPresent = dockercfg.NewHelper().GetDockerAuth(image, fmt.Sprintf("%s%d", dockercfg.PullSourceAuthType, imageSecretIndex))
268
+			if authPresent {
269
+				glog.V(5).Infof("Registry server Address: %s", dockerAuth.ServerAddress)
270
+				glog.V(5).Infof("Registry server User Name: %s", dockerAuth.Username)
271
+				glog.V(5).Infof("Registry server Email: %s", dockerAuth.Email)
272
+				passwordPresent := "<<empty>>"
273
+				if len(dockerAuth.Password) > 0 {
274
+					passwordPresent = "<<non-empty>>"
275
+				}
276
+				glog.V(5).Infof("Registry server Password: %s", passwordPresent)
277
+			}
278
+		}
279
+	}
280
+
281
+	exists := true
282
+	if !forcePull {
283
+		_, err := dockerClient.InspectImage(image)
284
+		if err != nil && err == docker.ErrNoSuchImage {
285
+			exists = false
286
+		} else if err != nil {
287
+			return err
288
+		}
289
+	}
290
+
291
+	if !exists || forcePull {
292
+		if err := dockerClient.PullImage(docker.PullImageOptions{Repository: image}, dockerAuth); err != nil {
293
+			return fmt.Errorf("error pulling image %v: %v", image, err)
268 294
 		}
295
+
269 296
 	}
270 297
 
271 298
 	// Create container to copy from
... ...
@@ -51,6 +51,9 @@ func (client testDockerClient) PullImage(opts docker.PullImageOptions, auth dock
51 51
 func (client testDockerClient) RemoveContainer(opts docker.RemoveContainerOptions) error {
52 52
 	return nil
53 53
 }
54
+func (client testDockerClient) InspectImage(name string) (*docker.Image, error) {
55
+	return nil, nil
56
+}
54 57
 
55 58
 type testStiBuilderFactory struct {
56 59
 	getStrategyErr error
... ...
@@ -92,11 +92,7 @@ func (bs *CustomBuildStrategy) CreateBuildPod(build *buildapi.Build) (*kapi.Pod,
92 92
 
93 93
 	if strategy.ExposeDockerSocket {
94 94
 		setupDockerSocket(pod)
95
-		var sourceImageSecret *kapi.LocalObjectReference
96
-		if build.Spec.Source.Image != nil {
97
-			sourceImageSecret = build.Spec.Source.Image.PullSecret
98
-		}
99
-		setupDockerSecrets(pod, build.Spec.Output.PushSecret, strategy.PullSecret, sourceImageSecret)
95
+		setupDockerSecrets(pod, build.Spec.Output.PushSecret, strategy.PullSecret, build.Spec.Source.Images)
100 96
 	}
101 97
 	setupSourceSecrets(pod, build.Spec.Source.SourceSecret)
102 98
 	setupSecrets(pod, build.Spec.Source.Secrets)
... ...
@@ -77,11 +77,7 @@ func (bs *DockerBuildStrategy) CreateBuildPod(build *buildapi.Build) (*kapi.Pod,
77 77
 	}
78 78
 
79 79
 	setupDockerSocket(pod)
80
-	var sourceImageSecret *kapi.LocalObjectReference
81
-	if build.Spec.Source.Image != nil {
82
-		sourceImageSecret = build.Spec.Source.Image.PullSecret
83
-	}
84
-	setupDockerSecrets(pod, build.Spec.Output.PushSecret, strategy.PullSecret, sourceImageSecret)
80
+	setupDockerSecrets(pod, build.Spec.Output.PushSecret, strategy.PullSecret, build.Spec.Source.Images)
85 81
 	setupSourceSecrets(pod, build.Spec.Source.SourceSecret)
86 82
 	setupSecrets(pod, build.Spec.Source.Secrets)
87 83
 
... ...
@@ -99,11 +99,7 @@ func (bs *SourceBuildStrategy) CreateBuildPod(build *buildapi.Build) (*kapi.Pod,
99 99
 	}
100 100
 
101 101
 	setupDockerSocket(pod)
102
-	var sourceImageSecret *kapi.LocalObjectReference
103
-	if build.Spec.Source.Image != nil {
104
-		sourceImageSecret = build.Spec.Source.Image.PullSecret
105
-	}
106
-	setupDockerSecrets(pod, build.Spec.Output.PushSecret, strategy.PullSecret, sourceImageSecret)
102
+	setupDockerSecrets(pod, build.Spec.Output.PushSecret, strategy.PullSecret, build.Spec.Source.Images)
107 103
 	setupSourceSecrets(pod, build.Spec.Source.SourceSecret)
108 104
 	setupSecrets(pod, build.Spec.Source.Secrets)
109 105
 	return pod, nil
... ...
@@ -1,10 +1,13 @@
1 1
 package strategy
2 2
 
3 3
 import (
4
+	"fmt"
4 5
 	"path/filepath"
6
+	"strconv"
5 7
 
6 8
 	"github.com/golang/glog"
7 9
 	buildapi "github.com/openshift/origin/pkg/build/api"
10
+	"github.com/openshift/origin/pkg/build/builder/cmd/dockercfg"
8 11
 	imageapi "github.com/openshift/origin/pkg/image/api"
9 12
 	"github.com/openshift/origin/pkg/util/namer"
10 13
 	kapi "k8s.io/kubernetes/pkg/api"
... ...
@@ -78,8 +81,8 @@ func setupBuildEnv(build *buildapi.Build, pod *kapi.Pod) error {
78 78
 
79 79
 // mountSecretVolume is a helper method responsible for actual mounting secret
80 80
 // volumes into a pod.
81
-func mountSecretVolume(pod *kapi.Pod, secretName, mountPath, volumePrefix string) {
82
-	volumeName := namer.GetName(secretName, volumePrefix, kvalidation.DNS1123SubdomainMaxLength)
81
+func mountSecretVolume(pod *kapi.Pod, secretName, mountPath, volumeSuffix string) {
82
+	volumeName := namer.GetName(secretName, volumeSuffix, kvalidation.DNS1123SubdomainMaxLength)
83 83
 	volume := kapi.Volume{
84 84
 		Name: volumeName,
85 85
 		VolumeSource: kapi.VolumeSource{
... ...
@@ -99,7 +102,7 @@ func mountSecretVolume(pod *kapi.Pod, secretName, mountPath, volumePrefix string
99 99
 
100 100
 // setupDockerSecrets mounts Docker Registry secrets into Pod running the build,
101 101
 // allowing Docker to authenticate against private registries or Docker Hub.
102
-func setupDockerSecrets(pod *kapi.Pod, pushSecret, pullSecret, sourceImageSecret *kapi.LocalObjectReference) {
102
+func setupDockerSecrets(pod *kapi.Pod, pushSecret, pullSecret *kapi.LocalObjectReference, imageSources []buildapi.ImageSource) {
103 103
 	if pushSecret != nil {
104 104
 		mountSecretVolume(pod, pushSecret.Name, DockerPushSecretMountPath, "push")
105 105
 		pod.Spec.Containers[0].Env = append(pod.Spec.Containers[0].Env, []kapi.EnvVar{
... ...
@@ -116,12 +119,16 @@ func setupDockerSecrets(pod *kapi.Pod, pushSecret, pullSecret, sourceImageSecret
116 116
 		glog.V(3).Infof("%s will be used for docker pull in %s", DockerPullSecretMountPath, pod.Name)
117 117
 	}
118 118
 
119
-	if sourceImageSecret != nil {
120
-		mountSecretVolume(pod, sourceImageSecret.Name, SourceImagePullSecretMountPath, "source-image")
119
+	for i, imageSource := range imageSources {
120
+		if imageSource.PullSecret == nil {
121
+			continue
122
+		}
123
+		mountPath := filepath.Join(SourceImagePullSecretMountPath, strconv.Itoa(i))
124
+		mountSecretVolume(pod, imageSource.PullSecret.Name, mountPath, "source-image")
121 125
 		pod.Spec.Containers[0].Env = append(pod.Spec.Containers[0].Env, []kapi.EnvVar{
122
-			{Name: "PULL_SOURCE_DOCKERCFG_PATH", Value: filepath.Join(SourceImagePullSecretMountPath, kapi.DockerConfigKey)},
126
+			{Name: fmt.Sprintf("%s%d", dockercfg.PullSourceAuthType, i), Value: filepath.Join(mountPath, kapi.DockerConfigKey)},
123 127
 		}...)
124
-		glog.V(3).Infof("%s will be used for docker pull in %s", SourceImagePullSecretMountPath, pod.Name)
128
+		glog.V(3).Infof("%s will be used for docker pull in %s", mountPath, pod.Name)
125 129
 
126 130
 	}
127 131
 }
... ...
@@ -424,16 +424,18 @@ func (g *BuildGenerator) generateBuildFromConfig(ctx kapi.Context, bc *buildapi.
424 424
 	strategyImageChangeTrigger := getStrategyImageChangeTrigger(bc)
425 425
 
426 426
 	// Resolve image source if present
427
-	if build.Spec.Source.Image != nil {
428
-		if build.Spec.Source.Image.PullSecret == nil {
429
-			build.Spec.Source.Image.PullSecret = g.resolveImageSecret(ctx, builderSecrets, &build.Spec.Source.Image.From, bc.Namespace)
427
+	for i, sourceImage := range build.Spec.Source.Images {
428
+		if sourceImage.PullSecret == nil {
429
+			sourceImage.PullSecret = g.resolveImageSecret(ctx, builderSecrets, &sourceImage.From, bc.Namespace)
430 430
 		}
431
-		sourceImage, err := g.resolveImageStreamReference(ctx, build.Spec.Source.Image.From, bc.Namespace)
431
+		sourceImageSpec, err := g.resolveImageStreamReference(ctx, sourceImage.From, bc.Namespace)
432 432
 		if err != nil {
433 433
 			return nil, err
434 434
 		}
435
-		build.Spec.Source.Image.From.Kind = "DockerImage"
436
-		build.Spec.Source.Image.From.Name = sourceImage
435
+		sourceImage.From.Kind = "DockerImage"
436
+		sourceImage.From.Name = sourceImageSpec
437
+		sourceImage.From.Namespace = ""
438
+		build.Spec.Source.Images[i] = sourceImage
437 439
 	}
438 440
 
439 441
 	// If the Build is using a From reference instead of a resolved image, we need to resolve that From
... ...
@@ -456,6 +456,13 @@ func setupAppConfig(f *clientcmd.Factory, out io.Writer, c *cobra.Command, args
456 456
 	if config.AllowMissingImages && config.AsSearch {
457 457
 		return cmdutil.UsageError(c, "--allow-missing-images and --search are mutually exclusive.")
458 458
 	}
459
+
460
+	if len(config.SourceImage) != 0 && len(config.SourceImagePath) == 0 {
461
+		return cmdutil.UsageError(c, "--source-image-path must be specified when --source-image is specified.")
462
+	}
463
+	if len(config.SourceImage) == 0 && len(config.SourceImagePath) != 0 {
464
+		return cmdutil.UsageError(c, "--source-image must be specified when --source-image-path is specified.")
465
+	}
459 466
 	return nil
460 467
 }
461 468
 
... ...
@@ -51,7 +51,10 @@ You can use '%[1]s status' to check the progress.`
51 51
   $ %[1]s new-build https://github.com/openshift/ruby-hello-world RACK_ENV=development
52 52
 
53 53
   # Create a build config from a remote repository and inject the npmrc into a build
54
-  $ %[1]s new-build https://github.com/openshift/ruby-hello-world --build-secret npmrc:.npmrc`
54
+  $ %[1]s new-build https://github.com/openshift/ruby-hello-world --build-secret npmrc:.npmrc
55
+  
56
+  # Create a build config that gets its input from a remote repository and another Docker image
57
+  $ %[1]s new-build https://github.com/openshift/ruby-hello-world --image-source=openshift/jenkins-1-centos7 --image-source-path=/var/lib/jenkins:/tmp`
55 58
 
56 59
 	newBuildNoInput = `You must specify one or more images, image streams, or source code locations to create a build.
57 60
 
... ...
@@ -113,6 +116,8 @@ func NewCmdNewBuild(fullName string, f *clientcmd.Factory, in io.Reader, out io.
113 113
 	cmd.Flags().StringVar(&config.ContextDir, "context-dir", "", "Context directory to be used for the build.")
114 114
 	cmd.Flags().BoolVar(&config.DryRun, "dry-run", false, "If true, do not actually create resources.")
115 115
 	cmd.Flags().BoolVar(&config.NoOutput, "no-output", false, "If true, the build output will not be pushed anywhere.")
116
+	cmd.Flags().StringVar(&config.SourceImage, "source-image", "", "Specify an image to use as source for the build.  You must also specify --source-image-path.")
117
+	cmd.Flags().StringVar(&config.SourceImagePath, "source-image-path", "", "Specify the file or directory to copy from the source image and its destination in the build directory. Format: [source]:[destination-dir].")
116 118
 	cmdutil.AddPrinterFlags(cmd)
117 119
 
118 120
 	return cmd
... ...
@@ -173,6 +173,12 @@ type BuildConfigDescriber struct {
173 173
 	host string
174 174
 }
175 175
 
176
+func nameAndNamespace(ns, name string) string {
177
+	if len(ns) != 0 {
178
+		return fmt.Sprintf("%s/%s", ns, name)
179
+	}
180
+	return name
181
+}
176 182
 func describeBuildSpec(p buildapi.BuildSpec, out *tabwriter.Writer) {
177 183
 	formatString(out, "Strategy", buildapi.StrategyType(p.Strategy))
178 184
 	if p.Source.Dockerfile != nil {
... ...
@@ -223,7 +229,18 @@ func describeBuildSpec(p buildapi.BuildSpec, out *tabwriter.Writer) {
223 223
 		}
224 224
 		formatString(out, "Build Secrets", strings.Join(result, ","))
225 225
 	}
226
-
226
+	if len(p.Source.Images) == 1 && len(p.Source.Images[0].Paths) == 1 {
227
+		image := p.Source.Images[0]
228
+		path := image.Paths[0]
229
+		formatString(out, "Image Source", fmt.Sprintf("copies %s from %s to %s", path.SourcePath, nameAndNamespace(image.From.Namespace, image.From.Name), path.DestinationDir))
230
+	} else {
231
+		for _, image := range p.Source.Images {
232
+			formatString(out, "Image Source", fmt.Sprintf("%s", nameAndNamespace(image.From.Namespace, image.From.Name)))
233
+			for _, path := range image.Paths {
234
+				fmt.Fprintf(out, "\t- %s -> %s\n", path.SourcePath, path.DestinationDir)
235
+			}
236
+		}
237
+	}
227 238
 	switch {
228 239
 	case p.Strategy.DockerStrategy != nil:
229 240
 		describeDockerStrategy(p.Strategy.DockerStrategy, out)
... ...
@@ -234,11 +251,7 @@ func describeBuildSpec(p buildapi.BuildSpec, out *tabwriter.Writer) {
234 234
 	}
235 235
 
236 236
 	if p.Output.To != nil {
237
-		if len(p.Output.To.Namespace) != 0 {
238
-			formatString(out, "Output to", fmt.Sprintf("%s %s/%s", p.Output.To.Kind, p.Output.To.Namespace, p.Output.To.Name))
239
-		} else {
240
-			formatString(out, "Output to", fmt.Sprintf("%s %s", p.Output.To.Kind, p.Output.To.Name))
241
-		}
237
+		formatString(out, "Output to", fmt.Sprintf("%s %s", p.Output.To.Kind, nameAndNamespace(p.Output.To.Namespace, p.Output.To.Name)))
242 238
 	}
243 239
 
244 240
 	if p.Output.PushSecret != nil {
... ...
@@ -263,11 +276,7 @@ func describeBuildSpec(p buildapi.BuildSpec, out *tabwriter.Writer) {
263 263
 
264 264
 func describeSourceStrategy(s *buildapi.SourceBuildStrategy, out *tabwriter.Writer) {
265 265
 	if len(s.From.Name) != 0 {
266
-		if len(s.From.Namespace) != 0 {
267
-			formatString(out, "From Image", fmt.Sprintf("%s %s/%s", s.From.Kind, s.From.Namespace, s.From.Name))
268
-		} else {
269
-			formatString(out, "From Image", fmt.Sprintf("%s %s", s.From.Kind, s.From.Name))
270
-		}
266
+		formatString(out, "From Image", fmt.Sprintf("%s %s", s.From.Kind, nameAndNamespace(s.From.Namespace, s.From.Name)))
271 267
 	}
272 268
 	if len(s.Scripts) != 0 {
273 269
 		formatString(out, "Scripts", s.Scripts)
... ...
@@ -285,11 +294,7 @@ func describeSourceStrategy(s *buildapi.SourceBuildStrategy, out *tabwriter.Writ
285 285
 
286 286
 func describeDockerStrategy(s *buildapi.DockerBuildStrategy, out *tabwriter.Writer) {
287 287
 	if s.From != nil && len(s.From.Name) != 0 {
288
-		if len(s.From.Namespace) != 0 {
289
-			formatString(out, "From Image", fmt.Sprintf("%s %s/%s", s.From.Kind, s.From.Namespace, s.From.Name))
290
-		} else {
291
-			formatString(out, "From Image", fmt.Sprintf("%s %s", s.From.Kind, s.From.Name))
292
-		}
288
+		formatString(out, "From Image", fmt.Sprintf("%s %s", s.From.Kind, nameAndNamespace(s.From.Namespace, s.From.Name)))
293 289
 	}
294 290
 	if len(s.DockerfilePath) != 0 {
295 291
 		formatString(out, "Dockerfile Path", s.DockerfilePath)
... ...
@@ -307,11 +312,7 @@ func describeDockerStrategy(s *buildapi.DockerBuildStrategy, out *tabwriter.Writ
307 307
 
308 308
 func describeCustomStrategy(s *buildapi.CustomBuildStrategy, out *tabwriter.Writer) {
309 309
 	if len(s.From.Name) != 0 {
310
-		if len(s.From.Namespace) != 0 {
311
-			formatString(out, "Image Reference", fmt.Sprintf("%s %s/%s", s.From.Kind, s.From.Namespace, s.From.Name))
312
-		} else {
313
-			formatString(out, "Image Reference", fmt.Sprintf("%s %s", s.From.Kind, s.From.Name))
314
-		}
310
+		formatString(out, "Image Reference", fmt.Sprintf("%s %s", s.From.Kind, nameAndNamespace(s.From.Namespace, s.From.Name)))
315 311
 	}
316 312
 	if s.ExposeDockerSocket {
317 313
 		formatString(out, "Expose Docker Socket", "yes")
... ...
@@ -100,6 +100,10 @@ type SourceRef struct {
100 100
 	ContextDir string
101 101
 	Secrets    []buildapi.SecretBuildSource
102 102
 
103
+	SourceImage     *ImageRef
104
+	ImageSourcePath string
105
+	ImageDestPath   string
106
+
103 107
 	DockerfileContents string
104 108
 
105 109
 	Binary bool
... ...
@@ -153,6 +157,24 @@ func (r *SourceRef) BuildSource() (*buildapi.BuildSource, []buildapi.BuildTrigge
153 153
 	if r.Binary {
154 154
 		source.Binary = &buildapi.BinaryBuildSource{}
155 155
 	}
156
+	if r.SourceImage != nil {
157
+		objRef := r.SourceImage.ObjectReference()
158
+		imgSrc := buildapi.ImageSource{}
159
+		imgSrc.From = objRef
160
+		imgSrc.Paths = []buildapi.ImageSourcePath{
161
+			{
162
+				SourcePath:     r.ImageSourcePath,
163
+				DestinationDir: r.ImageDestPath,
164
+			},
165
+		}
166
+		triggers = append(triggers, buildapi.BuildTriggerPolicy{
167
+			Type: buildapi.ImageChangeBuildTriggerType,
168
+			ImageChange: &buildapi.ImageChangeTrigger{
169
+				From: &objRef,
170
+			},
171
+		})
172
+		source.Images = []buildapi.ImageSource{imgSrc}
173
+	}
156 174
 	return source, triggers
157 175
 }
158 176
 
... ...
@@ -75,6 +75,9 @@ type AppConfig struct {
75 75
 	AllowMissingImages bool
76 76
 	Deploy             bool
77 77
 
78
+	SourceImage     string
79
+	SourceImagePath string
80
+
78 81
 	SkipGeneration        bool
79 82
 	AllowGenerationErrors bool
80 83
 
... ...
@@ -491,6 +494,17 @@ func (c *AppConfig) search(components app.ComponentReferences) error {
491 491
 	return errors.NewAggregate(errs)
492 492
 }
493 493
 
494
+func (c *AppConfig) detectPartialMatches(components app.ComponentReferences) error {
495
+	errs := []error{}
496
+	for _, ref := range components {
497
+		input := ref.Input()
498
+		if input.ResolvedMatch.Score != 0.0 {
499
+			errs = append(errs, fmt.Errorf("component %q had only a partial match of %q - if this is the value you want to use, specify it explicitly", input.From, input.ResolvedMatch.Name))
500
+		}
501
+	}
502
+	return errors.NewAggregate(errs)
503
+}
504
+
494 505
 // inferBuildTypes infers build status and mismatches between source and docker builders
495 506
 func (c *AppConfig) inferBuildTypes(components app.ComponentReferences) (app.ComponentReferences, error) {
496 507
 	errs := []error{}
... ...
@@ -526,9 +540,6 @@ func (c *AppConfig) inferBuildTypes(components app.ComponentReferences) (app.Com
526 526
 				errs = append(errs, fmt.Errorf("the resolved match %q for component %q cannot build source code - check whether this is the image you want to use, then use --strategy=source to build using source or --strategy=docker to treat this as a Docker base image and set up a layered Docker build", input.ResolvedMatch.Name, ref))
527 527
 				continue
528 528
 			}
529
-		case input.ResolvedMatch.Score != 0.0:
530
-			errs = append(errs, fmt.Errorf("component %q had only a partial match of %q - if this is the value you want to use, specify it explicitly", input.From, input.ResolvedMatch.Name))
531
-			continue
532 529
 		}
533 530
 	}
534 531
 	if len(components) == 0 && c.BinaryBuild {
... ...
@@ -567,8 +578,9 @@ func (c *AppConfig) ensureHasSource(components app.ComponentReferences, reposito
567 567
 			return fmt.Errorf("the following images require source code: %s\n"+
568 568
 				" and the following repositories are not used: %s\nUse '[image]~[repo]' to declare which code goes with which image", components, repositories)
569 569
 		case len(repositories) == 1:
570
-			glog.Infof("Using %q as the source for build", repositories[0])
570
+			glog.V(2).Infof("Using %q as the source for build", repositories[0])
571 571
 			for _, component := range components {
572
+				glog.V(2).Infof("Pairing with component %v", component)
572 573
 				component.Input().Use(repositories[0])
573 574
 				repositories[0].UsedBy(component)
574 575
 			}
... ...
@@ -912,6 +924,47 @@ func (c *AppConfig) RunQuery() (*QueryResult, error) {
912 912
 	}, nil
913 913
 }
914 914
 
915
+func (c *AppConfig) addImageSource(sourceRepos app.SourceRepositories) (app.ComponentReference, app.SourceRepositories, error) {
916
+	if len(c.SourceImage) == 0 {
917
+		return nil, sourceRepos, nil
918
+	}
919
+	paths := strings.SplitN(c.SourceImagePath, ":", 2)
920
+	var sourcePath, destPath string
921
+	switch len(paths) {
922
+	case 1:
923
+		sourcePath = paths[0]
924
+	case 2:
925
+		sourcePath = paths[0]
926
+		destPath = paths[1]
927
+	}
928
+	compRef, _, err := app.NewComponentInput(c.SourceImage)
929
+	if err != nil {
930
+		return nil, nil, err
931
+	}
932
+	resolver := app.PerfectMatchWeightedResolver{}
933
+	if c.imageStreamByAnnotationSearcher != nil {
934
+		resolver = append(resolver, app.WeightedResolver{Searcher: c.imageStreamByAnnotationSearcher, Weight: 0.0})
935
+	}
936
+	if c.imageStreamSearcher != nil {
937
+		resolver = append(resolver, app.WeightedResolver{Searcher: c.imageStreamSearcher, Weight: 1.0})
938
+	}
939
+	if c.dockerSearcher != nil {
940
+		resolver = append(resolver, app.WeightedResolver{Searcher: c.dockerSearcher, Weight: 2.0})
941
+	}
942
+	compRef.Resolver = resolver
943
+	switch len(sourceRepos) {
944
+	case 0:
945
+		sourceRepos = append(sourceRepos, app.NewImageSourceRepository(compRef, sourcePath, destPath))
946
+	case 1:
947
+		sourceRepos[0].SetSourceImage(compRef)
948
+		sourceRepos[0].SetSourceImagePath(sourcePath, destPath)
949
+	default:
950
+		return nil, nil, fmt.Errorf("--image-source cannot be used with multiple source repositories")
951
+	}
952
+
953
+	return compRef, sourceRepos, nil
954
+}
955
+
915 956
 // run executes the provided config applying provided acceptors.
916 957
 func (c *AppConfig) run(acceptors app.Acceptors) (*AppResult, error) {
917 958
 	c.ensureDockerSearcher()
... ...
@@ -928,7 +981,21 @@ func (c *AppConfig) run(acceptors app.Acceptors) (*AppResult, error) {
928 928
 		return nil, err
929 929
 	}
930 930
 
931
-	if err := c.resolve(components); err != nil {
931
+	var imageComp app.ComponentReference
932
+	imageComp, repositories, err = c.addImageSource(repositories)
933
+	if err != nil {
934
+		return nil, err
935
+	}
936
+	componentsIncludingImageComps := components
937
+	if imageComp != nil {
938
+		componentsIncludingImageComps = append(components, imageComp)
939
+	}
940
+	if err := c.resolve(componentsIncludingImageComps); err != nil {
941
+		return nil, err
942
+	}
943
+
944
+	err = c.detectPartialMatches(componentsIncludingImageComps)
945
+	if err != nil {
932 946
 		return nil, err
933 947
 	}
934 948
 
... ...
@@ -1002,6 +1002,7 @@ func TestRunBuilds(t *testing.T) {
1002 1002
 		checkResult func(*AppResult) error
1003 1003
 		checkOutput func(stdout, stderr io.Reader) error
1004 1004
 	}{
1005
+
1005 1006
 		{
1006 1007
 			name: "successful ruby app generation",
1007 1008
 			config: &AppConfig{
... ...
@@ -1210,6 +1211,116 @@ func TestRunBuilds(t *testing.T) {
1210 1210
 				return err.Error() == "--dockerfile cannot be used with multiple source repositories"
1211 1211
 			},
1212 1212
 		},
1213
+
1214
+		{
1215
+			name: "successful input image source build with a repository",
1216
+			config: &AppConfig{
1217
+				SourceRepositories: []string{
1218
+					"https://github.com/openshift/ruby-hello-world",
1219
+				},
1220
+				SourceImage:     "centos/mongodb-26-centos7",
1221
+				SourceImagePath: "/src:dst",
1222
+			},
1223
+			expected: map[string][]string{
1224
+				"buildConfig": {"ruby-hello-world"},
1225
+				"imageStream": {"mongodb-26-centos7", "ruby-22-centos7", "ruby-hello-world"},
1226
+			},
1227
+			checkResult: func(res *AppResult) error {
1228
+				var bc *buildapi.BuildConfig
1229
+				for _, item := range res.List.Items {
1230
+					switch v := item.(type) {
1231
+					case *buildapi.BuildConfig:
1232
+						if bc != nil {
1233
+							return fmt.Errorf("want one BuildConfig got multiple: %#v", res.List.Items)
1234
+						}
1235
+						bc = v
1236
+					}
1237
+				}
1238
+				if bc == nil {
1239
+					return fmt.Errorf("want one BuildConfig got none: %#v", res.List.Items)
1240
+				}
1241
+				var got string
1242
+
1243
+				want := "mongodb-26-centos7:latest"
1244
+				got = bc.Spec.Source.Images[0].From.Name
1245
+				if got != want {
1246
+					return fmt.Errorf("bc.Spec.Source.Image.From.Name = %q; want %q", got, want)
1247
+				}
1248
+
1249
+				want = "ImageStreamTag"
1250
+				got = bc.Spec.Source.Images[0].From.Kind
1251
+				if got != want {
1252
+					return fmt.Errorf("bc.Spec.Source.Image.From.Kind = %q; want %q", got, want)
1253
+				}
1254
+
1255
+				want = "/src"
1256
+				got = bc.Spec.Source.Images[0].Paths[0].SourcePath
1257
+				if got != want {
1258
+					return fmt.Errorf("bc.Spec.Source.Image.Paths[0].SourcePath = %q; want %q", got, want)
1259
+				}
1260
+
1261
+				want = "dst"
1262
+				got = bc.Spec.Source.Images[0].Paths[0].DestinationDir
1263
+				if got != want {
1264
+					return fmt.Errorf("bc.Spec.Source.Image.Paths[0].DestinationDir = %q; want %q", got, want)
1265
+				}
1266
+				return nil
1267
+			},
1268
+		},
1269
+		{
1270
+			name: "successful input image source build with no repository",
1271
+			config: &AppConfig{
1272
+				Components:      []string{"centos/mysql-56-centos7"},
1273
+				To:              "outputimage",
1274
+				SourceImage:     "centos/mongodb-26-centos7",
1275
+				SourceImagePath: "/src:dst",
1276
+			},
1277
+			expected: map[string][]string{
1278
+				"buildConfig": {"outputimage"},
1279
+				"imageStream": {"mongodb-26-centos7", "mysql-56-centos7", "outputimage"},
1280
+			},
1281
+			checkResult: func(res *AppResult) error {
1282
+				var bc *buildapi.BuildConfig
1283
+				for _, item := range res.List.Items {
1284
+					switch v := item.(type) {
1285
+					case *buildapi.BuildConfig:
1286
+						if bc != nil {
1287
+							return fmt.Errorf("want one BuildConfig got multiple: %#v", res.List.Items)
1288
+						}
1289
+						bc = v
1290
+					}
1291
+				}
1292
+				if bc == nil {
1293
+					return fmt.Errorf("want one BuildConfig got none: %#v", res.List.Items)
1294
+				}
1295
+				var got string
1296
+
1297
+				want := "mongodb-26-centos7:latest"
1298
+				got = bc.Spec.Source.Images[0].From.Name
1299
+				if got != want {
1300
+					return fmt.Errorf("bc.Spec.Source.Image.From.Name = %q; want %q", got, want)
1301
+				}
1302
+
1303
+				want = "ImageStreamTag"
1304
+				got = bc.Spec.Source.Images[0].From.Kind
1305
+				if got != want {
1306
+					return fmt.Errorf("bc.Spec.Source.Image.From.Kind = %q; want %q", got, want)
1307
+				}
1308
+
1309
+				want = "/src"
1310
+				got = bc.Spec.Source.Images[0].Paths[0].SourcePath
1311
+				if got != want {
1312
+					return fmt.Errorf("bc.Spec.Source.Image.Paths[0].SourcePath = %q; want %q", got, want)
1313
+				}
1314
+
1315
+				want = "dst"
1316
+				got = bc.Spec.Source.Images[0].Paths[0].DestinationDir
1317
+				if got != want {
1318
+					return fmt.Errorf("bc.Spec.Source.Image.Paths[0].DestinationDir = %q; want %q", got, want)
1319
+				}
1320
+				return nil
1321
+			},
1322
+		},
1213 1323
 	}
1214 1324
 	for _, test := range tests {
1215 1325
 		stdout, stderr := PrepareAppConfig(test.config)
... ...
@@ -1280,11 +1391,7 @@ func PrepareAppConfig(config *AppConfig) (stdout, stderr *bytes.Buffer) {
1280 1280
 	config.dockerSearcher = app.DockerRegistrySearcher{
1281 1281
 		Client: dockerregistry.NewClient(10 * time.Second),
1282 1282
 	}
1283
-	config.imageStreamByAnnotationSearcher = &app.ImageStreamByAnnotationSearcher{
1284
-		Client:            &client.Fake{},
1285
-		ImageStreamImages: &client.Fake{},
1286
-		Namespaces:        []string{"default"},
1287
-	}
1283
+	config.imageStreamByAnnotationSearcher = fakeImageStreamSearcher()
1288 1284
 	config.imageStreamSearcher = fakeImageStreamSearcher()
1289 1285
 	config.originNamespace = "default"
1290 1286
 	config.osclient = &client.Fake{}
... ...
@@ -41,6 +41,7 @@ func (r PerfectMatchWeightedResolver) Resolve(value string) (*ComponentMatch, er
41 41
 	imperfect := ScoredComponentMatches{}
42 42
 	var group WeightedResolvers
43 43
 	for i, resolver := range r {
44
+		// lump all resolvers with the same weight into a single group
44 45
 		if len(group) == 0 || resolver.Weight == group[0].Weight {
45 46
 			group = append(group, resolver)
46 47
 			if i != len(r)-1 && r[i+1].Weight == group[0].Weight {
... ...
@@ -308,7 +309,10 @@ func resolveExact(resolver Resolver, value string) (exact *ComponentMatch, inexa
308 308
 			return nil, nil, err
309 309
 		}
310 310
 	}
311
-	return match, nil, nil
311
+	if match.Score == 0.0 {
312
+		return match, nil, nil
313
+	}
314
+	return nil, ComponentMatches{match}, nil
312 315
 }
313 316
 
314 317
 func searchExact(searcher Searcher, value string) (exact *ComponentMatch, inexact []*ComponentMatch, err error) {
... ...
@@ -319,7 +323,6 @@ func searchExact(searcher Searcher, value string) (exact *ComponentMatch, inexac
319 319
 
320 320
 	exactMatches := matches.Exact()
321 321
 	inexactMatches := matches.Inexact()
322
-
323 322
 	switch len(exactMatches) {
324 323
 	case 0:
325 324
 		return nil, inexactMatches, nil
... ...
@@ -84,7 +84,7 @@ func (r DockerClientSearcher) Search(terms ...string) (ComponentMatches, error)
84 84
 				if tags := matchTag(image, term, ref.Registry, ref.Namespace, ref.Name, ref.Tag); len(tags) > 0 {
85 85
 					for i := range tags {
86 86
 						tags[i].LocalOnly = true
87
-						glog.V(5).Infof("Found local docker image match %q for %q", tags[i].Value, term)
87
+						glog.V(5).Infof("Found local docker image match %q with score %f", tags[i].Value, tags[i].Score)
88 88
 					}
89 89
 					termMatches = append(termMatches, tags...)
90 90
 				}
... ...
@@ -129,7 +129,6 @@ func (r DockerClientSearcher) Search(terms ...string) (ComponentMatches, error)
129 129
 	if len(errs) != 0 {
130 130
 		return nil, utilerrors.NewAggregate(errs)
131 131
 	}
132
-
133 132
 	return componentMatches, nil
134 133
 }
135 134
 
... ...
@@ -241,6 +241,15 @@ func (p *Pipeline) Objects(accept, objectAccept Acceptor) (Objects, error) {
241 241
 		if objectAccept.Accept(build) {
242 242
 			objects = append(objects, build)
243 243
 		}
244
+		if p.Build.Source != nil && p.Build.Source.SourceImage != nil && p.Build.Source.SourceImage.AsImageStream && accept.Accept(p.Build.Source.SourceImage) {
245
+			srcImage, err := p.Build.Source.SourceImage.ImageStream()
246
+			if err != nil {
247
+				return nil, err
248
+			}
249
+			if objectAccept.Accept(srcImage) {
250
+				objects = append(objects, srcImage)
251
+			}
252
+		}
244 253
 	}
245 254
 	if p.Deployment != nil && accept.Accept(p.Deployment) {
246 255
 		dc, err := p.Deployment.DeploymentConfig()
... ...
@@ -82,13 +82,16 @@ func IsRemoteRepository(s string) bool {
82 82
 
83 83
 // SourceRepository represents a code repository that may be the target of a build.
84 84
 type SourceRepository struct {
85
-	location   string
86
-	url        url.URL
87
-	localDir   string
88
-	remoteURL  *url.URL
89
-	contextDir string
90
-	secrets    []buildapi.SecretBuildSource
91
-	info       *SourceRepositoryInfo
85
+	location        string
86
+	url             url.URL
87
+	localDir        string
88
+	remoteURL       *url.URL
89
+	contextDir      string
90
+	secrets         []buildapi.SecretBuildSource
91
+	info            *SourceRepositoryInfo
92
+	sourceImage     ComponentReference
93
+	sourceImageFrom string
94
+	sourceImageTo   string
92 95
 
93 96
 	usedBy           []ComponentReference
94 97
 	buildWithDocker  bool
... ...
@@ -131,6 +134,16 @@ func NewBinarySourceRepository() *SourceRepository {
131 131
 	}
132 132
 }
133 133
 
134
+func NewImageSourceRepository(compRef ComponentReference, from, to string) *SourceRepository {
135
+	return &SourceRepository{
136
+		sourceImage:      compRef,
137
+		sourceImageFrom:  from,
138
+		sourceImageTo:    to,
139
+		ignoreRepository: true,
140
+		location:         compRef.Input().From,
141
+	}
142
+}
143
+
134 144
 // UsedBy sets up which component uses the source repository
135 145
 func (r *SourceRepository) UsedBy(ref ComponentReference) {
136 146
 	r.usedBy = append(r.usedBy, ref)
... ...
@@ -257,6 +270,17 @@ func (r *SourceRepository) Secrets() []buildapi.SecretBuildSource {
257 257
 	return r.secrets
258 258
 }
259 259
 
260
+// SetSourceImage sets the source(input) image for a repository
261
+func (r *SourceRepository) SetSourceImage(c ComponentReference) {
262
+	r.sourceImage = c
263
+}
264
+
265
+// SetSourceImagePath sets the source/destination to use when copying from the SourceImage
266
+func (r *SourceRepository) SetSourceImagePath(source, dest string) {
267
+	r.sourceImageFrom = source
268
+	r.sourceImageTo = dest
269
+}
270
+
260 271
 // AddDockerfile adds the Dockerfile contents to the SourceRepository and
261 272
 // configure it to build with Docker strategy. Returns an error if the contents
262 273
 // are invalid.
... ...
@@ -422,6 +446,16 @@ func StrategyAndSourceForRepository(repo *SourceRepository, image *ImageRef) (*B
422 422
 		Secrets: repo.secrets,
423 423
 	}
424 424
 
425
+	if repo.sourceImage != nil {
426
+		srcImageRef, err := InputImageFromMatch(repo.sourceImage.Input().ResolvedMatch)
427
+		if err != nil {
428
+			return nil, nil, err
429
+		}
430
+		source.SourceImage = srcImageRef
431
+		source.ImageSourcePath = repo.sourceImageFrom
432
+		source.ImageDestPath = repo.sourceImageTo
433
+	}
434
+
425 435
 	if (repo.ignoreRepository || repo.forceAddDockerfile) && repo.Info() != nil && repo.Info().Dockerfile != nil {
426 436
 		source.DockerfileContents = repo.Info().Dockerfile.Contents()
427 437
 	}
... ...
@@ -132,6 +132,9 @@ os::cmd::expect_failure_and_text 'oc new-build mysql -o yaml' 'you must specify
132 132
 os::cmd::expect_success_and_text 'oc new-build mysql --binary -o yaml --to mysql:bin' 'type: Binary'
133 133
 os::cmd::expect_success_and_text 'oc new-build mysql https://github.com/openshift/ruby-hello-world --strategy=docker -o yaml' 'type: Docker'
134 134
 os::cmd::expect_failure_and_text 'oc new-build mysql https://github.com/openshift/ruby-hello-world --binary' 'specifying binary builds and source repositories at the same time is not allowed'
135
+# new-build image source tests
136
+os::cmd::expect_failure_and_text 'oc new-build mysql --source-image centos' 'error: --source-image-path must be specified when --source-image is specified.'
137
+os::cmd::expect_failure_and_text 'oc new-build mysql --source-image-path foo' 'error: --source-image must be specified when --source-image-path is specified.'
135 138
 
136 139
 # do not allow use of non-existent image (should fail)
137 140
 os::cmd::expect_failure_and_text 'oc new-app  openshift/bogusImage https://github.com/openshift/ruby-hello-world.git -o yaml' "no match for"
... ...
@@ -1,42 +1,83 @@
1 1
 package builds
2 2
 
3 3
 import (
4
-	"fmt"
4
+	"time"
5 5
 
6 6
 	g "github.com/onsi/ginkgo"
7 7
 	o "github.com/onsi/gomega"
8 8
 
9
-	"github.com/openshift/origin/test/extended/images"
10 9
 	exutil "github.com/openshift/origin/test/extended/util"
11 10
 )
12 11
 
13 12
 var _ = g.Describe("builds: image source", func() {
14 13
 	defer g.GinkgoRecover()
15 14
 	var (
16
-		buildFixture = exutil.FixturePath("fixtures", "test-build-hello-openshift.yaml")
17
-		helloBuilder = exutil.FixturePath("fixtures", "hello-builder")
18
-		oc           = exutil.NewCLI("build-image-source", exutil.KubeConfigPath())
15
+		buildFixture     = exutil.FixturePath("fixtures", "test-imagesource-build.yaml")
16
+		oc               = exutil.NewCLI("build-image-source", exutil.KubeConfigPath())
17
+		imageSourceLabel = exutil.ParseLabelsOrDie("app=imagesourceapp")
18
+		imageDockerLabel = exutil.ParseLabelsOrDie("app=imagedockerapp")
19 19
 	)
20 20
 
21 21
 	g.JustBeforeEach(func() {
22 22
 		g.By("waiting for builder service account")
23 23
 		err := exutil.WaitForBuilderAccount(oc.KubeREST().ServiceAccounts(oc.Namespace()))
24 24
 		o.Expect(err).NotTo(o.HaveOccurred())
25
+
26
+		g.By("waiting for imagestreams to be imported")
27
+		err = exutil.WaitForAnImageStream(oc.AdminREST().ImageStreams("openshift"), "jenkins", exutil.CheckImageStreamLatestTagPopulatedFn, exutil.CheckImageStreamTagNotFoundFn)
28
+		o.Expect(err).NotTo(o.HaveOccurred())
25 29
 	})
26 30
 
27 31
 	g.Describe("build with image source", func() {
28
-		g.It("should complete successfully and deploy resulting image", func() {
29
-			g.By("Creating build configs, deployment config, and service for hello-openshift")
32
+		g.It("should complete successfully and contain the expected file", func() {
33
+			g.By("Creating build configs for source build")
30 34
 			err := oc.Run("create").Args("-f", buildFixture).Execute()
31 35
 			o.Expect(err).NotTo(o.HaveOccurred())
32
-			g.By("starting the builder image build with a directory")
33
-			err = oc.Run("start-build").Args("hello-builder", fmt.Sprintf("--from-dir=%s", helloBuilder)).Execute()
36
+			g.By("starting the source strategy build")
37
+			err = oc.Run("start-build").Args("imagesourcebuild").Execute()
38
+			o.Expect(err).NotTo(o.HaveOccurred())
39
+			g.By("expecting the builds to complete successfully")
40
+			err = exutil.WaitForABuild(oc.REST().Builds(oc.Namespace()), "imagesourcebuild-1", exutil.CheckBuildSuccessFn, exutil.CheckBuildFailedFn)
41
+			o.Expect(err).NotTo(o.HaveOccurred())
42
+
43
+			g.By("expecting the pod to deploy successfully")
44
+			pods, err := exutil.WaitForPods(oc.KubeREST().Pods(oc.Namespace()), imageSourceLabel, exutil.CheckPodIsRunningFn, 1, 2*time.Minute)
45
+			o.Expect(err).NotTo(o.HaveOccurred())
46
+			o.Expect(len(pods)).To(o.Equal(1))
47
+			pod, err := oc.KubeREST().Pods(oc.Namespace()).Get(pods[0])
34 48
 			o.Expect(err).NotTo(o.HaveOccurred())
35
-			g.By("expect the builds to complete successfully and deploy a hello-openshift pod")
36
-			success, err := images.CheckPageContains(oc, "hello-openshift", "", "Hello OpenShift!")
49
+
50
+			g.By("expecting the pod to contain the file from the input image")
51
+			out, err := oc.Run("exec").Args(pod.Name, "-c", pod.Spec.Containers[0].Name, "--", "ls", "injected/dir").Output()
37 52
 			o.Expect(err).NotTo(o.HaveOccurred())
38
-			o.Expect(success).To(o.BeTrue())
53
+			o.Expect(out).To(o.ContainSubstring("jenkins.war"))
54
+		})
55
+	})
56
+	g.Describe("build with image docker", func() {
57
+		g.It("should complete successfully and contain the expected file", func() {
58
+			g.By("Creating build configs for docker build")
59
+			err := oc.Run("create").Args("-f", buildFixture).Execute()
60
+			o.Expect(err).NotTo(o.HaveOccurred())
61
+			g.By("starting the docker strategy build")
62
+			err = oc.Run("start-build").Args("imagedockerbuild").Execute()
63
+			o.Expect(err).NotTo(o.HaveOccurred())
64
+			g.By("expect the builds to complete successfully")
65
+			err = exutil.WaitForABuild(oc.REST().Builds(oc.Namespace()), "imagedockerbuild-1", exutil.CheckBuildSuccessFn, exutil.CheckBuildFailedFn)
66
+			o.Expect(err).NotTo(o.HaveOccurred())
67
+
68
+			g.By("expect the pod to deploy successfully")
69
+			pods, err := exutil.WaitForPods(oc.KubeREST().Pods(oc.Namespace()), imageDockerLabel, exutil.CheckPodIsRunningFn, 1, 2*time.Minute)
70
+			o.Expect(err).NotTo(o.HaveOccurred())
71
+			o.Expect(len(pods)).To(o.Equal(1))
72
+			pod, err := oc.KubeREST().Pods(oc.Namespace()).Get(pods[0])
73
+			o.Expect(err).NotTo(o.HaveOccurred())
74
+
75
+			g.By("expecting the pod to contain the file from the input image")
76
+			out, err := oc.Run("exec").Args(pod.Name, "-c", pod.Spec.Containers[0].Name, "--", "ls", "injected/dir").Output()
77
+			o.Expect(err).NotTo(o.HaveOccurred())
78
+			o.Expect(out).To(o.ContainSubstring("jenkins.war"))
39 79
 		})
40 80
 
41 81
 	})
82
+
42 83
 })
43 84
deleted file mode 100644
... ...
@@ -1,175 +0,0 @@
1
-apiVersion: v1
2
-kind: List
3
-metadata: {}
4
-items:
5
-- apiVersion: v1
6
-  kind: BuildConfig
7
-  metadata:
8
-    labels:
9
-      build: hello-builder
10
-    name: hello-builder
11
-  spec:
12
-    output:
13
-      to:
14
-        kind: ImageStreamTag
15
-        name: hello-builder:latest
16
-    source:
17
-      binary: {}
18
-      type: Binary
19
-    strategy:
20
-      dockerStrategy: {}
21
-      type: Docker
22
-    triggers: []
23
-- apiVersion: v1
24
-  kind: BuildConfig
25
-  metadata:
26
-    labels:
27
-      build: hello-openshift
28
-    name: hello-openshift
29
-  spec:
30
-    output:
31
-      to:
32
-        kind: ImageStreamTag
33
-        name: hello-openshift:latest
34
-    source:
35
-      contextDir: examples/hello-openshift
36
-      git:
37
-        uri: https://github.com/openshift/origin.git
38
-      image:
39
-        from:
40
-          kind: ImageStreamTag
41
-          name: hello-openshift-binary:latest
42
-        paths:
43
-        - destinationDir: examples/hello-openshift/bin
44
-          sourcePath: /tmp/hello-openshift
45
-      type: Git
46
-    strategy:
47
-      dockerStrategy: {}
48
-      type: Docker
49
-    triggers:
50
-    - imageChange:
51
-        from:
52
-          kind: ImageStreamTag
53
-          name: hello-openshift-binary:latest
54
-      type: ImageChange
55
-- apiVersion: v1
56
-  kind: BuildConfig
57
-  metadata:
58
-    labels:
59
-      build: hello-openshift-binary
60
-    name: hello-openshift-binary
61
-  spec:
62
-    output:
63
-      to:
64
-        kind: ImageStreamTag
65
-        name: hello-openshift-binary:latest
66
-    source:
67
-      contextDir: examples/hello-openshift
68
-      git:
69
-        uri: https://github.com/openshift/origin.git
70
-      type: Git
71
-    strategy:
72
-      sourceStrategy:
73
-        from:
74
-          kind: ImageStreamTag
75
-          name: hello-builder:latest
76
-      type: Source
77
-    triggers:
78
-    - imageChange: {}
79
-      type: ImageChange
80
-  status:
81
-    lastVersion: 0
82
-- apiVersion: v1
83
-  kind: ImageStream
84
-  metadata:
85
-    labels:
86
-      build: hello-builder
87
-    name: hello-builder
88
-  spec: {}
89
-- apiVersion: v1
90
-  kind: ImageStream
91
-  metadata:
92
-    labels:
93
-      build: hello-openshift
94
-    name: hello-openshift
95
-  spec: {}
96
-- apiVersion: v1
97
-  kind: ImageStream
98
-  metadata:
99
-    labels:
100
-      build: hello-openshift-binary
101
-    name: hello-openshift-binary
102
-  spec: {}
103
-- apiVersion: v1
104
-  kind: DeploymentConfig
105
-  metadata:
106
-    labels:
107
-      app: hello-openshift
108
-    name: hello-openshift
109
-  spec:
110
-    replicas: 1
111
-    selector:
112
-      app: hello-openshift
113
-      deploymentconfig: hello-openshift
114
-    strategy:
115
-      resources: {}
116
-      rollingParams:
117
-        intervalSeconds: 1
118
-        maxSurge: 25%
119
-        maxUnavailable: 25%
120
-        timeoutSeconds: 600
121
-        updatePeriodSeconds: 1
122
-      type: Rolling
123
-    template:
124
-      metadata:
125
-        labels:
126
-          app: hello-openshift
127
-          deploymentconfig: hello-openshift
128
-      spec:
129
-        containers:
130
-        - image: hello-openshift
131
-          imagePullPolicy: Always
132
-          readinessProbe:
133
-            httpGet:
134
-              port: 8080
135
-          name: hello-openshift
136
-          ports:
137
-          - containerPort: 8080
138
-            protocol: TCP
139
-          - containerPort: 8888
140
-            protocol: TCP
141
-          terminationMessagePath: /dev/termination-log
142
-        dnsPolicy: ClusterFirst
143
-        restartPolicy: Always
144
-        securityContext: {}
145
-        terminationGracePeriodSeconds: 30
146
-    triggers:
147
-    - imageChangeParams:
148
-        automatic: true
149
-        containerNames:
150
-        - hello-openshift
151
-        from:
152
-          kind: ImageStreamTag
153
-          name: hello-openshift:latest
154
-      type: ImageChange
155
-- apiVersion: v1
156
-  kind: Service
157
-  metadata:
158
-    labels:
159
-      app: hello-openshift
160
-    name: hello-openshift
161
-  spec:
162
-    ports:
163
-    - name: 8080-tcp
164
-      port: 8080
165
-      protocol: TCP
166
-      targetPort: 8080
167
-    - name: 8888-tcp
168
-      port: 8888
169
-      protocol: TCP
170
-      targetPort: 8888
171
-    selector:
172
-      app: hello-openshift
173
-      deploymentconfig: hello-openshift
174
-    sessionAffinity: None
175
-    type: ClusterIP
176 1
new file mode 100644
... ...
@@ -0,0 +1,162 @@
0
+apiVersion: v1
1
+kind: List
2
+metadata: {}
3
+items:
4
+- apiVersion: v1
5
+  kind: BuildConfig
6
+  metadata:
7
+    labels:
8
+      build: imagesourcebuild
9
+    name: imagesourcebuild
10
+  spec:
11
+    output:
12
+      to:
13
+        kind: ImageStreamTag
14
+        name: imagesourceapp:latest
15
+    source:
16
+      git:
17
+        uri: https://github.com/openshift/ruby-hello-world.git
18
+      images:
19
+      - from:
20
+          kind: ImageStreamTag
21
+          name: jenkins:latest
22
+          namespace: openshift
23
+        paths:
24
+        - destinationDir: injected/dir
25
+          sourcePath: /usr/lib/jenkins/jenkins.war
26
+    strategy:
27
+      sourceStrategy: 
28
+        forcePull: true
29
+        from: 
30
+          kind: ImageStreamTag
31
+          name: ruby:latest
32
+          namespace: openshift
33
+- apiVersion: v1
34
+  kind: BuildConfig
35
+  metadata:
36
+    labels:
37
+      build: imagedockerbuild
38
+    name: imagedockerbuild
39
+  spec:
40
+    output:
41
+      to:
42
+        kind: ImageStreamTag
43
+        name: imagedockerapp:latest
44
+    source:
45
+      git:
46
+        uri: https://github.com/openshift/ruby-hello-world.git
47
+      images:
48
+      - from:
49
+          kind: ImageStreamTag
50
+          name: jenkins:latest
51
+          namespace: openshift
52
+        paths:
53
+        - destinationDir: injected/dir
54
+          sourcePath: /usr/lib/jenkins/jenkins.war
55
+    strategy:
56
+      sourceStrategy: 
57
+        forcePull: true
58
+        from: 
59
+          kind: ImageStreamTag
60
+          name: ruby:latest
61
+          namespace: openshift
62
+- apiVersion: v1
63
+  kind: ImageStream
64
+  metadata:
65
+    name: imagesourceapp
66
+  spec: {}
67
+- apiVersion: v1
68
+  kind: ImageStream
69
+  metadata:
70
+    name: imagedockerapp
71
+  spec: {}
72
+
73
+- apiVersion: v1
74
+  kind: DeploymentConfig
75
+  metadata:
76
+    name: imagesourceapp
77
+  spec:
78
+    replicas: 1
79
+    selector:
80
+      app: imagesourceapp
81
+      deploymentconfig: imagesourceapp
82
+    strategy:
83
+      type: Rolling
84
+    template:
85
+      metadata:
86
+        labels:
87
+          app: imagesourceapp
88
+          deploymentconfig: imagesourceapp
89
+      spec:
90
+        containers:
91
+        - image: imagesourceapp
92
+          imagePullPolicy: Always
93
+          readinessProbe:
94
+            httpGet:
95
+              port: 8080
96
+          name: imagesourceapp
97
+          ports:
98
+          - containerPort: 8080
99
+            protocol: TCP
100
+          - containerPort: 8888
101
+            protocol: TCP
102
+          terminationMessagePath: /dev/termination-log
103
+        dnsPolicy: ClusterFirst
104
+        restartPolicy: Always
105
+        securityContext: {}
106
+        terminationGracePeriodSeconds: 30
107
+    triggers:
108
+    - imageChangeParams:
109
+        automatic: true
110
+        containerNames:
111
+        - imagesourceapp
112
+        from:
113
+          kind: ImageStreamTag
114
+          name: imagesourceapp:latest
115
+      type: ImageChange
116
+
117
+
118
+- apiVersion: v1
119
+  kind: DeploymentConfig
120
+  metadata:
121
+    name: imagedockerapp
122
+  spec:
123
+    replicas: 1
124
+    selector:
125
+      app: imagedockerapp
126
+      deploymentconfig: imagedockerapp
127
+    strategy:
128
+      type: Rolling
129
+    template:
130
+      metadata:
131
+        labels:
132
+          app: imagedockerapp
133
+          deploymentconfig: imagedockerapp
134
+      spec:
135
+        containers:
136
+        - image: imagedockerapp
137
+          imagePullPolicy: Always
138
+          readinessProbe:
139
+            httpGet:
140
+              port: 8080
141
+          name: imagedockerapp
142
+          ports:
143
+          - containerPort: 8080
144
+            protocol: TCP
145
+          - containerPort: 8888
146
+            protocol: TCP
147
+          terminationMessagePath: /dev/termination-log
148
+        dnsPolicy: ClusterFirst
149
+        restartPolicy: Always
150
+        securityContext: {}
151
+        terminationGracePeriodSeconds: 30
152
+    triggers:
153
+    - imageChangeParams:
154
+        automatic: true
155
+        containerNames:
156
+        - imagedockerapp
157
+        from:
158
+          kind: ImageStreamTag
159
+          name: imagedockerapp:latest
160
+      type: ImageChange
161
+      
0 162
\ No newline at end of file
... ...
@@ -142,7 +142,7 @@ func WaitForAnImageStream(client client.ImageStreamInterface,
142 142
 				return nil
143 143
 			}
144 144
 			if isFailed(&list.Items[i]) {
145
-				return fmt.Errorf("The deployment %q status is %q",
145
+				return fmt.Errorf("The image stream %q status is %q",
146 146
 					name, list.Items[i].Annotations[imageapi.DockerImageRepositoryCheckAnnotation])
147 147
 			}
148 148
 		}