From ee93f6185be3ae73c16cf41ae397bae3ce2f6c55 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Fri, 27 Dec 2013 10:47:42 -0700 Subject: [PATCH 01/64] Move UserLookup functionality into a separate pkg/user submodule that implements proper parsing of /etc/passwd and /etc/group, and use that to add support for "docker run -u user:group" and for getting supplementary groups (if ":group" is not specified) Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- execdriver/lxc/init.go | 27 ++-- integration/buildfile_test.go | 80 +++++++++++ pkg/user/MAINTAINERS | 1 + pkg/user/user.go | 245 ++++++++++++++++++++++++++++++++++ pkg/user/user_test.go | 94 +++++++++++++ utils/utils.go | 31 ----- 6 files changed, 430 insertions(+), 48 deletions(-) create mode 100644 pkg/user/MAINTAINERS create mode 100644 pkg/user/user.go create mode 100644 pkg/user/user_test.go diff --git a/execdriver/lxc/init.go b/execdriver/lxc/init.go index 7c2b039c50..d3af40049f 100644 --- a/execdriver/lxc/init.go +++ b/execdriver/lxc/init.go @@ -4,11 +4,10 @@ import ( "fmt" "github.com/dotcloud/docker/execdriver" "github.com/dotcloud/docker/pkg/netlink" - "github.com/dotcloud/docker/utils" + "github.com/dotcloud/docker/pkg/user" "github.com/syndtr/gocapability/capability" "net" "os" - "strconv" "strings" "syscall" ) @@ -79,28 +78,22 @@ func setupWorkingDirectory(args *execdriver.InitArgs) error { // Takes care of dropping privileges to the desired user func changeUser(args *execdriver.InitArgs) error { - if args.User == "" { - return nil - } - userent, err := utils.UserLookup(args.User) + uid, gid, suppGids, err := user.GetUserGroupSupplementary( + args.User, + syscall.Getuid(), syscall.Getgid(), + ) if err != nil { - return fmt.Errorf("Unable to find user %v: %v", args.User, err) + return err } - uid, err := strconv.Atoi(userent.Uid) - if err != nil { - return fmt.Errorf("Invalid uid: %v", userent.Uid) + if err := syscall.Setgroups(suppGids); err != nil { + return fmt.Errorf("Setgroups failed: %v", err) } - gid, err := strconv.Atoi(userent.Gid) - if err != nil { - return fmt.Errorf("Invalid gid: %v", userent.Gid) - } - if err := syscall.Setgid(gid); err != nil { - return fmt.Errorf("setgid failed: %v", err) + return fmt.Errorf("Setgid failed: %v", err) } if err := syscall.Setuid(uid); err != nil { - return fmt.Errorf("setuid failed: %v", err) + return fmt.Errorf("Setuid failed: %v", err) } return nil diff --git a/integration/buildfile_test.go b/integration/buildfile_test.go index 16a51e575d..0a8479e50a 100644 --- a/integration/buildfile_test.go +++ b/integration/buildfile_test.go @@ -148,6 +148,86 @@ RUN [ "$(/hello.sh)" = "hello world" ] nil, }, + // Users and groups + { + ` +FROM {IMAGE} + +# Make sure our defaults work +RUN [ "$(id -u):$(id -g)" = '0:0' ] +RUN [ "$(id -un):$(id -gn)" = 'root:root' ] + +# TODO decide if "args.user = strconv.Itoa(syscall.Getuid())" is acceptable behavior for changeUser in sysvinit instead of "return nil" when "USER" isn't specified (so that we get the proper group list even if that is the empty list, even in the default case of not supplying an explicit USER to run as, which implies USER 0) +USER root +RUN [ "$(id -G) -- $(id -Gn)" = '0 -- root' ] + +# Setup dockerio user and group +RUN echo 'dockerio:x:1000:1000::/bin:/bin/false' >> /etc/passwd +RUN echo 'dockerio:x:1000:' >> /etc/group + +# Make sure we can switch to our user and all the information is exactly as we expect it to be +USER dockerio +RUN [ "$(id -u):$(id -g)" = '1000:1000' ] +RUN [ "$(id -un):$(id -gn)" = 'dockerio:dockerio' ] +RUN [ "$(id -G) -- $(id -Gn)" = '1000 -- dockerio' ] + +# Switch back to root and double check that worked exactly as we might expect it to +USER root +RUN [ "$(id -u):$(id -g)" = '0:0' ] +RUN [ "$(id -un):$(id -gn)" = 'root:root' ] +RUN [ "$(id -G) -- $(id -Gn)" = '0 -- root' ] + +# Add a "supplementary" group for our dockerio user +RUN echo 'supplementary:x:1001:dockerio' >> /etc/group + +# ... and then go verify that we get it like we expect +USER dockerio +RUN [ "$(id -u):$(id -g)" = '1000:1000' ] +RUN [ "$(id -un):$(id -gn)" = 'dockerio:dockerio' ] +RUN [ "$(id -G) -- $(id -Gn)" = '1000 1001 -- dockerio supplementary' ] +USER 1000 +RUN [ "$(id -u):$(id -g)" = '1000:1000' ] +RUN [ "$(id -un):$(id -gn)" = 'dockerio:dockerio' ] +RUN [ "$(id -G) -- $(id -Gn)" = '1000 1001 -- dockerio supplementary' ] + +# and finally, super test the new "user:group" syntax +USER dockerio:dockerio +RUN [ "$(id -u):$(id -g)" = '1000:1000' ] +RUN [ "$(id -un):$(id -gn)" = 'dockerio:dockerio' ] +RUN [ "$(id -G) -- $(id -Gn)" = '1000 -- dockerio' ] +USER 1000:dockerio +RUN [ "$(id -u):$(id -g)" = '1000:1000' ] +RUN [ "$(id -un):$(id -gn)" = 'dockerio:dockerio' ] +RUN [ "$(id -G) -- $(id -Gn)" = '1000 -- dockerio' ] +USER dockerio:1000 +RUN [ "$(id -u):$(id -g)" = '1000:1000' ] +RUN [ "$(id -un):$(id -gn)" = 'dockerio:dockerio' ] +RUN [ "$(id -G) -- $(id -Gn)" = '1000 -- dockerio' ] +USER 1000:1000 +RUN [ "$(id -u):$(id -g)" = '1000:1000' ] +RUN [ "$(id -un):$(id -gn)" = 'dockerio:dockerio' ] +RUN [ "$(id -G) -- $(id -Gn)" = '1000 -- dockerio' ] +USER dockerio:supplementary +RUN [ "$(id -u):$(id -g)" = '1000:1001' ] +RUN [ "$(id -un):$(id -gn)" = 'dockerio:supplementary' ] +RUN [ "$(id -G) -- $(id -Gn)" = '1001 -- supplementary' ] +USER dockerio:1001 +RUN [ "$(id -u):$(id -g)" = '1000:1001' ] +RUN [ "$(id -un):$(id -gn)" = 'dockerio:supplementary' ] +RUN [ "$(id -G) -- $(id -Gn)" = '1001 -- supplementary' ] +USER 1000:supplementary +RUN [ "$(id -u):$(id -g)" = '1000:1001' ] +RUN [ "$(id -un):$(id -gn)" = 'dockerio:supplementary' ] +RUN [ "$(id -G) -- $(id -Gn)" = '1001 -- supplementary' ] +USER 1000:1001 +RUN [ "$(id -u):$(id -g)" = '1000:1001' ] +RUN [ "$(id -un):$(id -gn)" = 'dockerio:supplementary' ] +RUN [ "$(id -G) -- $(id -Gn)" = '1001 -- supplementary' ] +`, + nil, + nil, + }, + // Environment variable { ` diff --git a/pkg/user/MAINTAINERS b/pkg/user/MAINTAINERS new file mode 100644 index 0000000000..18e05a3070 --- /dev/null +++ b/pkg/user/MAINTAINERS @@ -0,0 +1 @@ +Tianon Gravi (@tianon) diff --git a/pkg/user/user.go b/pkg/user/user.go new file mode 100644 index 0000000000..30fc90f0e4 --- /dev/null +++ b/pkg/user/user.go @@ -0,0 +1,245 @@ +package user + +import ( + "bufio" + "fmt" + "io" + "os" + "reflect" + "strconv" + "strings" +) + +type User struct { + Name string + Pass string + Uid int + Gid int + Gecos string + Home string + Shell string +} + +type Group struct { + Name string + Pass string + Gid int + List []string +} + +func parseLine(line string, v ...interface{}) { + if line == "" { + return + } + + parts := strings.Split(line, ":") + for i, p := range parts { + if len(v) <= i { + // if we have more "parts" than we have places to put them, bail for great "tolerance" of naughty configuration files + break + } + + t := reflect.TypeOf(v[i]) + if t.Kind() != reflect.Ptr { + // panic, because this is a programming/logic error, not a runtime one + panic("parseLine expects only pointers! argument " + strconv.Itoa(i) + " is not a pointer!") + } + + switch t.Elem().Kind() { + case reflect.String: + // "root", "adm", "/bin/bash" + *v[i].(*string) = p + case reflect.Int: + // "0", "4", "1000" + *v[i].(*int), _ = strconv.Atoi(p) + // ignore string to int conversion errors, for great "tolerance" of naughty configuration files + case reflect.Slice, reflect.Array: + // "", "root", "root,adm,daemon" + list := []string{} + if p != "" { + list = strings.Split(p, ",") + } + *v[i].(*[]string) = list + } + } +} + +func ParsePasswd() ([]*User, error) { + return ParsePasswdFilter(nil) +} + +func ParsePasswdFilter(filter func(*User) bool) ([]*User, error) { + f, err := os.Open("/etc/passwd") + if err != nil { + return nil, err + } + defer f.Close() + return parsePasswdFile(f, filter) +} + +func parsePasswdFile(r io.Reader, filter func(*User) bool) ([]*User, error) { + var ( + s = bufio.NewScanner(r) + out = []*User{} + ) + + for s.Scan() { + if err := s.Err(); err != nil { + return nil, err + } + + text := strings.TrimSpace(s.Text()) + if text == "" { + continue + } + + // see: man 5 passwd + // name:password:UID:GID:GECOS:directory:shell + // Name:Pass:Uid:Gid:Gecos:Home:Shell + // root:x:0:0:root:/root:/bin/bash + // adm:x:3:4:adm:/var/adm:/bin/false + p := &User{} + parseLine( + text, + &p.Name, &p.Pass, &p.Uid, &p.Gid, &p.Gecos, &p.Home, &p.Shell, + ) + + if filter == nil || filter(p) { + out = append(out, p) + } + } + + return out, nil +} + +func ParseGroup() ([]*Group, error) { + return ParseGroupFilter(nil) +} + +func ParseGroupFilter(filter func(*Group) bool) ([]*Group, error) { + f, err := os.Open("/etc/group") + if err != nil { + return nil, err + } + defer f.Close() + return parseGroupFile(f, filter) +} + +func parseGroupFile(r io.Reader, filter func(*Group) bool) ([]*Group, error) { + var ( + s = bufio.NewScanner(r) + out = []*Group{} + ) + + for s.Scan() { + if err := s.Err(); err != nil { + return nil, err + } + + text := s.Text() + if text == "" { + continue + } + + // see: man 5 group + // group_name:password:GID:user_list + // Name:Pass:Gid:List + // root:x:0:root + // adm:x:4:root,adm,daemon + p := &Group{} + parseLine( + text, + &p.Name, &p.Pass, &p.Gid, &p.List, + ) + + if filter == nil || filter(p) { + out = append(out, p) + } + } + + return out, nil +} + +// Given a string like "user", "1000", "user:group", "1000:1000", returns the uid, gid, and list of supplementary group IDs, if possible. +func GetUserGroupSupplementary(userSpec string, defaultUid int, defaultGid int) (int, int, []int, error) { + var ( + uid = defaultUid + gid = defaultGid + suppGids = []int{} + + userArg, groupArg string + ) + + // allow for userArg to have either "user" syntax, or optionally "user:group" syntax + parseLine(userSpec, &userArg, &groupArg) + + users, err := ParsePasswdFilter(func(u *User) bool { + if userArg == "" { + return u.Uid == uid + } + return u.Name == userArg || strconv.Itoa(u.Uid) == userArg + }) + if err != nil && !os.IsNotExist(err) { + if userArg == "" { + userArg = strconv.Itoa(uid) + } + return 0, 0, nil, fmt.Errorf("Unable to find user %v: %v", userArg, err) + } + + haveUser := users != nil && len(users) > 0 + if haveUser { + // if we found any user entries that matched our filter, let's take the first one as "correct" + uid = users[0].Uid + gid = users[0].Gid + } else if userArg != "" { + // we asked for a user but didn't find them... let's check to see if we wanted a numeric user + uid, err = strconv.Atoi(userArg) + if err != nil { + // not numeric - we have to bail + return 0, 0, nil, fmt.Errorf("Unable to find user %v", userArg) + } + + // if userArg couldn't be found in /etc/passwd but is numeric, just roll with it - this is legit + } + + if groupArg != "" || (haveUser && users[0].Name != "") { + groups, err := ParseGroupFilter(func(g *Group) bool { + if groupArg != "" { + return g.Name == groupArg || strconv.Itoa(g.Gid) == groupArg + } + for _, u := range g.List { + if u == users[0].Name { + return true + } + } + return false + }) + if err != nil && !os.IsNotExist(err) { + return 0, 0, nil, fmt.Errorf("Unable to find groups for user %v: %v", users[0].Name, err) + } + + haveGroup := groups != nil && len(groups) > 0 + if groupArg != "" { + if haveGroup { + // if we found any group entries that matched our filter, let's take the first one as "correct" + gid = groups[0].Gid + } else { + // we asked for a group but didn't find id... let's check to see if we wanted a numeric group + gid, err = strconv.Atoi(groupArg) + if err != nil { + // not numeric - we have to bail + return 0, 0, nil, fmt.Errorf("Unable to find group %v", groupArg) + } + + // if groupArg couldn't be found in /etc/group but is numeric, just roll with it - this is legit + } + } else if haveGroup { + suppGids = make([]int, len(groups)) + for i, group := range groups { + suppGids[i] = group.Gid + } + } + } + + return uid, gid, suppGids, nil +} diff --git a/pkg/user/user_test.go b/pkg/user/user_test.go new file mode 100644 index 0000000000..136632c27e --- /dev/null +++ b/pkg/user/user_test.go @@ -0,0 +1,94 @@ +package user + +import ( + "strings" + "testing" +) + +func TestUserParseLine(t *testing.T) { + var ( + a, b string + c []string + d int + ) + + parseLine("", &a, &b) + if a != "" || b != "" { + t.Fatalf("a and b should be empty ('%v', '%v')", a, b) + } + + parseLine("a", &a, &b) + if a != "a" || b != "" { + t.Fatalf("a should be 'a' and b should be empty ('%v', '%v')", a, b) + } + + parseLine("bad boys:corny cows", &a, &b) + if a != "bad boys" || b != "corny cows" { + t.Fatalf("a should be 'bad boys' and b should be 'corny cows' ('%v', '%v')", a, b) + } + + parseLine("", &c) + if len(c) != 0 { + t.Fatalf("c should be empty (%#v)", c) + } + + parseLine("d,e,f:g:h:i,j,k", &c, &a, &b, &c) + if a != "g" || b != "h" || len(c) != 3 || c[0] != "i" || c[1] != "j" || c[2] != "k" { + t.Fatalf("a should be 'g', b should be 'h', and c should be ['i','j','k'] ('%v', '%v', '%#v')", a, b, c) + } + + parseLine("::::::::::", &a, &b, &c) + if a != "" || b != "" || len(c) != 0 { + t.Fatalf("a, b, and c should all be empty ('%v', '%v', '%#v')", a, b, c) + } + + parseLine("not a number", &d) + if d != 0 { + t.Fatalf("d should be 0 (%v)", d) + } + + parseLine("b:12:c", &a, &d, &b) + if a != "b" || b != "c" || d != 12 { + t.Fatalf("a should be 'b' and b should be 'c', and d should be 12 ('%v', '%v', %v)", a, b, d) + } +} + +func TestUserParsePasswd(t *testing.T) { + users, err := parsePasswdFile(strings.NewReader(` +root:x:0:0:root:/root:/bin/bash +adm:x:3:4:adm:/var/adm:/bin/false +this is just some garbage data +`), nil) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if len(users) != 3 { + t.Fatalf("Expected 3 users, got %v", len(users)) + } + if users[0].Uid != 0 || users[0].Name != "root" { + t.Fatalf("Expected users[0] to be 0 - root, got %v - %v", users[0].Uid, users[0].Name) + } + if users[1].Uid != 3 || users[1].Name != "adm" { + t.Fatalf("Expected users[1] to be 3 - adm, got %v - %v", users[1].Uid, users[1].Name) + } +} + +func TestUserParseGroup(t *testing.T) { + groups, err := parseGroupFile(strings.NewReader(` +root:x:0:root +adm:x:4:root,adm,daemon +this is just some garbage data +`), nil) + if err != nil { + t.Fatalf("Unexpected error: %v", err) + } + if len(groups) != 3 { + t.Fatalf("Expected 3 groups, got %v", len(groups)) + } + if groups[0].Gid != 0 || groups[0].Name != "root" || len(groups[0].List) != 1 { + t.Fatalf("Expected groups[0] to be 0 - root - 1 member, got %v - %v - %v", groups[0].Gid, groups[0].Name, len(groups[0].List)) + } + if groups[1].Gid != 4 || groups[1].Name != "adm" || len(groups[1].List) != 3 { + t.Fatalf("Expected groups[1] to be 4 - adm - 3 members, got %v - %v - %v", groups[1].Gid, groups[1].Name, len(groups[1].List)) + } +} diff --git a/utils/utils.go b/utils/utils.go index 542ab49702..5caf792549 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -836,37 +836,6 @@ 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, error) { - file, err := ioutil.ReadFile("/etc/passwd") - if err != nil { - return nil, err - } - for _, line := range strings.Split(string(file), "\n") { - data := strings.Split(line, ":") - if len(data) > 5 && (data[0] == uid || data[2] == uid) { - return &User{ - Uid: data[2], - Gid: data[3], - Username: data[0], - Name: data[4], - HomeDir: data[5], - }, nil - } - } - return nil, fmt.Errorf("User not found in /etc/passwd") -} - // An StatusError reports an unsuccessful exit by a command. type StatusError struct { Status string From 4ffc52385cec66cbd930bc6b5ab828ed65f43dbb Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Fri, 17 Jan 2014 13:41:38 -0800 Subject: [PATCH 02/64] Use type switch instead of reflection Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- pkg/user/user.go | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/pkg/user/user.go b/pkg/user/user.go index 30fc90f0e4..1672f7e679 100644 --- a/pkg/user/user.go +++ b/pkg/user/user.go @@ -5,7 +5,6 @@ import ( "fmt" "io" "os" - "reflect" "strconv" "strings" ) @@ -39,28 +38,25 @@ func parseLine(line string, v ...interface{}) { break } - t := reflect.TypeOf(v[i]) - if t.Kind() != reflect.Ptr { + switch e := v[i].(type) { + case *string: + // "root", "adm", "/bin/bash" + *e = p + case *int: + // "0", "4", "1000" + // ignore string to int conversion errors, for great "tolerance" of naughty configuration files + *e, _ = strconv.Atoi(p) + case *[]string: + // "", "root", "root,adm,daemon" + if p != "" { + *e = strings.Split(p, ",") + } else { + *e = []string{} + } + default: // panic, because this is a programming/logic error, not a runtime one panic("parseLine expects only pointers! argument " + strconv.Itoa(i) + " is not a pointer!") } - - switch t.Elem().Kind() { - case reflect.String: - // "root", "adm", "/bin/bash" - *v[i].(*string) = p - case reflect.Int: - // "0", "4", "1000" - *v[i].(*int), _ = strconv.Atoi(p) - // ignore string to int conversion errors, for great "tolerance" of naughty configuration files - case reflect.Slice, reflect.Array: - // "", "root", "root,adm,daemon" - list := []string{} - if p != "" { - list = strings.Split(p, ",") - } - *v[i].(*[]string) = list - } } } From e8963748fca208e6015e3cb7981fc0bf07929e3e Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Mon, 27 Jan 2014 23:14:30 -0700 Subject: [PATCH 03/64] Consolidate a bunch of the supplementary-groups tests to cut down on the number of RUN lines in a single Dockerfile within TestBuild Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- integration/buildfile_test.go | 59 +++++++++++------------------------ 1 file changed, 19 insertions(+), 40 deletions(-) diff --git a/integration/buildfile_test.go b/integration/buildfile_test.go index 0a8479e50a..2165fe3b06 100644 --- a/integration/buildfile_test.go +++ b/integration/buildfile_test.go @@ -154,12 +154,11 @@ RUN [ "$(/hello.sh)" = "hello world" ] FROM {IMAGE} # Make sure our defaults work -RUN [ "$(id -u):$(id -g)" = '0:0' ] -RUN [ "$(id -un):$(id -gn)" = 'root:root' ] +RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)" = '0:0/root:root' ] # TODO decide if "args.user = strconv.Itoa(syscall.Getuid())" is acceptable behavior for changeUser in sysvinit instead of "return nil" when "USER" isn't specified (so that we get the proper group list even if that is the empty list, even in the default case of not supplying an explicit USER to run as, which implies USER 0) USER root -RUN [ "$(id -G) -- $(id -Gn)" = '0 -- root' ] +RUN [ "$(id -G):$(id -Gn)" = '0:root' ] # Setup dockerio user and group RUN echo 'dockerio:x:1000:1000::/bin:/bin/false' >> /etc/passwd @@ -167,62 +166,42 @@ RUN echo 'dockerio:x:1000:' >> /etc/group # Make sure we can switch to our user and all the information is exactly as we expect it to be USER dockerio -RUN [ "$(id -u):$(id -g)" = '1000:1000' ] -RUN [ "$(id -un):$(id -gn)" = 'dockerio:dockerio' ] -RUN [ "$(id -G) -- $(id -Gn)" = '1000 -- dockerio' ] +RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1000:1000/dockerio:dockerio/1000:dockerio' ] # Switch back to root and double check that worked exactly as we might expect it to USER root -RUN [ "$(id -u):$(id -g)" = '0:0' ] -RUN [ "$(id -un):$(id -gn)" = 'root:root' ] -RUN [ "$(id -G) -- $(id -Gn)" = '0 -- root' ] +RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '0:0/root:root/0:root' ] # Add a "supplementary" group for our dockerio user RUN echo 'supplementary:x:1001:dockerio' >> /etc/group # ... and then go verify that we get it like we expect USER dockerio -RUN [ "$(id -u):$(id -g)" = '1000:1000' ] -RUN [ "$(id -un):$(id -gn)" = 'dockerio:dockerio' ] -RUN [ "$(id -G) -- $(id -Gn)" = '1000 1001 -- dockerio supplementary' ] +RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1000:1000/dockerio:dockerio/1000 1001:dockerio supplementary' ] USER 1000 -RUN [ "$(id -u):$(id -g)" = '1000:1000' ] -RUN [ "$(id -un):$(id -gn)" = 'dockerio:dockerio' ] -RUN [ "$(id -G) -- $(id -Gn)" = '1000 1001 -- dockerio supplementary' ] +RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1000:1000/dockerio:dockerio/1000 1001:dockerio supplementary' ] -# and finally, super test the new "user:group" syntax +# super test the new "user:group" syntax USER dockerio:dockerio -RUN [ "$(id -u):$(id -g)" = '1000:1000' ] -RUN [ "$(id -un):$(id -gn)" = 'dockerio:dockerio' ] -RUN [ "$(id -G) -- $(id -Gn)" = '1000 -- dockerio' ] +RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1000:1000/dockerio:dockerio/1000:dockerio' ] USER 1000:dockerio -RUN [ "$(id -u):$(id -g)" = '1000:1000' ] -RUN [ "$(id -un):$(id -gn)" = 'dockerio:dockerio' ] -RUN [ "$(id -G) -- $(id -Gn)" = '1000 -- dockerio' ] +RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1000:1000/dockerio:dockerio/1000:dockerio' ] USER dockerio:1000 -RUN [ "$(id -u):$(id -g)" = '1000:1000' ] -RUN [ "$(id -un):$(id -gn)" = 'dockerio:dockerio' ] -RUN [ "$(id -G) -- $(id -Gn)" = '1000 -- dockerio' ] +RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1000:1000/dockerio:dockerio/1000:dockerio' ] USER 1000:1000 -RUN [ "$(id -u):$(id -g)" = '1000:1000' ] -RUN [ "$(id -un):$(id -gn)" = 'dockerio:dockerio' ] -RUN [ "$(id -G) -- $(id -Gn)" = '1000 -- dockerio' ] +RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1000:1000/dockerio:dockerio/1000:dockerio' ] USER dockerio:supplementary -RUN [ "$(id -u):$(id -g)" = '1000:1001' ] -RUN [ "$(id -un):$(id -gn)" = 'dockerio:supplementary' ] -RUN [ "$(id -G) -- $(id -Gn)" = '1001 -- supplementary' ] +RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1000:1001/dockerio:supplementary/1001:supplementary' ] USER dockerio:1001 -RUN [ "$(id -u):$(id -g)" = '1000:1001' ] -RUN [ "$(id -un):$(id -gn)" = 'dockerio:supplementary' ] -RUN [ "$(id -G) -- $(id -Gn)" = '1001 -- supplementary' ] +RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1000:1001/dockerio:supplementary/1001:supplementary' ] USER 1000:supplementary -RUN [ "$(id -u):$(id -g)" = '1000:1001' ] -RUN [ "$(id -un):$(id -gn)" = 'dockerio:supplementary' ] -RUN [ "$(id -G) -- $(id -Gn)" = '1001 -- supplementary' ] +RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1000:1001/dockerio:supplementary/1001:supplementary' ] USER 1000:1001 -RUN [ "$(id -u):$(id -g)" = '1000:1001' ] -RUN [ "$(id -un):$(id -gn)" = 'dockerio:supplementary' ] -RUN [ "$(id -G) -- $(id -Gn)" = '1001 -- supplementary' ] +RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1000:1001/dockerio:supplementary/1001:supplementary' ] + +# make sure unknown uid/gid still works properly +USER 1042:1043 +RUN [ "$(id -u):$(id -g)/$(id -un):$(id -gn)/$(id -G):$(id -Gn)" = '1042:1043/1042:1043/1043:1043' ] `, nil, nil, From 17c124baf6a0f3725615b22aa52ca96ac5b95326 Mon Sep 17 00:00:00 2001 From: James DeFelice Date: Fri, 31 Jan 2014 12:44:20 -0500 Subject: [PATCH 04/64] Update lxc_template.go If networking is disabled, but then pipework is used later to add nics, the network still doesn't function. Using flags=up for empty networking fixes this. Docker-DCO-1.1-Signed-off-by: James DeFelice (github: jdef) --- execdriver/lxc/lxc_template.go | 1 + 1 file changed, 1 insertion(+) diff --git a/execdriver/lxc/lxc_template.go b/execdriver/lxc/lxc_template.go index 705bdf5363..a89365f989 100644 --- a/execdriver/lxc/lxc_template.go +++ b/execdriver/lxc/lxc_template.go @@ -15,6 +15,7 @@ lxc.network.name = eth0 {{else}} # network is disabled (-n=false) lxc.network.type = empty +lxc.network.flags = up {{end}} # root filesystem From d43c85961f3378607ace3e05dbab43ab2481699c Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Wed, 5 Feb 2014 10:15:22 +1000 Subject: [PATCH 05/64] remove mention of #docker-meeting Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 93c1d11b1f..ba8c3d1a67 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -163,7 +163,7 @@ If you have any questions, please refer to the FAQ in the [docs](http://docs.doc * Step 1: learn the component inside out * Step 2: make yourself useful by contributing code, bugfixes, support etc. * Step 3: volunteer on the irc channel (#docker@freenode) -* Step 4: propose yourself at a scheduled #docker-meeting +* Step 4: propose yourself at a scheduled docker meeting in #docker-dev Don't forget: being a maintainer is a time investment. Make sure you will have time to make yourself available. You don't have to be a maintainer to make a difference on the project! From a17d7d9a73a23dc1fb2269b34418f2c7a645598b Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Wed, 5 Feb 2014 13:24:17 +1000 Subject: [PATCH 06/64] be a little more explicit about what WORKDIR does Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) --- docs/sources/reference/builder.rst | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/docs/sources/reference/builder.rst b/docs/sources/reference/builder.rst index 2f71b87a93..571824c36c 100644 --- a/docs/sources/reference/builder.rst +++ b/docs/sources/reference/builder.rst @@ -269,7 +269,7 @@ the container's filesystem at path ````. source directory being built (also called the *context* of the build) or a remote file URL. -```` is the path at which the source will be copied in the +```` is the absolute path to which the source will be copied inside the destination container. All new files and directories are created with mode 0755, uid and gid @@ -399,8 +399,10 @@ the image. ``WORKDIR /path/to/workdir`` -The ``WORKDIR`` instruction sets the working directory in which -the command given by ``CMD`` is executed. +The ``WORKDIR`` instruction sets the working directory for the ``RUN``, ``CMD`` and +``ENTRYPOINT`` Dockerfile commands that follow it. + +It can be used multiple times in the one Dockerfile. 3.11 ONBUILD ------------ From 347dc3942704742c1d24983a1e9cb10ae7ab8839 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 5 Feb 2014 11:19:48 -0800 Subject: [PATCH 07/64] Fix bad rebase where bridgeiface was removed Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- config.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config.go b/config.go index fc04c9ff16..dc6e8b554f 100644 --- a/config.go +++ b/config.go @@ -39,6 +39,7 @@ func DaemonConfigFromJob(job *engine.Job) *DaemonConfig { EnableIptables: job.GetenvBool("EnableIptables"), EnableIpForward: job.GetenvBool("EnableIpForward"), BridgeIP: job.Getenv("BridgeIP"), + BridgeIface: job.Getenv("BridgeIface"), DefaultIp: net.ParseIP(job.Getenv("DefaultIp")), InterContainerCommunication: job.GetenvBool("InterContainerCommunication"), GraphDriver: job.Getenv("GraphDriver"), @@ -51,7 +52,7 @@ func DaemonConfigFromJob(job *engine.Job) *DaemonConfig { } else { config.Mtu = GetDefaultNetworkMtu() } - config.DisableNetwork = job.Getenv("BridgeIface") == DisableNetworkBridge + config.DisableNetwork = config.BridgeIface == DisableNetworkBridge return config } From 63d4b293e7c91b8e2ed8da56f338f1bd0a4e1464 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 4 Feb 2014 17:13:00 +0100 Subject: [PATCH 08/64] devmapper: Rename DeviceSet.RemoveDevice to DeleteDevice "Remove" is a bit overloaded, as it is also what deactivating a device mapper device is called. Using "delete" is more clear here. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- graphdriver/devmapper/deviceset.go | 8 ++++---- graphdriver/devmapper/driver.go | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/graphdriver/devmapper/deviceset.go b/graphdriver/devmapper/deviceset.go index 8432d92a4e..b6f21fa386 100644 --- a/graphdriver/devmapper/deviceset.go +++ b/graphdriver/devmapper/deviceset.go @@ -290,7 +290,7 @@ func (devices *DeviceSet) setupBaseImage() error { if oldInfo != nil && !oldInfo.Initialized { utils.Debugf("Removing uninitialized base image") - if err := devices.removeDevice(""); err != nil { + if err := devices.deleteDevice(""); err != nil { utils.Debugf("\n--->Err: %s\n", err) return err } @@ -562,7 +562,7 @@ func (devices *DeviceSet) AddDevice(hash, baseHash string) error { return nil } -func (devices *DeviceSet) removeDevice(hash string) error { +func (devices *DeviceSet) deleteDevice(hash string) error { info := devices.Devices[hash] if info == nil { return fmt.Errorf("hash %s doesn't exists", hash) @@ -610,11 +610,11 @@ func (devices *DeviceSet) removeDevice(hash string) error { return nil } -func (devices *DeviceSet) RemoveDevice(hash string) error { +func (devices *DeviceSet) DeleteDevice(hash string) error { devices.Lock() defer devices.Unlock() - return devices.removeDevice(hash) + return devices.deleteDevice(hash) } func (devices *DeviceSet) deactivateDevice(hash string) error { diff --git a/graphdriver/devmapper/driver.go b/graphdriver/devmapper/driver.go index 664899cfbf..9b6cb109d2 100644 --- a/graphdriver/devmapper/driver.go +++ b/graphdriver/devmapper/driver.go @@ -99,7 +99,7 @@ func (d *Driver) Remove(id string) error { if err := d.unmount(id, mp); err != nil { return err } - return d.DeviceSet.RemoveDevice(id) + return d.DeviceSet.DeleteDevice(id) } func (d *Driver) Get(id string) (string, error) { From dca21dfac77de2bbd69dd5a52b8a2b7816a54367 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 4 Feb 2014 16:06:54 +0100 Subject: [PATCH 09/64] devmapper: add and use removeDeviceAndWait helper This adds a function that calls the lowlevel removeDevice and then waits for it to finish. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- graphdriver/devmapper/deviceset.go | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/graphdriver/devmapper/deviceset.go b/graphdriver/devmapper/deviceset.go index b6f21fa386..e4c835a3de 100644 --- a/graphdriver/devmapper/deviceset.go +++ b/graphdriver/devmapper/deviceset.go @@ -632,28 +632,33 @@ func (devices *DeviceSet) deactivateDevice(hash string) error { return err } if devinfo.Exists != 0 { - if err := removeDevice(devname); err != nil { + if err := devices.removeDeviceAndWait(devname); err != nil { utils.Debugf("\n--->Err: %s\n", err) return err } - if err := devices.waitRemove(hash); err != nil { - return err - } } return nil } +// Issues the underlying dm remove operation and then waits +// for it to finish. +func (devices *DeviceSet) removeDeviceAndWait(devname string) error { + if err := removeDevice(devname); err != nil { + return err + } + if err := devices.waitRemove(devname); err != nil { + return err + } + return nil +} + // waitRemove blocks until either: // a) the device registered at - is removed, // or b) the 1 second timeout expires. -func (devices *DeviceSet) waitRemove(hash string) error { - utils.Debugf("[deviceset %s] waitRemove(%s)", devices.devicePrefix, hash) - defer utils.Debugf("[deviceset %s] waitRemove(%) END", devices.devicePrefix, hash) - devname, err := devices.byHash(hash) - if err != nil { - return err - } +func (devices *DeviceSet) waitRemove(devname string) error { + utils.Debugf("[deviceset %s] waitRemove(%s)", devices.devicePrefix, devname) + defer utils.Debugf("[deviceset %s] waitRemove(%s) END", devices.devicePrefix, devname) i := 0 for ; i < 1000; i += 1 { devinfo, err := getInfo(devname) From 3841e8862596637b904acf7f64b657bf3c9b49b1 Mon Sep 17 00:00:00 2001 From: Joe Shaw Date: Wed, 5 Feb 2014 14:04:19 -0500 Subject: [PATCH 10/64] Always download things over https Docker-DCO-1.1-Signed-off-by: Joe Shaw (github: joeshaw) --- docs/sources/installation/mac.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/installation/mac.rst b/docs/sources/installation/mac.rst index 6fc5ed10bf..4f0550ad3d 100644 --- a/docs/sources/installation/mac.rst +++ b/docs/sources/installation/mac.rst @@ -66,7 +66,7 @@ Run the following commands to get it downloaded and set up: .. code-block:: bash # Get the file - curl -o docker http://get.docker.io/builds/Darwin/x86_64/docker-latest + curl -o docker https://get.docker.io/builds/Darwin/x86_64/docker-latest # Mark it executable chmod +x docker From f5d028c83868082049994ad544c99291a117797d Mon Sep 17 00:00:00 2001 From: James Mills Date: Sat, 1 Feb 2014 22:39:11 +1000 Subject: [PATCH 11/64] Added installation docs for CRUX as a Docker Host. Docker-DCO-1.1-Signed-off-by: James Mills (github: therealprologic) --- docs/sources/faq.rst | 1 + docs/sources/installation/cruxlinux.rst | 98 +++++++++++++++++++++++++ docs/sources/installation/index.rst | 1 + 3 files changed, 100 insertions(+) create mode 100644 docs/sources/installation/cruxlinux.rst diff --git a/docs/sources/faq.rst b/docs/sources/faq.rst index 037f4d797b..cf072f34a7 100644 --- a/docs/sources/faq.rst +++ b/docs/sources/faq.rst @@ -175,6 +175,7 @@ Linux: - Gentoo - ArchLinux - openSUSE 12.3+ +- CRUX 3.0+ Cloud: diff --git a/docs/sources/installation/cruxlinux.rst b/docs/sources/installation/cruxlinux.rst new file mode 100644 index 0000000000..d1970cd1bf --- /dev/null +++ b/docs/sources/installation/cruxlinux.rst @@ -0,0 +1,98 @@ +:title: Installation on CRUX Linux +:description: Docker installation on CRUX Linux. +:keywords: crux linux, virtualization, Docker, documentation, installation + +.. _crux_linux: + + +CRUX Linux +========== + +.. include:: install_header.inc + +.. include:: install_unofficial.inc + +Installing on CRUX Linux can be handled via the ports from `James Mills `_: + +* `docker `_ + +* `docker-bin `_ + +* `docker-git `_ + +The ``docker`` port will install the latest tagged version of Docker. +The ``docker-bin`` port will install the latest tagged versin of Docker from upstream built binaries. +The ``docker-git`` package will build from the current master branch. + + +Installation +------------ + +For the time being (*until the CRUX Docker port(s) get into the official contrib repository*) you will need to install +`James Mills' `_ ports repository. You can do so via: + +Download the ``httpup`` file to ``/etc/ports/``: +:: + + curl -q -o - http://crux.nu/portdb/?a=getup&q=prologic > /etc/ports/prologic.httpup + + +Add ``prtdir /usr/ports/prologic`` to ``/etc/prt-get.conf``: +:: + + vim /etc/prt-get.conf + + # or: + echo "prtdir /usr/ports/prologic" >> /etc/prt-get.conf + + +Update ports and prt-get cache: +:: + + ports -u + prt-get cache + + +To install (*and its dependencies*): +:: + + prt-get depinst docker + + +Use ``docker-bin`` for the upstream binary or ``docker-git`` to build and install from the master branch from git. + + +Kernel Requirements +------------------- + +To have a working **CRUX+Docker** Host you must ensure your Kernel +has the necessary modules enabled for LXC containers to function +correctly and Docker Daemon to work properly. + +Please read the ``README.rst``: +:: + + prt-get readme docker + +There is a ``test_kernel_config.sh`` script in the above ports which you can use to test your Kernel configuration: + +:: + + cd /usr/ports/prologic/docker + ./test_kernel_config.sh /usr/src/linux/.config + + +Starting Docker +--------------- + +There is a rc script created for Docker. To start the Docker service: + +:: + + sudo su - + /etc/rc.d/docker start + +To start on system boot: + +- Edit ``/etc/rc.conf`` +- Put ``docker`` into the ``SERVICES=(...)`` array after ``net``. diff --git a/docs/sources/installation/index.rst b/docs/sources/installation/index.rst index 04c155d885..39c1f6a292 100644 --- a/docs/sources/installation/index.rst +++ b/docs/sources/installation/index.rst @@ -21,6 +21,7 @@ Contents: rhel fedora archlinux + cruxlinux gentoolinux openSUSE frugalware From e2eace9715253b5254d1596f3ee91e2f61127929 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Wed, 5 Feb 2014 16:49:43 -0800 Subject: [PATCH 12/64] Do not ping registry from the cli Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- auth/auth.go | 13 ++++++++----- commands.go | 6 +----- server.go | 14 +++++++++++++- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/auth/auth.go b/auth/auth.go index 770a6a0c0f..cbca81f3e3 100644 --- a/auth/auth.go +++ b/auth/auth.go @@ -151,12 +151,15 @@ func SaveConfig(configFile *ConfigFile) error { // try to register/login to the registry server func Login(authConfig *AuthConfig, factory *utils.HTTPRequestFactory) (string, error) { - client := &http.Client{} - reqStatusCode := 0 - var status string - var reqBody []byte + var ( + status string + reqBody []byte + err error + client = &http.Client{} + reqStatusCode = 0 + serverAddress = authConfig.ServerAddress + ) - serverAddress := authConfig.ServerAddress if serverAddress == "" { serverAddress = IndexServerAddress() } diff --git a/commands.go b/commands.go index 15d4507030..cc019f8c10 100644 --- a/commands.go +++ b/commands.go @@ -266,11 +266,7 @@ func (cli *DockerCli) CmdLogin(args ...string) error { } 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) + serverAddress = cmd.Arg(0) } promptDefault := func(prompt string, configDefault string) { diff --git a/server.go b/server.go index 2942eaeb5b..f108f61740 100644 --- a/server.go +++ b/server.go @@ -200,8 +200,20 @@ func (srv *Server) ContainerKill(job *engine.Job) engine.Status { } func (srv *Server) Auth(job *engine.Job) engine.Status { - authConfig := &auth.AuthConfig{} + var ( + err error + authConfig = &auth.AuthConfig{} + ) + job.GetenvJson("authConfig", authConfig) + // TODO: this is only done here because auth and registry need to be merged into one pkg + if addr := authConfig.ServerAddress; addr != "" && addr != auth.IndexServerAddress() { + addr, err = registry.ExpandAndVerifyRegistryUrl(addr) + if err != nil { + return job.Error(err) + } + authConfig.ServerAddress = addr + } status, err := auth.Login(authConfig, srv.HTTPRequestFactory(nil)) if err != nil { return job.Error(err) From 55e5e37c4f085c0216a3d99dc7e3c3f12c2c7a88 Mon Sep 17 00:00:00 2001 From: Darren Shepherd Date: Wed, 5 Feb 2014 19:09:55 -0700 Subject: [PATCH 13/64] Fix broken images API for version <1.7 Simple typo, "ID" should be "Id". This is causing the images response to not have IDs in it. Docker-DCO-1.1-Signed-off-by: Darren Shepherd (github: ibuildthecloud) --- api/api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/api.go b/api/api.go index 741dc69085..376c906c31 100644 --- a/api/api.go +++ b/api/api.go @@ -222,7 +222,7 @@ func getImagesJSON(eng *engine.Engine, version float64, w http.ResponseWriter, r outLegacy := &engine.Env{} outLegacy.Set("Repository", parts[0]) outLegacy.Set("Tag", parts[1]) - outLegacy.Set("ID", out.Get("ID")) + outLegacy.Set("Id", out.Get("Id")) outLegacy.SetInt64("Created", out.GetInt64("Created")) outLegacy.SetInt64("Size", out.GetInt64("Size")) outLegacy.SetInt64("VirtualSize", out.GetInt64("VirtualSize")) From 1f6000eaa0d571d34e2c9207692fb82c4a022061 Mon Sep 17 00:00:00 2001 From: unclejack Date: Thu, 6 Feb 2014 11:06:25 +0200 Subject: [PATCH 14/64] update issue filing & PR submission instructions Docker-DCO-1.1-Signed-off-by: Cristian Staretu (github: unclejack) --- CONTRIBUTING.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 93c1d11b1f..ee40b1d3b2 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -7,8 +7,10 @@ feels wrong or incomplete. ## Reporting Issues When reporting [issues](https://github.com/dotcloud/docker/issues) -on GitHub please include your host OS ( Ubuntu 12.04, Fedora 19, etc... ) -and the output of `docker version` along with the output of `docker info` if possible. +on GitHub please include your host OS (Ubuntu 12.04, Fedora 19, etc), +the output of `uname -a` and the output of `docker version` along with +the output of `docker info`. Please include the steps required to reproduce +the problem if possible and applicable. This information will help us review and fix your issue faster. ## Build Environment @@ -86,6 +88,8 @@ curl -o .git/hooks/pre-commit https://raw.github.com/edsrzf/gofmt-git-hook/maste Pull requests descriptions should be as clear as possible and include a reference to all the issues that they address. +Pull requests mustn't contain commits from other users or branches. + Code review comments may be added to your pull request. Discuss, then make the suggested modifications and push additional commits to your feature branch. Be sure to post a comment after pushing. The new commits will show up in the pull From bbe382bec4b24d8aee09570f2c3920cc1c9710ca Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Thu, 6 Feb 2014 03:18:12 -0800 Subject: [PATCH 15/64] Check for nil information return Fixes #3912 Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- networkdriver/lxc/driver.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/networkdriver/lxc/driver.go b/networkdriver/lxc/driver.go index c767fd2208..3f9c0af011 100644 --- a/networkdriver/lxc/driver.go +++ b/networkdriver/lxc/driver.go @@ -353,6 +353,10 @@ func Release(job *engine.Job) engine.Status { proto string ) + if containerInterface == nil { + return job.Errorf("No network information to release for %s", id) + } + for _, nat := range containerInterface.PortMappings { if err := portmapper.Unmap(nat); err != nil { log.Printf("Unable to unmap port %s: %s", nat, err) From b95c560fdda4813319a2377e240592a3261e30ef Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 4 Feb 2014 18:19:09 +0100 Subject: [PATCH 16/64] devmapper: Move refcounting to DeviceSet We already have some kind of refcounting in DeviceSet, this fleshes it out to allow it to completely subsume the refcounting in devmapper.Driver. This allows us to drop the double refcounting, and the locking inside devmapper.Driver. This, in particular the locking simplification will make it easier in the future to parallelize the device mapper. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- graphdriver/devmapper/deviceset.go | 108 +++++++++++++++++++-------- graphdriver/devmapper/driver.go | 70 ++++------------- graphdriver/devmapper/driver_test.go | 3 - 3 files changed, 93 insertions(+), 88 deletions(-) diff --git a/graphdriver/devmapper/deviceset.go b/graphdriver/devmapper/deviceset.go index e4c835a3de..4d708c840a 100644 --- a/graphdriver/devmapper/deviceset.go +++ b/graphdriver/devmapper/deviceset.go @@ -29,6 +29,15 @@ type DevInfo struct { TransactionId uint64 `json:"transaction_id"` Initialized bool `json:"initialized"` devices *DeviceSet `json:"-"` + + mountCount int `json:"-"` + mountPath string `json:"-"` + // A floating mount means one reference is not owned and + // will be stolen by the next mount. This allows us to + // avoid unmounting directly after creation before the + // first get (since we need to mount to set up the device + // a bit first). + floating bool `json:"-"` } type MetaData struct { @@ -43,7 +52,6 @@ type DeviceSet struct { TransactionId uint64 NewTransactionId uint64 nextFreeDevice int - activeMounts map[string]int } type DiskUsage struct { @@ -69,6 +77,14 @@ type DevStatus struct { HighestMappedSector uint64 } +type UnmountMode int + +const ( + UnmountRegular UnmountMode = iota + UnmountFloat + UnmountSink +) + func getDevName(name string) string { return "/dev/mapper/" + name } @@ -733,13 +749,12 @@ func (devices *DeviceSet) Shutdown() error { utils.Debugf("[devmapper] Shutting down DeviceSet: %s", devices.root) defer utils.Debugf("[deviceset %s] shutdown END", devices.devicePrefix) - for path, count := range devices.activeMounts { - for i := count; i > 0; i-- { - if err := sysUnmount(path, 0); err != nil { - utils.Debugf("Shutdown unmounting %s, error: %s\n", path, err) + for _, info := range devices.Devices { + if info.mountCount > 0 { + if err := sysUnmount(info.mountPath, 0); err != nil { + utils.Debugf("Shutdown unmounting %s, error: %s\n", info.mountPath, err) } } - delete(devices.activeMounts, path) } for _, d := range devices.Devices { @@ -761,22 +776,32 @@ func (devices *DeviceSet) Shutdown() error { return nil } -func (devices *DeviceSet) MountDevice(hash, path string, readOnly bool) error { +func (devices *DeviceSet) MountDevice(hash, path string) error { devices.Lock() defer devices.Unlock() + info := devices.Devices[hash] + + if info.mountCount > 0 { + if path != info.mountPath { + return fmt.Errorf("Trying to mount devmapper device in multple places (%s, %s)", info.mountPath, path) + } + + if info.floating { + // Steal floating ref + info.floating = false + } else { + info.mountCount++ + } + return nil + } + if err := devices.activateDeviceIfNeeded(hash); err != nil { return fmt.Errorf("Error activating devmapper device for '%s': %s", hash, err) } - info := devices.Devices[hash] - var flags uintptr = sysMsMgcVal - if readOnly { - flags = flags | sysMsRdOnly - } - err := sysMount(info.DevName(), path, "ext4", flags, "discard") if err != nil && err == sysEInval { err = sysMount(info.DevName(), path, "ext4", flags, "") @@ -785,20 +810,50 @@ func (devices *DeviceSet) MountDevice(hash, path string, readOnly bool) error { return fmt.Errorf("Error mounting '%s' on '%s': %s", info.DevName(), path, err) } - count := devices.activeMounts[path] - devices.activeMounts[path] = count + 1 + info.mountCount = 1 + info.mountPath = path + info.floating = false return devices.setInitialized(hash) } -func (devices *DeviceSet) UnmountDevice(hash, path string, deactivate bool) error { - utils.Debugf("[devmapper] UnmountDevice(hash=%s path=%s)", hash, path) +func (devices *DeviceSet) UnmountDevice(hash string, mode UnmountMode) error { + utils.Debugf("[devmapper] UnmountDevice(hash=%s, mode=%d)", hash, mode) defer utils.Debugf("[devmapper] UnmountDevice END") devices.Lock() defer devices.Unlock() - utils.Debugf("[devmapper] Unmount(%s)", path) - if err := sysUnmount(path, 0); err != nil { + info := devices.Devices[hash] + + if mode == UnmountFloat { + if info.floating { + return fmt.Errorf("UnmountDevice: can't float floating reference %s\n", hash) + } + + // Leave this reference floating + info.floating = true + return nil + } + + if mode == UnmountSink { + if !info.floating { + // Someone already sunk this + return nil + } + // Otherwise, treat this as a regular unmount + } + + if info.mountCount == 0 { + return fmt.Errorf("UnmountDevice: device not-mounted id %s\n", hash) + } + + info.mountCount-- + if info.mountCount > 0 { + return nil + } + + utils.Debugf("[devmapper] Unmount(%s)", info.mountPath) + if err := sysUnmount(info.mountPath, 0); err != nil { utils.Debugf("\n--->Err: %s\n", err) return err } @@ -809,15 +864,9 @@ func (devices *DeviceSet) UnmountDevice(hash, path string, deactivate bool) erro return err } - if count := devices.activeMounts[path]; count > 1 { - devices.activeMounts[path] = count - 1 - } else { - delete(devices.activeMounts, path) - } + devices.deactivateDevice(hash) - if deactivate { - devices.deactivateDevice(hash) - } + info.mountPath = "" return nil } @@ -960,9 +1009,8 @@ func NewDeviceSet(root string, doInit bool) (*DeviceSet, error) { SetDevDir("/dev") devices := &DeviceSet{ - root: root, - MetaData: MetaData{Devices: make(map[string]*DevInfo)}, - activeMounts: make(map[string]int), + root: root, + MetaData: MetaData{Devices: make(map[string]*DevInfo)}, } if err := devices.initDevmapper(doInit); err != nil { diff --git a/graphdriver/devmapper/driver.go b/graphdriver/devmapper/driver.go index 9b6cb109d2..25b998e59f 100644 --- a/graphdriver/devmapper/driver.go +++ b/graphdriver/devmapper/driver.go @@ -8,7 +8,6 @@ import ( "github.com/dotcloud/docker/utils" "io/ioutil" "path" - "sync" ) func init() { @@ -22,9 +21,7 @@ func init() { type Driver struct { *DeviceSet - home string - sync.Mutex // Protects concurrent modification to active - active map[string]int + home string } var Init = func(home string) (graphdriver.Driver, error) { @@ -35,7 +32,6 @@ var Init = func(home string) (graphdriver.Driver, error) { d := &Driver{ DeviceSet: deviceSet, home: home, - active: make(map[string]int), } return d, nil } @@ -83,55 +79,36 @@ func (d *Driver) Create(id, parent string) error { return err } + // We float this reference so that the next Get call can + // steal it, so we don't have to unmount + if err := d.DeviceSet.UnmountDevice(id, UnmountFloat); err != nil { + return err + } + return nil } func (d *Driver) Remove(id string) error { - // Protect the d.active from concurrent access - d.Lock() - defer d.Unlock() - - if d.active[id] != 0 { - utils.Errorf("Warning: removing active id %s\n", id) - } - - mp := path.Join(d.home, "mnt", id) - if err := d.unmount(id, mp); err != nil { + // Sink the float from create in case no Get() call was made + if err := d.DeviceSet.UnmountDevice(id, UnmountSink); err != nil { return err } + // This assumes the device has been properly Get/Put:ed and thus is unmounted return d.DeviceSet.DeleteDevice(id) } func (d *Driver) Get(id string) (string, error) { - // Protect the d.active from concurrent access - d.Lock() - defer d.Unlock() - - count := d.active[id] - mp := path.Join(d.home, "mnt", id) - if count == 0 { - if err := d.mount(id, mp); err != nil { - return "", err - } + if err := d.mount(id, mp); err != nil { + return "", err } - d.active[id] = count + 1 - return path.Join(mp, "rootfs"), nil } func (d *Driver) Put(id string) { - // Protect the d.active from concurrent access - d.Lock() - defer d.Unlock() - - if count := d.active[id]; count > 1 { - d.active[id] = count - 1 - } else { - mp := path.Join(d.home, "mnt", id) - d.unmount(id, mp) - delete(d.active, id) + if err := d.DeviceSet.UnmountDevice(id, UnmountRegular); err != nil { + utils.Errorf("Warning: error unmounting device %s: %s\n", id, err) } } @@ -140,25 +117,8 @@ func (d *Driver) mount(id, mountPoint string) error { if err := osMkdirAll(mountPoint, 0755); err != nil && !osIsExist(err) { return err } - // If mountpoint is already mounted, do nothing - if mounted, err := Mounted(mountPoint); err != nil { - return fmt.Errorf("Error checking mountpoint: %s", err) - } else if mounted { - return nil - } // Mount the device - return d.DeviceSet.MountDevice(id, mountPoint, false) -} - -func (d *Driver) unmount(id, mountPoint string) error { - // If mountpoint is not mounted, do nothing - if mounted, err := Mounted(mountPoint); err != nil { - return fmt.Errorf("Error checking mountpoint: %s", err) - } else if !mounted { - return nil - } - // Unmount the device - return d.DeviceSet.UnmountDevice(id, mountPoint, true) + return d.DeviceSet.MountDevice(id, mountPoint) } func (d *Driver) Exists(id string) bool { diff --git a/graphdriver/devmapper/driver_test.go b/graphdriver/devmapper/driver_test.go index 785845ce6e..ff4ec45e5d 100644 --- a/graphdriver/devmapper/driver_test.go +++ b/graphdriver/devmapper/driver_test.go @@ -495,7 +495,6 @@ func TestDriverCreate(t *testing.T) { "DmTaskCreate", "DmTaskGetInfo", "sysMount", - "Mounted", "DmTaskRun", "DmTaskSetTarget", "DmTaskSetSector", @@ -614,7 +613,6 @@ func TestDriverRemove(t *testing.T) { "DmTaskCreate", "DmTaskGetInfo", "sysMount", - "Mounted", "DmTaskRun", "DmTaskSetTarget", "DmTaskSetSector", @@ -645,7 +643,6 @@ func TestDriverRemove(t *testing.T) { "DmTaskSetTarget", "DmTaskSetAddNode", "DmUdevWait", - "Mounted", "sysUnmount", ) }() From 7e25cd5891f84c2fab656c364002290e4b87f934 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 4 Feb 2014 16:08:45 +0100 Subject: [PATCH 17/64] devmapper: Use removeDeviceAndWait in DeviceSet.removeDevice() This makes sure the device is removed just like in deactivateDevice. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- graphdriver/devmapper/deviceset.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphdriver/devmapper/deviceset.go b/graphdriver/devmapper/deviceset.go index 4d708c840a..4955fb5cf0 100644 --- a/graphdriver/devmapper/deviceset.go +++ b/graphdriver/devmapper/deviceset.go @@ -595,7 +595,7 @@ func (devices *DeviceSet) deleteDevice(hash string) error { devinfo, _ := getInfo(info.Name()) if devinfo != nil && devinfo.Exists != 0 { - if err := removeDevice(info.Name()); err != nil { + if err := devices.removeDeviceAndWait(info.Name()); err != nil { utils.Debugf("Error removing device: %s\n", err) return err } From 2c82fd93d8a01cc1f53fe861378e6d2dca0486c6 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Wed, 5 Feb 2014 21:37:53 +0100 Subject: [PATCH 18/64] devmapper: Handle EBUSY while removing For some reason we seem to get transient EBUSY when removing thinp devices, which prohibit removing containers. When this happens we retry a few times which seems to fix the issue for me. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- graphdriver/devmapper/deviceset.go | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/graphdriver/devmapper/deviceset.go b/graphdriver/devmapper/deviceset.go index 4955fb5cf0..3dedfd75b5 100644 --- a/graphdriver/devmapper/deviceset.go +++ b/graphdriver/devmapper/deviceset.go @@ -12,6 +12,7 @@ import ( "path" "path/filepath" "strconv" + "strings" "sync" "time" ) @@ -52,6 +53,7 @@ type DeviceSet struct { TransactionId uint64 NewTransactionId uint64 nextFreeDevice int + sawBusy bool } type DiskUsage struct { @@ -371,6 +373,10 @@ func (devices *DeviceSet) log(level int, file string, line int, dmError int, mes return // Ignore _LOG_DEBUG } + if strings.Contains(message, "busy") { + devices.sawBusy = true + } + utils.Debugf("libdevmapper(%d): %s:%d (%d) %s", level, file, line, dmError, message) } @@ -660,9 +666,26 @@ func (devices *DeviceSet) deactivateDevice(hash string) error { // Issues the underlying dm remove operation and then waits // for it to finish. func (devices *DeviceSet) removeDeviceAndWait(devname string) error { - if err := removeDevice(devname); err != nil { + var err error + + for i := 0; i < 10; i++ { + devices.sawBusy = false + err = removeDevice(devname) + if err == nil { + break + } + if !devices.sawBusy { + return err + } + + // If we see EBUSY it may be a transient error, + // sleep a bit a retry a few times. + time.Sleep(5 * time.Millisecond) + } + if err != nil { return err } + if err := devices.waitRemove(devname); err != nil { return err } From d5d948a038933251fe39021883b71f3399ab116c Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 6 Feb 2014 09:28:30 -0800 Subject: [PATCH 19/64] Add json content type to /containers/json api endpoint Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- api/api.go | 1 + 1 file changed, 1 insertion(+) diff --git a/api/api.go b/api/api.go index 376c906c31..7c229eead0 100644 --- a/api/api.go +++ b/api/api.go @@ -320,6 +320,7 @@ func getContainersJSON(eng *engine.Engine, version float64, w http.ResponseWrite job.Setenv("limit", r.Form.Get("limit")) if version >= 1.5 { + w.Header().Set("Content-Type", "application/json") job.Stdout.Add(w) } else if outs, err = job.Stdout.AddTable(); err != nil { return err From e634f2394085d5dc7e716553b330acb8694bf6d3 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Thu, 6 Feb 2014 11:12:57 -0700 Subject: [PATCH 20/64] Add "ONBUILD" highlighting to our vim syntax file Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- contrib/syntax/vim/syntax/dockerfile.vim | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/contrib/syntax/vim/syntax/dockerfile.vim b/contrib/syntax/vim/syntax/dockerfile.vim index fb5cab23cb..ec79ae7476 100644 --- a/contrib/syntax/vim/syntax/dockerfile.vim +++ b/contrib/syntax/vim/syntax/dockerfile.vim @@ -11,8 +11,7 @@ let b:current_syntax = "dockerfile" syntax case ignore -syntax match dockerfileKeyword /\v^\s*(FROM|MAINTAINER|RUN|CMD|EXPOSE|ENV|ADD)\s/ -syntax match dockerfileKeyword /\v^\s*(ENTRYPOINT|VOLUME|USER|WORKDIR)\s/ +syntax match dockerfileKeyword /\v^\s*(ONBUILD\s+)?(ADD|CMD|ENTRYPOINT|ENV|EXPOSE|FROM|MAINTAINER|RUN|USER|VOLUME|WORKDIR)\s/ highlight link dockerfileKeyword Keyword syntax region dockerfileString start=/\v"/ skip=/\v\\./ end=/\v"/ From 6561d65438ed2e10143a5a8bac32bf62d5d8950d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Asbj=C3=B8rn=20Enge?= Date: Thu, 6 Feb 2014 20:35:45 +0100 Subject: [PATCH 21/64] Added Dockerfile.tmPreferences for hotkey commenting Docker-DCO-1.1-Signed-off-by: Asbjorn Enge (github: asbjornenge) --- .../syntax/textmate/Dockerfile.tmPreferences | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 contrib/syntax/textmate/Dockerfile.tmPreferences diff --git a/contrib/syntax/textmate/Dockerfile.tmPreferences b/contrib/syntax/textmate/Dockerfile.tmPreferences new file mode 100644 index 0000000000..20f0d04ca8 --- /dev/null +++ b/contrib/syntax/textmate/Dockerfile.tmPreferences @@ -0,0 +1,24 @@ + + + + + name + Comments + scope + source.dockerfile + settings + + shellVariables + + + name + TM_COMMENT_START + value + # + + + + uuid + 2B215AC0-A7F3-4090-9FF6-F4842BD56CA7 + + From e95bb0b4a9ef642d02f5549ba8375f9952eff0b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Asbj=C3=B8rn=20Enge?= Date: Thu, 6 Feb 2014 20:37:49 +0100 Subject: [PATCH 22/64] Removed the YAML file since I wasn't using it Docker-DCO-1.1-Signed-off-by: Asbjorn Enge (github: asbjornenge) --- .../textmate/Dockerfile.YAML-tmLanguage | 23 ------------------- 1 file changed, 23 deletions(-) delete mode 100644 contrib/syntax/textmate/Dockerfile.YAML-tmLanguage diff --git a/contrib/syntax/textmate/Dockerfile.YAML-tmLanguage b/contrib/syntax/textmate/Dockerfile.YAML-tmLanguage deleted file mode 100644 index 2323c40558..0000000000 --- a/contrib/syntax/textmate/Dockerfile.YAML-tmLanguage +++ /dev/null @@ -1,23 +0,0 @@ -# [PackageDev] target_format: plist, ext: tmLanguage ---- -name: Dockerfile -scopeName: source.dockerfile -uuid: a39d8795-59d2-49af-aa00-fe74ee29576e - -patterns: -# Keywords -- name: keyword.control.dockerfile - match: ^\s*(FROM|MAINTAINER|RUN|CMD|EXPOSE|ENV|ADD)\s -- name: keyword.operator.dockerfile - match: ^\s*(ENTRYPOINT|VOLUME|USER|WORKDIR)\s -# String -- name: string.quoted.double.dockerfile - begin: "\"" - end: "\"" - patterns: - - name: constant.character.escaped.dockerfile - match: \\. -# Comment -- name: comment.block.dockerfile - match: ^\s*#.*$ -... \ No newline at end of file From 647db3938d27d832d09f622b304c7bd23bc00d74 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Asbj=C3=B8rn=20Enge?= Date: Thu, 6 Feb 2014 20:38:20 +0100 Subject: [PATCH 23/64] Added suppport for the ONBUILD instruction Docker-DCO-1.1-Signed-off-by: Asbjorn Enge (github: asbjornenge) --- contrib/syntax/textmate/Dockerfile.tmLanguage | 34 +++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/contrib/syntax/textmate/Dockerfile.tmLanguage b/contrib/syntax/textmate/Dockerfile.tmLanguage index fa8f38992e..94aa8975ad 100644 --- a/contrib/syntax/textmate/Dockerfile.tmLanguage +++ b/contrib/syntax/textmate/Dockerfile.tmLanguage @@ -12,15 +12,37 @@ match - ^\s*(FROM|MAINTAINER|RUN|CMD|EXPOSE|ENV|ADD)\s - name - keyword.control.dockerfile + ^\s*(ONBUILD|)\s*(FROM|MAINTAINER|RUN|EXPOSE|ENV|ADD|VOLUME|USER|WORKDIR)\s + captures + + 0 + + name + keyword.control.dockerfile + + 1 + + name + keyword.other.special-method.dockerfile + + match - ^\s*(ENTRYPOINT|VOLUME|USER|WORKDIR)\s - name - keyword.operator.dockerfile + ^\s*(ONBUILD|)\s*(CMD|ENTRYPOINT)\s + captures + + 0 + + name + keyword.operator.dockerfile + + 1 + + name + keyword.other.special-method.dockerfile + + begin From 3d86f07f8776fd97f1f75e99ad535a9b6543059a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Asbj=C3=B8rn=20Enge?= Date: Thu, 6 Feb 2014 20:38:25 +0100 Subject: [PATCH 24/64] Updated readme Docker-DCO-1.1-Signed-off-by: Asbjorn Enge (github: asbjornenge) --- contrib/syntax/textmate/README.md | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/contrib/syntax/textmate/README.md b/contrib/syntax/textmate/README.md index 157b5c9da0..8f3ec693b7 100644 --- a/contrib/syntax/textmate/README.md +++ b/contrib/syntax/textmate/README.md @@ -1,9 +1,16 @@ # Dockerfile.tmLanguage -Pretty basic Dockerfile.tmLanguage for Sublime Text syntax highlighting. +Dockerfile syntaxt highlighting for TextMate and Sublime Text. -PR's with syntax updates, suggestions etc. are all very much appreciated! +## Install -I'll get to making this installable via Package Control soon! +### Sublime Text + +Available for Sublime Text under [package control](https://sublime.wbond.net/packages/Dockerfile%20Syntax%20Highlighting). +Search for *Dockerfile Syntax Highlighting* + +### TextMate + +*...unknown. Probably put it somewhere smart.* enjoy. From 888e8da2832871b10f60ba9d3058fc442812ca17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Asbj=C3=B8rn=20Enge?= Date: Thu, 6 Feb 2014 20:48:15 +0100 Subject: [PATCH 25/64] Improved regex Docker-DCO-1.1-Signed-off-by: Asbjorn Enge (github: asbjornenge) --- contrib/syntax/textmate/Dockerfile.tmLanguage | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/contrib/syntax/textmate/Dockerfile.tmLanguage b/contrib/syntax/textmate/Dockerfile.tmLanguage index 94aa8975ad..5d6aa53618 100644 --- a/contrib/syntax/textmate/Dockerfile.tmLanguage +++ b/contrib/syntax/textmate/Dockerfile.tmLanguage @@ -12,7 +12,7 @@ match - ^\s*(ONBUILD|)\s*(FROM|MAINTAINER|RUN|EXPOSE|ENV|ADD|VOLUME|USER|WORKDIR)\s + ^\s*(ONBUILD\s+)?(FROM|MAINTAINER|RUN|EXPOSE|ENV|ADD|VOLUME|USER|WORKDIR)\s captures 0 @@ -29,7 +29,7 @@ match - ^\s*(ONBUILD|)\s*(CMD|ENTRYPOINT)\s + ^\s*(ONBUILD\s+)?(CMD|ENTRYPOINT)\s captures 0 From f6c6f303f84773987afd5f83d9c1e2170fa140e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Asbj=C3=B8rn=20Enge?= Date: Thu, 6 Feb 2014 20:50:36 +0100 Subject: [PATCH 26/64] Added support for single quoted strings Docker-DCO-1.1-Signed-off-by: Asbjorn Enge (github: asbjornenge) --- contrib/syntax/textmate/Dockerfile.tmLanguage | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/contrib/syntax/textmate/Dockerfile.tmLanguage b/contrib/syntax/textmate/Dockerfile.tmLanguage index 5d6aa53618..13b586e5cb 100644 --- a/contrib/syntax/textmate/Dockerfile.tmLanguage +++ b/contrib/syntax/textmate/Dockerfile.tmLanguage @@ -61,6 +61,23 @@ + + begin + ' + end + ' + name + string.quoted.single.dockerfile + patterns + + + match + \\. + name + constant.character.escaped.dockerfile + + + match ^\s*#.*$ From 25d87553f678e247b3036db7804599624191dfd1 Mon Sep 17 00:00:00 2001 From: Daniel Mizyrycki Date: Mon, 3 Feb 2014 18:44:28 -0800 Subject: [PATCH 27/64] docker-ci: Add new docker coverage report Docker-DCO-1.1-Signed-off-by: Daniel Mizyrycki (github: mzdaniel) --- .../docker-ci/docker-coverage/gocoverage.sh | 52 +++++++++++++++++++ 1 file changed, 52 insertions(+) create mode 100755 hack/infrastructure/docker-ci/docker-coverage/gocoverage.sh diff --git a/hack/infrastructure/docker-ci/docker-coverage/gocoverage.sh b/hack/infrastructure/docker-ci/docker-coverage/gocoverage.sh new file mode 100755 index 0000000000..fdacc290b4 --- /dev/null +++ b/hack/infrastructure/docker-ci/docker-coverage/gocoverage.sh @@ -0,0 +1,52 @@ +#!/bin/bash + +export PATH='/go/bin':$PATH +export DOCKER_PATH='/go/src/github.com/dotcloud/docker' + +# Signal coverage report name, parsed by docker-ci +set -x +COVERAGE_PATH=$(date +"docker-%Y%m%d%H%M%S") +set +x + +REPORTS="/data/$COVERAGE_PATH" +INDEX="$REPORTS/index.html" + +# Test docker +cd $DOCKER_PATH +./hack/make.sh test; exit_status=$? +PROFILE_PATH="$(ls -d $DOCKER_PATH/bundles/* | sed -n '$ p')/test/coverprofiles" + +if [ "$exit_status" -eq "0" ]; then + # Download coverage dependencies + go get github.com/axw/gocov/gocov + go get -u github.com/matm/gocov-html + + # Create coverage report + mkdir -p $REPORTS + cd $PROFILE_PATH + cat > $INDEX << "EOF" + + + + + +Docker Coverage Report + +

Docker Coverage Report

+ + +EOF + for profile in *; do + gocov convert $profile | gocov-html >$REPORTS/$profile.html + echo "" >> $INDEX + done + echo "
packagepct
$profile" >> $INDEX + go tool cover -func=$profile | sed -En '$ s/.+\t(.+)/\1/p' >> $INDEX + echo "
" >> $INDEX +fi + +# Signal test and coverage result, parsed by docker-ci +set -x +exit $exit_status + From 15711ed670e8ab59611030ea1191189ad495ea63 Mon Sep 17 00:00:00 2001 From: Brandon Philips Date: Thu, 6 Feb 2014 11:34:25 -0800 Subject: [PATCH 28/64] pkg: systemd: add initial MAINTAINERS I volunteered for pkg/systemd MAINTAINER and there were no objections during the #docker-dev meeting. For context I wrote most of the stuff in here and wrote the dependent calls in api.go. Plus, I actively test the code via CoreOS. Docker-DCO-1.1-Signed-off-by: Brandon Philips (github: philips) --- pkg/systemd/MAINTAINERS | 1 + 1 file changed, 1 insertion(+) create mode 100644 pkg/systemd/MAINTAINERS diff --git a/pkg/systemd/MAINTAINERS b/pkg/systemd/MAINTAINERS new file mode 100644 index 0000000000..51228b368a --- /dev/null +++ b/pkg/systemd/MAINTAINERS @@ -0,0 +1 @@ +Brandon Philips (@philips) From 304e33a2fe2d006cc1063c8060c6e22c9ade3d59 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 6 Feb 2014 22:26:09 +0100 Subject: [PATCH 29/64] devmapper: Fix UnmountDevice for non-existing device Properly error out if passed an id that doesn't exist. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- graphdriver/devmapper/deviceset.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/graphdriver/devmapper/deviceset.go b/graphdriver/devmapper/deviceset.go index 3dedfd75b5..c1f2f558a0 100644 --- a/graphdriver/devmapper/deviceset.go +++ b/graphdriver/devmapper/deviceset.go @@ -847,6 +847,9 @@ func (devices *DeviceSet) UnmountDevice(hash string, mode UnmountMode) error { defer devices.Unlock() info := devices.Devices[hash] + if info == nil { + return fmt.Errorf("UnmountDevice: no such device %s\n", hash) + } if mode == UnmountFloat { if info.floating { From 693d3f8c6ab4c9fed5fe4c5ac5fcb44568fe1638 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Thu, 6 Feb 2014 23:08:17 +0100 Subject: [PATCH 30/64] devmapper: Fix MountDevice for non-existing Device Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- graphdriver/devmapper/deviceset.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/graphdriver/devmapper/deviceset.go b/graphdriver/devmapper/deviceset.go index c1f2f558a0..637192dbcb 100644 --- a/graphdriver/devmapper/deviceset.go +++ b/graphdriver/devmapper/deviceset.go @@ -804,6 +804,9 @@ func (devices *DeviceSet) MountDevice(hash, path string) error { defer devices.Unlock() info := devices.Devices[hash] + if info == nil { + return fmt.Errorf("Unknown device %s", hash) + } if info.mountCount > 0 { if path != info.mountPath { From 55b74bfe469115f25a52c6ad48b13eb9c7e6197c Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Fri, 7 Feb 2014 01:42:11 +0100 Subject: [PATCH 31/64] Added Security FAQ security Docker-DCO-1.1-Signed-off-by: James Turnbull (github: jamtur01) --- docs/sources/faq.rst | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/sources/faq.rst b/docs/sources/faq.rst index cf072f34a7..5c8e69703f 100644 --- a/docs/sources/faq.rst +++ b/docs/sources/faq.rst @@ -183,6 +183,12 @@ Cloud: - Google Compute Engine - Rackspace +How do I report a security issue with Docker? +............................................. + +You can learn about the project's security policy `here `_ +and report security issues to this `mailbox `_. + Can I help by adding some questions and answers? ................................................ From b2945f27767f35a273feb6e255945f3e1f6108d2 Mon Sep 17 00:00:00 2001 From: Dafydd Crosby Date: Fri, 7 Feb 2014 00:10:47 -0700 Subject: [PATCH 32/64] Fix RST formatting --- docs/sources/reference/run.rst | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/sources/reference/run.rst b/docs/sources/reference/run.rst index 307edace00..d8de280671 100644 --- a/docs/sources/reference/run.rst +++ b/docs/sources/reference/run.rst @@ -143,6 +143,7 @@ Network Settings ---------------- :: + -n=true : Enable networking for this container -dns=[] : Set custom dns servers for the container From f289b3a19e03b99a6618009320abaec2c0cf1ec1 Mon Sep 17 00:00:00 2001 From: German DZ Date: Fri, 7 Feb 2014 09:22:10 +0100 Subject: [PATCH 33/64] New folder structure to support TextMate2 bundles format Docker-DCO-1.1-Signed-off-by: German Del Zotto (github: GermanDZ) --- .../Preferences}/Dockerfile.tmPreferences | 0 .../Syntaxes}/Dockerfile.tmLanguage | 0 .../syntax/textmate/Docker.tmbundle/info.plist | 16 ++++++++++++++++ 3 files changed, 16 insertions(+) rename contrib/syntax/textmate/{ => Docker.tmbundle/Preferences}/Dockerfile.tmPreferences (100%) rename contrib/syntax/textmate/{ => Docker.tmbundle/Syntaxes}/Dockerfile.tmLanguage (100%) create mode 100644 contrib/syntax/textmate/Docker.tmbundle/info.plist diff --git a/contrib/syntax/textmate/Dockerfile.tmPreferences b/contrib/syntax/textmate/Docker.tmbundle/Preferences/Dockerfile.tmPreferences similarity index 100% rename from contrib/syntax/textmate/Dockerfile.tmPreferences rename to contrib/syntax/textmate/Docker.tmbundle/Preferences/Dockerfile.tmPreferences diff --git a/contrib/syntax/textmate/Dockerfile.tmLanguage b/contrib/syntax/textmate/Docker.tmbundle/Syntaxes/Dockerfile.tmLanguage similarity index 100% rename from contrib/syntax/textmate/Dockerfile.tmLanguage rename to contrib/syntax/textmate/Docker.tmbundle/Syntaxes/Dockerfile.tmLanguage diff --git a/contrib/syntax/textmate/Docker.tmbundle/info.plist b/contrib/syntax/textmate/Docker.tmbundle/info.plist new file mode 100644 index 0000000000..239f4b0a9b --- /dev/null +++ b/contrib/syntax/textmate/Docker.tmbundle/info.plist @@ -0,0 +1,16 @@ + + + + + contactEmailRot13 + germ@andz.com.ar + contactName + GermanDZ + description + Helpers for Docker. + name + Docker + uuid + 8B9DDBAF-E65C-4E12-FFA7-467D4AA535B1 + + From ac06646b1fb7172937c52d5f1217dcac5a86f0f9 Mon Sep 17 00:00:00 2001 From: German DZ Date: Fri, 7 Feb 2014 09:30:11 +0100 Subject: [PATCH 34/64] Instructions for Textmate's bundle installation Docker-DCO-1.1-Signed-off-by: German Del Zotto (github: GermanDZ) --- contrib/syntax/textmate/README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/contrib/syntax/textmate/README.md b/contrib/syntax/textmate/README.md index 8f3ec693b7..e78b76af45 100644 --- a/contrib/syntax/textmate/README.md +++ b/contrib/syntax/textmate/README.md @@ -1,4 +1,4 @@ -# Dockerfile.tmLanguage +# Docker.tmbundle Dockerfile syntaxt highlighting for TextMate and Sublime Text. @@ -6,11 +6,11 @@ Dockerfile syntaxt highlighting for TextMate and Sublime Text. ### Sublime Text -Available for Sublime Text under [package control](https://sublime.wbond.net/packages/Dockerfile%20Syntax%20Highlighting). +Available for Sublime Text under [package control](https://sublime.wbond.net/packages/Dockerfile%20Syntax%20Highlighting). Search for *Dockerfile Syntax Highlighting* -### TextMate +### TextMate 2 -*...unknown. Probably put it somewhere smart.* +Copy the directory `Docker.tmbundle` (showed as a Package in OSX) to `~/Library/Application Support/TextMate/Managed/Bundles` enjoy. From f787bec4a6853b46b1ba755d4f8a2ee07d9cbd3e Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Fri, 7 Feb 2014 15:21:33 +1000 Subject: [PATCH 35/64] please, for the love of Docker, do. not. use. symlinks Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) --- docs/sources/reference/commandline/cli.rst | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index ae77080309..5f4f821e12 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -102,12 +102,17 @@ the ``-H`` flag for the client. docker ps # both are equal - To run the daemon with `systemd socket activation `_, use ``docker -d -H fd://``. Using ``fd://`` will work perfectly for most setups but you can also specify individual sockets too ``docker -d -H fd://3``. If the specified socket activated files aren't found then docker will exit. You can find examples of using systemd socket activation with docker and systemd in the `docker source tree `_. +.. warning:: + Docker and LXC do not support the use of softlinks for either the Docker data directory (``/var/lib/docker``) or for ``/tmp``. + If your system is likely to be set up in that way, you can use ``readlink -f`` to canonicalise the links: + + ``TMPDIR=$(readlink -f /tmp) /usr/local/bin/docker -d -D -g $(readlink -f /var/lib/docker) -H unix:// $EXPOSE_ALL > /var/lib/boot2docker/docker.log 2>&1`` + .. _cli_attach: ``attach`` From 7c06d5e34e2ebf5006ce3a34438f18c071153e97 Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Fri, 7 Feb 2014 19:09:52 -0800 Subject: [PATCH 36/64] Remove panic in lxc driver. Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- execdriver/lxc/driver.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/execdriver/lxc/driver.go b/execdriver/lxc/driver.go index 4c3979e718..ee4d02a6b6 100644 --- a/execdriver/lxc/driver.go +++ b/execdriver/lxc/driver.go @@ -279,7 +279,8 @@ func (i *info) IsRunning() bool { output, err := i.driver.getInfo(i.ID) if err != nil { - panic(err) + utils.Errorf("Error getting info for lxc container %s: %s (%s)", i.ID, err, output) + return false } if strings.Contains(string(output), "RUNNING") { running = true From 695719b29a6311a56faae0c6ed3c985b7a75add0 Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Sun, 9 Feb 2014 07:25:34 +0100 Subject: [PATCH 37/64] devmapper: Enable skip_block_zeroing This makes the device mapper not zero out blocks allocated on the thinp device. This is safe in our use case, as we access the device via a filesystem that doesn't leak any uninitialized data to userspace. This partially helps with https://github.com/dotcloud/docker/issues/3280 and should generally improve preformance on the devicemapper backend. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- graphdriver/devmapper/devmapper.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graphdriver/devmapper/devmapper.go b/graphdriver/devmapper/devmapper.go index 7f83a09df9..7317118dcf 100644 --- a/graphdriver/devmapper/devmapper.go +++ b/graphdriver/devmapper/devmapper.go @@ -324,7 +324,7 @@ func createPool(poolName string, dataFile, metadataFile *osFile) error { return fmt.Errorf("Can't get data size") } - params := metadataFile.Name() + " " + dataFile.Name() + " 128 32768" + params := metadataFile.Name() + " " + dataFile.Name() + " 128 32768 1 skip_block_zeroing" if err := task.AddTarget(0, size/512, "thin-pool", params); err != nil { return fmt.Errorf("Can't add target") } From 0cba91e26447e14260f111bef6d3745d2dd55444 Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Sun, 9 Feb 2014 01:43:46 -0800 Subject: [PATCH 38/64] Add bidirectional iptables rule back to links Fixes #4014 Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- networkdriver/lxc/driver.go | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/networkdriver/lxc/driver.go b/networkdriver/lxc/driver.go index 3f9c0af011..0bc24b91b6 100644 --- a/networkdriver/lxc/driver.go +++ b/networkdriver/lxc/driver.go @@ -172,7 +172,6 @@ func setupIPTables(addr net.Addr, icc bool) error { iptables.Raw(append([]string{"-D"}, acceptArgs...)...) if !iptables.Exists(dropArgs...) { - utils.Debugf("Disable inter-container communication") if output, err := iptables.Raw(append([]string{"-I"}, dropArgs...)...); err != nil { return fmt.Errorf("Unable to prevent intercontainer communication: %s", err) @@ -470,6 +469,20 @@ func LinkContainers(job *engine.Job) engine.Status { job.Errorf("Error toggle iptables forward: %s", output) return engine.StatusErr } + + if output, err := iptables.Raw(action, "FORWARD", + "-i", bridgeIface, "-o", bridgeIface, + "-p", proto, + "-s", childIP, + "--sport", port, + "-d", parentIP, + "-j", "ACCEPT"); !ignoreErrors && err != nil { + job.Error(err) + return engine.StatusErr + } else if len(output) != 0 { + job.Errorf("Error toggle iptables forward: %s", output) + return engine.StatusErr + } } return engine.StatusOK } From 3dfc910d7774d57c533b067fbe59d6b24dd803cd Mon Sep 17 00:00:00 2001 From: "Guillaume J. Charmes" Date: Thu, 6 Feb 2014 14:13:03 -0800 Subject: [PATCH 39/64] Remove linux specific calls Docker-DCO-1.1-Signed-off-by: Guillaume J. Charmes (github: creack) --- archive/archive.go | 25 +++++++++++-------- archive/stat_linux.go | 7 ++++++ .../{stat_darwin.go => stat_unsupported.go} | 8 ++++-- 3 files changed, 28 insertions(+), 12 deletions(-) rename archive/{stat_darwin.go => stat_unsupported.go} (67%) diff --git a/archive/archive.go b/archive/archive.go index b1400c2210..3a1c111ea2 100644 --- a/archive/archive.go +++ b/archive/archive.go @@ -5,6 +5,7 @@ import ( "bytes" "compress/bzip2" "compress/gzip" + "errors" "fmt" "github.com/dotcloud/docker/utils" "io" @@ -17,14 +18,18 @@ import ( "syscall" ) -type Archive io.Reader +type ( + Archive io.Reader + Compression int + TarOptions struct { + Includes []string + Compression Compression + } +) -type Compression int - -type TarOptions struct { - Includes []string - Compression Compression -} +var ( + ErrNotImplemented = errors.New("Function not implemented") +) const ( Uncompressed Compression = iota @@ -236,14 +241,14 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader *tar.Reader) return fmt.Errorf("Unhandled tar header type %d\n", hdr.Typeflag) } - if err := syscall.Lchown(path, hdr.Uid, hdr.Gid); err != nil { + if err := os.Lchown(path, hdr.Uid, hdr.Gid); err != nil { return err } // There is no LChmod, so ignore mode for symlink. Also, this // must happen after chown, as that can modify the file mode if hdr.Typeflag != tar.TypeSymlink { - if err := syscall.Chmod(path, uint32(hdr.Mode&07777)); err != nil { + if err := os.Chmod(path, os.FileMode(hdr.Mode&07777)); err != nil { return err } } @@ -251,7 +256,7 @@ func createTarFile(path, extractDir string, hdr *tar.Header, reader *tar.Reader) ts := []syscall.Timespec{timeToTimespec(hdr.AccessTime), timeToTimespec(hdr.ModTime)} // syscall.UtimesNano doesn't support a NOFOLLOW flag atm, and if hdr.Typeflag != tar.TypeSymlink { - if err := syscall.UtimesNano(path, ts); err != nil { + if err := UtimesNano(path, ts); err != nil { return err } } else { diff --git a/archive/stat_linux.go b/archive/stat_linux.go index 2f7a520ccd..f87a99c55a 100644 --- a/archive/stat_linux.go +++ b/archive/stat_linux.go @@ -30,3 +30,10 @@ func LUtimesNano(path string, ts []syscall.Timespec) error { return nil } + +func UtimesNano(path string, ts []syscall.Timespec) error { + if err := syscall.UtimesNano(path, ts); err != nil { + return err + } + return nil +} diff --git a/archive/stat_darwin.go b/archive/stat_unsupported.go similarity index 67% rename from archive/stat_darwin.go rename to archive/stat_unsupported.go index 32203299dd..50ca461867 100644 --- a/archive/stat_darwin.go +++ b/archive/stat_unsupported.go @@ -1,4 +1,4 @@ -// +build !linux !amd64 +// +build !linux package archive @@ -13,5 +13,9 @@ func getLastModification(stat *syscall.Stat_t) syscall.Timespec { } func LUtimesNano(path string, ts []syscall.Timespec) error { - return nil + return ErrNotImplemented +} + +func UtimesNano(path string, ts []syscall.Timespec) error { + return ErrNotImplemented } From d3d85d38fb5bdd0327fdc9fe8b637cb0f4e1dcf2 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Sun, 9 Feb 2014 18:21:01 -0700 Subject: [PATCH 40/64] Add slightly better GOPATH detection/handling This also adds a new "AUTO_GOPATH" environment variable that will create an appropriate GOPATH as part of the build process. Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- hack/make.sh | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/hack/make.sh b/hack/make.sh index ef13c1a283..d73b33a4fa 100755 --- a/hack/make.sh +++ b/hack/make.sh @@ -68,6 +68,19 @@ else exit 1 fi +if [ "$AUTO_GOPATH" ]; then + rm -rf .gopath + mkdir -p .gopath/src/github.com/dotcloud + ln -sf ../../../.. .gopath/src/github.com/dotcloud/docker + export GOPATH="$(pwd)/.gopath:$(pwd)/vendor" +fi + +if [ ! "$GOPATH" ]; then + echo >&2 'error: missing GOPATH; please see http://golang.org/doc/code.html#GOPATH' + echo >&2 ' alternatively, set AUTO_GOPATH=1' + exit 1 +fi + # Use these flags when compiling the tests and final binary LDFLAGS='-X main.GITCOMMIT "'$GITCOMMIT'" -X main.VERSION "'$VERSION'" -w' LDFLAGS_STATIC='-X github.com/dotcloud/docker/utils.IAMSTATIC true -linkmode external -extldflags "-lpthread -static -Wl,--unresolved-symbols=ignore-in-object-files"' From 44821158409d59024173336188e087c605e1da1a Mon Sep 17 00:00:00 2001 From: Michael Crosby Date: Mon, 10 Feb 2014 11:04:24 -0800 Subject: [PATCH 41/64] Improve no command handling Docker-DCO-1.1-Signed-off-by: Michael Crosby (github: crosbymichael) --- runtime.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/runtime.go b/runtime.go index 7e4ae79b40..a16238e200 100644 --- a/runtime.go +++ b/runtime.go @@ -378,9 +378,7 @@ func (runtime *Runtime) Create(config *Config, name string) (*Container, []strin } } - if len(config.Entrypoint) != 0 && config.Cmd == nil { - config.Cmd = []string{} - } else if config.Cmd == nil || len(config.Cmd) == 0 { + if len(config.Entrypoint) == 0 && len(config.Cmd) == 0 { return nil, nil, fmt.Errorf("No command specified") } From 10d57b648fe77fe34642d0af6c5c010ff1180c85 Mon Sep 17 00:00:00 2001 From: "O.S.Tezer" Date: Sun, 9 Feb 2014 14:40:31 +0200 Subject: [PATCH 42/64] docs: Installation Instruction Title & Description Fixes Docker-DCO-1.1-Signed-off-by: O.S. Tezer (github: ostezer) 1. All titles are listed by simple platform names apart from Windows' "Installing Docker On Windows". Changed this to "Windows" to match the rest. 2. Some articles' description (and title) information does not match the majority. Modified them to match the rest (i.e. Please note this project is currently under heavy development. It should not be used in production.) 3. Removed "Linux" from Gentoo & Ubuntu descriptions. --- docs/sources/installation/amazon.rst | 2 +- docs/sources/installation/archlinux.rst | 2 +- docs/sources/installation/fedora.rst | 2 +- docs/sources/installation/frugalware.rst | 2 +- docs/sources/installation/gentoolinux.rst | 4 ++-- docs/sources/installation/mac.rst | 2 +- docs/sources/installation/openSUSE.rst | 2 +- docs/sources/installation/rackspace.rst | 4 ++-- docs/sources/installation/rhel.rst | 2 +- docs/sources/installation/ubuntulinux.rst | 2 +- docs/sources/installation/windows.rst | 8 ++++---- 11 files changed, 16 insertions(+), 16 deletions(-) diff --git a/docs/sources/installation/amazon.rst b/docs/sources/installation/amazon.rst index e8fdc2c1ca..31090a070c 100644 --- a/docs/sources/installation/amazon.rst +++ b/docs/sources/installation/amazon.rst @@ -1,5 +1,5 @@ :title: Installation on Amazon EC2 -:description: Docker installation on Amazon EC2 +:description: Please note this project is currently under heavy development. It should not be used in production. :keywords: amazon ec2, virtualization, cloud, docker, documentation, installation Amazon EC2 diff --git a/docs/sources/installation/archlinux.rst b/docs/sources/installation/archlinux.rst index 2d823bfd46..c9b4c1d2c5 100644 --- a/docs/sources/installation/archlinux.rst +++ b/docs/sources/installation/archlinux.rst @@ -1,5 +1,5 @@ :title: Installation on Arch Linux -:description: Docker installation on Arch Linux. +:description: Please note this project is currently under heavy development. It should not be used in production. :keywords: arch linux, virtualization, docker, documentation, installation .. _arch_linux: diff --git a/docs/sources/installation/fedora.rst b/docs/sources/installation/fedora.rst index 6dd2bf91d9..7e0aee78fd 100644 --- a/docs/sources/installation/fedora.rst +++ b/docs/sources/installation/fedora.rst @@ -1,4 +1,4 @@ -:title: Requirements and Installation on Fedora +:title: Installation on Fedora :description: Please note this project is currently under heavy development. It should not be used in production. :keywords: Docker, Docker documentation, Fedora, requirements, virtualbox, vagrant, git, ssh, putty, cygwin, linux diff --git a/docs/sources/installation/frugalware.rst b/docs/sources/installation/frugalware.rst index de2b92ae10..ed9bb2bfaa 100644 --- a/docs/sources/installation/frugalware.rst +++ b/docs/sources/installation/frugalware.rst @@ -1,5 +1,5 @@ :title: Installation on FrugalWare -:description: Docker installation on FrugalWare. +:description: Please note this project is currently under heavy development. It should not be used in production. :keywords: frugalware linux, virtualization, docker, documentation, installation .. _frugalware: diff --git a/docs/sources/installation/gentoolinux.rst b/docs/sources/installation/gentoolinux.rst index 421af0a1e7..5abfddeb91 100644 --- a/docs/sources/installation/gentoolinux.rst +++ b/docs/sources/installation/gentoolinux.rst @@ -1,5 +1,5 @@ -:title: Installation on Gentoo Linux -:description: Docker installation instructions and nuances for Gentoo Linux. +:title: Installation on Gentoo +:description: Please note this project is currently under heavy development. It should not be used in production. :keywords: gentoo linux, virtualization, docker, documentation, installation .. _gentoo_linux: diff --git a/docs/sources/installation/mac.rst b/docs/sources/installation/mac.rst index c247c4aba2..3cf1c93a15 100644 --- a/docs/sources/installation/mac.rst +++ b/docs/sources/installation/mac.rst @@ -1,4 +1,4 @@ -:title: Requirements and Installation on Mac OS X 10.6 Snow Leopard +:title: Installation on Mac OS X 10.6 Snow Leopard :description: Please note this project is currently under heavy development. It should not be used in production. :keywords: Docker, Docker documentation, requirements, virtualbox, ssh, linux, os x, osx, mac diff --git a/docs/sources/installation/openSUSE.rst b/docs/sources/installation/openSUSE.rst index ded5de44a4..c791beacbf 100644 --- a/docs/sources/installation/openSUSE.rst +++ b/docs/sources/installation/openSUSE.rst @@ -1,5 +1,5 @@ :title: Installation on openSUSE -:description: Docker installation on openSUSE. +:description: Please note this project is currently under heavy development. It should not be used in production. :keywords: openSUSE, virtualbox, docker, documentation, installation .. _openSUSE: diff --git a/docs/sources/installation/rackspace.rst b/docs/sources/installation/rackspace.rst index d0005a14bc..687131a413 100644 --- a/docs/sources/installation/rackspace.rst +++ b/docs/sources/installation/rackspace.rst @@ -1,5 +1,5 @@ -:title: Rackspace Cloud Installation -:description: Installing Docker on Ubuntu proviced by Rackspace +:title: Installation on Rackspace Cloud +:description: Please note this project is currently under heavy development. It should not be used in production. :keywords: Rackspace Cloud, installation, docker, linux, ubuntu Rackspace Cloud diff --git a/docs/sources/installation/rhel.rst b/docs/sources/installation/rhel.rst index 9036fb79ea..7930da6309 100644 --- a/docs/sources/installation/rhel.rst +++ b/docs/sources/installation/rhel.rst @@ -1,4 +1,4 @@ -:title: Requirements and Installation on Red Hat Enterprise Linux +:title: Installation on Red Hat Enterprise Linux :description: Please note this project is currently under heavy development. It should not be used in production. :keywords: Docker, Docker documentation, requirements, linux, rhel, centos diff --git a/docs/sources/installation/ubuntulinux.rst b/docs/sources/installation/ubuntulinux.rst index 3d6ee6415d..f37be90d7d 100644 --- a/docs/sources/installation/ubuntulinux.rst +++ b/docs/sources/installation/ubuntulinux.rst @@ -1,4 +1,4 @@ -:title: Requirements and Installation on Ubuntu Linux +:title: Installation on Ubuntu :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 diff --git a/docs/sources/installation/windows.rst b/docs/sources/installation/windows.rst index c980a32df9..9d965ea3fe 100644 --- a/docs/sources/installation/windows.rst +++ b/docs/sources/installation/windows.rst @@ -1,11 +1,11 @@ -:title: Requirements and Installation on Windows -:description: Docker's tutorial to run docker on Windows +:title: Installation on Windows +:description: Please note this project is currently under heavy development. It should not be used in production. :keywords: Docker, Docker documentation, Windows, requirements, virtualbox, vagrant, git, ssh, putty, cygwin .. _windows: -Installing Docker on Windows -============================ +Windows +======= Docker can run on Windows using a VM like VirtualBox. You then run Linux within the VM. From 5d29749e9dba4d918fc5ed5d6049f397e98967cf Mon Sep 17 00:00:00 2001 From: Victor Vieux Date: Mon, 10 Feb 2014 23:52:15 +0000 Subject: [PATCH 43/64] fix --run in docker commit Docker-DCO-1.1-Signed-off-by: Victor Vieux (github: vieux) --- api/api.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/api/api.go b/api/api.go index 7c229eead0..3bc8a8b504 100644 --- a/api/api.go +++ b/api/api.go @@ -367,7 +367,7 @@ func postCommit(eng *engine.Engine, version float64, w http.ResponseWriter, r *h env engine.Env job = eng.Job("commit", r.Form.Get("container")) ) - if err := config.Import(r.Body); err != nil { + if err := config.Decode(r.Body); err != nil { utils.Errorf("%s", err) } From 5051b8610d7802db2d479cfba14009552a95789f Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Tue, 11 Feb 2014 10:10:50 +1000 Subject: [PATCH 44/64] bring back the explaination of our LGTM process Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) --- CONTRIBUTING.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ee40b1d3b2..3c5a57e131 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -109,6 +109,18 @@ name and email address match your git configuration. The AUTHORS file is regenerated occasionally from the git commit history, so a mismatch may result in your changes being overwritten. +### Merge approval + +Docker maintainers use LGTM (looks good to me) in comments on the code review +to indicate acceptance. + +A change requires LGTMs from an absolute majority of the maintainers of each +component affected. For example, if a change affects docs/ and registry/, it +needs an absolute majority from the maintainers of docs/ AND, separately, an +absolute majority of the maintainers of registry + +For more details see [MAINTAINERS.md](hack/MAINTAINERS.md) + ### Sign your work The sign-off is a simple line at the end of the explanation for the From c4b9e1c9add2ab3c97cc89c8f6b12b83750cea1d Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Mon, 10 Feb 2014 16:10:15 +1000 Subject: [PATCH 45/64] point out that ENV DEBIAN_FRONTEND will persist, so its not recommended Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) --- docs/sources/reference/builder.rst | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/docs/sources/reference/builder.rst b/docs/sources/reference/builder.rst index 571824c36c..4b6a151006 100644 --- a/docs/sources/reference/builder.rst +++ b/docs/sources/reference/builder.rst @@ -251,9 +251,14 @@ value ````. This value will be passed to all future ``RUN`` instructions. This is functionally equivalent to prefixing the command with ``=`` +The environment variables set using ``ENV`` will persist when a container is run +from the resulting image. You can view the values using ``docker inspect``, and change them using ``docker run --env =``. + .. note:: - The environment variables will persist when a container is run - from the resulting image. + One example where this can cause unexpected consequenses, is setting + ``ENV DEBIAN_FRONTEND noninteractive``. + Which will persist when the container is run interactively; for example: + ``docker run -t -i image bash`` .. _dockerfile_add: From 82863aecab824118e223b53fed905ed5fd9182bb Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Tue, 11 Feb 2014 14:58:42 +1000 Subject: [PATCH 46/64] add a little info on upgrading Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) --- docs/sources/installation/mac.rst | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/sources/installation/mac.rst b/docs/sources/installation/mac.rst index 4f0550ad3d..5381842432 100644 --- a/docs/sources/installation/mac.rst +++ b/docs/sources/installation/mac.rst @@ -147,6 +147,18 @@ If SSH complains about keys: ssh-keygen -R '[localhost]:2022' +Upgrading to a newer release of boot2docker +------------------------------------------- + +To upgrade an initialised VM, you can use the following 3 commands. Your persistence +disk will not be changed, so you won't lose your images and containers: + +.. code-block:: bash + + ./boot2docker stop + ./boot2docker download + ./boot2docker start + About the way Docker works on Mac OS X: --------------------------------------- From 2343fe44533f19ebae5e6127f4a2a19d1d8773fa Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 11 Feb 2014 09:40:13 +0100 Subject: [PATCH 47/64] devmapper: Remove directory when removing devicemapper device We're currently leaving around lots of empty directories in /var/lib/docker/devicemapper/mnt/ for removed images and containers. Fix this by removing the directory when the device is removed. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- graphdriver/devmapper/driver.go | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/graphdriver/devmapper/driver.go b/graphdriver/devmapper/driver.go index 25b998e59f..4d414f9a75 100644 --- a/graphdriver/devmapper/driver.go +++ b/graphdriver/devmapper/driver.go @@ -7,6 +7,7 @@ import ( "github.com/dotcloud/docker/graphdriver" "github.com/dotcloud/docker/utils" "io/ioutil" + "os" "path" ) @@ -94,7 +95,16 @@ func (d *Driver) Remove(id string) error { return err } // This assumes the device has been properly Get/Put:ed and thus is unmounted - return d.DeviceSet.DeleteDevice(id) + if err := d.DeviceSet.DeleteDevice(id); err != nil { + return err + } + + mp := path.Join(d.home, "mnt", id) + if err := os.RemoveAll(mp); err != nil && !os.IsNotExist(err) { + return err + } + + return nil } func (d *Driver) Get(id string) (string, error) { From 02fddffd51da782f912e2709ea814c330269515b Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 11 Feb 2014 11:10:55 +0100 Subject: [PATCH 48/64] lxc: Drop NET_ADMIN capability in non-privileged containers With this capability set the container can e.g. change the ip address of his devices to that of another container on the docker0 bridge. In a quick test I was able to listen to a port on a different ip than the one docker assigned me, but was not able to hijack an open port redirection that another container had open. Maybe its possible with some more knowledge of networking though. Anyway, network setup is meant to be handled by docker, not the apps, so I believe denying this is generally in the spirit of docker, and it closes down potential security issues. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- execdriver/lxc/init.go | 1 + 1 file changed, 1 insertion(+) diff --git a/execdriver/lxc/init.go b/execdriver/lxc/init.go index 7c2b039c50..0a7746dd15 100644 --- a/execdriver/lxc/init.go +++ b/execdriver/lxc/init.go @@ -127,6 +127,7 @@ func setupCapabilities(args *execdriver.InitArgs) error { capability.CAP_AUDIT_CONTROL, capability.CAP_MAC_OVERRIDE, capability.CAP_MAC_ADMIN, + capability.CAP_NET_ADMIN, } c, err := capability.NewPid(os.Getpid()) From b2146cb0e7a23553d1520ed87837a1b5455c9130 Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Tue, 11 Feb 2014 19:53:45 +1000 Subject: [PATCH 49/64] add a little more information about the docker run -P option Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) --- docs/sources/reference/commandline/cli.rst | 4 ++++ docs/sources/use/port_redirection.rst | 9 +++++++++ 2 files changed, 13 insertions(+) diff --git a/docs/sources/reference/commandline/cli.rst b/docs/sources/reference/commandline/cli.rst index 5f4f821e12..18cef685aa 100644 --- a/docs/sources/reference/commandline/cli.rst +++ b/docs/sources/reference/commandline/cli.rst @@ -1088,6 +1088,10 @@ is, ``docker run`` is equivalent to the API ``/containers/create`` then The ``docker run`` command can be used in combination with ``docker commit`` to :ref:`change the command that a container runs `. +See :ref:`port_redirection` for more detailed information about the ``--expose``, +``-p``, ``-P`` and ``--link`` parameters, and :ref:`working_with_links_names` for +specific examples using ``--link``. + Known Issues (run -volumes-from) ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/docs/sources/use/port_redirection.rst b/docs/sources/use/port_redirection.rst index 5cddb238e4..2f4fd37c76 100644 --- a/docs/sources/use/port_redirection.rst +++ b/docs/sources/use/port_redirection.rst @@ -31,6 +31,15 @@ container, Docker provide ways to bind the container port to an interface of the host system. To simplify communication between containers, Docker provides the linking mechanism. +Auto map all exposed ports on the host +------------------------------------- + +To bind all the exposed container ports to the host automatically, use +``docker run -P ``. The mapped host ports will be auto-selected +from a pool of unused ports (49000..49900), and you will need to use +``docker ps``, ``docker inspect `` or +``docker port `` to determine what they are. + Binding a port to a host interface ----------------------------------- From 0c71015dcf788c5342bb9422a447b7f9ad12d43d Mon Sep 17 00:00:00 2001 From: Alexander Larsson Date: Tue, 11 Feb 2014 18:02:30 +0100 Subject: [PATCH 50/64] Avoid extra mount/unmount during container registration Runtime.Register() called driver.Get()/Put() in order to read back the basefs of the container. However, this is not needed, as the basefs is read during container.Mount() anyway, and basefs is only valid while mounted (and all current calls satisfy this). This seems minor, but this is actually problematic, as the Get/Put pair will create a spurious mount/unmount cycle that is not needed and slows things down. Additionally it will create a supurious devicemapper activate/deactivate cycle that causes races with udev as seen in https://github.com/dotcloud/docker/issues/4036. With this change devicemapper is now race-free, and container startup is slightly faster. Docker-DCO-1.1-Signed-off-by: Alexander Larsson (github: alexlarsson) --- runtime.go | 8 -------- 1 file changed, 8 deletions(-) diff --git a/runtime.go b/runtime.go index a16238e200..176f51b0b1 100644 --- a/runtime.go +++ b/runtime.go @@ -133,14 +133,6 @@ func (runtime *Runtime) Register(container *Container) error { return err } - // Get the root filesystem from the driver - basefs, err := runtime.driver.Get(container.ID) - if err != nil { - return fmt.Errorf("Error getting container filesystem %s from driver %s: %s", container.ID, runtime.driver, err) - } - defer runtime.driver.Put(container.ID) - container.basefs = basefs - container.runtime = runtime // Attach to stdout and stderr From d35b03ef8db9d773937133277cc16364a551ab74 Mon Sep 17 00:00:00 2001 From: Vladimir Bulyga Date: Wed, 12 Feb 2014 00:26:19 +0200 Subject: [PATCH 51/64] Update remote_api_client_libraries.rst hello, add please my interface Docker-DCO-1.1-Signed-off-by: Vladimir (github: 13w) --- docs/sources/reference/api/remote_api_client_libraries.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/sources/reference/api/remote_api_client_libraries.rst b/docs/sources/reference/api/remote_api_client_libraries.rst index 45ce8ff9d1..c7ced0055e 100644 --- a/docs/sources/reference/api/remote_api_client_libraries.rst +++ b/docs/sources/reference/api/remote_api_client_libraries.rst @@ -26,6 +26,9 @@ and we will add the libraries here. +----------------------+----------------+--------------------------------------------+----------+ | Javascript | docker-js | https://github.com/dgoujard/docker-js | Active | +----------------------+----------------+--------------------------------------------+----------+ +| Javascript (Angular) | docker-cp | https://github.com/13W/docker-cp | Active | +| **WebUI** | | | | ++----------------------+----------------+--------------------------------------------+----------+ | Javascript (Angular) | dockerui | https://github.com/crosbymichael/dockerui | Active | | **WebUI** | | | | +----------------------+----------------+--------------------------------------------+----------+ From 49b928c0b05852b58881ce8ebdb164db92befd3a Mon Sep 17 00:00:00 2001 From: Cameron Boehmer Date: Sun, 9 Feb 2014 05:05:19 -0800 Subject: [PATCH 52/64] add port forwarding notes for mac/boot2docker docs Docker-DCO-1.1-Signed-off-by: Cameron Boehmer (github: cameronboehmer) --- docs/sources/installation/mac.rst | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/docs/sources/installation/mac.rst b/docs/sources/installation/mac.rst index 4f0550ad3d..5d57c258de 100644 --- a/docs/sources/installation/mac.rst +++ b/docs/sources/installation/mac.rst @@ -49,10 +49,10 @@ Run the following commands to get boot2docker: # Enter the installation directory cd ~/bin - + # Get the file curl https://raw.github.com/steeve/boot2docker/master/boot2docker > boot2docker - + # Mark it executable chmod +x boot2docker @@ -67,13 +67,13 @@ Run the following commands to get it downloaded and set up: # Get the file curl -o docker https://get.docker.io/builds/Darwin/x86_64/docker-latest - + # Mark it executable chmod +x docker # Set the environment variable for the docker daemon export DOCKER_HOST=tcp:// - + # Copy the executable file sudo cp docker /usr/local/bin/ @@ -94,7 +94,7 @@ Inside the ``~/bin`` directory, run the following commands: # Run the VM (the docker daemon) ./boot2docker up - + # To see all available commands: ./boot2docker @@ -116,6 +116,21 @@ client just like any other application. # Git commit (server): c348c04 # Go version (server): go1.2 +Forwarding VM Port Range to Host +------------------------------- + +If we take the port range that docker uses by default with the -P option +(49000-49900), and forward same range from host to vm, we'll be able to interact +with our containers as if they were running locally: + +.. code-block:: bash + + # vm must be powered off + for i in {4900..49900}; do + VBoxManage modifyvm "boot2docker-vm" --natpf1 "tcp-port$i,tcp,,$i,,$i"; + VBoxManage modifyvm "boot2docker-vm" --natpf1 "udp-port$i,udp,,$i,,$i"; + done + SSH-ing The VM -------------- From ae3c7dec3b075a263da8d1e1cc510275b281e799 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Tue, 11 Feb 2014 16:05:45 -0800 Subject: [PATCH 53/64] Move docker version introspection to a sub-package. This facilitates the refactoring of commands.go. Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) --- commands.go | 5 ----- docker/docker.go | 12 +++--------- dockerversion/dockerversion.go | 11 +++++++++++ hack/make.sh | 2 +- version.go | 9 +++++++++ 5 files changed, 24 insertions(+), 15 deletions(-) create mode 100644 dockerversion/dockerversion.go diff --git a/commands.go b/commands.go index cc019f8c10..55cb3e21c7 100644 --- a/commands.go +++ b/commands.go @@ -38,11 +38,6 @@ import ( "time" ) -var ( - GITCOMMIT string - VERSION string -) - var ( ErrConnectionRefused = errors.New("Can't connect to docker daemon. Is 'docker -d' running on this host?") ) diff --git a/docker/docker.go b/docker/docker.go index d92f4d98ea..df99a20450 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -8,17 +8,13 @@ import ( "github.com/dotcloud/docker" "github.com/dotcloud/docker/api" + "github.com/dotcloud/docker/dockerversion" "github.com/dotcloud/docker/engine" flag "github.com/dotcloud/docker/pkg/mflag" "github.com/dotcloud/docker/sysinit" "github.com/dotcloud/docker/utils" ) -var ( - GITCOMMIT string - VERSION string -) - func main() { if selfPath := utils.SelfPath(); selfPath == "/sbin/init" || selfPath == "/.dockerinit" { // Running in init mode @@ -71,8 +67,6 @@ func main() { if *flDebug { os.Setenv("DEBUG", "1") } - docker.GITCOMMIT = GITCOMMIT - docker.VERSION = VERSION if *flDaemon { if flag.NArg() != 0 { flag.Usage() @@ -104,7 +98,7 @@ func main() { job = eng.Job("serveapi", flHosts.GetAll()...) job.SetenvBool("Logging", true) job.SetenvBool("EnableCors", *flEnableCors) - job.Setenv("Version", VERSION) + job.Setenv("Version", dockerversion.VERSION) if err := job.Run(); err != nil { log.Fatal(err) } @@ -126,5 +120,5 @@ func main() { } func showVersion() { - fmt.Printf("Docker version %s, build %s\n", VERSION, GITCOMMIT) + fmt.Printf("Docker version %s, build %s\n", dockerversion.VERSION, dockerversion.GITCOMMIT) } diff --git a/dockerversion/dockerversion.go b/dockerversion/dockerversion.go new file mode 100644 index 0000000000..1872691ed2 --- /dev/null +++ b/dockerversion/dockerversion.go @@ -0,0 +1,11 @@ +package dockerversion + +// FIXME: this should be embedded in the docker/docker.go, +// but we can't because distro policy requires us to +// package a separate dockerinit binary, and that binary needs +// to know its version too. + +var ( + GITCOMMIT string + VERSION string +) diff --git a/hack/make.sh b/hack/make.sh index d73b33a4fa..011b796201 100755 --- a/hack/make.sh +++ b/hack/make.sh @@ -82,7 +82,7 @@ if [ ! "$GOPATH" ]; then fi # Use these flags when compiling the tests and final binary -LDFLAGS='-X main.GITCOMMIT "'$GITCOMMIT'" -X main.VERSION "'$VERSION'" -w' +LDFLAGS='-X github.com/dotcloud/docker/dockerversion.GITCOMMIT "'$GITCOMMIT'" -X github.com/dotcloud/docker/dockerversion.VERSION "'$VERSION'" -w' LDFLAGS_STATIC='-X github.com/dotcloud/docker/utils.IAMSTATIC true -linkmode external -extldflags "-lpthread -static -Wl,--unresolved-symbols=ignore-in-object-files"' BUILDFLAGS='-tags netgo -a' diff --git a/version.go b/version.go index a4288245f7..2eee68c2f2 100644 --- a/version.go +++ b/version.go @@ -1,11 +1,20 @@ package docker import ( + "github.com/dotcloud/docker/dockerversion" "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/utils" "runtime" ) +var ( + // FIXME: this is a convenience indirection to preserve legacy + // code. It can be removed by using dockerversion.VERSION and + // dockerversion.GITCOMMIT directly + GITCOMMIT string = dockerversion.GITCOMMIT + VERSION string = dockerversion.VERSION +) + func init() { engine.Register("version", jobVersion) } From c76a4e34896fd0785cf982372a3713c4ef70754b Mon Sep 17 00:00:00 2001 From: Andy Rothfusz Date: Tue, 11 Feb 2014 16:19:42 -0800 Subject: [PATCH 54/64] fix underline/heading --- docs/sources/installation/mac.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/installation/mac.rst b/docs/sources/installation/mac.rst index 86a01f6bb6..7d2a0b19e8 100644 --- a/docs/sources/installation/mac.rst +++ b/docs/sources/installation/mac.rst @@ -117,7 +117,7 @@ client just like any other application. # Go version (server): go1.2 Forwarding VM Port Range to Host -------------------------------- +-------------------------------- If we take the port range that docker uses by default with the -P option (49000-49900), and forward same range from host to vm, we'll be able to interact From da04f49b383c02ee28c32f948048b9e9a402bb4f Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Tue, 11 Feb 2014 17:26:54 -0700 Subject: [PATCH 55/64] Move even more stuff into dockerversion Also, use it in all the places. :) Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- commands.go | 17 +++++++++-------- dockerinit/dockerinit.go | 5 ----- dockerversion/dockerversion.go | 4 ++++ graph.go | 3 ++- hack/make/dynbinary | 2 +- runtime.go | 3 ++- server.go | 3 ++- utils/utils.go | 13 ++++--------- version.go | 12 ++---------- 9 files changed, 26 insertions(+), 36 deletions(-) diff --git a/commands.go b/commands.go index 55cb3e21c7..651173555e 100644 --- a/commands.go +++ b/commands.go @@ -11,6 +11,7 @@ import ( "github.com/dotcloud/docker/api" "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/auth" + "github.com/dotcloud/docker/dockerversion" "github.com/dotcloud/docker/engine" flag "github.com/dotcloud/docker/pkg/mflag" "github.com/dotcloud/docker/pkg/sysinfo" @@ -383,12 +384,12 @@ func (cli *DockerCli) CmdVersion(args ...string) error { cmd.Usage() return nil } - if VERSION != "" { - fmt.Fprintf(cli.out, "Client version: %s\n", VERSION) + if dockerversion.VERSION != "" { + fmt.Fprintf(cli.out, "Client version: %s\n", dockerversion.VERSION) } fmt.Fprintf(cli.out, "Go version (client): %s\n", runtime.Version()) - if GITCOMMIT != "" { - fmt.Fprintf(cli.out, "Git commit (client): %s\n", GITCOMMIT) + if dockerversion.GITCOMMIT != "" { + fmt.Fprintf(cli.out, "Git commit (client): %s\n", dockerversion.GITCOMMIT) } body, _, err := readBody(cli.call("GET", "/version", nil, false)) @@ -413,7 +414,7 @@ func (cli *DockerCli) CmdVersion(args ...string) error { release := utils.GetReleaseVersion() if release != "" { fmt.Fprintf(cli.out, "Last stable version: %s", release) - if (VERSION != "" || remoteVersion.Exists("Version")) && (strings.Trim(VERSION, "-dev") != release || strings.Trim(remoteVersion.Get("Version"), "-dev") != release) { + if (dockerversion.VERSION != "" || remoteVersion.Exists("Version")) && (strings.Trim(dockerversion.VERSION, "-dev") != release || strings.Trim(remoteVersion.Get("Version"), "-dev") != release) { fmt.Fprintf(cli.out, ", please update docker") } fmt.Fprintf(cli.out, "\n") @@ -2298,7 +2299,7 @@ func (cli *DockerCli) call(method, path string, data interface{}, passAuthInfo b } } } - req.Header.Set("User-Agent", "Docker-Client/"+VERSION) + req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION) req.Host = cli.addr if data != nil { req.Header.Set("Content-Type", "application/json") @@ -2355,7 +2356,7 @@ func (cli *DockerCli) stream(method, path string, in io.Reader, out io.Writer, h if err != nil { return err } - req.Header.Set("User-Agent", "Docker-Client/"+VERSION) + req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION) req.Host = cli.addr if method == "POST" { req.Header.Set("Content-Type", "plain/text") @@ -2419,7 +2420,7 @@ func (cli *DockerCli) hijack(method, path string, setRawTerminal bool, in io.Rea if err != nil { return err } - req.Header.Set("User-Agent", "Docker-Client/"+VERSION) + req.Header.Set("User-Agent", "Docker-Client/"+dockerversion.VERSION) req.Header.Set("Content-Type", "plain/text") req.Host = cli.addr diff --git a/dockerinit/dockerinit.go b/dockerinit/dockerinit.go index 0c363f4ac3..1d0689387a 100644 --- a/dockerinit/dockerinit.go +++ b/dockerinit/dockerinit.go @@ -4,11 +4,6 @@ import ( "github.com/dotcloud/docker/sysinit" ) -var ( - GITCOMMIT string - VERSION string -) - func main() { // Running in init mode sysinit.SysInit() diff --git a/dockerversion/dockerversion.go b/dockerversion/dockerversion.go index 1872691ed2..c130ac2810 100644 --- a/dockerversion/dockerversion.go +++ b/dockerversion/dockerversion.go @@ -8,4 +8,8 @@ package dockerversion var ( GITCOMMIT string VERSION string + + IAMSTATIC bool // whether or not Docker itself was compiled statically via ./hack/make.sh binary + INITSHA1 string // sha1sum of separate static dockerinit, if Docker itself was compiled dynamically via ./hack/make.sh dynbinary + INITPATH string // custom location to search for a valid dockerinit binary (available for packagers as a last resort escape hatch) ) diff --git a/graph.go b/graph.go index 42da42c8af..138c7b8613 100644 --- a/graph.go +++ b/graph.go @@ -3,6 +3,7 @@ package docker import ( "fmt" "github.com/dotcloud/docker/archive" + "github.com/dotcloud/docker/dockerversion" "github.com/dotcloud/docker/graphdriver" "github.com/dotcloud/docker/utils" "io" @@ -130,7 +131,7 @@ func (graph *Graph) Create(layerData archive.Archive, container *Container, comm ID: GenerateID(), Comment: comment, Created: time.Now().UTC(), - DockerVersion: VERSION, + DockerVersion: dockerversion.VERSION, Author: author, Config: config, Architecture: runtime.GOARCH, diff --git a/hack/make/dynbinary b/hack/make/dynbinary index c02094c0c5..7de3a6cb59 100644 --- a/hack/make/dynbinary +++ b/hack/make/dynbinary @@ -12,6 +12,6 @@ export DOCKER_INITSHA1="$(sha1sum $DEST/dockerinit-$VERSION | cut -d' ' -f1)" # exported so that "dyntest" can easily access it later without recalculating it ( - export LDFLAGS_STATIC="-X github.com/dotcloud/docker/utils.INITSHA1 \"$DOCKER_INITSHA1\" -X github.com/dotcloud/docker/utils.INITPATH \"$DOCKER_INITPATH\"" + export LDFLAGS_STATIC="-X github.com/dotcloud/docker/dockerversion.INITSHA1 \"$DOCKER_INITSHA1\" -X github.com/dotcloud/docker/dockerversion.INITPATH \"$DOCKER_INITPATH\"" source "$(dirname "$BASH_SOURCE")/binary" ) diff --git a/runtime.go b/runtime.go index 176f51b0b1..cec5444090 100644 --- a/runtime.go +++ b/runtime.go @@ -4,6 +4,7 @@ import ( "container/list" "fmt" "github.com/dotcloud/docker/archive" + "github.com/dotcloud/docker/dockerversion" "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/execdriver" "github.com/dotcloud/docker/execdriver/chroot" @@ -678,7 +679,7 @@ func NewRuntimeFromDirectory(config *DaemonConfig, eng *engine.Engine) (*Runtime return nil, err } - localCopy := path.Join(config.Root, "init", fmt.Sprintf("dockerinit-%s", VERSION)) + localCopy := path.Join(config.Root, "init", fmt.Sprintf("dockerinit-%s", dockerversion.VERSION)) sysInitPath := utils.DockerInitPath(localCopy) if sysInitPath == "" { return nil, fmt.Errorf("Could not locate dockerinit: This usually means docker was built incorrectly. See http://docs.docker.io/en/latest/contributing/devenvironment for official build instructions.") diff --git a/server.go b/server.go index f108f61740..cb677266e5 100644 --- a/server.go +++ b/server.go @@ -6,6 +6,7 @@ import ( "fmt" "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/auth" + "github.com/dotcloud/docker/dockerversion" "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/pkg/graphdb" "github.com/dotcloud/docker/registry" @@ -827,7 +828,7 @@ func (srv *Server) DockerInfo(job *engine.Job) engine.Status { v.SetInt("NEventsListener", len(srv.events)) v.Set("KernelVersion", kernelVersion) v.Set("IndexServerAddress", auth.IndexServerAddress()) - v.Set("InitSha1", utils.INITSHA1) + v.Set("InitSha1", dockerversion.INITSHA1) v.Set("InitPath", initPath) if _, err := v.WriteTo(job.Stdout); err != nil { return job.Error(err) diff --git a/utils/utils.go b/utils/utils.go index 5caf792549..60952606d5 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -8,6 +8,7 @@ import ( "encoding/json" "errors" "fmt" + "github.com/dotcloud/docker/dockerversion" "index/suffixarray" "io" "io/ioutil" @@ -23,12 +24,6 @@ import ( "time" ) -var ( - IAMSTATIC bool // whether or not Docker itself was compiled statically via ./hack/make.sh binary - INITSHA1 string // sha1sum of separate static dockerinit, if Docker itself was compiled dynamically via ./hack/make.sh dynbinary - INITPATH string // custom location to search for a valid dockerinit binary (available for packagers as a last resort escape hatch) -) - // A common interface to access the Fatal method of // both testing.B and testing.T. type Fataler interface { @@ -201,7 +196,7 @@ func isValidDockerInitPath(target string, selfPath string) bool { // target and if target == "" { return false } - if IAMSTATIC { + if dockerversion.IAMSTATIC { if selfPath == "" { return false } @@ -218,7 +213,7 @@ func isValidDockerInitPath(target string, selfPath string) bool { // target and } return os.SameFile(targetFileInfo, selfPathFileInfo) } - return INITSHA1 != "" && dockerInitSha1(target) == INITSHA1 + return dockerversion.INITSHA1 != "" && dockerInitSha1(target) == dockerversion.INITSHA1 } // Figure out the path of our dockerinit (which may be SelfPath()) @@ -230,7 +225,7 @@ func DockerInitPath(localCopy string) string { } var possibleInits = []string{ localCopy, - INITPATH, + dockerversion.INITPATH, filepath.Join(filepath.Dir(selfPath), "dockerinit"), // FHS 3.0 Draft: "/usr/libexec includes internal binaries that are not intended to be executed directly by users or shell scripts. Applications may use a single subdirectory under /usr/libexec." diff --git a/version.go b/version.go index 2eee68c2f2..88298a16cb 100644 --- a/version.go +++ b/version.go @@ -7,14 +7,6 @@ import ( "runtime" ) -var ( - // FIXME: this is a convenience indirection to preserve legacy - // code. It can be removed by using dockerversion.VERSION and - // dockerversion.GITCOMMIT directly - GITCOMMIT string = dockerversion.GITCOMMIT - VERSION string = dockerversion.VERSION -) - func init() { engine.Register("version", jobVersion) } @@ -31,8 +23,8 @@ func jobVersion(job *engine.Job) engine.Status { // environment. func dockerVersion() *engine.Env { v := &engine.Env{} - v.Set("Version", VERSION) - v.Set("GitCommit", GITCOMMIT) + v.Set("Version", dockerversion.VERSION) + v.Set("GitCommit", dockerversion.GITCOMMIT) v.Set("GoVersion", runtime.Version()) v.Set("Os", runtime.GOOS) v.Set("Arch", runtime.GOARCH) From 2ed2ba4e8caa2d70d57d124a2f3509f400d8187e Mon Sep 17 00:00:00 2001 From: Sven Dowideit Date: Fri, 7 Feb 2014 09:37:10 +1000 Subject: [PATCH 56/64] rewrite the PostgreSQL example using a Dockerfile, and add details to it Docker-DCO-1.1-Signed-off-by: Sven Dowideit (github: SvenDowideit) --- .../examples/postgresql_service.Dockerfile | 53 ++++++ docs/sources/examples/postgresql_service.rst | 179 +++++++----------- 2 files changed, 121 insertions(+), 111 deletions(-) create mode 100644 docs/sources/examples/postgresql_service.Dockerfile diff --git a/docs/sources/examples/postgresql_service.Dockerfile b/docs/sources/examples/postgresql_service.Dockerfile new file mode 100644 index 0000000000..af1423f258 --- /dev/null +++ b/docs/sources/examples/postgresql_service.Dockerfile @@ -0,0 +1,53 @@ +# +# example Dockerfile for http://docs.docker.io/en/latest/examples/postgresql_service/ +# + +FROM ubuntu +MAINTAINER SvenDowideit@docker.com + +# Add the PostgreSQL PGP key to verify their Debian packages. +# It should be the same key as https://www.postgresql.org/media/keys/ACCC4CF8.asc +RUN apt-key adv --keyserver keyserver.ubuntu.com --recv-keys B97B0AFCAA1A47F044F244A07FCC7D46ACCC4CF8 + +# Add PostgreSQL's repository. It contains the most recent stable release +# of PostgreSQL, ``9.3``. +RUN echo "deb http://apt.postgresql.org/pub/repos/apt/ precise-pgdg main" > /etc/apt/sources.list.d/pgdg.list + +# Update the Ubuntu and PostgreSQL repository indexes +RUN apt-get update + +# Install ``python-software-properties``, ``software-properties-common`` and PostgreSQL 9.3 +# There are some warnings (in red) that show up during the build. You can hide +# them by prefixing each apt-get statement with DEBIAN_FRONTEND=noninteractive +RUN apt-get -y -q install python-software-properties software-properties-common +RUN apt-get -y -q install postgresql-9.3 postgresql-client-9.3 postgresql-contrib-9.3 + +# Note: The official Debian and Ubuntu images automatically ``apt-get clean`` +# after each ``apt-get`` + +# Run the rest of the commands as the ``postgres`` user created by the ``postgres-9.3`` package when it was ``apt-get installed`` +USER postgres + +# Create a PostgreSQL role named ``docker`` with ``docker`` as the password and +# then create a database `docker` owned by the ``docker`` role. +# Note: here we use ``&&\`` to run commands one after the other - the ``\`` +# allows the RUN command to span multiple lines. +RUN /etc/init.d/postgresql start &&\ + psql --command "CREATE USER docker WITH SUPERUSER PASSWORD 'docker';" &&\ + createdb -O docker docker + +# Adjust PostgreSQL configuration so that remote connections to the +# database are possible. +RUN echo "host all all 0.0.0.0/0 md5" >> /etc/postgresql/9.3/main/pg_hba.conf + +# And add ``listen_addresses`` to ``/etc/postgresql/9.3/main/postgresql.conf`` +RUN echo "listen_addresses='*'" >> /etc/postgresql/9.3/main/postgresql.conf + +# Expose the PostgreSQL port +EXPOSE 5432 + +# Add VOLUMEs to allow backup of config, logs and databases +VOLUME ["/etc/postgresql", "/var/log/postgresql", "/var/lib/postgresql"] + +# Set the default command to run when starting the container +CMD ["/usr/lib/postgresql/9.3/bin/postgres", "-D", "/var/lib/postgresql/9.3/main", "-c", "config_file=/etc/postgresql/9.3/main/postgresql.conf"] diff --git a/docs/sources/examples/postgresql_service.rst b/docs/sources/examples/postgresql_service.rst index 1c427563e7..5a2323471b 100644 --- a/docs/sources/examples/postgresql_service.rst +++ b/docs/sources/examples/postgresql_service.rst @@ -9,152 +9,109 @@ PostgreSQL Service .. include:: example_header.inc -.. note:: - - A shorter version of `this blog post`_. - -.. _this blog post: http://zaiste.net/2013/08/docker_postgresql_how_to/ - Installing PostgreSQL on Docker ------------------------------- -Run an interactive shell in a Docker container. +Assuming there is no Docker image that suits your needs in `the index`_, you +can create one yourself. -.. code-block:: bash +.. _the index: http://index.docker.io - sudo docker run -i -t ubuntu /bin/bash - -Update its dependencies. - -.. code-block:: bash - - apt-get update - -Install ``python-software-properties``, ``software-properties-common``, ``wget`` and ``vim``. - -.. code-block:: bash - - apt-get -y install python-software-properties software-properties-common wget vim - -Add PostgreSQL's repository. It contains the most recent stable release -of PostgreSQL, ``9.3``. - -.. code-block:: bash - - wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | apt-key add - - echo "deb http://apt.postgresql.org/pub/repos/apt/ precise-pgdg main" > /etc/apt/sources.list.d/pgdg.list - apt-get update - -Finally, install PostgreSQL 9.3 - -.. code-block:: bash - - apt-get -y install postgresql-9.3 postgresql-client-9.3 postgresql-contrib-9.3 - -Now, create a PostgreSQL superuser role that can create databases and -other roles. Following Vagrant's convention the role will be named -``docker`` with ``docker`` password assigned to it. - -.. code-block:: bash - - su postgres -c "createuser -P -d -r -s docker" - -Create a test database also named ``docker`` owned by previously created ``docker`` -role. - -.. code-block:: bash - - su postgres -c "createdb -O docker docker" - -Adjust PostgreSQL configuration so that remote connections to the -database are possible. Make sure that inside -``/etc/postgresql/9.3/main/pg_hba.conf`` you have following line: - -.. code-block:: bash - - host all all 0.0.0.0/0 md5 - -Additionaly, inside ``/etc/postgresql/9.3/main/postgresql.conf`` -uncomment ``listen_addresses`` like so: - -.. code-block:: bash - - listen_addresses='*' +Start by creating a new Dockerfile: .. note:: This PostgreSQL setup is for development only purposes. Refer - to PostgreSQL documentation how to fine-tune these settings so that it - is secure enough. + to the PostgreSQL documentation to fine-tune these settings so that it + is suitably secure. -Exit. +.. literalinclude:: postgresql_service.Dockerfile + +Build an image from the Dockerfile assign it a name. .. code-block:: bash - exit + $ sudo docker build -t eg_postgresql . -Create an image from our container and assign it a name. The ```` -is in the Bash prompt; you can also locate it using ``docker ps -a``. +And run the PostgreSQL server container (in the foreground): .. code-block:: bash - sudo docker commit /postgresql + $ sudo docker run -rm -P -name pg_test eg_postgresql -Finally, run the PostgreSQL server via ``docker``. +There are 2 ways to connect to the PostgreSQL server. We can use +:ref:`working_with_links_names`, or we can access it from our host (or the network). + +.. note:: The ``-rm`` removes the container and its image when the container + exists successfully. + +Using container linking +^^^^^^^^^^^^^^^^^^^^^^^ + +Containers can be linked to another container's ports directly using +``-link remote_name:local_alias`` in the client's ``docker run``. This will +set a number of environment variables that can then be used to connect: .. code-block:: bash - CONTAINER=$(sudo docker run -d -p 5432 \ - -t /postgresql \ - /bin/su postgres -c '/usr/lib/postgresql/9.3/bin/postgres \ - -D /var/lib/postgresql/9.3/main \ - -c config_file=/etc/postgresql/9.3/main/postgresql.conf') + $ sudo docker run -rm -t -i -link pg_test:pg eg_postgresql bash -Connect the PostgreSQL server using ``psql`` (You will need the -postgresql client installed on the machine. For ubuntu, use something -like ``sudo apt-get install postgresql-client``). + postgres@7ef98b1b7243:/$ psql -h $PG_PORT_5432_TCP_ADDR -p $PG_PORT_5432_TCP_PORT -d docker -U docker --password + +Connecting from your host system +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Assuming you have the postgresql-client installed, you can use the host-mapped port +to test as well. You need to use ``docker ps`` to find out what local host port the +container is mapped to first: .. code-block:: bash - CONTAINER_IP=$(sudo docker inspect -format='{{.NetworkSettings.IPAddress}}' $CONTAINER) - psql -h $CONTAINER_IP -p 5432 -d docker -U docker -W + $ docker ps + CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES + 5e24362f27f6 eg_postgresql:latest /usr/lib/postgresql/ About an hour ago Up About an hour 0.0.0.0:49153->5432/tcp pg_test + $ psql -h localhost -p 49153 -d docker -U docker --password -As before, create roles or databases if needed. +Testing the database +^^^^^^^^^^^^^^^^^^^^ + +Once you have authenticated and have a ``docker =#`` prompt, you can +create a table and populate it. .. code-block:: bash psql (9.3.1) Type "help" for help. - docker=# CREATE DATABASE foo OWNER=docker; - CREATE DATABASE + docker=# CREATE TABLE cities ( + docker(# name varchar(80), + docker(# location point + docker(# ); + CREATE TABLE + docker=# INSERT INTO cities VALUES ('San Francisco', '(-194.0, 53.0)'); + INSERT 0 1 + docker=# select * from cities; + name | location + ---------------+----------- + San Francisco | (-194,53) + (1 row) -Additionally, publish your newly created image on the Docker Index. +Using the container volumes +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +You can use the defined volumes to inspect the PostgreSQL log files and to backup your +configuration and data: .. code-block:: bash - sudo docker login - Username: - [...] + docker run -rm --volumes-from pg_test -t -i busybox sh -.. code-block:: bash + / # ls + bin etc lib linuxrc mnt proc run sys usr + dev home lib64 media opt root sbin tmp var + / # ls /etc/postgresql/9.3/main/ + environment pg_hba.conf postgresql.conf + pg_ctl.conf pg_ident.conf start.conf + /tmp # ls /var/log + ldconfig postgresql - sudo docker push /postgresql - -PostgreSQL service auto-launch ------------------------------- - -Running our image seems complicated. We have to specify the whole command with -``docker run``. Let's simplify it so the service starts automatically when the -container starts. - -.. code-block:: bash - - sudo docker commit -run='{"Cmd": \ - ["/bin/su", "postgres", "-c", "/usr/lib/postgresql/9.3/bin/postgres -D \ - /var/lib/postgresql/9.3/main -c \ - config_file=/etc/postgresql/9.3/main/postgresql.conf"], "PortSpecs": ["5432"]}' \ - /postgresql - -From now on, just type ``docker run /postgresql`` and -PostgreSQL should automatically start. From 3ecd8ff0c80c593e1874cbfa0cc2abf946eeaf66 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Tue, 11 Feb 2014 16:48:44 -0800 Subject: [PATCH 57/64] New package `nat`: utilities for manipulating the text description of network ports. This facilitates the refactoring of commands.go Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) --- commands.go | 9 +- config_test.go | 3 +- container.go | 52 ++------- container_unit_test.go | 7 +- integration/runtime_test.go | 11 +- links.go | 11 +- links_test.go | 11 +- nat/nat.go | 133 ++++++++++++++++++++++++ nat/sort.go | 28 +++++ sorter_unit_test.go => nat/sort_test.go | 6 +- sorter.go | 25 ----- utils.go | 103 ++---------------- 12 files changed, 212 insertions(+), 187 deletions(-) create mode 100644 nat/nat.go create mode 100644 nat/sort.go rename sorter_unit_test.go => nat/sort_test.go (86%) diff --git a/commands.go b/commands.go index cc019f8c10..db8cc1d2f5 100644 --- a/commands.go +++ b/commands.go @@ -12,6 +12,7 @@ import ( "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/auth" "github.com/dotcloud/docker/engine" + "github.com/dotcloud/docker/nat" flag "github.com/dotcloud/docker/pkg/mflag" "github.com/dotcloud/docker/pkg/sysinfo" "github.com/dotcloud/docker/pkg/term" @@ -799,7 +800,7 @@ func (cli *DockerCli) CmdPort(args ...string) error { return err } - if frontends, exists := out.NetworkSettings.Ports[Port(port+"/"+proto)]; exists && frontends != nil { + if frontends, exists := out.NetworkSettings.Ports[nat.Port(port+"/"+proto)]; exists && frontends != nil { for _, frontend := range frontends { fmt.Fprintf(cli.out, "%s:%s\n", frontend.HostIp, frontend.HostPort) } @@ -1792,7 +1793,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf cmd.Var(&flLinks, []string{"#link", "-link"}, "Add link to another container (name:alias)") cmd.Var(&flEnv, []string{"e", "-env"}, "Set environment variables") - cmd.Var(&flPublish, []string{"p", "-publish"}, fmt.Sprintf("Publish a container's port to the host (format: %s) (use 'docker port' to see the actual mapping)", PortSpecTemplateFormat)) + cmd.Var(&flPublish, []string{"p", "-publish"}, fmt.Sprintf("Publish a container's port to the host (format: %s) (use 'docker port' to see the actual mapping)", nat.PortSpecTemplateFormat)) cmd.Var(&flExpose, []string{"#expose", "-expose"}, "Expose a port from the container without publishing it to your host") cmd.Var(&flDns, []string{"#dns", "-dns"}, "Set custom dns servers") cmd.Var(&flVolumesFrom, []string{"#volumes-from", "-volumes-from"}, "Mount volumes from the specified container(s)") @@ -1885,7 +1886,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf domainname = parts[1] } - ports, portBindings, err := parsePortSpecs(flPublish.GetAll()) + ports, portBindings, err := nat.ParsePortSpecs(flPublish.GetAll()) if err != nil { return nil, nil, cmd, err } @@ -1895,7 +1896,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf if strings.Contains(e, ":") { return nil, nil, cmd, fmt.Errorf("Invalid port format for --expose: %s", e) } - p := NewPort(splitProtoPort(e)) + p := nat.NewPort(nat.SplitProtoPort(e)) if _, exists := ports[p]; !exists { ports[p] = struct{}{} } diff --git a/config_test.go b/config_test.go index 31c961135a..1c808163e2 100644 --- a/config_test.go +++ b/config_test.go @@ -1,6 +1,7 @@ package docker import ( + "github.com/dotcloud/docker/nat" "testing" ) @@ -125,7 +126,7 @@ func TestMergeConfig(t *testing.T) { t.Fatalf("Expected VolumesFrom to be 1111, found %s", configUser.VolumesFrom) } - ports, _, err := parsePortSpecs([]string{"0000"}) + ports, _, err := nat.ParsePortSpecs([]string{"0000"}) if err != nil { t.Error(err) } diff --git a/container.go b/container.go index 81e8749d2a..991c2aeee7 100644 --- a/container.go +++ b/container.go @@ -8,6 +8,7 @@ import ( "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/execdriver" "github.com/dotcloud/docker/graphdriver" + "github.com/dotcloud/docker/nat" "github.com/dotcloud/docker/pkg/mount" "github.com/dotcloud/docker/pkg/term" "github.com/dotcloud/docker/utils" @@ -86,7 +87,7 @@ type Config struct { AttachStdout bool AttachStderr bool PortSpecs []string // Deprecated - Can be in the format of 8080/tcp - ExposedPorts map[Port]struct{} + ExposedPorts map[nat.Port]struct{} Tty bool // Attach standard streams to a tty, including stdin if it is not closed. OpenStdin bool // Open stdin StdinOnce bool // If true, close stdin after the 1 attached client disconnects. @@ -147,7 +148,7 @@ type HostConfig struct { ContainerIDFile string LxcConf []KeyValuePair Privileged bool - PortBindings map[Port][]PortBinding + PortBindings nat.PortMap Links []string PublishAllPorts bool } @@ -189,38 +190,7 @@ type KeyValuePair struct { Value string } -type PortBinding struct { - HostIp string - HostPort string -} - -// 80/tcp -type Port string - -func (p Port) Proto() string { - parts := strings.Split(string(p), "/") - if len(parts) == 1 { - return "tcp" - } - return parts[1] -} - -func (p Port) Port() string { - return strings.Split(string(p), "/")[0] -} - -func (p Port) Int() int { - i, err := parsePort(p.Port()) - if err != nil { - panic(err) - } - return i -} - -func NewPort(proto, port string) Port { - return Port(fmt.Sprintf("%s/%s", port, proto)) -} - +// FIXME: move deprecated port stuff to nat to clean up the core. type PortMapping map[string]string // Deprecated type NetworkSettings struct { @@ -229,13 +199,13 @@ type NetworkSettings struct { Gateway string Bridge string PortMapping map[string]PortMapping // Deprecated - Ports map[Port][]PortBinding + Ports nat.PortMap } func (settings *NetworkSettings) PortMappingAPI() *engine.Table { var outs = engine.NewTable("", 0) for port, bindings := range settings.Ports { - p, _ := parsePort(port.Port()) + p, _ := nat.ParsePort(port.Port()) if len(bindings) == 0 { out := &engine.Env{} out.SetInt("PublicPort", p) @@ -245,7 +215,7 @@ func (settings *NetworkSettings) PortMappingAPI() *engine.Table { } for _, binding := range bindings { out := &engine.Env{} - h, _ := parsePort(binding.HostPort) + h, _ := nat.ParsePort(binding.HostPort) out.SetInt("PrivatePort", p) out.SetInt("PublicPort", h) out.Set("Type", port.Proto()) @@ -1152,8 +1122,8 @@ func (container *Container) allocateNetwork() error { } var ( - portSpecs = make(map[Port]struct{}) - bindings = make(map[Port][]PortBinding) + portSpecs = make(nat.PortSet) + bindings = make(nat.PortMap) ) if !container.State.IsGhost() { @@ -1177,7 +1147,7 @@ func (container *Container) allocateNetwork() error { for port := range portSpecs { binding := bindings[port] if container.hostConfig.PublishAllPorts && len(binding) == 0 { - binding = append(binding, PortBinding{}) + binding = append(binding, nat.PortBinding{}) } for i := 0; i < len(binding); i++ { @@ -1593,7 +1563,7 @@ func (container *Container) Copy(resource string) (archive.Archive, error) { } // Returns true if the container exposes a certain port -func (container *Container) Exposes(p Port) bool { +func (container *Container) Exposes(p nat.Port) bool { _, exists := container.Config.ExposedPorts[p] return exists } diff --git a/container_unit_test.go b/container_unit_test.go index 679ff57e73..dd915ad2e4 100644 --- a/container_unit_test.go +++ b/container_unit_test.go @@ -1,6 +1,7 @@ package docker import ( + "github.com/dotcloud/docker/nat" "testing" ) @@ -22,7 +23,7 @@ func TestParseLxcConfOpt(t *testing.T) { } func TestParseNetworkOptsPrivateOnly(t *testing.T) { - ports, bindings, err := parsePortSpecs([]string{"192.168.1.100::80"}) + ports, bindings, err := nat.ParsePortSpecs([]string{"192.168.1.100::80"}) if err != nil { t.Fatal(err) } @@ -64,7 +65,7 @@ func TestParseNetworkOptsPrivateOnly(t *testing.T) { } func TestParseNetworkOptsPublic(t *testing.T) { - ports, bindings, err := parsePortSpecs([]string{"192.168.1.100:8080:80"}) + ports, bindings, err := nat.ParsePortSpecs([]string{"192.168.1.100:8080:80"}) if err != nil { t.Fatal(err) } @@ -106,7 +107,7 @@ func TestParseNetworkOptsPublic(t *testing.T) { } func TestParseNetworkOptsUdp(t *testing.T) { - ports, bindings, err := parsePortSpecs([]string{"192.168.1.100::6000/udp"}) + ports, bindings, err := nat.ParsePortSpecs([]string{"192.168.1.100::6000/udp"}) if err != nil { t.Fatal(err) } diff --git a/integration/runtime_test.go b/integration/runtime_test.go index da95967a30..70dde8f497 100644 --- a/integration/runtime_test.go +++ b/integration/runtime_test.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/dotcloud/docker" "github.com/dotcloud/docker/engine" + "github.com/dotcloud/docker/nat" "github.com/dotcloud/docker/sysinit" "github.com/dotcloud/docker/utils" "io" @@ -368,7 +369,7 @@ func startEchoServerContainer(t *testing.T, proto string) (*docker.Runtime, *doc eng = NewTestEngine(t) runtime = mkRuntimeFromEngine(eng, t) port = 5554 - p docker.Port + p nat.Port ) defer func() { if err != nil { @@ -387,8 +388,8 @@ func startEchoServerContainer(t *testing.T, proto string) (*docker.Runtime, *doc } else { t.Fatal(fmt.Errorf("Unknown protocol %v", proto)) } - ep := make(map[docker.Port]struct{}, 1) - p = docker.Port(fmt.Sprintf("%s/%s", strPort, proto)) + ep := make(map[nat.Port]struct{}, 1) + p = nat.Port(fmt.Sprintf("%s/%s", strPort, proto)) ep[p] = struct{}{} jobCreate := eng.Job("create") @@ -411,8 +412,8 @@ func startEchoServerContainer(t *testing.T, proto string) (*docker.Runtime, *doc } jobStart := eng.Job("start", id) - portBindings := make(map[docker.Port][]docker.PortBinding) - portBindings[p] = []docker.PortBinding{ + portBindings := make(map[nat.Port][]nat.PortBinding) + portBindings[p] = []nat.PortBinding{ {}, } if err := jobStart.SetenvJson("PortsBindings", portBindings); err != nil { diff --git a/links.go b/links.go index aa1c08374b..ff39947a0d 100644 --- a/links.go +++ b/links.go @@ -3,6 +3,7 @@ package docker import ( "fmt" "github.com/dotcloud/docker/engine" + "github.com/dotcloud/docker/nat" "path" "strings" ) @@ -12,7 +13,7 @@ type Link struct { ChildIP string Name string ChildEnvironment []string - Ports []Port + Ports []nat.Port IsEnabled bool eng *engine.Engine } @@ -25,7 +26,7 @@ func NewLink(parent, child *Container, name string, eng *engine.Engine) (*Link, return nil, fmt.Errorf("Cannot link to a non running container: %s AS %s", child.Name, name) } - ports := make([]Port, len(child.Config.ExposedPorts)) + ports := make([]nat.Port, len(child.Config.ExposedPorts)) var i int for p := range child.Config.ExposedPorts { ports[i] = p @@ -85,14 +86,14 @@ func (l *Link) ToEnv() []string { } // Default port rules -func (l *Link) getDefaultPort() *Port { - var p Port +func (l *Link) getDefaultPort() *nat.Port { + var p nat.Port i := len(l.Ports) if i == 0 { return nil } else if i > 1 { - sortPorts(l.Ports, func(ip, jp Port) bool { + nat.Sort(l.Ports, func(ip, jp nat.Port) bool { // If the two ports have the same number, tcp takes priority // Sort in desc order return ip.Int() < jp.Int() || (ip.Int() == jp.Int() && strings.ToLower(ip.Proto()) == "tcp") diff --git a/links_test.go b/links_test.go index 8a266a9a3d..0286d2395b 100644 --- a/links_test.go +++ b/links_test.go @@ -1,6 +1,7 @@ package docker import ( + "github.com/dotcloud/docker/nat" "strings" "testing" ) @@ -22,9 +23,9 @@ func TestLinkNew(t *testing.T) { from := newMockLinkContainer(fromID, "172.0.17.2") from.Config.Env = []string{} from.State = State{Running: true} - ports := make(map[Port]struct{}) + ports := make(nat.PortSet) - ports[Port("6379/tcp")] = struct{}{} + ports[nat.Port("6379/tcp")] = struct{}{} from.Config.ExposedPorts = ports @@ -51,7 +52,7 @@ func TestLinkNew(t *testing.T) { t.Fail() } for _, p := range link.Ports { - if p != Port("6379/tcp") { + if p != nat.Port("6379/tcp") { t.Fail() } } @@ -64,9 +65,9 @@ func TestLinkEnv(t *testing.T) { from := newMockLinkContainer(fromID, "172.0.17.2") from.Config.Env = []string{"PASSWORD=gordon"} from.State = State{Running: true} - ports := make(map[Port]struct{}) + ports := make(nat.PortSet) - ports[Port("6379/tcp")] = struct{}{} + ports[nat.Port("6379/tcp")] = struct{}{} from.Config.ExposedPorts = ports diff --git a/nat/nat.go b/nat/nat.go new file mode 100644 index 0000000000..f3af362f8b --- /dev/null +++ b/nat/nat.go @@ -0,0 +1,133 @@ +package nat + +// nat is a convenience package for docker's manipulation of strings describing +// network ports. + +import ( + "fmt" + "github.com/dotcloud/docker/utils" + "strconv" + "strings" +) + +const ( + PortSpecTemplate = "ip:hostPort:containerPort" + PortSpecTemplateFormat = "ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort" +) + +type PortBinding struct { + HostIp string + HostPort string +} + +type PortMap map[Port][]PortBinding + +type PortSet map[Port]struct{} + +// 80/tcp +type Port string + +func NewPort(proto, port string) Port { + return Port(fmt.Sprintf("%s/%s", port, proto)) +} + +func ParsePort(rawPort string) (int, error) { + port, err := strconv.ParseUint(rawPort, 10, 16) + if err != nil { + return 0, err + } + return int(port), nil +} + +func (p Port) Proto() string { + parts := strings.Split(string(p), "/") + if len(parts) == 1 { + return "tcp" + } + return parts[1] +} + +func (p Port) Port() string { + return strings.Split(string(p), "/")[0] +} + +func (p Port) Int() int { + i, err := ParsePort(p.Port()) + if err != nil { + panic(err) + } + return i +} + +// Splits a port in the format of port/proto +func SplitProtoPort(rawPort string) (string, string) { + parts := strings.Split(rawPort, "/") + l := len(parts) + if l == 0 { + return "", "" + } + if l == 1 { + return "tcp", rawPort + } + return parts[0], parts[1] +} + +// We will receive port specs in the format of ip:public:private/proto and these need to be +// parsed in the internal types +func ParsePortSpecs(ports []string) (map[Port]struct{}, map[Port][]PortBinding, error) { + var ( + exposedPorts = make(map[Port]struct{}, len(ports)) + bindings = make(map[Port][]PortBinding) + ) + + for _, rawPort := range ports { + proto := "tcp" + + if i := strings.LastIndex(rawPort, "/"); i != -1 { + proto = rawPort[i+1:] + rawPort = rawPort[:i] + } + if !strings.Contains(rawPort, ":") { + rawPort = fmt.Sprintf("::%s", rawPort) + } else if len(strings.Split(rawPort, ":")) == 2 { + rawPort = fmt.Sprintf(":%s", rawPort) + } + + parts, err := utils.PartParser(PortSpecTemplate, rawPort) + if err != nil { + return nil, nil, err + } + + var ( + containerPort = parts["containerPort"] + rawIp = parts["ip"] + hostPort = parts["hostPort"] + ) + + if containerPort == "" { + return nil, nil, fmt.Errorf("No port specified: %s", rawPort) + } + if _, err := strconv.ParseUint(containerPort, 10, 16); err != nil { + return nil, nil, fmt.Errorf("Invalid containerPort: %s", containerPort) + } + if _, err := strconv.ParseUint(hostPort, 10, 16); hostPort != "" && err != nil { + return nil, nil, fmt.Errorf("Invalid hostPort: %s", hostPort) + } + + port := NewPort(proto, containerPort) + if _, exists := exposedPorts[port]; !exists { + exposedPorts[port] = struct{}{} + } + + binding := PortBinding{ + HostIp: rawIp, + HostPort: hostPort, + } + bslice, exists := bindings[port] + if !exists { + bslice = []PortBinding{} + } + bindings[port] = append(bslice, binding) + } + return exposedPorts, bindings, nil +} diff --git a/nat/sort.go b/nat/sort.go new file mode 100644 index 0000000000..f36c12f7bb --- /dev/null +++ b/nat/sort.go @@ -0,0 +1,28 @@ +package nat + +import "sort" + +type portSorter struct { + ports []Port + by func(i, j Port) bool +} + +func (s *portSorter) Len() int { + return len(s.ports) +} + +func (s *portSorter) Swap(i, j int) { + s.ports[i], s.ports[j] = s.ports[j], s.ports[i] +} + +func (s *portSorter) Less(i, j int) bool { + ip := s.ports[i] + jp := s.ports[j] + + return s.by(ip, jp) +} + +func Sort(ports []Port, predicate func(i, j Port) bool) { + s := &portSorter{ports, predicate} + sort.Sort(s) +} diff --git a/sorter_unit_test.go b/nat/sort_test.go similarity index 86% rename from sorter_unit_test.go rename to nat/sort_test.go index 0669feedb3..5d490e321b 100644 --- a/sorter_unit_test.go +++ b/nat/sort_test.go @@ -1,4 +1,4 @@ -package docker +package nat import ( "fmt" @@ -11,7 +11,7 @@ func TestSortUniquePorts(t *testing.T) { Port("22/tcp"), } - sortPorts(ports, func(ip, jp Port) bool { + Sort(ports, func(ip, jp Port) bool { return ip.Int() < jp.Int() || (ip.Int() == jp.Int() && ip.Proto() == "tcp") }) @@ -30,7 +30,7 @@ func TestSortSamePortWithDifferentProto(t *testing.T) { Port("6379/udp"), } - sortPorts(ports, func(ip, jp Port) bool { + Sort(ports, func(ip, jp Port) bool { return ip.Int() < jp.Int() || (ip.Int() == jp.Int() && ip.Proto() == "tcp") }) diff --git a/sorter.go b/sorter.go index 9b3e1a9486..b49ac58c24 100644 --- a/sorter.go +++ b/sorter.go @@ -2,31 +2,6 @@ package docker import "sort" -type portSorter struct { - ports []Port - by func(i, j Port) bool -} - -func (s *portSorter) Len() int { - return len(s.ports) -} - -func (s *portSorter) Swap(i, j int) { - s.ports[i], s.ports[j] = s.ports[j], s.ports[i] -} - -func (s *portSorter) Less(i, j int) bool { - ip := s.ports[i] - jp := s.ports[j] - - return s.by(ip, jp) -} - -func sortPorts(ports []Port, predicate func(i, j Port) bool) { - s := &portSorter{ports, predicate} - sort.Sort(s) -} - type containerSorter struct { containers []*Container by func(i, j *Container) bool diff --git a/utils.go b/utils.go index e3ba08d51c..d4718954da 100644 --- a/utils.go +++ b/utils.go @@ -3,10 +3,10 @@ package docker import ( "fmt" "github.com/dotcloud/docker/archive" + "github.com/dotcloud/docker/nat" "github.com/dotcloud/docker/pkg/namesgenerator" "github.com/dotcloud/docker/utils" "io" - "strconv" "strings" "sync/atomic" ) @@ -98,7 +98,7 @@ func MergeConfig(userConf, imageConf *Config) error { userConf.ExposedPorts = imageConf.ExposedPorts } else if imageConf.ExposedPorts != nil { if userConf.ExposedPorts == nil { - userConf.ExposedPorts = make(map[Port]struct{}) + userConf.ExposedPorts = make(nat.PortSet) } for port := range imageConf.ExposedPorts { if _, exists := userConf.ExposedPorts[port]; !exists { @@ -109,9 +109,9 @@ func MergeConfig(userConf, imageConf *Config) error { if userConf.PortSpecs != nil && len(userConf.PortSpecs) > 0 { if userConf.ExposedPorts == nil { - userConf.ExposedPorts = make(map[Port]struct{}) + userConf.ExposedPorts = make(nat.PortSet) } - ports, _, err := parsePortSpecs(userConf.PortSpecs) + ports, _, err := nat.ParsePortSpecs(userConf.PortSpecs) if err != nil { return err } @@ -125,10 +125,10 @@ func MergeConfig(userConf, imageConf *Config) error { if imageConf.PortSpecs != nil && len(imageConf.PortSpecs) > 0 { utils.Debugf("Migrating image port specs to containter: %s", strings.Join(imageConf.PortSpecs, ", ")) if userConf.ExposedPorts == nil { - userConf.ExposedPorts = make(map[Port]struct{}) + userConf.ExposedPorts = make(nat.PortSet) } - ports, _, err := parsePortSpecs(imageConf.PortSpecs) + ports, _, err := nat.ParsePortSpecs(imageConf.PortSpecs) if err != nil { return err } @@ -212,96 +212,9 @@ func parseLxcOpt(opt string) (string, string, error) { return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]), nil } -// FIXME: network related stuff (including parsing) should be grouped in network file -const ( - PortSpecTemplate = "ip:hostPort:containerPort" - PortSpecTemplateFormat = "ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort" -) - -// We will receive port specs in the format of ip:public:private/proto and these need to be -// parsed in the internal types -func parsePortSpecs(ports []string) (map[Port]struct{}, map[Port][]PortBinding, error) { - var ( - exposedPorts = make(map[Port]struct{}, len(ports)) - bindings = make(map[Port][]PortBinding) - ) - - for _, rawPort := range ports { - proto := "tcp" - - if i := strings.LastIndex(rawPort, "/"); i != -1 { - proto = rawPort[i+1:] - rawPort = rawPort[:i] - } - if !strings.Contains(rawPort, ":") { - rawPort = fmt.Sprintf("::%s", rawPort) - } else if len(strings.Split(rawPort, ":")) == 2 { - rawPort = fmt.Sprintf(":%s", rawPort) - } - - parts, err := utils.PartParser(PortSpecTemplate, rawPort) - if err != nil { - return nil, nil, err - } - - var ( - containerPort = parts["containerPort"] - rawIp = parts["ip"] - hostPort = parts["hostPort"] - ) - - if containerPort == "" { - return nil, nil, fmt.Errorf("No port specified: %s", rawPort) - } - if _, err := strconv.ParseUint(containerPort, 10, 16); err != nil { - return nil, nil, fmt.Errorf("Invalid containerPort: %s", containerPort) - } - if _, err := strconv.ParseUint(hostPort, 10, 16); hostPort != "" && err != nil { - return nil, nil, fmt.Errorf("Invalid hostPort: %s", hostPort) - } - - port := NewPort(proto, containerPort) - if _, exists := exposedPorts[port]; !exists { - exposedPorts[port] = struct{}{} - } - - binding := PortBinding{ - HostIp: rawIp, - HostPort: hostPort, - } - bslice, exists := bindings[port] - if !exists { - bslice = []PortBinding{} - } - bindings[port] = append(bslice, binding) - } - return exposedPorts, bindings, nil -} - -// Splits a port in the format of port/proto -func splitProtoPort(rawPort string) (string, string) { - parts := strings.Split(rawPort, "/") - l := len(parts) - if l == 0 { - return "", "" - } - if l == 1 { - return "tcp", rawPort - } - return parts[0], parts[1] -} - -func parsePort(rawPort string) (int, error) { - port, err := strconv.ParseUint(rawPort, 10, 16) - if err != nil { - return 0, err - } - return int(port), nil -} - func migratePortMappings(config *Config, hostConfig *HostConfig) error { if config.PortSpecs != nil { - ports, bindings, err := parsePortSpecs(config.PortSpecs) + ports, bindings, err := nat.ParsePortSpecs(config.PortSpecs) if err != nil { return err } @@ -314,7 +227,7 @@ func migratePortMappings(config *Config, hostConfig *HostConfig) error { } if config.ExposedPorts == nil { - config.ExposedPorts = make(map[Port]struct{}, len(ports)) + config.ExposedPorts = make(nat.PortSet, len(ports)) } for k, v := range ports { config.ExposedPorts[k] = v From 6fd8e5d976e872447447d8c25d20d525305f281d Mon Sep 17 00:00:00 2001 From: Andy Rothfusz Date: Tue, 11 Feb 2014 16:53:08 -0800 Subject: [PATCH 58/64] fix underline/heading Docker-DCO-1.1-Signed-off-by: Andy Rothfusz (github: metalivedev) --- docs/sources/use/port_redirection.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/sources/use/port_redirection.rst b/docs/sources/use/port_redirection.rst index 2f4fd37c76..2612d63aec 100644 --- a/docs/sources/use/port_redirection.rst +++ b/docs/sources/use/port_redirection.rst @@ -32,7 +32,7 @@ interface of the host system. To simplify communication between containers, Docker provides the linking mechanism. Auto map all exposed ports on the host -------------------------------------- +-------------------------------------- To bind all the exposed container ports to the host automatically, use ``docker run -P ``. The mapped host ports will be auto-selected From 7ea725fdc530e989516fae60f5dafecb139d75b8 Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Tue, 11 Feb 2014 18:23:17 -0700 Subject: [PATCH 59/64] Fix the one spot I missed dockerversion Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- hack/make.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hack/make.sh b/hack/make.sh index 011b796201..55409c55fc 100755 --- a/hack/make.sh +++ b/hack/make.sh @@ -83,7 +83,7 @@ fi # Use these flags when compiling the tests and final binary LDFLAGS='-X github.com/dotcloud/docker/dockerversion.GITCOMMIT "'$GITCOMMIT'" -X github.com/dotcloud/docker/dockerversion.VERSION "'$VERSION'" -w' -LDFLAGS_STATIC='-X github.com/dotcloud/docker/utils.IAMSTATIC true -linkmode external -extldflags "-lpthread -static -Wl,--unresolved-symbols=ignore-in-object-files"' +LDFLAGS_STATIC='-X github.com/dotcloud/docker/dockerversion.IAMSTATIC true -linkmode external -extldflags "-lpthread -static -Wl,--unresolved-symbols=ignore-in-object-files"' BUILDFLAGS='-tags netgo -a' HAVE_GO_TEST_COVER= From 0d871840b202fc31418990bbcbe0df1c4ad689fb Mon Sep 17 00:00:00 2001 From: Tianon Gravi Date: Tue, 11 Feb 2014 19:10:23 -0700 Subject: [PATCH 60/64] Fix the tests, too Seriously. There's not much codebase left we haven't touched. Docker-DCO-1.1-Signed-off-by: Andrew Page (github: tianon) --- integration/api_test.go | 3 ++- integration/graph_test.go | 5 +++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/integration/api_test.go b/integration/api_test.go index 82de56a8ba..d3efb75969 100644 --- a/integration/api_test.go +++ b/integration/api_test.go @@ -8,6 +8,7 @@ import ( "fmt" "github.com/dotcloud/docker" "github.com/dotcloud/docker/api" + "github.com/dotcloud/docker/dockerversion" "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/utils" "io" @@ -45,7 +46,7 @@ func TestGetVersion(t *testing.T) { t.Fatal(err) } out.Close() - expected := docker.VERSION + expected := dockerversion.VERSION if result := v.Get("Version"); result != expected { t.Errorf("Expected version %s, %s found", expected, result) } diff --git a/integration/graph_test.go b/integration/graph_test.go index eec4c5c7dc..ff1c0d9361 100644 --- a/integration/graph_test.go +++ b/integration/graph_test.go @@ -4,6 +4,7 @@ import ( "errors" "github.com/dotcloud/docker" "github.com/dotcloud/docker/archive" + "github.com/dotcloud/docker/dockerversion" "github.com/dotcloud/docker/graphdriver" "github.com/dotcloud/docker/utils" "io" @@ -105,8 +106,8 @@ func TestGraphCreate(t *testing.T) { if image.Comment != "Testing" { t.Fatalf("Wrong comment: should be '%s', not '%s'", "Testing", image.Comment) } - if image.DockerVersion != docker.VERSION { - t.Fatalf("Wrong docker_version: should be '%s', not '%s'", docker.VERSION, image.DockerVersion) + if image.DockerVersion != dockerversion.VERSION { + t.Fatalf("Wrong docker_version: should be '%s', not '%s'", dockerversion.VERSION, image.DockerVersion) } images, err := graph.Map() if err != nil { From e08a1c53aa5a19a17d40d41accbab040611c4411 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Tue, 11 Feb 2014 17:48:50 -0800 Subject: [PATCH 61/64] Move api-specific code to the api package This facilitates the refactoring of commands.go. Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) --- api/api.go | 9 +++++++++ docker/docker.go | 2 +- opts.go | 10 ---------- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/api/api.go b/api/api.go index 3bc8a8b504..e0077a94e3 100644 --- a/api/api.go +++ b/api/api.go @@ -27,6 +27,7 @@ import ( "syscall" ) +// FIXME: move code common to client and server to common.go const ( APIVERSION = 1.9 DEFAULTHTTPHOST = "127.0.0.1" @@ -34,6 +35,14 @@ const ( DEFAULTUNIXSOCKET = "/var/run/docker.sock" ) +func ValidateHost(val string) (string, error) { + host, err := utils.ParseHost(DEFAULTHTTPHOST, DEFAULTHTTPPORT, DEFAULTUNIXSOCKET, val) + if err != nil { + return val, err + } + return host, nil +} + type HttpApiFunc func(eng *engine.Engine, version float64, w http.ResponseWriter, r *http.Request, vars map[string]string) error func init() { diff --git a/docker/docker.go b/docker/docker.go index d92f4d98ea..d2fe7c2596 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -42,7 +42,7 @@ func main() { flDefaultIp = flag.String([]string{"#ip", "-ip"}, "0.0.0.0", "Default IP address to use when binding container ports") flInterContainerComm = flag.Bool([]string{"#icc", "-icc"}, true, "Enable inter-container communication") flGraphDriver = flag.String([]string{"s", "-storage-driver"}, "", "Force the docker runtime to use a specific storage driver") - flHosts = docker.NewListOpts(docker.ValidateHost) + flHosts = docker.NewListOpts(api.ValidateHost) flMtu = flag.Int([]string{"#mtu", "-mtu"}, 0, "Set the containers network MTU; if no value is provided: default to the default route MTU or 1500 if not default route is available") ) flag.Var(&flDns, []string{"#dns", "-dns"}, "Force docker to use specific DNS servers") diff --git a/opts.go b/opts.go index b1d71c491d..dffbcb404e 100644 --- a/opts.go +++ b/opts.go @@ -2,8 +2,6 @@ package docker import ( "fmt" - "github.com/dotcloud/docker/api" - "github.com/dotcloud/docker/utils" "os" "path/filepath" "regexp" @@ -129,14 +127,6 @@ func ValidateEnv(val string) (string, error) { return fmt.Sprintf("%s=%s", val, os.Getenv(val)), nil } -func ValidateHost(val string) (string, error) { - host, err := utils.ParseHost(api.DEFAULTHTTPHOST, api.DEFAULTHTTPPORT, api.DEFAULTUNIXSOCKET, val) - if err != nil { - return val, err - } - return host, nil -} - func ValidateIp4Address(val string) (string, error) { re := regexp.MustCompile(`^(([0-9]+\.){3}([0-9]+))\s*$`) var ns = re.FindSubmatch([]byte(val)) From e6e320acc757f22e113da755293583402c6c1e7c Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Tue, 11 Feb 2014 18:46:55 -0800 Subject: [PATCH 62/64] pkg/opts: a collection of custom value parsers implementing flag.Value This facilitates the refactoring of commands.go. Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) --- commands.go | 19 ++++++++++--------- docker/docker.go | 5 +++-- opts.go => pkg/opts/opts.go | 13 ++++++++++++- opts_unit_test.go => pkg/opts/opts_test.go | 2 +- utils.go | 3 ++- 5 files changed, 28 insertions(+), 14 deletions(-) rename opts.go => pkg/opts/opts.go (88%) rename opts_unit_test.go => pkg/opts/opts_test.go (97%) diff --git a/commands.go b/commands.go index db8cc1d2f5..973a41d86a 100644 --- a/commands.go +++ b/commands.go @@ -14,6 +14,7 @@ import ( "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/nat" flag "github.com/dotcloud/docker/pkg/mflag" + "github.com/dotcloud/docker/pkg/opts" "github.com/dotcloud/docker/pkg/sysinfo" "github.com/dotcloud/docker/pkg/term" "github.com/dotcloud/docker/registry" @@ -1757,16 +1758,16 @@ func ParseRun(args []string, sysInfo *sysinfo.SysInfo) (*Config, *HostConfig, *f func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Config, *HostConfig, *flag.FlagSet, error) { var ( // FIXME: use utils.ListOpts for attach and volumes? - flAttach = NewListOpts(ValidateAttach) - flVolumes = NewListOpts(ValidatePath) - flLinks = NewListOpts(ValidateLink) - flEnv = NewListOpts(ValidateEnv) + flAttach = opts.NewListOpts(opts.ValidateAttach) + flVolumes = opts.NewListOpts(opts.ValidatePath) + flLinks = opts.NewListOpts(opts.ValidateLink) + flEnv = opts.NewListOpts(opts.ValidateEnv) - flPublish ListOpts - flExpose ListOpts - flDns ListOpts - flVolumesFrom ListOpts - flLxcOpts ListOpts + flPublish opts.ListOpts + flExpose opts.ListOpts + flDns opts.ListOpts + flVolumesFrom opts.ListOpts + flLxcOpts opts.ListOpts flAutoRemove = cmd.Bool([]string{"#rm", "-rm"}, false, "Automatically remove the container when it exits (incompatible with -d)") flDetach = cmd.Bool([]string{"d", "-detach"}, false, "Detached mode: Run container in the background, print new container id") diff --git a/docker/docker.go b/docker/docker.go index d2fe7c2596..775dfd3533 100644 --- a/docker/docker.go +++ b/docker/docker.go @@ -10,6 +10,7 @@ import ( "github.com/dotcloud/docker/api" "github.com/dotcloud/docker/engine" flag "github.com/dotcloud/docker/pkg/mflag" + "github.com/dotcloud/docker/pkg/opts" "github.com/dotcloud/docker/sysinit" "github.com/dotcloud/docker/utils" ) @@ -36,13 +37,13 @@ func main() { pidfile = flag.String([]string{"p", "-pidfile"}, "/var/run/docker.pid", "Path to use for daemon PID file") flRoot = flag.String([]string{"g", "-graph"}, "/var/lib/docker", "Path to use as the root of the docker runtime") flEnableCors = flag.Bool([]string{"#api-enable-cors", "-api-enable-cors"}, false, "Enable CORS headers in the remote API") - flDns = docker.NewListOpts(docker.ValidateIp4Address) + flDns = opts.NewListOpts(opts.ValidateIp4Address) flEnableIptables = flag.Bool([]string{"#iptables", "-iptables"}, true, "Disable docker's addition of iptables rules") flEnableIpForward = flag.Bool([]string{"#ip-forward", "-ip-forward"}, true, "Disable enabling of net.ipv4.ip_forward") flDefaultIp = flag.String([]string{"#ip", "-ip"}, "0.0.0.0", "Default IP address to use when binding container ports") flInterContainerComm = flag.Bool([]string{"#icc", "-icc"}, true, "Enable inter-container communication") flGraphDriver = flag.String([]string{"s", "-storage-driver"}, "", "Force the docker runtime to use a specific storage driver") - flHosts = docker.NewListOpts(api.ValidateHost) + flHosts = opts.NewListOpts(api.ValidateHost) flMtu = flag.Int([]string{"#mtu", "-mtu"}, 0, "Set the containers network MTU; if no value is provided: default to the default route MTU or 1500 if not default route is available") ) flag.Var(&flDns, []string{"#dns", "-dns"}, "Force docker to use specific DNS servers") diff --git a/opts.go b/pkg/opts/opts.go similarity index 88% rename from opts.go rename to pkg/opts/opts.go index dffbcb404e..a1b8752bad 100644 --- a/opts.go +++ b/pkg/opts/opts.go @@ -1,7 +1,8 @@ -package docker +package opts import ( "fmt" + "github.com/dotcloud/docker/utils" "os" "path/filepath" "regexp" @@ -97,6 +98,16 @@ func ValidateLink(val string) (string, error) { return val, nil } +// FIXME: this is a duplicate of docker.utils.parseLink. +// it can't be moved to a separate links/ package because +// links depends on Container which is defined in the core. +// +// Links come in the format of +// name:alias +func parseLink(rawLink string) (map[string]string, error) { + return utils.PartParser("name:alias", rawLink) +} + func ValidatePath(val string) (string, error) { var containerPath string diff --git a/opts_unit_test.go b/pkg/opts/opts_test.go similarity index 97% rename from opts_unit_test.go rename to pkg/opts/opts_test.go index 67b061771b..a5c1fac9ca 100644 --- a/opts_unit_test.go +++ b/pkg/opts/opts_test.go @@ -1,4 +1,4 @@ -package docker +package opts import ( "testing" diff --git a/utils.go b/utils.go index d4718954da..e3a58cc67e 100644 --- a/utils.go +++ b/utils.go @@ -5,6 +5,7 @@ import ( "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/nat" "github.com/dotcloud/docker/pkg/namesgenerator" + "github.com/dotcloud/docker/pkg/opts" "github.com/dotcloud/docker/utils" "io" "strings" @@ -192,7 +193,7 @@ func MergeConfig(userConf, imageConf *Config) error { return nil } -func parseLxcConfOpts(opts ListOpts) ([]KeyValuePair, error) { +func parseLxcConfOpts(opts opts.ListOpts) ([]KeyValuePair, error) { out := make([]KeyValuePair, opts.Len()) for i, o := range opts.GetAll() { k, v, err := parseLxcOpt(o) From e3140e1e69b24f32ffcc2dd94b7d82ad9305a7bc Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Tue, 11 Feb 2014 20:16:58 -0800 Subject: [PATCH 63/64] Remove useless code in client implementation of 'run'. This facilitates refactoring commands.go. Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) --- commands.go | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/commands.go b/commands.go index 973a41d86a..331e519c8d 100644 --- a/commands.go +++ b/commands.go @@ -1993,12 +1993,7 @@ func (cli *DockerCli) CmdRun(args ...string) error { stream, statusCode, err := cli.call("POST", "/containers/create?"+containerValues.Encode(), config, false) //if image not found try to pull it if statusCode == 404 { - _, tag := utils.ParseRepositoryTag(config.Image) - if tag == "" { - tag = DEFAULTTAG - } - - fmt.Fprintf(cli.err, "Unable to find image '%s' (tag: %s) locally\n", config.Image, tag) + fmt.Fprintf(cli.err, "Unable to find image '%s' locally\n", config.Image) v := url.Values{} repos, tag := utils.ParseRepositoryTag(config.Image) From 6393c38339e11b4a099a460ecf46bb5cafc4283b Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Tue, 11 Feb 2014 20:04:39 -0800 Subject: [PATCH 64/64] Move the canonical run configuration objects to a sub-package * Config is now runconfig.Config * HostConfig is now runconfig.HostConfig * MergeConfig is now runconfig.Merge * CompareConfig is now runconfig.Compare * ParseRun is now runconfig.Parse * ContainerConfigFromJob is now runconfig.ContainerConfigFromJob * ContainerHostConfigFromJob is now runconfig.ContainerHostConfigFromJob This facilitates refactoring commands.go and shrinks the core. Docker-DCO-1.1-Signed-off-by: Solomon Hykes (github: shykes) --- buildfile.go | 11 +- commands.go | 212 +----------------- commands_unit_test.go | 7 +- container.go | 117 +--------- container_unit_test.go | 17 -- graph.go | 3 +- image.go | 23 +- integration/api_test.go | 39 ++-- integration/container_test.go | 78 +++---- integration/runtime_test.go | 33 +-- integration/server_test.go | 21 +- integration/utils_test.go | 9 +- links_test.go | 3 +- runconfig/compare.go | 67 ++++++ runconfig/config.go | 76 +++++++ config_test.go => runconfig/config_test.go | 30 +-- runconfig/hostconfig.go | 39 ++++ runconfig/merge.go | 119 ++++++++++ runconfig/parse.go | 246 +++++++++++++++++++++ runconfig/parse_test.go | 22 ++ runtime.go | 11 +- server.go | 17 +- utils.go | 205 +---------------- 23 files changed, 731 insertions(+), 674 deletions(-) create mode 100644 runconfig/compare.go create mode 100644 runconfig/config.go rename config_test.go => runconfig/config_test.go (83%) create mode 100644 runconfig/hostconfig.go create mode 100644 runconfig/merge.go create mode 100644 runconfig/parse.go create mode 100644 runconfig/parse_test.go diff --git a/buildfile.go b/buildfile.go index 2a4b163bec..a121276c21 100644 --- a/buildfile.go +++ b/buildfile.go @@ -9,6 +9,7 @@ import ( "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/auth" "github.com/dotcloud/docker/registry" + "github.com/dotcloud/docker/runconfig" "github.com/dotcloud/docker/utils" "io" "io/ioutil" @@ -38,7 +39,7 @@ type buildFile struct { image string maintainer string - config *Config + config *runconfig.Config contextPath string context *utils.TarSum @@ -101,7 +102,7 @@ func (b *buildFile) CmdFrom(name string) error { } } b.image = image.ID - b.config = &Config{} + b.config = &runconfig.Config{} if image.Config != nil { b.config = image.Config } @@ -158,14 +159,14 @@ func (b *buildFile) CmdRun(args string) error { if b.image == "" { return fmt.Errorf("Please provide a source image with `from` prior to run") } - config, _, _, err := ParseRun(append([]string{b.image}, b.buildCmdFromJson(args)...), nil) + config, _, _, err := runconfig.Parse(append([]string{b.image}, b.buildCmdFromJson(args)...), nil) if err != nil { return err } cmd := b.config.Cmd b.config.Cmd = nil - MergeConfig(b.config, config) + runconfig.Merge(b.config, config) defer func(cmd []string) { b.config.Cmd = cmd }(cmd) @@ -742,7 +743,7 @@ func NewBuildFile(srv *Server, outStream, errStream io.Writer, verbose, utilizeC return &buildFile{ runtime: srv.runtime, srv: srv, - config: &Config{}, + config: &runconfig.Config{}, outStream: outStream, errStream: errStream, tmpContainers: make(map[string]struct{}), diff --git a/commands.go b/commands.go index ac93a32638..285b7a57b4 100644 --- a/commands.go +++ b/commands.go @@ -15,10 +15,9 @@ import ( "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/nat" flag "github.com/dotcloud/docker/pkg/mflag" - "github.com/dotcloud/docker/pkg/opts" - "github.com/dotcloud/docker/pkg/sysinfo" "github.com/dotcloud/docker/pkg/term" "github.com/dotcloud/docker/registry" + "github.com/dotcloud/docker/runconfig" "github.com/dotcloud/docker/utils" "io" "io/ioutil" @@ -1449,11 +1448,11 @@ func (cli *DockerCli) CmdCommit(args ...string) error { v.Set("comment", *flComment) v.Set("author", *flAuthor) var ( - config *Config + config *runconfig.Config env engine.Env ) if *flConfig != "" { - config = &Config{} + config = &runconfig.Config{} if err := json.Unmarshal([]byte(*flConfig), config); err != nil { return err } @@ -1743,210 +1742,9 @@ func (cli *DockerCli) CmdTag(args ...string) error { return nil } -//FIXME Only used in tests -func ParseRun(args []string, sysInfo *sysinfo.SysInfo) (*Config, *HostConfig, *flag.FlagSet, error) { - cmd := flag.NewFlagSet("run", flag.ContinueOnError) - cmd.SetOutput(ioutil.Discard) - cmd.Usage = nil - return parseRun(cmd, args, sysInfo) -} - -func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Config, *HostConfig, *flag.FlagSet, error) { - var ( - // FIXME: use utils.ListOpts for attach and volumes? - flAttach = opts.NewListOpts(opts.ValidateAttach) - flVolumes = opts.NewListOpts(opts.ValidatePath) - flLinks = opts.NewListOpts(opts.ValidateLink) - flEnv = opts.NewListOpts(opts.ValidateEnv) - - flPublish opts.ListOpts - flExpose opts.ListOpts - flDns opts.ListOpts - flVolumesFrom opts.ListOpts - flLxcOpts opts.ListOpts - - flAutoRemove = cmd.Bool([]string{"#rm", "-rm"}, false, "Automatically remove the container when it exits (incompatible with -d)") - flDetach = cmd.Bool([]string{"d", "-detach"}, false, "Detached mode: Run container in the background, print new container id") - flNetwork = cmd.Bool([]string{"n", "-networking"}, true, "Enable networking for this container") - flPrivileged = cmd.Bool([]string{"#privileged", "-privileged"}, false, "Give extended privileges to this container") - flPublishAll = cmd.Bool([]string{"P", "-publish-all"}, false, "Publish all exposed ports to the host interfaces") - flStdin = cmd.Bool([]string{"i", "-interactive"}, false, "Keep stdin open even if not attached") - flTty = cmd.Bool([]string{"t", "-tty"}, false, "Allocate a pseudo-tty") - flContainerIDFile = cmd.String([]string{"#cidfile", "-cidfile"}, "", "Write the container ID to the file") - flEntrypoint = cmd.String([]string{"#entrypoint", "-entrypoint"}, "", "Overwrite the default entrypoint of the image") - flHostname = cmd.String([]string{"h", "-hostname"}, "", "Container host name") - flMemoryString = cmd.String([]string{"m", "-memory"}, "", "Memory limit (format: , where unit = b, k, m or g)") - flUser = cmd.String([]string{"u", "-user"}, "", "Username or UID") - flWorkingDir = cmd.String([]string{"w", "-workdir"}, "", "Working directory inside the container") - flCpuShares = cmd.Int64([]string{"c", "-cpu-shares"}, 0, "CPU shares (relative weight)") - - // For documentation purpose - _ = cmd.Bool([]string{"#sig-proxy", "-sig-proxy"}, true, "Proxify all received signal to the process (even in non-tty mode)") - _ = cmd.String([]string{"#name", "-name"}, "", "Assign a name to the container") - ) - - cmd.Var(&flAttach, []string{"a", "-attach"}, "Attach to stdin, stdout or stderr.") - cmd.Var(&flVolumes, []string{"v", "-volume"}, "Bind mount a volume (e.g. from the host: -v /host:/container, from docker: -v /container)") - cmd.Var(&flLinks, []string{"#link", "-link"}, "Add link to another container (name:alias)") - cmd.Var(&flEnv, []string{"e", "-env"}, "Set environment variables") - - cmd.Var(&flPublish, []string{"p", "-publish"}, fmt.Sprintf("Publish a container's port to the host (format: %s) (use 'docker port' to see the actual mapping)", nat.PortSpecTemplateFormat)) - cmd.Var(&flExpose, []string{"#expose", "-expose"}, "Expose a port from the container without publishing it to your host") - cmd.Var(&flDns, []string{"#dns", "-dns"}, "Set custom dns servers") - cmd.Var(&flVolumesFrom, []string{"#volumes-from", "-volumes-from"}, "Mount volumes from the specified container(s)") - cmd.Var(&flLxcOpts, []string{"#lxc-conf", "-lxc-conf"}, "Add custom lxc options -lxc-conf=\"lxc.cgroup.cpuset.cpus = 0,1\"") - - if err := cmd.Parse(args); err != nil { - return nil, nil, cmd, err - } - - // Check if the kernel supports memory limit cgroup. - if sysInfo != nil && *flMemoryString != "" && !sysInfo.MemoryLimit { - *flMemoryString = "" - } - - // Validate input params - if *flDetach && flAttach.Len() > 0 { - return nil, nil, cmd, ErrConflictAttachDetach - } - if *flWorkingDir != "" && !path.IsAbs(*flWorkingDir) { - return nil, nil, cmd, ErrInvalidWorikingDirectory - } - if *flDetach && *flAutoRemove { - return nil, nil, cmd, ErrConflictDetachAutoRemove - } - - // If neither -d or -a are set, attach to everything by default - if flAttach.Len() == 0 && !*flDetach { - if !*flDetach { - flAttach.Set("stdout") - flAttach.Set("stderr") - if *flStdin { - flAttach.Set("stdin") - } - } - } - - var flMemory int64 - if *flMemoryString != "" { - parsedMemory, err := utils.RAMInBytes(*flMemoryString) - if err != nil { - return nil, nil, cmd, err - } - flMemory = parsedMemory - } - - var binds []string - // add any bind targets to the list of container volumes - for bind := range flVolumes.GetMap() { - if arr := strings.Split(bind, ":"); len(arr) > 1 { - if arr[0] == "/" { - return nil, nil, cmd, fmt.Errorf("Invalid bind mount: source can't be '/'") - } - dstDir := arr[1] - flVolumes.Set(dstDir) - binds = append(binds, bind) - flVolumes.Delete(bind) - } else if bind == "/" { - return nil, nil, cmd, fmt.Errorf("Invalid volume: path can't be '/'") - } - } - - var ( - parsedArgs = cmd.Args() - runCmd []string - entrypoint []string - image string - ) - if len(parsedArgs) >= 1 { - image = cmd.Arg(0) - } - if len(parsedArgs) > 1 { - runCmd = parsedArgs[1:] - } - if *flEntrypoint != "" { - entrypoint = []string{*flEntrypoint} - } - - lxcConf, err := parseLxcConfOpts(flLxcOpts) - if err != nil { - return nil, nil, cmd, err - } - - var ( - domainname string - hostname = *flHostname - parts = strings.SplitN(hostname, ".", 2) - ) - if len(parts) > 1 { - hostname = parts[0] - domainname = parts[1] - } - - ports, portBindings, err := nat.ParsePortSpecs(flPublish.GetAll()) - if err != nil { - return nil, nil, cmd, err - } - - // Merge in exposed ports to the map of published ports - for _, e := range flExpose.GetAll() { - if strings.Contains(e, ":") { - return nil, nil, cmd, fmt.Errorf("Invalid port format for --expose: %s", e) - } - p := nat.NewPort(nat.SplitProtoPort(e)) - if _, exists := ports[p]; !exists { - ports[p] = struct{}{} - } - } - - config := &Config{ - Hostname: hostname, - Domainname: domainname, - PortSpecs: nil, // Deprecated - ExposedPorts: ports, - User: *flUser, - Tty: *flTty, - NetworkDisabled: !*flNetwork, - OpenStdin: *flStdin, - Memory: flMemory, - CpuShares: *flCpuShares, - AttachStdin: flAttach.Get("stdin"), - AttachStdout: flAttach.Get("stdout"), - AttachStderr: flAttach.Get("stderr"), - Env: flEnv.GetAll(), - Cmd: runCmd, - Dns: flDns.GetAll(), - Image: image, - Volumes: flVolumes.GetMap(), - VolumesFrom: strings.Join(flVolumesFrom.GetAll(), ","), - Entrypoint: entrypoint, - WorkingDir: *flWorkingDir, - } - - hostConfig := &HostConfig{ - Binds: binds, - ContainerIDFile: *flContainerIDFile, - LxcConf: lxcConf, - Privileged: *flPrivileged, - PortBindings: portBindings, - Links: flLinks.GetAll(), - PublishAllPorts: *flPublishAll, - } - - if sysInfo != nil && flMemory > 0 && !sysInfo.SwapLimit { - //fmt.Fprintf(stdout, "WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n") - config.MemorySwap = -1 - } - - // When allocating stdin in attached mode, close stdin at client disconnect - if config.OpenStdin && config.AttachStdin { - config.StdinOnce = true - } - return config, hostConfig, cmd, nil -} - func (cli *DockerCli) CmdRun(args ...string) error { - config, hostConfig, cmd, err := parseRun(cli.Subcmd("run", "[OPTIONS] IMAGE [COMMAND] [ARG...]", "Run a command in a new container"), args, nil) + // FIXME: just use runconfig.Parse already + config, hostConfig, cmd, err := runconfig.ParseSubcommand(cli.Subcmd("run", "[OPTIONS] IMAGE [COMMAND] [ARG...]", "Run a command in a new container"), args, nil) if err != nil { return err } diff --git a/commands_unit_test.go b/commands_unit_test.go index e44d9a1854..60d8d60398 100644 --- a/commands_unit_test.go +++ b/commands_unit_test.go @@ -1,16 +1,17 @@ package docker import ( + "github.com/dotcloud/docker/runconfig" "strings" "testing" ) -func parse(t *testing.T, args string) (*Config, *HostConfig, error) { - config, hostConfig, _, err := ParseRun(strings.Split(args+" ubuntu bash", " "), nil) +func parse(t *testing.T, args string) (*runconfig.Config, *runconfig.HostConfig, error) { + config, hostConfig, _, err := runconfig.Parse(strings.Split(args+" ubuntu bash", " "), nil) return config, hostConfig, err } -func mustParse(t *testing.T, args string) (*Config, *HostConfig) { +func mustParse(t *testing.T, args string) (*runconfig.Config, *runconfig.HostConfig) { config, hostConfig, err := parse(t, args) if err != nil { t.Fatal(err) diff --git a/container.go b/container.go index 991c2aeee7..f068d00375 100644 --- a/container.go +++ b/container.go @@ -11,6 +11,7 @@ import ( "github.com/dotcloud/docker/nat" "github.com/dotcloud/docker/pkg/mount" "github.com/dotcloud/docker/pkg/term" + "github.com/dotcloud/docker/runconfig" "github.com/dotcloud/docker/utils" "github.com/kr/pty" "io" @@ -42,7 +43,7 @@ type Container struct { Path string Args []string - Config *Config + Config *runconfig.Config State State Image string @@ -68,109 +69,11 @@ type Container struct { // Store rw/ro in a separate structure to preserve reverse-compatibility on-disk. // Easier than migrating older container configs :) VolumesRW map[string]bool - hostConfig *HostConfig + hostConfig *runconfig.HostConfig activeLinks map[string]*Link } -// Note: the Config structure should hold only portable information about the container. -// Here, "portable" means "independent from the host we are running on". -// Non-portable information *should* appear in HostConfig. -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 - CpuShares int64 // CPU shares (relative weight vs. other containers) - AttachStdin bool - AttachStdout bool - AttachStderr bool - PortSpecs []string // Deprecated - Can be in the format of 8080/tcp - ExposedPorts map[nat.Port]struct{} - Tty bool // Attach standard streams to a tty, including stdin if it is not closed. - OpenStdin bool // Open stdin - StdinOnce bool // If true, close stdin after the 1 attached client disconnects. - Env []string - Cmd []string - Dns []string - Image string // Name of the image as it was passed by the operator (eg. could be symbolic) - Volumes map[string]struct{} - VolumesFrom string - WorkingDir string - Entrypoint []string - NetworkDisabled bool - OnBuild []string -} - -func ContainerConfigFromJob(job *engine.Job) *Config { - config := &Config{ - Hostname: job.Getenv("Hostname"), - Domainname: job.Getenv("Domainname"), - User: job.Getenv("User"), - Memory: job.GetenvInt64("Memory"), - MemorySwap: job.GetenvInt64("MemorySwap"), - CpuShares: job.GetenvInt64("CpuShares"), - AttachStdin: job.GetenvBool("AttachStdin"), - AttachStdout: job.GetenvBool("AttachStdout"), - AttachStderr: job.GetenvBool("AttachStderr"), - Tty: job.GetenvBool("Tty"), - OpenStdin: job.GetenvBool("OpenStdin"), - StdinOnce: job.GetenvBool("StdinOnce"), - Image: job.Getenv("Image"), - VolumesFrom: job.Getenv("VolumesFrom"), - WorkingDir: job.Getenv("WorkingDir"), - NetworkDisabled: job.GetenvBool("NetworkDisabled"), - } - job.GetenvJson("ExposedPorts", &config.ExposedPorts) - job.GetenvJson("Volumes", &config.Volumes) - if PortSpecs := job.GetenvList("PortSpecs"); PortSpecs != nil { - config.PortSpecs = PortSpecs - } - if Env := job.GetenvList("Env"); Env != nil { - config.Env = Env - } - if Cmd := job.GetenvList("Cmd"); Cmd != nil { - config.Cmd = Cmd - } - if Dns := job.GetenvList("Dns"); Dns != nil { - config.Dns = Dns - } - if Entrypoint := job.GetenvList("Entrypoint"); Entrypoint != nil { - config.Entrypoint = Entrypoint - } - - return config -} - -type HostConfig struct { - Binds []string - ContainerIDFile string - LxcConf []KeyValuePair - Privileged bool - PortBindings nat.PortMap - Links []string - PublishAllPorts bool -} - -func ContainerHostConfigFromJob(job *engine.Job) *HostConfig { - hostConfig := &HostConfig{ - ContainerIDFile: job.Getenv("ContainerIDFile"), - Privileged: job.GetenvBool("Privileged"), - PublishAllPorts: job.GetenvBool("PublishAllPorts"), - } - job.GetenvJson("LxcConf", &hostConfig.LxcConf) - job.GetenvJson("PortBindings", &hostConfig.PortBindings) - if Binds := job.GetenvList("Binds"); Binds != nil { - hostConfig.Binds = Binds - } - if Links := job.GetenvList("Links"); Links != nil { - hostConfig.Links = Links - } - - return hostConfig -} - type BindMap struct { SrcPath string DstPath string @@ -178,18 +81,10 @@ type BindMap struct { } var ( - ErrContainerStart = errors.New("The container failed to start. Unknown error") - ErrContainerStartTimeout = errors.New("The container failed to start due to timed out.") - ErrInvalidWorikingDirectory = errors.New("The working directory is invalid. It needs to be an absolute path.") - ErrConflictAttachDetach = errors.New("Conflicting options: -a and -d") - ErrConflictDetachAutoRemove = errors.New("Conflicting options: -rm and -d") + ErrContainerStart = errors.New("The container failed to start. Unknown error") + ErrContainerStartTimeout = errors.New("The container failed to start due to timed out.") ) -type KeyValuePair struct { - Key string - Value string -} - // FIXME: move deprecated port stuff to nat to clean up the core. type PortMapping map[string]string // Deprecated @@ -292,7 +187,7 @@ func (container *Container) ToDisk() (err error) { } func (container *Container) readHostConfig() error { - container.hostConfig = &HostConfig{} + container.hostConfig = &runconfig.HostConfig{} // If the hostconfig file does not exist, do not read it. // (We still have to initialize container.hostConfig, // but that's OK, since we just did that above.) diff --git a/container_unit_test.go b/container_unit_test.go index dd915ad2e4..3877b7f0da 100644 --- a/container_unit_test.go +++ b/container_unit_test.go @@ -5,23 +5,6 @@ import ( "testing" ) -func TestParseLxcConfOpt(t *testing.T) { - opts := []string{"lxc.utsname=docker", "lxc.utsname = docker "} - - for _, o := range opts { - k, v, err := parseLxcOpt(o) - if err != nil { - t.FailNow() - } - if k != "lxc.utsname" { - t.Fail() - } - if v != "docker" { - t.Fail() - } - } -} - func TestParseNetworkOptsPrivateOnly(t *testing.T) { ports, bindings, err := nat.ParsePortSpecs([]string{"192.168.1.100::80"}) if err != nil { diff --git a/graph.go b/graph.go index 138c7b8613..01cd50f4f0 100644 --- a/graph.go +++ b/graph.go @@ -5,6 +5,7 @@ import ( "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/dockerversion" "github.com/dotcloud/docker/graphdriver" + "github.com/dotcloud/docker/runconfig" "github.com/dotcloud/docker/utils" "io" "io/ioutil" @@ -126,7 +127,7 @@ func (graph *Graph) Get(name string) (*Image, error) { } // Create creates a new image and registers it in the graph. -func (graph *Graph) Create(layerData archive.Archive, container *Container, comment, author string, config *Config) (*Image, error) { +func (graph *Graph) Create(layerData archive.Archive, container *Container, comment, author string, config *runconfig.Config) (*Image, error) { img := &Image{ ID: GenerateID(), Comment: comment, diff --git a/image.go b/image.go index dbd2173597..593dc14f00 100644 --- a/image.go +++ b/image.go @@ -7,6 +7,7 @@ import ( "fmt" "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/graphdriver" + "github.com/dotcloud/docker/runconfig" "github.com/dotcloud/docker/utils" "io" "io/ioutil" @@ -18,17 +19,17 @@ import ( ) type Image struct { - ID string `json:"id"` - Parent string `json:"parent,omitempty"` - Comment string `json:"comment,omitempty"` - Created time.Time `json:"created"` - Container string `json:"container,omitempty"` - ContainerConfig Config `json:"container_config,omitempty"` - DockerVersion string `json:"docker_version,omitempty"` - Author string `json:"author,omitempty"` - Config *Config `json:"config,omitempty"` - Architecture string `json:"architecture,omitempty"` - OS string `json:"os,omitempty"` + ID string `json:"id"` + Parent string `json:"parent,omitempty"` + Comment string `json:"comment,omitempty"` + Created time.Time `json:"created"` + Container string `json:"container,omitempty"` + ContainerConfig runconfig.Config `json:"container_config,omitempty"` + DockerVersion string `json:"docker_version,omitempty"` + Author string `json:"author,omitempty"` + Config *runconfig.Config `json:"config,omitempty"` + Architecture string `json:"architecture,omitempty"` + OS string `json:"os,omitempty"` graph *Graph Size int64 } diff --git a/integration/api_test.go b/integration/api_test.go index d3efb75969..c587f111a2 100644 --- a/integration/api_test.go +++ b/integration/api_test.go @@ -10,6 +10,7 @@ import ( "github.com/dotcloud/docker/api" "github.com/dotcloud/docker/dockerversion" "github.com/dotcloud/docker/engine" + "github.com/dotcloud/docker/runconfig" "github.com/dotcloud/docker/utils" "io" "net" @@ -309,7 +310,7 @@ func TestGetContainersJSON(t *testing.T) { } beginLen := len(outs.Data) - containerID := createTestContainer(eng, &docker.Config{ + containerID := createTestContainer(eng, &runconfig.Config{ Image: unitTestImageID, Cmd: []string{"echo", "test"}, }, t) @@ -346,7 +347,7 @@ func TestGetContainersExport(t *testing.T) { // Create a container and remove a file containerID := createTestContainer(eng, - &docker.Config{ + &runconfig.Config{ Image: unitTestImageID, Cmd: []string{"touch", "/test"}, }, @@ -394,7 +395,7 @@ func TestGetContainersChanges(t *testing.T) { // Create a container and remove a file containerID := createTestContainer(eng, - &docker.Config{ + &runconfig.Config{ Image: unitTestImageID, Cmd: []string{"/bin/rm", "/etc/passwd"}, }, @@ -433,7 +434,7 @@ func TestGetContainersTop(t *testing.T) { defer mkRuntimeFromEngine(eng, t).Nuke() containerID := createTestContainer(eng, - &docker.Config{ + &runconfig.Config{ Image: unitTestImageID, Cmd: []string{"/bin/sh", "-c", "cat"}, OpenStdin: true, @@ -510,7 +511,7 @@ func TestGetContainersByName(t *testing.T) { // Create a container and remove a file containerID := createTestContainer(eng, - &docker.Config{ + &runconfig.Config{ Image: unitTestImageID, Cmd: []string{"echo", "test"}, }, @@ -542,7 +543,7 @@ func TestPostCommit(t *testing.T) { // Create a container and remove a file containerID := createTestContainer(eng, - &docker.Config{ + &runconfig.Config{ Image: unitTestImageID, Cmd: []string{"touch", "/test"}, }, @@ -578,7 +579,7 @@ func TestPostContainersCreate(t *testing.T) { eng := NewTestEngine(t) defer mkRuntimeFromEngine(eng, t).Nuke() - configJSON, err := json.Marshal(&docker.Config{ + configJSON, err := json.Marshal(&runconfig.Config{ Image: unitTestImageID, Memory: 33554432, Cmd: []string{"touch", "/test"}, @@ -620,7 +621,7 @@ func TestPostContainersKill(t *testing.T) { defer mkRuntimeFromEngine(eng, t).Nuke() containerID := createTestContainer(eng, - &docker.Config{ + &runconfig.Config{ Image: unitTestImageID, Cmd: []string{"/bin/cat"}, OpenStdin: true, @@ -659,7 +660,7 @@ func TestPostContainersRestart(t *testing.T) { defer mkRuntimeFromEngine(eng, t).Nuke() containerID := createTestContainer(eng, - &docker.Config{ + &runconfig.Config{ Image: unitTestImageID, Cmd: []string{"/bin/top"}, OpenStdin: true, @@ -705,7 +706,7 @@ func TestPostContainersStart(t *testing.T) { containerID := createTestContainer( eng, - &docker.Config{ + &runconfig.Config{ Image: unitTestImageID, Cmd: []string{"/bin/cat"}, OpenStdin: true, @@ -713,7 +714,7 @@ func TestPostContainersStart(t *testing.T) { t, ) - hostConfigJSON, err := json.Marshal(&docker.HostConfig{}) + hostConfigJSON, err := json.Marshal(&runconfig.HostConfig{}) req, err := http.NewRequest("POST", "/containers/"+containerID+"/start", bytes.NewReader(hostConfigJSON)) if err != nil { @@ -758,7 +759,7 @@ func TestRunErrorBindMountRootSource(t *testing.T) { containerID := createTestContainer( eng, - &docker.Config{ + &runconfig.Config{ Image: unitTestImageID, Cmd: []string{"/bin/cat"}, OpenStdin: true, @@ -766,7 +767,7 @@ func TestRunErrorBindMountRootSource(t *testing.T) { t, ) - hostConfigJSON, err := json.Marshal(&docker.HostConfig{ + hostConfigJSON, err := json.Marshal(&runconfig.HostConfig{ Binds: []string{"/:/tmp"}, }) @@ -792,7 +793,7 @@ func TestPostContainersStop(t *testing.T) { defer mkRuntimeFromEngine(eng, t).Nuke() containerID := createTestContainer(eng, - &docker.Config{ + &runconfig.Config{ Image: unitTestImageID, Cmd: []string{"/bin/top"}, OpenStdin: true, @@ -832,7 +833,7 @@ func TestPostContainersWait(t *testing.T) { defer mkRuntimeFromEngine(eng, t).Nuke() containerID := createTestContainer(eng, - &docker.Config{ + &runconfig.Config{ Image: unitTestImageID, Cmd: []string{"/bin/sleep", "1"}, OpenStdin: true, @@ -870,7 +871,7 @@ func TestPostContainersAttach(t *testing.T) { defer mkRuntimeFromEngine(eng, t).Nuke() containerID := createTestContainer(eng, - &docker.Config{ + &runconfig.Config{ Image: unitTestImageID, Cmd: []string{"/bin/cat"}, OpenStdin: true, @@ -948,7 +949,7 @@ func TestPostContainersAttachStderr(t *testing.T) { defer mkRuntimeFromEngine(eng, t).Nuke() containerID := createTestContainer(eng, - &docker.Config{ + &runconfig.Config{ Image: unitTestImageID, Cmd: []string{"/bin/sh", "-c", "/bin/cat >&2"}, OpenStdin: true, @@ -1029,7 +1030,7 @@ func TestDeleteContainers(t *testing.T) { defer mkRuntimeFromEngine(eng, t).Nuke() containerID := createTestContainer(eng, - &docker.Config{ + &runconfig.Config{ Image: unitTestImageID, Cmd: []string{"touch", "/test"}, }, @@ -1164,7 +1165,7 @@ func TestPostContainersCopy(t *testing.T) { // Create a container and remove a file containerID := createTestContainer(eng, - &docker.Config{ + &runconfig.Config{ Image: unitTestImageID, Cmd: []string{"touch", "/test.txt"}, }, diff --git a/integration/container_test.go b/integration/container_test.go index 97f4cd282f..b961e1d147 100644 --- a/integration/container_test.go +++ b/integration/container_test.go @@ -3,7 +3,7 @@ package docker import ( "bufio" "fmt" - "github.com/dotcloud/docker" + "github.com/dotcloud/docker/runconfig" "github.com/dotcloud/docker/utils" "io" "io/ioutil" @@ -20,7 +20,7 @@ func TestIDFormat(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) container1, _, err := runtime.Create( - &docker.Config{ + &runconfig.Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/sh", "-c", "echo hello world"}, }, @@ -234,7 +234,7 @@ func TestCommitAutoRun(t *testing.T) { t.Errorf("Container shouldn't be running") } - img, err := runtime.Commit(container1, "", "", "unit test commited image", "", &docker.Config{Cmd: []string{"cat", "/world"}}) + img, err := runtime.Commit(container1, "", "", "unit test commited image", "", &runconfig.Config{Cmd: []string{"cat", "/world"}}) if err != nil { t.Error(err) } @@ -415,7 +415,7 @@ func TestOutput(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) container, _, err := runtime.Create( - &docker.Config{ + &runconfig.Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"echo", "-n", "foobar"}, }, @@ -438,7 +438,7 @@ func TestContainerNetwork(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) container, _, err := runtime.Create( - &docker.Config{ + &runconfig.Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"ping", "-c", "1", "127.0.0.1"}, }, @@ -460,7 +460,7 @@ func TestKillDifferentUser(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - container, _, err := runtime.Create(&docker.Config{ + container, _, err := runtime.Create(&runconfig.Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"cat"}, OpenStdin: true, @@ -520,7 +520,7 @@ func TestCreateVolume(t *testing.T) { runtime := mkRuntimeFromEngine(eng, t) defer nuke(runtime) - config, hc, _, err := docker.ParseRun([]string{"-v", "/var/lib/data", unitTestImageID, "echo", "hello", "world"}, nil) + config, hc, _, err := runconfig.Parse([]string{"-v", "/var/lib/data", unitTestImageID, "echo", "hello", "world"}, nil) if err != nil { t.Fatal(err) } @@ -552,7 +552,7 @@ func TestCreateVolume(t *testing.T) { func TestKill(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - container, _, err := runtime.Create(&docker.Config{ + container, _, err := runtime.Create(&runconfig.Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"sleep", "2"}, }, @@ -596,7 +596,7 @@ func TestExitCode(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - trueContainer, _, err := runtime.Create(&docker.Config{ + trueContainer, _, err := runtime.Create(&runconfig.Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/true"}, }, "") @@ -611,7 +611,7 @@ func TestExitCode(t *testing.T) { t.Fatalf("Unexpected exit code %d (expected 0)", code) } - falseContainer, _, err := runtime.Create(&docker.Config{ + falseContainer, _, err := runtime.Create(&runconfig.Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/false"}, }, "") @@ -630,7 +630,7 @@ func TestExitCode(t *testing.T) { func TestRestart(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - container, _, err := runtime.Create(&docker.Config{ + container, _, err := runtime.Create(&runconfig.Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"echo", "-n", "foobar"}, }, @@ -661,7 +661,7 @@ func TestRestart(t *testing.T) { func TestRestartStdin(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - container, _, err := runtime.Create(&docker.Config{ + container, _, err := runtime.Create(&runconfig.Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"cat"}, @@ -739,7 +739,7 @@ func TestUser(t *testing.T) { defer nuke(runtime) // Default user must be root - container, _, err := runtime.Create(&docker.Config{ + container, _, err := runtime.Create(&runconfig.Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"id"}, }, @@ -758,7 +758,7 @@ func TestUser(t *testing.T) { } // Set a username - container, _, err = runtime.Create(&docker.Config{ + container, _, err = runtime.Create(&runconfig.Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"id"}, @@ -779,7 +779,7 @@ func TestUser(t *testing.T) { } // Set a UID - container, _, err = runtime.Create(&docker.Config{ + container, _, err = runtime.Create(&runconfig.Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"id"}, @@ -800,7 +800,7 @@ func TestUser(t *testing.T) { } // Set a different user by uid - container, _, err = runtime.Create(&docker.Config{ + container, _, err = runtime.Create(&runconfig.Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"id"}, @@ -823,7 +823,7 @@ func TestUser(t *testing.T) { } // Set a different user by username - container, _, err = runtime.Create(&docker.Config{ + container, _, err = runtime.Create(&runconfig.Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"id"}, @@ -844,7 +844,7 @@ func TestUser(t *testing.T) { } // Test an wrong username - container, _, err = runtime.Create(&docker.Config{ + container, _, err = runtime.Create(&runconfig.Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"id"}, @@ -866,7 +866,7 @@ func TestMultipleContainers(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - container1, _, err := runtime.Create(&docker.Config{ + container1, _, err := runtime.Create(&runconfig.Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"sleep", "2"}, }, @@ -877,7 +877,7 @@ func TestMultipleContainers(t *testing.T) { } defer runtime.Destroy(container1) - container2, _, err := runtime.Create(&docker.Config{ + container2, _, err := runtime.Create(&runconfig.Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"sleep", "2"}, }, @@ -921,7 +921,7 @@ func TestMultipleContainers(t *testing.T) { func TestStdin(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - container, _, err := runtime.Create(&docker.Config{ + container, _, err := runtime.Create(&runconfig.Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"cat"}, @@ -966,7 +966,7 @@ func TestStdin(t *testing.T) { func TestTty(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - container, _, err := runtime.Create(&docker.Config{ + container, _, err := runtime.Create(&runconfig.Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"cat"}, @@ -1013,7 +1013,7 @@ func TestEnv(t *testing.T) { os.Setenv("TRICKY", "tri\ncky\n") runtime := mkRuntime(t) defer nuke(runtime) - config, _, _, err := docker.ParseRun([]string{"-e=FALSE=true", "-e=TRUE", "-e=TRICKY", GetTestImage(runtime).ID, "env"}, nil) + config, _, _, err := runconfig.Parse([]string{"-e=FALSE=true", "-e=TRUE", "-e=TRICKY", GetTestImage(runtime).ID, "env"}, nil) if err != nil { t.Fatal(err) } @@ -1067,7 +1067,7 @@ func TestEntrypoint(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) container, _, err := runtime.Create( - &docker.Config{ + &runconfig.Config{ Image: GetTestImage(runtime).ID, Entrypoint: []string{"/bin/echo"}, Cmd: []string{"-n", "foobar"}, @@ -1091,7 +1091,7 @@ func TestEntrypointNoCmd(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) container, _, err := runtime.Create( - &docker.Config{ + &runconfig.Config{ Image: GetTestImage(runtime).ID, Entrypoint: []string{"/bin/echo", "foobar"}, }, @@ -1114,7 +1114,7 @@ func BenchmarkRunSequencial(b *testing.B) { runtime := mkRuntime(b) defer nuke(runtime) for i := 0; i < b.N; i++ { - container, _, err := runtime.Create(&docker.Config{ + container, _, err := runtime.Create(&runconfig.Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"echo", "-n", "foo"}, }, @@ -1147,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 := runtime.Create(&docker.Config{ + container, _, err := runtime.Create(&runconfig.Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"echo", "-n", "foo"}, }, @@ -1301,7 +1301,7 @@ func TestFromVolumesInReadonlyMode(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) container, _, err := runtime.Create( - &docker.Config{ + &runconfig.Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/echo", "-n", "foobar"}, Volumes: map[string]struct{}{"/test": {}}, @@ -1321,7 +1321,7 @@ func TestFromVolumesInReadonlyMode(t *testing.T) { } container2, _, err := runtime.Create( - &docker.Config{ + &runconfig.Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/echo", "-n", "foobar"}, VolumesFrom: container.ID + ":ro", @@ -1362,7 +1362,7 @@ func TestVolumesFromReadonlyMount(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) container, _, err := runtime.Create( - &docker.Config{ + &runconfig.Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/echo", "-n", "foobar"}, Volumes: map[string]struct{}{"/test": {}}, @@ -1382,7 +1382,7 @@ func TestVolumesFromReadonlyMount(t *testing.T) { } container2, _, err := runtime.Create( - &docker.Config{ + &runconfig.Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/echo", "-n", "foobar"}, VolumesFrom: container.ID, @@ -1418,7 +1418,7 @@ func TestRestartWithVolumes(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - container, _, err := runtime.Create(&docker.Config{ + container, _, err := runtime.Create(&runconfig.Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"echo", "-n", "foobar"}, Volumes: map[string]struct{}{"/test": {}}, @@ -1462,7 +1462,7 @@ func TestVolumesFromWithVolumes(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - container, _, err := runtime.Create(&docker.Config{ + container, _, err := runtime.Create(&runconfig.Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"sh", "-c", "echo -n bar > /test/foo"}, Volumes: map[string]struct{}{"/test": {}}, @@ -1491,7 +1491,7 @@ func TestVolumesFromWithVolumes(t *testing.T) { } container2, _, err := runtime.Create( - &docker.Config{ + &runconfig.Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"cat", "/test/foo"}, VolumesFrom: container.ID, @@ -1529,7 +1529,7 @@ func TestOnlyLoopbackExistsWhenUsingDisableNetworkOption(t *testing.T) { runtime := mkRuntimeFromEngine(eng, t) defer nuke(runtime) - config, hc, _, err := docker.ParseRun([]string{"-n=false", GetTestImage(runtime).ID, "ip", "addr", "show"}, nil) + config, hc, _, err := runconfig.Parse([]string{"-n=false", GetTestImage(runtime).ID, "ip", "addr", "show"}, nil) if err != nil { t.Fatal(err) } @@ -1617,7 +1617,7 @@ func TestMultipleVolumesFrom(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - container, _, err := runtime.Create(&docker.Config{ + container, _, err := runtime.Create(&runconfig.Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"sh", "-c", "echo -n bar > /test/foo"}, Volumes: map[string]struct{}{"/test": {}}, @@ -1646,7 +1646,7 @@ func TestMultipleVolumesFrom(t *testing.T) { } container2, _, err := runtime.Create( - &docker.Config{ + &runconfig.Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"sh", "-c", "echo -n bar > /other/foo"}, Volumes: map[string]struct{}{"/other": {}}, @@ -1668,7 +1668,7 @@ func TestMultipleVolumesFrom(t *testing.T) { } container3, _, err := runtime.Create( - &docker.Config{ + &runconfig.Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/echo", "-n", "foobar"}, VolumesFrom: strings.Join([]string{container.ID, container2.ID}, ","), @@ -1696,7 +1696,7 @@ func TestRestartGhost(t *testing.T) { defer nuke(runtime) container, _, err := runtime.Create( - &docker.Config{ + &runconfig.Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"sh", "-c", "echo -n bar > /test/foo"}, Volumes: map[string]struct{}{"/test": {}}, diff --git a/integration/runtime_test.go b/integration/runtime_test.go index 70dde8f497..170f4c9638 100644 --- a/integration/runtime_test.go +++ b/integration/runtime_test.go @@ -6,6 +6,7 @@ import ( "github.com/dotcloud/docker" "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/nat" + "github.com/dotcloud/docker/runconfig" "github.com/dotcloud/docker/sysinit" "github.com/dotcloud/docker/utils" "io" @@ -200,7 +201,7 @@ func TestRuntimeCreate(t *testing.T) { t.Errorf("Expected 0 containers, %v found", len(runtime.List())) } - container, _, err := runtime.Create(&docker.Config{ + container, _, err := runtime.Create(&runconfig.Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"ls", "-al"}, }, @@ -243,23 +244,23 @@ func TestRuntimeCreate(t *testing.T) { // Test that conflict error displays correct details testContainer, _, _ := runtime.Create( - &docker.Config{ + &runconfig.Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"ls", "-al"}, }, "conflictname", ) - if _, _, err := runtime.Create(&docker.Config{Image: GetTestImage(runtime).ID, Cmd: []string{"ls", "-al"}}, testContainer.Name); err == nil || !strings.Contains(err.Error(), utils.TruncateID(testContainer.ID)) { + if _, _, err := runtime.Create(&runconfig.Config{Image: GetTestImage(runtime).ID, Cmd: []string{"ls", "-al"}}, testContainer.Name); err == nil || !strings.Contains(err.Error(), utils.TruncateID(testContainer.ID)) { t.Fatalf("Name conflict error doesn't include the correct short id. Message was: %s", err.Error()) } // Make sure create with bad parameters returns an error - if _, _, err = runtime.Create(&docker.Config{Image: GetTestImage(runtime).ID}, ""); err == nil { + if _, _, err = runtime.Create(&runconfig.Config{Image: GetTestImage(runtime).ID}, ""); err == nil { t.Fatal("Builder.Create should throw an error when Cmd is missing") } if _, _, err := runtime.Create( - &docker.Config{ + &runconfig.Config{ Image: GetTestImage(runtime).ID, Cmd: []string{}, }, @@ -268,7 +269,7 @@ func TestRuntimeCreate(t *testing.T) { t.Fatal("Builder.Create should throw an error when Cmd is empty") } - config := &docker.Config{ + config := &runconfig.Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"/bin/ls"}, PortSpecs: []string{"80"}, @@ -281,7 +282,7 @@ func TestRuntimeCreate(t *testing.T) { } // test expose 80:8000 - container, warnings, err := runtime.Create(&docker.Config{ + container, warnings, err := runtime.Create(&runconfig.Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"ls", "-al"}, PortSpecs: []string{"80:8000"}, @@ -300,7 +301,7 @@ func TestDestroy(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - container, _, err := runtime.Create(&docker.Config{ + container, _, err := runtime.Create(&runconfig.Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"ls", "-al"}, }, "") @@ -712,7 +713,7 @@ func TestDefaultContainerName(t *testing.T) { runtime := mkRuntimeFromEngine(eng, t) defer nuke(runtime) - config, _, _, err := docker.ParseRun([]string{unitTestImageID, "echo test"}, nil) + config, _, _, err := runconfig.Parse([]string{unitTestImageID, "echo test"}, nil) if err != nil { t.Fatal(err) } @@ -736,7 +737,7 @@ func TestRandomContainerName(t *testing.T) { runtime := mkRuntimeFromEngine(eng, t) defer nuke(runtime) - config, _, _, err := docker.ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil) + config, _, _, err := runconfig.Parse([]string{GetTestImage(runtime).ID, "echo test"}, nil) if err != nil { t.Fatal(err) } @@ -767,7 +768,7 @@ func TestContainerNameValidation(t *testing.T) { {"abc-123_AAA.1", true}, {"\000asdf", false}, } { - config, _, _, err := docker.ParseRun([]string{unitTestImageID, "echo test"}, nil) + config, _, _, err := runconfig.Parse([]string{unitTestImageID, "echo test"}, nil) if err != nil { if !test.Valid { continue @@ -808,7 +809,7 @@ func TestLinkChildContainer(t *testing.T) { runtime := mkRuntimeFromEngine(eng, t) defer nuke(runtime) - config, _, _, err := docker.ParseRun([]string{unitTestImageID, "echo test"}, nil) + config, _, _, err := runconfig.Parse([]string{unitTestImageID, "echo test"}, nil) if err != nil { t.Fatal(err) } @@ -824,7 +825,7 @@ func TestLinkChildContainer(t *testing.T) { t.Fatalf("Expect webapp id to match container id: %s != %s", webapp.ID, container.ID) } - config, _, _, err = docker.ParseRun([]string{GetTestImage(runtime).ID, "echo test"}, nil) + config, _, _, err = runconfig.Parse([]string{GetTestImage(runtime).ID, "echo test"}, nil) if err != nil { t.Fatal(err) } @@ -850,7 +851,7 @@ func TestGetAllChildren(t *testing.T) { runtime := mkRuntimeFromEngine(eng, t) defer nuke(runtime) - config, _, _, err := docker.ParseRun([]string{unitTestImageID, "echo test"}, nil) + config, _, _, err := runconfig.Parse([]string{unitTestImageID, "echo test"}, nil) if err != nil { t.Fatal(err) } @@ -866,7 +867,7 @@ func TestGetAllChildren(t *testing.T) { t.Fatalf("Expect webapp id to match container id: %s != %s", webapp.ID, container.ID) } - config, _, _, err = docker.ParseRun([]string{unitTestImageID, "echo test"}, nil) + config, _, _, err = runconfig.Parse([]string{unitTestImageID, "echo test"}, nil) if err != nil { t.Fatal(err) } @@ -903,7 +904,7 @@ func TestDestroyWithInitLayer(t *testing.T) { runtime := mkRuntime(t) defer nuke(runtime) - container, _, err := runtime.Create(&docker.Config{ + container, _, err := runtime.Create(&runconfig.Config{ Image: GetTestImage(runtime).ID, Cmd: []string{"ls", "-al"}, }, "") diff --git a/integration/server_test.go b/integration/server_test.go index 45d4930ad7..2b7ef13cbd 100644 --- a/integration/server_test.go +++ b/integration/server_test.go @@ -3,6 +3,7 @@ package docker import ( "github.com/dotcloud/docker" "github.com/dotcloud/docker/engine" + "github.com/dotcloud/docker/runconfig" "strings" "testing" "time" @@ -71,7 +72,7 @@ func TestCreateRm(t *testing.T) { eng := NewTestEngine(t) defer mkRuntimeFromEngine(eng, t).Nuke() - config, _, _, err := docker.ParseRun([]string{unitTestImageID, "echo test"}, nil) + config, _, _, err := runconfig.Parse([]string{unitTestImageID, "echo test"}, nil) if err != nil { t.Fatal(err) } @@ -118,7 +119,7 @@ func TestCreateNumberHostname(t *testing.T) { eng := NewTestEngine(t) defer mkRuntimeFromEngine(eng, t).Nuke() - config, _, _, err := docker.ParseRun([]string{"-h", "web.0", unitTestImageID, "echo test"}, nil) + config, _, _, err := runconfig.Parse([]string{"-h", "web.0", unitTestImageID, "echo test"}, nil) if err != nil { t.Fatal(err) } @@ -130,7 +131,7 @@ func TestCreateNumberUsername(t *testing.T) { eng := NewTestEngine(t) defer mkRuntimeFromEngine(eng, t).Nuke() - config, _, _, err := docker.ParseRun([]string{"-u", "1002", unitTestImageID, "echo test"}, nil) + config, _, _, err := runconfig.Parse([]string{"-u", "1002", unitTestImageID, "echo test"}, nil) if err != nil { t.Fatal(err) } @@ -142,7 +143,7 @@ func TestCreateRmVolumes(t *testing.T) { eng := NewTestEngine(t) defer mkRuntimeFromEngine(eng, t).Nuke() - config, hostConfig, _, err := docker.ParseRun([]string{"-v", "/srv", unitTestImageID, "echo", "test"}, nil) + config, hostConfig, _, err := runconfig.Parse([]string{"-v", "/srv", unitTestImageID, "echo", "test"}, nil) if err != nil { t.Fatal(err) } @@ -202,7 +203,7 @@ func TestCommit(t *testing.T) { eng := NewTestEngine(t) defer mkRuntimeFromEngine(eng, t).Nuke() - config, _, _, err := docker.ParseRun([]string{unitTestImageID, "/bin/cat"}, nil) + config, _, _, err := runconfig.Parse([]string{unitTestImageID, "/bin/cat"}, nil) if err != nil { t.Fatal(err) } @@ -224,7 +225,7 @@ func TestRestartKillWait(t *testing.T) { runtime := mkRuntimeFromEngine(eng, t) defer runtime.Nuke() - config, hostConfig, _, err := docker.ParseRun([]string{"-i", unitTestImageID, "/bin/cat"}, nil) + config, hostConfig, _, err := runconfig.Parse([]string{"-i", unitTestImageID, "/bin/cat"}, nil) if err != nil { t.Fatal(err) } @@ -302,7 +303,7 @@ func TestCreateStartRestartStopStartKillRm(t *testing.T) { srv := mkServerFromEngine(eng, t) defer mkRuntimeFromEngine(eng, t).Nuke() - config, hostConfig, _, err := docker.ParseRun([]string{"-i", unitTestImageID, "/bin/cat"}, nil) + config, hostConfig, _, err := runconfig.Parse([]string{"-i", unitTestImageID, "/bin/cat"}, nil) if err != nil { t.Fatal(err) } @@ -401,7 +402,7 @@ func TestRmi(t *testing.T) { initialImages := getAllImages(eng, t) - config, hostConfig, _, err := docker.ParseRun([]string{unitTestImageID, "echo", "test"}, nil) + config, hostConfig, _, err := runconfig.Parse([]string{unitTestImageID, "echo", "test"}, nil) if err != nil { t.Fatal(err) } @@ -548,7 +549,7 @@ func TestListContainers(t *testing.T) { srv := mkServerFromEngine(eng, t) defer mkRuntimeFromEngine(eng, t).Nuke() - config := docker.Config{ + config := runconfig.Config{ Image: unitTestImageID, Cmd: []string{"/bin/sh", "-c", "cat"}, OpenStdin: true, @@ -671,7 +672,7 @@ func TestDeleteTagWithExistingContainers(t *testing.T) { } // Create a container from the image - config, _, _, err := docker.ParseRun([]string{unitTestImageID, "echo test"}, nil) + config, _, _, err := runconfig.Parse([]string{unitTestImageID, "echo test"}, nil) if err != nil { t.Fatal(err) } diff --git a/integration/utils_test.go b/integration/utils_test.go index 450cb7527f..6b0f458564 100644 --- a/integration/utils_test.go +++ b/integration/utils_test.go @@ -16,6 +16,7 @@ import ( "github.com/dotcloud/docker" "github.com/dotcloud/docker/engine" + "github.com/dotcloud/docker/runconfig" "github.com/dotcloud/docker/utils" ) @@ -48,7 +49,7 @@ func mkRuntime(f utils.Fataler) *docker.Runtime { return r } -func createNamedTestContainer(eng *engine.Engine, config *docker.Config, f utils.Fataler, name string) (shortId string) { +func createNamedTestContainer(eng *engine.Engine, config *runconfig.Config, f utils.Fataler, name string) (shortId string) { job := eng.Job("create", name) if err := job.ImportEnv(config); err != nil { f.Fatal(err) @@ -60,7 +61,7 @@ func createNamedTestContainer(eng *engine.Engine, config *docker.Config, f utils return } -func createTestContainer(eng *engine.Engine, config *docker.Config, f utils.Fataler) (shortId string) { +func createTestContainer(eng *engine.Engine, config *runconfig.Config, f utils.Fataler) (shortId string) { return createNamedTestContainer(eng, config, f, "") } @@ -252,8 +253,8 @@ func readFile(src string, t *testing.T) (content string) { // dynamically replaced by the current test image. // The caller is responsible for destroying the container. // Call t.Fatal() at the first error. -func mkContainer(r *docker.Runtime, args []string, t *testing.T) (*docker.Container, *docker.HostConfig, error) { - config, hc, _, err := docker.ParseRun(args, nil) +func mkContainer(r *docker.Runtime, args []string, t *testing.T) (*docker.Container, *runconfig.HostConfig, error) { + config, hc, _, err := runconfig.Parse(args, nil) defer func() { if err != nil && t != nil { t.Fatal(err) diff --git a/links_test.go b/links_test.go index 0286d2395b..7b85a8e86d 100644 --- a/links_test.go +++ b/links_test.go @@ -2,13 +2,14 @@ package docker import ( "github.com/dotcloud/docker/nat" + "github.com/dotcloud/docker/runconfig" "strings" "testing" ) func newMockLinkContainer(id string, ip string) *Container { return &Container{ - Config: &Config{}, + Config: &runconfig.Config{}, ID: id, NetworkSettings: &NetworkSettings{ IPAddress: ip, diff --git a/runconfig/compare.go b/runconfig/compare.go new file mode 100644 index 0000000000..c09f897716 --- /dev/null +++ b/runconfig/compare.go @@ -0,0 +1,67 @@ +package runconfig + +// Compare two Config struct. Do not compare the "Image" nor "Hostname" fields +// If OpenStdin is set, then it differs +func Compare(a, b *Config) bool { + if a == nil || b == nil || + a.OpenStdin || b.OpenStdin { + return false + } + if a.AttachStdout != b.AttachStdout || + a.AttachStderr != b.AttachStderr || + a.User != b.User || + a.Memory != b.Memory || + a.MemorySwap != b.MemorySwap || + a.CpuShares != b.CpuShares || + a.OpenStdin != b.OpenStdin || + a.Tty != b.Tty || + a.VolumesFrom != b.VolumesFrom { + return false + } + if len(a.Cmd) != len(b.Cmd) || + len(a.Dns) != len(b.Dns) || + len(a.Env) != len(b.Env) || + len(a.PortSpecs) != len(b.PortSpecs) || + len(a.ExposedPorts) != len(b.ExposedPorts) || + len(a.Entrypoint) != len(b.Entrypoint) || + len(a.Volumes) != len(b.Volumes) { + return false + } + + for i := 0; i < len(a.Cmd); i++ { + if a.Cmd[i] != b.Cmd[i] { + return false + } + } + for i := 0; i < len(a.Dns); i++ { + if a.Dns[i] != b.Dns[i] { + return false + } + } + for i := 0; i < len(a.Env); i++ { + if a.Env[i] != b.Env[i] { + return false + } + } + for i := 0; i < len(a.PortSpecs); i++ { + if a.PortSpecs[i] != b.PortSpecs[i] { + return false + } + } + for k := range a.ExposedPorts { + if _, exists := b.ExposedPorts[k]; !exists { + return false + } + } + for i := 0; i < len(a.Entrypoint); i++ { + if a.Entrypoint[i] != b.Entrypoint[i] { + return false + } + } + for key := range a.Volumes { + if _, exists := b.Volumes[key]; !exists { + return false + } + } + return true +} diff --git a/runconfig/config.go b/runconfig/config.go new file mode 100644 index 0000000000..9faa823a57 --- /dev/null +++ b/runconfig/config.go @@ -0,0 +1,76 @@ +package runconfig + +import ( + "github.com/dotcloud/docker/engine" + "github.com/dotcloud/docker/nat" +) + +// Note: the Config structure should hold only portable information about the container. +// Here, "portable" means "independent from the host we are running on". +// Non-portable information *should* appear in HostConfig. +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 + CpuShares int64 // CPU shares (relative weight vs. other containers) + AttachStdin bool + AttachStdout bool + AttachStderr bool + PortSpecs []string // Deprecated - Can be in the format of 8080/tcp + ExposedPorts map[nat.Port]struct{} + Tty bool // Attach standard streams to a tty, including stdin if it is not closed. + OpenStdin bool // Open stdin + StdinOnce bool // If true, close stdin after the 1 attached client disconnects. + Env []string + Cmd []string + Dns []string + Image string // Name of the image as it was passed by the operator (eg. could be symbolic) + Volumes map[string]struct{} + VolumesFrom string + WorkingDir string + Entrypoint []string + NetworkDisabled bool + OnBuild []string +} + +func ContainerConfigFromJob(job *engine.Job) *Config { + config := &Config{ + Hostname: job.Getenv("Hostname"), + Domainname: job.Getenv("Domainname"), + User: job.Getenv("User"), + Memory: job.GetenvInt64("Memory"), + MemorySwap: job.GetenvInt64("MemorySwap"), + CpuShares: job.GetenvInt64("CpuShares"), + AttachStdin: job.GetenvBool("AttachStdin"), + AttachStdout: job.GetenvBool("AttachStdout"), + AttachStderr: job.GetenvBool("AttachStderr"), + Tty: job.GetenvBool("Tty"), + OpenStdin: job.GetenvBool("OpenStdin"), + StdinOnce: job.GetenvBool("StdinOnce"), + Image: job.Getenv("Image"), + VolumesFrom: job.Getenv("VolumesFrom"), + WorkingDir: job.Getenv("WorkingDir"), + NetworkDisabled: job.GetenvBool("NetworkDisabled"), + } + job.GetenvJson("ExposedPorts", &config.ExposedPorts) + job.GetenvJson("Volumes", &config.Volumes) + if PortSpecs := job.GetenvList("PortSpecs"); PortSpecs != nil { + config.PortSpecs = PortSpecs + } + if Env := job.GetenvList("Env"); Env != nil { + config.Env = Env + } + if Cmd := job.GetenvList("Cmd"); Cmd != nil { + config.Cmd = Cmd + } + if Dns := job.GetenvList("Dns"); Dns != nil { + config.Dns = Dns + } + if Entrypoint := job.GetenvList("Entrypoint"); Entrypoint != nil { + config.Entrypoint = Entrypoint + } + + return config +} diff --git a/config_test.go b/runconfig/config_test.go similarity index 83% rename from config_test.go rename to runconfig/config_test.go index 1c808163e2..3ef31491fc 100644 --- a/config_test.go +++ b/runconfig/config_test.go @@ -1,11 +1,11 @@ -package docker +package runconfig import ( "github.com/dotcloud/docker/nat" "testing" ) -func TestCompareConfig(t *testing.T) { +func TestCompare(t *testing.T) { volumes1 := make(map[string]struct{}) volumes1["/test1"] = struct{}{} config1 := Config{ @@ -45,24 +45,24 @@ func TestCompareConfig(t *testing.T) { VolumesFrom: "11111111", Volumes: volumes2, } - if CompareConfig(&config1, &config2) { - t.Fatalf("CompareConfig should return false, Dns are different") + if Compare(&config1, &config2) { + t.Fatalf("Compare should return false, Dns are different") } - if CompareConfig(&config1, &config3) { - t.Fatalf("CompareConfig should return false, PortSpecs are different") + if Compare(&config1, &config3) { + t.Fatalf("Compare should return false, PortSpecs are different") } - if CompareConfig(&config1, &config4) { - t.Fatalf("CompareConfig should return false, VolumesFrom are different") + if Compare(&config1, &config4) { + t.Fatalf("Compare should return false, VolumesFrom are different") } - if CompareConfig(&config1, &config5) { - t.Fatalf("CompareConfig should return false, Volumes are different") + if Compare(&config1, &config5) { + t.Fatalf("Compare should return false, Volumes are different") } - if !CompareConfig(&config1, &config1) { - t.Fatalf("CompareConfig should return true") + if !Compare(&config1, &config1) { + t.Fatalf("Compare should return true") } } -func TestMergeConfig(t *testing.T) { +func TestMerge(t *testing.T) { volumesImage := make(map[string]struct{}) volumesImage["/test1"] = struct{}{} volumesImage["/test2"] = struct{}{} @@ -83,7 +83,7 @@ func TestMergeConfig(t *testing.T) { Volumes: volumesUser, } - if err := MergeConfig(configUser, configImage); err != nil { + if err := Merge(configUser, configImage); err != nil { t.Error(err) } @@ -134,7 +134,7 @@ func TestMergeConfig(t *testing.T) { ExposedPorts: ports, } - if err := MergeConfig(configUser, configImage2); err != nil { + if err := Merge(configUser, configImage2); err != nil { t.Error(err) } diff --git a/runconfig/hostconfig.go b/runconfig/hostconfig.go new file mode 100644 index 0000000000..6c8618ee81 --- /dev/null +++ b/runconfig/hostconfig.go @@ -0,0 +1,39 @@ +package runconfig + +import ( + "github.com/dotcloud/docker/engine" + "github.com/dotcloud/docker/nat" +) + +type HostConfig struct { + Binds []string + ContainerIDFile string + LxcConf []KeyValuePair + Privileged bool + PortBindings nat.PortMap + Links []string + PublishAllPorts bool +} + +type KeyValuePair struct { + Key string + Value string +} + +func ContainerHostConfigFromJob(job *engine.Job) *HostConfig { + hostConfig := &HostConfig{ + ContainerIDFile: job.Getenv("ContainerIDFile"), + Privileged: job.GetenvBool("Privileged"), + PublishAllPorts: job.GetenvBool("PublishAllPorts"), + } + job.GetenvJson("LxcConf", &hostConfig.LxcConf) + job.GetenvJson("PortBindings", &hostConfig.PortBindings) + if Binds := job.GetenvList("Binds"); Binds != nil { + hostConfig.Binds = Binds + } + if Links := job.GetenvList("Links"); Links != nil { + hostConfig.Links = Links + } + + return hostConfig +} diff --git a/runconfig/merge.go b/runconfig/merge.go new file mode 100644 index 0000000000..a8d677baa8 --- /dev/null +++ b/runconfig/merge.go @@ -0,0 +1,119 @@ +package runconfig + +import ( + "github.com/dotcloud/docker/nat" + "github.com/dotcloud/docker/utils" + "strings" +) + +func Merge(userConf, imageConf *Config) error { + if userConf.User == "" { + userConf.User = imageConf.User + } + if userConf.Memory == 0 { + userConf.Memory = imageConf.Memory + } + if userConf.MemorySwap == 0 { + userConf.MemorySwap = imageConf.MemorySwap + } + if userConf.CpuShares == 0 { + userConf.CpuShares = imageConf.CpuShares + } + if userConf.ExposedPorts == nil || len(userConf.ExposedPorts) == 0 { + userConf.ExposedPorts = imageConf.ExposedPorts + } else if imageConf.ExposedPorts != nil { + if userConf.ExposedPorts == nil { + userConf.ExposedPorts = make(nat.PortSet) + } + for port := range imageConf.ExposedPorts { + if _, exists := userConf.ExposedPorts[port]; !exists { + userConf.ExposedPorts[port] = struct{}{} + } + } + } + + if userConf.PortSpecs != nil && len(userConf.PortSpecs) > 0 { + if userConf.ExposedPorts == nil { + userConf.ExposedPorts = make(nat.PortSet) + } + ports, _, err := nat.ParsePortSpecs(userConf.PortSpecs) + if err != nil { + return err + } + for port := range ports { + if _, exists := userConf.ExposedPorts[port]; !exists { + userConf.ExposedPorts[port] = struct{}{} + } + } + userConf.PortSpecs = nil + } + if imageConf.PortSpecs != nil && len(imageConf.PortSpecs) > 0 { + // FIXME: I think we can safely remove this. Leaving it for now for the sake of reverse-compat paranoia. + utils.Debugf("Migrating image port specs to containter: %s", strings.Join(imageConf.PortSpecs, ", ")) + if userConf.ExposedPorts == nil { + userConf.ExposedPorts = make(nat.PortSet) + } + + ports, _, err := nat.ParsePortSpecs(imageConf.PortSpecs) + if err != nil { + return err + } + for port := range ports { + if _, exists := userConf.ExposedPorts[port]; !exists { + userConf.ExposedPorts[port] = struct{}{} + } + } + } + if !userConf.Tty { + userConf.Tty = imageConf.Tty + } + if !userConf.OpenStdin { + userConf.OpenStdin = imageConf.OpenStdin + } + if !userConf.StdinOnce { + userConf.StdinOnce = imageConf.StdinOnce + } + if userConf.Env == nil || len(userConf.Env) == 0 { + userConf.Env = imageConf.Env + } else { + for _, imageEnv := range imageConf.Env { + found := false + imageEnvKey := strings.Split(imageEnv, "=")[0] + for _, userEnv := range userConf.Env { + userEnvKey := strings.Split(userEnv, "=")[0] + if imageEnvKey == userEnvKey { + found = true + } + } + if !found { + userConf.Env = append(userConf.Env, imageEnv) + } + } + } + if userConf.Cmd == nil || len(userConf.Cmd) == 0 { + userConf.Cmd = imageConf.Cmd + } + if userConf.Dns == nil || len(userConf.Dns) == 0 { + userConf.Dns = imageConf.Dns + } else { + //duplicates aren't an issue here + userConf.Dns = append(userConf.Dns, imageConf.Dns...) + } + if userConf.Entrypoint == nil || len(userConf.Entrypoint) == 0 { + userConf.Entrypoint = imageConf.Entrypoint + } + if userConf.WorkingDir == "" { + userConf.WorkingDir = imageConf.WorkingDir + } + if userConf.VolumesFrom == "" { + userConf.VolumesFrom = imageConf.VolumesFrom + } + if userConf.Volumes == nil || len(userConf.Volumes) == 0 { + userConf.Volumes = imageConf.Volumes + } else { + for k, v := range imageConf.Volumes { + userConf.Volumes[k] = v + } + } + return nil +} diff --git a/runconfig/parse.go b/runconfig/parse.go new file mode 100644 index 0000000000..fb08c068b2 --- /dev/null +++ b/runconfig/parse.go @@ -0,0 +1,246 @@ +package runconfig + +import ( + "fmt" + "github.com/dotcloud/docker/nat" + flag "github.com/dotcloud/docker/pkg/mflag" + "github.com/dotcloud/docker/pkg/opts" + "github.com/dotcloud/docker/pkg/sysinfo" + "github.com/dotcloud/docker/utils" + "io/ioutil" + "path" + "strings" +) + +var ( + ErrInvalidWorikingDirectory = fmt.Errorf("The working directory is invalid. It needs to be an absolute path.") + ErrConflictAttachDetach = fmt.Errorf("Conflicting options: -a and -d") + ErrConflictDetachAutoRemove = fmt.Errorf("Conflicting options: -rm and -d") +) + +//FIXME Only used in tests +func Parse(args []string, sysInfo *sysinfo.SysInfo) (*Config, *HostConfig, *flag.FlagSet, error) { + cmd := flag.NewFlagSet("run", flag.ContinueOnError) + cmd.SetOutput(ioutil.Discard) + cmd.Usage = nil + return parseRun(cmd, args, sysInfo) +} + +// FIXME: this maps the legacy commands.go code. It should be merged with Parse to only expose a single parse function. +func ParseSubcommand(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Config, *HostConfig, *flag.FlagSet, error) { + return parseRun(cmd, args, sysInfo) +} + +func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Config, *HostConfig, *flag.FlagSet, error) { + var ( + // FIXME: use utils.ListOpts for attach and volumes? + flAttach = opts.NewListOpts(opts.ValidateAttach) + flVolumes = opts.NewListOpts(opts.ValidatePath) + flLinks = opts.NewListOpts(opts.ValidateLink) + flEnv = opts.NewListOpts(opts.ValidateEnv) + + flPublish opts.ListOpts + flExpose opts.ListOpts + flDns opts.ListOpts + flVolumesFrom opts.ListOpts + flLxcOpts opts.ListOpts + + flAutoRemove = cmd.Bool([]string{"#rm", "-rm"}, false, "Automatically remove the container when it exits (incompatible with -d)") + flDetach = cmd.Bool([]string{"d", "-detach"}, false, "Detached mode: Run container in the background, print new container id") + flNetwork = cmd.Bool([]string{"n", "-networking"}, true, "Enable networking for this container") + flPrivileged = cmd.Bool([]string{"#privileged", "-privileged"}, false, "Give extended privileges to this container") + flPublishAll = cmd.Bool([]string{"P", "-publish-all"}, false, "Publish all exposed ports to the host interfaces") + flStdin = cmd.Bool([]string{"i", "-interactive"}, false, "Keep stdin open even if not attached") + flTty = cmd.Bool([]string{"t", "-tty"}, false, "Allocate a pseudo-tty") + flContainerIDFile = cmd.String([]string{"#cidfile", "-cidfile"}, "", "Write the container ID to the file") + flEntrypoint = cmd.String([]string{"#entrypoint", "-entrypoint"}, "", "Overwrite the default entrypoint of the image") + flHostname = cmd.String([]string{"h", "-hostname"}, "", "Container host name") + flMemoryString = cmd.String([]string{"m", "-memory"}, "", "Memory limit (format: , where unit = b, k, m or g)") + flUser = cmd.String([]string{"u", "-user"}, "", "Username or UID") + flWorkingDir = cmd.String([]string{"w", "-workdir"}, "", "Working directory inside the container") + flCpuShares = cmd.Int64([]string{"c", "-cpu-shares"}, 0, "CPU shares (relative weight)") + + // For documentation purpose + _ = cmd.Bool([]string{"#sig-proxy", "-sig-proxy"}, true, "Proxify all received signal to the process (even in non-tty mode)") + _ = cmd.String([]string{"#name", "-name"}, "", "Assign a name to the container") + ) + + cmd.Var(&flAttach, []string{"a", "-attach"}, "Attach to stdin, stdout or stderr.") + cmd.Var(&flVolumes, []string{"v", "-volume"}, "Bind mount a volume (e.g. from the host: -v /host:/container, from docker: -v /container)") + cmd.Var(&flLinks, []string{"#link", "-link"}, "Add link to another container (name:alias)") + cmd.Var(&flEnv, []string{"e", "-env"}, "Set environment variables") + + cmd.Var(&flPublish, []string{"p", "-publish"}, fmt.Sprintf("Publish a container's port to the host (format: %s) (use 'docker port' to see the actual mapping)", nat.PortSpecTemplateFormat)) + cmd.Var(&flExpose, []string{"#expose", "-expose"}, "Expose a port from the container without publishing it to your host") + cmd.Var(&flDns, []string{"#dns", "-dns"}, "Set custom dns servers") + cmd.Var(&flVolumesFrom, []string{"#volumes-from", "-volumes-from"}, "Mount volumes from the specified container(s)") + cmd.Var(&flLxcOpts, []string{"#lxc-conf", "-lxc-conf"}, "Add custom lxc options -lxc-conf=\"lxc.cgroup.cpuset.cpus = 0,1\"") + + if err := cmd.Parse(args); err != nil { + return nil, nil, cmd, err + } + + // Check if the kernel supports memory limit cgroup. + if sysInfo != nil && *flMemoryString != "" && !sysInfo.MemoryLimit { + *flMemoryString = "" + } + + // Validate input params + if *flDetach && flAttach.Len() > 0 { + return nil, nil, cmd, ErrConflictAttachDetach + } + if *flWorkingDir != "" && !path.IsAbs(*flWorkingDir) { + return nil, nil, cmd, ErrInvalidWorikingDirectory + } + if *flDetach && *flAutoRemove { + return nil, nil, cmd, ErrConflictDetachAutoRemove + } + + // If neither -d or -a are set, attach to everything by default + if flAttach.Len() == 0 && !*flDetach { + if !*flDetach { + flAttach.Set("stdout") + flAttach.Set("stderr") + if *flStdin { + flAttach.Set("stdin") + } + } + } + + var flMemory int64 + if *flMemoryString != "" { + parsedMemory, err := utils.RAMInBytes(*flMemoryString) + if err != nil { + return nil, nil, cmd, err + } + flMemory = parsedMemory + } + + var binds []string + // add any bind targets to the list of container volumes + for bind := range flVolumes.GetMap() { + if arr := strings.Split(bind, ":"); len(arr) > 1 { + if arr[0] == "/" { + return nil, nil, cmd, fmt.Errorf("Invalid bind mount: source can't be '/'") + } + dstDir := arr[1] + flVolumes.Set(dstDir) + binds = append(binds, bind) + flVolumes.Delete(bind) + } else if bind == "/" { + return nil, nil, cmd, fmt.Errorf("Invalid volume: path can't be '/'") + } + } + + var ( + parsedArgs = cmd.Args() + runCmd []string + entrypoint []string + image string + ) + if len(parsedArgs) >= 1 { + image = cmd.Arg(0) + } + if len(parsedArgs) > 1 { + runCmd = parsedArgs[1:] + } + if *flEntrypoint != "" { + entrypoint = []string{*flEntrypoint} + } + + lxcConf, err := parseLxcConfOpts(flLxcOpts) + if err != nil { + return nil, nil, cmd, err + } + + var ( + domainname string + hostname = *flHostname + parts = strings.SplitN(hostname, ".", 2) + ) + if len(parts) > 1 { + hostname = parts[0] + domainname = parts[1] + } + + ports, portBindings, err := nat.ParsePortSpecs(flPublish.GetAll()) + if err != nil { + return nil, nil, cmd, err + } + + // Merge in exposed ports to the map of published ports + for _, e := range flExpose.GetAll() { + if strings.Contains(e, ":") { + return nil, nil, cmd, fmt.Errorf("Invalid port format for --expose: %s", e) + } + p := nat.NewPort(nat.SplitProtoPort(e)) + if _, exists := ports[p]; !exists { + ports[p] = struct{}{} + } + } + + config := &Config{ + Hostname: hostname, + Domainname: domainname, + PortSpecs: nil, // Deprecated + ExposedPorts: ports, + User: *flUser, + Tty: *flTty, + NetworkDisabled: !*flNetwork, + OpenStdin: *flStdin, + Memory: flMemory, + CpuShares: *flCpuShares, + AttachStdin: flAttach.Get("stdin"), + AttachStdout: flAttach.Get("stdout"), + AttachStderr: flAttach.Get("stderr"), + Env: flEnv.GetAll(), + Cmd: runCmd, + Dns: flDns.GetAll(), + Image: image, + Volumes: flVolumes.GetMap(), + VolumesFrom: strings.Join(flVolumesFrom.GetAll(), ","), + Entrypoint: entrypoint, + WorkingDir: *flWorkingDir, + } + + hostConfig := &HostConfig{ + Binds: binds, + ContainerIDFile: *flContainerIDFile, + LxcConf: lxcConf, + Privileged: *flPrivileged, + PortBindings: portBindings, + Links: flLinks.GetAll(), + PublishAllPorts: *flPublishAll, + } + + if sysInfo != nil && flMemory > 0 && !sysInfo.SwapLimit { + //fmt.Fprintf(stdout, "WARNING: Your kernel does not support swap limit capabilities. Limitation discarded.\n") + config.MemorySwap = -1 + } + + // When allocating stdin in attached mode, close stdin at client disconnect + if config.OpenStdin && config.AttachStdin { + config.StdinOnce = true + } + return config, hostConfig, cmd, nil +} + +func parseLxcConfOpts(opts opts.ListOpts) ([]KeyValuePair, error) { + out := make([]KeyValuePair, opts.Len()) + for i, o := range opts.GetAll() { + k, v, err := parseLxcOpt(o) + if err != nil { + return nil, err + } + out[i] = KeyValuePair{Key: k, Value: v} + } + return out, nil +} + +func parseLxcOpt(opt string) (string, string, error) { + parts := strings.SplitN(opt, "=", 2) + if len(parts) != 2 { + return "", "", fmt.Errorf("Unable to parse lxc conf option: %s", opt) + } + return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]), nil +} diff --git a/runconfig/parse_test.go b/runconfig/parse_test.go new file mode 100644 index 0000000000..2b89e88ec3 --- /dev/null +++ b/runconfig/parse_test.go @@ -0,0 +1,22 @@ +package runconfig + +import ( + "testing" +) + +func TestParseLxcConfOpt(t *testing.T) { + opts := []string{"lxc.utsname=docker", "lxc.utsname = docker "} + + for _, o := range opts { + k, v, err := parseLxcOpt(o) + if err != nil { + t.FailNow() + } + if k != "lxc.utsname" { + t.Fail() + } + if v != "docker" { + t.Fail() + } + } +} diff --git a/runtime.go b/runtime.go index cec5444090..828d3f0e66 100644 --- a/runtime.go +++ b/runtime.go @@ -18,6 +18,7 @@ import ( "github.com/dotcloud/docker/networkdriver/portallocator" "github.com/dotcloud/docker/pkg/graphdb" "github.com/dotcloud/docker/pkg/sysinfo" + "github.com/dotcloud/docker/runconfig" "github.com/dotcloud/docker/utils" "io" "io/ioutil" @@ -329,7 +330,7 @@ func (runtime *Runtime) restore() error { } // Create creates a new container from the given configuration with a given name. -func (runtime *Runtime) Create(config *Config, name string) (*Container, []string, error) { +func (runtime *Runtime) Create(config *runconfig.Config, name string) (*Container, []string, error) { // Lookup image img, err := runtime.repositories.LookupImage(config.Image) if err != nil { @@ -347,7 +348,7 @@ func (runtime *Runtime) Create(config *Config, name string) (*Container, []strin return nil, nil, fmt.Errorf("Cannot create container with more than %d parents", MaxImageDepth) } - checkDeprecatedExpose := func(config *Config) bool { + checkDeprecatedExpose := func(config *runconfig.Config) bool { if config != nil { if config.PortSpecs != nil { for _, p := range config.PortSpecs { @@ -366,7 +367,7 @@ func (runtime *Runtime) Create(config *Config, name string) (*Container, []strin } if img.Config != nil { - if err := MergeConfig(config, img.Config); err != nil { + if err := runconfig.Merge(config, img.Config); err != nil { return nil, nil, err } } @@ -441,7 +442,7 @@ func (runtime *Runtime) Create(config *Config, name string) (*Container, []strin Path: entrypoint, Args: args, //FIXME: de-duplicate from config Config: config, - hostConfig: &HostConfig{}, + hostConfig: &runconfig.HostConfig{}, Image: img.ID, // Always use the resolved image id NetworkSettings: &NetworkSettings{}, Name: name, @@ -518,7 +519,7 @@ func (runtime *Runtime) Create(config *Config, name string) (*Container, []strin // 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) { +func (runtime *Runtime) Commit(container *Container, repository, tag, comment, author string, config *runconfig.Config) (*Image, error) { // FIXME: freeze the container before copying it to avoid data corruption? // FIXME: this shouldn't be in commands. if err := container.Mount(); err != nil { diff --git a/server.go b/server.go index cb677266e5..7c60f29378 100644 --- a/server.go +++ b/server.go @@ -10,6 +10,7 @@ import ( "github.com/dotcloud/docker/engine" "github.com/dotcloud/docker/pkg/graphdb" "github.com/dotcloud/docker/registry" + "github.com/dotcloud/docker/runconfig" "github.com/dotcloud/docker/utils" "io" "io/ioutil" @@ -662,7 +663,7 @@ func (srv *Server) ImageInsert(job *engine.Job) engine.Status { } defer file.Body.Close() - config, _, _, err := ParseRun([]string{img.ID, "echo", "insert", url, path}, srv.runtime.sysInfo) + config, _, _, err := runconfig.Parse([]string{img.ID, "echo", "insert", url, path}, srv.runtime.sysInfo) if err != nil { return job.Error(err) } @@ -1043,7 +1044,7 @@ func (srv *Server) ContainerCommit(job *engine.Job) engine.Status { if container == nil { return job.Errorf("No such container: %s", name) } - var config Config + var config runconfig.Config if err := job.GetenvJson("config", &config); err != nil { return job.Error(err) } @@ -1623,7 +1624,7 @@ func (srv *Server) ContainerCreate(job *engine.Job) engine.Status { } else if len(job.Args) > 1 { return job.Errorf("Usage: %s", job.Name) } - config := ContainerConfigFromJob(job) + config := runconfig.ContainerConfigFromJob(job) if config.Memory != 0 && config.Memory < 524288 { return job.Errorf("Minimum memory limit allowed is 512k") } @@ -1989,7 +1990,7 @@ func (srv *Server) canDeleteImage(imgID string) error { return nil } -func (srv *Server) ImageGetCached(imgID string, config *Config) (*Image, error) { +func (srv *Server) ImageGetCached(imgID string, config *runconfig.Config) (*Image, error) { // Retrieve all images images, err := srv.runtime.graph.Map() @@ -2013,7 +2014,7 @@ func (srv *Server) ImageGetCached(imgID string, config *Config) (*Image, error) if err != nil { return nil, err } - if CompareConfig(&img.ContainerConfig, config) { + if runconfig.Compare(&img.ContainerConfig, config) { if match == nil || match.Created.Before(img.Created) { match = img } @@ -2022,7 +2023,7 @@ func (srv *Server) ImageGetCached(imgID string, config *Config) (*Image, error) return match, nil } -func (srv *Server) RegisterLinks(container *Container, hostConfig *HostConfig) error { +func (srv *Server) RegisterLinks(container *Container, hostConfig *runconfig.HostConfig) error { runtime := srv.runtime if hostConfig != nil && hostConfig.Links != nil { @@ -2066,7 +2067,7 @@ func (srv *Server) ContainerStart(job *engine.Job) engine.Status { } // If no environment was set, then no hostconfig was passed. if len(job.Environ()) > 0 { - hostConfig := ContainerHostConfigFromJob(job) + hostConfig := runconfig.ContainerHostConfigFromJob(job) // Validate the HostConfig binds. Make sure that: // 1) the source of a bind mount isn't / // The bind mount "/:/foo" isn't allowed. @@ -2310,7 +2311,7 @@ func (srv *Server) JobInspect(job *engine.Job) engine.Status { } object = &struct { *Container - HostConfig *HostConfig + HostConfig *runconfig.HostConfig }{container, container.hostConfig} default: return job.Errorf("Unknown kind: %s", kind) diff --git a/utils.go b/utils.go index e3a58cc67e..68cd2f24e5 100644 --- a/utils.go +++ b/utils.go @@ -1,14 +1,12 @@ package docker import ( - "fmt" "github.com/dotcloud/docker/archive" "github.com/dotcloud/docker/nat" "github.com/dotcloud/docker/pkg/namesgenerator" - "github.com/dotcloud/docker/pkg/opts" + "github.com/dotcloud/docker/runconfig" "github.com/dotcloud/docker/utils" "io" - "strings" "sync/atomic" ) @@ -16,204 +14,7 @@ type Change struct { archive.Change } -// Compare two Config struct. Do not compare the "Image" nor "Hostname" fields -// If OpenStdin is set, then it differs -func CompareConfig(a, b *Config) bool { - if a == nil || b == nil || - a.OpenStdin || b.OpenStdin { - return false - } - if a.AttachStdout != b.AttachStdout || - a.AttachStderr != b.AttachStderr || - a.User != b.User || - a.Memory != b.Memory || - a.MemorySwap != b.MemorySwap || - a.CpuShares != b.CpuShares || - a.OpenStdin != b.OpenStdin || - a.Tty != b.Tty || - a.VolumesFrom != b.VolumesFrom { - return false - } - if len(a.Cmd) != len(b.Cmd) || - len(a.Dns) != len(b.Dns) || - len(a.Env) != len(b.Env) || - len(a.PortSpecs) != len(b.PortSpecs) || - len(a.ExposedPorts) != len(b.ExposedPorts) || - len(a.Entrypoint) != len(b.Entrypoint) || - len(a.Volumes) != len(b.Volumes) { - return false - } - - for i := 0; i < len(a.Cmd); i++ { - if a.Cmd[i] != b.Cmd[i] { - return false - } - } - for i := 0; i < len(a.Dns); i++ { - if a.Dns[i] != b.Dns[i] { - return false - } - } - for i := 0; i < len(a.Env); i++ { - if a.Env[i] != b.Env[i] { - return false - } - } - for i := 0; i < len(a.PortSpecs); i++ { - if a.PortSpecs[i] != b.PortSpecs[i] { - return false - } - } - for k := range a.ExposedPorts { - if _, exists := b.ExposedPorts[k]; !exists { - return false - } - } - for i := 0; i < len(a.Entrypoint); i++ { - if a.Entrypoint[i] != b.Entrypoint[i] { - return false - } - } - for key := range a.Volumes { - if _, exists := b.Volumes[key]; !exists { - return false - } - } - return true -} - -func MergeConfig(userConf, imageConf *Config) error { - if userConf.User == "" { - userConf.User = imageConf.User - } - if userConf.Memory == 0 { - userConf.Memory = imageConf.Memory - } - if userConf.MemorySwap == 0 { - userConf.MemorySwap = imageConf.MemorySwap - } - if userConf.CpuShares == 0 { - userConf.CpuShares = imageConf.CpuShares - } - if userConf.ExposedPorts == nil || len(userConf.ExposedPorts) == 0 { - userConf.ExposedPorts = imageConf.ExposedPorts - } else if imageConf.ExposedPorts != nil { - if userConf.ExposedPorts == nil { - userConf.ExposedPorts = make(nat.PortSet) - } - for port := range imageConf.ExposedPorts { - if _, exists := userConf.ExposedPorts[port]; !exists { - userConf.ExposedPorts[port] = struct{}{} - } - } - } - - if userConf.PortSpecs != nil && len(userConf.PortSpecs) > 0 { - if userConf.ExposedPorts == nil { - userConf.ExposedPorts = make(nat.PortSet) - } - ports, _, err := nat.ParsePortSpecs(userConf.PortSpecs) - if err != nil { - return err - } - for port := range ports { - if _, exists := userConf.ExposedPorts[port]; !exists { - userConf.ExposedPorts[port] = struct{}{} - } - } - userConf.PortSpecs = nil - } - if imageConf.PortSpecs != nil && len(imageConf.PortSpecs) > 0 { - utils.Debugf("Migrating image port specs to containter: %s", strings.Join(imageConf.PortSpecs, ", ")) - if userConf.ExposedPorts == nil { - userConf.ExposedPorts = make(nat.PortSet) - } - - ports, _, err := nat.ParsePortSpecs(imageConf.PortSpecs) - if err != nil { - return err - } - for port := range ports { - if _, exists := userConf.ExposedPorts[port]; !exists { - userConf.ExposedPorts[port] = struct{}{} - } - } - } - if !userConf.Tty { - userConf.Tty = imageConf.Tty - } - if !userConf.OpenStdin { - userConf.OpenStdin = imageConf.OpenStdin - } - if !userConf.StdinOnce { - userConf.StdinOnce = imageConf.StdinOnce - } - if userConf.Env == nil || len(userConf.Env) == 0 { - userConf.Env = imageConf.Env - } else { - for _, imageEnv := range imageConf.Env { - found := false - imageEnvKey := strings.Split(imageEnv, "=")[0] - for _, userEnv := range userConf.Env { - userEnvKey := strings.Split(userEnv, "=")[0] - if imageEnvKey == userEnvKey { - found = true - } - } - if !found { - userConf.Env = append(userConf.Env, imageEnv) - } - } - } - if userConf.Cmd == nil || len(userConf.Cmd) == 0 { - userConf.Cmd = imageConf.Cmd - } - if userConf.Dns == nil || len(userConf.Dns) == 0 { - userConf.Dns = imageConf.Dns - } else { - //duplicates aren't an issue here - userConf.Dns = append(userConf.Dns, imageConf.Dns...) - } - if userConf.Entrypoint == nil || len(userConf.Entrypoint) == 0 { - userConf.Entrypoint = imageConf.Entrypoint - } - if userConf.WorkingDir == "" { - userConf.WorkingDir = imageConf.WorkingDir - } - if userConf.VolumesFrom == "" { - userConf.VolumesFrom = imageConf.VolumesFrom - } - if userConf.Volumes == nil || len(userConf.Volumes) == 0 { - userConf.Volumes = imageConf.Volumes - } else { - for k, v := range imageConf.Volumes { - userConf.Volumes[k] = v - } - } - return nil -} - -func parseLxcConfOpts(opts opts.ListOpts) ([]KeyValuePair, error) { - out := make([]KeyValuePair, opts.Len()) - for i, o := range opts.GetAll() { - k, v, err := parseLxcOpt(o) - if err != nil { - return nil, err - } - out[i] = KeyValuePair{Key: k, Value: v} - } - return out, nil -} - -func parseLxcOpt(opt string) (string, string, error) { - parts := strings.SplitN(opt, "=", 2) - if len(parts) != 2 { - return "", "", fmt.Errorf("Unable to parse lxc conf option: %s", opt) - } - return strings.TrimSpace(parts[0]), strings.TrimSpace(parts[1]), nil -} - -func migratePortMappings(config *Config, hostConfig *HostConfig) error { +func migratePortMappings(config *runconfig.Config, hostConfig *runconfig.HostConfig) error { if config.PortSpecs != nil { ports, bindings, err := nat.ParsePortSpecs(config.PortSpecs) if err != nil { @@ -222,7 +23,7 @@ func migratePortMappings(config *Config, hostConfig *HostConfig) error { config.PortSpecs = nil if len(bindings) > 0 { if hostConfig == nil { - hostConfig = &HostConfig{} + hostConfig = &runconfig.HostConfig{} } hostConfig.PortBindings = bindings }