| 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 { |
| desc := ocispec.Descriptor{ |
| Digest: dgst, |
| } |
| 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, 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 |
| } |
| } |