Add new "bashbrew remote arches" command

This command will, given a remote image reference, look up the list of platforms from it and match them to supported bashbrew architectures (providing content descriptors for each).

Also, refactor registry code to be more correct: previously, this couldn't fetch from Docker without `DOCKERHUB_PUBLIC_PROXY` (see `registry-1.docker.io` change) and was ignoring content digests.  Now it works correctly with or without `DOCKERHUB_PUBLIC_PROXY`, verifies the size of every object it pulls, verifies the digest, _and_ should continue working with the in-progress Moby containerd-integration (where the local image ID becomes the digest of the manifest or index instead of the digest of the config blob as it is today).
This commit is contained in:
Tianon Gravi 2022-11-02 17:16:34 -07:00
parent 0feb2b9342
commit b20e82cb0d
10 changed files with 469 additions and 147 deletions

View File

@ -6,4 +6,5 @@
!go.sum
!manifest/
!pkg/
!registry/
!scripts/

View File

@ -1,17 +1,15 @@
package architecture
import "path"
import (
"path"
"github.com/containerd/containerd/platforms"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
// https://github.com/opencontainers/image-spec/blob/v1.0.1/image-index.md#image-index-property-descriptions
// see "platform" (under "manifests")
type OCIPlatform struct {
OS string `json:"os"`
Architecture string `json:"architecture"`
Variant string `json:"variant,omitempty"`
//OSVersion string `json:"os.version,omitempty"`
//OSFeatures []string `json:"os.features,omitempty"`
}
type OCIPlatform ocispec.Platform
var SupportedArches = map[string]OCIPlatform{
"amd64": {OS: "linux", Architecture: "amd64"},
@ -36,3 +34,18 @@ func (p OCIPlatform) String() string {
p.Variant,
)
}
func Normalize(p ocispec.Platform) ocispec.Platform {
p = platforms.Normalize(p)
if p.Architecture == "arm64" && p.Variant == "" {
// 😭 https://github.com/containerd/containerd/blob/1c90a442489720eec95342e1789ee8a5e1b9536f/platforms/database.go#L98 (inconsistent normalization of "linux/arm -> linux/arm/v7" vs "linux/arm64/v8 -> linux/arm64")
p.Variant = "v8"
// TODO get pedantic about amd64 variants too? (in our defense, those variants didn't exist when we defined our "amd64", unlike "arm64v8" 👀)
}
return p
}
func (p OCIPlatform) Is(q OCIPlatform) bool {
// (assumes "p" and "q" are both already bashbrew normalized, like one of the SupportedArches above)
return p.OS == q.OS && p.Architecture == q.Architecture && p.Variant == q.Variant
}

View File

@ -4,6 +4,8 @@ import (
"testing"
"github.com/docker-library/bashbrew/architecture"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
func TestString(t *testing.T) {
@ -21,3 +23,45 @@ func TestString(t *testing.T) {
})
}
}
func TestIs(t *testing.T) {
tests := map[bool][][2]architecture.OCIPlatform{
true: {
{architecture.SupportedArches["amd64"], architecture.SupportedArches["amd64"]},
{architecture.SupportedArches["arm32v5"], architecture.SupportedArches["arm32v5"]},
{architecture.SupportedArches["arm32v6"], architecture.SupportedArches["arm32v6"]},
{architecture.SupportedArches["arm32v7"], architecture.SupportedArches["arm32v7"]},
{architecture.SupportedArches["arm64v8"], architecture.OCIPlatform{OS: "linux", Architecture: "arm64", Variant: "v8"}},
{architecture.SupportedArches["windows-amd64"], architecture.OCIPlatform{OS: "windows", Architecture: "amd64", OSVersion: "1.2.3.4"}},
},
false: {
{architecture.SupportedArches["amd64"], architecture.OCIPlatform{OS: "linux", Architecture: "amd64", Variant: "v4"}},
{architecture.SupportedArches["amd64"], architecture.SupportedArches["arm64v8"]},
{architecture.SupportedArches["amd64"], architecture.SupportedArches["i386"]},
{architecture.SupportedArches["amd64"], architecture.SupportedArches["windows-amd64"]},
{architecture.SupportedArches["arm32v7"], architecture.SupportedArches["arm32v6"]},
{architecture.SupportedArches["arm32v7"], architecture.SupportedArches["arm64v8"]},
{architecture.SupportedArches["arm64v8"], architecture.OCIPlatform{OS: "linux", Architecture: "arm64", Variant: "v9"}},
},
}
for expected, test := range tests {
for _, platforms := range test {
t.Run(platforms[0].String()+" vs "+platforms[1].String(), func(t *testing.T) {
if got := platforms[0].Is(platforms[1]); got != expected {
t.Errorf("expected %v; got %v", expected, got)
}
})
}
}
}
func TestNormalize(t *testing.T) {
for arch, expected := range architecture.SupportedArches {
t.Run(arch, func(t *testing.T) {
normal := architecture.OCIPlatform(architecture.Normalize(ocispec.Platform(expected)))
if !expected.Is(normal) {
t.Errorf("expected %#v; got %#v", expected, normal)
}
})
}
}

View File

@ -39,6 +39,7 @@ func cmdPush(c *cli.Context) error {
}
// we can't use "r.Tags()" here because it will include SharedTags, which we never want to push directly (see "cmd-put-shared.go")
TagsLoop:
for i, tag := range entry.Tags {
if uniq && i > 0 {
break
@ -47,10 +48,12 @@ func cmdPush(c *cli.Context) error {
if !force {
localImageId, _ := dockerInspect("{{.Id}}", tag)
registryImageId := fetchRegistryImageId(tag)
if registryImageId != "" && localImageId == registryImageId {
fmt.Fprintf(os.Stderr, "skipping %s (remote image matches local)\n", tag)
continue
registryImageIds := fetchRegistryImageIds(tag)
for _, registryImageId := range registryImageIds {
if localImageId == registryImageId {
fmt.Fprintf(os.Stderr, "skipping %s (remote image matches local)\n", tag)
continue TagsLoop
}
}
}
fmt.Printf("Pushing %s\n", tag)

View File

@ -0,0 +1,70 @@
package main
import (
"context"
"encoding/json"
"fmt"
"sort"
"github.com/docker-library/bashbrew/registry"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/urfave/cli"
)
func cmdRemoteArches(c *cli.Context) error {
args := c.Args()
if len(args) < 1 {
return fmt.Errorf("expected at least one argument")
}
doJson := c.Bool("json")
ctx := context.Background()
for _, arg := range args {
img, err := registry.Resolve(ctx, arg)
if err != nil {
return err
}
arches, err := img.Architectures(ctx)
if err != nil {
return err
}
if doJson {
ret := struct {
Ref string `json:"ref"`
Desc ocispec.Descriptor `json:"desc"`
Arches map[string][]ocispec.Descriptor `json:"arches"`
}{
Ref: img.ImageRef,
Desc: img.Desc,
Arches: map[string][]ocispec.Descriptor{},
}
for arch, imgs := range arches {
for _, obj := range imgs {
ret.Arches[arch] = append(ret.Arches[arch], obj.Desc)
}
}
out, err := json.Marshal(ret)
if err != nil {
return err
}
fmt.Println(string(out))
} else {
fmt.Printf("%s -> %s\n", img.ImageRef, img.Desc.Digest)
// Go.....
keys := []string{}
for arch := range arches {
keys = append(keys, arch)
}
sort.Strings(keys)
for _, arch := range keys {
for _, obj := range arches[arch] {
fmt.Printf(" %s -> %s\n", arch, obj.Desc.Digest)
}
}
}
}
return nil
}

View File

@ -239,6 +239,11 @@ func main() {
Name: "target-namespace",
Usage: `target namespace to act into ("docker tag namespace/repo:tag target-namespace/repo:tag", "docker push target-namespace/repo:tag")`,
},
"json": cli.BoolFlag{
Name: "json",
Usage: "output machine-readable JSON instead of human-readable text",
},
}
app.Commands = []cli.Command{
@ -395,6 +400,22 @@ func main() {
Category: "plumbing",
},
{
Name: "remote",
Usage: "query registries for bashbrew-related data",
Before: subcommandBeforeFactory("remote"),
Category: "plumbing",
Subcommands: []cli.Command{
{
Name: "arches",
Usage: "returns a list of bashbrew architectures and content descriptors for the specified image(s)",
Flags: []cli.Flag{
commonFlags["json"],
},
Action: cmdRemoteArches,
},
},
},
}
err := app.Run(os.Args)

View File

@ -2,63 +2,48 @@ package main
import (
"context"
"encoding/json"
"net/url"
"os"
"github.com/containerd/containerd/images"
"github.com/containerd/containerd/reference/docker"
"github.com/containerd/containerd/remotes"
dockerremote "github.com/containerd/containerd/remotes/docker"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
"github.com/docker-library/bashbrew/registry"
)
var registryImageIdCache = map[string]string{}
var registryImageIdsCache = map[string][]string{}
// assumes the provided image name is NOT a manifest list (used for testing whether we need to "bashbrew push" or whether the remote image is already up-to-date)
// this does NOT handle authentication, and will return the empty string for repositories which require it (causing "bashbrew push" to simply shell out to "docker push" which will handle authentication appropriately)
func fetchRegistryImageId(image string) string {
func fetchRegistryImageIds(image string) []string {
ctx := context.Background()
ref, resolver, err := fetchRegistryResolveHelper(image)
img, err := registry.Resolve(ctx, image)
if err != nil {
return ""
return nil
}
name, desc, err := resolver.Resolve(ctx, ref)
digest := img.Desc.Digest.String()
if ids, ok := registryImageIdsCache[digest]; ok {
return ids
}
manifests, err := img.Manifests(ctx)
if err != nil {
return ""
return nil
}
if desc.MediaType != images.MediaTypeDockerSchema2Manifest && desc.MediaType != ocispec.MediaTypeImageManifest {
return ""
ids := []string{}
if img.IsImageIndex() {
ids = append(ids, digest)
}
digest := desc.Digest.String()
if id, ok := registryImageIdCache[digest]; ok {
return id
for _, manifestDesc := range manifests {
ids = append(ids, manifestDesc.Digest.String())
manifest, err := img.At(manifestDesc).Manifest(ctx)
if err != nil {
continue
}
ids = append(ids, manifest.Config.Digest.String())
}
fetcher, err := resolver.Fetcher(ctx, name)
if err != nil {
return ""
if len(ids) > 0 {
registryImageIdsCache[digest] = ids
}
r, err := fetcher.Fetch(ctx, desc)
if err != nil {
return ""
}
defer r.Close()
var manifest ocispec.Manifest
if err := json.NewDecoder(r).Decode(&manifest); err != nil {
return ""
}
id := manifest.Config.Digest.String()
if id != "" {
registryImageIdCache[digest] = id
}
return id
return ids
}
var registryManifestListCache = map[string][]string{}
@ -67,46 +52,22 @@ var registryManifestListCache = map[string][]string{}
func fetchRegistryManiestListDigests(image string) []string {
ctx := context.Background()
ref, resolver, err := fetchRegistryResolveHelper(image)
img, err := registry.Resolve(ctx, image)
if err != nil {
return nil
}
name, desc, err := resolver.Resolve(ctx, ref)
if err != nil {
return nil
}
digest := desc.Digest.String()
if desc.MediaType == images.MediaTypeDockerSchema2Manifest || desc.MediaType == ocispec.MediaTypeImageManifest {
return []string{digest}
}
if desc.MediaType != images.MediaTypeDockerSchema2ManifestList && desc.MediaType != ocispec.MediaTypeImageIndex {
return nil
}
digest := img.Desc.Digest.String()
if digests, ok := registryManifestListCache[digest]; ok {
return digests
}
fetcher, err := resolver.Fetcher(ctx, name)
manifests, err := img.Manifests(ctx)
if err != nil {
return nil
}
r, err := fetcher.Fetch(ctx, desc)
if err != nil {
return nil
}
defer r.Close()
var manifestList ocispec.Index
if err := json.NewDecoder(r).Decode(&manifestList); err != nil {
return nil
}
digests := []string{}
for _, manifest := range manifestList.Manifests {
for _, manifest := range manifests {
if manifest.Digest != "" {
digests = append(digests, manifest.Digest.String())
}
@ -116,30 +77,3 @@ func fetchRegistryManiestListDigests(image string) []string {
}
return digests
}
func fetchRegistryResolveHelper(image string) (string, remotes.Resolver, error) {
ref, err := docker.ParseAnyReference(image)
if err != nil {
return "", nil, err
}
if namedRef, ok := ref.(docker.Named); ok {
// add ":latest" if necessary
namedRef = docker.TagNameOnly(namedRef)
ref = namedRef
}
return ref.String(), dockerremote.NewResolver(dockerremote.ResolverOptions{
Host: func(host string) (string, error) {
if host == "docker.io" {
if publicProxy := os.Getenv("DOCKERHUB_PUBLIC_PROXY"); publicProxy != "" {
if publicProxyURL, err := url.Parse(publicProxy); err == nil {
// TODO Scheme (also not sure if "host:port" will be satisfactory to containerd here, but 🤷)
return publicProxyURL.Host, nil
} else {
return "", err
}
}
}
return host, nil
},
}), nil
}

19
go.mod
View File

@ -3,9 +3,9 @@ module github.com/docker-library/bashbrew
go 1.18
require (
github.com/containerd/containerd v1.5.11
github.com/containerd/containerd v1.6.9
github.com/go-git/go-git/v5 v5.4.2
github.com/opencontainers/image-spec v1.0.2
github.com/opencontainers/image-spec v1.1.0-rc2.0.20221013174636-8159c8264e2e
github.com/sirupsen/logrus v1.8.1
github.com/urfave/cli v1.22.5
pault.ag/go/debian v0.12.0
@ -13,7 +13,7 @@ require (
)
require (
github.com/Microsoft/go-winio v0.5.1 // indirect
github.com/Microsoft/go-winio v0.5.2 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20211112122917-428f8eabeeb3 // indirect
github.com/acomagu/bufpipe v1.0.3 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
@ -28,16 +28,15 @@ require (
github.com/mitchellh/go-homedir v1.1.0 // indirect
github.com/moby/locker v1.0.1 // indirect
github.com/opencontainers/go-digest v1.0.0 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sergi/go-diff v1.2.0 // indirect
github.com/xanzy/ssh-agent v0.3.1 // indirect
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 // indirect
golang.org/x/net v0.0.0-20211123203042-d83791d6bcd9 // indirect
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd // indirect
golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c // indirect
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881 // indirect
google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12 // indirect
google.golang.org/grpc v1.42.0 // indirect
google.golang.org/protobuf v1.27.1 // indirect
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f // indirect
google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 // indirect
google.golang.org/grpc v1.47.0 // indirect
google.golang.org/protobuf v1.28.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
)

58
go.sum
View File

@ -5,9 +5,8 @@ github.com/DataDog/zstd v1.4.8/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwS
github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA=
github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0=
github.com/Microsoft/go-winio v0.5.0/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY=
github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
github.com/Microsoft/go-winio v0.5.2 h1:a9IhgEQBCUEk6QCdml9CiJGhAws+YwffDHEMp1VMrpA=
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/ProtonMail/go-crypto v0.0.0-20210428141323-04723f9f07d7/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
github.com/ProtonMail/go-crypto v0.0.0-20211112122917-428f8eabeeb3 h1:XcF0cTDJeiuZ5NU8w7WUDge0HRwwNRmxj/GGk6KSA6g=
github.com/ProtonMail/go-crypto v0.0.0-20211112122917-428f8eabeeb3/go.mod h1:z4/9nQmJSSwwds7ejkxaJwO37dru3geImFUdJlaLzQo=
@ -19,18 +18,16 @@ github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kd
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211001041855-01bcc9b48dfe/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
github.com/containerd/containerd v1.5.11 h1:+biZCY9Kns9t2J8L9hOqubjvNQBr1ULdmR7kL+omKoY=
github.com/containerd/containerd v1.5.11/go.mod h1:FJl/l1urLXpO3oKDx2No2ouBno2GSI56nTl02HfHeZY=
github.com/containerd/containerd v1.6.9 h1:IN/r8DUes/B5lEGTNfIiUkfZBtIQJGx2ai703dV6lRA=
github.com/containerd/containerd v1.6.9/go.mod h1:XVicUvkxOrftE2Q1YWUXgZwkkAxwQYNOFzYWvfVfEfQ=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
@ -44,8 +41,7 @@ github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymF
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
github.com/envoyproxy/go-control-plane v0.10.2-0.20220325020618-49ff273808a1/go.mod h1:KJwIaB5Mv44NWtYuAOFCVOjcI94vtpEz2JU/D2v6IjE=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc=
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
@ -81,8 +77,9 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ=
github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw=
github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU=
@ -113,8 +110,8 @@ github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQ
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U=
github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM=
github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM=
github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/opencontainers/image-spec v1.1.0-rc2.0.20221013174636-8159c8264e2e h1:s/Yjbl65/SrXqrMXDSP7eeC1vGZP3mOpya4rNeTwTKY=
github.com/opencontainers/image-spec v1.1.0-rc2.0.20221013174636-8159c8264e2e/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
@ -133,7 +130,6 @@ github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMB
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
@ -155,8 +151,8 @@ golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPh
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871 h1:/pEO3GD/ABYAjuakUS6xSEmmlyVS4kxBNkeA9tLJiTI=
golang.org/x/crypto v0.0.0-20211117183948-ae814b36b871/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd h1:XcWmESyNjXJMLahc3mqVQJcgSTDxFxhETVlfk9uGc38=
golang.org/x/crypto v0.0.0-20220315160706-3147a52a75dd/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
@ -168,11 +164,12 @@ golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k=
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20211123203042-d83791d6bcd9 h1:0qxwC5n+ttVOINCBeRHO0nq9X7uy8SDsPoi5OaCdIEI=
golang.org/x/net v0.0.0-20211123203042-d83791d6bcd9/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b h1:PxfKdU9lEEDYjdIzOtC4qFWgkU2rGHdKlKowJSMN9h0=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -190,7 +187,9 @@ golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -199,14 +198,14 @@ golang.org/x/sys v0.0.0-20210502180810-71e4cd670f79/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881 h1:TyHqChC80pFkXWraUUf6RuB5IqFdQieMLwwCJokV2pc=
golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f h1:v4INt8xihDGvnrfjMDVXGxw9wrfxYyCjk0KbXjhR55s=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
@ -221,17 +220,17 @@ google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoA
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12 h1:DN5b3HU13J4sMd/QjDx34U6afpaexKTDdop+26pdjdk=
google.golang.org/genproto v0.0.0-20211129164237-f09f9a12af12/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21 h1:hrbNEivu7Zn1pxvHk6MBrq9iE22woVILTHqexqBxe6I=
google.golang.org/genproto v0.0.0-20220502173005-c8bf987b8c21/go.mod h1:RAyBrSAP7Fh3Nc84ghnVLDPuV51xc9agzmm4Ph6i0Q4=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
google.golang.org/grpc v1.42.0 h1:XT2/MFpuPFsEX2fWh3YQtHkZ+WYZFQRfaUgLZYj/p6A=
google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
google.golang.org/grpc v1.46.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
google.golang.org/grpc v1.47.0 h1:9n77onPX5F3qfFCqjy9dhn8PbNQsIKeVU04J9G7umt8=
google.golang.org/grpc v1.47.0/go.mod h1:vN9eftEi1UMyUsIF80+uQXhHjbXYbm0uXoFCACuMGWk=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@ -243,8 +242,9 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw=
google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
@ -257,8 +257,8 @@ gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

237
registry/registry.go Normal file
View File

@ -0,0 +1,237 @@
package registry
import (
"context"
"encoding/json"
"fmt"
"io"
"net/url"
"os"
"unicode"
// thanks, go-digest...
_ "crypto/sha256"
_ "crypto/sha512"
"github.com/docker-library/bashbrew/architecture"
"github.com/containerd/containerd/images"
"github.com/containerd/containerd/reference/docker"
"github.com/containerd/containerd/remotes"
dockerremote "github.com/containerd/containerd/remotes/docker"
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
)
type ResolvedObject struct {
Desc ocispec.Descriptor
ImageRef string
resolver remotes.Resolver
fetcher remotes.Fetcher
}
func (obj ResolvedObject) fetchJSON(ctx context.Context, v interface{}) error {
// prevent go-digest panics later
if err := obj.Desc.Digest.Validate(); err != nil {
return err
}
// (perhaps use a containerd content store?? they do validation of all content they ingest, and then there's a cache)
r, err := obj.fetcher.Fetch(ctx, obj.Desc)
if err != nil {
return err
}
defer r.Close()
// make sure we can't possibly read (much) more than we're supposed to
limited := &io.LimitedReader{
R: r,
N: obj.Desc.Size + 1, // +1 to allow us to detect if we read too much (see verification below)
}
// copy all read data into the digest verifier so we can validate afterwards
verifier := obj.Desc.Digest.Verifier()
tee := io.TeeReader(limited, verifier)
// decode directly! (mostly avoids double memory hit for big objects)
// (TODO protect against malicious objects somehow?)
if err := json.NewDecoder(tee).Decode(v); err != nil {
return err
}
// read anything leftover ...
bs, err := io.ReadAll(tee)
if err != nil {
return err
}
// ... and make sure it was just whitespace, if anything
for _, b := range bs {
if !unicode.IsSpace(rune(b)) {
return fmt.Errorf("unexpected non-whitespace at the end of %q: %+v\n", obj.Desc.Digest.String(), rune(b))
}
}
// after reading *everything*, we should have exactly one byte left in our LimitedReader (anything else is an error)
if limited.N < 1 {
return fmt.Errorf("size of %q is bigger than it should be (%d)", obj.Desc.Digest.String(), obj.Desc.Size)
} else if limited.N > 1 {
return fmt.Errorf("size of %q is %d bytes smaller than it should be (%d)", obj.Desc.Digest.String(), limited.N-1, obj.Desc.Size)
}
// and finally, let's verify our checksum
if !verifier.Verified() {
return fmt.Errorf("digest of %q not correct", obj.Desc.Digest.String())
}
return nil
}
func get[T any](ctx context.Context, obj ResolvedObject) (*T, error) {
var ret T
if err := obj.fetchJSON(ctx, &ret); err != nil {
return nil, err
}
return &ret, nil
}
// At returns a new object pointing to the given descriptor (still within the context of the same repository as the original resolved object)
func (obj ResolvedObject) At(desc ocispec.Descriptor) *ResolvedObject {
obj.Desc = desc
return &obj
}
// Index assumes the given object is an "index" or "manifest list" and fetches/returns the parsed index JSON
func (obj ResolvedObject) Index(ctx context.Context) (*ocispec.Index, error) {
if !obj.IsImageIndex() {
return nil, fmt.Errorf("unknown media type: %q", obj.Desc.MediaType)
}
return get[ocispec.Index](ctx, obj)
}
// Manifests returns a list of "content descriptors" that corresponds to either this object (if it is a single-image manifest) or all the manifests of the index/manifest list this object represents
func (obj ResolvedObject) Manifests(ctx context.Context) ([]ocispec.Descriptor, error) {
if obj.IsImageManifest() {
return []ocispec.Descriptor{obj.Desc}, nil
}
index, err := obj.Index(ctx)
if err != nil {
return nil, err
}
return index.Manifests, nil
}
// Architectures returns a map of "bashbrew architecture" strings to a list of members of the object (as either a manifest or an index) which match the given "bashbrew architecture" (either in an explicit "platform" object or by reading all the way down into the image "config" object for the platform fields)
func (obj ResolvedObject) Architectures(ctx context.Context) (map[string][]ResolvedObject, error) {
manifests, err := obj.Manifests(ctx)
if err != nil {
return nil, err
}
ret := map[string][]ResolvedObject{}
for _, manifestDesc := range manifests {
obj := obj.At(manifestDesc)
if obj.Desc.Platform == nil || obj.Desc.Platform.OS == "" || obj.Desc.Platform.Architecture == "" {
manifest, err := obj.Manifest(ctx)
if err != nil {
return nil, err // TODO should we really return this, or should we ignore it?
}
config, err := obj.At(manifest.Config).ConfigBlob(ctx)
if err != nil {
return nil, err // TODO should we really return this, or should we ignore it?
}
obj.Desc.Platform = &config.Platform
}
objPlat := architecture.Normalize(*obj.Desc.Platform)
obj.Desc.Platform = &objPlat
for arch, plat := range architecture.SupportedArches {
if plat.Is(architecture.OCIPlatform(objPlat)) {
ret[arch] = append(ret[arch], *obj)
}
}
}
return ret, nil
}
// Manifest assumes the given object is a (single-image) "manifest" (see [ResolvedObject.At]) and fetches/returns the parsed manifest JSON
func (obj ResolvedObject) Manifest(ctx context.Context) (*ocispec.Manifest, error) {
if !obj.IsImageManifest() {
return nil, fmt.Errorf("unknown media type: %q", obj.Desc.MediaType)
}
return get[ocispec.Manifest](ctx, obj)
}
// ConfigBlob assumes the given object is a "config" blob (see [ResolvedObject.At]) and fetches/returns the parsed config object
func (obj ResolvedObject) ConfigBlob(ctx context.Context) (*ocispec.Image, error) {
if obj.Desc.MediaType != "application/vnd.oci.image.config.v1+json" && obj.Desc.MediaType != "application/vnd.docker.container.image.v1+json" {
return nil, fmt.Errorf("unknown media type: %q", obj.Desc.MediaType)
}
return get[ocispec.Image](ctx, obj)
}
func (obj ResolvedObject) IsImageManifest() bool {
return obj.Desc.MediaType == ocispec.MediaTypeImageManifest || obj.Desc.MediaType == images.MediaTypeDockerSchema2Manifest
}
func (obj ResolvedObject) IsImageIndex() bool {
return obj.Desc.MediaType == ocispec.MediaTypeImageIndex || obj.Desc.MediaType == images.MediaTypeDockerSchema2ManifestList
}
// Resolve returns an object which can be used to query a registry for manifest objects or certain blobs with type checking helpers
func Resolve(ctx context.Context, image string) (*ResolvedObject, error) {
var (
obj = ResolvedObject{
ImageRef: image,
}
err error
)
obj.ImageRef, obj.resolver, err = resolverHelper(obj.ImageRef)
if err != nil {
return nil, err
}
obj.ImageRef, obj.Desc, err = obj.resolver.Resolve(ctx, obj.ImageRef)
if err != nil {
return nil, err
}
obj.fetcher, err = obj.resolver.Fetcher(ctx, obj.ImageRef)
if err != nil {
return nil, err
}
return &obj, nil
}
func resolverHelper(image string) (string, remotes.Resolver, error) {
ref, err := docker.ParseAnyReference(image)
if err != nil {
return "", nil, err
}
if namedRef, ok := ref.(docker.Named); ok {
// add ":latest" if necessary
namedRef = docker.TagNameOnly(namedRef)
ref = namedRef
}
return ref.String(), dockerremote.NewResolver(dockerremote.ResolverOptions{
// TODO port this to "Hosts:" (especially so we can return Scheme correctly) but requires reimplementing some of https://github.com/containerd/containerd/blob/v1.6.9/remotes/docker/resolver.go#L161-L184 😞
Host: func(host string) (string, error) {
if host == "docker.io" {
if publicProxy := os.Getenv("DOCKERHUB_PUBLIC_PROXY"); publicProxy != "" {
if publicProxyURL, err := url.Parse(publicProxy); err == nil {
// TODO Scheme (also not sure if "host:port" will be satisfactory to containerd here, but 🤷)
return publicProxyURL.Host, nil
} else {
return "", err
}
}
return "registry-1.docker.io", nil // https://github.com/containerd/containerd/blob/1c90a442489720eec95342e1789ee8a5e1b9536f/remotes/docker/registry.go#L193
}
return host, nil
},
}), nil
}