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",
|
"Comment": "v1.5.0",
|
||||||
"Rev": "a8a31eff10544860d2188dddabdee4d727545796"
|
"Rev": "a8a31eff10544860d2188dddabdee4d727545796"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"ImportPath": "github.com/docker/docker/pkg/homedir",
|
|
||||||
"Comment": "v1.7.0",
|
|
||||||
"Rev": "0baf60984522744eed290348f33f396c046b2f3a"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"ImportPath": "github.com/docker/docker/pkg/ioutils",
|
"ImportPath": "github.com/docker/docker/pkg/ioutils",
|
||||||
"Comment": "v1.5.0",
|
"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
|
package hyperv
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"archive/tar"
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
|
@ -13,10 +17,6 @@ import (
|
||||||
"github.com/docker/machine/utils"
|
"github.com/docker/machine/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
isoFilename = "boot2docker-hyperv.iso"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Driver struct {
|
type Driver struct {
|
||||||
*drivers.BaseDriver
|
*drivers.BaseDriver
|
||||||
boot2DockerURL string
|
boot2DockerURL string
|
||||||
|
@ -146,10 +146,56 @@ func (d *Driver) Create() error {
|
||||||
|
|
||||||
d.setMachineNameIfNotSet()
|
d.setMachineNameIfNotSet()
|
||||||
|
|
||||||
b2dutils := utils.NewB2dUtils("", "", isoFilename)
|
var isoURL string
|
||||||
if err := b2dutils.CopyIsoToMachineDir(d.boot2DockerURL, d.MachineName); err != nil {
|
|
||||||
|
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
|
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...")
|
log.Infof("Creating SSH key...")
|
||||||
|
|
||||||
|
@ -164,7 +210,8 @@ func (d *Driver) Create() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := d.generateDiskImage(); err != nil {
|
err = d.generateDiskImage()
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -181,8 +228,9 @@ func (d *Driver) Create() error {
|
||||||
command = []string{
|
command = []string{
|
||||||
"Set-VMDvdDrive",
|
"Set-VMDvdDrive",
|
||||||
"-VMName", d.MachineName,
|
"-VMName", d.MachineName,
|
||||||
"-Path", fmt.Sprintf("'%s'", d.ResolveStorePath(isoFilename))}
|
"-Path", fmt.Sprintf("'%s'", d.ResolveStorePath("boot2docker.iso"))}
|
||||||
if _, err = execute(command); err != nil {
|
_, err = execute(command)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,7 +238,8 @@ func (d *Driver) Create() error {
|
||||||
"Add-VMHardDiskDrive",
|
"Add-VMHardDiskDrive",
|
||||||
"-VMName", d.MachineName,
|
"-VMName", d.MachineName,
|
||||||
"-Path", fmt.Sprintf("'%s'", d.diskImage)}
|
"-Path", fmt.Sprintf("'%s'", d.diskImage)}
|
||||||
if _, err = execute(command); err != nil {
|
_, err = execute(command)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -198,7 +247,8 @@ func (d *Driver) Create() error {
|
||||||
"Connect-VMNetworkAdapter",
|
"Connect-VMNetworkAdapter",
|
||||||
"-VMName", d.MachineName,
|
"-VMName", d.MachineName,
|
||||||
"-SwitchName", fmt.Sprintf("'%s'", virtualSwitch)}
|
"-SwitchName", fmt.Sprintf("'%s'", virtualSwitch)}
|
||||||
if _, err = execute(command); err != nil {
|
_, err = execute(command)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -207,35 +257,6 @@ func (d *Driver) Create() error {
|
||||||
return err
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -273,7 +294,8 @@ func (d *Driver) Start() error {
|
||||||
command := []string{
|
command := []string{
|
||||||
"Start-VM",
|
"Start-VM",
|
||||||
"-Name", d.MachineName}
|
"-Name", d.MachineName}
|
||||||
if _, err := execute(command); err != nil {
|
_, err := execute(command)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -281,18 +303,16 @@ func (d *Driver) Start() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := d.GetIP(); err != nil {
|
d.IPAddress, err = d.GetIP()
|
||||||
return err
|
return err
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Driver) Stop() error {
|
func (d *Driver) Stop() error {
|
||||||
command := []string{
|
command := []string{
|
||||||
"Stop-VM",
|
"Stop-VM",
|
||||||
"-Name", d.MachineName}
|
"-Name", d.MachineName}
|
||||||
if _, err := execute(command); err != nil {
|
_, err := execute(command)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
|
@ -324,11 +344,8 @@ func (d *Driver) Remove() error {
|
||||||
"Remove-VM",
|
"Remove-VM",
|
||||||
"-Name", d.MachineName,
|
"-Name", d.MachineName,
|
||||||
"-Force"}
|
"-Force"}
|
||||||
if _, err = execute(command); err != nil {
|
_, err = execute(command)
|
||||||
return err
|
return err
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Driver) Restart() error {
|
func (d *Driver) Restart() error {
|
||||||
|
@ -345,7 +362,8 @@ func (d *Driver) Kill() error {
|
||||||
"Stop-VM",
|
"Stop-VM",
|
||||||
"-Name", d.MachineName,
|
"-Name", d.MachineName,
|
||||||
"-TurnOff"}
|
"-TurnOff"}
|
||||||
if _, err := execute(command); err != nil {
|
_, err := execute(command)
|
||||||
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for {
|
for {
|
||||||
|
@ -391,35 +409,101 @@ func (d *Driver) publicSSHKeyPath() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Driver) generateDiskImage() error {
|
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")
|
d.diskImage = d.ResolveStorePath("disk.vhd")
|
||||||
|
fixed := d.ResolveStorePath("fixed.vhd")
|
||||||
log.Infof("Creating VHD")
|
log.Infof("Creating VHD")
|
||||||
command := []string{
|
command := []string{
|
||||||
"New-VHD",
|
"New-VHD",
|
||||||
"-Path", fmt.Sprintf("'%s'", d.diskImage),
|
"-Path", fmt.Sprintf("'%s'", fixed),
|
||||||
"-SizeBytes", fmt.Sprintf("%dMB", d.diskSize),
|
"-SizeBytes", "10MB",
|
||||||
}
|
"-Fixed"}
|
||||||
|
_, err := execute(command)
|
||||||
if _, err := execute(command); err != nil {
|
if err != nil {
|
||||||
return err
|
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) {
|
// Make a boot2docker VM disk image.
|
||||||
ip, err := d.GetIP()
|
// 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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
file = &tar.Header{Name: ".ssh/authorized_keys", Size: int64(len(pubKey)), Mode: 0644}
|
||||||
sshAuth := &ssh.Auth{
|
if err := tw.WriteHeader(file); err != nil {
|
||||||
Passwords: []string{"docker"},
|
|
||||||
Keys: []string{d.GetSSHKeyPath()},
|
|
||||||
}
|
|
||||||
sshClient, err := ssh.NewNativeClient(d.GetSSHUsername(), ip, d.SSHPort, sshAuth)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
if _, err := tw.Write([]byte(pubKey)); err != nil {
|
||||||
return sshClient, 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
|
package virtualbox
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"archive/tar"
|
||||||
|
"bytes"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"os/exec"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
@ -16,7 +19,6 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
"github.com/docker/docker/pkg/homedir"
|
|
||||||
"github.com/docker/machine/drivers"
|
"github.com/docker/machine/drivers"
|
||||||
"github.com/docker/machine/log"
|
"github.com/docker/machine/log"
|
||||||
"github.com/docker/machine/ssh"
|
"github.com/docker/machine/ssh"
|
||||||
|
@ -25,7 +27,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
isoFilename = "boot2docker-virtualbox.iso"
|
isoFilename = "boot2docker.iso"
|
||||||
defaultHostOnlyCIDR = "192.168.99.1/24"
|
defaultHostOnlyCIDR = "192.168.99.1/24"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -153,7 +155,7 @@ func (d *Driver) Create() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
b2dutils := utils.NewB2dUtils("", "", isoFilename)
|
b2dutils := utils.NewB2dUtils("", "")
|
||||||
if err := b2dutils.CopyIsoToMachineDir(d.Boot2DockerURL, d.MachineName); err != nil {
|
if err := b2dutils.CopyIsoToMachineDir(d.Boot2DockerURL, d.MachineName); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -201,10 +203,9 @@ func (d *Driver) Create() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debugf("Creating disk image...")
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := vbm("createvm",
|
if err := vbm("createvm",
|
||||||
|
@ -274,7 +275,7 @@ func (d *Driver) Create() error {
|
||||||
"--port", "0",
|
"--port", "0",
|
||||||
"--device", "0",
|
"--device", "0",
|
||||||
"--type", "dvddrive",
|
"--type", "dvddrive",
|
||||||
"--medium", d.ResolveStorePath(isoFilename)); err != nil {
|
"--medium", d.ResolveStorePath("boot2docker.iso")); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -287,38 +288,35 @@ func (d *Driver) Create() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
shareDir := homedir.Get()
|
|
||||||
shareName := shareDir
|
|
||||||
|
|
||||||
log.Debugf("creating share: path=%s", shareDir)
|
|
||||||
|
|
||||||
// let VBoxService do nice magic automounting (when it's used)
|
// 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 {
|
if err := vbm("guestproperty", "set", d.MachineName, "/VirtualBox/GuestAdd/SharedFolders/MountPrefix", "/"); err != nil {
|
||||||
return err
|
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 != "" {
|
if shareDir != "" {
|
||||||
log.Debugf("setting up shareDir")
|
|
||||||
if _, err := os.Stat(shareDir); err != nil && !os.IsNotExist(err) {
|
if _, err := os.Stat(shareDir); err != nil && !os.IsNotExist(err) {
|
||||||
log.Debugf("setting up share failed: %s", err)
|
|
||||||
return err
|
return err
|
||||||
} else if !os.IsNotExist(err) {
|
} else if !os.IsNotExist(err) {
|
||||||
|
if shareName == "" {
|
||||||
// parts of the VBox internal code are buggy with share names that start with "/"
|
// parts of the VBox internal code are buggy with share names that start with "/"
|
||||||
shareName = strings.TrimLeft(shareDir, "/")
|
shareName = strings.TrimLeft(shareDir, "/")
|
||||||
|
// TODO do some basic Windows -> MSYS path conversion
|
||||||
// translate to msys git path
|
// ie, s!^([a-z]+):[/\\]+!\1/!; s!\\!/!g
|
||||||
if runtime.GOOS == "windows" {
|
|
||||||
mountName, err := translateWindowsMount(shareDir)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
shareName = mountName
|
|
||||||
}
|
|
||||||
|
|
||||||
log.Debugf("adding shared folder: name=%q dir=%q", shareName, shareDir)
|
|
||||||
|
|
||||||
// woo, shareDir exists! let's carry on!
|
// woo, shareDir exists! let's carry on!
|
||||||
if err := vbm("sharedfolder", "add", d.MachineName, "--name", shareName, "--hostpath", shareDir, "--automount"); err != nil {
|
if err := vbm("sharedfolder", "add", d.MachineName, "--name", shareName, "--hostpath", shareDir, "--automount"); err != nil {
|
||||||
|
@ -338,47 +336,13 @@ func (d *Driver) Create() error {
|
||||||
return err
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Driver) hostOnlyIpAvailable() bool {
|
func (d *Driver) hostOnlyIpAvailable() bool {
|
||||||
ip, err := d.GetIP()
|
ip, err := d.GetIP()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("ERROR getting IP: %s", err)
|
log.Debug("ERROR getting IP: %s", err)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if ip != "" {
|
if ip != "" {
|
||||||
|
@ -421,8 +385,8 @@ func (d *Driver) Start() error {
|
||||||
log.Infof("VM not in restartable state")
|
log.Infof("VM not in restartable state")
|
||||||
}
|
}
|
||||||
|
|
||||||
addr, err := d.GetSSHHostname()
|
// Wait for SSH over NAT to be available before returning to user
|
||||||
if err := ssh.WaitForTCP(fmt.Sprintf("%s:%d", addr, d.SSHPort)); err != nil {
|
if err := drivers.WaitForSSH(d); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -431,6 +395,8 @@ func (d *Driver) Start() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
d.IPAddress, err = d.GetIP()
|
||||||
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -536,17 +502,11 @@ func (d *Driver) GetIP() (string, error) {
|
||||||
return "", drivers.ErrHostIsNotRunning
|
return "", drivers.ErrHostIsNotRunning
|
||||||
}
|
}
|
||||||
|
|
||||||
sshClient, err := d.getLocalSSHClient()
|
output, err := drivers.RunSSHCommandFromDriver(d, "ip addr show dev eth1")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
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)
|
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
|
// 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")
|
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 {
|
func (d *Driver) setupHostOnlyNetwork(machineName string) error {
|
||||||
hostOnlyCIDR := d.HostOnlyCIDR
|
hostOnlyCIDR := d.HostOnlyCIDR
|
||||||
|
|
||||||
|
@ -619,6 +626,69 @@ func (d *Driver) setupHostOnlyNetwork(machineName string) error {
|
||||||
return nil
|
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
|
// Select an available port, trying the specified
|
||||||
// port first, falling back on an OS selected port.
|
// port first, falling back on an OS selected port.
|
||||||
func getAvailableTCPPort(port int) (int, error) {
|
func getAvailableTCPPort(port int) (int, error) {
|
||||||
|
@ -685,35 +755,3 @@ func getRandomIPinSubnet(baseIP net.IP) (net.IP, error) {
|
||||||
|
|
||||||
return dhcpAddr, nil
|
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])
|
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
|
package vmwarefusion
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"archive/tar"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"text/template"
|
"text/template"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/codegangsta/cli"
|
"github.com/codegangsta/cli"
|
||||||
"github.com/docker/docker/pkg/homedir"
|
|
||||||
"github.com/docker/machine/drivers"
|
"github.com/docker/machine/drivers"
|
||||||
"github.com/docker/machine/log"
|
"github.com/docker/machine/log"
|
||||||
"github.com/docker/machine/ssh"
|
"github.com/docker/machine/ssh"
|
||||||
|
@ -24,8 +25,8 @@ import (
|
||||||
|
|
||||||
const (
|
const (
|
||||||
B2DUser = "docker"
|
B2DUser = "docker"
|
||||||
B2DPass = "docker"
|
B2DPass = "tcuser"
|
||||||
isoFilename = "boot2docker-vmware.iso"
|
isoFilename = "boot2docker.iso"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Driver for VMware Fusion
|
// Driver for VMware Fusion
|
||||||
|
@ -160,7 +161,7 @@ func (d *Driver) PreCreateCheck() error {
|
||||||
|
|
||||||
func (d *Driver) Create() error {
|
func (d *Driver) Create() error {
|
||||||
|
|
||||||
b2dutils := utils.NewB2dUtils("", "", isoFilename)
|
b2dutils := utils.NewB2dUtils("", "")
|
||||||
if err := b2dutils.CopyIsoToMachineDir(d.Boot2DockerURL, d.MachineName); err != nil {
|
if err := b2dutils.CopyIsoToMachineDir(d.Boot2DockerURL, d.MachineName); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -206,7 +207,7 @@ func (d *Driver) Create() error {
|
||||||
|
|
||||||
log.Infof("Waiting for VM to come online...")
|
log.Infof("Waiting for VM to come online...")
|
||||||
for i := 1; i <= 60; i++ {
|
for i := 1; i <= 60; i++ {
|
||||||
ip, err = d.GetIP()
|
ip, err = d.getIPfromDHCPLease()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Debugf("Not there yet %d/%d, error: %s", i, 60, err)
|
log.Debugf("Not there yet %d/%d, error: %s", i, 60, err)
|
||||||
time.Sleep(2 * time.Second)
|
time.Sleep(2 * time.Second)
|
||||||
|
@ -226,42 +227,40 @@ func (d *Driver) Create() error {
|
||||||
// we got an IP, let's copy ssh keys over
|
// we got an IP, let's copy ssh keys over
|
||||||
d.IPAddress = ip
|
d.IPAddress = ip
|
||||||
|
|
||||||
// use ssh to set keys
|
// Generate a tar keys bundle
|
||||||
sshClient, err := d.getLocalSSHClient()
|
if err := d.generateKeyBundle(); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// add pub key for user
|
// Test if /var/lib/boot2docker exists
|
||||||
pubKey, err := ioutil.ReadFile(d.publicSSHKeyPath())
|
vmrun("-gu", B2DUser, "-gp", B2DPass, "directoryExistsInGuest", d.vmxPath(), "/var/lib/boot2docker")
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if out, err := sshClient.Output(fmt.Sprintf(
|
// Copy SSH keys bundle
|
||||||
"mkdir -p /home/%s/.ssh",
|
vmrun("-gu", B2DUser, "-gp", B2DPass, "CopyFileFromHostToGuest", d.vmxPath(), d.ResolveStorePath("userdata.tar"), "/home/docker/userdata.tar")
|
||||||
d.GetSSHUsername(),
|
|
||||||
)); err != nil {
|
|
||||||
log.Error(out)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if out, err := sshClient.Output(fmt.Sprintf(
|
// Expand tar file.
|
||||||
"printf '%%s' '%s' | tee /home/%s/.ssh/authorized_keys",
|
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")
|
||||||
string(pubKey),
|
|
||||||
d.GetSSHUsername(),
|
|
||||||
)); err != nil {
|
|
||||||
log.Error(out)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Enable Shared Folders
|
// Enable Shared Folders
|
||||||
vmrun("-gu", B2DUser, "-gp", B2DPass, "enableSharedFolders", d.vmxPath())
|
vmrun("-gu", B2DUser, "-gp", B2DPass, "enableSharedFolders", d.vmxPath())
|
||||||
|
|
||||||
if err := d.setupSharedDirs(); err != nil {
|
var shareName, shareDir string // TODO configurable at some point
|
||||||
return err
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -270,8 +269,21 @@ func (d *Driver) Start() error {
|
||||||
vmrun("start", d.vmxPath(), "nogui")
|
vmrun("start", d.vmxPath(), "nogui")
|
||||||
|
|
||||||
log.Debugf("Mounting Shared Folders...")
|
log.Debugf("Mounting Shared Folders...")
|
||||||
if err := d.setupSharedDirs(); err != nil {
|
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
|
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
|
return nil
|
||||||
|
@ -406,35 +418,57 @@ func (d *Driver) publicSSHKeyPath() string {
|
||||||
return d.GetSSHKeyPath() + ".pub"
|
return d.GetSSHKeyPath() + ".pub"
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Driver) setupSharedDirs() error {
|
// Make a boot2docker userdata.tar key bundle
|
||||||
shareDir := homedir.Get()
|
func (d *Driver) generateKeyBundle() error {
|
||||||
shareName := "Home"
|
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
|
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
|
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 (
|
const (
|
||||||
isoFilename = "boot2docker-vmware.iso"
|
isoFilename = "boot2docker.iso"
|
||||||
B2DISOName = isoFilename
|
B2DISOName = isoFilename
|
||||||
DefaultCPUNumber = 2
|
DefaultCPUNumber = 2
|
||||||
B2DUser = "docker"
|
B2DUser = "docker"
|
||||||
|
@ -225,7 +225,7 @@ func (d *Driver) Create() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
b2dutils := utils.NewB2dUtils("", "", isoFilename)
|
b2dutils := utils.NewB2dUtils("", "")
|
||||||
if err := b2dutils.CopyIsoToMachineDir(d.Boot2DockerURL, d.MachineName); err != nil {
|
if err := b2dutils.CopyIsoToMachineDir(d.Boot2DockerURL, d.MachineName); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,19 +1,21 @@
|
||||||
package provision
|
package provision
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"path"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
"github.com/docker/machine/drivers"
|
"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/provision/pkgaction"
|
||||||
|
"github.com/docker/machine/libmachine/swarm"
|
||||||
"github.com/docker/machine/log"
|
"github.com/docker/machine/log"
|
||||||
"github.com/docker/machine/state"
|
"github.com/docker/machine/state"
|
||||||
"github.com/docker/machine/utils"
|
"github.com/docker/machine/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
|
||||||
ErrUnknownDriver = errors.New("unknown driver")
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
Register("boot2docker", &RegisteredProvisioner{
|
Register("boot2docker", &RegisteredProvisioner{
|
||||||
New: NewBoot2DockerProvisioner,
|
New: NewBoot2DockerProvisioner,
|
||||||
|
@ -21,23 +23,29 @@ func init() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBoot2DockerProvisioner(d drivers.Driver) Provisioner {
|
func NewBoot2DockerProvisioner(d drivers.Driver) Provisioner {
|
||||||
g := GenericProvisioner{
|
return &Boot2DockerProvisioner{
|
||||||
DockerOptionsDir: "/etc/docker",
|
|
||||||
DaemonOptionsFile: "/etc/systemd/system/docker.service",
|
|
||||||
OsReleaseId: "docker",
|
|
||||||
Packages: []string{},
|
|
||||||
Driver: d,
|
Driver: d,
|
||||||
}
|
}
|
||||||
p := &Boot2DockerProvisioner{
|
|
||||||
DebianProvisioner{
|
|
||||||
GenericProvisioner: g,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
return p
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Boot2DockerProvisioner struct {
|
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 {
|
func (provisioner *Boot2DockerProvisioner) upgradeIso() error {
|
||||||
|
@ -45,62 +53,174 @@ func (provisioner *Boot2DockerProvisioner) upgradeIso() error {
|
||||||
|
|
||||||
if err := provisioner.Driver.Stop(); err != nil {
|
if err := provisioner.Driver.Stop(); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := utils.WaitFor(drivers.MachineInState(provisioner.Driver, state.Stopped)); err != nil {
|
if err := utils.WaitFor(drivers.MachineInState(provisioner.Driver, state.Stopped)); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
machineName := provisioner.GetDriver().GetMachineName()
|
machineName := provisioner.GetDriver().GetMachineName()
|
||||||
|
|
||||||
log.Infof("Upgrading machine %s...", machineName)
|
log.Infof("Upgrading machine %s...", machineName)
|
||||||
|
|
||||||
isoFilename := ""
|
b2dutils := utils.NewB2dUtils("", "")
|
||||||
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)
|
|
||||||
|
|
||||||
// Usually we call this implicitly, but call it here explicitly to get
|
// Usually we call this implicitly, but call it here explicitly to get
|
||||||
// the latest boot2docker ISO.
|
// the latest boot2docker ISO.
|
||||||
if err := b2dutils.DownloadLatestBoot2Docker(); err != nil {
|
if err := b2dutils.DownloadLatestBoot2Docker(); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Copy the latest version of boot2docker ISO to the machine's directory
|
// Copy the latest version of boot2docker ISO to the machine's directory
|
||||||
if err := b2dutils.CopyIsoToMachineDir("", machineName); err != nil {
|
if err := b2dutils.CopyIsoToMachineDir("", machineName); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
log.Infof("Starting machine back up...")
|
||||||
|
|
||||||
if err := provisioner.Driver.Start(); err != nil {
|
if err := provisioner.Driver.Start(); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return utils.WaitFor(drivers.MachineInState(provisioner.Driver, state.Running))
|
return utils.WaitFor(drivers.MachineInState(provisioner.Driver, state.Running))
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (provisioner *Boot2DockerProvisioner) Package(name string, action pkgaction.PackageAction) error {
|
func (provisioner *Boot2DockerProvisioner) Package(name string, action pkgaction.PackageAction) error {
|
||||||
if name == "docker" && action == pkgaction.Upgrade {
|
if name == "docker" && action == pkgaction.Upgrade {
|
||||||
if err := provisioner.upgradeIso(); err != nil {
|
if err := provisioner.upgradeIso(); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return nil
|
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 (
|
const (
|
||||||
versionsUrl = "http://releases.rancher.com/os/versions.yml"
|
versionsUrl = "http://releases.rancher.com/os/versions.yml"
|
||||||
isoUrl = "https://github.com/rancherio/os/releases/download/%s/machine-rancheros.iso"
|
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/
|
hostnameTmpl = `sudo mkdir -p /var/lib/rancher/conf/cloud-config.d/
|
||||||
sudo tee /var/lib/rancher/conf/cloud-config.d/machine-hostname.yml << EOF
|
sudo tee /var/lib/rancher/conf/cloud-config.d/machine-hostname.yml << EOF
|
||||||
#cloud-config
|
#cloud-config
|
||||||
|
@ -176,7 +175,7 @@ func (provisioner *RancherProvisioner) upgradeIso() error {
|
||||||
|
|
||||||
log.Infof("Upgrading machine %s...", machineName)
|
log.Infof("Upgrading machine %s...", machineName)
|
||||||
|
|
||||||
b2dutils := utils.NewB2dUtils("", "", isoFilename)
|
b2dutils := utils.NewB2dUtils("", "")
|
||||||
|
|
||||||
url, err := provisioner.getLatestISOURL()
|
url, err := provisioner.getLatestISOURL()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -8,24 +8,11 @@ import (
|
||||||
|
|
||||||
"github.com/docker/machine/drivers/fakedriver"
|
"github.com/docker/machine/drivers/fakedriver"
|
||||||
"github.com/docker/machine/libmachine/auth"
|
"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) {
|
func TestGenerateDockerOptionsBoot2Docker(t *testing.T) {
|
||||||
g := GenericProvisioner{
|
|
||||||
Driver: &fakedriver.FakeDriver{},
|
|
||||||
EngineOptions: engineOptions(),
|
|
||||||
}
|
|
||||||
p := &Boot2DockerProvisioner{
|
p := &Boot2DockerProvisioner{
|
||||||
DebianProvisioner{
|
Driver: &fakedriver.FakeDriver{},
|
||||||
g,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
dockerPort := 1234
|
dockerPort := 1234
|
||||||
p.AuthOptions = auth.AuthOptions{
|
p.AuthOptions = auth.AuthOptions{
|
||||||
|
@ -33,38 +20,37 @@ func TestGenerateDockerOptionsBoot2Docker(t *testing.T) {
|
||||||
ServerKeyRemotePath: "/test/server-key",
|
ServerKeyRemotePath: "/test/server-key",
|
||||||
ServerCertRemotePath: "/test/server-cert",
|
ServerCertRemotePath: "/test/server-cert",
|
||||||
}
|
}
|
||||||
|
engineConfigPath := "/var/lib/boot2docker/profile"
|
||||||
|
|
||||||
dockerCfg, err := p.GenerateDockerOptions(dockerPort)
|
dockerCfg, err := p.GenerateDockerOptions(dockerPort)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
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 {
|
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)
|
t.Fatalf("-H docker port invalid; expected %d", dockerPort)
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Index(dockerCfg.EngineOptions, fmt.Sprintf("--tlscacert %s", p.AuthOptions.CaCertRemotePath)) == -1 {
|
if strings.Index(dockerCfg.EngineOptions, fmt.Sprintf("CACERT=%s", p.AuthOptions.CaCertRemotePath)) == -1 {
|
||||||
t.Fatalf("--tlscacert option invalid; expected %s", p.AuthOptions.CaCertRemotePath)
|
t.Fatalf("CACERT option invalid; expected %s", p.AuthOptions.CaCertRemotePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Index(dockerCfg.EngineOptions, fmt.Sprintf("--tlscert %s", p.AuthOptions.ServerCertRemotePath)) == -1 {
|
if strings.Index(dockerCfg.EngineOptions, fmt.Sprintf("SERVERKEY=%s", p.AuthOptions.ServerKeyRemotePath)) == -1 {
|
||||||
t.Fatalf("--tlscert option invalid; expected %s", p.AuthOptions.ServerCertRemotePath)
|
t.Fatalf("SERVERKEY option invalid; expected %s", p.AuthOptions.ServerKeyRemotePath)
|
||||||
}
|
}
|
||||||
|
|
||||||
if strings.Index(dockerCfg.EngineOptions, fmt.Sprintf("--tlskey %s", p.AuthOptions.ServerKeyRemotePath)) == -1 {
|
if strings.Index(dockerCfg.EngineOptions, fmt.Sprintf("SERVERCERT=%s", p.AuthOptions.ServerCertRemotePath)) == -1 {
|
||||||
t.Fatalf("--tlskey option invalid; expected %s", p.AuthOptions.ServerKeyRemotePath)
|
t.Fatalf("SERVERCERT option invalid; expected %s", p.AuthOptions.ServerCertRemotePath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMachinePortBoot2Docker(t *testing.T) {
|
func TestMachinePortBoot2Docker(t *testing.T) {
|
||||||
g := GenericProvisioner{
|
|
||||||
Driver: &fakedriver.FakeDriver{},
|
|
||||||
EngineOptions: engineOptions(),
|
|
||||||
}
|
|
||||||
p := &Boot2DockerProvisioner{
|
p := &Boot2DockerProvisioner{
|
||||||
DebianProvisioner{
|
Driver: &fakedriver.FakeDriver{},
|
||||||
g,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
dockerPort := 2376
|
dockerPort := 2376
|
||||||
bindUrl := fmt.Sprintf("tcp://0.0.0.0:%d", dockerPort)
|
bindUrl := fmt.Sprintf("tcp://0.0.0.0:%d", dockerPort)
|
||||||
|
@ -95,14 +81,8 @@ func TestMachinePortBoot2Docker(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMachineCustomPortBoot2Docker(t *testing.T) {
|
func TestMachineCustomPortBoot2Docker(t *testing.T) {
|
||||||
g := GenericProvisioner{
|
|
||||||
Driver: &fakedriver.FakeDriver{},
|
|
||||||
EngineOptions: engineOptions(),
|
|
||||||
}
|
|
||||||
p := &Boot2DockerProvisioner{
|
p := &Boot2DockerProvisioner{
|
||||||
DebianProvisioner{
|
Driver: &fakedriver.FakeDriver{},
|
||||||
g,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
dockerPort := 3376
|
dockerPort := 3376
|
||||||
bindUrl := fmt.Sprintf("tcp://0.0.0.0:%d", dockerPort)
|
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" {
|
@test "$DRIVER: create with supported engine options" {
|
||||||
run machine create -d $DRIVER \
|
run machine create -d $DRIVER \
|
||||||
--engine-label spam=eggs \
|
--engine-label spam=eggs \
|
||||||
--engine-storage-driver devicemapper \
|
--engine-storage-driver overlay \
|
||||||
--engine-insecure-registry registry.myco.com \
|
--engine-insecure-registry registry.myco.com \
|
||||||
$NAME
|
$NAME
|
||||||
echo "$output"
|
echo "$output"
|
||||||
|
@ -19,5 +19,5 @@ load ${BASE_TEST_DIR}/helpers.bash
|
||||||
|
|
||||||
@test "$DRIVER: check for engine storage driver" {
|
@test "$DRIVER: check for engine storage driver" {
|
||||||
storage_driver_info=$(docker $(machine config $NAME) info | grep "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
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
//"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
@ -45,10 +45,11 @@ type B2dUtils struct {
|
||||||
githubBaseUrl string
|
githubBaseUrl string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewB2dUtils(githubApiBaseUrl, githubBaseUrl, isoFilename string) *B2dUtils {
|
func NewB2dUtils(githubApiBaseUrl, githubBaseUrl string) *B2dUtils {
|
||||||
defaultBaseApiUrl := "https://api.github.com"
|
defaultBaseApiUrl := "https://api.github.com"
|
||||||
defaultBaseUrl := "https://github.com"
|
defaultBaseUrl := "https://github.com"
|
||||||
imgCachePath := GetMachineCacheDir()
|
imgCachePath := GetMachineCacheDir()
|
||||||
|
isoFilename := "boot2docker.iso"
|
||||||
|
|
||||||
if githubApiBaseUrl == "" {
|
if githubApiBaseUrl == "" {
|
||||||
githubApiBaseUrl = defaultBaseApiUrl
|
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").
|
// 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
|
// 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) {
|
func (b *B2dUtils) GetLatestBoot2DockerReleaseURL() (string, error) {
|
||||||
//client := getClient()
|
client := getClient()
|
||||||
//apiUrl := fmt.Sprintf("%s/repos/boot2docker/boot2docker/releases", b.githubApiBaseUrl)
|
apiUrl := fmt.Sprintf("%s/repos/boot2docker/boot2docker/releases", b.githubApiBaseUrl)
|
||||||
//rsp, err := client.Get(apiUrl)
|
rsp, err := client.Get(apiUrl)
|
||||||
//if err != nil {
|
if err != nil {
|
||||||
// return "", err
|
return "", err
|
||||||
//}
|
}
|
||||||
//defer rsp.Body.Close()
|
defer rsp.Body.Close()
|
||||||
|
|
||||||
//var t []struct {
|
var t []struct {
|
||||||
// TagName string `json:"tag_name"`
|
TagName string `json:"tag_name"`
|
||||||
// PreRelease bool `json:"prerelease"`
|
}
|
||||||
//}
|
if err := json.NewDecoder(rsp.Body).Decode(&t); err != nil {
|
||||||
//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)
|
||||||
// return "", fmt.Errorf("Error demarshaling the Github API response: %s\nYou may be getting rate limited by Github.", err)
|
}
|
||||||
//}
|
if len(t) == 0 {
|
||||||
//if len(t) == 0 {
|
return "", fmt.Errorf("no releases found")
|
||||||
// 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
|
|
||||||
|
|
||||||
|
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 {
|
func removeFileIfExists(name string) error {
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package utils
|
package utils
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
@ -10,26 +11,20 @@ import (
|
||||||
|
|
||||||
func TestGetLatestBoot2DockerReleaseUrl(t *testing.T) {
|
func TestGetLatestBoot2DockerReleaseUrl(t *testing.T) {
|
||||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
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))
|
w.Write([]byte(respText))
|
||||||
}))
|
}))
|
||||||
defer ts.Close()
|
defer ts.Close()
|
||||||
|
|
||||||
b := NewB2dUtils(ts.URL, ts.URL, "virtualbox")
|
b := NewB2dUtils(ts.URL, ts.URL)
|
||||||
isoUrl, err := b.GetLatestBoot2DockerReleaseURL()
|
isoUrl, err := b.GetLatestBoot2DockerReleaseURL()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: update to release URL once we get the releases worked
|
expectedUrl := fmt.Sprintf("%s/boot2docker/boot2docker/releases/download/0.1/boot2docker.iso", ts.URL)
|
||||||
// out for b2d-ng
|
if isoUrl != expectedUrl {
|
||||||
//expectedUrl := fmt.Sprintf("%s/boot2docker/boot2docker/releases/download/0.1/boot2docker.iso", ts.URL)
|
t.Fatalf("expected url %s; received %s", isoUrl)
|
||||||
//if isoUrl != expectedUrl {
|
|
||||||
// t.Fatalf("expected url %s; received %s", isoUrl)
|
|
||||||
//}
|
|
||||||
|
|
||||||
if isoUrl == "" {
|
|
||||||
t.Fatalf("expected a url for the iso")
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,7 +42,7 @@ func TestDownloadIso(t *testing.T) {
|
||||||
t.Fatal(err)
|
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 {
|
if err := b.DownloadISO(tmpDir, filename, ts.URL); err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue