Copilot commented on code in PR #305:
URL: 
https://github.com/apache/cloudstack-terraform-provider/pull/305#discussion_r3642585283


##########
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 `<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, and `managed` is set to `true` so unrelated rules
+created outside of this resource aren't dropped on the next apply.*

Review Comment:
   The new Import note says setting `managed` to `true` prevents unrelated 
rules from being dropped, but `managed` is documented earlier as deleting any 
port forwards not in config. This note should clarify that users must ensure 
their config matches the imported `forward` set (or keep `managed` disabled) to 
avoid accidental deletion.



##########
cloudstack/resource_cloudstack_port_forward.go:
##########
@@ -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

Review Comment:
   The importer unconditionally sets `managed = true`. Given `managed` is 
documented as deleting any port forwards not present in config, importing into 
a partial/stub config can lead to unintended rule deletions on the next apply. 
Consider leaving `managed` at its default (false) or documenting very 
explicitly that import requires users to adopt the full imported `forward` set 
before applying.



##########
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"},

Review Comment:
   This comment is misleading: `managed=true` means the resource will delete 
port forwards not in config (per the resource docs). The ignore is fine for the 
test, but the comment should explain that the importer sets `managed=true` and 
the test config leaves it at default, hence the ignore.



##########
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,
+               },

Review Comment:
   Import support was added for service offerings, but this PR doesn't include 
a corresponding website docs update describing the import ID format (service 
offering ID) and that the importer resolves the required `name` from the ID. 
Without docs, users won't discover or use this feature correctly.



##########
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,
+               },

Review Comment:
   Host import is enabled via passthrough, but several required arguments 
(e.g., `url`, `username`, `password`, and provider-local timeouts/flags) cannot 
be read back from CloudStack and thus won't be populated on import/Read. Users 
are likely to see perpetual diffs or forced replacement unless they use 
`lifecycle.ignore_changes` for these fields. Consider documenting these import 
caveats (and/or switching to a custom importer/schema approach) so imports are 
not misleading.



##########
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,
+               },

Review Comment:
   Import support was added for network offerings, but this PR doesn't include 
a corresponding website docs update describing the import ID format (network 
offering ID) and that the importer resolves the required `name` from the ID. 
This should be documented alongside the resource.



##########
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 
6eb22f91-7454-4107-89f4-36afcdf33021
+```

Review Comment:
   This doc advertises import support, but the corresponding acceptance test is 
currently skipped due to a cloudstack-go issue. Adding a brief note here would 
set expectations and reduce surprise if import behaves unexpectedly.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to