aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorOphestra <cat@gensokyo.uk>2026-09-28 12:27:34 +0900
committerOphestra <cat@gensokyo.uk>2026-09-28 12:27:34 +0900
commitaae629a16bdce3931c508f498db7055b2301d156 (patch)
tree4236b246ccb2ff0157be3ab1e356556a561878e7
parentcfbf2a91f639cac8c6966194c4bbbc0407d96ef8 (diff)
pkg: archive unpack helper
This exposes the behaviour of curing an archiveArtifact without requiring a cache. This can be handy and avoids lots of duplicate code. Signed-off-by: Ophestra <cat@gensokyo.uk>
-rw-r--r--pkg/archive.go67
-rw-r--r--pkg/archive_test.go53
-rw-r--r--pkg/pkg_test.go10
-rw-r--r--pkg/tar_test.go7
4 files changed, 89 insertions, 48 deletions
diff --git a/pkg/archive.go b/pkg/archive.go
index e9f76f5b..532c0df9 100644
--- a/pkg/archive.go
+++ b/pkg/archive.go
@@ -314,36 +314,25 @@ func (archiveArtifact) IsExclusive() bool { return false }
// Revision satisfies [RevisionArtifact] for status behaviour.
func (archiveArtifact) Revision() uint64 { return 0 }
-// Cure cures the [Artifact], producing a directory located at work.
-func (a archiveArtifact) Cure(t *TContext) (err error) {
- var status io.Writer
- if status, err = t.GetStatusWriter(); err != nil {
- return
- }
-
- var r io.ReadCloser
- if r, err = t.Open(a.f); err != nil {
- return
- }
-
- defer func() {
- closeErr := r.Close()
- if err == nil {
- err = closeErr
- }
- }()
-
+// Unpack reads an archive stream from r and unpacks its contents to dir. If
+// writeStatus is non-nil, it is called with a user-facing message after
+// each successfully unpacked entry.
+func Unpack(
+ r io.Reader,
+ dir string,
+ writeStatus func(s string) error,
+) (err error) {
type dirTargetPerm struct {
path string
mode fs.FileMode
}
var madeDirectories []dirTargetPerm
- if err = os.MkdirAll(t.GetWorkDir().String(), 0700); err != nil {
+ if err = os.MkdirAll(dir, 0700); err != nil {
return
}
var root *os.Root
- if root, err = os.OpenRoot(t.GetWorkDir().String()); err != nil {
+ if root, err = os.OpenRoot(dir); err != nil {
return
}
defer func() {
@@ -355,7 +344,6 @@ func (a archiveArtifact) Cure(t *TContext) (err error) {
var header *ArchiveHeader
ar := NewReader(r)
- msg := t.GetMessage()
for header, err = ar.Next(); err == nil; header, err = ar.Next() {
var m string
if header.Mode.IsRegular() {
@@ -422,11 +410,8 @@ func (a archiveArtifact) Cure(t *TContext) (err error) {
return InvalidFileModeError(header.Mode)
}
- if m != "" {
- msg.Verbose(m)
- if _, err = io.WriteString(status, m); err != nil {
- return
- } else if _, err = status.Write([]byte{'\n'}); err != nil {
+ if writeStatus != nil && m != "" {
+ if err = writeStatus(m); err != nil {
return
}
}
@@ -446,6 +431,34 @@ func (a archiveArtifact) Cure(t *TContext) (err error) {
return
}
+// Cure cures the [Artifact], producing a directory located at work.
+func (a archiveArtifact) Cure(t *TContext) (err error) {
+ var status io.Writer
+ if status, err = t.GetStatusWriter(); err != nil {
+ return
+ }
+
+ var r io.ReadCloser
+ if r, err = t.Open(a.f); err != nil {
+ return
+ }
+
+ msg := t.GetMessage()
+ err = Unpack(r, t.GetWorkDir().String(), func(s string) (err error) {
+ msg.Verbose(s)
+ if _, err = io.WriteString(status, s); err != nil {
+ return
+ }
+ _, err = status.Write([]byte{'\n'})
+ return
+ })
+
+ if closeErr := r.Close(); err == nil {
+ err = closeErr
+ }
+ return
+}
+
// CuresExempt exempts the cheap [KindArchive] implementation often found at
// the end of a [FileArtifact] pipeline.
func (archiveArtifact) CuresExempt() {}
diff --git a/pkg/archive_test.go b/pkg/archive_test.go
index 1ca631ee..0171d914 100644
--- a/pkg/archive_test.go
+++ b/pkg/archive_test.go
@@ -2,9 +2,12 @@ package pkg_test
import (
"bytes"
+ "crypto/sha512"
+ "errors"
"io"
"io/fs"
"maps"
+ "net/http"
"reflect"
"testing"
"testing/fstest"
@@ -176,33 +179,53 @@ func TestArchiveArtifact(t *testing.T) {
want := maps.Clone(archiveTestdata)
want["."].Mode = fs.ModeDir | 0500
+ var buf bytes.Buffer
+ if err := pkg.Write(archiveTestdata, ".", &buf); err != nil {
+ t.Fatal(err)
+ }
+ wantChecksum := sha512.Sum384(buf.Bytes())
+ wantChecksumEncode := pkg.Encode(wantChecksum)
+
+ var transport http.Transport
+ client := http.Client{Transport: &transport}
+ transport.RegisterProtocol("file", http.NewFileTransportFS(fstest.MapFS{
+ "testdata": {Data: buf.Bytes(), Mode: 0400},
+ }))
+
checkWithCache(t, []cacheTestCase{
{"unpack", 0, nil, func(t *testing.T, base *check.Absolute, c *pkg.Cache) {
- var buf bytes.Buffer
- if err := pkg.Write(archiveTestdata, ".", &buf); err != nil {
- t.Fatal(err)
- }
-
cureMany(t, c, []cureStep{
{"sample", pkg.NewArchive(
pkg.NewFile("", buf.Bytes()),
), ignorePathname, expectsFS(want), pkg.WNew, nil},
+
+ {"close error passthrough", pkg.NewArchive(
+ pkg.NewHTTPGet(
+ &client,
+ "file:///testdata",
+ pkg.Checksum{},
+ ),
+ ), nil, nil, pkg.WNew, errors.Join(&pkg.ChecksumMismatchError{
+ Got: wantChecksum,
+ })},
})
+
+ dropFaults(t, base)
}, expectsFS{
".": {Mode: fs.ModeDir | 0700},
- "checksum": {Mode: fs.ModeDir | 0700},
- "checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F": {Mode: fs.ModeDir | 0500},
- "checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F/block": {Mode: fs.ModeDir | 0700},
- "checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F/block/uevent": {Mode: 0600},
- "checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F/devices": {Mode: fs.ModeDir | 0700},
- "checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F/devices/empty": {Mode: fs.ModeDir | 0700},
- "checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F/devices/sub": {Mode: fs.ModeDir | 0700},
- "checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F/devices/sub/uevent": {Mode: 0600, Data: []byte("add")},
- "checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F/devices/uevent": {Mode: 0600, Data: []byte("add")},
+ "checksum": {Mode: fs.ModeDir | 0700},
+ "checksum/" + wantChecksumEncode: {Mode: fs.ModeDir | 0500},
+ "checksum/" + wantChecksumEncode + "/block": {Mode: fs.ModeDir | 0700},
+ "checksum/" + wantChecksumEncode + "/block/uevent": {Mode: 0600},
+ "checksum/" + wantChecksumEncode + "/devices": {Mode: fs.ModeDir | 0700},
+ "checksum/" + wantChecksumEncode + "/devices/empty": {Mode: fs.ModeDir | 0700},
+ "checksum/" + wantChecksumEncode + "/devices/sub": {Mode: fs.ModeDir | 0700},
+ "checksum/" + wantChecksumEncode + "/devices/sub/uevent": {Mode: 0600, Data: []byte("add")},
+ "checksum/" + wantChecksumEncode + "/devices/uevent": {Mode: 0600, Data: []byte("add")},
"identifier": {Mode: fs.ModeDir | 0700},
- "identifier/iyvWY0irMoW4AVRnG_du07d_nAEUtbHdeiaAYRdp-vf3wstM9u67IokOsPfU9eyM": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/CBPcoVHuVUTVRCMbRl8J30RSSzm_tyfuXaZ-HlZsanY1sY50meOVmgaWDrGKbx9F")},
+ "identifier/iyvWY0irMoW4AVRnG_du07d_nAEUtbHdeiaAYRdp-vf3wstM9u67IokOsPfU9eyM": {Mode: fs.ModeSymlink | 0777, Data: []byte("../checksum/" + wantChecksumEncode)},
"status": {Mode: fs.ModeDir | 0711},
"status/iyvWY0irMoW4AVRnG_du07d_nAEUtbHdeiaAYRdp-vf3wstM9u67IokOsPfU9eyM": {Mode: 0444, Data: []byte(statusHeader + `drwx------ block
diff --git a/pkg/pkg_test.go b/pkg/pkg_test.go
index 2d5fdf8e..66639b5b 100644
--- a/pkg/pkg_test.go
+++ b/pkg/pkg_test.go
@@ -740,6 +740,16 @@ func newWantScrubError(base *check.Absolute) *pkg.ScrubError {
}
}
+// dropFaults removes all fault entries from a cache under testing.
+func dropFaults(t *testing.T, base *check.Absolute) {
+ fault := base.Append("fault")
+ if err := os.RemoveAll(fault.String()); err != nil {
+ t.Fatal(err)
+ } else if err = os.Mkdir(fault.String(), 0); err != nil {
+ t.Fatal(err)
+ }
+}
+
func TestCache(t *testing.T) {
t.Parallel()
diff --git a/pkg/tar_test.go b/pkg/tar_test.go
index 58ec3aed..e8a42f73 100644
--- a/pkg/tar_test.go
+++ b/pkg/tar_test.go
@@ -249,10 +249,5 @@ func checkTarHTTP(
}), nil, nil, pkg.WNew, errors.Join(stub.UniqueError(0xcafe))},
})
- fault := base.Append("fault")
- if err := os.RemoveAll(fault.String()); err != nil {
- t.Fatal(err)
- } else if err = os.Mkdir(fault.String(), 0); err != nil {
- t.Fatal(err)
- }
+ dropFaults(t, base)
}