2017-12-13 00:59:06 +00:00
|
|
|
package oci
|
|
|
|
|
|
|
|
import (
|
|
|
|
"time"
|
|
|
|
|
2017-12-18 00:20:19 +00:00
|
|
|
"github.com/containerd/containerd/images"
|
2017-12-13 00:59:06 +00:00
|
|
|
"github.com/containerd/containerd/images/oci"
|
2017-12-18 00:20:19 +00:00
|
|
|
"github.com/docker/distribution/reference"
|
2017-12-13 00:59:06 +00:00
|
|
|
"github.com/moby/buildkit/cache"
|
|
|
|
"github.com/moby/buildkit/exporter"
|
|
|
|
"github.com/moby/buildkit/exporter/containerimage"
|
|
|
|
"github.com/moby/buildkit/session"
|
|
|
|
"github.com/moby/buildkit/session/filesync"
|
|
|
|
"github.com/moby/buildkit/util/progress"
|
2017-12-18 00:20:19 +00:00
|
|
|
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
|
|
|
"github.com/pkg/errors"
|
2017-12-13 00:59:06 +00:00
|
|
|
"github.com/sirupsen/logrus"
|
|
|
|
"golang.org/x/net/context"
|
|
|
|
)
|
|
|
|
|
2017-12-18 00:20:19 +00:00
|
|
|
type ExporterVariant string
|
|
|
|
|
2017-12-13 00:59:06 +00:00
|
|
|
const (
|
|
|
|
exporterImageConfig = "containerimage.config"
|
2017-12-18 00:20:19 +00:00
|
|
|
keyImageName = "name"
|
|
|
|
VariantOCI = "oci"
|
|
|
|
VariantDocker = "docker"
|
2017-12-13 00:59:06 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type Opt struct {
|
|
|
|
SessionManager *session.Manager
|
|
|
|
ImageWriter *containerimage.ImageWriter
|
2017-12-18 00:20:19 +00:00
|
|
|
Variant ExporterVariant
|
2017-12-13 00:59:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type imageExporter struct {
|
|
|
|
opt Opt
|
|
|
|
}
|
|
|
|
|
|
|
|
func New(opt Opt) (exporter.Exporter, error) {
|
|
|
|
im := &imageExporter{opt: opt}
|
|
|
|
return im, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *imageExporter) Resolve(ctx context.Context, opt map[string]string) (exporter.ExporterInstance, error) {
|
|
|
|
id := session.FromContext(ctx)
|
|
|
|
if id == "" {
|
|
|
|
return nil, errors.New("could not access local files without session")
|
|
|
|
}
|
|
|
|
|
|
|
|
timeoutCtx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
|
|
|
defer cancel()
|
|
|
|
|
|
|
|
caller, err := e.opt.SessionManager.Get(timeoutCtx, id)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
i := &imageExporterInstance{imageExporter: e, caller: caller}
|
|
|
|
for k, v := range opt {
|
|
|
|
switch k {
|
|
|
|
case exporterImageConfig:
|
|
|
|
i.config = []byte(v)
|
2017-12-18 00:20:19 +00:00
|
|
|
case keyImageName:
|
|
|
|
parsed, err := reference.ParseNormalizedNamed(v)
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrapf(err, "failed to parse %s", v)
|
|
|
|
}
|
|
|
|
i.name = reference.TagNameOnly(parsed).String()
|
2017-12-13 00:59:06 +00:00
|
|
|
default:
|
|
|
|
logrus.Warnf("oci exporter: unknown option %s", k)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return i, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type imageExporterInstance struct {
|
|
|
|
*imageExporter
|
|
|
|
config []byte
|
|
|
|
caller session.Caller
|
2017-12-18 00:20:19 +00:00
|
|
|
name string
|
2017-12-13 00:59:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
func (e *imageExporterInstance) Name() string {
|
|
|
|
return "exporting to oci image format"
|
|
|
|
}
|
|
|
|
|
|
|
|
func (e *imageExporterInstance) Export(ctx context.Context, ref cache.ImmutableRef, opt map[string][]byte) error {
|
|
|
|
if config, ok := opt[exporterImageConfig]; ok {
|
|
|
|
e.config = config
|
|
|
|
}
|
|
|
|
desc, err := e.opt.ImageWriter.Commit(ctx, ref, e.config)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-12-28 19:56:04 +00:00
|
|
|
defer func() {
|
|
|
|
e.opt.ImageWriter.ContentStore().Delete(context.TODO(), desc.Digest)
|
|
|
|
}()
|
2017-12-18 00:20:19 +00:00
|
|
|
if desc.Annotations == nil {
|
|
|
|
desc.Annotations = map[string]string{}
|
|
|
|
}
|
|
|
|
desc.Annotations[ocispec.AnnotationCreated] = time.Now().UTC().Format(time.RFC3339)
|
|
|
|
|
|
|
|
exp, err := getExporter(e.opt.Variant, e.name)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2017-12-13 00:59:06 +00:00
|
|
|
|
|
|
|
w, err := filesync.CopyFileWriter(ctx, e.caller)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
report := oneOffProgress(ctx, "sending tarball")
|
2017-12-18 00:20:19 +00:00
|
|
|
if err := exp.Export(ctx, e.opt.ImageWriter.ContentStore(), *desc, w); err != nil {
|
2017-12-13 00:59:06 +00:00
|
|
|
w.Close()
|
|
|
|
return report(err)
|
|
|
|
}
|
|
|
|
return report(w.Close())
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
2017-12-18 00:20:19 +00:00
|
|
|
|
|
|
|
func getExporter(variant ExporterVariant, name string) (images.Exporter, error) {
|
|
|
|
switch variant {
|
|
|
|
case VariantOCI:
|
|
|
|
return &oci.V1Exporter{}, nil
|
|
|
|
case VariantDocker:
|
|
|
|
return &DockerExporter{name: name}, nil
|
|
|
|
default:
|
|
|
|
return nil, errors.Errorf("invalid variant %q", variant)
|
|
|
|
}
|
|
|
|
}
|