Skip to content

Commit 6ce499c

Browse files
sipsmatonistiigi
authored andcommitted
applier: add hack to support docker zstd layers
Before this, buildkit was able to create and push layers of type vnd.docker.image.rootfs.diff.tar.zstd but if you tried to then pull and unpack those layers in buildkit, you'd get an error from containerd: `failed to get stream processor for application/vnd.docker.image.rootfs.diff.tar.zstd: no processor for media-type` While that media type is not official, support for exporting it was added to buildkit in the past anyways since it works in practice and is accepted by many registries. It thus seems logical that buildkit should also support pulling and unpacking those layers too. There is support for registering stream processors in containerd, but those only work when using the OCI worker since it relies on the apply implementation being in the same process as buildkitd. As a fallback, we instead just implement a hack that swaps out the docker zstd media type for the oci one before sending it to containerd. This works in practice because the two types are compatible. Signed-off-by: Erik Sipsma <[email protected]>
1 parent f21a96c commit 6ce499c

File tree

4 files changed

+86
-52
lines changed

4 files changed

+86
-52
lines changed

client/client_test.go

Lines changed: 75 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -3765,66 +3765,95 @@ func testBuildExportZstd(t *testing.T, sb integration.Sandbox) {
37653765

37663766
func testPullZstdImage(t *testing.T, sb integration.Sandbox) {
37673767
integration.CheckFeatureCompat(t, sb, integration.FeatureDirectPush)
3768-
c, err := New(sb.Context(), sb.Address())
3769-
require.NoError(t, err)
3770-
defer c.Close()
3768+
for _, ociMediaTypes := range []bool{true, false} {
3769+
ociMediaTypes := ociMediaTypes
3770+
t.Run(t.Name()+fmt.Sprintf("/ociMediaTypes=%t", ociMediaTypes), func(t *testing.T) {
3771+
c, err := New(sb.Context(), sb.Address())
3772+
require.NoError(t, err)
3773+
defer c.Close()
37713774

3772-
busybox := llb.Image("busybox:latest")
3773-
cmd := `sh -e -c "echo -n zstd > data"`
3775+
busybox := llb.Image("busybox:latest")
3776+
cmd := `sh -e -c "echo -n zstd > data"`
37743777

3775-
st := llb.Scratch()
3776-
st = busybox.Run(llb.Shlex(cmd), llb.Dir("/wd")).AddMount("/wd", st)
3778+
st := llb.Scratch()
3779+
st = busybox.Run(llb.Shlex(cmd), llb.Dir("/wd")).AddMount("/wd", st)
37773780

3778-
def, err := st.Marshal(sb.Context())
3779-
require.NoError(t, err)
3781+
def, err := st.Marshal(sb.Context())
3782+
require.NoError(t, err)
37803783

3781-
registry, err := sb.NewRegistry()
3782-
if errors.Is(err, integration.ErrRequirements) {
3783-
t.Skip(err.Error())
3784-
}
3785-
require.NoError(t, err)
3784+
registry, err := sb.NewRegistry()
3785+
if errors.Is(err, integration.ErrRequirements) {
3786+
t.Skip(err.Error())
3787+
}
3788+
require.NoError(t, err)
37863789

3787-
target := registry + "/buildkit/build/exporter:zstd"
3790+
target := registry + "/buildkit/build/exporter:zstd"
37883791

3789-
_, err = c.Solve(sb.Context(), def, SolveOpt{
3790-
Exports: []ExportEntry{
3791-
{
3792-
Type: ExporterImage,
3793-
Attrs: map[string]string{
3794-
"name": target,
3795-
"push": "true",
3796-
"compression": "zstd",
3792+
_, err = c.Solve(sb.Context(), def, SolveOpt{
3793+
Exports: []ExportEntry{
3794+
{
3795+
Type: ExporterImage,
3796+
Attrs: map[string]string{
3797+
"name": target,
3798+
"push": "true",
3799+
"compression": "zstd",
3800+
"oci-mediatypes": strconv.FormatBool(ociMediaTypes),
3801+
},
3802+
},
3803+
},
3804+
}, nil)
3805+
require.NoError(t, err)
37973806

3798-
// containerd applier supports only zstd with oci-mediatype.
3799-
"oci-mediatypes": "true",
3807+
ensurePruneAll(t, c, sb)
3808+
3809+
st = llb.Image(target).File(llb.Copy(llb.Image(target), "/data", "/zdata"))
3810+
def, err = st.Marshal(sb.Context())
3811+
require.NoError(t, err)
3812+
3813+
destDir := t.TempDir()
3814+
3815+
out := filepath.Join(destDir, "out.tar")
3816+
outW, err := os.Create(out)
3817+
require.NoError(t, err)
3818+
3819+
_, err = c.Solve(sb.Context(), def, SolveOpt{
3820+
Exports: []ExportEntry{
3821+
{
3822+
Type: ExporterOCI,
3823+
Output: fixedWriteCloser(outW),
3824+
Attrs: map[string]string{
3825+
"oci-mediatypes": strconv.FormatBool(ociMediaTypes),
3826+
},
3827+
},
38003828
},
3801-
},
3802-
},
3803-
}, nil)
3804-
require.NoError(t, err)
3829+
}, nil)
3830+
require.NoError(t, err)
38053831

3806-
ensurePruneAll(t, c, sb)
3832+
dt, err := os.ReadFile(out)
3833+
require.NoError(t, err)
38073834

3808-
st = llb.Scratch().File(llb.Copy(llb.Image(target), "/data", "/zdata"))
3835+
m, err := testutil.ReadTarToMap(dt, false)
3836+
require.NoError(t, err)
38093837

3810-
def, err = st.Marshal(sb.Context())
3811-
require.NoError(t, err)
3838+
var index ocispecs.Index
3839+
err = json.Unmarshal(m["index.json"].Data, &index)
3840+
require.NoError(t, err)
38123841

3813-
destDir := t.TempDir()
3842+
var mfst ocispecs.Manifest
3843+
err = json.Unmarshal(m["blobs/sha256/"+index.Manifests[0].Digest.Hex()].Data, &mfst)
3844+
require.NoError(t, err)
38143845

3815-
_, err = c.Solve(sb.Context(), def, SolveOpt{
3816-
Exports: []ExportEntry{
3817-
{
3818-
Type: ExporterLocal,
3819-
OutputDir: destDir,
3820-
},
3821-
},
3822-
}, nil)
3823-
require.NoError(t, err)
3846+
firstLayer := mfst.Layers[0]
3847+
if ociMediaTypes {
3848+
require.Equal(t, ocispecs.MediaTypeImageLayer+"+zstd", firstLayer.MediaType)
3849+
} else {
3850+
require.Equal(t, images.MediaTypeDockerSchema2Layer+".zstd", firstLayer.MediaType)
3851+
}
38243852

3825-
dt, err := os.ReadFile(filepath.Join(destDir, "zdata"))
3826-
require.NoError(t, err)
3827-
require.Equal(t, dt, []byte("zstd"))
3853+
zstdLayerDigest := firstLayer.Digest.Hex()
3854+
require.Equal(t, m["blobs/sha256/"+zstdLayerDigest].Data[:4], []byte{0x28, 0xb5, 0x2f, 0xfd})
3855+
})
3856+
}
38283857
}
38293858
func testBuildPushAndValidate(t *testing.T, sb integration.Sandbox) {
38303859
integration.CheckFeatureCompat(t, sb, integration.FeatureDirectPush)

util/compression/compression.go

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@ func (c Config) SetLevel(l int) Config {
7878

7979
const (
8080
mediaTypeDockerSchema2LayerZstd = images.MediaTypeDockerSchema2Layer + ".zstd"
81-
mediaTypeImageLayerZstd = ocispecs.MediaTypeImageLayer + "+zstd" // unreleased image-spec#790
8281
)
8382

8483
var Default = Gzip
@@ -104,7 +103,7 @@ func fromMediaType(mediaType string) (Type, error) {
104103
return Uncompressed, nil
105104
case ocispecs.MediaTypeImageLayerGzip, ocispecs.MediaTypeImageLayerNonDistributableGzip: //nolint:staticcheck // ignore SA1019: Non-distributable layers are deprecated, and not recommended for future use.
106105
return Gzip, nil
107-
case mediaTypeImageLayerZstd, ocispecs.MediaTypeImageLayerNonDistributableZstd: //nolint:staticcheck // ignore SA1019: Non-distributable layers are deprecated, and not recommended for future use.
106+
case ocispecs.MediaTypeImageLayerZstd, ocispecs.MediaTypeImageLayerNonDistributableZstd: //nolint:staticcheck // ignore SA1019: Non-distributable layers are deprecated, and not recommended for future use.
108107
return Zstd, nil
109108
default:
110109
return nil, errors.Errorf("unsupported media type %s", mediaType)
@@ -193,7 +192,7 @@ var toDockerLayerType = map[string]string{
193192
images.MediaTypeDockerSchema2LayerForeignGzip: images.MediaTypeDockerSchema2LayerForeignGzip,
194193
ocispecs.MediaTypeImageLayerNonDistributable: images.MediaTypeDockerSchema2LayerForeign, //nolint:staticcheck // ignore SA1019: Non-distributable layers are deprecated, and not recommended for future use.
195194
ocispecs.MediaTypeImageLayerNonDistributableGzip: images.MediaTypeDockerSchema2LayerForeignGzip, //nolint:staticcheck // ignore SA1019: Non-distributable layers are deprecated, and not recommended for future use.
196-
mediaTypeImageLayerZstd: mediaTypeDockerSchema2LayerZstd,
195+
ocispecs.MediaTypeImageLayerZstd: mediaTypeDockerSchema2LayerZstd,
197196
mediaTypeDockerSchema2LayerZstd: mediaTypeDockerSchema2LayerZstd,
198197
}
199198

@@ -207,8 +206,8 @@ var toOCILayerType = map[string]string{
207206
images.MediaTypeDockerSchema2LayerGzip: ocispecs.MediaTypeImageLayerGzip,
208207
images.MediaTypeDockerSchema2LayerForeign: ocispecs.MediaTypeImageLayerNonDistributable, //nolint:staticcheck // ignore SA1019: Non-distributable layers are deprecated, and not recommended for future use.
209208
images.MediaTypeDockerSchema2LayerForeignGzip: ocispecs.MediaTypeImageLayerNonDistributableGzip, //nolint:staticcheck // ignore SA1019: Non-distributable layers are deprecated, and not recommended for future use.
210-
mediaTypeImageLayerZstd: mediaTypeImageLayerZstd,
211-
mediaTypeDockerSchema2LayerZstd: mediaTypeImageLayerZstd,
209+
ocispecs.MediaTypeImageLayerZstd: ocispecs.MediaTypeImageLayerZstd,
210+
mediaTypeDockerSchema2LayerZstd: ocispecs.MediaTypeImageLayerZstd,
212211
}
213212

214213
func convertLayerMediaType(ctx context.Context, mediaType string, oci bool) string {

util/compression/zstd.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ func (c zstdType) NeedsForceCompression() bool {
4747
}
4848

4949
func (c zstdType) MediaType() string {
50-
return mediaTypeImageLayerZstd
50+
return ocispecs.MediaTypeImageLayerZstd
5151
}
5252

5353
func (c zstdType) String() string {

util/winlayers/applier.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ type winApplier struct {
3737
}
3838

3939
func (s *winApplier) Apply(ctx context.Context, desc ocispecs.Descriptor, mounts []mount.Mount, opts ...diff.ApplyOpt) (d ocispecs.Descriptor, err error) {
40+
// HACK:, containerd doesn't know about vnd.docker.image.rootfs.diff.tar.zstd, but that
41+
// media type is compatible w/ the oci type, so just lie and say it's the oci type
42+
if desc.MediaType == images.MediaTypeDockerSchema2Layer+".zstd" {
43+
desc.MediaType = ocispecs.MediaTypeImageLayerZstd
44+
}
45+
4046
if !hasWindowsLayerMode(ctx) {
4147
return s.apply(ctx, desc, mounts, opts...)
4248
}

0 commit comments

Comments
 (0)