From 01c7556e3ab112d0870e50b427509bb2982dc693 Mon Sep 17 00:00:00 2001 From: Nathan LeClaire Date: Thu, 29 Oct 2015 10:42:10 -0700 Subject: [PATCH] Add docker-machine provision command Signed-off-by: Nathan LeClaire --- commands/commands.go | 6 ++ commands/provision.go | 7 ++ commands/provision_test.go | 68 ++++++++++++++++ docs/reference/provision.md | 37 +++++++++ libmachine/host/host.go | 9 +++ libmachine/provision/provisioner.go | 19 ++++- .../provisiontest/fake_provisioner.go | 79 +++++++++++++++++++ 7 files changed, 224 insertions(+), 1 deletion(-) create mode 100644 commands/provision.go create mode 100644 commands/provision_test.go create mode 100644 docs/reference/provision.md create mode 100644 libmachine/provision/provisiontest/fake_provisioner.go diff --git a/commands/commands.go b/commands/commands.go index 137500a124..fd82916489 100644 --- a/commands/commands.go +++ b/commands/commands.go @@ -240,6 +240,11 @@ var Commands = []cli.Command{ }, }, }, + { + Name: "provision", + Usage: "Re-provision existing machines", + Action: runCommand(cmdProvision), + }, { Name: "regenerate-certs", Usage: "Regenerate TLS Certificates for a machine", @@ -355,6 +360,7 @@ func machineCommand(actionName string, host *host.Host, errorChan chan<- error) "kill": host.Kill, "upgrade": host.Upgrade, "ip": printIP(host), + "provision": host.Provision, } log.Debugf("command=%s machine=%s", actionName, host.Name) diff --git a/commands/provision.go b/commands/provision.go new file mode 100644 index 0000000000..d7070413b9 --- /dev/null +++ b/commands/provision.go @@ -0,0 +1,7 @@ +package commands + +import "github.com/docker/machine/libmachine" + +func cmdProvision(c CommandLine, api libmachine.API) error { + return runAction("provision", c, api) +} diff --git a/commands/provision_test.go b/commands/provision_test.go new file mode 100644 index 0000000000..e7d6c36cb9 --- /dev/null +++ b/commands/provision_test.go @@ -0,0 +1,68 @@ +package commands + +import ( + "testing" + + "github.com/docker/machine/commands/commandstest" + "github.com/docker/machine/drivers/fakedriver" + "github.com/docker/machine/libmachine" + "github.com/docker/machine/libmachine/auth" + "github.com/docker/machine/libmachine/engine" + "github.com/docker/machine/libmachine/host" + "github.com/docker/machine/libmachine/libmachinetest" + "github.com/docker/machine/libmachine/provision" + "github.com/docker/machine/libmachine/provision/provisiontest" + "github.com/docker/machine/libmachine/swarm" + "github.com/stretchr/testify/assert" +) + +func TestCmdProvision(t *testing.T) { + testCases := []struct { + commandLine CommandLine + api libmachine.API + expectedErr error + }{ + { + commandLine: &commandstest.FakeCommandLine{ + CliArgs: []string{"foo", "bar"}, + }, + api: &libmachinetest.FakeAPI{ + Hosts: []*host.Host{ + { + Name: "foo", + Driver: &fakedriver.Driver{}, + HostOptions: &host.Options{ + EngineOptions: &engine.Options{}, + AuthOptions: &auth.Options{}, + SwarmOptions: &swarm.Options{}, + }, + }, + { + Name: "bar", + Driver: &fakedriver.Driver{}, + HostOptions: &host.Options{ + EngineOptions: &engine.Options{}, + AuthOptions: &auth.Options{}, + SwarmOptions: &swarm.Options{}, + }, + }, + }, + }, + expectedErr: nil, + }, + } + + provision.SetDetector(&provisiontest.FakeDetector{ + Provisioner: provisiontest.NewFakeProvisioner(nil), + }) + + // fakeprovisioner always returns "true" for compatible host, so we + // just need to register it. + provision.Register("fakeprovisioner", &provision.RegisteredProvisioner{ + New: provisiontest.NewFakeProvisioner, + }) + + for _, tc := range testCases { + assert.Equal(t, tc.expectedErr, cmdProvision(tc.commandLine, tc.api)) + } +} diff --git a/docs/reference/provision.md b/docs/reference/provision.md new file mode 100644 index 0000000000..032ce6c883 --- /dev/null +++ b/docs/reference/provision.md @@ -0,0 +1,37 @@ + + +# provision + +Re-run provisioning on a created machine. + +Sometimes it may be helpful to re-run Machine's provisioning process on a +created machine. Reasons for doing so may include a failure during the original +provisioning process, or a drift from the desired system state (including the +originally specified Swarm or Engine configuration). + +Usage is `docker-machine provision [name]`. Multiple names may be specified. + + $ docker-machine provision foo bar + Copying certs to the local machine directory... + Copying certs to the remote machine... + Setting Docker configuration on the remote daemon... + +The Machine provisioning process will: + +1. Set the hostname on the instance to the name Machine addresses it by (e.g. +`default`). +2. Install Docker if it is not present already. +3. Generate a set of certificates (usually with the default, self-signed CA) and +configure the daemon to accept connections over TLS. +4. Copy the generated certificates to the server and local config directory. +5. Configure the Docker Engine according to the options specified at create +time. +6. Configure and activate Swarm if applicable. diff --git a/libmachine/host/host.go b/libmachine/host/host.go index 7df0ace5d2..1e2abe7397 100644 --- a/libmachine/host/host.go +++ b/libmachine/host/host.go @@ -192,3 +192,12 @@ func (h *Host) ConfigureAuth() error { // Call provision to re-provision the certs properly. return provisioner.Provision(swarm.Options{}, *h.HostOptions.AuthOptions, *h.HostOptions.EngineOptions) } + +func (h *Host) Provision() error { + provisioner, err := provision.DetectProvisioner(h.Driver) + if err != nil { + return err + } + + return provisioner.Provision(*h.HostOptions.SwarmOptions, *h.HostOptions.AuthOptions, *h.HostOptions.EngineOptions) +} diff --git a/libmachine/provision/provisioner.go b/libmachine/provision/provisioner.go index ade6beb752..2c55913532 100644 --- a/libmachine/provision/provisioner.go +++ b/libmachine/provision/provisioner.go @@ -12,13 +12,26 @@ import ( "github.com/docker/machine/libmachine/swarm" ) -var provisioners = make(map[string]*RegisteredProvisioner) +var ( + provisioners = make(map[string]*RegisteredProvisioner) + detector Detector = &StandardDetector{} +) type SSHCommander interface { // Short-hand for accessing an SSH command from the driver. SSHCommand(args string) (string, error) } +type Detector interface { + DetectProvisioner(d drivers.Driver) (Provisioner, error) +} + +type StandardDetector struct{} + +func SetDetector(newDetector Detector) { + detector = newDetector +} + // Provisioner defines distribution specific actions type Provisioner interface { fmt.Stringer @@ -77,6 +90,10 @@ func Register(name string, p *RegisteredProvisioner) { } func DetectProvisioner(d drivers.Driver) (Provisioner, error) { + return detector.DetectProvisioner(d) +} + +func (detector StandardDetector) DetectProvisioner(d drivers.Driver) (Provisioner, error) { log.Info("Detecting the provisioner...") osReleaseOut, err := drivers.RunSSHCommandFromDriver(d, "cat /etc/os-release") diff --git a/libmachine/provision/provisiontest/fake_provisioner.go b/libmachine/provision/provisiontest/fake_provisioner.go new file mode 100644 index 0000000000..700e36a8d1 --- /dev/null +++ b/libmachine/provision/provisiontest/fake_provisioner.go @@ -0,0 +1,79 @@ +package provisiontest + +import ( + "github.com/docker/machine/libmachine/auth" + "github.com/docker/machine/libmachine/drivers" + "github.com/docker/machine/libmachine/engine" + "github.com/docker/machine/libmachine/provision" + "github.com/docker/machine/libmachine/provision/pkgaction" + "github.com/docker/machine/libmachine/provision/serviceaction" + "github.com/docker/machine/libmachine/swarm" +) + +type FakeDetector struct { + provision.Provisioner +} + +func (fd *FakeDetector) DetectProvisioner(d drivers.Driver) (provision.Provisioner, error) { + return fd.Provisioner, nil +} + +type FakeProvisioner struct{} + +func NewFakeProvisioner(d drivers.Driver) provision.Provisioner { + return &FakeProvisioner{} +} + +func (fp *FakeProvisioner) SSHCommand(args string) (string, error) { + return "", nil +} + +func (fp *FakeProvisioner) String() string { + return "fakeprovisioner" +} + +func (fp *FakeProvisioner) GenerateDockerOptions(dockerPort int) (*provision.DockerOptions, error) { + return nil, nil +} + +func (fp *FakeProvisioner) GetDockerOptionsDir() string { + return "" +} + +func (fp *FakeProvisioner) GetAuthOptions() auth.Options { + return auth.Options{} +} + +func (fp *FakeProvisioner) Package(name string, action pkgaction.PackageAction) error { + return nil +} + +func (fp *FakeProvisioner) Hostname() (string, error) { + return "", nil +} + +func (fp *FakeProvisioner) SetHostname(hostname string) error { + return nil +} + +func (fp *FakeProvisioner) CompatibleWithHost() bool { + return true +} + +func (fp *FakeProvisioner) Provision(swarmOptions swarm.Options, authOptions auth.Options, engineOptions engine.Options) error { + return nil +} + +func (fp *FakeProvisioner) Service(name string, action serviceaction.ServiceAction) error { + return nil +} + +func (fp *FakeProvisioner) GetDriver() drivers.Driver { + return nil +} + +func (fp *FakeProvisioner) SetOsReleaseInfo(info *provision.OsRelease) {} + +func (fp *FakeProvisioner) GetOsReleaseInfo() (*provision.OsRelease, error) { + return nil, nil +}