Skip to content

Commit 5d0d647

Browse files
committed
feat: make cidr optional for L2 networks
- Add 'type' field to cloudstack_network resource schema - Make 'cidr' field conditionally required based on network type - L2 networks: cidr not required, IP parameters skipped - L3 networks: cidr required, maintains backward compatibility - Add CustomizeDiff validation for proper type/cidr combinations - Update parseCIDR function to handle L2 networks without cidr - Update resource creation logic to skip IP config for L2 networks - Update read function to detect and set network type - Add comprehensive test coverage for L2 networks - Update documentation with L2 and L3 usage examples Fixes #214
1 parent 3021ce2 commit 5d0d647

File tree

3 files changed

+143
-18
lines changed

3 files changed

+143
-18
lines changed

cloudstack/resource_cloudstack_network.go

Lines changed: 75 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
package cloudstack
2121

2222
import (
23+
"context"
2324
"fmt"
2425
"log"
2526
"net"
@@ -59,6 +60,7 @@ func resourceCloudStackNetwork() *schema.Resource {
5960
Importer: &schema.ResourceImporter{
6061
State: importStatePassthrough,
6162
},
63+
CustomizeDiff: resourceCloudStackNetworkCustomizeDiff,
6264

6365
Schema: map[string]*schema.Schema{
6466
"name": {
@@ -72,9 +74,23 @@ func resourceCloudStackNetwork() *schema.Resource {
7274
Computed: true,
7375
},
7476

77+
"type": {
78+
Type: schema.TypeString,
79+
Optional: true,
80+
Default: "L3",
81+
ForceNew: true,
82+
ValidateFunc: func(val interface{}, key string) (warns []string, errs []error) {
83+
v := val.(string)
84+
if v != "L2" && v != "L3" {
85+
errs = append(errs, fmt.Errorf("%q must be either 'L2' or 'L3', got: %s", key, v))
86+
}
87+
return
88+
},
89+
},
90+
7591
"cidr": {
7692
Type: schema.TypeString,
77-
Required: true,
93+
Optional: true,
7894
ForceNew: true,
7995
},
8096

@@ -158,6 +174,23 @@ func resourceCloudStackNetwork() *schema.Resource {
158174
}
159175
}
160176

177+
func resourceCloudStackNetworkCustomizeDiff(ctx context.Context, d *schema.ResourceDiff, meta interface{}) error {
178+
networkType := d.Get("type").(string)
179+
cidr := d.Get("cidr").(string)
180+
181+
// For L3 networks, cidr is required
182+
if networkType == "L3" && cidr == "" {
183+
return fmt.Errorf("cidr is required when type is L3")
184+
}
185+
186+
// For L2 networks, cidr should not be provided
187+
if networkType == "L2" && cidr != "" {
188+
return fmt.Errorf("cidr should not be provided when type is L2")
189+
}
190+
191+
return nil
192+
}
193+
161194
func resourceCloudStackNetworkCreate(d *schema.ResourceData, meta interface{}) error {
162195
cs := meta.(*cloudstack.CloudStackClient)
163196

@@ -190,23 +223,31 @@ func resourceCloudStackNetworkCreate(d *schema.ResourceData, meta interface{}) e
190223
return err
191224
}
192225

193-
m, err := parseCIDR(d, no.Specifyipranges)
194-
if err != nil {
195-
return err
196-
}
226+
// Check if this is an L2 network
227+
networkType := d.Get("type").(string)
228+
if networkType == "L2" {
229+
// For L2 networks, we don't set IP-related parameters
230+
// The network offering should handle VLAN-only configuration
231+
} else {
232+
// For L3 networks, parse CIDR and set IP parameters
233+
m, err := parseCIDR(d, no.Specifyipranges)
234+
if err != nil {
235+
return err
236+
}
197237

198-
// Set the needed IP config
199-
p.SetGateway(m["gateway"])
200-
p.SetNetmask(m["netmask"])
238+
// Set the needed IP config
239+
p.SetGateway(m["gateway"])
240+
p.SetNetmask(m["netmask"])
201241

202-
// Only set the start IP if we have one
203-
if startip, ok := m["startip"]; ok {
204-
p.SetStartip(startip)
205-
}
242+
// Only set the start IP if we have one
243+
if startip, ok := m["startip"]; ok {
244+
p.SetStartip(startip)
245+
}
206246

207-
// Only set the end IP if we have one
208-
if endip, ok := m["endip"]; ok {
209-
p.SetEndip(endip)
247+
// Only set the end IP if we have one
248+
if endip, ok := m["endip"]; ok {
249+
p.SetEndip(endip)
250+
}
210251
}
211252

212253
// Set the network domain if we have one
@@ -305,6 +346,13 @@ func resourceCloudStackNetworkRead(d *schema.ResourceData, meta interface{}) err
305346
d.Set("gateway", n.Gateway)
306347
d.Set("network_domain", n.Networkdomain)
307348
d.Set("vpc_id", n.Vpcid)
349+
350+
// Determine network type based on CIDR presence
351+
if n.Cidr == "" {
352+
d.Set("type", "L2")
353+
} else {
354+
d.Set("type", "L3")
355+
}
308356

309357
if n.Aclid == "" {
310358
n.Aclid = none
@@ -439,7 +487,19 @@ func resourceCloudStackNetworkDelete(d *schema.ResourceData, meta interface{}) e
439487
func parseCIDR(d *schema.ResourceData, specifyiprange bool) (map[string]string, error) {
440488
m := make(map[string]string, 4)
441489

490+
// Check if this is an L2 network
491+
networkType := d.Get("type").(string)
492+
if networkType == "L2" {
493+
// For L2 networks, we don't need CIDR parsing
494+
// Just return empty values for IP-related fields
495+
return m, nil
496+
}
497+
442498
cidr := d.Get("cidr").(string)
499+
if cidr == "" {
500+
return nil, fmt.Errorf("cidr is required for L3 networks")
501+
}
502+
443503
ip, ipnet, err := net.ParseCIDR(cidr)
444504
if err != nil {
445505
return nil, fmt.Errorf("Unable to parse cidr %s: %s", cidr, err)

cloudstack/resource_cloudstack_network_test.go

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,26 @@ func TestAccCloudStackNetwork_importProject(t *testing.T) {
165165
})
166166
}
167167

168+
func TestAccCloudStackNetwork_L2(t *testing.T) {
169+
var network cloudstack.Network
170+
171+
resource.Test(t, resource.TestCase{
172+
PreCheck: func() { testAccPreCheck(t) },
173+
Providers: testAccProviders,
174+
CheckDestroy: testAccCheckCloudStackNetworkDestroy,
175+
Steps: []resource.TestStep{
176+
{
177+
Config: testAccCloudStackNetwork_L2,
178+
Check: resource.ComposeTestCheckFunc(
179+
testAccCheckCloudStackNetworkExists(
180+
"cloudstack_network.foo", &network),
181+
testAccCheckCloudStackNetworkL2Attributes(&network),
182+
),
183+
},
184+
},
185+
})
186+
}
187+
168188
func testAccCheckCloudStackNetworkExists(
169189
n string, network *cloudstack.Network) resource.TestCheckFunc {
170190
return func(s *terraform.State) error {
@@ -244,6 +264,27 @@ func testAccCheckCloudStackNetworkVPCAttributes(
244264
}
245265
}
246266

267+
func testAccCheckCloudStackNetworkL2Attributes(
268+
network *cloudstack.Network) resource.TestCheckFunc {
269+
return func(s *terraform.State) error {
270+
271+
if network.Name != "terraform-l2-network" {
272+
return fmt.Errorf("Bad name: %s", network.Name)
273+
}
274+
275+
if network.Displaytext != "terraform-l2-network" {
276+
return fmt.Errorf("Bad display name: %s", network.Displaytext)
277+
}
278+
279+
// L2 networks should not have a CIDR
280+
if network.Cidr != "" {
281+
return fmt.Errorf("L2 network should not have CIDR, got: %s", network.Cidr)
282+
}
283+
284+
return nil
285+
}
286+
}
287+
247288
func testAccCheckCloudStackNetworkDestroy(s *terraform.State) error {
248289
cs := testAccProvider.Meta().(*cloudstack.CloudStackClient)
249290

@@ -377,3 +418,13 @@ resource "cloudstack_network" "foo" {
377418
acl_id = cloudstack_network_acl.bar.id
378419
zone = cloudstack_vpc.foo.zone
379420
}`
421+
422+
const testAccCloudStackNetwork_L2 = `
423+
resource "cloudstack_network" "foo" {
424+
name = "terraform-l2-network"
425+
display_text = "terraform-l2-network"
426+
type = "L2"
427+
network_offering = "DefaultSharedNetworkOffering"
428+
zone = "Sandbox-simulator"
429+
vlan = 100
430+
}`

website/docs/r/network.html.markdown

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,30 @@ Creates a network.
1212

1313
## Example Usage
1414

15-
Basic usage:
15+
Basic L3 network usage:
1616

1717
```hcl
1818
resource "cloudstack_network" "default" {
1919
name = "test-network"
20+
type = "L3"
2021
cidr = "10.0.0.0/16"
2122
network_offering = "Default Network"
2223
zone = "zone-1"
2324
}
2425
```
2526

27+
L2 VLAN-only network usage:
28+
29+
```hcl
30+
resource "cloudstack_network" "l2_network" {
31+
name = "l2-network"
32+
type = "L2"
33+
network_offering = "L2 Network Offering"
34+
zone = "zone-1"
35+
vlan = 100
36+
}
37+
```
38+
2639
## Argument Reference
2740

2841
The following arguments are supported:
@@ -31,8 +44,9 @@ The following arguments are supported:
3144

3245
* `display_text` - (Optional) The display text of the network.
3346

34-
* `cidr` - (Required) The CIDR block for the network. Changing this forces a new
35-
resource to be created.
47+
* `type` - (Optional) The type of network. Valid values are "L2" for VLAN-only networks and "L3" for IP-based networks. Defaults to "L3".
48+
49+
* `cidr` - (Optional) The CIDR block for the network. Required when `type` is "L3", not allowed when `type` is "L2". Changing this forces a new resource to be created.
3650

3751
* `gateway` - (Optional) Gateway that will be provided to the instances in this
3852
network. Defaults to the first usable IP in the range.

0 commit comments

Comments
 (0)