From 306bccdb5b1555150c2ee9f60529a4841c2c2d57 Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Wed, 22 Jul 2026 16:33:20 -0400 Subject: [PATCH 1/3] Add terraform import support for several cloudstack resources --- .../resource_cloudstack_autoscale_policy.go | 3 + .../resource_cloudstack_autoscale_vm_group.go | 3 + ...esource_cloudstack_autoscale_vm_profile.go | 3 + ...ce_cloudstack_autoscale_vm_profile_test.go | 21 +++++ cloudstack/resource_cloudstack_condition.go | 3 + cloudstack/resource_cloudstack_counter.go | 3 + cloudstack/resource_cloudstack_host.go | 3 + cloudstack/resource_cloudstack_host_test.go | 26 ++++++ cloudstack/resource_cloudstack_ipaddress.go | 3 + .../resource_cloudstack_ipaddress_test.go | 19 ++++ .../resource_cloudstack_loadbalancer_rule.go | 3 + ...ource_cloudstack_loadbalancer_rule_test.go | 21 +++++ .../resource_cloudstack_network_offering.go | 20 +++++ .../resource_cloudstack_port_forward.go | 86 +++++++++++++++++++ .../resource_cloudstack_port_forward_test.go | 22 +++++ .../resource_cloudstack_service_offering.go | 20 +++++ ...source_cloudstack_service_offering_test.go | 18 ++++ cloudstack/resource_cloudstack_static_nat.go | 3 + .../resource_cloudstack_static_nat_test.go | 19 ++++ .../resource_cloudstack_vpn_connection.go | 3 + ...resource_cloudstack_vpn_connection_test.go | 19 ++++ website/docs/r/autoscale_policy.html.markdown | 8 ++ .../docs/r/autoscale_vm_profile.html.markdown | 8 ++ website/docs/r/condition.html.markdown | 8 ++ website/docs/r/counter.html.markdown | 8 ++ website/docs/r/ipaddress.html.markdown | 15 ++++ .../docs/r/loadbalancer_rule.html.markdown | 18 ++++ website/docs/r/port_forward.html.markdown | 19 ++++ website/docs/r/static_nat.html.markdown | 15 ++++ website/docs/r/vpn_connection.html.markdown | 8 ++ 30 files changed, 428 insertions(+) diff --git a/cloudstack/resource_cloudstack_autoscale_policy.go b/cloudstack/resource_cloudstack_autoscale_policy.go index fe390b5e..8de316e9 100644 --- a/cloudstack/resource_cloudstack_autoscale_policy.go +++ b/cloudstack/resource_cloudstack_autoscale_policy.go @@ -34,6 +34,9 @@ func resourceCloudStackAutoScalePolicy() *schema.Resource { Read: resourceCloudStackAutoScalePolicyRead, Update: resourceCloudStackAutoScalePolicyUpdate, Delete: resourceCloudStackAutoScalePolicyDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, Schema: map[string]*schema.Schema{ "name": { diff --git a/cloudstack/resource_cloudstack_autoscale_vm_group.go b/cloudstack/resource_cloudstack_autoscale_vm_group.go index 8d0eb7e6..d282032d 100644 --- a/cloudstack/resource_cloudstack_autoscale_vm_group.go +++ b/cloudstack/resource_cloudstack_autoscale_vm_group.go @@ -35,6 +35,9 @@ func resourceCloudStackAutoScaleVMGroup() *schema.Resource { Read: resourceCloudStackAutoScaleVMGroupRead, Update: resourceCloudStackAutoScaleVMGroupUpdate, Delete: resourceCloudStackAutoScaleVMGroupDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, Schema: map[string]*schema.Schema{ "lbrule_id": { diff --git a/cloudstack/resource_cloudstack_autoscale_vm_profile.go b/cloudstack/resource_cloudstack_autoscale_vm_profile.go index 0032b92a..0bc71ae1 100644 --- a/cloudstack/resource_cloudstack_autoscale_vm_profile.go +++ b/cloudstack/resource_cloudstack_autoscale_vm_profile.go @@ -35,6 +35,9 @@ func resourceCloudStackAutoScaleVMProfile() *schema.Resource { Read: resourceCloudStackAutoScaleVMProfileRead, Update: resourceCloudStackAutoScaleVMProfileUpdate, Delete: resourceCloudStackAutoScaleVMProfileDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, Schema: map[string]*schema.Schema{ "service_offering": { diff --git a/cloudstack/resource_cloudstack_autoscale_vm_profile_test.go b/cloudstack/resource_cloudstack_autoscale_vm_profile_test.go index f417f45c..97bb5b6f 100644 --- a/cloudstack/resource_cloudstack_autoscale_vm_profile_test.go +++ b/cloudstack/resource_cloudstack_autoscale_vm_profile_test.go @@ -85,6 +85,27 @@ func TestAccCloudStackAutoscaleVMProfile_update(t *testing.T) { }) } +func TestAccCloudStackAutoscaleVMProfile_import(t *testing.T) { + t.Skip("Skipping due to bug in cloudstack-go library") + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackAutoscaleVMProfileDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCloudStackAutoscaleVMProfile_basic, + }, + + { + ResourceName: "cloudstack_autoscale_vm_profile.foo", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func testAccCheckResourceMetadata(vmProfile *cloudstack.AutoScaleVmProfile) resource.TestCheckFunc { return func(s *terraform.State) error { cs := testAccProvider.Meta().(*cloudstack.CloudStackClient) diff --git a/cloudstack/resource_cloudstack_condition.go b/cloudstack/resource_cloudstack_condition.go index cd74e5cb..be734edd 100644 --- a/cloudstack/resource_cloudstack_condition.go +++ b/cloudstack/resource_cloudstack_condition.go @@ -33,6 +33,9 @@ func resourceCloudStackCondition() *schema.Resource { Read: resourceCloudStackConditionRead, Update: resourceCloudStackConditionUpdate, Delete: resourceCloudStackConditionDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, Schema: map[string]*schema.Schema{ "counter_id": { diff --git a/cloudstack/resource_cloudstack_counter.go b/cloudstack/resource_cloudstack_counter.go index ddd7e9e4..ca780475 100644 --- a/cloudstack/resource_cloudstack_counter.go +++ b/cloudstack/resource_cloudstack_counter.go @@ -32,6 +32,9 @@ func resourceCloudStackCounter() *schema.Resource { Create: resourceCloudStackCounterCreate, Read: resourceCloudStackCounterRead, Delete: resourceCloudStackCounterDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, Schema: map[string]*schema.Schema{ "name": { diff --git a/cloudstack/resource_cloudstack_host.go b/cloudstack/resource_cloudstack_host.go index 3e88d728..ec6e6664 100644 --- a/cloudstack/resource_cloudstack_host.go +++ b/cloudstack/resource_cloudstack_host.go @@ -37,6 +37,9 @@ func resourceCloudStackHost() *schema.Resource { Update: resourceCloudStackHostUpdate, Create: resourceCloudStackHostCreate, Delete: resourceCloudStackHostDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, Schema: map[string]*schema.Schema{ "hypervisor": { Type: schema.TypeString, diff --git a/cloudstack/resource_cloudstack_host_test.go b/cloudstack/resource_cloudstack_host_test.go index da91b8ab..25b53652 100644 --- a/cloudstack/resource_cloudstack_host_test.go +++ b/cloudstack/resource_cloudstack_host_test.go @@ -49,6 +49,32 @@ func TestAccCloudStackHost_basic(t *testing.T) { }) } +func TestAccCloudStackHost_import(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccCloudStackHost_basic, + }, + + { + ResourceName: "cloudstack_host.test", + ImportState: true, + ImportStateVerify: true, + // CloudStack's listHosts API never returns the connection URL or + // credentials, and these timeouts/flags are provider-local knobs that + // aren't part of the host object, so Read can't populate any of them. + ImportStateVerifyIgnore: []string{ + "url", "username", "password", + "create_timeout", "destroy_timeout", + "prevent_destroy", "force_destroy", + }, + }, + }, + }) +} + const testAccCloudStackHost_basic = ` data "cloudstack_zone" "zone" { filter { diff --git a/cloudstack/resource_cloudstack_ipaddress.go b/cloudstack/resource_cloudstack_ipaddress.go index c7db4ac0..536196ec 100644 --- a/cloudstack/resource_cloudstack_ipaddress.go +++ b/cloudstack/resource_cloudstack_ipaddress.go @@ -33,6 +33,9 @@ func resourceCloudStackIPAddress() *schema.Resource { Create: resourceCloudStackIPAddressCreate, Read: resourceCloudStackIPAddressRead, Delete: resourceCloudStackIPAddressDelete, + Importer: &schema.ResourceImporter{ + State: importStatePassthrough, + }, Schema: map[string]*schema.Schema{ "is_portable": { diff --git a/cloudstack/resource_cloudstack_ipaddress_test.go b/cloudstack/resource_cloudstack_ipaddress_test.go index 82b8ffce..53115e25 100644 --- a/cloudstack/resource_cloudstack_ipaddress_test.go +++ b/cloudstack/resource_cloudstack_ipaddress_test.go @@ -49,6 +49,25 @@ func TestAccCloudStackIPAddress_basic(t *testing.T) { }) } +func TestAccCloudStackIPAddress_import(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackIPAddressDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCloudStackIPAddress_basic, + }, + + { + ResourceName: "cloudstack_ipaddress.foo", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func TestAccCloudStackIPAddress_vpc(t *testing.T) { var ipaddr cloudstack.PublicIpAddress diff --git a/cloudstack/resource_cloudstack_loadbalancer_rule.go b/cloudstack/resource_cloudstack_loadbalancer_rule.go index 6ebf52b5..1c53f17b 100644 --- a/cloudstack/resource_cloudstack_loadbalancer_rule.go +++ b/cloudstack/resource_cloudstack_loadbalancer_rule.go @@ -37,6 +37,9 @@ func resourceCloudStackLoadBalancerRule() *schema.Resource { Read: resourceCloudStackLoadBalancerRuleRead, Update: resourceCloudStackLoadBalancerRuleUpdate, Delete: resourceCloudStackLoadBalancerRuleDelete, + Importer: &schema.ResourceImporter{ + State: importStatePassthrough, + }, Schema: map[string]*schema.Schema{ "name": { diff --git a/cloudstack/resource_cloudstack_loadbalancer_rule_test.go b/cloudstack/resource_cloudstack_loadbalancer_rule_test.go index 8a9c7920..f251152f 100644 --- a/cloudstack/resource_cloudstack_loadbalancer_rule_test.go +++ b/cloudstack/resource_cloudstack_loadbalancer_rule_test.go @@ -53,6 +53,27 @@ func TestAccCloudStackLoadBalancerRule_basic(t *testing.T) { }) } +func TestAccCloudStackLoadBalancerRule_import(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackLoadBalancerRuleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCloudStackLoadBalancerRule_basic, + }, + + { + ResourceName: "cloudstack_loadbalancer_rule.foo", + ImportState: true, + ImportStateVerify: true, + // certificate_id can't be read back from the CloudStack API (write-only field) + ImportStateVerifyIgnore: []string{"certificate_id"}, + }, + }, + }) +} + func TestAccCloudStackLoadBalancerRule_update(t *testing.T) { var id string diff --git a/cloudstack/resource_cloudstack_network_offering.go b/cloudstack/resource_cloudstack_network_offering.go index d6adf4b8..f260e636 100644 --- a/cloudstack/resource_cloudstack_network_offering.go +++ b/cloudstack/resource_cloudstack_network_offering.go @@ -33,6 +33,9 @@ func resourceCloudStackNetworkOffering() *schema.Resource { Read: resourceCloudStackNetworkOfferingRead, Update: resourceCloudStackNetworkOfferingUpdate, Delete: resourceCloudStackNetworkOfferingDelete, + Importer: &schema.ResourceImporter{ + State: resourceCloudStackNetworkOfferingImport, + }, Schema: map[string]*schema.Schema{ "name": { Type: schema.TypeString, @@ -342,6 +345,23 @@ func resourceCloudStackNetworkOfferingDelete(d *schema.ResourceData, meta interf return nil } +func resourceCloudStackNetworkOfferingImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + cs := meta.(*cloudstack.CloudStackClient) + + // Read looks the offering up by name, so resolve the name from the ID first + n, count, err := cs.NetworkOffering.GetNetworkOfferingByID(d.Id()) + if err != nil { + if count == 0 { + return nil, fmt.Errorf("network offering with ID %s does not exist", d.Id()) + } + return nil, err + } + + d.Set("name", n.Name) + + return []*schema.ResourceData{d}, nil +} + func resourceCloudStackNetworkOfferingRead(d *schema.ResourceData, meta interface{}) error { cs := meta.(*cloudstack.CloudStackClient) log.Printf("[DEBUG] Retrieving Network Offering %s", d.Get("name").(string)) diff --git a/cloudstack/resource_cloudstack_port_forward.go b/cloudstack/resource_cloudstack_port_forward.go index 2b006738..661f8ebb 100644 --- a/cloudstack/resource_cloudstack_port_forward.go +++ b/cloudstack/resource_cloudstack_port_forward.go @@ -38,6 +38,9 @@ func resourceCloudStackPortForward() *schema.Resource { Read: resourceCloudStackPortForwardRead, Update: resourceCloudStackPortForwardUpdate, Delete: resourceCloudStackPortForwardDelete, + Importer: &schema.ResourceImporter{ + State: resourceCloudStackPortForwardImport, + }, Schema: map[string]*schema.Schema{ "ip_address_id": { @@ -230,6 +233,89 @@ func createPortForward(d *schema.ResourceData, meta interface{}, forward map[str return nil } +func resourceCloudStackPortForwardImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + cs := meta.(*cloudstack.CloudStackClient) + + // Try to split the ID to extract the optional project name. + s := strings.SplitN(d.Id(), "/", 2) + if len(s) == 2 { + d.Set("project", s[0]) + } + + ipAddressID := s[len(s)-1] + d.SetId(ipAddressID) + + // Make sure the IP address exists + if _, count, err := cs.Address.GetPublicIpAddressByID( + ipAddressID, + cloudstack.WithProject(d.Get("project").(string)), + ); err != nil { + if count == 0 { + return nil, fmt.Errorf("IP address with ID %s does not exist", ipAddressID) + } + return nil, err + } + + // Get all the forwards configured for this IP address + p := cs.Firewall.NewListPortForwardingRulesParams() + p.SetIpaddressid(ipAddressID) + p.SetListall(true) + + if err := setProjectid(p, cs, d); err != nil { + return nil, err + } + + l, err := cs.Firewall.ListPortForwardingRules(p) + if err != nil { + return nil, err + } + + forwards := resourceCloudStackPortForward().Schema["forward"].ZeroValue().(*schema.Set) + for _, f := range l.PortForwardingRules { + privPort, err := strconv.Atoi(f.Privateport) + if err != nil { + return nil, err + } + + pubPort, err := strconv.Atoi(f.Publicport) + if err != nil { + return nil, err + } + + forward := map[string]interface{}{ + "protocol": f.Protocol, + "private_port": privPort, + "public_port": pubPort, + "virtual_machine_id": f.Virtualmachineid, + "vm_guest_ip": f.Vmguestip, + "uuid": f.Id, + } + + if f.Privateendport != "" && f.Privateendport != f.Privateport { + privEndPort, err := strconv.Atoi(f.Privateendport) + if err != nil { + return nil, err + } + forward["private_end_port"] = privEndPort + } + + if f.Publicendport != "" && f.Publicendport != f.Publicport { + pubEndPort, err := strconv.Atoi(f.Publicendport) + if err != nil { + return nil, err + } + forward["public_end_port"] = pubEndPort + } + + forwards.Add(forward) + } + + d.Set("forward", forwards) + d.Set("managed", true) + + return []*schema.ResourceData{d}, nil +} + func resourceCloudStackPortForwardRead(d *schema.ResourceData, meta interface{}) error { cs := meta.(*cloudstack.CloudStackClient) diff --git a/cloudstack/resource_cloudstack_port_forward_test.go b/cloudstack/resource_cloudstack_port_forward_test.go index bb15f289..44a0a804 100644 --- a/cloudstack/resource_cloudstack_port_forward_test.go +++ b/cloudstack/resource_cloudstack_port_forward_test.go @@ -57,6 +57,28 @@ func TestAccCloudStackPortForward_basic(t *testing.T) { }) } +func TestAccCloudStackPortForward_import(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackPortForwardDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCloudStackPortForward_basic, + }, + + { + ResourceName: "cloudstack_port_forward.foo", + ImportState: true, + ImportStateVerify: true, + // The importer sets managed=true so unrelated forwards on the same IP + // aren't silently dropped; the applied config leaves it at its default. + ImportStateVerifyIgnore: []string{"managed"}, + }, + }, + }) +} + func TestAccCloudStackPortForward_update(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, diff --git a/cloudstack/resource_cloudstack_service_offering.go b/cloudstack/resource_cloudstack_service_offering.go index b2b2b92d..227a879b 100644 --- a/cloudstack/resource_cloudstack_service_offering.go +++ b/cloudstack/resource_cloudstack_service_offering.go @@ -34,6 +34,9 @@ func resourceCloudStackServiceOffering() *schema.Resource { Read: resourceCloudStackServiceOfferingRead, Update: resourceCloudStackServiceOfferingUpdate, Delete: resourceCloudStackServiceOfferingDelete, + Importer: &schema.ResourceImporter{ + State: resourceCloudStackServiceOfferingImport, + }, Schema: map[string]*schema.Schema{ "name": { Type: schema.TypeString, @@ -246,6 +249,23 @@ func resourceCloudStackServiceOfferingCreate(d *schema.ResourceData, meta interf return resourceCloudStackServiceOfferingRead(d, meta) } +func resourceCloudStackServiceOfferingImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + cs := meta.(*cloudstack.CloudStackClient) + + // Read looks the offering up by name, so resolve the name from the ID first + s, count, err := cs.ServiceOffering.GetServiceOfferingByID(d.Id()) + if err != nil { + if count == 0 { + return nil, fmt.Errorf("service offering with ID %s does not exist", d.Id()) + } + return nil, err + } + + d.Set("name", s.Name) + + return []*schema.ResourceData{d}, nil +} + func resourceCloudStackServiceOfferingRead(d *schema.ResourceData, meta interface{}) error { cs := meta.(*cloudstack.CloudStackClient) log.Printf("[DEBUG] Retrieving Service Offering %s", d.Get("name").(string)) diff --git a/cloudstack/resource_cloudstack_service_offering_test.go b/cloudstack/resource_cloudstack_service_offering_test.go index c4034287..9bb34f3a 100644 --- a/cloudstack/resource_cloudstack_service_offering_test.go +++ b/cloudstack/resource_cloudstack_service_offering_test.go @@ -47,6 +47,24 @@ func TestAccCloudStackServiceOffering_basic(t *testing.T) { }) } +func TestAccCloudStackServiceOffering_import(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + Steps: []resource.TestStep{ + { + Config: testAccCloudStackServiceOffering_basic, + }, + + { + ResourceName: "cloudstack_service_offering.test1", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + const testAccCloudStackServiceOffering_basic = ` resource "cloudstack_service_offering" "test1" { name = "service_offering_1" diff --git a/cloudstack/resource_cloudstack_static_nat.go b/cloudstack/resource_cloudstack_static_nat.go index 2b2c4027..36fef6c6 100644 --- a/cloudstack/resource_cloudstack_static_nat.go +++ b/cloudstack/resource_cloudstack_static_nat.go @@ -34,6 +34,9 @@ func resourceCloudStackStaticNAT() *schema.Resource { Exists: resourceCloudStackStaticNATExists, Read: resourceCloudStackStaticNATRead, Delete: resourceCloudStackStaticNATDelete, + Importer: &schema.ResourceImporter{ + State: importStatePassthrough, + }, Schema: map[string]*schema.Schema{ "ip_address_id": { diff --git a/cloudstack/resource_cloudstack_static_nat_test.go b/cloudstack/resource_cloudstack_static_nat_test.go index eebebcb0..8004718e 100644 --- a/cloudstack/resource_cloudstack_static_nat_test.go +++ b/cloudstack/resource_cloudstack_static_nat_test.go @@ -48,6 +48,25 @@ func TestAccCloudStackStaticNAT_basic(t *testing.T) { }) } +func TestAccCloudStackStaticNAT_import(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackStaticNATDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCloudStackStaticNAT_basic, + }, + + { + ResourceName: "cloudstack_static_nat.foo", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func testAccCheckCloudStackStaticNATExists( n string, ipaddr *cloudstack.PublicIpAddress) resource.TestCheckFunc { return func(s *terraform.State) error { diff --git a/cloudstack/resource_cloudstack_vpn_connection.go b/cloudstack/resource_cloudstack_vpn_connection.go index a493e16d..d1bf4d89 100644 --- a/cloudstack/resource_cloudstack_vpn_connection.go +++ b/cloudstack/resource_cloudstack_vpn_connection.go @@ -33,6 +33,9 @@ func resourceCloudStackVPNConnection() *schema.Resource { Create: resourceCloudStackVPNConnectionCreate, Read: resourceCloudStackVPNConnectionRead, Delete: resourceCloudStackVPNConnectionDelete, + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, Schema: map[string]*schema.Schema{ "customer_gateway_id": { diff --git a/cloudstack/resource_cloudstack_vpn_connection_test.go b/cloudstack/resource_cloudstack_vpn_connection_test.go index 55d42a75..1eef989e 100644 --- a/cloudstack/resource_cloudstack_vpn_connection_test.go +++ b/cloudstack/resource_cloudstack_vpn_connection_test.go @@ -49,6 +49,25 @@ func TestAccCloudStackVPNConnection_basic(t *testing.T) { }) } +func TestAccCloudStackVPNConnection_import(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackVPNConnectionDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCloudStackVPNConnection_basic, + }, + + { + ResourceName: "cloudstack_vpn_connection.foo-bar", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + func testAccCheckCloudStackVPNConnectionExists( n string, vpnConnection *cloudstack.VpnConnection) resource.TestCheckFunc { return func(s *terraform.State) error { diff --git a/website/docs/r/autoscale_policy.html.markdown b/website/docs/r/autoscale_policy.html.markdown index 98da7426..3abe3c2a 100644 --- a/website/docs/r/autoscale_policy.html.markdown +++ b/website/docs/r/autoscale_policy.html.markdown @@ -61,3 +61,11 @@ The following arguments are supported: The following attributes are exported: * `id` - The autoscale policy ID. + +## Import + +Autoscale policies can be imported using the `id`, e.g. + +```shell +$ terraform import cloudstack_autoscale_policy.default eb22f91-7454-4107-89f4-36afcdf33021 +``` diff --git a/website/docs/r/autoscale_vm_profile.html.markdown b/website/docs/r/autoscale_vm_profile.html.markdown index 4213cccf..f9149fca 100644 --- a/website/docs/r/autoscale_vm_profile.html.markdown +++ b/website/docs/r/autoscale_vm_profile.html.markdown @@ -74,3 +74,11 @@ The following arguments are supported: The following attributes are exported: * `id` - The autoscale VM profile ID. + +## Import + +Autoscale VM profiles can be imported using the `id`, e.g. + +```shell +$ terraform import cloudstack_autoscale_vm_profile.default eb22f91-7454-4107-89f4-36afcdf33021 +``` diff --git a/website/docs/r/condition.html.markdown b/website/docs/r/condition.html.markdown index 68f80d65..23956a3d 100644 --- a/website/docs/r/condition.html.markdown +++ b/website/docs/r/condition.html.markdown @@ -59,3 +59,11 @@ The following arguments are supported: The following attributes are exported: * `id` - The condition ID. + +## Import + +Conditions can be imported using the `id`, e.g. + +```shell +$ terraform import cloudstack_condition.default eb22f91-7454-4107-89f4-36afcdf33021 +``` diff --git a/website/docs/r/counter.html.markdown b/website/docs/r/counter.html.markdown index 9699df96..0dada3fb 100644 --- a/website/docs/r/counter.html.markdown +++ b/website/docs/r/counter.html.markdown @@ -41,3 +41,11 @@ The following arguments are supported: The following attributes are exported: * `id` - The counter ID. + +## Import + +Counters can be imported using the `id`, e.g. + +```shell +$ terraform import cloudstack_counter.default eb22f91-7454-4107-89f4-36afcdf33021 +``` diff --git a/website/docs/r/ipaddress.html.markdown b/website/docs/r/ipaddress.html.markdown index 27bcbd20..042753b9 100644 --- a/website/docs/r/ipaddress.html.markdown +++ b/website/docs/r/ipaddress.html.markdown @@ -46,3 +46,18 @@ The following attributes are exported: * `id` - The ID of the acquired and associated IP address. * `ip_address` - The IP address that was acquired and associated. + +## Import + +IP addresses can be imported; use `` as the import ID. For +example: + +```shell +$ terraform import cloudstack_ipaddress.default 6226ea4d-9cbe-4cc9-b30c-b9532146da5b +``` + +When importing into a project you need to prefix the import ID with the project name: + +```shell +$ terraform import cloudstack_ipaddress.default my-project/6226ea4d-9cbe-4cc9-b30c-b9532146da5b +``` diff --git a/website/docs/r/loadbalancer_rule.html.markdown b/website/docs/r/loadbalancer_rule.html.markdown index 890d0443..00cd9683 100644 --- a/website/docs/r/loadbalancer_rule.html.markdown +++ b/website/docs/r/loadbalancer_rule.html.markdown @@ -70,3 +70,21 @@ The following attributes are exported: * `id` - The load balancer rule ID. * `description` - The description of the load balancer rule. + +## Import + +Load balancer rules can be imported; use `` as the import ID. For +example: + +```shell +$ terraform import cloudstack_loadbalancer_rule.default 6226ea4d-9cbe-4cc9-b30c-b9532146da5b +``` + +When importing into a project you need to prefix the import ID with the project name: + +```shell +$ terraform import cloudstack_loadbalancer_rule.default my-project/6226ea4d-9cbe-4cc9-b30c-b9532146da5b +``` + +*NOTE: `certificate_id` cannot be read back from the CloudStack API, so it will +always be empty after an import.* diff --git a/website/docs/r/port_forward.html.markdown b/website/docs/r/port_forward.html.markdown index 23d1eb30..b5ffe523 100644 --- a/website/docs/r/port_forward.html.markdown +++ b/website/docs/r/port_forward.html.markdown @@ -70,3 +70,22 @@ The following attributes are exported: * `id` - The ID of the IP address for which the port forwards are created. * `vm_guest_ip` - The IP address of the virtual machine that is used for the port forwarding rule. + +## Import + +Port forwards can be imported; use the `` for which the rules +are configured as the import ID. For example: + +```shell +$ terraform import cloudstack_port_forward.default 6226ea4d-9cbe-4cc9-b30c-b9532146da5b +``` + +When importing into a project you need to prefix the import ID with the project name: + +```shell +$ terraform import cloudstack_port_forward.default my-project/6226ea4d-9cbe-4cc9-b30c-b9532146da5b +``` + +*NOTE: All existing port forwarding rules for the IP address are imported into +a single `forward` set, and `managed` is set to `true` so unrelated rules +created outside of this resource aren't dropped on the next apply.* diff --git a/website/docs/r/static_nat.html.markdown b/website/docs/r/static_nat.html.markdown index 74847820..f62bf94f 100644 --- a/website/docs/r/static_nat.html.markdown +++ b/website/docs/r/static_nat.html.markdown @@ -43,3 +43,18 @@ The following attributes are exported: * `id` - The static nat ID. * `vm_guest_ip` - The IP address of the virtual machine that is used to forward the static NAT traffic to. + +## Import + +Static NAT can be imported; use the `` for which static NAT is +enabled as the import ID. For example: + +```shell +$ terraform import cloudstack_static_nat.default 6226ea4d-9cbe-4cc9-b30c-b9532146da5b +``` + +When importing into a project you need to prefix the import ID with the project name: + +```shell +$ terraform import cloudstack_static_nat.default my-project/6226ea4d-9cbe-4cc9-b30c-b9532146da5b +``` diff --git a/website/docs/r/vpn_connection.html.markdown b/website/docs/r/vpn_connection.html.markdown index 7a2f7011..56896acc 100644 --- a/website/docs/r/vpn_connection.html.markdown +++ b/website/docs/r/vpn_connection.html.markdown @@ -36,3 +36,11 @@ The following arguments are supported: The following attributes are exported: * `id` - The ID of the VPN Connection. + +## Import + +VPN Connections can be imported using the `id`, e.g. + +```shell +$ terraform import cloudstack_vpn_connection.default eb22f91-7454-4107-89f4-36afcdf33021 +``` From 57ef4b14baed57015802cd9ed825e15a1d9f5eaf Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Fri, 24 Jul 2026 10:37:54 -0400 Subject: [PATCH 2/3] fix read for static nat --- cloudstack/resource_cloudstack_static_nat.go | 1 + 1 file changed, 1 insertion(+) diff --git a/cloudstack/resource_cloudstack_static_nat.go b/cloudstack/resource_cloudstack_static_nat.go index 36fef6c6..0e6e2a85 100644 --- a/cloudstack/resource_cloudstack_static_nat.go +++ b/cloudstack/resource_cloudstack_static_nat.go @@ -163,6 +163,7 @@ func resourceCloudStackStaticNATRead(d *schema.ResourceData, meta interface{}) e d.Set("virtual_machine_id", ip.Virtualmachineid) d.Set("vm_guest_ip", ip.Vmipaddress) + d.Set("ip_address_id", ip.Id) setValueOrID(d, "project", ip.Project, ip.Projectid) From 84f4c2fa80bf6f4fedade89625f780f930252e71 Mon Sep 17 00:00:00 2001 From: Pearl Dsilva Date: Fri, 24 Jul 2026 10:43:11 -0400 Subject: [PATCH 3/3] fix read for port forward --- cloudstack/resource_cloudstack_port_forward.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/cloudstack/resource_cloudstack_port_forward.go b/cloudstack/resource_cloudstack_port_forward.go index 661f8ebb..49768ffe 100644 --- a/cloudstack/resource_cloudstack_port_forward.go +++ b/cloudstack/resource_cloudstack_port_forward.go @@ -244,6 +244,7 @@ func resourceCloudStackPortForwardImport(d *schema.ResourceData, meta interface{ ipAddressID := s[len(s)-1] d.SetId(ipAddressID) + d.Set("ip_address_id", ipAddressID) // Make sure the IP address exists if _, count, err := cs.Address.GetPublicIpAddressByID( @@ -335,6 +336,8 @@ func resourceCloudStackPortForwardRead(d *schema.ResourceData, meta interface{}) return err } + d.Set("ip_address_id", d.Id()) + // Get all the forwards from the running environment p := cs.Firewall.NewListPortForwardingRulesParams() p.SetIpaddressid(d.Id())