mirror of https://github.com/docker/docs.git
308 lines
7.4 KiB
Go
308 lines
7.4 KiB
Go
package google
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/docker/machine/log"
|
|
"github.com/docker/machine/ssh"
|
|
raw "google.golang.org/api/compute/v1"
|
|
)
|
|
|
|
// ComputeUtil is used to wrap the raw GCE API code and store common parameters.
|
|
type ComputeUtil struct {
|
|
zone string
|
|
instanceName string
|
|
userName string
|
|
project string
|
|
diskTypeURL string
|
|
service *raw.Service
|
|
zoneURL string
|
|
authTokenPath string
|
|
globalURL string
|
|
ipAddress string
|
|
SwarmMaster bool
|
|
SwarmHost string
|
|
}
|
|
|
|
const (
|
|
apiURL = "https://www.googleapis.com/compute/v1/projects/"
|
|
imageName = "https://www.googleapis.com/compute/v1/projects/ubuntu-os-cloud/global/images/ubuntu-1404-trusty-v20150316"
|
|
firewallRule = "docker-machines"
|
|
port = "2376"
|
|
firewallTargetTag = "docker-machine"
|
|
dockerStartCommand = "sudo service docker start"
|
|
dockerStopCommand = "sudo service docker stop"
|
|
)
|
|
|
|
// NewComputeUtil creates and initializes a ComputeUtil.
|
|
func newComputeUtil(driver *Driver) (*ComputeUtil, error) {
|
|
service, err := newGCEService(driver.storePath, driver.AuthTokenPath)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
c := ComputeUtil{
|
|
authTokenPath: driver.AuthTokenPath,
|
|
zone: driver.Zone,
|
|
instanceName: driver.MachineName,
|
|
userName: driver.SSHUser,
|
|
project: driver.Project,
|
|
diskTypeURL: driver.DiskType,
|
|
service: service,
|
|
zoneURL: apiURL + driver.Project + "/zones/" + driver.Zone,
|
|
globalURL: apiURL + driver.Project + "/global",
|
|
SwarmMaster: driver.SwarmMaster,
|
|
SwarmHost: driver.SwarmHost,
|
|
}
|
|
return &c, nil
|
|
}
|
|
|
|
func (c *ComputeUtil) diskName() string {
|
|
return c.instanceName + "-disk"
|
|
}
|
|
|
|
func (c *ComputeUtil) diskType() string {
|
|
return apiURL + c.project + "/zones/" + c.zone + "/diskTypes/" + c.diskTypeURL
|
|
}
|
|
|
|
// disk returns the gce Disk.
|
|
func (c *ComputeUtil) disk() (*raw.Disk, error) {
|
|
return c.service.Disks.Get(c.project, c.zone, c.diskName()).Do()
|
|
}
|
|
|
|
// deleteDisk deletes the persistent disk.
|
|
func (c *ComputeUtil) deleteDisk() error {
|
|
log.Infof("Deleting disk.")
|
|
op, err := c.service.Disks.Delete(c.project, c.zone, c.diskName()).Do()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
log.Infof("Waiting for disk to delete.")
|
|
return c.waitForRegionalOp(op.Name)
|
|
}
|
|
|
|
func (c *ComputeUtil) firewallRule() (*raw.Firewall, error) {
|
|
return c.service.Firewalls.Get(c.project, firewallRule).Do()
|
|
}
|
|
|
|
func (c *ComputeUtil) createFirewallRule() error {
|
|
log.Infof("Creating firewall rule.")
|
|
allowed := []*raw.FirewallAllowed{
|
|
|
|
{
|
|
IPProtocol: "tcp",
|
|
Ports: []string{
|
|
port,
|
|
},
|
|
},
|
|
}
|
|
|
|
if c.SwarmMaster {
|
|
u, err := url.Parse(c.SwarmHost)
|
|
if err != nil {
|
|
return fmt.Errorf("error authorizing port for swarm: %s", err)
|
|
}
|
|
|
|
parts := strings.Split(u.Host, ":")
|
|
swarmPort := parts[1]
|
|
allowed = append(allowed, &raw.FirewallAllowed{
|
|
IPProtocol: "tcp",
|
|
Ports: []string{
|
|
swarmPort,
|
|
},
|
|
})
|
|
}
|
|
rule := &raw.Firewall{
|
|
Allowed: allowed,
|
|
SourceRanges: []string{
|
|
"0.0.0.0/0",
|
|
},
|
|
TargetTags: []string{
|
|
firewallTargetTag,
|
|
},
|
|
Name: firewallRule,
|
|
}
|
|
op, err := c.service.Firewalls.Insert(c.project, rule).Do()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return c.waitForGlobalOp(op.Name)
|
|
}
|
|
|
|
// instance retrieves the instance.
|
|
func (c *ComputeUtil) instance() (*raw.Instance, error) {
|
|
return c.service.Instances.Get(c.project, c.zone, c.instanceName).Do()
|
|
}
|
|
|
|
// createInstance creates a GCE VM instance.
|
|
func (c *ComputeUtil) createInstance(d *Driver) error {
|
|
log.Infof("Creating instance.")
|
|
// The rule will either exist or be nil in case of an error.
|
|
if rule, _ := c.firewallRule(); rule == nil {
|
|
if err := c.createFirewallRule(); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
instance := &raw.Instance{
|
|
Name: c.instanceName,
|
|
Description: "docker host vm",
|
|
MachineType: c.zoneURL + "/machineTypes/" + d.MachineType,
|
|
Disks: []*raw.AttachedDisk{
|
|
{
|
|
Boot: true,
|
|
AutoDelete: false,
|
|
Type: "PERSISTENT",
|
|
Mode: "READ_WRITE",
|
|
},
|
|
},
|
|
NetworkInterfaces: []*raw.NetworkInterface{
|
|
{
|
|
AccessConfigs: []*raw.AccessConfig{
|
|
{Type: "ONE_TO_ONE_NAT"},
|
|
},
|
|
Network: c.globalURL + "/networks/default",
|
|
},
|
|
},
|
|
Tags: &raw.Tags{
|
|
Items: []string{
|
|
firewallTargetTag,
|
|
},
|
|
},
|
|
ServiceAccounts: []*raw.ServiceAccount{
|
|
{
|
|
Email: "default",
|
|
Scopes: strings.Split(d.Scopes, ","),
|
|
},
|
|
},
|
|
}
|
|
disk, err := c.disk()
|
|
if disk == nil || err != nil {
|
|
instance.Disks[0].InitializeParams = &raw.AttachedDiskInitializeParams{
|
|
DiskName: c.diskName(),
|
|
SourceImage: imageName,
|
|
// The maximum supported disk size is 1000GB, the cast should be fine.
|
|
DiskSizeGb: int64(d.DiskSize),
|
|
DiskType: c.diskType(),
|
|
}
|
|
} else {
|
|
instance.Disks[0].Source = c.zoneURL + "/disks/" + c.instanceName + "-disk"
|
|
}
|
|
op, err := c.service.Instances.Insert(c.project, c.zone, instance).Do()
|
|
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
log.Infof("Waiting for Instance...")
|
|
if err = c.waitForRegionalOp(op.Name); err != nil {
|
|
return err
|
|
}
|
|
|
|
instance, err = c.instance()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// Update the SSH Key
|
|
sshKey, err := ioutil.ReadFile(d.GetSSHKeyPath() + ".pub")
|
|
if err != nil {
|
|
return err
|
|
}
|
|
log.Infof("Uploading SSH Key")
|
|
op, err = c.service.Instances.SetMetadata(c.project, c.zone, c.instanceName, &raw.Metadata{
|
|
Fingerprint: instance.Metadata.Fingerprint,
|
|
Items: []*raw.MetadataItems{
|
|
{
|
|
Key: "sshKeys",
|
|
Value: c.userName + ":" + string(sshKey) + "\n",
|
|
},
|
|
},
|
|
}).Do()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
log.Infof("Waiting for SSH Key")
|
|
err = c.waitForRegionalOp(op.Name)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// deleteInstance deletes the instance, leaving the persistent disk.
|
|
func (c *ComputeUtil) deleteInstance() error {
|
|
log.Infof("Deleting instance.")
|
|
op, err := c.service.Instances.Delete(c.project, c.zone, c.instanceName).Do()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
log.Infof("Waiting for instance to delete.")
|
|
return c.waitForRegionalOp(op.Name)
|
|
}
|
|
|
|
func (c *ComputeUtil) executeCommands(commands []string, ip, sshKeyPath string) error {
|
|
for _, command := range commands {
|
|
auth := &ssh.Auth{
|
|
Keys: []string{sshKeyPath},
|
|
}
|
|
|
|
client, err := ssh.NewClient(c.userName, ip, 22, auth)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if _, err := client.Output(command); err != nil {
|
|
return err
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (c *ComputeUtil) waitForOp(opGetter func() (*raw.Operation, error)) error {
|
|
for {
|
|
op, err := opGetter()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
log.Debugf("operation %q status: %s", op.Name, op.Status)
|
|
if op.Status == "DONE" {
|
|
if op.Error != nil {
|
|
return fmt.Errorf("Operation error: %v", *op.Error.Errors[0])
|
|
}
|
|
break
|
|
}
|
|
time.Sleep(1 * time.Second)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// waitForOp waits for the GCE Operation to finish.
|
|
func (c *ComputeUtil) waitForRegionalOp(name string) error {
|
|
return c.waitForOp(func() (*raw.Operation, error) {
|
|
return c.service.ZoneOperations.Get(c.project, c.zone, name).Do()
|
|
})
|
|
}
|
|
|
|
func (c *ComputeUtil) waitForGlobalOp(name string) error {
|
|
return c.waitForOp(func() (*raw.Operation, error) {
|
|
return c.service.GlobalOperations.Get(c.project, name).Do()
|
|
})
|
|
}
|
|
|
|
// ip retrieves and returns the external IP address of the instance.
|
|
func (c *ComputeUtil) ip() (string, error) {
|
|
if c.ipAddress == "" {
|
|
instance, err := c.service.Instances.Get(c.project, c.zone, c.instanceName).Do()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
c.ipAddress = instance.NetworkInterfaces[0].AccessConfigs[0].NatIP
|
|
}
|
|
return c.ipAddress, nil
|
|
}
|