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