Skip to content

Commit 8e6ba17

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

File tree

4 files changed

+74
-17
lines changed

4 files changed

+74
-17
lines changed

go.mod

+1-1
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

go.sum

+2-2
Original file line numberDiff line numberDiff line change
@@ -91,8 +91,8 @@ 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=
94+
github.com/buildpacks/imgutil v0.0.0-20250224200932-4dcbf829e753 h1:wfR3MA91PSFuKlaT07lx8BU8x918GSHmz6phaSbXJ6g=
95+
github.com/buildpacks/imgutil v0.0.0-20250224200932-4dcbf829e753/go.mod h1:n2R6VRuWsAX3cyHCp/u0Z4WJcixny0gYg075J39owrk=
9696
github.com/buildpacks/lifecycle v0.20.4 h1:VVVTrd9y1LHY3adchh6oktw0wKQuYsWLq3/g23TLaGQ=
9797
github.com/buildpacks/lifecycle v0.20.4/go.mod h1:ZsExeEhN+6Qws7iDHJl6PV6zsHycgK/RmDKnRgKQTH0=
9898
github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM=

pkg/client/create_builder.go

+30-14
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,28 @@ 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"
439-
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)
444+
func (c *Client) uriFromLifecycleVersion(version semver.Version, os string, architecture string) (string, error) {
445+
image, err := c.indexFactory.FetchIndex(config.DefaultLifecycleImageRepo, imgutil.FromBaseIndex(config.DefaultLifecycleImageRepo))
446+
if err != nil {
447+
return "", err
448+
}
449+
manifest, err := image.IndexManifest()
450+
if err != nil {
451+
return "", err
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))
454+
for _, m := range manifest.Manifests {
455+
if m.Platform.OS == os && m.Platform.Architecture == architecture {
456+
return lifecycleDownloadURL(version, os, architecture), nil
457+
}
449458
}
450459

451-
return fmt.Sprintf("https://github.com/buildpacks/lifecycle/releases/download/v%s/lifecycle-v%s+linux.%s.tgz", version.String(), version.String(), arch)
460+
return "", fmt.Errorf("could not determine lifecyle, unsupported os/arch: %s/%s", os, architecture)
461+
}
462+
463+
func lifecycleDownloadURL(version semver.Version, os, architecture string) string {
464+
if architecture == "amd64" {
465+
architecture = "x86-64"
466+
}
467+
return fmt.Sprintf("https://github.com/buildpacks/lifecycle/releases/download/v%s/lifecycle-v%s+%s.%s.tgz", version.String(), version.String(), os, architecture)
452468
}

pkg/client/create_builder_test.go

+41
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)