diff options
| -rw-r--r-- | pkg/archive.go | 67 | ||||
| -rw-r--r-- | pkg/archive_test.go | 53 | ||||
| -rw-r--r-- | pkg/pkg_test.go | 10 | ||||
| -rw-r--r-- | pkg/tar_test.go | 7 |
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) } |
