1
0
mirror of https://github.com/goreleaser/goreleaser.git synced 2025-01-30 04:50:45 +02:00

feat(archive): support tar.zst (#4825)

closes #4820
This commit is contained in:
Carlos Alexandro Becker 2024-05-03 10:29:55 -03:00 committed by GitHub
parent fbddb7081d
commit e538341179
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 214 additions and 4 deletions

2
go.mod
View File

@ -215,7 +215,7 @@ require (
github.com/jmespath/go-jmespath v0.4.0 // indirect
github.com/josharian/intern v1.0.0 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/klauspost/compress v1.17.7 // indirect
github.com/klauspost/compress v1.17.7
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/letsencrypt/boulder v0.0.0-20231026200631-000cd05d5491 // indirect

View File

@ -10,6 +10,7 @@ import (
"github.com/goreleaser/goreleaser/pkg/archive/tar"
"github.com/goreleaser/goreleaser/pkg/archive/targz"
"github.com/goreleaser/goreleaser/pkg/archive/tarxz"
"github.com/goreleaser/goreleaser/pkg/archive/tarzst"
"github.com/goreleaser/goreleaser/pkg/archive/zip"
"github.com/goreleaser/goreleaser/pkg/config"
)
@ -31,6 +32,8 @@ func New(w io.Writer, format string) (Archive, error) {
return gzip.New(w), nil
case "tar.xz", "txz":
return tarxz.New(w), nil
case "tar.zst":
return tarzst.New(w), nil
case "zip":
return zip.New(w), nil
}

View File

@ -18,7 +18,7 @@ func TestArchive(t *testing.T) {
require.NoError(t, empty.Close())
require.NoError(t, os.Mkdir(folder+"/folder-inside", 0o755))
for _, format := range []string{"tar.gz", "zip", "gz", "tar.xz", "tar", "tgz", "txz"} {
for _, format := range []string{"tar.gz", "zip", "gz", "tar.xz", "tar", "tgz", "txz", "tar.zst"} {
format := format
t.Run(format, func(t *testing.T) {
f1, err := os.Create(filepath.Join(t.TempDir(), "1.tar"))
@ -37,7 +37,7 @@ func TestArchive(t *testing.T) {
require.NoError(t, archive.Close())
require.NoError(t, f1.Close())
if format == "tar.xz" || format == "txz" || format == "gz" {
if format == "tar.xz" || format == "txz" || format == "gz" || format == "tar.zst" {
_, err := Copying(f1, io.Discard, format)
require.Error(t, err)
return

View File

@ -0,0 +1,40 @@
// Package tarzst implements the Archive interface providing tar.zst archiving
// and compression.
package tarzst
import (
"io"
"github.com/goreleaser/goreleaser/pkg/archive/tar"
"github.com/goreleaser/goreleaser/pkg/config"
"github.com/klauspost/compress/zstd"
)
// Archive as tar.zst.
type Archive struct {
zstw *zstd.Encoder
tw *tar.Archive
}
// New tar.zst archive.
func New(target io.Writer) Archive {
zstw, _ := zstd.NewWriter(target)
tw := tar.New(zstw)
return Archive{
zstw: zstw,
tw: &tw,
}
}
// Close all closeables.
func (a Archive) Close() error {
if err := a.tw.Close(); err != nil {
return err
}
return a.zstw.Close()
}
// Add file to the archive.
func (a Archive) Add(f config.File) error {
return a.tw.Add(f)
}

View File

@ -0,0 +1,157 @@
package tarzst
import (
"archive/tar"
"io"
"io/fs"
"os"
"path/filepath"
"testing"
"time"
"github.com/goreleaser/goreleaser/pkg/config"
"github.com/klauspost/compress/zstd"
"github.com/stretchr/testify/require"
)
func TestTarZstFile(t *testing.T) {
tmp := t.TempDir()
f, err := os.Create(filepath.Join(tmp, "test.tar.zst"))
require.NoError(t, err)
defer f.Close() // nolint: errcheck
archive := New(f)
defer archive.Close() // nolint: errcheck
require.Error(t, archive.Add(config.File{
Source: "../testdata/nope.txt",
Destination: "nope.txt",
}))
require.NoError(t, archive.Add(config.File{
Source: "../testdata/foo.txt",
Destination: "foo.txt",
}))
require.NoError(t, archive.Add(config.File{
Source: "../testdata/sub1",
Destination: "sub1",
}))
require.NoError(t, archive.Add(config.File{
Source: "../testdata/sub1/bar.txt",
Destination: "sub1/bar.txt",
}))
require.NoError(t, archive.Add(config.File{
Source: "../testdata/sub1/executable",
Destination: "sub1/executable",
}))
require.NoError(t, archive.Add(config.File{
Source: "../testdata/sub1/sub2",
Destination: "sub1/sub2",
}))
require.NoError(t, archive.Add(config.File{
Source: "../testdata/sub1/sub2/subfoo.txt",
Destination: "sub1/sub2/subfoo.txt",
}))
require.NoError(t, archive.Add(config.File{
Source: "../testdata/regular.txt",
Destination: "regular.txt",
}))
require.NoError(t, archive.Add(config.File{
Source: "../testdata/link.txt",
Destination: "link.txt",
}))
require.NoError(t, archive.Close())
require.Error(t, archive.Add(config.File{
Source: "tar.go",
Destination: "tar.go",
}))
require.NoError(t, f.Close())
f, err = os.Open(f.Name())
require.NoError(t, err)
defer f.Close() // nolint: errcheck
info, err := f.Stat()
require.NoError(t, err)
require.Lessf(t, info.Size(), int64(500), "archived file should be smaller than %d", info.Size())
zstf, err := zstd.NewReader(f)
require.NoError(t, err)
var paths []string
r := tar.NewReader(zstf)
for {
next, err := r.Next()
if err == io.EOF {
break
}
require.NoError(t, err)
paths = append(paths, next.Name)
if next.Name == "sub1/executable" {
ex := next.FileInfo().Mode()&0o111 != 0
require.True(t, ex, "expected executable permissions, got %s", next.FileInfo().Mode())
}
if next.Name == "link.txt" {
require.Equal(t, "regular.txt", next.Linkname)
}
}
require.Equal(t, []string{
"foo.txt",
"sub1",
"sub1/bar.txt",
"sub1/executable",
"sub1/sub2",
"sub1/sub2/subfoo.txt",
"regular.txt",
"link.txt",
}, paths)
}
func TestTarZstFileInfo(t *testing.T) {
now := time.Now().Truncate(time.Second)
f, err := os.Create(filepath.Join(t.TempDir(), "test.tar.gz"))
require.NoError(t, err)
defer f.Close() // nolint: errcheck
archive := New(f)
defer archive.Close() // nolint: errcheck
require.NoError(t, archive.Add(config.File{
Source: "../testdata/foo.txt",
Destination: "nope.txt",
Info: config.FileInfo{
Mode: 0o755,
Owner: "carlos",
Group: "root",
ParsedMTime: now,
},
}))
require.NoError(t, archive.Close())
require.NoError(t, f.Close())
f, err = os.Open(f.Name())
require.NoError(t, err)
defer f.Close() // nolint: errcheck
zstf, err := zstd.NewReader(f)
require.NoError(t, err)
var found int
r := tar.NewReader(zstf)
for {
next, err := r.Next()
if err == io.EOF {
break
}
require.NoError(t, err)
found++
require.Equal(t, "nope.txt", next.Name)
require.Equal(t, now, next.ModTime)
require.Equal(t, fs.FileMode(0o755), next.FileInfo().Mode())
require.Equal(t, "carlos", next.Uname)
require.Equal(t, 0, next.Uid)
require.Equal(t, "root", next.Gname)
require.Equal(t, 0, next.Gid)
}
require.Equal(t, 1, found)
}

View File

@ -24,7 +24,17 @@ archives:
# If format is `binary`, no archives are created and the binaries are instead
# uploaded directly.
#
# Valid options are `tar.gz`, `tgz`, `tar.xz`, `txz`, tar`, `gz`, `zip`, and `binary`.
# Valid options are:
# - `tar.gz`
# - `tgz`
# - `tar.xz`
# - `txz`
# - `tar.zst` (Since v1.26)
# - `tar`
# - `gz`
# - `zip`
# - `binary`
#
# Default: 'tar.gz'
format: zip