Merge pull request #1640 from nathanleclaire/revert_ngb2d

Revert "Merge pull request #1552 from ehazlett/b2d-next"
This commit is contained in:
Nathan LeClaire 2015-08-03 15:20:39 -07:00
commit af08468f00
20 changed files with 598 additions and 1328 deletions

5
Godeps/Godeps.json generated
View File

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

View File

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

View File

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

View File

@ -1,2 +0,0 @@
Tianon Gravi <admwiggin@gmail.com> (@tianon)
Aleksa Sarai <cyphar@cyphar.com> (@cyphar)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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