diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..baf846d --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +docker-machine-driver-parallels +*.log diff --git a/Makefile b/Makefile index f9f0ec1..d23a350 100644 --- a/Makefile +++ b/Makefile @@ -1,12 +1,17 @@ default: build -build: - go build -i -o docker-machine-driver-parallels ./bin +bin/docker-machine-driver-parallels: + go build -i -o ./bin/docker-machine-driver-parallels ./bin + +build: clean bin/docker-machine-driver-parallels clean: - $(RM) docker-machine-driver-parallels + $(RM) bin/docker-machine-driver-parallels + +install: bin/docker-machine-driver-parallels + cp -f ./bin/docker-machine-driver-parallels $(GOPATH)/bin/ -install: build - cp -r ./docker-machine-driver-parallels /usr/local/bin/ +test-acceptance: + test/integration/run-bats.sh test/integration/bats/ -.PHONY: install +.PHONY: clean build install test-acceptance diff --git a/README.md b/README.md index 575de62..eb4f01b 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,105 @@ # Docker Machine Parallels Driver -### PREVIEW -This is a pre-release version of Parallels Driver for Docker Machine. -Work is still in progress. +This is a plugin for [Docker Machine](https://docs.docker.com/machine/) allowing +to create Docker hosts locally on [Parallels Desktop for Mac](http://www.parallels.com/products/desktop/) -This is a plugin for Docker Machine, wich is gonna be compatible with Docker -Machine v0.5.0. -Refer to this PR for more details: https://github.com/docker/machine/pull/1902 +## Requirements +* OS X 10.9+ +* [Docker Machine](https://docs.docker.com/machine/) 0.5.0+ (is bundled to + [Docker Toolbox](https://www.docker.com/docker-toolbox) 1.9.0+) +* [Parallels Desktop](http://www.parallels.com/products/desktop/) 11.0.0+ **Pro** or +**Business** edition (_Standard edition is not supported!_) -## Build +## Installation +To install this plugin, download the binary `docker-machine-driver-parallels` +and make it available by `$PATH`, for example by putting it to `/usr/local/bin/`: + +```console +$ curl -L https://github.com/Parallels/docker-machine-parallels/releases/download/v1.0.0/docker-machine-driver-parallels > /usr/local/bin/docker-machine-driver-parallels + +$ chmod +x /usr/local/bin/docker-machine-driver-parallels +``` + +The latest version of `docker-machine-driver-parallels` binary is available on +the ["Releases"](https://github.com/Parallels/docker-machine-parallels/releases) page. + +## Usage +Official documentation for Docker Machine [is available here](https://docs.docker.com/machine/). + +To create a Parallels Desktop virtual machine for Docker purposes just run this +command: + +``` +$ docker-machine create --driver=parallels prl-dev +``` + +Available options: + + - `--parallels-boot2docker-url`: The URL of the boot2docker image. + - `--parallels-disk-size`: Size of disk for the host VM (in MB). + - `--parallels-memory`: Size of memory for the host VM (in MB). + - `--parallels-cpu-count`: Number of CPUs to use to create the VM (-1 to use the number of CPUs available). + - `--parallels-no-share`: Disable the sharing of `/Users` directory + +The `--parallels-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 `--parallels-boot2docker-url=""`), but the +option also supports specifying ISOs by the `http://` and `file://` protocols. + +Environment variables and default values: + +| CLI option | Environment variable | Default | +|-------------------------------|-----------------------------|--------------------------| +| `--parallels-boot2docker-url` | `PARALLELS_BOOT2DOCKER_URL` | *Latest boot2docker url* | +| `--parallels-cpu-count` | `PARALLELS_CPU_COUNT` | `1` | +| `--parallels-disk-size` | `PARALLELS_DISK_SIZE` | `20000` | +| `--parallels-memory` | `PARALLELS_MEMORY_SIZE` | `1024` | +| `--parallels-no-share` | - | `false` | + +## Development + +### Build from Source +If you wish to work on Parallels Driver for Docker machine, you'll first need +[Go](http://www.golang.org) installed (version 1.5+ is required). +Make sure Go is properly installed, including setting up a [GOPATH](http://golang.org/doc/code.html#GOPATH). + +Run these commands to build the plugin binary: ```bash $ go get -d github.com/Parallels/docker-machine-parallels $ cd $GOPATH/github.com/Parallels/docker-machine-parallels -$ make +$ make build +``` + +After the build is complete, `bin/docker-machine-driver-parallels` binary will +be created. If you want to copy it to the `${GOPATH}/bin/`, run `make install`. + +### Acceptance Tests + +We use [BATS](https://github.com/sstephenson/bats) for acceptance testing, so, +[install it](https://github.com/sstephenson/bats#installing-bats-from-source) first. + +You also need to build the plugin binary by calling `make build`. + +Then you can run acceptance tests using this command: + +```bash +$ make test-acceptance ``` -The binary will appear in the working directory. Just make it available on the `$PATH` +Acceptance tests will invoke the general `docker-machine` binary available by +`$PATH`. If you want to specify it explicitly, just set `MACHINE_BINARY` env variable: + +```bash +$ MACHINE_BINARY=/path/to/docker-machine make test-acceptance +``` ## Authors diff --git a/test/integration/bats/bad-create-iso.bats b/test/integration/bats/bad-create-iso.bats new file mode 100644 index 0000000..9519e87 --- /dev/null +++ b/test/integration/bats/bad-create-iso.bats @@ -0,0 +1,10 @@ +#!/usr/bin/env bats + +load ${BASE_TEST_DIR}/helpers.bash + +export BAD_URL="http://dev.null:9111/bad.iso" + +@test "$DRIVER: Should not allow machine creation with bad ISO" { + run machine create -d parallels --parallels-boot2docker-url $BAD_URL $NAME + [[ ${status} -eq 1 ]] +} diff --git a/test/integration/bats/certs-checksum.bats b/test/integration/bats/certs-checksum.bats new file mode 100644 index 0000000..fc80b03 --- /dev/null +++ b/test/integration/bats/certs-checksum.bats @@ -0,0 +1,19 @@ +#!/usr/bin/env bats + +load ${BASE_TEST_DIR}/helpers.bash + +@test "$DRIVER: create" { + run machine create -d $DRIVER $NAME +} + +@test "$DRIVER: verify that server cert checksum matches local checksum" { + # Have to create this directory and file or else the OpenSSL checksum will barf. + machine ssh $NAME -- sudo mkdir -p /usr/local/ssl + machine ssh $NAME -- sudo touch /usr/local/ssl/openssl.cnf + + SERVER_CHECKSUM=$(machine ssh $NAME -- openssl dgst -sha256 /var/lib/boot2docker/ca.pem | awk '{ print $2 }') + LOCAL_CHECKSUM=$(openssl dgst -sha256 $MACHINE_STORAGE_PATH/certs/ca.pem | awk '{ print $2 }') + echo ${SERVER_CHECKSUM} + echo ${LOCAL_CHECKSUM} + [[ ${SERVER_CHECKSUM} == ${LOCAL_CHECKSUM} ]] +} diff --git a/test/integration/bats/core-commands.bats b/test/integration/bats/core-commands.bats new file mode 100644 index 0000000..cd2ea55 --- /dev/null +++ b/test/integration/bats/core-commands.bats @@ -0,0 +1,153 @@ +#!/usr/bin/env bats + +load ${BASE_TEST_DIR}/helpers.bash + +@test "$DRIVER: machine should not exist" { + run machine inspect $NAME + echo ${output} + [ "$status" -eq 1 ] + [[ ${lines[0]} == "Host \"$NAME\" does not exist" ]] +} + +@test "$DRIVER: create" { + run machine create -d $DRIVER $NAME + echo ${output} + [ "$status" -eq 0 ] +} + +@test "$DRIVER: appears with ls" { + run machine ls -q + echo ${output} + [ "$status" -eq 0 ] + [[ ${lines[0]} == "$NAME" ]] +} + +@test "$DRIVER: has status 'started' appearing in ls" { + run machine ls -q --filter state=Running + echo ${output} + [ "$status" -eq 0 ] + [[ ${lines[0]} == "$NAME" ]] +} + +@test "$DRIVER: create with same name again fails" { + run machine create -d $DRIVER $NAME + echo ${output} + [ "$status" -eq 1 ] + [[ ${lines[0]} == "Host already exists: \"$NAME\"" ]] +} + +@test "$DRIVER: run busybox container" { + run docker $(machine config $NAME) run busybox echo hello world + echo ${output} + [ "$status" -eq 0 ] +} + +@test "$DRIVER: url" { + run machine url $NAME + echo ${output} + [ "$status" -eq 0 ] +} + +@test "$DRIVER: ip" { + run machine ip $NAME + echo ${output} + [ "$status" -eq 0 ] +} + +@test "$DRIVER: ssh" { + run machine ssh $NAME -- ls -lah / + echo ${output} + [ "$status" -eq 0 ] + [[ ${lines[0]} =~ "total" ]] +} + +@test "$DRIVER: docker commands with the socket should work" { + run machine ssh $NAME -- sudo docker version + echo ${output} +} + +@test "$DRIVER: shared folder is mounted" { + run machine ssh $NAME -- "mount | grep prl_fs | awk '{ print $3 }'" + echo ${output} + [ "$status" -eq 0 ] + [[ ${output} == *"/Users"* ]] +} + +@test "$DRIVER: stop" { + run machine stop $NAME + echo ${output} + [ "$status" -eq 0 ] +} + +@test "$DRIVER: machine should show stopped after stop" { + run machine ls + echo ${output} + [ "$status" -eq 0 ] + [[ ${lines[1]} == *"Stopped"* ]] +} + +@test "$DRIVER: url should show an error when machine is stopped" { + run machine url $NAME + echo ${output} + [ "$status" -eq 1 ] + [[ ${output} == *"not running"* ]] +} + +@test "$DRIVER: env should show an error when machine is stopped" { + run machine env $NAME + echo ${output} + [ "$status" -eq 1 ] + [[ ${output} == *"not running. Please start"* ]] +} + +@test "$DRIVER: machine should not allow upgrade when stopped" { + run machine upgrade $NAME + echo ${output} + [[ "$status" -eq 1 ]] +} + +@test "$DRIVER: start" { + run machine start $NAME + echo ${output} + [ "$status" -eq 0 ] +} + +@test "$DRIVER: machine should show running after start" { + run machine ls + echo ${output} + [ "$status" -eq 0 ] + [[ ${lines[1]} == *"Running"* ]] +} + +@test "$DRIVER: kill" { + run machine kill $NAME + echo ${output} + [ "$status" -eq 0 ] +} + +@test "$DRIVER: machine should show stopped after kill" { + run machine ls + echo ${output} + [ "$status" -eq 0 ] + [[ ${lines[1]} == *"Stopped"* ]] +} + +@test "$DRIVER: restart" { + run machine restart $NAME + echo ${output} + [ "$status" -eq 0 ] +} + +@test "$DRIVER: machine should show running after restart" { + run machine ls + echo ${output} + [ "$status" -eq 0 ] + [[ ${lines[1]} == *"Running"* ]] +} + +@test "$DRIVER: status" { + run machine status $NAME + echo ${output} + [ "$status" -eq 0 ] + [[ ${output} == *"Running"* ]] +} diff --git a/test/integration/bats/custom-mem-disk.bats b/test/integration/bats/custom-mem-disk.bats new file mode 100644 index 0000000..9e51b90 --- /dev/null +++ b/test/integration/bats/custom-mem-disk.bats @@ -0,0 +1,49 @@ +#!/usr/bin/env bats + +load ${BASE_TEST_DIR}/helpers.bash + +# Default memsize is 1024MB and disksize is 20000MB +# These values are defined in parallels.go +export DEFAULT_MEMSIZE=1024 +export DEFAULT_DISKSIZE=20000 +export CUSTOM_MEMSIZE=1536 +export CUSTOM_DISKSIZE=10000 +export CUSTOM_CPUCOUNT=1 + +function findDiskSize() { + run bash -c "prlctl list -i $NAME | grep 'hdd0.*sata' | grep -o '\d*Mb' | awk -F 'Mb' '{print $1}'" +} + +function findMemorySize() { + run bash -c "prlctl list -i $NAME | grep 'memory ' | grep -o '[0-9]\+'" +} + +function findCPUCount() { + run bash -c "prlctl list -i $NAME | grep -o 'cpus=\d*' | cut -d'=' -f2" +} + +@test "$DRIVER: create with custom disk, cpu count and memory size flags" { + run machine create -d $DRIVER --parallels-cpu-count $CUSTOM_CPUCOUNT --parallels-disk-size $CUSTOM_DISKSIZE --parallels-memory $CUSTOM_MEMSIZE $NAME + [ "$status" -eq 0 ] +} + +@test "$DRIVER: check custom machine memory size" { + findMemorySize + [[ ${output} == "$CUSTOM_MEMSIZE" ]] +} + +@test "$DRIVER: check custom machine disksize" { + findDiskSize + [[ ${output} == *"$CUSTOM_DISKSIZE"* ]] +} + +@test "$DRIVER: check custom machine cpucount" { + findCPUCount + [[ ${output} == "$CUSTOM_CPUCOUNT" ]] +} + +@test "$DRIVER: machine should show running after create" { + run machine ls + [ "$status" -eq 0 ] + [[ ${lines[1]} == *"Running"* ]] +} diff --git a/test/integration/bats/no-share.bats b/test/integration/bats/no-share.bats new file mode 100644 index 0000000..3994704 --- /dev/null +++ b/test/integration/bats/no-share.bats @@ -0,0 +1,13 @@ +#!/usr/bin/env bats + +load ${BASE_TEST_DIR}/helpers.bash + +@test "$DRIVER: create with disabled sharing" { + run machine create -d $DRIVER --parallels-no-share $NAME +} + +@test "$DRIVER: shared folder is not mounted mounted" { + run machine ssh $NAME -- "mount | grep prl_fs" + echo ${output} + [ "$status" -eq 1 ] +} diff --git a/test/integration/bats/pause-save-start.bats b/test/integration/bats/pause-save-start.bats new file mode 100644 index 0000000..f5f31c9 --- /dev/null +++ b/test/integration/bats/pause-save-start.bats @@ -0,0 +1,53 @@ +#!/usr/bin/env bats + +load ${BASE_TEST_DIR}/helpers.bash + +@test "$DRIVER: create" { + run machine create -d $DRIVER $NAME + [ "$status" -eq 0 ] +} + +@test "$DRIVER: prlctl pause" { + run prlctl pause $NAME + [ "$status" -eq 0 ] +} + +@test "$DRIVER: machine should show paused after 'prlctl pause'" { + run machine ls + [ "$status" -eq 0 ] + [[ ${lines[1]} == *"Paused"* ]] +} + +@test "$DRIVER: start after paused" { + run machine start $NAME + [ "$status" -eq 0 ] +} + +@test "$DRIVER: machine should show running after start" { + run machine ls + [ "$status" -eq 0 ] + [[ ${lines[1]} == *"Running"* ]] +} + +@test "$DRIVER: prlctl suspend" { + run prlctl suspend $NAME + [ "$status" -eq 0 ] +} + +@test "$DRIVER: machine should show saved after 'prlctl suspend'" { + run machine ls + [ "$status" -eq 0 ] + [[ ${lines[1]} == *"$NAME"* ]] + [[ ${lines[1]} == *"Saved"* ]] +} + +@test "$DRIVER: start after saved" { + run machine start $NAME + [ "$status" -eq 0 ] +} + +@test "$DRIVER: machine should show running after start" { + run machine ls + [ "$status" -eq 0 ] + [[ ${lines[1]} == *"Running"* ]] +} diff --git a/test/integration/bats/upgrade.bats b/test/integration/bats/upgrade.bats new file mode 100644 index 0000000..73cf942 --- /dev/null +++ b/test/integration/bats/upgrade.bats @@ -0,0 +1,26 @@ +#!/usr/bin/env bats + +load ${BASE_TEST_DIR}/helpers.bash + +export OLD_ISO_URL="https://github.com/Parallels/boot2docker/releases/download/v1.7.0-prl-tools/boot2docker.iso" + +@test "$DRIVER: create for upgrade" { + run machine create -d parallels --parallels-boot2docker-url $OLD_ISO_URL $NAME +} + +@test "$DRIVER: verify that docker version is old" { + # Have to run this over SSH due to client/server mismatch restriction + SERVER_VERSION=$(machine ssh $NAME docker version | grep 'Server version' | awk '{ print $3; }') + [[ "$SERVER_VERSION" == "1.7.0" ]] +} + +@test "$DRIVER: upgrade" { + run machine upgrade $NAME + echo ${output} + [ "$status" -eq 0 ] +} + +@test "$DRIVER: upgrade is correct version" { + SERVER_VERSION=$(docker $(machine config $NAME) version | grep 'Server version' | awk '{ print $3; }') + [[ "$SERVER_VERSION" != "1.7.0" ]] +} diff --git a/test/integration/helpers.bash b/test/integration/helpers.bash new file mode 100644 index 0000000..8ef2392 --- /dev/null +++ b/test/integration/helpers.bash @@ -0,0 +1,35 @@ +#!/bin/bash + +echo_to_log() { + echo "$BATS_TEST_NAME +---------- +$output +---------- + +" >> ${BATS_LOG} +} + +teardown() { + echo_to_log +} + +function errecho () { + >&2 echo "$@" +} + +function force_env () { + if [[ ${!1} != "$2" ]]; then + errecho "This test requires the $1 environment variable to be set to $2 in order to run properly." + exit 1 + fi +} + +function require_env () { + if [[ -z ${!1} ]]; then + errecho "This test requires the $1 environment variable to be set in order to run." + exit 1 + fi +} + +# Make sure these aren't set while tests run (can cause confusing behavior) +unset DOCKER_HOST DOCKER_TLS_VERIFY DOCKER_CERT_DIR diff --git a/test/integration/run-bats.sh b/test/integration/run-bats.sh new file mode 100755 index 0000000..015cf90 --- /dev/null +++ b/test/integration/run-bats.sh @@ -0,0 +1,86 @@ +#!/bin/bash + +set -e + +# Wrapper script to run bats tests for "parallels" driver. +# Usage: ./run-bats.sh [subtest] + +function quiet_run () { + if [[ "$VERBOSE" == "1" ]]; then + "$@" + else + "$@" &>/dev/null + fi +} + +function cleanup_machines() { + if [[ $(machine ls -q | wc -l) -ne 0 ]]; then + #quiet_run machine stop $(machine ls -q) + quiet_run machine rm -f $(machine ls -q) + fi +} + +function machine() { + export PATH="$PLUGIN_ROOT"/bin:$PATH + "$MACHINE_BINARY" "$@" +} + +function run_bats() { + for bats_file in $(find "$1" -name \*.bats); do + # BATS returns non-zero to indicate the tests have failed, we shouldn't + # neccesarily bail in this case, so that's the reason for the e toggle. + set +e + echo "=> $bats_file" + bats "$bats_file" + if [[ $? -ne 0 ]]; then + EXIT_STATUS=1 + fi + set -e + echo + cleanup_machines + done +} + +# Set this ourselves in case bats call fails +EXIT_STATUS=0 +export BATS_FILE="$1" + +if [[ -z "$BATS_FILE" ]]; then + echo "You must specify a bats test to run." + exit 1 +fi + +if [[ ! -e "$BATS_FILE" ]]; then + echo "Requested bats file or directory not found: $BATS_FILE" + exit 1 +fi + +# Set defaults if variables are not defined +export MACHINE_BINARY=${MACHINE_BINARY:-"docker-machine"} + +export DRIVER="parallels" +export BASE_TEST_DIR=$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd ) +export PLUGIN_ROOT="$BASE_TEST_DIR/../.." +export NAME="bats-$DRIVER-test" +export MACHINE_STORAGE_PATH="/tmp/machine-bats-test-$DRIVER" +export BATS_LOG="$PLUGIN_ROOT/bats.log" + +# This function gets used in the integration tests, so export it. +export -f machine + +rm -f "$BATS_LOG" + +run_bats "$BATS_FILE" + +if [[ -d "$MACHINE_STORAGE_PATH" ]]; then + rm -r "$MACHINE_STORAGE_PATH" +fi + +set +e +pkill docker-machine +if [[ $? -eq 0 ]]; then + EXIT_STATUS=1 +fi +set -e + +exit ${EXIT_STATUS}