1package build // import "github.com/docker/docker/api/server/backend/build"
2
3import (
4	"context"
5	"fmt"
6	"strconv"
7
8	"github.com/docker/distribution/reference"
9	"github.com/docker/docker/api/types"
10	"github.com/docker/docker/api/types/backend"
11	"github.com/docker/docker/api/types/events"
12	"github.com/docker/docker/builder"
13	buildkit "github.com/docker/docker/builder/builder-next"
14	daemonevents "github.com/docker/docker/daemon/events"
15	"github.com/docker/docker/image"
16	"github.com/docker/docker/pkg/stringid"
17	"github.com/pkg/errors"
18	"google.golang.org/grpc"
19)
20
21// ImageComponent provides an interface for working with images
22type ImageComponent interface {
23	SquashImage(from string, to string) (string, error)
24	TagImageWithReference(image.ID, reference.Named) error
25}
26
27// Builder defines interface for running a build
28type Builder interface {
29	Build(context.Context, backend.BuildConfig) (*builder.Result, error)
30}
31
32// Backend provides build functionality to the API router
33type Backend struct {
34	builder        Builder
35	imageComponent ImageComponent
36	buildkit       *buildkit.Builder
37	eventsService  *daemonevents.Events
38}
39
40// NewBackend creates a new build backend from components
41func NewBackend(components ImageComponent, builder Builder, buildkit *buildkit.Builder, es *daemonevents.Events) (*Backend, error) {
42	return &Backend{imageComponent: components, builder: builder, buildkit: buildkit, eventsService: es}, nil
43}
44
45// RegisterGRPC registers buildkit controller to the grpc server.
46func (b *Backend) RegisterGRPC(s *grpc.Server) {
47	if b.buildkit != nil {
48		b.buildkit.RegisterGRPC(s)
49	}
50}
51
52// Build builds an image from a Source
53func (b *Backend) Build(ctx context.Context, config backend.BuildConfig) (string, error) {
54	options := config.Options
55	useBuildKit := options.Version == types.BuilderBuildKit
56
57	tagger, err := NewTagger(b.imageComponent, config.ProgressWriter.StdoutFormatter, options.Tags)
58	if err != nil {
59		return "", err
60	}
61
62	var build *builder.Result
63	if useBuildKit {
64		build, err = b.buildkit.Build(ctx, config)
65		if err != nil {
66			return "", err
67		}
68	} else {
69		build, err = b.builder.Build(ctx, config)
70		if err != nil {
71			return "", err
72		}
73	}
74
75	if build == nil {
76		return "", nil
77	}
78
79	var imageID = build.ImageID
80	if options.Squash {
81		if imageID, err = squashBuild(build, b.imageComponent); err != nil {
82			return "", err
83		}
84		if config.ProgressWriter.AuxFormatter != nil {
85			if err = config.ProgressWriter.AuxFormatter.Emit("moby.image.id", types.BuildResult{ID: imageID}); err != nil {
86				return "", err
87			}
88		}
89	}
90
91	if !useBuildKit {
92		stdout := config.ProgressWriter.StdoutFormatter
93		fmt.Fprintf(stdout, "Successfully built %s\n", stringid.TruncateID(imageID))
94	}
95	if imageID != "" {
96		err = tagger.TagImages(image.ID(imageID))
97	}
98	return imageID, err
99}
100
101// PruneCache removes all cached build sources
102func (b *Backend) PruneCache(ctx context.Context, opts types.BuildCachePruneOptions) (*types.BuildCachePruneReport, error) {
103	buildCacheSize, cacheIDs, err := b.buildkit.Prune(ctx, opts)
104	if err != nil {
105		return nil, errors.Wrap(err, "failed to prune build cache")
106	}
107	b.eventsService.Log("prune", events.BuilderEventType, events.Actor{
108		Attributes: map[string]string{
109			"reclaimed": strconv.FormatInt(buildCacheSize, 10),
110		},
111	})
112	return &types.BuildCachePruneReport{SpaceReclaimed: uint64(buildCacheSize), CachesDeleted: cacheIDs}, nil
113}
114
115// Cancel cancels the build by ID
116func (b *Backend) Cancel(ctx context.Context, id string) error {
117	return b.buildkit.Cancel(ctx, id)
118}
119
120func squashBuild(build *builder.Result, imageComponent ImageComponent) (string, error) {
121	var fromID string
122	if build.FromImage != nil {
123		fromID = build.FromImage.ImageID()
124	}
125	imageID, err := imageComponent.SquashImage(build.ImageID, fromID)
126	if err != nil {
127		return "", errors.Wrap(err, "error squashing image")
128	}
129	return imageID, nil
130}
131