You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@cloudstack.apache.org by su...@apache.org on 2026/08/18 12:03:29 UTC
(cloudstack-terraform-provider) branch main updated: Add terraform import support for several cloudstack resources (#305)
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 ae84c54 Add terraform import support for several cloudstack resources (#305)
ae84c54 is described below
commit ae84c54d1d7ed9b3a78b83ee7bc913cad8a1df3d
Author: Pearl Dsilva <pe...@gmail.com>
AuthorDate: Tue Aug 18 08:03:24 2026 -0400
Add terraform import support for several cloudstack resources (#305)
---
cloudstack/resource_cloudstack_autoscale_policy.go | 3 +
.../resource_cloudstack_autoscale_vm_group.go | 3 +
.../resource_cloudstack_autoscale_vm_profile.go | 3 +
...esource_cloudstack_autoscale_vm_profile_test.go | 21 +++++
cloudstack/resource_cloudstack_condition.go | 3 +
cloudstack/resource_cloudstack_counter.go | 3 +
cloudstack/resource_cloudstack_host.go | 22 +++--
cloudstack/resource_cloudstack_host_test.go | 26 ++++++
cloudstack/resource_cloudstack_ipaddress.go | 44 ++++++++++
cloudstack/resource_cloudstack_ipaddress_test.go | 19 +++++
.../resource_cloudstack_loadbalancer_rule.go | 3 +
.../resource_cloudstack_loadbalancer_rule_test.go | 21 +++++
cloudstack/resource_cloudstack_network_offering.go | 20 +++++
cloudstack/resource_cloudstack_port_forward.go | 93 ++++++++++++++++++++++
.../resource_cloudstack_port_forward_test.go | 23 ++++++
cloudstack/resource_cloudstack_service_offering.go | 20 +++++
.../resource_cloudstack_service_offering_test.go | 18 +++++
cloudstack/resource_cloudstack_static_nat.go | 4 +
cloudstack/resource_cloudstack_static_nat_test.go | 19 +++++
cloudstack/resource_cloudstack_vpn_connection.go | 3 +
.../resource_cloudstack_vpn_connection_test.go | 19 +++++
website/docs/r/autoscale_policy.html.markdown | 8 ++
website/docs/r/autoscale_vm_profile.html.markdown | 11 +++
website/docs/r/condition.html.markdown | 8 ++
website/docs/r/counter.html.markdown | 8 ++
website/docs/r/ipaddress.html.markdown | 15 ++++
website/docs/r/loadbalancer_rule.html.markdown | 18 +++++
website/docs/r/network_offering.html.markdown | 4 +
website/docs/r/port_forward.html.markdown | 20 +++++
website/docs/r/service_offering.html.markdown | 4 +
website/docs/r/static_nat.html.markdown | 15 ++++
website/docs/r/vpn_connection.html.markdown | 8 ++
32 files changed, 503 insertions(+), 6 deletions(-)
diff --git a/cloudstack/resource_cloudstack_autoscale_policy.go b/cloudstack/resource_cloudstack_autoscale_policy.go
index fe390b5..8de316e 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 8d0eb7e..d282032 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 0032b92..0bc71ae 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 f417f45..97bb5b6 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 cd74e5c..be734ed 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 ddd7e9e..ca78047 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 3e88d72..5f65639 100644
--- a/cloudstack/resource_cloudstack_host.go
+++ b/cloudstack/resource_cloudstack_host.go
@@ -37,6 +37,15 @@ func resourceCloudStackHost() *schema.Resource {
Update: resourceCloudStackHostUpdate,
Create: resourceCloudStackHostCreate,
Delete: resourceCloudStackHostDelete,
+ // NOTE: CloudStack's listHosts API never returns url, username, or
+ // password, so Read cannot repopulate them after import. url is also
+ // ForceNew, so leaving it unset in an imported config will plan a
+ // destroy/recreate of the host. After importing, set these fields
+ // explicitly in config (or use lifecycle.ignore_changes) to avoid
+ // unexpected replacement on the next apply.
+ Importer: &schema.ResourceImporter{
+ State: schema.ImportStatePassthrough,
+ },
Schema: map[string]*schema.Schema{
"hypervisor": {
Type: schema.TypeString,
@@ -224,12 +233,13 @@ func resourceCloudStackHostRead(d *schema.ResourceData, meta interface{}) error
d.SetId(h.Id)
fields := map[string]interface{}{
- "hypervisor": h.Hypervisor,
- "pod_id": h.Podid,
- "zone_id": h.Zoneid,
- "state": h.State,
- "resource_state": h.Resourcestate,
- "name": h.Name,
+ "hypervisor": h.Hypervisor,
+ "pod_id": h.Podid,
+ "zone_id": h.Zoneid,
+ "state": h.State,
+ "resource_state": h.Resourcestate,
+ "allocation_state": h.Resourcestate,
+ "name": h.Name,
}
for k, v := range fields {
diff --git a/cloudstack/resource_cloudstack_host_test.go b/cloudstack/resource_cloudstack_host_test.go
index da91b8a..25b5365 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 28663e2..1af251f 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: resourceCloudStackIPAddressImport,
+ },
Schema: map[string]*schema.Schema{
"is_portable": {
@@ -171,6 +174,41 @@ func resourceCloudStackIPAddressCreate(d *schema.ResourceData, meta interface{})
return resourceCloudStackIPAddressRead(d, meta)
}
+func resourceCloudStackIPAddressImport(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)
+
+ ip, count, err := cs.Address.GetPublicIpAddressByID(
+ ipAddressID,
+ cloudstack.WithProject(d.Get("project").(string)),
+ )
+ if err != nil {
+ if count == 0 {
+ return nil, fmt.Errorf("IP address with ID %s does not exist", ipAddressID)
+ }
+ return nil, err
+ }
+
+ // Seed whichever of network_id/vpc_id actually applies before Read runs:
+ // Read only refreshes these when already present in state, so import
+ // needs to set the right one here first.
+ if ip.Vpcid != "" {
+ d.Set("vpc_id", ip.Vpcid)
+ } else if ip.Associatednetworkid != "" {
+ d.Set("network_id", ip.Associatednetworkid)
+ }
+
+ return []*schema.ResourceData{d}, nil
+}
+
func resourceCloudStackIPAddressRead(d *schema.ResourceData, meta interface{}) error {
cs := meta.(*cloudstack.CloudStackClient)
@@ -208,6 +246,12 @@ func resourceCloudStackIPAddressRead(d *schema.ResourceData, meta interface{}) e
// Updated the IP address
d.Set("ip_address", ip.Ipaddress)
+ // Only refresh network_id/vpc_id if already present in state: a plain
+ // zone-scoped IP (neither set) can still come back from the API with an
+ // associated network under the hood, and syncing that into an Optional,
+ // non-Computed, ForceNew field would create a permanent diff. The
+ // importer is responsible for seeding whichever one applies before this
+ // Read runs.
if _, ok := d.GetOk("network_id"); ok {
d.Set("network_id", ip.Associatednetworkid)
}
diff --git a/cloudstack/resource_cloudstack_ipaddress_test.go b/cloudstack/resource_cloudstack_ipaddress_test.go
index 26606ee..51faf18 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 6ebf52b..1c53f17 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 8a9c792..f251152 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 d6adf4b..f260e63 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 358b8a6..be7e1cc 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": {
@@ -245,6 +248,94 @@ 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)
+ d.Set("ip_address_id", 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
+ }
+
+ // Leave vm_guest_ip unset here, mirroring Read: it's only meant to
+ // reflect a value the user explicitly configured, not whatever
+ // CloudStack auto-selected. Populating it from the API on import
+ // would bake the auto-selected IP into the set element's hash,
+ // permanently diverging from a config that leaves it unset.
+ forward := map[string]interface{}{
+ "protocol": f.Protocol,
+ "private_port": privPort,
+ "public_port": pubPort,
+ "virtual_machine_id": f.Virtualmachineid,
+ "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)
@@ -276,6 +367,8 @@ func resourceCloudStackPortForwardRead(d *schema.ResourceData, meta interface{})
return err
}
+ d.Set("ip_address_id", d.Id())
+
// Set the project if the IP address belongs to one
setValueOrID(d, "project", ip.Project, ip.Projectid)
diff --git a/cloudstack/resource_cloudstack_port_forward_test.go b/cloudstack/resource_cloudstack_port_forward_test.go
index c653ad1..e770425 100644
--- a/cloudstack/resource_cloudstack_port_forward_test.go
+++ b/cloudstack/resource_cloudstack_port_forward_test.go
@@ -57,6 +57,29 @@ 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 to bring all existing forwards for the
+ // IP address under this resource's control; the applied config leaves
+ // it at its default (false), so we ignore it for verification.
+ 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 323e3b8..7c427d7 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,
@@ -248,6 +251,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 c403428..9bb34f3 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 2b2c402..0e6e2a8 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": {
@@ -160,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)
diff --git a/cloudstack/resource_cloudstack_static_nat_test.go b/cloudstack/resource_cloudstack_static_nat_test.go
index eebebcb..8004718 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 a493e16..d1bf4d8 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 55d42a7..1eef989 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 98da742..3abe3c2 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 4213ccc..2a55b74 100644
--- a/website/docs/r/autoscale_vm_profile.html.markdown
+++ b/website/docs/r/autoscale_vm_profile.html.markdown
@@ -74,3 +74,14 @@ 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
+```
+
+*NOTE: Import acceptance test coverage is currently skipped due to an upstream
+cloudstack-go issue.*
diff --git a/website/docs/r/condition.html.markdown b/website/docs/r/condition.html.markdown
index 68f80d6..23956a3 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 9699df9..0dada3f 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 2a059eb..19c1898 100644
--- a/website/docs/r/ipaddress.html.markdown
+++ b/website/docs/r/ipaddress.html.markdown
@@ -105,3 +105,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 `<IPADDRESSID>` 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 890d044..00cd968 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 `<LOADBALANCERRULEID>` 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/network_offering.html.markdown b/website/docs/r/network_offering.html.markdown
index 8449299..e302b41 100644
--- a/website/docs/r/network_offering.html.markdown
+++ b/website/docs/r/network_offering.html.markdown
@@ -78,3 +78,7 @@ Network offerings can be imported; use `<NETWORKOFFERINGID>` as the import ID. F
```shell
$ terraform import cloudstack_network_offering.example <NETWORKOFFERINGID>
```
+
+*NOTE: The importer looks up the network offering by ID and resolves the
+required `name` attribute from it, so it does not need to be set in the
+configuration beforehand.*
diff --git a/website/docs/r/port_forward.html.markdown b/website/docs/r/port_forward.html.markdown
index 7b3d753..273cb18 100644
--- a/website/docs/r/port_forward.html.markdown
+++ b/website/docs/r/port_forward.html.markdown
@@ -115,3 +115,23 @@ 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 `<IPADDRESSID>` 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. Review and copy the imported rules into your config
+before running `terraform apply`, otherwise missing rules may be deleted if
+`managed` is `true`.*
diff --git a/website/docs/r/service_offering.html.markdown b/website/docs/r/service_offering.html.markdown
index 57cfe84..6a5754f 100644
--- a/website/docs/r/service_offering.html.markdown
+++ b/website/docs/r/service_offering.html.markdown
@@ -105,3 +105,7 @@ Service offerings can be imported; use `<SERVICEOFFERINGID>` as the import ID. F
```shell
$ terraform import cloudstack_service_offering.example <SERVICEOFFERINGID>
```
+
+*NOTE: The importer looks up the service offering by ID and resolves the
+required `name` attribute from it, so it does not need to be set in the
+configuration beforehand.*
diff --git a/website/docs/r/static_nat.html.markdown b/website/docs/r/static_nat.html.markdown
index 7484782..f62bf94 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 `<IPADDRESSID>` 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 7a2f701..56896ac 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
+```