This is an automated email from the ASF dual-hosted git repository. Pearl1594 pushed a commit to branch support-internal-lb in repository https://gitbox.apache.org/repos/asf/cloudstack-terraform-provider.git
commit 93148a011712517785c5953461168124f4e0bfcd Author: Pearl Dsilva <[email protected]> AuthorDate: Fri Aug 14 14:06:41 2026 -0400 Add support for internal LB --- .../resource_cloudstack_loadbalancer_rule.go | 29 ++++++++-- .../resource_cloudstack_loadbalancer_rule_test.go | 66 ++++++++++++++++++++++ 2 files changed, 91 insertions(+), 4 deletions(-) diff --git a/cloudstack/resource_cloudstack_loadbalancer_rule.go b/cloudstack/resource_cloudstack_loadbalancer_rule.go index 6ebf52b..b15200e 100644 --- a/cloudstack/resource_cloudstack_loadbalancer_rule.go +++ b/cloudstack/resource_cloudstack_loadbalancer_rule.go @@ -50,9 +50,14 @@ func resourceCloudStackLoadBalancerRule() *schema.Resource { Computed: true, }, + // Optional, not Required: CloudStack's own createLoadBalancerRule + // marks publicipid as optional -- an internal LB in a VPC (no + // public IP at all, just network_id) is a real, supported case. + // See verifyLoadBalancerRule, which enforces that at least one of + // ip_address_id/network_id is set instead. "ip_address_id": { Type: schema.TypeString, - Required: true, + Optional: true, ForceNew: true, }, @@ -163,8 +168,12 @@ func resourceCloudStackLoadBalancerRuleCreate(d *schema.ResourceData, meta inter p.SetCidrlist(cidrList) } - // Set the ipaddress id - p.SetPublicipid(d.Get("ip_address_id").(string)) + // Set the ipaddress id, when given -- omitted entirely for an internal + // LB (network_id-only, no public IP), matching real CloudStack's own + // optional publicipid semantics. + if ipAddressID, ok := d.GetOk("ip_address_id"); ok { + p.SetPublicipid(ipAddressID.(string)) + } // Create the load balancer rule r, err := cs.LoadBalancer.CreateLoadBalancerRule(p) @@ -230,7 +239,13 @@ func resourceCloudStackLoadBalancerRuleRead(d *schema.ResourceData, meta interfa } d.Set("name", lb.Name) - d.Set("ip_address_id", lb.Publicipid) + // Only set ip_address_id if the user specified it, mirroring network_id's + // own guard below -- an internal LB's lb.Publicipid comes back empty + // from CloudStack, and setting that explicitly would fight the schema's + // Optional (no Computed) declaration. + if _, ok := d.GetOk("ip_address_id"); ok { + d.Set("ip_address_id", lb.Publicipid) + } d.Set("algorithm", lb.Algorithm) d.Set("public_port", public_port) d.Set("private_port", private_port) @@ -534,6 +549,12 @@ func resourceCloudStackLoadBalancerRuleDelete(d *schema.ResourceData, meta inter } func verifyLoadBalancerRule(d *schema.ResourceData) error { + _, hasIP := d.GetOk("ip_address_id") + _, hasNetwork := d.GetOk("network_id") + if !hasIP && !hasNetwork { + return fmt.Errorf("at least one of ip_address_id or network_id must be set") + } + if protocol, ok := d.GetOk("protocol"); ok { protocol := protocol.(string) diff --git a/cloudstack/resource_cloudstack_loadbalancer_rule_test.go b/cloudstack/resource_cloudstack_loadbalancer_rule_test.go index 8a9c792..ab24f60 100644 --- a/cloudstack/resource_cloudstack_loadbalancer_rule_test.go +++ b/cloudstack/resource_cloudstack_loadbalancer_rule_test.go @@ -210,6 +210,36 @@ func TestAccCloudStackLoadBalancerRule_vpcUpdate(t *testing.T) { }) } +// TestAccCloudStackLoadBalancerRule_internal exercises a pure internal LB: +// network_id set, ip_address_id omitted entirely -- no public IP at all. +// Real CloudStack's createLoadBalancerRule marks publicipid optional for +// exactly this VPC-internal case; before this fix the schema's +// Required:true on ip_address_id made it impossible to even plan such a +// config, regardless of what the real API allowed. +func TestAccCloudStackLoadBalancerRule_internal(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccCheckCloudStackLoadBalancerRuleDestroy, + Steps: []resource.TestStep{ + { + Config: testAccCloudStackLoadBalancerRule_internal, + Check: resource.ComposeTestCheckFunc( + testAccCheckCloudStackLoadBalancerRuleExist("cloudstack_loadbalancer_rule.foo", nil), + resource.TestCheckResourceAttr( + "cloudstack_loadbalancer_rule.foo", "name", "terraform-ilb"), + resource.TestCheckResourceAttr( + "cloudstack_loadbalancer_rule.foo", "ip_address_id", ""), + resource.TestCheckResourceAttr( + "cloudstack_loadbalancer_rule.foo", "public_port", "8080"), + resource.TestCheckResourceAttr( + "cloudstack_loadbalancer_rule.foo", "private_port", "8080"), + ), + }, + }, + }) +} + func testAccCheckCloudStackLoadBalancerRuleExist(n string, id *string) resource.TestCheckFunc { return func(s *terraform.State) error { rs, ok := s.RootModule().Resources[n] @@ -466,3 +496,39 @@ resource "cloudstack_loadbalancer_rule" "foo" { member_ids = [cloudstack_instance.foobar1.id, cloudstack_instance.foobar2.id] cidrlist = ["20.0.0.0/8"] }` + +const testAccCloudStackLoadBalancerRule_internal = ` +resource "cloudstack_vpc" "foo" { + name = "terraform-vpc" + cidr = "10.0.0.0/8" + vpc_offering = "Default VPC offering" + 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 +} + +resource "cloudstack_instance" "foobar1" { + name = "terraform-server1" + display_name = "terraform" + 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 +} + +resource "cloudstack_loadbalancer_rule" "foo" { + name = "terraform-ilb" + algorithm = "roundrobin" + network_id = cloudstack_network.foo.id + public_port = 8080 + private_port = 8080 + member_ids = [cloudstack_instance.foobar1.id] +}`
