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 86dc428 Add support for autoscale VM groups (#213)
86dc428 is described below
commit 86dc4282d2ef604ec0492d3ebd9b9455b88ac777
Author: Pearl Dsilva <[email protected]>
AuthorDate: Thu Sep 25 03:33:05 2025 -0400
Add support for autoscale VM groups (#213)
* Add support for Autoscale VM groups
* register resources
* Add support for datasources for all autoscale resources
* add support to delete asg with cleanup. Support to enable and disable asg
* fix conflict
* add documentation
* update
* update go sdk version
* wait for asg to move to disabled state before making modifications to lb
rule associated to an asg or autoscale vm profile
---------
Co-authored-by: dahn <[email protected]>
---
.../data_source_cloudstack_autoscale_policy.go | 152 +++++++
.../data_source_cloudstack_autoscale_vm_group.go | 188 +++++++++
.../data_source_cloudstack_autoscale_vm_profile.go | 148 +++++++
cloudstack/data_source_cloudstack_condition.go | 110 +++++
cloudstack/data_source_cloudstack_counter.go | 122 ++++++
cloudstack/provider.go | 45 +-
cloudstack/resource_cloudstack_autoscale_policy.go | 226 +++++++++++
.../resource_cloudstack_autoscale_vm_group.go | 452 +++++++++++++++++++++
.../resource_cloudstack_autoscale_vm_profile.go | 366 +++++++++++++++--
cloudstack/resource_cloudstack_condition.go | 182 +++++++++
cloudstack/resource_cloudstack_counter.go | 126 ++++++
.../resource_cloudstack_loadbalancer_rule.go | 198 ++++++++-
go.mod | 5 +-
go.sum | 7 +-
website/docs/d/autoscale_policy.html.markdown | 69 ++++
website/docs/d/autoscale_vm_group.html.markdown | 71 ++++
website/docs/d/autoscale_vm_profile.html.markdown | 76 ++++
website/docs/d/condition.html.markdown | 61 +++
website/docs/d/counter.html.markdown | 57 +++
website/docs/r/autoscale_policy.html.markdown | 63 +++
website/docs/r/autoscale_vm_group.html.markdown | 111 +++++
website/docs/r/condition.html.markdown | 61 +++
website/docs/r/counter.html.markdown | 43 ++
23 files changed, 2846 insertions(+), 93 deletions(-)
diff --git a/cloudstack/data_source_cloudstack_autoscale_policy.go
b/cloudstack/data_source_cloudstack_autoscale_policy.go
new file mode 100644
index 0000000..c8bea3c
--- /dev/null
+++ b/cloudstack/data_source_cloudstack_autoscale_policy.go
@@ -0,0 +1,152 @@
+//
+// 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"
+ "log"
+
+ "github.com/apache/cloudstack-go/v2/cloudstack"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+)
+
+func dataSourceCloudstackAutoscalePolicy() *schema.Resource {
+ return &schema.Resource{
+ Read: dataSourceCloudstackAutoscalePolicyRead,
+
+ Schema: map[string]*schema.Schema{
+ "id": {
+ Type: schema.TypeString,
+ Optional: true,
+ Computed: true,
+ },
+
+ "name": {
+ Type: schema.TypeString,
+ Optional: true,
+ Computed: true,
+ },
+
+ "action": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+
+ "duration": {
+ Type: schema.TypeInt,
+ Computed: true,
+ },
+
+ "quiet_time": {
+ Type: schema.TypeInt,
+ Computed: true,
+ },
+
+ "condition_ids": {
+ Type: schema.TypeSet,
+ Computed: true,
+ Elem: &schema.Schema{Type:
schema.TypeString},
+ },
+
+ "account_name": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+
+ "domain_id": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+
+ "project_id": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ },
+ }
+}
+
+func dataSourceCloudstackAutoscalePolicyRead(d *schema.ResourceData, meta
interface{}) error {
+ cs := meta.(*cloudstack.CloudStackClient)
+
+ id, idOk := d.GetOk("id")
+ name, nameOk := d.GetOk("name")
+
+ if !idOk && !nameOk {
+ return fmt.Errorf("either 'id' or 'name' must be specified")
+ }
+
+ var policy *cloudstack.AutoScalePolicy
+
+ if idOk {
+ p := cs.AutoScale.NewListAutoScalePoliciesParams()
+ p.SetId(id.(string))
+
+ resp, err := cs.AutoScale.ListAutoScalePolicies(p)
+ if err != nil {
+ return fmt.Errorf("failed to list autoscale policies:
%s", err)
+ }
+
+ if resp.Count == 0 {
+ return fmt.Errorf("autoscale policy with ID %s not
found", id.(string))
+ }
+
+ policy = resp.AutoScalePolicies[0]
+ } else {
+ p := cs.AutoScale.NewListAutoScalePoliciesParams()
+
+ resp, err := cs.AutoScale.ListAutoScalePolicies(p)
+ if err != nil {
+ return fmt.Errorf("failed to list autoscale policies:
%s", err)
+ }
+
+ for _, pol := range resp.AutoScalePolicies {
+ if pol.Name == name.(string) {
+ policy = pol
+ break
+ }
+ }
+
+ if policy == nil {
+ return fmt.Errorf("autoscale policy with name %s not
found", name.(string))
+ }
+ }
+
+ log.Printf("[DEBUG] Found autoscale policy: %s", policy.Name)
+
+ d.SetId(policy.Id)
+ d.Set("name", policy.Name)
+ d.Set("action", policy.Action)
+ d.Set("duration", policy.Duration)
+ d.Set("quiet_time", policy.Quiettime)
+ d.Set("account_name", policy.Account)
+ d.Set("domain_id", policy.Domainid)
+ if policy.Projectid != "" {
+ d.Set("project_id", policy.Projectid)
+ }
+
+ conditionIds := make([]string, len(policy.Conditions))
+ for i, condition := range policy.Conditions {
+ conditionIds[i] = condition.Id
+ }
+ d.Set("condition_ids", conditionIds)
+
+ return nil
+}
diff --git a/cloudstack/data_source_cloudstack_autoscale_vm_group.go
b/cloudstack/data_source_cloudstack_autoscale_vm_group.go
new file mode 100644
index 0000000..135cd4c
--- /dev/null
+++ b/cloudstack/data_source_cloudstack_autoscale_vm_group.go
@@ -0,0 +1,188 @@
+//
+// 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"
+ "log"
+
+ "github.com/apache/cloudstack-go/v2/cloudstack"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+)
+
+func dataSourceCloudstackAutoscaleVMGroup() *schema.Resource {
+ return &schema.Resource{
+ Read: dataSourceCloudstackAutoscaleVMGroupRead,
+
+ Schema: map[string]*schema.Schema{
+ "id": {
+ Type: schema.TypeString,
+ Optional: true,
+ Computed: true,
+ },
+
+ "name": {
+ Type: schema.TypeString,
+ Optional: true,
+ Computed: true,
+ },
+
+ "lbrule_id": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+
+ "min_members": {
+ Type: schema.TypeInt,
+ Computed: true,
+ },
+
+ "max_members": {
+ Type: schema.TypeInt,
+ Computed: true,
+ },
+
+ "vm_profile_id": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+
+ "scaleup_policy_ids": {
+ Type: schema.TypeSet,
+ Computed: true,
+ Elem: &schema.Schema{Type:
schema.TypeString},
+ },
+
+ "scaledown_policy_ids": {
+ Type: schema.TypeSet,
+ Computed: true,
+ Elem: &schema.Schema{Type:
schema.TypeString},
+ },
+
+ "state": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+
+ "interval": {
+ Type: schema.TypeInt,
+ Computed: true,
+ },
+
+ "available_virtual_machine_count": {
+ Type: schema.TypeInt,
+ Computed: true,
+ },
+
+ "account_name": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+
+ "domain_id": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+
+ "project_id": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ },
+ }
+}
+
+func dataSourceCloudstackAutoscaleVMGroupRead(d *schema.ResourceData, meta
interface{}) error {
+ cs := meta.(*cloudstack.CloudStackClient)
+
+ id, idOk := d.GetOk("id")
+ name, nameOk := d.GetOk("name")
+
+ if !idOk && !nameOk {
+ return fmt.Errorf("either 'id' or 'name' must be specified")
+ }
+
+ var group *cloudstack.AutoScaleVmGroup
+
+ if idOk {
+ p := cs.AutoScale.NewListAutoScaleVmGroupsParams()
+ p.SetId(id.(string))
+
+ resp, err := cs.AutoScale.ListAutoScaleVmGroups(p)
+ if err != nil {
+ return fmt.Errorf("failed to list autoscale VM groups:
%s", err)
+ }
+
+ if resp.Count == 0 {
+ return fmt.Errorf("autoscale VM group with ID %s not
found", id.(string))
+ }
+
+ group = resp.AutoScaleVmGroups[0]
+ } else {
+ p := cs.AutoScale.NewListAutoScaleVmGroupsParams()
+
+ resp, err := cs.AutoScale.ListAutoScaleVmGroups(p)
+ if err != nil {
+ return fmt.Errorf("failed to list autoscale VM groups:
%s", err)
+ }
+
+ for _, grp := range resp.AutoScaleVmGroups {
+ if grp.Name == name.(string) {
+ group = grp
+ break
+ }
+ }
+
+ if group == nil {
+ return fmt.Errorf("autoscale VM group with name %s not
found", name.(string))
+ }
+ }
+
+ log.Printf("[DEBUG] Found autoscale VM group: %s", group.Name)
+
+ d.SetId(group.Id)
+ d.Set("name", group.Name)
+ d.Set("lbrule_id", group.Lbruleid)
+ d.Set("min_members", group.Minmembers)
+ d.Set("max_members", group.Maxmembers)
+ d.Set("vm_profile_id", group.Vmprofileid)
+ d.Set("state", group.State)
+ d.Set("interval", group.Interval)
+ d.Set("available_virtual_machine_count",
group.Availablevirtualmachinecount)
+ d.Set("account_name", group.Account)
+ d.Set("domain_id", group.Domainid)
+ if group.Projectid != "" {
+ d.Set("project_id", group.Projectid)
+ }
+
+ scaleupPolicyIds := make([]string, len(group.Scaleuppolicies))
+ for i, policy := range group.Scaleuppolicies {
+ scaleupPolicyIds[i] = policy.Id
+ }
+ d.Set("scaleup_policy_ids", scaleupPolicyIds)
+
+ scaledownPolicyIds := make([]string, len(group.Scaledownpolicies))
+ for i, policy := range group.Scaledownpolicies {
+ scaledownPolicyIds[i] = policy.Id
+ }
+ d.Set("scaledown_policy_ids", scaledownPolicyIds)
+
+ return nil
+}
diff --git a/cloudstack/data_source_cloudstack_autoscale_vm_profile.go
b/cloudstack/data_source_cloudstack_autoscale_vm_profile.go
new file mode 100644
index 0000000..47d38c0
--- /dev/null
+++ b/cloudstack/data_source_cloudstack_autoscale_vm_profile.go
@@ -0,0 +1,148 @@
+//
+// 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"
+ "log"
+
+ "github.com/apache/cloudstack-go/v2/cloudstack"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+)
+
+func dataSourceCloudstackAutoscaleVMProfile() *schema.Resource {
+ return &schema.Resource{
+ Read: dataSourceCloudstackAutoscaleVMProfileRead,
+
+ Schema: map[string]*schema.Schema{
+ "id": {
+ Type: schema.TypeString,
+ Optional: true,
+ Computed: true,
+ },
+
+ "service_offering": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+
+ "template": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+
+ "zone": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+
+ "destroy_vm_grace_period": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+
+ "counter_param_list": {
+ Type: schema.TypeMap,
+ Computed: true,
+ Elem: &schema.Schema{Type:
schema.TypeString},
+ },
+
+ "user_data": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+
+ "user_data_details": {
+ Type: schema.TypeMap,
+ Computed: true,
+ Elem: &schema.Schema{Type:
schema.TypeString},
+ },
+
+ "account_name": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+
+ "domain_id": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+
+ "project_id": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+
+ "display": {
+ Type: schema.TypeBool,
+ Computed: true,
+ },
+
+ "other_deploy_params": {
+ Type: schema.TypeMap,
+ Computed: true,
+ Elem: &schema.Schema{Type:
schema.TypeString},
+ },
+ },
+ }
+}
+
+func dataSourceCloudstackAutoscaleVMProfileRead(d *schema.ResourceData, meta
interface{}) error {
+ cs := meta.(*cloudstack.CloudStackClient)
+
+ id, idOk := d.GetOk("id")
+
+ if !idOk {
+ return fmt.Errorf("'id' must be specified")
+ }
+
+ p := cs.AutoScale.NewListAutoScaleVmProfilesParams()
+ p.SetId(id.(string))
+
+ resp, err := cs.AutoScale.ListAutoScaleVmProfiles(p)
+ if err != nil {
+ return fmt.Errorf("failed to list autoscale VM profiles: %s",
err)
+ }
+
+ if resp.Count == 0 {
+ return fmt.Errorf("autoscale VM profile with ID %s not found",
id.(string))
+ }
+
+ profile := resp.AutoScaleVmProfiles[0]
+
+ log.Printf("[DEBUG] Found autoscale VM profile: %s", profile.Id)
+
+ d.SetId(profile.Id)
+ d.Set("service_offering", profile.Serviceofferingid)
+ d.Set("template", profile.Templateid)
+ d.Set("zone", profile.Zoneid)
+ d.Set("account_name", profile.Account)
+ d.Set("domain_id", profile.Domainid)
+ if profile.Projectid != "" {
+ d.Set("project_id", profile.Projectid)
+ }
+ d.Set("display", profile.Fordisplay)
+
+ if profile.Userdata != "" {
+ d.Set("user_data", profile.Userdata)
+ }
+
+ return nil
+}
diff --git a/cloudstack/data_source_cloudstack_condition.go
b/cloudstack/data_source_cloudstack_condition.go
new file mode 100644
index 0000000..d901f28
--- /dev/null
+++ b/cloudstack/data_source_cloudstack_condition.go
@@ -0,0 +1,110 @@
+//
+// 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"
+ "log"
+
+ "github.com/apache/cloudstack-go/v2/cloudstack"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+)
+
+func dataSourceCloudstackCondition() *schema.Resource {
+ return &schema.Resource{
+ Read: dataSourceCloudstackConditionRead,
+
+ Schema: map[string]*schema.Schema{
+ "id": {
+ Type: schema.TypeString,
+ Optional: true,
+ Computed: true,
+ },
+
+ "counter_id": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+
+ "relational_operator": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+
+ "threshold": {
+ Type: schema.TypeFloat,
+ Computed: true,
+ },
+
+ "account_name": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+
+ "domain_id": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+
+ "project_id": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ },
+ }
+}
+
+func dataSourceCloudstackConditionRead(d *schema.ResourceData, meta
interface{}) error {
+ cs := meta.(*cloudstack.CloudStackClient)
+
+ id, idOk := d.GetOk("id")
+
+ if !idOk {
+ return fmt.Errorf("'id' must be specified")
+ }
+
+ p := cs.AutoScale.NewListConditionsParams()
+ p.SetId(id.(string))
+
+ resp, err := cs.AutoScale.ListConditions(p)
+ if err != nil {
+ return fmt.Errorf("failed to list conditions: %s", err)
+ }
+
+ if resp.Count == 0 {
+ return fmt.Errorf("condition with ID %s not found", id.(string))
+ }
+
+ condition := resp.Conditions[0]
+
+ log.Printf("[DEBUG] Found condition: %s", condition.Id)
+
+ d.SetId(condition.Id)
+ d.Set("counter_id", condition.Counterid)
+ d.Set("relational_operator", condition.Relationaloperator)
+ d.Set("threshold", condition.Threshold)
+ d.Set("account_name", condition.Account)
+ d.Set("domain_id", condition.Domainid)
+ if condition.Projectid != "" {
+ d.Set("project_id", condition.Projectid)
+ }
+
+ return nil
+}
diff --git a/cloudstack/data_source_cloudstack_counter.go
b/cloudstack/data_source_cloudstack_counter.go
new file mode 100644
index 0000000..dab1e44
--- /dev/null
+++ b/cloudstack/data_source_cloudstack_counter.go
@@ -0,0 +1,122 @@
+//
+// 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"
+ "log"
+
+ "github.com/apache/cloudstack-go/v2/cloudstack"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+)
+
+func dataSourceCloudstackCounter() *schema.Resource {
+ return &schema.Resource{
+ Read: dataSourceCloudstackCounterRead,
+
+ Schema: map[string]*schema.Schema{
+ "id": {
+ Type: schema.TypeString,
+ Optional: true,
+ Computed: true,
+ },
+
+ "name": {
+ Type: schema.TypeString,
+ Optional: true,
+ Computed: true,
+ },
+
+ "source": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+
+ "value": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+
+ "counter_provider": {
+ Type: schema.TypeString,
+ Computed: true,
+ },
+ },
+ }
+}
+
+func dataSourceCloudstackCounterRead(d *schema.ResourceData, meta interface{})
error {
+ cs := meta.(*cloudstack.CloudStackClient)
+
+ id, idOk := d.GetOk("id")
+ name, nameOk := d.GetOk("name")
+
+ if !idOk && !nameOk {
+ return fmt.Errorf("either 'id' or 'name' must be specified")
+ }
+
+ var counter *cloudstack.Counter
+
+ if idOk {
+ // Get counter by ID
+ p := cs.AutoScale.NewListCountersParams()
+ p.SetId(id.(string))
+
+ resp, err := cs.AutoScale.ListCounters(p)
+ if err != nil {
+ return fmt.Errorf("failed to list counters: %s", err)
+ }
+
+ if resp.Count == 0 {
+ return fmt.Errorf("counter with ID %s not found",
id.(string))
+ }
+
+ counter = resp.Counters[0]
+ } else {
+ // Get counter by name
+ p := cs.AutoScale.NewListCountersParams()
+
+ resp, err := cs.AutoScale.ListCounters(p)
+ if err != nil {
+ return fmt.Errorf("failed to list counters: %s", err)
+ }
+
+ for _, c := range resp.Counters {
+ if c.Name == name.(string) {
+ counter = c
+ break
+ }
+ }
+
+ if counter == nil {
+ return fmt.Errorf("counter with name %s not found",
name.(string))
+ }
+ }
+
+ log.Printf("[DEBUG] Found counter: %s", counter.Name)
+
+ d.SetId(counter.Id)
+ d.Set("name", counter.Name)
+ d.Set("source", counter.Source)
+ d.Set("value", counter.Value)
+ d.Set("counter_provider", counter.Provider)
+
+ return nil
+}
diff --git a/cloudstack/provider.go b/cloudstack/provider.go
index 91c0f5c..277dfae 100644
--- a/cloudstack/provider.go
+++ b/cloudstack/provider.go
@@ -78,31 +78,40 @@ func Provider() *schema.Provider {
},
DataSourcesMap: map[string]*schema.Resource{
- "cloudstack_template":
dataSourceCloudstackTemplate(),
- "cloudstack_ssh_keypair":
dataSourceCloudstackSSHKeyPair(),
- "cloudstack_instance":
dataSourceCloudstackInstance(),
- "cloudstack_network_offering":
dataSourceCloudstackNetworkOffering(),
- "cloudstack_zone":
dataSourceCloudStackZone(),
- "cloudstack_service_offering":
dataSourceCloudstackServiceOffering(),
- "cloudstack_volume":
dataSourceCloudstackVolume(),
- "cloudstack_vpc":
dataSourceCloudstackVPC(),
- "cloudstack_ipaddress":
dataSourceCloudstackIPAddress(),
- "cloudstack_user":
dataSourceCloudstackUser(),
- "cloudstack_vpn_connection":
dataSourceCloudstackVPNConnection(),
- "cloudstack_pod":
dataSourceCloudstackPod(),
- "cloudstack_domain":
dataSourceCloudstackDomain(),
- "cloudstack_project":
dataSourceCloudstackProject(),
- "cloudstack_physical_network":
dataSourceCloudStackPhysicalNetwork(),
- "cloudstack_role":
dataSourceCloudstackRole(),
- "cloudstack_cluster":
dataSourceCloudstackCluster(),
- "cloudstack_limits":
dataSourceCloudStackLimits(),
+ "cloudstack_autoscale_policy":
dataSourceCloudstackAutoscalePolicy(),
+ "cloudstack_autoscale_vm_group":
dataSourceCloudstackAutoscaleVMGroup(),
+ "cloudstack_autoscale_vm_profile":
dataSourceCloudstackAutoscaleVMProfile(),
+ "cloudstack_condition":
dataSourceCloudstackCondition(),
+ "cloudstack_counter":
dataSourceCloudstackCounter(),
+ "cloudstack_template":
dataSourceCloudstackTemplate(),
+ "cloudstack_ssh_keypair":
dataSourceCloudstackSSHKeyPair(),
+ "cloudstack_instance":
dataSourceCloudstackInstance(),
+ "cloudstack_network_offering":
dataSourceCloudstackNetworkOffering(),
+ "cloudstack_zone":
dataSourceCloudStackZone(),
+ "cloudstack_service_offering":
dataSourceCloudstackServiceOffering(),
+ "cloudstack_volume":
dataSourceCloudstackVolume(),
+ "cloudstack_vpc":
dataSourceCloudstackVPC(),
+ "cloudstack_ipaddress":
dataSourceCloudstackIPAddress(),
+ "cloudstack_user":
dataSourceCloudstackUser(),
+ "cloudstack_vpn_connection":
dataSourceCloudstackVPNConnection(),
+ "cloudstack_pod":
dataSourceCloudstackPod(),
+ "cloudstack_domain":
dataSourceCloudstackDomain(),
+ "cloudstack_project":
dataSourceCloudstackProject(),
+ "cloudstack_physical_network":
dataSourceCloudStackPhysicalNetwork(),
+ "cloudstack_role":
dataSourceCloudstackRole(),
+ "cloudstack_cluster":
dataSourceCloudstackCluster(),
+ "cloudstack_limits":
dataSourceCloudStackLimits(),
},
ResourcesMap: map[string]*schema.Resource{
"cloudstack_affinity_group":
resourceCloudStackAffinityGroup(),
"cloudstack_attach_volume":
resourceCloudStackAttachVolume(),
+ "cloudstack_autoscale_policy":
resourceCloudStackAutoScalePolicy(),
+ "cloudstack_autoscale_vm_group":
resourceCloudStackAutoScaleVMGroup(),
"cloudstack_autoscale_vm_profile":
resourceCloudStackAutoScaleVMProfile(),
+ "cloudstack_condition":
resourceCloudStackCondition(),
"cloudstack_configuration":
resourceCloudStackConfiguration(),
+ "cloudstack_counter":
resourceCloudStackCounter(),
"cloudstack_cluster":
resourceCloudStackCluster(),
"cloudstack_disk":
resourceCloudStackDisk(),
"cloudstack_egress_firewall":
resourceCloudStackEgressFirewall(),
diff --git a/cloudstack/resource_cloudstack_autoscale_policy.go
b/cloudstack/resource_cloudstack_autoscale_policy.go
new file mode 100644
index 0000000..fe390b5
--- /dev/null
+++ b/cloudstack/resource_cloudstack_autoscale_policy.go
@@ -0,0 +1,226 @@
+//
+// 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"
+ "log"
+ "strings"
+
+ "github.com/apache/cloudstack-go/v2/cloudstack"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+)
+
+func resourceCloudStackAutoScalePolicy() *schema.Resource {
+ return &schema.Resource{
+ Create: resourceCloudStackAutoScalePolicyCreate,
+ Read: resourceCloudStackAutoScalePolicyRead,
+ Update: resourceCloudStackAutoScalePolicyUpdate,
+ Delete: resourceCloudStackAutoScalePolicyDelete,
+
+ Schema: map[string]*schema.Schema{
+ "name": {
+ Type: schema.TypeString,
+ Optional: true,
+ Description: "the name of the autoscale policy",
+ },
+ "action": {
+ Type: schema.TypeString,
+ Required: true,
+ Description: "the action to be executed if all
the conditions evaluate to true for the specified duration (case insensitive:
scaleup/SCALEUP or scaledown/SCALEDOWN)",
+ ForceNew: true,
+ StateFunc: func(val interface{}) string {
+ return strings.ToUpper(val.(string))
+ },
+ DiffSuppressFunc: func(k, old, new string, d
*schema.ResourceData) bool {
+ return strings.ToUpper(old) ==
strings.ToUpper(new)
+ },
+ },
+ "duration": {
+ Type: schema.TypeInt,
+ Required: true,
+ Description: "the duration in which the
conditions have to be true before action is taken",
+ },
+ "quiet_time": {
+ Type: schema.TypeInt,
+ Optional: true,
+ Description: "the cool down period in which the
policy should not be evaluated after the action has been taken",
+ },
+ "condition_ids": {
+ Type: schema.TypeSet,
+ Elem: &schema.Schema{Type:
schema.TypeString},
+ Required: true,
+ Description: "the list of IDs of the conditions
that are being evaluated on every interval",
+ },
+ },
+ }
+}
+
+func resourceCloudStackAutoScalePolicyCreate(d *schema.ResourceData, meta
interface{}) error {
+ cs := meta.(*cloudstack.CloudStackClient)
+
+ action := d.Get("action").(string)
+ duration := d.Get("duration").(int)
+
+ conditionIds := []string{}
+ if v, ok := d.GetOk("condition_ids"); ok {
+ conditionSet := v.(*schema.Set)
+ for _, id := range conditionSet.List() {
+ conditionIds = append(conditionIds, id.(string))
+ }
+ }
+
+ p := cs.AutoScale.NewCreateAutoScalePolicyParams(action, conditionIds,
duration)
+
+ if v, ok := d.GetOk("name"); ok {
+ p.SetName(v.(string))
+ }
+ if v, ok := d.GetOk("quiet_time"); ok {
+ p.SetQuiettime(v.(int))
+ }
+
+ log.Printf("[DEBUG] Creating autoscale policy")
+ resp, err := cs.AutoScale.CreateAutoScalePolicy(p)
+ if err != nil {
+ return fmt.Errorf("Error creating autoscale policy: %s", err)
+ }
+
+ d.SetId(resp.Id)
+ log.Printf("[DEBUG] Autoscale policy created with ID: %s", resp.Id)
+
+ conditionSet := schema.NewSet(schema.HashString, []interface{}{})
+ for _, id := range conditionIds {
+ conditionSet.Add(id)
+ }
+ d.Set("condition_ids", conditionSet)
+ d.Set("name", d.Get("name").(string))
+ d.Set("action", action)
+ d.Set("duration", duration)
+ if v, ok := d.GetOk("quiet_time"); ok {
+ d.Set("quiet_time", v.(int))
+ }
+
+ return nil
+}
+
+func resourceCloudStackAutoScalePolicyRead(d *schema.ResourceData, meta
interface{}) error {
+ cs := meta.(*cloudstack.CloudStackClient)
+
+ p := cs.AutoScale.NewListAutoScalePoliciesParams()
+ p.SetId(d.Id())
+
+ resp, err := cs.AutoScale.ListAutoScalePolicies(p)
+ if err != nil {
+ return fmt.Errorf("Error retrieving autoscale policy: %s", err)
+ }
+
+ if resp.Count == 0 {
+ log.Printf("[DEBUG] Autoscale policy %s no longer exists",
d.Id())
+ d.SetId("")
+ return nil
+ }
+
+ policy := resp.AutoScalePolicies[0]
+ d.Set("name", policy.Name)
+ // CloudStack always returns uppercase actions (SCALEUP/SCALEDOWN)
+ // Our StateFunc normalizes user input to uppercase, so this should
match
+ d.Set("action", policy.Action)
+ d.Set("duration", policy.Duration)
+ d.Set("quiet_time", policy.Quiettime)
+
+ conditionIds := schema.NewSet(schema.HashString, []interface{}{})
+ for _, condition := range policy.Conditions {
+ // Try to extract the ID from the condition object
+ if condition == nil {
+ continue
+ }
+
+ // The condition is a *cloudstack.Condition struct, so access
the Id field directly
+ if condition.Id != "" {
+ conditionIds.Add(condition.Id)
+ } else {
+ log.Printf("[WARN] Condition has empty ID: %+v",
condition)
+ }
+ }
+ d.Set("condition_ids", conditionIds)
+
+ return nil
+}
+
+func resourceCloudStackAutoScalePolicyUpdate(d *schema.ResourceData, meta
interface{}) error {
+ cs := meta.(*cloudstack.CloudStackClient)
+
+ if d.HasChange("name") || d.HasChange("condition_ids") ||
d.HasChange("duration") || d.HasChange("quiet_time") {
+ log.Printf("[DEBUG] Updating autoscale policy: %s", d.Id())
+
+ p := cs.AutoScale.NewUpdateAutoScalePolicyParams(d.Id())
+
+ if d.HasChange("name") {
+ if v, ok := d.GetOk("name"); ok {
+ p.SetName(v.(string))
+ }
+ }
+
+ if d.HasChange("duration") {
+ duration := d.Get("duration").(int)
+ p.SetDuration(duration)
+ }
+
+ if d.HasChange("quiet_time") {
+ if v, ok := d.GetOk("quiet_time"); ok {
+ p.SetQuiettime(v.(int))
+ }
+ }
+
+ if d.HasChange("condition_ids") {
+ conditionIds := []string{}
+ if v, ok := d.GetOk("condition_ids"); ok {
+ conditionSet := v.(*schema.Set)
+ for _, id := range conditionSet.List() {
+ conditionIds = append(conditionIds,
id.(string))
+ }
+ }
+ p.SetConditionids(conditionIds)
+ }
+
+ _, err := cs.AutoScale.UpdateAutoScalePolicy(p)
+ if err != nil {
+ return fmt.Errorf("Error updating autoscale policy:
%s", err)
+ }
+
+ log.Printf("[DEBUG] Autoscale policy updated successfully: %s",
d.Id())
+ }
+
+ return resourceCloudStackAutoScalePolicyRead(d, meta)
+}
+
+func resourceCloudStackAutoScalePolicyDelete(d *schema.ResourceData, meta
interface{}) error {
+ cs := meta.(*cloudstack.CloudStackClient)
+
+ p := cs.AutoScale.NewDeleteAutoScalePolicyParams(d.Id())
+
+ log.Printf("[DEBUG] Deleting autoscale policy: %s", d.Id())
+ _, err := cs.AutoScale.DeleteAutoScalePolicy(p)
+ if err != nil {
+ return fmt.Errorf("Error deleting autoscale policy: %s", err)
+ }
+
+ return nil
+}
diff --git a/cloudstack/resource_cloudstack_autoscale_vm_group.go
b/cloudstack/resource_cloudstack_autoscale_vm_group.go
new file mode 100644
index 0000000..8d0eb7e
--- /dev/null
+++ b/cloudstack/resource_cloudstack_autoscale_vm_group.go
@@ -0,0 +1,452 @@
+//
+// 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"
+ "log"
+ "strings"
+ "time"
+
+ "github.com/apache/cloudstack-go/v2/cloudstack"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+)
+
+func resourceCloudStackAutoScaleVMGroup() *schema.Resource {
+ return &schema.Resource{
+ Create: resourceCloudStackAutoScaleVMGroupCreate,
+ Read: resourceCloudStackAutoScaleVMGroupRead,
+ Update: resourceCloudStackAutoScaleVMGroupUpdate,
+ Delete: resourceCloudStackAutoScaleVMGroupDelete,
+
+ Schema: map[string]*schema.Schema{
+ "lbrule_id": {
+ Type: schema.TypeString,
+ Required: true,
+ ForceNew: true,
+ Description: "the ID of the load balancer rule",
+ },
+
+ "name": {
+ Type: schema.TypeString,
+ Optional: true,
+ Description: "the name of the autoscale
vmgroup",
+ },
+
+ "min_members": {
+ Type: schema.TypeInt,
+ Required: true,
+ Description: "the minimum number of members in
the vmgroup, the number of instances in the vm group will be equal to or more
than this number",
+ },
+
+ "max_members": {
+ Type: schema.TypeInt,
+ Required: true,
+ Description: "the maximum number of members in
the vmgroup, The number of instances in the vm group will be equal to or less
than this number",
+ },
+
+ "interval": {
+ Type: schema.TypeInt,
+ Optional: true,
+ Description: "the frequency in which the
performance counters to be collected",
+ },
+
+ "scaleup_policy_ids": {
+ Type: schema.TypeSet,
+ Elem: &schema.Schema{Type:
schema.TypeString},
+ Required: true,
+ Description: "list of scaleup autoscale
policies",
+ },
+
+ "scaledown_policy_ids": {
+ Type: schema.TypeSet,
+ Elem: &schema.Schema{Type:
schema.TypeString},
+ Required: true,
+ Description: "list of scaledown autoscale
policies",
+ },
+
+ "vm_profile_id": {
+ Type: schema.TypeString,
+ Required: true,
+ ForceNew: true,
+ Description: "the autoscale profile that
contains information about the vms in the vm group",
+ },
+
+ "display": {
+ Type: schema.TypeBool,
+ Optional: true,
+ Description: "an optional field, whether to the
display the group to the end user or not",
+ },
+
+ "state": {
+ Type: schema.TypeString,
+ Optional: true,
+ Default: "enable",
+ Description: "the state of the autoscale vm
group (enable or disable)",
+ ValidateFunc: func(v interface{}, k string) (ws
[]string, errors []error) {
+ value := v.(string)
+ if value != "enable" && value !=
"disable" {
+ errors = append(errors,
fmt.Errorf("state must be either 'enable' or 'disable'"))
+ }
+ return
+ },
+ },
+
+ "cleanup": {
+ Type: schema.TypeBool,
+ Optional: true,
+ Default: false,
+ Description: "true if all the members of
autoscale vm group has to be cleaned up, false otherwise",
+ },
+ },
+ }
+}
+
+func resourceCloudStackAutoScaleVMGroupCreate(d *schema.ResourceData, meta
interface{}) error {
+ cs := meta.(*cloudstack.CloudStackClient)
+
+ lbruleid := d.Get("lbrule_id").(string)
+ minmembers := d.Get("min_members").(int)
+ maxmembers := d.Get("max_members").(int)
+ vmprofileid := d.Get("vm_profile_id").(string)
+
+ scaleUpPolicyIds := []string{}
+ if v, ok := d.GetOk("scaleup_policy_ids"); ok {
+ scaleUpSet := v.(*schema.Set)
+ for _, id := range scaleUpSet.List() {
+ scaleUpPolicyIds = append(scaleUpPolicyIds, id.(string))
+ }
+ }
+
+ scaleDownPolicyIds := []string{}
+ if v, ok := d.GetOk("scaledown_policy_ids"); ok {
+ scaleDownSet := v.(*schema.Set)
+ for _, id := range scaleDownSet.List() {
+ scaleDownPolicyIds = append(scaleDownPolicyIds,
id.(string))
+ }
+ }
+
+ p := cs.AutoScale.NewCreateAutoScaleVmGroupParams(lbruleid, maxmembers,
minmembers, scaleDownPolicyIds, scaleUpPolicyIds, vmprofileid)
+
+ if v, ok := d.GetOk("name"); ok {
+ p.SetName(v.(string))
+ }
+
+ if v, ok := d.GetOk("interval"); ok {
+ p.SetInterval(v.(int))
+ }
+
+ if v, ok := d.GetOk("display"); ok {
+ p.SetFordisplay(v.(bool))
+ }
+
+ log.Printf("[DEBUG] Creating autoscale VM group")
+ resp, err := cs.AutoScale.CreateAutoScaleVmGroup(p)
+ if err != nil {
+ return fmt.Errorf("Error creating autoscale VM group: %s", err)
+ }
+
+ d.SetId(resp.Id)
+ log.Printf("[DEBUG] Autoscale VM group created with ID: %s", resp.Id)
+
+ requestedState := d.Get("state").(string)
+ if requestedState == "disable" {
+ log.Printf("[DEBUG] Disabling autoscale VM group as requested:
%s", resp.Id)
+ disableParams :=
cs.AutoScale.NewDisableAutoScaleVmGroupParams(resp.Id)
+ _, err = cs.AutoScale.DisableAutoScaleVmGroup(disableParams)
+ if err != nil {
+ return fmt.Errorf("Error disabling autoscale VM group
after creation: %s", err)
+ }
+ }
+
+ if v, ok := d.GetOk("name"); ok {
+ d.Set("name", v.(string))
+ }
+ d.Set("lbrule_id", lbruleid)
+ d.Set("min_members", minmembers)
+ d.Set("max_members", maxmembers)
+ d.Set("vm_profile_id", vmprofileid)
+ if v, ok := d.GetOk("interval"); ok {
+ d.Set("interval", v.(int))
+ }
+ if v, ok := d.GetOk("display"); ok {
+ d.Set("display", v.(bool))
+ }
+
+ scaleUpSet := schema.NewSet(schema.HashString, []interface{}{})
+ for _, id := range scaleUpPolicyIds {
+ scaleUpSet.Add(id)
+ }
+ d.Set("scaleup_policy_ids", scaleUpSet)
+
+ scaleDownSet := schema.NewSet(schema.HashString, []interface{}{})
+ for _, id := range scaleDownPolicyIds {
+ scaleDownSet.Add(id)
+ }
+ d.Set("scaledown_policy_ids", scaleDownSet)
+
+ d.Set("state", requestedState)
+
+ return nil
+}
+
+func resourceCloudStackAutoScaleVMGroupRead(d *schema.ResourceData, meta
interface{}) error {
+ cs := meta.(*cloudstack.CloudStackClient)
+
+ p := cs.AutoScale.NewListAutoScaleVmGroupsParams()
+ p.SetId(d.Id())
+
+ resp, err := cs.AutoScale.ListAutoScaleVmGroups(p)
+ if err != nil {
+ return fmt.Errorf("Error retrieving autoscale VM group: %s",
err)
+ }
+
+ if resp.Count == 0 {
+ log.Printf("[DEBUG] Autoscale VM group %s no longer exists",
d.Id())
+ d.SetId("")
+ return nil
+ }
+
+ group := resp.AutoScaleVmGroups[0]
+ d.Set("name", group.Name)
+ d.Set("lbrule_id", group.Lbruleid)
+ d.Set("min_members", group.Minmembers)
+ d.Set("max_members", group.Maxmembers)
+ d.Set("interval", group.Interval)
+ d.Set("vm_profile_id", group.Vmprofileid)
+ d.Set("display", group.Fordisplay)
+
+ terraformState := "enable"
+ if strings.ToLower(group.State) == "disabled" {
+ terraformState = "disable"
+ }
+
+ currentConfigState := d.Get("state").(string)
+ log.Printf("[DEBUG] CloudStack API state: %s, mapped to Terraform
state: %s", group.State, terraformState)
+ log.Printf("[DEBUG] Current config state: %s, will set state to: %s",
currentConfigState, terraformState)
+
+ d.Set("state", terraformState)
+
+ scaleUpPolicyIds := schema.NewSet(schema.HashString, []interface{}{})
+ if group.Scaleuppolicies != nil {
+ for _, policy := range group.Scaleuppolicies {
+ // Extract the ID from the AutoScalePolicy object
+ if policy != nil {
+ scaleUpPolicyIds.Add(policy.Id)
+ }
+ }
+ }
+ d.Set("scaleup_policy_ids", scaleUpPolicyIds)
+
+ scaleDownPolicyIds := schema.NewSet(schema.HashString, []interface{}{})
+ if group.Scaledownpolicies != nil {
+ for _, policy := range group.Scaledownpolicies {
+ // Extract the ID from the AutoScalePolicy object
+ if policy != nil {
+ scaleDownPolicyIds.Add(policy.Id)
+ }
+ }
+ }
+ d.Set("scaledown_policy_ids", scaleDownPolicyIds)
+
+ return nil
+}
+
+func resourceCloudStackAutoScaleVMGroupUpdate(d *schema.ResourceData, meta
interface{}) error {
+ cs := meta.(*cloudstack.CloudStackClient)
+
+ oldState, newState := d.GetChange("state")
+ log.Printf("[DEBUG] State in terraform config: %s",
d.Get("state").(string))
+ log.Printf("[DEBUG] Old state: %s, New state: %s", oldState.(string),
newState.(string))
+
+ changes := []string{}
+ if d.HasChange("name") {
+ changes = append(changes, "name")
+ }
+ if d.HasChange("min_members") {
+ changes = append(changes, "min_members")
+ }
+ if d.HasChange("max_members") {
+ changes = append(changes, "max_members")
+ }
+ if d.HasChange("interval") {
+ changes = append(changes, "interval")
+ }
+ if d.HasChange("scaleup_policy_ids") {
+ changes = append(changes, "scaleup_policy_ids")
+ }
+ if d.HasChange("scaledown_policy_ids") {
+ changes = append(changes, "scaledown_policy_ids")
+ }
+ if d.HasChange("display") {
+ changes = append(changes, "display")
+ }
+ if d.HasChange("state") {
+ changes = append(changes, "state")
+ }
+ log.Printf("[DEBUG] Detected changes in autoscale VM group: %v",
changes)
+
+ if d.HasChange("name") || d.HasChange("min_members") ||
d.HasChange("max_members") ||
+ d.HasChange("interval") || d.HasChange("scaleup_policy_ids") ||
+ d.HasChange("scaledown_policy_ids") || d.HasChange("display")
|| d.HasChange("state") {
+
+ log.Printf("[DEBUG] Updating autoscale VM group: %s", d.Id())
+
+ // Check current state to determine operation order
+ currentState := "enable"
+ if oldState, newState := d.GetChange("state");
d.HasChange("state") {
+ currentState = oldState.(string)
+ log.Printf("[DEBUG] State change detected: %s -> %s",
currentState, newState.(string))
+ }
+
+ if d.HasChange("state") {
+ newState := d.Get("state").(string)
+ if newState == "disable" && currentState == "enable" {
+ log.Printf("[DEBUG] Disabling autoscale VM
group before other updates: %s", d.Id())
+ disableParams :=
cs.AutoScale.NewDisableAutoScaleVmGroupParams(d.Id())
+ _, err :=
cs.AutoScale.DisableAutoScaleVmGroup(disableParams)
+ if err != nil {
+ return fmt.Errorf("Error disabling
autoscale VM group: %s", err)
+ }
+ // Wait a moment for disable to take effect
+ time.Sleep(1 * time.Second)
+ }
+ }
+
+ if d.HasChange("name") || d.HasChange("min_members") ||
d.HasChange("max_members") ||
+ d.HasChange("interval") ||
d.HasChange("scaleup_policy_ids") ||
+ d.HasChange("scaledown_policy_ids") ||
d.HasChange("display") {
+
+ p :=
cs.AutoScale.NewUpdateAutoScaleVmGroupParams(d.Id())
+
+ if d.HasChange("name") {
+ if v, ok := d.GetOk("name"); ok {
+ p.SetName(v.(string))
+ }
+ }
+
+ if d.HasChange("min_members") {
+ minmembers := d.Get("min_members").(int)
+ p.SetMinmembers(minmembers)
+ }
+
+ if d.HasChange("max_members") {
+ maxmembers := d.Get("max_members").(int)
+ p.SetMaxmembers(maxmembers)
+ }
+
+ if d.HasChange("interval") {
+ if v, ok := d.GetOk("interval"); ok {
+ p.SetInterval(v.(int))
+ }
+ }
+
+ if d.HasChange("scaleup_policy_ids") {
+ scaleUpPolicyIds := []string{}
+ if v, ok := d.GetOk("scaleup_policy_ids"); ok {
+ scaleUpSet := v.(*schema.Set)
+ for _, id := range scaleUpSet.List() {
+ scaleUpPolicyIds =
append(scaleUpPolicyIds, id.(string))
+ }
+ }
+ p.SetScaleuppolicyids(scaleUpPolicyIds)
+ }
+
+ if d.HasChange("scaledown_policy_ids") {
+ scaleDownPolicyIds := []string{}
+ if v, ok := d.GetOk("scaledown_policy_ids"); ok
{
+ scaleDownSet := v.(*schema.Set)
+ for _, id := range scaleDownSet.List() {
+ scaleDownPolicyIds =
append(scaleDownPolicyIds, id.(string))
+ }
+ }
+ p.SetScaledownpolicyids(scaleDownPolicyIds)
+ }
+
+ if d.HasChange("display") {
+ if v, ok := d.GetOk("display"); ok {
+ p.SetFordisplay(v.(bool))
+ }
+ }
+
+ log.Printf("[DEBUG] Applying parameter updates to
autoscale VM group: %s", d.Id())
+ _, err := cs.AutoScale.UpdateAutoScaleVmGroup(p)
+ if err != nil {
+ return fmt.Errorf("Error updating autoscale VM
group parameters: %s", err)
+ }
+ }
+
+ if d.HasChange("state") {
+ newState := d.Get("state").(string)
+ if newState == "enable" {
+ log.Printf("[DEBUG] Enabling autoscale VM group
after updates: %s", d.Id())
+ enableParams :=
cs.AutoScale.NewEnableAutoScaleVmGroupParams(d.Id())
+ _, err :=
cs.AutoScale.EnableAutoScaleVmGroup(enableParams)
+ if err != nil {
+ return fmt.Errorf("Error enabling
autoscale VM group: %s", err)
+ }
+ }
+ }
+
+ log.Printf("[DEBUG] Autoscale VM group updated successfully:
%s", d.Id())
+ }
+
+ return resourceCloudStackAutoScaleVMGroupRead(d, meta)
+}
+
+func resourceCloudStackAutoScaleVMGroupDelete(d *schema.ResourceData, meta
interface{}) error {
+ cs := meta.(*cloudstack.CloudStackClient)
+
+ p := cs.AutoScale.NewDeleteAutoScaleVmGroupParams(d.Id())
+
+ cleanup := d.Get("cleanup").(bool)
+ p.SetCleanup(cleanup)
+ log.Printf("[DEBUG] Deleting autoscale VM group with cleanup=%t",
cleanup)
+ log.Printf("[DEBUG] This deletion was triggered by Terraform - check if
this should be an update instead")
+
+ log.Printf("[DEBUG] Disabling autoscale VM group before deletion: %s",
d.Id())
+ disableParams := cs.AutoScale.NewDisableAutoScaleVmGroupParams(d.Id())
+ _, err := cs.AutoScale.DisableAutoScaleVmGroup(disableParams)
+ if err != nil {
+ if !strings.Contains(err.Error(), "Invalid parameter id value")
&&
+ !strings.Contains(err.Error(), "entity does not exist")
&&
+ !strings.Contains(err.Error(), "already disabled") {
+ return fmt.Errorf("Error disabling autoscale VM group:
%s", err)
+ }
+ }
+
+ time.Sleep(2 * time.Second)
+ log.Printf("[DEBUG] Autoscale VM group disabled, proceeding with
deletion: %s", d.Id())
+
+ log.Printf("[DEBUG] Deleting autoscale VM group: %s", d.Id())
+ _, err = cs.AutoScale.DeleteAutoScaleVmGroup(p)
+ if err != nil {
+ // This is a very poor way to be told the ID does no longer
exist :(
+ if strings.Contains(err.Error(), fmt.Sprintf(
+ "Invalid parameter id value=%s due to incorrect long
value format, "+
+ "or entity does not exist", d.Id())) {
+ return nil
+ }
+
+ return fmt.Errorf("Error deleting autoscale VM group: %s", err)
+ }
+
+ return nil
+}
diff --git a/cloudstack/resource_cloudstack_autoscale_vm_profile.go
b/cloudstack/resource_cloudstack_autoscale_vm_profile.go
index 04fc5d5..0032b92 100644
--- a/cloudstack/resource_cloudstack_autoscale_vm_profile.go
+++ b/cloudstack/resource_cloudstack_autoscale_vm_profile.go
@@ -38,33 +38,95 @@ func resourceCloudStackAutoScaleVMProfile()
*schema.Resource {
Schema: map[string]*schema.Schema{
"service_offering": {
- Type: schema.TypeString,
- Required: true,
- ForceNew: true,
+ Type: schema.TypeString,
+ Required: true,
+ ForceNew: true,
+ Description: "the service offering of the auto
deployed virtual machine",
},
"template": {
- Type: schema.TypeString,
- Required: true,
+ Type: schema.TypeString,
+ Required: true,
+ Description: "the template of the auto deployed
virtual machine",
},
"zone": {
- Type: schema.TypeString,
- Required: true,
- ForceNew: true,
+ Type: schema.TypeString,
+ Required: true,
+ ForceNew: true,
+ Description: "availability zone for the auto
deployed virtual machine",
},
"destroy_vm_grace_period": {
- Type: schema.TypeString,
- Optional: true,
- Computed: true,
+ Type: schema.TypeString,
+ Optional: true,
+ Computed: true,
+ Description: "the time allowed for existing
connections to get closed before a vm is expunged",
},
"other_deploy_params": {
- Type: schema.TypeMap,
- Optional: true,
- Computed: true,
- ForceNew: true,
+ Type: schema.TypeMap,
+ Optional: true,
+ Computed: true,
+ ForceNew: true,
+ Description: "parameters other than
zoneId/serviceOfferringId/templateId of the auto deployed virtual machine",
+ },
+
+ "counter_param_list": {
+ Type: schema.TypeMap,
+ Optional: true,
+ Description: "counterparam list. Example:
counterparam[0].name=snmpcommunity&counterparam[0].value=public&counterparam[1].name=snmpport&counterparam[1].value=161",
+ },
+
+ "user_data": {
+ Type: schema.TypeString,
+ Optional: true,
+ Description: "an optional binary data that can
be sent to the virtual machine upon a successful deployment. This binary data
must be base64 encoded before adding it to the request.",
+ },
+
+ "user_data_id": {
+ Type: schema.TypeString,
+ Optional: true,
+ Description: "the ID of the Userdata",
+ },
+
+ "user_data_details": {
+ Type: schema.TypeMap,
+ Optional: true,
+ Description: "used to specify the parameters
values for the variables in userdata",
+ },
+
+ "autoscale_user_id": {
+ Type: schema.TypeString,
+ Optional: true,
+ Description: "the ID of the user used to launch
and destroy the VMs",
+ },
+
+ "display": {
+ Type: schema.TypeBool,
+ Optional: true,
+ Description: "an optional field, whether to the
display the profile to the end user or not",
+ },
+
+ "account_name": {
+ Type: schema.TypeString,
+ Optional: true,
+ ForceNew: true,
+ Description: "account that will own the
autoscale VM profile",
+ },
+
+ "project_id": {
+ Type: schema.TypeString,
+ Optional: true,
+ ForceNew: true,
+ Description: "an optional project for the
autoscale VM profile",
+ },
+
+ "domain_id": {
+ Type: schema.TypeString,
+ Optional: true,
+ ForceNew: true,
+ Description: "domain ID of the account owning a
autoscale VM profile",
},
"metadata": metadataSchema(),
@@ -75,19 +137,16 @@ func resourceCloudStackAutoScaleVMProfile()
*schema.Resource {
func resourceCloudStackAutoScaleVMProfileCreate(d *schema.ResourceData, meta
interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
- // Retrieve the service_offering ID
serviceofferingid, e := retrieveID(cs, "service_offering",
d.Get("service_offering").(string))
if e != nil {
return e.Error()
}
- // Retrieve the zone ID
zoneid, e := retrieveID(cs, "zone", d.Get("zone").(string))
if e != nil {
return e.Error()
}
- // Retrieve the template ID
templateid, e := retrieveTemplateID(cs, zoneid,
d.Get("template").(string))
if e != nil {
return e.Error()
@@ -111,7 +170,50 @@ func resourceCloudStackAutoScaleVMProfileCreate(d
*schema.ResourceData, meta int
p.SetOtherdeployparams(nv)
}
- // Create the new vm profile
+ if v, ok := d.GetOk("counter_param_list"); ok {
+ nv := make(map[string]string)
+ for k, v := range v.(map[string]interface{}) {
+ nv[k] = v.(string)
+ }
+ p.SetCounterparam(nv)
+ }
+
+ if v, ok := d.GetOk("user_data"); ok {
+ p.SetUserdata(v.(string))
+ }
+
+ if v, ok := d.GetOk("user_data_id"); ok {
+ p.SetUserdataid(v.(string))
+ }
+
+ if v, ok := d.GetOk("user_data_details"); ok {
+ nv := make(map[string]string)
+ for k, v := range v.(map[string]interface{}) {
+ nv[k] = v.(string)
+ }
+ p.SetUserdatadetails(nv)
+ }
+
+ if v, ok := d.GetOk("autoscale_user_id"); ok {
+ p.SetAutoscaleuserid(v.(string))
+ }
+
+ if v, ok := d.GetOk("display"); ok {
+ p.SetFordisplay(v.(bool))
+ }
+
+ if v, ok := d.GetOk("account_name"); ok {
+ p.SetAccount(v.(string))
+ }
+
+ if v, ok := d.GetOk("project_id"); ok {
+ p.SetProjectid(v.(string))
+ }
+
+ if v, ok := d.GetOk("domain_id"); ok {
+ p.SetDomainid(v.(string))
+ }
+
r, err := cs.AutoScale.CreateAutoScaleVmProfile(p)
if err != nil {
return fmt.Errorf("Error creating AutoScaleVmProfile %s: %s",
d.Id(), err)
@@ -119,12 +221,11 @@ func resourceCloudStackAutoScaleVMProfileCreate(d
*schema.ResourceData, meta int
d.SetId(r.Id)
- // Set metadata if necessary
if err = setMetadata(cs, d, "AutoScaleVmProfile"); err != nil {
return fmt.Errorf("Error setting metadata on the
AutoScaleVmProfile %s: %s", d.Id(), err)
}
- return nil
+ return resourceCloudStackAutoScaleVMProfileRead(d, meta)
}
func resourceCloudStackAutoScaleVMProfileRead(d *schema.ResourceData, meta
interface{}) error {
@@ -168,6 +269,38 @@ func resourceCloudStackAutoScaleVMProfileRead(d
*schema.ResourceData, meta inter
d.Set("other_deploy_params", p.Otherdeployparams)
}
+ if p.Userdata != "" {
+ d.Set("user_data", p.Userdata)
+ }
+
+ if p.Userdataid != "" {
+ d.Set("user_data_id", p.Userdataid)
+ }
+
+ if p.Userdatadetails != "" {
+ if _, ok := d.GetOk("user_data_details"); !ok {
+ d.Set("user_data_details", map[string]interface{}{})
+ }
+ }
+
+ if p.Autoscaleuserid != "" {
+ d.Set("autoscale_user_id", p.Autoscaleuserid)
+ }
+
+ d.Set("display", p.Fordisplay)
+
+ if p.Account != "" {
+ d.Set("account_name", p.Account)
+ }
+
+ if p.Projectid != "" {
+ d.Set("project_id", p.Projectid)
+ }
+
+ if p.Domainid != "" {
+ d.Set("domain_id", p.Domainid)
+ }
+
metadata, err := getMetadata(cs, d, "AutoScaleVmProfile")
if err != nil {
return err
@@ -177,40 +310,185 @@ func resourceCloudStackAutoScaleVMProfileRead(d
*schema.ResourceData, meta inter
return nil
}
+// waitForVMGroupsState waits for the specified VM groups to reach the desired
state
+func waitForVMGroupsState(cs *cloudstack.CloudStackClient, groupIDs []string,
desiredState string) error {
+ maxRetries := 30 // 30 * 2 seconds = 60 seconds max wait
+ for i := 0; i < maxRetries; i++ {
+ allInDesiredState := true
+
+ for _, groupID := range groupIDs {
+ group, _, err :=
cs.AutoScale.GetAutoScaleVmGroupByID(groupID)
+ if err != nil {
+ return fmt.Errorf("Error checking state of VM
group %s: %s", groupID, err)
+ }
+
+ groupInDesiredState := false
+ if desiredState == "disabled" {
+ groupInDesiredState = (group.State ==
"disabled")
+ } else if desiredState == "enabled" {
+ groupInDesiredState = (group.State == "enabled")
+ } else {
+ groupInDesiredState = (group.State ==
desiredState)
+ }
+
+ if !groupInDesiredState {
+ allInDesiredState = false
+ log.Printf("[DEBUG] VM group %s is in state
'%s', waiting for '%s'", groupID, group.State, desiredState)
+ break
+ }
+ }
+
+ if allInDesiredState {
+ log.Printf("[INFO] All VM groups have reached desired
state: %s", desiredState)
+ return nil
+ }
+
+ if i < maxRetries-1 {
+ log.Printf("[INFO] Waiting for VM groups to reach state
'%s' (attempt %d/%d)", desiredState, i+1, maxRetries)
+ time.Sleep(2 * time.Second)
+ }
+ }
+
+ return fmt.Errorf("Timeout waiting for VM groups to reach state '%s'
after %d seconds", desiredState, maxRetries*2)
+}
+
+func waitForVMGroupsToBeDisabled(cs *cloudstack.CloudStackClient, profileID
string) error {
+ log.Printf("[DEBUG] Waiting for VM groups using profile %s to be
disabled", profileID)
+ listParams := cs.AutoScale.NewListAutoScaleVmGroupsParams()
+ listParams.SetVmprofileid(profileID)
+
+ groups, err := cs.AutoScale.ListAutoScaleVmGroups(listParams)
+ if err != nil {
+ log.Printf("[ERROR] Failed to list VM groups for profile %s:
%s", profileID, err)
+ return fmt.Errorf("Error listing autoscale VM groups: %s", err)
+ }
+
+ log.Printf("[DEBUG] Found %d VM groups using profile %s",
len(groups.AutoScaleVmGroups), profileID)
+
+ var groupIDs []string
+ for _, group := range groups.AutoScaleVmGroups {
+ log.Printf("[DEBUG] VM group %s (%s) current state: %s",
group.Name, group.Id, group.State)
+ groupIDs = append(groupIDs, group.Id)
+ }
+
+ if len(groupIDs) == 0 {
+ log.Printf("[DEBUG] No VM groups found using profile %s",
profileID)
+ return nil
+ }
+
+ log.Printf("[INFO] Waiting for %d VM groups to be disabled for profile
update", len(groupIDs))
+ if err := waitForVMGroupsState(cs, groupIDs, "disabled"); err != nil {
+ return fmt.Errorf("Autoscale VM groups must be disabled before
updating profile: %s", err)
+ }
+
+ log.Printf("[DEBUG] All VM groups are now disabled for profile %s",
profileID)
+ return nil
+}
+
func resourceCloudStackAutoScaleVMProfileUpdate(d *schema.ResourceData, meta
interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
+ log.Printf("[DEBUG] Profile update requested for ID: %s", d.Id())
+ for _, key := range []string{"template", "destroy_vm_grace_period",
"counter_param_list", "user_data", "user_data_id", "user_data_details",
"autoscale_user_id", "display", "metadata"} {
+ if d.HasChange(key) {
+ old, new := d.GetChange(key)
+ log.Printf("[DEBUG] Field '%s' changed from %v to %v",
key, old, new)
+ }
+ }
- // Create a new parameter struct
- p := cs.AutoScale.NewUpdateAutoScaleVmProfileParams(d.Id())
+ // Check if we only have metadata changes (which don't require
CloudStack API update)
+ onlyMetadataChanges := d.HasChange("metadata") &&
+ !d.HasChange("template") &&
+ !d.HasChange("destroy_vm_grace_period") &&
+ !d.HasChange("counter_param_list") &&
+ !d.HasChange("user_data") &&
+ !d.HasChange("user_data_id") &&
+ !d.HasChange("user_data_details") &&
+ !d.HasChange("autoscale_user_id") &&
+ !d.HasChange("display")
+
+ if !onlyMetadataChanges {
+ if err := waitForVMGroupsToBeDisabled(cs, d.Id()); err != nil {
+ return fmt.Errorf("Autoscale VM groups must be disabled
before updating profile: %s", err)
+ }
- if d.HasChange("template") {
- zoneid, e := retrieveID(cs, "zone", d.Get("zone").(string))
- if e != nil {
- return e.Error()
+ p := cs.AutoScale.NewUpdateAutoScaleVmProfileParams(d.Id())
+
+ if d.HasChange("template") {
+ zoneid, e := retrieveID(cs, "zone",
d.Get("zone").(string))
+ if e != nil {
+ return e.Error()
+ }
+ templateid, e := retrieveTemplateID(cs, zoneid,
d.Get("template").(string))
+ if e != nil {
+ return e.Error()
+ }
+ p.SetTemplateid(templateid)
}
- templateid, e := retrieveTemplateID(cs, zoneid,
d.Get("template").(string))
- if e != nil {
- return e.Error()
+
+ if d.HasChange("destroy_vm_grace_period") {
+ if v, ok := d.GetOk("destroy_vm_grace_period"); ok {
+ duration, err := time.ParseDuration(v.(string))
+ if err != nil {
+ return err
+ }
+
p.SetExpungevmgraceperiod(int(duration.Seconds()))
+ }
}
- p.SetTemplateid(templateid)
- }
- if d.HasChange("destroy_vm_grace_period") {
- duration, err :=
time.ParseDuration(d.Get("destroy_vm_grace_period").(string))
- if err != nil {
- return err
+ if d.HasChange("counter_param_list") {
+ if v, ok := d.GetOk("counter_param_list"); ok {
+ nv := make(map[string]string)
+ for k, v := range v.(map[string]interface{}) {
+ nv[k] = v.(string)
+ }
+ p.SetCounterparam(nv)
+ }
}
- p.SetExpungevmgraceperiod(int(duration.Seconds()))
- }
- _, err := cs.AutoScale.UpdateAutoScaleVmProfile(p)
- if err != nil {
- return fmt.Errorf("Error updating AutoScaleVmProfile %s: %s",
d.Id(), err)
+ if d.HasChange("user_data") {
+ if v, ok := d.GetOk("user_data"); ok {
+ p.SetUserdata(v.(string))
+ }
+ }
+
+ if d.HasChange("user_data_id") {
+ if v, ok := d.GetOk("user_data_id"); ok {
+ p.SetUserdataid(v.(string))
+ }
+ }
+
+ if d.HasChange("user_data_details") {
+ if v, ok := d.GetOk("user_data_details"); ok {
+ nv := make(map[string]string)
+ for k, v := range v.(map[string]interface{}) {
+ nv[k] = v.(string)
+ }
+ p.SetUserdatadetails(nv)
+ }
+ }
+
+ if d.HasChange("autoscale_user_id") {
+ if v, ok := d.GetOk("autoscale_user_id"); ok {
+ p.SetAutoscaleuserid(v.(string))
+ }
+ }
+
+ if d.HasChange("display") {
+ if v, ok := d.GetOk("display"); ok {
+ p.SetFordisplay(v.(bool))
+ }
+ }
+
+ log.Printf("[DEBUG] Performing CloudStack API update for
profile %s", d.Id())
+ _, updateErr := cs.AutoScale.UpdateAutoScaleVmProfile(p)
+ if updateErr != nil {
+ return fmt.Errorf("Error updating AutoScaleVmProfile
%s: %s", d.Id(), updateErr)
+ }
}
if d.HasChange("metadata") {
- if err := updateMetadata(cs, d, "AutoScaleVmProfile"); err !=
nil {
- return fmt.Errorf("Error updating tags on
AutoScaleVmProfile %s: %s", d.Id(), err)
+ if metadataErr := updateMetadata(cs, d, "AutoScaleVmProfile");
metadataErr != nil {
+ return fmt.Errorf("Error updating tags on
AutoScaleVmProfile %s: %s", d.Id(), metadataErr)
}
}
@@ -220,10 +498,8 @@ func resourceCloudStackAutoScaleVMProfileUpdate(d
*schema.ResourceData, meta int
func resourceCloudStackAutoScaleVMProfileDelete(d *schema.ResourceData, meta
interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
- // Create a new parameter struct
p := cs.AutoScale.NewDeleteAutoScaleVmProfileParams(d.Id())
- // Delete the template
log.Printf("[INFO] Deleting AutoScaleVmProfile: %s", d.Id())
_, err := cs.AutoScale.DeleteAutoScaleVmProfile(p)
if err != nil {
diff --git a/cloudstack/resource_cloudstack_condition.go
b/cloudstack/resource_cloudstack_condition.go
new file mode 100644
index 0000000..cd74e5c
--- /dev/null
+++ b/cloudstack/resource_cloudstack_condition.go
@@ -0,0 +1,182 @@
+//
+// 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"
+ "log"
+
+ "github.com/apache/cloudstack-go/v2/cloudstack"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+)
+
+func resourceCloudStackCondition() *schema.Resource {
+ return &schema.Resource{
+ Create: resourceCloudStackConditionCreate,
+ Read: resourceCloudStackConditionRead,
+ Update: resourceCloudStackConditionUpdate,
+ Delete: resourceCloudStackConditionDelete,
+
+ Schema: map[string]*schema.Schema{
+ "counter_id": {
+ Type: schema.TypeString,
+ Required: true,
+ Description: "The ID of the counter to be used
in the condition.",
+ ForceNew: true,
+ },
+ "relational_operator": {
+ Type: schema.TypeString,
+ Required: true,
+ Description: "Relational Operator to be used
with threshold. Valid values are EQ, GT, LT, GE, LE.",
+ },
+ "threshold": {
+ Type: schema.TypeFloat,
+ Required: true,
+ Description: "Value for which the Counter will
be evaluated with the Operator selected.",
+ },
+ "account_name": {
+ Type: schema.TypeString,
+ Required: true,
+ Description: "the account of the condition.
Must be used with the domainId parameter.",
+ ForceNew: true,
+ },
+ "domain_id": {
+ Type: schema.TypeString,
+ Required: true,
+ Description: "the domain ID of the account.",
+ ForceNew: true,
+ },
+ "project_id": {
+ Type: schema.TypeString,
+ Optional: true,
+ Description: "optional project for the
condition",
+ ForceNew: true,
+ },
+ },
+ }
+}
+
+func resourceCloudStackConditionRead(d *schema.ResourceData, meta interface{})
error {
+ cs := meta.(*cloudstack.CloudStackClient)
+
+ p := cs.AutoScale.NewListConditionsParams()
+ p.SetId(d.Id())
+
+ resp, err := cs.AutoScale.ListConditions(p)
+ if err != nil {
+ return fmt.Errorf("Error retrieving condition: %s", err)
+ }
+
+ if resp.Count == 0 {
+ log.Printf("[DEBUG] Condition %s no longer exists", d.Id())
+ d.SetId("")
+ return nil
+ }
+
+ condition := resp.Conditions[0]
+ d.Set("counter_id", condition.Counterid)
+ d.Set("relational_operator", condition.Relationaloperator)
+ d.Set("threshold", condition.Threshold)
+ d.Set("account_name", condition.Account)
+ d.Set("domain_id", condition.Domainid)
+ if condition.Projectid != "" {
+ d.Set("project_id", condition.Projectid)
+ }
+
+ return nil
+}
+
+func resourceCloudStackConditionUpdate(d *schema.ResourceData, meta
interface{}) error {
+ cs := meta.(*cloudstack.CloudStackClient)
+
+ if d.HasChange("relational_operator") || d.HasChange("threshold") {
+ log.Printf("[DEBUG] Updating condition: %s", d.Id())
+
+ relationaloperator := d.Get("relational_operator").(string)
+ threshold := d.Get("threshold").(float64)
+
+ p := cs.AutoScale.NewUpdateConditionParams(d.Id(),
relationaloperator, int64(threshold))
+
+ _, err := cs.AutoScale.UpdateCondition(p)
+ if err != nil {
+ return fmt.Errorf("Error updating condition: %s", err)
+ }
+
+ log.Printf("[DEBUG] Condition updated successfully: %s", d.Id())
+ }
+
+ return resourceCloudStackConditionRead(d, meta)
+}
+
+func resourceCloudStackConditionDelete(d *schema.ResourceData, meta
interface{}) error {
+ cs := meta.(*cloudstack.CloudStackClient)
+
+ p := cs.AutoScale.NewDeleteConditionParams(d.Id())
+
+ log.Printf("[DEBUG] Deleting condition: %s", d.Id())
+ _, err := cs.AutoScale.DeleteCondition(p)
+ if err != nil {
+ return fmt.Errorf("Error deleting condition: %s", err)
+ }
+
+ return nil
+}
+func resourceCloudStackConditionCreate(d *schema.ResourceData, meta
interface{}) error {
+ cs := meta.(*cloudstack.CloudStackClient)
+ counterid := d.Get("counter_id")
+ relationaloperator := d.Get("relational_operator").(string)
+ threshold := d.Get("threshold").(float64)
+
+ account, accountOk := d.GetOk("account_name")
+ domainid, domainOk := d.GetOk("domain_id")
+
+ if !accountOk || !domainOk {
+ return fmt.Errorf("account_name and domain_id are required
fields")
+ }
+
+ p := cs.AutoScale.NewCreateConditionParams(counterid.(string),
relationaloperator, int64(threshold))
+ p.SetAccount(account.(string))
+ p.SetDomainid(domainid.(string))
+
+ if v, ok := d.GetOk("project_id"); ok {
+ p.SetProjectid(v.(string))
+ }
+
+ log.Printf("[DEBUG] Creating condition")
+ resp, err := cs.AutoScale.CreateCondition(p)
+ if err != nil {
+ return fmt.Errorf("Error creating condition: %s", err)
+ }
+
+ d.SetId(resp.Id)
+ log.Printf("[DEBUG] Condition created with ID: %s", resp.Id)
+
+ // Set the values directly instead of calling read to avoid JSON
unmarshaling issues
+ d.Set("counter_id", counterid.(string))
+ d.Set("relational_operator", relationaloperator)
+ d.Set("threshold", threshold)
+ d.Set("account_name", account.(string))
+ d.Set("domain_id", domainid.(string))
+ if v, ok := d.GetOk("project_id"); ok {
+ d.Set("project_id", v.(string))
+ }
+
+ return nil
+}
diff --git a/cloudstack/resource_cloudstack_counter.go
b/cloudstack/resource_cloudstack_counter.go
new file mode 100644
index 0000000..ddd7e9e
--- /dev/null
+++ b/cloudstack/resource_cloudstack_counter.go
@@ -0,0 +1,126 @@
+//
+// 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"
+ "log"
+
+ "github.com/apache/cloudstack-go/v2/cloudstack"
+ "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+)
+
+func resourceCloudStackCounter() *schema.Resource {
+ return &schema.Resource{
+ Create: resourceCloudStackCounterCreate,
+ Read: resourceCloudStackCounterRead,
+ Delete: resourceCloudStackCounterDelete,
+
+ Schema: map[string]*schema.Schema{
+ "name": {
+ Type: schema.TypeString,
+ Required: true,
+ Description: "Name of the counter",
+ ForceNew: true,
+ },
+
+ "source": {
+ Type: schema.TypeString,
+ Required: true,
+ Description: "Source of the counter",
+ ForceNew: true,
+ },
+ "value": {
+ Type: schema.TypeString,
+ Required: true,
+ Description: "Value of the counter e.g. oid in
case of snmp",
+ ForceNew: true,
+ },
+ "counter_provider": {
+ Type: schema.TypeString,
+ Required: true,
+ Description: "Provider of the counter",
+ ForceNew: true,
+ },
+ },
+ }
+}
+
+func resourceCloudStackCounterCreate(d *schema.ResourceData, meta interface{})
error {
+ cs := meta.(*cloudstack.CloudStackClient)
+
+ name := d.Get("name").(string)
+ source := d.Get("source").(string)
+ value := d.Get("value").(string)
+ provider := d.Get("counter_provider").(string)
+
+ p := cs.AutoScale.NewCreateCounterParams(name, provider, source, value)
+
+ log.Printf("[DEBUG] Creating counter: %s", name)
+ resp, err := cs.AutoScale.CreateCounter(p)
+ if err != nil {
+ return fmt.Errorf("Error creating counter: %s", err)
+ }
+
+ d.SetId(resp.Id)
+ log.Printf("[DEBUG] Counter created with ID: %s", resp.Id)
+
+ return resourceCloudStackCounterRead(d, meta)
+}
+
+func resourceCloudStackCounterRead(d *schema.ResourceData, meta interface{})
error {
+ cs := meta.(*cloudstack.CloudStackClient)
+
+ p := cs.AutoScale.NewListCountersParams()
+ p.SetId(d.Id())
+
+ resp, err := cs.AutoScale.ListCounters(p)
+ if err != nil {
+ return fmt.Errorf("Error retrieving counter: %s", err)
+ }
+
+ if resp.Count == 0 {
+ log.Printf("[DEBUG] Counter %s no longer exists", d.Id())
+ d.SetId("")
+ return nil
+ }
+
+ counter := resp.Counters[0]
+ d.Set("name", counter.Name)
+ d.Set("source", counter.Source)
+ d.Set("value", counter.Value)
+ d.Set("counter_provider", counter.Provider)
+
+ return nil
+}
+
+func resourceCloudStackCounterDelete(d *schema.ResourceData, meta interface{})
error {
+ cs := meta.(*cloudstack.CloudStackClient)
+
+ p := cs.AutoScale.NewDeleteCounterParams(d.Id())
+
+ log.Printf("[DEBUG] Deleting counter: %s", d.Id())
+ _, err := cs.AutoScale.DeleteCounter(p)
+ if err != nil {
+ return fmt.Errorf("Error deleting counter: %s", err)
+ }
+
+ return nil
+}
diff --git a/cloudstack/resource_cloudstack_loadbalancer_rule.go
b/cloudstack/resource_cloudstack_loadbalancer_rule.go
index 5c5de86..c31c861 100644
--- a/cloudstack/resource_cloudstack_loadbalancer_rule.go
+++ b/cloudstack/resource_cloudstack_loadbalancer_rule.go
@@ -25,6 +25,7 @@ import (
"regexp"
"strconv"
"strings"
+ "time"
"github.com/apache/cloudstack-go/v2/cloudstack"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
@@ -258,11 +259,73 @@ func resourceCloudStackLoadBalancerRuleRead(d
*schema.ResourceData, meta interfa
for _, i := range l.LoadBalancerRuleInstances {
mbs = append(mbs, i.Id)
}
+
+ asgCheckParams := cs.AutoScale.NewListAutoScaleVmGroupsParams()
+ asgCheckParams.SetLbruleid(d.Id())
+
+ asgGroups, err := cs.AutoScale.ListAutoScaleVmGroups(asgCheckParams)
+ if err != nil {
+ log.Printf("[WARN] Failed to check for autoscale VM groups
during read: %s", err)
+ }
+
+ if len(asgGroups.AutoScaleVmGroups) > 0 {
+ log.Printf("[DEBUG] Load balancer rule %s is managed by %d
autoscale VM group(s), current members: %v",
+ d.Id(), len(asgGroups.AutoScaleVmGroups), mbs)
+
+ if currentMemberIds, ok := d.GetOk("member_ids"); ok {
+ currentSet := currentMemberIds.(*schema.Set)
+ if currentSet.Len() == 0 && len(mbs) > 0 {
+ d.Set("member_ids", []string{})
+ return nil
+ }
+ }
+ }
+
d.Set("member_ids", mbs)
return nil
}
+func waitForASGsToBeDisabled(cs *cloudstack.CloudStackClient, lbRuleID string)
error {
+ log.Printf("[DEBUG] Waiting for autoscale VM groups using load balancer
rule %s to be disabled", lbRuleID)
+
+ maxRetries := 60 // 60 * 2 seconds = 120 seconds max wait (longer for
Terraform-driven changes)
+ for i := 0; i < maxRetries; i++ {
+ listParams := cs.AutoScale.NewListAutoScaleVmGroupsParams()
+ listParams.SetLbruleid(lbRuleID)
+
+ groups, err := cs.AutoScale.ListAutoScaleVmGroups(listParams)
+ if err != nil {
+ log.Printf("[WARN] Failed to list autoscale VM groups:
%s", err)
+ time.Sleep(2 * time.Second)
+ continue
+ }
+
+ allDisabled := true
+ var enabledGroups []string
+
+ for _, group := range groups.AutoScaleVmGroups {
+ if group.State != "disabled" && group.State !=
"disable" {
+ allDisabled = false
+ enabledGroups = append(enabledGroups,
fmt.Sprintf("%s(%s:%s)", group.Name, group.Id, group.State))
+ }
+ }
+
+ if allDisabled {
+ log.Printf("[INFO] All autoscale VM groups using load
balancer rule %s are now disabled", lbRuleID)
+ return nil
+ }
+
+ if i < maxRetries-1 {
+ log.Printf("[DEBUG] Waiting for autoscale VM groups to
be disabled (attempt %d/%d). Groups still enabled: %v",
+ i+1, maxRetries, enabledGroups)
+ time.Sleep(2 * time.Second)
+ }
+ }
+
+ return fmt.Errorf("Timeout waiting for autoscale VM groups to be
disabled after %d seconds", maxRetries*2)
+}
+
func resourceCloudStackLoadBalancerRuleUpdate(d *schema.ResourceData, meta
interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
@@ -324,35 +387,126 @@ func resourceCloudStackLoadBalancerRuleUpdate(d
*schema.ResourceData, meta inter
}
if d.HasChange("member_ids") {
- o, n := d.GetChange("member_ids")
- ombs, nmbs := o.(*schema.Set), n.(*schema.Set)
+ log.Printf("[DEBUG] Load balancer rule %s member_ids change
detected", d.Id())
- setToStringList := func(s *schema.Set) []string {
- l := make([]string, s.Len())
- for i, v := range s.List() {
- l[i] = v.(string)
- }
- return l
+ asgCheckParams := cs.AutoScale.NewListAutoScaleVmGroupsParams()
+ asgCheckParams.SetLbruleid(d.Id())
+
+ asgGroups, err :=
cs.AutoScale.ListAutoScaleVmGroups(asgCheckParams)
+ if err != nil {
+ log.Printf("[WARN] Failed to check for autoscale VM
groups: %s", err)
}
- membersToAdd := setToStringList(nmbs.Difference(ombs))
- membersToRemove := setToStringList(ombs.Difference(nmbs))
+ if len(asgGroups.AutoScaleVmGroups) > 0 {
+ log.Printf("[INFO] Load balancer rule %s is managed by
%d autoscale VM group(s), handling member updates carefully",
+ d.Id(), len(asgGroups.AutoScaleVmGroups))
- log.Printf("[DEBUG] Members to add: %v, remove: %v",
membersToAdd, membersToRemove)
+ o, n := d.GetChange("member_ids")
+ ombs, nmbs := o.(*schema.Set), n.(*schema.Set)
- if len(membersToAdd) > 0 {
- p :=
cs.LoadBalancer.NewAssignToLoadBalancerRuleParams(d.Id())
- p.SetVirtualmachineids(membersToAdd)
- if _, err :=
cs.LoadBalancer.AssignToLoadBalancerRule(p); err != nil {
- return err
+ setToStringList := func(s *schema.Set) []string {
+ l := make([]string, s.Len())
+ for i, v := range s.List() {
+ l[i] = v.(string)
+ }
+ return l
+ }
+
+ oldMembers := setToStringList(ombs)
+ newMembers := setToStringList(nmbs)
+
+ log.Printf("[DEBUG] Terraform state - old members: %v,
new members: %v", oldMembers, newMembers)
+
+ p :=
cs.LoadBalancer.NewListLoadBalancerRuleInstancesParams(d.Id())
+ currentInstances, err :=
cs.LoadBalancer.ListLoadBalancerRuleInstances(p)
+ if err != nil {
+ return fmt.Errorf("Error listing current load
balancer members: %s", err)
+ }
+
+ var currentMembers []string
+ for _, i := range
currentInstances.LoadBalancerRuleInstances {
+ currentMembers = append(currentMembers, i.Id)
+ }
+
+ log.Printf("[DEBUG] CloudStack actual members: %v",
currentMembers)
+
+ // If Terraform state is empty but CloudStack has
members, it means autoscale is managing them
+ if len(oldMembers) == 0 && len(currentMembers) > 0 {
+ log.Printf("[INFO] Detected autoscale-managed
members in load balancer. Skipping member updates to avoid conflicts.")
+ log.Printf("[INFO] Autoscale VM groups will
manage the member lifecycle automatically.")
+
+ d.Set("member_ids", currentMembers)
+ return
resourceCloudStackLoadBalancerRuleRead(d, meta)
+ }
+
+ if len(newMembers) > 0 {
+ log.Printf("[WARN] Explicit member_ids
specified for autoscale-managed load balancer. This may conflict with autoscale
operations.")
+
+ if err := waitForASGsToBeDisabled(cs, d.Id());
err != nil {
+ return fmt.Errorf("Autoscale VM groups
must be disabled before modifying load balancer members: %s", err)
+ }
+
+ membersToAdd :=
setToStringList(nmbs.Difference(ombs))
+ membersToRemove :=
setToStringList(ombs.Difference(nmbs))
+
+ log.Printf("[DEBUG] Explicit member changes -
to add: %v, to remove: %v", membersToAdd, membersToRemove)
+
+ if len(membersToRemove) > 0 {
+ log.Printf("[DEBUG] Removing %d
explicit members from load balancer rule %s", len(membersToRemove), d.Id())
+ removeParams :=
cs.LoadBalancer.NewRemoveFromLoadBalancerRuleParams(d.Id())
+
removeParams.SetVirtualmachineids(membersToRemove)
+ if _, err :=
cs.LoadBalancer.RemoveFromLoadBalancerRule(removeParams); err != nil {
+ return fmt.Errorf("Error
removing explicit members from load balancer rule %s: %s. Members: %v", d.Id(),
err, membersToRemove)
+ }
+ }
+
+ if len(membersToAdd) > 0 {
+ log.Printf("[DEBUG] Adding %d explicit
members to load balancer rule %s", len(membersToAdd), d.Id())
+ addParams :=
cs.LoadBalancer.NewAssignToLoadBalancerRuleParams(d.Id())
+
addParams.SetVirtualmachineids(membersToAdd)
+ if _, err :=
cs.LoadBalancer.AssignToLoadBalancerRule(addParams); err != nil {
+ return fmt.Errorf("Error adding
explicit members to load balancer rule %s: %s. Members: %v", d.Id(), err,
membersToAdd)
+ }
+ }
+ }
+ } else {
+ // No autoscale groups, proceed with normal member
management
+ log.Printf("[DEBUG] No autoscale groups found,
proceeding with normal member management")
+
+ o, n := d.GetChange("member_ids")
+ ombs, nmbs := o.(*schema.Set), n.(*schema.Set)
+
+ setToStringList := func(s *schema.Set) []string {
+ l := make([]string, s.Len())
+ for i, v := range s.List() {
+ l[i] = v.(string)
+ }
+ return l
+ }
+
+ membersToAdd := setToStringList(nmbs.Difference(ombs))
+ membersToRemove :=
setToStringList(ombs.Difference(nmbs))
+
+ log.Printf("[DEBUG] Members to add: %v, remove: %v",
membersToAdd, membersToRemove)
+
+ if len(membersToRemove) > 0 {
+ log.Printf("[DEBUG] Removing %d members from
load balancer rule %s", len(membersToRemove), d.Id())
+ p :=
cs.LoadBalancer.NewRemoveFromLoadBalancerRuleParams(d.Id())
+ p.SetVirtualmachineids(membersToRemove)
+ if _, err :=
cs.LoadBalancer.RemoveFromLoadBalancerRule(p); err != nil {
+ return fmt.Errorf("Error removing
members from load balancer rule %s: %s. Members to remove: %v", d.Id(), err,
membersToRemove)
+ }
+ log.Printf("[DEBUG] Successfully removed
members from load balancer rule")
}
- }
- if len(membersToRemove) > 0 {
- p :=
cs.LoadBalancer.NewRemoveFromLoadBalancerRuleParams(d.Id())
- p.SetVirtualmachineids(membersToRemove)
- if _, err :=
cs.LoadBalancer.RemoveFromLoadBalancerRule(p); err != nil {
- return err
+ if len(membersToAdd) > 0 {
+ log.Printf("[DEBUG] Adding %d members to load
balancer rule %s", len(membersToAdd), d.Id())
+ p :=
cs.LoadBalancer.NewAssignToLoadBalancerRuleParams(d.Id())
+ p.SetVirtualmachineids(membersToAdd)
+ if _, err :=
cs.LoadBalancer.AssignToLoadBalancerRule(p); err != nil {
+ return fmt.Errorf("Error adding members
to load balancer rule %s: %s. Members to add: %v", d.Id(), err, membersToAdd)
+ }
+ log.Printf("[DEBUG] Successfully added members
to load balancer rule")
}
}
}
diff --git a/go.mod b/go.mod
index de1b205..339e856 100644
--- a/go.mod
+++ b/go.mod
@@ -18,7 +18,7 @@
module github.com/terraform-providers/terraform-provider-cloudstack
require (
- github.com/apache/cloudstack-go/v2 v2.17.1
+ github.com/apache/cloudstack-go/v2 v2.17.2
github.com/go-ini/ini v1.67.0
github.com/hashicorp/go-multierror v1.1.1
github.com/hashicorp/terraform-plugin-framework v1.12.0
@@ -35,7 +35,6 @@ require (
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/cloudflare/circl v1.6.1 // indirect
github.com/fatih/color v1.16.0 // indirect
- github.com/golang/mock v1.6.0 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/hashicorp/errwrap v1.0.0 // indirect
@@ -85,5 +84,3 @@ require (
)
go 1.23.0
-
-toolchain go1.22.4
diff --git a/go.sum b/go.sum
index d71638e..4b49b66 100644
--- a/go.sum
+++ b/go.sum
@@ -6,8 +6,8 @@ github.com/ProtonMail/go-crypto v1.1.0-alpha.0
h1:nHGfwXmFvJrSR9xu8qL7BkO4DqTHXE
github.com/ProtonMail/go-crypto v1.1.0-alpha.0/go.mod
h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
github.com/agext/levenshtein v1.2.2
h1:0S/Yg6LYmFJ5stwQeRp6EeOcCbj7xiqQSdNelsXvaqE=
github.com/agext/levenshtein v1.2.2/go.mod
h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
-github.com/apache/cloudstack-go/v2 v2.17.1
h1:XD0bGDOv+MCavXJfc/qxILgJh+cHJbudpqQ1FzA2sDI=
-github.com/apache/cloudstack-go/v2 v2.17.1/go.mod
h1:p/YBUwIEkQN6CQxFhw8Ff0wzf1MY0qRRRuGYNbcb1F8=
+github.com/apache/cloudstack-go/v2 v2.17.2
h1:fMqLUTpHZeJoSVQiD2hDBSoqOvGJ9QMoOaWABW8lc/0=
+github.com/apache/cloudstack-go/v2 v2.17.2/go.mod
h1:p/YBUwIEkQN6CQxFhw8Ff0wzf1MY0qRRRuGYNbcb1F8=
github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod
h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec=
github.com/apparentlymart/go-textseg/v15 v15.0.0
h1:uYvfpb3DyLSCGWnctWKGj857c6ew1u1fNQOlOtuGxQY=
github.com/apparentlymart/go-textseg/v15 v15.0.0/go.mod
h1:K8XmNZdhEBkdlyDdvbmmsvpAG721bKi0joRfFdHIWJ4=
@@ -168,7 +168,6 @@ golang.org/x/crypto
v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod
h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
golang.org/x/crypto v0.36.0/go.mod
h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
-golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod
h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.18.0 h1:5+9lSbEzPSdWkH32vYPBwEpX8KwDbM52Ud9xBUvNlb0=
golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
@@ -231,4 +230,4 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod
h1:JHkPIbrfpd72SG/EV
gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME=
gopkg.in/warnings.v0 v0.1.2/go.mod
h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
-gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
\ No newline at end of file
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/website/docs/d/autoscale_policy.html.markdown
b/website/docs/d/autoscale_policy.html.markdown
new file mode 100644
index 0000000..be2e72b
--- /dev/null
+++ b/website/docs/d/autoscale_policy.html.markdown
@@ -0,0 +1,69 @@
+---
+layout: "cloudstack"
+page_title: "CloudStack: cloudstack_autoscale_policy"
+sidebar_current: "docs-cloudstack-data-source-autoscale-policy"
+description: |-
+ Gets information about a CloudStack autoscale policy.
+---
+
+# cloudstack_autoscale_policy
+
+Use this data source to get information about a CloudStack autoscale policy.
+
+## Example Usage
+
+```hcl
+# Get policy by ID
+data "cloudstack_autoscale_policy" "existing_policy" {
+ id = "6a8dc025-d7c9-4676-8a7d-e2d9b55e7e60"
+}
+
+# Get policy by name
+data "cloudstack_autoscale_policy" "scale_up_policy" {
+ filter {
+ name = "name"
+ value = "scale-up-policy"
+ }
+}
+
+# Use in an autoscale VM group
+resource "cloudstack_autoscale_vm_group" "vm_group" {
+ name = "web-autoscale"
+ lbrule_id = cloudstack_loadbalancer_rule.lb.id
+ min_members = 1
+ max_members = 5
+ vm_profile_id = cloudstack_autoscale_vm_profile.profile.id
+
+ scaleup_policy_ids = [
+ data.cloudstack_autoscale_policy.existing_policy.id
+ ]
+
+ scaledown_policy_ids = [
+ cloudstack_autoscale_policy.scale_down.id
+ ]
+}
+```
+
+## Argument Reference
+
+The following arguments are supported:
+
+* `id` - (Optional) The ID of the autoscale policy.
+
+* `filter` - (Optional) One or more name/value pairs to filter off of. You can
apply filters on any exported attributes.
+
+## Attributes Reference
+
+The following attributes are exported:
+
+* `id` - The autoscale policy ID.
+
+* `name` - The name of the policy.
+
+* `action` - The action (SCALEUP or SCALEDOWN).
+
+* `duration` - The duration in seconds.
+
+* `quiet_time` - The quiet time in seconds.
+
+* `condition_ids` - The list of condition IDs used by this policy.
diff --git a/website/docs/d/autoscale_vm_group.html.markdown
b/website/docs/d/autoscale_vm_group.html.markdown
new file mode 100644
index 0000000..e8b8ba8
--- /dev/null
+++ b/website/docs/d/autoscale_vm_group.html.markdown
@@ -0,0 +1,71 @@
+---
+layout: "cloudstack"
+page_title: "CloudStack: cloudstack_autoscale_vm_group"
+sidebar_current: "docs-cloudstack-data-source-autoscale-vm-group"
+description: |-
+ Gets information about a CloudStack autoscale VM group.
+---
+
+# cloudstack_autoscale_vm_group
+
+Use this data source to get information about a CloudStack autoscale VM group.
+
+## Example Usage
+
+```hcl
+# Get autoscale VM group by ID
+data "cloudstack_autoscale_vm_group" "existing_group" {
+ id = "156a819a-dec1-4166-aab3-657c271fa4a3"
+}
+
+# Get autoscale VM group by name
+data "cloudstack_autoscale_vm_group" "web_group" {
+ filter {
+ name = "name"
+ value = "web-server-autoscale"
+ }
+}
+
+# Output information about the group
+output "autoscale_group_state" {
+ value = data.cloudstack_autoscale_vm_group.existing_group.state
+}
+
+output "current_members" {
+ value = "Min:
${data.cloudstack_autoscale_vm_group.existing_group.min_members}, Max:
${data.cloudstack_autoscale_vm_group.existing_group.max_members}"
+}
+```
+
+## Argument Reference
+
+The following arguments are supported:
+
+* `id` - (Optional) The ID of the autoscale VM group.
+
+* `filter` - (Optional) One or more name/value pairs to filter off of. You can
apply filters on any exported attributes.
+
+## Attributes Reference
+
+The following attributes are exported:
+
+* `id` - The autoscale VM group ID.
+
+* `name` - The name of the autoscale VM group.
+
+* `lbrule_id` - The load balancer rule ID.
+
+* `min_members` - The minimum number of members.
+
+* `max_members` - The maximum number of members.
+
+* `vm_profile_id` - The VM profile ID.
+
+* `interval` - The monitoring interval in seconds.
+
+* `display` - Whether the group is displayed to end users.
+
+* `state` - The current state of the group (enable or disable).
+
+* `scaleup_policy_ids` - The list of scale-up policy IDs.
+
+* `scaledown_policy_ids` - The list of scale-down policy IDs.
diff --git a/website/docs/d/autoscale_vm_profile.html.markdown
b/website/docs/d/autoscale_vm_profile.html.markdown
new file mode 100644
index 0000000..a66375a
--- /dev/null
+++ b/website/docs/d/autoscale_vm_profile.html.markdown
@@ -0,0 +1,76 @@
+---
+layout: "cloudstack"
+page_title: "CloudStack: cloudstack_autoscale_vm_profile"
+sidebar_current: "docs-cloudstack-data-source-autoscale-vm-profile"
+description: |-
+ Gets information about a CloudStack autoscale VM profile.
+---
+
+# cloudstack_autoscale_vm_profile
+
+Use this data source to get information about a CloudStack autoscale VM
profile.
+
+## Example Usage
+
+```hcl
+# Get VM profile by ID
+data "cloudstack_autoscale_vm_profile" "existing_profile" {
+ id = "a596f7a2-95b8-4f0e-9f15-88f4091f18fe"
+}
+
+# Get VM profile by filter
+data "cloudstack_autoscale_vm_profile" "web_profile" {
+ filter {
+ name = "service_offering"
+ value = "Small Instance"
+ }
+}
+
+# Use in an autoscale VM group
+resource "cloudstack_autoscale_vm_group" "vm_group" {
+ name = "web-autoscale"
+ lbrule_id = cloudstack_loadbalancer_rule.lb.id
+ min_members = 1
+ max_members = 5
+ vm_profile_id = data.cloudstack_autoscale_vm_profile.existing_profile.id
+
+ scaleup_policy_ids = [cloudstack_autoscale_policy.scale_up.id]
+ scaledown_policy_ids = [cloudstack_autoscale_policy.scale_down.id]
+}
+```
+
+## Argument Reference
+
+The following arguments are supported:
+
+* `id` - (Optional) The ID of the autoscale VM profile.
+
+* `filter` - (Optional) One or more name/value pairs to filter off of. You can
apply filters on any exported attributes.
+
+## Attributes Reference
+
+The following attributes are exported:
+
+* `id` - The autoscale VM profile ID.
+
+* `service_offering` - The service offering name or ID.
+
+* `template` - The template name or ID.
+
+* `zone` - The zone name or ID.
+
+* `destroy_vm_grace_period` - The grace period for VM destruction.
+
+* `counter_param_list` - Counter parameters for monitoring.
+
+* `user_data` - User data for VM initialization.
+
+* `user_data_details` - Additional user data details.
+
+* `account_name` - The account name that owns the profile.
+
+* `domain_id` - The domain ID where the profile exists.
+
+* `display` - Whether the profile is displayed to end users.
+
+* `other_deploy_params` - Additional deployment parameters.
diff --git a/website/docs/d/condition.html.markdown
b/website/docs/d/condition.html.markdown
new file mode 100644
index 0000000..adcfe71
--- /dev/null
+++ b/website/docs/d/condition.html.markdown
@@ -0,0 +1,61 @@
+---
+layout: "cloudstack"
+page_title: "CloudStack: cloudstack_condition"
+sidebar_current: "docs-cloudstack-data-source-condition"
+description: |-
+ Gets information about a CloudStack autoscale condition.
+---
+
+# cloudstack_condition
+
+Use this data source to get information about a CloudStack autoscale condition.
+
+## Example Usage
+
+```hcl
+# Get condition by ID
+data "cloudstack_condition" "existing_condition" {
+ id = "c2f0591b-ce9b-499a-81f2-8fc6318b0c72"
+}
+
+# Get condition by filter
+data "cloudstack_condition" "cpu_condition" {
+ filter {
+ name = "threshold"
+ value = "80"
+ }
+}
+
+# Use in a policy
+resource "cloudstack_autoscale_policy" "scale_policy" {
+ name = "scale-up-policy"
+ action = "scaleup"
+ duration = 300
+ quiet_time = 300
+ condition_ids = [data.cloudstack_condition.existing_condition.id]
+}
+```
+
+## Argument Reference
+
+The following arguments are supported:
+
+* `id` - (Optional) The ID of the condition.
+
+* `filter` - (Optional) One or more name/value pairs to filter off of. You can
apply filters on any exported attributes.
+
+## Attributes Reference
+
+The following attributes are exported:
+
+* `id` - The condition ID.
+
+* `counter_id` - The counter ID being monitored.
+
+* `relational_operator` - The relational operator for the condition.
+
+* `threshold` - The threshold value.
+
+* `account_name` - The account name that owns the condition.
+
+* `domain_id` - The domain ID where the condition exists.
diff --git a/website/docs/d/counter.html.markdown
b/website/docs/d/counter.html.markdown
new file mode 100644
index 0000000..e207ee8
--- /dev/null
+++ b/website/docs/d/counter.html.markdown
@@ -0,0 +1,57 @@
+---
+layout: "cloudstack"
+page_title: "CloudStack: cloudstack_counter"
+sidebar_current: "docs-cloudstack-data-source-counter"
+description: |-
+ Gets information about a CloudStack counter.
+---
+
+# cloudstack_counter
+
+Use this data source to get information about a CloudStack counter for use in
autoscale conditions.
+
+## Example Usage
+
+```hcl
+# Get counter by ID
+data "cloudstack_counter" "cpu_counter" {
+ id = "959e11c0-8416-11f0-9a72-1e001b000238"
+}
+
+# Get counter by name
+data "cloudstack_counter" "memory_counter" {
+ filter {
+ name = "name"
+ value = "VM CPU - average percentage"
+ }
+}
+
+# Use in a condition
+resource "cloudstack_condition" "scale_up" {
+ counter_id = data.cloudstack_counter.cpu_counter.id
+ relational_operator = "GT"
+ threshold = 80.0
+ account_name = "admin"
+ domain_id = "1"
+}
+```
+
+## Argument Reference
+
+The following arguments are supported:
+
+* `id` - (Optional) The ID of the counter.
+
+* `filter` - (Optional) One or more name/value pairs to filter off of. You can
apply filters on any exported attributes.
+
+## Attributes Reference
+
+The following attributes are exported:
+
+* `id` - The counter ID.
+
+* `name` - The name of the counter.
+
+* `source` - The source of the counter.
+
+* `value` - The metric value monitored by the counter.
diff --git a/website/docs/r/autoscale_policy.html.markdown
b/website/docs/r/autoscale_policy.html.markdown
new file mode 100644
index 0000000..98da742
--- /dev/null
+++ b/website/docs/r/autoscale_policy.html.markdown
@@ -0,0 +1,63 @@
+---
+layout: "cloudstack"
+page_title: "CloudStack: cloudstack_autoscale_policy"
+sidebar_current: "docs-cloudstack-autoscale-policy"
+description: |-
+ Creates an autoscale policy.
+---
+
+# cloudstack_autoscale_policy
+
+Creates an autoscale policy that defines when and how to scale virtual
machines based on conditions.
+
+## Example Usage
+
+```hcl
+resource "cloudstack_condition" "scale_up_condition" {
+ counter_id = data.cloudstack_counter.cpu_counter.id
+ relational_operator = "GT"
+ threshold = 80.0
+ account_name = "admin"
+ domain_id = "67bc8dbe-8416-11f0-9a72-1e001b000238"
+}
+
+resource "cloudstack_autoscale_policy" "scale_up_policy" {
+ name = "scale-up-policy"
+ action = "scaleup" # Case insensitive: scaleup/SCALEUP
+ duration = 300 # 5 minutes
+ quiet_time = 300 # 5 minutes
+ condition_ids = [cloudstack_condition.scale_up_condition.id]
+}
+
+resource "cloudstack_autoscale_policy" "scale_down_policy" {
+ name = "scale-down-policy"
+ action = "scaledown" # Case insensitive: scaledown/SCALEDOWN
+ duration = 300
+ quiet_time = 600 # 10 minutes quiet time
+ condition_ids = [cloudstack_condition.scale_down_condition.id]
+}
+```
+
+## Argument Reference
+
+The following arguments are supported:
+
+* `name` - (Optional) The name of the autoscale policy.
+
+* `action` - (Required) The action to be executed when conditions are met.
Valid values are:
+ * `"scaleup"` or `"SCALEUP"` - Scale up (add instances)
+ * `"scaledown"` or `"SCALEDOWN"` - Scale down (remove instances)
+
+ **Note**: The action field is case-insensitive.
+
+* `duration` - (Required) The duration in seconds for which the conditions
must be true before the action is taken.
+
+* `quiet_time` - (Optional) The cool down period in seconds during which the
policy should not be evaluated after the action has been taken.
+
+* `condition_ids` - (Required) A list of condition IDs that must all evaluate
to true for the policy to trigger.
+
+## Attributes Reference
+
+The following attributes are exported:
+
+* `id` - The autoscale policy ID.
diff --git a/website/docs/r/autoscale_vm_group.html.markdown
b/website/docs/r/autoscale_vm_group.html.markdown
new file mode 100644
index 0000000..fa31dca
--- /dev/null
+++ b/website/docs/r/autoscale_vm_group.html.markdown
@@ -0,0 +1,111 @@
+---
+layout: "cloudstack"
+page_title: "CloudStack: cloudstack_autoscale_vm_group"
+sidebar_current: "docs-cloudstack-autoscale-vm-group"
+description: |-
+ Creates an autoscale VM group.
+---
+
+# cloudstack_autoscale_vm_group
+
+Creates an autoscale VM group that automatically scales virtual machines based
on policies and load balancer rules.
+
+## Example Usage
+
+```hcl
+# Basic autoscale VM group
+resource "cloudstack_autoscale_vm_group" "vm_group" {
+ name = "web-server-autoscale"
+ lbrule_id = cloudstack_loadbalancer_rule.lb.id
+ min_members = 1
+ max_members = 5
+ vm_profile_id = cloudstack_autoscale_vm_profile.profile.id
+ state = "enable" # or "disable"
+ cleanup = true # or false
+
+ scaleup_policy_ids = [
+ cloudstack_autoscale_policy.scale_up_policy.id
+ ]
+
+ scaledown_policy_ids = [
+ cloudstack_autoscale_policy.scale_down_policy.id
+ ]
+}
+
+# Autoscale VM group with optional parameters
+resource "cloudstack_autoscale_vm_group" "advanced_vm_group" {
+ name = "advanced-autoscale-group"
+ lbrule_id = cloudstack_loadbalancer_rule.lb.id
+ min_members = 2
+ max_members = 10
+ vm_profile_id = cloudstack_autoscale_vm_profile.profile.id
+ interval = 30 # Monitor every 30 seconds
+ display = true
+ state = "enable"
+ cleanup = false # Keep VMs when deleting group
+
+ scaleup_policy_ids = [
+ cloudstack_autoscale_policy.cpu_scale_up.id,
+ cloudstack_autoscale_policy.memory_scale_up.id
+ ]
+
+ scaledown_policy_ids = [
+ cloudstack_autoscale_policy.scale_down.id
+ ]
+}
+```
+
+## Argument Reference
+
+The following arguments are supported:
+
+* `lbrule_id` - (Required) The ID of the load balancer rule. Changing this
forces a new resource to be created.
+
+* `min_members` - (Required) The minimum number of members in the VM group.
The number of instances will be equal to or more than this number.
+
+* `max_members` - (Required) The maximum number of members in the VM group.
The number of instances will be equal to or less than this number.
+
+* `vm_profile_id` - (Required) The ID of the autoscale VM profile that
contains information about the VMs in the group. Changing this forces a new
resource to be created.
+
+* `scaleup_policy_ids` - (Required) A list of scale-up autoscale policy IDs.
+
+* `scaledown_policy_ids` - (Required) A list of scale-down autoscale policy
IDs.
+
+* `name` - (Optional) The name of the autoscale VM group.
+
+* `interval` - (Optional) The frequency in seconds at which performance
counters are collected. Defaults to CloudStack's default interval.
+
+* `display` - (Optional) Whether to display the group to the end user.
Defaults to `true`.
+
+* `state` - (Optional) The state of the autoscale VM group. Valid values are:
+ * `"enable"` - Enable the autoscale group (default)
+ * `"disable"` - Disable the autoscale group
+
+ **Note**: When set to `"disable"`, the autoscale group stops monitoring and
scaling, but existing VMs remain running.
+
+* `cleanup` - (Optional) Whether all members of the autoscale VM group should
be cleaned up when the group is deleted. Defaults to `false`.
+ * `true` - Destroy all VMs when deleting the autoscale group
+ * `false` - Leave VMs running when deleting the autoscale group
+
+## Attributes Reference
+
+The following attributes are exported:
+
+* `id` - The autoscale VM group ID.
+
+## State Management
+
+The `state` parameter allows you to enable or disable the autoscale group
without destroying it:
+
+* **Enabling**: Changes from `"disable"` to `"enable"` will activate autoscale
monitoring and allow automatic scaling.
+* **Disabling**: Changes from `"enable"` to `"disable"` will stop autoscale
monitoring but keep all existing VMs running.
+
+This is useful for temporarily pausing autoscale behavior during maintenance
or testing.
+
+## Import
+
+Autoscale VM groups can be imported using the `id`, e.g.
+
+```
+$ terraform import cloudstack_autoscale_vm_group.vm_group
eb22f91-7454-4107-89f4-36afcdf33021
+```
diff --git a/website/docs/r/condition.html.markdown
b/website/docs/r/condition.html.markdown
new file mode 100644
index 0000000..68f80d6
--- /dev/null
+++ b/website/docs/r/condition.html.markdown
@@ -0,0 +1,61 @@
+---
+layout: "cloudstack"
+page_title: "CloudStack: cloudstack_condition"
+sidebar_current: "docs-cloudstack-condition"
+description: |-
+ Creates a condition for autoscale policies.
+---
+
+# cloudstack_condition
+
+Creates a condition that evaluates performance metrics against thresholds for
autoscale policies.
+
+## Example Usage
+
+```hcl
+# Reference an existing counter
+data "cloudstack_counter" "cpu_counter" {
+ id = "959e11c0-8416-11f0-9a72-1e001b000238"
+}
+
+resource "cloudstack_condition" "scale_up_condition" {
+ counter_id = data.cloudstack_counter.cpu_counter.id
+ relational_operator = "GT"
+ threshold = 80.0
+ account_name = "admin"
+ domain_id = "1"
+}
+
+resource "cloudstack_condition" "scale_down_condition" {
+ counter_id = data.cloudstack_counter.cpu_counter.id
+ relational_operator = "LT"
+ threshold = 20.0
+ account_name = "admin"
+ domain_id = "1"
+}
+```
+
+## Argument Reference
+
+The following arguments are supported:
+
+* `counter_id` - (Required) The ID of the counter to monitor.
+
+* `relational_operator` - (Required) The relational operator for the
condition. Valid values are:
+ * `"GT"` - Greater than
+ * `"LT"` - Less than
+ * `"EQ"` - Equal to
+ * `"GE"` - Greater than or equal to
+ * `"LE"` - Less than or equal to
+
+* `threshold` - (Required) The threshold value to compare against.
+
+* `account_name` - (Required) The account name that owns this condition.
+
+* `domain_id` - (Required) The domain ID where the condition will be created.
+
+## Attributes Reference
+
+The following attributes are exported:
+
+* `id` - The condition ID.
diff --git a/website/docs/r/counter.html.markdown
b/website/docs/r/counter.html.markdown
new file mode 100644
index 0000000..9699df9
--- /dev/null
+++ b/website/docs/r/counter.html.markdown
@@ -0,0 +1,43 @@
+---
+layout: "cloudstack"
+page_title: "CloudStack: cloudstack_counter"
+sidebar_current: "docs-cloudstack-counter"
+description: |-
+ Creates a counter for autoscale policies.
+---
+
+# cloudstack_counter
+
+Creates a counter that can be used in autoscale conditions to monitor
performance metrics.
+
+## Example Usage
+
+```hcl
+resource "cloudstack_counter" "cpu_counter" {
+ name = "cpu-counter"
+ source = "cpu"
+ value = "cpuused"
+}
+
+resource "cloudstack_counter" "memory_counter" {
+ name = "memory-counter"
+ source = "memory"
+ value = "memoryused"
+}
+```
+
+## Argument Reference
+
+The following arguments are supported:
+
+* `name` - (Required) The name of the counter.
+
+* `source` - (Required) The source of the counter (e.g., "cpu", "memory",
"network").
+
+* `value` - (Required) The specific metric value to monitor (e.g., "cpuused",
"memoryused").
+
+## Attributes Reference
+
+The following attributes are exported:
+
+* `id` - The counter ID.