200 lines
5.7 KiB
Go
200 lines
5.7 KiB
Go
package layers_test
|
|
|
|
import (
|
|
"archive/tar"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/apex/log"
|
|
"github.com/apex/log/handlers/memory"
|
|
"github.com/sclevine/spec"
|
|
"github.com/sclevine/spec/report"
|
|
|
|
"github.com/buildpacks/lifecycle/layers"
|
|
h "github.com/buildpacks/lifecycle/testhelpers"
|
|
)
|
|
|
|
func TestDirLayers(t *testing.T) {
|
|
spec.Run(t, "Factory", testDirs, spec.Parallel(), spec.Report(report.Terminal{}))
|
|
}
|
|
|
|
func testDirs(t *testing.T, when spec.G, it spec.S) {
|
|
var (
|
|
factory *layers.Factory
|
|
dir string
|
|
logHandler = memory.New()
|
|
)
|
|
it.Before(func() {
|
|
var err error
|
|
artifactDir, err := os.MkdirTemp("", "layers.slices.layer")
|
|
h.AssertNil(t, err)
|
|
factory = &layers.Factory{
|
|
ArtifactsDir: artifactDir,
|
|
Logger: &log.Logger{Handler: logHandler},
|
|
UID: 1234,
|
|
GID: 4321,
|
|
}
|
|
dir, err = filepath.Abs(filepath.Join("testdata", "target-dir"))
|
|
h.AssertNil(t, err)
|
|
})
|
|
|
|
it.After(func() {
|
|
h.AssertNil(t, os.RemoveAll(factory.ArtifactsDir))
|
|
})
|
|
|
|
when("#DirLayer", func() {
|
|
var dirLayer layers.Layer
|
|
|
|
it.Before(func() {
|
|
var err error
|
|
dirLayer, err = factory.DirLayer("some-layer-id", dir, "some-created-by")
|
|
h.AssertNil(t, err)
|
|
})
|
|
|
|
it("creates a layer from the directory", func() {
|
|
// parent layers should have uid/gid matching the filesystem
|
|
// the dir and it's children should have normalized uid/gid
|
|
h.AssertEq(t, dirLayer.ID, "some-layer-id")
|
|
assertTarEntries(t, dirLayer.TarPath, append(parents(t, dir), []*tar.Header{
|
|
{
|
|
Name: tarPath(dir),
|
|
Uid: factory.UID,
|
|
Gid: factory.GID,
|
|
Typeflag: tar.TypeDir,
|
|
},
|
|
{
|
|
Name: tarPath(filepath.Join(dir, "dir-link")),
|
|
Uid: factory.UID,
|
|
Gid: factory.GID,
|
|
Typeflag: tar.TypeSymlink,
|
|
},
|
|
{
|
|
Name: tarPath(filepath.Join(dir, "file-link.txt")),
|
|
Uid: factory.UID,
|
|
Gid: factory.GID,
|
|
Typeflag: tar.TypeSymlink,
|
|
},
|
|
{
|
|
Name: tarPath(filepath.Join(dir, "file.txt")),
|
|
Uid: factory.UID,
|
|
Gid: factory.GID,
|
|
Typeflag: tar.TypeReg,
|
|
},
|
|
{
|
|
Name: tarPath(filepath.Join(dir, "other-dir")),
|
|
Uid: factory.UID,
|
|
Gid: factory.GID,
|
|
Typeflag: tar.TypeDir,
|
|
},
|
|
{
|
|
Name: tarPath(filepath.Join(dir, "other-dir", "other-file.md")),
|
|
Uid: factory.UID,
|
|
Gid: factory.GID,
|
|
Typeflag: tar.TypeReg,
|
|
},
|
|
{
|
|
Name: tarPath(filepath.Join(dir, "other-dir", "other-file.txt")),
|
|
Uid: factory.UID,
|
|
Gid: factory.GID,
|
|
Typeflag: tar.TypeReg,
|
|
},
|
|
{
|
|
Name: tarPath(filepath.Join(dir, "some-dir")),
|
|
Uid: factory.UID,
|
|
Gid: factory.GID,
|
|
Typeflag: tar.TypeDir,
|
|
},
|
|
{
|
|
Name: tarPath(filepath.Join(dir, "some-dir", "file.md")),
|
|
Uid: factory.UID,
|
|
Gid: factory.GID,
|
|
Typeflag: tar.TypeReg,
|
|
},
|
|
{
|
|
Name: tarPath(filepath.Join(dir, "some-dir", "some-file.txt")),
|
|
Uid: factory.UID,
|
|
Gid: factory.GID,
|
|
Typeflag: tar.TypeReg,
|
|
},
|
|
}...))
|
|
// it returns history
|
|
h.AssertEq(t, dirLayer.History.CreatedBy, "some-created-by")
|
|
})
|
|
|
|
it("reuses tars when possible", func() {
|
|
layerTar, err := os.Stat(dirLayer.TarPath)
|
|
h.AssertNil(t, err)
|
|
modTime := layerTar.ModTime()
|
|
reusedDirLayer, err := factory.DirLayer("some-layer-id", dir, "some-created-by")
|
|
h.AssertNil(t, err)
|
|
h.AssertEq(t, reusedDirLayer, dirLayer)
|
|
layerTar, err = os.Stat(reusedDirLayer.TarPath)
|
|
h.AssertNil(t, err)
|
|
h.AssertEq(t, modTime, layerTar.ModTime()) // assert file has not been modified
|
|
|
|
h.AssertEq(t, len(logHandler.Entries), 1)
|
|
h.AssertEq(t,
|
|
logHandler.Entries[0].Message,
|
|
fmt.Sprintf("Reusing tarball for layer \"some-layer-id\" with SHA: %s\n", dirLayer.Digest),
|
|
)
|
|
})
|
|
})
|
|
}
|
|
|
|
func assertTarEntries(t *testing.T, tarPath string, expectedEntries []*tar.Header) {
|
|
t.Helper()
|
|
lf, err := os.Open(tarPath)
|
|
h.AssertNil(t, err)
|
|
defer lf.Close()
|
|
tr := tar.NewReader(lf)
|
|
assertOSSpecificEntries(t, tr)
|
|
var allEntryNames []string
|
|
for i, expected := range expectedEntries {
|
|
header, err := tr.Next()
|
|
if err == io.EOF {
|
|
t.Fatalf("missing expected archive entry '%s'\n archive contained %v", expected.Name, allEntryNames)
|
|
}
|
|
h.AssertNil(t, err)
|
|
allEntryNames = append(allEntryNames, header.Name)
|
|
if header.Name != expected.Name {
|
|
t.Fatalf("expected entry '%d' to have name %q, got %q", i, expected.Name, header.Name)
|
|
}
|
|
if header.Typeflag != expected.Typeflag {
|
|
t.Fatalf("expected entry '%s' to have type %q, got %q", expected.Name, expected.Typeflag, header.Typeflag)
|
|
}
|
|
if expected.Mode != 0 && header.Mode != expected.Mode { // FIXME: add modes to all expects to remove the 0 hack
|
|
t.Fatalf("expected entry '%s' to have mode %d, got %d", expected.Name, expected.Mode, header.Mode)
|
|
}
|
|
assertOSSpecificFields(t, expected, header)
|
|
if !header.ModTime.Equal(time.Date(1980, time.January, 1, 0, 0, 1, 0, time.UTC)) {
|
|
t.Fatalf("expected entry '%s' to normalized mod time, got '%s", expected.Name, header.ModTime)
|
|
}
|
|
if header.Uname != "" {
|
|
t.Fatalf("expected entry '%s' to empty Uname, got '%s", expected.Name, header.Uname)
|
|
}
|
|
if header.Gname != "" {
|
|
t.Fatalf("expected entry '%s' to empty Gname, got '%s", expected.Name, header.Gname)
|
|
}
|
|
h.AssertEq(t, header.Typeflag, expected.Typeflag)
|
|
}
|
|
header, err := tr.Next()
|
|
if err != io.EOF {
|
|
t.Fatalf("unexpected archive entry '%s'", header.Name)
|
|
}
|
|
}
|
|
|
|
func parents(t *testing.T, file string) []*tar.Header {
|
|
t.Helper()
|
|
parent := filepath.Dir(file)
|
|
if parent == `/` || parent == filepath.VolumeName(file)+`\` {
|
|
return []*tar.Header{}
|
|
}
|
|
fi, err := os.Stat(parent)
|
|
h.AssertNil(t, err)
|
|
return append(parents(t, parent), parentHeader(parent, fi))
|
|
}
|