Skip to content
Draft
82 changes: 37 additions & 45 deletions internal/builder/trusted_builder.go
Original file line number Diff line number Diff line change
@@ -1,125 +1,116 @@
package builder

import (
"slices"

"github.com/google/go-containerregistry/pkg/name"

"github.com/buildpacks/pack/internal/config"
)

type KnownBuilder struct {
// TrustedBuilders is a flat list of builder image names that are trusted by default.
var TrustedBuilders = []string{
"gcr.io/buildpacks/builder:google-22",
"heroku/builder:24",
"heroku/builder:22",
"heroku/builder:20",
"paketobuildpacks/builder-jammy-base",
"paketobuildpacks/builder-jammy-full",
"paketobuildpacks/builder-jammy-tiny",
"paketobuildpacks/builder-jammy-buildpackless-static",
"paketobuildpacks/ubuntu-noble-builder",
"paketobuildpacks/builder-ubi8-base",
"paketobuildpacks/ubi-9-builder",
"paketobuildpacks/ubi-10-builder",
}

// SuggestedBuilder contains display metadata for recommended builders.
type SuggestedBuilder struct {
Vendor string
Image string
DefaultDescription string
Suggested bool
Trusted bool
}

var KnownBuilders = []KnownBuilder{
// SuggestedBuilders is the list of builders shown by `builder suggest`.
var SuggestedBuilders = []SuggestedBuilder{
{
Vendor: "Google",
Image: "gcr.io/buildpacks/builder:google-22",
DefaultDescription: "Ubuntu 22.04 base image with buildpacks for .NET, Dart, Go, Java, Node.js, PHP, Python, and Ruby",
Suggested: true,
Trusted: true,
},
{
Vendor: "Heroku",
Image: "heroku/builder:24",
DefaultDescription: "Ubuntu 24.04 AMD64+ARM64 base image with buildpacks for Go, Java, Node.js, PHP, Python, Ruby & Scala.",
Suggested: true,
Trusted: true,
},
{
Vendor: "Heroku",
Image: "heroku/builder:22",
DefaultDescription: "Ubuntu 22.04 AMD64 base image with buildpacks for Go, Java, Node.js, PHP, Python, Ruby & Scala.",
Suggested: false,
Trusted: true,
},
{
Vendor: "Heroku",
Image: "heroku/builder:20",
DefaultDescription: "Ubuntu 20.04 AMD64 base image with buildpacks for Go, Java, Node.js, PHP, Python, Ruby & Scala.",
Suggested: false,
Trusted: true,
},
{
Vendor: "Paketo Buildpacks",
Image: "paketobuildpacks/builder-jammy-base",
DefaultDescription: "Small base image with buildpacks for Java, Node.js, Golang, .NET Core, Python & Ruby",
Suggested: true,
Trusted: true,
},
{
Vendor: "Paketo Buildpacks",
Image: "paketobuildpacks/builder-jammy-full",
DefaultDescription: "Larger base image with buildpacks for Java, Node.js, Golang, .NET Core, Python, Ruby, & PHP",
Suggested: true,
Trusted: true,
},
{
Vendor: "Paketo Buildpacks",
Image: "paketobuildpacks/builder-jammy-tiny",
DefaultDescription: "Tiny base image (jammy build image, distroless run image) with buildpacks for Golang & Java",
Suggested: true,
Trusted: true,
},
{
Vendor: "Paketo Buildpacks",
Image: "paketobuildpacks/builder-jammy-buildpackless-static",
DefaultDescription: "Static base image (jammy build image, distroless run image) suitable for static binaries like Go or Rust",
Suggested: true,
Trusted: true,
},
{
Vendor: "Paketo Buildpacks",
Image: "paketobuildpacks/ubuntu-noble-builder",
DefaultDescription: "Small base image with buildpacks for Java, Node.js or .NET Core",
Suggested: true,
Trusted: true,
},
{
Vendor: "Paketo Buildpacks",
Image: "paketobuildpacks/builder-ubi8-base",
DefaultDescription: "Universal Base Image (RHEL8) with buildpacks to build Node.js or Java runtimes. Support also the new extension feature (aka apply Dockerfile)",
Suggested: true,
Trusted: true,
},
{
Vendor: "Paketo Buildpacks",
Image: "paketobuildpacks/ubi-9-builder",
DefaultDescription: "Universal Base Image (RHEL9) with buildpacks to build Node.js runtimes.",
Suggested: true,
Trusted: true,
},
{
Vendor: "Paketo Buildpacks",
Image: "paketobuildpacks/ubi-10-builder",
DefaultDescription: "Universal Base Image (RHEL10) with buildpacks to build Node.js runtimes.",
Suggested: true,
Trusted: true,
},
}

func IsKnownTrustedBuilder(builderName string) bool {
for _, knownBuilder := range KnownBuilders {
if builderName == knownBuilder.Image && knownBuilder.Trusted {
return true
}
}
return false
return slices.Contains(TrustedBuilders, builderName)
}

func IsTrustedBuilder(cfg config.Config, builderName string) (bool, error) {
builderReference, err := name.ParseReference(builderName, name.WithDefaultTag(""))
if err != nil {
return false, err
}

// Collect all trusted builder names
trustedBuilderNames := make([]string, len(TrustedBuilders))
copy(trustedBuilderNames, TrustedBuilders)

// Add user-configured trusted builders
for _, trustedBuilder := range cfg.TrustedBuilders {
trustedBuilderReference, err := name.ParseReference(trustedBuilder.Name, name.WithDefaultTag(""))
trustedBuilderNames = append(trustedBuilderNames, trustedBuilder.Name)
}

// Check if builder matches any trusted builder
for _, trustedBuilderName := range trustedBuilderNames {
trustedBuilderReference, err := name.ParseReference(trustedBuilderName, name.WithDefaultTag(""))
if err != nil {
return false, err
}

if trustedBuilderReference.Identifier() != "" {
if builderReference.Name() == trustedBuilderReference.Name() {
return true, nil
Expand All @@ -130,5 +121,6 @@ func IsTrustedBuilder(cfg config.Config, builderName string) (bool, error) {
}
}
}

return false, nil
}
27 changes: 27 additions & 0 deletions internal/builder/trusted_builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,16 @@ func TestTrustedBuilder(t *testing.T) {
}

func trustedBuilder(t *testing.T, when spec.G, it spec.S) {
when("SuggestedBuilders", func() {
it("are all trusted", func() {
for _, suggestedBuilder := range bldr.SuggestedBuilders {
isTrusted, err := bldr.IsTrustedBuilder(config.Config{}, suggestedBuilder.Image)
h.AssertNil(t, err)
h.AssertTrue(t, isTrusted)
}
})
})

when("IsKnownTrustedBuilder", func() {
it("matches exactly", func() {
h.AssertTrue(t, bldr.IsKnownTrustedBuilder("paketobuildpacks/builder-jammy-base"))
Expand All @@ -30,6 +40,23 @@ func trustedBuilder(t *testing.T, when spec.G, it spec.S) {
})

when("IsTrustedBuilder", func() {
it("trusts known trusted builders", func() {
// Known builder with exact tag match
isTrusted, err := bldr.IsTrustedBuilder(config.Config{}, "heroku/builder:24")
h.AssertNil(t, err)
h.AssertTrue(t, isTrusted)

// Known builder without tag should match any tag
isTrusted, err = bldr.IsTrustedBuilder(config.Config{}, "paketobuildpacks/builder-jammy-base:latest")
h.AssertNil(t, err)
h.AssertTrue(t, isTrusted)

// Unknown builder should not be trusted
isTrusted, err = bldr.IsTrustedBuilder(config.Config{}, "my/private/builder")
h.AssertNil(t, err)
h.AssertFalse(t, isTrusted)
})

it("trust image without tag", func() {
cfg := config.Config{
TrustedBuilders: []config.TrustedBuilder{
Expand Down
2 changes: 1 addition & 1 deletion internal/commands/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ func Build(logger logging.Logger, cfg config.Config, packClient PackClient) *cob
if err != nil {
return err
}
trustBuilder := isTrusted || bldr.IsKnownTrustedBuilder(builder) || flags.TrustBuilder
trustBuilder := isTrusted || flags.TrustBuilder
if trustBuilder {
logger.Debugf("Builder %s is trusted", style.Symbol(builder))
if flags.LifecycleImage != "" {
Expand Down
19 changes: 19 additions & 0 deletions internal/commands/builder_inspect_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,25 @@ func testBuilderInspectCommand(t *testing.T, when spec.G, it spec.S) {
})
})

when("image is a known trusted builder", func() {
it("passes builder info with trusted true to the writer's `Print` method", func() {
builderWriter := newDefaultBuilderWriter()

command := commands.BuilderInspect(
logger,
config.Config{},
newDefaultBuilderInspector(),
newWriterFactory(returnsForWriter(builderWriter)),
)
command.SetArgs([]string{"heroku/builder:24"})

err := command.Execute()
assert.Nil(err)

assert.Equal(builderWriter.ReceivedBuilderInfo.Trusted, true)
})
})

when("default builder is configured and is the same as specified by the command", func() {
it("passes builder info with isDefault true to the writer's `Print` method", func() {
cfg.DefaultBuilder = "the/default-builder"
Expand Down
6 changes: 3 additions & 3 deletions internal/commands/builder_suggest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func testSuggestCommand(t *testing.T, when spec.G, it spec.S) {
})

it("displays descriptions from metadata", func() {
commands.WriteSuggestedBuilder(logger, mockClient, []bldr.KnownBuilder{{
commands.WriteSuggestedBuilder(logger, mockClient, []bldr.SuggestedBuilder{{
Vendor: "Builder",
Image: "gcr.io/some/builder:latest",
DefaultDescription: "Default description",
Expand All @@ -65,7 +65,7 @@ func testSuggestCommand(t *testing.T, when spec.G, it spec.S) {
})

it("displays default descriptions", func() {
commands.WriteSuggestedBuilder(logger, mockClient, []bldr.KnownBuilder{{
commands.WriteSuggestedBuilder(logger, mockClient, []bldr.SuggestedBuilder{{
Vendor: "Builder",
Image: "gcr.io/some/builder:latest",
DefaultDescription: "Default description",
Expand All @@ -81,7 +81,7 @@ func testSuggestCommand(t *testing.T, when spec.G, it spec.S) {
})

it("displays default descriptions", func() {
commands.WriteSuggestedBuilder(logger, mockClient, []bldr.KnownBuilder{{
commands.WriteSuggestedBuilder(logger, mockClient, []bldr.SuggestedBuilder{{
Vendor: "Builder",
Image: "gcr.io/some/builder:latest",
DefaultDescription: "Default description",
Expand Down
10 changes: 3 additions & 7 deletions internal/commands/config_trusted_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func addTrustedBuilder(args []string, logger logging.Logger, cfg config.Config,
if err != nil {
return err
}
if isTrusted || bldr.IsKnownTrustedBuilder(imageName) {
if isTrusted {
logger.Infof("Builder %s is already trusted", style.Symbol(imageName))
return nil
}
Expand Down Expand Up @@ -103,12 +103,8 @@ func removeTrustedBuilder(args []string, logger logging.Logger, cfg config.Confi
}

func getTrustedBuilders(cfg config.Config) []string {
var trustedBuilders []string
for _, knownBuilder := range bldr.KnownBuilders {
if knownBuilder.Trusted {
trustedBuilders = append(trustedBuilders, knownBuilder.Image)
}
}
trustedBuilders := make([]string, len(bldr.TrustedBuilders))
copy(trustedBuilders, bldr.TrustedBuilders)

for _, builder := range cfg.TrustedBuilders {
trustedBuilders = append(trustedBuilders, builder.Name)
Expand Down
6 changes: 3 additions & 3 deletions internal/commands/config_trusted_builder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ func testTrustedBuilderCommand(t *testing.T, when spec.G, it spec.S) {
when("list", func() {
var args = []string{"list"}

it("shows suggested builders and locally trusted builder in alphabetical order", func() {
it("shows known and locally trusted builders in alphabetical order", func() {
builderName := "great-builder-" + h.RandString(8)

command.SetArgs(args)
Expand Down Expand Up @@ -171,7 +171,7 @@ func testTrustedBuilderCommand(t *testing.T, when spec.G, it spec.S) {
})
})

when("builder is a suggested builder", func() {
when("builder is a known trusted builder", func() {
it("does nothing", func() {
h.AssertNil(t, os.WriteFile(configPath, []byte(""), os.ModePerm))

Expand Down Expand Up @@ -268,7 +268,7 @@ func testTrustedBuilderCommand(t *testing.T, when spec.G, it spec.S) {
})
})

when("builder is a suggested builder", func() {
when("builder is a known trusted builder", func() {
it("does nothing and reports that ", func() {
builder := "paketobuildpacks/builder-jammy-base"
command := commands.ConfigTrustedBuilder(logger, config.Config{}, configPath)
Expand Down
2 changes: 1 addition & 1 deletion internal/commands/list_trusted_builders_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func testListTrustedBuildersCommand(t *testing.T, when spec.G, it spec.S) {
h.AssertContains(t, outBuf.String(), "Trusted Builders:")
})

it("shows suggested builders and locally trusted builder in alphabetical order", func() {
it("shows known and locally trusted builders in alphabetical order", func() {
builderName := "great-builder-" + h.RandString(8)

h.AssertNil(t, command.Execute())
Expand Down
14 changes: 4 additions & 10 deletions internal/commands/suggest_builders.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,10 @@ func suggestSettingBuilder(logger logging.Logger, inspector BuilderInspector) {
}

func suggestBuilders(logger logging.Logger, client BuilderInspector) {
suggestedBuilders := []bldr.KnownBuilder{}
for _, knownBuilder := range bldr.KnownBuilders {
if knownBuilder.Suggested {
suggestedBuilders = append(suggestedBuilders, knownBuilder)
}
}
WriteSuggestedBuilder(logger, client, suggestedBuilders)
WriteSuggestedBuilder(logger, client, bldr.SuggestedBuilders)
}

func WriteSuggestedBuilder(logger logging.Logger, inspector BuilderInspector, builders []bldr.KnownBuilder) {
func WriteSuggestedBuilder(logger logging.Logger, inspector BuilderInspector, builders []bldr.SuggestedBuilder) {
sort.Slice(builders, func(i, j int) bool {
if builders[i].Vendor == builders[j].Vendor {
return builders[i].Image < builders[j].Image
Expand All @@ -66,7 +60,7 @@ func WriteSuggestedBuilder(logger logging.Logger, inspector BuilderInspector, bu
wg.Add(len(builders))

for i, builder := range builders {
go func(w *sync.WaitGroup, i int, builder bldr.KnownBuilder) {
go func(w *sync.WaitGroup, i int, builder bldr.SuggestedBuilder) {
descriptions[i] = getBuilderDescription(builder, inspector)
w.Done()
}(&wg, i, builder)
Expand All @@ -84,7 +78,7 @@ func WriteSuggestedBuilder(logger logging.Logger, inspector BuilderInspector, bu
logger.Info("\tpack builder inspect <builder-image>")
}

func getBuilderDescription(builder bldr.KnownBuilder, inspector BuilderInspector) string {
func getBuilderDescription(builder bldr.SuggestedBuilder, inspector BuilderInspector) string {
info, err := inspector.InspectBuilder(builder.Image, false)
if err == nil && info != nil && info.Description != "" {
return info.Description
Expand Down
6 changes: 3 additions & 3 deletions internal/commands/suggest_builders_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func testSuggestBuildersCommand(t *testing.T, when spec.G, it spec.S) {
})

it("displays descriptions from metadata", func() {
commands.WriteSuggestedBuilder(logger, mockClient, []bldr.KnownBuilder{{
commands.WriteSuggestedBuilder(logger, mockClient, []bldr.SuggestedBuilder{{
Vendor: "Builder",
Image: "gcr.io/some/builder:latest",
DefaultDescription: "Default description",
Expand All @@ -65,7 +65,7 @@ func testSuggestBuildersCommand(t *testing.T, when spec.G, it spec.S) {
})

it("displays default descriptions", func() {
commands.WriteSuggestedBuilder(logger, mockClient, []bldr.KnownBuilder{{
commands.WriteSuggestedBuilder(logger, mockClient, []bldr.SuggestedBuilder{{
Vendor: "Builder",
Image: "gcr.io/some/builder:latest",
DefaultDescription: "Default description",
Expand All @@ -81,7 +81,7 @@ func testSuggestBuildersCommand(t *testing.T, when spec.G, it spec.S) {
})

it("displays default descriptions", func() {
commands.WriteSuggestedBuilder(logger, mockClient, []bldr.KnownBuilder{{
commands.WriteSuggestedBuilder(logger, mockClient, []bldr.SuggestedBuilder{{
Vendor: "Builder",
Image: "gcr.io/some/builder:latest",
DefaultDescription: "Default description",
Expand Down
Loading
Loading