361 lines
14 KiB
Go
361 lines
14 KiB
Go
package layer_test
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/apex/log"
|
|
"github.com/apex/log/handlers/discard"
|
|
"github.com/sclevine/spec"
|
|
"github.com/sclevine/spec/report"
|
|
|
|
"github.com/buildpacks/lifecycle/api"
|
|
"github.com/buildpacks/lifecycle/buildpack"
|
|
"github.com/buildpacks/lifecycle/internal/layer"
|
|
"github.com/buildpacks/lifecycle/platform"
|
|
"github.com/buildpacks/lifecycle/platform/files"
|
|
h "github.com/buildpacks/lifecycle/testhelpers"
|
|
)
|
|
|
|
func TestLayerMetadataRestorer(t *testing.T) {
|
|
spec.Run(t, "MetadataRestorer", testLayerMetadataRestorer, spec.Report(report.Terminal{}))
|
|
}
|
|
|
|
func testLayerMetadataRestorer(t *testing.T, when spec.G, it spec.S) {
|
|
var (
|
|
layerDir string
|
|
layerMetadataRestorer layer.MetadataRestorer
|
|
layerSHAStore layer.SHAStore
|
|
layersMetadata files.LayersMetadata
|
|
cacheMetadata platform.CacheMetadata
|
|
buildpacks []buildpack.GroupElement
|
|
skipLayers bool
|
|
logger log.Logger
|
|
)
|
|
|
|
it.Before(func() {
|
|
var err error
|
|
|
|
layerDir, err = os.MkdirTemp("", "lifecycle-layer-dir")
|
|
h.AssertNil(t, err)
|
|
logger = log.Logger{Handler: &discard.Handler{}}
|
|
layerMetadataRestorer = layer.NewDefaultMetadataRestorer(layerDir, skipLayers, &logger, api.Platform.Latest())
|
|
layerSHAStore = layer.NewSHAStore()
|
|
})
|
|
|
|
it.After(func() {
|
|
h.AssertNil(t, os.RemoveAll(layerDir))
|
|
})
|
|
|
|
when("#Restore", func() {
|
|
it.Before(func() {
|
|
buildpacks = []buildpack.GroupElement{
|
|
{ID: "metadata.buildpack", API: api.Buildpack.Latest().String()},
|
|
{ID: "no.cache.buildpack", API: api.Buildpack.Latest().String()},
|
|
{ID: "escaped/buildpack/id", API: api.Buildpack.Latest().String()},
|
|
}
|
|
})
|
|
|
|
when("app and cache metadata are not present", func() {
|
|
it("does not restore any metadata", func() {
|
|
err := layerMetadataRestorer.Restore(buildpacks, layersMetadata, cacheMetadata, layerSHAStore)
|
|
h.AssertNil(t, err)
|
|
|
|
files, err := os.ReadDir(layerDir)
|
|
h.AssertNil(t, err)
|
|
h.AssertEq(t, len(files), 0)
|
|
})
|
|
})
|
|
|
|
when("only app metadata is present", func() {
|
|
it.Before(func() {
|
|
layerMetaDataJSON := h.MustReadFile(t, filepath.Join("testdata", "app_metadata.json"))
|
|
h.AssertNil(t, json.Unmarshal(layerMetaDataJSON, &layersMetadata))
|
|
})
|
|
|
|
it("restores each store metadata", func() {
|
|
err := layerMetadataRestorer.Restore(buildpacks, layersMetadata, cacheMetadata, layerSHAStore)
|
|
h.AssertNil(t, err)
|
|
|
|
files, err := os.ReadDir(layerDir)
|
|
h.AssertNil(t, err)
|
|
h.AssertEq(t, len(files), 2)
|
|
|
|
for _, data := range []struct{ name, want string }{
|
|
// store.toml files.
|
|
{"metadata.buildpack/store.toml", "[metadata]\n [metadata.metadata-buildpack-store-data]\n store-key = \"store-val\""},
|
|
{"no.cache.buildpack/store.toml", "[metadata]\n [metadata.no-cache-buildpack-store-data]\n store-key = \"store-val\""},
|
|
} {
|
|
got := h.MustReadFile(t, filepath.Join(layerDir, data.name))
|
|
h.AssertStringContains(t, string(got), data.want)
|
|
}
|
|
})
|
|
})
|
|
|
|
when("only cache metadata is present", func() {
|
|
it.Before(func() {
|
|
cacheMetaDataJSON := h.MustReadFile(t, filepath.Join("testdata", "cache_metadata.json"))
|
|
h.AssertNil(t, json.Unmarshal(cacheMetaDataJSON, &cacheMetadata))
|
|
})
|
|
|
|
it("does not restore each store metadata", func() {
|
|
err := layerMetadataRestorer.Restore(buildpacks, layersMetadata, cacheMetadata, layerSHAStore)
|
|
h.AssertNil(t, err)
|
|
for _, file := range []string{
|
|
// store.toml files.
|
|
"metadata.buildpack/store.toml",
|
|
"no.cache.buildpack/store.toml",
|
|
} {
|
|
h.AssertPathDoesNotExist(t, filepath.Join(layerDir, file))
|
|
}
|
|
})
|
|
})
|
|
|
|
when("app and cache metadata are present", func() {
|
|
it.Before(func() {
|
|
cacheMetaDataJSON := h.MustReadFile(t, filepath.Join("testdata", "cache_metadata.json"))
|
|
h.AssertNil(t, json.Unmarshal(cacheMetaDataJSON, &cacheMetadata))
|
|
|
|
layerMetaDataJSON := h.MustReadFile(t, filepath.Join("testdata", "app_metadata.json"))
|
|
h.AssertNil(t, json.Unmarshal(layerMetaDataJSON, &layersMetadata))
|
|
})
|
|
|
|
it("restores app and cache layer metadata", func() {
|
|
err := layerMetadataRestorer.Restore(buildpacks, layersMetadata, cacheMetadata, layerSHAStore)
|
|
h.AssertNil(t, err)
|
|
|
|
unsetFlags := "[types]"
|
|
for _, data := range []struct{ name, want string }{
|
|
// App layers.
|
|
{"metadata.buildpack/launch.toml", "[metadata]\n launch-key = \"launch-value\""},
|
|
{"metadata.buildpack/launch-build-cache.toml", "[metadata]\n launch-build-cache-key = \"launch-build-cache-value\""},
|
|
{"metadata.buildpack/launch-cache.toml", "[metadata]\n launch-cache-key = \"launch-cache-value\""},
|
|
{"no.cache.buildpack/some-layer.toml", "[metadata]\n some-layer-key = \"some-layer-value\""},
|
|
// Cache-image-only layers.
|
|
{"metadata.buildpack/cache.toml", "[metadata]\n cache-key = \"cache-value\""},
|
|
// Cached launch layers not in app
|
|
{"metadata.buildpack/launch-cache-not-in-app.toml", "[metadata]\n cache-only-key = \"cache-only-value\"\n launch-cache-key = \"cache-specific-value\""},
|
|
} {
|
|
got := h.MustReadFile(t, filepath.Join(layerDir, data.name))
|
|
h.AssertStringContains(t, string(got), data.want)
|
|
h.AssertStringDoesNotContain(t, string(got), unsetFlags) // The [types] table shouldn't exist. The build, cache and launch flags are set to false.
|
|
}
|
|
})
|
|
|
|
when("platformAPI is less than 0.14", func() {
|
|
it.Before(func() {
|
|
layerMetadataRestorer = layer.NewDefaultMetadataRestorer(layerDir, skipLayers, &logger, api.MustParse("0.13"))
|
|
})
|
|
|
|
it("ignores launch-cache-not-in-app", func() {
|
|
err := layerMetadataRestorer.Restore(buildpacks, layersMetadata, cacheMetadata, layerSHAStore)
|
|
h.AssertNil(t, err)
|
|
|
|
h.AssertPathDoesNotExist(t, filepath.Join(layerDir, "metadata.buildpack/launch-cache-not-in-app.toml"))
|
|
unsetFlags := "[types]"
|
|
for _, data := range []struct{ name, want string }{
|
|
// App layers.
|
|
{"metadata.buildpack/launch.toml", "[metadata]\n launch-key = \"launch-value\""},
|
|
{"metadata.buildpack/launch-build-cache.toml", "[metadata]\n launch-build-cache-key = \"launch-build-cache-value\""},
|
|
{"metadata.buildpack/launch-cache.toml", "[metadata]\n launch-cache-key = \"launch-cache-value\""},
|
|
{"no.cache.buildpack/some-layer.toml", "[metadata]\n some-layer-key = \"some-layer-value\""},
|
|
// Cache-image-only layers.
|
|
{"metadata.buildpack/cache.toml", "[metadata]\n cache-key = \"cache-value\""},
|
|
} {
|
|
got := h.MustReadFile(t, filepath.Join(layerDir, data.name))
|
|
h.AssertStringContains(t, string(got), data.want)
|
|
h.AssertStringDoesNotContain(t, string(got), unsetFlags) // The [types] table shouldn't exist. The build, cache and launch flags are set to false.
|
|
}
|
|
})
|
|
})
|
|
|
|
it("restores layer metadata without the launch, build and cache flags", func() {
|
|
buildpacks = []buildpack.GroupElement{
|
|
{ID: "metadata.buildpack", API: api.Buildpack.Latest().String()},
|
|
{ID: "no.cache.buildpack", API: api.Buildpack.Latest().String()},
|
|
}
|
|
|
|
err := layerMetadataRestorer.Restore(buildpacks, layersMetadata, cacheMetadata, layerSHAStore)
|
|
h.AssertNil(t, err)
|
|
|
|
unsetFlags := "[types]"
|
|
for _, data := range []struct{ name, want string }{
|
|
{"metadata.buildpack/launch.toml", "[metadata]\n launch-key = \"launch-value\""},
|
|
{"no.cache.buildpack/some-layer.toml", "[metadata]\n some-layer-key = \"some-layer-value\""},
|
|
} {
|
|
got := h.MustReadFile(t, filepath.Join(layerDir, data.name))
|
|
h.AssertStringContains(t, string(got), data.want)
|
|
h.AssertStringDoesNotContain(t, string(got), unsetFlags) // The [types] table shouldn't exist. The build, cache and launch flags are set to false.
|
|
}
|
|
})
|
|
|
|
it("does not overwrite metadata from app image", func() {
|
|
err := layerMetadataRestorer.Restore(buildpacks, layersMetadata, cacheMetadata, layerSHAStore)
|
|
h.AssertNil(t, err)
|
|
|
|
for _, name := range []string{
|
|
"metadata.buildpack/launch-build-cache.toml",
|
|
"metadata.buildpack/launch-cache.toml",
|
|
} {
|
|
got := h.MustReadFile(t, filepath.Join(layerDir, name))
|
|
avoid := "[metadata]\n cache-only-key = \"cache-only-value\""
|
|
if strings.Contains(string(got), avoid) {
|
|
t.Errorf("Expected %q to not contain %q, got %q", name, avoid, got)
|
|
}
|
|
}
|
|
})
|
|
|
|
it("does not restore cache=true layers for non-selected groups", func() {
|
|
err := layerMetadataRestorer.Restore(buildpacks, layersMetadata, cacheMetadata, layerSHAStore)
|
|
h.AssertNil(t, err)
|
|
|
|
h.AssertPathDoesNotExist(t, filepath.Join(layerDir, "no.group.buildpack"))
|
|
})
|
|
|
|
it("does not restore cache=false layer metadata", func() {
|
|
err := layerMetadataRestorer.Restore(buildpacks, layersMetadata, cacheMetadata, layerSHAStore)
|
|
h.AssertNil(t, err)
|
|
|
|
h.AssertPathDoesNotExist(t, filepath.Join(layerDir, "metadata.buildpack", "cache-false.toml"))
|
|
h.AssertPathDoesNotExist(t, filepath.Join(layerDir, "metadata.buildpack", "cache-false.sha"))
|
|
})
|
|
|
|
it("does not restore launch=false layer metadata", func() {
|
|
err := layerMetadataRestorer.Restore(buildpacks, layersMetadata, cacheMetadata, layerSHAStore)
|
|
h.AssertNil(t, err)
|
|
|
|
h.AssertPathDoesNotExist(t, filepath.Join(layerDir, "metadata.buildpack", "launch-false.toml"))
|
|
h.AssertPathDoesNotExist(t, filepath.Join(layerDir, "metadata.buildpack", "launch-false.sha"))
|
|
})
|
|
|
|
it("does not restore build=true, cache=false layer metadata", func() {
|
|
err := layerMetadataRestorer.Restore(buildpacks, layersMetadata, cacheMetadata, layerSHAStore)
|
|
h.AssertNil(t, err)
|
|
|
|
h.AssertPathDoesNotExist(t, filepath.Join(layerDir, "metadata.buildpack", "launch-build.sha"))
|
|
})
|
|
|
|
it("restores escaped buildpack layer metadata", func() {
|
|
err := layerMetadataRestorer.Restore(buildpacks, layersMetadata, cacheMetadata, layerSHAStore)
|
|
h.AssertNil(t, err)
|
|
|
|
path := filepath.Join(layerDir, "escaped_buildpack_id", "escaped-bp-layer.toml")
|
|
got := h.MustReadFile(t, path)
|
|
want := "[metadata]\n escaped-bp-layer-key = \"escaped-bp-layer-value\""
|
|
|
|
h.AssertStringContains(t, string(got), want)
|
|
})
|
|
|
|
it("restores each store metadata", func() {
|
|
err := layerMetadataRestorer.Restore(buildpacks, layersMetadata, cacheMetadata, layerSHAStore)
|
|
h.AssertNil(t, err)
|
|
for _, data := range []struct{ name, want string }{
|
|
// store.toml files.
|
|
{"metadata.buildpack/store.toml", "[metadata]\n [metadata.metadata-buildpack-store-data]\n store-key = \"store-val\""},
|
|
{"no.cache.buildpack/store.toml", "[metadata]\n [metadata.no-cache-buildpack-store-data]\n store-key = \"store-val\""},
|
|
} {
|
|
got := h.MustReadFile(t, filepath.Join(layerDir, data.name))
|
|
h.AssertStringContains(t, string(got), data.want)
|
|
}
|
|
})
|
|
|
|
when("app and cache metadata are inconsistent with each other", func() { // cache was manipulated or deleted
|
|
it.Before(func() {
|
|
metadata := h.MustReadFile(t, filepath.Join("testdata", "cache_inconsistent_metadata.json"))
|
|
h.AssertNil(t, json.Unmarshal(metadata, &cacheMetadata))
|
|
})
|
|
|
|
when("app metadata cache=true, cache metadata cache=false", func() {
|
|
it("treats the layer as cache=false", func() {
|
|
err := layerMetadataRestorer.Restore(buildpacks, layersMetadata, cacheMetadata, layerSHAStore)
|
|
h.AssertNil(t, err)
|
|
|
|
h.AssertPathDoesNotExist(t, filepath.Join(layerDir, "metadata.buildpack", "cache.toml"))
|
|
h.AssertPathDoesNotExist(t, filepath.Join(layerDir, "metadata.buildpack", "launch-build-cache.toml"))
|
|
h.AssertPathDoesNotExist(t, filepath.Join(layerDir, "metadata.buildpack", "launch-cache.toml"))
|
|
})
|
|
})
|
|
})
|
|
|
|
when("subset of buildpacks are detected", func() {
|
|
it.Before(func() {
|
|
buildpacks = []buildpack.GroupElement{{ID: "no.cache.buildpack", API: api.Buildpack.Latest().String()}}
|
|
})
|
|
|
|
it("restores layers for detected buildpacks", func() {
|
|
err := layerMetadataRestorer.Restore(buildpacks, layersMetadata, cacheMetadata, layerSHAStore)
|
|
h.AssertNil(t, err)
|
|
|
|
path := filepath.Join(layerDir, "no.cache.buildpack", "some-layer.toml")
|
|
got := h.MustReadFile(t, path)
|
|
want := "[metadata]\n some-layer-key = \"some-layer-value\""
|
|
|
|
h.AssertStringContains(t, string(got), want)
|
|
})
|
|
|
|
it("does not restore layers for undetected buildpacks", func() {
|
|
err := layerMetadataRestorer.Restore(buildpacks, layersMetadata, cacheMetadata, layerSHAStore)
|
|
h.AssertNil(t, err)
|
|
|
|
h.AssertPathDoesNotExist(t, filepath.Join(layerDir, "metadata.buildpack"))
|
|
})
|
|
})
|
|
|
|
when("there are no buildpacks are detected", func() {
|
|
it.Before(func() {
|
|
buildpacks = []buildpack.GroupElement{}
|
|
})
|
|
|
|
it("does not restore layers for any buildpacks", func() {
|
|
err := layerMetadataRestorer.Restore(buildpacks, layersMetadata, cacheMetadata, layerSHAStore)
|
|
h.AssertNil(t, err)
|
|
|
|
h.AssertPathDoesNotExist(t, filepath.Join(layerDir, "metadata.buildpack"))
|
|
})
|
|
})
|
|
|
|
when("skip layers is true", func() {
|
|
it.Before(func() {
|
|
skipLayers = true
|
|
layerMetadataRestorer = layer.NewDefaultMetadataRestorer(layerDir, skipLayers, &logger, api.Platform.Latest())
|
|
})
|
|
|
|
it("does not write buildpack layer metadata", func() {
|
|
err := layerMetadataRestorer.Restore(buildpacks, layersMetadata, cacheMetadata, layerSHAStore)
|
|
h.AssertNil(t, err)
|
|
|
|
files, err := os.ReadDir(layerDir)
|
|
h.AssertNil(t, err)
|
|
h.AssertEq(t, len(files), 2)
|
|
|
|
files, err = os.ReadDir(filepath.Join(layerDir, "metadata.buildpack"))
|
|
h.AssertNil(t, err)
|
|
// expect 1 file b/c of store.toml
|
|
h.AssertEq(t, len(files), 1)
|
|
|
|
files, err = os.ReadDir(filepath.Join(layerDir, "no.cache.buildpack"))
|
|
h.AssertNil(t, err)
|
|
// expect 1 file b/c of store.toml
|
|
h.AssertEq(t, len(files), 1)
|
|
})
|
|
|
|
it("restores each store metadata", func() {
|
|
err := layerMetadataRestorer.Restore(buildpacks, layersMetadata, cacheMetadata, layerSHAStore)
|
|
h.AssertNil(t, err)
|
|
for _, data := range []struct{ name, want string }{
|
|
// store.toml files.
|
|
{"metadata.buildpack/store.toml", "[metadata]\n [metadata.metadata-buildpack-store-data]\n store-key = \"store-val\""},
|
|
{"no.cache.buildpack/store.toml", "[metadata]\n [metadata.no-cache-buildpack-store-data]\n store-key = \"store-val\""},
|
|
} {
|
|
got := h.MustReadFile(t, filepath.Join(layerDir, data.name))
|
|
h.AssertStringContains(t, string(got), data.want)
|
|
}
|
|
})
|
|
})
|
|
})
|
|
})
|
|
}
|