Merge pull request #2752 from dgageot/google-reuse

Support creating a machine from an existing google VM
This commit is contained in:
Jean-Laurent de Morlhon 2016-01-25 15:39:51 +01:00
commit 01f4ffd21f
3 changed files with 96 additions and 29 deletions

View File

@ -50,6 +50,7 @@ To create a machine instance, specify `--driver google`, the project id and the
- `--google-preemptible`: Instance preemptibility. - `--google-preemptible`: Instance preemptibility.
- `--google-tags`: Instance tags (comma-separated). - `--google-tags`: Instance tags (comma-separated).
- `--google-use-internal-ip`: When this option is used during create it will make docker-machine use internal rather than public NATed IPs. The flag is persistent in the sense that a machine created with it retains the IP. It's useful for managing docker machines from another machine on the same network e.g. while deploying swarm. - `--google-use-internal-ip`: When this option is used during create it will make docker-machine use internal rather than public NATed IPs. The flag is persistent in the sense that a machine created with it retains the IP. It's useful for managing docker machines from another machine on the same network e.g. while deploying swarm.
- `--google-use-existing`: Don't create a new VM, use an existing one. This is useful when you'd like to provision Docker on a VM you created yourself, maybe because it uses create options not supported by this driver.
The GCE driver will use the `ubuntu-1510-wily-v20151114` instance image unless otherwise specified. To obtain a The GCE driver will use the `ubuntu-1510-wily-v20151114` instance image unless otherwise specified. To obtain a
list of image URLs run: list of image URLs run:
@ -72,3 +73,4 @@ Environment variables and default values:
| `--google-preemptible` | `GOOGLE_PREEMPTIBLE` | - | | `--google-preemptible` | `GOOGLE_PREEMPTIBLE` | - |
| `--google-tags` | `GOOGLE_TAGS` | - | | `--google-tags` | `GOOGLE_TAGS` | - |
| `--google-use-internal-ip` | `GOOGLE_USE_INTERNAL_IP` | - | | `--google-use-internal-ip` | `GOOGLE_USE_INTERNAL_IP` | - |
| `--google-use-existing` | `GOOGLE_USE_EXISTING` | - |

View File

@ -88,6 +88,11 @@ func (c *ComputeUtil) disk() (*raw.Disk, error) {
// deleteDisk deletes the persistent disk. // deleteDisk deletes the persistent disk.
func (c *ComputeUtil) deleteDisk() error { func (c *ComputeUtil) deleteDisk() error {
disk, _ := c.disk()
if disk == nil {
return nil
}
log.Infof("Deleting disk.") log.Infof("Deleting disk.")
op, err := c.service.Disks.Delete(c.project, c.zone, c.diskName()).Do() op, err := c.service.Disks.Delete(c.project, c.zone, c.diskName()).Do()
if err != nil { if err != nil {
@ -162,8 +167,9 @@ func (c *ComputeUtil) portsUsed() ([]string, error) {
return ports, nil return ports, nil
} }
func (c *ComputeUtil) createFirewallRule() error { // openFirewallPorts configures the firewall to open docker and swarm ports.
log.Infof("Opening firewall ports.") func (c *ComputeUtil) openFirewallPorts() error {
log.Infof("Opening firewall ports")
create := false create := false
rule, _ := c.firewallRule() rule, _ := c.firewallRule()
@ -213,11 +219,7 @@ func (c *ComputeUtil) instance() (*raw.Instance, error) {
// createInstance creates a GCE VM instance. // createInstance creates a GCE VM instance.
func (c *ComputeUtil) createInstance(d *Driver) error { func (c *ComputeUtil) createInstance(d *Driver) error {
log.Infof("Creating instance.") log.Infof("Creating instance")
if err := c.createFirewallRule(); err != nil {
return err
}
instance := &raw.Instance{ instance := &raw.Instance{
Name: c.instanceName, Name: c.instanceName,
@ -280,7 +282,7 @@ func (c *ComputeUtil) createInstance(d *Driver) error {
return err return err
} }
log.Infof("Waiting for Instance...") log.Infof("Waiting for Instance")
if err = c.waitForRegionalOp(op.Name); err != nil { if err = c.waitForRegionalOp(op.Name); err != nil {
return err return err
} }
@ -290,15 +292,58 @@ func (c *ComputeUtil) createInstance(d *Driver) error {
return err return err
} }
// Update the SSH Key return c.uploadSSHKey(instance, d.GetSSHKeyPath())
sshKey, err := ioutil.ReadFile(d.GetSSHKeyPath() + ".pub") }
// configureInstance configures an existing instance for use with Docker Machine.
func (c *ComputeUtil) configureInstance(d *Driver) error {
log.Infof("Configuring instance")
instance, err := c.instance()
if err != nil { if err != nil {
return err return err
} }
if err := c.addFirewallTag(instance); err != nil {
return err
}
return c.uploadSSHKey(instance, d.GetSSHKeyPath())
}
// addFirewallTag adds a tag to the instance to match the firewall rule.
func (c *ComputeUtil) addFirewallTag(instance *raw.Instance) error {
log.Infof("Adding tag for the firewall rule")
tags := instance.Tags
for _, tag := range tags.Items {
if tag == firewallTargetTag {
return nil
}
}
tags.Items = append(tags.Items, firewallTargetTag)
op, err := c.service.Instances.SetTags(c.project, c.zone, instance.Name, tags).Do()
if err != nil {
return err
}
return c.waitForRegionalOp(op.Name)
}
// uploadSSHKey updates the instance metadata with the given ssh key.
func (c *ComputeUtil) uploadSSHKey(instance *raw.Instance, sshKeyPath string) error {
log.Infof("Uploading SSH Key") log.Infof("Uploading SSH Key")
sshKey, err := ioutil.ReadFile(sshKeyPath + ".pub")
if err != nil {
return err
}
metaDataValue := fmt.Sprintf("%s:%s %s\n", c.userName, strings.TrimSpace(string(sshKey)), c.userName) metaDataValue := fmt.Sprintf("%s:%s %s\n", c.userName, strings.TrimSpace(string(sshKey)), c.userName)
op, err = c.service.Instances.SetMetadata(c.project, c.zone, c.instanceName, &raw.Metadata{
op, err := c.service.Instances.SetMetadata(c.project, c.zone, c.instanceName, &raw.Metadata{
Fingerprint: instance.Metadata.Fingerprint, Fingerprint: instance.Metadata.Fingerprint,
Items: []*raw.MetadataItems{ Items: []*raw.MetadataItems{
{ {
@ -307,10 +352,6 @@ func (c *ComputeUtil) createInstance(d *Driver) error {
}, },
}, },
}).Do() }).Do()
if err != nil {
return err
}
log.Infof("Waiting for SSH Key")
return c.waitForRegionalOp(op.Name) return c.waitForRegionalOp(op.Name)
} }
@ -360,6 +401,7 @@ func (c *ComputeUtil) startInstance() error {
return c.waitForRegionalOp(op.Name) return c.waitForRegionalOp(op.Name)
} }
// waitForOp waits for the operation to finish.
func (c *ComputeUtil) waitForOp(opGetter func() (*raw.Operation, error)) error { func (c *ComputeUtil) waitForOp(opGetter func() (*raw.Operation, error)) error {
for { for {
op, err := opGetter() op, err := opGetter()
@ -367,7 +409,7 @@ func (c *ComputeUtil) waitForOp(opGetter func() (*raw.Operation, error)) error {
return err return err
} }
log.Debugf("operation %q status: %s", op.Name, op.Status) log.Debugf("Operation %q status: %s", op.Name, op.Status)
if op.Status == "DONE" { if op.Status == "DONE" {
if op.Error != nil { if op.Error != nil {
return fmt.Errorf("Operation error: %v", *op.Error.Errors[0]) return fmt.Errorf("Operation error: %v", *op.Error.Errors[0])
@ -379,7 +421,7 @@ func (c *ComputeUtil) waitForOp(opGetter func() (*raw.Operation, error)) error {
return nil return nil
} }
// waitForOp waits for the operation to finish. // waitForRegionalOp waits for the regional operation to finish.
func (c *ComputeUtil) waitForRegionalOp(name string) error { func (c *ComputeUtil) waitForRegionalOp(name string) error {
return c.waitForOp(func() (*raw.Operation, error) { return c.waitForOp(func() (*raw.Operation, error) {
return c.service.ZoneOperations.Get(c.project, c.zone, name).Do() return c.service.ZoneOperations.Get(c.project, c.zone, name).Do()

View File

@ -26,6 +26,7 @@ type Driver struct {
DiskSize int DiskSize int
Project string Project string
Tags string Tags string
UseExisting bool
} }
const ( const (
@ -110,6 +111,11 @@ func (d *Driver) GetCreateFlags() []mcnflag.Flag {
Usage: "Use internal GCE Instance IP rather than public one", Usage: "Use internal GCE Instance IP rather than public one",
EnvVar: "GOOGLE_USE_INTERNAL_IP", EnvVar: "GOOGLE_USE_INTERNAL_IP",
}, },
mcnflag.BoolFlag{
Name: "google-use-existing",
Usage: "Don't create a new VM, use an existing one",
EnvVar: "GOOGLE_USE_EXISTING",
},
} }
} }
@ -156,6 +162,8 @@ func (d *Driver) SetConfigFromFlags(flags drivers.DriverOptions) error {
} }
d.Zone = flags.String("google-zone") d.Zone = flags.String("google-zone")
d.UseExisting = flags.Bool("google-use-existing")
if !d.UseExisting {
d.MachineType = flags.String("google-machine-type") d.MachineType = flags.String("google-machine-type")
d.MachineImage = flags.String("google-machine-image") d.MachineImage = flags.String("google-machine-image")
d.DiskSize = flags.Int("google-disk-size") d.DiskSize = flags.Int("google-disk-size")
@ -165,6 +173,7 @@ func (d *Driver) SetConfigFromFlags(flags drivers.DriverOptions) error {
d.UseInternalIP = flags.Bool("google-use-internal-ip") d.UseInternalIP = flags.Bool("google-use-internal-ip")
d.Scopes = flags.String("google-scopes") d.Scopes = flags.String("google-scopes")
d.Tags = flags.String("google-tags") d.Tags = flags.String("google-tags")
}
d.SSHUser = flags.String("google-username") d.SSHUser = flags.String("google-username")
d.SSHPort = 22 d.SSHPort = 22
d.SetSwarmConfigFromFlags(flags) d.SetSwarmConfigFromFlags(flags)
@ -191,8 +200,15 @@ func (d *Driver) PreCreateCheck() error {
// doesn't exist, so just check instance for nil. // doesn't exist, so just check instance for nil.
log.Infof("Check if the instance already exists") log.Infof("Check if the instance already exists")
if instance, _ := c.instance(); instance != nil { instance, _ := c.instance()
return fmt.Errorf("Instance %v already exists.", d.MachineName) if d.UseExisting {
if instance == nil {
return fmt.Errorf("Unable to find instance %q in zone %q.", d.MachineName, d.Zone)
}
} else {
if instance != nil {
return fmt.Errorf("Instance %q already exists in zone %q.", d.MachineName, d.Zone)
}
} }
return nil return nil
@ -213,6 +229,13 @@ func (d *Driver) Create() error {
return err return err
} }
if err := c.openFirewallPorts(); err != nil {
return err
}
if d.UseExisting {
return c.configureInstance(d)
}
return c.createInstance(d) return c.createInstance(d)
} }