Skip to content

Commit c7ed274

Browse files
authored
Handle network prefixes correctly (#753)
* wip * fix: corrected testcase * fix: change prefix to workaround bug in current environment * fix: made acceptance test more robust for randomized nameserver order * fix: updated documentation * fix: linter issue * fix: acceptance test still relied on a fixed order of nameservers * fix: fixed import acceptance testcase
1 parent 6d49b2f commit c7ed274

6 files changed

Lines changed: 280 additions & 68 deletions

File tree

docs/data-sources/network.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,12 @@ data "stackit_network" "example" {
3232
- `id` (String) Terraform's internal resource ID. It is structured as "`project_id`,`network_id`".
3333
- `ipv4_gateway` (String) The IPv4 gateway of a network. If not specified, the first IP of the network will be assigned as the gateway.
3434
- `ipv4_nameservers` (List of String) The IPv4 nameservers of the network.
35-
- `ipv4_prefix` (String) The IPv4 prefix of the network (CIDR).
35+
- `ipv4_prefix` (String, Deprecated) The IPv4 prefix of the network (CIDR).
3636
- `ipv4_prefix_length` (Number) The IPv4 prefix length of the network.
3737
- `ipv4_prefixes` (List of String) The IPv4 prefixes of the network.
3838
- `ipv6_gateway` (String) The IPv6 gateway of a network. If not specified, the first IP of the network will be assigned as the gateway.
3939
- `ipv6_nameservers` (List of String) The IPv6 nameservers of the network.
40-
- `ipv6_prefix` (String) The IPv6 prefix of the network (CIDR).
40+
- `ipv6_prefix` (String, Deprecated) The IPv6 prefix of the network (CIDR).
4141
- `ipv6_prefix_length` (Number) The IPv6 prefix length of the network.
4242
- `ipv6_prefixes` (List of String) The IPv6 prefixes of the network.
4343
- `labels` (Map of String) Labels are key-value string pairs which can be attached to a resource container

stackit/internal/services/iaas/iaas_acc_test.go

Lines changed: 161 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ package iaas_test
22

33
import (
44
"context"
5+
"errors"
56
"fmt"
7+
"net/http"
68
"os"
79
"path/filepath"
810
"strings"
@@ -12,8 +14,10 @@ import (
1214
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
1315
"github.com/hashicorp/terraform-plugin-testing/terraform"
1416
"github.com/stackitcloud/stackit-sdk-go/core/config"
17+
"github.com/stackitcloud/stackit-sdk-go/core/oapierror"
1518
"github.com/stackitcloud/stackit-sdk-go/core/utils"
1619
"github.com/stackitcloud/stackit-sdk-go/services/iaas"
20+
"github.com/stackitcloud/stackit-sdk-go/services/iaas/wait"
1721
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
1822
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/testutil"
1923
)
@@ -31,9 +35,10 @@ var networkResource = map[string]string{
3135
"ipv4_prefix_length": "24",
3236
"nameserver0": "1.2.3.4",
3337
"nameserver1": "5.6.7.8",
34-
"ipv4_gateway": "10.1.2.1",
35-
"ipv4_prefix": "10.1.2.1/24",
38+
"ipv4_gateway": "10.2.2.1",
39+
"ipv4_prefix": "10.2.2.0/24",
3640
"routed": "false",
41+
"name_updated": fmt.Sprintf("acc-test-%s", acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum)),
3742
}
3843

3944
var networkAreaResource = map[string]string{
@@ -444,6 +449,117 @@ func testAccImageConfig(name string) string {
444449
)
445450
}
446451

452+
func TestAccNetwork(t *testing.T) {
453+
resource.Test(t, resource.TestCase{
454+
ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories,
455+
CheckDestroy: testAccCheckNetworkDestroy,
456+
Steps: []resource.TestStep{
457+
458+
// Creation
459+
{
460+
Config: networkResourceConfig(
461+
networkResource["name"],
462+
fmt.Sprintf("[%q, %q]",
463+
networkResource["nameserver0"],
464+
networkResource["nameserver1"]),
465+
),
466+
Check: resource.ComposeAggregateTestCheckFunc(
467+
resource.TestCheckResourceAttrSet("stackit_network.network", "network_id"),
468+
resource.TestCheckResourceAttr("stackit_network.network", "name", networkResource["name"]),
469+
resource.TestCheckResourceAttr("stackit_network.network", "ipv4_nameservers.#", "2"),
470+
// nameservers may be returned in a randomized order, so we have to check them with a helper function
471+
resource.TestCheckTypeSetElemAttr("stackit_network.network", "nameservers.*", networkResource["nameserver0"]),
472+
resource.TestCheckTypeSetElemAttr("stackit_network.network", "nameservers.*", networkResource["nameserver1"]),
473+
resource.TestCheckResourceAttr("stackit_network.network", "ipv4_gateway", networkResource["ipv4_gateway"]),
474+
resource.TestCheckResourceAttr("stackit_network.network", "ipv4_prefix", networkResource["ipv4_prefix"]),
475+
resource.TestCheckResourceAttr("stackit_network.network", "ipv4_prefix_length", networkResource["ipv4_prefix_length"]),
476+
),
477+
},
478+
// Data source
479+
{
480+
Config: fmt.Sprintf(`
481+
%s
482+
483+
data "stackit_network" "network" {
484+
project_id = "%s"
485+
network_id = stackit_network.network.network_id
486+
}
487+
`, networkResourceConfig(
488+
networkResource["name"],
489+
fmt.Sprintf("[%q, %q]",
490+
networkResource["nameserver0"],
491+
networkResource["nameserver1"]),
492+
),
493+
testutil.ProjectId,
494+
),
495+
Check: resource.ComposeAggregateTestCheckFunc(
496+
resource.TestCheckResourceAttrSet("data.stackit_network.network", "network_id"),
497+
resource.TestCheckResourceAttr("data.stackit_network.network", "name", networkResource["name"]),
498+
resource.TestCheckResourceAttr("data.stackit_network.network", "ipv4_gateway", networkResource["ipv4_gateway"]),
499+
resource.TestCheckResourceAttr("data.stackit_network.network", "ipv4_nameservers.#", "2"),
500+
// nameservers may be returned in a randomized order, so we have to check them with a helper function
501+
resource.TestCheckTypeSetElemAttr("stackit_network.network", "nameservers.*", networkResource["nameserver0"]),
502+
resource.TestCheckTypeSetElemAttr("stackit_network.network", "nameservers.*", networkResource["nameserver1"]),
503+
resource.TestCheckResourceAttr("data.stackit_network.network", "ipv4_prefix", networkResource["ipv4_prefix"]),
504+
resource.TestCheckResourceAttr("data.stackit_network.network", "ipv4_prefix_length", networkResource["ipv4_prefix_length"]),
505+
resource.TestCheckResourceAttr("data.stackit_network.network", "ipv4_prefixes.#", "1"),
506+
resource.TestCheckResourceAttr("data.stackit_network.network", "ipv4_prefixes.0", networkResource["ipv4_prefix"]),
507+
resource.TestCheckResourceAttr("data.stackit_network.network", "routed", networkResource["routed"]),
508+
),
509+
},
510+
511+
// Import
512+
{
513+
ResourceName: "stackit_network.network",
514+
ImportStateIdFunc: func(s *terraform.State) (string, error) {
515+
r, ok := s.RootModule().Resources["stackit_network.network"]
516+
if !ok {
517+
return "", fmt.Errorf("couldn't find resource stackit_network.network")
518+
}
519+
networkId, ok := r.Primary.Attributes["network_id"]
520+
if !ok {
521+
return "", fmt.Errorf("couldn't find attribute network_id")
522+
}
523+
return fmt.Sprintf("%s,%s", testutil.ProjectId, networkId), nil
524+
},
525+
ImportState: true,
526+
Check: resource.ComposeAggregateTestCheckFunc(
527+
resource.TestCheckResourceAttrSet("data.stackit_network.network", "network_id"),
528+
resource.TestCheckResourceAttr("data.stackit_network.network", "name", networkResource["name"]),
529+
resource.TestCheckResourceAttr("data.stackit_network.network", "ipv4_gateway", networkResource["ipv4_gateway"]),
530+
resource.TestCheckResourceAttr("data.stackit_network.network", "ipv4_nameservers.#", "2"),
531+
// nameservers may be returned in a randomized order, so we have to check them with a helper function
532+
resource.TestCheckTypeSetElemAttr("stackit_network.network", "nameservers.*", networkResource["nameserver0"]),
533+
resource.TestCheckTypeSetElemAttr("stackit_network.network", "nameservers.*", networkResource["nameserver1"]),
534+
resource.TestCheckResourceAttr("data.stackit_network.network", "ipv4_prefix", networkResource["ipv4_prefix"]),
535+
resource.TestCheckResourceAttr("data.stackit_network.network", "ipv4_prefix_length", networkResource["ipv4_prefix_length"]),
536+
resource.TestCheckResourceAttr("data.stackit_network.network", "ipv4_prefixes.#", "1"),
537+
resource.TestCheckResourceAttr("data.stackit_network.network", "ipv4_prefixes.0", networkResource["ipv4_prefix"]),
538+
resource.TestCheckResourceAttr("data.stackit_network.network", "routed", networkResource["routed"]),
539+
),
540+
},
541+
542+
// Update
543+
{
544+
Config: networkResourceConfig(
545+
networkResource["name_updated"],
546+
fmt.Sprintf("[%q, %q]",
547+
networkResource["nameserver0"],
548+
networkResource["nameserver1"]),
549+
),
550+
Check: resource.ComposeAggregateTestCheckFunc(
551+
resource.TestCheckResourceAttrSet("stackit_network.network", "network_id"),
552+
resource.TestCheckResourceAttr("stackit_network.network", "name", networkResource["name_updated"]),
553+
resource.TestCheckResourceAttr("stackit_network.network", "ipv4_nameservers.#", "2"),
554+
resource.TestCheckResourceAttr("stackit_network.network", "ipv4_gateway", networkResource["ipv4_gateway"]),
555+
resource.TestCheckResourceAttr("stackit_network.network", "ipv4_prefix", networkResource["ipv4_prefix"]),
556+
resource.TestCheckResourceAttr("stackit_network.network", "ipv4_prefix_length", networkResource["ipv4_prefix_length"])),
557+
},
558+
// Deletion is done by the framework implicitly
559+
},
560+
})
561+
}
562+
447563
func TestAccNetworkArea(t *testing.T) {
448564
resource.Test(t, resource.TestCase{
449565
ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories,
@@ -1557,6 +1673,49 @@ func TestAccImage(t *testing.T) {
15571673
})
15581674
}
15591675

1676+
func testAccCheckNetworkDestroy(s *terraform.State) error {
1677+
ctx := context.Background()
1678+
var client *iaas.APIClient
1679+
var err error
1680+
if testutil.IaaSCustomEndpoint == "" {
1681+
client, err = iaas.NewAPIClient(
1682+
config.WithRegion("eu01"),
1683+
)
1684+
} else {
1685+
client, err = iaas.NewAPIClient(
1686+
config.WithEndpoint(testutil.IaaSCustomEndpoint),
1687+
)
1688+
}
1689+
if err != nil {
1690+
return fmt.Errorf("creating client: %w", err)
1691+
}
1692+
1693+
var errs []error
1694+
// networks
1695+
for _, rs := range s.RootModule().Resources {
1696+
if rs.Type != "stackit_network" {
1697+
continue
1698+
}
1699+
networkId := strings.Split(rs.Primary.ID, core.Separator)[1]
1700+
err := client.DeleteNetworkExecute(ctx, testutil.ProjectId, networkId)
1701+
if err != nil {
1702+
var oapiErr *oapierror.GenericOpenAPIError
1703+
if errors.As(err, &oapiErr) {
1704+
if oapiErr.StatusCode == http.StatusNotFound {
1705+
continue
1706+
}
1707+
}
1708+
errs = append(errs, fmt.Errorf("cannot trigger network deletion %q: %w", networkId, err))
1709+
}
1710+
_, err = wait.DeleteNetworkWaitHandler(ctx, client, testutil.ProjectId, networkId).WaitWithContext(ctx)
1711+
if err != nil {
1712+
errs = append(errs, fmt.Errorf("cannot delete network %q: %w", networkId, err))
1713+
}
1714+
}
1715+
1716+
return errors.Join(errs...)
1717+
}
1718+
15601719
func testAccCheckNetworkAreaDestroy(s *terraform.State) error {
15611720
ctx := context.Background()
15621721
var client *iaas.APIClient

stackit/internal/services/iaas/network/datasource.go

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package network
33
import (
44
"context"
55
"fmt"
6+
"net"
67
"net/http"
78
"strings"
89

@@ -147,8 +148,9 @@ func (d *networkDataSource) Schema(_ context.Context, _ datasource.SchemaRequest
147148
ElementType: types.StringType,
148149
},
149150
"ipv4_prefix": schema.StringAttribute{
150-
Description: "The IPv4 prefix of the network (CIDR).",
151-
Computed: true,
151+
Description: "The IPv4 prefix of the network (CIDR).",
152+
DeprecationMessage: "The API supports reading multiple prefixes. So using the attribute 'ipv4_prefixes` should be preferred. This attribute will be populated with the first element from the list",
153+
Computed: true,
152154
},
153155
"ipv4_prefix_length": schema.Int64Attribute{
154156
Description: "The IPv4 prefix length of the network.",
@@ -175,8 +177,9 @@ func (d *networkDataSource) Schema(_ context.Context, _ datasource.SchemaRequest
175177
ElementType: types.StringType,
176178
},
177179
"ipv6_prefix": schema.StringAttribute{
178-
Description: "The IPv6 prefix of the network (CIDR).",
179-
Computed: true,
180+
Description: "The IPv6 prefix of the network (CIDR).",
181+
DeprecationMessage: "The API supports reading multiple prefixes. So using the attribute 'ipv6_prefixes` should be preferred. This attribute will be populated with the first element from the list",
182+
Computed: true,
180183
},
181184
"ipv6_prefix_length": schema.Int64Attribute{
182185
Description: "The IPv6 prefix length of the network.",
@@ -321,6 +324,17 @@ func mapDataSourceFields(ctx context.Context, networkResp *iaas.Network, model *
321324
if diags.HasError() {
322325
return fmt.Errorf("map network prefixes: %w", core.DiagsToError(diags))
323326
}
327+
if len(respPrefixes) > 0 {
328+
model.IPv4Prefix = types.StringValue(respPrefixes[0])
329+
_, netmask, err := net.ParseCIDR(respPrefixes[0])
330+
if err != nil {
331+
// silently ignore parsing error for the netmask
332+
model.IPv4PrefixLength = types.Int64Null()
333+
} else {
334+
ones, _ := netmask.Mask.Size()
335+
model.IPv4PrefixLength = types.Int64Value(int64(ones))
336+
}
337+
}
324338

325339
model.Prefixes = prefixesTF
326340
model.IPv4Prefixes = prefixesTF
@@ -361,7 +375,17 @@ func mapDataSourceFields(ctx context.Context, networkResp *iaas.Network, model *
361375
if diags.HasError() {
362376
return fmt.Errorf("map network IPv6 prefixes: %w", core.DiagsToError(diags))
363377
}
364-
378+
if len(respPrefixesV6) > 0 {
379+
model.IPv6Prefix = types.StringValue(respPrefixesV6[0])
380+
_, netmask, err := net.ParseCIDR(respPrefixesV6[0])
381+
if err != nil {
382+
// silently ignore parsing error for the netmask
383+
model.IPv6PrefixLength = types.Int64Null()
384+
} else {
385+
ones, _ := netmask.Mask.Size()
386+
model.IPv6PrefixLength = types.Int64Value(int64(ones))
387+
}
388+
}
365389
model.IPv6Prefixes = prefixesV6TF
366390
}
367391

0 commit comments

Comments
 (0)