concourse / registry-image-resource

a resource for images in a Docker registry
Apache License 2.0
89 stars 107 forks source link

Can't push OCI image layout #49

Open ikorolev93 opened 5 years ago

ikorolev93 commented 5 years ago

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.

cirocosta commented 5 years ago

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)

vito commented 5 years ago

@cirocosta Broken link - maybe the repo's private?

vito commented 5 years ago

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.

cirocosta commented 5 years ago

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:

// 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!

vito commented 5 years ago

@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

vito commented 5 years ago

@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:

vito commented 5 years ago

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?

vito commented 5 years ago

Ahh, looks like it's also for supporting multiple platforms. In that case we might just want to push all of them.

cirocosta commented 5 years ago

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 🤔

vito commented 5 years ago

@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.

kuznero commented 1 year ago

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

kuznero commented 1 year ago

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.

mreiche commented 10 months ago

ok, after a not so short research the following change fixed my problem:

Finally!