This commit is contained in:
Misty Stanley-Jones 2016-09-28 20:44:12 -07:00
commit 29f7d3396b
1058 changed files with 319307 additions and 76 deletions

4
.dockerignore Normal file
View File

@ -0,0 +1,4 @@
docker-machine*
*.log
bin
cover

62
.gitignore vendored
View File

@ -1,62 +0,0 @@
OSS-LICENSES
.DS_Store
shell/shell
shell/shell-history
shell/out.txt
shell/shell-config.json
shell/shell-env.json
shell9pAPI/shell9pAPI
shell9pAPI/sftp/sftp
shell/sftpKeys
shellAgent/shellAgent
shellAgent/hostKey.pem
shell/key.pem
shell/key.pub
agentSDK/node/node_modules/*
!agentSDK/node/node_modules/dockersdk
agentSDK/lib/agentlib.dylib
agentSDK/node/key.pem
agentSDK/node/keyC.pem
agentSDK/node/keyNode.pem
_tests
all-packages.txt
# ignore compiled binaries
v1/agent/agent
v1/agent/com.docker.agent.exe
v1/cmd/proxy/proxy.exe
# ignore generated SSH keys
v1/clitool/key.pem
v1/agent/hostKey.pem
# ignore files generated by cli testing
v1/agent/application_storage
v1/bin
v1/cmd/agent/agent
v1/db/irmin/_build
v1/db/irmin9p/irmin9p
v1/vendor/github.com/mortdeus/go9p/srv/examples/ufs/ufs
# ignore compiled boot2docker, since we will rebuild it
v1/boot2docker/boot2docker-data.img.tar
v1/boot2docker/vmlinuz64
v1/boot2docker/initrd.img
v1/devtool_sdk/libsrpc/libsrpc
v1/devtool_sdk/libsrpc/libsrpc.dylib
v1/devtool_sdk/libsrpc/libsrpc.h
# ignore temporary files from editors
*.swp
*~
*.sdf
*.VC.opendb
_cache
# PLEASE DON'T PUT SUBPROJECT IGNORE PATTERNS HERE.
# PUT THEM IN THE SUBPROJECT'S .gitignore FILE.
# THIS MAKES MAINTENANCE EASIER. THANK YOU.
v1/mac/dependencies
v1/mac/bin/Windows/Dependencies/HyperVInstaller.ps1

1
.godir
View File

@ -1 +0,0 @@
github.com/docker/swarm

View File

@ -1,13 +0,0 @@
sudo: false
language: node_js
node_js:
- "4.2.2"
cache:
directories:
- node_modules
script:
- npm install
- npm test

629
CHANGELOG.md Normal file
View File

@ -0,0 +1,629 @@
# Changelog
# 0.8.0 (2016-6-14)
General
- Fix issue with plugin heartbeat log repeating on disconnect
- Add `tcsh` support to `env --shell`
- Add `zsh` completion scripts
- Bump Go version to 1.6.2
Drivers
- `amazonec2`
- Workaround to prevent orphaned SSH keys
- `virtualbox`
- Add option for VM UI type (`--virtualbox-ui-type`)
- `vmwarefusion`
- Fix CPU option inconsistency
- `openstack`
- Expose user data parameter (`--openstack-user-data-file`)
- `generic`
- Copy public key to created Machine directory
Provisioners
- Add Oracle Enterprise Linux support
- Fix port binding of Swarm master
- Add ability to create a manager instance which does not get scheduled on
- Introduce `--swarm-join-opt` to pass options to agent nodes
- Various SSH-related fixes
- Fix state for upgrade path
# 0.7.0 (2016-4-13)
General
- `DRIVER` environment variable now supported to supply value for `create --driver` flag
- Update to Go 1.6.1
- SSH client has been refactored
- RC versions of Machine will now create and upgrade to boot2docker RCs instead
of stable versions if available
Drivers
- `azure`
- Driver has been completely re-written to use resource templates and a significantly easier-to-use authentication model
- `digitalocean`
- New `--digitalocean-ssh-key-fingerprint` for using existing SSH keys instead of creating new ones
- `virtualbox`
- Fix issue with `bootlocal.sh`
- New `--virtualbox-nictype` flag to set driver for NAT network
- More robust host-only interface collision detection
- Add support for running VirtualBox on a Windows 32 bit host
- Change default DNS passthrough handling
- `amazonec2`
- Specifying multiple security groups to use is now supported
- `exoscale`
- Add support for user-data
- `hyperv`
- Machines can now be created by a non-administrator
- `rackspace`
- New `--rackspace-active-timeout` parameter
- `vmwarefusion`
- Bind mount shared folder directory by default
- `google`
- New `--google-use-internal-ip-only` parameter
Provisioners
- General
- Support for specifying Docker engine port in some cases
- CentOS
- Now defaults to using upstream `get.docker.com` script instead of custom RPMs.
- boot2docker
- More robust eth* interface detection
- Swarm
- Add `--swarm-experimental` parameter to enable experimental Swarm features
# 0.6.0 (2016-02-04)
+ Fix SSH wait before provisioning issue
# 0.6.0-rc4 (2016-02-03)
General
+ `env`
+ Fix shell auto detection
Drivers
+ `exoscale`
+ Fix configuration of exoscale endpoint
# 0.6.0-rc3 (2016-02-01)
- Exit with code 3 if error is during pre-create check
# 0.6.0-rc2 (2016-01-28)
- Fix issue creating Swarms
- Fix `ls` header issue
- Add code to wait for Docker daemon before returning from `start` / `restart`
- Start porting integration tests to Go from BATS
- Add Appveyor for Windows tests
- Update CoreOS provisioner to use `docker daemon`
- Various documentation and error message fixes
- Add ability to create GCE machine using existing VM
# 0.6.0-rc1 (2016-01-18)
General
- Update to Go 1.5.3
- Short form of command invocations is now supported
- `docker-machine start`, `docker-machine stop` and others will now use
`default` as the machine name argument if one is not specified
- Fix issue with panics in drivers
- Machine now returns exit code 3 if the pre-create check fails.
- This is potentially useful for scripting `docker-machine`.
- `docker-machine provision` command added to allow re-running of provisioning
on instances.
- This allows users to re-run provisioning if it fails during `create`
instead of needing to completely start over.
Provisioning
- Most provisioners now use `docker daemon` instead of `docker -d`
- Swarm masters now run with replication enabled
- If `/var/lib` is a BTRFS partition, `btrfs` will now be used as the storage
driver for the instance
Drivers
- Amazon EC2
- Default VPC will be used automatically if none is specified
- Credentials are now be read from the conventional `~/.aws/credentials`
file automatically
- Fix a few issues such as nil pointer dereferences
- VMware Fusion
- Try to get IP from multiple DHCP lease files
- OpenStack
- Only derive tenant ID if tenant name is supplied
# 0.5.6 (2016-01-11)
General
- `create`
- Set swarm master to advertise on port 3376
- Fix swarm restart policy
- Stop asking for ssh key passwords interactively
- `env`
- Improve documentation
- Fix bash on windows
- Automatic shell detection on Windows
- `help`
- Don't show the full path to `docker-machine.exe` on windows
- `ls`
- Allow custom format
- Improve documentation
- `restart`
- Improve documentation
- `rm`
- Improve documentation
- Better user experience when removing multiple hosts
- `version`
- Don't show the full path to `docker-machine.exe` on windows
- `start`, `stop`, `restart`, `kill`
- Better logs and homogeneous behaviour across all drivers
Build
- Introduce CI tests for external binary compatibility
- Add amazon EC2 integration test
Misc
- Improve BugSnags reports: better shell detection, better windows version detection
- Update DockerClient dependency
- Improve bash-completion script
- Improve documentation for bash-completion
Drivers
- Amazon EC2
- Improve documentation
- Support optional tags
- Option to create EbsOptimized instances
- Google
- Fix remove when instance is stopped
- Openstack
- Flags to import and reuse existing nova keypairs
- VirtualBox
- Fix multiple bugs related to host-only adapters
- Retry commands when `VBoxManage` is not ready
- Reject VirtualBox versions older that 4.3
- Fail with a clear message when Hyper-v installation prevents VirtualBox from working
- Print a warning for Boot2Docker v1.9.1, which is known to have an issue with AUFS
- Vmware Fusion
- Support soft links in VM paths
Libmachine
- Fix code sample that uses libmachine
- libmachine can be used in external applications
# 0.5.5 (2015-12-28)
General
- `env`
- Better error message if swarm is down
- Add quotes to command if there are spaces in the path
- Fix Powershell env hints
- Default to cmd shell on windows
- Detect fish shell
- `scp`
- Ignore empty ssh key
- `stop`, `start`, `kill`
- Add feedback to the user
- `rm`
- Now works when `config.json` is not found
- `ssh`
- Disable ControlPath
- Log which SSH client is used
- `ls`
- Listing is now faster by reducing calls to the driver
- Shows if the active machine is a swarm cluster
Build
- Automate 90% of the release process
- Upgrade to Go 1.5.2
- Don't build 32bits binaries for Linux and OSX
- Prevent makefile from defaulting to using containers
Misc
- Update docker-machine version
- Updated the bash completion with new options added
- Bugsnag: Retrieve windows version on non-english OS
Drivers
- Amazon EC2
- Convert API calls to official SDK
- Make DeviceName configurable
- Digital Ocean
- Custom SSH port support
- Generic
- Don't support `kill` since `stop` is not supported
- Google
- Coreos provisionning
- Hyper-V
- Lot's of code simplifications
- Pre-Check that the user is an Administrator
- Pre-Check that the virtual switch exists
- Add Environment variables for each flag
- Fix how Powershell is detected
- VSwitch name should be saved to config.json
- Add a flag to set the CPU count
- Close handle after copying boot2docker.iso into vm folder - will otherwise keep hyper-v from starting vm
- Update Boot2Docker cache in PreCreateCheck phase
- OpenStack
- Filter floating IPs by tenant ID
- Virtualbox
- Reject duplicate hostonlyifs Name/IP with clear message
- Detect when hostonlyif can't be created. Point to known working version of VirtualBox
- Don't create the VM if no hardware virtualization is available and add a flag to force create
- Add `VBox.log` to bugsnag crashreport
- Update Boot2Docker cache in PreCreateCheck phase
- Detect Incompatibility with Hyper-v
- VSphere
- Rewrite driver to work with govmomi instead of wrapping govc
- All
- Change host restart to use the driver implementation
- Fix truncated logs
- Increase heartbeat interval and timeout
Provisioners
- Download latest Boot2Docker if it is out-of-date
- Add swarm config to coreos
- All provisioners now honor `engine-install-url`
# 0.5.4 (2015-12-28)
This is a patch release to fix a regression with STDOUT/STDERR behavior (#2587).
# 0.5.3 (2015-12-14)
**Please note**: With this release Machine will be reverting back to distribution in a single binary, which is more efficient on bandwidth and hard disk space. All the core driver plugins are now included in the main binary. You will want to delete the old driver binaries that you might have in your path.
e.g.:
```console
$ rm /usr/local/bin/docker-machine-driver-{amazonec2,azure,digitalocean,exoscale,generic,google,hyperv,none,openstack,rackspace,softlayer,virtualbox,vmwarefusion,vmwarevcloudair,vmwarevsphere}
```
Non-core driver plugins should still work as intended (in externally distributed binaries of the form `docker-machine-driver-name`. Please report any issues you encounter them with externally loaded plugins.
General
- Optionally report crashes to Bugsnag to help us improve docker-machine
- Fix multiple nil dereferences in `docker-machine ls` command
- Improve the build and CI
- `docker-machine env` now supports emacs
- Run Swarm containers in provisioning step using Docker API instead of SSH/shell
- Show docker daemon version in `docker-machine ls`
- `docker-machine ls` can filter by engine label
- `docker-machine ls` filters are case insensitive
- `--timeout` flag for `docker-machine ls`
- Logs use `logrus` library
- Swarm container network is now `host`
- Added advertise flag to Swarm manager template
- Fix `help` flag for `docker-machine ssh`
- Add confirmation `-y` flag to `docker-machine rm`
- Fix `docker-machine config` for fish
- Embed all core drivers in `docker-machine` binary to reduce the bundle from 120M to 15M
Drivers
- Generic
- Support password protected ssh keys though ssh-agent
- Support DNS names
- Virtualbox
- Show a warning if virtualbox is too old
- Recognize yet another Hardware Virtualization issue pattern
- Fix Hardware Virtualization on Linux/AMD
- Add the `--virtualbox-host-dns-resolver` flag
- Allow virtualbox DNSProxy override
- Google
- Open firewall port for Swarm when needed
- VMware Fusion
- Explicitly set umask before invoking vmrun in vmwarefusion
- Activate the plugin only on OSX
- Add id/gid option to mount when using vmhgfs
- Fix for vSphere driver boot2docker ISO issues
- Digital Ocean
- Support for creating Droplets with Cloud-init User Data
- Openstack
- Sanitize keynames by replacing dots with underscores
- All
- Most base images are now set to `Ubuntu 15.10`
- Fix compatibility with drivers developed with docker-machine 0.5.0
- Better error report for broken/incompatible drivers
- Don't break `config.json` configuration when the disk is full
Provisioners
- Increase timeout for installing boot2docker
- Support `Ubuntu 15.10`
Misc
- Improve the documentation
- Update known drivers list
# 0.5.2 (2015-11-30)
General
- Bash autocompletion and helpers fixed
- Remove `RawDriver` from `config.json` - Driver parameters can now be edited
directly again in this file.
- Change fish `env` variable setting to be global
- Add `docker-machine version` command
- Move back to normal `codegangsta/cli` upstream
- `--tls-san` flag for extra SANs
Drivers
- Fix `GetURL` IPv6 compatibility
- Add documentation page for available 3rd party drivers
- VirtualBox
- Support for shared folders and virtualization detection on Linux hosts
- Improved detection of invalid host-only interface settings
- Google
- Update default images
- VMware Fusion
- Add option to disable shared folder
- Generic
- New environment variables for flags
Provisioners
- Support for Ubuntu >=15.04. This means Ubuntu machines can be created which
work with `overlay` driver of lib network.
- Fix issue with current netstat / daemon availability checking
# 0.5.1 (2015-11-16)
- Fixed boot2docker VM import regression
- Fix regression breaking `docker-machine env -u` to unset environment variables
- Enhanced virtualization capability detection and `VBoxManage` path detection
- Properly lock VirtualBox access when running several commands concurrently
- Allow plugins to write to STDOUT without `--debug` enabled
- Fix Rackspace driver regression
- Support colons in `docker-machine scp` filepaths
- Pass environment variables for provisioned Engines to Swarm as well
- Various enhancements around boot2docker ISO upgrade (progress bar, increased timeout)
# 0.5.0 (2015-11-1)
- General
- Add pluggable driver model
- Clean up code to be more modular and reusable in `libmachine`
- Add `--github-api-token` for situations where users are getting rate limited
by GitHub attempting to get the current `boot2docker.iso` version
- Various enhancements around the Makefile and build toolchain (still an active WIP)
- Disable SSH multiplex explicitly in commands run with the "External" client
- Show "-" for "inactive" machines instead of nothing
- Make daemon status detection more robust
- Provisioners
- New CoreOS, SUSE, and Arch Linux provisioners
- Fixes around package installation / upgrade code on Debian and Ubuntu
- CLI
- Support for regular expression pattern matching and matching by names in `ls --filter`
- `--no-proxy` flag for `env` (sets `NO_PROXY` in addition to other environment variables)
- Drivers
- `openstack`
- `--openstack-ip-version` parameter
- `--openstack-active-timeout` parameter
- `google`
- fix destructive behavior of `start` / `stop`
- `hyperv`
- fix issues with PowerShell
- `vmwarefusion`
- some issues with shared folders fixed
- `--vmwarefusion-configdrive-url` option for configuration via `cloud-init`
- `amazonec2`
- `--amazonec2-use-private-address` option to use private networking
- `virtualbox`
- Enhancements around robustness of the created host-only network
- Fix IPv6 network mask prefix parsing
- `--virtualbox-no-share` option to disable the automatic home directory mount
- `--virtualbox-hostonly-nictype` and `--virtualbox-hostonly-nicpromisc` for controlling settings around the created hostonly NIC
# 0.4.1 (2015-08)
- Fixes `upgrade` functionality on Debian based systems
- Fixes `upgrade` functionality on Ubuntu based systems
# 0.4.0 (2015-08-11)
## Updates
- HTTP Proxy support for Docker Engine
- RedHat distros now use Docker Yum repositories
- Ability to set environment variables in the Docker Engine
- Internal libmachine updates for stability
## Drivers
- Google:
- Preemptible instances
- Static IP support
## Fixes
- Swarm Discovery Flag is verified
- Timeout added to `ls` command to prevent hangups
- SSH command failure now reports information about error
- Configuration migration updates
# 0.3.0 (2015-06-18)
## Features
- Engine option configuration (ability to configure all engine options)
- Swarm option configuration (ability to configure all swarm options)
- New Provisioning system to allow for greater flexibility and stability for installing and configuring Docker
- New Provisioners
- Rancher OS
- RedHat Enterprise Linux 7.0+ (experimental)
- Fedora 21+ (experimental)
- Debian 8+ (experimental)
- PowerShell support (configure Windows Docker CLI)
- Command Prompt (cmd.exe) support (configure Windows Docker CLI)
- Filter command help by driver
- Ability to import Boot2Docker instances
- Boot2Docker CLI migration guide (experimental)
- Format option for `inspect` command
- New logging output format to improve readability and display across platforms
- Updated "active" machine concept - now is implicit according to `DOCKER_HOST` environment variable. Note: this removes the implicit "active" machine and can no longer be specified with the `active` command. You change the "active" host by using the `env` command instead.
- Specify Swarm version (`--swarm-image` flag)
## Drivers
- New: Exoscale Driver
- New: Generic Driver (provision any host with supported base OS and SSH)
- Amazon EC2
- SSH user is configurable
- Support for Spot instances
- Add option to use private address only
- Base AMI updated to 20150417
- Google
- Support custom disk types
- Updated base image to v20150316
- Openstack
- Support for Keystone v3 domains
- Rackspace
- Misc fixes including environment variable for Flavor Id and stability
- Softlayer
- Enable local disk as provisioning option
- Fixes for SSH access errors
- Fixed bug where public IP would always be returned when requesting private
- Add support for specifying public and private VLAN IDs
- VirtualBox
- Use Intel network interface driver (adds great stability)
- Stability fixes for NAT access
- Use DNS pass through
- Default CPU to single core for improved performance
- Enable shared folder support for Windows hosts
- VMware Fusion
- Boot2Docker ISO updated
- Shared folder support
## Fixes
- Provisioning improvements to ensure Docker is available
- SSH improvements for provisioning stability
- Fixed SSH key generation bug on Windows
- Help formatting for improved readability
## Breaking Changes
- "Short-Form" name reference no longer supported Instead of "docker-machine " implying the active host you must now use docker-machine
- VMware shared folders require Boot2Docker 1.7
## Special Thanks
We would like to thank all contributors. Machine would not be where it is
without you. We would also like to give special thanks to the following
contributors for outstanding contributions to the project:
- @frapposelli for VMware updates and fixes
- @hairyhenderson for several improvements to Softlayer driver, inspect formatting and lots of fixes
- @ibuildthecloud for rancher os provisioning
- @sthulb for portable SSH library
- @vincentbernat for exoscale
- @zchee for Amazon updates and great doc updates
# 0.2.0 (2015-04-16)
Core Stability and Driver Updates
## Core
- Support for system proxy environment
- New command to regenerate TLS certificates
- Note: this will restart the Docker engine to apply
- Updates to driver operations (create, start, stop, etc) for better reliability
- New internal `libmachine` package for internal api (not ready for public usage)
- Updated Driver Interface
- [Driver Spec](https://github.com/docker/machine/blob/master/docs/DRIVER_SPEC.md)
- Removed host provisioning from Drivers to enable a more consistent install
- Removed SSH commands from each Driver for more consistent operations
- Swarm: machine now uses Swarm default binpacking strategy
## Driver Updates
- All drivers updated to new Driver interface
- Amazon EC2
- Better checking for subnets on creation
- Support for using Private IPs in VPC
- Fixed bug with duplicate security group authorization with Swarm
- Support for IAM instance profile
- Fixed bug where IP was not properly detected upon stop
- DigitalOcean
- IPv6 support
- Backup option
- Private Networking
- Openstack / Rackspace
- Gophercloud updated to latest version
- New insecure flag to disable TLS (use with caution)
- Google
- Google source image updated
- Ability to specify auth token via file
- VMware Fusion
- Paravirtualized driver for disk (pvscsi)
- Enhanced paravirtualized NIC (vmxnet3)
- Power option updates
- SSH keys persistent across reboots
- Stop now gracefully stops VM
- vCPUs now match host CPUs
- SoftLayer
- Fixed provision bug where `curl` was not present
- VirtualBox
- Correct power operations with Saved VM state
- Fixed bug where image option was ignored
## CLI
- Auto-regeneration of TLS certificates when TLS error is detected
- Note: this will restart the Docker engine to apply
- Minor UI updates including improved sorting and updated command docs
- Bug with `config` and `env` with spaces fixed
- Note: you now must use `eval $(docker-machine env machine)` to load environment settings
- Updates to better support `fish` shell
- Use `--tlsverify` for both `config` and `env` commands
- Commands now use eval for better interoperability with shell
## Testing
- New integration test framework (bats)
# 0.1.0 (2015-02-26)
Initial beta release.
- Provision Docker Engines using multiple drivers
- Provide light management for the machines
- Create, Start, Stop, Restart, Kill, Remove, SSH
- Configure the Docker Engine for secure communication (TLS)
- Easily switch target machine for fast configuration of Docker Engine client
- Provision Swarm clusters (experimental)
## Included drivers
- Amazon EC2
- Digital Ocean
- Google
- Microsoft Azure
- Microsoft Hyper-V
- Openstack
- Rackspace
- VirtualBox
- VMware Fusion
- VMware vCloud Air
- VMware vSphere

288
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,288 @@
# Contributing to machine
[![GoDoc](https://godoc.org/github.com/docker/machine?status.png)](https://godoc.org/github.com/docker/machine)
[![Build Status](https://travis-ci.org/docker/machine.svg?branch=master)](https://travis-ci.org/docker/machine)
[![Windows Build Status](https://ci.appveyor.com/api/projects/status/github/docker/machine?svg=true)](https://ci.appveyor.com/project/dmp42/machine-fp5u5)
[![Coverage Status](https://coveralls.io/repos/docker/machine/badge.svg?branch=master&service=github)](https://coveralls.io/github/docker/machine?branch=master)
Want to hack on Machine? Awesome! Here are instructions to get you
started.
Machine is a part of the [Docker](https://www.docker.com) project, and follows
the same rules and principles. If you're already familiar with the way
Docker does things, you'll feel right at home.
Otherwise, please read [Docker's contributions
guidelines](https://github.com/docker/docker/blob/master/CONTRIBUTING.md).
# Building
The requirements to build Machine are:
1. A running instance of Docker or a Golang 1.6 development environment
2. The `bash` shell
3. [Make](https://www.gnu.org/software/make/)
## Build using Docker containers
To build the `docker-machine` binary using containers, simply run:
$ export USE_CONTAINER=true
$ make build
## Local Go development environment
Make sure the source code directory is under a correct directory structure;
Example of cloning and preparing the correct environment `GOPATH`:
$ mkdir docker-machine
$ cd docker-machine
$ export GOPATH="$PWD"
$ go get github.com/docker/machine
$ cd src/github.com/docker/machine
If you want to use your existing workspace, make sure your `GOPATH` is set to
the directory that contains your `src` directory, e.g.:
$ export GOPATH=/home/yourname/work
$ mkdir -p $GOPATH/src/github.com/docker
$ cd $GOPATH/src/github.com/docker && git clone git@github.com:docker/machine.git
$ cd machine
At this point, simply run:
$ make build
## Built binary
After the build is complete a `bin/docker-machine` binary will be created.
You may call:
$ make clean
to clean-up build results.
## Tests and validation
We use the usual `go` tools for this, to run those commands you need at least the linter which you can
install with `go get -u github.com/golang/lint/golint`
To run basic validation (dco, fmt), and the project unit tests, call:
$ make test
If you want more indepth validation (vet, lint), and all tests with race detection, call:
$ make validate
If you make a pull request, it is highly encouraged that you submit tests for
the code that you have added or modified in the same pull request.
## Code Coverage
To generate an html code coverage report of the Machine codebase, run:
make coverage-serve
And navigate to <http://localhost:8000> (hit `CTRL+C` to stop the server).
### Native build
Alternatively, if you are building natively, you can simply run:
make coverage-html
This will generate and open the report file:
![](/docs/img/coverage.png)
## List of all targets
### High-level targets
make clean
make build
make test
make validate
### Advanced build targets
Build for all supported OSes and architectures (binaries will be in the `bin` project subfolder):
make build-x
Build for a specific list of OSes and architectures:
TARGET_OS=linux TARGET_ARCH="amd64 arm" make build-x
You can further control build options through the following environment variables:
DEBUG=true # enable debug build
STATIC=true # build static (note: when cross-compiling, the build is always static)
VERBOSE=true # verbose output
PREFIX=folder # put binaries in another folder (not the default `./bin`)
Scrub build results:
make build-clean
### Coverage targets
make coverage-html
make coverage-serve
make coverage-send
make coverage-generate
make coverage-clean
### Tests targets
make test-short
make test-long
make test-integration
### Validation targets
make fmt
make vet
make lint
make dco
### Restore, update and save dependencies
When you make a fresh copy of the repo, all the dependecies are in `vendor/` directory for the builds to work. If you want to update the dependencies
#### Restore the dependencies
make dep-restore
This uses godep to restores all the dependencies to your `$GOPATH`. Note that this changes the packages in your `$GOPATH`
#### Add one ore more dependencies
go get -u <new dependency>
#### Save the dependencies to `vendor/`
make dep-save
4. Verify the changes in your repo, commit and submit a pull request
## Integration Tests
### Setup
We use [BATS](https://github.com/sstephenson/bats) for integration testing, so,
first make sure to [install it](https://github.com/sstephenson/bats#installing-bats-from-source).
### Basic Usage
You first need to build, calling `make build`.
You can then invoke integration tests calling `DRIVER=foo make test-integration TESTSUITE`, where `TESTSUITE` is
one of the `test/integration` subfolder, and `foo` is the specific driver you want to test.
Examples:
```console
$ DRIVER=virtualbox make test-integration test/integration/core/core-commands.bats
✓ virtualbox: machine should not exist
✓ virtualbox: create
✓ virtualbox: ls
✓ virtualbox: run busybox container
✓ virtualbox: url
✓ virtualbox: ip
✓ virtualbox: ssh
✓ virtualbox: docker commands with the socket should work
✓ virtualbox: stop
✓ virtualbox: machine should show stopped after stop
✓ virtualbox: machine should now allow upgrade when stopped
✓ virtualbox: start
✓ virtualbox: machine should show running after start
✓ virtualbox: kill
✓ virtualbox: machine should show stopped after kill
✓ virtualbox: restart
✓ virtualbox: machine should show running after restart
17 tests, 0 failures
Cleaning up machines...
Successfully removed bats-virtualbox-test
```
To invoke a directory of tests recursively:
```console
$ DRIVER=virtualbox make test-integration test/integration/core/
...
```
### Extra Create Arguments
In some cases, for instance to test the creation of a specific base OS (e.g.
RHEL) as opposed to the default with the common tests, you may want to run
common tests with different create arguments than you get out of the box.
Keep in mind that Machine supports environment variables for many of these
flags. So, for instance, you could run the command (substituting, of course,
the proper secrets):
$ DRIVER=amazonec2 \
AWS_VPC_ID=vpc-xxxxxxx \
AWS_SECRET_ACCESS_KEY=yyyyyyyyyyyyy \
AWS_ACCESS_KEY_ID=zzzzzzzzzzzzzzzz \
AWS_AMI=ami-12663b7a \
AWS_SSH_USER=ec2-user \
make test-integration test/integration/core
in order to run the core tests on Red Hat Enterprise Linux on Amazon.
### Layout
The `test/integration` directory is layed out to divide up tests based on the
areas which the test. If you are uncertain where to put yours, we are happy to
guide you.
At the time of writing, there is:
1. A `core` directory which contains tests that are applicable to all drivers.
2. A `drivers` directory which contains tests that are applicable only to
specific drivers with sub-directories for each provider.
3. A `cli` directory which is meant for testing functionality of the command
line interface, without much regard for driver-specific details.
### Guidelines
The best practices for writing integration tests on Docker Machine are still a
work in progress, but here are some general guidelines from the maintainers:
1. Ideally, each test file should have only one concern.
2. Tests generally should not spin up more than one machine unless the test is
deliberately testing something which involves multiple machines, such as an `ls`
test which involves several machines, or a test intended to create and check
some property of a Swarm cluster.
3. BATS will print the output of commands executed during a test if the test
fails. This can be useful, for instance to dump the magic `$output` variable
that BATS provides and/or to get debugging information.
4. It is not strictly needed to clean up the machines as part of the test. The
BATS wrapper script has a hook to take care of cleaning up all created machines
after each test.
# Drivers
Docker Machine has several included drivers that supports provisioning hosts
in various providers. If you wish to contribute a driver, we ask the following
to ensure we keep the driver in a consistent and stable state:
- Address issues filed against this driver in a timely manner
- Review PRs for the driver
- Be responsible for maintaining the infrastructure to run unit tests
and integration tests on the new supported environment
- Participate in a weekly driver maintainer meeting
If you can commit to those, the next step is to make sure the driver adheres
to the [spec](https://github.com/docker/machine/blob/master/docs/DRIVER_SPEC.md).
Once you have created and tested the driver, you can open a PR.
Note: even if those are met does not guarantee a driver will be accepted.
If you have questions, please do not hesitate to contact us on IRC.

11
Dockerfile Normal file
View File

@ -0,0 +1,11 @@
FROM golang:1.7.1
RUN go get github.com/golang/lint/golint \
github.com/mattn/goveralls \
golang.org/x/tools/cover
ENV USER root
WORKDIR /go/src/github.com/docker/machine
COPY . ./
RUN mkdir bin

617
Godeps/Godeps.json generated Normal file
View File

@ -0,0 +1,617 @@
{
"ImportPath": "github.com/docker/machine",
"GoVersion": "go1.6",
"GodepVersion": "v66",
"Packages": [
"github.com/docker/machine",
"github.com/docker/machine/cmd",
"github.com/docker/machine/commands",
"github.com/docker/machine/commands/commandstest",
"github.com/docker/machine/commands/mcndirs",
"github.com/docker/machine/drivers/amazonec2",
"github.com/docker/machine/drivers/azure",
"github.com/docker/machine/drivers/azure/azureutil",
"github.com/docker/machine/drivers/azure/logutil",
"github.com/docker/machine/drivers/digitalocean",
"github.com/docker/machine/drivers/errdriver",
"github.com/docker/machine/drivers/exoscale",
"github.com/docker/machine/drivers/fakedriver",
"github.com/docker/machine/drivers/generic",
"github.com/docker/machine/drivers/google",
"github.com/docker/machine/drivers/hyperv",
"github.com/docker/machine/drivers/none",
"github.com/docker/machine/drivers/openstack",
"github.com/docker/machine/drivers/rackspace",
"github.com/docker/machine/drivers/softlayer",
"github.com/docker/machine/drivers/virtualbox",
"github.com/docker/machine/drivers/vmwarefusion",
"github.com/docker/machine/drivers/vmwarevcloudair",
"github.com/docker/machine/drivers/vmwarevsphere",
"github.com/docker/machine/its",
"github.com/docker/machine/its/cli",
"github.com/docker/machine/its/thirdparty",
"github.com/docker/machine/libmachine",
"github.com/docker/machine/libmachine/auth",
"github.com/docker/machine/libmachine/cert",
"github.com/docker/machine/libmachine/check",
"github.com/docker/machine/libmachine/crashreport",
"github.com/docker/machine/libmachine/drivers",
"github.com/docker/machine/libmachine/drivers/plugin",
"github.com/docker/machine/libmachine/drivers/plugin/localbinary",
"github.com/docker/machine/libmachine/drivers/rpc",
"github.com/docker/machine/libmachine/engine",
"github.com/docker/machine/libmachine/examples",
"github.com/docker/machine/libmachine/host",
"github.com/docker/machine/libmachine/hosttest",
"github.com/docker/machine/libmachine/libmachinetest",
"github.com/docker/machine/libmachine/log",
"github.com/docker/machine/libmachine/mcndockerclient",
"github.com/docker/machine/libmachine/mcnerror",
"github.com/docker/machine/libmachine/mcnflag",
"github.com/docker/machine/libmachine/mcnutils",
"github.com/docker/machine/libmachine/persist",
"github.com/docker/machine/libmachine/persist/persisttest",
"github.com/docker/machine/libmachine/provider",
"github.com/docker/machine/libmachine/provision",
"github.com/docker/machine/libmachine/provision/pkgaction",
"github.com/docker/machine/libmachine/provision/provisiontest",
"github.com/docker/machine/libmachine/provision/serviceaction",
"github.com/docker/machine/libmachine/shell",
"github.com/docker/machine/libmachine/ssh",
"github.com/docker/machine/libmachine/ssh/sshtest",
"github.com/docker/machine/libmachine/state",
"github.com/docker/machine/libmachine/swarm",
"github.com/docker/machine/libmachine/version",
"github.com/docker/machine/version"
],
"Deps": [
{
"ImportPath": "github.com/Azure/azure-sdk-for-go/arm/compute",
"Comment": "v2.1.1-beta",
"Rev": "a1883f7b98346e4908a6c25230c95a8a3026a10c"
},
{
"ImportPath": "github.com/Azure/azure-sdk-for-go/arm/network",
"Comment": "v2.1.1-beta",
"Rev": "a1883f7b98346e4908a6c25230c95a8a3026a10c"
},
{
"ImportPath": "github.com/Azure/azure-sdk-for-go/arm/resources/resources",
"Comment": "v2.1.1-beta",
"Rev": "a1883f7b98346e4908a6c25230c95a8a3026a10c"
},
{
"ImportPath": "github.com/Azure/azure-sdk-for-go/arm/resources/subscriptions",
"Comment": "v2.1.1-beta",
"Rev": "a1883f7b98346e4908a6c25230c95a8a3026a10c"
},
{
"ImportPath": "github.com/Azure/azure-sdk-for-go/arm/storage",
"Comment": "v2.1.1-beta",
"Rev": "a1883f7b98346e4908a6c25230c95a8a3026a10c"
},
{
"ImportPath": "github.com/Azure/azure-sdk-for-go/storage",
"Comment": "v2.1.1-beta",
"Rev": "a1883f7b98346e4908a6c25230c95a8a3026a10c"
},
{
"ImportPath": "github.com/Azure/go-autorest/autorest",
"Comment": "v7.0.4-6-gec603a8",
"Rev": "ec603a8ea5ffc45df35c7d948d9acfd6cbc07b46"
},
{
"ImportPath": "github.com/Azure/go-autorest/autorest/azure",
"Comment": "v7.0.4-6-gec603a8",
"Rev": "ec603a8ea5ffc45df35c7d948d9acfd6cbc07b46"
},
{
"ImportPath": "github.com/Azure/go-autorest/autorest/date",
"Comment": "v7.0.4-6-gec603a8",
"Rev": "ec603a8ea5ffc45df35c7d948d9acfd6cbc07b46"
},
{
"ImportPath": "github.com/Azure/go-autorest/autorest/to",
"Comment": "v7.0.4-6-gec603a8",
"Rev": "ec603a8ea5ffc45df35c7d948d9acfd6cbc07b46"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws",
"Comment": "v1.0.2",
"Rev": "9d7bc2d6ca2ada0468f705f0e9725ca97f8550c8"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/awserr",
"Comment": "v1.0.2",
"Rev": "9d7bc2d6ca2ada0468f705f0e9725ca97f8550c8"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/awsutil",
"Comment": "v1.0.2",
"Rev": "9d7bc2d6ca2ada0468f705f0e9725ca97f8550c8"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/client",
"Comment": "v1.0.2",
"Rev": "9d7bc2d6ca2ada0468f705f0e9725ca97f8550c8"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/client/metadata",
"Comment": "v1.0.2",
"Rev": "9d7bc2d6ca2ada0468f705f0e9725ca97f8550c8"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/corehandlers",
"Comment": "v1.0.2",
"Rev": "9d7bc2d6ca2ada0468f705f0e9725ca97f8550c8"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/credentials",
"Comment": "v1.0.2",
"Rev": "9d7bc2d6ca2ada0468f705f0e9725ca97f8550c8"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds",
"Comment": "v1.0.2",
"Rev": "9d7bc2d6ca2ada0468f705f0e9725ca97f8550c8"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/defaults",
"Comment": "v1.0.2",
"Rev": "9d7bc2d6ca2ada0468f705f0e9725ca97f8550c8"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/ec2metadata",
"Comment": "v1.0.2",
"Rev": "9d7bc2d6ca2ada0468f705f0e9725ca97f8550c8"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/request",
"Comment": "v1.0.2",
"Rev": "9d7bc2d6ca2ada0468f705f0e9725ca97f8550c8"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/aws/session",
"Comment": "v1.0.2",
"Rev": "9d7bc2d6ca2ada0468f705f0e9725ca97f8550c8"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/endpoints",
"Comment": "v1.0.2",
"Rev": "9d7bc2d6ca2ada0468f705f0e9725ca97f8550c8"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/ec2query",
"Comment": "v1.0.2",
"Rev": "9d7bc2d6ca2ada0468f705f0e9725ca97f8550c8"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/query/queryutil",
"Comment": "v1.0.2",
"Rev": "9d7bc2d6ca2ada0468f705f0e9725ca97f8550c8"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/rest",
"Comment": "v1.0.2",
"Rev": "9d7bc2d6ca2ada0468f705f0e9725ca97f8550c8"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/protocol/xml/xmlutil",
"Comment": "v1.0.2",
"Rev": "9d7bc2d6ca2ada0468f705f0e9725ca97f8550c8"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/signer/v4",
"Comment": "v1.0.2",
"Rev": "9d7bc2d6ca2ada0468f705f0e9725ca97f8550c8"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/private/waiter",
"Comment": "v1.0.2",
"Rev": "9d7bc2d6ca2ada0468f705f0e9725ca97f8550c8"
},
{
"ImportPath": "github.com/aws/aws-sdk-go/service/ec2",
"Comment": "v1.0.2",
"Rev": "9d7bc2d6ca2ada0468f705f0e9725ca97f8550c8"
},
{
"ImportPath": "github.com/bugsnag/bugsnag-go",
"Comment": "v1.0.5-19-g02e9528",
"Rev": "02e952891c52fbcb15f113d90633897355783b6e"
},
{
"ImportPath": "github.com/bugsnag/bugsnag-go/errors",
"Comment": "v1.0.5-19-g02e9528",
"Rev": "02e952891c52fbcb15f113d90633897355783b6e"
},
{
"ImportPath": "github.com/bugsnag/osext",
"Rev": "0dd3f918b21bec95ace9dc86c7e70266cfc5c702"
},
{
"ImportPath": "github.com/bugsnag/panicwrap",
"Comment": "1.0.0",
"Rev": "e5f9854865b9778a45169fc249e99e338d4d6f27"
},
{
"ImportPath": "github.com/cenkalti/backoff",
"Rev": "9831e1e25c874e0a0601b6dc43641071414eec7a"
},
{
"ImportPath": "github.com/codegangsta/cli",
"Comment": "v1.11.0-6-g0302d39",
"Rev": "0302d3914d2a6ad61404584cdae6e6dbc9c03599"
},
{
"ImportPath": "github.com/davecgh/go-spew/spew",
"Rev": "5215b55f46b2b919f50a1df0eaa5886afe4e3b3d"
},
{
"ImportPath": "github.com/dgrijalva/jwt-go",
"Comment": "v2.4.0-4-gafef698",
"Rev": "afef698c326bfd906b11659432544e5aae441d44"
},
{
"ImportPath": "github.com/digitalocean/godo",
"Comment": "v0.9.0-8-g2124bf3",
"Rev": "2124bf3eeeb4ac070337bb19ef7b76a745de56f4"
},
{
"ImportPath": "github.com/docker/docker/pkg/term",
"Comment": "v1.5.0",
"Rev": "a8a31eff10544860d2188dddabdee4d727545796"
},
{
"ImportPath": "github.com/docker/go-units",
"Comment": "v0.1.0-21-g0bbddae",
"Rev": "0bbddae09c5a5419a8c6dcdd7ff90da3d450393b"
},
{
"ImportPath": "github.com/go-ini/ini",
"Comment": "v0-56-g03e0e7d",
"Rev": "03e0e7d51a13a91c765d8d0161246bc14a38001a"
},
{
"ImportPath": "github.com/golang/protobuf/proto",
"Rev": "3c84672111d91bb5ac31719e112f9f7126a0e26e"
},
{
"ImportPath": "github.com/google/go-querystring/query",
"Rev": "30f7a39f4a218feb5325f3aebc60c32a572a8274"
},
{
"ImportPath": "github.com/jmespath/go-jmespath",
"Comment": "0.2.2",
"Rev": "3433f3ea46d9f8019119e7dd41274e112a2359a9"
},
{
"ImportPath": "github.com/mitchellh/mapstructure",
"Rev": "740c764bc6149d3f1806231418adb9f52c11bcbf"
},
{
"ImportPath": "github.com/pmezard/go-difflib/difflib",
"Rev": "792786c7400a136282c1664665ae0a8db921c6c2"
},
{
"ImportPath": "github.com/pyr/egoscale/src/egoscale",
"Rev": "347f81398d2ea1f3eebf1cd27ee3183669e34819"
},
{
"ImportPath": "github.com/rackspace/gophercloud",
"Comment": "v1.0.0-558-gce0f487",
"Rev": "ce0f487f6747ab43c4e4404722df25349385bebd"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack",
"Comment": "v1.0.0-558-gce0f487",
"Rev": "ce0f487f6747ab43c4e4404722df25349385bebd"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/floatingip",
"Comment": "v1.0.0-558-gce0f487",
"Rev": "ce0f487f6747ab43c4e4404722df25349385bebd"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/keypairs",
"Comment": "v1.0.0-558-gce0f487",
"Rev": "ce0f487f6747ab43c4e4404722df25349385bebd"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/extensions/startstop",
"Comment": "v1.0.0-558-gce0f487",
"Rev": "ce0f487f6747ab43c4e4404722df25349385bebd"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/flavors",
"Comment": "v1.0.0-558-gce0f487",
"Rev": "ce0f487f6747ab43c4e4404722df25349385bebd"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/images",
"Comment": "v1.0.0-558-gce0f487",
"Rev": "ce0f487f6747ab43c4e4404722df25349385bebd"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/compute/v2/servers",
"Comment": "v1.0.0-558-gce0f487",
"Rev": "ce0f487f6747ab43c4e4404722df25349385bebd"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/identity/v2/tenants",
"Comment": "v1.0.0-558-gce0f487",
"Rev": "ce0f487f6747ab43c4e4404722df25349385bebd"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/identity/v2/tokens",
"Comment": "v1.0.0-558-gce0f487",
"Rev": "ce0f487f6747ab43c4e4404722df25349385bebd"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/identity/v3/tokens",
"Comment": "v1.0.0-558-gce0f487",
"Rev": "ce0f487f6747ab43c4e4404722df25349385bebd"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/networking/v2/extensions/layer3/floatingips",
"Comment": "v1.0.0-558-gce0f487",
"Rev": "ce0f487f6747ab43c4e4404722df25349385bebd"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/networking/v2/networks",
"Comment": "v1.0.0-558-gce0f487",
"Rev": "ce0f487f6747ab43c4e4404722df25349385bebd"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/networking/v2/ports",
"Comment": "v1.0.0-558-gce0f487",
"Rev": "ce0f487f6747ab43c4e4404722df25349385bebd"
},
{
"ImportPath": "github.com/rackspace/gophercloud/openstack/utils",
"Comment": "v1.0.0-558-gce0f487",
"Rev": "ce0f487f6747ab43c4e4404722df25349385bebd"
},
{
"ImportPath": "github.com/rackspace/gophercloud/pagination",
"Comment": "v1.0.0-558-gce0f487",
"Rev": "ce0f487f6747ab43c4e4404722df25349385bebd"
},
{
"ImportPath": "github.com/rackspace/gophercloud/rackspace",
"Comment": "v1.0.0-558-gce0f487",
"Rev": "ce0f487f6747ab43c4e4404722df25349385bebd"
},
{
"ImportPath": "github.com/rackspace/gophercloud/rackspace/identity/v2/tokens",
"Comment": "v1.0.0-558-gce0f487",
"Rev": "ce0f487f6747ab43c4e4404722df25349385bebd"
},
{
"ImportPath": "github.com/rackspace/gophercloud/testhelper",
"Comment": "v1.0.0-558-gce0f487",
"Rev": "ce0f487f6747ab43c4e4404722df25349385bebd"
},
{
"ImportPath": "github.com/rackspace/gophercloud/testhelper/client",
"Comment": "v1.0.0-558-gce0f487",
"Rev": "ce0f487f6747ab43c4e4404722df25349385bebd"
},
{
"ImportPath": "github.com/samalba/dockerclient",
"Rev": "f661dd4754aa5c52da85d04b5871ee0e11f4b59c"
},
{
"ImportPath": "github.com/skarademir/naturalsort",
"Rev": "69a5d87bef620f77ee8508db30c846b3b84b111e"
},
{
"ImportPath": "github.com/stretchr/objx",
"Rev": "1a9d0bb9f541897e62256577b352fdbc1fb4fd94"
},
{
"ImportPath": "github.com/stretchr/testify/assert",
"Comment": "v1.1.3-4-g1f4a164",
"Rev": "1f4a1643a57e798696635ea4c126e9127adb7d3c"
},
{
"ImportPath": "github.com/stretchr/testify/mock",
"Comment": "v1.1.3-4-g1f4a164",
"Rev": "1f4a1643a57e798696635ea4c126e9127adb7d3c"
},
{
"ImportPath": "github.com/tent/http-link-go",
"Rev": "ac974c61c2f990f4115b119354b5e0b47550e888"
},
{
"ImportPath": "github.com/vmware/govcloudair",
"Comment": "v0.0.2",
"Rev": "66a23eaabc61518f91769939ff541886fe1dceef"
},
{
"ImportPath": "github.com/vmware/govcloudair/types/v56",
"Comment": "v0.0.2",
"Rev": "66a23eaabc61518f91769939ff541886fe1dceef"
},
{
"ImportPath": "github.com/vmware/govmomi",
"Comment": "v0.1.0-454-g9051bd6",
"Rev": "9051bd6b44125d9472e0c148b5965692ab283d4a"
},
{
"ImportPath": "github.com/vmware/govmomi/find",
"Comment": "v0.1.0-454-g9051bd6",
"Rev": "9051bd6b44125d9472e0c148b5965692ab283d4a"
},
{
"ImportPath": "github.com/vmware/govmomi/guest",
"Comment": "v0.1.0-454-g9051bd6",
"Rev": "9051bd6b44125d9472e0c148b5965692ab283d4a"
},
{
"ImportPath": "github.com/vmware/govmomi/list",
"Comment": "v0.1.0-454-g9051bd6",
"Rev": "9051bd6b44125d9472e0c148b5965692ab283d4a"
},
{
"ImportPath": "github.com/vmware/govmomi/object",
"Comment": "v0.1.0-454-g9051bd6",
"Rev": "9051bd6b44125d9472e0c148b5965692ab283d4a"
},
{
"ImportPath": "github.com/vmware/govmomi/property",
"Comment": "v0.1.0-454-g9051bd6",
"Rev": "9051bd6b44125d9472e0c148b5965692ab283d4a"
},
{
"ImportPath": "github.com/vmware/govmomi/session",
"Comment": "v0.1.0-454-g9051bd6",
"Rev": "9051bd6b44125d9472e0c148b5965692ab283d4a"
},
{
"ImportPath": "github.com/vmware/govmomi/task",
"Comment": "v0.1.0-454-g9051bd6",
"Rev": "9051bd6b44125d9472e0c148b5965692ab283d4a"
},
{
"ImportPath": "github.com/vmware/govmomi/vim25",
"Comment": "v0.1.0-454-g9051bd6",
"Rev": "9051bd6b44125d9472e0c148b5965692ab283d4a"
},
{
"ImportPath": "github.com/vmware/govmomi/vim25/debug",
"Comment": "v0.1.0-454-g9051bd6",
"Rev": "9051bd6b44125d9472e0c148b5965692ab283d4a"
},
{
"ImportPath": "github.com/vmware/govmomi/vim25/methods",
"Comment": "v0.1.0-454-g9051bd6",
"Rev": "9051bd6b44125d9472e0c148b5965692ab283d4a"
},
{
"ImportPath": "github.com/vmware/govmomi/vim25/mo",
"Comment": "v0.1.0-454-g9051bd6",
"Rev": "9051bd6b44125d9472e0c148b5965692ab283d4a"
},
{
"ImportPath": "github.com/vmware/govmomi/vim25/progress",
"Comment": "v0.1.0-454-g9051bd6",
"Rev": "9051bd6b44125d9472e0c148b5965692ab283d4a"
},
{
"ImportPath": "github.com/vmware/govmomi/vim25/soap",
"Comment": "v0.1.0-454-g9051bd6",
"Rev": "9051bd6b44125d9472e0c148b5965692ab283d4a"
},
{
"ImportPath": "github.com/vmware/govmomi/vim25/types",
"Comment": "v0.1.0-454-g9051bd6",
"Rev": "9051bd6b44125d9472e0c148b5965692ab283d4a"
},
{
"ImportPath": "github.com/vmware/govmomi/vim25/xml",
"Comment": "v0.1.0-454-g9051bd6",
"Rev": "9051bd6b44125d9472e0c148b5965692ab283d4a"
},
{
"ImportPath": "golang.org/x/crypto/curve25519",
"Rev": "beef0f4390813b96e8e68fd78570396d0f4751fc"
},
{
"ImportPath": "golang.org/x/crypto/ssh",
"Rev": "beef0f4390813b96e8e68fd78570396d0f4751fc"
},
{
"ImportPath": "golang.org/x/crypto/ssh/terminal",
"Rev": "beef0f4390813b96e8e68fd78570396d0f4751fc"
},
{
"ImportPath": "golang.org/x/net/context",
"Rev": "4f2fc6c1e69d41baf187332ee08fbd2b296f21ed"
},
{
"ImportPath": "golang.org/x/net/context/ctxhttp",
"Rev": "4f2fc6c1e69d41baf187332ee08fbd2b296f21ed"
},
{
"ImportPath": "golang.org/x/oauth2",
"Rev": "442624c9ec9243441e83b374a9e22ac549b5c51d"
},
{
"ImportPath": "golang.org/x/oauth2/google",
"Rev": "442624c9ec9243441e83b374a9e22ac549b5c51d"
},
{
"ImportPath": "golang.org/x/oauth2/internal",
"Rev": "442624c9ec9243441e83b374a9e22ac549b5c51d"
},
{
"ImportPath": "golang.org/x/oauth2/jws",
"Rev": "442624c9ec9243441e83b374a9e22ac549b5c51d"
},
{
"ImportPath": "golang.org/x/oauth2/jwt",
"Rev": "442624c9ec9243441e83b374a9e22ac549b5c51d"
},
{
"ImportPath": "golang.org/x/sys/windows/registry",
"Rev": "d9157a9621b69ad1d8d77a1933590c416593f24f"
},
{
"ImportPath": "google.golang.org/api/compute/v1",
"Rev": "030d584ade5f79aa2ed0ce067e8f7da50c9a10d5"
},
{
"ImportPath": "google.golang.org/api/gensupport",
"Rev": "030d584ade5f79aa2ed0ce067e8f7da50c9a10d5"
},
{
"ImportPath": "google.golang.org/api/googleapi",
"Rev": "030d584ade5f79aa2ed0ce067e8f7da50c9a10d5"
},
{
"ImportPath": "google.golang.org/api/googleapi/internal/uritemplates",
"Rev": "030d584ade5f79aa2ed0ce067e8f7da50c9a10d5"
},
{
"ImportPath": "google.golang.org/appengine",
"Rev": "6a436539be38c296a8075a871cc536686b458371"
},
{
"ImportPath": "google.golang.org/appengine/internal",
"Rev": "6a436539be38c296a8075a871cc536686b458371"
},
{
"ImportPath": "google.golang.org/appengine/internal/app_identity",
"Rev": "6a436539be38c296a8075a871cc536686b458371"
},
{
"ImportPath": "google.golang.org/appengine/internal/base",
"Rev": "6a436539be38c296a8075a871cc536686b458371"
},
{
"ImportPath": "google.golang.org/appengine/internal/datastore",
"Rev": "6a436539be38c296a8075a871cc536686b458371"
},
{
"ImportPath": "google.golang.org/appengine/internal/log",
"Rev": "6a436539be38c296a8075a871cc536686b458371"
},
{
"ImportPath": "google.golang.org/appengine/internal/modules",
"Rev": "6a436539be38c296a8075a871cc536686b458371"
},
{
"ImportPath": "google.golang.org/appengine/internal/remote_api",
"Rev": "6a436539be38c296a8075a871cc536686b458371"
},
{
"ImportPath": "google.golang.org/cloud/compute/metadata",
"Rev": "975617b05ea8a58727e6c1a06b6161ff4185a9f2"
},
{
"ImportPath": "google.golang.org/cloud/internal",
"Rev": "975617b05ea8a58727e6c1a06b6161ff4185a9f2"
}
]
}

5
Godeps/Readme generated Normal file
View File

@ -0,0 +1,5 @@
This directory tree is generated automatically by godep.
Please do not edit.
See https://github.com/tools/godep for more information.

191
LICENSE Normal file
View File

@ -0,0 +1,191 @@
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
Copyright 2014 Docker, Inc.
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.

46
MAINTAINERS Normal file
View File

@ -0,0 +1,46 @@
# Machine maintainers file
#
# This file describes who runs the docker/machine project and how.
# This is a living document - if you see something out of date or missing, speak up!
#
# It is structured to be consumable by both humans and programs.
# To extract its contents programmatically, use any TOML-compliant parser.
#
# This file is compiled into the MAINTAINERS file in docker/opensource.
#
[Org]
[Org."Core maintainers"]
people = [
"dgageot",
"ehazlett",
"jeanlaurent",
"nathanleclaire",
]
[people]
# A reference list of all people associated with the project.
# All other sections should refer to people by their canonical key
# in the people section.
# ADD YOURSELF HERE IN ALPHABETICAL ORDER
[people.dgageot]
Name = "David Gageot"
Email = "david.gageot@docker.com"
GitHub = "dgageot"
[people.ehazlett]
Name = "Evan Hazlett"
Email = "ejhazlett@gmail.com"
GitHub = "ehazlett"
[people.jeanlaurent]
Name = "Jean-Laurent de Morlhon"
Email = "jeanlaurent@docker.com>"
GitHub = "jeanlaurent"
[people.nathanleclaire]
Name = "Nathan LeClaire"
Email = "nathan.leclaire@docker.com"
GitHub = "nathanleclaire"

18
Makefile Normal file
View File

@ -0,0 +1,18 @@
# Plain make targets if not requested inside a container
ifneq (,$(findstring test-integration,$(MAKECMDGOALS)))
include Makefile.inc
include mk/main.mk
else ifneq ($(USE_CONTAINER), true)
include Makefile.inc
include mk/main.mk
else
# Otherwise, with docker, swallow all targets and forward into a container
DOCKER_BUILD_DONE := ""
test: .DEFAULT
.DEFAULT:
@test ! -z "$(DOCKER_BUILD_DONE)" || ./script/build_in_container.sh $(MAKECMDGOALS)
$(eval DOCKER_BUILD_DONE := "done")
endif

31
Makefile.inc Normal file
View File

@ -0,0 +1,31 @@
# Project name, used to name the binaries
PKG_NAME := docker-machine
# If true, disable optimizations and does NOT strip the binary
DEBUG ?=
# If true, "build" will produce a static binary (cross compile always produce static build regardless)
STATIC ?=
# If true, turn on verbose output for build
VERBOSE ?=
# Build tags
BUILDTAGS ?=
# Adjust number of parallel builds (XXX not used)
PARALLEL ?= -1
# Coverage default directory
COVERAGE_DIR ?= cover
# Whether to perform targets inside a docker container, or natively on the host
USE_CONTAINER ?=
# List of cross compilation targets
ifeq ($(TARGET_OS),)
TARGET_OS := darwin linux windows
endif
ifeq ($(TARGET_ARCH),)
TARGET_ARCH := amd64 386
endif
# Output prefix, defaults to local directory if not specified
ifeq ($(PREFIX),)
PREFIX := $(shell pwd)
endif

143
README.md Normal file
View File

@ -0,0 +1,143 @@
# Docker Machine
![](/docs/img/logo.png)
Machine lets you create Docker hosts on your computer, on cloud providers, and
inside your own data center. It creates servers, installs Docker on them, then
configures the Docker client to talk to them.
It works a bit like this:
```console
$ docker-machine create -d virtualbox default
Running pre-create checks...
Creating machine...
(default) Creating VirtualBox VM...
(default) Creating SSH key...
(default) Starting VM...
Waiting for machine to be running, this may take a few minutes...
Machine is running, waiting for SSH to be available...
Detecting operating system of created instance...
Detecting the provisioner...
Provisioning with boot2docker...
Copying certs to the local machine directory...
Copying certs to the remote machine...
Setting Docker configuration on the remote daemon...
Checking connection to Docker...
Docker is up and running!
To see how to connect Docker to this machine, run: docker-machine env default
$ docker-machine ls
NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS
default - virtualbox Running tcp://192.168.99.188:2376 v1.9.1
$ eval "$(docker-machine env default)"
$ docker run busybox echo hello world
Unable to find image 'busybox:latest' locally
511136ea3c5a: Pull complete
df7546f9f060: Pull complete
ea13149945cb: Pull complete
4986bf8c1536: Pull complete
hello world
```
In addition to local VMs, you can create and manage cloud servers:
```console
$ docker-machine create -d digitalocean --digitalocean-access-token=secret staging
Creating SSH key...
Creating Digital Ocean droplet...
To see how to connect Docker to this machine, run: docker-machine env staging
$ docker-machine ls
NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS
default - virtualbox Running tcp://192.168.99.188:2376 v1.9.1
staging - digitalocean Running tcp://203.0.113.81:2376 v1.9.1
```
## Installation and documentation
Full documentation [is available here](https://docs.docker.com/machine/).
## Contributing
Want to hack on Machine? Please start with the [Contributing Guide](https://github.com/docker/machine/blob/master/CONTRIBUTING.md).
## Driver Plugins
In addition to the core driver plugins bundled alongside Docker Machine, users
can make and distribute their own plugin for any virtualization technology or
cloud provider. To browse the list of known Docker Machine plugins, please [see
this document in our
repo](https://github.com/docker/machine/blob/master/docs/AVAILABLE_DRIVER_PLUGINS.md).
## Troubleshooting
Docker Machine tries to do the right thing in a variety of scenarios but
sometimes things do not go according to plan. Here is a quick troubleshooting
guide which may help you to resolve of the issues you may be seeing.
Note that some of the suggested solutions are only available on the Docker
Machine master branch. If you need them, consider compiling Docker Machine from
source.
#### `docker-machine` hangs
A common issue with Docker Machine is that it will hang when attempting to start
up the virtual machine. Since starting the machine is part of the `create`
process, `create` is often where these types of errors show up.
A hang could be due to a variety of factors, but the most common suspect is
networking. Consider the following:
- Are you using a VPN? If so, try disconnecting and see if creation will
succeed without the VPN. Some VPN software aggressively controls routes and
you may need to [manually add the route](https://github.com/docker/machine/issues/1500#issuecomment-121134958).
- Are you connected to a proxy server, corporate or otherwise? If so, take a
look at the `--no-proxy` flag for `env` and at [setting environment variables
for the created Docker Engine](https://docs.docker.com/machine/reference/create/#specifying-configuration-options-for-the-created-docker-engine).
- Are there a lot of host-only interfaces listed by the command `VBoxManage list
hostonlyifs`? If so, this has sometimes been known to cause bugs. Consider
removing the ones you are not using (`VBoxManage hostonlyif remove name`) and
trying machine creation again.
We are keenly aware of this as an issue and working towards a set of solutions
which is robust for all users, so please give us feedback and/or report issues,
workarounds, and desired workflows as you discover them.
#### Machine creation errors out before finishing
If you see messages such as "exit status 1" creating machines with VirtualBox,
this frequently indicates that there is an issue with VirtualBox itself. Please
[file an issue](https://github.com/docker/machine/issues/new) and include a link
to a [Github Gist](https://gist.github.com/) with the output of the VirtualBox
log (usually located at
`$HOME/.docker/machine/machines/machinename/machinename/Logs/VBox.log`), as well
as the output of running the Docker Machine command which is failing with the
global `--debug` flag enabled. This will help us to track down which versions
of VirtualBox are failing where, and under which conditions.
If you see messages such as "exit status 255", this frequently indicates there
has been an issue with SSH. Please investigate your SSH configuration if you
have one, and/or [file an issue](https://github.com/docker/machine/issues).
#### "You may be getting rate limited by Github" error message
In order to `create` or `upgrade` virtual machines running Docker, Docker
Machine will check the Github API for the latest release of the [boot2docker
operating system](https://github.com/boot2docker/boot2docker). The Github API
allows for a small number of unauthenticated requests from a given client, but
if you share an IP address with many other users (e.g. in an office), you may
get rate limited by their API, and Docker Machine will error out with messages
indicating this.
In order to work around this issue, you can [generate a
token](https://help.github.com/articles/creating-an-access-token-for-command-line-use/)
and pass it to Docker Machine using the global `--github-api-token` flag like
so:
```console
$ docker-machine --github-api-token=token create -d virtualbox newbox
```
This should eliminate any issues you've been experiencing with rate limiting.

34
ROADMAP.md Normal file
View File

@ -0,0 +1,34 @@
# Machine Roadmap
Machine currently works really well for development and test environments. The
goal is to make it work better for provisioning and managing production
environments.
This is not a simple task -- production is inherently far more complex than
development -- but there are three things which are big steps towards that goal:
**client/server architecture**, **swarm integration** and **flexible
provisioning**.
(Note: this document is a high-level overview of where we are taking Machine.
For what is coming in specific releases, see our [upcoming
milestones](https://github.com/docker/machine/milestones).)
### Docker Engine / Swarm Configuration
Currently there are only a few things that can be configured in the Docker Engine and Swarm. This will enable more operations such as Engine labels and Swarm strategies.
### Boot2Docker Migration Support
Currently both Machine and Boot2Docker provider similar functionality. This will enable users to migrate from boot2docker to machine.
### Expand Provisioner
Machine currently supports running Boot2Docker for "local" providers and Ubuntu for "remote" providers. This will expand the provisioning capabilities to include other base operating systems such as Red Hat-like distributions and possibly other "just enough" operating systems.
### Windows Experience
Currently, the Machine on Windows experience is not as good as the Mac / Linux. There is no "recommended" path to use Machine and there are several inconsistencies on Windows such as logging and output formatting.
# Project Planning
An [Open-Source Planning Process](https://github.com/docker/machine/wiki/Open-Source-Planning-Process) is used to define the Roadmap. [Project Pages](https://github.com/docker/machine/wiki) define the goals for each Milestone and identify current progress.

18
appveyor.yml Normal file
View File

@ -0,0 +1,18 @@
version: "{build}"
skip_tags: true
os: Windows Server 2012 R2
environment:
GOPATH: c:\gopath
clone_folder: c:\gopath\src\github.com\docker\machine
build_script:
- go build -i -o ./bin/docker-machine.exe ./cmd/machine.go
test_script:
- powershell -Command go test -v ./libmachine/shell
deploy: off

30
circle.yml Normal file
View File

@ -0,0 +1,30 @@
machine:
pre:
- bash < <(curl -s -S -L https://raw.githubusercontent.com/moovweb/gvm/1.0.22/binscripts/gvm-installer)
post:
- gvm install go1.6 -B --name=stable
environment:
CHECKOUT: /home/ubuntu/$CIRCLE_PROJECT_REPONAME
BASE_DIR: src/github.com/$CIRCLE_PROJECT_USERNAME/$CIRCLE_PROJECT_REPONAME
BASE_STABLE: ../../../$HOME/.gvm/pkgsets/stable/global/$BASE_DIR
GO15VENDOREXPERIMENT: 1
dependencies:
override:
- >
gvm use stable &&
mkdir -p "$(dirname $BASE_STABLE)" &&
cp -R "$CHECKOUT" "$BASE_STABLE"
test:
pre:
- gvm use stable && make build:
pwd: $BASE_STABLE
- gvm use stable && go get github.com/docker/docker-machine-driver-ci-test
override:
- gvm use stable && PATH=../../../../bin:$PATH DRIVER=ci-test go test -v github.com/docker/machine/its/...:
pwd: $BASE_STABLE
timeout: 600

215
cmd/machine.go Normal file
View File

@ -0,0 +1,215 @@
package main
import (
"fmt"
"os"
"strconv"
"path/filepath"
"github.com/codegangsta/cli"
"github.com/docker/machine/commands"
"github.com/docker/machine/commands/mcndirs"
"github.com/docker/machine/drivers/amazonec2"
"github.com/docker/machine/drivers/azure"
"github.com/docker/machine/drivers/digitalocean"
"github.com/docker/machine/drivers/exoscale"
"github.com/docker/machine/drivers/generic"
"github.com/docker/machine/drivers/google"
"github.com/docker/machine/drivers/hyperv"
"github.com/docker/machine/drivers/none"
"github.com/docker/machine/drivers/openstack"
"github.com/docker/machine/drivers/rackspace"
"github.com/docker/machine/drivers/softlayer"
"github.com/docker/machine/drivers/virtualbox"
"github.com/docker/machine/drivers/vmwarefusion"
"github.com/docker/machine/drivers/vmwarevcloudair"
"github.com/docker/machine/drivers/vmwarevsphere"
"github.com/docker/machine/libmachine/drivers/plugin"
"github.com/docker/machine/libmachine/drivers/plugin/localbinary"
"github.com/docker/machine/libmachine/log"
"github.com/docker/machine/version"
)
var AppHelpTemplate = `Usage: {{.Name}} {{if .Flags}}[OPTIONS] {{end}}COMMAND [arg...]
{{.Usage}}
Version: {{.Version}}{{if or .Author .Email}}
Author:{{if .Author}}
{{.Author}}{{if .Email}} - <{{.Email}}>{{end}}{{else}}
{{.Email}}{{end}}{{end}}
{{if .Flags}}
Options:
{{range .Flags}}{{.}}
{{end}}{{end}}
Commands:
{{range .Commands}}{{.Name}}{{with .ShortName}}, {{.}}{{end}}{{ "\t" }}{{.Usage}}
{{end}}
Run '{{.Name}} COMMAND --help' for more information on a command.
`
var CommandHelpTemplate = `Usage: docker-machine {{.Name}}{{if .Flags}} [OPTIONS]{{end}} [arg...]
{{.Usage}}{{if .Description}}
Description:
{{.Description}}{{end}}{{if .Flags}}
Options:
{{range .Flags}}
{{.}}{{end}}{{ end }}
`
func setDebugOutputLevel() {
// TODO: I'm not really a fan of this method and really would rather
// use -v / --verbose TBQH
for _, f := range os.Args {
if f == "-D" || f == "--debug" || f == "-debug" {
log.SetDebug(true)
}
}
debugEnv := os.Getenv("MACHINE_DEBUG")
if debugEnv != "" {
showDebug, err := strconv.ParseBool(debugEnv)
if err != nil {
fmt.Fprintf(os.Stderr, "Error parsing boolean value from MACHINE_DEBUG: %s\n", err)
os.Exit(1)
}
log.SetDebug(showDebug)
}
}
func main() {
if os.Getenv(localbinary.PluginEnvKey) == localbinary.PluginEnvVal {
driverName := os.Getenv(localbinary.PluginEnvDriverName)
runDriver(driverName)
return
}
localbinary.CurrentBinaryIsDockerMachine = true
setDebugOutputLevel()
cli.AppHelpTemplate = AppHelpTemplate
cli.CommandHelpTemplate = CommandHelpTemplate
app := cli.NewApp()
app.Name = filepath.Base(os.Args[0])
app.Author = "Docker Machine Contributors"
app.Email = "https://github.com/docker/machine"
app.Commands = commands.Commands
app.CommandNotFound = cmdNotFound
app.Usage = "Create and manage machines running Docker."
app.Version = version.FullVersion()
log.Debug("Docker Machine Version: ", app.Version)
app.Flags = []cli.Flag{
cli.BoolFlag{
Name: "debug, D",
Usage: "Enable debug mode",
},
cli.StringFlag{
EnvVar: "MACHINE_STORAGE_PATH",
Name: "storage-path, s",
Value: mcndirs.GetBaseDir(),
Usage: "Configures storage path",
},
cli.StringFlag{
EnvVar: "MACHINE_TLS_CA_CERT",
Name: "tls-ca-cert",
Usage: "CA to verify remotes against",
Value: "",
},
cli.StringFlag{
EnvVar: "MACHINE_TLS_CA_KEY",
Name: "tls-ca-key",
Usage: "Private key to generate certificates",
Value: "",
},
cli.StringFlag{
EnvVar: "MACHINE_TLS_CLIENT_CERT",
Name: "tls-client-cert",
Usage: "Client cert to use for TLS",
Value: "",
},
cli.StringFlag{
EnvVar: "MACHINE_TLS_CLIENT_KEY",
Name: "tls-client-key",
Usage: "Private key used in client TLS auth",
Value: "",
},
cli.StringFlag{
EnvVar: "MACHINE_GITHUB_API_TOKEN",
Name: "github-api-token",
Usage: "Token to use for requests to the Github API",
Value: "",
},
cli.BoolFlag{
EnvVar: "MACHINE_NATIVE_SSH",
Name: "native-ssh",
Usage: "Use the native (Go-based) SSH implementation.",
},
cli.StringFlag{
EnvVar: "MACHINE_BUGSNAG_API_TOKEN",
Name: "bugsnag-api-token",
Usage: "BugSnag API token for crash reporting",
Value: "",
},
}
if err := app.Run(os.Args); err != nil {
log.Error(err)
}
}
func runDriver(driverName string) {
switch driverName {
case "amazonec2":
plugin.RegisterDriver(amazonec2.NewDriver("", ""))
case "azure":
plugin.RegisterDriver(azure.NewDriver("", ""))
case "digitalocean":
plugin.RegisterDriver(digitalocean.NewDriver("", ""))
case "exoscale":
plugin.RegisterDriver(exoscale.NewDriver("", ""))
case "generic":
plugin.RegisterDriver(generic.NewDriver("", ""))
case "google":
plugin.RegisterDriver(google.NewDriver("", ""))
case "hyperv":
plugin.RegisterDriver(hyperv.NewDriver("", ""))
case "none":
plugin.RegisterDriver(none.NewDriver("", ""))
case "openstack":
plugin.RegisterDriver(openstack.NewDriver("", ""))
case "rackspace":
plugin.RegisterDriver(rackspace.NewDriver("", ""))
case "softlayer":
plugin.RegisterDriver(softlayer.NewDriver("", ""))
case "virtualbox":
plugin.RegisterDriver(virtualbox.NewDriver("", ""))
case "vmwarefusion":
plugin.RegisterDriver(vmwarefusion.NewDriver("", ""))
case "vmwarevcloudair":
plugin.RegisterDriver(vmwarevcloudair.NewDriver("", ""))
case "vmwarevsphere":
plugin.RegisterDriver(vmwarevsphere.NewDriver("", ""))
default:
fmt.Fprintf(os.Stderr, "Unsupported driver: %s\n", driverName)
os.Exit(1)
}
}
func cmdNotFound(c *cli.Context, command string) {
log.Errorf(
"%s: '%s' is not a %s command. See '%s --help'.",
c.App.Name,
command,
c.App.Name,
os.Args[0],
)
os.Exit(1)
}

17
cmd/machine_test.go Normal file
View File

@ -0,0 +1,17 @@
package main
import (
"os"
"testing"
"github.com/docker/machine/commands/mcndirs"
)
func TestStorePathSetCorrectly(t *testing.T) {
mcndirs.BaseDir = ""
os.Args = []string{"docker-machine", "--storage-path", "/tmp/foo"}
main()
if mcndirs.BaseDir != "/tmp/foo" {
t.Fatal("Expected MACHINE_STORAGE_PATH environment variable to be /tmp/foo but was ", os.Getenv("MACHINE_STORAGE_PATH"))
}
}

60
commands/active.go Normal file
View File

@ -0,0 +1,60 @@
package commands
import (
"errors"
"fmt"
"time"
"github.com/docker/machine/libmachine"
"github.com/docker/machine/libmachine/persist"
"github.com/docker/machine/libmachine/state"
)
const (
activeDefaultTimeout = 10
)
var (
errNoActiveHost = errors.New("No active host found")
errActiveTimeout = errors.New("Error getting active host: timeout")
)
func cmdActive(c CommandLine, api libmachine.API) error {
if len(c.Args()) > 0 {
return ErrTooManyArguments
}
hosts, hostsInError, err := persist.LoadAllHosts(api)
if err != nil {
return fmt.Errorf("Error getting active host: %s", err)
}
timeout := time.Duration(c.Int("timeout")) * time.Second
items := getHostListItems(hosts, hostsInError, timeout)
active, err := activeHost(items)
if err != nil {
return err
}
fmt.Println(active.Name)
return nil
}
func activeHost(items []HostListItem) (HostListItem, error) {
timeout := false
for _, item := range items {
if item.ActiveHost || item.ActiveSwarm {
return item, nil
}
if item.State == state.Timeout {
timeout = true
}
}
if timeout {
return HostListItem{}, errActiveTimeout
}
return HostListItem{}, errNoActiveHost
}

110
commands/active_test.go Normal file
View File

@ -0,0 +1,110 @@
package commands
import (
"testing"
"github.com/docker/machine/libmachine/state"
"github.com/stretchr/testify/assert"
)
func TestCmdActiveNone(t *testing.T) {
hostListItems := []HostListItem{
{
Name: "host1",
ActiveHost: false,
ActiveSwarm: false,
State: state.Running,
},
{
Name: "host2",
ActiveHost: false,
ActiveSwarm: false,
State: state.Running,
},
{
Name: "host3",
ActiveHost: false,
ActiveSwarm: false,
State: state.Running,
},
}
_, err := activeHost(hostListItems)
assert.Equal(t, err, errNoActiveHost)
}
func TestCmdActiveHost(t *testing.T) {
hostListItems := []HostListItem{
{
Name: "host1",
ActiveHost: false,
ActiveSwarm: false,
State: state.Timeout,
},
{
Name: "host2",
ActiveHost: true,
ActiveSwarm: false,
State: state.Running,
},
{
Name: "host3",
ActiveHost: false,
ActiveSwarm: false,
State: state.Running,
},
}
active, err := activeHost(hostListItems)
assert.Equal(t, err, nil)
assert.Equal(t, active.Name, "host2")
}
func TestCmdActiveSwarm(t *testing.T) {
hostListItems := []HostListItem{
{
Name: "host1",
ActiveHost: false,
ActiveSwarm: false,
State: state.Running,
},
{
Name: "host2",
ActiveHost: false,
ActiveSwarm: false,
State: state.Running,
},
{
Name: "host3",
ActiveHost: false,
ActiveSwarm: true,
State: state.Running,
},
}
active, err := activeHost(hostListItems)
assert.Equal(t, err, nil)
assert.Equal(t, active.Name, "host3")
}
func TestCmdActiveTimeout(t *testing.T) {
hostListItems := []HostListItem{
{
Name: "host1",
ActiveHost: false,
ActiveSwarm: false,
State: state.Running,
},
{
Name: "host2",
ActiveHost: false,
ActiveSwarm: false,
State: state.Running,
},
{
Name: "host3",
ActiveHost: false,
ActiveSwarm: false,
State: state.Timeout,
},
}
_, err := activeHost(hostListItems)
assert.Equal(t, err, errActiveTimeout)
}

464
commands/commands.go Normal file
View File

@ -0,0 +1,464 @@
package commands
import (
"errors"
"fmt"
"os"
"strings"
"github.com/codegangsta/cli"
"github.com/docker/machine/commands/mcndirs"
"github.com/docker/machine/libmachine"
"github.com/docker/machine/libmachine/crashreport"
"github.com/docker/machine/libmachine/host"
"github.com/docker/machine/libmachine/log"
"github.com/docker/machine/libmachine/mcnerror"
"github.com/docker/machine/libmachine/mcnutils"
"github.com/docker/machine/libmachine/persist"
"github.com/docker/machine/libmachine/ssh"
)
const (
defaultMachineName = "default"
)
var (
ErrHostLoad = errors.New("All specified hosts had errors loading their configuration.")
ErrNoDefault = fmt.Errorf("Error: No machine name(s) specified and no %q machine exists.", defaultMachineName)
ErrNoMachineSpecified = errors.New("Error: Expected to get one or more machine names as arguments")
ErrExpectedOneMachine = errors.New("Error: Expected one machine name as an argument")
ErrTooManyArguments = errors.New("Error: Too many arguments given")
osExit = func(code int) { os.Exit(code) }
)
// CommandLine contains all the information passed to the commands on the command line.
type CommandLine interface {
ShowHelp()
ShowVersion()
Application() *cli.App
Args() cli.Args
IsSet(name string) bool
Bool(name string) bool
Int(name string) int
String(name string) string
StringSlice(name string) []string
GlobalString(name string) string
FlagNames() (names []string)
Generic(name string) interface{}
}
type contextCommandLine struct {
*cli.Context
}
func (c *contextCommandLine) ShowHelp() {
cli.ShowCommandHelp(c.Context, c.Command.Name)
}
func (c *contextCommandLine) ShowVersion() {
cli.ShowVersion(c.Context)
}
func (c *contextCommandLine) Application() *cli.App {
return c.App
}
// targetHost returns a specific host name if one is indicated by the first CLI
// arg, or the default host name if no host is specified.
func targetHost(c CommandLine, api libmachine.API) (string, error) {
if len(c.Args()) == 0 {
defaultExists, err := api.Exists(defaultMachineName)
if err != nil {
return "", fmt.Errorf("Error checking if host %q exists: %s", defaultMachineName, err)
}
if defaultExists {
return defaultMachineName, nil
}
return "", ErrNoDefault
}
return c.Args()[0], nil
}
func runAction(actionName string, c CommandLine, api libmachine.API) error {
var (
hostsToLoad []string
)
// If user did not specify a machine name explicitly, use the 'default'
// machine if it exists. This allows short form commands such as
// 'docker-machine stop' for convenience.
if len(c.Args()) == 0 {
target, err := targetHost(c, api)
if err != nil {
return err
}
hostsToLoad = []string{target}
} else {
hostsToLoad = c.Args()
}
hosts, hostsInError := persist.LoadHosts(api, hostsToLoad)
if len(hostsInError) > 0 {
errs := []error{}
for _, err := range hostsInError {
errs = append(errs, err)
}
return consolidateErrs(errs)
}
if len(hosts) == 0 {
return ErrHostLoad
}
if errs := runActionForeachMachine(actionName, hosts); len(errs) > 0 {
return consolidateErrs(errs)
}
for _, h := range hosts {
if err := api.Save(h); err != nil {
return fmt.Errorf("Error saving host to store: %s", err)
}
}
return nil
}
func runCommand(command func(commandLine CommandLine, api libmachine.API) error) func(context *cli.Context) {
return func(context *cli.Context) {
api := libmachine.NewClient(mcndirs.GetBaseDir(), mcndirs.GetMachineCertDir())
defer api.Close()
if context.GlobalBool("native-ssh") {
api.SSHClientType = ssh.Native
}
api.GithubAPIToken = context.GlobalString("github-api-token")
api.Filestore.Path = context.GlobalString("storage-path")
// TODO (nathanleclaire): These should ultimately be accessed
// through the libmachine client by the rest of the code and
// not through their respective modules. For now, however,
// they are also being set the way that they originally were
// set to preserve backwards compatibility.
mcndirs.BaseDir = api.Filestore.Path
mcnutils.GithubAPIToken = api.GithubAPIToken
ssh.SetDefaultClient(api.SSHClientType)
if err := command(&contextCommandLine{context}, api); err != nil {
log.Error(err)
if crashErr, ok := err.(crashreport.CrashError); ok {
crashReporter := crashreport.NewCrashReporter(mcndirs.GetBaseDir(), context.GlobalString("bugsnag-api-token"))
crashReporter.Send(crashErr)
if _, ok := crashErr.Cause.(mcnerror.ErrDuringPreCreate); ok {
osExit(3)
return
}
}
osExit(1)
return
}
}
}
func confirmInput(msg string) (bool, error) {
fmt.Printf("%s (y/n): ", msg)
var resp string
_, err := fmt.Scanln(&resp)
if err != nil {
return false, err
}
confirmed := strings.Index(strings.ToLower(resp), "y") == 0
return confirmed, nil
}
var Commands = []cli.Command{
{
Name: "active",
Usage: "Print which machine is active",
Action: runCommand(cmdActive),
Flags: []cli.Flag{
cli.IntFlag{
Name: "timeout, t",
Usage: fmt.Sprintf("Timeout in seconds, default to %ds", activeDefaultTimeout),
Value: activeDefaultTimeout,
},
},
},
{
Name: "config",
Usage: "Print the connection config for machine",
Description: "Argument is a machine name.",
Action: runCommand(cmdConfig),
Flags: []cli.Flag{
cli.BoolFlag{
Name: "swarm",
Usage: "Display the Swarm config instead of the Docker daemon",
},
},
},
{
Flags: SharedCreateFlags,
Name: "create",
Usage: "Create a machine",
Description: fmt.Sprintf("Run '%s create --driver name' to include the create flags for that driver in the help text.", os.Args[0]),
Action: runCommand(cmdCreateOuter),
SkipFlagParsing: true,
},
{
Name: "env",
Usage: "Display the commands to set up the environment for the Docker client",
Description: "Argument is a machine name.",
Action: runCommand(cmdEnv),
Flags: []cli.Flag{
cli.BoolFlag{
Name: "swarm",
Usage: "Display the Swarm config instead of the Docker daemon",
},
cli.StringFlag{
Name: "shell",
Usage: "Force environment to be configured for a specified shell: [fish, cmd, powershell, tcsh], default is auto-detect",
},
cli.BoolFlag{
Name: "unset, u",
Usage: "Unset variables instead of setting them",
},
cli.BoolFlag{
Name: "no-proxy",
Usage: "Add machine IP to NO_PROXY environment variable",
},
},
},
{
Name: "inspect",
Usage: "Inspect information about a machine",
Description: "Argument is a machine name.",
Action: runCommand(cmdInspect),
Flags: []cli.Flag{
cli.StringFlag{
Name: "format, f",
Usage: "Format the output using the given go template.",
Value: "",
},
},
},
{
Name: "ip",
Usage: "Get the IP address of a machine",
Description: "Argument(s) are one or more machine names.",
Action: runCommand(cmdIP),
},
{
Name: "kill",
Usage: "Kill a machine",
Description: "Argument(s) are one or more machine names.",
Action: runCommand(cmdKill),
},
{
Name: "ls",
Usage: "List machines",
Action: runCommand(cmdLs),
Flags: []cli.Flag{
cli.BoolFlag{
Name: "quiet, q",
Usage: "Enable quiet mode",
},
cli.StringSliceFlag{
Name: "filter",
Usage: "Filter output based on conditions provided",
Value: &cli.StringSlice{},
},
cli.IntFlag{
Name: "timeout, t",
Usage: fmt.Sprintf("Timeout in seconds, default to %ds", lsDefaultTimeout),
Value: lsDefaultTimeout,
},
cli.StringFlag{
Name: "format, f",
Usage: "Pretty-print machines using a Go template",
},
},
},
{
Name: "provision",
Usage: "Re-provision existing machines",
Action: runCommand(cmdProvision),
},
{
Name: "regenerate-certs",
Usage: "Regenerate TLS Certificates for a machine",
Description: "Argument(s) are one or more machine names.",
Action: runCommand(cmdRegenerateCerts),
Flags: []cli.Flag{
cli.BoolFlag{
Name: "force, f",
Usage: "Force rebuild and do not prompt",
},
},
},
{
Name: "restart",
Usage: "Restart a machine",
Description: "Argument(s) are one or more machine names.",
Action: runCommand(cmdRestart),
},
{
Flags: []cli.Flag{
cli.BoolFlag{
Name: "force, f",
Usage: "Remove local configuration even if machine cannot be removed, also implies an automatic yes (`-y`)",
},
cli.BoolFlag{
Name: "y",
Usage: "Assumes automatic yes to proceed with remove, without prompting further user confirmation",
},
},
Name: "rm",
Usage: "Remove a machine",
Description: "Argument(s) are one or more machine names.",
Action: runCommand(cmdRm),
},
{
Name: "ssh",
Usage: "Log into or run a command on a machine with SSH.",
Description: "Arguments are [machine-name] [command]",
Action: runCommand(cmdSSH),
SkipFlagParsing: true,
},
{
Name: "scp",
Usage: "Copy files between machines",
Description: "Arguments are [machine:][path] [machine:][path].",
Action: runCommand(cmdScp),
Flags: []cli.Flag{
cli.BoolFlag{
Name: "recursive, r",
Usage: "Copy files recursively (required to copy directories)",
},
},
},
{
Name: "start",
Usage: "Start a machine",
Description: "Argument(s) are one or more machine names.",
Action: runCommand(cmdStart),
},
{
Name: "status",
Usage: "Get the status of a machine",
Description: "Argument is a machine name.",
Action: runCommand(cmdStatus),
},
{
Name: "stop",
Usage: "Stop a machine",
Description: "Argument(s) are one or more machine names.",
Action: runCommand(cmdStop),
},
{
Name: "upgrade",
Usage: "Upgrade a machine to the latest version of Docker",
Description: "Argument(s) are one or more machine names.",
Action: runCommand(cmdUpgrade),
},
{
Name: "url",
Usage: "Get the URL of a machine",
Description: "Argument is a machine name.",
Action: runCommand(cmdURL),
},
{
Name: "version",
Usage: "Show the Docker Machine version or a machine docker version",
Action: runCommand(cmdVersion),
},
}
func printIP(h *host.Host) func() error {
return func() error {
ip, err := h.Driver.GetIP()
if err != nil {
return fmt.Errorf("Error getting IP address: %s", err)
}
fmt.Println(ip)
return nil
}
}
// machineCommand maps the command name to the corresponding machine command.
// We run commands concurrently and communicate back an error if there was one.
func machineCommand(actionName string, host *host.Host, errorChan chan<- error) {
// TODO: These actions should have their own type.
commands := map[string](func() error){
"configureAuth": host.ConfigureAuth,
"start": host.Start,
"stop": host.Stop,
"restart": host.Restart,
"kill": host.Kill,
"upgrade": host.Upgrade,
"ip": printIP(host),
"provision": host.Provision,
}
log.Debugf("command=%s machine=%s", actionName, host.Name)
errorChan <- commands[actionName]()
}
// runActionForeachMachine will run the command across multiple machines
func runActionForeachMachine(actionName string, machines []*host.Host) []error {
var (
numConcurrentActions = 0
errorChan = make(chan error)
errs = []error{}
)
for _, machine := range machines {
numConcurrentActions++
go machineCommand(actionName, machine, errorChan)
}
// TODO: We should probably only do 5-10 of these
// at a time, since otherwise cloud providers might
// rate limit us.
for i := 0; i < numConcurrentActions; i++ {
if err := <-errorChan; err != nil {
errs = append(errs, err)
}
}
close(errorChan)
return errs
}
func consolidateErrs(errs []error) error {
finalErr := ""
for _, err := range errs {
finalErr = fmt.Sprintf("%s\n%s", finalErr, err)
}
return errors.New(strings.TrimSpace(finalErr))
}

243
commands/commands_test.go Normal file
View File

@ -0,0 +1,243 @@
package commands
import (
"errors"
"flag"
"testing"
"github.com/codegangsta/cli"
"github.com/docker/machine/commands/commandstest"
"github.com/docker/machine/drivers/fakedriver"
"github.com/docker/machine/libmachine"
"github.com/docker/machine/libmachine/crashreport"
"github.com/docker/machine/libmachine/host"
"github.com/docker/machine/libmachine/hosttest"
"github.com/docker/machine/libmachine/mcnerror"
"github.com/docker/machine/libmachine/provision"
"github.com/docker/machine/libmachine/state"
"github.com/stretchr/testify/assert"
)
func TestRunActionForeachMachine(t *testing.T) {
defer provision.SetDetector(&provision.StandardDetector{})
provision.SetDetector(&provision.FakeDetector{
Provisioner: provision.NewNetstatProvisioner(),
})
// Assume a bunch of machines in randomly started or
// stopped states.
machines := []*host.Host{
{
Name: "foo",
DriverName: "fakedriver",
Driver: &fakedriver.Driver{
MockState: state.Running,
},
},
{
Name: "bar",
DriverName: "fakedriver",
Driver: &fakedriver.Driver{
MockState: state.Stopped,
},
},
{
Name: "baz",
// Ssh, don't tell anyone but this
// driver only _thinks_ it's named
// virtualbox... (to test serial actions)
// It's actually FakeDriver!
DriverName: "virtualbox",
Driver: &fakedriver.Driver{
MockState: state.Stopped,
},
},
{
Name: "spam",
DriverName: "virtualbox",
Driver: &fakedriver.Driver{
MockState: state.Running,
},
},
{
Name: "eggs",
DriverName: "fakedriver",
Driver: &fakedriver.Driver{
MockState: state.Stopped,
},
},
{
Name: "ham",
DriverName: "fakedriver",
Driver: &fakedriver.Driver{
MockState: state.Running,
},
},
}
runActionForeachMachine("start", machines)
for _, machine := range machines {
machineState, _ := machine.Driver.GetState()
assert.Equal(t, state.Running, machineState)
}
runActionForeachMachine("stop", machines)
for _, machine := range machines {
machineState, _ := machine.Driver.GetState()
assert.Equal(t, state.Stopped, machineState)
}
}
func TestPrintIPEmptyGivenLocalEngine(t *testing.T) {
stdoutGetter := commandstest.NewStdoutGetter()
defer stdoutGetter.Stop()
host, _ := hosttest.GetDefaultTestHost()
err := printIP(host)()
assert.NoError(t, err)
assert.Equal(t, "\n", stdoutGetter.Output())
}
func TestPrintIPPrintsGivenRemoteEngine(t *testing.T) {
stdoutGetter := commandstest.NewStdoutGetter()
defer stdoutGetter.Stop()
host, _ := hosttest.GetDefaultTestHost()
host.Driver = &fakedriver.Driver{
MockState: state.Running,
MockIP: "1.2.3.4",
}
err := printIP(host)()
assert.NoError(t, err)
assert.Equal(t, "1.2.3.4\n", stdoutGetter.Output())
}
func TestConsolidateError(t *testing.T) {
cases := []struct {
inputErrs []error
expectedErr error
}{
{
inputErrs: []error{
errors.New("Couldn't remove host 'bar'"),
},
expectedErr: errors.New("Couldn't remove host 'bar'"),
},
{
inputErrs: []error{
errors.New("Couldn't remove host 'bar'"),
errors.New("Couldn't remove host 'foo'"),
},
expectedErr: errors.New("Couldn't remove host 'bar'\nCouldn't remove host 'foo'"),
},
}
for _, c := range cases {
assert.Equal(t, c.expectedErr, consolidateErrs(c.inputErrs))
}
}
type MockCrashReporter struct {
sent bool
}
func (m *MockCrashReporter) Send(err crashreport.CrashError) error {
m.sent = true
return nil
}
func TestSendCrashReport(t *testing.T) {
defer func(fnOsExit func(code int)) { osExit = fnOsExit }(osExit)
osExit = func(code int) {}
defer func(factory func(baseDir string, apiKey string) crashreport.CrashReporter) {
crashreport.NewCrashReporter = factory
}(crashreport.NewCrashReporter)
tests := []struct {
description string
err error
sent bool
}{
{
description: "Should send crash error",
err: crashreport.CrashError{
Cause: errors.New("BUG"),
Command: "command",
Context: "context",
DriverName: "virtualbox",
},
sent: true,
},
{
description: "Should not send standard error",
err: errors.New("BUG"),
sent: false,
},
}
for _, test := range tests {
mockCrashReporter := &MockCrashReporter{}
crashreport.NewCrashReporter = func(baseDir string, apiKey string) crashreport.CrashReporter {
return mockCrashReporter
}
command := func(commandLine CommandLine, api libmachine.API) error {
return test.err
}
context := cli.NewContext(cli.NewApp(), &flag.FlagSet{}, nil)
runCommand(command)(context)
assert.Equal(t, test.sent, mockCrashReporter.sent, test.description)
}
}
func TestReturnExitCode1onError(t *testing.T) {
command := func(commandLine CommandLine, api libmachine.API) error {
return errors.New("foo is not bar")
}
exitCode := checkErrorCodeForCommand(command)
assert.Equal(t, 1, exitCode)
}
func TestReturnExitCode3onErrorDuringPreCreate(t *testing.T) {
command := func(commandLine CommandLine, api libmachine.API) error {
return crashreport.CrashError{
Cause: mcnerror.ErrDuringPreCreate{
Cause: errors.New("foo is not bar"),
},
}
}
exitCode := checkErrorCodeForCommand(command)
assert.Equal(t, 3, exitCode)
}
func checkErrorCodeForCommand(command func(commandLine CommandLine, api libmachine.API) error) int {
var setExitCode int
originalOSExit := osExit
defer func() {
osExit = originalOSExit
}()
osExit = func(code int) {
setExitCode = code
}
context := cli.NewContext(cli.NewApp(), &flag.FlagSet{}, nil)
runCommand(command)(context)
return setExitCode
}

View File

@ -0,0 +1,100 @@
package commandstest
import (
"github.com/codegangsta/cli"
)
type FakeFlagger struct {
Data map[string]interface{}
}
type FakeCommandLine struct {
LocalFlags, GlobalFlags *FakeFlagger
HelpShown, VersionShown bool
CliArgs []string
}
func (ff FakeFlagger) String(key string) string {
if value, ok := ff.Data[key]; ok {
return value.(string)
}
return ""
}
func (ff FakeFlagger) StringSlice(key string) []string {
if value, ok := ff.Data[key]; ok {
return value.([]string)
}
return []string{}
}
func (ff FakeFlagger) Int(key string) int {
if value, ok := ff.Data[key]; ok {
return value.(int)
}
return 0
}
func (ff FakeFlagger) Bool(key string) bool {
if value, ok := ff.Data[key]; ok {
return value.(bool)
}
return false
}
func (fcli *FakeCommandLine) IsSet(key string) bool {
_, ok := fcli.LocalFlags.Data[key]
return ok
}
func (fcli *FakeCommandLine) String(key string) string {
return fcli.LocalFlags.String(key)
}
func (fcli *FakeCommandLine) StringSlice(key string) []string {
return fcli.LocalFlags.StringSlice(key)
}
func (fcli *FakeCommandLine) Int(key string) int {
return fcli.LocalFlags.Int(key)
}
func (fcli *FakeCommandLine) Bool(key string) bool {
if fcli.LocalFlags == nil {
return false
}
return fcli.LocalFlags.Bool(key)
}
func (fcli *FakeCommandLine) GlobalString(key string) string {
return fcli.GlobalFlags.String(key)
}
func (fcli *FakeCommandLine) Generic(name string) interface{} {
return fcli.LocalFlags.Data[name]
}
func (fcli *FakeCommandLine) FlagNames() []string {
flagNames := []string{}
for key := range fcli.LocalFlags.Data {
flagNames = append(flagNames, key)
}
return flagNames
}
func (fcli *FakeCommandLine) ShowHelp() {
fcli.HelpShown = true
}
func (fcli *FakeCommandLine) Application() *cli.App {
return cli.NewApp()
}
func (fcli *FakeCommandLine) Args() cli.Args {
return fcli.CliArgs
}
func (fcli *FakeCommandLine) ShowVersion() {
fcli.VersionShown = true
}

View File

@ -0,0 +1,54 @@
package commandstest
import (
"bytes"
"io"
"os"
)
var (
stdout *os.File
)
func init() {
stdout = os.Stdout
}
type StdoutGetter interface {
Output() string
Stop()
}
type stdoutCapturer struct {
stdout *os.File
output chan string
}
func NewStdoutGetter() StdoutGetter {
r, w, _ := os.Pipe()
os.Stdout = w
output := make(chan string)
go func() {
var testOutput bytes.Buffer
io.Copy(&testOutput, r)
output <- testOutput.String()
}()
return &stdoutCapturer{
stdout: w,
output: output,
}
}
func (c *stdoutCapturer) Output() string {
c.stdout.Close()
text := <-c.output
close(c.output)
return text
}
func (c *stdoutCapturer) Stop() {
os.Stdout = stdout
}

38
commands/config.go Normal file
View File

@ -0,0 +1,38 @@
package commands
import (
"fmt"
"os"
"github.com/docker/machine/libmachine"
"github.com/docker/machine/libmachine/check"
"github.com/docker/machine/libmachine/log"
)
func cmdConfig(c CommandLine, api libmachine.API) error {
// Ensure that log messages always go to stderr when this command is
// being run (it is intended to be run in a subshell)
log.SetOutWriter(os.Stderr)
target, err := targetHost(c, api)
if err != nil {
return err
}
host, err := api.Load(target)
if err != nil {
return err
}
dockerHost, authOptions, err := check.DefaultConnChecker.Check(host, c.Bool("swarm"))
if err != nil {
return fmt.Errorf("Error running connection boilerplate: %s", err)
}
log.Debug(dockerHost)
fmt.Printf("--tlsverify\n--tlscacert=%q\n--tlscert=%q\n--tlskey=%q\n-H=%s\n",
authOptions.CaCertPath, authOptions.ClientCertPath, authOptions.ClientKeyPath, dockerHost)
return nil
}

465
commands/create.go Normal file
View File

@ -0,0 +1,465 @@
package commands
import (
"encoding/json"
"flag"
"fmt"
"os"
"path/filepath"
"regexp"
"sort"
"strings"
"errors"
"time"
"github.com/codegangsta/cli"
"github.com/docker/machine/commands/mcndirs"
"github.com/docker/machine/libmachine"
"github.com/docker/machine/libmachine/auth"
"github.com/docker/machine/libmachine/crashreport"
"github.com/docker/machine/libmachine/drivers"
"github.com/docker/machine/libmachine/drivers/rpc"
"github.com/docker/machine/libmachine/engine"
"github.com/docker/machine/libmachine/host"
"github.com/docker/machine/libmachine/log"
"github.com/docker/machine/libmachine/mcnerror"
"github.com/docker/machine/libmachine/mcnflag"
"github.com/docker/machine/libmachine/swarm"
)
var (
errNoMachineName = errors.New("Error: No machine name specified")
)
var (
SharedCreateFlags = []cli.Flag{
cli.StringFlag{
Name: "driver, d",
Usage: fmt.Sprintf(
"Driver to create machine with.",
),
Value: "none",
EnvVar: "MACHINE_DRIVER",
},
cli.StringFlag{
Name: "engine-install-url",
Usage: "Custom URL to use for engine installation",
Value: drivers.DefaultEngineInstallURL,
EnvVar: "MACHINE_DOCKER_INSTALL_URL",
},
cli.StringSliceFlag{
Name: "engine-opt",
Usage: "Specify arbitrary flags to include with the created engine in the form flag=value",
Value: &cli.StringSlice{},
},
cli.StringSliceFlag{
Name: "engine-insecure-registry",
Usage: "Specify insecure registries to allow with the created engine",
Value: &cli.StringSlice{},
},
cli.StringSliceFlag{
Name: "engine-registry-mirror",
Usage: "Specify registry mirrors to use",
Value: &cli.StringSlice{},
EnvVar: "ENGINE_REGISTRY_MIRROR",
},
cli.StringSliceFlag{
Name: "engine-label",
Usage: "Specify labels for the created engine",
Value: &cli.StringSlice{},
},
cli.StringFlag{
Name: "engine-storage-driver",
Usage: "Specify a storage driver to use with the engine",
},
cli.StringSliceFlag{
Name: "engine-env",
Usage: "Specify environment variables to set in the engine",
Value: &cli.StringSlice{},
},
cli.BoolFlag{
Name: "swarm",
Usage: "Configure Machine to join a Swarm cluster",
},
cli.StringFlag{
Name: "swarm-image",
Usage: "Specify Docker image to use for Swarm",
Value: "swarm:latest",
EnvVar: "MACHINE_SWARM_IMAGE",
},
cli.BoolFlag{
Name: "swarm-master",
Usage: "Configure Machine to be a Swarm master",
},
cli.StringFlag{
Name: "swarm-discovery",
Usage: "Discovery service to use with Swarm",
Value: "",
},
cli.StringFlag{
Name: "swarm-strategy",
Usage: "Define a default scheduling strategy for Swarm",
Value: "spread",
},
cli.StringSliceFlag{
Name: "swarm-opt",
Usage: "Define arbitrary flags for Swarm master",
Value: &cli.StringSlice{},
},
cli.StringSliceFlag{
Name: "swarm-join-opt",
Usage: "Define arbitrary flags for Swarm join",
Value: &cli.StringSlice{},
},
cli.StringFlag{
Name: "swarm-host",
Usage: "ip/socket to listen on for Swarm master",
Value: "tcp://0.0.0.0:3376",
},
cli.StringFlag{
Name: "swarm-addr",
Usage: "addr to advertise for Swarm (default: detect and use the machine IP)",
Value: "",
},
cli.BoolFlag{
Name: "swarm-experimental",
Usage: "Enable Swarm experimental features",
},
cli.StringSliceFlag{
Name: "tls-san",
Usage: "Support extra SANs for TLS certs",
Value: &cli.StringSlice{},
},
}
)
func cmdCreateInner(c CommandLine, api libmachine.API) error {
if len(c.Args()) > 1 {
return fmt.Errorf("Invalid command line. Found extra arguments %v", c.Args()[1:])
}
name := c.Args().First()
if name == "" {
c.ShowHelp()
return errNoMachineName
}
validName := host.ValidateHostName(name)
if !validName {
return fmt.Errorf("Error creating machine: %s", mcnerror.ErrInvalidHostname)
}
if err := validateSwarmDiscovery(c.String("swarm-discovery")); err != nil {
return fmt.Errorf("Error parsing swarm discovery: %s", err)
}
// TODO: Fix hacky JSON solution
rawDriver, err := json.Marshal(&drivers.BaseDriver{
MachineName: name,
StorePath: c.GlobalString("storage-path"),
})
if err != nil {
return fmt.Errorf("Error attempting to marshal bare driver data: %s", err)
}
driverName := c.String("driver")
h, err := api.NewHost(driverName, rawDriver)
if err != nil {
return fmt.Errorf("Error getting new host: %s", err)
}
h.HostOptions = &host.Options{
AuthOptions: &auth.Options{
CertDir: mcndirs.GetMachineCertDir(),
CaCertPath: tlsPath(c, "tls-ca-cert", "ca.pem"),
CaPrivateKeyPath: tlsPath(c, "tls-ca-key", "ca-key.pem"),
ClientCertPath: tlsPath(c, "tls-client-cert", "cert.pem"),
ClientKeyPath: tlsPath(c, "tls-client-key", "key.pem"),
ServerCertPath: filepath.Join(mcndirs.GetMachineDir(), name, "server.pem"),
ServerKeyPath: filepath.Join(mcndirs.GetMachineDir(), name, "server-key.pem"),
StorePath: filepath.Join(mcndirs.GetMachineDir(), name),
ServerCertSANs: c.StringSlice("tls-san"),
},
EngineOptions: &engine.Options{
ArbitraryFlags: c.StringSlice("engine-opt"),
Env: c.StringSlice("engine-env"),
InsecureRegistry: c.StringSlice("engine-insecure-registry"),
Labels: c.StringSlice("engine-label"),
RegistryMirror: c.StringSlice("engine-registry-mirror"),
StorageDriver: c.String("engine-storage-driver"),
TLSVerify: true,
InstallURL: c.String("engine-install-url"),
},
SwarmOptions: &swarm.Options{
IsSwarm: c.Bool("swarm") || c.Bool("swarm-master"),
Image: c.String("swarm-image"),
Agent: c.Bool("swarm"),
Master: c.Bool("swarm-master"),
Discovery: c.String("swarm-discovery"),
Address: c.String("swarm-addr"),
Host: c.String("swarm-host"),
Strategy: c.String("swarm-strategy"),
ArbitraryFlags: c.StringSlice("swarm-opt"),
ArbitraryJoinFlags: c.StringSlice("swarm-join-opt"),
IsExperimental: c.Bool("swarm-experimental"),
},
}
exists, err := api.Exists(h.Name)
if err != nil {
return fmt.Errorf("Error checking if host exists: %s", err)
}
if exists {
return mcnerror.ErrHostAlreadyExists{
Name: h.Name,
}
}
// driverOpts is the actual data we send over the wire to set the
// driver parameters (an interface fulfilling drivers.DriverOptions,
// concrete type rpcdriver.RpcFlags).
mcnFlags := h.Driver.GetCreateFlags()
driverOpts := getDriverOpts(c, mcnFlags)
if err := h.Driver.SetConfigFromFlags(driverOpts); err != nil {
return fmt.Errorf("Error setting machine configuration from flags provided: %s", err)
}
if err := api.Create(h); err != nil {
// Wait for all the logs to reach the client
time.Sleep(2 * time.Second)
vBoxLog := ""
if h.DriverName == "virtualbox" {
vBoxLog = filepath.Join(api.GetMachinesDir(), h.Name, h.Name, "Logs", "VBox.log")
}
return crashreport.CrashError{
Cause: err,
Command: "Create",
Context: "api.performCreate",
DriverName: h.DriverName,
LogFilePath: vBoxLog,
}
}
if err := api.Save(h); err != nil {
return fmt.Errorf("Error attempting to save store: %s", err)
}
log.Infof("To see how to connect your Docker Client to the Docker Engine running on this virtual machine, run: %s env %s", os.Args[0], name)
return nil
}
// The following function is needed because the CLI acrobatics that we're doing
// (with having an "outer" and "inner" function each with their own custom
// settings and flag parsing needs) are not well supported by codegangsta/cli.
//
// Instead of trying to make a convoluted series of flag parsing and relying on
// codegangsta/cli internals work well, we simply read the flags we're
// interested in from the outer function into module-level variables, and then
// use them from the "inner" function.
//
// I'm not very pleased about this, but it seems to be the only decent
// compromise without drastically modifying codegangsta/cli internals or our
// own CLI.
func flagHackLookup(flagName string) string {
// e.g. "-d" for "--driver"
flagPrefix := flagName[1:3]
// TODO: Should we support -flag-name (single hyphen) syntax as well?
for i, arg := range os.Args {
if strings.Contains(arg, flagPrefix) {
// format '--driver foo' or '-d foo'
if arg == flagPrefix || arg == flagName {
if i+1 < len(os.Args) {
return os.Args[i+1]
}
}
// format '--driver=foo' or '-d=foo'
if strings.HasPrefix(arg, flagPrefix+"=") || strings.HasPrefix(arg, flagName+"=") {
return strings.Split(arg, "=")[1]
}
}
}
return ""
}
func cmdCreateOuter(c CommandLine, api libmachine.API) error {
const (
flagLookupMachineName = "flag-lookup"
)
// We didn't recognize the driver name.
driverName := flagHackLookup("--driver")
if driverName == "" {
//TODO: Check Environment have to include flagHackLookup function.
driverName = os.Getenv("MACHINE_DRIVER")
if driverName == "" {
c.ShowHelp()
return nil // ?
}
}
// TODO: Fix hacky JSON solution
rawDriver, err := json.Marshal(&drivers.BaseDriver{
MachineName: flagLookupMachineName,
})
if err != nil {
return fmt.Errorf("Error attempting to marshal bare driver data: %s", err)
}
h, err := api.NewHost(driverName, rawDriver)
if err != nil {
return err
}
// TODO: So much flag manipulation and voodoo here, it seems to be
// asking for trouble.
//
// mcnFlags is the data we get back over the wire (type mcnflag.Flag)
// to indicate which parameters are available.
mcnFlags := h.Driver.GetCreateFlags()
// This bit will actually make "create" display the correct flags based
// on the requested driver.
cliFlags, err := convertMcnFlagsToCliFlags(mcnFlags)
if err != nil {
return fmt.Errorf("Error trying to convert provided driver flags to cli flags: %s", err)
}
for i := range c.Application().Commands {
cmd := &c.Application().Commands[i]
if cmd.HasName("create") {
cmd = addDriverFlagsToCommand(cliFlags, cmd)
}
}
return c.Application().Run(os.Args)
}
func getDriverOpts(c CommandLine, mcnflags []mcnflag.Flag) drivers.DriverOptions {
// TODO: This function is pretty damn YOLO and would benefit from some
// sanity checking around types and assertions.
//
// But, we need it so that we can actually send the flags for creating
// a machine over the wire (cli.Context is a no go since there is so
// much stuff in it).
driverOpts := rpcdriver.RPCFlags{
Values: make(map[string]interface{}),
}
for _, f := range mcnflags {
driverOpts.Values[f.String()] = f.Default()
// Hardcoded logic for boolean... :(
if f.Default() == nil {
driverOpts.Values[f.String()] = false
}
}
for _, name := range c.FlagNames() {
getter, ok := c.Generic(name).(flag.Getter)
if ok {
driverOpts.Values[name] = getter.Get()
} else {
// TODO: This is pretty hacky. StringSlice is the only
// type so far we have to worry about which is not a
// Getter, though.
if c.IsSet(name) {
driverOpts.Values[name] = c.StringSlice(name)
}
}
}
return driverOpts
}
func convertMcnFlagsToCliFlags(mcnFlags []mcnflag.Flag) ([]cli.Flag, error) {
cliFlags := []cli.Flag{}
for _, f := range mcnFlags {
switch t := f.(type) {
// TODO: It seems pretty wrong to just default "nil" to this,
// but cli.BoolFlag doesn't have a "Value" field (false is
// always the default)
case *mcnflag.BoolFlag:
f := f.(*mcnflag.BoolFlag)
cliFlags = append(cliFlags, cli.BoolFlag{
Name: f.Name,
EnvVar: f.EnvVar,
Usage: f.Usage,
})
case *mcnflag.IntFlag:
f := f.(*mcnflag.IntFlag)
cliFlags = append(cliFlags, cli.IntFlag{
Name: f.Name,
EnvVar: f.EnvVar,
Usage: f.Usage,
Value: f.Value,
})
case *mcnflag.StringFlag:
f := f.(*mcnflag.StringFlag)
cliFlags = append(cliFlags, cli.StringFlag{
Name: f.Name,
EnvVar: f.EnvVar,
Usage: f.Usage,
Value: f.Value,
})
case *mcnflag.StringSliceFlag:
f := f.(*mcnflag.StringSliceFlag)
cliFlags = append(cliFlags, cli.StringSliceFlag{
Name: f.Name,
EnvVar: f.EnvVar,
Usage: f.Usage,
//TODO: Is this used with defaults? Can we convert the literal []string to cli.StringSlice properly?
Value: &cli.StringSlice{},
})
default:
log.Warn("Flag is ", f)
return nil, fmt.Errorf("Flag is unrecognized flag type: %T", t)
}
}
return cliFlags, nil
}
func addDriverFlagsToCommand(cliFlags []cli.Flag, cmd *cli.Command) *cli.Command {
cmd.Flags = append(SharedCreateFlags, cliFlags...)
cmd.SkipFlagParsing = false
cmd.Action = runCommand(cmdCreateInner)
sort.Sort(ByFlagName(cmd.Flags))
return cmd
}
func validateSwarmDiscovery(discovery string) error {
if discovery == "" {
return nil
}
matched, err := regexp.MatchString(`[^:]*://.*`, discovery)
if err != nil {
return err
}
if matched {
return nil
}
return fmt.Errorf("Swarm Discovery URL was in the wrong format: %s", discovery)
}
func tlsPath(c CommandLine, flag string, defaultName string) string {
path := c.GlobalString(flag)
if path != "" {
return path
}
return filepath.Join(mcndirs.GetMachineCertDir(), defaultName)
}

119
commands/create_test.go Normal file
View File

@ -0,0 +1,119 @@
package commands
import (
"testing"
"flag"
"github.com/docker/machine/commands/commandstest"
"github.com/docker/machine/libmachine/mcnflag"
"github.com/stretchr/testify/assert"
)
func TestValidateSwarmDiscoveryErrorsGivenInvalidURL(t *testing.T) {
err := validateSwarmDiscovery("foo")
assert.Error(t, err)
}
func TestValidateSwarmDiscoveryAcceptsEmptyString(t *testing.T) {
err := validateSwarmDiscovery("")
assert.NoError(t, err)
}
func TestValidateSwarmDiscoveryAcceptsValidFormat(t *testing.T) {
err := validateSwarmDiscovery("token://deadbeefcafe")
assert.NoError(t, err)
}
type fakeFlagGetter struct {
flag.Value
value interface{}
}
func (ff fakeFlagGetter) Get() interface{} {
return ff.value
}
var nilStringSlice []string
var getDriverOptsFlags = []mcnflag.Flag{
mcnflag.BoolFlag{
Name: "bool",
},
mcnflag.IntFlag{
Name: "int",
},
mcnflag.IntFlag{
Name: "int_defaulted",
Value: 42,
},
mcnflag.StringFlag{
Name: "string",
},
mcnflag.StringFlag{
Name: "string_defaulted",
Value: "bob",
},
mcnflag.StringSliceFlag{
Name: "stringslice",
},
mcnflag.StringSliceFlag{
Name: "stringslice_defaulted",
Value: []string{"joe"},
},
}
var getDriverOptsTests = []struct {
data map[string]interface{}
expected map[string]interface{}
}{
{
expected: map[string]interface{}{
"bool": false,
"int": 0,
"int_defaulted": 42,
"string": "",
"string_defaulted": "bob",
"stringslice": nilStringSlice,
"stringslice_defaulted": []string{"joe"},
},
},
{
data: map[string]interface{}{
"bool": fakeFlagGetter{value: true},
"int": fakeFlagGetter{value: 42},
"int_defaulted": fakeFlagGetter{value: 37},
"string": fakeFlagGetter{value: "jake"},
"string_defaulted": fakeFlagGetter{value: "george"},
// NB: StringSlices are not flag.Getters.
"stringslice": []string{"ford"},
"stringslice_defaulted": []string{"zaphod", "arthur"},
},
expected: map[string]interface{}{
"bool": true,
"int": 42,
"int_defaulted": 37,
"string": "jake",
"string_defaulted": "george",
"stringslice": []string{"ford"},
"stringslice_defaulted": []string{"zaphod", "arthur"},
},
},
}
func TestGetDriverOpts(t *testing.T) {
for _, tt := range getDriverOptsTests {
commandLine := &commandstest.FakeCommandLine{
LocalFlags: &commandstest.FakeFlagger{
Data: tt.data,
},
}
driverOpts := getDriverOpts(commandLine, getDriverOptsFlags)
assert.Equal(t, tt.expected["bool"], driverOpts.Bool("bool"))
assert.Equal(t, tt.expected["int"], driverOpts.Int("int"))
assert.Equal(t, tt.expected["int_defaulted"], driverOpts.Int("int_defaulted"))
assert.Equal(t, tt.expected["string"], driverOpts.String("string"))
assert.Equal(t, tt.expected["string_defaulted"], driverOpts.String("string_defaulted"))
assert.Equal(t, tt.expected["stringslice"], driverOpts.StringSlice("stringslice"))
assert.Equal(t, tt.expected["stringslice_defaulted"], driverOpts.StringSlice("stringslice_defaulted"))
}
}

268
commands/env.go Normal file
View File

@ -0,0 +1,268 @@
package commands
import (
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"text/template"
"github.com/docker/machine/commands/mcndirs"
"github.com/docker/machine/libmachine"
"github.com/docker/machine/libmachine/check"
"github.com/docker/machine/libmachine/log"
"github.com/docker/machine/libmachine/shell"
)
const (
envTmpl = `{{ .Prefix }}DOCKER_TLS_VERIFY{{ .Delimiter }}{{ .DockerTLSVerify }}{{ .Suffix }}{{ .Prefix }}DOCKER_HOST{{ .Delimiter }}{{ .DockerHost }}{{ .Suffix }}{{ .Prefix }}DOCKER_CERT_PATH{{ .Delimiter }}{{ .DockerCertPath }}{{ .Suffix }}{{ .Prefix }}DOCKER_MACHINE_NAME{{ .Delimiter }}{{ .MachineName }}{{ .Suffix }}{{ if .NoProxyVar }}{{ .Prefix }}{{ .NoProxyVar }}{{ .Delimiter }}{{ .NoProxyValue }}{{ .Suffix }}{{end}}{{ .UsageHint }}`
)
var (
errImproperUnsetEnvArgs = errors.New("Error: Expected no machine name when the -u flag is present")
defaultUsageHinter UsageHintGenerator
)
func init() {
defaultUsageHinter = &EnvUsageHintGenerator{}
}
type ShellConfig struct {
Prefix string
Delimiter string
Suffix string
DockerCertPath string
DockerHost string
DockerTLSVerify string
UsageHint string
MachineName string
NoProxyVar string
NoProxyValue string
}
func cmdEnv(c CommandLine, api libmachine.API) error {
var (
err error
shellCfg *ShellConfig
)
// Ensure that log messages always go to stderr when this command is
// being run (it is intended to be run in a subshell)
log.SetOutWriter(os.Stderr)
if c.Bool("unset") {
shellCfg, err = shellCfgUnset(c, api)
if err != nil {
return err
}
} else {
shellCfg, err = shellCfgSet(c, api)
if err != nil {
return err
}
}
return executeTemplateStdout(shellCfg)
}
func shellCfgSet(c CommandLine, api libmachine.API) (*ShellConfig, error) {
if len(c.Args()) > 1 {
return nil, ErrExpectedOneMachine
}
target, err := targetHost(c, api)
if err != nil {
return nil, err
}
host, err := api.Load(target)
if err != nil {
return nil, err
}
dockerHost, _, err := check.DefaultConnChecker.Check(host, c.Bool("swarm"))
if err != nil {
return nil, fmt.Errorf("Error checking TLS connection: %s", err)
}
userShell, err := getShell(c.String("shell"))
if err != nil {
return nil, err
}
shellCfg := &ShellConfig{
DockerCertPath: filepath.Join(mcndirs.GetMachineDir(), host.Name),
DockerHost: dockerHost,
DockerTLSVerify: "1",
UsageHint: defaultUsageHinter.GenerateUsageHint(userShell, os.Args),
MachineName: host.Name,
}
if c.Bool("no-proxy") {
ip, err := host.Driver.GetIP()
if err != nil {
return nil, fmt.Errorf("Error getting host IP: %s", err)
}
noProxyVar, noProxyValue := findNoProxyFromEnv()
// add the docker host to the no_proxy list idempotently
switch {
case noProxyValue == "":
noProxyValue = ip
case strings.Contains(noProxyValue, ip):
//ip already in no_proxy list, nothing to do
default:
noProxyValue = fmt.Sprintf("%s,%s", noProxyValue, ip)
}
shellCfg.NoProxyVar = noProxyVar
shellCfg.NoProxyValue = noProxyValue
}
switch userShell {
case "fish":
shellCfg.Prefix = "set -gx "
shellCfg.Suffix = "\";\n"
shellCfg.Delimiter = " \""
case "powershell":
shellCfg.Prefix = "$Env:"
shellCfg.Suffix = "\"\n"
shellCfg.Delimiter = " = \""
case "cmd":
shellCfg.Prefix = "SET "
shellCfg.Suffix = "\n"
shellCfg.Delimiter = "="
case "tcsh":
shellCfg.Prefix = "setenv "
shellCfg.Suffix = "\";\n"
shellCfg.Delimiter = " \""
case "emacs":
shellCfg.Prefix = "(setenv \""
shellCfg.Suffix = "\")\n"
shellCfg.Delimiter = "\" \""
default:
shellCfg.Prefix = "export "
shellCfg.Suffix = "\"\n"
shellCfg.Delimiter = "=\""
}
return shellCfg, nil
}
func shellCfgUnset(c CommandLine, api libmachine.API) (*ShellConfig, error) {
if len(c.Args()) != 0 {
return nil, errImproperUnsetEnvArgs
}
userShell, err := getShell(c.String("shell"))
if err != nil {
return nil, err
}
shellCfg := &ShellConfig{
UsageHint: defaultUsageHinter.GenerateUsageHint(userShell, os.Args),
}
if c.Bool("no-proxy") {
shellCfg.NoProxyVar, shellCfg.NoProxyValue = findNoProxyFromEnv()
}
switch userShell {
case "fish":
shellCfg.Prefix = "set -e "
shellCfg.Suffix = ";\n"
shellCfg.Delimiter = ""
case "powershell":
shellCfg.Prefix = `Remove-Item Env:\\`
shellCfg.Suffix = "\n"
shellCfg.Delimiter = ""
case "cmd":
shellCfg.Prefix = "SET "
shellCfg.Suffix = "\n"
shellCfg.Delimiter = "="
case "emacs":
shellCfg.Prefix = "(setenv \""
shellCfg.Suffix = ")\n"
shellCfg.Delimiter = "\" nil"
case "tcsh":
shellCfg.Prefix = "unsetenv "
shellCfg.Suffix = ";\n"
shellCfg.Delimiter = ""
default:
shellCfg.Prefix = "unset "
shellCfg.Suffix = "\n"
shellCfg.Delimiter = ""
}
return shellCfg, nil
}
func executeTemplateStdout(shellCfg *ShellConfig) error {
t := template.New("envConfig")
tmpl, err := t.Parse(envTmpl)
if err != nil {
return err
}
return tmpl.Execute(os.Stdout, shellCfg)
}
func getShell(userShell string) (string, error) {
if userShell != "" {
return userShell, nil
}
return shell.Detect()
}
func findNoProxyFromEnv() (string, string) {
// first check for an existing lower case no_proxy var
noProxyVar := "no_proxy"
noProxyValue := os.Getenv("no_proxy")
// otherwise default to allcaps HTTP_PROXY
if noProxyValue == "" {
noProxyVar = "NO_PROXY"
noProxyValue = os.Getenv("NO_PROXY")
}
return noProxyVar, noProxyValue
}
type UsageHintGenerator interface {
GenerateUsageHint(string, []string) string
}
type EnvUsageHintGenerator struct{}
func (g *EnvUsageHintGenerator) GenerateUsageHint(userShell string, args []string) string {
cmd := ""
comment := "#"
dockerMachinePath := args[0]
if strings.Contains(dockerMachinePath, " ") || strings.Contains(dockerMachinePath, `\`) {
args[0] = fmt.Sprintf("\"%s\"", dockerMachinePath)
}
commandLine := strings.Join(args, " ")
switch userShell {
case "fish":
cmd = fmt.Sprintf("eval (%s)", commandLine)
case "powershell":
cmd = fmt.Sprintf("& %s | Invoke-Expression", commandLine)
case "cmd":
cmd = fmt.Sprintf("\t@FOR /f \"tokens=*\" %%i IN ('%s') DO @%%i", commandLine)
comment = "REM"
case "emacs":
cmd = fmt.Sprintf("(with-temp-buffer (shell-command \"%s\" (current-buffer)) (eval-buffer))", commandLine)
comment = ";;"
case "tcsh":
cmd = fmt.Sprintf("eval `%s`", commandLine)
comment = ":"
default:
cmd = fmt.Sprintf("eval $(%s)", commandLine)
}
return fmt.Sprintf("%s Run this command to configure your shell: \n%s %s\n", comment, comment, cmd)
}

600
commands/env_test.go Normal file
View File

@ -0,0 +1,600 @@
package commands
import (
"os"
"path/filepath"
"testing"
"github.com/docker/machine/commands/commandstest"
"github.com/docker/machine/commands/mcndirs"
"github.com/docker/machine/drivers/fakedriver"
"github.com/docker/machine/libmachine"
"github.com/docker/machine/libmachine/auth"
"github.com/docker/machine/libmachine/check"
"github.com/docker/machine/libmachine/host"
"github.com/docker/machine/libmachine/libmachinetest"
"github.com/docker/machine/libmachine/state"
"github.com/stretchr/testify/assert"
)
type FakeConnChecker struct {
DockerHost string
AuthOptions *auth.Options
Err error
}
func (fcc *FakeConnChecker) Check(_ *host.Host, _ bool) (string, *auth.Options, error) {
return fcc.DockerHost, fcc.AuthOptions, fcc.Err
}
type SimpleUsageHintGenerator struct {
Hint string
}
func (suhg *SimpleUsageHintGenerator) GenerateUsageHint(_ string, _ []string) string {
return suhg.Hint
}
func TestHints(t *testing.T) {
var tests = []struct {
userShell string
commandLine []string
expectedHints string
}{
{"", []string{"machine", "env", "default"}, "# Run this command to configure your shell: \n# eval $(machine env default)\n"},
{"", []string{"machine", "env", "--no-proxy", "default"}, "# Run this command to configure your shell: \n# eval $(machine env --no-proxy default)\n"},
{"", []string{"machine", "env", "--swarm", "default"}, "# Run this command to configure your shell: \n# eval $(machine env --swarm default)\n"},
{"", []string{"machine", "env", "--no-proxy", "--swarm", "default"}, "# Run this command to configure your shell: \n# eval $(machine env --no-proxy --swarm default)\n"},
{"", []string{"machine", "env", "--unset"}, "# Run this command to configure your shell: \n# eval $(machine env --unset)\n"},
{"", []string{`C:\\Program Files\docker-machine.exe`, "env", "default"}, "# Run this command to configure your shell: \n# eval $(\"C:\\\\Program Files\\docker-machine.exe\" env default)\n"},
{"", []string{`C:\\Me\docker-machine.exe`, "env", "default"}, "# Run this command to configure your shell: \n# eval $(\"C:\\\\Me\\docker-machine.exe\" env default)\n"},
{"fish", []string{"./machine", "env", "--shell=fish", "default"}, "# Run this command to configure your shell: \n# eval (./machine env --shell=fish default)\n"},
{"fish", []string{"./machine", "env", "--shell=fish", "--no-proxy", "default"}, "# Run this command to configure your shell: \n# eval (./machine env --shell=fish --no-proxy default)\n"},
{"fish", []string{"./machine", "env", "--shell=fish", "--swarm", "default"}, "# Run this command to configure your shell: \n# eval (./machine env --shell=fish --swarm default)\n"},
{"fish", []string{"./machine", "env", "--shell=fish", "--no-proxy", "--swarm", "default"}, "# Run this command to configure your shell: \n# eval (./machine env --shell=fish --no-proxy --swarm default)\n"},
{"fish", []string{"./machine", "env", "--shell=fish", "--unset"}, "# Run this command to configure your shell: \n# eval (./machine env --shell=fish --unset)\n"},
{"powershell", []string{"./machine", "env", "--shell=powershell", "default"}, "# Run this command to configure your shell: \n# & ./machine env --shell=powershell default | Invoke-Expression\n"},
{"powershell", []string{"./machine", "env", "--shell=powershell", "--no-proxy", "default"}, "# Run this command to configure your shell: \n# & ./machine env --shell=powershell --no-proxy default | Invoke-Expression\n"},
{"powershell", []string{"./machine", "env", "--shell=powershell", "--swarm", "default"}, "# Run this command to configure your shell: \n# & ./machine env --shell=powershell --swarm default | Invoke-Expression\n"},
{"powershell", []string{"./machine", "env", "--shell=powershell", "--no-proxy", "--swarm", "default"}, "# Run this command to configure your shell: \n# & ./machine env --shell=powershell --no-proxy --swarm default | Invoke-Expression\n"},
{"powershell", []string{"./machine", "env", "--shell=powershell", "--unset"}, "# Run this command to configure your shell: \n# & ./machine env --shell=powershell --unset | Invoke-Expression\n"},
{"powershell", []string{"./machine", "env", "--shell=powershell", "--unset"}, "# Run this command to configure your shell: \n# & ./machine env --shell=powershell --unset | Invoke-Expression\n"},
{"powershell", []string{`C:\\Program Files\docker-machine.exe`, "env", "--shell=powershell", "default"}, "# Run this command to configure your shell: \n# & \"C:\\\\Program Files\\docker-machine.exe\" env --shell=powershell default | Invoke-Expression\n"},
{"powershell", []string{`C:\\Me\docker-machine.exe`, "env", "--shell=powershell", "default"}, "# Run this command to configure your shell: \n# & \"C:\\\\Me\\docker-machine.exe\" env --shell=powershell default | Invoke-Expression\n"},
{"cmd", []string{"./machine", "env", "--shell=cmd", "default"}, "REM Run this command to configure your shell: \nREM \t@FOR /f \"tokens=*\" %i IN ('./machine env --shell=cmd default') DO @%i\n"},
{"cmd", []string{"./machine", "env", "--shell=cmd", "--no-proxy", "default"}, "REM Run this command to configure your shell: \nREM \t@FOR /f \"tokens=*\" %i IN ('./machine env --shell=cmd --no-proxy default') DO @%i\n"},
{"cmd", []string{"./machine", "env", "--shell=cmd", "--swarm", "default"}, "REM Run this command to configure your shell: \nREM \t@FOR /f \"tokens=*\" %i IN ('./machine env --shell=cmd --swarm default') DO @%i\n"},
{"cmd", []string{"./machine", "env", "--shell=cmd", "--no-proxy", "--swarm", "default"}, "REM Run this command to configure your shell: \nREM \t@FOR /f \"tokens=*\" %i IN ('./machine env --shell=cmd --no-proxy --swarm default') DO @%i\n"},
{"cmd", []string{"./machine", "env", "--shell=cmd", "--unset"}, "REM Run this command to configure your shell: \nREM \t@FOR /f \"tokens=*\" %i IN ('./machine env --shell=cmd --unset') DO @%i\n"},
{"cmd", []string{`C:\\Program Files\docker-machine.exe`, "env", "--shell=cmd", "default"}, "REM Run this command to configure your shell: \nREM \t@FOR /f \"tokens=*\" %i IN ('\"C:\\\\Program Files\\docker-machine.exe\" env --shell=cmd default') DO @%i\n"},
{"cmd", []string{`C:\\Me\docker-machine.exe`, "env", "--shell=cmd", "default"}, "REM Run this command to configure your shell: \nREM \t@FOR /f \"tokens=*\" %i IN ('\"C:\\\\Me\\docker-machine.exe\" env --shell=cmd default') DO @%i\n"},
{"emacs", []string{"./machine", "env", "--shell=emacs", "default"}, ";; Run this command to configure your shell: \n;; (with-temp-buffer (shell-command \"./machine env --shell=emacs default\" (current-buffer)) (eval-buffer))\n"},
{"emacs", []string{"./machine", "env", "--shell=emacs", "--no-proxy", "default"}, ";; Run this command to configure your shell: \n;; (with-temp-buffer (shell-command \"./machine env --shell=emacs --no-proxy default\" (current-buffer)) (eval-buffer))\n"},
{"emacs", []string{"./machine", "env", "--shell=emacs", "--swarm", "default"}, ";; Run this command to configure your shell: \n;; (with-temp-buffer (shell-command \"./machine env --shell=emacs --swarm default\" (current-buffer)) (eval-buffer))\n"},
{"emacs", []string{"./machine", "env", "--shell=emacs", "--no-proxy", "--swarm", "default"}, ";; Run this command to configure your shell: \n;; (with-temp-buffer (shell-command \"./machine env --shell=emacs --no-proxy --swarm default\" (current-buffer)) (eval-buffer))\n"},
{"emacs", []string{"./machine", "env", "--shell=emacs", "--unset"}, ";; Run this command to configure your shell: \n;; (with-temp-buffer (shell-command \"./machine env --shell=emacs --unset\" (current-buffer)) (eval-buffer))\n"},
{"tcsh", []string{"./machine", "env", "--shell=tcsh", "default"}, ": Run this command to configure your shell: \n: eval `./machine env --shell=tcsh default`\n"},
{"tcsh", []string{"./machine", "env", "--shell=tcsh", "--no-proxy", "default"}, ": Run this command to configure your shell: \n: eval `./machine env --shell=tcsh --no-proxy default`\n"},
{"tcsh", []string{"./machine", "env", "--shell=tcsh", "--swarm", "default"}, ": Run this command to configure your shell: \n: eval `./machine env --shell=tcsh --swarm default`\n"},
{"tcsh", []string{"./machine", "env", "--shell=tcsh", "--no-proxy", "--swarm", "default"}, ": Run this command to configure your shell: \n: eval `./machine env --shell=tcsh --no-proxy --swarm default`\n"},
{"tcsh", []string{"./machine", "env", "--shell=tcsh", "--unset"}, ": Run this command to configure your shell: \n: eval `./machine env --shell=tcsh --unset`\n"},
}
for _, test := range tests {
hints := defaultUsageHinter.GenerateUsageHint(test.userShell, test.commandLine)
assert.Equal(t, test.expectedHints, hints)
}
}
func revertUsageHinter(uhg UsageHintGenerator) {
defaultUsageHinter = uhg
}
func TestShellCfgSet(t *testing.T) {
const (
usageHint = "This is a usage hint"
)
// TODO: This should be embedded in some kind of wrapper struct for all
// these `env` operations.
defer revertUsageHinter(defaultUsageHinter)
defaultUsageHinter = &SimpleUsageHintGenerator{usageHint}
var tests = []struct {
description string
commandLine CommandLine
api libmachine.API
connChecker check.ConnChecker
noProxyVar string
noProxyValue string
expectedShellCfg *ShellConfig
expectedErr error
}{
{
description: "no host name specified",
api: &libmachinetest.FakeAPI{
Hosts: []*host.Host{},
},
commandLine: &commandstest.FakeCommandLine{
CliArgs: nil,
},
expectedShellCfg: nil,
expectedErr: ErrNoDefault,
},
{
description: "bash shell set happy path without any flags set",
commandLine: &commandstest.FakeCommandLine{
CliArgs: []string{"quux"},
LocalFlags: &commandstest.FakeFlagger{
Data: map[string]interface{}{
"shell": "bash",
"swarm": false,
"no-proxy": false,
},
},
},
api: &libmachinetest.FakeAPI{
Hosts: []*host.Host{
{
Name: "quux",
},
},
},
connChecker: &FakeConnChecker{
DockerHost: "tcp://1.2.3.4:2376",
AuthOptions: nil,
Err: nil,
},
expectedShellCfg: &ShellConfig{
Prefix: "export ",
Delimiter: "=\"",
Suffix: "\"\n",
DockerCertPath: filepath.Join(mcndirs.GetMachineDir(), "quux"),
DockerHost: "tcp://1.2.3.4:2376",
DockerTLSVerify: "1",
UsageHint: usageHint,
MachineName: "quux",
},
expectedErr: nil,
},
{
description: "bash shell set happy path with 'default' vm",
commandLine: &commandstest.FakeCommandLine{
CliArgs: []string{},
LocalFlags: &commandstest.FakeFlagger{
Data: map[string]interface{}{
"shell": "bash",
"swarm": false,
"no-proxy": false,
},
},
},
api: &libmachinetest.FakeAPI{
Hosts: []*host.Host{
{
Name: defaultMachineName,
},
},
},
connChecker: &FakeConnChecker{
DockerHost: "tcp://1.2.3.4:2376",
AuthOptions: nil,
Err: nil,
},
expectedShellCfg: &ShellConfig{
Prefix: "export ",
Delimiter: "=\"",
Suffix: "\"\n",
DockerCertPath: filepath.Join(mcndirs.GetMachineDir(), defaultMachineName),
DockerHost: "tcp://1.2.3.4:2376",
DockerTLSVerify: "1",
UsageHint: usageHint,
MachineName: defaultMachineName,
},
expectedErr: nil,
},
{
description: "fish shell set happy path",
commandLine: &commandstest.FakeCommandLine{
CliArgs: []string{"quux"},
LocalFlags: &commandstest.FakeFlagger{
Data: map[string]interface{}{
"shell": "fish",
"swarm": false,
"no-proxy": false,
},
},
},
api: &libmachinetest.FakeAPI{
Hosts: []*host.Host{
{
Name: "quux",
},
},
},
connChecker: &FakeConnChecker{
DockerHost: "tcp://1.2.3.4:2376",
AuthOptions: nil,
Err: nil,
},
expectedShellCfg: &ShellConfig{
Prefix: "set -gx ",
Suffix: "\";\n",
Delimiter: " \"",
DockerCertPath: filepath.Join(mcndirs.GetMachineDir(), "quux"),
DockerHost: "tcp://1.2.3.4:2376",
DockerTLSVerify: "1",
UsageHint: usageHint,
MachineName: "quux",
},
expectedErr: nil,
},
{
description: "powershell set happy path",
commandLine: &commandstest.FakeCommandLine{
CliArgs: []string{"quux"},
LocalFlags: &commandstest.FakeFlagger{
Data: map[string]interface{}{
"shell": "powershell",
"swarm": false,
"no-proxy": false,
},
},
},
api: &libmachinetest.FakeAPI{
Hosts: []*host.Host{
{
Name: "quux",
},
},
},
connChecker: &FakeConnChecker{
DockerHost: "tcp://1.2.3.4:2376",
AuthOptions: nil,
Err: nil,
},
expectedShellCfg: &ShellConfig{
Prefix: "$Env:",
Suffix: "\"\n",
Delimiter: " = \"",
DockerCertPath: filepath.Join(mcndirs.GetMachineDir(), "quux"),
DockerHost: "tcp://1.2.3.4:2376",
DockerTLSVerify: "1",
UsageHint: usageHint,
MachineName: "quux",
},
expectedErr: nil,
},
{
description: "emacs set happy path",
commandLine: &commandstest.FakeCommandLine{
CliArgs: []string{"quux"},
LocalFlags: &commandstest.FakeFlagger{
Data: map[string]interface{}{
"shell": "emacs",
"swarm": false,
"no-proxy": false,
},
},
},
api: &libmachinetest.FakeAPI{
Hosts: []*host.Host{
{
Name: "quux",
},
},
},
connChecker: &FakeConnChecker{
DockerHost: "tcp://1.2.3.4:2376",
AuthOptions: nil,
Err: nil,
},
expectedShellCfg: &ShellConfig{
Prefix: "(setenv \"",
Suffix: "\")\n",
Delimiter: "\" \"",
DockerCertPath: filepath.Join(mcndirs.GetMachineDir(), "quux"),
DockerHost: "tcp://1.2.3.4:2376",
DockerTLSVerify: "1",
UsageHint: usageHint,
MachineName: "quux",
},
expectedErr: nil,
},
{
description: "cmd.exe happy path",
commandLine: &commandstest.FakeCommandLine{
CliArgs: []string{"quux"},
LocalFlags: &commandstest.FakeFlagger{
Data: map[string]interface{}{
"shell": "cmd",
"swarm": false,
"no-proxy": false,
},
},
},
api: &libmachinetest.FakeAPI{
Hosts: []*host.Host{
{
Name: "quux",
},
},
},
connChecker: &FakeConnChecker{
DockerHost: "tcp://1.2.3.4:2376",
AuthOptions: nil,
Err: nil,
},
expectedShellCfg: &ShellConfig{
Prefix: "SET ",
Suffix: "\n",
Delimiter: "=",
DockerCertPath: filepath.Join(mcndirs.GetMachineDir(), "quux"),
DockerHost: "tcp://1.2.3.4:2376",
DockerTLSVerify: "1",
UsageHint: usageHint,
MachineName: "quux",
},
expectedErr: nil,
},
{
description: "bash shell set happy path with --no-proxy flag; no existing environment variable set",
commandLine: &commandstest.FakeCommandLine{
CliArgs: []string{"quux"},
LocalFlags: &commandstest.FakeFlagger{
Data: map[string]interface{}{
"shell": "bash",
"swarm": false,
"no-proxy": true,
},
},
},
api: &libmachinetest.FakeAPI{
Hosts: []*host.Host{
{
Name: "quux",
Driver: &fakedriver.Driver{
MockState: state.Running,
MockIP: "1.2.3.4",
},
},
},
},
connChecker: &FakeConnChecker{
DockerHost: "tcp://1.2.3.4:2376",
AuthOptions: nil,
Err: nil,
},
expectedShellCfg: &ShellConfig{
Prefix: "export ",
Delimiter: "=\"",
Suffix: "\"\n",
DockerCertPath: filepath.Join(mcndirs.GetMachineDir(), "quux"),
DockerHost: "tcp://1.2.3.4:2376",
DockerTLSVerify: "1",
UsageHint: usageHint,
NoProxyVar: "NO_PROXY",
NoProxyValue: "1.2.3.4", // From FakeDriver
MachineName: "quux",
},
noProxyVar: "NO_PROXY",
noProxyValue: "",
expectedErr: nil,
},
{
description: "bash shell set happy path with --no-proxy flag; existing environment variable _is_ set",
commandLine: &commandstest.FakeCommandLine{
CliArgs: []string{"quux"},
LocalFlags: &commandstest.FakeFlagger{
Data: map[string]interface{}{
"shell": "bash",
"swarm": false,
"no-proxy": true,
},
},
},
api: &libmachinetest.FakeAPI{
Hosts: []*host.Host{
{
Name: "quux",
Driver: &fakedriver.Driver{
MockState: state.Running,
MockIP: "1.2.3.4",
},
},
},
},
connChecker: &FakeConnChecker{
DockerHost: "tcp://1.2.3.4:2376",
AuthOptions: nil,
Err: nil,
},
expectedShellCfg: &ShellConfig{
Prefix: "export ",
Delimiter: "=\"",
Suffix: "\"\n",
DockerCertPath: filepath.Join(mcndirs.GetMachineDir(), "quux"),
DockerHost: "tcp://1.2.3.4:2376",
DockerTLSVerify: "1",
UsageHint: usageHint,
NoProxyVar: "no_proxy",
NoProxyValue: "192.168.59.1,1.2.3.4", // From FakeDriver
MachineName: "quux",
},
noProxyVar: "no_proxy",
noProxyValue: "192.168.59.1",
expectedErr: nil,
},
}
for _, test := range tests {
// TODO: Ideally this should not hit the environment at all but
// rather should go through an interface.
os.Setenv(test.noProxyVar, test.noProxyValue)
t.Log(test.description)
check.DefaultConnChecker = test.connChecker
shellCfg, err := shellCfgSet(test.commandLine, test.api)
assert.Equal(t, test.expectedShellCfg, shellCfg)
assert.Equal(t, test.expectedErr, err)
os.Unsetenv(test.noProxyVar)
}
}
func TestShellCfgUnset(t *testing.T) {
const (
usageHint = "This is the unset usage hint"
)
defer revertUsageHinter(defaultUsageHinter)
defaultUsageHinter = &SimpleUsageHintGenerator{usageHint}
var tests = []struct {
description string
commandLine CommandLine
api libmachine.API
connChecker check.ConnChecker
noProxyVar string
noProxyValue string
expectedShellCfg *ShellConfig
expectedErr error
}{
{
description: "more than expected args passed in",
commandLine: &commandstest.FakeCommandLine{
CliArgs: []string{"foo", "bar"},
},
expectedShellCfg: nil,
expectedErr: errImproperUnsetEnvArgs,
},
{
description: "bash shell unset happy path without any flags set",
commandLine: &commandstest.FakeCommandLine{
CliArgs: nil,
LocalFlags: &commandstest.FakeFlagger{
Data: map[string]interface{}{
"shell": "bash",
"swarm": false,
"no-proxy": false,
},
},
},
api: &libmachinetest.FakeAPI{},
connChecker: &FakeConnChecker{
DockerHost: "tcp://1.2.3.4:2376",
AuthOptions: nil,
Err: nil,
},
expectedShellCfg: &ShellConfig{
Prefix: "unset ",
Suffix: "\n",
Delimiter: "",
UsageHint: usageHint,
},
expectedErr: nil,
},
{
description: "fish shell unset happy path",
commandLine: &commandstest.FakeCommandLine{
CliArgs: nil,
LocalFlags: &commandstest.FakeFlagger{
Data: map[string]interface{}{
"shell": "fish",
"swarm": false,
"no-proxy": false,
},
},
},
api: &libmachinetest.FakeAPI{},
connChecker: &FakeConnChecker{
DockerHost: "tcp://1.2.3.4:2376",
AuthOptions: nil,
Err: nil,
},
expectedShellCfg: &ShellConfig{
Prefix: "set -e ",
Suffix: ";\n",
Delimiter: "",
UsageHint: usageHint,
},
expectedErr: nil,
},
{
description: "powershell unset happy path",
commandLine: &commandstest.FakeCommandLine{
CliArgs: nil,
LocalFlags: &commandstest.FakeFlagger{
Data: map[string]interface{}{
"shell": "powershell",
"swarm": false,
"no-proxy": false,
},
},
},
api: &libmachinetest.FakeAPI{},
connChecker: &FakeConnChecker{
DockerHost: "tcp://1.2.3.4:2376",
AuthOptions: nil,
Err: nil,
},
expectedShellCfg: &ShellConfig{
Prefix: `Remove-Item Env:\\`,
Suffix: "\n",
Delimiter: "",
UsageHint: usageHint,
},
expectedErr: nil,
},
{
description: "cmd.exe unset happy path",
commandLine: &commandstest.FakeCommandLine{
CliArgs: nil,
LocalFlags: &commandstest.FakeFlagger{
Data: map[string]interface{}{
"shell": "cmd",
"swarm": false,
"no-proxy": false,
},
},
},
api: &libmachinetest.FakeAPI{},
connChecker: &FakeConnChecker{
DockerHost: "tcp://1.2.3.4:2376",
AuthOptions: nil,
Err: nil,
},
expectedShellCfg: &ShellConfig{
Prefix: "SET ",
Suffix: "\n",
Delimiter: "=",
UsageHint: usageHint,
},
expectedErr: nil,
},
// TODO: There is kind of a funny bug (feature?) I discovered
// reasoning about unset() where if there was a NO_PROXY value
// set _before_ the original docker-machine env, it won't be
// restored (NO_PROXY won't be unset at all, it will stay the
// same). We should define expected behavior in this case.
}
for _, test := range tests {
os.Setenv(test.noProxyVar, test.noProxyValue)
t.Log(test.description)
check.DefaultConnChecker = test.connChecker
shellCfg, err := shellCfgUnset(test.commandLine, test.api)
assert.Equal(t, test.expectedShellCfg, shellCfg)
assert.Equal(t, test.expectedErr, err)
os.Setenv(test.noProxyVar, "")
}
}

17
commands/flag_sort.go Normal file
View File

@ -0,0 +1,17 @@
package commands
import "github.com/codegangsta/cli"
type ByFlagName []cli.Flag
func (flags ByFlagName) Len() int {
return len(flags)
}
func (flags ByFlagName) Swap(i, j int) {
flags[i], flags[j] = flags[j], flags[i]
}
func (flags ByFlagName) Less(i, j int) bool {
return flags[i].String() < flags[j].String()
}

72
commands/inspect.go Normal file
View File

@ -0,0 +1,72 @@
package commands
import (
"encoding/json"
"fmt"
"os"
"text/template"
"github.com/docker/machine/libmachine"
)
var funcMap = template.FuncMap{
"json": func(v interface{}) string {
a, _ := json.Marshal(v)
return string(a)
},
"prettyjson": func(v interface{}) string {
a, _ := json.MarshalIndent(v, "", " ")
return string(a)
},
}
func cmdInspect(c CommandLine, api libmachine.API) error {
if len(c.Args()) > 1 {
c.ShowHelp()
return ErrExpectedOneMachine
}
target, err := targetHost(c, api)
if err != nil {
return err
}
host, err := api.Load(target)
if err != nil {
return err
}
tmplString := c.String("format")
if tmplString != "" {
var tmpl *template.Template
var err error
if tmpl, err = template.New("").Funcs(funcMap).Parse(tmplString); err != nil {
return fmt.Errorf("Template parsing error: %v\n", err)
}
jsonHost, err := json.Marshal(host)
if err != nil {
return err
}
obj := make(map[string]interface{})
if err := json.Unmarshal(jsonHost, &obj); err != nil {
return err
}
if err := tmpl.Execute(os.Stdout, obj); err != nil {
return err
}
os.Stdout.Write([]byte{'\n'})
} else {
prettyJSON, err := json.MarshalIndent(host, "", " ")
if err != nil {
return err
}
fmt.Println(string(prettyJSON))
}
return nil
}

31
commands/inspect_test.go Normal file
View File

@ -0,0 +1,31 @@
package commands
import (
"testing"
"github.com/docker/machine/commands/commandstest"
"github.com/docker/machine/libmachine"
"github.com/docker/machine/libmachine/libmachinetest"
"github.com/stretchr/testify/assert"
)
func TestCmdInspect(t *testing.T) {
testCases := []struct {
commandLine CommandLine
api libmachine.API
expectedErr error
}{
{
commandLine: &commandstest.FakeCommandLine{
CliArgs: []string{"foo", "bar"},
},
api: &libmachinetest.FakeAPI{},
expectedErr: ErrExpectedOneMachine,
},
}
for _, tc := range testCases {
err := cmdInspect(tc.commandLine, tc.api)
assert.Equal(t, tc.expectedErr, err)
}
}

7
commands/ip.go Normal file
View File

@ -0,0 +1,7 @@
package commands
import "github.com/docker/machine/libmachine"
func cmdIP(c CommandLine, api libmachine.API) error {
return runAction("ip", c, api)
}

79
commands/ip_test.go Normal file
View File

@ -0,0 +1,79 @@
package commands
import (
"testing"
"github.com/docker/machine/commands/commandstest"
"github.com/docker/machine/drivers/fakedriver"
"github.com/docker/machine/libmachine"
"github.com/docker/machine/libmachine/host"
"github.com/docker/machine/libmachine/libmachinetest"
"github.com/docker/machine/libmachine/state"
"github.com/stretchr/testify/assert"
)
func TestCmdIPMissingMachineName(t *testing.T) {
commandLine := &commandstest.FakeCommandLine{}
api := &libmachinetest.FakeAPI{}
err := cmdURL(commandLine, api)
assert.Equal(t, err, ErrNoDefault)
}
func TestCmdIP(t *testing.T) {
testCases := []struct {
commandLine CommandLine
api libmachine.API
expectedErr error
expectedOut string
}{
{
commandLine: &commandstest.FakeCommandLine{
CliArgs: []string{"machine"},
},
api: &libmachinetest.FakeAPI{
Hosts: []*host.Host{
{
Name: "machine",
Driver: &fakedriver.Driver{
MockState: state.Running,
MockIP: "1.2.3.4",
},
},
},
},
expectedErr: nil,
expectedOut: "1.2.3.4\n",
},
{
commandLine: &commandstest.FakeCommandLine{
CliArgs: []string{},
},
api: &libmachinetest.FakeAPI{
Hosts: []*host.Host{
{
Name: "default",
Driver: &fakedriver.Driver{
MockState: state.Running,
MockIP: "1.2.3.4",
},
},
},
},
expectedErr: nil,
expectedOut: "1.2.3.4\n",
},
}
for _, tc := range testCases {
stdoutGetter := commandstest.NewStdoutGetter()
err := cmdIP(tc.commandLine, tc.api)
assert.Equal(t, tc.expectedErr, err)
assert.Equal(t, tc.expectedOut, stdoutGetter.Output())
stdoutGetter.Stop()
}
}

7
commands/kill.go Normal file
View File

@ -0,0 +1,7 @@
package commands
import "github.com/docker/machine/libmachine"
func cmdKill(c CommandLine, api libmachine.API) error {
return runAction("kill", c, api)
}

56
commands/kill_test.go Normal file
View File

@ -0,0 +1,56 @@
package commands
import (
"testing"
"github.com/docker/machine/commands/commandstest"
"github.com/docker/machine/drivers/fakedriver"
"github.com/docker/machine/libmachine/host"
"github.com/docker/machine/libmachine/libmachinetest"
"github.com/docker/machine/libmachine/state"
"github.com/stretchr/testify/assert"
)
func TestCmdKillMissingMachineName(t *testing.T) {
commandLine := &commandstest.FakeCommandLine{}
api := &libmachinetest.FakeAPI{}
err := cmdKill(commandLine, api)
assert.Equal(t, ErrNoDefault, err)
}
func TestCmdKill(t *testing.T) {
commandLine := &commandstest.FakeCommandLine{
CliArgs: []string{"machineToKill1", "machineToKill2"},
}
api := &libmachinetest.FakeAPI{
Hosts: []*host.Host{
{
Name: "machineToKill1",
Driver: &fakedriver.Driver{
MockState: state.Running,
},
},
{
Name: "machineToKill2",
Driver: &fakedriver.Driver{
MockState: state.Running,
},
},
{
Name: "machine",
Driver: &fakedriver.Driver{
MockState: state.Running,
},
},
},
}
err := cmdKill(commandLine, api)
assert.NoError(t, err)
assert.Equal(t, state.Stopped, libmachinetest.State(api, "machineToKill1"))
assert.Equal(t, state.Stopped, libmachinetest.State(api, "machineToKill2"))
assert.Equal(t, state.Running, libmachinetest.State(api, "machine"))
}

506
commands/ls.go Normal file
View File

@ -0,0 +1,506 @@
package commands
import (
"errors"
"fmt"
"os"
"regexp"
"sort"
"strings"
"text/tabwriter"
"text/template"
"time"
"io"
"github.com/docker/machine/libmachine"
"github.com/docker/machine/libmachine/drivers"
"github.com/docker/machine/libmachine/engine"
"github.com/docker/machine/libmachine/host"
"github.com/docker/machine/libmachine/log"
"github.com/docker/machine/libmachine/mcndockerclient"
"github.com/docker/machine/libmachine/persist"
"github.com/docker/machine/libmachine/state"
"github.com/docker/machine/libmachine/swarm"
"github.com/skarademir/naturalsort"
)
const (
lsDefaultTimeout = 10
tableFormatKey = "table"
lsDefaultFormat = "table {{ .Name }}\t{{ .Active }}\t{{ .DriverName}}\t{{ .State }}\t{{ .URL }}\t{{ .Swarm }}\t{{ .DockerVersion }}\t{{ .Error}}"
)
var (
headers = map[string]string{
"Name": "NAME",
"Active": "ACTIVE",
"ActiveHost": "ACTIVE_HOST",
"ActiveSwarm": "ACTIVE_SWARM",
"DriverName": "DRIVER",
"State": "STATE",
"URL": "URL",
"SwarmOptions": "SWARM_OPTIONS",
"Swarm": "SWARM",
"EngineOptions": "ENGINE_OPTIONS",
"Error": "ERRORS",
"DockerVersion": "DOCKER",
"ResponseTime": "RESPONSE",
}
)
type HostListItem struct {
Name string
Active string
ActiveHost bool
ActiveSwarm bool
DriverName string
State state.State
URL string
SwarmOptions *swarm.Options
Swarm string
EngineOptions *engine.Options
Error string
DockerVersion string
ResponseTime time.Duration
}
// FilterOptions -
type FilterOptions struct {
SwarmName []string
DriverName []string
State []string
Name []string
Labels []string
}
func cmdLs(c CommandLine, api libmachine.API) error {
filters, err := parseFilters(c.StringSlice("filter"))
if err != nil {
return err
}
hostList, hostInError, err := persist.LoadAllHosts(api)
if err != nil {
return err
}
hostList = filterHosts(hostList, filters)
// Just print out the names if we're being quiet
if c.Bool("quiet") {
for _, host := range hostList {
fmt.Println(host.Name)
}
return nil
}
template, table, err := parseFormat(c.String("format"))
if err != nil {
return err
}
var w io.Writer
if table {
tabWriter := tabwriter.NewWriter(os.Stdout, 5, 1, 3, ' ', 0)
defer tabWriter.Flush()
w = tabWriter
if err := template.Execute(w, headers); err != nil {
return err
}
} else {
w = os.Stdout
}
timeout := time.Duration(c.Int("timeout")) * time.Second
items := getHostListItems(hostList, hostInError, timeout)
swarmMasters := make(map[string]string)
swarmInfo := make(map[string]string)
for _, host := range hostList {
if host.HostOptions != nil {
swarmOptions := host.HostOptions.SwarmOptions
if swarmOptions.Master {
swarmMasters[swarmOptions.Discovery] = host.Name
}
if swarmOptions.Discovery != "" {
swarmInfo[host.Name] = swarmOptions.Discovery
}
}
}
for _, item := range items {
swarmColumn := ""
if item.SwarmOptions != nil && item.SwarmOptions.Discovery != "" {
swarmColumn = swarmMasters[item.SwarmOptions.Discovery]
if item.SwarmOptions.Master {
swarmColumn = fmt.Sprintf("%s (master)", swarmColumn)
}
}
item.Swarm = swarmColumn
if err := template.Execute(w, item); err != nil {
return err
}
}
return nil
}
func parseFormat(format string) (*template.Template, bool, error) {
table := false
finalFormat := format
if finalFormat == "" {
finalFormat = lsDefaultFormat
}
if strings.HasPrefix(finalFormat, tableFormatKey) {
table = true
finalFormat = finalFormat[len(tableFormatKey):]
}
finalFormat = strings.Trim(finalFormat, " ")
r := strings.NewReplacer(`\t`, "\t", `\n`, "\n")
finalFormat = r.Replace(finalFormat)
template, err := template.New("").Parse(finalFormat + "\n")
if err != nil {
return nil, false, err
}
return template, table, nil
}
func parseFilters(filters []string) (FilterOptions, error) {
options := FilterOptions{}
for _, f := range filters {
kv := strings.SplitN(f, "=", 2)
if len(kv) != 2 {
return options, errors.New("Unsupported filter syntax.")
}
key, value := strings.ToLower(kv[0]), kv[1]
switch key {
case "swarm":
options.SwarmName = append(options.SwarmName, value)
case "driver":
options.DriverName = append(options.DriverName, value)
case "state":
options.State = append(options.State, value)
case "name":
options.Name = append(options.Name, value)
case "label":
options.Labels = append(options.Labels, value)
default:
return options, fmt.Errorf("Unsupported filter key '%s'", key)
}
}
return options, nil
}
func filterHosts(hosts []*host.Host, filters FilterOptions) []*host.Host {
if len(filters.SwarmName) == 0 &&
len(filters.DriverName) == 0 &&
len(filters.State) == 0 &&
len(filters.Name) == 0 &&
len(filters.Labels) == 0 {
return hosts
}
filteredHosts := []*host.Host{}
swarmMasters := getSwarmMasters(hosts)
for _, h := range hosts {
if filterHost(h, filters, swarmMasters) {
filteredHosts = append(filteredHosts, h)
}
}
return filteredHosts
}
func getSwarmMasters(hosts []*host.Host) map[string]string {
swarmMasters := make(map[string]string)
for _, h := range hosts {
if h.HostOptions != nil {
swarmOptions := h.HostOptions.SwarmOptions
if swarmOptions != nil && swarmOptions.Master {
swarmMasters[swarmOptions.Discovery] = h.Name
}
}
}
return swarmMasters
}
func filterHost(host *host.Host, filters FilterOptions, swarmMasters map[string]string) bool {
swarmMatches := matchesSwarmName(host, filters.SwarmName, swarmMasters)
driverMatches := matchesDriverName(host, filters.DriverName)
stateMatches := matchesState(host, filters.State)
nameMatches := matchesName(host, filters.Name)
labelMatches := matchesLabel(host, filters.Labels)
return swarmMatches && driverMatches && stateMatches && nameMatches && labelMatches
}
func matchesSwarmName(host *host.Host, swarmNames []string, swarmMasters map[string]string) bool {
if len(swarmNames) == 0 {
return true
}
for _, n := range swarmNames {
if host.HostOptions != nil && host.HostOptions.SwarmOptions != nil {
if strings.EqualFold(n, swarmMasters[host.HostOptions.SwarmOptions.Discovery]) {
return true
}
}
}
return false
}
func matchesDriverName(host *host.Host, driverNames []string) bool {
if len(driverNames) == 0 {
return true
}
for _, n := range driverNames {
if strings.EqualFold(host.DriverName, n) {
return true
}
}
return false
}
func matchesState(host *host.Host, states []string) bool {
if len(states) == 0 {
return true
}
for _, n := range states {
s, err := host.Driver.GetState()
if err != nil {
log.Warn(err)
}
if strings.EqualFold(n, s.String()) {
return true
}
}
return false
}
func matchesName(host *host.Host, names []string) bool {
if len(names) == 0 {
return true
}
for _, n := range names {
r, err := regexp.Compile(n)
if err != nil {
log.Error(err)
os.Exit(1) // TODO: Can we get rid of this call, and exit 'properly' ?
}
if r.MatchString(host.Driver.GetMachineName()) {
return true
}
}
return false
}
func matchesLabel(host *host.Host, labels []string) bool {
if len(labels) == 0 {
return true
}
var englabels = make(map[string]string, len(host.HostOptions.EngineOptions.Labels))
if host.HostOptions != nil && host.HostOptions.EngineOptions.Labels != nil {
for _, s := range host.HostOptions.EngineOptions.Labels {
kv := strings.SplitN(s, "=", 2)
englabels[kv[0]] = kv[1]
}
}
for _, l := range labels {
kv := strings.SplitN(l, "=", 2)
if val, exists := englabels[kv[0]]; exists && strings.EqualFold(val, kv[1]) {
return true
}
}
return false
}
// PERFORMANCE: The code of this function is complicated because we try
// to call the underlying drivers as less as possible to get the information
// we need.
func attemptGetHostState(h *host.Host, stateQueryChan chan<- HostListItem) {
requestBeginning := time.Now()
url := ""
currentState := state.None
dockerVersion := "Unknown"
hostError := ""
url, err := h.URL()
// PERFORMANCE: if we have the url, it's ok to assume the host is running
// This reduces the number of calls to the drivers
if err == nil {
if url != "" {
currentState = state.Running
} else {
currentState, err = h.Driver.GetState()
}
} else {
currentState, _ = h.Driver.GetState()
}
if err == nil && url != "" {
// PERFORMANCE: Reuse the url instead of asking the host again.
// This reduces the number of calls to the drivers
dockerHost := &mcndockerclient.RemoteDocker{
HostURL: url,
AuthOption: h.AuthOptions(),
}
dockerVersion, err = mcndockerclient.DockerVersion(dockerHost)
if err != nil {
dockerVersion = "Unknown"
} else {
dockerVersion = fmt.Sprintf("v%s", dockerVersion)
}
}
if err != nil {
hostError = err.Error()
}
if hostError == drivers.ErrHostIsNotRunning.Error() {
hostError = ""
}
var swarmOptions *swarm.Options
var engineOptions *engine.Options
if h.HostOptions != nil {
swarmOptions = h.HostOptions.SwarmOptions
engineOptions = h.HostOptions.EngineOptions
}
isMaster := false
swarmHost := ""
if swarmOptions != nil {
isMaster = swarmOptions.Master
swarmHost = swarmOptions.Host
}
activeHost := isActive(currentState, url)
activeSwarm := isSwarmActive(currentState, url, isMaster, swarmHost)
active := "-"
if activeHost {
active = "*"
}
if activeSwarm {
active = "* (swarm)"
}
stateQueryChan <- HostListItem{
Name: h.Name,
Active: active,
ActiveHost: activeHost,
ActiveSwarm: activeSwarm,
DriverName: h.Driver.DriverName(),
State: currentState,
URL: url,
SwarmOptions: swarmOptions,
EngineOptions: engineOptions,
DockerVersion: dockerVersion,
Error: hostError,
ResponseTime: time.Now().Round(time.Millisecond).Sub(requestBeginning.Round(time.Millisecond)),
}
}
func getHostState(h *host.Host, hostListItemsChan chan<- HostListItem, timeout time.Duration) {
// This channel is used to communicate the properties we are querying
// about the host in the case of a successful read.
stateQueryChan := make(chan HostListItem)
go attemptGetHostState(h, stateQueryChan)
select {
// If we get back useful information, great. Forward it straight to
// the original parent channel.
case hli := <-stateQueryChan:
hostListItemsChan <- hli
// Otherwise, give up after a predetermined duration.
case <-time.After(timeout):
hostListItemsChan <- HostListItem{
Name: h.Name,
DriverName: h.Driver.DriverName(),
State: state.Timeout,
ResponseTime: timeout,
}
}
}
func getHostListItems(hostList []*host.Host, hostsInError map[string]error, timeout time.Duration) []HostListItem {
log.Debugf("timeout set to %s", timeout)
hostListItems := []HostListItem{}
hostListItemsChan := make(chan HostListItem)
for _, h := range hostList {
go getHostState(h, hostListItemsChan, timeout)
}
for range hostList {
hostListItems = append(hostListItems, <-hostListItemsChan)
}
close(hostListItemsChan)
for name, err := range hostsInError {
hostListItems = append(hostListItems, newHostListItemInError(name, err))
}
sortHostListItemsByName(hostListItems)
return hostListItems
}
func newHostListItemInError(name string, err error) HostListItem {
return HostListItem{
Name: name,
DriverName: "not found",
State: state.Error,
Error: strings.Replace(err.Error(), "\n", " ", -1),
}
}
func sortHostListItemsByName(items []HostListItem) {
m := make(map[string]HostListItem, len(items))
s := make([]string, len(items))
for i, v := range items {
name := strings.ToLower(v.Name)
m[name] = v
s[i] = name
}
sort.Sort(naturalsort.NaturalSort(s))
for i, v := range s {
items[i] = m[v]
}
}
func isActive(currentState state.State, hostURL string) bool {
return currentState == state.Running && hostURL == os.Getenv("DOCKER_HOST")
}
func isSwarmActive(currentState state.State, hostURL string, isMaster bool, swarmHost string) bool {
return isMaster && currentState == state.Running && toSwarmURL(hostURL, swarmHost) == os.Getenv("DOCKER_HOST")
}
func urlPort(urlWithPort string) string {
parts := strings.Split(urlWithPort, ":")
return parts[len(parts)-1]
}
func toSwarmURL(hostURL string, swarmHost string) string {
hostPort := urlPort(hostURL)
swarmPort := urlPort(swarmHost)
return strings.Replace(hostURL, ":"+hostPort, ":"+swarmPort, 1)
}

535
commands/ls_test.go Normal file
View File

@ -0,0 +1,535 @@
package commands
import (
"os"
"testing"
"time"
"errors"
"github.com/docker/machine/drivers/fakedriver"
"github.com/docker/machine/libmachine/engine"
"github.com/docker/machine/libmachine/host"
"github.com/docker/machine/libmachine/mcndockerclient"
"github.com/docker/machine/libmachine/state"
"github.com/docker/machine/libmachine/swarm"
"github.com/stretchr/testify/assert"
)
func TestParseFiltersErrorsGivenInvalidFilter(t *testing.T) {
_, err := parseFilters([]string{"foo=bar"})
assert.EqualError(t, err, "Unsupported filter key 'foo'")
}
func TestParseFiltersSwarm(t *testing.T) {
actual, _ := parseFilters([]string{"swarm=foo"})
assert.Equal(t, actual, FilterOptions{SwarmName: []string{"foo"}})
}
func TestParseFiltersDriver(t *testing.T) {
actual, _ := parseFilters([]string{"driver=bar"})
assert.Equal(t, actual, FilterOptions{DriverName: []string{"bar"}})
}
func TestParseFiltersState(t *testing.T) {
actual, _ := parseFilters([]string{"state=Running"})
assert.Equal(t, actual, FilterOptions{State: []string{"Running"}})
}
func TestParseFiltersName(t *testing.T) {
actual, _ := parseFilters([]string{"name=dev"})
assert.Equal(t, actual, FilterOptions{Name: []string{"dev"}})
}
func TestParseFiltersLabel(t *testing.T) {
actual, err := parseFilters([]string{"label=com.example.foo=bar"})
assert.EqualValues(t, actual, FilterOptions{Labels: []string{"com.example.foo=bar"}})
assert.Nil(t, err, "returned err value must be Nil")
}
func TestParseFiltersAll(t *testing.T) {
actual, _ := parseFilters([]string{"swarm=foo", "driver=bar", "state=Stopped", "name=dev"})
assert.Equal(t, actual, FilterOptions{SwarmName: []string{"foo"}, DriverName: []string{"bar"}, State: []string{"Stopped"}, Name: []string{"dev"}})
}
func TestParseFiltersAllCase(t *testing.T) {
actual, err := parseFilters([]string{"sWarM=foo", "DrIver=bar", "StaTe=Stopped", "NAMe=dev", "LABEL=com=foo"})
assert.Equal(t, actual, FilterOptions{SwarmName: []string{"foo"}, DriverName: []string{"bar"}, State: []string{"Stopped"}, Name: []string{"dev"}, Labels: []string{"com=foo"}})
assert.Nil(t, err, "err should be nil")
}
func TestParseFiltersDuplicates(t *testing.T) {
actual, _ := parseFilters([]string{"swarm=foo", "driver=bar", "name=mark", "swarm=baz", "driver=qux", "state=Running", "state=Starting", "name=time"})
assert.Equal(t, actual, FilterOptions{SwarmName: []string{"foo", "baz"}, DriverName: []string{"bar", "qux"}, State: []string{"Running", "Starting"}, Name: []string{"mark", "time"}})
}
func TestParseFiltersValueWithEqual(t *testing.T) {
actual, _ := parseFilters([]string{"driver=bar=baz"})
assert.Equal(t, actual, FilterOptions{DriverName: []string{"bar=baz"}})
}
func TestFilterHostsReturnsFiltersValuesCaseInsensitive(t *testing.T) {
opts := FilterOptions{
SwarmName: []string{"fOo"},
DriverName: []string{"ViRtUaLboX"},
State: []string{"StOPpeD"},
Labels: []string{"com.EXAMPLE.app=FOO"},
}
hosts := []*host.Host{}
actual := filterHosts(hosts, opts)
assert.EqualValues(t, actual, hosts)
}
func TestFilterHostsReturnsSameGivenNoFilters(t *testing.T) {
opts := FilterOptions{}
hosts := []*host.Host{
{
Name: "testhost",
DriverName: "fakedriver",
},
}
actual := filterHosts(hosts, opts)
assert.EqualValues(t, actual, hosts)
}
func TestFilterHostsReturnSetLabel(t *testing.T) {
opts := FilterOptions{
Labels: []string{"com.class.foo=bar"},
}
hosts := []*host.Host{
{
Name: "testhost",
DriverName: "fakedriver",
HostOptions: &host.Options{
EngineOptions: &engine.Options{
Labels: []string{"com.class.foo=bar"},
},
},
},
}
actual := filterHosts(hosts, opts)
assert.EqualValues(t, actual, hosts)
}
func TestFilterHostsReturnsEmptyGivenEmptyHosts(t *testing.T) {
opts := FilterOptions{
SwarmName: []string{"foo"},
}
hosts := []*host.Host{}
assert.Empty(t, filterHosts(hosts, opts))
}
func TestFilterHostsReturnsEmptyGivenNonMatchingFilters(t *testing.T) {
opts := FilterOptions{
SwarmName: []string{"foo"},
}
hosts := []*host.Host{
{
Name: "testhost",
DriverName: "fakedriver",
},
}
assert.Empty(t, filterHosts(hosts, opts))
}
func TestFilterHostsBySwarmName(t *testing.T) {
opts := FilterOptions{
SwarmName: []string{"master"},
}
master :=
&host.Host{
Name: "master",
HostOptions: &host.Options{
SwarmOptions: &swarm.Options{Master: true, Discovery: "foo"},
},
}
node1 :=
&host.Host{
Name: "node1",
HostOptions: &host.Options{
SwarmOptions: &swarm.Options{Master: false, Discovery: "foo"},
},
}
othermaster :=
&host.Host{
Name: "othermaster",
HostOptions: &host.Options{
SwarmOptions: &swarm.Options{Master: true, Discovery: "bar"},
},
}
hosts := []*host.Host{master, node1, othermaster}
expected := []*host.Host{master, node1}
assert.EqualValues(t, filterHosts(hosts, opts), expected)
}
func TestFilterHostsByDriverName(t *testing.T) {
opts := FilterOptions{
DriverName: []string{"fakedriver"},
}
node1 :=
&host.Host{
Name: "node1",
DriverName: "fakedriver",
}
node2 :=
&host.Host{
Name: "node2",
DriverName: "virtualbox",
}
node3 :=
&host.Host{
Name: "node3",
DriverName: "fakedriver",
}
hosts := []*host.Host{node1, node2, node3}
expected := []*host.Host{node1, node3}
assert.EqualValues(t, filterHosts(hosts, opts), expected)
}
func TestFilterHostsByState(t *testing.T) {
opts := FilterOptions{
State: []string{"Paused", "Saved", "Stopped"},
}
node1 :=
&host.Host{
Name: "node1",
DriverName: "fakedriver",
Driver: &fakedriver.Driver{MockState: state.Paused},
}
node2 :=
&host.Host{
Name: "node2",
DriverName: "virtualbox",
Driver: &fakedriver.Driver{MockState: state.Stopped},
}
node3 :=
&host.Host{
Name: "node3",
DriverName: "fakedriver",
Driver: &fakedriver.Driver{MockState: state.Running},
}
hosts := []*host.Host{node1, node2, node3}
expected := []*host.Host{node1, node2}
assert.EqualValues(t, filterHosts(hosts, opts), expected)
}
func TestFilterHostsByName(t *testing.T) {
opts := FilterOptions{
Name: []string{"fire", "ice", "earth", "a.?r"},
}
node1 :=
&host.Host{
Name: "fire",
DriverName: "fakedriver",
Driver: &fakedriver.Driver{MockState: state.Paused, MockName: "fire"},
}
node2 :=
&host.Host{
Name: "ice",
DriverName: "adriver",
Driver: &fakedriver.Driver{MockState: state.Paused, MockName: "ice"},
}
node3 :=
&host.Host{
Name: "air",
DriverName: "nodriver",
Driver: &fakedriver.Driver{MockState: state.Paused, MockName: "air"},
}
node4 :=
&host.Host{
Name: "water",
DriverName: "falsedriver",
Driver: &fakedriver.Driver{MockState: state.Paused, MockName: "water"},
}
hosts := []*host.Host{node1, node2, node3, node4}
expected := []*host.Host{node1, node2, node3}
assert.EqualValues(t, filterHosts(hosts, opts), expected)
}
func TestFilterHostsMultiFlags(t *testing.T) {
opts := FilterOptions{
SwarmName: []string{},
DriverName: []string{"fakedriver", "virtualbox"},
}
node1 :=
&host.Host{
Name: "node1",
DriverName: "fakedriver",
}
node2 :=
&host.Host{
Name: "node2",
DriverName: "virtualbox",
}
node3 :=
&host.Host{
Name: "node3",
DriverName: "softlayer",
}
hosts := []*host.Host{node1, node2, node3}
expected := []*host.Host{node1, node2}
assert.EqualValues(t, filterHosts(hosts, opts), expected)
}
func TestFilterHostsDifferentFlagsProduceAND(t *testing.T) {
opts := FilterOptions{
DriverName: []string{"virtualbox"},
State: []string{"Running"},
}
hosts := []*host.Host{
{
Name: "node1",
DriverName: "fakedriver",
Driver: &fakedriver.Driver{MockState: state.Paused},
},
{
Name: "node2",
DriverName: "virtualbox",
Driver: &fakedriver.Driver{MockState: state.Stopped},
},
{
Name: "node3",
DriverName: "fakedriver",
Driver: &fakedriver.Driver{MockState: state.Running},
},
}
assert.Empty(t, filterHosts(hosts, opts))
}
func TestGetHostListItems(t *testing.T) {
defer func(versioner mcndockerclient.DockerVersioner) { mcndockerclient.CurrentDockerVersioner = versioner }(mcndockerclient.CurrentDockerVersioner)
mcndockerclient.CurrentDockerVersioner = &mcndockerclient.FakeDockerVersioner{Version: "1.9"}
// TODO: Ideally this would mockable via interface instead.
defer func(host string) { os.Setenv("DOCKER_HOST", host) }(os.Getenv("DOCKER_HOST"))
os.Setenv("DOCKER_HOST", "tcp://active.host.com:2376")
hosts := []*host.Host{
{
Name: "foo",
Driver: &fakedriver.Driver{
MockState: state.Running,
MockIP: "active.host.com",
},
},
{
Name: "bar100",
Driver: &fakedriver.Driver{
MockState: state.Stopped,
},
},
{
Name: "bar10",
Driver: &fakedriver.Driver{
MockState: state.Error,
},
},
}
expected := []struct {
name string
state state.State
active bool
version string
error string
}{
{"bar10", state.Error, false, "Unknown", "Unable to get ip"},
{"bar100", state.Stopped, false, "Unknown", ""},
{"foo", state.Running, true, "v1.9", ""},
}
items := getHostListItems(hosts, map[string]error{}, 10*time.Second)
for i := range expected {
assert.Equal(t, expected[i].name, items[i].Name)
assert.Equal(t, expected[i].state, items[i].State)
assert.Equal(t, expected[i].active, items[i].ActiveHost)
assert.Equal(t, expected[i].version, items[i].DockerVersion)
assert.Equal(t, expected[i].error, items[i].Error)
}
}
func TestGetHostListItemsEnvDockerHostUnset(t *testing.T) {
defer func(versioner mcndockerclient.DockerVersioner) { mcndockerclient.CurrentDockerVersioner = versioner }(mcndockerclient.CurrentDockerVersioner)
mcndockerclient.CurrentDockerVersioner = &mcndockerclient.FakeDockerVersioner{Version: "1.9"}
defer func(host string) { os.Setenv("DOCKER_HOST", host) }(os.Getenv("DOCKER_HOST"))
os.Unsetenv("DOCKER_HOST")
hosts := []*host.Host{
{
Name: "foo",
Driver: &fakedriver.Driver{
MockState: state.Running,
MockIP: "120.0.0.1",
},
},
{
Name: "bar",
Driver: &fakedriver.Driver{
MockState: state.Stopped,
},
},
{
Name: "baz",
Driver: &fakedriver.Driver{
MockState: state.Saved,
},
},
}
expected := map[string]struct {
state state.State
active bool
}{
"foo": {state.Running, false},
"bar": {state.Stopped, false},
"baz": {state.Saved, false},
}
items := getHostListItems(hosts, map[string]error{}, 10*time.Second)
for _, item := range items {
expected := expected[item.Name]
assert.Equal(t, expected.state, item.State)
assert.Equal(t, expected.active, item.ActiveHost)
}
}
func TestIsActive(t *testing.T) {
cases := []struct {
dockerHost string
state state.State
expected bool
}{
{"", state.Running, false},
{"tcp://5.6.7.8:2376", state.Running, false},
{"tcp://1.2.3.4:2376", state.Stopped, false},
{"tcp://1.2.3.4:2376", state.Running, true},
{"tcp://1.2.3.4:3376", state.Running, false},
}
for _, c := range cases {
os.Unsetenv("DOCKER_HOST")
if c.dockerHost != "" {
os.Setenv("DOCKER_HOST", c.dockerHost)
}
actual := isActive(c.state, "tcp://1.2.3.4:2376")
assert.Equal(t, c.expected, actual)
}
}
func TestIsSwarmActive(t *testing.T) {
cases := []struct {
dockerHost string
state state.State
isMaster bool
expected bool
}{
{"", state.Running, false, false},
{"tcp://5.6.7.8:3376", state.Running, true, false},
{"tcp://1.2.3.4:3376", state.Stopped, true, false},
{"tcp://1.2.3.4:3376", state.Running, true, true},
{"tcp://1.2.3.4:3376", state.Running, false, false},
{"tcp://1.2.3.4:2376", state.Running, true, false},
}
for _, c := range cases {
os.Unsetenv("DOCKER_HOST")
if c.dockerHost != "" {
os.Setenv("DOCKER_HOST", c.dockerHost)
}
actual := isSwarmActive(c.state, "tcp://1.2.3.4:2376", c.isMaster, "tcp://0.0.0.0:3376")
assert.Equal(t, c.expected, actual)
}
}
func TestGetHostStateTimeout(t *testing.T) {
hosts := []*host.Host{
{
Name: "foo",
Driver: &fakedriver.Driver{
MockState: state.Timeout,
},
},
}
hostItem := getHostListItems(hosts, nil, time.Millisecond)[0]
assert.Equal(t, "foo", hostItem.Name)
assert.Equal(t, state.Timeout, hostItem.State)
assert.Equal(t, "Driver", hostItem.DriverName)
assert.Equal(t, time.Millisecond, hostItem.ResponseTime)
}
func TestGetHostStateError(t *testing.T) {
hosts := []*host.Host{
{
Name: "foo",
Driver: &fakedriver.Driver{
MockState: state.Error,
},
},
}
hostItem := getHostListItems(hosts, nil, 10*time.Second)[0]
assert.Equal(t, "foo", hostItem.Name)
assert.Equal(t, state.Error, hostItem.State)
assert.Equal(t, "Driver", hostItem.DriverName)
assert.Empty(t, hostItem.URL)
assert.Equal(t, "Unable to get ip", hostItem.Error)
assert.Nil(t, hostItem.SwarmOptions)
}
func TestGetSomeHostInError(t *testing.T) {
defer func(versioner mcndockerclient.DockerVersioner) { mcndockerclient.CurrentDockerVersioner = versioner }(mcndockerclient.CurrentDockerVersioner)
mcndockerclient.CurrentDockerVersioner = &mcndockerclient.FakeDockerVersioner{Version: "1.9"}
hosts := []*host.Host{
{
Name: "foo",
Driver: &fakedriver.Driver{
MockState: state.Running,
},
},
}
hostsInError := map[string]error{
"bar": errors.New("invalid memory address or nil pointer dereference"),
}
hostItems := getHostListItems(hosts, hostsInError, 10*time.Second)
assert.Equal(t, 2, len(hostItems))
hostItem := hostItems[0]
assert.Equal(t, "bar", hostItem.Name)
assert.Equal(t, state.Error, hostItem.State)
assert.Equal(t, "not found", hostItem.DriverName)
assert.Empty(t, hostItem.URL)
assert.Equal(t, "invalid memory address or nil pointer dereference", hostItem.Error)
assert.Nil(t, hostItem.SwarmOptions)
hostItem = hostItems[1]
assert.Equal(t, "foo", hostItem.Name)
assert.Equal(t, state.Running, hostItem.State)
}
func TestOnErrorWithMultilineComment(t *testing.T) {
err := errors.New("MissingParameter: The request must contain the parameter InstanceId\n status code: 400, request id:")
itemInError := newHostListItemInError("foo", err)
assert.Equal(t, itemInError.Error, "MissingParameter: The request must contain the parameter InstanceId status code: 400, request id:")
}

27
commands/mcndirs/utils.go Normal file
View File

@ -0,0 +1,27 @@
package mcndirs
import (
"os"
"path/filepath"
"github.com/docker/machine/libmachine/mcnutils"
)
var (
BaseDir = os.Getenv("MACHINE_STORAGE_PATH")
)
func GetBaseDir() string {
if BaseDir == "" {
BaseDir = filepath.Join(mcnutils.GetHomeDir(), ".docker", "machine")
}
return BaseDir
}
func GetMachineDir() string {
return filepath.Join(GetBaseDir(), "machines")
}
func GetMachineCertDir() string {
return filepath.Join(GetBaseDir(), "certs")
}

View File

@ -0,0 +1,70 @@
package mcndirs
import (
"path"
"strings"
"testing"
"github.com/docker/machine/libmachine/mcnutils"
)
func TestGetBaseDir(t *testing.T) {
// reset any override env var
BaseDir = ""
homeDir := mcnutils.GetHomeDir()
baseDir := GetBaseDir()
if strings.Index(baseDir, homeDir) != 0 {
t.Fatalf("expected base dir with prefix %s; received %s", homeDir, baseDir)
}
}
func TestGetCustomBaseDir(t *testing.T) {
root := "/tmp"
BaseDir = root
baseDir := GetBaseDir()
if strings.Index(baseDir, root) != 0 {
t.Fatalf("expected base dir with prefix %s; received %s", root, baseDir)
}
BaseDir = ""
}
func TestGetMachineDir(t *testing.T) {
root := "/tmp"
BaseDir = root
machineDir := GetMachineDir()
if strings.Index(machineDir, root) != 0 {
t.Fatalf("expected machine dir with prefix %s; received %s", root, machineDir)
}
path, filename := path.Split(machineDir)
if strings.Index(path, root) != 0 {
t.Fatalf("expected base path of %s; received %s", root, path)
}
if filename != "machines" {
t.Fatalf("expected machine dir \"machines\"; received %s", filename)
}
BaseDir = ""
}
func TestGetMachineCertDir(t *testing.T) {
root := "/tmp"
BaseDir = root
clientDir := GetMachineCertDir()
if strings.Index(clientDir, root) != 0 {
t.Fatalf("expected machine client cert dir with prefix %s; received %s", root, clientDir)
}
path, filename := path.Split(clientDir)
if strings.Index(path, root) != 0 {
t.Fatalf("expected base path of %s; received %s", root, path)
}
if filename != "certs" {
t.Fatalf("expected machine client dir \"certs\"; received %s", filename)
}
BaseDir = ""
}

7
commands/provision.go Normal file
View File

@ -0,0 +1,7 @@
package commands
import "github.com/docker/machine/libmachine"
func cmdProvision(c CommandLine, api libmachine.API) error {
return runAction("provision", c, api)
}

View File

@ -0,0 +1,67 @@
package commands
import (
"testing"
"github.com/docker/machine/commands/commandstest"
"github.com/docker/machine/drivers/fakedriver"
"github.com/docker/machine/libmachine"
"github.com/docker/machine/libmachine/auth"
"github.com/docker/machine/libmachine/engine"
"github.com/docker/machine/libmachine/host"
"github.com/docker/machine/libmachine/libmachinetest"
"github.com/docker/machine/libmachine/provision"
"github.com/docker/machine/libmachine/swarm"
"github.com/stretchr/testify/assert"
)
func TestCmdProvision(t *testing.T) {
testCases := []struct {
commandLine CommandLine
api libmachine.API
expectedErr error
}{
{
commandLine: &commandstest.FakeCommandLine{
CliArgs: []string{"foo", "bar"},
},
api: &libmachinetest.FakeAPI{
Hosts: []*host.Host{
{
Name: "foo",
Driver: &fakedriver.Driver{},
HostOptions: &host.Options{
EngineOptions: &engine.Options{},
AuthOptions: &auth.Options{},
SwarmOptions: &swarm.Options{},
},
},
{
Name: "bar",
Driver: &fakedriver.Driver{},
HostOptions: &host.Options{
EngineOptions: &engine.Options{},
AuthOptions: &auth.Options{},
SwarmOptions: &swarm.Options{},
},
},
},
},
expectedErr: nil,
},
}
provision.SetDetector(&provision.FakeDetector{
Provisioner: provision.NewFakeProvisioner(nil),
})
// fakeprovisioner always returns "true" for compatible host, so we
// just need to register it.
provision.Register("fakeprovisioner", &provision.RegisteredProvisioner{
New: provision.NewFakeProvisioner,
})
for _, tc := range testCases {
assert.Equal(t, tc.expectedErr, cmdProvision(tc.commandLine, tc.api))
}
}

View File

@ -0,0 +1,23 @@
package commands
import (
"github.com/docker/machine/libmachine"
"github.com/docker/machine/libmachine/log"
)
func cmdRegenerateCerts(c CommandLine, api libmachine.API) error {
if !c.Bool("force") {
ok, err := confirmInput("Regenerate TLS machine certs? Warning: this is irreversible.")
if err != nil {
return err
}
if !ok {
return nil
}
}
log.Infof("Regenerating TLS certificates")
return runAction("configureAuth", c, api)
}

16
commands/restart.go Normal file
View File

@ -0,0 +1,16 @@
package commands
import (
"github.com/docker/machine/libmachine"
"github.com/docker/machine/libmachine/log"
)
func cmdRestart(c CommandLine, api libmachine.API) error {
if err := runAction("restart", c, api); err != nil {
return err
}
log.Info("Restarted machines may have new IP addresses. You may need to re-run the `docker-machine env` command.")
return nil
}

88
commands/rm.go Normal file
View File

@ -0,0 +1,88 @@
package commands
import (
"fmt"
"strings"
"errors"
"github.com/docker/machine/libmachine"
"github.com/docker/machine/libmachine/log"
)
func cmdRm(c CommandLine, api libmachine.API) error {
if len(c.Args()) == 0 {
c.ShowHelp()
return ErrNoMachineSpecified
}
log.Info(fmt.Sprintf("About to remove %s", strings.Join(c.Args(), ", ")))
force := c.Bool("force")
confirm := c.Bool("y")
var errorOccured []string
if !userConfirm(confirm, force) {
return nil
}
for _, hostName := range c.Args() {
err := removeRemoteMachine(hostName, api)
if err != nil {
errorOccured = collectError(fmt.Sprintf("Error removing host %q: %s", hostName, err), force, errorOccured)
}
if err == nil || force {
removeErr := removeLocalMachine(hostName, api)
if removeErr != nil {
errorOccured = collectError(fmt.Sprintf("Can't remove \"%s\"", hostName), force, errorOccured)
} else {
log.Infof("Successfully removed %s", hostName)
}
}
}
if len(errorOccured) > 0 && !force {
return errors.New(strings.Join(errorOccured, "\n"))
}
return nil
}
func userConfirm(confirm bool, force bool) bool {
if confirm || force {
return true
}
sure, err := confirmInput(fmt.Sprintf("Are you sure?"))
if err != nil {
return false
}
return sure
}
func removeRemoteMachine(hostName string, api libmachine.API) error {
currentHost, loaderr := api.Load(hostName)
if loaderr != nil {
return loaderr
}
return currentHost.Driver.Remove()
}
func removeLocalMachine(hostName string, api libmachine.API) error {
exist, _ := api.Exists(hostName)
if !exist {
return errors.New(hostName + " does not exist.")
}
return api.Remove(hostName)
}
func collectError(message string, force bool, errorOccured []string) []string {
if force {
log.Error(message)
}
return append(errorOccured, message)
}

233
commands/rm_test.go Normal file
View File

@ -0,0 +1,233 @@
package commands
import (
"testing"
"errors"
"github.com/docker/machine/commands/commandstest"
"github.com/docker/machine/drivers/fakedriver"
"github.com/docker/machine/libmachine/host"
"github.com/docker/machine/libmachine/libmachinetest"
"github.com/stretchr/testify/assert"
)
func TestCmdRmMissingMachineName(t *testing.T) {
commandLine := &commandstest.FakeCommandLine{}
api := &libmachinetest.FakeAPI{}
err := cmdRm(commandLine, api)
assert.Equal(t, ErrNoMachineSpecified, err)
assert.True(t, commandLine.HelpShown)
}
func TestCmdRm(t *testing.T) {
commandLine := &commandstest.FakeCommandLine{
CliArgs: []string{"machineToRemove1", "machineToRemove2"},
LocalFlags: &commandstest.FakeFlagger{
Data: map[string]interface{}{
"y": true,
},
},
}
api := &libmachinetest.FakeAPI{
Hosts: []*host.Host{
{
Name: "machineToRemove1",
Driver: &fakedriver.Driver{},
},
{
Name: "machineToRemove2",
Driver: &fakedriver.Driver{},
},
{
Name: "machine",
Driver: &fakedriver.Driver{},
},
},
}
err := cmdRm(commandLine, api)
assert.NoError(t, err)
assert.False(t, libmachinetest.Exists(api, "machineToRemove1"))
assert.False(t, libmachinetest.Exists(api, "machineToRemove2"))
assert.True(t, libmachinetest.Exists(api, "machine"))
}
func TestCmdRmforcefully(t *testing.T) {
commandLine := &commandstest.FakeCommandLine{
CliArgs: []string{"machineToRemove1", "machineToRemove2"},
LocalFlags: &commandstest.FakeFlagger{
Data: map[string]interface{}{
"force": true,
},
},
}
api := &libmachinetest.FakeAPI{
Hosts: []*host.Host{
{
Name: "machineToRemove1",
Driver: &fakedriver.Driver{},
},
{
Name: "machineToRemove2",
Driver: &fakedriver.Driver{},
},
},
}
err := cmdRm(commandLine, api)
assert.NoError(t, err)
assert.False(t, libmachinetest.Exists(api, "machineToRemove1"))
assert.False(t, libmachinetest.Exists(api, "machineToRemove2"))
}
func TestCmdRmforceDoesAutoConfirm(t *testing.T) {
commandLine := &commandstest.FakeCommandLine{
CliArgs: []string{"machineToRemove1", "machineToRemove2"},
LocalFlags: &commandstest.FakeFlagger{
Data: map[string]interface{}{
"y": false,
"force": true,
},
},
}
api := &libmachinetest.FakeAPI{
Hosts: []*host.Host{
{
Name: "machineToRemove1",
Driver: &fakedriver.Driver{},
},
{
Name: "machineToRemove2",
Driver: &fakedriver.Driver{},
},
},
}
err := cmdRm(commandLine, api)
assert.NoError(t, err)
assert.False(t, libmachinetest.Exists(api, "machineToRemove1"))
assert.False(t, libmachinetest.Exists(api, "machineToRemove2"))
}
func TestCmdRmforceConfirmUnset(t *testing.T) {
commandLine := &commandstest.FakeCommandLine{
CliArgs: []string{"machineToRemove1"},
LocalFlags: &commandstest.FakeFlagger{
Data: map[string]interface{}{
"y": false,
"force": false,
},
},
}
api := &libmachinetest.FakeAPI{
Hosts: []*host.Host{
{
Name: "machineToRemove1",
Driver: &fakedriver.Driver{},
},
},
}
err := cmdRm(commandLine, api)
assert.NoError(t, err)
assert.True(t, libmachinetest.Exists(api, "machineToRemove1"))
}
type DriverWithRemoveWhichFail struct {
fakedriver.Driver
}
func (d *DriverWithRemoveWhichFail) Remove() error {
return errors.New("unknown error")
}
func TestDontStopWhenADriverRemovalFails(t *testing.T) {
commandLine := &commandstest.FakeCommandLine{
CliArgs: []string{"machineToRemove1", "machineToRemove2", "machineToRemove3"},
LocalFlags: &commandstest.FakeFlagger{
Data: map[string]interface{}{
"y": true,
},
},
}
api := &libmachinetest.FakeAPI{
Hosts: []*host.Host{
{
Name: "machineToRemove1",
Driver: &fakedriver.Driver{},
},
{
Name: "machineToRemove2",
Driver: &DriverWithRemoveWhichFail{},
},
{
Name: "machineToRemove3",
Driver: &fakedriver.Driver{},
},
},
}
err := cmdRm(commandLine, api)
assert.EqualError(t, err, "Error removing host \"machineToRemove2\": unknown error")
assert.False(t, libmachinetest.Exists(api, "machineToRemove1"))
assert.True(t, libmachinetest.Exists(api, "machineToRemove2"))
assert.False(t, libmachinetest.Exists(api, "machineToRemove3"))
}
func TestForceRemoveEvenWhenItFails(t *testing.T) {
commandLine := &commandstest.FakeCommandLine{
CliArgs: []string{"machineToRemove1"},
LocalFlags: &commandstest.FakeFlagger{
Data: map[string]interface{}{
"y": true,
"force": true,
},
},
}
api := &libmachinetest.FakeAPI{
Hosts: []*host.Host{
{
Name: "machineToRemove1",
Driver: &DriverWithRemoveWhichFail{},
},
},
}
err := cmdRm(commandLine, api)
assert.NoError(t, err)
assert.False(t, libmachinetest.Exists(api, "machineToRemove1"))
}
func TestDontRemoveMachineIsRemovalFailsAndNotForced(t *testing.T) {
commandLine := &commandstest.FakeCommandLine{
CliArgs: []string{"machineToRemove1"},
LocalFlags: &commandstest.FakeFlagger{
Data: map[string]interface{}{
"y": true,
"force": false,
},
},
}
api := &libmachinetest.FakeAPI{
Hosts: []*host.Host{
{
Name: "machineToRemove1",
Driver: &DriverWithRemoveWhichFail{},
},
},
}
err := cmdRm(commandLine, api)
assert.EqualError(t, err, "Error removing host \"machineToRemove1\": unknown error")
assert.True(t, libmachinetest.Exists(api, "machineToRemove1"))
}

170
commands/scp.go Normal file
View File

@ -0,0 +1,170 @@
package commands
import (
"errors"
"fmt"
"os"
"os/exec"
"strings"
"github.com/docker/machine/libmachine"
"github.com/docker/machine/libmachine/log"
"github.com/docker/machine/libmachine/persist"
)
var (
errWrongNumberArguments = errors.New("Improper number of arguments")
// TODO: possibly move this to ssh package
baseSSHArgs = []string{
"-o", "IdentitiesOnly=yes",
"-o", "StrictHostKeyChecking=no",
"-o", "UserKnownHostsFile=/dev/null",
"-o", "LogLevel=quiet", // suppress "Warning: Permanently added '[localhost]:2022' (ECDSA) to the list of known hosts."
}
)
// HostInfo gives the mandatory information to connect to a host.
type HostInfo interface {
GetMachineName() string
GetIP() (string, error)
GetSSHUsername() string
GetSSHKeyPath() string
}
// HostInfoLoader loads host information.
type HostInfoLoader interface {
load(name string) (HostInfo, error)
}
type storeHostInfoLoader struct {
store persist.Store
}
func (s *storeHostInfoLoader) load(name string) (HostInfo, error) {
host, err := s.store.Load(name)
if err != nil {
return nil, fmt.Errorf("Error loading host: %s", err)
}
return host.Driver, nil
}
func cmdScp(c CommandLine, api libmachine.API) error {
args := c.Args()
if len(args) != 2 {
c.ShowHelp()
return errWrongNumberArguments
}
src := args[0]
dest := args[1]
hostInfoLoader := &storeHostInfoLoader{api}
cmd, err := getScpCmd(src, dest, c.Bool("recursive"), hostInfoLoader)
if err != nil {
return err
}
return runCmdWithStdIo(*cmd)
}
func getScpCmd(src, dest string, recursive bool, hostInfoLoader HostInfoLoader) (*exec.Cmd, error) {
cmdPath, err := exec.LookPath("scp")
if err != nil {
return nil, errors.New("Error: You must have a copy of the scp binary locally to use the scp feature.")
}
srcHost, srcPath, srcOpts, err := getInfoForScpArg(src, hostInfoLoader)
if err != nil {
return nil, err
}
destHost, destPath, destOpts, err := getInfoForScpArg(dest, hostInfoLoader)
if err != nil {
return nil, err
}
// TODO: Check that "-3" flag is available in user's version of scp.
// It is on every system I've checked, but the manual mentioned it's "newer"
sshArgs := baseSSHArgs
sshArgs = append(sshArgs, "-3")
if recursive {
sshArgs = append(sshArgs, "-r")
}
// Append needed -i / private key flags to command.
sshArgs = append(sshArgs, srcOpts...)
sshArgs = append(sshArgs, destOpts...)
// Append actual arguments for the scp command (i.e. docker@<ip>:/path)
locationArg, err := generateLocationArg(srcHost, srcPath)
if err != nil {
return nil, err
}
sshArgs = append(sshArgs, locationArg)
locationArg, err = generateLocationArg(destHost, destPath)
if err != nil {
return nil, err
}
sshArgs = append(sshArgs, locationArg)
cmd := exec.Command(cmdPath, sshArgs...)
log.Debug(*cmd)
return cmd, nil
}
func getInfoForScpArg(hostAndPath string, hostInfoLoader HostInfoLoader) (HostInfo, string, []string, error) {
// Local path. e.g. "/tmp/foo"
if !strings.Contains(hostAndPath, ":") {
return nil, hostAndPath, nil, nil
}
// Path with hostname. e.g. "hostname:/usr/bin/cmatrix"
parts := strings.SplitN(hostAndPath, ":", 2)
hostName := parts[0]
path := parts[1]
if hostName == "localhost" {
return nil, path, nil, nil
}
// Remote path
hostInfo, err := hostInfoLoader.load(hostName)
if err != nil {
return nil, "", nil, fmt.Errorf("Error loading host: %s", err)
}
args := []string{}
if hostInfo.GetSSHKeyPath() != "" {
args = append(args, "-i", hostInfo.GetSSHKeyPath())
}
return hostInfo, path, args, nil
}
func generateLocationArg(hostInfo HostInfo, path string) (string, error) {
if hostInfo == nil {
return path, nil
}
ip, err := hostInfo.GetIP()
if err != nil {
return "", err
}
location := fmt.Sprintf("%s@%s:%s", hostInfo.GetSSHUsername(), ip, path)
return location, nil
}
func runCmdWithStdIo(cmd exec.Cmd) error {
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}

136
commands/scp_test.go Normal file
View File

@ -0,0 +1,136 @@
package commands
import (
"os/exec"
"testing"
"github.com/stretchr/testify/assert"
)
type MockHostInfo struct {
name string
ip string
sshUsername string
sshKeyPath string
}
func (h *MockHostInfo) GetMachineName() string {
return h.name
}
func (h *MockHostInfo) GetIP() (string, error) {
return h.ip, nil
}
func (h *MockHostInfo) GetSSHUsername() string {
return h.sshUsername
}
func (h *MockHostInfo) GetSSHKeyPath() string {
return h.sshKeyPath
}
type MockHostInfoLoader struct {
hostInfo MockHostInfo
}
func (l *MockHostInfoLoader) load(name string) (HostInfo, error) {
info := l.hostInfo
info.name = name
return &info, nil
}
func TestGetInfoForLocalScpArg(t *testing.T) {
host, path, opts, err := getInfoForScpArg("/tmp/foo", nil)
assert.Nil(t, host)
assert.Equal(t, "/tmp/foo", path)
assert.Nil(t, opts)
assert.NoError(t, err)
host, path, opts, err = getInfoForScpArg("localhost:C:\\path", nil)
assert.Nil(t, host)
assert.Equal(t, "C:\\path", path)
assert.Nil(t, opts)
assert.NoError(t, err)
}
func TestGetInfoForRemoteScpArg(t *testing.T) {
hostInfoLoader := MockHostInfoLoader{MockHostInfo{
sshKeyPath: "/fake/keypath/id_rsa",
}}
host, path, opts, err := getInfoForScpArg("myfunhost:/home/docker/foo", &hostInfoLoader)
assert.Equal(t, "myfunhost", host.GetMachineName())
assert.Equal(t, "/home/docker/foo", path)
assert.Equal(t, []string{"-i", "/fake/keypath/id_rsa"}, opts)
assert.NoError(t, err)
host, path, opts, err = getInfoForScpArg("myfunhost:C:\\path", &hostInfoLoader)
assert.Equal(t, "myfunhost", host.GetMachineName())
assert.Equal(t, "C:\\path", path)
assert.NoError(t, err)
}
func TestHostLocation(t *testing.T) {
arg, err := generateLocationArg(nil, "/home/docker/foo")
assert.Equal(t, "/home/docker/foo", arg)
assert.NoError(t, err)
}
func TestRemoteLocation(t *testing.T) {
hostInfo := MockHostInfo{
ip: "12.34.56.78",
sshUsername: "root",
}
arg, err := generateLocationArg(&hostInfo, "/home/docker/foo")
assert.Equal(t, "root@12.34.56.78:/home/docker/foo", arg)
assert.NoError(t, err)
}
func TestGetScpCmd(t *testing.T) {
hostInfoLoader := MockHostInfoLoader{MockHostInfo{
ip: "12.34.56.78",
sshUsername: "root",
sshKeyPath: "/fake/keypath/id_rsa",
}}
cmd, err := getScpCmd("/tmp/foo", "myfunhost:/home/docker/foo", true, &hostInfoLoader)
expectedArgs := append(
baseSSHArgs,
"-3",
"-r",
"-i",
"/fake/keypath/id_rsa",
"/tmp/foo",
"root@12.34.56.78:/home/docker/foo",
)
expectedCmd := exec.Command("/usr/bin/scp", expectedArgs...)
assert.Equal(t, expectedCmd, cmd)
assert.NoError(t, err)
}
func TestGetScpCmdWithoutSshKey(t *testing.T) {
hostInfoLoader := MockHostInfoLoader{MockHostInfo{
ip: "1.2.3.4",
sshUsername: "user",
}}
cmd, err := getScpCmd("/tmp/foo", "myfunhost:/home/docker/foo", true, &hostInfoLoader)
expectedArgs := append(
baseSSHArgs,
"-3",
"-r",
"/tmp/foo",
"user@1.2.3.4:/home/docker/foo",
)
expectedCmd := exec.Command("/usr/bin/scp", expectedArgs...)
assert.Equal(t, expectedCmd, cmd)
assert.NoError(t, err)
}

51
commands/ssh.go Normal file
View File

@ -0,0 +1,51 @@
package commands
import (
"fmt"
"github.com/docker/machine/libmachine"
"github.com/docker/machine/libmachine/state"
)
type errStateInvalidForSSH struct {
HostName string
}
func (e errStateInvalidForSSH) Error() string {
return fmt.Sprintf("Error: Cannot run SSH command: Host %q is not running", e.HostName)
}
func cmdSSH(c CommandLine, api libmachine.API) error {
// Check for help flag -- Needed due to SkipFlagParsing
firstArg := c.Args().First()
if firstArg == "-help" || firstArg == "--help" || firstArg == "-h" {
c.ShowHelp()
return nil
}
target, err := targetHost(c, api)
if err != nil {
return err
}
host, err := api.Load(target)
if err != nil {
return err
}
currentState, err := host.Driver.GetState()
if err != nil {
return err
}
if currentState != state.Running {
return errStateInvalidForSSH{host.Name}
}
client, err := host.CreateSSHClient()
if err != nil {
return err
}
return client.Shell(c.Args().Tail()...)
}

111
commands/ssh_test.go Normal file
View File

@ -0,0 +1,111 @@
package commands
import (
"testing"
"github.com/docker/machine/commands/commandstest"
"github.com/docker/machine/drivers/fakedriver"
"github.com/docker/machine/libmachine"
"github.com/docker/machine/libmachine/drivers"
"github.com/docker/machine/libmachine/host"
"github.com/docker/machine/libmachine/libmachinetest"
"github.com/docker/machine/libmachine/ssh"
"github.com/docker/machine/libmachine/ssh/sshtest"
"github.com/docker/machine/libmachine/state"
"github.com/stretchr/testify/assert"
)
type FakeSSHClientCreator struct {
client ssh.Client
}
func (fsc *FakeSSHClientCreator) CreateSSHClient(d drivers.Driver) (ssh.Client, error) {
if fsc.client == nil {
fsc.client = &sshtest.FakeClient{}
}
return fsc.client, nil
}
func TestCmdSSH(t *testing.T) {
testCases := []struct {
commandLine CommandLine
api libmachine.API
expectedErr error
helpShown bool
clientCreator host.SSHClientCreator
expectedShell []string
}{
{
commandLine: &commandstest.FakeCommandLine{
CliArgs: []string{"-h"},
},
api: &libmachinetest.FakeAPI{},
expectedErr: nil,
helpShown: true,
},
{
commandLine: &commandstest.FakeCommandLine{
CliArgs: []string{"--help"},
},
api: &libmachinetest.FakeAPI{},
expectedErr: nil,
helpShown: true,
},
{
commandLine: &commandstest.FakeCommandLine{
CliArgs: []string{},
},
api: &libmachinetest.FakeAPI{},
expectedErr: ErrNoDefault,
},
{
commandLine: &commandstest.FakeCommandLine{
CliArgs: []string{"default", "df", "-h"},
},
api: &libmachinetest.FakeAPI{
Hosts: []*host.Host{
{
Name: "default",
Driver: &fakedriver.Driver{
MockState: state.Running,
},
},
},
},
expectedErr: nil,
clientCreator: &FakeSSHClientCreator{},
expectedShell: []string{"df", "-h"},
},
{
commandLine: &commandstest.FakeCommandLine{
CliArgs: []string{"default"},
},
api: &libmachinetest.FakeAPI{
Hosts: []*host.Host{
{
Name: "default",
Driver: &fakedriver.Driver{
MockState: state.Stopped,
},
},
},
},
expectedErr: errStateInvalidForSSH{"default"},
},
}
for _, tc := range testCases {
host.SetSSHClientCreator(tc.clientCreator)
err := cmdSSH(tc.commandLine, tc.api)
assert.Equal(t, err, tc.expectedErr)
if fcl, ok := tc.commandLine.(*commandstest.FakeCommandLine); ok {
assert.Equal(t, tc.helpShown, fcl.HelpShown)
}
if fcc, ok := tc.clientCreator.(*FakeSSHClientCreator); ok {
assert.Equal(t, tc.expectedShell, fcc.client.(*sshtest.FakeClient).ActivatedShell)
}
}
}

16
commands/start.go Normal file
View File

@ -0,0 +1,16 @@
package commands
import (
"github.com/docker/machine/libmachine"
"github.com/docker/machine/libmachine/log"
)
func cmdStart(c CommandLine, api libmachine.API) error {
if err := runAction("start", c, api); err != nil {
return err
}
log.Info("Started machines may have new IP addresses. You may need to re-run the `docker-machine env` command.")
return nil
}

31
commands/status.go Normal file
View File

@ -0,0 +1,31 @@
package commands
import (
"github.com/docker/machine/libmachine"
"github.com/docker/machine/libmachine/log"
)
func cmdStatus(c CommandLine, api libmachine.API) error {
if len(c.Args()) > 1 {
return ErrExpectedOneMachine
}
target, err := targetHost(c, api)
if err != nil {
return err
}
host, err := api.Load(target)
if err != nil {
return err
}
currentState, err := host.Driver.GetState()
if err != nil {
log.Errorf("error getting state for host %s: %s", host.Name, err)
}
log.Info(currentState)
return nil
}

7
commands/stop.go Normal file
View File

@ -0,0 +1,7 @@
package commands
import "github.com/docker/machine/libmachine"
func cmdStop(c CommandLine, api libmachine.API) error {
return runAction("stop", c, api)
}

103
commands/stop_test.go Normal file
View File

@ -0,0 +1,103 @@
package commands
import (
"testing"
"github.com/docker/machine/commands/commandstest"
"github.com/docker/machine/drivers/fakedriver"
"github.com/docker/machine/libmachine"
"github.com/docker/machine/libmachine/host"
"github.com/docker/machine/libmachine/libmachinetest"
"github.com/docker/machine/libmachine/state"
"github.com/stretchr/testify/assert"
)
func TestCmdStop(t *testing.T) {
testCases := []struct {
commandLine CommandLine
api libmachine.API
expectedErr error
expectedStates map[string]state.State
}{
{
commandLine: &commandstest.FakeCommandLine{
CliArgs: []string{},
},
api: &libmachinetest.FakeAPI{
Hosts: []*host.Host{
{
Name: "default",
Driver: &fakedriver.Driver{
MockState: state.Running,
},
},
},
},
expectedErr: nil,
expectedStates: map[string]state.State{
"default": state.Stopped,
},
},
{
commandLine: &commandstest.FakeCommandLine{
CliArgs: []string{},
},
api: &libmachinetest.FakeAPI{
Hosts: []*host.Host{
{
Name: "foobar",
Driver: &fakedriver.Driver{
MockState: state.Running,
},
},
},
},
expectedErr: ErrNoDefault,
expectedStates: map[string]state.State{
"foobar": state.Running,
},
},
{
commandLine: &commandstest.FakeCommandLine{
CliArgs: []string{"machineToStop1", "machineToStop2"},
},
api: &libmachinetest.FakeAPI{
Hosts: []*host.Host{
{
Name: "machineToStop1",
Driver: &fakedriver.Driver{
MockState: state.Running,
},
},
{
Name: "machineToStop2",
Driver: &fakedriver.Driver{
MockState: state.Running,
},
},
{
Name: "machine",
Driver: &fakedriver.Driver{
MockState: state.Running,
},
},
},
},
expectedErr: nil,
expectedStates: map[string]state.State{
"machineToStop1": state.Stopped,
"machineToStop2": state.Stopped,
"machine": state.Running,
},
},
}
for _, tc := range testCases {
err := cmdStop(tc.commandLine, tc.api)
assert.Equal(t, tc.expectedErr, err)
for hostName, expectedState := range tc.expectedStates {
assert.Equal(t, expectedState, libmachinetest.State(tc.api, hostName))
}
}
}

7
commands/upgrade.go Normal file
View File

@ -0,0 +1,7 @@
package commands
import "github.com/docker/machine/libmachine"
func cmdUpgrade(c CommandLine, api libmachine.API) error {
return runAction("upgrade", c, api)
}

32
commands/url.go Normal file
View File

@ -0,0 +1,32 @@
package commands
import (
"fmt"
"github.com/docker/machine/libmachine"
)
func cmdURL(c CommandLine, api libmachine.API) error {
if len(c.Args()) > 1 {
return ErrExpectedOneMachine
}
target, err := targetHost(c, api)
if err != nil {
return err
}
host, err := api.Load(target)
if err != nil {
return err
}
url, err := host.URL()
if err != nil {
return err
}
fmt.Println(url)
return nil
}

57
commands/url_test.go Normal file
View File

@ -0,0 +1,57 @@
package commands
import (
"testing"
"github.com/docker/machine/commands/commandstest"
"github.com/docker/machine/drivers/fakedriver"
"github.com/docker/machine/libmachine/host"
"github.com/docker/machine/libmachine/libmachinetest"
"github.com/docker/machine/libmachine/state"
"github.com/stretchr/testify/assert"
)
func TestCmdURLMissingMachineName(t *testing.T) {
commandLine := &commandstest.FakeCommandLine{}
api := &libmachinetest.FakeAPI{}
err := cmdURL(commandLine, api)
assert.Equal(t, ErrNoDefault, err)
}
func TestCmdURLTooManyNames(t *testing.T) {
commandLine := &commandstest.FakeCommandLine{
CliArgs: []string{"machineToRemove1", "machineToRemove2"},
}
api := &libmachinetest.FakeAPI{}
err := cmdURL(commandLine, api)
assert.EqualError(t, err, "Error: Expected one machine name as an argument")
}
func TestCmdURL(t *testing.T) {
commandLine := &commandstest.FakeCommandLine{
CliArgs: []string{"machine"},
}
api := &libmachinetest.FakeAPI{
Hosts: []*host.Host{
{
Name: "machine",
Driver: &fakedriver.Driver{
MockState: state.Running,
MockIP: "120.0.0.1",
},
},
},
}
stdoutGetter := commandstest.NewStdoutGetter()
defer stdoutGetter.Stop()
err := cmdURL(commandLine, api)
assert.NoError(t, err)
assert.Equal(t, "tcp://120.0.0.1:2376\n", stdoutGetter.Output())
}

40
commands/version.go Normal file
View File

@ -0,0 +1,40 @@
package commands
import (
"fmt"
"io"
"os"
"github.com/docker/machine/libmachine"
"github.com/docker/machine/libmachine/mcndockerclient"
)
func cmdVersion(c CommandLine, api libmachine.API) error {
return printVersion(c, api, os.Stdout)
}
func printVersion(c CommandLine, api libmachine.API, out io.Writer) error {
if len(c.Args()) == 0 {
c.ShowVersion()
return nil
}
if len(c.Args()) != 1 {
return ErrExpectedOneMachine
}
host, err := api.Load(c.Args().First())
if err != nil {
return err
}
version, err := mcndockerclient.DockerVersion(host)
if err != nil {
return err
}
fmt.Fprintln(out, version)
return nil
}

89
commands/version_test.go Normal file
View File

@ -0,0 +1,89 @@
package commands
import (
"errors"
"testing"
"bytes"
"github.com/docker/machine/commands/commandstest"
"github.com/docker/machine/libmachine/host"
"github.com/docker/machine/libmachine/libmachinetest"
"github.com/docker/machine/libmachine/mcndockerclient"
"github.com/stretchr/testify/assert"
)
func TestCmdVersion(t *testing.T) {
commandLine := &commandstest.FakeCommandLine{}
api := &libmachinetest.FakeAPI{}
err := cmdVersion(commandLine, api)
assert.True(t, commandLine.VersionShown)
assert.NoError(t, err)
}
func TestCmdVersionTooManyNames(t *testing.T) {
commandLine := &commandstest.FakeCommandLine{
CliArgs: []string{"machine1", "machine2"},
}
api := &libmachinetest.FakeAPI{}
err := cmdVersion(commandLine, api)
assert.EqualError(t, err, "Error: Expected one machine name as an argument")
}
func TestCmdVersionNotFound(t *testing.T) {
commandLine := &commandstest.FakeCommandLine{
CliArgs: []string{"unknown"},
}
api := &libmachinetest.FakeAPI{}
err := cmdVersion(commandLine, api)
assert.EqualError(t, err, `Host does not exist: "unknown"`)
}
func TestCmdVersionOnHost(t *testing.T) {
defer func(versioner mcndockerclient.DockerVersioner) { mcndockerclient.CurrentDockerVersioner = versioner }(mcndockerclient.CurrentDockerVersioner)
mcndockerclient.CurrentDockerVersioner = &mcndockerclient.FakeDockerVersioner{Version: "1.9.1"}
commandLine := &commandstest.FakeCommandLine{
CliArgs: []string{"machine"},
}
api := &libmachinetest.FakeAPI{
Hosts: []*host.Host{
{
Name: "machine",
},
},
}
out := &bytes.Buffer{}
err := printVersion(commandLine, api, out)
assert.NoError(t, err)
assert.Equal(t, "1.9.1\n", out.String())
}
func TestCmdVersionFailure(t *testing.T) {
defer func(versioner mcndockerclient.DockerVersioner) { mcndockerclient.CurrentDockerVersioner = versioner }(mcndockerclient.CurrentDockerVersioner)
mcndockerclient.CurrentDockerVersioner = &mcndockerclient.FakeDockerVersioner{Err: errors.New("connection failure")}
commandLine := &commandstest.FakeCommandLine{
CliArgs: []string{"machine"},
}
api := &libmachinetest.FakeAPI{
Hosts: []*host.Host{
{
Name: "machine",
},
},
}
out := &bytes.Buffer{}
err := printVersion(commandLine, api, out)
assert.EqualError(t, err, "connection failure")
}

1
contrib/completion/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
!docker-machine*

View File

@ -0,0 +1,47 @@
#
# bash prompt support for docker-machine
#
# This script allows you to see the active machine in your bash prompt.
#
# To enable:
# 1a. Copy this file somewhere and source it in your .bashrc
# source /some/where/docker-machine-prompt.bash
# 1b. Alternatively, just copy this file into into /etc/bash_completion.d
# 2. Change your PS1 to call __docker-machine-ps1 as command-substitution
# PS1='[\u@\h \W$(__docker_machine_ps1 " [%s]")]\$ '
#
# Configuration:
#
# DOCKER_MACHINE_PS1_SHOWSTATUS
# When set, the machine status is indicated in the prompt. This can be slow,
# so use with care.
#
__docker_machine_ps1 () {
local format=${1:- [%s]}
if test ${DOCKER_MACHINE_NAME}; then
local status
if test ${DOCKER_MACHINE_PS1_SHOWSTATUS:-false} = true; then
status=$(docker-machine status ${DOCKER_MACHINE_NAME})
case ${status} in
Running)
status=' R'
;;
Stopping)
status=' R->S'
;;
Starting)
status=' S->R'
;;
Error|Timeout)
status=' E'
;;
*)
# Just consider everything elase as 'stopped'
status=' S'
;;
esac
fi
printf -- "${format}" "${DOCKER_MACHINE_NAME}${status}"
fi
}

View File

@ -0,0 +1,56 @@
#
# Function wrapper to docker-machine that adds a use subcommand.
#
# The use subcommand runs `eval "$(docker-machine env [args])"`, which is a lot
# less typing.
#
# To enable:
# 1a. Copy this file somewhere and source it in your .bashrc
# source /some/where/docker-machine-wrapper.bash
# 1b. Alternatively, just copy this file into into /etc/bash_completion.d
#
# Configuration:
#
# DOCKER_MACHINE_WRAPPED
# When set to a value other than true, this will disable the alias wrapper
# alias for docker-machine. This is useful if you don't want the wrapper,
# but it is installed by default by your installation.
#
: ${DOCKER_MACHINE_WRAPPED:=true}
__docker_machine_wrapper () {
if [[ "$1" == use ]]; then
# Special use wrapper
shift 1
case "$1" in
-h|--help|"")
cat <<EOF
Usage: docker-machine use [OPTIONS] [arg...]
Evaluate the commands to set up the environment for the Docker client
Description:
Argument is a machine name.
Options:
--swarm Display the Swarm config instead of the Docker daemon
--unset, -u Unset variables instead of setting them
EOF
;;
*)
eval "$(docker-machine env "$@")"
echo "Active machine: ${DOCKER_MACHINE_NAME}"
;;
esac
else
# Just call the actual docker-machine app
command docker-machine "$@"
fi
}
if [[ ${DOCKER_MACHINE_WRAPPED} = true ]]; then
alias docker-machine=__docker_machine_wrapper
fi

View File

@ -0,0 +1,252 @@
#
# bash completion file for docker-machine commands
#
# This script provides completion of:
# - commands and their options
# - machine names
# - filepaths
#
# To enable the completions either:
# - place this file in /etc/bash_completion.d
# or
# - copy this file to e.g. ~/.docker-machine-completion.sh and add the line
# below to your .bashrc after bash completion features are loaded
# . ~/.docker-machine-completion.sh
#
_docker_machine_active() {
if [[ "${cur}" == -* ]]; then
COMPREPLY=($(compgen -W "--help" -- "${cur}"))
else
COMPREPLY=()
fi
}
_docker_machine_config() {
if [[ "${cur}" == -* ]]; then
COMPREPLY=($(compgen -W "--swarm --help" -- "${cur}"))
else
COMPREPLY=($(compgen -W "$(docker-machine ls -q)" -- "${cur}"))
fi
}
_docker_machine_create() {
# cheating, b/c there are approximately one zillion options to create
COMPREPLY=($(compgen -W "$(docker-machine create --help | grep '^ -' | sed 's/^ //; s/[^a-z0-9-].*$//')" -- "${cur}"))
}
_docker_machine_env() {
case "${prev}" in
--shell)
# What are the options for --shell?
COMPREPLY=()
;;
*)
if [[ "${cur}" == -* ]]; then
COMPREPLY=($(compgen -W "--swarm --shell --unset --no-proxy --help" -- "${cur}"))
else
COMPREPLY=($(compgen -W "$(docker-machine ls -q)" -- "${cur}"))
fi
esac
}
# See docker-machine-wrapper.bash for the use command
_docker_machine_use() {
if [[ "${cur}" == -* ]]; then
COMPREPLY=($(compgen -W "--swarm --unset --help" -- "${cur}"))
else
COMPREPLY=($(compgen -W "$(docker-machine ls -q)" -- "${cur}"))
fi
}
_docker_machine_inspect() {
case "${prev}" in
-f|--format)
COMPREPLY=()
;;
*)
if [[ "${cur}" == -* ]]; then
COMPREPLY=($(compgen -W "--format --help" -- "${cur}"))
else
COMPREPLY=($(compgen -W "$(docker-machine ls -q)" -- "${cur}"))
fi
;;
esac
}
_docker_machine_ip() {
if [[ "${cur}" == -* ]]; then
COMPREPLY=($(compgen -W "--help" -- "${cur}"))
else
COMPREPLY=($(compgen -W "$(docker-machine ls -q)" -- "${cur}"))
fi
}
_docker_machine_kill() {
if [[ "${cur}" == -* ]]; then
COMPREPLY=($(compgen -W "--help" -- "${cur}"))
else
COMPREPLY=($(compgen -W "$(docker-machine ls -q)" -- "${cur}"))
fi
}
_docker_machine_ls() {
case "${prev}" in
--filter)
COMPREPLY=()
;;
*)
COMPREPLY=($(compgen -W "--quiet --filter --format --timeout --help" -- "${cur}"))
;;
esac
}
_docker_machine_regenerate_certs() {
if [[ "${cur}" == -* ]]; then
COMPREPLY=($(compgen -W "--help --force" -- "${cur}"))
else
COMPREPLY=($(compgen -W "$(docker-machine ls -q)" -- "${cur}"))
fi
}
_docker_machine_restart() {
if [[ "${cur}" == -* ]]; then
COMPREPLY=($(compgen -W "--help" -- "${cur}"))
else
COMPREPLY=($(compgen -W "$(docker-machine ls -q)" -- "${cur}"))
fi
}
_docker_machine_rm() {
if [[ "${cur}" == -* ]]; then
COMPREPLY=($(compgen -W "--help --force -y" -- "${cur}"))
else
# For rm, it's best to be explicit
COMPREPLY=()
fi
}
_docker_machine_ssh() {
if [[ "${cur}" == -* ]]; then
COMPREPLY=($(compgen -W "--help" -- "${cur}"))
else
COMPREPLY=($(compgen -W "$(docker-machine ls -q)" -- "${cur}"))
fi
}
_docker_machine_scp() {
if [[ "${cur}" == -* ]]; then
COMPREPLY=($(compgen -W "--help --recursive" -- "${cur}"))
else
_filedir
# It would be really nice to ssh to the machine and ls to complete
# remote files.
COMPREPLY=($(compgen -W "$(docker-machine ls -q | sed 's/$/:/')" -- "${cur}") "${COMPREPLY[@]}")
fi
}
_docker_machine_start() {
if [[ "${cur}" == -* ]]; then
COMPREPLY=($(compgen -W "--help" -- "${cur}"))
else
COMPREPLY=($(compgen -W "$(docker-machine ls -q)" -- "${cur}"))
fi
}
_docker_machine_status() {
if [[ "${cur}" == -* ]]; then
COMPREPLY=($(compgen -W "--help" -- "${cur}"))
else
COMPREPLY=($(compgen -W "$(docker-machine ls -q)" -- "${cur}"))
fi
}
_docker_machine_stop() {
if [[ "${cur}" == -* ]]; then
COMPREPLY=($(compgen -W "--help" -- "${cur}"))
else
COMPREPLY=($(compgen -W "$(docker-machine ls -q)" -- "${cur}"))
fi
}
_docker_machine_upgrade() {
if [[ "${cur}" == -* ]]; then
COMPREPLY=($(compgen -W "--help" -- "${cur}"))
else
COMPREPLY=($(compgen -W "$(docker-machine ls -q)" -- "${cur}"))
fi
}
_docker_machine_url() {
if [[ "${cur}" == -* ]]; then
COMPREPLY=($(compgen -W "--help" -- "${cur}"))
else
COMPREPLY=($(compgen -W "$(docker-machine ls -q)" -- "${cur}"))
fi
}
_docker_machine_version() {
if [[ "${cur}" == -* ]]; then
COMPREPLY=($(compgen -W "--help" -- "${cur}"))
else
COMPREPLY=($(compgen -W "$(docker-machine ls -q)" -- "${cur}"))
fi
}
_docker_machine_help() {
if [[ "${cur}" == -* ]]; then
COMPREPLY=($(compgen -W "--help" -- "${cur}"))
else
COMPREPLY=($(compgen -W "${commands[*]}" -- "${cur}"))
fi
}
_docker_machine_docker_machine() {
if [[ " ${wants_file[*]} " =~ " ${prev} " ]]; then
_filedir
elif [[ " ${wants_dir[*]} " =~ " ${prev} " ]]; then
_filedir -d
elif [[ "${cur}" == -* ]]; then
COMPREPLY=($(compgen -W "${flags[*]} ${wants_dir[*]} ${wants_file[*]}" -- "${cur}"))
else
COMPREPLY=($(compgen -W "${commands[*]}" -- "${cur}"))
fi
}
_docker_machine() {
COMPREPLY=()
local commands=(active config create env inspect ip kill ls regenerate-certs restart rm ssh scp start status stop upgrade url version help)
local flags=(--debug --native-ssh --github-api-token --bugsnag-api-token --help --version)
local wants_dir=(--storage-path)
local wants_file=(--tls-ca-cert --tls-ca-key --tls-client-cert --tls-client-key)
# Add the use subcommand, if we have an alias loaded
if [[ ${DOCKER_MACHINE_WRAPPED} = true ]]; then
commands=("${commands[@]}" use)
fi
local cur prev words cword
_get_comp_words_by_ref -n : cur prev words cword
local i
local command=docker-machine
for (( i=1; i < ${cword}; ++i)); do
local word=${words[i]}
if [[ " ${wants_file[*]} ${wants_dir[*]} " =~ " ${word} " ]]; then
# skip the next option
(( ++i ))
elif [[ " ${commands[*]} " =~ " ${word} " ]]; then
command=${word}
fi
done
local completion_func=_docker_machine_"${command//-/_}"
if declare -F "${completion_func}" > /dev/null; then
${completion_func}
fi
return 0
}
complete -F _docker_machine docker-machine

View File

@ -0,0 +1,358 @@
#compdef docker-machine
# Description
# -----------
# zsh completion for docker-machine
# https://github.com/leonhartX/docker-machine-zsh-completion
# -------------------------------------------------------------------------
# Version
# -------
# 0.1.1
# -------------------------------------------------------------------------
# Authors
# -------
# * Ke Xu <leonhartx.k@gmail.com>
# -------------------------------------------------------------------------
# Inspiration
# -----------
# * @sdurrheimer docker-compose-zsh-completion https://github.com/sdurrheimer/docker-compose-zsh-completion
# * @ilkka _docker-machine
__docker-machine_get_hosts() {
[[ $PREFIX = -* ]] && return 1
local state
declare -a hosts
state=$1; shift
if [[ $state != all ]]; then
hosts=(${(f)"$(_call_program commands docker-machine ls -q --filter state=$state)"})
else
hosts=(${(f)"$(_call_program commands docker-machine ls -q)"})
fi
_describe 'host' hosts "$@" && ret=0
return ret
}
__docker-machine_hosts_with_state() {
declare -a hosts
hosts=(${(f)"$(_call_program commands docker-machine ls -f '{{.Name}}\:{{.DriverName}}\({{.State}}\)\ {{.URL}}')"})
_describe 'host' hosts
}
__docker-machine_hosts_all() {
__docker-machine_get_hosts all "$@"
}
__docker-machine_hosts_running() {
__docker-machine_get_hosts Running "$@"
}
__docker-machine_get_swarm() {
declare -a swarms
swarms=(${(f)"$(_call_program commands docker-machine ls -f {{.Swarm}} | awk '{print $1}')"})
_describe 'swarm' swarms
}
__docker-machine_hosts_and_files() {
_alternative "hosts:host:__docker-machine_hosts_all -qS ':'" 'files:files:_path_files'
}
__docker-machine_filters() {
[[ $PREFIX = -* ]] && return 1
integer ret=1
if compset -P '*='; then
case "${${words[-1]%=*}#*=}" in
(driver)
_describe -t driver-filter-opts "driver filter" opts_driver && ret=0
;;
(swarm)
__docker-machine_get_swarm && ret=0
;;
(state)
opts_state=('Running' 'Paused' 'Saved' 'Stopped' 'Stopping' 'Starting' 'Error')
_describe -t state-filter-opts "state filter" opts_state && ret=0
;;
(name)
__docker-machine_hosts_all && ret=0
;;
(label)
_message 'label' && ret=0
;;
*)
_message 'value' && ret=0
;;
esac
else
opts=('driver' 'swarm' 'state' 'name' 'label')
_describe -t filter-opts "filter" opts -qS "=" && ret=0
fi
return ret
}
__get_swarm_discovery() {
declare -a masters serivces
local service
services=()
masters=($(docker-machine ls -f {{.Swarm}} |grep '(master)' |awk '{print $1}'))
for master in $masters; do
service=${${${(f)"$(_call_program commands docker-machine inspect -f '{{.HostOptions.SwarmOptions.Discovery}}:{{.Name}}' $master)"}/:/\\:}}
services=($services $service)
done
_describe -t services "swarm service" services && ret=0
return ret
}
__get_create_argument() {
typeset -g docker_machine_driver
if [[ CURRENT -le 2 ]]; then
docker_machine_driver="none"
elif [[ CURRENT > 2 && $words[CURRENT-2] = '-d' || $words[CURRENT-2] = '--driver' ]]; then
docker_machine_driver=$words[CURRENT-1]
elif [[ $words[CURRENT-1] =~ '^(-d|--driver)=' ]]; then
docker_machine_driver=${${words[CURRENT-1]}/*=/}
fi
local driver_opt_cmd
local -a opts_provider opts_common opts_read_argument
opts_read_argument=(
": :->argument"
)
opts_common=(
$opts_help \
'(--driver -d)'{--driver=,-d=}'[Driver to create machine with]:dirver:->driver-option' \
'--engine-install-url=[Custom URL to use for engine installation]:url' \
'*--engine-opt=[Specify arbitrary flags to include with the created engine in the form flag=value]:flag' \
'*--engine-insecure-registry=[Specify insecure registries to allow with the created engine]:registry' \
'*--engine-registry-mirror=[Specify registry mirrors to use]:mirror' \
'*--engine-label=[Specify labels for the created engine]:label' \
'--engine-storage-driver=[Specify a storage driver to use with the engine]:storage-driver:->storage-driver-option' \
'*--engine-env=[Specify environment variables to set in the engine]:environment' \
'--swarm[Configure Machine with Swarm]' \
'--swarm-image=[Specify Docker image to use for Swarm]:image' \
'--swarm-master[Configure Machine to be a Swarm master]' \
'--swarm-discovery=[Discovery service to use with Swarm]:service:->swarm-service' \
'--swarm-strategy=[Define a default scheduling strategy for Swarm]:strategy:(spread binpack random)' \
'*--swarm-opt=[Define arbitrary flags for swarm]:flag' \
'*--swarm-join-opt=[Define arbitrary flags for Swarm join]:flag' \
'--swarm-host=[ip/socket to listen on for Swarm master]:host' \
'--swarm-addr=[addr to advertise for Swarm (default: detect and use the machine IP)]:address' \
'--swarm-experimental[Enable Swarm experimental features]' \
'*--tls-san=[Support extra SANs for TLS certs]:option'
)
driver_opt_cmd="docker-machine create -d $docker_machine_driver | grep $docker_machine_driver | sed -e 's/\(--.*\)\ *\[\1[^]]*\]/*\1/g' -e 's/\(\[[^]]*\)/\\\\\\1\\\\/g' -e 's/\".*\"\(.*\)/\1/g' | awk '{printf \"%s[\", \$1; for(i=2;i<=NF;i++) {printf \"%s \", \$i}; print \"]\"}'"
if [[ $docker_machine_driver != "none" ]]; then
opts_provider=(${(f)"$(_call_program commands $driver_opt_cmd)"})
_arguments \
$opts_provider \
$opts_read_argument \
$opts_common && ret=0
else
_arguments $opts_common && ret=0
fi
case $state in
(driver-option)
_describe -t driver-option "driver" opts_driver && ret=0
;;
(storage-driver-option)
_describe -t storage-driver-option "storage driver" opts_storage_driver && ret=0
;;
(swarm-service)
__get_swarm_discovery && ret=0
;;
(argument)
ret=0
;;
esac
return ret
}
__docker-machine_subcommand() {
local opts_help="(- :)"{-h,--help}"[Print usage]"
local -a opts_only_host opts_driver opts_storage_driver opts_stragery
opts_only_host=(
"$opts_help"
"*:host:__docker-machine_hosts_all"
)
opts_driver=('amazonec2' 'azure' 'digitalocean' 'exoscale' 'generic' 'google' 'hyperv' 'none' 'openstack' 'rackspace' 'softlayer' 'virtualbox' 'vmwarefusion' 'vmwarevcloudair' 'vmwarevsphere')
opts_storage_driver=('overlay' 'aufs' 'btrfs' 'devicemapper' 'vfs' 'zfs')
integer ret=1
case "$words[1]" in
(active)
_arguments \
$opts_help \
'(--timeout -t)'{--timeout=,-t=}'[Timeout in seconds, default to 10s]:seconds' && ret=0
;;
(config)
_arguments \
$opts_help \
'--swarm[Display the Swarm config instead of the Docker daemon]' \
"*:host:__docker-machine_hosts_all" && ret=0
;;
(create)
__get_create_argument
;;
(env)
_arguments \
$opts_help \
'--swarm[Display the Swarm config instead of the Docker daemon]' \
'--shell=[Force environment to be configured for a specified shell: \[fish, cmd, powershell\], default is auto-detect]:shell' \
'(--unset -u)'{--unset,-u}'[Unset variables instead of setting them]' \
'--no-proxy[Add machine IP to NO_PROXY environment variable]' \
'*:host:__docker-machine_hosts_running' && ret=0
;;
(help)
_arguments ':subcommand:__docker-machine_commands' && ret=0
;;
(inspect)
_arguments \
$opts_help \
'(--format -f)'{--format=,-f=}'[Format the output using the given go template]:template' \
'*:host:__docker-machine_hosts_all' && ret=0
;;
(ip)
_arguments \
$opts_help \
'*:host:__docker-machine_hosts_running' && ret=0
;;
(kill)
_arguments \
$opts_help \
'*:host:__docker-machine_hosts_with_state' && ret=0
;;
(ls)
_arguments \
$opts_help \
'(--quiet -q)'{--quiet,-q}'[Enable quiet mode]' \
'*--filter=[Filter output based on conditions provided]:filter:->filter-options' \
'(--timeout -t)'{--timeout=,-t=}'[Timeout in seconds, default to 10s]:seconds' \
'(--format -f)'{--format=,-f=}'[Pretty-print machines using a Go template]:template' && ret=0
case $state in
(filter-options)
__docker-machine_filters && ret=0
;;
esac
;;
(provision)
_arguments $opts_only_host && ret=0
;;
(regenerate-certs)
_arguments \
$opts_help \
'(--force -f)'{--force,-f}'[Force rebuild and do not prompt]' \
'*:host:__docker-machine_hosts_all' && ret=0
;;
(restart)
_arguments \
$opts_help \
'*:host:__docker-machine_hosts_with_state' && ret=0
;;
(rm)
_arguments \
$opts_help \
'(--force -f)'{--force,-f}'[Remove local configuration even if machine cannot be removed, also implies an automatic yes (`-y`)]' \
'-y[Assumes automatic yes to proceed with remove, without prompting further user confirmation]' \
'*:host:__docker-machine_hosts_with_state' && ret=0
;;
(scp)
_arguments \
$opts_help \
'(--recursive -r)'{--recursive,-r}'[Copy files recursively (required to copy directories))]' \
'*:files:__docker-machine_hosts_and_files' && ret=0
;;
(ssh)
_arguments \
$opts_help \
'*:host:__docker-machine_hosts_running' && ret=0
;;
(start)
_arguments \
$opts_help \
'*:host:__docker-machine_hosts_with_state' && ret=0
;;
(status)
_arguments $opts_only_host && ret=0
;;
(stop)
_arguments \
$opts_help \
'*:host:__docker-machine_hosts_with_state' && ret=0
;;
(upgrade)
_arguments $opts_only_host && ret=0
;;
(url)
_arguments \
$opts_help \
'*:host:__docker-machine_hosts_running' && ret=0
;;
esac
return ret
}
__docker-machine_commands() {
local cache_policy
zstyle -s ":completion:${curcontext}:" cache-policy cache_policy
if [[ -z "$cache_policy" ]]; then
zstyle ":completion:${curcontext}:" cache-policy __docker-machine_caching_policy
fi
if ( [[ ${+_docker_machine_subcommands} -eq 0 ]] || _cache_invalid docker_machine_subcommands) \
&& ! _retrieve_cache docker_machine_subcommands;
then
local -a lines
lines=(${(f)"$(_call_program commands docker-machine 2>&1)"})
_docker_machine_subcommands=(${${${lines[$((${lines[(i)Commands:]} + 1)),${lines[(I) *]}]}## #}/$'\t'##/:})
(( $#_docker_machine_subcommands > 0 )) && _store_cache docker_machine_subcommands _docker_machine_subcommands
fi
_describe -t docker-machine-commands "docker-machine command" _docker_machine_subcommands
}
__docker-machine_caching_policy() {
oldp=( "$1"(Nmh+1) )
(( $#oldp ))
}
_docker-machine() {
if [[ $service != docker-machine ]]; then
_call_function - _$service
return
fi
local curcontext="$curcontext" state line
integer ret=1
typeset -A opt_args
_arguments -C \
"(- :)"{-h,--help}"[Show help]" \
"(-D --debug)"{-D,--debug}"[Enable debug mode]" \
'(-s --stroage-path)'{-s,--storage-path}'[Configures storage path]:file:_files' \
'--tls-ca-cert[CA to verify remotes against]:file:_files' \
'--tls-ca-key[Private key to generate certificates]:file:_files' \
'--tls-client-cert[Client cert to use for TLS]:file:_files' \
'--tls-client-key[Private key used in client TLS auth]:file:_files' \
'--github-api-token[Token to use for requests to the Github API]' \
'--native-ssh[Use the native (Go-based) SSH implementation.]' \
'--bugsnag-api-token[BugSnag API token for crash reporting]' \
'(- :)'{-v,--version}'[Print the version]' \
"(-): :->command" \
"(-)*:: :->option-or-argument" && ret=0
case $state in
(command)
__docker-machine_commands && ret=0
;;
(option-or-argument)
curcontext=${curcontext%:*:*}:docker-machine-$words[1]:
__docker-machine_subcommand && ret=0
ret=0
;;
esac
return ret
}
_docker-machine "$@"

4
doc.go Normal file
View File

@ -0,0 +1,4 @@
// Package machine defines interfaces to manage a variety of docker instances
// deployed on different backends (VMs, baremetal).
// The goal is to allow users get from zero to docker as fast as possible.
package machine

View File

@ -0,0 +1,46 @@
<!--[metadata]>
+++
draft = true
title = "Machine plugins"
description = "Machine plugins"
keywords = ["Docker, documentation, manual, guide, reference, api"]
+++
<![end-metadata]-->
# Available driver plugins
This document is intended to act as a reference for the available 3rd-party
driver plugins available in the ecosystem beyond the core Machine drivers. If
you have created a Docker Machine driver, we highly encourage you to submit a
pull request adding the relevant information to the list. Submitting your
driver here will allow others to discover it and the core Machine team to keep
you informed of upstream changes.
**NOTE**: The linked repositories are not maintained by or formally associated
with Docker Inc. Use 3rd party plugins at your own risk.
| Name | Repository | Maintainer GitHub Handle | Maintainer Email |
| ---------------------- | ------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------- | ------------------------------------------------------------ |
| 1&1 Cloud Server | <https://github.com/1and1/docker-machine-driver-oneandone> | [StackPointCloud, Inc.](https://github.com/stackpointcloud) | sdk@1and1.com |
| Aliyun ECS | <https://github.com/denverdino/docker-machine-driver-aliyunecs> | [denverdino](https://github.com/denverdino)<br/>[menglingwei](https://github.com/menglingwei) | denverdino@gmail.com<br/>v.con@qq.com |
| Amazon Cloud Formation | <https://github.com/jeffellin/machine-cloudformation> | [Jeff Ellin](https://github.com/jeffellin) | acf@ellin.com |
| BrightBox | <https://github.com/brightbox/docker-machine-driver-brightbox> | [NeilW](https://github.com/NeilW) | neil@aldur.co.uk |
| CenturyLink Cloud | <https://github.com/CenturyLinkCloud/docker-machine-driver-clc> | [ack](https://github.com/ack) | albert.choi@ctl.io |
| Citrix XenServer | <https://github.com/xenserver/docker-machine-driver-xenserver> | [robertbreker](https://github.com/robertbreker)<br>[phusl](https://github.com/phusl) | robert.breker@citrix.com<br>phus.lu@citrix.com |
| Docker-In-Docker | <https://github.com/nathanleclaire/docker-machine-driver-dind> | [nathanleclaire](https://github.com/nathanleclaire) | nathan.leclaire@gmail.com |
| HPE OneView | <https://github.com/HewlettPackard/docker-machine-oneview> | [wenlock](https://github.com/wenlock)<br>[miqui](https://github.com/miqui) | wenlock@hpe.com<br>miqui@hpe.com |
| KVM | <https://github.com/dhiltgen/docker-machine-kvm> | [dhiltgen](https://github.com/dhiltgen) | daniel.hiltgen@docker.com |
| OpenNebula | <https://github.com/OpenNebula/docker-machine-opennebula> | [jmelis](https://github.com/jmelis) | jmelis@opennebula.org |
| OVH Cloud | <https://github.com/yadutaf/docker-machine-driver-ovh> | [yadutaf](https://github.com/yadutaf) | jt@yadutaf.fr |
| Packet | <https://github.com/packethost/docker-machine-driver-packet> | [betawaffle](https://github.com/betawaffle) | andy@packet.net |
| ProfitBricks | <https://github.com/profitbricks/docker-machine-driver-profitbricks> | [StackPointCloud, Inc.](https://github.com/stackpointcloud) | legal90@gmail.com |
| Parallels Desktop for Mac | <https://github.com/Parallels/docker-machine-parallels> | [legal90](https://github.com/legal90) | legal90@gmail.com |
| RackHD | <https://github.com/emccode/docker-machine-rackhd> | [kacole2](https://github.com/kacole2) | kendrick.coleman@emc.com |
| SAKURA CLOUD | <https://github.com/yamamoto-febc/docker-machine-sakuracloud> | [yamamoto-febc](https://github.com/yamamoto-febc) | yamamoto.febc@gmail.com |
| Scaleway | <https://github.com/scaleway/docker-machine-driver-scaleway> | [scaleway](https://github.com/scaleway) | opensource@scaleway.com |
| Skytap | <https://github.com/skytap/docker-machine-driver-skytap> | [dantjones](https://github.com/dantjones) | djones@skytap.com |
| Ubiquity Hosting | <https://github.com/ubiquityhosting/docker-machine-driver-ubiquity> | [Justin Canington](https://github.com/justacan)<br>[Andrew Ayers](https://github.com/andrew-ayers) | justin.canington@nobistech.net<br>andrew.ayers@nobistech.net |
| UCloud | <https://github.com/ucloud/docker-machine-ucloud> | [xiaohui](https://github.com/xiaohui) | xiaohui.zju@gmail.com |
| VMWare Workstation | <https://github.com/pecigonzalo/docker-machine-vmwareworkstation> | [pecigonzalo](https://github.com/pecigonzalo) | pecigonzalo@outlook.com |
| VULTR | <https://github.com/janeczku/docker-machine-vultr> | [janeczku](https://github.com/janeczku) | jb@festplatte.eu.org |
| xhyve | <https://github.com/zchee/docker-machine-driver-xhyve> | [zchee](https://github.com/zchee) | zchee.io@gmail.com |

149
docs/DRIVER_SPEC.md Normal file
View File

@ -0,0 +1,149 @@
<!--[metadata]>
+++
draft=true
title = "Docker Machine"
description = "machine"
keywords = ["machine, orchestration, install, installation, docker, documentation"]
[menu.main]
parent="mn_install"
+++
<![end-metadata]-->
# Machine Driver Specification v1
This is the standard configuration and specification for version 1 drivers.
Along with defining how a driver should provision instances, the standard
also discusses behavior and operations Machine expects.
# Requirements
The following are required for a driver to be included as a supported driver
for Docker Machine.
## Base Operating System
The provider must offer a base operating system supported by the Docker Engine.
Currently Machine requires Ubuntu for non-Boot2Docker machines. This will
change in the future.
## API Access
We prefer accessing the provider service via HTTP APIs and strongly recommend
using those over external executables. For example, using the Amazon EC2 API
instead of the EC2 command line tools. If in doubt, contact a project
maintainer.
## SSH
The provider must offer SSH access to control the instance. This does not
have to be public, but must offer it as Machine relies on SSH for system
level maintenance.
# Provider Operations
The following instance operations should be supported by the provider.
## Create
`Create` will launch a new instance and make sure it is ready for provisioning.
This includes setting up the instance with the proper SSH keys and making
sure SSH is available including any access control (firewall). This should
return an error on failure.
## Remove
`Remove` will remove the instance from the provider. This should remove the
instance and any associated services or artifacts that were created as part
of the instance including keys and access groups. This should return an
error on failure.
## Start
`Start` will start a stopped instance. This should ensure the instance is
ready for operations such as SSH and Docker. This should return an error on
failure.
## Stop
`Stop` will stop a running instance. This should ensure the instance is
stopped and return an error on failure.
## Kill
`Kill` will forcibly stop a running instance. This should ensure the instance
is stopped and return an error on failure.
## Restart
`Restart` will restart a running instance. This should ensure the instance
is ready for operations such as SSH and Docker. This should return an error
on failure.
## Status
`Status` will return the state of the instance. This should return the
current state of the instance (running, stopped, error, etc). This should
return an error on failure.
# Testing
Testing is strongly recommended for drivers. Unit tests are preferred as well
as inclusion into the [integration tests](https://github.com/docker/machine#integration-tests).
# Maintaining
Driver plugin maintainers are encouraged to host their own repo and distribute
the driver plugins as executables.
# Implementation
The following describes what is needed to create a Machine Driver. The driver
interface has methods that must be implemented for all drivers. These include
operations such as `Create`, `Remove`, `Start`, `Stop` etc.
For details see the [Driver Interface](https://github.com/docker/machine/blob/master/drivers/drivers.go#L24).
To provide this functionality, you should embed the `drivers.BaseDriver` struct, similar to the following:
type Driver struct {
*drivers.BaseDriver
DriverSpecificField string
}
Each driver must then use an `init` func to "register" the driver:
func init() {
drivers.Register("drivername", &drivers.RegisteredDriver{
New: NewDriver,
GetCreateFlags: GetCreateFlags,
})
}
## Flags
Driver flags are used for provider specific customizations. To add flags, use
a `GetCreateFlags` func. For example:
func GetCreateFlags() []cli.Flag {
return []cli.Flag{
cli.StringFlag{
EnvVar: "DRIVERNAME_TOKEN",
Name: "drivername-token",
Usage: "Provider access token",
},
cli.StringFlag{
EnvVar: "DRIVERNAME_IMAGE",
Name: "drivername-image",
Usage: "Provider Image",
Value: "ubuntu-14-04-x64",
},
}
}
## Examples
You can reference the existing [Drivers](https://github.com/docker/machine/tree/master/drivers)
as well.

9
docs/Dockerfile Normal file
View File

@ -0,0 +1,9 @@
FROM docs/base:oss
MAINTAINER Docker Docs <docs@docker.com>
env PROJECT=machine
# To get the git info for this repo
COPY . /src
RUN rm -rf /docs/content/$PROJECT/
COPY . /docs/content/$PROJECT/

38
docs/Makefile Normal file
View File

@ -0,0 +1,38 @@
.PHONY: all default docs docs-build docs-shell shell test
# to allow `make DOCSDIR=docs docs-shell` (to create a bind mount in docs)
DOCS_MOUNT := $(if $(DOCSDIR),-v $(CURDIR)/$(DOCSDIR):/$(DOCSDIR))
# to allow `make DOCSPORT=9000 docs`
DOCSPORT := 8000
# Get the IP ADDRESS
DOCKER_IP=$(shell python -c "import urlparse ; print urlparse.urlparse('$(DOCKER_HOST)').hostname or ''")
HUGO_BASE_URL=$(shell test -z "$(DOCKER_IP)" && echo localhost || echo "$(DOCKER_IP)")
HUGO_BIND_IP=0.0.0.0
GIT_BRANCH := $(shell git rev-parse --abbrev-ref HEAD 2>/dev/null)
GIT_BRANCH_CLEAN := $(shell echo $(GIT_BRANCH) | sed -e "s/[^[:alnum:]]/-/g")
DOCKER_DOCS_IMAGE := docker-docs$(if $(GIT_BRANCH_CLEAN),:$(GIT_BRANCH_CLEAN))
DOCKER_RUN_DOCS := docker run --rm -it $(DOCS_MOUNT) -e AWS_S3_BUCKET -e NOCACHE
# for some docs workarounds (see below in "docs-build" target)
GITCOMMIT := $(shell git rev-parse --short HEAD 2>/dev/null)
default: docs
docs: docs-build
$(DOCKER_RUN_DOCS) -p $(if $(DOCSPORT),$(DOCSPORT):)8000 -e DOCKERHOST "$(DOCKER_DOCS_IMAGE)" hugo server --port=$(DOCSPORT) --baseUrl=$(HUGO_BASE_URL) --bind=$(HUGO_BIND_IP)
docs-draft: docs-build
$(DOCKER_RUN_DOCS) -p $(if $(DOCSPORT),$(DOCSPORT):)8000 -e DOCKERHOST "$(DOCKER_DOCS_IMAGE)" hugo server --buildDrafts="true" --port=$(DOCSPORT) --baseUrl=$(HUGO_BASE_URL) --bind=$(HUGO_BIND_IP)
docs-shell: docs-build
$(DOCKER_RUN_DOCS) -p $(if $(DOCSPORT),$(DOCSPORT):)8000 "$(DOCKER_DOCS_IMAGE)" bash
test: docs-build
$(DOCKER_RUN_DOCS) "$(DOCKER_DOCS_IMAGE)"
docs-build:
docker build -t "$(DOCKER_DOCS_IMAGE)" .

89
docs/README.md Normal file
View File

@ -0,0 +1,89 @@
<!--[metadata]>
+++
draft = true
title = "Machine README"
description = "Machine README"
keywords = ["Docker, documentation, manual, guide, reference, api"]
+++
<![end-metadata]-->
# Contributing to the Docker Machine documentation
The documentation in this directory is part of the [this documentation](https://docs.docker.com). Docker uses [the Hugo static generator](http://gohugo.io/overview/introduction/) to convert project Markdown files to a static HTML site.
You don't need to be a Hugo expert to contribute to the machine documentation. If you are familiar with Markdown, you can modify the content in the `docs` files.
If you want to add a new file or change the location of the document in the menu, you do need to know a little more. If you want the detail of contributing, [use our contributor guide](http://docs.docker.com/project/make-a-contribution/).
## Documentation contributing workflow
1. Edit a Markdown file in the tree.
2. Save your changes.
3. Make sure all your changes maintain an 80 character line wrap.
All check lines you've written. Don't wrap content you didn't change material.
4. Make sure you are in the `docs` subdirectory.
5. Build the documentation.
$ make docs
---> ffcf3f6c4e97
Removing intermediate container a676414185e8
Successfully built ffcf3f6c4e97
docker run --rm -it -e AWS_S3_BUCKET -e NOCACHE -p 8000:8000 -e DOCKERHOST "docs-base:test-tooling" hugo server --port=8000 --baseUrl=192.168.59.103 --bind=0.0.0.0
ERROR: 2015/06/13 MenuEntry's .Url is deprecated and will be removed in Hugo 0.15. Use .URL instead.
0 of 4 drafts rendered
0 future content
12 pages created
0 paginator pages created
0 tags created
0 categories created
in 55 ms
Serving pages from /docs/public
Web Server is available at http://0.0.0.0:8000/
Press Ctrl+C to stop
6. Open the available server in your browser.
The documentation server has the complete menu but only the Docker machine
documentation resolves. You can't access the other project docs from this
localized build.
## Tips on Hugo metadata and menu positioning
The top of each Docker machine documentation file contains TOML metadata. The metadata is commented out to prevent it from appearing in GitHub.
<!--[metadata]>
+++
title = "Extending services in machine"
description = "How to use Docker machine's extends keyword to share configuration between files and projects"
keywords = ["fig, composition, machine, docker, orchestration, documentation, docs"]
[menu.main]
parent="workw_machine"
weight=2
+++
<![end-metadata]-->
The metadata alone has this structure:
+++
title = "Extending services in machine"
description = "How to use Docker machine's extends keyword to share configuration between files and projects"
keywords = ["fig, composition, machine, docker, orchestration, documentation, docs"]
[menu.main]
parent="workw_machine"
weight=2
+++
The `[menu.main]` section refers to navigation defined [in the main Docker menu](https://github.com/docker/docs-base/blob/hugo/config.toml). This metadata says _add a menu item called_ Extending services in machine _to the menu with the_ `smn_workdw_machine` _identifier_. If you locate the menu in the configuration, you'll find _Create multi-container applications_ is the menu title.
You can move an article in the tree by specifying a new parent. You can shift the location of the item by changing its weight. Higher numbers are heavier and shift the item to the bottom of menu. Low or no numbers shift it up.
## Other key documentation repositories
The `docker/docs-base` repository contains [the Hugo theme and menu configuration](https://github.com/docker/docs-base). If you open the `Dockerfile` you'll see the `make docs` relies on this as a base image for building the machine documentation.
The `docker/docs.docker.com` repository contains [build system for building the Docker documentation site](https://github.com/docker/docs.docker.com). Fork this repository to build the entire documentation site.

45
docs/RELEASE.md Normal file
View File

@ -0,0 +1,45 @@
<!--[metadata]>
+++
draft=true
+++
<![end-metadata]-->
# Docker Machine Release Process
The Docker Machine release process is fairly straightforward and as many steps
have been taken as possible to make it automated, but there is a procedure and
several "checklist items" which should be documented. This document is intended
to cover the current Docker Machine release process. It is written for Docker
Machine core maintainers who might find themselves performing a release.
0. The new version of `azure` driver released in 0.7.0 is not backwards compatible
and therefore errors out with a message saying the new driver is unsupported with
the new version. The commit 7b961604 should be undone prior to 0.8.0 release and
this notice must be removed from `docs/RELEASE.md`.
1. **Get a GITHUB_TOKEN** Check that you have a proper `GITHUB_TOKEN`. This
token needs only to have the `repo` scope. The token can be created on github
in the settings > Personal Access Token menu.
2. **Run the release script** At the root of the project, run the following
command `GITHUB_TOKEN=XXXX script/release.sh X.Y.Z` where `XXXX` is the
value of the GITHUB_TOKEN generated, `X.Y.Z` the version to release
( Explicitly excluding the 'v' prefix, the script takes care of it.). As of
now, this version number must match the content of `version/version.go`. The
script has been built to be as resilient as possible, cleaning everything
it does along its way if necessary. You can run it many times in a row,
fixing the various bits along the way.
3. **Update the changelog on github** -- The script generated a list of all
commits since last release. You need to edit this manually, getting rid of
non critical details, and putting emphasis to what need our users attention.
4. **Update the CHANGELOG.md** -- Add the same notes from the previous step to
the `CHANGELOG.md` file in the repository.
5. **Update the Documentation** -- Ensure that the `docs` branch on GitHub
(which the Docker docs team uses to deploy from) is up to date with the
changes to be deployed from the release branch / master. Make sure to
update `docs/install-machine.md` to have the correct version as well.
6. **Verify the Installation** -- Copy and paste the suggested commands in the
installation notes to ensure that they work properly. Best of all, grab an
(uninvolved) buddy and have them try it. `docker-machine -v` should give
them the released version once they have run the install commands.
7. (Optional) **Drink a Glass of Wine** -- You've worked hard on this release.
You deserve it. For wine suggestions, please consult your friendly
neighborhood sommelier.

59
docs/completion.md Normal file
View File

@ -0,0 +1,59 @@
<!--[metadata]>
+++
title = "Command-line Completion"
description = "Install Machine command-line completion"
keywords = ["machine, docker, orchestration, cli, reference"]
[menu.main]
identifier="machine_completion"
parent="workw_machine"
weight=99
+++
<![end-metadata]-->
# Command-line Completion
Docker Machine comes with [command completion](http://en.wikipedia.org/wiki/Command-line_completion)
for the bash and zsh shell.
## Installing Command Completion
### Bash
Make sure bash completion is installed. If you use a current Linux in a non-minimal installation, bash completion should be available.
On a Mac, install with `brew install bash-completion`
Place the completion scripts in `/etc/bash_completion.d/` (`` `brew --prefix`/etc/bash_completion.d/`` on a Mac), using e.g.
files=(docker-machine docker-machine-wrapper docker-machine-prompt)
for f in "${files[@]}"; do
curl -L https://raw.githubusercontent.com/docker/machine/v$(docker-machine --version | tr -ds ',' ' ' | awk 'NR==1{print $(3)}')/contrib/completion/bash/$f.bash > `brew --prefix`/etc/bash_completion.d/$f
done
Completion will be available upon next login.
### Zsh
Place the completion scripts in your `/path/to/zsh/completion`, using e.g. `~/.zsh/completion/`
mkdir -p ~/.zsh/completion
curl -L https://raw.githubusercontent.com/docker/machine/v$(docker-machine --version | tr -ds ',' ' ' | awk 'NR==1{print $(3)}')/contrib/completion/zsh/_docker-machine > ~/.zsh/completion/_docker-machine
Include the directory in your `$fpath`, e.g. by adding in `~/.zshrc`
fpath=(~/.zsh/completion $fpath)
Make sure `compinit` is loaded or do it by adding in `~/.zshrc`
autoload -Uz compinit && compinit -i
Then reload your shell
exec $SHELL -l
<!--[metadata]>
## Available completions
**TODO**
<![end-metadata]-->

88
docs/concepts.md Normal file
View File

@ -0,0 +1,88 @@
<!--[metadata]>
+++
title = "Machine concepts and help"
description = "Understand concepts for Docker Machine, including drivers, base OS, IP addresses, environment variables"
keywords = ["docker, machine, amazonec2, azure, digitalocean, google, openstack, rackspace, softlayer, virtualbox, vmwarefusion, vmwarevcloudair, vmwarevsphere, exoscale"]
[menu.main]
parent="workw_machine"
weight=-40
+++
<![end-metadata]-->
# Understand Machine concepts and get help
Docker Machine allows you to provision Docker machines in a variety of environments, including virtual machines that reside on your local system, on cloud providers, or on bare metal servers (physical computers). Docker Machine creates a Docker host, and you use the Docker Engine client as needed to build images and create containers on the host.
## Drivers for creating machines
To create a virtual machine, you supply Docker Machine with the name of the driver you want use. The driver determines where the virtual machine is created. For example, on a local Mac or Windows system, the driver is typically Oracle VirtualBox. For provisioning physical machines, a generic driver is provided. For cloud providers, Docker Machine supports drivers such as AWS, Microsoft Azure, Digital Ocean, and many more. The Docker Machine reference includes a complete [list of supported drivers](drivers/index.md).
## Default base operating systems for local and cloud hosts
Since Docker runs on Linux, each VM that Docker Machine provisions relies on a
base operating system. For convenience, there are default base operating
systems. For the Oracle Virtual Box driver, this base operating system is <a href="https://github.com/boot2docker/boot2docker" target="_blank">boot2docker</a>. For drivers used to connect to cloud providers, the base operating system is Ubuntu 12.04+. You can change this default when you create a machine. The Docker Machine reference includes a complete [list of
supported operating systems](drivers/os-base.md).
## IP addresses for Docker hosts
For each machine you create, the Docker host address is the IP address of the
Linux VM. This address is assigned by the `docker-machine create` subcommand.
You use the `docker-machine ls` command to list the machines you have created.
The `docker-machine ip <machine-name>` command returns a specific host's IP
address.
## Configuring CLI environment variables for a Docker host
Before you can run a `docker` command on a machine, you need to configure your
command-line to point to that machine. The `docker-machine env <machine-name>`
subcommand outputs the configuration command you should use.
For a complete list of `docker-machine` subcommands, see the [Docker Machine subcommand reference](reference/index.md).
## Crash Reporting
Provisioning a host is a complex matter that can fail for a lot of reasons. Your
workstation may have a wide variety of shell, network configuration, VPN, proxy
or firewall issues. There are also reasons from the other end of the chain:
your cloud provider or the network in between.
To help `docker-machine` be as stable as possible, we added a monitoring of
crashes whenever you try to `create` or `upgrade` a host. This will send, over
HTTPS, to Bugsnag some information about your `docker-machine` version, build,
OS, ARCH, the path to your current shell and, the history of the last command as
you could see it with a `--debug` option. This data is sent to help us pinpoint
recurring issues with `docker-machine` and will only be transmitted in the case
of a crash of `docker-machine`.
If you wish to opt out of error reporting, you can create a `no-error-report`
file in your `$HOME/.docker/machine` directory, and Docker Machine will disable
this behavior. e.g.:
$ mkdir -p ~/.docker/machine && touch ~/.docker/machine/no-error-report
Leaving the file empty is fine -- Docker Machine just checks for its presence.
## Getting help
Docker Machine is still in its infancy and under active development. If you need
help, would like to contribute, or simply want to talk about the project with
like-minded individuals, we have a number of open channels for communication.
- To report bugs or file feature requests: please use the [issue tracker on
Github](https://github.com/docker/machine/issues).
- To talk about the project with people in real time: please join the
`#docker-machine` channel on IRC.
- To contribute code or documentation changes: please [submit a pull request on
Github](https://github.com/docker/machine/pulls).
For more information and resources, please visit
[our help page](/opensource/get-help.md).
## Where to go next
- Create and run a Docker host on your [local system using VirtualBox](get-started.md)
- Provision multiple Docker hosts [on your cloud provider](get-started-cloud.md)
- <a href="../drivers/" target="_blank">Docker Machine driver reference</a>
- <a href="../reference/" target="_blank">Docker Machine subcommand reference</a>

168
docs/drivers/aws.md Normal file
View File

@ -0,0 +1,168 @@
<!--[metadata]>
+++
title = "Amazon Web Services"
description = "Amazon Web Services driver for machine"
keywords = ["machine, Amazon Web Services, driver"]
[menu.main]
parent="smn_machine_drivers"
+++
<![end-metadata]-->
# Amazon Web Services
Create machines on [Amazon Web Services](http://aws.amazon.com).
To create machines on [Amazon Web Services](http://aws.amazon.com), you must supply two parameters: the AWS Access Key ID and the AWS Secret Access Key.
## Configuring credentials
Before using the amazonec2 driver, ensure that you've configured credentials.
### AWS credential file
One way to configure credentials is to use the standard credential file for Amazon AWS `~/.aws/credentials` file, which might look like:
[default]
aws_access_key_id = AKID1234567890
aws_secret_access_key = MY-SECRET-KEY
On Mac OS or various flavors of Linux you can install the [AWS Command Line Interface](http://docs.aws.amazon.com/cli/latest/userguide/cli-chap-getting-started.html#cli-quick-configuration) (`aws cli`) in the terminal and use the `aws configure` command which guides you through the creation of the credentials file.
This is the simplest method, you can then create a new machine with:
$ docker-machine create --driver amazonec2 aws01
### Command line flags
Alternatively, you can use the flags `--amazonec2-access-key` and `--amazonec2-secret-key` on the command line:
$ docker-machine create --driver amazonec2 --amazonec2-access-key AKI******* --amazonec2-secret-key 8T93C******* aws01
### Environment variables
You can use environment variables:
$ export AWS_ACCESS_KEY_ID=AKID1234567890
$ export AWS_SECRET_ACCESS_KEY=MY-SECRET-KEY
$ docker-machine create --driver amazonec2 aws01
## Options
- `--amazonec2-access-key`: Your access key id for the Amazon Web Services API.
- `--amazonec2-secret-key`: Your secret access key for the Amazon Web Services API.
- `--amazonec2-session-token`: Your session token for the Amazon Web Services API.
- `--amazonec2-ami`: The AMI ID of the instance to use.
- `--amazonec2-region`: The region to use when launching the instance.
- `--amazonec2-vpc-id`: Your VPC ID to launch the instance in.
- `--amazonec2-zone`: The AWS zone to launch the instance in (i.e. one of a,b,c,d,e).
- `--amazonec2-subnet-id`: AWS VPC subnet id.
- `--amazonec2-security-group`: AWS VPC security group name.
- `--amazonec2-tags`: AWS extra tag key-value pairs (comma-separated, e.g. key1,value1,key2,value2).
- `--amazonec2-instance-type`: The instance type to run.
- `--amazonec2-device-name`: The root device name of the instance.
- `--amazonec2-root-size`: The root disk size of the instance (in GB).
- `--amazonec2-volume-type`: The Amazon EBS volume type to be attached to the instance.
- `--amazonec2-iam-instance-profile`: The AWS IAM role name to be used as the instance profile.
- `--amazonec2-ssh-user`: The SSH Login username, which must match the default SSH user set in the ami used.
- `--amazonec2-request-spot-instance`: Use spot instances.
- `--amazonec2-spot-price`: Spot instance bid price (in dollars). Require the `--amazonec2-request-spot-instance` flag.
- `--amazonec2-use-private-address`: Use the private IP address for docker-machine, but still create a public IP address.
- `--amazonec2-private-address-only`: Use the private IP address only.
- `--amazonec2-monitoring`: Enable CloudWatch Monitoring.
- `--amazonec2-use-ebs-optimized-instance`: Create an EBS Optimized Instance, instance type must support it.
- `--amazonec2-ssh-keypath`: Path to Private Key file to use for instance. Matching public key with .pub extension should exist
- `--amazonec2-retries`: Set retry count for recoverable failures (use -1 to disable)
#### Environment variables and default values:
| CLI option | Environment variable | Default |
| ---------------------------------------- | ----------------------- | ---------------- |
| `--amazonec2-access-key` | `AWS_ACCESS_KEY_ID` | - |
| `--amazonec2-secret-key` | `AWS_SECRET_ACCESS_KEY` | - |
| `--amazonec2-session-token` | `AWS_SESSION_TOKEN` | - |
| `--amazonec2-ami` | `AWS_AMI` | `ami-5f709f34` |
| `--amazonec2-region` | `AWS_DEFAULT_REGION` | `us-east-1` |
| `--amazonec2-vpc-id` | `AWS_VPC_ID` | - |
| `--amazonec2-zone` | `AWS_ZONE` | `a` |
| `--amazonec2-subnet-id` | `AWS_SUBNET_ID` | - |
| `--amazonec2-security-group` | `AWS_SECURITY_GROUP` | `docker-machine` |
| `--amazonec2-tags` | `AWS_TAGS` | - |
| `--amazonec2-instance-type` | `AWS_INSTANCE_TYPE` | `t2.micro` |
| `--amazonec2-device-name` | `AWS_DEVICE_NAME` | `/dev/sda1` |
| `--amazonec2-root-size` | `AWS_ROOT_SIZE` | `16` |
| `--amazonec2-volume-type` | `AWS_VOLUME_TYPE` | `gp2` |
| `--amazonec2-iam-instance-profile` | `AWS_INSTANCE_PROFILE` | - |
| `--amazonec2-ssh-user` | `AWS_SSH_USER` | `ubuntu` |
| `--amazonec2-request-spot-instance` | - | `false` |
| `--amazonec2-spot-price` | - | `0.50` |
| `--amazonec2-use-private-address` | - | `false` |
| `--amazonec2-private-address-only` | - | `false` |
| `--amazonec2-monitoring` | - | `false` |
| `--amazonec2-use-ebs-optimized-instance` | - | `false` |
| `--amazonec2-ssh-keypath` | `AWS_SSH_KEYPATH` | - |
| `--amazonec2-retries` | - | `5` |
## Default AMIs
By default, the Amazon EC2 driver will use a daily image of Ubuntu 15.10.
| Region | AMI ID |
| -------------- | ------------ |
| ap-northeast-1 | ami-b36d4edd |
| ap-southeast-1 | ami-1069af73 |
| ap-southeast-2 | ami-1d336a7e |
| cn-north-1 | ami-79eb2214 |
| eu-west-1 | ami-8aa67cf9 |
| eu-central-1 | ami-ab0210c7 |
| sa-east-1 | ami-185de774 |
| us-east-1 | ami-26d5af4c |
| us-west-1 | ami-9cbcd2fc |
| us-west-2 | ami-16b1a077 |
| us-gov-west-1 | ami-b0bad893 |
## Security Group
Note that a security group will be created and associated to the host. This security group will have the following ports opened inbound:
- ssh (22/tcp)
- docker (2376/tcp)
- swarm (3376/tcp), only if the node is a swarm master
If you specify a security group yourself using the `--amazonec2-security-group` flag, the above ports will be checked and opened and the security group modified.
If you want more ports to be opened, like application specific ports, use the aws console and modify the configuration manually.
## VPC ID
We determine your default vpc id at the start of a command.
In some cases, either because your account does not have a default vpc, or you don't want to use the default one, you can specify a vpc with the `--amazonec2-vpc-id` flag.
To find the VPC ID:
1. Login to the AWS console
2. Go to **Services -> VPC -> Your VPCs**.
3. Locate the VPC ID you want from the _VPC_ column.
4. Go to **Services -> VPC -> Subnets**. Examine the _Availability Zone_ column to verify that zone `a` exists and matches your VPC ID.
For example, `us-east1-a` is in the `a` availability zone. If the `a` zone is not present, you can create a new subnet in that zone or specify a different zone when you create the machine.
To create a machine with a non-default vpc-id:
$ docker-machine create --driver amazonec2 --amazonec2-access-key AKI******* --amazonec2-secret-key 8T93C********* --amazonec2-vpc-id vpc-****** aws02
This example assumes the VPC ID was found in the `a` availability zone. Use the`--amazonec2-zone` flag to specify a zone other than the `a` zone. For example, `--amazonec2-zone c` signifies `us-east1-c`.
## VPC Connectivity
Machine uses SSH to complete the set up of instances in EC2 and requires the ability to access the instance directly.
If you use the flag `--amazonec2-private-address-only`, you will need to ensure that you have some method of accessing the new instance from within the internal network of the VPC (e.g. a corporate VPN to the VPC, a VPN instance inside the VPC or using Docker-machine from an instance within your VPC).
Configuration of VPCs is beyond the scope of this guide, however the first step in troubleshooting is ensuring if you are using private subnets that you follow the design guidance in the [AWS VPC User Guide](http://docs.aws.amazon.com/AmazonVPC/latest/UserGuide/VPC_Scenario2.html) and have some form of NAT available so that the set up process can access the internet to complete set up.
## Custom AMI and SSH username
The default SSH username for the default AMIs is `ubuntu`.
You need to change the SSH username only if the custom AMI you use has a different SSH username.
You can change the SSH username with the `--amazonec2-ssh-user` according to the AMI you selected with the `--amazonec2-ami`.

125
docs/drivers/azure.md Normal file
View File

@ -0,0 +1,125 @@
<!--[metadata]>
+++
title = "Microsoft Azure"
description = "Microsoft Azure driver for machine"
keywords = ["machine, Microsoft Azure, driver"]
[menu.main]
parent="smn_machine_drivers"
+++
<![end-metadata]-->
# Microsoft Azure
You will need an Azure Subscription to use this Docker Machine driver.
[Sign up for a free trial.][trial]
> **NOTE:** This documentation is for the new version of the Azure driver, which started
> shipping with v0.7.0. This driver is not backwards-compatible with the old
> Azure driver. If you want to continue managing your existing Azure machines, please
> download and use machine versions prior to v0.7.0.
[azure]: http://azure.microsoft.com/
[trial]: https://azure.microsoft.com/free/
## Authentication
The first time you try to create a machine, Azure driver will ask you to
authenticate:
$ docker-machine create --driver azure --azure-subscription-id <subs-id> <machine-name>
Running pre-create checks...
Microsoft Azure: To sign in, use a web browser to open the page https://aka.ms/devicelogin.
Enter the code [...] to authenticate.
After authenticating, the driver will remember your credentials up to two weeks.
> **KNOWN ISSUE:** There is a known issue with Azure Active Directory causing stored
> credentials to expire within hours rather than 14 days when the user logs in with
> personal Microsoft Account (formerly _Live ID_) instead of an Active Directory account.
> Currently, there is no ETA for resolution, however in the meanwhile you can
> [create an AAD account][aad-docs] and login with that as a workaround.
[aad-docs]: https://azure.microsoft.com/documentation/articles/virtual-machines-windows-create-aad-work-id/
## Options
Azure driver only has a single required argument to make things easier. Please
read the optional flags to configure machine details and placement further.
Required:
- `--azure-subscription-id`: **(required)** Your Azure Subscription ID.
Optional:
- `--azure-image`: Azure virtual machine image in the format of Publisher:Offer:Sku:Version [[?][vm-image]]
- `--azure-location`: Azure region to create the virtual machine. [[?][location]]
- `--azure-resource-group`: Azure Resource Group name to create the resources in.
- `--azure-size`: Size for Azure Virtual Machine. [[?][vm-size]]
- `--azure-ssh-user`: Username for SSH login.
- `--azure-vnet`: Azure Virtual Network name to connect the virtual machine.
[[?][vnet]] To specify a Virtual Network from another resource group, use `resourcegroup:vnet-name` format.
- `--azure-subnet`: Azure Subnet Name to be used within the Virtual Network.
- `--azure-subnet-prefix`: Private CIDR block. Used to create subnet if it does not exist. Must match in the case that the subnet does exist.
- `--azure-availability-set`: Azure Availability Set to place the virtual machine into. [[?][av-set]]
- `--azure-open-port`: Make additional port number(s) accessible from the Internet [[?][nsg]]
- `--azure-private-ip-address`: Specify a static private IP address for the machine.
- `--azure-use-private-ip`: Use private IP address of the machine to connect. It's useful for managing Docker machines from another machine on the same network e.g. while deploying Swarm.
- `--azure-no-public-ip`: Do not create a public IP address for the machine (implies `--azure-use-private-ip`). Should be used only when creating machines from an Azure VM within the same subnet.
- `--azure-static-public-ip`: Assign a static public IP address to the machine.
- `--azure-docker-port`: Port number for Docker engine.
- `--azure-environment`: Azure environment (e.g. `AzurePublicCloud`, `AzureChinaCloud`).
[vm-image]: https://azure.microsoft.com/en-us/documentation/articles/resource-groups-vm-searching/
[location]: https://azure.microsoft.com/en-us/regions/
[vm-size]: https://azure.microsoft.com/en-us/documentation/articles/virtual-machines-size-specs/
[vnet]: https://azure.microsoft.com/en-us/documentation/articles/virtual-networks-overview/
[av-set]: https://azure.microsoft.com/en-us/documentation/articles/virtual-machines-manage-availability/
#### Environment variables and default values
| CLI option | Environment variable | Default |
| ------------------------------- | ----------------------------- | ------------------ |
| **`--azure-subscription-id`** | `AZURE_SUBSCRIPTION_ID` | - |
| `--azure-environment` | `AZURE_ENVIRONMENT` | `AzurePublicCloud` |
| `--azure-image` | `AZURE_IMAGE` | `canonical:UbuntuServer:16.04.0-LTS:latest` |
| `--azure-location` | `AZURE_LOCATION` | `westus` |
| `--azure-resource-group` | `AZURE_RESOURCE_GROUP` | `docker-machine` |
| `--azure-size` | `AZURE_SIZE` | `Standard_A2` |
| `--azure-ssh-user` | `AZURE_SSH_USER` | `docker-user` |
| `--azure-vnet` | `AZURE_VNET` | `docker-machine` |
| `--azure-subnet` | `AZURE_SUBNET` | `docker-machine` |
| `--azure-subnet-prefix` | `AZURE_SUBNET_PREFIX` | `192.168.0.0/16` |
| `--azure-availability-set` | `AZURE_AVAILABILITY_SET` | `docker-machine` |
| `--azure-open-port` | - | - |
| `--azure-private-ip-address` | - | - |
| `--azure-use-private-ip` | - | - |
| `--azure-no-public-ip` | - | - |
| `--azure-static-public-ip` | - | - |
| `--azure-docker-port` | `AZURE_DOCKER_PORT` | `2376` |
## Notes
Azure runs fully on the new [Azure Resource Manager (ARM)][arm] stack. Each
machine created comes with a few more Azure resources associated with it:
* A [Virtual Network][vnet] and a subnet under it is created to place your
machines into. This establishes a local network between your docker machines.
* An [Availability Set][av-set] is created to maximize availability of your
machines.
These are created once when the first machine is created and reused afterwards.
Although they are free resources, driver does a best effort to clean them up
after the last machine using these resources is removed.
Each machine is created with a public dynamic IP address for external
connectivity. All its ports (except Docker and SSH) are closed by default. You
can use `--azure-open-port` argument to specify multiple port numbers to be
accessible from Internet.
Once the machine is created, you can modify [Network Security Group][nsg]
rules and open ports of the machine from the [Azure Portal][portal].
[arm]: https://azure.microsoft.com/en-us/documentation/articles/resource-group-overview/
[nsg]: https://azure.microsoft.com/en-us/documentation/articles/virtual-networks-nsg/
[portal]: https://portal.azure.com/

View File

@ -0,0 +1,52 @@
<!--[metadata]>
+++
title = "Digital Ocean"
description = "Digital Ocean driver for machine"
keywords = ["machine, Digital Ocean, driver"]
[menu.main]
parent="smn_machine_drivers"
+++
<![end-metadata]-->
# Digital Ocean
Create Docker machines on [Digital Ocean](https://www.digitalocean.com/).
You need to create a personal access token under "Apps & API" in the Digital Ocean
Control Panel and pass that to `docker-machine create` with the `--digitalocean-access-token` option.
## Usage
$ docker-machine create --driver digitalocean --digitalocean-access-token=aa9399a2175a93b17b1c86c807e08d3fc4b79876545432a629602f61cf6ccd6b test-this
## Options
- `--digitalocean-access-token`: **required** Your personal access token for the Digital Ocean API.
- `--digitalocean-image`: The name of the Digital Ocean image to use.
- `--digitalocean-region`: The region to create the droplet in, see [Regions API](https://developers.digitalocean.com/documentation/v2/#regions) for how to get a list.
- `--digitalocean-size`: The size of the Digital Ocean droplet (larger than default options are of the form `2gb`).
- `--digitalocean-ipv6`: Enable IPv6 support for the droplet.
- `--digitalocean-private-networking`: Enable private networking support for the droplet.
- `--digitalocean-backups`: Enable Digital Oceans backups for the droplet.
- `--digitalocean-userdata`: Path to file containing User Data for the droplet.
- `--digitalocean-ssh-user`: SSH username.
- `--digitalocean-ssh-port`: SSH port.
- `--digitalocean-ssh-key-fingerprint`: Use an existing SSH key instead of creating a new one, see [SSH keys](https://developers.digitalocean.com/documentation/v2/#ssh-keys).
The DigitalOcean driver will use `ubuntu-15-10-x64` as the default image.
#### Environment variables and default values
| CLI option | Environment variable | Default |
| ----------------------------------- | --------------------------------- | ------------------ |
| **`--digitalocean-access-token`** | `DIGITALOCEAN_ACCESS_TOKEN` | - |
| `--digitalocean-image` | `DIGITALOCEAN_IMAGE` | `ubuntu-15-10-x64` |
| `--digitalocean-region` | `DIGITALOCEAN_REGION` | `nyc3` |
| `--digitalocean-size` | `DIGITALOCEAN_SIZE` | `512mb` |
| `--digitalocean-ipv6` | `DIGITALOCEAN_IPV6` | `false` |
| `--digitalocean-private-networking` | `DIGITALOCEAN_PRIVATE_NETWORKING` | `false` |
| `--digitalocean-backups` | `DIGITALOCEAN_BACKUPS` | `false` |
| `--digitalocean-userdata` | `DIGITALOCEAN_USERDATA` | - |
| `--digitalocean-ssh-user` | `DIGITALOCEAN_SSH_USER` | `root` |
| `--digitalocean-ssh-port` | `DIGITALOCEAN_SSH_PORT` | 22 |
| `--digitalocean-ssh-key-fingerprint`| `DIGITALOCEAN_SSH_KEY_FINGERPRINT`| - |

51
docs/drivers/exoscale.md Normal file
View File

@ -0,0 +1,51 @@
<!--[metadata]>
+++
title = "exoscale"
description = "exoscale driver for machine"
keywords = ["machine, exoscale, driver"]
[menu.main]
parent="smn_machine_drivers"
+++
<![end-metadata]-->
# Exoscale
Create machines on [exoscale](https://www.exoscale.ch/).
Get your API key and API secret key from [API details](https://portal.exoscale.ch/account/api) and pass them to `machine create` with the `--exoscale-api-key` and `--exoscale-api-secret-key` options.
## Usage
$ docker-machine create --driver exoscale --exoscale-api-key=API --exoscale-api-secret-key=SECRET vm
## Options
- `--exoscale-url`: Your API endpoint.
- `--exoscale-api-key`: **required** Your API key.
- `--exoscale-api-secret-key`: **required** Your API secret key.
- `--exoscale-instance-profile`: Instance profile.
- `--exoscale-disk-size`: Disk size for the host in GB (10, 50, 100, 200, 400).
- `--exoscale-image`: Image template (eg. ubuntu-14.04, ubuntu-15.10).
- `--exoscale-security-group`: Security group. It will be created if it doesn't exist.
- `--exoscale-availability-zone`: Exoscale availability zone.
- `--exoscale-ssh-user`: SSH username, which must match the default SSH user for the used image.
- `--exoscale-userdata`: Path to file containing user data for cloud-init.
If a custom security group is provided, you need to ensure that you allow TCP ports 22 and 2376 in an ingress rule. Moreover, if you want to use Swarm, also add TCP port 3376.
There is a limit to the number of docker machines that an anti-affinity group can have. This can be worked around by specifying an additional anti-affinity group using `--exoscale-affinity-group=docker-machineX`
#### Environment variables and default values
| CLI option | Environment variable | Default |
| ------------------------------- | ---------------------------- | --------------------------------- |
| `--exoscale-url` | `EXOSCALE_ENDPOINT` | `https://api.exoscale.ch/compute` |
| **`--exoscale-api-key`** | `EXOSCALE_API_KEY` | - |
| **`--exoscale-api-secret-key`** | `EXOSCALE_API_SECRET` | - |
| `--exoscale-instance-profile` | `EXOSCALE_INSTANCE_PROFILE` | `small` |
| `--exoscale-disk-size` | `EXOSCALE_DISK_SIZE` | `50` |
| `--exoscale-image` | `EXOSCALE_IMAGE` | `ubuntu-15.10` |
| `--exoscale-security-group` | `EXOSCALE_SECURITY_GROUP` | `docker-machine` |
| `--exoscale-availability-zone` | `EXOSCALE_AVAILABILITY_ZONE` | `ch-gva-2` |
| `--exoscale-ssh-user` | `EXOSCALE_SSH_USER` | `ubuntu` |
| `--exoscale-userdata` | `EXOSCALE_USERDATA` | - |

77
docs/drivers/gce.md Normal file
View File

@ -0,0 +1,77 @@
<!--[metadata]>
+++
title = "Google Compute Engine"
description = "Google Compute Engine driver for machine"
keywords = ["machine, Google Compute Engine, driver"]
[menu.main]
parent="smn_machine_drivers"
+++
<![end-metadata]-->
# Google Compute Engine
Create machines on [Google Compute Engine](https://cloud.google.com/compute/).
You will need a Google account and a project id.
See <https://cloud.google.com/compute/docs/projects> for details on projects.
### Credentials
The Google driver uses [Application Default Credentials](https://developers.google.com/identity/protocols/application-default-credentials)
to get authorization credentials for use in calling Google APIs.
So if `docker-machine` is used from a GCE host, authentication will happen automatically
via the built-in service account.
Otherwise, [install gcloud](https://cloud.google.com/sdk/) and get
through the oauth2 process with `gcloud auth login`.
### Example
To create a machine instance, specify `--driver google`, the project id and the machine name.
$ gcloud auth login
$ docker-machine create --driver google --google-project PROJECT_ID vm01
$ docker-machine create --driver google \
--google-project PROJECT_ID \
--google-zone us-central1-a \
--google-machine-type f1-micro \
vm02
### Options
- `--google-project`: **required** The id of your project to use when launching the instance.
- `--google-zone`: The zone to launch the instance.
- `--google-machine-type`: The type of instance.
- `--google-machine-image`: The absolute URL to a base VM image to instantiate.
- `--google-username`: The username to use for the instance.
- `--google-scopes`: The scopes for OAuth 2.0 to Access Google APIs. See [Google Compute Engine Doc](https://cloud.google.com/storage/docs/authentication).
- `--google-disk-size`: The disk size of instance.
- `--google-disk-type`: The disk type of instance.
- `--google-address`: Instance's static external IP (name or IP).
- `--google-preemptible`: Instance preemptibility.
- `--google-tags`: Instance tags (comma-separated).
- `--google-use-internal-ip`: When this option is used during create it will make docker-machine use internal rather than public NATed IPs. The flag is persistent in the sense that a machine created with it retains the IP. It's useful for managing docker machines from another machine on the same network e.g. while deploying swarm.
- `--google-use-internal-ip-only`: When this option is used during create, the new VM will not be assigned a public IP address. This is useful only when the host running `docker-machine` is located inside the Google Cloud infrastructure; otherwise, `docker-machine` can't reach the VM to provision the Docker daemon. The presence of this flag implies `--google-use-internal-ip`.
- `--google-use-existing`: Don't create a new VM, use an existing one. This is useful when you'd like to provision Docker on a VM you created yourself, maybe because it uses create options not supported by this driver.
The GCE driver will use the `ubuntu-1510-wily-v20151114` instance image unless otherwise specified. To obtain a
list of image URLs run:
gcloud compute images list --uri
#### Environment variables and default values
| CLI option | Environment variable | Default |
| -------------------------- | ------------------------ | ------------------------------------ |
| **`--google-project`** | `GOOGLE_PROJECT` | - |
| `--google-zone` | `GOOGLE_ZONE` | `us-central1-a` |
| `--google-machine-type` | `GOOGLE_MACHINE_TYPE` | `f1-standard-1` |
| `--google-machine-image` | `GOOGLE_MACHINE_IMAGE` | `ubuntu-1510-wily-v20151114` |
| `--google-username` | `GOOGLE_USERNAME` | `docker-user` |
| `--google-scopes` | `GOOGLE_SCOPES` | `devstorage.read_only,logging.write` |
| `--google-disk-size` | `GOOGLE_DISK_SIZE` | `10` |
| `--google-disk-type` | `GOOGLE_DISK_TYPE` | `pd-standard` |
| `--google-address` | `GOOGLE_ADDRESS` | - |
| `--google-preemptible` | `GOOGLE_PREEMPTIBLE` | - |
| `--google-tags` | `GOOGLE_TAGS` | - |
| `--google-use-internal-ip` | `GOOGLE_USE_INTERNAL_IP` | - |
| `--google-use-existing` | `GOOGLE_USE_EXISTING` | - |

65
docs/drivers/generic.md Normal file
View File

@ -0,0 +1,65 @@
<!--[metadata]>
+++
title = "Generic"
description = "Generic driver for machine"
keywords = ["machine, Generic, driver"]
[menu.main]
parent="smn_machine_drivers"
+++
<![end-metadata]-->
# Generic
Create machines using an existing VM/Host with SSH.
This is useful if you are using a provider that Machine does not support
directly or if you would like to import an existing host to allow Docker
Machine to manage.
The driver will perform a list of tasks on create:
- If docker is not running on the host, it will be installed automatically.
- It will update the host packages (`apt-get update`, `yum update`...).
- It will generate certificates to secure the docker daemon.
- The docker daemon will be restarted, thus all running containers will be stopped.
- The hostname will be changed to fit the machine name.
### Example
To create a machine instance, specify `--driver generic`, the IP address or DNS
name of the host and the path to the SSH private key authorized to connect
to the host.
$ docker-machine create \
--driver generic \
--generic-ip-address=203.0.113.81 \
--generic-ssh-key ~/.ssh/id_rsa \
vm
### Sudo privileges
The user that is used to SSH into the host can be specified with
`--generic-ssh-user` flag. This user has to have password-less sudo
privileges.
If it's not the case, you need to edit the `sudoers` file and configure the user
as a sudoer with `NOPASSWD`. See https://help.ubuntu.com/community/Sudoers.
### Options
- `--generic-engine-port`: Port to use for Docker Daemon (Note: This flag will not work with boot2docker).
- `--generic-ip-address`: **required** IP Address of host.
- `--generic-ssh-key`: Path to the SSH user private key.
- `--generic-ssh-user`: SSH username used to connect.
- `--generic-ssh-port`: Port to use for SSH.
> **Note**: You must use a base operating system supported by Machine.
#### Environment variables and default values
| CLI option | Environment variable | Default |
| -------------------------- | -------------------- | ------------------------- |
| `--generic-engine-port` | `GENERIC_ENGINE_PORT`| `2376` |
| **`--generic-ip-address`** | `GENERIC_IP_ADDRESS` | - |
| `--generic-ssh-key` | `GENERIC_SSH_KEY` | - |
| `--generic-ssh-user` | `GENERIC_SSH_USER` | `root` |
| `--generic-ssh-port` | `GENERIC_SSH_PORT` | `22` |

139
docs/drivers/hyper-v.md Normal file
View File

@ -0,0 +1,139 @@
<!--[metadata]>
+++
title = "Microsoft Hyper-V"
description = "Microsoft Hyper-V driver for machine"
keywords = ["machine, Microsoft Hyper-V, driver"]
[menu.main]
parent="smn_machine_drivers"
+++
<![end-metadata]-->
# Microsoft Hyper-V
Creates a Boot2Docker virtual machine locally on your Windows machine
using Hyper-V.
Hyper-V must be enabled on your desktop system. Docker for Windows automatically
enables it upon install. See this article on the Microsoft developer network for
[instructions on how to manually enable
Hyper-V](https://msdn.microsoft.com/en-us/virtualization/hyperv_on_windows/quick_start/walkthrough_install).
> **Notes**:
>
> * You will need to use an Administrator level account to create and manage Hyper-V machines.
>
>* You will need an existing virtual switch to use the
> driver. Hyper-V can share an external network interface (aka
> bridging), see [this blog](http://blogs.technet.com/b/canitpro/archive/2014/03/11/step-by-step-enabling-hyper-v-for-use-on-windows-8-1.aspx).
> If you would like to use NAT, create an internal network, and use
> [Internet Connection Sharing](http://www.packet6.com/allowing-windows-8-1-hyper-v-vm-to-work-with-wifi/).
>
> * This reference page includes an [example](#example) that shows how to use an elelvated (Administrator-level) PowerShell and how to create and use an external network switch.
## Usage
$ docker-machine create --driver hyperv vm
## Options
- `--hyperv-boot2docker-url`: The URL of the boot2docker ISO.
- `--hyperv-virtual-switch`: Name of the virtual switch to use.
- `--hyperv-disk-size`: Size of disk for the host in MB.
- `--hyperv-memory`: Size of memory for the host in MB.
- `--hyperv-cpu-count`: Number of CPUs for the host.
- `--hyperv-static-macaddress`: Hyper-V network adapter's static MAC address.
- `--hyperv-vlan-id`: Hyper-V network adapter's VLAN ID if any.
## Environment variables and default values
| CLI option | Environment variable | Default |
| ---------------------------- | -------------------------- | ------------------------ |
| `--hyperv-boot2docker-url` | `HYPERV_BOOT2DOCKER_URL` | _Latest boot2docker url_ |
| `--hyperv-virtual-switch` | `HYPERV_VIRTUAL_SWITCH` | _first found_ |
| `--hyperv-disk-size` | `HYPERV_DISK_SIZE` | `20000` |
| `--hyperv-memory` | `HYPERV_MEMORY` | `1024` |
| `--hyperv-cpu-count` | `HYPERV_CPU_COUNT` | `1` |
| `--hyperv-static-macaddress` | `HYPERV_STATIC_MACADDRESS` | _undefined_ |
| `--hyperv-cpu-count` | `HYPERV_VLAN_ID` | _undefined_ |
## Example
#### 1. Make sure Hyper-V is enabled
Hyper-V is automatically enabled on a Docker for Windows install. To enable it manually, see [instructions on how to manually enable Hyper-V](https://msdn.microsoft.com/en-us/virtualization/hyperv_on_windows/quick_start/walkthrough_install) on the Microsoft developer network.
#### 2. Set up a new external network switch
Make sure you have Ethernet connectivity while you are doing this.
Open the **Hyper-V Manager**. (On Windows 10, just search for the Hyper-V Manager in the search field in the lower left search field.)
Select the Virtual Switch Manager on the left-side **Actions** panel.
![Hyper-V manager](../img/hyperv-manager.png)
Set up a new external network switch to use instad of DockerNAT network switch (for Moby), which is set up by default when you install Docker for Windows. (Or if you already have another network switch set up, you can use that one.)
For this example, we created a virtual switch called `Primary Virtual Switch`.
![Defining a new virtual switch](../img/hyperv-network-switch.png)
#### 3. Reboot
See [this issue on virtualbox: Hangs on Waiting for VM to start #986](https://github.com/docker/machine/issues/986).
A reboot of your desktop system clears out any problems with the routing tables. Without a reboot first, `docker-machine create ...` might get hung up on `Waiting for VM to start`.
#### 4. Create the nodes with Docker Machine and the Microsoft Hyper-V driver
* Start an "elevated" PowerShell (i.e., running as administrator). To do this, search for PowerShell, right-click, and choose Run as administrator.
* Run the `docker-machine create` commands to create machines.
For example, if you follow along with the [Swarm mode
tutorial](/engine/swarm/swarm-tutorial/index.md) which asks you to create [three
networked host
machines](/engine/swarm/swarm-tutorial/index.md#three-networked-host-machines),
you can create these swarm nodes: `manager1`, `worker1`, `worker2`.
* Use the Microsoft Hyper-V driver and reference the new virtual switch you created.
docker-machine create -d hyperv --hyperv-virtual-switch "<NameOfVirtualSwitch>" <nameOfNode>
Here is an example of creating `manager1` node:
```shell
PS C:\WINDOWS\system32> docker-machine create -d hyperv --hyperv-virtual-switch "Primary Virtual Switch" manager1
Running pre-create checks...
Creating machine...
(manager1) Copying C:\Users\Vicky\.docker\machine\cache\boot2docker.iso to C:\Users\Vicky\.docker\machine\machines\manag
er1\boot2docker.iso...
(manager1) Creating SSH key...
(manager1) Creating VM...
(manager1) Using switch "Primary Virtual Switch"
(manager1) Creating VHD
(manager1) Starting VM...
(manager1) Waiting for host to start...
Waiting for machine to be running, this may take a few minutes...
Detecting operating system of created instance...
Waiting for SSH to be available...
Detecting the provisioner...
Provisioning with boot2docker...
Copying certs to the local machine directory...
Copying certs to the remote machine...
Setting Docker configuration on the remote daemon...
Checking connection to Docker...
Docker is up and running!
To see how to connect your Docker Client to the Docker Engine running on this virtual machine, run: C:\Program Files\Doc
ker\Docker\Resources\bin\docker-machine.exe env manager1
PS C:\WINDOWS\system32>
```
* Use the same process, driver and network switch to create the other nodes.
For our example, the commands will look like this:
```shell
docker-machine create -d hyperv --hyperv-virtual-switch "Primary Virtual Switch" worker1
docker-machine create -d hyperv --hyperv-virtual-switch "Primary Virtual Switch" worker2
```

28
docs/drivers/index.md Normal file
View File

@ -0,0 +1,28 @@
<!--[metadata]>
+++
title = "Drivers"
description = "Reference for drivers Docker Machine supports"
keywords = ["machine, drivers, supports"]
[menu.main]
parent="workw_machine"
identifier="smn_machine_drivers"
weight=90
+++
<![end-metadata]-->
# Supported Drivers
- [Amazon Web Services](aws.md)
- [Microsoft Azure](azure.md)
- [Digital Ocean](digital-ocean.md)
- [Exoscale](exoscale.md)
- [Google Compute Engine](gce.md)
- [Generic](generic.md)
- [Microsoft Hyper-V](hyper-v.md)
- [OpenStack](openstack.md)
- [Rackspace](rackspace.md)
- [IBM Softlayer](soft-layer.md)
- [Oracle VirtualBox](virtualbox.md)
- [VMware vCloud Air](vm-cloud.md)
- [VMware Fusion](vm-fusion.md)
- [VMware vSphere](vsphere.md)

77
docs/drivers/openstack.md Normal file
View File

@ -0,0 +1,77 @@
<!--[metadata]>
+++
title = "OpenStack"
description = "OpenStack driver for machine"
keywords = ["machine, OpenStack, driver"]
[menu.main]
parent="smn_machine_drivers"
+++
<![end-metadata]-->
# OpenStack
Create machines on [OpenStack](http://www.openstack.org/software/)
Mandatory:
- `--openstack-auth-url`: Keystone service base URL.
- `--openstack-flavor-id` or `--openstack-flavor-name`: Identify the flavor that will be used for the machine.
- `--openstack-image-id` or `--openstack-image-name`: Identify the image that will be used for the machine.
## Usage
$ docker-machine create --driver openstack vm
## Options
- `--openstack-active-timeout`: The timeout in seconds until the OpenStack instance must be active.
- `--openstack-availability-zone`: The availability zone in which to launch the server.
- `--openstack-domain-name` or `--openstack-domain-id`: Domain to use for authentication (Keystone v3 only).
- `--openstack-endpoint-type`: Endpoint type can be `internalURL`, `adminURL` on `publicURL`. If is a helper for the driver
to choose the right URL in the OpenStack service catalog. If not provided the default id `publicURL`
- `--openstack-floatingip-pool`: The IP pool that will be used to get a public IP can assign it to the machine. If there is an
IP address already allocated but not assigned to any machine, this IP will be chosen and assigned to the machine. If
there is no IP address already allocated a new IP will be allocated and assigned to the machine.
- `--openstack-keypair-name`: Specify the existing Nova keypair to use.
- `--openstack-insecure`: Explicitly allow openstack driver to perform "insecure" SSL (https) requests. The server's certificate will not be verified against any certificate authorities. This option should be used with caution.
- `--openstack-ip-version`: If the instance has both IPv4 and IPv6 address, you can select IP version. If not provided `4` will be used.
- `--openstack-net-name` or `--openstack-net-id`: Identify the private network the machine will be connected on. If your OpenStack project project contains only one private network it will be use automatically.
- `--openstack-password`: User password. It can be omitted if the standard environment variable `OS_PASSWORD` is set.
- `--openstack-private-key-file`: Used with `--openstack-keypair-name`, associates the private key to the keypair.
- `--openstack-region`: The region to work on. Can be omitted if there is only one region on the OpenStack.
- `--openstack-sec-groups`: If security groups are available on your OpenStack you can specify a comma separated list
to use for the machine (e.g. `secgrp001,secgrp002`).
- `--openstack-username`: User identifier to authenticate with.
- `--openstack-ssh-port`: Customize the SSH port if the SSH server on the machine does not listen on the default port.
- `--openstack-ssh-user`: The username to use for SSH into the machine. If not provided `root` will be used.
- `--openstack-tenant-name` or `--openstack-tenant-id`: Identify the tenant in which the machine will be created.
#### Environment variables and default values
| CLI option | Environment variable | Default |
| ------------------------------- | ---------------------- | ----------- |
| `--openstack-active-timeout` | `OS_ACTIVE_TIMEOUT` | `200` |
| `--openstack-auth-url` | `OS_AUTH_URL` | - |
| `--openstack-availability-zone` | `OS_AVAILABILITY_ZONE` | - |
| `--openstack-domain-id` | `OS_DOMAIN_ID` | - |
| `--openstack-domain-name` | `OS_DOMAIN_NAME` | - |
| `--openstack-endpoint-type` | `OS_ENDPOINT_TYPE` | `publicURL` |
| `--openstack-flavor-id` | `OS_FLAVOR_ID` | - |
| `--openstack-flavor-name` | `OS_FLAVOR_NAME` | - |
| `--openstack-floatingip-pool` | `OS_FLOATINGIP_POOL` | - |
| `--openstack-image-id` | `OS_IMAGE_ID` | - |
| `--openstack-image-name` | `OS_IMAGE_NAME` | - |
| `--openstack-insecure` | `OS_INSECURE` | `false` |
| `--openstack-ip-version` | `OS_IP_VERSION` | `4` |
| `--openstack-keypair-name` | `OS_KEYPAIR_NAME` | - |
| `--openstack-net-id` | `OS_NETWORK_ID` | - |
| `--openstack-net-name` | `OS_NETWORK_NAME` | - |
| `--openstack-password` | `OS_PASSWORD` | - |
| `--openstack-private-key-file` | `OS_PRIVATE_KEY_FILE` | - |
| `--openstack-region` | `OS_REGION_NAME` | - |
| `--openstack-sec-groups` | `OS_SECURITY_GROUPS` | - |
| `--openstack-ssh-port` | `OS_SSH_PORT` | `22` |
| `--openstack-ssh-user` | `OS_SSH_USER` | `root` |
| `--openstack-tenant-id` | `OS_TENANT_ID` | - |
| `--openstack-tenant-name` | `OS_TENANT_NAME` | - |
| `--openstack-username` | `OS_USERNAME` | - |

57
docs/drivers/os-base.md Normal file
View File

@ -0,0 +1,57 @@
<!--[metadata]>
+++
title = "Driver options and operating system defaults"
description = "Identify active machines"
keywords = ["machine, driver, base, operating system"]
[menu.main]
parent="smn_machine_drivers"
weight=-1
+++
<![end-metadata]-->
# Driver options and operating system defaults
When Docker Machine provisions containers on local network provider or with a
remote, cloud provider such as Amazon Web Services, you must define both the
driver for your provider and a base operating system. There are over 10
supported drivers and a generic driver for adding machines for other providers.
Each driver has a set of options specific to that provider. These options
provide information to machine such as connection credentials, ports, and so
forth. For example, to create an Azure machine:
Grab your subscription ID from the portal, then run `docker-machine create` with
these details:
```bash
$ docker-machine create -d azure --azure-subscription-id="SUB_ID" --azure-subscription-cert="mycert.pem" A-VERY-UNIQUE-NAME
```
To see a list of providers and review the options available to a provider, see
the reference for that driver.
In addition to the provider, you have the option of identifying a base operating
system. It is an option because Docker Machine has defaults for both local and
remote providers. For local providers such as VirtualBox, Fusion, Hyper-V, and
so forth, the default base operating system is Boot2Docker. For cloud providers,
the base operating system is the latest Ubuntu LTS the provider supports.
| Operating System | Version | Notes |
| ----------------------- | ------- | ------------------ |
| Boot2Docker | 1.5+ | default for local |
| Ubuntu | 12.04+ | default for remote |
| RancherOS | 0.3+ | |
| Debian | 8.0+ | experimental |
| RedHat Enterprise Linux | 7.0+ | experimental |
| CentOS | 7+ | experimental |
| Fedora | 21+ | experimental |
To use a different base operating system on a remote provider, specify the
provider's image flag and one of its available images. For example, to select a
`debian-8-x64` image on DigitalOcean you would supply the
`--digitalocean-image=debian-8-x64` flag.
If you change the base image for a provider, you may also need to change
the SSH user. For example, the default Red Hat AMI on EC2 expects the
SSH user to be `ec2-user`, so you would have to specify this with
`--amazonec2-ssh-user ec2-user`.

45
docs/drivers/rackspace.md Normal file
View File

@ -0,0 +1,45 @@
<!--[metadata]>
+++
title = "Rackspace"
description = "Rackspace driver for machine"
keywords = ["machine, Rackspace, driver"]
[menu.main]
parent="smn_machine_drivers"
+++
<![end-metadata]-->
# Rackspace
Create machines on [Rackspace cloud](http://www.rackspace.com/cloud)
## Usage
$ docker-machine create --driver rackspace --rackspace-username=user --rackspace-api-key=KEY --rackspace-region=region vm
## Options
- `--rackspace-username`: **required** Rackspace account username.
- `--rackspace-api-key`: **required** Rackspace API key.
- `--rackspace-region`: **required** Rackspace region name.
- `--rackspace-endpoint-type`: Rackspace endpoint type (`adminURL`, `internalURL` or the default `publicURL`).
- `--rackspace-image-id`: Rackspace image ID. Default: Ubuntu 15.10 (Wily Werewolf) (PVHVM).
- `--rackspace-flavor-id`: Rackspace flavor ID. Default: General Purpose 1GB.
- `--rackspace-ssh-user`: SSH user for the newly booted machine.
- `--rackspace-ssh-port`: SSH port for the newly booted machine.
- `--rackspace-docker-install`: Set if Docker has to be installed on the machine.
The Rackspace driver will use `59a3fadd-93e7-4674-886a-64883e17115f` (Ubuntu 15.10) by default.
#### Environment variables and default values
| CLI option | Environment variable | Default |
| ---------------------------- | -------------------- | -------------------------------------- |
| **`--rackspace-username`** | `OS_USERNAME` | - |
| **`--rackspace-api-key`** | `OS_API_KEY` | - |
| **`--rackspace-region`** | `OS_REGION_NAME` | - |
| `--rackspace-endpoint-type` | `OS_ENDPOINT_TYPE` | `publicURL` |
| `--rackspace-image-id` | - | `59a3fadd-93e7-4674-886a-64883e17115f` |
| `--rackspace-flavor-id` | `OS_FLAVOR_ID` | `general1-1` |
| `--rackspace-ssh-user` | - | `root` |
| `--rackspace-ssh-port` | - | `22` |
| `--rackspace-docker-install` | - | `true` |

View File

@ -0,0 +1,60 @@
<!--[metadata]>
+++
title = "IBM Softlayer"
description = "IBM Softlayer driver for machine"
keywords = ["machine, IBM Softlayer, driver"]
[menu.main]
parent="smn_machine_drivers"
+++
<![end-metadata]-->
# IBM Softlayer
Create machines on [Softlayer](http://softlayer.com).
You need to generate an API key in the softlayer control panel.
[Retrieve your API key](http://knowledgelayer.softlayer.com/procedure/retrieve-your-api-key)
## Usage
$ docker-machine create --driver softlayer --softlayer-user=user --softlayer-api-key=KEY --softlayer-domain=domain vm
## Options
- `--softlayer-memory`: Memory for host in MB.
- `--softlayer-disk-size`: A value of `0` will set the SoftLayer default.
- `--softlayer-user`: **required** Username for your SoftLayer account, api key needs to match this user.
- `--softlayer-api-key`: **required** API key for your user account.
- `--softlayer-region`: SoftLayer region.
- `--softlayer-cpu`: Number of CPUs for the machine.
- `--softlayer-hostname`: Hostname for the machine.
- `--softlayer-domain`: **required** Domain name for the machine.
- `--softlayer-api-endpoint`: Change SoftLayer API endpoint.
- `--softlayer-hourly-billing`: Specifies that hourly billing should be used, otherwise monthly billing is used.
- `--softlayer-local-disk`: Use local machine disk instead of SoftLayer SAN.
- `--softlayer-private-net-only`: Disable public networking.
- `--softlayer-image`: OS Image to use.
- `--softlayer-public-vlan-id`: Your public VLAN ID.
- `--softlayer-private-vlan-id`: Your private VLAN ID.
The SoftLayer driver will use `UBUNTU_LATEST` as the image type by default.
#### Environment variables and default values
| CLI option | Environment variable | Default |
| ------------------------------ | --------------------------- | --------------------------- |
| `--softlayer-memory` | `SOFTLAYER_MEMORY` | `1024` |
| `--softlayer-disk-size` | `SOFTLAYER_DISK_SIZE` | `0` |
| **`--softlayer-user`** | `SOFTLAYER_USER` | - |
| **`--softlayer-api-key`** | `SOFTLAYER_API_KEY` | - |
| `--softlayer-region` | `SOFTLAYER_REGION` | `dal01` |
| `--softlayer-cpu` | `SOFTLAYER_CPU` | `1` |
| `--softlayer-hostname` | `SOFTLAYER_HOSTNAME` | `docker` |
| **`--softlayer-domain`** | `SOFTLAYER_DOMAIN` | - |
| `--softlayer-api-endpoint` | `SOFTLAYER_API_ENDPOINT` | `api.softlayer.com/rest/v3` |
| `--softlayer-hourly-billing` | `SOFTLAYER_HOURLY_BILLING` | `false` |
| `--softlayer-local-disk` | `SOFTLAYER_LOCAL_DISK` | `false` |
| `--softlayer-private-net-only` | `SOFTLAYER_PRIVATE_NET` | `false` |
| `--softlayer-image` | `SOFTLAYER_IMAGE` | `UBUNTU_LATEST` |
| `--softlayer-public-vlan-id` | `SOFTLAYER_PUBLIC_VLAN_ID` | `0` |
| `--softlayer-private-vlan-id` | `SOFTLAYER_PRIVATE_VLAN_ID` | `0` |

View File

@ -0,0 +1,98 @@
<!--[metadata]>
+++
title = "Oracle VirtualBox"
description = "Oracle VirtualBox driver for machine"
keywords = ["machine, Oracle VirtualBox, driver"]
[menu.main]
parent="smn_machine_drivers"
+++
<![end-metadata]-->
# Oracle VirtualBox
Create machines locally using [VirtualBox](https://www.virtualbox.org/).
This driver requires VirtualBox 5+ to be installed on your host.
Using VirtualBox 4.3+ should work but will give you a warning. Older versions
will refuse to work.
## Usage
$ docker-machine create --driver=virtualbox vbox-test
You can create an entirely new machine or you can convert a Boot2Docker VM into
a machine by importing the VM. To convert a Boot2Docker VM, you'd use the following
command:
$ docker-machine create -d virtualbox --virtualbox-import-boot2docker-vm boot2docker-vm b2d
The size of the VM's disk can be configured this way:
$ docker-machine create -d virtualbox --virtualbox-disk-size "100000" large
## Options
- `--virtualbox-memory`: Size of memory for the host in MB.
- `--virtualbox-cpu-count`: Number of CPUs to use to create the VM. Defaults to single CPU.
- `--virtualbox-disk-size`: Size of disk for the host in MB.
- `--virtualbox-host-dns-resolver`: Use the host DNS resolver. (Boolean value, defaults to false)
- `--virtualbox-boot2docker-url`: The URL of the boot2docker image. Defaults to the latest available version.
- `--virtualbox-import-boot2docker-vm`: The name of a Boot2Docker VM to import.
- `--virtualbox-hostonly-cidr`: The CIDR of the host only adapter.
- `--virtualbox-hostonly-nictype`: Host Only Network Adapter Type. Possible values are are '82540EM' (Intel PRO/1000), 'Am79C973' (PCnet-FAST III) and 'virtio' Paravirtualized network adapter.
- `--virtualbox-hostonly-nicpromisc`: Host Only Network Adapter Promiscuous Mode. Possible options are deny , allow-vms, allow-all
- `--virtualbox-no-share`: Disable the mount of your home directory
- `--virtualbox-no-dns-proxy`: Disable proxying all DNS requests to the host (Boolean value, default to false)
- `--virtualbox-no-vtx-check`: Disable checking for the availability of hardware virtualization before the vm is started
The `--virtualbox-boot2docker-url` flag takes a few different forms. By
default, if no value is specified for this flag, Machine will check locally for
a boot2docker ISO. If one is found, that will be used as the ISO for the
created machine. If one is not found, the latest ISO release available on
[boot2docker/boot2docker](https://github.com/boot2docker/boot2docker) will be
downloaded and stored locally for future use. Note that this means you must run
`docker-machine upgrade` deliberately on a machine if you wish to update the "cached"
boot2docker ISO.
This is the default behavior (when `--virtualbox-boot2docker-url=""`), but the
option also supports specifying ISOs by the `http://` and `file://` protocols.
`file://` will look at the path specified locally to locate the ISO: for
instance, you could specify `--virtualbox-boot2docker-url
file://$HOME/Downloads/rc.iso` to test out a release candidate ISO that you have
downloaded already. You could also just get an ISO straight from the Internet
using the `http://` form.
To customize the host only adapter, you can use the `--virtualbox-hostonly-cidr`
flag. This will specify the host IP and Machine will calculate the VirtualBox
DHCP server address (a random IP on the subnet between `.1` and `.25`) so
it does not clash with the specified host IP.
Machine will also specify the DHCP lower bound to `.100` and the upper bound
to `.254`. For example, a specified CIDR of `192.168.24.1/24` would have a
DHCP server between `192.168.24.2-25`, a lower bound of `192.168.24.100` and
upper bound of `192.168.24.254`.
#### Environment variables and default values
| CLI option | Environment variable | Default |
| ------------------------------------ | ---------------------------------- | ------------------------ |
| `--virtualbox-memory` | `VIRTUALBOX_MEMORY_SIZE` | `1024` |
| `--virtualbox-cpu-count` | `VIRTUALBOX_CPU_COUNT` | `1` |
| `--virtualbox-disk-size` | `VIRTUALBOX_DISK_SIZE` | `20000` |
| `--virtualbox-host-dns-resolver` | `VIRTUALBOX_HOST_DNS_RESOLVER` | `false` |
| `--virtualbox-boot2docker-url` | `VIRTUALBOX_BOOT2DOCKER_URL` | _Latest boot2docker url_ |
| `--virtualbox-import-boot2docker-vm` | `VIRTUALBOX_BOOT2DOCKER_IMPORT_VM` | `boot2docker-vm` |
| `--virtualbox-hostonly-cidr` | `VIRTUALBOX_HOSTONLY_CIDR` | `192.168.99.1/24` |
| `--virtualbox-hostonly-nictype` | `VIRTUALBOX_HOSTONLY_NIC_TYPE` | `82540EM` |
| `--virtualbox-hostonly-nicpromisc` | `VIRTUALBOX_HOSTONLY_NIC_PROMISC` | `deny` |
| `--virtualbox-no-share` | `VIRTUALBOX_NO_SHARE` | `false` |
| `--virtualbox-no-dns-proxy` | `VIRTUALBOX_NO_DNS_PROXY` | `false` |
| `--virtualbox-no-vtx-check` | `VIRTUALBOX_NO_VTX_CHECK` | `false` |
## Known Issues
Vboxfs suffers from a [longstanding bug](https://www.virtualbox.org/ticket/9069)
causing [sendfile(2)](http://linux.die.net/man/2/sendfile) to serve cached file
contents.
This will often cause problems when using a web server such as nginx to serve
static files from a shared volume. For development environments, a good
workaround is to disable sendfile in your server configuration.

55
docs/drivers/vm-cloud.md Normal file
View File

@ -0,0 +1,55 @@
<!--[metadata]>
+++
title = "VMware vCloud Air"
description = "VMware vCloud Air driver for machine"
keywords = ["machine, VMware vCloud Air, driver"]
[menu.main]
parent="smn_machine_drivers"
+++
<![end-metadata]-->
# VMware vCloud Air
Creates machines on [vCloud Air](http://vcloud.vmware.com) subscription service. You need an account within an existing subscription of vCloud Air VPC or Dedicated Cloud.
## Usage
$ docker-machine create --driver vmwarevcloudair --vmwarevcloudair-username=user --vmwarevcloudair-password=SECRET vm
## Options
- `--vmwarevcloudair-username`: **required** vCloud Air Username.
- `--vmwarevcloudair-password`: **required** vCloud Air Password.
- `--vmwarevcloudair-computeid`: Compute ID (if using Dedicated Cloud).
- `--vmwarevcloudair-vdcid`: Virtual Data Center ID.
- `--vmwarevcloudair-orgvdcnetwork`: Organization VDC Network to attach.
- `--vmwarevcloudair-edgegateway`: Organization Edge Gateway.
- `--vmwarevcloudair-publicip`: Org Public IP to use.
- `--vmwarevcloudair-catalog`: Catalog.
- `--vmwarevcloudair-catalogitem`: Catalog Item.
- `--vmwarevcloudair-provision`: Install Docker binaries.
- `--vmwarevcloudair-cpu-count`: VM CPU Count.
- `--vmwarevcloudair-memory-size`: VM Memory Size in MB.
- `--vmwarevcloudair-ssh-port`: SSH port.
- `--vmwarevcloudair-docker-port`: Docker port.
The VMware vCloud Air driver will use the `Ubuntu Server 12.04 LTS (amd64 20140927)` image by default.
#### Environment variables and default values
| CLI option | Environment variable | Default |
| --------------------------------- | ------------------------- | ------------------------------------------ |
| **`--vmwarevcloudair-username`** | `VCLOUDAIR_USERNAME` | - |
| **`--vmwarevcloudair-password`** | `VCLOUDAIR_PASSWORD` | - |
| `--vmwarevcloudair-computeid` | `VCLOUDAIR_COMPUTEID` | - |
| `--vmwarevcloudair-vdcid` | `VCLOUDAIR_VDCID` | - |
| `--vmwarevcloudair-orgvdcnetwork` | `VCLOUDAIR_ORGVDCNETWORK` | `<vdcid>-default-routed` |
| `--vmwarevcloudair-edgegateway` | `VCLOUDAIR_EDGEGATEWAY` | `<vdcid>` |
| `--vmwarevcloudair-publicip` | `VCLOUDAIR_PUBLICIP` | - |
| `--vmwarevcloudair-catalog` | `VCLOUDAIR_CATALOG` | `Public Catalog` |
| `--vmwarevcloudair-catalogitem` | `VCLOUDAIR_CATALOGITEM` | `Ubuntu Server 12.04 LTS (amd64 20140927)` |
| `--vmwarevcloudair-provision` | `VCLOUDAIR_PROVISION` | `true` |
| `--vmwarevcloudair-cpu-count` | `VCLOUDAIR_CPU_COUNT` | `1` |
| `--vmwarevcloudair-memory-size` | `VCLOUDAIR_MEMORY_SIZE` | `2048` |
| `--vmwarevcloudair-ssh-port` | `VCLOUDAIR_SSH_PORT` | `22` |
| `--vmwarevcloudair-docker-port` | `VCLOUDAIR_DOCKER_PORT` | `2376` |

38
docs/drivers/vm-fusion.md Normal file
View File

@ -0,0 +1,38 @@
<!--[metadata]>
+++
title = "VMware Fusion"
description = "VMware Fusion driver for machine"
keywords = ["machine, VMware Fusion, driver"]
[menu.main]
parent="smn_machine_drivers"
+++
<![end-metadata]-->
# VMware Fusion
Creates machines locally on [VMware Fusion](http://www.vmware.com/products/fusion). Requires VMware Fusion to be installed.
## Usage
$ docker-machine create --driver vmwarefusion vm
## Options
- `--vmwarefusion-boot2docker-url`: URL for boot2docker image.
- `--vmwarefusion-cpu-count`: Number of CPUs for the machine (-1 to use the number of CPUs available)
- `--vmwarefusion-disk-size`: Size of disk for host VM (in MB).
- `--vmwarefusion-memory-size`: Size of memory for host VM (in MB).
- `--vmwarefusion-no-share`: Disable the mount of your home directory.
The VMware Fusion driver uses the latest boot2docker image.
See [frapposelli/boot2docker](https://github.com/frapposelli/boot2docker/tree/vmware-64bit)
#### Environment variables and default values
| CLI option | Environment variable | Default |
| -------------------------------- | ------------------------ | ------------------------ |
| `--vmwarefusion-boot2docker-url` | `FUSION_BOOT2DOCKER_URL` | _Latest boot2docker url_ |
| `--vmwarefusion-cpu-count` | `FUSION_CPU_COUNT` | `1` |
| `--vmwarefusion-disk-size` | `FUSION_DISK_SIZE` | `20000` |
| `--vmwarefusion-memory-size` | `FUSION_MEMORY_SIZE` | `1024` |
| `--vmwarefusion-no-share` | `FUSION_NO_SHARE` | `false` |

53
docs/drivers/vsphere.md Normal file
View File

@ -0,0 +1,53 @@
<!--[metadata]>
+++
title = "VMware vSphere"
description = "VMware vSphere driver for machine"
keywords = ["machine, VMware vSphere, driver"]
[menu.main]
parent="smn_machine_drivers"
+++
<![end-metadata]-->
# VMware vSphere
Creates machines on a [VMware vSphere](http://www.vmware.com/products/vsphere) Virtual Infrastructure. The machine must have a working vSphere ESXi installation. You can use a paid license or free 60 day trial license. Your installation may also include an optional VCenter server.
## Usage
$ docker-machine create --driver vmwarevsphere --vmwarevsphere-username=user --vmwarevsphere-password=SECRET vm
## Options
- `--vmwarevsphere-username`: **required** vSphere Username.
- `--vmwarevsphere-password`: **required** vSphere Password.
- `--vmwarevsphere-cpu-count`: CPU number for Docker VM.
- `--vmwarevsphere-memory-size`: Size of memory for Docker VM (in MB).
- `--vmwarevsphere-disk-size`: Size of disk for Docker VM (in MB).
- `--vmwarevsphere-boot2docker-url`: URL for boot2docker image.
- `--vmwarevsphere-vcenter`: IP/hostname for vCenter (or ESXi if connecting directly to a single host).
- `--vmwarevsphere-vcenter-port`: vSphere Port for vCenter.
- `--vmwarevsphere-network`: Network where the Docker VM will be attached.
- `--vmwarevsphere-datastore`: Datastore for Docker VM.
- `--vmwarevsphere-datacenter`: Datacenter for Docker VM (must be set to `ha-datacenter` when connecting to a single host).
- `--vmwarevsphere-pool`: Resource pool for Docker VM.
- `--vmwarevsphere-hostsystem`: vSphere compute resource where the docker VM will be instantiated (use <cluster>/* or <cluster>/<host> if using a cluster).
The VMware vSphere driver uses the latest boot2docker image.
#### Environment variables and default values
| CLI option | Environment variable | Default |
| --------------------------------- | ------------------------- | ------------------------ |
| **`--vmwarevsphere-username`** | `VSPHERE_USERNAME` | - |
| **`--vmwarevsphere-password`** | `VSPHERE_PASSWORD` | - |
| `--vmwarevsphere-cpu-count` | `VSPHERE_CPU_COUNT` | `2` |
| `--vmwarevsphere-memory-size` | `VSPHERE_MEMORY_SIZE` | `2048` |
| `--vmwarevsphere-boot2docker-url` | `VSPHERE_BOOT2DOCKER_URL` | _Latest boot2docker url_ |
| `--vmwarevsphere-vcenter` | `VSPHERE_VCENTER` | - |
| `--vmwarevsphere-vcenter-port` | `VSPHERE_VCENTER_PORT` | 443 |
| `--vmwarevsphere-disk-size` | `VSPHERE_DISK_SIZE` | `20000` |
| `--vmwarevsphere-network` | `VSPHERE_NETWORK` | - |
| `--vmwarevsphere-datastore` | `VSPHERE_DATASTORE` | - |
| `--vmwarevsphere-datacenter` | `VSPHERE_DATACENTER` | - |
| `--vmwarevsphere-pool` | `VSPHERE_POOL` | - |
| `--vmwarevsphere-hostsystem` | `VSPHERE_HOSTSYSTEM` | - |

132
docs/examples/aws.md Normal file
View File

@ -0,0 +1,132 @@
<!--[metadata]>
+++
title = "Provision AWS EC2 Instances"
description = "Using Docker Machine to provision hosts on AWS"
keywords = ["docker, machine, cloud, aws"]
[menu.main]
parent="cloud_examples"
weight=2
+++
<![end-metadata]-->
# Amazon Web Services (AWS) EC2 example
Follow along with this example to create a Dockerized <a href="https://aws.amazon.com/" target="_blank"> Amazon Web Services (AWS)</a> EC2 instance.
### Step 1. Sign up for AWS and configure credentials
1. If you are not already an AWS user, sign up for <a href="https://aws.amazon.com/" target="_blank"> AWS</a> to create an account and get root access to EC2 cloud computers.
If you have an Amazon account, you can use it as your root user account.
2. Create an IAM (Identity and Access Management) administrator user, an admin group, and a key pair associated with a region.
From the AWS menus, select **Services** > **IAM** to get started.
To create machines on AWS, you must supply two parameters:
* an AWS Access Key ID
* an AWS Secret Access Key
See the AWS documentation on <a href="http://docs.aws.amazon.com/AWSEC2/latest/UserGuide/get-set-up-for-amazon-ec2.html" target="_blank">Setting Up with Amazon EC2</a>. Follow the steps for "Create an IAM User" and "Create a Key Pair".
### Step 2. Use Machine to create the instance
1. Optionally, create an AWS credential file.
You can create an `~/.aws/credentials` file to hold your AWS keys so that you don't have to type them every time you run the `docker-machine create` command. Here is an example of a credentials file.
[default]
aws_access_key_id = AKID1234567890
aws_secret_access_key = MY-SECRET-KEY
2. Run `docker-machine create` with the `amazonec2` driver, your keys, and a name for the new instance.
**Using a credentials file**
If you specified your keys in a credentials file, this command looks like this to create an instance called `aws-sandbox`:
docker-machine create --driver amazonec2 aws-sandbox
**Specifying keys at the command line**
If you don't have a credentials file, you can use the flags `--amazonec2-access-key` and `--amazonec2-secret-key` on the command line:
$ docker-machine create --driver amazonec2 --amazonec2-access-key AKI******* --amazonec2-secret-key 8T93C******* aws-sandbox
**Specifying a region**
By default, the driver creates new instances in region us-east-1 (North Virginia). You can specify a different region by using the `--amazonec2-region` flag. For example, this command creates a machine called "aws-01" in us-west-1 (Northern California).
$ docker-machine create --driver amazonec2 --amazonec2-region us-west-1 aws-01
3. Go to the AWS EC2 Dashboard to view the new instance.
Log into AWS with your IAM credentials, and navigate to your EC2 Running Instances.
![instance on AWS EC2 Dashboard](../img/aws-instance-east.png)
**Note**: Make sure you set the region appropriately from the menu in the upper right; otherwise, you won't see the new instance. If you did not specify a region as part of `docker-machine create` (with the optional `--amazonec2-region` flag), then the region will be US East, which is the default.
3. At the command terminal, run `docker-machine ls`.
$ docker-machine ls
NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS
aws-sandbox * amazonec2 Running tcp://52.90.113.128:2376 v1.10.0
default - virtualbox Running tcp://192.168.99.100:2376 v1.10.0-rc4
aws-sandbox - digitalocean Running tcp://104.131.43.236:2376 v1.9.1
The new `aws-sandbox` instance is running, and it is the active host as indicated by the asterisk (*). When you create a new machine, your command shell automatically connects to it. If for some reason your new machine is not the active host, you'll need to run `docker-machine env aws-sandbox`, followed by `eval $(docker-machine env aws-sandbox)` to connect to it.
### Step 3. Run Docker commands on the instance
1. Run some `docker-machine` commands to inspect the remote host. For example, `docker-machine ip <machine>` gets the host IP address and `docker-machine inspect <machine>` lists all the details.
$ docker-machine ip
192.168.99.100
$ docker-machine inspect aws-sandbox
{
"ConfigVersion": 3,
"Driver": {
"IPAddress": "52.90.113.128",
"MachineName": "aws-sandbox",
"SSHUser": "ubuntu",
"SSHPort": 22,
...
2. Verify Docker Engine is installed correctly by running `docker` commands.
Start with something basic like `docker run hello-world`, or for a more interesting test, run a Dockerized webserver on your new remote machine.
In this example, the `-p` option is used to expose port 80 from the `nginx` container and make it accessible on port `8000` of the `aws-sandbox` host.
$ docker run -d -p 8000:80 --name webserver kitematic/hello-world-nginx
Unable to find image 'kitematic/hello-world-nginx:latest' locally
latest: Pulling from kitematic/hello-world-nginx
a285d7f063ea: Pull complete
2d7baf27389b: Pull complete
...
Digest: sha256:ec0ca6dcb034916784c988b4f2432716e2e92b995ac606e080c7a54b52b87066
Status: Downloaded newer image for kitematic/hello-world-nginx:latest
942dfb4a0eaae75bf26c9785ade4ff47ceb2ec2a152be82b9d7960e8b5777e65
In a web browser, go to `http://<host_ip>:8000` to bring up the webserver home page. You got the `<host_ip>` from the output of the `docker-machine ip <machine>` command you ran in a previous step. Use the port you exposed in the `docker run` command.
![nginx webserver](../img/nginx-webserver.png)
### Step 4. Use Machine to remove the instance
To remove an instance and all of its containers and images, first stop the machine, then use `docker-machine rm`:
$ docker-machine stop aws-sandbox
$ docker-machine rm aws-sandbox
Do you really want to remove "aws-sandbox"? (y/n): y
Successfully removed aws-sandbox
## Where to go next
- [Understand Machine concepts](../concepts.md)
- [Docker Machine driver reference](../drivers/index.md)
- [Docker Machine subcommand reference](../reference/index.md)
- [Provision a Docker Swarm cluster with Docker Machine](/swarm/provision-with-machine.md)

17
docs/examples/index.md Normal file
View File

@ -0,0 +1,17 @@
<!--[metadata]>
+++
title = "Learn by example"
description = "Examples of cloud installs"
keywords = ["docker, machine, amazonec2, azure, digitalocean, google, openstack, rackspace, softlayer, virtualbox, vmwarefusion, vmwarevcloudair, vmwarevsphere, exoscale"]
[menu.main]
parent="workw_machine"
identifier="cloud_examples"
weight="-50"
+++
<![end-metadata]-->
# Learn by example
- [Digital Ocean Example](ocean.md)
- [AWS Example](aws.md)

143
docs/examples/ocean.md Normal file
View File

@ -0,0 +1,143 @@
<!--[metadata]>
+++
title = "Provision Digital Ocean Droplets"
description = "Using Docker Machine to provision hosts on Digital Ocean"
keywords = ["docker, machine, cloud, digital ocean"]
[menu.main]
parent="cloud_examples"
weight=1
+++
<![end-metadata]-->
# Digital Ocean example
Follow along with this example to create a Dockerized <a href="https://digitalocean.com" target="_blank">Digital Ocean</a> Droplet (cloud host).
### Step 1. Create a Digital Ocean account
If you have not done so already, go to <a href="https://digitalocean.com" target="_blank">Digital Ocean</a>, create an account, and log in.
### Step 2. Generate a personal access token
To generate your access token:
1. Go to the Digital Ocean administrator console and click **API** in the header.
![Click API in Digital Ocean console](../img/ocean_click_api.png)
2. Click **Generate New Token** to get to the token generator.
![Generate token](../img/ocean_gen_token.png)
3. Give the token a clever name (e.g. "machine"), make sure the **Write (Optional)** checkbox is checked, and click **Generate Token**.
![Name and generate token](../img/ocean_token_create.png)
4. Grab (copy to clipboard) the generated big long hex string and store it somewhere safe.
![Copy and save personal access token](../img/ocean_save_token.png)
This is the personal access token you'll use in the next step to create your cloud server.
### Step 3. Use Machine to create the Droplet
1. Run `docker-machine create` with the `digitalocean` driver and pass your key to the `--digitalocean-access-token` flag, along with a name for the new cloud server.
For this example, we'll call our new Droplet "docker-sandbox".
$ docker-machine create --driver digitalocean --digitalocean-access-token xxxxx docker-sandbox
Running pre-create checks...
Creating machine...
(docker-sandbox) OUT | Creating SSH key...
(docker-sandbox) OUT | Creating Digital Ocean droplet...
(docker-sandbox) OUT | Waiting for IP address to be assigned to the Droplet...
Waiting for machine to be running, this may take a few minutes...
Machine is running, waiting for SSH to be available...
Detecting operating system of created instance...
Detecting the provisioner...
Provisioning created instance...
Copying certs to the local machine directory...
Copying certs to the remote machine...
Setting Docker configuration on the remote daemon...
To see how to connect Docker to this machine, run: docker-machine env docker-sandbox
When the Droplet is created, Docker generates a unique SSH key and stores it on your local system in `~/.docker/machines`. Initially, this is used to provision the host. Later, it's used under the hood to access the Droplet directly with the `docker-machine ssh` command. Docker Engine is installed on the cloud server and the daemon is configured to accept remote connections over TCP using TLS for authentication.
2. Go to the Digital Ocean console to view the new Droplet.
![Droplet in Digital Ocean created with Machine](../img/ocean_droplet.png)
3. At the command terminal, run `docker-machine ls`.
$ docker-machine ls
NAME ACTIVE DRIVER STATE URL SWARM
default - virtualbox Running tcp://192.168.99.100:2376
docker-sandbox * digitalocean Running tcp://45.55.139.48:2376
The new `docker-sandbox` machine is running, and it is the active host as indicated by the asterisk (*). When you create a new machine, your command shell automatically connects to it. If for some reason your new machine is not the active host, you'll need to run `docker-machine env docker-sandbox`, followed by `eval $(docker-machine env docker-sandbox)` to connect to it.
### Step 4. Run Docker commands on the Droplet
1. Run some `docker-machine` commands to inspect the remote host. For example, `docker-machine ip <machine>` gets the host IP adddress and `docker-machine inspect <machine>` lists all the details.
$ docker-machine ip docker-sandbox
104.131.43.236
$ docker-machine inspect docker-sandbox
{
"ConfigVersion": 3,
"Driver": {
"IPAddress": "104.131.43.236",
"MachineName": "docker-sandbox",
"SSHUser": "root",
"SSHPort": 22,
"SSHKeyPath": "/Users/samanthastevens/.docker/machine/machines/docker-sandbox/id_rsa",
"StorePath": "/Users/samanthastevens/.docker/machine",
"SwarmMaster": false,
"SwarmHost": "tcp://0.0.0.0:3376",
"SwarmDiscovery": "",
...
2. Verify Docker Engine is installed correctly by running `docker` commands.
Start with something basic like `docker run hello-world`, or for a more interesting test, run a Dockerized webserver on your new remote machine.
In this example, the `-p` option is used to expose port 80 from the `nginx` container and make it accessible on port `8000` of the `docker-sandbox` host.
$ docker run -d -p 8000:80 --name webserver kitematic/hello-world-nginx
Unable to find image 'kitematic/hello-world-nginx:latest' locally
latest: Pulling from kitematic/hello-world-nginx
a285d7f063ea: Pull complete
2d7baf27389b: Pull complete
...
Digest: sha256:ec0ca6dcb034916784c988b4f2432716e2e92b995ac606e080c7a54b52b87066
Status: Downloaded newer image for kitematic/hello-world-nginx:latest
942dfb4a0eaae75bf26c9785ade4ff47ceb2ec2a152be82b9d7960e8b5777e65
In a web browser, go to `http://<host_ip>:8000` to bring up the webserver home page. You got the `<host_ip>` from the output of the `docker-machine ip <machine>` command you ran in a previous step. Use the port you exposed in the `docker run` command.
![nginx webserver](../img/nginx-webserver.png)
### Step 5. Use Machine to remove the Droplet
To remove a host and all of its containers and images, first stop the machine, then use `docker-machine rm`:
$ docker-machine stop docker-sandbox
$ docker-machine rm docker-sandbox
Do you really want to remove "docker-sandbox"? (y/n): y
Successfully removed docker-sandbox
$ docker-machine ls
NAME ACTIVE DRIVER STATE URL SWARM
default * virtualbox Running tcp:////xxx.xxx.xx.xxx:xxxx
If you monitor the Digital Ocean console while you run these commands, you will see it update first to reflect that the Droplet was stopped, and then removed.
If you create a host with Docker Machine, but remove it through the cloud provider console, Machine will lose track of the server status. So please use the `docker-machine rm` command for hosts you create with `docker-machine create`.
## Where to go next
- [Understand Machine concepts](../concepts.md)
- [Docker Machine driver reference](../drivers/index.md)
- [Docker Machine subcommand reference](../reference/index.md)
- [Provision a Docker Swarm cluster with Docker Machine](/swarm/provision-with-machine.md)

87
docs/get-started-cloud.md Normal file
View File

@ -0,0 +1,87 @@
<!--[metadata]>
+++
title = "Provision hosts in the cloud"
description = "Using Docker Machine to provision hosts on cloud providers"
keywords = ["docker, machine, amazonec2, azure, digitalocean, google, openstack, rackspace, softlayer, virtualbox, vmwarefusion, vmwarevcloudair, vmwarevsphere, exoscale"]
[menu.main]
parent="workw_machine"
weight=-60
+++
<![end-metadata]-->
# Use Docker Machine to provision hosts on cloud providers
Docker Machine driver plugins are available for many cloud platforms, so you can use Machine to provision cloud hosts. When you use Docker Machine for provisioning, you create cloud hosts with Docker Engine installed on them.
You'll need to install and run Docker Machine, and create an account with the cloud provider.
Then you provide account verification, security credentials, and configuration options for the providers as flags to `docker-machine create`. The flags are unique for each cloud-specific driver. For instance, to pass a Digital Ocean access token you use the `--digitalocean-access-token` flag. Take a look at the examples below for Digital Ocean and AWS.
## Examples
### Digital Ocean
For Digital Ocean, this command creates a Droplet (cloud host) called "docker-sandbox".
$ docker-machine create --driver digitalocean --digitalocean-access-token xxxxx docker-sandbox
For a step-by-step guide on using Machine to create Docker hosts on Digital Ocean, see the [Digital Ocean Example](examples/ocean.md).
### Amazon Web Services (AWS)
For AWS EC2, this command creates an instance called "aws-sandbox":
$ docker-machine create --driver amazonec2 --amazonec2-access-key AKI******* --amazonec2-secret-key 8T93C******* aws-sandbox
For a step-by-step guide on using Machine to create Dockerized AWS instances, see the [Amazon Web Services (AWS) example](examples/aws.md).
## The docker-machine create command
The `docker-machine create` command typically requires that you specify, at a minimum:
* `--driver` - to indicate the provider on which to create the machine (VirtualBox, DigitalOcean, AWS, and so on)
* Account verification and security credentials (for cloud providers), specific to the cloud service you are using
* `<machine>` - name of the host you want to create
For convenience, `docker-machine` will use sensible defaults for choosing settings such as the image that the server is based on, but you override the defaults using the respective flags (e.g. `--digitalocean-image`). This is useful if, for example, you want to create a cloud server with a lot of memory and CPUs (by default `docker-machine` creates a small server).
For a full list of the flags/settings available and their defaults, see the output of `docker-machine create -h` at the command line, the <a href="../reference/create/" target="_blank">create</a> command in the Machine <a href="../reference/" target="_blank">command line reference</a>, and <a href="https://docs.docker.com/machine/drivers/os-base/" target="_blank">driver options and operating system defaults</a> in the Machine driver reference.
## Drivers for cloud providers
When you install Docker Machine, you get a set of drivers for various cloud providers (like Amazon Web Services, Digital Ocean, or Microsoft Azure) and local providers (like Oracle VirtualBox, VMWare Fusion, or Microsoft Hyper-V).
See <a href="../drivers/" target="_blank">Docker Machine driver reference</a> for details on the drivers, including required flags and configuration options (which vary by provider).
## 3rd-party driver plugins
Several Docker Machine driver plugins for use with other cloud platforms are available from 3rd party contributors. These are use-at-your-own-risk plugins, not maintained by or formally associated with Docker.
See <a href="https://github.com/docker/machine/blob/master/docs/AVAILABLE_DRIVER_PLUGINS.md" target="_blank">Available driver plugins</a> in the docker/machine repo on GitHub.
## Adding a host without a driver
You can add a host to Docker which only has a URL and no driver. Then you can use the machine name you provide here for an existing host so you dont have to type out the URL every time you run a Docker command.
$ docker-machine create --url=tcp://50.134.234.20:2376 custombox
$ docker-machine ls
NAME ACTIVE DRIVER STATE URL
custombox * none Running tcp://50.134.234.20:2376
## Using Machine to provision Docker Swarm clusters
Docker Machine can also provision <a href="https://docs.docker.com/swarm/overview/" target="_blank">Docker Swarm</a> clusters. This can be used with any driver and will be secured with TLS.
* To get started with Swarm, see <a href="https://docs.docker.com/swarm/get-swarm/" target="_blank">How to get Docker Swarm</a>.
* To learn how to use Machine to provision a Swarm cluster, see <a href="https://docs.docker.com/swarm/provision-with-machine/" target="_blank">Provision a Swarm cluster with Docker Machine</a>.
## Where to go next
- Example: Provision Dockerized [Digital Ocean Droplets](examples/ocean.md)
- Example: Provision Dockerized [AWS EC2 Instances](examples/aws.md)
- [Understand Machine concepts](concepts.md)
- [Docker Machine driver reference](drivers/index.md)
- [Docker Machine subcommand reference](reference/index.md)
- [Provision a Docker Swarm cluster with Docker Machine](/swarm/provision-with-machine.md)

331
docs/get-started.md Normal file
View File

@ -0,0 +1,331 @@
<!--[metadata]>
+++
title = "Get started with a local VM"
description = "Get started with Docker Machine and a local VM"
keywords = ["docker, machine, virtualbox, local"]
[menu.main]
parent="workw_machine"
weight=-70
+++
<![end-metadata]-->
# Get started with Docker Machine and a local VM
Let's take a look at using `docker-machine` to create, use and manage a
Docker host inside of a local virtual machine.
## Prerequisite Information
With the advent of [Docker for Mac](/docker-for-mac/index.md) and [Docker for
Windows](/docker-for-windows/index.md) as replacements for [Docker
Toolbox](/toolbox/overview.md), we recommend that you use these for your primary
Docker workflows.
However, Docker Machine is still available to create and manage machines for
power users or multi-node experimentation. Both Docker for Mac and Docker for
Windows include the newest version of Docker Machine, so when you install either
of these, you get `docker-machine`.
The new solutions come with their own native virtualization solutions rather
than using Oracle VirtualBox, so there are new considerations to keep in mind
when using Machine to create local VMs. Docker for Mac allows for the creation
of additional `docker-machine` based `virtualbox` machines without issue, but in
the case of Docker for Windows, you will need to use the `hyperv` driver
instead.
#### If you are using Docker for Windows
Docker for Windows uses [Microsoft
Hyper-V](https://msdn.microsoft.com/en-us/virtualization/hyperv_on_windows/windows_welcome)
for virtualization, and Hyper-V and Oracle VirtualBox are incompatible.
Therefore, you cannot run the two solutions simultaneously. But you can still
use `docker-machine` to create local VMs by using the Microsoft Hyper-V driver.
* If you are using Docker for Windows, the only prequisite is to have Docker for Windows installed. You will use the Microsoft `hyperv` driver to create
local machines. (See the [Docker Machine driver for Microsoft
Hyper-V](drivers/hyper-v.md).)
#### If you are using Docker for Mac
Docker for Mac uses [HyperKit](https://github.com/docker/HyperKit/), a
lightweight OS X virtualization solution built on top of the
[Hypervisor.framework](https://developer.apple.com/reference/hypervisor) in OS X
10.10 Yosemite and higher.
Currently, there is no `docker-machine create` driver for HyperKit, so you will
use `virtualbox` driver to create local machines. (See the [Docker Machine
driver for Oracle VirtualBox](drivers/virtualbox.md).) Note that you can run
both HyperKit and Oracle VirtualBox on the same system. To learn more, see
[Docker for Mac vs. Docker Toolbox](/docker-for-mac/docker-toolbox/).
* Make sure you have <a href="https://www.virtualbox.org/wiki/Downloads" target="_blank">the latest VirtualBox</a> correctly installed on your system
(either as part of an earlier Toolbox install, or manual install).
#### If you are using Docker Toolbox
Docker for Mac and Docker for Windows both require newer versions of their
respective operating systems, so users with older OS versions must use Docker
Toolbox.
* If you are using Docker Toolbox on either Mac or an older version Windows system (without Hyper-V), you will use the `virtualbox` driver to create
a local machine based on Oracle <a href= "https://www.virtualbox.org/"
target="_blank">VirtualBox</a>. (See the [Docker Machine driver for Oracle
VirtualBox](drivers/virtualbox.md) )
<br />
* If you are using Docker Toolbox on a Windows system that has Hyper-V but cannot run Docker for Windows (for example Windows 8 Pro), you must use the
`hyperv` driver to create local machines. (See the [Docker Machine driver for
Microsoft Hyper-V](drivers/hyper-v.md).)
<br />
* Make sure you have <a href="https://www.virtualbox.org/wiki/Downloads" target="_blank">the latest VirtualBox</a> correctly installed on your system. If
you used <a href="https://www.docker.com/products/docker-toolbox"
target="_blank">Toolbox</a> for <a
href="https://docs.docker.com/engine/installation/mac/" target="_blank">Mac</a>
or <a href="https://docs.docker.com/engine/installation/windows/"
target="_blank">Windows</a> to install Docker Machine, VirtualBox is
automatically installed.
<br />
* If you used the Quickstart Terminal to launch your first machine and set your terminal environment to point to it, a default machine was automatically
created. If this is the case, you can still follow along with these steps, but
create another machine and name it something other than "default" (e.g., staging
or sandbox).
## Use Machine to run Docker containers
To run a Docker container, you:
* create a new (or start an existing) Docker virtual machine
* switch your environment to your new VM
* use the docker client to create, load, and manage containers
Once you create a machine, you can reuse it as often as you like. Like any VirtualBox VM, it maintains its configuration between uses.
The examples here show how to create and start a machine, run Docker commands, and work with containers.
## Create a machine
1. Open a command shell or terminal window.
These command examples shows a Bash shell. For a different shell, such as C Shell, the same commands are the same except where noted.
2. Use `docker-machine ls` to list available machines.
In this example, no machines have been created yet.
$ docker-machine ls
NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS
3. Create a machine.
Run the `docker-machine create` command, pass the appropriate driver to the
`--driver` flag and provide a machine name. If this is your first machine, name
it `default` as shown in the example. If you already have a "default" machine,
choose another name for this new machine.
* If you are using Toolbox on Mac, Toolbox on older Windows systems without Hyper-V, or Docker for Mac, use `virtualbox` as the driver, as shown in this example. (The Docker Machine VirtualBox driver reference is [here](drivers/virtualbox.md).) (See [prerequisites](#prerequisite-information) above to learn more.)
* On Docker for Windows systems that support Hyper-V, use the `hyperv` driver as shown in the [Docker Machine Microsoft Hyper-V driver reference](drivers/hyper-v.md). (See [prerequisites](#prerequisite-information) above to learn more.)
$ docker-machine create --driver virtualbox default
Running pre-create checks...
Creating machine...
(staging) Copying /Users/ripley/.docker/machine/cache/boot2docker.iso to /Users/ripley/.docker/machine/machines/default/boot2docker.iso...
(staging) Creating VirtualBox VM...
(staging) Creating SSH key...
(staging) Starting the VM...
(staging) Waiting for an IP...
Waiting for machine to be running, this may take a few minutes...
Machine is running, waiting for SSH to be available...
Detecting operating system of created instance...
Detecting the provisioner...
Provisioning with boot2docker...
Copying certs to the local machine directory...
Copying certs to the remote machine...
Setting Docker configuration on the remote daemon...
Checking connection to Docker...
Docker is up and running!
To see how to connect Docker to this machine, run: docker-machine env default
This command downloads a lightweight Linux distribution (<a href="https://github.com/boot2docker/boot2docker" target="_blank">boot2docker</a>) with the Docker daemon installed, and creates and starts a VirtualBox VM with Docker running.
4. List available machines again to see your newly minted machine.
$ docker-machine ls
NAME ACTIVE DRIVER STATE URL SWARM DOCKER ERRORS
default * virtualbox Running tcp://192.168.99.187:2376 v1.9.1
5. Get the environment commands for your new VM.
As noted in the output of the `docker-machine create` command, you need to tell Docker to talk to the new machine. You can do this with the `docker-machine env` command.
$ docker-machine env default
export DOCKER_TLS_VERIFY="1"
export DOCKER_HOST="tcp://172.16.62.130:2376"
export DOCKER_CERT_PATH="/Users/<yourusername>/.docker/machine/machines/default"
export DOCKER_MACHINE_NAME="default"
# Run this command to configure your shell:
# eval "$(docker-machine env default)"
6. Connect your shell to the new machine.
$ eval "$(docker-machine env default)"
**Note**: If you are using `fish`, or a Windows shell such as
Powershell/`cmd.exe` the above method will not work as described.
Instead, see <a href="https://docs.docker.com/machine/reference/env/" target="_blank">the `env` command's documentation</a>
to learn how to set the environment variables for your shell.
This sets environment variables for the current shell that the Docker client will read which specify the TLS settings. You need to do this each time you open a new shell or restart your machine.
You can now run Docker commands on this host.
## Run containers and experiment with Machine commands
Run a container with `docker run` to verify your set up.
1. Use `docker run` to download and run `busybox` with a simple 'echo' command.
$ docker run busybox echo hello world
Unable to find image 'busybox' locally
Pulling repository busybox
e72ac664f4f0: Download complete
511136ea3c5a: Download complete
df7546f9f060: Download complete
e433a6c5b276: Download complete
hello world
2. Get the host IP address.
Any exposed ports are available on the Docker hosts IP address, which you can get using the `docker-machine ip` command:
$ docker-machine ip default
192.168.99.100
3. Run a webserver (<a href="https://www.nginx.com/" target="_blank">nginx</a>) in a container with the following command:
$ docker run -d -p 8000:80 nginx
When the image is finished pulling, you can hit the server at port 8000 on the IP address given to you by `docker-machine ip`. For instance:
$ curl $(docker-machine ip default):8000
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
body {
width: 35em;
margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif;
}
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>
You can create and manage as many local VMs running Docker as you please; just run `docker-machine create` again. All created machines will appear in the output of `docker-machine ls`.
## Start and stop machines
If you are finished using a host for the time being, you can stop it with `docker-machine stop` and later start it again with `docker-machine start`.
$ docker-machine stop default
$ docker-machine start default
## Operate on machines without specifying the name
Some `docker-machine` commands will assume that the given operation should be run on a machine named `default` (if it exists) if no machine name is specified. Because using a local VM named `default` is such a common pattern, this allows you to save some typing on the most frequently used Machine commands.
For example:
$ docker-machine stop
Stopping "default"....
Machine "default" was stopped.
$ docker-machine start
Starting "default"...
(default) Waiting for an IP...
Machine "default" was started.
Started machines may have new IP addresses. You may need to re-run the `docker-machine env` command.
$ eval $(docker-machine env)
$ docker-machine ip
192.168.99.100
Commands that follow this style are:
- `docker-machine config`
- `docker-machine env`
- `docker-machine inspect`
- `docker-machine ip`
- `docker-machine kill`
- `docker-machine provision`
- `docker-machine regenerate-certs`
- `docker-machine restart`
- `docker-machine ssh`
- `docker-machine start`
- `docker-machine status`
- `docker-machine stop`
- `docker-machine upgrade`
- `docker-machine url`
For machines other than `default`, and commands other than those listed above, you must always specify the name explicitly as an argument.
## Start local machines on startup
In order to ensure that the Docker client is automatically configured at the
start of each shell session, some users like to embed `eval $(docker-machine env
default)` in their shell profiles (e.g., the `~/.bash_profile` file). However,
this fails if the `default` machine is not running. If desired, you can
configure your system to start the `default` machine automatically.
Here is an example of how to configure this on OS X.
Create a file called `com.docker.machine.default.plist` under `~/Library/LaunchAgents` with the following content:
```
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin</string>
</dict>
<key>Label</key>
<string>com.docker.machine.default</string>
<key>ProgramArguments</key>
<array>
<string>/usr/local/bin/docker-machine</string>
<string>start</string>
<string>default</string>
</array>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>
```
You can change the `default` string above to make this `LaunchAgent` start any machine(s) you desire.
## Where to go next
- Provision multiple Docker hosts [on your cloud provider](get-started-cloud.md)
- [Understand Machine concepts](concepts.md)
- [Docker Machine list of reference pages for all supported drivers](/machine/drivers/index.md)
- [Docker Machine driver for Oracle VirtualBox](drivers/virtualbox.md)
- [Docker Machine driver for Microsoft Hyper-V](drivers/hyper-v.md)
- [`docker-machine` command line reference](reference/index.md)

Binary file not shown.

After

Width:  |  Height:  |  Size: 348 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 311 KiB

BIN
docs/img/coverage.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 194 KiB

BIN
docs/img/docker-engine.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

BIN
docs/img/engine.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Some files were not shown because too many files have changed in this diff Show More