Skip to content

Also support manifest lists (multi-arch docker images, docker apps) #196

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,12 @@ $ reg manifest r.j3ss.co/htop
}
```

For multi-architecture docker images, `--index` allow to get the Manifest index (a.k.a Manifest List) with
all supported platforms/architecture listed.

OCI compatibility can be checked using `--oci` option, which will force use of [OCI media types](https://github.com/opencontainers/image-spec/blob/master/media-types.md).


### Get the Digest
```console
$ reg digest r.j3ss.co/htop
Expand Down
12 changes: 9 additions & 3 deletions digest.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,15 @@ func (cmd *digestCommand) ShortHelp() string { return digestHelp }
func (cmd *digestCommand) LongHelp() string { return digestHelp }
func (cmd *digestCommand) Hidden() bool { return false }

func (cmd *digestCommand) Register(fs *flag.FlagSet) {}
func (cmd *digestCommand) Register(fs *flag.FlagSet) {
fs.BoolVar(&cmd.index, "index", false, "get manifest index (multi-architecture images, docker apps)")
fs.BoolVar(&cmd.oci, "oci", false, "use OCI media type only")
}

type digestCommand struct{}
type digestCommand struct {
index bool
oci bool
}

func (cmd *digestCommand) Run(ctx context.Context, args []string) error {
if len(args) < 1 {
Expand All @@ -37,7 +43,7 @@ func (cmd *digestCommand) Run(ctx context.Context, args []string) error {
}

// Get the digest.
digest, err := r.Digest(ctx, image)
digest, err := r.Digest(ctx, image, mediatypes(cmd.index, cmd.oci)...)
if err != nil {
return err
}
Expand Down
29 changes: 26 additions & 3 deletions manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import (
"encoding/json"
"flag"
"fmt"

"github.com/docker/distribution/manifest/manifestlist"
"github.com/docker/distribution/manifest/schema2"
"github.com/genuinetools/reg/registry"
v1 "github.com/opencontainers/image-spec/specs-go/v1"
)

const manifestHelp = `Get the json manifest for a repository.`
Expand All @@ -19,10 +21,14 @@ func (cmd *manifestCommand) Hidden() bool { return false }

func (cmd *manifestCommand) Register(fs *flag.FlagSet) {
fs.BoolVar(&cmd.v1, "v1", false, "force the version of the manifest retrieved to v1")
fs.BoolVar(&cmd.index, "index", false, "get manifest index (multi-architecture images, docker apps)")
fs.BoolVar(&cmd.oci, "oci", false, "use OCI media type only")
}

type manifestCommand struct {
v1 bool
v1 bool
index bool
oci bool
}

func (cmd *manifestCommand) Run(ctx context.Context, args []string) error {
Expand Down Expand Up @@ -50,7 +56,7 @@ func (cmd *manifestCommand) Run(ctx context.Context, args []string) error {
}
} else {
// Get the v2 manifest.
manifest, err = r.Manifest(ctx, image.Path, image.Reference())
manifest, err = r.Manifest(ctx, image.Path, image.Reference(), mediatypes(cmd.index, cmd.oci)...)
if err != nil {
return err
}
Expand All @@ -64,3 +70,20 @@ func (cmd *manifestCommand) Run(ctx context.Context, args []string) error {
fmt.Println(string(b))
return nil
}

func mediatypes(index, oci bool) []string {
mediatypes := []string{}
if oci {
mediatypes = append(mediatypes, v1.MediaTypeImageManifest)
} else {
mediatypes = append(mediatypes, schema2.MediaTypeManifest)
}
if index {
if oci {
mediatypes = append(mediatypes, v1.MediaTypeImageIndex)
} else {
mediatypes = append(mediatypes, manifestlist.MediaTypeManifestList)
}
}
return mediatypes
}
14 changes: 13 additions & 1 deletion manifest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,26 @@ func TestManifestV1(t *testing.T) {
}
}

func TestManifestList(t *testing.T) {
out, err := run("manifest", "--index", "docker.io/library/busybox")
if err != nil {
t.Fatalf("output: %s, error: %v", out, err)
}

expected := `"mediaType": "application\/vnd.docker.distribution.manifest.list.v2+json"`
if !strings.Contains(out, expected) {
t.Fatalf("expected: %s\ngot: %s", expected, out)
}
}

func TestManifestWithHubDomain(t *testing.T) {
// Regression test for https://github.com/genuinetools/reg/issues/164
out, err := run("manifest", "busybox")
if err != nil {
t.Fatalf("output: %s, error: %v", out, err)
}

expected := `"schemaVersion": 2,`
expected := `"schemaVersion": 2`
if !strings.Contains(out, expected) {
t.Fatalf("expected: %s\ngot: %s", expected, out)
}
Expand Down
15 changes: 11 additions & 4 deletions registry/digest.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@ package registry
import (
"context"
"fmt"
"github.com/docker/distribution/manifest/schema2"
"github.com/sirupsen/logrus"
"net/http"
"strings"

"github.com/docker/distribution/manifest/schema2"
digest "github.com/opencontainers/go-digest"
"github.com/opencontainers/go-digest"
)

// Digest returns the digest for an image.
func (r *Registry) Digest(ctx context.Context, image Image) (digest.Digest, error) {
func (r *Registry) Digest(ctx context.Context, image Image, mediatypes ...string) (digest.Digest, error) {
if len(image.Digest) > 1 {
// return early if we already have an image digest.
return image.Digest, nil
Expand All @@ -25,7 +27,12 @@ func (r *Registry) Digest(ctx context.Context, image Image) (digest.Digest, erro
return "", err
}

req.Header.Add("Accept", schema2.MediaTypeManifest)
if mediatypes == nil {
mediatypes = []string{schema2.MediaTypeManifest}
}
logrus.Debugf("Using media types %s", mediatypes)
req.Header.Add("Accept", strings.Join(mediatypes, ", "))

resp, err := r.Client.Do(req.WithContext(ctx))
if err != nil {
return "", err
Expand Down
12 changes: 9 additions & 3 deletions registry/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@ import (
"context"
"encoding/json"
"errors"
"github.com/sirupsen/logrus"
"io/ioutil"
"net/http"
"strings"

"github.com/docker/distribution"
"github.com/docker/distribution/manifest/manifestlist"
Expand All @@ -20,7 +22,7 @@ var (
)

// Manifest returns the manifest for a specific repository:tag.
func (r *Registry) Manifest(ctx context.Context, repository, ref string) (distribution.Manifest, error) {
func (r *Registry) Manifest(ctx context.Context, repository, ref string, mediatypes ...string) (distribution.Manifest, error) {
uri := r.url("/v2/%s/manifests/%s", repository, ref)
r.Logf("registry.manifests uri=%s repository=%s ref=%s", uri, repository, ref)

Expand All @@ -29,7 +31,11 @@ func (r *Registry) Manifest(ctx context.Context, repository, ref string) (distri
return nil, err
}

req.Header.Add("Accept", schema2.MediaTypeManifest)
if mediatypes == nil {
mediatypes = []string{schema2.MediaTypeManifest}
}
logrus.Debugf("Using media types %s", mediatypes)
req.Header.Add("Accept", strings.Join(mediatypes, ", "))

resp, err := r.Client.Do(req.WithContext(ctx))
if err != nil {
Expand All @@ -41,7 +47,7 @@ func (r *Registry) Manifest(ctx context.Context, repository, ref string) (distri
if err != nil {
return nil, err
}
r.Logf("registry.manifests resp.Status=%s, body=%s", resp.Status, body)
r.Logf("registry.manifests resp.Status=%s, type=%s, body=%s", resp.Status, resp.Header.Get("Content-type"), body)

m, _, err := distribution.UnmarshalManifest(resp.Header.Get("Content-Type"), body)
if err != nil {
Expand Down