Skip to content

Commit 2b54510

Browse files
authored
fix: consider base image media type when appending layers (#1437)
* fix: consider base image media type when appending layers * fix docs * fix unit test * adress changes
1 parent 7196cf3 commit 2b54510

File tree

7 files changed

+128
-11
lines changed

7 files changed

+128
-11
lines changed

cmd/crane/cmd/append.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
v1 "github.com/google/go-containerregistry/pkg/v1"
2424
"github.com/google/go-containerregistry/pkg/v1/empty"
2525
"github.com/google/go-containerregistry/pkg/v1/mutate"
26+
"github.com/google/go-containerregistry/pkg/v1/types"
2627
specsv1 "github.com/opencontainers/image-spec/specs-go/v1"
2728
"github.com/spf13/cobra"
2829
)
@@ -31,7 +32,7 @@ import (
3132
func NewCmdAppend(options *[]crane.Option) *cobra.Command {
3233
var baseRef, newTag, outFile string
3334
var newLayers []string
34-
var annotate bool
35+
var annotate, ociEmptyBase bool
3536

3637
appendCmd := &cobra.Command{
3738
Use: "append",
@@ -51,6 +52,10 @@ container image.`,
5152
if baseRef == "" {
5253
logs.Warn.Printf("base unspecified, using empty image")
5354
base = empty.Image
55+
if ociEmptyBase {
56+
base = mutate.MediaType(base, types.OCIManifestSchema1)
57+
base = mutate.ConfigMediaType(base, types.OCIConfigJSON)
58+
}
5459
} else {
5560
base, err = crane.Pull(baseRef, *options...)
5661
if err != nil {
@@ -108,7 +113,9 @@ container image.`,
108113
appendCmd.Flags().StringSliceVarP(&newLayers, "new_layer", "f", []string{}, "Path to tarball to append to image")
109114
appendCmd.Flags().StringVarP(&outFile, "output", "o", "", "Path to new tarball of resulting image")
110115
appendCmd.Flags().BoolVar(&annotate, "set-base-image-annotations", false, "If true, annotate the resulting image as being based on the base image")
116+
appendCmd.Flags().BoolVar(&ociEmptyBase, "oci-empty-base", false, "If true, empty base image will have OCI media types instead of Docker")
111117

118+
appendCmd.MarkFlagsMutuallyExclusive("oci-empty-base", "base")
112119
appendCmd.MarkFlagRequired("new_tag")
113120
appendCmd.MarkFlagRequired("new_layer")
114121
return appendCmd

cmd/crane/doc/crane_append.md

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pkg/crane/append.go

Lines changed: 21 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,10 @@ import (
2323
"github.com/google/go-containerregistry/pkg/v1/mutate"
2424
"github.com/google/go-containerregistry/pkg/v1/stream"
2525
"github.com/google/go-containerregistry/pkg/v1/tarball"
26+
"github.com/google/go-containerregistry/pkg/v1/types"
2627
)
2728

2829
func isWindows(img v1.Image) (bool, error) {
29-
if img == nil {
30-
return false, nil
31-
}
3230
cfg, err := img.ConfigFile()
3331
if err != nil {
3432
return false, err
@@ -42,14 +40,30 @@ func isWindows(img v1.Image) (bool, error) {
4240
// "windows"), the contents of the tarballs will be modified to be suitable for
4341
// a Windows container image.`,
4442
func Append(base v1.Image, paths ...string) (v1.Image, error) {
43+
if base == nil {
44+
return nil, fmt.Errorf("invalid argument: base")
45+
}
46+
4547
win, err := isWindows(base)
4648
if err != nil {
4749
return nil, fmt.Errorf("getting base image: %w", err)
4850
}
4951

52+
baseMediaType, err := base.MediaType()
53+
54+
if err != nil {
55+
return nil, fmt.Errorf("getting base image media type: %w", err)
56+
}
57+
58+
layerType := types.DockerLayer
59+
60+
if baseMediaType == types.OCIManifestSchema1 {
61+
layerType = types.OCILayer
62+
}
63+
5064
layers := make([]v1.Layer, 0, len(paths))
5165
for _, path := range paths {
52-
layer, err := getLayer(path)
66+
layer, err := getLayer(path, layerType)
5367
if err != nil {
5468
return nil, fmt.Errorf("reading layer %q: %w", path, err)
5569
}
@@ -67,16 +81,16 @@ func Append(base v1.Image, paths ...string) (v1.Image, error) {
6781
return mutate.AppendLayers(base, layers...)
6882
}
6983

70-
func getLayer(path string) (v1.Layer, error) {
84+
func getLayer(path string, layerType types.MediaType) (v1.Layer, error) {
7185
f, err := streamFile(path)
7286
if err != nil {
7387
return nil, err
7488
}
7589
if f != nil {
76-
return stream.NewLayer(f), nil
90+
return stream.NewLayer(f, stream.WithMediaType(layerType)), nil
7791
}
7892

79-
return tarball.LayerFromFile(path)
93+
return tarball.LayerFromFile(path, tarball.WithMediaType(layerType))
8094
}
8195

8296
// If we're dealing with a named pipe, trying to open it multiple times will

pkg/crane/append_test.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Copyright 2022 Google LLC All Rights Reserved.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package crane_test
16+
17+
import (
18+
"testing"
19+
20+
"github.com/google/go-containerregistry/pkg/crane"
21+
"github.com/google/go-containerregistry/pkg/v1/empty"
22+
"github.com/google/go-containerregistry/pkg/v1/mutate"
23+
"github.com/google/go-containerregistry/pkg/v1/types"
24+
)
25+
26+
func TestAppendWithOCIBaseImage(t *testing.T) {
27+
base := mutate.MediaType(empty.Image, types.OCIManifestSchema1)
28+
img, err := crane.Append(base, "testdata/content.tar")
29+
30+
if err != nil {
31+
t.Fatalf("crane.Append(): %v", err)
32+
}
33+
34+
layers, err := img.Layers()
35+
36+
if err != nil {
37+
t.Fatalf("img.Layers(): %v", err)
38+
}
39+
40+
mediaType, err := layers[0].MediaType()
41+
42+
if err != nil {
43+
t.Fatalf("layers[0].MediaType(): %v", err)
44+
}
45+
46+
if got, want := mediaType, types.OCILayer; got != want {
47+
t.Errorf("MediaType(): want %q, got %q", want, got)
48+
}
49+
}
50+
51+
func TestAppendWithDockerBaseImage(t *testing.T) {
52+
img, err := crane.Append(empty.Image, "testdata/content.tar")
53+
54+
if err != nil {
55+
t.Fatalf("crane.Append(): %v", err)
56+
}
57+
58+
layers, err := img.Layers()
59+
60+
if err != nil {
61+
t.Fatalf("img.Layers(): %v", err)
62+
}
63+
64+
mediaType, err := layers[0].MediaType()
65+
66+
if err != nil {
67+
t.Fatalf("layers[0].MediaType(): %v", err)
68+
}
69+
70+
if got, want := mediaType, types.DockerLayer; got != want {
71+
t.Errorf("MediaType(): want %q, got %q", want, got)
72+
}
73+
}

pkg/crane/testdata/content.tar

10 KB
Binary file not shown.

pkg/v1/stream/layer.go

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ type Layer struct {
4848
mu sync.Mutex
4949
digest, diffID *v1.Hash
5050
size int64
51+
mediaType types.MediaType
5152
}
5253

5354
var _ v1.Layer = (*Layer)(nil)
@@ -62,11 +63,21 @@ func WithCompressionLevel(level int) LayerOption {
6263
}
6364
}
6465

66+
// WithMediaType is a functional option for overriding the layer's media type.
67+
func WithMediaType(mt types.MediaType) LayerOption {
68+
return func(l *Layer) {
69+
l.mediaType = mt
70+
}
71+
}
72+
6573
// NewLayer creates a Layer from an io.ReadCloser.
6674
func NewLayer(rc io.ReadCloser, opts ...LayerOption) *Layer {
6775
layer := &Layer{
6876
blob: rc,
6977
compression: gzip.BestSpeed,
78+
// We use DockerLayer for now as uncompressed layers
79+
// are unimplemented
80+
mediaType: types.DockerLayer,
7081
}
7182

7283
for _, opt := range opts {
@@ -108,9 +119,7 @@ func (l *Layer) Size() (int64, error) {
108119

109120
// MediaType implements v1.Layer
110121
func (l *Layer) MediaType() (types.MediaType, error) {
111-
// We return DockerLayer for now as uncompressed layers
112-
// are unimplemented
113-
return types.DockerLayer, nil
122+
return l.mediaType, nil
114123
}
115124

116125
// Uncompressed implements v1.Layer.

pkg/v1/stream/layer_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,3 +284,16 @@ func TestMediaType(t *testing.T) {
284284
t.Errorf("MediaType(): want %q, got %q", want, got)
285285
}
286286
}
287+
288+
func TestMediaTypeOption(t *testing.T) {
289+
l := NewLayer(ioutil.NopCloser(strings.NewReader("hello")), WithMediaType(types.OCILayer))
290+
mediaType, err := l.MediaType()
291+
292+
if err != nil {
293+
t.Fatalf("MediaType(): %v", err)
294+
}
295+
296+
if got, want := mediaType, types.OCILayer; got != want {
297+
t.Errorf("MediaType(): want %q, got %q", want, got)
298+
}
299+
}

0 commit comments

Comments
 (0)