mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
This commit is contained in:
commit
352991bdf4
23 changed files with 1499 additions and 755 deletions
23
CHANGELOG.md
23
CHANGELOG.md
|
@ -1,5 +1,28 @@
|
|||
# Changelog
|
||||
|
||||
## 0.4.4 (2013-06-19)
|
||||
- Builder: fix a regression introduced in 0.4.3 which caused builds to fail on new clients.
|
||||
|
||||
## 0.4.3 (2013-06-19)
|
||||
+ Builder: ADD of a local file will detect tar archives and unpack them
|
||||
* Runtime: Remove bsdtar dependency
|
||||
* Runtime: Add unix socket and multiple -H support
|
||||
* Runtime: Prevent rm of running containers
|
||||
* Runtime: Use go1.1 cookiejar
|
||||
* Builder: ADD improvements: use tar for copy + automatically unpack local archives
|
||||
* Builder: ADD uses tar/untar for copies instead of calling 'cp -ar'
|
||||
* Builder: nicer output for 'docker build'
|
||||
* Builder: fixed the behavior of ADD to be (mostly) reverse-compatible, predictable and well-documented.
|
||||
* Client: HumanReadable ProgressBar sizes in pull
|
||||
* Client: Fix docker version's git commit output
|
||||
* API: Send all tags on History API call
|
||||
* API: Add tag lookup to history command. Fixes #882
|
||||
- Runtime: Fix issue detaching from running TTY container
|
||||
- Runtime: Forbid parralel push/pull for a single image/repo. Fixes #311
|
||||
- Runtime: Fix race condition within Run command when attaching.
|
||||
- Builder: fix a bug which caused builds to fail if ADD was the first command
|
||||
- Documentation: fix missing command in irc bouncer example
|
||||
|
||||
## 0.4.2 (2013-06-17)
|
||||
- Packaging: Bumped version to work around an Ubuntu bug
|
||||
|
||||
|
|
1
FIXME
1
FIXME
|
@ -33,3 +33,4 @@ to put them - so we put them here :)
|
|||
* Caching after an ADD
|
||||
* entry point config
|
||||
* bring back git revision info, looks like it was lost
|
||||
* Clean up the ProgressReader api, it's a PITA to use
|
||||
|
|
3
Makefile
3
Makefile
|
@ -74,6 +74,9 @@ endif
|
|||
test: all
|
||||
@(cd $(DOCKER_DIR); sudo -E go test $(GO_OPTIONS))
|
||||
|
||||
testall: all
|
||||
@(cd $(DOCKER_DIR); sudo -E go test ./... $(GO_OPTIONS))
|
||||
|
||||
fmt:
|
||||
@gofmt -s -l -w .
|
||||
|
||||
|
|
110
api.go
110
api.go
|
@ -9,15 +9,17 @@ import (
|
|||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const APIVERSION = 1.2
|
||||
const APIVERSION = 1.3
|
||||
const DEFAULTHTTPHOST string = "127.0.0.1"
|
||||
const DEFAULTHTTPPORT int = 4243
|
||||
|
||||
func hijackServer(w http.ResponseWriter) (io.ReadCloser, io.Writer, error) {
|
||||
conn, _, err := w.(http.Hijacker).Hijack()
|
||||
|
@ -719,8 +721,8 @@ func postImagesGetCache(srv *Server, version float64, w http.ResponseWriter, r *
|
|||
}
|
||||
|
||||
func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error {
|
||||
if err := r.ParseMultipartForm(4096); err != nil {
|
||||
return err
|
||||
if version < 1.3 {
|
||||
return fmt.Errorf("Multipart upload for build is no longer supported. Please upgrade your docker client.")
|
||||
}
|
||||
remoteURL := r.FormValue("remote")
|
||||
repoName := r.FormValue("t")
|
||||
|
@ -731,68 +733,47 @@ func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Requ
|
|||
repoName = remoteParts[0]
|
||||
}
|
||||
|
||||
var dockerfile, context io.Reader
|
||||
var context io.Reader
|
||||
|
||||
if remoteURL == "" {
|
||||
d, _, err := r.FormFile("Dockerfile")
|
||||
context = r.Body
|
||||
} else if utils.IsGIT(remoteURL) {
|
||||
if !strings.HasPrefix(remoteURL, "git://") {
|
||||
remoteURL = "https://" + remoteURL
|
||||
}
|
||||
root, err := ioutil.TempDir("", "docker-build-git")
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
dockerfile = d
|
||||
}
|
||||
c, _, err := r.FormFile("Context")
|
||||
defer os.RemoveAll(root)
|
||||
|
||||
if output, err := exec.Command("git", "clone", remoteURL, root).CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("Error trying to use git: %s (%s)", err, output)
|
||||
}
|
||||
|
||||
c, err := Tar(root, Bzip2)
|
||||
if err != nil {
|
||||
if err != http.ErrMissingFile {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
context = c
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if utils.IsGIT(remoteURL) {
|
||||
if !strings.HasPrefix(remoteURL, "git://") {
|
||||
remoteURL = "https://" + remoteURL
|
||||
}
|
||||
root, err := ioutil.TempDir("", "docker-build-git")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer os.RemoveAll(root)
|
||||
|
||||
if output, err := exec.Command("git", "clone", remoteURL, root).CombinedOutput(); err != nil {
|
||||
return fmt.Errorf("Error trying to use git: %s (%s)", err, output)
|
||||
}
|
||||
|
||||
d, err := os.Open(path.Join(root, "Dockerfile"))
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
return fmt.Errorf("No Dockerfile found in the repository")
|
||||
}
|
||||
return err
|
||||
} else {
|
||||
dockerfile = d
|
||||
}
|
||||
|
||||
c, err := Tar(root, Bzip2)
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
context = c
|
||||
}
|
||||
|
||||
} else if utils.IsURL(remoteURL) {
|
||||
f, err := utils.Download(remoteURL, ioutil.Discard)
|
||||
if err != nil {
|
||||
return err
|
||||
} else {
|
||||
dockerfile = f.Body
|
||||
}
|
||||
defer f.Body.Close()
|
||||
context = c
|
||||
} else if utils.IsURL(remoteURL) {
|
||||
f, err := utils.Download(remoteURL, ioutil.Discard)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Body.Close()
|
||||
dockerFile, err := ioutil.ReadAll(f.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c, err := mkBuildContext(string(dockerFile), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
context = c
|
||||
}
|
||||
|
||||
b := NewBuildFile(srv, utils.NewWriteFlusher(w))
|
||||
if id, err := b.Build(dockerfile, context); err != nil {
|
||||
if id, err := b.Build(context); err != nil {
|
||||
fmt.Fprintf(w, "Error build: %s\n", err)
|
||||
} else if repoName != "" {
|
||||
srv.runtime.repositories.Set(repoName, tag, id, false)
|
||||
|
@ -865,6 +846,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
|
|||
localFct := fct
|
||||
f := func(w http.ResponseWriter, r *http.Request) {
|
||||
utils.Debugf("Calling %s %s", localMethod, localRoute)
|
||||
|
||||
if logging {
|
||||
log.Println(r.Method, r.RequestURI)
|
||||
}
|
||||
|
@ -885,6 +867,7 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
|
|||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
if err := localFct(srv, version, w, r, mux.Vars(r)); err != nil {
|
||||
httpError(w, err)
|
||||
}
|
||||
|
@ -901,12 +884,21 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) {
|
|||
return r, nil
|
||||
}
|
||||
|
||||
func ListenAndServe(addr string, srv *Server, logging bool) error {
|
||||
log.Printf("Listening for HTTP on %s\n", addr)
|
||||
func ListenAndServe(proto, addr string, srv *Server, logging bool) error {
|
||||
log.Printf("Listening for HTTP on %s (%s)\n", addr, proto)
|
||||
|
||||
r, err := createRouter(srv, logging)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return http.ListenAndServe(addr, r)
|
||||
l, e := net.Listen(proto, addr)
|
||||
if e != nil {
|
||||
return e
|
||||
}
|
||||
//as the daemon is launched as root, change to permission of the socket to allow non-root to connect
|
||||
if proto == "unix" {
|
||||
os.Chmod(addr, 0777)
|
||||
}
|
||||
httpSrv := http.Server{Addr: addr, Handler: r}
|
||||
return httpSrv.Serve(l)
|
||||
}
|
||||
|
|
98
archive.go
98
archive.go
|
@ -1,7 +1,9 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bufio"
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
|
@ -10,6 +12,7 @@ import (
|
|||
"os"
|
||||
"os/exec"
|
||||
"path"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type Archive io.Reader
|
||||
|
@ -160,51 +163,60 @@ func CopyWithTar(src, dst string) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var dstExists bool
|
||||
dstSt, err := os.Stat(dst)
|
||||
if err != nil {
|
||||
if !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
dstExists = true
|
||||
}
|
||||
// Things that can go wrong if the source is a directory
|
||||
if srcSt.IsDir() {
|
||||
// The destination exists and is a regular file
|
||||
if dstExists && !dstSt.IsDir() {
|
||||
return fmt.Errorf("Can't copy a directory over a regular file")
|
||||
}
|
||||
// Things that can go wrong if the source is a regular file
|
||||
} else {
|
||||
utils.Debugf("The destination exists, it's a directory, and doesn't end in /")
|
||||
// The destination exists, it's a directory, and doesn't end in /
|
||||
if dstExists && dstSt.IsDir() && dst[len(dst)-1] != '/' {
|
||||
return fmt.Errorf("Can't copy a regular file over a directory %s |%s|", dst, dst[len(dst)-1])
|
||||
}
|
||||
}
|
||||
// Create the destination
|
||||
var dstDir string
|
||||
if srcSt.IsDir() || dst[len(dst)-1] == '/' {
|
||||
// The destination ends in /, or the source is a directory
|
||||
// --> dst is the holding directory and needs to be created for -C
|
||||
dstDir = dst
|
||||
} else {
|
||||
// The destination doesn't end in /
|
||||
// --> dst is the file
|
||||
dstDir = path.Dir(dst)
|
||||
}
|
||||
if !dstExists {
|
||||
// Create the holding directory if necessary
|
||||
utils.Debugf("Creating the holding directory %s", dstDir)
|
||||
if err := os.MkdirAll(dstDir, 0700); err != nil && !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if !srcSt.IsDir() {
|
||||
return TarUntar(path.Dir(src), []string{path.Base(src)}, dstDir)
|
||||
return CopyFileWithTar(src, dst)
|
||||
}
|
||||
return TarUntar(src, nil, dstDir)
|
||||
// Create dst, copy src's content into it
|
||||
utils.Debugf("Creating dest directory: %s", dst)
|
||||
if err := os.MkdirAll(dst, 0700); err != nil && !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
utils.Debugf("Calling TarUntar(%s, %s)", src, dst)
|
||||
return TarUntar(src, nil, dst)
|
||||
}
|
||||
|
||||
// CopyFileWithTar emulates the behavior of the 'cp' command-line
|
||||
// for a single file. It copies a regular file from path `src` to
|
||||
// path `dst`, and preserves all its metadata.
|
||||
//
|
||||
// If `dst` ends with a trailing slash '/', the final destination path
|
||||
// will be `dst/base(src)`.
|
||||
func CopyFileWithTar(src, dst string) error {
|
||||
utils.Debugf("CopyFileWithTar(%s, %s)", src, dst)
|
||||
srcSt, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if srcSt.IsDir() {
|
||||
return fmt.Errorf("Can't copy a directory")
|
||||
}
|
||||
// Clean up the trailing /
|
||||
if dst[len(dst)-1] == '/' {
|
||||
dst = path.Join(dst, filepath.Base(src))
|
||||
}
|
||||
// Create the holding directory if necessary
|
||||
if err := os.MkdirAll(filepath.Dir(dst), 0700); err != nil && !os.IsExist(err) {
|
||||
return err
|
||||
}
|
||||
buf := new(bytes.Buffer)
|
||||
tw := tar.NewWriter(buf)
|
||||
hdr, err := tar.FileInfoHeader(srcSt, "")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hdr.Name = filepath.Base(dst)
|
||||
if err := tw.WriteHeader(hdr); err != nil {
|
||||
return err
|
||||
}
|
||||
srcF, err := os.Open(src)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if _, err := io.Copy(tw, srcF); err != nil {
|
||||
return err
|
||||
}
|
||||
tw.Close()
|
||||
return Untar(buf, filepath.Dir(dst))
|
||||
}
|
||||
|
||||
// CmdStream executes a command, and returns its stdout as a stream.
|
||||
|
|
|
@ -82,7 +82,7 @@ func decodeAuth(authStr string) (*AuthConfig, error) {
|
|||
func LoadConfig(rootPath string) (*AuthConfig, error) {
|
||||
confFile := path.Join(rootPath, CONFIGFILE)
|
||||
if _, err := os.Stat(confFile); err != nil {
|
||||
return &AuthConfig{rootPath:rootPath}, ErrConfigFileMissing
|
||||
return &AuthConfig{rootPath: rootPath}, ErrConfigFileMissing
|
||||
}
|
||||
b, err := ioutil.ReadFile(confFile)
|
||||
if err != nil {
|
||||
|
|
|
@ -1,314 +0,0 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"io"
|
||||
"net/url"
|
||||
"os"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type builderClient struct {
|
||||
cli *DockerCli
|
||||
|
||||
image string
|
||||
maintainer string
|
||||
config *Config
|
||||
|
||||
tmpContainers map[string]struct{}
|
||||
tmpImages map[string]struct{}
|
||||
|
||||
needCommit bool
|
||||
}
|
||||
|
||||
func (b *builderClient) clearTmp(containers, images map[string]struct{}) {
|
||||
for i := range images {
|
||||
if _, _, err := b.cli.call("DELETE", "/images/"+i, nil); err != nil {
|
||||
utils.Debugf("%s", err)
|
||||
}
|
||||
utils.Debugf("Removing image %s", i)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *builderClient) CmdFrom(name string) error {
|
||||
obj, statusCode, err := b.cli.call("GET", "/images/"+name+"/json", nil)
|
||||
if statusCode == 404 {
|
||||
|
||||
remote := name
|
||||
var tag string
|
||||
if strings.Contains(remote, ":") {
|
||||
remoteParts := strings.Split(remote, ":")
|
||||
tag = remoteParts[1]
|
||||
remote = remoteParts[0]
|
||||
}
|
||||
var out io.Writer
|
||||
if os.Getenv("DEBUG") != "" {
|
||||
out = os.Stdout
|
||||
} else {
|
||||
out = &utils.NopWriter{}
|
||||
}
|
||||
if err := b.cli.stream("POST", "/images/create?fromImage="+remote+"&tag="+tag, nil, out); err != nil {
|
||||
return err
|
||||
}
|
||||
obj, _, err = b.cli.call("GET", "/images/"+name+"/json", nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
img := &APIID{}
|
||||
if err := json.Unmarshal(obj, img); err != nil {
|
||||
return err
|
||||
}
|
||||
b.image = img.ID
|
||||
utils.Debugf("Using image %s", b.image)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *builderClient) CmdMaintainer(name string) error {
|
||||
b.needCommit = true
|
||||
b.maintainer = name
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *builderClient) CmdRun(args string) error {
|
||||
if b.image == "" {
|
||||
return fmt.Errorf("Please provide a source image with `from` prior to run")
|
||||
}
|
||||
config, _, err := ParseRun([]string{b.image, "/bin/sh", "-c", args}, nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cmd, env := b.config.Cmd, b.config.Env
|
||||
b.config.Cmd = nil
|
||||
MergeConfig(b.config, config)
|
||||
|
||||
body, statusCode, err := b.cli.call("POST", "/images/getCache", &APIImageConfig{ID: b.image, Config: b.config})
|
||||
if err != nil {
|
||||
if statusCode != 404 {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if statusCode != 404 {
|
||||
apiID := &APIID{}
|
||||
if err := json.Unmarshal(body, apiID); err != nil {
|
||||
return err
|
||||
}
|
||||
utils.Debugf("Use cached version")
|
||||
b.image = apiID.ID
|
||||
return nil
|
||||
}
|
||||
cid, err := b.run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
b.config.Cmd, b.config.Env = cmd, env
|
||||
return b.commit(cid)
|
||||
}
|
||||
|
||||
func (b *builderClient) CmdEnv(args string) error {
|
||||
b.needCommit = true
|
||||
tmp := strings.SplitN(args, " ", 2)
|
||||
if len(tmp) != 2 {
|
||||
return fmt.Errorf("Invalid ENV format")
|
||||
}
|
||||
key := strings.Trim(tmp[0], " ")
|
||||
value := strings.Trim(tmp[1], " ")
|
||||
|
||||
for i, elem := range b.config.Env {
|
||||
if strings.HasPrefix(elem, key+"=") {
|
||||
b.config.Env[i] = key + "=" + value
|
||||
return nil
|
||||
}
|
||||
}
|
||||
b.config.Env = append(b.config.Env, key+"="+value)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *builderClient) CmdCmd(args string) error {
|
||||
b.needCommit = true
|
||||
var cmd []string
|
||||
if err := json.Unmarshal([]byte(args), &cmd); err != nil {
|
||||
utils.Debugf("Error unmarshalling: %s, using /bin/sh -c", err)
|
||||
b.config.Cmd = []string{"/bin/sh", "-c", args}
|
||||
} else {
|
||||
b.config.Cmd = cmd
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *builderClient) CmdExpose(args string) error {
|
||||
ports := strings.Split(args, " ")
|
||||
b.config.PortSpecs = append(ports, b.config.PortSpecs...)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *builderClient) CmdInsert(args string) error {
|
||||
// tmp := strings.SplitN(args, "\t ", 2)
|
||||
// sourceUrl, destPath := tmp[0], tmp[1]
|
||||
|
||||
// v := url.Values{}
|
||||
// v.Set("url", sourceUrl)
|
||||
// v.Set("path", destPath)
|
||||
// body, _, err := b.cli.call("POST", "/images/insert?"+v.Encode(), nil)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
// apiId := &APIId{}
|
||||
// if err := json.Unmarshal(body, apiId); err != nil {
|
||||
// return err
|
||||
// }
|
||||
|
||||
// FIXME: Reimplement this, we need to retrieve the resulting Id
|
||||
return fmt.Errorf("INSERT not implemented")
|
||||
}
|
||||
|
||||
func (b *builderClient) run() (string, error) {
|
||||
if b.image == "" {
|
||||
return "", fmt.Errorf("Please provide a source image with `from` prior to run")
|
||||
}
|
||||
b.config.Image = b.image
|
||||
body, _, err := b.cli.call("POST", "/containers/create", b.config)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
apiRun := &APIRun{}
|
||||
if err := json.Unmarshal(body, apiRun); err != nil {
|
||||
return "", err
|
||||
}
|
||||
for _, warning := range apiRun.Warnings {
|
||||
fmt.Fprintln(os.Stderr, "WARNING: ", warning)
|
||||
}
|
||||
|
||||
//start the container
|
||||
_, _, err = b.cli.call("POST", "/containers/"+apiRun.ID+"/start", nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
b.tmpContainers[apiRun.ID] = struct{}{}
|
||||
|
||||
// Wait for it to finish
|
||||
body, _, err = b.cli.call("POST", "/containers/"+apiRun.ID+"/wait", nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
apiWait := &APIWait{}
|
||||
if err := json.Unmarshal(body, apiWait); err != nil {
|
||||
return "", err
|
||||
}
|
||||
if apiWait.StatusCode != 0 {
|
||||
return "", fmt.Errorf("The command %v returned a non-zero code: %d", b.config.Cmd, apiWait.StatusCode)
|
||||
}
|
||||
|
||||
return apiRun.ID, nil
|
||||
}
|
||||
|
||||
func (b *builderClient) commit(id string) error {
|
||||
if b.image == "" {
|
||||
return fmt.Errorf("Please provide a source image with `from` prior to run")
|
||||
}
|
||||
b.config.Image = b.image
|
||||
|
||||
if id == "" {
|
||||
cmd := b.config.Cmd
|
||||
b.config.Cmd = []string{"true"}
|
||||
cid, err := b.run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
id = cid
|
||||
b.config.Cmd = cmd
|
||||
}
|
||||
|
||||
// Commit the container
|
||||
v := url.Values{}
|
||||
v.Set("container", id)
|
||||
v.Set("author", b.maintainer)
|
||||
|
||||
body, _, err := b.cli.call("POST", "/commit?"+v.Encode(), b.config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
apiID := &APIID{}
|
||||
if err := json.Unmarshal(body, apiID); err != nil {
|
||||
return err
|
||||
}
|
||||
b.tmpImages[apiID.ID] = struct{}{}
|
||||
b.image = apiID.ID
|
||||
b.needCommit = false
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *builderClient) Build(dockerfile, context io.Reader) (string, error) {
|
||||
defer b.clearTmp(b.tmpContainers, b.tmpImages)
|
||||
file := bufio.NewReader(dockerfile)
|
||||
for {
|
||||
line, err := file.ReadString('\n')
|
||||
if err != nil {
|
||||
if err == io.EOF {
|
||||
break
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
line = strings.Replace(strings.TrimSpace(line), " ", " ", 1)
|
||||
// Skip comments and empty line
|
||||
if len(line) == 0 || line[0] == '#' {
|
||||
continue
|
||||
}
|
||||
tmp := strings.SplitN(line, " ", 2)
|
||||
if len(tmp) != 2 {
|
||||
return "", fmt.Errorf("Invalid Dockerfile format")
|
||||
}
|
||||
instruction := strings.ToLower(strings.Trim(tmp[0], " "))
|
||||
arguments := strings.Trim(tmp[1], " ")
|
||||
|
||||
fmt.Fprintf(os.Stderr, "%s %s (%s)\n", strings.ToUpper(instruction), arguments, b.image)
|
||||
|
||||
method, exists := reflect.TypeOf(b).MethodByName("Cmd" + strings.ToUpper(instruction[:1]) + strings.ToLower(instruction[1:]))
|
||||
if !exists {
|
||||
fmt.Fprintf(os.Stderr, "Skipping unknown instruction %s\n", strings.ToUpper(instruction))
|
||||
}
|
||||
ret := method.Func.Call([]reflect.Value{reflect.ValueOf(b), reflect.ValueOf(arguments)})[0].Interface()
|
||||
if ret != nil {
|
||||
return "", ret.(error)
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stderr, "===> %v\n", b.image)
|
||||
}
|
||||
if b.needCommit {
|
||||
if err := b.commit(""); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
if b.image != "" {
|
||||
// The build is successful, keep the temporary containers and images
|
||||
for i := range b.tmpImages {
|
||||
delete(b.tmpImages, i)
|
||||
}
|
||||
for i := range b.tmpContainers {
|
||||
delete(b.tmpContainers, i)
|
||||
}
|
||||
fmt.Fprintf(os.Stderr, "Build finished. image id: %s\n", b.image)
|
||||
return b.image, nil
|
||||
}
|
||||
return "", fmt.Errorf("An error occured during the build\n")
|
||||
}
|
||||
|
||||
func NewBuilderClient(addr string, port int) BuildFile {
|
||||
return &builderClient{
|
||||
cli: NewDockerCli(addr, port),
|
||||
config: &Config{},
|
||||
tmpContainers: make(map[string]struct{}),
|
||||
tmpImages: make(map[string]struct{}),
|
||||
}
|
||||
}
|
29
buildfile.go
29
buildfile.go
|
@ -14,7 +14,7 @@ import (
|
|||
)
|
||||
|
||||
type BuildFile interface {
|
||||
Build(io.Reader, io.Reader) (string, error)
|
||||
Build(io.Reader) (string, error)
|
||||
CmdFrom(string) error
|
||||
CmdRun(string) error
|
||||
}
|
||||
|
@ -327,18 +327,23 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (b *buildFile) Build(dockerfile, context io.Reader) (string, error) {
|
||||
if context != nil {
|
||||
name, err := ioutil.TempDir("/tmp", "docker-build")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := Untar(context, name); err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer os.RemoveAll(name)
|
||||
b.context = name
|
||||
func (b *buildFile) Build(context io.Reader) (string, error) {
|
||||
// FIXME: @creack any reason for using /tmp instead of ""?
|
||||
// FIXME: @creack "name" is a terrible variable name
|
||||
name, err := ioutil.TempDir("/tmp", "docker-build")
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if err := Untar(context, name); err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer os.RemoveAll(name)
|
||||
b.context = name
|
||||
dockerfile, err := os.Open(path.Join(name, "Dockerfile"))
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("Can't build a directory with no Dockerfile")
|
||||
}
|
||||
// FIXME: "file" is also a terrible variable name ;)
|
||||
file := bufio.NewReader(dockerfile)
|
||||
stepN := 0
|
||||
for {
|
||||
|
|
|
@ -1,47 +1,91 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"strings"
|
||||
"io/ioutil"
|
||||
"testing"
|
||||
)
|
||||
|
||||
const Dockerfile = `
|
||||
# VERSION 0.1
|
||||
# DOCKER-VERSION 0.2
|
||||
// mkTestContext generates a build context from the contents of the provided dockerfile.
|
||||
// This context is suitable for use as an argument to BuildFile.Build()
|
||||
func mkTestContext(dockerfile string, files [][2]string, t *testing.T) Archive {
|
||||
context, err := mkBuildContext(dockerfile, files)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return context
|
||||
}
|
||||
|
||||
from ` + unitTestImageName + `
|
||||
maintainer docker
|
||||
expose 22
|
||||
env FOO BAR
|
||||
cmd ["echo", "hello", "world"]
|
||||
// A testContextTemplate describes a build context and how to test it
|
||||
type testContextTemplate struct {
|
||||
// Contents of the Dockerfile
|
||||
dockerfile string
|
||||
// Additional files in the context, eg [][2]string{"./passwd", "gordon"}
|
||||
files [][2]string
|
||||
}
|
||||
|
||||
// A table of all the contexts to build and test.
|
||||
// A new docker runtime will be created and torn down for each context.
|
||||
var testContexts []testContextTemplate = []testContextTemplate{
|
||||
{
|
||||
`
|
||||
from docker-ut
|
||||
run sh -c 'echo root:testpass > /tmp/passwd'
|
||||
run mkdir -p /var/run/sshd
|
||||
add . /src
|
||||
`
|
||||
run [ "$(cat /tmp/passwd)" = "root:testpass" ]
|
||||
run [ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ]
|
||||
`,
|
||||
nil,
|
||||
},
|
||||
|
||||
const DockerfileNoNewLine = `
|
||||
# VERSION 0.1
|
||||
# DOCKER-VERSION 0.2
|
||||
{
|
||||
`
|
||||
from docker-ut
|
||||
add foo /usr/lib/bla/bar
|
||||
run [ "$(cat /usr/lib/bla/bar)" = 'hello world!' ]
|
||||
`,
|
||||
[][2]string{{"foo", "hello world!"}},
|
||||
},
|
||||
|
||||
from ` + unitTestImageName + `
|
||||
maintainer docker
|
||||
expose 22
|
||||
{
|
||||
`
|
||||
from docker-ut
|
||||
add f /
|
||||
run [ "$(cat /f)" = "hello" ]
|
||||
add f /abc
|
||||
run [ "$(cat /abc)" = "hello" ]
|
||||
add f /x/y/z
|
||||
run [ "$(cat /x/y/z)" = "hello" ]
|
||||
add f /x/y/d/
|
||||
run [ "$(cat /x/y/d/f)" = "hello" ]
|
||||
add d /
|
||||
run [ "$(cat /ga)" = "bu" ]
|
||||
add d /somewhere
|
||||
run [ "$(cat /somewhere/ga)" = "bu" ]
|
||||
add d /anotherplace/
|
||||
run [ "$(cat /anotherplace/ga)" = "bu" ]
|
||||
add d /somewheeeere/over/the/rainbooow
|
||||
run [ "$(cat /somewheeeere/over/the/rainbooow/ga)" = "bu" ]
|
||||
`,
|
||||
[][2]string{
|
||||
{"f", "hello"},
|
||||
{"d/ga", "bu"},
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
`
|
||||
from docker-ut
|
||||
env FOO BAR
|
||||
cmd ["echo", "hello", "world"]
|
||||
run sh -c 'echo root:testpass > /tmp/passwd'
|
||||
run mkdir -p /var/run/sshd
|
||||
add . /src`
|
||||
|
||||
// FIXME: test building with a context
|
||||
|
||||
// FIXME: test building with a local ADD as first command
|
||||
run [ "$FOO" = "BAR" ]
|
||||
`,
|
||||
nil,
|
||||
},
|
||||
}
|
||||
|
||||
// FIXME: test building with 2 successive overlapping ADD commands
|
||||
|
||||
func TestBuildFile(t *testing.T) {
|
||||
dockerfiles := []string{Dockerfile, DockerfileNoNewLine}
|
||||
for _, Dockerfile := range dockerfiles {
|
||||
func TestBuild(t *testing.T) {
|
||||
for _, ctx := range testContexts {
|
||||
runtime, err := newTestRuntime()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
|
@ -50,75 +94,9 @@ func TestBuildFile(t *testing.T) {
|
|||
|
||||
srv := &Server{runtime: runtime}
|
||||
|
||||
buildfile := NewBuildFile(srv, &utils.NopWriter{})
|
||||
|
||||
context, err := fakeTar()
|
||||
if err != nil {
|
||||
buildfile := NewBuildFile(srv, ioutil.Discard)
|
||||
if _, err := buildfile.Build(mkTestContext(ctx.dockerfile, ctx.files, t)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
imgID, err := buildfile.Build(strings.NewReader(Dockerfile), context)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
builder := NewBuilder(runtime)
|
||||
container, err := builder.Create(
|
||||
&Config{
|
||||
Image: imgID,
|
||||
Cmd: []string{"cat", "/tmp/passwd"},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer runtime.Destroy(container)
|
||||
|
||||
output, err := container.Output()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(output) != "root:testpass\n" {
|
||||
t.Fatalf("Unexpected output. Read '%s', expected '%s'", output, "root:testpass\n")
|
||||
}
|
||||
|
||||
container2, err := builder.Create(
|
||||
&Config{
|
||||
Image: imgID,
|
||||
Cmd: []string{"ls", "-d", "/var/run/sshd"},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer runtime.Destroy(container2)
|
||||
|
||||
output, err = container2.Output()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(output) != "/var/run/sshd\n" {
|
||||
t.Fatal("/var/run/sshd has not been created")
|
||||
}
|
||||
|
||||
container3, err := builder.Create(
|
||||
&Config{
|
||||
Image: imgID,
|
||||
Cmd: []string{"ls", "/src"},
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer runtime.Destroy(container3)
|
||||
|
||||
output, err = container3.Output()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if string(output) != "etc\nvar\n" {
|
||||
t.Fatalf("Unexpected output. Expected: '%s', received: '%s'", "etc\nvar\n", string(output))
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
164
commands.go
164
commands.go
|
@ -1,6 +1,7 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"flag"
|
||||
|
@ -10,14 +11,12 @@ import (
|
|||
"github.com/dotcloud/docker/utils"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"mime/multipart"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httputil"
|
||||
"net/url"
|
||||
"os"
|
||||
"os/signal"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"regexp"
|
||||
|
@ -29,7 +28,7 @@ import (
|
|||
"unicode"
|
||||
)
|
||||
|
||||
const VERSION = "0.4.2"
|
||||
const VERSION = "0.4.4"
|
||||
|
||||
var (
|
||||
GITCOMMIT string
|
||||
|
@ -40,8 +39,8 @@ func (cli *DockerCli) getMethod(name string) (reflect.Method, bool) {
|
|||
return reflect.TypeOf(cli).MethodByName(methodName)
|
||||
}
|
||||
|
||||
func ParseCommands(addr string, port int, args ...string) error {
|
||||
cli := NewDockerCli(addr, port)
|
||||
func ParseCommands(proto, addr string, args ...string) error {
|
||||
cli := NewDockerCli(proto, addr)
|
||||
|
||||
if len(args) > 0 {
|
||||
method, exists := cli.getMethod(args[0])
|
||||
|
@ -74,7 +73,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error {
|
|||
return nil
|
||||
}
|
||||
}
|
||||
help := fmt.Sprintf("Usage: docker [OPTIONS] COMMAND [arg...]\n -H=\"%s:%d\": Host:port to bind/connect to\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n", cli.host, cli.port)
|
||||
help := fmt.Sprintf("Usage: docker [OPTIONS] COMMAND [arg...]\n -H=[tcp://%s:%d]: tcp://host:port to bind/connect to or unix://path/to/socker to use\n\nA self-sufficient runtime for linux containers.\n\nCommands:\n", DEFAULTHTTPHOST, DEFAULTHTTPPORT)
|
||||
for _, command := range [][2]string{
|
||||
{"attach", "Attach to a running container"},
|
||||
{"build", "Build a container from a Dockerfile"},
|
||||
|
@ -131,6 +130,31 @@ func (cli *DockerCli) CmdInsert(args ...string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// mkBuildContext returns an archive of an empty context with the contents
|
||||
// of `dockerfile` at the path ./Dockerfile
|
||||
func mkBuildContext(dockerfile string, files [][2]string) (Archive, error) {
|
||||
buf := new(bytes.Buffer)
|
||||
tw := tar.NewWriter(buf)
|
||||
files = append(files, [2]string{"Dockerfile", dockerfile})
|
||||
for _, file := range files {
|
||||
name, content := file[0], file[1]
|
||||
hdr := &tar.Header{
|
||||
Name: name,
|
||||
Size: int64(len(content)),
|
||||
}
|
||||
if err := tw.WriteHeader(hdr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if _, err := tw.Write([]byte(content)); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
if err := tw.Close(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return buf, nil
|
||||
}
|
||||
|
||||
func (cli *DockerCli) CmdBuild(args ...string) error {
|
||||
cmd := Subcmd("build", "[OPTIONS] PATH | URL | -", "Build a new container image from the source code at PATH")
|
||||
tag := cmd.String("t", "", "Tag to be applied to the resulting image in case of success")
|
||||
|
@ -143,85 +167,55 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
|
|||
}
|
||||
|
||||
var (
|
||||
multipartBody io.Reader
|
||||
file io.ReadCloser
|
||||
contextPath string
|
||||
context Archive
|
||||
isRemote bool
|
||||
err error
|
||||
)
|
||||
|
||||
// Init the needed component for the Multipart
|
||||
buff := bytes.NewBuffer([]byte{})
|
||||
multipartBody = buff
|
||||
w := multipart.NewWriter(buff)
|
||||
boundary := strings.NewReader("\r\n--" + w.Boundary() + "--\r\n")
|
||||
|
||||
compression := Bzip2
|
||||
|
||||
isRemote := false
|
||||
|
||||
if cmd.Arg(0) == "-" {
|
||||
file = os.Stdin
|
||||
// As a special case, 'docker build -' will build from an empty context with the
|
||||
// contents of stdin as a Dockerfile
|
||||
dockerfile, err := ioutil.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
context, err = mkBuildContext(string(dockerfile), nil)
|
||||
} else if utils.IsURL(cmd.Arg(0)) || utils.IsGIT(cmd.Arg(0)) {
|
||||
isRemote = true
|
||||
} else {
|
||||
// Send Dockerfile from arg/Dockerfile (deprecate later)
|
||||
f, err := os.Open(path.Join(cmd.Arg(0), "Dockerfile"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
file = f
|
||||
// Send context from arg
|
||||
// Create a FormFile multipart for the context if needed
|
||||
// FIXME: Use NewTempArchive in order to have the size and avoid too much memory usage?
|
||||
context, err := Tar(cmd.Arg(0), compression)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// NOTE: Do this in case '.' or '..' is input
|
||||
absPath, err := filepath.Abs(cmd.Arg(0))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
wField, err := w.CreateFormFile("Context", filepath.Base(absPath)+"."+compression.Extension())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// FIXME: Find a way to have a progressbar for the upload too
|
||||
context, err = Tar(cmd.Arg(0), Uncompressed)
|
||||
}
|
||||
var body io.Reader
|
||||
// Setup an upload progress bar
|
||||
// FIXME: ProgressReader shouldn't be this annoyning to use
|
||||
if context != nil {
|
||||
sf := utils.NewStreamFormatter(false)
|
||||
io.Copy(wField, utils.ProgressReader(ioutil.NopCloser(context), -1, os.Stdout, sf.FormatProgress("Caching Context", "%v/%v (%v)"), sf))
|
||||
multipartBody = io.MultiReader(multipartBody, boundary)
|
||||
body = utils.ProgressReader(ioutil.NopCloser(context), 0, os.Stderr, sf.FormatProgress("Uploading context", "%v bytes%0.0s%0.0s"), sf)
|
||||
}
|
||||
if !isRemote {
|
||||
// Create a FormFile multipart for the Dockerfile
|
||||
wField, err := w.CreateFormFile("Dockerfile", "Dockerfile")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
io.Copy(wField, file)
|
||||
}
|
||||
multipartBody = io.MultiReader(multipartBody, boundary)
|
||||
|
||||
// Upload the build context
|
||||
v := &url.Values{}
|
||||
v.Set("t", *tag)
|
||||
if isRemote {
|
||||
v.Set("remote", cmd.Arg(0))
|
||||
}
|
||||
// Send the multipart request with correct content-type
|
||||
req, err := http.NewRequest("POST", fmt.Sprintf("http://%s:%d%s?%s", cli.host, cli.port, "/build", v.Encode()), multipartBody)
|
||||
req, err := http.NewRequest("POST", fmt.Sprintf("/v%g/build?%s", APIVERSION, v.Encode()), body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Content-Type", w.FormDataContentType())
|
||||
if contextPath != "" {
|
||||
req.Header.Set("X-Docker-Context-Compression", compression.Flag())
|
||||
fmt.Println("Uploading Context...")
|
||||
if context != nil {
|
||||
req.Header.Set("Content-Type", "application/tar")
|
||||
}
|
||||
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
dial, err := net.Dial(cli.proto, cli.addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
clientconn := httputil.NewClientConn(dial, nil)
|
||||
resp, err := clientconn.Do(req)
|
||||
defer clientconn.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
// Check for errors
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 400 {
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
|
@ -1040,10 +1034,10 @@ func (cli *DockerCli) CmdLogs(args ...string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
if err := cli.stream("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stdout=1", nil, os.Stdout); err != nil {
|
||||
if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stdout=1", false, nil, os.Stdout); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := cli.stream("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stderr=1", nil, os.Stderr); err != nil {
|
||||
if err := cli.hijack("POST", "/containers/"+cmd.Arg(0)+"/attach?logs=1&stderr=1", false, nil, os.Stderr); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
|
@ -1311,7 +1305,7 @@ func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int,
|
|||
params = bytes.NewBuffer(buf)
|
||||
}
|
||||
|
||||
req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%d/v%g%s", cli.host, cli.port, APIVERSION, path), params)
|
||||
req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), params)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
|
@ -1321,7 +1315,13 @@ func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int,
|
|||
} else if method == "POST" {
|
||||
req.Header.Set("Content-Type", "plain/text")
|
||||
}
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
dial, err := net.Dial(cli.proto, cli.addr)
|
||||
if err != nil {
|
||||
return nil, -1, err
|
||||
}
|
||||
clientconn := httputil.NewClientConn(dial, nil)
|
||||
resp, err := clientconn.Do(req)
|
||||
defer clientconn.Close()
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "connection refused") {
|
||||
return nil, -1, fmt.Errorf("Can't connect to docker daemon. Is 'docker -d' running on this host?")
|
||||
|
@ -1346,7 +1346,7 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
|
|||
if (method == "POST" || method == "PUT") && in == nil {
|
||||
in = bytes.NewReader([]byte{})
|
||||
}
|
||||
req, err := http.NewRequest(method, fmt.Sprintf("http://%s:%d/v%g%s", cli.host, cli.port, APIVERSION, path), in)
|
||||
req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), in)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -1354,7 +1354,13 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
|
|||
if method == "POST" {
|
||||
req.Header.Set("Content-Type", "plain/text")
|
||||
}
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
dial, err := net.Dial(cli.proto, cli.addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
clientconn := httputil.NewClientConn(dial, nil)
|
||||
resp, err := clientconn.Do(req)
|
||||
defer clientconn.Close()
|
||||
if err != nil {
|
||||
if strings.Contains(err.Error(), "connection refused") {
|
||||
return fmt.Errorf("Can't connect to docker daemon. Is 'docker -d' running on this host?")
|
||||
|
@ -1362,6 +1368,7 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
|
|||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode < 200 || resp.StatusCode >= 400 {
|
||||
body, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
|
@ -1399,19 +1406,24 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) e
|
|||
}
|
||||
|
||||
func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in *os.File, out io.Writer) error {
|
||||
|
||||
req, err := http.NewRequest(method, fmt.Sprintf("/v%g%s", APIVERSION, path), nil)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("User-Agent", "Docker-Client/"+VERSION)
|
||||
req.Header.Set("Content-Type", "plain/text")
|
||||
dial, err := net.Dial("tcp", fmt.Sprintf("%s:%d", cli.host, cli.port))
|
||||
|
||||
dial, err := net.Dial(cli.proto, cli.addr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
clientconn := httputil.NewClientConn(dial, nil)
|
||||
clientconn.Do(req)
|
||||
defer clientconn.Close()
|
||||
|
||||
// Server hijacks the connection, error 'connection closed' expected
|
||||
clientconn.Do(req)
|
||||
|
||||
rwc, br := clientconn.Hijack()
|
||||
defer rwc.Close()
|
||||
|
||||
|
@ -1487,13 +1499,13 @@ func Subcmd(name, signature, description string) *flag.FlagSet {
|
|||
return flags
|
||||
}
|
||||
|
||||
func NewDockerCli(addr string, port int) *DockerCli {
|
||||
func NewDockerCli(proto, addr string) *DockerCli {
|
||||
authConfig, _ := auth.LoadConfig(os.Getenv("HOME"))
|
||||
return &DockerCli{addr, port, authConfig}
|
||||
return &DockerCli{proto, addr, authConfig}
|
||||
}
|
||||
|
||||
type DockerCli struct {
|
||||
host string
|
||||
port int
|
||||
proto string
|
||||
addr string
|
||||
authConfig *auth.AuthConfig
|
||||
}
|
||||
|
|
|
@ -24,40 +24,29 @@ func main() {
|
|||
docker.SysInit()
|
||||
return
|
||||
}
|
||||
host := "127.0.0.1"
|
||||
port := 4243
|
||||
// FIXME: Switch d and D ? (to be more sshd like)
|
||||
flDaemon := flag.Bool("d", false, "Daemon mode")
|
||||
flDebug := flag.Bool("D", false, "Debug mode")
|
||||
flAutoRestart := flag.Bool("r", false, "Restart previously running containers")
|
||||
bridgeName := flag.String("b", "", "Attach containers to a pre-existing network bridge")
|
||||
pidfile := flag.String("p", "/var/run/docker.pid", "File containing process PID")
|
||||
flHost := flag.String("H", fmt.Sprintf("%s:%d", host, port), "Host:port to bind/connect to")
|
||||
flEnableCors := flag.Bool("api-enable-cors", false, "Enable CORS requests in the remote api.")
|
||||
flDns := flag.String("dns", "", "Set custom dns servers")
|
||||
flHosts := docker.ListOpts{fmt.Sprintf("tcp://%s:%d", docker.DEFAULTHTTPHOST, docker.DEFAULTHTTPPORT)}
|
||||
flag.Var(&flHosts, "H", "tcp://host:port to bind/connect to or unix://path/to/socket to use")
|
||||
flag.Parse()
|
||||
if len(flHosts) > 1 {
|
||||
flHosts = flHosts[1:len(flHosts)] //trick to display a nice defaul value in the usage
|
||||
}
|
||||
for i, flHost := range flHosts {
|
||||
flHosts[i] = utils.ParseHost(docker.DEFAULTHTTPHOST, docker.DEFAULTHTTPPORT, flHost)
|
||||
}
|
||||
|
||||
if *bridgeName != "" {
|
||||
docker.NetworkBridgeIface = *bridgeName
|
||||
} else {
|
||||
docker.NetworkBridgeIface = docker.DefaultNetworkBridge
|
||||
}
|
||||
|
||||
if strings.Contains(*flHost, ":") {
|
||||
hostParts := strings.Split(*flHost, ":")
|
||||
if len(hostParts) != 2 {
|
||||
log.Fatal("Invalid bind address format.")
|
||||
os.Exit(-1)
|
||||
}
|
||||
if hostParts[0] != "" {
|
||||
host = hostParts[0]
|
||||
}
|
||||
if p, err := strconv.Atoi(hostParts[1]); err == nil {
|
||||
port = p
|
||||
}
|
||||
} else {
|
||||
host = *flHost
|
||||
}
|
||||
|
||||
if *flDebug {
|
||||
os.Setenv("DEBUG", "1")
|
||||
}
|
||||
|
@ -67,12 +56,17 @@ func main() {
|
|||
flag.Usage()
|
||||
return
|
||||
}
|
||||
if err := daemon(*pidfile, host, port, *flAutoRestart, *flEnableCors, *flDns); err != nil {
|
||||
if err := daemon(*pidfile, flHosts, *flAutoRestart, *flEnableCors, *flDns); err != nil {
|
||||
log.Fatal(err)
|
||||
os.Exit(-1)
|
||||
}
|
||||
} else {
|
||||
if err := docker.ParseCommands(host, port, flag.Args()...); err != nil {
|
||||
if len(flHosts) > 1 {
|
||||
log.Fatal("Please specify only one -H")
|
||||
return
|
||||
}
|
||||
protoAddrParts := strings.SplitN(flHosts[0], "://", 2)
|
||||
if err := docker.ParseCommands(protoAddrParts[0], protoAddrParts[1], flag.Args()...); err != nil {
|
||||
log.Fatal(err)
|
||||
os.Exit(-1)
|
||||
}
|
||||
|
@ -106,10 +100,7 @@ func removePidFile(pidfile string) {
|
|||
}
|
||||
}
|
||||
|
||||
func daemon(pidfile, addr string, port int, autoRestart, enableCors bool, flDns string) error {
|
||||
if addr != "127.0.0.1" {
|
||||
log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
|
||||
}
|
||||
func daemon(pidfile string, protoAddrs []string, autoRestart, enableCors bool, flDns string) error {
|
||||
if err := createPidFile(pidfile); err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
@ -131,6 +122,28 @@ func daemon(pidfile, addr string, port int, autoRestart, enableCors bool, flDns
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return docker.ListenAndServe(fmt.Sprintf("%s:%d", addr, port), server, true)
|
||||
chErrors := make(chan error, len(protoAddrs))
|
||||
for _, protoAddr := range protoAddrs {
|
||||
protoAddrParts := strings.SplitN(protoAddr, "://", 2)
|
||||
if protoAddrParts[0] == "unix" {
|
||||
syscall.Unlink(protoAddrParts[1])
|
||||
} else if protoAddrParts[0] == "tcp" {
|
||||
if !strings.HasPrefix(protoAddrParts[1], "127.0.0.1") {
|
||||
log.Println("/!\\ DON'T BIND ON ANOTHER IP ADDRESS THAN 127.0.0.1 IF YOU DON'T KNOW WHAT YOU'RE DOING /!\\")
|
||||
}
|
||||
} else {
|
||||
log.Fatal("Invalid protocol format.")
|
||||
os.Exit(-1)
|
||||
}
|
||||
go func() {
|
||||
chErrors <- docker.ListenAndServe(protoAddrParts[0], protoAddrParts[1], server, true)
|
||||
}()
|
||||
}
|
||||
for i := 0; i < len(protoAddrs); i += 1 {
|
||||
err := <-chErrors
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1030,5 +1030,5 @@ In this version of the API, /attach, uses hijacking to transport stdin, stdout a
|
|||
|
||||
To enable cross origin requests to the remote api add the flag "-api-enable-cors" when running docker in daemon mode.
|
||||
|
||||
docker -d -H="192.168.1.9:4243" -api-enable-cors
|
||||
docker -d -H="tcp://192.168.1.9:4243" -api-enable-cors
|
||||
|
||||
|
|
1038
docs/sources/api/docker_remote_api_v1.3.rst
Normal file
1038
docs/sources/api/docker_remote_api_v1.3.rst
Normal file
File diff suppressed because it is too large
Load diff
|
@ -15,7 +15,7 @@ To list available commands, either run ``docker`` with no parameters or execute
|
|||
|
||||
$ docker
|
||||
Usage: docker [OPTIONS] COMMAND [arg...]
|
||||
-H="127.0.0.1:4243": Host:port to bind/connect to
|
||||
-H=[tcp://127.0.0.1:4243]: tcp://host:port to bind/connect to or unix://path/to/socket to use
|
||||
|
||||
A self-sufficient runtime for linux containers.
|
||||
|
||||
|
|
|
@ -33,11 +33,20 @@ Running an interactive shell
|
|||
# allocate a tty, attach stdin and stdout
|
||||
docker run -i -t base /bin/bash
|
||||
|
||||
Bind Docker to another host/port
|
||||
--------------------------------
|
||||
Bind Docker to another host/port or a unix socket
|
||||
-------------------------------------------------
|
||||
|
||||
If you want Docker to listen to another port and bind to another ip
|
||||
use -host and -port on both deamon and client
|
||||
With -H it is possible to make the Docker daemon to listen on a specific ip and port. By default, it will listen on 127.0.0.1:4243 to allow only local connections but you can set it to 0.0.0.0:4243 or a specific host ip to give access to everybody.
|
||||
|
||||
Similarly, the Docker client can use -H to connect to a custom port.
|
||||
|
||||
-H accepts host and port assignment in the following format: tcp://[host][:port] or unix://path
|
||||
For example:
|
||||
|
||||
* tcp://host -> tcp connection on host:4243
|
||||
* tcp://host:port -> tcp connection on host:port
|
||||
* tcp://:port -> tcp connection on 127.0.0.1:port
|
||||
* unix://path/to/socket -> unix socket located at path/to/socket
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
|
@ -46,6 +55,17 @@ use -host and -port on both deamon and client
|
|||
# Download a base image
|
||||
docker -H :5555 pull base
|
||||
|
||||
You can use multiple -H, for example, if you want to listen
|
||||
on both tcp and a unix socket
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
# Run docker in daemon mode
|
||||
sudo <path to>/docker -H tcp://127.0.0.1:4243 -H unix:///var/run/docker.sock
|
||||
# Download a base image
|
||||
docker pull base
|
||||
# OR
|
||||
docker -H unix:///var/run/docker.sock pull base
|
||||
|
||||
Starting a long-running worker process
|
||||
--------------------------------------
|
||||
|
|
|
@ -1,10 +0,0 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
)
|
||||
|
||||
func getKernelVersion() (*utils.KernelVersionInfo, error) {
|
||||
return nil, fmt.Errorf("Kernel version detection is not available on darwin")
|
||||
}
|
|
@ -1,71 +0,0 @@
|
|||
package docker
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// FIXME: Move this to utils package
|
||||
func getKernelVersion() (*utils.KernelVersionInfo, error) {
|
||||
var (
|
||||
uts syscall.Utsname
|
||||
flavor string
|
||||
kernel, major, minor int
|
||||
err error
|
||||
)
|
||||
|
||||
if err := syscall.Uname(&uts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
release := make([]byte, len(uts.Release))
|
||||
|
||||
i := 0
|
||||
for _, c := range uts.Release {
|
||||
release[i] = byte(c)
|
||||
i++
|
||||
}
|
||||
|
||||
// Remove the \x00 from the release for Atoi to parse correctly
|
||||
release = release[:bytes.IndexByte(release, 0)]
|
||||
|
||||
tmp := strings.SplitN(string(release), "-", 2)
|
||||
tmp2 := strings.SplitN(tmp[0], ".", 3)
|
||||
|
||||
if len(tmp2) > 0 {
|
||||
kernel, err = strconv.Atoi(tmp2[0])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if len(tmp2) > 1 {
|
||||
major, err = strconv.Atoi(tmp2[1])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if len(tmp2) > 2 {
|
||||
minor, err = strconv.Atoi(tmp2[2])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if len(tmp) == 2 {
|
||||
flavor = tmp[1]
|
||||
} else {
|
||||
flavor = ""
|
||||
}
|
||||
|
||||
return &utils.KernelVersionInfo{
|
||||
Kernel: kernel,
|
||||
Major: major,
|
||||
Minor: minor,
|
||||
Flavor: flavor,
|
||||
}, nil
|
||||
}
|
|
@ -19,19 +19,14 @@ run add-apt-repository "deb http://archive.ubuntu.com/ubuntu $(lsb_release -sc)
|
|||
run add-apt-repository -y ppa:dotcloud/docker-golang/ubuntu
|
||||
run apt-get update
|
||||
# Packages required to checkout, build and upload docker
|
||||
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q s3cmd
|
||||
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q curl
|
||||
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q s3cmd curl
|
||||
run curl -s -o /go.tar.gz https://go.googlecode.com/files/go1.1.1.linux-amd64.tar.gz
|
||||
run tar -C /usr/local -xzf /go.tar.gz
|
||||
run echo "export PATH=/usr/local/go/bin:$PATH" > /.bashrc
|
||||
run echo "export PATH=/usr/local/go/bin:$PATH" > /.bash_profile
|
||||
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q git
|
||||
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q build-essential
|
||||
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q git build-essential
|
||||
# Packages required to build an ubuntu package
|
||||
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q golang-stable
|
||||
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q debhelper
|
||||
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q autotools-dev
|
||||
run apt-get install -y -q devscripts
|
||||
run DEBIAN_FRONTEND=noninteractive apt-get install -y -q golang-stable debhelper autotools-dev devscripts
|
||||
# Copy dockerbuilder files into the container
|
||||
add . /src
|
||||
run cp /src/dockerbuilder /usr/local/bin/ && chmod +x /usr/local/bin/dockerbuilder
|
||||
|
|
|
@ -7,10 +7,10 @@ import (
|
|||
"fmt"
|
||||
"github.com/dotcloud/docker/auth"
|
||||
"github.com/dotcloud/docker/utils"
|
||||
"github.com/shin-/cookiejar"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/cookiejar"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
@ -314,7 +314,7 @@ func (r *Registry) opaqueRequest(method, urlStr string, body io.Reader) (*http.R
|
|||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
req.URL.Opaque = strings.Replace(urlStr, req.URL.Scheme + ":", "", 1)
|
||||
req.URL.Opaque = strings.Replace(urlStr, req.URL.Scheme+":", "", 1)
|
||||
return req, err
|
||||
}
|
||||
|
||||
|
@ -453,11 +453,6 @@ func (r *Registry) SearchRepositories(term string) (*SearchResults, error) {
|
|||
return result, err
|
||||
}
|
||||
|
||||
func (r *Registry) ResetClient(authConfig *auth.AuthConfig) {
|
||||
r.authConfig = authConfig
|
||||
r.client.Jar = cookiejar.NewCookieJar()
|
||||
}
|
||||
|
||||
func (r *Registry) GetAuthConfig(withPasswd bool) *auth.AuthConfig {
|
||||
password := ""
|
||||
if withPasswd {
|
||||
|
@ -493,18 +488,18 @@ type Registry struct {
|
|||
authConfig *auth.AuthConfig
|
||||
}
|
||||
|
||||
func NewRegistry(root string, authConfig *auth.AuthConfig) *Registry {
|
||||
func NewRegistry(root string, authConfig *auth.AuthConfig) (r *Registry, err error) {
|
||||
httpTransport := &http.Transport{
|
||||
DisableKeepAlives: true,
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
}
|
||||
|
||||
r := &Registry{
|
||||
r = &Registry{
|
||||
authConfig: authConfig,
|
||||
client: &http.Client{
|
||||
Transport: httpTransport,
|
||||
},
|
||||
}
|
||||
r.client.Jar = cookiejar.NewCookieJar()
|
||||
return r
|
||||
r.client.Jar, err = cookiejar.New(nil)
|
||||
return r, err
|
||||
}
|
||||
|
|
26
server.go
26
server.go
|
@ -55,8 +55,11 @@ func (srv *Server) ContainerExport(name string, out io.Writer) error {
|
|||
}
|
||||
|
||||
func (srv *Server) ImagesSearch(term string) ([]APISearch, error) {
|
||||
|
||||
results, err := registry.NewRegistry(srv.runtime.root, nil).SearchRepositories(term)
|
||||
r, err := registry.NewRegistry(srv.runtime.root, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results, err := r.SearchRepositories(term)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -450,12 +453,15 @@ func (srv *Server) poolRemove(kind, key string) error {
|
|||
}
|
||||
|
||||
func (srv *Server) ImagePull(name, tag, endpoint string, out io.Writer, sf *utils.StreamFormatter, authConfig *auth.AuthConfig) error {
|
||||
r, err := registry.NewRegistry(srv.runtime.root, authConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := srv.poolAdd("pull", name+":"+tag); err != nil {
|
||||
return err
|
||||
}
|
||||
defer srv.poolRemove("pull", name+":"+tag)
|
||||
|
||||
r := registry.NewRegistry(srv.runtime.root, authConfig)
|
||||
out = utils.NewWriteFlusher(out)
|
||||
if endpoint != "" {
|
||||
if err := srv.pullImage(r, out, name, endpoint, nil, sf); err != nil {
|
||||
|
@ -654,8 +660,10 @@ func (srv *Server) ImagePush(name, endpoint string, out io.Writer, sf *utils.Str
|
|||
|
||||
out = utils.NewWriteFlusher(out)
|
||||
img, err := srv.runtime.graph.Get(name)
|
||||
r := registry.NewRegistry(srv.runtime.root, authConfig)
|
||||
|
||||
r, err2 := registry.NewRegistry(srv.runtime.root, authConfig)
|
||||
if err2 != nil {
|
||||
return err2
|
||||
}
|
||||
if err != nil {
|
||||
out.Write(sf.FormatStatus("The push refers to a repository [%s] (len: %d)", name, len(srv.runtime.repositories.Repositories[name])))
|
||||
// If it fails, try to get the repository
|
||||
|
@ -971,17 +979,17 @@ func (srv *Server) ContainerAttach(name string, logs, stream, stdin, stdout, std
|
|||
if stdout {
|
||||
cLog, err := container.ReadLog("stdout")
|
||||
if err != nil {
|
||||
utils.Debugf(err.Error())
|
||||
utils.Debugf("Error reading logs (stdout): %s", err)
|
||||
} else if _, err := io.Copy(out, cLog); err != nil {
|
||||
utils.Debugf(err.Error())
|
||||
utils.Debugf("Error streaming logs (stdout): %s", err)
|
||||
}
|
||||
}
|
||||
if stderr {
|
||||
cLog, err := container.ReadLog("stderr")
|
||||
if err != nil {
|
||||
utils.Debugf(err.Error())
|
||||
utils.Debugf("Error reading logs (stderr): %s", err)
|
||||
} else if _, err := io.Copy(out, cLog); err != nil {
|
||||
utils.Debugf(err.Error())
|
||||
utils.Debugf("Error streaming logs (stderr): %s", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,16 +9,16 @@ const (
|
|||
getTermios = syscall.TIOCGETA
|
||||
setTermios = syscall.TIOCSETA
|
||||
|
||||
ECHO = 0x00000008
|
||||
ONLCR = 0x2
|
||||
ISTRIP = 0x20
|
||||
INLCR = 0x40
|
||||
ISIG = 0x80
|
||||
IGNCR = 0x80
|
||||
ICANON = 0x100
|
||||
ICRNL = 0x100
|
||||
IXOFF = 0x400
|
||||
IXON = 0x200
|
||||
ECHO = 0x00000008
|
||||
ONLCR = 0x2
|
||||
ISTRIP = 0x20
|
||||
INLCR = 0x40
|
||||
ISIG = 0x80
|
||||
IGNCR = 0x80
|
||||
ICANON = 0x100
|
||||
ICRNL = 0x100
|
||||
IXOFF = 0x400
|
||||
IXON = 0x200
|
||||
)
|
||||
|
||||
type Termios struct {
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"index/suffixarray"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
|
@ -660,3 +661,28 @@ func CheckLocalDns() bool {
|
|||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func ParseHost(host string, port int, addr string) string {
|
||||
if strings.HasPrefix(addr, "unix://") {
|
||||
return addr
|
||||
}
|
||||
if strings.HasPrefix(addr, "tcp://") {
|
||||
addr = strings.TrimPrefix(addr, "tcp://")
|
||||
}
|
||||
if strings.Contains(addr, ":") {
|
||||
hostParts := strings.Split(addr, ":")
|
||||
if len(hostParts) != 2 {
|
||||
log.Fatal("Invalid bind address format.")
|
||||
os.Exit(-1)
|
||||
}
|
||||
if hostParts[0] != "" {
|
||||
host = hostParts[0]
|
||||
}
|
||||
if p, err := strconv.Atoi(hostParts[1]); err == nil {
|
||||
port = p
|
||||
}
|
||||
} else {
|
||||
host = addr
|
||||
}
|
||||
return fmt.Sprintf("tcp://%s:%d", host, port)
|
||||
}
|
||||
|
|
|
@ -274,3 +274,21 @@ func TestHumanSize(t *testing.T) {
|
|||
t.Errorf("1024 -> expected 1.024 kB, got %s", size1024)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseHost(t *testing.T) {
|
||||
if addr := ParseHost("127.0.0.1", 4243, "0.0.0.0"); addr != "tcp://0.0.0.0:4243" {
|
||||
t.Errorf("0.0.0.0 -> expected tcp://0.0.0.0:4243, got %s", addr)
|
||||
}
|
||||
if addr := ParseHost("127.0.0.1", 4243, "0.0.0.1:5555"); addr != "tcp://0.0.0.1:5555" {
|
||||
t.Errorf("0.0.0.1:5555 -> expected tcp://0.0.0.1:5555, got %s", addr)
|
||||
}
|
||||
if addr := ParseHost("127.0.0.1", 4243, ":6666"); addr != "tcp://127.0.0.1:6666" {
|
||||
t.Errorf(":6666 -> expected tcp://127.0.0.1:6666, got %s", addr)
|
||||
}
|
||||
if addr := ParseHost("127.0.0.1", 4243, "tcp://:7777"); addr != "tcp://127.0.0.1:7777" {
|
||||
t.Errorf("tcp://:7777 -> expected tcp://127.0.0.1:7777, got %s", addr)
|
||||
}
|
||||
if addr := ParseHost("127.0.0.1", 4243, "unix:///var/run/docker.sock"); addr != "unix:///var/run/docker.sock" {
|
||||
t.Errorf("unix:///var/run/docker.sock -> expected unix:///var/run/docker.sock, got %s", addr)
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue