11package exoscale
22
33import (
4+ "bytes"
45 "encoding/base64"
56 "errors"
67 "fmt"
78 "io/ioutil"
89 "net"
910 "os"
10- "regexp"
11+ "os/user"
12+ "path/filepath"
1113 "strings"
1214
1315 "github.com/docker/machine/libmachine/drivers"
1416 "github.com/docker/machine/libmachine/log"
1517 "github.com/docker/machine/libmachine/mcnflag"
18+ "github.com/docker/machine/libmachine/mcnutils"
1619 "github.com/docker/machine/libmachine/state"
1720 "github.com/exoscale/egoscale"
1821)
@@ -26,9 +29,10 @@ type Driver struct {
2629 InstanceProfile string
2730 DiskSize int64
2831 Image string
29- SecurityGroup string
30- AffinityGroup string
32+ SecurityGroups [] string
33+ AffinityGroups [] string
3134 AvailabilityZone string
35+ SSHKey string
3236 KeyPair string
3337 PublicKey string
3438 UserDataFile string
@@ -43,6 +47,7 @@ const (
4347 defaultImage = "Linux Ubuntu 16.04 LTS 64-bit"
4448 defaultAvailabilityZone = "CH-DK-2"
4549 defaultSSHUser = "root"
50+ defaultSecurityGroup = "docker-machine"
4651 defaultAffinityGroupType = "host anti-affinity"
4752 defaultCloudInit = `#cloud-config
4853manage_etc_hosts: localhost
@@ -89,7 +94,7 @@ func (d *Driver) GetCreateFlags() []mcnflag.Flag {
8994 mcnflag.StringSliceFlag {
9095 EnvVar : "EXOSCALE_SECURITY_GROUP" ,
9196 Name : "exoscale-security-group" ,
92- Value : []string {},
97+ Value : []string {defaultSecurityGroup },
9398 Usage : "exoscale security group" ,
9499 },
95100 mcnflag.StringFlag {
@@ -104,6 +109,12 @@ func (d *Driver) GetCreateFlags() []mcnflag.Flag {
104109 Value : "" ,
105110 Usage : "name of the ssh user" ,
106111 },
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+ },
107118 mcnflag.StringFlag {
108119 EnvVar : "EXOSCALE_USERDATA" ,
109120 Name : "exoscale-userdata" ,
@@ -145,19 +156,23 @@ func (d *Driver) GetSSHHostname() (string, error) {
145156func (d * Driver ) GetSSHUsername () string {
146157 if d .SSHUser == "" {
147158 name := strings .ToLower (d .Image )
148- re := regexp .MustCompile (`\b[0-9.]+\b` )
149- version := re .FindString (d .Image )
150159
151160 if strings .Contains (name , "ubuntu" ) {
152161 return "ubuntu"
153162 }
154- if strings .Contains (name , "centos" ) && version >= "7.3" {
163+ if strings .Contains (name , "centos" ) {
155164 return "centos"
156165 }
166+ if strings .Contains (name , "redhat" ) {
167+ return "cloud-user"
168+ }
169+ if strings .Contains (name , "fedora" ) {
170+ return "fedora"
171+ }
157172 if strings .Contains (name , "coreos" ) {
158173 return "core"
159174 }
160- if strings .Contains (name , "debian" ) && version >= "8" {
175+ if strings .Contains (name , "debian" ) {
161176 return "debian"
162177 }
163178 return defaultSSHUser
@@ -180,17 +195,11 @@ func (d *Driver) SetConfigFromFlags(flags drivers.DriverOptions) error {
180195 d .InstanceProfile = flags .String ("exoscale-instance-profile" )
181196 d .DiskSize = int64 (flags .Int ("exoscale-disk-size" ))
182197 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" )
192200 d .AvailabilityZone = flags .String ("exoscale-availability-zone" )
193201 d .SSHUser = flags .String ("exoscale-ssh-user" )
202+ d .SSHKey = flags .String ("exoscale-ssh-key" )
194203 d .UserDataFile = flags .String ("exoscale-userdata" )
195204 d .SetSwarmConfigFromFlags (flags )
196205
@@ -394,7 +403,6 @@ func (d *Driver) Create() error {
394403 if err != nil {
395404 return err
396405 }
397- userData := base64 .StdEncoding .EncodeToString (cloudInit )
398406
399407 log .Infof ("Querying exoscale for the requested parameters..." )
400408 client := egoscale .NewClient (d .URL , d .APIKey , d .APISecretKey )
@@ -414,8 +422,16 @@ func (d *Driver) Create() error {
414422 // Image UUID
415423 var tpl string
416424 images , ok := topology .Images [strings .ToLower (d .Image )]
425+
417426 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 ]
419435 }
420436 if ! ok {
421437 return fmt .Errorf ("Unable to find image %v with size %d" ,
@@ -432,9 +448,12 @@ func (d *Driver) Create() error {
432448 log .Debugf ("Profile %v = %s" , d .InstanceProfile , profile )
433449
434450 // 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+
438457 sg , ok := topology .SecurityGroups [group ]
439458 if ! ok {
440459 log .Infof ("Security group %v does not exist, create it" , group )
@@ -445,13 +464,15 @@ func (d *Driver) Create() error {
445464 sg = securityGroup .ID
446465 }
447466 log .Debugf ("Security group %v = %s" , group , sg )
448- sgs [ idx ] = sg
467+ sgs = append ( sgs , sg )
449468 }
450469
451470 // 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+ }
455476 ag , ok := topology .AffinityGroups [group ]
456477 if ! ok {
457478 log .Infof ("Affinity Group %v does not exist, create it" , group )
@@ -462,32 +483,73 @@ func (d *Driver) Create() error {
462483 ag = affinityGroup .ID
463484 }
464485 log .Debugf ("Affinity group %v = %s" , group , ag )
465- ags [ idx ] = ag
486+ ags = append ( ags , ag )
466487 }
467488
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+ }
477537 }
478- d .KeyPair = keypairName
479538
480539 log .Infof ("Spawn exoscale host..." )
481540 log .Debugf ("Using the following cloud-init file:" )
482541 log .Debugf ("%s" , string (cloudInit ))
483542
543+ // Base64 encode the userdata
544+ userData := base64 .StdEncoding .EncodeToString (cloudInit )
545+
484546 req := & egoscale.DeployVirtualMachine {
485547 TemplateID : tpl ,
486548 ServiceOfferingID : profile ,
487549 UserData : userData ,
488550 ZoneID : zone ,
489- KeyPair : d .KeyPair ,
490551 Name : d .MachineName ,
552+ KeyPair : d .KeyPair ,
491553 DisplayName : d .MachineName ,
492554 RootDiskSize : d .DiskSize ,
493555 SecurityGroupIDs : sgs ,
@@ -506,6 +568,19 @@ func (d *Driver) Create() error {
506568 d .IPAddress = IPAddress .String ()
507569 }
508570 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+ }
509584
510585 return nil
511586}
@@ -547,19 +622,25 @@ func (d *Driver) Kill() error {
547622
548623// Remove destroys the VM instance and the associated SSH key.
549624func (d * Driver ) Remove () error {
550- cs := d .client ()
625+ client := d .client ()
551626
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+ }
555632 }
556633
557634 // 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+ }
559640
560641 log .Infof ("The Anti-Affinity group and Security group were not removed" )
561642
562- return err
643+ return nil
563644}
564645
565646// Build a cloud-init user data string that will install and run
0 commit comments