diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index afd4c7a9bc..18dd12df0b 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -46,11 +46,6 @@ "Comment": "v1.5.0", "Rev": "a8a31eff10544860d2188dddabdee4d727545796" }, - { - "ImportPath": "github.com/docker/docker/pkg/homedir", - "Comment": "v1.7.0", - "Rev": "0baf60984522744eed290348f33f396c046b2f3a" - }, { "ImportPath": "github.com/docker/docker/pkg/ioutils", "Comment": "v1.5.0", diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/homedir/homedir.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/homedir/homedir.go deleted file mode 100644 index 61137a8f5d..0000000000 --- a/Godeps/_workspace/src/github.com/docker/docker/pkg/homedir/homedir.go +++ /dev/null @@ -1,39 +0,0 @@ -package homedir - -import ( - "os" - "runtime" - - "github.com/docker/libcontainer/user" -) - -// Key returns the env var name for the user's home dir based on -// the platform being run on -func Key() string { - if runtime.GOOS == "windows" { - return "USERPROFILE" - } - return "HOME" -} - -// Get returns the home directory of the current user with the help of -// environment variables depending on the target operating system. -// Returned path should be used with "path/filepath" to form new paths. -func Get() string { - home := os.Getenv(Key()) - if home == "" && runtime.GOOS != "windows" { - if u, err := user.CurrentUser(); err == nil { - return u.Home - } - } - return home -} - -// GetShortcutString returns the string that is shortcut to user's home directory -// in the native shell of the platform running on. -func GetShortcutString() string { - if runtime.GOOS == "windows" { - return "%USERPROFILE%" // be careful while using in format functions - } - return "~" -} diff --git a/Godeps/_workspace/src/github.com/docker/docker/pkg/homedir/homedir_test.go b/Godeps/_workspace/src/github.com/docker/docker/pkg/homedir/homedir_test.go deleted file mode 100644 index 7a95cb2bd7..0000000000 --- a/Godeps/_workspace/src/github.com/docker/docker/pkg/homedir/homedir_test.go +++ /dev/null @@ -1,24 +0,0 @@ -package homedir - -import ( - "path/filepath" - "testing" -) - -func TestGet(t *testing.T) { - home := Get() - if home == "" { - t.Fatal("returned home directory is empty") - } - - if !filepath.IsAbs(home) { - t.Fatalf("returned path is not absolute: %s", home) - } -} - -func TestGetShortcutString(t *testing.T) { - shortcut := GetShortcutString() - if shortcut == "" { - t.Fatal("returned shortcut string is empty") - } -} diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/user/MAINTAINERS b/Godeps/_workspace/src/github.com/docker/libcontainer/user/MAINTAINERS deleted file mode 100644 index edbe200669..0000000000 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/user/MAINTAINERS +++ /dev/null @@ -1,2 +0,0 @@ -Tianon Gravi (@tianon) -Aleksa Sarai (@cyphar) diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup.go b/Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup.go deleted file mode 100644 index 6f8a982ff7..0000000000 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup.go +++ /dev/null @@ -1,108 +0,0 @@ -package user - -import ( - "errors" - "fmt" - "syscall" -) - -var ( - // The current operating system does not provide the required data for user lookups. - ErrUnsupported = errors.New("user lookup: operating system does not provide passwd-formatted data") -) - -func lookupUser(filter func(u User) bool) (User, error) { - // Get operating system-specific passwd reader-closer. - passwd, err := GetPasswd() - if err != nil { - return User{}, err - } - defer passwd.Close() - - // Get the users. - users, err := ParsePasswdFilter(passwd, filter) - if err != nil { - return User{}, err - } - - // No user entries found. - if len(users) == 0 { - return User{}, fmt.Errorf("no matching entries in passwd file") - } - - // Assume the first entry is the "correct" one. - return users[0], nil -} - -// CurrentUser looks up the current user by their user id in /etc/passwd. If the -// user cannot be found (or there is no /etc/passwd file on the filesystem), -// then CurrentUser returns an error. -func CurrentUser() (User, error) { - return LookupUid(syscall.Getuid()) -} - -// LookupUser looks up a user by their username in /etc/passwd. If the user -// cannot be found (or there is no /etc/passwd file on the filesystem), then -// LookupUser returns an error. -func LookupUser(username string) (User, error) { - return lookupUser(func(u User) bool { - return u.Name == username - }) -} - -// LookupUid looks up a user by their user id in /etc/passwd. If the user cannot -// be found (or there is no /etc/passwd file on the filesystem), then LookupId -// returns an error. -func LookupUid(uid int) (User, error) { - return lookupUser(func(u User) bool { - return u.Uid == uid - }) -} - -func lookupGroup(filter func(g Group) bool) (Group, error) { - // Get operating system-specific group reader-closer. - group, err := GetGroup() - if err != nil { - return Group{}, err - } - defer group.Close() - - // Get the users. - groups, err := ParseGroupFilter(group, filter) - if err != nil { - return Group{}, err - } - - // No user entries found. - if len(groups) == 0 { - return Group{}, fmt.Errorf("no matching entries in group file") - } - - // Assume the first entry is the "correct" one. - return groups[0], nil -} - -// CurrentGroup looks up the current user's group by their primary group id's -// entry in /etc/passwd. If the group cannot be found (or there is no -// /etc/group file on the filesystem), then CurrentGroup returns an error. -func CurrentGroup() (Group, error) { - return LookupGid(syscall.Getgid()) -} - -// LookupGroup looks up a group by its name in /etc/group. If the group cannot -// be found (or there is no /etc/group file on the filesystem), then LookupGroup -// returns an error. -func LookupGroup(groupname string) (Group, error) { - return lookupGroup(func(g Group) bool { - return g.Name == groupname - }) -} - -// LookupGid looks up a group by its group id in /etc/group. If the group cannot -// be found (or there is no /etc/group file on the filesystem), then LookupGid -// returns an error. -func LookupGid(gid int) (Group, error) { - return lookupGroup(func(g Group) bool { - return g.Gid == gid - }) -} diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup_unix.go b/Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup_unix.go deleted file mode 100644 index 758b734c22..0000000000 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup_unix.go +++ /dev/null @@ -1,30 +0,0 @@ -// +build darwin dragonfly freebsd linux netbsd openbsd solaris - -package user - -import ( - "io" - "os" -) - -// Unix-specific path to the passwd and group formatted files. -const ( - unixPasswdPath = "/etc/passwd" - unixGroupPath = "/etc/group" -) - -func GetPasswdPath() (string, error) { - return unixPasswdPath, nil -} - -func GetPasswd() (io.ReadCloser, error) { - return os.Open(unixPasswdPath) -} - -func GetGroupPath() (string, error) { - return unixGroupPath, nil -} - -func GetGroup() (io.ReadCloser, error) { - return os.Open(unixGroupPath) -} diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup_unsupported.go b/Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup_unsupported.go deleted file mode 100644 index 7217948870..0000000000 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup_unsupported.go +++ /dev/null @@ -1,21 +0,0 @@ -// +build !darwin,!dragonfly,!freebsd,!linux,!netbsd,!openbsd,!solaris - -package user - -import "io" - -func GetPasswdPath() (string, error) { - return "", ErrUnsupported -} - -func GetPasswd() (io.ReadCloser, error) { - return nil, ErrUnsupported -} - -func GetGroupPath() (string, error) { - return "", ErrUnsupported -} - -func GetGroup() (io.ReadCloser, error) { - return nil, ErrUnsupported -} diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/user/user.go b/Godeps/_workspace/src/github.com/docker/libcontainer/user/user.go deleted file mode 100644 index d7439f12e3..0000000000 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/user/user.go +++ /dev/null @@ -1,350 +0,0 @@ -package user - -import ( - "bufio" - "fmt" - "io" - "os" - "strconv" - "strings" -) - -const ( - minId = 0 - maxId = 1<<31 - 1 //for 32-bit systems compatibility -) - -var ( - ErrRange = fmt.Errorf("Uids and gids must be in range %d-%d", minId, maxId) -) - -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 - } - - 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!") - } - } -} - -func ParsePasswdFile(path string) ([]User, error) { - passwd, err := os.Open(path) - if err != nil { - return nil, err - } - defer passwd.Close() - return ParsePasswd(passwd) -} - -func ParsePasswd(passwd io.Reader) ([]User, error) { - return ParsePasswdFilter(passwd, nil) -} - -func ParsePasswdFileFilter(path string, filter func(User) bool) ([]User, error) { - passwd, err := os.Open(path) - if err != nil { - return nil, err - } - defer passwd.Close() - return ParsePasswdFilter(passwd, filter) -} - -func ParsePasswdFilter(r io.Reader, filter func(User) bool) ([]User, error) { - if r == nil { - return nil, fmt.Errorf("nil source for passwd-formatted data") - } - - 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 ParseGroupFile(path string) ([]Group, error) { - group, err := os.Open(path) - if err != nil { - return nil, err - } - defer group.Close() - return ParseGroup(group) -} - -func ParseGroup(group io.Reader) ([]Group, error) { - return ParseGroupFilter(group, nil) -} - -func ParseGroupFileFilter(path string, filter func(Group) bool) ([]Group, error) { - group, err := os.Open(path) - if err != nil { - return nil, err - } - defer group.Close() - return ParseGroupFilter(group, filter) -} - -func ParseGroupFilter(r io.Reader, filter func(Group) bool) ([]Group, error) { - if r == nil { - return nil, fmt.Errorf("nil source for group-formatted data") - } - - 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 -} - -type ExecUser struct { - Uid, Gid int - Sgids []int - Home string -} - -// GetExecUserPath is a wrapper for GetExecUser. It reads data from each of the -// given file paths and uses that data as the arguments to GetExecUser. If the -// files cannot be opened for any reason, the error is ignored and a nil -// io.Reader is passed instead. -func GetExecUserPath(userSpec string, defaults *ExecUser, passwdPath, groupPath string) (*ExecUser, error) { - passwd, err := os.Open(passwdPath) - if err != nil { - passwd = nil - } else { - defer passwd.Close() - } - - group, err := os.Open(groupPath) - if err != nil { - group = nil - } else { - defer group.Close() - } - - return GetExecUser(userSpec, defaults, passwd, group) -} - -// GetExecUser parses a user specification string (using the passwd and group -// readers as sources for /etc/passwd and /etc/group data, respectively). In -// the case of blank fields or missing data from the sources, the values in -// defaults is used. -// -// GetExecUser will return an error if a user or group literal could not be -// found in any entry in passwd and group respectively. -// -// Examples of valid user specifications are: -// * "" -// * "user" -// * "uid" -// * "user:group" -// * "uid:gid -// * "user:gid" -// * "uid:group" -func GetExecUser(userSpec string, defaults *ExecUser, passwd, group io.Reader) (*ExecUser, error) { - var ( - userArg, groupArg string - name string - ) - - if defaults == nil { - defaults = new(ExecUser) - } - - // Copy over defaults. - user := &ExecUser{ - Uid: defaults.Uid, - Gid: defaults.Gid, - Sgids: defaults.Sgids, - Home: defaults.Home, - } - - // Sgids slice *cannot* be nil. - if user.Sgids == nil { - user.Sgids = []int{} - } - - // allow for userArg to have either "user" syntax, or optionally "user:group" syntax - parseLine(userSpec, &userArg, &groupArg) - - users, err := ParsePasswdFilter(passwd, func(u User) bool { - if userArg == "" { - return u.Uid == user.Uid - } - return u.Name == userArg || strconv.Itoa(u.Uid) == userArg - }) - if err != nil && passwd != nil { - if userArg == "" { - userArg = strconv.Itoa(user.Uid) - } - return 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" - name = users[0].Name - user.Uid = users[0].Uid - user.Gid = users[0].Gid - user.Home = users[0].Home - } else if userArg != "" { - // we asked for a user but didn't find them... let's check to see if we wanted a numeric user - user.Uid, err = strconv.Atoi(userArg) - if err != nil { - // not numeric - we have to bail - return nil, fmt.Errorf("Unable to find user %v", userArg) - } - - // Must be inside valid uid range. - if user.Uid < minId || user.Uid > maxId { - return nil, ErrRange - } - - // if userArg couldn't be found in /etc/passwd but is numeric, just roll with it - this is legit - } - - if groupArg != "" || name != "" { - groups, err := ParseGroupFilter(group, func(g Group) bool { - // Explicit group format takes precedence. - if groupArg != "" { - return g.Name == groupArg || strconv.Itoa(g.Gid) == groupArg - } - - // Check if user is a member. - for _, u := range g.List { - if u == name { - return true - } - } - - return false - }) - if err != nil && group != nil { - return 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" - user.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 - user.Gid, err = strconv.Atoi(groupArg) - if err != nil { - // not numeric - we have to bail - return nil, fmt.Errorf("Unable to find group %v", groupArg) - } - - // Ensure gid is inside gid range. - if user.Gid < minId || user.Gid > maxId { - return nil, ErrRange - } - - // if groupArg couldn't be found in /etc/group but is numeric, just roll with it - this is legit - } - } else if haveGroup { - // If implicit group format, fill supplementary gids. - user.Sgids = make([]int, len(groups)) - for i, group := range groups { - user.Sgids[i] = group.Gid - } - } - } - - return user, nil -} diff --git a/Godeps/_workspace/src/github.com/docker/libcontainer/user/user_test.go b/Godeps/_workspace/src/github.com/docker/libcontainer/user/user_test.go deleted file mode 100644 index 4fe008fb39..0000000000 --- a/Godeps/_workspace/src/github.com/docker/libcontainer/user/user_test.go +++ /dev/null @@ -1,352 +0,0 @@ -package user - -import ( - "io" - "reflect" - "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 := ParsePasswdFilter(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 := ParseGroupFilter(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)) - } -} - -func TestValidGetExecUser(t *testing.T) { - const passwdContent = ` -root:x:0:0:root user:/root:/bin/bash -adm:x:42:43:adm:/var/adm:/bin/false -this is just some garbage data -` - const groupContent = ` -root:x:0:root -adm:x:43: -grp:x:1234:root,adm -this is just some garbage data -` - defaultExecUser := ExecUser{ - Uid: 8888, - Gid: 8888, - Sgids: []int{8888}, - Home: "/8888", - } - - tests := []struct { - ref string - expected ExecUser - }{ - { - ref: "root", - expected: ExecUser{ - Uid: 0, - Gid: 0, - Sgids: []int{0, 1234}, - Home: "/root", - }, - }, - { - ref: "adm", - expected: ExecUser{ - Uid: 42, - Gid: 43, - Sgids: []int{1234}, - Home: "/var/adm", - }, - }, - { - ref: "root:adm", - expected: ExecUser{ - Uid: 0, - Gid: 43, - Sgids: defaultExecUser.Sgids, - Home: "/root", - }, - }, - { - ref: "adm:1234", - expected: ExecUser{ - Uid: 42, - Gid: 1234, - Sgids: defaultExecUser.Sgids, - Home: "/var/adm", - }, - }, - { - ref: "42:1234", - expected: ExecUser{ - Uid: 42, - Gid: 1234, - Sgids: defaultExecUser.Sgids, - Home: "/var/adm", - }, - }, - { - ref: "1337:1234", - expected: ExecUser{ - Uid: 1337, - Gid: 1234, - Sgids: defaultExecUser.Sgids, - Home: defaultExecUser.Home, - }, - }, - { - ref: "1337", - expected: ExecUser{ - Uid: 1337, - Gid: defaultExecUser.Gid, - Sgids: defaultExecUser.Sgids, - Home: defaultExecUser.Home, - }, - }, - { - ref: "", - expected: ExecUser{ - Uid: defaultExecUser.Uid, - Gid: defaultExecUser.Gid, - Sgids: defaultExecUser.Sgids, - Home: defaultExecUser.Home, - }, - }, - } - - for _, test := range tests { - passwd := strings.NewReader(passwdContent) - group := strings.NewReader(groupContent) - - execUser, err := GetExecUser(test.ref, &defaultExecUser, passwd, group) - if err != nil { - t.Logf("got unexpected error when parsing '%s': %s", test.ref, err.Error()) - t.Fail() - continue - } - - if !reflect.DeepEqual(test.expected, *execUser) { - t.Logf("got: %#v", execUser) - t.Logf("expected: %#v", test.expected) - t.Fail() - continue - } - } -} - -func TestInvalidGetExecUser(t *testing.T) { - const passwdContent = ` -root:x:0:0:root user:/root:/bin/bash -adm:x:42:43:adm:/var/adm:/bin/false -this is just some garbage data -` - const groupContent = ` -root:x:0:root -adm:x:43: -grp:x:1234:root,adm -this is just some garbage data -` - - tests := []string{ - // No such user/group. - "notuser", - "notuser:notgroup", - "root:notgroup", - "notuser:adm", - "8888:notgroup", - "notuser:8888", - - // Invalid user/group values. - "-1:0", - "0:-3", - "-5:-2", - } - - for _, test := range tests { - passwd := strings.NewReader(passwdContent) - group := strings.NewReader(groupContent) - - execUser, err := GetExecUser(test, nil, passwd, group) - if err == nil { - t.Logf("got unexpected success when parsing '%s': %#v", test, execUser) - t.Fail() - continue - } - } -} - -func TestGetExecUserNilSources(t *testing.T) { - const passwdContent = ` -root:x:0:0:root user:/root:/bin/bash -adm:x:42:43:adm:/var/adm:/bin/false -this is just some garbage data -` - const groupContent = ` -root:x:0:root -adm:x:43: -grp:x:1234:root,adm -this is just some garbage data -` - - defaultExecUser := ExecUser{ - Uid: 8888, - Gid: 8888, - Sgids: []int{8888}, - Home: "/8888", - } - - tests := []struct { - ref string - passwd, group bool - expected ExecUser - }{ - { - ref: "", - passwd: false, - group: false, - expected: ExecUser{ - Uid: 8888, - Gid: 8888, - Sgids: []int{8888}, - Home: "/8888", - }, - }, - { - ref: "root", - passwd: true, - group: false, - expected: ExecUser{ - Uid: 0, - Gid: 0, - Sgids: []int{8888}, - Home: "/root", - }, - }, - { - ref: "0", - passwd: false, - group: false, - expected: ExecUser{ - Uid: 0, - Gid: 8888, - Sgids: []int{8888}, - Home: "/8888", - }, - }, - { - ref: "0:0", - passwd: false, - group: false, - expected: ExecUser{ - Uid: 0, - Gid: 0, - Sgids: []int{8888}, - Home: "/8888", - }, - }, - } - - for _, test := range tests { - var passwd, group io.Reader - - if test.passwd { - passwd = strings.NewReader(passwdContent) - } - - if test.group { - group = strings.NewReader(groupContent) - } - - execUser, err := GetExecUser(test.ref, &defaultExecUser, passwd, group) - if err != nil { - t.Logf("got unexpected error when parsing '%s': %s", test.ref, err.Error()) - t.Fail() - continue - } - - if !reflect.DeepEqual(test.expected, *execUser) { - t.Logf("got: %#v", execUser) - t.Logf("expected: %#v", test.expected) - t.Fail() - continue - } - } -} diff --git a/drivers/hyperv/hyperv_windows.go b/drivers/hyperv/hyperv_windows.go index 458fdc8f40..849cbb8ab2 100644 --- a/drivers/hyperv/hyperv_windows.go +++ b/drivers/hyperv/hyperv_windows.go @@ -1,8 +1,12 @@ package hyperv import ( + "archive/tar" + "bytes" "fmt" "io/ioutil" + "os" + "path/filepath" "time" "github.com/codegangsta/cli" @@ -13,10 +17,6 @@ import ( "github.com/docker/machine/utils" ) -const ( - isoFilename = "boot2docker-hyperv.iso" -) - type Driver struct { *drivers.BaseDriver boot2DockerURL string @@ -146,9 +146,55 @@ func (d *Driver) Create() error { d.setMachineNameIfNotSet() - b2dutils := utils.NewB2dUtils("", "", isoFilename) - if err := b2dutils.CopyIsoToMachineDir(d.boot2DockerURL, d.MachineName); err != nil { - return err + var isoURL string + + b2dutils := utils.NewB2dUtils("", "") + + if d.boot2DockerLoc == "" { + if d.boot2DockerURL != "" { + isoURL = d.boot2DockerURL + log.Infof("Downloading boot2docker.iso from %s...", isoURL) + if err := b2dutils.DownloadISO(d.ResolveStorePath("."), "boot2docker.iso", isoURL); err != nil { + return err + } + } else { + // todo: check latest release URL, download if it's new + // until then always use "latest" + isoURL, err = b2dutils.GetLatestBoot2DockerReleaseURL() + if err != nil { + log.Warnf("Unable to check for the latest release: %s", err) + + } + // todo: use real constant for .docker + rootPath := filepath.Join(utils.GetDockerDir()) + imgPath := filepath.Join(rootPath, "images") + commonIsoPath := filepath.Join(imgPath, "boot2docker.iso") + if _, err := os.Stat(commonIsoPath); os.IsNotExist(err) { + log.Infof("Downloading boot2docker.iso to %s...", commonIsoPath) + // just in case boot2docker.iso has been manually deleted + if _, err := os.Stat(imgPath); os.IsNotExist(err) { + if err := os.Mkdir(imgPath, 0700); err != nil { + return err + + } + + } + if err := b2dutils.DownloadISO(imgPath, "boot2docker.iso", isoURL); err != nil { + return err + + } + + } + isoDest := d.ResolveStorePath("boot2docker.iso") + if err := utils.CopyFile(commonIsoPath, isoDest); err != nil { + return err + + } + } + } else { + if err := utils.CopyFile(d.boot2DockerLoc, d.ResolveStorePath("boot2docker.iso")); err != nil { + return err + } } log.Infof("Creating SSH key...") @@ -164,7 +210,8 @@ func (d *Driver) Create() error { return err } - if err := d.generateDiskImage(); err != nil { + err = d.generateDiskImage() + if err != nil { return err } @@ -181,8 +228,9 @@ func (d *Driver) Create() error { command = []string{ "Set-VMDvdDrive", "-VMName", d.MachineName, - "-Path", fmt.Sprintf("'%s'", d.ResolveStorePath(isoFilename))} - if _, err = execute(command); err != nil { + "-Path", fmt.Sprintf("'%s'", d.ResolveStorePath("boot2docker.iso"))} + _, err = execute(command) + if err != nil { return err } @@ -190,7 +238,8 @@ func (d *Driver) Create() error { "Add-VMHardDiskDrive", "-VMName", d.MachineName, "-Path", fmt.Sprintf("'%s'", d.diskImage)} - if _, err = execute(command); err != nil { + _, err = execute(command) + if err != nil { return err } @@ -198,7 +247,8 @@ func (d *Driver) Create() error { "Connect-VMNetworkAdapter", "-VMName", d.MachineName, "-SwitchName", fmt.Sprintf("'%s'", virtualSwitch)} - if _, err = execute(command); err != nil { + _, err = execute(command) + if err != nil { return err } @@ -207,35 +257,6 @@ func (d *Driver) Create() error { return err } - // use ssh to set keys - sshClient, err := d.getLocalSSHClient() - if err != nil { - return err - } - - // add pub key for user - pubKey, err := ioutil.ReadFile(d.publicSSHKeyPath()) - if err != nil { - return err - } - - if out, err := sshClient.Output(fmt.Sprintf( - "mkdir -p /home/%s/.ssh", - d.GetSSHUsername(), - )); err != nil { - log.Error(out) - return err - } - - if out, err := sshClient.Output(fmt.Sprintf( - "printf '%%s' '%s' | tee /home/%s/.ssh/authorized_keys", - string(pubKey), - d.GetSSHUsername(), - )); err != nil { - log.Error(out) - return err - } - return nil } @@ -273,7 +294,8 @@ func (d *Driver) Start() error { command := []string{ "Start-VM", "-Name", d.MachineName} - if _, err := execute(command); err != nil { + _, err := execute(command) + if err != nil { return err } @@ -281,18 +303,16 @@ func (d *Driver) Start() error { return err } - if _, err := d.GetIP(); err != nil { - return err - } - - return nil + d.IPAddress, err = d.GetIP() + return err } func (d *Driver) Stop() error { command := []string{ "Stop-VM", "-Name", d.MachineName} - if _, err := execute(command); err != nil { + _, err := execute(command) + if err != nil { return err } for { @@ -324,11 +344,8 @@ func (d *Driver) Remove() error { "Remove-VM", "-Name", d.MachineName, "-Force"} - if _, err = execute(command); err != nil { - return err - } - - return nil + _, err = execute(command) + return err } func (d *Driver) Restart() error { @@ -345,7 +362,8 @@ func (d *Driver) Kill() error { "Stop-VM", "-Name", d.MachineName, "-TurnOff"} - if _, err := execute(command); err != nil { + _, err := execute(command) + if err != nil { return err } for { @@ -391,35 +409,101 @@ func (d *Driver) publicSSHKeyPath() string { } func (d *Driver) generateDiskImage() error { + // Create a small fixed vhd, put the tar in, + // convert to dynamic, then resize + d.diskImage = d.ResolveStorePath("disk.vhd") + fixed := d.ResolveStorePath("fixed.vhd") log.Infof("Creating VHD") command := []string{ "New-VHD", - "-Path", fmt.Sprintf("'%s'", d.diskImage), - "-SizeBytes", fmt.Sprintf("%dMB", d.diskSize), - } - - if _, err := execute(command); err != nil { + "-Path", fmt.Sprintf("'%s'", fixed), + "-SizeBytes", "10MB", + "-Fixed"} + _, err := execute(command) + if err != nil { return err } - return nil + tarBuf, err := d.generateTar() + if err != nil { + return err + } + + file, err := os.OpenFile(fixed, os.O_WRONLY, 0644) + if err != nil { + return err + } + defer file.Close() + file.Seek(0, os.SEEK_SET) + _, err = file.Write(tarBuf.Bytes()) + if err != nil { + return err + } + file.Close() + + command = []string{ + "Convert-VHD", + "-Path", fmt.Sprintf("'%s'", fixed), + "-DestinationPath", fmt.Sprintf("'%s'", d.diskImage), + "-VHDType", "Dynamic"} + _, err = execute(command) + if err != nil { + return err + } + command = []string{ + "Resize-VHD", + "-Path", fmt.Sprintf("'%s'", d.diskImage), + "-SizeBytes", fmt.Sprintf("%dMB", d.diskSize)} + _, err = execute(command) + if err != nil { + return err + } + + return err } -func (d *Driver) getLocalSSHClient() (ssh.Client, error) { - ip, err := d.GetIP() +// Make a boot2docker VM disk image. +// See https://github.com/boot2docker/boot2docker/blob/master/rootfs/rootfs/etc/rc.d/automount +func (d *Driver) generateTar() (*bytes.Buffer, error) { + magicString := "boot2docker, please format-me" + + buf := new(bytes.Buffer) + tw := tar.NewWriter(buf) + + // magicString first so the automount script knows to format the disk + file := &tar.Header{Name: magicString, Size: int64(len(magicString))} + if err := tw.WriteHeader(file); err != nil { + return nil, err + } + if _, err := tw.Write([]byte(magicString)); err != nil { + return nil, err + } + // .ssh/key.pub => authorized_keys + file = &tar.Header{Name: ".ssh", Typeflag: tar.TypeDir, Mode: 0700} + if err := tw.WriteHeader(file); err != nil { + return nil, err + } + pubKey, err := ioutil.ReadFile(d.publicSSHKeyPath()) if err != nil { return nil, err } - - sshAuth := &ssh.Auth{ - Passwords: []string{"docker"}, - Keys: []string{d.GetSSHKeyPath()}, - } - sshClient, err := ssh.NewNativeClient(d.GetSSHUsername(), ip, d.SSHPort, sshAuth) - if err != nil { + file = &tar.Header{Name: ".ssh/authorized_keys", Size: int64(len(pubKey)), Mode: 0644} + if err := tw.WriteHeader(file); err != nil { return nil, err } - - return sshClient, nil + if _, err := tw.Write([]byte(pubKey)); err != nil { + return nil, err + } + file = &tar.Header{Name: ".ssh/authorized_keys2", Size: int64(len(pubKey)), Mode: 0644} + if err := tw.WriteHeader(file); err != nil { + return nil, err + } + if _, err := tw.Write([]byte(pubKey)); err != nil { + return nil, err + } + if err := tw.Close(); err != nil { + return nil, err + } + return buf, nil } diff --git a/drivers/virtualbox/virtualbox.go b/drivers/virtualbox/virtualbox.go index 3e91c097bb..0fde6bb5c5 100644 --- a/drivers/virtualbox/virtualbox.go +++ b/drivers/virtualbox/virtualbox.go @@ -1,13 +1,16 @@ package virtualbox import ( + "archive/tar" + "bytes" "errors" "fmt" + "io" "io/ioutil" "math/rand" "net" "os" - "path" + "os/exec" "path/filepath" "regexp" "runtime" @@ -16,7 +19,6 @@ import ( "time" "github.com/codegangsta/cli" - "github.com/docker/docker/pkg/homedir" "github.com/docker/machine/drivers" "github.com/docker/machine/log" "github.com/docker/machine/ssh" @@ -25,7 +27,7 @@ import ( ) const ( - isoFilename = "boot2docker-virtualbox.iso" + isoFilename = "boot2docker.iso" defaultHostOnlyCIDR = "192.168.99.1/24" ) @@ -153,7 +155,7 @@ func (d *Driver) Create() error { return err } - b2dutils := utils.NewB2dUtils("", "", isoFilename) + b2dutils := utils.NewB2dUtils("", "") if err := b2dutils.CopyIsoToMachineDir(d.Boot2DockerURL, d.MachineName); err != nil { return err } @@ -201,10 +203,9 @@ func (d *Driver) Create() error { } log.Debugf("Creating disk image...") - if err := vbm("createhd", "--size", fmt.Sprintf("%d", d.DiskSize), "--format", "VMDK", "--filename", d.diskPath()); err != nil { + if err := d.generateDiskImage(d.DiskSize); err != nil { return err } - } if err := vbm("createvm", @@ -274,7 +275,7 @@ func (d *Driver) Create() error { "--port", "0", "--device", "0", "--type", "dvddrive", - "--medium", d.ResolveStorePath(isoFilename)); err != nil { + "--medium", d.ResolveStorePath("boot2docker.iso")); err != nil { return err } @@ -287,39 +288,36 @@ func (d *Driver) Create() error { return err } - shareDir := homedir.Get() - shareName := shareDir - - log.Debugf("creating share: path=%s", shareDir) - // let VBoxService do nice magic automounting (when it's used) - if err := vbm("guestproperty", "set", d.MachineName, "/VirtualBox/GuestAdd/SharedFolders/MountDir", "/"); err != nil { - return err - } if err := vbm("guestproperty", "set", d.MachineName, "/VirtualBox/GuestAdd/SharedFolders/MountPrefix", "/"); err != nil { return err } + if err := vbm("guestproperty", "set", d.MachineName, "/VirtualBox/GuestAdd/SharedFolders/MountDir", "/"); err != nil { + return err + } + + var shareName, shareDir string // TODO configurable at some point + switch runtime.GOOS { + case "windows": + shareName = "c/Users" + shareDir = "c:\\Users" + case "darwin": + shareName = "Users" + shareDir = "/Users" + // TODO "linux" + } if shareDir != "" { - log.Debugf("setting up shareDir") if _, err := os.Stat(shareDir); err != nil && !os.IsNotExist(err) { - log.Debugf("setting up share failed: %s", err) return err } else if !os.IsNotExist(err) { - // parts of the VBox internal code are buggy with share names that start with "/" - shareName = strings.TrimLeft(shareDir, "/") - - // translate to msys git path - if runtime.GOOS == "windows" { - mountName, err := translateWindowsMount(shareDir) - if err != nil { - return err - } - shareName = mountName + if shareName == "" { + // parts of the VBox internal code are buggy with share names that start with "/" + shareName = strings.TrimLeft(shareDir, "/") + // TODO do some basic Windows -> MSYS path conversion + // ie, s!^([a-z]+):[/\\]+!\1/!; s!\\!/!g } - log.Debugf("adding shared folder: name=%q dir=%q", shareName, shareDir) - // woo, shareDir exists! let's carry on! if err := vbm("sharedfolder", "add", d.MachineName, "--name", shareName, "--hostpath", shareDir, "--automount"); err != nil { return err @@ -338,47 +336,13 @@ func (d *Driver) Create() error { return err } - // use ssh to set keys - sshClient, err := d.getLocalSSHClient() - if err != nil { - return err - } - - // add pub key for user - pubKey, err := ioutil.ReadFile(d.publicSSHKeyPath()) - if err != nil { - return err - } - - if out, err := sshClient.Output(fmt.Sprintf( - "mkdir -p /home/%s/.ssh", - d.GetSSHUsername(), - )); err != nil { - log.Error(out) - return err - } - - if out, err := sshClient.Output(fmt.Sprintf( - "printf '%%s' '%s' | tee /home/%s/.ssh/authorized_keys", - string(pubKey), - d.GetSSHUsername(), - )); err != nil { - log.Error(out) - return err - } - - ip, err := d.GetIP() - if err != nil { - return err - } - d.IPAddress = ip return nil } func (d *Driver) hostOnlyIpAvailable() bool { ip, err := d.GetIP() if err != nil { - log.Debugf("ERROR getting IP: %s", err) + log.Debug("ERROR getting IP: %s", err) return false } if ip != "" { @@ -421,8 +385,8 @@ func (d *Driver) Start() error { log.Infof("VM not in restartable state") } - addr, err := d.GetSSHHostname() - if err := ssh.WaitForTCP(fmt.Sprintf("%s:%d", addr, d.SSHPort)); err != nil { + // Wait for SSH over NAT to be available before returning to user + if err := drivers.WaitForSSH(d); err != nil { return err } @@ -431,6 +395,8 @@ func (d *Driver) Start() error { return err } + d.IPAddress, err = d.GetIP() + return err } @@ -536,17 +502,11 @@ func (d *Driver) GetIP() (string, error) { return "", drivers.ErrHostIsNotRunning } - sshClient, err := d.getLocalSSHClient() + output, err := drivers.RunSSHCommandFromDriver(d, "ip addr show dev eth1") if err != nil { return "", err } - output, err := sshClient.Output("ip addr show dev eth1") - if err != nil { - log.Debug(output) - return "", err - } - log.Debugf("SSH returned: %s\nEND SSH\n", output) // parse to find: inet 192.168.59.103/24 brd 192.168.59.255 scope global eth1 @@ -569,6 +529,53 @@ func (d *Driver) diskPath() string { return d.ResolveStorePath("disk.vmdk") } +// Make a boot2docker VM disk image. +func (d *Driver) generateDiskImage(size int) error { + log.Debugf("Creating %d MB hard disk image...", size) + + magicString := "boot2docker, please format-me" + + buf := new(bytes.Buffer) + tw := tar.NewWriter(buf) + + // magicString first so the automount script knows to format the disk + file := &tar.Header{Name: magicString, Size: int64(len(magicString))} + if err := tw.WriteHeader(file); err != nil { + return err + } + if _, err := tw.Write([]byte(magicString)); err != nil { + return err + } + // .ssh/key.pub => authorized_keys + file = &tar.Header{Name: ".ssh", Typeflag: tar.TypeDir, Mode: 0700} + if err := tw.WriteHeader(file); err != nil { + return err + } + pubKey, err := ioutil.ReadFile(d.publicSSHKeyPath()) + if err != nil { + return err + } + file = &tar.Header{Name: ".ssh/authorized_keys", Size: int64(len(pubKey)), Mode: 0644} + if err := tw.WriteHeader(file); err != nil { + return err + } + if _, err := tw.Write([]byte(pubKey)); err != nil { + return err + } + file = &tar.Header{Name: ".ssh/authorized_keys2", Size: int64(len(pubKey)), Mode: 0644} + if err := tw.WriteHeader(file); err != nil { + return err + } + if _, err := tw.Write([]byte(pubKey)); err != nil { + return err + } + if err := tw.Close(); err != nil { + return err + } + raw := bytes.NewReader(buf.Bytes()) + return createDiskImage(d.diskPath(), size, raw) +} + func (d *Driver) setupHostOnlyNetwork(machineName string) error { hostOnlyCIDR := d.HostOnlyCIDR @@ -619,6 +626,69 @@ func (d *Driver) setupHostOnlyNetwork(machineName string) error { return nil } +// createDiskImage makes a disk image at dest with the given size in MB. If r is +// not nil, it will be read as a raw disk image to convert from. +func createDiskImage(dest string, size int, r io.Reader) error { + // Convert a raw image from stdin to the dest VMDK image. + sizeBytes := int64(size) << 20 // usually won't fit in 32-bit int (max 2GB) + // FIXME: why isn't this just using the vbm*() functions? + cmd := exec.Command(vboxManageCmd, "convertfromraw", "stdin", dest, + fmt.Sprintf("%d", sizeBytes), "--format", "VMDK") + + if os.Getenv("DEBUG") != "" { + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + } + + stdin, err := cmd.StdinPipe() + if err != nil { + return err + } + if err := cmd.Start(); err != nil { + return err + } + + n, err := io.Copy(stdin, r) + if err != nil { + return err + } + + // The total number of bytes written to stdin must match sizeBytes, or + // VBoxManage.exe on Windows will fail. Fill remaining with zeros. + if left := sizeBytes - n; left > 0 { + if err := zeroFill(stdin, left); err != nil { + return err + } + } + + // cmd won't exit until the stdin is closed. + if err := stdin.Close(); err != nil { + return err + } + + return cmd.Wait() +} + +// zeroFill writes n zero bytes into w. +func zeroFill(w io.Writer, n int64) error { + const blocksize = 32 << 10 + zeros := make([]byte, blocksize) + var k int + var err error + for n > 0 { + if n > blocksize { + k, err = w.Write(zeros) + } else { + k, err = w.Write(zeros[:n]) + } + if err != nil { + return err + } + n -= int64(k) + } + return nil +} + // Select an available port, trying the specified // port first, falling back on an OS selected port. func getAvailableTCPPort(port int) (int, error) { @@ -685,35 +755,3 @@ func getRandomIPinSubnet(baseIP net.IP) (net.IP, error) { return dhcpAddr, nil } - -func (d *Driver) getLocalSSHClient() (ssh.Client, error) { - sshAuth := &ssh.Auth{ - Passwords: []string{"docker"}, - Keys: []string{d.GetSSHKeyPath()}, - } - sshClient, err := ssh.NewNativeClient(d.GetSSHUsername(), "127.0.0.1", d.SSHPort, sshAuth) - if err != nil { - return nil, err - } - - return sshClient, nil -} - -func translateWindowsMount(p string) (string, error) { - re := regexp.MustCompile(`(?P[^:]+):\\(?P.*)`) - m := re.FindStringSubmatch(p) - - var drive, fullPath string - - if len(m) < 3 { - return "", fmt.Errorf("unable to parse home directory") - } - - drive = m[1] - fullPath = m[2] - - nPath := strings.Replace(fullPath, "\\", "/", -1) - - tPath := path.Join("/", strings.ToLower(drive), nPath) - return tPath, nil -} diff --git a/drivers/virtualbox/virtualbox_test.go b/drivers/virtualbox/virtualbox_test.go index 7b63f3b08d..5df5e5f4cb 100644 --- a/drivers/virtualbox/virtualbox_test.go +++ b/drivers/virtualbox/virtualbox_test.go @@ -29,39 +29,3 @@ func TestGetRandomIPinSubnet(t *testing.T) { t.Fatalf("expected third octet of %d; received %d", testIP[2], newIP[2]) } } - -func TestTranslateWindowsMount(t *testing.T) { - p1 := `C:\Users\foo` - r, err := translateWindowsMount(p1) - if err != nil { - t.Fatal(err) - } - - if r != `/c/Users/foo` { - t.Fatalf("expected to match /c/Users/foo") - } -} - -func TestTranslateWindowsMountCustomDrive(t *testing.T) { - p1 := `D:\Users\foo` - r, err := translateWindowsMount(p1) - if err != nil { - t.Fatal(err) - } - - if r != `/d/Users/foo` { - t.Fatalf("expected to match /d/Users/foo") - } -} - -func TestTranslateWindowsMountLongPath(t *testing.T) { - p1 := `c:\path\to\users\foo` - r, err := translateWindowsMount(p1) - if err != nil { - t.Fatal(err) - } - - if r != `/c/path/to/users/foo` { - t.Fatalf("expected to match /c/path/to/users/foo") - } -} diff --git a/drivers/vmwarefusion/fusion_darwin.go b/drivers/vmwarefusion/fusion_darwin.go index fbfdef00ea..30cc20c252 100644 --- a/drivers/vmwarefusion/fusion_darwin.go +++ b/drivers/vmwarefusion/fusion_darwin.go @@ -5,16 +5,17 @@ package vmwarefusion import ( + "archive/tar" "fmt" "io/ioutil" "os" "regexp" + "runtime" "strings" "text/template" "time" "github.com/codegangsta/cli" - "github.com/docker/docker/pkg/homedir" "github.com/docker/machine/drivers" "github.com/docker/machine/log" "github.com/docker/machine/ssh" @@ -24,8 +25,8 @@ import ( const ( B2DUser = "docker" - B2DPass = "docker" - isoFilename = "boot2docker-vmware.iso" + B2DPass = "tcuser" + isoFilename = "boot2docker.iso" ) // Driver for VMware Fusion @@ -160,7 +161,7 @@ func (d *Driver) PreCreateCheck() error { func (d *Driver) Create() error { - b2dutils := utils.NewB2dUtils("", "", isoFilename) + b2dutils := utils.NewB2dUtils("", "") if err := b2dutils.CopyIsoToMachineDir(d.Boot2DockerURL, d.MachineName); err != nil { return err } @@ -206,7 +207,7 @@ func (d *Driver) Create() error { log.Infof("Waiting for VM to come online...") for i := 1; i <= 60; i++ { - ip, err = d.GetIP() + ip, err = d.getIPfromDHCPLease() if err != nil { log.Debugf("Not there yet %d/%d, error: %s", i, 60, err) time.Sleep(2 * time.Second) @@ -226,42 +227,40 @@ func (d *Driver) Create() error { // we got an IP, let's copy ssh keys over d.IPAddress = ip - // use ssh to set keys - sshClient, err := d.getLocalSSHClient() - if err != nil { + // Generate a tar keys bundle + if err := d.generateKeyBundle(); err != nil { return err } - // add pub key for user - pubKey, err := ioutil.ReadFile(d.publicSSHKeyPath()) - if err != nil { - return err - } + // Test if /var/lib/boot2docker exists + vmrun("-gu", B2DUser, "-gp", B2DPass, "directoryExistsInGuest", d.vmxPath(), "/var/lib/boot2docker") - if out, err := sshClient.Output(fmt.Sprintf( - "mkdir -p /home/%s/.ssh", - d.GetSSHUsername(), - )); err != nil { - log.Error(out) - return err - } + // Copy SSH keys bundle + vmrun("-gu", B2DUser, "-gp", B2DPass, "CopyFileFromHostToGuest", d.vmxPath(), d.ResolveStorePath("userdata.tar"), "/home/docker/userdata.tar") - if out, err := sshClient.Output(fmt.Sprintf( - "printf '%%s' '%s' | tee /home/%s/.ssh/authorized_keys", - string(pubKey), - d.GetSSHUsername(), - )); err != nil { - log.Error(out) - return err - } + // Expand tar file. + vmrun("-gu", B2DUser, "-gp", B2DPass, "runScriptInGuest", d.vmxPath(), "/bin/sh", "sudo /bin/mv /home/docker/userdata.tar /var/lib/boot2docker/userdata.tar && sudo tar xf /var/lib/boot2docker/userdata.tar -C /home/docker/ > /var/log/userdata.log 2>&1 && sudo chown -R docker:staff /home/docker") // Enable Shared Folders vmrun("-gu", B2DUser, "-gp", B2DPass, "enableSharedFolders", d.vmxPath()) - if err := d.setupSharedDirs(); err != nil { - return err + var shareName, shareDir string // TODO configurable at some point + switch runtime.GOOS { + case "darwin": + shareName = "Users" + shareDir = "/Users" + // TODO "linux" and "windows" } + if shareDir != "" { + if _, err := os.Stat(shareDir); err != nil && !os.IsNotExist(err) { + return err + } else if !os.IsNotExist(err) { + // add shared folder, create mountpoint and mount it. + vmrun("-gu", B2DUser, "-gp", B2DPass, "addSharedFolder", d.vmxPath(), shareName, shareDir) + vmrun("-gu", B2DUser, "-gp", B2DPass, "runScriptInGuest", d.vmxPath(), "/bin/sh", "sudo mkdir "+shareDir+" && sudo mount -t vmhgfs .host:/"+shareName+" "+shareDir) + } + } return nil } @@ -270,8 +269,21 @@ func (d *Driver) Start() error { vmrun("start", d.vmxPath(), "nogui") log.Debugf("Mounting Shared Folders...") - if err := d.setupSharedDirs(); err != nil { - return err + var shareName, shareDir string // TODO configurable at some point + switch runtime.GOOS { + case "darwin": + shareName = "Users" + shareDir = "/Users" + // TODO "linux" and "windows" + } + + if shareDir != "" { + if _, err := os.Stat(shareDir); err != nil && !os.IsNotExist(err) { + return err + } else if !os.IsNotExist(err) { + // create mountpoint and mount shared folder + vmrun("-gu", B2DUser, "-gp", B2DPass, "runScriptInGuest", d.vmxPath(), "/bin/sh", "sudo mkdir "+shareDir+" && sudo mount -t vmhgfs .host:/"+shareName+" "+shareDir) + } } return nil @@ -406,35 +418,57 @@ func (d *Driver) publicSSHKeyPath() string { return d.GetSSHKeyPath() + ".pub" } -func (d *Driver) setupSharedDirs() error { - shareDir := homedir.Get() - shareName := "Home" +// Make a boot2docker userdata.tar key bundle +func (d *Driver) generateKeyBundle() error { + log.Debugf("Creating Tar key bundle...") - if _, err := os.Stat(shareDir); err != nil && !os.IsNotExist(err) { + magicString := "boot2docker, this is vmware speaking" + + tf, err := os.Create(d.ResolveStorePath("userdata.tar")) + if err != nil { + return err + } + defer tf.Close() + var fileWriter = tf + + tw := tar.NewWriter(fileWriter) + defer tw.Close() + + // magicString first so we can figure out who originally wrote the tar. + file := &tar.Header{Name: magicString, Size: int64(len(magicString))} + if err := tw.WriteHeader(file); err != nil { + return err + } + if _, err := tw.Write([]byte(magicString)); err != nil { + return err + } + // .ssh/key.pub => authorized_keys + file = &tar.Header{Name: ".ssh", Typeflag: tar.TypeDir, Mode: 0700} + if err := tw.WriteHeader(file); err != nil { + return err + } + pubKey, err := ioutil.ReadFile(d.publicSSHKeyPath()) + if err != nil { + return err + } + file = &tar.Header{Name: ".ssh/authorized_keys", Size: int64(len(pubKey)), Mode: 0644} + if err := tw.WriteHeader(file); err != nil { + return err + } + if _, err := tw.Write([]byte(pubKey)); err != nil { + return err + } + file = &tar.Header{Name: ".ssh/authorized_keys2", Size: int64(len(pubKey)), Mode: 0644} + if err := tw.WriteHeader(file); err != nil { + return err + } + if _, err := tw.Write([]byte(pubKey)); err != nil { + return err + } + if err := tw.Close(); err != nil { return err - } else if !os.IsNotExist(err) { - // add shared folder, create mountpoint and mount it. - vmrun("-gu", B2DUser, "-gp", B2DPass, "addSharedFolder", d.vmxPath(), shareName, shareDir) - vmrun("-gu", B2DUser, "-gp", B2DPass, "runScriptInGuest", d.vmxPath(), "/bin/sh", "sudo mkdir -p "+shareDir+" && sudo mount -t vmhgfs .host:/"+shareName+" "+shareDir) } return nil -} - -func (d *Driver) getLocalSSHClient() (ssh.Client, error) { - ip, err := d.GetIP() - if err != nil { - return nil, err - } - - sshAuth := &ssh.Auth{ - Passwords: []string{"docker"}, - Keys: []string{d.GetSSHKeyPath()}, - } - sshClient, err := ssh.NewNativeClient(d.GetSSHUsername(), ip, d.SSHPort, sshAuth) - if err != nil { - return nil, err - } - - return sshClient, nil + } diff --git a/drivers/vmwarevsphere/vsphere.go b/drivers/vmwarevsphere/vsphere.go index 2f5676f6c5..829cd5e24e 100644 --- a/drivers/vmwarevsphere/vsphere.go +++ b/drivers/vmwarevsphere/vsphere.go @@ -25,7 +25,7 @@ import ( ) const ( - isoFilename = "boot2docker-vmware.iso" + isoFilename = "boot2docker.iso" B2DISOName = isoFilename DefaultCPUNumber = 2 B2DUser = "docker" @@ -225,7 +225,7 @@ func (d *Driver) Create() error { return err } - b2dutils := utils.NewB2dUtils("", "", isoFilename) + b2dutils := utils.NewB2dUtils("", "") if err := b2dutils.CopyIsoToMachineDir(d.Boot2DockerURL, d.MachineName); err != nil { return err } diff --git a/libmachine/provision/boot2docker.go b/libmachine/provision/boot2docker.go index fcd09e3d2b..485cfb852e 100644 --- a/libmachine/provision/boot2docker.go +++ b/libmachine/provision/boot2docker.go @@ -1,19 +1,21 @@ package provision import ( - "errors" + "bytes" + "fmt" + "path" + "text/template" "github.com/docker/machine/drivers" + "github.com/docker/machine/libmachine/auth" + "github.com/docker/machine/libmachine/engine" "github.com/docker/machine/libmachine/provision/pkgaction" + "github.com/docker/machine/libmachine/swarm" "github.com/docker/machine/log" "github.com/docker/machine/state" "github.com/docker/machine/utils" ) -var ( - ErrUnknownDriver = errors.New("unknown driver") -) - func init() { Register("boot2docker", &RegisteredProvisioner{ New: NewBoot2DockerProvisioner, @@ -21,23 +23,29 @@ func init() { } func NewBoot2DockerProvisioner(d drivers.Driver) Provisioner { - g := GenericProvisioner{ - DockerOptionsDir: "/etc/docker", - DaemonOptionsFile: "/etc/systemd/system/docker.service", - OsReleaseId: "docker", - Packages: []string{}, - Driver: d, + return &Boot2DockerProvisioner{ + Driver: d, } - p := &Boot2DockerProvisioner{ - DebianProvisioner{ - GenericProvisioner: g, - }, - } - return p } type Boot2DockerProvisioner struct { - DebianProvisioner + OsReleaseInfo *OsRelease + Driver drivers.Driver + AuthOptions auth.AuthOptions + EngineOptions engine.EngineOptions + SwarmOptions swarm.SwarmOptions +} + +func (provisioner *Boot2DockerProvisioner) Service(name string, action pkgaction.ServiceAction) error { + var ( + err error + ) + + if _, err = provisioner.SSHCommand(fmt.Sprintf("sudo /etc/init.d/%s %s", name, action.String())); err != nil { + return err + } + + return nil } func (provisioner *Boot2DockerProvisioner) upgradeIso() error { @@ -45,62 +53,174 @@ func (provisioner *Boot2DockerProvisioner) upgradeIso() error { if err := provisioner.Driver.Stop(); err != nil { return err - } if err := utils.WaitFor(drivers.MachineInState(provisioner.Driver, state.Stopped)); err != nil { return err - } machineName := provisioner.GetDriver().GetMachineName() log.Infof("Upgrading machine %s...", machineName) - isoFilename := "" - switch provisioner.GetDriver().DriverName() { - case "virtualbox": - isoFilename = "boot2docker-virtualbox.iso" - case "vmwarefusion", "vmwarevsphere": - isoFilename = "boot2docker-vmware.iso" - case "hyper-v": - isoFilename = "boot2docker-hyperv.iso" - default: - return ErrUnknownDriver - } - - b2dutils := utils.NewB2dUtils("", "", isoFilename) + b2dutils := utils.NewB2dUtils("", "") // Usually we call this implicitly, but call it here explicitly to get // the latest boot2docker ISO. if err := b2dutils.DownloadLatestBoot2Docker(); err != nil { return err - } // Copy the latest version of boot2docker ISO to the machine's directory if err := b2dutils.CopyIsoToMachineDir("", machineName); err != nil { return err - } + log.Infof("Starting machine back up...") + if err := provisioner.Driver.Start(); err != nil { return err - } return utils.WaitFor(drivers.MachineInState(provisioner.Driver, state.Running)) - } func (provisioner *Boot2DockerProvisioner) Package(name string, action pkgaction.PackageAction) error { if name == "docker" && action == pkgaction.Upgrade { if err := provisioner.upgradeIso(); err != nil { return err - } - } return nil - +} + +func (provisioner *Boot2DockerProvisioner) Hostname() (string, error) { + return provisioner.SSHCommand("hostname") +} + +func (provisioner *Boot2DockerProvisioner) SetHostname(hostname string) error { + if _, err := provisioner.SSHCommand(fmt.Sprintf( + "sudo /usr/bin/sethostname %s && echo %q | sudo tee /var/lib/boot2docker/etc/hostname", + hostname, + hostname, + )); err != nil { + return err + } + + return nil +} + +func (provisioner *Boot2DockerProvisioner) GetDockerOptionsDir() string { + return "/var/lib/boot2docker" +} + +func (provisioner *Boot2DockerProvisioner) GetAuthOptions() auth.AuthOptions { + return provisioner.AuthOptions +} + +func (provisioner *Boot2DockerProvisioner) GenerateDockerOptions(dockerPort int) (*DockerOptions, error) { + var ( + engineCfg bytes.Buffer + ) + + driverNameLabel := fmt.Sprintf("provider=%s", provisioner.Driver.DriverName()) + provisioner.EngineOptions.Labels = append(provisioner.EngineOptions.Labels, driverNameLabel) + + engineConfigTmpl := ` +EXTRA_ARGS=' +{{ range .EngineOptions.Labels }}--label {{.}} +{{ end }}{{ range .EngineOptions.InsecureRegistry }}--insecure-registry {{.}} +{{ end }}{{ range .EngineOptions.RegistryMirror }}--registry-mirror {{.}} +{{ end }}{{ range .EngineOptions.ArbitraryFlags }}--{{.}} +{{ end }} +' +CACERT={{.AuthOptions.CaCertRemotePath}} +DOCKER_HOST='-H tcp://0.0.0.0:{{.DockerPort}}' +DOCKER_STORAGE={{.EngineOptions.StorageDriver}} +DOCKER_TLS=auto +SERVERKEY={{.AuthOptions.ServerKeyRemotePath}} +SERVERCERT={{.AuthOptions.ServerCertRemotePath}} + +{{range .EngineOptions.Env}}export \"{{ printf "%q" . }}\" +{{end}} +` + t, err := template.New("engineConfig").Parse(engineConfigTmpl) + if err != nil { + return nil, err + } + + engineConfigContext := EngineConfigContext{ + DockerPort: dockerPort, + AuthOptions: provisioner.AuthOptions, + EngineOptions: provisioner.EngineOptions, + } + + t.Execute(&engineCfg, engineConfigContext) + + daemonOptsDir := path.Join(provisioner.GetDockerOptionsDir(), "profile") + return &DockerOptions{ + EngineOptions: engineCfg.String(), + EngineOptionsPath: daemonOptsDir, + }, nil +} + +func (provisioner *Boot2DockerProvisioner) CompatibleWithHost() bool { + return provisioner.OsReleaseInfo.Id == "boot2docker" +} + +func (provisioner *Boot2DockerProvisioner) SetOsReleaseInfo(info *OsRelease) { + provisioner.OsReleaseInfo = info +} + +func (provisioner *Boot2DockerProvisioner) GetOsReleaseInfo() (*OsRelease, error) { + return provisioner.OsReleaseInfo, nil +} + +func (provisioner *Boot2DockerProvisioner) Provision(swarmOptions swarm.SwarmOptions, authOptions auth.AuthOptions, engineOptions engine.EngineOptions) error { + provisioner.SwarmOptions = swarmOptions + provisioner.AuthOptions = authOptions + provisioner.EngineOptions = engineOptions + + if provisioner.EngineOptions.StorageDriver == "" { + provisioner.EngineOptions.StorageDriver = "aufs" + } + + if err := provisioner.SetHostname(provisioner.Driver.GetMachineName()); err != nil { + return err + } + + ip, err := provisioner.GetDriver().GetIP() + if err != nil { + return err + } + + // b2d hosts need to wait for the daemon to be up + // before continuing with provisioning + if err := utils.WaitForDocker(ip, 2376); err != nil { + return err + } + + if err := makeDockerOptionsDir(provisioner); err != nil { + return err + } + + provisioner.AuthOptions = setRemoteAuthOptions(provisioner) + + if err := ConfigureAuth(provisioner); err != nil { + return err + } + + if err := configureSwarm(provisioner, swarmOptions, provisioner.AuthOptions); err != nil { + return err + } + + return nil +} + +func (provisioner *Boot2DockerProvisioner) SSHCommand(args string) (string, error) { + return drivers.RunSSHCommandFromDriver(provisioner.Driver, args) +} + +func (provisioner *Boot2DockerProvisioner) GetDriver() drivers.Driver { + return provisioner.Driver } diff --git a/libmachine/provision/rancheros.go b/libmachine/provision/rancheros.go index be0ef5e3eb..c71a2adebe 100644 --- a/libmachine/provision/rancheros.go +++ b/libmachine/provision/rancheros.go @@ -19,7 +19,6 @@ import ( const ( versionsUrl = "http://releases.rancher.com/os/versions.yml" isoUrl = "https://github.com/rancherio/os/releases/download/%s/machine-rancheros.iso" - isoFilename = "rancheros.iso" hostnameTmpl = `sudo mkdir -p /var/lib/rancher/conf/cloud-config.d/ sudo tee /var/lib/rancher/conf/cloud-config.d/machine-hostname.yml << EOF #cloud-config @@ -176,7 +175,7 @@ func (provisioner *RancherProvisioner) upgradeIso() error { log.Infof("Upgrading machine %s...", machineName) - b2dutils := utils.NewB2dUtils("", "", isoFilename) + b2dutils := utils.NewB2dUtils("", "") url, err := provisioner.getLatestISOURL() if err != nil { diff --git a/libmachine/provision/utils_test.go b/libmachine/provision/utils_test.go index 7fbae961e6..10617e638d 100644 --- a/libmachine/provision/utils_test.go +++ b/libmachine/provision/utils_test.go @@ -8,24 +8,11 @@ import ( "github.com/docker/machine/drivers/fakedriver" "github.com/docker/machine/libmachine/auth" - "github.com/docker/machine/libmachine/engine" ) -func engineOptions() engine.EngineOptions { - return engine.EngineOptions{ - StorageDriver: "aufs", - } -} - func TestGenerateDockerOptionsBoot2Docker(t *testing.T) { - g := GenericProvisioner{ - Driver: &fakedriver.FakeDriver{}, - EngineOptions: engineOptions(), - } p := &Boot2DockerProvisioner{ - DebianProvisioner{ - g, - }, + Driver: &fakedriver.FakeDriver{}, } dockerPort := 1234 p.AuthOptions = auth.AuthOptions{ @@ -33,38 +20,37 @@ func TestGenerateDockerOptionsBoot2Docker(t *testing.T) { ServerKeyRemotePath: "/test/server-key", ServerCertRemotePath: "/test/server-cert", } + engineConfigPath := "/var/lib/boot2docker/profile" dockerCfg, err := p.GenerateDockerOptions(dockerPort) if err != nil { t.Fatal(err) } + if dockerCfg.EngineOptionsPath != engineConfigPath { + t.Fatalf("expected engine path %s; received %s", engineConfigPath, dockerCfg.EngineOptionsPath) + } + if strings.Index(dockerCfg.EngineOptions, fmt.Sprintf("-H tcp://0.0.0.0:%d", dockerPort)) == -1 { t.Fatalf("-H docker port invalid; expected %d", dockerPort) } - if strings.Index(dockerCfg.EngineOptions, fmt.Sprintf("--tlscacert %s", p.AuthOptions.CaCertRemotePath)) == -1 { - t.Fatalf("--tlscacert option invalid; expected %s", p.AuthOptions.CaCertRemotePath) + if strings.Index(dockerCfg.EngineOptions, fmt.Sprintf("CACERT=%s", p.AuthOptions.CaCertRemotePath)) == -1 { + t.Fatalf("CACERT option invalid; expected %s", p.AuthOptions.CaCertRemotePath) } - if strings.Index(dockerCfg.EngineOptions, fmt.Sprintf("--tlscert %s", p.AuthOptions.ServerCertRemotePath)) == -1 { - t.Fatalf("--tlscert option invalid; expected %s", p.AuthOptions.ServerCertRemotePath) + if strings.Index(dockerCfg.EngineOptions, fmt.Sprintf("SERVERKEY=%s", p.AuthOptions.ServerKeyRemotePath)) == -1 { + t.Fatalf("SERVERKEY option invalid; expected %s", p.AuthOptions.ServerKeyRemotePath) } - if strings.Index(dockerCfg.EngineOptions, fmt.Sprintf("--tlskey %s", p.AuthOptions.ServerKeyRemotePath)) == -1 { - t.Fatalf("--tlskey option invalid; expected %s", p.AuthOptions.ServerKeyRemotePath) + if strings.Index(dockerCfg.EngineOptions, fmt.Sprintf("SERVERCERT=%s", p.AuthOptions.ServerCertRemotePath)) == -1 { + t.Fatalf("SERVERCERT option invalid; expected %s", p.AuthOptions.ServerCertRemotePath) } } func TestMachinePortBoot2Docker(t *testing.T) { - g := GenericProvisioner{ - Driver: &fakedriver.FakeDriver{}, - EngineOptions: engineOptions(), - } p := &Boot2DockerProvisioner{ - DebianProvisioner{ - g, - }, + Driver: &fakedriver.FakeDriver{}, } dockerPort := 2376 bindUrl := fmt.Sprintf("tcp://0.0.0.0:%d", dockerPort) @@ -95,14 +81,8 @@ func TestMachinePortBoot2Docker(t *testing.T) { } func TestMachineCustomPortBoot2Docker(t *testing.T) { - g := GenericProvisioner{ - Driver: &fakedriver.FakeDriver{}, - EngineOptions: engineOptions(), - } p := &Boot2DockerProvisioner{ - DebianProvisioner{ - g, - }, + Driver: &fakedriver.FakeDriver{}, } dockerPort := 3376 bindUrl := fmt.Sprintf("tcp://0.0.0.0:%d", dockerPort) diff --git a/test/integration/core/supported-engine-options.bats b/test/integration/core/supported-engine-options.bats index d6f06e8c2a..afa56678f9 100644 --- a/test/integration/core/supported-engine-options.bats +++ b/test/integration/core/supported-engine-options.bats @@ -5,7 +5,7 @@ load ${BASE_TEST_DIR}/helpers.bash @test "$DRIVER: create with supported engine options" { run machine create -d $DRIVER \ --engine-label spam=eggs \ - --engine-storage-driver devicemapper \ + --engine-storage-driver overlay \ --engine-insecure-registry registry.myco.com \ $NAME echo "$output" @@ -19,5 +19,5 @@ load ${BASE_TEST_DIR}/helpers.bash @test "$DRIVER: check for engine storage driver" { storage_driver_info=$(docker $(machine config $NAME) info | grep "Storage Driver") - [[ $storage_driver_info =~ "devicemapper" ]] + [[ $storage_driver_info =~ "overlay" ]] } diff --git a/utils/b2d.go b/utils/b2d.go index df130e1472..e49651a2e4 100644 --- a/utils/b2d.go +++ b/utils/b2d.go @@ -1,7 +1,7 @@ package utils import ( - //"encoding/json" + "encoding/json" "fmt" "io" "io/ioutil" @@ -45,10 +45,11 @@ type B2dUtils struct { githubBaseUrl string } -func NewB2dUtils(githubApiBaseUrl, githubBaseUrl, isoFilename string) *B2dUtils { +func NewB2dUtils(githubApiBaseUrl, githubBaseUrl string) *B2dUtils { defaultBaseApiUrl := "https://api.github.com" defaultBaseUrl := "https://github.com" imgCachePath := GetMachineCacheDir() + isoFilename := "boot2docker.iso" if githubApiBaseUrl == "" { githubApiBaseUrl = defaultBaseApiUrl @@ -70,41 +71,27 @@ func NewB2dUtils(githubApiBaseUrl, githubBaseUrl, isoFilename string) *B2dUtils // Get the latest boot2docker release tag name (e.g. "v0.6.0"). // FIXME: find or create some other way to get the "latest release" of boot2docker since the GitHub API has a pretty low rate limit on API requests func (b *B2dUtils) GetLatestBoot2DockerReleaseURL() (string, error) { - //client := getClient() - //apiUrl := fmt.Sprintf("%s/repos/boot2docker/boot2docker/releases", b.githubApiBaseUrl) - //rsp, err := client.Get(apiUrl) - //if err != nil { - // return "", err - //} - //defer rsp.Body.Close() + client := getClient() + apiUrl := fmt.Sprintf("%s/repos/boot2docker/boot2docker/releases", b.githubApiBaseUrl) + rsp, err := client.Get(apiUrl) + if err != nil { + return "", err + } + defer rsp.Body.Close() - //var t []struct { - // TagName string `json:"tag_name"` - // PreRelease bool `json:"prerelease"` - //} - //if err := json.NewDecoder(rsp.Body).Decode(&t); err != nil { - // return "", fmt.Errorf("Error demarshaling the Github API response: %s\nYou may be getting rate limited by Github.", err) - //} - //if len(t) == 0 { - // return "", fmt.Errorf("no releases found") - //} - - //// find the latest "released" release (i.e. not pre-release) - //isoUrl := "" - //for _, r := range t { - // if !r.PreRelease { - // tag := r.TagName - // isoUrl = fmt.Sprintf("%s/boot2docker/boot2docker/releases/download/%s/boot2docker.iso", b.githubBaseUrl, tag) - // break - // } - //} - //return isoUrl, nil - - // TODO: once we decide on the final versioning and location we will - // enable the above "check for latest" - u := fmt.Sprintf("https://s3.amazonaws.com/docker-mcn/public/b2d-next/%s", b.isoFilename) - return u, nil + var t []struct { + TagName string `json:"tag_name"` + } + if err := json.NewDecoder(rsp.Body).Decode(&t); err != nil { + return "", fmt.Errorf("Error demarshaling the Github API response: %s\nYou may be getting rate limited by Github.", err) + } + if len(t) == 0 { + return "", fmt.Errorf("no releases found") + } + tag := t[0].TagName + isoUrl := fmt.Sprintf("%s/boot2docker/boot2docker/releases/download/%s/boot2docker.iso", b.githubBaseUrl, tag) + return isoUrl, nil } func removeFileIfExists(name string) error { diff --git a/utils/b2d_test.go b/utils/b2d_test.go index e780a16e7b..2fe5a38103 100644 --- a/utils/b2d_test.go +++ b/utils/b2d_test.go @@ -1,6 +1,7 @@ package utils import ( + "fmt" "io/ioutil" "net/http" "net/http/httptest" @@ -10,26 +11,20 @@ import ( func TestGetLatestBoot2DockerReleaseUrl(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - respText := `[{"tag_name": "0.2", "prerelease": true, "tag_name": "0.1", "prerelease": false}]` + respText := `[{"tag_name": "0.1"}]` w.Write([]byte(respText)) })) defer ts.Close() - b := NewB2dUtils(ts.URL, ts.URL, "virtualbox") + b := NewB2dUtils(ts.URL, ts.URL) isoUrl, err := b.GetLatestBoot2DockerReleaseURL() if err != nil { t.Fatal(err) } - // TODO: update to release URL once we get the releases worked - // out for b2d-ng - //expectedUrl := fmt.Sprintf("%s/boot2docker/boot2docker/releases/download/0.1/boot2docker.iso", ts.URL) - //if isoUrl != expectedUrl { - // t.Fatalf("expected url %s; received %s", isoUrl) - //} - - if isoUrl == "" { - t.Fatalf("expected a url for the iso") + expectedUrl := fmt.Sprintf("%s/boot2docker/boot2docker/releases/download/0.1/boot2docker.iso", ts.URL) + if isoUrl != expectedUrl { + t.Fatalf("expected url %s; received %s", isoUrl) } } @@ -47,7 +42,7 @@ func TestDownloadIso(t *testing.T) { t.Fatal(err) } - b := NewB2dUtils(ts.URL, ts.URL, "") + b := NewB2dUtils(ts.URL, ts.URL) if err := b.DownloadISO(tmpDir, filename, ts.URL); err != nil { t.Fatal(err) }