diff --git a/pkg/build/gobuild.go b/pkg/build/gobuild.go index 752eafd6ed..c753314195 100644 --- a/pkg/build/gobuild.go +++ b/pkg/build/gobuild.go @@ -30,6 +30,7 @@ import ( "os/exec" "path" "path/filepath" + "runtime" "strconv" "strings" "text/template" @@ -48,6 +49,8 @@ import ( "github.com/sigstore/cosign/pkg/oci/signed" "github.com/sigstore/cosign/pkg/oci/static" ctypes "github.com/sigstore/cosign/pkg/types" + "golang.org/x/sync/errgroup" + "golang.org/x/sync/semaphore" "golang.org/x/tools/go/packages" ) @@ -78,6 +81,7 @@ type gobuild struct { platformMatcher *platformMatcher dir string labels map[string]string + semaphore *semaphore.Weighted cache *layerCache } @@ -97,6 +101,7 @@ type gobuildOpener struct { platform string labels map[string]string dir string + jobs int } func (gbo *gobuildOpener) Open() (Interface, error) { @@ -107,6 +112,9 @@ func (gbo *gobuildOpener) Open() (Interface, error) { if err != nil { return nil, err } + if gbo.jobs == 0 { + gbo.jobs = runtime.GOMAXPROCS(0) + } return &gobuild{ getBase: gbo.getBase, creationTime: gbo.creationTime, @@ -123,6 +131,7 @@ func (gbo *gobuildOpener) Open() (Interface, error) { buildToDiff: map[string]buildIDToDiffID{}, diffToDesc: map[string]diffIDToDescriptor{}, }, + semaphore: semaphore.NewWeighted(int64(gbo.jobs)), }, nil } @@ -642,6 +651,11 @@ func (g *gobuild) configForImportPath(ip string) Config { } func (g *gobuild) buildOne(ctx context.Context, refStr string, base v1.Image, platform *v1.Platform) (oci.SignedImage, error) { + if err := g.semaphore.Acquire(ctx, 1); err != nil { + return nil, err + } + defer g.semaphore.Release(1) + ref := newRef(refStr) cf, err := base.ConfigFile() @@ -872,9 +886,12 @@ func (g *gobuild) buildAll(ctx context.Context, ref string, baseIndex v1.ImageIn return nil, err } + errg, ctx := errgroup.WithContext(ctx) + // Build an image for each child from the base and append it to a new index to produce the result. - adds := []ocimutate.IndexAddendum{} - for _, desc := range im.Manifests { + adds := make([]ocimutate.IndexAddendum, len(im.Manifests)) + for i, desc := range im.Manifests { + i, desc := i, desc // Nested index is pretty rare. We could support this in theory, but return an error for now. if desc.MediaType != types.OCIManifestSchema1 && desc.MediaType != types.DockerManifestSchema2 { return nil, fmt.Errorf("%q has unexpected mediaType %q in base for %q", desc.Digest, desc.MediaType, ref) @@ -884,25 +901,32 @@ func (g *gobuild) buildAll(ctx context.Context, ref string, baseIndex v1.ImageIn continue } - baseImage, err := baseIndex.Image(desc.Digest) - if err != nil { - return nil, err - } - img, err := g.buildOne(ctx, ref, baseImage, desc.Platform) - if err != nil { - return nil, err - } - adds = append(adds, ocimutate.IndexAddendum{ - Add: img, - Descriptor: v1.Descriptor{ - URLs: desc.URLs, - MediaType: desc.MediaType, - Annotations: desc.Annotations, - Platform: desc.Platform, - }, + errg.Go(func() error { + baseImage, err := baseIndex.Image(desc.Digest) + if err != nil { + return err + } + img, err := g.buildOne(ctx, ref, baseImage, desc.Platform) + if err != nil { + return err + } + adds[i] = ocimutate.IndexAddendum{ + Add: img, + Descriptor: v1.Descriptor{ + URLs: desc.URLs, + MediaType: desc.MediaType, + Annotations: desc.Annotations, + Platform: desc.Platform, + }, + } + return nil }) } + if err := errg.Wait(); err != nil { + return nil, err + } + baseType, err := baseIndex.MediaType() if err != nil { return nil, err diff --git a/pkg/build/limit.go b/pkg/build/limit.go index e45fcf9e87..4d048b5ad7 100644 --- a/pkg/build/limit.go +++ b/pkg/build/limit.go @@ -52,6 +52,8 @@ func (l *Limiter) Build(ctx context.Context, ip string) (Result, error) { } // NewLimiter returns a new builder that only allows n concurrent builds of b. +// +// Deprecated: Obsoleted by WithJobs option. func NewLimiter(b Interface, n int) *Limiter { return &Limiter{ Builder: b, diff --git a/pkg/build/options.go b/pkg/build/options.go index 322ec297a8..4c8ff53a9b 100644 --- a/pkg/build/options.go +++ b/pkg/build/options.go @@ -142,3 +142,11 @@ func withSBOMber(sbom sbomber) Option { return nil } } + +// WithJobs limits the number of concurrent builds. +func WithJobs(jobs int) Option { + return func(gbo *gobuildOpener) error { + gbo.jobs = jobs + return nil + } +} diff --git a/pkg/commands/resolver.go b/pkg/commands/resolver.go index 7ddda5449a..a760a44e5c 100644 --- a/pkg/commands/resolver.go +++ b/pkg/commands/resolver.go @@ -26,7 +26,6 @@ import ( "log" "os" "path" - "runtime" "strings" "sync" @@ -89,6 +88,7 @@ func gobuildOptions(bo *options.BuildOptions) ([]build.Option, error) { opts := []build.Option{ build.WithBaseImages(getBaseImage(platform, bo)), build.WithPlatforms(platform), + build.WithJobs(bo.ConcurrentBuilds), } if creationTime != nil { opts = append(opts, build.WithCreationTime(*creationTime)) @@ -141,11 +141,6 @@ func makeBuilder(ctx context.Context, bo *options.BuildOptions) (*build.Caching, return nil, err } - if bo.ConcurrentBuilds == 0 { - bo.ConcurrentBuilds = runtime.GOMAXPROCS(0) - } - innerBuilder = build.NewLimiter(innerBuilder, bo.ConcurrentBuilds) - // tl;dr Wrap builder in a caching builder. // // The caching builder should on Build calls: