Skip to content

Commit 7677578

Browse files
authored
Merge pull request #5264 from ktock/bumpup-stargz
Bump up stargz-snapshotter to v0.19.1
2 parents 0850bdd + 83039f8 commit 7677578

11 files changed

Lines changed: 312 additions & 17 deletions

File tree

‎Dockerfile‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ ARG CNI_PLUGINS_VERSION=v1.9.1@BINARY
2424
# Extra deps: Build
2525
ARG BUILDKIT_VERSION=v0.33.1@BINARY
2626
# Extra deps: Lazy-pulling
27-
ARG STARGZ_SNAPSHOTTER_VERSION=v0.18.2@BINARY
27+
ARG STARGZ_SNAPSHOTTER_VERSION=v0.19.1@BINARY
2828
# Extra deps: Encryption
2929
ARG IMGCRYPT_VERSION=v2.0.3@3cd28929043ba2847633c32b806a6ccda8cd030f
3030
# Extra deps: Rootless

‎Dockerfile.d/SHA256SUMS.d/stargz-snapshotter-v0.18.2‎

Lines changed: 0 additions & 3 deletions
This file was deleted.
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
0e963599d7da5179810dcfaa1095e3d562660b7f6d526a9fb791a5e30f252680 stargz-snapshotter-v0.19.1-linux-amd64.tar.gz
2+
0bada57e932e0498aa9faf16b8c2b9b7997617ec65357ef5d09585c1abb61758 stargz-snapshotter-v0.19.1-linux-arm64.tar.gz
3+
f1cf855870af16a653d8acb9daa3edf84687c2c05323cb958f078fb148af3eec stargz-snapshotter.service

‎cmd/nerdctl/container/container_run_stargz_linux_test.go‎

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,37 @@
1717
package container
1818

1919
import (
20+
"archive/tar"
21+
"bytes"
22+
"compress/gzip"
23+
"context"
24+
"encoding/json"
25+
"errors"
26+
"fmt"
27+
"io"
28+
"path/filepath"
2029
"testing"
2130

31+
digest "github.com/opencontainers/go-digest"
32+
imagespecversioned "github.com/opencontainers/image-spec/specs-go"
33+
imagespec "github.com/opencontainers/image-spec/specs-go/v1"
34+
"gotest.tools/v3/assert"
35+
36+
containerd "github.com/containerd/containerd/v2/client"
37+
"github.com/containerd/containerd/v2/core/content"
38+
"github.com/containerd/containerd/v2/core/images"
39+
"github.com/containerd/containerd/v2/defaults"
40+
"github.com/containerd/nerdctl/mod/tigron/expect"
2241
"github.com/containerd/nerdctl/mod/tigron/require"
2342
"github.com/containerd/nerdctl/mod/tigron/test"
43+
"github.com/containerd/nerdctl/mod/tigron/tig"
44+
"github.com/containerd/platforms"
45+
"github.com/containerd/stargz-snapshotter/estargz"
2446

47+
"github.com/containerd/nerdctl/v2/pkg/rootlessutil"
2548
"github.com/containerd/nerdctl/v2/pkg/testutil"
2649
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest"
50+
"github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest/registry"
2751
)
2852

2953
func TestRunStargz(t *testing.T) {
@@ -41,3 +65,148 @@ func TestRunStargz(t *testing.T) {
4165

4266
testCase.Run(t)
4367
}
68+
69+
// createInvalidESGZImage creates an eStargz image from the first layer of srcRef (an eStargz image) with an extra
70+
// file added to the layer, while keeping the original image config. As the diffID in the config (the one of srcRef)
71+
// doesn't match the modified layer, the resulting image has the same ChainID as srcRef but different contents and
72+
// TOC digest. The image is stored in containerd as newRef.
73+
func createInvalidESGZImage(ctx context.Context, t tig.T, client *containerd.Client, srcRef, newRef string) {
74+
cs := client.ContentStore()
75+
srcImg, err := client.GetImage(ctx, srcRef)
76+
assert.NilError(t, err)
77+
srcManifest, err := images.Manifest(ctx, cs, srcImg.Target(), platforms.Default())
78+
assert.NilError(t, err)
79+
layer, err := content.ReadBlob(ctx, cs, srcManifest.Layers[0])
80+
assert.NilError(t, err)
81+
82+
var modified bytes.Buffer
83+
tw := tar.NewWriter(&modified)
84+
gz, err := gzip.NewReader(bytes.NewReader(layer))
85+
assert.NilError(t, err)
86+
tr := tar.NewReader(gz)
87+
for {
88+
h, err := tr.Next()
89+
if err == io.EOF {
90+
break
91+
}
92+
assert.NilError(t, err)
93+
assert.NilError(t, tw.WriteHeader(h))
94+
_, err = io.Copy(tw, tr)
95+
assert.NilError(t, err)
96+
}
97+
contents := "MODIFIED"
98+
assert.NilError(t, tw.WriteHeader(&tar.Header{Typeflag: tar.TypeReg, Name: "/modified.txt", Size: int64(len(contents))}))
99+
_, err = tw.Write([]byte(contents))
100+
assert.NilError(t, err)
101+
assert.NilError(t, tw.Close())
102+
103+
modifiedData := modified.Bytes()
104+
esgz, err := estargz.Build(io.NewSectionReader(bytes.NewReader(modifiedData), 0, int64(len(modifiedData))))
105+
assert.NilError(t, err)
106+
defer esgz.Close()
107+
layerData, err := io.ReadAll(esgz)
108+
assert.NilError(t, err)
109+
layerDesc := imagespec.Descriptor{
110+
MediaType: imagespec.MediaTypeImageLayerGzip,
111+
Digest: digest.FromBytes(layerData),
112+
Size: int64(len(layerData)),
113+
Annotations: map[string]string{estargz.TOCJSONDigestAnnotation: esgz.TOCDigest().String()},
114+
}
115+
assert.NilError(t, content.WriteBlob(ctx, cs, "layer-invalid", bytes.NewReader(layerData), layerDesc))
116+
117+
srcConfig, err := images.Config(ctx, cs, srcImg.Target(), platforms.Default())
118+
assert.NilError(t, err)
119+
manifest := &imagespec.Manifest{
120+
Versioned: imagespecversioned.Versioned{SchemaVersion: 2},
121+
MediaType: imagespec.MediaTypeImageManifest,
122+
Config: srcConfig,
123+
Layers: []imagespec.Descriptor{layerDesc},
124+
}
125+
manifestBlob, err := json.Marshal(manifest)
126+
assert.NilError(t, err)
127+
manifestDesc := imagespec.Descriptor{
128+
MediaType: imagespec.MediaTypeImageManifest,
129+
Digest: digest.FromBytes(manifestBlob),
130+
Size: int64(len(manifestBlob)),
131+
}
132+
labels := map[string]string{}
133+
for i, d := range []imagespec.Descriptor{layerDesc, srcConfig} {
134+
labels[fmt.Sprintf("containerd.io/gc.ref.content.%d", i)] = d.Digest.String()
135+
}
136+
assert.NilError(t, content.WriteBlob(ctx, cs, "manifest-invalid", bytes.NewReader(manifestBlob), manifestDesc, content.WithLabels(labels)))
137+
_, err = client.ImageService().Create(ctx, images.Image{Name: newRef, Target: manifestDesc})
138+
assert.NilError(t, err)
139+
}
140+
141+
func newTestContainerdClient(helpers test.Helpers) *containerd.Client {
142+
addr := defaults.DefaultAddress
143+
if rootlessutil.IsRootless() {
144+
stateDir, err := rootlessutil.RootlessKitStateDir()
145+
assert.NilError(helpers.T(), err)
146+
childPid, err := rootlessutil.RootlessKitChildPid(stateDir)
147+
assert.NilError(helpers.T(), err)
148+
addr = filepath.Join("/proc", fmt.Sprintf("%d", childPid), "root", defaults.DefaultAddress)
149+
}
150+
client, err := containerd.New(addr, containerd.WithDefaultNamespace(string(helpers.Read(nerdtest.Namespace))))
151+
assert.NilError(helpers.T(), err)
152+
return client
153+
}
154+
155+
func TestPullStargzInvalidChainID(t *testing.T) {
156+
testCase := nerdtest.Setup()
157+
158+
testCase.Require = require.All(
159+
nerdtest.Stargz,
160+
nerdtest.Registry,
161+
require.Amd64,
162+
require.Not(nerdtest.Docker),
163+
)
164+
165+
var reg *registry.Server
166+
167+
testCase.Setup = func(data test.Data, helpers test.Helpers) {
168+
reg = nerdtest.RegistryWithNoAuth(data, helpers, 0, false)
169+
reg.Setup(data, helpers)
170+
base := fmt.Sprintf("127.0.0.1:%d/alpine", reg.Port)
171+
esgz := base + ":esgz"
172+
invalid := base + ":esgz-invalid"
173+
174+
// A valid eStargz image
175+
helpers.Ensure("pull", "--quiet", testutil.AlpineImage)
176+
helpers.Ensure("image", "convert", "--estargz", "--oci", testutil.AlpineImage, esgz)
177+
helpers.Ensure("push", esgz)
178+
179+
// An eStargz image whose layer doesn't match the diffID in the config
180+
client := newTestContainerdClient(helpers)
181+
defer client.Close()
182+
createInvalidESGZImage(context.Background(), helpers.T(), client, esgz, invalid)
183+
helpers.Ensure("push", invalid)
184+
185+
// Remove local copies so that the following pulls fetch from the registry
186+
helpers.Ensure("rmi", "-f", esgz, invalid)
187+
188+
data.Labels().Set("esgz", esgz)
189+
data.Labels().Set("invalid", invalid)
190+
}
191+
192+
testCase.Cleanup = func(data test.Data, helpers test.Helpers) {
193+
if v := data.Labels().Get("esgz"); v != "" {
194+
helpers.Anyhow("rmi", "-f", v)
195+
}
196+
if v := data.Labels().Get("invalid"); v != "" {
197+
helpers.Anyhow("rmi", "-f", v)
198+
}
199+
if reg != nil {
200+
reg.Cleanup(data, helpers)
201+
}
202+
}
203+
204+
testCase.Command = func(data test.Data, helpers test.Helpers) test.TestableCommand {
205+
helpers.Ensure("--snapshotter=stargz", "pull", data.Labels().Get("invalid"))
206+
return helpers.Command("--snapshotter=stargz", "pull", data.Labels().Get("esgz"))
207+
}
208+
209+
testCase.Expected = test.Expects(expect.ExitCodeGenericFail, []error{errors.New("existing images contain invalid ChainIDs")}, nil)
210+
211+
testCase.Run(t)
212+
}

‎go.mod‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ require (
2020
github.com/containerd/nerdctl/mod/tigron v0.0.0 //gomodjail:unconfined
2121
github.com/containerd/nydus-snapshotter v0.16.1 //gomodjail:unconfined
2222
github.com/containerd/platforms v1.0.0-rc.5 //gomodjail:unconfined
23-
github.com/containerd/stargz-snapshotter v0.18.2 //gomodjail:unconfined
24-
github.com/containerd/stargz-snapshotter/estargz v0.18.2 //gomodjail:unconfined
25-
github.com/containerd/stargz-snapshotter/ipfs v0.18.2 //gomodjail:unconfined
23+
github.com/containerd/stargz-snapshotter v0.19.1 //gomodjail:unconfined
24+
github.com/containerd/stargz-snapshotter/estargz v0.19.1 //gomodjail:unconfined
25+
github.com/containerd/stargz-snapshotter/ipfs v0.19.1 //gomodjail:unconfined
2626
github.com/containerd/typeurl/v2 v2.3.0
2727
github.com/docker/cli v29.9.2+incompatible //gomodjail:unconfined
2828
github.com/docker/go-connections v0.8.2 //gomodjail:unconfined

‎go.sum‎

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,12 @@ github.com/containerd/platforms v1.0.0-rc.5 h1:vXd569rDrz8LeMXzAnBsy6LADV5YtsD8o
5656
github.com/containerd/platforms v1.0.0-rc.5/go.mod h1:lKlMXyLybmBedS/JJm11uDofzI8L2v0J2ZbYvNsbq1A=
5757
github.com/containerd/plugin v1.1.0 h1:O+7lczNJVMy8rz0YNx3xGB8tTf5qY4i5abF041Ew19U=
5858
github.com/containerd/plugin v1.1.0/go.mod h1:qBTum+A8lJ6lO44A19Eo7y1OlcLj4OWFH1DA/vnHmcc=
59-
github.com/containerd/stargz-snapshotter v0.18.2 h1:Ev/sxfQUjwzJQ9eqy3XzttcQ3osMIqkQgMYlcET+10M=
60-
github.com/containerd/stargz-snapshotter v0.18.2/go.mod h1:iS0a4lgCFjGbdBJNrm1jwvaMFGGnQ6PZ5Sd09i060h8=
61-
github.com/containerd/stargz-snapshotter/estargz v0.18.2 h1:yXkZFYIzz3eoLwlTUZKz2iQ4MrckBxJjkmD16ynUTrw=
62-
github.com/containerd/stargz-snapshotter/estargz v0.18.2/go.mod h1:XyVU5tcJ3PRpkA9XS2T5us6Eg35yM0214Y+wvrZTBrY=
63-
github.com/containerd/stargz-snapshotter/ipfs v0.18.2 h1:GPkGnRka0GcsiAXh7rN2C+XesH/6BBuJcdtV6ymGlGg=
64-
github.com/containerd/stargz-snapshotter/ipfs v0.18.2/go.mod h1:a7cHDMpUfj5f3aZ3/50jEyapRZGjyaOKSDgFH4eiEnE=
59+
github.com/containerd/stargz-snapshotter v0.19.1 h1:g6peP3Xabv6MCmbVxP7vYDWvXjfwbBe+fhiZGTA6mvo=
60+
github.com/containerd/stargz-snapshotter v0.19.1/go.mod h1:BckND+0wYCQrUuTetyGDTKH6CmB9RWOArXXZsSgHtIk=
61+
github.com/containerd/stargz-snapshotter/estargz v0.19.1 h1:PD/uZYqmyyEXZl3fEPM2ieCpilv7Oow9FC2OCBfkDRs=
62+
github.com/containerd/stargz-snapshotter/estargz v0.19.1/go.mod h1:Cr0pxOkuN7Y61+Sik6X6/nPtePW7hsKWdBt+MoHQRr0=
63+
github.com/containerd/stargz-snapshotter/ipfs v0.19.1 h1:Z3FfA8H+5iKBVn/dptPb56/hoQB2KB5OqhOGwyd60V0=
64+
github.com/containerd/stargz-snapshotter/ipfs v0.19.1/go.mod h1:xUq517/zM/tGeXZUHvmtWN9cRwsSM9wgDRiQcDDtyE8=
6565
github.com/containerd/ttrpc v1.2.10 h1:qQvy3mokhoXON7FXiOOc05O7Up6nrNj6amJF8urNvDc=
6666
github.com/containerd/ttrpc v1.2.10/go.mod h1:YrmEQKkMbBA4EM26AzVAIwHGIYgptkL4CJ/yVTaDyBE=
6767
github.com/containerd/typeurl/v2 v2.3.0 h1:HZHPhRWo5XMy3QGQoPrUzbW/2ckwjfweHmOwlkIrPAQ=

‎pkg/imgutil/imgutil.go‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,11 @@ func PullImage(ctx context.Context, client *containerd.Client, resolver remotes.
246246

247247
// different remote snapshotters will update pull.Config separately
248248
snOpt.apply(config, ref, options.RFlags)
249+
if snOpt.needsVerify() {
250+
if err := snOpt.verify(ctx, client, ref, resolver); err != nil {
251+
return nil, fmt.Errorf("snapshot verifier returned an error: %w", err)
252+
}
253+
}
249254
} else {
250255
log.G(ctx).Debugf("The image will not be unpacked. Platforms=%v.", options.OCISpecPlatform)
251256
}

‎pkg/imgutil/snapshotter.go‎

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@
1717
package imgutil
1818

1919
import (
20+
"context"
2021
"strings"
2122

2223
containerd "github.com/containerd/containerd/v2/client"
2324
"github.com/containerd/containerd/v2/core/images"
25+
"github.com/containerd/containerd/v2/core/remotes"
2426
ctdsnapshotters "github.com/containerd/containerd/v2/pkg/snapshotters"
2527
"github.com/containerd/log"
2628
"github.com/containerd/stargz-snapshotter/fs/source"
@@ -44,7 +46,7 @@ const (
4446
// remote snapshotters explicitly handled by nerdctl
4547
var builtinRemoteSnapshotterOpts = map[string]snapshotterOpts{
4648
snapshotterNameOverlaybd: &remoteSnapshotterOpts{snapshotter: "overlaybd"},
47-
snapshotterNameStargz: &remoteSnapshotterOpts{snapshotter: "stargz", extraLabels: stargzExtraLabels},
49+
snapshotterNameStargz: &remoteSnapshotterOpts{snapshotter: "stargz", extraLabels: stargzExtraLabels, verifier: stargzVerifier},
4850
snapshotterNameNydus: &remoteSnapshotterOpts{snapshotter: "nydus"},
4951
snapshotterNameSoci: &remoteSnapshotterOpts{snapshotter: "soci", extraLabels: sociExtraLabels},
5052
snapshotterNameCvmfs: &remoteSnapshotterOpts{snapshotter: "cvmfs-snapshotter"},
@@ -55,6 +57,8 @@ var builtinRemoteSnapshotterOpts = map[string]snapshotterOpts{
5557
type snapshotterOpts interface {
5658
apply(config *pull.Config, ref string, rFlags types.RemoteSnapshotterFlags)
5759
isRemote() bool
60+
verify(ctx context.Context, client *containerd.Client, ref string, resolver remotes.Resolver) error
61+
needsVerify() bool
5862
}
5963

6064
// getSnapshotterOpts get snapshotter opts by fuzzy matching of the snapshotter name
@@ -76,6 +80,7 @@ func getSnapshotterOpts(snapshotter string) snapshotterOpts {
7680
type remoteSnapshotterOpts struct {
7781
snapshotter string
7882
extraLabels func(func(images.Handler) images.Handler, types.RemoteSnapshotterFlags) func(images.Handler) images.Handler
83+
verifier func(ctx context.Context, client *containerd.Client, ref string, resolver remotes.Resolver) error
7984
}
8085

8186
func (rs *remoteSnapshotterOpts) isRemote() bool {
@@ -94,6 +99,17 @@ func (rs *remoteSnapshotterOpts) apply(config *pull.Config, ref string, rFlags t
9499
)
95100
}
96101

102+
func (rs *remoteSnapshotterOpts) verify(ctx context.Context, client *containerd.Client, ref string, resolver remotes.Resolver) error {
103+
if rs.verifier != nil {
104+
return rs.verifier(ctx, client, ref, resolver)
105+
}
106+
return nil
107+
}
108+
109+
func (rs *remoteSnapshotterOpts) needsVerify() bool {
110+
return rs.verifier != nil
111+
}
112+
97113
// defaultSnapshotterOpts is for snapshotters that
98114
// not handled separately
99115
type defaultSnapshotterOpts struct {
@@ -111,6 +127,14 @@ func (dsn *defaultSnapshotterOpts) isRemote() bool {
111127
return false
112128
}
113129

130+
func (dsn *defaultSnapshotterOpts) verify(ctx context.Context, client *containerd.Client, ref string, resolver remotes.Resolver) error {
131+
return nil
132+
}
133+
134+
func (dsn *defaultSnapshotterOpts) needsVerify() bool {
135+
return false
136+
}
137+
114138
func stargzExtraLabels(f func(images.Handler) images.Handler, rFlags types.RemoteSnapshotterFlags) func(images.Handler) images.Handler {
115139
return source.AppendExtraLabelsHandler(prefetchSize, f)
116140
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
Copyright The containerd Authors.
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 imgutil
18+
19+
import (
20+
"context"
21+
"fmt"
22+
23+
containerd "github.com/containerd/containerd/v2/client"
24+
"github.com/containerd/containerd/v2/core/remotes"
25+
"github.com/containerd/stargz-snapshotter/service/verifier"
26+
27+
"github.com/containerd/nerdctl/v2/pkg/referenceutil"
28+
)
29+
30+
// stargzVerifier normalizes ref (e.g., adds the default tag) because the underlying
31+
// verifier requires a tag or a digest. It is linux-only: stargz-snapshotter's service package depends on
32+
// linux-only containerd packages (e.g., overlayutils).
33+
func stargzVerifier(ctx context.Context, client *containerd.Client, ref string, resolver remotes.Resolver) error {
34+
parsed, err := referenceutil.Parse(ref)
35+
if err != nil {
36+
return fmt.Errorf("failed to parse image reference %q: %w", ref, err)
37+
}
38+
return verifier.VerifyImage(ctx, client, parsed.String(), resolver)
39+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//go:build !linux
2+
3+
/*
4+
Copyright The containerd Authors.
5+
6+
Licensed under the Apache License, Version 2.0 (the "License");
7+
you may not use this file except in compliance with the License.
8+
You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing, software
13+
distributed under the License is distributed on an "AS IS" BASIS,
14+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
See the License for the specific language governing permissions and
16+
limitations under the License.
17+
*/
18+
19+
package imgutil
20+
21+
import (
22+
"context"
23+
24+
containerd "github.com/containerd/containerd/v2/client"
25+
"github.com/containerd/containerd/v2/core/remotes"
26+
)
27+
28+
// stargzVerifier is nil on non-Linux platforms (stargz-snapshotter's verifier is linux-only).
29+
var stargzVerifier func(ctx context.Context, client *containerd.Client, ref string, resolver remotes.Resolver) error

0 commit comments

Comments
 (0)