├── .dockerignore ├── .github ├── renovate.json └── workflows │ ├── fossa.yaml │ ├── release.yaml │ ├── renovate.yml │ └── test.yaml ├── .gitignore ├── Dockerfile.dapper ├── LICENSE ├── Makefile ├── Makefile.inc ├── README.md ├── cmd └── rancher-machine │ ├── machine.go │ ├── machine_test.go │ └── setup_test.go ├── commands ├── active.go ├── active_test.go ├── commands.go ├── commands_test.go ├── commandstest │ ├── fake_command_line.go │ └── stdout_capture.go ├── config.go ├── create.go ├── create_test.go ├── env.go ├── env_test.go ├── flag_sort.go ├── inspect.go ├── inspect_test.go ├── ip.go ├── ip_test.go ├── kill.go ├── kill_test.go ├── ls.go ├── ls_test.go ├── mcndirs │ ├── utils.go │ └── utils_test.go ├── mount.go ├── mount_test.go ├── provision.go ├── provision_test.go ├── regeneratecerts.go ├── restart.go ├── rm.go ├── rm_test.go ├── scp.go ├── scp_test.go ├── scp_unix.go ├── scp_windows.go ├── ssh.go ├── ssh_test.go ├── start.go ├── status.go ├── stop.go ├── stop_test.go ├── upgrade.go ├── url.go ├── url_test.go ├── version.go └── version_test.go ├── contrib └── completion │ ├── .gitignore │ ├── bash │ ├── docker-machine-prompt.bash │ ├── docker-machine-wrapper.bash │ └── docker-machine.bash │ └── zsh │ └── _docker-machine ├── drivers ├── amazonec2 │ ├── amazonec2.go │ ├── amazonec2_test.go │ ├── awscredentials.go │ ├── awscredentials_test.go │ ├── ec2client.go │ ├── logger.go │ ├── region.go │ └── stub_test.go ├── azure │ ├── azure.go │ ├── azure_test.go │ ├── azureutil │ │ ├── auth.go │ │ ├── azureutil.go │ │ ├── cleanup.go │ │ ├── clients.go │ │ ├── context.go │ │ ├── inspector.go │ │ ├── naming.go │ │ ├── powerstate.go │ │ ├── tenantid.go │ │ └── util.go │ ├── logutil │ │ └── logfields.go │ ├── util.go │ └── util_test.go ├── digitalocean │ ├── digitalocean.go │ └── digitalocean_test.go ├── driverutil │ ├── util.go │ └── util_test.go ├── errdriver │ └── error.go ├── exoscale │ ├── exoscale.go │ └── exoscale_test.go ├── fakedriver │ └── fakedriver.go ├── generic │ ├── generic.go │ └── generic_test.go ├── google │ ├── compute_util.go │ ├── compute_util_test.go │ ├── google.go │ └── google_test.go ├── hyperv │ ├── hyperv.go │ ├── hyperv_test.go │ └── powershell.go ├── none │ └── driver.go ├── noop │ └── driver.go ├── openstack │ ├── client.go │ ├── openstack.go │ └── openstack_test.go ├── pod │ └── pod.go ├── rackspace │ ├── client.go │ ├── rackspace.go │ └── rackspace_test.go ├── softlayer │ ├── driver.go │ ├── driver_test.go │ └── softlayer.go ├── virtualbox │ ├── disk.go │ ├── disk_test.go │ ├── ip.go │ ├── misc.go │ ├── network.go │ ├── network_test.go │ ├── vbm.go │ ├── vbm_test.go │ ├── virtualbox.go │ ├── virtualbox_darwin.go │ ├── virtualbox_darwin_test.go │ ├── virtualbox_freebsd.go │ ├── virtualbox_linux.go │ ├── virtualbox_linux_test.go │ ├── virtualbox_openbsd.go │ ├── virtualbox_test.go │ ├── virtualbox_windows.go │ ├── vm.go │ ├── vm_test.go │ ├── vtx.go │ └── vtx_test.go ├── vmwarefusion │ ├── fusion.go │ ├── fusion_darwin.go │ ├── fusion_darwin_test.go │ ├── vmrun_darwin.go │ └── vmx_darwin.go ├── vmwarevcloudair │ ├── vcloudair.go │ └── vcloudlair_test.go └── vmwarevsphere │ ├── cloudinit.go │ ├── create.go │ ├── flags.go │ ├── log.go │ ├── utils.go │ ├── vm.go │ ├── vsphere.go │ └── vsphere_test.go ├── experimental ├── README.md ├── b2d_migration.md └── b2d_migration_tasks.md ├── go.mod ├── go.sum ├── its ├── cli │ ├── create_rm_test.go │ ├── driver_help_test.go │ ├── help_test.go │ ├── inspect_test.go │ ├── ls_test.go │ ├── status_test.go │ └── url_test.go ├── tester.go └── thirdparty │ └── commands_test.go ├── libmachine ├── auth │ └── auth.go ├── cert │ ├── bootstrap.go │ ├── cert.go │ └── cert_test.go ├── check │ ├── check.go │ └── check_test.go ├── crashreport │ ├── crash_report.go │ ├── crash_report_logger.go │ ├── crash_report_test.go │ ├── os_darwin.go │ ├── os_freebsd.go │ ├── os_linux.go │ ├── os_openbsd.go │ ├── os_windows.go │ └── os_windows_test.go ├── drivers │ ├── base.go │ ├── base_test.go │ ├── check.go │ ├── drivers.go │ ├── notsupported.go │ ├── plugin │ │ ├── localbinary │ │ │ ├── plugin.go │ │ │ ├── plugin_test.go │ │ │ ├── utils.go │ │ │ └── utils_windows.go │ │ └── register_driver.go │ ├── rpc │ │ ├── client_driver.go │ │ ├── server_driver.go │ │ ├── server_driver_test.go │ │ ├── util.go │ │ └── util_test.go │ ├── serial.go │ ├── serial_test.go │ └── utils.go ├── engine │ └── engine.go ├── examples │ └── main.go ├── host │ ├── host.go │ ├── host_test.go │ ├── host_v0.go │ ├── host_v2.go │ ├── migrate.go │ ├── migrate_test.go │ ├── migrate_v0_v1.go │ ├── migrate_v0_v1_test.go │ ├── migrate_v0_v3_test.go │ ├── migrate_v1_v2.go │ ├── migrate_v1_v2_test.go │ └── migrate_v2_v3.go ├── hosttest │ └── default_test_host.go ├── libmachine.go ├── libmachinetest │ └── fake_api.go ├── log │ ├── fmt_machine_logger.go │ ├── fmt_machine_logger_test.go │ ├── history_recorder.go │ ├── history_recorder_test.go │ ├── log.go │ ├── log_test.go │ └── machine_logger.go ├── mcndockerclient │ ├── docker_client.go │ ├── docker_host.go │ ├── docker_versioner.go │ └── fake_docker_versioner.go ├── mcnerror │ └── errors.go ├── mcnflag │ └── flag.go ├── mcnutils │ ├── b2d.go │ ├── b2d_test.go │ ├── utils.go │ └── utils_test.go ├── persist │ ├── filestore.go │ ├── filestore_test.go │ ├── persisttest │ │ ├── fakestore.go │ │ └── fakestore_test.go │ ├── secretstore.go │ └── store.go ├── provider │ └── provider.go ├── provision │ ├── amazonlinux.go │ ├── arch.go │ ├── arch_test.go │ ├── boot2docker.go │ ├── centos.go │ ├── configure_swarm.go │ ├── coreos.go │ ├── custom_script.go │ ├── debian.go │ ├── debian_test.go │ ├── engine_config_context.go │ ├── errors.go │ ├── fake_provisioner.go │ ├── fedora.go │ ├── fedora_coreos.go │ ├── generic.go │ ├── oraclelinux.go │ ├── os_release.go │ ├── os_release_test.go │ ├── photonos.go │ ├── pkgaction │ │ ├── pkg_action.go │ │ └── pkg_action_test.go │ ├── provisioner.go │ ├── provisiontest │ │ ├── sshcommander.go │ │ └── sshcommander_test.go │ ├── rancheros.go │ ├── redhat.go │ ├── redhat_ssh_commander.go │ ├── redhat_test.go │ ├── rockylinux.go │ ├── serviceaction │ │ ├── service_action.go │ │ └── service_action_test.go │ ├── suse.go │ ├── systemd.go │ ├── ubuntu_systemd.go │ ├── ubuntu_systemd_test.go │ ├── ubuntu_upstart.go │ ├── ubuntu_upstart_test.go │ ├── utils.go │ └── utils_test.go ├── shell │ ├── shell.go │ ├── shell_test.go │ ├── shell_unix_test.go │ ├── shell_windows.go │ └── shell_windows_test.go ├── ssh │ ├── client.go │ ├── client_test.go │ ├── keys.go │ ├── keys_test.go │ ├── ssh_test.go │ └── sshtest │ │ └── fake_client.go ├── state │ ├── state.go │ └── state_test.go ├── swarm │ └── swarm.go ├── util │ └── util.go ├── version │ └── version.go └── versioncmp │ ├── compare.go │ └── compare_test.go ├── package ├── Dockerfile ├── download_driver.sh └── entrypoint.sh ├── scripts ├── build ├── ci ├── clean ├── entry ├── package ├── release ├── test ├── validate └── version ├── test ├── integration │ ├── .gitignore │ ├── amazonec2 │ │ ├── amazon.bats │ │ ├── create-ebsinstance.bats │ │ └── createwithkeypair.bats │ ├── core │ │ ├── certs-extra-san.bats │ │ ├── core-commands.bats │ │ ├── crashreport.bats │ │ ├── engine-options.bats │ │ ├── env_shell.bats │ │ ├── inspect_format.bats │ │ ├── regenerate-certs.bats │ │ ├── scp.bats │ │ ├── ssh-backends.bats │ │ └── swarm-options.bats │ ├── helpers.bash │ ├── run-bats.sh │ └── virtualbox │ │ ├── certs-checksum.bats │ │ ├── create-with-upgrading.bats │ │ ├── custom-mem-disk.bats │ │ ├── dns.bats │ │ ├── guards.bats │ │ ├── pause-save-start.bats │ │ └── upgrade.bats └── provision │ ├── rancheros.bats │ └── redhat.bats └── version └── version.go /.dockerignore: -------------------------------------------------------------------------------- 1 | *.log 2 | cover 3 | bin 4 | ./bin 5 | ./.dapper 6 | ./dist 7 | ./.github 8 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "github>rancher/renovate-config#release" 4 | ], 5 | "baseBranches": [ 6 | "master" 7 | ], 8 | "prHourlyLimit": 2 9 | } 10 | -------------------------------------------------------------------------------- /.github/workflows/fossa.yaml: -------------------------------------------------------------------------------- 1 | name: Run Fossa Scan 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | # For manual scans. 8 | workflow_dispatch: 9 | 10 | jobs: 11 | fossa: 12 | runs-on: ubuntu-latest 13 | permissions: 14 | contents: read 15 | id-token: write # needed for the Vault authentication 16 | steps: 17 | - name: Checkout 18 | uses: actions/checkout@v4 19 | 20 | - name: Read FOSSA token 21 | uses: rancher-eio/read-vault-secrets@main 22 | with: 23 | secrets: | 24 | secret/data/github/org/rancher/fossa/push token | FOSSA_API_KEY_PUSH_ONLY 25 | 26 | - name: FOSSA scan 27 | uses: fossas/fossa-action@main 28 | with: 29 | api-key: ${{ env.FOSSA_API_KEY_PUSH_ONLY }} 30 | run-tests: false 31 | -------------------------------------------------------------------------------- /.github/workflows/renovate.yml: -------------------------------------------------------------------------------- 1 | name: Renovate 2 | on: 3 | workflow_dispatch: 4 | inputs: 5 | logLevel: 6 | description: "Override default log level" 7 | required: false 8 | default: "info" 9 | type: string 10 | overrideSchedule: 11 | description: "Override all schedules" 12 | required: false 13 | default: "false" 14 | type: string 15 | # Run twice in the early morning (UTC) for initial and follow up steps (create pull request and merge) 16 | schedule: 17 | - cron: '30 4,6 * * *' 18 | 19 | jobs: 20 | call-workflow: 21 | uses: rancher/renovate-config/.github/workflows/renovate.yml@release 22 | with: 23 | logLevel: ${{ inputs.logLevel || 'info' }} 24 | overrideSchedule: ${{ github.event.inputs.overrideSchedule == 'true' && '{''schedule'':null}' || '' }} 25 | secrets: inherit 26 | -------------------------------------------------------------------------------- /.github/workflows/test.yaml: -------------------------------------------------------------------------------- 1 | name: Run Tests 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | 9 | jobs: 10 | tests: 11 | runs-on: ubuntu-latest 12 | permissions: 13 | contents: read 14 | steps: 15 | - name: Checkout code 16 | uses: actions/checkout@v4 17 | - name: Setup Go 18 | uses: actions/setup-go@v5 19 | with: 20 | go-version-file: 'go.mod' 21 | - name: Run tests 22 | run: ./scripts/test 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.log 2 | *.iml 3 | .idea/ 4 | /bin 5 | cover 6 | /.dapper 7 | /dist 8 | *.swp 9 | rancher-machine 10 | .DS_STORE 11 | test-data* 12 | -------------------------------------------------------------------------------- /Dockerfile.dapper: -------------------------------------------------------------------------------- 1 | FROM registry.suse.com/bci/golang:1.24 2 | 3 | ARG DAPPER_HOST_ARCH 4 | ENV HOST_ARCH=${DAPPER_HOST_ARCH} ARCH=${DAPPER_HOST_ARCH} 5 | 6 | RUN zypper -n install gcc ca-certificates git wget curl vim less file tar gzip && \ 7 | rm -f /bin/sh && ln -s /bin/bash /bin/sh 8 | 9 | ENV GOLANG_ARCH_s390x=s390x GOLANG_ARCH_amd64=amd64 GOLANG_ARCH_arm=armv6l GOLANG_ARCH_arm64=arm64 GOLANG_ARCH=GOLANG_ARCH_${ARCH} \ 10 | GOPATH=/go PATH=/go/bin:/usr/local/go/bin:${PATH} SHELL=/bin/bash 11 | 12 | RUN if [ "${ARCH}" == "amd64" ]; then \ 13 | curl -sL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | sh -s v1.63.4; \ 14 | fi 15 | 16 | ENV DOCKER_URL_amd64=https://get.docker.com/builds/Linux/x86_64/docker-1.10.3 \ 17 | DOCKER_URL_arm=https://github.com/rancher/docker/releases/download/v1.10.3-ros1/docker-1.10.3_arm \ 18 | DOCKER_URL_arm64=https://github.com/rancher/docker/releases/download/v1.10.3-ros1/docker-1.10.3_arm64 \ 19 | DOCKER_URL=DOCKER_URL_${ARCH} 20 | 21 | RUN if [ "${ARCH}" == "s390x" ]; then \ 22 | curl -L https://download.docker.com/linux/static/stable/s390x/docker-18.06.3-ce.tgz | tar xzvf - && \ 23 | cp docker/docker /usr/bin/ && \ 24 | chmod +x /usr/bin/docker; \ 25 | else \ 26 | wget -O - ${!DOCKER_URL} > /usr/bin/docker && chmod +x /usr/bin/docker; \ 27 | fi 28 | 29 | ENV DAPPER_ENV CROSS DRONE_TAG 30 | ENV DAPPER_SOURCE /go/src/github.com/rancher/machine/ 31 | ENV DAPPER_OUTPUT ./bin ./dist 32 | ENV DAPPER_DOCKER_SOCKET true 33 | ENV HOME ${DAPPER_SOURCE} 34 | WORKDIR ${DAPPER_SOURCE} 35 | 36 | ENTRYPOINT ["./scripts/entry"] 37 | CMD ["ci"] 38 | 39 | RUN zypper -n install unzip 40 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | TARGETS := $(shell ls scripts) 2 | 3 | .dapper: 4 | @echo Downloading dapper 5 | @curl -sL https://releases.rancher.com/dapper/latest/dapper-$$(uname -s)-$$(uname -m) > .dapper.tmp 6 | @@chmod +x .dapper.tmp 7 | @./.dapper.tmp -v 8 | @mv .dapper.tmp .dapper 9 | 10 | $(TARGETS): .dapper 11 | ./.dapper $@ 12 | 13 | .DEFAULT_GOAL := ci 14 | 15 | .PHONY: $(TARGETS) 16 | -------------------------------------------------------------------------------- /Makefile.inc: -------------------------------------------------------------------------------- 1 | # Project name, used to name the binaries 2 | PKG_NAME := docker-machine 3 | 4 | # If true, disable optimizations and does NOT strip the binary 5 | DEBUG ?= 6 | # If true, "build" will produce a static binary (cross compile always produce static build regardless) 7 | STATIC ?= 8 | # If true, turn on verbose output for build 9 | VERBOSE ?= 10 | # Build tags 11 | BUILDTAGS ?= 12 | # Adjust number of parallel builds (XXX not used) 13 | PARALLEL ?= -1 14 | # Coverage default directory 15 | COVERAGE_DIR ?= cover 16 | # Whether to perform targets inside a docker container, or natively on the host 17 | USE_CONTAINER ?= 18 | 19 | # List of cross compilation targets 20 | ifeq ($(TARGET_OS),) 21 | TARGET_OS := darwin linux windows 22 | endif 23 | 24 | ifeq ($(TARGET_ARCH),) 25 | TARGET_ARCH := amd64 arm arm64 386 26 | endif 27 | 28 | # Output prefix, defaults to local directory if not specified 29 | ifeq ($(PREFIX),) 30 | PREFIX := $(shell pwd) 31 | endif 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rancher Machine, a fork of [Docker Machine](https://github.com/docker/machine) 2 | 3 | Machine lets you create Docker hosts on your computer, on cloud providers, and 4 | inside your own data center. It creates servers, installs Docker on them, then 5 | configures the Docker client to talk to them. 6 | 7 | ## Installation and documentation 8 | The original full Docker Machine documentation [is available here](https://gcbw.github.io/docker.github.io/machine/). 9 | 10 | This project is intended to be embedded and executed by the full [Rancher](https://github.com/rancher/rancher) product 11 | and the stand alone cli functionality will remain but the human use of it will not be the primary focus as we will expect 12 | inputs provided by other things like Terraform or UIs. 13 | 14 | Cli binaries can be found in our [Releases Pages](https://github.com/rancher/machine/releases) 15 | 16 | ## Issues 17 | 18 | For historical context you can read the [Docker Machine Issues](https://github.com/docker/machine/issues) 19 | but all new issues created for Rancher Machine will need to be created 20 | in [Rancher](https://github.com/rancher/rancher/issues) 21 | 22 | ## Driver Plugins 23 | 24 | In addition to the core driver plugins bundled alongside Rancher Machine, users 25 | can make and distribute their own plugin for any virtualization technology or 26 | cloud provider. To browse the list of known Rancher Machine plugins, please [see 27 | this document in our 28 | docs repo](https://github.com/docker/docker.github.io/blob/master/machine/AVAILABLE_DRIVER_PLUGINS.md). 29 | -------------------------------------------------------------------------------- /commands/active.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | 7 | "time" 8 | 9 | "github.com/rancher/machine/libmachine" 10 | "github.com/rancher/machine/libmachine/persist" 11 | "github.com/rancher/machine/libmachine/state" 12 | ) 13 | 14 | const ( 15 | activeDefaultTimeout = 10 16 | ) 17 | 18 | var ( 19 | errNoActiveHost = errors.New("No active host found") 20 | errActiveTimeout = errors.New("Error getting active host: timeout") 21 | ) 22 | 23 | func cmdActive(c CommandLine, api libmachine.API) error { 24 | if len(c.Args()) > 0 { 25 | return ErrTooManyArguments 26 | } 27 | 28 | hosts, hostsInError, err := persist.LoadAllHosts(api) 29 | if err != nil { 30 | return fmt.Errorf("Error getting active host: %s", err) 31 | } 32 | 33 | timeout := time.Duration(c.Int("timeout")) * time.Second 34 | items := getHostListItems(hosts, hostsInError, timeout) 35 | 36 | active, err := activeHost(items) 37 | 38 | if err != nil { 39 | return err 40 | } 41 | 42 | fmt.Println(active.Name) 43 | return nil 44 | } 45 | 46 | func activeHost(items []HostListItem) (HostListItem, error) { 47 | timeout := false 48 | for _, item := range items { 49 | if item.ActiveHost || item.ActiveSwarm { 50 | return item, nil 51 | } 52 | if item.State == state.Timeout { 53 | timeout = true 54 | } 55 | } 56 | if timeout { 57 | return HostListItem{}, errActiveTimeout 58 | } 59 | return HostListItem{}, errNoActiveHost 60 | } 61 | -------------------------------------------------------------------------------- /commands/commandstest/fake_command_line.go: -------------------------------------------------------------------------------- 1 | package commandstest 2 | 3 | import ( 4 | "github.com/urfave/cli" 5 | ) 6 | 7 | type FakeFlagger struct { 8 | Data map[string]interface{} 9 | } 10 | 11 | type FakeCommandLine struct { 12 | LocalFlags, GlobalFlags *FakeFlagger 13 | HelpShown, VersionShown bool 14 | CliArgs []string 15 | } 16 | 17 | func (ff FakeFlagger) String(key string) string { 18 | if value, ok := ff.Data[key]; ok { 19 | return value.(string) 20 | } 21 | return "" 22 | } 23 | 24 | func (ff FakeFlagger) StringSlice(key string) []string { 25 | if value, ok := ff.Data[key]; ok { 26 | return value.([]string) 27 | } 28 | return []string{} 29 | } 30 | 31 | func (ff FakeFlagger) Int(key string) int { 32 | if value, ok := ff.Data[key]; ok { 33 | return value.(int) 34 | } 35 | return 0 36 | } 37 | 38 | func (ff FakeFlagger) Bool(key string) bool { 39 | if value, ok := ff.Data[key]; ok { 40 | return value.(bool) 41 | } 42 | return false 43 | } 44 | 45 | func (fcli *FakeCommandLine) IsSet(key string) bool { 46 | _, ok := fcli.LocalFlags.Data[key] 47 | return ok 48 | } 49 | 50 | func (fcli *FakeCommandLine) String(key string) string { 51 | return fcli.LocalFlags.String(key) 52 | } 53 | 54 | func (fcli *FakeCommandLine) StringSlice(key string) []string { 55 | return fcli.LocalFlags.StringSlice(key) 56 | } 57 | 58 | func (fcli *FakeCommandLine) Int(key string) int { 59 | return fcli.LocalFlags.Int(key) 60 | } 61 | 62 | func (fcli *FakeCommandLine) Bool(key string) bool { 63 | if fcli.LocalFlags == nil { 64 | return false 65 | } 66 | return fcli.LocalFlags.Bool(key) 67 | } 68 | 69 | func (fcli *FakeCommandLine) GlobalString(key string) string { 70 | return fcli.GlobalFlags.String(key) 71 | } 72 | 73 | func (fcli *FakeCommandLine) Generic(name string) interface{} { 74 | return fcli.LocalFlags.Data[name] 75 | } 76 | 77 | func (fcli *FakeCommandLine) FlagNames() []string { 78 | flagNames := []string{} 79 | for key := range fcli.LocalFlags.Data { 80 | flagNames = append(flagNames, key) 81 | } 82 | 83 | return flagNames 84 | } 85 | 86 | func (fcli *FakeCommandLine) ShowHelp() { 87 | fcli.HelpShown = true 88 | } 89 | 90 | func (fcli *FakeCommandLine) Application() *cli.App { 91 | return cli.NewApp() 92 | } 93 | 94 | func (fcli *FakeCommandLine) Args() cli.Args { 95 | return fcli.CliArgs 96 | } 97 | 98 | func (fcli *FakeCommandLine) ShowVersion() { 99 | fcli.VersionShown = true 100 | } 101 | -------------------------------------------------------------------------------- /commands/commandstest/stdout_capture.go: -------------------------------------------------------------------------------- 1 | package commandstest 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | 7 | "os" 8 | ) 9 | 10 | var ( 11 | stdout *os.File 12 | ) 13 | 14 | func init() { 15 | stdout = os.Stdout 16 | } 17 | 18 | type StdoutGetter interface { 19 | Output() string 20 | Stop() 21 | } 22 | 23 | type stdoutCapturer struct { 24 | stdout *os.File 25 | output chan string 26 | } 27 | 28 | func NewStdoutGetter() StdoutGetter { 29 | r, w, _ := os.Pipe() 30 | os.Stdout = w 31 | 32 | output := make(chan string) 33 | go func() { 34 | var testOutput bytes.Buffer 35 | io.Copy(&testOutput, r) 36 | output <- testOutput.String() 37 | }() 38 | 39 | return &stdoutCapturer{ 40 | stdout: w, 41 | output: output, 42 | } 43 | } 44 | 45 | func (c *stdoutCapturer) Output() string { 46 | c.stdout.Close() 47 | text := <-c.output 48 | close(c.output) 49 | return text 50 | } 51 | 52 | func (c *stdoutCapturer) Stop() { 53 | os.Stdout = stdout 54 | } 55 | -------------------------------------------------------------------------------- /commands/config.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | 8 | "github.com/rancher/machine/commands/mcndirs" 9 | "github.com/rancher/machine/libmachine" 10 | "github.com/rancher/machine/libmachine/check" 11 | "github.com/rancher/machine/libmachine/log" 12 | ) 13 | 14 | func cmdConfig(c CommandLine, api libmachine.API) error { 15 | // Ensure that log messages always go to stderr when this command is 16 | // being run (it is intended to be run in a subshell) 17 | log.SetOutWriter(os.Stderr) 18 | 19 | target, err := targetHost(c, api) 20 | if err != nil { 21 | return err 22 | } 23 | 24 | host, err := api.Load(target) 25 | if err != nil { 26 | return err 27 | } 28 | 29 | dockerHost, _, err := check.DefaultConnChecker.Check(host, c.Bool("swarm")) 30 | if err != nil { 31 | return fmt.Errorf("Error running connection boilerplate: %s", err) 32 | } 33 | 34 | log.Debug(dockerHost) 35 | 36 | tlsCACert := filepath.Join(mcndirs.GetMachineDir(), host.Name, "ca.pem") 37 | tlsCert := filepath.Join(mcndirs.GetMachineDir(), host.Name, "cert.pem") 38 | tlsKey := filepath.Join(mcndirs.GetMachineDir(), host.Name, "key.pem") 39 | 40 | // TODO(nathanleclaire): These magic strings for the certificate file 41 | // names should be cross-package constants. 42 | fmt.Printf("--tlsverify\n--tlscacert=%q\n--tlscert=%q\n--tlskey=%q\n-H=%s\n", 43 | tlsCACert, tlsCert, tlsKey, dockerHost) 44 | 45 | return nil 46 | } 47 | -------------------------------------------------------------------------------- /commands/flag_sort.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import "github.com/urfave/cli" 4 | 5 | type ByFlagName []cli.Flag 6 | 7 | func (flags ByFlagName) Len() int { 8 | return len(flags) 9 | } 10 | 11 | func (flags ByFlagName) Swap(i, j int) { 12 | flags[i], flags[j] = flags[j], flags[i] 13 | } 14 | 15 | func (flags ByFlagName) Less(i, j int) bool { 16 | return flags[i].String() < flags[j].String() 17 | } 18 | -------------------------------------------------------------------------------- /commands/inspect.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "encoding/json" 5 | "fmt" 6 | "os" 7 | "text/template" 8 | 9 | "github.com/rancher/machine/libmachine" 10 | ) 11 | 12 | var funcMap = template.FuncMap{ 13 | "json": func(v interface{}) string { 14 | a, _ := json.Marshal(v) 15 | return string(a) 16 | }, 17 | "prettyjson": func(v interface{}) string { 18 | a, _ := json.MarshalIndent(v, "", " ") 19 | return string(a) 20 | }, 21 | } 22 | 23 | func cmdInspect(c CommandLine, api libmachine.API) error { 24 | if len(c.Args()) > 1 { 25 | c.ShowHelp() 26 | return ErrExpectedOneMachine 27 | } 28 | 29 | target, err := targetHost(c, api) 30 | if err != nil { 31 | return err 32 | } 33 | 34 | host, err := api.Load(target) 35 | if err != nil { 36 | return err 37 | } 38 | 39 | tmplString := c.String("format") 40 | if tmplString != "" { 41 | var tmpl *template.Template 42 | var err error 43 | if tmpl, err = template.New("").Funcs(funcMap).Parse(tmplString); err != nil { 44 | return fmt.Errorf("template parsing error: %v", err) 45 | } 46 | 47 | jsonHost, err := json.Marshal(host) 48 | if err != nil { 49 | return err 50 | } 51 | 52 | obj := make(map[string]interface{}) 53 | if err := json.Unmarshal(jsonHost, &obj); err != nil { 54 | return err 55 | } 56 | 57 | if err := tmpl.Execute(os.Stdout, obj); err != nil { 58 | return err 59 | } 60 | 61 | os.Stdout.Write([]byte{'\n'}) 62 | } else { 63 | prettyJSON, err := json.MarshalIndent(host, "", " ") 64 | if err != nil { 65 | return err 66 | } 67 | 68 | fmt.Println(string(prettyJSON)) 69 | } 70 | 71 | return nil 72 | } 73 | -------------------------------------------------------------------------------- /commands/inspect_test.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/rancher/machine/commands/commandstest" 7 | "github.com/rancher/machine/libmachine" 8 | "github.com/rancher/machine/libmachine/libmachinetest" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestCmdInspect(t *testing.T) { 13 | testCases := []struct { 14 | commandLine CommandLine 15 | api libmachine.API 16 | expectedErr error 17 | }{ 18 | { 19 | commandLine: &commandstest.FakeCommandLine{ 20 | CliArgs: []string{"foo", "bar"}, 21 | }, 22 | api: &libmachinetest.FakeAPI{}, 23 | expectedErr: ErrExpectedOneMachine, 24 | }, 25 | } 26 | 27 | for _, tc := range testCases { 28 | err := cmdInspect(tc.commandLine, tc.api) 29 | assert.Equal(t, tc.expectedErr, err) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /commands/ip.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import "github.com/rancher/machine/libmachine" 4 | 5 | func cmdIP(c CommandLine, api libmachine.API) error { 6 | return runAction("ip", c, api) 7 | } 8 | -------------------------------------------------------------------------------- /commands/ip_test.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/rancher/machine/commands/commandstest" 7 | "github.com/rancher/machine/drivers/fakedriver" 8 | "github.com/rancher/machine/libmachine" 9 | "github.com/rancher/machine/libmachine/host" 10 | "github.com/rancher/machine/libmachine/libmachinetest" 11 | "github.com/rancher/machine/libmachine/state" 12 | "github.com/stretchr/testify/assert" 13 | ) 14 | 15 | func TestCmdIPMissingMachineName(t *testing.T) { 16 | commandLine := &commandstest.FakeCommandLine{} 17 | api := &libmachinetest.FakeAPI{} 18 | 19 | err := cmdURL(commandLine, api) 20 | 21 | assert.Equal(t, err, ErrNoDefault) 22 | } 23 | 24 | func TestCmdIP(t *testing.T) { 25 | testCases := []struct { 26 | commandLine CommandLine 27 | api libmachine.API 28 | expectedErr error 29 | expectedOut string 30 | }{ 31 | { 32 | commandLine: &commandstest.FakeCommandLine{ 33 | CliArgs: []string{"machine"}, 34 | }, 35 | api: &libmachinetest.FakeAPI{ 36 | Hosts: []*host.Host{ 37 | { 38 | Name: "machine", 39 | Driver: &fakedriver.Driver{ 40 | MockState: state.Running, 41 | MockIP: "1.2.3.4", 42 | }, 43 | }, 44 | }, 45 | }, 46 | expectedErr: nil, 47 | expectedOut: "1.2.3.4\n", 48 | }, 49 | { 50 | commandLine: &commandstest.FakeCommandLine{ 51 | CliArgs: []string{}, 52 | }, 53 | api: &libmachinetest.FakeAPI{ 54 | Hosts: []*host.Host{ 55 | { 56 | Name: "default", 57 | Driver: &fakedriver.Driver{ 58 | MockState: state.Running, 59 | MockIP: "1.2.3.4", 60 | }, 61 | }, 62 | }, 63 | }, 64 | expectedErr: nil, 65 | expectedOut: "1.2.3.4\n", 66 | }, 67 | } 68 | 69 | for _, tc := range testCases { 70 | stdoutGetter := commandstest.NewStdoutGetter() 71 | 72 | err := cmdIP(tc.commandLine, tc.api) 73 | 74 | assert.Equal(t, tc.expectedErr, err) 75 | assert.Equal(t, tc.expectedOut, stdoutGetter.Output()) 76 | 77 | stdoutGetter.Stop() 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /commands/kill.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import "github.com/rancher/machine/libmachine" 4 | 5 | func cmdKill(c CommandLine, api libmachine.API) error { 6 | return runAction("kill", c, api) 7 | } 8 | -------------------------------------------------------------------------------- /commands/kill_test.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/rancher/machine/commands/commandstest" 7 | "github.com/rancher/machine/drivers/fakedriver" 8 | "github.com/rancher/machine/libmachine/host" 9 | "github.com/rancher/machine/libmachine/libmachinetest" 10 | "github.com/rancher/machine/libmachine/state" 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func TestCmdKillMissingMachineName(t *testing.T) { 15 | commandLine := &commandstest.FakeCommandLine{} 16 | api := &libmachinetest.FakeAPI{} 17 | 18 | err := cmdKill(commandLine, api) 19 | 20 | assert.Equal(t, ErrNoDefault, err) 21 | } 22 | 23 | func TestCmdKill(t *testing.T) { 24 | commandLine := &commandstest.FakeCommandLine{ 25 | CliArgs: []string{"machineToKill1", "machineToKill2"}, 26 | } 27 | api := &libmachinetest.FakeAPI{ 28 | Hosts: []*host.Host{ 29 | { 30 | Name: "machineToKill1", 31 | Driver: &fakedriver.Driver{ 32 | MockState: state.Running, 33 | }, 34 | }, 35 | { 36 | Name: "machineToKill2", 37 | Driver: &fakedriver.Driver{ 38 | MockState: state.Running, 39 | }, 40 | }, 41 | { 42 | Name: "machine", 43 | Driver: &fakedriver.Driver{ 44 | MockState: state.Running, 45 | }, 46 | }, 47 | }, 48 | } 49 | 50 | err := cmdKill(commandLine, api) 51 | assert.NoError(t, err) 52 | 53 | assert.Equal(t, state.Stopped, libmachinetest.State(api, "machineToKill1")) 54 | assert.Equal(t, state.Stopped, libmachinetest.State(api, "machineToKill2")) 55 | assert.Equal(t, state.Running, libmachinetest.State(api, "machine")) 56 | } 57 | -------------------------------------------------------------------------------- /commands/mcndirs/utils.go: -------------------------------------------------------------------------------- 1 | package mcndirs 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | 7 | "github.com/rancher/machine/libmachine/mcnutils" 8 | ) 9 | 10 | var ( 11 | BaseDir = os.Getenv("MACHINE_STORAGE_PATH") 12 | ) 13 | 14 | func GetBaseDir() string { 15 | if BaseDir == "" { 16 | BaseDir = filepath.Join(mcnutils.GetHomeDir(), ".docker", "machine") 17 | } 18 | return BaseDir 19 | } 20 | 21 | func GetMachineDir() string { 22 | return filepath.Join(GetBaseDir(), "machines") 23 | } 24 | 25 | func GetMachineCertDir() string { 26 | return filepath.Join(GetBaseDir(), "certs") 27 | } 28 | -------------------------------------------------------------------------------- /commands/mcndirs/utils_test.go: -------------------------------------------------------------------------------- 1 | package mcndirs 2 | 3 | import ( 4 | "path" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/rancher/machine/libmachine/mcnutils" 9 | ) 10 | 11 | func TestGetBaseDir(t *testing.T) { 12 | // reset any override env var 13 | BaseDir = "" 14 | 15 | homeDir := mcnutils.GetHomeDir() 16 | baseDir := GetBaseDir() 17 | 18 | if strings.Index(baseDir, homeDir) != 0 { 19 | t.Fatalf("expected base dir with prefix %s; received %s", homeDir, baseDir) 20 | } 21 | } 22 | 23 | func TestGetCustomBaseDir(t *testing.T) { 24 | root := "/tmp" 25 | BaseDir = root 26 | baseDir := GetBaseDir() 27 | 28 | if strings.Index(baseDir, root) != 0 { 29 | t.Fatalf("expected base dir with prefix %s; received %s", root, baseDir) 30 | } 31 | BaseDir = "" 32 | } 33 | 34 | func TestGetMachineDir(t *testing.T) { 35 | root := "/tmp" 36 | BaseDir = root 37 | machineDir := GetMachineDir() 38 | 39 | if strings.Index(machineDir, root) != 0 { 40 | t.Fatalf("expected machine dir with prefix %s; received %s", root, machineDir) 41 | } 42 | 43 | path, filename := path.Split(machineDir) 44 | if strings.Index(path, root) != 0 { 45 | t.Fatalf("expected base path of %s; received %s", root, path) 46 | } 47 | if filename != "machines" { 48 | t.Fatalf("expected machine dir \"machines\"; received %s", filename) 49 | } 50 | BaseDir = "" 51 | } 52 | 53 | func TestGetMachineCertDir(t *testing.T) { 54 | root := "/tmp" 55 | BaseDir = root 56 | clientDir := GetMachineCertDir() 57 | 58 | if strings.Index(clientDir, root) != 0 { 59 | t.Fatalf("expected machine client cert dir with prefix %s; received %s", root, clientDir) 60 | } 61 | 62 | path, filename := path.Split(clientDir) 63 | if strings.Index(path, root) != 0 { 64 | t.Fatalf("expected base path of %s; received %s", root, path) 65 | } 66 | if filename != "certs" { 67 | t.Fatalf("expected machine client dir \"certs\"; received %s", filename) 68 | } 69 | BaseDir = "" 70 | } 71 | -------------------------------------------------------------------------------- /commands/mount_test.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "os/exec" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestGetMountCmd(t *testing.T) { 11 | hostInfoLoader := MockHostInfoLoader{MockHostInfo{ 12 | ip: "12.34.56.78", 13 | sshPort: 234, 14 | sshUsername: "root", 15 | sshKeyPath: "/fake/keypath/id_rsa", 16 | }} 17 | 18 | path, err := exec.LookPath("sshfs") 19 | if err != nil { 20 | t.Skip("sshfs not found (install sshfs ?)") 21 | } 22 | cmd, err := getMountCmd("myfunhost:/home/docker/foo", "/tmp/foo", false, &hostInfoLoader) 23 | 24 | expectedArgs := append( 25 | baseSSHFSArgs, 26 | "-o", 27 | "IdentitiesOnly=yes", 28 | "-o", 29 | "Port=234", 30 | "-o", 31 | "IdentityFile=/fake/keypath/id_rsa", 32 | "root@12.34.56.78:/home/docker/foo", 33 | "/tmp/foo", 34 | ) 35 | expectedCmd := exec.Command(path, expectedArgs...) 36 | 37 | assert.Equal(t, expectedCmd, cmd) 38 | assert.NoError(t, err) 39 | } 40 | 41 | func TestGetMountCmdWithoutSshKey(t *testing.T) { 42 | hostInfoLoader := MockHostInfoLoader{MockHostInfo{ 43 | ip: "1.2.3.4", 44 | sshUsername: "user", 45 | }} 46 | 47 | path, err := exec.LookPath("sshfs") 48 | if err != nil { 49 | t.Skip("sshfs not found (install sshfs ?)") 50 | } 51 | cmd, err := getMountCmd("myfunhost:/home/docker/foo", "", false, &hostInfoLoader) 52 | 53 | expectedArgs := append( 54 | baseSSHFSArgs, 55 | "user@1.2.3.4:/home/docker/foo", 56 | "/home/docker/foo", 57 | ) 58 | expectedCmd := exec.Command(path, expectedArgs...) 59 | 60 | assert.Equal(t, expectedCmd, cmd) 61 | assert.NoError(t, err) 62 | } 63 | 64 | func TestGetMountCmdUnmount(t *testing.T) { 65 | hostInfoLoader := MockHostInfoLoader{MockHostInfo{ 66 | ip: "1.2.3.4", 67 | sshUsername: "user", 68 | }} 69 | 70 | path, err := exec.LookPath("fusermount") 71 | if err != nil { 72 | t.Skip("fusermount not found (install fuse ?)") 73 | } 74 | cmd, err := getMountCmd("myfunhost:/home/docker/foo", "/tmp/foo", true, &hostInfoLoader) 75 | 76 | expectedArgs := []string{ 77 | "-u", 78 | "/tmp/foo", 79 | } 80 | expectedCmd := exec.Command(path, expectedArgs...) 81 | 82 | assert.Equal(t, expectedCmd, cmd) 83 | assert.NoError(t, err) 84 | } 85 | -------------------------------------------------------------------------------- /commands/provision.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import "github.com/rancher/machine/libmachine" 4 | 5 | func cmdProvision(c CommandLine, api libmachine.API) error { 6 | return runAction("provision", c, api) 7 | } 8 | -------------------------------------------------------------------------------- /commands/provision_test.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/rancher/machine/commands/commandstest" 7 | "github.com/rancher/machine/drivers/fakedriver" 8 | "github.com/rancher/machine/libmachine" 9 | "github.com/rancher/machine/libmachine/auth" 10 | "github.com/rancher/machine/libmachine/engine" 11 | "github.com/rancher/machine/libmachine/host" 12 | "github.com/rancher/machine/libmachine/libmachinetest" 13 | "github.com/rancher/machine/libmachine/provision" 14 | "github.com/rancher/machine/libmachine/swarm" 15 | "github.com/stretchr/testify/assert" 16 | ) 17 | 18 | func TestCmdProvision(t *testing.T) { 19 | testCases := []struct { 20 | commandLine CommandLine 21 | api libmachine.API 22 | expectedErr error 23 | }{ 24 | { 25 | commandLine: &commandstest.FakeCommandLine{ 26 | CliArgs: []string{"foo", "bar"}, 27 | }, 28 | api: &libmachinetest.FakeAPI{ 29 | Hosts: []*host.Host{ 30 | { 31 | Name: "foo", 32 | Driver: &fakedriver.Driver{}, 33 | HostOptions: &host.Options{ 34 | EngineOptions: &engine.Options{}, 35 | AuthOptions: &auth.Options{}, 36 | SwarmOptions: &swarm.Options{}, 37 | }, 38 | }, 39 | { 40 | Name: "bar", 41 | Driver: &fakedriver.Driver{}, 42 | HostOptions: &host.Options{ 43 | EngineOptions: &engine.Options{}, 44 | AuthOptions: &auth.Options{}, 45 | SwarmOptions: &swarm.Options{}, 46 | }, 47 | }, 48 | }, 49 | }, 50 | expectedErr: nil, 51 | }, 52 | } 53 | 54 | provision.SetDetector(&provision.FakeDetector{ 55 | Provisioner: provision.NewFakeProvisioner(nil), 56 | }) 57 | 58 | // fakeprovisioner always returns "true" for compatible host, so we 59 | // just need to register it. 60 | provision.Register("fakeprovisioner", &provision.RegisteredProvisioner{ 61 | New: provision.NewFakeProvisioner, 62 | }) 63 | 64 | for _, tc := range testCases { 65 | assert.Equal(t, tc.expectedErr, cmdProvision(tc.commandLine, tc.api)) 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /commands/regeneratecerts.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/rancher/machine/libmachine" 5 | "github.com/rancher/machine/libmachine/log" 6 | ) 7 | 8 | func cmdRegenerateCerts(c CommandLine, api libmachine.API) error { 9 | if !c.Bool("force") { 10 | ok, err := confirmInput("Regenerate TLS machine certs? Warning: this is irreversible.") 11 | if err != nil { 12 | return err 13 | } 14 | 15 | if !ok { 16 | return nil 17 | } 18 | } 19 | 20 | log.Infof("Regenerating TLS certificates") 21 | 22 | if c.Bool("client-certs") { 23 | return runAction("configureAllAuth", c, api) 24 | } 25 | return runAction("configureAuth", c, api) 26 | } 27 | -------------------------------------------------------------------------------- /commands/restart.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/rancher/machine/libmachine" 5 | "github.com/rancher/machine/libmachine/log" 6 | ) 7 | 8 | func cmdRestart(c CommandLine, api libmachine.API) error { 9 | if err := runAction("restart", c, api); err != nil { 10 | return err 11 | } 12 | 13 | log.Info("Restarted machines may have new IP addresses. You may need to re-run the `docker-machine env` command.") 14 | 15 | return nil 16 | } 17 | -------------------------------------------------------------------------------- /commands/scp_unix.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package commands 4 | 5 | import ( 6 | "github.com/rancher/machine/libmachine" 7 | ) 8 | 9 | func cmdScp(c CommandLine, api libmachine.API) error { 10 | args := c.Args() 11 | if len(args) != 2 { 12 | c.ShowHelp() 13 | return errWrongNumberArguments 14 | } 15 | 16 | src := args[0] 17 | dest := args[1] 18 | 19 | hostInfoLoader := &storeHostInfoLoader{api} 20 | 21 | cmd, err := getScpCmd(src, dest, c.Bool("recursive"), c.Bool("delta"), c.Bool("quiet"), hostInfoLoader) 22 | if err != nil { 23 | return err 24 | } 25 | 26 | return runCmdWithStdIo(*cmd) 27 | } 28 | -------------------------------------------------------------------------------- /commands/scp_windows.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | "syscall" 7 | 8 | "github.com/rancher/machine/libmachine" 9 | ) 10 | 11 | func cmdScp(c CommandLine, api libmachine.API) error { 12 | args := c.Args() 13 | if len(args) != 2 { 14 | c.ShowHelp() 15 | return errWrongNumberArguments 16 | } 17 | 18 | src := args[0] 19 | dest := args[1] 20 | 21 | hostInfoLoader := &storeHostInfoLoader{api} 22 | 23 | cmd, err := getScpCmd(src, dest, c.Bool("recursive"), c.Bool("delta"), c.Bool("quiet"), hostInfoLoader) 24 | if err != nil { 25 | return err 26 | } 27 | 28 | // Default argument escaping is not valid for scp.exe with quoted arguments, so we do it ourselves 29 | // see golang/go#15566 30 | cmd.SysProcAttr = &syscall.SysProcAttr{} 31 | cmd.SysProcAttr.CmdLine = fmt.Sprintf("%s %s", cmd.Path, strings.Join(cmd.Args, " ")) 32 | 33 | return runCmdWithStdIo(*cmd) 34 | } 35 | -------------------------------------------------------------------------------- /commands/ssh.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/rancher/machine/libmachine" 7 | "github.com/rancher/machine/libmachine/state" 8 | ) 9 | 10 | type errStateInvalidForSSH struct { 11 | HostName string 12 | } 13 | 14 | func (e errStateInvalidForSSH) Error() string { 15 | return fmt.Sprintf("Error: Cannot run SSH command: Host %q is not running", e.HostName) 16 | } 17 | 18 | func cmdSSH(c CommandLine, api libmachine.API) error { 19 | // Check for help flag -- Needed due to SkipFlagParsing 20 | firstArg := c.Args().First() 21 | if firstArg == "-help" || firstArg == "--help" || firstArg == "-h" { 22 | c.ShowHelp() 23 | return nil 24 | } 25 | 26 | target, err := targetHost(c, api) 27 | if err != nil { 28 | return err 29 | } 30 | 31 | host, err := api.Load(target) 32 | if err != nil { 33 | return err 34 | } 35 | 36 | currentState, err := host.Driver.GetState() 37 | if err != nil { 38 | return err 39 | } 40 | 41 | if currentState != state.Running { 42 | return errStateInvalidForSSH{host.Name} 43 | } 44 | 45 | client, err := host.CreateSSHClient() 46 | if err != nil { 47 | return err 48 | } 49 | 50 | return client.Shell(c.Args().Tail()...) 51 | } 52 | -------------------------------------------------------------------------------- /commands/start.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "github.com/rancher/machine/libmachine" 5 | "github.com/rancher/machine/libmachine/log" 6 | ) 7 | 8 | func cmdStart(c CommandLine, api libmachine.API) error { 9 | if err := runAction("start", c, api); err != nil { 10 | return err 11 | } 12 | 13 | log.Info("Started machines may have new IP addresses. You may need to re-run the `docker-machine env` command.") 14 | 15 | return nil 16 | } 17 | -------------------------------------------------------------------------------- /commands/status.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/rancher/machine/libmachine" 8 | "github.com/rancher/machine/libmachine/log" 9 | "github.com/rancher/machine/libmachine/state" 10 | ) 11 | 12 | type notFoundError string 13 | 14 | func (nf notFoundError) Error() string { 15 | return string(nf) 16 | } 17 | 18 | func cmdStatus(c CommandLine, api libmachine.API) error { 19 | if len(c.Args()) > 1 { 20 | return ErrExpectedOneMachine 21 | } 22 | 23 | target, err := targetHost(c, api) 24 | if err != nil { 25 | return err 26 | } 27 | 28 | host, err := api.Load(target) 29 | if err != nil { 30 | return err 31 | } 32 | 33 | // Save any host configuration that may have changed before returning. 34 | defer func() { 35 | if saveErr := api.Save(host); saveErr != nil { 36 | log.Warnf("error saving updated host configuration: %v", saveErr) 37 | } 38 | }() 39 | 40 | currentState, err := host.Driver.GetState() 41 | if err != nil { 42 | if !strings.Contains(strings.ToLower(err.Error()), "not found") { 43 | return fmt.Errorf("error getting state for host %s: %s", host.Name, err) 44 | } 45 | 46 | currentState = state.NotFound 47 | err = notFoundError(fmt.Sprintf("%v not found", host.Name)) 48 | } 49 | 50 | log.Info(currentState) 51 | 52 | return err 53 | } 54 | -------------------------------------------------------------------------------- /commands/stop.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import "github.com/rancher/machine/libmachine" 4 | 5 | func cmdStop(c CommandLine, api libmachine.API) error { 6 | return runAction("stop", c, api) 7 | } 8 | -------------------------------------------------------------------------------- /commands/upgrade.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import "github.com/rancher/machine/libmachine" 4 | 5 | func cmdUpgrade(c CommandLine, api libmachine.API) error { 6 | return runAction("upgrade", c, api) 7 | } 8 | -------------------------------------------------------------------------------- /commands/url.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/rancher/machine/libmachine" 7 | ) 8 | 9 | func cmdURL(c CommandLine, api libmachine.API) error { 10 | if len(c.Args()) > 1 { 11 | return ErrExpectedOneMachine 12 | } 13 | 14 | target, err := targetHost(c, api) 15 | if err != nil { 16 | return err 17 | } 18 | 19 | host, err := api.Load(target) 20 | if err != nil { 21 | return err 22 | } 23 | 24 | url, err := host.URL() 25 | if err != nil { 26 | return err 27 | } 28 | 29 | fmt.Println(url) 30 | 31 | return nil 32 | } 33 | -------------------------------------------------------------------------------- /commands/url_test.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/rancher/machine/commands/commandstest" 7 | "github.com/rancher/machine/drivers/fakedriver" 8 | "github.com/rancher/machine/libmachine/host" 9 | "github.com/rancher/machine/libmachine/libmachinetest" 10 | "github.com/rancher/machine/libmachine/state" 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | func TestCmdURLMissingMachineName(t *testing.T) { 15 | commandLine := &commandstest.FakeCommandLine{} 16 | api := &libmachinetest.FakeAPI{} 17 | 18 | err := cmdURL(commandLine, api) 19 | 20 | assert.Equal(t, ErrNoDefault, err) 21 | } 22 | 23 | func TestCmdURLTooManyNames(t *testing.T) { 24 | commandLine := &commandstest.FakeCommandLine{ 25 | CliArgs: []string{"machineToRemove1", "machineToRemove2"}, 26 | } 27 | api := &libmachinetest.FakeAPI{} 28 | 29 | err := cmdURL(commandLine, api) 30 | 31 | assert.EqualError(t, err, "Error: Expected one machine name as an argument") 32 | } 33 | 34 | func TestCmdURL(t *testing.T) { 35 | commandLine := &commandstest.FakeCommandLine{ 36 | CliArgs: []string{"machine"}, 37 | } 38 | api := &libmachinetest.FakeAPI{ 39 | Hosts: []*host.Host{ 40 | { 41 | Name: "machine", 42 | Driver: &fakedriver.Driver{ 43 | MockState: state.Running, 44 | MockIP: "120.0.0.1", 45 | }, 46 | }, 47 | }, 48 | } 49 | 50 | stdoutGetter := commandstest.NewStdoutGetter() 51 | defer stdoutGetter.Stop() 52 | 53 | err := cmdURL(commandLine, api) 54 | 55 | assert.NoError(t, err) 56 | assert.Equal(t, "tcp://120.0.0.1:2376\n", stdoutGetter.Output()) 57 | } 58 | -------------------------------------------------------------------------------- /commands/version.go: -------------------------------------------------------------------------------- 1 | package commands 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | 8 | "github.com/rancher/machine/libmachine" 9 | "github.com/rancher/machine/libmachine/mcndockerclient" 10 | ) 11 | 12 | func cmdVersion(c CommandLine, api libmachine.API) error { 13 | return printVersion(c, api, os.Stdout) 14 | } 15 | 16 | func printVersion(c CommandLine, api libmachine.API, out io.Writer) error { 17 | if len(c.Args()) == 0 { 18 | c.ShowVersion() 19 | return nil 20 | } 21 | 22 | if len(c.Args()) != 1 { 23 | return ErrExpectedOneMachine 24 | } 25 | 26 | host, err := api.Load(c.Args().First()) 27 | if err != nil { 28 | return err 29 | } 30 | 31 | if host.HostOptions.AuthOptions != nil { 32 | version, err := mcndockerclient.DockerVersion(host) 33 | if err != nil { 34 | return err 35 | } 36 | 37 | fmt.Fprintln(out, version) 38 | } else { 39 | fmt.Fprintln(out, "Docker was not installed on machine") 40 | } 41 | 42 | return nil 43 | } 44 | -------------------------------------------------------------------------------- /contrib/completion/.gitignore: -------------------------------------------------------------------------------- 1 | !docker-machine* 2 | -------------------------------------------------------------------------------- /contrib/completion/bash/docker-machine-prompt.bash: -------------------------------------------------------------------------------- 1 | # 2 | # bash prompt support for docker-machine 3 | # 4 | # This script allows you to see the active machine in your bash prompt. 5 | # 6 | # To enable: 7 | # 1a. Copy this file somewhere and source it in your .bashrc 8 | # source /some/where/docker-machine-prompt.bash 9 | # 1b. Alternatively, just copy this file into into /etc/bash_completion.d 10 | # 2. Change your PS1 to call __docker-machine-ps1 as command-substitution 11 | # PS1='[\u@\h \W$(__docker_machine_ps1 " [%s]")]\$ ' 12 | # 13 | # Configuration: 14 | # 15 | # DOCKER_MACHINE_PS1_SHOWSTATUS 16 | # When set, the machine status is indicated in the prompt. This can be slow, 17 | # so use with care. 18 | # 19 | 20 | __docker_machine_ps1 () { 21 | local format=${1:- [%s]} 22 | if test ${DOCKER_MACHINE_NAME}; then 23 | local status 24 | if test ${DOCKER_MACHINE_PS1_SHOWSTATUS:-false} = true; then 25 | status=$(docker-machine status ${DOCKER_MACHINE_NAME}) 26 | case ${status} in 27 | Running) 28 | status=' R' 29 | ;; 30 | Stopping) 31 | status=' R->S' 32 | ;; 33 | Starting) 34 | status=' S->R' 35 | ;; 36 | Error|Timeout) 37 | status=' E' 38 | ;; 39 | *) 40 | # Just consider everything elase as 'stopped' 41 | status=' S' 42 | ;; 43 | esac 44 | fi 45 | printf -- "${format}" "${DOCKER_MACHINE_NAME}${status}" 46 | fi 47 | } 48 | -------------------------------------------------------------------------------- /contrib/completion/bash/docker-machine-wrapper.bash: -------------------------------------------------------------------------------- 1 | # 2 | # Function wrapper to docker-machine that adds a use subcommand. 3 | # 4 | # The use subcommand runs `eval "$(docker-machine env [args])"`, which is a lot 5 | # less typing. 6 | # 7 | # To enable: 8 | # 1a. Copy this file somewhere and source it in your .bashrc 9 | # source /some/where/docker-machine-wrapper.bash 10 | # 1b. Alternatively, just copy this file into into /etc/bash_completion.d 11 | # 12 | # Configuration: 13 | # 14 | # DOCKER_MACHINE_WRAPPED 15 | # When set to a value other than true, this will disable the alias wrapper 16 | # alias for docker-machine. This is useful if you don't want the wrapper, 17 | # but it is installed by default by your installation. 18 | # 19 | 20 | : ${DOCKER_MACHINE_WRAPPED:=true} 21 | 22 | __docker_machine_wrapper () { 23 | if [[ "$1" == use ]]; then 24 | # Special use wrapper 25 | shift 1 26 | case "$1" in 27 | -h|--help|"") 28 | cat < 2 | +++ 3 | draft=true 4 | title = "Docker Machine" 5 | description = "machine" 6 | keywords = ["machine, orchestration, install, installation, docker, documentation"] 7 | [menu.main] 8 | parent="mn_install" 9 | +++ 10 | 11 | 12 | # Boot2Docker Migration 13 | 14 | This document is a rough guide to what will need to be completed to support 15 | migrating from boot2docker-cli to Machine. It is not meant to be a user guide 16 | but more so an internal guide to what we will want to support. 17 | 18 | ## Existing Boot2Docker Instances 19 | 20 | We will need to import the disk to "migrate" the existing Docker data to the 21 | new Machine. This should not be too much work as instead of creating the 22 | virtual disk we will simply copy this one. From there, provisioning should 23 | happen as normal (cert regeneration, option configuration, etc). 24 | 25 | ## CLI 26 | 27 | Currently almost every b2d command has a comparable Machine command. I do not 28 | feel we need to have the exact same naming but we will want to create a 29 | migration user guide to inform the users of what is different. 30 | 31 | ## Boot2Docker Host Alias 32 | 33 | Boot2Docker also modifies the local system host file to create a `boot2docker` 34 | alias that can be used by the host system. We will need to decide if we want 35 | to support this and, if so, how to implement. Perhaps local aliases for each 36 | Machine name? 37 | 38 | ## Installer and Initial Setup 39 | 40 | There is a Boot2Docker installer that assists the users in getting started. 41 | It installs VirtualBox along with the b2d CLI. We will need something similar. 42 | This will probably be part of a larger installation project with the various 43 | Docker platform tools. 44 | 45 | ## Updates 46 | 47 | Machine already supports the `upgrade` command to update the Machine instances. 48 | I'm not sure if we want to add a mechanism to update the local Machine binary 49 | and/or the Docker CLI binary as well. We will need to discuss. 50 | -------------------------------------------------------------------------------- /its/cli/driver_help_test.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/rancher/machine/its" 7 | ) 8 | 9 | func TestDriverHelp(t *testing.T) { 10 | test := its.NewTest(t) 11 | defer test.TearDown() 12 | 13 | test.SkipDriver("ci-test") 14 | 15 | test.Run("no --help flag or command specified", func() { 16 | test.Machine("create -d $DRIVER").Should().Fail("Error: No machine name specified") 17 | }) 18 | 19 | test.Run("-h flag specified", func() { 20 | test.Machine("create -d $DRIVER -h").Should().Succeed(test.DriverName()) 21 | }) 22 | 23 | test.Run("--help flag specified", func() { 24 | test.Machine("create -d $DRIVER --help").Should().Succeed(test.DriverName()) 25 | }) 26 | } 27 | -------------------------------------------------------------------------------- /its/cli/inspect_test.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/rancher/machine/its" 7 | ) 8 | 9 | func TestInspect(t *testing.T) { 10 | test := its.NewTest(t) 11 | defer test.TearDown() 12 | 13 | test.Run("inspect: show error in case of no args", func() { 14 | test.Machine("inspect").Should().Fail(`Error: No machine name(s) specified and no "default" machine exists`) 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /its/cli/status_test.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/rancher/machine/its" 7 | ) 8 | 9 | func TestStatus(t *testing.T) { 10 | test := its.NewTest(t) 11 | defer test.TearDown() 12 | 13 | test.Run("status: show error in case of no args", func() { 14 | test.Machine("status").Should().Fail(`Error: No machine name(s) specified and no "default" machine exists`) 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /its/cli/url_test.go: -------------------------------------------------------------------------------- 1 | package cli 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/rancher/machine/its" 7 | ) 8 | 9 | func TestUrl(t *testing.T) { 10 | test := its.NewTest(t) 11 | defer test.TearDown() 12 | 13 | test.Run("url: show error in case of no args", func() { 14 | test.Machine("url").Should().Fail(`Error: No machine name(s) specified and no "default" machine exists`) 15 | }) 16 | } 17 | -------------------------------------------------------------------------------- /its/thirdparty/commands_test.go: -------------------------------------------------------------------------------- 1 | package thirdparty 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/rancher/machine/its" 7 | ) 8 | 9 | func TestThirdPartyCompatibility(t *testing.T) { 10 | test := its.NewTest(t) 11 | defer test.TearDown() 12 | 13 | test.RequireDriver("ci-test") 14 | 15 | test.Run("create", func() { 16 | test.Machine("create -d $DRIVER --url url default").Should().Succeed() 17 | }) 18 | 19 | test.Run("ls", func() { 20 | test.Machine("ls -q").Should().Succeed().ContainLines(1).EqualLine(0, "default") 21 | }) 22 | 23 | test.Run("url", func() { 24 | test.Machine("url default").Should().Succeed("url") 25 | }) 26 | 27 | test.Run("status", func() { 28 | test.Machine("status default").Should().Succeed("Running") 29 | }) 30 | 31 | test.Run("rm", func() { 32 | test.Machine("rm -y default").Should().Succeed() 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /libmachine/auth/auth.go: -------------------------------------------------------------------------------- 1 | package auth 2 | 3 | type Options struct { 4 | CertDir string 5 | CaCertPath string 6 | CaPrivateKeyPath string 7 | CaCertRemotePath string 8 | ServerCertPath string 9 | ServerKeyPath string 10 | ClientKeyPath string 11 | ServerCertRemotePath string 12 | ServerKeyRemotePath string 13 | ClientCertPath string 14 | ServerCertSANs []string 15 | // StorePath is left in for historical reasons, but not really meant to 16 | // be used directly. 17 | StorePath string 18 | } 19 | -------------------------------------------------------------------------------- /libmachine/cert/cert_test.go: -------------------------------------------------------------------------------- 1 | package cert 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "testing" 7 | ) 8 | 9 | func TestGenerateCACertificate(t *testing.T) { 10 | tmpDir, err := os.MkdirTemp("", "machine-test-") 11 | if err != nil { 12 | t.Fatal(err) 13 | } 14 | // cleanup 15 | defer os.RemoveAll(tmpDir) 16 | 17 | caCertPath := filepath.Join(tmpDir, "ca.pem") 18 | caKeyPath := filepath.Join(tmpDir, "key.pem") 19 | testOrg := "test-org" 20 | bits := 2048 21 | if err := GenerateCACertificate(caCertPath, caKeyPath, testOrg, bits); err != nil { 22 | t.Fatal(err) 23 | } 24 | 25 | if _, err := os.Stat(caCertPath); err != nil { 26 | t.Fatal(err) 27 | } 28 | if _, err := os.Stat(caKeyPath); err != nil { 29 | t.Fatal(err) 30 | } 31 | } 32 | 33 | func TestGenerateCert(t *testing.T) { 34 | tmpDir, err := os.MkdirTemp("", "machine-test-") 35 | if err != nil { 36 | t.Fatal(err) 37 | } 38 | // cleanup 39 | defer os.RemoveAll(tmpDir) 40 | 41 | caCertPath := filepath.Join(tmpDir, "ca.pem") 42 | caKeyPath := filepath.Join(tmpDir, "key.pem") 43 | certPath := filepath.Join(tmpDir, "cert.pem") 44 | keyPath := filepath.Join(tmpDir, "cert-key.pem") 45 | testOrg := "test-org" 46 | bits := 2048 47 | if err := GenerateCACertificate(caCertPath, caKeyPath, testOrg, bits); err != nil { 48 | t.Fatal(err) 49 | } 50 | 51 | if _, err := os.Stat(caCertPath); err != nil { 52 | t.Fatal(err) 53 | } 54 | if _, err := os.Stat(caKeyPath); err != nil { 55 | t.Fatal(err) 56 | } 57 | 58 | opts := &Options{ 59 | Hosts: []string{}, 60 | CertFile: certPath, 61 | CAKeyFile: caKeyPath, 62 | CAFile: caCertPath, 63 | KeyFile: keyPath, 64 | Org: testOrg, 65 | Bits: bits, 66 | SwarmMaster: false, 67 | } 68 | 69 | if err := GenerateCert(opts); err != nil { 70 | t.Fatal(err) 71 | } 72 | 73 | if _, err := os.Stat(certPath); err != nil { 74 | t.Fatalf("certificate not created at %s", certPath) 75 | } 76 | 77 | if _, err := os.Stat(keyPath); err != nil { 78 | t.Fatalf("key not created at %s", keyPath) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /libmachine/check/check_test.go: -------------------------------------------------------------------------------- 1 | package check 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "crypto/tls" 8 | 9 | "github.com/rancher/machine/libmachine/auth" 10 | "github.com/rancher/machine/libmachine/cert" 11 | "github.com/stretchr/testify/assert" 12 | ) 13 | 14 | type FakeValidateCertificate struct { 15 | IsValid bool 16 | Err error 17 | } 18 | 19 | type FakeCertGenerator struct { 20 | fakeValidateCertificate *FakeValidateCertificate 21 | } 22 | 23 | func (fcg FakeCertGenerator) GenerateCACertificate(certFile, keyFile, org string, bits int) error { 24 | return nil 25 | } 26 | 27 | func (fcg FakeCertGenerator) GenerateCert(opts *cert.Options) error { 28 | return nil 29 | } 30 | 31 | func (fcg FakeCertGenerator) ValidateCertificate(addr string, authOptions *auth.Options) (bool, error) { 32 | return fcg.fakeValidateCertificate.IsValid, fcg.fakeValidateCertificate.Err 33 | } 34 | 35 | func (fcg FakeCertGenerator) ReadTLSConfig(addr string, authOptions *auth.Options) (*tls.Config, error) { 36 | return nil, nil 37 | } 38 | 39 | func TestCheckCert(t *testing.T) { 40 | errCertsExpired := errors.New("Certs have expired") 41 | 42 | cases := []struct { 43 | hostURL string 44 | authOptions *auth.Options 45 | valid bool 46 | checkErr error 47 | expectedErr error 48 | }{ 49 | {"192.168.99.100:2376", &auth.Options{}, true, nil, nil}, 50 | {"192.168.99.100:2376", &auth.Options{}, false, nil, ErrCertInvalid{wrappedErr: nil, hostURL: "192.168.99.100:2376"}}, 51 | {"192.168.99.100:2376", &auth.Options{}, false, errCertsExpired, ErrCertInvalid{wrappedErr: errCertsExpired, hostURL: "192.168.99.100:2376"}}, 52 | } 53 | 54 | for _, c := range cases { 55 | fcg := FakeCertGenerator{fakeValidateCertificate: &FakeValidateCertificate{c.valid, c.checkErr}} 56 | cert.SetCertGenerator(fcg) 57 | err := checkCert(c.hostURL, c.authOptions) 58 | assert.Equal(t, c.expectedErr, err) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /libmachine/crashreport/crash_report_logger.go: -------------------------------------------------------------------------------- 1 | package crashreport 2 | 3 | import "github.com/rancher/machine/libmachine/log" 4 | 5 | type logger struct{} 6 | 7 | func (d *logger) Printf(fmtString string, args ...interface{}) { 8 | log.Debugf(fmtString, args) 9 | } 10 | -------------------------------------------------------------------------------- /libmachine/crashreport/crash_report_test.go: -------------------------------------------------------------------------------- 1 | package crashreport 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "testing" 7 | 8 | "github.com/bugsnag/bugsnag-go" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestFileIsNotReadWhenNotExisting(t *testing.T) { 13 | metaData := bugsnag.MetaData{} 14 | addFile("not existing", &metaData) 15 | assert.Empty(t, metaData) 16 | } 17 | 18 | func TestRead(t *testing.T) { 19 | metaData := bugsnag.MetaData{} 20 | content := "foo\nbar\nqix\n" 21 | fileName := createTempFile(t, content) 22 | defer os.Remove(fileName) 23 | addFile(fileName, &metaData) 24 | assert.Equal(t, "foo\nbar\nqix\n", metaData["logfile"][filepath.Base(fileName)]) 25 | } 26 | 27 | func createTempFile(t *testing.T, content string) string { 28 | file, err := os.CreateTemp("", "") 29 | if err != nil { 30 | t.Fatal(err) 31 | } 32 | if err := os.WriteFile(file.Name(), []byte(content), 0644); err != nil { 33 | t.Fatal(err) 34 | } 35 | return file.Name() 36 | } 37 | -------------------------------------------------------------------------------- /libmachine/crashreport/os_darwin.go: -------------------------------------------------------------------------------- 1 | package crashreport 2 | 3 | import "os/exec" 4 | 5 | func localOSVersion() string { 6 | command := exec.Command("bash", "-c", `sw_vers | grep ProductVersion | cut -d$'\t' -f2`) 7 | output, err := command.Output() 8 | if err != nil { 9 | return "" 10 | } 11 | return string(output) 12 | } 13 | -------------------------------------------------------------------------------- /libmachine/crashreport/os_freebsd.go: -------------------------------------------------------------------------------- 1 | package crashreport 2 | 3 | import "os/exec" 4 | 5 | func localOSVersion() string { 6 | command := exec.Command("uname", "-r") 7 | output, err := command.Output() 8 | if err != nil { 9 | return "" 10 | } 11 | return string(output) 12 | } 13 | -------------------------------------------------------------------------------- /libmachine/crashreport/os_linux.go: -------------------------------------------------------------------------------- 1 | package crashreport 2 | 3 | import "os/exec" 4 | 5 | func localOSVersion() string { 6 | command := exec.Command("bash", "-c", `cat /etc/os-release | grep 'VERSION=' | cut -d'=' -f2`) 7 | output, err := command.Output() 8 | if err != nil { 9 | return "" 10 | } 11 | return string(output) 12 | } 13 | -------------------------------------------------------------------------------- /libmachine/crashreport/os_openbsd.go: -------------------------------------------------------------------------------- 1 | package crashreport 2 | 3 | import "os/exec" 4 | 5 | func localOSVersion() string { 6 | command := exec.Command("uname", "-r") 7 | output, err := command.Output() 8 | if err != nil { 9 | return "" 10 | } 11 | return string(output) 12 | } 13 | -------------------------------------------------------------------------------- /libmachine/crashreport/os_windows.go: -------------------------------------------------------------------------------- 1 | package crashreport 2 | 3 | import ( 4 | "os/exec" 5 | "strings" 6 | ) 7 | 8 | func localOSVersion() string { 9 | command := exec.Command("ver") 10 | output, err := command.Output() 11 | if err == nil { 12 | return parseVerOutput(string(output)) 13 | } 14 | 15 | command = exec.Command("systeminfo") 16 | output, err = command.Output() 17 | if err == nil { 18 | return parseSystemInfoOutput(string(output)) 19 | } 20 | 21 | return "" 22 | } 23 | 24 | func parseSystemInfoOutput(output string) string { 25 | lines := strings.Split(string(output), "\n") 26 | for _, line := range lines { 27 | if strings.HasPrefix(line, "OS Version:") { 28 | return strings.TrimSpace(line[len("OS Version:"):]) 29 | } 30 | } 31 | 32 | // If we couldn't find the version, maybe the output is not in English 33 | // Let's parse the fourth line since it seems to be the one always used 34 | // for the version. 35 | if len(lines) >= 4 { 36 | parts := strings.Split(lines[3], ":") 37 | if len(parts) == 2 { 38 | return strings.TrimSpace(parts[1]) 39 | } 40 | } 41 | 42 | return "" 43 | } 44 | 45 | func parseVerOutput(output string) string { 46 | return strings.TrimSpace(output) 47 | } 48 | -------------------------------------------------------------------------------- /libmachine/crashreport/os_windows_test.go: -------------------------------------------------------------------------------- 1 | package crashreport 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestParseVerOutput(t *testing.T) { 10 | output := ` 11 | 12 | Microsoft Windows [version 6.3.9600] 13 | 14 | ` 15 | 16 | assert.Equal(t, "Microsoft Windows [version 6.3.9600]", parseVerOutput(output)) 17 | } 18 | 19 | func TestParseSystemInfoOutput(t *testing.T) { 20 | output := ` 21 | Host Name: DESKTOP-3A5PULA 22 | OS Name: Microsoft Windows 10 Enterprise 23 | OS Version: 10.0.10240 N/A Build 10240 24 | OS Manufacturer: Microsoft Corporation 25 | OS Configuration: Standalone Workstation 26 | OS Build Type: Multiprocessor Free 27 | Registered Owner: Windows User 28 | ` 29 | 30 | assert.Equal(t, "10.0.10240 N/A Build 10240", parseSystemInfoOutput(output)) 31 | } 32 | 33 | func TestParseNonEnglishSystemInfoOutput(t *testing.T) { 34 | output := ` 35 | Ignored: ... 36 | Ignored: ... 37 | Version du Système: 10.0.10350 38 | ` 39 | 40 | assert.Equal(t, "10.0.10350", parseSystemInfoOutput(output)) 41 | } 42 | 43 | func TestParseInvalidSystemInfoOutput(t *testing.T) { 44 | output := "Invalid" 45 | 46 | assert.Empty(t, parseSystemInfoOutput(output)) 47 | } 48 | -------------------------------------------------------------------------------- /libmachine/drivers/base_test.go: -------------------------------------------------------------------------------- 1 | package drivers 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/rancher/machine/libmachine/mcnflag" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | func TestIP(t *testing.T) { 12 | cases := []struct { 13 | baseDriver *BaseDriver 14 | expectedIP string 15 | expectedErr error 16 | }{ 17 | {&BaseDriver{}, "", errors.New("IP address is not set")}, 18 | {&BaseDriver{IPAddress: "2001:4860:0:2001::68"}, "2001:4860:0:2001::68", nil}, 19 | {&BaseDriver{IPAddress: "192.168.0.1"}, "192.168.0.1", nil}, 20 | {&BaseDriver{IPAddress: "::1"}, "::1", nil}, 21 | {&BaseDriver{IPAddress: "hostname"}, "hostname", nil}, 22 | } 23 | 24 | for _, c := range cases { 25 | ip, err := c.baseDriver.GetIP() 26 | assert.Equal(t, c.expectedIP, ip) 27 | assert.Equal(t, c.expectedErr, err) 28 | } 29 | } 30 | 31 | func TestEngineInstallUrlFlagEmpty(t *testing.T) { 32 | assert.False(t, EngineInstallURLFlagSet(&CheckDriverOptions{})) 33 | } 34 | 35 | func createDriverOptionWithEngineInstall(url string) *CheckDriverOptions { 36 | return &CheckDriverOptions{ 37 | FlagsValues: map[string]interface{}{"engine-install-url": url}, 38 | CreateFlags: []mcnflag.Flag{mcnflag.StringFlag{Name: "engine-install-url", Value: ""}}, 39 | } 40 | } 41 | 42 | func TestEngineInstallUrlFlagDefault(t *testing.T) { 43 | options := createDriverOptionWithEngineInstall(DefaultEngineInstallURL) 44 | assert.False(t, EngineInstallURLFlagSet(options)) 45 | } 46 | 47 | func TestEngineInstallUrlFlagSet(t *testing.T) { 48 | options := createDriverOptionWithEngineInstall("https://test.docker.com") 49 | assert.True(t, EngineInstallURLFlagSet(options)) 50 | } 51 | -------------------------------------------------------------------------------- /libmachine/drivers/check.go: -------------------------------------------------------------------------------- 1 | package drivers 2 | 3 | import "github.com/rancher/machine/libmachine/mcnflag" 4 | 5 | // CheckDriverOptions implements DriverOptions and is used to validate flag parsing 6 | type CheckDriverOptions struct { 7 | FlagsValues map[string]interface{} 8 | CreateFlags []mcnflag.Flag 9 | InvalidFlags []string 10 | } 11 | 12 | func (o *CheckDriverOptions) String(key string) string { 13 | for _, flag := range o.CreateFlags { 14 | if flag.String() == key { 15 | f, ok := flag.(mcnflag.StringFlag) 16 | if !ok { 17 | o.InvalidFlags = append(o.InvalidFlags, flag.String()) 18 | } 19 | 20 | value, present := o.FlagsValues[key].(string) 21 | if present { 22 | return value 23 | } 24 | return f.Value 25 | } 26 | } 27 | 28 | return "" 29 | } 30 | 31 | func (o *CheckDriverOptions) StringSlice(key string) []string { 32 | for _, flag := range o.CreateFlags { 33 | if flag.String() == key { 34 | f, ok := flag.(mcnflag.StringSliceFlag) 35 | if !ok { 36 | o.InvalidFlags = append(o.InvalidFlags, flag.String()) 37 | } 38 | 39 | value, present := o.FlagsValues[key].([]string) 40 | if present { 41 | return value 42 | } 43 | return f.Value 44 | } 45 | } 46 | 47 | return nil 48 | } 49 | 50 | func (o *CheckDriverOptions) Int(key string) int { 51 | for _, flag := range o.CreateFlags { 52 | if flag.String() == key { 53 | f, ok := flag.(mcnflag.IntFlag) 54 | if !ok { 55 | o.InvalidFlags = append(o.InvalidFlags, flag.String()) 56 | } 57 | 58 | value, present := o.FlagsValues[key].(int) 59 | if present { 60 | return value 61 | } 62 | return f.Value 63 | } 64 | } 65 | 66 | return 0 67 | } 68 | 69 | func (o *CheckDriverOptions) Bool(key string) bool { 70 | for _, flag := range o.CreateFlags { 71 | if flag.String() == key { 72 | _, ok := flag.(mcnflag.BoolFlag) 73 | if !ok { 74 | o.InvalidFlags = append(o.InvalidFlags, flag.String()) 75 | } 76 | } 77 | } 78 | 79 | value, present := o.FlagsValues[key].(bool) 80 | if present { 81 | return value 82 | } 83 | return false 84 | } 85 | -------------------------------------------------------------------------------- /libmachine/drivers/notsupported.go: -------------------------------------------------------------------------------- 1 | package drivers 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/rancher/machine/libmachine/mcnflag" 7 | "github.com/rancher/machine/libmachine/state" 8 | ) 9 | 10 | type DriverNotSupported struct { 11 | *BaseDriver 12 | Name string 13 | } 14 | 15 | type NotSupported struct { 16 | DriverName string 17 | } 18 | 19 | func (e NotSupported) Error() string { 20 | return fmt.Sprintf("Driver %q not supported on this platform.", e.DriverName) 21 | } 22 | 23 | // NewDriverNotSupported creates a placeholder Driver that replaces 24 | // a driver that is not supported on a given platform. eg fusion on linux. 25 | func NewDriverNotSupported(driverName, hostName, storePath string) Driver { 26 | return &DriverNotSupported{ 27 | BaseDriver: &BaseDriver{ 28 | MachineName: hostName, 29 | StorePath: storePath, 30 | }, 31 | Name: driverName, 32 | } 33 | } 34 | 35 | func (d *DriverNotSupported) DriverName() string { 36 | return d.Name 37 | } 38 | 39 | func (d *DriverNotSupported) PreCreateCheck() error { 40 | return NotSupported{d.DriverName()} 41 | } 42 | 43 | func (d *DriverNotSupported) GetCreateFlags() []mcnflag.Flag { 44 | return nil 45 | } 46 | 47 | func (d *DriverNotSupported) SetConfigFromFlags(flags DriverOptions) error { 48 | return NotSupported{d.DriverName()} 49 | } 50 | 51 | func (d *DriverNotSupported) GetURL() (string, error) { 52 | return "", NotSupported{d.DriverName()} 53 | } 54 | 55 | func (d *DriverNotSupported) GetSSHHostname() (string, error) { 56 | return "", NotSupported{d.DriverName()} 57 | } 58 | 59 | func (d *DriverNotSupported) GetState() (state.State, error) { 60 | return state.Error, NotSupported{d.DriverName()} 61 | } 62 | 63 | func (d *DriverNotSupported) Create() error { 64 | return NotSupported{d.DriverName()} 65 | } 66 | 67 | func (d *DriverNotSupported) Remove() error { 68 | return NotSupported{d.DriverName()} 69 | } 70 | 71 | func (d *DriverNotSupported) Start() error { 72 | return NotSupported{d.DriverName()} 73 | } 74 | 75 | func (d *DriverNotSupported) Stop() error { 76 | return NotSupported{d.DriverName()} 77 | } 78 | 79 | func (d *DriverNotSupported) Restart() error { 80 | return NotSupported{d.DriverName()} 81 | } 82 | 83 | func (d *DriverNotSupported) Kill() error { 84 | return NotSupported{d.DriverName()} 85 | } 86 | 87 | func (d *DriverNotSupported) Upgrade() error { 88 | return NotSupported{d.DriverName()} 89 | } 90 | -------------------------------------------------------------------------------- /libmachine/drivers/plugin/localbinary/utils.go: -------------------------------------------------------------------------------- 1 | //go:build !windows 2 | 3 | package localbinary 4 | 5 | import ( 6 | "fmt" 7 | "os" 8 | "os/exec" 9 | "strconv" 10 | "syscall" 11 | ) 12 | 13 | func getCommand(name string, args ...string) (*exec.Cmd, error) { 14 | cmd := exec.Command(name, args...) 15 | gid := os.Getenv(PluginGID) 16 | uid := os.Getenv(PluginUID) 17 | if uid != "" && gid != "" { 18 | uid, err := strconv.Atoi(uid) 19 | if err != nil { 20 | return nil, fmt.Errorf("error parsing user ID: %w", err) 21 | } 22 | gid, err := strconv.Atoi(gid) 23 | if err != nil { 24 | return nil, fmt.Errorf("error parsing group ID: %w", err) 25 | } 26 | cmd.SysProcAttr = &syscall.SysProcAttr{ 27 | Credential: &syscall.Credential{ 28 | Uid: uint32(uid), 29 | Gid: uint32(gid), 30 | }, 31 | } 32 | } 33 | return cmd, nil 34 | } 35 | -------------------------------------------------------------------------------- /libmachine/drivers/plugin/localbinary/utils_windows.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | 3 | package localbinary 4 | 5 | import "os/exec" 6 | 7 | func getCommand(name string, args ...string) (*exec.Cmd, error) { 8 | cmd := exec.Command(name, args...) 9 | return cmd, nil 10 | } 11 | -------------------------------------------------------------------------------- /libmachine/drivers/plugin/register_driver.go: -------------------------------------------------------------------------------- 1 | package plugin 2 | 3 | import ( 4 | "fmt" 5 | "net" 6 | "net/http" 7 | "net/rpc" 8 | "os" 9 | "time" 10 | 11 | "github.com/rancher/machine/libmachine/drivers" 12 | "github.com/rancher/machine/libmachine/drivers/plugin/localbinary" 13 | "github.com/rancher/machine/libmachine/drivers/rpc" 14 | "github.com/rancher/machine/libmachine/log" 15 | "github.com/rancher/machine/libmachine/version" 16 | ) 17 | 18 | var ( 19 | heartbeatTimeout = 10 * time.Second 20 | ) 21 | 22 | func RegisterDriver(d drivers.Driver) { 23 | if os.Getenv(localbinary.PluginEnvKey) != localbinary.PluginEnvVal { 24 | fmt.Fprintf(os.Stderr, `This is a Docker Machine plugin binary. 25 | Plugin binaries are not intended to be invoked directly. 26 | Please use this plugin through the main 'docker-machine' binary. 27 | (API version: %d) 28 | `, version.APIVersion) 29 | os.Exit(1) 30 | } 31 | 32 | log.SetDebug(true) 33 | os.Setenv("MACHINE_DEBUG", "1") 34 | 35 | rpcd := rpcdriver.NewRPCServerDriver(d) 36 | rpc.RegisterName(rpcdriver.RPCServiceNameV0, rpcd) 37 | rpc.RegisterName(rpcdriver.RPCServiceNameV1, rpcd) 38 | rpc.HandleHTTP() 39 | 40 | listener, err := net.Listen("tcp", "127.0.0.1:0") 41 | if err != nil { 42 | fmt.Fprintf(os.Stderr, "Error loading RPC server: %s\n", err) 43 | os.Exit(1) 44 | } 45 | defer listener.Close() 46 | 47 | fmt.Println(listener.Addr()) 48 | 49 | go http.Serve(listener, nil) 50 | 51 | for { 52 | select { 53 | case <-rpcd.CloseCh: 54 | log.Debug("Closing plugin on server side") 55 | os.Exit(0) 56 | case <-rpcd.HeartbeatCh: 57 | continue 58 | case <-time.After(heartbeatTimeout): 59 | // TODO: Add heartbeat retry logic 60 | os.Exit(1) 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /libmachine/drivers/rpc/server_driver_test.go: -------------------------------------------------------------------------------- 1 | package rpcdriver 2 | 3 | import ( 4 | "errors" 5 | "testing" 6 | 7 | "github.com/rancher/machine/drivers/fakedriver" 8 | "github.com/stretchr/testify/assert" 9 | ) 10 | 11 | type panicDriver struct { 12 | *fakedriver.Driver 13 | panicErr error 14 | returnErr error 15 | } 16 | 17 | type FakeStacker struct { 18 | trace []byte 19 | } 20 | 21 | func (fs *FakeStacker) Stack() []byte { 22 | return fs.trace 23 | } 24 | 25 | func (p *panicDriver) Create() error { 26 | if p.panicErr != nil { 27 | panic(p.panicErr) 28 | } 29 | return p.returnErr 30 | } 31 | 32 | func TestRPCServerDriverCreate(t *testing.T) { 33 | testCases := []struct { 34 | description string 35 | expectedErr error 36 | serverDriver *RPCServerDriver 37 | stacker Stacker 38 | }{ 39 | { 40 | description: "Happy path", 41 | expectedErr: nil, 42 | serverDriver: &RPCServerDriver{ 43 | ActualDriver: &panicDriver{ 44 | returnErr: nil, 45 | }, 46 | }, 47 | }, 48 | { 49 | description: "Normal error, no panic", 50 | expectedErr: errors.New("API not available"), 51 | serverDriver: &RPCServerDriver{ 52 | ActualDriver: &panicDriver{ 53 | returnErr: errors.New("API not available"), 54 | }, 55 | }, 56 | }, 57 | { 58 | description: "Panic happened during create", 59 | expectedErr: errors.New("Panic in the driver: index out of range\nSTACK TRACE"), 60 | serverDriver: &RPCServerDriver{ 61 | ActualDriver: &panicDriver{ 62 | panicErr: errors.New("index out of range"), 63 | }, 64 | }, 65 | stacker: &FakeStacker{ 66 | trace: []byte("STACK TRACE"), 67 | }, 68 | }, 69 | } 70 | 71 | for _, tc := range testCases { 72 | stdStacker = tc.stacker 73 | assert.Equal(t, tc.expectedErr, tc.serverDriver.Create(nil, nil)) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /libmachine/drivers/rpc/util_test.go: -------------------------------------------------------------------------------- 1 | package rpcdriver 2 | 3 | import ( 4 | "reflect" 5 | "strings" 6 | "testing" 7 | 8 | "github.com/rancher/machine/libmachine/mcnflag" 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestGetDriverOpts(t *testing.T) { 13 | flags := []mcnflag.Flag{ 14 | mcnflag.StringFlag{ 15 | Name: "string-value", 16 | }, 17 | mcnflag.StringFlag{ 18 | Name: "default-string-value", 19 | Value: "default", 20 | }, 21 | mcnflag.IntFlag{ 22 | Name: "int-value", 23 | }, 24 | mcnflag.IntFlag{ 25 | Name: "default-int-value", 26 | Value: 42, 27 | }, 28 | mcnflag.StringSliceFlag{ 29 | Name: "string-slice-value", 30 | }, 31 | mcnflag.StringSliceFlag{ 32 | Name: "default-string-slice-value", 33 | Value: []string{"test", "string"}, 34 | }, 35 | mcnflag.BoolFlag{ 36 | Name: "bool-value", 37 | }, 38 | } 39 | args := strings.Split("some random args --string-value value --int-value=2 --string-slice-value one,two --bool-value", " ") 40 | expected := map[string]any{ 41 | "string-value": "value", 42 | "default-string-value": "default", 43 | "int-value": 2, 44 | "default-int-value": 42, 45 | "string-slice-value": []string{"one", "two"}, 46 | "default-string-slice-value": []string{"test", "string"}, 47 | "bool-value": true, 48 | } 49 | 50 | result := GetDriverOpts(flags, args) 51 | assert.True(t, reflect.DeepEqual(expected, result.Values)) 52 | } 53 | -------------------------------------------------------------------------------- /libmachine/drivers/utils.go: -------------------------------------------------------------------------------- 1 | package drivers 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "github.com/rancher/machine/libmachine/log" 8 | "github.com/rancher/machine/libmachine/ssh" 9 | ) 10 | 11 | func GetSSHClientFromDriver(d Driver) (ssh.Client, error) { 12 | address, err := d.GetSSHHostname() 13 | if err != nil { 14 | return nil, err 15 | } 16 | 17 | port, err := d.GetSSHPort() 18 | if err != nil { 19 | return nil, err 20 | } 21 | 22 | var auth *ssh.Auth 23 | if d.GetSSHKeyPath() == "" { 24 | auth = &ssh.Auth{} 25 | } else { 26 | auth = &ssh.Auth{ 27 | Keys: []string{d.GetSSHKeyPath()}, 28 | } 29 | } 30 | 31 | client, err := ssh.NewClient(d.GetSSHUsername(), address, port, auth) 32 | return client, err 33 | 34 | } 35 | 36 | func RunSSHCommandFromDriver(d Driver, command string) (string, error) { 37 | client, err := GetSSHClientFromDriver(d) 38 | if err != nil { 39 | return "", err 40 | } 41 | 42 | log.Debugf("About to run SSH command:\n%s", command) 43 | 44 | output, err := client.Output(command) 45 | log.Debugf("SSH cmd err, output: %v: %s", err, output) 46 | if err != nil { 47 | return "", fmt.Errorf(`ssh command error: command: %s err: %v output: %s`, command, err, output) 48 | } 49 | 50 | return output, nil 51 | } 52 | 53 | // WaitForSSH tries to run `exit 0` on the host machine using the driver. It will retry up to 54 | // 60 times with 3 seconds in between each attempt. If the command still errors after the final 55 | // attempt, the error will be returned. 56 | func WaitForSSH(d Driver) error { 57 | var lastErr error 58 | for i := 0; i < 60; i++ { 59 | log.Debug("Getting to WaitForSSH function...") 60 | if _, lastErr = RunSSHCommandFromDriver(d, "exit 0"); lastErr == nil { 61 | return nil 62 | } 63 | 64 | log.Debugf("Error getting SSH command 'exit 0' : %s", lastErr) 65 | time.Sleep(3 * time.Second) 66 | } 67 | 68 | return fmt.Errorf("Too many retries waiting for SSH to be available. Last error: %w", lastErr) 69 | } 70 | -------------------------------------------------------------------------------- /libmachine/engine/engine.go: -------------------------------------------------------------------------------- 1 | package engine 2 | 3 | const ( 4 | DefaultPort = 2376 5 | ) 6 | 7 | type Options struct { 8 | ArbitraryFlags []string 9 | DNS []string `json:"Dns"` 10 | GraphDir string 11 | Env []string 12 | Ipv6 bool 13 | InsecureRegistry []string 14 | Labels []string 15 | LogLevel string 16 | StorageDriver string 17 | SelinuxEnabled bool 18 | TLSVerify bool `json:"TlsVerify"` 19 | RegistryMirror []string 20 | InstallURL string 21 | } 22 | -------------------------------------------------------------------------------- /libmachine/host/host_test.go: -------------------------------------------------------------------------------- 1 | package host 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/rancher/machine/drivers/fakedriver" 7 | _ "github.com/rancher/machine/drivers/none" 8 | "github.com/rancher/machine/libmachine/provision" 9 | "github.com/rancher/machine/libmachine/state" 10 | ) 11 | 12 | func TestValidateHostnameValid(t *testing.T) { 13 | hosts := []string{ 14 | "zomg", 15 | "test-ing", 16 | "some.h0st", 17 | } 18 | 19 | for _, v := range hosts { 20 | isValid := ValidateHostName(v) 21 | if !isValid { 22 | t.Fatalf("Thought a valid hostname was invalid: %s", v) 23 | } 24 | } 25 | } 26 | 27 | func TestValidateHostnameInvalid(t *testing.T) { 28 | hosts := []string{ 29 | "zom_g", 30 | "test$ing", 31 | "some😄host", 32 | } 33 | 34 | for _, v := range hosts { 35 | isValid := ValidateHostName(v) 36 | if isValid { 37 | t.Fatalf("Thought an invalid hostname was valid: %s", v) 38 | } 39 | } 40 | } 41 | 42 | func TestStart(t *testing.T) { 43 | defer provision.SetDetector(&provision.StandardDetector{}) 44 | provision.SetDetector(&provision.FakeDetector{ 45 | Provisioner: provision.NewNetstatProvisioner(), 46 | }) 47 | 48 | host := &Host{ 49 | Driver: &fakedriver.Driver{ 50 | MockState: state.Stopped, 51 | }, 52 | } 53 | 54 | if err := host.Start(); err != nil { 55 | t.Fatalf("Expected no error but got one: %s", err) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /libmachine/host/host_v0.go: -------------------------------------------------------------------------------- 1 | package host 2 | 3 | import "github.com/rancher/machine/libmachine/drivers" 4 | 5 | type V0 struct { 6 | Name string `json:"-"` 7 | Driver drivers.Driver 8 | DriverName string 9 | ConfigVersion int 10 | HostOptions *Options 11 | 12 | StorePath string 13 | CaCertPath string 14 | PrivateKeyPath string 15 | ServerCertPath string 16 | ServerKeyPath string 17 | ClientCertPath string 18 | SwarmHost string 19 | SwarmMaster bool 20 | SwarmDiscovery string 21 | ClientKeyPath string 22 | } 23 | 24 | type MetadataV0 struct { 25 | HostOptions Options 26 | DriverName string 27 | 28 | ConfigVersion int 29 | StorePath string 30 | CaCertPath string 31 | PrivateKeyPath string 32 | ServerCertPath string 33 | ServerKeyPath string 34 | ClientCertPath string 35 | } 36 | -------------------------------------------------------------------------------- /libmachine/host/host_v2.go: -------------------------------------------------------------------------------- 1 | package host 2 | 3 | import "github.com/rancher/machine/libmachine/drivers" 4 | 5 | type V2 struct { 6 | ConfigVersion int 7 | Driver drivers.Driver 8 | DriverName string 9 | HostOptions *Options 10 | Name string 11 | } 12 | -------------------------------------------------------------------------------- /libmachine/host/migrate_v0_v3_test.go: -------------------------------------------------------------------------------- 1 | package host 2 | 3 | import "testing" 4 | 5 | var ( 6 | v0conf = []byte(`{"DriverName":"virtualbox","Driver":{"IPAddress":"192.168.99.100","SSHUser":"docker","SSHPort":53507,"MachineName":"dev","CaCertPath":"/Users/ljrittle/.docker/machine/certs/ca.pem","PrivateKeyPath":"/Users/ljrittle/.docker/machine/certs/ca-key.pem","SwarmMaster":false,"SwarmHost":"tcp://0.0.0.0:3376","SwarmDiscovery":"","CPU":-1,"Memory":1024,"DiskSize":20000,"Boot2DockerURL":"","Boot2DockerImportVM":"","HostOnlyCIDR":""},"StorePath":"/Users/ljrittle/.docker/machine/machines/dev","HostOptions":{"Driver":"","Memory":0,"Disk":0,"EngineOptions":{"ArbitraryFlags":null,"Dns":null,"GraphDir":"","Ipv6":false,"InsecureRegistry":null,"Labels":null,"LogLevel":"","StorageDriver":"","SelinuxEnabled":false,"TlsCaCert":"","TlsCert":"","TlsKey":"","TlsVerify":false,"RegistryMirror":null,"InstallURL":""},"SwarmOptions":{"IsSwarm":false,"Address":"","Discovery":"","Master":false,"Host":"tcp://0.0.0.0:3376","Image":"","Strategy":"","Heartbeat":0,"Overcommit":0,"TlsCaCert":"","TlsCert":"","TlsKey":"","TlsVerify":false,"ArbitraryFlags":null},"AuthOptions":{"StorePath":"/Users/ljrittle/.docker/machine/machines/dev","CaCertPath":"/Users/ljrittle/.docker/machine/certs/ca.pem","CaCertRemotePath":"","ServerCertPath":"/Users/ljrittle/.docker/machine/certs/server.pem","ServerKeyPath":"/Users/ljrittle/.docker/machine/certs/server-key.pem","ClientKeyPath":"/Users/ljrittle/.docker/machine/certs/key.pem","ServerCertRemotePath":"","ServerKeyRemotePath":"","PrivateKeyPath":"/Users/ljrittle/.docker/machine/certs/ca-key.pem","ClientCertPath":"/Users/ljrittle/.docker/machine/certs/cert.pem"}}}`) 7 | ) 8 | 9 | func TestMigrateHostV0ToHostV3(t *testing.T) { 10 | h := &Host{} 11 | migratedHost, migrationPerformed, err := MigrateHost(h, v0conf) 12 | if err != nil { 13 | t.Fatalf("Error attempting to migrate host: %s", err) 14 | } 15 | 16 | if !migrationPerformed { 17 | t.Fatal("Expected a migration to be reported as performed but it was not") 18 | } 19 | 20 | if migratedHost.DriverName != "virtualbox" { 21 | t.Fatalf("Expected %q, got %q for the driver name", "virtualbox", migratedHost.DriverName) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /libmachine/host/migrate_v2_v3.go: -------------------------------------------------------------------------------- 1 | package host 2 | 3 | import ( 4 | "bytes" 5 | "encoding/json" 6 | 7 | "github.com/rancher/machine/libmachine/log" 8 | ) 9 | 10 | type RawHost struct { 11 | Driver *json.RawMessage 12 | } 13 | 14 | func MigrateHostV2ToHostV3(hostV2 *V2, data []byte, storePath string) *Host { 15 | // Migrate to include RawDriver so that driver plugin will work 16 | // smoothly. 17 | rawHost := &RawHost{} 18 | if err := json.Unmarshal(data, &rawHost); err != nil { 19 | log.Warnf("Could not unmarshal raw host for RawDriver information: %s", err) 20 | } 21 | 22 | m := make(map[string]interface{}) 23 | 24 | // Must migrate to include store path in driver since it was not 25 | // previously stored in drivers directly 26 | d := json.NewDecoder(bytes.NewReader(*rawHost.Driver)) 27 | d.UseNumber() 28 | if err := d.Decode(&m); err != nil { 29 | log.Warnf("Could not unmarshal raw host into map[string]interface{}: %s", err) 30 | } 31 | 32 | m["StorePath"] = storePath 33 | 34 | // Now back to []byte 35 | rawDriver, err := json.Marshal(m) 36 | if err != nil { 37 | log.Warnf("Could not re-marshal raw driver: %s", err) 38 | } 39 | 40 | h := &Host{ 41 | ConfigVersion: 2, 42 | DriverName: hostV2.DriverName, 43 | Name: hostV2.Name, 44 | HostOptions: hostV2.HostOptions, 45 | RawDriver: rawDriver, 46 | } 47 | 48 | return h 49 | } 50 | -------------------------------------------------------------------------------- /libmachine/hosttest/default_test_host.go: -------------------------------------------------------------------------------- 1 | package hosttest 2 | 3 | import ( 4 | "github.com/rancher/machine/drivers/none" 5 | "github.com/rancher/machine/libmachine/auth" 6 | "github.com/rancher/machine/libmachine/engine" 7 | "github.com/rancher/machine/libmachine/host" 8 | "github.com/rancher/machine/libmachine/swarm" 9 | "github.com/rancher/machine/libmachine/version" 10 | ) 11 | 12 | const ( 13 | DefaultHostName = "test-host" 14 | HostTestCaCert = "test-cert" 15 | HostTestPrivateKey = "test-key" 16 | ) 17 | 18 | type DriverOptionsMock struct { 19 | Data map[string]interface{} 20 | } 21 | 22 | func (d DriverOptionsMock) String(key string) string { 23 | return d.Data[key].(string) 24 | } 25 | 26 | func (d DriverOptionsMock) StringSlice(key string) []string { 27 | return d.Data[key].([]string) 28 | } 29 | 30 | func (d DriverOptionsMock) Int(key string) int { 31 | return d.Data[key].(int) 32 | } 33 | 34 | func (d DriverOptionsMock) Bool(key string) bool { 35 | return d.Data[key].(bool) 36 | } 37 | 38 | func GetTestDriverFlags() *DriverOptionsMock { 39 | flags := &DriverOptionsMock{ 40 | Data: map[string]interface{}{ 41 | "name": DefaultHostName, 42 | "url": "unix:///var/run/docker.sock", 43 | "swarm": false, 44 | "swarm-host": "", 45 | "swarm-master": false, 46 | "swarm-discovery": "", 47 | }, 48 | } 49 | return flags 50 | } 51 | 52 | func GetDefaultTestHost() (*host.Host, error) { 53 | hostOptions := &host.Options{ 54 | EngineOptions: &engine.Options{}, 55 | SwarmOptions: &swarm.Options{}, 56 | AuthOptions: &auth.Options{ 57 | CaCertPath: HostTestCaCert, 58 | CaPrivateKeyPath: HostTestPrivateKey, 59 | }, 60 | } 61 | 62 | driver := none.NewDriver(DefaultHostName, "/tmp/artifacts") 63 | 64 | host := &host.Host{ 65 | ConfigVersion: version.ConfigVersion, 66 | Name: DefaultHostName, 67 | Driver: driver, 68 | DriverName: "none", 69 | HostOptions: hostOptions, 70 | } 71 | 72 | flags := GetTestDriverFlags() 73 | if err := host.Driver.SetConfigFromFlags(flags); err != nil { 74 | return nil, err 75 | } 76 | 77 | return host, nil 78 | } 79 | -------------------------------------------------------------------------------- /libmachine/libmachinetest/fake_api.go: -------------------------------------------------------------------------------- 1 | package libmachinetest 2 | 3 | import ( 4 | "github.com/rancher/machine/libmachine" 5 | "github.com/rancher/machine/libmachine/drivers" 6 | "github.com/rancher/machine/libmachine/host" 7 | "github.com/rancher/machine/libmachine/mcnerror" 8 | "github.com/rancher/machine/libmachine/state" 9 | ) 10 | 11 | type FakeAPI struct { 12 | Hosts []*host.Host 13 | } 14 | 15 | func (api *FakeAPI) NewPluginDriver(string, []byte) (drivers.Driver, error) { 16 | return nil, nil 17 | } 18 | 19 | func (api *FakeAPI) Close() error { 20 | return nil 21 | } 22 | 23 | func (api *FakeAPI) NewHost(driverName string, rawDriver []byte) (*host.Host, error) { 24 | return nil, nil 25 | } 26 | 27 | func (api *FakeAPI) Create(h *host.Host) error { 28 | return nil 29 | } 30 | 31 | func (api *FakeAPI) Exists(name string) (bool, error) { 32 | for _, host := range api.Hosts { 33 | if name == host.Name { 34 | return true, nil 35 | } 36 | } 37 | 38 | return false, nil 39 | } 40 | 41 | func (api *FakeAPI) List() ([]string, error) { 42 | return []string{}, nil 43 | } 44 | 45 | func (api *FakeAPI) Load(name string) (*host.Host, error) { 46 | for _, host := range api.Hosts { 47 | if name == host.Name { 48 | return host, nil 49 | } 50 | } 51 | 52 | return nil, mcnerror.ErrHostDoesNotExist{ 53 | Name: name, 54 | } 55 | } 56 | 57 | func (api *FakeAPI) Remove(name string) error { 58 | newHosts := []*host.Host{} 59 | 60 | for _, host := range api.Hosts { 61 | if name != host.Name { 62 | newHosts = append(newHosts, host) 63 | } 64 | } 65 | 66 | api.Hosts = newHosts 67 | 68 | return nil 69 | } 70 | 71 | func (api *FakeAPI) Save(host *host.Host) error { 72 | return nil 73 | } 74 | 75 | func (api FakeAPI) GetMachinesDir() string { 76 | return "" 77 | } 78 | 79 | func State(api libmachine.API, name string) state.State { 80 | host, _ := api.Load(name) 81 | machineState, _ := host.Driver.GetState() 82 | return machineState 83 | } 84 | 85 | func Exists(api libmachine.API, name string) bool { 86 | exists, _ := api.Exists(name) 87 | return exists 88 | } 89 | -------------------------------------------------------------------------------- /libmachine/log/fmt_machine_logger.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | ) 8 | 9 | type FmtMachineLogger struct { 10 | outWriter io.Writer 11 | errWriter io.Writer 12 | debug bool 13 | history *HistoryRecorder 14 | } 15 | 16 | // NewFmtMachineLogger creates a MachineLogger implementation used by the drivers 17 | func NewFmtMachineLogger() MachineLogger { 18 | return &FmtMachineLogger{ 19 | outWriter: os.Stdout, 20 | errWriter: os.Stderr, 21 | debug: false, 22 | history: NewHistoryRecorder(), 23 | } 24 | } 25 | 26 | func (ml *FmtMachineLogger) SetDebug(debug bool) { 27 | ml.debug = debug 28 | } 29 | 30 | func (ml *FmtMachineLogger) SetOutWriter(out io.Writer) { 31 | ml.outWriter = out 32 | } 33 | 34 | func (ml *FmtMachineLogger) SetErrWriter(err io.Writer) { 35 | ml.errWriter = err 36 | } 37 | 38 | func (ml *FmtMachineLogger) Debug(args ...interface{}) { 39 | ml.history.Record(args...) 40 | if ml.debug { 41 | fmt.Fprintln(ml.outWriter, args...) 42 | } 43 | } 44 | 45 | func (ml *FmtMachineLogger) Debugf(fmtString string, args ...interface{}) { 46 | ml.history.Recordf(fmtString, args...) 47 | if ml.debug { 48 | fmt.Fprintf(ml.outWriter, fmtString+"\n", args...) 49 | } 50 | } 51 | 52 | func (ml *FmtMachineLogger) Error(args ...interface{}) { 53 | ml.history.Record(args...) 54 | fmt.Fprintln(ml.errWriter, args...) 55 | } 56 | 57 | func (ml *FmtMachineLogger) Errorf(fmtString string, args ...interface{}) { 58 | ml.history.Recordf(fmtString, args...) 59 | fmt.Fprintf(ml.errWriter, fmtString+"\n", args...) 60 | } 61 | 62 | func (ml *FmtMachineLogger) Info(args ...interface{}) { 63 | ml.history.Record(args...) 64 | fmt.Fprintln(ml.outWriter, args...) 65 | } 66 | 67 | func (ml *FmtMachineLogger) Infof(fmtString string, args ...interface{}) { 68 | ml.history.Recordf(fmtString, args...) 69 | fmt.Fprintf(ml.outWriter, fmtString+"\n", args...) 70 | } 71 | 72 | func (ml *FmtMachineLogger) Warn(args ...interface{}) { 73 | ml.history.Record(args...) 74 | fmt.Fprintln(ml.outWriter, args...) 75 | } 76 | 77 | func (ml *FmtMachineLogger) Warnf(fmtString string, args ...interface{}) { 78 | ml.history.Recordf(fmtString, args...) 79 | fmt.Fprintf(ml.outWriter, fmtString+"\n", args...) 80 | } 81 | 82 | func (ml *FmtMachineLogger) History() []string { 83 | return ml.history.records 84 | } 85 | -------------------------------------------------------------------------------- /libmachine/log/history_recorder.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "fmt" 5 | "sync" 6 | ) 7 | 8 | type HistoryRecorder struct { 9 | lock *sync.Mutex 10 | records []string 11 | } 12 | 13 | func NewHistoryRecorder() *HistoryRecorder { 14 | return &HistoryRecorder{ 15 | lock: &sync.Mutex{}, 16 | records: []string{}, 17 | } 18 | } 19 | 20 | func (ml *HistoryRecorder) History() []string { 21 | return ml.records 22 | } 23 | 24 | func (ml *HistoryRecorder) Record(args ...interface{}) { 25 | ml.lock.Lock() 26 | defer ml.lock.Unlock() 27 | ml.records = append(ml.records, fmt.Sprint(args...)) 28 | } 29 | 30 | func (ml *HistoryRecorder) Recordf(fmtString string, args ...interface{}) { 31 | ml.lock.Lock() 32 | defer ml.lock.Unlock() 33 | ml.records = append(ml.records, fmt.Sprintf(fmtString, args...)) 34 | } 35 | -------------------------------------------------------------------------------- /libmachine/log/history_recorder_test.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestRecording(t *testing.T) { 10 | recorder := NewHistoryRecorder() 11 | recorder.Record("foo") 12 | recorder.Record("bar") 13 | recorder.Record("qix") 14 | assert.Equal(t, recorder.History(), []string{"foo", "bar", "qix"}) 15 | } 16 | 17 | func TestFormattedRecording(t *testing.T) { 18 | recorder := NewHistoryRecorder() 19 | recorder.Recordf("%s, %s and %s", "foo", "bar", "qix") 20 | assert.Equal(t, recorder.History()[0], "foo, bar and qix") 21 | } 22 | -------------------------------------------------------------------------------- /libmachine/log/log.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import ( 4 | "io" 5 | "regexp" 6 | ) 7 | 8 | const redactedText = "" 9 | 10 | var ( 11 | logger = NewFmtMachineLogger() 12 | 13 | // (?s) enables '.' to match '\n' -- see https://golang.org/pkg/regexp/syntax/ 14 | certRegex = regexp.MustCompile("(?s)-----BEGIN CERTIFICATE-----.*-----END CERTIFICATE-----") 15 | keyRegex = regexp.MustCompile("(?s)-----BEGIN RSA PRIVATE KEY-----.*-----END RSA PRIVATE KEY-----") 16 | ) 17 | 18 | func stripSecrets(original []string) []string { 19 | stripped := []string{} 20 | for _, line := range original { 21 | line = certRegex.ReplaceAllString(line, redactedText) 22 | line = keyRegex.ReplaceAllString(line, redactedText) 23 | stripped = append(stripped, line) 24 | } 25 | return stripped 26 | } 27 | 28 | func Debug(args ...interface{}) { 29 | logger.Debug(args...) 30 | } 31 | 32 | func Debugf(fmtString string, args ...interface{}) { 33 | logger.Debugf(fmtString, args...) 34 | } 35 | 36 | func Error(args ...interface{}) { 37 | logger.Error(args...) 38 | } 39 | 40 | func Errorf(fmtString string, args ...interface{}) { 41 | logger.Errorf(fmtString, args...) 42 | } 43 | 44 | func Info(args ...interface{}) { 45 | logger.Info(args...) 46 | } 47 | 48 | func Infof(fmtString string, args ...interface{}) { 49 | logger.Infof(fmtString, args...) 50 | } 51 | 52 | func Warn(args ...interface{}) { 53 | logger.Warn(args...) 54 | } 55 | 56 | func Warnf(fmtString string, args ...interface{}) { 57 | logger.Warnf(fmtString, args...) 58 | } 59 | 60 | func SetDebug(debug bool) { 61 | logger.SetDebug(debug) 62 | } 63 | 64 | func SetOutWriter(out io.Writer) { 65 | logger.SetOutWriter(out) 66 | } 67 | 68 | func SetErrWriter(err io.Writer) { 69 | logger.SetErrWriter(err) 70 | } 71 | 72 | func History() []string { 73 | return stripSecrets(logger.History()) 74 | } 75 | -------------------------------------------------------------------------------- /libmachine/log/machine_logger.go: -------------------------------------------------------------------------------- 1 | package log 2 | 3 | import "io" 4 | 5 | type MachineLogger interface { 6 | SetDebug(debug bool) 7 | 8 | SetOutWriter(io.Writer) 9 | SetErrWriter(io.Writer) 10 | 11 | Debug(args ...interface{}) 12 | Debugf(fmtString string, args ...interface{}) 13 | 14 | Error(args ...interface{}) 15 | Errorf(fmtString string, args ...interface{}) 16 | 17 | Info(args ...interface{}) 18 | Infof(fmtString string, args ...interface{}) 19 | 20 | Warn(args ...interface{}) 21 | Warnf(fmtString string, args ...interface{}) 22 | 23 | History() []string 24 | } 25 | -------------------------------------------------------------------------------- /libmachine/mcndockerclient/docker_client.go: -------------------------------------------------------------------------------- 1 | package mcndockerclient 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/rancher/machine/libmachine/cert" 7 | "github.com/samalba/dockerclient" 8 | ) 9 | 10 | // DockerClient creates a docker client for a given host. 11 | func DockerClient(dockerHost DockerHost) (*dockerclient.DockerClient, error) { 12 | url, err := dockerHost.URL() 13 | if err != nil { 14 | return nil, err 15 | } 16 | 17 | tlsConfig, err := cert.ReadTLSConfig(url, dockerHost.AuthOptions()) 18 | if err != nil { 19 | return nil, fmt.Errorf("Unable to read TLS config: %s", err) 20 | } 21 | 22 | return dockerclient.NewDockerClient(url, tlsConfig) 23 | } 24 | 25 | // CreateContainer creates a docker container. 26 | func CreateContainer(dockerHost DockerHost, config *dockerclient.ContainerConfig, name string) error { 27 | docker, err := DockerClient(dockerHost) 28 | if err != nil { 29 | return err 30 | } 31 | 32 | if err = docker.PullImage(config.Image, nil); err != nil { 33 | return fmt.Errorf("Unable to pull image: %s", err) 34 | } 35 | 36 | var authConfig *dockerclient.AuthConfig 37 | containerID, err := docker.CreateContainer(config, name, authConfig) 38 | if err != nil { 39 | return fmt.Errorf("Error while creating container: %s", err) 40 | } 41 | 42 | if err = docker.StartContainer(containerID, &config.HostConfig); err != nil { 43 | return fmt.Errorf("Error while starting container: %s", err) 44 | } 45 | 46 | return nil 47 | } 48 | -------------------------------------------------------------------------------- /libmachine/mcndockerclient/docker_host.go: -------------------------------------------------------------------------------- 1 | package mcndockerclient 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/rancher/machine/libmachine/auth" 7 | ) 8 | 9 | type URLer interface { 10 | // URL returns the Docker host URL 11 | URL() (string, error) 12 | } 13 | 14 | type AuthOptionser interface { 15 | // AuthOptions returns the authOptions 16 | AuthOptions() *auth.Options 17 | } 18 | 19 | type DockerHost interface { 20 | URLer 21 | AuthOptionser 22 | } 23 | 24 | type RemoteDocker struct { 25 | HostURL string 26 | AuthOption *auth.Options 27 | } 28 | 29 | // URL returns the Docker host URL 30 | func (rd *RemoteDocker) URL() (string, error) { 31 | if rd == nil || rd.HostURL == "" { 32 | return "", fmt.Errorf("Docker Host URL not set") 33 | } 34 | 35 | return rd.HostURL, nil 36 | } 37 | 38 | // AuthOptions returns the authOptions 39 | func (rd *RemoteDocker) AuthOptions() *auth.Options { 40 | if rd == nil { 41 | return nil 42 | } 43 | return rd.AuthOption 44 | } 45 | -------------------------------------------------------------------------------- /libmachine/mcndockerclient/docker_versioner.go: -------------------------------------------------------------------------------- 1 | package mcndockerclient 2 | 3 | import "fmt" 4 | 5 | var CurrentDockerVersioner DockerVersioner = &defaultDockerVersioner{} 6 | 7 | type DockerVersioner interface { 8 | DockerVersion(host DockerHost) (string, error) 9 | } 10 | 11 | func DockerVersion(host DockerHost) (string, error) { 12 | return CurrentDockerVersioner.DockerVersion(host) 13 | } 14 | 15 | type defaultDockerVersioner struct{} 16 | 17 | func (dv *defaultDockerVersioner) DockerVersion(host DockerHost) (string, error) { 18 | client, err := DockerClient(host) 19 | if err != nil { 20 | return "", fmt.Errorf("Unable to query docker version: %s", err) 21 | } 22 | 23 | version, err := client.Version() 24 | if err != nil { 25 | return "", fmt.Errorf("Unable to query docker version: %s", err) 26 | } 27 | 28 | return version.Version, nil 29 | } 30 | -------------------------------------------------------------------------------- /libmachine/mcndockerclient/fake_docker_versioner.go: -------------------------------------------------------------------------------- 1 | package mcndockerclient 2 | 3 | type FakeDockerVersioner struct { 4 | Version string 5 | Err error 6 | } 7 | 8 | func (dv *FakeDockerVersioner) DockerVersion(host DockerHost) (string, error) { 9 | if dv.Err != nil { 10 | return "", dv.Err 11 | } 12 | 13 | return dv.Version, nil 14 | } 15 | -------------------------------------------------------------------------------- /libmachine/mcnerror/errors.go: -------------------------------------------------------------------------------- 1 | package mcnerror 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "strings" 7 | 8 | "github.com/rancher/machine/libmachine/state" 9 | ) 10 | 11 | var ( 12 | ErrInvalidHostname = errors.New("Invalid hostname specified. Allowed hostname chars are: 0-9a-zA-Z . -") 13 | ) 14 | 15 | type ErrHostDoesNotExist struct { 16 | Name string 17 | } 18 | 19 | func (e ErrHostDoesNotExist) Error() string { 20 | return fmt.Sprintf("Docker machine %q does not exist. Use \"docker-machine ls\" to list machines. Use \"docker-machine create\" to add a new one.", e.Name) 21 | } 22 | 23 | type ErrHostAlreadyExists struct { 24 | Name string 25 | } 26 | 27 | func (e ErrHostAlreadyExists) Error() string { 28 | return fmt.Sprintf("Docker machine %q already exists", e.Name) 29 | } 30 | 31 | type ErrDuringPreCreate struct { 32 | Cause error 33 | } 34 | 35 | func (e ErrDuringPreCreate) Error() string { 36 | return fmt.Sprintf("Error with pre-create check: %q", e.Cause) 37 | } 38 | 39 | type ErrHostAlreadyInState struct { 40 | Name string 41 | State state.State 42 | } 43 | 44 | func (e ErrHostAlreadyInState) Error() string { 45 | return fmt.Sprintf("Machine %q is already %s.", e.Name, strings.ToLower(e.State.String())) 46 | } 47 | -------------------------------------------------------------------------------- /libmachine/mcnflag/flag.go: -------------------------------------------------------------------------------- 1 | package mcnflag 2 | 3 | import "fmt" 4 | 5 | type Flag interface { 6 | fmt.Stringer 7 | Default() interface{} 8 | } 9 | 10 | type StringFlag struct { 11 | Name string 12 | Usage string 13 | EnvVar string 14 | Value string 15 | } 16 | 17 | // TODO: Could this be done more succinctly using embedding? 18 | func (f StringFlag) String() string { 19 | return f.Name 20 | } 21 | 22 | func (f StringFlag) Default() interface{} { 23 | return f.Value 24 | } 25 | 26 | type StringSliceFlag struct { 27 | Name string 28 | Usage string 29 | EnvVar string 30 | Value []string 31 | } 32 | 33 | // TODO: Could this be done more succinctly using embedding? 34 | func (f StringSliceFlag) String() string { 35 | return f.Name 36 | } 37 | 38 | func (f StringSliceFlag) Default() interface{} { 39 | return f.Value 40 | } 41 | 42 | type IntFlag struct { 43 | Name string 44 | Usage string 45 | EnvVar string 46 | Value int 47 | } 48 | 49 | // TODO: Could this be done more succinctly using embedding? 50 | func (f IntFlag) String() string { 51 | return f.Name 52 | } 53 | 54 | func (f IntFlag) Default() interface{} { 55 | return f.Value 56 | } 57 | 58 | type BoolFlag struct { 59 | Name string 60 | Usage string 61 | EnvVar string 62 | } 63 | 64 | // TODO: Could this be done more succinctly using embedding? 65 | func (f BoolFlag) String() string { 66 | return f.Name 67 | } 68 | 69 | func (f BoolFlag) Default() interface{} { 70 | return false 71 | } 72 | -------------------------------------------------------------------------------- /libmachine/mcnutils/utils_test.go: -------------------------------------------------------------------------------- 1 | package mcnutils 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "runtime" 7 | "testing" 8 | ) 9 | 10 | func TestCopyFile(t *testing.T) { 11 | testStr := "test-machine" 12 | 13 | srcFile, err := os.CreateTemp("", "machine-test-") 14 | if err != nil { 15 | t.Fatal(err) 16 | } 17 | srcFi, err := srcFile.Stat() 18 | if err != nil { 19 | t.Fatal(err) 20 | } 21 | 22 | srcFile.Write([]byte(testStr)) 23 | srcFile.Close() 24 | 25 | srcFilePath := filepath.Join(os.TempDir(), srcFi.Name()) 26 | 27 | destFile, err := os.CreateTemp("", "machine-copy-test-") 28 | if err != nil { 29 | t.Fatal(err) 30 | } 31 | 32 | destFi, err := destFile.Stat() 33 | if err != nil { 34 | t.Fatal(err) 35 | } 36 | 37 | destFile.Close() 38 | 39 | destFilePath := filepath.Join(os.TempDir(), destFi.Name()) 40 | 41 | if err := CopyFile(srcFilePath, destFilePath); err != nil { 42 | t.Fatal(err) 43 | } 44 | 45 | data, err := os.ReadFile(destFilePath) 46 | if err != nil { 47 | t.Fatal(err) 48 | } 49 | 50 | if string(data) != testStr { 51 | t.Fatalf("expected data \"%s\"; received \"%s\"", testStr, string(data)) 52 | } 53 | } 54 | 55 | func TestGetUsername(t *testing.T) { 56 | currentUser := "unknown" 57 | switch runtime.GOOS { 58 | case "darwin", "linux": 59 | currentUser = os.Getenv("USER") 60 | case "windows": 61 | currentUser = os.Getenv("USERNAME") 62 | } 63 | 64 | username := GetUsername() 65 | if username != currentUser { 66 | t.Fatalf("expected username %s; received %s", currentUser, username) 67 | } 68 | } 69 | 70 | func TestGenerateRandomID(t *testing.T) { 71 | id := GenerateRandomID() 72 | 73 | if len(id) != 64 { 74 | t.Fatalf("Id returned is incorrect: %s", id) 75 | } 76 | } 77 | 78 | func TestShortenId(t *testing.T) { 79 | id := GenerateRandomID() 80 | truncID := TruncateID(id) 81 | if len(truncID) != 12 { 82 | t.Fatalf("Id returned is incorrect: truncate on %s returned %s", id, truncID) 83 | } 84 | } 85 | 86 | func TestShortenIdEmpty(t *testing.T) { 87 | id := "" 88 | truncID := TruncateID(id) 89 | if len(truncID) > len(id) { 90 | t.Fatalf("Id returned is incorrect: truncate on %s returned %s", id, truncID) 91 | } 92 | } 93 | 94 | func TestShortenIdInvalid(t *testing.T) { 95 | id := "1234" 96 | truncID := TruncateID(id) 97 | if len(truncID) != len(id) { 98 | t.Fatalf("Id returned is incorrect: truncate on %s returned %s", id, truncID) 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /libmachine/persist/persisttest/fakestore.go: -------------------------------------------------------------------------------- 1 | package persisttest 2 | 3 | import "github.com/rancher/machine/libmachine/host" 4 | 5 | type FakeStore struct { 6 | Hosts []*host.Host 7 | ExistsErr, ListErr, LoadErr, RemoveErr, SaveErr error 8 | } 9 | 10 | func (fs *FakeStore) Exists(name string) (bool, error) { 11 | if fs.ExistsErr != nil { 12 | return false, fs.ExistsErr 13 | } 14 | for _, h := range fs.Hosts { 15 | if h.Name == name { 16 | return true, nil 17 | } 18 | } 19 | 20 | return false, nil 21 | } 22 | 23 | func (fs *FakeStore) List() ([]string, error) { 24 | names := []string{} 25 | for _, h := range fs.Hosts { 26 | names = append(names, h.Name) 27 | } 28 | return names, fs.ListErr 29 | } 30 | 31 | func (fs *FakeStore) Load(name string) (*host.Host, error) { 32 | if fs.LoadErr != nil { 33 | return nil, fs.LoadErr 34 | } 35 | for _, h := range fs.Hosts { 36 | if h.Name == name { 37 | return h, nil 38 | } 39 | } 40 | 41 | return nil, nil 42 | } 43 | 44 | func (fs *FakeStore) Remove(name string) error { 45 | if fs.RemoveErr != nil { 46 | return fs.RemoveErr 47 | } 48 | for i, h := range fs.Hosts { 49 | if h.Name == name { 50 | fs.Hosts = append(fs.Hosts[:i], fs.Hosts[i+1:]...) 51 | return nil 52 | } 53 | } 54 | return nil 55 | } 56 | 57 | func (fs *FakeStore) Save(host *host.Host) error { 58 | if fs.SaveErr == nil { 59 | fs.Hosts = append(fs.Hosts, host) 60 | return nil 61 | } 62 | return fs.SaveErr 63 | } 64 | 65 | func (fs *FakeStore) GetMachinesDir() string { 66 | return "" 67 | } 68 | -------------------------------------------------------------------------------- /libmachine/persist/store.go: -------------------------------------------------------------------------------- 1 | package persist 2 | 3 | import ( 4 | "github.com/rancher/machine/libmachine/host" 5 | ) 6 | 7 | type Store interface { 8 | // Exists returns whether a machine exists or not 9 | Exists(name string) (bool, error) 10 | 11 | // List returns a list of all hosts in the store 12 | List() ([]string, error) 13 | 14 | // Load loads a host by name 15 | Load(name string) (*host.Host, error) 16 | 17 | // Remove removes a machine from the store 18 | Remove(name string) error 19 | 20 | // Save persists a machine in the store 21 | Save(host *host.Host) error 22 | 23 | // GetMachinesDir gets the location on disk where the machine configs will be stored 24 | GetMachinesDir() string 25 | } 26 | 27 | func LoadHosts(s Store, hostNames []string) ([]*host.Host, map[string]error) { 28 | loadedHosts := []*host.Host{} 29 | errors := map[string]error{} 30 | 31 | for _, hostName := range hostNames { 32 | h, err := s.Load(hostName) 33 | if err != nil { 34 | errors[hostName] = err 35 | } else { 36 | loadedHosts = append(loadedHosts, h) 37 | } 38 | } 39 | 40 | return loadedHosts, errors 41 | } 42 | 43 | func LoadAllHosts(s Store) ([]*host.Host, map[string]error, error) { 44 | hostNames, err := s.List() 45 | if err != nil { 46 | return nil, nil, err 47 | } 48 | loadedHosts, hostInError := LoadHosts(s, hostNames) 49 | return loadedHosts, hostInError, nil 50 | } 51 | -------------------------------------------------------------------------------- /libmachine/provider/provider.go: -------------------------------------------------------------------------------- 1 | package provider 2 | 3 | import "github.com/rancher/machine/libmachine/host" 4 | 5 | type Provider interface { 6 | // IsValid checks whether or not the Provider can successfully create 7 | // machines. If the check does not pass, the provider is no good. 8 | IsValid() bool 9 | 10 | // Create calls out to the driver this provider is associated with, to 11 | // actually create the resource. 12 | Create() (host.Host, error) 13 | } 14 | -------------------------------------------------------------------------------- /libmachine/provision/arch_test.go: -------------------------------------------------------------------------------- 1 | package provision 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/rancher/machine/drivers/fakedriver" 7 | "github.com/rancher/machine/libmachine/auth" 8 | "github.com/rancher/machine/libmachine/engine" 9 | "github.com/rancher/machine/libmachine/provision/provisiontest" 10 | "github.com/rancher/machine/libmachine/swarm" 11 | ) 12 | 13 | func TestArchDefaultStorageDriver(t *testing.T) { 14 | p := NewArchProvisioner(&fakedriver.Driver{}).(*ArchProvisioner) 15 | p.SSHCommander = provisiontest.NewFakeSSHCommander(provisiontest.FakeSSHCommanderOptions{}) 16 | p.Provision(swarm.Options{}, auth.Options{}, engine.Options{}) 17 | if p.EngineOptions.StorageDriver != "overlay" { 18 | t.Fatal("Default storage driver should be overlay") 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /libmachine/provision/custom_script.go: -------------------------------------------------------------------------------- 1 | package provision 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/rancher/machine/libmachine/provision/pkgaction" 8 | ) 9 | 10 | func WithCustomScript(provisioner Provisioner, customScriptPath, hostname string) error { 11 | if provisioner == nil { 12 | return nil 13 | } 14 | 15 | if hostname == "" { 16 | hostname = provisioner.GetDriver().GetMachineName() 17 | } 18 | 19 | if err := provisioner.SetHostname(hostname); err != nil { 20 | return err 21 | } 22 | 23 | for _, pkg := range provisioner.GetPackages() { 24 | if err := provisioner.Package(pkg, pkgaction.Install); err != nil { 25 | return err 26 | } 27 | } 28 | 29 | customScriptContents, err := os.ReadFile(customScriptPath) 30 | if err != nil { 31 | return fmt.Errorf("unable to read file %s: %v", customScriptPath, err) 32 | } 33 | 34 | if output, err := provisioner.SSHCommand(fmt.Sprintf("cat <<'OEOF' >/tmp/install_script.sh\n%s\nOEOF", string(customScriptContents))); err != nil { 35 | return fmt.Errorf("error uploading custom script: output: %s, error: %s", output, err) 36 | } 37 | if output, err := provisioner.SSHCommand("sudo sh /tmp/install_script.sh"); err != nil { 38 | return fmt.Errorf("error running custom script: output: %s, error: %s", output, err) 39 | } 40 | 41 | return nil 42 | } 43 | -------------------------------------------------------------------------------- /libmachine/provision/debian_test.go: -------------------------------------------------------------------------------- 1 | package provision 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/rancher/machine/drivers/fakedriver" 7 | "github.com/rancher/machine/libmachine/auth" 8 | "github.com/rancher/machine/libmachine/engine" 9 | "github.com/rancher/machine/libmachine/provision/provisiontest" 10 | "github.com/rancher/machine/libmachine/swarm" 11 | ) 12 | 13 | func TestDebianDefaultStorageDriver(t *testing.T) { 14 | p := NewDebianProvisioner(&fakedriver.Driver{}).(*DebianProvisioner) 15 | p.SSHCommander = provisiontest.NewFakeSSHCommander(provisiontest.FakeSSHCommanderOptions{}) 16 | p.Provision(swarm.Options{}, auth.Options{}, engine.Options{}) 17 | if p.EngineOptions.StorageDriver != DefaultStorageDriver { 18 | t.Fatalf("Default storage driver should be %s", DefaultStorageDriver) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /libmachine/provision/engine_config_context.go: -------------------------------------------------------------------------------- 1 | package provision 2 | 3 | import ( 4 | "github.com/rancher/machine/libmachine/auth" 5 | "github.com/rancher/machine/libmachine/engine" 6 | ) 7 | 8 | type EngineConfigContext struct { 9 | DockerPort int 10 | AuthOptions auth.Options 11 | EngineOptions engine.Options 12 | DockerOptionsDir string 13 | } 14 | -------------------------------------------------------------------------------- /libmachine/provision/errors.go: -------------------------------------------------------------------------------- 1 | package provision 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | ) 7 | 8 | var ( 9 | ErrDetectionFailed = errors.New("OS type not recognized") 10 | ) 11 | 12 | type ErrDaemonAvailable struct { 13 | wrappedErr error 14 | } 15 | 16 | func (e ErrDaemonAvailable) Error() string { 17 | return fmt.Sprintf("Unable to verify the Docker daemon is listening: %s", e.wrappedErr) 18 | } 19 | 20 | func NewErrDaemonAvailable(err error) ErrDaemonAvailable { 21 | return ErrDaemonAvailable{ 22 | wrappedErr: err, 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /libmachine/provision/fedora.go: -------------------------------------------------------------------------------- 1 | package provision 2 | 3 | import ( 4 | "github.com/rancher/machine/libmachine/drivers" 5 | ) 6 | 7 | func init() { 8 | Register("Fedora", &RegisteredProvisioner{ 9 | New: NewFedoraProvisioner, 10 | }) 11 | } 12 | 13 | func NewFedoraProvisioner(d drivers.Driver) Provisioner { 14 | return &FedoraProvisioner{ 15 | NewRedHatProvisioner("fedora", d), 16 | } 17 | } 18 | 19 | type FedoraProvisioner struct { 20 | *RedHatProvisioner 21 | } 22 | 23 | func (provisioner *FedoraProvisioner) String() string { 24 | return "fedora" 25 | } 26 | 27 | func (provisioner *FedoraProvisioner) CompatibleWithHost() bool { 28 | isFedora := provisioner.OsReleaseInfo.ID == provisioner.OsReleaseID 29 | isCoreOS := provisioner.OsReleaseInfo.VariantID == "coreos" 30 | return isFedora && !isCoreOS 31 | } 32 | -------------------------------------------------------------------------------- /libmachine/provision/oraclelinux.go: -------------------------------------------------------------------------------- 1 | package provision 2 | 3 | import ( 4 | "github.com/rancher/machine/libmachine/drivers" 5 | ) 6 | 7 | func init() { 8 | Register("OracleLinux", &RegisteredProvisioner{ 9 | New: NewOracleLinuxProvisioner, 10 | }) 11 | } 12 | 13 | func NewOracleLinuxProvisioner(d drivers.Driver) Provisioner { 14 | return &OracleLinuxProvisioner{ 15 | NewRedHatProvisioner("ol", d), 16 | } 17 | } 18 | 19 | type OracleLinuxProvisioner struct { 20 | *RedHatProvisioner 21 | } 22 | 23 | func (provisioner *OracleLinuxProvisioner) String() string { 24 | return "ol" 25 | } 26 | -------------------------------------------------------------------------------- /libmachine/provision/pkgaction/pkg_action.go: -------------------------------------------------------------------------------- 1 | package pkgaction 2 | 3 | type PackageAction int 4 | 5 | const ( 6 | Install PackageAction = iota 7 | Remove 8 | Upgrade 9 | Purge 10 | ) 11 | 12 | var packageActions = []string{ 13 | "install", 14 | "remove", 15 | "upgrade", 16 | "purge", 17 | } 18 | 19 | func (s PackageAction) String() string { 20 | if int(s) >= 0 && int(s) < len(packageActions) { 21 | return packageActions[s] 22 | } 23 | 24 | return "" 25 | } 26 | -------------------------------------------------------------------------------- /libmachine/provision/pkgaction/pkg_action_test.go: -------------------------------------------------------------------------------- 1 | package pkgaction 2 | 3 | import "testing" 4 | 5 | func TestActionValue(t *testing.T) { 6 | if Install.String() != "install" { 7 | t.Fatalf("Expected %q but got %q", "install", Install.String()) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /libmachine/provision/provisiontest/sshcommander.go: -------------------------------------------------------------------------------- 1 | //Package provisiontest provides utilities for testing provisioners 2 | package provisiontest 3 | 4 | import "errors" 5 | 6 | //FakeSSHCommanderOptions is intended to create a FakeSSHCommander without actually knowing the underlying sshcommands by passing it to NewSSHCommander 7 | type FakeSSHCommanderOptions struct { 8 | //Result of the ssh command to look up the FilesystemType 9 | FilesystemType string 10 | } 11 | 12 | //FakeSSHCommander is an implementation of provision.SSHCommander to provide predictable responses set by testing code 13 | //Extend it when needed 14 | type FakeSSHCommander struct { 15 | Responses map[string]string 16 | } 17 | 18 | //NewFakeSSHCommander creates a FakeSSHCommander without actually knowing the underlying sshcommands 19 | func NewFakeSSHCommander(options FakeSSHCommanderOptions) *FakeSSHCommander { 20 | if options.FilesystemType == "" { 21 | options.FilesystemType = "ext4" 22 | } 23 | sshCmder := &FakeSSHCommander{ 24 | Responses: map[string]string{ 25 | "stat -f -c %T /var/lib": options.FilesystemType + "\n", 26 | }, 27 | } 28 | 29 | return sshCmder 30 | } 31 | 32 | //SSHCommand is an implementation of provision.SSHCommander.SSHCommand to provide predictable responses set by testing code 33 | func (sshCmder *FakeSSHCommander) SSHCommand(args string) (string, error) { 34 | response, commandRegistered := sshCmder.Responses[args] 35 | if !commandRegistered { 36 | return "", errors.New("Command not registered in FakeSSHCommander") 37 | } 38 | return response, nil 39 | } 40 | -------------------------------------------------------------------------------- /libmachine/provision/provisiontest/sshcommander_test.go: -------------------------------------------------------------------------------- 1 | package provisiontest 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/stretchr/testify/assert" 7 | ) 8 | 9 | func TestCreateFakeSSHCommander(t *testing.T) { 10 | sshCmder := NewFakeSSHCommander(FakeSSHCommanderOptions{FilesystemType: "btrfs"}) 11 | output, err := sshCmder.SSHCommand("stat -f -c %T /var/lib") 12 | if err != nil || output != "btrfs\n" { 13 | t.Fatal("FakeSSHCommander should have returned btrfs and no error but returned '", output, "' and error", err) 14 | } 15 | } 16 | 17 | func TestStatSSHCommand(t *testing.T) { 18 | sshCmder := FakeSSHCommander{ 19 | Responses: map[string]string{"sshcommand": "sshcommandresponse"}, 20 | } 21 | 22 | output, err := sshCmder.SSHCommand("sshcommand") 23 | assert.NoError(t, err) 24 | assert.Equal(t, "sshcommandresponse", output) 25 | 26 | output, err = sshCmder.SSHCommand("errorcommand") 27 | assert.Error(t, err) 28 | } 29 | -------------------------------------------------------------------------------- /libmachine/provision/redhat_ssh_commander.go: -------------------------------------------------------------------------------- 1 | package provision 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/rancher/machine/libmachine/drivers" 7 | "github.com/rancher/machine/libmachine/log" 8 | "github.com/rancher/machine/libmachine/ssh" 9 | ) 10 | 11 | type RedHatSSHCommander struct { 12 | Driver drivers.Driver 13 | } 14 | 15 | func (sshCmder RedHatSSHCommander) SSHCommand(args string) (string, error) { 16 | client, err := drivers.GetSSHClientFromDriver(sshCmder.Driver) 17 | if err != nil { 18 | return "", err 19 | } 20 | 21 | log.Debugf("About to run SSH command:\n%s", args) 22 | 23 | // redhat needs "-t" for tty allocation on ssh therefore we check for the 24 | // external client and add as needed. 25 | // Note: CentOS 7.0 needs multiple "-tt" to force tty allocation when ssh has 26 | // no local tty. 27 | var output string 28 | switch c := client.(type) { 29 | case *ssh.ExternalClient: 30 | c.BaseArgs = append(c.BaseArgs, "-tt") 31 | output, err = c.Output(args) 32 | case *ssh.NativeClient: 33 | output, err = c.OutputWithPty(args) 34 | } 35 | 36 | log.Debugf("SSH cmd err, output: %v: %s", err, output) 37 | if err != nil { 38 | return "", fmt.Errorf(`RHEL ssh command error: command: %s err: %v output: %s`, args, err, output) 39 | } 40 | 41 | return output, nil 42 | } 43 | -------------------------------------------------------------------------------- /libmachine/provision/redhat_test.go: -------------------------------------------------------------------------------- 1 | package provision 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/rancher/machine/drivers/fakedriver" 7 | "github.com/rancher/machine/libmachine/auth" 8 | "github.com/rancher/machine/libmachine/engine" 9 | "github.com/rancher/machine/libmachine/provision/provisiontest" 10 | "github.com/rancher/machine/libmachine/swarm" 11 | ) 12 | 13 | func TestRedHatDefaultStorageDriver(t *testing.T) { 14 | p := NewRedHatProvisioner("", &fakedriver.Driver{}) 15 | p.SSHCommander = provisiontest.NewFakeSSHCommander(provisiontest.FakeSSHCommanderOptions{}) 16 | p.Provision(swarm.Options{}, auth.Options{}, engine.Options{}) 17 | if p.EngineOptions.StorageDriver != DefaultStorageDriver { 18 | t.Fatalf("Default storage driver should be %s", DefaultStorageDriver) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /libmachine/provision/rockylinux.go: -------------------------------------------------------------------------------- 1 | package provision 2 | 3 | import ( 4 | "github.com/rancher/machine/libmachine/drivers" 5 | ) 6 | 7 | func init() { 8 | Register("Rocky", &RegisteredProvisioner{ 9 | New: NewRockyProvisioner, 10 | }) 11 | } 12 | 13 | func NewRockyProvisioner(d drivers.Driver) Provisioner { 14 | return &RockyProvisioner{ 15 | NewRedHatProvisioner("rocky", d), 16 | } 17 | } 18 | 19 | type RockyProvisioner struct { 20 | *RedHatProvisioner 21 | } 22 | 23 | func (provisioner *RockyProvisioner) String() string { 24 | return "rocky" 25 | } 26 | -------------------------------------------------------------------------------- /libmachine/provision/serviceaction/service_action.go: -------------------------------------------------------------------------------- 1 | package serviceaction 2 | 3 | type ServiceAction int 4 | 5 | const ( 6 | Restart ServiceAction = iota 7 | Start 8 | Stop 9 | Enable 10 | Disable 11 | DaemonReload 12 | ) 13 | 14 | var serviceActions = []string{ 15 | "restart", 16 | "start", 17 | "stop", 18 | "enable", 19 | "disable", 20 | "daemon-reload", 21 | } 22 | 23 | func (s ServiceAction) String() string { 24 | if int(s) >= 0 && int(s) < len(serviceActions) { 25 | return serviceActions[s] 26 | } 27 | 28 | return "" 29 | } 30 | -------------------------------------------------------------------------------- /libmachine/provision/serviceaction/service_action_test.go: -------------------------------------------------------------------------------- 1 | package serviceaction 2 | 3 | import "testing" 4 | 5 | func TestActionValue(t *testing.T) { 6 | if Restart.String() != "restart" { 7 | t.Fatalf("Expected %s but got %s", "install", Restart.String()) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /libmachine/provision/ubuntu_systemd_test.go: -------------------------------------------------------------------------------- 1 | package provision 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/rancher/machine/drivers/fakedriver" 7 | "github.com/rancher/machine/libmachine/auth" 8 | "github.com/rancher/machine/libmachine/engine" 9 | "github.com/rancher/machine/libmachine/provision/provisiontest" 10 | "github.com/rancher/machine/libmachine/swarm" 11 | ) 12 | 13 | func TestUbuntuSystemdCompatibleWithHost(t *testing.T) { 14 | info := &OsRelease{ 15 | ID: "ubuntu", 16 | VersionID: "15.04", 17 | } 18 | p := NewUbuntuSystemdProvisioner(nil) 19 | p.SetOsReleaseInfo(info) 20 | 21 | compatible := p.CompatibleWithHost() 22 | 23 | if !compatible { 24 | t.Fatalf("expected to be compatible with ubuntu 15.04") 25 | } 26 | 27 | info.VersionID = "14.04" 28 | 29 | compatible = p.CompatibleWithHost() 30 | 31 | if compatible { 32 | t.Fatalf("expected to NOT be compatible with ubuntu 14.04") 33 | } 34 | 35 | } 36 | 37 | func TestUbuntuSystemdDefaultStorageDriver(t *testing.T) { 38 | p := NewUbuntuSystemdProvisioner(&fakedriver.Driver{}).(*UbuntuSystemdProvisioner) 39 | p.SSHCommander = provisiontest.NewFakeSSHCommander(provisiontest.FakeSSHCommanderOptions{}) 40 | p.Provision(swarm.Options{}, auth.Options{}, engine.Options{}) 41 | if p.EngineOptions.StorageDriver != DefaultStorageDriver { 42 | t.Fatalf("Default storage driver should be %s", DefaultStorageDriver) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /libmachine/provision/ubuntu_upstart_test.go: -------------------------------------------------------------------------------- 1 | package provision 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/rancher/machine/drivers/fakedriver" 7 | "github.com/rancher/machine/libmachine/auth" 8 | "github.com/rancher/machine/libmachine/engine" 9 | "github.com/rancher/machine/libmachine/provision/provisiontest" 10 | "github.com/rancher/machine/libmachine/swarm" 11 | ) 12 | 13 | func TestUbuntuCompatibleWithHost(t *testing.T) { 14 | info := &OsRelease{ 15 | ID: "ubuntu", 16 | VersionID: "14.04", 17 | } 18 | p := NewUbuntuProvisioner(nil) 19 | p.SetOsReleaseInfo(info) 20 | 21 | compatible := p.CompatibleWithHost() 22 | 23 | if !compatible { 24 | t.Fatalf("expected to be compatible with ubuntu 14.04") 25 | } 26 | 27 | info.VersionID = "15.04" 28 | 29 | compatible = p.CompatibleWithHost() 30 | 31 | if compatible { 32 | t.Fatalf("expected to NOT be compatible with ubuntu 15.04") 33 | } 34 | 35 | } 36 | 37 | func TestUbuntuDefaultStorageDriver(t *testing.T) { 38 | p := NewUbuntuProvisioner(&fakedriver.Driver{}).(*UbuntuProvisioner) 39 | p.SSHCommander = provisiontest.NewFakeSSHCommander(provisiontest.FakeSSHCommanderOptions{}) 40 | p.Provision(swarm.Options{}, auth.Options{}, engine.Options{}) 41 | if p.EngineOptions.StorageDriver != DefaultStorageDriver { 42 | t.Fatalf("Default storage driver should be %s", DefaultStorageDriver) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /libmachine/shell/shell.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package shell 4 | 5 | import ( 6 | "errors" 7 | "fmt" 8 | "os" 9 | "path/filepath" 10 | ) 11 | 12 | var ( 13 | ErrUnknownShell = errors.New("Error: Unknown shell") 14 | ) 15 | 16 | // Detect detects user's current shell. 17 | func Detect() (string, error) { 18 | shell := os.Getenv("SHELL") 19 | 20 | if shell == "" { 21 | fmt.Printf("The default lines below are for a sh/bash shell, you can specify the shell you're using, with the --shell flag.\n\n") 22 | return "", ErrUnknownShell 23 | } 24 | 25 | return filepath.Base(shell), nil 26 | } 27 | -------------------------------------------------------------------------------- /libmachine/shell/shell_test.go: -------------------------------------------------------------------------------- 1 | package shell 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestDetectBash(t *testing.T) { 11 | defer func(shell string) { os.Setenv("SHELL", shell) }(os.Getenv("SHELL")) 12 | os.Setenv("SHELL", "/bin/bash") 13 | 14 | shell, err := Detect() 15 | 16 | assert.Equal(t, "bash", shell) 17 | assert.NoError(t, err) 18 | } 19 | 20 | func TestDetectFish(t *testing.T) { 21 | defer func(shell string) { os.Setenv("SHELL", shell) }(os.Getenv("SHELL")) 22 | os.Setenv("SHELL", "/bin/fish") 23 | 24 | shell, err := Detect() 25 | 26 | assert.Equal(t, "fish", shell) 27 | assert.NoError(t, err) 28 | } 29 | -------------------------------------------------------------------------------- /libmachine/shell/shell_unix_test.go: -------------------------------------------------------------------------------- 1 | // +build !windows 2 | 3 | package shell 4 | 5 | import ( 6 | "os" 7 | "testing" 8 | 9 | "github.com/stretchr/testify/assert" 10 | ) 11 | 12 | func TestUnknownShell(t *testing.T) { 13 | defer func(shell string) { os.Setenv("SHELL", shell) }(os.Getenv("SHELL")) 14 | os.Setenv("SHELL", "") 15 | 16 | shell, err := Detect() 17 | 18 | assert.Equal(t, err, ErrUnknownShell) 19 | assert.Empty(t, shell) 20 | } 21 | -------------------------------------------------------------------------------- /libmachine/shell/shell_windows.go: -------------------------------------------------------------------------------- 1 | package shell 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | "strings" 8 | "syscall" 9 | "unsafe" 10 | ) 11 | 12 | // re-implementation of private function in https://github.com/golang/go/blob/master/src/syscall/syscall_windows.go#L945 13 | func getProcessEntry(pid int) (pe *syscall.ProcessEntry32, err error) { 14 | snapshot, err := syscall.CreateToolhelp32Snapshot(syscall.TH32CS_SNAPPROCESS, 0) 15 | if err != nil { 16 | return nil, err 17 | } 18 | defer syscall.CloseHandle(syscall.Handle(snapshot)) 19 | 20 | var processEntry syscall.ProcessEntry32 21 | processEntry.Size = uint32(unsafe.Sizeof(processEntry)) 22 | err = syscall.Process32First(snapshot, &processEntry) 23 | if err != nil { 24 | return nil, err 25 | } 26 | 27 | for { 28 | if processEntry.ProcessID == uint32(pid) { 29 | pe = &processEntry 30 | return 31 | } 32 | 33 | err = syscall.Process32Next(snapshot, &processEntry) 34 | if err != nil { 35 | return nil, err 36 | } 37 | } 38 | } 39 | 40 | // getNameAndItsPpid returns the exe file name its parent process id. 41 | func getNameAndItsPpid(pid int) (exefile string, parentid int, err error) { 42 | pe, err := getProcessEntry(pid) 43 | if err != nil { 44 | return "", 0, err 45 | } 46 | 47 | name := syscall.UTF16ToString(pe.ExeFile[:]) 48 | return name, int(pe.ParentProcessID), nil 49 | } 50 | 51 | func Detect() (string, error) { 52 | shell := os.Getenv("SHELL") 53 | 54 | if shell == "" { 55 | shell, shellppid, err := getNameAndItsPpid(os.Getppid()) 56 | if err != nil { 57 | return "cmd", err // defaulting to cmd 58 | } 59 | if strings.Contains(strings.ToLower(shell), "powershell") { 60 | return "powershell", nil 61 | } else if strings.Contains(strings.ToLower(shell), "cmd") { 62 | return "cmd", nil 63 | } else { 64 | shell, _, err := getNameAndItsPpid(shellppid) 65 | if err != nil { 66 | return "cmd", err // defaulting to cmd 67 | } 68 | if strings.Contains(strings.ToLower(shell), "powershell") { 69 | return "powershell", nil 70 | } else if strings.Contains(strings.ToLower(shell), "cmd") { 71 | return "cmd", nil 72 | } else { 73 | fmt.Printf("You can further specify your shell with either 'cmd' or 'powershell' with the --shell flag.\n\n") 74 | return "cmd", nil // this could be either powershell or cmd, defaulting to cmd 75 | } 76 | } 77 | } 78 | 79 | if os.Getenv("__fish_bin_dir") != "" { 80 | return "fish", nil 81 | } 82 | 83 | return filepath.Base(shell), nil 84 | } 85 | -------------------------------------------------------------------------------- /libmachine/shell/shell_windows_test.go: -------------------------------------------------------------------------------- 1 | package shell 2 | 3 | import ( 4 | "os" 5 | "testing" 6 | 7 | "github.com/stretchr/testify/assert" 8 | ) 9 | 10 | func TestDetect(t *testing.T) { 11 | defer func(shell string) { os.Setenv("SHELL", shell) }(os.Getenv("SHELL")) 12 | os.Setenv("SHELL", "") 13 | 14 | shell, err := Detect() 15 | 16 | assert.Equal(t, "powershell", shell) 17 | assert.NoError(t, err) 18 | } 19 | 20 | func TestGetNameAndItsPpidOfCurrent(t *testing.T) { 21 | shell, shellppid, err := getNameAndItsPpid(os.Getpid()) 22 | 23 | assert.Equal(t, "shell.test.exe", shell) 24 | assert.Equal(t, os.Getppid(), shellppid) 25 | assert.NoError(t, err) 26 | } 27 | 28 | func TestGetNameAndItsPpidOfParent(t *testing.T) { 29 | shell, _, err := getNameAndItsPpid(os.Getppid()) 30 | 31 | assert.Equal(t, "go.exe", shell) 32 | assert.NoError(t, err) 33 | } 34 | 35 | func TestGetNameAndItsPpidOfGrandParent(t *testing.T) { 36 | shell, shellppid, err := getNameAndItsPpid(os.Getppid()) 37 | shell, shellppid, err = getNameAndItsPpid(shellppid) 38 | 39 | assert.Equal(t, "powershell.exe", shell) 40 | assert.NoError(t, err) 41 | } 42 | -------------------------------------------------------------------------------- /libmachine/ssh/keys_test.go: -------------------------------------------------------------------------------- 1 | package ssh 2 | 3 | import ( 4 | "encoding/pem" 5 | "testing" 6 | ) 7 | 8 | func TestNewKeyPair(t *testing.T) { 9 | pair, err := NewKeyPair() 10 | if err != nil { 11 | t.Fatal(err) 12 | } 13 | 14 | if privPem := pem.EncodeToMemory(&pem.Block{Type: "RSA PRIVATE KEY", Headers: nil, Bytes: pair.PrivateKey}); len(privPem) == 0 { 15 | t.Fatal("No PEM returned") 16 | } 17 | 18 | if fingerprint := pair.Fingerprint(); len(fingerprint) == 0 { 19 | t.Fatal("Unable to generate fingerprint") 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /libmachine/ssh/ssh_test.go: -------------------------------------------------------------------------------- 1 | package ssh 2 | 3 | import ( 4 | "os" 5 | "path/filepath" 6 | "testing" 7 | ) 8 | 9 | func TestGenerateSSHKey(t *testing.T) { 10 | tmpDir, err := os.MkdirTemp("", "machine-test-") 11 | if err != nil { 12 | t.Fatal(err) 13 | } 14 | 15 | filename := filepath.Join(tmpDir, "sshkey") 16 | 17 | if err := GenerateSSHKey(filename); err != nil { 18 | t.Fatal(err) 19 | } 20 | 21 | if _, err := os.Stat(filename); err != nil { 22 | t.Fatalf("expected ssh key at %s", filename) 23 | } 24 | 25 | // cleanup 26 | _ = os.RemoveAll(tmpDir) 27 | } 28 | -------------------------------------------------------------------------------- /libmachine/ssh/sshtest/fake_client.go: -------------------------------------------------------------------------------- 1 | package sshtest 2 | 3 | import "io" 4 | 5 | type CmdResult struct { 6 | Out string 7 | Err error 8 | } 9 | 10 | type FakeClient struct { 11 | ActivatedShell []string 12 | Outputs map[string]CmdResult 13 | } 14 | 15 | func (fsc *FakeClient) Output(command string) (string, error) { 16 | outerr := fsc.Outputs[command] 17 | return outerr.Out, outerr.Err 18 | } 19 | 20 | func (fsc *FakeClient) Shell(args ...string) error { 21 | fsc.ActivatedShell = args 22 | return nil 23 | } 24 | 25 | func (fsc *FakeClient) Start(command string) (io.ReadCloser, io.ReadCloser, error) { 26 | return nil, nil, nil 27 | } 28 | 29 | func (fsc *FakeClient) Wait() error { 30 | return nil 31 | } 32 | -------------------------------------------------------------------------------- /libmachine/state/state.go: -------------------------------------------------------------------------------- 1 | package state 2 | 3 | // State represents the state of a host 4 | type State int 5 | 6 | const ( 7 | None State = iota 8 | Running 9 | Paused 10 | Saved 11 | Stopped 12 | Stopping 13 | Starting 14 | Error 15 | Timeout 16 | NotFound 17 | ) 18 | 19 | var states = []string{ 20 | "", 21 | "Running", 22 | "Paused", 23 | "Saved", 24 | "Stopped", 25 | "Stopping", 26 | "Starting", 27 | "Error", 28 | "Timeout", 29 | "Not Found", 30 | } 31 | 32 | // Given a State type, returns its string representation 33 | func (s State) String() string { 34 | if int(s) >= 0 && int(s) < len(states) { 35 | return states[s] 36 | } 37 | return "" 38 | } 39 | -------------------------------------------------------------------------------- /libmachine/state/state_test.go: -------------------------------------------------------------------------------- 1 | package state 2 | 3 | import ( 4 | "testing" 5 | ) 6 | 7 | func TestDaemonCreate(t *testing.T) { 8 | if None.String() != "" { 9 | t.Fatal("None state should be empty string") 10 | } 11 | if Running.String() != "Running" { 12 | t.Fatal("Running state should be 'Running'") 13 | } 14 | if Error.String() != "Error" { 15 | t.Fatal("Error state should be 'Error'") 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /libmachine/swarm/swarm.go: -------------------------------------------------------------------------------- 1 | package swarm 2 | 3 | const ( 4 | DiscoveryServiceEndpoint = "https://discovery-stage.hub.docker.com/v1" 5 | ) 6 | 7 | type Options struct { 8 | IsSwarm bool 9 | Address string 10 | Discovery string 11 | Agent bool 12 | Master bool 13 | Host string 14 | Image string 15 | Strategy string 16 | Heartbeat int 17 | Overcommit float64 18 | ArbitraryFlags []string 19 | ArbitraryJoinFlags []string 20 | Env []string 21 | IsExperimental bool 22 | } 23 | -------------------------------------------------------------------------------- /libmachine/util/util.go: -------------------------------------------------------------------------------- 1 | package util 2 | 3 | import ( 4 | "net/http" 5 | "net/url" 6 | "os" 7 | ) 8 | 9 | func FindEnvAny(names ...string) string { 10 | for _, n := range names { 11 | if val := os.Getenv(n); val != "" { 12 | return val 13 | } 14 | } 15 | return "" 16 | } 17 | 18 | // GetProxyURL returns the URL of the proxy to use for this given hostUrl as indicated by the environment variables 19 | // HTTP_PROXY, HTTPS_PROXY and NO_PROXY (or the lowercase versions thereof). 20 | // HTTPS_PROXY takes precedence over HTTP_PROXY for https requests. 21 | // The hostUrl may be either a complete URL or a "host[:port]", in which case the "http" scheme is assumed. 22 | func GetProxyURL(hostUrl string) (*url.URL, error) { 23 | req, err := http.NewRequest(http.MethodGet, hostUrl, nil) 24 | if err != nil { 25 | return nil, err 26 | } 27 | proxy, err := http.ProxyFromEnvironment(req) 28 | if err != nil { 29 | return nil, err 30 | } 31 | return proxy, nil 32 | } 33 | -------------------------------------------------------------------------------- /libmachine/version/version.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | var ( 4 | // APIVersion dictates which version of the libmachine API this is. 5 | APIVersion = 1 6 | 7 | // ConfigVersion dictates which version of the config.json format is 8 | // used. It needs to be bumped if there is a breaking change, and 9 | // therefore migration, introduced to the config file format. 10 | ConfigVersion = 3 11 | ) 12 | -------------------------------------------------------------------------------- /package/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG BCI_VERSION=15.6 2 | 3 | FROM registry.suse.com/bci/bci-busybox:${BCI_VERSION} AS final 4 | FROM registry.suse.com/bci/bci-base:${BCI_VERSION} AS builder 5 | 6 | # Creates the base dir for the target image, and hydrates it with the 7 | # final image's contents. 8 | RUN mkdir /chroot 9 | COPY --from=final / /chroot/ 10 | 11 | RUN zypper --non-interactive refresh && \ 12 | zypper --installroot /chroot -n rm busybox-less && \ 13 | zypper --installroot /chroot -n install \ 14 | git-core curl mkisofs openssh-clients openssl patterns-base-fips && \ 15 | zypper -n clean -a && \ 16 | rm -rf /chroot/tmp/* /chroot/var/tmp/* /chroot/usr/share/doc/packages/* 17 | 18 | RUN useradd -u 1000 machine 19 | RUN cp /etc/passwd /chroot/etc/passwd 20 | 21 | COPY download_driver.sh /chroot/usr/local/bin/ 22 | RUN chmod +x /chroot/usr/local/bin/download_driver.sh 23 | 24 | COPY rancher-machine entrypoint.sh /chroot/usr/local/bin/ 25 | RUN chmod 0755 /chroot/usr/local/bin 26 | 27 | FROM scratch 28 | 29 | ENV SSL_CERT_DIR /etc/rancher/ssl 30 | 31 | COPY --from=builder /chroot / 32 | 33 | RUN mkdir -p .docker/machine/machines /etc/rancher/ssl /home/machine && \ 34 | chown -R machine /etc/rancher/ssl && \ 35 | chown -R machine /home/machine && \ 36 | chown machine /usr/local/bin 37 | 38 | USER 1000 39 | WORKDIR /home/machine 40 | 41 | ENTRYPOINT ["entrypoint.sh"] 42 | -------------------------------------------------------------------------------- /package/download_driver.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | driver_prefix=docker-machine-driver- 4 | 5 | if [ -n "${SSL_CERT_DIR}" ]; then 6 | ln -s /etc/ssl/certs/* "${SSL_CERT_DIR}" 7 | fi 8 | 9 | if [ -x "$(command -v c_rehash)" ]; then 10 | # c_rehash is run here instead of update-ca-certificates because the latter requires root privileges 11 | # and the rancher-machine container is run as non-root user. 12 | c_rehash 13 | fi 14 | 15 | curl -sLO "$1" || echo "Curl failed with error code $?" 16 | driver_file=$(ls $driver_prefix*) 17 | driver_name=$(echo "$driver_file" | sed -e "s/^$driver_prefix//" -e "s/[-_\.].*$//") 18 | driver_path=driver_dir/$driver_prefix$driver_name 19 | 20 | # Verify the hash of the driver, if it is provided 21 | if [ "$2" ]; then 22 | if [ "$2 $driver_file" != "$(sha256sum $driver_file)" ]; then 23 | echo "downloaded file $driver_file failed sha256 checksum" 24 | exit 1 25 | fi 26 | fi 27 | 28 | mkdir driver_dir 29 | 30 | file_type_output=$(file "$driver_file") 31 | echo $driver_file 32 | echo $file_type_output 33 | if echo "$file_type_output" | grep -q "ELF"; then 34 | driver_path=$driver_file 35 | elif echo "$file_type_output" | grep -q "Zip archive"; then 36 | if ! unzip -qq -d driver_dir "$driver_file" 2>/dev/null; then 37 | echo "could not unzip $driver_name" 38 | exit 1 39 | fi 40 | elif echo "$file_type_output" | grep -q "gzip compressed"; then 41 | if ! tar zxf "$driver_file" -C driver_dir 2>/dev/null; then 42 | echo "could not inflate $driver_name" 43 | exit 1 44 | fi 45 | else 46 | echo "driver file does not seem to be in an accepted format" 47 | exit 1 48 | fi 49 | 50 | chmod +x $driver_path 51 | mv $driver_path /usr/local/bin/$driver_prefix$driver_name 52 | rm -rf driver_dir $driver_file $driver_path 53 | -------------------------------------------------------------------------------- /package/entrypoint.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | termination_log="/dev/termination-log" 4 | 5 | for i; do 6 | shift 7 | case $i in 8 | --driver-download-url=*) 9 | driver_url=${i##--driver-download-url=} 10 | ;; 11 | --driver-hash=*) 12 | driver_hash=${i##--driver-hash=} 13 | ;; 14 | *) 15 | # Breaking up arguments that are passed to rancher-machine allows for handling values with spaces. 16 | flag=${i%%=*} 17 | value=${i#--*=} 18 | if [ "$flag" = "$value" ]; then 19 | # If flag and value are the same, then the argument that was passed doesn't have an equal-sign and can be passed as-is. 20 | set -- "$@" "$flag" 21 | else 22 | set -- "$@" "$flag" "$value" 23 | fi 24 | ;; 25 | esac 26 | done 27 | 28 | if [ -n "$driver_url" ]; then 29 | echo "Downloading driver from $driver_url" | tee -a $termination_log 30 | if [ -z "$driver_hash" ]; then 31 | echo "driver-hash not provided, will not verify after download" | tee -a $termination_log 32 | fi 33 | if ! { { { { download_driver.sh "$driver_url" "$driver_hash" 2>&1; echo $? >&3; } | tee -a $termination_log >&4; } 3>&1; } | { read xs; exit $xs; } } 4>&1; then 34 | echo "download of driver from $driver_url failed" | tee -a $termination_log 35 | exit 1 36 | fi 37 | fi 38 | 39 | { { { { rancher-machine "$@" 2>&1; echo $? >&3; } | tee -a $termination_log >&4; } 3>&1; } | { read xs; exit $xs; } } 4>&1 40 | -------------------------------------------------------------------------------- /scripts/build: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -ex 3 | 4 | cd $(dirname $0)/.. 5 | 6 | . ./scripts/version 7 | 8 | mkdir -p bin 9 | 10 | if [ "$CROSS" = 1 ]; then 11 | CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags "-X main.VERSION=$VERSION" -o ./bin/rancher-machine-Darwin-x86_64 ./cmd/rancher-machine 12 | CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags "-X main.VERSION=$VERSION" -o ./bin/rancher-machine-Darwin-arm64 ./cmd/rancher-machine 13 | CGO_ENABLED=0 GOOS=windows go build -ldflags "-X main.VERSION=$VERSION" -o ./bin/rancher-machine-Windows-x86_64.exe ./cmd/rancher-machine 14 | CGO_ENABLED=0 GOARCH=arm64 go build -a -tags netgo -installsuffix netgo -ldflags "-X main.VERSION=$VERSION" -o ./bin/rancher-machine-Linux-arm64 ./cmd/rancher-machine 15 | CGO_ENABLED=0 GOARCH=ppc64le go build -a -tags netgo -installsuffix netgo -ldflags "-X main.VERSION=$VERSION" -o ./bin/rancher-machine-Linux-ppc64le ./cmd/rancher-machine 16 | CGO_ENABLED=0 GOARCH=s390x go build -a -tags netgo -installsuffix netgo -ldflags "-X main.VERSION=$VERSION" -o ./bin/rancher-machine-Linux-s390x ./cmd/rancher-machine 17 | CGO_ENABLED=0 GOARCH=arm GOARM=6 go build -a -tags netgo -installsuffix netgo -ldflags "-X main.VERSION=$VERSION" -o ./bin/rancher-machine-Linux-arm ./cmd/rancher-machine 18 | CGO_ENABLED=0 GOARCH=arm GOARM=6 go build -a -tags netgo -installsuffix netgo -ldflags "-X main.VERSION=$VERSION" -o ./bin/rancher-machine-Linux-armv6l ./cmd/rancher-machine 19 | CGO_ENABLED=0 GOARCH=arm GOARM=7 go build -a -tags netgo -installsuffix netgo -ldflags "-X main.VERSION=$VERSION" -o ./bin/rancher-machine-Linux-armv7l ./cmd/rancher-machine 20 | CGO_ENABLED=0 GOARCH=amd64 go build -a -tags netgo -installsuffix netgo -ldflags "-X main.VERSION=$VERSION -extldflags '-static -s'" -o ./bin/rancher-machine-Linux-x86_64 ./cmd/rancher-machine 21 | cp ./bin/rancher-machine-Linux-arm64 ./bin/rancher-machine-Linux-aarch64 22 | 23 | cp ./bin/rancher-machine-$(uname -s)-$(uname -m) ./bin/rancher-machine 24 | echo Built ./bin/rancher-machine-$(uname -s)-$(uname -m) 25 | else 26 | CGO_ENABLED=0 go build -a -tags netgo -installsuffix netgo \ 27 | -ldflags "-w -s -extldflags '-static' 28 | -X github.com/rancher/machine/version.Version=$VERSION 29 | -X github.com/rancher/machine/version.GitCommit=$COMMIT" \ 30 | -o ./bin/rancher-machine ./cmd/rancher-machine 31 | # Ignore the exec format error as the binary might be for a different arch from the action runner's 32 | ./bin/rancher-machine --version || true 33 | fi 34 | -------------------------------------------------------------------------------- /scripts/ci: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -e 3 | 4 | cd $(dirname $0) 5 | 6 | ./clean 7 | # ./validate 8 | ./build 9 | ./test 10 | ./package 11 | -------------------------------------------------------------------------------- /scripts/clean: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | cd $(dirname $0)/.. 6 | BASEDIR=$(pwd) 7 | 8 | rm -rf $BASEDIR/build 9 | rm -rf $BASEDIR/dist 10 | 11 | -------------------------------------------------------------------------------- /scripts/entry: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | trap "chown -R $DAPPER_UID:$DAPPER_GID ." exit 5 | 6 | mkdir -p bin dist 7 | if [ -e ./scripts/$1 ]; then 8 | ./scripts/"$@" 9 | else 10 | exec "$@" 11 | fi 12 | -------------------------------------------------------------------------------- /scripts/package: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | source $(dirname $0)/version 6 | 7 | if [ -z "$ARCH" ]; then 8 | ARCH=amd64 9 | fi 10 | 11 | SUFFIX="" 12 | [ "${ARCH}" != "amd64" ] && SUFFIX="_${ARCH}" 13 | 14 | TAG=${TAG:-${VERSION}${SUFFIX}} 15 | REPO=${REPO:-rancher} 16 | IMAGE=${REPO}/machine:${TAG} 17 | 18 | cd $(dirname $0)/.. 19 | BASEDIR=$(pwd) 20 | mkdir -p $BASEDIR/dist/artifacts 21 | 22 | cd $BASEDIR/bin 23 | tar -cvzf $BASEDIR/dist/artifacts/rancher-machine-${ARCH}.tar.gz rancher-machine 24 | cd $BASEDIR 25 | 26 | cp ./bin/rancher-machine ./package 27 | 28 | # Uncomment the following line if you would like to build the Docker image when using Make and Dapper for developing/testing 29 | # docker build -f package/Dockerfile -t ${IMAGE} package 30 | -------------------------------------------------------------------------------- /scripts/release: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | exec $(dirname $0)/ci 4 | -------------------------------------------------------------------------------- /scripts/test: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | cd $(dirname $0)/.. 6 | 7 | # Run driver tests. 8 | go test -cover ./drivers/... 9 | 10 | # Compile the rancher-machine binary because it is used by the tests in cmd/rancher-machine. 11 | cd ./cmd/rancher-machine 12 | go build . 13 | 14 | # Run CLI tests. We're using -v here to ensure that command output isn't swallowed by go test 15 | # if there are test failures. 16 | go test -v ./... 17 | -------------------------------------------------------------------------------- /scripts/validate: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | cd $(dirname $0)/.. 5 | 6 | echo Running validation 7 | 8 | PACKAGES="$(go list ./...)" 9 | 10 | if ! command -v golangci-lint; then 11 | echo Skipping validation: no golangci-lint available 12 | exit 13 | fi 14 | 15 | echo Running validation 16 | 17 | echo Running: golangci-lint 18 | golangci-lint run --deadline 10m 19 | 20 | echo Running: go fmt 21 | test -z "$(go fmt ${PACKAGES} | tee /dev/stderr)" 22 | -------------------------------------------------------------------------------- /scripts/version: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | COMMIT=$(git rev-parse --short HEAD) 3 | VERSION=${DRONE_TAG:-$(git tag -l --contains HEAD | head -n 1)} 4 | if [ -z "$VERSION" ]; then 5 | VERSION=$(git rev-parse --short HEAD) 6 | if [ -n "$(git status --porcelain --untracked-files=no)" ]; then 7 | VERSION="$VERSION-dirty" 8 | fi 9 | fi 10 | -------------------------------------------------------------------------------- /test/integration/.gitignore: -------------------------------------------------------------------------------- 1 | env-* 2 | -------------------------------------------------------------------------------- /test/integration/amazonec2/amazon.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load ${BASE_TEST_DIR}/helpers.bash 4 | 5 | only_if_env DRIVER amazonec2 6 | 7 | use_disposable_machine 8 | 9 | require_env AWS_ACCESS_KEY_ID 10 | require_env AWS_SECRET_ACCESS_KEY 11 | 12 | @test "$DRIVER: Should Create a default host" { 13 | run machine create -d amazonec2 $NAME 14 | echo ${output} 15 | [ "$status" -eq 0 ] 16 | } 17 | -------------------------------------------------------------------------------- /test/integration/amazonec2/create-ebsinstance.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load ${BASE_TEST_DIR}/helpers.bash 4 | 5 | only_if_env DRIVER amazonec2 6 | 7 | use_disposable_machine 8 | 9 | require_env AWS_ACCESS_KEY_ID 10 | require_env AWS_SECRET_ACCESS_KEY 11 | require_env AWS_VPC_ID 12 | 13 | require_env AWS_DEFAULT_REGION 14 | require_env AWS_ZONE 15 | 16 | @test "$DRIVER: Should Create an EBS Optimized Instance" { 17 | #Use Instance Type that supports EBS Optimize 18 | run machine create -d amazonec2 --amazonec2-instance-type=m4.large --amazonec2-use-ebs-optimized-instance $NAME 19 | echo ${output} 20 | [ "$status" -eq 0 ] 21 | } 22 | 23 | @test "$DRIVER: Check the machine is up" { 24 | run docker $(machine config $NAME) run --rm -e AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID -e AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY -e AWS_DEFAULT_REGION=$AWS_DEFAULT_REGION -e AWS_ZONE=$AWS_ZONE -e AWS_VPC_ID=$AWS_VPC_ID blendle/aws-cli ec2 describe-instances --filters Name=tag:Name,Values=$NAME Name=instance-state-name,Values=running --query 'Reservations[0].Instances[0].EbsOptimized' --output text 25 | echo ${output} 26 | [[ ${lines[*]:-1} =~ "True" ]] 27 | } -------------------------------------------------------------------------------- /test/integration/amazonec2/createwithkeypair.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load ${BASE_TEST_DIR}/helpers.bash 4 | 5 | only_if_env DRIVER amazonec2 6 | 7 | use_disposable_machine 8 | 9 | require_env AWS_ACCESS_KEY_ID 10 | 11 | require_env AWS_SECRET_ACCESS_KEY 12 | 13 | export AWS_SSH_DIR="$MACHINE_STORAGE_PATH/mcnkeys" 14 | 15 | export AWS_SSH_KEYPATH=$AWS_SSH_DIR/id_rsa 16 | 17 | @test "$DRIVER: Should Create Instance with Pre existing SSH Key" { 18 | 19 | mkdir -p $AWS_SSH_DIR 20 | 21 | run ssh-keygen -f $AWS_SSH_KEYPATH -t rsa -N '' 22 | 23 | machine create -d amazonec2 $NAME 24 | 25 | run diff $AWS_SSH_KEYPATH $MACHINE_STORAGE_PATH/machines/$NAME/id_rsa 26 | [[ $output == "" ]] 27 | 28 | run diff $AWS_SSH_KEYPATH.pub $MACHINE_STORAGE_PATH/machines/$NAME/id_rsa.pub 29 | [[ $output == "" ]] 30 | 31 | 32 | } -------------------------------------------------------------------------------- /test/integration/core/certs-extra-san.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load ${BASE_TEST_DIR}/helpers.bash 4 | 5 | use_disposable_machine 6 | 7 | @test "$DRIVER: create" { 8 | run machine create --tls-san foo.bar.tld --tls-san 10.42.42.42 -d $DRIVER $NAME 9 | echo ${output} 10 | [ "$status" -eq 0 ] 11 | } 12 | 13 | @test "$DRIVER: verify that server cert contains the extra SANs" { 14 | machine ssh $NAME -- openssl x509 -in /var/lib/boot2docker/server.pem -text | grep 'DNS:foo.bar.tld' 15 | machine ssh $NAME -- openssl x509 -in /var/lib/boot2docker/server.pem -text | grep 'IP Address:10.42.42.42' 16 | } 17 | 18 | @test "$DRIVER: verify that server cert SANs are still there after 'regenerate-certs'" { 19 | machine regenerate-certs -f $NAME 20 | machine ssh $NAME -- openssl x509 -in /var/lib/boot2docker/server.pem -text | grep 'DNS:foo.bar.tld' 21 | machine ssh $NAME -- openssl x509 -in /var/lib/boot2docker/server.pem -text | grep 'IP Address:10.42.42.42' 22 | } 23 | -------------------------------------------------------------------------------- /test/integration/core/crashreport.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load ${BASE_TEST_DIR}/helpers.bash 4 | 5 | only_if_env DRIVER virtualbox 6 | 7 | use_disposable_machine 8 | 9 | @test "$DRIVER: should send bugsnag report" { 10 | # we exploit a 'bug' where vboxmanage wont allow a machine created with 1mb of RAM 11 | run machine --bugsnag-api-token nonexisting -D create -d virtualbox --virtualbox-memory 1 $NAME 12 | echo ${output} 13 | [ "$status" -eq 1 ] 14 | [[ ${output} == *"notifying bugsnag"* ]] 15 | } 16 | -------------------------------------------------------------------------------- /test/integration/core/engine-options.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load ${BASE_TEST_DIR}/helpers.bash 4 | 5 | use_disposable_machine 6 | 7 | @test "$DRIVER: create with supported engine options" { 8 | run machine create -d $DRIVER \ 9 | --engine-label spam=eggs \ 10 | --engine-storage-driver overlay \ 11 | --engine-insecure-registry registry.myco.com \ 12 | --engine-env=TEST=VALUE \ 13 | --engine-opt log-driver=none \ 14 | $NAME 15 | echo "$output" 16 | [ $status -eq 0 ] 17 | } 18 | 19 | @test "$DRIVER: check for engine label" { 20 | spamlabel=$(docker $(machine config $NAME) info | grep spam) 21 | [[ $spamlabel =~ "spam=eggs" ]] 22 | } 23 | 24 | @test "$DRIVER: check for engine storage driver" { 25 | storage_driver_info=$(docker $(machine config $NAME) info | grep "Storage Driver") 26 | [[ $storage_driver_info =~ "overlay" ]] 27 | } 28 | 29 | @test "$DRIVER: test docker process envs" { 30 | # get pid of docker process, check process envs for set Environment Variable from above test 31 | run machine ssh $NAME 'sudo cat /proc/$(pgrep -f "docker [d]aemon")/environ' 32 | echo ${output} 33 | [ $status -eq 0 ] 34 | [[ "${output}" =~ "TEST=VALUE" ]] 35 | } 36 | 37 | @test "$DRIVER: check created engine option (log driver)" { 38 | docker $(machine config $NAME) run --name nolog busybox echo this should not be logged 39 | run docker $(machine config $NAME) inspect -f '{{.HostConfig.LogConfig.Type}}' nolog 40 | echo ${output} 41 | [ ${output} == "none" ] 42 | } 43 | -------------------------------------------------------------------------------- /test/integration/core/inspect_format.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load ${BASE_TEST_DIR}/helpers.bash 4 | 5 | use_shared_machine 6 | 7 | @test "$DRIVER: inspect format template" { 8 | run machine inspect -f '{{.DriverName}}' $NAME 9 | [[ "$output" == "$DRIVER" ]] 10 | } 11 | 12 | @test "$DRIVER: inspect format template json directive" { 13 | run machine inspect -f '{{json .DriverName}}' $NAME 14 | [[ "$output" == "\"$DRIVER\"" ]] 15 | } 16 | 17 | @test "$DRIVER: inspect format template pretty json directive" { 18 | linecount=$(machine inspect -f '{{prettyjson .Driver}}' $NAME | wc -l) 19 | [[ "$linecount" -gt 1 ]] 20 | } 21 | 22 | @test "$DRIVER: check .Driver output is not flawed" { 23 | only_if_env DRIVER virtualbox 24 | run machine inspect -f '{{.Driver.SSHUser}}' $NAME 25 | [ "$status" -eq 0 ] 26 | [[ ${output} == "docker" ]] 27 | } 28 | -------------------------------------------------------------------------------- /test/integration/core/regenerate-certs.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load ${BASE_TEST_DIR}/helpers.bash 4 | 5 | use_shared_machine 6 | 7 | @test "$DRIVER: regenerate the certs" { 8 | run machine regenerate-certs -f $NAME 9 | [[ ${status} -eq 0 ]] 10 | } 11 | 12 | @test "$DRIVER: make sure docker still works" { 13 | run docker $(machine config $NAME) version 14 | [[ ${status} -eq 0 ]] 15 | } 16 | -------------------------------------------------------------------------------- /test/integration/core/scp.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load ${BASE_TEST_DIR}/helpers.bash 4 | 5 | use_shared_machine 6 | export SECOND_MACHINE="$NAME-2" 7 | 8 | @test "$DRIVER: test machine scp command from remote to host" { 9 | machine ssh $NAME 'echo A file created remotely! >/tmp/foo.txt' 10 | machine scp $NAME:/tmp/foo.txt . 11 | [[ $(cat foo.txt) == "A file created remotely!" ]] 12 | } 13 | 14 | @test "$DRIVER: test machine scp command from host to remote" { 15 | teardown () { 16 | rm foo.txt 17 | } 18 | echo A file created locally! >foo.txt 19 | machine scp foo.txt $NAME:/tmp/foo.txt 20 | [[ $(machine ssh $NAME cat /tmp/foo.txt) == "A file created locally!" ]] 21 | } 22 | 23 | @test "$DRIVER: create machine to test transferring files from machine to machine" { 24 | run machine create -d $DRIVER $SECOND_MACHINE 25 | [[ ${status} -eq 0 ]] 26 | } 27 | 28 | @test "$DRIVER: scp from one machine to another" { 29 | run machine ssh $NAME 'echo A file hopping around! >/tmp/foo.txt' 30 | run machine scp $NAME:/tmp/foo.txt $SECOND_MACHINE:/tmp/foo.txt 31 | [[ $(machine ssh ${SECOND_MACHINE} cat /tmp/foo.txt) == "A file hopping around!" ]] 32 | } 33 | -------------------------------------------------------------------------------- /test/integration/core/ssh-backends.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load ${BASE_TEST_DIR}/helpers.bash 4 | 5 | use_shared_machine 6 | 7 | @test "$DRIVER: test external ssh backend" { 8 | run machine ssh $NAME df -h 9 | [[ "$status" -eq 0 ]] 10 | } 11 | 12 | @test "$DRIVER: test command did what it purported to -- external ssh" { 13 | run machine ssh $NAME echo foo 14 | [[ "$output" == "foo" ]] 15 | } 16 | 17 | @test "$DRIVER: test native ssh backend" { 18 | run machine --native-ssh ssh $NAME df -h 19 | [[ "$status" -eq 0 ]] 20 | } 21 | 22 | @test "$DRIVER: test command did what it purported to -- native ssh" { 23 | run machine --native-ssh ssh $NAME echo foo 24 | [[ "$output" =~ "foo" ]] 25 | } 26 | 27 | @test "$DRIVER: ensure that ssh extra arguments work" { 28 | # don't run this test if we can't use external SSH 29 | which ssh 30 | if [[ $? -ne 0 ]]; then 31 | skip 32 | fi 33 | 34 | # this will not fare well if -C doesn't get interpreted as "use compression" 35 | # like intended 36 | run machine ssh $NAME -C echo foo 37 | 38 | [[ "$status" -eq 0 ]] 39 | [[ "$output" == "foo" ]] 40 | } 41 | -------------------------------------------------------------------------------- /test/integration/core/swarm-options.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load ${BASE_TEST_DIR}/helpers.bash 4 | 5 | export TOKEN=$(curl -sS -X POST "https://discovery.hub.docker.com/v1/clusters") 6 | 7 | @test "create swarm master" { 8 | run machine create -d $DRIVER --swarm --swarm-master --swarm-discovery "token://$TOKEN" --swarm-strategy binpack --swarm-opt heartbeat=5s queenbee 9 | echo ${output} 10 | [[ "$status" -eq 0 ]] 11 | } 12 | 13 | @test "create swarm node" { 14 | run machine create -d $DRIVER --swarm --swarm-discovery "token://$TOKEN" workerbee 15 | [[ "$status" -eq 0 ]] 16 | } 17 | 18 | @test "ensure strategy is correct" { 19 | strategy=$(docker $(machine config --swarm queenbee) info | grep "Strategy:" | awk '{ print $2 }') 20 | echo ${strategy} 21 | [[ "$strategy" == "binpack" ]] 22 | } 23 | 24 | @test "ensure heartbeat" { 25 | heartbeat_arg=$(docker $(machine config queenbee) inspect -f '{{index .Args}}' swarm-agent-master) 26 | echo ${heartbeat_arg} 27 | [[ "$heartbeat_arg" =~ "--heartbeat=5s" ]] 28 | } 29 | 30 | @test "ls command should not show as swarm active if normal active" { 31 | eval $(machine env queenbee) 32 | run machine ls --filter name=queenbee 33 | [[ ${lines[1]} != *"* (swarm)"* ]] 34 | } 35 | 36 | @test "ls command should show as swarm active" { 37 | eval $(machine env --swarm queenbee) 38 | run machine ls --filter name=queenbee 39 | echo ${output} 40 | [[ ${lines[1]} == *"* (swarm)"* ]] 41 | } 42 | 43 | @test "active command should show the host as active if normal active" { 44 | eval $(machine env queenbee) 45 | run machine active 46 | echo ${output} 47 | [[ ${lines[0]} == "queenbee" ]] 48 | } 49 | 50 | @test "active command should show the host as active if swarm active" { 51 | eval $(machine env --swarm queenbee) 52 | run machine active 53 | echo ${output} 54 | [[ ${lines[0]} == "queenbee" ]] 55 | } 56 | -------------------------------------------------------------------------------- /test/integration/helpers.bash: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | function echo_to_log { 4 | echo "$BATS_TEST_NAME 5 | ---------- 6 | $output 7 | ---------- 8 | 9 | " >> ${BATS_LOG} 10 | } 11 | 12 | function teardown { 13 | echo_to_log 14 | } 15 | 16 | function errecho { 17 | >&2 echo "$@" 18 | } 19 | 20 | function only_if_env { 21 | if [[ ${!1} != "$2" ]]; then 22 | errecho "This test requires the $1 environment variable to be set to $2. Skipping..." 23 | skip 24 | fi 25 | } 26 | 27 | function require_env { 28 | if [[ -z ${!1} ]]; then 29 | errecho "This test requires the $1 environment variable to be set in order to run." 30 | exit 1 31 | fi 32 | } 33 | 34 | function use_disposable_machine { 35 | if [[ -z "$NAME" ]]; then 36 | export NAME="bats-$DRIVER-test-$(date +%s)" 37 | fi 38 | } 39 | 40 | function use_shared_machine { 41 | if [[ -z "$NAME" ]]; then 42 | export NAME="$SHARED_NAME" 43 | if [[ $(machine ls -q --filter name=$NAME | wc -l) -eq 0 ]]; then 44 | machine create -d $DRIVER $NAME &>/dev/null 45 | fi 46 | fi 47 | } 48 | 49 | # Make sure these aren't set while tests run (can cause confusing behavior) 50 | unset DOCKER_HOST DOCKER_TLS_VERIFY DOCKER_CERT_DIR 51 | -------------------------------------------------------------------------------- /test/integration/virtualbox/certs-checksum.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load ${BASE_TEST_DIR}/helpers.bash 4 | 5 | only_if_env DRIVER virtualbox 6 | 7 | use_shared_machine 8 | 9 | @test "$DRIVER: verify that server cert checksum matches local checksum" { 10 | # TODO: This test is tightly coupled to VirtualBox right now, but should be 11 | # available for all providers ideally. 12 | # 13 | # TODO: Does this test work OK on Linux? cc @ehazlett 14 | # 15 | # Have to create this directory and file or else the OpenSSL checksum will barf. 16 | machine ssh $NAME -- sudo mkdir -p /usr/local/ssl 17 | machine ssh $NAME -- sudo touch /usr/local/ssl/openssl.cnf 18 | 19 | SERVER_CHECKSUM=$(machine ssh $NAME -- openssl dgst -sha256 /var/lib/boot2docker/ca.pem | awk '{ print $2 }') 20 | LOCAL_CHECKSUM=$(openssl dgst -sha256 $MACHINE_STORAGE_PATH/certs/ca.pem | awk '{ print $2 }') 21 | echo ${SERVER_CHECKSUM} 22 | echo ${LOCAL_CHECKSUM} 23 | [[ ${SERVER_CHECKSUM} == ${LOCAL_CHECKSUM} ]] 24 | } 25 | -------------------------------------------------------------------------------- /test/integration/virtualbox/create-with-upgrading.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load ${BASE_TEST_DIR}/helpers.bash 4 | 5 | only_if_env DRIVER virtualbox 6 | 7 | use_disposable_machine 8 | 9 | export CACHE_DIR="$MACHINE_STORAGE_PATH/cache" 10 | export ISO_PATH="$CACHE_DIR/boot2docker.iso" 11 | export OLD_ISO_URL="https://github.com/boot2docker/boot2docker/releases/download/v1.4.1/boot2docker.iso" 12 | 13 | @test "$DRIVER: download the old version iso" { 14 | run mkdir -p $CACHE_DIR 15 | run curl $OLD_ISO_URL -L -o $ISO_PATH 16 | echo ${output} 17 | [ "$status" -eq 0 ] 18 | } 19 | 20 | @test "$DRIVER: create with upgrading" { 21 | run machine create -d $DRIVER $NAME 22 | echo ${output} 23 | [ "$status" -eq 0 ] 24 | } 25 | 26 | @test "$DRIVER: create is correct version" { 27 | SERVER_VERSION=$(docker $(machine config $NAME) version | grep 'Server version' | awk '{ print $3; }') 28 | [[ "$SERVER_VERSION" != "1.4.1" ]] 29 | } 30 | -------------------------------------------------------------------------------- /test/integration/virtualbox/custom-mem-disk.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load ${BASE_TEST_DIR}/helpers.bash 4 | 5 | only_if_env DRIVER virtualbox 6 | 7 | use_disposable_machine 8 | 9 | # Default memsize is 1024MB and disksize is 20000MB 10 | # These values are defined in drivers/virtualbox/virtualbox.go 11 | export DEFAULT_MEMSIZE=1024 12 | export DEFAULT_DISKSIZE=20000 13 | export CUSTOM_MEMSIZE=1536 14 | export CUSTOM_DISKSIZE=10000 15 | export CUSTOM_CPUCOUNT=1 16 | 17 | function findDiskSize() { 18 | # SATA-0-0 is usually the boot2disk.iso image 19 | # We assume that SATA 1-0 is root disk VMDK and grab this UUID 20 | # e.g. "SATA-ImageUUID-1-0"="fb5f33a7-e4e3-4cb9-877c-f9415ae2adea" 21 | # TODO(slashk): does this work on Windows ? 22 | run bash -c "VBoxManage showvminfo --machinereadable $NAME | grep SATA-ImageUUID-1-0 | cut -d'=' -f2" 23 | run bash -c "VBoxManage showhdinfo $output | grep "Capacity:" | awk -F' ' '{ print $2 }'" 24 | } 25 | 26 | function findMemorySize() { 27 | run bash -c "VBoxManage showvminfo --machinereadable $NAME | grep memory= | cut -d'=' -f2" 28 | } 29 | 30 | function findCPUCount() { 31 | run bash -c "VBoxManage showvminfo --machinereadable $NAME | grep cpus= | cut -d'=' -f2" 32 | } 33 | 34 | @test "$DRIVER: create with custom disk, cpu count and memory size flags" { 35 | run machine create -d $DRIVER --virtualbox-cpu-count $CUSTOM_CPUCOUNT --virtualbox-disk-size $CUSTOM_DISKSIZE --virtualbox-memory $CUSTOM_MEMSIZE $NAME 36 | [ "$status" -eq 0 ] 37 | } 38 | 39 | @test "$DRIVER: check custom machine memory size" { 40 | findMemorySize 41 | [[ ${output} == "$CUSTOM_MEMSIZE" ]] 42 | } 43 | 44 | @test "$DRIVER: check custom machine disksize" { 45 | findDiskSize 46 | [[ ${output} == *"$CUSTOM_DISKSIZE"* ]] 47 | } 48 | 49 | @test "$DRIVER: check custom machine cpucount" { 50 | findCPUCount 51 | [[ ${output} == "$CUSTOM_CPUCOUNT" ]] 52 | } 53 | 54 | @test "$DRIVER: machine should show running after create" { 55 | run machine ls 56 | [ "$status" -eq 0 ] 57 | [[ ${lines[1]} == *"Running"* ]] 58 | } 59 | -------------------------------------------------------------------------------- /test/integration/virtualbox/dns.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load ${BASE_TEST_DIR}/helpers.bash 4 | 5 | only_if_env DRIVER virtualbox 6 | 7 | use_disposable_machine 8 | 9 | @test "$DRIVER: Create a vm with a dns proxy set" { 10 | run machine create -d $DRIVER --virtualbox-dns-proxy=true $NAME 11 | [[ ${status} -eq 0 ]] 12 | } 13 | 14 | @test "$DRIVER: Check DNSProxy flag is properly set during machine creation" { 15 | run bash -c "cat ${MACHINE_STORAGE_PATH}/machines/$NAME/$NAME/Logs/VBox.log | grep DNSProxy | grep '(1)'" 16 | [[ ${status} -eq 0 ]] 17 | } -------------------------------------------------------------------------------- /test/integration/virtualbox/guards.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load ${BASE_TEST_DIR}/helpers.bash 4 | 5 | only_if_env DRIVER virtualbox 6 | 7 | use_disposable_machine 8 | 9 | @test "$DRIVER: Should not allow machine creation with bad ISO" { 10 | run machine create -d virtualbox --virtualbox-boot2docker-url http://dev.null:9111/bad.iso $NAME 11 | [[ ${status} -eq 1 ]] 12 | } 13 | 14 | @test "$DRIVER: Should not allow machine creation with engine-install-url" { 15 | run machine create --engine-install-url https://test.docker.com -d virtualbox $NAME 16 | [[ ${output} == *"--engine-install-url cannot be used"* ]] 17 | } -------------------------------------------------------------------------------- /test/integration/virtualbox/pause-save-start.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load ${BASE_TEST_DIR}/helpers.bash 4 | 5 | only_if_env DRIVER virtualbox 6 | 7 | use_shared_machine 8 | 9 | @test "$DRIVER: VBoxManage pause" { 10 | run VBoxManage controlvm $NAME pause 11 | [ "$status" -eq 0 ] 12 | } 13 | 14 | @test "$DRIVER: machine should show paused after VBoxManage pause" { 15 | run machine ls 16 | [ "$status" -eq 0 ] 17 | [[ ${lines[1]} == *"Paused"* ]] 18 | } 19 | 20 | @test "$DRIVER: start after paused" { 21 | run machine start $NAME 22 | [ "$status" -eq 0 ] 23 | } 24 | 25 | @test "$DRIVER: machine should show running after start" { 26 | run machine ls 27 | [ "$status" -eq 0 ] 28 | [[ ${lines[1]} == *"Running"* ]] 29 | } 30 | 31 | @test "$DRIVER: VBoxManage savestate" { 32 | run VBoxManage controlvm $NAME savestate 33 | [ "$status" -eq 0 ] 34 | } 35 | 36 | @test "$DRIVER: machine should show saved after VBoxManage savestate" { 37 | run machine ls 38 | [ "$status" -eq 0 ] 39 | [[ ${lines[1]} == *"$NAME"* ]] 40 | [[ ${lines[1]} == *"Saved"* ]] 41 | } 42 | 43 | @test "$DRIVER: start after saved" { 44 | run machine start $NAME 45 | [ "$status" -eq 0 ] 46 | } 47 | 48 | @test "$DRIVER: machine should show running after start" { 49 | run machine ls 50 | [ "$status" -eq 0 ] 51 | [[ ${lines[1]} == *"Running"* ]] 52 | } 53 | -------------------------------------------------------------------------------- /test/integration/virtualbox/upgrade.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load ${BASE_TEST_DIR}/helpers.bash 4 | 5 | only_if_env DRIVER virtualbox 6 | 7 | use_disposable_machine 8 | 9 | export OLD_ISO_URL="https://github.com/boot2docker/boot2docker/releases/download/v1.4.1/boot2docker.iso" 10 | 11 | @test "$DRIVER: create for upgrade" { 12 | run machine create -d virtualbox --virtualbox-boot2docker-url $OLD_ISO_URL $NAME 13 | } 14 | 15 | @test "$DRIVER: verify that docker version is old" { 16 | # Have to run this over SSH due to client/server mismatch restriction 17 | SERVER_VERSION=$(machine ssh $NAME docker version | grep 'Server version' | awk '{ print $3; }') 18 | [[ "$SERVER_VERSION" == "1.4.1" ]] 19 | } 20 | 21 | @test "$DRIVER: upgrade" { 22 | run machine upgrade $NAME 23 | echo ${output} 24 | [ "$status" -eq 0 ] 25 | } 26 | 27 | @test "$DRIVER: upgrade is correct version" { 28 | SERVER_VERSION=$(docker $(machine config $NAME) version | grep 'Server version' | awk '{ print $3; }') 29 | [[ "$SERVER_VERSION" != "1.4.1" ]] 30 | } 31 | -------------------------------------------------------------------------------- /test/provision/rancheros.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load ${BASE_TEST_DIR}/helpers.bash 4 | 5 | 6 | # this should move to the makefile 7 | 8 | if [[ "$DRIVER" != "virtualbox" ]]; then 9 | exit 0 10 | fi 11 | 12 | export RANCHEROS_VERSION="v1.3.0" 13 | export RANCHEROS_ISO="https://releases.rancher.com/os/$RANCHEROS_VERSION/rancheros.iso" 14 | 15 | @test "$DRIVER: create with RancherOS ISO" { 16 | VIRTUALBOX_BOOT2DOCKER_URL="$RANCHEROS_ISO" run ${BASE_TEST_DIR}/run-bats.sh ${BASE_TEST_DIR}/core 17 | echo ${output} 18 | [ ${status} -eq 0 ] 19 | } 20 | -------------------------------------------------------------------------------- /test/provision/redhat.bats: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bats 2 | 3 | load ${BASE_TEST_DIR}/helpers.bash 4 | 5 | # this should move to the makefile 6 | 7 | if [[ "$DRIVER" != "amazonec2" ]]; then 8 | exit 0 9 | fi 10 | 11 | require_env AWS_VPC_ID 12 | require_env AWS_ACCESS_KEY_ID 13 | require_env AWS_SECRET_ACCESS_KEY 14 | 15 | @test "$DRIVER: create using RedHat AMI" { 16 | # Oh snap, recursive stuff!! 17 | AWS_AMI=ami-12663b7a AWS_SSH_USER=ec2-user run ${BASE_TEST_DIR}/run-bats.sh ${BASE_TEST_DIR}/core 18 | echo ${output} 19 | [ ${status} -eq 0 ] 20 | } 21 | -------------------------------------------------------------------------------- /version/version.go: -------------------------------------------------------------------------------- 1 | package version 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | ) 7 | 8 | var ( 9 | Version = "dev" 10 | 11 | // GitCommit will be overwritten automatically by the build system 12 | GitCommit = "HEAD" 13 | ) 14 | 15 | // FullVersion formats the version to be printed 16 | func FullVersion() string { 17 | return fmt.Sprintf("%s, build %s", Version, GitCommit) 18 | } 19 | 20 | // RC checks if the Machine version is a release candidate or not 21 | func RC() bool { 22 | return strings.Contains(Version, "rc") 23 | } 24 | --------------------------------------------------------------------------------