This is an automated email from the ASF dual-hosted git repository.

sureshanaparti 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 d89fc4f  SystemVM offering declaration (#327)
d89fc4f is described below

commit d89fc4f61d0a6ea44ea100034eba1d0239e7ac19
Author: bddvlpr <[email protected]>
AuthorDate: Mon Sep 7 08:38:40 2026 +0200

    SystemVM offering declaration (#327)
    
    * Add support for SystemVM offerings
    
    * Add network rate <-> domainrouter and ha <-> local storage acc tests
---
 cloudstack/provider.go                             |   1 +
 cloudstack/resource_cloudstack_nic_test.go         |  99 ++++++
 .../resource_cloudstack_system_service_offering.go | 352 +++++++++++++++++++++
 ...urce_cloudstack_system_service_offering_test.go | 215 +++++++++++++
 website/docs/README.md                             |   1 +
 .../docs/r/system_service_offering.html.markdown   |  83 +++++
 6 files changed, 751 insertions(+)

diff --git a/cloudstack/provider.go b/cloudstack/provider.go
index d268867..f4d3a4a 100644
--- a/cloudstack/provider.go
+++ b/cloudstack/provider.go
@@ -164,6 +164,7 @@ func Provider() *schema.Provider {
                        "cloudstack_volume":                         
resourceCloudStackVolume(),
                        "cloudstack_zone":                           
resourceCloudStackZone(),
                        "cloudstack_service_offering":               
resourceCloudStackServiceOffering(),
+                       "cloudstack_system_service_offering":        
resourceCloudStackSystemServiceOffering(),
                        "cloudstack_account":                        
resourceCloudStackAccount(),
                        "cloudstack_project":                        
resourceCloudStackProject(),
                        "cloudstack_user":                           
resourceCloudStackUser(),
diff --git a/cloudstack/resource_cloudstack_nic_test.go 
b/cloudstack/resource_cloudstack_nic_test.go
index d82d542..76cf36c 100644
--- a/cloudstack/resource_cloudstack_nic_test.go
+++ b/cloudstack/resource_cloudstack_nic_test.go
@@ -79,6 +79,69 @@ func TestAccCloudStackNIC_update(t *testing.T) {
        })
 }
 
+func TestAccCloudStackNIC_projectRefresh(t *testing.T) {
+       const resourceName = "cloudstack_nic.foo"
+       var nicID string
+
+       resource.Test(t, resource.TestCase{
+               PreCheck:     func() { testAccPreCheck(t) },
+               Providers:    testAccProviders,
+               CheckDestroy: testAccCheckCloudStackNICDestroy,
+               Steps: []resource.TestStep{
+                       {
+                               Config: testAccCloudStackNIC_project,
+                               Check: resource.ComposeTestCheckFunc(
+                                       
testAccCaptureCloudStackNICID(resourceName, &nicID),
+                                       
resource.TestCheckResourceAttr("cloudstack_instance.foobar", "project", 
"terraform"),
+                                       
resource.TestCheckResourceAttrSet(resourceName, "ip_address"),
+                                       
resource.TestCheckResourceAttr(resourceName, "ip_address", "10.1.2.123"),
+                               ),
+                       },
+                       {
+                               RefreshState: true,
+                               Check: resource.ComposeTestCheckFunc(
+                                       
testAccCheckCloudStackNICID(resourceName, &nicID),
+                                       
resource.TestCheckResourceAttrSet(resourceName, "ip_address"),
+                                       
resource.TestCheckResourceAttr(resourceName, "ip_address", "10.1.2.123"),
+                               ),
+                       },
+                       {
+                               Config:   testAccCloudStackNIC_project,
+                               PlanOnly: true,
+                       },
+               },
+       })
+}
+
+func testAccCaptureCloudStackNICID(n string, nicID *string) 
resource.TestCheckFunc {
+       return func(s *terraform.State) error {
+               rs, ok := s.RootModule().Resources[n]
+               if !ok {
+                       return fmt.Errorf("Not found: %s", n)
+               }
+               if rs.Primary.ID == "" {
+                       return fmt.Errorf("No NIC ID is set")
+               }
+
+               *nicID = rs.Primary.ID
+               return nil
+       }
+}
+
+func testAccCheckCloudStackNICID(n string, nicID *string) 
resource.TestCheckFunc {
+       return func(s *terraform.State) error {
+               rs, ok := s.RootModule().Resources[n]
+               if !ok {
+                       return fmt.Errorf("Not found: %s", n)
+               }
+               if rs.Primary.ID != *nicID {
+                       return fmt.Errorf("NIC ID changed from %s to %s during 
refresh", *nicID, rs.Primary.ID)
+               }
+
+               return nil
+       }
+}
+
 func TestAccCloudStackNIC_macaddress(t *testing.T) {
        var nic cloudstack.Nic
 
@@ -271,6 +334,42 @@ resource "cloudstack_nic" "foo" {
   ip_address = "10.1.2.123"
 }`
 
+const testAccCloudStackNIC_project = `
+resource "cloudstack_network" "foo" {
+  name = "terraform-project-network-primary"
+  display_text = "terraform-project-network-primary"
+  cidr = "10.1.1.0/24"
+  network_offering = "DefaultIsolatedNetworkOfferingWithSourceNatService"
+  project = "terraform"
+  zone = "Sandbox-simulator"
+}
+
+resource "cloudstack_network" "bar" {
+  name = "terraform-project-network-secondary"
+  display_text = "terraform-project-network-secondary"
+  cidr = "10.1.2.0/24"
+  network_offering = "DefaultIsolatedNetworkOfferingWithSourceNatService"
+  project = "terraform"
+  zone = "Sandbox-simulator"
+}
+
+resource "cloudstack_instance" "foobar" {
+  name = "terraform-project-nic-test"
+  display_name = "terraform-project-nic-test"
+  service_offering = "Medium Instance"
+  network_id = cloudstack_network.foo.id
+  template = "CentOS 5.6 (64-bit) no GUI (Simulator)"
+  project = "terraform"
+  zone = "Sandbox-simulator"
+  expunge = true
+}
+
+resource "cloudstack_nic" "foo" {
+  network_id = cloudstack_network.bar.id
+  virtual_machine_id = cloudstack_instance.foobar.id
+  ip_address = "10.1.2.123"
+}`
+
 const testAccCloudStackNIC_macaddress = `
 resource "cloudstack_network" "foo" {
   name = "terraform-network-primary"
diff --git a/cloudstack/resource_cloudstack_system_service_offering.go 
b/cloudstack/resource_cloudstack_system_service_offering.go
new file mode 100644
index 0000000..d2d3e94
--- /dev/null
+++ b/cloudstack/resource_cloudstack_system_service_offering.go
@@ -0,0 +1,352 @@
+//
+// 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 (
+       "context"
+       "fmt"
+       "log"
+       "strings"
+
+       "github.com/apache/cloudstack-go/v2/cloudstack"
+       "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+       "github.com/hashicorp/terraform-plugin-sdk/v2/helper/validation"
+)
+
+func resourceCloudStackSystemServiceOffering() *schema.Resource {
+       return &schema.Resource{
+               Create: resourceCloudStackSystemServiceOfferingCreate,
+               Read:   resourceCloudStackSystemServiceOfferingRead,
+               Update: resourceCloudStackSystemServiceOfferingUpdate,
+               Delete: resourceCloudStackSystemServiceOfferingDelete,
+               Importer: &schema.ResourceImporter{
+                       State: schema.ImportStatePassthrough,
+               },
+               CustomizeDiff: 
resourceCloudStackSystemServiceOfferingCustomizeDiff,
+               Schema: map[string]*schema.Schema{
+                       "name": {
+                               Type:         schema.TypeString,
+                               Required:     true,
+                               ValidateFunc: validation.StringIsNotEmpty,
+                       },
+                       "display_text": {
+                               Type:         schema.TypeString,
+                               Required:     true,
+                               ValidateFunc: validation.StringIsNotEmpty,
+                       },
+                       "system_vm_type": {
+                               Description: "The system VM type that uses this 
offering",
+                               Type:        schema.TypeString,
+                               Required:    true,
+                               ForceNew:    true,
+                               StateFunc: func(v interface{}) string {
+                                       return strings.ToLower(v.(string))
+                               },
+                               ValidateFunc: validation.StringInSlice([]string{
+                                       "domainrouter",
+                                       "consoleproxy",
+                                       "secondarystoragevm",
+                               }, true),
+                       },
+                       "cpu_number": {
+                               Description:  "Number of CPU cores",
+                               Type:         schema.TypeInt,
+                               Required:     true,
+                               ForceNew:     true,
+                               ValidateFunc: validation.IntAtLeast(1),
+                       },
+                       "cpu_speed": {
+                               Description:  "Speed of each CPU core in MHz",
+                               Type:         schema.TypeInt,
+                               Required:     true,
+                               ForceNew:     true,
+                               ValidateFunc: validation.IntAtLeast(0),
+                       },
+                       "memory": {
+                               Description:  "Memory reserved by the system VM 
in MB",
+                               Type:         schema.TypeInt,
+                               Required:     true,
+                               ForceNew:     true,
+                               ValidateFunc: validation.IntAtLeast(32),
+                       },
+                       "storage_type": {
+                               Description: "The storage type of the offering",
+                               Type:        schema.TypeString,
+                               Optional:    true,
+                               ForceNew:    true,
+                               Default:     "shared",
+                               StateFunc: func(v interface{}) string {
+                                       return strings.ToLower(v.(string))
+                               },
+                               ValidateFunc: 
validation.StringInSlice([]string{"local", "shared"}, true),
+                       },
+                       "network_rate": {
+                               Description:  "Network rate in Mbps; valid only 
for domain router offerings",
+                               Type:         schema.TypeInt,
+                               Optional:     true,
+                               ForceNew:     true,
+                               ValidateFunc: validation.IntAtLeast(1),
+                       },
+                       "offer_ha": {
+                               Description: "Whether the system offering 
supports HA",
+                               Type:        schema.TypeBool,
+                               Optional:    true,
+                               ForceNew:    true,
+                               Default:     false,
+                       },
+                       "limit_cpu_use": {
+                               Description: "Whether CPU usage is limited to 
the offering's committed resources",
+                               Type:        schema.TypeBool,
+                               Optional:    true,
+                               ForceNew:    true,
+                               Default:     false,
+                       },
+                       "host_tags": {
+                               Description: "Host tags associated with the 
offering",
+                               Type:        schema.TypeString,
+                               Optional:    true,
+                       },
+                       "storage_tags": {
+                               Description: "Storage tags associated with the 
offering",
+                               Type:        schema.TypeString,
+                               Optional:    true,
+                       },
+                       "domain_ids": {
+                               Description: "IDs of the domains that can use 
the offering; omit for a public offering",
+                               Type:        schema.TypeSet,
+                               Optional:    true,
+                               Elem: &schema.Schema{
+                                       Type:         schema.TypeString,
+                                       ValidateFunc: 
validation.StringIsNotEmpty,
+                               },
+                               Set: schema.HashString,
+                       },
+               },
+       }
+}
+
+func resourceCloudStackSystemServiceOfferingCustomizeDiff(_ context.Context, d 
*schema.ResourceDiff, _ interface{}) error {
+       _, hasNetworkRate := d.GetOk("network_rate")
+       return validateSystemServiceOfferingConfiguration(
+               d.Get("system_vm_type").(string),
+               hasNetworkRate,
+               d.Get("storage_type").(string),
+               d.Get("offer_ha").(bool),
+       )
+}
+
+func validateSystemServiceOfferingConfiguration(systemVMType string, 
hasNetworkRate bool, storageType string, offerHA bool) error {
+       if hasNetworkRate && !strings.EqualFold(systemVMType, "domainrouter") {
+               return fmt.Errorf("network_rate can only be set when 
system_vm_type is domainrouter")
+       }
+
+       if offerHA && strings.EqualFold(storageType, "local") {
+               return fmt.Errorf("offer_ha cannot be enabled when storage_type 
is local")
+       }
+
+       return nil
+}
+
+func resourceCloudStackSystemServiceOfferingCreate(d *schema.ResourceData, 
meta interface{}) error {
+       cs := meta.(*cloudstack.CloudStackClient)
+       name := d.Get("name").(string)
+       p := 
cs.ServiceOffering.NewCreateServiceOfferingParams(d.Get("display_text").(string),
 name)
+
+       p.SetIssystem(true)
+       p.SetSystemvmtype(d.Get("system_vm_type").(string))
+       p.SetCpunumber(d.Get("cpu_number").(int))
+       p.SetCpuspeed(d.Get("cpu_speed").(int))
+       p.SetMemory(d.Get("memory").(int))
+       p.SetCustomized(false)
+       p.SetStoragetype(d.Get("storage_type").(string))
+       p.SetOfferha(d.Get("offer_ha").(bool))
+       p.SetLimitcpuuse(d.Get("limit_cpu_use").(bool))
+
+       if v, ok := d.GetOk("network_rate"); ok {
+               p.SetNetworkrate(v.(int))
+       }
+       if v, ok := d.GetOk("host_tags"); ok {
+               p.SetHosttags(v.(string))
+       }
+       if v, ok := d.GetOk("storage_tags"); ok {
+               p.SetTags(v.(string))
+       }
+       if domainIDs := 
expandSystemServiceOfferingDomainIDs(d.Get("domain_ids")); len(domainIDs) > 0 {
+               p.SetDomainid(domainIDs)
+       }
+
+       log.Printf("[DEBUG] Creating System Service Offering %s", name)
+       offering, err := cs.ServiceOffering.CreateServiceOffering(p)
+       if err != nil {
+               return fmt.Errorf("error creating System Service Offering %s: 
%s", name, err)
+       }
+
+       d.SetId(offering.Id)
+       log.Printf("[DEBUG] System Service Offering %s successfully created", 
name)
+
+       return resourceCloudStackSystemServiceOfferingRead(d, meta)
+}
+
+func resourceCloudStackSystemServiceOfferingRead(d *schema.ResourceData, meta 
interface{}) error {
+       cs := meta.(*cloudstack.CloudStackClient)
+       log.Printf("[DEBUG] Retrieving System Service Offering %s", d.Id())
+
+       offering, count, err := getSystemServiceOfferingByID(cs, d.Id())
+       if err != nil {
+               return err
+       }
+       if count == 0 {
+               log.Printf("[DEBUG] System Service Offering %s no longer 
exists", d.Id())
+               d.SetId("")
+               return nil
+       }
+
+       fields := map[string]interface{}{
+               "name":           offering.Name,
+               "display_text":   offering.Displaytext,
+               "system_vm_type": strings.ToLower(offering.Systemvmtype),
+               "cpu_number":     offering.Cpunumber,
+               "cpu_speed":      offering.Cpuspeed,
+               "memory":         offering.Memory,
+               "storage_type":   strings.ToLower(offering.Storagetype),
+               "network_rate":   offering.Networkrate,
+               "offer_ha":       offering.Offerha,
+               "limit_cpu_use":  offering.Limitcpuuse,
+               "host_tags":      offering.Hosttags,
+               "storage_tags":   offering.Storagetags,
+               "domain_ids":     
flattenSystemServiceOfferingDomainIDs(offering.Domainid),
+       }
+
+       for key, value := range fields {
+               if err := d.Set(key, value); err != nil {
+                       return fmt.Errorf("error setting %s for System Service 
Offering %s: %s", key, d.Id(), err)
+               }
+       }
+
+       return nil
+}
+
+func resourceCloudStackSystemServiceOfferingUpdate(d *schema.ResourceData, 
meta interface{}) error {
+       cs := meta.(*cloudstack.CloudStackClient)
+       p := cs.ServiceOffering.NewUpdateServiceOfferingParams(d.Id())
+       hasChanges := false
+
+       if d.HasChange("name") {
+               p.SetName(d.Get("name").(string))
+               hasChanges = true
+       }
+       if d.HasChange("display_text") {
+               p.SetDisplaytext(d.Get("display_text").(string))
+               hasChanges = true
+       }
+       if d.HasChange("host_tags") {
+               p.SetHosttags(d.Get("host_tags").(string))
+               hasChanges = true
+       }
+       if d.HasChange("storage_tags") {
+               p.SetStoragetags(d.Get("storage_tags").(string))
+               hasChanges = true
+       }
+       if d.HasChange("domain_ids") {
+               domainIDs := 
expandSystemServiceOfferingDomainIDs(d.Get("domain_ids"))
+               if len(domainIDs) == 0 {
+                       p.SetDomainid("public")
+               } else {
+                       p.SetDomainid(strings.Join(domainIDs, ","))
+               }
+               hasChanges = true
+       }
+
+       if hasChanges {
+               log.Printf("[DEBUG] Updating System Service Offering %s", 
d.Id())
+               if _, err := cs.ServiceOffering.UpdateServiceOffering(p); err 
!= nil {
+                       return fmt.Errorf("error updating System Service 
Offering %s: %s", d.Id(), err)
+               }
+       }
+
+       return resourceCloudStackSystemServiceOfferingRead(d, meta)
+}
+
+func resourceCloudStackSystemServiceOfferingDelete(d *schema.ResourceData, 
meta interface{}) error {
+       cs := meta.(*cloudstack.CloudStackClient)
+       p := cs.ServiceOffering.NewDeleteServiceOfferingParams(d.Id())
+
+       log.Printf("[DEBUG] Deleting System Service Offering %s", d.Id())
+       if _, err := cs.ServiceOffering.DeleteServiceOffering(p); err != nil {
+               return fmt.Errorf("error deleting System Service Offering %s: 
%s", d.Id(), err)
+       }
+
+       d.SetId("")
+       return nil
+}
+
+func getSystemServiceOfferingByID(cs *cloudstack.CloudStackClient, id string) 
(*cloudstack.ServiceOffering, int, error) {
+       p := cs.ServiceOffering.NewListServiceOfferingsParams()
+       p.SetId(id)
+       p.SetIssystem(true)
+
+       response, err := cs.ServiceOffering.ListServiceOfferings(p)
+       if err != nil {
+               return nil, -1, fmt.Errorf("error retrieving System Service 
Offering %s: %s", id, err)
+       }
+       if response.Count == 0 {
+               return nil, 0, nil
+       }
+       if response.Count != 1 {
+               return nil, response.Count, fmt.Errorf("expected one System 
Service Offering for ID %s, got %d", id, response.Count)
+       }
+
+       offering := response.ServiceOfferings[0]
+       if !offering.Issystem {
+               return nil, 1, fmt.Errorf("service offering %s is not a system 
offering", id)
+       }
+
+       return offering, 1, nil
+}
+
+func expandSystemServiceOfferingDomainIDs(value interface{}) []string {
+       set, ok := value.(*schema.Set)
+       if !ok || set.Len() == 0 {
+               return nil
+       }
+
+       domainIDs := make([]string, 0, set.Len())
+       for _, value := range set.List() {
+               domainIDs = append(domainIDs, value.(string))
+       }
+
+       return domainIDs
+}
+
+func flattenSystemServiceOfferingDomainIDs(domainID string) []string {
+       if domainID == "" || strings.EqualFold(domainID, "public") {
+               return nil
+       }
+
+       values := strings.Split(domainID, ",")
+       domainIDs := make([]string, 0, len(values))
+       for _, value := range values {
+               if value = strings.TrimSpace(value); value != "" {
+                       domainIDs = append(domainIDs, value)
+               }
+       }
+
+       return domainIDs
+}
diff --git a/cloudstack/resource_cloudstack_system_service_offering_test.go 
b/cloudstack/resource_cloudstack_system_service_offering_test.go
new file mode 100644
index 0000000..37ec43a
--- /dev/null
+++ b/cloudstack/resource_cloudstack_system_service_offering_test.go
@@ -0,0 +1,215 @@
+//
+// 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"
+       "regexp"
+       "testing"
+
+       "github.com/apache/cloudstack-go/v2/cloudstack"
+       "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+       "github.com/hashicorp/terraform-plugin-testing/terraform"
+)
+
+func TestAccSystemServiceOffering(t *testing.T) {
+       var offering cloudstack.ServiceOffering
+       var originalID string
+       const resourceName = "cloudstack_system_service_offering.test"
+
+       resource.Test(t, resource.TestCase{
+               PreCheck:     func() { testAccPreCheck(t) },
+               Providers:    testAccProviders,
+               CheckDestroy: 
testAccCheckCloudStackSystemServiceOfferingDestroy,
+               Steps: []resource.TestStep{
+                       {
+                               Config: 
testAccCloudStackSystemServiceOfferingConfig("terraform-system-offering", 
"Terraform System Offering", 1, "compute", "primary"),
+                               Check: resource.ComposeTestCheckFunc(
+                                       
testAccCheckCloudStackSystemServiceOfferingExists(resourceName, &offering),
+                                       
testAccCaptureCloudStackSystemServiceOfferingID(resourceName, &originalID),
+                                       
resource.TestCheckResourceAttr(resourceName, "system_vm_type", "domainrouter"),
+                                       
resource.TestCheckResourceAttr(resourceName, "cpu_number", "1"),
+                                       
resource.TestCheckResourceAttr(resourceName, "cpu_speed", "500"),
+                                       
resource.TestCheckResourceAttr(resourceName, "memory", "256"),
+                                       
resource.TestCheckResourceAttr(resourceName, "storage_type", "shared"),
+                                       
resource.TestCheckResourceAttr(resourceName, "network_rate", "100"),
+                                       
resource.TestCheckResourceAttr(resourceName, "offer_ha", "true"),
+                                       
resource.TestCheckResourceAttr(resourceName, "limit_cpu_use", "true"),
+                               ),
+                       },
+                       {
+                               Config: 
testAccCloudStackSystemServiceOfferingConfig("terraform-system-offering-updated",
 "Terraform System Offering Updated", 1, "compute-updated", "secondary"),
+                               Check: resource.ComposeTestCheckFunc(
+                                       
testAccCheckCloudStackSystemServiceOfferingExists(resourceName, &offering),
+                                       
testAccCheckCloudStackSystemServiceOfferingID(resourceName, &originalID, false),
+                                       
resource.TestCheckResourceAttr(resourceName, "name", 
"terraform-system-offering-updated"),
+                                       
resource.TestCheckResourceAttr(resourceName, "display_text", "Terraform System 
Offering Updated"),
+                                       
resource.TestCheckResourceAttr(resourceName, "host_tags", "compute-updated"),
+                                       
resource.TestCheckResourceAttr(resourceName, "storage_tags", "secondary"),
+                               ),
+                       },
+                       {
+                               ResourceName:      resourceName,
+                               ImportState:       true,
+                               ImportStateVerify: true,
+                       },
+                       {
+                               Config: 
testAccCloudStackSystemServiceOfferingConfig("terraform-system-offering-updated",
 "Terraform System Offering Updated", 2, "compute-updated", "secondary"),
+                               Check: resource.ComposeTestCheckFunc(
+                                       
testAccCheckCloudStackSystemServiceOfferingExists(resourceName, &offering),
+                                       
testAccCheckCloudStackSystemServiceOfferingID(resourceName, &originalID, true),
+                                       
resource.TestCheckResourceAttr(resourceName, "cpu_number", "2"),
+                               ),
+                       },
+               },
+       })
+}
+
+func TestAccSystemServiceOfferingValidation(t *testing.T) {
+       resource.Test(t, resource.TestCase{
+               PreCheck:  func() { testAccPreCheck(t) },
+               Providers: testAccProviders,
+               Steps: []resource.TestStep{
+                       {
+                               Config:      
testAccCloudStackSystemServiceOfferingInvalidNetworkRate,
+                               ExpectError: regexp.MustCompile("network_rate 
can only be set when system_vm_type is domainrouter"),
+                       },
+                       {
+                               Config:      
testAccCloudStackSystemServiceOfferingInvalidHA,
+                               ExpectError: regexp.MustCompile("offer_ha 
cannot be enabled when storage_type is local"),
+                       },
+               },
+       })
+}
+
+const testAccCloudStackSystemServiceOfferingInvalidNetworkRate = `
+resource "cloudstack_system_service_offering" "invalid" {
+  name           = "terraform-invalid-system-offering"
+  display_text   = "Terraform Invalid System Offering"
+  system_vm_type = "consoleproxy"
+  cpu_number     = 1
+  cpu_speed      = 500
+  memory         = 256
+  network_rate   = 100
+}
+`
+
+const testAccCloudStackSystemServiceOfferingInvalidHA = `
+resource "cloudstack_system_service_offering" "invalid" {
+  name           = "terraform-invalid-system-offering"
+  display_text   = "Terraform Invalid System Offering"
+  system_vm_type = "domainrouter"
+  cpu_number     = 1
+  cpu_speed      = 500
+  memory         = 256
+  storage_type   = "local"
+  offer_ha       = true
+}
+`
+
+func testAccCloudStackSystemServiceOfferingConfig(name, displayText string, 
cpuNumber int, hostTags, storageTags string) string {
+       return fmt.Sprintf(`
+resource "cloudstack_system_service_offering" "test" {
+  name           = %q
+  display_text   = %q
+  system_vm_type = "domainrouter"
+  cpu_number     = %d
+  cpu_speed      = 500
+  memory         = 256
+  network_rate   = 100
+  offer_ha       = true
+  limit_cpu_use  = true
+  host_tags      = %q
+  storage_tags   = %q
+}
+`, name, displayText, cpuNumber, hostTags, storageTags)
+}
+
+func testAccCheckCloudStackSystemServiceOfferingExists(name string, offering 
*cloudstack.ServiceOffering) resource.TestCheckFunc {
+       return func(state *terraform.State) error {
+               resourceState, ok := state.RootModule().Resources[name]
+               if !ok {
+                       return fmt.Errorf("not found: %s", name)
+               }
+               if resourceState.Primary.ID == "" {
+                       return fmt.Errorf("no System Service Offering ID is 
set")
+               }
+
+               cs := testAccProvider.Meta().(*cloudstack.CloudStackClient)
+               result, count, err := getSystemServiceOfferingByID(cs, 
resourceState.Primary.ID)
+               if err != nil {
+                       return err
+               }
+               if count != 1 {
+                       return fmt.Errorf("System Service Offering %s not 
found", resourceState.Primary.ID)
+               }
+
+               *offering = *result
+               return nil
+       }
+}
+
+func testAccCaptureCloudStackSystemServiceOfferingID(name string, id *string) 
resource.TestCheckFunc {
+       return func(state *terraform.State) error {
+               resourceState, ok := state.RootModule().Resources[name]
+               if !ok || resourceState.Primary.ID == "" {
+                       return fmt.Errorf("no System Service Offering ID is 
set")
+               }
+
+               *id = resourceState.Primary.ID
+               return nil
+       }
+}
+
+func testAccCheckCloudStackSystemServiceOfferingID(name string, originalID 
*string, expectChanged bool) resource.TestCheckFunc {
+       return func(state *terraform.State) error {
+               resourceState, ok := state.RootModule().Resources[name]
+               if !ok || resourceState.Primary.ID == "" {
+                       return fmt.Errorf("no System Service Offering ID is 
set")
+               }
+
+               changed := resourceState.Primary.ID != *originalID
+               if changed != expectChanged {
+                       return fmt.Errorf("unexpected System Service Offering 
ID change: original=%s current=%s", *originalID, resourceState.Primary.ID)
+               }
+
+               return nil
+       }
+}
+
+func testAccCheckCloudStackSystemServiceOfferingDestroy(state 
*terraform.State) error {
+       cs := testAccProvider.Meta().(*cloudstack.CloudStackClient)
+
+       for _, resourceState := range state.RootModule().Resources {
+               if resourceState.Type != "cloudstack_system_service_offering" {
+                       continue
+               }
+
+               _, count, err := getSystemServiceOfferingByID(cs, 
resourceState.Primary.ID)
+               if err != nil {
+                       return err
+               }
+               if count != 0 {
+                       return fmt.Errorf("System Service Offering %s still 
exists", resourceState.Primary.ID)
+               }
+       }
+
+       return nil
+}
diff --git a/website/docs/README.md b/website/docs/README.md
index c99234a..c664185 100644
--- a/website/docs/README.md
+++ b/website/docs/README.md
@@ -98,6 +98,7 @@ The following arguments are supported:
 - [security_group](./r/security_group.html.markdown)
 - [security_group_rule](./r/security_group_rule.html.markdown)
 - [service_offering](./r/service_offering.html.markdown)
+- [system_service_offering](./r/system_service_offering.html.markdown)
 - [ssh_keypair](./r/ssh_keypair.html.markdown)
 - [static_nat](./r/static_nat.html.markdown)
 - [static_route](./r/static_route.html.markdown)
diff --git a/website/docs/r/system_service_offering.html.markdown 
b/website/docs/r/system_service_offering.html.markdown
new file mode 100644
index 0000000..be71b5f
--- /dev/null
+++ b/website/docs/r/system_service_offering.html.markdown
@@ -0,0 +1,83 @@
+---
+layout: default
+title: "CloudStack: cloudstack_system_service_offering"
+sidebar_current: "docs-cloudstack-resource-system-service-offering"
+description: |-
+    Creates a System Service Offering
+---
+
+# CloudStack: cloudstack_system_service_offering
+
+A `cloudstack_system_service_offering` resource manages a service offering for
+CloudStack system VMs.
+
+## Example Usage
+
+```hcl
+resource "cloudstack_system_service_offering" "router" {
+  name           = "redundant-router-offering"
+  display_text   = "Redundant router offering"
+  system_vm_type = "domainrouter"
+  cpu_number     = 2
+  cpu_speed      = 1000
+  memory         = 2048
+  network_rate   = 200
+  offer_ha       = true
+}
+```
+
+## Argument Reference
+
+The following arguments are supported:
+
+* `name` - (Required) Name of the system service offering.
+
+* `display_text` - (Required) Display text of the system service offering.
+
+* `system_vm_type` - (Required) Type of system VM that uses the offering. Valid
+  values are `domainrouter`, `consoleproxy`, and `secondarystoragevm`. Changing
+  this forces a new resource to be created.
+
+* `cpu_number` - (Required) Number of CPU cores. Changing this forces a new
+  resource to be created.
+
+* `cpu_speed` - (Required) Speed of each CPU core in MHz. Changing this forces
+  a new resource to be created.
+
+* `memory` - (Required) Memory reserved by the system VM in MB. Changing this
+  forces a new resource to be created.
+
+* `storage_type` - (Optional) Storage type of the offering. Valid values are
+  `local` and `shared`. Defaults to `shared`. Changing this forces a new 
resource
+  to be created. `local` storage cannot be combined with `offer_ha = true`.
+
+* `network_rate` - (Optional) Network rate in Mbps. This can only be set for a
+  `domainrouter` offering. Changing this forces a new resource to be created.
+
+* `offer_ha` - (Optional) Whether the offering supports HA. Defaults to 
`false`.
+  Changing this forces a new resource to be created.
+
+* `limit_cpu_use` - (Optional) Whether CPU usage is limited to the offering's
+  committed resources. Defaults to `false`. Changing this forces a new resource
+  to be created.
+
+* `host_tags` - (Optional) Host tags associated with the offering.
+
+* `storage_tags` - (Optional) Storage tags associated with the offering.
+
+* `domain_ids` - (Optional) Set of domain IDs that can use the offering. Omit
+  this argument to make the offering public.
+
+## Attributes Reference
+
+The following attributes are exported:
+
+* `id` - The ID of the system service offering.
+
+## Import
+
+System service offerings can be imported using their ID. For example:
+
+```shell
+$ terraform import cloudstack_system_service_offering.router 
<SYSTEMSERVICEOFFERINGID>
+```

Reply via email to