Little helper to run CNCF's k3s in Docker
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
k3d/vendor/github.com/google/go-containerregistry/pkg/crane/optimize.go

237 lines
6.4 KiB

// Copyright 2020 Google LLC All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package crane
import (
"errors"
"fmt"
"github.com/containerd/stargz-snapshotter/estargz"
"github.com/google/go-containerregistry/pkg/logs"
"github.com/google/go-containerregistry/pkg/name"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/google/go-containerregistry/pkg/v1/empty"
"github.com/google/go-containerregistry/pkg/v1/mutate"
"github.com/google/go-containerregistry/pkg/v1/remote"
"github.com/google/go-containerregistry/pkg/v1/tarball"
"github.com/google/go-containerregistry/pkg/v1/types"
)
// Optimize optimizes a remote image or index from src to dst.
// THIS API IS EXPERIMENTAL AND SUBJECT TO CHANGE WITHOUT WARNING.
func Optimize(src, dst string, prioritize []string, opt ...Option) error {
pset := newStringSet(prioritize)
o := makeOptions(opt...)
srcRef, err := name.ParseReference(src, o.Name...)
if err != nil {
return fmt.Errorf("parsing reference %q: %w", src, err)
}
dstRef, err := name.ParseReference(dst, o.Name...)
if err != nil {
return fmt.Errorf("parsing reference for %q: %w", dst, err)
}
logs.Progress.Printf("Optimizing from %v to %v", srcRef, dstRef)
desc, err := remote.Get(srcRef, o.Remote...)
if err != nil {
return fmt.Errorf("fetching %q: %w", src, err)
}
switch desc.MediaType {
case types.OCIImageIndex, types.DockerManifestList:
// Handle indexes separately.
if o.Platform != nil {
// If platform is explicitly set, don't optimize the whole index, just the appropriate image.
if err := optimizeAndPushImage(desc, dstRef, pset, o); err != nil {
return fmt.Errorf("failed to optimize image: %w", err)
}
} else {
if err := optimizeAndPushIndex(desc, dstRef, pset, o); err != nil {
return fmt.Errorf("failed to optimize index: %w", err)
}
}
case types.DockerManifestSchema1, types.DockerManifestSchema1Signed:
return errors.New("docker schema 1 images are not supported")
default:
// Assume anything else is an image, since some registries don't set mediaTypes properly.
if err := optimizeAndPushImage(desc, dstRef, pset, o); err != nil {
return fmt.Errorf("failed to optimize image: %w", err)
}
}
return nil
}
func optimizeAndPushImage(desc *remote.Descriptor, dstRef name.Reference, prioritize stringSet, o Options) error {
img, err := desc.Image()
if err != nil {
return err
}
missing, oimg, err := optimizeImage(img, prioritize)
if err != nil {
return err
}
if len(missing) > 0 {
return fmt.Errorf("the following prioritized files were missing from image: %v", missing.List())
}
return remote.Write(dstRef, oimg, o.Remote...)
}
func optimizeImage(img v1.Image, prioritize stringSet) (stringSet, v1.Image, error) {
cfg, err := img.ConfigFile()
if err != nil {
return nil, nil, err
}
ocfg := cfg.DeepCopy()
ocfg.History = nil
ocfg.RootFS.DiffIDs = nil
oimg, err := mutate.ConfigFile(empty.Image, ocfg)
if err != nil {
return nil, nil, err
}
layers, err := img.Layers()
if err != nil {
return nil, nil, err
}
missingFromImage := newStringSet(prioritize.List())
olayers := make([]mutate.Addendum, 0, len(layers))
for _, layer := range layers {
missingFromLayer := []string{}
olayer, err := tarball.LayerFromOpener(layer.Uncompressed,
tarball.WithEstargz,
tarball.WithEstargzOptions(
estargz.WithPrioritizedFiles(prioritize.List()),
estargz.WithAllowPrioritizeNotFound(&missingFromLayer),
))
if err != nil {
return nil, nil, err
}
missingFromImage = missingFromImage.Intersection(newStringSet(missingFromLayer))
olayers = append(olayers, mutate.Addendum{
Layer: olayer,
MediaType: types.DockerLayer,
})
}
oimg, err = mutate.Append(oimg, olayers...)
if err != nil {
return nil, nil, err
}
return missingFromImage, oimg, nil
}
func optimizeAndPushIndex(desc *remote.Descriptor, dstRef name.Reference, prioritize stringSet, o Options) error {
idx, err := desc.ImageIndex()
if err != nil {
return err
}
missing, oidx, err := optimizeIndex(idx, prioritize)
if err != nil {
return err
}
if len(missing) > 0 {
return fmt.Errorf("the following prioritized files were missing from all images: %v", missing.List())
}
return remote.WriteIndex(dstRef, oidx, o.Remote...)
}
func optimizeIndex(idx v1.ImageIndex, prioritize stringSet) (stringSet, v1.ImageIndex, error) {
im, err := idx.IndexManifest()
if err != nil {
return nil, nil, err
}
missingFromIndex := newStringSet(prioritize.List())
// Build an image for each child from the base and append it to a new index to produce the result.
adds := make([]mutate.IndexAddendum, 0, len(im.Manifests))
for _, desc := range im.Manifests {
img, err := idx.Image(desc.Digest)
if err != nil {
return nil, nil, err
}
missingFromImage, oimg, err := optimizeImage(img, prioritize)
if err != nil {
return nil, nil, err
}
missingFromIndex = missingFromIndex.Intersection(missingFromImage)
adds = append(adds, mutate.IndexAddendum{
Add: oimg,
Descriptor: v1.Descriptor{
URLs: desc.URLs,
MediaType: desc.MediaType,
Annotations: desc.Annotations,
Platform: desc.Platform,
},
})
}
idxType, err := idx.MediaType()
if err != nil {
return nil, nil, err
}
return missingFromIndex, mutate.IndexMediaType(mutate.AppendManifests(empty.Index, adds...), idxType), nil
}
type stringSet map[string]struct{}
func newStringSet(in []string) stringSet {
ss := stringSet{}
for _, s := range in {
ss[s] = struct{}{}
}
return ss
}
func (s stringSet) List() []string {
result := make([]string, 0, len(s))
for k := range s {
result = append(result, k)
}
return result
}
func (s stringSet) Intersection(rhs stringSet) stringSet {
// To appease ST1016
lhs := s
// Make sure len(lhs) >= len(rhs)
if len(lhs) < len(rhs) {
return rhs.Intersection(lhs)
}
result := stringSet{}
for k := range lhs {
if _, ok := rhs[k]; ok {
result[k] = struct{}{}
}
}
return result
}