Skip to content
This repository was archived by the owner on Mar 27, 2024. It is now read-only.

Commit e57f9f2

Browse files
authored
Add a "MutableSource" type that allows for basic image modifications. (#180)
1 parent 9b34be4 commit e57f9f2

File tree

7 files changed

+390
-38
lines changed

7 files changed

+390
-38
lines changed

cmd/root.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ func getPrepperForImage(image string) (pkgutil.Prepper, error) {
172172
return nil, err
173173
}
174174

175-
src, err = cache.NewFileCache(cacheDir, ref, src)
175+
src, err = cache.NewFileCache(cacheDir, ref)
176176
if err != nil {
177177
return nil, err
178178
}

pkg/cache/BUILD.bazel

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ go_library(
66
importpath = "github.com/GoogleCloudPlatform/container-diff/pkg/cache",
77
visibility = ["//visibility:public"],
88
deps = [
9+
"//pkg/image:go_default_library",
910
"//vendor/github.com/containers/image/types:go_default_library",
10-
"//vendor/github.com/opencontainers/go-digest:go_default_library",
1111
"//vendor/github.com/sirupsen/logrus:go_default_library",
1212
],
1313
)

pkg/cache/file_cache.go

Lines changed: 11 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -17,32 +17,34 @@ limitations under the License.
1717
package cache
1818

1919
import (
20-
"context"
2120
"io"
2221
"io/ioutil"
2322
"os"
2423
"path/filepath"
2524

25+
"github.com/GoogleCloudPlatform/container-diff/pkg/image"
2626
"github.com/containers/image/types"
27-
digest "github.com/opencontainers/go-digest"
2827
"github.com/sirupsen/logrus"
2928
)
3029

3130
type FileCache struct {
3231
RootDir string
33-
Ref types.ImageReference
34-
src types.ImageSource
32+
*image.ProxySource
3533
}
3634

37-
func NewFileCache(dir string, ref types.ImageReference, src types.ImageSource) (*FileCache, error) {
35+
func NewFileCache(dir string, ref types.ImageReference) (*FileCache, error) {
3836
if err := os.MkdirAll(dir, 0700); err != nil {
3937
return nil, err
4038
}
4139

40+
ps, err := image.NewProxySource(ref)
41+
if err != nil {
42+
return nil, err
43+
}
44+
4245
return &FileCache{
43-
RootDir: dir,
44-
Ref: ref,
45-
src: src,
46+
RootDir: dir,
47+
ProxySource: ps,
4648
}, nil
4749
}
4850

@@ -80,24 +82,6 @@ func (c *FileCache) Invalidate(layer types.BlobInfo) error {
8082
return os.RemoveAll(filepath.Join(c.RootDir, layerId))
8183
}
8284

83-
// Implement types.ImageSource
84-
85-
func (c *FileCache) Reference() types.ImageReference {
86-
return c.Ref
87-
}
88-
89-
func (c *FileCache) Close() error {
90-
return nil
91-
}
92-
93-
func (c *FileCache) GetManifest(d *digest.Digest) ([]byte, string, error) {
94-
return c.src.GetManifest(d)
95-
}
96-
97-
func (c *FileCache) GetTargetManifest(digest digest.Digest) ([]byte, string, error) {
98-
return c.GetTargetManifest(digest)
99-
}
100-
10185
// GetBlob returns a stream for the specified blob, and the blob’s size (or -1 if unknown).
10286
// The Digest field in BlobInfo is guaranteed to be provided; Size may be -1.
10387
func (c *FileCache) GetBlob(bi types.BlobInfo) (io.ReadCloser, int64, error) {
@@ -106,7 +90,7 @@ func (c *FileCache) GetBlob(bi types.BlobInfo) (io.ReadCloser, int64, error) {
10690
return r, bi.Size, err
10791
}
10892
// Add to the cache then return
109-
r, size, err := c.src.GetBlob(bi)
93+
r, size, err := c.ProxySource.GetBlob(bi)
11094
if err != nil {
11195
return nil, 0, err
11296
}
@@ -116,12 +100,3 @@ func (c *FileCache) GetBlob(bi types.BlobInfo) (io.ReadCloser, int64, error) {
116100
}
117101
return r, size, err
118102
}
119-
120-
// GetSignatures returns the image's signatures. It may use a remote (= slow) service.
121-
func (c *FileCache) GetSignatures(ctx context.Context, d *digest.Digest) ([][]byte, error) {
122-
return c.src.GetSignatures(ctx, d)
123-
}
124-
125-
func (c *FileCache) LayerInfosForCopy() []types.BlobInfo {
126-
return nil
127-
}

pkg/image/BUILD.bazel

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
2+
3+
go_library(
4+
name = "go_default_library",
5+
srcs = [
6+
"mutable_source.go",
7+
"proxy_types.go",
8+
],
9+
importpath = "github.com/GoogleCloudPlatform/container-diff/pkg/image",
10+
visibility = ["//visibility:public"],
11+
deps = [
12+
"//vendor/github.com/containers/image/manifest:go_default_library",
13+
"//vendor/github.com/containers/image/types:go_default_library",
14+
"//vendor/github.com/opencontainers/go-digest:go_default_library",
15+
],
16+
)
17+
18+
go_test(
19+
name = "go_default_test",
20+
srcs = ["mutable_source_test.go"],
21+
embed = [":go_default_library"],
22+
importpath = "github.com/GoogleCloudPlatform/container-diff/pkg/image",
23+
deps = [
24+
"//vendor/github.com/containers/image/manifest:go_default_library",
25+
"//vendor/github.com/containers/image/types:go_default_library",
26+
"//vendor/github.com/opencontainers/go-digest:go_default_library",
27+
],
28+
)

pkg/image/mutable_source.go

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
/*
2+
Copyright 2017 Google, Inc. All rights reserved.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package image
18+
19+
import (
20+
"bytes"
21+
"compress/gzip"
22+
"encoding/json"
23+
"io"
24+
"io/ioutil"
25+
"time"
26+
27+
"github.com/containers/image/manifest"
28+
"github.com/containers/image/types"
29+
digest "github.com/opencontainers/go-digest"
30+
)
31+
32+
type MutableSource struct {
33+
ProxySource
34+
mfst *manifest.Schema2
35+
cfg *manifest.Schema2Image
36+
extraBlobs map[string][]byte
37+
extraLayers []digest.Digest
38+
}
39+
40+
func NewMutableSource(r types.ImageReference) (*MutableSource, error) {
41+
src, err := r.NewImageSource(nil)
42+
if err != nil {
43+
return nil, err
44+
}
45+
img, err := r.NewImage(nil)
46+
if err != nil {
47+
return nil, err
48+
}
49+
50+
ms := &MutableSource{
51+
ProxySource: ProxySource{
52+
Ref: r,
53+
ImageSource: src,
54+
img: img,
55+
},
56+
extraBlobs: make(map[string][]byte),
57+
}
58+
if err := ms.populateManifestAndConfig(); err != nil {
59+
return nil, err
60+
}
61+
return ms, nil
62+
}
63+
64+
// GetManifest marshals the stored manifest to the byte format.
65+
func (m *MutableSource) GetManifest(_ *digest.Digest) ([]byte, string, error) {
66+
s, err := json.Marshal(m.mfst)
67+
if err := m.saveConfig(); err != nil {
68+
return nil, "", err
69+
}
70+
return s, manifest.DockerV2Schema2MediaType, err
71+
}
72+
73+
// populateManifestAndConfig parses the raw manifest and configs, storing them on the struct.
74+
func (m *MutableSource) populateManifestAndConfig() error {
75+
mfstBytes, _, err := m.GetManifest(nil)
76+
if err != nil {
77+
return err
78+
}
79+
80+
m.mfst, err = manifest.Schema2FromManifest(mfstBytes)
81+
if err != nil {
82+
return err
83+
}
84+
85+
bi := types.BlobInfo{Digest: m.mfst.ConfigDescriptor.Digest}
86+
r, _, err := m.GetBlob(bi)
87+
if err != nil {
88+
return err
89+
}
90+
91+
cfgBytes, err := ioutil.ReadAll(r)
92+
if err != nil {
93+
return err
94+
}
95+
96+
return json.Unmarshal(cfgBytes, &m.cfg)
97+
}
98+
99+
// GetBlob first checks the stored "extra" blobs, then proxies the call to the original source.
100+
func (m *MutableSource) GetBlob(bi types.BlobInfo) (io.ReadCloser, int64, error) {
101+
if b, ok := m.extraBlobs[bi.Digest.String()]; ok {
102+
return ioutil.NopCloser(bytes.NewReader(b)), int64(len(b)), nil
103+
}
104+
return m.ImageSource.GetBlob(bi)
105+
}
106+
107+
func gzipBytes(b []byte) ([]byte, error) {
108+
buf := bytes.NewBuffer([]byte{})
109+
w := gzip.NewWriter(buf)
110+
_, err := w.Write(b)
111+
w.Close()
112+
if err != nil {
113+
return nil, err
114+
}
115+
return buf.Bytes(), nil
116+
}
117+
118+
// appendLayer appends an uncompressed blob to the image, preserving the invariants required across the config and manifest.
119+
func (m *MutableSource) appendLayer(content []byte) error {
120+
compressedBlob, err := gzipBytes(content)
121+
if err != nil {
122+
return err
123+
}
124+
125+
dgst := digest.FromBytes(compressedBlob)
126+
127+
// Add the layer to the manifest.
128+
descriptor := manifest.Schema2Descriptor{
129+
MediaType: manifest.DockerV2Schema2LayerMediaType,
130+
Size: int64(len(content)),
131+
Digest: dgst,
132+
}
133+
m.mfst.LayersDescriptors = append(m.mfst.LayersDescriptors, descriptor)
134+
135+
m.extraBlobs[dgst.String()] = compressedBlob
136+
m.extraLayers = append(m.extraLayers, dgst)
137+
138+
// Also add it to the config.
139+
diffID := digest.FromBytes(content)
140+
m.cfg.RootFS.DiffIDs = append(m.cfg.RootFS.DiffIDs, diffID)
141+
history := manifest.Schema2History{
142+
Created: time.Now(),
143+
Author: "container-diff",
144+
}
145+
m.cfg.History = append(m.cfg.History, history)
146+
147+
return nil
148+
}
149+
150+
// saveConfig marshals the stored image config, and updates the references to it in the manifest.
151+
func (m *MutableSource) saveConfig() error {
152+
cfgBlob, err := json.Marshal(m.cfg)
153+
if err != nil {
154+
return err
155+
}
156+
157+
cfgDigest := digest.FromBytes(cfgBlob)
158+
m.extraBlobs[cfgDigest.String()] = cfgBlob
159+
m.mfst.ConfigDescriptor = manifest.Schema2Descriptor{
160+
MediaType: manifest.DockerV2Schema2ConfigMediaType,
161+
Size: int64(len(cfgBlob)),
162+
Digest: cfgDigest,
163+
}
164+
return nil
165+
}

0 commit comments

Comments
 (0)