Skip to content

Commit 7cf9ae7

Browse files
committed
Support creating a machine on an existing google VM
Signed-off-by: David Gageot <david@gageot.net>
1 parent ad3e6b5 commit 7cf9ae7

File tree

3 files changed

+96
-29
lines changed

3 files changed

+96
-29
lines changed

docs/drivers/gce.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ To create a machine instance, specify `--driver google`, the project id and the
3838

3939
### Options
4040

41-
- `--google-project`: **required** The id of your project to use when launching the instance.
41+
- `--google-project`: **required** The id of your project to use when launching the instance.
4242
- `--google-zone`: The zone to launch the instance.
4343
- `--google-machine-type`: The type of instance.
4444
- `--google-machine-image`: The absolute URL to a base VM image to instantiate.
@@ -50,6 +50,7 @@ To create a machine instance, specify `--driver google`, the project id and the
5050
- `--google-preemptible`: Instance preemptibility.
5151
- `--google-tags`: Instance tags (comma-separated).
5252
- `--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.
53+
- `--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.
5354

5455
The GCE driver will use the `ubuntu-1510-wily-v20151114` instance image unless otherwise specified. To obtain a
5556
list of image URLs run:
@@ -72,3 +73,4 @@ Environment variables and default values:
7273
| `--google-preemptible` | `GOOGLE_PREEMPTIBLE` | - |
7374
| `--google-tags` | `GOOGLE_TAGS` | - |
7475
| `--google-use-internal-ip` | `GOOGLE_USE_INTERNAL_IP` | - |
76+
| `--google-use-existing` | `GOOGLE_USE_EXISTING` | - |

drivers/google/compute_util.go

Lines changed: 59 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,11 @@ func (c *ComputeUtil) disk() (*raw.Disk, error) {
8888

8989
// deleteDisk deletes the persistent disk.
9090
func (c *ComputeUtil) deleteDisk() error {
91+
disk, _ := c.disk()
92+
if disk == nil {
93+
return nil
94+
}
95+
9196
log.Infof("Deleting disk.")
9297
op, err := c.service.Disks.Delete(c.project, c.zone, c.diskName()).Do()
9398
if err != nil {
@@ -162,8 +167,9 @@ func (c *ComputeUtil) portsUsed() ([]string, error) {
162167
return ports, nil
163168
}
164169

165-
func (c *ComputeUtil) createFirewallRule() error {
166-
log.Infof("Opening firewall ports.")
170+
// openFirewallPorts configures the firewall to open docker and swarm ports.
171+
func (c *ComputeUtil) openFirewallPorts() error {
172+
log.Infof("Opening firewall ports")
167173

168174
create := false
169175
rule, _ := c.firewallRule()
@@ -213,11 +219,7 @@ func (c *ComputeUtil) instance() (*raw.Instance, error) {
213219

214220
// createInstance creates a GCE VM instance.
215221
func (c *ComputeUtil) createInstance(d *Driver) error {
216-
log.Infof("Creating instance.")
217-
218-
if err := c.createFirewallRule(); err != nil {
219-
return err
220-
}
222+
log.Infof("Creating instance")
221223

222224
instance := &raw.Instance{
223225
Name: c.instanceName,
@@ -280,7 +282,7 @@ func (c *ComputeUtil) createInstance(d *Driver) error {
280282
return err
281283
}
282284

283-
log.Infof("Waiting for Instance...")
285+
log.Infof("Waiting for Instance")
284286
if err = c.waitForRegionalOp(op.Name); err != nil {
285287
return err
286288
}
@@ -290,15 +292,58 @@ func (c *ComputeUtil) createInstance(d *Driver) error {
290292
return err
291293
}
292294

293-
// Update the SSH Key
294-
sshKey, err := ioutil.ReadFile(d.GetSSHKeyPath() + ".pub")
295+
return c.uploadSSHKey(instance, d.GetSSHKeyPath())
296+
}
297+
298+
// configureInstance configures an existing instance for use with Docker Machine.
299+
func (c *ComputeUtil) configureInstance(d *Driver) error {
300+
log.Infof("Configuring instance")
301+
302+
instance, err := c.instance()
295303
if err != nil {
296304
return err
297305
}
298306

307+
if err := c.addFirewallTag(instance); err != nil {
308+
return err
309+
}
310+
311+
return c.uploadSSHKey(instance, d.GetSSHKeyPath())
312+
}
313+
314+
// addFirewallTag adds a tag to the instance to match the firewall rule.
315+
func (c *ComputeUtil) addFirewallTag(instance *raw.Instance) error {
316+
log.Infof("Adding tag for the firewall rule")
317+
318+
tags := instance.Tags
319+
for _, tag := range tags.Items {
320+
if tag == firewallTargetTag {
321+
return nil
322+
}
323+
}
324+
325+
tags.Items = append(tags.Items, firewallTargetTag)
326+
327+
op, err := c.service.Instances.SetTags(c.project, c.zone, instance.Name, tags).Do()
328+
if err != nil {
329+
return err
330+
}
331+
332+
return c.waitForRegionalOp(op.Name)
333+
}
334+
335+
// uploadSSHKey updates the instance metadata with the given ssh key.
336+
func (c *ComputeUtil) uploadSSHKey(instance *raw.Instance, sshKeyPath string) error {
299337
log.Infof("Uploading SSH Key")
338+
339+
sshKey, err := ioutil.ReadFile(sshKeyPath + ".pub")
340+
if err != nil {
341+
return err
342+
}
343+
300344
metaDataValue := fmt.Sprintf("%s:%s %s\n", c.userName, strings.TrimSpace(string(sshKey)), c.userName)
301-
op, err = c.service.Instances.SetMetadata(c.project, c.zone, c.instanceName, &raw.Metadata{
345+
346+
op, err := c.service.Instances.SetMetadata(c.project, c.zone, c.instanceName, &raw.Metadata{
302347
Fingerprint: instance.Metadata.Fingerprint,
303348
Items: []*raw.MetadataItems{
304349
{
@@ -307,10 +352,6 @@ func (c *ComputeUtil) createInstance(d *Driver) error {
307352
},
308353
},
309354
}).Do()
310-
if err != nil {
311-
return err
312-
}
313-
log.Infof("Waiting for SSH Key")
314355

315356
return c.waitForRegionalOp(op.Name)
316357
}
@@ -360,14 +401,15 @@ func (c *ComputeUtil) startInstance() error {
360401
return c.waitForRegionalOp(op.Name)
361402
}
362403

404+
// waitForOp waits for the operation to finish.
363405
func (c *ComputeUtil) waitForOp(opGetter func() (*raw.Operation, error)) error {
364406
for {
365407
op, err := opGetter()
366408
if err != nil {
367409
return err
368410
}
369411

370-
log.Debugf("operation %q status: %s", op.Name, op.Status)
412+
log.Debugf("Operation %q status: %s", op.Name, op.Status)
371413
if op.Status == "DONE" {
372414
if op.Error != nil {
373415
return fmt.Errorf("Operation error: %v", *op.Error.Errors[0])
@@ -379,7 +421,7 @@ func (c *ComputeUtil) waitForOp(opGetter func() (*raw.Operation, error)) error {
379421
return nil
380422
}
381423

382-
// waitForOp waits for the operation to finish.
424+
// waitForRegionalOp waits for the regional operation to finish.
383425
func (c *ComputeUtil) waitForRegionalOp(name string) error {
384426
return c.waitForOp(func() (*raw.Operation, error) {
385427
return c.service.ZoneOperations.Get(c.project, c.zone, name).Do()

drivers/google/google.go

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ type Driver struct {
2626
DiskSize int
2727
Project string
2828
Tags string
29+
UseExisting bool
2930
}
3031

3132
const (
@@ -110,6 +111,11 @@ func (d *Driver) GetCreateFlags() []mcnflag.Flag {
110111
Usage: "Use internal GCE Instance IP rather than public one",
111112
EnvVar: "GOOGLE_USE_INTERNAL_IP",
112113
},
114+
mcnflag.BoolFlag{
115+
Name: "google-use-existing",
116+
Usage: "Don't create a new VM, use an existing one",
117+
EnvVar: "GOOGLE_USE_EXISTING",
118+
},
113119
}
114120
}
115121

@@ -156,15 +162,18 @@ func (d *Driver) SetConfigFromFlags(flags drivers.DriverOptions) error {
156162
}
157163

158164
d.Zone = flags.String("google-zone")
159-
d.MachineType = flags.String("google-machine-type")
160-
d.MachineImage = flags.String("google-machine-image")
161-
d.DiskSize = flags.Int("google-disk-size")
162-
d.DiskType = flags.String("google-disk-type")
163-
d.Address = flags.String("google-address")
164-
d.Preemptible = flags.Bool("google-preemptible")
165-
d.UseInternalIP = flags.Bool("google-use-internal-ip")
166-
d.Scopes = flags.String("google-scopes")
167-
d.Tags = flags.String("google-tags")
165+
d.UseExisting = flags.Bool("google-use-existing")
166+
if !d.UseExisting {
167+
d.MachineType = flags.String("google-machine-type")
168+
d.MachineImage = flags.String("google-machine-image")
169+
d.DiskSize = flags.Int("google-disk-size")
170+
d.DiskType = flags.String("google-disk-type")
171+
d.Address = flags.String("google-address")
172+
d.Preemptible = flags.Bool("google-preemptible")
173+
d.UseInternalIP = flags.Bool("google-use-internal-ip")
174+
d.Scopes = flags.String("google-scopes")
175+
d.Tags = flags.String("google-tags")
176+
}
168177
d.SSHUser = flags.String("google-username")
169178
d.SSHPort = 22
170179
d.SetSwarmConfigFromFlags(flags)
@@ -191,8 +200,15 @@ func (d *Driver) PreCreateCheck() error {
191200
// doesn't exist, so just check instance for nil.
192201
log.Infof("Check if the instance already exists")
193202

194-
if instance, _ := c.instance(); instance != nil {
195-
return fmt.Errorf("Instance %v already exists.", d.MachineName)
203+
instance, _ := c.instance()
204+
if d.UseExisting {
205+
if instance == nil {
206+
return fmt.Errorf("Unable to find instance %q in zone %q.", d.MachineName, d.Zone)
207+
}
208+
} else {
209+
if instance != nil {
210+
return fmt.Errorf("Instance %q already exists in zone %q.", d.MachineName, d.Zone)
211+
}
196212
}
197213

198214
return nil
@@ -213,6 +229,13 @@ func (d *Driver) Create() error {
213229
return err
214230
}
215231

232+
if err := c.openFirewallPorts(); err != nil {
233+
return err
234+
}
235+
236+
if d.UseExisting {
237+
return c.configureInstance(d)
238+
}
216239
return c.createInstance(d)
217240
}
218241

0 commit comments

Comments
 (0)