@@ -2,9 +2,14 @@ package manifest
2
2
3
3
import (
4
4
"context"
5
+ "encoding/json"
5
6
"fmt"
7
+ "regexp"
8
+ "runtime"
6
9
"sync"
7
10
11
+ "github.com/spf13/pflag"
12
+
8
13
"github.com/docker/distribution"
9
14
"github.com/docker/distribution/manifest/manifestlist"
10
15
"github.com/docker/distribution/manifest/schema1"
@@ -17,10 +22,178 @@ import (
17
22
"github.com/golang/glog"
18
23
digest "github.com/opencontainers/go-digest"
19
24
25
+ "github.com/openshift/origin/pkg/image/apis/image/docker10"
20
26
imagereference "github.com/openshift/origin/pkg/image/apis/image/reference"
27
+ "github.com/openshift/origin/pkg/image/dockerlayer/add"
21
28
)
22
29
23
- func ProcessManifestList (ctx context.Context , srcDigest digest.Digest , srcManifest distribution.Manifest , manifests distribution.ManifestService , ref imagereference.DockerImageReference , filterFn func (* manifestlist.ManifestDescriptor , bool ) bool ) ([]distribution.Manifest , distribution.Manifest , digest.Digest , error ) {
30
+ // FilterOptions assist in filtering out unneeded manifests from ManifestList objects.
31
+ type FilterOptions struct {
32
+ FilterByOS string
33
+ DefaultOSFilter bool
34
+ OSFilter * regexp.Regexp
35
+ }
36
+
37
+ // Bind adds the options to the flag set.
38
+ func (o * FilterOptions ) Bind (flags * pflag.FlagSet ) {
39
+ flags .StringVar (& o .FilterByOS , "filter-by-os" , o .FilterByOS , "A regular expression to control which images are mirrored. Images will be passed as '<platform>/<architecture>[/<variant>]'." )
40
+ }
41
+
42
+ // Complete checks whether the flags are ready for use.
43
+ func (o * FilterOptions ) Complete (flags * pflag.FlagSet ) error {
44
+ pattern := o .FilterByOS
45
+ if len (pattern ) == 0 && ! flags .Changed ("filter-by-os" ) {
46
+ o .DefaultOSFilter = true
47
+ pattern = regexp .QuoteMeta (fmt .Sprintf ("%s/%s" , runtime .GOOS , runtime .GOARCH ))
48
+ }
49
+ if len (pattern ) > 0 {
50
+ re , err := regexp .Compile (pattern )
51
+ if err != nil {
52
+ return fmt .Errorf ("--filter-by-os was not a valid regular expression: %v" , err )
53
+ }
54
+ o .OSFilter = re
55
+ }
56
+ return nil
57
+ }
58
+
59
+ // Include returns true if the provided manifest should be included, or the first image if the user didn't alter the
60
+ // default selection and there is only one image.
61
+ func (o * FilterOptions ) Include (d * manifestlist.ManifestDescriptor , hasMultiple bool ) bool {
62
+ if o .OSFilter == nil {
63
+ return true
64
+ }
65
+ if o .DefaultOSFilter && ! hasMultiple {
66
+ return true
67
+ }
68
+ if len (d .Platform .Variant ) > 0 {
69
+ return o .OSFilter .MatchString (fmt .Sprintf ("%s/%s/%s" , d .Platform .OS , d .Platform .Architecture , d .Platform .Variant ))
70
+ }
71
+ return o .OSFilter .MatchString (fmt .Sprintf ("%s/%s" , d .Platform .OS , d .Platform .Architecture ))
72
+ }
73
+
74
+ // IncludeAll returns true if the provided manifest matches the filter, or all if there was no filter.
75
+ func (o * FilterOptions ) IncludeAll (d * manifestlist.ManifestDescriptor , hasMultiple bool ) bool {
76
+ if o .OSFilter == nil {
77
+ return true
78
+ }
79
+ if len (d .Platform .Variant ) > 0 {
80
+ return o .OSFilter .MatchString (fmt .Sprintf ("%s/%s/%s" , d .Platform .OS , d .Platform .Architecture , d .Platform .Variant ))
81
+ }
82
+ return o .OSFilter .MatchString (fmt .Sprintf ("%s/%s" , d .Platform .OS , d .Platform .Architecture ))
83
+ }
84
+
85
+ type FilterFunc func (* manifestlist.ManifestDescriptor , bool ) bool
86
+
87
+ // PreferManifestList specifically requests a manifest list first
88
+ var PreferManifestList = distribution .WithManifestMediaTypes ([]string {
89
+ manifestlist .MediaTypeManifestList ,
90
+ schema2 .MediaTypeManifest ,
91
+ })
92
+
93
+ // FirstManifest returns the first manifest at the request location that matches the filter function.
94
+ func FirstManifest (ctx context.Context , from imagereference.DockerImageReference , repo distribution.Repository , filterFn FilterFunc ) (distribution.Manifest , digest.Digest , string , error ) {
95
+ var srcDigest digest.Digest
96
+ if len (from .Tag ) > 0 {
97
+ desc , err := repo .Tags (ctx ).Get (ctx , from .Tag )
98
+ if err != nil {
99
+ return nil , "" , "" , err
100
+ }
101
+ srcDigest = desc .Digest
102
+ } else {
103
+ srcDigest = digest .Digest (from .ID )
104
+ }
105
+ manifests , err := repo .Manifests (ctx )
106
+ if err != nil {
107
+ return nil , "" , "" , err
108
+ }
109
+ srcManifest , err := manifests .Get (ctx , srcDigest , PreferManifestList )
110
+ if err != nil {
111
+ return nil , "" , "" , err
112
+ }
113
+
114
+ originalSrcDigest := srcDigest
115
+ srcManifests , srcManifest , srcDigest , err := ProcessManifestList (ctx , srcDigest , srcManifest , manifests , from , filterFn )
116
+ if err != nil {
117
+ return nil , "" , "" , err
118
+ }
119
+ if len (srcManifests ) == 0 {
120
+ return nil , "" , "" , fmt .Errorf ("filtered all images from %s" , from )
121
+ }
122
+
123
+ var location string
124
+ if srcDigest == originalSrcDigest {
125
+ location = fmt .Sprintf ("manifest %s" , srcDigest )
126
+ } else {
127
+ location = fmt .Sprintf ("manifest %s in manifest list %s" , srcDigest , originalSrcDigest )
128
+ }
129
+ return srcManifest , srcDigest , location , nil
130
+ }
131
+
132
+ // ManifestToImageConfig takes an image manifest and converts it into a structured object.
133
+ func ManifestToImageConfig (ctx context.Context , srcManifest distribution.Manifest , blobs distribution.BlobService , location string ) (* docker10.DockerImageConfig , []distribution.Descriptor , error ) {
134
+ switch t := srcManifest .(type ) {
135
+ case * schema2.DeserializedManifest :
136
+ if t .Config .MediaType != schema2 .MediaTypeImageConfig {
137
+ return nil , nil , fmt .Errorf ("%s does not have the expected image configuration media type: %s" , location , t .Config .MediaType )
138
+ }
139
+ configJSON , err := blobs .Get (ctx , t .Config .Digest )
140
+ if err != nil {
141
+ return nil , nil , fmt .Errorf ("cannot retrieve image configuration for %s: %v" , location , err )
142
+ }
143
+ glog .V (4 ).Infof ("Raw image config json:\n %s" , string (configJSON ))
144
+ config := & docker10.DockerImageConfig {}
145
+ if err := json .Unmarshal (configJSON , & config ); err != nil {
146
+ return nil , nil , fmt .Errorf ("unable to parse image configuration: %v" , err )
147
+ }
148
+
149
+ base := config
150
+ layers := t .Layers
151
+ base .Size = 0
152
+ for _ , layer := range t .Layers {
153
+ base .Size += layer .Size
154
+ }
155
+
156
+ return base , layers , nil
157
+
158
+ case * schema1.SignedManifest :
159
+ if glog .V (4 ) {
160
+ _ , configJSON , _ := srcManifest .Payload ()
161
+ glog .Infof ("Raw image config json:\n %s" , string (configJSON ))
162
+ }
163
+ if len (t .History ) == 0 {
164
+ return nil , nil , fmt .Errorf ("input image is in an unknown format: no v1Compatibility history" )
165
+ }
166
+ config := & docker10.DockerV1CompatibilityImage {}
167
+ if err := json .Unmarshal ([]byte (t .History [0 ].V1Compatibility ), & config ); err != nil {
168
+ return nil , nil , err
169
+ }
170
+
171
+ base := & docker10.DockerImageConfig {}
172
+ if err := docker10 .Convert_DockerV1CompatibilityImage_to_DockerImageConfig (config , base ); err != nil {
173
+ return nil , nil , err
174
+ }
175
+
176
+ // schema1 layers are in reverse order
177
+ layers := make ([]distribution.Descriptor , 0 , len (t .FSLayers ))
178
+ for i := len (t .FSLayers ) - 1 ; i >= 0 ; i -- {
179
+ layer := distribution.Descriptor {
180
+ MediaType : schema2 .MediaTypeLayer ,
181
+ Digest : t .FSLayers [i ].BlobSum ,
182
+ // size must be reconstructed from the blobs
183
+ }
184
+ // we must reconstruct the tar sum from the blobs
185
+ add .AddLayerToConfig (base , layer , "" )
186
+ layers = append (layers , layer )
187
+ }
188
+
189
+ return base , layers , nil
190
+
191
+ default :
192
+ return nil , nil , fmt .Errorf ("unknown image manifest of type %T from %s" , srcManifest , location )
193
+ }
194
+ }
195
+
196
+ func ProcessManifestList (ctx context.Context , srcDigest digest.Digest , srcManifest distribution.Manifest , manifests distribution.ManifestService , ref imagereference.DockerImageReference , filterFn FilterFunc ) ([]distribution.Manifest , distribution.Manifest , digest.Digest , error ) {
24
197
var srcManifests []distribution.Manifest
25
198
switch t := srcManifest .(type ) {
26
199
case * manifestlist.DeserializedManifestList :
0 commit comments