blob: a23d148122947849b78f58630b73e6459b9f344b [file] [log] [blame]
package build
import (
"context"
"fmt"
"strconv"
"github.com/distribution/reference"
"github.com/moby/moby/api/types/build"
"github.com/moby/moby/api/types/events"
"github.com/moby/moby/v2/daemon/builder"
daemonevents "github.com/moby/moby/v2/daemon/events"
buildkit "github.com/moby/moby/v2/daemon/internal/builder-next"
"github.com/moby/moby/v2/daemon/internal/image"
"github.com/moby/moby/v2/daemon/internal/stringid"
"github.com/moby/moby/v2/daemon/server/buildbackend"
"github.com/pkg/errors"
"google.golang.org/grpc"
)
// ImageComponent provides an interface for working with images
type ImageComponent interface {
SquashImage(from string, to string) (string, error)
TagImage(context.Context, image.ID, reference.Named) error
}
// Builder defines interface for running a build
type Builder interface {
Build(context.Context, buildbackend.BuildConfig) (*builder.Result, error)
}
// Backend provides build functionality to the API router
type Backend struct {
builder Builder
imageComponent ImageComponent
buildkit *buildkit.Builder
eventsService *daemonevents.Events
}
// NewBackend creates a new build backend from components
func NewBackend(components ImageComponent, builder Builder, buildkit *buildkit.Builder, es *daemonevents.Events) (*Backend, error) {
return &Backend{imageComponent: components, builder: builder, buildkit: buildkit, eventsService: es}, nil
}
// RegisterGRPC registers buildkit controller to the grpc server.
func (b *Backend) RegisterGRPC(s *grpc.Server) {
if b.buildkit != nil {
b.buildkit.RegisterGRPC(s)
}
}
// Build builds an image from a Source
func (b *Backend) Build(ctx context.Context, config buildbackend.BuildConfig) (string, error) {
options := config.Options
useBuildKit := options.Version == build.BuilderBuildKit
tags, err := sanitizeRepoAndTags(options.Tags)
if err != nil {
return "", err
}
var buildResult *builder.Result
if useBuildKit {
buildResult, err = b.buildkit.Build(ctx, config)
if err != nil {
return "", err
}
} else {
buildResult, err = b.builder.Build(ctx, config)
if err != nil {
return "", err
}
}
if buildResult == nil {
return "", nil
}
imageID := buildResult.ImageID
if options.Squash {
if imageID, err = squashBuild(buildResult, b.imageComponent); err != nil {
return "", err
}
if config.ProgressWriter.AuxFormatter != nil {
if err = config.ProgressWriter.AuxFormatter.Emit("moby.image.id", build.Result{ID: imageID}); err != nil {
return "", err
}
}
}
if imageID != "" && !useBuildKit {
stdout := config.ProgressWriter.StdoutFormatter
_, _ = fmt.Fprintf(stdout, "Successfully built %s\n", stringid.TruncateID(imageID))
err = tagImages(ctx, b.imageComponent, config.ProgressWriter.StdoutFormatter, image.ID(imageID), tags)
}
return imageID, err
}
// PruneCache removes all cached build sources
func (b *Backend) PruneCache(ctx context.Context, opts buildbackend.CachePruneOptions) (*build.CachePruneReport, error) {
buildCacheSize, cacheIDs, err := b.buildkit.Prune(ctx, opts)
if err != nil {
return nil, errors.Wrap(err, "failed to prune build cache")
}
b.eventsService.Log(events.ActionPrune, events.BuilderEventType, events.Actor{
Attributes: map[string]string{
"reclaimed": strconv.FormatInt(buildCacheSize, 10),
},
})
return &build.CachePruneReport{SpaceReclaimed: uint64(buildCacheSize), CachesDeleted: cacheIDs}, nil
}
// Cancel cancels the build by ID
func (b *Backend) Cancel(ctx context.Context, id string) error {
return b.buildkit.Cancel(ctx, id)
}
func squashBuild(build *builder.Result, imageComponent ImageComponent) (string, error) {
var fromID string
if build.FromImage != nil {
fromID = build.FromImage.ImageID()
}
imageID, err := imageComponent.SquashImage(build.ImageID, fromID)
if err != nil {
return "", errors.Wrap(err, "error squashing image")
}
return imageID, nil
}