This is an automated email from the ASF dual-hosted git repository. pearl11594 pushed a commit to branch add-userdata-support in repository https://gitbox.apache.org/repos/asf/cloudstack-terraform-provider.git
commit 052a0e66e9d8b8627d314f815918c383cb2d1f9a Author: Pearl Dsilva <[email protected]> AuthorDate: Tue Oct 21 23:09:01 2025 -0400 Add support for userdata and linking to templates --- cloudstack/provider.go | 2 + cloudstack/resource_cloudstack_instance.go | 89 ++++++++++++++++- cloudstack/resource_cloudstack_template.go | 149 ++++++++++++++++++++++++++++ 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 | 152 +++++++++++++++++++++++++++++ 7 files changed, 671 insertions(+), 1 deletion(-) diff --git a/cloudstack/provider.go b/cloudstack/provider.go index 63d0234..75f751c 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_userdata": resourceCloudStackUserData(), }, ConfigureFunc: providerConfigure, diff --git a/cloudstack/resource_cloudstack_instance.go b/cloudstack/resource_cloudstack_instance.go index 4caa345..3a1f0b0 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,31 @@ func resourceCloudStackInstanceRead(d *schema.ResourceData, meta interface{}) er d.Set("boot_mode", vm.Bootmode) } + // Set userdata information if available + if vm.Userdataid != "" { + d.Set("userdata_id", vm.Userdataid) + } + + // Handle userdata with graceful Base64 decoding + if vm.Userdata != "" { + decoded, err := base64.StdEncoding.DecodeString(vm.Userdata) + if err != nil { + // If decoding fails, store the raw data + d.Set("user_data", vm.Userdata) + } else { + // Successfully decoded, store as string + d.Set("user_data", string(decoded)) + } + } + + // Set userdata_details if available (CloudStack might return them in a specific format) + if vm.Userdatadetails != "" { + // Note: CloudStack typically returns userdata details as a JSON string or key-value pairs + // We may need to parse this based on the actual CloudStack response format + // For now, we'll leave this as a placeholder since the exact format needs to be verified + log.Printf("[DEBUG] Instance %s has userdata details: %s", vm.Name, vm.Userdatadetails) + } + return nil } @@ -609,7 +659,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 +814,42 @@ func resourceCloudStackInstanceUpdate(d *schema.ResourceData, meta interface{}) } } + // Check if the userdata_id has changed and if so, update it + 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) + } + } + + // Check if the userdata_details has changed and if so, update it + 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/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..5392b54 --- /dev/null +++ b/website/docs/r/userdata.html.markdown @@ -0,0 +1,152 @@ +--- +layout: "cloudstack" +page_title: "CloudStack: cloudstack_userdata" +sidebar_current: "docs-cloudstack-resource-userdata" +description: |- + Registers and manages user data in CloudStack for VM initialization. +--- + +# cloudstack_userdata + +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_userdata" "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_userdata" "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_userdata" "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_userdata.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_userdata.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_userdata.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)
