Skip to content

Commit 73f6927

Browse files
More fixes for target compat checking during detect (#1354)
* More fixes for target compat checking during detect - If a buildpack fails to specify os/arch (but specifies distro) still check targets - If the run image fails to specify os/arch (this should not happen actually as we will fail during analyze) still check targets - Fix typo in buildpack descriptor struct so that we actually get stack information - If we get distro information from /etc/os-release, persist this information to later invocations to that the log message printed when errors are encountered will be accurate - Don't override inner `i` in loop (this should not actually affect the outer loop but is confusing) Signed-off-by: Natalie Arellano <[email protected]> * When stack is "any", don't infer empty target as it is not needed Missing targets is sufficient for wildcard match Signed-off-by: Natalie Arellano <[email protected]> * Remove backwards compatible glue that actually causes fewer builds to succeed Fixes #1355 Signed-off-by: Natalie Arellano <[email protected]> * Remove exit (this was added for debugging purposes) Signed-off-by: Natalie Arellano <[email protected]> --------- Signed-off-by: Natalie Arellano <[email protected]>
1 parent 1a1de08 commit 73f6927

File tree

12 files changed

+226
-72
lines changed

12 files changed

+226
-72
lines changed

acceptance/testdata/detector/Dockerfile

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM ubuntu:bionic
1+
FROM ubuntu:jammy
22

33
ARG cnb_uid=1234
44
ARG cnb_gid=1000
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
1-
api = "0.10"
1+
api = "0.9"
22

33
[buildpack]
4-
id = "simple_buildpack"
5-
version = "simple_buildpack_version"
6-
name = "Simple Buildpack"
4+
id = "simple_buildpack"
5+
version = "simple_buildpack_version"
6+
name = "Simple Buildpack"
7+
8+
[[stacks]]
9+
id = "io.buildpacks.stacks.bionic"
10+
11+
[[stacks]]
12+
id = "io.buildpacks.stacks.jammy"

buildpack/bp_descriptor.go

+5-2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88

99
"github.com/BurntSushi/toml"
1010

11+
"github.com/buildpacks/lifecycle/api"
1112
"github.com/buildpacks/lifecycle/internal/encoding"
1213
)
1314

@@ -17,7 +18,7 @@ type BpDescriptor struct {
1718
Order Order `toml:"order"`
1819
WithRootDir string `toml:"-"`
1920
Targets []TargetMetadata `toml:"targets"`
20-
Stacks []StackMetadata `tome:"stacks"` // just for backwards compat so we can check if it's the bionic stack, which we translate to a target
21+
Stacks []StackMetadata `toml:"stacks"` // just for backwards compat so we can check if it's the bionic stack, which we translate to a target
2122

2223
}
2324

@@ -69,7 +70,9 @@ func ReadBpDescriptor(path string) (*BpDescriptor, error) {
6970
if len(descriptor.Targets) == 0 {
7071
for _, stack := range descriptor.Stacks {
7172
if stack.ID == "io.buildpacks.stacks.bionic" {
72-
descriptor.Targets = append(descriptor.Targets, TargetMetadata{OS: "linux", Arch: "amd64", Distros: []OSDistro{{Name: "ubuntu", Version: "18.04"}}})
73+
if api.MustParse(descriptor.API()).AtLeast("0.10") || len(descriptor.Stacks) == 1 {
74+
descriptor.Targets = append(descriptor.Targets, TargetMetadata{OS: "linux", Arch: "amd64", Distros: []OSDistro{{Name: "ubuntu", Version: "18.04"}}})
75+
}
7376
} else if stack.ID == "*" {
7477
descriptor.Targets = append(descriptor.Targets, TargetMetadata{}) // matches any
7578
}

buildpack/bp_descriptor_test.go

+133-37
Original file line numberDiff line numberDiff line change
@@ -81,44 +81,140 @@ func testBpDescriptor(t *testing.T, when spec.G, it spec.S) {
8181
h.AssertEq(t, descriptor.Targets[0].Distros[0].Version, "V8.4-2L3")
8282
})
8383

84-
it("does translate one special stack value into target values for older apis", func() {
85-
path := filepath.Join("testdata", "buildpack", "by-id", "B", "v1", "buildpack.toml")
86-
descriptor, err := buildpack.ReadBpDescriptor(path)
87-
h.AssertNil(t, err)
88-
// common sanity checks
89-
h.AssertEq(t, descriptor.WithAPI, "0.7")
90-
h.AssertEq(t, descriptor.Buildpack.ID, "B")
91-
h.AssertEq(t, descriptor.Buildpack.Name, "Buildpack B")
92-
h.AssertEq(t, descriptor.Buildpack.Version, "v1")
93-
h.AssertEq(t, descriptor.Buildpack.Homepage, "Buildpack B Homepage")
94-
h.AssertEq(t, descriptor.Buildpack.SBOM, []string{"application/vnd.cyclonedx+json"})
95-
// specific behaviors for this test
96-
h.AssertEq(t, descriptor.Stacks[0].ID, "io.buildpacks.stacks.bionic")
97-
h.AssertEq(t, len(descriptor.Targets), 1)
98-
h.AssertEq(t, descriptor.Targets[0].Arch, "amd64")
99-
h.AssertEq(t, descriptor.Targets[0].OS, "linux")
100-
h.AssertEq(t, descriptor.Targets[0].Distros[0].Name, "ubuntu")
101-
h.AssertEq(t, descriptor.Targets[0].Distros[0].Version, "18.04")
102-
})
84+
when("translating stacks to targets", func() {
85+
when("older buildpacks", func() {
86+
when("there is only bionic", func() {
87+
it("creates a target", func() {
88+
path := filepath.Join("testdata", "buildpack", "by-id", "B", "v1", "buildpack.toml")
89+
descriptor, err := buildpack.ReadBpDescriptor(path)
90+
h.AssertNil(t, err)
91+
// common sanity checks
92+
h.AssertEq(t, descriptor.WithAPI, "0.7")
93+
h.AssertEq(t, descriptor.Buildpack.ID, "B")
94+
h.AssertEq(t, descriptor.Buildpack.Name, "Buildpack B")
95+
h.AssertEq(t, descriptor.Buildpack.Version, "v1")
96+
h.AssertEq(t, descriptor.Buildpack.Homepage, "Buildpack B Homepage")
97+
h.AssertEq(t, descriptor.Buildpack.SBOM, []string{"application/vnd.cyclonedx+json"})
98+
// specific behaviors for this test
99+
h.AssertEq(t, descriptor.Stacks[0].ID, "io.buildpacks.stacks.bionic")
100+
h.AssertEq(t, len(descriptor.Targets), 1)
101+
h.AssertEq(t, descriptor.Targets[0].Arch, "amd64")
102+
h.AssertEq(t, descriptor.Targets[0].OS, "linux")
103+
h.AssertEq(t, descriptor.Targets[0].Distros[0].Name, "ubuntu")
104+
h.AssertEq(t, descriptor.Targets[0].Distros[0].Version, "18.04")
105+
})
106+
})
103107

104-
it("translates one special stack value into target values", func() {
105-
path := filepath.Join("testdata", "buildpack", "by-id", "B", "v2", "buildpack.toml")
106-
descriptor, err := buildpack.ReadBpDescriptor(path)
107-
h.AssertNil(t, err)
108-
// common sanity checks
109-
h.AssertEq(t, descriptor.WithAPI, "0.12")
110-
h.AssertEq(t, descriptor.Buildpack.ID, "B")
111-
h.AssertEq(t, descriptor.Buildpack.Name, "Buildpack B")
112-
h.AssertEq(t, descriptor.Buildpack.Version, "v1")
113-
h.AssertEq(t, descriptor.Buildpack.Homepage, "Buildpack B Homepage")
114-
h.AssertEq(t, descriptor.Buildpack.SBOM, []string{"application/vnd.cyclonedx+json"})
115-
// specific behaviors for this test
116-
h.AssertEq(t, descriptor.Stacks[0].ID, "io.buildpacks.stacks.bionic")
117-
h.AssertEq(t, len(descriptor.Targets), 1)
118-
h.AssertEq(t, descriptor.Targets[0].Arch, "amd64")
119-
h.AssertEq(t, descriptor.Targets[0].OS, "linux")
120-
h.AssertEq(t, descriptor.Targets[0].Distros[0].Name, "ubuntu")
121-
h.AssertEq(t, descriptor.Targets[0].Distros[0].Version, "18.04")
108+
when("there are multiple stacks", func() {
109+
it("does NOT create a target", func() {
110+
path := filepath.Join("testdata", "buildpack", "by-id", "B", "v1.2", "buildpack.toml")
111+
descriptor, err := buildpack.ReadBpDescriptor(path)
112+
h.AssertNil(t, err)
113+
// common sanity checks
114+
h.AssertEq(t, descriptor.WithAPI, "0.7")
115+
h.AssertEq(t, descriptor.Buildpack.ID, "B")
116+
h.AssertEq(t, descriptor.Buildpack.Name, "Buildpack B")
117+
h.AssertEq(t, descriptor.Buildpack.Version, "v1.2")
118+
h.AssertEq(t, descriptor.Buildpack.Homepage, "Buildpack B Homepage")
119+
h.AssertEq(t, descriptor.Buildpack.SBOM, []string{"application/vnd.cyclonedx+json"})
120+
// specific behaviors for this test
121+
h.AssertEq(t, descriptor.Stacks[0].ID, "io.buildpacks.stacks.bionic")
122+
h.AssertEq(t, len(descriptor.Targets), 0)
123+
})
124+
})
125+
126+
when("there is a wildcard stack", func() {
127+
it("creates a wildcard target", func() {
128+
path := filepath.Join("testdata", "buildpack", "by-id", "B", "v1.star", "buildpack.toml")
129+
descriptor, err := buildpack.ReadBpDescriptor(path)
130+
h.AssertNil(t, err)
131+
// common sanity checks
132+
h.AssertEq(t, descriptor.WithAPI, "0.7")
133+
h.AssertEq(t, descriptor.Buildpack.ID, "B")
134+
h.AssertEq(t, descriptor.Buildpack.Name, "Buildpack B")
135+
h.AssertEq(t, descriptor.Buildpack.Version, "v1.star")
136+
h.AssertEq(t, descriptor.Buildpack.Homepage, "Buildpack B Homepage")
137+
h.AssertEq(t, descriptor.Buildpack.SBOM, []string{"application/vnd.cyclonedx+json"})
138+
// specific behaviors for this test
139+
h.AssertEq(t, descriptor.Stacks[0].ID, "*")
140+
h.AssertEq(t, len(descriptor.Targets), 1)
141+
// a target that is completely empty will always match whatever is the base image target
142+
h.AssertEq(t, descriptor.Targets[0].Arch, "")
143+
h.AssertEq(t, descriptor.Targets[0].OS, "")
144+
h.AssertEq(t, descriptor.Targets[0].ArchVariant, "")
145+
h.AssertEq(t, len(descriptor.Targets[0].Distros), 0)
146+
})
147+
})
148+
})
149+
150+
when("newer buildpacks", func() {
151+
when("there is only bionic", func() {
152+
it("creates a target", func() {
153+
path := filepath.Join("testdata", "buildpack", "by-id", "B", "v2", "buildpack.toml")
154+
descriptor, err := buildpack.ReadBpDescriptor(path)
155+
h.AssertNil(t, err)
156+
// common sanity checks
157+
h.AssertEq(t, descriptor.WithAPI, "0.12")
158+
h.AssertEq(t, descriptor.Buildpack.ID, "B")
159+
h.AssertEq(t, descriptor.Buildpack.Name, "Buildpack B")
160+
h.AssertEq(t, descriptor.Buildpack.Version, "v2")
161+
h.AssertEq(t, descriptor.Buildpack.Homepage, "Buildpack B Homepage")
162+
h.AssertEq(t, descriptor.Buildpack.SBOM, []string{"application/vnd.cyclonedx+json"})
163+
// specific behaviors for this test
164+
h.AssertEq(t, descriptor.Stacks[0].ID, "io.buildpacks.stacks.bionic")
165+
h.AssertEq(t, len(descriptor.Targets), 1)
166+
h.AssertEq(t, descriptor.Targets[0].Arch, "amd64")
167+
h.AssertEq(t, descriptor.Targets[0].OS, "linux")
168+
h.AssertEq(t, descriptor.Targets[0].Distros[0].Name, "ubuntu")
169+
h.AssertEq(t, descriptor.Targets[0].Distros[0].Version, "18.04")
170+
})
171+
})
172+
173+
when("there are multiple stacks", func() {
174+
it("creates a target", func() {
175+
path := filepath.Join("testdata", "buildpack", "by-id", "B", "v2.2", "buildpack.toml")
176+
descriptor, err := buildpack.ReadBpDescriptor(path)
177+
h.AssertNil(t, err)
178+
// common sanity checks
179+
h.AssertEq(t, descriptor.WithAPI, "0.12")
180+
h.AssertEq(t, descriptor.Buildpack.ID, "B")
181+
h.AssertEq(t, descriptor.Buildpack.Name, "Buildpack B")
182+
h.AssertEq(t, descriptor.Buildpack.Version, "v2.2")
183+
h.AssertEq(t, descriptor.Buildpack.Homepage, "Buildpack B Homepage")
184+
h.AssertEq(t, descriptor.Buildpack.SBOM, []string{"application/vnd.cyclonedx+json"})
185+
// specific behaviors for this test
186+
h.AssertEq(t, descriptor.Stacks[0].ID, "io.buildpacks.stacks.bionic")
187+
h.AssertEq(t, len(descriptor.Targets), 1)
188+
h.AssertEq(t, descriptor.Targets[0].Arch, "amd64")
189+
h.AssertEq(t, descriptor.Targets[0].OS, "linux")
190+
h.AssertEq(t, descriptor.Targets[0].Distros[0].Name, "ubuntu")
191+
h.AssertEq(t, descriptor.Targets[0].Distros[0].Version, "18.04")
192+
})
193+
})
194+
195+
when("there is a wildcard stack", func() {
196+
it("creates a wildcard target", func() {
197+
path := filepath.Join("testdata", "buildpack", "by-id", "B", "v2.star", "buildpack.toml")
198+
descriptor, err := buildpack.ReadBpDescriptor(path)
199+
h.AssertNil(t, err)
200+
// common sanity checks
201+
h.AssertEq(t, descriptor.WithAPI, "0.12")
202+
h.AssertEq(t, descriptor.Buildpack.ID, "B")
203+
h.AssertEq(t, descriptor.Buildpack.Name, "Buildpack B")
204+
h.AssertEq(t, descriptor.Buildpack.Version, "v2.star")
205+
h.AssertEq(t, descriptor.Buildpack.Homepage, "Buildpack B Homepage")
206+
h.AssertEq(t, descriptor.Buildpack.SBOM, []string{"application/vnd.cyclonedx+json"})
207+
// specific behaviors for this test
208+
h.AssertEq(t, descriptor.Stacks[0].ID, "*")
209+
h.AssertEq(t, len(descriptor.Targets), 1)
210+
// a target that is completely empty will always match whatever is the base image target
211+
h.AssertEq(t, descriptor.Targets[0].Arch, "")
212+
h.AssertEq(t, descriptor.Targets[0].OS, "")
213+
h.AssertEq(t, descriptor.Targets[0].ArchVariant, "")
214+
h.AssertEq(t, len(descriptor.Targets[0].Distros), 0)
215+
})
216+
})
217+
})
122218
})
123219

124220
it("does not translate non-special stack values", func() {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
api = "0.7"
2+
3+
[buildpack]
4+
id = "B"
5+
name = "Buildpack B"
6+
version = "v1.2"
7+
homepage = "Buildpack B Homepage"
8+
sbom-formats = ["application/vnd.cyclonedx+json"]
9+
10+
[[stacks]]
11+
id = "io.buildpacks.stacks.bionic"
12+
13+
[[stacks]]
14+
id = "io.buildpacks.stacks.jammy"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
api = "0.7"
2+
3+
[buildpack]
4+
id = "B"
5+
name = "Buildpack B"
6+
version = "v1.star"
7+
homepage = "Buildpack B Homepage"
8+
sbom-formats = ["application/vnd.cyclonedx+json"]
9+
10+
[[stacks]]
11+
id = "*"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
api = "0.12"
2+
3+
[buildpack]
4+
id = "B"
5+
name = "Buildpack B"
6+
version = "v2.2"
7+
homepage = "Buildpack B Homepage"
8+
sbom-formats = ["application/vnd.cyclonedx+json"]
9+
10+
[[stacks]]
11+
id = "io.buildpacks.stacks.bionic"
12+
13+
[[stacks]]
14+
id = "io.buildpacks.stacks.jammy"
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
api = "0.12"
2+
3+
[buildpack]
4+
id = "B"
5+
name = "Buildpack B"
6+
version = "v2.star"
7+
homepage = "Buildpack B Homepage"
8+
sbom-formats = ["application/vnd.cyclonedx+json"]
9+
10+
[[stacks]]
11+
id = "*"

buildpack/testdata/buildpack/by-id/B/v2/buildpack.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ api = "0.12"
33
[buildpack]
44
id = "B"
55
name = "Buildpack B"
6-
version = "v1"
6+
version = "v2"
77
homepage = "Buildpack B Homepage"
88
sbom-formats = ["application/vnd.cyclonedx+json"]
99

phase/detector.go

+16-23
Original file line numberDiff line numberDiff line change
@@ -144,26 +144,14 @@ func (d *Detector) detectOrder(order buildpack.Order, done, next []buildpack.Gro
144144
return nil, nil, ErrFailedDetection
145145
}
146146

147-
// isWildcard returns true IFF the Arch and OS are unspecified, meaning that the target arch/os are "any"
148-
func isWildcard(t files.TargetMetadata) bool {
149-
return t.Arch == "" && t.OS == ""
150-
}
151-
152-
func hasWildcard(ts []buildpack.TargetMetadata) bool {
153-
for _, tm := range ts {
154-
if tm.OS == "" && tm.Arch == "" {
155-
return true
156-
}
157-
}
158-
return false
159-
}
160-
161147
func (d *Detector) detectGroup(group buildpack.Group, done []buildpack.GroupElement, wg *sync.WaitGroup) ([]buildpack.GroupElement, []files.BuildPlanEntry, error) {
162148
// used below to mark each item as "done" by appending it to the done list
163149
markDone := func(groupEl buildpack.GroupElement, descriptor buildpack.Descriptor) {
164150
done = append(done, groupEl.WithAPI(descriptor.API()).WithHomepage(descriptor.Homepage()))
165151
}
166152

153+
runImageTargetInfo := d.AnalyzeMD.RunImageTarget()
154+
167155
for i, groupEl := range group.Group {
168156
// Continue if element has already been processed.
169157
if hasIDForKind(done, groupEl.Kind(), groupEl.ID) {
@@ -175,7 +163,7 @@ func (d *Detector) detectGroup(group buildpack.Group, done []buildpack.GroupElem
175163
return d.detectOrder(groupEl.OrderExtensions, done, group.Group[i+1:], true, wg)
176164
}
177165

178-
// Lookup element in store. <-- "the store" is the directory where all the buildpacks are.
166+
// Lookup element in store (the "store" is the directory where all the buildpacks are).
179167
var (
180168
descriptor buildpack.Descriptor
181169
err error
@@ -192,26 +180,31 @@ func (d *Detector) detectGroup(group buildpack.Group, done []buildpack.GroupElem
192180
// FIXME: cyclical references lead to infinite recursion
193181
return d.detectOrder(order, done, group.Group[i+1:], groupEl.Optional, wg)
194182
}
195-
descriptor = bpDescriptor // standardize the type so below we don't have to care whether it was an extension
183+
descriptor = bpDescriptor // Standardize the type so below we don't have to care whether it is an extension.
196184
} else {
197185
descriptor, err = d.DirStore.LookupExt(groupEl.ID, groupEl.Version)
198186
if err != nil {
199187
return nil, nil, err
200188
}
201189
}
190+
191+
// Check target compatibility.
202192
if d.PlatformAPI.AtLeast("0.12") {
203-
targetMatch := false
204-
if isWildcard(d.AnalyzeMD.RunImageTarget()) || hasWildcard(descriptor.TargetsList()) {
193+
var targetMatch bool
194+
if len(descriptor.TargetsList()) == 0 {
195+
// This is actually just for tests. In practice, the lifecycle will infer a target with at least an OS
196+
// when targets are missing from buildpack.toml.
205197
targetMatch = true
206198
} else {
207-
for i := range descriptor.TargetsList() {
208-
d.Logger.Debugf("Checking for match against descriptor: %s", descriptor.TargetsList()[i])
209-
if platform.TargetSatisfiedForBuild(&fsutil.Detect{}, *d.AnalyzeMD.RunImage.TargetMetadata, descriptor.TargetsList()[i], d.Logger) {
199+
for _, target := range descriptor.TargetsList() {
200+
d.Logger.Debugf("Checking for match against descriptor: %s", target)
201+
if platform.TargetSatisfiedForBuild(&fsutil.Detect{}, &runImageTargetInfo, target, d.Logger) {
210202
targetMatch = true
211203
break
212204
}
213205
}
214206
}
207+
215208
if !targetMatch && !groupEl.Optional {
216209
markDone(groupEl, descriptor)
217210
d.Runs.Store(
@@ -220,7 +213,7 @@ func (d *Detector) detectGroup(group buildpack.Group, done []buildpack.GroupElem
220213
Code: -1,
221214
Err: fmt.Errorf(
222215
"unable to satisfy target os/arch constraints; run image: %s, buildpack: %s",
223-
encoding.ToJSONMaybe(d.AnalyzeMD.RunImage.TargetMetadata),
216+
encoding.ToJSONMaybe(runImageTargetInfo),
224217
encoding.ToJSONMaybe(descriptor.TargetsList()),
225218
),
226219
})
@@ -240,7 +233,7 @@ func (d *Detector) detectGroup(group buildpack.Group, done []buildpack.GroupElem
240233
BuildConfigDir: d.BuildConfigDir,
241234
PlatformDir: d.PlatformDir,
242235
Env: env.NewBuildEnv(os.Environ()),
243-
TargetEnv: platform.EnvVarsFor(&fsutil.Detect{}, d.AnalyzeMD.RunImageTarget(), d.Logger),
236+
TargetEnv: platform.EnvVarsFor(&fsutil.Detect{}, runImageTargetInfo, d.Logger),
244237
}
245238
d.Runs.Store(key, d.Executor.Detect(descriptor, inputs, d.Logger)) // this is where we finally invoke bin/detect
246239
}

0 commit comments

Comments
 (0)