1package build
2
3import (
4	"fmt"
5
6	"github.com/docker/distribution/reference"
7	"github.com/docker/docker/api/types"
8	"github.com/docker/docker/api/types/backend"
9	"github.com/docker/docker/builder"
10	"github.com/docker/docker/builder/fscache"
11	"github.com/docker/docker/image"
12	"github.com/docker/docker/pkg/stringid"
13	"github.com/pkg/errors"
14	"golang.org/x/net/context"
15)
16
17// ImageComponent provides an interface for working with images
18type ImageComponent interface {
19	SquashImage(from string, to string) (string, error)
20	TagImageWithReference(image.ID, string, reference.Named) error
21}
22
23// Builder defines interface for running a build
24type Builder interface {
25	Build(context.Context, backend.BuildConfig) (*builder.Result, error)
26}
27
28// Backend provides build functionality to the API router
29type Backend struct {
30	builder        Builder
31	fsCache        *fscache.FSCache
32	imageComponent ImageComponent
33}
34
35// NewBackend creates a new build backend from components
36func NewBackend(components ImageComponent, builder Builder, fsCache *fscache.FSCache) (*Backend, error) {
37	return &Backend{imageComponent: components, builder: builder, fsCache: fsCache}, nil
38}
39
40// Build builds an image from a Source
41func (b *Backend) Build(ctx context.Context, config backend.BuildConfig) (string, error) {
42	options := config.Options
43	tagger, err := NewTagger(b.imageComponent, config.ProgressWriter.StdoutFormatter, options.Tags)
44	if err != nil {
45		return "", err
46	}
47
48	build, err := b.builder.Build(ctx, config)
49	if err != nil {
50		return "", err
51	}
52
53	var imageID = build.ImageID
54	if options.Squash {
55		if imageID, err = squashBuild(build, b.imageComponent); err != nil {
56			return "", err
57		}
58		if config.ProgressWriter.AuxFormatter != nil {
59			if err = config.ProgressWriter.AuxFormatter.Emit(types.BuildResult{ID: imageID}); err != nil {
60				return "", err
61			}
62		}
63	}
64
65	stdout := config.ProgressWriter.StdoutFormatter
66	fmt.Fprintf(stdout, "Successfully built %s\n", stringid.TruncateID(imageID))
67	err = tagger.TagImages(image.ID(imageID))
68	return imageID, err
69}
70
71// PruneCache removes all cached build sources
72func (b *Backend) PruneCache(ctx context.Context) (*types.BuildCachePruneReport, error) {
73	size, err := b.fsCache.Prune(ctx)
74	if err != nil {
75		return nil, errors.Wrap(err, "failed to prune build cache")
76	}
77	return &types.BuildCachePruneReport{SpaceReclaimed: size}, nil
78}
79
80func squashBuild(build *builder.Result, imageComponent ImageComponent) (string, error) {
81	var fromID string
82	if build.FromImage != nil {
83		fromID = build.FromImage.ImageID()
84	}
85	imageID, err := imageComponent.SquashImage(build.ImageID, fromID)
86	if err != nil {
87		return "", errors.Wrap(err, "error squashing image")
88	}
89	return imageID, nil
90}
91