From 01ce312c2d3e56a7993f7d644675513e1acca17c Mon Sep 17 00:00:00 2001 From: David Calavera Date: Fri, 26 Jul 2013 17:40:45 -0700 Subject: [PATCH 001/128] Exit from `docker login` on SIGTERM and SIGINT. Fixes #1299. --- commands.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/commands.go b/commands.go index 0fabaa385f..1fb56e98a6 100644 --- a/commands.go +++ b/commands.go @@ -319,6 +319,14 @@ func (cli *DockerCli) CmdLogin(args ...string) error { authconfig = auth.AuthConfig{} } + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) + go func() { + for sig := range c { + os.Exit(1) + } + }() + if *flUsername == "" { fmt.Fprintf(cli.out, "Username (%s): ", authconfig.Username) username = readAndEchoString(cli.in, cli.out) From 7cc90f2bc552f5b3b49e65e19ac877089e3db137 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Fri, 26 Jul 2013 18:12:05 -0700 Subject: [PATCH 002/128] Use a more idiomatic syntax to capture the exit. --- commands.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/commands.go b/commands.go index 1fb56e98a6..d2764086ef 100644 --- a/commands.go +++ b/commands.go @@ -319,12 +319,11 @@ func (cli *DockerCli) CmdLogin(args ...string) error { authconfig = auth.AuthConfig{} } - c := make(chan os.Signal, 1) - signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) + sigchan := make(chan os.Signal, 1) + signal.Notify(sigchan, syscall.SIGINT, syscall.SIGTERM) go func() { - for sig := range c { - os.Exit(1) - } + <-sigchan + os.Exit(1) }() if *flUsername == "" { From bb06fe8dd9b84a196eae19e4c955bb2e5a09dba2 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sat, 27 Jul 2013 09:13:02 -0700 Subject: [PATCH 003/128] Allow to generate signals when termios is in raw mode. --- commands.go | 7 ------- term/termios_darwin.go | 2 +- term/termios_linux.go | 2 +- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/commands.go b/commands.go index d2764086ef..0fabaa385f 100644 --- a/commands.go +++ b/commands.go @@ -319,13 +319,6 @@ func (cli *DockerCli) CmdLogin(args ...string) error { authconfig = auth.AuthConfig{} } - sigchan := make(chan os.Signal, 1) - signal.Notify(sigchan, syscall.SIGINT, syscall.SIGTERM) - go func() { - <-sigchan - os.Exit(1) - }() - if *flUsername == "" { fmt.Fprintf(cli.out, "Username (%s): ", authconfig.Username) username = readAndEchoString(cli.in, cli.out) diff --git a/term/termios_darwin.go b/term/termios_darwin.go index 24e79de4b2..0f6b24b184 100644 --- a/term/termios_darwin.go +++ b/term/termios_darwin.go @@ -44,7 +44,7 @@ func MakeRaw(fd uintptr) (*State, error) { newState.Iflag &^= (ISTRIP | INLCR | IGNCR | IXON | IXOFF) newState.Iflag |= ICRNL newState.Oflag |= ONLCR - newState.Lflag &^= (ECHO | ICANON | ISIG) + newState.Lflag &^= (ECHO | ICANON) if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(setTermios), uintptr(unsafe.Pointer(&newState))); err != 0 { return nil, err diff --git a/term/termios_linux.go b/term/termios_linux.go index 4a717c84a7..22f4fff430 100644 --- a/term/termios_linux.go +++ b/term/termios_linux.go @@ -33,7 +33,7 @@ func MakeRaw(fd uintptr) (*State, error) { newState.Iflag &^= (syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK | syscall.ISTRIP | syscall.INLCR | syscall.IGNCR | syscall.ICRNL | syscall.IXON) newState.Oflag &^= syscall.OPOST - newState.Lflag &^= (syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG | syscall.IEXTEN) + newState.Lflag &^= (syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.IEXTEN) newState.Cflag &^= (syscall.CSIZE | syscall.PARENB) newState.Cflag |= syscall.CS8 From 75ac50a9a0669b464a95e069e66da5d47df9182f Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sat, 3 Aug 2013 16:43:20 -0700 Subject: [PATCH 004/128] Stop making a raw terminal to ask for registry login credentials. It only disables echo asking for the password and lets the terminal to handle everything else. It fixes #1392 since blank spaces are not discarded as they did before. It also cleans the login code a little bit to improve readability. --- commands.go | 111 +++++++++++++++------------------------------------ term/term.go | 44 ++++++++++++++++---- 2 files changed, 68 insertions(+), 87 deletions(-) diff --git a/commands.go b/commands.go index a2099b9607..416076d3d5 100644 --- a/commands.go +++ b/commands.go @@ -2,6 +2,7 @@ package docker import ( "archive/tar" + "bufio" "bytes" "encoding/json" "flag" @@ -24,7 +25,6 @@ import ( "syscall" "text/tabwriter" "time" - "unicode" ) const VERSION = "0.5.0-dev" @@ -253,73 +253,18 @@ func (cli *DockerCli) CmdBuild(args ...string) error { // 'docker login': login / register a user to registry service. func (cli *DockerCli) CmdLogin(args ...string) error { - var readStringOnRawTerminal = func(stdin io.Reader, stdout io.Writer, echo bool) string { - char := make([]byte, 1) - buffer := make([]byte, 64) - var i = 0 - for i < len(buffer) { - n, err := stdin.Read(char) - if n > 0 { - if char[0] == '\r' || char[0] == '\n' { - stdout.Write([]byte{'\r', '\n'}) - break - } else if char[0] == 127 || char[0] == '\b' { - if i > 0 { - if echo { - stdout.Write([]byte{'\b', ' ', '\b'}) - } - i-- - } - } else if !unicode.IsSpace(rune(char[0])) && - !unicode.IsControl(rune(char[0])) { - if echo { - stdout.Write(char) - } - buffer[i] = char[0] - i++ - } - } - if err != nil { - if err != io.EOF { - fmt.Fprintf(stdout, "Read error: %v\r\n", err) - } - break - } - } - return string(buffer[:i]) - } - var readAndEchoString = func(stdin io.Reader, stdout io.Writer) string { - return readStringOnRawTerminal(stdin, stdout, true) - } - var readString = func(stdin io.Reader, stdout io.Writer) string { - return readStringOnRawTerminal(stdin, stdout, false) - } - cmd := Subcmd("login", "[OPTIONS]", "Register or Login to the docker registry server") - flUsername := cmd.String("u", "", "username") - flPassword := cmd.String("p", "", "password") - flEmail := cmd.String("e", "", "email") + + username := *cmd.String("u", "", "username") + password := *cmd.String("p", "", "password") + email := *cmd.String("e", "", "email") err := cmd.Parse(args) + if err != nil { return nil } - var oldState *term.State - if *flUsername == "" || *flPassword == "" || *flEmail == "" { - oldState, err = term.SetRawTerminal(cli.terminalFd) - if err != nil { - return err - } - defer term.RestoreTerminal(cli.terminalFd, oldState) - } - - var ( - username string - password string - email string - ) - - var promptDefault = func(prompt string, configDefault string) { + promptDefault := func(prompt string, configDefault string) { if configDefault == "" { fmt.Fprintf(cli.out, "%s: ", prompt) } else { @@ -327,47 +272,55 @@ func (cli *DockerCli) CmdLogin(args ...string) error { } } + readInput := func(in io.Reader) (string, error) { + reader := bufio.NewReader(in) + line, err := reader.ReadString('\n') + if err != nil { + return "", err + } + return line, nil + } + authconfig, ok := cli.configFile.Configs[auth.IndexServerAddress()] if !ok { authconfig = auth.AuthConfig{} } - if *flUsername == "" { + if username == "" { promptDefault("Username", authconfig.Username) - username = readAndEchoString(cli.in, cli.out) + username, _ = readInput(cli.in) if username == "" { username = authconfig.Username } - } else { - username = *flUsername } + if username != authconfig.Username { - if *flPassword == "" { + if password == "" { + oldState, _ := term.SaveState(cli.terminalFd) fmt.Fprintf(cli.out, "Password: ") - password = readString(cli.in, cli.out) + + term.DisableEcho(cli.terminalFd, cli.out, oldState) + password, _ = readInput(cli.in) + + term.RestoreTerminal(cli.terminalFd, oldState) + if password == "" { return fmt.Errorf("Error : Password Required") } - } else { - password = *flPassword } - if *flEmail == "" { - promptDefault("Email", authconfig.Email) - email = readAndEchoString(cli.in, cli.out) + if email == "" { + promptDefault("\nEmail", authconfig.Email) + email, _ = readInput(cli.in) if email == "" { email = authconfig.Email } - } else { - email = *flEmail } } else { password = authconfig.Password email = authconfig.Email } - if oldState != nil { - term.RestoreTerminal(cli.terminalFd, oldState) - } + authconfig.Username = username authconfig.Password = password authconfig.Email = email @@ -1620,7 +1573,7 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea }) if in != nil && setRawTerminal && cli.isTerminal && os.Getenv("NORAW") == "" { - oldState, err := term.SetRawTerminal(cli.terminalFd) + oldState, err := term.SetRawTerminal(cli.terminalFd, cli.out) if err != nil { return err } diff --git a/term/term.go b/term/term.go index f4d66a71d6..074319c287 100644 --- a/term/term.go +++ b/term/term.go @@ -1,6 +1,8 @@ package term import ( + "fmt" + "io" "os" "os/signal" "syscall" @@ -43,17 +45,43 @@ func RestoreTerminal(fd uintptr, state *State) error { return err } -func SetRawTerminal(fd uintptr) (*State, error) { +func SaveState(fd uintptr) (*State, error) { + var oldState State + if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, getTermios, uintptr(unsafe.Pointer(&oldState.termios))); err != 0 { + return nil, err + } + + return &oldState, nil +} + +func DisableEcho(fd uintptr, out io.Writer, state *State) error { + newState := state.termios + newState.Lflag &^= syscall.ECHO + + HandleInterrupt(fd, out, state) + if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, setTermios, uintptr(unsafe.Pointer(&newState))); err != 0 { + return err + } + return nil +} + +func HandleInterrupt(fd uintptr, out io.Writer, state *State) { + sigchan := make(chan os.Signal, 1) + signal.Notify(sigchan, os.Interrupt) + + go func() { + _ = <-sigchan + fmt.Fprintf(out, "\n") + RestoreTerminal(fd, state) + os.Exit(0) + }() +} + +func SetRawTerminal(fd uintptr, out io.Writer) (*State, error) { oldState, err := MakeRaw(fd) if err != nil { return nil, err } - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt) - go func() { - _ = <-c - RestoreTerminal(fd, oldState) - os.Exit(0) - }() + HandleInterrupt(fd, out, oldState) return oldState, err } From 4089a20cf476d2241b051d9ba913b7adde90661d Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sat, 3 Aug 2013 17:27:15 -0700 Subject: [PATCH 005/128] Exit if there is any error reading from stdin. --- commands.go | 13 +++++++------ term/term.go | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/commands.go b/commands.go index 416076d3d5..51efacd09b 100644 --- a/commands.go +++ b/commands.go @@ -272,13 +272,14 @@ func (cli *DockerCli) CmdLogin(args ...string) error { } } - readInput := func(in io.Reader) (string, error) { + readInput := func(in io.Reader, out io.Writer) string { reader := bufio.NewReader(in) line, err := reader.ReadString('\n') if err != nil { - return "", err + fmt.Fprintln(out, err.Error()) + os.Exit(1) } - return line, nil + return line } authconfig, ok := cli.configFile.Configs[auth.IndexServerAddress()] @@ -288,7 +289,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error { if username == "" { promptDefault("Username", authconfig.Username) - username, _ = readInput(cli.in) + username = readInput(cli.in, cli.out) if username == "" { username = authconfig.Username } @@ -300,7 +301,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error { fmt.Fprintf(cli.out, "Password: ") term.DisableEcho(cli.terminalFd, cli.out, oldState) - password, _ = readInput(cli.in) + password = readInput(cli.in, cli.out) term.RestoreTerminal(cli.terminalFd, oldState) @@ -311,7 +312,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error { if email == "" { promptDefault("\nEmail", authconfig.Email) - email, _ = readInput(cli.in) + email = readInput(cli.in, cli.out) if email == "" { email = authconfig.Email } diff --git a/term/term.go b/term/term.go index 074319c287..2c78d6806e 100644 --- a/term/term.go +++ b/term/term.go @@ -71,7 +71,7 @@ func HandleInterrupt(fd uintptr, out io.Writer, state *State) { go func() { _ = <-sigchan - fmt.Fprintf(out, "\n") + fmt.Fprint(out, "\n") RestoreTerminal(fd, state) os.Exit(0) }() From bdaa87ff2158781c0772e1cab26b52ae6ec07ce4 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sat, 3 Aug 2013 21:30:07 -0700 Subject: [PATCH 006/128] Print a new line after getting the password from stdin. --- commands.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/commands.go b/commands.go index 51efacd09b..814084f353 100644 --- a/commands.go +++ b/commands.go @@ -302,6 +302,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error { term.DisableEcho(cli.terminalFd, cli.out, oldState) password = readInput(cli.in, cli.out) + fmt.Fprint(cli.out, "\n") term.RestoreTerminal(cli.terminalFd, oldState) @@ -311,7 +312,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error { } if email == "" { - promptDefault("\nEmail", authconfig.Email) + promptDefault("Email", authconfig.Email) email = readInput(cli.in, cli.out) if email == "" { email = authconfig.Email From 0e21de9a25edfe56fa996055712d25b7a3345f0d Mon Sep 17 00:00:00 2001 From: "Sean P. Kane" Date: Wed, 7 Aug 2013 09:38:49 -0700 Subject: [PATCH 007/128] Assume that if VAGRANT_DEFAULT_PROVIDER is set we shouldn't install vbox tools --- Vagrantfile | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Vagrantfile b/Vagrantfile index aadabb8711..4ff8c6ec14 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -25,7 +25,8 @@ Vagrant::Config.run do |config| "apt-get update -qq; apt-get install -q -y linux-image-3.8.0-19-generic; " # Add guest additions if local vbox VM is_vbox = true - ARGV.each do |arg| is_vbox &&= !arg.downcase.start_with?("--provider") end + # The logic here makes a few assumptions (i.e. no one uses --provider=virtualbox) + ARGV.each do |arg| is_vbox &&= ( !arg.downcase.start_with?("--provider") && !ENV['VAGRANT_DEFAULT_PROVIDER'] )end if is_vbox pkg_cmd << "apt-get install -q -y linux-headers-3.8.0-19-generic dkms; " \ "echo 'Downloading VBox Guest Additions...'; " \ From 8a18999d2352d9c93e44935222e24874476c5018 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sat, 17 Aug 2013 21:08:00 -0700 Subject: [PATCH 008/128] Add the ISIG syscall back to not kill the client withing a shell with ctrl+c. --- term/termios_darwin.go | 2 +- term/termios_linux.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/term/termios_darwin.go b/term/termios_darwin.go index 0f6b24b184..24e79de4b2 100644 --- a/term/termios_darwin.go +++ b/term/termios_darwin.go @@ -44,7 +44,7 @@ func MakeRaw(fd uintptr) (*State, error) { newState.Iflag &^= (ISTRIP | INLCR | IGNCR | IXON | IXOFF) newState.Iflag |= ICRNL newState.Oflag |= ONLCR - newState.Lflag &^= (ECHO | ICANON) + newState.Lflag &^= (ECHO | ICANON | ISIG) if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(setTermios), uintptr(unsafe.Pointer(&newState))); err != 0 { return nil, err diff --git a/term/termios_linux.go b/term/termios_linux.go index 22f4fff430..6a76460a54 100644 --- a/term/termios_linux.go +++ b/term/termios_linux.go @@ -33,7 +33,7 @@ func MakeRaw(fd uintptr) (*State, error) { newState.Iflag &^= (syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK | syscall.ISTRIP | syscall.INLCR | syscall.IGNCR | syscall.ICRNL | syscall.IXON) newState.Oflag &^= syscall.OPOST - newState.Lflag &^= (syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.IEXTEN) + newState.Lflag &^= (syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG | syscall.EXTEN) newState.Cflag &^= (syscall.CSIZE | syscall.PARENB) newState.Cflag |= syscall.CS8 From 45543d012e98de5ed9b1e415c8ce417fe02d3c55 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sat, 17 Aug 2013 21:29:37 -0700 Subject: [PATCH 009/128] Simplify term signal handler. --- commands.go | 5 +++-- term/term.go | 25 ++++++++++++------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/commands.go b/commands.go index 814084f353..9300758a34 100644 --- a/commands.go +++ b/commands.go @@ -300,7 +300,8 @@ func (cli *DockerCli) CmdLogin(args ...string) error { oldState, _ := term.SaveState(cli.terminalFd) fmt.Fprintf(cli.out, "Password: ") - term.DisableEcho(cli.terminalFd, cli.out, oldState) + term.DisableEcho(cli.terminalFd, oldState) + password = readInput(cli.in, cli.out) fmt.Fprint(cli.out, "\n") @@ -1575,7 +1576,7 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea }) if in != nil && setRawTerminal && cli.isTerminal && os.Getenv("NORAW") == "" { - oldState, err := term.SetRawTerminal(cli.terminalFd, cli.out) + oldState, err := term.SetRawTerminal(cli.terminalFd) if err != nil { return err } diff --git a/term/term.go b/term/term.go index 2c78d6806e..d8d4d1a655 100644 --- a/term/term.go +++ b/term/term.go @@ -54,34 +54,33 @@ func SaveState(fd uintptr) (*State, error) { return &oldState, nil } -func DisableEcho(fd uintptr, out io.Writer, state *State) error { +func DisableEcho(fd uintptr, state *State) error { newState := state.termios newState.Lflag &^= syscall.ECHO - HandleInterrupt(fd, out, state) if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, setTermios, uintptr(unsafe.Pointer(&newState))); err != 0 { return err } + handleInterrupt(fd, state) return nil } -func HandleInterrupt(fd uintptr, out io.Writer, state *State) { +func SetRawTerminal(fd uintptr) (*State, error) { + oldState, err := MakeRaw(fd) + if err != nil { + return nil, err + } + handleInterrupt(fd, oldState) + return oldState, err +} + +func handleInterrupt(fd uintptr, state *State) { sigchan := make(chan os.Signal, 1) signal.Notify(sigchan, os.Interrupt) go func() { _ = <-sigchan - fmt.Fprint(out, "\n") RestoreTerminal(fd, state) os.Exit(0) }() } - -func SetRawTerminal(fd uintptr, out io.Writer) (*State, error) { - oldState, err := MakeRaw(fd) - if err != nil { - return nil, err - } - HandleInterrupt(fd, out, oldState) - return oldState, err -} From 276d2bbf1d400415bec8d4652ac61e570b9206e3 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sat, 17 Aug 2013 22:22:11 -0700 Subject: [PATCH 010/128] Remove unused imports. --- term/term.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/term/term.go b/term/term.go index d8d4d1a655..5929c2caa1 100644 --- a/term/term.go +++ b/term/term.go @@ -1,8 +1,6 @@ package term import ( - "fmt" - "io" "os" "os/signal" "syscall" From e69f7142190451eb1e0553e2dfab8e916dc9cead Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sat, 17 Aug 2013 22:22:24 -0700 Subject: [PATCH 011/128] Fix syscall name. --- term/termios_linux.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/term/termios_linux.go b/term/termios_linux.go index 6a76460a54..4a717c84a7 100644 --- a/term/termios_linux.go +++ b/term/termios_linux.go @@ -33,7 +33,7 @@ func MakeRaw(fd uintptr) (*State, error) { newState.Iflag &^= (syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK | syscall.ISTRIP | syscall.INLCR | syscall.IGNCR | syscall.ICRNL | syscall.IXON) newState.Oflag &^= syscall.OPOST - newState.Lflag &^= (syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG | syscall.EXTEN) + newState.Lflag &^= (syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG | syscall.IEXTEN) newState.Cflag &^= (syscall.CSIZE | syscall.PARENB) newState.Cflag |= syscall.CS8 From 6aff117164a8e665aa9e417ab019581297336d2e Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sat, 17 Aug 2013 22:28:05 -0700 Subject: [PATCH 012/128] Use flag.StringVar to capture the command line flags. --- commands.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/commands.go b/commands.go index 9300758a34..ee71e5fa77 100644 --- a/commands.go +++ b/commands.go @@ -255,9 +255,11 @@ func (cli *DockerCli) CmdBuild(args ...string) error { func (cli *DockerCli) CmdLogin(args ...string) error { cmd := Subcmd("login", "[OPTIONS]", "Register or Login to the docker registry server") - username := *cmd.String("u", "", "username") - password := *cmd.String("p", "", "password") - email := *cmd.String("e", "", "email") + var username, password, email string + + cmd.StringVar(&username, "u", "", "username") + cmd.StringVar(&password, "p", "", "password") + cmd.StringVar(&email, "e", "", "email") err := cmd.Parse(args) if err != nil { From 30726c378572f8aa2256875b1b39b94144acff80 Mon Sep 17 00:00:00 2001 From: Daniel Mizyrycki Date: Tue, 20 Aug 2013 12:18:29 -0700 Subject: [PATCH 013/128] Improve docker-ci deployment and tests --- testing/Vagrantfile | 27 +++++++++++++++++---------- testing/buildbot/master.cfg | 8 ++++---- testing/buildbot/requirements.txt | 1 + 3 files changed, 22 insertions(+), 14 deletions(-) diff --git a/testing/Vagrantfile b/testing/Vagrantfile index 033f14b3de..fd1c5916c8 100644 --- a/testing/Vagrantfile +++ b/testing/Vagrantfile @@ -2,11 +2,10 @@ # vi: set ft=ruby : BOX_NAME = "docker-ci" -BOX_URI = "http://files.vagrantup.com/precise64.box" -AWS_AMI = "ami-d0f89fb9" +BOX_URI = "http://cloud-images.ubuntu.com/vagrant/raring/current/raring-server-cloudimg-amd64-vagrant-disk1.box" +AWS_AMI = "ami-10314d79" DOCKER_PATH = "/data/docker" CFG_PATH = "#{DOCKER_PATH}/testing/buildbot" -BUILDBOT_IP = "192.168.33.41" on_vbox = File.file?("#{File.dirname(__FILE__)}/.vagrant/machines/default/virtualbox/id") | \ Dir.glob("#{File.dirname(__FILE__)}/.vagrant/machines/default/*/id").empty? & \ (on_vbox=true; ARGV.each do |arg| on_vbox &&= !arg.downcase.start_with?("--provider") end; on_vbox) @@ -16,16 +15,22 @@ Vagrant::Config.run do |config| # Setup virtual machine box. This VM configuration code is always executed. config.vm.box = BOX_NAME config.vm.box_url = BOX_URI + config.vm.forward_port 8010, 8010 config.vm.share_folder "v-data", DOCKER_PATH, "#{File.dirname(__FILE__)}/.." - config.vm.network :hostonly, BUILDBOT_IP # Deploy buildbot and its dependencies if it was not done if Dir.glob("#{File.dirname(__FILE__)}/.vagrant/machines/default/*/id").empty? # Add memory limitation capabilities pkg_cmd = 'sed -Ei \'s/^(GRUB_CMDLINE_LINUX_DEFAULT)=.+/\\1="cgroup_enable=memory swapaccount=1 quiet"/\' /etc/default/grub; ' - # Install new kernel - pkg_cmd << "apt-get update -qq; apt-get install -q -y linux-image-generic-lts-raring; " + # Adjust kernel + pkg_cmd << "apt-get update -qq; " + if on_vbox + pkg_cmd << "apt-get install -q -y linux-image-extra-`uname -r`; " + else + pkg_cmd << "apt-get install -q -y linux-image-generic; " + end + # Deploy buildbot CI pkg_cmd << "apt-get install -q -y python-dev python-pip supervisor; " \ "pip install -r #{CFG_PATH}/requirements.txt; " \ @@ -36,10 +41,12 @@ Vagrant::Config.run do |config| "#{CFG_PATH}/setup_credentials.sh #{USER} " \ "#{ENV['REGISTRY_USER']} #{ENV['REGISTRY_PWD']}; " # Install docker dependencies - pkg_cmd << "apt-get install -q -y python-software-properties; " \ - "add-apt-repository -y ppa:dotcloud/docker-golang/ubuntu; apt-get update -qq; " \ - "DEBIAN_FRONTEND=noninteractive apt-get install -q -y lxc git mercurial golang-stable aufs-tools make; " - # Activate new kernel + pkg_cmd << "curl -s https://go.googlecode.com/files/go1.1.1.linux-amd64.tar.gz | " \ + "tar -v -C /usr/local -xz; ln -s /usr/local/go/bin/go /usr/bin/go; " \ + "DEBIAN_FRONTEND=noninteractive apt-get install -q -y lxc git mercurial aufs-tools make; " \ + "export GOPATH=/data/docker-dependencies; go get -d github.com/dotcloud/docker; " \ + "rm -rf ${GOPATH}/src/github.com/dotcloud/docker; " + # Activate new kernel options pkg_cmd << "shutdown -r +1; " config.vm.provision :shell, :inline => pkg_cmd end diff --git a/testing/buildbot/master.cfg b/testing/buildbot/master.cfg index 05dcacbf58..6714f2fa8a 100644 --- a/testing/buildbot/master.cfg +++ b/testing/buildbot/master.cfg @@ -22,7 +22,6 @@ GITHUB_DOCKER = 'github.com/dotcloud/docker' BUILDBOT_PATH = '/data/buildbot' DOCKER_PATH = '/data/docker' BUILDER_PATH = '/data/buildbot/slave/{0}/build'.format(BUILDER_NAME) -DOCKER_BUILD_PATH = BUILDER_PATH + '/src/github.com/dotcloud/docker' # Credentials set by setup.sh and Vagrantfile BUILDBOT_PWD = '' @@ -57,9 +56,10 @@ c['schedulers'] += [Nightly(name='daily', branch=None, builderNames=['coverage', # Docker commit test factory = BuildFactory() factory.addStep(ShellCommand(description='Docker',logEnviron=False,usePTY=True, - command=["sh", "-c", Interpolate("cd ..; rm -rf build; export GOPATH={0}; " - "go get -d {1}; cd {2}; git reset --hard %(src::revision:-unknown)s; " - "go test -v".format(BUILDER_PATH,GITHUB_DOCKER,DOCKER_BUILD_PATH))])) + command=["sh", "-c", Interpolate("cd ..; rm -rf build; mkdir build; " + "cp -r {2}-dependencies/src {0}; export GOPATH={0}; go get {3}; cd {1}; " + "git reset --hard %(src::revision)s; go test -v".format( + BUILDER_PATH, BUILDER_PATH+'/src/'+GITHUB_DOCKER, DOCKER_PATH, GITHUB_DOCKER))])) c['builders'] = [BuilderConfig(name=BUILDER_NAME,slavenames=['buildworker'], factory=factory)] diff --git a/testing/buildbot/requirements.txt b/testing/buildbot/requirements.txt index 4e183ba062..c5d7dd0191 100644 --- a/testing/buildbot/requirements.txt +++ b/testing/buildbot/requirements.txt @@ -5,3 +5,4 @@ buildbot_slave==0.8.7p1 nose==1.2.1 requests==1.1.0 flask==0.10.1 +simplejson==2.3.2 From ee64e099e06175ec9c7735b591f51cfcdfbdd6fe Mon Sep 17 00:00:00 2001 From: Daniel Mizyrycki Date: Tue, 20 Aug 2013 12:32:32 -0700 Subject: [PATCH 014/128] testing, issue #773: Automatically test pull requests on docker-ci --- testing/buildbot/github.py | 169 ++++++++++++++++++++++++++++++++++++ testing/buildbot/master.cfg | 16 ++++ testing/buildbot/setup.sh | 3 + 3 files changed, 188 insertions(+) create mode 100644 testing/buildbot/github.py diff --git a/testing/buildbot/github.py b/testing/buildbot/github.py new file mode 100644 index 0000000000..b0fe98a135 --- /dev/null +++ b/testing/buildbot/github.py @@ -0,0 +1,169 @@ +# This file is part of Buildbot. Buildbot is free software: you can +# redistribute it and/or modify it under the terms of the GNU General Public +# License as published by the Free Software Foundation, version 2. +# +# This program is distributed in the hope that it will be useful, but WITHOUT +# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# this program; if not, write to the Free Software Foundation, Inc., 51 +# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. +# +# Copyright Buildbot Team Members + +#!/usr/bin/env python +""" +github_buildbot.py is based on git_buildbot.py + +github_buildbot.py will determine the repository information from the JSON +HTTP POST it receives from github.com and build the appropriate repository. +If your github repository is private, you must add a ssh key to the github +repository for the user who initiated the build on the buildslave. + +""" + +import re +import datetime +from twisted.python import log +import calendar + +try: + import json + assert json +except ImportError: + import simplejson as json + +# python is silly about how it handles timezones +class fixedOffset(datetime.tzinfo): + """ + fixed offset timezone + """ + def __init__(self, minutes, hours, offsetSign = 1): + self.minutes = int(minutes) * offsetSign + self.hours = int(hours) * offsetSign + self.offset = datetime.timedelta(minutes = self.minutes, + hours = self.hours) + + def utcoffset(self, dt): + return self.offset + + def dst(self, dt): + return datetime.timedelta(0) + +def convertTime(myTestTimestamp): + #"1970-01-01T00:00:00+00:00" + # Normalize myTestTimestamp + if myTestTimestamp[-1] == 'Z': + myTestTimestamp = myTestTimestamp[:-1] + '-00:00' + matcher = re.compile(r'(\d\d\d\d)-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)([-+])(\d\d):(\d\d)') + result = matcher.match(myTestTimestamp) + (year, month, day, hour, minute, second, offsetsign, houroffset, minoffset) = \ + result.groups() + if offsetsign == '+': + offsetsign = 1 + else: + offsetsign = -1 + + offsetTimezone = fixedOffset( minoffset, houroffset, offsetsign ) + myDatetime = datetime.datetime( int(year), + int(month), + int(day), + int(hour), + int(minute), + int(second), + 0, + offsetTimezone) + return calendar.timegm( myDatetime.utctimetuple() ) + +def getChanges(request, options = None): + """ + Reponds only to POST events and starts the build process + + :arguments: + request + the http request object + """ + payload = json.loads(request.args['payload'][0]) + if 'pull_request' in payload: + user = payload['repository']['owner']['login'] + repo = payload['repository']['name'] + repo_url = payload['repository']['html_url'] + else: + user = payload['repository']['owner']['name'] + repo = payload['repository']['name'] + repo_url = payload['repository']['url'] + project = request.args.get('project', None) + if project: + project = project[0] + elif project is None: + project = '' + # This field is unused: + #private = payload['repository']['private'] + changes = process_change(payload, user, repo, repo_url, project) + log.msg("Received %s changes from github" % len(changes)) + return (changes, 'git') + +def process_change(payload, user, repo, repo_url, project): + """ + Consumes the JSON as a python object and actually starts the build. + + :arguments: + payload + Python Object that represents the JSON sent by GitHub Service + Hook. + """ + changes = [] + + newrev = payload['after'] if 'after' in payload else payload['pull_request']['head']['sha'] + refname = payload['ref'] if 'ref' in payload else payload['pull_request']['head']['ref'] + + # We only care about regular heads, i.e. branches + match = re.match(r"^(refs\/heads\/|)([^/]+)$", refname) + if not match: + log.msg("Ignoring refname `%s': Not a branch" % refname) + return [] + + branch = match.groups()[1] + if re.match(r"^0*$", newrev): + log.msg("Branch `%s' deleted, ignoring" % branch) + return [] + else: + if 'pull_request' in payload: + changes = [{ + 'category' : 'github_pullrequest', + 'who' : user, + 'files' : [], + 'comments' : payload['pull_request']['title'], + 'revision' : newrev, + 'when' : convertTime(payload['pull_request']['updated_at']), + 'branch' : branch, + 'revlink' : '{0}/commit/{1}'.format(repo_url,newrev), + 'repository' : repo_url, + 'project' : project }] + return changes + for commit in payload['commits']: + files = [] + if 'added' in commit: + files.extend(commit['added']) + if 'modified' in commit: + files.extend(commit['modified']) + if 'removed' in commit: + files.extend(commit['removed']) + when = convertTime( commit['timestamp']) + log.msg("New revision: %s" % commit['id'][:8]) + chdict = dict( + who = commit['author']['name'] + + " <" + commit['author']['email'] + ">", + files = files, + comments = commit['message'], + revision = commit['id'], + when = when, + branch = branch, + revlink = commit['url'], + repository = repo_url, + project = project) + changes.append(chdict) + return changes + diff --git a/testing/buildbot/master.cfg b/testing/buildbot/master.cfg index 6714f2fa8a..cc261c7a3e 100644 --- a/testing/buildbot/master.cfg +++ b/testing/buildbot/master.cfg @@ -22,6 +22,7 @@ GITHUB_DOCKER = 'github.com/dotcloud/docker' BUILDBOT_PATH = '/data/buildbot' DOCKER_PATH = '/data/docker' BUILDER_PATH = '/data/buildbot/slave/{0}/build'.format(BUILDER_NAME) +PULL_REQUEST_PATH = '/data/buildbot/slave/pullrequest/build' # Credentials set by setup.sh and Vagrantfile BUILDBOT_PWD = '' @@ -48,6 +49,9 @@ c['schedulers'] = [ForceScheduler(name='trigger', builderNames=[BUILDER_NAME, c['schedulers'] += [SingleBranchScheduler(name="all", change_filter=filter.ChangeFilter(branch='master'), treeStableTimer=None, builderNames=[BUILDER_NAME])] +c['schedulers'] += [SingleBranchScheduler(name='pullrequest', + change_filter=filter.ChangeFilter(category='github_pullrequest'), treeStableTimer=None, + builderNames=['pullrequest'])] c['schedulers'] += [Nightly(name='daily', branch=None, builderNames=['coverage','registry'], hour=0, minute=30)] @@ -60,9 +64,21 @@ factory.addStep(ShellCommand(description='Docker',logEnviron=False,usePTY=True, "cp -r {2}-dependencies/src {0}; export GOPATH={0}; go get {3}; cd {1}; " "git reset --hard %(src::revision)s; go test -v".format( BUILDER_PATH, BUILDER_PATH+'/src/'+GITHUB_DOCKER, DOCKER_PATH, GITHUB_DOCKER))])) + c['builders'] = [BuilderConfig(name=BUILDER_NAME,slavenames=['buildworker'], factory=factory)] +# Docker pull request test +factory = BuildFactory() +factory.addStep(ShellCommand(description='pull_request',logEnviron=False,usePTY=True, + command=["sh", "-c", Interpolate("cd ..; rm -rf build; mkdir build; " + "cp -r {2}-dependencies/src {0}; export GOPATH={0}; go get {3}; cd {1}; " + "git fetch %(src::repository)s %(src::branch)s:PR-%(src::branch)s; " + "git checkout %(src::revision)s; git rebase master; go test -v".format( + PULL_REQUEST_PATH, PULL_REQUEST_PATH+'/src/'+GITHUB_DOCKER, DOCKER_PATH, GITHUB_DOCKER))])) +c['builders'] += [BuilderConfig(name='pullrequest',slavenames=['buildworker'], + factory=factory)] + # Docker coverage test coverage_cmd = ('GOPATH=`pwd` go get -d github.com/dotcloud/docker\n' 'GOPATH=`pwd` go get github.com/axw/gocov/gocov\n' diff --git a/testing/buildbot/setup.sh b/testing/buildbot/setup.sh index 937533ba1f..99e4f7f104 100755 --- a/testing/buildbot/setup.sh +++ b/testing/buildbot/setup.sh @@ -36,6 +36,9 @@ run "sed -i -E 's#(SMTP_PWD = ).+#\1\"$SMTP_PWD\"#' master/master.cfg" run "sed -i -E 's#(EMAIL_RCP = ).+#\1\"$EMAIL_RCP\"#' master/master.cfg" run "buildslave create-slave slave $SLAVE_SOCKET $SLAVE_NAME $BUILDBOT_PWD" +# Patch github webstatus to capture pull requests +cp $CFG_PATH/github.py /usr/local/lib/python2.7/dist-packages/buildbot/status/web/hooks + # Allow buildbot subprocesses (docker tests) to properly run in containers, # in particular with docker -u run "sed -i 's/^umask = None/umask = 000/' slave/buildbot.tac" From 62e84785b6ffec04a39cf611d7eaff21f2532195 Mon Sep 17 00:00:00 2001 From: Thijs Terlouw Date: Wed, 21 Aug 2013 15:23:12 +0200 Subject: [PATCH 015/128] proper resolv.conf parsing --- utils/utils.go | 22 +++++++++++++++++++--- utils/utils_test.go | 10 ++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/utils/utils.go b/utils/utils.go index 6a5beb8e48..a8ed1deebc 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -772,21 +772,37 @@ func GetResolvConf() ([]byte, error) { // CheckLocalDns looks into the /etc/resolv.conf, // it returns true if there is a local nameserver or if there is no nameserver. func CheckLocalDns(resolvConf []byte) bool { - if !bytes.Contains(resolvConf, []byte("nameserver")) { + var parsedResolvConf = ParseResolvConf(resolvConf) + if !bytes.Contains(parsedResolvConf, []byte("nameserver")) { return true } - for _, ip := range [][]byte{ []byte("127.0.0.1"), []byte("127.0.1.1"), } { - if bytes.Contains(resolvConf, ip) { + if bytes.Contains(parsedResolvConf, ip) { return true } } return false } +// ParseResolvConf parses the resolv.conf file into lines and strips away comments. +func ParseResolvConf(resolvConf []byte) []byte { + lines := bytes.Split(resolvConf, []byte("\n")) + var noCommentsResolvConf []byte + for _, currentLine := range lines { + var cleanLine = bytes.TrimLeft(currentLine, " \t") + var commentIndex = bytes.Index(cleanLine, []byte("#")) + if ( commentIndex == -1 ) { + noCommentsResolvConf = append(noCommentsResolvConf, cleanLine...) + } else { + noCommentsResolvConf = append(noCommentsResolvConf, cleanLine[:commentIndex]...) + } + } + return noCommentsResolvConf +} + func ParseHost(host string, port int, addr string) string { if strings.HasPrefix(addr, "unix://") { return addr diff --git a/utils/utils_test.go b/utils/utils_test.go index 1030b2902a..cdf9c87073 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -323,6 +323,16 @@ func TestCheckLocalDns(t *testing.T) { nameserver 10.0.2.3 search dotcloud.net`: false, `# Dynamic +#nameserver 127.0.0.1 +nameserver 10.0.2.3 +search dotcloud.net`: false, + `# Dynamic +nameserver 10.0.2.3 #not used 127.0.1.1 +search dotcloud.net`: false, + `# Dynamic +#nameserver 10.0.2.3 +#search dotcloud.net`: true, + `# Dynamic nameserver 127.0.0.1 search dotcloud.net`: true, `# Dynamic From c349b4d56c338bc43c81667bb927518b923998cb Mon Sep 17 00:00:00 2001 From: Thijs Terlouw Date: Wed, 21 Aug 2013 15:48:39 +0200 Subject: [PATCH 016/128] Keep linebreaks and generalize code --- utils/utils.go | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/utils/utils.go b/utils/utils.go index a8ed1deebc..1fd4c77a7e 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -772,7 +772,7 @@ func GetResolvConf() ([]byte, error) { // CheckLocalDns looks into the /etc/resolv.conf, // it returns true if there is a local nameserver or if there is no nameserver. func CheckLocalDns(resolvConf []byte) bool { - var parsedResolvConf = ParseResolvConf(resolvConf) + var parsedResolvConf = StripComments(resolvConf, []byte("#")) if !bytes.Contains(parsedResolvConf, []byte("nameserver")) { return true } @@ -787,20 +787,20 @@ func CheckLocalDns(resolvConf []byte) bool { return false } -// ParseResolvConf parses the resolv.conf file into lines and strips away comments. -func ParseResolvConf(resolvConf []byte) []byte { - lines := bytes.Split(resolvConf, []byte("\n")) - var noCommentsResolvConf []byte +// StripComments parses input into lines and strips away comments. +func StripComments(input []byte, commentMarker []byte) []byte { + lines := bytes.Split(input, []byte("\n")) + var output []byte for _, currentLine := range lines { - var cleanLine = bytes.TrimLeft(currentLine, " \t") - var commentIndex = bytes.Index(cleanLine, []byte("#")) + var commentIndex = bytes.Index(currentLine, commentMarker) if ( commentIndex == -1 ) { - noCommentsResolvConf = append(noCommentsResolvConf, cleanLine...) + output = append(output, currentLine...) } else { - noCommentsResolvConf = append(noCommentsResolvConf, cleanLine[:commentIndex]...) + output = append(output, currentLine[:commentIndex]...) } + output = append(output, []byte("\n")...) } - return noCommentsResolvConf + return output } func ParseHost(host string, port int, addr string) string { From 5e3386473a4d4c37da348c4111164449deb6dc6b Mon Sep 17 00:00:00 2001 From: Daniel Mizyrycki Date: Wed, 21 Aug 2013 17:39:45 -0700 Subject: [PATCH 017/128] testing, issue #773: Add infrastructure docker-ci PR documentation --- hack/infrastructure/docker-ci.rst | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 hack/infrastructure/docker-ci.rst diff --git a/hack/infrastructure/docker-ci.rst b/hack/infrastructure/docker-ci.rst new file mode 100644 index 0000000000..abb8492cf0 --- /dev/null +++ b/hack/infrastructure/docker-ci.rst @@ -0,0 +1,15 @@ +docker-ci github pull request +============================= + +The entire docker pull request test workflow is event driven by github. Its +usage is fully automatic and the results are logged in docker-ci.dotcloud.com + +Each time there is a pull request on docker's github project, github connects +to docker-ci using github's rest API documented in http://developer.github.com/v3/repos/hooks +The issued command to program github's notification PR event was: +curl -u GITHUB_USER:GITHUB_PASSWORD -d '{"name":"web","active":true,"events":["pull_request"],"config":{"url":"http://docker-ci.dotcloud.com:8011/change_hook/github?project=docker"}}' https://api.github.com/repos/dotcloud/docker/hooks + +buildbot (0.8.7p1) was patched using ./testing/buildbot/github.py, so it +can understand the PR data github sends to it. Originally PR #1603 (ee64e099e0) +implemented this capability. Also we added a new scheduler to exclusively filter +PRs. and the 'pullrequest' builder to rebase the PR on top of master and test it. From f83d556bc0dec17af72bf3644fefdf0f232e206f Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sat, 24 Aug 2013 17:16:52 -0700 Subject: [PATCH 018/128] Make Kawsar Saiyeed @KSid the maintainer for contributed tools --- contrib/MAINTAINERS | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contrib/MAINTAINERS b/contrib/MAINTAINERS index 0b7931f907..2531745dc6 100644 --- a/contrib/MAINTAINERS +++ b/contrib/MAINTAINERS @@ -1 +1 @@ -# Maintainer wanted! Enroll on #docker@freenode +Kawsar Saiyeed From 331f983593a527f04046c2d6131847adcdf95e3e Mon Sep 17 00:00:00 2001 From: Greg Thornton Date: Mon, 26 Aug 2013 09:43:49 -0500 Subject: [PATCH 019/128] Start docker after lxc-net to prevent ip forwarding race --- packaging/ubuntu/docker.upstart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packaging/ubuntu/docker.upstart b/packaging/ubuntu/docker.upstart index 143be03402..2370cb5553 100644 --- a/packaging/ubuntu/docker.upstart +++ b/packaging/ubuntu/docker.upstart @@ -1,6 +1,6 @@ description "Run docker" -start on filesystem or runlevel [2345] +start on filesystem and started lxc-net stop on runlevel [!2345] respawn From d80b50d4b4759665b93e713b99239aba9893416e Mon Sep 17 00:00:00 2001 From: jbbarth Date: Mon, 26 Aug 2013 17:45:52 +0200 Subject: [PATCH 020/128] Improve formatting with 'go fmt' as stated in CONTRIBUTING.md As 'go fmt' doesn't support verifying files in multiple directories, it's probably a good idea to run it on all '*.go' files from time to time with something like this: find . -name "*.go" | xargs dirname | sort -u | xargs -n 1 echo go fmt --- commands.go | 2 +- commands_test.go | 1 - docker/docker.go | 2 +- sysinit.go | 3 +-- 4 files changed, 3 insertions(+), 5 deletions(-) diff --git a/commands.go b/commands.go index 236fb65d10..41e20158b8 100644 --- a/commands.go +++ b/commands.go @@ -30,7 +30,7 @@ import ( var ( GITCOMMIT string - VERSION string + VERSION string ) func (cli *DockerCli) getMethod(name string) (reflect.Method, bool) { diff --git a/commands_test.go b/commands_test.go index 25e4804361..2946da8792 100644 --- a/commands_test.go +++ b/commands_test.go @@ -152,7 +152,6 @@ func TestRunWorkdirExists(t *testing.T) { } - func TestRunExit(t *testing.T) { stdin, stdinPipe := io.Pipe() stdout, stdoutPipe := io.Pipe() diff --git a/docker/docker.go b/docker/docker.go index 6ac0c9379d..9bbd40b696 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -16,7 +16,7 @@ import ( var ( GITCOMMIT string - VERSION string + VERSION string ) func main() { diff --git a/sysinit.go b/sysinit.go index aa5d2b2a17..34f1cbdac6 100644 --- a/sysinit.go +++ b/sysinit.go @@ -27,10 +27,9 @@ func setupWorkingDirectory(workdir string) { if workdir == "" { return } - syscall.Chdir(workdir) + syscall.Chdir(workdir) } - // Takes care of dropping privileges to the desired user func changeUser(u string) { if u == "" { From 98018df07856811536722df84b9558e0efdabdca Mon Sep 17 00:00:00 2001 From: shin- Date: Wed, 28 Aug 2013 00:20:35 +0200 Subject: [PATCH 021/128] More descriptive, easier to process container portmappings information in the API --- api_params.go | 16 ++++++++++++++-- commands.go | 18 ++++++++++++++++-- commands_test.go | 1 - container.go | 26 ++++++++++++++++++-------- server.go | 2 +- sysinit.go | 3 +-- 6 files changed, 50 insertions(+), 16 deletions(-) diff --git a/api_params.go b/api_params.go index df879f63ee..43a536d601 100644 --- a/api_params.go +++ b/api_params.go @@ -1,5 +1,7 @@ package docker +import "encoding/json" + type APIHistory struct { ID string `json:"Id"` Tags []string `json:",omitempty"` @@ -47,7 +49,7 @@ type APIContainers struct { Command string Created int64 Status string - Ports string + Ports []APIPort SizeRw int64 SizeRootFs int64 } @@ -67,7 +69,17 @@ type APIRun struct { } type APIPort struct { - Port string + PrivatePort int64 + PublicPort int64 + Type string +} + +func (port *APIPort) MarshalJSON() ([]byte, error) { + return json.Marshal(map[string]interface{}{ + "PrivatePort": port.PrivatePort, + "PublicPort": port.PublicPort, + "Type": port.Type, + }) } type APIVersion struct { diff --git a/commands.go b/commands.go index 236fb65d10..3090086cf0 100644 --- a/commands.go +++ b/commands.go @@ -20,6 +20,7 @@ import ( "path/filepath" "reflect" "runtime" + "sort" "strconv" "strings" "syscall" @@ -996,6 +997,19 @@ func (cli *DockerCli) CmdImages(args ...string) error { return nil } +func displayablePorts(ports []APIPort) string { + result := []string{} + for _, port := range ports { + if port.Type == "tcp" { + result = append(result, fmt.Sprintf("%d->%d", port.PublicPort, port.PrivatePort)) + } else { + result = append(result, fmt.Sprintf("%d->%d/%s", port.PublicPort, port.PrivatePort, port.Type)) + } + } + sort.Strings(result) + return strings.Join(result, ", ") +} + func (cli *DockerCli) CmdPs(args ...string) error { cmd := Subcmd("ps", "[OPTIONS]", "List containers") quiet := cmd.Bool("q", false, "Only display numeric IDs") @@ -1053,9 +1067,9 @@ func (cli *DockerCli) CmdPs(args ...string) error { for _, out := range outs { if !*quiet { if *noTrunc { - fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t", out.ID, out.Image, out.Command, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, out.Ports) + fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t", out.ID, out.Image, out.Command, utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, displayablePorts(out.Ports)) } else { - fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t", utils.TruncateID(out.ID), out.Image, utils.Trunc(out.Command, 20), utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, out.Ports) + fmt.Fprintf(w, "%s\t%s\t%s\t%s ago\t%s\t%s\t", utils.TruncateID(out.ID), out.Image, utils.Trunc(out.Command, 20), utils.HumanDuration(time.Now().Sub(time.Unix(out.Created, 0))), out.Status, displayablePorts(out.Ports)) } if *size { if out.SizeRootFs > 0 { diff --git a/commands_test.go b/commands_test.go index 25e4804361..2946da8792 100644 --- a/commands_test.go +++ b/commands_test.go @@ -152,7 +152,6 @@ func TestRunWorkdirExists(t *testing.T) { } - func TestRunExit(t *testing.T) { stdin, stdinPipe := io.Pipe() stdout, stdoutPipe := io.Pipe() diff --git a/container.go b/container.go index 9099d90f6f..bd33ae802b 100644 --- a/container.go +++ b/container.go @@ -15,7 +15,6 @@ import ( "os/exec" "path" "path/filepath" - "sort" "strconv" "strings" "syscall" @@ -253,17 +252,28 @@ type NetworkSettings struct { PortMapping map[string]PortMapping } -// String returns a human-readable description of the port mapping defined in the settings -func (settings *NetworkSettings) PortMappingHuman() string { - var mapping []string +// returns a more easy to process description of the port mapping defined in the settings +func (settings *NetworkSettings) PortMappingAPI() []APIPort { + var mapping []APIPort for private, public := range settings.PortMapping["Tcp"] { - mapping = append(mapping, fmt.Sprintf("%s->%s", public, private)) + pubint, _ := strconv.ParseInt(public, 0, 0) + privint, _ := strconv.ParseInt(private, 0, 0) + mapping = append(mapping, APIPort{ + PrivatePort: privint, + PublicPort: pubint, + Type: "tcp", + }) } for private, public := range settings.PortMapping["Udp"] { - mapping = append(mapping, fmt.Sprintf("%s->%s/udp", public, private)) + pubint, _ := strconv.ParseInt(public, 0, 0) + privint, _ := strconv.ParseInt(private, 0, 0) + mapping = append(mapping, APIPort{ + PrivatePort: privint, + PublicPort: pubint, + Type: "udp", + }) } - sort.Strings(mapping) - return strings.Join(mapping, ", ") + return mapping } // Inject the io.Reader at the given path. Note: do not close the reader diff --git a/server.go b/server.go index 646cb44877..386f0e27b4 100644 --- a/server.go +++ b/server.go @@ -386,7 +386,7 @@ func (srv *Server) Containers(all, size bool, n int, since, before string) []API c.Command = fmt.Sprintf("%s %s", container.Path, strings.Join(container.Args, " ")) c.Created = container.Created.Unix() c.Status = container.State.String() - c.Ports = container.NetworkSettings.PortMappingHuman() + c.Ports = container.NetworkSettings.PortMappingAPI() if size { c.SizeRw, c.SizeRootFs = container.GetSize() } diff --git a/sysinit.go b/sysinit.go index aa5d2b2a17..34f1cbdac6 100644 --- a/sysinit.go +++ b/sysinit.go @@ -27,10 +27,9 @@ func setupWorkingDirectory(workdir string) { if workdir == "" { return } - syscall.Chdir(workdir) + syscall.Chdir(workdir) } - // Takes care of dropping privileges to the desired user func changeUser(u string) { if u == "" { From 466a88b9a8331154730551c0009b67cded83ba58 Mon Sep 17 00:00:00 2001 From: Thatcher Peskens Date: Tue, 27 Aug 2013 19:59:36 -0700 Subject: [PATCH 022/128] added apt-key finger tip and fingerprint in ubuntu installation page --- docs/sources/installation/ubuntulinux.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/sources/installation/ubuntulinux.rst b/docs/sources/installation/ubuntulinux.rst index 4142a9c373..cb79192709 100644 --- a/docs/sources/installation/ubuntulinux.rst +++ b/docs/sources/installation/ubuntulinux.rst @@ -67,6 +67,7 @@ to follow them again.* .. code-block:: bash # Add the Docker repository key to your local keychain + # using apt-key finger you can check the fingerprint matches 36A1 D786 9245 C895 0F96 6E92 D857 6A8B A88D 21E9 sudo sh -c "curl https://get.docker.io/gpg | apt-key add -" # Add the Docker repository to your apt sources list. @@ -120,6 +121,7 @@ to follow them again.* .. code-block:: bash # Add the Docker repository key to your local keychain + # using apt-key finger you can check the fingerprint matches 36A1 D786 9245 C895 0F96 6E92 D857 6A8B A88D 21E9 sudo sh -c "curl http://get.docker.io/gpg | apt-key add -" # Add the Docker repository to your apt sources list. From 69a93b36d5a43c22402af5beea01d60b1b3be91f Mon Sep 17 00:00:00 2001 From: Emily Rose Date: Wed, 28 Aug 2013 04:12:37 -0700 Subject: [PATCH 023/128] Update docker_remote_api_v1.4.rst Fixed a (very serious) typo. --- docs/sources/api/docker_remote_api_v1.4.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/api/docker_remote_api_v1.4.rst b/docs/sources/api/docker_remote_api_v1.4.rst index d512de9ca3..020bc20ae4 100644 --- a/docs/sources/api/docker_remote_api_v1.4.rst +++ b/docs/sources/api/docker_remote_api_v1.4.rst @@ -679,7 +679,7 @@ Create an image :statuscode 500: server error -Insert a file in a image +Insert a file in an image ************************ .. http:post:: /images/(name)/insert From 20772f90fff54f95fc8cafe2b0c9be4c093cbf64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20Fr=C3=B6ssman?= Date: Wed, 28 Aug 2013 18:02:14 +0200 Subject: [PATCH 024/128] Fix bash completion, remove have Should solve #1639. --- contrib/docker.bash | 2 -- 1 file changed, 2 deletions(-) diff --git a/contrib/docker.bash b/contrib/docker.bash index 32f2b5f8f1..63df98825c 100644 --- a/contrib/docker.bash +++ b/contrib/docker.bash @@ -21,7 +21,6 @@ # If the docker daemon is using a unix socket for communication your user # must have access to the socket for the completions to function correctly -have docker && { __docker_containers_all() { local containers @@ -542,4 +541,3 @@ _docker() } complete -F _docker docker -} \ No newline at end of file From b1eae313ad7d28142c7e0a8a60e3961bed0c84f0 Mon Sep 17 00:00:00 2001 From: Andy Rothfusz Date: Wed, 28 Aug 2013 17:26:10 -0700 Subject: [PATCH 025/128] Fix #1685: Notes on production use. General installation cleanup. --- docs/sources/installation/amazon.rst | 18 +++-- docs/sources/installation/archlinux.rst | 8 +- docs/sources/installation/binaries.rst | 15 ++-- docs/sources/installation/install_header.inc | 7 ++ .../installation/install_unofficial.inc | 7 ++ docs/sources/installation/rackspace.rst | 31 ++++---- docs/sources/installation/ubuntulinux.rst | 42 +++++++---- docs/sources/installation/upgrading.rst | 32 ++++++-- docs/sources/installation/vagrant.rst | 27 ++++--- docs/sources/installation/windows.rst | 73 ++++++++++++------- docs/sources/use/basics.rst | 2 + 11 files changed, 167 insertions(+), 95 deletions(-) create mode 100644 docs/sources/installation/install_header.inc create mode 100644 docs/sources/installation/install_unofficial.inc diff --git a/docs/sources/installation/amazon.rst b/docs/sources/installation/amazon.rst index 333374f976..b9344042fa 100644 --- a/docs/sources/installation/amazon.rst +++ b/docs/sources/installation/amazon.rst @@ -5,18 +5,20 @@ Using Vagrant (Amazon EC2) ========================== -This page explains how to setup and run an Amazon EC2 instance from your local machine. -Vagrant is not necessary to run Docker on EC2. You can follow the :ref:`ubuntu_linux` instructions -installing Docker on any EC2 instance running Ubuntu +This page explains how to setup and run an Amazon EC2 instance from +your local machine. **Vagrant is not necessary to run Docker on +EC2.** You can follow the :ref:`ubuntu_linux` instructions installing +Docker on any EC2 instance running Ubuntu. - Please note this is a community contributed installation path. The only 'official' installation is using the - :ref:`ubuntu_linux` installation path. This version may sometimes be out of date. - - Installation ------------ -Docker can now be installed on Amazon EC2 with a single vagrant command. Vagrant 1.1 or higher is required. +.. include:: install_header.inc + +.. include:: install_unofficial.inc + +Docker can now be installed on Amazon EC2 with a single vagrant +command. Vagrant 1.1 or higher is required. 1. Install vagrant from http://www.vagrantup.com/ (or use your package manager) 2. Install the vagrant aws plugin diff --git a/docs/sources/installation/archlinux.rst b/docs/sources/installation/archlinux.rst index 722c150194..4f0f211747 100644 --- a/docs/sources/installation/archlinux.rst +++ b/docs/sources/installation/archlinux.rst @@ -7,10 +7,6 @@ Arch Linux ========== - Please note this is a community contributed installation path. The only 'official' installation is using the - :ref:`ubuntu_linux` installation path. This version may sometimes be out of date. - - Installing on Arch Linux is not officially supported but can be handled via either of the following AUR packages: @@ -36,6 +32,10 @@ either AUR package. Installation ------------ +.. include:: install_header.inc + +.. include:: install_unofficial.inc + The instructions here assume **yaourt** is installed. See `Arch User Repository `_ for information on building and installing packages from the AUR if you have not diff --git a/docs/sources/installation/binaries.rst b/docs/sources/installation/binaries.rst index 24de528145..fea48bd7a9 100644 --- a/docs/sources/installation/binaries.rst +++ b/docs/sources/installation/binaries.rst @@ -7,9 +7,10 @@ Binaries ======== - **Please note this project is currently under heavy development. It should not be used in production.** +.. include:: install_header.inc -**This instruction set is meant for hackers who want to try out Docker on a variety of environments.** +**This instruction set is meant for hackers who want to try out Docker +on a variety of environments.** Right now, the officially supported distributions are: @@ -23,14 +24,10 @@ But we know people have had success running it under - Suse - :ref:`arch_linux` +Check Your Kernel +----------------- -Dependencies: -------------- - -* 3.8 Kernel (read more about :ref:`kernel`) -* AUFS filesystem support -* lxc -* xz-utils +Your host's Linux kernel must meet the Docker :ref:`kernel` Get the docker binary: ---------------------- diff --git a/docs/sources/installation/install_header.inc b/docs/sources/installation/install_header.inc new file mode 100644 index 0000000000..c9b9e4c494 --- /dev/null +++ b/docs/sources/installation/install_header.inc @@ -0,0 +1,7 @@ + +.. note:: + + Docker is still under heavy development! We don't recommend using + it in production yet, but we're getting closer with each + release. Please see our blog post, `"Getting to Docker 1.0" + `_ diff --git a/docs/sources/installation/install_unofficial.inc b/docs/sources/installation/install_unofficial.inc new file mode 100644 index 0000000000..8d121918b5 --- /dev/null +++ b/docs/sources/installation/install_unofficial.inc @@ -0,0 +1,7 @@ + +.. note:: + + This is a community contributed installation path. The only + 'official' installation is using the :ref:`ubuntu_linux` + installation path. This version may be out of date because it + depends on some binaries to be updated and published diff --git a/docs/sources/installation/rackspace.rst b/docs/sources/installation/rackspace.rst index 7f360682e2..2a4bdbc955 100644 --- a/docs/sources/installation/rackspace.rst +++ b/docs/sources/installation/rackspace.rst @@ -6,21 +6,22 @@ Rackspace Cloud =============== - Please note this is a community contributed installation path. The only 'official' installation is using the - :ref:`ubuntu_linux` installation path. This version may sometimes be out of date. +.. include:: install_unofficial.inc - -Installing Docker on Ubuntu provided by Rackspace is pretty straightforward, and you should mostly be able to follow the +Installing Docker on Ubuntu provided by Rackspace is pretty +straightforward, and you should mostly be able to follow the :ref:`ubuntu_linux` installation guide. **However, there is one caveat:** -If you are using any linux not already shipping with the 3.8 kernel you will need to install it. And this is a little -more difficult on Rackspace. +If you are using any linux not already shipping with the 3.8 kernel +you will need to install it. And this is a little more difficult on +Rackspace. -Rackspace boots their servers using grub's menu.lst and does not like non 'virtual' packages (e.g. xen compatible) -kernels there, although they do work. This makes ``update-grub`` to not have the expected result, and you need to -set the kernel manually. +Rackspace boots their servers using grub's ``menu.lst`` and does not +like non 'virtual' packages (e.g. xen compatible) kernels there, +although they do work. This makes ``update-grub`` to not have the +expected result, and you need to set the kernel manually. **Do not attempt this on a production machine!** @@ -33,7 +34,8 @@ set the kernel manually. apt-get install linux-generic-lts-raring -Great, now you have kernel installed in /boot/, next is to make it boot next time. +Great, now you have kernel installed in ``/boot/``, next is to make it +boot next time. .. code-block:: bash @@ -43,9 +45,10 @@ Great, now you have kernel installed in /boot/, next is to make it boot next tim # this should return some results -Now you need to manually edit /boot/grub/menu.lst, you will find a section at the bottom with the existing options. -Copy the top one and substitute the new kernel into that. Make sure the new kernel is on top, and double check kernel -and initrd point to the right files. +Now you need to manually edit ``/boot/grub/menu.lst``, you will find a +section at the bottom with the existing options. Copy the top one and +substitute the new kernel into that. Make sure the new kernel is on +top, and double check kernel and initrd point to the right files. Make special care to double check the kernel and initrd entries. @@ -92,4 +95,4 @@ Verify the kernel was updated # nice! 3.8. -Now you can finish with the :ref:`ubuntu_linux` instructions. \ No newline at end of file +Now you can finish with the :ref:`ubuntu_linux` instructions. diff --git a/docs/sources/installation/ubuntulinux.rst b/docs/sources/installation/ubuntulinux.rst index 4142a9c373..dec2eb59cb 100644 --- a/docs/sources/installation/ubuntulinux.rst +++ b/docs/sources/installation/ubuntulinux.rst @@ -2,15 +2,17 @@ :description: Please note this project is currently under heavy development. It should not be used in production. :keywords: Docker, Docker documentation, requirements, virtualbox, vagrant, git, ssh, putty, cygwin, linux -**These instructions have changed for 0.6. If you are upgrading from an earlier version, you will need to follow them again.** - .. _ubuntu_linux: Ubuntu Linux ============ - **Please note this project is currently under heavy development. It should not be used in production.** +.. warning:: + These instructions have changed for 0.6. If you are upgrading from + an earlier version, you will need to follow them again. + +.. include:: install_header.inc Right now, the officially supported distribution are: @@ -22,7 +24,8 @@ Docker has the following dependencies * Linux kernel 3.8 (read more about :ref:`kernel`) * AUFS file system support (we are working on BTRFS support as an alternative) -Please read :ref:`ufw`, if you plan to use `UFW (Uncomplicated Firewall) `_ +Please read :ref:`ufw`, if you plan to use `UFW (Uncomplicated +Firewall) `_ .. _ubuntu_precise: @@ -38,12 +41,13 @@ Dependencies **Linux kernel 3.8** Due to a bug in LXC, docker works best on the 3.8 kernel. Precise -comes with a 3.2 kernel, so we need to upgrade it. The kernel you'll install when following these steps -comes with AUFS built in. We also include the generic headers -to enable packages that depend on them, like ZFS and the VirtualBox -guest additions. If you didn't install the headers for your "precise" -kernel, then you can skip these headers for the "raring" kernel. But -it is safer to include them if you're not sure. +comes with a 3.2 kernel, so we need to upgrade it. The kernel you'll +install when following these steps comes with AUFS built in. We also +include the generic headers to enable packages that depend on them, +like ZFS and the VirtualBox guest additions. If you didn't install the +headers for your "precise" kernel, then you can skip these headers for +the "raring" kernel. But it is safer to include them if you're not +sure. .. code-block:: bash @@ -59,10 +63,13 @@ it is safer to include them if you're not sure. Installation ------------ +.. warning:: + + These instructions have changed for 0.6. If you are upgrading from + an earlier version, you will need to follow them again. + Docker is available as a Debian package, which makes installation easy. -*Please note that these instructions have changed for 0.6. If you are upgrading from an earlier version, you will need -to follow them again.* .. code-block:: bash @@ -136,7 +143,8 @@ Verify it worked .. code-block:: bash - # download the base 'ubuntu' container and run bash inside it while setting up an interactive shell + # download the base 'ubuntu' container + # and run bash inside it while setting up an interactive shell sudo docker run -i -t ubuntu /bin/bash # type exit to exit @@ -150,7 +158,8 @@ Verify it worked Docker and UFW ^^^^^^^^^^^^^^ -Docker uses a bridge to manage containers networking, by default UFW drop all `forwarding`, a first step is to enable forwarding: +Docker uses a bridge to manage containers networking, by default UFW +drop all `forwarding`, a first step is to enable forwarding: .. code-block:: bash @@ -168,8 +177,9 @@ Then reload UFW: sudo ufw reload -UFW's default set of rules denied all `incoming`, so if you want to be able to reach your containers from another host, -you should allow incoming connections on the docker port (default 4243): +UFW's default set of rules denied all `incoming`, so if you want to be +able to reach your containers from another host, you should allow +incoming connections on the docker port (default 4243): .. code-block:: bash diff --git a/docs/sources/installation/upgrading.rst b/docs/sources/installation/upgrading.rst index 9fa47904be..47482314f3 100644 --- a/docs/sources/installation/upgrading.rst +++ b/docs/sources/installation/upgrading.rst @@ -5,18 +5,32 @@ .. _upgrading: Upgrading -============ +========= -**These instructions are for upgrading Docker** +The technique for upgrading ``docker`` to a newer version depends on +how you installed ``docker``. + +.. versionadded:: 0.5.3 + You may wish to add a ``docker`` group to your system to avoid using sudo with ``docker``. (see :ref:`dockergroup`) -After normal installation -------------------------- +After ``apt-get`` +----------------- -If you installed Docker normally using apt-get or used Vagrant, use apt-get to upgrade. +If you installed Docker using ``apt-get`` or Vagrant, then you should +use ``apt-get`` to upgrade. + +.. versionadded:: 0.6 + Add Docker repository information to your system first. .. code-block:: bash + # Add the Docker repository key to your local keychain + sudo sh -c "curl https://get.docker.io/gpg | apt-key add -" + + # Add the Docker repository to your apt sources list. + sudo sh -c "echo deb https://get.docker.io/ubuntu docker main > /etc/apt/sources.list.d/docker.list" + # update your sources list sudo apt-get update @@ -27,7 +41,7 @@ If you installed Docker normally using apt-get or used Vagrant, use apt-get to u After manual installation ------------------------- -If you installed the Docker binary +If you installed the Docker :ref:`binaries` then follow these steps: .. code-block:: bash @@ -48,8 +62,10 @@ If you installed the Docker binary tar -xf docker-latest.tgz -Start docker in daemon mode (-d) and disconnect (&) starting ./docker will start the version in your current dir rather than a version which -might reside in your path. +Start docker in daemon mode (``-d``) and disconnect, running the +daemon in the background (``&``). Starting as ``./docker`` guarantees +to run the version in your current directory rather than a version +which might reside in your path. .. code-block:: bash diff --git a/docs/sources/installation/vagrant.rst b/docs/sources/installation/vagrant.rst index 568ec584ec..14f5bf5cdd 100644 --- a/docs/sources/installation/vagrant.rst +++ b/docs/sources/installation/vagrant.rst @@ -2,31 +2,36 @@ :description: This guide will setup a new virtualbox virtual machine with docker installed on your computer. :keywords: Docker, Docker documentation, virtualbox, vagrant, git, ssh, putty, cygwin -**Vagrant installation is temporarily out of date, it will be updated for 0.6 soon.** - .. _install_using_vagrant: Using Vagrant (Mac, Linux) ========================== -This guide will setup a new virtualbox virtual machine with docker installed on your computer. This works on most operating -systems, including MacOX, Windows, Linux, FreeBSD and others. If you can install these and have at least 400Mb RAM -to spare you should be good. - +This guide will setup a new virtualbox virtual machine with docker +installed on your computer. This works on most operating systems, +including MacOX, Windows, Linux, FreeBSD and others. If you can +install these and have at least 400Mb RAM to spare you should be good. Install Vagrant and Virtualbox ------------------------------ -1. Install virtualbox from https://www.virtualbox.org/ (or use your package manager) -2. Install vagrant from http://www.vagrantup.com/ (or use your package manager) -3. Install git if you had not installed it before, check if it is installed by running - ``git`` in a terminal window +.. include:: install_header.inc + +.. include:: install_unofficial.inc + +#. Install virtualbox from https://www.virtualbox.org/ (or use your + package manager) +#. Install vagrant from http://www.vagrantup.com/ (or use your package + manager) +#. Install git if you had not installed it before, check if it is + installed by running ``git`` in a terminal window Spin it up ---------- -1. Fetch the docker sources (this includes the Vagrantfile for machine setup). +1. Fetch the docker sources (this includes the ``Vagrantfile`` for + machine setup). .. code-block:: bash diff --git a/docs/sources/installation/windows.rst b/docs/sources/installation/windows.rst index 889db4c670..a6b30aa41e 100644 --- a/docs/sources/installation/windows.rst +++ b/docs/sources/installation/windows.rst @@ -2,21 +2,21 @@ :description: Docker's tutorial to run docker on Windows :keywords: Docker, Docker documentation, Windows, requirements, virtualbox, vagrant, git, ssh, putty, cygwin -**Vagrant installation is temporarily out of date, it will be updated for 0.6 soon.** - .. _windows: Using Vagrant (Windows) ======================= - Please note this is a community contributed installation path. The only 'official' installation is using the :ref:`ubuntu_linux` installation path. This version - may be out of date because it depends on some binaries to be updated and published +Docker can run on Windows using a VM like VirtualBox. You then run +Linux within the VM. - - -Requirements +Installation ------------ +.. include:: install_header.inc + +.. include:: install_unofficial.inc + 1. Install virtualbox from https://www.virtualbox.org - or follow this tutorial__ .. __: http://www.slideshare.net/julienbarbier42/install-virtualbox-on-windows-7 @@ -35,7 +35,10 @@ We recommend having at least 2Gb of free disk space and 2Gb of RAM (or more). Opening a command prompt ------------------------ -First open a cmd prompt. Press Windows key and then press “R” key. This will open the RUN dialog box for you. Type “cmd” and press Enter. Or you can click on Start, type “cmd” in the “Search programs and files” field, and click on cmd.exe. +First open a cmd prompt. Press Windows key and then press “R” +key. This will open the RUN dialog box for you. Type “cmd” and press +Enter. Or you can click on Start, type “cmd” in the “Search programs +and files” field, and click on cmd.exe. .. image:: images/win/_01.gif :alt: Git install @@ -47,14 +50,17 @@ This should open a cmd prompt window. :alt: run docker :align: center -Alternatively, you can also use a Cygwin terminal, or Git Bash (or any other command line program you are usually using). The next steps would be the same. +Alternatively, you can also use a Cygwin terminal, or Git Bash (or any +other command line program you are usually using). The next steps +would be the same. .. _launch_ubuntu: Launch an Ubuntu virtual server ------------------------------- -Let’s download and run an Ubuntu image with docker binaries already installed. +Let’s download and run an Ubuntu image with docker binaries already +installed. .. code-block:: bash @@ -66,7 +72,9 @@ Let’s download and run an Ubuntu image with docker binaries already installed. :alt: run docker :align: center -Congratulations! You are running an Ubuntu server with docker installed on it. You do not see it though, because it is running in the background. +Congratulations! You are running an Ubuntu server with docker +installed on it. You do not see it though, because it is running in +the background. Log onto your Ubuntu server --------------------------- @@ -85,7 +93,12 @@ Run the following command vagrant ssh -You may see an error message starting with “`ssh` executable not found”. In this case it means that you do not have SSH in your PATH. If you do not have SSH in your PATH you can set it up with the “set” command. For instance, if your ssh.exe is in the folder named “C:\Program Files (x86)\Git\bin”, then you can run the following command: +You may see an error message starting with “`ssh` executable not +found”. In this case it means that you do not have SSH in your +PATH. If you do not have SSH in your PATH you can set it up with the +“set” command. For instance, if your ssh.exe is in the folder named +“C:\Program Files (x86)\Git\bin”, then you can run the following +command: .. code-block:: bash @@ -104,13 +117,16 @@ First step is to get the IP and port of your Ubuntu server. Simply run: vagrant ssh-config -You should see an output with HostName and Port information. In this example, HostName is 127.0.0.1 and port is 2222. And the User is “vagrant”. The password is not shown, but it is also “vagrant”. +You should see an output with HostName and Port information. In this +example, HostName is 127.0.0.1 and port is 2222. And the User is +“vagrant”. The password is not shown, but it is also “vagrant”. .. image:: images/win/ssh-config.gif :alt: run docker :align: center -You can now use this information for connecting via SSH to your server. To do so you can: +You can now use this information for connecting via SSH to your +server. To do so you can: - Use putty.exe OR - Use SSH from a terminal @@ -118,8 +134,9 @@ You can now use this information for connecting via SSH to your server. To do so Use putty.exe ''''''''''''' -You can download putty.exe from this page http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html -Launch putty.exe and simply enter the information you got from last step. +You can download putty.exe from this page +http://www.chiark.greenend.org.uk/~sgtatham/putty/download.html Launch +putty.exe and simply enter the information you got from last step. .. image:: images/win/putty.gif :alt: run docker @@ -134,7 +151,9 @@ Open, and enter user = vagrant and password = vagrant. SSH from a terminal ''''''''''''''''''' -You can also run this command on your favorite terminal (windows prompt, cygwin, git-bash, …). Make sure to adapt the IP and port from what you got from the vagrant ssh-config command. +You can also run this command on your favorite terminal (windows +prompt, cygwin, git-bash, …). Make sure to adapt the IP and port from +what you got from the vagrant ssh-config command. .. code-block:: bash @@ -146,12 +165,14 @@ Enter user = vagrant and password = vagrant. :alt: run docker :align: center -Congratulations, you are now logged onto your Ubuntu Server, running on top of your Windows machine ! +Congratulations, you are now logged onto your Ubuntu Server, running +on top of your Windows machine ! Running Docker -------------- -First you have to be root in order to run docker. Simply run the following command: +First you have to be root in order to run docker. Simply run the +following command: .. code-block:: bash @@ -179,10 +200,11 @@ VM does not boot .. image:: images/win/ts_go_bios.JPG -If you run into this error message "The VM failed to remain in the 'running' -state while attempting to boot", please check that your computer has virtualization -technology available and activated by going to the BIOS. Here's an example for an HP -computer (System configuration / Device configuration) +If you run into this error message "The VM failed to remain in the +'running' state while attempting to boot", please check that your +computer has virtualization technology available and activated by +going to the BIOS. Here's an example for an HP computer (System +configuration / Device configuration) .. image:: images/win/hp_bios_vm.JPG @@ -192,5 +214,6 @@ Docker is not installed .. image:: images/win/ts_no_docker.JPG -If you run into this error message "The program 'docker' is currently not installed", -try deleting the docker folder and restart from :ref:`launch_ubuntu` +If you run into this error message "The program 'docker' is currently +not installed", try deleting the docker folder and restart from +:ref:`launch_ubuntu` diff --git a/docs/sources/use/basics.rst b/docs/sources/use/basics.rst index acae031f09..d37bf40b05 100644 --- a/docs/sources/use/basics.rst +++ b/docs/sources/use/basics.rst @@ -37,6 +37,8 @@ Running an interactive shell # use the escape sequence Ctrl-p + Ctrl-q sudo docker run -i -t ubuntu /bin/bash +.. _dockergroup: + Why ``sudo``? ------------- From c1f6914e4374fcafa710c75eb23e8cffeeb45169 Mon Sep 17 00:00:00 2001 From: Emily Rose Date: Wed, 28 Aug 2013 20:01:18 -0700 Subject: [PATCH 026/128] Correct number of asterisks. --- docs/sources/api/docker_remote_api_v1.4.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/api/docker_remote_api_v1.4.rst b/docs/sources/api/docker_remote_api_v1.4.rst index 020bc20ae4..729f52ca88 100644 --- a/docs/sources/api/docker_remote_api_v1.4.rst +++ b/docs/sources/api/docker_remote_api_v1.4.rst @@ -680,7 +680,7 @@ Create an image Insert a file in an image -************************ +************************* .. http:post:: /images/(name)/insert From 37a236947eaa611a441328a0dfc015091402292b Mon Sep 17 00:00:00 2001 From: Andrew Munsell Date: Wed, 28 Aug 2013 22:18:47 -0700 Subject: [PATCH 027/128] Add privileged flag in documentation for container creation --- docs/sources/api/docker_remote_api_v1.4.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/sources/api/docker_remote_api_v1.4.rst b/docs/sources/api/docker_remote_api_v1.4.rst index 0adb5b834e..85012e9af2 100644 --- a/docs/sources/api/docker_remote_api_v1.4.rst +++ b/docs/sources/api/docker_remote_api_v1.4.rst @@ -119,6 +119,7 @@ Create a container "AttachStdout":true, "AttachStderr":true, "PortSpecs":null, + "Privileged": false, "Tty":false, "OpenStdin":false, "StdinOnce":false, From 18d572abb4b633d766c9a28119a118761103f5f8 Mon Sep 17 00:00:00 2001 From: Emily Rose Date: Wed, 28 Aug 2013 22:28:31 -0700 Subject: [PATCH 028/128] Added Emily Rose to AUTHORS. --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 271cc5f65b..5bf18aa51d 100644 --- a/AUTHORS +++ b/AUTHORS @@ -34,6 +34,7 @@ Dominik Honnef Don Spaulding Dr Nic Williams Elias Probst +Emily Rose Eric Hanchrow Eric Myhre Erno Hopearuoho From 075253238dff72bdf03179fcb1d2b90ca097e7f0 Mon Sep 17 00:00:00 2001 From: dsissitka Date: Thu, 29 Aug 2013 12:27:35 -0400 Subject: [PATCH 029/128] Fixed a minor syntax error. --- docs/sources/api/docker_remote_api_v1.4.rst | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/sources/api/docker_remote_api_v1.4.rst b/docs/sources/api/docker_remote_api_v1.4.rst index 0adb5b834e..442235ce6f 100644 --- a/docs/sources/api/docker_remote_api_v1.4.rst +++ b/docs/sources/api/docker_remote_api_v1.4.rst @@ -1091,15 +1091,15 @@ Create a new image from a container's changes {"Id":"596069db4bf5"} - :query container: source container - :query repo: repository - :query tag: tag - :query m: commit message - :query author: author (eg. "John Hannibal Smith ") - :query run: config automatically applied when the image is run. (ex: {"Cmd": ["cat", "/world"], "PortSpecs":["22"]}) - :statuscode 201: no error - :statuscode 404: no such container - :statuscode 500: server error + :query container: source container + :query repo: repository + :query tag: tag + :query m: commit message + :query author: author (eg. "John Hannibal Smith ") + :query run: config automatically applied when the image is run. (ex: {"Cmd": ["cat", "/world"], "PortSpecs":["22"]}) + :statuscode 201: no error + :statuscode 404: no such container + :statuscode 500: server error Monitor Docker's events From 57892365ef325d824ba6ce45a8e24e48cd117f0c Mon Sep 17 00:00:00 2001 From: dsissitka Date: Thu, 29 Aug 2013 12:35:37 -0400 Subject: [PATCH 030/128] Fixed a minor syntax error. --- docs/sources/api/docker_remote_api_v1.4.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/api/docker_remote_api_v1.4.rst b/docs/sources/api/docker_remote_api_v1.4.rst index 0adb5b834e..59536bfaff 100644 --- a/docs/sources/api/docker_remote_api_v1.4.rst +++ b/docs/sources/api/docker_remote_api_v1.4.rst @@ -1082,7 +1082,7 @@ Create a new image from a container's changes POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1 - **Example response**: + **Example response**: .. sourcecode:: http From 230dc07d372cccb1aa1198ae300d06e418c73af6 Mon Sep 17 00:00:00 2001 From: Andy Rothfusz Date: Thu, 29 Aug 2013 11:24:59 -0700 Subject: [PATCH 031/128] fix logo path --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index accb7d2e9b..336dd94302 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Platform-as-a-Service. It benefits directly from the experience accumulated over several years of large-scale operation and support of hundreds of thousands of applications and databases. -![Docker L](docs/sources/concepts/images/dockerlogo-h.png "Docker") +![Docker L](docs/sources/static_files/dockerlogo-h.png "Docker") ## Better than VMs From f1d0625cf895abd1e6b22db3470216b9b9c5ef29 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Fri, 26 Jul 2013 17:40:45 -0700 Subject: [PATCH 032/128] Exit from `docker login` on SIGTERM and SIGINT. Fixes #1299. --- commands.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/commands.go b/commands.go index 236fb65d10..53f6706ae4 100644 --- a/commands.go +++ b/commands.go @@ -333,6 +333,14 @@ func (cli *DockerCli) CmdLogin(args ...string) error { authconfig = auth.AuthConfig{} } + c := make(chan os.Signal, 1) + signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) + go func() { + for sig := range c { + os.Exit(1) + } + }() + if *flUsername == "" { promptDefault("Username", authconfig.Username) username = readAndEchoString(cli.in, cli.out) From c3154fdf4d15aed049a9c18a36cc8511ed7e82c1 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Fri, 26 Jul 2013 18:12:05 -0700 Subject: [PATCH 033/128] Use a more idiomatic syntax to capture the exit. --- commands.go | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/commands.go b/commands.go index 53f6706ae4..e078a97fd1 100644 --- a/commands.go +++ b/commands.go @@ -333,12 +333,11 @@ func (cli *DockerCli) CmdLogin(args ...string) error { authconfig = auth.AuthConfig{} } - c := make(chan os.Signal, 1) - signal.Notify(c, syscall.SIGINT, syscall.SIGTERM) + sigchan := make(chan os.Signal, 1) + signal.Notify(sigchan, syscall.SIGINT, syscall.SIGTERM) go func() { - for sig := range c { - os.Exit(1) - } + <-sigchan + os.Exit(1) }() if *flUsername == "" { From 23dc52f52804219bd57b34217d8778bda0444f13 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sat, 27 Jul 2013 09:13:02 -0700 Subject: [PATCH 034/128] Allow to generate signals when termios is in raw mode. --- commands.go | 7 ------- term/termios_darwin.go | 2 +- term/termios_linux.go | 2 +- 3 files changed, 2 insertions(+), 9 deletions(-) diff --git a/commands.go b/commands.go index e078a97fd1..236fb65d10 100644 --- a/commands.go +++ b/commands.go @@ -333,13 +333,6 @@ func (cli *DockerCli) CmdLogin(args ...string) error { authconfig = auth.AuthConfig{} } - sigchan := make(chan os.Signal, 1) - signal.Notify(sigchan, syscall.SIGINT, syscall.SIGTERM) - go func() { - <-sigchan - os.Exit(1) - }() - if *flUsername == "" { promptDefault("Username", authconfig.Username) username = readAndEchoString(cli.in, cli.out) diff --git a/term/termios_darwin.go b/term/termios_darwin.go index 24e79de4b2..0f6b24b184 100644 --- a/term/termios_darwin.go +++ b/term/termios_darwin.go @@ -44,7 +44,7 @@ func MakeRaw(fd uintptr) (*State, error) { newState.Iflag &^= (ISTRIP | INLCR | IGNCR | IXON | IXOFF) newState.Iflag |= ICRNL newState.Oflag |= ONLCR - newState.Lflag &^= (ECHO | ICANON | ISIG) + newState.Lflag &^= (ECHO | ICANON) if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(setTermios), uintptr(unsafe.Pointer(&newState))); err != 0 { return nil, err diff --git a/term/termios_linux.go b/term/termios_linux.go index 4a717c84a7..22f4fff430 100644 --- a/term/termios_linux.go +++ b/term/termios_linux.go @@ -33,7 +33,7 @@ func MakeRaw(fd uintptr) (*State, error) { newState.Iflag &^= (syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK | syscall.ISTRIP | syscall.INLCR | syscall.IGNCR | syscall.ICRNL | syscall.IXON) newState.Oflag &^= syscall.OPOST - newState.Lflag &^= (syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG | syscall.IEXTEN) + newState.Lflag &^= (syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.IEXTEN) newState.Cflag &^= (syscall.CSIZE | syscall.PARENB) newState.Cflag |= syscall.CS8 From 2357fecc92c57e2fcd4a37c60d713508210358f7 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sat, 3 Aug 2013 16:43:20 -0700 Subject: [PATCH 035/128] Stop making a raw terminal to ask for registry login credentials. It only disables echo asking for the password and lets the terminal to handle everything else. It fixes #1392 since blank spaces are not discarded as they did before. It also cleans the login code a little bit to improve readability. --- commands.go | 113 +++++++++++++++------------------------------------ term/term.go | 44 ++++++++++++++++---- 2 files changed, 68 insertions(+), 89 deletions(-) diff --git a/commands.go b/commands.go index 236fb65d10..0643bc8d58 100644 --- a/commands.go +++ b/commands.go @@ -2,6 +2,7 @@ package docker import ( "archive/tar" + "bufio" "bytes" "encoding/json" "flag" @@ -25,7 +26,6 @@ import ( "syscall" "text/tabwriter" "time" - "unicode" ) var ( @@ -252,75 +252,18 @@ func (cli *DockerCli) CmdBuild(args ...string) error { // 'docker login': login / register a user to registry service. func (cli *DockerCli) CmdLogin(args ...string) error { - var readStringOnRawTerminal = func(stdin io.Reader, stdout io.Writer, echo bool) string { - char := make([]byte, 1) - buffer := make([]byte, 64) - var i = 0 - for i < len(buffer) { - n, err := stdin.Read(char) - if n > 0 { - if char[0] == '\r' || char[0] == '\n' { - stdout.Write([]byte{'\r', '\n'}) - break - } else if char[0] == 127 || char[0] == '\b' { - if i > 0 { - if echo { - stdout.Write([]byte{'\b', ' ', '\b'}) - } - i-- - } - } else if !unicode.IsSpace(rune(char[0])) && - !unicode.IsControl(rune(char[0])) { - if echo { - stdout.Write(char) - } - buffer[i] = char[0] - i++ - } - } - if err != nil { - if err != io.EOF { - fmt.Fprintf(stdout, "Read error: %v\r\n", err) - } - break - } - } - return string(buffer[:i]) - } - var readAndEchoString = func(stdin io.Reader, stdout io.Writer) string { - return readStringOnRawTerminal(stdin, stdout, true) - } - var readString = func(stdin io.Reader, stdout io.Writer) string { - return readStringOnRawTerminal(stdin, stdout, false) - } - cmd := Subcmd("login", "[OPTIONS]", "Register or Login to the docker registry server") - flUsername := cmd.String("u", "", "username") - flPassword := cmd.String("p", "", "password") - flEmail := cmd.String("e", "", "email") + + username := *cmd.String("u", "", "username") + password := *cmd.String("p", "", "password") + email := *cmd.String("e", "", "email") err := cmd.Parse(args) + if err != nil { return nil } - cli.LoadConfigFile() - - var oldState *term.State - if *flUsername == "" || *flPassword == "" || *flEmail == "" { - oldState, err = term.SetRawTerminal(cli.terminalFd) - if err != nil { - return err - } - defer term.RestoreTerminal(cli.terminalFd, oldState) - } - - var ( - username string - password string - email string - ) - - var promptDefault = func(prompt string, configDefault string) { + promptDefault := func(prompt string, configDefault string) { if configDefault == "" { fmt.Fprintf(cli.out, "%s: ", prompt) } else { @@ -328,47 +271,55 @@ func (cli *DockerCli) CmdLogin(args ...string) error { } } + readInput := func(in io.Reader) (string, error) { + reader := bufio.NewReader(in) + line, err := reader.ReadString('\n') + if err != nil { + return "", err + } + return line, nil + } + authconfig, ok := cli.configFile.Configs[auth.IndexServerAddress()] if !ok { authconfig = auth.AuthConfig{} } - if *flUsername == "" { + if username == "" { promptDefault("Username", authconfig.Username) - username = readAndEchoString(cli.in, cli.out) + username, _ = readInput(cli.in) if username == "" { username = authconfig.Username } - } else { - username = *flUsername } + if username != authconfig.Username { - if *flPassword == "" { + if password == "" { + oldState, _ := term.SaveState(cli.terminalFd) fmt.Fprintf(cli.out, "Password: ") - password = readString(cli.in, cli.out) + + term.DisableEcho(cli.terminalFd, cli.out, oldState) + password, _ = readInput(cli.in) + + term.RestoreTerminal(cli.terminalFd, oldState) + if password == "" { return fmt.Errorf("Error : Password Required") } - } else { - password = *flPassword } - if *flEmail == "" { - promptDefault("Email", authconfig.Email) - email = readAndEchoString(cli.in, cli.out) + if email == "" { + promptDefault("\nEmail", authconfig.Email) + email, _ = readInput(cli.in) if email == "" { email = authconfig.Email } - } else { - email = *flEmail } } else { password = authconfig.Password email = authconfig.Email } - if oldState != nil { - term.RestoreTerminal(cli.terminalFd, oldState) - } + authconfig.Username = username authconfig.Password = password authconfig.Email = email @@ -1694,7 +1645,7 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea } if in != nil && setRawTerminal && cli.isTerminal && os.Getenv("NORAW") == "" { - oldState, err := term.SetRawTerminal(cli.terminalFd) + oldState, err := term.SetRawTerminal(cli.terminalFd, cli.out) if err != nil { return err } diff --git a/term/term.go b/term/term.go index f4d66a71d6..074319c287 100644 --- a/term/term.go +++ b/term/term.go @@ -1,6 +1,8 @@ package term import ( + "fmt" + "io" "os" "os/signal" "syscall" @@ -43,17 +45,43 @@ func RestoreTerminal(fd uintptr, state *State) error { return err } -func SetRawTerminal(fd uintptr) (*State, error) { +func SaveState(fd uintptr) (*State, error) { + var oldState State + if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, getTermios, uintptr(unsafe.Pointer(&oldState.termios))); err != 0 { + return nil, err + } + + return &oldState, nil +} + +func DisableEcho(fd uintptr, out io.Writer, state *State) error { + newState := state.termios + newState.Lflag &^= syscall.ECHO + + HandleInterrupt(fd, out, state) + if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, setTermios, uintptr(unsafe.Pointer(&newState))); err != 0 { + return err + } + return nil +} + +func HandleInterrupt(fd uintptr, out io.Writer, state *State) { + sigchan := make(chan os.Signal, 1) + signal.Notify(sigchan, os.Interrupt) + + go func() { + _ = <-sigchan + fmt.Fprintf(out, "\n") + RestoreTerminal(fd, state) + os.Exit(0) + }() +} + +func SetRawTerminal(fd uintptr, out io.Writer) (*State, error) { oldState, err := MakeRaw(fd) if err != nil { return nil, err } - c := make(chan os.Signal, 1) - signal.Notify(c, os.Interrupt) - go func() { - _ = <-c - RestoreTerminal(fd, oldState) - os.Exit(0) - }() + HandleInterrupt(fd, out, oldState) return oldState, err } From 6e4a818ee65008a043d3ba8e5053cc43babb2f66 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sat, 3 Aug 2013 17:27:15 -0700 Subject: [PATCH 036/128] Exit if there is any error reading from stdin. --- commands.go | 13 +++++++------ term/term.go | 2 +- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/commands.go b/commands.go index 0643bc8d58..ecd9c63155 100644 --- a/commands.go +++ b/commands.go @@ -271,13 +271,14 @@ func (cli *DockerCli) CmdLogin(args ...string) error { } } - readInput := func(in io.Reader) (string, error) { + readInput := func(in io.Reader, out io.Writer) string { reader := bufio.NewReader(in) line, err := reader.ReadString('\n') if err != nil { - return "", err + fmt.Fprintln(out, err.Error()) + os.Exit(1) } - return line, nil + return line } authconfig, ok := cli.configFile.Configs[auth.IndexServerAddress()] @@ -287,7 +288,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error { if username == "" { promptDefault("Username", authconfig.Username) - username, _ = readInput(cli.in) + username = readInput(cli.in, cli.out) if username == "" { username = authconfig.Username } @@ -299,7 +300,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error { fmt.Fprintf(cli.out, "Password: ") term.DisableEcho(cli.terminalFd, cli.out, oldState) - password, _ = readInput(cli.in) + password = readInput(cli.in, cli.out) term.RestoreTerminal(cli.terminalFd, oldState) @@ -310,7 +311,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error { if email == "" { promptDefault("\nEmail", authconfig.Email) - email, _ = readInput(cli.in) + email = readInput(cli.in, cli.out) if email == "" { email = authconfig.Email } diff --git a/term/term.go b/term/term.go index 074319c287..2c78d6806e 100644 --- a/term/term.go +++ b/term/term.go @@ -71,7 +71,7 @@ func HandleInterrupt(fd uintptr, out io.Writer, state *State) { go func() { _ = <-sigchan - fmt.Fprintf(out, "\n") + fmt.Fprint(out, "\n") RestoreTerminal(fd, state) os.Exit(0) }() From f18889bf674e574a3ac1315ed7bbd56bf638e6c5 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sat, 3 Aug 2013 21:30:07 -0700 Subject: [PATCH 037/128] Print a new line after getting the password from stdin. --- commands.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/commands.go b/commands.go index ecd9c63155..b58a2e360d 100644 --- a/commands.go +++ b/commands.go @@ -301,6 +301,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error { term.DisableEcho(cli.terminalFd, cli.out, oldState) password = readInput(cli.in, cli.out) + fmt.Fprint(cli.out, "\n") term.RestoreTerminal(cli.terminalFd, oldState) @@ -310,7 +311,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error { } if email == "" { - promptDefault("\nEmail", authconfig.Email) + promptDefault("Email", authconfig.Email) email = readInput(cli.in, cli.out) if email == "" { email = authconfig.Email From b54ba5095bc8b9323a3649806df98345cbe1f3b0 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sat, 17 Aug 2013 21:08:00 -0700 Subject: [PATCH 038/128] Add the ISIG syscall back to not kill the client withing a shell with ctrl+c. --- term/termios_darwin.go | 2 +- term/termios_linux.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/term/termios_darwin.go b/term/termios_darwin.go index 0f6b24b184..24e79de4b2 100644 --- a/term/termios_darwin.go +++ b/term/termios_darwin.go @@ -44,7 +44,7 @@ func MakeRaw(fd uintptr) (*State, error) { newState.Iflag &^= (ISTRIP | INLCR | IGNCR | IXON | IXOFF) newState.Iflag |= ICRNL newState.Oflag |= ONLCR - newState.Lflag &^= (ECHO | ICANON) + newState.Lflag &^= (ECHO | ICANON | ISIG) if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, uintptr(setTermios), uintptr(unsafe.Pointer(&newState))); err != 0 { return nil, err diff --git a/term/termios_linux.go b/term/termios_linux.go index 22f4fff430..6a76460a54 100644 --- a/term/termios_linux.go +++ b/term/termios_linux.go @@ -33,7 +33,7 @@ func MakeRaw(fd uintptr) (*State, error) { newState.Iflag &^= (syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK | syscall.ISTRIP | syscall.INLCR | syscall.IGNCR | syscall.ICRNL | syscall.IXON) newState.Oflag &^= syscall.OPOST - newState.Lflag &^= (syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.IEXTEN) + newState.Lflag &^= (syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG | syscall.EXTEN) newState.Cflag &^= (syscall.CSIZE | syscall.PARENB) newState.Cflag |= syscall.CS8 From b8a89628339dd63b5f2ff3d28715a5431412b65a Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sat, 17 Aug 2013 21:29:37 -0700 Subject: [PATCH 039/128] Simplify term signal handler. --- commands.go | 5 +++-- term/term.go | 25 ++++++++++++------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/commands.go b/commands.go index b58a2e360d..3dbcc41cf6 100644 --- a/commands.go +++ b/commands.go @@ -299,7 +299,8 @@ func (cli *DockerCli) CmdLogin(args ...string) error { oldState, _ := term.SaveState(cli.terminalFd) fmt.Fprintf(cli.out, "Password: ") - term.DisableEcho(cli.terminalFd, cli.out, oldState) + term.DisableEcho(cli.terminalFd, oldState) + password = readInput(cli.in, cli.out) fmt.Fprint(cli.out, "\n") @@ -1647,7 +1648,7 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea } if in != nil && setRawTerminal && cli.isTerminal && os.Getenv("NORAW") == "" { - oldState, err := term.SetRawTerminal(cli.terminalFd, cli.out) + oldState, err := term.SetRawTerminal(cli.terminalFd) if err != nil { return err } diff --git a/term/term.go b/term/term.go index 2c78d6806e..d8d4d1a655 100644 --- a/term/term.go +++ b/term/term.go @@ -54,34 +54,33 @@ func SaveState(fd uintptr) (*State, error) { return &oldState, nil } -func DisableEcho(fd uintptr, out io.Writer, state *State) error { +func DisableEcho(fd uintptr, state *State) error { newState := state.termios newState.Lflag &^= syscall.ECHO - HandleInterrupt(fd, out, state) if _, _, err := syscall.Syscall(syscall.SYS_IOCTL, fd, setTermios, uintptr(unsafe.Pointer(&newState))); err != 0 { return err } + handleInterrupt(fd, state) return nil } -func HandleInterrupt(fd uintptr, out io.Writer, state *State) { +func SetRawTerminal(fd uintptr) (*State, error) { + oldState, err := MakeRaw(fd) + if err != nil { + return nil, err + } + handleInterrupt(fd, oldState) + return oldState, err +} + +func handleInterrupt(fd uintptr, state *State) { sigchan := make(chan os.Signal, 1) signal.Notify(sigchan, os.Interrupt) go func() { _ = <-sigchan - fmt.Fprint(out, "\n") RestoreTerminal(fd, state) os.Exit(0) }() } - -func SetRawTerminal(fd uintptr, out io.Writer) (*State, error) { - oldState, err := MakeRaw(fd) - if err != nil { - return nil, err - } - HandleInterrupt(fd, out, oldState) - return oldState, err -} From e7ee2f443ad93803e8312bc224da618d555aebdc Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sat, 17 Aug 2013 22:22:11 -0700 Subject: [PATCH 040/128] Remove unused imports. --- term/term.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/term/term.go b/term/term.go index d8d4d1a655..5929c2caa1 100644 --- a/term/term.go +++ b/term/term.go @@ -1,8 +1,6 @@ package term import ( - "fmt" - "io" "os" "os/signal" "syscall" From 78d995bbd6dc8022faef251835ae6001c4c3da49 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sat, 17 Aug 2013 22:22:24 -0700 Subject: [PATCH 041/128] Fix syscall name. --- term/termios_linux.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/term/termios_linux.go b/term/termios_linux.go index 6a76460a54..4a717c84a7 100644 --- a/term/termios_linux.go +++ b/term/termios_linux.go @@ -33,7 +33,7 @@ func MakeRaw(fd uintptr) (*State, error) { newState.Iflag &^= (syscall.IGNBRK | syscall.BRKINT | syscall.PARMRK | syscall.ISTRIP | syscall.INLCR | syscall.IGNCR | syscall.ICRNL | syscall.IXON) newState.Oflag &^= syscall.OPOST - newState.Lflag &^= (syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG | syscall.EXTEN) + newState.Lflag &^= (syscall.ECHO | syscall.ECHONL | syscall.ICANON | syscall.ISIG | syscall.IEXTEN) newState.Cflag &^= (syscall.CSIZE | syscall.PARENB) newState.Cflag |= syscall.CS8 From 9f8e5a93b4b7c6b190177c73085e7d2d2d93c64b Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sat, 17 Aug 2013 22:28:05 -0700 Subject: [PATCH 042/128] Use flag.StringVar to capture the command line flags. --- commands.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/commands.go b/commands.go index 3dbcc41cf6..be2a4c02d4 100644 --- a/commands.go +++ b/commands.go @@ -254,9 +254,11 @@ func (cli *DockerCli) CmdBuild(args ...string) error { func (cli *DockerCli) CmdLogin(args ...string) error { cmd := Subcmd("login", "[OPTIONS]", "Register or Login to the docker registry server") - username := *cmd.String("u", "", "username") - password := *cmd.String("p", "", "password") - email := *cmd.String("e", "", "email") + var username, password, email string + + cmd.StringVar(&username, "u", "", "username") + cmd.StringVar(&password, "p", "", "password") + cmd.StringVar(&email, "e", "", "email") err := cmd.Parse(args) if err != nil { From 3f141e1fd3a11507b049359ea59253b49395d494 Mon Sep 17 00:00:00 2001 From: Greg Thornton Date: Thu, 29 Aug 2013 14:06:24 -0500 Subject: [PATCH 043/128] Update remaining upstart scripts to wait for lxc-net --- contrib/install.sh | 2 +- hack/release/make.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/install.sh b/contrib/install.sh index 3cf7169a07..40e3aaafb4 100755 --- a/contrib/install.sh +++ b/contrib/install.sh @@ -47,7 +47,7 @@ else echo "Creating /etc/init/dockerd.conf..." cat >/etc/init/dockerd.conf < Date: Thu, 29 Aug 2013 23:49:41 +0000 Subject: [PATCH 049/128] added a Dockerfile which installs all deps and builds the docs. --- docs/Dockerfile | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 docs/Dockerfile diff --git a/docs/Dockerfile b/docs/Dockerfile new file mode 100644 index 0000000000..e5fd267ebd --- /dev/null +++ b/docs/Dockerfile @@ -0,0 +1,15 @@ +from ubuntu:12.04 +maintainer Nick Stinemates + +run apt-get update +run apt-get install -y python-setuptools make +run easy_install pip +add . /docs +run pip install -r /docs/requirements.txt +run cd /docs; make docs + +expose 8000 + +workdir /docs/_build/html + +entrypoint ["python", "-m", "SimpleHTTPServer"] From e3b58d302795fbbfa6c117774906a4c9efd536f4 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 30 Aug 2013 00:46:43 +0000 Subject: [PATCH 050/128] hide version when not available --- commands.go | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/commands.go b/commands.go index 41e20158b8..01256b1da8 100644 --- a/commands.go +++ b/commands.go @@ -434,8 +434,9 @@ func (cli *DockerCli) CmdVersion(args ...string) error { cmd.Usage() return nil } - - fmt.Fprintf(cli.out, "Client version: %s\n", VERSION) + if VERSION != "" { + fmt.Fprintf(cli.out, "Client version: %s\n", VERSION) + } fmt.Fprintf(cli.out, "Go version (client): %s\n", runtime.Version()) if GITCOMMIT != "" { fmt.Fprintf(cli.out, "Git commit (client): %s\n", GITCOMMIT) @@ -452,7 +453,9 @@ func (cli *DockerCli) CmdVersion(args ...string) error { utils.Debugf("Error unmarshal: body: %s, err: %s\n", body, err) return err } - fmt.Fprintf(cli.out, "Server version: %s\n", out.Version) + if out.Version != "" { + fmt.Fprintf(cli.out, "Server version: %s\n", out.Version) + } if out.GitCommit != "" { fmt.Fprintf(cli.out, "Git commit (server): %s\n", out.GitCommit) } @@ -463,7 +466,7 @@ func (cli *DockerCli) CmdVersion(args ...string) error { release := utils.GetReleaseVersion() if release != "" { fmt.Fprintf(cli.out, "Last stable version: %s", release) - if strings.Trim(VERSION, "-dev") != release || strings.Trim(out.Version, "-dev") != release { + if (VERSION != "" || out.Version != "") && (strings.Trim(VERSION, "-dev") != release || strings.Trim(out.Version, "-dev") != release) { fmt.Fprintf(cli.out, ", please update docker") } fmt.Fprintf(cli.out, "\n") From c0e95fa68a37166ce551a33332bbede6b6531b68 Mon Sep 17 00:00:00 2001 From: dsissitka Date: Thu, 29 Aug 2013 21:05:18 -0400 Subject: [PATCH 051/128] Updated "Use -> The Basics" to use ubuntu:12.10. ubuntu:latest doesn't have nc. ubuntu:12.10 does. --- docs/sources/use/basics.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/use/basics.rst b/docs/sources/use/basics.rst index d37bf40b05..8b4676f6ee 100644 --- a/docs/sources/use/basics.rst +++ b/docs/sources/use/basics.rst @@ -142,7 +142,7 @@ Expose a service on a TCP port .. code-block:: bash # Expose port 4444 of this container, and tell netcat to listen on it - JOB=$(sudo docker run -d -p 4444 ubuntu /bin/nc -l -p 4444) + JOB=$(sudo docker run -d -p 4444 ubuntu:12.10 /bin/nc -l -p 4444) # Which public port is NATed to my container? PORT=$(sudo docker port $JOB 4444) From 6c206f7d7835310c39ef20d3375e949b1fbfd7b9 Mon Sep 17 00:00:00 2001 From: "Shih-Yuan Lee (FourDollars)" Date: Wed, 21 Aug 2013 12:44:58 +0800 Subject: [PATCH 052/128] Install Ubuntu raring backported kernel from official archives directly. --- Vagrantfile | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Vagrantfile b/Vagrantfile index 4cee3a04d0..53620eabc6 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -20,15 +20,13 @@ Vagrant::Config.run do |config| pkg_cmd = "wget -q -O - http://get.docker.io/gpg | apt-key add -;" \ "echo deb https://get.docker.io/ubuntu docker main > /etc/apt/sources.list.d/docker.list;" \ "apt-get update -qq; apt-get install -q -y --force-yes lxc-docker; " - # Add X.org Ubuntu backported 3.8 kernel - pkg_cmd << "apt-get update -qq; apt-get install -q -y python-software-properties; " \ - "add-apt-repository -y ppa:ubuntu-x-swat/r-lts-backport; " \ - "apt-get update -qq; apt-get install -q -y linux-image-3.8.0-19-generic; " + # Add Ubuntu raring backported kernel + pkg_cmd << "apt-get update -qq; apt-get install -q -y linux-image-generic-lts-raring; " # Add guest additions if local vbox VM is_vbox = true ARGV.each do |arg| is_vbox &&= !arg.downcase.start_with?("--provider") end if is_vbox - pkg_cmd << "apt-get install -q -y linux-headers-3.8.0-19-generic dkms; " \ + pkg_cmd << "apt-get install -q -y linux-headers-generic-lts-raring dkms; " \ "echo 'Downloading VBox Guest Additions...'; " \ "wget -q http://dlc.sun.com.edgesuite.net/virtualbox/4.2.12/VBoxGuestAdditions_4.2.12.iso; " # Prepare the VM to add guest additions after reboot From 1a8a540209c997f5e75a476b942365aad892ad35 Mon Sep 17 00:00:00 2001 From: Morten Siebuhr Date: Fri, 30 Aug 2013 15:23:32 +0200 Subject: [PATCH 053/128] Document FROM : Dockerfile instruction. --- docs/sources/use/builder.rst | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/docs/sources/use/builder.rst b/docs/sources/use/builder.rst index 7a985e766b..61c7fccd1d 100644 --- a/docs/sources/use/builder.rst +++ b/docs/sources/use/builder.rst @@ -68,6 +68,10 @@ building images. ``FROM `` +Or + + ``FROM :`` + The ``FROM`` instruction sets the :ref:`base_image_def` for subsequent instructions. As such, a valid Dockerfile must have ``FROM`` as its first instruction. The image can be any valid image -- it is @@ -81,6 +85,9 @@ especially easy to start by **pulling an image** from the to create multiple images. Simply make a note of the last image id output by the commit before each new ``FROM`` command. +If no ``tag`` is given to the ``FROM`` instruction, ``latest`` is +assumed. If the used tag does not exist, an error will be returned. + 3.2 MAINTAINER -------------- From d605e82badf64c3033f5f26199285aed414f63dd Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Fri, 30 Aug 2013 18:08:29 +0000 Subject: [PATCH 054/128] fix error in docker build when param is not a directory --- commands.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/commands.go b/commands.go index 6d78c032f3..0b276ff77f 100644 --- a/commands.go +++ b/commands.go @@ -187,8 +187,10 @@ func (cli *DockerCli) CmdBuild(args ...string) error { } else if utils.IsURL(cmd.Arg(0)) || utils.IsGIT(cmd.Arg(0)) { isRemote = true } else { - if _, err := os.Stat(cmd.Arg(0)); err != nil { + if fi, err := os.Stat(cmd.Arg(0)); err != nil { return err + } else if !fi.IsDir() { + return fmt.Errorf("\"%s\" is not a path or URL. Please provide a path to a directory containing a Dockerfile.", cmd.Arg(0)) } context, err = Tar(cmd.Arg(0), Uncompressed) } From 6756e786ac36d4e5cda46541b5d6e0b2913b6997 Mon Sep 17 00:00:00 2001 From: Brian Olsen Date: Fri, 30 Aug 2013 02:49:11 +0200 Subject: [PATCH 055/128] Just fixing gofmt issues in other people's code. --- container.go | 10 +++++----- network.go | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/container.go b/container.go index 39c19c53d4..5799fa4e12 100644 --- a/container.go +++ b/container.go @@ -11,6 +11,7 @@ import ( "io" "io/ioutil" "log" + "net" "os" "os/exec" "path" @@ -20,7 +21,6 @@ import ( "strings" "syscall" "time" - "net" ) type Container struct { @@ -813,10 +813,10 @@ func (container *Container) allocateNetwork() error { iface = &NetworkInterface{disabled: true} } else { iface = &NetworkInterface{ - IPNet: net.IPNet{IP: net.ParseIP(container.NetworkSettings.IPAddress), Mask: manager.bridgeNetwork.Mask}, + IPNet: net.IPNet{IP: net.ParseIP(container.NetworkSettings.IPAddress), Mask: manager.bridgeNetwork.Mask}, Gateway: manager.bridgeNetwork.IP, manager: manager, - } + } ipNum := ipToInt(iface.IPNet.IP) manager.ipAllocator.inUse[ipNum] = struct{}{} } @@ -827,10 +827,10 @@ func (container *Container) allocateNetwork() error { portSpecs = container.Config.PortSpecs } else { for backend, frontend := range container.NetworkSettings.PortMapping["Tcp"] { - portSpecs = append(portSpecs, fmt.Sprintf("%s:%s/tcp",frontend, backend)) + portSpecs = append(portSpecs, fmt.Sprintf("%s:%s/tcp", frontend, backend)) } for backend, frontend := range container.NetworkSettings.PortMapping["Udp"] { - portSpecs = append(portSpecs, fmt.Sprintf("%s:%s/udp",frontend, backend)) + portSpecs = append(portSpecs, fmt.Sprintf("%s:%s/udp", frontend, backend)) } } diff --git a/network.go b/network.go index c2673bd803..b552919253 100644 --- a/network.go +++ b/network.go @@ -642,7 +642,7 @@ func (manager *NetworkManager) Allocate() (*NetworkInterface, error) { if err != nil { return nil, err } - // avoid duplicate IP + // avoid duplicate IP ipNum := ipToInt(ip) firstIP := manager.ipAllocator.network.IP.To4().Mask(manager.ipAllocator.network.Mask) firstIPNum := ipToInt(firstIP) + 1 From 7a9c71183212a40fdd10369d009a9f97b56fe359 Mon Sep 17 00:00:00 2001 From: Brian Olsen Date: Fri, 30 Aug 2013 02:29:35 +0200 Subject: [PATCH 056/128] Copies content from image to volumne if non-empty. Fixes #1582. --- container.go | 33 +++++++++++++++++++++++------ container_test.go | 54 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 81 insertions(+), 6 deletions(-) diff --git a/container.go b/container.go index 5799fa4e12..6b52983b0d 100644 --- a/container.go +++ b/container.go @@ -642,11 +642,13 @@ func (container *Container) Start(hostConfig *HostConfig) error { if _, exists := container.Volumes[volPath]; exists { continue } + var srcPath string + srcRW := false // If an external bind is defined for this volume, use that as a source if bindMap, exists := binds[volPath]; exists { - container.Volumes[volPath] = bindMap.SrcPath + srcPath = bindMap.SrcPath if strings.ToLower(bindMap.Mode) == "rw" { - container.VolumesRW[volPath] = true + srcRW = true } // Otherwise create an directory in $ROOT/volumes/ and use that } else { @@ -654,17 +656,36 @@ func (container *Container) Start(hostConfig *HostConfig) error { if err != nil { return err } - srcPath, err := c.layer() + srcPath, err = c.layer() if err != nil { return err } - container.Volumes[volPath] = srcPath - container.VolumesRW[volPath] = true // RW by default + srcRW = true // RW by default } + container.Volumes[volPath] = srcPath + container.VolumesRW[volPath] = srcRW // Create the mountpoint - if err := os.MkdirAll(path.Join(container.RootfsPath(), volPath), 0755); err != nil { + rootVolPath := path.Join(container.RootfsPath(), volPath) + if err := os.MkdirAll(rootVolPath, 0755); err != nil { return nil } + if srcRW { + volList, err := ioutil.ReadDir(rootVolPath) + if err != nil { + return err + } + if len(volList) > 0 { + srcList, err := ioutil.ReadDir(srcPath) + if err != nil { + return err + } + if len(srcList) == 0 { + if err := CopyWithTar(rootVolPath, srcPath); err != nil { + return err + } + } + } + } } if err := container.generateLXCConfig(hostConfig); err != nil { diff --git a/container_test.go b/container_test.go index ba48ceb47a..9b6b563048 100644 --- a/container_test.go +++ b/container_test.go @@ -1193,6 +1193,60 @@ func tempDir(t *testing.T) string { return tmpDir } +// Test for #1582 +func TestCopyVolumeContent(t *testing.T) { + r := mkRuntime(t) + defer nuke(r) + + // Put some content in a directory of a container and commit it + container1, _, _ := mkContainer(r, []string{"_", "/bin/sh", "-c", "mkdir -p /hello/local && echo hello > /hello/local/world"}, t) + defer r.Destroy(container1) + + if container1.State.Running { + t.Errorf("Container shouldn't be running") + } + if err := container1.Run(); err != nil { + t.Fatal(err) + } + if container1.State.Running { + t.Errorf("Container shouldn't be running") + } + + rwTar, err := container1.ExportRw() + if err != nil { + t.Error(err) + } + img, err := r.graph.Create(rwTar, container1, "unit test commited image", "", nil) + if err != nil { + t.Error(err) + } + + // Test that the content is copied from the image to the volume + tmpDir1 := tempDir(t) + defer os.RemoveAll(tmpDir1) + stdout1, _ := runContainer(r, []string{"-v", fmt.Sprintf("%s:/hello", tmpDir1), img.ID, "find", "/hello"}, t) + if !(strings.Contains(stdout1, "/hello/local/world") && strings.Contains(stdout1, "/hello/local")) { + t.Fatal("Container failed to transfer content to volume") + } + + // Test that the content is not copied when the volume is readonly + tmpDir2 := tempDir(t) + defer os.RemoveAll(tmpDir2) + stdout2, _ := runContainer(r, []string{"-v", fmt.Sprintf("%s:/hello:ro", tmpDir2), img.ID, "find", "/hello"}, t) + if strings.Contains(stdout2, "/hello/local/world") || strings.Contains(stdout2, "/hello/local") { + t.Fatal("Container transfered content to readonly volume") + } + + // Test that the content is not copied when the volume is non-empty + tmpDir3 := tempDir(t) + defer os.RemoveAll(tmpDir3) + writeFile(path.Join(tmpDir3, "touch-me"), "", t) + stdout3, _ := runContainer(r, []string{"-v", fmt.Sprintf("%s:/hello:rw", tmpDir3), img.ID, "find", "/hello"}, t) + if strings.Contains(stdout3, "/hello/local/world") || strings.Contains(stdout3, "/hello/local") || !strings.Contains(stdout3, "/hello/touch-me") { + t.Fatal("Container transfered content to non-empty volume") + } +} + func TestBindMounts(t *testing.T) { r := mkRuntime(t) defer nuke(r) From 46c9c5c84317907153f06284dc5110b53a0fbf3c Mon Sep 17 00:00:00 2001 From: Brian Olsen Date: Fri, 30 Aug 2013 21:45:01 +0200 Subject: [PATCH 057/128] Made calling changelog before run return empty. Fixes #1705. --- changes.go | 2 +- container_test.go | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/changes.go b/changes.go index dc1b015726..43573cd606 100644 --- a/changes.go +++ b/changes.go @@ -99,7 +99,7 @@ func Changes(layers []string, rw string) ([]Change, error) { changes = append(changes, change) return nil }) - if err != nil { + if err != nil && !os.IsNotExist(err) { return nil, err } return changes, nil diff --git a/container_test.go b/container_test.go index ba48ceb47a..b06d531cf7 100644 --- a/container_test.go +++ b/container_test.go @@ -138,12 +138,21 @@ func TestDiff(t *testing.T) { container1, _, _ := mkContainer(runtime, []string{"_", "/bin/rm", "/etc/passwd"}, t) defer runtime.Destroy(container1) + // The changelog should be empty and not fail before run. See #1705 + c, err := container1.Changes() + if err != nil { + t.Fatal(err) + } + if len(c) != 0 { + t.Fatalf("Changelog should be empty before run") + } + if err := container1.Run(); err != nil { t.Fatal(err) } // Check the changelog - c, err := container1.Changes() + c, err = container1.Changes() if err != nil { t.Fatal(err) } From e42d3a1bfaa555c68cb4a45a2272f8ebf5a3782b Mon Sep 17 00:00:00 2001 From: Matt Apperson Date: Sat, 31 Aug 2013 22:13:15 -0400 Subject: [PATCH 058/128] Added NodeJS library --- docs/sources/api/docker_remote_api.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/sources/api/docker_remote_api.rst b/docs/sources/api/docker_remote_api.rst index 82db83d507..6e4bd2bba0 100644 --- a/docs/sources/api/docker_remote_api.rst +++ b/docs/sources/api/docker_remote_api.rst @@ -181,6 +181,9 @@ and we will add the libraries here. +----------------------+----------------+--------------------------------------------+ | Ruby | docker-api | https://github.com/swipely/docker-api | +----------------------+----------------+--------------------------------------------+ +| Javascript (NodeJS) | docker.io | https://github.com/appersonlabs/docker.io | +| | | Install via NPM: `npm install docker.io` | ++----------------------+----------------+--------------------------------------------+ | Javascript | docker-js | https://github.com/dgoujard/docker-js | +----------------------+----------------+--------------------------------------------+ | Javascript (Angular) | dockerui | https://github.com/crosbymichael/dockerui | From 1fca99ad908c530fcd03158a56767b55500a8521 Mon Sep 17 00:00:00 2001 From: Brandon Liu Date: Sat, 31 Aug 2013 20:31:21 -0700 Subject: [PATCH 059/128] Replace Graph.All with Graph.Map --- api_test.go | 2 +- graph.go | 19 ++++--------------- graph_test.go | 16 ++++++++++------ runtime_test.go | 10 +++++----- server.go | 6 +++--- 5 files changed, 23 insertions(+), 30 deletions(-) diff --git a/api_test.go b/api_test.go index fc7eeed713..9d773301ef 100644 --- a/api_test.go +++ b/api_test.go @@ -68,7 +68,7 @@ func TestGetInfo(t *testing.T) { srv := &Server{runtime: runtime} - initialImages, err := srv.runtime.graph.All() + initialImages, err := srv.runtime.graph.Map() if err != nil { t.Fatal(err) } diff --git a/graph.go b/graph.go index c54725fdb4..09770f8e30 100644 --- a/graph.go +++ b/graph.go @@ -272,27 +272,16 @@ func (graph *Graph) Delete(name string) error { // Map returns a list of all images in the graph, addressable by ID. func (graph *Graph) Map() (map[string]*Image, error) { - // FIXME: this should replace All() - all, err := graph.All() + images := make(map[string]*Image) + err := graph.WalkAll(func(image *Image) { + images[image.ID] = image + }) if err != nil { return nil, err } - images := make(map[string]*Image, len(all)) - for _, image := range all { - images[image.ID] = image - } return images, nil } -// All returns a list of all images in the graph. -func (graph *Graph) All() ([]*Image, error) { - var images []*Image - err := graph.WalkAll(func(image *Image) { - images = append(images, image) - }) - return images, err -} - // WalkAll iterates over each image in the graph, and passes it to a handler. // The walking order is undetermined. func (graph *Graph) WalkAll(handler func(*Image)) error { diff --git a/graph_test.go b/graph_test.go index 32fb0ef441..471016938d 100644 --- a/graph_test.go +++ b/graph_test.go @@ -20,11 +20,11 @@ func TestInit(t *testing.T) { if _, err := os.Stat(graph.Root); err != nil { t.Fatal(err) } - // All() should be empty - if l, err := graph.All(); err != nil { + // Map() should be empty + if l, err := graph.Map(); err != nil { t.Fatal(err) } else if len(l) != 0 { - t.Fatalf("List() should return %d, not %d", 0, len(l)) + t.Fatalf("len(Map()) should return %d, not %d", 0, len(l)) } } @@ -76,11 +76,15 @@ func TestGraphCreate(t *testing.T) { if image.DockerVersion != VERSION { t.Fatalf("Wrong docker_version: should be '%s', not '%s'", VERSION, image.DockerVersion) } - if images, err := graph.All(); err != nil { + images, err := graph.Map() + if err != nil { t.Fatal(err) } else if l := len(images); l != 1 { t.Fatalf("Wrong number of images. Should be %d, not %d", 1, l) } + if images[image.ID] == nil { + t.Fatalf("Could not find image with id %s", image.ID) + } } func TestRegister(t *testing.T) { @@ -99,7 +103,7 @@ func TestRegister(t *testing.T) { if err != nil { t.Fatal(err) } - if images, err := graph.All(); err != nil { + if images, err := graph.Map(); err != nil { t.Fatal(err) } else if l := len(images); l != 1 { t.Fatalf("Wrong number of images. Should be %d, not %d", 1, l) @@ -274,7 +278,7 @@ func TestByParent(t *testing.T) { } func assertNImages(graph *Graph, t *testing.T, n int) { - if images, err := graph.All(); err != nil { + if images, err := graph.Map(); err != nil { t.Fatal(err) } else if actualN := len(images); actualN != n { t.Fatalf("Expected %d images, found %d", n, actualN) diff --git a/runtime_test.go b/runtime_test.go index a65d962fa6..3cd7a0a5d0 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -50,7 +50,7 @@ func cleanup(runtime *Runtime) error { container.Kill() runtime.Destroy(container) } - images, err := runtime.graph.All() + images, err := runtime.graph.Map() if err != nil { return err } @@ -123,13 +123,13 @@ func init() { // FIXME: test that ImagePull(json=true) send correct json output func GetTestImage(runtime *Runtime) *Image { - imgs, err := runtime.graph.All() + imgs, err := runtime.graph.Map() if err != nil { panic(err) } - for i := range imgs { - if imgs[i].ID == unitTestImageID { - return imgs[i] + for _, image := range imgs { + if image.ID == unitTestImageID { + return image } } panic(fmt.Errorf("Test image %v not found", unitTestImageID)) diff --git a/server.go b/server.go index 646cb44877..b27f149996 100644 --- a/server.go +++ b/server.go @@ -158,7 +158,7 @@ func (srv *Server) ImageInsert(name, url, path string, out io.Writer, sf *utils. } func (srv *Server) ImagesViz(out io.Writer) error { - images, _ := srv.runtime.graph.All() + images, _ := srv.runtime.graph.Map() if images == nil { return nil } @@ -247,7 +247,7 @@ func (srv *Server) Images(all bool, filter string) ([]APIImages, error) { } func (srv *Server) DockerInfo() *APIInfo { - images, _ := srv.runtime.graph.All() + images, _ := srv.runtime.graph.Map() var imgcount int if images == nil { imgcount = 0 @@ -1064,7 +1064,7 @@ func (srv *Server) ImageDelete(name string, autoPrune bool) ([]APIRmi, error) { func (srv *Server) ImageGetCached(imgID string, config *Config) (*Image, error) { // Retrieve all images - images, err := srv.runtime.graph.All() + images, err := srv.runtime.graph.Map() if err != nil { return nil, err } From 113bb396cdef5e07a19306dea767870b2775214d Mon Sep 17 00:00:00 2001 From: Brandon Liu Date: Sat, 31 Aug 2013 20:34:51 -0700 Subject: [PATCH 060/128] Don't export Graph.walkAll. --- graph.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/graph.go b/graph.go index 09770f8e30..34adec1b04 100644 --- a/graph.go +++ b/graph.go @@ -273,7 +273,7 @@ func (graph *Graph) Delete(name string) error { // Map returns a list of all images in the graph, addressable by ID. func (graph *Graph) Map() (map[string]*Image, error) { images := make(map[string]*Image) - err := graph.WalkAll(func(image *Image) { + err := graph.walkAll(func(image *Image) { images[image.ID] = image }) if err != nil { @@ -282,9 +282,9 @@ func (graph *Graph) Map() (map[string]*Image, error) { return images, nil } -// WalkAll iterates over each image in the graph, and passes it to a handler. +// walkAll iterates over each image in the graph, and passes it to a handler. // The walking order is undetermined. -func (graph *Graph) WalkAll(handler func(*Image)) error { +func (graph *Graph) walkAll(handler func(*Image)) error { files, err := ioutil.ReadDir(graph.Root) if err != nil { return err @@ -306,7 +306,7 @@ func (graph *Graph) WalkAll(handler func(*Image)) error { // If an image has no children, it will not have an entry in the table. func (graph *Graph) ByParent() (map[string][]*Image, error) { byParent := make(map[string][]*Image) - err := graph.WalkAll(func(image *Image) { + err := graph.walkAll(func(image *Image) { parent, err := graph.Get(image.Parent) if err != nil { return @@ -328,7 +328,7 @@ func (graph *Graph) Heads() (map[string]*Image, error) { if err != nil { return nil, err } - err = graph.WalkAll(func(image *Image) { + err = graph.walkAll(func(image *Image) { // If it's not in the byParent lookup table, then // it's not a parent -> so it's a head! if _, exists := byParent[image.ID]; !exists { From ce53e21ea6790cf7c2e96f8c5f0725bdf41a80f0 Mon Sep 17 00:00:00 2001 From: David Calavera Date: Sun, 1 Sep 2013 16:12:07 -0700 Subject: [PATCH 061/128] Read the stdin line properly. Load the auth config before it's used. --- commands.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/commands.go b/commands.go index 95f67f4f33..7760108588 100644 --- a/commands.go +++ b/commands.go @@ -277,14 +277,15 @@ func (cli *DockerCli) CmdLogin(args ...string) error { readInput := func(in io.Reader, out io.Writer) string { reader := bufio.NewReader(in) - line, err := reader.ReadString('\n') + line, _, err := reader.ReadLine() if err != nil { fmt.Fprintln(out, err.Error()) os.Exit(1) } - return line + return string(line) } + cli.LoadConfigFile() authconfig, ok := cli.configFile.Configs[auth.IndexServerAddress()] if !ok { authconfig = auth.AuthConfig{} From 6380b42edbf98dec3b04b3b74fcfbfd63dba4034 Mon Sep 17 00:00:00 2001 From: Thijs Terlouw Date: Tue, 3 Sep 2013 16:35:22 +0200 Subject: [PATCH 062/128] Add 2 missing cli commands to docs (events + insert) and alphabetically order docker output --- AUTHORS | 1 + commands.go | 2 +- docs/sources/commandline/command/events.rst | 34 +++++++++++++++++++++ docs/sources/commandline/command/insert.rst | 23 ++++++++++++++ docs/sources/commandline/index.rst | 2 ++ 5 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 docs/sources/commandline/command/events.rst create mode 100644 docs/sources/commandline/command/insert.rst diff --git a/AUTHORS b/AUTHORS index 5bf18aa51d..791d966cef 100644 --- a/AUTHORS +++ b/AUTHORS @@ -104,6 +104,7 @@ Solomon Hykes Sridhar Ratnakumar Stefan Praszalowicz Thatcher Peskens +Thijs Terlouw Thomas Bikeev Thomas Hansen Tianon Gravi diff --git a/commands.go b/commands.go index 0b276ff77f..ffa94973bf 100644 --- a/commands.go +++ b/commands.go @@ -91,7 +91,6 @@ func (cli *DockerCli) CmdHelp(args ...string) error { {"login", "Register or Login to the docker registry server"}, {"logs", "Fetch the logs of a container"}, {"port", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT"}, - {"top", "Lookup the running processes of a container"}, {"ps", "List containers"}, {"pull", "Pull an image or a repository from the docker registry server"}, {"push", "Push an image or a repository to the docker registry server"}, @@ -103,6 +102,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error { {"start", "Start a stopped container"}, {"stop", "Stop a running container"}, {"tag", "Tag an image into a repository"}, + {"top", "Lookup the running processes of a container"}, {"version", "Show the docker version information"}, {"wait", "Block until a container stops, then print its exit code"}, } { diff --git a/docs/sources/commandline/command/events.rst b/docs/sources/commandline/command/events.rst new file mode 100644 index 0000000000..b8dd591fb1 --- /dev/null +++ b/docs/sources/commandline/command/events.rst @@ -0,0 +1,34 @@ +:title: Events Command +:description: Get real time events from the server +:keywords: events, docker, documentation + +================================================================= +``events`` -- Get real time events from the server +================================================================= + +:: + + Usage: docker events + + Get real time events from the server + +Examples +-------- + +Starting and stopping a container +................................. + +.. code-block:: bash + + $ sudo docker start 4386fb97867d + $ sudo docker stop 4386fb97867d + +In another shell + +.. code-block:: bash + + $ sudo docker events + [2013-09-03 15:49:26 +0200 CEST] 4386fb97867d: (from 12de384bfb10) start + [2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) die + [2013-09-03 15:49:29 +0200 CEST] 4386fb97867d: (from 12de384bfb10) stop + diff --git a/docs/sources/commandline/command/insert.rst b/docs/sources/commandline/command/insert.rst new file mode 100644 index 0000000000..0f2612c9af --- /dev/null +++ b/docs/sources/commandline/command/insert.rst @@ -0,0 +1,23 @@ +:title: Insert Command +:description: Insert a file in an image +:keywords: insert, image, docker, documentation + +========================================================================== +``insert`` -- Insert a file in an image +========================================================================== + +:: + + Usage: docker insert IMAGE URL PATH + + Insert a file from URL in the IMAGE at PATH + +Examples +-------- + +Insert file from github +....................... + +.. code-block:: bash + + $ sudo docker insert 8283e18b24bc https://raw.github.com/metalivedev/django/master/postinstall /tmp/postinstall.sh diff --git a/docs/sources/commandline/index.rst b/docs/sources/commandline/index.rst index 5c2b373205..0e7c8738b3 100644 --- a/docs/sources/commandline/index.rst +++ b/docs/sources/commandline/index.rst @@ -17,11 +17,13 @@ Contents: commit cp diff + events export history images import info + insert inspect kill login From ea813d8593d30e430ce05597becb1e394cb72a25 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Tue, 3 Sep 2013 18:30:32 +0000 Subject: [PATCH 063/128] Update tar pkg revision number --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 8694f07c37..43f493d3ff 100644 --- a/Dockerfile +++ b/Dockerfile @@ -27,7 +27,7 @@ run /bin/echo -e '[default]\naccess_key=$AWS_ACCESS_KEY\nsecret_key=$AWS_SECRET_ run PKG=github.com/kr/pty REV=27435c699; git clone http://$PKG /go/src/$PKG && cd /go/src/$PKG && git checkout -f $REV run PKG=github.com/gorilla/context/ REV=708054d61e5; git clone http://$PKG /go/src/$PKG && cd /go/src/$PKG && git checkout -f $REV run PKG=github.com/gorilla/mux/ REV=9b36453141c; git clone http://$PKG /go/src/$PKG && cd /go/src/$PKG && git checkout -f $REV -run PKG=github.com/dotcloud/tar/ REV=d06045a6d9; git clone http://$PKG /go/src/$PKG && cd /go/src/$PKG && git checkout -f $REV +run PKG=github.com/dotcloud/tar/ REV=e5ea6bb21a3294; git clone http://$PKG /go/src/$PKG && cd /go/src/$PKG && git checkout -f $REV run PKG=code.google.com/p/go.net/ REV=84a4013f96e0; hg clone http://$PKG /go/src/$PKG && cd /go/src/$PKG && hg checkout $REV # Upload docker source add . /go/src/github.com/dotcloud/docker From fcee6056dc50de7698772a3049cdfa1eb0f2416f Mon Sep 17 00:00:00 2001 From: Marco Hennings Date: Tue, 3 Sep 2013 20:45:49 +0200 Subject: [PATCH 064/128] Login against private registry To improve the use of docker with a private registry the login command is extended with a parameter for the server address. While implementing i noticed that two problems hindered authentication to a private registry: 1. the resolve of the authentication did not match during push because the looked up key was for example localhost:8080 but the stored one would have been https://localhost:8080 Besides The lookup needs to still work if the https->http fallback is used 2. During pull of an image no authentication is sent, which means all repositories are expected to be private. These points are fixed now. The changes are implemented in a way to be compatible to existing behavior both in the API as also with the private registry. Update: - login does not require the full url any more, you can login to the repository prefix: example: docker logon localhost:8080 Fixed corner corner cases: - When login is done during pull and push the registry endpoint is used and not the central index - When Remote sends a 401 during pull, it is now correctly delegating to CmdLogin - After a Login is done pull and push are using the newly entered login data, and not the previous ones. This one seems to be also broken in master, too. - Auth config is now transfered in a parameter instead of the body when /images/create is called. --- api.go | 13 ++- auth/auth.go | 97 ++++++++++++++++++--- commands.go | 80 ++++++++++++----- docs/sources/api/docker_remote_api_v1.4.rst | 3 +- docs/sources/commandline/command/login.rst | 9 +- registry/registry.go | 33 ++++++- server.go | 3 + 7 files changed, 199 insertions(+), 39 deletions(-) diff --git a/api.go b/api.go index 9e094231b5..0debbe94f3 100644 --- a/api.go +++ b/api.go @@ -3,6 +3,7 @@ package docker import ( "code.google.com/p/go.net/websocket" "encoding/json" + "encoding/base64" "fmt" "github.com/dotcloud/docker/auth" "github.com/dotcloud/docker/utils" @@ -394,6 +395,16 @@ func postImagesCreate(srv *Server, version float64, w http.ResponseWriter, r *ht tag := r.Form.Get("tag") repo := r.Form.Get("repo") + authEncoded := r.Form.Get("authConfig") + authConfig := &auth.AuthConfig{} + if authEncoded != "" { + authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) + if err := json.NewDecoder(authJson).Decode(authConfig); err != nil { + // for a pull it is not an error if no auth was given + // to increase compatibilit to existing api it is defaulting to be empty + authConfig = &auth.AuthConfig{} + } + } if version > 1.0 { w.Header().Set("Content-Type", "application/json") } @@ -405,7 +416,7 @@ func postImagesCreate(srv *Server, version float64, w http.ResponseWriter, r *ht metaHeaders[k] = v } } - if err := srv.ImagePull(image, tag, w, sf, &auth.AuthConfig{}, metaHeaders, version > 1.3); err != nil { + if err := srv.ImagePull(image, tag, w, sf, authConfig, metaHeaders, version > 1.3); err != nil { if sf.Used() { w.Write(sf.FormatError(err)) return nil diff --git a/auth/auth.go b/auth/auth.go index 91314877c7..aff6de6dce 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -26,10 +26,11 @@ var ( ) type AuthConfig struct { - Username string `json:"username,omitempty"` - Password string `json:"password,omitempty"` - Auth string `json:"auth"` - Email string `json:"email"` + Username string `json:"username,omitempty"` + Password string `json:"password,omitempty"` + Auth string `json:"auth"` + Email string `json:"email"` + ServerAddress string `json:"serveraddress,omitempty"` } type ConfigFile struct { @@ -96,6 +97,7 @@ func LoadConfig(rootPath string) (*ConfigFile, error) { } origEmail := strings.Split(arr[1], " = ") authConfig.Email = origEmail[1] + authConfig.ServerAddress = IndexServerAddress() configFile.Configs[IndexServerAddress()] = authConfig } else { for k, authConfig := range configFile.Configs { @@ -105,6 +107,7 @@ func LoadConfig(rootPath string) (*ConfigFile, error) { } authConfig.Auth = "" configFile.Configs[k] = authConfig + authConfig.ServerAddress = k } } return &configFile, nil @@ -125,7 +128,7 @@ func SaveConfig(configFile *ConfigFile) error { authCopy.Auth = encodeAuth(&authCopy) authCopy.Username = "" authCopy.Password = "" - + authCopy.ServerAddress = "" configs[k] = authCopy } @@ -146,14 +149,26 @@ func Login(authConfig *AuthConfig, factory *utils.HTTPRequestFactory) (string, e reqStatusCode := 0 var status string var reqBody []byte - jsonBody, err := json.Marshal(authConfig) + + serverAddress := authConfig.ServerAddress + if serverAddress == "" { + serverAddress = IndexServerAddress() + } + + loginAgainstOfficialIndex := serverAddress == IndexServerAddress() + + // to avoid sending the server address to the server it should be removed before marshalled + authCopy := *authConfig + authCopy.ServerAddress = "" + + jsonBody, err := json.Marshal(authCopy) if err != nil { return "", fmt.Errorf("Config Error: %s", err) } // using `bytes.NewReader(jsonBody)` here causes the server to respond with a 411 status. b := strings.NewReader(string(jsonBody)) - req1, err := http.Post(IndexServerAddress()+"users/", "application/json; charset=utf-8", b) + req1, err := http.Post(serverAddress+"users/", "application/json; charset=utf-8", b) if err != nil { return "", fmt.Errorf("Server Error: %s", err) } @@ -165,14 +180,23 @@ func Login(authConfig *AuthConfig, factory *utils.HTTPRequestFactory) (string, e } if reqStatusCode == 201 { - status = "Account created. Please use the confirmation link we sent" + - " to your e-mail to activate it." + if loginAgainstOfficialIndex { + status = "Account created. Please use the confirmation link we sent" + + " to your e-mail to activate it." + } else { + status = "Account created. Please see the documentation of the registry " + serverAddress + " for instructions how to activate it." + } } else if reqStatusCode == 403 { - return "", fmt.Errorf("Login: Your account hasn't been activated. " + - "Please check your e-mail for a confirmation link.") + if loginAgainstOfficialIndex { + return "", fmt.Errorf("Login: Your account hasn't been activated. " + + "Please check your e-mail for a confirmation link.") + } else { + return "", fmt.Errorf("Login: Your account hasn't been activated. " + + "Please see the documentation of the registry " + serverAddress + " for instructions how to activate it.") + } } else if reqStatusCode == 400 { if string(reqBody) == "\"Username or email already exists\"" { - req, err := factory.NewRequest("GET", IndexServerAddress()+"users/", nil) + req, err := factory.NewRequest("GET", serverAddress+"users/", nil) req.SetBasicAuth(authConfig.Username, authConfig.Password) resp, err := client.Do(req) if err != nil { @@ -199,3 +223,52 @@ func Login(authConfig *AuthConfig, factory *utils.HTTPRequestFactory) (string, e } return status, nil } + +// this method matches a auth configuration to a server address or a url +func (config *ConfigFile) ResolveAuthConfig(registry string) AuthConfig { + if registry == IndexServerAddress() || len(registry) == 0 { + // default to the index server + return config.Configs[IndexServerAddress()] + } + // if its not the index server there are three cases: + // + // 1. this is a full config url -> it should be used as is + // 2. it could be a full url, but with the wrong protocol + // 3. it can be the hostname optionally with a port + // + // as there is only one auth entry which is fully qualified we need to start + // parsing and matching + + swapProtocoll := func(url string) string { + if strings.HasPrefix(url, "http:") { + return strings.Replace(url, "http:", "https:", 1) + } + if strings.HasPrefix(url, "https:") { + return strings.Replace(url, "https:", "http:", 1) + } + return url + } + + resolveIgnoringProtocol := func(url string) AuthConfig { + if c, found := config.Configs[url]; found { + return c + } + registrySwappedProtocoll := swapProtocoll(url) + // now try to match with the different protocol + if c, found := config.Configs[registrySwappedProtocoll]; found { + return c + } + return AuthConfig{} + } + + // match both protocols as it could also be a server name like httpfoo + if strings.HasPrefix(registry, "http:") || strings.HasPrefix(registry, "https:") { + return resolveIgnoringProtocol(registry) + } + + url := "https://" + registry + if !strings.Contains(registry, "/") { + url = url + "/v1/" + } + return resolveIgnoringProtocol(url) +} diff --git a/commands.go b/commands.go index b2a4d3db13..16340d74ae 100644 --- a/commands.go +++ b/commands.go @@ -4,10 +4,12 @@ import ( "archive/tar" "bufio" "bytes" + "encoding/base64" "encoding/json" "flag" "fmt" "github.com/dotcloud/docker/auth" + "github.com/dotcloud/docker/registry" "github.com/dotcloud/docker/term" "github.com/dotcloud/docker/utils" "io" @@ -91,6 +93,7 @@ func (cli *DockerCli) CmdHelp(args ...string) error { {"login", "Register or Login to the docker registry server"}, {"logs", "Fetch the logs of a container"}, {"port", "Lookup the public-facing port which is NAT-ed to PRIVATE_PORT"}, + {"top", "Lookup the running processes of a container"}, {"ps", "List containers"}, {"pull", "Pull an image or a repository from the docker registry server"}, {"push", "Push an image or a repository to the docker registry server"}, @@ -102,7 +105,6 @@ func (cli *DockerCli) CmdHelp(args ...string) error { {"start", "Start a stopped container"}, {"stop", "Stop a running container"}, {"tag", "Tag an image into a repository"}, - {"top", "Lookup the running processes of a container"}, {"version", "Show the docker version information"}, {"wait", "Block until a container stops, then print its exit code"}, } { @@ -187,10 +189,8 @@ func (cli *DockerCli) CmdBuild(args ...string) error { } else if utils.IsURL(cmd.Arg(0)) || utils.IsGIT(cmd.Arg(0)) { isRemote = true } else { - if fi, err := os.Stat(cmd.Arg(0)); err != nil { + if _, err := os.Stat(cmd.Arg(0)); err != nil { return err - } else if !fi.IsDir() { - return fmt.Errorf("\"%s\" is not a path or URL. Please provide a path to a directory containing a Dockerfile.", cmd.Arg(0)) } context, err = Tar(cmd.Arg(0), Uncompressed) } @@ -254,7 +254,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error { // 'docker login': login / register a user to registry service. func (cli *DockerCli) CmdLogin(args ...string) error { - cmd := Subcmd("login", "[OPTIONS]", "Register or Login to the docker registry server") + cmd := Subcmd("login", "[OPTIONS] [SERVER]", "Register or Login to a docker registry server, if no server is specified \""+auth.IndexServerAddress()+"\" is the default.") var username, password, email string @@ -262,10 +262,17 @@ func (cli *DockerCli) CmdLogin(args ...string) error { cmd.StringVar(&password, "p", "", "password") cmd.StringVar(&email, "e", "", "email") err := cmd.Parse(args) - if err != nil { return nil } + serverAddress := auth.IndexServerAddress() + if len(cmd.Args()) > 0 { + serverAddress, err = registry.ExpandAndVerifyRegistryUrl(cmd.Arg(0)) + if err != nil { + return err + } + fmt.Fprintf(cli.out, "Login against server at %s\n", serverAddress) + } promptDefault := func(prompt string, configDefault string) { if configDefault == "" { @@ -298,19 +305,16 @@ func (cli *DockerCli) CmdLogin(args ...string) error { username = authconfig.Username } } - if username != authconfig.Username { if password == "" { oldState, _ := term.SaveState(cli.terminalFd) fmt.Fprintf(cli.out, "Password: ") - term.DisableEcho(cli.terminalFd, oldState) password = readInput(cli.in, cli.out) fmt.Fprint(cli.out, "\n") term.RestoreTerminal(cli.terminalFd, oldState) - if password == "" { return fmt.Errorf("Error : Password Required") } @@ -327,15 +331,15 @@ func (cli *DockerCli) CmdLogin(args ...string) error { password = authconfig.Password email = authconfig.Email } - authconfig.Username = username authconfig.Password = password authconfig.Email = email - cli.configFile.Configs[auth.IndexServerAddress()] = authconfig + authconfig.ServerAddress = serverAddress + cli.configFile.Configs[serverAddress] = authconfig - body, statusCode, err := cli.call("POST", "/auth", cli.configFile.Configs[auth.IndexServerAddress()]) + body, statusCode, err := cli.call("POST", "/auth", cli.configFile.Configs[serverAddress]) if statusCode == 401 { - delete(cli.configFile.Configs, auth.IndexServerAddress()) + delete(cli.configFile.Configs, serverAddress) auth.SaveConfig(cli.configFile) return err } @@ -812,6 +816,13 @@ func (cli *DockerCli) CmdPush(args ...string) error { cli.LoadConfigFile() + // Resolve the Repository name from fqn to endpoint + name + endpoint, _, err := registry.ResolveRepositoryName(name) + if err != nil { + return err + } + // Resolve the Auth config relevant for this server + authConfig := cli.configFile.ResolveAuthConfig(endpoint) // If we're not using a custom registry, we know the restrictions // applied to repository names and can warn the user in advance. // Custom repositories can have different rules, and we must also @@ -825,8 +836,8 @@ func (cli *DockerCli) CmdPush(args ...string) error { } v := url.Values{} - push := func() error { - buf, err := json.Marshal(cli.configFile.Configs[auth.IndexServerAddress()]) + push := func(authConfig auth.AuthConfig) error { + buf, err := json.Marshal(authConfig) if err != nil { return err } @@ -834,13 +845,14 @@ func (cli *DockerCli) CmdPush(args ...string) error { return cli.stream("POST", "/images/"+name+"/push?"+v.Encode(), bytes.NewBuffer(buf), cli.out) } - if err := push(); err != nil { - if err.Error() == "Authentication is required." { + if err := push(authConfig); err != nil { + if err.Error() == registry.ErrLoginRequired.Error() { fmt.Fprintln(cli.out, "\nPlease login prior to push:") - if err := cli.CmdLogin(""); err != nil { + if err := cli.CmdLogin(endpoint); err != nil { return err } - return push() + authConfig := cli.configFile.ResolveAuthConfig(endpoint) + return push(authConfig) } return err } @@ -864,11 +876,39 @@ func (cli *DockerCli) CmdPull(args ...string) error { *tag = parsedTag } + // Resolve the Repository name from fqn to endpoint + name + endpoint, _, err := registry.ResolveRepositoryName(remote) + if err != nil { + return err + } + + cli.LoadConfigFile() + + // Resolve the Auth config relevant for this server + authConfig := cli.configFile.ResolveAuthConfig(endpoint) v := url.Values{} v.Set("fromImage", remote) v.Set("tag", *tag) - if err := cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.out); err != nil { + pull := func(authConfig auth.AuthConfig) error { + buf, err := json.Marshal(authConfig) + if err != nil { + return err + } + v.Set("authConfig", base64.URLEncoding.EncodeToString(buf)) + + return cli.stream("POST", "/images/create?"+v.Encode(), bytes.NewBuffer(buf), cli.out) + } + + if err := pull(authConfig); err != nil { + if err.Error() == registry.ErrLoginRequired.Error() { + fmt.Fprintln(cli.out, "\nPlease login prior to push:") + if err := cli.CmdLogin(endpoint); err != nil { + return err + } + authConfig := cli.configFile.ResolveAuthConfig(endpoint) + return pull(authConfig) + } return err } diff --git a/docs/sources/api/docker_remote_api_v1.4.rst b/docs/sources/api/docker_remote_api_v1.4.rst index 3ab5cdee0a..df355c78a5 100644 --- a/docs/sources/api/docker_remote_api_v1.4.rst +++ b/docs/sources/api/docker_remote_api_v1.4.rst @@ -991,7 +991,8 @@ Check auth configuration { "username":"hannibal", "password:"xxxx", - "email":"hannibal@a-team.com" + "email":"hannibal@a-team.com", + "serveraddress":"https://index.docker.io/v1/" } **Example response**: diff --git a/docs/sources/commandline/command/login.rst b/docs/sources/commandline/command/login.rst index 57ecaeb00e..46f354d6be 100644 --- a/docs/sources/commandline/command/login.rst +++ b/docs/sources/commandline/command/login.rst @@ -8,10 +8,17 @@ :: - Usage: docker login [OPTIONS] + Usage: docker login [OPTIONS] [SERVER] Register or Login to the docker registry server -e="": email -p="": password -u="": username + + If you want to login to a private registry you can + specify this by adding the server name. + + example: + docker login localhost:8080 + diff --git a/registry/registry.go b/registry/registry.go index 759652f074..f24e0b3502 100644 --- a/registry/registry.go +++ b/registry/registry.go @@ -22,6 +22,7 @@ import ( var ( ErrAlreadyExists = errors.New("Image already exists") ErrInvalidRepositoryName = errors.New("Invalid repository name (ex: \"registry.domain.tld/myrepos\")") + ErrLoginRequired = errors.New("Authentication is required.") ) func pingRegistryEndpoint(endpoint string) error { @@ -102,17 +103,38 @@ func ResolveRepositoryName(reposName string) (string, string, error) { if err := validateRepositoryName(reposName); err != nil { return "", "", err } + endpoint, err := ExpandAndVerifyRegistryUrl(hostname) + if err != nil { + return "", "", err + } + return endpoint, reposName, err +} + +// this method expands the registry name as used in the prefix of a repo +// to a full url. if it already is a url, there will be no change. +// The registry is pinged to test if it http or https +func ExpandAndVerifyRegistryUrl(hostname string) (string, error) { + if strings.HasPrefix(hostname, "http:") || strings.HasPrefix(hostname, "https:") { + // if there is no slash after https:// (8 characters) then we have no path in the url + if strings.LastIndex(hostname, "/") < 9 { + // there is no path given. Expand with default path + hostname = hostname + "/v1/" + } + if err := pingRegistryEndpoint(hostname); err != nil { + return "", errors.New("Invalid Registry endpoint: " + err.Error()) + } + return hostname, nil + } endpoint := fmt.Sprintf("https://%s/v1/", hostname) if err := pingRegistryEndpoint(endpoint); err != nil { utils.Debugf("Registry %s does not work (%s), falling back to http", endpoint, err) endpoint = fmt.Sprintf("http://%s/v1/", hostname) if err = pingRegistryEndpoint(endpoint); err != nil { //TODO: triggering highland build can be done there without "failing" - return "", "", errors.New("Invalid Registry endpoint: " + err.Error()) + return "", errors.New("Invalid Registry endpoint: " + err.Error()) } } - err := validateRepositoryName(reposName) - return endpoint, reposName, err + return endpoint, nil } func doWithCookies(c *http.Client, req *http.Request) (*http.Response, error) { @@ -139,6 +161,9 @@ func (r *Registry) GetRemoteHistory(imgID, registry string, token []string) ([]s req.Header.Set("Authorization", "Token "+strings.Join(token, ", ")) res, err := doWithCookies(r.client, req) if err != nil || res.StatusCode != 200 { + if res.StatusCode == 401 { + return nil, ErrLoginRequired + } if res != nil { return nil, utils.NewHTTPRequestError(fmt.Sprintf("Internal server error: %d trying to fetch remote history for %s", res.StatusCode, imgID), res) } @@ -282,7 +307,7 @@ func (r *Registry) GetRepositoryData(indexEp, remote string) (*RepositoryData, e } defer res.Body.Close() if res.StatusCode == 401 { - return nil, utils.NewHTTPRequestError(fmt.Sprintf("Please login first (HTTP code %d)", res.StatusCode), res) + return nil, ErrLoginRequired } // TODO: Right now we're ignoring checksums in the response body. // In the future, we need to use them to check image validity. diff --git a/server.go b/server.go index 646cb44877..e1ff55206e 100644 --- a/server.go +++ b/server.go @@ -655,6 +655,9 @@ func (srv *Server) ImagePull(localName string, tag string, out io.Writer, sf *ut out = utils.NewWriteFlusher(out) err = srv.pullRepository(r, out, localName, remoteName, tag, endpoint, sf, parallel) + if err == registry.ErrLoginRequired { + return err + } if err != nil { if err := srv.pullImage(r, out, remoteName, endpoint, nil, sf); err != nil { return err From da3bb9a7c6ad79f224ee91a2dd053c1e1b2ad053 Mon Sep 17 00:00:00 2001 From: Marco Hennings Date: Fri, 23 Aug 2013 09:38:33 +0200 Subject: [PATCH 065/128] Move authConfig to a Parameter on postImagePush, too --- api.go | 23 +++++++++++++++++++---- commands.go | 5 +++-- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/api.go b/api.go index 0debbe94f3..8b6d0280e7 100644 --- a/api.go +++ b/api.go @@ -2,8 +2,8 @@ package docker import ( "code.google.com/p/go.net/websocket" - "encoding/json" "encoding/base64" + "encoding/json" "fmt" "github.com/dotcloud/docker/auth" "github.com/dotcloud/docker/utils" @@ -491,12 +491,27 @@ func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http metaHeaders[k] = v } } - if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil { - return err - } if err := parseForm(r); err != nil { return err } + authConfig := &auth.AuthConfig{} + + authEncoded := r.Form.Get("authConfig") + if authEncoded != "" { + // the new format is to handle the authConfg as a parameter + authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) + if err := json.NewDecoder(authJson).Decode(authConfig); err != nil { + // for a pull it is not an error if no auth was given + // to increase compatibilit to existing api it is defaulting to be empty + authConfig = &auth.AuthConfig{} + } + } else { + // the old format is supported for compatibility if there was no authConfig parameter + if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil { + return err + } + + } if vars == nil { return fmt.Errorf("Missing parameter") diff --git a/commands.go b/commands.go index 16340d74ae..3d3728ac9e 100644 --- a/commands.go +++ b/commands.go @@ -841,8 +841,9 @@ func (cli *DockerCli) CmdPush(args ...string) error { if err != nil { return err } + v.Set("authConfig", base64.URLEncoding.EncodeToString(buf)) - return cli.stream("POST", "/images/"+name+"/push?"+v.Encode(), bytes.NewBuffer(buf), cli.out) + return cli.stream("POST", "/images/"+name+"/push?"+v.Encode(), nil, cli.out) } if err := push(authConfig); err != nil { @@ -897,7 +898,7 @@ func (cli *DockerCli) CmdPull(args ...string) error { } v.Set("authConfig", base64.URLEncoding.EncodeToString(buf)) - return cli.stream("POST", "/images/create?"+v.Encode(), bytes.NewBuffer(buf), cli.out) + return cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.out) } if err := pull(authConfig); err != nil { From ad322d7cca4857292d41e164058382f83a06cf98 Mon Sep 17 00:00:00 2001 From: Marco Hennings Date: Fri, 23 Aug 2013 14:02:24 +0200 Subject: [PATCH 066/128] Send corrent endpoint authentication when an image is pulled during the run cmd. --- commands.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/commands.go b/commands.go index 3d3728ac9e..8aba44b594 100644 --- a/commands.go +++ b/commands.go @@ -1432,6 +1432,25 @@ func (cli *DockerCli) CmdRun(args ...string) error { repos, tag := utils.ParseRepositoryTag(config.Image) v.Set("fromImage", repos) v.Set("tag", tag) + + // Resolve the Repository name from fqn to endpoint + name + var endpoint string + endpoint, _, err = registry.ResolveRepositoryName(repos) + if err != nil { + return err + } + + // Load the auth config file, to be able to pull the image + cli.LoadConfigFile() + + // Resolve the Auth config relevant for this server + authConfig := cli.configFile.ResolveAuthConfig(endpoint) + buf, err := json.Marshal(authConfig) + if err != nil { + return err + } + v.Set("authConfig", base64.URLEncoding.EncodeToString(buf)) + err = cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.err) if err != nil { return err From a2603477ddd0a7eba794dd5fd917a4d88c5241da Mon Sep 17 00:00:00 2001 From: Marco Hennings Date: Sat, 24 Aug 2013 21:23:14 +0200 Subject: [PATCH 067/128] Merge on current master --- api.go | 1 - 1 file changed, 1 deletion(-) diff --git a/api.go b/api.go index 8b6d0280e7..ecc9fc47a9 100644 --- a/api.go +++ b/api.go @@ -484,7 +484,6 @@ func postImagesInsert(srv *Server, version float64, w http.ResponseWriter, r *ht } func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - authConfig := &auth.AuthConfig{} metaHeaders := map[string][]string{} for k, v := range r.Header { if strings.HasPrefix(k, "X-Meta-") { From d04beb7f4315c6b659958227954398437a69e5d6 Mon Sep 17 00:00:00 2001 From: shin- Date: Fri, 30 Aug 2013 21:46:19 +0200 Subject: [PATCH 068/128] Pass auth config through headers rather than as URL param --- api.go | 23 +++++++++-------------- commands.go | 35 +++++++++++++++++++++++++---------- 2 files changed, 34 insertions(+), 24 deletions(-) diff --git a/api.go b/api.go index ecc9fc47a9..28765b1b5f 100644 --- a/api.go +++ b/api.go @@ -2,7 +2,6 @@ package docker import ( "code.google.com/p/go.net/websocket" - "encoding/base64" "encoding/json" "fmt" "github.com/dotcloud/docker/auth" @@ -395,13 +394,12 @@ func postImagesCreate(srv *Server, version float64, w http.ResponseWriter, r *ht tag := r.Form.Get("tag") repo := r.Form.Get("repo") - authEncoded := r.Form.Get("authConfig") + authJson := r.Header.Get("X-Registry-Auth") authConfig := &auth.AuthConfig{} - if authEncoded != "" { - authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) - if err := json.NewDecoder(authJson).Decode(authConfig); err != nil { + if authJson != "" { + if err := json.NewDecoder(strings.NewReader(authJson)).Decode(authConfig); err != nil { // for a pull it is not an error if no auth was given - // to increase compatibilit to existing api it is defaulting to be empty + // to increase compatibility with the existing api it is defaulting to be empty authConfig = &auth.AuthConfig{} } } @@ -495,17 +493,14 @@ func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http } authConfig := &auth.AuthConfig{} - authEncoded := r.Form.Get("authConfig") - if authEncoded != "" { - // the new format is to handle the authConfg as a parameter - authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) - if err := json.NewDecoder(authJson).Decode(authConfig); err != nil { - // for a pull it is not an error if no auth was given - // to increase compatibilit to existing api it is defaulting to be empty + authJson := r.Header.Get("X-Registry-Auth") + if authJson != "" { + if err := json.NewDecoder(strings.NewReader(authJson)).Decode(authConfig); err != nil { + // to increase compatibility with the existing api it is defaulting to be empty authConfig = &auth.AuthConfig{} } } else { - // the old format is supported for compatibility if there was no authConfig parameter + // the old format is supported for compatibility if there was no authConfig header if err := json.NewDecoder(r.Body).Decode(authConfig); err != nil { return err } diff --git a/commands.go b/commands.go index 8aba44b594..83aa7bfeb9 100644 --- a/commands.go +++ b/commands.go @@ -128,7 +128,7 @@ func (cli *DockerCli) CmdInsert(args ...string) error { v.Set("url", cmd.Arg(1)) v.Set("path", cmd.Arg(2)) - if err := cli.stream("POST", "/images/"+cmd.Arg(0)+"/insert?"+v.Encode(), nil, cli.out); err != nil { + if err := cli.stream("POST", "/images/"+cmd.Arg(0)+"/insert?"+v.Encode(), nil, cli.out, nil); err != nil { return err } return nil @@ -795,7 +795,7 @@ func (cli *DockerCli) CmdImport(args ...string) error { v.Set("tag", tag) v.Set("fromSrc", src) - err := cli.stream("POST", "/images/create?"+v.Encode(), cli.in, cli.out) + err := cli.stream("POST", "/images/create?"+v.Encode(), cli.in, cli.out, nil) if err != nil { return err } @@ -841,9 +841,13 @@ func (cli *DockerCli) CmdPush(args ...string) error { if err != nil { return err } - v.Set("authConfig", base64.URLEncoding.EncodeToString(buf)) + registryAuthHeader := []string{ + string(buf), + } - return cli.stream("POST", "/images/"+name+"/push?"+v.Encode(), nil, cli.out) + return cli.stream("POST", "/images/"+name+"/push?"+v.Encode(), nil, cli.out, map[string][]string{ + "X-Registry-Auth": registryAuthHeader, + }) } if err := push(authConfig); err != nil { @@ -896,9 +900,13 @@ func (cli *DockerCli) CmdPull(args ...string) error { if err != nil { return err } - v.Set("authConfig", base64.URLEncoding.EncodeToString(buf)) + registryAuthHeader := []string{ + string(buf), + } - return cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.out) + return cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.out, map[string][]string{ + "X-Registry-Auth": registryAuthHeader, + }) } if err := pull(authConfig); err != nil { @@ -1143,7 +1151,7 @@ func (cli *DockerCli) CmdEvents(args ...string) error { v.Set("since", *since) } - if err := cli.stream("GET", "/events?"+v.Encode(), nil, cli.out); err != nil { + if err := cli.stream("GET", "/events?"+v.Encode(), nil, cli.out, nil); err != nil { return err } return nil @@ -1160,7 +1168,7 @@ func (cli *DockerCli) CmdExport(args ...string) error { return nil } - if err := cli.stream("GET", "/containers/"+cmd.Arg(0)+"/export", nil, cli.out); err != nil { + if err := cli.stream("GET", "/containers/"+cmd.Arg(0)+"/export", nil, cli.out, nil); err != nil { return err } return nil @@ -1451,7 +1459,7 @@ func (cli *DockerCli) CmdRun(args ...string) error { } v.Set("authConfig", base64.URLEncoding.EncodeToString(buf)) - err = cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.err) + err = cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.err, nil) if err != nil { return err } @@ -1628,7 +1636,7 @@ func (cli *DockerCli) call(method, path string, data interface{}) ([]byte, int, return body, resp.StatusCode, nil } -func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer) error { +func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer, headers map[string][]string) error { if (method == "POST" || method == "PUT") && in == nil { in = bytes.NewReader([]byte{}) } @@ -1641,6 +1649,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") } + + if headers != nil { + for k, v := range headers { + req.Header[k] = v + } + } + dial, err := net.Dial(cli.proto, cli.addr) if err != nil { if strings.Contains(err.Error(), "connection refused") { From dd4aab8411ec070baeb14ecc43f683d12b722746 Mon Sep 17 00:00:00 2001 From: shin- Date: Fri, 30 Aug 2013 22:49:37 +0200 Subject: [PATCH 069/128] Use base64 encoding --- api.go | 18 +++++++++++------- commands.go | 4 ++-- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/api.go b/api.go index 28765b1b5f..39d242099d 100644 --- a/api.go +++ b/api.go @@ -2,6 +2,7 @@ package docker import ( "code.google.com/p/go.net/websocket" + "encoding/base64" "encoding/json" "fmt" "github.com/dotcloud/docker/auth" @@ -394,10 +395,11 @@ func postImagesCreate(srv *Server, version float64, w http.ResponseWriter, r *ht tag := r.Form.Get("tag") repo := r.Form.Get("repo") - authJson := r.Header.Get("X-Registry-Auth") + authEncoded := r.Header.Get("X-Registry-Auth") authConfig := &auth.AuthConfig{} - if authJson != "" { - if err := json.NewDecoder(strings.NewReader(authJson)).Decode(authConfig); err != nil { + if authEncoded != "" { + authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) + if err := json.NewDecoder(authJson).Decode(authConfig); err != nil { // for a pull it is not an error if no auth was given // to increase compatibility with the existing api it is defaulting to be empty authConfig = &auth.AuthConfig{} @@ -493,10 +495,12 @@ func postImagesPush(srv *Server, version float64, w http.ResponseWriter, r *http } authConfig := &auth.AuthConfig{} - authJson := r.Header.Get("X-Registry-Auth") - if authJson != "" { - if err := json.NewDecoder(strings.NewReader(authJson)).Decode(authConfig); err != nil { - // to increase compatibility with the existing api it is defaulting to be empty + authEncoded := r.Header.Get("X-Registry-Auth") + if authEncoded != "" { + // the new format is to handle the authConfig as a header + authJson := base64.NewDecoder(base64.URLEncoding, strings.NewReader(authEncoded)) + if err := json.NewDecoder(authJson).Decode(authConfig); err != nil { + // to increase compatibility to existing api it is defaulting to be empty authConfig = &auth.AuthConfig{} } } else { diff --git a/commands.go b/commands.go index 83aa7bfeb9..24f6de13a6 100644 --- a/commands.go +++ b/commands.go @@ -842,7 +842,7 @@ func (cli *DockerCli) CmdPush(args ...string) error { return err } registryAuthHeader := []string{ - string(buf), + base64.URLEncoding.EncodeToString(buf), } return cli.stream("POST", "/images/"+name+"/push?"+v.Encode(), nil, cli.out, map[string][]string{ @@ -901,7 +901,7 @@ func (cli *DockerCli) CmdPull(args ...string) error { return err } registryAuthHeader := []string{ - string(buf), + base64.URLEncoding.EncodeToString(buf), } return cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.out, map[string][]string{ From ded973219e997f52634eb18d0cfe828472412dd8 Mon Sep 17 00:00:00 2001 From: Marco Hennings Date: Fri, 30 Aug 2013 23:15:07 +0200 Subject: [PATCH 070/128] Move auth header on run cmd --- commands.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/commands.go b/commands.go index 24f6de13a6..b373267456 100644 --- a/commands.go +++ b/commands.go @@ -1457,9 +1457,13 @@ func (cli *DockerCli) CmdRun(args ...string) error { if err != nil { return err } - v.Set("authConfig", base64.URLEncoding.EncodeToString(buf)) - err = cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.err, nil) + registryAuthHeader := []string{ + base64.URLEncoding.EncodeToString(buf), + } + err = cli.stream("POST", "/images/create?"+v.Encode(), nil, cli.err, map[string][]string{ + "X-Registry-Auth": registryAuthHeader, + }) if err != nil { return err } From fac4cedcc105a7b5a6789917ddf9a5e9745bd2d3 Mon Sep 17 00:00:00 2001 From: Hector Castro Date: Tue, 3 Sep 2013 16:29:07 -0400 Subject: [PATCH 071/128] Add a Docker example for running Riak. --- docs/sources/examples/index.rst | 1 + .../sources/examples/running_riak_service.rst | 151 ++++++++++++++++++ 2 files changed, 152 insertions(+) create mode 100644 docs/sources/examples/running_riak_service.rst diff --git a/docs/sources/examples/index.rst b/docs/sources/examples/index.rst index 2664b95e54..1114790280 100644 --- a/docs/sources/examples/index.rst +++ b/docs/sources/examples/index.rst @@ -22,3 +22,4 @@ Contents: couchdb_data_volumes postgresql_service mongodb + running_riak_service diff --git a/docs/sources/examples/running_riak_service.rst b/docs/sources/examples/running_riak_service.rst new file mode 100644 index 0000000000..d98de6a77c --- /dev/null +++ b/docs/sources/examples/running_riak_service.rst @@ -0,0 +1,151 @@ +:title: Running a Riak service +:description: Build a Docker image with Riak pre-installed +:keywords: docker, example, package installation, networking, riak + +Riak Service +============================== + +.. include:: example_header.inc + +The goal of this example is to show you how to build a Docker image with Riak +pre-installed. + +Creating a ``Dockerfile`` ++++++++++++++++++++++++++ + +Create an empty file called ``Dockerfile``: + +.. code-block:: bash + + touch Dockerfile + +Next, define the parent image you want to use to build your image on top of. +We’ll use `Ubuntu `_ (tag: ``latest``), +which is available on the `docker index `_: + +.. code-block:: bash + + # Riak + # + # VERSION 0.1.0 + + # Use the Ubuntu base image provided by dotCloud + FROM ubuntu:latest + MAINTAINER Hector Castro hector@basho.com + +Next, we update the APT cache and apply any updates: + +.. code-block:: bash + + # Update the APT cache + RUN sed -i.bak 's/main$/main universe/' /etc/apt/sources.list + RUN apt-get update + RUN apt-get upgrade -y + +After that, we install and setup a few dependencies: + +- ``curl`` is used to download Basho's APT repository key +- ``lsb-release`` helps us derive the Ubuntu release codename +- ``openssh-server`` allows us to login to containers remotely and join Riak + nodes to form a cluster +- ``supervisor`` is used manage the OpenSSH and Riak processes + +.. code-block:: bash + + # Install and setup project dependencies + RUN apt-get install -y curl lsb-release supervisor openssh-server + + RUN mkdir -p /var/run/sshd + RUN mkdir -p /var/log/supervisor + + RUN locale-gen en_US en_US.UTF-8 + + ADD supervisord.conf /etc/supervisor/conf.d/supervisord.conf + + RUN echo 'root:basho' | chpasswd + +Next, we add Basho's APT repository: + +.. code-block:: bash + + RUN curl -s http://apt.basho.com/gpg/basho.apt.key | apt-key add -- + RUN echo "deb http://apt.basho.com $(lsb_release -cs) main" > /etc/apt/sources.list.d/basho.list + RUN apt-get update + +After that, we install Riak and alter a few defaults: + +.. code-block:: bash + + # Install Riak and prepare it to run + RUN apt-get install -y riak + RUN sed -i.bak 's/127.0.0.1/0.0.0.0/' /etc/riak/app.config + RUN echo "ulimit -n 4096" >> /etc/default/riak + +Almost there. Next, we add a hack to get us by the lack of ``initctl``: + +.. code-block:: bash + + # Hack for initctl + # See: https://github.com/dotcloud/docker/issues/1024 + RUN dpkg-divert --local --rename --add /sbin/initctl + RUN ln -s /bin/true /sbin/initctl + +Then, we expose the Riak Protocol Buffers and HTTP interfaces, along with SSH: + +.. code-block:: bash + + # Expose Riak Protocol Buffers and HTTP interfaces, along with SSH + EXPOSE 8087 8098 22 + +Finally, run ``supervisord`` so that Riak and OpenSSH are started: + +.. code-block:: bash + + CMD ["/usr/bin/supervisord"] + +Create a ``supervisord`` configuration file ++++++++++++++++++++++++++++++++++++++++++++ + +Create an empty file called ``supervisord.conf``. Make sure it's at the same +level as your ``Dockerfile``: + +.. code-block:: bash + + touch supervisord.conf + +Populate it with the following program definitions: + +.. code-block:: bash + + [supervisord] + nodaemon=true + + [program:sshd] + command=/usr/sbin/sshd -D + stdout_logfile=/var/log/supervisor/%(program_name)s.log + stderr_logfile=/var/log/supervisor/%(program_name)s.log + autorestart=true + + [program:riak] + command=bash -c ". /etc/default/riak && /usr/sbin/riak console" + pidfile=/var/log/riak/riak.pid + stdout_logfile=/var/log/supervisor/%(program_name)s.log + stderr_logfile=/var/log/supervisor/%(program_name)s.log + +Build the Docker image for Riak ++++++++++++++++++++++++++++++++ + +Now you should be able to build a Docker image for Riak: + +.. code-block:: bash + + docker build -t "/riak" . + +Next steps +++++++++++ + +Riak is a distributed database. Many production deployments consist of `at +least five nodes `_. See the `docker-riak `_ project details on how to deploy a Riak cluster using Docker and +Pipework. From 846524115bb6d1ce850f9babba59467c1054fbdd Mon Sep 17 00:00:00 2001 From: Daniel Mizyrycki Date: Tue, 3 Sep 2013 15:38:06 -0700 Subject: [PATCH 072/128] testing, issue #1620: Add index functional test on docker-ci --- testing/Vagrantfile | 10 +++-- testing/buildbot/master.cfg | 20 ++++++--- testing/buildbot/requirements.txt | 1 + testing/functionaltests/test_index.py | 61 +++++++++++++++++++++++++++ 4 files changed, 82 insertions(+), 10 deletions(-) create mode 100755 testing/functionaltests/test_index.py diff --git a/testing/Vagrantfile b/testing/Vagrantfile index fd1c5916c8..1d15709a36 100644 --- a/testing/Vagrantfile +++ b/testing/Vagrantfile @@ -40,10 +40,12 @@ Vagrant::Config.run do |config| "#{ENV['SMTP_PWD']} #{ENV['EMAIL_RCP']}; " \ "#{CFG_PATH}/setup_credentials.sh #{USER} " \ "#{ENV['REGISTRY_USER']} #{ENV['REGISTRY_PWD']}; " - # Install docker dependencies - pkg_cmd << "curl -s https://go.googlecode.com/files/go1.1.1.linux-amd64.tar.gz | " \ - "tar -v -C /usr/local -xz; ln -s /usr/local/go/bin/go /usr/bin/go; " \ - "DEBIAN_FRONTEND=noninteractive apt-get install -q -y lxc git mercurial aufs-tools make; " \ + # Install docker and testing dependencies + pkg_cmd << "curl -s https://go.googlecode.com/files/go1.1.2.linux-amd64.tar.gz | " \ + " tar -v -C /usr/local -xz; ln -s /usr/local/go/bin/go /usr/bin/go; " \ + "curl -s https://phantomjs.googlecode.com/files/phantomjs-1.9.1-linux-x86_64.tar.bz2 | " \ + " tar jx -C /usr/bin --strip-components=2 phantomjs-1.9.1-linux-x86_64/bin/phantomjs; " \ + "DEBIAN_FRONTEND=noninteractive apt-get install -q -y lxc git mercurial aufs-tools make libfontconfig; " \ "export GOPATH=/data/docker-dependencies; go get -d github.com/dotcloud/docker; " \ "rm -rf ${GOPATH}/src/github.com/dotcloud/docker; " # Activate new kernel options diff --git a/testing/buildbot/master.cfg b/testing/buildbot/master.cfg index cc261c7a3e..7962fe9e9d 100644 --- a/testing/buildbot/master.cfg +++ b/testing/buildbot/master.cfg @@ -45,16 +45,17 @@ c['slavePortnum'] = PORT_MASTER # Schedulers c['schedulers'] = [ForceScheduler(name='trigger', builderNames=[BUILDER_NAME, - 'registry','coverage'])] + 'index','registry','coverage'])] c['schedulers'] += [SingleBranchScheduler(name="all", change_filter=filter.ChangeFilter(branch='master'), treeStableTimer=None, builderNames=[BUILDER_NAME])] c['schedulers'] += [SingleBranchScheduler(name='pullrequest', change_filter=filter.ChangeFilter(category='github_pullrequest'), treeStableTimer=None, builderNames=['pullrequest'])] -c['schedulers'] += [Nightly(name='daily', branch=None, builderNames=['coverage','registry'], +c['schedulers'] += [Nightly(name='daily', branch=None, builderNames=['coverage'], hour=0, minute=30)] - +c['schedulers'] += [Nightly(name='every4hrs', branch=None, builderNames=['registry','index'], + hour=range(0,24,4), minute=15)] # Builders # Docker commit test @@ -64,7 +65,6 @@ factory.addStep(ShellCommand(description='Docker',logEnviron=False,usePTY=True, "cp -r {2}-dependencies/src {0}; export GOPATH={0}; go get {3}; cd {1}; " "git reset --hard %(src::revision)s; go test -v".format( BUILDER_PATH, BUILDER_PATH+'/src/'+GITHUB_DOCKER, DOCKER_PATH, GITHUB_DOCKER))])) - c['builders'] = [BuilderConfig(name=BUILDER_NAME,slavenames=['buildworker'], factory=factory)] @@ -91,16 +91,24 @@ factory.addStep(ShellCommand(description='Coverage',logEnviron=False,usePTY=True c['builders'] += [BuilderConfig(name='coverage',slavenames=['buildworker'], factory=factory)] -# Registry Functionaltest builder +# Registry functional test factory = BuildFactory() factory.addStep(ShellCommand(description='registry', logEnviron=False, command='. {0}/master/credentials.cfg; ' '{1}/testing/functionaltests/test_registry.sh'.format(BUILDBOT_PATH, DOCKER_PATH), usePTY=True)) - c['builders'] += [BuilderConfig(name='registry',slavenames=['buildworker'], factory=factory)] +# Index functional test +factory = BuildFactory() +factory.addStep(ShellCommand(description='index', logEnviron=False, + command='. {0}/master/credentials.cfg; ' + '{1}/testing/functionaltests/test_index.py'.format(BUILDBOT_PATH, + DOCKER_PATH), usePTY=True)) +c['builders'] += [BuilderConfig(name='index',slavenames=['buildworker'], + factory=factory)] + # Status authz_cfg = authz.Authz(auth=auth.BasicAuth([(TEST_USER, TEST_PWD)]), diff --git a/testing/buildbot/requirements.txt b/testing/buildbot/requirements.txt index c5d7dd0191..d2dcf1d125 100644 --- a/testing/buildbot/requirements.txt +++ b/testing/buildbot/requirements.txt @@ -6,3 +6,4 @@ nose==1.2.1 requests==1.1.0 flask==0.10.1 simplejson==2.3.2 +selenium==2.35.0 diff --git a/testing/functionaltests/test_index.py b/testing/functionaltests/test_index.py new file mode 100755 index 0000000000..fd002c81e8 --- /dev/null +++ b/testing/functionaltests/test_index.py @@ -0,0 +1,61 @@ +#!/usr/bin/python + +import os +username, password = os.environ['DOCKER_CREDS'].split(':') + +from selenium import webdriver +from selenium.webdriver.common.by import By +from selenium.webdriver.common.keys import Keys +from selenium.webdriver.support.ui import Select +from selenium.common.exceptions import NoSuchElementException +import unittest, time, re + +class Docker(unittest.TestCase): + def setUp(self): + self.driver = webdriver.PhantomJS() + self.driver.implicitly_wait(30) + self.base_url = "http://www.docker.io/" + self.verificationErrors = [] + self.accept_next_alert = True + + def test_docker(self): + driver = self.driver + print "Login into {0} as login user {1} ...".format(self.base_url,username) + driver.get(self.base_url + "/") + driver.find_element_by_link_text("INDEX").click() + driver.find_element_by_link_text("login").click() + driver.find_element_by_id("id_username").send_keys(username) + driver.find_element_by_id("id_password").send_keys(password) + print "Checking login user ..." + driver.find_element_by_css_selector("input[type=\"submit\"]").click() + try: self.assertEqual("test", driver.find_element_by_css_selector("h3").text) + except AssertionError as e: self.verificationErrors.append(str(e)) + print "Login user {0} found".format(username) + + def is_element_present(self, how, what): + try: self.driver.find_element(by=how, value=what) + except NoSuchElementException, e: return False + return True + + def is_alert_present(self): + try: self.driver.switch_to_alert() + except NoAlertPresentException, e: return False + return True + + def close_alert_and_get_its_text(self): + try: + alert = self.driver.switch_to_alert() + alert_text = alert.text + if self.accept_next_alert: + alert.accept() + else: + alert.dismiss() + return alert_text + finally: self.accept_next_alert = True + + def tearDown(self): + self.driver.quit() + self.assertEqual([], self.verificationErrors) + +if __name__ == "__main__": + unittest.main() From 52fe45249798991a35f5a5b5a4e1af26ea42221c Mon Sep 17 00:00:00 2001 From: Markus Fix Date: Tue, 3 Sep 2013 17:19:05 +0200 Subject: [PATCH 073/128] Changed docker apt repo URI from https to http because the http driver for apt is not installed by default --- Vagrantfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Vagrantfile b/Vagrantfile index 53620eabc6..4767a8b1cc 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -18,7 +18,7 @@ Vagrant::Config.run do |config| if Dir.glob("#{File.dirname(__FILE__)}/.vagrant/machines/default/*/id").empty? # Add lxc-docker package pkg_cmd = "wget -q -O - http://get.docker.io/gpg | apt-key add -;" \ - "echo deb https://get.docker.io/ubuntu docker main > /etc/apt/sources.list.d/docker.list;" \ + "echo deb http://get.docker.io/ubuntu docker main > /etc/apt/sources.list.d/docker.list;" \ "apt-get update -qq; apt-get install -q -y --force-yes lxc-docker; " # Add Ubuntu raring backported kernel pkg_cmd << "apt-get update -qq; apt-get install -q -y linux-image-generic-lts-raring; " From 91ee67954961e9f8e5b4827022c2a3aee7997253 Mon Sep 17 00:00:00 2001 From: Daniel Mizyrycki Date: Tue, 3 Sep 2013 17:14:49 -0700 Subject: [PATCH 074/128] pr #1718: Fetch docker gpg over https --- Vagrantfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Vagrantfile b/Vagrantfile index 4767a8b1cc..0b4df3d684 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -17,7 +17,7 @@ Vagrant::Config.run do |config| # Provision docker and new kernel if deployment was not done if Dir.glob("#{File.dirname(__FILE__)}/.vagrant/machines/default/*/id").empty? # Add lxc-docker package - pkg_cmd = "wget -q -O - http://get.docker.io/gpg | apt-key add -;" \ + pkg_cmd = "wget -q -O - https://get.docker.io/gpg | apt-key add -;" \ "echo deb http://get.docker.io/ubuntu docker main > /etc/apt/sources.list.d/docker.list;" \ "apt-get update -qq; apt-get install -q -y --force-yes lxc-docker; " # Add Ubuntu raring backported kernel From b3a70d767d913ec476bc1af53983c91a10d17f84 Mon Sep 17 00:00:00 2001 From: shin- Date: Wed, 4 Sep 2013 02:20:50 +0200 Subject: [PATCH 075/128] Compute dependency graph and upload layers in the right order when pushing --- server.go | 96 ++++++++++++++++++++++++------------ utils/utils.go | 116 ++++++++++++++++++++++++++++++++++++++++++++ utils/utils_test.go | 57 ++++++++++++++++++++++ 3 files changed, 237 insertions(+), 32 deletions(-) diff --git a/server.go b/server.go index 646cb44877..69edabf07a 100644 --- a/server.go +++ b/server.go @@ -667,29 +667,57 @@ func (srv *Server) ImagePull(localName string, tag string, out io.Writer, sf *ut // Retrieve the all the images to be uploaded in the correct order // Note: we can't use a map as it is not ordered -func (srv *Server) getImageList(localRepo map[string]string) ([]*registry.ImgData, error) { - var imgList []*registry.ImgData +func (srv *Server) getImageList(localRepo map[string]string) ([][]*registry.ImgData, error) { + imgList := map[string]*registry.ImgData{} + depGraph := utils.NewDependencyGraph() - imageSet := make(map[string]struct{}) for tag, id := range localRepo { img, err := srv.runtime.graph.Get(id) if err != nil { return nil, err } - img.WalkHistory(func(img *Image) error { - if _, exists := imageSet[img.ID]; exists { + depGraph.NewNode(img.ID) + img.WalkHistory(func(current *Image) error { + imgList[current.ID] = ®istry.ImgData{ + ID: current.ID, + Tag: tag, + } + parent, err := current.GetParent() + if err != nil { + return err + } + if parent == nil { return nil } - imageSet[img.ID] = struct{}{} - - imgList = append([]*registry.ImgData{{ - ID: img.ID, - Tag: tag, - }}, imgList...) + depGraph.NewNode(parent.ID) + depGraph.AddDependency(current.ID, parent.ID) return nil }) } - return imgList, nil + + traversalMap, err := depGraph.GenerateTraversalMap() + if err != nil { + return nil, err + } + + utils.Debugf("Traversal map: %v", traversalMap) + result := [][]*registry.ImgData{} + for _, round := range traversalMap { + dataRound := []*registry.ImgData{} + for _, imgID := range round { + dataRound = append(dataRound, imgList[imgID]) + } + result = append(result, dataRound) + } + return result, nil +} + +func flatten(slc [][]*registry.ImgData) []*registry.ImgData { + result := []*registry.ImgData{} + for _, x := range slc { + result = append(result, x...) + } + return result } func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, localName, remoteName string, localRepo map[string]string, indexEp string, sf *utils.StreamFormatter) error { @@ -698,39 +726,43 @@ func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, localName if err != nil { return err } + flattenedImgList := flatten(imgList) out.Write(sf.FormatStatus("", "Sending image list")) var repoData *registry.RepositoryData - repoData, err = r.PushImageJSONIndex(indexEp, remoteName, imgList, false, nil) + repoData, err = r.PushImageJSONIndex(indexEp, remoteName, flattenedImgList, false, nil) if err != nil { return err } for _, ep := range repoData.Endpoints { out.Write(sf.FormatStatus("", "Pushing repository %s (%d tags)", localName, len(localRepo))) - // For each image within the repo, push them - for _, elem := range imgList { - if _, exists := repoData.ImgList[elem.ID]; exists { - out.Write(sf.FormatStatus("", "Image %s already pushed, skipping", elem.ID)) - continue - } else if r.LookupRemoteImage(elem.ID, ep, repoData.Tokens) { - out.Write(sf.FormatStatus("", "Image %s already pushed, skipping", elem.ID)) - continue - } - if checksum, err := srv.pushImage(r, out, remoteName, elem.ID, ep, repoData.Tokens, sf); err != nil { - // FIXME: Continue on error? - return err - } else { - elem.Checksum = checksum - } - out.Write(sf.FormatStatus("", "Pushing tags for rev [%s] on {%s}", elem.ID, ep+"repositories/"+remoteName+"/tags/"+elem.Tag)) - if err := r.PushRegistryTag(remoteName, elem.ID, elem.Tag, ep, repoData.Tokens); err != nil { - return err + // This section can not be parallelized (each round depends on the previous one) + for _, round := range imgList { + // FIXME: This section can be parallelized + for _, elem := range round { + if _, exists := repoData.ImgList[elem.ID]; exists { + out.Write(sf.FormatStatus("", "Image %s already pushed, skipping", elem.ID)) + continue + } else if r.LookupRemoteImage(elem.ID, ep, repoData.Tokens) { + out.Write(sf.FormatStatus("", "Image %s already pushed, skipping", elem.ID)) + continue + } + if checksum, err := srv.pushImage(r, out, remoteName, elem.ID, ep, repoData.Tokens, sf); err != nil { + // FIXME: Continue on error? + return err + } else { + elem.Checksum = checksum + } + out.Write(sf.FormatStatus("", "Pushing tags for rev [%s] on {%s}", elem.ID, ep+"repositories/"+remoteName+"/tags/"+elem.Tag)) + if err := r.PushRegistryTag(remoteName, elem.ID, elem.Tag, ep, repoData.Tokens); err != nil { + return err + } } } } - if _, err := r.PushImageJSONIndex(indexEp, remoteName, imgList, true, repoData.Endpoints); err != nil { + if _, err := r.PushImageJSONIndex(indexEp, remoteName, flattenedImgList, true, repoData.Endpoints); err != nil { return err } diff --git a/utils/utils.go b/utils/utils.go index e8cf08aaba..b761da0fbd 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -889,3 +889,119 @@ func UserLookup(uid string) (*user.User, error) { } return nil, fmt.Errorf("User not found in /etc/passwd") } + +type DependencyGraph struct{ + nodes map[string]*DependencyNode +} + +type DependencyNode struct{ + id string + deps map[*DependencyNode]bool +} + +func NewDependencyGraph() DependencyGraph { + return DependencyGraph{ + nodes: map[string]*DependencyNode{}, + } +} + +func (graph *DependencyGraph) addNode(node *DependencyNode) string { + if graph.nodes[node.id] == nil { + graph.nodes[node.id] = node + } + return node.id +} + +func (graph *DependencyGraph) NewNode(id string) string { + if graph.nodes[id] != nil { + return id + } + nd := &DependencyNode{ + id: id, + deps: map[*DependencyNode]bool{}, + } + graph.addNode(nd) + return id +} + +func (graph *DependencyGraph) AddDependency(node, to string) error { + if graph.nodes[node] == nil { + return fmt.Errorf("Node %s does not belong to this graph", node) + } + + if graph.nodes[to] == nil { + return fmt.Errorf("Node %s does not belong to this graph", to) + } + + if node == to { + return fmt.Errorf("Dependency loops are forbidden!") + } + + graph.nodes[node].addDependency(graph.nodes[to]) + return nil +} + +func (node *DependencyNode) addDependency(to *DependencyNode) bool { + node.deps[to] = true + return node.deps[to] +} + +func (node *DependencyNode) Degree() int { + return len(node.deps) +} + +// The magic happens here :: +func (graph *DependencyGraph) GenerateTraversalMap() ([][]string, error) { + Debugf("Generating traversal map. Nodes: %d", len(graph.nodes)) + result := [][]string{} + processed := map[*DependencyNode]bool{} + // As long as we haven't processed all nodes... + for len(processed) < len(graph.nodes) { + // Use a temporary buffer for processed nodes, otherwise + // nodes that depend on each other could end up in the same round. + tmp_processed := []*DependencyNode{} + for _, node := range graph.nodes { + // If the node has more dependencies than what we have cleared, + // it won't be valid for this round. + if node.Degree() > len(processed) { + continue + } + // If it's already processed, get to the next one + if processed[node] { + continue + } + // It's not been processed yet and has 0 deps. Add it! + // (this is a shortcut for what we're doing below) + if node.Degree() == 0 { + tmp_processed = append(tmp_processed, node) + continue + } + // If at least one dep hasn't been processed yet, we can't + // add it. + ok := true + for dep, _ := range node.deps { + if !processed[dep] { + ok = false + break + } + } + // All deps have already been processed. Add it! + if ok { + tmp_processed = append(tmp_processed, node) + } + } + Debugf("Round %d: found %d available nodes", len(result), len(tmp_processed)) + // If no progress has been made this round, + // that means we have circular dependencies. + if len(tmp_processed) == 0 { + return nil, fmt.Errorf("Could not find a solution to this dependency graph") + } + round := []string{} + for _, nd := range tmp_processed { + round = append(round, nd.id) + processed[nd] = true + } + result = append(result, round) + } + return result, nil +} \ No newline at end of file diff --git a/utils/utils_test.go b/utils/utils_test.go index 3341650860..be796b2381 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -365,3 +365,60 @@ func TestParseRelease(t *testing.T) { assertParseRelease(t, "3.4.54.longterm-1", &KernelVersionInfo{Kernel: 3, Major: 4, Minor: 54, Flavor: "1"}, 0) assertParseRelease(t, "3.8.0-19-generic", &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Flavor: "19-generic"}, 0) } + + +func TestDependencyGraphCircular(t *testing.T) { + g1 := NewDependencyGraph() + a := g1.NewNode("a") + b := g1.NewNode("b") + g1.AddDependency(a, b) + g1.AddDependency(b, a) + res, err := g1.GenerateTraversalMap() + if res != nil { + t.Fatalf("Expected nil result") + } + if err == nil { + t.Fatalf("Expected error (circular graph can not be resolved)") + } +} + +func TestDependencyGraph(t *testing.T) { + g1 := NewDependencyGraph() + a := g1.NewNode("a") + b := g1.NewNode("b") + c := g1.NewNode("c") + d := g1.NewNode("d") + g1.AddDependency(b, a) + g1.AddDependency(c, a) + g1.AddDependency(d, c) + g1.AddDependency(d, b) + res, err := g1.GenerateTraversalMap() + + if err != nil { + t.Fatalf("%s", err) + } + + if res == nil { + t.Fatalf("Unexpected nil result") + } + + if len(res) != 3 { + t.Fatalf("Expected map of length 3, found %d instead", len(res)) + } + + if len(res[0]) != 1 || res[0][0] != "a" { + t.Fatalf("Expected [a], found %v instead", res[0]) + } + + if len(res[1]) != 2 { + t.Fatalf("Expected 2 nodes for step 2, found %d", len(res[1])) + } + + if (res[1][0] != "b" && res[1][1] != "b") || (res[1][0] != "c" && res[1][1] != "c") { + t.Fatalf("Expected [b, c], found %v instead", res[1]) + } + + if len(res[2]) != 1 || res[2][0] != "d" { + t.Fatalf("Expected [d], found %v instead", res[2]) + } +} \ No newline at end of file From 251d1261b0c2edfc8b014141cc03d32c30263226 Mon Sep 17 00:00:00 2001 From: Briehan Lombaard Date: Wed, 4 Sep 2013 10:52:53 +0200 Subject: [PATCH 076/128] Fixed typos --- docs/sources/commandline/command/run.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/sources/commandline/command/run.rst b/docs/sources/commandline/command/run.rst index cd283669e6..d29e70dd50 100644 --- a/docs/sources/commandline/command/run.rst +++ b/docs/sources/commandline/command/run.rst @@ -67,7 +67,7 @@ use-cases, like running Docker within Docker. docker run -w /path/to/dir/ -i -t ubuntu pwd -The ``-w`` lets the command beeing executed inside directory given, +The ``-w`` lets the command being executed inside directory given, here /path/to/dir/. If the path does not exists it is created inside the container. @@ -76,8 +76,8 @@ container. docker run -v `pwd`:`pwd` -w `pwd` -i -t ubuntu pwd The ``-v`` flag mounts the current working directory into the container. -The ``-w`` lets the command beeing executed inside the current -working directory, by changeing into the directory to the value +The ``-w`` lets the command being executed inside the current +working directory, by changing into the directory to the value returned by ``pwd``. So this combination executes the command using the container, but inside the current working directory. From 9ce1d02ef6b0593861809248307d058656f56304 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Wed, 4 Sep 2013 02:45:54 -0600 Subject: [PATCH 077/128] Add Gentoo documentation --- docs/sources/installation/gentoolinux.rst | 98 +++++++++++++++++++++++ docs/sources/installation/index.rst | 1 + 2 files changed, 99 insertions(+) create mode 100644 docs/sources/installation/gentoolinux.rst diff --git a/docs/sources/installation/gentoolinux.rst b/docs/sources/installation/gentoolinux.rst new file mode 100644 index 0000000000..6fdd51747a --- /dev/null +++ b/docs/sources/installation/gentoolinux.rst @@ -0,0 +1,98 @@ +:title: Installation on Gentoo Linux +:description: Docker installation instructions and nuances for Gentoo Linux. +:keywords: gentoo linux, virtualization, docker, documentation, installation + +.. _gentoo_linux: + +Gentoo Linux +============ + +.. include:: install_header.inc + +.. include:: install_unofficial.inc + +Installing Docker on Gentoo Linux can be accomplished by using the overlay provided at https://github.com/tianon/docker-overlay. The most up-to-date documentation for properly installing the overlay can be found in the overlay README. The information here is provided for reference, and may be out of date. + +Installation +^^^^^^^^^^^^ + +Ensure that layman is installed: + +.. code-block:: bash + + sudo emerge -av app-portage/layman + +Using your favorite editor, add ``https://raw.github.com/tianon/docker-overlay/master/repositories.xml`` to the ``overlays`` section in ``/etc/layman/layman.cfg`` (as per instructions on the `Gentoo Wiki `_), then invoke the following: + +.. code-block:: bash + + sudo layman -f -a docker + +Once that completes, the ``app-emulation/lxc-docker`` package will be available for emerge: + +.. code-block:: bash + + sudo emerge -av app-emulation/lxc-docker + +If you prefer to use the official binaries, or just do not wish to compile docker, emerge ``app-emulation/lxc-docker-bin`` instead. It is important to remember that Gentoo is still an unsupported platform, even when using the official binaries. + +The package should already include all the necessary dependencies. For the simplest installation experiece, use ``sys-kernel/aufs-sources`` directly as your kernel sources. If you prefer not to use ``sys-kernel/aufs-sources``, the portage tree also contains ``sys-fs/aufs3``, which contains the patches necessary for adding AUFS support to other kernel source packages (and a ``kernel-patch`` use flag to perform the patching automatically). + +Between ``app-emulation/lxc`` and ``app-emulation/lxc-docker``, all the necessary kernel configuration flags should be checked for and warned about in the standard manner. + +If any issues arise from this ebuild or the resulting binary, including and especially missing kernel configuration flags and/or dependencies, `open an issue `_ on the docker-overlay repository or ping tianon in the #docker IRC channel. + +Starting Docker +^^^^^^^^^^^^^^^ + +Ensure that you are running a kernel that includes the necessary AUFS support and includes all the necessary modules and/or configuration for LXC. + +OpenRC +------ + +To start the docker daemon: + +.. code-block:: bash + + sudo /etc/init.d/docker start + +To start on system boot: + +.. code-block:: bash + + sudo rc-update add docker default + +systemd +------- + +To start the docker daemon: + +.. code-block:: bash + + sudo systemctl start docker.service + +To start on system boot: + +.. code-block:: bash + + sudo systemctl enable docker.service + +Network Configuration +^^^^^^^^^^^^^^^^^^^^^ + +IPv4 packet forwarding is disabled by default, so internet access from inside the container will not work unless ``net.ipv4.ip_forward`` is enabled: + +.. code-block:: bash + + sudo sysctl -w net.ipv4.ip_forward=1 + +Or, to enable it more permanently: + +.. code-block:: bash + + echo net.ipv4.ip_forward = 1 | sudo tee /etc/sysctl.d/docker.conf + +fork/exec /usr/sbin/lxc-start: operation not permitted +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Unfortunately, Gentoo suffers from `issue #1422 `_, meaning that after every fresh start of docker, the first docker run fails due to some tricky terminal issues, so be sure to run something trivial (such as ``docker run -i -t busybox echo hi``) before attempting to run anything important. diff --git a/docs/sources/installation/index.rst b/docs/sources/installation/index.rst index c2a93f5a01..1a73cb7ae6 100644 --- a/docs/sources/installation/index.rst +++ b/docs/sources/installation/index.rst @@ -24,5 +24,6 @@ Contents: amazon rackspace archlinux + gentoolinux upgrading kernel From 58ffd03bf16f41b9c954e61ef948768f44871c28 Mon Sep 17 00:00:00 2001 From: Vladimir Kirillov Date: Wed, 4 Sep 2013 12:53:38 +0300 Subject: [PATCH 078/128] add new docker api client for erlang, erldocker --- docs/sources/api/docker_remote_api.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/sources/api/docker_remote_api.rst b/docs/sources/api/docker_remote_api.rst index 6e4bd2bba0..be15c3494e 100644 --- a/docs/sources/api/docker_remote_api.rst +++ b/docs/sources/api/docker_remote_api.rst @@ -191,4 +191,5 @@ and we will add the libraries here. +----------------------+----------------+--------------------------------------------+ | Java | docker-java | https://github.com/kpelykh/docker-java | +----------------------+----------------+--------------------------------------------+ - +| Erlang | erldocker | https://github.com/proger/erldocker | ++----------------------+----------------+--------------------------------------------+ From 368d0385e1d426a32da66135e7ff8d4f5a3c7a26 Mon Sep 17 00:00:00 2001 From: Thijs Terlouw Date: Wed, 4 Sep 2013 13:21:44 +0200 Subject: [PATCH 079/128] Fix documentation index for cli --- docs/sources/commandline/cli.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/sources/commandline/cli.rst b/docs/sources/commandline/cli.rst index 9f8c296fe6..edc618b823 100644 --- a/docs/sources/commandline/cli.rst +++ b/docs/sources/commandline/cli.rst @@ -32,11 +32,13 @@ Available Commands command/commit command/cp command/diff + command/events command/export command/history command/images command/import command/info + command/insert command/inspect command/kill command/login From 34145f9840b62de85d385a65702f054962878cae Mon Sep 17 00:00:00 2001 From: Daniel Mizyrycki Date: Wed, 4 Sep 2013 10:05:54 -0700 Subject: [PATCH 080/128] deployment, issue #1787: Remove deprecated port forwarding from /Vagrantfile --- Vagrantfile | 1 - 1 file changed, 1 deletion(-) diff --git a/Vagrantfile b/Vagrantfile index 66de6fe227..ab631dbe26 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -12,7 +12,6 @@ Vagrant::Config.run do |config| # Setup virtual machine box. This VM configuration code is always executed. config.vm.box = BOX_NAME config.vm.box_url = BOX_URI - config.vm.forward_port 4243, 4243 # Provision docker and new kernel if deployment was not done if Dir.glob("#{File.dirname(__FILE__)}/.vagrant/machines/default/*/id").empty? From ad5796de9febb5724bd2bf0639ae617c2ff34ba5 Mon Sep 17 00:00:00 2001 From: Tommaso Visconti Date: Wed, 4 Sep 2013 19:17:10 +0200 Subject: [PATCH 081/128] Adds instruction about UDP port redirection --- docs/sources/use/port_redirection.rst | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/sources/use/port_redirection.rst b/docs/sources/use/port_redirection.rst index b19673af27..5c321d429d 100644 --- a/docs/sources/use/port_redirection.rst +++ b/docs/sources/use/port_redirection.rst @@ -8,7 +8,7 @@ Port redirection ================ -Docker can redirect public TCP ports to your container, so it can be +Docker can redirect public TCP and UDP ports to your container, so it can be reached over the network. Port redirection is done on ``docker run`` using the -p flag. @@ -25,6 +25,12 @@ will be allocated. # PUBLIC port 80 is redirected to PRIVATE port 80 sudo docker run -p 80:80 +To redirect a UDP port the redirection must be expressed as *PUBLIC:PRIVATE/udp*: + +.. code-block:: bash + + # PUBLIC port 5300 is redirected to the PRIVATE port 53 using UDP + sudo docker run -p 5300:53/udp Default port redirects can be built into a container with the ``EXPOSE`` build command. From 28e75b23b39aa0aee951378bf1039886c00f7783 Mon Sep 17 00:00:00 2001 From: Jimmy Cuadra Date: Sat, 17 Aug 2013 23:58:04 -0700 Subject: [PATCH 082/128] Don't install VirtualBox Guest Additions if VAGRANT_DEFAULT_PROVIDER is set. --- Vagrantfile | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/Vagrantfile b/Vagrantfile index ab631dbe26..4609f9fa2d 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -22,11 +22,8 @@ Vagrant::Config.run do |config| # Add Ubuntu raring backported kernel pkg_cmd << "apt-get update -qq; apt-get install -q -y linux-image-generic-lts-raring; " # Add guest additions if local vbox VM - is_vbox = true - # The logic here makes a few assumptions (i.e. no one uses --provider=virtualbox) - ARGV.each do |arg| is_vbox &&= ( !arg.downcase.start_with?("--provider") && !ENV['VAGRANT_DEFAULT_PROVIDER'] )end - if is_vbox - pkg_cmd << "apt-get install -q -y linux-headers-generic-lts-raring dkms; " \ + if ENV["VAGRANT_DEFAULT_PROVIDER"].nil? && ARGV.none? { |arg| arg.downcase.start_with?("--provider") } + pkg_cmd << "apt-get install -q -y linux-headers-3.8.0-19-generic dkms; " \ "echo 'Downloading VBox Guest Additions...'; " \ "wget -q http://dlc.sun.com.edgesuite.net/virtualbox/4.2.12/VBoxGuestAdditions_4.2.12.iso; " # Prepare the VM to add guest additions after reboot From 396274fa6db88466fef0b1a5e314ba5b077cd3f2 Mon Sep 17 00:00:00 2001 From: Elias Probst Date: Wed, 4 Sep 2013 21:56:51 +0200 Subject: [PATCH 083/128] Typo --- docs/sources/installation/gentoolinux.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/installation/gentoolinux.rst b/docs/sources/installation/gentoolinux.rst index 6fdd51747a..aa809768ee 100644 --- a/docs/sources/installation/gentoolinux.rst +++ b/docs/sources/installation/gentoolinux.rst @@ -36,7 +36,7 @@ Once that completes, the ``app-emulation/lxc-docker`` package will be available If you prefer to use the official binaries, or just do not wish to compile docker, emerge ``app-emulation/lxc-docker-bin`` instead. It is important to remember that Gentoo is still an unsupported platform, even when using the official binaries. -The package should already include all the necessary dependencies. For the simplest installation experiece, use ``sys-kernel/aufs-sources`` directly as your kernel sources. If you prefer not to use ``sys-kernel/aufs-sources``, the portage tree also contains ``sys-fs/aufs3``, which contains the patches necessary for adding AUFS support to other kernel source packages (and a ``kernel-patch`` use flag to perform the patching automatically). +The package should already include all the necessary dependencies. For the simplest installation experience, use ``sys-kernel/aufs-sources`` directly as your kernel sources. If you prefer not to use ``sys-kernel/aufs-sources``, the portage tree also contains ``sys-fs/aufs3``, which contains the patches necessary for adding AUFS support to other kernel source packages (and a ``kernel-patch`` use flag to perform the patching automatically). Between ``app-emulation/lxc`` and ``app-emulation/lxc-docker``, all the necessary kernel configuration flags should be checked for and warned about in the standard manner. From 98edd0e7510183f561716307299be8834617917b Mon Sep 17 00:00:00 2001 From: shin- Date: Wed, 4 Sep 2013 22:58:58 +0200 Subject: [PATCH 084/128] Updated docs for API v1.5 --- docs/sources/api/docker_remote_api.rst | 26 +- docs/sources/api/docker_remote_api_v1.5.rst | 1175 +++++++++++++++++++ 2 files changed, 1199 insertions(+), 2 deletions(-) create mode 100644 docs/sources/api/docker_remote_api_v1.5.rst diff --git a/docs/sources/api/docker_remote_api.rst b/docs/sources/api/docker_remote_api.rst index be15c3494e..70db4a5eb1 100644 --- a/docs/sources/api/docker_remote_api.rst +++ b/docs/sources/api/docker_remote_api.rst @@ -27,14 +27,36 @@ Docker Remote API 2. Versions =========== -The current version of the API is 1.4 +The current version of the API is 1.5 Calling /images//insert is the same as calling -/v1.4/images//insert +/v1.5/images//insert You can still call an old version of the api using /v1.0/images//insert +:doc:`docker_remote_api_v1.5` +***************************** + +What's new +---------- + +.. http:post:: /images/create + + **New!** You can now pass registry credentials (via an AuthConfig object) + through the `X-Registry-Auth` header + +.. http:post:: /images/(name)/push + + **New!** The AuthConfig object now needs to be passed through + the `X-Registry-Auth` header + +.. http:get:: /containers/json + + **New!** The format of the `Ports` entry has been changed to a list of + dicts each containing `PublicPort`, `PrivatePort` and `Type` describing a + port mapping. + :doc:`docker_remote_api_v1.4` ***************************** diff --git a/docs/sources/api/docker_remote_api_v1.5.rst b/docs/sources/api/docker_remote_api_v1.5.rst new file mode 100644 index 0000000000..4f994f5bf0 --- /dev/null +++ b/docs/sources/api/docker_remote_api_v1.5.rst @@ -0,0 +1,1175 @@ +:title: Remote API v1.5 +:description: API Documentation for Docker +:keywords: API, Docker, rcli, REST, documentation + +:orphan: + +====================== +Docker Remote API v1.5 +====================== + +.. contents:: Table of Contents + +1. Brief introduction +===================== + +- The Remote API is replacing rcli +- Default port in the docker daemon is 4243 +- The API tends to be REST, but for some complex commands, like attach or pull, the HTTP connection is hijacked to transport stdout stdin and stderr + +2. Endpoints +============ + +2.1 Containers +-------------- + +List containers +*************** + +.. http:get:: /containers/json + + List containers + + **Example request**: + + .. sourcecode:: http + + GET /containers/json?all=1&before=8dfafdbc3a40&size=1 HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + [ + { + "Id": "8dfafdbc3a40", + "Image": "base:latest", + "Command": "echo 1", + "Created": 1367854155, + "Status": "Exit 0", + "Ports":[{"PrivatePort": 2222, "PublicPort": 3333, "Type": "tcp"}], + "SizeRw":12288, + "SizeRootFs":0 + }, + { + "Id": "9cd87474be90", + "Image": "base:latest", + "Command": "echo 222222", + "Created": 1367854155, + "Status": "Exit 0", + "Ports":[], + "SizeRw":12288, + "SizeRootFs":0 + }, + { + "Id": "3176a2479c92", + "Image": "base:latest", + "Command": "echo 3333333333333333", + "Created": 1367854154, + "Status": "Exit 0", + "Ports":[], + "SizeRw":12288, + "SizeRootFs":0 + }, + { + "Id": "4cb07b47f9fb", + "Image": "base:latest", + "Command": "echo 444444444444444444444444444444444", + "Created": 1367854152, + "Status": "Exit 0", + "Ports":[], + "SizeRw":12288, + "SizeRootFs":0 + } + ] + + :query all: 1/True/true or 0/False/false, Show all containers. Only running containers are shown by default + :query limit: Show ``limit`` last created containers, include non-running ones. + :query since: Show only containers created since Id, include non-running ones. + :query before: Show only containers created before Id, include non-running ones. + :query size: 1/True/true or 0/False/false, Show the containers sizes + :statuscode 200: no error + :statuscode 400: bad parameter + :statuscode 500: server error + + +Create a container +****************** + +.. http:post:: /containers/create + + Create a container + + **Example request**: + + .. sourcecode:: http + + POST /containers/create HTTP/1.1 + Content-Type: application/json + + { + "Hostname":"", + "User":"", + "Memory":0, + "MemorySwap":0, + "AttachStdin":false, + "AttachStdout":true, + "AttachStderr":true, + "PortSpecs":null, + "Privileged": false, + "Tty":false, + "OpenStdin":false, + "StdinOnce":false, + "Env":null, + "Cmd":[ + "date" + ], + "Dns":null, + "Image":"base", + "Volumes":{}, + "VolumesFrom":"", + "WorkingDir":"" + + } + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 201 OK + Content-Type: application/json + + { + "Id":"e90e34656806" + "Warnings":[] + } + + :jsonparam config: the container's configuration + :statuscode 201: no error + :statuscode 404: no such container + :statuscode 406: impossible to attach (container not running) + :statuscode 500: server error + + +Inspect a container +******************* + +.. http:get:: /containers/(id)/json + + Return low-level information on the container ``id`` + + **Example request**: + + .. sourcecode:: http + + GET /containers/4fa6e0f0c678/json HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "Id": "4fa6e0f0c6786287e131c3852c58a2e01cc697a68231826813597e4994f1d6e2", + "Created": "2013-05-07T14:51:42.041847+02:00", + "Path": "date", + "Args": [], + "Config": { + "Hostname": "4fa6e0f0c678", + "User": "", + "Memory": 0, + "MemorySwap": 0, + "AttachStdin": false, + "AttachStdout": true, + "AttachStderr": true, + "PortSpecs": null, + "Tty": false, + "OpenStdin": false, + "StdinOnce": false, + "Env": null, + "Cmd": [ + "date" + ], + "Dns": null, + "Image": "base", + "Volumes": {}, + "VolumesFrom": "", + "WorkingDir":"" + + }, + "State": { + "Running": false, + "Pid": 0, + "ExitCode": 0, + "StartedAt": "2013-05-07T14:51:42.087658+02:01360", + "Ghost": false + }, + "Image": "b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", + "NetworkSettings": { + "IpAddress": "", + "IpPrefixLen": 0, + "Gateway": "", + "Bridge": "", + "PortMapping": null + }, + "SysInitPath": "/home/kitty/go/src/github.com/dotcloud/docker/bin/docker", + "ResolvConfPath": "/etc/resolv.conf", + "Volumes": {} + } + + :statuscode 200: no error + :statuscode 404: no such container + :statuscode 500: server error + + +List processes running inside a container +***************************************** + +.. http:get:: /containers/(id)/top + + List processes running inside the container ``id`` + + **Example request**: + + .. sourcecode:: http + + GET /containers/4fa6e0f0c678/top HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "Titles":[ + "USER", + "PID", + "%CPU", + "%MEM", + "VSZ", + "RSS", + "TTY", + "STAT", + "START", + "TIME", + "COMMAND" + ], + "Processes":[ + ["root","20147","0.0","0.1","18060","1864","pts/4","S","10:06","0:00","bash"], + ["root","20271","0.0","0.0","4312","352","pts/4","S+","10:07","0:00","sleep","10"] + ] + } + + :query ps_args: ps arguments to use (eg. aux) + :statuscode 200: no error + :statuscode 404: no such container + :statuscode 500: server error + + +Inspect changes on a container's filesystem +******************************************* + +.. http:get:: /containers/(id)/changes + + Inspect changes on container ``id`` 's filesystem + + **Example request**: + + .. sourcecode:: http + + GET /containers/4fa6e0f0c678/changes HTTP/1.1 + + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + [ + { + "Path":"/dev", + "Kind":0 + }, + { + "Path":"/dev/kmsg", + "Kind":1 + }, + { + "Path":"/test", + "Kind":1 + } + ] + + :statuscode 200: no error + :statuscode 404: no such container + :statuscode 500: server error + + +Export a container +****************** + +.. http:get:: /containers/(id)/export + + Export the contents of container ``id`` + + **Example request**: + + .. sourcecode:: http + + GET /containers/4fa6e0f0c678/export HTTP/1.1 + + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/octet-stream + + {{ STREAM }} + + :statuscode 200: no error + :statuscode 404: no such container + :statuscode 500: server error + + +Start a container +***************** + +.. http:post:: /containers/(id)/start + + Start the container ``id`` + + **Example request**: + + .. sourcecode:: http + + POST /containers/(id)/start HTTP/1.1 + Content-Type: application/json + + { + "Binds":["/tmp:/tmp"], + "LxcConf":{"lxc.utsname":"docker"} + } + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 204 No Content + Content-Type: text/plain + + :jsonparam hostConfig: the container's host configuration (optional) + :statuscode 204: no error + :statuscode 404: no such container + :statuscode 500: server error + + +Stop a container +**************** + +.. http:post:: /containers/(id)/stop + + Stop the container ``id`` + + **Example request**: + + .. sourcecode:: http + + POST /containers/e90e34656806/stop?t=5 HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 204 OK + + :query t: number of seconds to wait before killing the container + :statuscode 204: no error + :statuscode 404: no such container + :statuscode 500: server error + + +Restart a container +******************* + +.. http:post:: /containers/(id)/restart + + Restart the container ``id`` + + **Example request**: + + .. sourcecode:: http + + POST /containers/e90e34656806/restart?t=5 HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 204 OK + + :query t: number of seconds to wait before killing the container + :statuscode 204: no error + :statuscode 404: no such container + :statuscode 500: server error + + +Kill a container +**************** + +.. http:post:: /containers/(id)/kill + + Kill the container ``id`` + + **Example request**: + + .. sourcecode:: http + + POST /containers/e90e34656806/kill HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 204 OK + + :statuscode 204: no error + :statuscode 404: no such container + :statuscode 500: server error + + +Attach to a container +********************* + +.. http:post:: /containers/(id)/attach + + Attach to the container ``id`` + + **Example request**: + + .. sourcecode:: http + + POST /containers/16253994b7c4/attach?logs=1&stream=0&stdout=1 HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/vnd.docker.raw-stream + + {{ STREAM }} + + :query logs: 1/True/true or 0/False/false, return logs. Default false + :query stream: 1/True/true or 0/False/false, return stream. Default false + :query stdin: 1/True/true or 0/False/false, if stream=true, attach to stdin. Default false + :query stdout: 1/True/true or 0/False/false, if logs=true, return stdout log, if stream=true, attach to stdout. Default false + :query stderr: 1/True/true or 0/False/false, if logs=true, return stderr log, if stream=true, attach to stderr. Default false + :statuscode 200: no error + :statuscode 400: bad parameter + :statuscode 404: no such container + :statuscode 500: server error + + +Wait a container +**************** + +.. http:post:: /containers/(id)/wait + + Block until container ``id`` stops, then returns the exit code + + **Example request**: + + .. sourcecode:: http + + POST /containers/16253994b7c4/wait HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + {"StatusCode":0} + + :statuscode 200: no error + :statuscode 404: no such container + :statuscode 500: server error + + +Remove a container +******************* + +.. http:delete:: /containers/(id) + + Remove the container ``id`` from the filesystem + + **Example request**: + + .. sourcecode:: http + + DELETE /containers/16253994b7c4?v=1 HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 204 OK + + :query v: 1/True/true or 0/False/false, Remove the volumes associated to the container. Default false + :statuscode 204: no error + :statuscode 400: bad parameter + :statuscode 404: no such container + :statuscode 500: server error + + +Copy files or folders from a container +************************************** + +.. http:post:: /containers/(id)/copy + + Copy files or folders of container ``id`` + + **Example request**: + + .. sourcecode:: http + + POST /containers/4fa6e0f0c678/copy HTTP/1.1 + Content-Type: application/json + + { + "Resource":"test.txt" + } + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/octet-stream + + {{ STREAM }} + + :statuscode 200: no error + :statuscode 404: no such container + :statuscode 500: server error + + +2.2 Images +---------- + +List Images +*********** + +.. http:get:: /images/(format) + + List images ``format`` could be json or viz (json default) + + **Example request**: + + .. sourcecode:: http + + GET /images/json?all=0 HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + [ + { + "Repository":"base", + "Tag":"ubuntu-12.10", + "Id":"b750fe79269d", + "Created":1364102658, + "Size":24653, + "VirtualSize":180116135 + }, + { + "Repository":"base", + "Tag":"ubuntu-quantal", + "Id":"b750fe79269d", + "Created":1364102658, + "Size":24653, + "VirtualSize":180116135 + } + ] + + + **Example request**: + + .. sourcecode:: http + + GET /images/viz HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: text/plain + + digraph docker { + "d82cbacda43a" -> "074be284591f" + "1496068ca813" -> "08306dc45919" + "08306dc45919" -> "0e7893146ac2" + "b750fe79269d" -> "1496068ca813" + base -> "27cf78414709" [style=invis] + "f71189fff3de" -> "9a33b36209ed" + "27cf78414709" -> "b750fe79269d" + "0e7893146ac2" -> "d6434d954665" + "d6434d954665" -> "d82cbacda43a" + base -> "e9aa60c60128" [style=invis] + "074be284591f" -> "f71189fff3de" + "b750fe79269d" [label="b750fe79269d\nbase",shape=box,fillcolor="paleturquoise",style="filled,rounded"]; + "e9aa60c60128" [label="e9aa60c60128\nbase2",shape=box,fillcolor="paleturquoise",style="filled,rounded"]; + "9a33b36209ed" [label="9a33b36209ed\ntest",shape=box,fillcolor="paleturquoise",style="filled,rounded"]; + base [style=invisible] + } + + :query all: 1/True/true or 0/False/false, Show all containers. Only running containers are shown by default + :statuscode 200: no error + :statuscode 400: bad parameter + :statuscode 500: server error + + +Create an image +*************** + +.. http:post:: /images/create + + Create an image, either by pull it from the registry or by importing it + + **Example request**: + + .. sourcecode:: http + + POST /images/create?fromImage=base HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + {"status":"Pulling..."} + {"status":"Pulling", "progress":"1/? (n/a)"} + {"error":"Invalid..."} + ... + + When using this endpoint to pull an image from the registry, + the ``X-Registry-Auth`` header can be used to include a + base64-encoded AuthConfig object. + + :query fromImage: name of the image to pull + :query fromSrc: source to import, - means stdin + :query repo: repository + :query tag: tag + :query registry: the registry to pull from + :statuscode 200: no error + :statuscode 500: server error + + +Insert a file in an image +************************* + +.. http:post:: /images/(name)/insert + + Insert a file from ``url`` in the image ``name`` at ``path`` + + **Example request**: + + .. sourcecode:: http + + POST /images/test/insert?path=/usr&url=myurl HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + {"status":"Inserting..."} + {"status":"Inserting", "progress":"1/? (n/a)"} + {"error":"Invalid..."} + ... + + :statuscode 200: no error + :statuscode 500: server error + + +Inspect an image +**************** + +.. http:get:: /images/(name)/json + + Return low-level information on the image ``name`` + + **Example request**: + + .. sourcecode:: http + + GET /images/base/json HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "id":"b750fe79269d2ec9a3c593ef05b4332b1d1a02a62b4accb2c21d589ff2f5f2dc", + "parent":"27cf784147099545", + "created":"2013-03-23T22:24:18.818426-07:00", + "container":"3d67245a8d72ecf13f33dffac9f79dcdf70f75acb84d308770391510e0c23ad0", + "container_config": + { + "Hostname":"", + "User":"", + "Memory":0, + "MemorySwap":0, + "AttachStdin":false, + "AttachStdout":false, + "AttachStderr":false, + "PortSpecs":null, + "Tty":true, + "OpenStdin":true, + "StdinOnce":false, + "Env":null, + "Cmd": ["/bin/bash"] + ,"Dns":null, + "Image":"base", + "Volumes":null, + "VolumesFrom":"", + "WorkingDir":"" + }, + "Size": 6824592 + } + + :statuscode 200: no error + :statuscode 404: no such image + :statuscode 500: server error + + +Get the history of an image +*************************** + +.. http:get:: /images/(name)/history + + Return the history of the image ``name`` + + **Example request**: + + .. sourcecode:: http + + GET /images/base/history HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + [ + { + "Id":"b750fe79269d", + "Created":1364102658, + "CreatedBy":"/bin/bash" + }, + { + "Id":"27cf78414709", + "Created":1364068391, + "CreatedBy":"" + } + ] + + :statuscode 200: no error + :statuscode 404: no such image + :statuscode 500: server error + + +Push an image on the registry +***************************** + +.. http:post:: /images/(name)/push + + Push the image ``name`` on the registry + + **Example request**: + + .. sourcecode:: http + + POST /images/test/push HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + {"status":"Pushing..."} + {"status":"Pushing", "progress":"1/? (n/a)"} + {"error":"Invalid..."} + ... + + The ``X-Registry-Auth`` header can be used to include a + base64-encoded AuthConfig object. + + :query registry: the registry you wan to push, optional + :statuscode 200: no error + :statuscode 404: no such image + :statuscode 500: server error + + +Tag an image into a repository +****************************** + +.. http:post:: /images/(name)/tag + + Tag the image ``name`` into a repository + + **Example request**: + + .. sourcecode:: http + + POST /images/test/tag?repo=myrepo&force=0 HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + + :query repo: The repository to tag in + :query force: 1/True/true or 0/False/false, default false + :statuscode 200: no error + :statuscode 400: bad parameter + :statuscode 404: no such image + :statuscode 409: conflict + :statuscode 500: server error + + +Remove an image +*************** + +.. http:delete:: /images/(name) + + Remove the image ``name`` from the filesystem + + **Example request**: + + .. sourcecode:: http + + DELETE /images/test HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-type: application/json + + [ + {"Untagged":"3e2f21a89f"}, + {"Deleted":"3e2f21a89f"}, + {"Deleted":"53b4f83ac9"} + ] + + :statuscode 200: no error + :statuscode 404: no such image + :statuscode 409: conflict + :statuscode 500: server error + + +Search images +************* + +.. http:get:: /images/search + + Search for an image in the docker index + + **Example request**: + + .. sourcecode:: http + + GET /images/search?term=sshd HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + [ + { + "Name":"cespare/sshd", + "Description":"" + }, + { + "Name":"johnfuller/sshd", + "Description":"" + }, + { + "Name":"dhrp/mongodb-sshd", + "Description":"" + } + ] + + :query term: term to search + :statuscode 200: no error + :statuscode 500: server error + + +2.3 Misc +-------- + +Build an image from Dockerfile via stdin +**************************************** + +.. http:post:: /build + + Build an image from Dockerfile via stdin + + **Example request**: + + .. sourcecode:: http + + POST /build HTTP/1.1 + + {{ STREAM }} + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + + {{ STREAM }} + + + The stream must be a tar archive compressed with one of the following algorithms: + identity (no compression), gzip, bzip2, xz. The archive must include a file called + `Dockerfile` at its root. It may include any number of other files, which will be + accessible in the build context (See the ADD build command). + + The Content-type header should be set to "application/tar". + + :query t: repository name (and optionally a tag) to be applied to the resulting image in case of success + :query q: suppress verbose build output + :query nocache: do not use the cache when building the image + :statuscode 200: no error + :statuscode 500: server error + + +Check auth configuration +************************ + +.. http:post:: /auth + + Get the default username and email + + **Example request**: + + .. sourcecode:: http + + POST /auth HTTP/1.1 + Content-Type: application/json + + { + "username":"hannibal", + "password:"xxxx", + "email":"hannibal@a-team.com", + "serveraddress":"https://index.docker.io/v1/" + } + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + + :statuscode 200: no error + :statuscode 204: no error + :statuscode 500: server error + + +Display system-wide information +******************************* + +.. http:get:: /info + + Display system-wide information + + **Example request**: + + .. sourcecode:: http + + GET /info HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "Containers":11, + "Images":16, + "Debug":false, + "NFd": 11, + "NGoroutines":21, + "MemoryLimit":true, + "SwapLimit":false, + "IPv4Forwarding":true + } + + :statuscode 200: no error + :statuscode 500: server error + + +Show the docker version information +*********************************** + +.. http:get:: /version + + Show the docker version information + + **Example request**: + + .. sourcecode:: http + + GET /version HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "Version":"0.2.2", + "GitCommit":"5a2a5cc+CHANGES", + "GoVersion":"go1.0.3" + } + + :statuscode 200: no error + :statuscode 500: server error + + +Create a new image from a container's changes +********************************************* + +.. http:post:: /commit + + Create a new image from a container's changes + + **Example request**: + + .. sourcecode:: http + + POST /commit?container=44c004db4b17&m=message&repo=myrepo HTTP/1.1 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 201 OK + Content-Type: application/vnd.docker.raw-stream + + {"Id":"596069db4bf5"} + + :query container: source container + :query repo: repository + :query tag: tag + :query m: commit message + :query author: author (eg. "John Hannibal Smith ") + :query run: config automatically applied when the image is run. (ex: {"Cmd": ["cat", "/world"], "PortSpecs":["22"]}) + :statuscode 201: no error + :statuscode 404: no such container + :statuscode 500: server error + + +Monitor Docker's events +*********************** + +.. http:get:: /events + + Get events from docker, either in real time via streaming, or via polling (using `since`) + + **Example request**: + + .. sourcecode:: http + + POST /events?since=1374067924 + + **Example response**: + + .. sourcecode:: http + + HTTP/1.1 200 OK + Content-Type: application/json + + {"status":"create","id":"dfdf82bd3881","from":"base:latest","time":1374067924} + {"status":"start","id":"dfdf82bd3881","from":"base:latest","time":1374067924} + {"status":"stop","id":"dfdf82bd3881","from":"base:latest","time":1374067966} + {"status":"destroy","id":"dfdf82bd3881","from":"base:latest","time":1374067970} + + :query since: timestamp used for polling + :statuscode 200: no error + :statuscode 500: server error + + +3. Going further +================ + +3.1 Inside 'docker run' +----------------------- + +Here are the steps of 'docker run' : + +* Create the container +* If the status code is 404, it means the image doesn't exists: + * Try to pull it + * Then retry to create the container +* Start the container +* If you are not in detached mode: + * Attach to the container, using logs=1 (to have stdout and stderr from the container's start) and stream=1 +* If in detached mode or only stdin is attached: + * Display the container's id + + +3.2 Hijacking +------------- + +In this version of the API, /attach, uses hijacking to transport stdin, stdout and stderr on the same socket. This might change in the future. + +3.3 CORS Requests +----------------- + +To enable cross origin requests to the remote api add the flag "-api-enable-cors" when running docker in daemon mode. + +.. code-block:: bash + + docker -d -H="192.168.1.9:4243" -api-enable-cors + From 8878943ac9ab3b3344d398b45e68ebb7cea879f0 Mon Sep 17 00:00:00 2001 From: Daniel Mizyrycki Date: Wed, 4 Sep 2013 14:38:03 -0700 Subject: [PATCH 085/128] deployment, issue #1578: Avoid pinning kernel headers. Add Vagrantfile assumptions --- Vagrantfile | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Vagrantfile b/Vagrantfile index 4609f9fa2d..85af1280a2 100644 --- a/Vagrantfile +++ b/Vagrantfile @@ -13,7 +13,8 @@ Vagrant::Config.run do |config| config.vm.box = BOX_NAME config.vm.box_url = BOX_URI - # Provision docker and new kernel if deployment was not done + # Provision docker and new kernel if deployment was not done. + # It is assumed Vagrant can successfully launch the provider instance. if Dir.glob("#{File.dirname(__FILE__)}/.vagrant/machines/default/*/id").empty? # Add lxc-docker package pkg_cmd = "wget -q -O - https://get.docker.io/gpg | apt-key add -;" \ @@ -21,9 +22,10 @@ Vagrant::Config.run do |config| "apt-get update -qq; apt-get install -q -y --force-yes lxc-docker; " # Add Ubuntu raring backported kernel pkg_cmd << "apt-get update -qq; apt-get install -q -y linux-image-generic-lts-raring; " - # Add guest additions if local vbox VM + # Add guest additions if local vbox VM. As virtualbox is the default provider, + # it is assumed it won't be explicitly stated. if ENV["VAGRANT_DEFAULT_PROVIDER"].nil? && ARGV.none? { |arg| arg.downcase.start_with?("--provider") } - pkg_cmd << "apt-get install -q -y linux-headers-3.8.0-19-generic dkms; " \ + pkg_cmd << "apt-get install -q -y linux-headers-generic-lts-raring dkms; " \ "echo 'Downloading VBox Guest Additions...'; " \ "wget -q http://dlc.sun.com.edgesuite.net/virtualbox/4.2.12/VBoxGuestAdditions_4.2.12.iso; " # Prepare the VM to add guest additions after reboot From 9ae5054c3477c6eb9afc64ad44ff41bb3f10a8ab Mon Sep 17 00:00:00 2001 From: shin- Date: Wed, 4 Sep 2013 23:41:44 +0200 Subject: [PATCH 086/128] Bumped API version in api.go ; added <1.5 behavior to getContainersJSON --- api.go | 14 ++++++++++++-- api_params.go | 24 ++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/api.go b/api.go index 39d242099d..fa6057945f 100644 --- a/api.go +++ b/api.go @@ -21,7 +21,7 @@ import ( "strings" ) -const APIVERSION = 1.4 +const APIVERSION = 1.5 const DEFAULTHTTPHOST = "127.0.0.1" const DEFAULTHTTPPORT = 4243 const DEFAULTUNIXSOCKET = "/var/run/docker.sock" @@ -327,8 +327,18 @@ func getContainersJSON(srv *Server, version float64, w http.ResponseWriter, r *h n = -1 } + var b []byte outs := srv.Containers(all, size, n, since, before) - b, err := json.Marshal(outs) + if version < 1.5 { + outs2 := []APIContainersOld{} + for _, ctnr := range outs { + outs2 = append(outs2, ctnr.ToLegacy()) + } + b, err = json.Marshal(outs2) + } else { + b, err = json.Marshal(outs) + } + if err != nil { return err } diff --git a/api_params.go b/api_params.go index 43a536d601..6403bc6a26 100644 --- a/api_params.go +++ b/api_params.go @@ -54,6 +54,30 @@ type APIContainers struct { SizeRootFs int64 } +func (self *APIContainers) ToLegacy() APIContainersOld { + return APIContainersOld{ + ID: self.ID, + Image: self.Image, + Command: self.Command, + Created: self.Created, + Status: self.Status, + Ports: displayablePorts(self.Ports), + SizeRw: self.SizeRw, + SizeRootFs: self.SizeRootFs, + } +} + +type APIContainersOld struct { + ID string `json:"Id"` + Image string + Command string + Created int64 + Status string + Ports string + SizeRw int64 + SizeRootFs int64 +} + type APISearch struct { Name string Description string From 62823cfb97ec35e59833fd4723c392685d55827c Mon Sep 17 00:00:00 2001 From: Deni Bertovic Date: Thu, 5 Sep 2013 00:06:41 +0200 Subject: [PATCH 087/128] fixed docs for installing binary --- docs/sources/installation/binaries.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/installation/binaries.rst b/docs/sources/installation/binaries.rst index fea48bd7a9..28663fb779 100644 --- a/docs/sources/installation/binaries.rst +++ b/docs/sources/installation/binaries.rst @@ -34,8 +34,8 @@ Get the docker binary: .. code-block:: bash - wget http://get.docker.io/builds/Linux/x86_64/docker-latest.tgz - tar -xf docker-latest.tgz + wget --output-document=docker https://get.docker.io/builds/Linux/x86_64/docker-latest + chmod +x docker Run the docker daemon From ad04cacd28da1557324b8ad6fb53b7284e8bf866 Mon Sep 17 00:00:00 2001 From: Victor Coisne Date: Wed, 4 Sep 2013 17:47:18 -0700 Subject: [PATCH 088/128] Add step to docker installation using vagrant (Mac, Linux) --- docs/sources/installation/vagrant.rst | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/docs/sources/installation/vagrant.rst b/docs/sources/installation/vagrant.rst index 14f5bf5cdd..05fb579d5e 100644 --- a/docs/sources/installation/vagrant.rst +++ b/docs/sources/installation/vagrant.rst @@ -37,7 +37,13 @@ Spin it up git clone https://github.com/dotcloud/docker.git -2. Run vagrant from the sources directory +2. Change directory to docker + + .. code-block:: bash + + cd docker + +3. Run vagrant from the sources directory .. code-block:: bash From bf6e241d977f36db57c66a5b8dd056b36c0d8695 Mon Sep 17 00:00:00 2001 From: Daniel Mizyrycki Date: Wed, 4 Sep 2013 18:20:42 -0700 Subject: [PATCH 089/128] testing, issue #1802: Temporarily skip TestGetContainersTop --- api_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/api_test.go b/api_test.go index fc7eeed713..1e8ed20b98 100644 --- a/api_test.go +++ b/api_test.go @@ -449,6 +449,7 @@ func TestGetContainersChanges(t *testing.T) { } func TestGetContainersTop(t *testing.T) { + t.Skip("Fixme. Skipping test for now. Reported error when testing using dind: 'api_test.go:527: Expected 2 processes, found 0.'") runtime, err := newTestRuntime() if err != nil { t.Fatal(err) From fdf1fccd3eec7b534b8423fe3ea5b6e116b233fa Mon Sep 17 00:00:00 2001 From: Bouke Haarsma Date: Thu, 5 Sep 2013 10:40:11 +0200 Subject: [PATCH 090/128] Fix memory recommendations (Byte, not bit) --- docs/sources/installation/vagrant.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/installation/vagrant.rst b/docs/sources/installation/vagrant.rst index 05fb579d5e..3d7faf515e 100644 --- a/docs/sources/installation/vagrant.rst +++ b/docs/sources/installation/vagrant.rst @@ -10,7 +10,7 @@ Using Vagrant (Mac, Linux) This guide will setup a new virtualbox virtual machine with docker installed on your computer. This works on most operating systems, including MacOX, Windows, Linux, FreeBSD and others. If you can -install these and have at least 400Mb RAM to spare you should be good. +install these and have at least 400MB RAM to spare you should be good. Install Vagrant and Virtualbox ------------------------------ From 82dd417ef6966b0f3d812122c18420feb64f49a7 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Thu, 5 Sep 2013 13:36:16 -0600 Subject: [PATCH 091/128] Reformat Gentoo install instructions to 80 columns --- docs/sources/installation/gentoolinux.rst | 47 ++++++++++++++++++----- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/docs/sources/installation/gentoolinux.rst b/docs/sources/installation/gentoolinux.rst index aa809768ee..5749b6dc0b 100644 --- a/docs/sources/installation/gentoolinux.rst +++ b/docs/sources/installation/gentoolinux.rst @@ -11,7 +11,10 @@ Gentoo Linux .. include:: install_unofficial.inc -Installing Docker on Gentoo Linux can be accomplished by using the overlay provided at https://github.com/tianon/docker-overlay. The most up-to-date documentation for properly installing the overlay can be found in the overlay README. The information here is provided for reference, and may be out of date. +Installing Docker on Gentoo Linux can be accomplished by using the overlay +provided at https://github.com/tianon/docker-overlay. The most up-to-date +documentation for properly installing the overlay can be found in the overlay +README. The information here is provided for reference, and may be out of date. Installation ^^^^^^^^^^^^ @@ -22,30 +25,49 @@ Ensure that layman is installed: sudo emerge -av app-portage/layman -Using your favorite editor, add ``https://raw.github.com/tianon/docker-overlay/master/repositories.xml`` to the ``overlays`` section in ``/etc/layman/layman.cfg`` (as per instructions on the `Gentoo Wiki `_), then invoke the following: +Using your favorite editor, add +``https://raw.github.com/tianon/docker-overlay/master/repositories.xml`` to the +``overlays`` section in ``/etc/layman/layman.cfg`` (as per instructions on the +`Gentoo Wiki `_), +then invoke the following: .. code-block:: bash sudo layman -f -a docker -Once that completes, the ``app-emulation/lxc-docker`` package will be available for emerge: +Once that completes, the ``app-emulation/lxc-docker`` package will be available +for emerge: .. code-block:: bash sudo emerge -av app-emulation/lxc-docker -If you prefer to use the official binaries, or just do not wish to compile docker, emerge ``app-emulation/lxc-docker-bin`` instead. It is important to remember that Gentoo is still an unsupported platform, even when using the official binaries. +If you prefer to use the official binaries, or just do not wish to compile +docker, emerge ``app-emulation/lxc-docker-bin`` instead. It is important to +remember that Gentoo is still an unsupported platform, even when using the +official binaries. -The package should already include all the necessary dependencies. For the simplest installation experience, use ``sys-kernel/aufs-sources`` directly as your kernel sources. If you prefer not to use ``sys-kernel/aufs-sources``, the portage tree also contains ``sys-fs/aufs3``, which contains the patches necessary for adding AUFS support to other kernel source packages (and a ``kernel-patch`` use flag to perform the patching automatically). +The package should already include all the necessary dependencies. For the +simplest installation experience, use ``sys-kernel/aufs-sources`` directly as +your kernel sources. If you prefer not to use ``sys-kernel/aufs-sources``, the +portage tree also contains ``sys-fs/aufs3``, which contains the patches +necessary for adding AUFS support to other kernel source packages (and a +``kernel-patch`` use flag to perform the patching automatically). -Between ``app-emulation/lxc`` and ``app-emulation/lxc-docker``, all the necessary kernel configuration flags should be checked for and warned about in the standard manner. +Between ``app-emulation/lxc`` and ``app-emulation/lxc-docker``, all the +necessary kernel configuration flags should be checked for and warned about in +the standard manner. -If any issues arise from this ebuild or the resulting binary, including and especially missing kernel configuration flags and/or dependencies, `open an issue `_ on the docker-overlay repository or ping tianon in the #docker IRC channel. +If any issues arise from this ebuild or the resulting binary, including and +especially missing kernel configuration flags and/or dependencies, `open an +issue `_ on the docker-overlay +repository or ping tianon in the #docker IRC channel. Starting Docker ^^^^^^^^^^^^^^^ -Ensure that you are running a kernel that includes the necessary AUFS support and includes all the necessary modules and/or configuration for LXC. +Ensure that you are running a kernel that includes the necessary AUFS support +and includes all the necessary modules and/or configuration for LXC. OpenRC ------ @@ -80,7 +102,8 @@ To start on system boot: Network Configuration ^^^^^^^^^^^^^^^^^^^^^ -IPv4 packet forwarding is disabled by default, so internet access from inside the container will not work unless ``net.ipv4.ip_forward`` is enabled: +IPv4 packet forwarding is disabled by default, so internet access from inside +the container will not work unless ``net.ipv4.ip_forward`` is enabled: .. code-block:: bash @@ -95,4 +118,8 @@ Or, to enable it more permanently: fork/exec /usr/sbin/lxc-start: operation not permitted ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Unfortunately, Gentoo suffers from `issue #1422 `_, meaning that after every fresh start of docker, the first docker run fails due to some tricky terminal issues, so be sure to run something trivial (such as ``docker run -i -t busybox echo hi``) before attempting to run anything important. +Unfortunately, Gentoo suffers from `issue #1422 +`_, meaning that after every +fresh start of docker, the first docker run fails due to some tricky terminal +issues, so be sure to run something trivial (such as ``docker run -i -t busybox +echo hi``) before attempting to run anything important. From d368c2dee99b35830392938a783137f344e5b89a Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Thu, 5 Sep 2013 13:36:56 -0600 Subject: [PATCH 092/128] Update Gentoo docs to reflect the package name change from app-emulation/lxc-docker to app-emulation/docker as discussed in today's #docker-meeting --- docs/sources/installation/gentoolinux.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/sources/installation/gentoolinux.rst b/docs/sources/installation/gentoolinux.rst index 5749b6dc0b..6e41e049d1 100644 --- a/docs/sources/installation/gentoolinux.rst +++ b/docs/sources/installation/gentoolinux.rst @@ -35,15 +35,15 @@ then invoke the following: sudo layman -f -a docker -Once that completes, the ``app-emulation/lxc-docker`` package will be available +Once that completes, the ``app-emulation/docker`` package will be available for emerge: .. code-block:: bash - sudo emerge -av app-emulation/lxc-docker + sudo emerge -av app-emulation/docker If you prefer to use the official binaries, or just do not wish to compile -docker, emerge ``app-emulation/lxc-docker-bin`` instead. It is important to +docker, emerge ``app-emulation/docker-bin`` instead. It is important to remember that Gentoo is still an unsupported platform, even when using the official binaries. @@ -54,7 +54,7 @@ portage tree also contains ``sys-fs/aufs3``, which contains the patches necessary for adding AUFS support to other kernel source packages (and a ``kernel-patch`` use flag to perform the patching automatically). -Between ``app-emulation/lxc`` and ``app-emulation/lxc-docker``, all the +Between ``app-emulation/lxc`` and ``app-emulation/docker``, all the necessary kernel configuration flags should be checked for and warned about in the standard manner. From 5ec2fea6dd3360de326717ed01909b8fe9c39d0f Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 5 Sep 2013 22:28:51 +0000 Subject: [PATCH 093/128] Detect images/containers conflicts in docker inspect --- api.go | 10 ++++++++++ commands.go | 2 +- docs/sources/api/docker_remote_api_v1.4.rst | 2 ++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/api.go b/api.go index 9e094231b5..83f43cc741 100644 --- a/api.go +++ b/api.go @@ -819,6 +819,11 @@ func getContainersByName(srv *Server, version float64, w http.ResponseWriter, r if err != nil { return err } + + _, err = srv.ImageInspect(name) + if err == nil { + return fmt.Errorf("Conflict between containers and images") + } b, err := json.Marshal(container) if err != nil { return err @@ -837,6 +842,11 @@ func getImagesByName(srv *Server, version float64, w http.ResponseWriter, r *htt if err != nil { return err } + + _, err = srv.ContainerInspect(name) + if err == nil { + return fmt.Errorf("Conflict between containers and images") + } b, err := json.Marshal(image) if err != nil { return err diff --git a/commands.go b/commands.go index b2a4d3db13..656790624e 100644 --- a/commands.go +++ b/commands.go @@ -577,7 +577,7 @@ func (cli *DockerCli) CmdInspect(args ...string) error { if err != nil { obj, _, err = cli.call("GET", "/images/"+name+"/json", nil) if err != nil { - fmt.Fprintf(cli.err, "%s\n", err) + fmt.Fprintf(cli.err, "No such image or container: %s\n", name) continue } } diff --git a/docs/sources/api/docker_remote_api_v1.4.rst b/docs/sources/api/docker_remote_api_v1.4.rst index 3ab5cdee0a..6f71fe953f 100644 --- a/docs/sources/api/docker_remote_api_v1.4.rst +++ b/docs/sources/api/docker_remote_api_v1.4.rst @@ -224,6 +224,7 @@ Inspect a container :statuscode 200: no error :statuscode 404: no such container + :statuscode 409: conflict between containers and images :statuscode 500: server error @@ -760,6 +761,7 @@ Inspect an image :statuscode 200: no error :statuscode 404: no such image + :statuscode 409: conflict between containers and images :statuscode 500: server error From 20a763e51934f3b52e48b5a6ba37d63852fd9a0b Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 5 Sep 2013 22:33:04 +0000 Subject: [PATCH 094/128] fix indent in doc --- docs/sources/api/docker_remote_api_v1.4.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/api/docker_remote_api_v1.4.rst b/docs/sources/api/docker_remote_api_v1.4.rst index 6f71fe953f..4f946e1e25 100644 --- a/docs/sources/api/docker_remote_api_v1.4.rst +++ b/docs/sources/api/docker_remote_api_v1.4.rst @@ -762,7 +762,7 @@ Inspect an image :statuscode 200: no error :statuscode 404: no such image :statuscode 409: conflict between containers and images - :statuscode 500: server error + :statuscode 500: server error Get the history of an image From 0d7044955a4b63c4df2a611095b87ae417a3cf9b Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 5 Sep 2013 23:00:52 +0000 Subject: [PATCH 095/128] remove unused getCache endpoint --- api.go | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/api.go b/api.go index 9e094231b5..4c568d4bc7 100644 --- a/api.go +++ b/api.go @@ -845,29 +845,6 @@ func getImagesByName(srv *Server, version float64, w http.ResponseWriter, r *htt return nil } -func postImagesGetCache(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { - apiConfig := &APIImageConfig{} - if err := json.NewDecoder(r.Body).Decode(apiConfig); err != nil { - return err - } - - image, err := srv.ImageGetCached(apiConfig.ID, apiConfig.Config) - if err != nil { - return err - } - if image == nil { - w.WriteHeader(http.StatusNotFound) - return nil - } - apiID := &APIID{ID: image.ID} - b, err := json.Marshal(apiID) - if err != nil { - return err - } - writeJSON(w, b) - return nil -} - func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error { if version < 1.3 { return fmt.Errorf("Multipart upload for build is no longer supported. Please upgrade your docker client.") @@ -1043,7 +1020,6 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) { "/images/{name:.*}/insert": postImagesInsert, "/images/{name:.*}/push": postImagesPush, "/images/{name:.*}/tag": postImagesTag, - "/images/getCache": postImagesGetCache, "/containers/create": postContainersCreate, "/containers/{name:.*}/kill": postContainersKill, "/containers/{name:.*}/restart": postContainersRestart, From 3bc73fa21e4428cd7040df6c5a384bd0e4772236 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 5 Sep 2013 23:54:03 +0000 Subject: [PATCH 096/128] Return the process exit code for run commands --- commands.go | 30 +++++++++++++++++++++++------- 1 file changed, 23 insertions(+), 7 deletions(-) diff --git a/commands.go b/commands.go index b2a4d3db13..7512577a2c 100644 --- a/commands.go +++ b/commands.go @@ -367,16 +367,11 @@ func (cli *DockerCli) CmdWait(args ...string) error { return nil } for _, name := range cmd.Args() { - body, _, err := cli.call("POST", "/containers/"+name+"/wait", nil) + status, err := waitForExit(cli, name) if err != nil { fmt.Fprintf(cli.err, "%s", err) } else { - var out APIWait - err = json.Unmarshal(body, &out) - if err != nil { - return err - } - fmt.Fprintf(cli.out, "%d\n", out.StatusCode) + fmt.Fprintf(cli.out, "%d\n", status) } } return nil @@ -1477,8 +1472,16 @@ func (cli *DockerCli) CmdRun(args ...string) error { } if !config.AttachStdout && !config.AttachStderr { + // Detached mode <-wait + } else { + status, err := waitForExit(cli, runResult.ID) + if err != nil { + return err + } + os.Exit(status) } + return nil } @@ -1759,6 +1762,19 @@ func (cli *DockerCli) LoadConfigFile() (err error) { return err } +func waitForExit(cli *DockerCli, containerId string) (int, error) { + body, _, err := cli.call("POST", "/containers/"+containerId+"/wait", nil) + if err != nil { + return -1, err + } + + var out APIWait + if err := json.Unmarshal(body, &out); err != nil { + return -1, err + } + return out.StatusCode, nil +} + func NewDockerCli(in io.ReadCloser, out, err io.Writer, proto, addr string) *DockerCli { var ( isTerminal = false From 8374dac02141c1409b93bb3838f6dabbba4d5b14 Mon Sep 17 00:00:00 2001 From: Martin Redmond Date: Thu, 5 Sep 2013 08:28:02 -0400 Subject: [PATCH 097/128] From FIXME: local path for ADD --- buildfile.go | 2 +- buildfile_test.go | 48 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/buildfile.go b/buildfile.go index 4c8db2c60e..579699f067 100644 --- a/buildfile.go +++ b/buildfile.go @@ -293,7 +293,7 @@ func (b *buildFile) addContext(container *Container, orig, dest string) error { } fi, err := os.Stat(origPath) if err != nil { - return err + return fmt.Errorf("%s: no such file or directory", orig) } if fi.IsDir() { if err := CopyWithTar(origPath, destPath); err != nil { diff --git a/buildfile_test.go b/buildfile_test.go index 14986161d8..a204240d3a 100644 --- a/buildfile_test.go +++ b/buildfile_test.go @@ -483,3 +483,51 @@ func TestForbiddenContextPath(t *testing.T) { t.Fail() } } + +func TestBuildADDFileNotFound(t *testing.T) { + runtime, err := newTestRuntime() + if err != nil { + t.Fatal(err) + } + defer nuke(runtime) + + srv := &Server{ + runtime: runtime, + pullingPool: make(map[string]struct{}), + pushingPool: make(map[string]struct{}), + } + + context := testContextTemplate{` + from {IMAGE} + add foo /usr/local/bar + `, + nil, nil} + + httpServer, err := mkTestingFileServer(context.remoteFiles) + if err != nil { + t.Fatal(err) + } + defer httpServer.Close() + + idx := strings.LastIndex(httpServer.URL, ":") + if idx < 0 { + t.Fatalf("could not get port from test http server address %s", httpServer.URL) + } + port := httpServer.URL[idx+1:] + + ip := srv.runtime.networkManager.bridgeNetwork.IP + dockerfile := constructDockerfile(context.dockerfile, ip, port) + + buildfile := NewBuildFile(srv, ioutil.Discard, false, true) + _, err = buildfile.Build(mkTestContext(dockerfile, context.files, t)) + + if err == nil { + t.Log("Error should not be nil") + t.Fail() + } + + if err.Error() != "foo: no such file or directory" { + t.Logf("Error message is not expected: %s", err.Error()) + t.Fail() + } +} From 35bcba80116668864380319d9b6fe0249cefd114 Mon Sep 17 00:00:00 2001 From: Martin Redmond Date: Fri, 6 Sep 2013 15:51:49 -0400 Subject: [PATCH 098/128] improve image listing --- server.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/server.go b/server.go index 69edabf07a..6db1c63928 100644 --- a/server.go +++ b/server.go @@ -210,8 +210,10 @@ func (srv *Server) Images(all bool, filter string) ([]APIImages, error) { } outs := []APIImages{} //produce [] when empty instead of 'null' for name, repository := range srv.runtime.repositories.Repositories { - if filter != "" && name != filter { - continue + if filter != "" { + if match, _ := path.Match ( filter, name ); !match { + continue + } } for tag, id := range repository { var out APIImages From b44d11312054d1d7bc0f0bbc8eeaddc2d30c26cc Mon Sep 17 00:00:00 2001 From: Martin Redmond Date: Fri, 6 Sep 2013 16:16:10 -0400 Subject: [PATCH 099/128] filter image listing using path.Match --- server.go | 4 ++-- server_test.go | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/server.go b/server.go index 6db1c63928..a48d590653 100644 --- a/server.go +++ b/server.go @@ -211,7 +211,7 @@ func (srv *Server) Images(all bool, filter string) ([]APIImages, error) { outs := []APIImages{} //produce [] when empty instead of 'null' for name, repository := range srv.runtime.repositories.Repositories { if filter != "" { - if match, _ := path.Match ( filter, name ); !match { + if match, _ := path.Match(filter, name); !match { continue } } @@ -681,7 +681,7 @@ func (srv *Server) getImageList(localRepo map[string]string) ([][]*registry.ImgD depGraph.NewNode(img.ID) img.WalkHistory(func(current *Image) error { imgList[current.ID] = ®istry.ImgData{ - ID: current.ID, + ID: current.ID, Tag: tag, } parent, err := current.GetParent() diff --git a/server_test.go b/server_test.go index 95ebcf2459..ad87b4d0a3 100644 --- a/server_test.go +++ b/server_test.go @@ -431,3 +431,57 @@ func TestRmi(t *testing.T) { } } } + +func TestImagesFilter(t *testing.T) { + runtime := mkRuntime(t) + defer nuke(runtime) + + srv := &Server{runtime: runtime} + + if err := srv.runtime.repositories.Set("utest", "tag1", unitTestImageName, false); err != nil { + t.Fatal(err) + } + + if err := srv.runtime.repositories.Set("utest/docker", "tag2", unitTestImageName, false); err != nil { + t.Fatal(err) + } + if err := srv.runtime.repositories.Set("utest:5000/docker", "tag3", unitTestImageName, false); err != nil { + t.Fatal(err) + } + + images, err := srv.Images(false, "utest*/*") + if err != nil { + t.Fatal(err) + } + + if len(images) != 2 { + t.Fatal("incorrect number of matches returned") + } + + images, err = srv.Images(false, "utest") + if err != nil { + t.Fatal(err) + } + + if len(images) != 1 { + t.Fatal("incorrect number of matches returned") + } + + images, err = srv.Images(false, "utest*") + if err != nil { + t.Fatal(err) + } + + if len(images) != 1 { + t.Fatal("incorrect number of matches returned") + } + + images, err = srv.Images(false, "*5000*/*") + if err != nil { + t.Fatal(err) + } + + if len(images) != 1 { + t.Fatal("incorrect number of matches returned") + } +} From 03a28da5f78bd0f20eca3390a2dddca4ccb6ca81 Mon Sep 17 00:00:00 2001 From: Andy Rothfusz Date: Fri, 6 Sep 2013 13:34:24 -0700 Subject: [PATCH 100/128] Add Swiftype search to docs --- docs/theme/docker/layout.html | 27 +++++++++++++++++++++++++++ docs/theme/docker/static/css/main.css | 18 ++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/docs/theme/docker/layout.html b/docs/theme/docker/layout.html index 9b3f565637..91c561235c 100755 --- a/docs/theme/docker/layout.html +++ b/docs/theme/docker/layout.html @@ -110,6 +110,13 @@ @@ -121,6 +128,26 @@ {% block body %}{% endblock %} + +
+ + diff --git a/docs/theme/docker/static/css/main.css b/docs/theme/docker/static/css/main.css index fc1fd4886d..43ec37cfcd 100755 --- a/docs/theme/docker/static/css/main.css +++ b/docs/theme/docker/static/css/main.css @@ -391,3 +391,21 @@ dt:hover > a.headerlink { float: right; visibility: hidden; } + +/* Swiftype style */ + +#st-search-input { + margin-right: 14px; + margin-left: 9px; + height: 19px; + width: 120px; + +} +#swiftype-img { + border: none; + width: 145px; + height: auto; + margin: 0px auto; + margin-left: 13px; + margin-top: -30px; +} \ No newline at end of file From b9ca311008a14910877c6283b1dc7fb4a14bef4c Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 6 Sep 2013 14:48:06 -0700 Subject: [PATCH 101/128] Update roadmap --- hack/ROADMAP.md | 96 ++++++++++--------------------------------------- 1 file changed, 18 insertions(+), 78 deletions(-) diff --git a/hack/ROADMAP.md b/hack/ROADMAP.md index f335db6953..e7dbf80a8a 100644 --- a/hack/ROADMAP.md +++ b/hack/ROADMAP.md @@ -7,99 +7,39 @@ For a more complete view of planned and requested improvements, see [the Github Tu suggest changes to the roadmap, including additions, please write the change as if it were already in effect, and make a pull request. -Broader kernel support ----------------------- -Our goal is to make Docker run everywhere, but currently Docker requires [Linux version 3.8 or higher with lxc and aufs support](http://docs.docker.io/en/latest/installation/kernel.html). If you're deploying new machines for the purpose of running Docker, this is a fairly easy requirement to meet. -However, if you're adding Docker to an existing deployment, you may not have the flexibility to update and patch the kernel. +## Container wiring and service discovery -Expanding Docker's kernel support is a priority. This includes running on older kernel versions, -but also on kernels with no AUFS support, or with incomplete lxc capabilities. +In its current version, docker doesn’t make it very easy to manipulate multiple containers as a cohesive group (ie. orchestration), and it doesn’t make it seamless for containers to connect to each other as network services (ie. wiring). + +To achieve wiring and orchestration with docker today, you need to write glue scripts yourself, or use one several companion tools available, like Orchestra, Shipper, Deis, Pipeworks, etc. + +We want the Docker API to support orchestration and wiring natively, so that these tools can cleanly and seamlessly integrate into the Docker user experience, and remain interoperable with each other. -Cross-architecture support --------------------------- +## Better integration with process supervisors -Our goal is to make Docker run everywhere. However currently Docker only runs on x86_64 systems. -We plan on expanding architecture support, so that Docker containers can be created and used on more architectures. +For docker to be fully usable in production, it needs to cleanly integrate with the host machine’s process supervisor of choice. Whether it’s sysV-init, upstart, systemd, runit or supervisord, we want to make sure docker plays nice with your existing system. This will be a major focus of the 0.7 release. -Even more integrations ----------------------- +## Plugin API -We want Docker to be the secret ingredient that makes your existing tools more awesome. -Thanks to this philosophy, Docker has already been integrated with -[Puppet](http://forge.puppetlabs.com/garethr/docker), [Chef](http://www.opscode.com/chef), -[Openstack Nova](https://github.com/dotcloud/openstack-docker), [Jenkins](https://github.com/georgebashi/jenkins-docker-plugin), -[DotCloud sandbox](http://github.com/dotcloud/sandbox), [Pallet](https://github.com/pallet/pallet-docker), -[Strider CI](http://blog.frozenridge.co/next-generation-continuous-integration-deployment-with-dotclouds-docker-and-strider/) -and even [Heroku buildpacks](https://github.com/progrium/buildstep). +We want Docker to run everywhere, and to integrate with every devops tool. Those are ambitious goals, and the only way to reach them is with the Docker community. For the community to participate fully, we need an API which allows Docker to be deeply and easily customized. -Expect Docker to integrate with even more of your favorite tools going forward, including: - -* Alternative storage backends such as ZFS, LVM or [BTRFS](github.com/dotcloud/docker/issues/443) -* Alternative containerization backends such as [OpenVZ](http://openvz.org), Solaris Zones, BSD Jails and even plain Chroot. -* Process managers like [Supervisord](http://supervisord.org/), [Runit](http://smarden.org/runit/), [Gaffer](https://gaffer.readthedocs.org/en/latest/#gaffer) and [Systemd](http://www.freedesktop.org/wiki/Software/systemd/) -* Build and integration tools like Make, Maven, Scons, Jenkins, Buildbot and Cruise Control. -* Configuration management tools like [Puppet](http://puppetlabs.com), [Chef](http://www.opscode.com/chef/) and [Salt](http://saltstack.org) -* Personal development environments like [Vagrant](http://vagrantup.com), [Boxen](http://boxen.github.com/), [Koding](http://koding.com) and [Cloud9](http://c9.io). -* Orchestration tools like [Zookeeper](http://zookeeper.apache.org/), [Mesos](http://incubator.apache.org/mesos/) and [Galaxy](https://github.com/ning/galaxy) -* Infrastructure deployment tools like [Openstack](http://openstack.org), [Apache Cloudstack](http://apache.cloudstack.org), [Ganeti](https://code.google.com/p/ganeti/) +We are working on a plugin API which will make Docker very, very customization-friendly. We believe it will facilitate the integrations listed above – and many more we didn’t even think about. -Plugin API ----------- +## Broader kernel support -We want Docker to run everywhere, and to integrate with every devops tool. -Those are ambitious goals, and the only way to reach them is with the Docker community. -For the community to participate fully, we need an API which allows Docker to be deeply and easily customized. +Our goal is to make Docker run everywhere, but currently Docker requires Linux version 3.8 or higher with lxc and aufs support. If you’re deploying new machines for the purpose of running Docker, this is a fairly easy requirement to meet. However, if you’re adding Docker to an existing deployment, you may not have the flexibility to update and patch the kernel. -We are working on a plugin API which will make Docker very, very customization-friendly. -We believe it will facilitate the integrations listed above - and many more we didn't even think about. - -Let us know if you want to start playing with the API before it's generally available. +Expanding Docker’s kernel support is a priority. This includes running on older kernel versions, but also on kernels with no AUFS support, or with incomplete lxc capabilities. -Externally mounted volumes --------------------------- +## Cross-architecture support -In 0.3 we [introduced data volumes](https://github.com/dotcloud/docker/wiki/Docker-0.3.0-release-note%2C-May-6-2013#data-volumes), -a great mechanism for manipulating persistent data such as database files, log files, etc. +Our goal is to make Docker run everywhere. However currently Docker only runs on x86_64 systems. We plan on expanding architecture support, so that Docker containers can be created and used on more architectures. -Data volumes can be shared between containers, a powerful capability [which allows many advanced use cases](http://docs.docker.io/en/latest/examples/couchdb_data_volumes.html). In the future it will also be possible to share volumes between a container and the underlying host. This will make certain scenarios much easier, such as using a high-performance storage backend for your production database, -making live development changes available to a container, etc. +## Production-ready - -Better documentation --------------------- - -We believe that great documentation is worth 10 features. We are often told that "Docker's documentation is great for a 2-month old project". -Our goal is to make it great, period. - -If you have feedback on how to improve our documentation, please get in touch by replying to this email, -or by [filing an issue](https://github.com/dotcloud/docker/issues). We always appreciate it! - - -Production-ready ----------------- - -Docker is still alpha software, and not suited for production. -We are working hard to get there, and we are confident that it will be possible within a few months. - - -Advanced port redirections --------------------------- - -Docker currently supports 2 flavors of port redirection: STATIC->STATIC (eg. "redirect public port 80 to private port 80") -and RANDOM->STATIC (eg. "redirect any public port to private port 80"). - -With these 2 flavors, docker can support the majority of backend programs out there. But some applications have more exotic -requirements, generally to implement custom clustering techniques. These applications include Hadoop, MongoDB, Riak, RabbitMQ, -Disco, and all programs relying on Erlang's OTP. - -To support these applications, Docker needs to support more advanced redirection flavors, including: - -* RANDOM->RANDOM -* STATIC1->STATIC2 - -These flavors should be implemented without breaking existing semantics, if at all possible. +Docker is still beta software, and not suited for production. We are working hard to get there, and we are confident that it will be possible within a few months. Stay tuned for a more detailed roadmap soon. From bcb8d3fd86ac620dc4f35c805b4e0f8e8fcb7663 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 6 Sep 2013 14:52:14 -0700 Subject: [PATCH 102/128] README: refer to the docs for install instructions to remove duplication. --- README.md | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 336dd94302..cbe5dfee2a 100644 --- a/README.md +++ b/README.md @@ -143,25 +143,11 @@ as they can be built by running a Unix command in a container. Install instructions ================== -Quick install on Ubuntu 12.04 and 12.10 ---------------------------------------- +Docker can be installed on your local machine as well as servers - both bare metal and virtualized. +It is available as a binary on most modern Linux systems, or as a VM on Windows, Mac and other systems. -```bash -curl https://get.docker.io | sudo sh -x -``` +For the most up-to-date install instructions, see the [install page on the documentation](http://docs.docker.io/en/latest/installation/). -Binary installs ----------------- - -Docker supports the following binary installation methods. Note that -some methods are community contributions and not yet officially -supported. - -* [Ubuntu 12.04 and 12.10 (officially supported)](http://docs.docker.io/en/latest/installation/ubuntulinux/) -* [Arch Linux](http://docs.docker.io/en/latest/installation/archlinux/) -* [Mac OS X (with Vagrant)](http://docs.docker.io/en/latest/installation/vagrant/) -* [Windows (with Vagrant)](http://docs.docker.io/en/latest/installation/windows/) -* [Amazon EC2 (with Vagrant)](http://docs.docker.io/en/latest/installation/amazon/) Usage examples ============== From 70ab25a5dbc8f54dbe15e9375687b4669ed919df Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 6 Sep 2013 15:08:01 -0700 Subject: [PATCH 103/128] README: harmonize intro with website --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cbe5dfee2a..7b19f95ee9 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ Docker: the Linux container engine ================================== -Docker is an open-source engine which automates the deployment of -applications as highly portable, self-sufficient containers. +Docker is an open source project to pack, ship and run any application +as a lightweight container Docker containers are both *hardware-agnostic* and *platform-agnostic*. This means that they can run anywhere, from your From 6679f3b053344e6e99d5e490720c9ccb924a7d4c Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 6 Sep 2013 15:09:04 -0700 Subject: [PATCH 104/128] README: replace 'install instructions' and 'usage examples' with references to the website and docs --- README.md | 104 +++++------------------------------------------------- 1 file changed, 9 insertions(+), 95 deletions(-) diff --git a/README.md b/README.md index 7b19f95ee9..19e7013441 100644 --- a/README.md +++ b/README.md @@ -140,111 +140,25 @@ Note that Docker doesn't care *how* dependencies are built - as long as they can be built by running a Unix command in a container. -Install instructions -================== +Getting started +=============== Docker can be installed on your local machine as well as servers - both bare metal and virtualized. It is available as a binary on most modern Linux systems, or as a VM on Windows, Mac and other systems. -For the most up-to-date install instructions, see the [install page on the documentation](http://docs.docker.io/en/latest/installation/). +We also offer an interactive tutorial for quickly learning the basics of using Docker. + + +For up-to-date install instructions and online tutorials, see the [Getting Started page](http://www.docker.io/gettingstarted/). Usage examples ============== -First run the ``docker`` daemon -------------------------------- +Docker can be used to run short-lived commands, long-running daemons (app servers, databases etc.), +interactive shell sessions, etc. -All the examples assume your machine is running the ``docker`` -daemon. To run the ``docker`` daemon in the background, simply type: - -```bash -# On a production system you want this running in an init script -sudo docker -d & -``` - -Now you can run ``docker`` in client mode: all commands will be -forwarded to the ``docker`` daemon, so the client can run from any -account. - -```bash -# Now you can run docker commands from any account. -docker help -``` - - -Throwaway shell in a base Ubuntu image --------------------------------------- - -```bash -docker pull ubuntu:12.10 - -# Run an interactive shell, allocate a tty, attach stdin and stdout -# To detach the tty without exiting the shell, use the escape sequence Ctrl-p + Ctrl-q -docker run -i -t ubuntu:12.10 /bin/bash -``` - -Starting a long-running worker process --------------------------------------- - -```bash -# Start a very useful long-running process -JOB=$(docker run -d ubuntu /bin/sh -c "while true; do echo Hello world; sleep 1; done") - -# Collect the output of the job so far -docker logs $JOB - -# Kill the job -docker kill $JOB -``` - -Running an irc bouncer ----------------------- - -```bash -BOUNCER_ID=$(docker run -d -p 6667 -u irc shykes/znc zncrun $USER $PASSWORD) -echo "Configure your irc client to connect to port $(docker port $BOUNCER_ID 6667) of this machine" -``` - -Running Redis -------------- - -```bash -REDIS_ID=$(docker run -d -p 6379 shykes/redis redis-server) -echo "Configure your redis client to connect to port $(docker port $REDIS_ID 6379) of this machine" -``` - -Share your own image! ---------------------- - -```bash -CONTAINER=$(docker run -d ubuntu:12.10 apt-get install -y curl) -docker commit -m "Installed curl" $CONTAINER $USER/betterbase -docker push $USER/betterbase -``` - -A list of publicly available images is [available -here](https://github.com/dotcloud/docker/wiki/Public-docker-images). - -Expose a service on a TCP port ------------------------------- - -```bash -# Expose port 4444 of this container, and tell netcat to listen on it -JOB=$(docker run -d -p 4444 base /bin/nc -l -p 4444) - -# Which public port is NATed to my container? -PORT=$(docker port $JOB 4444) - -# Connect to the public port via the host's public address -# Please note that because of how routing works connecting to localhost or 127.0.0.1 $PORT will not work. -# Replace *eth0* according to your local interface name. -IP=$(ip -o -4 addr list eth0 | perl -n -e 'if (m{inet\s([\d\.]+)\/\d+\s}xms) { print $1 }') -echo hello world | nc $IP $PORT - -# Verify that the network connection worked -echo "Daemon received: $(docker logs $JOB)" -``` +You can find a [list of real-world examples](http://docs.docker.io/en/latest/examples/) in the documentation. Under the hood -------------- From eed00a4afd1e8e8e35f8ca640c94d9c9e9babaf7 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 6 Sep 2013 15:09:40 -0700 Subject: [PATCH 105/128] README: remove original shipping containers 'manifesto'. It's a little long to stay here. --- README.md | 136 ------------------------------------------------------ 1 file changed, 136 deletions(-) diff --git a/README.md b/README.md index 19e7013441..a36bfba73f 100644 --- a/README.md +++ b/README.md @@ -188,142 +188,6 @@ They are probably not perfect, please let us know if anything feels wrong or incomplete. -Note ----- - -We also keep the documentation in this repository. The website -documentation is generated using Sphinx using these sources. Please -find it under docs/sources/ and read more about it -https://github.com/dotcloud/docker/tree/master/docs/README.md - -Please feel free to fix / update the documentation and send us pull -requests. More tutorials are also welcome. - - -Setting up a dev environment ----------------------------- - -Instructions that have been verified to work on Ubuntu 12.10, - -```bash -sudo apt-get -y install lxc curl xz-utils golang git mercurial - -export GOPATH=~/go/ -export PATH=$GOPATH/bin:$PATH - -mkdir -p $GOPATH/src/github.com/dotcloud -cd $GOPATH/src/github.com/dotcloud -git clone https://github.com/dotcloud/docker.git -cd docker - -go get -v github.com/dotcloud/docker/... -go install -v github.com/dotcloud/docker/... -``` - -Then run the docker daemon, - -```bash -sudo $GOPATH/bin/docker -d -``` - -Run the `go install` command (above) to recompile docker. - - -What is a Standard Container? -============================= - -Docker defines a unit of software delivery called a Standard -Container. The goal of a Standard Container is to encapsulate a -software component and all its dependencies in a format that is -self-describing and portable, so that any compliant runtime can run it -without extra dependencies, regardless of the underlying machine and -the contents of the container. - -The spec for Standard Containers is currently a work in progress, but -it is very straightforward. It mostly defines 1) an image format, 2) a -set of standard operations, and 3) an execution environment. - -A great analogy for this is the shipping container. Just like how -Standard Containers are a fundamental unit of software delivery, -shipping containers are a fundamental unit of physical delivery. - -### 1. STANDARD OPERATIONS - -Just like shipping containers, Standard Containers define a set of -STANDARD OPERATIONS. Shipping containers can be lifted, stacked, -locked, loaded, unloaded and labelled. Similarly, Standard Containers -can be started, stopped, copied, snapshotted, downloaded, uploaded and -tagged. - - -### 2. CONTENT-AGNOSTIC - -Just like shipping containers, Standard Containers are -CONTENT-AGNOSTIC: all standard operations have the same effect -regardless of the contents. A shipping container will be stacked in -exactly the same way whether it contains Vietnamese powder coffee or -spare Maserati parts. Similarly, Standard Containers are started or -uploaded in the same way whether they contain a postgres database, a -php application with its dependencies and application server, or Java -build artifacts. - - -### 3. INFRASTRUCTURE-AGNOSTIC - -Both types of containers are INFRASTRUCTURE-AGNOSTIC: they can be -transported to thousands of facilities around the world, and -manipulated by a wide variety of equipment. A shipping container can -be packed in a factory in Ukraine, transported by truck to the nearest -routing center, stacked onto a train, loaded into a German boat by an -Australian-built crane, stored in a warehouse at a US facility, -etc. Similarly, a standard container can be bundled on my laptop, -uploaded to S3, downloaded, run and snapshotted by a build server at -Equinix in Virginia, uploaded to 10 staging servers in a home-made -Openstack cluster, then sent to 30 production instances across 3 EC2 -regions. - - -### 4. DESIGNED FOR AUTOMATION - -Because they offer the same standard operations regardless of content -and infrastructure, Standard Containers, just like their physical -counterparts, are extremely well-suited for automation. In fact, you -could say automation is their secret weapon. - -Many things that once required time-consuming and error-prone human -effort can now be programmed. Before shipping containers, a bag of -powder coffee was hauled, dragged, dropped, rolled and stacked by 10 -different people in 10 different locations by the time it reached its -destination. 1 out of 50 disappeared. 1 out of 20 was damaged. The -process was slow, inefficient and cost a fortune - and was entirely -different depending on the facility and the type of goods. - -Similarly, before Standard Containers, by the time a software -component ran in production, it had been individually built, -configured, bundled, documented, patched, vendored, templated, tweaked -and instrumented by 10 different people on 10 different -computers. Builds failed, libraries conflicted, mirrors crashed, -post-it notes were lost, logs were misplaced, cluster updates were -half-broken. The process was slow, inefficient and cost a fortune - -and was entirely different depending on the language and -infrastructure provider. - - -### 5. INDUSTRIAL-GRADE DELIVERY - -There are 17 million shipping containers in existence, packed with -every physical good imaginable. Every single one of them can be loaded -onto the same boats, by the same cranes, in the same facilities, and -sent anywhere in the World with incredible efficiency. It is -embarrassing to think that a 30 ton shipment of coffee can safely -travel half-way across the World in *less time* than it takes a -software team to deliver its code from one datacenter to another -sitting 10 miles away. - -With Standard Containers we can put an end to that embarrassment, by -making INDUSTRIAL-GRADE DELIVERY of software a reality. - - ### Legal Transfers of Docker shall be in accordance with applicable export From b07314e2e066a1308040e1eb45a96a0e1056f28a Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Fri, 6 Sep 2013 23:00:21 +0000 Subject: [PATCH 106/128] Remove import os/user --- utils/utils.go | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/utils/utils.go b/utils/utils.go index b761da0fbd..aa34abdddc 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -14,7 +14,6 @@ import ( "net/http" "os" "os/exec" - "os/user" "path/filepath" "runtime" "strconv" @@ -802,7 +801,7 @@ func StripComments(input []byte, commentMarker []byte) []byte { var output []byte for _, currentLine := range lines { var commentIndex = bytes.Index(currentLine, commentMarker) - if ( commentIndex == -1 ) { + if commentIndex == -1 { output = append(output, currentLine...) } else { output = append(output, currentLine[:commentIndex]...) @@ -867,10 +866,18 @@ func ParseRepositoryTag(repos string) (string, string) { return repos, "" } +type User struct { + Uid string // user id + Gid string // primary group id + Username string + Name string + HomeDir string +} + // UserLookup check if the given username or uid is present in /etc/passwd // and returns the user struct. // If the username is not found, an error is returned. -func UserLookup(uid string) (*user.User, error) { +func UserLookup(uid string) (*User, error) { file, err := ioutil.ReadFile("/etc/passwd") if err != nil { return nil, err @@ -878,7 +885,7 @@ func UserLookup(uid string) (*user.User, error) { for _, line := range strings.Split(string(file), "\n") { data := strings.Split(line, ":") if len(data) > 5 && (data[0] == uid || data[2] == uid) { - return &user.User{ + return &User{ Uid: data[2], Gid: data[3], Username: data[0], @@ -890,13 +897,13 @@ func UserLookup(uid string) (*user.User, error) { return nil, fmt.Errorf("User not found in /etc/passwd") } -type DependencyGraph struct{ +type DependencyGraph struct { nodes map[string]*DependencyNode } -type DependencyNode struct{ - id string - deps map[*DependencyNode]bool +type DependencyNode struct { + id string + deps map[*DependencyNode]bool } func NewDependencyGraph() DependencyGraph { @@ -917,7 +924,7 @@ func (graph *DependencyGraph) NewNode(id string) string { return id } nd := &DependencyNode{ - id: id, + id: id, deps: map[*DependencyNode]bool{}, } graph.addNode(nd) @@ -979,7 +986,7 @@ func (graph *DependencyGraph) GenerateTraversalMap() ([][]string, error) { // If at least one dep hasn't been processed yet, we can't // add it. ok := true - for dep, _ := range node.deps { + for dep := range node.deps { if !processed[dep] { ok = false break @@ -991,7 +998,7 @@ func (graph *DependencyGraph) GenerateTraversalMap() ([][]string, error) { } } Debugf("Round %d: found %d available nodes", len(result), len(tmp_processed)) - // If no progress has been made this round, + // If no progress has been made this round, // that means we have circular dependencies. if len(tmp_processed) == 0 { return nil, fmt.Errorf("Could not find a solution to this dependency graph") @@ -1004,4 +1011,4 @@ func (graph *DependencyGraph) GenerateTraversalMap() ([][]string, error) { result = append(result, round) } return result, nil -} \ No newline at end of file +} From 24e02043a2672320abebc3bd5fa6a592e2ebd082 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 6 Sep 2013 17:33:05 -0700 Subject: [PATCH 107/128] Merge builder.go into runtime.go --- api_test.go | 39 +++++------- builder.go | 154 ---------------------------------------------- buildfile.go | 10 ++- container_test.go | 70 ++++++++++----------- runtime.go | 131 +++++++++++++++++++++++++++++++++++++++ runtime_test.go | 12 ++-- server.go | 10 ++- utils_test.go | 2 +- 8 files changed, 191 insertions(+), 237 deletions(-) delete mode 100644 builder.go diff --git a/api_test.go b/api_test.go index 1e8ed20b98..837a20c244 100644 --- a/api_test.go +++ b/api_test.go @@ -321,7 +321,7 @@ func TestGetContainersJSON(t *testing.T) { srv := &Server{runtime: runtime} - container, err := NewBuilder(runtime).Create(&Config{ + container, err := runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"echo", "test"}, }) @@ -357,10 +357,8 @@ func TestGetContainersExport(t *testing.T) { srv := &Server{runtime: runtime} - builder := NewBuilder(runtime) - // Create a container and remove a file - container, err := builder.Create( + container, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"touch", "/test"}, @@ -409,10 +407,8 @@ func TestGetContainersChanges(t *testing.T) { srv := &Server{runtime: runtime} - builder := NewBuilder(runtime) - // Create a container and remove a file - container, err := builder.Create( + container, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/rm", "/etc/passwd"}, @@ -458,9 +454,7 @@ func TestGetContainersTop(t *testing.T) { srv := &Server{runtime: runtime} - builder := NewBuilder(runtime) - - container, err := builder.Create( + container, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/sh", "-c", "cat"}, @@ -541,10 +535,8 @@ func TestGetContainersByName(t *testing.T) { srv := &Server{runtime: runtime} - builder := NewBuilder(runtime) - // Create a container and remove a file - container, err := builder.Create( + container, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"echo", "test"}, @@ -574,10 +566,9 @@ func TestPostCommit(t *testing.T) { srv := &Server{runtime: runtime} - builder := NewBuilder(runtime) // Create a container and remove a file - container, err := builder.Create( + container, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"touch", "/test"}, @@ -671,7 +662,7 @@ func TestPostContainersKill(t *testing.T) { srv := &Server{runtime: runtime} - container, err := NewBuilder(runtime).Create( + container, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/cat"}, @@ -713,7 +704,7 @@ func TestPostContainersRestart(t *testing.T) { srv := &Server{runtime: runtime} - container, err := NewBuilder(runtime).Create( + container, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/cat"}, @@ -767,7 +758,7 @@ func TestPostContainersStart(t *testing.T) { srv := &Server{runtime: runtime} - container, err := NewBuilder(runtime).Create( + container, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/cat"}, @@ -817,7 +808,7 @@ func TestPostContainersStop(t *testing.T) { srv := &Server{runtime: runtime} - container, err := NewBuilder(runtime).Create( + container, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/cat"}, @@ -864,7 +855,7 @@ func TestPostContainersWait(t *testing.T) { srv := &Server{runtime: runtime} - container, err := NewBuilder(runtime).Create( + container, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/sleep", "1"}, @@ -906,7 +897,7 @@ func TestPostContainersAttach(t *testing.T) { srv := &Server{runtime: runtime} - container, err := NewBuilder(runtime).Create( + container, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/cat"}, @@ -998,7 +989,7 @@ func TestDeleteContainers(t *testing.T) { srv := &Server{runtime: runtime} - container, err := NewBuilder(runtime).Create(&Config{ + container, err := runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"touch", "/test"}, }) @@ -1185,10 +1176,8 @@ func TestPostContainersCopy(t *testing.T) { srv := &Server{runtime: runtime} - builder := NewBuilder(runtime) - // Create a container and remove a file - container, err := builder.Create( + container, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"touch", "/test.txt"}, diff --git a/builder.go b/builder.go deleted file mode 100644 index 9124f76ac1..0000000000 --- a/builder.go +++ /dev/null @@ -1,154 +0,0 @@ -package docker - -import ( - "fmt" - "github.com/dotcloud/docker/utils" - "os" - "path" - "time" -) - -var defaultDns = []string{"8.8.8.8", "8.8.4.4"} - -type Builder struct { - runtime *Runtime - repositories *TagStore - graph *Graph - - config *Config - image *Image -} - -func NewBuilder(runtime *Runtime) *Builder { - return &Builder{ - runtime: runtime, - graph: runtime.graph, - repositories: runtime.repositories, - } -} - -func (builder *Builder) Create(config *Config) (*Container, error) { - // Lookup image - img, err := builder.repositories.LookupImage(config.Image) - if err != nil { - return nil, err - } - - if img.Config != nil { - MergeConfig(config, img.Config) - } - - if len(config.Entrypoint) != 0 && config.Cmd == nil { - config.Cmd = []string{} - } else if config.Cmd == nil || len(config.Cmd) == 0 { - return nil, fmt.Errorf("No command specified") - } - - // Generate id - id := GenerateID() - // Generate default hostname - // FIXME: the lxc template no longer needs to set a default hostname - if config.Hostname == "" { - config.Hostname = id[:12] - } - - var args []string - var entrypoint string - - if len(config.Entrypoint) != 0 { - entrypoint = config.Entrypoint[0] - args = append(config.Entrypoint[1:], config.Cmd...) - } else { - entrypoint = config.Cmd[0] - args = config.Cmd[1:] - } - - container := &Container{ - // FIXME: we should generate the ID here instead of receiving it as an argument - ID: id, - Created: time.Now(), - Path: entrypoint, - Args: args, //FIXME: de-duplicate from config - Config: config, - Image: img.ID, // Always use the resolved image id - NetworkSettings: &NetworkSettings{}, - // FIXME: do we need to store this in the container? - SysInitPath: sysInitPath, - } - container.root = builder.runtime.containerRoot(container.ID) - // Step 1: create the container directory. - // This doubles as a barrier to avoid race conditions. - if err := os.Mkdir(container.root, 0700); err != nil { - return nil, err - } - - resolvConf, err := utils.GetResolvConf() - if err != nil { - return nil, err - } - - if len(config.Dns) == 0 && len(builder.runtime.Dns) == 0 && utils.CheckLocalDns(resolvConf) { - //"WARNING: Docker detected local DNS server on resolv.conf. Using default external servers: %v", defaultDns - builder.runtime.Dns = defaultDns - } - - // If custom dns exists, then create a resolv.conf for the container - if len(config.Dns) > 0 || len(builder.runtime.Dns) > 0 { - var dns []string - if len(config.Dns) > 0 { - dns = config.Dns - } else { - dns = builder.runtime.Dns - } - container.ResolvConfPath = path.Join(container.root, "resolv.conf") - f, err := os.Create(container.ResolvConfPath) - if err != nil { - return nil, err - } - defer f.Close() - for _, dns := range dns { - if _, err := f.Write([]byte("nameserver " + dns + "\n")); err != nil { - return nil, err - } - } - } else { - container.ResolvConfPath = "/etc/resolv.conf" - } - - // Step 2: save the container json - if err := container.ToDisk(); err != nil { - return nil, err - } - // Step 3: register the container - if err := builder.runtime.Register(container); err != nil { - return nil, err - } - return container, nil -} - -// Commit creates a new filesystem image from the current state of a container. -// The image can optionally be tagged into a repository -func (builder *Builder) Commit(container *Container, repository, tag, comment, author string, config *Config) (*Image, error) { - // FIXME: freeze the container before copying it to avoid data corruption? - // FIXME: this shouldn't be in commands. - if err := container.EnsureMounted(); err != nil { - return nil, err - } - - rwTar, err := container.ExportRw() - if err != nil { - return nil, err - } - // Create a new image from the container's base layers + a new layer from container changes - img, err := builder.graph.Create(rwTar, container, comment, author, config) - if err != nil { - return nil, err - } - // Register the image if needed - if repository != "" { - if err := builder.repositories.Set(repository, tag, img.ID, true); err != nil { - return img, err - } - } - return img, nil -} diff --git a/buildfile.go b/buildfile.go index 4c8db2c60e..c3c212c693 100644 --- a/buildfile.go +++ b/buildfile.go @@ -23,7 +23,6 @@ type BuildFile interface { type buildFile struct { runtime *Runtime - builder *Builder srv *Server image string @@ -337,7 +336,7 @@ func (b *buildFile) CmdAdd(args string) error { b.config.Image = b.image // Create the container and start it - container, err := b.builder.Create(b.config) + container, err := b.runtime.Create(b.config) if err != nil { return err } @@ -372,7 +371,7 @@ func (b *buildFile) run() (string, error) { b.config.Image = b.image // Create the container and start it - c, err := b.builder.Create(b.config) + c, err := b.runtime.Create(b.config) if err != nil { return "", err } @@ -428,7 +427,7 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error { } } - container, err := b.builder.Create(b.config) + container, err := b.runtime.Create(b.config) if err != nil { return err } @@ -450,7 +449,7 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error { autoConfig := *b.config autoConfig.Cmd = autoCmd // Commit the container - image, err := b.builder.Commit(container, "", "", "", b.maintainer, &autoConfig) + image, err := b.runtime.Commit(container, "", "", "", b.maintainer, &autoConfig) if err != nil { return err } @@ -524,7 +523,6 @@ func (b *buildFile) Build(context io.Reader) (string, error) { func NewBuildFile(srv *Server, out io.Writer, verbose, utilizeCache bool) BuildFile { return &buildFile{ - builder: NewBuilder(srv.runtime), runtime: srv.runtime, srv: srv, config: &Config{}, diff --git a/container_test.go b/container_test.go index b06d531cf7..b792af76d5 100644 --- a/container_test.go +++ b/container_test.go @@ -18,7 +18,7 @@ import ( func TestIDFormat(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - container1, err := NewBuilder(runtime).Create( + container1, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/sh", "-c", "echo hello world"}, @@ -388,7 +388,7 @@ func TestRun(t *testing.T) { func TestOutput(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - container, err := NewBuilder(runtime).Create( + container, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"echo", "-n", "foobar"}, @@ -411,7 +411,7 @@ func TestKillDifferentUser(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - container, err := NewBuilder(runtime).Create(&Config{ + container, err := runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"cat"}, OpenStdin: true, @@ -471,7 +471,7 @@ func TestCreateVolume(t *testing.T) { if err != nil { t.Fatal(err) } - c, err := NewBuilder(runtime).Create(config) + c, err := runtime.Create(config) if err != nil { t.Fatal(err) } @@ -486,7 +486,7 @@ func TestCreateVolume(t *testing.T) { func TestKill(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - container, err := NewBuilder(runtime).Create(&Config{ + container, err := runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"sleep", "2"}, }, @@ -530,9 +530,7 @@ func TestExitCode(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - builder := NewBuilder(runtime) - - trueContainer, err := builder.Create(&Config{ + trueContainer, err := runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/true", ""}, }) @@ -547,7 +545,7 @@ func TestExitCode(t *testing.T) { t.Errorf("Unexpected exit code %d (expected 0)", trueContainer.State.ExitCode) } - falseContainer, err := builder.Create(&Config{ + falseContainer, err := runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/false", ""}, }) @@ -566,7 +564,7 @@ func TestExitCode(t *testing.T) { func TestRestart(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - container, err := NewBuilder(runtime).Create(&Config{ + container, err := runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"echo", "-n", "foobar"}, }, @@ -596,7 +594,7 @@ func TestRestart(t *testing.T) { func TestRestartStdin(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - container, err := NewBuilder(runtime).Create(&Config{ + container, err := runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"cat"}, @@ -673,10 +671,8 @@ func TestUser(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - builder := NewBuilder(runtime) - // Default user must be root - container, err := builder.Create(&Config{ + container, err := runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"id"}, }, @@ -694,7 +690,7 @@ func TestUser(t *testing.T) { } // Set a username - container, err = builder.Create(&Config{ + container, err = runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"id"}, @@ -714,7 +710,7 @@ func TestUser(t *testing.T) { } // Set a UID - container, err = builder.Create(&Config{ + container, err = runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"id"}, @@ -734,7 +730,7 @@ func TestUser(t *testing.T) { } // Set a different user by uid - container, err = builder.Create(&Config{ + container, err = runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"id"}, @@ -756,7 +752,7 @@ func TestUser(t *testing.T) { } // Set a different user by username - container, err = builder.Create(&Config{ + container, err = runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"id"}, @@ -776,7 +772,7 @@ func TestUser(t *testing.T) { } // Test an wrong username - container, err = builder.Create(&Config{ + container, err = runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"id"}, @@ -797,9 +793,7 @@ func TestMultipleContainers(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - builder := NewBuilder(runtime) - - container1, err := builder.Create(&Config{ + container1, err := runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"sleep", "2"}, }, @@ -809,7 +803,7 @@ func TestMultipleContainers(t *testing.T) { } defer runtime.Destroy(container1) - container2, err := builder.Create(&Config{ + container2, err := runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"sleep", "2"}, }, @@ -853,7 +847,7 @@ func TestMultipleContainers(t *testing.T) { func TestStdin(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - container, err := NewBuilder(runtime).Create(&Config{ + container, err := runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"cat"}, @@ -898,7 +892,7 @@ func TestStdin(t *testing.T) { func TestTty(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - container, err := NewBuilder(runtime).Create(&Config{ + container, err := runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"cat"}, @@ -943,7 +937,7 @@ func TestTty(t *testing.T) { func TestEnv(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - container, err := NewBuilder(runtime).Create(&Config{ + container, err := runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"env"}, }, @@ -992,7 +986,7 @@ func TestEnv(t *testing.T) { func TestEntrypoint(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - container, err := NewBuilder(runtime).Create( + container, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Entrypoint: []string{"/bin/echo"}, @@ -1015,7 +1009,7 @@ func TestEntrypoint(t *testing.T) { func TestEntrypointNoCmd(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - container, err := NewBuilder(runtime).Create( + container, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Entrypoint: []string{"/bin/echo", "foobar"}, @@ -1066,7 +1060,7 @@ func TestLXCConfig(t *testing.T) { cpuMin := 100 cpuMax := 10000 cpu := cpuMin + rand.Intn(cpuMax-cpuMin) - container, err := NewBuilder(runtime).Create(&Config{ + container, err := runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/true"}, @@ -1090,7 +1084,7 @@ func TestLXCConfig(t *testing.T) { func TestCustomLxcConfig(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - container, err := NewBuilder(runtime).Create(&Config{ + container, err := runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/true"}, @@ -1121,7 +1115,7 @@ func BenchmarkRunSequencial(b *testing.B) { runtime := mkRuntime(b) defer nuke(runtime) for i := 0; i < b.N; i++ { - container, err := NewBuilder(runtime).Create(&Config{ + container, err := runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"echo", "-n", "foo"}, }, @@ -1153,7 +1147,7 @@ func BenchmarkRunParallel(b *testing.B) { complete := make(chan error) tasks = append(tasks, complete) go func(i int, complete chan error) { - container, err := NewBuilder(runtime).Create(&Config{ + container, err := runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"echo", "-n", "foo"}, }, @@ -1229,7 +1223,7 @@ func TestBindMounts(t *testing.T) { func TestVolumesFromReadonlyMount(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - container, err := NewBuilder(runtime).Create( + container, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/echo", "-n", "foobar"}, @@ -1248,7 +1242,7 @@ func TestVolumesFromReadonlyMount(t *testing.T) { t.Fail() } - container2, err := NewBuilder(runtime).Create( + container2, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/echo", "-n", "foobar"}, @@ -1284,7 +1278,7 @@ func TestRestartWithVolumes(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - container, err := NewBuilder(runtime).Create(&Config{ + container, err := runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"echo", "-n", "foobar"}, Volumes: map[string]struct{}{"/test": {}}, @@ -1327,7 +1321,7 @@ func TestVolumesFromWithVolumes(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - container, err := NewBuilder(runtime).Create(&Config{ + container, err := runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"sh", "-c", "echo -n bar > /test/foo"}, Volumes: map[string]struct{}{"/test": {}}, @@ -1354,7 +1348,7 @@ func TestVolumesFromWithVolumes(t *testing.T) { t.Fail() } - container2, err := NewBuilder(runtime).Create( + container2, err := runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"cat", "/test/foo"}, @@ -1395,7 +1389,7 @@ func TestOnlyLoopbackExistsWhenUsingDisableNetworkOption(t *testing.T) { if err != nil { t.Fatal(err) } - c, err := NewBuilder(runtime).Create(config) + c, err := runtime.Create(config) if err != nil { t.Fatal(err) } diff --git a/runtime.go b/runtime.go index 002c0fe10a..92141be1c9 100644 --- a/runtime.go +++ b/runtime.go @@ -14,6 +14,8 @@ import ( "strings" ) +var defaultDns = []string{"8.8.8.8", "8.8.4.4"} + type Capabilities struct { MemoryLimit bool SwapLimit bool @@ -233,6 +235,7 @@ func (runtime *Runtime) restore() error { return nil } +// FIXME: comment please func (runtime *Runtime) UpdateCapabilities(quiet bool) { if cgroupMemoryMountpoint, err := utils.FindCgroupMountpoint("memory"); err != nil { if !quiet { @@ -260,6 +263,133 @@ func (runtime *Runtime) UpdateCapabilities(quiet bool) { } } +// Create creates a new container from the given configuration. +func (runtime *Runtime) Create(config *Config) (*Container, error) { + // Lookup image + img, err := runtime.repositories.LookupImage(config.Image) + if err != nil { + return nil, err + } + + if img.Config != nil { + MergeConfig(config, img.Config) + } + + if len(config.Entrypoint) != 0 && config.Cmd == nil { + config.Cmd = []string{} + } else if config.Cmd == nil || len(config.Cmd) == 0 { + return nil, fmt.Errorf("No command specified") + } + + // Generate id + id := GenerateID() + // Generate default hostname + // FIXME: the lxc template no longer needs to set a default hostname + if config.Hostname == "" { + config.Hostname = id[:12] + } + + var args []string + var entrypoint string + + if len(config.Entrypoint) != 0 { + entrypoint = config.Entrypoint[0] + args = append(config.Entrypoint[1:], config.Cmd...) + } else { + entrypoint = config.Cmd[0] + args = config.Cmd[1:] + } + + container := &Container{ + // FIXME: we should generate the ID here instead of receiving it as an argument + ID: id, + Created: time.Now(), + Path: entrypoint, + Args: args, //FIXME: de-duplicate from config + Config: config, + Image: img.ID, // Always use the resolved image id + NetworkSettings: &NetworkSettings{}, + // FIXME: do we need to store this in the container? + SysInitPath: sysInitPath, + } + container.root = runtime.containerRoot(container.ID) + // Step 1: create the container directory. + // This doubles as a barrier to avoid race conditions. + if err := os.Mkdir(container.root, 0700); err != nil { + return nil, err + } + + resolvConf, err := utils.GetResolvConf() + if err != nil { + return nil, err + } + + if len(config.Dns) == 0 && len(runtime.Dns) == 0 && utils.CheckLocalDns(resolvConf) { + //"WARNING: Docker detected local DNS server on resolv.conf. Using default external servers: %v", defaultDns + runtime.Dns = defaultDns + } + + // If custom dns exists, then create a resolv.conf for the container + if len(config.Dns) > 0 || len(runtime.Dns) > 0 { + var dns []string + if len(config.Dns) > 0 { + dns = config.Dns + } else { + dns = runtime.Dns + } + container.ResolvConfPath = path.Join(container.root, "resolv.conf") + f, err := os.Create(container.ResolvConfPath) + if err != nil { + return nil, err + } + defer f.Close() + for _, dns := range dns { + if _, err := f.Write([]byte("nameserver " + dns + "\n")); err != nil { + return nil, err + } + } + } else { + container.ResolvConfPath = "/etc/resolv.conf" + } + + // Step 2: save the container json + if err := container.ToDisk(); err != nil { + return nil, err + } + // Step 3: register the container + if err := runtime.Register(container); err != nil { + return nil, err + } + return container, nil +} + +// Commit creates a new filesystem image from the current state of a container. +// The image can optionally be tagged into a repository +func (runtime *Runtime) Commit(container *Container, repository, tag, comment, author string, config *Config) (*Image, error) { + // FIXME: freeze the container before copying it to avoid data corruption? + // FIXME: this shouldn't be in commands. + if err := container.EnsureMounted(); err != nil { + return nil, err + } + + rwTar, err := container.ExportRw() + if err != nil { + return nil, err + } + // Create a new image from the container's base layers + a new layer from container changes + img, err := runtime.graph.Create(rwTar, container, comment, author, config) + if err != nil { + return nil, err + } + // Register the image if needed + if repository != "" { + if err := runtime.repositories.Set(repository, tag, img.ID, true); err != nil { + return img, err + } + } + return img, nil +} + // FIXME: harmonize with NewGraph() func NewRuntime(flGraphPath string, autoRestart bool, dns []string) (*Runtime, error) { runtime, err := NewRuntimeFromDirectory(flGraphPath, autoRestart) @@ -347,3 +477,4 @@ func (history *History) Add(container *Container) { *history = append(*history, container) sort.Sort(history) } + diff --git a/runtime_test.go b/runtime_test.go index a65d962fa6..188e9c9076 100644 --- a/runtime_test.go +++ b/runtime_test.go @@ -144,9 +144,7 @@ func TestRuntimeCreate(t *testing.T) { t.Errorf("Expected 0 containers, %v found", len(runtime.List())) } - builder := NewBuilder(runtime) - - container, err := builder.Create(&Config{ + container, err := runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"ls", "-al"}, }, @@ -187,7 +185,7 @@ func TestRuntimeCreate(t *testing.T) { } // Make sure crete with bad parameters returns an error - _, err = builder.Create( + _, err = runtime.Create( &Config{ Image: GetTestImage(runtime).ID, }, @@ -196,7 +194,7 @@ func TestRuntimeCreate(t *testing.T) { t.Fatal("Builder.Create should throw an error when Cmd is missing") } - _, err = builder.Create( + _, err = runtime.Create( &Config{ Image: GetTestImage(runtime).ID, Cmd: []string{}, @@ -210,7 +208,7 @@ func TestRuntimeCreate(t *testing.T) { func TestDestroy(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - container, err := NewBuilder(runtime).Create(&Config{ + container, err := runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"ls", "-al"}, }, @@ -296,7 +294,7 @@ func startEchoServerContainer(t *testing.T, proto string) (*Runtime, *Container, t.Fatal(fmt.Errorf("Unknown protocol %v", proto)) } t.Log("Trying port", strPort) - container, err = NewBuilder(runtime).Create(&Config{ + container, err = runtime.Create(&Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"sh", "-c", cmd}, PortSpecs: []string{fmt.Sprintf("%s/%s", strPort, proto)}, diff --git a/server.go b/server.go index 69edabf07a..8350ce597f 100644 --- a/server.go +++ b/server.go @@ -139,8 +139,7 @@ func (srv *Server) ImageInsert(name, url, path string, out io.Writer, sf *utils. return "", err } - b := NewBuilder(srv.runtime) - c, err := b.Create(config) + c, err := srv.runtime.Create(config) if err != nil { return "", err } @@ -149,7 +148,7 @@ func (srv *Server) ImageInsert(name, url, path string, out io.Writer, sf *utils. return "", err } // FIXME: Handle custom repo, tag comment, author - img, err = b.Commit(c, "", "", img.Comment, img.Author, nil) + img, err = srv.runtime.Commit(c, "", "", img.Comment, img.Author, nil) if err != nil { return "", err } @@ -400,7 +399,7 @@ func (srv *Server) ContainerCommit(name, repo, tag, author, comment string, conf if container == nil { return "", fmt.Errorf("No such container: %s", name) } - img, err := NewBuilder(srv.runtime).Commit(container, repo, tag, comment, author, config) + img, err := srv.runtime.Commit(container, repo, tag, comment, author, config) if err != nil { return "", err } @@ -904,8 +903,7 @@ func (srv *Server) ContainerCreate(config *Config) (string, error) { if config.Memory > 0 && !srv.runtime.capabilities.SwapLimit { config.MemorySwap = -1 } - b := NewBuilder(srv.runtime) - container, err := b.Create(config) + container, err := srv.runtime.Create(config) if err != nil { if srv.runtime.graph.IsNotExist(err) { diff --git a/utils_test.go b/utils_test.go index e8aae17186..740a5fc1bc 100644 --- a/utils_test.go +++ b/utils_test.go @@ -101,7 +101,7 @@ func mkContainer(r *Runtime, args []string, t *testing.T) (*Container, *HostConf if config.Image == "_" { config.Image = GetTestImage(r).ID } - c, err := NewBuilder(r).Create(config) + c, err := r.Create(config) if err != nil { return nil, nil, err } From 6a9f4ecf9bc4263baef8e9a1d86f1474f9c66d31 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Fri, 6 Sep 2013 17:43:34 -0700 Subject: [PATCH 108/128] Add missing comments to runtime.go --- runtime.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/runtime.go b/runtime.go index 92141be1c9..ab514c40d7 100644 --- a/runtime.go +++ b/runtime.go @@ -44,6 +44,7 @@ func init() { sysInitPath = utils.SelfPath() } +// List returns an array of all containers registered in the runtime. func (runtime *Runtime) List() []*Container { containers := new(History) for e := runtime.containers.Front(); e != nil; e = e.Next() { @@ -62,6 +63,8 @@ func (runtime *Runtime) getContainerElement(id string) *list.Element { return nil } +// Get looks for a container by the specified ID or name, and returns it. +// If the container is not found, or if an error occurs, nil is returned. func (runtime *Runtime) Get(name string) *Container { id, err := runtime.idIndex.Get(name) if err != nil { @@ -74,6 +77,8 @@ func (runtime *Runtime) Get(name string) *Container { return e.Value.(*Container) } +// Exists returns a true if a container of the specified ID or name exists, +// false otherwise. func (runtime *Runtime) Exists(id string) bool { return runtime.Get(id) != nil } @@ -82,6 +87,9 @@ func (runtime *Runtime) containerRoot(id string) string { return path.Join(runtime.repository, id) } +// Load reads the contents of a container from disk and registers +// it with Register. +// This is typically done at startup. func (runtime *Runtime) Load(id string) (*Container, error) { container := &Container{root: runtime.containerRoot(id)} if err := container.FromDisk(); err != nil { @@ -179,6 +187,7 @@ func (runtime *Runtime) LogToDisk(src *utils.WriteBroadcaster, dst, stream strin return nil } +// Destroy unregisters a container from the runtime and cleanly removes its contents from the filesystem. func (runtime *Runtime) Destroy(container *Container) error { if container == nil { return fmt.Errorf("The given container is ") @@ -235,7 +244,7 @@ func (runtime *Runtime) restore() error { return nil } -// FIXME: comment please +// FIXME: comment please! func (runtime *Runtime) UpdateCapabilities(quiet bool) { if cgroupMemoryMountpoint, err := utils.FindCgroupMountpoint("memory"); err != nil { if !quiet { @@ -455,6 +464,8 @@ func NewRuntimeFromDirectory(root string, autoRestart bool) (*Runtime, error) { return runtime, nil } +// History is a convenience type for storing a list of containers, +// ordered by creation date. type History []*Container func (history *History) Len() int { From eca861a99d1a5abe91704b55d044eb8280adb8d1 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Sat, 7 Sep 2013 17:03:40 -0700 Subject: [PATCH 109/128] Add missing package import --- runtime.go | 1 + 1 file changed, 1 insertion(+) diff --git a/runtime.go b/runtime.go index ab514c40d7..1ee7aa26f9 100644 --- a/runtime.go +++ b/runtime.go @@ -12,6 +12,7 @@ import ( "path" "sort" "strings" + "time" ) var defaultDns = []string{"8.8.8.8", "8.8.4.4"} From 268928ab35a5402ff37413ab258200c4bed48790 Mon Sep 17 00:00:00 2001 From: Marko Mikulicic Date: Mon, 9 Sep 2013 11:01:15 +0100 Subject: [PATCH 110/128] Please add go-dockerclient to docker API clients doc --- docs/sources/api/docker_remote_api.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/sources/api/docker_remote_api.rst b/docs/sources/api/docker_remote_api.rst index be15c3494e..5d8657c469 100644 --- a/docs/sources/api/docker_remote_api.rst +++ b/docs/sources/api/docker_remote_api.rst @@ -193,3 +193,5 @@ and we will add the libraries here. +----------------------+----------------+--------------------------------------------+ | Erlang | erldocker | https://github.com/proger/erldocker | +----------------------+----------------+--------------------------------------------+ +| Go | go-dockerclient| https://github.com/fsouza/go-dockerclient | ++----------------------+----------------+--------------------------------------------+ From 630ae43e7d11fa34421a253c765edb6cef5a3371 Mon Sep 17 00:00:00 2001 From: Brian Shumate Date: Mon, 9 Sep 2013 10:19:45 -0400 Subject: [PATCH 111/128] improve sentence readabilty --- docs/sources/examples/hello_world_daemon.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/sources/examples/hello_world_daemon.rst b/docs/sources/examples/hello_world_daemon.rst index 1d9c4ebe7c..d208c774d8 100644 --- a/docs/sources/examples/hello_world_daemon.rst +++ b/docs/sources/examples/hello_world_daemon.rst @@ -11,8 +11,8 @@ Hello World Daemon The most boring daemon ever written. -This example assumes you have Docker installed and with the Ubuntu -image already imported ``docker pull ubuntu``. We will use the Ubuntu +This example assumes you have Docker installed and the Ubuntu +image already imported with ``docker pull ubuntu``. We will use the Ubuntu image to run a simple hello world daemon that will just print hello world to standard out every second. It will continue to do this until we stop it. From 9749d94fb0f3034247dbd969f6dad390f2605e1a Mon Sep 17 00:00:00 2001 From: Brian Shumate Date: Mon, 9 Sep 2013 10:30:06 -0400 Subject: [PATCH 112/128] note about exiting from attachment prior to running --- docs/sources/examples/hello_world_daemon.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/sources/examples/hello_world_daemon.rst b/docs/sources/examples/hello_world_daemon.rst index d208c774d8..6fc4e94ebd 100644 --- a/docs/sources/examples/hello_world_daemon.rst +++ b/docs/sources/examples/hello_world_daemon.rst @@ -56,6 +56,8 @@ Attach to the container to see the results in realtime. process to see what is going on. - **$CONTAINER_ID** The Id of the container we want to attach too. +Exit from the container attachment by pressing Control-C. + .. code-block:: bash sudo docker ps From 64bc08f1c4bee42ff5a17b2d6ababb288fd8f209 Mon Sep 17 00:00:00 2001 From: shin- Date: Mon, 9 Sep 2013 21:02:37 +0200 Subject: [PATCH 113/128] Push tags to registry even if images are already uploaded --- server.go | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/server.go b/server.go index 69edabf07a..d2c4e5fa59 100644 --- a/server.go +++ b/server.go @@ -741,10 +741,24 @@ func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, localName for _, round := range imgList { // FIXME: This section can be parallelized for _, elem := range round { + var pushTags func() error + pushTags = func() error { + out.Write(sf.FormatStatus("", "Pushing tags for rev [%s] on {%s}", elem.ID, ep+"repositories/"+remoteName+"/tags/"+elem.Tag)) + if err := r.PushRegistryTag(remoteName, elem.ID, elem.Tag, ep, repoData.Tokens); err != nil { + return err + } + return nil + } if _, exists := repoData.ImgList[elem.ID]; exists { + if err := pushTags(); err != nil { + return err + } out.Write(sf.FormatStatus("", "Image %s already pushed, skipping", elem.ID)) continue } else if r.LookupRemoteImage(elem.ID, ep, repoData.Tokens) { + if err := pushTags(); err != nil { + return err + } out.Write(sf.FormatStatus("", "Image %s already pushed, skipping", elem.ID)) continue } @@ -754,10 +768,7 @@ func (srv *Server) pushRepository(r *registry.Registry, out io.Writer, localName } else { elem.Checksum = checksum } - out.Write(sf.FormatStatus("", "Pushing tags for rev [%s] on {%s}", elem.ID, ep+"repositories/"+remoteName+"/tags/"+elem.Tag)) - if err := r.PushRegistryTag(remoteName, elem.ID, elem.Tag, ep, repoData.Tokens); err != nil { - return err - } + return pushTags() } } } From fe99e51634f7068ce997e9866516f2b14a93bda9 Mon Sep 17 00:00:00 2001 From: Andy Rothfusz Date: Mon, 9 Sep 2013 13:19:07 -0700 Subject: [PATCH 114/128] Created a little landing page for the examples, combined first two. --- docs/sources/examples/hello_world.rst | 128 ++++++++++++++++++- docs/sources/examples/hello_world_daemon.rst | 95 -------------- docs/sources/examples/index.rst | 8 +- docs/sources/examples/running_examples.rst | 23 ---- docs/theme/docker/layout.html | 4 - 5 files changed, 131 insertions(+), 127 deletions(-) delete mode 100644 docs/sources/examples/hello_world_daemon.rst delete mode 100644 docs/sources/examples/running_examples.rst diff --git a/docs/sources/examples/hello_world.rst b/docs/sources/examples/hello_world.rst index 26e6f4c8a5..0591a99449 100644 --- a/docs/sources/examples/hello_world.rst +++ b/docs/sources/examples/hello_world.rst @@ -2,6 +2,28 @@ :description: A simple hello world example with Docker :keywords: docker, example, hello world +.. _running_examples: + +Running the Examples +==================== + +All the examples assume your machine is running the docker daemon. To +run the docker daemon in the background, simply type: + +.. code-block:: bash + + sudo docker -d & + +Now you can run docker in client mode: by defalt all commands will be +forwarded to the ``docker`` daemon via a protected Unix socket, so you +must run as root. + +.. code-block:: bash + + sudo docker help + +---- + .. _hello_world: Hello World @@ -49,4 +71,108 @@ See the example in action -Continue to the :ref:`hello_world_daemon` example. +---- + +.. _hello_world_daemon: + +Hello World Daemon +================== + +.. include:: example_header.inc + +And now for the most boring daemon ever written! + +This example assumes you have Docker installed and the Ubuntu +image already imported with ``docker pull ubuntu``. We will use the Ubuntu +image to run a simple hello world daemon that will just print hello +world to standard out every second. It will continue to do this until +we stop it. + +**Steps:** + +.. code-block:: bash + + CONTAINER_ID=$(sudo docker run -d ubuntu /bin/sh -c "while true; do echo hello world; sleep 1; done") + +We are going to run a simple hello world daemon in a new container +made from the *ubuntu* image. + +- **"docker run -d "** run a command in a new container. We pass "-d" + so it runs as a daemon. +- **"ubuntu"** is the image we want to run the command inside of. +- **"/bin/sh -c"** is the command we want to run in the container +- **"while true; do echo hello world; sleep 1; done"** is the mini + script we want to run, that will just print hello world once a + second until we stop it. +- **$CONTAINER_ID** the output of the run command will return a + container id, we can use in future commands to see what is going on + with this process. + +.. code-block:: bash + + sudo docker logs $CONTAINER_ID + +Check the logs make sure it is working correctly. + +- **"docker logs**" This will return the logs for a container +- **$CONTAINER_ID** The Id of the container we want the logs for. + +.. code-block:: bash + + sudo docker attach $CONTAINER_ID + +Attach to the container to see the results in realtime. + +- **"docker attach**" This will allow us to attach to a background + process to see what is going on. +- **$CONTAINER_ID** The Id of the container we want to attach too. + +Exit from the container attachment by pressing Control-C. + +.. code-block:: bash + + sudo docker ps + +Check the process list to make sure it is running. + +- **"docker ps"** this shows all running process managed by docker + +.. code-block:: bash + + sudo docker stop $CONTAINER_ID + +Stop the container, since we don't need it anymore. + +- **"docker stop"** This stops a container +- **$CONTAINER_ID** The Id of the container we want to stop. + +.. code-block:: bash + + sudo docker ps + +Make sure it is really stopped. + + +**Video:** + +See the example in action + +.. raw:: html + +
+ +
+ +The next example in the series is a :ref:`python_web_app` example, or +you could skip to any of the other examples: + +.. toctree:: + :maxdepth: 1 + + python_web_app + nodejs_web_app + running_redis_service + running_ssh_service + couchdb_data_volumes + postgresql_service + mongodb diff --git a/docs/sources/examples/hello_world_daemon.rst b/docs/sources/examples/hello_world_daemon.rst deleted file mode 100644 index 6fc4e94ebd..0000000000 --- a/docs/sources/examples/hello_world_daemon.rst +++ /dev/null @@ -1,95 +0,0 @@ -:title: Hello world daemon example -:description: A simple hello world daemon example with Docker -:keywords: docker, example, hello world, daemon - -.. _hello_world_daemon: - -Hello World Daemon -================== - -.. include:: example_header.inc - -The most boring daemon ever written. - -This example assumes you have Docker installed and the Ubuntu -image already imported with ``docker pull ubuntu``. We will use the Ubuntu -image to run a simple hello world daemon that will just print hello -world to standard out every second. It will continue to do this until -we stop it. - -**Steps:** - -.. code-block:: bash - - CONTAINER_ID=$(sudo docker run -d ubuntu /bin/sh -c "while true; do echo hello world; sleep 1; done") - -We are going to run a simple hello world daemon in a new container -made from the *ubuntu* image. - -- **"docker run -d "** run a command in a new container. We pass "-d" - so it runs as a daemon. -- **"ubuntu"** is the image we want to run the command inside of. -- **"/bin/sh -c"** is the command we want to run in the container -- **"while true; do echo hello world; sleep 1; done"** is the mini - script we want to run, that will just print hello world once a - second until we stop it. -- **$CONTAINER_ID** the output of the run command will return a - container id, we can use in future commands to see what is going on - with this process. - -.. code-block:: bash - - sudo docker logs $CONTAINER_ID - -Check the logs make sure it is working correctly. - -- **"docker logs**" This will return the logs for a container -- **$CONTAINER_ID** The Id of the container we want the logs for. - -.. code-block:: bash - - sudo docker attach $CONTAINER_ID - -Attach to the container to see the results in realtime. - -- **"docker attach**" This will allow us to attach to a background - process to see what is going on. -- **$CONTAINER_ID** The Id of the container we want to attach too. - -Exit from the container attachment by pressing Control-C. - -.. code-block:: bash - - sudo docker ps - -Check the process list to make sure it is running. - -- **"docker ps"** this shows all running process managed by docker - -.. code-block:: bash - - sudo docker stop $CONTAINER_ID - -Stop the container, since we don't need it anymore. - -- **"docker stop"** This stops a container -- **$CONTAINER_ID** The Id of the container we want to stop. - -.. code-block:: bash - - sudo docker ps - -Make sure it is really stopped. - - -**Video:** - -See the example in action - -.. raw:: html - -
- -
- -Continue to the :ref:`python_web_app` example. diff --git a/docs/sources/examples/index.rst b/docs/sources/examples/index.rst index 2664b95e54..0b091f88d8 100644 --- a/docs/sources/examples/index.rst +++ b/docs/sources/examples/index.rst @@ -5,16 +5,16 @@ Examples -============ +======== -Contents: +Here are some examples of how to use Docker to create running +processes, starting from a very simple *Hello World* and progressing +to more substantial services like you might find in production. .. toctree:: :maxdepth: 1 - running_examples hello_world - hello_world_daemon python_web_app nodejs_web_app running_redis_service diff --git a/docs/sources/examples/running_examples.rst b/docs/sources/examples/running_examples.rst deleted file mode 100644 index 6097f29fec..0000000000 --- a/docs/sources/examples/running_examples.rst +++ /dev/null @@ -1,23 +0,0 @@ -:title: Running the Examples -:description: An overview on how to run the docker examples -:keywords: docker, examples, how to - -.. _running_examples: - -Running the Examples --------------------- - -All the examples assume your machine is running the docker daemon. To -run the docker daemon in the background, simply type: - - .. code-block:: bash - - sudo docker -d & - -Now you can run docker in client mode: by defalt all commands will be -forwarded to the ``docker`` daemon via a protected Unix socket, so you -must run as root. - - .. code-block:: bash - - sudo docker help diff --git a/docs/theme/docker/layout.html b/docs/theme/docker/layout.html index 91c561235c..b3c76aa489 100755 --- a/docs/theme/docker/layout.html +++ b/docs/theme/docker/layout.html @@ -113,10 +113,6 @@
- - Search by Swiftype - From 0a436e03b8289510ad191a89fcbd5ec06fb8865d Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Thu, 29 Aug 2013 22:21:32 +0000 Subject: [PATCH 115/128] add domainname support --- container.go | 25 +++++++++++++++++++------ sysinit.go | 5 ++++- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/container.go b/container.go index 39c19c53d4..488cba3229 100644 --- a/container.go +++ b/container.go @@ -11,6 +11,7 @@ import ( "io" "io/ioutil" "log" + "net" "os" "os/exec" "path" @@ -20,7 +21,6 @@ import ( "strings" "syscall" "time" - "net" ) type Container struct { @@ -61,6 +61,7 @@ type Container struct { type Config struct { Hostname string + Domainname string User string Memory int64 // Memory limit (in bytes) MemorySwap int64 // Total memory usage (memory + swap); set `-1' to disable swap @@ -203,8 +204,17 @@ func ParseRun(args []string, capabilities *Capabilities) (*Config, *HostConfig, return nil, nil, cmd, err } + hostname := *flHostname + domainname := "" + + parts := strings.SplitN(hostname, ".", 2) + if len(parts) > 1 { + hostname = parts[0] + domainname = parts[1] + } config := &Config{ - Hostname: *flHostname, + Hostname: hostname, + Domainname: domainname, PortSpecs: flPorts, User: *flUser, Tty: *flTty, @@ -692,6 +702,9 @@ func (container *Container) Start(hostConfig *HostConfig) error { params = append(params, "-e", "TERM=xterm") } + params = append(params, "-h", container.Config.Hostname) + params = append(params, "-d", container.Config.Domainname) + // Setup environment params = append(params, "-e", "HOME=/", @@ -813,10 +826,10 @@ func (container *Container) allocateNetwork() error { iface = &NetworkInterface{disabled: true} } else { iface = &NetworkInterface{ - IPNet: net.IPNet{IP: net.ParseIP(container.NetworkSettings.IPAddress), Mask: manager.bridgeNetwork.Mask}, + IPNet: net.IPNet{IP: net.ParseIP(container.NetworkSettings.IPAddress), Mask: manager.bridgeNetwork.Mask}, Gateway: manager.bridgeNetwork.IP, manager: manager, - } + } ipNum := ipToInt(iface.IPNet.IP) manager.ipAllocator.inUse[ipNum] = struct{}{} } @@ -827,10 +840,10 @@ func (container *Container) allocateNetwork() error { portSpecs = container.Config.PortSpecs } else { for backend, frontend := range container.NetworkSettings.PortMapping["Tcp"] { - portSpecs = append(portSpecs, fmt.Sprintf("%s:%s/tcp",frontend, backend)) + portSpecs = append(portSpecs, fmt.Sprintf("%s:%s/tcp", frontend, backend)) } for backend, frontend := range container.NetworkSettings.PortMapping["Udp"] { - portSpecs = append(portSpecs, fmt.Sprintf("%s:%s/udp",frontend, backend)) + portSpecs = append(portSpecs, fmt.Sprintf("%s:%s/udp", frontend, backend)) } } diff --git a/sysinit.go b/sysinit.go index 34f1cbdac6..fe741382c3 100644 --- a/sysinit.go +++ b/sysinit.go @@ -4,6 +4,7 @@ import ( "flag" "fmt" "github.com/dotcloud/docker/utils" + "io/ioutil" "log" "os" "os/exec" @@ -92,7 +93,8 @@ func SysInit() { var u = flag.String("u", "", "username or uid") var gw = flag.String("g", "", "gateway address") var workdir = flag.String("w", "", "workdir") - + var hostname = flag.String("h", "", "hostname") + var domainname = flag.String("d", "", "domainname") var flEnv ListOpts flag.Var(&flEnv, "e", "Set environment variables") @@ -101,6 +103,7 @@ func SysInit() { cleanupEnv(flEnv) setupNetworking(*gw) setupWorkingDirectory(*workdir) + setupHostname(*hostname, *domainname) changeUser(*u) executeProgram(flag.Arg(0), flag.Args()) } From 4f2e59f94a771c4b60947726815d065ef0ea253a Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 9 Sep 2013 18:57:25 +0000 Subject: [PATCH 116/128] bind mount /etc/hosts and /etc/hostname --- builder.go | 27 ++++++++++++++++++++++++++- container.go | 5 ++--- lxc_template.go | 4 ++++ sysinit.go | 5 +---- 4 files changed, 33 insertions(+), 8 deletions(-) diff --git a/builder.go b/builder.go index 9124f76ac1..5ccce8293e 100644 --- a/builder.go +++ b/builder.go @@ -3,6 +3,7 @@ package docker import ( "fmt" "github.com/dotcloud/docker/utils" + "io/ioutil" "os" "path" "time" @@ -119,7 +120,31 @@ func (builder *Builder) Create(config *Config) (*Container, error) { if err := container.ToDisk(); err != nil { return nil, err } - // Step 3: register the container + + // Step 3: if hostname, build hostname and hosts files + container.HostnamePath = path.Join(container.root, "hostname") + ioutil.WriteFile(container.HostnamePath, []byte(container.Config.Hostname+"\n"), 0644) + + hostsContent := []byte("127.0.0.1\tlocalhost\n" + + "::1\t\tlocalhost ip6-localhost ip6-loopback\n" + + "fe00::0\t\tip6-localnet\n" + + "ff00::0\t\tip6-mcastprefix\n" + + "ff02::1\t\tip6-allnodes\n" + + "ff02::2\t\tip6-allrouters\n") + + container.HostsPath = path.Join(container.root, "hosts") + + if container.Config.Domainname != "" { + hostsContent = append([]byte("127.0.0.1\t"+container.Config.Hostname+"."+container.Config.Domainname+" "+container.Config.Hostname+"\n"+ + "::1\t\t"+container.Config.Hostname+"."+container.Config.Domainname+" "+container.Config.Hostname+"\n"), hostsContent...) + } else { + hostsContent = append([]byte("127.0.0.1\t"+container.Config.Hostname+"\n"+ + "::1\t\t"+container.Config.Hostname+"\n"), hostsContent...) + } + + ioutil.WriteFile(container.HostsPath, hostsContent, 0644) + + // Step 4: register the container if err := builder.runtime.Register(container); err != nil { return nil, err } diff --git a/container.go b/container.go index 488cba3229..ecbf8e561f 100644 --- a/container.go +++ b/container.go @@ -42,6 +42,8 @@ type Container struct { SysInitPath string ResolvConfPath string + HostnamePath string + HostsPath string cmd *exec.Cmd stdout *utils.WriteBroadcaster @@ -702,9 +704,6 @@ func (container *Container) Start(hostConfig *HostConfig) error { params = append(params, "-e", "TERM=xterm") } - params = append(params, "-h", container.Config.Hostname) - params = append(params, "-d", container.Config.Domainname) - // Setup environment params = append(params, "-e", "HOME=/", diff --git a/lxc_template.go b/lxc_template.go index d357c02b43..7013581232 100644 --- a/lxc_template.go +++ b/lxc_template.go @@ -30,6 +30,10 @@ lxc.network.ipv4 = {{.NetworkSettings.IPAddress}}/{{.NetworkSettings.IPPrefixLen {{$ROOTFS := .RootfsPath}} lxc.rootfs = {{$ROOTFS}} +# enable domain name support +lxc.mount.entry = {{.HostnamePath}} {{$ROOTFS}}/etc/hostname none bind,ro 0 0 +lxc.mount.entry = {{.HostsPath}} {{$ROOTFS}}/etc/hosts none bind,ro 0 0 + # use a dedicated pts for the container (and limit the number of pseudo terminal # available) lxc.pts = 1024 diff --git a/sysinit.go b/sysinit.go index fe741382c3..34f1cbdac6 100644 --- a/sysinit.go +++ b/sysinit.go @@ -4,7 +4,6 @@ import ( "flag" "fmt" "github.com/dotcloud/docker/utils" - "io/ioutil" "log" "os" "os/exec" @@ -93,8 +92,7 @@ func SysInit() { var u = flag.String("u", "", "username or uid") var gw = flag.String("g", "", "gateway address") var workdir = flag.String("w", "", "workdir") - var hostname = flag.String("h", "", "hostname") - var domainname = flag.String("d", "", "domainname") + var flEnv ListOpts flag.Var(&flEnv, "e", "Set environment variables") @@ -103,7 +101,6 @@ func SysInit() { cleanupEnv(flEnv) setupNetworking(*gw) setupWorkingDirectory(*workdir) - setupHostname(*hostname, *domainname) changeUser(*u) executeProgram(flag.Arg(0), flag.Args()) } From 446ca4b57b228a2d030f0c816e08948ff7da1d79 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 9 Sep 2013 19:40:25 +0000 Subject: [PATCH 117/128] fix init layer --- builder.go | 22 ++++++++++++---------- graph.go | 2 ++ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/builder.go b/builder.go index 5ccce8293e..6e04c8de8d 100644 --- a/builder.go +++ b/builder.go @@ -125,21 +125,23 @@ func (builder *Builder) Create(config *Config) (*Container, error) { container.HostnamePath = path.Join(container.root, "hostname") ioutil.WriteFile(container.HostnamePath, []byte(container.Config.Hostname+"\n"), 0644) - hostsContent := []byte("127.0.0.1\tlocalhost\n" + - "::1\t\tlocalhost ip6-localhost ip6-loopback\n" + - "fe00::0\t\tip6-localnet\n" + - "ff00::0\t\tip6-mcastprefix\n" + - "ff02::1\t\tip6-allnodes\n" + - "ff02::2\t\tip6-allrouters\n") + hostsContent := []byte(` +127.0.0.1 localhost +::1 localhost ip6-localhost ip6-loopback +fe00::0 ip6-localnet +ff00::0 ip6-mcastprefix +ff02::1 ip6-allnodes +ff02::2 ip6-allrouters +`) container.HostsPath = path.Join(container.root, "hosts") if container.Config.Domainname != "" { - hostsContent = append([]byte("127.0.0.1\t"+container.Config.Hostname+"."+container.Config.Domainname+" "+container.Config.Hostname+"\n"+ - "::1\t\t"+container.Config.Hostname+"."+container.Config.Domainname+" "+container.Config.Hostname+"\n"), hostsContent...) + hostsContent = append([]byte(fmt.Sprintf("::1\t\t%s.%s %s\n", container.Config.Hostname, container.Config.Domainname, container.Config.Hostname)), hostsContent...) + hostsContent = append([]byte(fmt.Sprintf("127.0.0.1\t%s.%s %s\n", container.Config.Hostname, container.Config.Domainname, container.Config.Hostname)), hostsContent...) } else { - hostsContent = append([]byte("127.0.0.1\t"+container.Config.Hostname+"\n"+ - "::1\t\t"+container.Config.Hostname+"\n"), hostsContent...) + hostsContent = append([]byte(fmt.Sprintf("::1\t\t%s\n", container.Config.Hostname)), hostsContent...) + hostsContent = append([]byte(fmt.Sprintf("127.0.0.1\t%s\n", container.Config.Hostname)), hostsContent...) } ioutil.WriteFile(container.HostsPath, hostsContent, 0644) diff --git a/graph.go b/graph.go index c54725fdb4..ffbb3f6a6a 100644 --- a/graph.go +++ b/graph.go @@ -202,6 +202,8 @@ func (graph *Graph) getDockerInitLayer() (string, error) { "/sys": "dir", "/.dockerinit": "file", "/etc/resolv.conf": "file", + "/etc/hosts": "file", + "/etc/hostname": "file", // "var/run": "dir", // "var/lock": "dir", } { From 46a1cd69a99a9adc7bc366f1eb2c03b62f464d39 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 9 Sep 2013 21:26:35 +0000 Subject: [PATCH 118/128] only os.Exits on error --- commands.go | 4 +++- docker/docker.go | 3 +++ utils/utils.go | 9 +++++++++ 3 files changed, 15 insertions(+), 1 deletion(-) diff --git a/commands.go b/commands.go index b5eed0888f..df6feac93b 100644 --- a/commands.go +++ b/commands.go @@ -1479,7 +1479,9 @@ func (cli *DockerCli) CmdRun(args ...string) error { if err != nil { return err } - os.Exit(status) + if status != 0 { + return &utils.StatusError{status} + } } return nil diff --git a/docker/docker.go b/docker/docker.go index 9bbd40b696..a0021f3a87 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -75,6 +75,9 @@ func main() { } protoAddrParts := strings.SplitN(flHosts[0], "://", 2) if err := docker.ParseCommands(protoAddrParts[0], protoAddrParts[1], flag.Args()...); err != nil { + if sterr, ok := err.(*utils.StatusError); ok { + os.Exit(sterr.Status) + } log.Fatal(err) os.Exit(-1) } diff --git a/utils/utils.go b/utils/utils.go index aa34abdddc..d417690c0c 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -1012,3 +1012,12 @@ func (graph *DependencyGraph) GenerateTraversalMap() ([][]string, error) { } return result, nil } + +// An StatusError reports an unsuccessful exit by a command. +type StatusError struct { + Status int +} + +func (e *StatusError) Error() string { + return fmt.Sprintf("Status: %d", e.Status) +} From 3c90e96b6d9e16b3983cd7e387b06e81130f1e7c Mon Sep 17 00:00:00 2001 From: Sridhar Ratnakumar Date: Mon, 9 Sep 2013 14:42:45 -0700 Subject: [PATCH 119/128] remove docker-ruby from docs we don't maintain it anymore as we now recommend proper HTTP api based clients instead. --- docs/sources/api/docker_remote_api.rst | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/sources/api/docker_remote_api.rst b/docs/sources/api/docker_remote_api.rst index be15c3494e..f76d50a62f 100644 --- a/docs/sources/api/docker_remote_api.rst +++ b/docs/sources/api/docker_remote_api.rst @@ -175,8 +175,6 @@ and we will add the libraries here. +======================+================+============================================+ | Python | docker-py | https://github.com/dotcloud/docker-py | +----------------------+----------------+--------------------------------------------+ -| Ruby | docker-ruby | https://github.com/ActiveState/docker-ruby | -+----------------------+----------------+--------------------------------------------+ | Ruby | docker-client | https://github.com/geku/docker-client | +----------------------+----------------+--------------------------------------------+ | Ruby | docker-api | https://github.com/swipely/docker-api | From 6678a26d1c94e7838c055a3da3b91ae3de8c3e3c Mon Sep 17 00:00:00 2001 From: Jason McVetta Date: Mon, 9 Sep 2013 15:11:30 -0700 Subject: [PATCH 120/128] gofmt --- api_test.go | 2 +- container.go | 10 +++++----- network.go | 2 +- server.go | 2 +- utils/utils_test.go | 3 +-- 5 files changed, 9 insertions(+), 10 deletions(-) diff --git a/api_test.go b/api_test.go index 1e8ed20b98..0a627f2e85 100644 --- a/api_test.go +++ b/api_test.go @@ -449,7 +449,7 @@ func TestGetContainersChanges(t *testing.T) { } func TestGetContainersTop(t *testing.T) { - t.Skip("Fixme. Skipping test for now. Reported error when testing using dind: 'api_test.go:527: Expected 2 processes, found 0.'") + t.Skip("Fixme. Skipping test for now. Reported error when testing using dind: 'api_test.go:527: Expected 2 processes, found 0.'") runtime, err := newTestRuntime() if err != nil { t.Fatal(err) diff --git a/container.go b/container.go index 39c19c53d4..5799fa4e12 100644 --- a/container.go +++ b/container.go @@ -11,6 +11,7 @@ import ( "io" "io/ioutil" "log" + "net" "os" "os/exec" "path" @@ -20,7 +21,6 @@ import ( "strings" "syscall" "time" - "net" ) type Container struct { @@ -813,10 +813,10 @@ func (container *Container) allocateNetwork() error { iface = &NetworkInterface{disabled: true} } else { iface = &NetworkInterface{ - IPNet: net.IPNet{IP: net.ParseIP(container.NetworkSettings.IPAddress), Mask: manager.bridgeNetwork.Mask}, + IPNet: net.IPNet{IP: net.ParseIP(container.NetworkSettings.IPAddress), Mask: manager.bridgeNetwork.Mask}, Gateway: manager.bridgeNetwork.IP, manager: manager, - } + } ipNum := ipToInt(iface.IPNet.IP) manager.ipAllocator.inUse[ipNum] = struct{}{} } @@ -827,10 +827,10 @@ func (container *Container) allocateNetwork() error { portSpecs = container.Config.PortSpecs } else { for backend, frontend := range container.NetworkSettings.PortMapping["Tcp"] { - portSpecs = append(portSpecs, fmt.Sprintf("%s:%s/tcp",frontend, backend)) + portSpecs = append(portSpecs, fmt.Sprintf("%s:%s/tcp", frontend, backend)) } for backend, frontend := range container.NetworkSettings.PortMapping["Udp"] { - portSpecs = append(portSpecs, fmt.Sprintf("%s:%s/udp",frontend, backend)) + portSpecs = append(portSpecs, fmt.Sprintf("%s:%s/udp", frontend, backend)) } } diff --git a/network.go b/network.go index c2673bd803..b552919253 100644 --- a/network.go +++ b/network.go @@ -642,7 +642,7 @@ func (manager *NetworkManager) Allocate() (*NetworkInterface, error) { if err != nil { return nil, err } - // avoid duplicate IP + // avoid duplicate IP ipNum := ipToInt(ip) firstIP := manager.ipAllocator.network.IP.To4().Mask(manager.ipAllocator.network.Mask) firstIPNum := ipToInt(firstIP) + 1 diff --git a/server.go b/server.go index d2c4e5fa59..129cace8ab 100644 --- a/server.go +++ b/server.go @@ -679,7 +679,7 @@ func (srv *Server) getImageList(localRepo map[string]string) ([][]*registry.ImgD depGraph.NewNode(img.ID) img.WalkHistory(func(current *Image) error { imgList[current.ID] = ®istry.ImgData{ - ID: current.ID, + ID: current.ID, Tag: tag, } parent, err := current.GetParent() diff --git a/utils/utils_test.go b/utils/utils_test.go index be796b2381..9a55e7f62d 100644 --- a/utils/utils_test.go +++ b/utils/utils_test.go @@ -366,7 +366,6 @@ func TestParseRelease(t *testing.T) { assertParseRelease(t, "3.8.0-19-generic", &KernelVersionInfo{Kernel: 3, Major: 8, Minor: 0, Flavor: "19-generic"}, 0) } - func TestDependencyGraphCircular(t *testing.T) { g1 := NewDependencyGraph() a := g1.NewNode("a") @@ -421,4 +420,4 @@ func TestDependencyGraph(t *testing.T) { if len(res[2]) != 1 || res[2][0] != "d" { t.Fatalf("Expected [d], found %v instead", res[2]) } -} \ No newline at end of file +} From dd806b4ecd2c94356342da720e580ab13288df33 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 9 Sep 2013 22:19:28 +0000 Subject: [PATCH 121/128] add Martin Redmond to AUTHORS --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 791d966cef..d4964eb917 100644 --- a/AUTHORS +++ b/AUTHORS @@ -74,6 +74,7 @@ Louis Opter Marco Hennings Marcus Farkas Mark McGranaghan +Martin Redmond Maxim Treskin meejah Michael Crosby From 672f1e068392e95966cd57b4bc49bd8d1b8859dd Mon Sep 17 00:00:00 2001 From: Jason McVetta Date: Mon, 9 Sep 2013 15:21:04 -0700 Subject: [PATCH 122/128] failing test case for multiline command in dockerfile --- buildfile_test.go | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/buildfile_test.go b/buildfile_test.go index 14986161d8..aa2cfa4fdf 100644 --- a/buildfile_test.go +++ b/buildfile_test.go @@ -45,6 +45,21 @@ run [ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ] nil, }, + // Exactly the same as above, except uses a line split with a \ to test + // multiline support. + { + ` +from {IMAGE} +run sh -c 'echo root:testpass \ + > /tmp/passwd' +run mkdir -p /var/run/sshd +run [ "$(cat /tmp/passwd)" = "root:testpass" ] +run [ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ] +`, + nil, + nil, + }, + { ` from {IMAGE} From 6921ca4813a583948d3871a53d0349b834f31036 Mon Sep 17 00:00:00 2001 From: Jason McVetta Date: Mon, 9 Sep 2013 16:42:04 -0700 Subject: [PATCH 123/128] read Dockerfile into memory before parsing, to facilitate regexp preprocessing --- buildfile.go | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/buildfile.go b/buildfile.go index 4c8db2c60e..5472b84982 100644 --- a/buildfile.go +++ b/buildfile.go @@ -1,7 +1,6 @@ package docker import ( - "bufio" "encoding/json" "fmt" "github.com/dotcloud/docker/utils" @@ -459,6 +458,8 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error { return nil } +var multilineRegex = regexp.MustCompile("\\.*\n") + 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 @@ -471,22 +472,26 @@ func (b *buildFile) Build(context io.Reader) (string, error) { } defer os.RemoveAll(name) b.context = name - dockerfile, err := os.Open(path.Join(name, "Dockerfile")) - if err != nil { + filename := path.Join(name, "Dockerfile") + if _, err := os.Stat(filename); os.IsNotExist(err) { return "", fmt.Errorf("Can't build a directory with no Dockerfile") } - // FIXME: "file" is also a terrible variable name ;) - file := bufio.NewReader(dockerfile) + fileBytes, err := ioutil.ReadFile(filename) + if err != nil { + return "", err + } + dockerfile := string(fileBytes) + // dockerfile = multilineRegex.ReplaceAllString(dockerfile, " ") stepN := 0 - for { - line, err := file.ReadString('\n') - if err != nil { - if err == io.EOF && line == "" { - break - } else if err != io.EOF { - return "", err - } - } + for _, line := range strings.Split(dockerfile, "\n") { + /* line, err := dockerfile.ReadString('\n') + if err != nil { + if err == io.EOF && line == "" { + break + } else if err != io.EOF { + return "", err + } + }*/ line = strings.Trim(strings.Replace(line, "\t", " ", -1), " \t\r\n") // Skip comments and empty line if len(line) == 0 || line[0] == '#' { From ebb934c1b044f4bbffe312e9c88c00134c94ef7f Mon Sep 17 00:00:00 2001 From: Jason McVetta Date: Mon, 9 Sep 2013 17:02:45 -0700 Subject: [PATCH 124/128] line continuation regex --- buildfile.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/buildfile.go b/buildfile.go index 5472b84982..c40ee5f063 100644 --- a/buildfile.go +++ b/buildfile.go @@ -458,7 +458,8 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error { return nil } -var multilineRegex = regexp.MustCompile("\\.*\n") +// Long lines can be split with a backslash +var lineContinuation = regexp.MustCompile(`\s*\\.*\n`) func (b *buildFile) Build(context io.Reader) (string, error) { // FIXME: @creack any reason for using /tmp instead of ""? @@ -481,7 +482,7 @@ func (b *buildFile) Build(context io.Reader) (string, error) { return "", err } dockerfile := string(fileBytes) - // dockerfile = multilineRegex.ReplaceAllString(dockerfile, " ") + dockerfile = lineContinuation.ReplaceAllString(dockerfile, " ") stepN := 0 for _, line := range strings.Split(dockerfile, "\n") { /* line, err := dockerfile.ReadString('\n') From 4f3b8033f287051156e6b81aeff9da3c44f64cba Mon Sep 17 00:00:00 2001 From: Jason McVetta Date: Mon, 9 Sep 2013 17:17:09 -0700 Subject: [PATCH 125/128] cruft removal --- buildfile.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/buildfile.go b/buildfile.go index c40ee5f063..cbb6ecfcef 100644 --- a/buildfile.go +++ b/buildfile.go @@ -485,14 +485,6 @@ func (b *buildFile) Build(context io.Reader) (string, error) { dockerfile = lineContinuation.ReplaceAllString(dockerfile, " ") stepN := 0 for _, line := range strings.Split(dockerfile, "\n") { - /* line, err := dockerfile.ReadString('\n') - if err != nil { - if err == io.EOF && line == "" { - break - } else if err != io.EOF { - return "", err - } - }*/ line = strings.Trim(strings.Replace(line, "\t", " ", -1), " \t\r\n") // Skip comments and empty line if len(line) == 0 || line[0] == '#' { From c01f6df43e1280c398d921c2d5eff01db8c26f94 Mon Sep 17 00:00:00 2001 From: Jason McVetta Date: Mon, 9 Sep 2013 18:22:58 -0700 Subject: [PATCH 126/128] Test dockerfile with line containing literal "\n" --- buildfile_test.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/buildfile_test.go b/buildfile_test.go index aa2cfa4fdf..bdc3680386 100644 --- a/buildfile_test.go +++ b/buildfile_test.go @@ -60,6 +60,19 @@ run [ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ] nil, }, + // Line containing literal "\n" + { + ` +from {IMAGE} +run sh -c 'echo root:testpass > /tmp/passwd' +run echo "foo \n bar"; echo "baz" +run mkdir -p /var/run/sshd +run [ "$(cat /tmp/passwd)" = "root:testpass" ] +run [ "$(ls -d /var/run/sshd)" = "/var/run/sshd" ] +`, + nil, + nil, + }, { ` from {IMAGE} From 6a4afb7f8eacde9cb81202347258983908a9f7c6 Mon Sep 17 00:00:00 2001 From: Jason McVetta Date: Mon, 9 Sep 2013 18:23:42 -0700 Subject: [PATCH 127/128] stricter regexp for Dockerfile line continuations --- buildfile.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/buildfile.go b/buildfile.go index cbb6ecfcef..8ff21bf939 100644 --- a/buildfile.go +++ b/buildfile.go @@ -459,7 +459,7 @@ func (b *buildFile) commit(id string, autoCmd []string, comment string) error { } // Long lines can be split with a backslash -var lineContinuation = regexp.MustCompile(`\s*\\.*\n`) +var lineContinuation = regexp.MustCompile(`\s*\\\s*\n`) func (b *buildFile) Build(context io.Reader) (string, error) { // FIXME: @creack any reason for using /tmp instead of ""? From b7826f56664c85e44207ce5e1e685ed04016feb6 Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Tue, 10 Sep 2013 16:55:27 +0000 Subject: [PATCH 128/128] add Brian Olsen to AUTHORS --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index d4964eb917..af0eb3bdd8 100644 --- a/AUTHORS +++ b/AUTHORS @@ -17,6 +17,7 @@ Antony Messerli Barry Allard Brandon Liu Brian McCallister +Brian Olsen Bruno Bigras Caleb Spare Calen Pennington