Compare commits

...

12 Commits

Author SHA1 Message Date
Joe Kimmel 02e9a563eb force pack acceptance tests to build with a version of go that can still make HTTP requests to docker daemon (#1158)
Signed-off-by: Joe Kimmel <jkimmel@vmware.com>
2023-08-03 16:11:18 -04:00
Natalie Arellano bfc5cda949
Field renames per spec review (#1170)
* Rename distributions -> distros in the buildpack spec

Signed-off-by: Natalie Arellano <narellano@vmware.com>

* Rename distributions -> distros in the platform spec

Signed-off-by: Natalie Arellano <narellano@vmware.com>

---------

Signed-off-by: Natalie Arellano <narellano@vmware.com>
2023-08-03 15:06:47 -04:00
Natalie Arellano 6bc53d6f70
Remove CNB_TARGET_ID according to https://github.com/buildpacks/spec/pull/374 and https://github.com/buildpacks/spec/pull/375 (#1175)
Signed-off-by: Natalie Arellano <narellano@vmware.com>
2023-08-03 14:37:21 -04:00
Natalie Arellano b363b2a3b2
Add -daemon to restorer (#1168)
This is needed when extensions were used to switch (but not extend) the run image
and we need to re-read the target data from the image config.

In such cases, we don't need the run image to exist in a registry,
because we don't need a manifest for kaniko.

Signed-off-by: Natalie Arellano <narellano@vmware.com>
2023-07-31 11:48:58 -04:00
Natalie Arellano 87d4f057b8
Simplifies target matching logic per spec PR review (#1166)
* Update units without updating code

Signed-off-by: Natalie Arellano <narellano@vmware.com>

* Update code

Signed-off-by: Natalie Arellano <narellano@vmware.com>

* Unpend test

Signed-off-by: Natalie Arellano <narellano@vmware.com>

* Add units for rebase without updating code

Signed-off-by: Natalie Arellano <narellano@vmware.com>

* Update rebase code

Signed-off-by: Natalie Arellano <narellano@vmware.com>

* Fix lint

Signed-off-by: Natalie Arellano <narellano@vmware.com>

* When we read the descriptor file, don't fill in "*" as a magic value as missing values are wildcard matches

Signed-off-by: Natalie Arellano <narellano@vmware.com>

* Stricter validation for rebase

Signed-off-by: Natalie Arellano <narellano@vmware.com>

---------

Signed-off-by: Natalie Arellano <narellano@vmware.com>
2023-07-31 09:42:18 -04:00
Joe Kimmel dc6af53456
timestamp logs and phase error message cherry-picks (#1164)
* timestamp logs for entry/exit for all the top-level Lifecycle package functions

Signed-off-by: Joe Kimmel <jkimmel@vmware.com>

fixing names

Signed-off-by: Joe Kimmel <jkimmel@vmware.com>

using defer to make one-liners for fun and profit

Signed-off-by: Joe Kimmel <jkimmel@vmware.com>

and today we thank our brave linters for preventing critical defects such as unnecessary trailing newlines from being merged. Its about time somebody thought of the children.

Signed-off-by: Joe Kimmel <jkimmel@vmware.com>

* be more helpful when you dont recognize the phase

Signed-off-by: Joe Kimmel <jkimmel@vmware.com>

---------

Signed-off-by: Joe Kimmel <jkimmel@vmware.com>
2023-07-28 12:14:17 -04:00
Joe Kimmel 40ac1ee05e
Merge pull request #1159 from buildpacks/fix/restorer-target-update
Fix restorer target update
2023-07-27 11:16:46 -07:00
Natalie Arellano fbe3cb075d Fix acceptance by providing a base image when we instantiate the remote run image
Signed-off-by: Natalie Arellano <narellano@vmware.com>
2023-07-25 15:45:37 -04:00
Natalie Arellano c5b14c5bbc Add test for empty digest not returned
Signed-off-by: Natalie Arellano <narellano@vmware.com>
2023-07-25 15:31:36 -04:00
Joe Kimmel 0580136826 warn when a positional argument might have been a flag (#1147)
Signed-off-by: Joe Kimmel <jkimmel@vmware.com>
2023-07-20 09:28:31 -07:00
Joe Kimmel 495eefbd77 add explanatory debug logs so a reader knows why the buildpacks are read twice.
Signed-off-by: Joe Kimmel <jkimmel@vmware.com>
2023-07-20 09:16:47 -07:00
Joe Kimmel 147890216d restorer gets layers flag again
Signed-off-by: Joe Kimmel <jkimmel@vmware.com>
2023-07-20 09:16:39 -07:00
36 changed files with 472 additions and 197 deletions

View File

@ -255,8 +255,7 @@ jobs:
- name: Set up go
uses: actions/setup-go@v3
with:
check-latest: true
go-version-file: 'pack/go.mod'
go-version: '1.20.5'
- uses: actions/download-artifact@v2
with:
name: version
@ -297,8 +296,7 @@ jobs:
- name: Set up go
uses: actions/setup-go@v3
with:
check-latest: true
go-version-file: 'pack/go.mod'
go-version: '1.20.5'
- name: Add runner IP to daemon insecure-registries and firewall
shell: powershell
run: |

View File

@ -20,6 +20,8 @@ import (
h "github.com/buildpacks/lifecycle/testhelpers"
)
const emptyImageSHA = "03cbce912ef1a8a658f73c660ab9c539d67188622f00b15c4f15b89b884f0e10"
var (
restoreImage string
restoreRegAuthConfig string
@ -239,6 +241,8 @@ func testRestorerFunc(platformAPI string) func(t *testing.T, when spec.G, it spe
h.AssertStringContains(t, analyzedMD.RunImage.Reference, restoreRegFixtures.ReadOnlyRunImage+"@sha256:")
h.AssertEq(t, analyzedMD.RunImage.Image, restoreRegFixtures.ReadOnlyRunImage)
h.AssertEq(t, analyzedMD.RunImage.TargetMetadata.OS, "linux")
t.Log("does not return the digest for an empty image")
h.AssertStringDoesNotContain(t, analyzedMD.RunImage.Reference, restoreRegFixtures.ReadOnlyRunImage+"@sha256:"+emptyImageSHA)
t.Log("writes run image manifest and config to the kaniko cache")
ref, err := name.ParseReference(analyzedMD.RunImage.Reference)
h.AssertNil(t, err)
@ -274,6 +278,8 @@ func testRestorerFunc(platformAPI string) func(t *testing.T, when spec.G, it spe
h.AssertStringContains(t, analyzedMD.RunImage.Reference, restoreRegFixtures.ReadOnlyRunImage+"@sha256:")
h.AssertEq(t, analyzedMD.RunImage.Image, restoreRegFixtures.ReadOnlyRunImage)
h.AssertEq(t, analyzedMD.RunImage.TargetMetadata.OS, "linux")
t.Log("does not return the digest for an empty image")
h.AssertStringDoesNotContain(t, analyzedMD.RunImage.Reference, restoreRegFixtures.ReadOnlyRunImage+"@sha256:"+emptyImageSHA)
t.Log("does not write run image manifest and config to the kaniko cache")
fis, err := os.ReadDir(filepath.Join(copyDir, "kaniko"))
h.AssertNil(t, err)
@ -285,6 +291,39 @@ func testRestorerFunc(platformAPI string) func(t *testing.T, when spec.G, it spe
h.AssertNil(t, analyzedMD.RunImage.TargetMetadata)
}
})
when("-daemon", func() {
it("updates run image reference in analyzed.toml to include digest and target data on newer platforms", func() {
h.SkipIf(t, api.MustParse(platformAPI).LessThan("0.12"), "Platform API < 0.12 does not support -daemon flag")
h.DockerRunAndCopy(t,
containerName,
copyDir,
"/",
restoreImage,
h.WithFlags(append(
dockerSocketMount,
"--env", "CNB_PLATFORM_API="+platformAPI,
"--env", "DOCKER_CONFIG=/docker-config",
"--network", restoreRegNetwork,
)...),
h.WithArgs(
"-analyzed", "/layers/some-extend-false-analyzed.toml",
"-daemon",
"-log-level", "debug",
),
)
t.Log("updates run image reference in analyzed.toml to include digest and target data")
analyzedMD, err := lifecycle.Config.ReadAnalyzed(filepath.Join(copyDir, "layers", "some-extend-false-analyzed.toml"), cmd.DefaultLogger)
h.AssertNil(t, err)
h.AssertStringDoesNotContain(t, analyzedMD.RunImage.Reference, "@sha256:") // daemon image ID
h.AssertEq(t, analyzedMD.RunImage.Image, restoreRegFixtures.ReadOnlyRunImage)
h.AssertEq(t, analyzedMD.RunImage.TargetMetadata.OS, "linux")
t.Log("does not write run image manifest and config to the kaniko cache")
fis, err := os.ReadDir(filepath.Join(copyDir, "kaniko"))
h.AssertNil(t, err)
h.AssertEq(t, len(fis), 1) // .gitkeep
})
})
})
}
}

View File

@ -27,7 +27,6 @@ echo
echo "CNB_TARGET_ARCH:" `printenv CNB_TARGET_ARCH`
echo "CNB_TARGET_ARCH_VARIANT:" `printenv CNB_TARGET_ARCH_VARIANT`
echo "CNB_TARGET_OS:" `printenv CNB_TARGET_OS`
echo "CNB_TARGET_ID:" `printenv CNB_TARGET_ID`
echo "CNB_TARGET_DISTRO_NAME:" `printenv CNB_TARGET_DISTRO_NAME`
echo "CNB_TARGET_DISTRO_VERSION:" `printenv CNB_TARGET_DISTRO_VERSION`

View File

@ -3,7 +3,7 @@
os = "linux"
arch = "amd64"
arch-variant = "some-variant"
[run-image.target.distribution]
[run-image.target.distro]
name = "ubuntu"
version = "some-cute-version"

View File

@ -190,6 +190,7 @@ func (f *AnalyzerFactory) setRun(analyzer *Analyzer, imageRef string) error {
// Analyze fetches the layers metadata from the previous image and writes analyzed.toml.
func (a *Analyzer) Analyze() (files.Analyzed, error) {
defer log.NewMeasurement("Analyzer", a.Logger)()
var (
err error
appMeta files.LayersMetadata

View File

@ -666,14 +666,14 @@ func testAnalyzer(platformAPI string) func(t *testing.T, when spec.G, it spec.S)
h.AssertEq(t, md.RunImage.Reference, "s0m3D1g3sT")
})
it("populates Target metadata from the run image", func() {
h.AssertNil(t, image.SetLabel("io.buildpacks.id", "id software"))
it("populates target metadata from the run image", func() {
h.AssertNil(t, image.SetLabel("io.buildpacks.base.id", "id software"))
h.AssertNil(t, image.SetOS("windows"))
h.AssertNil(t, image.SetOSVersion("95"))
h.AssertNil(t, image.SetArchitecture("Pentium"))
h.AssertNil(t, image.SetVariant("MMX"))
h.AssertNil(t, image.SetLabel("io.buildpacks.distribution.name", "moobuntu"))
h.AssertNil(t, image.SetLabel("io.buildpacks.distribution.version", "Helpful Holstein"))
h.AssertNil(t, image.SetLabel("io.buildpacks.distro.name", "moobuntu"))
h.AssertNil(t, image.SetLabel("io.buildpacks.distro.version", "Helpful Holstein"))
md, err := analyzer.Analyze()
h.AssertNil(t, err)
@ -685,9 +685,9 @@ func testAnalyzer(platformAPI string) func(t *testing.T, when spec.G, it spec.S)
h.AssertEq(t, md.RunImage.TargetMetadata.ArchVariant, "MMX")
h.AssertEq(t, md.RunImage.TargetMetadata.OS, "windows")
h.AssertEq(t, md.RunImage.TargetMetadata.ID, "id software")
h.AssertNotNil(t, md.RunImage.TargetMetadata.Distribution)
h.AssertEq(t, md.RunImage.TargetMetadata.Distribution.Name, "moobuntu")
h.AssertEq(t, md.RunImage.TargetMetadata.Distribution.Version, "Helpful Holstein")
h.AssertNotNil(t, md.RunImage.TargetMetadata.Distro)
h.AssertEq(t, md.RunImage.TargetMetadata.Distro.Name, "moobuntu")
h.AssertEq(t, md.RunImage.TargetMetadata.Distro.Version, "Helpful Holstein")
}
})
})

View File

@ -49,7 +49,7 @@ type Builder struct {
}
func (b *Builder) Build() (*files.BuildMetadata, error) {
b.Logger.Debug("Starting build")
defer log.NewMeasurement("Builder", b.Logger)()
// ensure layers SBOM directory is removed
if err := os.RemoveAll(filepath.Join(b.LayersDir, "sbom")); err != nil {
@ -142,7 +142,6 @@ func (b *Builder) Build() (*files.BuildMetadata, error) {
b.Group.GroupExtensions[i] = ext.NoExtension().NoOpt()
}
b.Logger.Debug("Finished build")
return &files.BuildMetadata{
BOM: launchBOM,
Buildpacks: b.Group.Group,

View File

@ -3,11 +3,12 @@
package buildpack
import (
"fmt"
"os"
"path/filepath"
"github.com/BurntSushi/toml"
"github.com/buildpacks/lifecycle/internal/encoding"
)
type BpDescriptor struct {
@ -25,29 +26,22 @@ type StackMetadata struct {
}
type TargetMetadata struct {
OS string `json:"os" toml:"os"`
Arch string `json:"arch" toml:"arch"`
ArchVariant string `json:"arch-variant,omitempty" toml:"arch-variant"`
Distributions []OSDistribution `json:"distributions,omitempty" toml:"distributions"`
OS string `json:"os" toml:"os"`
Arch string `json:"arch" toml:"arch"`
ArchVariant string `json:"arch-variant,omitempty" toml:"arch-variant"`
Distros []OSDistro `json:"distros,omitempty" toml:"distros"`
}
func (t *TargetMetadata) String() string {
s := fmt.Sprintf("OS: %s, Arch: %s, ArchVariant: %s", t.OS, t.Arch, t.ArchVariant)
if len(t.Distributions) > 0 {
s += fmt.Sprintf(", Distributions: %s", t.Distributions)
}
return s
return encoding.ToJSONMaybe(*t)
}
type OSDistribution struct {
// OSDistro is an OS distribution that a buildpack or extension can support.
type OSDistro struct {
Name string `json:"name" toml:"name"`
Version string `json:"version" toml:"version"`
}
func (d OSDistribution) String() string {
return fmt.Sprintf("Distribution: (Name: %s, Version: %s)", d.Name, d.Version)
}
type BpInfo struct {
BaseInfo
SBOM []string `toml:"sbom-formats,omitempty" json:"sbom-formats,omitempty"`
@ -75,9 +69,9 @@ func ReadBpDescriptor(path string) (*BpDescriptor, error) {
if len(descriptor.Targets) == 0 {
for _, stack := range descriptor.Stacks {
if stack.ID == "io.buildpacks.stacks.bionic" {
descriptor.Targets = append(descriptor.Targets, TargetMetadata{OS: "linux", Arch: "amd64", Distributions: []OSDistribution{{Name: "ubuntu", Version: "18.04"}}})
descriptor.Targets = append(descriptor.Targets, TargetMetadata{OS: "linux", Arch: "amd64", Distros: []OSDistro{{Name: "ubuntu", Version: "18.04"}}})
} else if stack.ID == "*" {
descriptor.Targets = append(descriptor.Targets, TargetMetadata{OS: "*", Arch: "*", Distributions: []OSDistribution{}})
descriptor.Targets = append(descriptor.Targets, TargetMetadata{}) // matches any
}
}
}
@ -93,10 +87,10 @@ func ReadBpDescriptor(path string) (*BpDescriptor, error) {
bf := binFiles[len(binFiles)-i-1] // we're iterating backwards b/c os.ReadDir sorts "build.exe" after "build" but we want to preferentially detect windows first.
fname := bf.Name()
if fname == "build.exe" || fname == "build.bat" {
descriptor.Targets = append(descriptor.Targets, TargetMetadata{OS: "windows", Arch: "*"})
descriptor.Targets = append(descriptor.Targets, TargetMetadata{OS: "windows"})
}
if fname == "build" {
descriptor.Targets = append(descriptor.Targets, TargetMetadata{OS: "linux", Arch: "*"})
descriptor.Targets = append(descriptor.Targets, TargetMetadata{OS: "linux"})
}
}
}

View File

@ -24,14 +24,14 @@ func testBpDescriptor(t *testing.T, when spec.G, it spec.S) {
OS: "some-os",
Arch: "some-arch",
ArchVariant: "some-arch-variant",
Distributions: []buildpack.OSDistribution{
Distros: []buildpack.OSDistro{
{
Name: "some-os-dist",
Version: "some-os-dist-version",
},
},
}
h.AssertEq(t, tm.String(), "OS: some-os, Arch: some-arch, ArchVariant: some-arch-variant, Distributions: [Distribution: (Name: some-os-dist, Version: some-os-dist-version)]")
h.AssertEq(t, tm.String(), `{"os":"some-os","arch":"some-arch","arch-variant":"some-arch-variant","distros":[{"name":"some-os-dist","version":"some-os-dist-version"}]}`)
})
})
@ -42,7 +42,7 @@ func testBpDescriptor(t *testing.T, when spec.G, it spec.S) {
Arch: "some-arch",
ArchVariant: "some-arch-variant",
}
h.AssertEq(t, tm.String(), "OS: some-os, Arch: some-arch, ArchVariant: some-arch-variant")
h.AssertEq(t, tm.String(), `{"os":"some-os","arch":"some-arch","arch-variant":"some-arch-variant"}`)
})
})
})
@ -77,8 +77,8 @@ func testBpDescriptor(t *testing.T, when spec.G, it spec.S) {
h.AssertEq(t, len(descriptor.Targets), 1)
h.AssertEq(t, descriptor.Targets[0].Arch, "IA64")
h.AssertEq(t, descriptor.Targets[0].OS, "OpenVMS")
h.AssertEq(t, descriptor.Targets[0].Distributions[0].Name, "VSI OpenVMS")
h.AssertEq(t, descriptor.Targets[0].Distributions[0].Version, "V8.4-2L3")
h.AssertEq(t, descriptor.Targets[0].Distros[0].Name, "VSI OpenVMS")
h.AssertEq(t, descriptor.Targets[0].Distros[0].Version, "V8.4-2L3")
})
it("does translate one special stack value into target values for older apis", func() {
@ -97,8 +97,8 @@ func testBpDescriptor(t *testing.T, when spec.G, it spec.S) {
h.AssertEq(t, len(descriptor.Targets), 1)
h.AssertEq(t, descriptor.Targets[0].Arch, "amd64")
h.AssertEq(t, descriptor.Targets[0].OS, "linux")
h.AssertEq(t, descriptor.Targets[0].Distributions[0].Name, "ubuntu")
h.AssertEq(t, descriptor.Targets[0].Distributions[0].Version, "18.04")
h.AssertEq(t, descriptor.Targets[0].Distros[0].Name, "ubuntu")
h.AssertEq(t, descriptor.Targets[0].Distros[0].Version, "18.04")
})
it("translates one special stack value into target values", func() {
@ -117,8 +117,8 @@ func testBpDescriptor(t *testing.T, when spec.G, it spec.S) {
h.AssertEq(t, len(descriptor.Targets), 1)
h.AssertEq(t, descriptor.Targets[0].Arch, "amd64")
h.AssertEq(t, descriptor.Targets[0].OS, "linux")
h.AssertEq(t, descriptor.Targets[0].Distributions[0].Name, "ubuntu")
h.AssertEq(t, descriptor.Targets[0].Distributions[0].Version, "18.04")
h.AssertEq(t, descriptor.Targets[0].Distros[0].Name, "ubuntu")
h.AssertEq(t, descriptor.Targets[0].Distros[0].Version, "18.04")
})
it("does not translate non-special stack values", func() {
@ -152,9 +152,9 @@ func testBpDescriptor(t *testing.T, when spec.G, it spec.S) {
h.AssertEq(t, descriptor.Stacks[0].ID, "some.non-magic.value")
h.AssertEq(t, len(descriptor.Targets), 1)
h.AssertEq(t, len(descriptor.Targets), 1)
h.AssertEq(t, descriptor.Targets[0].Arch, "*")
h.AssertEq(t, descriptor.Targets[0].Arch, "")
h.AssertEq(t, descriptor.Targets[0].OS, "linux")
h.AssertEq(t, len(descriptor.Targets[0].Distributions), 0)
h.AssertEq(t, len(descriptor.Targets[0].Distros), 0)
})
it("detects windows/* if batch files are present and ignores linux", func() {
@ -170,9 +170,9 @@ func testBpDescriptor(t *testing.T, when spec.G, it spec.S) {
h.AssertEq(t, descriptor.Buildpack.SBOM, []string{"application/vnd.cyclonedx+json"})
// specific behaviors for this test
h.AssertEq(t, len(descriptor.Targets), 2)
h.AssertEq(t, descriptor.Targets[0].Arch, "*")
h.AssertEq(t, descriptor.Targets[0].Arch, "")
h.AssertEq(t, descriptor.Targets[0].OS, "windows")
h.AssertEq(t, descriptor.Targets[1].Arch, "*")
h.AssertEq(t, descriptor.Targets[1].Arch, "")
h.AssertEq(t, descriptor.Targets[1].OS, "linux")
})
})

View File

@ -48,19 +48,18 @@ func (d *ExtDescriptor) inferTargets() error {
bf := binFiles[len(binFiles)-i-1] // we're iterating backwards b/c os.ReadDir sorts "foo.exe" after "foo" but we want to preferentially detect windows first.
fname := bf.Name()
if !windowsDetected && (fname == "detect.exe" || fname == "detect.bat" || fname == "generate.exe" || fname == "generate.bat") {
d.Targets = append(d.Targets, TargetMetadata{OS: "windows", Arch: "*"})
d.Targets = append(d.Targets, TargetMetadata{OS: "windows"})
windowsDetected = true
}
if !linuxDetected && (fname == "detect" || fname == "generate") {
d.Targets = append(d.Targets, TargetMetadata{OS: "linux", Arch: "*"})
d.Targets = append(d.Targets, TargetMetadata{OS: "linux"})
linuxDetected = true
}
}
}
}
// fallback: if nothing worked just mark it */*
if len(d.Targets) == 0 {
d.Targets = append(d.Targets, TargetMetadata{OS: "*", Arch: "*"})
d.Targets = append(d.Targets, TargetMetadata{}) // matches any
}
return nil
}

View File

@ -29,15 +29,15 @@ func testExtDescriptor(t *testing.T, when spec.G, it spec.S) {
h.AssertEq(t, descriptor.Extension.Homepage, "Extension A Homepage")
h.AssertEq(t, len(descriptor.Targets), 1)
h.AssertEq(t, descriptor.Targets[0].OS, "linux")
h.AssertEq(t, descriptor.Targets[0].Arch, "*")
h.AssertEq(t, descriptor.Targets[0].Arch, "")
})
it("infers */* if there's no files to infer from", func() {
path := filepath.Join("testdata", "extension", "by-id", "B", "v1", "extension.toml")
descriptor, err := buildpack.ReadExtDescriptor(path)
h.AssertNil(t, err)
h.AssertEq(t, len(descriptor.Targets), 1)
h.AssertEq(t, descriptor.Targets[0].OS, "*")
h.AssertEq(t, descriptor.Targets[0].Arch, "*")
h.AssertEq(t, descriptor.Targets[0].OS, "")
h.AssertEq(t, descriptor.Targets[0].Arch, "")
})
it("slices, it dices, it even does windows", func() {
path := filepath.Join("testdata", "extension", "by-id", "D", "v1", "extension.toml")
@ -45,7 +45,7 @@ func testExtDescriptor(t *testing.T, when spec.G, it spec.S) {
h.AssertNil(t, err)
h.AssertEq(t, len(descriptor.Targets), 1)
h.AssertEq(t, descriptor.Targets[0].OS, "windows")
h.AssertEq(t, descriptor.Targets[0].Arch, "*")
h.AssertEq(t, descriptor.Targets[0].Arch, "")
})
})
}

View File

@ -10,6 +10,6 @@ sbom-formats = ["application/vnd.cyclonedx+json"]
[[targets]]
os = "OpenVMS"
arch = "IA64"
[[targets.distributions]]
[[targets.distros]]
name = "VSI OpenVMS"
version = "V8.4-2L3"

View File

@ -19,6 +19,7 @@ type LayerDir interface {
}
func (e *Exporter) Cache(layersDir string, cacheStore Cache) error {
defer log.NewMeasurement("Cache", e.Logger)()
var err error
if !cacheStore.Exists() {
e.Logger.Info("Layer cache not found")

View File

@ -269,19 +269,19 @@ func testCache(t *testing.T, when spec.G, it spec.S) {
AnyTimes()
layersDir = filepath.Join("testdata", "cacher", "invalid-layers")
h.AssertNil(t, exporter.Cache(layersDir, testCache))
h.AssertEq(t, len(logHandler.Entries), 3)
h.AssertEq(t, len(logHandler.Entries), 5)
})
it("warns when there is a cache=true layer without contents", func() {
h.AssertStringContains(t, logHandler.Entries[0].Message, "Failed to cache layer 'buildpack.id:cache-true-no-contents' because it has no contents")
h.AssertStringContains(t, logHandler.Entries[1].Message, "Failed to cache layer 'buildpack.id:cache-true-no-contents' because it has no contents")
})
it("warns when there is an error adding a layer", func() {
h.AssertStringContains(t, logHandler.Entries[1].Message, "Failed to cache layer 'buildpack.id:layer-1': creating layer 'buildpack.id:layer-1': test error")
h.AssertStringContains(t, logHandler.Entries[2].Message, "Failed to cache layer 'buildpack.id:layer-1': creating layer 'buildpack.id:layer-1': test error")
})
it("continues caching valid layers", func() {
h.AssertStringContains(t, logHandler.Entries[2].Message, "Adding cache layer 'buildpack.id:layer-2'")
h.AssertStringContains(t, logHandler.Entries[3].Message, "Adding cache layer 'buildpack.id:layer-2'")
assertCacheHasLayer(t, testCache, "buildpack.id:layer-2")
})
})

View File

@ -57,6 +57,12 @@ func Run(c Command, withPhaseName string, asSubcommand bool) {
}
cmd.DefaultLogger.Debugf("Starting %s...", withPhaseName)
for _, arg := range flagSet.Args() {
if arg[0:1] == "-" {
cmd.DefaultLogger.Warnf("Warning: unconsumed flag-like positional arg: \n\t%s\n\t This will not be interpreted as a flag.\n\t Did you mean to put this before the first positional argument?", arg)
}
}
// Warn when CNB_PLATFORM_API is unset
if os.Getenv(platform.EnvPlatformAPI) == "" {
cmd.DefaultLogger.Warnf("%s is unset; using Platform API version '%s'", platform.EnvPlatformAPI, platform.DefaultPlatformAPI)

View File

@ -75,7 +75,7 @@ func subcommand(platformAPI string) {
case "extend":
cli.Run(&extendCmd{Platform: platform.NewPlatformFor(platformAPI)}, phase, true)
default:
cmd.Exit(cmd.FailCode(cmd.CodeForInvalidArgs, "unknown phase:", phase))
cmd.Exit(cmd.FailCode(cmd.CodeForInvalidArgs, "recognize phase:", phase, "\nValid phases: detect, analyze, restore, build, export, rebase, create, extend"))
}
}

View File

@ -10,6 +10,7 @@ import (
"github.com/buildpacks/imgutil/layout"
"github.com/buildpacks/imgutil/layout/sparse"
"github.com/buildpacks/imgutil/remote"
"github.com/docker/docker/client"
"github.com/google/go-containerregistry/pkg/authn"
"github.com/buildpacks/lifecycle"
@ -17,6 +18,7 @@ import (
"github.com/buildpacks/lifecycle/buildpack"
"github.com/buildpacks/lifecycle/cmd"
"github.com/buildpacks/lifecycle/cmd/lifecycle/cli"
"github.com/buildpacks/lifecycle/image"
"github.com/buildpacks/lifecycle/internal/encoding"
"github.com/buildpacks/lifecycle/internal/layer"
"github.com/buildpacks/lifecycle/platform"
@ -29,12 +31,14 @@ const kanikoDir = "/kaniko"
type restoreCmd struct {
*platform.Platform
keychain authn.Keychain // construct if necessary before dropping privileges
docker client.CommonAPIClient // construct if necessary before dropping privileges
keychain authn.Keychain // construct if necessary before dropping privileges
}
// DefineFlags defines the flags that are considered valid and reads their values (if provided).
func (r *restoreCmd) DefineFlags() {
if r.PlatformAPI.AtLeast("0.12") {
cli.FlagUseDaemon(&r.UseDaemon)
cli.FlagGeneratedDir(&r.GeneratedDir)
}
if r.PlatformAPI.AtLeast("0.10") {
@ -46,9 +50,10 @@ func (r *restoreCmd) DefineFlags() {
}
cli.FlagCacheDir(&r.CacheDir)
cli.FlagCacheImage(&r.CacheImageRef)
cli.FlagGroupPath(&r.GroupPath)
cli.FlagUID(&r.UID)
cli.FlagGID(&r.GID)
cli.FlagGroupPath(&r.GroupPath)
cli.FlagLayersDir(&r.LayersDir)
cli.FlagUID(&r.UID)
}
// Args validates arguments and flags, and fills in default values.
@ -68,6 +73,13 @@ func (r *restoreCmd) Privileges() error {
if err != nil {
return cmd.FailErr(err, "resolve keychain")
}
if r.UseDaemon {
var err error
r.docker, err = priv.DockerClient()
if err != nil {
return cmd.FailErr(err, "initialize docker client")
}
}
if err = priv.EnsureOwner(r.UID, r.GID, r.LayersDir, r.CacheDir, r.KanikoDir); err != nil {
return cmd.FailErr(err, "chown volumes")
}
@ -98,28 +110,29 @@ func (r *restoreCmd) Exec() error {
cmd.DefaultLogger.Debugf(encoding.ToJSONMaybe(analyzedMD.BuildImage))
}
var (
remoteRunImage imgutil.Image
runImage imgutil.Image
)
runImageName := analyzedMD.RunImageImage() // FIXME: if we have a digest reference available in `Reference` (e.g., in the non-daemon case) we should use it
if r.supportsRunImageExtension() && needsPulling(analyzedMD.RunImage) {
cmd.DefaultLogger.Debugf("Pulling run image metadata for %s...", runImageName)
remoteRunImage, err = r.pullSparse(runImageName)
runImage, err = r.pullSparse(runImageName)
if err != nil {
return cmd.FailErr(err, "pull run image")
}
// update analyzed metadata, even if we only needed to pull the image metadata, because
// the extender needs a digest reference in analyzed.toml,
// and daemon images will only have a daemon image ID
if err = updateAnalyzedMD(&analyzedMD, remoteRunImage); err != nil {
if err = updateAnalyzedMD(&analyzedMD, runImage); err != nil {
return cmd.FailErr(err, "update analyzed metadata")
}
} else if r.supportsTargetData() && needsUpdating(analyzedMD.RunImage) {
cmd.DefaultLogger.Debugf("Updating run image info in analyzed metadata...")
remoteRunImage, err = remote.NewImage(runImageName, r.keychain)
if err != nil || !remoteRunImage.Found() {
h := image.NewHandler(r.docker, r.keychain, r.LayoutDir, r.UseLayout)
runImage, err = h.InitImage(runImageName)
if err != nil || !runImage.Found() {
return cmd.FailErr(err, "pull run image")
}
if err = updateAnalyzedMD(&analyzedMD, remoteRunImage); err != nil {
if err = updateAnalyzedMD(&analyzedMD, runImage); err != nil {
return cmd.FailErr(err, "update analyzed metadata")
}
}
@ -197,7 +210,7 @@ func (r *restoreCmd) supportsBuildImageExtension() bool {
}
func (r *restoreCmd) supportsRunImageExtension() bool {
return r.PlatformAPI.AtLeast("0.12")
return r.PlatformAPI.AtLeast("0.12") && !r.UseLayout // FIXME: add layout support as part of https://github.com/buildpacks/lifecycle/issues/1057
}
func (r *restoreCmd) supportsTargetData() bool {

View File

@ -126,6 +126,7 @@ func (f *DetectorFactory) verifyAPIs(orderBp buildpack.Order, orderExt buildpack
}
func (d *Detector) Detect() (buildpack.Group, files.Plan, error) {
defer log.NewMeasurement("Detector", d.Logger)()
group, plan, detectErr := d.DetectOrder(d.Order)
for _, e := range d.memHandler.Entries {
if detectErr != nil || e.Level >= d.Logger.LogLevel() {
@ -196,7 +197,7 @@ func isWildcard(t files.TargetMetadata) bool {
func hasWildcard(ts []buildpack.TargetMetadata) bool {
for _, tm := range ts {
if tm.OS == "*" && tm.Arch == "*" {
if tm.OS == "" && tm.Arch == "" {
return true
}
}

View File

@ -826,9 +826,9 @@ func testDetector(t *testing.T, when spec.G, it spec.S) {
it("totally works if the constraints are met", func() {
detector.AnalyzeMD.RunImage = &files.RunImage{
TargetMetadata: &files.TargetMetadata{
OS: "MacOS",
Arch: "ARM64",
Distribution: &files.OSDistribution{Name: "MacOS", Version: "snow cheetah"},
OS: "MacOS",
Arch: "ARM64",
Distro: &files.OSDistro{Name: "MacOS", Version: "snow cheetah"},
},
}
@ -837,9 +837,9 @@ func testDetector(t *testing.T, when spec.G, it spec.S) {
Buildpack: buildpack.BpInfo{BaseInfo: buildpack.BaseInfo{ID: "A", Version: "v1"}},
Targets: []buildpack.TargetMetadata{
{Arch: "P6", ArchVariant: "Pentium Pro", OS: "Win95",
Distributions: []buildpack.OSDistribution{
Distros: []buildpack.OSDistro{
{Name: "Windows 95", Version: "OSR1"}, {Name: "Windows 95", Version: "OSR2.5"}}},
{Arch: "ARM64", OS: "MacOS", Distributions: []buildpack.OSDistribution{{Name: "MacOS", Version: "snow cheetah"}}}},
{Arch: "ARM64", OS: "MacOS", Distros: []buildpack.OSDistro{{Name: "MacOS", Version: "snow cheetah"}}}},
}
dirStore.EXPECT().LookupBp("A", "v1").Return(bpA1, nil).AnyTimes()
executor.EXPECT().Detect(bpA1, gomock.Any(), gomock.Any())
@ -862,9 +862,9 @@ func testDetector(t *testing.T, when spec.G, it spec.S) {
it("was born to be wildcard compliant", func() {
detector.AnalyzeMD.RunImage = &files.RunImage{
TargetMetadata: &files.TargetMetadata{
OS: "MacOS",
Arch: "ARM64",
Distribution: &files.OSDistribution{Name: "MacOS", Version: "snow cheetah"},
OS: "MacOS",
Arch: "ARM64",
Distro: &files.OSDistro{Name: "MacOS", Version: "snow cheetah"},
},
}
@ -872,7 +872,7 @@ func testDetector(t *testing.T, when spec.G, it spec.S) {
WithAPI: "0.12",
Buildpack: buildpack.BpInfo{BaseInfo: buildpack.BaseInfo{ID: "A", Version: "v1"}},
Targets: []buildpack.TargetMetadata{
{Arch: "*", OS: "*"}},
{Arch: "", OS: ""}},
}
dirStore.EXPECT().LookupBp("A", "v1").Return(bpA1, nil).AnyTimes()
executor.EXPECT().Detect(bpA1, gomock.Any(), gomock.Any())
@ -896,9 +896,9 @@ func testDetector(t *testing.T, when spec.G, it spec.S) {
it("totally works if the constraints are met", func() {
detector.AnalyzeMD.RunImage = &files.RunImage{
TargetMetadata: &files.TargetMetadata{
OS: "MacOS",
Arch: "ARM64",
Distribution: &files.OSDistribution{Name: "MacOS", Version: "snow cheetah"},
OS: "MacOS",
Arch: "ARM64",
Distro: &files.OSDistro{Name: "MacOS", Version: "snow cheetah"},
},
}
@ -917,9 +917,9 @@ func testDetector(t *testing.T, when spec.G, it spec.S) {
Buildpack: buildpack.BpInfo{BaseInfo: buildpack.BaseInfo{ID: "A", Version: "v1"}},
Targets: []buildpack.TargetMetadata{
{Arch: "P6", ArchVariant: "Pentium Pro", OS: "Win95",
Distributions: []buildpack.OSDistribution{
Distros: []buildpack.OSDistro{
{Name: "Windows 95", Version: "OSR1"}, {Name: "Windows 95", Version: "OSR2.5"}}},
{Arch: "ARM64", OS: "MacOS", Distributions: []buildpack.OSDistribution{{Name: "MacOS", Version: "snow cheetah"}}}},
{Arch: "ARM64", OS: "MacOS", Distros: []buildpack.OSDistro{{Name: "MacOS", Version: "snow cheetah"}}}},
}
dirStore.EXPECT().LookupBp("A", "v1").Return(bpA1, nil).AnyTimes()
@ -946,9 +946,9 @@ func testDetector(t *testing.T, when spec.G, it spec.S) {
it("errors if the buildpacks don't share that target arch/os", func() {
detector.AnalyzeMD.RunImage = &files.RunImage{
TargetMetadata: &files.TargetMetadata{
OS: "MacOS",
Arch: "ARM64",
Distribution: &files.OSDistribution{Name: "MacOS", Version: "some kind of big cat"},
OS: "MacOS",
Arch: "ARM64",
Distro: &files.OSDistro{Name: "MacOS", Version: "some kind of big cat"},
},
}
@ -957,10 +957,10 @@ func testDetector(t *testing.T, when spec.G, it spec.S) {
Buildpack: buildpack.BpInfo{BaseInfo: buildpack.BaseInfo{ID: "A", Version: "v1"}},
Targets: []buildpack.TargetMetadata{
{Arch: "P6", ArchVariant: "Pentium Pro", OS: "Win95",
Distributions: []buildpack.OSDistribution{
Distros: []buildpack.OSDistro{
{Name: "Windows 95", Version: "OSR1"}, {Name: "Windows 95", Version: "OSR2.5"}}},
{Arch: "Pentium M", OS: "Win98",
Distributions: []buildpack.OSDistribution{{Name: "Windows 2000", Version: "Server"}}},
Distros: []buildpack.OSDistro{{Name: "Windows 2000", Version: "Server"}}},
},
}
dirStore.EXPECT().LookupBp("A", "v1").Return(bpA1, nil).AnyTimes()
@ -972,7 +972,7 @@ func testDetector(t *testing.T, when spec.G, it spec.S) {
h.AssertEq(t, ok, true)
outs := val.(buildpack.DetectOutputs)
h.AssertEq(t, outs.Code, -1)
h.AssertStringContains(t, outs.Err.Error(), `unable to satisfy target os/arch constraints; run image: {"os":"MacOS","arch":"ARM64","distribution":{"name":"MacOS","version":"some kind of big cat"}}, buildpack: [{"os":"Win95","arch":"P6","arch-variant":"Pentium Pro","distributions":[{"name":"Windows 95","version":"OSR1"},{"name":"Windows 95","version":"OSR2.5"}]},{"os":"Win98","arch":"Pentium M","distributions":[{"name":"Windows 2000","version":"Server"}]}]`)
h.AssertStringContains(t, outs.Err.Error(), `unable to satisfy target os/arch constraints; run image: {"os":"MacOS","arch":"ARM64","distro":{"name":"MacOS","version":"some kind of big cat"}}, buildpack: [{"os":"Win95","arch":"P6","arch-variant":"Pentium Pro","distros":[{"name":"Windows 95","version":"OSR1"},{"name":"Windows 95","version":"OSR2.5"}]},{"os":"Win98","arch":"Pentium M","distros":[{"name":"Windows 2000","version":"Server"}]}]`)
return []buildpack.GroupElement{}, []files.BuildPlanEntry{}, nil
})

1
env/build.go vendored
View File

@ -11,7 +11,6 @@ var BuildEnvIncludelist = []string{
"CNB_TARGET_OS",
"CNB_TARGET_ARCH",
"CNB_TARGET_ARCH_VARIANT",
"CNB_TARGET_ID",
"CNB_TARGET_DISTRO_NAME",
"CNB_TARGET_DISTRO_VERSION",
"HOSTNAME",

2
env/build_test.go vendored
View File

@ -38,7 +38,6 @@ func testBuildEnv(t *testing.T, when spec.G, it spec.S) {
"CNB_TARGET_ARCH=st-louis",
"CNB_TARGET_ARCH_VARIANT=suburban",
"CNB_TARGET_OS=BeOS",
"CNB_TARGET_ID=tahr-jzay",
"CNB_TARGET_DISTRO_NAME=web",
"CNB_TARGET_DISTRO_VERSION=3.0",
"HOSTNAME=some-hostname",
@ -64,7 +63,6 @@ func testBuildEnv(t *testing.T, when spec.G, it spec.S) {
"CNB_TARGET_ARCH_VARIANT=suburban",
"CNB_TARGET_DISTRO_NAME=web",
"CNB_TARGET_DISTRO_VERSION=3.0",
"CNB_TARGET_ID=tahr-jzay",
"CNB_TARGET_OS=BeOS",
"CPATH=some-cpath",
"HOME=some-home",

View File

@ -88,6 +88,7 @@ type ExportOptions struct {
func (e *Exporter) Export(opts ExportOptions) (files.Report, error) {
var err error
defer log.NewMeasurement("Exporter", e.Logger)()
if e.PlatformAPI.AtLeast("0.11") {
if err = e.copyBuildpacksioSBOMs(opts); err != nil {

View File

@ -285,6 +285,7 @@ const (
)
func (e *Extender) extend(kind string, baseImage v1.Image, logger log.Logger) (v1.Image, error) {
defer log.NewMeasurement("Extender", logger)()
logger.Debugf("Extending base image for %s: %s", kind, e.ImageRef)
dockerfiles, err := e.dockerfilesFor(kind, logger)
if err != nil {

View File

@ -118,6 +118,7 @@ type GenerateResult struct {
}
func (g *Generator) Generate() (GenerateResult, error) {
defer log.NewMeasurement("Generator", g.Logger)()
inputs := g.getGenerateInputs()
extensionOutputParentDir, err := os.MkdirTemp("", "cnb-extensions-generated.")
if err != nil {

View File

@ -1,3 +1,4 @@
// Package log has logging interfaces for convenience in lifecycle
package log
import "github.com/apex/log"

33
log/timelog.go Normal file
View File

@ -0,0 +1,33 @@
package log
import "time"
// Chronit is, I guess, short for chronological unit because it measures time or something
type Chronit struct {
StartTime time.Time
EndTime time.Time
Log Logger
FunctionName string
}
// NewMeasurement initializes a chronological measuring tool, logs out the start time, and returns a function you can defer that will log the end time
func NewMeasurement(funcName string, lager Logger) func() {
c := Chronit{Log: lager, FunctionName: funcName}
c.RecordStart()
return func() {
c.RecordEnd()
}
}
// RecordStart grabs the current time and logs it, but it will be called for you if you use the NewMeasurement convenience function.
func (c *Chronit) RecordStart() {
c.StartTime = time.Now()
c.Log.Infof("Timer: %s started at %s", c.FunctionName, c.StartTime.Format(time.RFC3339))
}
// RecordEnd is called in the function returned by NewMeasurement.
// the EndTime will be populated just in case you'll keep the object in scope for later.
func (c *Chronit) RecordEnd() {
c.EndTime = time.Now()
c.Log.Infof("Timer: %s ran for %v and ended at %s", c.FunctionName, c.EndTime.Sub(c.StartTime), c.EndTime.Format(time.RFC3339))
}

83
log/timelog_test.go Normal file
View File

@ -0,0 +1,83 @@
package log_test
import (
"testing"
"time"
"github.com/sclevine/spec"
"github.com/sclevine/spec/report"
"github.com/buildpacks/lifecycle/log"
h "github.com/buildpacks/lifecycle/testhelpers"
)
type mockLog struct {
callCount map[string]int
}
func (m mockLog) incr(key string) {
val, ok := m.callCount[key]
if !ok {
m.callCount[key] = 1
} else {
m.callCount[key] = val + 1
}
}
func (m mockLog) Debug(msg string) {
m.incr("Debug")
}
func (m mockLog) Debugf(fmt string, v ...interface{}) {
m.incr("Debug")
}
func (m mockLog) Info(msg string) {
m.incr("Info")
}
func (m mockLog) Infof(fmt string, v ...interface{}) {
m.incr("Info")
}
func (m mockLog) Warn(msg string) {
m.incr("Warn")
}
func (m mockLog) Warnf(fmt string, v ...interface{}) {
m.incr("Warn")
}
func (m mockLog) Error(msg string) {
m.incr("Error")
}
func (m mockLog) Errorf(fmt string, v ...interface{}) {
m.incr("Error")
}
func TestTimeLog(t *testing.T) {
spec.Run(t, "Exporter", testTimeLog, spec.Parallel(), spec.Report(report.Terminal{}))
}
func testTimeLog(t *testing.T, when spec.G, it spec.S) {
when("we use the time log", func() {
it("the granular api works step by step", func() {
logger := mockLog{callCount: map[string]int{}}
c1 := log.Chronit{}
nullTime := time.Time{}
h.AssertEq(t, c1.StartTime, nullTime)
h.AssertEq(t, c1.EndTime, nullTime)
c1.Log = logger
c1.RecordStart()
h.AssertEq(t, logger.callCount["Info"], 1)
h.AssertEq(t, c1.StartTime == nullTime, false)
h.AssertEq(t, c1.EndTime, nullTime)
c1.RecordEnd()
h.AssertEq(t, logger.callCount["Info"], 2)
h.AssertEq(t, c1.EndTime == nullTime, false)
})
it("the convenience functions call the logger", func() {
logger := mockLog{callCount: map[string]int{}}
endfunc := log.NewMeasurement("value", logger)
h.AssertEq(t, logger.callCount["Info"], 1)
endfunc()
h.AssertEq(t, logger.callCount["Info"], 2)
})
})
}

View File

@ -1,16 +1,16 @@
package files
import (
"fmt"
"os"
"github.com/BurntSushi/toml"
"github.com/buildpacks/lifecycle/buildpack"
"github.com/buildpacks/lifecycle/internal/encoding"
"github.com/buildpacks/lifecycle/log"
)
// Analyzed is written by the analyzer as analyzed.toml to record information about:
// Analyzed is written by the analyzer as analyzed.toml and updated in subsequent phases to record information about:
// * the previous image (if it exists),
// * the run image,
// * the build image (if provided).
@ -157,17 +157,15 @@ type TargetMetadata struct {
Arch string `json:"arch" toml:"arch"`
ArchVariant string `json:"arch-variant,omitempty" toml:"arch-variant,omitempty"`
Distribution *OSDistribution `json:"distribution,omitempty" toml:"distribution,omitempty"`
Distro *OSDistro `json:"distro,omitempty" toml:"distro,omitempty"`
}
func (t *TargetMetadata) String() string {
if t.Distribution != nil {
return fmt.Sprintf("OS: %s, Arch: %s, ArchVariant: %s, Distribution: (Name: %s, Version: %s)", t.OS, t.Arch, t.ArchVariant, t.Distribution.Name, t.Distribution.Version)
}
return fmt.Sprintf("OS: %s, Arch: %s, ArchVariant: %s", t.OS, t.Arch, t.ArchVariant)
return encoding.ToJSONMaybe(*t)
}
type OSDistribution struct {
// OSDistro is the OS distribution that a base image provides.
type OSDistro struct {
Name string `json:"name" toml:"name"`
Version string `json:"version" toml:"version"`
}

View File

@ -14,9 +14,12 @@ import (
)
const (
TargetLabel = "io.buildpacks.id"
OSDistributionNameLabel = "io.buildpacks.distribution.name"
OSDistributionVersionLabel = "io.buildpacks.distribution.version"
// TargetLabel is the label containing the target ID.
TargetLabel = "io.buildpacks.base.id"
// OSDistroNameLabel is the label containing the OS distribution name.
OSDistroNameLabel = "io.buildpacks.distro.name"
// OSDistroVersionLabel is the label containing the OS distribution version.
OSDistroVersionLabel = "io.buildpacks.distro.version"
)
func BestRunImageMirrorFor(targetRegistry string, runImageMD files.RunImageForExport, checkReadAccess CheckReadAccess) (string, error) {

View File

@ -149,15 +149,14 @@ func testRunImage(t *testing.T, when spec.G, it spec.S) {
when(".EnvVarsFor", func() {
it("returns the right thing", func() {
tm := files.TargetMetadata{Arch: "pentium", ArchVariant: "mmx", ID: "my-id", OS: "linux", Distribution: &files.OSDistribution{Name: "nix", Version: "22.11"}}
tm := files.TargetMetadata{Arch: "pentium", ArchVariant: "mmx", ID: "my-id", OS: "linux", Distro: &files.OSDistro{Name: "nix", Version: "22.11"}}
observed := platform.EnvVarsFor(tm)
h.AssertContains(t, observed, "CNB_TARGET_ARCH="+tm.Arch)
h.AssertContains(t, observed, "CNB_TARGET_ARCH_VARIANT="+tm.ArchVariant)
h.AssertContains(t, observed, "CNB_TARGET_DISTRO_NAME="+tm.Distribution.Name)
h.AssertContains(t, observed, "CNB_TARGET_DISTRO_VERSION="+tm.Distribution.Version)
h.AssertContains(t, observed, "CNB_TARGET_ID="+tm.ID)
h.AssertContains(t, observed, "CNB_TARGET_DISTRO_NAME="+tm.Distro.Name)
h.AssertContains(t, observed, "CNB_TARGET_DISTRO_VERSION="+tm.Distro.Version)
h.AssertContains(t, observed, "CNB_TARGET_OS="+tm.OS)
h.AssertEq(t, len(observed), 6)
h.AssertEq(t, len(observed), 5)
})
it("does not return the wrong thing", func() {

View File

@ -15,15 +15,12 @@ func EnvVarsFor(tm files.TargetMetadata) []string {
ret := []string{"CNB_TARGET_OS=" + tm.OS, "CNB_TARGET_ARCH=" + tm.Arch}
ret = append(ret, "CNB_TARGET_ARCH_VARIANT="+tm.ArchVariant)
var distName, distVersion string
if tm.Distribution != nil {
distName = tm.Distribution.Name
distVersion = tm.Distribution.Version
if tm.Distro != nil {
distName = tm.Distro.Name
distVersion = tm.Distro.Version
}
ret = append(ret, "CNB_TARGET_DISTRO_NAME="+distName)
ret = append(ret, "CNB_TARGET_DISTRO_VERSION="+distVersion)
if tm.ID != "" {
ret = append(ret, "CNB_TARGET_ID="+tm.ID)
}
return ret
}
@ -46,10 +43,10 @@ func GetTargetMetadata(fromImage imgutil.Image) (*files.TargetMetadata, error) {
if err != nil {
return &tm, err
}
distName, distNameExists := labels[OSDistributionNameLabel]
distVersion, distVersionExists := labels[OSDistributionVersionLabel]
distName, distNameExists := labels[OSDistroNameLabel]
distVersion, distVersionExists := labels[OSDistroVersionLabel]
if distNameExists || distVersionExists {
tm.Distribution = &files.OSDistribution{Name: distName, Version: distVersion}
tm.Distro = &files.OSDistro{Name: distName, Version: distVersion}
}
if id, exists := labels[TargetLabel]; exists {
tm.ID = id
@ -70,51 +67,58 @@ func GetTargetOSFromFileSystem(d fsutil.Detector, tm *files.TargetMetadata, logg
info := d.GetInfo(contents)
if info.Version != "" || info.Name != "" {
tm.OS = "linux"
tm.Distribution = &files.OSDistribution{Name: info.Name, Version: info.Version}
tm.Distro = &files.OSDistro{Name: info.Name, Version: info.Version}
}
}
}
// TargetSatisfiedForBuild treats optional fields (ArchVariant and Distributions) as wildcards if empty, returns true if all populated fields match
func TargetSatisfiedForBuild(t files.TargetMetadata, o buildpack.TargetMetadata) bool {
if (o.Arch != "*" && t.Arch != o.Arch) || (o.OS != "*" && t.OS != o.OS) {
// TargetSatisfiedForBuild treats empty fields as wildcards and returns true if all populated fields match.
func TargetSatisfiedForBuild(base files.TargetMetadata, module buildpack.TargetMetadata) bool {
if !matches(base.OS, module.OS) {
return false
}
if t.ArchVariant != "" && o.ArchVariant != "" && t.ArchVariant != o.ArchVariant {
if !matches(base.Arch, module.Arch) {
return false
}
if !matches(base.ArchVariant, module.ArchVariant) {
return false
}
if base.Distro == nil || len(module.Distros) == 0 {
return true
}
foundMatchingDist := false
for _, modDist := range module.Distros {
if matches(base.Distro.Name, modDist.Name) && matches(base.Distro.Version, modDist.Version) {
foundMatchingDist = true
break
}
}
return foundMatchingDist
}
// if either of the lengths of Distributions are zero, treat it as a wildcard.
if t.Distribution != nil && len(o.Distributions) > 0 {
// this could be more efficient but the lists are probably short...
found := false
for _, odist := range o.Distributions {
if t.Distribution.Name == odist.Name && t.Distribution.Version == odist.Version {
found = true
continue
}
}
if !found {
return false
}
func matches(target1, target2 string) bool {
if target1 == "" || target2 == "" {
return true
}
return true
return target1 == target2
}
// TargetSatisfiedForRebase treats optional fields (ArchVariant and Distribution fields) as wildcards if empty, returns true if all populated fields match
func TargetSatisfiedForRebase(t files.TargetMetadata, appTargetMetadata files.TargetMetadata) bool {
if t.Arch != appTargetMetadata.Arch || t.OS != appTargetMetadata.OS {
if t.OS != appTargetMetadata.OS || t.Arch != appTargetMetadata.Arch {
return false
}
if t.ArchVariant != "" && appTargetMetadata.ArchVariant != "" && t.ArchVariant != appTargetMetadata.ArchVariant {
if !matches(t.ArchVariant, appTargetMetadata.ArchVariant) {
return false
}
if t.Distribution != nil && appTargetMetadata.Distribution != nil {
if t.Distribution.Name != appTargetMetadata.Distribution.Name {
if t.Distro != nil {
if appTargetMetadata.Distro == nil {
return false
}
if t.Distribution.Version != appTargetMetadata.Distribution.Version {
if t.Distro.Name != "" && t.Distro.Name != appTargetMetadata.Distro.Name {
return false
}
if t.Distro.Version != "" && t.Distro.Version != appTargetMetadata.Distro.Version {
return false
}
}

View File

@ -21,50 +21,150 @@ func TestTargetData(t *testing.T) {
func testTargetData(t *testing.T, when spec.G, it spec.S) {
when(".TargetSatisfiedForBuild", func() {
it("requires equality of OS and Arch", func() {
d := files.TargetMetadata{OS: "Win95", Arch: "Pentium"}
var baseTarget files.TargetMetadata
when("base image data", func() {
when("has os and arch", func() {
baseTarget = files.TargetMetadata{OS: "Win95", Arch: "Pentium"}
if platform.TargetSatisfiedForBuild(d, buildpack.TargetMetadata{OS: "Win98", Arch: d.Arch}) {
t.Fatal("TargetMetadata with different OS were equal")
}
if platform.TargetSatisfiedForBuild(d, buildpack.TargetMetadata{OS: d.OS, Arch: "Pentium MMX"}) {
t.Fatal("TargetMetadata with different Arch were equal")
}
if !platform.TargetSatisfiedForBuild(d, buildpack.TargetMetadata{OS: d.OS, Arch: d.Arch, ArchVariant: "MMX"}) {
t.Fatal("blank arch variant was not treated as wildcard")
}
if !platform.TargetSatisfiedForBuild(d, buildpack.TargetMetadata{
OS: d.OS,
Arch: d.Arch,
Distributions: []buildpack.OSDistribution{{Name: "a", Version: "2"}},
}) {
t.Fatal("blank distributions list was not treated as wildcard")
}
when("buildpack data", func() {
when("has os and arch", func() {
it("must match", func() {
h.AssertEq(t, platform.TargetSatisfiedForBuild(baseTarget, buildpack.TargetMetadata{OS: baseTarget.OS, Arch: baseTarget.Arch}), true)
h.AssertEq(t, platform.TargetSatisfiedForBuild(baseTarget, buildpack.TargetMetadata{OS: "Win98", Arch: baseTarget.Arch}), false)
h.AssertEq(t, platform.TargetSatisfiedForBuild(baseTarget, buildpack.TargetMetadata{OS: baseTarget.OS, Arch: "Pentium MMX"}), false)
})
})
d.Distribution = &files.OSDistribution{Name: "A", Version: "1"}
if platform.TargetSatisfiedForBuild(d, buildpack.TargetMetadata{OS: d.OS, Arch: d.Arch, Distributions: []buildpack.OSDistribution{{Name: "g", Version: "2"}, {Name: "B", Version: "2"}}}) {
t.Fatal("unsatisfactory distribution lists were treated as satisfying")
}
if !platform.TargetSatisfiedForBuild(d, buildpack.TargetMetadata{OS: d.OS, Arch: d.Arch, Distributions: []buildpack.OSDistribution{}}) {
t.Fatal("blank distributions list was not treated as wildcard")
}
if !platform.TargetSatisfiedForBuild(d, buildpack.TargetMetadata{OS: d.OS, Arch: d.Arch, Distributions: []buildpack.OSDistribution{{Name: "B", Version: "2"}, {Name: "A", Version: "1"}}}) {
t.Fatal("distributions list including target's distribution not recognized as satisfying")
}
when("missing os and arch", func() {
it("matches", func() {
h.AssertEq(t, platform.TargetSatisfiedForBuild(baseTarget, buildpack.TargetMetadata{OS: "", Arch: ""}), true)
})
})
when("has extra information", func() {
it("matches", func() {
h.AssertEq(t, platform.TargetSatisfiedForBuild(baseTarget, buildpack.TargetMetadata{OS: baseTarget.OS, Arch: baseTarget.Arch, ArchVariant: "MMX"}), true)
h.AssertEq(t, platform.TargetSatisfiedForBuild(baseTarget, buildpack.TargetMetadata{
OS: baseTarget.OS,
Arch: baseTarget.Arch,
Distros: []buildpack.OSDistro{{Name: "a", Version: "2"}},
}), true)
})
})
})
when("has arch variant", func() {
baseTarget.ArchVariant = "some-arch-variant"
when("buildpack data", func() {
when("has arch variant", func() {
it("must match", func() {
h.AssertEq(t, platform.TargetSatisfiedForBuild(baseTarget, buildpack.TargetMetadata{OS: baseTarget.OS, Arch: baseTarget.Arch, ArchVariant: "some-arch-variant"}), true)
h.AssertEq(t, platform.TargetSatisfiedForBuild(baseTarget, buildpack.TargetMetadata{OS: baseTarget.OS, Arch: baseTarget.Arch, ArchVariant: "some-other-arch-variant"}), false)
})
})
when("missing arch variant", func() {
it("matches", func() {
h.AssertEq(t, platform.TargetSatisfiedForBuild(baseTarget, buildpack.TargetMetadata{OS: baseTarget.OS, Arch: baseTarget.Arch}), true)
})
})
})
})
when("has distro information", func() {
baseTarget.Distro = &files.OSDistro{Name: "A", Version: "1"}
when("buildpack data", func() {
when("has distro information", func() {
it("must match", func() {
h.AssertEq(t, platform.TargetSatisfiedForBuild(baseTarget, buildpack.TargetMetadata{OS: baseTarget.OS, Arch: baseTarget.Arch, Distros: []buildpack.OSDistro{{Name: "B", Version: "2"}, {Name: "A", Version: "1"}}}), true)
h.AssertEq(t, platform.TargetSatisfiedForBuild(baseTarget, buildpack.TargetMetadata{OS: baseTarget.OS, Arch: baseTarget.Arch, Distros: []buildpack.OSDistro{{Name: "g", Version: "2"}, {Name: "B", Version: "2"}}}), false)
})
})
when("missing distro information", func() {
it("matches", func() {
h.AssertEq(t, platform.TargetSatisfiedForBuild(baseTarget, buildpack.TargetMetadata{OS: baseTarget.OS, Arch: baseTarget.Arch}), true)
})
})
})
})
})
})
})
it("is cool with starry arches", func() {
d := files.TargetMetadata{OS: "windows", Arch: "amd64"}
if !platform.TargetSatisfiedForBuild(d, buildpack.TargetMetadata{OS: d.OS, Arch: "*"}) {
t.Fatal("Arch wildcard should have been satisfied with whatever we gave it")
}
})
when(".TargetSatisfiedForRebase", func() {
var baseTarget files.TargetMetadata
when("orig image data", func() {
when("has os and arch", func() {
baseTarget = files.TargetMetadata{OS: "Win95", Arch: "Pentium"}
it("is down with OS stars", func() {
d := files.TargetMetadata{OS: "plan 9", Arch: "amd64"}
if !platform.TargetSatisfiedForBuild(d, buildpack.TargetMetadata{OS: "*", Arch: d.Arch}) {
t.Fatal("OS wildcard should have been satisfied by plan 9")
}
when("new image data", func() {
it("must match", func() {
h.AssertEq(t, platform.TargetSatisfiedForRebase(baseTarget, files.TargetMetadata{OS: baseTarget.OS, Arch: baseTarget.Arch}), true)
h.AssertEq(t, platform.TargetSatisfiedForRebase(baseTarget, files.TargetMetadata{OS: "Win98", Arch: baseTarget.Arch}), false)
h.AssertEq(t, platform.TargetSatisfiedForRebase(baseTarget, files.TargetMetadata{OS: baseTarget.OS, Arch: "Pentium MMX"}), false)
})
when("has extra information", func() {
it("matches", func() {
h.AssertEq(t, platform.TargetSatisfiedForRebase(baseTarget, files.TargetMetadata{OS: baseTarget.OS, Arch: baseTarget.Arch, ArchVariant: "MMX"}), true)
h.AssertEq(t, platform.TargetSatisfiedForRebase(baseTarget, files.TargetMetadata{
OS: baseTarget.OS,
Arch: baseTarget.Arch,
Distro: &files.OSDistro{Name: "a", Version: "2"},
}), true)
})
})
})
when("has arch variant", func() {
baseTarget.ArchVariant = "some-arch-variant"
when("new image data", func() {
when("has arch variant", func() {
it("must match", func() {
h.AssertEq(t, platform.TargetSatisfiedForRebase(baseTarget, files.TargetMetadata{OS: baseTarget.OS, Arch: baseTarget.Arch, ArchVariant: "some-arch-variant"}), true)
h.AssertEq(t, platform.TargetSatisfiedForRebase(baseTarget, files.TargetMetadata{OS: baseTarget.OS, Arch: baseTarget.Arch, ArchVariant: "some-other-arch-variant"}), false)
})
})
when("missing arch variant", func() {
it("matches", func() {
h.AssertEq(t, platform.TargetSatisfiedForRebase(baseTarget, files.TargetMetadata{OS: baseTarget.OS, Arch: baseTarget.Arch}), true)
})
})
})
})
when("has distro information", func() {
baseTarget.Distro = &files.OSDistro{Name: "A", Version: "1"}
when("new image data", func() {
when("has distro information", func() {
it("must match", func() {
h.AssertEq(t, platform.TargetSatisfiedForRebase(baseTarget, files.TargetMetadata{
OS: baseTarget.OS,
Arch: baseTarget.Arch,
Distro: &files.OSDistro{Name: "A", Version: "1"},
}), true)
h.AssertEq(t, platform.TargetSatisfiedForRebase(baseTarget, files.TargetMetadata{
OS: baseTarget.OS,
Arch: baseTarget.Arch,
Distro: &files.OSDistro{Name: "B", Version: "2"},
}), false)
})
})
when("missing distro information", func() {
it("errors", func() {
h.AssertEq(t, platform.TargetSatisfiedForRebase(baseTarget, files.TargetMetadata{OS: baseTarget.OS, Arch: baseTarget.Arch}), false)
})
})
})
})
})
})
})
@ -76,8 +176,8 @@ func testTargetData(t *testing.T, when spec.G, it spec.S) {
t: t,
HasFile: true}
platform.GetTargetOSFromFileSystem(&d, &tm, logr)
h.AssertEq(t, "opensesame", tm.Distribution.Name)
h.AssertEq(t, "3.14", tm.Distribution.Version)
h.AssertEq(t, "opensesame", tm.Distro.Name)
h.AssertEq(t, "3.14", tm.Distro.Version)
})
it("doesn't populate if there's no file", func() {
@ -87,7 +187,7 @@ func testTargetData(t *testing.T, when spec.G, it spec.S) {
t: t,
HasFile: false}
platform.GetTargetOSFromFileSystem(&d, &tm, logr)
h.AssertNil(t, tm.Distribution)
h.AssertNil(t, tm.Distro)
})
it("doesn't populate if there's an error reading the file", func() {
@ -99,7 +199,7 @@ func testTargetData(t *testing.T, when spec.G, it spec.S) {
ReadFileErr: fmt.Errorf("I'm sorry Dave, I don't even remember exactly what HAL says"),
}
platform.GetTargetOSFromFileSystem(&d, &tm, logr)
h.AssertNil(t, tm.Distribution)
h.AssertNil(t, tm.Distro)
})
})
}

View File

@ -36,6 +36,7 @@ type RebaseReport struct {
}
func (r *Rebaser) Rebase(workingImage imgutil.Image, newBaseImage imgutil.Image, outputImageRef string, additionalNames []string) (RebaseReport, error) {
defer log.NewMeasurement("Rebaser", r.Logger)()
appPlatformAPI, err := workingImage.Env(platform.EnvPlatformAPI)
if err != nil {
return RebaseReport{}, fmt.Errorf("failed to get app image platform API: %w", err)

View File

@ -793,20 +793,20 @@ func testRebaser(t *testing.T, when spec.G, it spec.S) {
h.AssertError(t, err, `unable to satisfy target os/arch constraints; new run image: {"os":"linux","arch":"amd64","arch-variant":"variant2"}, old run image: {"os":"linux","arch":"amd64","arch-variant":"variant1"}`)
})
it("errors and prevents the rebase from taking place when the io.buildpacks.distribution.name are different", func() {
h.AssertNil(t, fakeAppImage.SetLabel("io.buildpacks.distribution.name", "distro1"))
h.AssertNil(t, fakeNewBaseImage.SetLabel("io.buildpacks.distribution.name", "distro2"))
it("errors and prevents the rebase from taking place when the io.buildpacks.distro.name are different", func() {
h.AssertNil(t, fakeAppImage.SetLabel("io.buildpacks.distro.name", "distro1"))
h.AssertNil(t, fakeNewBaseImage.SetLabel("io.buildpacks.distro.name", "distro2"))
_, err := rebaser.Rebase(fakeAppImage, fakeNewBaseImage, fakeAppImage.Name(), additionalNames)
h.AssertError(t, err, `unable to satisfy target os/arch constraints; new run image: {"os":"linux","arch":"amd64","distribution":{"name":"distro2","version":""}}, old run image: {"os":"linux","arch":"amd64","distribution":{"name":"distro1","version":""}}`)
h.AssertError(t, err, `unable to satisfy target os/arch constraints; new run image: {"os":"linux","arch":"amd64","distro":{"name":"distro2","version":""}}, old run image: {"os":"linux","arch":"amd64","distro":{"name":"distro1","version":""}}`)
})
it("errors and prevents the rebase from taking place when the io.buildpacks.distribution.version are different", func() {
h.AssertNil(t, fakeAppImage.SetLabel("io.buildpacks.distribution.version", "version1"))
h.AssertNil(t, fakeNewBaseImage.SetLabel("io.buildpacks.distribution.version", "version2"))
it("errors and prevents the rebase from taking place when the io.buildpacks.distro.version are different", func() {
h.AssertNil(t, fakeAppImage.SetLabel("io.buildpacks.distro.version", "version1"))
h.AssertNil(t, fakeNewBaseImage.SetLabel("io.buildpacks.distro.version", "version2"))
_, err := rebaser.Rebase(fakeAppImage, fakeNewBaseImage, fakeAppImage.Name(), additionalNames)
h.AssertError(t, err, `unable to satisfy target os/arch constraints; new run image: {"os":"linux","arch":"amd64","distribution":{"name":"","version":"version2"}}, old run image: {"os":"linux","arch":"amd64","distribution":{"name":"","version":"version1"}}`)
h.AssertError(t, err, `unable to satisfy target os/arch constraints; new run image: {"os":"linux","arch":"amd64","distro":{"name":"","version":"version2"}}, old run image: {"os":"linux","arch":"amd64","distro":{"name":"","version":"version1"}}`)
})
})
})

View File

@ -28,6 +28,7 @@ type Restorer struct {
// Restore restores metadata for launch and cache layers into the layers directory and attempts to restore layer data for cache=true layers, removing the layer when unsuccessful.
// If a usable cache is not provided, Restore will not restore any cache=true layer metadata.
func (r *Restorer) Restore(cache Cache) error {
defer log.NewMeasurement("Restorer", r.Logger)()
cacheMeta, err := retrieveCacheMetadata(cache, r.Logger)
if err != nil {
return err
@ -36,6 +37,7 @@ func (r *Restorer) Restore(cache Cache) error {
useShaFiles := !r.restoresLayerMetadata()
layerSHAStore := layer.NewSHAStore(useShaFiles)
if r.restoresLayerMetadata() {
r.Logger.Debug("Restoring Layer Metadata")
if err := r.LayerMetadataRestorer.Restore(r.Buildpacks, r.LayersMetadata, cacheMeta, layerSHAStore); err != nil {
return err
}
@ -61,6 +63,7 @@ func (r *Restorer) Restore(cache Cache) error {
cachedFn = buildpack.MadeCached
}
r.Logger.Debugf("Reading Buildpack Layers directory %s", r.LayersDir)
buildpackDir, err := buildpack.ReadLayersDir(r.LayersDir, bp, r.Logger)
if err != nil {
return errors.Wrapf(err, "reading buildpack layer directory")

View File

@ -17,9 +17,9 @@ func saveImage(image imgutil.Image, additionalNames []string, logger log.Logger)
}
func saveImageAs(image imgutil.Image, name string, additionalNames []string, logger log.Logger) (files.ImageReport, error) {
defer log.NewMeasurement("Saving "+name+"...", logger)()
var saveErr error
imageReport := files.ImageReport{}
logger.Infof("Saving %s...\n", name)
if err := image.SaveAs(name, additionalNames...); err != nil {
var ok bool
if saveErr, ok = err.(imgutil.SaveError); !ok {