@@ -5,29 +5,47 @@ import (
5
5
"io"
6
6
"net"
7
7
"net/http"
8
+ "os"
8
9
"strings"
9
10
10
11
v1 "github.com/google/go-containerregistry/pkg/v1"
12
+ "github.com/google/go-containerregistry/pkg/v1/layout"
11
13
"github.com/google/go-containerregistry/pkg/v1/tarball"
14
+ "github.com/google/go-containerregistry/pkg/v1/types"
12
15
"github.com/julienschmidt/httprouter"
13
16
"github.com/sirupsen/logrus"
14
17
)
15
18
16
19
type ImageArg struct {
17
- Image v1.Image
18
- ArgName string
20
+ Index v1.ImageIndex
21
+ Image v1.Image
22
+ BuildArgName string
19
23
}
20
24
type LocalRegistry map [string ]ImageArg
21
25
22
26
func LoadRegistry (imagePaths map [string ]string ) (LocalRegistry , error ) {
23
27
images := LocalRegistry {}
24
28
for name , path := range imagePaths {
25
- image , err := tarball . ImageFromPath (path , nil )
29
+ stat , err := os . Stat (path )
26
30
if err != nil {
27
- return nil , fmt .Errorf ("image from path: %w" , err )
31
+ return nil , fmt .Errorf ("error inspecting path: %w" , err )
28
32
}
29
33
30
- images [strings .ToLower (name )] = ImageArg {Image : image , ArgName : name }
34
+ var index v1.ImageIndex
35
+ var image v1.Image
36
+ if stat .IsDir () {
37
+ index , err = layout .ImageIndexFromPath (path )
38
+ if err != nil {
39
+ return nil , fmt .Errorf ("image from path: %w" , err )
40
+ }
41
+ } else {
42
+ image , err = tarball .ImageFromPath (path , nil )
43
+ if err != nil {
44
+ return nil , fmt .Errorf ("image from tarball: %w" , err )
45
+ }
46
+ }
47
+
48
+ images [strings .ToLower (name )] = ImageArg {Index : index , Image : image , BuildArgName : name }
31
49
}
32
50
33
51
return images , nil
@@ -63,7 +81,7 @@ func ServeRegistry(reg LocalRegistry) (string, error) {
63
81
func (registry LocalRegistry ) BuildArgs (port string ) []string {
64
82
var buildArgs []string
65
83
for name , image := range registry {
66
- buildArgs = append (buildArgs , fmt .Sprintf ("%s=localhost:%s/%s" , image .ArgName , port , name ))
84
+ buildArgs = append (buildArgs , fmt .Sprintf ("%s=localhost:%s/%s" , image .BuildArgName , port , name ))
67
85
}
68
86
69
87
return buildArgs
@@ -82,30 +100,94 @@ func (registry LocalRegistry) GetManifest(w http.ResponseWriter, r *http.Request
82
100
w .WriteHeader (http .StatusNotFound )
83
101
return
84
102
}
85
- image := img .Image
86
103
87
- mt , err := image .MediaType ()
88
- if err != nil {
89
- logrus .Errorf ("failed to get media type: %s" , err )
90
- w .WriteHeader (http .StatusInternalServerError )
91
- return
92
- }
104
+ var mediaType types.MediaType
105
+ var blob []byte
106
+ var digest v1.Hash
107
+ var err error
108
+
109
+ if img .Image != nil {
110
+ mediaType , err = img .Image .MediaType ()
111
+ if err != nil {
112
+ logrus .Errorf ("failed to get media type: %s" , err )
113
+ w .WriteHeader (http .StatusInternalServerError )
114
+ return
115
+ }
116
+
117
+ blob , err = img .Image .RawManifest ()
118
+ if err != nil {
119
+ logrus .Errorf ("failed to get manifest: %s" , err )
120
+ w .WriteHeader (http .StatusInternalServerError )
121
+ return
122
+ }
123
+
124
+ digest , err = img .Image .Digest ()
125
+ if err != nil {
126
+ logrus .Errorf ("failed to get digest: %s" , err )
127
+ w .WriteHeader (http .StatusInternalServerError )
128
+ return
129
+ }
93
130
94
- blob , err := image .RawManifest ()
95
- if err != nil {
96
- logrus .Errorf ("failed to get manifest: %s" , err )
97
- w .WriteHeader (http .StatusInternalServerError )
98
- return
99
131
}
100
132
101
- digest , err := image .Digest ()
102
- if err != nil {
103
- logrus .Errorf ("failed to get digest: %s" , err )
104
- w .WriteHeader (http .StatusInternalServerError )
105
- return
133
+ if img .Index != nil {
134
+ digest , err = img .Index .Digest ()
135
+ if err != nil {
136
+ logrus .Errorf ("error getting ImageIndex's digest: %s" , err )
137
+ w .WriteHeader (http .StatusInternalServerError )
138
+ return
139
+ }
140
+
141
+ // Check if we were given a Hash. An err means we were NOT given a Hash
142
+ // and got a string like "latest" or a semver. In that case we return
143
+ // the ImageIndex itself
144
+ refHash , err := v1 .NewHash (ref )
145
+ if digest .String () == ref || err != nil {
146
+ mediaType , err = img .Index .MediaType ()
147
+ if err != nil {
148
+ logrus .Errorf ("error getting MediaType: %s" , err )
149
+ w .WriteHeader (http .StatusInternalServerError )
150
+ return
151
+ }
152
+
153
+ blob , err = img .Index .RawManifest ()
154
+ if err != nil {
155
+ logrus .Errorf ("error getting RawManifest: %s" , err )
156
+ w .WriteHeader (http .StatusInternalServerError )
157
+ return
158
+ }
159
+ } else {
160
+ // TODO: technically there could be nested ImageIndex's, but they're
161
+ // not common so not bothering to handle those right now
162
+
163
+ //try and find ref inside ImageIndex
164
+ digest = refHash
165
+
166
+ image , err := img .Index .Image (digest )
167
+ if err != nil {
168
+ logrus .Errorf ("error getting Image from ImageIndex: %s" , err )
169
+ w .WriteHeader (http .StatusInternalServerError )
170
+ return
171
+ }
172
+
173
+ mediaType , err = image .MediaType ()
174
+ if err != nil {
175
+ logrus .Errorf ("error getting MediaType from Image: %s" , err )
176
+ w .WriteHeader (http .StatusInternalServerError )
177
+ return
178
+ }
179
+
180
+ blob , err = image .RawManifest ()
181
+ if err != nil {
182
+ logrus .Errorf ("error getting RawManifest from Image: %s" , err )
183
+ w .WriteHeader (http .StatusInternalServerError )
184
+ return
185
+ }
186
+ }
187
+
106
188
}
107
189
108
- w .Header ().Set ("Content-Type" , string (mt ))
190
+ w .Header ().Set ("Content-Type" , string (mediaType ))
109
191
w .Header ().Set ("Content-Length" , fmt .Sprintf ("%d" , len (blob )))
110
192
w .Header ().Set ("Docker-Content-Digest" , digest .String ())
111
193
@@ -133,7 +215,6 @@ func (registry LocalRegistry) GetBlob(w http.ResponseWriter, r *http.Request, p
133
215
w .WriteHeader (http .StatusNotFound )
134
216
return
135
217
}
136
- image := img .Image
137
218
138
219
hash , err := v1 .NewHash (dig )
139
220
if err != nil {
@@ -142,48 +223,89 @@ func (registry LocalRegistry) GetBlob(w http.ResponseWriter, r *http.Request, p
142
223
return
143
224
}
144
225
145
- cfgHash , err := image .ConfigName ()
146
- if err != nil {
147
- logrus .Errorf ("failed to get config hash: %s" , err )
148
- w .WriteHeader (http .StatusInternalServerError )
149
- return
150
- }
226
+ var layer v1.Layer
151
227
152
- if hash == cfgHash {
153
- manifest , err := image .Manifest ()
154
- if err != nil {
155
- logrus .Errorf ("get image manifest: %s" , err )
156
- return
157
- }
228
+ if img .Image != nil {
229
+ image := img .Image
158
230
159
- cfgBlob , err := image .RawConfigFile ()
231
+ cfgHash , err := image .ConfigName ()
160
232
if err != nil {
161
- logrus .Errorf ("failed to get config file : %s" , err )
233
+ logrus .Errorf ("failed to get config hash : %s" , err )
162
234
w .WriteHeader (http .StatusInternalServerError )
163
235
return
164
236
}
165
237
166
- w .Header ().Set ("Content-Type" , string (manifest .Config .MediaType ))
167
- w .Header ().Set ("Content-Length" , fmt .Sprintf ("%d" , len (cfgBlob )))
238
+ if hash == cfgHash {
239
+ manifest , err := image .Manifest ()
240
+ if err != nil {
241
+ logrus .Errorf ("get image manifest: %s" , err )
242
+ w .WriteHeader (http .StatusInternalServerError )
243
+ return
244
+ }
245
+
246
+ cfgBlob , err := image .RawConfigFile ()
247
+ if err != nil {
248
+ logrus .Errorf ("failed to get config file: %s" , err )
249
+ w .WriteHeader (http .StatusInternalServerError )
250
+ return
251
+ }
252
+
253
+ w .Header ().Set ("Content-Type" , string (manifest .Config .MediaType ))
254
+ w .Header ().Set ("Content-Length" , fmt .Sprintf ("%d" , len (cfgBlob )))
255
+
256
+ if r .Method == "HEAD" {
257
+ return
258
+ }
259
+
260
+ _ , err = w .Write (cfgBlob )
261
+ if err != nil {
262
+ logrus .Errorf ("write config blob: %s" , err )
263
+ return
264
+ }
265
+
266
+ return
267
+ }
168
268
169
- if r .Method == "HEAD" {
269
+ layer , err = image .LayerByDigest (hash )
270
+ if err != nil {
271
+ logrus .Errorf ("failed to get layer: %s" , err )
272
+ w .WriteHeader (http .StatusInternalServerError )
170
273
return
171
274
}
275
+ }
172
276
173
- _ , err = w .Write (cfgBlob )
277
+ if img .Index != nil {
278
+ index , err := img .Index .IndexManifest ()
174
279
if err != nil {
175
- logrus .Errorf ("write config blob: %s" , err )
280
+ logrus .Errorf ("error getting Manifest from ImageIndex: %s" , err )
281
+ w .WriteHeader (http .StatusInternalServerError )
176
282
return
177
283
}
178
284
179
- return
180
- }
285
+ // Search all images in the ImageIndex for the requested layer
286
+ for _ , desc := range index .Manifests {
287
+ if desc .MediaType .IsImage () {
288
+ img , err := img .Index .Image (desc .Digest )
289
+ if err != nil {
290
+ logrus .Errorf ("error getting image from ImageIndex: %s" , err )
291
+ w .WriteHeader (http .StatusInternalServerError )
292
+ return
293
+ }
294
+
295
+ // ignore errors related to not finding the layer and just keep searching
296
+ l , err := img .LayerByDigest (hash )
297
+ if err == nil {
298
+ layer = l
299
+ break
300
+ }
301
+ }
302
+ }
181
303
182
- layer , err := image . LayerByDigest ( hash )
183
- if err != nil {
184
- logrus . Errorf ( "failed to get layer: %s" , err )
185
- w . WriteHeader ( http . StatusInternalServerError )
186
- return
304
+ if layer == nil {
305
+ logrus . Errorf ( "layer not found in ImageIndex: %s" , err )
306
+ w . WriteHeader ( http . StatusNotFound )
307
+ return
308
+ }
187
309
}
188
310
189
311
size , err := layer .Size ()
0 commit comments