Skip to content

Commit 5061cde

Browse files
committed
add tests
1 parent c0c6e7e commit 5061cde

File tree

4 files changed

+343
-25
lines changed

4 files changed

+343
-25
lines changed

pkg/v1/layout/pusher.go

Lines changed: 122 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,10 @@ import (
2525
v1 "github.com/google/go-containerregistry/pkg/v1"
2626
"github.com/google/go-containerregistry/pkg/v1/partial"
2727
"github.com/google/go-containerregistry/pkg/v1/remote"
28+
"github.com/google/go-containerregistry/pkg/v1/stream"
2829
"github.com/google/go-containerregistry/pkg/v1/types"
2930
specsv1 "github.com/opencontainers/image-spec/specs-go/v1"
31+
"golang.org/x/sync/errgroup"
3032
)
3133

3234
func taggableToManifest(t partial.WithRawManifest) (partial.Artifact, error) {
@@ -114,46 +116,142 @@ func (lp *pusher) writeLayer(l v1.Layer) error {
114116
return nil
115117
}
116118

117-
// Push implements remote.Pusher.
118-
func (lp *pusher) Push(_ context.Context, ref name.Reference, t partial.WithRawManifest) error {
119-
mf, err := taggableToManifest(t)
120-
if err != nil {
121-
return err
122-
}
123-
b, desc, err := unpackTaggable(t)
119+
func (lp *pusher) writeLayers(pctx context.Context, img v1.Image) error {
120+
ls, err := img.Layers()
124121
if err != nil {
125122
return err
126123
}
127-
if err := lp.path.WriteBlob(desc.Digest, io.NopCloser(bytes.NewBuffer(b))); err != nil {
128-
return err
124+
125+
g, _ := errgroup.WithContext(pctx)
126+
127+
for _, l := range ls {
128+
l := l
129+
130+
g.Go(func() error {
131+
return lp.writeLayer(l)
132+
})
129133
}
130134

131-
if img, ok := mf.(v1.Image); ok {
132-
cl, err := partial.ConfigLayer(img)
133-
if err != nil {
135+
cl, err := partial.ConfigLayer(img)
136+
if errors.Is(err, stream.ErrNotComputed) {
137+
if err := g.Wait(); err != nil {
134138
return err
135139
}
136-
dg, err := cl.Digest()
140+
141+
cl, err := partial.ConfigLayer(img)
137142
if err != nil {
138143
return err
139144
}
140-
rc, err := img.RawConfigFile()
141-
if err != nil {
145+
146+
return lp.writeLayer(cl)
147+
} else if err != nil {
148+
return err
149+
}
150+
151+
g.Go(func() error {
152+
return lp.writeLayer(cl)
153+
})
154+
155+
return g.Wait()
156+
}
157+
158+
func (lp *pusher) writeChildren(pctx context.Context, idx v1.ImageIndex) error {
159+
children, err := partial.Manifests(idx)
160+
if err != nil {
161+
return err
162+
}
163+
164+
g, ctx := errgroup.WithContext(pctx)
165+
166+
for _, child := range children {
167+
child := child
168+
if err := lp.writeChild(ctx, child, g); err != nil {
142169
return err
143170
}
144-
err = lp.path.WriteBlob(dg, io.NopCloser(bytes.NewBuffer(rc)))
145-
if err != nil {
171+
}
172+
173+
return g.Wait()
174+
}
175+
176+
func (lp *pusher) writeDeps(ctx context.Context, m partial.Artifact) error {
177+
if img, ok := m.(v1.Image); ok {
178+
return lp.writeLayers(ctx, img)
179+
}
180+
181+
if idx, ok := m.(v1.ImageIndex); ok {
182+
return lp.writeChildren(ctx, idx)
183+
}
184+
185+
// This has no deps, not an error (e.g. something you want to just PUT).
186+
return nil
187+
}
188+
189+
func (lp *pusher) writeManifest(ctx context.Context, t partial.WithRawManifest) error {
190+
m, err := taggableToManifest(t)
191+
if err != nil {
192+
return err
193+
}
194+
195+
needDeps := true
196+
197+
if errors.Is(err, stream.ErrNotComputed) {
198+
if err := lp.writeDeps(ctx, m); err != nil {
146199
return err
147200
}
148-
ls, err := img.Layers()
149-
if err != nil {
201+
needDeps = false
202+
} else if err != nil {
203+
return err
204+
}
205+
206+
if needDeps {
207+
if err := lp.writeDeps(ctx, m); err != nil {
150208
return err
151209
}
152-
for _, l := range ls {
153-
if err = lp.writeLayer(l); err != nil {
154-
return err
155-
}
156-
}
210+
}
211+
212+
b, desc, err := unpackTaggable(t)
213+
if err != nil {
214+
return err
215+
}
216+
if err := lp.path.WriteBlob(desc.Digest, io.NopCloser(bytes.NewBuffer(b))); err != nil {
217+
return err
218+
}
219+
220+
return nil
221+
}
222+
223+
func (lp *pusher) writeChild(ctx context.Context, child partial.Describable, g *errgroup.Group) error {
224+
switch child := child.(type) {
225+
case v1.ImageIndex:
226+
// For recursive index, we want to do a depth-first launching of goroutines
227+
// to avoid deadlocking.
228+
//
229+
// Note that this is rare, so the impact of this should be really small.
230+
return lp.writeManifest(ctx, child)
231+
case v1.Image:
232+
g.Go(func() error {
233+
return lp.writeManifest(ctx, child)
234+
})
235+
case v1.Layer:
236+
g.Go(func() error {
237+
return lp.writeLayer(child)
238+
})
239+
default:
240+
// This can't happen.
241+
return fmt.Errorf("encountered unknown child: %T", child)
242+
}
243+
return nil
244+
}
245+
246+
// Push implements remote.Pusher.
247+
func (lp *pusher) Push(ctx context.Context, ref name.Reference, t partial.WithRawManifest) error {
248+
err := lp.writeManifest(ctx, t)
249+
if err != nil {
250+
return err
251+
}
252+
_, desc, err := unpackTaggable(t)
253+
if err != nil {
254+
return err
157255
}
158256
desc.Annotations = map[string]string{
159257
specsv1.AnnotationRefName: ref.String(),

pkg/v1/layout/pusher_test.go

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
// Copyright 2019 The original author or authors
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 layout
16+
17+
import (
18+
"context"
19+
"fmt"
20+
"os"
21+
"testing"
22+
23+
"github.com/google/go-containerregistry/pkg/name"
24+
v1 "github.com/google/go-containerregistry/pkg/v1"
25+
"github.com/google/go-containerregistry/pkg/v1/random"
26+
)
27+
28+
func mustOCILayout(t *testing.T) Path {
29+
tmp := t.TempDir()
30+
os.WriteFile(fmt.Sprintf("%s/index.json", tmp), []byte("{}"), os.ModePerm)
31+
path, err := FromPath(tmp)
32+
if err != nil {
33+
t.Errorf("FromPath() = %v", err)
34+
}
35+
return path
36+
}
37+
38+
func mustHaveManifest(t *testing.T, p Path, ref string) {
39+
idx, err := p.ImageIndex()
40+
if err != nil {
41+
t.Errorf("path.ImageIndex() = %v", err)
42+
}
43+
manifest, err := idx.IndexManifest()
44+
if err != nil {
45+
t.Errorf("idx.IndexManifest() = %v", err)
46+
}
47+
if manifest.Manifests[0].Annotations["org.opencontainers.image.ref.name"] != ref {
48+
t.Errorf("image does not contain %s", ref)
49+
}
50+
}
51+
52+
func enumerateImageBlobs(t *testing.T, img v1.Image) []v1.Hash {
53+
mgd, err := img.Digest()
54+
if err != nil {
55+
t.Errorf("img.Digest() = %v", err)
56+
}
57+
cdg, err := img.ConfigName()
58+
if err != nil {
59+
t.Errorf("img.ConfigName() = %v", err)
60+
}
61+
blobs := []v1.Hash{
62+
mgd,
63+
cdg,
64+
}
65+
layers, err := img.Layers()
66+
if err != nil {
67+
t.Errorf("img.Layers() = %v", err)
68+
}
69+
for _, layer := range layers {
70+
ldg, err := layer.Digest()
71+
if err != nil {
72+
t.Errorf("layer.Digest() = %v", err)
73+
}
74+
blobs = append(blobs, ldg)
75+
}
76+
return blobs
77+
}
78+
79+
func enumerateImageIndexBlobs(t *testing.T, idx v1.ImageIndex) []v1.Hash {
80+
mgd, err := idx.Digest()
81+
if err != nil {
82+
t.Errorf("idx.Digest() = %v", err)
83+
}
84+
blobs := []v1.Hash{mgd}
85+
imf, err := idx.IndexManifest()
86+
if err != nil {
87+
t.Errorf("img.Layers() = %v", err)
88+
}
89+
for _, manifest := range imf.Manifests {
90+
im, err := idx.Image(manifest.Digest)
91+
if err != nil {
92+
t.Errorf("idx.Image = %v", err)
93+
}
94+
blobs = append(blobs, enumerateImageBlobs(t, im)...)
95+
}
96+
return blobs
97+
}
98+
99+
func mustHaveBlobs(t *testing.T, p Path, blobs []v1.Hash) {
100+
for _, blob := range blobs {
101+
if !p.BlobExists(blob) {
102+
t.Fatalf("blob %s/%s is missing", blob.Algorithm, blob.Hex)
103+
}
104+
}
105+
}
106+
107+
func TestCanPushRandomImage(t *testing.T) {
108+
img, err := random.Image(1024, 10)
109+
if err != nil {
110+
t.Fatalf("random.Image() = %v", err)
111+
}
112+
path := mustOCILayout(t)
113+
pusher := NewPusher(path)
114+
ref := name.MustParseReference("local.random/image:latest")
115+
err = pusher.Push(context.TODO(), ref, img)
116+
if err != nil {
117+
t.Errorf("pusher.Push() = %v", err)
118+
}
119+
mustHaveManifest(t, path, "local.random/image:latest")
120+
mustHaveBlobs(t, path, enumerateImageBlobs(t, img))
121+
}
122+
123+
func TestCanPushRandomImageIndex(t *testing.T) {
124+
idx, err := random.Index(1024, 3, 3)
125+
if err != nil {
126+
t.Fatalf("random.Index() = %v", err)
127+
}
128+
path := mustOCILayout(t)
129+
pusher := NewPusher(path)
130+
ref := name.MustParseReference("local.random/index:latest")
131+
err = pusher.Push(context.TODO(), ref, idx)
132+
if err != nil {
133+
t.Errorf("pusher.Push() = %v", err)
134+
}
135+
mustHaveManifest(t, path, "local.random/index:latest")
136+
mustHaveBlobs(t, path, enumerateImageIndexBlobs(t, idx))
137+
}
138+
139+
func TestCanPushImageIndex(t *testing.T) {
140+
lp, err := FromPath(testPath)
141+
if err != nil {
142+
t.Fatalf("FromPath() = %v", err)
143+
}
144+
img, err := lp.Image(manifestDigest)
145+
if err != nil {
146+
t.Fatalf("Image() = %v", err)
147+
}
148+
149+
path := mustOCILayout(t)
150+
pusher := NewPusher(path)
151+
ref := name.MustParseReference("local/index:latest")
152+
153+
err = pusher.Push(context.TODO(), ref, img)
154+
if err != nil {
155+
t.Errorf("pusher.Push() = %v", err)
156+
}
157+
mustHaveManifest(t, path, "local/index:latest")
158+
mustHaveBlobs(t, path, enumerateImageBlobs(t, img))
159+
}
160+
161+
func TestCanPushImage(t *testing.T) {
162+
lp, err := FromPath(testPath)
163+
if err != nil {
164+
t.Fatalf("FromPath() = %v", err)
165+
}
166+
img, err := lp.Image(manifestDigest)
167+
if err != nil {
168+
t.Fatalf("Image() = %v", err)
169+
}
170+
171+
path := mustOCILayout(t)
172+
pusher := NewPusher(path)
173+
ref := name.MustParseReference("local/index:latest")
174+
175+
err = pusher.Push(context.TODO(), ref, img)
176+
if err != nil {
177+
t.Errorf("pusher.Push() = %v", err)
178+
}
179+
mustHaveManifest(t, path, "local/index:latest")
180+
mustHaveBlobs(t, path, enumerateImageBlobs(t, img))
181+
}

pkg/v1/layout/write.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,7 +320,7 @@ func (l Path) RemoveBlob(hash v1.Hash) error {
320320

321321
// BlobExists checks a blob exists at blobs/{hash.Algorithm}/{hash.Hex}
322322
func (l Path) BlobExists(hash v1.Hash) bool {
323-
dir := l.path("blobs", hash.Algorithm)
323+
dir := l.path("blobs", hash.Algorithm, hash.Hex)
324324
_, err := os.Stat(dir)
325325
return !errors.Is(err, os.ErrNotExist)
326326
}

0 commit comments

Comments
 (0)