Bladeren bron

Create build router separate from image router.

Signed-off-by: Anusha Ragunathan <anusha@docker.com>
Anusha Ragunathan 9 jaren geleden
bovenliggende
commit
f8dc044aec

+ 12 - 0
api/server/router/build/backend.go

@@ -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)
+}

+ 33 - 0
api/server/router/build/build.go

@@ -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),
+	}
+}

+ 239 - 0
api/server/router/build/build_routes.go

@@ -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
+}

+ 0 - 214
api/server/router/local/image.go

@@ -7,26 +7,17 @@ import (
 	"fmt"
 	"io"
 	"net/http"
-	"strconv"
 	"strings"
 
-	"github.com/Sirupsen/logrus"
 	"github.com/docker/distribution/digest"
 	"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"
 	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/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"
 )
 
@@ -306,211 +297,6 @@ func (s *router) getImagesByName(ctx context.Context, w http.ResponseWriter, r *
 	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 {
 	if err := httputils.ParseForm(r); err != nil {
 		return err

+ 0 - 1
api/server/router/local/local.go

@@ -97,7 +97,6 @@ func (r *router) initRoutes() {
 		NewGetRoute("/images/{name:.*}/json", r.getImagesByName),
 		// POST
 		NewPostRoute("/commit", r.postCommit),
-		NewPostRoute("/build", r.postBuild),
 		NewPostRoute("/images/create", r.postImagesCreate),
 		NewPostRoute("/images/load", r.postImagesLoad),
 		NewPostRoute("/images/{name:.*}/push", r.postImagesPush),

+ 2 - 0
api/server/server.go

@@ -10,6 +10,7 @@ import (
 	"github.com/Sirupsen/logrus"
 	"github.com/docker/docker/api/server/httputils"
 	"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/local"
 	"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(system.NewRouter(d))
 	s.addRouter(volume.NewRouter(d))
+	s.addRouter(build.NewRouter(d))
 }
 
 // addRouter adds a new router to the server.

+ 0 - 11
builder/builder.go

@@ -15,17 +15,6 @@ import (
 	"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.
 type Context interface {
 	// Close allows to signal that the filesystem tree won't be used anymore.

+ 1 - 1
builder/dockerfile/builder.go

@@ -70,7 +70,7 @@ type Config struct {
 }
 
 // Builder is a Dockerfile builder
-// It implements the builder.Builder interface.
+// It implements the builder.Backend interface.
 type Builder struct {
 	*Config