Skip to content

Commit

Permalink
Fix: #117 - Allow Multiple SSH Keypairs (#122)
Browse files Browse the repository at this point in the history
  • Loading branch information
CodeBleu authored Jul 30, 2024
1 parent 3cb0d3a commit 997dbb2
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 9 deletions.
53 changes: 46 additions & 7 deletions cloudstack/resource_cloudstack_instance.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,17 @@ func resourceCloudStackInstance() *schema.Resource {
ForceNew: true,
},

"keypair": {
Type: schema.TypeString,
Optional: true,
"keypair": &schema.Schema{
Type: schema.TypeString,
Optional: true,
ConflictsWith: []string{"keypairs"},
},

"keypairs": {
Type: schema.TypeList,
Optional: true,
Elem: &schema.Schema{Type: schema.TypeString},
ConflictsWith: []string{"keypair"},
},

"host_id": {
Expand Down Expand Up @@ -213,6 +221,7 @@ func resourceCloudStackInstance() *schema.Resource {
}

func resourceCloudStackInstanceCreate(d *schema.ResourceData, meta interface{}) error {

cs := meta.(*cloudstack.CloudStackClient)

// Retrieve the service_offering ID
Expand Down Expand Up @@ -354,6 +363,14 @@ func resourceCloudStackInstanceCreate(d *schema.ResourceData, meta interface{})
p.SetKeypair(keypair.(string))
}

if keypairs, ok := d.GetOk("keypairs"); ok {
var keypairStrings []string
for _, kp := range keypairs.([]interface{}) {
keypairStrings = append(keypairStrings, fmt.Sprintf("%v", kp))
}
p.SetKeypairs(keypairStrings)
}

// If a host_id is supplied, add it to the parameter struct

if hostid, ok := d.GetOk("host_id"); ok {
Expand Down Expand Up @@ -493,6 +510,7 @@ func resourceCloudStackInstanceRead(d *schema.ResourceData, meta interface{}) er
}

func resourceCloudStackInstanceUpdate(d *schema.ResourceData, meta interface{}) error {

cs := meta.(*cloudstack.CloudStackClient)

name := d.Get("name").(string)
Expand Down Expand Up @@ -537,7 +555,8 @@ func resourceCloudStackInstanceUpdate(d *schema.ResourceData, meta interface{})

// Attributes that require reboot to update
if d.HasChange("name") || d.HasChange("service_offering") || d.HasChange("affinity_group_ids") ||
d.HasChange("affinity_group_names") || d.HasChange("keypair") || d.HasChange("user_data") {
d.HasChange("affinity_group_names") || d.HasChange("keypair") || d.HasChange("keypairs") || d.HasChange("user_data") {

// Before we can actually make these changes, the virtual machine must be stopped
_, err := cs.VirtualMachine.StopVirtualMachine(
cs.VirtualMachine.NewStopVirtualMachineParams(d.Id()))
Expand Down Expand Up @@ -631,15 +650,35 @@ func resourceCloudStackInstanceUpdate(d *schema.ResourceData, meta interface{})
}

// Check if the keypair has changed and if so, update the keypair
if d.HasChange("keypair") {
log.Printf("[DEBUG] SSH keypair changed for %s, starting update", name)
if d.HasChange("keypair") || d.HasChange("keypairs") {
log.Printf("[DEBUG] SSH keypair(s) changed for %s, starting update", name)

p := cs.SSH.NewResetSSHKeyForVirtualMachineParams(d.Id())

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

if keypairs, ok := d.GetOk("keypairs"); ok {

// Convert keypairsInterface to []interface{}
keypairsInterfaces := keypairs.([]interface{})

// Now, safely convert []interface{} to []string with error handling
strKeyPairs := make([]string, len(keypairsInterfaces))

for i, v := range keypairsInterfaces {
switch v := v.(type) {
case string:
strKeyPairs[i] = v
default:
log.Printf("Value at index %d is not a string: %v", i, v)
continue
}
}
p.SetKeypairs(strKeyPairs)
}

// If there is a project supplied, we retrieve and set the project id
if err := setProjectid(p, cs, d); err != nil {
return err
Expand All @@ -648,7 +687,7 @@ func resourceCloudStackInstanceUpdate(d *schema.ResourceData, meta interface{})
_, err = cs.SSH.ResetSSHKeyForVirtualMachine(p)
if err != nil {
return fmt.Errorf(
"Error changing the SSH keypair for instance %s: %s", name, err)
"Error changing the SSH keypair(s) for instance %s: %s", name, err)
}
}

Expand Down
89 changes: 89 additions & 0 deletions cloudstack/resource_cloudstack_instance_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,68 @@ func TestAccCloudStackInstance_keyPair(t *testing.T) {
})
}

func TestAccCloudStackInstance_keyPairs(t *testing.T) {
var instance cloudstack.VirtualMachine

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckCloudStackInstanceDestroy,
Steps: []resource.TestStep{
{
Config: testAccCloudStackInstance_keyPairs,
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudStackInstanceExists("cloudstack_instance.foobar", &instance),
resource.TestCheckResourceAttr("cloudstack_instance.foobar", "keypairs.#", "2"), // Expecting 2 key pairs
resource.TestCheckResourceAttr("cloudstack_instance.foobar", "keypairs.0", "terraform-test-keypair-foo"),
resource.TestCheckResourceAttr("cloudstack_instance.foobar", "keypairs.1", "terraform-test-keypair-bar"),
),
},
},
})
}

func TestAccCloudStackInstance_keyPair_update(t *testing.T) {
var instance cloudstack.VirtualMachine

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckCloudStackInstanceDestroy,
Steps: []resource.TestStep{
{
Config: testAccCloudStackInstance_keyPair,
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudStackInstanceExists(
"cloudstack_instance.foobar", &instance),
resource.TestCheckResourceAttr(
"cloudstack_instance.foobar", "keypair", "terraform-test-keypair"),
),
},

{
Config: testAccCloudStackInstance_keyPairs,
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudStackInstanceExists("cloudstack_instance.foobar", &instance),
resource.TestCheckResourceAttr("cloudstack_instance.foobar", "keypairs.#", "2"), // Expecting 2 key pairs
resource.TestCheckResourceAttr("cloudstack_instance.foobar", "keypairs.0", "terraform-test-keypair-foo"),
resource.TestCheckResourceAttr("cloudstack_instance.foobar", "keypairs.1", "terraform-test-keypair-bar"),
),
},

{
Config: testAccCloudStackInstance_keyPair,
Check: resource.ComposeTestCheckFunc(
testAccCheckCloudStackInstanceExists(
"cloudstack_instance.foobar", &instance),
resource.TestCheckResourceAttr(
"cloudstack_instance.foobar", "keypair", "terraform-test-keypair"),
),
},
},
})
}

func TestAccCloudStackInstance_project(t *testing.T) {
var instance cloudstack.VirtualMachine

Expand Down Expand Up @@ -416,6 +478,33 @@ resource "cloudstack_instance" "foobar" {
expunge = true
}`

const testAccCloudStackInstance_keyPairs = `
resource "cloudstack_network" "foo" {
name = "terraform-network"
cidr = "10.1.1.0/24"
network_offering = "DefaultIsolatedNetworkOfferingWithSourceNatService"
zone = "Sandbox-simulator"
}
resource "cloudstack_ssh_keypair" "foo" {
name = "terraform-test-keypair-foo"
}
resource "cloudstack_ssh_keypair" "bar" {
name = "terraform-test-keypair-bar"
}
resource "cloudstack_instance" "foobar" {
name = "terraform-test"
display_name = "terraform-test"
service_offering= "Small Instance"
network_id = "${cloudstack_network.foo.id}"
template = "CentOS 5.6 (64-bit) no GUI (Simulator)"
zone = "Sandbox-simulator"
keypairs = ["${cloudstack_ssh_keypair.foo.name}", "${cloudstack_ssh_keypair.bar.name}"]
expunge = true
}`

const testAccCloudStackInstance_project = `
resource "cloudstack_network" "foo" {
name = "terraform-network"
Expand Down
7 changes: 5 additions & 2 deletions website/docs/r/instance.html.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ The following arguments are supported:
* `service_offering` - (Required) The name or ID of the service offering used
for this instance.

* `host_id` - (Optional) destination Host ID to deploy the VM to - parameter available
* `host_id` - (Optional) destination Host ID to deploy the VM to - parameter available
for root admin only

* `pod_id` - (Optional) destination Pod ID to deploy the VM to - parameter available for root admin only
Expand Down Expand Up @@ -82,7 +82,10 @@ The following arguments are supported:
instance. This can be either plain text or base64 encoded text.

* `keypair` - (Optional) The name of the SSH key pair that will be used to
access this instance.
access this instance. (Mutual exclusive with keypairs)

* `keypairs` - (Optional) A list of SSH key pair names that will be used to
access this instance. (Mutual exclusive with keypair)

* `expunge` - (Optional) This determines if the instance is expunged when it is
destroyed (defaults false)
Expand Down

0 comments on commit 997dbb2

Please sign in to comment.