Signed-off-by: Anusha Ragunathan <anusha@docker.com>
| 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. |