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 0c98bcd Add support for VPC offering resource (#306)
0c98bcd is described below
commit 0c98bcd51ef563a82eb6fbac4f6e8829cb2aaa5a
Author: Pearl Dsilva <[email protected]>
AuthorDate: Tue Aug 18 08:02:41 2026 -0400
Add support for VPC offering resource (#306)
---
cloudstack/data_source_cloudstack_vpc_offering.go | 230 ++++++++++++
.../data_source_cloudstack_vpc_offering_test.go | 123 +++++++
cloudstack/provider.go | 4 +-
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, 1074 insertions(+), 1 deletion(-)
diff --git a/cloudstack/data_source_cloudstack_vpc_offering.go
b/cloudstack/data_source_cloudstack_vpc_offering.go
new file mode 100644
index 0000000..d97d0c5
--- /dev/null
+++ b/cloudstack/data_source_cloudstack_vpc_offering.go
@@ -0,0 +1,230 @@
+//
+// 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 := fmt.Sprintf("%v",
vpcOfferingJSON[updatedName])
+ 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 0e0ee80..88756f6 100644
--- a/cloudstack/provider.go
+++ b/cloudstack/provider.go
@@ -91,8 +91,8 @@ func Provider() *schema.Provider {
"cloudstack_service_offering":
dataSourceCloudstackServiceOffering(),
"cloudstack_volume":
dataSourceCloudstackVolume(),
"cloudstack_vpc":
dataSourceCloudstackVPC(),
+ "cloudstack_vpc_offering":
dataSourceCloudstackVPCOffering(),
"cloudstack_ipaddress":
dataSourceCloudstackIPAddress(),
- "cloudstack_kubernetes_cluster_config":
dataSourceCloudstackKubernetesClusterConfig(),
"cloudstack_user":
dataSourceCloudstackUser(),
"cloudstack_vpn_connection":
dataSourceCloudstackVPNConnection(),
"cloudstack_pod":
dataSourceCloudstackPod(),
@@ -106,6 +106,7 @@ func Provider() *schema.Provider {
"cloudstack_quota_enabled":
dataSourceCloudStackQuotaEnabled(),
"cloudstack_quota_tariff":
dataSourceCloudStackQuotaTariff(),
"cloudstack_user_data":
dataSourceCloudstackUserData(),
+ "cloudstack_kubernetes_cluster_config":
dataSourceCloudstackKubernetesClusterConfig(),
},
ResourcesMap: map[string]*schema.Resource{
@@ -151,6 +152,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..fdcd2e4
--- /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 offerings available in all zones",
+ 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>
+```