internal/pkg: tar optional file
All checks were successful
Test / Create distribution (push) Successful in 47s
Test / Sandbox (push) Successful in 2m49s
Test / ShareFS (push) Successful in 4m46s
Test / Sandbox (race detector) (push) Successful in 5m17s
Test / Hpkg (push) Successful in 5m19s
Test / Hakurei (push) Successful in 5m31s
Test / Hakurei (race detector) (push) Successful in 7m27s
Test / Flake checks (push) Successful in 1m39s

This allows tar to take a single-file directory Artifact as input.

Signed-off-by: Ophestra <cat@gensokyo.uk>
This commit is contained in:
2026-01-07 22:15:55 +09:00
parent e8dda70c41
commit c86ff02d8d
5 changed files with 187 additions and 55 deletions

View File

@@ -209,6 +209,7 @@ func TestFlatten(t *testing.T) {
"identifier": {Mode: fs.ModeDir | 0700}, "identifier": {Mode: fs.ModeDir | 0700},
"identifier/-P_1iw6yVq_letMHncqcExSE0bYcDhYI5OdY6b1wKASf-Corufvj__XTBUq2Qd2a": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM")}, "identifier/-P_1iw6yVq_letMHncqcExSE0bYcDhYI5OdY6b1wKASf-Corufvj__XTBUq2Qd2a": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM")},
"identifier/0_rRxIqbX9LK9L_KDbuafotFz6HFkonNgO9gXhK1asM_Y1Pxn0amg756vRTo6m74": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM")},
"temp": {Mode: fs.ModeDir | 0700}, "temp": {Mode: fs.ModeDir | 0700},
"work": {Mode: fs.ModeDir | 0700}, "work": {Mode: fs.ModeDir | 0700},
@@ -230,10 +231,11 @@ func TestFlatten(t *testing.T) {
{Mode: fs.ModeDir | 0700, Path: "identifier"}, {Mode: fs.ModeDir | 0700, Path: "identifier"},
{Mode: fs.ModeSymlink | 0777, Path: "identifier/-P_1iw6yVq_letMHncqcExSE0bYcDhYI5OdY6b1wKASf-Corufvj__XTBUq2Qd2a", Data: []byte("../checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM")}, {Mode: fs.ModeSymlink | 0777, Path: "identifier/-P_1iw6yVq_letMHncqcExSE0bYcDhYI5OdY6b1wKASf-Corufvj__XTBUq2Qd2a", Data: []byte("../checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM")},
{Mode: fs.ModeSymlink | 0777, Path: "identifier/0_rRxIqbX9LK9L_KDbuafotFz6HFkonNgO9gXhK1asM_Y1Pxn0amg756vRTo6m74", Data: []byte("../checksum/cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM")},
{Mode: fs.ModeDir | 0700, Path: "temp"}, {Mode: fs.ModeDir | 0700, Path: "temp"},
{Mode: fs.ModeDir | 0700, Path: "work"}, {Mode: fs.ModeDir | 0700, Path: "work"},
}, pkg.MustDecode("PrNWkHqtSmdtVecctlI9Xf63dQPIyyLBIMCtRAP2-VqF9u1oQ8ydV7-WPbzEvMkG"), nil}, }, pkg.MustDecode("sxbgyX-bPoezbha214n2lbQhiVfTUBkhZ0EX6zI7mmkMdrCdwuMwhMBJphLQsy94"), nil},
{"sample tar expand step unpack", fstest.MapFS{ {"sample tar expand step unpack", fstest.MapFS{
".": {Mode: fs.ModeDir | 0500}, ".": {Mode: fs.ModeDir | 0500},
@@ -254,6 +256,7 @@ func TestFlatten(t *testing.T) {
"identifier": {Mode: fs.ModeDir | 0700}, "identifier": {Mode: fs.ModeDir | 0700},
"identifier/-P_1iw6yVq_letMHncqcExSE0bYcDhYI5OdY6b1wKASf-Corufvj__XTBUq2Qd2a": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/CH3AiUrCCcVOjOYLaMKKK1Da78989JtfHeIsxMzWOQFiN4mrCLDYpoDxLWqJWCUN")}, "identifier/-P_1iw6yVq_letMHncqcExSE0bYcDhYI5OdY6b1wKASf-Corufvj__XTBUq2Qd2a": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/CH3AiUrCCcVOjOYLaMKKK1Da78989JtfHeIsxMzWOQFiN4mrCLDYpoDxLWqJWCUN")},
"identifier/0_rRxIqbX9LK9L_KDbuafotFz6HFkonNgO9gXhK1asM_Y1Pxn0amg756vRTo6m74": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/CH3AiUrCCcVOjOYLaMKKK1Da78989JtfHeIsxMzWOQFiN4mrCLDYpoDxLWqJWCUN")},
"temp": {Mode: fs.ModeDir | 0700}, "temp": {Mode: fs.ModeDir | 0700},
"work": {Mode: fs.ModeDir | 0700}, "work": {Mode: fs.ModeDir | 0700},
@@ -266,10 +269,11 @@ func TestFlatten(t *testing.T) {
{Mode: fs.ModeDir | 0700, Path: "identifier"}, {Mode: fs.ModeDir | 0700, Path: "identifier"},
{Mode: fs.ModeSymlink | 0777, Path: "identifier/-P_1iw6yVq_letMHncqcExSE0bYcDhYI5OdY6b1wKASf-Corufvj__XTBUq2Qd2a", Data: []byte("../checksum/CH3AiUrCCcVOjOYLaMKKK1Da78989JtfHeIsxMzWOQFiN4mrCLDYpoDxLWqJWCUN")}, {Mode: fs.ModeSymlink | 0777, Path: "identifier/-P_1iw6yVq_letMHncqcExSE0bYcDhYI5OdY6b1wKASf-Corufvj__XTBUq2Qd2a", Data: []byte("../checksum/CH3AiUrCCcVOjOYLaMKKK1Da78989JtfHeIsxMzWOQFiN4mrCLDYpoDxLWqJWCUN")},
{Mode: fs.ModeSymlink | 0777, Path: "identifier/0_rRxIqbX9LK9L_KDbuafotFz6HFkonNgO9gXhK1asM_Y1Pxn0amg756vRTo6m74", Data: []byte("../checksum/CH3AiUrCCcVOjOYLaMKKK1Da78989JtfHeIsxMzWOQFiN4mrCLDYpoDxLWqJWCUN")},
{Mode: fs.ModeDir | 0700, Path: "temp"}, {Mode: fs.ModeDir | 0700, Path: "temp"},
{Mode: fs.ModeDir | 0700, Path: "work"}, {Mode: fs.ModeDir | 0700, Path: "work"},
}, pkg.MustDecode("YZUoGdMwmfW5sQWto9hQgMKah648rHKck8Ds_GGnqgCBpTAiZKOefpHCKnvktfYh"), nil}, }, pkg.MustDecode("4I8wx_h7NSJTlG5lbuz-GGEXrOg0GYC3M_503LYEBhv5XGWXfNIdIY9Q3eVSYldX"), nil},
{"testtool", fstest.MapFS{ {"testtool", fstest.MapFS{
".": {Mode: fs.ModeDir | 0500}, ".": {Mode: fs.ModeDir | 0500},

View File

@@ -300,26 +300,6 @@ func newTesttool() (
).String(), testtoolBin, 0500) ).String(), testtoolBin, 0500)
}, },
}} }}
testtoolDestroy = func(t *testing.T, base *check.Absolute, c *pkg.Cache) { testtoolDestroy = newDestroyArtifactFunc(testtool)
if pathname, checksum, err := c.Cure(testtool); err != nil {
t.Fatalf("Cure: error = %v", err)
} else if err = os.Remove(pathname.String()); err != nil {
t.Fatal(err)
} else {
p := base.Append(
"checksum",
pkg.Encode(checksum),
)
if err = os.Chmod(p.Append("bin").String(), 0700); err != nil {
t.Fatal(err)
}
if err = os.Chmod(p.String(), 0700); err != nil {
t.Fatal(err)
}
if err = os.RemoveAll(p.String()); err != nil {
t.Fatal(err)
}
}
}
return return
} }

View File

@@ -117,6 +117,59 @@ func newStubFile(
} }
} }
// destroyArtifact removes all traces of an [Artifact] from the on-disk cache.
// Do not use this in a test case without a very good reason to do so.
func destroyArtifact(
t *testing.T,
base *check.Absolute,
c *pkg.Cache,
a pkg.Artifact,
) {
if pathname, checksum, err := c.Cure(a); err != nil {
t.Fatalf("Cure: error = %v", err)
} else if err = os.Remove(pathname.String()); err != nil {
t.Fatal(err)
} else {
p := base.Append(
"checksum",
pkg.Encode(checksum),
)
if err = filepath.WalkDir(p.String(), func(
path string,
d fs.DirEntry,
err error,
) error {
if err != nil {
return err
}
if d.IsDir() {
return os.Chmod(path, 0700)
}
return nil
}); err != nil && !errors.Is(err, os.ErrNotExist) {
t.Fatal(err)
}
if err = os.RemoveAll(p.String()); err != nil {
t.Fatal(err)
}
}
}
// newDestroyArtifactFunc returns a function that calls destroyArtifact.
func newDestroyArtifactFunc(a pkg.Artifact) func(
t *testing.T,
base *check.Absolute,
c *pkg.Cache,
) {
return func(
t *testing.T,
base *check.Absolute,
c *pkg.Cache,
) {
destroyArtifact(t, base, c, a)
}
}
func TestIdent(t *testing.T) { func TestIdent(t *testing.T) {
t.Parallel() t.Parallel()

View File

@@ -28,15 +28,17 @@ const (
// A tarArtifact is an [Artifact] unpacking a tarball backed by a [File]. // A tarArtifact is an [Artifact] unpacking a tarball backed by a [File].
type tarArtifact struct { type tarArtifact struct {
// Caller-supplied backing tarball. // Caller-supplied backing tarball.
f File f Artifact
// Compression on top of the tarball. // Compression on top of the tarball.
compression uint64 compression uint64
} }
// NewTar returns a new [Artifact] backed by the supplied [File] and // NewTar returns a new [Artifact] backed by the supplied [Artifact] and
// compression method. // compression method. If f implements [File], its data might be used directly,
func NewTar(f File, compression uint64) Artifact { // eliminating the roundtrip to vfs. If f is a directory, it must contain a
return &tarArtifact{f: f, compression: compression} // single regular file.
func NewTar(a Artifact, compression uint64) Artifact {
return &tarArtifact{a, compression}
} }
// NewHTTPGetTar is abbreviation for NewHTTPGet passed to NewTar. // NewHTTPGetTar is abbreviation for NewHTTPGet passed to NewTar.
@@ -76,26 +78,46 @@ func (a *tarArtifact) Cure(c *CureContext) (err error) {
temp := c.GetTempDir() temp := c.GetTempDir()
var tr io.ReadCloser var tr io.ReadCloser
{ if file, ok := a.f.(File); ok {
if tr, err = c.OpenFile(file); err != nil {
if tr, err = c.OpenFile(a.f); err != nil { return
}
} else {
var pathname *check.Absolute
if pathname, _, err = c.Cure(a.f); err != nil {
return
}
var entries []os.DirEntry
if entries, err = os.ReadDir(pathname.String()); err != nil {
return
}
if len(entries) != 1 || !entries[0].Type().IsRegular() {
return errors.New(
"input directory does not contain a single regular file",
)
} else {
pathname = pathname.Append(entries[0].Name())
}
if tr, err = os.Open(pathname.String()); err != nil {
return return
} }
defer func(f io.ReadCloser) {
closeErr := f.Close()
if err == nil {
err = closeErr
}
}(tr)
tr = io.NopCloser(tr)
} }
defer func() { defer func(f io.ReadCloser) {
closeErr := tr.Close() closeErr := tr.Close()
if err == nil { if err == nil {
err = closeErr err = closeErr
} }
}()
closeErr = f.Close()
if err == nil {
err = closeErr
}
}(tr)
tr = io.NopCloser(tr)
switch a.compression { switch a.compression {
case TarUncompressed: case TarUncompressed:

View File

@@ -5,12 +5,15 @@ import (
"bytes" "bytes"
"compress/gzip" "compress/gzip"
"crypto/sha512" "crypto/sha512"
"errors"
"io/fs" "io/fs"
"net/http" "net/http"
"os"
"testing" "testing"
"testing/fstest" "testing/fstest"
"hakurei.app/container/check" "hakurei.app/container/check"
"hakurei.app/container/stub"
"hakurei.app/internal/pkg" "hakurei.app/internal/pkg"
) )
@@ -37,7 +40,7 @@ func TestTar(t *testing.T) {
}, pkg.MustDecode( }, pkg.MustDecode(
"cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM", "cTw0h3AmYe7XudSoyEMByduYXqGi-N5ZkTZ0t9K5elsu3i_jNIVF5T08KR1roBFM",
)) ))
}, pkg.MustDecode("PrNWkHqtSmdtVecctlI9Xf63dQPIyyLBIMCtRAP2-VqF9u1oQ8ydV7-WPbzEvMkG")}, }, pkg.MustDecode("sxbgyX-bPoezbha214n2lbQhiVfTUBkhZ0EX6zI7mmkMdrCdwuMwhMBJphLQsy94")},
{"http expand", nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) { {"http expand", nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
checkTarHTTP(t, base, c, fstest.MapFS{ checkTarHTTP(t, base, c, fstest.MapFS{
@@ -48,7 +51,7 @@ func TestTar(t *testing.T) {
}, pkg.MustDecode( }, pkg.MustDecode(
"CH3AiUrCCcVOjOYLaMKKK1Da78989JtfHeIsxMzWOQFiN4mrCLDYpoDxLWqJWCUN", "CH3AiUrCCcVOjOYLaMKKK1Da78989JtfHeIsxMzWOQFiN4mrCLDYpoDxLWqJWCUN",
)) ))
}, pkg.MustDecode("YZUoGdMwmfW5sQWto9hQgMKah648rHKck8Ds_GGnqgCBpTAiZKOefpHCKnvktfYh")}, }, pkg.MustDecode("4I8wx_h7NSJTlG5lbuz-GGEXrOg0GYC3M_503LYEBhv5XGWXfNIdIY9Q3eVSYldX")},
}) })
} }
@@ -115,18 +118,88 @@ func checkTarHTTP(
t.Fatalf("Ident: %s, want %s", pkg.Encode(id), pkg.Encode(wantIdent)) t.Fatalf("Ident: %s, want %s", pkg.Encode(id), pkg.Encode(wantIdent))
} }
wantPathname := base.Append( tarDir := stubArtifact{
"identifier", kind: pkg.KindExec,
pkg.Encode(wantIdent), params: []byte("directory containing a single regular file"),
) cure: func(c *pkg.CureContext) error {
if pathname, checksum, err := c.Cure(a); err != nil { work := c.GetWorkDir()
t.Fatalf("Cure: error = %v", err) if err := os.MkdirAll(work.String(), 0700); err != nil {
} else if !pathname.Is(wantPathname) { return err
t.Fatalf("Cure: %q, want %q", pathname, wantPathname) }
} else if checksum != wantChecksum { return os.WriteFile(
t.Fatalf("Cure: %v", &pkg.ChecksumMismatchError{ work.Append("sample.tar.gz").String(),
Got: checksum, []byte(testdata),
Want: wantChecksum, 0400,
}) )
},
} }
tarDirMulti := stubArtifact{
kind: pkg.KindExec,
params: []byte("directory containing a multiple entries"),
cure: func(c *pkg.CureContext) error {
work := c.GetWorkDir()
if err := os.MkdirAll(work.Append(
"garbage",
).String(), 0700); err != nil {
return err
}
return os.WriteFile(
work.Append("sample.tar.gz").String(),
[]byte(testdata),
0400,
)
},
}
tarDirType := stubArtifact{
kind: pkg.KindExec,
params: []byte("directory containing a symbolic link"),
cure: func(c *pkg.CureContext) error {
work := c.GetWorkDir()
if err := os.MkdirAll(work.String(), 0700); err != nil {
return err
}
return os.Symlink(
work.String(),
work.Append("sample.tar.gz").String(),
)
},
}
// destroy these to avoid including it in flatten test case
defer newDestroyArtifactFunc(tarDir)(t, base, c)
defer newDestroyArtifactFunc(tarDirMulti)(t, base, c)
defer newDestroyArtifactFunc(tarDirType)(t, base, c)
cureMany(t, c, []cureStep{
{"file", a, base.Append(
"identifier",
pkg.Encode(wantIdent),
), wantChecksum, nil},
{"directory", pkg.NewTar(
tarDir,
pkg.TarGzip,
), ignorePathname, wantChecksum, nil},
{"multiple entries", pkg.NewTar(
tarDirMulti,
pkg.TarGzip,
), nil, pkg.Checksum{}, errors.New(
"input directory does not contain a single regular file",
)},
{"bad type", pkg.NewTar(
tarDirType,
pkg.TarGzip,
), nil, pkg.Checksum{}, errors.New(
"input directory does not contain a single regular file",
)},
{"error passthrough", pkg.NewTar(stubArtifact{
kind: pkg.KindExec,
params: []byte("doomed artifact"),
cure: func(c *pkg.CureContext) error {
return stub.UniqueError(0xcafe)
},
}, pkg.TarGzip), nil, pkg.Checksum{}, stub.UniqueError(0xcafe)},
})
} }