diff --git a/go.mod b/go.mod index 6e3acd7b0a3c..63075f5fdb18 100644 --- a/go.mod +++ b/go.mod @@ -39,7 +39,6 @@ require ( github.com/juju/mutex/v2 v2.0.0 github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 github.com/klauspost/cpuid v1.2.0 - github.com/machine-drivers/docker-machine-driver-vmware v0.1.5 github.com/mattbaird/jsonpatch v0.0.0-20200820163806-098863c1fc24 github.com/mattn/go-isatty v0.0.20 github.com/mitchellh/go-ps v1.0.0 @@ -258,5 +257,4 @@ require ( replace ( github.com/Parallels/docker-machine-parallels/v2 => github.com/minikube-machine/machine-driver-parallels/v2 v2.0.2-0.20240730142131-ada9375ea417 github.com/docker/machine => github.com/minikube-machine/machine v0.0.0-20251109100456-3b479dcea7a3 - github.com/machine-drivers/docker-machine-driver-vmware => github.com/minikube-machine/machine-driver-vmware v0.1.6-0.20230701123042-a391c48b14d5 ) diff --git a/go.sum b/go.sum index d80559e27379..7bee6bbf7249 100644 --- a/go.sum +++ b/go.sum @@ -182,7 +182,6 @@ github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBi github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= github.com/docker/docker v0.0.0-20180621001606-093424bec097/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v17.12.0-ce-rc1.0.20181225093023-5ddb1d410a8b+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= -github.com/docker/docker v17.12.0-ce-rc1.0.20190115220918-5ec31380a5d3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v28.3.3+incompatible h1:Dypm25kh4rmk49v1eiVbsAtpAsYURjYkaKubwuBdxEI= github.com/docker/docker v28.3.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8= @@ -304,7 +303,6 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= @@ -477,8 +475,6 @@ github.com/minikube-machine/machine v0.0.0-20251109100456-3b479dcea7a3 h1:LO7khk github.com/minikube-machine/machine v0.0.0-20251109100456-3b479dcea7a3/go.mod h1:wrzTHaSSmyll2TxLCq4mTFL5RJfeFr6qger+VcqNm9g= github.com/minikube-machine/machine-driver-parallels/v2 v2.0.2-0.20240730142131-ada9375ea417 h1:f+neTRGCtvmW3Tm1V72vWpoTPuNOnXSQsHZdYOryfGM= github.com/minikube-machine/machine-driver-parallels/v2 v2.0.2-0.20240730142131-ada9375ea417/go.mod h1:NKwI5KryEmEHMZVj80t9JQcfXWZp4/ZYNBuw4C5sQ9E= -github.com/minikube-machine/machine-driver-vmware v0.1.6-0.20230701123042-a391c48b14d5 h1:1z7xOzfMO4aBR9+2nYjlhRXX1773fX60HTS0QGpGRPU= -github.com/minikube-machine/machine-driver-vmware v0.1.6-0.20230701123042-a391c48b14d5/go.mod h1:HifYFOWR0bAMN4hWtaSADClogvtPy/jV0aRC5alhrKo= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc= @@ -618,7 +614,6 @@ github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU= github.com/shoenig/test v0.6.4/go.mod h1:byHiCGXqrVaflBLAMq/srcZIHynQPQgeyvkvXnjqq0k= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= @@ -727,7 +722,6 @@ go4.org v0.0.0-20180809161055-417644f6feb5/go.mod h1:MkTOUMDaeVYJUOUsaDXIhWPZYa1 golang.org/x/build v0.0.0-20190927031335-2835ba2e683f h1:hXVePvSFG7tPGX4Pwk1d10ePFfoTCc0QmISfpKOHsS8= golang.org/x/build v0.0.0-20190927031335-2835ba2e683f/go.mod h1:fYw7AShPAhGMdXqA9gRadk/CcMsvLlClpE5oBwnS3dM= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190424203555-c05e17bb3b2d/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= diff --git a/pkg/drivers/vmware/LICENSE b/pkg/drivers/vmware/LICENSE new file mode 100644 index 000000000000..78858b14b0a5 --- /dev/null +++ b/pkg/drivers/vmware/LICENSE @@ -0,0 +1,14 @@ +Copyright 2017 The Kubernetes Authors All rights reserved. +Copyright 2017 VMware, Inc. All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/pkg/drivers/vmware/config.go b/pkg/drivers/vmware/config.go new file mode 100644 index 000000000000..9caf412fe9f7 --- /dev/null +++ b/pkg/drivers/vmware/config.go @@ -0,0 +1,138 @@ +/* +Copyright 2017 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* + * Copyright 2017 VMware, Inc. All rights reserved. Licensed under the Apache v2 License. + */ + +package vmware + +import ( + "github.com/docker/machine/libmachine/drivers" + "github.com/docker/machine/libmachine/mcnflag" +) + +const ( + defaultSSHUser = "docker" + defaultSSHPass = "tcuser" + defaultDiskSize = 20000 + defaultCPU = 1 + defaultMemory = 1024 + defaultWaitIP = 30000 + defaultNetworkType = "nat" +) + +// Config specifies the configuration of driver VMware +type Config struct { + *drivers.BaseDriver + + Memory int + DiskSize int + CPU int + ISO string + Boot2DockerURL string + + SSHPassword string + ConfigDriveISO string + ConfigDriveURL string + NoShare bool + + WaitIP int + NetworkType string +} + +// NewConfig creates a new Config +func NewConfig(hostname, storePath string) *Config { + return &Config{ + CPU: defaultCPU, + Memory: defaultMemory, + DiskSize: defaultDiskSize, + SSHPassword: defaultSSHPass, + WaitIP: defaultWaitIP, + NetworkType: defaultNetworkType, + BaseDriver: &drivers.BaseDriver{ + SSHUser: defaultSSHUser, + MachineName: hostname, + StorePath: storePath, + }, + } +} + +// GetCreateFlags registers the flags this driver adds to +// "docker hosts create" +func (c *Config) GetCreateFlags() []mcnflag.Flag { + return []mcnflag.Flag{ + mcnflag.StringFlag{ + EnvVar: "VMWARE_BOOT2DOCKER_URL", + Name: "vmware-boot2docker-url", + Usage: "URL for boot2docker image", + Value: "", + }, + mcnflag.StringFlag{ + EnvVar: "VMWARE_CONFIGDRIVE_URL", + Name: "vmware-configdrive-url", + Usage: "URL for cloud-init configdrive", + Value: "", + }, + mcnflag.IntFlag{ + EnvVar: "VMWARE_CPU_COUNT", + Name: "vmware-cpu-count", + Usage: "number of CPUs for the machine (-1 to use the number of CPUs available)", + Value: defaultCPU, + }, + mcnflag.IntFlag{ + EnvVar: "VMWARE_MEMORY_SIZE", + Name: "vmware-memory-size", + Usage: "size of memory for host VM (in MB)", + Value: defaultMemory, + }, + mcnflag.IntFlag{ + EnvVar: "VMWARE_DISK_SIZE", + Name: "vmware-disk-size", + Usage: "size of disk for host VM (in MB)", + Value: defaultDiskSize, + }, + mcnflag.StringFlag{ + EnvVar: "VMWARE_SSH_USER", + Name: "vmware-ssh-user", + Usage: "SSH user", + Value: defaultSSHUser, + }, + mcnflag.StringFlag{ + EnvVar: "VMWARE_SSH_PASSWORD", + Name: "vmware-ssh-password", + Usage: "SSH password", + Value: defaultSSHPass, + }, + mcnflag.BoolFlag{ + EnvVar: "VMWARE_NO_SHARE", + Name: "vmware-no-share", + Usage: "Disable the mount of your home directory", + }, + mcnflag.IntFlag{ + EnvVar: "VMWARE_WAIT_IP", + Name: "vmware-wait-ip", + Usage: "time to wait for vmrun to get an ip (in milliseconds)", + Value: defaultWaitIP, + }, + mcnflag.StringFlag{ + EnvVar: "VMWARE_NETWORK_TYPE", + Name: "vmware-network-type", + Usage: "Network connection type to use (e.g. 'nat', 'bridged', 'hostonly')", + Value: defaultNetworkType, + }, + } +} diff --git a/pkg/drivers/vmware/driver.go b/pkg/drivers/vmware/driver.go new file mode 100644 index 000000000000..e5bd5f3e22d7 --- /dev/null +++ b/pkg/drivers/vmware/driver.go @@ -0,0 +1,629 @@ +/* +Copyright 2017 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* + * Copyright 2017 VMware, Inc. All rights reserved. Licensed under the Apache v2 License. + */ + +package vmware + +import ( + "archive/tar" + "bytes" + "fmt" + "io" + "net" + "os" + "path/filepath" + "regexp" + "runtime" + "strings" + "text/template" + "time" + + "github.com/pkg/errors" + + "github.com/docker/machine/libmachine/drivers" + "github.com/docker/machine/libmachine/log" + "github.com/docker/machine/libmachine/mcnutils" + "github.com/docker/machine/libmachine/ssh" + "github.com/docker/machine/libmachine/state" +) + +const ( + isoFilename = "boot2docker.iso" + isoConfigDrive = "configdrive.iso" +) + +// Driver for VMware +type Driver struct { + *Config +} + +func NewDriver(hostname, storePath string) drivers.Driver { + return &Driver{ + Config: NewConfig(hostname, 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 "vmware" +} + +func (d *Driver) SetConfigFromFlags(flags drivers.DriverOptions) error { + d.Memory = flags.Int("vmware-memory-size") + d.CPU = flags.Int("vmware-cpu-count") + d.DiskSize = flags.Int("vmware-disk-size") + d.Boot2DockerURL = flags.String("vmware-boot2docker-url") + d.ConfigDriveURL = flags.String("vmware-configdrive-url") + d.ISO = d.ResolveStorePath(isoFilename) + d.ConfigDriveISO = d.ResolveStorePath(isoConfigDrive) + d.SetSwarmConfigFromFlags(flags) + d.SSHUser = flags.String("vmware-ssh-user") + d.SSHPassword = flags.String("vmware-ssh-password") + d.SSHPort = 22 + d.NoShare = flags.Bool("vmware-no-share") + d.WaitIP = flags.Int("vmware-wait-ip") + d.NetworkType = flags.String("vmware-network-type") + + // We support a maximum of 16 cpu to be consistent with Virtual Hardware 10 + // specs. + if d.CPU < 1 { + d.CPU = 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() (ip string, err error) { + s, err := d.GetState() + if err != nil { + return "", err + } + + if s != state.Running { + return "", drivers.ErrHostIsNotRunning + } + + // attempt to find the address from vmrun + if ip, err := d.getIPfromVmrun(); err == nil { + return ip, nil + } + + // 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, nil + } + + // 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) + return b2dutils.UpdateISOCache(d.Boot2DockerURL) +} + +func (d *Driver) Create() error { + if err := os.MkdirAll(filepath.Join(d.StorePath, "machines", d.GetMachineName()), 0755); err != nil { + return err + } + + 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 + } + if err = vmxt.Execute(vmxfile, d); err != nil { + return err + } + + // Generate vmdk file + diskImg := d.vmdkPath() + if _, err := os.Stat(diskImg); err != nil { + if !os.IsNotExist(err) { + return err + } + + if err := d.generateDiskImage(); err != nil { + return err + } + } + + return d.Start() +} + +func (d *Driver) generateDiskImage() error { + diskImg := d.vmdkPath() + + log.Infof("Creating %d MB hard disk image at %s...", d.DiskSize, diskImg) + + 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 := os.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(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(pubKey); err != nil { + return err + } + if err := tw.Close(); err != nil { + return err + } + + // we create a 1 MB temporary preallocated disk + // this will create 2 files: + // - ${name}-tmp.vmdk - a text file containing disk metadata + // - ${name}-tmp-flat.vmdk - disk raw data, initially filled with zeroes + // where ${name}.vmdk is the expected disk filename + tmpDiskPath := strings.Replace(diskImg, ".vmdk", "-tmp.vmdk", 1) + err = createDisk(tmpDiskPath, 1, diskTypePreallocated) + if err != nil { + return err + } + + // we write the tar stream at the beginning of the temporary disk raw data + tmpFlatPath := strings.Replace(tmpDiskPath, "-tmp.vmdk", "-tmp-flat.vmdk", 1) + f, err := os.OpenFile(tmpFlatPath, os.O_WRONLY, 0) + if err != nil { + return err + } + _, err = f.WriteAt(buf.Bytes(), 0) + f.Close() + if err != nil { + return err + } + + // we convert the temporary disk to a single, growable expected disk + err = convertDisk(tmpDiskPath, diskImg, diskTypeGrowable) + if err != nil { + return err + } + // and grow it to the expected size + err = growDisk(diskImg, d.DiskSize) + if err != nil { + return err + } + + // finally, we clean up the temporary disk + err = os.Remove(tmpFlatPath) + if err != nil { + return err + } + err = os.Remove(tmpDiskPath) + if err != nil { + return err + } + + return nil +} + +func (d *Driver) Start() error { + var ip string + var err error + + log.Infof("Starting %s...", d.MachineName) + _, _, err = vmrun("start", d.vmxPath(), "nogui") + if err != nil { + return err + } + + 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), 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 errors.New("machine didn't return an IP after 120 seconds, aborting") + } + + // we got an IP + d.IPAddress = ip + + 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 + return d.Start() +} + +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 errors.New("error stopping VM before deletion") + } + } + log.Infof("Deleting %s...", d.MachineName) + if _, _, err := vmrun("deleteVM", d.vmxPath(), "nogui"); err != nil { + return err + } + return nil +} + +func (d *Driver) Upgrade() error { + return errors.New("VMware 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 = io.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") { + matches := vmxparse.FindStringSubmatch(line) + if matches == nil { + continue + } + 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) getIPfromVmrun() (string, error) { + vmx := d.vmxPath() + + ip := regexp.MustCompile(`\d+\.\d+\.\d+\.\d+`) + stdout, _, _ := vmrunWait(time.Duration(d.WaitIP)*time.Millisecond, "getGuestIPAddress", vmx, "-wait") + if match := ip.FindString(stdout); match != "" { + return match, nil + } + + return "", errors.New("could not get IP from vmrun") +} + +func (d *Driver) getIPfromVmnetConfiguration(macaddr string) (string, error) { + + // DHCP lease table for NAT vmnet interface + confFiles, _ := filepath.Glob(DhcpConfigFiles()) + 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, nil + } + } + + 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 = io.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 (.+?);\r?$`) + // Get the MAC address associated. + mac := regexp.MustCompile(`^\s*hardware ethernet (.+?);\r?$`) + + // 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++ + 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-- + + 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(DhcpLeaseFiles()) + 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, nil + } + } + + 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 = io.ReadAll(dhcpfh); err != nil { + return "", err + } + + // Get the IP from the lease table. + leaseip := regexp.MustCompile(`^lease (.+?) {\r?$`) + // Get the lease end date time. + leaseend := regexp.MustCompile(`^\s*ends \d (.+?);\r?$`) + // Get the MAC address associated. + leasemac := regexp.MustCompile(`^\s*hardware ethernet (.+?);\r?$`) + + 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); len(matches) > 0 && 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" +} diff --git a/pkg/drivers/vmware/vmrun.go b/pkg/drivers/vmware/vmrun.go new file mode 100644 index 000000000000..6257131a5c78 --- /dev/null +++ b/pkg/drivers/vmware/vmrun.go @@ -0,0 +1,129 @@ +/* +Copyright 2017 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* + * Copyright 2017 VMware, Inc. All rights reserved. Licensed under the Apache v2 License. + */ + +package vmware + +import ( + "bytes" + "context" + "errors" + "fmt" + "io" + "os" + "os/exec" + "strings" + "time" + + "github.com/docker/machine/libmachine/log" +) + +type diskType int + +const ( + diskTypeGrowable diskType = 0 + diskTypePreallocated diskType = 2 +) + +var ( + vmrunbin = setVmwareCmd("vmrun") + vdiskmanbin = setVmwareCmd("vmware-vdiskmanager") +) + +var ( + ErrMachineExist = errors.New("machine already exists") + ErrMachineNotExist = errors.New("machine does not exist") + ErrVMRUNNotFound = errors.New("vmrun.exe not found") + ErrVDISKMANNotFound = errors.New("vmware-vdiskmanager.exe not found") +) + +func init() { + // vmrun with nogui on VMware Fusion through at least 8.0.1 doesn't work right + // if the umask is set to not allow world-readable permissions + SetUmask() +} + +func isMachineDebugEnabled() bool { + return os.Getenv("MACHINE_DEBUG") != "" +} + +func vmrun(args ...string) (string, string, error) { + cmd := exec.Command(vmrunbin, args...) + return vmrunCmd(cmd) +} + +func vmrunWait(timeout time.Duration, args ...string) (string, string, error) { + ctx, cancel := context.WithTimeout(context.Background(), timeout) + defer cancel() + + cmd := exec.CommandContext(ctx, vmrunbin, args...) + return vmrunCmd(cmd) +} + +func vmrunCmd(cmd *exec.Cmd) (string, string, error) { + var stdout bytes.Buffer + var stderr bytes.Buffer + cmd.Stdout, cmd.Stderr = &stdout, &stderr + + if isMachineDebugEnabled() { + // write stdout to stderr because stdout is used for parsing sometimes + cmd.Stdout = io.MultiWriter(os.Stderr, cmd.Stdout) + cmd.Stderr = io.MultiWriter(os.Stderr, cmd.Stderr) + } + + log.Debugf("executing: %v", strings.Join(cmd.Args, " ")) + + err := cmd.Run() + if err != nil { + if ee, ok := err.(*exec.Error); ok && ee == exec.ErrNotFound { + err = ErrVMRUNNotFound + } + } + + return stdout.String(), stderr.String(), err +} + +func vdiskmanager(args ...string) error { + cmd := exec.Command(vdiskmanbin, args...) + if isMachineDebugEnabled() { + // write stdout to stderr because stdout is used for parsing sometimes + cmd.Stdout = os.Stderr + cmd.Stderr = os.Stderr + } + + if stdout := cmd.Run(); stdout != nil { + if ee, ok := stdout.(*exec.Error); ok && ee == exec.ErrNotFound { + return ErrVDISKMANNotFound + } + } + return nil +} + +// Make a vmdk disk image with the given size (in MB). +func createDisk(path string, sizeInMB int, diskType diskType) error { + return vdiskmanager("-c", "-t", fmt.Sprintf("%d", diskType), "-s", fmt.Sprintf("%dMB", sizeInMB), "-a", "lsilogic", path) +} + +func convertDisk(srcPath string, destPath string, diskType diskType) error { + return vdiskmanager("-r", srcPath, "-t", fmt.Sprintf("%d", diskType), destPath) +} + +func growDisk(path string, sizeInMB int) error { + return vdiskmanager("-x", fmt.Sprintf("%dMB", sizeInMB), path) +} diff --git a/pkg/drivers/vmware/vmware.go b/pkg/drivers/vmware/vmware.go new file mode 100644 index 000000000000..769f7896f961 --- /dev/null +++ b/pkg/drivers/vmware/vmware.go @@ -0,0 +1,40 @@ +//go:build !darwin && !linux && !windows + +/* +Copyright 2017 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package vmware + +import "github.com/docker/machine/libmachine/drivers" + +func NewDriver(hostName, storePath string) drivers.Driver { + return drivers.NewDriverNotSupported("vmware", hostName, storePath) +} + +func DhcpConfigFiles() string { + return "" +} + +func DhcpLeaseFiles() string { + return "" +} + +func SetUmask() { +} + +func setVmwareCmd(cmd string) string { + return "" +} diff --git a/pkg/drivers/vmware/vmware_darwin.go b/pkg/drivers/vmware/vmware_darwin.go new file mode 100644 index 000000000000..be52dcdcde3e --- /dev/null +++ b/pkg/drivers/vmware/vmware_darwin.go @@ -0,0 +1,53 @@ +/* +Copyright 2017 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package vmware + +import ( + "os" + "os/exec" + "path/filepath" + "syscall" +) + +func DhcpConfigFiles() string { + return "/Library/Preferences/VMware Fusion/vmnet*/dhcpd.conf" +} + +func DhcpLeaseFiles() string { + return "/var/db/vmware/*.leases" +} + +func SetUmask() { + _ = syscall.Umask(022) +} + +// detect the vmrun and vmware-vdiskmanager cmds' path if needed +func setVmwareCmd(cmd string) string { + if path, err := exec.LookPath(cmd); err == nil { + return path + } + for _, fp := range []string{ + "/Applications/VMware Fusion.app/Contents/Library/", + } { + p := filepath.Join(fp, cmd) + _, err := os.Stat(p) + if err == nil { + return p + } + } + return cmd +} diff --git a/pkg/drivers/vmware/vmware_linux.go b/pkg/drivers/vmware/vmware_linux.go new file mode 100644 index 000000000000..661fac4b7d49 --- /dev/null +++ b/pkg/drivers/vmware/vmware_linux.go @@ -0,0 +1,42 @@ +/* +Copyright 2017 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package vmware + +import ( + "os/exec" + "syscall" +) + +func DhcpConfigFiles() string { + return "/etc/vmware/vmnet*/dhcpd/dhcpd.conf" +} + +func DhcpLeaseFiles() string { + return "/etc/vmware/vmnet*/dhcpd/dhcpd.leases" +} + +func SetUmask() { + _ = syscall.Umask(022) +} + +// detect the vmrun and vmware-vdiskmanager cmds' path if needed +func setVmwareCmd(cmd string) string { + if path, err := exec.LookPath(cmd); err == nil { + return path + } + return cmd +} diff --git a/pkg/drivers/vmware/vmware_windows.go b/pkg/drivers/vmware/vmware_windows.go new file mode 100644 index 000000000000..696b44a63fd3 --- /dev/null +++ b/pkg/drivers/vmware/vmware_windows.go @@ -0,0 +1,70 @@ +/* +Copyright 2017 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package vmware + +import ( + "fmt" + "os" + "path/filepath" + + "golang.org/x/sys/windows/registry" +) + +// https://docs.microsoft.com/en-us/windows/deployment/usmt/usmt-recognized-environment-variables + +func DhcpConfigFiles() string { + return filepath.Join(os.Getenv("ALLUSERSPROFILE"), `VMware\vmnetdhcp.conf`) +} + +func DhcpLeaseFiles() string { + return filepath.Join(os.Getenv("ALLUSERSPROFILE"), `VMware\vmnetdhcp.leases`) +} + +func SetUmask() { +} + +func setVmwareCmd(cmd string) string { + cmd = cmd + ".exe" + DefaultVMWareWSProductionRegistryKey := `SOFTWARE\WOW6432Node\VMware, Inc.` + DefaultVMwareCorePathKey := "Core" + k, err := registry.OpenKey(registry.LOCAL_MACHINE, DefaultVMWareWSProductionRegistryKey, registry.QUERY_VALUE) + if err != nil { + return "" + } + defer k.Close() + production, _, err := k.GetStringValue(DefaultVMwareCorePathKey) + if err != nil { + return "" + } + + //Get the VMware Product Install Path + DefaultVMwareWSRegistryKey := fmt.Sprintf(`SOFTWARE\WOW6432Node\VMware, Inc.\%s`, production) + DefaultVMwareWSInstallPathKey := "InstallPath" + + key, err := registry.OpenKey(registry.LOCAL_MACHINE, DefaultVMwareWSRegistryKey, registry.QUERY_VALUE) + if err != nil { + return "" + } + defer key.Close() + + value, _, err := key.GetStringValue(DefaultVMwareWSInstallPathKey) + if err != nil { + return "" + } + windowsInstallDir := value + return filepath.Join(windowsInstallDir, cmd) +} diff --git a/pkg/drivers/vmware/vmx.go b/pkg/drivers/vmware/vmx.go new file mode 100644 index 000000000000..84aad87c65af --- /dev/null +++ b/pkg/drivers/vmware/vmx.go @@ -0,0 +1,88 @@ +/* +Copyright 2017 The Kubernetes Authors All rights reserved. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +/* + * Copyright 2017 VMware, Inc. All rights reserved. Licensed under the Apache v2 License. + */ + +package vmware + +const vmx = ` +.encoding = "UTF-8" +config.version = "8" +displayName = "{{.MachineName}}" +ethernet0.present = "TRUE" +ethernet0.connectionType = "{{.NetworkType}}" +ethernet0.virtualDev = "vmxnet3" +ethernet0.wakeOnPcktRcv = "FALSE" +ethernet0.addressType = "generated" +ethernet0.linkStatePropagation.enable = "TRUE" +pciBridge0.present = "TRUE" +pciBridge4.present = "TRUE" +pciBridge4.virtualDev = "pcieRootPort" +pciBridge4.functions = "8" +pciBridge5.present = "TRUE" +pciBridge5.virtualDev = "pcieRootPort" +pciBridge5.functions = "8" +pciBridge6.present = "TRUE" +pciBridge6.virtualDev = "pcieRootPort" +pciBridge6.functions = "8" +pciBridge7.present = "TRUE" +pciBridge7.virtualDev = "pcieRootPort" +pciBridge7.functions = "8" +pciBridge0.pciSlotNumber = "17" +pciBridge4.pciSlotNumber = "21" +pciBridge5.pciSlotNumber = "22" +pciBridge6.pciSlotNumber = "23" +pciBridge7.pciSlotNumber = "24" +scsi0.pciSlotNumber = "160" +usb.pciSlotNumber = "32" +ethernet0.pciSlotNumber = "192" +sound.pciSlotNumber = "33" +vmci0.pciSlotNumber = "35" +sata0.pciSlotNumber = "36" +floppy0.present = "FALSE" +guestOS = "other3xlinux-64" +hpet0.present = "TRUE" +sata0.present = "TRUE" +sata0:1.present = "TRUE" +sata0:1.fileName = "{{.ISO}}" +sata0:1.deviceType = "cdrom-image" +{{ if .ConfigDriveURL }} +sata0:2.present = "TRUE" +sata0:2.fileName = "{{.ConfigDriveISO}}" +sata0:2.deviceType = "cdrom-image" +{{ end }} +vmci0.present = "TRUE" +mem.hotadd = "TRUE" +memsize = "{{.Memory}}" +powerType.powerOff = "soft" +powerType.powerOn = "soft" +powerType.reset = "soft" +powerType.suspend = "soft" +scsi0.present = "TRUE" +scsi0.virtualDev = "pvscsi" +scsi0:0.fileName = "{{.MachineName}}.vmdk" +scsi0:0.present = "TRUE" +tools.synctime = "TRUE" +virtualHW.productCompatibility = "hosted" +virtualHW.version = "10" +msg.autoanswer = "TRUE" +uuid.action = "create" +numvcpus = "{{.CPU}}" +hgfs.mapRootShare = "FALSE" +hgfs.linkRootShare = "FALSE" +` diff --git a/pkg/minikube/registry/drvs/vmware/vmware.go b/pkg/minikube/registry/drvs/vmware/vmware.go index 70911238ec3f..47b70fb62fe1 100644 --- a/pkg/minikube/registry/drvs/vmware/vmware.go +++ b/pkg/minikube/registry/drvs/vmware/vmware.go @@ -21,7 +21,8 @@ import ( "os/exec" "github.com/docker/machine/libmachine/drivers" - vmware "github.com/machine-drivers/docker-machine-driver-vmware/pkg/drivers/vmware" + + "k8s.io/minikube/pkg/drivers/vmware" "k8s.io/minikube/pkg/minikube/config" "k8s.io/minikube/pkg/minikube/download" "k8s.io/minikube/pkg/minikube/driver"