1
0
Fork 0
mirror of https://github.com/moby/moby.git synced 2022-11-09 12:21:53 -05:00

Merge branch 'simpler-build-upload' (#900) into builder_server-3 (#848)

This commit is contained in:
Solomon Hykes 2013-06-20 22:02:36 -07:00
commit 352991bdf4
23 changed files with 1499 additions and 755 deletions

View file

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

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

View file

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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