1
1
package exoscale
2
2
3
3
import (
4
+ "bytes"
4
5
"encoding/base64"
5
6
"errors"
6
7
"fmt"
7
8
"io/ioutil"
8
9
"net"
9
10
"os"
10
- "regexp"
11
+ "os/user"
12
+ "path/filepath"
11
13
"strings"
12
14
13
15
"github.com/docker/machine/libmachine/drivers"
14
16
"github.com/docker/machine/libmachine/log"
15
17
"github.com/docker/machine/libmachine/mcnflag"
18
+ "github.com/docker/machine/libmachine/mcnutils"
16
19
"github.com/docker/machine/libmachine/state"
17
20
"github.com/exoscale/egoscale"
18
21
)
@@ -26,9 +29,10 @@ type Driver struct {
26
29
InstanceProfile string
27
30
DiskSize int64
28
31
Image string
29
- SecurityGroup string
30
- AffinityGroup string
32
+ SecurityGroups [] string
33
+ AffinityGroups [] string
31
34
AvailabilityZone string
35
+ SSHKey string
32
36
KeyPair string
33
37
PublicKey string
34
38
UserDataFile string
@@ -43,6 +47,7 @@ const (
43
47
defaultImage = "Linux Ubuntu 16.04 LTS 64-bit"
44
48
defaultAvailabilityZone = "CH-DK-2"
45
49
defaultSSHUser = "root"
50
+ defaultSecurityGroup = "docker-machine"
46
51
defaultAffinityGroupType = "host anti-affinity"
47
52
defaultCloudInit = `#cloud-config
48
53
manage_etc_hosts: localhost
@@ -89,7 +94,7 @@ func (d *Driver) GetCreateFlags() []mcnflag.Flag {
89
94
mcnflag.StringSliceFlag {
90
95
EnvVar : "EXOSCALE_SECURITY_GROUP" ,
91
96
Name : "exoscale-security-group" ,
92
- Value : []string {},
97
+ Value : []string {defaultSecurityGroup },
93
98
Usage : "exoscale security group" ,
94
99
},
95
100
mcnflag.StringFlag {
@@ -104,6 +109,12 @@ func (d *Driver) GetCreateFlags() []mcnflag.Flag {
104
109
Value : "" ,
105
110
Usage : "name of the ssh user" ,
106
111
},
112
+ mcnflag.StringFlag {
113
+ EnvVar : "EXOSCALE_SSH_KEY" ,
114
+ Name : "exoscale-ssh-key" ,
115
+ Value : "" ,
116
+ Usage : "path to the SSH user private key" ,
117
+ },
107
118
mcnflag.StringFlag {
108
119
EnvVar : "EXOSCALE_USERDATA" ,
109
120
Name : "exoscale-userdata" ,
@@ -145,19 +156,23 @@ func (d *Driver) GetSSHHostname() (string, error) {
145
156
func (d * Driver ) GetSSHUsername () string {
146
157
if d .SSHUser == "" {
147
158
name := strings .ToLower (d .Image )
148
- re := regexp .MustCompile (`\b[0-9.]+\b` )
149
- version := re .FindString (d .Image )
150
159
151
160
if strings .Contains (name , "ubuntu" ) {
152
161
return "ubuntu"
153
162
}
154
- if strings .Contains (name , "centos" ) && version >= "7.3" {
163
+ if strings .Contains (name , "centos" ) {
155
164
return "centos"
156
165
}
166
+ if strings .Contains (name , "redhat" ) {
167
+ return "cloud-user"
168
+ }
169
+ if strings .Contains (name , "fedora" ) {
170
+ return "fedora"
171
+ }
157
172
if strings .Contains (name , "coreos" ) {
158
173
return "core"
159
174
}
160
- if strings .Contains (name , "debian" ) && version >= "8" {
175
+ if strings .Contains (name , "debian" ) {
161
176
return "debian"
162
177
}
163
178
return defaultSSHUser
@@ -180,17 +195,11 @@ func (d *Driver) SetConfigFromFlags(flags drivers.DriverOptions) error {
180
195
d .InstanceProfile = flags .String ("exoscale-instance-profile" )
181
196
d .DiskSize = int64 (flags .Int ("exoscale-disk-size" ))
182
197
d .Image = flags .String ("exoscale-image" )
183
- securityGroups := flags .StringSlice ("exoscale-security-group" )
184
- if len (securityGroups ) == 0 {
185
- securityGroups = []string {"docker-machine" }
186
- }
187
- d .SecurityGroup = strings .Join (securityGroups , "," )
188
- affinityGroups := flags .StringSlice ("exoscale-affinity-group" )
189
- if len (affinityGroups ) > 0 {
190
- d .AffinityGroup = strings .Join (affinityGroups , "," )
191
- }
198
+ d .SecurityGroups = flags .StringSlice ("exoscale-security-group" )
199
+ d .AffinityGroups = flags .StringSlice ("exoscale-affinity-group" )
192
200
d .AvailabilityZone = flags .String ("exoscale-availability-zone" )
193
201
d .SSHUser = flags .String ("exoscale-ssh-user" )
202
+ d .SSHKey = flags .String ("exoscale-ssh-key" )
194
203
d .UserDataFile = flags .String ("exoscale-userdata" )
195
204
d .SetSwarmConfigFromFlags (flags )
196
205
@@ -394,7 +403,6 @@ func (d *Driver) Create() error {
394
403
if err != nil {
395
404
return err
396
405
}
397
- userData := base64 .StdEncoding .EncodeToString (cloudInit )
398
406
399
407
log .Infof ("Querying exoscale for the requested parameters..." )
400
408
client := egoscale .NewClient (d .URL , d .APIKey , d .APISecretKey )
@@ -414,8 +422,16 @@ func (d *Driver) Create() error {
414
422
// Image UUID
415
423
var tpl string
416
424
images , ok := topology .Images [strings .ToLower (d .Image )]
425
+
417
426
if ok {
418
- tpl , ok = images [d .DiskSize ]
427
+ smallestDiskSize := d .DiskSize
428
+ for s := range images {
429
+ if s < smallestDiskSize {
430
+ smallestDiskSize = s
431
+ }
432
+ }
433
+
434
+ tpl , ok = images [smallestDiskSize ]
419
435
}
420
436
if ! ok {
421
437
return fmt .Errorf ("Unable to find image %v with size %d" ,
@@ -432,9 +448,12 @@ func (d *Driver) Create() error {
432
448
log .Debugf ("Profile %v = %s" , d .InstanceProfile , profile )
433
449
434
450
// Security groups
435
- securityGroups := strings .Split (d .SecurityGroup , "," )
436
- sgs := make ([]string , len (securityGroups ))
437
- for idx , group := range securityGroups {
451
+ sgs := make ([]string , 0 , len (d .SecurityGroups ))
452
+ for _ , group := range d .SecurityGroups {
453
+ if group == "" {
454
+ continue
455
+ }
456
+
438
457
sg , ok := topology .SecurityGroups [group ]
439
458
if ! ok {
440
459
log .Infof ("Security group %v does not exist, create it" , group )
@@ -445,13 +464,15 @@ func (d *Driver) Create() error {
445
464
sg = securityGroup .ID
446
465
}
447
466
log .Debugf ("Security group %v = %s" , group , sg )
448
- sgs [ idx ] = sg
467
+ sgs = append ( sgs , sg )
449
468
}
450
469
451
470
// Affinity Groups
452
- affinityGroups := strings .Split (d .AffinityGroup , "," )
453
- ags := make ([]string , len (affinityGroups ))
454
- for idx , group := range affinityGroups {
471
+ ags := make ([]string , 0 , len (d .AffinityGroups ))
472
+ for _ , group := range d .AffinityGroups {
473
+ if group == "" {
474
+ continue
475
+ }
455
476
ag , ok := topology .AffinityGroups [group ]
456
477
if ! ok {
457
478
log .Infof ("Affinity Group %v does not exist, create it" , group )
@@ -462,32 +483,73 @@ func (d *Driver) Create() error {
462
483
ag = affinityGroup .ID
463
484
}
464
485
log .Debugf ("Affinity group %v = %s" , group , ag )
465
- ags [ idx ] = ag
486
+ ags = append ( ags , ag )
466
487
}
467
488
468
- log .Infof ("Generate an SSH keypair..." )
469
- keypairName := fmt .Sprintf ("docker-machine-%s" , d .MachineName )
470
- kpresp , err := client .CreateKeypair (keypairName )
471
- if err != nil {
472
- return err
473
- }
474
- err = ioutil .WriteFile (d .GetSSHKeyPath (), []byte (kpresp .PrivateKey ), 0600 )
475
- if err != nil {
476
- return err
489
+ // SSH key pair
490
+ if d .SSHKey == "" {
491
+ var keyPairName string
492
+ keyPairName = fmt .Sprintf ("docker-machine-%s" , d .MachineName )
493
+ log .Infof ("Generate an SSH keypair..." )
494
+ resp , err := client .Request (& egoscale.CreateSSHKeyPair {
495
+ Name : keyPairName ,
496
+ })
497
+ if err != nil {
498
+ return fmt .Errorf ("SSH Key pair creation failed %s" , err )
499
+ }
500
+ keyPair := resp .(* egoscale.CreateSSHKeyPairResponse ).KeyPair
501
+ if err = ioutil .WriteFile (d .GetSSHKeyPath (), []byte (keyPair .PrivateKey ), 0600 ); err != nil {
502
+ return fmt .Errorf ("SSH public key could not be written %s" , err )
503
+ }
504
+ d .KeyPair = keyPairName
505
+ } else {
506
+ log .Infof ("Importing SSH key from %s" , d .SSHKey )
507
+
508
+ sshKey := d .SSHKey
509
+ if strings .HasPrefix (sshKey , "~/" ) {
510
+ usr , _ := user .Current ()
511
+ sshKey = filepath .Join (usr .HomeDir , sshKey [2 :])
512
+ } else {
513
+ var err error
514
+ if sshKey , err = filepath .Abs (sshKey ); err != nil {
515
+ return err
516
+ }
517
+ }
518
+
519
+ // Sending the SSH public key through the cloud-init config
520
+ pubKey , err := ioutil .ReadFile (sshKey + ".pub" )
521
+ if err != nil {
522
+ return fmt .Errorf ("Cannot read SSH public key %s" , err )
523
+ }
524
+
525
+ sshAuthorizedKeys := `
526
+ ssh_authorized_keys:
527
+ - `
528
+ cloudInit = bytes .Join ([][]byte {cloudInit , []byte (sshAuthorizedKeys ), pubKey }, []byte ("" ))
529
+
530
+ // Copying the private key into docker-machine
531
+ if err := mcnutils .CopyFile (sshKey , d .GetSSHKeyPath ()); err != nil {
532
+ return fmt .Errorf ("Unable to copy SSH file: %s" , err )
533
+ }
534
+ if err := os .Chmod (d .GetSSHKeyPath (), 0600 ); err != nil {
535
+ return fmt .Errorf ("Unable to set permissions on the SSH file: %s" , err )
536
+ }
477
537
}
478
- d .KeyPair = keypairName
479
538
480
539
log .Infof ("Spawn exoscale host..." )
481
540
log .Debugf ("Using the following cloud-init file:" )
482
541
log .Debugf ("%s" , string (cloudInit ))
483
542
543
+ // Base64 encode the userdata
544
+ userData := base64 .StdEncoding .EncodeToString (cloudInit )
545
+
484
546
req := & egoscale.DeployVirtualMachine {
485
547
TemplateID : tpl ,
486
548
ServiceOfferingID : profile ,
487
549
UserData : userData ,
488
550
ZoneID : zone ,
489
- KeyPair : d .KeyPair ,
490
551
Name : d .MachineName ,
552
+ KeyPair : d .KeyPair ,
491
553
DisplayName : d .MachineName ,
492
554
RootDiskSize : d .DiskSize ,
493
555
SecurityGroupIDs : sgs ,
@@ -506,6 +568,19 @@ func (d *Driver) Create() error {
506
568
d .IPAddress = IPAddress .String ()
507
569
}
508
570
d .ID = vm .ID
571
+ log .Infof ("IP Address: %v, SSH User: %v" , d .IPAddress , d .GetSSHUsername ())
572
+
573
+ // Destroy the SSH key from CloudStack
574
+ if d .KeyPair != "" {
575
+ if err := drivers .WaitForSSH (d ); err != nil {
576
+ return err
577
+ }
578
+
579
+ if err := client .BooleanRequest (& egoscale.DeleteSSHKeyPair {Name : d .KeyPair }); err != nil {
580
+ return err
581
+ }
582
+ d .KeyPair = ""
583
+ }
509
584
510
585
return nil
511
586
}
@@ -547,19 +622,25 @@ func (d *Driver) Kill() error {
547
622
548
623
// Remove destroys the VM instance and the associated SSH key.
549
624
func (d * Driver ) Remove () error {
550
- cs := d .client ()
625
+ client := d .client ()
551
626
552
- // Destroy the SSH key
553
- if err := cs .BooleanRequest (& egoscale.DeleteSSHKeyPair {Name : d .KeyPair }); err != nil {
554
- return err
627
+ // Destroy the SSH key from CloudStack
628
+ if d .KeyPair != "" {
629
+ if err := client .BooleanRequest (& egoscale.DeleteSSHKeyPair {Name : d .KeyPair }); err != nil {
630
+ return err
631
+ }
555
632
}
556
633
557
634
// Destroy the virtual machine
558
- _ , err := cs .AsyncRequest (& egoscale.DestroyVirtualMachine {ID : d .ID }, d .async )
635
+ if d .ID != "" {
636
+ if _ , err := client .AsyncRequest (& egoscale.DestroyVirtualMachine {ID : d .ID }, d .async ); err != nil {
637
+ return err
638
+ }
639
+ }
559
640
560
641
log .Infof ("The Anti-Affinity group and Security group were not removed" )
561
642
562
- return err
643
+ return nil
563
644
}
564
645
565
646
// Build a cloud-init user data string that will install and run
0 commit comments