This is an automated email from the ASF dual-hosted git repository.
kiranchavala pushed a commit to branch main
in repository
https://gitbox.apache.org/repos/asf/cloudstack-terraform-provider.git
The following commit(s) were added to refs/heads/main by this push:
new 6421746 Add support for userdata and linking to templates (#256)
6421746 is described below
commit 6421746c7e8a732412f28e2378490a4d910516ac
Author: Pearl Dsilva <[email protected]>
AuthorDate: Wed Oct 22 09:12:56 2025 -0400
Add support for userdata and linking to templates (#256)
* Add support for userdata and linking to templates
* add userdata resource and datasource
* Update userdata to user_data and other comments
---
cloudstack/data_source_cloudstack_user_data.go | 128 +++++++++++++++++++
cloudstack/provider.go | 2 +
cloudstack/resource_cloudstack_instance.go | 79 +++++++++++-
cloudstack/resource_cloudstack_template.go | 149 ++++++++++++++++++++++
cloudstack/resource_cloudstack_user_data.go | 166 +++++++++++++++++++++++++
website/docs/README.md | 2 +
website/docs/d/user_data.html.markdown | 126 +++++++++++++++++++
website/docs/r/instance.html.markdown | 86 +++++++++++++
website/docs/r/template.html.markdown | 68 ++++++++++
website/docs/r/userdata.html.markdown | 150 ++++++++++++++++++++++
10 files changed, 955 insertions(+), 1 deletion(-)
diff --git a/cloudstack/data_source_cloudstack_user_data.go
b/cloudstack/data_source_cloudstack_user_data.go
new file mode 100644
index 0000000..29ef501
--- /dev/null
+++ b/cloudstack/data_source_cloudstack_user_data.go
@@ -0,0 +1,128 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
+package cloudstack
+
+import (
+ "encoding/base64"
+ "fmt"
+ "log"
+
+ "github.com/apache/cloudstack-go/v2/cloudstack"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+)
+
+func dataSourceCloudstackUserData() *schema.Resource {
+ return &schema.Resource{
+ Read: dataSourceCloudstackUserDataRead,
+ Schema: map[string]*schema.Schema{
+ "filter": dataSourceFiltersSchema(),
+ "account": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "account_id": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "domain": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "domain_id": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "project": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "project_id": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "userdata_id": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "userdata": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ "params": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ },
+ }
+}
+
+func dataSourceCloudstackUserDataRead(d *schema.ResourceData, meta
interface{}) error {
+ cs := meta.(*cloudstack.CloudStackClient)
+
+ name := d.Get("name").(string)
+ p := cs.User.NewListUserDataParams()
+ p.SetName(name)
+
+ if v, ok := d.GetOk("account"); ok {
+ p.SetAccount(v.(string))
+ }
+ if v, ok := d.GetOk("domain_id"); ok {
+ p.SetDomainid(v.(string))
+ }
+
+ log.Printf("[DEBUG] Listing user data with name: %s", name)
+ userdataList, err := cs.User.ListUserData(p)
+ if err != nil {
+ return fmt.Errorf("Error listing user data with name %s: %s",
name, err)
+ }
+
+ if len(userdataList.UserData) == 0 {
+ return fmt.Errorf("No user data found with name: %s", name)
+ }
+ if len(userdataList.UserData) > 1 {
+ return fmt.Errorf("Multiple user data entries found with name:
%s", name)
+ }
+
+ userdata := userdataList.UserData[0]
+
+ d.SetId(userdata.Id)
+ d.Set("name", userdata.Name)
+ d.Set("account", userdata.Account)
+ d.Set("account_id", userdata.Accountid)
+ d.Set("domain", userdata.Domain)
+ d.Set("domain_id", userdata.Domainid)
+ d.Set("userdata_id", userdata.Id)
+ d.Set("params", userdata.Params)
+
+ if userdata.Project != "" {
+ d.Set("project", userdata.Project)
+ d.Set("project_id", userdata.Projectid)
+ }
+
+ if userdata.Userdata != "" {
+ decoded, err :=
base64.StdEncoding.DecodeString(userdata.Userdata)
+ if err != nil {
+ d.Set("userdata", userdata.Userdata) // Fallback: use
raw data
+ } else {
+ d.Set("userdata", string(decoded))
+ }
+ }
+ return nil
+}
diff --git a/cloudstack/provider.go b/cloudstack/provider.go
index 63d0234..7209014 100644
--- a/cloudstack/provider.go
+++ b/cloudstack/provider.go
@@ -104,6 +104,7 @@ func Provider() *schema.Provider {
"cloudstack_quota":
dataSourceCloudStackQuota(),
"cloudstack_quota_enabled":
dataSourceCloudStackQuotaEnabled(),
"cloudstack_quota_tariff":
dataSourceCloudStackQuotaTariff(),
+ "cloudstack_user_data":
dataSourceCloudstackUserData(),
},
ResourcesMap: map[string]*schema.Resource{
@@ -164,6 +165,7 @@ func Provider() *schema.Provider {
"cloudstack_limits":
resourceCloudStackLimits(),
"cloudstack_snapshot_policy":
resourceCloudStackSnapshotPolicy(),
"cloudstack_quota_tariff":
resourceCloudStackQuotaTariff(),
+ "cloudstack_user_data":
resourceCloudStackUserData(),
},
ConfigureFunc: providerConfigure,
diff --git a/cloudstack/resource_cloudstack_instance.go
b/cloudstack/resource_cloudstack_instance.go
index 4caa345..6a38ddb 100644
--- a/cloudstack/resource_cloudstack_instance.go
+++ b/cloudstack/resource_cloudstack_instance.go
@@ -212,6 +212,17 @@ func resourceCloudStackInstance() *schema.Resource {
},
},
+ "userdata_id": {
+ Type: schema.TypeString,
+ Optional: true,
+ },
+
+ "userdata_details": {
+ Type: schema.TypeMap,
+ Optional: true,
+ Elem: &schema.Schema{Type:
schema.TypeString},
+ },
+
"details": {
Type: schema.TypeMap,
Optional: true,
@@ -446,6 +457,20 @@ func resourceCloudStackInstanceCreate(d
*schema.ResourceData, meta interface{})
p.SetUserdata(ud)
}
+ if userdataID, ok := d.GetOk("userdata_id"); ok {
+ p.SetUserdataid(userdataID.(string))
+ }
+
+ if userdataDetails, ok := d.GetOk("userdata_details"); ok {
+ udDetails := make(map[string]string)
+ index := 0
+ for k, v := range userdataDetails.(map[string]interface{}) {
+ udDetails[fmt.Sprintf("userdatadetails[%d].%s", index,
k)] = v.(string)
+ index++
+ }
+ p.SetUserdatadetails(udDetails)
+ }
+
// Create the new instance
r, err := cs.VirtualMachine.DeployVirtualMachine(p)
if err != nil {
@@ -560,6 +585,23 @@ func resourceCloudStackInstanceRead(d
*schema.ResourceData, meta interface{}) er
d.Set("boot_mode", vm.Bootmode)
}
+ if vm.Userdataid != "" {
+ d.Set("userdata_id", vm.Userdataid)
+ }
+
+ if vm.Userdata != "" {
+ decoded, err := base64.StdEncoding.DecodeString(vm.Userdata)
+ if err != nil {
+ d.Set("user_data", vm.Userdata)
+ } else {
+ d.Set("user_data", string(decoded))
+ }
+ }
+
+ if vm.Userdatadetails != "" {
+ log.Printf("[DEBUG] Instance %s has userdata details: %s",
vm.Name, vm.Userdatadetails)
+ }
+
return nil
}
@@ -609,7 +651,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("keypairs") || d.HasChange("user_data") {
+ d.HasChange("affinity_group_names") || d.HasChange("keypair")
|| d.HasChange("keypairs") ||
+ d.HasChange("user_data") || d.HasChange("userdata_id") ||
d.HasChange("userdata_details") {
// Before we can actually make these changes, the virtual
machine must be stopped
_, err := cs.VirtualMachine.StopVirtualMachine(
@@ -763,6 +806,40 @@ func resourceCloudStackInstanceUpdate(d
*schema.ResourceData, meta interface{})
}
}
+ if d.HasChange("userdata_id") {
+ log.Printf("[DEBUG] userdata_id changed for %s,
starting update", name)
+
+ p :=
cs.VirtualMachine.NewUpdateVirtualMachineParams(d.Id())
+ if userdataID, ok := d.GetOk("userdata_id"); ok {
+ p.SetUserdataid(userdataID.(string))
+ }
+ _, err := cs.VirtualMachine.UpdateVirtualMachine(p)
+ if err != nil {
+ return fmt.Errorf(
+ "Error updating userdata_id for
instance %s: %s", name, err)
+ }
+ }
+
+ if d.HasChange("userdata_details") {
+ log.Printf("[DEBUG] userdata_details changed for %s,
starting update", name)
+
+ p :=
cs.VirtualMachine.NewUpdateVirtualMachineParams(d.Id())
+ if userdataDetails, ok := d.GetOk("userdata_details");
ok {
+ udDetails := make(map[string]string)
+ index := 0
+ for k, v := range
userdataDetails.(map[string]interface{}) {
+
udDetails[fmt.Sprintf("userdatadetails[%d].%s", index, k)] = v.(string)
+ index++
+ }
+ p.SetUserdatadetails(udDetails)
+ }
+ _, err := cs.VirtualMachine.UpdateVirtualMachine(p)
+ if err != nil {
+ return fmt.Errorf(
+ "Error updating userdata_details for
instance %s: %s", name, err)
+ }
+ }
+
// Start the virtual machine again
_, err = cs.VirtualMachine.StartVirtualMachine(
cs.VirtualMachine.NewStartVirtualMachineParams(d.Id()))
diff --git a/cloudstack/resource_cloudstack_template.go
b/cloudstack/resource_cloudstack_template.go
index 4316c7e..ced400a 100644
--- a/cloudstack/resource_cloudstack_template.go
+++ b/cloudstack/resource_cloudstack_template.go
@@ -133,6 +133,37 @@ func resourceCloudStackTemplate() *schema.Resource {
ForceNew: true,
},
+ "userdata_link": {
+ Type: schema.TypeList,
+ Optional: true,
+ MaxItems: 1,
+ Elem: &schema.Resource{
+ Schema: map[string]*schema.Schema{
+ "userdata_id": {
+ Type:
schema.TypeString,
+ Required: true,
+ Description: "The ID of
the user data to link to the template.",
+ },
+ "userdata_policy": {
+ Type:
schema.TypeString,
+ Optional: true,
+ Default:
"ALLOWOVERRIDE",
+ Description: "Override
policy of the userdata. Possible values: ALLOWOVERRIDE, APPEND, DENYOVERRIDE.
Default: ALLOWOVERRIDE",
+ },
+ "userdata_name": {
+ Type:
schema.TypeString,
+ Computed: true,
+ Description: "The name
of the linked user data.",
+ },
+ "userdata_params": {
+ Type:
schema.TypeString,
+ Computed: true,
+ Description: "The
parameters of the linked user data.",
+ },
+ },
+ },
+ },
+
"tags": tagsSchema(),
},
}
@@ -224,6 +255,11 @@ func resourceCloudStackTemplateCreate(d
*schema.ResourceData, meta interface{})
return fmt.Errorf("Error setting tags on the template %s: %s",
name, err)
}
+ // Link userdata if specified
+ if err = linkUserdataToTemplate(cs, d, r.RegisterTemplate[0].Id); err
!= nil {
+ return fmt.Errorf("Error linking userdata to template %s: %s",
name, err)
+ }
+
// Wait until the template is ready to use, or timeout with an error...
currentTime := time.Now().Unix()
timeout := int64(d.Get("is_ready_timeout").(int))
@@ -300,6 +336,11 @@ func resourceCloudStackTemplateRead(d
*schema.ResourceData, meta interface{}) er
setValueOrID(d, "project", t.Project, t.Projectid)
setValueOrID(d, "zone", t.Zonename, t.Zoneid)
+ // Read userdata link information
+ if err := readUserdataFromTemplate(d, t); err != nil {
+ return fmt.Errorf("Error reading userdata link from template:
%s", err)
+ }
+
return nil
}
@@ -349,6 +390,12 @@ func resourceCloudStackTemplateUpdate(d
*schema.ResourceData, meta interface{})
}
}
+ if d.HasChange("userdata_link") {
+ if err := updateUserdataLink(cs, d); err != nil {
+ return fmt.Errorf("Error updating userdata link for
template %s: %s", name, err)
+ }
+ }
+
return resourceCloudStackTemplateRead(d, meta)
}
@@ -383,3 +430,105 @@ func verifyTemplateParams(d *schema.ResourceData) error {
return nil
}
+
+func linkUserdataToTemplate(cs *cloudstack.CloudStackClient, d
*schema.ResourceData, templateID string) error {
+ userdataLinks := d.Get("userdata_link").([]interface{})
+ if len(userdataLinks) == 0 {
+ return nil
+ }
+
+ userdataLink := userdataLinks[0].(map[string]interface{})
+
+ p := cs.Template.NewLinkUserDataToTemplateParams()
+ p.SetTemplateid(templateID)
+ p.SetUserdataid(userdataLink["userdata_id"].(string))
+
+ if policy, ok := userdataLink["userdata_policy"].(string); ok && policy
!= "" {
+ p.SetUserdatapolicy(policy)
+ }
+
+ _, err := cs.Template.LinkUserDataToTemplate(p)
+ return err
+}
+
+func readUserdataFromTemplate(d *schema.ResourceData, template
*cloudstack.Template) error {
+ if template.Userdataid == "" {
+ d.Set("userdata_link", []interface{}{})
+ return nil
+ }
+
+ userdataLink := map[string]interface{}{
+ "userdata_id": template.Userdataid,
+ "userdata_name": template.Userdataname,
+ "userdata_params": template.Userdataparams,
+ }
+
+ if existingLinks := d.Get("userdata_link").([]interface{});
len(existingLinks) > 0 {
+ if existingLink, ok :=
existingLinks[0].(map[string]interface{}); ok {
+ if policy, exists := existingLink["userdata_policy"];
exists {
+ userdataLink["userdata_policy"] = policy
+ }
+ }
+ }
+
+ d.Set("userdata_link", []interface{}{userdataLink})
+ return nil
+}
+
+func updateUserdataLink(cs *cloudstack.CloudStackClient, d
*schema.ResourceData) error {
+ templateID := d.Id()
+
+ oldLinks, newLinks := d.GetChange("userdata_link")
+ oldLinksSlice := oldLinks.([]interface{})
+ newLinksSlice := newLinks.([]interface{})
+
+ // Check if we're removing userdata link (had one before, now empty)
+ if len(oldLinksSlice) > 0 && len(newLinksSlice) == 0 {
+ unlinkP := cs.Template.NewLinkUserDataToTemplateParams()
+ unlinkP.SetTemplateid(templateID)
+
+ _, err := cs.Template.LinkUserDataToTemplate(unlinkP)
+ if err != nil {
+ return fmt.Errorf("Error unlinking userdata from
template: %s", err)
+ }
+ log.Printf("[DEBUG] Unlinked userdata from template: %s",
templateID)
+ return nil
+ }
+
+ if len(newLinksSlice) > 0 {
+ newLink := newLinksSlice[0].(map[string]interface{})
+
+ if len(oldLinksSlice) > 0 {
+ oldLink := oldLinksSlice[0].(map[string]interface{})
+
+ if oldLink["userdata_id"].(string) ==
newLink["userdata_id"].(string) {
+ oldPolicy := ""
+ newPolicy := ""
+
+ if p, ok :=
oldLink["userdata_policy"].(string); ok {
+ oldPolicy = p
+ }
+ if p, ok :=
newLink["userdata_policy"].(string); ok {
+ newPolicy = p
+ }
+
+ if oldPolicy == newPolicy {
+ log.Printf("[DEBUG] Userdata link
unchanged, skipping API call")
+ return nil
+ }
+ }
+
+ unlinkP := cs.Template.NewLinkUserDataToTemplateParams()
+ unlinkP.SetTemplateid(templateID)
+
+ _, err := cs.Template.LinkUserDataToTemplate(unlinkP)
+ if err != nil {
+ log.Printf("[DEBUG] Error unlinking existing
userdata (this may be normal): %s", err)
+ }
+ }
+
+ return linkUserdataToTemplate(cs, d, templateID)
+ }
+
+ return nil
+}
diff --git a/cloudstack/resource_cloudstack_user_data.go
b/cloudstack/resource_cloudstack_user_data.go
new file mode 100644
index 0000000..65a3d2c
--- /dev/null
+++ b/cloudstack/resource_cloudstack_user_data.go
@@ -0,0 +1,166 @@
+//
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+//
+
+package cloudstack
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/apache/cloudstack-go/v2/cloudstack"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+)
+
+func resourceCloudStackUserData() *schema.Resource {
+ return &schema.Resource{
+ Create: resourceCloudStackUserDataCreate,
+ Read: resourceCloudStackUserDataRead,
+ Delete: resourceCloudStackUserDataDelete,
+ Importer: &schema.ResourceImporter{
+ State: importStatePassthrough,
+ },
+
+ Schema: map[string]*schema.Schema{
+ "name": {
+ Type: schema.TypeString,
+ Required: true,
+ ForceNew: true,
+ Description: "Name of the user data",
+ },
+
+ "userdata": {
+ Type: schema.TypeString,
+ Required: true,
+ ForceNew: true,
+ Description: "The user data content to be
registered",
+ },
+
+ "account": {
+ Type: schema.TypeString,
+ Optional: true,
+ ForceNew: true,
+ Description: "An optional account for the user
data. Must be used with domain_id.",
+ },
+
+ "domain_id": {
+ Type: schema.TypeString,
+ Optional: true,
+ ForceNew: true,
+ Description: "An optional domain ID for the
user data. If the account parameter is used, domain_id must also be used.",
+ },
+
+ "params": {
+ Type: schema.TypeSet,
+ Optional: true,
+ ForceNew: true,
+ Description: "Optional comma separated list of
variables declared in user data content.",
+ Elem: &schema.Schema{
+ Type: schema.TypeString,
+ },
+ },
+
+ "project_id": {
+ Type: schema.TypeString,
+ Optional: true,
+ ForceNew: true,
+ Description: "An optional project for the user
data.",
+ },
+ },
+ }
+}
+
+func resourceCloudStackUserDataCreate(d *schema.ResourceData, meta
interface{}) error {
+ cs := meta.(*cloudstack.CloudStackClient)
+
+ p := cs.User.NewRegisterUserDataParams(d.Get("name").(string),
d.Get("userdata").(string))
+ if v, ok := d.GetOk("account"); ok {
+ p.SetAccount(v.(string))
+ }
+ if v, ok := d.GetOk("domain_id"); ok {
+ p.SetDomainid(v.(string))
+ }
+ if v, ok := d.GetOk("project_id"); ok {
+ p.SetProjectid(v.(string))
+ }
+ if v, ok := d.GetOk("params"); ok {
+ paramsList := v.(*schema.Set).List()
+ var params []string
+ for _, param := range paramsList {
+ params = append(params, param.(string))
+ }
+ p.SetParams(strings.Join(params, ","))
+ }
+
+ userdata, err := cs.User.RegisterUserData(p)
+ if err != nil {
+ return fmt.Errorf("Error registering user data: %s", err)
+ }
+
+ d.SetId(userdata.Id)
+
+ return resourceCloudStackUserDataRead(d, meta)
+}
+
+func resourceCloudStackUserDataRead(d *schema.ResourceData, meta interface{})
error {
+ cs := meta.(*cloudstack.CloudStackClient)
+
+ id := d.Id()
+
+ p := cs.User.NewListUserDataParams()
+ p.SetId(id)
+
+ userdata, err := cs.User.ListUserData(p)
+ if err != nil {
+ return fmt.Errorf("Error retrieving user data with ID %s: %s",
id, err)
+ }
+
+ d.Set("name", userdata.UserData[0].Name)
+ d.Set("userdata", userdata.UserData[0].Userdata)
+ if d.Get("account").(string) != "" {
+ d.Set("account", userdata.UserData[0].Account)
+ }
+ if d.Get("domain_id").(string) != "" {
+ d.Set("domain_id", userdata.UserData[0].Domainid)
+ }
+ if userdata.UserData[0].Params != "" {
+ paramsList := strings.Split(userdata.UserData[0].Params, ",")
+ var paramsSet []interface{}
+ for _, param := range paramsList {
+ paramsSet = append(paramsSet, param)
+ }
+ d.Set("params", schema.NewSet(schema.HashString, paramsSet))
+ }
+ if userdata.UserData[0].Projectid != "" {
+ d.Set("project_id", userdata.UserData[0].Projectid)
+ }
+
+ return nil
+}
+
+func resourceCloudStackUserDataDelete(d *schema.ResourceData, meta
interface{}) error {
+ cs := meta.(*cloudstack.CloudStackClient)
+
+ p := cs.User.NewDeleteUserDataParams(d.Id())
+ _, err := cs.User.DeleteUserData(p)
+ if err != nil {
+ return fmt.Errorf("Error deleting user data with ID %s: %s",
d.Id(), err)
+ }
+
+ return nil
+}
diff --git a/website/docs/README.md b/website/docs/README.md
index 0c09b53..2fe8dd3 100644
--- a/website/docs/README.md
+++ b/website/docs/README.md
@@ -66,6 +66,7 @@ The following arguments are supported:
- [ssh_keypair](./d/ssh_keypair.html.markdown)
- [template](./d/template.html.markdown)
- [user](./d/user.html.markdown)
+- [user_data](./d/user_data.html.markdown)
- [volume](./d/volume.html.markdown)
- [vpc](./d/vpc.html.markdown)
- [vpn_connection](./d/vpn_connection.html.markdown)
@@ -101,6 +102,7 @@ The following arguments are supported:
- [static_route](./r/static_route.html.markdown)
- [template](./r/template.html.markdown)
- [user](./r/user.html.markdown)
+- [userdata](./r/userdata.html.markdown)
- [volume](./r/volume.html.markdown)
- [vpc](./r/vpc.html.markdown)
- [vpn_connection](./r/vpn_connection.html.markdown)
diff --git a/website/docs/d/user_data.html.markdown
b/website/docs/d/user_data.html.markdown
new file mode 100644
index 0000000..1e43132
--- /dev/null
+++ b/website/docs/d/user_data.html.markdown
@@ -0,0 +1,126 @@
+---
+layout: "cloudstack"
+page_title: "CloudStack: cloudstack_user_data"
+sidebar_current: "docs-cloudstack-datasource-user-data"
+description: |-
+ Get information about a CloudStack user data.
+---
+
+# cloudstack_user_data
+
+Use this data source to retrieve information about a CloudStack user data by
either its name or ID.
+
+## Example Usage
+
+### Find User Data by Name
+
+```hcl
+data "cloudstack_user_data" "web_init" {
+ filter {
+ name = "name"
+ value = "web-server-init"
+ }
+}
+
+# Use the user data in an instance
+resource "cloudstack_instance" "web" {
+ name = "web-server"
+ userdata_id = data.cloudstack_user_data.web_init.id
+ # ... other arguments ...
+}
+```
+
+### Find User Data by ID
+
+```hcl
+data "cloudstack_user_data" "app_init" {
+ filter {
+ name = "id"
+ value = "12345678-1234-1234-1234-123456789012"
+ }
+}
+```
+
+### Find Project-Scoped User Data
+
+```hcl
+data "cloudstack_user_data" "project_init" {
+ project = "my-project"
+
+ filter {
+ name = "name"
+ value = "project-specific-init"
+ }
+}
+```
+
+## Argument Reference
+
+The following arguments are supported:
+
+* `filter` - (Required) One or more name/value pairs to filter off of. You can
apply multiple filters to narrow down the results. See [Filters](#filters)
below for more details.
+
+* `project` - (Optional) The name or ID of the project to search in.
+
+### Filters
+
+The `filter` block supports the following arguments:
+
+* `name` - (Required) The name of the filter. Valid filter names are:
+ * `id` - Filter by user data ID
+ * `name` - Filter by user data name
+ * `account` - Filter by account name
+ * `domainid` - Filter by domain ID
+
+* `value` - (Required) The value to filter by.
+
+## Attributes Reference
+
+The following attributes are exported:
+
+* `id` - The user data ID.
+* `name` - The name of the user data.
+* `userdata` - The user data content.
+* `account` - The account name owning the user data.
+* `domain_id` - The domain ID where the user data belongs.
+* `project_id` - The project ID if the user data is project-scoped.
+* `params` - The list of parameter names defined in the user data
(comma-separated string).
+
+## Example with Template Integration
+
+```hcl
+# Find existing user data
+data "cloudstack_user_data" "app_bootstrap" {
+ filter {
+ name = "name"
+ value = "application-bootstrap"
+ }
+}
+
+# Use with template
+resource "cloudstack_template" "app_template" {
+ name = "application-template"
+ display_text = "Application Template with Bootstrap"
+ # ... other template arguments ...
+
+ userdata_link {
+ userdata_id = data.cloudstack_user_data.app_bootstrap.id
+ userdata_policy = "ALLOWOVERRIDE"
+ }
+}
+
+# Deploy instance with parameterized user data
+resource "cloudstack_instance" "app_server" {
+ name = "app-server-01"
+ template = cloudstack_template.app_template.id
+ # ... other instance arguments ...
+
+ userdata_id = data.cloudstack_user_data.app_bootstrap.id
+
+ userdata_details = {
+ "environment" = "production"
+ "app_version" = "v2.1.0"
+ "debug_enabled" = "false"
+ }
+}
+```
diff --git a/website/docs/r/instance.html.markdown
b/website/docs/r/instance.html.markdown
index 7b0b1bd..ebf3f20 100644
--- a/website/docs/r/instance.html.markdown
+++ b/website/docs/r/instance.html.markdown
@@ -13,6 +13,8 @@ disk offering, and template.
## Example Usage
+### Basic Instance
+
```hcl
resource "cloudstack_instance" "web" {
name = "server-1"
@@ -23,6 +25,86 @@ resource "cloudstack_instance" "web" {
}
```
+### Instance with Inline User Data
+
+```hcl
+resource "cloudstack_instance" "web_with_userdata" {
+ name = "web-server"
+ service_offering = "small"
+ network_id = "6eb22f91-7454-4107-89f4-36afcdf33021"
+ template = "Ubuntu 20.04"
+ zone = "zone-1"
+
+ user_data = base64encode(<<-EOF
+ #!/bin/bash
+ apt-get update
+ apt-get install -y nginx
+ systemctl enable nginx
+ systemctl start nginx
+ EOF
+ )
+}
+```
+
+### Instance with Registered User Data
+
+```hcl
+# First, create registered user data
+resource "cloudstack_userdata" "web_init" {
+ name = "web-server-init"
+
+ userdata = base64encode(<<-EOF
+ #!/bin/bash
+ apt-get update
+ apt-get install -y nginx
+
+ # Use parameters
+ echo "<h1>Welcome to $${app_name}!</h1>" > /var/www/html/index.html
+ echo "<p>Environment: $${environment}</p>" >> /var/www/html/index.html
+
+ systemctl enable nginx
+ systemctl start nginx
+ EOF
+ )
+
+ params = ["app_name", "environment"]
+}
+
+# Deploy instance with parameterized user data
+resource "cloudstack_instance" "app_server" {
+ name = "app-server-01"
+ service_offering = "medium"
+ network_id = "6eb22f91-7454-4107-89f4-36afcdf33021"
+ template = "Ubuntu 20.04"
+ zone = "zone-1"
+
+ userdata_id = cloudstack_userdata.web_init.id
+
+ userdata_details = {
+ "app_name" = "My Application"
+ "environment" = "production"
+ }
+}
+```
+
+### Instance with Template-Linked User Data
+
+```hcl
+# Use a template that has user data pre-linked
+resource "cloudstack_instance" "from_template" {
+ name = "template-instance"
+ service_offering = "small"
+ network_id = "6eb22f91-7454-4107-89f4-36afcdf33021"
+ template = cloudstack_template.web_template.id # Template with
userdata_link
+ zone = "zone-1"
+
+ # Override parameters for the template's linked user data
+ userdata_details = {
+ "app_name" = "Template-Based App"
+ }
+}
+```
+
## Argument Reference
The following arguments are supported:
@@ -93,6 +175,10 @@ The following arguments are supported:
* `user_data` - (Optional) The user data to provide when launching the
instance. This can be either plain text or base64 encoded text.
+* `userdata_id` - (Optional) The ID of a registered CloudStack user data to
use for this instance. Cannot be used together with `user_data`.
+
+* `userdata_details` - (Optional) A map of key-value pairs to pass as
parameters to the user data script. Only valid when `userdata_id` is specified.
Keys must match the parameter names defined in the user data.
+
* `keypair` - (Optional) The name of the SSH key pair that will be used to
access this instance. (Mutual exclusive with keypairs)
diff --git a/website/docs/r/template.html.markdown
b/website/docs/r/template.html.markdown
index 52eb7fa..1f7a7d1 100644
--- a/website/docs/r/template.html.markdown
+++ b/website/docs/r/template.html.markdown
@@ -82,6 +82,10 @@ The following arguments are supported:
* `for_cks` - (Optional) Set to `true` to indicate this template is for
CloudStack Kubernetes Service (CKS). CKS templates have special requirements
and capabilities. Defaults to `false`.
+### User Data Integration
+
+* `userdata_link` - (Optional) Link user data to this template. When
specified, instances deployed from this template will inherit the linked user
data. See [User Data Link](#user-data-link) below for more details.
+
### Template Properties
* `is_dynamically_scalable` - (Optional) Set to indicate if the template
contains tools to support dynamic scaling of VM cpu/memory. Defaults to `false`.
@@ -136,6 +140,70 @@ The following attributes are exported:
* `domain` - The domain name where the template belongs.
* `project` - The project name if the template is assigned to a project.
+## User Data Link
+
+The `userdata_link` block supports the following arguments:
+
+* `userdata_id` - (Required) The ID of the user data to link to this template.
+* `userdata_policy` - (Required) The user data policy for instances deployed
from this template. Valid values:
+ * `ALLOWOVERRIDE` - Allow instances to override the linked user data with
their own
+ * `APPEND` - Append instance-specific user data to the template's linked
user data
+ * `DENYOVERRIDE` - Prevent instances from overriding the linked user data
+
+When a `userdata_link` is configured, the following additional attributes are
exported:
+
+* `userdata_name` - The name of the linked user data
+* `userdata_params` - The parameters defined in the linked user data
+
+### Example Template with User Data
+
+```hcl
+# Create user data
+resource "cloudstack_userdata" "web_init" {
+ name = "web-server-initialization"
+
+ userdata = base64encode(<<-EOF
+ #!/bin/bash
+ apt-get update
+ apt-get install -y nginx
+ echo "<h1>Welcome to $${app_name}!</h1>" > /var/www/html/index.html
+ systemctl enable nginx
+ systemctl start nginx
+ EOF
+ )
+
+ params = ["app_name"]
+}
+
+# Create template with linked user data
+resource "cloudstack_template" "web_template" {
+ name = "web-server-template"
+ display_text = "Web Server Template with Auto-Setup"
+ format = "QCOW2"
+ hypervisor = "KVM"
+ os_type = "Ubuntu 20.04"
+ url = "http://example.com/ubuntu-20.04.qcow2"
+ zone = "zone1"
+
+ userdata_link {
+ userdata_id = cloudstack_userdata.web_init.id
+ userdata_policy = "ALLOWOVERRIDE"
+ }
+}
+
+# Deploy instance using template with user data
+resource "cloudstack_instance" "web_server" {
+ name = "web-01"
+ template = cloudstack_template.web_template.id
+ # ... other arguments ...
+
+ # Pass parameters to the linked user data
+ userdata_details = {
+ "app_name" = "Production Web App"
+ }
+}
+```
+
### Example CKS Template Usage
```hcl
diff --git a/website/docs/r/userdata.html.markdown
b/website/docs/r/userdata.html.markdown
new file mode 100644
index 0000000..58c65fb
--- /dev/null
+++ b/website/docs/r/userdata.html.markdown
@@ -0,0 +1,150 @@
+---
+layout: "cloudstack"
+page_title: "CloudStack: cloudstack_user_data"
+sidebar_current: "docs-cloudstack-resource-user-data"
+description: |-
+ Registers and manages user data in CloudStack for VM initialization.
+---
+
+# cloudstack_user_data
+
+Registers user data in CloudStack that can be used to initialize virtual
machines during deployment. User data typically contains scripts, configuration
files, or other initialization data that should be executed when a VM starts.
+
+## Example Usage
+
+### Basic User Data
+
+```hcl
+resource "cloudstack_user_data" "web_init" {
+ name = "web-server-init"
+
+ userdata = base64encode(<<-EOF
+ #!/bin/bash
+ apt-get update
+ apt-get install -y nginx
+ systemctl enable nginx
+ systemctl start nginx
+ EOF
+ )
+}
+```
+
+### Parameterized User Data
+
+```hcl
+resource "cloudstack_user_data" "app_init" {
+ name = "app-server-init"
+
+ userdata = base64encode(<<-EOF
+ #!/bin/bash
+ apt-get update
+ apt-get install -y nginx
+
+ # Use parameters passed from instance deployment
+ echo "<h1>Welcome to $${app_name}!</h1>" > /var/www/html/index.html
+ echo "<p>Environment: $${environment}</p>" >> /var/www/html/index.html
+ echo "<p>Debug Mode: $${debug_mode}</p>" >> /var/www/html/index.html
+
+ systemctl enable nginx
+ systemctl start nginx
+ EOF
+ )
+
+ # Define parameters that can be passed during instance deployment
+ params = ["app_name", "environment", "debug_mode"]
+}
+```
+
+### Project-Scoped User Data
+
+```hcl
+resource "cloudstack_user_data" "project_init" {
+ name = "project-specific-init"
+ project_id = "12345678-1234-1234-1234-123456789012"
+
+ userdata = base64encode(<<-EOF
+ #!/bin/bash
+ # Project-specific initialization
+ echo "Initializing project environment..."
+ EOF
+ )
+}
+```
+
+## Argument Reference
+
+The following arguments are supported:
+
+### Required Arguments
+
+* `name` - (Required) The name of the user data. Must be unique within the
account/project scope.
+* `userdata` - (Required) The user data content to be registered. Should be
base64 encoded. This is typically a cloud-init script or other initialization
data.
+
+### Optional Arguments
+
+* `account` - (Optional) The account name for the user data. Must be used
together with `domain_id`. If not specified, uses the current account.
+* `domain_id` - (Optional) The domain ID for the user data. Required when
`account` is specified.
+* `project_id` - (Optional) The project ID to create this user data for.
Cannot be used together with `account`/`domain_id`.
+* `params` - (Optional) A list of parameter names that are declared in the
user data content. These parameters can be passed values during instance
deployment using `userdata_details`.
+
+## Attributes Reference
+
+The following attributes are exported:
+
+* `id` - The user data ID.
+* `name` - The name of the user data.
+* `userdata` - The registered user data content.
+* `account` - The account name owning the user data.
+* `domain_id` - The domain ID where the user data belongs.
+* `project_id` - The project ID if the user data is project-scoped.
+* `params` - The list of parameter names defined in the user data.
+
+## Usage with Templates and Instances
+
+User data can be used in multiple ways:
+
+### 1. Linked to Templates
+
+```hcl
+resource "cloudstack_template" "web_template" {
+ name = "web-server-template"
+ # ... other template arguments ...
+
+ userdata_link {
+ userdata_id = cloudstack_user_data.app_init.id
+ userdata_policy = "ALLOWOVERRIDE" # Allow instance to override
+ }
+}
+```
+
+### 2. Direct Instance Usage
+
+```hcl
+resource "cloudstack_instance" "web_server" {
+ name = "web-server-01"
+ # ... other instance arguments ...
+
+ userdata_id = cloudstack_user_data.app_init.id # Pass parameter values to
the userdata script
+ userdata_details = {
+ "app_name" = "My Web Application"
+ "environment" = "production"
+ "debug_mode" = "false"
+ }
+}
+```
+
+## Import
+
+User data can be imported using the user data ID:
+
+```
+terraform import cloudstack_user_data.example
12345678-1234-1234-1234-123456789012
+```
+
+## Notes
+
+* User data content should be base64 encoded before registration
+* Parameter substitution in user data uses the format `${parameter_name}`
+* Parameters must be declared in the `params` list to be usable
+* User data is immutable after creation - changes require resource recreation
+* Maximum user data size depends on CloudStack configuration (typically 32KB)