mirror of https://github.com/docker/docs.git
Create build router separate from image router.
Signed-off-by: Anusha Ragunathan <anusha@docker.com>
This commit is contained in:
parent
6bf8844f11
commit
f8dc044aec
|
@ -0,0 +1,12 @@
|
||||||
|
package build
|
||||||
|
|
||||||
|
// Backend abstracts an image builder whose only purpose is to build an image referenced by an imageID.
|
||||||
|
type Backend interface {
|
||||||
|
// Build builds a Docker image referenced by an imageID string.
|
||||||
|
//
|
||||||
|
// Note: Tagging an image should not be done by a Builder, it should instead be done
|
||||||
|
// by the caller.
|
||||||
|
//
|
||||||
|
// TODO: make this return a reference instead of string
|
||||||
|
Build() (imageID string)
|
||||||
|
}
|
|
@ -0,0 +1,33 @@
|
||||||
|
package build
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/docker/docker/api/server/router"
|
||||||
|
"github.com/docker/docker/api/server/router/local"
|
||||||
|
"github.com/docker/docker/daemon"
|
||||||
|
)
|
||||||
|
|
||||||
|
// buildRouter is a router to talk with the build controller
|
||||||
|
type buildRouter struct {
|
||||||
|
backend *daemon.Daemon
|
||||||
|
routes []router.Route
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRouter initializes a new build router
|
||||||
|
func NewRouter(b *daemon.Daemon) router.Router {
|
||||||
|
r := &buildRouter{
|
||||||
|
backend: b,
|
||||||
|
}
|
||||||
|
r.initRoutes()
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
|
||||||
|
// Routes returns the available routers to the build controller
|
||||||
|
func (r *buildRouter) Routes() []router.Route {
|
||||||
|
return r.routes
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *buildRouter) initRoutes() {
|
||||||
|
r.routes = []router.Route{
|
||||||
|
local.NewPostRoute("/build", r.postBuild),
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,239 @@
|
||||||
|
package build
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/Sirupsen/logrus"
|
||||||
|
"github.com/docker/docker/api/server/httputils"
|
||||||
|
"github.com/docker/docker/api/types"
|
||||||
|
"github.com/docker/docker/builder"
|
||||||
|
"github.com/docker/docker/builder/dockerfile"
|
||||||
|
"github.com/docker/docker/daemon/daemonbuilder"
|
||||||
|
"github.com/docker/docker/pkg/archive"
|
||||||
|
"github.com/docker/docker/pkg/chrootarchive"
|
||||||
|
"github.com/docker/docker/pkg/ioutils"
|
||||||
|
"github.com/docker/docker/pkg/progress"
|
||||||
|
"github.com/docker/docker/pkg/streamformatter"
|
||||||
|
"github.com/docker/docker/pkg/ulimit"
|
||||||
|
"github.com/docker/docker/reference"
|
||||||
|
"github.com/docker/docker/runconfig"
|
||||||
|
"github.com/docker/docker/utils"
|
||||||
|
"golang.org/x/net/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// sanitizeRepoAndTags parses the raw "t" parameter received from the client
|
||||||
|
// to a slice of repoAndTag.
|
||||||
|
// It also validates each repoName and tag.
|
||||||
|
func sanitizeRepoAndTags(names []string) ([]reference.Named, error) {
|
||||||
|
var (
|
||||||
|
repoAndTags []reference.Named
|
||||||
|
// This map is used for deduplicating the "-t" parameter.
|
||||||
|
uniqNames = make(map[string]struct{})
|
||||||
|
)
|
||||||
|
for _, repo := range names {
|
||||||
|
if repo == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
ref, err := reference.ParseNamed(repo)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
ref = reference.WithDefaultTag(ref)
|
||||||
|
|
||||||
|
if _, isCanonical := ref.(reference.Canonical); isCanonical {
|
||||||
|
return nil, errors.New("build tag cannot contain a digest")
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, isTagged := ref.(reference.NamedTagged); !isTagged {
|
||||||
|
ref, err = reference.WithTag(ref, reference.DefaultTag)
|
||||||
|
}
|
||||||
|
|
||||||
|
nameWithTag := ref.String()
|
||||||
|
|
||||||
|
if _, exists := uniqNames[nameWithTag]; !exists {
|
||||||
|
uniqNames[nameWithTag] = struct{}{}
|
||||||
|
repoAndTags = append(repoAndTags, ref)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return repoAndTags, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (br *buildRouter) postBuild(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
|
var (
|
||||||
|
authConfigs = map[string]types.AuthConfig{}
|
||||||
|
authConfigsEncoded = r.Header.Get("X-Registry-Config")
|
||||||
|
buildConfig = &dockerfile.Config{}
|
||||||
|
)
|
||||||
|
|
||||||
|
if authConfigsEncoded != "" {
|
||||||
|
authConfigsJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authConfigsEncoded))
|
||||||
|
if err := json.NewDecoder(authConfigsJSON).Decode(&authConfigs); err != nil {
|
||||||
|
// for a pull it is not an error if no auth was given
|
||||||
|
// to increase compatibility with the existing api it is defaulting
|
||||||
|
// to be empty.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
|
||||||
|
version := httputils.VersionFromContext(ctx)
|
||||||
|
output := ioutils.NewWriteFlusher(w)
|
||||||
|
defer output.Close()
|
||||||
|
sf := streamformatter.NewJSONStreamFormatter()
|
||||||
|
errf := func(err error) error {
|
||||||
|
// Do not write the error in the http output if it's still empty.
|
||||||
|
// This prevents from writing a 200(OK) when there is an internal error.
|
||||||
|
if !output.Flushed() {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = w.Write(sf.FormatError(errors.New(utils.GetErrorMessage(err))))
|
||||||
|
if err != nil {
|
||||||
|
logrus.Warnf("could not write error response: %v", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if httputils.BoolValue(r, "forcerm") && version.GreaterThanOrEqualTo("1.12") {
|
||||||
|
buildConfig.Remove = true
|
||||||
|
} else if r.FormValue("rm") == "" && version.GreaterThanOrEqualTo("1.12") {
|
||||||
|
buildConfig.Remove = true
|
||||||
|
} else {
|
||||||
|
buildConfig.Remove = httputils.BoolValue(r, "rm")
|
||||||
|
}
|
||||||
|
if httputils.BoolValue(r, "pull") && version.GreaterThanOrEqualTo("1.16") {
|
||||||
|
buildConfig.Pull = true
|
||||||
|
}
|
||||||
|
|
||||||
|
repoAndTags, err := sanitizeRepoAndTags(r.Form["t"])
|
||||||
|
if err != nil {
|
||||||
|
return errf(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
buildConfig.DockerfileName = r.FormValue("dockerfile")
|
||||||
|
buildConfig.Verbose = !httputils.BoolValue(r, "q")
|
||||||
|
buildConfig.UseCache = !httputils.BoolValue(r, "nocache")
|
||||||
|
buildConfig.ForceRemove = httputils.BoolValue(r, "forcerm")
|
||||||
|
buildConfig.MemorySwap = httputils.Int64ValueOrZero(r, "memswap")
|
||||||
|
buildConfig.Memory = httputils.Int64ValueOrZero(r, "memory")
|
||||||
|
buildConfig.CPUShares = httputils.Int64ValueOrZero(r, "cpushares")
|
||||||
|
buildConfig.CPUPeriod = httputils.Int64ValueOrZero(r, "cpuperiod")
|
||||||
|
buildConfig.CPUQuota = httputils.Int64ValueOrZero(r, "cpuquota")
|
||||||
|
buildConfig.CPUSetCpus = r.FormValue("cpusetcpus")
|
||||||
|
buildConfig.CPUSetMems = r.FormValue("cpusetmems")
|
||||||
|
buildConfig.CgroupParent = r.FormValue("cgroupparent")
|
||||||
|
|
||||||
|
if r.Form.Get("shmsize") != "" {
|
||||||
|
shmSize, err := strconv.ParseInt(r.Form.Get("shmsize"), 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return errf(err)
|
||||||
|
}
|
||||||
|
buildConfig.ShmSize = &shmSize
|
||||||
|
}
|
||||||
|
|
||||||
|
if i := runconfig.IsolationLevel(r.FormValue("isolation")); i != "" {
|
||||||
|
if !runconfig.IsolationLevel.IsValid(i) {
|
||||||
|
return errf(fmt.Errorf("Unsupported isolation: %q", i))
|
||||||
|
}
|
||||||
|
buildConfig.Isolation = i
|
||||||
|
}
|
||||||
|
|
||||||
|
var buildUlimits = []*ulimit.Ulimit{}
|
||||||
|
ulimitsJSON := r.FormValue("ulimits")
|
||||||
|
if ulimitsJSON != "" {
|
||||||
|
if err := json.NewDecoder(strings.NewReader(ulimitsJSON)).Decode(&buildUlimits); err != nil {
|
||||||
|
return errf(err)
|
||||||
|
}
|
||||||
|
buildConfig.Ulimits = buildUlimits
|
||||||
|
}
|
||||||
|
|
||||||
|
var buildArgs = map[string]string{}
|
||||||
|
buildArgsJSON := r.FormValue("buildargs")
|
||||||
|
if buildArgsJSON != "" {
|
||||||
|
if err := json.NewDecoder(strings.NewReader(buildArgsJSON)).Decode(&buildArgs); err != nil {
|
||||||
|
return errf(err)
|
||||||
|
}
|
||||||
|
buildConfig.BuildArgs = buildArgs
|
||||||
|
}
|
||||||
|
|
||||||
|
remoteURL := r.FormValue("remote")
|
||||||
|
|
||||||
|
// Currently, only used if context is from a remote url.
|
||||||
|
// Look at code in DetectContextFromRemoteURL for more information.
|
||||||
|
createProgressReader := func(in io.ReadCloser) io.ReadCloser {
|
||||||
|
progressOutput := sf.NewProgressOutput(output, true)
|
||||||
|
return progress.NewProgressReader(in, progressOutput, r.ContentLength, "Downloading context", remoteURL)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
context builder.ModifiableContext
|
||||||
|
dockerfileName string
|
||||||
|
)
|
||||||
|
context, dockerfileName, err = daemonbuilder.DetectContextFromRemoteURL(r.Body, remoteURL, createProgressReader)
|
||||||
|
if err != nil {
|
||||||
|
return errf(err)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if err := context.Close(); err != nil {
|
||||||
|
logrus.Debugf("[BUILDER] failed to remove temporary context: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
uidMaps, gidMaps := br.backend.GetUIDGIDMaps()
|
||||||
|
defaultArchiver := &archive.Archiver{
|
||||||
|
Untar: chrootarchive.Untar,
|
||||||
|
UIDMaps: uidMaps,
|
||||||
|
GIDMaps: gidMaps,
|
||||||
|
}
|
||||||
|
docker := &daemonbuilder.Docker{
|
||||||
|
Daemon: br.backend,
|
||||||
|
OutOld: output,
|
||||||
|
AuthConfigs: authConfigs,
|
||||||
|
Archiver: defaultArchiver,
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err := dockerfile.NewBuilder(buildConfig, docker, builder.DockerIgnoreContext{ModifiableContext: context}, nil)
|
||||||
|
if err != nil {
|
||||||
|
return errf(err)
|
||||||
|
}
|
||||||
|
b.Stdout = &streamformatter.StdoutFormatter{Writer: output, StreamFormatter: sf}
|
||||||
|
b.Stderr = &streamformatter.StderrFormatter{Writer: output, StreamFormatter: sf}
|
||||||
|
|
||||||
|
if closeNotifier, ok := w.(http.CloseNotifier); ok {
|
||||||
|
finished := make(chan struct{})
|
||||||
|
defer close(finished)
|
||||||
|
go func() {
|
||||||
|
select {
|
||||||
|
case <-finished:
|
||||||
|
case <-closeNotifier.CloseNotify():
|
||||||
|
logrus.Infof("Client disconnected, cancelling job: build")
|
||||||
|
b.Cancel()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(dockerfileName) > 0 {
|
||||||
|
b.DockerfileName = dockerfileName
|
||||||
|
}
|
||||||
|
|
||||||
|
imgID, err := b.Build()
|
||||||
|
if err != nil {
|
||||||
|
return errf(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, rt := range repoAndTags {
|
||||||
|
if err := br.backend.TagImage(rt, imgID); err != nil {
|
||||||
|
return errf(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -7,26 +7,17 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/Sirupsen/logrus"
|
|
||||||
"github.com/docker/distribution/digest"
|
"github.com/docker/distribution/digest"
|
||||||
"github.com/docker/docker/api/server/httputils"
|
"github.com/docker/docker/api/server/httputils"
|
||||||
"github.com/docker/docker/api/types"
|
"github.com/docker/docker/api/types"
|
||||||
"github.com/docker/docker/builder"
|
|
||||||
"github.com/docker/docker/builder/dockerfile"
|
"github.com/docker/docker/builder/dockerfile"
|
||||||
"github.com/docker/docker/daemon/daemonbuilder"
|
|
||||||
derr "github.com/docker/docker/errors"
|
derr "github.com/docker/docker/errors"
|
||||||
"github.com/docker/docker/pkg/archive"
|
|
||||||
"github.com/docker/docker/pkg/chrootarchive"
|
|
||||||
"github.com/docker/docker/pkg/ioutils"
|
"github.com/docker/docker/pkg/ioutils"
|
||||||
"github.com/docker/docker/pkg/progress"
|
|
||||||
"github.com/docker/docker/pkg/streamformatter"
|
"github.com/docker/docker/pkg/streamformatter"
|
||||||
"github.com/docker/docker/pkg/ulimit"
|
|
||||||
"github.com/docker/docker/reference"
|
"github.com/docker/docker/reference"
|
||||||
"github.com/docker/docker/runconfig"
|
"github.com/docker/docker/runconfig"
|
||||||
"github.com/docker/docker/utils"
|
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -306,211 +297,6 @@ func (s *router) getImagesByName(ctx context.Context, w http.ResponseWriter, r *
|
||||||
return httputils.WriteJSON(w, http.StatusOK, imageInspect)
|
return httputils.WriteJSON(w, http.StatusOK, imageInspect)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *router) postBuild(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
|
||||||
var (
|
|
||||||
authConfigs = map[string]types.AuthConfig{}
|
|
||||||
authConfigsEncoded = r.Header.Get("X-Registry-Config")
|
|
||||||
buildConfig = &dockerfile.Config{}
|
|
||||||
)
|
|
||||||
|
|
||||||
if authConfigsEncoded != "" {
|
|
||||||
authConfigsJSON := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authConfigsEncoded))
|
|
||||||
if err := json.NewDecoder(authConfigsJSON).Decode(&authConfigs); err != nil {
|
|
||||||
// for a pull it is not an error if no auth was given
|
|
||||||
// to increase compatibility with the existing api it is defaulting
|
|
||||||
// to be empty.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
w.Header().Set("Content-Type", "application/json")
|
|
||||||
|
|
||||||
version := httputils.VersionFromContext(ctx)
|
|
||||||
output := ioutils.NewWriteFlusher(w)
|
|
||||||
defer output.Close()
|
|
||||||
sf := streamformatter.NewJSONStreamFormatter()
|
|
||||||
errf := func(err error) error {
|
|
||||||
// Do not write the error in the http output if it's still empty.
|
|
||||||
// This prevents from writing a 200(OK) when there is an internal error.
|
|
||||||
if !output.Flushed() {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
_, err = w.Write(sf.FormatError(errors.New(utils.GetErrorMessage(err))))
|
|
||||||
if err != nil {
|
|
||||||
logrus.Warnf("could not write error response: %v", err)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
if httputils.BoolValue(r, "forcerm") && version.GreaterThanOrEqualTo("1.12") {
|
|
||||||
buildConfig.Remove = true
|
|
||||||
} else if r.FormValue("rm") == "" && version.GreaterThanOrEqualTo("1.12") {
|
|
||||||
buildConfig.Remove = true
|
|
||||||
} else {
|
|
||||||
buildConfig.Remove = httputils.BoolValue(r, "rm")
|
|
||||||
}
|
|
||||||
if httputils.BoolValue(r, "pull") && version.GreaterThanOrEqualTo("1.16") {
|
|
||||||
buildConfig.Pull = true
|
|
||||||
}
|
|
||||||
|
|
||||||
repoAndTags, err := sanitizeRepoAndTags(r.Form["t"])
|
|
||||||
if err != nil {
|
|
||||||
return errf(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
buildConfig.DockerfileName = r.FormValue("dockerfile")
|
|
||||||
buildConfig.Verbose = !httputils.BoolValue(r, "q")
|
|
||||||
buildConfig.UseCache = !httputils.BoolValue(r, "nocache")
|
|
||||||
buildConfig.ForceRemove = httputils.BoolValue(r, "forcerm")
|
|
||||||
buildConfig.MemorySwap = httputils.Int64ValueOrZero(r, "memswap")
|
|
||||||
buildConfig.Memory = httputils.Int64ValueOrZero(r, "memory")
|
|
||||||
buildConfig.CPUShares = httputils.Int64ValueOrZero(r, "cpushares")
|
|
||||||
buildConfig.CPUPeriod = httputils.Int64ValueOrZero(r, "cpuperiod")
|
|
||||||
buildConfig.CPUQuota = httputils.Int64ValueOrZero(r, "cpuquota")
|
|
||||||
buildConfig.CPUSetCpus = r.FormValue("cpusetcpus")
|
|
||||||
buildConfig.CPUSetMems = r.FormValue("cpusetmems")
|
|
||||||
buildConfig.CgroupParent = r.FormValue("cgroupparent")
|
|
||||||
|
|
||||||
if r.Form.Get("shmsize") != "" {
|
|
||||||
shmSize, err := strconv.ParseInt(r.Form.Get("shmsize"), 10, 64)
|
|
||||||
if err != nil {
|
|
||||||
return errf(err)
|
|
||||||
}
|
|
||||||
buildConfig.ShmSize = &shmSize
|
|
||||||
}
|
|
||||||
|
|
||||||
if i := runconfig.IsolationLevel(r.FormValue("isolation")); i != "" {
|
|
||||||
if !runconfig.IsolationLevel.IsValid(i) {
|
|
||||||
return errf(fmt.Errorf("Unsupported isolation: %q", i))
|
|
||||||
}
|
|
||||||
buildConfig.Isolation = i
|
|
||||||
}
|
|
||||||
|
|
||||||
var buildUlimits = []*ulimit.Ulimit{}
|
|
||||||
ulimitsJSON := r.FormValue("ulimits")
|
|
||||||
if ulimitsJSON != "" {
|
|
||||||
if err := json.NewDecoder(strings.NewReader(ulimitsJSON)).Decode(&buildUlimits); err != nil {
|
|
||||||
return errf(err)
|
|
||||||
}
|
|
||||||
buildConfig.Ulimits = buildUlimits
|
|
||||||
}
|
|
||||||
|
|
||||||
var buildArgs = map[string]string{}
|
|
||||||
buildArgsJSON := r.FormValue("buildargs")
|
|
||||||
if buildArgsJSON != "" {
|
|
||||||
if err := json.NewDecoder(strings.NewReader(buildArgsJSON)).Decode(&buildArgs); err != nil {
|
|
||||||
return errf(err)
|
|
||||||
}
|
|
||||||
buildConfig.BuildArgs = buildArgs
|
|
||||||
}
|
|
||||||
|
|
||||||
remoteURL := r.FormValue("remote")
|
|
||||||
|
|
||||||
// Currently, only used if context is from a remote url.
|
|
||||||
// Look at code in DetectContextFromRemoteURL for more information.
|
|
||||||
createProgressReader := func(in io.ReadCloser) io.ReadCloser {
|
|
||||||
progressOutput := sf.NewProgressOutput(output, true)
|
|
||||||
return progress.NewProgressReader(in, progressOutput, r.ContentLength, "Downloading context", remoteURL)
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
context builder.ModifiableContext
|
|
||||||
dockerfileName string
|
|
||||||
)
|
|
||||||
context, dockerfileName, err = daemonbuilder.DetectContextFromRemoteURL(r.Body, remoteURL, createProgressReader)
|
|
||||||
if err != nil {
|
|
||||||
return errf(err)
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if err := context.Close(); err != nil {
|
|
||||||
logrus.Debugf("[BUILDER] failed to remove temporary context: %v", err)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
uidMaps, gidMaps := s.daemon.GetUIDGIDMaps()
|
|
||||||
defaultArchiver := &archive.Archiver{
|
|
||||||
Untar: chrootarchive.Untar,
|
|
||||||
UIDMaps: uidMaps,
|
|
||||||
GIDMaps: gidMaps,
|
|
||||||
}
|
|
||||||
docker := &daemonbuilder.Docker{
|
|
||||||
Daemon: s.daemon,
|
|
||||||
OutOld: output,
|
|
||||||
AuthConfigs: authConfigs,
|
|
||||||
Archiver: defaultArchiver,
|
|
||||||
}
|
|
||||||
|
|
||||||
b, err := dockerfile.NewBuilder(buildConfig, docker, builder.DockerIgnoreContext{ModifiableContext: context}, nil)
|
|
||||||
if err != nil {
|
|
||||||
return errf(err)
|
|
||||||
}
|
|
||||||
b.Stdout = &streamformatter.StdoutFormatter{Writer: output, StreamFormatter: sf}
|
|
||||||
b.Stderr = &streamformatter.StderrFormatter{Writer: output, StreamFormatter: sf}
|
|
||||||
|
|
||||||
if closeNotifier, ok := w.(http.CloseNotifier); ok {
|
|
||||||
finished := make(chan struct{})
|
|
||||||
defer close(finished)
|
|
||||||
go func() {
|
|
||||||
select {
|
|
||||||
case <-finished:
|
|
||||||
case <-closeNotifier.CloseNotify():
|
|
||||||
logrus.Infof("Client disconnected, cancelling job: build")
|
|
||||||
b.Cancel()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(dockerfileName) > 0 {
|
|
||||||
b.DockerfileName = dockerfileName
|
|
||||||
}
|
|
||||||
|
|
||||||
imgID, err := b.Build()
|
|
||||||
if err != nil {
|
|
||||||
return errf(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, rt := range repoAndTags {
|
|
||||||
if err := s.daemon.TagImage(rt, imgID); err != nil {
|
|
||||||
return errf(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// sanitizeRepoAndTags parses the raw "t" parameter received from the client
|
|
||||||
// to a slice of repoAndTag.
|
|
||||||
// It also validates each repoName and tag.
|
|
||||||
func sanitizeRepoAndTags(names []string) ([]reference.Named, error) {
|
|
||||||
var (
|
|
||||||
repoAndTags []reference.Named
|
|
||||||
// This map is used for deduplicating the "-t" parameter.
|
|
||||||
uniqNames = make(map[string]struct{})
|
|
||||||
)
|
|
||||||
for _, repo := range names {
|
|
||||||
if repo == "" {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
ref, err := reference.ParseNamed(repo)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
ref = reference.WithDefaultTag(ref)
|
|
||||||
|
|
||||||
if _, isCanonical := ref.(reference.Canonical); isCanonical {
|
|
||||||
return nil, errors.New("build tag cannot contain a digest")
|
|
||||||
}
|
|
||||||
|
|
||||||
nameWithTag := ref.String()
|
|
||||||
|
|
||||||
if _, exists := uniqNames[nameWithTag]; !exists {
|
|
||||||
uniqNames[nameWithTag] = struct{}{}
|
|
||||||
repoAndTags = append(repoAndTags, ref)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return repoAndTags, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *router) getImagesJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
func (s *router) getImagesJSON(ctx context.Context, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||||
if err := httputils.ParseForm(r); err != nil {
|
if err := httputils.ParseForm(r); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -97,7 +97,6 @@ func (r *router) initRoutes() {
|
||||||
NewGetRoute("/images/{name:.*}/json", r.getImagesByName),
|
NewGetRoute("/images/{name:.*}/json", r.getImagesByName),
|
||||||
// POST
|
// POST
|
||||||
NewPostRoute("/commit", r.postCommit),
|
NewPostRoute("/commit", r.postCommit),
|
||||||
NewPostRoute("/build", r.postBuild),
|
|
||||||
NewPostRoute("/images/create", r.postImagesCreate),
|
NewPostRoute("/images/create", r.postImagesCreate),
|
||||||
NewPostRoute("/images/load", r.postImagesLoad),
|
NewPostRoute("/images/load", r.postImagesLoad),
|
||||||
NewPostRoute("/images/{name:.*}/push", r.postImagesPush),
|
NewPostRoute("/images/{name:.*}/push", r.postImagesPush),
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"github.com/Sirupsen/logrus"
|
"github.com/Sirupsen/logrus"
|
||||||
"github.com/docker/docker/api/server/httputils"
|
"github.com/docker/docker/api/server/httputils"
|
||||||
"github.com/docker/docker/api/server/router"
|
"github.com/docker/docker/api/server/router"
|
||||||
|
"github.com/docker/docker/api/server/router/build"
|
||||||
"github.com/docker/docker/api/server/router/container"
|
"github.com/docker/docker/api/server/router/container"
|
||||||
"github.com/docker/docker/api/server/router/local"
|
"github.com/docker/docker/api/server/router/local"
|
||||||
"github.com/docker/docker/api/server/router/network"
|
"github.com/docker/docker/api/server/router/network"
|
||||||
|
@ -177,6 +178,7 @@ func (s *Server) InitRouters(d *daemon.Daemon) {
|
||||||
s.addRouter(network.NewRouter(d))
|
s.addRouter(network.NewRouter(d))
|
||||||
s.addRouter(system.NewRouter(d))
|
s.addRouter(system.NewRouter(d))
|
||||||
s.addRouter(volume.NewRouter(d))
|
s.addRouter(volume.NewRouter(d))
|
||||||
|
s.addRouter(build.NewRouter(d))
|
||||||
}
|
}
|
||||||
|
|
||||||
// addRouter adds a new router to the server.
|
// addRouter adds a new router to the server.
|
||||||
|
|
|
@ -15,17 +15,6 @@ import (
|
||||||
"github.com/docker/docker/runconfig"
|
"github.com/docker/docker/runconfig"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Builder abstracts a Docker builder whose only purpose is to build a Docker image referenced by an imageID.
|
|
||||||
type Builder interface {
|
|
||||||
// Build builds a Docker image referenced by an imageID string.
|
|
||||||
//
|
|
||||||
// Note: Tagging an image should not be done by a Builder, it should instead be done
|
|
||||||
// by the caller.
|
|
||||||
//
|
|
||||||
// TODO: make this return a reference instead of string
|
|
||||||
Build() (imageID string)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Context represents a file system tree.
|
// Context represents a file system tree.
|
||||||
type Context interface {
|
type Context interface {
|
||||||
// Close allows to signal that the filesystem tree won't be used anymore.
|
// Close allows to signal that the filesystem tree won't be used anymore.
|
||||||
|
|
|
@ -70,7 +70,7 @@ type Config struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Builder is a Dockerfile builder
|
// Builder is a Dockerfile builder
|
||||||
// It implements the builder.Builder interface.
|
// It implements the builder.Backend interface.
|
||||||
type Builder struct {
|
type Builder struct {
|
||||||
*Config
|
*Config
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue