diff --git a/api/server/form.go b/api/server/form.go new file mode 100644 index 0000000000..af1cd2075e --- /dev/null +++ b/api/server/form.go @@ -0,0 +1,20 @@ +package server + +import ( + "net/http" + "strconv" + "strings" +) + +func boolValue(r *http.Request, k string) bool { + s := strings.ToLower(strings.TrimSpace(r.FormValue(k))) + return !(s == "" || s == "0" || s == "no" || s == "false" || s == "none") +} + +func int64Value(r *http.Request, k string) int64 { + val, err := strconv.ParseInt(r.FormValue(k), 10, 64) + if err != nil { + return 0 + } + return val +} diff --git a/api/server/form_test.go b/api/server/form_test.go new file mode 100644 index 0000000000..5cf6c82c14 --- /dev/null +++ b/api/server/form_test.go @@ -0,0 +1,55 @@ +package server + +import ( + "net/http" + "net/url" + "testing" +) + +func TestBoolValue(t *testing.T) { + cases := map[string]bool{ + "": false, + "0": false, + "no": false, + "false": false, + "none": false, + "1": true, + "yes": true, + "true": true, + "one": true, + "100": true, + } + + for c, e := range cases { + v := url.Values{} + v.Set("test", c) + r, _ := http.NewRequest("POST", "", nil) + r.Form = v + + a := boolValue(r, "test") + if a != e { + t.Fatalf("Value: %s, expected: %v, actual: %v", c, e, a) + } + } +} + +func TestInt64Value(t *testing.T) { + cases := map[string]int64{ + "": 0, + "asdf": 0, + "0": 0, + "1": 1, + } + + for c, e := range cases { + v := url.Values{} + v.Set("test", c) + r, _ := http.NewRequest("POST", "", nil) + r.Form = v + + a := int64Value(r, "test") + if a != e { + t.Fatalf("Value: %s, expected: %v, actual: %v", c, e, a) + } + } +} diff --git a/api/server/server.go b/api/server/server.go index 65e1391670..1002526f9a 100644 --- a/api/server/server.go +++ b/api/server/server.go @@ -381,7 +381,7 @@ func (s *Server) getImagesJSON(eng *engine.Engine, version version.Version, w ht Filters: r.Form.Get("filters"), // FIXME this parameter could just be a match filter Filter: r.Form.Get("filter"), - All: toBool(r.Form.Get("all")), + All: boolValue(r, "all"), } images, err := s.daemon.Repositories().Images(&imagesConfig) @@ -597,8 +597,8 @@ func (s *Server) getContainersJSON(eng *engine.Engine, version version.Version, } config := &daemon.ContainersConfig{ - All: toBool(r.Form.Get("all")), - Size: toBool(r.Form.Get("size")), + All: boolValue(r, "all"), + Size: boolValue(r, "size"), Since: r.Form.Get("since"), Before: r.Form.Get("before"), Filters: r.Form.Get("filters"), @@ -640,14 +640,14 @@ func (s *Server) getContainersLogs(eng *engine.Engine, version version.Version, } // Validate args here, because we can't return not StatusOK after job.Run() call - stdout, stderr := toBool(r.Form.Get("stdout")), toBool(r.Form.Get("stderr")) + stdout, stderr := boolValue(r, "stdout"), boolValue(r, "stderr") if !(stdout || stderr) { return fmt.Errorf("Bad parameters: you must choose at least one stream") } logsConfig := &daemon.ContainerLogsConfig{ - Follow: toBool(r.Form.Get("follow")), - Timestamps: toBool(r.Form.Get("timestamps")), + Follow: boolValue(r, "follow"), + Timestamps: boolValue(r, "timestamps"), Tail: r.Form.Get("tail"), UseStdout: stdout, UseStderr: stderr, @@ -671,7 +671,7 @@ func (s *Server) postImagesTag(eng *engine.Engine, version version.Version, w ht repo := r.Form.Get("repo") tag := r.Form.Get("tag") - force := toBool(r.Form.Get("force")) + force := boolValue(r, "force") if err := s.daemon.Repositories().Tag(repo, tag, vars["name"], force); err != nil { return err } @@ -690,11 +690,20 @@ func (s *Server) postCommit(eng *engine.Engine, version version.Version, w http. cont := r.Form.Get("container") - pause := toBool(r.Form.Get("pause")) + pause := boolValue(r, "pause") if r.FormValue("pause") == "" && version.GreaterThanOrEqualTo("1.13") { pause = true } + c, _, err := runconfig.DecodeContainerConfig(r.Body) + if err != nil && err != io.EOF { //Do not fail if body is empty. + return err + } + + if c == nil { + c = &runconfig.Config{} + } + containerCommitConfig := &daemon.ContainerCommitConfig{ Pause: pause, Repo: r.Form.Get("repo"), @@ -702,10 +711,10 @@ func (s *Server) postCommit(eng *engine.Engine, version version.Version, w http. Author: r.Form.Get("author"), Comment: r.Form.Get("comment"), Changes: r.Form["changes"], - Config: r.Body, + Config: c, } - imgID, err := s.daemon.ContainerCommit(cont, containerCommitConfig) + imgID, err := builder.Commit(s.daemon, eng, cont, containerCommitConfig) if err != nil { return err } @@ -782,10 +791,15 @@ func (s *Server) postImagesCreate(eng *engine.Engine, version version.Version, w imageImportConfig.Json = false } - if err := s.daemon.Repositories().Import(src, repo, tag, imageImportConfig, eng); err != nil { + newConfig, err := builder.BuildFromConfig(s.daemon, eng, &runconfig.Config{}, imageImportConfig.Changes) + if err != nil { return err } + imageImportConfig.ContainerConfig = newConfig + if err := s.daemon.Repositories().Import(src, repo, tag, imageImportConfig); err != nil { + return err + } } return nil @@ -977,9 +991,9 @@ func (s *Server) deleteContainers(eng *engine.Engine, version version.Version, w name := vars["name"] config := &daemon.ContainerRmConfig{ - ForceRemove: toBool(r.Form.Get("force")), - RemoveVolume: toBool(r.Form.Get("v")), - RemoveLink: toBool(r.Form.Get("link")), + ForceRemove: boolValue(r, "force"), + RemoveVolume: boolValue(r, "v"), + RemoveLink: boolValue(r, "link"), } if err := s.daemon.ContainerRm(name, config); err != nil { @@ -1004,8 +1018,8 @@ func (s *Server) deleteImages(eng *engine.Engine, version version.Version, w htt } name := vars["name"] - force := toBool(r.Form.Get("force")) - noprune := toBool(r.Form.Get("noprune")) + force := boolValue(r, "force") + noprune := boolValue(r, "noprune") list, err := s.daemon.ImageDelete(name, force, noprune) if err != nil { @@ -1152,19 +1166,19 @@ func (s *Server) postContainersAttach(eng *engine.Engine, version version.Versio } else { errStream = outStream } - logs := toBool(r.Form.Get("logs")) - stream := toBool(r.Form.Get("stream")) + logs := boolValue(r, "logs") + stream := boolValue(r, "stream") var stdin io.ReadCloser var stdout, stderr io.Writer - if toBool(r.Form.Get("stdin")) { + if boolValue(r, "stdin") { stdin = inStream } - if toBool(r.Form.Get("stdout")) { + if boolValue(r, "stdout") { stdout = outStream } - if toBool(r.Form.Get("stderr")) { + if boolValue(r, "stderr") { stderr = errStream } @@ -1246,11 +1260,9 @@ func (s *Server) postBuild(eng *engine.Engine, version version.Version, w http.R authConfig = ®istry.AuthConfig{} configFileEncoded = r.Header.Get("X-Registry-Config") configFile = ®istry.ConfigFile{} - job = builder.NewBuildConfig(eng.Logging, eng.Stderr) + buildConfig = builder.NewBuildConfig() ) - b := &builder.BuilderJob{eng, getDaemon(eng)} - // This block can be removed when API versions prior to 1.9 are deprecated. // Both headers will be parsed and sent along to the daemon, but if a non-empty // ConfigFile is present, any value provided as an AuthConfig directly will @@ -1273,39 +1285,41 @@ func (s *Server) postBuild(eng *engine.Engine, version version.Version, w http.R } } + stdout := engine.NewOutput() + stdout.Set(utils.NewWriteFlusher(w)) + if version.GreaterThanOrEqualTo("1.8") { - job.JSONFormat = true - streamJSON(job.Stdout, w, true) - } else { - job.Stdout.Add(utils.NewWriteFlusher(w)) + w.Header().Set("Content-Type", "application/json") + buildConfig.JSONFormat = true } - if toBool(r.FormValue("forcerm")) && version.GreaterThanOrEqualTo("1.12") { - job.Remove = true + if boolValue(r, "forcerm") && version.GreaterThanOrEqualTo("1.12") { + buildConfig.Remove = true } else if r.FormValue("rm") == "" && version.GreaterThanOrEqualTo("1.12") { - job.Remove = true + buildConfig.Remove = true } else { - job.Remove = toBool(r.FormValue("rm")) + buildConfig.Remove = boolValue(r, "rm") } - if toBool(r.FormValue("pull")) && version.GreaterThanOrEqualTo("1.16") { - job.Pull = true + if boolValue(r, "pull") && version.GreaterThanOrEqualTo("1.16") { + buildConfig.Pull = true } - job.Stdin.Add(r.Body) - // FIXME(calavera): !!!!! Remote might not be used. Solve the mistery before merging - //job.Setenv("remote", r.FormValue("remote")) - job.DockerfileName = r.FormValue("dockerfile") - job.RepoName = r.FormValue("t") - job.SuppressOutput = toBool(r.FormValue("q")) - job.NoCache = toBool(r.FormValue("nocache")) - job.ForceRemove = toBool(r.FormValue("forcerm")) - job.AuthConfig = authConfig - job.ConfigFile = configFile - job.MemorySwap = toInt64(r.FormValue("memswap")) - job.Memory = toInt64(r.FormValue("memory")) - job.CpuShares = toInt64(r.FormValue("cpushares")) - job.CpuSetCpus = r.FormValue("cpusetcpus") - job.CpuSetMems = r.FormValue("cpusetmems") + buildConfig.Stdout = stdout + buildConfig.Context = r.Body + + buildConfig.RemoteURL = r.FormValue("remote") + buildConfig.DockerfileName = r.FormValue("dockerfile") + buildConfig.RepoName = r.FormValue("t") + buildConfig.SuppressOutput = boolValue(r, "q") + buildConfig.NoCache = boolValue(r, "nocache") + buildConfig.ForceRemove = boolValue(r, "forcerm") + buildConfig.AuthConfig = authConfig + buildConfig.ConfigFile = configFile + buildConfig.MemorySwap = int64Value(r, "memswap") + buildConfig.Memory = int64Value(r, "memory") + buildConfig.CpuShares = int64Value(r, "cpushares") + buildConfig.CpuSetCpus = r.FormValue("cpusetcpus") + buildConfig.CpuSetMems = r.FormValue("cpusetmems") // Job cancellation. Note: not all job types support this. if closeNotifier, ok := w.(http.CloseNotifier); ok { @@ -1316,13 +1330,13 @@ func (s *Server) postBuild(eng *engine.Engine, version version.Version, w http.R case <-finished: case <-closeNotifier.CloseNotify(): logrus.Infof("Client disconnected, cancelling job: build") - job.Cancel() + buildConfig.Cancel() } }() } - if err := b.CmdBuild(job); err != nil { - if !job.Stdout.Used() { + if err := builder.Build(s.daemon, eng, buildConfig); err != nil { + if !stdout.Used() { return err } sf := streamformatter.NewStreamFormatter(version.GreaterThanOrEqualTo("1.8")) @@ -1676,17 +1690,3 @@ func allocateDaemonPort(addr string) error { } return nil } - -func toBool(s string) bool { - s = strings.ToLower(strings.TrimSpace(s)) - return !(s == "" || s == "0" || s == "no" || s == "false" || s == "none") -} - -// FIXME(calavera): This is a copy of the Env.GetInt64 -func toInt64(s string) int64 { - val, err := strconv.ParseInt(s, 10, 64) - if err != nil { - return 0 - } - return val -} diff --git a/builder/job.go b/builder/job.go index 6ad64d716b..8a1cf3054c 100644 --- a/builder/job.go +++ b/builder/job.go @@ -2,7 +2,6 @@ package builder import ( "bytes" - "encoding/json" "fmt" "io" "io/ioutil" @@ -18,7 +17,6 @@ import ( "github.com/docker/docker/graph" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/httputils" - "github.com/docker/docker/pkg/ioutils" "github.com/docker/docker/pkg/parsers" "github.com/docker/docker/pkg/streamformatter" "github.com/docker/docker/pkg/urlutil" @@ -38,11 +36,6 @@ var validCommitCommands = map[string]bool{ "onbuild": true, } -type BuilderJob struct { - Engine *engine.Engine - Daemon *daemon.Daemon -} - type Config struct { DockerfileName string RemoteURL string @@ -61,9 +54,8 @@ type Config struct { AuthConfig *registry.AuthConfig ConfigFile *registry.ConfigFile - Stdout *engine.Output - Stderr *engine.Output - Stdin *engine.Input + Stdout io.Writer + Context io.ReadCloser // When closed, the job has been cancelled. // Note: not all jobs implement cancellation. // See Job.Cancel() and Job.WaitCancelled() @@ -83,24 +75,15 @@ func (b *Config) WaitCancelled() <-chan struct{} { return b.cancelled } -func NewBuildConfig(logging bool, err io.Writer) *Config { - c := &Config{ - Stdout: engine.NewOutput(), - Stderr: engine.NewOutput(), - Stdin: engine.NewInput(), - cancelled: make(chan struct{}), +func NewBuildConfig() *Config { + return &Config{ + AuthConfig: ®istry.AuthConfig{}, + ConfigFile: ®istry.ConfigFile{}, + cancelled: make(chan struct{}), } - if logging { - c.Stderr.Add(ioutils.NopWriteCloser(err)) - } - return c } -func (b *BuilderJob) Install() { - b.Engine.Register("build_config", b.CmdBuildConfig) -} - -func (b *BuilderJob) CmdBuild(buildConfig *Config) error { +func Build(d *daemon.Daemon, e *engine.Engine, buildConfig *Config) error { var ( repoName string tag string @@ -109,7 +92,7 @@ func (b *BuilderJob) CmdBuild(buildConfig *Config) error { repoName, tag = parsers.ParseRepositoryTag(buildConfig.RepoName) if repoName != "" { - if err := registry.ValidateRepositoryName(buildConfig.RepoName); err != nil { + if err := registry.ValidateRepositoryName(repoName); err != nil { return err } if len(tag) > 0 { @@ -120,7 +103,7 @@ func (b *BuilderJob) CmdBuild(buildConfig *Config) error { } if buildConfig.RemoteURL == "" { - context = ioutil.NopCloser(buildConfig.Stdin) + context = ioutil.NopCloser(buildConfig.Context) } else if urlutil.IsGitURL(buildConfig.RemoteURL) { if !urlutil.IsGitTransport(buildConfig.RemoteURL) { buildConfig.RemoteURL = "https://" + buildConfig.RemoteURL @@ -166,8 +149,8 @@ func (b *BuilderJob) CmdBuild(buildConfig *Config) error { sf := streamformatter.NewStreamFormatter(buildConfig.JSONFormat) builder := &Builder{ - Daemon: b.Daemon, - Engine: b.Engine, + Daemon: d, + Engine: e, OutStream: &streamformatter.StdoutFormater{ Writer: buildConfig.Stdout, StreamFormatter: sf, @@ -200,41 +183,28 @@ func (b *BuilderJob) CmdBuild(buildConfig *Config) error { } if repoName != "" { - b.Daemon.Repositories().Tag(repoName, tag, id, true) + return d.Repositories().Tag(repoName, tag, id, true) } return nil } -func (b *BuilderJob) CmdBuildConfig(job *engine.Job) error { - if len(job.Args) != 0 { - return fmt.Errorf("Usage: %s\n", job.Name) - } - - var ( - changes = job.GetenvList("changes") - newConfig runconfig.Config - ) - - if err := job.GetenvJson("config", &newConfig); err != nil { - return err - } - +func BuildFromConfig(d *daemon.Daemon, e *engine.Engine, c *runconfig.Config, changes []string) (*runconfig.Config, error) { ast, err := parser.Parse(bytes.NewBufferString(strings.Join(changes, "\n"))) if err != nil { - return err + return nil, err } // ensure that the commands are valid for _, n := range ast.Children { if !validCommitCommands[n.Value] { - return fmt.Errorf("%s is not a valid change command", n.Value) + return nil, fmt.Errorf("%s is not a valid change command", n.Value) } } builder := &Builder{ - Daemon: b.Daemon, - Engine: b.Engine, - Config: &newConfig, + Daemon: d, + Engine: e, + Config: c, OutStream: ioutil.Discard, ErrStream: ioutil.Discard, disableCommit: true, @@ -242,12 +212,32 @@ func (b *BuilderJob) CmdBuildConfig(job *engine.Job) error { for i, n := range ast.Children { if err := builder.dispatch(i, n); err != nil { - return err + return nil, err } } - if err := json.NewEncoder(job.Stdout).Encode(builder.Config); err != nil { - return err - } - return nil + return builder.Config, nil +} + +func Commit(d *daemon.Daemon, eng *engine.Engine, name string, c *daemon.ContainerCommitConfig) (string, error) { + container, err := d.Get(name) + if err != nil { + return "", err + } + + newConfig, err := BuildFromConfig(d, eng, c.Config, c.Changes) + if err != nil { + return "", err + } + + if err := runconfig.Merge(newConfig, container.Config); err != nil { + return "", err + } + + img, err := d.Commit(container, c.Repo, c.Tag, c.Comment, c.Author, c.Pause, newConfig) + if err != nil { + return "", err + } + + return img.ID, nil } diff --git a/daemon/commit.go b/daemon/commit.go index a60ed08191..0c49eb2c95 100644 --- a/daemon/commit.go +++ b/daemon/commit.go @@ -1,12 +1,6 @@ package daemon import ( - "bytes" - "encoding/json" - "io" - - "github.com/Sirupsen/logrus" - "github.com/docker/docker/engine" "github.com/docker/docker/image" "github.com/docker/docker/runconfig" ) @@ -18,49 +12,7 @@ type ContainerCommitConfig struct { Author string Comment string Changes []string - Config io.ReadCloser -} - -func (daemon *Daemon) ContainerCommit(name string, c *ContainerCommitConfig) (string, error) { - container, err := daemon.Get(name) - if err != nil { - return "", err - } - - var ( - subenv engine.Env - config = container.Config - stdoutBuffer = bytes.NewBuffer(nil) - newConfig runconfig.Config - ) - - if err := subenv.Decode(c.Config); err != nil { - logrus.Errorf("%s", err) - } - - buildConfigJob := daemon.eng.Job("build_config") - buildConfigJob.Stdout.Add(stdoutBuffer) - buildConfigJob.SetenvList("changes", c.Changes) - // FIXME this should be remove when we remove deprecated config param - buildConfigJob.SetenvSubEnv("config", &subenv) - - if err := buildConfigJob.Run(); err != nil { - return "", err - } - if err := json.NewDecoder(stdoutBuffer).Decode(&newConfig); err != nil { - return "", err - } - - if err := runconfig.Merge(&newConfig, config); err != nil { - return "", err - } - - img, err := daemon.Commit(container, c.Repo, c.Tag, c.Comment, c.Author, c.Pause, &newConfig) - if err != nil { - return "", err - } - - return img.ID, nil + Config *runconfig.Config } // Commit creates a new filesystem image from the current state of a container. diff --git a/docker/daemon.go b/docker/daemon.go index 0602ddf654..c6241b6060 100644 --- a/docker/daemon.go +++ b/docker/daemon.go @@ -11,7 +11,6 @@ import ( "github.com/Sirupsen/logrus" apiserver "github.com/docker/docker/api/server" "github.com/docker/docker/autogen/dockerversion" - "github.com/docker/docker/builder" "github.com/docker/docker/daemon" _ "github.com/docker/docker/daemon/execdriver/lxc" _ "github.com/docker/docker/daemon/execdriver/native" @@ -141,9 +140,6 @@ func mainDaemon() { "graphdriver": d.GraphDriver().String(), }).Info("Docker daemon") - b := &builder.BuilderJob{eng, d} - b.Install() - // after the daemon is done setting up we can tell the api to start // accepting connections with specified daemon api.AcceptConnections(d) @@ -155,7 +151,6 @@ func mainDaemon() { if errAPI != nil { logrus.Fatalf("Shutting down due to ServeAPI error: %v", errAPI) } - } // currentUserIsOwner checks whether the current user is the owner of the given diff --git a/graph/import.go b/graph/import.go index 5d86ba2cb9..50e605c948 100644 --- a/graph/import.go +++ b/graph/import.go @@ -1,13 +1,10 @@ package graph import ( - "bytes" - "encoding/json" "io" "net/http" "net/url" - "github.com/docker/docker/engine" "github.com/docker/docker/pkg/archive" "github.com/docker/docker/pkg/httputils" "github.com/docker/docker/pkg/progressreader" @@ -17,20 +14,18 @@ import ( ) type ImageImportConfig struct { - Changes []string - InConfig io.ReadCloser - Json bool - OutStream io.Writer - //OutStream WriteFlusher + Changes []string + InConfig io.ReadCloser + Json bool + OutStream io.Writer + ContainerConfig *runconfig.Config } -func (s *TagStore) Import(src string, repo string, tag string, imageImportConfig *ImageImportConfig, eng *engine.Engine) error { +func (s *TagStore) Import(src string, repo string, tag string, imageImportConfig *ImageImportConfig) error { var ( - sf = streamformatter.NewStreamFormatter(imageImportConfig.Json) - archive archive.ArchiveReader - resp *http.Response - stdoutBuffer = bytes.NewBuffer(nil) - newConfig runconfig.Config + sf = streamformatter.NewStreamFormatter(imageImportConfig.Json) + archive archive.ArchiveReader + resp *http.Response ) if src == "-" { @@ -63,20 +58,7 @@ func (s *TagStore) Import(src string, repo string, tag string, imageImportConfig archive = progressReader } - buildConfigJob := eng.Job("build_config") - buildConfigJob.Stdout.Add(stdoutBuffer) - buildConfigJob.SetenvList("changes", imageImportConfig.Changes) - // FIXME this should be remove when we remove deprecated config param - //buildConfigJob.Setenv("config", job.Getenv("config")) - - if err := buildConfigJob.Run(); err != nil { - return err - } - if err := json.NewDecoder(stdoutBuffer).Decode(&newConfig); err != nil { - return err - } - - img, err := s.graph.Create(archive, "", "", "Imported from "+src, "", nil, &newConfig) + img, err := s.graph.Create(archive, "", "", "Imported from "+src, "", nil, imageImportConfig.ContainerConfig) if err != nil { return err }