Skip to content

Commit ba5b443

Browse files
committed
Allow optimizing images with prioritiezed files info shared via registry
Signed-off-by: Kohei Tokunaga <ktokunaga.mail@gmail.com>
1 parent cf57861 commit ba5b443

File tree

13 files changed

+909
-84
lines changed

13 files changed

+909
-84
lines changed

analyzer/analyzer.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ func Analyze(ctx context.Context, client *containerd.Client, ref string, opts ..
188188
})
189189

190190
// Start to monitor "/" and run the task.
191-
rc, err := recorder.NewImageRecorder(ctx, cs, img, platforms.Default())
191+
rc, err := recorder.NewImageRecorder(ctx, cs, img, platforms.DefaultStrict())
192192
if err != nil {
193193
return "", err
194194
}

analyzer/recorder/images.go

+237
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
package recorder
2+
3+
import (
4+
"archive/tar"
5+
"compress/gzip"
6+
"context"
7+
"encoding/json"
8+
"fmt"
9+
"io"
10+
11+
"github.com/containerd/containerd"
12+
"github.com/containerd/containerd/archive/compression"
13+
"github.com/containerd/containerd/content"
14+
"github.com/containerd/containerd/errdefs"
15+
"github.com/containerd/containerd/images"
16+
"github.com/containerd/containerd/labels"
17+
"github.com/containerd/containerd/platforms"
18+
"github.com/containerd/stargz-snapshotter/util/containerdutil"
19+
"github.com/opencontainers/go-digest"
20+
ocispecVersion "github.com/opencontainers/image-spec/specs-go"
21+
ocispec "github.com/opencontainers/image-spec/specs-go/v1"
22+
)
23+
24+
const recordJSON = "stargz.record.json"
25+
26+
// RecordOutToImage writes the specified record out blob as an image.
27+
func RecordOutToImage(ctx context.Context, client *containerd.Client, recordOutDgst digest.Digest, ref string) (*images.Image, error) {
28+
cs := client.ContentStore()
29+
is := client.ImageService()
30+
31+
// Write blob
32+
ra, err := cs.ReaderAt(ctx, ocispec.Descriptor{Digest: recordOutDgst})
33+
if err != nil {
34+
return nil, err
35+
}
36+
defer ra.Close()
37+
recordSize := ra.Size()
38+
sr := io.NewSectionReader(ra, 0, recordSize)
39+
blobW, err := content.OpenWriter(ctx, cs, content.WithRef(fmt.Sprintf("recording-ref-%s", recordOutDgst)))
40+
if err != nil {
41+
return nil, err
42+
}
43+
defer blobW.Close()
44+
if err := blobW.Truncate(0); err != nil {
45+
return nil, err
46+
}
47+
zw := gzip.NewWriter(blobW)
48+
defer zw.Close()
49+
diffID := digest.Canonical.Digester()
50+
tw := tar.NewWriter(io.MultiWriter(zw, diffID.Hash()))
51+
if err := tw.WriteHeader(&tar.Header{
52+
Name: recordJSON,
53+
Typeflag: tar.TypeReg,
54+
Size: recordSize,
55+
}); err != nil {
56+
return nil, err
57+
}
58+
if _, err := io.CopyN(tw, sr, recordSize); err != nil {
59+
return nil, err
60+
}
61+
if err := tw.Close(); err != nil {
62+
return nil, err
63+
}
64+
if err := zw.Close(); err != nil {
65+
return nil, err
66+
}
67+
blobLabels := map[string]string{
68+
labels.LabelUncompressed: diffID.Digest().String(),
69+
}
70+
if err := blobW.Commit(ctx, 0, "", content.WithLabels(blobLabels)); err != nil && !errdefs.IsAlreadyExists(err) {
71+
return nil, err
72+
}
73+
blobInfo, err := cs.Info(ctx, blobW.Digest())
74+
if err != nil {
75+
return nil, err
76+
}
77+
blobDesc := ocispec.Descriptor{
78+
MediaType: ocispec.MediaTypeImageLayerGzip,
79+
Digest: blobInfo.Digest,
80+
Size: blobInfo.Size,
81+
}
82+
if err := blobW.Close(); err != nil {
83+
return nil, err
84+
}
85+
86+
// Write config
87+
configW, err := content.OpenWriter(ctx, cs, content.WithRef(fmt.Sprintf("recording-ref-config-%s", recordOutDgst)))
88+
if err != nil {
89+
return nil, err
90+
}
91+
defer configW.Close()
92+
if err := json.NewEncoder(configW).Encode(ocispec.Image{
93+
Architecture: platforms.DefaultSpec().Architecture,
94+
OS: platforms.DefaultSpec().OS,
95+
RootFS: ocispec.RootFS{
96+
Type: "layers",
97+
DiffIDs: []digest.Digest{diffID.Digest()},
98+
},
99+
}); err != nil {
100+
return nil, err
101+
}
102+
if err := configW.Commit(ctx, 0, ""); err != nil && !errdefs.IsAlreadyExists(err) {
103+
return nil, err
104+
}
105+
configInfo, err := cs.Info(ctx, configW.Digest())
106+
if err != nil {
107+
return nil, err
108+
}
109+
configDesc := ocispec.Descriptor{
110+
MediaType: ocispec.MediaTypeImageConfig,
111+
Digest: configInfo.Digest,
112+
Size: configInfo.Size,
113+
}
114+
if err := configW.Close(); err != nil {
115+
return nil, err
116+
}
117+
118+
// Write manifest
119+
manifestW, err := content.OpenWriter(ctx, cs, content.WithRef(fmt.Sprintf("recording-ref-manifest-%s", recordOutDgst)))
120+
if err != nil {
121+
return nil, err
122+
}
123+
defer manifestW.Close()
124+
if err := json.NewEncoder(manifestW).Encode(ocispec.Manifest{
125+
Versioned: ocispecVersion.Versioned{
126+
SchemaVersion: 2,
127+
},
128+
MediaType: ocispec.MediaTypeImageManifest,
129+
Config: configDesc,
130+
Layers: []ocispec.Descriptor{blobDesc},
131+
}); err != nil {
132+
return nil, err
133+
}
134+
if err := manifestW.Commit(ctx, 0, "", content.WithLabels(map[string]string{
135+
"containerd.io/gc.ref.content.record.config": configDesc.Digest.String(),
136+
"containerd.io/gc.ref.content.record.blob": blobDesc.Digest.String(),
137+
})); err != nil && !errdefs.IsAlreadyExists(err) {
138+
return nil, err
139+
}
140+
manifestInfo, err := cs.Info(ctx, manifestW.Digest())
141+
if err != nil {
142+
return nil, err
143+
}
144+
manifestDesc := ocispec.Descriptor{
145+
MediaType: ocispec.MediaTypeImageManifest,
146+
Digest: manifestInfo.Digest,
147+
Size: manifestInfo.Size,
148+
}
149+
if err := manifestW.Close(); err != nil {
150+
return nil, err
151+
}
152+
153+
// Write image
154+
_ = is.Delete(ctx, ref)
155+
res, err := is.Create(ctx, images.Image{
156+
Name: ref,
157+
Target: manifestDesc,
158+
})
159+
return &res, err
160+
}
161+
162+
// RecordInFromImage gets a record out file from the specified image.
163+
func RecordInFromImage(ctx context.Context, client *containerd.Client, ref string, platform platforms.MatchComparer) (digest.Digest, error) {
164+
is := client.ImageService()
165+
cs := client.ContentStore()
166+
167+
i, err := is.Get(ctx, ref)
168+
if err != nil {
169+
return "", err
170+
}
171+
172+
manifestDesc, err := containerdutil.ManifestDesc(ctx, cs, i.Target, platform)
173+
if err != nil {
174+
return "", err
175+
}
176+
p, err := content.ReadBlob(ctx, cs, manifestDesc)
177+
if err != nil {
178+
return "", err
179+
}
180+
var manifest ocispec.Manifest
181+
if err := json.Unmarshal(p, &manifest); err != nil {
182+
return "", err
183+
}
184+
if len(manifest.Layers) != 1 {
185+
return "", fmt.Errorf("record image must have 1 layer")
186+
}
187+
recordOut := manifest.Layers[0]
188+
189+
ra, err := cs.ReaderAt(ctx, recordOut)
190+
if err != nil {
191+
return "", err
192+
}
193+
defer ra.Close()
194+
dr, err := compression.DecompressStream(io.NewSectionReader(ra, 0, ra.Size()))
195+
if err != nil {
196+
return "", err
197+
}
198+
var recordOutR io.Reader
199+
var recordOutSize int64
200+
tr := tar.NewReader(dr)
201+
for {
202+
h, err := tr.Next()
203+
if err != nil {
204+
if err == io.EOF {
205+
break
206+
} else {
207+
return "", err
208+
}
209+
}
210+
if cleanEntryName(h.Name) == recordJSON {
211+
recordOutR, recordOutSize = tr, h.Size
212+
break
213+
}
214+
}
215+
if recordOutR == nil {
216+
return "", fmt.Errorf("failed to find record file")
217+
}
218+
recordW, err := content.OpenWriter(ctx, cs, content.WithRef(fmt.Sprintf("recording-in-ref-%s", manifestDesc.Digest)))
219+
if err != nil {
220+
return "", err
221+
}
222+
defer recordW.Close()
223+
if err := recordW.Truncate(0); err != nil {
224+
return "", err
225+
}
226+
if _, err := io.CopyN(recordW, recordOutR, recordOutSize); err != nil {
227+
return "", err
228+
}
229+
if err := recordW.Commit(ctx, 0, ""); err != nil && !errdefs.IsAlreadyExists(err) {
230+
return "", err
231+
}
232+
dgst := recordW.Digest()
233+
if err := recordW.Close(); err != nil {
234+
return "", err
235+
}
236+
return dgst, nil
237+
}

analyzer/recorder/recorder.go

-2
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@ import (
3131
"github.com/containerd/containerd/errdefs"
3232
"github.com/containerd/containerd/images"
3333
"github.com/containerd/containerd/images/converter/uncompress"
34-
"github.com/containerd/containerd/log"
3534
"github.com/containerd/containerd/platforms"
3635
"github.com/containerd/stargz-snapshotter/recorder"
3736
"github.com/containerd/stargz-snapshotter/util/containerdutil"
@@ -84,7 +83,6 @@ func imageRecorderFromManifest(ctx context.Context, cs content.Store, manifestDe
8483
// TODO: During optimization, we uncompress the blob several times (here and during
8584
// creating eStargz layer). We should unify this process for better optimization
8685
// performance.
87-
log.G(ctx).Infof("analyzing blob %q", desc.Digest)
8886
readerAt, err := cs.ReaderAt(ctx, desc)
8987
if err != nil {
9088
return nil, fmt.Errorf("failed to get reader blob %v: %w", desc.Digest, err)

0 commit comments

Comments
 (0)