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

Pearl1594 pushed a commit to branch vpc-offering-suppot
in repository 
https://gitbox.apache.org/repos/asf/cloudstack-terraform-provider.git

commit 5715e4c9317bff4ca2a8ff22685ff866a4c4d389
Author: Pearl Dsilva <[email protected]>
AuthorDate: Thu Jul 23 14:57:19 2026 -0400

    Add support for VPC offering resource
---
 cloudstack/data_source_cloudstack_vpc_offering.go  | 233 ++++++++++++
 .../data_source_cloudstack_vpc_offering_test.go    | 123 +++++++
 cloudstack/provider.go                             |   2 +
 cloudstack/resource_cloudstack_vpc_offering.go     | 406 +++++++++++++++++++++
 .../resource_cloudstack_vpc_offering_test.go       | 198 ++++++++++
 website/docs/d/vpc_offering.html.markdown          |  43 +++
 website/docs/r/vpc_offering.html.markdown          |  71 ++++
 7 files changed, 1076 insertions(+)

diff --git a/cloudstack/data_source_cloudstack_vpc_offering.go 
b/cloudstack/data_source_cloudstack_vpc_offering.go
new file mode 100644
index 0000000..82d3c8d
--- /dev/null
+++ b/cloudstack/data_source_cloudstack_vpc_offering.go
@@ -0,0 +1,233 @@
+//
+// 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 (
+       "encoding/json"
+       "fmt"
+       "log"
+       "regexp"
+       "strings"
+       "time"
+
+       "github.com/apache/cloudstack-go/v2/cloudstack"
+       "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
+)
+
+func dataSourceCloudstackVPCOffering() *schema.Resource {
+       return &schema.Resource{
+               Read: datasourceCloudStackVPCOfferingRead,
+               Schema: map[string]*schema.Schema{
+                       "filter": dataSourceFiltersSchema(),
+
+                       //Computed values
+                       "name": {
+                               Type:     schema.TypeString,
+                               Computed: true,
+                       },
+                       "display_text": {
+                               Type:     schema.TypeString,
+                               Computed: true,
+                       },
+                       "enable": {
+                               Type:     schema.TypeBool,
+                               Computed: true,
+                       },
+                       "for_nsx": {
+                               Type:     schema.TypeBool,
+                               Computed: true,
+                       },
+                       "internet_protocol": {
+                               Type:     schema.TypeString,
+                               Computed: true,
+                       },
+                       "network_mode": {
+                               Type:     schema.TypeString,
+                               Computed: true,
+                       },
+                       "routing_mode": {
+                               Type:     schema.TypeString,
+                               Computed: true,
+                       },
+                       "specify_as_number": {
+                               Type:     schema.TypeBool,
+                               Computed: true,
+                       },
+                       "is_default": {
+                               Type:     schema.TypeBool,
+                               Computed: true,
+                       },
+                       "supported_services": {
+                               Type:     schema.TypeSet,
+                               Computed: true,
+                               Elem:     &schema.Schema{Type: 
schema.TypeString},
+                       },
+                       "service_provider_list": {
+                               Type:     schema.TypeMap,
+                               Computed: true,
+                               Elem:     &schema.Schema{Type: 
schema.TypeString},
+                       },
+                       "service_capability_list": {
+                               Type:     schema.TypeList,
+                               Computed: true,
+                               Elem: &schema.Resource{
+                                       Schema: map[string]*schema.Schema{
+                                               "service": {
+                                                       Type:     
schema.TypeString,
+                                                       Computed: true,
+                                               },
+                                               "capability_type": {
+                                                       Type:     
schema.TypeString,
+                                                       Computed: true,
+                                               },
+                                               "capability_value": {
+                                                       Type:     
schema.TypeString,
+                                                       Computed: true,
+                                               },
+                                       },
+                               },
+                       },
+               },
+       }
+}
+
+func datasourceCloudStackVPCOfferingRead(d *schema.ResourceData, meta 
interface{}) error {
+       cs := meta.(*cloudstack.CloudStackClient)
+       p := cs.VPC.NewListVPCOfferingsParams()
+       csVPCOfferings, err := cs.VPC.ListVPCOfferings(p)
+
+       if err != nil {
+               return fmt.Errorf("Failed to list VPC offerings: %s", err)
+       }
+
+       filters := d.Get("filter")
+       var vpcOfferings []*cloudstack.VPCOffering
+
+       for _, o := range csVPCOfferings.VPCOfferings {
+               match, err := applyVPCOfferingFilters(o, filters.(*schema.Set))
+               if err != nil {
+                       return err
+               }
+               if match {
+                       vpcOfferings = append(vpcOfferings, o)
+               }
+       }
+
+       if len(vpcOfferings) == 0 {
+               return fmt.Errorf("No VPC offering is matching with the 
specified regex")
+       }
+       //return the latest VPC offering from the list of filtered VPC 
offerings according
+       //to its creation date
+       vpcOffering, err := latestVPCOffering(vpcOfferings)
+       if err != nil {
+               return err
+       }
+       log.Printf("[DEBUG] Selected VPC offering: %s\n", 
vpcOffering.Displaytext)
+
+       fullVPCOffering, _, err := cs.VPC.GetVPCOfferingByID(vpcOffering.Id)
+       if err != nil {
+               return fmt.Errorf("Error retrieving full VPC offering details: 
%s", err)
+       }
+
+       return vpcOfferingDescriptionAttributes(d, fullVPCOffering)
+}
+
+func vpcOfferingDescriptionAttributes(d *schema.ResourceData, vpcOffering 
*cloudstack.VPCOffering) error {
+       d.SetId(vpcOffering.Id)
+       d.Set("name", vpcOffering.Name)
+       d.Set("display_text", vpcOffering.Displaytext)
+       d.Set("enable", vpcOffering.State == "Enabled")
+       d.Set("for_nsx", vpcOffering.Fornsx)
+       d.Set("internet_protocol", vpcOffering.Internetprotocol)
+       d.Set("network_mode", vpcOffering.Networkmode)
+       d.Set("routing_mode", vpcOffering.Routingmode)
+       d.Set("specify_as_number", vpcOffering.Specifyasnumber)
+       d.Set("is_default", vpcOffering.Isdefault)
+
+       if len(vpcOffering.Service) > 0 {
+               services := make([]string, len(vpcOffering.Service))
+               serviceProviders := make(map[string]string)
+               var serviceCapabilities []map[string]interface{}
+               for i, service := range vpcOffering.Service {
+                       services[i] = service.Name
+                       if len(service.Provider) > 0 {
+                               serviceProviders[service.Name] = 
service.Provider[0].Name
+                       }
+                       for _, capability := range service.Capability {
+                               serviceCapabilities = 
append(serviceCapabilities, map[string]interface{}{
+                                       "service":          service.Name,
+                                       "capability_type":  capability.Name,
+                                       "capability_value": capability.Value,
+                               })
+                       }
+               }
+               d.Set("supported_services", services)
+               d.Set("service_provider_list", serviceProviders)
+               d.Set("service_capability_list", serviceCapabilities)
+       }
+
+       return nil
+}
+
+func latestVPCOffering(vpcOfferings []*cloudstack.VPCOffering) 
(*cloudstack.VPCOffering, error) {
+       var latest time.Time
+       var vpcOffering *cloudstack.VPCOffering
+
+       for _, o := range vpcOfferings {
+               created, err := time.Parse("2006-01-02T15:04:05-0700", 
o.Created)
+               if err != nil {
+                       return nil, fmt.Errorf("Failed to parse creation date 
of a VPC offering: %s", err)
+               }
+
+               if created.After(latest) {
+                       latest = created
+                       vpcOffering = o
+               }
+       }
+
+       return vpcOffering, nil
+}
+
+func applyVPCOfferingFilters(vpcOffering *cloudstack.VPCOffering, filters 
*schema.Set) (bool, error) {
+       var vpcOfferingJSON map[string]interface{}
+       k, _ := json.Marshal(vpcOffering)
+       err := json.Unmarshal(k, &vpcOfferingJSON)
+       if err != nil {
+               return false, err
+       }
+
+       for _, f := range filters.List() {
+               m := f.(map[string]interface{})
+               r, err := regexp.Compile(m["value"].(string))
+               if err != nil {
+                       return false, fmt.Errorf("Invalid regex: %s", err)
+               }
+               updatedName := strings.ReplaceAll(m["name"].(string), "_", "")
+               vpcOfferingField, ok := vpcOfferingJSON[updatedName].(string)
+               if !ok {
+                       continue
+               }
+               if !r.MatchString(vpcOfferingField) {
+                       return false, nil
+               }
+
+       }
+       return true, nil
+}
diff --git a/cloudstack/data_source_cloudstack_vpc_offering_test.go 
b/cloudstack/data_source_cloudstack_vpc_offering_test.go
new file mode 100644
index 0000000..c02aa43
--- /dev/null
+++ b/cloudstack/data_source_cloudstack_vpc_offering_test.go
@@ -0,0 +1,123 @@
+//
+// 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 (
+       "testing"
+
+       "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+)
+
+func TestAccVPCOfferingDataSource_basic(t *testing.T) {
+       resourceName := "cloudstack_vpc_offering.vpc-off-resource"
+       datasourceName := "data.cloudstack_vpc_offering.vpc-off-data-source"
+
+       resource.Test(t, resource.TestCase{
+               PreCheck:  func() { testAccPreCheck(t) },
+               Providers: testAccProviders,
+               Steps: []resource.TestStep{
+                       {
+                               Config: testVPCOfferingDataSourceConfig_basic,
+                               Check: resource.ComposeTestCheckFunc(
+                                       
resource.TestCheckResourceAttrPair(datasourceName, "name", resourceName, 
"name"),
+                                       
resource.TestCheckResourceAttrPair(datasourceName, "display_text", 
resourceName, "display_text"),
+                                       
resource.TestCheckResourceAttrPair(datasourceName, "enable", resourceName, 
"enable"),
+                               ),
+                       },
+               },
+       })
+}
+
+func TestAccVPCOfferingDataSource_withServices(t *testing.T) {
+       resourceName := "cloudstack_vpc_offering.vpc-off-resource"
+       datasourceName := "data.cloudstack_vpc_offering.vpc-off-data-source"
+
+       resource.Test(t, resource.TestCase{
+               PreCheck:  func() { testAccPreCheck(t) },
+               Providers: testAccProviders,
+               Steps: []resource.TestStep{
+                       {
+                               Config: 
testVPCOfferingDataSourceConfig_withServices,
+                               Check: resource.ComposeTestCheckFunc(
+                                       
resource.TestCheckResourceAttrPair(datasourceName, "name", resourceName, 
"name"),
+                                       
resource.TestCheckResourceAttrPair(datasourceName, "supported_services.#", 
resourceName, "supported_services.#"),
+                                       
resource.TestCheckResourceAttrPair(datasourceName, "service_provider_list.%", 
resourceName, "service_provider_list.%"),
+                                       
resource.TestCheckResourceAttrPair(datasourceName, 
"service_provider_list.Dhcp", resourceName, "service_provider_list.Dhcp"),
+                                       
resource.TestCheckResourceAttrPair(datasourceName, "enable", resourceName, 
"enable"),
+                               ),
+                       },
+               },
+       })
+}
+
+const testVPCOfferingDataSourceConfig_basic = `
+resource "cloudstack_vpc_offering" "vpc-off-resource"{
+  name         = "TestVPCOfferingDisplay01"
+  display_text = "TestVPCOfferingDisplay01"
+  enable       = true
+  // CloudStack always injects SourceNat and NetworkACL support into a VPC
+  // offering even if omitted here, so they must be declared explicitly to
+  // avoid permanent post-apply drift on these ForceNew attributes.
+  supported_services = ["SourceNat", "NetworkACL"]
+  service_provider_list = {
+    SourceNat  = "VpcVirtualRouter"
+    NetworkACL = "VpcVirtualRouter"
+  }
+}
+
+data "cloudstack_vpc_offering" "vpc-off-data-source"{
+  filter{
+    name = "name"
+    value = "TestVPCOfferingDisplay01"
+  }
+  depends_on = [
+    cloudstack_vpc_offering.vpc-off-resource
+  ]
+}
+`
+
+const testVPCOfferingDataSourceConfig_withServices = `
+resource "cloudstack_vpc_offering" "vpc-off-resource"{
+  name         = "TestVPCOfferingServices01"
+  display_text = "TestVPCOfferingServices01"
+  enable       = true
+  supported_services = ["Dhcp", "Dns", "SourceNat", "PortForwarding", "Lb", 
"UserData", "StaticNat", "NetworkACL"]
+  service_provider_list = {
+    Dhcp           = "VpcVirtualRouter"
+    Dns            = "VpcVirtualRouter"
+    SourceNat      = "VpcVirtualRouter"
+    PortForwarding = "VpcVirtualRouter"
+    Lb             = "VpcVirtualRouter"
+    UserData       = "VpcVirtualRouter"
+    StaticNat      = "VpcVirtualRouter"
+    NetworkACL     = "VpcVirtualRouter"
+  }
+}
+
+data "cloudstack_vpc_offering" "vpc-off-data-source"{
+  filter{
+    name = "name"
+    value = "TestVPCOfferingServices01"
+  }
+  depends_on = [
+    cloudstack_vpc_offering.vpc-off-resource
+  ]
+}
+`
diff --git a/cloudstack/provider.go b/cloudstack/provider.go
index 7209014..7a1a94d 100644
--- a/cloudstack/provider.go
+++ b/cloudstack/provider.go
@@ -91,6 +91,7 @@ func Provider() *schema.Provider {
                        "cloudstack_service_offering":     
dataSourceCloudstackServiceOffering(),
                        "cloudstack_volume":               
dataSourceCloudstackVolume(),
                        "cloudstack_vpc":                  
dataSourceCloudstackVPC(),
+                       "cloudstack_vpc_offering":         
dataSourceCloudstackVPCOffering(),
                        "cloudstack_ipaddress":            
dataSourceCloudstackIPAddress(),
                        "cloudstack_user":                 
dataSourceCloudstackUser(),
                        "cloudstack_vpn_connection":       
dataSourceCloudstackVPNConnection(),
@@ -147,6 +148,7 @@ func Provider() *schema.Provider {
                        "cloudstack_template":                       
resourceCloudStackTemplate(),
                        "cloudstack_traffic_type":                   
resourceCloudStackTrafficType(),
                        "cloudstack_vpc":                            
resourceCloudStackVPC(),
+                       "cloudstack_vpc_offering":                   
resourceCloudStackVPCOffering(),
                        "cloudstack_vpn_connection":                 
resourceCloudStackVPNConnection(),
                        "cloudstack_vpn_customer_gateway":           
resourceCloudStackVPNCustomerGateway(),
                        "cloudstack_vpn_gateway":                    
resourceCloudStackVPNGateway(),
diff --git a/cloudstack/resource_cloudstack_vpc_offering.go 
b/cloudstack/resource_cloudstack_vpc_offering.go
new file mode 100644
index 0000000..2f3523d
--- /dev/null
+++ b/cloudstack/resource_cloudstack_vpc_offering.go
@@ -0,0 +1,406 @@
+//
+// 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 resourceCloudStackVPCOffering() *schema.Resource {
+       return &schema.Resource{
+               Create: resourceCloudStackVPCOfferingCreate,
+               Read:   resourceCloudStackVPCOfferingRead,
+               Update: resourceCloudStackVPCOfferingUpdate,
+               Delete: resourceCloudStackVPCOfferingDelete,
+               Importer: &schema.ResourceImporter{
+                       State: importStatePassthrough,
+               },
+               Schema: map[string]*schema.Schema{
+                       "name": {
+                               Type:     schema.TypeString,
+                               Required: true,
+                       },
+                       "display_text": {
+                               Type:     schema.TypeString,
+                               Required: true,
+                       },
+                       "domain_id": {
+                               Type:        schema.TypeList,
+                               Elem:        &schema.Schema{Type: 
schema.TypeString},
+                               Optional:    true,
+                               Description: "the ID of the containing 
domain(s), null for public offerings",
+                               ForceNew:    true,
+                       },
+                       "zone_id": {
+                               Type:        schema.TypeList,
+                               Elem:        &schema.Schema{Type: 
schema.TypeString},
+                               Optional:    true,
+                               Description: "the ID of the containing zone(s), 
null for public offerings",
+                               ForceNew:    true,
+                       },
+                       "enable": {
+                               Type:        schema.TypeBool,
+                               Optional:    true,
+                               Description: "set to true if the offering is to 
be enabled during creation. Default is false",
+                       },
+                       "for_nsx": {
+                               Type:        schema.TypeBool,
+                               Optional:    true,
+                               Description: "true if the VPC offering is meant 
to be used for NSX, false otherwise",
+                               ForceNew:    true,
+                       },
+                       "nsx_support_lb": {
+                               Type:        schema.TypeBool,
+                               Optional:    true,
+                               Description: "true if the NSX supports Lb 
service, false otherwise",
+                               ForceNew:    true,
+                       },
+                       "internet_protocol": {
+                               Type:        schema.TypeString,
+                               Optional:    true,
+                               Computed:    true,
+                               Description: "The internet protocol of the VPC 
offering. Options are ipv4 and dualstack. Default is ipv4. dualstack will 
create a VPC offering that supports both IPv4 and IPv6",
+                               ForceNew:    true,
+                       },
+                       "network_mode": {
+                               Type:        schema.TypeString,
+                               Optional:    true,
+                               Computed:    true,
+                               Description: "Indicates the mode with which the 
VPC will operate. Valid option: NATTED or ROUTED",
+                               ForceNew:    true,
+                       },
+                       "routing_mode": {
+                               Type:        schema.TypeString,
+                               Optional:    true,
+                               Computed:    true,
+                               Description: "the routing mode for the VPC 
offering. Supported types are: Static or Dynamic.",
+                               ForceNew:    true,
+                       },
+                       "specify_as_number": {
+                               Type:        schema.TypeBool,
+                               Optional:    true,
+                               Description: "true if the VPC offering supports 
choosing AS number",
+                               ForceNew:    true,
+                       },
+                       "network_provider": {
+                               Type:        schema.TypeString,
+                               Optional:    true,
+                               Description: "network provider for the VPC 
offering",
+                               ForceNew:    true,
+                       },
+                       "service_offering_id": {
+                               Type:        schema.TypeString,
+                               Optional:    true,
+                               Description: "the ID of the service offering 
for the VPC router appliance",
+                               ForceNew:    true,
+                       },
+                       "supported_services": {
+                               Type:        schema.TypeSet,
+                               Elem:        &schema.Schema{Type: 
schema.TypeString},
+                               Required:    true,
+                               Description: "the list of supported services",
+                               ForceNew:    true,
+                       },
+                       "service_provider_list": {
+                               Type:        schema.TypeMap,
+                               Optional:    true,
+                               Computed:    true,
+                               Description: "provider to service mapping. If 
not specified, the provider for the service will be mapped to the default 
provider on the physical network",
+                               ForceNew:    true,
+                       },
+                       "service_capability_list": {
+                               Type:        schema.TypeList,
+                               Optional:    true,
+                               Computed:    true,
+                               ForceNew:    true,
+                               Description: "desired service capabilities as 
part of the VPC offering",
+                               Elem: &schema.Resource{
+                                       Schema: map[string]*schema.Schema{
+                                               "service": {
+                                                       Type:        
schema.TypeString,
+                                                       Required:    true,
+                                                       Description: "the 
service the capability applies to, e.g. SourceNat",
+                                               },
+                                               "capability_type": {
+                                                       Type:        
schema.TypeString,
+                                                       Required:    true,
+                                                       Description: "the 
capability type, e.g. RedundantRouter",
+                                               },
+                                               "capability_value": {
+                                                       Type:        
schema.TypeString,
+                                                       Required:    true,
+                                                       Description: "the 
capability value, e.g. true",
+                                               },
+                                       },
+                               },
+                       },
+                       "is_default": {
+                               Type:        schema.TypeBool,
+                               Computed:    true,
+                               Description: "true if this is the default VPC 
offering",
+                       },
+               },
+       }
+}
+
+func resourceCloudStackVPCOfferingCreate(d *schema.ResourceData, meta 
interface{}) error {
+       cs := meta.(*cloudstack.CloudStackClient)
+       name := d.Get("name").(string)
+       displayText := d.Get("display_text").(string)
+
+       // Create a new parameter struct
+       p := cs.VPC.NewCreateVPCOfferingParams(displayText, name)
+
+       if v, ok := d.GetOk("domain_id"); ok {
+               p.SetDomainid(expandStringList(v.([]interface{})))
+       }
+
+       if v, ok := d.GetOk("zone_id"); ok {
+               p.SetZoneid(expandStringList(v.([]interface{})))
+       }
+
+       if v, ok := d.GetOk("enable"); ok {
+               p.SetEnable(v.(bool))
+       }
+
+       if v, ok := d.GetOk("for_nsx"); ok {
+               p.SetFornsx(v.(bool))
+       }
+
+       if v, ok := d.GetOk("nsx_support_lb"); ok {
+               p.SetNsxsupportlb(v.(bool))
+       }
+
+       if v, ok := d.GetOk("internet_protocol"); ok {
+               p.SetInternetprotocol(v.(string))
+       }
+
+       if v, ok := d.GetOk("network_mode"); ok {
+               p.SetNetworkmode(v.(string))
+       }
+
+       if v, ok := d.GetOk("routing_mode"); ok {
+               p.SetRoutingmode(v.(string))
+       }
+
+       if v, ok := d.GetOk("specify_as_number"); ok {
+               p.SetSpecifyasnumber(v.(bool))
+       }
+
+       if v, ok := d.GetOk("network_provider"); ok {
+               p.SetProvider(v.(string))
+       }
+
+       if v, ok := d.GetOk("service_offering_id"); ok {
+               serviceOfferingID, e := retrieveID(cs, "service_offering", 
v.(string))
+               if e != nil {
+                       return e.Error()
+               }
+               p.SetServiceofferingid(serviceOfferingID)
+       }
+
+       if v, ok := d.GetOk("supported_services"); ok {
+               var supportedServices []string
+               for _, service := range v.(*schema.Set).List() {
+                       supportedServices = append(supportedServices, 
service.(string))
+               }
+               p.SetSupportedservices(supportedServices)
+       }
+
+       if v, ok := d.GetOk("service_provider_list"); ok {
+               m := make(map[string]string)
+               for key, value := range v.(map[string]interface{}) {
+                       m[key] = value.(string)
+               }
+               p.SetServiceproviderlist(m)
+       }
+
+       if v, ok := d.GetOk("service_capability_list"); ok {
+               var capabilities []map[string]string
+               for _, item := range v.([]interface{}) {
+                       c := item.(map[string]interface{})
+                       capabilities = append(capabilities, map[string]string{
+                               "service":         c["service"].(string),
+                               "capabilitytype":  
c["capability_type"].(string),
+                               "capabilityvalue": 
c["capability_value"].(string),
+                       })
+               }
+               p.SetServicecapabilitylist(capabilities)
+       }
+
+       log.Printf("[DEBUG] Creating VPC Offering %s", name)
+       o, err := cs.VPC.CreateVPCOffering(p)
+       if err != nil {
+               return fmt.Errorf("Error creating VPC Offering %s: %s", name, 
err)
+       }
+
+       d.SetId(o.Id)
+
+       log.Printf("[DEBUG] VPC Offering %s successfully created", name)
+       return resourceCloudStackVPCOfferingRead(d, meta)
+}
+
+func resourceCloudStackVPCOfferingRead(d *schema.ResourceData, meta 
interface{}) error {
+       cs := meta.(*cloudstack.CloudStackClient)
+       log.Printf("[DEBUG] Retrieving VPC Offering %s", d.Id())
+
+       // Get the VPC Offering details
+       o, count, err := cs.VPC.GetVPCOfferingByID(d.Id())
+       if err != nil {
+               if count == 0 {
+                       log.Printf("[DEBUG] VPC Offering %s does no longer 
exist", d.Id())
+                       d.SetId("")
+                       return nil
+               }
+
+               return err
+       }
+
+       d.SetId(o.Id)
+       d.Set("name", o.Name)
+       d.Set("display_text", o.Displaytext)
+       d.Set("enable", o.State == "Enabled")
+       d.Set("is_default", o.Isdefault)
+
+       d.Set("for_nsx", o.Fornsx)
+       d.Set("specify_as_number", o.Specifyasnumber)
+
+       if o.Internetprotocol != "" {
+               d.Set("internet_protocol", o.Internetprotocol)
+       }
+
+       if o.Networkmode != "" {
+               d.Set("network_mode", o.Networkmode)
+       }
+
+       if o.Routingmode != "" {
+               d.Set("routing_mode", o.Routingmode)
+       }
+
+       if o.Domainid != "" {
+               d.Set("domain_id", []string{o.Domainid})
+       }
+
+       if o.Zoneid != "" {
+               d.Set("zone_id", []string{o.Zoneid})
+       }
+
+       if len(o.Service) > 0 {
+               services := make([]string, len(o.Service))
+               serviceProviders := make(map[string]string)
+               var capabilities []map[string]interface{}
+               for i, service := range o.Service {
+                       services[i] = service.Name
+                       if len(service.Provider) > 0 {
+                               serviceProviders[service.Name] = 
service.Provider[0].Name
+                       }
+                       for _, capability := range service.Capability {
+                               capabilities = append(capabilities, 
map[string]interface{}{
+                                       "service":          service.Name,
+                                       "capability_type":  capability.Name,
+                                       "capability_value": capability.Value,
+                               })
+                       }
+               }
+               d.Set("supported_services", services)
+               d.Set("service_provider_list", serviceProviders)
+               if len(capabilities) > 0 {
+                       d.Set("service_capability_list", capabilities)
+               }
+       }
+
+       return nil
+}
+
+func resourceCloudStackVPCOfferingUpdate(d *schema.ResourceData, meta 
interface{}) error {
+       cs := meta.(*cloudstack.CloudStackClient)
+       name := d.Get("name").(string)
+
+       // Check if the name is changed and if so, update the VPC offering
+       if d.HasChange("name") {
+               log.Printf("[DEBUG] Name changed for %s, starting update", name)
+
+               p := cs.VPC.NewUpdateVPCOfferingParams(d.Id())
+               p.SetName(name)
+
+               _, err := cs.VPC.UpdateVPCOffering(p)
+               if err != nil {
+                       return fmt.Errorf("Error updating the name for VPC 
offering %s: %s", name, err)
+               }
+       }
+
+       // Check if the display text is changed and if so, update the VPC 
offering
+       if d.HasChange("display_text") {
+               log.Printf("[DEBUG] Display text changed for %s, starting 
update", name)
+
+               p := cs.VPC.NewUpdateVPCOfferingParams(d.Id())
+               p.SetDisplaytext(d.Get("display_text").(string))
+
+               _, err := cs.VPC.UpdateVPCOffering(p)
+               if err != nil {
+                       return fmt.Errorf("Error updating the display text for 
VPC offering %s: %s", name, err)
+               }
+       }
+
+       // Check if the enabled state is changed and if so, update the VPC 
offering
+       if d.HasChange("enable") {
+               log.Printf("[DEBUG] State changed for %s, starting update", 
name)
+
+               state := "Disabled"
+               if d.Get("enable").(bool) {
+                       state = "Enabled"
+               }
+
+               p := cs.VPC.NewUpdateVPCOfferingParams(d.Id())
+               p.SetState(state)
+
+               _, err := cs.VPC.UpdateVPCOffering(p)
+               if err != nil {
+                       return fmt.Errorf("Error updating the state for VPC 
offering %s: %s", name, err)
+               }
+       }
+
+       return resourceCloudStackVPCOfferingRead(d, meta)
+}
+
+func resourceCloudStackVPCOfferingDelete(d *schema.ResourceData, meta 
interface{}) error {
+       cs := meta.(*cloudstack.CloudStackClient)
+
+       // Create a new parameter struct
+       p := cs.VPC.NewDeleteVPCOfferingParams(d.Id())
+       _, err := cs.VPC.DeleteVPCOffering(p)
+       if err != nil {
+               return fmt.Errorf("Error deleting VPC Offering: %s", err)
+       }
+
+       return nil
+}
+
+func expandStringList(list []interface{}) []string {
+       result := make([]string, len(list))
+       for i, v := range list {
+               result[i] = v.(string)
+       }
+       return result
+}
diff --git a/cloudstack/resource_cloudstack_vpc_offering_test.go 
b/cloudstack/resource_cloudstack_vpc_offering_test.go
new file mode 100644
index 0000000..f0b468f
--- /dev/null
+++ b/cloudstack/resource_cloudstack_vpc_offering_test.go
@@ -0,0 +1,198 @@
+//
+// 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"
+       "testing"
+
+       "github.com/apache/cloudstack-go/v2/cloudstack"
+       "github.com/hashicorp/terraform-plugin-testing/helper/resource"
+       "github.com/hashicorp/terraform-plugin-testing/terraform"
+)
+
+func TestAccCloudStackVPCOffering_basic(t *testing.T) {
+       var vpcOffering cloudstack.VPCOffering
+
+       resource.Test(t, resource.TestCase{
+               PreCheck:     func() { testAccPreCheck(t) },
+               Providers:    testAccProviders,
+               CheckDestroy: testAccCheckCloudStackVPCOfferingDestroy,
+               Steps: []resource.TestStep{
+                       {
+                               Config: testAccCloudStackVPCOffering_basic,
+                               Check: resource.ComposeTestCheckFunc(
+                                       testAccCheckCloudStackVPCOfferingExists(
+                                               "cloudstack_vpc_offering.foo", 
&vpcOffering),
+                                       
testAccCheckCloudStackVPCOfferingAttributes(&vpcOffering),
+                                       resource.TestCheckResourceAttr(
+                                               "cloudstack_vpc_offering.foo", 
"enable", "true"),
+                               ),
+                       },
+               },
+       })
+}
+
+func TestAccCloudStackVPCOffering_serviceCapabilityList(t *testing.T) {
+       var vpcOffering cloudstack.VPCOffering
+
+       resource.Test(t, resource.TestCase{
+               PreCheck:     func() { testAccPreCheck(t) },
+               Providers:    testAccProviders,
+               CheckDestroy: testAccCheckCloudStackVPCOfferingDestroy,
+               Steps: []resource.TestStep{
+                       {
+                               Config: 
testAccCloudStackVPCOffering_serviceCapabilityList,
+                               Check: resource.ComposeTestCheckFunc(
+                                       testAccCheckCloudStackVPCOfferingExists(
+                                               
"cloudstack_vpc_offering.capability", &vpcOffering),
+                                       resource.TestCheckResourceAttr(
+                                               
"cloudstack_vpc_offering.capability", "service_capability_list.0.service", 
"SourceNat"),
+                                       resource.TestCheckResourceAttr(
+                                               
"cloudstack_vpc_offering.capability", 
"service_capability_list.0.capability_type", "RedundantRouter"),
+                                       resource.TestCheckResourceAttr(
+                                               
"cloudstack_vpc_offering.capability", 
"service_capability_list.0.capability_value", "true"),
+                               ),
+                       },
+               },
+       })
+}
+
+func TestAccCloudStackVPCOffering_import(t *testing.T) {
+       resource.Test(t, resource.TestCase{
+               PreCheck:     func() { testAccPreCheck(t) },
+               Providers:    testAccProviders,
+               CheckDestroy: testAccCheckCloudStackVPCOfferingDestroy,
+               Steps: []resource.TestStep{
+                       {
+                               Config: testAccCloudStackVPCOffering_basic,
+                       },
+
+                       {
+                               ResourceName:      
"cloudstack_vpc_offering.foo",
+                               ImportState:       true,
+                               ImportStateVerify: true,
+                       },
+               },
+       })
+}
+
+func testAccCheckCloudStackVPCOfferingExists(
+       n string, vpcOffering *cloudstack.VPCOffering) 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 VPC offering ID is set")
+               }
+
+               cs := testAccProvider.Meta().(*cloudstack.CloudStackClient)
+               o, _, err := cs.VPC.GetVPCOfferingByID(rs.Primary.ID)
+
+               if err != nil {
+                       return err
+               }
+
+               if o.Id != rs.Primary.ID {
+                       return fmt.Errorf("VPC offering not found")
+               }
+
+               *vpcOffering = *o
+
+               return nil
+       }
+}
+
+func testAccCheckCloudStackVPCOfferingAttributes(
+       vpcOffering *cloudstack.VPCOffering) resource.TestCheckFunc {
+       return func(s *terraform.State) error {
+
+               if vpcOffering.Name != "terraform-vpc-offering" {
+                       return fmt.Errorf("Bad name: %s", vpcOffering.Name)
+               }
+
+               if vpcOffering.Displaytext != "terraform-vpc-offering-text" {
+                       return fmt.Errorf("Bad display text: %s", 
vpcOffering.Displaytext)
+               }
+
+               return nil
+       }
+}
+
+func testAccCheckCloudStackVPCOfferingDestroy(s *terraform.State) error {
+       cs := testAccProvider.Meta().(*cloudstack.CloudStackClient)
+
+       for _, rs := range s.RootModule().Resources {
+               if rs.Type != "cloudstack_vpc_offering" {
+                       continue
+               }
+
+               if rs.Primary.ID == "" {
+                       return fmt.Errorf("No VPC offering ID is set")
+               }
+
+               _, _, err := cs.VPC.GetVPCOfferingByID(rs.Primary.ID)
+               if err == nil {
+                       return fmt.Errorf("VPC offering %s still exists", 
rs.Primary.ID)
+               }
+       }
+
+       return nil
+}
+
+const testAccCloudStackVPCOffering_basic = `
+resource "cloudstack_vpc_offering" "foo" {
+  name         = "terraform-vpc-offering"
+  display_text = "terraform-vpc-offering-text"
+  enable       = true
+  supported_services = ["Dhcp", "Dns", "SourceNat", "PortForwarding", "Lb", 
"UserData", "StaticNat", "NetworkACL"]
+  service_provider_list = {
+    Dhcp           = "VpcVirtualRouter"
+    Dns            = "VpcVirtualRouter"
+    SourceNat      = "VpcVirtualRouter"
+    PortForwarding = "VpcVirtualRouter"
+    Lb             = "VpcVirtualRouter"
+    UserData       = "VpcVirtualRouter"
+    StaticNat      = "VpcVirtualRouter"
+    NetworkACL     = "VpcVirtualRouter"
+  }
+}`
+
+const testAccCloudStackVPCOffering_serviceCapabilityList = `
+resource "cloudstack_vpc_offering" "capability" {
+  name         = "terraform-vpc-offering-capability"
+  display_text = "terraform-vpc-offering-capability-text"
+  // CloudStack always injects SourceNat and NetworkACL support into a VPC
+  // offering even if omitted here, so they must be declared explicitly to
+  // avoid permanent post-apply drift on these ForceNew attributes.
+  supported_services = ["SourceNat", "NetworkACL"]
+  service_provider_list = {
+    SourceNat  = "VpcVirtualRouter"
+    NetworkACL = "VpcVirtualRouter"
+  }
+  service_capability_list {
+    service          = "SourceNat"
+    capability_type  = "RedundantRouter"
+    capability_value = "true"
+  }
+}`
diff --git a/website/docs/d/vpc_offering.html.markdown 
b/website/docs/d/vpc_offering.html.markdown
new file mode 100644
index 0000000..59dd5e3
--- /dev/null
+++ b/website/docs/d/vpc_offering.html.markdown
@@ -0,0 +1,43 @@
+---
+layout: "cloudstack"
+page_title: "Cloudstack: cloudstack_vpc_offering"
+sidebar_current: "docs-cloudstack-cloudstack_vpc_offering"
+description: |-
+  Gets information about cloudstack VPC offering.
+---
+
+# cloudstack_vpc_offering
+
+Use this datasource to get information about a VPC offering for use in other 
resources.
+
+### Example Usage
+
+```hcl
+  data "cloudstack_vpc_offering" "vpc-off-data-source"{
+    filter{
+    name = "name"
+    value="Default VPC offering"
+    }
+  }
+```
+
+### Argument Reference
+
+* `filter` - (Required) 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:
+
+* `name` - The name of the VPC offering.
+* `display_text` - An alternate display text of the VPC offering.
+* `enable` - Whether the VPC offering is enabled.
+* `for_nsx` - Whether this VPC offering is meant to be used for NSX.
+* `internet_protocol` - The internet protocol supported by the VPC offering.
+* `network_mode` - The mode with which the VPC will operate.
+* `routing_mode` - The routing mode for the VPC offering.
+* `specify_as_number` - Whether the VPC offering supports choosing an AS 
number.
+* `is_default` - `true` if this is the default VPC offering.
+* `supported_services` - The list of supported services for this VPC offering.
+* `service_provider_list` - The map of service providers for the supported 
services.
+* `service_capability_list` - The map of service capabilities for this VPC 
offering.
diff --git a/website/docs/r/vpc_offering.html.markdown 
b/website/docs/r/vpc_offering.html.markdown
new file mode 100644
index 0000000..bf8998d
--- /dev/null
+++ b/website/docs/r/vpc_offering.html.markdown
@@ -0,0 +1,71 @@
+---
+layout: default
+page_title: "CloudStack: cloudstack_vpc_offering"
+sidebar_current: "docs-cloudstack-resource-vpc_offering"
+description: |-
+    Creates a VPC Offering
+---
+
+# CloudStack: cloudstack_vpc_offering
+
+A `cloudstack_vpc_offering` resource manages a VPC offering within CloudStack.
+
+## Example Usage
+
+```hcl
+resource "cloudstack_vpc_offering" "example" {
+    name         = "example-vpc-offering"
+    display_text = "Example VPC Offering"
+    enable       = true
+    supported_services = ["Dhcp", "Dns", "SourceNat", "PortForwarding", "Lb", 
"UserData", "StaticNat", "NetworkACL"]
+    service_provider_list = {
+        Dhcp           = "VpcVirtualRouter"
+        Dns            = "VpcVirtualRouter"
+        SourceNat      = "VpcVirtualRouter"
+        PortForwarding = "VpcVirtualRouter"
+        Lb             = "VpcVirtualRouter"
+        UserData       = "VpcVirtualRouter"
+        StaticNat      = "VpcVirtualRouter"
+        NetworkACL     = "VpcVirtualRouter"
+    }
+}
+```
+
+## Argument Reference
+
+The following arguments are supported:
+
+* `name` - (Required) The name of the VPC offering.
+* `display_text` - (Required) The display text of the VPC offering.
+* `domain_id` - (Optional) The ID of the containing domain(s), null for public 
offerings.
+* `zone_id` - (Optional) The ID of the containing zone(s), null for offerings 
available in all zones.
+* `enable` - (Optional) Whether to enable the VPC offering. Defaults to 
`false`.
+* `for_nsx` - (Optional) Whether this VPC offering is meant to be used for 
NSX. Defaults to `false`.
+* `nsx_support_lb` - (Optional) Whether the NSX supports the Lb service. 
Defaults to `false`.
+* `internet_protocol` - (Optional) The internet protocol. Possible values are 
"IPv4" or "dualstack". Defaults to "IPv4".
+* `network_mode` - (Optional) Indicates the mode with which the VPC will 
operate. Possible values are "NATTED" or "ROUTED".
+* `routing_mode` - (Optional) The routing mode for the VPC offering. Possible 
values are "Static" or "Dynamic".
+* `specify_as_number` - (Optional) Whether to allow specifying an AS number. 
Defaults to `false`.
+* `network_provider` - (Optional) The network provider for the VPC offering.
+* `service_offering_id` - (Optional) The ID or name of the service offering 
for the VPC router appliance.
+* `supported_services` - (Required) A list of supported services for this VPC 
offering. CloudStack always requires (and, if omitted, will silently add) 
`NetworkACL` support, and adds `SourceNat` support unless `network_mode` is 
`ROUTED`. Since this attribute forces recreation on change, omitting either of 
these services from your configuration will cause `terraform plan` to show a 
permanent diff wanting to recreate the resource - always list them explicitly.
+* `service_provider_list` - (Optional) A map of service providers for the 
supported services.
+* `service_capability_list` - (Optional) A list of desired service 
capabilities for this VPC offering. Each block supports:
+  * `service` - (Required) The service the capability applies to, e.g. 
`SourceNat`.
+  * `capability_type` - (Required) The capability type, e.g. `RedundantRouter`.
+  * `capability_value` - (Required) The capability value, e.g. `true`.
+
+## Attributes Reference
+
+The following attributes are exported:
+
+* `id` - The ID of the VPC offering.
+* `is_default` - `true` if this is the default VPC offering.
+
+## Import
+
+VPC offerings can be imported; use `<VPCOFFERINGID>` as the import ID. For 
example:
+
+```shell
+$ terraform import cloudstack_vpc_offering.example <VPCOFFERINGID>
+```


Reply via email to