Skip to content

Commit efbed70

Browse files
committed
move compression to a separate package
TODO: align with containerd pkg/archive/compression, which - also moved the utilities to Compress/Decompress streams - changed the Extension to not include ".tar" Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
1 parent b795214 commit efbed70

8 files changed

+196
-157
lines changed

archive.go

Lines changed: 27 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@ import (
88
"compress/bzip2"
99
"compress/gzip"
1010
"context"
11-
"encoding/binary"
1211
"errors"
1312
"fmt"
1413
"io"
@@ -27,6 +26,8 @@ import (
2726
"github.com/moby/patternmatcher"
2827
"github.com/moby/sys/sequential"
2928
"github.com/moby/sys/user"
29+
30+
"github.com/moby/go-archive/compression"
3031
)
3132

3233
// ImpliedDirectoryMode represents the mode (Unix permissions) applied to directories that are implied by files in a
@@ -43,7 +44,9 @@ const ImpliedDirectoryMode = 0o755
4344

4445
type (
4546
// Compression is the state represents if compressed or not.
46-
Compression int
47+
//
48+
// Deprecated: use [compression.Compression].
49+
Compression = compression.Compression
4750
// WhiteoutFormat is the format of whiteouts unpacked
4851
WhiteoutFormat int
4952

@@ -56,7 +59,7 @@ type (
5659
TarOptions struct {
5760
IncludeFiles []string
5861
ExcludePatterns []string
59-
Compression Compression
62+
Compression compression.Compression
6063
NoLchown bool
6164
IDMap user.IdentityMapping
6265
ChownOpts *ChownOpts
@@ -100,11 +103,11 @@ func NewDefaultArchiver() *Archiver {
100103
type breakoutError error
101104

102105
const (
103-
Uncompressed Compression = 0 // Uncompressed represents the uncompressed.
104-
Bzip2 Compression = 1 // Bzip2 is bzip2 compression algorithm.
105-
Gzip Compression = 2 // Gzip is gzip compression algorithm.
106-
Xz Compression = 3 // Xz is xz compression algorithm.
107-
Zstd Compression = 4 // Zstd is zstd compression algorithm.
106+
Uncompressed = compression.None // Deprecated: use [compression.None].
107+
Bzip2 = compression.Bzip2 // Deprecated: use [compression.Bzip2].
108+
Gzip = compression.Gzip // Deprecated: use [compression.Gzip].
109+
Xz = compression.Xz // Deprecated: use [compression.Xz].
110+
Zstd = compression.Zstd // Deprecated: use [compression.Zstd].
108111
)
109112

110113
const (
@@ -130,63 +133,11 @@ func IsArchivePath(path string) bool {
130133
return err == nil
131134
}
132135

133-
const (
134-
zstdMagicSkippableStart = 0x184D2A50
135-
zstdMagicSkippableMask = 0xFFFFFFF0
136-
)
137-
138-
var (
139-
bzip2Magic = []byte{0x42, 0x5A, 0x68}
140-
gzipMagic = []byte{0x1F, 0x8B, 0x08}
141-
xzMagic = []byte{0xFD, 0x37, 0x7A, 0x58, 0x5A, 0x00}
142-
zstdMagic = []byte{0x28, 0xb5, 0x2f, 0xfd}
143-
)
144-
145-
type matcher = func([]byte) bool
146-
147-
func magicNumberMatcher(m []byte) matcher {
148-
return func(source []byte) bool {
149-
return bytes.HasPrefix(source, m)
150-
}
151-
}
152-
153-
// zstdMatcher detects zstd compression algorithm.
154-
// Zstandard compressed data is made of one or more frames.
155-
// There are two frame formats defined by Zstandard: Zstandard frames and Skippable frames.
156-
// See https://datatracker.ietf.org/doc/html/rfc8878#section-3 for more details.
157-
func zstdMatcher() matcher {
158-
return func(source []byte) bool {
159-
if bytes.HasPrefix(source, zstdMagic) {
160-
// Zstandard frame
161-
return true
162-
}
163-
// skippable frame
164-
if len(source) < 8 {
165-
return false
166-
}
167-
// magic number from 0x184D2A50 to 0x184D2A5F.
168-
if binary.LittleEndian.Uint32(source[:4])&zstdMagicSkippableMask == zstdMagicSkippableStart {
169-
return true
170-
}
171-
return false
172-
}
173-
}
174-
175136
// DetectCompression detects the compression algorithm of the source.
176-
func DetectCompression(source []byte) Compression {
177-
compressionMap := map[Compression]matcher{
178-
Bzip2: magicNumberMatcher(bzip2Magic),
179-
Gzip: magicNumberMatcher(gzipMagic),
180-
Xz: magicNumberMatcher(xzMagic),
181-
Zstd: zstdMatcher(),
182-
}
183-
for _, compression := range []Compression{Bzip2, Gzip, Xz, Zstd} {
184-
fn := compressionMap[compression]
185-
if fn(source) {
186-
return compression
187-
}
188-
}
189-
return Uncompressed
137+
//
138+
// Deprecated: use [compression.Detect].
139+
func DetectCompression(source []byte) compression.Compression {
140+
return compression.Detect(source)
190141
}
191142

192143
func xzDecompress(ctx context.Context, archive io.Reader) (io.ReadCloser, error) {
@@ -278,12 +229,12 @@ func DecompressStream(archive io.Reader) (io.ReadCloser, error) {
278229
return nil, err
279230
}
280231

281-
switch comp := DetectCompression(bs); comp {
282-
case Uncompressed:
232+
switch comp := compression.Detect(bs); comp {
233+
case compression.None:
283234
return &readCloserWrapper{
284235
Reader: buf,
285236
}, nil
286-
case Gzip:
237+
case compression.Gzip:
287238
ctx, cancel := context.WithCancel(context.Background())
288239

289240
gzReader, err := gzDecompress(ctx, buf)
@@ -298,12 +249,12 @@ func DecompressStream(archive io.Reader) (io.ReadCloser, error) {
298249
return gzReader.Close()
299250
},
300251
}, nil
301-
case Bzip2:
252+
case compression.Bzip2:
302253
bz2Reader := bzip2.NewReader(buf)
303254
return &readCloserWrapper{
304255
Reader: bz2Reader,
305256
}, nil
306-
case Xz:
257+
case compression.Xz:
307258
ctx, cancel := context.WithCancel(context.Background())
308259

309260
xzReader, err := xzDecompress(ctx, buf)
@@ -319,7 +270,7 @@ func DecompressStream(archive io.Reader) (io.ReadCloser, error) {
319270
return xzReader.Close()
320271
},
321272
}, nil
322-
case Zstd:
273+
case compression.Zstd:
323274
zstdReader, err := zstd.NewReader(buf)
324275
if err != nil {
325276
return nil, err
@@ -343,13 +294,13 @@ type nopWriteCloser struct {
343294
func (nopWriteCloser) Close() error { return nil }
344295

345296
// CompressStream compresses the dest with specified compression algorithm.
346-
func CompressStream(dest io.Writer, comp Compression) (io.WriteCloser, error) {
297+
func CompressStream(dest io.Writer, comp compression.Compression) (io.WriteCloser, error) {
347298
switch comp {
348-
case Uncompressed:
299+
case compression.None:
349300
return nopWriteCloser{dest}, nil
350-
case Gzip:
301+
case compression.Gzip:
351302
return gzip.NewWriter(dest), nil
352-
case Bzip2, Xz:
303+
case compression.Bzip2, compression.Xz:
353304
// archive/bzip2 does not support writing, and there is no xz support at all
354305
// However, this is not a problem as docker only currently generates gzipped tars
355306
return nil, fmt.Errorf("unsupported compression format: %s", (&comp).Extension())
@@ -446,23 +397,6 @@ func ReplaceFileTarWrapper(inputTarStream io.ReadCloser, mods map[string]TarModi
446397
return pipeReader
447398
}
448399

449-
// Extension returns the extension of a file that uses the specified compression algorithm.
450-
func (c *Compression) Extension() string {
451-
switch *c {
452-
case Uncompressed:
453-
return "tar"
454-
case Bzip2:
455-
return "tar.bz2"
456-
case Gzip:
457-
return "tar.gz"
458-
case Xz:
459-
return "tar.xz"
460-
case Zstd:
461-
return "tar.zst"
462-
}
463-
return ""
464-
}
465-
466400
// assert that we implement [tar.FileInfoNames].
467401
var _ tar.FileInfoNames = (*nosysFileInfo)(nil)
468402

@@ -894,7 +828,7 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader io.Reader, o
894828

895829
// Tar creates an archive from the directory at `path`, and returns it as a
896830
// stream of bytes.
897-
func Tar(path string, comp Compression) (io.ReadCloser, error) {
831+
func Tar(path string, comp compression.Compression) (io.ReadCloser, error) {
898832
return TarWithOptions(path, &TarOptions{Compression: comp})
899833
}
900834

@@ -1322,7 +1256,7 @@ func untarHandler(tarArchive io.Reader, dest string, options *TarOptions, decomp
13221256
// TarUntar is a convenience function which calls Tar and Untar, with the output of one piped into the other.
13231257
// If either Tar or Untar fails, TarUntar aborts and returns the error.
13241258
func (archiver *Archiver) TarUntar(src, dst string) error {
1325-
archive, err := Tar(src, Uncompressed)
1259+
archive, err := Tar(src, compression.None)
13261260
if err != nil {
13271261
return err
13281262
}

archive_test.go

Lines changed: 8 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import (
2222
"gotest.tools/v3/assert"
2323
is "gotest.tools/v3/assert/cmp"
2424
"gotest.tools/v3/skip"
25+
26+
"github.com/moby/go-archive/compression"
2527
)
2628

2729
var defaultArchiver = NewDefaultArchiver()
@@ -170,7 +172,7 @@ func TestCompressStreamXzUnsupported(t *testing.T) {
170172
}
171173
defer dest.Close()
172174

173-
_, err = CompressStream(dest, Xz)
175+
_, err = CompressStream(dest, compression.Xz)
174176
if err == nil {
175177
t.Fatalf("Should fail as xz is unsupported for compression format.")
176178
}
@@ -183,7 +185,7 @@ func TestCompressStreamBzip2Unsupported(t *testing.T) {
183185
}
184186
defer dest.Close()
185187

186-
_, err = CompressStream(dest, Bzip2)
188+
_, err = CompressStream(dest, compression.Bzip2)
187189
if err == nil {
188190
t.Fatalf("Should fail as bzip2 is unsupported for compression format.")
189191
}
@@ -202,25 +204,6 @@ func TestCompressStreamInvalid(t *testing.T) {
202204
}
203205
}
204206

205-
func TestExtension(t *testing.T) {
206-
tests := []struct {
207-
compression Compression
208-
extension string
209-
}{
210-
{compression: -1, extension: ""},
211-
{compression: Uncompressed, extension: "tar"},
212-
{compression: Bzip2, extension: "tar.bz2"},
213-
{compression: Gzip, extension: "tar.gz"},
214-
{compression: Xz, extension: "tar.xz"},
215-
{compression: Zstd, extension: "tar.zst"},
216-
}
217-
for _, tc := range tests {
218-
if actual := tc.compression.Extension(); actual != tc.extension {
219-
t.Errorf("expected %s extension got %s", tc.extension, actual)
220-
}
221-
}
222-
}
223-
224207
func TestCmdStreamLargeStderr(t *testing.T) {
225208
cmd := exec.Command("sh", "-c", "dd if=/dev/zero bs=1k count=1000 of=/dev/stderr; echo hello")
226209
out, err := cmdStream(cmd, nil)
@@ -620,7 +603,7 @@ func tarUntar(t *testing.T, origin string, options *TarOptions) ([]Change, error
620603
}
621604
wrap := io.MultiReader(bytes.NewReader(buf), archive)
622605

623-
detectedCompression := DetectCompression(buf)
606+
detectedCompression := compression.Detect(buf)
624607
expected := options.Compression
625608
if detectedCompression.Extension() != expected.Extension() {
626609
return nil, fmt.Errorf("wrong compression detected; expected: %s, got: %s", expected.Extension(), detectedCompression.Extension())
@@ -637,34 +620,6 @@ func tarUntar(t *testing.T, origin string, options *TarOptions) ([]Change, error
637620
return ChangesDirs(origin, tmp)
638621
}
639622

640-
func TestDetectCompressionZstd(t *testing.T) {
641-
// test zstd compression without skippable frames.
642-
compressedData := []byte{
643-
0x28, 0xb5, 0x2f, 0xfd, // magic number of Zstandard frame: 0xFD2FB528
644-
0x04, 0x00, 0x31, 0x00, 0x00, // frame header
645-
0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, // data block "docker"
646-
0x16, 0x0e, 0x21, 0xc3, // content checksum
647-
}
648-
compression := DetectCompression(compressedData)
649-
if compression != Zstd {
650-
t.Fatal("Unexpected compression")
651-
}
652-
// test zstd compression with skippable frames.
653-
hex := []byte{
654-
0x50, 0x2a, 0x4d, 0x18, // magic number of skippable frame: 0x184D2A50 to 0x184D2A5F
655-
0x04, 0x00, 0x00, 0x00, // frame size
656-
0x5d, 0x00, 0x00, 0x00, // user data
657-
0x28, 0xb5, 0x2f, 0xfd, // magic number of Zstandard frame: 0xFD2FB528
658-
0x04, 0x00, 0x31, 0x00, 0x00, // frame header
659-
0x64, 0x6f, 0x63, 0x6b, 0x65, 0x72, // data block "docker"
660-
0x16, 0x0e, 0x21, 0xc3, // content checksum
661-
}
662-
compression = DetectCompression(hex)
663-
if compression != Zstd {
664-
t.Fatal("Unexpected compression")
665-
}
666-
}
667-
668623
func TestTarUntar(t *testing.T) {
669624
origin := t.TempDir()
670625
if err := os.WriteFile(filepath.Join(origin, "1"), []byte("hello world"), 0o700); err != nil {
@@ -677,9 +632,9 @@ func TestTarUntar(t *testing.T) {
677632
t.Fatal(err)
678633
}
679634

680-
for _, c := range []Compression{
681-
Uncompressed,
682-
Gzip,
635+
for _, c := range []compression.Compression{
636+
compression.None,
637+
compression.Gzip,
683638
} {
684639
changes, err := tarUntar(t, origin, &TarOptions{
685640
Compression: c,

archive_unix_test.go

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import (
2020
"gotest.tools/v3/assert"
2121
is "gotest.tools/v3/assert/cmp"
2222
"gotest.tools/v3/skip"
23+
24+
"github.com/moby/go-archive/compression"
2325
)
2426

2527
func TestCanonicalTarName(t *testing.T) {
@@ -82,7 +84,7 @@ func TestTarWithHardLink(t *testing.T) {
8284
defer os.RemoveAll(dest)
8385

8486
// we'll do this in two steps to separate failure
85-
fh, err := Tar(origin, Uncompressed)
87+
fh, err := Tar(origin, compression.None)
8688
assert.NilError(t, err)
8789

8890
// ensure we can read the whole thing with no error, before writing back out
@@ -211,7 +213,7 @@ func TestTarWithBlockCharFifo(t *testing.T) {
211213
defer os.RemoveAll(dest)
212214

213215
// we'll do this in two steps to separate failure
214-
fh, err := Tar(origin, Uncompressed)
216+
fh, err := Tar(origin, compression.None)
215217
assert.NilError(t, err)
216218

217219
// ensure we can read the whole thing with no error, before writing back out
@@ -254,7 +256,7 @@ func TestTarUntarWithXattr(t *testing.T) {
254256
out, err := exec.Command("setcap", "cap_block_suspend+ep", filepath.Join(origin, "2")).CombinedOutput()
255257
assert.NilError(t, err, string(out))
256258

257-
tarball, err := Tar(origin, Uncompressed)
259+
tarball, err := Tar(origin, compression.None)
258260
assert.NilError(t, err)
259261
defer tarball.Close()
260262
rdr := tar.NewReader(tarball)
@@ -275,9 +277,9 @@ func TestTarUntarWithXattr(t *testing.T) {
275277
}
276278
}
277279

278-
for _, c := range []Compression{
279-
Uncompressed,
280-
Gzip,
280+
for _, c := range []compression.Compression{
281+
compression.None,
282+
compression.Gzip,
281283
} {
282284
changes, err := tarUntar(t, origin, &TarOptions{
283285
Compression: c,

0 commit comments

Comments
 (0)