Skip to content

Commit 6421746

Browse files
authored
Add support for userdata and linking to templates (apache#256)
* Add support for userdata and linking to templates * add userdata resource and datasource * Update userdata to user_data and other comments
1 parent 7267192 commit 6421746

10 files changed

+955
-1
lines changed
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
//
2+
// Licensed to the Apache Software Foundation (ASF) under one
3+
// or more contributor license agreements. See the NOTICE file
4+
// distributed with this work for additional information
5+
// regarding copyright ownership. The ASF licenses this file
6+
// to you under the Apache License, Version 2.0 (the
7+
// "License"); you may not use this file except in compliance
8+
// with the License. You may obtain a copy of the License at
9+
//
10+
// http://www.apache.org/licenses/LICENSE-2.0
11+
//
12+
// Unless required by applicable law or agreed to in writing,
13+
// software distributed under the License is distributed on an
14+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
// KIND, either express or implied. See the License for the
16+
// specific language governing permissions and limitations
17+
// under the License.
18+
//
19+
20+
package cloudstack
21+
22+
import (
23+
"encoding/base64"
24+
"fmt"
25+
"log"
26+
27+
"github.com/apache/cloudstack-go/v2/cloudstack"
28+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
29+
)
30+
31+
func dataSourceCloudstackUserData() *schema.Resource {
32+
return &schema.Resource{
33+
Read: dataSourceCloudstackUserDataRead,
34+
Schema: map[string]*schema.Schema{
35+
"filter": dataSourceFiltersSchema(),
36+
"account": {
37+
Type: schema.TypeString,
38+
Computed: true,
39+
},
40+
"account_id": {
41+
Type: schema.TypeString,
42+
Computed: true,
43+
},
44+
"domain": {
45+
Type: schema.TypeString,
46+
Computed: true,
47+
},
48+
"domain_id": {
49+
Type: schema.TypeString,
50+
Computed: true,
51+
},
52+
"project": {
53+
Type: schema.TypeString,
54+
Computed: true,
55+
},
56+
"project_id": {
57+
Type: schema.TypeString,
58+
Computed: true,
59+
},
60+
"userdata_id": {
61+
Type: schema.TypeString,
62+
Computed: true,
63+
},
64+
"userdata": {
65+
Type: schema.TypeString,
66+
Computed: true,
67+
},
68+
"params": {
69+
Type: schema.TypeString,
70+
Computed: true,
71+
},
72+
},
73+
}
74+
}
75+
76+
func dataSourceCloudstackUserDataRead(d *schema.ResourceData, meta interface{}) error {
77+
cs := meta.(*cloudstack.CloudStackClient)
78+
79+
name := d.Get("name").(string)
80+
p := cs.User.NewListUserDataParams()
81+
p.SetName(name)
82+
83+
if v, ok := d.GetOk("account"); ok {
84+
p.SetAccount(v.(string))
85+
}
86+
if v, ok := d.GetOk("domain_id"); ok {
87+
p.SetDomainid(v.(string))
88+
}
89+
90+
log.Printf("[DEBUG] Listing user data with name: %s", name)
91+
userdataList, err := cs.User.ListUserData(p)
92+
if err != nil {
93+
return fmt.Errorf("Error listing user data with name %s: %s", name, err)
94+
}
95+
96+
if len(userdataList.UserData) == 0 {
97+
return fmt.Errorf("No user data found with name: %s", name)
98+
}
99+
if len(userdataList.UserData) > 1 {
100+
return fmt.Errorf("Multiple user data entries found with name: %s", name)
101+
}
102+
103+
userdata := userdataList.UserData[0]
104+
105+
d.SetId(userdata.Id)
106+
d.Set("name", userdata.Name)
107+
d.Set("account", userdata.Account)
108+
d.Set("account_id", userdata.Accountid)
109+
d.Set("domain", userdata.Domain)
110+
d.Set("domain_id", userdata.Domainid)
111+
d.Set("userdata_id", userdata.Id)
112+
d.Set("params", userdata.Params)
113+
114+
if userdata.Project != "" {
115+
d.Set("project", userdata.Project)
116+
d.Set("project_id", userdata.Projectid)
117+
}
118+
119+
if userdata.Userdata != "" {
120+
decoded, err := base64.StdEncoding.DecodeString(userdata.Userdata)
121+
if err != nil {
122+
d.Set("userdata", userdata.Userdata) // Fallback: use raw data
123+
} else {
124+
d.Set("userdata", string(decoded))
125+
}
126+
}
127+
return nil
128+
}

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_user_data": 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()))

0 commit comments

Comments
 (0)