Skip to content

Commit

Permalink
Add cloudstack ssh key resource and attr for instances
Browse files Browse the repository at this point in the history
  • Loading branch information
Benjamin vickers committed Jun 2, 2015
1 parent 434d0c9 commit 5e9d385
Show file tree
Hide file tree
Showing 6 changed files with 354 additions and 26 deletions.
1 change: 1 addition & 0 deletions builtin/providers/cloudstack/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ func Provider() terraform.ResourceProvider {
"cloudstack_network_acl_rule": resourceCloudStackNetworkACLRule(),
"cloudstack_nic": resourceCloudStackNIC(),
"cloudstack_port_forward": resourceCloudStackPortForward(),
"cloudstack_ssh_keypair": resourceCloudStackSSHKeyPair(),
"cloudstack_template": resourceCloudStackTemplate(),
"cloudstack_vpc": resourceCloudStackVPC(),
"cloudstack_vpn_connection": resourceCloudStackVPNConnection(),
Expand Down
2 changes: 2 additions & 0 deletions builtin/providers/cloudstack/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,8 @@ var CLOUDSTACK_VPC_OFFERING = ""
var CLOUDSTACK_VPC_NETWORK_CIDR = ""
var CLOUDSTACK_VPC_NETWORK_OFFERING = ""
var CLOUDSTACK_PUBLIC_IPADDRESS = ""
var CLOUDSTACK_SSH_KEYPAIR = ""
var CLOUDSTACK_SSH_PUBLIC_KEY = ""
var CLOUDSTACK_TEMPLATE = ""
var CLOUDSTACK_TEMPLATE_FORMAT = ""
var CLOUDSTACK_TEMPLATE_URL = ""
Expand Down
75 changes: 51 additions & 24 deletions builtin/providers/cloudstack/resource_cloudstack_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,11 @@ func resourceCloudStackInstance() *schema.Resource {
ForceNew: true,
},

"keypair": &schema.Schema{
Type: schema.TypeString,
Optional: true,
},

"user_data": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Expand Down Expand Up @@ -136,6 +141,10 @@ func resourceCloudStackInstanceCreate(d *schema.ResourceData, meta interface{})
p.SetIpaddress(ipaddres.(string))
}

if keypair, ok := d.GetOk("keypair"); ok {
p.SetKeypair(keypair.(string))
}

// If the user data contains any info, it needs to be base64 encoded and
// added to the parameter struct
if userData, ok := d.GetOk("user_data"); ok {
Expand Down Expand Up @@ -186,6 +195,7 @@ func resourceCloudStackInstanceRead(d *schema.ResourceData, meta interface{}) er
d.Set("display_name", vm.Displayname)
d.Set("ipaddress", vm.Nic[0].Ipaddress)
d.Set("zone", vm.Zonename)
//NB cloudstack sometimes sends back the wrong keypair name, so dont update it

setValueOrUUID(d, "network", vm.Nic[0].Networkname, vm.Nic[0].Networkid)
setValueOrUUID(d, "service_offering", vm.Serviceofferingname, vm.Serviceofferingid)
Expand Down Expand Up @@ -220,41 +230,58 @@ func resourceCloudStackInstanceUpdate(d *schema.ResourceData, meta interface{})
d.SetPartial("display_name")
}

// Check if the service offering is changed and if so, update the offering
if d.HasChange("service_offering") {
log.Printf("[DEBUG] Service offering changed for %s, starting update", name)

// Retrieve the service_offering UUID
serviceofferingid, e := retrieveUUID(cs, "service_offering", d.Get("service_offering").(string))
if e != nil {
return e.Error()
}

// Create a new parameter struct
p := cs.VirtualMachine.NewChangeServiceForVirtualMachineParams(d.Id(), serviceofferingid)

// Before we can actually change the service offering, the virtual machine must be stopped
// Attributes that require reboot to update
if d.HasChange("service_offering") || d.HasChange("keypair") {
// Before we can actually make these changes, the virtual machine must be stopped
_, err := cs.VirtualMachine.StopVirtualMachine(cs.VirtualMachine.NewStopVirtualMachineParams(d.Id()))
if err != nil {
return fmt.Errorf(
"Error stopping instance %s before changing service offering: %s", name, err)
"Error stopping instance %s before making changes: %s", name, err)
}
// Change the service offering
_, err = cs.VirtualMachine.ChangeServiceForVirtualMachine(p)
if err != nil {
return fmt.Errorf(
"Error changing the service offering for instance %s: %s", name, err)

// Check if the service offering is changed and if so, update the offering
if d.HasChange("service_offering") {
log.Printf("[DEBUG] Service offering changed for %s, starting update", name)

// Retrieve the service_offering UUID
serviceofferingid, e := retrieveUUID(cs, "service_offering", d.Get("service_offering").(string))
if e != nil {
return e.Error()
}

// Create a new parameter struct
p := cs.VirtualMachine.NewChangeServiceForVirtualMachineParams(d.Id(), serviceofferingid)

// Change the service offering
_, err = cs.VirtualMachine.ChangeServiceForVirtualMachine(p)
if err != nil {
return fmt.Errorf(
"Error changing the service offering for instance %s: %s", name, err)
}
d.SetPartial("service_offering")
}

if d.HasChange("keypair") {
log.Printf("[DEBUG] SSH keypair changed for %s, starting update", name)

p := cs.SSH.NewResetSSHKeyForVirtualMachineParams(d.Id(), d.Get("keypair").(string))

// Change the ssh keypair
_, err = cs.SSH.ResetSSHKeyForVirtualMachine(p)
if err != nil {
return fmt.Errorf(
"Error changing the SSH keypair for instance %s: %s", name, err)
}
d.SetPartial("keypair")
}

// Start the virtual machine again
_, err = cs.VirtualMachine.StartVirtualMachine(cs.VirtualMachine.NewStartVirtualMachineParams(d.Id()))
if err != nil {
return fmt.Errorf(
"Error starting instance %s after changing service offering: %s", name, err)
"Error starting instance %s after making changes", name)
}

d.SetPartial("service_offering")
}

d.Partial(false)
return resourceCloudStackInstanceRead(d, meta)
}
Expand Down
20 changes: 18 additions & 2 deletions builtin/providers/cloudstack/resource_cloudstack_instance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ func TestAccCloudStackInstance_basic(t *testing.T) {
"cloudstack_instance.foobar",
"user_data",
"0cf3dcdc356ec8369494cb3991985ecd5296cdd5"),
resource.TestCheckResourceAttr(
"cloudstack_instance.foobar",
"keypair",
CLOUDSTACK_SSH_KEYPAIR),
),
},
},
Expand All @@ -47,6 +51,14 @@ func TestAccCloudStackInstance_update(t *testing.T) {
testAccCheckCloudStackInstanceExists(
"cloudstack_instance.foobar", &instance),
testAccCheckCloudStackInstanceAttributes(&instance),
resource.TestCheckResourceAttr(
"cloudstack_instance.foobar",
"user_data",
"0cf3dcdc356ec8369494cb3991985ecd5296cdd5"),
resource.TestCheckResourceAttr(
"cloudstack_instance.foobar",
"keypair",
CLOUDSTACK_SSH_KEYPAIR),
),
},

Expand Down Expand Up @@ -193,13 +205,15 @@ resource "cloudstack_instance" "foobar" {
network = "%s"
template = "%s"
zone = "%s"
keypair = "%s"
user_data = "foobar\nfoo\nbar"
expunge = true
}`,
CLOUDSTACK_SERVICE_OFFERING_1,
CLOUDSTACK_NETWORK_1,
CLOUDSTACK_TEMPLATE,
CLOUDSTACK_ZONE)
CLOUDSTACK_ZONE,
CLOUDSTACK_SSH_KEYPAIR)

var testAccCloudStackInstance_renameAndResize = fmt.Sprintf(`
resource "cloudstack_instance" "foobar" {
Expand All @@ -209,13 +223,15 @@ resource "cloudstack_instance" "foobar" {
network = "%s"
template = "%s"
zone = "%s"
keypair = "%s"
user_data = "foobar\nfoo\nbar"
expunge = true
}`,
CLOUDSTACK_SERVICE_OFFERING_2,
CLOUDSTACK_NETWORK_1,
CLOUDSTACK_TEMPLATE,
CLOUDSTACK_ZONE)
CLOUDSTACK_ZONE,
CLOUDSTACK_SSH_KEYPAIR)

var testAccCloudStackInstance_fixedIP = fmt.Sprintf(`
resource "cloudstack_instance" "foobar" {
Expand Down
122 changes: 122 additions & 0 deletions builtin/providers/cloudstack/resource_cloudstack_ssh_keypair.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package cloudstack

import (
"fmt"
"log"
"strings"

"github.com/hashicorp/terraform/helper/schema"
"github.com/xanzy/go-cloudstack/cloudstack"
)

func resourceCloudStackSSHKeyPair() *schema.Resource {
return &schema.Resource{
Create: resourceCloudStackSSHKeyPairCreate,
Read: resourceCloudStackSSHKeyPairRead,
Delete: resourceCloudStackSSHKeyPairDelete,

Schema: map[string]*schema.Schema{
"name": &schema.Schema{
Type: schema.TypeString,
Required: true,
ForceNew: true,
},

"public_key": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ForceNew: true,
},

"private_key": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},

"fingerprint": &schema.Schema{
Type: schema.TypeString,
Computed: true,
},
},
}
}

func resourceCloudStackSSHKeyPairCreate(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)

name := d.Get("name").(string)
publicKey := d.Get("public_key").(string)

if publicKey != "" {
//register key supplied
p := cs.SSH.NewRegisterSSHKeyPairParams(name, publicKey)
r, err := cs.SSH.RegisterSSHKeyPair(p)
if err != nil {
return err
}
log.Printf("[DEBUG] RegisterSSHKeyPair response: %+v\n", r)
log.Printf("[DEBUG] Key pair successfully registered at Cloudstack")
d.SetId(name)
} else {
//no key supplied, must create one and return the private key
p := cs.SSH.NewCreateSSHKeyPairParams(name)
r, err := cs.SSH.CreateSSHKeyPair(p)
if err != nil {
return err
}
log.Printf("[DEBUG] CreateSSHKeyPair response: %+v\n", r)
log.Printf("[DEBUG] Key pair successfully generated at Cloudstack")
log.Printf("[DEBUG] Private key returned: %s", r.Privatekey)
d.Set("private_key", r.Privatekey)
d.SetId(name)
}

return resourceCloudStackSSHKeyPairRead(d, meta)
}

func resourceCloudStackSSHKeyPairRead(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)

log.Printf("[DEBUG] looking for ssh key %s with name %s", d.Id(), d.Get("name").(string))

p := cs.SSH.NewListSSHKeyPairsParams()
p.SetName(d.Get("name").(string))

r, err := cs.SSH.ListSSHKeyPairs(p)
if err != nil {
return err
}
if r.Count == 0 {
log.Printf("[DEBUG] Key pair %s does not exist", d.Get("name").(string))
d.Set("name", "")
return nil
}

//SSHKeyPair name is unique in a cloudstack account so dont need to check for multiple
d.Set("name", r.SSHKeyPairs[0].Name)
d.Set("fingerprint", r.SSHKeyPairs[0].Fingerprint)
log.Printf("[DEBUG] Read ssh key pair %+v\n", d)

return nil
}

func resourceCloudStackSSHKeyPairDelete(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)

// Create a new parameter struct
p := cs.SSH.NewDeleteSSHKeyPairParams(d.Get("name").(string))

// Remove the SSH Keypair
_, err := cs.SSH.DeleteSSHKeyPair(p)
if err != nil {
// This is a very poor way to be told the UUID does no longer exist :(
if strings.Contains(err.Error(), fmt.Sprintf(
"A key pair with name '%s' does not exist for account", d.Get("name").(string))) {
return nil
}

return fmt.Errorf("Error deleting SSH Keypair: %s", err)
}

return nil
}
Loading

0 comments on commit 5e9d385

Please sign in to comment.