Skip to content
This repository was archived by the owner on Feb 8, 2021. It is now read-only.

Commit 92605b8

Browse files
committed
Merge pull request #18761 from anusha-ragunathan/add-build-routes
Create build router separate from image router.
2 parents 6ee7c94 + f8dc044 commit 92605b8

File tree

8 files changed

+287
-227
lines changed

8 files changed

+287
-227
lines changed

api/server/router/build/backend.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package build
2+
3+
// Backend abstracts an image builder whose only purpose is to build an image referenced by an imageID.
4+
type Backend interface {
5+
// Build builds a Docker image referenced by an imageID string.
6+
//
7+
// Note: Tagging an image should not be done by a Builder, it should instead be done
8+
// by the caller.
9+
//
10+
// TODO: make this return a reference instead of string
11+
Build() (imageID string)
12+
}

api/server/router/build/build.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package build
2+
3+
import (
4+
"github.com/docker/docker/api/server/router"
5+
"github.com/docker/docker/api/server/router/local"
6+
"github.com/docker/docker/daemon"
7+
)
8+
9+
// buildRouter is a router to talk with the build controller
10+
type buildRouter struct {
11+
backend *daemon.Daemon
12+
routes []router.Route
13+
}
14+
15+
// NewRouter initializes a new build router
16+
func NewRouter(b *daemon.Daemon) router.Router {
17+
r := &buildRouter{
18+
backend: b,
19+
}
20+
r.initRoutes()
21+
return r
22+
}
23+
24+
// Routes returns the available routers to the build controller
25+
func (r *buildRouter) Routes() []router.Route {
26+
return r.routes
27+
}
28+
29+
func (r *buildRouter) initRoutes() {
30+
r.routes = []router.Route{
31+
local.NewPostRoute("/build", r.postBuild),
32+
}
33+
}
Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
package build
2+
3+
import (
4+
"encoding/base64"
5+
"encoding/json"
6+
"errors"
7+
"fmt"
8+
"io"
9+
"net/http"
10+
"strconv"
11+
"strings"
12+
13+
"github.com/Sirupsen/logrus"
14+
"github.com/docker/docker/api/server/httputils"
15+
"github.com/docker/docker/api/types"
16+
"github.com/docker/docker/builder"
17+
"github.com/docker/docker/builder/dockerfile"
18+
"github.com/docker/docker/daemon/daemonbuilder"
19+
"github.com/docker/docker/pkg/archive"
20+
"github.com/docker/docker/pkg/chrootarchive"
21+
"github.com/docker/docker/pkg/ioutils"
22+
"github.com/docker/docker/pkg/progress"
23+
"github.com/docker/docker/pkg/streamformatter"
24+
"github.com/docker/docker/pkg/ulimit"
25+
"github.com/docker/docker/reference"
26+
"github.com/docker/docker/runconfig"
27+
"github.com/docker/docker/utils"
28+
"golang.org/x/net/context"
29+
)
30+
31+
// sanitizeRepoAndTags parses the raw "t" parameter received from the client
32+
// to a slice of repoAndTag.
33+
// It also validates each repoName and tag.
34+
func sanitizeRepoAndTags(names []string) ([]reference.Named, error) {
35+
var (
36+
repoAndTags []reference.Named
37+
// This map is used for deduplicating the "-t" parameter.
38+
uniqNames = make(map[string]struct{})
39+
)
40+
for _, repo := range names {
41+
if repo == "" {
42+
continue
43+
}
44+
45+
ref, err := reference.ParseNamed(repo)
46+
if err != nil {
47+
return nil, err
48+
}
49+
50+
ref = reference.WithDefaultTag(ref)
51+
52+
if _, isCanonical := ref.(reference.Canonical); isCanonical {
53+
return nil, errors.New("build tag cannot contain a digest")
54+
}
55+
56+
if _, isTagged := ref.(reference.NamedTagged); !isTagged {
57+
ref, err = reference.WithTag(ref, reference.DefaultTag)
58+
}
59+
60+
nameWithTag := ref.String()
61+
62+
if _, exists := uniqNames[nameWithTag]; !exists {
63+
uniqNames[nameWithTag] = struct{}{}
64+
repoAndTags = append(repoAndTags, ref)
65+
}
66+
}
67+
return repoAndTags, nil
68+
}
69+
70+
func (br *buildRouter) postBuild(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
71+
var (
72+
authConfigs = map[string]types.AuthConfig{}
73+
authConfigsEncoded = r.Header.Get("X-Registry-Config")
74+
buildConfig = &dockerfile.Config{}
75+
)
76+
77+
if authConfigsEncoded != "" {
78+
authConfigsJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authConfigsEncoded))
79+
if err := json.NewDecoder(authConfigsJSON).Decode(&authConfigs); err != nil {
80+
// for a pull it is not an error if no auth was given
81+
// to increase compatibility with the existing api it is defaulting
82+
// to be empty.
83+
}
84+
}
85+
86+
w.Header().Set("Content-Type", "application/json")
87+
88+
version := httputils.VersionFromContext(ctx)
89+
output := ioutils.NewWriteFlusher(w)
90+
defer output.Close()
91+
sf := streamformatter.NewJSONStreamFormatter()
92+
errf := func(err error) error {
93+
// Do not write the error in the http output if it's still empty.
94+
// This prevents from writing a 200(OK) when there is an internal error.
95+
if !output.Flushed() {
96+
return err
97+
}
98+
_, err = w.Write(sf.FormatError(errors.New(utils.GetErrorMessage(err))))
99+
if err != nil {
100+
logrus.Warnf("could not write error response: %v", err)
101+
}
102+
return nil
103+
}
104+
105+
if httputils.BoolValue(r, "forcerm") && version.GreaterThanOrEqualTo("1.12") {
106+
buildConfig.Remove = true
107+
} else if r.FormValue("rm") == "" && version.GreaterThanOrEqualTo("1.12") {
108+
buildConfig.Remove = true
109+
} else {
110+
buildConfig.Remove = httputils.BoolValue(r, "rm")
111+
}
112+
if httputils.BoolValue(r, "pull") && version.GreaterThanOrEqualTo("1.16") {
113+
buildConfig.Pull = true
114+
}
115+
116+
repoAndTags, err := sanitizeRepoAndTags(r.Form["t"])
117+
if err != nil {
118+
return errf(err)
119+
}
120+
121+
buildConfig.DockerfileName = r.FormValue("dockerfile")
122+
buildConfig.Verbose = !httputils.BoolValue(r, "q")
123+
buildConfig.UseCache = !httputils.BoolValue(r, "nocache")
124+
buildConfig.ForceRemove = httputils.BoolValue(r, "forcerm")
125+
buildConfig.MemorySwap = httputils.Int64ValueOrZero(r, "memswap")
126+
buildConfig.Memory = httputils.Int64ValueOrZero(r, "memory")
127+
buildConfig.CPUShares = httputils.Int64ValueOrZero(r, "cpushares")
128+
buildConfig.CPUPeriod = httputils.Int64ValueOrZero(r, "cpuperiod")
129+
buildConfig.CPUQuota = httputils.Int64ValueOrZero(r, "cpuquota")
130+
buildConfig.CPUSetCpus = r.FormValue("cpusetcpus")
131+
buildConfig.CPUSetMems = r.FormValue("cpusetmems")
132+
buildConfig.CgroupParent = r.FormValue("cgroupparent")
133+
134+
if r.Form.Get("shmsize") != "" {
135+
shmSize, err := strconv.ParseInt(r.Form.Get("shmsize"), 10, 64)
136+
if err != nil {
137+
return errf(err)
138+
}
139+
buildConfig.ShmSize = &shmSize
140+
}
141+
142+
if i := runconfig.IsolationLevel(r.FormValue("isolation")); i != "" {
143+
if !runconfig.IsolationLevel.IsValid(i) {
144+
return errf(fmt.Errorf("Unsupported isolation: %q", i))
145+
}
146+
buildConfig.Isolation = i
147+
}
148+
149+
var buildUlimits = []*ulimit.Ulimit{}
150+
ulimitsJSON := r.FormValue("ulimits")
151+
if ulimitsJSON != "" {
152+
if err := json.NewDecoder(strings.NewReader(ulimitsJSON)).Decode(&buildUlimits); err != nil {
153+
return errf(err)
154+
}
155+
buildConfig.Ulimits = buildUlimits
156+
}
157+
158+
var buildArgs = map[string]string{}
159+
buildArgsJSON := r.FormValue("buildargs")
160+
if buildArgsJSON != "" {
161+
if err := json.NewDecoder(strings.NewReader(buildArgsJSON)).Decode(&buildArgs); err != nil {
162+
return errf(err)
163+
}
164+
buildConfig.BuildArgs = buildArgs
165+
}
166+
167+
remoteURL := r.FormValue("remote")
168+
169+
// Currently, only used if context is from a remote url.
170+
// Look at code in DetectContextFromRemoteURL for more information.
171+
createProgressReader := func(in io.ReadCloser) io.ReadCloser {
172+
progressOutput := sf.NewProgressOutput(output, true)
173+
return progress.NewProgressReader(in, progressOutput, r.ContentLength, "Downloading context", remoteURL)
174+
}
175+
176+
var (
177+
context builder.ModifiableContext
178+
dockerfileName string
179+
)
180+
context, dockerfileName, err = daemonbuilder.DetectContextFromRemoteURL(r.Body, remoteURL, createProgressReader)
181+
if err != nil {
182+
return errf(err)
183+
}
184+
defer func() {
185+
if err := context.Close(); err != nil {
186+
logrus.Debugf("[BUILDER] failed to remove temporary context: %v", err)
187+
}
188+
}()
189+
190+
uidMaps, gidMaps := br.backend.GetUIDGIDMaps()
191+
defaultArchiver := &archive.Archiver{
192+
Untar: chrootarchive.Untar,
193+
UIDMaps: uidMaps,
194+
GIDMaps: gidMaps,
195+
}
196+
docker := &daemonbuilder.Docker{
197+
Daemon: br.backend,
198+
OutOld: output,
199+
AuthConfigs: authConfigs,
200+
Archiver: defaultArchiver,
201+
}
202+
203+
b, err := dockerfile.NewBuilder(buildConfig, docker, builder.DockerIgnoreContext{ModifiableContext: context}, nil)
204+
if err != nil {
205+
return errf(err)
206+
}
207+
b.Stdout = &streamformatter.StdoutFormatter{Writer: output, StreamFormatter: sf}
208+
b.Stderr = &streamformatter.StderrFormatter{Writer: output, StreamFormatter: sf}
209+
210+
if closeNotifier, ok := w.(http.CloseNotifier); ok {
211+
finished := make(chan struct{})
212+
defer close(finished)
213+
go func() {
214+
select {
215+
case <-finished:
216+
case <-closeNotifier.CloseNotify():
217+
logrus.Infof("Client disconnected, cancelling job: build")
218+
b.Cancel()
219+
}
220+
}()
221+
}
222+
223+
if len(dockerfileName) > 0 {
224+
b.DockerfileName = dockerfileName
225+
}
226+
227+
imgID, err := b.Build()
228+
if err != nil {
229+
return errf(err)
230+
}
231+
232+
for _, rt := range repoAndTags {
233+
if err := br.backend.TagImage(rt, imgID); err != nil {
234+
return errf(err)
235+
}
236+
}
237+
238+
return nil
239+
}

0 commit comments

Comments
 (0)