Skip to content

Commit

Permalink
support fetching images in OCI format
Browse files Browse the repository at this point in the history
this enables fetching images and then `docker load`ing them in a build
  • Loading branch information
vito committed Aug 8, 2018
1 parent bd27f9c commit 19f7e0e
Show file tree
Hide file tree
Showing 6 changed files with 145 additions and 28 deletions.
28 changes: 22 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,33 @@ Reports the current digest that the registry has for the given tag.

### `in`: Fetch the image's rootfs and metadata.

This image is meant to be used for fetching Concourse task images and
pipeline-provided resource types.
#### Parameters

So by default, this resource will produce the following files:
* `format`: *Optional. Default `rootfs`.* The format to fetch as. (See below.)


#### Formats

##### `rootfs`

The `rootfs` format will fetch and unpack the image for use by Concourse task
and resource type images.

This the default for the sake of brevity in pipelines and task configs.

In this format, the resource will produce the following files:

* `rootfs/...`: the unpacked rootfs produced by the image.
* `metadata.json`: the runtime information to propagate to Concourse.

In a later release, it might be a good idea to optionally fetch the image in
other formats that are more useful for e.g. Docker's tooling (perhaps a file
equivalent to `docker save`).
##### `oci`

The `oci` format will fetch the image and write it to disk in OCI format. This
is analogous to running `docker save`.

In this format, the resource will produce the following files:

* `image.tar`: the OCI image tarball, suitable for passing to `docker load`.


### `out`: Push an image up to the registry under the given tags.
Expand Down
14 changes: 7 additions & 7 deletions check_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ var _ = Describe("Check", func() {

It("returns the current digest", func() {
Expect(res).To(Equal([]resource.Version{
{Digest: "sha256:64a6988c58cbdd634198f56452e8f8945e5b54a4bbca4bff7e960e1c830671ff"},
{Digest: LATEST_STATIC_DIGEST},
}))
})
})
Expand All @@ -63,13 +63,13 @@ var _ = Describe("Check", func() {
}

req.Version = &resource.Version{
Digest: "sha256:64a6988c58cbdd634198f56452e8f8945e5b54a4bbca4bff7e960e1c830671ff",
Digest: LATEST_STATIC_DIGEST,
}
})

It("returns the given digest", func() {
Expect(res).To(Equal([]resource.Version{
{Digest: "sha256:64a6988c58cbdd634198f56452e8f8945e5b54a4bbca4bff7e960e1c830671ff"},
{Digest: LATEST_STATIC_DIGEST},
}))
})
})
Expand All @@ -83,14 +83,14 @@ var _ = Describe("Check", func() {

req.Version = &resource.Version{
// this was previously pushed to the 'latest' tag
Digest: "sha256:031567a617423a84ad68b62267c30693185bd2b92c2668732efc8c70b036bd3a",
Digest: OLDER_STATIC_DIGEST,
}
})

It("returns the previous digest and the current digest", func() {
Expect(res).To(Equal([]resource.Version{
{Digest: "sha256:031567a617423a84ad68b62267c30693185bd2b92c2668732efc8c70b036bd3a"},
{Digest: "sha256:64a6988c58cbdd634198f56452e8f8945e5b54a4bbca4bff7e960e1c830671ff"},
{Digest: OLDER_STATIC_DIGEST},
{Digest: LATEST_STATIC_DIGEST},
}))
})
})
Expand All @@ -110,7 +110,7 @@ var _ = Describe("Check", func() {

It("returns only the current digest", func() {
Expect(res).To(Equal([]resource.Version{
{Digest: "sha256:64a6988c58cbdd634198f56452e8f8945e5b54a4bbca4bff7e960e1c830671ff"},
{Digest: LATEST_STATIC_DIGEST},
}))
})
})
Expand Down
44 changes: 36 additions & 8 deletions cmd/in/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,16 @@ import (
resource "github.com/concourse/registry-image-resource"
color "github.com/fatih/color"
"github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/google/go-containerregistry/pkg/v1/tarball"
"github.com/sirupsen/logrus"
)

type InRequest struct {
Source resource.Source `json:"source"`
Version resource.Version `json:"version"`
Source resource.Source `json:"source"`
Params resource.GetParams `json:"params"`
Version resource.Version `json:"version"`
}

type InResponse struct {
Expand Down Expand Up @@ -74,7 +77,37 @@ func main() {
return
}

err = unpackImage(filepath.Join(dest, "rootfs"), image, req.Source.Debug)
switch req.Params.Format() {
case "oci":
ociFormat(dest, req, image)
case "rootfs":
rootfsFormat(dest, req, image)
}

json.NewEncoder(os.Stdout).Encode(InResponse{
Version: req.Version,
Metadata: []resource.MetadataField{},
})
}

func ociFormat(dest string, req InRequest, image v1.Image) {
tag, err := name.NewTag(req.Source.Repository+":"+req.Source.Tag(), name.WeakValidation)
if err != nil {
logrus.Errorf("failed to construct tag reference: %s", err)
os.Exit(1)
return
}

err = tarball.WriteToFile(filepath.Join(dest, "image.tar"), tag, image, nil)
if err != nil {
logrus.Errorf("failed to write OCI image: %s", err)
os.Exit(1)
return
}
}

func rootfsFormat(dest string, req InRequest, image v1.Image) {
err := unpackImage(filepath.Join(dest, "rootfs"), image, req.Source.Debug)
if err != nil {
logrus.Errorf("failed to extract image: %s", err)
os.Exit(1)
Expand Down Expand Up @@ -111,9 +144,4 @@ func main() {
os.Exit(1)
return
}

json.NewEncoder(os.Stdout).Encode(InResponse{
Version: req.Version,
Metadata: []resource.MetadataField{},
})
}
44 changes: 38 additions & 6 deletions in_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ import (
"syscall"
"time"

"github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/tarball"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"

Expand All @@ -21,6 +24,7 @@ var _ = Describe("In", func() {

var req struct {
Source resource.Source
Params resource.GetParams
Version resource.Version
}

Expand Down Expand Up @@ -179,10 +183,38 @@ var _ = Describe("In", func() {
Expect(cat(rootfsPath("b"))).To(Equal("replaced\n"))
})
})
})

func cat(path string) string {
bytes, err := ioutil.ReadFile(path)
Expect(err).ToNot(HaveOccurred())
return string(bytes)
}
Describe("fetching in OCI format", func() {
var manifest *v1.Manifest

BeforeEach(func() {
req.Source.Repository = "concourse/test-image-static"
req.Params.RawFormat = "oci"

req.Version.Digest, manifest = latestManifest(req.Source.Repository)
})

It("saves the tagged image as image.tar instead of saving the rootfs", func() {
_, err := os.Stat(filepath.Join(destDir, "rootfs"))
Expect(os.IsNotExist(err)).To(BeTrue())

_, err = os.Stat(filepath.Join(destDir, "manifest.json"))
Expect(os.IsNotExist(err)).To(BeTrue())

tag, err := name.NewTag("concourse/test-image-static:latest", name.WeakValidation)
Expect(err).ToNot(HaveOccurred())

img, err := tarball.ImageFromPath(filepath.Join(destDir, "image.tar"), &tag)
Expect(err).ToNot(HaveOccurred())

fetchedManifest, err := img.Manifest()
Expect(err).ToNot(HaveOccurred())

// cannot assert against digest because the saved image's manifest isn't
// JSON-prettified, so it has a different sha256. so just assert against
// digest within manifest, which is what ends up being the 'image id'
// anyway.
Expect(fetchedManifest.Config.Digest).To(Equal(manifest.Config.Digest))
})
})
})
31 changes: 30 additions & 1 deletion suite_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ package resource_test

import (
"encoding/json"
"io/ioutil"
"os"
"testing"

"github.com/google/go-containerregistry/pkg/name"
"github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/remote"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
Expand All @@ -18,6 +20,10 @@ var bins struct {
Check string `json:"check"`
}

// see testdata/static/Dockerfile
const OLDER_STATIC_DIGEST = "sha256:031567a617423a84ad68b62267c30693185bd2b92c2668732efc8c70b036bd3a"
const LATEST_STATIC_DIGEST = "sha256:64a6988c58cbdd634198f56452e8f8945e5b54a4bbca4bff7e960e1c830671ff"

var _ = SynchronizedBeforeSuite(func() []byte {
var err error

Expand Down Expand Up @@ -53,7 +59,8 @@ var _ = SynchronizedBeforeSuite(func() []byte {
Expect(err).ToNot(HaveOccurred())
})

var _ = AfterSuite(func() {
var _ = SynchronizedAfterSuite(func() {
}, func() {
gexec.CleanupBuildArtifacts()
})

Expand All @@ -74,3 +81,25 @@ func latestDigest(ref string) string {

return digest.String()
}

func latestManifest(ref string) (string, *v1.Manifest) {
n, err := name.ParseReference(ref, name.WeakValidation)
Expect(err).ToNot(HaveOccurred())

image, err := remote.Image(n)
Expect(err).ToNot(HaveOccurred())

manifest, err := image.Manifest()
Expect(err).ToNot(HaveOccurred())

digest, err := image.Digest()
Expect(err).ToNot(HaveOccurred())

return digest.String(), manifest
}

func cat(path string) string {
bytes, err := ioutil.ReadFile(path)
Expect(err).ToNot(HaveOccurred())
return string(bytes)
}
12 changes: 12 additions & 0 deletions types.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,15 @@ type MetadataField struct {
Name string `json:"name"`
Value string `json:"value"`
}

type GetParams struct {
RawFormat string `json:"format"`
}

func (p GetParams) Format() string {
if p.RawFormat == "" {
return "rootfs"
}

return p.RawFormat
}

0 comments on commit 19f7e0e

Please sign in to comment.