Skip to content

Commit 81fa1ea

Browse files
authored
Merge pull request #130 from concourse/load-oci-images
load OCI images with IMAGE_ARG_*
2 parents d3261a5 + ca810d6 commit 81fa1ea

File tree

3 files changed

+254
-61
lines changed

3 files changed

+254
-61
lines changed

README.md

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,12 @@ _(As a convention in the list below, all task parameters are specified with a
6262
to build.
6363

6464
* `$BUILDKIT_SSH` your ssh key location that is mounted in your `Dockerfile`. This is
65-
generally used for pulling dependencies from private repositories.
65+
generally used for pulling dependencies from private repositories.
6666

6767
For Example. In your `Dockerfile`, you can mount a key as
6868
```
6969
RUN --mount=type=ssh,id=github_ssh_key pip install -U -r ./hats/requirements-test.txt
70-
```
70+
```
7171
7272
Then in your Concourse YAML configuration:
7373
```
@@ -122,10 +122,11 @@ _(As a convention in the list below, all task parameters are specified with a
122122
`(( mysecret ))` expands to in `/run/secrets/mysecret`.
123123

124124
* `$IMAGE_ARG_*`: params prefixed with `IMAGE_ARG_*` point to image tarballs
125-
(i.e. `docker save` format) to preload so that they do not have to be fetched
126-
during the build. An image reference will be provided as the given build arg
127-
name. For example, `IMAGE_ARG_base_image=ubuntu/image.tar` will set
128-
`base_image` to a local image reference for using `ubuntu/image.tar`.
125+
(i.e. `docker save` format) or path to images in OCI layout format, to preload
126+
so that they do not have to be fetched during the build. An image reference
127+
will be provided as the given build arg name. For example,
128+
`IMAGE_ARG_base_image=ubuntu/image.tar` will set `base_image` to a local image
129+
reference for using `ubuntu/image.tar`.
129130

130131
This must be accepted as an argument for use; for example:
131132

@@ -134,10 +135,10 @@ _(As a convention in the list below, all task parameters are specified with a
134135
FROM ${base_image}
135136
```
136137

137-
* `$IMAGE_PLATFORM`: Specify the target platform to build the image for. For
138-
example `IMAGE_PLATFORM=linux/arm64` will build the image for the Linux OS
139-
and `arm64` architecture. By default, images will be built for the current
140-
worker's platform that the task is running on.
138+
* `$IMAGE_PLATFORM`: Specify the target platform(s) to build the image for. For
139+
example `IMAGE_PLATFORM=linux/arm64,linux/amd64` will build the image for the
140+
Linux OS and architectures `arm64` and `amd64`. By default, images will be
141+
built for the current worker's platform that the task is running on.
141142

142143
* `$LABEL_*`: params prefixed with `LABEL_` will be set as image labels.
143144
For example `LABEL_foo=bar`, will set the `foo` label to `bar`.

registry.go

Lines changed: 173 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,47 @@ import (
55
"io"
66
"net"
77
"net/http"
8+
"os"
89
"strings"
910

1011
v1 "github.com/google/go-containerregistry/pkg/v1"
12+
"github.com/google/go-containerregistry/pkg/v1/layout"
1113
"github.com/google/go-containerregistry/pkg/v1/tarball"
14+
"github.com/google/go-containerregistry/pkg/v1/types"
1215
"github.com/julienschmidt/httprouter"
1316
"github.com/sirupsen/logrus"
1417
)
1518

1619
type ImageArg struct {
17-
Image v1.Image
18-
ArgName string
20+
Index v1.ImageIndex
21+
Image v1.Image
22+
BuildArgName string
1923
}
2024
type LocalRegistry map[string]ImageArg
2125

2226
func LoadRegistry(imagePaths map[string]string) (LocalRegistry, error) {
2327
images := LocalRegistry{}
2428
for name, path := range imagePaths {
25-
image, err := tarball.ImageFromPath(path, nil)
29+
stat, err := os.Stat(path)
2630
if err != nil {
27-
return nil, fmt.Errorf("image from path: %w", err)
31+
return nil, fmt.Errorf("error inspecting path: %w", err)
2832
}
2933

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}
3149
}
3250

3351
return images, nil
@@ -63,7 +81,7 @@ func ServeRegistry(reg LocalRegistry) (string, error) {
6381
func (registry LocalRegistry) BuildArgs(port string) []string {
6482
var buildArgs []string
6583
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))
6785
}
6886

6987
return buildArgs
@@ -82,30 +100,94 @@ func (registry LocalRegistry) GetManifest(w http.ResponseWriter, r *http.Request
82100
w.WriteHeader(http.StatusNotFound)
83101
return
84102
}
85-
image := img.Image
86103

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+
}
93130

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
99131
}
100132

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+
106188
}
107189

108-
w.Header().Set("Content-Type", string(mt))
190+
w.Header().Set("Content-Type", string(mediaType))
109191
w.Header().Set("Content-Length", fmt.Sprintf("%d", len(blob)))
110192
w.Header().Set("Docker-Content-Digest", digest.String())
111193

@@ -133,7 +215,6 @@ func (registry LocalRegistry) GetBlob(w http.ResponseWriter, r *http.Request, p
133215
w.WriteHeader(http.StatusNotFound)
134216
return
135217
}
136-
image := img.Image
137218

138219
hash, err := v1.NewHash(dig)
139220
if err != nil {
@@ -142,48 +223,89 @@ func (registry LocalRegistry) GetBlob(w http.ResponseWriter, r *http.Request, p
142223
return
143224
}
144225

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
151227

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
158230

159-
cfgBlob, err := image.RawConfigFile()
231+
cfgHash, err := image.ConfigName()
160232
if err != nil {
161-
logrus.Errorf("failed to get config file: %s", err)
233+
logrus.Errorf("failed to get config hash: %s", err)
162234
w.WriteHeader(http.StatusInternalServerError)
163235
return
164236
}
165237

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+
}
168268

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)
170273
return
171274
}
275+
}
172276

173-
_, err = w.Write(cfgBlob)
277+
if img.Index != nil {
278+
index, err := img.Index.IndexManifest()
174279
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)
176282
return
177283
}
178284

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+
}
181303

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+
}
187309
}
188310

189311
size, err := layer.Size()

0 commit comments

Comments
 (0)