diff --git a/api/server/backend/build/backend.go b/api/server/backend/build/backend.go index bdde6fa2ff..358b41b87b 100644 --- a/api/server/backend/build/backend.go +++ b/api/server/backend/build/backend.go @@ -79,8 +79,10 @@ func (b *Backend) Build(ctx context.Context, config backend.BuildConfig) (string } } - stdout := config.ProgressWriter.StdoutFormatter - fmt.Fprintf(stdout, "Successfully built %s\n", stringid.TruncateID(imageID)) + if !useBuildKit { + stdout := config.ProgressWriter.StdoutFormatter + fmt.Fprintf(stdout, "Successfully built %s\n", stringid.TruncateID(imageID)) + } err = tagger.TagImages(image.ID(imageID)) return imageID, err } @@ -94,6 +96,10 @@ func (b *Backend) PruneCache(ctx context.Context) (*types.BuildCachePruneReport, return &types.BuildCachePruneReport{SpaceReclaimed: size}, nil } +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 { diff --git a/api/server/router/build/backend.go b/api/server/router/build/backend.go index d82ef63af1..2ceae9d946 100644 --- a/api/server/router/build/backend.go +++ b/api/server/router/build/backend.go @@ -15,6 +15,8 @@ type Backend interface { // Prune build cache PruneCache(context.Context) (*types.BuildCachePruneReport, error) + + Cancel(context.Context, string) error } type experimentalProvider interface { diff --git a/api/server/router/build/build.go b/api/server/router/build/build.go index dc13a10602..811cd39181 100644 --- a/api/server/router/build/build.go +++ b/api/server/router/build/build.go @@ -25,5 +25,6 @@ func (r *buildRouter) initRoutes() { r.routes = []router.Route{ router.NewPostRoute("/build", r.postBuild, router.WithCancel), router.NewPostRoute("/build/prune", r.postPrune, router.WithCancel), + router.NewPostRoute("/build/cancel", r.postCancel), } } diff --git a/api/server/router/build/build_routes.go b/api/server/router/build/build_routes.go index 3e3668c42b..7acef8ba68 100644 --- a/api/server/router/build/build_routes.go +++ b/api/server/router/build/build_routes.go @@ -145,6 +145,7 @@ func newImageBuildOptions(ctx context.Context, r *http.Request) (*types.ImageBui options.CacheFrom = cacheFrom } options.SessionID = r.FormValue("session") + options.BuildID = r.FormValue("buildid") return options, nil } @@ -157,6 +158,17 @@ func (br *buildRouter) postPrune(ctx context.Context, w http.ResponseWriter, r * return httputils.WriteJSON(w, http.StatusOK, report) } +func (br *buildRouter) postCancel(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { + w.Header().Set("Content-Type", "application/json") + + id := r.FormValue("id") + if id == "" { + return errors.Errorf("build ID not provided") + } + + return br.backend.Cancel(ctx, id) +} + func (br *buildRouter) postBuild(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error { var ( notVerboseBuffer = bytes.NewBuffer(nil) diff --git a/api/types/client.go b/api/types/client.go index 3d2e057c9a..76e589ed84 100644 --- a/api/types/client.go +++ b/api/types/client.go @@ -181,6 +181,7 @@ type ImageBuildOptions struct { Target string SessionID string Platform string + BuildID string } // ImageBuildResponse holds information diff --git a/builder/builder-next/builder.go b/builder/builder-next/builder.go index 30472c831d..d654cbca1b 100644 --- a/builder/builder-next/builder.go +++ b/builder/builder-next/builder.go @@ -59,6 +59,9 @@ type Opt struct { type Builder struct { controller *control.Controller results *results + + mu sync.Mutex + jobs map[string]func() } func New(opt Opt) (*Builder, error) { @@ -71,11 +74,30 @@ func New(opt Opt) (*Builder, error) { b := &Builder{ controller: c, results: results, + jobs: map[string]func(){}, } return b, nil } +func (b *Builder) Cancel(ctx context.Context, id string) error { + b.mu.Lock() + if cancel, ok := b.jobs[id]; ok { + cancel() + } + b.mu.Unlock() + return nil +} + func (b *Builder) Build(ctx context.Context, opt backend.BuildConfig) (*builder.Result, error) { + if buildID := opt.Options.BuildID; buildID != "" { + b.mu.Lock() + ctx, b.jobs[buildID] = context.WithCancel(ctx) + b.mu.Unlock() + defer func() { + delete(b.jobs, buildID) + }() + } + id := identity.NewID() attrs := map[string]string{ diff --git a/client/build_cancel.go b/client/build_cancel.go new file mode 100644 index 0000000000..4cf8c980a9 --- /dev/null +++ b/client/build_cancel.go @@ -0,0 +1,21 @@ +package client // import "github.com/docker/docker/client" + +import ( + "net/url" + + "golang.org/x/net/context" +) + +// BuildCancel requests the daemon to cancel ongoing build request +func (cli *Client) BuildCancel(ctx context.Context, id string) error { + query := url.Values{} + query.Set("id", id) + + serverResp, err := cli.post(ctx, "/build/cancel", query, nil, nil) + if err != nil { + return err + } + defer ensureReaderClosed(serverResp) + + return nil +} diff --git a/client/image_build.go b/client/image_build.go index 6721460316..18a510415a 100644 --- a/client/image_build.go +++ b/client/image_build.go @@ -133,5 +133,8 @@ func (cli *Client) imageBuildOptionsToQuery(options types.ImageBuildOptions) (ur if options.Platform != "" { query.Set("platform", strings.ToLower(options.Platform)) } + if options.BuildID != "" { + query.Set("buildid", options.BuildID) + } return query, nil } diff --git a/client/interface.go b/client/interface.go index 0487a0b9f3..9250c468a6 100644 --- a/client/interface.go +++ b/client/interface.go @@ -86,6 +86,7 @@ type DistributionAPIClient interface { type ImageAPIClient interface { ImageBuild(ctx context.Context, context io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error) BuildCachePrune(ctx context.Context) (*types.BuildCachePruneReport, error) + BuildCancel(ctx context.Context, id string) error ImageCreate(ctx context.Context, parentReference string, options types.ImageCreateOptions) (io.ReadCloser, error) ImageHistory(ctx context.Context, image string) ([]image.HistoryResponseItem, error) ImageImport(ctx context.Context, source types.ImageImportSource, ref string, options types.ImageImportOptions) (io.ReadCloser, error)