Can't push OCI image layout
Reproduction:
#!/bin/bash
set -x
mkdir -p /tmp/repro
cd /tmp/repro
rm -f image.tar
buildah pull busybox
buildah push busybox oci-archive:image.tar
echo '{"params":{"image":"image/image.tar"},"source":{"repository":"example","tag":"latest"}}' \
| podman run -it --rm -v $PWD/image.tar:/build/image/image.tar concourse/registry-image-resource /opt/resource/out /build
Output:
+ mkdir -p /tmp/repro
+ cd /tmp/repro
+ buildah pull busybox
db8ee88ad75f6bdc74663f4992a185e2722fa29573abcc1a19186cc5ec09dceb
+ rm -f image.tar
+ buildah push busybox oci-archive:image.tar
Getting image source signatures
Copying blob 0d315111b484 done
Copying config d068e40b11 done
Writing manifest to image destination
Storing signatures
Successfully pushed image.tar:@sha256:42212762065786db5a6fee614c801044c9328da5389b30c314c001d1e186a2ce
+ echo '{"params":{"image":"image/image.tar"},"source":{"repository":"example","tag":"latest"}}'
+ podman run -it --rm -v /tmp/repro/image.tar:/build/image/image.tar concourse/registry-image-resource /opt/resource/out /build
{"params":{"image":"image/image.tar"},"source":{"repository":"example","tag":"latest"}}
ERRO[0000] could not load image from path 'image/image.tar': file manifest.json not found in tar
Indeed, there is no manifest.json in the OCI Image Layout Specification.
As a workaround, I changed my task to do buildah push whatever docker-archive:image.tar, but it would be nice if registry-image-resource supported OCI images.
Thanks for reporting, @deadNightTiger !
I noticed that this is also true (for the same reason) for anyone leveraging buildkit directly with --output type=oci,dest=image.tar.
(for anyone wanting to try that out, check https://github.com/cirocosta/buildkit-task)
@cirocosta Broken link - maybe the repo's private?
Also re: the original issue, this resource is pretty thin and relies on google/go-containerregistry for all the heavy lifting. It looks like it has OCI support in some form or another (https://github.com/google/go-containerregistry/pull/261, https://github.com/google/go-containerregistry/pull/406), but I haven't had time to look into it too deeply.
whoops, it was indeed private - just made it public :grin:
I was looking at the code, and yeah, it makes sense - the tarball package
doesn't consider that within a tarball, an image layout might exist.
With recent versions though, we can leverage that (with few modifications).
For instance, something like the following:
var img v1.Image
switch {
case req.Params.Layout != "":
imageIndex, err := layout.ImageIndexFromPath(req.Params.Layout)
if err != nil {
logrus.Errorf("could not find layout valid oci layout in %s: %s", req.Params.Layout, err)
os.Exit(1)
return
}
indexManifest, err := imageIndex.IndexManifest()
if err != nil {
logrus.Errorf("failed to retrieve index's manifest obj: %s", err)
os.Exit(1)
return
}
firstDigest := indexManifest.Manifests[0].Digest
img, err = imageIndex.Image(firstDigest)
if err != nil {
logrus.Errorf("failed to retrieve image from first digest %s: %s", firstDigest, err)
os.Exit(1)
return
}
case req.Params.Image != "":
imagePath := filepath.Join(src, req.Params.Image)
img, err = tarball.ImageFromPath(imagePath, nil)
if err != nil {
logrus.Errorf("could not load image from path '%s': %s", req.Params.Image, err)
os.Exit(1)
return
}
default:
logrus.Errorf("neither image nor layout specified")
os.Exit(1)
return
}
(ps.: the above depends on bumping our deps - https://github.com/concourse/registry-image-resource/pull/52)
Note that differently from the tarball, one can have multiple images (as the
idea of the image layout is that in the index you can specify as many images
as you'd like), thus, we end up having to pick one.
Also, layout in go-containerregistry assumes that you have a directory, not
a tarball (thus, we'd end up having to perform the unarchive'ing ourselves in
here if we'd like to accept a tarball there)
It seems to me that we could either:
- go with the way I did in the example above (just picking the first), or
- pushing all of the images pointed to by the manifest (at least for me, makes
more sense), e.g, leveraging
WriteIndex
// WriteIndex pushes the provided ImageIndex to the specified image reference.
// WriteIndex will attempt to push all of the referenced manifests before
// attempting to push the ImageIndex, to retain referential integrity.
func WriteIndex(ref name.Reference, ii v1.ImageIndex, options ...Option) error {
Wdyt?
thx!
@cirocosta option 2 sounds more correct; we should probably just mimic the behaviour of already-established tools for image pushing, and I'm assuming that's what they do
@cirocosta I suppose an alternative would be to push whichever manifest matches the name (+ tag) configured in the resource? I'm not really familiar enough with the use case of having multiple images in an OCI layout to judge whether that makes sense though. :thinking:
Eh, actually that's not as easy as I thought. I was thinking we could look at the manifest annotation org.opencontainers.image.ref.name but that only contains the tag value, and doesn't include the repository.
I suppose that may explain the use case for multiple images in one layout: each are different tags associated to the same repository?
Ahh, looks like it's also for supporting multiple platforms. In that case we might just want to push all of them.
yeahh, in that case, they all have the same name + tag 😬
I was even wondering about the name oci that we give to the current format. Should we rename it to docker, instead? As layout is the actual OCI 🤔
@cirocosta Hmm would it be accurate to say the current oci format (supported by in), while having Docker-specific manifest.json in it, is still valid OCI at least? i.e. it's a superset?
We could and probably should rename it, but it'd be backwards-incompatible, so I'm wondering if we should just leave it as-is.
On the other hand, we haven't gone official with this resource yet, so now would be the time to change it. We could add docker and oci, and have oci be "proper" OCI.
I am using concourse/concourse:7.9.1 and my pipeline looks like this trying to build and push multi-arch container images:
resources:
- name: repo
type: git
source:
uri: ....git
branch: ((branch))
private_key: ((private-key))
paths:
- src/golang/**
- name: golang-image
type: registry-image
icon: docker
source:
repository: ((registry))/((golang-builder-image-name))
tag: ((golang-builder-image-tag))
insecure: ((registry-insecure))
- name: alpine-image
type: registry-image
icon: docker
source:
repository: ((registry))/((golang-runner-image-name))
tag: ((golang-runner-image-tag))
insecure: ((registry-insecure))
- name: core-service-image
type: registry-image
icon: docker
source:
repository: ((registry))/core-service
insecure: ((registry-insecure))
jobs:
- name: build-container-image
plan:
- in_parallel:
- get: repo
trigger: true
- get: golang-image
params:
format: oci
- get: alpine-image
params:
format: oci
- task: detect-version
config:
platform: linux
image_resource:
type: registry-image
source:
repository: ((registry))/busybox
insecure: ((registry-insecure))
inputs:
- name: repo
outputs:
- name: version
run:
path: sh
args:
- -cx
- |
cat repo/src/golang/cmd/core-service/VERSION
cp -v repo/src/golang/cmd/core-service/VERSION version/
- load_var: version
file: version/VERSION
- task: build-image
privileged: true # oci-build-task must run in a privileged container
config:
platform: linux
image_resource:
type: registry-image
source:
repository: ((registry))/concourse/oci-build-task
insecure: ((registry-insecure))
inputs:
- name: repo
- name: golang-image
- name: alpine-image
outputs:
- name: image
params:
# ref: https://github.com/concourse/oci-build-task
CONTEXT: repo/src/golang
DOCKERFILE: repo/src/golang/cmd/core-service/Dockerfile
IMAGE_PLATFORM: ((platform))
IMAGE_ARG_BUILDER_IMAGE: golang-image/image.tar
IMAGE_ARG_RUNNER_IMAGE: alpine-image/image.tar
OUTPUT_OCI: true
BUILD_ARG_VERSION: ((.:version))
run:
path: build
- put: core-service-image
params:
image: image/image.tar
version: ((.:version))
oci-build-task seem to produce OCI image, but registry-image fails to push it to my registry with the following error:
ERRO[0000] could not load image from path 'image/image.tar': loading /tmp/build/put/image/image.tar as tarball: file manifest.json not found in tar
ok, after a not so short research the following change fixed my problem:
- put: core-service-image
params:
image: image/image
version: ((.:version))
get_params:
format: oci
Now all layers of OCI image can be successfully pushed to registry.
ok, after a not so short research the following change fixed my problem:
Finally!