Browse code

Create build router separate from image router.

Signed-off-by: Anusha Ragunathan <anusha@docker.com>

Anusha Ragunathan authored on 2015/12/18 09:17:50
Showing 8 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,12 @@
0
+package build
1
+
2
+// Backend abstracts an image builder whose only purpose is to build an image referenced by an imageID.
3
+type Backend interface {
4
+	// Build builds a Docker image referenced by an imageID string.
5
+	//
6
+	// Note: Tagging an image should not be done by a Builder, it should instead be done
7
+	// by the caller.
8
+	//
9
+	// TODO: make this return a reference instead of string
10
+	Build() (imageID string)
11
+}
0 12
new file mode 100644
... ...
@@ -0,0 +1,33 @@
0
+package build
1
+
2
+import (
3
+	"github.com/docker/docker/api/server/router"
4
+	"github.com/docker/docker/api/server/router/local"
5
+	"github.com/docker/docker/daemon"
6
+)
7
+
8
+// buildRouter is a router to talk with the build controller
9
+type buildRouter struct {
10
+	backend *daemon.Daemon
11
+	routes  []router.Route
12
+}
13
+
14
+// NewRouter initializes a new build router
15
+func NewRouter(b *daemon.Daemon) router.Router {
16
+	r := &buildRouter{
17
+		backend: b,
18
+	}
19
+	r.initRoutes()
20
+	return r
21
+}
22
+
23
+// Routes returns the available routers to the build controller
24
+func (r *buildRouter) Routes() []router.Route {
25
+	return r.routes
26
+}
27
+
28
+func (r *buildRouter) initRoutes() {
29
+	r.routes = []router.Route{
30
+		local.NewPostRoute("/build", r.postBuild),
31
+	}
32
+}
0 33
new file mode 100644
... ...
@@ -0,0 +1,239 @@
0
+package build
1
+
2
+import (
3
+	"encoding/base64"
4
+	"encoding/json"
5
+	"errors"
6
+	"fmt"
7
+	"io"
8
+	"net/http"
9
+	"strconv"
10
+	"strings"
11
+
12
+	"github.com/Sirupsen/logrus"
13
+	"github.com/docker/docker/api/server/httputils"
14
+	"github.com/docker/docker/api/types"
15
+	"github.com/docker/docker/builder"
16
+	"github.com/docker/docker/builder/dockerfile"
17
+	"github.com/docker/docker/daemon/daemonbuilder"
18
+	"github.com/docker/docker/pkg/archive"
19
+	"github.com/docker/docker/pkg/chrootarchive"
20
+	"github.com/docker/docker/pkg/ioutils"
21
+	"github.com/docker/docker/pkg/progress"
22
+	"github.com/docker/docker/pkg/streamformatter"
23
+	"github.com/docker/docker/pkg/ulimit"
24
+	"github.com/docker/docker/reference"
25
+	"github.com/docker/docker/runconfig"
26
+	"github.com/docker/docker/utils"
27
+	"golang.org/x/net/context"
28
+)
29
+
30
+// sanitizeRepoAndTags parses the raw "t" parameter received from the client
31
+// to a slice of repoAndTag.
32
+// It also validates each repoName and tag.
33
+func sanitizeRepoAndTags(names []string) ([]reference.Named, error) {
34
+	var (
35
+		repoAndTags []reference.Named
36
+		// This map is used for deduplicating the "-t" parameter.
37
+		uniqNames = make(map[string]struct{})
38
+	)
39
+	for _, repo := range names {
40
+		if repo == "" {
41
+			continue
42
+		}
43
+
44
+		ref, err := reference.ParseNamed(repo)
45
+		if err != nil {
46
+			return nil, err
47
+		}
48
+
49
+		ref = reference.WithDefaultTag(ref)
50
+
51
+		if _, isCanonical := ref.(reference.Canonical); isCanonical {
52
+			return nil, errors.New("build tag cannot contain a digest")
53
+		}
54
+
55
+		if _, isTagged := ref.(reference.NamedTagged); !isTagged {
56
+			ref, err = reference.WithTag(ref, reference.DefaultTag)
57
+		}
58
+
59
+		nameWithTag := ref.String()
60
+
61
+		if _, exists := uniqNames[nameWithTag]; !exists {
62
+			uniqNames[nameWithTag] = struct{}{}
63
+			repoAndTags = append(repoAndTags, ref)
64
+		}
65
+	}
66
+	return repoAndTags, nil
67
+}
68
+
69
+func (br *buildRouter) postBuild(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
70
+	var (
71
+		authConfigs        = map[string]types.AuthConfig{}
72
+		authConfigsEncoded = r.Header.Get("X-Registry-Config")
73
+		buildConfig        = &dockerfile.Config{}
74
+	)
75
+
76
+	if authConfigsEncoded != "" {
77
+		authConfigsJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authConfigsEncoded))
78
+		if err := json.NewDecoder(authConfigsJSON).Decode(&authConfigs); err != nil {
79
+			// for a pull it is not an error if no auth was given
80
+			// to increase compatibility with the existing api it is defaulting
81
+			// to be empty.
82
+		}
83
+	}
84
+
85
+	w.Header().Set("Content-Type", "application/json")
86
+
87
+	version := httputils.VersionFromContext(ctx)
88
+	output := ioutils.NewWriteFlusher(w)
89
+	defer output.Close()
90
+	sf := streamformatter.NewJSONStreamFormatter()
91
+	errf := func(err error) error {
92
+		// Do not write the error in the http output if it's still empty.
93
+		// This prevents from writing a 200(OK) when there is an internal error.
94
+		if !output.Flushed() {
95
+			return err
96
+		}
97
+		_, err = w.Write(sf.FormatError(errors.New(utils.GetErrorMessage(err))))
98
+		if err != nil {
99
+			logrus.Warnf("could not write error response: %v", err)
100
+		}
101
+		return nil
102
+	}
103
+
104
+	if httputils.BoolValue(r, "forcerm") && version.GreaterThanOrEqualTo("1.12") {
105
+		buildConfig.Remove = true
106
+	} else if r.FormValue("rm") == "" && version.GreaterThanOrEqualTo("1.12") {
107
+		buildConfig.Remove = true
108
+	} else {
109
+		buildConfig.Remove = httputils.BoolValue(r, "rm")
110
+	}
111
+	if httputils.BoolValue(r, "pull") && version.GreaterThanOrEqualTo("1.16") {
112
+		buildConfig.Pull = true
113
+	}
114
+
115
+	repoAndTags, err := sanitizeRepoAndTags(r.Form["t"])
116
+	if err != nil {
117
+		return errf(err)
118
+	}
119
+
120
+	buildConfig.DockerfileName = r.FormValue("dockerfile")
121
+	buildConfig.Verbose = !httputils.BoolValue(r, "q")
122
+	buildConfig.UseCache = !httputils.BoolValue(r, "nocache")
123
+	buildConfig.ForceRemove = httputils.BoolValue(r, "forcerm")
124
+	buildConfig.MemorySwap = httputils.Int64ValueOrZero(r, "memswap")
125
+	buildConfig.Memory = httputils.Int64ValueOrZero(r, "memory")
126
+	buildConfig.CPUShares = httputils.Int64ValueOrZero(r, "cpushares")
127
+	buildConfig.CPUPeriod = httputils.Int64ValueOrZero(r, "cpuperiod")
128
+	buildConfig.CPUQuota = httputils.Int64ValueOrZero(r, "cpuquota")
129
+	buildConfig.CPUSetCpus = r.FormValue("cpusetcpus")
130
+	buildConfig.CPUSetMems = r.FormValue("cpusetmems")
131
+	buildConfig.CgroupParent = r.FormValue("cgroupparent")
132
+
133
+	if r.Form.Get("shmsize") != "" {
134
+		shmSize, err := strconv.ParseInt(r.Form.Get("shmsize"), 10, 64)
135
+		if err != nil {
136
+			return errf(err)
137
+		}
138
+		buildConfig.ShmSize = &shmSize
139
+	}
140
+
141
+	if i := runconfig.IsolationLevel(r.FormValue("isolation")); i != "" {
142
+		if !runconfig.IsolationLevel.IsValid(i) {
143
+			return errf(fmt.Errorf("Unsupported isolation: %q", i))
144
+		}
145
+		buildConfig.Isolation = i
146
+	}
147
+
148
+	var buildUlimits = []*ulimit.Ulimit{}
149
+	ulimitsJSON := r.FormValue("ulimits")
150
+	if ulimitsJSON != "" {
151
+		if err := json.NewDecoder(strings.NewReader(ulimitsJSON)).Decode(&buildUlimits); err != nil {
152
+			return errf(err)
153
+		}
154
+		buildConfig.Ulimits = buildUlimits
155
+	}
156
+
157
+	var buildArgs = map[string]string{}
158
+	buildArgsJSON := r.FormValue("buildargs")
159
+	if buildArgsJSON != "" {
160
+		if err := json.NewDecoder(strings.NewReader(buildArgsJSON)).Decode(&buildArgs); err != nil {
161
+			return errf(err)
162
+		}
163
+		buildConfig.BuildArgs = buildArgs
164
+	}
165
+
166
+	remoteURL := r.FormValue("remote")
167
+
168
+	// Currently, only used if context is from a remote url.
169
+	// Look at code in DetectContextFromRemoteURL for more information.
170
+	createProgressReader := func(in io.ReadCloser) io.ReadCloser {
171
+		progressOutput := sf.NewProgressOutput(output, true)
172
+		return progress.NewProgressReader(in, progressOutput, r.ContentLength, "Downloading context", remoteURL)
173
+	}
174
+
175
+	var (
176
+		context        builder.ModifiableContext
177
+		dockerfileName string
178
+	)
179
+	context, dockerfileName, err = daemonbuilder.DetectContextFromRemoteURL(r.Body, remoteURL, createProgressReader)
180
+	if err != nil {
181
+		return errf(err)
182
+	}
183
+	defer func() {
184
+		if err := context.Close(); err != nil {
185
+			logrus.Debugf("[BUILDER] failed to remove temporary context: %v", err)
186
+		}
187
+	}()
188
+
189
+	uidMaps, gidMaps := br.backend.GetUIDGIDMaps()
190
+	defaultArchiver := &archive.Archiver{
191
+		Untar:   chrootarchive.Untar,
192
+		UIDMaps: uidMaps,
193
+		GIDMaps: gidMaps,
194
+	}
195
+	docker := &daemonbuilder.Docker{
196
+		Daemon:      br.backend,
197
+		OutOld:      output,
198
+		AuthConfigs: authConfigs,
199
+		Archiver:    defaultArchiver,
200
+	}
201
+
202
+	b, err := dockerfile.NewBuilder(buildConfig, docker, builder.DockerIgnoreContext{ModifiableContext: context}, nil)
203
+	if err != nil {
204
+		return errf(err)
205
+	}
206
+	b.Stdout = &streamformatter.StdoutFormatter{Writer: output, StreamFormatter: sf}
207
+	b.Stderr = &streamformatter.StderrFormatter{Writer: output, StreamFormatter: sf}
208
+
209
+	if closeNotifier, ok := w.(http.CloseNotifier); ok {
210
+		finished := make(chan struct{})
211
+		defer close(finished)
212
+		go func() {
213
+			select {
214
+			case <-finished:
215
+			case <-closeNotifier.CloseNotify():
216
+				logrus.Infof("Client disconnected, cancelling job: build")
217
+				b.Cancel()
218
+			}
219
+		}()
220
+	}
221
+
222
+	if len(dockerfileName) > 0 {
223
+		b.DockerfileName = dockerfileName
224
+	}
225
+
226
+	imgID, err := b.Build()
227
+	if err != nil {
228
+		return errf(err)
229
+	}
230
+
231
+	for _, rt := range repoAndTags {
232
+		if err := br.backend.TagImage(rt, imgID); err != nil {
233
+			return errf(err)
234
+		}
235
+	}
236
+
237
+	return nil
238
+}
... ...
@@ -7,26 +7,17 @@ import (
7 7
 	"fmt"
8 8
 	"io"
9 9
 	"net/http"
10
-	"strconv"
11 10
 	"strings"
12 11
 
13
-	"github.com/Sirupsen/logrus"
14 12
 	"github.com/docker/distribution/digest"
15 13
 	"github.com/docker/docker/api/server/httputils"
16 14
 	"github.com/docker/docker/api/types"
17
-	"github.com/docker/docker/builder"
18 15
 	"github.com/docker/docker/builder/dockerfile"
19
-	"github.com/docker/docker/daemon/daemonbuilder"
20 16
 	derr "github.com/docker/docker/errors"
21
-	"github.com/docker/docker/pkg/archive"
22
-	"github.com/docker/docker/pkg/chrootarchive"
23 17
 	"github.com/docker/docker/pkg/ioutils"
24
-	"github.com/docker/docker/pkg/progress"
25 18
 	"github.com/docker/docker/pkg/streamformatter"
26
-	"github.com/docker/docker/pkg/ulimit"
27 19
 	"github.com/docker/docker/reference"
28 20
 	"github.com/docker/docker/runconfig"
29
-	"github.com/docker/docker/utils"
30 21
 	"golang.org/x/net/context"
31 22
 )
32 23
 
... ...
@@ -306,211 +297,6 @@ func (s *router) getImagesByName(ctx context.Context, w http.ResponseWriter, r *
306 306
 	return httputils.WriteJSON(w, http.StatusOK, imageInspect)
307 307
 }
308 308
 
309
-func (s *router) postBuild(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
310
-	var (
311
-		authConfigs        = map[string]types.AuthConfig{}
312
-		authConfigsEncoded = r.Header.Get("X-Registry-Config")
313
-		buildConfig        = &dockerfile.Config{}
314
-	)
315
-
316
-	if authConfigsEncoded != "" {
317
-		authConfigsJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authConfigsEncoded))
318
-		if err := json.NewDecoder(authConfigsJSON).Decode(&authConfigs); err != nil {
319
-			// for a pull it is not an error if no auth was given
320
-			// to increase compatibility with the existing api it is defaulting
321
-			// to be empty.
322
-		}
323
-	}
324
-
325
-	w.Header().Set("Content-Type", "application/json")
326
-
327
-	version := httputils.VersionFromContext(ctx)
328
-	output := ioutils.NewWriteFlusher(w)
329
-	defer output.Close()
330
-	sf := streamformatter.NewJSONStreamFormatter()
331
-	errf := func(err error) error {
332
-		// Do not write the error in the http output if it's still empty.
333
-		// This prevents from writing a 200(OK) when there is an internal error.
334
-		if !output.Flushed() {
335
-			return err
336
-		}
337
-		_, err = w.Write(sf.FormatError(errors.New(utils.GetErrorMessage(err))))
338
-		if err != nil {
339
-			logrus.Warnf("could not write error response: %v", err)
340
-		}
341
-		return nil
342
-	}
343
-
344
-	if httputils.BoolValue(r, "forcerm") && version.GreaterThanOrEqualTo("1.12") {
345
-		buildConfig.Remove = true
346
-	} else if r.FormValue("rm") == "" && version.GreaterThanOrEqualTo("1.12") {
347
-		buildConfig.Remove = true
348
-	} else {
349
-		buildConfig.Remove = httputils.BoolValue(r, "rm")
350
-	}
351
-	if httputils.BoolValue(r, "pull") && version.GreaterThanOrEqualTo("1.16") {
352
-		buildConfig.Pull = true
353
-	}
354
-
355
-	repoAndTags, err := sanitizeRepoAndTags(r.Form["t"])
356
-	if err != nil {
357
-		return errf(err)
358
-	}
359
-
360
-	buildConfig.DockerfileName = r.FormValue("dockerfile")
361
-	buildConfig.Verbose = !httputils.BoolValue(r, "q")
362
-	buildConfig.UseCache = !httputils.BoolValue(r, "nocache")
363
-	buildConfig.ForceRemove = httputils.BoolValue(r, "forcerm")
364
-	buildConfig.MemorySwap = httputils.Int64ValueOrZero(r, "memswap")
365
-	buildConfig.Memory = httputils.Int64ValueOrZero(r, "memory")
366
-	buildConfig.CPUShares = httputils.Int64ValueOrZero(r, "cpushares")
367
-	buildConfig.CPUPeriod = httputils.Int64ValueOrZero(r, "cpuperiod")
368
-	buildConfig.CPUQuota = httputils.Int64ValueOrZero(r, "cpuquota")
369
-	buildConfig.CPUSetCpus = r.FormValue("cpusetcpus")
370
-	buildConfig.CPUSetMems = r.FormValue("cpusetmems")
371
-	buildConfig.CgroupParent = r.FormValue("cgroupparent")
372
-
373
-	if r.Form.Get("shmsize") != "" {
374
-		shmSize, err := strconv.ParseInt(r.Form.Get("shmsize"), 10, 64)
375
-		if err != nil {
376
-			return errf(err)
377
-		}
378
-		buildConfig.ShmSize = &shmSize
379
-	}
380
-
381
-	if i := runconfig.IsolationLevel(r.FormValue("isolation")); i != "" {
382
-		if !runconfig.IsolationLevel.IsValid(i) {
383
-			return errf(fmt.Errorf("Unsupported isolation: %q", i))
384
-		}
385
-		buildConfig.Isolation = i
386
-	}
387
-
388
-	var buildUlimits = []*ulimit.Ulimit{}
389
-	ulimitsJSON := r.FormValue("ulimits")
390
-	if ulimitsJSON != "" {
391
-		if err := json.NewDecoder(strings.NewReader(ulimitsJSON)).Decode(&buildUlimits); err != nil {
392
-			return errf(err)
393
-		}
394
-		buildConfig.Ulimits = buildUlimits
395
-	}
396
-
397
-	var buildArgs = map[string]string{}
398
-	buildArgsJSON := r.FormValue("buildargs")
399
-	if buildArgsJSON != "" {
400
-		if err := json.NewDecoder(strings.NewReader(buildArgsJSON)).Decode(&buildArgs); err != nil {
401
-			return errf(err)
402
-		}
403
-		buildConfig.BuildArgs = buildArgs
404
-	}
405
-
406
-	remoteURL := r.FormValue("remote")
407
-
408
-	// Currently, only used if context is from a remote url.
409
-	// Look at code in DetectContextFromRemoteURL for more information.
410
-	createProgressReader := func(in io.ReadCloser) io.ReadCloser {
411
-		progressOutput := sf.NewProgressOutput(output, true)
412
-		return progress.NewProgressReader(in, progressOutput, r.ContentLength, "Downloading context", remoteURL)
413
-	}
414
-
415
-	var (
416
-		context        builder.ModifiableContext
417
-		dockerfileName string
418
-	)
419
-	context, dockerfileName, err = daemonbuilder.DetectContextFromRemoteURL(r.Body, remoteURL, createProgressReader)
420
-	if err != nil {
421
-		return errf(err)
422
-	}
423
-	defer func() {
424
-		if err := context.Close(); err != nil {
425
-			logrus.Debugf("[BUILDER] failed to remove temporary context: %v", err)
426
-		}
427
-	}()
428
-
429
-	uidMaps, gidMaps := s.daemon.GetUIDGIDMaps()
430
-	defaultArchiver := &archive.Archiver{
431
-		Untar:   chrootarchive.Untar,
432
-		UIDMaps: uidMaps,
433
-		GIDMaps: gidMaps,
434
-	}
435
-	docker := &daemonbuilder.Docker{
436
-		Daemon:      s.daemon,
437
-		OutOld:      output,
438
-		AuthConfigs: authConfigs,
439
-		Archiver:    defaultArchiver,
440
-	}
441
-
442
-	b, err := dockerfile.NewBuilder(buildConfig, docker, builder.DockerIgnoreContext{ModifiableContext: context}, nil)
443
-	if err != nil {
444
-		return errf(err)
445
-	}
446
-	b.Stdout = &streamformatter.StdoutFormatter{Writer: output, StreamFormatter: sf}
447
-	b.Stderr = &streamformatter.StderrFormatter{Writer: output, StreamFormatter: sf}
448
-
449
-	if closeNotifier, ok := w.(http.CloseNotifier); ok {
450
-		finished := make(chan struct{})
451
-		defer close(finished)
452
-		go func() {
453
-			select {
454
-			case <-finished:
455
-			case <-closeNotifier.CloseNotify():
456
-				logrus.Infof("Client disconnected, cancelling job: build")
457
-				b.Cancel()
458
-			}
459
-		}()
460
-	}
461
-
462
-	if len(dockerfileName) > 0 {
463
-		b.DockerfileName = dockerfileName
464
-	}
465
-
466
-	imgID, err := b.Build()
467
-	if err != nil {
468
-		return errf(err)
469
-	}
470
-
471
-	for _, rt := range repoAndTags {
472
-		if err := s.daemon.TagImage(rt, imgID); err != nil {
473
-			return errf(err)
474
-		}
475
-	}
476
-
477
-	return nil
478
-}
479
-
480
-// sanitizeRepoAndTags parses the raw "t" parameter received from the client
481
-// to a slice of repoAndTag.
482
-// It also validates each repoName and tag.
483
-func sanitizeRepoAndTags(names []string) ([]reference.Named, error) {
484
-	var (
485
-		repoAndTags []reference.Named
486
-		// This map is used for deduplicating the "-t" parameter.
487
-		uniqNames = make(map[string]struct{})
488
-	)
489
-	for _, repo := range names {
490
-		if repo == "" {
491
-			continue
492
-		}
493
-
494
-		ref, err := reference.ParseNamed(repo)
495
-		if err != nil {
496
-			return nil, err
497
-		}
498
-		ref = reference.WithDefaultTag(ref)
499
-
500
-		if _, isCanonical := ref.(reference.Canonical); isCanonical {
501
-			return nil, errors.New("build tag cannot contain a digest")
502
-		}
503
-
504
-		nameWithTag := ref.String()
505
-
506
-		if _, exists := uniqNames[nameWithTag]; !exists {
507
-			uniqNames[nameWithTag] = struct{}{}
508
-			repoAndTags = append(repoAndTags, ref)
509
-		}
510
-	}
511
-	return repoAndTags, nil
512
-}
513
-
514 309
 func (s *router) getImagesJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
515 310
 	if err := httputils.ParseForm(r); err != nil {
516 311
 		return err
... ...
@@ -97,7 +97,6 @@ func (r *router) initRoutes() {
97 97
 		NewGetRoute("/images/{name:.*}/json", r.getImagesByName),
98 98
 		// POST
99 99
 		NewPostRoute("/commit", r.postCommit),
100
-		NewPostRoute("/build", r.postBuild),
101 100
 		NewPostRoute("/images/create", r.postImagesCreate),
102 101
 		NewPostRoute("/images/load", r.postImagesLoad),
103 102
 		NewPostRoute("/images/{name:.*}/push", r.postImagesPush),
... ...
@@ -10,6 +10,7 @@ import (
10 10
 	"github.com/Sirupsen/logrus"
11 11
 	"github.com/docker/docker/api/server/httputils"
12 12
 	"github.com/docker/docker/api/server/router"
13
+	"github.com/docker/docker/api/server/router/build"
13 14
 	"github.com/docker/docker/api/server/router/container"
14 15
 	"github.com/docker/docker/api/server/router/local"
15 16
 	"github.com/docker/docker/api/server/router/network"
... ...
@@ -177,6 +178,7 @@ func (s *Server) InitRouters(d *daemon.Daemon) {
177 177
 	s.addRouter(network.NewRouter(d))
178 178
 	s.addRouter(system.NewRouter(d))
179 179
 	s.addRouter(volume.NewRouter(d))
180
+	s.addRouter(build.NewRouter(d))
180 181
 }
181 182
 
182 183
 // addRouter adds a new router to the server.
... ...
@@ -15,17 +15,6 @@ import (
15 15
 	"github.com/docker/docker/runconfig"
16 16
 )
17 17
 
18
-// Builder abstracts a Docker builder whose only purpose is to build a Docker image referenced by an imageID.
19
-type Builder interface {
20
-	// Build builds a Docker image referenced by an imageID string.
21
-	//
22
-	// Note: Tagging an image should not be done by a Builder, it should instead be done
23
-	// by the caller.
24
-	//
25
-	// TODO: make this return a reference instead of string
26
-	Build() (imageID string)
27
-}
28
-
29 18
 // Context represents a file system tree.
30 19
 type Context interface {
31 20
 	// Close allows to signal that the filesystem tree won't be used anymore.
... ...
@@ -70,7 +70,7 @@ type Config struct {
70 70
 }
71 71
 
72 72
 // Builder is a Dockerfile builder
73
-// It implements the builder.Builder interface.
73
+// It implements the builder.Backend interface.
74 74
 type Builder struct {
75 75
 	*Config
76 76