Repository: machine-drivers/docker-machine-driver-vmware Branch: master Commit: b30963f81e6e Files: 23 Total size: 60.7 KB Directory structure: gitextract_7ixkvlyp/ ├── .github/ │ ├── dependabot.yml │ └── workflows/ │ └── release.yml ├── .gitignore ├── .goreleaser.yml ├── LICENSE ├── Makefile ├── README.md ├── go.mod ├── go.sum ├── hack/ │ └── integration.sh ├── main.go ├── pkg/ │ └── drivers/ │ └── vmware/ │ ├── config/ │ │ └── config.go │ ├── driver.go │ ├── driver_test.go │ ├── vmrun.go │ ├── vmware.go │ ├── vmware_darwin.go │ ├── vmware_linux.go │ ├── vmware_windows.go │ └── vmx.go └── test/ ├── docker-machine.bats ├── env.bash └── test_helper.bash ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/dependabot.yml ================================================ version: 2 updates: - package-ecosystem: "gomod" directory: "/" schedule: interval: "daily" - package-ecosystem: "github-actions" directory: "/" schedule: interval: "daily" ================================================ FILE: .github/workflows/release.yml ================================================ name: release on: push: tags: - '*' jobs: release: runs-on: ubuntu-latest steps: - name: Checkout entire git history uses: actions/checkout@v3.0.0 with: fetch-depth: 0 - name: Set up Go uses: actions/setup-go@v3 with: go-version: '1.16' - name: Run GoReleaser uses: goreleaser/goreleaser-action@v2.9.1 with: version: latest args: release --rm-dist env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} ================================================ FILE: .gitignore ================================================ ############################# # Compiled source # ############################# dist/ out/ /docker-machine-driver-vmware /docker-machine-driver-vmware.exe ############################# # Go files # ############################# *.dll *.dylib *.exe *.exe~ *.so ############################# # IDE generated files # ############################# .idea/ .vscode/ *.iml ############################# # Logs and temp files # ############################# *.log *.out *.tmp *~ ############################# # OS generated files # ############################# Thumbs.db .directory .DS_Store ================================================ FILE: .goreleaser.yml ================================================ --- project_name: docker-machine-driver-vmware before: hooks: - go mod tidy # you may remove this if you don't need go generate # - go generate ./... builds: - env: - CGO_ENABLED=0 goos: - linux - windows - darwin goarch: - amd64 archives: - id: github name_template: '{{ .ProjectName }}_{{ .Version }}_{{ .Os }}_{{ .Arch }}' format: tar.gz files: - LICENSE* - README* checksum: name_template: '{{ .ProjectName }}_{{ .Version }}_checksums.txt' changelog: sort: asc filters: exclude: - '^typo' - 'version bump' brews: - folder: Formula tap: owner: mikeroySoft name: homebrew-tap commit_author: name: Michael Roy email: mike@mikeroysoft.com homepage: "https://github.com/machine-drivers/docker-machine-driver-vmware/blob/master/README.md" description: "Cross-platform docker-machine driver for VMware Fusion and Workstation" test: | system "#{bin}/docker-machine-driver-vmware -v" install: | bin.install "docker-machine-driver-vmware" ================================================ FILE: LICENSE ================================================ Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] 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. ================================================ FILE: Makefile ================================================ OUT_DIR := out PROG := docker-machine-driver-vmware GOOS ?= $(shell go env GOOS) GOARCH ?= $(shell go env GOARCH) ifeq ($(GOOS),windows) BIN_SUFFIX := ".exe" endif .PHONY: build build: go build -o $(OUT_DIR)/$(PROG)$(BIN_SUFFIX) ./ .PHONY: dep dep: dep ensure .PHONY: test test: go test -race ./... .PHONY: check check: gofmt -l -s -d pkg/ cmd/ go tool vet pkg/ cmd/ .PHONY: integration integration: ifeq ($(GOOS),windows) else hack/integration.sh endif ================================================ FILE: README.md ================================================ # Docker Machine VMware Driver Create Docker machines locally on VMware [Fusion](https://www.vmware.com/products/fusion) and [Workstation](https://www.vmware.com/products/workstation). This driver requires VMware Workstation 14 (Windows/Linux) or VMware Fusion 10 (macOS) to be installed on your host. Earlier versions of Workstation/Fusion might still work with this driver, but it's not officially supported. > > Docker machine has a builtin driver called `vmwarefusion`. The main difference between > those drivers is that `vmware` also works on VMware Workstation, while `vmwarefusion` only > works on VMware Fusion. > ## License The Docker Machine VMware Driver is released under the [Apache License 2.0](https://github.com/machine-drivers/docker-machine-driver-vmware/blob/master/LICENSE) license, for more information see LICENSE or https://www.apache.org/licenses/LICENSE-2.0 ## Installation ### From a Release The latest version of the `docker-machine-driver-vmware` binary is available on the [GitHub Releases](https://github.com/machine-drivers/docker-machine-driver-vmware/releases) page. Download the binary that corresponds to your OS into a directory residing in your PATH. ### From Homebrew The driver is available for easy installation via Homebrew on macOS. ```shell $ brew install docker-machine-driver-vmware ``` ### From Source Make sure you have installed [Go](http://www.golang.org) and configured [GOPATH](http://golang.org/doc/code.html#GOPATH) properly. For MacOS and Linux, make sure `$GOPATH/bin` is part of your `$PATH` for MacOS and Linux. For Windows, make sure `%GOPATH%\bin` is included in `%PATH%`. Run the following command: ```shell go get -u github.com/machine-drivers/docker-machine-driver-vmware ``` ## Usage ```shell $ docker-machine create --driver=vmware default ``` ## Options - `--vmware-boot2docker-url`: URL for boot2docker image - `--vmware-configdrive-url`: URL for cloud-init configdrive - `--vmware-cpu-count`: Number of CPUs for the machine (-1 to use the number of CPUs available) - `--vmware-disk-size`: Size of disk for host VM (in MB) - `--vmware-memory-size`: Size of memory for host VM (in MB) - `--vmware-network-type`: Network connection type to use (e.g. 'nat', 'bridged', 'hostonly') - `--vmware-no-share`: Disable the mount of your home directory - `--vmware-ssh-password`: SSH password - `--vmware-ssh-user`: SSH user - `--vmware-wait-ip`: Time to wait for vmrun to get an ip (in milliseconds) #### Environment variables and default values | CLI option | Environment variable | Default | |----------------------------|------------------------|--------------------------| | `--vmware-boot2docker-url` | VMWARE_BOOT2DOCKER_URL | *Latest boot2docker url* | | `--vmware-configdrive-url` | VMWARE_CONFIGDRIVE_URL | - | | `--vmware-cpu-count` | VMWARE_CPU_COUNT | `1` | | `--vmware-disk-size` | VMWARE_DISK_SIZE | `20000` | | `--vmware-memory-size` | VMWARE_MEMORY_SIZE | `1024` | | `--vmware-network-type` | VMWARE_NETWORK_TYPE | `nat` | | `--vmware-no-share` | VMWARE_NO_SHARE | - | | `--vmware-ssh-password` | VMWARE_SSH_PASSWORD | `tcuser` | | `--vmware-ssh-user` | VMWARE_SSH_USER | `docker` | | `--vmware-wait-ip` | VMWARE_WAIT_IP | `30000` | ================================================ FILE: go.mod ================================================ module github.com/machine-drivers/docker-machine-driver-vmware go 1.15 require ( github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect github.com/docker/docker v17.12.0-ce-rc1.0.20190115220918-5ec31380a5d3+incompatible // indirect github.com/docker/machine v0.16.2 github.com/google/go-cmp v0.5.4 // indirect github.com/hectane/go-acl v0.0.0-20190604041725-da78bae5fc95 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/sirupsen/logrus v1.3.0 // indirect golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc golang.org/x/sys v0.0.0-20190529164535-6a60838ec259 gotest.tools v2.2.0+incompatible // indirect ) replace github.com/docker/machine => github.com/machine-drivers/machine v0.7.1-0.20210719174735-6eca26732baa ================================================ FILE: go.sum ================================================ github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 h1:w+iIsaOQNcT7OZ575w+acHgRric5iCyQh+xv+KJ4HB8= github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/docker/docker v17.12.0-ce-rc1.0.20190115220918-5ec31380a5d3+incompatible h1:9BKrmOW0BfGRCQUqcrYRzqlBH5e91YnmLKBOANMwc5E= github.com/docker/docker v17.12.0-ce-rc1.0.20190115220918-5ec31380a5d3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/google/go-cmp v0.5.4 h1:L8R9j+yAqZuZjsqh/z+F1NCffTKKLShY6zXTItVIZ8M= github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/hectane/go-acl v0.0.0-20190604041725-da78bae5fc95 h1:S4qyfL2sEm5Budr4KVMyEniCy+PbS55651I/a+Kn/NQ= github.com/hectane/go-acl v0.0.0-20190604041725-da78bae5fc95/go.mod h1:QiyDdbZLaJ/mZP4Zwc9g2QsfaEA4o7XvvgZegSci5/E= github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/machine-drivers/machine v0.7.1-0.20210719174735-6eca26732baa h1:RDn5zVjqpQP8yElV/30YUNiDsjksDSqq30JVQfo1wzY= github.com/machine-drivers/machine v0.7.1-0.20210719174735-6eca26732baa/go.mod h1:79Uwa2hGd5S39LDJt58s8JZcIhGEK6pkq9bsuTbFWbk= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/sirupsen/logrus v1.3.0 h1:hI/7Q+DtNZ2kINb6qt/lS+IyXnHQe9e90POfeewL/ME= github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc h1:F5tKCVGp+MUAHhKp5MZtGqAlGX3+oCsiL1Q629FL90M= golang.org/x/crypto v0.0.0-20190103213133-ff983b9c42bc/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190529164535-6a60838ec259 h1:so6Hr/LodwSZ5UQDu/7PmQiDeS112WwtLvU3lpSPZTU= golang.org/x/sys v0.0.0-20190529164535-6a60838ec259/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= ================================================ FILE: hack/integration.sh ================================================ #!/bin/bash set -ex ROOT=$(git rev-parse --show-toplevel) check_tool() { echo "Checking tool $1 ..." type "$1" >/dev/null || ( echo "error: missing $1" && exit 1 ) } check_tool bats check_tool docker-machine check_tool docker-machine-driver-vmware bats $ROOT/test/*.bats ================================================ FILE: main.go ================================================ /* Copyright 2016 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 main import ( "github.com/docker/machine/libmachine/drivers/plugin" "github.com/machine-drivers/docker-machine-driver-vmware/pkg/drivers/vmware" ) func main() { plugin.RegisterDriver(vmware.NewDriver("", "")) } ================================================ FILE: pkg/drivers/vmware/config/config.go ================================================ /* 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 config 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, }, } } ================================================ FILE: pkg/drivers/vmware/driver.go ================================================ /* 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/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/mcnutils" "github.com/docker/machine/libmachine/ssh" "github.com/docker/machine/libmachine/state" "github.com/machine-drivers/docker-machine-driver-vmware/pkg/drivers/vmware/config" cryptossh "golang.org/x/crypto/ssh" ) const ( isoFilename = "boot2docker.iso" isoConfigDrive = "configdrive.iso" ) // Driver for VMware type Driver struct { *config.Config } func NewDriver(hostname, storePath string) drivers.Driver { return &Driver{ Config: 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 = 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() (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, err } // 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) return b2dutils.UpdateISOCache(d.Boot2DockerURL) } func (d *Driver) Create() error { os.MkdirAll(filepath.Join(d.StorePath, "machines", d.GetMachineName()), 0755) 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 } } return d.Start() } func (d *Driver) Start() error { log.Infof("Starting %s...", d.MachineName) vmrun("start", d.vmxPath(), "nogui") var ip string var err error 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", d.SSHUser, "-gp", d.SSHPassword, "directoryExistsInGuest", d.vmxPath(), "/var/lib/boot2docker") // Copy SSH keys bundle vmrun("-gu", d.SSHUser, "-gp", d.SSHPassword, "CopyFileFromHostToGuest", d.vmxPath(), d.ResolveStorePath("userdata.tar"), "/home/docker/userdata.tar") // Expand tar file. vmrun("-gu", d.SSHUser, "-gp", d.SSHPassword, "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", d.SSHUser, "-gp", d.SSHPassword, "runScriptInGuest", d.vmxPath(), "/bin/sh", "sudo /bin/mv /home/docker/userdata.tar /var/lib/boot2docker/userdata.tar") // Enable Shared Folders vmrun("-gu", d.SSHUser, "-gp", d.SSHPassword, "enableSharedFolders", d.vmxPath()) shareName, hostDir, shareDir := getShareDriveAndName() if hostDir != "" && !d.NoShare { if _, err := os.Stat(hostDir); err != nil && !os.IsNotExist(err) { return err } else if !os.IsNotExist(err) { // add shared folder, create mountpoint and mount it. vmrun("-gu", d.SSHUser, "-gp", d.SSHPassword, "addSharedFolder", d.vmxPath(), shareName, hostDir) command := mountCommand(shareName, shareDir) vmrun("-gu", d.SSHUser, "-gp", d.SSHPassword, "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 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 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 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) getIPfromVmrun() (string, error) { vmx := d.vmxPath() ip := regexp.MustCompile(`\d+\.\d+\.\d+\.\d+`) stdout, _, _ := vmrun_wait(time.Duration(d.WaitIP)*time.Millisecond, "getGuestIPAddress", vmx, "-wait") if match := ip.FindString(stdout); match != "" { return match, nil } return "", fmt.Errorf("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, 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 (.+?);\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 = 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(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, 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 (.+?) {\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); 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 } return tw.Close() } // 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 } func mountCommand(shareName, shareDir string) string { // Replace C:\ to / due to Windows/Linux path difference shareDir = strings.Replace(shareDir, "C:\\", "/", 1) return "[ ! -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 } ================================================ FILE: pkg/drivers/vmware/driver_test.go ================================================ /* 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 ( "io/ioutil" "log" "os" "testing" "github.com/docker/machine/libmachine/drivers" "github.com/docker/machine/libmachine/state" ) var skip = !check(vmrunbin) || !check(vdiskmanbin) func check(path string) bool { _, err := os.Stat(path) if err != nil { log.Printf("%q is missing", path) return false } return true } func TestSetConfigFromFlags(t *testing.T) { driver := NewDriver("default", "path") checkFlags := &drivers.CheckDriverOptions{ FlagsValues: map[string]interface{}{}, CreateFlags: driver.GetCreateFlags(), } err := driver.SetConfigFromFlags(checkFlags) if err != nil { t.Fatal(err) } if len(checkFlags.InvalidFlags) != 0 { t.Fatalf("expect len(checkFlags.InvalidFlags) == 0; got %d", len(checkFlags.InvalidFlags)) } } func TestDriver(t *testing.T) { // skip driver tests if true { t.Skip() } path, err := ioutil.TempDir("", "vmware-driver-test") if err != nil { t.Fatal(err) } defer os.RemoveAll(path) driver := NewDriver("default", path) checkFlags := &drivers.CheckDriverOptions{ FlagsValues: map[string]interface{}{}, CreateFlags: driver.GetCreateFlags(), } err = driver.SetConfigFromFlags(checkFlags) if err != nil { t.Fatal(err) } driver.(*Driver).Boot2DockerURL = "https://github.com/boot2docker/boot2docker/releases/download/v17.10.0-ce-rc2/boot2docker.iso" err = driver.Create() if err != nil { t.Fatal(err) } defer driver.Remove() st, err := driver.GetState() if err != nil { t.Fatal(err) } if st != state.Running { t.Fatalf("expect state == Running; got %s", st.String()) } ip, err := driver.GetIP() if err != nil { t.Fatal(err) } if ip == "" { t.Fatal("expect ip non-zero; got ''") } username := driver.GetSSHUsername() if username == "" { t.Fatal("expect username non-zero; got ''") } key := driver.GetSSHKeyPath() if key == "" { t.Fatal("expect key non-zero; got ''") } port, err := driver.GetSSHPort() if err != nil { t.Fatal(err) } if port == 0 { t.Fatal("expect port not 0; got 0") } host, err := driver.GetSSHHostname() if err != nil { t.Fatal(err) } if host == "" { t.Fatal("expect host non-zero; got ''") } } ================================================ FILE: pkg/drivers/vmware/vmrun.go ================================================ /* 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" ) 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 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 vmrun_cmd(cmd) } func vmrun_wait(timeout time.Duration, args ...string) (string, string, error) { ctx, cancel := context.WithTimeout(context.Background(), timeout) defer cancel() cmd := exec.CommandContext(ctx, vmrunbin, args...) return vmrun_cmd(cmd) } func vmrun_cmd(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 } // Make a vmdk disk image with the given size (in MB). func vdiskmanager(dest string, size int) error { cmd := exec.Command(vdiskmanbin, "-c", "-t", "0", "-s", fmt.Sprintf("%dMB", size), "-a", "lsilogic", dest) 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 ErrVMRUNNotFound } } return nil } ================================================ FILE: pkg/drivers/vmware/vmware.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 "" } func getShareDriveAndName() (string, string, string) { return "", "", "" } ================================================ FILE: pkg/drivers/vmware/vmware_darwin.go ================================================ /* 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 } func getShareDriveAndName() (string, string, string) { return "Users", "/Users", "/hosthome" } ================================================ FILE: pkg/drivers/vmware/vmware_linux.go ================================================ /* 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 } func getShareDriveAndName() (string, string, string) { return "hosthome", "/home", "/hosthome" } ================================================ FILE: pkg/drivers/vmware/vmware_windows.go ================================================ /* 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) } func getShareDriveAndName() (string, string, string) { return "Users", os.Getenv("PUBLIC"), "/hosthome" } ================================================ FILE: pkg/drivers/vmware/vmx.go ================================================ /* 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" ` ================================================ FILE: test/docker-machine.bats ================================================ #!/usr/bin/env bats load test_helper load env @test "lifecycle" { id="vm-$$" run docker-machine create --driver="${DRIVER}" "${id}" assert_success run docker-machine status "${id}" assert_output "Running" run docker-machine url "${id}" assert_matches "^[a-z]+://([0-9]{1,3}\.){3}[0-9]{1,3}:[0-9]+$" run docker-machine ip "${id}" assert_matches "^([0-9]{1,3}\.){3}[0-9]{1,3}$" run docker-machine ssh "$id" ls assert_matches "boot2docker" run docker-machine restart "${id}" assert_success run docker-machine stop "${id}" assert_success run docker-machine status "${id}" assert_output "Stopped" run docker-machine start "${id}" assert_success run docker-machine kill "${id}" assert_success run docker-machine status "${id}" assert_output "Stopped" run docker-machine rm "${id}" -f assert_success run docker-machine status "${id}" assert_failure } ================================================ FILE: test/env.bash ================================================ DRIVER=vmware ================================================ FILE: test/test_helper.bash ================================================ # the following helpers are borrowed from the test_helper.bash in https://github.com/sstephenson/rbenv flunk() { { if [ "$#" -eq 0 ]; then cat - else echo "$@" fi } >&2 return 1 } assert_success() { if [ "$status" -ne 0 ]; then flunk "command failed with exit status $status: $output" elif [ "$#" -gt 0 ]; then assert_output "$1" fi } assert_failure() { if [ "$status" -ne 1 ]; then flunk $(printf "expected failed exit status=1, got status=%d" $status) elif [ "$#" -gt 0 ]; then assert_output "$1" fi } assert_equal() { if [ "$1" != "$2" ]; then { echo "expected: $1" echo "actual: $2" } | flunk fi } assert_output() { local expected if [ $# -eq 0 ]; then expected="$(cat -)" else expected="$1" fi assert_equal "$expected" "$output" } assert_matches() { local pattern="${1}" local actual="${2}" if [ $# -eq 1 ]; then actual="$output" fi if ! grep -E -q "${pattern}" <<<"${actual}"; then { echo "pattern: ${pattern}" echo "actual: ${actual}" } | flunk fi } assert_number() { assert_matches "^-?[0-9]+$" "$output" } assert_empty() { local actual="${1}" if [ $# -eq 0 ]; then actual="$(cat -)" fi if [ -n "${actual}" ]; then { echo "actual: ${actual}" } | flunk fi } assert_line() { if [ "$1" -ge 0 ] 2>/dev/null; then assert_equal "$2" "$(collapse_ws ${lines[$1]})" else local line for line in "${lines[@]}"; do if [ "$(collapse_ws $line)" = "$1" ]; then return 0; fi done flunk "expected line \`$1'" fi } refute_line() { if [ "$1" -ge 0 ] 2>/dev/null; then local num_lines="${#lines[@]}" if [ "$1" -lt "$num_lines" ]; then flunk "output has $num_lines lines" fi else local line for line in "${lines[@]}"; do if [ "$line" = "$1" ]; then flunk "expected to not find line \`$line'" fi done fi } assert() { if ! "$@"; then flunk "failed: $*" fi }