diff --git a/api/definitions/v1.imagesource/description.adoc b/api/definitions/v1.imagesource/description.adoc new file mode 100644 index 000000000000..42b3d0a72992 --- /dev/null +++ b/api/definitions/v1.imagesource/description.adoc @@ -0,0 +1,5 @@ +ImageSource is used to describe build source that will be extracted from an image. A reference of +type ImageStreamTag, ImageStreamImage or DockerImage may be used. A pull secret can be specified +to pull the image from an external registry or override the default service account secret if pulling +from the internal registry. A list of paths to copy from the image and their respective destination +within the build directory must be specified in the paths array. diff --git a/api/definitions/v1.imagesourcepath/description.adoc b/api/definitions/v1.imagesourcepath/description.adoc new file mode 100644 index 000000000000..4249160b96d9 --- /dev/null +++ b/api/definitions/v1.imagesourcepath/description.adoc @@ -0,0 +1,3 @@ +ImageSourcePath specifies the absolute path of a file or directory within a source image +to be copied to a relative directory of the build home. If a source directory is specified, all +files and directories under that directory are copied from the image. diff --git a/api/swagger-spec/oapi-v1.json b/api/swagger-spec/oapi-v1.json index 4b24fde0a760..e9da28c4cd4e 100644 --- a/api/swagger-spec/oapi-v1.json +++ b/api/swagger-spec/oapi-v1.json @@ -14703,6 +14703,10 @@ "$ref": "v1.GitBuildSource", "description": "optional information about git build source" }, + "image": { + "$ref": "v1.ImageSource", + "description": "optional image build source" + }, "contextDir": { "type": "string", "description": "specifies sub-directory where the source code for the application exists, allows for sources to be built from a directory other than the root of a repository" @@ -14746,6 +14750,47 @@ } } }, + "v1.ImageSource": { + "id": "v1.ImageSource", + "required": [ + "from", + "paths" + ], + "properties": { + "from": { + "$ref": "v1.ObjectReference", + "description": "reference to ImageStreamTag, ImageStreamImage, or DockerImage" + }, + "paths": { + "type": "array", + "items": { + "$ref": "v1.ImageSourcePath" + }, + "description": "paths to copy from image" + }, + "pullSecret": { + "$ref": "v1.LocalObjectReference", + "description": "overrides the default pull secret for the source image" + } + } + }, + "v1.ImageSourcePath": { + "id": "v1.ImageSourcePath", + "required": [ + "sourcePath", + "destinationDir" + ], + "properties": { + "sourcePath": { + "type": "string", + "description": "source path (directory or file) inside image" + }, + "destinationDir": { + "type": "string", + "description": "relative destination directory in build home" + } + } + }, "v1.LocalObjectReference": { "id": "v1.LocalObjectReference", "description": "LocalObjectReference contains enough information to let you locate the referenced object inside the same namespace.", diff --git a/pkg/api/deep_copy_generated.go b/pkg/api/deep_copy_generated.go index e069ab18b083..07f72010a19c 100644 --- a/pkg/api/deep_copy_generated.go +++ b/pkg/api/deep_copy_generated.go @@ -966,6 +966,14 @@ func deepCopy_api_BuildSource(in buildapi.BuildSource, out *buildapi.BuildSource } else { out.Git = nil } + if in.Image != nil { + out.Image = new(buildapi.ImageSource) + if err := deepCopy_api_ImageSource(*in.Image, out.Image, c); err != nil { + return err + } + } else { + out.Image = nil + } out.ContextDir = in.ContextDir if in.SourceSecret != nil { if newVal, err := c.DeepCopy(in.SourceSecret); err != nil { @@ -1219,6 +1227,40 @@ func deepCopy_api_ImageChangeTrigger(in buildapi.ImageChangeTrigger, out *builda return nil } +func deepCopy_api_ImageSource(in buildapi.ImageSource, out *buildapi.ImageSource, c *conversion.Cloner) error { + if newVal, err := c.DeepCopy(in.From); err != nil { + return err + } else { + out.From = newVal.(pkgapi.ObjectReference) + } + if in.Paths != nil { + out.Paths = make([]buildapi.ImageSourcePath, len(in.Paths)) + for i := range in.Paths { + if err := deepCopy_api_ImageSourcePath(in.Paths[i], &out.Paths[i], c); err != nil { + return err + } + } + } else { + out.Paths = nil + } + if in.PullSecret != nil { + if newVal, err := c.DeepCopy(in.PullSecret); err != nil { + return err + } else { + out.PullSecret = newVal.(*pkgapi.LocalObjectReference) + } + } else { + out.PullSecret = nil + } + return nil +} + +func deepCopy_api_ImageSourcePath(in buildapi.ImageSourcePath, out *buildapi.ImageSourcePath, c *conversion.Cloner) error { + out.SourcePath = in.SourcePath + out.DestinationDir = in.DestinationDir + return nil +} + func deepCopy_api_SecretSpec(in buildapi.SecretSpec, out *buildapi.SecretSpec, c *conversion.Cloner) error { if newVal, err := c.DeepCopy(in.SecretSource); err != nil { return err @@ -2896,6 +2938,8 @@ func init() { deepCopy_api_GitBuildSource, deepCopy_api_GitSourceRevision, deepCopy_api_ImageChangeTrigger, + deepCopy_api_ImageSource, + deepCopy_api_ImageSourcePath, deepCopy_api_SecretSpec, deepCopy_api_SourceBuildStrategy, deepCopy_api_SourceControlUser, diff --git a/pkg/api/v1/conversion_generated.go b/pkg/api/v1/conversion_generated.go index 12a36ad31e81..8086621569a8 100644 --- a/pkg/api/v1/conversion_generated.go +++ b/pkg/api/v1/conversion_generated.go @@ -1418,6 +1418,14 @@ func autoconvert_api_BuildSource_To_v1_BuildSource(in *buildapi.BuildSource, out } else { out.Git = nil } + if in.Image != nil { + out.Image = new(apiv1.ImageSource) + if err := convert_api_ImageSource_To_v1_ImageSource(in.Image, out.Image, s); err != nil { + return err + } + } else { + out.Image = nil + } out.ContextDir = in.ContextDir if in.SourceSecret != nil { out.SourceSecret = new(pkgapiv1.LocalObjectReference) @@ -1696,6 +1704,51 @@ func convert_api_ImageChangeTrigger_To_v1_ImageChangeTrigger(in *buildapi.ImageC return autoconvert_api_ImageChangeTrigger_To_v1_ImageChangeTrigger(in, out, s) } +func autoconvert_api_ImageSource_To_v1_ImageSource(in *buildapi.ImageSource, out *apiv1.ImageSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*buildapi.ImageSource))(in) + } + if err := convert_api_ObjectReference_To_v1_ObjectReference(&in.From, &out.From, s); err != nil { + return err + } + if in.Paths != nil { + out.Paths = make([]apiv1.ImageSourcePath, len(in.Paths)) + for i := range in.Paths { + if err := convert_api_ImageSourcePath_To_v1_ImageSourcePath(&in.Paths[i], &out.Paths[i], s); err != nil { + return err + } + } + } else { + out.Paths = nil + } + if in.PullSecret != nil { + out.PullSecret = new(pkgapiv1.LocalObjectReference) + if err := convert_api_LocalObjectReference_To_v1_LocalObjectReference(in.PullSecret, out.PullSecret, s); err != nil { + return err + } + } else { + out.PullSecret = nil + } + return nil +} + +func convert_api_ImageSource_To_v1_ImageSource(in *buildapi.ImageSource, out *apiv1.ImageSource, s conversion.Scope) error { + return autoconvert_api_ImageSource_To_v1_ImageSource(in, out, s) +} + +func autoconvert_api_ImageSourcePath_To_v1_ImageSourcePath(in *buildapi.ImageSourcePath, out *apiv1.ImageSourcePath, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*buildapi.ImageSourcePath))(in) + } + out.SourcePath = in.SourcePath + out.DestinationDir = in.DestinationDir + return nil +} + +func convert_api_ImageSourcePath_To_v1_ImageSourcePath(in *buildapi.ImageSourcePath, out *apiv1.ImageSourcePath, s conversion.Scope) error { + return autoconvert_api_ImageSourcePath_To_v1_ImageSourcePath(in, out, s) +} + func autoconvert_api_SecretSpec_To_v1_SecretSpec(in *buildapi.SecretSpec, out *apiv1.SecretSpec, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*buildapi.SecretSpec))(in) @@ -2132,6 +2185,14 @@ func autoconvert_v1_BuildSource_To_api_BuildSource(in *apiv1.BuildSource, out *b } else { out.Git = nil } + if in.Image != nil { + out.Image = new(buildapi.ImageSource) + if err := convert_v1_ImageSource_To_api_ImageSource(in.Image, out.Image, s); err != nil { + return err + } + } else { + out.Image = nil + } out.ContextDir = in.ContextDir if in.SourceSecret != nil { out.SourceSecret = new(pkgapi.LocalObjectReference) @@ -2411,6 +2472,51 @@ func convert_v1_ImageChangeTrigger_To_api_ImageChangeTrigger(in *apiv1.ImageChan return autoconvert_v1_ImageChangeTrigger_To_api_ImageChangeTrigger(in, out, s) } +func autoconvert_v1_ImageSource_To_api_ImageSource(in *apiv1.ImageSource, out *buildapi.ImageSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*apiv1.ImageSource))(in) + } + if err := convert_v1_ObjectReference_To_api_ObjectReference(&in.From, &out.From, s); err != nil { + return err + } + if in.Paths != nil { + out.Paths = make([]buildapi.ImageSourcePath, len(in.Paths)) + for i := range in.Paths { + if err := convert_v1_ImageSourcePath_To_api_ImageSourcePath(&in.Paths[i], &out.Paths[i], s); err != nil { + return err + } + } + } else { + out.Paths = nil + } + if in.PullSecret != nil { + out.PullSecret = new(pkgapi.LocalObjectReference) + if err := convert_v1_LocalObjectReference_To_api_LocalObjectReference(in.PullSecret, out.PullSecret, s); err != nil { + return err + } + } else { + out.PullSecret = nil + } + return nil +} + +func convert_v1_ImageSource_To_api_ImageSource(in *apiv1.ImageSource, out *buildapi.ImageSource, s conversion.Scope) error { + return autoconvert_v1_ImageSource_To_api_ImageSource(in, out, s) +} + +func autoconvert_v1_ImageSourcePath_To_api_ImageSourcePath(in *apiv1.ImageSourcePath, out *buildapi.ImageSourcePath, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*apiv1.ImageSourcePath))(in) + } + out.SourcePath = in.SourcePath + out.DestinationDir = in.DestinationDir + return nil +} + +func convert_v1_ImageSourcePath_To_api_ImageSourcePath(in *apiv1.ImageSourcePath, out *buildapi.ImageSourcePath, s conversion.Scope) error { + return autoconvert_v1_ImageSourcePath_To_api_ImageSourcePath(in, out, s) +} + func autoconvert_v1_SecretSpec_To_api_SecretSpec(in *apiv1.SecretSpec, out *buildapi.SecretSpec, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*apiv1.SecretSpec))(in) @@ -5289,6 +5395,8 @@ func init() { autoconvert_api_Identity_To_v1_Identity, autoconvert_api_ImageChangeTrigger_To_v1_ImageChangeTrigger, autoconvert_api_ImageList_To_v1_ImageList, + autoconvert_api_ImageSourcePath_To_v1_ImageSourcePath, + autoconvert_api_ImageSource_To_v1_ImageSource, autoconvert_api_ImageStreamImage_To_v1_ImageStreamImage, autoconvert_api_ImageStreamList_To_v1_ImageStreamList, autoconvert_api_ImageStreamMapping_To_v1_ImageStreamMapping, @@ -5398,6 +5506,8 @@ func init() { autoconvert_v1_Identity_To_api_Identity, autoconvert_v1_ImageChangeTrigger_To_api_ImageChangeTrigger, autoconvert_v1_ImageList_To_api_ImageList, + autoconvert_v1_ImageSourcePath_To_api_ImageSourcePath, + autoconvert_v1_ImageSource_To_api_ImageSource, autoconvert_v1_ImageStreamImage_To_api_ImageStreamImage, autoconvert_v1_ImageStreamList_To_api_ImageStreamList, autoconvert_v1_ImageStreamMapping_To_api_ImageStreamMapping, diff --git a/pkg/api/v1/deep_copy_generated.go b/pkg/api/v1/deep_copy_generated.go index e08ddf9aeaab..15f910527342 100644 --- a/pkg/api/v1/deep_copy_generated.go +++ b/pkg/api/v1/deep_copy_generated.go @@ -991,6 +991,14 @@ func deepCopy_v1_BuildSource(in apiv1.BuildSource, out *apiv1.BuildSource, c *co } else { out.Git = nil } + if in.Image != nil { + out.Image = new(apiv1.ImageSource) + if err := deepCopy_v1_ImageSource(*in.Image, out.Image, c); err != nil { + return err + } + } else { + out.Image = nil + } out.ContextDir = in.ContextDir if in.SourceSecret != nil { if newVal, err := c.DeepCopy(in.SourceSecret); err != nil { @@ -1245,6 +1253,40 @@ func deepCopy_v1_ImageChangeTrigger(in apiv1.ImageChangeTrigger, out *apiv1.Imag return nil } +func deepCopy_v1_ImageSource(in apiv1.ImageSource, out *apiv1.ImageSource, c *conversion.Cloner) error { + if newVal, err := c.DeepCopy(in.From); err != nil { + return err + } else { + out.From = newVal.(pkgapiv1.ObjectReference) + } + if in.Paths != nil { + out.Paths = make([]apiv1.ImageSourcePath, len(in.Paths)) + for i := range in.Paths { + if err := deepCopy_v1_ImageSourcePath(in.Paths[i], &out.Paths[i], c); err != nil { + return err + } + } + } else { + out.Paths = nil + } + if in.PullSecret != nil { + if newVal, err := c.DeepCopy(in.PullSecret); err != nil { + return err + } else { + out.PullSecret = newVal.(*pkgapiv1.LocalObjectReference) + } + } else { + out.PullSecret = nil + } + return nil +} + +func deepCopy_v1_ImageSourcePath(in apiv1.ImageSourcePath, out *apiv1.ImageSourcePath, c *conversion.Cloner) error { + out.SourcePath = in.SourcePath + out.DestinationDir = in.DestinationDir + return nil +} + func deepCopy_v1_SecretSpec(in apiv1.SecretSpec, out *apiv1.SecretSpec, c *conversion.Cloner) error { if newVal, err := c.DeepCopy(in.SecretSource); err != nil { return err @@ -2811,6 +2853,8 @@ func init() { deepCopy_v1_GitBuildSource, deepCopy_v1_GitSourceRevision, deepCopy_v1_ImageChangeTrigger, + deepCopy_v1_ImageSource, + deepCopy_v1_ImageSourcePath, deepCopy_v1_SecretSpec, deepCopy_v1_SourceBuildStrategy, deepCopy_v1_SourceControlUser, diff --git a/pkg/api/v1beta3/conversion_generated.go b/pkg/api/v1beta3/conversion_generated.go index 174e84d7be1a..77cbdd5d8d1a 100644 --- a/pkg/api/v1beta3/conversion_generated.go +++ b/pkg/api/v1beta3/conversion_generated.go @@ -1427,6 +1427,14 @@ func autoconvert_api_BuildSource_To_v1beta3_BuildSource(in *buildapi.BuildSource } else { out.Git = nil } + if in.Image != nil { + out.Image = new(apiv1beta3.ImageSource) + if err := convert_api_ImageSource_To_v1beta3_ImageSource(in.Image, out.Image, s); err != nil { + return err + } + } else { + out.Image = nil + } out.ContextDir = in.ContextDir if in.SourceSecret != nil { out.SourceSecret = new(pkgapiv1beta3.LocalObjectReference) @@ -1705,6 +1713,51 @@ func convert_api_ImageChangeTrigger_To_v1beta3_ImageChangeTrigger(in *buildapi.I return autoconvert_api_ImageChangeTrigger_To_v1beta3_ImageChangeTrigger(in, out, s) } +func autoconvert_api_ImageSource_To_v1beta3_ImageSource(in *buildapi.ImageSource, out *apiv1beta3.ImageSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*buildapi.ImageSource))(in) + } + if err := convert_api_ObjectReference_To_v1beta3_ObjectReference(&in.From, &out.From, s); err != nil { + return err + } + if in.Paths != nil { + out.Paths = make([]apiv1beta3.ImageSourcePath, len(in.Paths)) + for i := range in.Paths { + if err := convert_api_ImageSourcePath_To_v1beta3_ImageSourcePath(&in.Paths[i], &out.Paths[i], s); err != nil { + return err + } + } + } else { + out.Paths = nil + } + if in.PullSecret != nil { + out.PullSecret = new(pkgapiv1beta3.LocalObjectReference) + if err := convert_api_LocalObjectReference_To_v1beta3_LocalObjectReference(in.PullSecret, out.PullSecret, s); err != nil { + return err + } + } else { + out.PullSecret = nil + } + return nil +} + +func convert_api_ImageSource_To_v1beta3_ImageSource(in *buildapi.ImageSource, out *apiv1beta3.ImageSource, s conversion.Scope) error { + return autoconvert_api_ImageSource_To_v1beta3_ImageSource(in, out, s) +} + +func autoconvert_api_ImageSourcePath_To_v1beta3_ImageSourcePath(in *buildapi.ImageSourcePath, out *apiv1beta3.ImageSourcePath, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*buildapi.ImageSourcePath))(in) + } + out.SourcePath = in.SourcePath + out.DestinationDir = in.DestinationDir + return nil +} + +func convert_api_ImageSourcePath_To_v1beta3_ImageSourcePath(in *buildapi.ImageSourcePath, out *apiv1beta3.ImageSourcePath, s conversion.Scope) error { + return autoconvert_api_ImageSourcePath_To_v1beta3_ImageSourcePath(in, out, s) +} + func autoconvert_api_SecretSpec_To_v1beta3_SecretSpec(in *buildapi.SecretSpec, out *apiv1beta3.SecretSpec, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*buildapi.SecretSpec))(in) @@ -2141,6 +2194,14 @@ func autoconvert_v1beta3_BuildSource_To_api_BuildSource(in *apiv1beta3.BuildSour } else { out.Git = nil } + if in.Image != nil { + out.Image = new(buildapi.ImageSource) + if err := convert_v1beta3_ImageSource_To_api_ImageSource(in.Image, out.Image, s); err != nil { + return err + } + } else { + out.Image = nil + } out.ContextDir = in.ContextDir if in.SourceSecret != nil { out.SourceSecret = new(pkgapi.LocalObjectReference) @@ -2420,6 +2481,51 @@ func convert_v1beta3_ImageChangeTrigger_To_api_ImageChangeTrigger(in *apiv1beta3 return autoconvert_v1beta3_ImageChangeTrigger_To_api_ImageChangeTrigger(in, out, s) } +func autoconvert_v1beta3_ImageSource_To_api_ImageSource(in *apiv1beta3.ImageSource, out *buildapi.ImageSource, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*apiv1beta3.ImageSource))(in) + } + if err := convert_v1beta3_ObjectReference_To_api_ObjectReference(&in.From, &out.From, s); err != nil { + return err + } + if in.Paths != nil { + out.Paths = make([]buildapi.ImageSourcePath, len(in.Paths)) + for i := range in.Paths { + if err := convert_v1beta3_ImageSourcePath_To_api_ImageSourcePath(&in.Paths[i], &out.Paths[i], s); err != nil { + return err + } + } + } else { + out.Paths = nil + } + if in.PullSecret != nil { + out.PullSecret = new(pkgapi.LocalObjectReference) + if err := convert_v1beta3_LocalObjectReference_To_api_LocalObjectReference(in.PullSecret, out.PullSecret, s); err != nil { + return err + } + } else { + out.PullSecret = nil + } + return nil +} + +func convert_v1beta3_ImageSource_To_api_ImageSource(in *apiv1beta3.ImageSource, out *buildapi.ImageSource, s conversion.Scope) error { + return autoconvert_v1beta3_ImageSource_To_api_ImageSource(in, out, s) +} + +func autoconvert_v1beta3_ImageSourcePath_To_api_ImageSourcePath(in *apiv1beta3.ImageSourcePath, out *buildapi.ImageSourcePath, s conversion.Scope) error { + if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { + defaulting.(func(*apiv1beta3.ImageSourcePath))(in) + } + out.SourcePath = in.SourcePath + out.DestinationDir = in.DestinationDir + return nil +} + +func convert_v1beta3_ImageSourcePath_To_api_ImageSourcePath(in *apiv1beta3.ImageSourcePath, out *buildapi.ImageSourcePath, s conversion.Scope) error { + return autoconvert_v1beta3_ImageSourcePath_To_api_ImageSourcePath(in, out, s) +} + func autoconvert_v1beta3_SecretSpec_To_api_SecretSpec(in *apiv1beta3.SecretSpec, out *buildapi.SecretSpec, s conversion.Scope) error { if defaulting, found := s.DefaultingInterface(reflect.TypeOf(*in)); found { defaulting.(func(*apiv1beta3.SecretSpec))(in) @@ -5264,6 +5370,8 @@ func init() { autoconvert_api_Identity_To_v1beta3_Identity, autoconvert_api_ImageChangeTrigger_To_v1beta3_ImageChangeTrigger, autoconvert_api_ImageList_To_v1beta3_ImageList, + autoconvert_api_ImageSourcePath_To_v1beta3_ImageSourcePath, + autoconvert_api_ImageSource_To_v1beta3_ImageSource, autoconvert_api_ImageStreamImage_To_v1beta3_ImageStreamImage, autoconvert_api_ImageStreamList_To_v1beta3_ImageStreamList, autoconvert_api_ImageStreamMapping_To_v1beta3_ImageStreamMapping, @@ -5373,6 +5481,8 @@ func init() { autoconvert_v1beta3_Identity_To_api_Identity, autoconvert_v1beta3_ImageChangeTrigger_To_api_ImageChangeTrigger, autoconvert_v1beta3_ImageList_To_api_ImageList, + autoconvert_v1beta3_ImageSourcePath_To_api_ImageSourcePath, + autoconvert_v1beta3_ImageSource_To_api_ImageSource, autoconvert_v1beta3_ImageStreamImage_To_api_ImageStreamImage, autoconvert_v1beta3_ImageStreamList_To_api_ImageStreamList, autoconvert_v1beta3_ImageStreamMapping_To_api_ImageStreamMapping, diff --git a/pkg/api/v1beta3/deep_copy_generated.go b/pkg/api/v1beta3/deep_copy_generated.go index 8045a02d555b..63f1b0fd82b7 100644 --- a/pkg/api/v1beta3/deep_copy_generated.go +++ b/pkg/api/v1beta3/deep_copy_generated.go @@ -999,6 +999,14 @@ func deepCopy_v1beta3_BuildSource(in apiv1beta3.BuildSource, out *apiv1beta3.Bui } else { out.Git = nil } + if in.Image != nil { + out.Image = new(apiv1beta3.ImageSource) + if err := deepCopy_v1beta3_ImageSource(*in.Image, out.Image, c); err != nil { + return err + } + } else { + out.Image = nil + } out.ContextDir = in.ContextDir if in.SourceSecret != nil { if newVal, err := c.DeepCopy(in.SourceSecret); err != nil { @@ -1253,6 +1261,40 @@ func deepCopy_v1beta3_ImageChangeTrigger(in apiv1beta3.ImageChangeTrigger, out * return nil } +func deepCopy_v1beta3_ImageSource(in apiv1beta3.ImageSource, out *apiv1beta3.ImageSource, c *conversion.Cloner) error { + if newVal, err := c.DeepCopy(in.From); err != nil { + return err + } else { + out.From = newVal.(pkgapiv1beta3.ObjectReference) + } + if in.Paths != nil { + out.Paths = make([]apiv1beta3.ImageSourcePath, len(in.Paths)) + for i := range in.Paths { + if err := deepCopy_v1beta3_ImageSourcePath(in.Paths[i], &out.Paths[i], c); err != nil { + return err + } + } + } else { + out.Paths = nil + } + if in.PullSecret != nil { + if newVal, err := c.DeepCopy(in.PullSecret); err != nil { + return err + } else { + out.PullSecret = newVal.(*pkgapiv1beta3.LocalObjectReference) + } + } else { + out.PullSecret = nil + } + return nil +} + +func deepCopy_v1beta3_ImageSourcePath(in apiv1beta3.ImageSourcePath, out *apiv1beta3.ImageSourcePath, c *conversion.Cloner) error { + out.SourcePath = in.SourcePath + out.DestinationDir = in.DestinationDir + return nil +} + func deepCopy_v1beta3_SecretSpec(in apiv1beta3.SecretSpec, out *apiv1beta3.SecretSpec, c *conversion.Cloner) error { if newVal, err := c.DeepCopy(in.SecretSource); err != nil { return err @@ -2801,6 +2843,8 @@ func init() { deepCopy_v1beta3_GitBuildSource, deepCopy_v1beta3_GitSourceRevision, deepCopy_v1beta3_ImageChangeTrigger, + deepCopy_v1beta3_ImageSource, + deepCopy_v1beta3_ImageSourcePath, deepCopy_v1beta3_SecretSpec, deepCopy_v1beta3_SourceBuildStrategy, deepCopy_v1beta3_SourceControlUser, diff --git a/pkg/build/api/types.go b/pkg/build/api/types.go index 3483e85a8a3f..988e9a60d46e 100644 --- a/pkg/build/api/types.go +++ b/pkg/build/api/types.go @@ -188,6 +188,9 @@ type BuildSource struct { // Git contains optional information about git build source Git *GitBuildSource + // Image describes an image to be used to provide source for the build + Image *ImageSource + // ContextDir specifies the sub-directory where the source code for the application exists. // This allows to have buildable sources in directory other than root of // repository. @@ -198,9 +201,36 @@ type BuildSource struct { // The secret contains valid credentials for remote repository, where the // data's key represent the authentication method to be used and value is // the base64 encoded credentials. Supported auth methods are: ssh-privatekey. + // TODO: This needs to move under the GitBuildSource struct since it's only + // used for git authentication SourceSecret *kapi.LocalObjectReference } +// ImageSource describes an image that is used as source for the build +type ImageSource struct { + // From is a reference to an ImageStreamTag, ImageStreamImage, or DockerImage to + // copy source from. + From kapi.ObjectReference + + // Paths is a list of source and destination paths to copy from the image. + Paths []ImageSourcePath + + // PullSecret is a reference to a secret to be used to pull the image from a registry + // If the image is pulled from the OpenShift registry, this field does not need to be set. + PullSecret *kapi.LocalObjectReference +} + +// ImageSourcePath describes a path to be copied from a source image and its destination within the build directory. +type ImageSourcePath struct { + // SourcePath is the absolute path of the file or directory inside the image to + // copy to the build directory. + SourcePath string + + // DestinationDir is the relative directory within the build directory + // where files copied from the image are placed. + DestinationDir string +} + type BinaryBuildSource struct { // AsFile indicates that the provided binary input should be considered a single file // within the build input. For example, specifying "webapp.war" would place the provided diff --git a/pkg/build/api/v1/types.go b/pkg/build/api/v1/types.go index 398f08083a4c..ca57ad91c9ef 100644 --- a/pkg/build/api/v1/types.go +++ b/pkg/build/api/v1/types.go @@ -131,6 +131,8 @@ const ( BuildSourceDockerfile BuildSourceType = "Dockerfile" // BuildSourceBinary indicates the build will accept a Binary file as input. BuildSourceBinary BuildSourceType = "Binary" + // BuildSourceImage indicates the build will accept an image as input + BuildSourceImage BuildSourceType = "Image" ) // BuildSource is the SCM used for the build. @@ -158,6 +160,9 @@ type BuildSource struct { // Git contains optional information about git build source Git *GitBuildSource `json:"git,omitempty" description:"optional information about git build source"` + // Image describes an image to be used to provide source for the build + Image *ImageSource `json:"image,omitempty" description:"optional image build source"` + // ContextDir specifies the sub-directory where the source code for the application exists. // This allows to have buildable sources in directory other than root of // repository. @@ -171,6 +176,31 @@ type BuildSource struct { SourceSecret *kapi.LocalObjectReference `json:"sourceSecret,omitempty" description:"supported auth methods are: ssh-privatekey"` } +// ImageSource describes an image that is used as source for the build +type ImageSource struct { + // From is a reference to an ImageStreamTag, ImageStreamImage, or DockerImage to + // copy source from. + From kapi.ObjectReference `json:"from" description:"reference to ImageStreamTag, ImageStreamImage, or DockerImage"` + + // Paths is a list of source and destination paths to copy from the image. + Paths []ImageSourcePath `json:"paths" description:"paths to copy from image"` + + // PullSecret is a reference to a secret to be used to pull the image from a registry + // If the image is pulled from the OpenShift registry, this field does not need to be set. + PullSecret *kapi.LocalObjectReference `json:"pullSecret,omitempty" description:"overrides the default pull secret for the source image"` +} + +// ImageSourcePath describes a path to be copied from a source image and its destination within the build directory. +type ImageSourcePath struct { + // SourcePath is the absolute path of the file or directory inside the image to + // copy to the build directory. + SourcePath string `json:"sourcePath" description:"source path (directory or file) inside image"` + + // DestinationDir is the relative directory within the build directory + // where files copied from the image are placed. + DestinationDir string `json:"destinationDir" description:"relative destination directory in build home"` +} + type BinaryBuildSource struct { // AsFile indicates that the provided binary input should be considered a single file // within the build input. For example, specifying "webapp.war" would place the provided diff --git a/pkg/build/api/v1beta3/types.go b/pkg/build/api/v1beta3/types.go index 4726eced763b..eec5139d1c39 100644 --- a/pkg/build/api/v1beta3/types.go +++ b/pkg/build/api/v1beta3/types.go @@ -131,6 +131,8 @@ const ( BuildSourceDockerfile BuildSourceType = "Dockerfile" // BuildSourceBinary indicates the build will accept a Binary file as input. BuildSourceBinary BuildSourceType = "Binary" + // BuildSourceImage indicates the build will accept an image as input + BuildSourceImage BuildSourceType = "Image" ) // BuildSource is the SCM used for the build. @@ -154,6 +156,9 @@ type BuildSource struct { // Git contains optional information about git build source. Git *GitBuildSource `json:"git,omitempty"` + // Image describes an image to be used to provide source for the build + Image *ImageSource `json:"image,omitempty" description:"optional image build source"` + // Specify the sub-directory where the source code for the application exists. // This allows to have buildable sources in directory other than root of // repository. @@ -167,6 +172,31 @@ type BuildSource struct { SourceSecret *kapi.LocalObjectReference `json:"sourceSecret,omitempty" description:"supported auth methods are: ssh-privatekey"` } +// ImageSource describes an image that is used as source for the build +type ImageSource struct { + // From is a reference to an ImageStreamTag, ImageStreamImage, or DockerImage to + // copy source from. + From kapi.ObjectReference `json:"from" description:"reference to ImageStreamTag, ImageStreamImage, or DockerImage"` + + // Paths is a list of source and destination paths to copy from the image. + Paths []ImageSourcePath `json:"paths" description:"paths to copy from image"` + + // PullSecret is a reference to a secret to be used to pull the image from a registry + // If the image is pulled from the OpenShift registry, this field does not need to be set. + PullSecret *kapi.LocalObjectReference `json:"pullSecret,omitempty" description:"overrides the default pull secret for the source image"` +} + +// ImageSourcePath describes a path to be copied from a source image and its destination within the build directory. +type ImageSourcePath struct { + // SourcePath is the absolute path of the file or directory inside the image to + // copy to the build directory. + SourcePath string `json:"sourcePath" description:"source path (directory or file) inside image"` + + // DestinationDir is the relative directory within the build directory + // where files copied from the image are placed. + DestinationDir string `json:"destinationDir" description:"relative destination directory in build home"` +} + type BinaryBuildSource struct { // AsFile indicates that the provided binary input should be considered a single file // within the build input. For example, specifying "webapp.war" would place the provided diff --git a/pkg/build/api/validation/validation.go b/pkg/build/api/validation/validation.go index 2cce7366dae2..f10095119b1d 100644 --- a/pkg/build/api/validation/validation.go +++ b/pkg/build/api/validation/validation.go @@ -141,23 +141,27 @@ func hasProxy(source *buildapi.GitBuildSource) bool { func validateSource(input *buildapi.BuildSource, isCustomStrategy bool) fielderrors.ValidationErrorList { allErrs := fielderrors.ValidationErrorList{} + // Ensure that Git and Binary source types are mutually exclusive. + if input.Git != nil && input.Binary != nil && !isCustomStrategy { + allErrs = append(allErrs, fielderrors.NewFieldInvalid("git", "", "may not be set when binary is also set")) + allErrs = append(allErrs, fielderrors.NewFieldInvalid("binary", "", "may not be set when git is also set")) + return allErrs + } + + // Validate individual source type details if input.Git != nil { - // git and binary input sources are mutually exclusive - if input.Binary != nil && !isCustomStrategy { - return append(allErrs, fielderrors.NewFieldInvalid("binary", "", "may not be set when git also set")) - } allErrs = append(allErrs, validateGitSource(input.Git).Prefix("git")...) } if input.Binary != nil { - // git and binary input sources are mutually exclusive - if input.Git != nil && !isCustomStrategy { - return append(allErrs, fielderrors.NewFieldInvalid("git", "", "may not be set when binary is also set")) - } allErrs = append(allErrs, validateBinarySource(input.Binary).Prefix("binary")...) } if input.Dockerfile != nil { allErrs = append(allErrs, validateDockerfile(*input.Dockerfile)...) } + if input.Image != nil { + allErrs = append(allErrs, validateImageSource(input.Image).Prefix("image")...) + } + allErrs = append(allErrs, validateSecretRef(input.SourceSecret).Prefix("sourceSecret")...) if len(input.ContextDir) != 0 { @@ -221,6 +225,18 @@ func validateGitSource(git *buildapi.GitBuildSource) fielderrors.ValidationError return allErrs } +func validateImageSource(imageSource *buildapi.ImageSource) fielderrors.ValidationErrorList { + allErrs := fielderrors.ValidationErrorList{} + allErrs = append(allErrs, validateFromImageReference(&imageSource.From).Prefix("from")...) + if imageSource.PullSecret != nil { + allErrs = append(allErrs, validateSecretRef(imageSource.PullSecret).Prefix("pullSecret")...) + } + if len(imageSource.Paths) == 0 { + allErrs = append(allErrs, fielderrors.NewFieldRequired("paths")) + } + return allErrs +} + func validateBinarySource(source *buildapi.BinaryBuildSource) fielderrors.ValidationErrorList { allErrs := fielderrors.ValidationErrorList{} if len(source.AsFile) != 0 { diff --git a/pkg/build/api/validation/validation_test.go b/pkg/build/api/validation/validation_test.go index ddb4235ab550..5714a174f073 100644 --- a/pkg/build/api/validation/validation_test.go +++ b/pkg/build/api/validation/validation_test.go @@ -670,13 +670,14 @@ func TestValidateSource(t *testing.T) { // 3 { t: fielderrors.ValidationErrorTypeInvalid, - path: "binary", + path: "git", source: &buildapi.BuildSource{ Git: &buildapi.GitBuildSource{ URI: "https://example.com/repo.git", }, Binary: &buildapi.BinaryBuildSource{}, }, + multiple: true, }, // 4 { @@ -780,6 +781,78 @@ func TestValidateSource(t *testing.T) { ContextDir: "contextDir", }, }, + // 15 + { + ok: true, + source: &buildapi.BuildSource{ + Image: &buildapi.ImageSource{ + From: kapi.ObjectReference{ + Kind: "ImageStreamTag", + Name: "my-image:latest", + }, + Paths: []buildapi.ImageSourcePath{ + { + SourcePath: "/some/path", + DestinationDir: "test/dir", + }, + }, + }, + }, + }, + // 16 + { + t: fielderrors.ValidationErrorTypeRequired, + path: "image.paths", + source: &buildapi.BuildSource{ + Image: &buildapi.ImageSource{ + From: kapi.ObjectReference{ + Kind: "ImageStreamTag", + Name: "my-image:latest", + }, + }, + }, + }, + // 17 + { + t: fielderrors.ValidationErrorTypeInvalid, + path: "image.from.kind", + source: &buildapi.BuildSource{ + Image: &buildapi.ImageSource{ + From: kapi.ObjectReference{ + Kind: "InvalidKind", + Name: "my-image:latest", + }, + Paths: []buildapi.ImageSourcePath{ + { + SourcePath: "/some/path", + DestinationDir: "test/dir", + }, + }, + }, + }, + }, + // 18 + { + t: fielderrors.ValidationErrorTypeRequired, + path: "image.pullSecret.name", + source: &buildapi.BuildSource{ + Image: &buildapi.ImageSource{ + From: kapi.ObjectReference{ + Kind: "DockerImage", + Name: "my-image:latest", + }, + PullSecret: &kapi.LocalObjectReference{ + Name: "", + }, + Paths: []buildapi.ImageSourcePath{ + { + SourcePath: "/some/path", + DestinationDir: "test/dir", + }, + }, + }, + }, + }, } for i, tc := range errorCases { errors := validateSource(tc.source, false) diff --git a/pkg/build/builder/cmd/dockercfg/cfg.go b/pkg/build/builder/cmd/dockercfg/cfg.go index 4ddce1ec96ef..dfd9c4d837a6 100644 --- a/pkg/build/builder/cmd/dockercfg/cfg.go +++ b/pkg/build/builder/cmd/dockercfg/cfg.go @@ -22,6 +22,7 @@ const ( defaultRegistryServer = "https://index.docker.io/v1/" PushAuthType = "PUSH_DOCKERCFG_PATH" PullAuthType = "PULL_DOCKERCFG_PATH" + PullSourceAuthType = "PULL_SOURCE_DOCKERCFG_PATH" ) // Helper contains all the valid config options for reading the local dockercfg file diff --git a/pkg/build/builder/docker.go b/pkg/build/builder/docker.go index 8d021890c60f..22f0f20eb197 100644 --- a/pkg/build/builder/docker.go +++ b/pkg/build/builder/docker.go @@ -58,7 +58,7 @@ func (d *DockerBuilder) Build() error { if err != nil { return err } - sourceInfo, err := fetchSource(buildDir, d.build, d.urlTimeout, os.Stdin, d.gitClient) + sourceInfo, err := fetchSource(d.dockerClient, buildDir, d.build, d.urlTimeout, os.Stdin, d.gitClient) if err != nil { return err } diff --git a/pkg/build/builder/dockerutil.go b/pkg/build/builder/dockerutil.go index 5f0a679c5e50..f8745a2df4e4 100644 --- a/pkg/build/builder/dockerutil.go +++ b/pkg/build/builder/dockerutil.go @@ -35,6 +35,10 @@ type DockerClient interface { BuildImage(opts docker.BuildImageOptions) error PushImage(opts docker.PushImageOptions, auth docker.AuthConfiguration) error RemoveImage(name string) error + CreateContainer(opts docker.CreateContainerOptions) (*docker.Container, error) + DownloadFromContainer(id string, opts docker.DownloadFromContainerOptions) error + PullImage(opts docker.PullImageOptions, auth docker.AuthConfiguration) error + RemoveContainer(opts docker.RemoveContainerOptions) error } // pushImage pushes a docker image to the registry specified in its tag. diff --git a/pkg/build/builder/dockerutil_test.go b/pkg/build/builder/dockerutil_test.go index 9e7ded0c492a..a9af7d700b28 100644 --- a/pkg/build/builder/dockerutil_test.go +++ b/pkg/build/builder/dockerutil_test.go @@ -33,6 +33,20 @@ func (d *FakeDocker) RemoveImage(name string) error { return nil } +func (d *FakeDocker) CreateContainer(opts docker.CreateContainerOptions) (*docker.Container, error) { + return nil, nil +} + +func (d *FakeDocker) DownloadFromContainer(id string, opts docker.DownloadFromContainerOptions) error { + return nil +} +func (d *FakeDocker) PullImage(opts docker.PullImageOptions, auth docker.AuthConfiguration) error { + return nil +} +func (d *FakeDocker) RemoveContainer(opts docker.RemoveContainerOptions) error { + return nil +} + func TestDockerPush(t *testing.T) { verifyFunc := func(opts docker.PushImageOptions, auth docker.AuthConfiguration) error { if opts.Name != "test/image" { diff --git a/pkg/build/builder/source.go b/pkg/build/builder/source.go index 67fc2485843c..07f8da288582 100644 --- a/pkg/build/builder/source.go +++ b/pkg/build/builder/source.go @@ -10,12 +10,15 @@ import ( "strings" "time" + docker "github.com/fsouza/go-dockerclient" "github.com/golang/glog" s2igit "github.com/openshift/source-to-image/pkg/scm/git" "github.com/openshift/origin/pkg/build/api" + "github.com/openshift/origin/pkg/build/builder/cmd/dockercfg" "github.com/openshift/origin/pkg/generate/git" + "github.com/openshift/source-to-image/pkg/tar" ) const ( @@ -38,7 +41,7 @@ func (e gitNotFoundError) Error() string { // fetchSource retrieves the inputs defined by the build source into the // provided directory, or returns an error if retrieval is not possible. -func fetchSource(dir string, build *api.Build, urlTimeout time.Duration, in io.Reader, gitClient GitClient) (*git.SourceInfo, error) { +func fetchSource(dockerClient DockerClient, dir string, build *api.Build, urlTimeout time.Duration, in io.Reader, gitClient GitClient) (*git.SourceInfo, error) { hasGitSource := false // expect to receive input from STDIN @@ -62,6 +65,15 @@ func fetchSource(dir string, build *api.Build, urlTimeout time.Duration, in io.R } } + // extract source from an Image if specified + if build.Spec.Source.Image != nil { + // fetch image source + err := extractSourceFromImage(dockerClient, build.Spec.Source.Image.From.Name, dir, build.Spec.Source.Image.Paths) + if err != nil { + return nil, err + } + } + // a Dockerfile has been specified, create or overwrite into the destination if dockerfileSource := build.Spec.Source.Dockerfile; dockerfileSource != nil { baseDir := dir @@ -190,3 +202,89 @@ func extractGitSource(gitClient GitClient, gitSource *api.GitBuildSource, revisi } return true, nil } + +func copyImageSource(dockerClient DockerClient, containerID, sourceDir, destDir string, tarHelper tar.Tar) error { + // Setup destination directory + fi, err := os.Stat(destDir) + if err != nil { + if !os.IsNotExist(err) { + return err + } + glog.V(4).Infof("Creating image destination directory: %s", destDir) + err := os.MkdirAll(destDir, 0644) + if err != nil { + return err + } + } else { + if !fi.IsDir() { + return fmt.Errorf("destination %s must be a directory", destDir) + } + } + + tempFile, err := ioutil.TempFile("", "imgsrc") + if err != nil { + return err + } + glog.V(4).Infof("Downloading source from path %s in container %s to temporary archive %s", sourceDir, containerID, tempFile.Name()) + err = dockerClient.DownloadFromContainer(containerID, docker.DownloadFromContainerOptions{ + OutputStream: tempFile, + Path: sourceDir, + }) + if err != nil { + return err + } + if err := tempFile.Close(); err != nil { + return err + } + + // Extract the created tar file to the destination directory + file, err := os.Open(tempFile.Name()) + if err != nil { + return err + } + defer file.Close() + + glog.V(4).Infof("Extracting temporary tar %s to directory %s", tempFile.Name(), destDir) + var tarOutput io.Writer + if glog.V(4) { + tarOutput = os.Stdout + } + return tarHelper.ExtractTarStreamWithLogging(destDir, file, tarOutput) +} + +func extractSourceFromImage(dockerClient DockerClient, image, buildDir string, paths []api.ImageSourcePath) error { + glog.V(4).Infof("Extracting image source from %s", image) + + // Pre-pull image if a secret is specified + pullSecret := os.Getenv(dockercfg.PullSourceAuthType) + if len(pullSecret) > 0 { + dockerAuth, present := dockercfg.NewHelper().GetDockerAuth(image, dockercfg.PullSourceAuthType) + if present { + dockerClient.PullImage(docker.PullImageOptions{Repository: image}, dockerAuth) + } + } + + // Create container to copy from + container, err := dockerClient.CreateContainer(docker.CreateContainerOptions{ + Config: &docker.Config{ + Image: image, + }, + }) + if err != nil { + return fmt.Errorf("error creating source image container: %v", err) + } + defer dockerClient.RemoveContainer(docker.RemoveContainerOptions{ID: container.ID}) + + tarHelper := tar.New() + tarHelper.SetExclusionPattern(nil) + + for _, path := range paths { + glog.V(4).Infof("Extracting path %s from container %s to %s", path.SourcePath, container.ID, path.DestinationDir) + err := copyImageSource(dockerClient, container.ID, path.SourcePath, filepath.Join(buildDir, path.DestinationDir), tarHelper) + if err != nil { + return fmt.Errorf("error copying source path %s to %s: %v", path.SourcePath, path.DestinationDir, err) + } + } + + return nil +} diff --git a/pkg/build/builder/sti.go b/pkg/build/builder/sti.go index 6299ccb19b82..de7f0f48cd10 100644 --- a/pkg/build/builder/sti.go +++ b/pkg/build/builder/sti.go @@ -256,7 +256,7 @@ func (d *downloader) Download(config *s2iapi.Config) (*s2iapi.SourceInfo, error) } // fetch source - sourceInfo, err := fetchSource(targetDir, d.s.build, d.timeout, d.in, d.s.gitClient) + sourceInfo, err := fetchSource(d.s.dockerClient, targetDir, d.s.build, d.timeout, d.in, d.s.gitClient) if err != nil { return nil, err } diff --git a/pkg/build/builder/sti_test.go b/pkg/build/builder/sti_test.go index e1b7670adcc1..f270de476aa2 100644 --- a/pkg/build/builder/sti_test.go +++ b/pkg/build/builder/sti_test.go @@ -36,6 +36,22 @@ func (client testDockerClient) RemoveImage(name string) error { return nil } +func (client testDockerClient) CreateContainer(opts docker.CreateContainerOptions) (*docker.Container, error) { + return nil, nil +} + +func (client testDockerClient) DownloadFromContainer(id string, opts docker.DownloadFromContainerOptions) error { + return nil +} + +func (client testDockerClient) PullImage(opts docker.PullImageOptions, auth docker.AuthConfiguration) error { + return nil +} + +func (client testDockerClient) RemoveContainer(opts docker.RemoveContainerOptions) error { + return nil +} + type testStiBuilderFactory struct { getStrategyErr error buildError error diff --git a/pkg/build/controller/strategy/custom.go b/pkg/build/controller/strategy/custom.go index ec50fbceda5e..34854f2e80d4 100644 --- a/pkg/build/controller/strategy/custom.go +++ b/pkg/build/controller/strategy/custom.go @@ -92,7 +92,11 @@ func (bs *CustomBuildStrategy) CreateBuildPod(build *buildapi.Build) (*kapi.Pod, if strategy.ExposeDockerSocket { setupDockerSocket(pod) - setupDockerSecrets(pod, build.Spec.Output.PushSecret, strategy.PullSecret) + var sourceImageSecret *kapi.LocalObjectReference + if build.Spec.Source.Image != nil { + sourceImageSecret = build.Spec.Source.Image.PullSecret + } + setupDockerSecrets(pod, build.Spec.Output.PushSecret, strategy.PullSecret, sourceImageSecret) } setupSourceSecrets(pod, build.Spec.Source.SourceSecret) setupAdditionalSecrets(pod, build.Spec.Strategy.CustomStrategy.Secrets) diff --git a/pkg/build/controller/strategy/docker.go b/pkg/build/controller/strategy/docker.go index 0c48690ce594..d9fc435aeefa 100644 --- a/pkg/build/controller/strategy/docker.go +++ b/pkg/build/controller/strategy/docker.go @@ -77,7 +77,11 @@ func (bs *DockerBuildStrategy) CreateBuildPod(build *buildapi.Build) (*kapi.Pod, } setupDockerSocket(pod) - setupDockerSecrets(pod, build.Spec.Output.PushSecret, strategy.PullSecret) + var sourceImageSecret *kapi.LocalObjectReference + if build.Spec.Source.Image != nil { + sourceImageSecret = build.Spec.Source.Image.PullSecret + } + setupDockerSecrets(pod, build.Spec.Output.PushSecret, strategy.PullSecret, sourceImageSecret) setupSourceSecrets(pod, build.Spec.Source.SourceSecret) return pod, nil } diff --git a/pkg/build/controller/strategy/sti.go b/pkg/build/controller/strategy/sti.go index d52fc0bb44b7..a103fb1e0abe 100644 --- a/pkg/build/controller/strategy/sti.go +++ b/pkg/build/controller/strategy/sti.go @@ -99,7 +99,11 @@ func (bs *SourceBuildStrategy) CreateBuildPod(build *buildapi.Build) (*kapi.Pod, } setupDockerSocket(pod) - setupDockerSecrets(pod, build.Spec.Output.PushSecret, strategy.PullSecret) + var sourceImageSecret *kapi.LocalObjectReference + if build.Spec.Source.Image != nil { + sourceImageSecret = build.Spec.Source.Image.PullSecret + } + setupDockerSecrets(pod, build.Spec.Output.PushSecret, strategy.PullSecret, sourceImageSecret) setupSourceSecrets(pod, build.Spec.Source.SourceSecret) return pod, nil } diff --git a/pkg/build/controller/strategy/util.go b/pkg/build/controller/strategy/util.go index 72ae2d22b922..9080569a43be 100644 --- a/pkg/build/controller/strategy/util.go +++ b/pkg/build/controller/strategy/util.go @@ -13,10 +13,11 @@ import ( const ( // dockerSocketPath is the default path for the Docker socket inside the builder container - dockerSocketPath = "/var/run/docker.sock" - DockerPushSecretMountPath = "/var/run/secrets/openshift.io/push" - DockerPullSecretMountPath = "/var/run/secrets/openshift.io/pull" - sourceSecretMountPath = "/var/run/secrets/openshift.io/source" + dockerSocketPath = "/var/run/docker.sock" + DockerPushSecretMountPath = "/var/run/secrets/openshift.io/push" + DockerPullSecretMountPath = "/var/run/secrets/openshift.io/pull" + SourceImagePullSecretMountPath = "/var/run/secrets/openshift.io/source-image" + sourceSecretMountPath = "/var/run/secrets/openshift.io/source" ) var whitelistEnvVarNames = []string{"BUILD_LOGLEVEL"} @@ -97,13 +98,13 @@ func mountSecretVolume(pod *kapi.Pod, secretName, mountPath, volumePrefix string // setupDockerSecrets mounts Docker Registry secrets into Pod running the build, // allowing Docker to authenticate against private registries or Docker Hub. -func setupDockerSecrets(pod *kapi.Pod, pushSecret, pullSecret *kapi.LocalObjectReference) { +func setupDockerSecrets(pod *kapi.Pod, pushSecret, pullSecret, sourceImageSecret *kapi.LocalObjectReference) { if pushSecret != nil { mountSecretVolume(pod, pushSecret.Name, DockerPushSecretMountPath, "push") pod.Spec.Containers[0].Env = append(pod.Spec.Containers[0].Env, []kapi.EnvVar{ {Name: "PUSH_DOCKERCFG_PATH", Value: filepath.Join(DockerPushSecretMountPath, kapi.DockerConfigKey)}, }...) - glog.V(3).Infof("%s be used for docker push in %s", DockerPullSecretMountPath, pod.Name) + glog.V(3).Infof("%s will be used for docker push in %s", DockerPullSecretMountPath, pod.Name) } if pullSecret != nil { @@ -111,7 +112,16 @@ func setupDockerSecrets(pod *kapi.Pod, pushSecret, pullSecret *kapi.LocalObjectR pod.Spec.Containers[0].Env = append(pod.Spec.Containers[0].Env, []kapi.EnvVar{ {Name: "PULL_DOCKERCFG_PATH", Value: filepath.Join(DockerPullSecretMountPath, kapi.DockerConfigKey)}, }...) - glog.V(3).Infof("%s be used for docker pull in %s", DockerPullSecretMountPath, pod.Name) + glog.V(3).Infof("%s will be used for docker pull in %s", DockerPullSecretMountPath, pod.Name) + } + + if sourceImageSecret != nil { + mountSecretVolume(pod, sourceImageSecret.Name, SourceImagePullSecretMountPath, "source-image") + pod.Spec.Containers[0].Env = append(pod.Spec.Containers[0].Env, []kapi.EnvVar{ + {Name: "PULL_SOURCE_DOCKERCFG_PATH", Value: filepath.Join(SourceImagePullSecretMountPath, kapi.DockerConfigKey)}, + }...) + glog.V(3).Infof("%s will be used for docker pull in %s", SourceImagePullSecretMountPath, pod.Name) + } } diff --git a/pkg/build/generator/generator.go b/pkg/build/generator/generator.go index a8f5325dc6dd..0aaf7bba5fdf 100644 --- a/pkg/build/generator/generator.go +++ b/pkg/build/generator/generator.go @@ -397,6 +397,19 @@ func (g *BuildGenerator) generateBuildFromConfig(ctx kapi.Context, bc *buildapi. } strategyImageChangeTrigger := getStrategyImageChangeTrigger(bc) + // Resolve image source if present + if build.Spec.Source.Image != nil { + if build.Spec.Source.Image.PullSecret == nil { + build.Spec.Source.Image.PullSecret = g.resolveImageSecret(ctx, builderSecrets, &build.Spec.Source.Image.From, bc.Namespace) + } + sourceImage, err := g.resolveImageStreamReference(ctx, build.Spec.Source.Image.From, bc.Namespace) + if err != nil { + return nil, err + } + build.Spec.Source.Image.From.Kind = "DockerImage" + build.Spec.Source.Image.From.Name = sourceImage + } + // If the Build is using a From reference instead of a resolved image, we need to resolve that From // reference to a valid image so we can run the build. Builds do not consume ImageStream references, // only image specs. @@ -404,6 +417,7 @@ func (g *BuildGenerator) generateBuildFromConfig(ctx kapi.Context, bc *buildapi. if strategyImageChangeTrigger != nil { image = strategyImageChangeTrigger.LastTriggeredImageID } + switch { case build.Spec.Strategy.SourceStrategy != nil: if image == "" { diff --git a/test/extended/builds/image_source.go b/test/extended/builds/image_source.go new file mode 100644 index 000000000000..8dc04cafea50 --- /dev/null +++ b/test/extended/builds/image_source.go @@ -0,0 +1,42 @@ +package builds + +import ( + "fmt" + + g "github.com/onsi/ginkgo" + o "github.com/onsi/gomega" + + "github.com/openshift/origin/test/extended/images" + exutil "github.com/openshift/origin/test/extended/util" +) + +var _ = g.Describe("builds: image source", func() { + defer g.GinkgoRecover() + var ( + buildFixture = exutil.FixturePath("fixtures", "test-build-hello-openshift.yaml") + helloBuilder = exutil.FixturePath("fixtures", "hello-builder") + oc = exutil.NewCLI("build-image-source", exutil.KubeConfigPath()) + ) + + g.JustBeforeEach(func() { + g.By("waiting for builder service account") + err := exutil.WaitForBuilderAccount(oc.KubeREST().ServiceAccounts(oc.Namespace())) + o.Expect(err).NotTo(o.HaveOccurred()) + }) + + g.Describe("build with image source", func() { + g.It("should complete successfully and deploy resulting image", func() { + g.By("Creating build configs, deployment config, and service for hello-openshift") + err := oc.Run("create").Args("-f", buildFixture).Execute() + o.Expect(err).NotTo(o.HaveOccurred()) + g.By("starting the builder image build with a directory") + err = oc.Run("start-build").Args("hello-builder", fmt.Sprintf("--from-dir=%s", helloBuilder)).Execute() + o.Expect(err).NotTo(o.HaveOccurred()) + g.By("expect the builds to complete successfully and deploy a hello-openshift pod") + success, err := images.CheckPageContains(oc, "hello-openshift", "", "Hello OpenShift!") + o.Expect(err).NotTo(o.HaveOccurred()) + o.Expect(success).To(o.BeTrue()) + }) + + }) +}) diff --git a/test/extended/fixtures/hello-builder/Dockerfile b/test/extended/fixtures/hello-builder/Dockerfile new file mode 100644 index 000000000000..81b6a64c5db5 --- /dev/null +++ b/test/extended/fixtures/hello-builder/Dockerfile @@ -0,0 +1,6 @@ +FROM openshift/origin-release:latest +LABEL io.openshift.s2i.scripts-url=image:///usr/libexec/s2i +ENV STI_SCRIPTS_PATH=/usr/libexec/s2i +COPY scripts $STI_SCRIPTS_PATH +RUN chown 1001 /openshifttmp +USER 1001 diff --git a/test/extended/fixtures/hello-builder/scripts/assemble b/test/extended/fixtures/hello-builder/scripts/assemble new file mode 100755 index 000000000000..49042d7ea888 --- /dev/null +++ b/test/extended/fixtures/hello-builder/scripts/assemble @@ -0,0 +1,5 @@ +#!/bin/sh +unset GOPATH +cd /tmp/src +CGO_ENABLED=0 go build -a -installsuffix cgo -o hello-openshift -tags netgo +mv hello-openshift /tmp diff --git a/test/extended/fixtures/hello-builder/scripts/run b/test/extended/fixtures/hello-builder/scripts/run new file mode 100755 index 000000000000..b5e0af20f30f --- /dev/null +++ b/test/extended/fixtures/hello-builder/scripts/run @@ -0,0 +1,2 @@ +#!/bin/sh +echo This image is not meant to be run by itself diff --git a/test/extended/fixtures/test-build-hello-openshift.yaml b/test/extended/fixtures/test-build-hello-openshift.yaml new file mode 100644 index 000000000000..0b2ed12bc112 --- /dev/null +++ b/test/extended/fixtures/test-build-hello-openshift.yaml @@ -0,0 +1,175 @@ +apiVersion: v1 +kind: List +metadata: {} +items: +- apiVersion: v1 + kind: BuildConfig + metadata: + labels: + build: hello-builder + name: hello-builder + spec: + output: + to: + kind: ImageStreamTag + name: hello-builder:latest + source: + binary: {} + type: Binary + strategy: + dockerStrategy: {} + type: Docker + triggers: [] +- apiVersion: v1 + kind: BuildConfig + metadata: + labels: + build: hello-openshift + name: hello-openshift + spec: + output: + to: + kind: ImageStreamTag + name: hello-openshift:latest + source: + contextDir: examples/hello-openshift + git: + uri: https://github.com/openshift/origin.git + image: + from: + kind: ImageStreamTag + name: hello-openshift-binary:latest + paths: + - destinationPath: examples/hello-openshift/bin + sourceDir: /tmp/hello-openshift + type: Git + strategy: + dockerStrategy: {} + type: Docker + triggers: + - imageChange: + from: + kind: ImageStreamTag + name: hello-openshift-binary:latest + type: ImageChange +- apiVersion: v1 + kind: BuildConfig + metadata: + labels: + build: hello-openshift-binary + name: hello-openshift-binary + spec: + output: + to: + kind: ImageStreamTag + name: hello-openshift-binary:latest + source: + contextDir: examples/hello-openshift + git: + uri: https://github.com/openshift/origin.git + type: Git + strategy: + sourceStrategy: + from: + kind: ImageStreamTag + name: hello-builder:latest + type: Source + triggers: + - imageChange: {} + type: ImageChange + status: + lastVersion: 0 +- apiVersion: v1 + kind: ImageStream + metadata: + labels: + build: hello-builder + name: hello-builder + spec: {} +- apiVersion: v1 + kind: ImageStream + metadata: + labels: + build: hello-openshift + name: hello-openshift + spec: {} +- apiVersion: v1 + kind: ImageStream + metadata: + labels: + build: hello-openshift-binary + name: hello-openshift-binary + spec: {} +- apiVersion: v1 + kind: DeploymentConfig + metadata: + labels: + app: hello-openshift + name: hello-openshift + spec: + replicas: 1 + selector: + app: hello-openshift + deploymentconfig: hello-openshift + strategy: + resources: {} + rollingParams: + intervalSeconds: 1 + maxSurge: 25% + maxUnavailable: 25% + timeoutSeconds: 600 + updatePeriodSeconds: 1 + type: Rolling + template: + metadata: + labels: + app: hello-openshift + deploymentconfig: hello-openshift + spec: + containers: + - image: hello-openshift + imagePullPolicy: Always + readinessProbe: + httpGet: + port: 8080 + name: hello-openshift + ports: + - containerPort: 8080 + protocol: TCP + - containerPort: 8888 + protocol: TCP + terminationMessagePath: /dev/termination-log + dnsPolicy: ClusterFirst + restartPolicy: Always + securityContext: {} + terminationGracePeriodSeconds: 30 + triggers: + - imageChangeParams: + automatic: true + containerNames: + - hello-openshift + from: + kind: ImageStreamTag + name: hello-openshift:latest + type: ImageChange +- apiVersion: v1 + kind: Service + metadata: + labels: + app: hello-openshift + name: hello-openshift + spec: + ports: + - name: 8080-tcp + port: 8080 + protocol: TCP + targetPort: 8080 + - name: 8888-tcp + port: 8888 + protocol: TCP + targetPort: 8888 + selector: + app: hello-openshift + deploymentconfig: hello-openshift + sessionAffinity: None + type: ClusterIP