Merge pull request #2434 from dgageot/check-after-create

Check machine after create
This commit is contained in:
Nathan LeClaire 2015-11-30 16:24:43 -08:00
commit f7ea3d9f19
6 changed files with 137 additions and 119 deletions

View File

@ -2,39 +2,13 @@ package commands
import (
"fmt"
"net/url"
"os"
"strings"
"github.com/docker/machine/libmachine"
"github.com/docker/machine/libmachine/auth"
"github.com/docker/machine/libmachine/cert"
"github.com/docker/machine/libmachine/host"
"github.com/docker/machine/libmachine/check"
"github.com/docker/machine/libmachine/log"
"github.com/docker/machine/libmachine/state"
)
var (
defaultConnChecker ConnChecker
)
func init() {
defaultConnChecker = &MachineConnChecker{}
}
// ErrCertInvalid for when the cert is computed to be invalid.
type ErrCertInvalid struct {
wrappedErr error
hostURL string
}
func (e ErrCertInvalid) Error() string {
return fmt.Sprintf(`There was an error validating certificates for host %q: %s
You can attempt to regenerate them using 'docker-machine regenerate-certs [name]'.
Be advised that this will trigger a Docker daemon restart which will stop running containers.
`, e.hostURL, e.wrappedErr)
}
func cmdConfig(c CommandLine, api libmachine.API) error {
// Ensure that log messages always go to stderr when this command is
// being run (it is intended to be run in a subshell)
@ -49,7 +23,7 @@ func cmdConfig(c CommandLine, api libmachine.API) error {
return err
}
dockerHost, authOptions, err := defaultConnChecker.Check(host, c.Bool("swarm"))
dockerHost, authOptions, err := check.DefaultConnChecker.Check(host, c.Bool("swarm"))
if err != nil {
return fmt.Errorf("Error running connection boilerplate: %s", err)
}
@ -61,88 +35,3 @@ func cmdConfig(c CommandLine, api libmachine.API) error {
return nil
}
type ConnChecker interface {
Check(*host.Host, bool) (string, *auth.Options, error)
}
type MachineConnChecker struct{}
func (mcc *MachineConnChecker) Check(h *host.Host, swarm bool) (string, *auth.Options, error) {
hostState, err := h.Driver.GetState()
if err != nil {
// TODO: This is a common operation and should have a commonly
// defined error.
return "", &auth.Options{}, fmt.Errorf("Error trying to get host state: %s", err)
}
if hostState != state.Running {
return "", &auth.Options{}, fmt.Errorf("%s is not running. Please start it in order to use the connection settings", h.Name)
}
dockerHost, err := h.Driver.GetURL()
if err != nil {
return "", &auth.Options{}, fmt.Errorf("Error getting driver URL: %s", err)
}
if swarm {
var err error
dockerHost, err = parseSwarm(dockerHost, h)
if err != nil {
return "", &auth.Options{}, fmt.Errorf("Error parsing swarm: %s", err)
}
}
u, err := url.Parse(dockerHost)
if err != nil {
return "", &auth.Options{}, fmt.Errorf("Error parsing URL: %s", err)
}
authOptions := h.HostOptions.AuthOptions
if err := checkCert(u.Host, authOptions); err != nil {
return "", &auth.Options{}, fmt.Errorf("Error checking and/or regenerating the certs: %s", err)
}
return dockerHost, authOptions, nil
}
func checkCert(hostURL string, authOptions *auth.Options) error {
valid, err := cert.ValidateCertificate(hostURL, authOptions)
if !valid || err != nil {
return ErrCertInvalid{
wrappedErr: err,
hostURL: hostURL,
}
}
return nil
}
// TODO: This could use a unit test.
func parseSwarm(hostURL string, h *host.Host) (string, error) {
swarmOptions := h.HostOptions.SwarmOptions
if !swarmOptions.Master {
return "", fmt.Errorf("Error: %s is not a swarm master. The --swarm flag is intended for use with swarm masters", h.Name)
}
u, err := url.Parse(swarmOptions.Host)
if err != nil {
return "", fmt.Errorf("There was an error parsing the url: %s", err)
}
parts := strings.Split(u.Host, ":")
swarmPort := parts[1]
// get IP of machine to replace in case swarm host is 0.0.0.0
mURL, err := url.Parse(hostURL)
if err != nil {
return "", fmt.Errorf("There was an error parsing the url: %s", err)
}
mParts := strings.Split(mURL.Host, ":")
machineIP := mParts[0]
hostURL = fmt.Sprintf("tcp://%s:%s", machineIP, swarmPort)
return hostURL, nil
}

View File

@ -11,6 +11,7 @@ import (
"github.com/docker/machine/commands/mcndirs"
"github.com/docker/machine/libmachine"
"github.com/docker/machine/libmachine/check"
"github.com/docker/machine/libmachine/log"
)
@ -76,7 +77,7 @@ func shellCfgSet(c CommandLine, api libmachine.API) (*ShellConfig, error) {
return nil, err
}
dockerHost, _, err := defaultConnChecker.Check(host, c.Bool("swarm"))
dockerHost, _, err := check.DefaultConnChecker.Check(host, c.Bool("swarm"))
if err != nil {
return nil, fmt.Errorf("Error checking TLS connection: %s", err)
}

View File

@ -11,6 +11,7 @@ import (
"github.com/docker/machine/drivers/fakedriver"
"github.com/docker/machine/libmachine"
"github.com/docker/machine/libmachine/auth"
"github.com/docker/machine/libmachine/check"
"github.com/docker/machine/libmachine/host"
"github.com/docker/machine/libmachine/libmachinetest"
"github.com/docker/machine/libmachine/state"
@ -90,7 +91,7 @@ func TestShellCfgSet(t *testing.T) {
description string
commandLine CommandLine
api libmachine.API
connChecker ConnChecker
connChecker check.ConnChecker
noProxyVar string
noProxyValue string
expectedShellCfg *ShellConfig
@ -345,7 +346,7 @@ func TestShellCfgSet(t *testing.T) {
t.Log(test.description)
defaultConnChecker = test.connChecker
check.DefaultConnChecker = test.connChecker
shellCfg, err := shellCfgSet(test.commandLine, test.api)
assert.Equal(t, test.expectedShellCfg, shellCfg)
assert.Equal(t, test.expectedErr, err)
@ -366,7 +367,7 @@ func TestShellCfgUnset(t *testing.T) {
description string
commandLine CommandLine
api libmachine.API
connChecker ConnChecker
connChecker check.ConnChecker
noProxyVar string
noProxyValue string
expectedShellCfg *ShellConfig
@ -496,7 +497,7 @@ func TestShellCfgUnset(t *testing.T) {
t.Log(test.description)
defaultConnChecker = test.connChecker
check.DefaultConnChecker = test.connChecker
shellCfg, err := shellCfgUnset(test.commandLine, test.api)
assert.Equal(t, test.expectedShellCfg, shellCfg)
assert.Equal(t, test.expectedErr, err)

118
libmachine/check/check.go Normal file
View File

@ -0,0 +1,118 @@
package check
import (
"fmt"
"net/url"
"strings"
"github.com/docker/machine/libmachine/auth"
"github.com/docker/machine/libmachine/cert"
"github.com/docker/machine/libmachine/host"
"github.com/docker/machine/libmachine/state"
)
var (
DefaultConnChecker ConnChecker
)
func init() {
DefaultConnChecker = &MachineConnChecker{}
}
// ErrCertInvalid for when the cert is computed to be invalid.
type ErrCertInvalid struct {
wrappedErr error
hostURL string
}
func (e ErrCertInvalid) Error() string {
return fmt.Sprintf(`There was an error validating certificates for host %q: %s
You can attempt to regenerate them using 'docker-machine regenerate-certs [name]'.
Be advised that this will trigger a Docker daemon restart which will stop running containers.
`, e.hostURL, e.wrappedErr)
}
type ConnChecker interface {
Check(*host.Host, bool) (dockerHost string, authOptions *auth.Options, err error)
}
type MachineConnChecker struct{}
func (mcc *MachineConnChecker) Check(h *host.Host, swarm bool) (string, *auth.Options, error) {
hostState, err := h.Driver.GetState()
if err != nil {
// TODO: This is a common operation and should have a commonly
// defined error.
return "", &auth.Options{}, fmt.Errorf("Error trying to get host state: %s", err)
}
if hostState != state.Running {
return "", &auth.Options{}, fmt.Errorf("%s is not running. Please start it in order to use the connection settings", h.Name)
}
dockerHost, err := h.Driver.GetURL()
if err != nil {
return "", &auth.Options{}, fmt.Errorf("Error getting driver URL: %s", err)
}
if swarm {
var err error
dockerHost, err = parseSwarm(dockerHost, h)
if err != nil {
return "", &auth.Options{}, fmt.Errorf("Error parsing swarm: %s", err)
}
}
u, err := url.Parse(dockerHost)
if err != nil {
return "", &auth.Options{}, fmt.Errorf("Error parsing URL: %s", err)
}
authOptions := h.HostOptions.AuthOptions
if err := checkCert(u.Host, authOptions); err != nil {
return "", &auth.Options{}, fmt.Errorf("Error checking and/or regenerating the certs: %s", err)
}
return dockerHost, authOptions, nil
}
func checkCert(hostURL string, authOptions *auth.Options) error {
valid, err := cert.ValidateCertificate(hostURL, authOptions)
if !valid || err != nil {
return ErrCertInvalid{
wrappedErr: err,
hostURL: hostURL,
}
}
return nil
}
// TODO: This could use a unit test.
func parseSwarm(hostURL string, h *host.Host) (string, error) {
swarmOptions := h.HostOptions.SwarmOptions
if !swarmOptions.Master {
return "", fmt.Errorf("Error: %s is not a swarm master. The --swarm flag is intended for use with swarm masters", h.Name)
}
u, err := url.Parse(swarmOptions.Host)
if err != nil {
return "", fmt.Errorf("There was an error parsing the url: %s", err)
}
parts := strings.Split(u.Host, ":")
swarmPort := parts[1]
// get IP of machine to replace in case swarm host is 0.0.0.0
mURL, err := url.Parse(hostURL)
if err != nil {
return "", fmt.Errorf("There was an error parsing the url: %s", err)
}
mParts := strings.Split(mURL.Host, ":")
machineIP := mParts[0]
hostURL = fmt.Sprintf("tcp://%s:%s", machineIP, swarmPort)
return hostURL, nil
}

View File

@ -1,4 +1,4 @@
package commands
package check
import (
"errors"

View File

@ -6,6 +6,7 @@ import (
"github.com/docker/machine/libmachine/auth"
"github.com/docker/machine/libmachine/cert"
"github.com/docker/machine/libmachine/check"
"github.com/docker/machine/libmachine/drivers"
"github.com/docker/machine/libmachine/engine"
"github.com/docker/machine/libmachine/host"
@ -125,6 +126,14 @@ func (api *Client) Create(h *host.Host) error {
if err := provisioner.Provision(*h.HostOptions.SwarmOptions, *h.HostOptions.AuthOptions, *h.HostOptions.EngineOptions); err != nil {
return fmt.Errorf("Error running provisioning: %s", err)
}
// We should check the connection to docker here
log.Info("Checking connection to Docker...")
if _, _, err = check.DefaultConnChecker.Check(h, false); err != nil {
return fmt.Errorf("Error checking the host: %s", err)
}
log.Info("Docker is up and running!")
}
log.Debug("Reticulating splines...")