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/resolver" 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, rfn resolver.ResolveOptionsFunc) error { desc := ocispec.Descriptor{ Digest: dgst, } parsed, err := reference.ParseNormalizedNamed(ref) if err != nil { return err } ref = reference.TagNameOnly(parsed).String() opt := rfn(ref) opt.Credentials = getCredentialsFunc(ctx, sm) if insecure { opt.PlainHTTP = insecure } resolver := docker.NewResolver(opt) 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, desc) 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) 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) 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 } }