Browse code

builder: add graceful cancellation endpoint

Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>

Tonis Tiigi authored on 2018/04/20 03:08:33
Showing 9 changed files
... ...
@@ -79,8 +79,10 @@ func (b *Backend) Build(ctx context.Context, config backend.BuildConfig) (string
79 79
 		}
80 80
 	}
81 81
 
82
-	stdout := config.ProgressWriter.StdoutFormatter
83
-	fmt.Fprintf(stdout, "Successfully built %s\n", stringid.TruncateID(imageID))
82
+	if !useBuildKit {
83
+		stdout := config.ProgressWriter.StdoutFormatter
84
+		fmt.Fprintf(stdout, "Successfully built %s\n", stringid.TruncateID(imageID))
85
+	}
84 86
 	err = tagger.TagImages(image.ID(imageID))
85 87
 	return imageID, err
86 88
 }
... ...
@@ -94,6 +96,10 @@ func (b *Backend) PruneCache(ctx context.Context) (*types.BuildCachePruneReport,
94 94
 	return &types.BuildCachePruneReport{SpaceReclaimed: size}, nil
95 95
 }
96 96
 
97
+func (b *Backend) Cancel(ctx context.Context, id string) error {
98
+	return b.buildkit.Cancel(ctx, id)
99
+}
100
+
97 101
 func squashBuild(build *builder.Result, imageComponent ImageComponent) (string, error) {
98 102
 	var fromID string
99 103
 	if build.FromImage != nil {
... ...
@@ -15,6 +15,8 @@ type Backend interface {
15 15
 
16 16
 	// Prune build cache
17 17
 	PruneCache(context.Context) (*types.BuildCachePruneReport, error)
18
+
19
+	Cancel(context.Context, string) error
18 20
 }
19 21
 
20 22
 type experimentalProvider interface {
... ...
@@ -25,5 +25,6 @@ func (r *buildRouter) initRoutes() {
25 25
 	r.routes = []router.Route{
26 26
 		router.NewPostRoute("/build", r.postBuild, router.WithCancel),
27 27
 		router.NewPostRoute("/build/prune", r.postPrune, router.WithCancel),
28
+		router.NewPostRoute("/build/cancel", r.postCancel),
28 29
 	}
29 30
 }
... ...
@@ -145,6 +145,7 @@ func newImageBuildOptions(ctx context.Context, r *http.Request) (*types.ImageBui
145 145
 		options.CacheFrom = cacheFrom
146 146
 	}
147 147
 	options.SessionID = r.FormValue("session")
148
+	options.BuildID = r.FormValue("buildid")
148 149
 
149 150
 	return options, nil
150 151
 }
... ...
@@ -157,6 +158,17 @@ func (br *buildRouter) postPrune(ctx context.Context, w http.ResponseWriter, r *
157 157
 	return httputils.WriteJSON(w, http.StatusOK, report)
158 158
 }
159 159
 
160
+func (br *buildRouter) postCancel(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
161
+	w.Header().Set("Content-Type", "application/json")
162
+
163
+	id := r.FormValue("id")
164
+	if id == "" {
165
+		return errors.Errorf("build ID not provided")
166
+	}
167
+
168
+	return br.backend.Cancel(ctx, id)
169
+}
170
+
160 171
 func (br *buildRouter) postBuild(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
161 172
 	var (
162 173
 		notVerboseBuffer = bytes.NewBuffer(nil)
... ...
@@ -181,6 +181,7 @@ type ImageBuildOptions struct {
181 181
 	Target      string
182 182
 	SessionID   string
183 183
 	Platform    string
184
+	BuildID     string
184 185
 }
185 186
 
186 187
 // ImageBuildResponse holds information
... ...
@@ -59,6 +59,9 @@ type Opt struct {
59 59
 type Builder struct {
60 60
 	controller *control.Controller
61 61
 	results    *results
62
+
63
+	mu   sync.Mutex
64
+	jobs map[string]func()
62 65
 }
63 66
 
64 67
 func New(opt Opt) (*Builder, error) {
... ...
@@ -71,11 +74,30 @@ func New(opt Opt) (*Builder, error) {
71 71
 	b := &Builder{
72 72
 		controller: c,
73 73
 		results:    results,
74
+		jobs:       map[string]func(){},
74 75
 	}
75 76
 	return b, nil
76 77
 }
77 78
 
79
+func (b *Builder) Cancel(ctx context.Context, id string) error {
80
+	b.mu.Lock()
81
+	if cancel, ok := b.jobs[id]; ok {
82
+		cancel()
83
+	}
84
+	b.mu.Unlock()
85
+	return nil
86
+}
87
+
78 88
 func (b *Builder) Build(ctx context.Context, opt backend.BuildConfig) (*builder.Result, error) {
89
+	if buildID := opt.Options.BuildID; buildID != "" {
90
+		b.mu.Lock()
91
+		ctx, b.jobs[buildID] = context.WithCancel(ctx)
92
+		b.mu.Unlock()
93
+		defer func() {
94
+			delete(b.jobs, buildID)
95
+		}()
96
+	}
97
+
79 98
 	id := identity.NewID()
80 99
 
81 100
 	attrs := map[string]string{
82 101
new file mode 100644
... ...
@@ -0,0 +1,21 @@
0
+package client // import "github.com/docker/docker/client"
1
+
2
+import (
3
+	"net/url"
4
+
5
+	"golang.org/x/net/context"
6
+)
7
+
8
+// BuildCancel requests the daemon to cancel ongoing build request
9
+func (cli *Client) BuildCancel(ctx context.Context, id string) error {
10
+	query := url.Values{}
11
+	query.Set("id", id)
12
+
13
+	serverResp, err := cli.post(ctx, "/build/cancel", query, nil, nil)
14
+	if err != nil {
15
+		return err
16
+	}
17
+	defer ensureReaderClosed(serverResp)
18
+
19
+	return nil
20
+}
... ...
@@ -133,5 +133,8 @@ func (cli *Client) imageBuildOptionsToQuery(options types.ImageBuildOptions) (ur
133 133
 	if options.Platform != "" {
134 134
 		query.Set("platform", strings.ToLower(options.Platform))
135 135
 	}
136
+	if options.BuildID != "" {
137
+		query.Set("buildid", options.BuildID)
138
+	}
136 139
 	return query, nil
137 140
 }
... ...
@@ -86,6 +86,7 @@ type DistributionAPIClient interface {
86 86
 type ImageAPIClient interface {
87 87
 	ImageBuild(ctx context.Context, context io.Reader, options types.ImageBuildOptions) (types.ImageBuildResponse, error)
88 88
 	BuildCachePrune(ctx context.Context) (*types.BuildCachePruneReport, error)
89
+	BuildCancel(ctx context.Context, id string) error
89 90
 	ImageCreate(ctx context.Context, parentReference string, options types.ImageCreateOptions) (io.ReadCloser, error)
90 91
 	ImageHistory(ctx context.Context, image string) ([]image.HistoryResponseItem, error)
91 92
 	ImageImport(ctx context.Context, source types.ImageImportSource, ref string, options types.ImageImportOptions) (io.ReadCloser, error)