@@ -65,19 +65,23 @@ func NewRelease(f kcmdutil.Factory, parentName string, streams genericclioptions
6565
6666 OpenShift uses long-running active management processes called "operators" to
6767 keep the cluster running and manage component lifecycle. This command
68- composes a set of images and operator definitions into a single update payload
69- that can be used to update a cluster.
68+ composes a set of images with operator definitions into a single update payload
69+ that can be used to install or update a cluster.
7070
7171 Operators are expected to host the config they need to be installed to a cluster
7272 in the '/manifests' directory in their image. This command iterates over a set of
7373 operator images and extracts those manifests into a single, ordered list of
7474 Kubernetes objects that can then be iteratively updated on a cluster by the
7575 cluster version operator when it is time to perform an update. Manifest files are
76- renamed to '99_ <image_name>_<filename>' by default, and an operator author that
76+ renamed to '0000_70_ <image_name>_<filename>' by default, and an operator author that
7777 needs to provide a global-ordered file (before or after other operators) should
78- prepend '0000_' to their filename, which instructs the release builder to not
79- assign a component prefix. Only images with the label
80- 'release.openshift.io/operator=true' are considered to be included.
78+ prepend '0000_NN_<component>_' to their filename, which instructs the release builder
79+ to not assign a component prefix. Only images in the input that have the image label
80+ 'io.openshift.release.operator=true' will have manifests loaded.
81+
82+ If an image is in the input but is not referenced by an operator's image-references
83+ file, the image will not be included in the final release image unless
84+ --include=NAME is provided.
8185
8286 Mappings specified via SRC=DST positional arguments allows overriding particular
8387 operators with a specific image. For example:
@@ -86,13 +90,19 @@ func NewRelease(f kcmdutil.Factory, parentName string, streams genericclioptions
8690
8791 will override the default cluster-version-operator image with one pulled from
8892 registry.example.com.
89-
90- Experimental: This command is under active development and may change without notice.
9193 ` ),
9294 Example : templates .Examples (fmt .Sprintf (`
9395 # Create a release from the latest origin images and push to a DockerHub repo
9496 %[1]s new --from-image-stream=origin-v4.0 -n openshift --to-image docker.io/mycompany/myrepo:latest
95- ` , parentName )),
97+
98+ # Create a new release with updated metadata from a previous release
99+ %[1]s new --from-release registry.svc.ci.openshift.org/openshift/origin-release:v4.0 --name 4.0.1 \
100+ --previous 4.0.0 --metadata ... --to-image docker.io/mycompany/myrepo:latest
101+
102+ # Create a new release and override a single image
103+ %[1]s new --from-release registry.svc.ci.openshift.org/openshift/origin-release:v4.0 \
104+ cli=docker.io/mycompany/cli:latest
105+ ` , parentName )),
96106 Run : func (cmd * cobra.Command , args []string ) {
97107 kcmdutil .CheckErr (o .Complete (f , cmd , args ))
98108 kcmdutil .CheckErr (o .Run ())
@@ -101,8 +111,9 @@ func NewRelease(f kcmdutil.Factory, parentName string, streams genericclioptions
101111 flags := cmd .Flags ()
102112
103113 // image inputs
104- flags .StringSliceVarP (& o .Filenames , "filename" , "f " , o .Filenames , "A file defining a mapping of input images to use to build the release" )
114+ flags .StringSliceVar (& o .Filenames , "filename" , o .Filenames , "A file defining a mapping of input images to use to build the release" )
105115 flags .StringVar (& o .FromImageStream , "from-image-stream" , o .FromImageStream , "Look at all tags in the provided image stream and build a release payload from them." )
116+ flags .StringVarP (& o .FromImageStreamFile , "from-image-stream-file" , "f" , o .FromImageStreamFile , "Take the provided image stream on disk and build a release payload from it." )
106117 flags .StringVar (& o .FromDirectory , "from-dir" , o .FromDirectory , "Use this directory as the source for the release payload." )
107118 flags .StringVar (& o .FromReleaseImage , "from-release" , o .FromReleaseImage , "Use an existing release image as input." )
108119 flags .StringVar (& o .ReferenceMode , "reference-mode" , o .ReferenceMode , "By default, the image reference from an image stream points to the public registry for the stream and the image digest. Pass 'source' to build references to the originating image." )
@@ -150,9 +161,10 @@ type NewOptions struct {
150161
151162 FromReleaseImage string
152163
153- FromImageStream string
154- Namespace string
155- ReferenceMode string
164+ FromImageStream string
165+ FromImageStreamFile string
166+ Namespace string
167+ ReferenceMode string
156168
157169 ExtraComponentVersions string
158170 AllowedComponents []string
@@ -232,18 +244,25 @@ type imageData struct {
232244 Directory string
233245}
234246
235- func findStatusTagEvent (tags []imageapi.NamedTagEventList , name string ) * imageapi.TagEvent {
236- for _ , tag := range tags {
247+ func findStatusTagEvents (tags []imageapi.NamedTagEventList , name string ) * imageapi.NamedTagEventList {
248+ for i := range tags {
249+ tag := & tags [i ]
237250 if tag .Tag != name {
238251 continue
239252 }
240- if len (tag .Items ) == 0 {
241- return nil
242- }
243- return & tag .Items [0 ]
253+ return tag
244254 }
245255 return nil
246256}
257+
258+ func findStatusTagEvent (tags []imageapi.NamedTagEventList , name string ) * imageapi.TagEvent {
259+ events := findStatusTagEvents (tags , name )
260+ if events == nil || len (events .Items ) == 0 {
261+ return nil
262+ }
263+ return & events .Items [0 ]
264+ }
265+
247266func findSpecTag (tags []imageapi.TagReference , name string ) * imageapi.TagReference {
248267 for i , tag := range tags {
249268 if tag .Name != name {
@@ -273,12 +292,25 @@ func (o *NewOptions) cleanup() {
273292func (o * NewOptions ) Run () error {
274293 defer o .cleanup ()
275294
276- if len (o .FromImageStream ) > 0 && len (o .FromDirectory ) > 0 {
277- return fmt .Errorf ("only one of --from-image-stream and --from-dir may be specified" )
295+ sources := 0
296+ if len (o .FromImageStream ) > 0 {
297+ sources ++
298+ }
299+ if len (o .FromImageStreamFile ) > 0 {
300+ sources ++
301+ }
302+ if len (o .FromReleaseImage ) > 0 {
303+ sources ++
304+ }
305+ if len (o .FromDirectory ) > 0 {
306+ sources ++
307+ }
308+ if sources > 1 {
309+ return fmt .Errorf ("only one of --from-image-stream, --from-image-stream-file, --from-release, or --from-dir may be specified" )
278310 }
279- if len ( o . FromDirectory ) == 0 && len ( o . FromImageStream ) == 0 && len ( o . FromReleaseImage ) == 0 {
311+ if sources == 0 {
280312 if len (o .Mappings ) == 0 {
281- return fmt .Errorf ("must specify image mappings" )
313+ return fmt .Errorf ("must specify image mappings when no other source is defined " )
282314 }
283315 }
284316 if len (o .Mirror ) > 0 && o .ReferenceMode != "" && o .ReferenceMode != "public" {
@@ -438,16 +470,37 @@ func (o *NewOptions) Run() error {
438470
439471 fmt .Fprintf (o .ErrOut , "info: Found %d images in release\n " , len (is .Spec .Tags ))
440472
441- case len (o .FromImageStream ) > 0 :
473+ case len (o .FromImageStream ) > 0 , len ( o . FromImageStreamFile ) > 0 :
442474 is = & imageapi.ImageStream {}
443475 is .Annotations = map [string ]string {}
444476 if len (o .FromImageStream ) > 0 && len (o .Namespace ) > 0 {
445477 is .Annotations [annotationReleaseFromImageStream ] = fmt .Sprintf ("%s/%s" , o .Namespace , o .FromImageStream )
446478 }
447479
448- inputIS , err := o .ImageClient .ImageV1 ().ImageStreams (o .Namespace ).Get (o .FromImageStream , metav1.GetOptions {})
449- if err != nil {
450- return err
480+ var inputIS * imageapi.ImageStream
481+ if len (o .FromImageStreamFile ) > 0 {
482+ data , err := ioutil .ReadFile (o .FromImageStreamFile )
483+ if os .IsNotExist (err ) {
484+ return err
485+ }
486+ if err != nil {
487+ return fmt .Errorf ("unable to read input image stream file: %v" , err )
488+ }
489+ is := & imageapi.ImageStream {}
490+ if err := yaml .Unmarshal (data , & is ); err != nil {
491+ return fmt .Errorf ("unable to load input image stream file: %v" , err )
492+ }
493+ if is .Kind != "ImageStream" || is .APIVersion != "image.openshift.io/v1" {
494+ return fmt .Errorf ("unrecognized input image stream file, must be an ImageStream in image.openshift.io/v1" )
495+ }
496+ inputIS = is
497+
498+ } else {
499+ is , err := o .ImageClient .ImageV1 ().ImageStreams (o .Namespace ).Get (o .FromImageStream , metav1.GetOptions {})
500+ if err != nil {
501+ return err
502+ }
503+ inputIS = is
451504 }
452505
453506 if inputIS .Annotations == nil {
@@ -662,52 +715,101 @@ func resolveImageStreamTagsToReferenceMode(inputIS, is *imageapi.ImageStream, re
662715 if forceExternal && len (external ) == 0 {
663716 return fmt .Errorf ("only image streams or releases with public image repositories can be the source for releases when using the default --reference-mode" )
664717 }
665- for _ , tag := range inputIS .Status .Tags {
666- if exclude .Has (tag .Tag ) {
667- glog .V (2 ).Infof ("Excluded status tag %s" , tag .Tag )
718+
719+ externalFn := func (source , image string ) string {
720+ // filter source URLs
721+ if len (source ) > 0 && len (internal ) > 0 && strings .HasPrefix (source , internal ) {
722+ glog .V (2 ).Infof ("Can't use source %s because it points to the internal registry" , source )
723+ source = ""
724+ }
725+ // default to the external registry name
726+ if (forceExternal || len (source ) == 0 ) && len (external ) > 0 {
727+ return external + "@" + image
728+ }
729+ return source
730+ }
731+
732+ covered := sets .NewString ()
733+ for _ , ref := range inputIS .Spec .Tags {
734+ if exclude .Has (ref .Name ) {
735+ glog .V (2 ).Infof ("Excluded spec tag %s" , ref .Name )
668736 continue
669737 }
670- if len (tag .Items ) == 0 {
738+
739+ if ref .From != nil && ref .From .Kind == "DockerImage" {
740+ switch from , err := imagereference .Parse (ref .From .Name ); {
741+ case err != nil :
742+ return err
743+
744+ case len (from .ID ) > 0 :
745+ source := externalFn (ref .From .Name , from .ID )
746+ if len (source ) == 0 {
747+ glog .V (2 ).Infof ("Can't use spec tag %q because we cannot locate or calculate a source location" , ref .Name )
748+ continue
749+ }
750+
751+ ref := ref .DeepCopy ()
752+ ref .From = & corev1.ObjectReference {Kind : "DockerImage" , Name : source }
753+ is .Spec .Tags = append (is .Spec .Tags , * ref )
754+ covered .Insert (ref .Name )
755+
756+ case len (from .Tag ) > 0 :
757+ tag := findStatusTagEvents (inputIS .Status .Tags , ref .Name )
758+ if tag == nil {
759+ continue
760+ }
761+ if len (tag .Items ) == 0 {
762+ for _ , condition := range tag .Conditions {
763+ if condition .Type == imageapi .ImportSuccess && condition .Status != metav1 .StatusSuccess {
764+ return fmt .Errorf ("the tag %q in the source input stream has not been imported yet" , tag .Tag )
765+ }
766+ }
767+ continue
768+ }
769+ if ref .Generation != nil && * ref .Generation != tag .Items [0 ].Generation {
770+ return fmt .Errorf ("the tag %q in the source input stream has not been imported yet" , tag .Tag )
771+ }
772+ if len (tag .Items [0 ].Image ) == 0 {
773+ return fmt .Errorf ("the tag %q in the source input stream has no image id" , tag .Tag )
774+ }
775+
776+ source := externalFn (tag .Items [0 ].DockerImageReference , tag .Items [0 ].Image )
777+ ref := ref .DeepCopy ()
778+ ref .From = & corev1.ObjectReference {Kind : "DockerImage" , Name : source }
779+ is .Spec .Tags = append (is .Spec .Tags , * ref )
780+ covered .Insert (ref .Name )
781+ }
671782 continue
672783 }
784+ // TODO: support ImageStreamTag and ImageStreamImage
785+ }
673786
674- // attempt to identify the source image
675- source := tag .Items [0 ].DockerImageReference
676- if len (tag .Items [0 ].Image ) == 0 {
677- glog .V (2 ).Infof ("Ignored tag %q because it had no image id or reference" , tag .Tag )
787+ for _ , tag := range inputIS .Status .Tags {
788+ if covered .Has (tag .Tag ) {
678789 continue
679790 }
680- // eliminate status tag references that point to the outside
681- if len (source ) > 0 {
682- if len (internal ) > 0 && strings .HasPrefix (tag .Items [0 ].DockerImageReference , internal ) {
683- glog .V (2 ).Infof ("Can't use tag %q source %s because it points to the internal registry" , tag .Tag , source )
684- source = ""
685- }
791+ if exclude .Has (tag .Tag ) {
792+ glog .V (2 ).Infof ("Excluded status tag %s" , tag .Tag )
793+ continue
686794 }
687- ref := findSpecTag (inputIS .Spec .Tags , tag .Tag )
688- if ref == nil {
689- ref = & imageapi.TagReference {Name : tag .Tag }
690- } else {
691- // prevent unimported images from being skipped
692- if ref .Generation != nil && * ref .Generation != tag .Items [0 ].Generation {
693- return fmt .Errorf ("the tag %q in the source input stream has not been imported yet" , tag .Tag )
694- }
695- // use the tag ref as the source
696- if ref .From != nil && ref .From .Kind == "DockerImage" && ! strings .HasPrefix (ref .From .Name , internal ) {
697- if from , err := imagereference .Parse (ref .From .Name ); err == nil {
698- from .Tag = ""
699- from .ID = tag .Items [0 ].Image
700- source = from .Exact ()
701- } else {
702- glog .V (2 ).Infof ("Can't use tag %q from %s because it isn't a valid image reference" , tag .Tag , ref .From .Name )
795+
796+ // error if we haven't imported anything to this tag, or skip otherwise
797+ if len (tag .Items ) == 0 {
798+ for _ , condition := range tag .Conditions {
799+ if condition .Type == imageapi .ImportSuccess && condition .Status != metav1 .StatusSuccess {
800+ return fmt .Errorf ("the tag %q in the source input stream has not been imported yet" , tag .Tag )
703801 }
704802 }
705- ref = ref . DeepCopy ()
803+ continue
706804 }
707- // default to the external registry name
708- if (forceExternal || len (source ) == 0 ) && len (external ) > 0 {
709- source = external + "@" + tag .Items [0 ].Image
805+ // skip rather than error (user created a reference spec tag, then deleted it)
806+ if len (tag .Items [0 ].Image ) == 0 {
807+ glog .V (2 ).Infof ("the tag %q in the source input stream has no image id" , tag .Tag )
808+ continue
710809 }
810+
811+ // attempt to identify the source image
812+ source := externalFn (tag .Items [0 ].DockerImageReference , tag .Items [0 ].Image )
711813 if len (source ) == 0 {
712814 glog .V (2 ).Infof ("Can't use tag %q because we cannot locate or calculate a source location" , tag .Tag )
713815 continue
@@ -720,6 +822,7 @@ func resolveImageStreamTagsToReferenceMode(inputIS, is *imageapi.ImageStream, re
720822 sourceRef .ID = tag .Items [0 ].Image
721823 source = sourceRef .Exact ()
722824
825+ ref := & imageapi.TagReference {Name : tag .Tag }
723826 ref .From = & corev1.ObjectReference {Kind : "DockerImage" , Name : source }
724827 is .Spec .Tags = append (is .Spec .Tags , * ref )
725828 }
0 commit comments