imagetools: add create support
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>pull/37/head
parent
2f668d555c
commit
80ad78e372
@ -0,0 +1,186 @@
|
||||
package imagetools
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/containerd/containerd/images"
|
||||
"github.com/opencontainers/go-digest"
|
||||
"github.com/opencontainers/image-spec/specs-go"
|
||||
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
|
||||
"github.com/pkg/errors"
|
||||
"golang.org/x/sync/errgroup"
|
||||
)
|
||||
|
||||
func (r *Resolver) Combine(ctx context.Context, in string, descs []ocispec.Descriptor) ([]byte, ocispec.Descriptor, error) {
|
||||
ref, err := parseRef(in)
|
||||
if err != nil {
|
||||
return nil, ocispec.Descriptor{}, err
|
||||
}
|
||||
|
||||
eg, ctx := errgroup.WithContext(ctx)
|
||||
|
||||
dts := make([][]byte, len(descs))
|
||||
for i := range dts {
|
||||
func(i int) {
|
||||
eg.Go(func() error {
|
||||
dt, err := r.GetDescriptor(ctx, ref.String(), descs[i])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dts[i] = dt
|
||||
|
||||
if descs[i].MediaType == "" {
|
||||
mt, err := detectMediaType(dt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
descs[i].MediaType = mt
|
||||
}
|
||||
|
||||
mt := descs[i].MediaType
|
||||
|
||||
switch mt {
|
||||
case images.MediaTypeDockerSchema2Manifest, ocispec.MediaTypeImageManifest:
|
||||
if descs[i].Platform == nil {
|
||||
cfg, err := r.loadConfig(ctx, in, dt)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
descs[i].Platform = &ocispec.Platform{
|
||||
OS: cfg.OS,
|
||||
Architecture: cfg.Architecture,
|
||||
}
|
||||
}
|
||||
case images.MediaTypeDockerSchema1Manifest:
|
||||
return errors.Errorf("schema1 manifests are not allowed in manifest lists")
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}(i)
|
||||
}
|
||||
|
||||
if err := eg.Wait(); err != nil {
|
||||
return nil, ocispec.Descriptor{}, err
|
||||
}
|
||||
|
||||
// on single source, return original bytes
|
||||
if len(descs) == 1 {
|
||||
if mt := descs[0].MediaType; mt == images.MediaTypeDockerSchema2ManifestList || mt == ocispec.MediaTypeImageIndex {
|
||||
return dts[0], descs[0], nil
|
||||
}
|
||||
}
|
||||
|
||||
m := map[digest.Digest]int{}
|
||||
newDescs := make([]ocispec.Descriptor, 0, len(descs))
|
||||
|
||||
addDesc := func(d ocispec.Descriptor) {
|
||||
idx, ok := m[d.Digest]
|
||||
if ok {
|
||||
old := newDescs[idx]
|
||||
if old.MediaType == "" {
|
||||
old.MediaType = d.MediaType
|
||||
}
|
||||
if d.Platform != nil {
|
||||
old.Platform = d.Platform
|
||||
}
|
||||
if old.Annotations == nil {
|
||||
old.Annotations = map[string]string{}
|
||||
}
|
||||
for k, v := range d.Annotations {
|
||||
old.Annotations[k] = v
|
||||
}
|
||||
newDescs[idx] = old
|
||||
} else {
|
||||
m[d.Digest] = len(newDescs)
|
||||
newDescs = append(newDescs, d)
|
||||
}
|
||||
}
|
||||
|
||||
for i, desc := range descs {
|
||||
switch desc.MediaType {
|
||||
case images.MediaTypeDockerSchema2ManifestList, ocispec.MediaTypeImageIndex:
|
||||
var mfst ocispec.Index
|
||||
if err := json.Unmarshal(dts[i], &mfst); err != nil {
|
||||
return nil, ocispec.Descriptor{}, errors.WithStack(err)
|
||||
}
|
||||
for _, d := range mfst.Manifests {
|
||||
addDesc(d)
|
||||
}
|
||||
default:
|
||||
addDesc(desc)
|
||||
}
|
||||
}
|
||||
|
||||
mt := images.MediaTypeDockerSchema2ManifestList //ocispec.MediaTypeImageIndex
|
||||
idx := struct {
|
||||
// MediaType is reserved in the OCI spec but
|
||||
// excluded from go types.
|
||||
MediaType string `json:"mediaType,omitempty"`
|
||||
|
||||
ocispec.Index
|
||||
}{
|
||||
MediaType: mt,
|
||||
Index: ocispec.Index{
|
||||
Versioned: specs.Versioned{
|
||||
SchemaVersion: 2,
|
||||
},
|
||||
Manifests: newDescs,
|
||||
},
|
||||
}
|
||||
|
||||
idxBytes, err := json.MarshalIndent(idx, "", " ")
|
||||
if err != nil {
|
||||
return nil, ocispec.Descriptor{}, errors.Wrap(err, "failed to marshal index")
|
||||
}
|
||||
|
||||
return idxBytes, ocispec.Descriptor{
|
||||
MediaType: mt,
|
||||
Size: int64(len(idxBytes)),
|
||||
Digest: digest.FromBytes(idxBytes),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *Resolver) loadConfig(ctx context.Context, in string, dt []byte) (*ocispec.Image, error) {
|
||||
var manifest ocispec.Manifest
|
||||
if err := json.Unmarshal(dt, &manifest); err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
dt, err := r.GetDescriptor(ctx, in, manifest.Config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var img ocispec.Image
|
||||
if err := json.Unmarshal(dt, &img); err != nil {
|
||||
return nil, errors.WithStack(err)
|
||||
}
|
||||
|
||||
return &img, nil
|
||||
}
|
||||
|
||||
func detectMediaType(dt []byte) (string, error) {
|
||||
var mfst struct {
|
||||
MediaType string `json:"mediaType"`
|
||||
Config json.RawMessage `json:"config"`
|
||||
FSLayers []string `json:"fsLayers"`
|
||||
}
|
||||
|
||||
if err := json.Unmarshal(dt, &mfst); err != nil {
|
||||
return "", errors.WithStack(err)
|
||||
}
|
||||
|
||||
if mfst.MediaType != "" {
|
||||
return mfst.MediaType, nil
|
||||
}
|
||||
if mfst.Config != nil {
|
||||
return images.MediaTypeDockerSchema2Manifest, nil
|
||||
}
|
||||
if len(mfst.FSLayers) > 0 {
|
||||
return images.MediaTypeDockerSchema1Manifest, nil
|
||||
}
|
||||
|
||||
return images.MediaTypeDockerSchema2ManifestList, nil
|
||||
}
|
Loading…
Reference in New Issue