From af5cdf9e357f251333fdc2a30fbd96cfa4a1190d Mon Sep 17 00:00:00 2001 From: poddm <8801231+poddm@users.noreply.github.com> Date: Fri, 7 Aug 2026 10:43:00 -0700 Subject: [PATCH 1/6] disk offering updates --- .../resource_cloudstack_disk_offering.go | 390 +++++++++++++++++- 1 file changed, 375 insertions(+), 15 deletions(-) diff --git a/cloudstack/resource_cloudstack_disk_offering.go b/cloudstack/resource_cloudstack_disk_offering.go index 197eaf4b..0d7bc064 100644 --- a/cloudstack/resource_cloudstack_disk_offering.go +++ b/cloudstack/resource_cloudstack_disk_offering.go @@ -20,7 +20,7 @@ package cloudstack import ( - "log" + "fmt" "github.com/apache/cloudstack-go/v2/cloudstack" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -33,17 +33,148 @@ func resourceCloudStackDiskOffering() *schema.Resource { Update: resourceCloudStackDiskOfferingUpdate, Delete: resourceCloudStackDiskOfferingDelete, Schema: map[string]*schema.Schema{ - "name": { + "display_text": { Type: schema.TypeString, Required: true, }, - "display_text": { + "name": { Type: schema.TypeString, Required: true, }, + // + "cache_mode": { + Type: schema.TypeString, + Optional: true, + }, "disk_size": { Type: schema.TypeInt, - Required: true, + Optional: true, + }, + "disk_offering_strictness": { + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + }, + "domain_id": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "iops_read_rate": { + Type: schema.TypeInt, + Optional: true, + }, + "iops_read_rate_max": { + Type: schema.TypeInt, + Optional: true, + }, + "iops_read_rate_max_length": { + Type: schema.TypeInt, + Optional: true, + }, + "iops_write_rate": { + Type: schema.TypeInt, + Optional: true, + }, + "iops_write_rate_max": { + Type: schema.TypeInt, + Optional: true, + }, + "iops_write_rate_max_length": { + Type: schema.TypeInt, + Optional: true, + }, + "provisioning_type": { + Type: schema.TypeString, + Optional: true, + }, + "storage_type": { + Type: schema.TypeString, + Optional: true, + }, + "tags": { + Type: schema.TypeString, + Optional: true, + }, + "zone_id": { + Type: schema.TypeList, + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, + "hypervisor": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "bytes_read_rate": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + }, + "bytes_read_rate_max": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + }, + "bytes_read_rate_max_length": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + }, + "bytes_write_rate": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + }, + "bytes_write_rate_max": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + }, + "bytes_write_rate_max_length": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + }, + }, + }, + }, + "storage": { + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "min_iops": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + ForceNew: true, + }, + "max_iops": { + Type: schema.TypeInt, + Optional: true, + Computed: true, + ForceNew: true, + }, + "customized_iops": { + Type: schema.TypeBool, + Optional: true, + Computed: true, + ForceNew: true, + }, + "hypervisor_snapshot_reserve": { + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + }, + }, + }, }, }, } @@ -51,29 +182,258 @@ func resourceCloudStackDiskOffering() *schema.Resource { func resourceCloudStackDiskOfferingCreate(d *schema.ResourceData, meta interface{}) error { cs := meta.(*cloudstack.CloudStackClient) - name := d.Get("name").(string) - display_text := d.Get("display_text").(string) - disk_size := d.Get("disk_size").(int) // Create a new parameter struct - p := cs.DiskOffering.NewCreateDiskOfferingParams(name, display_text) - p.SetDisksize(int64(disk_size)) + p := cs.DiskOffering.NewCreateDiskOfferingParams(d.Get("name").(string), d.Get("display_text").(string)) - log.Printf("[DEBUG] Creating Disk Offering %s", name) - diskOff, err := cs.DiskOffering.CreateDiskOffering(p) + if v, ok := d.GetOk("cache_mode"); ok { + p.SetCachemode(v.(string)) + } + if v, ok := d.GetOk("disk_size"); ok { + p.SetDisksize(int64(v.(int))) + p.SetCustomized(false) + } else { + p.SetCustomized(true) + } + if v, ok := d.GetOk("disk_offering_strictness"); ok { + p.SetDisksizestrictness(v.(bool)) + } + if v, ok := d.GetOk("domain_id"); ok { + domain_id := v.([]interface{}) + items := make([]string, len(domain_id)) + for i, raw := range domain_id { + items[i] = raw.(string) + } + p.SetDomainid(items) + } + if v, ok := d.GetOk("iops_read_rate"); ok { + p.SetIopsreadrate(int64(v.(int))) + } + if v, ok := d.GetOk("iops_read_rate_max"); ok { + p.SetIopsreadratemax(int64(v.(int))) + } + if v, ok := d.GetOk("iops_read_rate_max_length"); ok { + p.SetIopsreadratemaxlength(int64(v.(int))) + } + if v, ok := d.GetOk("iops_write_rate"); ok { + p.SetIopsreadrate(int64(v.(int))) + } + if v, ok := d.GetOk("iops_write_rate_max"); ok { + p.SetIopsreadratemax(int64(v.(int))) + } + if v, ok := d.GetOk("iops_write_rate_max_length"); ok { + p.SetIopsreadratemaxlength(int64(v.(int))) + } + if v, ok := d.GetOk("provisioning_type"); ok { + p.SetProvisioningtype(v.(string)) + } + if v, ok := d.GetOk("storage_type"); ok { + p.SetStoragetype(v.(string)) + } + if v, ok := d.GetOk("tags"); ok { + p.SetTags(v.(string)) + } + if v, ok := d.GetOk("zone_id"); ok { + zone_id := v.([]interface{}) + items := make([]string, len(zone_id)) + for i, raw := range zone_id { + items[i] = raw.(string) + } + p.SetZoneid(items) + } + + // storage qos + if v, ok := d.GetOk("storage"); ok { + storageList := v.([]interface{}) + if len(storageList) > 0 && storageList[0] != nil { + storage := storageList[0].(map[string]interface{}) + + if v2, ok2 := storage["min_iops"]; ok2 { + p.SetMiniops(int64(v2.(int))) + } + if v2, ok2 := storage["max_iops"]; ok2 { + p.SetMaxiops(int64(v2.(int))) + } + if v2, ok2 := storage["customized_iops"]; ok2 { + p.SetCustomizediops(v2.(bool)) + } + } + } + + // hypervisor qos + if v, ok := d.GetOk("hypervisor"); ok { + hypervisorList := v.([]interface{}) + if len(hypervisorList) > 0 && hypervisorList[0] != nil { + hypervisor := hypervisorList[0].(map[string]interface{}) + + if v2, ok2 := hypervisor["bytes_read_rate"]; ok2 { + p.SetBytesreadrate(int64(v2.(int))) + } + if v2, ok2 := hypervisor["bytes_read_rate_max"]; ok2 { + p.SetBytesreadrate(int64(v2.(int))) + } + if v2, ok2 := hypervisor["bytes_read_rate_max_length"]; ok2 { + p.SetBytesreadrate(int64(v2.(int))) + } + if v2, ok2 := hypervisor["bytes_write_rate"]; ok2 { + p.SetBytesreadrate(int64(v2.(int))) + } + if v2, ok2 := hypervisor["bytes_write_rate_max"]; ok2 { + p.SetBytesreadrate(int64(v2.(int))) + } + if v2, ok2 := hypervisor["bytes_write_rate_max_length"]; ok2 { + p.SetBytesreadrate(int64(v2.(int))) + } + } + } + diskOff, err := cs.DiskOffering.CreateDiskOffering(p) if err != nil { return err } - log.Printf("[DEBUG] Disk Offering %s successfully created", name) d.SetId(diskOff.Id) return resourceCloudStackDiskOfferingRead(d, meta) } -func resourceCloudStackDiskOfferingRead(d *schema.ResourceData, meta interface{}) error { return nil } +func resourceCloudStackDiskOfferingRead(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + + r, _, err := cs.DiskOffering.GetDiskOfferingByID(d.Id()) + if err != nil { + return err + } + d.Set("display_text", r.Displaytext) + d.Set("name", r.Name) + + // + d.Set("cache_mode", r.CacheMode) + d.Set("disk_size", r.Disksize) + d.Set("disk_offering_strictness", r.Disksize) + d.Set("domain_id", r.Domainid) + d.Set("iops_read_rate", r.DiskIopsReadRate) + d.Set("iops_read_rate_max", r.DiskIopsReadRateMax) + d.Set("iops_read_rate_max_length", r.DiskIopsReadRateMaxLength) + d.Set("iops_write_rate", r.DiskIopsWriteRate) + d.Set("iops_write_rate_max", r.DiskIopsWriteRateMax) + d.Set("iops_write_rate_max_length", r.DiskIopsWriteRateMaxLength) + d.Set("provisioning_type", r.Provisioningtype) + d.Set("storage_type", r.Storagetype) + d.Set("tags", r.Tags) + d.Set("zone_id", r.Zoneid) + + // Only emit the hypervisor block when the API returns non-default QoS values, + // otherwise leave it null so configs that omit the block don't show perpetual drift. + if r.DiskBytesReadRate > 0 || r.DiskBytesReadRateMax > 0 || r.DiskBytesReadRateMaxLength > 0 || + r.DiskBytesWriteRate > 0 || r.DiskBytesWriteRateMax > 0 || r.DiskBytesWriteRateMaxLength > 0 { + hypervisor := make(map[string]interface{}) + hypervisor["bytes_read_rate"] = r.DiskBytesReadRate + hypervisor["bytes_read_rate_max"] = r.DiskBytesReadRateMax + hypervisor["bytes_read_rate_max_length"] = r.DiskBytesReadRateMaxLength + hypervisor["bytes_write_rate"] = r.DiskBytesWriteRate + hypervisor["bytes_write_rate_max"] = r.DiskBytesWriteRateMax + hypervisor["bytes_write_rate_max_length"] = r.DiskBytesWriteRateMaxLength + d.Set("hypervisor", []interface{}{hypervisor}) + } else { + d.Set("hypervisor", []interface{}{}) + } + + // Only emit the storage block when the API returns non-default QoS values. + if r.Miniops > 0 || r.Maxiops > 0 || r.Iscustomizediops { + storage := make(map[string]interface{}) + storage["min_iops"] = r.Miniops + storage["max_iops"] = r.Maxiops + storage["customized_iops"] = r.Iscustomizediops + d.Set("storage", []interface{}{storage}) + } else { + d.Set("storage", []interface{}{}) + } + + return nil + +} +func resourceCloudStackDiskOfferingUpdate(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) -func resourceCloudStackDiskOfferingUpdate(d *schema.ResourceData, meta interface{}) error { return nil } + // Create a new parameter struct + p := cs.DiskOffering.NewUpdateDiskOfferingParams(d.Id()) -func resourceCloudStackDiskOfferingDelete(d *schema.ResourceData, meta interface{}) error { return nil } + if v, ok := d.GetOk("cache_mode"); ok { + p.SetCachemode(v.(string)) + } + if v, ok := d.GetOk("display_text"); ok { + p.SetDisplaytext(v.(string)) + } + if v, ok := d.GetOk("domain_id"); ok { + p.SetDomainid(v.(string)) + } + if v, ok := d.GetOk("iops_read_rate"); ok { + p.SetIopsreadrate(int64(v.(int))) + } + if v, ok := d.GetOk("iops_read_rate_max"); ok { + p.SetIopsreadratemax(int64(v.(int))) + } + if v, ok := d.GetOk("iops_read_rate_max_length"); ok { + p.SetIopsreadratemaxlength(int64(v.(int))) + } + if v, ok := d.GetOk("iops_write_rate"); ok { + p.SetIopsreadrate(int64(v.(int))) + } + if v, ok := d.GetOk("iops_write_rate_max"); ok { + p.SetIopsreadratemax(int64(v.(int))) + } + if v, ok := d.GetOk("iops_write_rate_max_length"); ok { + p.SetIopsreadratemaxlength(int64(v.(int))) + } + if v, ok := d.GetOk("name"); ok { + p.SetName(v.(string)) + } + if v, ok := d.GetOk("tags"); ok { + p.SetTags(v.(string)) + } + if v, ok := d.GetOk("zone_id"); ok { + p.SetZoneid(fmt.Sprintf("%v", v)) + } + + // hypervisor qos + if v, ok := d.GetOk("hypervisor"); ok { + hypervisorList := v.([]interface{}) + if len(hypervisorList) > 0 && hypervisorList[0] != nil { + hypervisor := hypervisorList[0].(map[string]interface{}) + + if v2, ok2 := hypervisor["bytes_read_rate"]; ok2 { + p.SetBytesreadrate(int64(v2.(int))) + } + if v2, ok2 := hypervisor["bytes_read_rate_max"]; ok2 { + p.SetBytesreadrate(int64(v2.(int))) + } + if v2, ok2 := hypervisor["bytes_read_rate_max_length"]; ok2 { + p.SetBytesreadrate(int64(v2.(int))) + } + if v2, ok2 := hypervisor["bytes_write_rate"]; ok2 { + p.SetBytesreadrate(int64(v2.(int))) + } + if v2, ok2 := hypervisor["bytes_write_rate_max"]; ok2 { + p.SetBytesreadrate(int64(v2.(int))) + } + if v2, ok2 := hypervisor["bytes_write_rate_max_length"]; ok2 { + p.SetBytesreadrate(int64(v2.(int))) + } + } + } + + return resourceCloudStackDiskOfferingRead(d, meta) + +} + +func resourceCloudStackDiskOfferingDelete(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + + _, err := cs.DiskOffering.DeleteDiskOffering(cs.DiskOffering.NewDeleteDiskOfferingParams(d.Id())) + if err != nil { + return err + } + + return nil +} From 8623d1304f247cc4c48231eb6dc7e5370c54d1ca Mon Sep 17 00:00:00 2001 From: poddm <8801231+poddm@users.noreply.github.com> Date: Fri, 7 Aug 2026 11:11:01 -0700 Subject: [PATCH 2/6] update --- cloudstack/resource_cloudstack_disk_offering.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cloudstack/resource_cloudstack_disk_offering.go b/cloudstack/resource_cloudstack_disk_offering.go index 0d7bc064..e4c227ad 100644 --- a/cloudstack/resource_cloudstack_disk_offering.go +++ b/cloudstack/resource_cloudstack_disk_offering.go @@ -310,7 +310,7 @@ func resourceCloudStackDiskOfferingRead(d *schema.ResourceData, meta interface{} // d.Set("cache_mode", r.CacheMode) d.Set("disk_size", r.Disksize) - d.Set("disk_offering_strictness", r.Disksize) + d.Set("disk_offering_strictness", r.Disksizestrictness) d.Set("domain_id", r.Domainid) d.Set("iops_read_rate", r.DiskIopsReadRate) d.Set("iops_read_rate_max", r.DiskIopsReadRateMax) From 3382600503843a1fc106bac3a68cb91d975cc7a3 Mon Sep 17 00:00:00 2001 From: poddm <8801231+poddm@users.noreply.github.com> Date: Fri, 7 Aug 2026 12:21:13 -0700 Subject: [PATCH 3/6] updates --- cloudstack/resource_cloudstack_disk_offering.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/cloudstack/resource_cloudstack_disk_offering.go b/cloudstack/resource_cloudstack_disk_offering.go index e4c227ad..309331a7 100644 --- a/cloudstack/resource_cloudstack_disk_offering.go +++ b/cloudstack/resource_cloudstack_disk_offering.go @@ -21,6 +21,7 @@ package cloudstack import ( "fmt" + "strings" "github.com/apache/cloudstack-go/v2/cloudstack" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" @@ -311,7 +312,11 @@ func resourceCloudStackDiskOfferingRead(d *schema.ResourceData, meta interface{} d.Set("cache_mode", r.CacheMode) d.Set("disk_size", r.Disksize) d.Set("disk_offering_strictness", r.Disksizestrictness) - d.Set("domain_id", r.Domainid) + if r.Domainid != "" { + d.Set("domain_id", strings.Split(r.Domainid, ",")) + } else { + d.Set("domain_id", []string{}) + } d.Set("iops_read_rate", r.DiskIopsReadRate) d.Set("iops_read_rate_max", r.DiskIopsReadRateMax) d.Set("iops_read_rate_max_length", r.DiskIopsReadRateMaxLength) From e28c84b18d060be5d327822e3bccc2ed7917cdc2 Mon Sep 17 00:00:00 2001 From: poddm <8801231+poddm@users.noreply.github.com> Date: Fri, 7 Aug 2026 12:45:00 -0700 Subject: [PATCH 4/6] updates --- .../resource_cloudstack_disk_offering.go | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/cloudstack/resource_cloudstack_disk_offering.go b/cloudstack/resource_cloudstack_disk_offering.go index 309331a7..76c7e54a 100644 --- a/cloudstack/resource_cloudstack_disk_offering.go +++ b/cloudstack/resource_cloudstack_disk_offering.go @@ -20,7 +20,6 @@ package cloudstack import ( - "fmt" "strings" "github.com/apache/cloudstack-go/v2/cloudstack" @@ -241,6 +240,8 @@ func resourceCloudStackDiskOfferingCreate(d *schema.ResourceData, meta interface items[i] = raw.(string) } p.SetZoneid(items) + } else { + p.SetZoneid([]string{"all"}) } // storage qos @@ -326,7 +327,11 @@ func resourceCloudStackDiskOfferingRead(d *schema.ResourceData, meta interface{} d.Set("provisioning_type", r.Provisioningtype) d.Set("storage_type", r.Storagetype) d.Set("tags", r.Tags) - d.Set("zone_id", r.Zoneid) + if r.Zoneid != "" && r.Zoneid != "all" { + d.Set("zone_id", strings.Split(r.Zoneid, ",")) + } else { + d.Set("zone_id", []string{}) + } // Only emit the hypervisor block when the API returns non-default QoS values, // otherwise leave it null so configs that omit the block don't show perpetual drift. @@ -398,7 +403,14 @@ func resourceCloudStackDiskOfferingUpdate(d *schema.ResourceData, meta interface p.SetTags(v.(string)) } if v, ok := d.GetOk("zone_id"); ok { - p.SetZoneid(fmt.Sprintf("%v", v)) + zone_id := v.([]interface{}) + items := make([]string, len(zone_id)) + for i, raw := range zone_id { + items[i] = raw.(string) + } + p.SetZoneid(strings.Join(items, ",")) + } else { + p.SetZoneid("all") } // hypervisor qos From 7116c517cbc4433b80bee3fc4cac0ad8c4c43869 Mon Sep 17 00:00:00 2001 From: poddm <8801231+poddm@users.noreply.github.com> Date: Fri, 7 Aug 2026 13:27:00 -0700 Subject: [PATCH 5/6] updates --- cloudstack/resource_cloudstack_disk_offering.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/cloudstack/resource_cloudstack_disk_offering.go b/cloudstack/resource_cloudstack_disk_offering.go index 76c7e54a..aad53764 100644 --- a/cloudstack/resource_cloudstack_disk_offering.go +++ b/cloudstack/resource_cloudstack_disk_offering.go @@ -240,8 +240,6 @@ func resourceCloudStackDiskOfferingCreate(d *schema.ResourceData, meta interface items[i] = raw.(string) } p.SetZoneid(items) - } else { - p.SetZoneid([]string{"all"}) } // storage qos @@ -440,6 +438,11 @@ func resourceCloudStackDiskOfferingUpdate(d *schema.ResourceData, meta interface } } + _, err := cs.DiskOffering.UpdateDiskOffering(p) + if err != nil { + return err + } + return resourceCloudStackDiskOfferingRead(d, meta) } From 727bafa39de89b151b4f70ceb059562201241057 Mon Sep 17 00:00:00 2001 From: poddm <8801231+poddm@users.noreply.github.com> Date: Fri, 7 Aug 2026 14:09:22 -0700 Subject: [PATCH 6/6] updates --- cloudstack/resource_cloudstack_disk_offering.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/cloudstack/resource_cloudstack_disk_offering.go b/cloudstack/resource_cloudstack_disk_offering.go index aad53764..f067a990 100644 --- a/cloudstack/resource_cloudstack_disk_offering.go +++ b/cloudstack/resource_cloudstack_disk_offering.go @@ -89,10 +89,12 @@ func resourceCloudStackDiskOffering() *schema.Resource { "provisioning_type": { Type: schema.TypeString, Optional: true, + Computed: true, }, "storage_type": { Type: schema.TypeString, Optional: true, + Computed: true, }, "tags": { Type: schema.TypeString,