183 lines
4.7 KiB
Go
183 lines
4.7 KiB
Go
package push
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/containerd/containerd/content"
|
|
"github.com/containerd/containerd/images"
|
|
"github.com/containerd/containerd/remotes"
|
|
"github.com/containerd/containerd/remotes/docker"
|
|
"github.com/docker/distribution/reference"
|
|
"github.com/moby/buildkit/session"
|
|
"github.com/moby/buildkit/session/auth"
|
|
"github.com/moby/buildkit/util/imageutil"
|
|
"github.com/moby/buildkit/util/progress"
|
|
"github.com/moby/buildkit/util/tracing"
|
|
digest "github.com/opencontainers/go-digest"
|
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
func getCredentialsFunc(ctx context.Context, sm *session.Manager) func(string) (string, string, error) {
|
|
id := session.FromContext(ctx)
|
|
if id == "" {
|
|
return nil
|
|
}
|
|
return func(host string) (string, string, error) {
|
|
timeoutCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
|
defer cancel()
|
|
|
|
caller, err := sm.Get(timeoutCtx, id)
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
|
|
return auth.CredentialsFunc(context.TODO(), caller)(host)
|
|
}
|
|
}
|
|
|
|
func Push(ctx context.Context, sm *session.Manager, cs content.Provider, dgst digest.Digest, ref string, insecure bool) error {
|
|
|
|
parsed, err := reference.ParseNormalizedNamed(ref)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
ref = reference.TagNameOnly(parsed).String()
|
|
|
|
resolver := docker.NewResolver(docker.ResolverOptions{
|
|
Client: tracing.DefaultClient,
|
|
Credentials: getCredentialsFunc(ctx, sm),
|
|
PlainHTTP: insecure,
|
|
})
|
|
|
|
pusher, err := resolver.Pusher(ctx, ref)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var m sync.Mutex
|
|
manifestStack := []ocispec.Descriptor{}
|
|
|
|
filterHandler := images.HandlerFunc(func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
|
|
switch desc.MediaType {
|
|
case images.MediaTypeDockerSchema2Manifest, ocispec.MediaTypeImageManifest,
|
|
images.MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageIndex:
|
|
m.Lock()
|
|
manifestStack = append(manifestStack, desc)
|
|
m.Unlock()
|
|
return nil, images.ErrStopHandler
|
|
default:
|
|
return nil, nil
|
|
}
|
|
})
|
|
|
|
pushHandler := remotes.PushHandler(pusher, cs)
|
|
|
|
handlers := append([]images.Handler{},
|
|
childrenHandler(cs),
|
|
filterHandler,
|
|
pushHandler,
|
|
)
|
|
|
|
ra, err := cs.ReaderAt(ctx, dgst)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
mtype, err := imageutil.DetectManifestMediaType(ra)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
layersDone := oneOffProgress(ctx, "pushing layers")
|
|
err = images.Dispatch(ctx, images.Handlers(handlers...), ocispec.Descriptor{
|
|
Digest: dgst,
|
|
Size: ra.Size(),
|
|
MediaType: mtype,
|
|
})
|
|
layersDone(err)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
mfstDone := oneOffProgress(ctx, fmt.Sprintf("pushing manifest for %s", ref))
|
|
for i := len(manifestStack) - 1; i >= 0; i-- {
|
|
_, err := pushHandler(ctx, manifestStack[i])
|
|
if err != nil {
|
|
mfstDone(err)
|
|
return err
|
|
}
|
|
}
|
|
mfstDone(nil)
|
|
return nil
|
|
}
|
|
|
|
func oneOffProgress(ctx context.Context, id string) func(err error) error {
|
|
pw, _, _ := progress.FromContext(ctx)
|
|
now := time.Now()
|
|
st := progress.Status{
|
|
Started: &now,
|
|
}
|
|
pw.Write(id, st)
|
|
return func(err error) error {
|
|
// TODO: set error on status
|
|
now := time.Now()
|
|
st.Completed = &now
|
|
pw.Write(id, st)
|
|
pw.Close()
|
|
return err
|
|
}
|
|
}
|
|
|
|
func childrenHandler(provider content.Provider) images.HandlerFunc {
|
|
return func(ctx context.Context, desc ocispec.Descriptor) ([]ocispec.Descriptor, error) {
|
|
var descs []ocispec.Descriptor
|
|
switch desc.MediaType {
|
|
case images.MediaTypeDockerSchema2Manifest, ocispec.MediaTypeImageManifest:
|
|
p, err := content.ReadBlob(ctx, provider, desc.Digest)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// TODO(stevvooe): We just assume oci manifest, for now. There may be
|
|
// subtle differences from the docker version.
|
|
var manifest ocispec.Manifest
|
|
if err := json.Unmarshal(p, &manifest); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
descs = append(descs, manifest.Config)
|
|
descs = append(descs, manifest.Layers...)
|
|
case images.MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageIndex:
|
|
p, err := content.ReadBlob(ctx, provider, desc.Digest)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var index ocispec.Index
|
|
if err := json.Unmarshal(p, &index); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, m := range index.Manifests {
|
|
if m.Digest != "" {
|
|
descs = append(descs, m)
|
|
}
|
|
}
|
|
case images.MediaTypeDockerSchema2Layer, images.MediaTypeDockerSchema2LayerGzip,
|
|
images.MediaTypeDockerSchema2Config, ocispec.MediaTypeImageConfig,
|
|
ocispec.MediaTypeImageLayer, ocispec.MediaTypeImageLayerGzip:
|
|
// childless data types.
|
|
return nil, nil
|
|
default:
|
|
logrus.Warnf("encountered unknown type %v; children may not be fetched", desc.MediaType)
|
|
}
|
|
|
|
return descs, nil
|
|
}
|
|
}
|