Skip to content

Commit 0de1e74

Browse files
Ryan MoranForestEckhardt
Ryan Moran
authored andcommitted
Reorganized vacation package
- introduces vacation.NopArchive type for non-archive files - splits vacation implementation files up into type-named files - renames vacation test files to remove stutter - adds vacation.Archive.WithName option to allow NopArchives to have specified names - uses vacation.Archive.WithName option in postal.Service.Deliver to deliver file to location matching the dependency URI basename - includes application/jar mime-type as NopArchive type
1 parent ec0062f commit 0de1e74

21 files changed

+806
-579
lines changed

postal/service.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,8 @@ func (s Service) Deliver(dependency Dependency, cnbPath, layerPath, platformPath
156156

157157
validatedReader := cargo.NewValidatedReader(bundle, dependency.SHA256)
158158

159-
err = vacation.NewArchive(validatedReader).StripComponents(dependency.StripComponents).Decompress(layerPath)
159+
name := filepath.Base(dependency.URI)
160+
err = vacation.NewArchive(validatedReader).WithName(name).StripComponents(dependency.StripComponents).Decompress(layerPath)
160161
if err != nil {
161162
return err
162163
}

postal/service_test.go

+49
Original file line numberDiff line numberDiff line change
@@ -404,6 +404,7 @@ version = "this is super not semver"
404404
Expect(err).NotTo(HaveOccurred())
405405
Expect(info.Mode()).To(Equal(os.FileMode(0755)))
406406
})
407+
407408
context("when the dependency has a strip-components value set", func() {
408409
it.Before(func() {
409410
var err error
@@ -484,7 +485,55 @@ version = "this is super not semver"
484485
Expect(err).NotTo(HaveOccurred())
485486
Expect(info.Mode()).To(Equal(os.FileMode(0755)))
486487
})
488+
})
489+
490+
context("when the dependency should be a named file", func() {
491+
it.Before(func() {
492+
var err error
493+
layerPath, err = os.MkdirTemp("", "path")
494+
Expect(err).NotTo(HaveOccurred())
495+
496+
buffer := bytes.NewBuffer(nil)
497+
buffer.WriteString("some-file-contents")
498+
499+
sum := sha256.Sum256(buffer.Bytes())
500+
dependencySHA = hex.EncodeToString(sum[:])
501+
502+
transport.DropCall.Returns.ReadCloser = io.NopCloser(buffer)
503+
504+
deliver = func() error {
505+
return service.Deliver(postal.Dependency{
506+
ID: "some-entry",
507+
Stacks: []string{"some-stack"},
508+
URI: "https://dependencies.example.com/dependencies/some-file-name.txt",
509+
SHA256: dependencySHA,
510+
Version: "1.2.3",
511+
}, "some-cnb-path",
512+
layerPath,
513+
platformPath,
514+
)
515+
}
516+
})
517+
518+
it.After(func() {
519+
Expect(os.RemoveAll(layerPath)).To(Succeed())
520+
})
487521

522+
it("downloads the dependency and copies it into the path with the given name", func() {
523+
err := deliver()
524+
Expect(err).NotTo(HaveOccurred())
525+
526+
Expect(transport.DropCall.Receives.Root).To(Equal("some-cnb-path"))
527+
Expect(transport.DropCall.Receives.Uri).To(Equal("https://dependencies.example.com/dependencies/some-file-name.txt"))
528+
529+
files, err := filepath.Glob(fmt.Sprintf("%s/*", layerPath))
530+
Expect(err).NotTo(HaveOccurred())
531+
Expect(files).To(ConsistOf([]string{filepath.Join(layerPath, "some-file-name.txt")}))
532+
533+
content, err := os.ReadFile(filepath.Join(layerPath, "some-file-name.txt"))
534+
Expect(err).NotTo(HaveOccurred())
535+
Expect(string(content)).To(Equal("some-file-contents"))
536+
})
488537
})
489538

490539
context("when there is a dependency mapping via binding", func() {

vacation/archive.go

+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
package vacation
2+
3+
import (
4+
"bufio"
5+
"fmt"
6+
"io"
7+
"path/filepath"
8+
9+
"github.com/gabriel-vasile/mimetype"
10+
)
11+
12+
type Decompressor interface {
13+
Decompress(destination string) error
14+
}
15+
16+
// An Archive decompresses tar, gzip, xz, and bzip2 compressed tar, and zip files from
17+
// an input stream.
18+
type Archive struct {
19+
reader io.Reader
20+
components int
21+
name string
22+
}
23+
24+
// NewArchive returns a new Archive that reads from inputReader.
25+
func NewArchive(inputReader io.Reader) Archive {
26+
return Archive{
27+
reader: inputReader,
28+
name: "artifact",
29+
}
30+
}
31+
32+
// Decompress reads from Archive, determines the archive type of the input
33+
// stream, and writes files into the destination specified.
34+
//
35+
// Archive decompression will also handle files that are types "text/plain;
36+
// charset=utf-8" and write the contents of the input stream to a file name
37+
// "artifact" in the destination directory.
38+
func (a Archive) Decompress(destination string) error {
39+
// Convert reader into a buffered read so that the header can be peeked to
40+
// determine the type.
41+
bufferedReader := bufio.NewReader(a.reader)
42+
43+
// The number 3072 is lifted from the mimetype library and the definition of
44+
// the constant at the time of writing this functionality is listed below.
45+
// https://github.com/gabriel-vasile/mimetype/blob/c64c025a7c2d8d45ba57d3cebb50a1dbedb3ed7e/internal/matchers/matchers.go#L6
46+
header, err := bufferedReader.Peek(3072)
47+
if err != nil && err != io.EOF {
48+
return err
49+
}
50+
51+
mime := mimetype.Detect(header)
52+
53+
// This switch case is reponsible for determining what the decompression
54+
// strategy should be.
55+
var decompressor Decompressor
56+
switch mime.String() {
57+
case "application/x-tar":
58+
decompressor = NewTarArchive(bufferedReader).StripComponents(a.components)
59+
case "application/gzip":
60+
decompressor = NewTarGzipArchive(bufferedReader).StripComponents(a.components)
61+
case "application/x-xz":
62+
decompressor = NewTarXZArchive(bufferedReader).StripComponents(a.components)
63+
case "application/x-bzip2":
64+
decompressor = NewTarBzip2Archive(bufferedReader).StripComponents(a.components)
65+
case "application/zip":
66+
decompressor = NewZipArchive(bufferedReader)
67+
case "text/plain; charset=utf-8", "application/jar":
68+
destination = filepath.Join(destination, a.name)
69+
decompressor = NewNopArchive(bufferedReader)
70+
default:
71+
return fmt.Errorf("unsupported archive type: %s", mime.String())
72+
}
73+
74+
return decompressor.Decompress(destination)
75+
}
76+
77+
// StripComponents behaves like the --strip-components flag on tar command
78+
// removing the first n levels from the final decompression destination.
79+
// Setting this is a no-op for archive types that do not use --strip-components
80+
// (such as zip).
81+
func (a Archive) StripComponents(components int) Archive {
82+
a.components = components
83+
return a
84+
}
85+
86+
func (a Archive) WithName(name string) Archive {
87+
a.name = name
88+
return a
89+
}

vacation/vacation_archive_test.go vacation/archive_test.go

+94-1
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import (
1717
. "github.com/onsi/gomega"
1818
)
1919

20-
func testVacationArchive(t *testing.T, context spec.G, it spec.S) {
20+
func testArchive(t *testing.T, context spec.G, it spec.S) {
2121
var (
2222
Expect = NewWithT(t).Expect
2323
)
@@ -322,6 +322,99 @@ func testVacationArchive(t *testing.T, context spec.G, it spec.S) {
322322
})
323323
})
324324

325+
context("when passed the reader of a text file", func() {
326+
var (
327+
archive vacation.Archive
328+
tempDir string
329+
)
330+
331+
it.Before(func() {
332+
var err error
333+
tempDir, err = os.MkdirTemp("", "vacation")
334+
Expect(err).NotTo(HaveOccurred())
335+
336+
buffer := bytes.NewBuffer([]byte(`some contents`))
337+
338+
archive = vacation.NewArchive(buffer)
339+
})
340+
341+
it.After(func() {
342+
Expect(os.RemoveAll(tempDir)).To(Succeed())
343+
})
344+
345+
it("writes a text file onto the path", func() {
346+
err := archive.Decompress(tempDir)
347+
Expect(err).NotTo(HaveOccurred())
348+
349+
content, err := os.ReadFile(filepath.Join(tempDir, "artifact"))
350+
Expect(err).NotTo(HaveOccurred())
351+
Expect(content).To(Equal([]byte(`some contents`)))
352+
})
353+
354+
context("when given a name", func() {
355+
it.Before(func() {
356+
archive = archive.WithName("some-text-file")
357+
})
358+
359+
it("writes a text file onto the path with that name", func() {
360+
err := archive.Decompress(tempDir)
361+
Expect(err).NotTo(HaveOccurred())
362+
363+
content, err := os.ReadFile(filepath.Join(tempDir, "some-text-file"))
364+
Expect(err).NotTo(HaveOccurred())
365+
Expect(content).To(Equal([]byte(`some contents`)))
366+
})
367+
})
368+
})
369+
370+
context("when passed the reader of a jar file", func() {
371+
var (
372+
archive vacation.Archive
373+
tempDir string
374+
header []byte
375+
)
376+
377+
it.Before(func() {
378+
var err error
379+
tempDir, err = os.MkdirTemp("", "vacation")
380+
Expect(err).NotTo(HaveOccurred())
381+
382+
// JAR header copied from https://github.com/gabriel-vasile/mimetype/blob/c4c6791c993e7f509de8ef38f149a59533e30bbc/testdata/jar.jar
383+
header = []byte("\x50\x4b\x03\x04\x14\x00\x08\x08\x08\x00\x59\x71\xbf\x4c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x09\x00\x04\x00\x4d\x45\x54\x41\x2d\x49\x4e\x46\x2f\xfe\xca\x00\x00\x03\x00\x50\x4b\x07\x08\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x50\x4b\x03\x04\x14\x00\x08\x08\x08\x00\x59\x71\xbf\x4c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x14\x00\x00\x00\x4d\x45\x54\x41\x2d\x49\x4e\x46\x2f\x4d\x41\x4e\x49\x46\x45\x53\x54\x2e\x4d\x46\xf3\x4d\xcc\xcb\x4c\x4b\x2d\x2e\xd1\x0d\x4b\x2d\x2a\xce\xcc\xcf\xb3\x52\x30\xd4\x33\xe0\xe5\x72\x2e\x4a\x4d\x2c\x49\x4d\xd1\x75\xaa\x04\x09\x58\xe8\x19\xc4\x1b\x9a\x1a\x2a\x68\xf8\x17\x25\x26\xe7\xa4\x2a\x38\xe7\x17\x15\xe4\x17\x25\x96\x00\xd5\x6b\xf2\x72\xf9\x26\x66\xe6\xe9\x3a\xe7\x24\x16\x17\x5b\x29\x78\xa4\xe6\xe4\xe4\x87\xe7\x17\xe5\xa4\xf0\x72\xf1\x72\x01\x00\x50\x4b\x07\x08\x86\x7d\x5d\xeb\x5c\x00\x00\x00\x5d\x00\x00\x00\x50\x4b\x03\x04\x14\x00\x08\x08\x08\x00\x12\x71\xbf\x4c\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x10\x00\x00\x00\x48\x65\x6c\x6c\x6f\x57\x6f\x72\x6c\x64\x2e\x63\x6c\x61\x73\x73\x6d\x50\x4d\x4b\xc3\x40\x10\x7d\xdb\xa6\x4d\x13\x53\x53\x5b\x53\x3f\x0b\xf6\x50\x88\x22\xe6\xe2\xad\xe2\x45\x10\x0f\x45\x85\x88\x1e\x3c\x6d\xda\xa5\x6c\xd9\x24\x12\x13\xc1\x9f\xa5\x07\x05\x0f\xfe\x00\x7f\x94\x38\x1b\x85\x20\x74\x0f\xb3\x3b\x6f\xde\x9b\x79\xb3\x5f\xdf\x1f\x9f\x00\x8e\x31\xb0\xd1\x84\x6b\xa1\x83\xb5\x16\xba\x36\x7a\x58\x37\xe1\x99\xe8\x33\x34\x4f\x64\x22\xf3\x53\x86\xba\xbf\x7f\xcb\x60\x9c\xa5\x33\xc1\xe0\x4e\x64\x22\x2e\x8b\x38\x12\xd9\x0d\x8f\x14\x21\x46\xcc\x65\xc2\xd0\xf7\xef\x27\x0b\xfe\xc4\x03\xc5\x93\x79\x10\xe6\x99\x4c\xe6\x63\x2d\xb4\xc3\xb4\xc8\xa6\xe2\x5c\x6a\xb2\x7b\x21\x94\x4a\xef\xd2\x4c\xcd\x8e\x34\xdb\x81\x89\x96\x89\x0d\x07\x9b\xd8\x62\x68\x97\xe5\xc3\xbd\x92\x30\x34\xb1\xed\x60\x07\xbb\xd4\xa3\x92\x31\x74\xaa\x31\x57\xd1\x42\x4c\xf3\x7f\x50\xf8\xfc\x98\x8b\x98\x5c\xa7\x05\x15\xbc\x5f\x4f\x32\x0d\xae\xc9\x50\x4e\xb6\x04\x8f\xc7\x0c\xbd\x25\x30\x83\xf9\xa0\x33\x45\xdb\x78\xfe\xb2\x65\x30\x44\x83\xfe\x4b\x9f\x1a\x98\xb6\x4e\xd1\xa2\x6c\x40\x37\xa3\xbb\x71\xf0\x0e\xf6\x42\x0f\xb2\x4c\xb1\x59\x82\x9a\xb2\x02\xe7\x8f\x3a\x2a\xa5\x80\xf5\x8a\x5a\xb7\xfe\x06\xa3\xa2\xdb\x54\xa2\x1e\xd4\x55\x0b\xdb\xe5\x94\xd5\x1f\x50\x4b\x07\x08\xe5\x38\x99\x3f\x21\x01\x00\x00\xab\x01\x00\x00\x50\x4b\x01\x02\x14\x00\x14\x00\x08\x08\x08\x00\x59\x71\xbf\x4c\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x09\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x4d\x45\x54\x41\x2d\x49\x4e\x46\x2f\xfe\xca\x00\x00\x50\x4b\x01\x02\x14\x00\x14\x00\x08\x08\x08\x00\x59\x71\xbf\x4c\x86\x7d\x5d\xeb\x5c\x00\x00\x00\x5d\x00\x00\x00\x14\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x3d\x00\x00\x00\x4d\x45\x54\x41\x2d\x49\x4e\x46\x2f\x4d\x41\x4e\x49\x46\x45\x53\x54\x2e\x4d\x46\x50\x4b\x01\x02\x14\x00\x14\x00\x08\x08\x08\x00\x12\x71\xbf\x4c\xe5\x38\x99\x3f\x21\x01\x00\x00\xab\x01\x00\x00\x10\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xdb\x00\x00\x00\x48\x65\x6c\x6c\x6f\x57\x6f\x72\x6c\x64\x2e\x63\x6c\x61\x73\x73\x50\x4b\x05\x06\x00\x00\x00\x00\x03\x00\x03\x00\xbb\x00\x00\x00\x3a\x02\x00\x00\x00\x00")
384+
buffer := bytes.NewBuffer(header)
385+
386+
archive = vacation.NewArchive(buffer)
387+
})
388+
389+
it.After(func() {
390+
Expect(os.RemoveAll(tempDir)).To(Succeed())
391+
})
392+
393+
it("writes a jar file onto the path", func() {
394+
err := archive.Decompress(tempDir)
395+
Expect(err).NotTo(HaveOccurred())
396+
397+
content, err := os.ReadFile(filepath.Join(tempDir, "artifact"))
398+
Expect(err).NotTo(HaveOccurred())
399+
Expect(content).To(Equal(header))
400+
})
401+
402+
context("when given a name", func() {
403+
it.Before(func() {
404+
archive = archive.WithName("some-jar-file")
405+
})
406+
407+
it("writes a jar file onto the path with that name", func() {
408+
err := archive.Decompress(tempDir)
409+
Expect(err).NotTo(HaveOccurred())
410+
411+
content, err := os.ReadFile(filepath.Join(tempDir, "some-jar-file"))
412+
Expect(err).NotTo(HaveOccurred())
413+
Expect(content).To(Equal(header))
414+
})
415+
})
416+
})
417+
325418
context("failure cases", func() {
326419
context("the buffer passed is of are unknown type", func() {
327420
var (

vacation/init_test.go

+8-8
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,13 @@ import (
99

1010
func TestVacation(t *testing.T) {
1111
suite := spec.New("vacation", spec.Report(report.Terminal{}))
12-
suite("VacationArchive", testVacationArchive)
13-
suite("VacationTarBzip2", testVacationTarBzip2)
14-
suite("VacationSymlinkSorting", testVacationSymlinkSorting)
15-
suite("VacationTar", testVacationTar)
16-
suite("VacationTarGzip", testVacationTarGzip)
17-
suite("VacationTarXZ", testVacationTarXZ)
18-
suite("VacationText", testVacationText)
19-
suite("VacationZip", testVacationZip)
12+
suite("Archive", testArchive)
13+
suite("NopArchive", testNopArchive)
14+
suite("SymlinkSorting", testSymlinkSorting)
15+
suite("TarArchive", testTarArchive)
16+
suite("TarBzip2Archive", testTarBzip2Archive)
17+
suite("TarGzipArchive", testTarGzipArchive)
18+
suite("TarXZArchive", testTarXZArchive)
19+
suite("ZipArchive", testZipArchive)
2020
suite.Run(t)
2121
}

vacation/nop_archive.go

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package vacation
2+
3+
import (
4+
"io"
5+
"os"
6+
)
7+
8+
// A NopArchive implements the common archive interface, but acts as a no-op,
9+
// simply copying the reader to the destination.
10+
type NopArchive struct {
11+
reader io.Reader
12+
}
13+
14+
// NewNopArchive returns a new NopArchive
15+
func NewNopArchive(r io.Reader) NopArchive {
16+
return NopArchive{reader: r}
17+
}
18+
19+
// Decompress copies the reader contents into the destination specified.
20+
func (na NopArchive) Decompress(destination string) error {
21+
file, err := os.Create(destination)
22+
if err != nil {
23+
return err
24+
}
25+
defer file.Close()
26+
27+
_, err = io.Copy(file, na.reader)
28+
if err != nil {
29+
return err
30+
}
31+
32+
return nil
33+
}

vacation/nop_archive_test.go

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package vacation_test
2+
3+
import (
4+
"bytes"
5+
"os"
6+
"path/filepath"
7+
"testing"
8+
9+
"github.com/paketo-buildpacks/packit/vacation"
10+
"github.com/sclevine/spec"
11+
12+
. "github.com/onsi/gomega"
13+
)
14+
15+
func testNopArchive(t *testing.T, context spec.G, it spec.S) {
16+
var Expect = NewWithT(t).Expect
17+
18+
context("Decompress", func() {
19+
var (
20+
archive vacation.NopArchive
21+
tempDir string
22+
)
23+
24+
it.Before(func() {
25+
var err error
26+
tempDir, err = os.MkdirTemp("", "vacation")
27+
Expect(err).NotTo(HaveOccurred())
28+
29+
buffer := bytes.NewBuffer([]byte(`some contents`))
30+
31+
archive = vacation.NewNopArchive(buffer)
32+
})
33+
34+
it.After(func() {
35+
Expect(os.RemoveAll(tempDir)).To(Succeed())
36+
})
37+
38+
it("copies the contents of the reader to the destination", func() {
39+
err := archive.Decompress(filepath.Join(tempDir, "some-file"))
40+
Expect(err).NotTo(HaveOccurred())
41+
42+
content, err := os.ReadFile(filepath.Join(tempDir, "some-file"))
43+
Expect(err).NotTo(HaveOccurred())
44+
Expect(content).To(Equal([]byte(`some contents`)))
45+
})
46+
47+
context("failure cases", func() {
48+
context("when the destination file cannot be created", func() {
49+
it("returns an error", func() {
50+
err := archive.Decompress("/no/such/path")
51+
Expect(err).To(MatchError(ContainSubstring("no such file or directory")))
52+
})
53+
})
54+
})
55+
})
56+
}

vacation/vacation_symlink_sorting_test.go vacation/symlink_sorting_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import (
1515
. "github.com/onsi/gomega"
1616
)
1717

18-
func testVacationSymlinkSorting(t *testing.T, context spec.G, it spec.S) {
18+
func testSymlinkSorting(t *testing.T, context spec.G, it spec.S) {
1919
var (
2020
Expect = NewWithT(t).Expect
2121
)

0 commit comments

Comments
 (0)