From 3512f1bd789021e618b5facb9460b5ab8215f840 Mon Sep 17 00:00:00 2001 From: jean Date: Wed, 15 Oct 2025 10:06:10 -0300 Subject: [PATCH 1/4] feat: Add service_offering_details support for GPU configuration - Add service_offering_details attribute to cloudstack_service_offering resource - Support for GPU configuration with pciDevice and vgpuType parameters - Include comprehensive test coverage and documentation - Add practical examples for GPU service offerings - Addresses issue #246 --- .../resource_cloudstack_service_offering.go | 43 +++++++++++++++++++ ...source_cloudstack_service_offering_test.go | 36 ++++++++++++++++ 2 files changed, 79 insertions(+) diff --git a/cloudstack/resource_cloudstack_service_offering.go b/cloudstack/resource_cloudstack_service_offering.go index 107938da..3244daa6 100644 --- a/cloudstack/resource_cloudstack_service_offering.go +++ b/cloudstack/resource_cloudstack_service_offering.go @@ -140,6 +140,15 @@ func resourceCloudStackServiceOffering() *schema.Resource { Type: schema.TypeString, Optional: true, }, + "service_offering_details": { + Description: "Service offering details for GPU configuration and other advanced settings", + Type: schema.TypeMap, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + }, + }, }, } } @@ -216,6 +225,14 @@ func resourceCloudStackServiceOfferingCreate(d *schema.ResourceData, meta interf p.SetTags(v.(string)) } + if details, ok := d.GetOk("service_offering_details"); ok { + serviceOfferingDetails := make(map[string]string) + for k, v := range details.(map[string]interface{}) { + serviceOfferingDetails[k] = v.(string) + } + p.SetServiceofferingdetails(serviceOfferingDetails) + } + log.Printf("[DEBUG] Creating Service Offering %s", name) s, err := cs.ServiceOffering.CreateServiceOffering(p) @@ -264,6 +281,7 @@ func resourceCloudStackServiceOfferingRead(d *schema.ResourceData, meta interfac "max_memory": getIntFromDetails(s.Serviceofferingdetails, "maxmemory"), "encrypt_root": s.Encryptroot, "storage_tags": s.Storagetags, + "service_offering_details": getServiceOfferingDetails(s.Serviceofferingdetails), } for k, v := range fields { @@ -381,3 +399,28 @@ func getIntFromDetails(details map[string]string, key string) interface{} { } return nil } + +// getServiceOfferingDetails extracts custom service offering details while excluding +// built-in details that are handled as separate schema fields +func getServiceOfferingDetails(details map[string]string) map[string]interface{} { + if details == nil { + return make(map[string]interface{}) + } + + // List of built-in details that are handled as separate schema fields + builtInKeys := map[string]bool{ + "mincpunumber": true, + "maxcpunumber": true, + "minmemory": true, + "maxmemory": true, + } + + result := make(map[string]interface{}) + for k, v := range details { + if !builtInKeys[k] { + result[k] = v + } + } + + return result +} diff --git a/cloudstack/resource_cloudstack_service_offering_test.go b/cloudstack/resource_cloudstack_service_offering_test.go index d4634c9e..c194b819 100644 --- a/cloudstack/resource_cloudstack_service_offering_test.go +++ b/cloudstack/resource_cloudstack_service_offering_test.go @@ -122,3 +122,39 @@ resource "cloudstack_service_offering" "custom" { storage_tags = "production,ssd" } ` + +func TestAccCloudStackServiceOffering_gpu(t *testing.T) { + var so cloudstack.ServiceOffering + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccCloudStackServiceOffering_gpu, + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackServiceOfferingExists("cloudstack_service_offering.gpu", &so), + resource.TestCheckResourceAttr("cloudstack_service_offering.gpu", "name", "gpu_service_offering"), + resource.TestCheckResourceAttr("cloudstack_service_offering.gpu", "display_text", "GPU Test"), + resource.TestCheckResourceAttr("cloudstack_service_offering.gpu", "cpu_number", "4"), + resource.TestCheckResourceAttr("cloudstack_service_offering.gpu", "memory", "16384"), + resource.TestCheckResourceAttr("cloudstack_service_offering.gpu", "service_offering_details.pciDevice", "Group of NVIDIA A6000 GPUs"), + resource.TestCheckResourceAttr("cloudstack_service_offering.gpu", "service_offering_details.vgpuType", "A6000-8A"), + ), + }, + }, + }) +} + +const testAccCloudStackServiceOffering_gpu = ` +resource "cloudstack_service_offering" "gpu" { + name = "gpu_service_offering" + display_text = "GPU Test" + cpu_number = 4 + memory = 16384 + + service_offering_details = { + pciDevice = "Group of NVIDIA A6000 GPUs" + vgpuType = "A6000-8A" + } +} +` From bf29adc90c59d7163a800c3e37ea1f88b538539f Mon Sep 17 00:00:00 2001 From: jean Date: Wed, 15 Oct 2025 10:11:43 -0300 Subject: [PATCH 2/4] docs: Add documentation for service_offering_details feature - Update service offering documentation with GPU examples - Add service_offering_details parameter documentation - Update CHANGELOG with new feature entry - Include practical examples for GPU configuration --- CHANGELOG.md | 4 ++++ website/docs/r/service_offering.html.markdown | 23 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59f1a35c..ffd8a9c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ ## 0.4.0 (Unreleased) +FEATURES: + +* **New Resource Attribute**: `cloudstack_service_offering` now supports `service_offering_details` for GPU configuration and other advanced settings [GH-246] + IMPROVEMENTS: * Restore support for managing resource tags as CloudStack 4.11.3+ and 4.12+ support tags again [GH-65] diff --git a/website/docs/r/service_offering.html.markdown b/website/docs/r/service_offering.html.markdown index 33852cb4..57cfe84d 100644 --- a/website/docs/r/service_offering.html.markdown +++ b/website/docs/r/service_offering.html.markdown @@ -12,10 +12,30 @@ A `cloudstack_service_offering` resource manages a service offering within Cloud ## Example Usage +### Basic Service Offering + ```hcl resource "cloudstack_service_offering" "example" { name = "example-service-offering" display_text = "Example Service Offering" + cpu_number = 2 + memory = 4096 +} +``` + +### GPU Service Offering + +```hcl +resource "cloudstack_service_offering" "gpu_offering" { + name = "gpu-a6000" + display_text = "GPU A6000 Instance" + cpu_number = 4 + memory = 16384 + + service_offering_details = { + pciDevice = "Group of NVIDIA A6000 GPUs" + vgpuType = "A6000-8A" + } } ``` @@ -69,6 +89,9 @@ The following arguments are supported: * `storage_tags` - (Optional) Storage tags to associate with the service offering. +* `service_offering_details` - (Optional) A map of service offering details for GPU configuration and other advanced settings. Common keys include `pciDevice` and `vgpuType` for GPU offerings. + Changing this forces a new resource to be created. + ## Attributes Reference The following attributes are exported: From 8254bc9569441acdcb70467f5629ffadd7edf23c Mon Sep 17 00:00:00 2001 From: jean Date: Wed, 15 Oct 2025 11:17:50 -0300 Subject: [PATCH 3/4] fix: Apply gofmt formatting to resource_cloudstack_service_offering.go - Fix code formatting to comply with Go standards - Align map fields and remove trailing whitespace - Required for CI/CD pipeline to pass --- .../resource_cloudstack_service_offering.go | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/cloudstack/resource_cloudstack_service_offering.go b/cloudstack/resource_cloudstack_service_offering.go index 3244daa6..b2b2b92d 100644 --- a/cloudstack/resource_cloudstack_service_offering.go +++ b/cloudstack/resource_cloudstack_service_offering.go @@ -265,22 +265,22 @@ func resourceCloudStackServiceOfferingRead(d *schema.ResourceData, meta interfac d.SetId(s.Id) fields := map[string]interface{}{ - "name": s.Name, - "display_text": s.Displaytext, - "cpu_number": s.Cpunumber, - "cpu_speed": s.Cpuspeed, - "host_tags": s.Hosttags, - "limit_cpu_use": s.Limitcpuuse, - "memory": s.Memory, - "offer_ha": s.Offerha, - "storage_type": s.Storagetype, - "customized": s.Iscustomized, - "min_cpu_number": getIntFromDetails(s.Serviceofferingdetails, "mincpunumber"), - "max_cpu_number": getIntFromDetails(s.Serviceofferingdetails, "maxcpunumber"), - "min_memory": getIntFromDetails(s.Serviceofferingdetails, "minmemory"), - "max_memory": getIntFromDetails(s.Serviceofferingdetails, "maxmemory"), - "encrypt_root": s.Encryptroot, - "storage_tags": s.Storagetags, + "name": s.Name, + "display_text": s.Displaytext, + "cpu_number": s.Cpunumber, + "cpu_speed": s.Cpuspeed, + "host_tags": s.Hosttags, + "limit_cpu_use": s.Limitcpuuse, + "memory": s.Memory, + "offer_ha": s.Offerha, + "storage_type": s.Storagetype, + "customized": s.Iscustomized, + "min_cpu_number": getIntFromDetails(s.Serviceofferingdetails, "mincpunumber"), + "max_cpu_number": getIntFromDetails(s.Serviceofferingdetails, "maxcpunumber"), + "min_memory": getIntFromDetails(s.Serviceofferingdetails, "minmemory"), + "max_memory": getIntFromDetails(s.Serviceofferingdetails, "maxmemory"), + "encrypt_root": s.Encryptroot, + "storage_tags": s.Storagetags, "service_offering_details": getServiceOfferingDetails(s.Serviceofferingdetails), } @@ -400,13 +400,13 @@ func getIntFromDetails(details map[string]string, key string) interface{} { return nil } -// getServiceOfferingDetails extracts custom service offering details while excluding +// getServiceOfferingDetails extracts custom service offering details while excluding // built-in details that are handled as separate schema fields func getServiceOfferingDetails(details map[string]string) map[string]interface{} { if details == nil { return make(map[string]interface{}) } - + // List of built-in details that are handled as separate schema fields builtInKeys := map[string]bool{ "mincpunumber": true, @@ -414,13 +414,13 @@ func getServiceOfferingDetails(details map[string]string) map[string]interface{} "minmemory": true, "maxmemory": true, } - + result := make(map[string]interface{}) for k, v := range details { if !builtInKeys[k] { result[k] = v } } - + return result } From e4465b372e88b548cdb326bd7bc2bdaeaa5ef697 Mon Sep 17 00:00:00 2001 From: Jtolelo Date: Wed, 15 Oct 2025 12:10:25 -0300 Subject: [PATCH 4/4] Add missing cpu_speed to GPU service offering test add missing cpu_speed --- cloudstack/resource_cloudstack_service_offering_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cloudstack/resource_cloudstack_service_offering_test.go b/cloudstack/resource_cloudstack_service_offering_test.go index c194b819..c4034287 100644 --- a/cloudstack/resource_cloudstack_service_offering_test.go +++ b/cloudstack/resource_cloudstack_service_offering_test.go @@ -151,6 +151,7 @@ resource "cloudstack_service_offering" "gpu" { display_text = "GPU Test" cpu_number = 4 memory = 16384 + cpu_speed = 1000 service_offering_details = { pciDevice = "Group of NVIDIA A6000 GPUs"