binctr/container/pull.go
Jess Frazelle cdd93563f5 add better generate
Signed-off-by: Jess Frazelle <acidburn@microsoft.com>
2018-03-20 01:33:56 -04:00

196 lines
6.2 KiB
Go

package container
import (
"context"
"errors"
"fmt"
"io"
"log"
"runtime"
"github.com/docker/distribution"
"github.com/docker/distribution/manifest/manifestlist"
"github.com/docker/distribution/manifest/schema1"
"github.com/docker/distribution/manifest/schema2"
"github.com/docker/distribution/reference"
"github.com/genuinetools/reg/registry"
"github.com/genuinetools/reg/repoutils"
digest "github.com/opencontainers/go-digest"
)
func pull(ctx context.Context, dst distribution.Namespace, name reference.Named, tag string) error {
// Get the auth config.
auth, err := repoutils.GetAuthConfig("", "", reference.Domain(name))
if err != nil {
return err
}
// TODO: add flag to flip switch for turning off SSL verification
// Create a new registry client.
src, err := registry.New(auth, false)
if err != nil {
return fmt.Errorf("creating new registry api client failed: %v", err)
}
fmt.Println("pulling", name.String())
imgPath := reference.Path(name)
// Get the manifest.
manifest, err := src.Manifest(imgPath, tag)
if err != nil {
return fmt.Errorf("getting manifest for '%s:%s' failed: %v", imgPath, tag, err)
}
switch v := manifest.(type) {
case *schema1.SignedManifest:
return pullV1()
case *schema2.DeserializedManifest:
return pullV2(ctx, dst, src, v, name, imgPath, tag)
case *manifestlist.DeserializedManifestList:
return pullManifestList(ctx, dst, src, v, name, imgPath, tag)
}
return errors.New("unsupported manifest format")
}
func pullV1() error {
return errors.New("schema1 manifest not supported")
}
func pullV2(ctx context.Context, dst distribution.Namespace, src *registry.Registry, manifest *schema2.DeserializedManifest, name reference.Named, imgPath, tag string) error {
dstRepo, err := dst.Repository(ctx, name)
if err != nil {
return fmt.Errorf("creating the destination repository failed: %v", err)
}
dstBlobStore := dstRepo.Blobs(ctx)
for _, ref := range manifest.References() {
// TODO: make a progress bar
fmt.Printf("pulling layer %s\n", ref.Digest.String())
blob, err := src.DownloadLayer(imgPath, ref.Digest)
if err != nil {
return fmt.Errorf("getting remote blob %q failed failed: %v", ref.Digest.String(), err)
}
upload, err := dstBlobStore.Create(ctx)
if err != nil {
return fmt.Errorf("creating the local blob writer failed: %v", err)
}
if _, err := io.Copy(upload, blob); err != nil {
return fmt.Errorf("writing to the local blob failed: %v", err)
}
if _, err := upload.Commit(ctx, ref); err != nil {
return fmt.Errorf("commiting %q locally failed: %v", ref.Digest.String(), err)
}
upload.Close()
}
// Create the manifest service locally.
dms, err := dstRepo.Manifests(ctx)
if err != nil {
return fmt.Errorf("creating manifest service locally failed: %v", err)
}
// Put the manifest locally.
manDst, err := dms.Put(ctx, manifest, distribution.WithTag(tag))
if err != nil {
return fmt.Errorf("putting the manifest with tag %q locally failed: %v", tag, err)
}
// TODO: find a better way to get the manifest descriptor locally.
// Get the manifest descriptor.
mf, err := dms.Get(ctx, manDst)
if err != nil {
return fmt.Errorf("getting the manifest with digest %q locally failed: %v", manDst.String(), err)
}
mediatype, pl, err := mf.Payload()
if err != nil {
return fmt.Errorf("payload failed: %v", err)
}
_, desc, err := distribution.UnmarshalManifest(mediatype, pl)
if err != nil {
return fmt.Errorf("umarshal failed: %v", err)
}
// Put the tag locally.
if err := dstRepo.Tags(ctx).Tag(ctx, tag, desc); err != nil {
return fmt.Errorf("establishing a relationship between the tag %q and digest %q locally failed: %v", tag, manDst.String(), err)
}
return nil
}
func pullManifestList(ctx context.Context, dst distribution.Namespace, src *registry.Registry, mfstList *manifestlist.DeserializedManifestList, name reference.Named, imgPath, tag string) error {
if _, err := schema2ManifestDigest(name, mfstList); err != nil {
return err
}
log.Printf("%s resolved to a manifestList object with %d entries; looking for a %s/%s match", name, len(mfstList.Manifests), runtime.GOOS, runtime.GOARCH)
manifestMatches := filterManifests(mfstList.Manifests, runtime.GOOS)
if len(manifestMatches) == 0 {
return fmt.Errorf("no matching manifest for %s/%s in the manifest list entries", runtime.GOOS, runtime.GOARCH)
}
if len(manifestMatches) > 1 {
log.Printf("found multiple matches in manifest list, choosing best match %s", manifestMatches[0].Digest.String())
}
manifestDigest := manifestMatches[0].Digest
// Get the manifest.
manifest, err := src.Manifest(imgPath, manifestDigest.String())
if err != nil {
return fmt.Errorf("getting manifest for %s@%s failed: %v", imgPath, manifestDigest.String(), err)
}
switch v := manifest.(type) {
case *schema1.SignedManifest:
return pullV1()
case *schema2.DeserializedManifest:
return pullV2(ctx, dst, src, v, name, imgPath, tag)
}
return errors.New("unsupported manifest format")
}
// schema2ManifestDigest computes the manifest digest, and, if pulling by
// digest, ensures that it matches the requested digest.
func schema2ManifestDigest(ref reference.Named, mfst distribution.Manifest) (digest.Digest, error) {
_, canonical, err := mfst.Payload()
if err != nil {
return "", err
}
// If pull by digest, then verify the manifest digest.
if digested, isDigested := ref.(reference.Canonical); isDigested {
verifier := digested.Digest().Verifier()
if _, err := verifier.Write(canonical); err != nil {
return "", err
}
if !verifier.Verified() {
return "", fmt.Errorf("manifest verification failed for digest %s", digested.Digest())
}
return digested.Digest(), nil
}
return digest.FromBytes(canonical), nil
}
func filterManifests(manifests []manifestlist.ManifestDescriptor, os string) []manifestlist.ManifestDescriptor {
var matches []manifestlist.ManifestDescriptor
for _, manifestDescriptor := range manifests {
if manifestDescriptor.Platform.Architecture == runtime.GOARCH && manifestDescriptor.Platform.OS == os {
matches = append(matches, manifestDescriptor)
log.Printf("found match for %s/%s with media type %s, digest %s", os, runtime.GOARCH, manifestDescriptor.MediaType, manifestDescriptor.Digest.String())
}
}
return matches
}