mirror of
				https://gitea.com/Lydanne/buildx.git
				synced 2025-10-31 16:13:45 +08:00 
			
		
		
		
	imagetools: add create support
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
This commit is contained in:
		
							
								
								
									
										186
									
								
								util/imagetools/create.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										186
									
								
								util/imagetools/create.go
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| } | ||||
| @@ -35,35 +35,52 @@ func New(opt Opt) *Resolver { | ||||
| 	} | ||||
| } | ||||
|  | ||||
| func (r *Resolver) Get(ctx context.Context, in string) ([]byte, ocispec.Descriptor, error) { | ||||
| func (r *Resolver) Resolve(ctx context.Context, in string) (string, ocispec.Descriptor, error) { | ||||
| 	ref, err := parseRef(in) | ||||
| 	if err != nil { | ||||
| 		return nil, ocispec.Descriptor{}, err | ||||
| 		return "", ocispec.Descriptor{}, err | ||||
| 	} | ||||
|  | ||||
| 	in, desc, err := r.r.Resolve(ctx, ref.String()) | ||||
| 	if err != nil { | ||||
| 		return "", ocispec.Descriptor{}, err | ||||
| 	} | ||||
|  | ||||
| 	return in, desc, nil | ||||
| } | ||||
|  | ||||
| func (r *Resolver) Get(ctx context.Context, in string) ([]byte, ocispec.Descriptor, error) { | ||||
| 	in, desc, err := r.Resolve(ctx, in) | ||||
| 	if err != nil { | ||||
| 		return nil, ocispec.Descriptor{}, err | ||||
| 	} | ||||
|  | ||||
| 	fetcher, err := r.r.Fetcher(ctx, in) | ||||
| 	dt, err := r.GetDescriptor(ctx, in, desc) | ||||
| 	if err != nil { | ||||
| 		return nil, ocispec.Descriptor{}, err | ||||
| 	} | ||||
| 	return dt, desc, nil | ||||
| } | ||||
|  | ||||
| func (r *Resolver) GetDescriptor(ctx context.Context, in string, desc ocispec.Descriptor) ([]byte, error) { | ||||
| 	fetcher, err := r.r.Fetcher(ctx, in) | ||||
| 	if err != nil { | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	rc, err := fetcher.Fetch(ctx, desc) | ||||
| 	if err != nil { | ||||
| 		return nil, ocispec.Descriptor{}, err | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	buf := &bytes.Buffer{} | ||||
| 	_, err = io.Copy(buf, rc) | ||||
| 	rc.Close() | ||||
| 	if err != nil { | ||||
| 		return nil, ocispec.Descriptor{}, err | ||||
| 		return nil, err | ||||
| 	} | ||||
|  | ||||
| 	return buf.Bytes(), desc, nil | ||||
| 	return buf.Bytes(), nil | ||||
| } | ||||
|  | ||||
| func parseRef(s string) (reference.Named, error) { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Tonis Tiigi
					Tonis Tiigi