diff --git a/clients/vmware/cmd/list.go b/clients/vmware/cmd/list.go index b1cb7b6..81c0019 100644 --- a/clients/vmware/cmd/list.go +++ b/clients/vmware/cmd/list.go @@ -15,24 +15,73 @@ package cmd import ( + "crypto/tls" + "crypto/x509" "fmt" + "io/ioutil" + "log" + "net/http" + "path/filepath" + homedir "github.com/mitchellh/go-homedir" + "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/spf13/viper" ) // listCmd represents the list command var listCmd = &cobra.Command{ Use: "list", - Short: "A brief description of your command", - Long: `A longer description that spans multiple lines and likely contains examples -and usage of using your command. For example: - -Cobra is a CLI library for Go that empowers applications. -This application is a tool to generate the needed files -to quickly create a Cobra application.`, + Short: "List all running VMs", Run: func(cmd *cobra.Command, args []string) { - // TODO: Work your own magic here - fmt.Println("list called") + // d := virtualbox.NewDriver("", "") + // outList, err := d.List() + // if err != nil { + // log.Fatal(err) + // } + // fmt.Print(outList) + + host := viper.GetString("server.host") + port := viper.GetString("server.port") + + home, err := homedir.Dir() + if err != nil { + log.Fatal(errors.Wrap(err, "could not detect users home directory")) + } + // Create client + caCert, err := ioutil.ReadFile(filepath.Join(home, ".vmproxy", "cert.pem")) + if err != nil { + log.Fatal(err) + } + caCertPool := x509.NewCertPool() + caCertPool.AppendCertsFromPEM(caCert) + + // cert, err := tls.LoadX509KeyPair("client.crt", "client.key") + // if err != nil { + // log.Fatal(err) + // } + + client := &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + RootCAs: caCertPool, + // Certificates: []tls.Certificate{cert}, + }, + }, + } + + // Create request + req, err := http.NewRequest("GET", "https://"+host+":"+port+"/vmware/list", nil) + + // Fetch Request + resp, err := client.Do(req) + assert(err) + + // Read Response Body + respBody, _ := ioutil.ReadAll(resp.Body) + + // Display Results + fmt.Print(string(respBody)) }, } diff --git a/clients/vmware/cmd/root.go b/clients/vmware/cmd/root.go index a31506f..7525c38 100644 --- a/clients/vmware/cmd/root.go +++ b/clients/vmware/cmd/root.go @@ -16,6 +16,7 @@ package cmd import ( "fmt" + "log" "os" "github.com/spf13/cobra" @@ -34,9 +35,9 @@ examples and usage of using your application. For example: Cobra is a CLI library for Go that empowers applications. This application is a tool to generate the needed files to quickly create a Cobra application.`, -// Uncomment the following line if your bare application -// has an action associated with it: -// Run: func(cmd *cobra.Command, args []string) { }, + // Uncomment the following line if your bare application + // has an action associated with it: + // Run: func(cmd *cobra.Command, args []string) { }, } // Execute adds all child commands to the root command sets flags appropriately. @@ -48,6 +49,12 @@ func Execute() { } } +func assert(err error) { + if err != nil { + log.Fatal(err) + } +} + func init() { cobra.OnInitialize(initConfig) @@ -55,7 +62,7 @@ func init() { // Cobra supports Persistent Flags, which, if defined here, // will be global for your application. - RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.vmrun.yaml)") + RootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.vmware.yaml)") // Cobra also supports local flags, which will only run // when this action is called directly. RootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle") @@ -67,9 +74,9 @@ func initConfig() { viper.SetConfigFile(cfgFile) } - viper.SetConfigName(".vmrun") // name of config file (without extension) - viper.AddConfigPath("$HOME") // adding home directory as first search path - viper.AutomaticEnv() // read in environment variables that match + viper.SetConfigName(".vmware") // name of config file (without extension) + viper.AddConfigPath("$HOME") // adding home directory as first search path + viper.AutomaticEnv() // read in environment variables that match // If a config file is found, read it in. if err := viper.ReadInConfig(); err == nil { diff --git a/clients/vmware/cmd/start.go b/clients/vmware/cmd/start.go index 3628404..6dc7815 100644 --- a/clients/vmware/cmd/start.go +++ b/clients/vmware/cmd/start.go @@ -15,24 +15,73 @@ package cmd import ( + "crypto/tls" + "crypto/x509" "fmt" + "io/ioutil" + "log" + "net/http" + "path/filepath" + homedir "github.com/mitchellh/go-homedir" + "github.com/pkg/errors" "github.com/spf13/cobra" + "github.com/spf13/viper" ) // startCmd represents the start command var startCmd = &cobra.Command{ Use: "start", - Short: "A brief description of your command", - Long: `A longer description that spans multiple lines and likely contains examples -and usage of using your command. For example: - -Cobra is a CLI library for Go that empowers applications. -This application is a tool to generate the needed files -to quickly create a Cobra application.`, + Short: "List all running VMs", Run: func(cmd *cobra.Command, args []string) { - // TODO: Work your own magic here - fmt.Println("start called") + // d := virtualbox.NewDriver("", "") + // outList, err := d.List() + // if err != nil { + // log.Fatal(err) + // } + // fmt.Print(outList) + + host := viper.GetString("server.host") + port := viper.GetString("server.port") + + home, err := homedir.Dir() + if err != nil { + log.Fatal(errors.Wrap(err, "could not detect users home directory")) + } + // Create client + caCert, err := ioutil.ReadFile(filepath.Join(home, ".vmproxy", "cert.pem")) + if err != nil { + log.Fatal(err) + } + caCertPool := x509.NewCertPool() + caCertPool.AppendCertsFromPEM(caCert) + + // cert, err := tls.LoadX509KeyPair("client.crt", "client.key") + // if err != nil { + // log.Fatal(err) + // } + + client := &http.Client{ + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{ + RootCAs: caCertPool, + // Certificates: []tls.Certificate{cert}, + }, + }, + } + + // Create request + req, err := http.NewRequest("GET", "https://"+host+":"+port+"/vmware/start", nil) + + // Fetch Request + resp, err := client.Do(req) + assert(err) + + // Read Response Body + respBody, _ := ioutil.ReadAll(resp.Body) + + // Display Results + fmt.Print(string(respBody)) }, } diff --git a/drivers/vmwarefusion/fusion.go b/drivers/vmwarefusion/fusion.go index be433e5..d49228f 100644 --- a/drivers/vmwarefusion/fusion.go +++ b/drivers/vmwarefusion/fusion.go @@ -1,9 +1,780 @@ -// +build !darwin +/* + * Copyright 2014 VMware, Inc. All rights reserved. Licensed under the Apache v2 License. + */ package vmwarefusion -import "github.com/docker/machine/libmachine/drivers" +import ( + "archive/tar" + "bytes" + "fmt" + "io/ioutil" + "net" + "os" + "path/filepath" + "regexp" + "runtime" + "strings" + "text/template" + "time" + + "github.com/docker/machine/libmachine/drivers" + "github.com/docker/machine/libmachine/log" + "github.com/docker/machine/libmachine/mcnflag" + "github.com/docker/machine/libmachine/mcnutils" + "github.com/docker/machine/libmachine/ssh" + "github.com/docker/machine/libmachine/state" + cryptossh "golang.org/x/crypto/ssh" +) + +const ( + B2DUser = "docker" + B2DPass = "tcuser" + isoFilename = "boot2docker.iso" + isoConfigDrive = "configdrive.iso" +) + +// Driver for VMware Fusion +type Driver struct { + *drivers.BaseDriver + Memory int + DiskSize int + CPU int + ISO string + Boot2DockerURL string + + SSHPassword string + ConfigDriveISO string + ConfigDriveURL string + NoShare bool +} + +const ( + defaultSSHUser = B2DUser + defaultSSHPass = B2DPass + defaultDiskSize = 20000 + defaultCPU = 1 + defaultMemory = 1024 +) + +// GetCreateFlags registers the flags this driver adds to +// "docker hosts create" +func (d *Driver) GetCreateFlags() []mcnflag.Flag { + return []mcnflag.Flag{ + mcnflag.StringFlag{ + EnvVar: "FUSION_BOOT2DOCKER_URL", + Name: "vmwarefusion-boot2docker-url", + Usage: "Fusion URL for boot2docker image", + Value: "", + }, + mcnflag.StringFlag{ + EnvVar: "FUSION_CONFIGDRIVE_URL", + Name: "vmwarefusion-configdrive-url", + Usage: "Fusion URL for cloud-init configdrive", + Value: "", + }, + mcnflag.IntFlag{ + EnvVar: "FUSION_CPU_COUNT", + Name: "vmwarefusion-cpu-count", + Usage: "number of CPUs for the machine (-1 to use the number of CPUs available)", + Value: defaultCPU, + }, + mcnflag.IntFlag{ + EnvVar: "FUSION_MEMORY_SIZE", + Name: "vmwarefusion-memory-size", + Usage: "Fusion size of memory for host VM (in MB)", + Value: defaultMemory, + }, + mcnflag.IntFlag{ + EnvVar: "FUSION_DISK_SIZE", + Name: "vmwarefusion-disk-size", + Usage: "Fusion size of disk for host VM (in MB)", + Value: defaultDiskSize, + }, + mcnflag.StringFlag{ + EnvVar: "FUSION_SSH_USER", + Name: "vmwarefusion-ssh-user", + Usage: "SSH user", + Value: defaultSSHUser, + }, + mcnflag.StringFlag{ + EnvVar: "FUSION_SSH_PASSWORD", + Name: "vmwarefusion-ssh-password", + Usage: "SSH password", + Value: defaultSSHPass, + }, + mcnflag.BoolFlag{ + EnvVar: "FUSION_NO_SHARE", + Name: "vmwarefusion-no-share", + Usage: "Disable the mount of your home directory", + }, + } +} func NewDriver(hostName, storePath string) drivers.Driver { - return drivers.NewDriverNotSupported("vmwarefusion", hostName, storePath) + return &Driver{ + CPU: defaultCPU, + Memory: defaultMemory, + DiskSize: defaultDiskSize, + SSHPassword: defaultSSHPass, + BaseDriver: &drivers.BaseDriver{ + SSHUser: defaultSSHUser, + MachineName: hostName, + StorePath: storePath, + }, + } +} + +func (d *Driver) GetSSHHostname() (string, error) { + return d.GetIP() +} + +func (d *Driver) GetSSHUsername() string { + if d.SSHUser == "" { + d.SSHUser = "docker" + } + + return d.SSHUser +} + +// DriverName returns the name of the driver +func (d *Driver) DriverName() string { + return "vmwarefusion" +} + +func (d *Driver) SetConfigFromFlags(flags drivers.DriverOptions) error { + d.Memory = flags.Int("vmwarefusion-memory-size") + d.CPU = flags.Int("vmwarefusion-cpu-count") + d.DiskSize = flags.Int("vmwarefusion-disk-size") + d.Boot2DockerURL = flags.String("vmwarefusion-boot2docker-url") + d.ConfigDriveURL = flags.String("vmwarefusion-configdrive-url") + d.ISO = d.ResolveStorePath(isoFilename) + d.ConfigDriveISO = d.ResolveStorePath(isoConfigDrive) + d.SetSwarmConfigFromFlags(flags) + d.SSHUser = flags.String("vmwarefusion-ssh-user") + d.SSHPassword = flags.String("vmwarefusion-ssh-password") + d.SSHPort = 22 + d.NoShare = flags.Bool("vmwarefusion-no-share") + + // We support a maximum of 16 cpu to be consistent with Virtual Hardware 10 + // specs. + if d.CPU < 1 { + d.CPU = int(runtime.NumCPU()) + } + if d.CPU > 16 { + d.CPU = 16 + } + + return nil +} + +func (d *Driver) GetURL() (string, error) { + ip, err := d.GetIP() + if err != nil { + return "", err + } + if ip == "" { + return "", nil + } + return fmt.Sprintf("tcp://%s", net.JoinHostPort(ip, "2376")), nil +} + +func (d *Driver) GetIP() (string, error) { + s, err := d.GetState() + if err != nil { + return "", err + } + if s != state.Running { + return "", drivers.ErrHostIsNotRunning + } + + // determine MAC address for VM + macaddr, err := d.getMacAddressFromVmx() + if err != nil { + return "", err + } + + // attempt to find the address in the vmnet configuration + if ip, err := d.getIPfromVmnetConfiguration(macaddr); err == nil { + return ip, err + } + + // address not found in vmnet so look for a DHCP lease + ip, err := d.getIPfromDHCPLease(macaddr) + if err != nil { + return "", err + } + + return ip, nil +} + +func (d *Driver) GetState() (state.State, error) { + // VMRUN only tells use if the vm is running or not + vmxp, err := filepath.EvalSymlinks(d.vmxPath()) + if err != nil { + return state.Error, err + } + if stdout, _, _ := vmrun("list"); strings.Contains(stdout, vmxp) { + return state.Running, nil + } + return state.Stopped, nil +} + +func (d *Driver) ListVMs() (string, error) { + stdOut, _, err := vmrun("list") + if err != nil { + return "", err + } + return stdOut, nil +} + +// PreCreateCheck checks that the machine creation process can be started safely. +func (d *Driver) PreCreateCheck() error { + // Downloading boot2docker to cache should be done here to make sure + // that a download failure will not leave a machine half created. + b2dutils := mcnutils.NewB2dUtils(d.StorePath) + if err := b2dutils.UpdateISOCache(d.Boot2DockerURL); err != nil { + return err + } + + return nil +} + +func (d *Driver) Create() error { + b2dutils := mcnutils.NewB2dUtils(d.StorePath) + if err := b2dutils.CopyIsoToMachineDir(d.Boot2DockerURL, d.MachineName); err != nil { + return err + } + + // download cloud-init config drive + if d.ConfigDriveURL != "" { + if err := b2dutils.DownloadISO(d.ResolveStorePath("."), isoConfigDrive, d.ConfigDriveURL); err != nil { + return err + } + } + + log.Infof("Creating SSH key...") + if err := ssh.GenerateSSHKey(d.GetSSHKeyPath()); err != nil { + return err + } + + log.Infof("Creating VM...") + if err := os.MkdirAll(d.ResolveStorePath("."), 0755); err != nil { + return err + } + + if _, err := os.Stat(d.vmxPath()); err == nil { + return ErrMachineExist + } + + // Generate vmx config file from template + vmxt := template.Must(template.New("vmx").Parse(vmx)) + vmxfile, err := os.Create(d.vmxPath()) + if err != nil { + return err + } + vmxt.Execute(vmxfile, d) + + // Generate vmdk file + diskImg := d.ResolveStorePath(fmt.Sprintf("%s.vmdk", d.MachineName)) + if _, err := os.Stat(diskImg); err != nil { + if !os.IsNotExist(err) { + return err + } + + if err := vdiskmanager(diskImg, d.DiskSize); err != nil { + return err + } + } + + log.Infof("Starting %s...", d.MachineName) + vmrun("start", d.vmxPath(), "nogui") + + var ip string + + log.Infof("Waiting for VM to come online...") + for i := 1; i <= 60; i++ { + ip, err = d.GetIP() + if err != nil { + log.Debugf("Not there yet %d/%d, error: %s", i, 60, err) + time.Sleep(2 * time.Second) + continue + } + + if ip != "" { + log.Debugf("Got an ip: %s", ip) + conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", ip, 22), time.Duration(2*time.Second)) + if err != nil { + log.Debugf("SSH Daemon not responding yet: %s", err) + time.Sleep(2 * time.Second) + continue + } + conn.Close() + break + } + } + + if ip == "" { + return fmt.Errorf("Machine didn't return an IP after 120 seconds, aborting") + } + + // we got an IP, let's copy ssh keys over + d.IPAddress = ip + + // Do not execute the rest of boot2docker specific configuration + // The upload of the public ssh key uses a ssh connection, + // this works without installed vmware client tools + if d.ConfigDriveURL != "" { + var keyfh *os.File + var keycontent []byte + + log.Infof("Copy public SSH key to %s [%s]", d.MachineName, d.IPAddress) + + // create .ssh folder in users home + if err := executeSSHCommand(fmt.Sprintf("mkdir -p /home/%s/.ssh", d.SSHUser), d); err != nil { + return err + } + + // read generated public ssh key + if keyfh, err = os.Open(d.publicSSHKeyPath()); err != nil { + return err + } + defer keyfh.Close() + + if keycontent, err = ioutil.ReadAll(keyfh); err != nil { + return err + } + + // add public ssh key to authorized_keys + if err := executeSSHCommand(fmt.Sprintf("echo '%s' > /home/%s/.ssh/authorized_keys", string(keycontent), d.SSHUser), d); err != nil { + return err + } + + // make it secure + if err := executeSSHCommand(fmt.Sprintf("chmod 600 /home/%s/.ssh/authorized_keys", d.SSHUser), d); err != nil { + return err + } + + log.Debugf("Leaving create sequence early, configdrive found") + return nil + } + + // Generate a tar keys bundle + if err := d.generateKeyBundle(); err != nil { + return err + } + + // Test if /var/lib/boot2docker exists + vmrun("-gu", B2DUser, "-gp", B2DPass, "directoryExistsInGuest", d.vmxPath(), "/var/lib/boot2docker") + + // Copy SSH keys bundle + vmrun("-gu", B2DUser, "-gp", B2DPass, "CopyFileFromHostToGuest", d.vmxPath(), d.ResolveStorePath("userdata.tar"), "/home/docker/userdata.tar") + + // Expand tar file. + vmrun("-gu", B2DUser, "-gp", B2DPass, "runScriptInGuest", d.vmxPath(), "/bin/sh", "sudo sh -c \"tar xvf /home/docker/userdata.tar -C /home/docker > /var/log/userdata.log 2>&1 && chown -R docker:staff /home/docker\"") + + // copy to /var/lib/boot2docker + vmrun("-gu", B2DUser, "-gp", B2DPass, "runScriptInGuest", d.vmxPath(), "/bin/sh", "sudo /bin/mv /home/docker/userdata.tar /var/lib/boot2docker/userdata.tar") + + // Enable Shared Folders + vmrun("-gu", B2DUser, "-gp", B2DPass, "enableSharedFolders", d.vmxPath()) + + var shareName, shareDir string // TODO configurable at some point + switch runtime.GOOS { + case "darwin": + shareName = "Users" + shareDir = "/Users" + // TODO "linux" and "windows" + } + + if shareDir != "" && !d.NoShare { + 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) + command := "[ ! -d " + shareDir + " ]&& sudo mkdir " + shareDir + "; sudo mount --bind /mnt/hgfs/" + shareDir + " " + shareDir + " || [ -f /usr/local/bin/vmhgfs-fuse ]&& sudo /usr/local/bin/vmhgfs-fuse -o allow_other .host:/" + shareName + " " + shareDir + " || sudo mount -t vmhgfs -o uid=$(id -u),gid=$(id -g) .host:/" + shareName + " " + shareDir + vmrun("-gu", B2DUser, "-gp", B2DPass, "runScriptInGuest", d.vmxPath(), "/bin/sh", command) + } + } + return nil +} + +func (d *Driver) Start() error { + vmrun("start", d.vmxPath(), "nogui") + + // Do not execute the rest of boot2docker specific configuration, exit here + if d.ConfigDriveURL != "" { + log.Debugf("Leaving start sequence early, configdrive found") + return nil + } + + log.Debugf("Mounting Shared Folders...") + var shareName, shareDir string // TODO configurable at some point + switch runtime.GOOS { + case "darwin": + shareName = "Users" + shareDir = "/Users" + // TODO "linux" and "windows" + } + + if shareDir != "" { + if _, err := os.Stat(shareDir); err != nil && !os.IsNotExist(err) { + return err + } else if !os.IsNotExist(err) { + // create mountpoint and mount shared folder + command := "[ ! -d " + shareDir + " ]&& sudo mkdir " + shareDir + "; sudo mount --bind /mnt/hgfs/" + shareDir + " " + shareDir + " || [ -f /usr/local/bin/vmhgfs-fuse ]&& sudo /usr/local/bin/vmhgfs-fuse -o allow_other .host:/" + shareName + " " + shareDir + " || sudo mount -t vmhgfs -o uid=$(id -u),gid=$(id -g) .host:/" + shareName + " " + shareDir + vmrun("-gu", B2DUser, "-gp", B2DPass, "runScriptInGuest", d.vmxPath(), "/bin/sh", command) + } + } + + return nil +} + +func (d *Driver) Stop() error { + _, _, err := vmrun("stop", d.vmxPath(), "nogui") + return err +} + +func (d *Driver) Restart() error { + // Stop VM gracefully + if err := d.Stop(); err != nil { + return err + } + // Start it again and mount shared folder + if err := d.Start(); err != nil { + return err + } + return nil +} + +func (d *Driver) Kill() error { + _, _, err := vmrun("stop", d.vmxPath(), "hard nogui") + return err +} + +func (d *Driver) Remove() error { + s, _ := d.GetState() + if s == state.Running { + if err := d.Kill(); err != nil { + return fmt.Errorf("Error stopping VM before deletion") + } + } + log.Infof("Deleting %s...", d.MachineName) + vmrun("deleteVM", d.vmxPath(), "nogui") + return nil +} + +func (d *Driver) Upgrade() error { + return fmt.Errorf("VMware Fusion does not currently support the upgrade operation") +} + +func (d *Driver) vmxPath() string { + return d.ResolveStorePath(fmt.Sprintf("%s.vmx", d.MachineName)) +} + +func (d *Driver) vmdkPath() string { + return d.ResolveStorePath(fmt.Sprintf("%s.vmdk", d.MachineName)) +} + +func (d *Driver) getMacAddressFromVmx() (string, error) { + var vmxfh *os.File + var vmxcontent []byte + var err error + + if vmxfh, err = os.Open(d.vmxPath()); err != nil { + return "", err + } + defer vmxfh.Close() + + if vmxcontent, err = ioutil.ReadAll(vmxfh); err != nil { + return "", err + } + + // Look for generatedAddress as we're passing a VMX with addressType = "generated". + var macaddr string + vmxparse := regexp.MustCompile(`^ethernet0.generatedAddress\s*=\s*"(.*?)"\s*$`) + for _, line := range strings.Split(string(vmxcontent), "\n") { + if matches := vmxparse.FindStringSubmatch(line); matches == nil { + continue + } else { + macaddr = strings.ToLower(matches[1]) + } + } + + if macaddr == "" { + return "", fmt.Errorf("couldn't find MAC address in VMX file %s", d.vmxPath()) + } + + log.Debugf("MAC address in VMX: %s", macaddr) + + return macaddr, nil +} + +func (d *Driver) getIPfromVmnetConfiguration(macaddr string) (string, error) { + + // DHCP lease table for NAT vmnet interface + confFiles, _ := filepath.Glob("/Library/Preferences/VMware Fusion/vmnet*/dhcpd.conf") + for _, conffile := range confFiles { + log.Debugf("Trying to find IP address in configuration file: %s", conffile) + if ipaddr, err := d.getIPfromVmnetConfigurationFile(conffile, macaddr); err == nil { + return ipaddr, err + } + } + + return "", fmt.Errorf("IP not found for MAC %s in vmnet configuration files", macaddr) +} + +func (d *Driver) getIPfromVmnetConfigurationFile(conffile, macaddr string) (string, error) { + var conffh *os.File + var confcontent []byte + + var currentip string + var lastipmatch string + var lastmacmatch string + + var err error + + if conffh, err = os.Open(conffile); err != nil { + return "", err + } + defer conffh.Close() + + if confcontent, err = ioutil.ReadAll(conffh); err != nil { + return "", err + } + + // find all occurrences of 'host .* { .. }' and extract + // out of the inner block the MAC and IP addresses + + // key = MAC, value = IP + m := make(map[string]string) + + // Begin of a host block, that contains the IP, MAC + hostbegin := regexp.MustCompile(`^host (.+?) {`) + // End of a host block + hostend := regexp.MustCompile(`^}`) + + // Get the IP address. + ip := regexp.MustCompile(`^\s*fixed-address (.+?);$`) + // Get the MAC address associated. + mac := regexp.MustCompile(`^\s*hardware ethernet (.+?);$`) + + // we use a block depth so that just in case inner blocks exists + // we are not being fooled by them + blockdepth := 0 + for _, line := range strings.Split(string(confcontent), "\n") { + + if matches := hostbegin.FindStringSubmatch(line); matches != nil { + blockdepth = blockdepth + 1 + continue + } + + // we are only in interested in endings if we in a block. Otherwise we will count + // ending of non host blocks as well + if matches := hostend.FindStringSubmatch(line); blockdepth > 0 && matches != nil { + blockdepth = blockdepth - 1 + + if blockdepth == 0 { + // add data + m[lastmacmatch] = lastipmatch + + // reset all temp var holders + lastipmatch = "" + lastmacmatch = "" + } + + continue + } + + // only if we are within the first level of a block + // we are looking for addresses to extract + if blockdepth == 1 { + if matches := ip.FindStringSubmatch(line); matches != nil { + lastipmatch = matches[1] + continue + } + + if matches := mac.FindStringSubmatch(line); matches != nil { + lastmacmatch = strings.ToLower(matches[1]) + continue + } + } + } + + log.Debugf("Following IPs found %s", m) + + // map is filled to now lets check if we have a MAC associated to an IP + currentip, ok := m[strings.ToLower(macaddr)] + + if !ok { + return "", fmt.Errorf("IP not found for MAC %s in vmnet configuration", macaddr) + } + + log.Debugf("IP found in vmnet configuration file: %s", currentip) + + return currentip, nil + +} + +func (d *Driver) getIPfromDHCPLease(macaddr string) (string, error) { + + // DHCP lease table for NAT vmnet interface + leasesFiles, _ := filepath.Glob("/var/db/vmware/*.leases") + for _, dhcpfile := range leasesFiles { + log.Debugf("Trying to find IP address in leases file: %s", dhcpfile) + if ipaddr, err := d.getIPfromDHCPLeaseFile(dhcpfile, macaddr); err == nil { + return ipaddr, err + } + } + + return "", fmt.Errorf("IP not found for MAC %s in DHCP leases", macaddr) +} + +func (d *Driver) getIPfromDHCPLeaseFile(dhcpfile, macaddr string) (string, error) { + + var dhcpfh *os.File + var dhcpcontent []byte + var lastipmatch string + var currentip string + var lastleaseendtime time.Time + var currentleadeendtime time.Time + var err error + + if dhcpfh, err = os.Open(dhcpfile); err != nil { + return "", err + } + defer dhcpfh.Close() + + if dhcpcontent, err = ioutil.ReadAll(dhcpfh); err != nil { + return "", err + } + + // Get the IP from the lease table. + leaseip := regexp.MustCompile(`^lease (.+?) {$`) + // Get the lease end date time. + leaseend := regexp.MustCompile(`^\s*ends \d (.+?);$`) + // Get the MAC address associated. + leasemac := regexp.MustCompile(`^\s*hardware ethernet (.+?);$`) + + for _, line := range strings.Split(string(dhcpcontent), "\n") { + + if matches := leaseip.FindStringSubmatch(line); matches != nil { + lastipmatch = matches[1] + continue + } + + if matches := leaseend.FindStringSubmatch(line); matches != nil { + lastleaseendtime, _ = time.Parse("2006/01/02 15:04:05", matches[1]) + continue + } + + if matches := leasemac.FindStringSubmatch(line); matches != nil && matches[1] == macaddr && currentleadeendtime.Before(lastleaseendtime) { + currentip = lastipmatch + currentleadeendtime = lastleaseendtime + } + } + + if currentip == "" { + return "", fmt.Errorf("IP not found for MAC %s in DHCP leases", macaddr) + } + + log.Debugf("IP found in DHCP lease table: %s", currentip) + + return currentip, nil +} + +func (d *Driver) publicSSHKeyPath() string { + return d.GetSSHKeyPath() + ".pub" +} + +// Make a boot2docker userdata.tar key bundle +func (d *Driver) generateKeyBundle() error { + log.Debugf("Creating Tar key bundle...") + + 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 nil + +} + +// execute command over SSH with user / password authentication +func executeSSHCommand(command string, d *Driver) error { + log.Debugf("Execute executeSSHCommand: %s", command) + + config := &cryptossh.ClientConfig{ + User: d.SSHUser, + Auth: []cryptossh.AuthMethod{ + cryptossh.Password(d.SSHPassword), + }, + } + + client, err := cryptossh.Dial("tcp", fmt.Sprintf("%s:%d", d.IPAddress, d.SSHPort), config) + if err != nil { + log.Debugf("Failed to dial:", err) + return err + } + + session, err := client.NewSession() + if err != nil { + log.Debugf("Failed to create session: " + err.Error()) + return err + } + defer session.Close() + + var b bytes.Buffer + session.Stdout = &b + + if err := session.Run(command); err != nil { + log.Debugf("Failed to run: " + err.Error()) + return err + } + log.Debugf("Stdout from executeSSHCommand: %s", b.String()) + + return nil } diff --git a/drivers/vmwarefusion/fusion_darwin.go b/drivers/vmwarefusion/fusion_darwin.go deleted file mode 100644 index d83ecf9..0000000 --- a/drivers/vmwarefusion/fusion_darwin.go +++ /dev/null @@ -1,772 +0,0 @@ -/* - * Copyright 2014 VMware, Inc. All rights reserved. Licensed under the Apache v2 License. - */ - -package vmwarefusion - -import ( - "archive/tar" - "bytes" - "fmt" - "io/ioutil" - "net" - "os" - "path/filepath" - "regexp" - "runtime" - "strings" - "text/template" - "time" - - "github.com/docker/machine/libmachine/drivers" - "github.com/docker/machine/libmachine/log" - "github.com/docker/machine/libmachine/mcnflag" - "github.com/docker/machine/libmachine/mcnutils" - "github.com/docker/machine/libmachine/ssh" - "github.com/docker/machine/libmachine/state" - cryptossh "golang.org/x/crypto/ssh" -) - -const ( - B2DUser = "docker" - B2DPass = "tcuser" - isoFilename = "boot2docker.iso" - isoConfigDrive = "configdrive.iso" -) - -// Driver for VMware Fusion -type Driver struct { - *drivers.BaseDriver - Memory int - DiskSize int - CPU int - ISO string - Boot2DockerURL string - - SSHPassword string - ConfigDriveISO string - ConfigDriveURL string - NoShare bool -} - -const ( - defaultSSHUser = B2DUser - defaultSSHPass = B2DPass - defaultDiskSize = 20000 - defaultCPU = 1 - defaultMemory = 1024 -) - -// GetCreateFlags registers the flags this driver adds to -// "docker hosts create" -func (d *Driver) GetCreateFlags() []mcnflag.Flag { - return []mcnflag.Flag{ - mcnflag.StringFlag{ - EnvVar: "FUSION_BOOT2DOCKER_URL", - Name: "vmwarefusion-boot2docker-url", - Usage: "Fusion URL for boot2docker image", - Value: "", - }, - mcnflag.StringFlag{ - EnvVar: "FUSION_CONFIGDRIVE_URL", - Name: "vmwarefusion-configdrive-url", - Usage: "Fusion URL for cloud-init configdrive", - Value: "", - }, - mcnflag.IntFlag{ - EnvVar: "FUSION_CPU_COUNT", - Name: "vmwarefusion-cpu-count", - Usage: "number of CPUs for the machine (-1 to use the number of CPUs available)", - Value: defaultCPU, - }, - mcnflag.IntFlag{ - EnvVar: "FUSION_MEMORY_SIZE", - Name: "vmwarefusion-memory-size", - Usage: "Fusion size of memory for host VM (in MB)", - Value: defaultMemory, - }, - mcnflag.IntFlag{ - EnvVar: "FUSION_DISK_SIZE", - Name: "vmwarefusion-disk-size", - Usage: "Fusion size of disk for host VM (in MB)", - Value: defaultDiskSize, - }, - mcnflag.StringFlag{ - EnvVar: "FUSION_SSH_USER", - Name: "vmwarefusion-ssh-user", - Usage: "SSH user", - Value: defaultSSHUser, - }, - mcnflag.StringFlag{ - EnvVar: "FUSION_SSH_PASSWORD", - Name: "vmwarefusion-ssh-password", - Usage: "SSH password", - Value: defaultSSHPass, - }, - mcnflag.BoolFlag{ - EnvVar: "FUSION_NO_SHARE", - Name: "vmwarefusion-no-share", - Usage: "Disable the mount of your home directory", - }, - } -} - -func NewDriver(hostName, storePath string) drivers.Driver { - return &Driver{ - CPU: defaultCPU, - Memory: defaultMemory, - DiskSize: defaultDiskSize, - SSHPassword: defaultSSHPass, - BaseDriver: &drivers.BaseDriver{ - SSHUser: defaultSSHUser, - MachineName: hostName, - StorePath: storePath, - }, - } -} - -func (d *Driver) GetSSHHostname() (string, error) { - return d.GetIP() -} - -func (d *Driver) GetSSHUsername() string { - if d.SSHUser == "" { - d.SSHUser = "docker" - } - - return d.SSHUser -} - -// DriverName returns the name of the driver -func (d *Driver) DriverName() string { - return "vmwarefusion" -} - -func (d *Driver) SetConfigFromFlags(flags drivers.DriverOptions) error { - d.Memory = flags.Int("vmwarefusion-memory-size") - d.CPU = flags.Int("vmwarefusion-cpu-count") - d.DiskSize = flags.Int("vmwarefusion-disk-size") - d.Boot2DockerURL = flags.String("vmwarefusion-boot2docker-url") - d.ConfigDriveURL = flags.String("vmwarefusion-configdrive-url") - d.ISO = d.ResolveStorePath(isoFilename) - d.ConfigDriveISO = d.ResolveStorePath(isoConfigDrive) - d.SetSwarmConfigFromFlags(flags) - d.SSHUser = flags.String("vmwarefusion-ssh-user") - d.SSHPassword = flags.String("vmwarefusion-ssh-password") - d.SSHPort = 22 - d.NoShare = flags.Bool("vmwarefusion-no-share") - - // We support a maximum of 16 cpu to be consistent with Virtual Hardware 10 - // specs. - if d.CPU < 1 { - d.CPU = int(runtime.NumCPU()) - } - if d.CPU > 16 { - d.CPU = 16 - } - - return nil -} - -func (d *Driver) GetURL() (string, error) { - ip, err := d.GetIP() - if err != nil { - return "", err - } - if ip == "" { - return "", nil - } - return fmt.Sprintf("tcp://%s", net.JoinHostPort(ip, "2376")), nil -} - -func (d *Driver) GetIP() (string, error) { - s, err := d.GetState() - if err != nil { - return "", err - } - if s != state.Running { - return "", drivers.ErrHostIsNotRunning - } - - // determine MAC address for VM - macaddr, err := d.getMacAddressFromVmx() - if err != nil { - return "", err - } - - // attempt to find the address in the vmnet configuration - if ip, err := d.getIPfromVmnetConfiguration(macaddr); err == nil { - return ip, err - } - - // address not found in vmnet so look for a DHCP lease - ip, err := d.getIPfromDHCPLease(macaddr) - if err != nil { - return "", err - } - - return ip, nil -} - -func (d *Driver) GetState() (state.State, error) { - // VMRUN only tells use if the vm is running or not - vmxp, err := filepath.EvalSymlinks(d.vmxPath()) - if err != nil { - return state.Error, err - } - if stdout, _, _ := vmrun("list"); strings.Contains(stdout, vmxp) { - return state.Running, nil - } - return state.Stopped, nil -} - -// PreCreateCheck checks that the machine creation process can be started safely. -func (d *Driver) PreCreateCheck() error { - // Downloading boot2docker to cache should be done here to make sure - // that a download failure will not leave a machine half created. - b2dutils := mcnutils.NewB2dUtils(d.StorePath) - if err := b2dutils.UpdateISOCache(d.Boot2DockerURL); err != nil { - return err - } - - return nil -} - -func (d *Driver) Create() error { - b2dutils := mcnutils.NewB2dUtils(d.StorePath) - if err := b2dutils.CopyIsoToMachineDir(d.Boot2DockerURL, d.MachineName); err != nil { - return err - } - - // download cloud-init config drive - if d.ConfigDriveURL != "" { - if err := b2dutils.DownloadISO(d.ResolveStorePath("."), isoConfigDrive, d.ConfigDriveURL); err != nil { - return err - } - } - - log.Infof("Creating SSH key...") - if err := ssh.GenerateSSHKey(d.GetSSHKeyPath()); err != nil { - return err - } - - log.Infof("Creating VM...") - if err := os.MkdirAll(d.ResolveStorePath("."), 0755); err != nil { - return err - } - - if _, err := os.Stat(d.vmxPath()); err == nil { - return ErrMachineExist - } - - // Generate vmx config file from template - vmxt := template.Must(template.New("vmx").Parse(vmx)) - vmxfile, err := os.Create(d.vmxPath()) - if err != nil { - return err - } - vmxt.Execute(vmxfile, d) - - // Generate vmdk file - diskImg := d.ResolveStorePath(fmt.Sprintf("%s.vmdk", d.MachineName)) - if _, err := os.Stat(diskImg); err != nil { - if !os.IsNotExist(err) { - return err - } - - if err := vdiskmanager(diskImg, d.DiskSize); err != nil { - return err - } - } - - log.Infof("Starting %s...", d.MachineName) - vmrun("start", d.vmxPath(), "nogui") - - var ip string - - log.Infof("Waiting for VM to come online...") - for i := 1; i <= 60; i++ { - ip, err = d.GetIP() - if err != nil { - log.Debugf("Not there yet %d/%d, error: %s", i, 60, err) - time.Sleep(2 * time.Second) - continue - } - - if ip != "" { - log.Debugf("Got an ip: %s", ip) - conn, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", ip, 22), time.Duration(2*time.Second)) - if err != nil { - log.Debugf("SSH Daemon not responding yet: %s", err) - time.Sleep(2 * time.Second) - continue - } - conn.Close() - break - } - } - - if ip == "" { - return fmt.Errorf("Machine didn't return an IP after 120 seconds, aborting") - } - - // we got an IP, let's copy ssh keys over - d.IPAddress = ip - - // Do not execute the rest of boot2docker specific configuration - // The upload of the public ssh key uses a ssh connection, - // this works without installed vmware client tools - if d.ConfigDriveURL != "" { - var keyfh *os.File - var keycontent []byte - - log.Infof("Copy public SSH key to %s [%s]", d.MachineName, d.IPAddress) - - // create .ssh folder in users home - if err := executeSSHCommand(fmt.Sprintf("mkdir -p /home/%s/.ssh", d.SSHUser), d); err != nil { - return err - } - - // read generated public ssh key - if keyfh, err = os.Open(d.publicSSHKeyPath()); err != nil { - return err - } - defer keyfh.Close() - - if keycontent, err = ioutil.ReadAll(keyfh); err != nil { - return err - } - - // add public ssh key to authorized_keys - if err := executeSSHCommand(fmt.Sprintf("echo '%s' > /home/%s/.ssh/authorized_keys", string(keycontent), d.SSHUser), d); err != nil { - return err - } - - // make it secure - if err := executeSSHCommand(fmt.Sprintf("chmod 600 /home/%s/.ssh/authorized_keys", d.SSHUser), d); err != nil { - return err - } - - log.Debugf("Leaving create sequence early, configdrive found") - return nil - } - - // Generate a tar keys bundle - if err := d.generateKeyBundle(); err != nil { - return err - } - - // Test if /var/lib/boot2docker exists - vmrun("-gu", B2DUser, "-gp", B2DPass, "directoryExistsInGuest", d.vmxPath(), "/var/lib/boot2docker") - - // Copy SSH keys bundle - vmrun("-gu", B2DUser, "-gp", B2DPass, "CopyFileFromHostToGuest", d.vmxPath(), d.ResolveStorePath("userdata.tar"), "/home/docker/userdata.tar") - - // Expand tar file. - vmrun("-gu", B2DUser, "-gp", B2DPass, "runScriptInGuest", d.vmxPath(), "/bin/sh", "sudo sh -c \"tar xvf /home/docker/userdata.tar -C /home/docker > /var/log/userdata.log 2>&1 && chown -R docker:staff /home/docker\"") - - // copy to /var/lib/boot2docker - vmrun("-gu", B2DUser, "-gp", B2DPass, "runScriptInGuest", d.vmxPath(), "/bin/sh", "sudo /bin/mv /home/docker/userdata.tar /var/lib/boot2docker/userdata.tar") - - // Enable Shared Folders - vmrun("-gu", B2DUser, "-gp", B2DPass, "enableSharedFolders", d.vmxPath()) - - var shareName, shareDir string // TODO configurable at some point - switch runtime.GOOS { - case "darwin": - shareName = "Users" - shareDir = "/Users" - // TODO "linux" and "windows" - } - - if shareDir != "" && !d.NoShare { - 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) - command := "[ ! -d " + shareDir + " ]&& sudo mkdir " + shareDir + "; sudo mount --bind /mnt/hgfs/" + shareDir + " " + shareDir + " || [ -f /usr/local/bin/vmhgfs-fuse ]&& sudo /usr/local/bin/vmhgfs-fuse -o allow_other .host:/" + shareName + " " + shareDir + " || sudo mount -t vmhgfs -o uid=$(id -u),gid=$(id -g) .host:/" + shareName + " " + shareDir - vmrun("-gu", B2DUser, "-gp", B2DPass, "runScriptInGuest", d.vmxPath(), "/bin/sh", command) - } - } - return nil -} - -func (d *Driver) Start() error { - vmrun("start", d.vmxPath(), "nogui") - - // Do not execute the rest of boot2docker specific configuration, exit here - if d.ConfigDriveURL != "" { - log.Debugf("Leaving start sequence early, configdrive found") - return nil - } - - log.Debugf("Mounting Shared Folders...") - var shareName, shareDir string // TODO configurable at some point - switch runtime.GOOS { - case "darwin": - shareName = "Users" - shareDir = "/Users" - // TODO "linux" and "windows" - } - - if shareDir != "" { - if _, err := os.Stat(shareDir); err != nil && !os.IsNotExist(err) { - return err - } else if !os.IsNotExist(err) { - // create mountpoint and mount shared folder - command := "[ ! -d " + shareDir + " ]&& sudo mkdir " + shareDir + "; sudo mount --bind /mnt/hgfs/" + shareDir + " " + shareDir + " || [ -f /usr/local/bin/vmhgfs-fuse ]&& sudo /usr/local/bin/vmhgfs-fuse -o allow_other .host:/" + shareName + " " + shareDir + " || sudo mount -t vmhgfs -o uid=$(id -u),gid=$(id -g) .host:/" + shareName + " " + shareDir - vmrun("-gu", B2DUser, "-gp", B2DPass, "runScriptInGuest", d.vmxPath(), "/bin/sh", command) - } - } - - return nil -} - -func (d *Driver) Stop() error { - _, _, err := vmrun("stop", d.vmxPath(), "nogui") - return err -} - -func (d *Driver) Restart() error { - // Stop VM gracefully - if err := d.Stop(); err != nil { - return err - } - // Start it again and mount shared folder - if err := d.Start(); err != nil { - return err - } - return nil -} - -func (d *Driver) Kill() error { - _, _, err := vmrun("stop", d.vmxPath(), "hard nogui") - return err -} - -func (d *Driver) Remove() error { - s, _ := d.GetState() - if s == state.Running { - if err := d.Kill(); err != nil { - return fmt.Errorf("Error stopping VM before deletion") - } - } - log.Infof("Deleting %s...", d.MachineName) - vmrun("deleteVM", d.vmxPath(), "nogui") - return nil -} - -func (d *Driver) Upgrade() error { - return fmt.Errorf("VMware Fusion does not currently support the upgrade operation") -} - -func (d *Driver) vmxPath() string { - return d.ResolveStorePath(fmt.Sprintf("%s.vmx", d.MachineName)) -} - -func (d *Driver) vmdkPath() string { - return d.ResolveStorePath(fmt.Sprintf("%s.vmdk", d.MachineName)) -} - -func (d *Driver) getMacAddressFromVmx() (string, error) { - var vmxfh *os.File - var vmxcontent []byte - var err error - - if vmxfh, err = os.Open(d.vmxPath()); err != nil { - return "", err - } - defer vmxfh.Close() - - if vmxcontent, err = ioutil.ReadAll(vmxfh); err != nil { - return "", err - } - - // Look for generatedAddress as we're passing a VMX with addressType = "generated". - var macaddr string - vmxparse := regexp.MustCompile(`^ethernet0.generatedAddress\s*=\s*"(.*?)"\s*$`) - for _, line := range strings.Split(string(vmxcontent), "\n") { - if matches := vmxparse.FindStringSubmatch(line); matches == nil { - continue - } else { - macaddr = strings.ToLower(matches[1]) - } - } - - if macaddr == "" { - return "", fmt.Errorf("couldn't find MAC address in VMX file %s", d.vmxPath()) - } - - log.Debugf("MAC address in VMX: %s", macaddr) - - return macaddr, nil -} - -func (d *Driver) getIPfromVmnetConfiguration(macaddr string) (string, error) { - - // DHCP lease table for NAT vmnet interface - confFiles, _ := filepath.Glob("/Library/Preferences/VMware Fusion/vmnet*/dhcpd.conf") - for _, conffile := range confFiles { - log.Debugf("Trying to find IP address in configuration file: %s", conffile) - if ipaddr, err := d.getIPfromVmnetConfigurationFile(conffile, macaddr); err == nil { - return ipaddr, err - } - } - - return "", fmt.Errorf("IP not found for MAC %s in vmnet configuration files", macaddr) -} - -func (d *Driver) getIPfromVmnetConfigurationFile(conffile, macaddr string) (string, error) { - var conffh *os.File - var confcontent []byte - - var currentip string - var lastipmatch string - var lastmacmatch string - - var err error - - if conffh, err = os.Open(conffile); err != nil { - return "", err - } - defer conffh.Close() - - if confcontent, err = ioutil.ReadAll(conffh); err != nil { - return "", err - } - - // find all occurrences of 'host .* { .. }' and extract - // out of the inner block the MAC and IP addresses - - // key = MAC, value = IP - m := make(map[string]string) - - // Begin of a host block, that contains the IP, MAC - hostbegin := regexp.MustCompile(`^host (.+?) {`) - // End of a host block - hostend := regexp.MustCompile(`^}`) - - // Get the IP address. - ip := regexp.MustCompile(`^\s*fixed-address (.+?);$`) - // Get the MAC address associated. - mac := regexp.MustCompile(`^\s*hardware ethernet (.+?);$`) - - // we use a block depth so that just in case inner blocks exists - // we are not being fooled by them - blockdepth := 0 - for _, line := range strings.Split(string(confcontent), "\n") { - - if matches := hostbegin.FindStringSubmatch(line); matches != nil { - blockdepth = blockdepth + 1 - continue - } - - // we are only in interested in endings if we in a block. Otherwise we will count - // ending of non host blocks as well - if matches := hostend.FindStringSubmatch(line); blockdepth > 0 && matches != nil { - blockdepth = blockdepth - 1 - - if blockdepth == 0 { - // add data - m[lastmacmatch] = lastipmatch - - // reset all temp var holders - lastipmatch = "" - lastmacmatch = "" - } - - continue - } - - // only if we are within the first level of a block - // we are looking for addresses to extract - if blockdepth == 1 { - if matches := ip.FindStringSubmatch(line); matches != nil { - lastipmatch = matches[1] - continue - } - - if matches := mac.FindStringSubmatch(line); matches != nil { - lastmacmatch = strings.ToLower(matches[1]) - continue - } - } - } - - log.Debugf("Following IPs found %s", m) - - // map is filled to now lets check if we have a MAC associated to an IP - currentip, ok := m[strings.ToLower(macaddr)] - - if !ok { - return "", fmt.Errorf("IP not found for MAC %s in vmnet configuration", macaddr) - } - - log.Debugf("IP found in vmnet configuration file: %s", currentip) - - return currentip, nil - -} - -func (d *Driver) getIPfromDHCPLease(macaddr string) (string, error) { - - // DHCP lease table for NAT vmnet interface - leasesFiles, _ := filepath.Glob("/var/db/vmware/*.leases") - for _, dhcpfile := range leasesFiles { - log.Debugf("Trying to find IP address in leases file: %s", dhcpfile) - if ipaddr, err := d.getIPfromDHCPLeaseFile(dhcpfile, macaddr); err == nil { - return ipaddr, err - } - } - - return "", fmt.Errorf("IP not found for MAC %s in DHCP leases", macaddr) -} - -func (d *Driver) getIPfromDHCPLeaseFile(dhcpfile, macaddr string) (string, error) { - - var dhcpfh *os.File - var dhcpcontent []byte - var lastipmatch string - var currentip string - var lastleaseendtime time.Time - var currentleadeendtime time.Time - var err error - - if dhcpfh, err = os.Open(dhcpfile); err != nil { - return "", err - } - defer dhcpfh.Close() - - if dhcpcontent, err = ioutil.ReadAll(dhcpfh); err != nil { - return "", err - } - - // Get the IP from the lease table. - leaseip := regexp.MustCompile(`^lease (.+?) {$`) - // Get the lease end date time. - leaseend := regexp.MustCompile(`^\s*ends \d (.+?);$`) - // Get the MAC address associated. - leasemac := regexp.MustCompile(`^\s*hardware ethernet (.+?);$`) - - for _, line := range strings.Split(string(dhcpcontent), "\n") { - - if matches := leaseip.FindStringSubmatch(line); matches != nil { - lastipmatch = matches[1] - continue - } - - if matches := leaseend.FindStringSubmatch(line); matches != nil { - lastleaseendtime, _ = time.Parse("2006/01/02 15:04:05", matches[1]) - continue - } - - if matches := leasemac.FindStringSubmatch(line); matches != nil && matches[1] == macaddr && currentleadeendtime.Before(lastleaseendtime) { - currentip = lastipmatch - currentleadeendtime = lastleaseendtime - } - } - - if currentip == "" { - return "", fmt.Errorf("IP not found for MAC %s in DHCP leases", macaddr) - } - - log.Debugf("IP found in DHCP lease table: %s", currentip) - - return currentip, nil -} - -func (d *Driver) publicSSHKeyPath() string { - return d.GetSSHKeyPath() + ".pub" -} - -// Make a boot2docker userdata.tar key bundle -func (d *Driver) generateKeyBundle() error { - log.Debugf("Creating Tar key bundle...") - - 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 nil - -} - -// execute command over SSH with user / password authentication -func executeSSHCommand(command string, d *Driver) error { - log.Debugf("Execute executeSSHCommand: %s", command) - - config := &cryptossh.ClientConfig{ - User: d.SSHUser, - Auth: []cryptossh.AuthMethod{ - cryptossh.Password(d.SSHPassword), - }, - } - - client, err := cryptossh.Dial("tcp", fmt.Sprintf("%s:%d", d.IPAddress, d.SSHPort), config) - if err != nil { - log.Debugf("Failed to dial:", err) - return err - } - - session, err := client.NewSession() - if err != nil { - log.Debugf("Failed to create session: " + err.Error()) - return err - } - defer session.Close() - - var b bytes.Buffer - session.Stdout = &b - - if err := session.Run(command); err != nil { - log.Debugf("Failed to run: " + err.Error()) - return err - } - log.Debugf("Stdout from executeSSHCommand: %s", b.String()) - - return nil -} diff --git a/server/vmware/vmware.go b/server/vmware/vmware.go index a1e0923..f57a8ea 100644 --- a/server/vmware/vmware.go +++ b/server/vmware/vmware.go @@ -20,7 +20,7 @@ func List(w http.ResponseWriter, r *http.Request) { // panic(err) // } d := vmware.NewDriver("", "") - outPut := d.GetMachineName() + outPut := d.DriverName() // if err != nil { // w.WriteHeader(http.StatusInternalServerError) // w.Write([]byte(err.Error()))