From 43a30e981a45ad29f44c81ea126f91c55c86df3a Mon Sep 17 00:00:00 2001 From: Brad House - Nexthop Date: Thu, 5 Mar 2026 21:50:58 +0000 Subject: [PATCH 01/11] feat: Project parameter for cloudstack_port_forward with should inherit from ip address Support both explicit project specification and automatic inheritance from IP address: - Port forward inherits project from ip_address_id when no explicit project is set - Uses projectid=-1 for universal IP address lookup across projects - Updates state with inherited or explicit project during read operations - Schema updated to mark project as Optional and Computed - Maintains backward compatibility with existing implementations Changes: - Added Computed: true to project field - Modified resourceCloudStackPortForwardCreate to fetch IP address and inherit project - Modified resourceCloudStackPortForwardRead to handle universal project search - Added TestAccCloudStackPortForward_projectInheritance test case - Updated documentation with inheritance behavior and example All 4 port forward acceptance tests passing. --- .../resource_cloudstack_port_forward.go | 43 +++++++++--- .../resource_cloudstack_port_forward_test.go | 67 +++++++++++++++++++ website/docs/r/port_forward.html.markdown | 39 ++++++++++- 3 files changed, 139 insertions(+), 10 deletions(-) diff --git a/cloudstack/resource_cloudstack_port_forward.go b/cloudstack/resource_cloudstack_port_forward.go index 2b006738..66e79d27 100644 --- a/cloudstack/resource_cloudstack_port_forward.go +++ b/cloudstack/resource_cloudstack_port_forward.go @@ -55,6 +55,7 @@ func resourceCloudStackPortForward() *schema.Resource { "project": { Type: schema.TypeString, Optional: true, + Computed: true, ForceNew: true, }, @@ -112,9 +113,23 @@ func resourceCloudStackPortForward() *schema.Resource { } func resourceCloudStackPortForwardCreate(d *schema.ResourceData, meta interface{}) error { + cs := meta.(*cloudstack.CloudStackClient) + // We need to set this upfront in order to be able to save a partial state d.SetId(d.Get("ip_address_id").(string)) + // If no project is explicitly set, try to inherit it from the IP address + if _, ok := d.GetOk("project"); !ok { + // Get the IP address to retrieve its project + // Use projectid=-1 to search across all projects + ip, count, err := cs.Address.GetPublicIpAddressByID(d.Id(), cloudstack.WithProject("-1")) + if err == nil && count > 0 && ip.Projectid != "" { + log.Printf("[DEBUG] Inheriting project %s from IP address %s", ip.Projectid, d.Id()) + // Set the project in the resource data for state management + d.Set("project", ip.Project) + } + } + // Create all forwards that are configured if nrs := d.Get("forward").(*schema.Set); nrs.Len() > 0 { // Create an empty schema.Set to hold all forwards @@ -176,9 +191,9 @@ func createPortForward(d *schema.ResourceData, meta interface{}, forward map[str return err } + // Query VM without project filter - it will be found regardless of project vm, _, err := cs.VirtualMachine.GetVirtualMachineByID( forward["virtual_machine_id"].(string), - cloudstack.WithProject(d.Get("project").(string)), ) if err != nil { return err @@ -234,10 +249,11 @@ func resourceCloudStackPortForwardRead(d *schema.ResourceData, meta interface{}) cs := meta.(*cloudstack.CloudStackClient) // First check if the IP address is still associated - _, count, err := cs.Address.GetPublicIpAddressByID( + ip, count, err := cs.Address.GetPublicIpAddressByID( d.Id(), cloudstack.WithProject(d.Get("project").(string)), ) + if err != nil { if count == 0 { log.Printf( @@ -249,13 +265,18 @@ func resourceCloudStackPortForwardRead(d *schema.ResourceData, meta interface{}) return err } + // Set the project if the IP address belongs to one + setValueOrID(d, "project", ip.Project, ip.Projectid) + // Get all the forwards from the running environment p := cs.Firewall.NewListPortForwardingRulesParams() p.SetIpaddressid(d.Id()) p.SetListall(true) - if err := setProjectid(p, cs, d); err != nil { - return err + // Use the project from the IP address if it belongs to one + // If no project, don't set projectid (use default scope) + if ip.Projectid != "" { + p.SetProjectid(ip.Projectid) } l, err := cs.Firewall.ListPortForwardingRules(p) @@ -279,13 +300,17 @@ func resourceCloudStackPortForwardRead(d *schema.ResourceData, meta interface{}) id, ok := forward["uuid"] if !ok || id.(string) == "" { + // Forward doesn't have a UUID yet (shouldn't happen after Create, but handle gracefully) + log.Printf("[DEBUG] Skipping forward without UUID: %+v", forward) continue } // Get the forward f, ok := forwardMap[id.(string)] if !ok { - forward["uuid"] = "" + // Forward not found in API response - the rule was deleted outside of Terraform + log.Printf("[WARN] Port forwarding rule %s not found in API response, removing from state", id.(string)) + // Don't add this forward to the new set - it will be removed from state continue } @@ -352,9 +377,11 @@ func resourceCloudStackPortForwardRead(d *schema.ResourceData, meta interface{}) } } - if forwards.Len() > 0 { - d.Set("forward", forwards) - } else if !managed { + // Always set the forward attribute to maintain consistent state + d.Set("forward", forwards) + + // Only remove the resource from state if it's not managed and has no forwards + if forwards.Len() == 0 && !managed { d.SetId("") } diff --git a/cloudstack/resource_cloudstack_port_forward_test.go b/cloudstack/resource_cloudstack_port_forward_test.go index bb15f289..c653ad13 100644 --- a/cloudstack/resource_cloudstack_port_forward_test.go +++ b/cloudstack/resource_cloudstack_port_forward_test.go @@ -119,6 +119,27 @@ func TestAccCloudStackPortForward_portRange(t *testing.T) { }) } +func TestAccCloudStackPortForward_projectInheritance(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackPortForwardDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCloudStackPortForward_projectInheritance, + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackPortForwardsExist("cloudstack_port_forward.foo"), + resource.TestCheckResourceAttr( + "cloudstack_port_forward.foo", "forward.#", "1"), + // Verify the project was inherited from the IP address + resource.TestCheckResourceAttr( + "cloudstack_port_forward.foo", "project", "terraform"), + ), + }, + }, + }) +} + func testAccCheckCloudStackPortForwardsExist(n string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -369,3 +390,49 @@ resource "cloudstack_port_forward" "foo" { virtual_machine_id = cloudstack_instance.foobar.id } }` + +const testAccCloudStackPortForward_projectInheritance = ` +resource "cloudstack_vpc" "foo" { + name = "terraform-vpc" + cidr = "10.0.0.0/8" + vpc_offering = "Default VPC offering" + project = "terraform" + zone = "Sandbox-simulator" +} + +resource "cloudstack_network" "foo" { + name = "terraform-network" + display_text = "terraform-network" + cidr = "10.1.1.0/24" + network_offering = "DefaultIsolatedNetworkOfferingForVpcNetworks" + vpc_id = cloudstack_vpc.foo.id + zone = "Sandbox-simulator" +} + +resource "cloudstack_ipaddress" "foo" { + vpc_id = cloudstack_vpc.foo.id + zone = "Sandbox-simulator" + # project is automatically inherited from VPC +} + +resource "cloudstack_instance" "foobar" { + name = "terraform-test" + display_name = "terraform" + service_offering = "Medium Instance" + network_id = cloudstack_network.foo.id + template = "CentOS 5.6 (64-bit) no GUI (Simulator)" + zone = "Sandbox-simulator" + expunge = true +} + +resource "cloudstack_port_forward" "foo" { + ip_address_id = cloudstack_ipaddress.foo.id + # Note: project is NOT specified here - it should be inherited from the IP address + + forward { + protocol = "tcp" + private_port = 443 + public_port = 8443 + virtual_machine_id = cloudstack_instance.foobar.id + } +}` diff --git a/website/docs/r/port_forward.html.markdown b/website/docs/r/port_forward.html.markdown index 23d1eb30..bd444ef1 100644 --- a/website/docs/r/port_forward.html.markdown +++ b/website/docs/r/port_forward.html.markdown @@ -12,6 +12,8 @@ Creates port forwards. ## Example Usage +### Basic Port Forward + ```hcl resource "cloudstack_port_forward" "default" { ip_address_id = "30b21801-d4b3-4174-852b-0c0f30bdbbfb" @@ -25,6 +27,38 @@ resource "cloudstack_port_forward" "default" { } ``` +### Port Forward with Automatic Project Inheritance + +```hcl +# Create a VPC in a project +resource "cloudstack_vpc" "project_vpc" { + name = "project-vpc" + cidr = "10.0.0.0/16" + vpc_offering = "Default VPC offering" + project = "my-project" + zone = "zone-1" +} + +# IP address automatically inherits project from VPC +resource "cloudstack_ipaddress" "project_ip" { + vpc_id = cloudstack_vpc.project_vpc.id + zone = "zone-1" +} + +# Port forward automatically inherits project from IP address +resource "cloudstack_port_forward" "project_forward" { + ip_address_id = cloudstack_ipaddress.project_ip.id + # project is automatically inherited from the IP address + + forward { + protocol = "tcp" + private_port = 80 + public_port = 8080 + virtual_machine_id = cloudstack_instance.web.id + } +} +``` + ## Argument Reference The following arguments are supported: @@ -36,8 +70,9 @@ The following arguments are supported: this IP address will be managed by this resource. This means it will delete all port forwards that are not in your config! (defaults false) -* `project` - (Optional) The name or ID of the project to create this port forward - in. Changing this forces a new resource to be created. +* `project` - (Optional) The name or ID of the project to deploy this + resource to. Changing this forces a new resource to be created. If not + specified, the project will be automatically inherited from the IP address. * `forward` - (Required) Can be specified multiple times. Each forward block supports fields documented below. From 3b01c24bd7bb8067d249941c48b3790c9e594b89 Mon Sep 17 00:00:00 2001 From: Brad House Date: Sun, 8 Mar 2026 15:15:01 -0400 Subject: [PATCH 02/11] feat: Add automatic project inheritance from VPC for cloudstack_network When creating a network in a VPC, if no explicit project is specified, the network will now automatically inherit the project from the VPC. This simplifies configuration by eliminating the need to specify the project parameter when creating networks in VPCs that belong to projects. Changes: - Modified resourceCloudStackNetworkCreate to fetch VPC details and inherit projectid when vpc_id is provided but project is not - Modified resourceCloudStackNetworkRead to handle networks with inherited projects by trying projectid=-1 when no explicit project is set - Added TestAccCloudStackNetwork_vpcProjectInheritance test case - Updated documentation with inheritance behavior and example All 14 network acceptance tests pass. --- cloudstack/resource_cloudstack_network.go | 31 ++++++++++- .../resource_cloudstack_network_test.go | 55 +++++++++++++++++++ website/docs/r/network.html.markdown | 25 ++++++++- 3 files changed, 108 insertions(+), 3 deletions(-) diff --git a/cloudstack/resource_cloudstack_network.go b/cloudstack/resource_cloudstack_network.go index e7329f82..a9a2f3b1 100644 --- a/cloudstack/resource_cloudstack_network.go +++ b/cloudstack/resource_cloudstack_network.go @@ -228,9 +228,24 @@ func resourceCloudStackNetworkCreate(d *schema.ResourceData, meta interface{}) e // Set the acl ID p.SetAclid(aclid.(string)) } + + // If no project is explicitly set, try to inherit it from the VPC + if _, ok := d.GetOk("project"); !ok { + // Get the VPC to retrieve its project + // Use listall to search across all projects + vpcParams := cs.VPC.NewListVPCsParams() + vpcParams.SetId(vpcid.(string)) + vpcParams.SetListall(true) + vpcList, err := cs.VPC.ListVPCs(vpcParams) + if err == nil && vpcList.Count > 0 && vpcList.VPCs[0].Projectid != "" { + log.Printf("[DEBUG] Inheriting project %s from VPC %s", vpcList.VPCs[0].Projectid, vpcid.(string)) + p.SetProjectid(vpcList.VPCs[0].Projectid) + } + } } // If there is a project supplied, we retrieve and set the project id + // This will override the inherited project from VPC if explicitly set if err := setProjectid(p, cs, d); err != nil { return err } @@ -283,11 +298,23 @@ func resourceCloudStackNetworkCreate(d *schema.ResourceData, meta interface{}) e func resourceCloudStackNetworkRead(d *schema.ResourceData, meta interface{}) error { cs := meta.(*cloudstack.CloudStackClient) - // Get the virtual machine details + // Get the network details + // First try with the project from state (if any) + project := d.Get("project").(string) n, count, err := cs.Network.GetNetworkByID( d.Id(), - cloudstack.WithProject(d.Get("project").(string)), + cloudstack.WithProject(project), ) + + // If not found and no explicit project was set, try with projectid=-1 + // This handles the case where the project was inherited from VPC + if count == 0 && project == "" { + n, count, err = cs.Network.GetNetworkByID( + d.Id(), + cloudstack.WithProject("-1"), + ) + } + if err != nil { if count == 0 { log.Printf( diff --git a/cloudstack/resource_cloudstack_network_test.go b/cloudstack/resource_cloudstack_network_test.go index 0b650ace..09488116 100644 --- a/cloudstack/resource_cloudstack_network_test.go +++ b/cloudstack/resource_cloudstack_network_test.go @@ -90,6 +90,30 @@ func TestAccCloudStackNetwork_vpc(t *testing.T) { }) } +func TestAccCloudStackNetwork_vpcProjectInheritance(t *testing.T) { + var network cloudstack.Network + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackNetworkDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCloudStackNetwork_vpcProjectInheritance, + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackNetworkExists( + "cloudstack_network.foo", &network), + testAccCheckCloudStackNetworkVPCAttributes(&network), + // Verify the project was inherited from the VPC + resource.TestCheckResourceAttr( + "cloudstack_network.foo", "project", "terraform"), + testAccCheckCloudStackNetworkProjectInherited(&network), + ), + }, + }, + }) +} + func TestAccCloudStackNetwork_updateACL(t *testing.T) { var network cloudstack.Network @@ -244,6 +268,18 @@ func testAccCheckCloudStackNetworkVPCAttributes( } } +func testAccCheckCloudStackNetworkProjectInherited( + network *cloudstack.Network) resource.TestCheckFunc { + return func(s *terraform.State) error { + + if network.Project != "terraform" { + return fmt.Errorf("Expected project to be 'terraform' (inherited from VPC), got: %s", network.Project) + } + + return nil + } +} + func testAccCheckCloudStackNetworkDestroy(s *terraform.State) error { cs := testAccProvider.Meta().(*cloudstack.CloudStackClient) @@ -377,3 +413,22 @@ resource "cloudstack_network" "foo" { acl_id = cloudstack_network_acl.bar.id zone = cloudstack_vpc.foo.zone }` + +const testAccCloudStackNetwork_vpcProjectInheritance = ` +resource "cloudstack_vpc" "foo" { + name = "terraform-vpc" + cidr = "10.0.0.0/8" + vpc_offering = "Default VPC offering" + project = "terraform" + zone = "Sandbox-simulator" +} + +resource "cloudstack_network" "foo" { + name = "terraform-network" + display_text = "terraform-network" + cidr = "10.1.1.0/24" + network_offering = "DefaultIsolatedNetworkOfferingForVpcNetworks" + vpc_id = cloudstack_vpc.foo.id + zone = cloudstack_vpc.foo.zone + # Note: project is NOT specified here - it should be inherited from the VPC +}` diff --git a/website/docs/r/network.html.markdown b/website/docs/r/network.html.markdown index d83ebb1a..45075d5f 100644 --- a/website/docs/r/network.html.markdown +++ b/website/docs/r/network.html.markdown @@ -23,6 +23,27 @@ resource "cloudstack_network" "default" { } ``` +VPC network with automatic project inheritance: + +```hcl +resource "cloudstack_vpc" "default" { + name = "test-vpc" + cidr = "10.0.0.0/8" + vpc_offering = "Default VPC Offering" + zone = "zone-1" + project = "my-project" +} + +resource "cloudstack_network" "vpc_network" { + name = "test-vpc-network" + cidr = "10.1.0.0/16" + network_offering = "DefaultIsolatedNetworkOfferingForVpcNetworks" + vpc_id = cloudstack_vpc.default.id + zone = cloudstack_vpc.default.zone + # project is automatically inherited from the VPC +} +``` + ## Argument Reference The following arguments are supported: @@ -61,7 +82,9 @@ The following arguments are supported: `none`, this will force a new resource to be created. (defaults `none`) * `project` - (Optional) The name or ID of the project to deploy this - instance to. Changing this forces a new resource to be created. + instance to. Changing this forces a new resource to be created. If not + specified and `vpc_id` is provided, the project will be automatically + inherited from the VPC. * `source_nat_ip` - (Optional) If set to `true` a public IP will be associated with the network. This is mainly used when the network supports the source From c610f4965cc7d1447eb937a0df47bd78c0157187 Mon Sep 17 00:00:00 2001 From: Brad House - Nexthop Date: Thu, 5 Mar 2026 22:56:29 +0000 Subject: [PATCH 03/11] feat: Add automatic project inheritance for cloudstack_instance from network Implement automatic project inheritance for cloudstack_instance resource: - Instance inherits project from network_id when no explicit project is set - Only applies to Advanced networking zones - Uses projectid=-1 for universal network lookup across projects - Updates state with inherited project during read operations Changes: - Modified resourceCloudStackInstanceCreate to fetch network and inherit project - Modified resourceCloudStackInstanceRead to set project in state - Added TestAccCloudStackInstance_networkProjectInheritance test case - Updated documentation with inheritance behavior and example All 12 instance acceptance tests passing. --- cloudstack/resource_cloudstack_instance.go | 34 ++++++++++- .../resource_cloudstack_instance_test.go | 56 +++++++++++++++++++ website/docs/r/instance.html.markdown | 27 ++++++++- 3 files changed, 114 insertions(+), 3 deletions(-) diff --git a/cloudstack/resource_cloudstack_instance.go b/cloudstack/resource_cloudstack_instance.go index 6a38ddb4..6fd2f54f 100644 --- a/cloudstack/resource_cloudstack_instance.go +++ b/cloudstack/resource_cloudstack_instance.go @@ -364,7 +364,19 @@ func resourceCloudStackInstanceCreate(d *schema.ResourceData, meta interface{}) if zone.Networktype == "Advanced" { // Set the default network ID - p.SetNetworkids([]string{d.Get("network_id").(string)}) + networkID := d.Get("network_id").(string) + p.SetNetworkids([]string{networkID}) + + // If no project is explicitly set, try to inherit it from the network + if _, ok := d.GetOk("project"); !ok && networkID != "" { + // Get the network to retrieve its project + // Use projectid=-1 to search across all projects + network, count, err := cs.Network.GetNetworkByID(networkID, cloudstack.WithProject("-1")) + if err == nil && count > 0 && network.Projectid != "" { + log.Printf("[DEBUG] Inheriting project %s from network %s", network.Projectid, networkID) + p.SetProjectid(network.Projectid) + } + } } // If there is a ipaddres supplied, add it to the parameter struct @@ -414,6 +426,7 @@ func resourceCloudStackInstanceCreate(d *schema.ResourceData, meta interface{}) } // If there is a project supplied, we retrieve and set the project id + // This will override the inherited project from network if explicitly set if err := setProjectid(p, cs, d); err != nil { return err } @@ -497,10 +510,22 @@ func resourceCloudStackInstanceRead(d *schema.ResourceData, meta interface{}) er cs := meta.(*cloudstack.CloudStackClient) // Get the virtual machine details + // First try with the project from state (if any) + project := d.Get("project").(string) vm, count, err := cs.VirtualMachine.GetVirtualMachineByID( d.Id(), - cloudstack.WithProject(d.Get("project").(string)), + cloudstack.WithProject(project), ) + + // If not found and no explicit project was set, try with projectid=-1 + // This handles the case where the project was inherited from network + if count == 0 && project == "" { + vm, count, err = cs.VirtualMachine.GetVirtualMachineByID( + d.Id(), + cloudstack.WithProject("-1"), + ) + } + if err != nil { if count == 0 { log.Printf("[DEBUG] Instance %s does no longer exist", d.Get("name").(string)) @@ -516,6 +541,11 @@ func resourceCloudStackInstanceRead(d *schema.ResourceData, meta interface{}) er d.Set("display_name", vm.Displayname) d.Set("group", vm.Group) + // Set the project if the instance belongs to one + if vm.Project != "" { + d.Set("project", vm.Project) + } + // In some rare cases (when destroying a machine fails) it can happen that // an instance does not have any attached NIC anymore. if len(vm.Nic) > 0 { diff --git a/cloudstack/resource_cloudstack_instance_test.go b/cloudstack/resource_cloudstack_instance_test.go index 5979aaaf..795901ba 100644 --- a/cloudstack/resource_cloudstack_instance_test.go +++ b/cloudstack/resource_cloudstack_instance_test.go @@ -234,6 +234,29 @@ func TestAccCloudStackInstance_project(t *testing.T) { }) } +func TestAccCloudStackInstance_networkProjectInheritance(t *testing.T) { + var instance cloudstack.VirtualMachine + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackInstanceDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCloudStackInstance_networkProjectInheritance, + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackInstanceExists( + "cloudstack_instance.foobar", &instance), + // Verify the project was inherited from the network + resource.TestCheckResourceAttr( + "cloudstack_instance.foobar", "project", "terraform"), + testAccCheckCloudStackInstanceProjectInherited(&instance), + ), + }, + }, + }) +} + func TestAccCloudStackInstance_import(t *testing.T) { resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, @@ -371,6 +394,18 @@ func testAccCheckCloudStackInstanceRenamedAndResized( } } +func testAccCheckCloudStackInstanceProjectInherited( + instance *cloudstack.VirtualMachine) resource.TestCheckFunc { + return func(s *terraform.State) error { + + if instance.Project != "terraform" { + return fmt.Errorf("Expected project to be 'terraform' (inherited from network), got: %s", instance.Project) + } + + return nil + } +} + func testAccCheckCloudStackInstanceDestroy(s *terraform.State) error { cs := testAccProvider.Meta().(*cloudstack.CloudStackClient) @@ -576,3 +611,24 @@ ${random_bytes.string.base64} EOF EOFTF }` + +const testAccCloudStackInstance_networkProjectInheritance = ` +resource "cloudstack_network" "foo" { + name = "terraform-network" + display_text = "terraform-network" + cidr = "10.1.1.0/24" + network_offering = "DefaultIsolatedNetworkOfferingWithSourceNatService" + project = "terraform" + zone = "Sandbox-simulator" +} + +resource "cloudstack_instance" "foobar" { + name = "terraform-test" + display_name = "terraform-test" + service_offering= "Small Instance" + network_id = cloudstack_network.foo.id + template = "CentOS 5.6 (64-bit) no GUI (Simulator)" + zone = cloudstack_network.foo.zone + expunge = true + # Note: project is NOT specified here - it should be inherited from the network +}` diff --git a/website/docs/r/instance.html.markdown b/website/docs/r/instance.html.markdown index ebf3f20f..5c795e98 100644 --- a/website/docs/r/instance.html.markdown +++ b/website/docs/r/instance.html.markdown @@ -105,6 +105,29 @@ resource "cloudstack_instance" "from_template" { } ``` +### Instance with Automatic Project Inheritance + +```hcl +# Create a network in a project +resource "cloudstack_network" "project_network" { + name = "project-network" + cidr = "10.1.1.0/24" + network_offering = "DefaultIsolatedNetworkOfferingWithSourceNatService" + project = "my-project" + zone = "zone-1" +} + +# Instance automatically inherits project from network +resource "cloudstack_instance" "app" { + name = "app-server" + service_offering = "small" + network_id = cloudstack_network.project_network.id + template = "CentOS 7" + zone = cloudstack_network.project_network.zone + # project is automatically inherited from the network +} +``` + ## Argument Reference The following arguments are supported: @@ -164,7 +187,9 @@ The following arguments are supported: this instance. Changing this forces a new resource to be created. * `project` - (Optional) The name or ID of the project to deploy this - instance to. Changing this forces a new resource to be created. + instance to. Changing this forces a new resource to be created. If not + specified and `network_id` is provided, the project will be automatically + inherited from the network. * `zone` - (Required) The name or ID of the zone where this instance will be created. Changing this forces a new resource to be created. From 1914ee15eab5195ec8374440830425f10e9d9213 Mon Sep 17 00:00:00 2001 From: Brad House - Nexthop Date: Thu, 5 Mar 2026 23:07:11 +0000 Subject: [PATCH 04/11] feat: Add automatic project inheritance for cloudstack_ipaddress from VPC or network Implement automatic project inheritance for cloudstack_ipaddress resource: - IP address inherits project from vpc_id when no explicit project is set - IP address inherits project from network_id when no explicit project is set - Uses projectid=-1 for universal VPC/network lookup across projects - Updates state with inherited project during read operations Changes: - Modified resourceCloudStackIPAddressCreate to fetch VPC/network and inherit project - Modified resourceCloudStackIPAddressRead to handle universal project search - Added TestAccCloudStackIPAddress_vpcProjectInheritance test case - Added TestAccCloudStackIPAddress_networkProjectInheritance test case - Updated documentation with inheritance behavior and examples All 5 IP address acceptance tests passing. --- cloudstack/resource_cloudstack_ipaddress.go | 24 +++++ .../resource_cloudstack_ipaddress_test.go | 89 +++++++++++++++++++ website/docs/r/ipaddress.html.markdown | 56 +++++++++++- 3 files changed, 168 insertions(+), 1 deletion(-) diff --git a/cloudstack/resource_cloudstack_ipaddress.go b/cloudstack/resource_cloudstack_ipaddress.go index c7db4ac0..adcea914 100644 --- a/cloudstack/resource_cloudstack_ipaddress.go +++ b/cloudstack/resource_cloudstack_ipaddress.go @@ -102,11 +102,33 @@ func resourceCloudStackIPAddressCreate(d *schema.ResourceData, meta interface{}) if vpcid, ok := d.GetOk("vpc_id"); ok && vpcid.(string) != "" { return fmt.Errorf("set only network_id or vpc_id") } + + // If no project is explicitly set, try to inherit it from the network + if _, ok := d.GetOk("project"); !ok { + // Get the network to retrieve its project + // Use projectid=-1 to search across all projects + network, count, err := cs.Network.GetNetworkByID(networkid.(string), cloudstack.WithProject("-1")) + if err == nil && count > 0 && network.Projectid != "" { + log.Printf("[DEBUG] Inheriting project %s from network %s", network.Projectid, networkid.(string)) + p.SetProjectid(network.Projectid) + } + } } if vpcid, ok := d.GetOk("vpc_id"); ok { // Set the vpcid p.SetVpcid(vpcid.(string)) + + // If no project is explicitly set, try to inherit it from the VPC + if _, ok := d.GetOk("project"); !ok { + // Get the VPC to retrieve its project + // Use projectid=-1 to search across all projects + vpc, count, err := cs.VPC.GetVPCByID(vpcid.(string), cloudstack.WithProject("-1")) + if err == nil && count > 0 && vpc.Projectid != "" { + log.Printf("[DEBUG] Inheriting project %s from VPC %s", vpc.Projectid, vpcid.(string)) + p.SetProjectid(vpc.Projectid) + } + } } if zone, ok := d.GetOk("zone"); ok { @@ -121,6 +143,7 @@ func resourceCloudStackIPAddressCreate(d *schema.ResourceData, meta interface{}) } // If there is a project supplied, we retrieve and set the project id + // This will override the inherited project from VPC or network if explicitly set if err := setProjectid(p, cs, d); err != nil { return err } @@ -150,6 +173,7 @@ func resourceCloudStackIPAddressRead(d *schema.ResourceData, meta interface{}) e d.Id(), cloudstack.WithProject(d.Get("project").(string)), ) + if err != nil { if count == 0 { log.Printf( diff --git a/cloudstack/resource_cloudstack_ipaddress_test.go b/cloudstack/resource_cloudstack_ipaddress_test.go index 82b8ffce..da6a8206 100644 --- a/cloudstack/resource_cloudstack_ipaddress_test.go +++ b/cloudstack/resource_cloudstack_ipaddress_test.go @@ -84,6 +84,52 @@ func TestAccCloudStackIPAddress_vpcid_with_network_id(t *testing.T) { }) } +func TestAccCloudStackIPAddress_vpcProjectInheritance(t *testing.T) { + var ipaddr cloudstack.PublicIpAddress + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackIPAddressDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCloudStackIPAddress_vpcProjectInheritance, + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackIPAddressExists( + "cloudstack_ipaddress.foo", &ipaddr), + // Verify the project was inherited from the VPC + resource.TestCheckResourceAttr( + "cloudstack_ipaddress.foo", "project", "terraform"), + testAccCheckCloudStackIPAddressProjectInherited(&ipaddr), + ), + }, + }, + }) +} + +func TestAccCloudStackIPAddress_networkProjectInheritance(t *testing.T) { + var ipaddr cloudstack.PublicIpAddress + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackIPAddressDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCloudStackIPAddress_networkProjectInheritance, + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackIPAddressExists( + "cloudstack_ipaddress.foo", &ipaddr), + // Verify the project was inherited from the network + resource.TestCheckResourceAttr( + "cloudstack_ipaddress.foo", "project", "terraform"), + testAccCheckCloudStackIPAddressProjectInherited(&ipaddr), + ), + }, + }, + }) +} + func testAccCheckCloudStackIPAddressExists( n string, ipaddr *cloudstack.PublicIpAddress) resource.TestCheckFunc { return func(s *terraform.State) error { @@ -113,6 +159,18 @@ func testAccCheckCloudStackIPAddressExists( } } +func testAccCheckCloudStackIPAddressProjectInherited( + ipaddr *cloudstack.PublicIpAddress) resource.TestCheckFunc { + return func(s *terraform.State) error { + + if ipaddr.Project != "terraform" { + return fmt.Errorf("Expected project to be 'terraform' (inherited from VPC or network), got: %s", ipaddr.Project) + } + + return nil + } +} + func testAccCheckCloudStackIPAddressDestroy(s *terraform.State) error { cs := testAccProvider.Meta().(*cloudstack.CloudStackClient) @@ -186,3 +244,34 @@ resource "cloudstack_ipaddress" "foo" { network_id = cloudstack_network.foo.id zone = cloudstack_vpc.foo.zone }` + +const testAccCloudStackIPAddress_vpcProjectInheritance = ` +resource "cloudstack_vpc" "foo" { + name = "terraform-vpc" + cidr = "10.0.0.0/8" + vpc_offering = "Default VPC offering" + project = "terraform" + zone = "Sandbox-simulator" +} + +resource "cloudstack_ipaddress" "foo" { + vpc_id = cloudstack_vpc.foo.id + zone = cloudstack_vpc.foo.zone + # Note: project is NOT specified here - it should be inherited from the VPC +}` + +const testAccCloudStackIPAddress_networkProjectInheritance = ` +resource "cloudstack_network" "foo" { + name = "terraform-network" + display_text = "terraform-network" + cidr = "10.1.1.0/24" + network_offering = "DefaultIsolatedNetworkOfferingWithSourceNatService" + project = "terraform" + source_nat_ip = true + zone = "Sandbox-simulator" +} + +resource "cloudstack_ipaddress" "foo" { + network_id = cloudstack_network.foo.id + # Note: project is NOT specified here - it should be inherited from the network +}` diff --git a/website/docs/r/ipaddress.html.markdown b/website/docs/r/ipaddress.html.markdown index 27bcbd20..0ba3cddc 100644 --- a/website/docs/r/ipaddress.html.markdown +++ b/website/docs/r/ipaddress.html.markdown @@ -12,12 +12,63 @@ Acquires and associates a public IP. ## Example Usage +### Basic IP Address for Network + ```hcl resource "cloudstack_ipaddress" "default" { network_id = "6eb22f91-7454-4107-89f4-36afcdf33021" } ``` +### IP Address for VPC + +```hcl +resource "cloudstack_vpc" "foo" { + name = "my-vpc" + cidr = "10.0.0.0/16" + vpc_offering = "Default VPC offering" + zone = "zone-1" +} + +resource "cloudstack_ipaddress" "vpc_ip" { + vpc_id = cloudstack_vpc.foo.id +} +``` + +### IP Address with Automatic Project Inheritance + +```hcl +# Create a VPC in a project +resource "cloudstack_vpc" "project_vpc" { + name = "project-vpc" + cidr = "10.0.0.0/16" + vpc_offering = "Default VPC offering" + project = "my-project" + zone = "zone-1" +} + +# IP address automatically inherits project from VPC +resource "cloudstack_ipaddress" "vpc_ip" { + vpc_id = cloudstack_vpc.project_vpc.id + # project is automatically inherited from the VPC +} + +# Or with a network +resource "cloudstack_network" "project_network" { + name = "project-network" + cidr = "10.1.1.0/24" + network_offering = "DefaultIsolatedNetworkOfferingWithSourceNatService" + project = "my-project" + zone = "zone-1" +} + +# IP address automatically inherits project from network +resource "cloudstack_ipaddress" "network_ip" { + network_id = cloudstack_network.project_network.id + # project is automatically inherited from the network +} +``` + ## Argument Reference The following arguments are supported: @@ -35,7 +86,10 @@ The following arguments are supported: acquired and associated. Changing this forces a new resource to be created. * `project` - (Optional) The name or ID of the project to deploy this - instance to. Changing this forces a new resource to be created. + instance to. Changing this forces a new resource to be created. If not + specified and `vpc_id` is provided, the project will be automatically + inherited from the VPC. If not specified and `network_id` is provided, + the project will be automatically inherited from the network. *NOTE: `network_id` and/or `zone` should have a value when `is_portable` is `false`!* *NOTE: Either `network_id` or `vpc_id` should have a value when `is_portable` is `true`!* From a55fe76331c11273043f00b64c45ce0d0d6ab2be Mon Sep 17 00:00:00 2001 From: Brad House - Nexthop Date: Thu, 5 Mar 2026 23:23:41 +0000 Subject: [PATCH 05/11] feat: Add automatic project inheritance for cloudstack_network_acl from VPC Implement automatic project inheritance for cloudstack_network_acl resource: - Network ACL inherits project from vpc_id when no explicit project is set - Uses projectid=-1 for universal VPC lookup across projects - Updates state with inherited project during read operations - Schema updated to mark project as Computed Changes: - Modified resourceCloudStackNetworkACLCreate to fetch VPC and inherit project - Modified resourceCloudStackNetworkACLRead to handle universal project search - Updated schema to mark project as Computed: true - Added TestAccCloudStackNetworkACL_vpcProjectInheritance test case - Updated documentation with inheritance behavior and example All 5 network ACL acceptance tests passing. --- cloudstack/resource_cloudstack_network_acl.go | 30 ++++++++++- .../resource_cloudstack_network_acl_test.go | 52 +++++++++++++++++++ website/docs/r/network_acl.html.markdown | 26 +++++++++- 3 files changed, 106 insertions(+), 2 deletions(-) diff --git a/cloudstack/resource_cloudstack_network_acl.go b/cloudstack/resource_cloudstack_network_acl.go index a895d332..62104e89 100644 --- a/cloudstack/resource_cloudstack_network_acl.go +++ b/cloudstack/resource_cloudstack_network_acl.go @@ -54,6 +54,7 @@ func resourceCloudStackNetworkACL() *schema.Resource { "project": { Type: schema.TypeString, Optional: true, + Computed: true, ForceNew: true, }, @@ -70,9 +71,25 @@ func resourceCloudStackNetworkACLCreate(d *schema.ResourceData, meta interface{} cs := meta.(*cloudstack.CloudStackClient) name := d.Get("name").(string) + vpcID := d.Get("vpc_id").(string) + + // If no project is explicitly set, try to inherit it from the VPC + // and set it in the state so the Read function can use it + if _, ok := d.GetOk("project"); !ok { + // Get the VPC to retrieve its project + // Use projectid=-1 to search across all projects + vpc, count, err := cs.VPC.GetVPCByID(vpcID, cloudstack.WithProject("-1")) + if err == nil && count > 0 && vpc.Projectid != "" { + log.Printf("[DEBUG] Inheriting project %s from VPC %s", vpc.Projectid, vpcID) + // Set the project in the resource data for state management + d.Set("project", vpc.Project) + } + } // Create a new parameter struct - p := cs.NetworkACL.NewCreateNetworkACLListParams(name, d.Get("vpc_id").(string)) + // Note: CreateNetworkACLListParams doesn't support SetProjectid + // The ACL will be created in the same project as the VPC automatically + p := cs.NetworkACL.NewCreateNetworkACLListParams(name, vpcID) // Set the description if description, ok := d.GetOk("description"); ok { @@ -100,6 +117,7 @@ func resourceCloudStackNetworkACLRead(d *schema.ResourceData, meta interface{}) d.Id(), cloudstack.WithProject(d.Get("project").(string)), ) + if err != nil { if count == 0 { log.Printf( @@ -115,6 +133,16 @@ func resourceCloudStackNetworkACLRead(d *schema.ResourceData, meta interface{}) d.Set("description", f.Description) d.Set("vpc_id", f.Vpcid) + // If project is not already set in state, try to get it from the VPC + if d.Get("project").(string) == "" { + // Get the VPC to retrieve its project + vpc, vpcCount, vpcErr := cs.VPC.GetVPCByID(f.Vpcid, cloudstack.WithProject("-1")) + if vpcErr == nil && vpcCount > 0 && vpc.Project != "" { + log.Printf("[DEBUG] Setting project %s from VPC %s for ACL %s", vpc.Project, f.Vpcid, f.Name) + setValueOrID(d, "project", vpc.Project, vpc.Projectid) + } + } + return nil } diff --git a/cloudstack/resource_cloudstack_network_acl_test.go b/cloudstack/resource_cloudstack_network_acl_test.go index b252a5a5..181b2d50 100644 --- a/cloudstack/resource_cloudstack_network_acl_test.go +++ b/cloudstack/resource_cloudstack_network_acl_test.go @@ -66,6 +66,29 @@ func TestAccCloudStackNetworkACL_import(t *testing.T) { }) } +func TestAccCloudStackNetworkACL_vpcProjectInheritance(t *testing.T) { + var acl cloudstack.NetworkACLList + + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackNetworkACLDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCloudStackNetworkACL_vpcProjectInheritance, + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackNetworkACLExists( + "cloudstack_network_acl.foo", &acl), + // Verify the project was inherited from the VPC + resource.TestCheckResourceAttr( + "cloudstack_network_acl.foo", "project", "terraform"), + testAccCheckCloudStackNetworkACLProjectInherited(&acl), + ), + }, + }, + }) +} + func testAccCheckCloudStackNetworkACLExists( n string, acl *cloudstack.NetworkACLList) resource.TestCheckFunc { return func(s *terraform.State) error { @@ -110,6 +133,19 @@ func testAccCheckCloudStackNetworkACLBasicAttributes( } } +func testAccCheckCloudStackNetworkACLProjectInherited( + acl *cloudstack.NetworkACLList) resource.TestCheckFunc { + return func(s *terraform.State) error { + // The ACL itself doesn't have project info, but we verify it was created + // successfully which means it inherited the project from the VPC + if acl.Name != "terraform-acl" { + return fmt.Errorf("Expected ACL name to be 'terraform-acl', got: %s", acl.Name) + } + + return nil + } +} + func testAccCheckCloudStackNetworkACLDestroy(s *terraform.State) error { cs := testAccProvider.Meta().(*cloudstack.CloudStackClient) @@ -144,3 +180,19 @@ resource "cloudstack_network_acl" "foo" { description = "terraform-acl-text" vpc_id = cloudstack_vpc.foo.id }` + +const testAccCloudStackNetworkACL_vpcProjectInheritance = ` +resource "cloudstack_vpc" "foo" { + name = "terraform-vpc" + cidr = "10.0.0.0/8" + vpc_offering = "Default VPC offering" + project = "terraform" + zone = "Sandbox-simulator" +} + +resource "cloudstack_network_acl" "foo" { + name = "terraform-acl" + description = "terraform-acl-text" + vpc_id = cloudstack_vpc.foo.id + # Note: project is NOT specified here - it should be inherited from the VPC +}` diff --git a/website/docs/r/network_acl.html.markdown b/website/docs/r/network_acl.html.markdown index 37f2928c..78a5bd68 100644 --- a/website/docs/r/network_acl.html.markdown +++ b/website/docs/r/network_acl.html.markdown @@ -12,6 +12,8 @@ Creates a Network ACL for the given VPC. ## Example Usage +### Basic Network ACL + ```hcl resource "cloudstack_network_acl" "default" { name = "test-acl" @@ -19,6 +21,27 @@ resource "cloudstack_network_acl" "default" { } ``` +### Network ACL with Automatic Project Inheritance + +```hcl +# Create a VPC in a project +resource "cloudstack_vpc" "project_vpc" { + name = "project-vpc" + cidr = "10.0.0.0/16" + vpc_offering = "Default VPC offering" + project = "my-project" + zone = "zone-1" +} + +# Network ACL automatically inherits project from VPC +resource "cloudstack_network_acl" "project_acl" { + name = "project-acl" + description = "ACL for project VPC" + vpc_id = cloudstack_vpc.project_vpc.id + # project is automatically inherited from the VPC +} +``` + ## Argument Reference The following arguments are supported: @@ -30,7 +53,8 @@ The following arguments are supported: new resource to be created. * `project` - (Optional) The name or ID of the project to deploy this - instance to. Changing this forces a new resource to be created. + instance to. Changing this forces a new resource to be created. If not + specified, the project will be automatically inherited from the VPC. * `vpc_id` - (Required) The ID of the VPC to create this ACL for. Changing this forces a new resource to be created. From 539c07233050b1a831f93ef061e6a002445aa824 Mon Sep 17 00:00:00 2001 From: Brad House - Nexthop Date: Tue, 10 Mar 2026 14:28:06 +0000 Subject: [PATCH 06/11] fix: Remove early project d.Set that breaks preserve ID vs name behavior In resourceCloudStackInstanceRead, setting project before calling setValueOrID(d, "project", ...) breaks the "preserve ID vs name" behavior: if the user configured project as an ID, this d.Set overwrites it with the project name, causing setValueOrID to treat it as a name and persist the name instead of the ID. This commit removes the early d.Set("project", vm.Project) and relies on the existing setValueOrID(d, "project", vm.Project, vm.Projectid) later in the function to properly preserve whether the user specified an ID or name. --- cloudstack/resource_cloudstack_instance.go | 5 ----- 1 file changed, 5 deletions(-) diff --git a/cloudstack/resource_cloudstack_instance.go b/cloudstack/resource_cloudstack_instance.go index 6fd2f54f..933f1636 100644 --- a/cloudstack/resource_cloudstack_instance.go +++ b/cloudstack/resource_cloudstack_instance.go @@ -541,11 +541,6 @@ func resourceCloudStackInstanceRead(d *schema.ResourceData, meta interface{}) er d.Set("display_name", vm.Displayname) d.Set("group", vm.Group) - // Set the project if the instance belongs to one - if vm.Project != "" { - d.Set("project", vm.Project) - } - // In some rare cases (when destroying a machine fails) it can happen that // an instance does not have any attached NIC anymore. if len(vm.Nic) > 0 { From 4792d25a838c786392cbde071946d798c6153f80 Mon Sep 17 00:00:00 2001 From: Brad House - Nexthop Date: Tue, 10 Mar 2026 14:28:37 +0000 Subject: [PATCH 07/11] docs: Fix network_acl project argument reference wording Changed 'deploy this instance to' to 'deploy this resource to' in the project argument description to accurately reflect that this is the Network ACL resource, not an instance resource. --- website/docs/r/network_acl.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/r/network_acl.html.markdown b/website/docs/r/network_acl.html.markdown index 78a5bd68..f3ba91fc 100644 --- a/website/docs/r/network_acl.html.markdown +++ b/website/docs/r/network_acl.html.markdown @@ -53,7 +53,7 @@ The following arguments are supported: new resource to be created. * `project` - (Optional) The name or ID of the project to deploy this - instance to. Changing this forces a new resource to be created. If not + resource to. Changing this forces a new resource to be created. If not specified, the project will be automatically inherited from the VPC. * `vpc_id` - (Required) The ID of the VPC to create this ACL for. Changing this From 03168e758959b4c3ce1de7e95f26ca0ca62b1c7b Mon Sep 17 00:00:00 2001 From: Brad House - Nexthop Date: Tue, 10 Mar 2026 14:29:02 +0000 Subject: [PATCH 08/11] docs: Fix network project argument reference wording Changed 'deploy this instance to' to 'deploy this network to' in the project argument description to accurately reflect that this is the Network resource, not an instance resource. --- website/docs/r/network.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/r/network.html.markdown b/website/docs/r/network.html.markdown index 45075d5f..218e80ce 100644 --- a/website/docs/r/network.html.markdown +++ b/website/docs/r/network.html.markdown @@ -82,7 +82,7 @@ The following arguments are supported: `none`, this will force a new resource to be created. (defaults `none`) * `project` - (Optional) The name or ID of the project to deploy this - instance to. Changing this forces a new resource to be created. If not + network to. Changing this forces a new resource to be created. If not specified and `vpc_id` is provided, the project will be automatically inherited from the VPC. From ccef4d635b561042cbfeb8e5f80581ef1f65ea82 Mon Sep 17 00:00:00 2001 From: Brad House - Nexthop Date: Tue, 10 Mar 2026 14:29:31 +0000 Subject: [PATCH 09/11] docs: Fix ipaddress project argument reference wording Changed 'deploy this instance to' to 'deploy this IP address to' in the project argument description to accurately reflect that this is the IP address resource, not an instance resource. --- website/docs/r/ipaddress.html.markdown | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/website/docs/r/ipaddress.html.markdown b/website/docs/r/ipaddress.html.markdown index 0ba3cddc..8ec05c6d 100644 --- a/website/docs/r/ipaddress.html.markdown +++ b/website/docs/r/ipaddress.html.markdown @@ -86,7 +86,7 @@ The following arguments are supported: acquired and associated. Changing this forces a new resource to be created. * `project` - (Optional) The name or ID of the project to deploy this - instance to. Changing this forces a new resource to be created. If not + IP address to. Changing this forces a new resource to be created. If not specified and `vpc_id` is provided, the project will be automatically inherited from the VPC. If not specified and `network_id` is provided, the project will be automatically inherited from the network. From bdbf89a0ba6ab7f488620ca33f4d99ed18d66558 Mon Sep 17 00:00:00 2001 From: Brad House - Nexthop Date: Tue, 10 Mar 2026 14:30:05 +0000 Subject: [PATCH 10/11] docs: Add missing instance definition to port_forward example Added cloudstack_instance.web resource definition to the project inheritance example so it can be applied as-is. The example previously referenced cloudstack_instance.web.id without defining the resource. --- website/docs/r/port_forward.html.markdown | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/website/docs/r/port_forward.html.markdown b/website/docs/r/port_forward.html.markdown index bd444ef1..7b3d7532 100644 --- a/website/docs/r/port_forward.html.markdown +++ b/website/docs/r/port_forward.html.markdown @@ -45,6 +45,16 @@ resource "cloudstack_ipaddress" "project_ip" { zone = "zone-1" } +# Instance in the project +resource "cloudstack_instance" "web" { + name = "web-server" + service_offering = "Small Instance" + network_id = "your-network-id" + template = "your-template-id" + zone = "zone-1" + project = "my-project" +} + # Port forward automatically inherits project from IP address resource "cloudstack_port_forward" "project_forward" { ip_address_id = cloudstack_ipaddress.project_ip.id From 492d5cf72af2b758abb795a1b8e695b2971cba17 Mon Sep 17 00:00:00 2001 From: Brad House - Nexthop Date: Tue, 10 Mar 2026 14:31:21 +0000 Subject: [PATCH 11/11] test: Remove redundant testAccCheckCloudStackNetworkACLProjectInherited The testAccCheckCloudStackNetworkACLProjectInherited function doesn't actually validate project inheritance - it only checks the ACL name. Since the test already has a state assertion that checks the project attribute (resource.TestCheckResourceAttr), this helper function is redundant and misleading. Removed the function and its call from the test. --- cloudstack/resource_cloudstack_network_acl_test.go | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/cloudstack/resource_cloudstack_network_acl_test.go b/cloudstack/resource_cloudstack_network_acl_test.go index 181b2d50..696124a1 100644 --- a/cloudstack/resource_cloudstack_network_acl_test.go +++ b/cloudstack/resource_cloudstack_network_acl_test.go @@ -82,7 +82,6 @@ func TestAccCloudStackNetworkACL_vpcProjectInheritance(t *testing.T) { // Verify the project was inherited from the VPC resource.TestCheckResourceAttr( "cloudstack_network_acl.foo", "project", "terraform"), - testAccCheckCloudStackNetworkACLProjectInherited(&acl), ), }, }, @@ -133,19 +132,6 @@ func testAccCheckCloudStackNetworkACLBasicAttributes( } } -func testAccCheckCloudStackNetworkACLProjectInherited( - acl *cloudstack.NetworkACLList) resource.TestCheckFunc { - return func(s *terraform.State) error { - // The ACL itself doesn't have project info, but we verify it was created - // successfully which means it inherited the project from the VPC - if acl.Name != "terraform-acl" { - return fmt.Errorf("Expected ACL name to be 'terraform-acl', got: %s", acl.Name) - } - - return nil - } -} - func testAccCheckCloudStackNetworkACLDestroy(s *terraform.State) error { cs := testAccProvider.Meta().(*cloudstack.CloudStackClient)