Skip to content

Commit a78c6c8

Browse files
committed
Dynamically determine valid lifecycle
Signed-off-by: Johannes Dillmann <[email protected]>
1 parent c13cab3 commit a78c6c8

File tree

4 files changed

+68
-17
lines changed

4 files changed

+68
-17
lines changed

go.mod

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ require (
66
github.com/Masterminds/semver v1.5.0
77
github.com/Microsoft/go-winio v0.6.2
88
github.com/apex/log v1.9.0
9-
github.com/buildpacks/imgutil v0.0.0-20240605145725-186f89b2d168
9+
github.com/buildpacks/imgutil v0.0.0-20250224200932-4dcbf829e753
1010
github.com/buildpacks/lifecycle v0.20.4
1111
github.com/docker/cli v27.5.1+incompatible
1212
github.com/docker/docker v27.5.1+incompatible
@@ -144,6 +144,8 @@ require (
144144
gopkg.in/yaml.v2 v2.4.0 // indirect
145145
)
146146

147+
replace github.com/buildpacks/imgutil => ../imgutil
148+
147149
replace github.com/BurntSushi/toml => github.com/BurntSushi/toml v1.3.2
148150

149151
go 1.23.0

go.sum

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,6 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
9191
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
9292
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
9393
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
94-
github.com/buildpacks/imgutil v0.0.0-20240605145725-186f89b2d168 h1:yVYVi1V7x1bXklOx9lpbTfteyzQKGZC/wkl+IlaVRlU=
95-
github.com/buildpacks/imgutil v0.0.0-20240605145725-186f89b2d168/go.mod h1:n2R6VRuWsAX3cyHCp/u0Z4WJcixny0gYg075J39owrk=
9694
github.com/buildpacks/lifecycle v0.20.4 h1:VVVTrd9y1LHY3adchh6oktw0wKQuYsWLq3/g23TLaGQ=
9795
github.com/buildpacks/lifecycle v0.20.4/go.mod h1:ZsExeEhN+6Qws7iDHJl6PV6zsHycgK/RmDKnRgKQTH0=
9896
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=

pkg/client/create_builder.go

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import (
1616

1717
pubbldr "github.com/buildpacks/pack/builder"
1818
"github.com/buildpacks/pack/internal/builder"
19+
"github.com/buildpacks/pack/internal/config"
1920
"github.com/buildpacks/pack/internal/paths"
2021
"github.com/buildpacks/pack/internal/style"
2122
"github.com/buildpacks/pack/pkg/buildpack"
@@ -284,14 +285,20 @@ func (c *Client) fetchLifecycle(ctx context.Context, config pubbldr.LifecycleCon
284285
return nil, errors.Wrapf(err, "%s must be a valid semver", style.Symbol("lifecycle.version"))
285286
}
286287

287-
uri = c.uriFromLifecycleVersion(*v, os, architecture)
288+
uri, err = c.uriFromLifecycleVersion(*v, os, architecture)
289+
if err != nil {
290+
return nil, errors.Wrap(err, "determine lifecycle")
291+
}
288292
case config.URI != "":
289293
uri, err = paths.FilePathToURI(config.URI, relativeBaseDir)
290294
if err != nil {
291-
return nil, err
295+
return nil, errors.Wrap(err, "determine lifecycle")
292296
}
293297
default:
294-
uri = c.uriFromLifecycleVersion(*semver.MustParse(builder.DefaultLifecycleVersion), os, architecture)
298+
uri, err = c.uriFromLifecycleVersion(*semver.MustParse(builder.DefaultLifecycleVersion), os, architecture)
299+
if err != nil {
300+
return nil, errors.Wrap(err, "determine lifecycle")
301+
}
295302
}
296303

297304
blob, err := c.downloader.Download(ctx, uri)
@@ -434,19 +441,22 @@ func validateModule(kind string, module buildpack.BuildModule, source, expectedI
434441
return nil
435442
}
436443

437-
func (c *Client) uriFromLifecycleVersion(version semver.Version, os string, architecture string) string {
438-
arch := "x86-64"
444+
func (c *Client) uriFromLifecycleVersion(version semver.Version, os string, architecture string) (string, error) {
445+
image, _ := c.indexFactory.FetchIndex(config.DefaultLifecycleImageRepo, imgutil.FromBaseIndex(config.DefaultLifecycleImageRepo))
446+
manifest, _ := image.IndexManifest()
439447

440-
if os == "windows" {
441-
return fmt.Sprintf("https://github.com/buildpacks/lifecycle/releases/download/v%s/lifecycle-v%s+windows.%s.tgz", version.String(), version.String(), arch)
448+
for _, m := range manifest.Manifests {
449+
if m.Platform.OS == os && m.Platform.Architecture == architecture {
450+
return lifecycleDownloadURL(version, os, architecture), nil
451+
}
442452
}
443453

444-
if builder.SupportedLinuxArchitecture(architecture) {
445-
arch = architecture
446-
} else {
447-
// FIXME: this should probably be an error case in the future, see https://github.com/buildpacks/pack/issues/2163
448-
c.logger.Warnf("failed to find a lifecycle binary for requested architecture %s, defaulting to %s", style.Symbol(architecture), style.Symbol(arch))
449-
}
454+
return "", fmt.Errorf("could not determine lifecyle, unsupported os/arch: %s/%s", os, architecture)
455+
}
450456

451-
return fmt.Sprintf("https://github.com/buildpacks/lifecycle/releases/download/v%s/lifecycle-v%s+linux.%s.tgz", version.String(), version.String(), arch)
457+
func lifecycleDownloadURL(version semver.Version, os, architecture string) string {
458+
if architecture == "amd64" {
459+
architecture = "x86-64"
460+
}
461+
return fmt.Sprintf("https://github.com/buildpacks/lifecycle/releases/download/v%s/lifecycle-v%s+%s.%s.tgz", version.String(), version.String(), os, architecture)
452462
}

pkg/client/create_builder_test.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"github.com/buildpacks/lifecycle/api"
1414
"github.com/docker/docker/api/types/system"
1515
"github.com/golang/mock/gomock"
16+
v1 "github.com/google/go-containerregistry/pkg/v1"
1617
"github.com/heroku/color"
1718
"github.com/pkg/errors"
1819
"github.com/sclevine/spec"
@@ -49,10 +50,12 @@ func testCreateBuilder(t *testing.T, when spec.G, it spec.S) {
4950
mockBuildpackDownloader *testmocks.MockBuildpackDownloader
5051
mockImageFactory *testmocks.MockImageFactory
5152
mockImageFetcher *testmocks.MockImageFetcher
53+
mockIndexFactory *testmocks.MockIndexFactory
5254
mockDockerClient *testmocks.MockCommonAPIClient
5355
fakeBuildImage *fakes.Image
5456
fakeRunImage *fakes.Image
5557
fakeRunImageMirror *fakes.Image
58+
fakeLifecycleImage *fakes.ImageIndex
5659
opts client.CreateBuilderOptions
5760
subject *client.Client
5861
logger logging.Logger
@@ -68,6 +71,10 @@ func testCreateBuilder(t *testing.T, when spec.G, it spec.S) {
6871
mockImageFetcher.EXPECT().Fetch(gomock.Any(), "some/build-image", gomock.Any()).Return(fakeBuildImage, nil)
6972
}
7073

74+
var prepareIndexFetcherWithLifecycleImage = func() {
75+
mockIndexFactory.EXPECT().FetchIndex(gomock.Any(), gomock.Any()).Return(fakeLifecycleImage, nil)
76+
}
77+
7178
var createBuildpack = func(descriptor dist.BuildpackDescriptor) buildpack.BuildModule {
7279
buildpack, err := ifakes.NewFakeBuildpack(descriptor, 0644)
7380
h.AssertNil(t, err)
@@ -89,6 +96,7 @@ func testCreateBuilder(t *testing.T, when spec.G, it spec.S) {
8996
mockDownloader = testmocks.NewMockBlobDownloader(mockController)
9097
mockImageFetcher = testmocks.NewMockImageFetcher(mockController)
9198
mockImageFactory = testmocks.NewMockImageFactory(mockController)
99+
mockIndexFactory = testmocks.NewMockIndexFactory(mockController)
92100
mockDockerClient = testmocks.NewMockCommonAPIClient(mockController)
93101
mockBuildpackDownloader = testmocks.NewMockBuildpackDownloader(mockController)
94102

@@ -101,6 +109,29 @@ func testCreateBuilder(t *testing.T, when spec.G, it spec.S) {
101109
fakeRunImage = fakes.NewImage("some/run-image", "", nil)
102110
h.AssertNil(t, fakeRunImage.SetLabel("io.buildpacks.stack.id", "some.stack.id"))
103111

112+
fakeLifecycleImage = &fakes.ImageIndex{
113+
Manifests: []v1.Descriptor{
114+
{
115+
Platform: &v1.Platform{
116+
OS: "linux",
117+
Architecture: "amd64",
118+
},
119+
},
120+
{
121+
Platform: &v1.Platform{
122+
OS: "linux",
123+
Architecture: "arm64",
124+
},
125+
},
126+
{
127+
Platform: &v1.Platform{
128+
OS: "windows",
129+
Architecture: "amd64",
130+
},
131+
},
132+
},
133+
}
134+
104135
fakeRunImageMirror = fakes.NewImage("localhost:5000/some/run-image", "", nil)
105136
h.AssertNil(t, fakeRunImageMirror.SetLabel("io.buildpacks.stack.id", "some.stack.id"))
106137

@@ -124,6 +155,7 @@ func testCreateBuilder(t *testing.T, when spec.G, it spec.S) {
124155
client.WithDownloader(mockDownloader),
125156
client.WithImageFactory(mockImageFactory),
126157
client.WithFetcher(mockImageFetcher),
158+
client.WithIndexFactory(mockIndexFactory),
127159
client.WithDockerClient(mockDockerClient),
128160
client.WithBuildpackDownloader(mockBuildpackDownloader),
129161
)
@@ -452,6 +484,7 @@ func testCreateBuilder(t *testing.T, when spec.G, it spec.S) {
452484
client.WithDownloader(mockDownloader),
453485
client.WithImageFactory(mockImageFactory),
454486
client.WithFetcher(mockImageFetcher),
487+
client.WithIndexFactory(mockIndexFactory),
455488
client.WithExperimental(true),
456489
)
457490
h.AssertNil(t, err)
@@ -516,6 +549,7 @@ func testCreateBuilder(t *testing.T, when spec.G, it spec.S) {
516549
it("should download from predetermined uri", func() {
517550
prepareFetcherWithBuildImage()
518551
prepareFetcherWithRunImages()
552+
prepareIndexFetcherWithLifecycleImage()
519553
opts.Config.Lifecycle.URI = ""
520554
opts.Config.Lifecycle.Version = "3.4.5"
521555

@@ -533,6 +567,7 @@ func testCreateBuilder(t *testing.T, when spec.G, it spec.S) {
533567
it("should download from predetermined uri for arm64", func() {
534568
prepareFetcherWithBuildImage()
535569
prepareFetcherWithRunImages()
570+
prepareIndexFetcherWithLifecycleImage()
536571
opts.Config.Lifecycle.URI = ""
537572
opts.Config.Lifecycle.Version = "3.4.5"
538573
h.AssertNil(t, fakeBuildImage.SetArchitecture("arm64"))
@@ -557,12 +592,14 @@ func testCreateBuilder(t *testing.T, when spec.G, it spec.S) {
557592
client.WithDownloader(mockDownloader),
558593
client.WithImageFactory(mockImageFactory),
559594
client.WithFetcher(mockImageFetcher),
595+
client.WithIndexFactory(mockIndexFactory),
560596
client.WithExperimental(true),
561597
)
562598
h.AssertNil(t, err)
563599

564600
prepareFetcherWithBuildImage()
565601
prepareFetcherWithRunImages()
602+
prepareIndexFetcherWithLifecycleImage()
566603
opts.Config.Lifecycle.URI = ""
567604
opts.Config.Lifecycle.Version = "3.4.5"
568605
h.AssertNil(t, fakeBuildImage.SetOS("windows"))
@@ -584,6 +621,7 @@ func testCreateBuilder(t *testing.T, when spec.G, it spec.S) {
584621
it("should download default lifecycle", func() {
585622
prepareFetcherWithBuildImage()
586623
prepareFetcherWithRunImages()
624+
prepareIndexFetcherWithLifecycleImage()
587625
opts.Config.Lifecycle.URI = ""
588626
opts.Config.Lifecycle.Version = ""
589627

@@ -605,6 +643,7 @@ func testCreateBuilder(t *testing.T, when spec.G, it spec.S) {
605643
it("should download default lifecycle on arm64", func() {
606644
prepareFetcherWithBuildImage()
607645
prepareFetcherWithRunImages()
646+
prepareIndexFetcherWithLifecycleImage()
608647
opts.Config.Lifecycle.URI = ""
609648
opts.Config.Lifecycle.Version = ""
610649
h.AssertNil(t, fakeBuildImage.SetArchitecture("arm64"))
@@ -633,12 +672,14 @@ func testCreateBuilder(t *testing.T, when spec.G, it spec.S) {
633672
client.WithDownloader(mockDownloader),
634673
client.WithImageFactory(mockImageFactory),
635674
client.WithFetcher(mockImageFetcher),
675+
client.WithIndexFactory(mockIndexFactory),
636676
client.WithExperimental(true),
637677
)
638678
h.AssertNil(t, err)
639679

640680
prepareFetcherWithBuildImage()
641681
prepareFetcherWithRunImages()
682+
prepareIndexFetcherWithLifecycleImage()
642683
opts.Config.Lifecycle.URI = ""
643684
opts.Config.Lifecycle.Version = ""
644685
h.AssertNil(t, fakeBuildImage.SetOS("windows"))

0 commit comments

Comments
 (0)