mirror of https://github.com/docker/docs.git
Merge pull request #1640 from nathanleclaire/revert_ngb2d
Revert "Merge pull request #1552 from ehazlett/b2d-next"
This commit is contained in:
commit
af08468f00
|
@ -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",
|
||||
|
|
|
@ -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 "~"
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
}
|
|
@ -1,2 +0,0 @@
|
|||
Tianon Gravi <admwiggin@gmail.com> (@tianon)
|
||||
Aleksa Sarai <cyphar@cyphar.com> (@cyphar)
|
|
@ -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
|
||||
})
|
||||
}
|
|
@ -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)
|
||||
}
|
21
Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup_unsupported.go
generated
vendored
21
Godeps/_workspace/src/github.com/docker/libcontainer/user/lookup_unsupported.go
generated
vendored
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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<drive>[^:]+):\\(?P<path>.*)`)
|
||||
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
|
||||
}
|
||||
|
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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" ]]
|
||||
}
|
||||
|
|
57
utils/b2d.go
57
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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue