Skip to content

Commit 997dbb2

Browse files
authored
Fix: #117 - Allow Multiple SSH Keypairs (#122)
1 parent 3cb0d3a commit 997dbb2

File tree

3 files changed

+140
-9
lines changed

3 files changed

+140
-9
lines changed

cloudstack/resource_cloudstack_instance.go

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -139,9 +139,17 @@ func resourceCloudStackInstance() *schema.Resource {
139139
ForceNew: true,
140140
},
141141

142-
"keypair": {
143-
Type: schema.TypeString,
144-
Optional: true,
142+
"keypair": &schema.Schema{
143+
Type: schema.TypeString,
144+
Optional: true,
145+
ConflictsWith: []string{"keypairs"},
146+
},
147+
148+
"keypairs": {
149+
Type: schema.TypeList,
150+
Optional: true,
151+
Elem: &schema.Schema{Type: schema.TypeString},
152+
ConflictsWith: []string{"keypair"},
145153
},
146154

147155
"host_id": {
@@ -213,6 +221,7 @@ func resourceCloudStackInstance() *schema.Resource {
213221
}
214222

215223
func resourceCloudStackInstanceCreate(d *schema.ResourceData, meta interface{}) error {
224+
216225
cs := meta.(*cloudstack.CloudStackClient)
217226

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

366+
if keypairs, ok := d.GetOk("keypairs"); ok {
367+
var keypairStrings []string
368+
for _, kp := range keypairs.([]interface{}) {
369+
keypairStrings = append(keypairStrings, fmt.Sprintf("%v", kp))
370+
}
371+
p.SetKeypairs(keypairStrings)
372+
}
373+
357374
// If a host_id is supplied, add it to the parameter struct
358375

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

495512
func resourceCloudStackInstanceUpdate(d *schema.ResourceData, meta interface{}) error {
513+
496514
cs := meta.(*cloudstack.CloudStackClient)
497515

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

538556
// Attributes that require reboot to update
539557
if d.HasChange("name") || d.HasChange("service_offering") || d.HasChange("affinity_group_ids") ||
540-
d.HasChange("affinity_group_names") || d.HasChange("keypair") || d.HasChange("user_data") {
558+
d.HasChange("affinity_group_names") || d.HasChange("keypair") || d.HasChange("keypairs") || d.HasChange("user_data") {
559+
541560
// Before we can actually make these changes, the virtual machine must be stopped
542561
_, err := cs.VirtualMachine.StopVirtualMachine(
543562
cs.VirtualMachine.NewStopVirtualMachineParams(d.Id()))
@@ -631,15 +650,35 @@ func resourceCloudStackInstanceUpdate(d *schema.ResourceData, meta interface{})
631650
}
632651

633652
// Check if the keypair has changed and if so, update the keypair
634-
if d.HasChange("keypair") {
635-
log.Printf("[DEBUG] SSH keypair changed for %s, starting update", name)
653+
if d.HasChange("keypair") || d.HasChange("keypairs") {
654+
log.Printf("[DEBUG] SSH keypair(s) changed for %s, starting update", name)
636655

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

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

662+
if keypairs, ok := d.GetOk("keypairs"); ok {
663+
664+
// Convert keypairsInterface to []interface{}
665+
keypairsInterfaces := keypairs.([]interface{})
666+
667+
// Now, safely convert []interface{} to []string with error handling
668+
strKeyPairs := make([]string, len(keypairsInterfaces))
669+
670+
for i, v := range keypairsInterfaces {
671+
switch v := v.(type) {
672+
case string:
673+
strKeyPairs[i] = v
674+
default:
675+
log.Printf("Value at index %d is not a string: %v", i, v)
676+
continue
677+
}
678+
}
679+
p.SetKeypairs(strKeyPairs)
680+
}
681+
643682
// If there is a project supplied, we retrieve and set the project id
644683
if err := setProjectid(p, cs, d); err != nil {
645684
return err
@@ -648,7 +687,7 @@ func resourceCloudStackInstanceUpdate(d *schema.ResourceData, meta interface{})
648687
_, err = cs.SSH.ResetSSHKeyForVirtualMachine(p)
649688
if err != nil {
650689
return fmt.Errorf(
651-
"Error changing the SSH keypair for instance %s: %s", name, err)
690+
"Error changing the SSH keypair(s) for instance %s: %s", name, err)
652691
}
653692
}
654693

cloudstack/resource_cloudstack_instance_test.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,68 @@ func TestAccCloudStackInstance_keyPair(t *testing.T) {
150150
})
151151
}
152152

153+
func TestAccCloudStackInstance_keyPairs(t *testing.T) {
154+
var instance cloudstack.VirtualMachine
155+
156+
resource.Test(t, resource.TestCase{
157+
PreCheck: func() { testAccPreCheck(t) },
158+
Providers: testAccProviders,
159+
CheckDestroy: testAccCheckCloudStackInstanceDestroy,
160+
Steps: []resource.TestStep{
161+
{
162+
Config: testAccCloudStackInstance_keyPairs,
163+
Check: resource.ComposeTestCheckFunc(
164+
testAccCheckCloudStackInstanceExists("cloudstack_instance.foobar", &instance),
165+
resource.TestCheckResourceAttr("cloudstack_instance.foobar", "keypairs.#", "2"), // Expecting 2 key pairs
166+
resource.TestCheckResourceAttr("cloudstack_instance.foobar", "keypairs.0", "terraform-test-keypair-foo"),
167+
resource.TestCheckResourceAttr("cloudstack_instance.foobar", "keypairs.1", "terraform-test-keypair-bar"),
168+
),
169+
},
170+
},
171+
})
172+
}
173+
174+
func TestAccCloudStackInstance_keyPair_update(t *testing.T) {
175+
var instance cloudstack.VirtualMachine
176+
177+
resource.Test(t, resource.TestCase{
178+
PreCheck: func() { testAccPreCheck(t) },
179+
Providers: testAccProviders,
180+
CheckDestroy: testAccCheckCloudStackInstanceDestroy,
181+
Steps: []resource.TestStep{
182+
{
183+
Config: testAccCloudStackInstance_keyPair,
184+
Check: resource.ComposeTestCheckFunc(
185+
testAccCheckCloudStackInstanceExists(
186+
"cloudstack_instance.foobar", &instance),
187+
resource.TestCheckResourceAttr(
188+
"cloudstack_instance.foobar", "keypair", "terraform-test-keypair"),
189+
),
190+
},
191+
192+
{
193+
Config: testAccCloudStackInstance_keyPairs,
194+
Check: resource.ComposeTestCheckFunc(
195+
testAccCheckCloudStackInstanceExists("cloudstack_instance.foobar", &instance),
196+
resource.TestCheckResourceAttr("cloudstack_instance.foobar", "keypairs.#", "2"), // Expecting 2 key pairs
197+
resource.TestCheckResourceAttr("cloudstack_instance.foobar", "keypairs.0", "terraform-test-keypair-foo"),
198+
resource.TestCheckResourceAttr("cloudstack_instance.foobar", "keypairs.1", "terraform-test-keypair-bar"),
199+
),
200+
},
201+
202+
{
203+
Config: testAccCloudStackInstance_keyPair,
204+
Check: resource.ComposeTestCheckFunc(
205+
testAccCheckCloudStackInstanceExists(
206+
"cloudstack_instance.foobar", &instance),
207+
resource.TestCheckResourceAttr(
208+
"cloudstack_instance.foobar", "keypair", "terraform-test-keypair"),
209+
),
210+
},
211+
},
212+
})
213+
}
214+
153215
func TestAccCloudStackInstance_project(t *testing.T) {
154216
var instance cloudstack.VirtualMachine
155217

@@ -416,6 +478,33 @@ resource "cloudstack_instance" "foobar" {
416478
expunge = true
417479
}`
418480

481+
const testAccCloudStackInstance_keyPairs = `
482+
resource "cloudstack_network" "foo" {
483+
name = "terraform-network"
484+
cidr = "10.1.1.0/24"
485+
network_offering = "DefaultIsolatedNetworkOfferingWithSourceNatService"
486+
zone = "Sandbox-simulator"
487+
}
488+
489+
resource "cloudstack_ssh_keypair" "foo" {
490+
name = "terraform-test-keypair-foo"
491+
}
492+
493+
resource "cloudstack_ssh_keypair" "bar" {
494+
name = "terraform-test-keypair-bar"
495+
}
496+
497+
resource "cloudstack_instance" "foobar" {
498+
name = "terraform-test"
499+
display_name = "terraform-test"
500+
service_offering= "Small Instance"
501+
network_id = "${cloudstack_network.foo.id}"
502+
template = "CentOS 5.6 (64-bit) no GUI (Simulator)"
503+
zone = "Sandbox-simulator"
504+
keypairs = ["${cloudstack_ssh_keypair.foo.name}", "${cloudstack_ssh_keypair.bar.name}"]
505+
expunge = true
506+
}`
507+
419508
const testAccCloudStackInstance_project = `
420509
resource "cloudstack_network" "foo" {
421510
name = "terraform-network"

website/docs/r/instance.html.markdown

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ The following arguments are supported:
3434
* `service_offering` - (Required) The name or ID of the service offering used
3535
for this instance.
3636

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

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

8484
* `keypair` - (Optional) The name of the SSH key pair that will be used to
85-
access this instance.
85+
access this instance. (Mutual exclusive with keypairs)
86+
87+
* `keypairs` - (Optional) A list of SSH key pair names that will be used to
88+
access this instance. (Mutual exclusive with keypair)
8689

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

0 commit comments

Comments
 (0)