package libmachine import ( "fmt" "path/filepath" "io" "github.com/docker/machine/drivers/errdriver" "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/drivers/plugin/localbinary" "github.com/docker/machine/libmachine/drivers/rpc" "github.com/docker/machine/libmachine/engine" "github.com/docker/machine/libmachine/host" "github.com/docker/machine/libmachine/log" "github.com/docker/machine/libmachine/mcnerror" "github.com/docker/machine/libmachine/mcnutils" "github.com/docker/machine/libmachine/persist" "github.com/docker/machine/libmachine/provision" "github.com/docker/machine/libmachine/ssh" "github.com/docker/machine/libmachine/state" "github.com/docker/machine/libmachine/swarm" "github.com/docker/machine/libmachine/version" ) type API interface { io.Closer NewHost(driverName string, rawDriver []byte) (*host.Host, error) Create(h *host.Host) error persist.Store GetMachinesDir() string } type Client struct { certsDir string IsDebug bool SSHClientType ssh.ClientType GithubAPIToken string *persist.Filestore clientDriverFactory rpcdriver.RPCClientDriverFactory } func NewClient(storePath, certsDir string) *Client { return &Client{ certsDir: certsDir, IsDebug: false, SSHClientType: ssh.External, Filestore: persist.NewFilestore(storePath, certsDir, certsDir), clientDriverFactory: rpcdriver.NewRPCClientDriverFactory(), } } func (api *Client) NewHost(driverName string, rawDriver []byte) (*host.Host, error) { driver, err := api.clientDriverFactory.NewRPCClientDriver(driverName, rawDriver) if err != nil { return nil, err } return &host.Host{ ConfigVersion: version.ConfigVersion, Name: driver.GetMachineName(), Driver: driver, DriverName: driver.DriverName(), HostOptions: &host.Options{ AuthOptions: &auth.Options{ CertDir: api.certsDir, CaCertPath: filepath.Join(api.certsDir, "ca.pem"), CaPrivateKeyPath: filepath.Join(api.certsDir, "ca-key.pem"), ClientCertPath: filepath.Join(api.certsDir, "cert.pem"), ClientKeyPath: filepath.Join(api.certsDir, "key.pem"), ServerCertPath: filepath.Join(api.GetMachinesDir(), "server.pem"), ServerKeyPath: filepath.Join(api.GetMachinesDir(), "server-key.pem"), }, EngineOptions: &engine.Options{ InstallURL: drivers.DefaultEngineInstallURL, StorageDriver: "aufs", TLSVerify: true, }, SwarmOptions: &swarm.Options{ Host: "tcp://0.0.0.0:3376", Image: "swarm:latest", Strategy: "spread", }, }, }, nil } func (api *Client) Load(name string) (*host.Host, error) { h, err := api.Filestore.Load(name) if err != nil { return nil, err } d, err := api.clientDriverFactory.NewRPCClientDriver(h.DriverName, h.RawDriver) if err != nil { // Not being able to find a driver binary is a "known error" if _, ok := err.(localbinary.ErrPluginBinaryNotFound); ok { h.Driver = errdriver.NewDriver(h.DriverName) return h, nil } return nil, err } if h.DriverName == "virtualbox" { h.Driver = drivers.NewSerialDriver(d) } else { h.Driver = d } return h, nil } // Create is the wrapper method which covers all of the boilerplate around // actually creating, provisioning, and persisting an instance in the store. func (api *Client) Create(h *host.Host) error { if err := cert.BootstrapCertificates(h.AuthOptions()); err != nil { return fmt.Errorf("Error generating certificates: %s", err) } log.Info("Running pre-create checks...") if err := h.Driver.PreCreateCheck(); err != nil { return mcnerror.ErrDuringPreCreate{ Cause: err, } } if err := api.Save(h); err != nil { return fmt.Errorf("Error saving host to store before attempting creation: %s", err) } log.Info("Creating machine...") if err := api.performCreate(h); err != nil { return fmt.Errorf("Error creating machine: %s", err) } log.Debug("Reticulating splines...") return nil } func (api *Client) performCreate(h *host.Host) error { if err := h.Driver.Create(); err != nil { return fmt.Errorf("Error in driver during machine creation: %s", err) } if err := api.Save(h); err != nil { return fmt.Errorf("Error saving host to store after attempting creation: %s", err) } // TODO: Not really a fan of just checking "none" or "ci-test" here. if h.Driver.DriverName() == "none" || h.Driver.DriverName() == "ci-test" { return nil } log.Info("Waiting for machine to be running, this may take a few minutes...") if err := mcnutils.WaitFor(drivers.MachineInState(h.Driver, state.Running)); err != nil { return fmt.Errorf("Error waiting for machine to be running: %s", err) } log.Info("Detecting operating system of created instance...") provisioner, err := provision.DetectProvisioner(h.Driver) if err != nil { return fmt.Errorf("Error detecting OS: %s", err) } log.Infof("Provisioning with %s...", provisioner.String()) 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!") return nil } func (api *Client) Close() error { return api.clientDriverFactory.Close() }