Skip to content

Commit d15478d

Browse files
committed
Add support for userdata and linking to templates
1 parent 7267192 commit d15478d

File tree

7 files changed

+661
-1
lines changed

7 files changed

+661
-1
lines changed

cloudstack/provider.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ func Provider() *schema.Provider {
104104
"cloudstack_quota": dataSourceCloudStackQuota(),
105105
"cloudstack_quota_enabled": dataSourceCloudStackQuotaEnabled(),
106106
"cloudstack_quota_tariff": dataSourceCloudStackQuotaTariff(),
107+
"cloudstack_user_data": dataSourceCloudstackUserData(),
107108
},
108109

109110
ResourcesMap: map[string]*schema.Resource{
@@ -164,6 +165,7 @@ func Provider() *schema.Provider {
164165
"cloudstack_limits": resourceCloudStackLimits(),
165166
"cloudstack_snapshot_policy": resourceCloudStackSnapshotPolicy(),
166167
"cloudstack_quota_tariff": resourceCloudStackQuotaTariff(),
168+
"cloudstack_userdata": resourceCloudStackUserData(),
167169
},
168170

169171
ConfigureFunc: providerConfigure,

cloudstack/resource_cloudstack_instance.go

Lines changed: 78 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,17 @@ func resourceCloudStackInstance() *schema.Resource {
212212
},
213213
},
214214

215+
"userdata_id": {
216+
Type: schema.TypeString,
217+
Optional: true,
218+
},
219+
220+
"userdata_details": {
221+
Type: schema.TypeMap,
222+
Optional: true,
223+
Elem: &schema.Schema{Type: schema.TypeString},
224+
},
225+
215226
"details": {
216227
Type: schema.TypeMap,
217228
Optional: true,
@@ -446,6 +457,20 @@ func resourceCloudStackInstanceCreate(d *schema.ResourceData, meta interface{})
446457
p.SetUserdata(ud)
447458
}
448459

460+
if userdataID, ok := d.GetOk("userdata_id"); ok {
461+
p.SetUserdataid(userdataID.(string))
462+
}
463+
464+
if userdataDetails, ok := d.GetOk("userdata_details"); ok {
465+
udDetails := make(map[string]string)
466+
index := 0
467+
for k, v := range userdataDetails.(map[string]interface{}) {
468+
udDetails[fmt.Sprintf("userdatadetails[%d].%s", index, k)] = v.(string)
469+
index++
470+
}
471+
p.SetUserdatadetails(udDetails)
472+
}
473+
449474
// Create the new instance
450475
r, err := cs.VirtualMachine.DeployVirtualMachine(p)
451476
if err != nil {
@@ -560,6 +585,23 @@ func resourceCloudStackInstanceRead(d *schema.ResourceData, meta interface{}) er
560585
d.Set("boot_mode", vm.Bootmode)
561586
}
562587

588+
if vm.Userdataid != "" {
589+
d.Set("userdata_id", vm.Userdataid)
590+
}
591+
592+
if vm.Userdata != "" {
593+
decoded, err := base64.StdEncoding.DecodeString(vm.Userdata)
594+
if err != nil {
595+
d.Set("user_data", vm.Userdata)
596+
} else {
597+
d.Set("user_data", string(decoded))
598+
}
599+
}
600+
601+
if vm.Userdatadetails != "" {
602+
log.Printf("[DEBUG] Instance %s has userdata details: %s", vm.Name, vm.Userdatadetails)
603+
}
604+
563605
return nil
564606
}
565607

@@ -609,7 +651,8 @@ func resourceCloudStackInstanceUpdate(d *schema.ResourceData, meta interface{})
609651

610652
// Attributes that require reboot to update
611653
if d.HasChange("name") || d.HasChange("service_offering") || d.HasChange("affinity_group_ids") ||
612-
d.HasChange("affinity_group_names") || d.HasChange("keypair") || d.HasChange("keypairs") || d.HasChange("user_data") {
654+
d.HasChange("affinity_group_names") || d.HasChange("keypair") || d.HasChange("keypairs") ||
655+
d.HasChange("user_data") || d.HasChange("userdata_id") || d.HasChange("userdata_details") {
613656

614657
// Before we can actually make these changes, the virtual machine must be stopped
615658
_, err := cs.VirtualMachine.StopVirtualMachine(
@@ -763,6 +806,40 @@ func resourceCloudStackInstanceUpdate(d *schema.ResourceData, meta interface{})
763806
}
764807
}
765808

809+
if d.HasChange("userdata_id") {
810+
log.Printf("[DEBUG] userdata_id changed for %s, starting update", name)
811+
812+
p := cs.VirtualMachine.NewUpdateVirtualMachineParams(d.Id())
813+
if userdataID, ok := d.GetOk("userdata_id"); ok {
814+
p.SetUserdataid(userdataID.(string))
815+
}
816+
_, err := cs.VirtualMachine.UpdateVirtualMachine(p)
817+
if err != nil {
818+
return fmt.Errorf(
819+
"Error updating userdata_id for instance %s: %s", name, err)
820+
}
821+
}
822+
823+
if d.HasChange("userdata_details") {
824+
log.Printf("[DEBUG] userdata_details changed for %s, starting update", name)
825+
826+
p := cs.VirtualMachine.NewUpdateVirtualMachineParams(d.Id())
827+
if userdataDetails, ok := d.GetOk("userdata_details"); ok {
828+
udDetails := make(map[string]string)
829+
index := 0
830+
for k, v := range userdataDetails.(map[string]interface{}) {
831+
udDetails[fmt.Sprintf("userdatadetails[%d].%s", index, k)] = v.(string)
832+
index++
833+
}
834+
p.SetUserdatadetails(udDetails)
835+
}
836+
_, err := cs.VirtualMachine.UpdateVirtualMachine(p)
837+
if err != nil {
838+
return fmt.Errorf(
839+
"Error updating userdata_details for instance %s: %s", name, err)
840+
}
841+
}
842+
766843
// Start the virtual machine again
767844
_, err = cs.VirtualMachine.StartVirtualMachine(
768845
cs.VirtualMachine.NewStartVirtualMachineParams(d.Id()))

cloudstack/resource_cloudstack_template.go

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,37 @@ func resourceCloudStackTemplate() *schema.Resource {
133133
ForceNew: true,
134134
},
135135

136+
"userdata_link": {
137+
Type: schema.TypeList,
138+
Optional: true,
139+
MaxItems: 1,
140+
Elem: &schema.Resource{
141+
Schema: map[string]*schema.Schema{
142+
"userdata_id": {
143+
Type: schema.TypeString,
144+
Required: true,
145+
Description: "The ID of the user data to link to the template.",
146+
},
147+
"userdata_policy": {
148+
Type: schema.TypeString,
149+
Optional: true,
150+
Default: "ALLOWOVERRIDE",
151+
Description: "Override policy of the userdata. Possible values: ALLOWOVERRIDE, APPEND, DENYOVERRIDE. Default: ALLOWOVERRIDE",
152+
},
153+
"userdata_name": {
154+
Type: schema.TypeString,
155+
Computed: true,
156+
Description: "The name of the linked user data.",
157+
},
158+
"userdata_params": {
159+
Type: schema.TypeString,
160+
Computed: true,
161+
Description: "The parameters of the linked user data.",
162+
},
163+
},
164+
},
165+
},
166+
136167
"tags": tagsSchema(),
137168
},
138169
}
@@ -224,6 +255,11 @@ func resourceCloudStackTemplateCreate(d *schema.ResourceData, meta interface{})
224255
return fmt.Errorf("Error setting tags on the template %s: %s", name, err)
225256
}
226257

258+
// Link userdata if specified
259+
if err = linkUserdataToTemplate(cs, d, r.RegisterTemplate[0].Id); err != nil {
260+
return fmt.Errorf("Error linking userdata to template %s: %s", name, err)
261+
}
262+
227263
// Wait until the template is ready to use, or timeout with an error...
228264
currentTime := time.Now().Unix()
229265
timeout := int64(d.Get("is_ready_timeout").(int))
@@ -300,6 +336,11 @@ func resourceCloudStackTemplateRead(d *schema.ResourceData, meta interface{}) er
300336
setValueOrID(d, "project", t.Project, t.Projectid)
301337
setValueOrID(d, "zone", t.Zonename, t.Zoneid)
302338

339+
// Read userdata link information
340+
if err := readUserdataFromTemplate(d, t); err != nil {
341+
return fmt.Errorf("Error reading userdata link from template: %s", err)
342+
}
343+
303344
return nil
304345
}
305346

@@ -349,6 +390,12 @@ func resourceCloudStackTemplateUpdate(d *schema.ResourceData, meta interface{})
349390
}
350391
}
351392

393+
if d.HasChange("userdata_link") {
394+
if err := updateUserdataLink(cs, d); err != nil {
395+
return fmt.Errorf("Error updating userdata link for template %s: %s", name, err)
396+
}
397+
}
398+
352399
return resourceCloudStackTemplateRead(d, meta)
353400
}
354401

@@ -383,3 +430,105 @@ func verifyTemplateParams(d *schema.ResourceData) error {
383430

384431
return nil
385432
}
433+
434+
func linkUserdataToTemplate(cs *cloudstack.CloudStackClient, d *schema.ResourceData, templateID string) error {
435+
userdataLinks := d.Get("userdata_link").([]interface{})
436+
if len(userdataLinks) == 0 {
437+
return nil
438+
}
439+
440+
userdataLink := userdataLinks[0].(map[string]interface{})
441+
442+
p := cs.Template.NewLinkUserDataToTemplateParams()
443+
p.SetTemplateid(templateID)
444+
p.SetUserdataid(userdataLink["userdata_id"].(string))
445+
446+
if policy, ok := userdataLink["userdata_policy"].(string); ok && policy != "" {
447+
p.SetUserdatapolicy(policy)
448+
}
449+
450+
_, err := cs.Template.LinkUserDataToTemplate(p)
451+
return err
452+
}
453+
454+
func readUserdataFromTemplate(d *schema.ResourceData, template *cloudstack.Template) error {
455+
if template.Userdataid == "" {
456+
d.Set("userdata_link", []interface{}{})
457+
return nil
458+
}
459+
460+
userdataLink := map[string]interface{}{
461+
"userdata_id": template.Userdataid,
462+
"userdata_name": template.Userdataname,
463+
"userdata_params": template.Userdataparams,
464+
}
465+
466+
if existingLinks := d.Get("userdata_link").([]interface{}); len(existingLinks) > 0 {
467+
if existingLink, ok := existingLinks[0].(map[string]interface{}); ok {
468+
if policy, exists := existingLink["userdata_policy"]; exists {
469+
userdataLink["userdata_policy"] = policy
470+
}
471+
}
472+
}
473+
474+
d.Set("userdata_link", []interface{}{userdataLink})
475+
return nil
476+
}
477+
478+
func updateUserdataLink(cs *cloudstack.CloudStackClient, d *schema.ResourceData) error {
479+
templateID := d.Id()
480+
481+
oldLinks, newLinks := d.GetChange("userdata_link")
482+
oldLinksSlice := oldLinks.([]interface{})
483+
newLinksSlice := newLinks.([]interface{})
484+
485+
// Check if we're removing userdata link (had one before, now empty)
486+
if len(oldLinksSlice) > 0 && len(newLinksSlice) == 0 {
487+
unlinkP := cs.Template.NewLinkUserDataToTemplateParams()
488+
unlinkP.SetTemplateid(templateID)
489+
490+
_, err := cs.Template.LinkUserDataToTemplate(unlinkP)
491+
if err != nil {
492+
return fmt.Errorf("Error unlinking userdata from template: %s", err)
493+
}
494+
log.Printf("[DEBUG] Unlinked userdata from template: %s", templateID)
495+
return nil
496+
}
497+
498+
if len(newLinksSlice) > 0 {
499+
newLink := newLinksSlice[0].(map[string]interface{})
500+
501+
if len(oldLinksSlice) > 0 {
502+
oldLink := oldLinksSlice[0].(map[string]interface{})
503+
504+
if oldLink["userdata_id"].(string) == newLink["userdata_id"].(string) {
505+
oldPolicy := ""
506+
newPolicy := ""
507+
508+
if p, ok := oldLink["userdata_policy"].(string); ok {
509+
oldPolicy = p
510+
}
511+
if p, ok := newLink["userdata_policy"].(string); ok {
512+
newPolicy = p
513+
}
514+
515+
if oldPolicy == newPolicy {
516+
log.Printf("[DEBUG] Userdata link unchanged, skipping API call")
517+
return nil
518+
}
519+
}
520+
521+
unlinkP := cs.Template.NewLinkUserDataToTemplateParams()
522+
unlinkP.SetTemplateid(templateID)
523+
524+
_, err := cs.Template.LinkUserDataToTemplate(unlinkP)
525+
if err != nil {
526+
log.Printf("[DEBUG] Error unlinking existing userdata (this may be normal): %s", err)
527+
}
528+
}
529+
530+
return linkUserdataToTemplate(cs, d, templateID)
531+
}
532+
533+
return nil
534+
}

0 commit comments

Comments
 (0)