Skip to content

Commit

Permalink
Start to flesh out crane optimize.
Browse files Browse the repository at this point in the history
This is a hidden command, which roundtrips a remote image to a target image through `tarball.LayerFromOpener(layer.Uncompressed)`.

Right now this does nothing to force estargz (still need `GGCR_EXPERIMENT_ESTARGZ=1`) or prioritize files (need `estargz.WithPrioritizedFiles(foo)`), but want to start the convo.

Fixes: #878
  • Loading branch information
mattmoor committed Dec 19, 2020
1 parent 8b4c3b5 commit b6faed6
Show file tree
Hide file tree
Showing 3 changed files with 216 additions and 0 deletions.
39 changes: 39 additions & 0 deletions cmd/crane/cmd/optimize.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright 2018 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 cmd

import (
"log"

"github.com/google/go-containerregistry/pkg/crane"
"github.com/spf13/cobra"
)

// NewCmdOptimize creates a new cobra.Command for the optimize subcommand.
func NewCmdOptimize(options *[]crane.Option) *cobra.Command {
return &cobra.Command{
Use: "optimize SRC DST",
Hidden: true,
Aliases: []string{"opt"},
Short: "Optimize a remote container image from src to dst",
Args: cobra.ExactArgs(2),
Run: func(_ *cobra.Command, args []string) {
src, dst := args[0], args[1]
if err := crane.Optimize(src, dst, *options...); err != nil {
log.Fatal(err)
}
},
}
}
1 change: 1 addition & 0 deletions cmd/crane/cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ func New(use, short string, options []crane.Option) *cobra.Command {
NewCmdExport(&options),
NewCmdList(&options),
NewCmdManifest(&options),
NewCmdOptimize(&options),
NewCmdPull(&options),
NewCmdPush(&options),
NewCmdRebase(&options),
Expand Down
176 changes: 176 additions & 0 deletions pkg/crane/optimize.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
// Copyright 2018 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/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.
func Optimize(src, dst string, opt ...Option) error {
o := makeOptions(opt...)
srcRef, err := name.ParseReference(src, o.name...)
if err != nil {
return fmt.Errorf("parsing reference %q: %v", src, err)
}

dstRef, err := name.ParseReference(dst, o.name...)
if err != nil {
return fmt.Errorf("parsing reference for %q: %v", 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: %v", 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, o); err != nil {
return fmt.Errorf("failed to optimize image: %v", err)
}
} else {
if err := optimizeAndPushIndex(desc, dstRef, o); err != nil {
return fmt.Errorf("failed to optimize index: %v", 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, o); err != nil {
return fmt.Errorf("failed to optimize image: %v", err)
}
}

return nil
}

func optimizeAndPushImage(desc *remote.Descriptor, dstRef name.Reference, o options) error {
img, err := desc.Image()
if err != nil {
return err
}

oimg, err := optimizeImage(img, o)
if err != nil {
return err
}

return remote.Write(dstRef, oimg, o.remote...)
}

func optimizeImage(img v1.Image, o options) (v1.Image, error) {
cfg, err := img.ConfigFile()
if err != nil {
return nil, err
}
ocfg := cfg.DeepCopy()
ocfg.History = nil
ocfg.RootFS.DiffIDs = nil

oimg, err := mutate.ConfigFile(empty.Image, ocfg)
if err != nil {
return nil, err
}

layers, err := img.Layers()
if err != nil {
return nil, err
}

olayers := make([]mutate.Addendum, 0, len(layers))
for i, layer := range layers {
olayer, err := tarball.LayerFromOpener(layer.Uncompressed)
if err != nil {
return nil, err
}

olayers = append(olayers, mutate.Addendum{
Layer: olayer,
History: cfg.History[i],
MediaType: types.DockerLayer,
})
}

return mutate.Append(oimg, olayers...)
}

func optimizeAndPushIndex(desc *remote.Descriptor, dstRef name.Reference, o options) error {
idx, err := desc.ImageIndex()
if err != nil {
return err
}

oidx, err := optimizeIndex(idx, o)
if err != nil {
return err
}

return remote.WriteIndex(dstRef, oidx, o.remote...)
}

func optimizeIndex(idx v1.ImageIndex, o options) (v1.ImageIndex, error) {
im, err := idx.IndexManifest()
if err != nil {
return nil, err
}

// 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, err
}

oimg, err := optimizeImage(img, o)
if err != nil {
return nil, err
}
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, err
}

return mutate.IndexMediaType(mutate.AppendManifests(empty.Index, adds...), idxType), nil
}

0 comments on commit b6faed6

Please sign in to comment.