feat(iaas): add iaas network v2 alpha (#899)

* add experimental network v2
This commit is contained in:
Marcel Jacek 2025-07-07 13:25:54 +02:00 committed by GitHub
parent a00b0466d5
commit d9dc1d4495
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 3777 additions and 923 deletions

View file

@ -183,7 +183,7 @@ To enable experiments set the experiments field in the provider definition:
```hcl ```hcl
provider "stackit" { provider "stackit" {
default_region = "eu01" default_region = "eu01"
experiments = ["iam", "routing-tables"] experiments = ["iam", "routing-tables", "network"]
} }
``` ```
@ -197,6 +197,12 @@ Enables IAM management features in the Terraform provider. The underlying IAM AP
This feature enables experimental routing table capabilities in the Terraform Provider, available only to designated SNAs at this time. This feature enables experimental routing table capabilities in the Terraform Provider, available only to designated SNAs at this time.
#### `network`
The `stackit_network` provides the fields `region` and `routing_table_id` when the experiment flag `network` is set.
The underlying API is not stable yet and could change in the future.
If you don't need these fields, don't set the experiment flag `network`, to use the stable api.
## Acceptance Tests ## Acceptance Tests
Terraform acceptance tests are run using the command `make test-acceptance-tf`. For all services, Terraform acceptance tests are run using the command `make test-acceptance-tf`. For all services,

View file

@ -27,6 +27,11 @@ data "stackit_network" "example" {
- `network_id` (String) The network ID. - `network_id` (String) The network ID.
- `project_id` (String) STACKIT project ID to which the network is associated. - `project_id` (String) STACKIT project ID to which the network is associated.
### Optional
- `region` (String) Can only be used when experimental "network" is set. This is likely going to undergo significant changes or be removed in the future.
The resource region. If not defined, the provider region is used.
### Read-Only ### Read-Only
- `id` (String) Terraform's internal resource ID. It is structured as "`project_id`,`network_id`". - `id` (String) Terraform's internal resource ID. It is structured as "`project_id`,`network_id`".
@ -46,3 +51,5 @@ data "stackit_network" "example" {
- `prefixes` (List of String, Deprecated) The prefixes of the network. This field is deprecated and will be removed soon, use `ipv4_prefixes` to read the prefixes of the IPv4 networks. - `prefixes` (List of String, Deprecated) The prefixes of the network. This field is deprecated and will be removed soon, use `ipv4_prefixes` to read the prefixes of the IPv4 networks.
- `public_ip` (String) The public IP of the network. - `public_ip` (String) The public IP of the network.
- `routed` (Boolean) Shows if the network is routed and therefore accessible from other networks. - `routed` (Boolean) Shows if the network is routed and therefore accessible from other networks.
- `routing_table_id` (String) Can only be used when experimental "network" is set. This is likely going to undergo significant changes or be removed in the future. Use it at your own discretion.
The ID of the routing table associated with the network.

View file

@ -157,7 +157,7 @@ Note: AWS specific checks must be skipped as they do not work on STACKIT. For de
- `default_region` (String) Region will be used as the default location for regional services. Not all services require a region, some are global - `default_region` (String) Region will be used as the default location for regional services. Not all services require a region, some are global
- `dns_custom_endpoint` (String) Custom endpoint for the DNS service - `dns_custom_endpoint` (String) Custom endpoint for the DNS service
- `enable_beta_resources` (Boolean) Enable beta resources. Default is false. - `enable_beta_resources` (Boolean) Enable beta resources. Default is false.
- `experiments` (List of String) Enables experiments. These are unstable features without official support. More information can be found in the README. Available Experiments: [iam routing-tables] - `experiments` (List of String) Enables experiments. These are unstable features without official support. More information can be found in the README. Available Experiments: iam, routing-tables, network
- `git_custom_endpoint` (String) Custom endpoint for the Git service - `git_custom_endpoint` (String) Custom endpoint for the Git service
- `iaas_custom_endpoint` (String) Custom endpoint for the IaaS service - `iaas_custom_endpoint` (String) Custom endpoint for the IaaS service
- `loadbalancer_custom_endpoint` (String) Custom endpoint for the Load Balancer service - `loadbalancer_custom_endpoint` (String) Custom endpoint for the Load Balancer service

View file

@ -63,7 +63,11 @@ resource "stackit_network" "example_non_routed_network" {
- `nameservers` (List of String, Deprecated) The nameservers of the network. This field is deprecated and will be removed soon, use `ipv4_nameservers` to configure the nameservers for IPv4. - `nameservers` (List of String, Deprecated) The nameservers of the network. This field is deprecated and will be removed soon, use `ipv4_nameservers` to configure the nameservers for IPv4.
- `no_ipv4_gateway` (Boolean) If set to `true`, the network doesn't have a gateway. - `no_ipv4_gateway` (Boolean) If set to `true`, the network doesn't have a gateway.
- `no_ipv6_gateway` (Boolean) If set to `true`, the network doesn't have a gateway. - `no_ipv6_gateway` (Boolean) If set to `true`, the network doesn't have a gateway.
- `region` (String) Can only be used when experimental "network" is set.
The resource region. If not defined, the provider region is used.
- `routed` (Boolean) If set to `true`, the network is routed and therefore accessible from other networks. - `routed` (Boolean) If set to `true`, the network is routed and therefore accessible from other networks.
- `routing_table_id` (String) Can only be used when experimental "network" is set.
The ID of the routing table associated with the network.
### Read-Only ### Read-Only

2
go.mod
View file

@ -16,7 +16,7 @@ require (
github.com/stackitcloud/stackit-sdk-go/services/dns v0.17.0 github.com/stackitcloud/stackit-sdk-go/services/dns v0.17.0
github.com/stackitcloud/stackit-sdk-go/services/git v0.6.0 github.com/stackitcloud/stackit-sdk-go/services/git v0.6.0
github.com/stackitcloud/stackit-sdk-go/services/iaas v0.26.0 github.com/stackitcloud/stackit-sdk-go/services/iaas v0.26.0
github.com/stackitcloud/stackit-sdk-go/services/iaasalpha v0.1.19-alpha github.com/stackitcloud/stackit-sdk-go/services/iaasalpha v0.1.21-alpha
github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v1.4.0 github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v1.4.0
github.com/stackitcloud/stackit-sdk-go/services/logme v0.25.0 github.com/stackitcloud/stackit-sdk-go/services/logme v0.25.0
github.com/stackitcloud/stackit-sdk-go/services/mariadb v0.25.0 github.com/stackitcloud/stackit-sdk-go/services/mariadb v0.25.0

4
go.sum
View file

@ -162,8 +162,8 @@ github.com/stackitcloud/stackit-sdk-go/services/git v0.6.0 h1:C+8z3MdvnTngcH9L72
github.com/stackitcloud/stackit-sdk-go/services/git v0.6.0/go.mod h1:agI7SONeLR/IZL3TOgn1tDzfS63O2rWKQE8+huRjEzU= github.com/stackitcloud/stackit-sdk-go/services/git v0.6.0/go.mod h1:agI7SONeLR/IZL3TOgn1tDzfS63O2rWKQE8+huRjEzU=
github.com/stackitcloud/stackit-sdk-go/services/iaas v0.26.0 h1:7qm/Tft79wFlHomPdgjUJ9uJU8kEk+k9ficMGRoHtf0= github.com/stackitcloud/stackit-sdk-go/services/iaas v0.26.0 h1:7qm/Tft79wFlHomPdgjUJ9uJU8kEk+k9ficMGRoHtf0=
github.com/stackitcloud/stackit-sdk-go/services/iaas v0.26.0/go.mod h1:lUGkcbyMkd4nRBDFmKohIwlgtOZqQo4Ek5S5ajw90Xg= github.com/stackitcloud/stackit-sdk-go/services/iaas v0.26.0/go.mod h1:lUGkcbyMkd4nRBDFmKohIwlgtOZqQo4Ek5S5ajw90Xg=
github.com/stackitcloud/stackit-sdk-go/services/iaasalpha v0.1.19-alpha h1:HnQyJSXbtYzN9IhTO02zxLrcSxyauIbeJD+GTf23A50= github.com/stackitcloud/stackit-sdk-go/services/iaasalpha v0.1.21-alpha h1:m1jq6a8dbUe+suFuUNdHmM/cSehpGLUtDbK1CqLqydg=
github.com/stackitcloud/stackit-sdk-go/services/iaasalpha v0.1.19-alpha/go.mod h1:Wt77ucOwpe9g/84LijU+YhWbn3vLcpkAoRy2i+FobNQ= github.com/stackitcloud/stackit-sdk-go/services/iaasalpha v0.1.21-alpha/go.mod h1:Nu1b5Phsv8plgZ51+fkxPVsU91ZJ5Ayz+cthilxdmQ8=
github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v1.4.0 h1:Ef4SyTBjIkfwaws4mssa6AoK+OokHFtr7ZIflUpoXVE= github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v1.4.0 h1:Ef4SyTBjIkfwaws4mssa6AoK+OokHFtr7ZIflUpoXVE=
github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v1.4.0/go.mod h1:FiVhDlw9+yuTiUmnyGLn2qpsLW26w9OC4TS1y78czvg= github.com/stackitcloud/stackit-sdk-go/services/loadbalancer v1.4.0/go.mod h1:FiVhDlw9+yuTiUmnyGLn2qpsLW26w9OC4TS1y78czvg=
github.com/stackitcloud/stackit-sdk-go/services/logme v0.25.0 h1:QKOfaB7EcuJmBCxpFXN2K7g2ih0gQM6cyZ1VhTmtQfI= github.com/stackitcloud/stackit-sdk-go/services/logme v0.25.0 h1:QKOfaB7EcuJmBCxpFXN2K7g2ih0gQM6cyZ1VhTmtQfI=

View file

@ -13,9 +13,11 @@ import (
const ( const (
RoutingTablesExperiment = "routing-tables" RoutingTablesExperiment = "routing-tables"
NetworkExperiment = "network"
IamExperiment = "iam"
) )
var AvailableExperiments = []string{"iam", RoutingTablesExperiment} var AvailableExperiments = []string{IamExperiment, RoutingTablesExperiment, NetworkExperiment}
// Check if an experiment is valid. // Check if an experiment is valid.
func ValidExperiment(experiment string, diags *diag.Diagnostics) bool { func ValidExperiment(experiment string, diags *diag.Diagnostics) bool {
@ -31,11 +33,21 @@ func ValidExperiment(experiment string, diags *diag.Diagnostics) bool {
// Check if an experiment is enabled. // Check if an experiment is enabled.
func CheckExperimentEnabled(ctx context.Context, data *core.ProviderData, experiment, resourceName string, resourceType core.ResourceType, diags *diag.Diagnostics) { func CheckExperimentEnabled(ctx context.Context, data *core.ProviderData, experiment, resourceName string, resourceType core.ResourceType, diags *diag.Diagnostics) {
if CheckExperimentEnabledWithoutError(ctx, data, experiment, resourceName, resourceType, diags) {
return
}
errTitle := fmt.Sprintf("%s is part of the %s experiment, which is currently disabled by default", resourceName, experiment)
errContent := fmt.Sprintf(`Enable the %s experiment by adding it into your provider block.`, experiment)
tflog.Error(ctx, fmt.Sprintf("%s | %s", errTitle, errContent))
diags.AddError(errTitle, errContent)
}
func CheckExperimentEnabledWithoutError(ctx context.Context, data *core.ProviderData, experiment, resourceName string, resourceType core.ResourceType, diags *diag.Diagnostics) bool {
if !ValidExperiment(experiment, diags) { if !ValidExperiment(experiment, diags) {
errTitle := fmt.Sprintf("The experiment %s does not exist.", experiment) errTitle := fmt.Sprintf("The experiment %s does not exist.", experiment)
errContent := "This is a bug in the STACKIT Terraform Provider. Please open an issue here: https://github.com/stackitcloud/terraform-provider-stackit/issues" errContent := "This is a bug in the STACKIT Terraform Provider. Please open an issue here: https://github.com/stackitcloud/terraform-provider-stackit/issues"
diags.AddError(errTitle, errContent) diags.AddError(errTitle, errContent)
return return false
} }
experimentActive := slices.ContainsFunc(data.Experiments, func(e string) bool { experimentActive := slices.ContainsFunc(data.Experiments, func(e string) bool {
return strings.EqualFold(e, experiment) return strings.EqualFold(e, experiment)
@ -46,12 +58,9 @@ func CheckExperimentEnabled(ctx context.Context, data *core.ProviderData, experi
warnContent := fmt.Sprintf("This %s is part of the %s experiment and is likely going to undergo significant changes or be removed in the future. Use it at your own discretion.", resourceType, experiment) warnContent := fmt.Sprintf("This %s is part of the %s experiment and is likely going to undergo significant changes or be removed in the future. Use it at your own discretion.", resourceType, experiment)
tflog.Warn(ctx, fmt.Sprintf("%s | %s", warnTitle, warnContent)) tflog.Warn(ctx, fmt.Sprintf("%s | %s", warnTitle, warnContent))
diags.AddWarning(warnTitle, warnContent) diags.AddWarning(warnTitle, warnContent)
return return true
} }
errTitle := fmt.Sprintf("%s is part of the %s experiment, which is currently disabled by default", resourceName, experiment) return false
errContent := fmt.Sprintf(`Enable the %s experiment by adding it into your provider block.`, experiment)
tflog.Error(ctx, fmt.Sprintf("%s | %s", errTitle, errContent))
diags.AddError(errTitle, errContent)
} }
func AddExperimentDescription(description, experiment string, resourceType core.ResourceType) string { func AddExperimentDescription(description, experiment string, resourceType core.ResourceType) string {

View file

@ -21,7 +21,7 @@ func TestValidExperiment(t *testing.T) {
{ {
name: "valid", name: "valid",
args: args{ args: args{
experiment: "iam", experiment: IamExperiment,
diags: &diag.Diagnostics{}, diags: &diag.Diagnostics{},
}, },
want: true, want: true,
@ -64,9 +64,9 @@ func TestCheckExperimentEnabled(t *testing.T) {
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
data: &core.ProviderData{ data: &core.ProviderData{
Experiments: []string{"iam"}, Experiments: []string{IamExperiment},
}, },
experiment: "iam", experiment: IamExperiment,
resourceType: core.Resource, resourceType: core.Resource,
diags: &diag.Diagnostics{}, diags: &diag.Diagnostics{},
}, },
@ -80,7 +80,7 @@ func TestCheckExperimentEnabled(t *testing.T) {
data: &core.ProviderData{ data: &core.ProviderData{
Experiments: []string{}, Experiments: []string{},
}, },
experiment: "iam", experiment: IamExperiment,
resourceType: core.Resource, resourceType: core.Resource,
diags: &diag.Diagnostics{}, diags: &diag.Diagnostics{},
}, },
@ -92,7 +92,7 @@ func TestCheckExperimentEnabled(t *testing.T) {
args: args{ args: args{
ctx: context.Background(), ctx: context.Background(),
data: &core.ProviderData{ data: &core.ProviderData{
Experiments: []string{"iam"}, Experiments: []string{IamExperiment},
}, },
experiment: "foobar", experiment: "foobar",
resourceType: core.Resource, resourceType: core.Resource,
@ -101,6 +101,34 @@ func TestCheckExperimentEnabled(t *testing.T) {
wantDiagsErr: true, wantDiagsErr: true,
wantDiagsWarning: false, wantDiagsWarning: false,
}, },
{
name: "enabled multiple experiment",
args: args{
ctx: context.Background(),
data: &core.ProviderData{
Experiments: []string{IamExperiment, NetworkExperiment, RoutingTablesExperiment},
},
experiment: NetworkExperiment,
resourceType: core.Resource,
diags: &diag.Diagnostics{},
},
wantDiagsErr: false,
wantDiagsWarning: true,
},
{
name: "enabled multiple experiment - without the required experiment",
args: args{
ctx: context.Background(),
data: &core.ProviderData{
Experiments: []string{IamExperiment, RoutingTablesExperiment},
},
experiment: NetworkExperiment,
resourceType: core.Resource,
diags: &diag.Diagnostics{},
},
wantDiagsErr: true,
wantDiagsWarning: false,
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
@ -114,3 +142,111 @@ func TestCheckExperimentEnabled(t *testing.T) {
}) })
} }
} }
func TestCheckExperimentEnabledWithoutError(t *testing.T) {
type args struct {
ctx context.Context
data *core.ProviderData
experiment string
resourceName string
resourceType core.ResourceType
diags *diag.Diagnostics
}
tests := []struct {
name string
args args
wantEnabled bool
wantDiagsErr bool
wantDiagsWarning bool
}{
{
name: "enabled",
args: args{
ctx: context.Background(),
data: &core.ProviderData{
Experiments: []string{IamExperiment},
},
experiment: IamExperiment,
resourceType: core.Resource,
diags: &diag.Diagnostics{},
},
wantEnabled: true,
wantDiagsErr: false,
wantDiagsWarning: true,
},
{
name: "disabled - no error",
args: args{
ctx: context.Background(),
data: &core.ProviderData{
Experiments: []string{},
},
experiment: NetworkExperiment,
resourceType: core.Resource,
diags: &diag.Diagnostics{},
},
wantEnabled: false,
wantDiagsErr: false,
wantDiagsWarning: false,
},
{
name: "invalid experiment",
args: args{
ctx: context.Background(),
data: &core.ProviderData{
Experiments: []string{IamExperiment},
},
experiment: "foobar",
resourceType: core.Resource,
diags: &diag.Diagnostics{},
},
wantEnabled: false,
wantDiagsErr: true,
wantDiagsWarning: false,
},
{
name: "enabled multiple experiment",
args: args{
ctx: context.Background(),
data: &core.ProviderData{
Experiments: []string{IamExperiment, NetworkExperiment, RoutingTablesExperiment},
},
experiment: NetworkExperiment,
resourceType: core.Resource,
diags: &diag.Diagnostics{},
},
wantEnabled: true,
wantDiagsErr: false,
wantDiagsWarning: true,
},
{
name: "enabled multiple experiment - without the required experiment",
args: args{
ctx: context.Background(),
data: &core.ProviderData{
Experiments: []string{IamExperiment, RoutingTablesExperiment},
},
experiment: NetworkExperiment,
resourceType: core.Resource,
diags: &diag.Diagnostics{},
},
wantEnabled: false,
wantDiagsErr: false,
wantDiagsWarning: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := CheckExperimentEnabledWithoutError(tt.args.ctx, tt.args.data, tt.args.experiment, tt.args.resourceName, tt.args.resourceType, tt.args.diags); got != tt.wantEnabled {
t.Errorf("CheckExperimentEnabledWithoutError() = %v, want %v", got, tt.wantEnabled)
}
if got := tt.args.diags.HasError(); got != tt.wantDiagsErr {
t.Errorf("CheckExperimentEnabled() diags.HasError() = %v, want %v", got, tt.wantDiagsErr)
}
if got := tt.args.diags.WarningsCount() > 0; got != tt.wantDiagsWarning {
t.Errorf("CheckExperimentEnabled() diags.WarningsCount() > 0 = %v, want %v", got, tt.wantDiagsErr)
}
})
}
}

View file

@ -32,9 +32,6 @@ var roleTargets = []string{
"organization", "organization",
} }
// This resource is part of the "iam" experiment
var experiment = "iam"
// Ensure the implementation satisfies the expected interfaces. // Ensure the implementation satisfies the expected interfaces.
var ( var (
_ resource.Resource = &roleAssignmentResource{} _ resource.Resource = &roleAssignmentResource{}
@ -84,7 +81,7 @@ func (r *roleAssignmentResource) Configure(ctx context.Context, req resource.Con
return return
} }
features.CheckExperimentEnabled(ctx, &providerData, experiment, fmt.Sprintf("stackit_authorization_%s_role_assignment", r.apiName), core.Resource, &resp.Diagnostics) features.CheckExperimentEnabled(ctx, &providerData, features.IamExperiment, fmt.Sprintf("stackit_authorization_%s_role_assignment", r.apiName), core.Resource, &resp.Diagnostics)
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {
return return
} }
@ -100,7 +97,7 @@ func (r *roleAssignmentResource) Configure(ctx context.Context, req resource.Con
// Schema defines the schema for the resource. // Schema defines the schema for the resource.
func (r *roleAssignmentResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { func (r *roleAssignmentResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
descriptions := map[string]string{ descriptions := map[string]string{
"main": features.AddExperimentDescription(fmt.Sprintf("%s Role Assignment resource schema.", r.apiName), experiment, core.Resource), "main": features.AddExperimentDescription(fmt.Sprintf("%s Role Assignment resource schema.", r.apiName), features.IamExperiment, core.Resource),
"id": "Terraform's internal resource identifier. It is structured as \"[resource_id],[role],[subject]\".", "id": "Terraform's internal resource identifier. It is structured as \"[resource_id],[role],[subject]\".",
"resource_id": fmt.Sprintf("%s Resource to assign the role to.", r.apiName), "resource_id": fmt.Sprintf("%s Resource to assign the role to.", r.apiName),
"role": "Role to be assigned", "role": "Role to be assigned",

View file

@ -5,6 +5,7 @@ import (
_ "embed" _ "embed"
"errors" "errors"
"fmt" "fmt"
"maps"
"net/http" "net/http"
"os" "os"
"path/filepath" "path/filepath"
@ -21,6 +22,8 @@ import (
"github.com/stackitcloud/stackit-sdk-go/core/utils" "github.com/stackitcloud/stackit-sdk-go/core/utils"
"github.com/stackitcloud/stackit-sdk-go/services/iaas" "github.com/stackitcloud/stackit-sdk-go/services/iaas"
"github.com/stackitcloud/stackit-sdk-go/services/iaas/wait" "github.com/stackitcloud/stackit-sdk-go/services/iaas/wait"
"github.com/stackitcloud/stackit-sdk-go/services/iaasalpha"
waitAlpha "github.com/stackitcloud/stackit-sdk-go/services/iaasalpha/wait"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/testutil" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/testutil"
) )
@ -50,11 +53,17 @@ var (
//go:embed testdata/resource-network-area-max.tf //go:embed testdata/resource-network-area-max.tf
resourceNetworkAreaMaxConfig string resourceNetworkAreaMaxConfig string
//go:embed testdata/resource-network-min.tf //go:embed testdata/resource-network-v1-min.tf
resourceNetworkMinConfig string resourceNetworkV1MinConfig string
//go:embed testdata/resource-network-max.tf //go:embed testdata/resource-network-v1-max.tf
resourceNetworkMaxConfig string resourceNetworkV1MaxConfig string
//go:embed testdata/resource-network-v2-min.tf
resourceNetworkV2MinConfig string
//go:embed testdata/resource-network-v2-max.tf
resourceNetworkV2MaxConfig string
//go:embed testdata/resource-network-interface-min.tf //go:embed testdata/resource-network-interface-min.tf
resourceNetworkInterfaceMinConfig string resourceNetworkInterfaceMinConfig string
@ -83,21 +92,10 @@ var (
const ( const (
keypairPublicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIDsPd27M449akqCtdFg2+AmRVJz6eWio0oMP9dVg7XZ" keypairPublicKey = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIDsPd27M449akqCtdFg2+AmRVJz6eWio0oMP9dVg7XZ"
// TODO: create network area using terraform resource instead once it's out of experimental stage and GA
testNetworkAreaId = "25bbf23a-8134-4439-9f5e-1641caf8354e"
) )
// Network resource data
var networkResource = map[string]string{
"project_id": testutil.ProjectId,
"name": fmt.Sprintf("acc-test-%s", acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum)),
"ipv4_prefix_length": "24",
"nameserver0": "1.2.3.4",
"nameserver1": "5.6.7.8",
"ipv4_gateway": "10.2.2.1",
"ipv4_prefix": "10.2.2.0/24",
"routed": "false",
"name_updated": fmt.Sprintf("acc-test-%s", acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum)),
}
var testConfigServerVarsMin = config.Variables{ var testConfigServerVarsMin = config.Variables{
"project_id": config.StringVariable(testutil.ProjectId), "project_id": config.StringVariable(testutil.ProjectId),
"name": config.StringVariable(fmt.Sprintf("tf-acc-%s", acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum))), "name": config.StringVariable(fmt.Sprintf("tf-acc-%s", acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum))),
@ -225,31 +223,67 @@ var testConfigVolumeVarsMaxUpdated = func() config.Variables {
return updatedConfig return updatedConfig
}() }()
var testConfigNetworkVarsMin = config.Variables{ var testConfigNetworkV1VarsMin = config.Variables{
"project_id": config.StringVariable(testutil.ProjectId), "project_id": config.StringVariable(testutil.ProjectId),
"name": config.StringVariable(fmt.Sprintf("tf-acc-%s", acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum))), "name": config.StringVariable(fmt.Sprintf("tf-acc-%s", acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum))),
} }
var testConfigNetworkVarsMax = config.Variables{ var testConfigNetworkV1VarsMax = config.Variables{
"project_id": config.StringVariable(testutil.ProjectId), "project_id": config.StringVariable(testutil.ProjectId),
"name": config.StringVariable(fmt.Sprintf("tf-acc-%s", acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum))), "name": config.StringVariable(fmt.Sprintf("tf-acc-%s", acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum))),
"ipv4_gateway": config.StringVariable("10.2.2.1"), "ipv4_gateway": config.StringVariable("10.2.2.1"),
"ipv4_nameservers": config.StringVariable("10.2.2.2"), "ipv4_nameserver_0": config.StringVariable("10.2.2.2"),
"ipv4_nameserver_1": config.StringVariable("10.2.2.3"),
"ipv4_prefix": config.StringVariable("10.2.2.0/24"), "ipv4_prefix": config.StringVariable("10.2.2.0/24"),
"ipv4_prefix_length": config.IntegerVariable(24), "ipv4_prefix_length": config.IntegerVariable(24),
"routed": config.BoolVariable(false), "routed": config.BoolVariable(false),
"label": config.StringVariable("label"), "label": config.StringVariable("label"),
} }
var testConfigNetworkVarsMaxUpdated = func() config.Variables { var testConfigNetworkV1VarsMaxUpdated = func() config.Variables {
updatedConfig := config.Variables{} updatedConfig := config.Variables{}
for k, v := range testConfigNetworkVarsMax { for k, v := range testConfigNetworkV1VarsMax {
updatedConfig[k] = v updatedConfig[k] = v
} }
updatedConfig["name"] = config.StringVariable(fmt.Sprintf("%s-updated", testutil.ConvertConfigVariable(updatedConfig["name"])))
updatedConfig["ipv4_gateway"] = config.StringVariable("") updatedConfig["ipv4_gateway"] = config.StringVariable("")
updatedConfig["ipv4_nameservers"] = config.StringVariable("10.2.2.3") updatedConfig["ipv4_nameserver_0"] = config.StringVariable("10.2.2.10")
updatedConfig["ipv4_prefix"] = config.StringVariable("10.2.2.0/25") updatedConfig["label"] = config.StringVariable("updated")
updatedConfig["ipv4_prefix_length"] = config.IntegerVariable(25) return updatedConfig
}()
var testConfigNetworkV2VarsMin = config.Variables{
"project_id": config.StringVariable(testutil.ProjectId),
"name": config.StringVariable(fmt.Sprintf("tf-acc-%s", acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum))),
}
var testConfigNetworkV2VarsMinUpdated = func() config.Variables {
updatedConfig := config.Variables{}
maps.Copy(updatedConfig, testConfigNetworkV2VarsMin)
updatedConfig["name"] = config.StringVariable(fmt.Sprintf("%s-updated", testutil.ConvertConfigVariable(updatedConfig["name"])))
return updatedConfig
}()
var testConfigNetworkV2VarsMax = config.Variables{
"project_id": config.StringVariable(testutil.ProjectId),
"name": config.StringVariable(fmt.Sprintf("tf-acc-%s", acctest.RandStringFromCharSet(5, acctest.CharSetAlphaNum))),
"ipv4_gateway": config.StringVariable("10.2.2.1"),
"ipv4_nameserver_0": config.StringVariable("10.2.2.2"),
"ipv4_nameserver_1": config.StringVariable("10.2.2.3"),
"ipv4_prefix": config.StringVariable("10.2.2.0/24"),
"ipv4_prefix_length": config.IntegerVariable(24),
"routed": config.BoolVariable(true),
"label": config.StringVariable("label"),
"organization_id": config.StringVariable(testutil.OrganizationId),
"network_area_id": config.StringVariable(testNetworkAreaId),
}
var testConfigNetworkV2VarsMaxUpdated = func() config.Variables {
updatedConfig := config.Variables{}
maps.Copy(updatedConfig, testConfigNetworkV2VarsMax)
updatedConfig["name"] = config.StringVariable(fmt.Sprintf("%s-updated", testutil.ConvertConfigVariable(updatedConfig["name"])))
updatedConfig["ipv4_gateway"] = config.StringVariable("")
updatedConfig["ipv4_nameserver_0"] = config.StringVariable("10.2.2.10")
updatedConfig["label"] = config.StringVariable("updated") updatedConfig["label"] = config.StringVariable("updated")
return updatedConfig return updatedConfig
}() }()
@ -456,20 +490,20 @@ var testConfigKeyPairMaxUpdated = func() config.Variables {
// if no local file is provided the test should create a default file and work with this instead of failing // if no local file is provided the test should create a default file and work with this instead of failing
var localFileForIaasImage os.File var localFileForIaasImage os.File
func TestAccNetworkMin(t *testing.T) { func TestAccNetworkV1Min(t *testing.T) {
t.Logf("TestAccNetworkMin name: %s", testutil.ConvertConfigVariable(testConfigNetworkVarsMin["name"])) t.Logf("TestAccNetworkV1Min name: %s", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMin["name"]))
resource.ParallelTest(t, resource.TestCase{ resource.ParallelTest(t, resource.TestCase{
ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories, ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories,
CheckDestroy: testAccCheckDestroy, CheckDestroy: testAccCheckDestroy,
Steps: []resource.TestStep{ Steps: []resource.TestStep{
// Creation // Creation
{ {
ConfigVariables: testConfigNetworkVarsMin, ConfigVariables: testConfigNetworkV1VarsMin,
Config: fmt.Sprintf("%s\n%s", testutil.IaaSProviderConfig(), resourceNetworkMinConfig), Config: fmt.Sprintf("%s\n%s", testutil.IaaSProviderConfig(), resourceNetworkV1MinConfig),
Check: resource.ComposeAggregateTestCheckFunc( Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttrSet("stackit_network.network", "network_id"), resource.TestCheckResourceAttrSet("stackit_network.network", "network_id"),
resource.TestCheckResourceAttr("stackit_network.network", "project_id", testutil.ConvertConfigVariable(testConfigNetworkVarsMin["project_id"])), resource.TestCheckResourceAttr("stackit_network.network", "project_id", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMin["project_id"])),
resource.TestCheckResourceAttr("stackit_network.network", "name", testutil.ConvertConfigVariable(testConfigNetworkVarsMin["name"])), resource.TestCheckResourceAttr("stackit_network.network", "name", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMin["name"])),
resource.TestCheckResourceAttrSet("stackit_network.network", "ipv4_prefixes.#"), resource.TestCheckResourceAttrSet("stackit_network.network", "ipv4_prefixes.#"),
resource.TestCheckResourceAttrSet("stackit_network.network", "ipv6_prefixes.#"), resource.TestCheckResourceAttrSet("stackit_network.network", "ipv6_prefixes.#"),
resource.TestCheckResourceAttrSet("stackit_network.network", "public_ip"), resource.TestCheckResourceAttrSet("stackit_network.network", "public_ip"),
@ -477,7 +511,7 @@ func TestAccNetworkMin(t *testing.T) {
}, },
// Data source // Data source
{ {
ConfigVariables: testConfigNetworkVarsMin, ConfigVariables: testConfigNetworkV1VarsMin,
Config: fmt.Sprintf(` Config: fmt.Sprintf(`
%s %s
%s %s
@ -487,12 +521,12 @@ func TestAccNetworkMin(t *testing.T) {
network_id = stackit_network.network.network_id network_id = stackit_network.network.network_id
} }
`, `,
testutil.IaaSProviderConfig(), resourceNetworkMinConfig, testutil.IaaSProviderConfig(), resourceNetworkV1MinConfig,
), ),
Check: resource.ComposeAggregateTestCheckFunc( Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttrSet("data.stackit_network.network", "network_id"), resource.TestCheckResourceAttrSet("data.stackit_network.network", "network_id"),
resource.TestCheckResourceAttr("data.stackit_network.network", "project_id", testutil.ConvertConfigVariable(testConfigNetworkVarsMin["project_id"])), resource.TestCheckResourceAttr("data.stackit_network.network", "project_id", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMin["project_id"])),
resource.TestCheckResourceAttr("data.stackit_network.network", "name", testutil.ConvertConfigVariable(testConfigNetworkVarsMin["name"])), resource.TestCheckResourceAttr("data.stackit_network.network", "name", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMin["name"])),
resource.TestCheckResourceAttrSet("data.stackit_network.network", "ipv4_prefixes.#"), resource.TestCheckResourceAttrSet("data.stackit_network.network", "ipv4_prefixes.#"),
resource.TestCheckResourceAttrSet("data.stackit_network.network", "ipv6_prefixes.#"), resource.TestCheckResourceAttrSet("data.stackit_network.network", "ipv6_prefixes.#"),
resource.TestCheckResourceAttrSet("data.stackit_network.network", "public_ip"), resource.TestCheckResourceAttrSet("data.stackit_network.network", "public_ip"),
@ -501,7 +535,7 @@ func TestAccNetworkMin(t *testing.T) {
// Import // Import
{ {
ConfigVariables: testConfigNetworkVarsMin, ConfigVariables: testConfigNetworkV1VarsMin,
ResourceName: "stackit_network.network", ResourceName: "stackit_network.network",
ImportStateIdFunc: func(s *terraform.State) (string, error) { ImportStateIdFunc: func(s *terraform.State) (string, error) {
r, ok := s.RootModule().Resources["stackit_network.network"] r, ok := s.RootModule().Resources["stackit_network.network"]
@ -517,8 +551,8 @@ func TestAccNetworkMin(t *testing.T) {
ImportState: true, ImportState: true,
Check: resource.ComposeAggregateTestCheckFunc( Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttrSet("stackit_network.network", "network_id"), resource.TestCheckResourceAttrSet("stackit_network.network", "network_id"),
resource.TestCheckResourceAttr("stackit_network.network", "project_id", testutil.ConvertConfigVariable(testConfigNetworkVarsMin["project_id"])), resource.TestCheckResourceAttr("stackit_network.network", "project_id", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMin["project_id"])),
resource.TestCheckResourceAttr("stackit_network.network", "name", testutil.ConvertConfigVariable(testConfigNetworkVarsMin["name"])), resource.TestCheckResourceAttr("stackit_network.network", "name", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMin["name"])),
resource.TestCheckResourceAttrSet("stackit_network.network", "ipv4_prefixes.#"), resource.TestCheckResourceAttrSet("stackit_network.network", "ipv4_prefixes.#"),
resource.TestCheckResourceAttrSet("stackit_network.network", "ipv6_prefixes.#"), resource.TestCheckResourceAttrSet("stackit_network.network", "ipv6_prefixes.#"),
resource.TestCheckResourceAttrSet("stackit_network.network", "public_ip"), resource.TestCheckResourceAttrSet("stackit_network.network", "public_ip"),
@ -530,70 +564,106 @@ func TestAccNetworkMin(t *testing.T) {
}) })
} }
func TestAccNetworkMax(t *testing.T) { func TestAccNetworkV1Max(t *testing.T) {
t.Logf("TestAccNetworkMax name: %s", testutil.ConvertConfigVariable(testConfigNetworkVarsMax["name"])) t.Logf("TestAccNetworkV1Max name: %s", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMax["name"]))
resource.ParallelTest(t, resource.TestCase{ resource.ParallelTest(t, resource.TestCase{
ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories, ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories,
CheckDestroy: testAccCheckDestroy, CheckDestroy: testAccCheckDestroy,
Steps: []resource.TestStep{ Steps: []resource.TestStep{
// Creation // Creation
{ {
ConfigVariables: testConfigNetworkVarsMax, ConfigVariables: testConfigNetworkV1VarsMax,
Config: fmt.Sprintf("%s\n%s", testutil.IaaSProviderConfig(), resourceNetworkMaxConfig), Config: fmt.Sprintf("%s\n%s", testutil.IaaSProviderConfig(), resourceNetworkV1MaxConfig),
Check: resource.ComposeAggregateTestCheckFunc( Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttrSet("stackit_network.network", "network_id"), resource.TestCheckResourceAttrSet("stackit_network.network_prefix", "network_id"),
resource.TestCheckResourceAttr("stackit_network.network", "project_id", testutil.ConvertConfigVariable(testConfigNetworkVarsMax["project_id"])), resource.TestCheckResourceAttr("stackit_network.network_prefix", "project_id", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMax["project_id"])),
resource.TestCheckResourceAttr("stackit_network.network", "name", testutil.ConvertConfigVariable(testConfigNetworkVarsMax["name"])), resource.TestCheckResourceAttr("stackit_network.network_prefix", "name", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMax["name"])),
resource.TestCheckResourceAttr("stackit_network.network", "ipv4_gateway", testutil.ConvertConfigVariable(testConfigNetworkVarsMax["ipv4_gateway"])), resource.TestCheckResourceAttr("stackit_network.network_prefix", "ipv4_gateway", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMax["ipv4_gateway"])),
resource.TestCheckNoResourceAttr("stackit_network.network", "no_ipv4_gateway"), resource.TestCheckNoResourceAttr("stackit_network.network_prefix", "no_ipv4_gateway"),
resource.TestCheckResourceAttr("stackit_network.network", "ipv4_nameservers.#", "1"), resource.TestCheckResourceAttr("stackit_network.network_prefix", "ipv4_nameservers.#", "2"),
resource.TestCheckResourceAttr("stackit_network.network", "ipv4_nameservers.0", testutil.ConvertConfigVariable(testConfigNetworkVarsMax["ipv4_nameservers"])), resource.TestCheckResourceAttr("stackit_network.network_prefix", "ipv4_nameservers.0", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMax["ipv4_nameserver_0"])),
resource.TestCheckResourceAttr("stackit_network.network", "ipv4_prefix", testutil.ConvertConfigVariable(testConfigNetworkVarsMax["ipv4_prefix"])), resource.TestCheckResourceAttr("stackit_network.network_prefix", "ipv4_nameservers.1", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMax["ipv4_nameserver_1"])),
resource.TestCheckResourceAttr("stackit_network.network", "ipv4_prefix_length", testutil.ConvertConfigVariable(testConfigNetworkVarsMax["ipv4_prefix_length"])), resource.TestCheckResourceAttr("stackit_network.network_prefix", "ipv4_prefix_length", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMax["ipv4_prefix_length"])),
resource.TestCheckResourceAttr("stackit_network.network", "ipv4_prefixes.#", "1"), resource.TestCheckResourceAttr("stackit_network.network_prefix", "ipv4_prefix", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMax["ipv4_prefix"])),
resource.TestCheckResourceAttrSet("stackit_network.network", "ipv6_prefixes.#"), resource.TestCheckResourceAttr("stackit_network.network_prefix", "ipv4_prefixes.#", "1"),
resource.TestCheckResourceAttr("stackit_network.network", "routed", testutil.ConvertConfigVariable(testConfigNetworkVarsMax["routed"])), resource.TestCheckResourceAttrSet("stackit_network.network_prefix", "ipv6_prefixes.#"),
resource.TestCheckResourceAttr("stackit_network.network", "labels.acc-test", testutil.ConvertConfigVariable(testConfigNetworkVarsMax["label"])), resource.TestCheckResourceAttr("stackit_network.network_prefix", "routed", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMax["routed"])),
resource.TestCheckNoResourceAttr("stackit_network.network", "public_ip")), resource.TestCheckResourceAttr("stackit_network.network_prefix", "labels.acc-test", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMax["label"])),
resource.TestCheckNoResourceAttr("stackit_network.network_prefix", "public_ip"),
resource.TestCheckResourceAttrSet("stackit_network.network_prefix_length", "network_id"),
resource.TestCheckResourceAttr("stackit_network.network_prefix_length", "project_id", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMax["project_id"])),
resource.TestCheckResourceAttr("stackit_network.network_prefix_length", "name", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMax["name"])),
resource.TestCheckNoResourceAttr("stackit_network.network_prefix_length", "ipv4_gateway"),
resource.TestCheckResourceAttr("stackit_network.network_prefix_length", "no_ipv4_gateway", "true"),
resource.TestCheckResourceAttr("stackit_network.network_prefix_length", "ipv4_nameservers.#", "2"),
resource.TestCheckResourceAttr("stackit_network.network_prefix_length", "ipv4_nameservers.0", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMax["ipv4_nameserver_0"])),
resource.TestCheckResourceAttr("stackit_network.network_prefix_length", "ipv4_nameservers.1", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMax["ipv4_nameserver_1"])),
resource.TestCheckResourceAttr("stackit_network.network_prefix_length", "ipv4_prefix_length", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMax["ipv4_prefix_length"])),
resource.TestCheckResourceAttrSet("stackit_network.network_prefix_length", "ipv4_prefix"),
resource.TestCheckResourceAttrSet("stackit_network.network_prefix_length", "ipv6_prefixes.#"),
resource.TestCheckResourceAttr("stackit_network.network_prefix_length", "routed", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMax["routed"])),
resource.TestCheckResourceAttr("stackit_network.network_prefix_length", "labels.acc-test", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMax["label"])),
resource.TestCheckNoResourceAttr("stackit_network.network_prefix_length", "public_ip"),
),
}, },
// Data source // Data source
{ {
ConfigVariables: testConfigNetworkVarsMax, ConfigVariables: testConfigNetworkV1VarsMax,
Config: fmt.Sprintf(` Config: fmt.Sprintf(`
%s %s
%s %s
data "stackit_network" "network" { data "stackit_network" "network_prefix" {
project_id = stackit_network.network.project_id project_id = stackit_network.network_prefix.project_id
network_id = stackit_network.network.network_id network_id = stackit_network.network_prefix.network_id
}
data "stackit_network" "network_prefix_length" {
project_id = stackit_network.network_prefix_length.project_id
network_id = stackit_network.network_prefix_length.network_id
} }
`, `,
testutil.IaaSProviderConfig(), resourceNetworkMaxConfig, testutil.IaaSProviderConfig(), resourceNetworkV1MaxConfig,
), ),
Check: resource.ComposeAggregateTestCheckFunc( Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttrSet("data.stackit_network.network", "network_id"), resource.TestCheckResourceAttrSet("data.stackit_network.network_prefix", "network_id"),
resource.TestCheckResourceAttr("data.stackit_network.network", "project_id", testutil.ConvertConfigVariable(testConfigNetworkVarsMax["project_id"])), resource.TestCheckResourceAttr("data.stackit_network.network_prefix", "project_id", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMax["project_id"])),
resource.TestCheckResourceAttr("data.stackit_network.network", "name", testutil.ConvertConfigVariable(testConfigNetworkVarsMax["name"])), resource.TestCheckResourceAttr("data.stackit_network.network_prefix", "name", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMax["name"])),
resource.TestCheckResourceAttr("data.stackit_network.network", "ipv4_gateway", testutil.ConvertConfigVariable(testConfigNetworkVarsMax["ipv4_gateway"])), resource.TestCheckResourceAttr("data.stackit_network.network_prefix", "ipv4_gateway", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMax["ipv4_gateway"])),
resource.TestCheckResourceAttr("data.stackit_network.network", "ipv4_nameservers.#", "1"), resource.TestCheckResourceAttr("data.stackit_network.network_prefix", "ipv4_nameservers.#", "2"),
resource.TestCheckResourceAttr("data.stackit_network.network", "ipv4_nameservers.0", testutil.ConvertConfigVariable(testConfigNetworkVarsMax["ipv4_nameservers"])), resource.TestCheckTypeSetElemAttr("data.stackit_network.network_prefix", "ipv4_nameservers.*", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMax["ipv4_nameserver_0"])),
resource.TestCheckResourceAttr("data.stackit_network.network", "ipv4_prefix", testutil.ConvertConfigVariable(testConfigNetworkVarsMax["ipv4_prefix"])), resource.TestCheckTypeSetElemAttr("data.stackit_network.network_prefix", "ipv4_nameservers.*", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMax["ipv4_nameserver_1"])),
resource.TestCheckResourceAttr("data.stackit_network.network", "ipv4_prefix_length", testutil.ConvertConfigVariable(testConfigNetworkVarsMax["ipv4_prefix_length"])), resource.TestCheckResourceAttr("data.stackit_network.network_prefix", "ipv4_prefix", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMax["ipv4_prefix"])),
resource.TestCheckResourceAttr("data.stackit_network.network", "ipv4_prefixes.#", "1"), resource.TestCheckResourceAttr("data.stackit_network.network_prefix", "ipv4_prefix_length", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMax["ipv4_prefix_length"])),
resource.TestCheckResourceAttrSet("data.stackit_network.network", "ipv6_prefixes.#"), resource.TestCheckResourceAttr("data.stackit_network.network_prefix", "ipv4_prefixes.#", "1"),
resource.TestCheckResourceAttr("data.stackit_network.network", "routed", testutil.ConvertConfigVariable(testConfigNetworkVarsMax["routed"])), resource.TestCheckResourceAttrSet("data.stackit_network.network_prefix", "ipv6_prefixes.#"),
resource.TestCheckResourceAttr("data.stackit_network.network", "labels.acc-test", testutil.ConvertConfigVariable(testConfigNetworkVarsMax["label"])), resource.TestCheckResourceAttr("data.stackit_network.network_prefix", "routed", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMax["routed"])),
resource.TestCheckResourceAttr("data.stackit_network.network_prefix", "labels.acc-test", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMax["label"])),
resource.TestCheckResourceAttrSet("data.stackit_network.network_prefix_length", "network_id"),
resource.TestCheckResourceAttr("data.stackit_network.network_prefix_length", "project_id", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMax["project_id"])),
resource.TestCheckResourceAttr("data.stackit_network.network_prefix_length", "name", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMax["name"])),
resource.TestCheckNoResourceAttr("data.stackit_network.network_prefix_length", "ipv4_gateway"),
resource.TestCheckResourceAttr("data.stackit_network.network_prefix_length", "ipv4_nameservers.#", "2"),
resource.TestCheckTypeSetElemAttr("data.stackit_network.network_prefix_length", "ipv4_nameservers.*", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMax["ipv4_nameserver_0"])),
resource.TestCheckTypeSetElemAttr("data.stackit_network.network_prefix_length", "ipv4_nameservers.*", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMax["ipv4_nameserver_1"])),
resource.TestCheckResourceAttr("data.stackit_network.network_prefix_length", "ipv4_prefix_length", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMax["ipv4_prefix_length"])),
resource.TestCheckResourceAttr("data.stackit_network.network_prefix_length", "ipv4_prefixes.#", "1"),
resource.TestCheckResourceAttrSet("data.stackit_network.network_prefix_length", "ipv4_prefix"),
resource.TestCheckResourceAttrSet("data.stackit_network.network_prefix_length", "ipv6_prefixes.#"),
resource.TestCheckResourceAttr("data.stackit_network.network_prefix_length", "routed", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMax["routed"])),
resource.TestCheckResourceAttr("data.stackit_network.network_prefix_length", "labels.acc-test", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMax["label"])),
), ),
}, },
// Import // Import
{ {
ConfigVariables: testConfigNetworkVarsMax, ConfigVariables: testConfigNetworkV1VarsMax,
ResourceName: "stackit_network.network", ResourceName: "stackit_network.network_prefix",
ImportStateIdFunc: func(s *terraform.State) (string, error) { ImportStateIdFunc: func(s *terraform.State) (string, error) {
r, ok := s.RootModule().Resources["stackit_network.network"] r, ok := s.RootModule().Resources["stackit_network.network_prefix"]
if !ok { if !ok {
return "", fmt.Errorf("couldn't find resource stackit_network.network") return "", fmt.Errorf("couldn't find resource stackit_network.network_prefix")
} }
networkId, ok := r.Primary.Attributes["network_id"] networkId, ok := r.Primary.Attributes["network_id"]
if !ok { if !ok {
@ -603,39 +673,449 @@ func TestAccNetworkMax(t *testing.T) {
}, },
ImportState: true, ImportState: true,
Check: resource.ComposeAggregateTestCheckFunc( Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttrSet("data.stackit_network.network", "network_id"), resource.TestCheckResourceAttrSet("stackit_network.network_prefix", "network_id"),
resource.TestCheckResourceAttr("data.stackit_network.network", "name", networkResource["name"]), resource.TestCheckResourceAttr("stackit_network.network_prefix", "name", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMax["project_id"])),
resource.TestCheckResourceAttr("data.stackit_network.network", "ipv4_gateway", networkResource["ipv4_gateway"]), resource.TestCheckResourceAttr("stackit_network.network_prefix", "ipv4_gateway", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMax["ipv4_gateway"])),
resource.TestCheckResourceAttr("data.stackit_network.network", "ipv4_nameservers.#", "2"), resource.TestCheckResourceAttr("stackit_network.network_prefix", "ipv4_nameservers.#", "2"),
// nameservers may be returned in a randomized order, so we have to check them with a helper function // nameservers may be returned in a randomized order, so we have to check them with a helper function
resource.TestCheckTypeSetElemAttr("stackit_network.network", "nameservers.*", networkResource["nameserver0"]), resource.TestCheckTypeSetElemAttr("stackit_network.network_prefix", "nameservers.*", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMax["ipv4_nameserver_0"])),
resource.TestCheckTypeSetElemAttr("stackit_network.network", "nameservers.*", networkResource["nameserver1"]), resource.TestCheckTypeSetElemAttr("stackit_network.network_prefix", "nameservers.*", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMax["ipv4_nameserver_1"])),
resource.TestCheckResourceAttr("data.stackit_network.network", "ipv4_prefix", networkResource["ipv4_prefix"]), resource.TestCheckResourceAttr("stackit_network.network_prefix", "ipv4_prefix", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMax["ipv4_prefix"])),
resource.TestCheckResourceAttr("data.stackit_network.network", "ipv4_prefix_length", networkResource["ipv4_prefix_length"]), resource.TestCheckResourceAttr("stackit_network.network_prefix", "ipv4_prefix_length", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMax["ipv4_prefix_length"])),
resource.TestCheckResourceAttr("data.stackit_network.network", "ipv4_prefixes.#", "1"), resource.TestCheckResourceAttr("stackit_network.network_prefix", "ipv4_prefixes.#", "1"),
resource.TestCheckResourceAttr("data.stackit_network.network", "ipv4_prefixes.0", networkResource["ipv4_prefix"]), resource.TestCheckResourceAttr("stackit_network.network_prefix", "ipv4_prefixes.0", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMax["ipv4_prefix"])),
resource.TestCheckResourceAttr("data.stackit_network.network", "routed", networkResource["routed"]), resource.TestCheckResourceAttr("stackit_network.network_prefix", "routed", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMax["routed"])),
),
},
{
ConfigVariables: testConfigNetworkV1VarsMax,
ResourceName: "stackit_network.network_prefix_length",
ImportStateIdFunc: func(s *terraform.State) (string, error) {
r, ok := s.RootModule().Resources["stackit_network.network_prefix_length"]
if !ok {
return "", fmt.Errorf("couldn't find resource stackit_network.network_prefix_length")
}
networkId, ok := r.Primary.Attributes["network_id"]
if !ok {
return "", fmt.Errorf("couldn't find attribute network_id")
}
return fmt.Sprintf("%s,%s", testutil.ProjectId, networkId), nil
},
ImportState: true,
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttrSet("stackit_network.network_prefix_length", "network_id"),
resource.TestCheckResourceAttr("stackit_network.network_prefix_length", "name", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMax["project_id"])),
resource.TestCheckNoResourceAttr("stackit_network.network_prefix_length", "ipv4_gateway"),
resource.TestCheckResourceAttr("stackit_network.network_prefix_length", "ipv4_nameservers.#", "2"),
// nameservers may be returned in a randomized order, so we have to check them with a helper function
resource.TestCheckTypeSetElemAttr("stackit_network.network_prefix_length", "nameservers.*", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMax["ipv4_nameserver_0"])),
resource.TestCheckTypeSetElemAttr("stackit_network.network_prefix_length", "nameservers.*", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMax["ipv4_nameserver_1"])),
resource.TestCheckResourceAttrSet("stackit_network.network_prefix_length", "ipv4_prefix"),
resource.TestCheckResourceAttr("stackit_network.network_prefix_length", "ipv4_prefix_length", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMax["ipv4_prefix_length"])),
resource.TestCheckResourceAttr("stackit_network.network_prefix_length", "ipv4_prefixes.#", "1"),
resource.TestCheckResourceAttr("stackit_network.network_prefix_length", "routed", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMax["routed"])),
), ),
}, },
// Update // Update
{ {
ConfigVariables: testConfigNetworkVarsMaxUpdated, ConfigVariables: testConfigNetworkV1VarsMaxUpdated,
Config: fmt.Sprintf("%s\n%s", testutil.IaaSProviderConfig(), resourceNetworkMaxConfig), Config: fmt.Sprintf("%s\n%s", testutil.IaaSProviderConfig(), resourceNetworkV1MaxConfig),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttrSet("stackit_network.network_prefix", "network_id"),
resource.TestCheckResourceAttr("stackit_network.network_prefix", "project_id", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMaxUpdated["project_id"])),
resource.TestCheckResourceAttr("stackit_network.network_prefix", "name", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMaxUpdated["name"])),
resource.TestCheckNoResourceAttr("stackit_network.network_prefix", "ipv4_gateway"),
resource.TestCheckResourceAttr("stackit_network.network_prefix", "no_ipv4_gateway", "true"),
resource.TestCheckResourceAttr("stackit_network.network_prefix", "ipv4_nameservers.#", "2"),
resource.TestCheckResourceAttr("stackit_network.network_prefix", "ipv4_nameservers.0", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMaxUpdated["ipv4_nameserver_0"])),
resource.TestCheckResourceAttr("stackit_network.network_prefix", "ipv4_nameservers.1", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMaxUpdated["ipv4_nameserver_1"])),
resource.TestCheckResourceAttr("stackit_network.network_prefix", "ipv4_prefix", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMaxUpdated["ipv4_prefix"])),
resource.TestCheckResourceAttr("stackit_network.network_prefix", "ipv4_prefix_length", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMaxUpdated["ipv4_prefix_length"])),
resource.TestCheckResourceAttr("stackit_network.network_prefix", "ipv4_prefixes.#", "1"),
resource.TestCheckResourceAttrSet("stackit_network.network_prefix", "ipv6_prefixes.#"),
resource.TestCheckResourceAttr("stackit_network.network_prefix", "routed", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMaxUpdated["routed"])),
resource.TestCheckResourceAttr("stackit_network.network_prefix", "labels.acc-test", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMaxUpdated["label"])),
resource.TestCheckNoResourceAttr("stackit_network.network_prefix", "public_ip"),
resource.TestCheckResourceAttrSet("stackit_network.network_prefix_length", "network_id"),
resource.TestCheckResourceAttr("stackit_network.network_prefix_length", "project_id", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMaxUpdated["project_id"])),
resource.TestCheckResourceAttr("stackit_network.network_prefix_length", "name", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMaxUpdated["name"])),
resource.TestCheckNoResourceAttr("stackit_network.network_prefix_length", "ipv4_gateway"),
resource.TestCheckResourceAttr("stackit_network.network_prefix_length", "no_ipv4_gateway", "true"),
resource.TestCheckResourceAttr("stackit_network.network_prefix_length", "ipv4_nameservers.#", "2"),
resource.TestCheckResourceAttr("stackit_network.network_prefix_length", "ipv4_nameservers.0", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMaxUpdated["ipv4_nameserver_0"])),
resource.TestCheckResourceAttr("stackit_network.network_prefix_length", "ipv4_nameservers.1", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMaxUpdated["ipv4_nameserver_1"])),
resource.TestCheckResourceAttr("stackit_network.network_prefix_length", "ipv4_prefix_length", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMaxUpdated["ipv4_prefix_length"])),
resource.TestCheckResourceAttrSet("stackit_network.network_prefix_length", "ipv4_prefix"),
resource.TestCheckResourceAttrSet("stackit_network.network_prefix_length", "ipv6_prefixes.#"),
resource.TestCheckResourceAttr("stackit_network.network_prefix_length", "routed", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMaxUpdated["routed"])),
resource.TestCheckResourceAttr("stackit_network.network_prefix_length", "labels.acc-test", testutil.ConvertConfigVariable(testConfigNetworkV1VarsMaxUpdated["label"])),
resource.TestCheckNoResourceAttr("stackit_network.network_prefix_length", "public_ip"),
),
},
// Deletion is done by the framework implicitly
},
})
}
func TestAccNetworkV2Min(t *testing.T) {
t.Logf("TestAccNetworkV2Min name: %s", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMin["name"]))
resource.ParallelTest(t, resource.TestCase{
ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories,
CheckDestroy: testAccCheckNetworkV2Destroy,
Steps: []resource.TestStep{
// Creation
{
ConfigVariables: testConfigNetworkV2VarsMin,
Config: fmt.Sprintf("%s\n%s", testutil.IaaSProviderConfigWithExperiments(), resourceNetworkV2MinConfig),
Check: resource.ComposeAggregateTestCheckFunc( Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttrSet("stackit_network.network", "network_id"), resource.TestCheckResourceAttrSet("stackit_network.network", "network_id"),
resource.TestCheckResourceAttr("stackit_network.network", "project_id", testutil.ConvertConfigVariable(testConfigNetworkVarsMaxUpdated["project_id"])), resource.TestCheckResourceAttr("stackit_network.network", "project_id", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMin["project_id"])),
resource.TestCheckResourceAttr("stackit_network.network", "name", testutil.ConvertConfigVariable(testConfigNetworkVarsMaxUpdated["name"])), resource.TestCheckResourceAttr("stackit_network.network", "name", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMin["name"])),
resource.TestCheckNoResourceAttr("stackit_network.network", "ipv4_gateway"), resource.TestCheckResourceAttrSet("stackit_network.network", "ipv4_prefixes.#"),
resource.TestCheckResourceAttr("stackit_network.network", "no_ipv4_gateway", "true"),
resource.TestCheckResourceAttr("stackit_network.network", "ipv4_nameservers.#", "1"),
resource.TestCheckResourceAttr("stackit_network.network", "ipv4_nameservers.0", testutil.ConvertConfigVariable(testConfigNetworkVarsMaxUpdated["ipv4_nameservers"])),
resource.TestCheckResourceAttr("stackit_network.network", "ipv4_prefix", testutil.ConvertConfigVariable(testConfigNetworkVarsMaxUpdated["ipv4_prefix"])),
resource.TestCheckResourceAttr("stackit_network.network", "ipv4_prefix_length", testutil.ConvertConfigVariable(testConfigNetworkVarsMaxUpdated["ipv4_prefix_length"])),
resource.TestCheckResourceAttr("stackit_network.network", "ipv4_prefixes.#", "1"),
resource.TestCheckResourceAttrSet("stackit_network.network", "ipv6_prefixes.#"), resource.TestCheckResourceAttrSet("stackit_network.network", "ipv6_prefixes.#"),
resource.TestCheckResourceAttr("stackit_network.network", "routed", testutil.ConvertConfigVariable(testConfigNetworkVarsMaxUpdated["routed"])), resource.TestCheckResourceAttrSet("stackit_network.network", "public_ip"),
resource.TestCheckResourceAttr("stackit_network.network", "labels.acc-test", testutil.ConvertConfigVariable(testConfigNetworkVarsMaxUpdated["label"])), resource.TestCheckResourceAttrSet("stackit_network.network", "region"),
resource.TestCheckNoResourceAttr("stackit_network.network", "public_ip"), resource.TestCheckResourceAttrSet("stackit_network.network", "routing_table_id"),
),
},
// Data source
{
ConfigVariables: testConfigNetworkV2VarsMin,
Config: fmt.Sprintf(`
%s
%s
data "stackit_network" "network" {
project_id = stackit_network.network.project_id
network_id = stackit_network.network.network_id
}
`,
testutil.IaaSProviderConfigWithExperiments(), resourceNetworkV2MinConfig,
),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttrSet("data.stackit_network.network", "network_id"),
resource.TestCheckResourceAttr("data.stackit_network.network", "project_id", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMin["project_id"])),
resource.TestCheckResourceAttr("data.stackit_network.network", "name", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMin["name"])),
resource.TestCheckResourceAttrSet("data.stackit_network.network", "ipv4_prefixes.#"),
resource.TestCheckResourceAttrSet("data.stackit_network.network", "ipv6_prefixes.#"),
resource.TestCheckResourceAttrSet("data.stackit_network.network", "public_ip"),
resource.TestCheckResourceAttrSet("data.stackit_network.network", "region"),
resource.TestCheckResourceAttrSet("data.stackit_network.network", "routing_table_id"),
),
},
// Import
{
ConfigVariables: testConfigNetworkV2VarsMin,
ResourceName: "stackit_network.network",
ImportStateIdFunc: func(s *terraform.State) (string, error) {
r, ok := s.RootModule().Resources["stackit_network.network"]
if !ok {
return "", fmt.Errorf("couldn't find resource stackit_network.network")
}
region, ok := r.Primary.Attributes["region"]
if !ok {
return "", fmt.Errorf("couldn't find attribute region")
}
networkId, ok := r.Primary.Attributes["network_id"]
if !ok {
return "", fmt.Errorf("couldn't find attribute network_id")
}
return fmt.Sprintf("%s,%s,%s", testutil.ProjectId, region, networkId), nil
},
ImportState: true,
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttrSet("stackit_network.network", "network_id"),
resource.TestCheckResourceAttr("stackit_network.network", "project_id", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMin["project_id"])),
resource.TestCheckResourceAttr("stackit_network.network", "name", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMin["name"])),
resource.TestCheckResourceAttrSet("stackit_network.network", "ipv4_prefixes.#"),
resource.TestCheckResourceAttrSet("stackit_network.network", "ipv6_prefixes.#"),
resource.TestCheckResourceAttrSet("stackit_network.network", "public_ip"),
resource.TestCheckResourceAttrSet("stackit_network.network", "region"),
resource.TestCheckResourceAttrSet("stackit_network.network", "routing_table_id"),
),
},
// Update
{
ConfigVariables: testConfigNetworkV2VarsMinUpdated,
Config: fmt.Sprintf("%s\n%s", testutil.IaaSProviderConfigWithExperiments(), resourceNetworkV2MinConfig),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttrSet("stackit_network.network", "network_id"),
resource.TestCheckResourceAttr("stackit_network.network", "project_id", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMinUpdated["project_id"])),
resource.TestCheckResourceAttr("stackit_network.network", "name", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMinUpdated["name"])),
resource.TestCheckResourceAttrSet("stackit_network.network", "ipv4_prefixes.#"),
resource.TestCheckResourceAttrSet("stackit_network.network", "ipv6_prefixes.#"),
resource.TestCheckResourceAttrSet("stackit_network.network", "public_ip"),
resource.TestCheckResourceAttrSet("stackit_network.network", "region"),
resource.TestCheckResourceAttrSet("stackit_network.network", "routing_table_id"),
),
},
// Deletion is done by the framework implicitly
},
})
}
func TestAccNetworkV2Max(t *testing.T) {
t.Logf("TestAccNetworkV2Max name: %s", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMax["name"]))
resource.ParallelTest(t, resource.TestCase{
ProtoV6ProviderFactories: testutil.TestAccProtoV6ProviderFactories,
CheckDestroy: testAccCheckNetworkV2Destroy,
Steps: []resource.TestStep{
// Creation
{
ConfigVariables: testConfigNetworkV2VarsMax,
Config: fmt.Sprintf("%s\n%s", testutil.IaaSProviderConfigWithExperiments(), resourceNetworkV2MaxConfig),
Check: resource.ComposeAggregateTestCheckFunc(
// TODO: enable test cases for prefix option, when the API works again
// Network with prefix
// resource.TestCheckResourceAttrSet("stackit_network.network_prefix", "network_id"),
// resource.TestCheckResourceAttr("stackit_network.network_prefix", "project_id", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMax["project_id"])),
// resource.TestCheckResourceAttr("stackit_network.network_prefix", "name", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMax["name"])),
// resource.TestCheckResourceAttr("stackit_network.network_prefix", "ipv4_gateway", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMax["ipv4_gateway"])),
// resource.TestCheckNoResourceAttr("stackit_network.network_prefix", "no_ipv4_gateway"),
// resource.TestCheckResourceAttr("stackit_network.network_prefix", "ipv4_nameservers.#", "2"),
// resource.TestCheckResourceAttr("stackit_network.network_prefix", "ipv4_nameservers.0", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMax["ipv4_nameserver_0"])),
// resource.TestCheckResourceAttr("stackit_network.network_prefix", "ipv4_nameservers.1", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMax["ipv4_nameserver_1"])),
// resource.TestCheckResourceAttr("stackit_network.network_prefix", "ipv4_prefix_length", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMax["ipv4_prefix_length"])),
// resource.TestCheckResourceAttr("stackit_network.network_prefix", "ipv4_prefix", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMax["ipv4_prefix"])),
// resource.TestCheckResourceAttr("stackit_network.network_prefix", "ipv4_prefixes.#", "1"),
// resource.TestCheckResourceAttrSet("stackit_network.network_prefix", "ipv6_prefixes.#"),
// resource.TestCheckResourceAttr("stackit_network.network_prefix", "routed", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMax["routed"])),
// resource.TestCheckResourceAttr("stackit_network.network_prefix", "labels.acc-test", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMax["label"])),
// resource.TestCheckNoResourceAttr("stackit_network.network_prefix", "public_ip"),
// Network with prefix_length
resource.TestCheckResourceAttrSet("stackit_network.network_prefix_length", "network_id"),
resource.TestCheckResourceAttr("stackit_network.network_prefix_length", "project_id", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMax["project_id"])),
resource.TestCheckResourceAttr("stackit_network.network_prefix_length", "name", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMax["name"])),
// resource.TestCheckResourceAttrSet("stackit_network.network_prefix_length", "ipv4_gateway"),
// resource.TestCheckResourceAttr("stackit_network.network_prefix_length", "no_ipv4_gateway", "true"),
resource.TestCheckResourceAttr("stackit_network.network_prefix_length", "ipv4_nameservers.#", "2"),
resource.TestCheckResourceAttr("stackit_network.network_prefix_length", "ipv4_nameservers.0", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMax["ipv4_nameserver_0"])),
resource.TestCheckResourceAttr("stackit_network.network_prefix_length", "ipv4_nameservers.1", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMax["ipv4_nameserver_1"])),
resource.TestCheckResourceAttr("stackit_network.network_prefix_length", "ipv4_prefix_length", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMax["ipv4_prefix_length"])),
resource.TestCheckResourceAttrSet("stackit_network.network_prefix_length", "ipv4_prefix"),
resource.TestCheckResourceAttrSet("stackit_network.network_prefix_length", "ipv6_prefixes.#"),
resource.TestCheckResourceAttr("stackit_network.network_prefix_length", "routed", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMax["routed"])),
resource.TestCheckResourceAttr("stackit_network.network_prefix_length", "labels.acc-test", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMax["label"])),
resource.TestCheckResourceAttrSet("stackit_network.network_prefix_length", "public_ip"),
resource.TestCheckResourceAttr("stackit_network.network_prefix_length", "region", testutil.Region),
resource.TestCheckResourceAttrPair(
"stackit_network.network_prefix_length", "routing_table_id",
"stackit_routing_table.routing_table", "routing_table_id",
),
// Routing table
resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "organization_id", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMax["organization_id"])),
resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "network_area_id", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMax["network_area_id"])),
resource.TestCheckResourceAttrSet("stackit_routing_table.routing_table", "routing_table_id"),
resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "name", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMax["name"])),
resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "labels.%", "0"),
resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "region", testutil.Region),
resource.TestCheckNoResourceAttr("stackit_routing_table.routing_table", "description"),
resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "system_routes", "true"),
resource.TestCheckResourceAttrSet("stackit_routing_table.routing_table", "created_at"),
resource.TestCheckResourceAttrSet("stackit_routing_table.routing_table", "updated_at"),
),
},
// Data source
{
ConfigVariables: testConfigNetworkV2VarsMax,
Config: fmt.Sprintf(`
%s
%s
//data "stackit_network" "network_prefix" {
// project_id = stackit_network.network_prefix.project_id
// network_id = stackit_network.network_prefix.network_id
//}
data "stackit_network" "network_prefix_length" {
project_id = stackit_network.network_prefix_length.project_id
network_id = stackit_network.network_prefix_length.network_id
}
data "stackit_routing_table" "routing_table" {
organization_id = stackit_routing_table.routing_table.organization_id
network_area_id = stackit_routing_table.routing_table.network_area_id
routing_table_id = stackit_routing_table.routing_table.routing_table_id
}
`,
testutil.IaaSProviderConfigWithExperiments(), resourceNetworkV2MaxConfig,
),
Check: resource.ComposeAggregateTestCheckFunc(
// TODO: enable test cases for prefix option, when the API works again
// Network with prefix
// resource.TestCheckResourceAttrSet("data.stackit_network.network_prefix", "network_id"),
// resource.TestCheckResourceAttr("data.stackit_network.network_prefix", "project_id", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMax["project_id"])),
// resource.TestCheckResourceAttr("data.stackit_network.network_prefix", "name", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMax["name"])),
// resource.TestCheckResourceAttr("data.stackit_network.network_prefix", "ipv4_gateway", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMax["ipv4_gateway"])),
// resource.TestCheckResourceAttr("data.stackit_network.network_prefix", "ipv4_nameservers.#", "2"),
// resource.TestCheckTypeSetElemAttr("data.stackit_network.network_prefix", "ipv4_nameservers.*", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMax["ipv4_nameserver_0"])),
// resource.TestCheckTypeSetElemAttr("data.stackit_network.network_prefix", "ipv4_nameservers.*", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMax["ipv4_nameserver_1"])),
// resource.TestCheckResourceAttr("data.stackit_network.network_prefix", "ipv4_prefix", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMax["ipv4_prefix"])),
// resource.TestCheckResourceAttr("data.stackit_network.network_prefix", "ipv4_prefix_length", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMax["ipv4_prefix_length"])),
// resource.TestCheckResourceAttr("data.stackit_network.network_prefix", "ipv4_prefixes.#", "1"),
// resource.TestCheckResourceAttrSet("data.stackit_network.network_prefix", "ipv6_prefixes.#"),
// resource.TestCheckResourceAttr("data.stackit_network.network_prefix", "routed", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMax["routed"])),
// resource.TestCheckResourceAttr("data.stackit_network.network_prefix", "labels.acc-test", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMax["label"])),
// Network with prefix_length
resource.TestCheckResourceAttrSet("data.stackit_network.network_prefix_length", "network_id"),
resource.TestCheckResourceAttr("data.stackit_network.network_prefix_length", "project_id", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMax["project_id"])),
resource.TestCheckResourceAttr("data.stackit_network.network_prefix_length", "name", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMax["name"])),
// resource.TestCheckNoResourceAttr("data.stackit_network.network_prefix_length", "ipv4_gateway"),
resource.TestCheckResourceAttr("data.stackit_network.network_prefix_length", "ipv4_nameservers.#", "2"),
resource.TestCheckTypeSetElemAttr("data.stackit_network.network_prefix_length", "ipv4_nameservers.*", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMax["ipv4_nameserver_0"])),
resource.TestCheckTypeSetElemAttr("data.stackit_network.network_prefix_length", "ipv4_nameservers.*", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMax["ipv4_nameserver_1"])),
resource.TestCheckResourceAttr("data.stackit_network.network_prefix_length", "ipv4_prefix_length", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMax["ipv4_prefix_length"])),
resource.TestCheckResourceAttr("data.stackit_network.network_prefix_length", "ipv4_prefixes.#", "1"),
resource.TestCheckResourceAttrSet("data.stackit_network.network_prefix_length", "ipv4_prefix"),
resource.TestCheckResourceAttrSet("data.stackit_network.network_prefix_length", "ipv6_prefixes.#"),
resource.TestCheckResourceAttr("data.stackit_network.network_prefix_length", "routed", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMax["routed"])),
resource.TestCheckResourceAttr("data.stackit_network.network_prefix_length", "labels.acc-test", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMax["label"])),
resource.TestCheckResourceAttr("data.stackit_network.network_prefix_length", "region", testutil.Region),
resource.TestCheckResourceAttrPair(
"data.stackit_network.network_prefix_length", "routing_table_id",
"data.stackit_routing_table.routing_table", "routing_table_id",
),
// Routing table
resource.TestCheckResourceAttr("data.stackit_routing_table.routing_table", "organization_id", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMax["organization_id"])),
resource.TestCheckResourceAttr("data.stackit_routing_table.routing_table", "network_area_id", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMax["network_area_id"])),
resource.TestCheckResourceAttrSet("data.stackit_routing_table.routing_table", "routing_table_id"),
resource.TestCheckResourceAttr("data.stackit_routing_table.routing_table", "name", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMax["name"])),
resource.TestCheckResourceAttr("data.stackit_routing_table.routing_table", "labels.%", "0"),
resource.TestCheckResourceAttr("data.stackit_routing_table.routing_table", "region", testutil.Region),
resource.TestCheckNoResourceAttr("data.stackit_routing_table.routing_table", "description"),
resource.TestCheckResourceAttr("data.stackit_routing_table.routing_table", "system_routes", "true"),
resource.TestCheckResourceAttrSet("data.stackit_routing_table.routing_table", "created_at"),
resource.TestCheckResourceAttrSet("data.stackit_routing_table.routing_table", "updated_at"),
),
},
// Import
// TODO: enable test cases for prefix option, when the API works again
//{
// ConfigVariables: testConfigNetworkV2VarsMax,
// ResourceName: "stackit_network.network_prefix",
// ImportStateIdFunc: func(s *terraform.State) (string, error) {
// r, ok := s.RootModule().Resources["stackit_network.network_prefix"]
// if !ok {
// return "", fmt.Errorf("couldn't find resource stackit_network.network_prefix")
// }
// networkId, ok := r.Primary.Attributes["network_id"]
// if !ok {
// return "", fmt.Errorf("couldn't find attribute network_id")
// }
// return fmt.Sprintf("%s,%s", testutil.ProjectId, networkId), nil
// },
// ImportState: true,
// Check: resource.ComposeAggregateTestCheckFunc(
// resource.TestCheckResourceAttrSet("stackit_network.network_prefix", "network_id"),
// resource.TestCheckResourceAttr("stackit_network.network_prefix", "name", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMax["project_id"])),
// resource.TestCheckResourceAttr("stackit_network.network_prefix", "ipv4_gateway", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMax["ipv4_gateway"])),
// resource.TestCheckResourceAttr("stackit_network.network_prefix", "ipv4_nameservers.#", "2"),
// // nameservers may be returned in a randomized order, so we have to check them with a helper function
// resource.TestCheckTypeSetElemAttr("stackit_network.network_prefix", "nameservers.*", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMax["ipv4_nameserver_0"])),
// resource.TestCheckTypeSetElemAttr("stackit_network.network_prefix", "nameservers.*", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMax["ipv4_nameserver_1"])),
// resource.TestCheckResourceAttr("stackit_network.network_prefix", "ipv4_prefix", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMax["ipv4_prefix"])),
// resource.TestCheckResourceAttr("stackit_network.network_prefix", "ipv4_prefix_length", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMax["ipv4_prefix_length"])),
// resource.TestCheckResourceAttr("stackit_network.network_prefix", "ipv4_prefixes.#", "1"),
// resource.TestCheckResourceAttr("stackit_network.network_prefix", "ipv4_prefixes.0", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMax["ipv4_prefix"])),
// resource.TestCheckResourceAttr("stackit_network.network_prefix", "routed", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMax["routed"])),
// ),
// },
{
ConfigVariables: testConfigNetworkV2VarsMax,
ResourceName: "stackit_network.network_prefix_length",
ImportStateIdFunc: func(s *terraform.State) (string, error) {
r, ok := s.RootModule().Resources["stackit_network.network_prefix_length"]
if !ok {
return "", fmt.Errorf("couldn't find resource stackit_network.network_prefix_length")
}
region, ok := r.Primary.Attributes["region"]
if !ok {
return "", fmt.Errorf("couldn't find attribute region")
}
networkId, ok := r.Primary.Attributes["network_id"]
if !ok {
return "", fmt.Errorf("couldn't find attribute network_id")
}
return fmt.Sprintf("%s,%s,%s", testutil.ProjectId, region, networkId), nil
},
ImportState: true,
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttrSet("stackit_network.network_prefix_length", "network_id"),
resource.TestCheckResourceAttr("stackit_network.network_prefix_length", "name", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMax["project_id"])),
// resource.TestCheckNoResourceAttr("stackit_network.network_prefix_length", "ipv4_gateway"),
resource.TestCheckResourceAttr("stackit_network.network_prefix_length", "ipv4_nameservers.#", "2"),
// nameservers may be returned in a randomized order, so we have to check them with a helper function
resource.TestCheckTypeSetElemAttr("stackit_network.network_prefix_length", "nameservers.*", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMax["ipv4_nameserver_0"])),
resource.TestCheckTypeSetElemAttr("stackit_network.network_prefix_length", "nameservers.*", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMax["ipv4_nameserver_1"])),
resource.TestCheckResourceAttrSet("stackit_network.network_prefix_length", "ipv4_prefix"),
resource.TestCheckResourceAttr("stackit_network.network_prefix_length", "ipv4_prefix_length", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMax["ipv4_prefix_length"])),
resource.TestCheckResourceAttr("stackit_network.network_prefix_length", "ipv4_prefixes.#", "1"),
resource.TestCheckResourceAttr("stackit_network.network_prefix_length", "routed", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMax["routed"])),
resource.TestCheckResourceAttr("stackit_network.network_prefix_length", "region", testutil.Region),
),
},
// Update
{
ConfigVariables: testConfigNetworkV2VarsMaxUpdated,
Config: fmt.Sprintf("%s\n%s", testutil.IaaSProviderConfigWithExperiments(), resourceNetworkV2MaxConfig),
Check: resource.ComposeAggregateTestCheckFunc(
// TODO: enable test cases for prefix option, when the API works again
// resource.TestCheckResourceAttrSet("stackit_network.network_prefix", "network_id"),
// resource.TestCheckResourceAttr("stackit_network.network_prefix", "project_id", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMaxUpdated["project_id"])),
// resource.TestCheckResourceAttr("stackit_network.network_prefix", "name", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMaxUpdated["name"])),
// resource.TestCheckNoResourceAttr("stackit_network.network_prefix", "ipv4_gateway"),
// resource.TestCheckResourceAttr("stackit_network.network_prefix", "no_ipv4_gateway", "true"),
// resource.TestCheckResourceAttr("stackit_network.network_prefix", "ipv4_nameservers.#", "2"),
// resource.TestCheckResourceAttr("stackit_network.network_prefix", "ipv4_nameservers.0", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMaxUpdated["ipv4_nameserver_0"])),
// resource.TestCheckResourceAttr("stackit_network.network_prefix", "ipv4_nameservers.1", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMaxUpdated["ipv4_nameserver_1"])),
// resource.TestCheckResourceAttr("stackit_network.network_prefix", "ipv4_prefix", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMaxUpdated["ipv4_prefix"])),
// resource.TestCheckResourceAttr("stackit_network.network_prefix", "ipv4_prefix_length", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMaxUpdated["ipv4_prefix_length"])),
// resource.TestCheckResourceAttr("stackit_network.network_prefix", "ipv4_prefixes.#", "1"),
// resource.TestCheckResourceAttrSet("stackit_network.network_prefix", "ipv6_prefixes.#"),
// resource.TestCheckResourceAttr("stackit_network.network_prefix", "routed", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMaxUpdated["routed"])),
// resource.TestCheckResourceAttr("stackit_network.network_prefix", "labels.acc-test", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMaxUpdated["label"])),
// resource.TestCheckNoResourceAttr("stackit_network.network_prefix", "public_ip"),
resource.TestCheckResourceAttrSet("stackit_network.network_prefix_length", "network_id"),
resource.TestCheckResourceAttr("stackit_network.network_prefix_length", "project_id", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMaxUpdated["project_id"])),
resource.TestCheckResourceAttr("stackit_network.network_prefix_length", "name", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMaxUpdated["name"])),
// resource.TestCheckNoResourceAttr("stackit_network.network_prefix_length", "ipv4_gateway"),
// resource.TestCheckResourceAttr("stackit_network.network_prefix_length", "no_ipv4_gateway", "true"),
resource.TestCheckResourceAttr("stackit_network.network_prefix_length", "ipv4_nameservers.#", "2"),
resource.TestCheckResourceAttr("stackit_network.network_prefix_length", "ipv4_nameservers.0", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMaxUpdated["ipv4_nameserver_0"])),
resource.TestCheckResourceAttr("stackit_network.network_prefix_length", "ipv4_nameservers.1", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMaxUpdated["ipv4_nameserver_1"])),
resource.TestCheckResourceAttr("stackit_network.network_prefix_length", "ipv4_prefix_length", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMaxUpdated["ipv4_prefix_length"])),
resource.TestCheckResourceAttrSet("stackit_network.network_prefix_length", "ipv4_prefix"),
resource.TestCheckResourceAttrSet("stackit_network.network_prefix_length", "ipv6_prefixes.#"),
resource.TestCheckResourceAttr("stackit_network.network_prefix_length", "routed", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMaxUpdated["routed"])),
resource.TestCheckResourceAttr("stackit_network.network_prefix_length", "labels.acc-test", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMaxUpdated["label"])),
resource.TestCheckResourceAttrSet("stackit_network.network_prefix_length", "public_ip"),
resource.TestCheckResourceAttr("stackit_network.network_prefix_length", "region", testutil.Region),
resource.TestCheckResourceAttrPair(
"stackit_network.network_prefix_length", "routing_table_id",
"stackit_routing_table.routing_table", "routing_table_id",
),
// Routing table
resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "organization_id", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMaxUpdated["organization_id"])),
resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "network_area_id", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMaxUpdated["network_area_id"])),
resource.TestCheckResourceAttrSet("stackit_routing_table.routing_table", "routing_table_id"),
resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "name", testutil.ConvertConfigVariable(testConfigNetworkV2VarsMaxUpdated["name"])),
resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "labels.%", "0"),
resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "region", testutil.Region),
resource.TestCheckNoResourceAttr("stackit_routing_table.routing_table", "description"),
resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "system_routes", "true"),
resource.TestCheckResourceAttrSet("stackit_routing_table.routing_table", "created_at"),
resource.TestCheckResourceAttrSet("stackit_routing_table.routing_table", "updated_at"),
), ),
}, },
// Deletion is done by the framework implicitly // Deletion is done by the framework implicitly
@ -3544,7 +4024,7 @@ func TestAccImageMax(t *testing.T) {
func testAccCheckDestroy(s *terraform.State) error { func testAccCheckDestroy(s *terraform.State) error {
checkFunctions := []func(s *terraform.State) error{ checkFunctions := []func(s *terraform.State) error{
testAccCheckNetworkDestroy, testAccCheckNetworkV1Destroy,
testAccCheckNetworkInterfaceDestroy, testAccCheckNetworkInterfaceDestroy,
testAccCheckNetworkAreaDestroy, testAccCheckNetworkAreaDestroy,
testAccCheckIaaSVolumeDestroy, testAccCheckIaaSVolumeDestroy,
@ -3573,7 +4053,7 @@ func testAccCheckDestroy(s *terraform.State) error {
return errors.Join(errs...) return errors.Join(errs...)
} }
func testAccCheckNetworkDestroy(s *terraform.State) error { func testAccCheckNetworkV1Destroy(s *terraform.State) error {
ctx := context.Background() ctx := context.Background()
var client *iaas.APIClient var client *iaas.APIClient
var err error var err error
@ -3616,6 +4096,48 @@ func testAccCheckNetworkDestroy(s *terraform.State) error {
return errors.Join(errs...) return errors.Join(errs...)
} }
func testAccCheckNetworkV2Destroy(s *terraform.State) error {
ctx := context.Background()
var client *iaasalpha.APIClient
var err error
if testutil.IaaSCustomEndpoint == "" {
client, err = iaasalpha.NewAPIClient()
} else {
client, err = iaasalpha.NewAPIClient(
stackitSdkConfig.WithEndpoint(testutil.IaaSCustomEndpoint),
)
}
if err != nil {
return fmt.Errorf("creating client: %w", err)
}
var errs []error
// networks
for _, rs := range s.RootModule().Resources {
if rs.Type != "stackit_network" {
continue
}
region := strings.Split(rs.Primary.ID, core.Separator)[1]
networkId := strings.Split(rs.Primary.ID, core.Separator)[2]
err := client.DeleteNetworkExecute(ctx, testutil.ProjectId, region, networkId)
if err != nil {
var oapiErr *oapierror.GenericOpenAPIError
if errors.As(err, &oapiErr) {
if oapiErr.StatusCode == http.StatusNotFound {
continue
}
}
errs = append(errs, fmt.Errorf("cannot trigger network deletion %q: %w", networkId, err))
}
_, err = waitAlpha.DeleteNetworkWaitHandler(ctx, client, testutil.ProjectId, region, networkId).WaitWithContext(ctx)
if err != nil {
errs = append(errs, fmt.Errorf("cannot delete network %q: %w", networkId, err))
}
}
return errors.Join(errs...)
}
func testAccCheckNetworkInterfaceDestroy(s *terraform.State) error { func testAccCheckNetworkInterfaceDestroy(s *terraform.State) error {
ctx := context.Background() ctx := context.Background()
var client *iaas.APIClient var client *iaas.APIClient

View file

@ -2,12 +2,14 @@ package network
import ( import (
"context" "context"
"fmt"
"net"
"net/http"
"github.com/stackitcloud/stackit-sdk-go/services/iaasalpha"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/features"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/network/utils/v1network"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/network/utils/v2network"
iaasUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/utils" iaasUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/utils"
iaasAlphaUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaasalpha/utils"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource"
@ -17,7 +19,6 @@ import (
"github.com/hashicorp/terraform-plugin-log/tflog" "github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/stackitcloud/stackit-sdk-go/services/iaas" "github.com/stackitcloud/stackit-sdk-go/services/iaas"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate"
) )
@ -26,28 +27,6 @@ var (
_ datasource.DataSource = &networkDataSource{} _ datasource.DataSource = &networkDataSource{}
) )
type DataSourceModel struct {
Id types.String `tfsdk:"id"` // needed by TF
ProjectId types.String `tfsdk:"project_id"`
NetworkId types.String `tfsdk:"network_id"`
Name types.String `tfsdk:"name"`
Nameservers types.List `tfsdk:"nameservers"`
IPv4Gateway types.String `tfsdk:"ipv4_gateway"`
IPv4Nameservers types.List `tfsdk:"ipv4_nameservers"`
IPv4Prefix types.String `tfsdk:"ipv4_prefix"`
IPv4PrefixLength types.Int64 `tfsdk:"ipv4_prefix_length"`
Prefixes types.List `tfsdk:"prefixes"`
IPv4Prefixes types.List `tfsdk:"ipv4_prefixes"`
IPv6Gateway types.String `tfsdk:"ipv6_gateway"`
IPv6Nameservers types.List `tfsdk:"ipv6_nameservers"`
IPv6Prefix types.String `tfsdk:"ipv6_prefix"`
IPv6PrefixLength types.Int64 `tfsdk:"ipv6_prefix_length"`
IPv6Prefixes types.List `tfsdk:"ipv6_prefixes"`
PublicIP types.String `tfsdk:"public_ip"`
Labels types.Map `tfsdk:"labels"`
Routed types.Bool `tfsdk:"routed"`
}
// NewNetworkDataSource is a helper function to simplify the provider implementation. // NewNetworkDataSource is a helper function to simplify the provider implementation.
func NewNetworkDataSource() datasource.DataSource { func NewNetworkDataSource() datasource.DataSource {
return &networkDataSource{} return &networkDataSource{}
@ -56,6 +35,10 @@ func NewNetworkDataSource() datasource.DataSource {
// networkDataSource is the data source implementation. // networkDataSource is the data source implementation.
type networkDataSource struct { type networkDataSource struct {
client *iaas.APIClient client *iaas.APIClient
// alphaClient will be used in case the experimental flag "network" is set
alphaClient *iaasalpha.APIClient
isExperimental bool
providerData core.ProviderData
} }
// Metadata returns the data source type name. // Metadata returns the data source type name.
@ -64,16 +47,30 @@ func (d *networkDataSource) Metadata(_ context.Context, req datasource.MetadataR
} }
func (d *networkDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { func (d *networkDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) {
providerData, ok := conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics) var ok bool
d.providerData, ok = conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics)
if !ok { if !ok {
return return
} }
apiClient := iaasUtils.ConfigureClient(ctx, &providerData, &resp.Diagnostics) d.isExperimental = features.CheckExperimentEnabledWithoutError(ctx, &d.providerData, features.NetworkExperiment, "stackit_network", core.Datasource, &resp.Diagnostics)
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {
return return
} }
d.client = apiClient
if d.isExperimental {
alphaApiClient := iaasAlphaUtils.ConfigureClient(ctx, &d.providerData, &resp.Diagnostics)
if resp.Diagnostics.HasError() {
return
}
d.alphaClient = alphaApiClient
} else {
apiClient := iaasUtils.ConfigureClient(ctx, &d.providerData, &resp.Diagnostics)
if resp.Diagnostics.HasError() {
return
}
d.client = apiClient
}
tflog.Info(ctx, "IaaS client configured") tflog.Info(ctx, "IaaS client configured")
} }
@ -181,193 +178,28 @@ func (d *networkDataSource) Schema(_ context.Context, _ datasource.SchemaRequest
Description: "Shows if the network is routed and therefore accessible from other networks.", Description: "Shows if the network is routed and therefore accessible from other networks.",
Computed: true, Computed: true,
}, },
"region": schema.StringAttribute{
// the region cannot be found, so it has to be passed
Optional: true,
Description: "Can only be used when experimental \"network\" is set. This is likely going to undergo significant changes or be removed in the future.\nThe resource region. If not defined, the provider region is used.",
},
"routing_table_id": schema.StringAttribute{
Description: "Can only be used when experimental \"network\" is set. This is likely going to undergo significant changes or be removed in the future. Use it at your own discretion.\nThe ID of the routing table associated with the network.",
Computed: true,
Validators: []validator.String{
validate.UUID(),
validate.NoSeparator(),
},
},
}, },
} }
} }
// Read refreshes the Terraform state with the latest data. // Read refreshes the Terraform state with the latest data.
func (d *networkDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { // nolint:gocritic // function signature required by Terraform func (d *networkDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
var model DataSourceModel if !d.isExperimental {
diags := req.Config.Get(ctx, &model) v1network.DatasourceRead(ctx, req, resp, d.client)
resp.Diagnostics.Append(diags...) } else {
if resp.Diagnostics.HasError() { v2network.DatasourceRead(ctx, req, resp, d.alphaClient, d.providerData)
return
} }
projectId := model.ProjectId.ValueString()
networkId := model.NetworkId.ValueString()
ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "network_id", networkId)
networkResp, err := d.client.GetNetwork(ctx, projectId, networkId).Execute()
if err != nil {
utils.LogError(
ctx,
&resp.Diagnostics,
err,
"Reading network",
fmt.Sprintf("Network with ID %q does not exist in project %q.", networkId, projectId),
map[int]string{
http.StatusForbidden: fmt.Sprintf("Project with ID %q not found or forbidden access", projectId),
},
)
resp.State.RemoveResource(ctx)
return
}
err = mapDataSourceFields(ctx, networkResp, &model)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading network", fmt.Sprintf("Processing API payload: %v", err))
return
}
diags = resp.State.Set(ctx, model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
tflog.Info(ctx, "Network read")
}
func mapDataSourceFields(ctx context.Context, networkResp *iaas.Network, model *DataSourceModel) error {
if networkResp == nil {
return fmt.Errorf("response input is nil")
}
if model == nil {
return fmt.Errorf("model input is nil")
}
var networkId string
if model.NetworkId.ValueString() != "" {
networkId = model.NetworkId.ValueString()
} else if networkResp.NetworkId != nil {
networkId = *networkResp.NetworkId
} else {
return fmt.Errorf("network id not present")
}
model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), networkId)
labels, err := iaasUtils.MapLabels(ctx, networkResp.Labels, model.Labels)
if err != nil {
return err
}
// IPv4
if networkResp.Nameservers == nil {
model.Nameservers = types.ListNull(types.StringType)
model.IPv4Nameservers = types.ListNull(types.StringType)
} else {
respNameservers := *networkResp.Nameservers
modelNameservers, err := utils.ListValuetoStringSlice(model.Nameservers)
modelIPv4Nameservers, errIpv4 := utils.ListValuetoStringSlice(model.IPv4Nameservers)
if err != nil {
return fmt.Errorf("get current network nameservers from model: %w", err)
}
if errIpv4 != nil {
return fmt.Errorf("get current IPv4 network nameservers from model: %w", errIpv4)
}
reconciledNameservers := utils.ReconcileStringSlices(modelNameservers, respNameservers)
reconciledIPv4Nameservers := utils.ReconcileStringSlices(modelIPv4Nameservers, respNameservers)
nameserversTF, diags := types.ListValueFrom(ctx, types.StringType, reconciledNameservers)
ipv4NameserversTF, ipv4Diags := types.ListValueFrom(ctx, types.StringType, reconciledIPv4Nameservers)
if diags.HasError() {
return fmt.Errorf("map network nameservers: %w", core.DiagsToError(diags))
}
if ipv4Diags.HasError() {
return fmt.Errorf("map IPv4 network nameservers: %w", core.DiagsToError(ipv4Diags))
}
model.Nameservers = nameserversTF
model.IPv4Nameservers = ipv4NameserversTF
}
if networkResp.Prefixes == nil {
model.Prefixes = types.ListNull(types.StringType)
model.IPv4Prefixes = types.ListNull(types.StringType)
} else {
respPrefixes := *networkResp.Prefixes
prefixesTF, diags := types.ListValueFrom(ctx, types.StringType, respPrefixes)
if diags.HasError() {
return fmt.Errorf("map network prefixes: %w", core.DiagsToError(diags))
}
if len(respPrefixes) > 0 {
model.IPv4Prefix = types.StringValue(respPrefixes[0])
_, netmask, err := net.ParseCIDR(respPrefixes[0])
if err != nil {
// silently ignore parsing error for the netmask
model.IPv4PrefixLength = types.Int64Null()
} else {
ones, _ := netmask.Mask.Size()
model.IPv4PrefixLength = types.Int64Value(int64(ones))
}
}
model.Prefixes = prefixesTF
model.IPv4Prefixes = prefixesTF
}
if networkResp.Gateway != nil {
model.IPv4Gateway = types.StringPointerValue(networkResp.GetGateway())
} else {
model.IPv4Gateway = types.StringNull()
}
// IPv6
if networkResp.NameserversV6 == nil {
model.IPv6Nameservers = types.ListNull(types.StringType)
} else {
respIPv6Nameservers := *networkResp.NameserversV6
modelIPv6Nameservers, errIpv6 := utils.ListValuetoStringSlice(model.IPv6Nameservers)
if errIpv6 != nil {
return fmt.Errorf("get current IPv6 network nameservers from model: %w", errIpv6)
}
reconciledIPv6Nameservers := utils.ReconcileStringSlices(modelIPv6Nameservers, respIPv6Nameservers)
ipv6NameserversTF, ipv6Diags := types.ListValueFrom(ctx, types.StringType, reconciledIPv6Nameservers)
if ipv6Diags.HasError() {
return fmt.Errorf("map IPv6 network nameservers: %w", core.DiagsToError(ipv6Diags))
}
model.IPv6Nameservers = ipv6NameserversTF
}
if networkResp.PrefixesV6 == nil {
model.IPv6Prefixes = types.ListNull(types.StringType)
} else {
respPrefixesV6 := *networkResp.PrefixesV6
prefixesV6TF, diags := types.ListValueFrom(ctx, types.StringType, respPrefixesV6)
if diags.HasError() {
return fmt.Errorf("map network IPv6 prefixes: %w", core.DiagsToError(diags))
}
if len(respPrefixesV6) > 0 {
model.IPv6Prefix = types.StringValue(respPrefixesV6[0])
_, netmask, err := net.ParseCIDR(respPrefixesV6[0])
if err != nil {
// silently ignore parsing error for the netmask
model.IPv6PrefixLength = types.Int64Null()
} else {
ones, _ := netmask.Mask.Size()
model.IPv6PrefixLength = types.Int64Value(int64(ones))
}
}
model.IPv6Prefixes = prefixesV6TF
}
if networkResp.Gatewayv6 != nil {
model.IPv6Gateway = types.StringPointerValue(networkResp.GetGatewayv6())
} else {
model.IPv6Gateway = types.StringNull()
}
model.NetworkId = types.StringValue(networkId)
model.Name = types.StringPointerValue(networkResp.Name)
model.PublicIP = types.StringPointerValue(networkResp.PublicIp)
model.Labels = labels
model.Routed = types.BoolPointerValue(networkResp.Routed)
return nil
} }

View file

@ -2,31 +2,30 @@ package network
import ( import (
"context" "context"
"fmt"
"net"
"net/http"
"strings"
iaasUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/utils"
"github.com/hashicorp/terraform-plugin-framework-validators/resourcevalidator" "github.com/hashicorp/terraform-plugin-framework-validators/resourcevalidator"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource" "github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema" "github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/listplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/schema/validator" "github.com/hashicorp/terraform-plugin-framework/schema/validator"
"github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog" "github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/stackitcloud/stackit-sdk-go/core/oapierror"
"github.com/stackitcloud/stackit-sdk-go/services/iaas" "github.com/stackitcloud/stackit-sdk-go/services/iaas"
"github.com/stackitcloud/stackit-sdk-go/services/iaas/wait" "github.com/stackitcloud/stackit-sdk-go/services/iaasalpha"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/features"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/network/utils/model"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/network/utils/v1network"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/network/utils/v2network"
iaasUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/utils"
iaasAlphaUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaasalpha/utils"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/validate"
) )
@ -38,30 +37,6 @@ var (
_ resource.ResourceWithImportState = &networkResource{} _ resource.ResourceWithImportState = &networkResource{}
) )
type Model struct {
Id types.String `tfsdk:"id"` // needed by TF
ProjectId types.String `tfsdk:"project_id"`
NetworkId types.String `tfsdk:"network_id"`
Name types.String `tfsdk:"name"`
Nameservers types.List `tfsdk:"nameservers"`
IPv4Gateway types.String `tfsdk:"ipv4_gateway"`
IPv4Nameservers types.List `tfsdk:"ipv4_nameservers"`
IPv4Prefix types.String `tfsdk:"ipv4_prefix"`
IPv4PrefixLength types.Int64 `tfsdk:"ipv4_prefix_length"`
Prefixes types.List `tfsdk:"prefixes"`
IPv4Prefixes types.List `tfsdk:"ipv4_prefixes"`
IPv6Gateway types.String `tfsdk:"ipv6_gateway"`
IPv6Nameservers types.List `tfsdk:"ipv6_nameservers"`
IPv6Prefix types.String `tfsdk:"ipv6_prefix"`
IPv6PrefixLength types.Int64 `tfsdk:"ipv6_prefix_length"`
IPv6Prefixes types.List `tfsdk:"ipv6_prefixes"`
PublicIP types.String `tfsdk:"public_ip"`
Labels types.Map `tfsdk:"labels"`
Routed types.Bool `tfsdk:"routed"`
NoIPv4Gateway types.Bool `tfsdk:"no_ipv4_gateway"`
NoIPv6Gateway types.Bool `tfsdk:"no_ipv6_gateway"`
}
// NewNetworkResource is a helper function to simplify the provider implementation. // NewNetworkResource is a helper function to simplify the provider implementation.
func NewNetworkResource() resource.Resource { func NewNetworkResource() resource.Resource {
return &networkResource{} return &networkResource{}
@ -70,6 +45,10 @@ func NewNetworkResource() resource.Resource {
// networkResource is the resource implementation. // networkResource is the resource implementation.
type networkResource struct { type networkResource struct {
client *iaas.APIClient client *iaas.APIClient
// alphaClient will be used in case the experimental flag "network" is set
alphaClient *iaasalpha.APIClient
isExperimental bool
providerData core.ProviderData
} }
// Metadata returns the resource type name. // Metadata returns the resource type name.
@ -79,29 +58,85 @@ func (r *networkResource) Metadata(_ context.Context, req resource.MetadataReque
// Configure adds the provider configured client to the resource. // Configure adds the provider configured client to the resource.
func (r *networkResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { func (r *networkResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
providerData, ok := conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics) var ok bool
r.providerData, ok = conversion.ParseProviderData(ctx, req.ProviderData, &resp.Diagnostics)
if !ok { if !ok {
return return
} }
apiClient := iaasUtils.ConfigureClient(ctx, &providerData, &resp.Diagnostics) r.isExperimental = features.CheckExperimentEnabledWithoutError(ctx, &r.providerData, features.NetworkExperiment, "stackit_network", core.Resource, &resp.Diagnostics)
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {
return return
} }
r.client = apiClient
if r.isExperimental {
alphaApiClient := iaasAlphaUtils.ConfigureClient(ctx, &r.providerData, &resp.Diagnostics)
if resp.Diagnostics.HasError() {
return
}
r.alphaClient = alphaApiClient
} else {
apiClient := iaasUtils.ConfigureClient(ctx, &r.providerData, &resp.Diagnostics)
if resp.Diagnostics.HasError() {
return
}
r.client = apiClient
}
tflog.Info(ctx, "IaaS client configured") tflog.Info(ctx, "IaaS client configured")
} }
func (r networkResource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) { // ModifyPlan implements resource.ResourceWithModifyPlan.
var model Model // Use the modifier to set the effective region in the current plan.
resp.Diagnostics.Append(req.Config.Get(ctx, &model)...) func (r *networkResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) { // nolint:gocritic // function signature required by Terraform
// If the v1 api is used, it's not required to get the fallback region because it isn't used
if !r.isExperimental {
return
}
var configModel model.Model
// skip initial empty configuration to avoid follow-up errors
if req.Config.Raw.IsNull() {
return
}
resp.Diagnostics.Append(req.Config.Get(ctx, &configModel)...)
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {
return return
} }
if !model.Nameservers.IsUnknown() && !model.IPv4Nameservers.IsUnknown() && !model.Nameservers.IsNull() && !model.IPv4Nameservers.IsNull() { var planModel model.Model
resp.Diagnostics.Append(req.Plan.Get(ctx, &planModel)...)
if resp.Diagnostics.HasError() {
return
}
utils.AdaptRegion(ctx, configModel.Region, &planModel.Region, r.providerData.GetRegion(), resp)
if resp.Diagnostics.HasError() {
return
}
resp.Diagnostics.Append(resp.Plan.Set(ctx, planModel)...)
if resp.Diagnostics.HasError() {
return
}
}
func (r *networkResource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) {
var resourceModel model.Model
resp.Diagnostics.Append(req.Config.Get(ctx, &resourceModel)...)
if resp.Diagnostics.HasError() {
return
}
if !resourceModel.Nameservers.IsUnknown() && !resourceModel.IPv4Nameservers.IsUnknown() && !resourceModel.Nameservers.IsNull() && !resourceModel.IPv4Nameservers.IsNull() {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error configuring network", "You cannot provide both the `nameservers` and `ipv4_nameservers` fields simultaneously. Please remove the deprecated `nameservers` field, and use `ipv4_nameservers` to configure nameservers for IPv4.") core.LogAndAddError(ctx, &resp.Diagnostics, "Error configuring network", "You cannot provide both the `nameservers` and `ipv4_nameservers` fields simultaneously. Please remove the deprecated `nameservers` field, and use `ipv4_nameservers` to configure nameservers for IPv4.")
} }
if !r.isExperimental {
if !utils.IsUndefined(resourceModel.Region) {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error configuring network", "Setting the `region` is not supported yet. This can only be configured when the experiments `network` is set.")
}
if !utils.IsUndefined(resourceModel.RoutingTableID) {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error configuring network", "Setting the field `routing_table_id` is not supported yet. This can only be configured when the experiments `network` is set.")
}
}
} }
// ConfigValidators validates the resource configuration // ConfigValidators validates the resource configuration
@ -115,6 +150,22 @@ func (r *networkResource) ConfigValidators(_ context.Context) []resource.ConfigV
path.MatchRoot("no_ipv6_gateway"), path.MatchRoot("no_ipv6_gateway"),
path.MatchRoot("ipv6_gateway"), path.MatchRoot("ipv6_gateway"),
), ),
resourcevalidator.Conflicting(
path.MatchRoot("ipv4_prefix"),
path.MatchRoot("ipv4_prefix_length"),
),
resourcevalidator.Conflicting(
path.MatchRoot("ipv6_prefix"),
path.MatchRoot("ipv6_prefix_length"),
),
resourcevalidator.Conflicting(
path.MatchRoot("ipv4_prefix_length"),
path.MatchRoot("ipv4_gateway"),
),
resourcevalidator.Conflicting(
path.MatchRoot("ipv6_prefix_length"),
path.MatchRoot("ipv6_gateway"),
),
} }
} }
@ -196,13 +247,16 @@ func (r *networkResource) Schema(_ context.Context, _ resource.SchemaRequest, re
validate.CIDR(), validate.CIDR(),
}, },
PlanModifiers: []planmodifier.String{ PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(), stringplanmodifier.RequiresReplaceIfConfigured(),
}, },
}, },
"ipv4_prefix_length": schema.Int64Attribute{ "ipv4_prefix_length": schema.Int64Attribute{
Description: "The IPv4 prefix length of the network.", Description: "The IPv4 prefix length of the network.",
Computed: true, Computed: true,
Optional: true, Optional: true,
PlanModifiers: []planmodifier.Int64{
int64planmodifier.RequiresReplaceIfConfigured(),
},
}, },
"prefixes": schema.ListAttribute{ "prefixes": schema.ListAttribute{
Description: "The prefixes of the network. This field is deprecated and will be removed soon, use `ipv4_prefixes` to read the prefixes of the IPv4 networks.", Description: "The prefixes of the network. This field is deprecated and will be removed soon, use `ipv4_prefixes` to read the prefixes of the IPv4 networks.",
@ -285,498 +339,73 @@ func (r *networkResource) Schema(_ context.Context, _ resource.SchemaRequest, re
boolplanmodifier.RequiresReplace(), boolplanmodifier.RequiresReplace(),
}, },
}, },
"routing_table_id": schema.StringAttribute{
Description: "Can only be used when experimental \"network\" is set.\nThe ID of the routing table associated with the network.",
Optional: true,
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
Validators: []validator.String{
validate.UUID(),
validate.NoSeparator(),
},
},
"region": schema.StringAttribute{
Optional: true,
// must be computed to allow for storing the override value from the provider
Computed: true,
Description: "Can only be used when experimental \"network\" is set.\nThe resource region. If not defined, the provider region is used.",
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplaceIfConfigured(),
},
},
}, },
} }
} }
// Create creates the resource and sets the initial Terraform state. // Create creates the resource and sets the initial Terraform state.
func (r *networkResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { // nolint:gocritic // function signature required by Terraform func (r *networkResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { // nolint:gocritic // function signature required by Terraform
// Retrieve values from plan if !r.isExperimental {
var model Model v1network.Create(ctx, req, resp, r.client)
diags := req.Plan.Get(ctx, &model) } else {
resp.Diagnostics.Append(diags...) v2network.Create(ctx, req, resp, r.alphaClient)
if resp.Diagnostics.HasError() {
return
} }
projectId := model.ProjectId.ValueString()
ctx = tflog.SetField(ctx, "project_id", projectId)
// Generate API request body from model
payload, err := toCreatePayload(ctx, &model)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating network", fmt.Sprintf("Creating API payload: %v", err))
return
}
// Create new network
network, err := r.client.CreateNetwork(ctx, projectId).CreateNetworkPayload(*payload).Execute()
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating network", fmt.Sprintf("Calling API: %v", err))
return
}
networkId := *network.NetworkId
network, err = wait.CreateNetworkWaitHandler(ctx, r.client, projectId, networkId).WaitWithContext(ctx)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating network", fmt.Sprintf("Network creation waiting: %v", err))
return
}
ctx = tflog.SetField(ctx, "network_id", networkId)
// Map response body to schema
err = mapFields(ctx, network, &model)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating network", fmt.Sprintf("Processing API payload: %v", err))
return
}
// Set state to fully populated data
diags = resp.State.Set(ctx, model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
tflog.Info(ctx, "Network created")
} }
// Read refreshes the Terraform state with the latest data. // Read refreshes the Terraform state with the latest data.
func (r *networkResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { // nolint:gocritic // function signature required by Terraform func (r *networkResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { // nolint:gocritic // function signature required by Terraform
var model Model if !r.isExperimental {
diags := req.State.Get(ctx, &model) v1network.Read(ctx, req, resp, r.client)
resp.Diagnostics.Append(diags...) } else {
if resp.Diagnostics.HasError() { v2network.Read(ctx, req, resp, r.alphaClient, r.providerData)
return
} }
projectId := model.ProjectId.ValueString()
networkId := model.NetworkId.ValueString()
ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "network_id", networkId)
networkResp, err := r.client.GetNetwork(ctx, projectId, networkId).Execute()
if err != nil {
oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped
if ok && oapiErr.StatusCode == http.StatusNotFound {
resp.State.RemoveResource(ctx)
return
}
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading network", fmt.Sprintf("Calling API: %v", err))
return
}
// Map response body to schema
err = mapFields(ctx, networkResp, &model)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading network", fmt.Sprintf("Processing API payload: %v", err))
return
}
// Set refreshed state
diags = resp.State.Set(ctx, model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
tflog.Info(ctx, "Network read")
} }
// Update updates the resource and sets the updated Terraform state on success. // Update updates the resource and sets the updated Terraform state on success.
func (r *networkResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { // nolint:gocritic // function signature required by Terraform func (r *networkResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { // nolint:gocritic // function signature required by Terraform
// Retrieve values from plan if !r.isExperimental {
var model Model v1network.Update(ctx, req, resp, r.client)
diags := req.Plan.Get(ctx, &model) } else {
resp.Diagnostics.Append(diags...) v2network.Update(ctx, req, resp, r.alphaClient)
if resp.Diagnostics.HasError() {
return
} }
projectId := model.ProjectId.ValueString()
networkId := model.NetworkId.ValueString()
ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "network_id", networkId)
// Retrieve values from state
var stateModel Model
diags = req.State.Get(ctx, &stateModel)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
// Generate API request body from model
payload, err := toUpdatePayload(ctx, &model, &stateModel)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating network", fmt.Sprintf("Creating API payload: %v", err))
return
}
// Update existing network
err = r.client.PartialUpdateNetwork(ctx, projectId, networkId).PartialUpdateNetworkPayload(*payload).Execute()
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating network", fmt.Sprintf("Calling API: %v", err))
return
}
waitResp, err := wait.UpdateNetworkWaitHandler(ctx, r.client, projectId, networkId).WaitWithContext(ctx)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating network", fmt.Sprintf("Network update waiting: %v", err))
return
}
err = mapFields(ctx, waitResp, &model)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating network", fmt.Sprintf("Processing API payload: %v", err))
return
}
diags = resp.State.Set(ctx, model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
tflog.Info(ctx, "Network updated")
} }
// Delete deletes the resource and removes the Terraform state on success. // Delete deletes the resource and removes the Terraform state on success.
func (r *networkResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { // nolint:gocritic // function signature required by Terraform func (r *networkResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { // nolint:gocritic // function signature required by Terraform
// Retrieve values from state if !r.isExperimental {
var model Model v1network.Delete(ctx, req, resp, r.client)
diags := req.State.Get(ctx, &model) } else {
resp.Diagnostics.Append(diags...) v2network.Delete(ctx, req, resp, r.alphaClient)
if resp.Diagnostics.HasError() {
return
} }
projectId := model.ProjectId.ValueString()
networkId := model.NetworkId.ValueString()
ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "network_id", networkId)
// Delete existing network
err := r.client.DeleteNetwork(ctx, projectId, networkId).Execute()
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting network", fmt.Sprintf("Calling API: %v", err))
return
}
_, err = wait.DeleteNetworkWaitHandler(ctx, r.client, projectId, networkId).WaitWithContext(ctx)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting network", fmt.Sprintf("Network deletion waiting: %v", err))
return
}
tflog.Info(ctx, "Network deleted")
} }
// ImportState imports a resource into the Terraform state on success. // ImportState imports a resource into the Terraform state on success.
// The expected format of the resource import identifier is: project_id,network_id // The expected format of the resource import identifier is: project_id,network_id
func (r *networkResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { func (r *networkResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
idParts := strings.Split(req.ID, core.Separator) if !r.isExperimental {
v1network.ImportState(ctx, req, resp)
if len(idParts) != 2 || idParts[0] == "" || idParts[1] == "" { } else {
core.LogAndAddError(ctx, &resp.Diagnostics, v2network.ImportState(ctx, req, resp)
"Error importing network",
fmt.Sprintf("Expected import identifier with format: [project_id],[network_id] Got: %q", req.ID),
)
return
} }
projectId := idParts[0]
networkId := idParts[1]
ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "network_id", networkId)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), projectId)...)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("network_id"), networkId)...)
tflog.Info(ctx, "Network state imported")
}
func mapFields(ctx context.Context, networkResp *iaas.Network, model *Model) error {
if networkResp == nil {
return fmt.Errorf("response input is nil")
}
if model == nil {
return fmt.Errorf("model input is nil")
}
var networkId string
if model.NetworkId.ValueString() != "" {
networkId = model.NetworkId.ValueString()
} else if networkResp.NetworkId != nil {
networkId = *networkResp.NetworkId
} else {
return fmt.Errorf("network id not present")
}
model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), networkId)
labels, err := iaasUtils.MapLabels(ctx, networkResp.Labels, model.Labels)
if err != nil {
return err
}
// IPv4
if networkResp.Nameservers == nil {
model.Nameservers = types.ListNull(types.StringType)
model.IPv4Nameservers = types.ListNull(types.StringType)
} else {
respNameservers := *networkResp.Nameservers
modelNameservers, err := utils.ListValuetoStringSlice(model.Nameservers)
modelIPv4Nameservers, errIpv4 := utils.ListValuetoStringSlice(model.IPv4Nameservers)
if err != nil {
return fmt.Errorf("get current network nameservers from model: %w", err)
}
if errIpv4 != nil {
return fmt.Errorf("get current IPv4 network nameservers from model: %w", errIpv4)
}
reconciledNameservers := utils.ReconcileStringSlices(modelNameservers, respNameservers)
reconciledIPv4Nameservers := utils.ReconcileStringSlices(modelIPv4Nameservers, respNameservers)
nameserversTF, diags := types.ListValueFrom(ctx, types.StringType, reconciledNameservers)
ipv4NameserversTF, ipv4Diags := types.ListValueFrom(ctx, types.StringType, reconciledIPv4Nameservers)
if diags.HasError() {
return fmt.Errorf("map network nameservers: %w", core.DiagsToError(diags))
}
if ipv4Diags.HasError() {
return fmt.Errorf("map IPv4 network nameservers: %w", core.DiagsToError(ipv4Diags))
}
model.Nameservers = nameserversTF
model.IPv4Nameservers = ipv4NameserversTF
}
if networkResp.Prefixes == nil {
model.Prefixes = types.ListNull(types.StringType)
model.IPv4Prefixes = types.ListNull(types.StringType)
} else {
respPrefixes := *networkResp.Prefixes
prefixesTF, diags := types.ListValueFrom(ctx, types.StringType, respPrefixes)
if diags.HasError() {
return fmt.Errorf("map network prefixes: %w", core.DiagsToError(diags))
}
if len(respPrefixes) > 0 {
model.IPv4Prefix = types.StringValue(respPrefixes[0])
_, netmask, err := net.ParseCIDR(respPrefixes[0])
if err != nil {
// silently ignore parsing error for the netmask
model.IPv4PrefixLength = types.Int64Null()
} else {
ones, _ := netmask.Mask.Size()
model.IPv4PrefixLength = types.Int64Value(int64(ones))
}
}
model.Prefixes = prefixesTF
model.IPv4Prefixes = prefixesTF
}
if networkResp.Gateway != nil {
model.IPv4Gateway = types.StringPointerValue(networkResp.GetGateway())
} else {
model.IPv4Gateway = types.StringNull()
}
// IPv6
if networkResp.NameserversV6 == nil {
model.IPv6Nameservers = types.ListNull(types.StringType)
} else {
respIPv6Nameservers := *networkResp.NameserversV6
modelIPv6Nameservers, errIpv6 := utils.ListValuetoStringSlice(model.IPv6Nameservers)
if errIpv6 != nil {
return fmt.Errorf("get current IPv6 network nameservers from model: %w", errIpv6)
}
reconciledIPv6Nameservers := utils.ReconcileStringSlices(modelIPv6Nameservers, respIPv6Nameservers)
ipv6NameserversTF, ipv6Diags := types.ListValueFrom(ctx, types.StringType, reconciledIPv6Nameservers)
if ipv6Diags.HasError() {
return fmt.Errorf("map IPv6 network nameservers: %w", core.DiagsToError(ipv6Diags))
}
model.IPv6Nameservers = ipv6NameserversTF
}
if networkResp.PrefixesV6 == nil {
model.IPv6Prefixes = types.ListNull(types.StringType)
} else {
respPrefixesV6 := *networkResp.PrefixesV6
prefixesV6TF, diags := types.ListValueFrom(ctx, types.StringType, respPrefixesV6)
if diags.HasError() {
return fmt.Errorf("map network IPv6 prefixes: %w", core.DiagsToError(diags))
}
if len(respPrefixesV6) > 0 {
model.IPv6Prefix = types.StringValue(respPrefixesV6[0])
_, netmask, err := net.ParseCIDR(respPrefixesV6[0])
if err != nil {
// silently ignore parsing error for the netmask
model.IPv6PrefixLength = types.Int64Null()
} else {
ones, _ := netmask.Mask.Size()
model.IPv6PrefixLength = types.Int64Value(int64(ones))
}
}
model.IPv6Prefixes = prefixesV6TF
}
if networkResp.Gatewayv6 != nil {
model.IPv6Gateway = types.StringPointerValue(networkResp.GetGatewayv6())
} else {
model.IPv6Gateway = types.StringNull()
}
model.NetworkId = types.StringValue(networkId)
model.Name = types.StringPointerValue(networkResp.Name)
model.PublicIP = types.StringPointerValue(networkResp.PublicIp)
model.Labels = labels
model.Routed = types.BoolPointerValue(networkResp.Routed)
return nil
}
func toCreatePayload(ctx context.Context, model *Model) (*iaas.CreateNetworkPayload, error) {
if model == nil {
return nil, fmt.Errorf("nil model")
}
addressFamily := &iaas.CreateNetworkAddressFamily{}
modelIPv6Nameservers := []string{}
for _, ipv6ns := range model.IPv6Nameservers.Elements() {
ipv6NameserverString, ok := ipv6ns.(types.String)
if !ok {
return nil, fmt.Errorf("type assertion failed")
}
modelIPv6Nameservers = append(modelIPv6Nameservers, ipv6NameserverString.ValueString())
}
if !(model.IPv6Prefix.IsNull() || model.IPv6PrefixLength.IsNull() || model.IPv6Nameservers.IsNull()) {
addressFamily.Ipv6 = &iaas.CreateNetworkIPv6Body{
Nameservers: &modelIPv6Nameservers,
Prefix: conversion.StringValueToPointer(model.IPv6Prefix),
PrefixLength: conversion.Int64ValueToPointer(model.IPv6PrefixLength),
}
if model.NoIPv6Gateway.ValueBool() {
addressFamily.Ipv6.Gateway = iaas.NewNullableString(nil)
} else if !(model.IPv6Gateway.IsUnknown() || model.IPv6Gateway.IsNull()) {
addressFamily.Ipv6.Gateway = iaas.NewNullableString(conversion.StringValueToPointer(model.IPv6Gateway))
}
}
modelIPv4Nameservers := []string{}
var modelIPv4List []attr.Value
if !(model.IPv4Nameservers.IsNull() || model.IPv4Nameservers.IsUnknown()) {
modelIPv4List = model.IPv4Nameservers.Elements()
} else {
modelIPv4List = model.Nameservers.Elements()
}
for _, ipv4ns := range modelIPv4List {
ipv4NameserverString, ok := ipv4ns.(types.String)
if !ok {
return nil, fmt.Errorf("type assertion failed")
}
modelIPv4Nameservers = append(modelIPv4Nameservers, ipv4NameserverString.ValueString())
}
if !model.IPv4Prefix.IsNull() || !model.IPv4PrefixLength.IsNull() || !model.IPv4Nameservers.IsNull() || !model.Nameservers.IsNull() {
addressFamily.Ipv4 = &iaas.CreateNetworkIPv4Body{
Nameservers: &modelIPv4Nameservers,
Prefix: conversion.StringValueToPointer(model.IPv4Prefix),
PrefixLength: conversion.Int64ValueToPointer(model.IPv4PrefixLength),
}
if model.NoIPv4Gateway.ValueBool() {
addressFamily.Ipv4.Gateway = iaas.NewNullableString(nil)
} else if !(model.IPv4Gateway.IsUnknown() || model.IPv4Gateway.IsNull()) {
addressFamily.Ipv4.Gateway = iaas.NewNullableString(conversion.StringValueToPointer(model.IPv4Gateway))
}
}
labels, err := conversion.ToStringInterfaceMap(ctx, model.Labels)
if err != nil {
return nil, fmt.Errorf("converting to Go map: %w", err)
}
payload := iaas.CreateNetworkPayload{
Name: conversion.StringValueToPointer(model.Name),
Labels: &labels,
Routed: conversion.BoolValueToPointer(model.Routed),
}
if addressFamily.Ipv6 != nil || addressFamily.Ipv4 != nil {
payload.AddressFamily = addressFamily
}
return &payload, nil
}
func toUpdatePayload(ctx context.Context, model, stateModel *Model) (*iaas.PartialUpdateNetworkPayload, error) {
if model == nil {
return nil, fmt.Errorf("nil model")
}
addressFamily := &iaas.UpdateNetworkAddressFamily{}
modelIPv6Nameservers := []string{}
for _, ipv6ns := range model.IPv6Nameservers.Elements() {
ipv6NameserverString, ok := ipv6ns.(types.String)
if !ok {
return nil, fmt.Errorf("type assertion failed")
}
modelIPv6Nameservers = append(modelIPv6Nameservers, ipv6NameserverString.ValueString())
}
if !(model.IPv6Nameservers.IsNull() || model.IPv6Nameservers.IsUnknown()) {
addressFamily.Ipv6 = &iaas.UpdateNetworkIPv6Body{
Nameservers: &modelIPv6Nameservers,
}
if model.NoIPv6Gateway.ValueBool() {
addressFamily.Ipv6.Gateway = iaas.NewNullableString(nil)
} else if !(model.IPv6Gateway.IsUnknown() || model.IPv6Gateway.IsNull()) {
addressFamily.Ipv6.Gateway = iaas.NewNullableString(conversion.StringValueToPointer(model.IPv6Gateway))
}
}
modelIPv4Nameservers := []string{}
var modelIPv4List []attr.Value
if !(model.IPv4Nameservers.IsNull() || model.IPv4Nameservers.IsUnknown()) {
modelIPv4List = model.IPv4Nameservers.Elements()
} else {
modelIPv4List = model.Nameservers.Elements()
}
for _, ipv4ns := range modelIPv4List {
ipv4NameserverString, ok := ipv4ns.(types.String)
if !ok {
return nil, fmt.Errorf("type assertion failed")
}
modelIPv4Nameservers = append(modelIPv4Nameservers, ipv4NameserverString.ValueString())
}
if !model.IPv4Nameservers.IsNull() || !model.Nameservers.IsNull() {
addressFamily.Ipv4 = &iaas.UpdateNetworkIPv4Body{
Nameservers: &modelIPv4Nameservers,
}
if model.NoIPv4Gateway.ValueBool() {
addressFamily.Ipv4.Gateway = iaas.NewNullableString(nil)
} else if !(model.IPv4Gateway.IsUnknown() || model.IPv4Gateway.IsNull()) {
addressFamily.Ipv4.Gateway = iaas.NewNullableString(conversion.StringValueToPointer(model.IPv4Gateway))
}
}
currentLabels := stateModel.Labels
labels, err := conversion.ToJSONMapPartialUpdatePayload(ctx, currentLabels, model.Labels)
if err != nil {
return nil, fmt.Errorf("converting to Go map: %w", err)
}
payload := iaas.PartialUpdateNetworkPayload{
Name: conversion.StringValueToPointer(model.Name),
Labels: &labels,
}
if addressFamily.Ipv6 != nil || addressFamily.Ipv4 != nil {
payload.AddressFamily = addressFamily
}
return &payload, nil
} }

View file

@ -0,0 +1,53 @@
package model
import "github.com/hashicorp/terraform-plugin-framework/types"
type Model struct {
Id types.String `tfsdk:"id"` // needed by TF
ProjectId types.String `tfsdk:"project_id"`
NetworkId types.String `tfsdk:"network_id"`
Name types.String `tfsdk:"name"`
Nameservers types.List `tfsdk:"nameservers"`
IPv4Gateway types.String `tfsdk:"ipv4_gateway"`
IPv4Nameservers types.List `tfsdk:"ipv4_nameservers"`
IPv4Prefix types.String `tfsdk:"ipv4_prefix"`
IPv4PrefixLength types.Int64 `tfsdk:"ipv4_prefix_length"`
Prefixes types.List `tfsdk:"prefixes"`
IPv4Prefixes types.List `tfsdk:"ipv4_prefixes"`
IPv6Gateway types.String `tfsdk:"ipv6_gateway"`
IPv6Nameservers types.List `tfsdk:"ipv6_nameservers"`
IPv6Prefix types.String `tfsdk:"ipv6_prefix"`
IPv6PrefixLength types.Int64 `tfsdk:"ipv6_prefix_length"`
IPv6Prefixes types.List `tfsdk:"ipv6_prefixes"`
PublicIP types.String `tfsdk:"public_ip"`
Labels types.Map `tfsdk:"labels"`
Routed types.Bool `tfsdk:"routed"`
NoIPv4Gateway types.Bool `tfsdk:"no_ipv4_gateway"`
NoIPv6Gateway types.Bool `tfsdk:"no_ipv6_gateway"`
Region types.String `tfsdk:"region"`
RoutingTableID types.String `tfsdk:"routing_table_id"`
}
type DataSourceModel struct {
Id types.String `tfsdk:"id"` // needed by TF
ProjectId types.String `tfsdk:"project_id"`
NetworkId types.String `tfsdk:"network_id"`
Name types.String `tfsdk:"name"`
Nameservers types.List `tfsdk:"nameservers"`
IPv4Gateway types.String `tfsdk:"ipv4_gateway"`
IPv4Nameservers types.List `tfsdk:"ipv4_nameservers"`
IPv4Prefix types.String `tfsdk:"ipv4_prefix"`
IPv4PrefixLength types.Int64 `tfsdk:"ipv4_prefix_length"`
Prefixes types.List `tfsdk:"prefixes"`
IPv4Prefixes types.List `tfsdk:"ipv4_prefixes"`
IPv6Gateway types.String `tfsdk:"ipv6_gateway"`
IPv6Nameservers types.List `tfsdk:"ipv6_nameservers"`
IPv6Prefix types.String `tfsdk:"ipv6_prefix"`
IPv6PrefixLength types.Int64 `tfsdk:"ipv6_prefix_length"`
IPv6Prefixes types.List `tfsdk:"ipv6_prefixes"`
PublicIP types.String `tfsdk:"public_ip"`
Labels types.Map `tfsdk:"labels"`
Routed types.Bool `tfsdk:"routed"`
Region types.String `tfsdk:"region"`
RoutingTableID types.String `tfsdk:"routing_table_id"`
}

View file

@ -0,0 +1,203 @@
package v1network
import (
"context"
"fmt"
"net"
"net/http"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/stackitcloud/stackit-sdk-go/services/iaas"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
networkModel "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/network/utils/model"
iaasUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/utils"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
)
func DatasourceRead(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse, client *iaas.APIClient) { // nolint:gocritic // function signature required by Terraform
var model networkModel.DataSourceModel
diags := req.Config.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
projectId := model.ProjectId.ValueString()
networkId := model.NetworkId.ValueString()
ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "network_id", networkId)
networkResp, err := client.GetNetwork(ctx, projectId, networkId).Execute()
if err != nil {
utils.LogError(
ctx,
&resp.Diagnostics,
err,
"Reading network",
fmt.Sprintf("Network with ID %q does not exist in project %q.", networkId, projectId),
map[int]string{
http.StatusForbidden: fmt.Sprintf("Project with ID %q not found or forbidden access", projectId),
},
)
resp.State.RemoveResource(ctx)
return
}
err = mapDataSourceFields(ctx, networkResp, &model)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading network", fmt.Sprintf("Processing API payload: %v", err))
return
}
diags = resp.State.Set(ctx, model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
tflog.Info(ctx, "Network read")
}
func mapDataSourceFields(ctx context.Context, networkResp *iaas.Network, model *networkModel.DataSourceModel) error {
if networkResp == nil {
return fmt.Errorf("response input is nil")
}
if model == nil {
return fmt.Errorf("model input is nil")
}
var networkId string
if model.NetworkId.ValueString() != "" {
networkId = model.NetworkId.ValueString()
} else if networkResp.NetworkId != nil {
networkId = *networkResp.NetworkId
} else {
return fmt.Errorf("network id not present")
}
model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), networkId)
labels, err := iaasUtils.MapLabels(ctx, networkResp.Labels, model.Labels)
if err != nil {
return err
}
// IPv4
if networkResp.Nameservers == nil {
model.Nameservers = types.ListNull(types.StringType)
model.IPv4Nameservers = types.ListNull(types.StringType)
} else {
respNameservers := *networkResp.Nameservers
modelNameservers, err := utils.ListValuetoStringSlice(model.Nameservers)
modelIPv4Nameservers, errIpv4 := utils.ListValuetoStringSlice(model.IPv4Nameservers)
if err != nil {
return fmt.Errorf("get current network nameservers from model: %w", err)
}
if errIpv4 != nil {
return fmt.Errorf("get current IPv4 network nameservers from model: %w", errIpv4)
}
reconciledNameservers := utils.ReconcileStringSlices(modelNameservers, respNameservers)
reconciledIPv4Nameservers := utils.ReconcileStringSlices(modelIPv4Nameservers, respNameservers)
nameserversTF, diags := types.ListValueFrom(ctx, types.StringType, reconciledNameservers)
ipv4NameserversTF, ipv4Diags := types.ListValueFrom(ctx, types.StringType, reconciledIPv4Nameservers)
if diags.HasError() {
return fmt.Errorf("map network nameservers: %w", core.DiagsToError(diags))
}
if ipv4Diags.HasError() {
return fmt.Errorf("map IPv4 network nameservers: %w", core.DiagsToError(ipv4Diags))
}
model.Nameservers = nameserversTF
model.IPv4Nameservers = ipv4NameserversTF
}
if networkResp.Prefixes == nil {
model.Prefixes = types.ListNull(types.StringType)
model.IPv4Prefixes = types.ListNull(types.StringType)
} else {
respPrefixes := *networkResp.Prefixes
prefixesTF, diags := types.ListValueFrom(ctx, types.StringType, respPrefixes)
if diags.HasError() {
return fmt.Errorf("map network prefixes: %w", core.DiagsToError(diags))
}
if len(respPrefixes) > 0 {
model.IPv4Prefix = types.StringValue(respPrefixes[0])
_, netmask, err := net.ParseCIDR(respPrefixes[0])
if err != nil {
// silently ignore parsing error for the netmask
model.IPv4PrefixLength = types.Int64Null()
} else {
ones, _ := netmask.Mask.Size()
model.IPv4PrefixLength = types.Int64Value(int64(ones))
}
}
model.Prefixes = prefixesTF
model.IPv4Prefixes = prefixesTF
}
model.IPv4Gateway = types.StringNull()
if networkResp.Gateway != nil {
model.IPv4Gateway = types.StringPointerValue(networkResp.GetGateway())
}
// IPv6
if networkResp.NameserversV6 == nil {
model.IPv6Nameservers = types.ListNull(types.StringType)
} else {
respIPv6Nameservers := *networkResp.NameserversV6
modelIPv6Nameservers, errIpv6 := utils.ListValuetoStringSlice(model.IPv6Nameservers)
if errIpv6 != nil {
return fmt.Errorf("get current IPv6 network nameservers from model: %w", errIpv6)
}
reconciledIPv6Nameservers := utils.ReconcileStringSlices(modelIPv6Nameservers, respIPv6Nameservers)
ipv6NameserversTF, ipv6Diags := types.ListValueFrom(ctx, types.StringType, reconciledIPv6Nameservers)
if ipv6Diags.HasError() {
return fmt.Errorf("map IPv6 network nameservers: %w", core.DiagsToError(ipv6Diags))
}
model.IPv6Nameservers = ipv6NameserversTF
}
if networkResp.PrefixesV6 == nil {
model.IPv6Prefixes = types.ListNull(types.StringType)
} else {
respPrefixesV6 := *networkResp.PrefixesV6
prefixesV6TF, diags := types.ListValueFrom(ctx, types.StringType, respPrefixesV6)
if diags.HasError() {
return fmt.Errorf("map network IPv6 prefixes: %w", core.DiagsToError(diags))
}
if len(respPrefixesV6) > 0 {
model.IPv6Prefix = types.StringValue(respPrefixesV6[0])
_, netmask, err := net.ParseCIDR(respPrefixesV6[0])
if err != nil {
// silently ignore parsing error for the netmask
model.IPv6PrefixLength = types.Int64Null()
} else {
ones, _ := netmask.Mask.Size()
model.IPv6PrefixLength = types.Int64Value(int64(ones))
}
}
model.IPv6Prefixes = prefixesV6TF
}
model.IPv6Gateway = types.StringNull()
if networkResp.Gatewayv6 != nil {
model.IPv6Gateway = types.StringPointerValue(networkResp.GetGatewayv6())
}
model.NetworkId = types.StringValue(networkId)
model.Name = types.StringPointerValue(networkResp.Name)
model.PublicIP = types.StringPointerValue(networkResp.PublicIp)
model.Labels = labels
model.Routed = types.BoolPointerValue(networkResp.Routed)
model.RoutingTableID = types.StringNull()
model.Region = types.StringNull()
return nil
}

View file

@ -1,4 +1,4 @@
package network package v1network
import ( import (
"context" "context"
@ -9,19 +9,20 @@ import (
"github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types"
"github.com/stackitcloud/stackit-sdk-go/core/utils" "github.com/stackitcloud/stackit-sdk-go/core/utils"
"github.com/stackitcloud/stackit-sdk-go/services/iaas" "github.com/stackitcloud/stackit-sdk-go/services/iaas"
networkModel "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/network/utils/model"
) )
func TestMapDataSourceFields(t *testing.T) { func TestMapDataSourceFields(t *testing.T) {
tests := []struct { tests := []struct {
description string description string
state DataSourceModel state networkModel.DataSourceModel
input *iaas.Network input *iaas.Network
expected DataSourceModel expected networkModel.DataSourceModel
isValid bool isValid bool
}{ }{
{ {
"id_ok", "id_ok",
DataSourceModel{ networkModel.DataSourceModel{
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"), NetworkId: types.StringValue("nid"),
}, },
@ -29,7 +30,7 @@ func TestMapDataSourceFields(t *testing.T) {
NetworkId: utils.Ptr("nid"), NetworkId: utils.Ptr("nid"),
Gateway: iaas.NewNullableString(nil), Gateway: iaas.NewNullableString(nil),
}, },
DataSourceModel{ networkModel.DataSourceModel{
Id: types.StringValue("pid,nid"), Id: types.StringValue("pid,nid"),
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"), NetworkId: types.StringValue("nid"),
@ -54,7 +55,7 @@ func TestMapDataSourceFields(t *testing.T) {
}, },
{ {
"values_ok", "values_ok",
DataSourceModel{ networkModel.DataSourceModel{
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"), NetworkId: types.StringValue("nid"),
}, },
@ -85,7 +86,7 @@ func TestMapDataSourceFields(t *testing.T) {
Gateway: iaas.NewNullableString(utils.Ptr("gateway")), Gateway: iaas.NewNullableString(utils.Ptr("gateway")),
Gatewayv6: iaas.NewNullableString(utils.Ptr("gateway")), Gatewayv6: iaas.NewNullableString(utils.Ptr("gateway")),
}, },
DataSourceModel{ networkModel.DataSourceModel{
Id: types.StringValue("pid,nid"), Id: types.StringValue("pid,nid"),
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"), NetworkId: types.StringValue("nid"),
@ -130,7 +131,7 @@ func TestMapDataSourceFields(t *testing.T) {
}, },
{ {
"ipv4_nameservers_changed_outside_tf", "ipv4_nameservers_changed_outside_tf",
DataSourceModel{ networkModel.DataSourceModel{
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"), NetworkId: types.StringValue("nid"),
Nameservers: types.ListValueMust(types.StringType, []attr.Value{ Nameservers: types.ListValueMust(types.StringType, []attr.Value{
@ -149,7 +150,7 @@ func TestMapDataSourceFields(t *testing.T) {
"ns3", "ns3",
}, },
}, },
DataSourceModel{ networkModel.DataSourceModel{
Id: types.StringValue("pid,nid"), Id: types.StringValue("pid,nid"),
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"), NetworkId: types.StringValue("nid"),
@ -172,7 +173,7 @@ func TestMapDataSourceFields(t *testing.T) {
}, },
{ {
"ipv6_nameservers_changed_outside_tf", "ipv6_nameservers_changed_outside_tf",
DataSourceModel{ networkModel.DataSourceModel{
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"), NetworkId: types.StringValue("nid"),
IPv6Nameservers: types.ListValueMust(types.StringType, []attr.Value{ IPv6Nameservers: types.ListValueMust(types.StringType, []attr.Value{
@ -187,7 +188,7 @@ func TestMapDataSourceFields(t *testing.T) {
"ns3", "ns3",
}, },
}, },
DataSourceModel{ networkModel.DataSourceModel{
Id: types.StringValue("pid,nid"), Id: types.StringValue("pid,nid"),
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"), NetworkId: types.StringValue("nid"),
@ -207,7 +208,7 @@ func TestMapDataSourceFields(t *testing.T) {
}, },
{ {
"ipv4_prefixes_changed_outside_tf", "ipv4_prefixes_changed_outside_tf",
DataSourceModel{ networkModel.DataSourceModel{
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"), NetworkId: types.StringValue("nid"),
Prefixes: types.ListValueMust(types.StringType, []attr.Value{ Prefixes: types.ListValueMust(types.StringType, []attr.Value{
@ -222,7 +223,7 @@ func TestMapDataSourceFields(t *testing.T) {
"10.100.10.0/16", "10.100.10.0/16",
}, },
}, },
DataSourceModel{ networkModel.DataSourceModel{
Id: types.StringValue("pid,nid"), Id: types.StringValue("pid,nid"),
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"), NetworkId: types.StringValue("nid"),
@ -248,7 +249,7 @@ func TestMapDataSourceFields(t *testing.T) {
}, },
{ {
"ipv6_prefixes_changed_outside_tf", "ipv6_prefixes_changed_outside_tf",
DataSourceModel{ networkModel.DataSourceModel{
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"), NetworkId: types.StringValue("nid"),
IPv6Prefixes: types.ListValueMust(types.StringType, []attr.Value{ IPv6Prefixes: types.ListValueMust(types.StringType, []attr.Value{
@ -263,7 +264,7 @@ func TestMapDataSourceFields(t *testing.T) {
"fd12:3456:789a:4::/64", "fd12:3456:789a:4::/64",
}, },
}, },
DataSourceModel{ networkModel.DataSourceModel{
Id: types.StringValue("pid,nid"), Id: types.StringValue("pid,nid"),
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"), NetworkId: types.StringValue("nid"),
@ -286,14 +287,14 @@ func TestMapDataSourceFields(t *testing.T) {
}, },
{ {
"ipv4_ipv6_gateway_nil", "ipv4_ipv6_gateway_nil",
DataSourceModel{ networkModel.DataSourceModel{
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"), NetworkId: types.StringValue("nid"),
}, },
&iaas.Network{ &iaas.Network{
NetworkId: utils.Ptr("nid"), NetworkId: utils.Ptr("nid"),
}, },
DataSourceModel{ networkModel.DataSourceModel{
Id: types.StringValue("pid,nid"), Id: types.StringValue("pid,nid"),
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"), NetworkId: types.StringValue("nid"),
@ -316,18 +317,18 @@ func TestMapDataSourceFields(t *testing.T) {
}, },
{ {
"response_nil_fail", "response_nil_fail",
DataSourceModel{}, networkModel.DataSourceModel{},
nil, nil,
DataSourceModel{}, networkModel.DataSourceModel{},
false, false,
}, },
{ {
"no_resource_id", "no_resource_id",
DataSourceModel{ networkModel.DataSourceModel{
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue("pid"),
}, },
&iaas.Network{}, &iaas.Network{},
DataSourceModel{}, networkModel.DataSourceModel{},
false, false,
}, },
} }

View file

@ -0,0 +1,512 @@
package v1network
import (
"context"
"fmt"
"net"
"net/http"
"strings"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/stackitcloud/stackit-sdk-go/core/oapierror"
"github.com/stackitcloud/stackit-sdk-go/services/iaas"
"github.com/stackitcloud/stackit-sdk-go/services/iaas/wait"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
networkModel "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/network/utils/model"
iaasUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/utils"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
)
func Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse, client *iaas.APIClient) { // nolint:gocritic // function signature required by Terraform
// Retrieve values from plan
var model networkModel.Model
diags := req.Plan.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
projectId := model.ProjectId.ValueString()
ctx = tflog.SetField(ctx, "project_id", projectId)
// Generate API request body from model
payload, err := toCreatePayload(ctx, &model)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating network", fmt.Sprintf("Creating API payload: %v", err))
return
}
// Create new network
network, err := client.CreateNetwork(ctx, projectId).CreateNetworkPayload(*payload).Execute()
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating network", fmt.Sprintf("Calling API: %v", err))
return
}
networkId := *network.NetworkId
network, err = wait.CreateNetworkWaitHandler(ctx, client, projectId, networkId).WaitWithContext(ctx)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating network", fmt.Sprintf("Network creation waiting: %v", err))
return
}
ctx = tflog.SetField(ctx, "network_id", networkId)
// Map response body to schema
err = mapFields(ctx, network, &model)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating network", fmt.Sprintf("Processing API payload: %v", err))
return
}
// Set state to fully populated data
diags = resp.State.Set(ctx, model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
tflog.Info(ctx, "Network created")
}
func Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse, client *iaas.APIClient) { // nolint:gocritic // function signature required by Terraform
var model networkModel.Model
diags := req.State.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
projectId := model.ProjectId.ValueString()
networkId := model.NetworkId.ValueString()
ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "network_id", networkId)
networkResp, err := client.GetNetwork(ctx, projectId, networkId).Execute()
if err != nil {
oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped
if ok && oapiErr.StatusCode == http.StatusNotFound {
resp.State.RemoveResource(ctx)
return
}
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading network", fmt.Sprintf("Calling API: %v", err))
return
}
// Map response body to schema
err = mapFields(ctx, networkResp, &model)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading network", fmt.Sprintf("Processing API payload: %v", err))
return
}
// Set refreshed state
diags = resp.State.Set(ctx, model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
tflog.Info(ctx, "Network read")
}
func Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse, client *iaas.APIClient) { // nolint:gocritic // function signature required by Terraform
// Retrieve values from plan
var model networkModel.Model
diags := req.Plan.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
projectId := model.ProjectId.ValueString()
networkId := model.NetworkId.ValueString()
ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "network_id", networkId)
// Retrieve values from state
var stateModel networkModel.Model
diags = req.State.Get(ctx, &stateModel)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
// Generate API request body from model
payload, err := toUpdatePayload(ctx, &model, &stateModel)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating network", fmt.Sprintf("Creating API payload: %v", err))
return
}
// Update existing network
err = client.PartialUpdateNetwork(ctx, projectId, networkId).PartialUpdateNetworkPayload(*payload).Execute()
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating network", fmt.Sprintf("Calling API: %v", err))
return
}
waitResp, err := wait.UpdateNetworkWaitHandler(ctx, client, projectId, networkId).WaitWithContext(ctx)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating network", fmt.Sprintf("Network update waiting: %v", err))
return
}
err = mapFields(ctx, waitResp, &model)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating network", fmt.Sprintf("Processing API payload: %v", err))
return
}
diags = resp.State.Set(ctx, model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
tflog.Info(ctx, "Network updated")
}
func Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse, client *iaas.APIClient) { // nolint:gocritic // function signature required by Terraform
// Retrieve values from state
var model networkModel.Model
diags := req.State.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
projectId := model.ProjectId.ValueString()
networkId := model.NetworkId.ValueString()
ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "network_id", networkId)
// Delete existing network
err := client.DeleteNetwork(ctx, projectId, networkId).Execute()
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting network", fmt.Sprintf("Calling API: %v", err))
return
}
_, err = wait.DeleteNetworkWaitHandler(ctx, client, projectId, networkId).WaitWithContext(ctx)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting network", fmt.Sprintf("Network deletion waiting: %v", err))
return
}
tflog.Info(ctx, "Network deleted")
}
// ImportState imports a resource into the Terraform state on success.
// The expected format of the resource import identifier is: project_id,network_id
func ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
idParts := strings.Split(req.ID, core.Separator)
if len(idParts) != 2 || idParts[0] == "" || idParts[1] == "" {
core.LogAndAddError(ctx, &resp.Diagnostics,
"Error importing network",
fmt.Sprintf("Expected import identifier with format: [project_id],[network_id] Got: %q", req.ID),
)
return
}
projectId := idParts[0]
networkId := idParts[1]
ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "network_id", networkId)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), projectId)...)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("network_id"), networkId)...)
tflog.Info(ctx, "Network state imported")
}
func mapFields(ctx context.Context, networkResp *iaas.Network, model *networkModel.Model) error {
if networkResp == nil {
return fmt.Errorf("response input is nil")
}
if model == nil {
return fmt.Errorf("model input is nil")
}
var networkId string
if model.NetworkId.ValueString() != "" {
networkId = model.NetworkId.ValueString()
} else if networkResp.NetworkId != nil {
networkId = *networkResp.NetworkId
} else {
return fmt.Errorf("network id not present")
}
model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), networkId)
labels, err := iaasUtils.MapLabels(ctx, networkResp.Labels, model.Labels)
if err != nil {
return err
}
// IPv4
if networkResp.Nameservers == nil {
model.Nameservers = types.ListNull(types.StringType)
model.IPv4Nameservers = types.ListNull(types.StringType)
} else {
respNameservers := *networkResp.Nameservers
modelNameservers, err := utils.ListValuetoStringSlice(model.Nameservers)
modelIPv4Nameservers, errIpv4 := utils.ListValuetoStringSlice(model.IPv4Nameservers)
if err != nil {
return fmt.Errorf("get current network nameservers from model: %w", err)
}
if errIpv4 != nil {
return fmt.Errorf("get current IPv4 network nameservers from model: %w", errIpv4)
}
reconciledNameservers := utils.ReconcileStringSlices(modelNameservers, respNameservers)
reconciledIPv4Nameservers := utils.ReconcileStringSlices(modelIPv4Nameservers, respNameservers)
nameserversTF, diags := types.ListValueFrom(ctx, types.StringType, reconciledNameservers)
ipv4NameserversTF, ipv4Diags := types.ListValueFrom(ctx, types.StringType, reconciledIPv4Nameservers)
if diags.HasError() {
return fmt.Errorf("map network nameservers: %w", core.DiagsToError(diags))
}
if ipv4Diags.HasError() {
return fmt.Errorf("map IPv4 network nameservers: %w", core.DiagsToError(ipv4Diags))
}
model.Nameservers = nameserversTF
model.IPv4Nameservers = ipv4NameserversTF
}
if networkResp.Prefixes == nil {
model.Prefixes = types.ListNull(types.StringType)
model.IPv4Prefixes = types.ListNull(types.StringType)
} else {
respPrefixes := *networkResp.Prefixes
prefixesTF, diags := types.ListValueFrom(ctx, types.StringType, respPrefixes)
if diags.HasError() {
return fmt.Errorf("map network prefixes: %w", core.DiagsToError(diags))
}
if len(respPrefixes) > 0 {
model.IPv4Prefix = types.StringValue(respPrefixes[0])
_, netmask, err := net.ParseCIDR(respPrefixes[0])
if err != nil {
// silently ignore parsing error for the netmask
model.IPv4PrefixLength = types.Int64Null()
} else {
ones, _ := netmask.Mask.Size()
model.IPv4PrefixLength = types.Int64Value(int64(ones))
}
}
model.Prefixes = prefixesTF
model.IPv4Prefixes = prefixesTF
}
if networkResp.Gateway != nil {
model.IPv4Gateway = types.StringPointerValue(networkResp.GetGateway())
} else {
model.IPv4Gateway = types.StringNull()
}
// IPv6
if networkResp.NameserversV6 == nil {
model.IPv6Nameservers = types.ListNull(types.StringType)
} else {
respIPv6Nameservers := *networkResp.NameserversV6
modelIPv6Nameservers, errIpv6 := utils.ListValuetoStringSlice(model.IPv6Nameservers)
if errIpv6 != nil {
return fmt.Errorf("get current IPv6 network nameservers from model: %w", errIpv6)
}
reconciledIPv6Nameservers := utils.ReconcileStringSlices(modelIPv6Nameservers, respIPv6Nameservers)
ipv6NameserversTF, ipv6Diags := types.ListValueFrom(ctx, types.StringType, reconciledIPv6Nameservers)
if ipv6Diags.HasError() {
return fmt.Errorf("map IPv6 network nameservers: %w", core.DiagsToError(ipv6Diags))
}
model.IPv6Nameservers = ipv6NameserversTF
}
if networkResp.PrefixesV6 == nil {
model.IPv6Prefixes = types.ListNull(types.StringType)
} else {
respPrefixesV6 := *networkResp.PrefixesV6
prefixesV6TF, diags := types.ListValueFrom(ctx, types.StringType, respPrefixesV6)
if diags.HasError() {
return fmt.Errorf("map network IPv6 prefixes: %w", core.DiagsToError(diags))
}
if len(respPrefixesV6) > 0 {
model.IPv6Prefix = types.StringValue(respPrefixesV6[0])
_, netmask, err := net.ParseCIDR(respPrefixesV6[0])
if err != nil {
// silently ignore parsing error for the netmask
model.IPv6PrefixLength = types.Int64Null()
} else {
ones, _ := netmask.Mask.Size()
model.IPv6PrefixLength = types.Int64Value(int64(ones))
}
}
model.IPv6Prefixes = prefixesV6TF
}
if networkResp.Gatewayv6 != nil {
model.IPv6Gateway = types.StringPointerValue(networkResp.GetGatewayv6())
} else {
model.IPv6Gateway = types.StringNull()
}
model.NetworkId = types.StringValue(networkId)
model.Name = types.StringPointerValue(networkResp.Name)
model.PublicIP = types.StringPointerValue(networkResp.PublicIp)
model.Labels = labels
model.Routed = types.BoolPointerValue(networkResp.Routed)
model.Region = types.StringNull()
model.RoutingTableID = types.StringNull()
return nil
}
func toCreatePayload(ctx context.Context, model *networkModel.Model) (*iaas.CreateNetworkPayload, error) {
if model == nil {
return nil, fmt.Errorf("nil model")
}
addressFamily := &iaas.CreateNetworkAddressFamily{}
modelIPv6Nameservers := []string{}
for _, ipv6ns := range model.IPv6Nameservers.Elements() {
ipv6NameserverString, ok := ipv6ns.(types.String)
if !ok {
return nil, fmt.Errorf("type assertion failed")
}
modelIPv6Nameservers = append(modelIPv6Nameservers, ipv6NameserverString.ValueString())
}
if !(model.IPv6Prefix.IsNull() || model.IPv6PrefixLength.IsNull() || model.IPv6Nameservers.IsNull()) {
addressFamily.Ipv6 = &iaas.CreateNetworkIPv6Body{
Nameservers: &modelIPv6Nameservers,
Prefix: conversion.StringValueToPointer(model.IPv6Prefix),
PrefixLength: conversion.Int64ValueToPointer(model.IPv6PrefixLength),
}
if model.NoIPv6Gateway.ValueBool() {
addressFamily.Ipv6.Gateway = iaas.NewNullableString(nil)
} else if !(model.IPv6Gateway.IsUnknown() || model.IPv6Gateway.IsNull()) {
addressFamily.Ipv6.Gateway = iaas.NewNullableString(conversion.StringValueToPointer(model.IPv6Gateway))
}
}
modelIPv4Nameservers := []string{}
var modelIPv4List []attr.Value
if !(model.IPv4Nameservers.IsNull() || model.IPv4Nameservers.IsUnknown()) {
modelIPv4List = model.IPv4Nameservers.Elements()
} else {
modelIPv4List = model.Nameservers.Elements()
}
for _, ipv4ns := range modelIPv4List {
ipv4NameserverString, ok := ipv4ns.(types.String)
if !ok {
return nil, fmt.Errorf("type assertion failed")
}
modelIPv4Nameservers = append(modelIPv4Nameservers, ipv4NameserverString.ValueString())
}
if !model.IPv4Prefix.IsNull() || !model.IPv4PrefixLength.IsNull() || !model.IPv4Nameservers.IsNull() || !model.Nameservers.IsNull() {
addressFamily.Ipv4 = &iaas.CreateNetworkIPv4Body{
Nameservers: &modelIPv4Nameservers,
Prefix: conversion.StringValueToPointer(model.IPv4Prefix),
PrefixLength: conversion.Int64ValueToPointer(model.IPv4PrefixLength),
}
if model.NoIPv4Gateway.ValueBool() {
addressFamily.Ipv4.Gateway = iaas.NewNullableString(nil)
} else if !(model.IPv4Gateway.IsUnknown() || model.IPv4Gateway.IsNull()) {
addressFamily.Ipv4.Gateway = iaas.NewNullableString(conversion.StringValueToPointer(model.IPv4Gateway))
}
}
labels, err := conversion.ToStringInterfaceMap(ctx, model.Labels)
if err != nil {
return nil, fmt.Errorf("converting to Go map: %w", err)
}
payload := iaas.CreateNetworkPayload{
Name: conversion.StringValueToPointer(model.Name),
Labels: &labels,
Routed: conversion.BoolValueToPointer(model.Routed),
}
if addressFamily.Ipv6 != nil || addressFamily.Ipv4 != nil {
payload.AddressFamily = addressFamily
}
return &payload, nil
}
func toUpdatePayload(ctx context.Context, model, stateModel *networkModel.Model) (*iaas.PartialUpdateNetworkPayload, error) {
if model == nil {
return nil, fmt.Errorf("nil model")
}
addressFamily := &iaas.UpdateNetworkAddressFamily{}
modelIPv6Nameservers := []string{}
for _, ipv6ns := range model.IPv6Nameservers.Elements() {
ipv6NameserverString, ok := ipv6ns.(types.String)
if !ok {
return nil, fmt.Errorf("type assertion failed")
}
modelIPv6Nameservers = append(modelIPv6Nameservers, ipv6NameserverString.ValueString())
}
if !(model.IPv6Nameservers.IsNull() || model.IPv6Nameservers.IsUnknown()) {
addressFamily.Ipv6 = &iaas.UpdateNetworkIPv6Body{
Nameservers: &modelIPv6Nameservers,
}
if model.NoIPv6Gateway.ValueBool() {
addressFamily.Ipv6.Gateway = iaas.NewNullableString(nil)
} else if !(model.IPv6Gateway.IsUnknown() || model.IPv6Gateway.IsNull()) {
addressFamily.Ipv6.Gateway = iaas.NewNullableString(conversion.StringValueToPointer(model.IPv6Gateway))
}
}
modelIPv4Nameservers := []string{}
var modelIPv4List []attr.Value
if !(model.IPv4Nameservers.IsNull() || model.IPv4Nameservers.IsUnknown()) {
modelIPv4List = model.IPv4Nameservers.Elements()
} else {
modelIPv4List = model.Nameservers.Elements()
}
for _, ipv4ns := range modelIPv4List {
ipv4NameserverString, ok := ipv4ns.(types.String)
if !ok {
return nil, fmt.Errorf("type assertion failed")
}
modelIPv4Nameservers = append(modelIPv4Nameservers, ipv4NameserverString.ValueString())
}
if !model.IPv4Nameservers.IsNull() || !model.Nameservers.IsNull() {
addressFamily.Ipv4 = &iaas.UpdateNetworkIPv4Body{
Nameservers: &modelIPv4Nameservers,
}
if model.NoIPv4Gateway.ValueBool() {
addressFamily.Ipv4.Gateway = iaas.NewNullableString(nil)
} else if !(model.IPv4Gateway.IsUnknown() || model.IPv4Gateway.IsNull()) {
addressFamily.Ipv4.Gateway = iaas.NewNullableString(conversion.StringValueToPointer(model.IPv4Gateway))
}
}
currentLabels := stateModel.Labels
labels, err := conversion.ToJSONMapPartialUpdatePayload(ctx, currentLabels, model.Labels)
if err != nil {
return nil, fmt.Errorf("converting to Go map: %w", err)
}
payload := iaas.PartialUpdateNetworkPayload{
Name: conversion.StringValueToPointer(model.Name),
Labels: &labels,
}
if addressFamily.Ipv6 != nil || addressFamily.Ipv4 != nil {
payload.AddressFamily = addressFamily
}
return &payload, nil
}

View file

@ -1,4 +1,4 @@
package network package v1network
import ( import (
"context" "context"
@ -9,19 +9,20 @@ import (
"github.com/hashicorp/terraform-plugin-framework/types" "github.com/hashicorp/terraform-plugin-framework/types"
"github.com/stackitcloud/stackit-sdk-go/core/utils" "github.com/stackitcloud/stackit-sdk-go/core/utils"
"github.com/stackitcloud/stackit-sdk-go/services/iaas" "github.com/stackitcloud/stackit-sdk-go/services/iaas"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/network/utils/model"
) )
func TestMapFields(t *testing.T) { func TestMapFields(t *testing.T) {
tests := []struct { tests := []struct {
description string description string
state Model state model.Model
input *iaas.Network input *iaas.Network
expected Model expected model.Model
isValid bool isValid bool
}{ }{
{ {
"id_ok", "id_ok",
Model{ model.Model{
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"), NetworkId: types.StringValue("nid"),
}, },
@ -29,7 +30,7 @@ func TestMapFields(t *testing.T) {
NetworkId: utils.Ptr("nid"), NetworkId: utils.Ptr("nid"),
Gateway: iaas.NewNullableString(nil), Gateway: iaas.NewNullableString(nil),
}, },
Model{ model.Model{
Id: types.StringValue("pid,nid"), Id: types.StringValue("pid,nid"),
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"), NetworkId: types.StringValue("nid"),
@ -54,7 +55,7 @@ func TestMapFields(t *testing.T) {
}, },
{ {
"values_ok", "values_ok",
Model{ model.Model{
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"), NetworkId: types.StringValue("nid"),
}, },
@ -85,7 +86,7 @@ func TestMapFields(t *testing.T) {
Gateway: iaas.NewNullableString(utils.Ptr("gateway")), Gateway: iaas.NewNullableString(utils.Ptr("gateway")),
Gatewayv6: iaas.NewNullableString(utils.Ptr("gateway")), Gatewayv6: iaas.NewNullableString(utils.Ptr("gateway")),
}, },
Model{ model.Model{
Id: types.StringValue("pid,nid"), Id: types.StringValue("pid,nid"),
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"), NetworkId: types.StringValue("nid"),
@ -130,7 +131,7 @@ func TestMapFields(t *testing.T) {
}, },
{ {
"ipv4_nameservers_changed_outside_tf", "ipv4_nameservers_changed_outside_tf",
Model{ model.Model{
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"), NetworkId: types.StringValue("nid"),
Nameservers: types.ListValueMust(types.StringType, []attr.Value{ Nameservers: types.ListValueMust(types.StringType, []attr.Value{
@ -149,7 +150,7 @@ func TestMapFields(t *testing.T) {
"ns3", "ns3",
}, },
}, },
Model{ model.Model{
Id: types.StringValue("pid,nid"), Id: types.StringValue("pid,nid"),
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"), NetworkId: types.StringValue("nid"),
@ -172,7 +173,7 @@ func TestMapFields(t *testing.T) {
}, },
{ {
"ipv6_nameservers_changed_outside_tf", "ipv6_nameservers_changed_outside_tf",
Model{ model.Model{
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"), NetworkId: types.StringValue("nid"),
IPv6Nameservers: types.ListValueMust(types.StringType, []attr.Value{ IPv6Nameservers: types.ListValueMust(types.StringType, []attr.Value{
@ -187,7 +188,7 @@ func TestMapFields(t *testing.T) {
"ns3", "ns3",
}, },
}, },
Model{ model.Model{
Id: types.StringValue("pid,nid"), Id: types.StringValue("pid,nid"),
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"), NetworkId: types.StringValue("nid"),
@ -207,7 +208,7 @@ func TestMapFields(t *testing.T) {
}, },
{ {
"ipv4_prefixes_changed_outside_tf", "ipv4_prefixes_changed_outside_tf",
Model{ model.Model{
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"), NetworkId: types.StringValue("nid"),
Prefixes: types.ListValueMust(types.StringType, []attr.Value{ Prefixes: types.ListValueMust(types.StringType, []attr.Value{
@ -222,7 +223,7 @@ func TestMapFields(t *testing.T) {
"192.168.55.0/24", "192.168.55.0/24",
}, },
}, },
Model{ model.Model{
Id: types.StringValue("pid,nid"), Id: types.StringValue("pid,nid"),
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"), NetworkId: types.StringValue("nid"),
@ -248,7 +249,7 @@ func TestMapFields(t *testing.T) {
}, },
{ {
"ipv6_prefixes_changed_outside_tf", "ipv6_prefixes_changed_outside_tf",
Model{ model.Model{
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"), NetworkId: types.StringValue("nid"),
IPv6Prefixes: types.ListValueMust(types.StringType, []attr.Value{ IPv6Prefixes: types.ListValueMust(types.StringType, []attr.Value{
@ -263,7 +264,7 @@ func TestMapFields(t *testing.T) {
"fd12:3456:789a:2::/64", "fd12:3456:789a:2::/64",
}, },
}, },
Model{ model.Model{
Id: types.StringValue("pid,nid"), Id: types.StringValue("pid,nid"),
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"), NetworkId: types.StringValue("nid"),
@ -286,14 +287,14 @@ func TestMapFields(t *testing.T) {
}, },
{ {
"ipv4_ipv6_gateway_nil", "ipv4_ipv6_gateway_nil",
Model{ model.Model{
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"), NetworkId: types.StringValue("nid"),
}, },
&iaas.Network{ &iaas.Network{
NetworkId: utils.Ptr("nid"), NetworkId: utils.Ptr("nid"),
}, },
Model{ model.Model{
Id: types.StringValue("pid,nid"), Id: types.StringValue("pid,nid"),
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"), NetworkId: types.StringValue("nid"),
@ -316,18 +317,18 @@ func TestMapFields(t *testing.T) {
}, },
{ {
"response_nil_fail", "response_nil_fail",
Model{}, model.Model{},
nil, nil,
Model{}, model.Model{},
false, false,
}, },
{ {
"no_resource_id", "no_resource_id",
Model{ model.Model{
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue("pid"),
}, },
&iaas.Network{}, &iaas.Network{},
Model{}, model.Model{},
false, false,
}, },
} }
@ -353,13 +354,13 @@ func TestMapFields(t *testing.T) {
func TestToCreatePayload(t *testing.T) { func TestToCreatePayload(t *testing.T) {
tests := []struct { tests := []struct {
description string description string
input *Model input *model.Model
expected *iaas.CreateNetworkPayload expected *iaas.CreateNetworkPayload
isValid bool isValid bool
}{ }{
{ {
"default_ok", "default_ok",
&Model{ &model.Model{
Name: types.StringValue("name"), Name: types.StringValue("name"),
IPv4Nameservers: types.ListValueMust(types.StringType, []attr.Value{ IPv4Nameservers: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("ns1"), types.StringValue("ns1"),
@ -395,7 +396,7 @@ func TestToCreatePayload(t *testing.T) {
}, },
{ {
"ipv4_nameservers_okay", "ipv4_nameservers_okay",
&Model{ &model.Model{
Name: types.StringValue("name"), Name: types.StringValue("name"),
Nameservers: types.ListValueMust(types.StringType, []attr.Value{ Nameservers: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("ns1"), types.StringValue("ns1"),
@ -431,7 +432,7 @@ func TestToCreatePayload(t *testing.T) {
}, },
{ {
"ipv6_default_ok", "ipv6_default_ok",
&Model{ &model.Model{
Name: types.StringValue("name"), Name: types.StringValue("name"),
IPv6Nameservers: types.ListValueMust(types.StringType, []attr.Value{ IPv6Nameservers: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("ns1"), types.StringValue("ns1"),
@ -488,14 +489,14 @@ func TestToCreatePayload(t *testing.T) {
func TestToUpdatePayload(t *testing.T) { func TestToUpdatePayload(t *testing.T) {
tests := []struct { tests := []struct {
description string description string
input *Model input *model.Model
state Model state model.Model
expected *iaas.PartialUpdateNetworkPayload expected *iaas.PartialUpdateNetworkPayload
isValid bool isValid bool
}{ }{
{ {
"default_ok", "default_ok",
&Model{ &model.Model{
Name: types.StringValue("name"), Name: types.StringValue("name"),
IPv4Nameservers: types.ListValueMust(types.StringType, []attr.Value{ IPv4Nameservers: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("ns1"), types.StringValue("ns1"),
@ -507,7 +508,7 @@ func TestToUpdatePayload(t *testing.T) {
Routed: types.BoolValue(true), Routed: types.BoolValue(true),
IPv4Gateway: types.StringValue("gateway"), IPv4Gateway: types.StringValue("gateway"),
}, },
Model{ model.Model{
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"), NetworkId: types.StringValue("nid"),
Labels: types.MapNull(types.StringType), Labels: types.MapNull(types.StringType),
@ -531,7 +532,7 @@ func TestToUpdatePayload(t *testing.T) {
}, },
{ {
"ipv4_nameservers_okay", "ipv4_nameservers_okay",
&Model{ &model.Model{
Name: types.StringValue("name"), Name: types.StringValue("name"),
Nameservers: types.ListValueMust(types.StringType, []attr.Value{ Nameservers: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("ns1"), types.StringValue("ns1"),
@ -543,7 +544,7 @@ func TestToUpdatePayload(t *testing.T) {
Routed: types.BoolValue(true), Routed: types.BoolValue(true),
IPv4Gateway: types.StringValue("gateway"), IPv4Gateway: types.StringValue("gateway"),
}, },
Model{ model.Model{
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"), NetworkId: types.StringValue("nid"),
Labels: types.MapNull(types.StringType), Labels: types.MapNull(types.StringType),
@ -567,7 +568,7 @@ func TestToUpdatePayload(t *testing.T) {
}, },
{ {
"ipv4_gateway_nil", "ipv4_gateway_nil",
&Model{ &model.Model{
Name: types.StringValue("name"), Name: types.StringValue("name"),
IPv4Nameservers: types.ListValueMust(types.StringType, []attr.Value{ IPv4Nameservers: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("ns1"), types.StringValue("ns1"),
@ -578,7 +579,7 @@ func TestToUpdatePayload(t *testing.T) {
}), }),
Routed: types.BoolValue(true), Routed: types.BoolValue(true),
}, },
Model{ model.Model{
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"), NetworkId: types.StringValue("nid"),
Labels: types.MapNull(types.StringType), Labels: types.MapNull(types.StringType),
@ -601,7 +602,7 @@ func TestToUpdatePayload(t *testing.T) {
}, },
{ {
"ipv6_default_ok", "ipv6_default_ok",
&Model{ &model.Model{
Name: types.StringValue("name"), Name: types.StringValue("name"),
IPv6Nameservers: types.ListValueMust(types.StringType, []attr.Value{ IPv6Nameservers: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("ns1"), types.StringValue("ns1"),
@ -613,7 +614,7 @@ func TestToUpdatePayload(t *testing.T) {
Routed: types.BoolValue(true), Routed: types.BoolValue(true),
IPv6Gateway: types.StringValue("gateway"), IPv6Gateway: types.StringValue("gateway"),
}, },
Model{ model.Model{
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"), NetworkId: types.StringValue("nid"),
Labels: types.MapNull(types.StringType), Labels: types.MapNull(types.StringType),
@ -637,7 +638,7 @@ func TestToUpdatePayload(t *testing.T) {
}, },
{ {
"ipv6_gateway_nil", "ipv6_gateway_nil",
&Model{ &model.Model{
Name: types.StringValue("name"), Name: types.StringValue("name"),
IPv6Nameservers: types.ListValueMust(types.StringType, []attr.Value{ IPv6Nameservers: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("ns1"), types.StringValue("ns1"),
@ -648,7 +649,7 @@ func TestToUpdatePayload(t *testing.T) {
}), }),
Routed: types.BoolValue(true), Routed: types.BoolValue(true),
}, },
Model{ model.Model{
ProjectId: types.StringValue("pid"), ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"), NetworkId: types.StringValue("nid"),
Labels: types.MapNull(types.StringType), Labels: types.MapNull(types.StringType),

View file

@ -0,0 +1,215 @@
package v2network
import (
"context"
"fmt"
"net"
"net/http"
"github.com/hashicorp/terraform-plugin-framework/datasource"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/stackitcloud/stackit-sdk-go/services/iaasalpha"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
networkModel "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/network/utils/model"
iaasUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/utils"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
)
func DatasourceRead(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse, client *iaasalpha.APIClient, providerData core.ProviderData) { // nolint:gocritic // function signature required by Terraform
var model networkModel.DataSourceModel
diags := req.Config.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
projectId := model.ProjectId.ValueString()
networkId := model.NetworkId.ValueString()
region := providerData.GetRegionWithOverride(model.Region)
ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "network_id", networkId)
networkResp, err := client.GetNetwork(ctx, projectId, region, networkId).Execute()
if err != nil {
utils.LogError(
ctx,
&resp.Diagnostics,
err,
"Reading network",
fmt.Sprintf("Network with ID %q does not exist in project %q.", networkId, projectId),
map[int]string{
http.StatusForbidden: fmt.Sprintf("Project with ID %q not found or forbidden access", projectId),
},
)
resp.State.RemoveResource(ctx)
return
}
err = mapDataSourceFields(ctx, networkResp, &model, region)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading network", fmt.Sprintf("Processing API payload: %v", err))
return
}
diags = resp.State.Set(ctx, model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
tflog.Info(ctx, "Network read")
}
func mapDataSourceFields(ctx context.Context, networkResp *iaasalpha.Network, model *networkModel.DataSourceModel, region string) error {
if networkResp == nil {
return fmt.Errorf("response input is nil")
}
if model == nil {
return fmt.Errorf("model input is nil")
}
var networkId string
if model.NetworkId.ValueString() != "" {
networkId = model.NetworkId.ValueString()
} else if networkResp.Id != nil {
networkId = *networkResp.Id
} else {
return fmt.Errorf("network id not present")
}
model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), region, networkId)
labels, err := iaasUtils.MapLabels(ctx, networkResp.Labels, model.Labels)
if err != nil {
return err
}
// IPv4
if networkResp.Ipv4 == nil || networkResp.Ipv4.Nameservers == nil {
model.Nameservers = types.ListNull(types.StringType)
model.IPv4Nameservers = types.ListNull(types.StringType)
} else {
respNameservers := *networkResp.Ipv4.Nameservers
modelNameservers, err := utils.ListValuetoStringSlice(model.Nameservers)
modelIPv4Nameservers, errIpv4 := utils.ListValuetoStringSlice(model.IPv4Nameservers)
if err != nil {
return fmt.Errorf("get current network nameservers from model: %w", err)
}
if errIpv4 != nil {
return fmt.Errorf("get current IPv4 network nameservers from model: %w", errIpv4)
}
reconciledNameservers := utils.ReconcileStringSlices(modelNameservers, respNameservers)
reconciledIPv4Nameservers := utils.ReconcileStringSlices(modelIPv4Nameservers, respNameservers)
nameserversTF, diags := types.ListValueFrom(ctx, types.StringType, reconciledNameservers)
ipv4NameserversTF, ipv4Diags := types.ListValueFrom(ctx, types.StringType, reconciledIPv4Nameservers)
if diags.HasError() {
return fmt.Errorf("map network nameservers: %w", core.DiagsToError(diags))
}
if ipv4Diags.HasError() {
return fmt.Errorf("map IPv4 network nameservers: %w", core.DiagsToError(ipv4Diags))
}
model.Nameservers = nameserversTF
model.IPv4Nameservers = ipv4NameserversTF
}
if networkResp.Ipv4 == nil || networkResp.Ipv4.Prefixes == nil {
model.Prefixes = types.ListNull(types.StringType)
model.IPv4Prefixes = types.ListNull(types.StringType)
} else {
respPrefixes := *networkResp.Ipv4.Prefixes
prefixesTF, diags := types.ListValueFrom(ctx, types.StringType, respPrefixes)
if diags.HasError() {
return fmt.Errorf("map network prefixes: %w", core.DiagsToError(diags))
}
if len(respPrefixes) > 0 {
model.IPv4Prefix = types.StringValue(respPrefixes[0])
_, netmask, err := net.ParseCIDR(respPrefixes[0])
if err != nil {
// silently ignore parsing error for the netmask
model.IPv4PrefixLength = types.Int64Null()
} else {
ones, _ := netmask.Mask.Size()
model.IPv4PrefixLength = types.Int64Value(int64(ones))
}
}
model.Prefixes = prefixesTF
model.IPv4Prefixes = prefixesTF
}
if networkResp.Ipv4 == nil || networkResp.Ipv4.Gateway == nil {
model.IPv4Gateway = types.StringNull()
} else {
model.IPv4Gateway = types.StringPointerValue(networkResp.Ipv4.GetGateway())
}
if networkResp.Ipv4 == nil || networkResp.Ipv4.PublicIp == nil {
model.PublicIP = types.StringNull()
} else {
model.PublicIP = types.StringPointerValue(networkResp.Ipv4.PublicIp)
}
// IPv6
if networkResp.Ipv6 == nil || networkResp.Ipv6.Nameservers == nil {
model.IPv6Nameservers = types.ListNull(types.StringType)
} else {
respIPv6Nameservers := *networkResp.Ipv6.Nameservers
modelIPv6Nameservers, errIpv6 := utils.ListValuetoStringSlice(model.IPv6Nameservers)
if errIpv6 != nil {
return fmt.Errorf("get current IPv6 network nameservers from model: %w", errIpv6)
}
reconciledIPv6Nameservers := utils.ReconcileStringSlices(modelIPv6Nameservers, respIPv6Nameservers)
ipv6NameserversTF, ipv6Diags := types.ListValueFrom(ctx, types.StringType, reconciledIPv6Nameservers)
if ipv6Diags.HasError() {
return fmt.Errorf("map IPv6 network nameservers: %w", core.DiagsToError(ipv6Diags))
}
model.IPv6Nameservers = ipv6NameserversTF
}
if networkResp.Ipv6 == nil || networkResp.Ipv6.Prefixes == nil {
model.IPv6Prefixes = types.ListNull(types.StringType)
} else {
respPrefixesV6 := *networkResp.Ipv6.Prefixes
prefixesV6TF, diags := types.ListValueFrom(ctx, types.StringType, respPrefixesV6)
if diags.HasError() {
return fmt.Errorf("map network IPv6 prefixes: %w", core.DiagsToError(diags))
}
if len(respPrefixesV6) > 0 {
model.IPv6Prefix = types.StringValue(respPrefixesV6[0])
_, netmask, err := net.ParseCIDR(respPrefixesV6[0])
if err != nil {
// silently ignore parsing error for the netmask
model.IPv6PrefixLength = types.Int64Null()
} else {
ones, _ := netmask.Mask.Size()
model.IPv6PrefixLength = types.Int64Value(int64(ones))
}
}
model.IPv6Prefixes = prefixesV6TF
}
if networkResp.Ipv6 == nil || networkResp.Ipv6.Gateway == nil {
model.IPv6Gateway = types.StringNull()
} else {
model.IPv6Gateway = types.StringPointerValue(networkResp.Ipv6.GetGateway())
}
model.RoutingTableID = types.StringNull()
if networkResp.RoutingTableId != nil {
model.RoutingTableID = types.StringValue(*networkResp.RoutingTableId)
}
model.NetworkId = types.StringValue(networkId)
model.Name = types.StringPointerValue(networkResp.Name)
model.Labels = labels
model.Routed = types.BoolPointerValue(networkResp.Routed)
model.Region = types.StringValue(region)
return nil
}

View file

@ -0,0 +1,387 @@
package v2network
import (
"context"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/stackitcloud/stackit-sdk-go/core/utils"
"github.com/stackitcloud/stackit-sdk-go/services/iaasalpha"
networkModel "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/network/utils/model"
)
const (
testRegion = "region"
)
func TestMapDataSourceFields(t *testing.T) {
tests := []struct {
description string
state networkModel.DataSourceModel
input *iaasalpha.Network
region string
expected networkModel.DataSourceModel
isValid bool
}{
{
"id_ok",
networkModel.DataSourceModel{
ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"),
},
&iaasalpha.Network{
Id: utils.Ptr("nid"),
Ipv4: &iaasalpha.NetworkIPv4{
Gateway: iaasalpha.NewNullableString(nil),
},
},
testRegion,
networkModel.DataSourceModel{
Id: types.StringValue("pid,region,nid"),
ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"),
Name: types.StringNull(),
Nameservers: types.ListNull(types.StringType),
IPv4Nameservers: types.ListNull(types.StringType),
IPv4PrefixLength: types.Int64Null(),
IPv4Gateway: types.StringNull(),
IPv4Prefix: types.StringNull(),
Prefixes: types.ListNull(types.StringType),
IPv4Prefixes: types.ListNull(types.StringType),
IPv6Nameservers: types.ListNull(types.StringType),
IPv6PrefixLength: types.Int64Null(),
IPv6Gateway: types.StringNull(),
IPv6Prefix: types.StringNull(),
IPv6Prefixes: types.ListNull(types.StringType),
PublicIP: types.StringNull(),
Labels: types.MapNull(types.StringType),
Routed: types.BoolNull(),
Region: types.StringValue(testRegion),
},
true,
},
{
"values_ok",
networkModel.DataSourceModel{
ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"),
},
&iaasalpha.Network{
Id: utils.Ptr("nid"),
Name: utils.Ptr("name"),
Ipv4: &iaasalpha.NetworkIPv4{
Nameservers: &[]string{
"ns1",
"ns2",
},
Prefixes: &[]string{
"192.168.42.0/24",
"10.100.10.0/16",
},
PublicIp: utils.Ptr("publicIp"),
Gateway: iaasalpha.NewNullableString(utils.Ptr("gateway")),
},
Ipv6: &iaasalpha.NetworkIPv6{
Nameservers: &[]string{
"ns1",
"ns2",
},
Prefixes: &[]string{
"fd12:3456:789a:1::/64",
"fd12:3456:789a:2::/64",
},
Gateway: iaasalpha.NewNullableString(utils.Ptr("gateway")),
},
Labels: &map[string]interface{}{
"key": "value",
},
Routed: utils.Ptr(true),
},
testRegion,
networkModel.DataSourceModel{
Id: types.StringValue("pid,region,nid"),
ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"),
Name: types.StringValue("name"),
Nameservers: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("ns1"),
types.StringValue("ns2"),
}),
IPv4Nameservers: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("ns1"),
types.StringValue("ns2"),
}),
IPv4PrefixLength: types.Int64Value(24),
Prefixes: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("192.168.42.0/24"),
types.StringValue("10.100.10.0/16"),
}),
IPv4Prefix: types.StringValue("192.168.42.0/24"),
IPv4Prefixes: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("192.168.42.0/24"),
types.StringValue("10.100.10.0/16"),
}),
IPv6Nameservers: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("ns1"),
types.StringValue("ns2"),
}),
IPv6PrefixLength: types.Int64Value(64),
IPv6Prefix: types.StringValue("fd12:3456:789a:1::/64"),
IPv6Prefixes: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("fd12:3456:789a:1::/64"),
types.StringValue("fd12:3456:789a:2::/64"),
}),
PublicIP: types.StringValue("publicIp"),
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
"key": types.StringValue("value"),
}),
Routed: types.BoolValue(true),
IPv4Gateway: types.StringValue("gateway"),
IPv6Gateway: types.StringValue("gateway"),
Region: types.StringValue(testRegion),
},
true,
},
{
"ipv4_nameservers_changed_outside_tf",
networkModel.DataSourceModel{
ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"),
Nameservers: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("ns1"),
types.StringValue("ns2"),
}),
IPv4Nameservers: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("ns1"),
types.StringValue("ns2"),
}),
},
&iaasalpha.Network{
Id: utils.Ptr("nid"),
Ipv4: &iaasalpha.NetworkIPv4{
Nameservers: &[]string{
"ns2",
"ns3",
},
},
},
testRegion,
networkModel.DataSourceModel{
Id: types.StringValue("pid,region,nid"),
ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"),
Name: types.StringNull(),
IPv6Prefixes: types.ListNull(types.StringType),
IPv6Nameservers: types.ListNull(types.StringType),
Prefixes: types.ListNull(types.StringType),
IPv4Prefixes: types.ListNull(types.StringType),
Nameservers: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("ns2"),
types.StringValue("ns3"),
}),
IPv4Nameservers: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("ns2"),
types.StringValue("ns3"),
}),
Labels: types.MapNull(types.StringType),
Region: types.StringValue(testRegion),
},
true,
},
{
"ipv6_nameservers_changed_outside_tf",
networkModel.DataSourceModel{
ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"),
IPv6Nameservers: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("ns1"),
types.StringValue("ns2"),
}),
},
&iaasalpha.Network{
Id: utils.Ptr("nid"),
Ipv6: &iaasalpha.NetworkIPv6{
Nameservers: &[]string{
"ns2",
"ns3",
},
},
},
testRegion,
networkModel.DataSourceModel{
Id: types.StringValue("pid,region,nid"),
ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"),
Name: types.StringNull(),
IPv6Prefixes: types.ListNull(types.StringType),
IPv4Nameservers: types.ListNull(types.StringType),
Prefixes: types.ListNull(types.StringType),
IPv4Prefixes: types.ListNull(types.StringType),
Nameservers: types.ListNull(types.StringType),
IPv6Nameservers: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("ns2"),
types.StringValue("ns3"),
}),
Labels: types.MapNull(types.StringType),
Region: types.StringValue(testRegion),
},
true,
},
{
"ipv4_prefixes_changed_outside_tf",
networkModel.DataSourceModel{
ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"),
Prefixes: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("192.168.42.0/24"),
types.StringValue("10.100.10.0/16"),
}),
},
&iaasalpha.Network{
Id: utils.Ptr("nid"),
Ipv4: &iaasalpha.NetworkIPv4{
Prefixes: &[]string{
"10.100.20.0/16",
"10.100.10.0/16",
},
},
},
testRegion,
networkModel.DataSourceModel{
Id: types.StringValue("pid,region,nid"),
ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"),
Name: types.StringNull(),
IPv6Nameservers: types.ListNull(types.StringType),
IPv6PrefixLength: types.Int64Null(),
IPv6Prefixes: types.ListNull(types.StringType),
Labels: types.MapNull(types.StringType),
Nameservers: types.ListNull(types.StringType),
IPv4Nameservers: types.ListNull(types.StringType),
IPv4PrefixLength: types.Int64Value(16),
IPv4Prefix: types.StringValue("10.100.20.0/16"),
Prefixes: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("10.100.20.0/16"),
types.StringValue("10.100.10.0/16"),
}),
IPv4Prefixes: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("10.100.20.0/16"),
types.StringValue("10.100.10.0/16"),
}),
Region: types.StringValue(testRegion),
},
true,
},
{
"ipv6_prefixes_changed_outside_tf",
networkModel.DataSourceModel{
ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"),
IPv6Prefixes: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("fd12:3456:789a:1::/64"),
types.StringValue("fd12:3456:789a:2::/64"),
}),
},
&iaasalpha.Network{
Id: utils.Ptr("nid"),
Ipv6: &iaasalpha.NetworkIPv6{
Prefixes: &[]string{
"fd12:3456:789a:3::/64",
"fd12:3456:789a:4::/64",
},
},
},
testRegion,
networkModel.DataSourceModel{
Id: types.StringValue("pid,region,nid"),
ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"),
Name: types.StringNull(),
IPv4Nameservers: types.ListNull(types.StringType),
IPv4PrefixLength: types.Int64Null(),
Prefixes: types.ListNull(types.StringType),
IPv4Prefixes: types.ListNull(types.StringType),
Labels: types.MapNull(types.StringType),
Nameservers: types.ListNull(types.StringType),
IPv6Nameservers: types.ListNull(types.StringType),
IPv6PrefixLength: types.Int64Value(64),
IPv6Prefix: types.StringValue("fd12:3456:789a:3::/64"),
IPv6Prefixes: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("fd12:3456:789a:3::/64"),
types.StringValue("fd12:3456:789a:4::/64"),
}),
Region: types.StringValue(testRegion),
},
true,
},
{
"ipv4_ipv6_gateway_nil",
networkModel.DataSourceModel{
ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"),
},
&iaasalpha.Network{
Id: utils.Ptr("nid"),
},
testRegion,
networkModel.DataSourceModel{
Id: types.StringValue("pid,region,nid"),
ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"),
Name: types.StringNull(),
Nameservers: types.ListNull(types.StringType),
IPv4Nameservers: types.ListNull(types.StringType),
IPv4PrefixLength: types.Int64Null(),
IPv4Gateway: types.StringNull(),
Prefixes: types.ListNull(types.StringType),
IPv4Prefixes: types.ListNull(types.StringType),
IPv6Nameservers: types.ListNull(types.StringType),
IPv6PrefixLength: types.Int64Null(),
IPv6Gateway: types.StringNull(),
IPv6Prefixes: types.ListNull(types.StringType),
PublicIP: types.StringNull(),
Labels: types.MapNull(types.StringType),
Routed: types.BoolNull(),
Region: types.StringValue(testRegion),
},
true,
},
{
"response_nil_fail",
networkModel.DataSourceModel{},
nil,
testRegion,
networkModel.DataSourceModel{},
false,
},
{
"no_resource_id",
networkModel.DataSourceModel{
ProjectId: types.StringValue("pid"),
},
&iaasalpha.Network{},
testRegion,
networkModel.DataSourceModel{},
false,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
err := mapDataSourceFields(context.Background(), tt.input, &tt.state, tt.region)
if !tt.isValid && err == nil {
t.Fatalf("Should have failed")
}
if tt.isValid && err != nil {
t.Fatalf("Should not have failed: %v", err)
}
if tt.isValid {
diff := cmp.Diff(tt.state, tt.expected)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
}
})
}
}

View file

@ -0,0 +1,555 @@
package v2network
import (
"context"
"fmt"
"net"
"net/http"
"strings"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/hashicorp/terraform-plugin-log/tflog"
"github.com/stackitcloud/stackit-sdk-go/core/oapierror"
"github.com/stackitcloud/stackit-sdk-go/services/iaasalpha"
"github.com/stackitcloud/stackit-sdk-go/services/iaasalpha/wait"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/conversion"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
networkModel "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/network/utils/model"
iaasUtils "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/utils"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/utils"
)
func Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse, client *iaasalpha.APIClient) { // nolint:gocritic // function signature required by Terraform
// Retrieve values from plan
var model networkModel.Model
diags := req.Plan.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
projectId := model.ProjectId.ValueString()
region := model.Region.ValueString()
ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "region", region)
// Generate API request body from model
payload, err := toCreatePayload(ctx, &model)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating network", fmt.Sprintf("Creating API payload: %v", err))
return
}
// Create new network
network, err := client.CreateNetwork(ctx, projectId, region).CreateNetworkPayload(*payload).Execute()
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating network", fmt.Sprintf("Calling API: %v", err))
return
}
networkId := *network.Id
network, err = wait.CreateNetworkWaitHandler(ctx, client, projectId, region, networkId).WaitWithContext(ctx)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating network", fmt.Sprintf("Network creation waiting: %v", err))
return
}
ctx = tflog.SetField(ctx, "network_id", networkId)
// Map response body to schema
err = mapFields(ctx, network, &model, region)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error creating network", fmt.Sprintf("Processing API payload: %v", err))
return
}
// Set state to fully populated data
diags = resp.State.Set(ctx, model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
tflog.Info(ctx, "Network created")
}
func Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse, client *iaasalpha.APIClient, providerData core.ProviderData) { // nolint:gocritic // function signature required by Terraform
var model networkModel.Model
diags := req.State.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
projectId := model.ProjectId.ValueString()
networkId := model.NetworkId.ValueString()
region := providerData.GetRegionWithOverride(model.Region)
ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "network_id", networkId)
ctx = tflog.SetField(ctx, "region", region)
networkResp, err := client.GetNetwork(ctx, projectId, region, networkId).Execute()
if err != nil {
oapiErr, ok := err.(*oapierror.GenericOpenAPIError) //nolint:errorlint //complaining that error.As should be used to catch wrapped errors, but this error should not be wrapped
if ok && oapiErr.StatusCode == http.StatusNotFound {
resp.State.RemoveResource(ctx)
return
}
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading network", fmt.Sprintf("Calling API: %v", err))
return
}
// Map response body to schema
err = mapFields(ctx, networkResp, &model, region)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error reading network", fmt.Sprintf("Processing API payload: %v", err))
return
}
// Set refreshed state
diags = resp.State.Set(ctx, model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
tflog.Info(ctx, "Network read")
}
func Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse, client *iaasalpha.APIClient) { // nolint:gocritic // function signature required by Terraform
// Retrieve values from plan
var model networkModel.Model
diags := req.Plan.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
projectId := model.ProjectId.ValueString()
networkId := model.NetworkId.ValueString()
region := model.Region.ValueString()
ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "network_id", networkId)
ctx = tflog.SetField(ctx, "region", region)
// Retrieve values from state
var stateModel networkModel.Model
diags = req.State.Get(ctx, &stateModel)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
// Generate API request body from model
payload, err := toUpdatePayload(ctx, &model, &stateModel)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating network", fmt.Sprintf("Creating API payload: %v", err))
return
}
// Update existing network
err = client.PartialUpdateNetwork(ctx, projectId, region, networkId).PartialUpdateNetworkPayload(*payload).Execute()
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating network", fmt.Sprintf("Calling API: %v", err))
return
}
waitResp, err := wait.UpdateNetworkWaitHandler(ctx, client, projectId, region, networkId).WaitWithContext(ctx)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating network", fmt.Sprintf("Network update waiting: %v", err))
return
}
err = mapFields(ctx, waitResp, &model, region)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error updating network", fmt.Sprintf("Processing API payload: %v", err))
return
}
diags = resp.State.Set(ctx, model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
tflog.Info(ctx, "Network updated")
}
func Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse, client *iaasalpha.APIClient) { // nolint:gocritic // function signature required by Terraform
// Retrieve values from state
var model networkModel.Model
diags := req.State.Get(ctx, &model)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
projectId := model.ProjectId.ValueString()
networkId := model.NetworkId.ValueString()
region := model.Region.ValueString()
ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "network_id", networkId)
ctx = tflog.SetField(ctx, "region", region)
// Delete existing network
err := client.DeleteNetwork(ctx, projectId, region, networkId).Execute()
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting network", fmt.Sprintf("Calling API: %v", err))
return
}
_, err = wait.DeleteNetworkWaitHandler(ctx, client, projectId, region, networkId).WaitWithContext(ctx)
if err != nil {
core.LogAndAddError(ctx, &resp.Diagnostics, "Error deleting network", fmt.Sprintf("Network deletion waiting: %v", err))
return
}
tflog.Info(ctx, "Network deleted")
}
// ImportState imports a resource into the Terraform state on success.
// The expected format of the resource import identifier is: project_id,region,network_id
func ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
idParts := strings.Split(req.ID, core.Separator)
if len(idParts) != 3 || idParts[0] == "" || idParts[1] == "" || idParts[2] == "" {
core.LogAndAddError(ctx, &resp.Diagnostics,
"Error importing network",
fmt.Sprintf("Expected import identifier with format: [project_id],[region],[network_id] Got: %q", req.ID),
)
return
}
projectId := idParts[0]
region := idParts[1]
networkId := idParts[2]
ctx = tflog.SetField(ctx, "project_id", projectId)
ctx = tflog.SetField(ctx, "region", region)
ctx = tflog.SetField(ctx, "network_id", networkId)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("project_id"), projectId)...)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("region"), region)...)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("network_id"), networkId)...)
tflog.Info(ctx, "Network state imported")
}
func mapFields(ctx context.Context, networkResp *iaasalpha.Network, model *networkModel.Model, region string) error {
if networkResp == nil {
return fmt.Errorf("response input is nil")
}
if model == nil {
return fmt.Errorf("model input is nil")
}
var networkId string
if model.NetworkId.ValueString() != "" {
networkId = model.NetworkId.ValueString()
} else if networkResp.Id != nil {
networkId = *networkResp.Id
} else {
return fmt.Errorf("network id not present")
}
model.Id = utils.BuildInternalTerraformId(model.ProjectId.ValueString(), region, networkId)
labels, err := iaasUtils.MapLabels(ctx, networkResp.Labels, model.Labels)
if err != nil {
return err
}
// IPv4
if networkResp.Ipv4 == nil || networkResp.Ipv4.Nameservers == nil {
model.Nameservers = types.ListNull(types.StringType)
model.IPv4Nameservers = types.ListNull(types.StringType)
} else {
respNameservers := *networkResp.Ipv4.Nameservers
modelNameservers, err := utils.ListValuetoStringSlice(model.Nameservers)
modelIPv4Nameservers, errIpv4 := utils.ListValuetoStringSlice(model.IPv4Nameservers)
if err != nil {
return fmt.Errorf("get current network nameservers from model: %w", err)
}
if errIpv4 != nil {
return fmt.Errorf("get current IPv4 network nameservers from model: %w", errIpv4)
}
reconciledNameservers := utils.ReconcileStringSlices(modelNameservers, respNameservers)
reconciledIPv4Nameservers := utils.ReconcileStringSlices(modelIPv4Nameservers, respNameservers)
nameserversTF, diags := types.ListValueFrom(ctx, types.StringType, reconciledNameservers)
ipv4NameserversTF, ipv4Diags := types.ListValueFrom(ctx, types.StringType, reconciledIPv4Nameservers)
if diags.HasError() {
return fmt.Errorf("map network nameservers: %w", core.DiagsToError(diags))
}
if ipv4Diags.HasError() {
return fmt.Errorf("map IPv4 network nameservers: %w", core.DiagsToError(ipv4Diags))
}
model.Nameservers = nameserversTF
model.IPv4Nameservers = ipv4NameserversTF
}
if networkResp.Ipv4 == nil || networkResp.Ipv4.Prefixes == nil {
model.Prefixes = types.ListNull(types.StringType)
model.IPv4Prefixes = types.ListNull(types.StringType)
} else {
respPrefixes := *networkResp.Ipv4.Prefixes
prefixesTF, diags := types.ListValueFrom(ctx, types.StringType, respPrefixes)
if diags.HasError() {
return fmt.Errorf("map network prefixes: %w", core.DiagsToError(diags))
}
if len(respPrefixes) > 0 {
model.IPv4Prefix = types.StringValue(respPrefixes[0])
_, netmask, err := net.ParseCIDR(respPrefixes[0])
if err != nil {
tflog.Error(ctx, fmt.Sprintf("ipv4_prefix_length: %+v", err))
// silently ignore parsing error for the netmask
model.IPv4PrefixLength = types.Int64Null()
} else {
ones, _ := netmask.Mask.Size()
model.IPv4PrefixLength = types.Int64Value(int64(ones))
}
}
model.Prefixes = prefixesTF
model.IPv4Prefixes = prefixesTF
}
if networkResp.Ipv4 == nil || networkResp.Ipv4.Gateway == nil {
model.IPv4Gateway = types.StringNull()
} else {
model.IPv4Gateway = types.StringPointerValue(networkResp.Ipv4.GetGateway())
}
if networkResp.Ipv4 == nil || networkResp.Ipv4.PublicIp == nil {
model.PublicIP = types.StringNull()
} else {
model.PublicIP = types.StringPointerValue(networkResp.Ipv4.PublicIp)
}
// IPv6
if networkResp.Ipv6 == nil || networkResp.Ipv6.Nameservers == nil {
model.IPv6Nameservers = types.ListNull(types.StringType)
} else {
respIPv6Nameservers := *networkResp.Ipv6.Nameservers
modelIPv6Nameservers, errIpv6 := utils.ListValuetoStringSlice(model.IPv6Nameservers)
if errIpv6 != nil {
return fmt.Errorf("get current IPv6 network nameservers from model: %w", errIpv6)
}
reconciledIPv6Nameservers := utils.ReconcileStringSlices(modelIPv6Nameservers, respIPv6Nameservers)
ipv6NameserversTF, ipv6Diags := types.ListValueFrom(ctx, types.StringType, reconciledIPv6Nameservers)
if ipv6Diags.HasError() {
return fmt.Errorf("map IPv6 network nameservers: %w", core.DiagsToError(ipv6Diags))
}
model.IPv6Nameservers = ipv6NameserversTF
}
if networkResp.Ipv6 == nil || networkResp.Ipv6.Prefixes == nil {
model.IPv6Prefixes = types.ListNull(types.StringType)
} else {
respPrefixesV6 := *networkResp.Ipv6.Prefixes
prefixesV6TF, diags := types.ListValueFrom(ctx, types.StringType, respPrefixesV6)
if diags.HasError() {
return fmt.Errorf("map network IPv6 prefixes: %w", core.DiagsToError(diags))
}
if len(respPrefixesV6) > 0 {
model.IPv6Prefix = types.StringValue(respPrefixesV6[0])
_, netmask, err := net.ParseCIDR(respPrefixesV6[0])
if err != nil {
// silently ignore parsing error for the netmask
model.IPv6PrefixLength = types.Int64Null()
} else {
ones, _ := netmask.Mask.Size()
model.IPv6PrefixLength = types.Int64Value(int64(ones))
}
}
model.IPv6Prefixes = prefixesV6TF
}
if networkResp.Ipv6 == nil || networkResp.Ipv6.Gateway == nil {
model.IPv6Gateway = types.StringNull()
} else {
model.IPv6Gateway = types.StringPointerValue(networkResp.Ipv6.GetGateway())
}
if networkResp.RoutingTableId != nil {
model.RoutingTableID = types.StringPointerValue(networkResp.RoutingTableId)
} else {
model.RoutingTableID = types.StringNull()
}
model.NetworkId = types.StringValue(networkId)
model.Name = types.StringPointerValue(networkResp.Name)
model.Labels = labels
model.Routed = types.BoolPointerValue(networkResp.Routed)
model.Region = types.StringValue(region)
return nil
}
func toCreatePayload(ctx context.Context, model *networkModel.Model) (*iaasalpha.CreateNetworkPayload, error) {
if model == nil {
return nil, fmt.Errorf("nil model")
}
modelIPv6Nameservers := []string{}
for _, ipv6ns := range model.IPv6Nameservers.Elements() {
ipv6NameserverString, ok := ipv6ns.(types.String)
if !ok {
return nil, fmt.Errorf("type assertion failed")
}
modelIPv6Nameservers = append(modelIPv6Nameservers, ipv6NameserverString.ValueString())
}
var ipv6Body *iaasalpha.CreateNetworkIPv6
if !utils.IsUndefined(model.IPv6PrefixLength) {
ipv6Body = &iaasalpha.CreateNetworkIPv6{
CreateNetworkIPv6WithPrefixLength: &iaasalpha.CreateNetworkIPv6WithPrefixLength{
Nameservers: &modelIPv6Nameservers,
PrefixLength: conversion.Int64ValueToPointer(model.IPv6PrefixLength),
},
}
} else if !utils.IsUndefined(model.IPv6Prefix) {
var gateway *iaasalpha.NullableString
if model.NoIPv6Gateway.ValueBool() {
gateway = iaasalpha.NewNullableString(nil)
} else if !(model.IPv6Gateway.IsUnknown() || model.IPv6Gateway.IsNull()) {
gateway = iaasalpha.NewNullableString(conversion.StringValueToPointer(model.IPv6Gateway))
}
ipv6Body = &iaasalpha.CreateNetworkIPv6{
CreateNetworkIPv6WithPrefix: &iaasalpha.CreateNetworkIPv6WithPrefix{
Gateway: gateway,
Nameservers: &modelIPv6Nameservers,
Prefix: conversion.StringValueToPointer(model.IPv6Prefix),
},
}
}
modelIPv4Nameservers := []string{}
var modelIPv4List []attr.Value
if !(model.IPv4Nameservers.IsNull() || model.IPv4Nameservers.IsUnknown()) {
modelIPv4List = model.IPv4Nameservers.Elements()
} else {
modelIPv4List = model.Nameservers.Elements()
}
for _, ipv4ns := range modelIPv4List {
ipv4NameserverString, ok := ipv4ns.(types.String)
if !ok {
return nil, fmt.Errorf("type assertion failed")
}
modelIPv4Nameservers = append(modelIPv4Nameservers, ipv4NameserverString.ValueString())
}
var ipv4Body *iaasalpha.CreateNetworkIPv4
if !utils.IsUndefined(model.IPv4PrefixLength) {
ipv4Body = &iaasalpha.CreateNetworkIPv4{
CreateNetworkIPv4WithPrefixLength: &iaasalpha.CreateNetworkIPv4WithPrefixLength{
Nameservers: &modelIPv4Nameservers,
PrefixLength: conversion.Int64ValueToPointer(model.IPv4PrefixLength),
},
}
} else if !utils.IsUndefined(model.IPv4Prefix) {
var gateway *iaasalpha.NullableString
if model.NoIPv4Gateway.ValueBool() {
gateway = iaasalpha.NewNullableString(nil)
} else if !(model.IPv4Gateway.IsUnknown() || model.IPv4Gateway.IsNull()) {
gateway = iaasalpha.NewNullableString(conversion.StringValueToPointer(model.IPv4Gateway))
}
ipv4Body = &iaasalpha.CreateNetworkIPv4{
CreateNetworkIPv4WithPrefix: &iaasalpha.CreateNetworkIPv4WithPrefix{
Nameservers: &modelIPv4Nameservers,
Prefix: conversion.StringValueToPointer(model.IPv4Prefix),
Gateway: gateway,
},
}
}
labels, err := conversion.ToStringInterfaceMap(ctx, model.Labels)
if err != nil {
return nil, fmt.Errorf("converting to Go map: %w", err)
}
payload := iaasalpha.CreateNetworkPayload{
Name: conversion.StringValueToPointer(model.Name),
Labels: &labels,
Routed: conversion.BoolValueToPointer(model.Routed),
Ipv4: ipv4Body,
Ipv6: ipv6Body,
RoutingTableId: conversion.StringValueToPointer(model.RoutingTableID),
}
return &payload, nil
}
func toUpdatePayload(ctx context.Context, model, stateModel *networkModel.Model) (*iaasalpha.PartialUpdateNetworkPayload, error) {
if model == nil {
return nil, fmt.Errorf("nil model")
}
modelIPv6Nameservers := []string{}
for _, ipv6ns := range model.IPv6Nameservers.Elements() {
ipv6NameserverString, ok := ipv6ns.(types.String)
if !ok {
return nil, fmt.Errorf("type assertion failed")
}
modelIPv6Nameservers = append(modelIPv6Nameservers, ipv6NameserverString.ValueString())
}
var ipv6Body *iaasalpha.UpdateNetworkIPv6Body
if !(model.IPv6Nameservers.IsNull() || model.IPv6Nameservers.IsUnknown()) {
ipv6Body = &iaasalpha.UpdateNetworkIPv6Body{
Nameservers: &modelIPv6Nameservers,
}
if model.NoIPv6Gateway.ValueBool() {
ipv6Body.Gateway = iaasalpha.NewNullableString(nil)
} else if !(model.IPv6Gateway.IsUnknown() || model.IPv6Gateway.IsNull()) {
ipv6Body.Gateway = iaasalpha.NewNullableString(conversion.StringValueToPointer(model.IPv6Gateway))
}
}
modelIPv4Nameservers := []string{}
var modelIPv4List []attr.Value
if !(model.IPv4Nameservers.IsNull() || model.IPv4Nameservers.IsUnknown()) {
modelIPv4List = model.IPv4Nameservers.Elements()
} else {
modelIPv4List = model.Nameservers.Elements()
}
for _, ipv4ns := range modelIPv4List {
ipv4NameserverString, ok := ipv4ns.(types.String)
if !ok {
return nil, fmt.Errorf("type assertion failed")
}
modelIPv4Nameservers = append(modelIPv4Nameservers, ipv4NameserverString.ValueString())
}
var ipv4Body *iaasalpha.UpdateNetworkIPv4Body
if !model.IPv4Nameservers.IsNull() || !model.Nameservers.IsNull() {
ipv4Body = &iaasalpha.UpdateNetworkIPv4Body{
Nameservers: &modelIPv4Nameservers,
}
if model.NoIPv4Gateway.ValueBool() {
ipv4Body.Gateway = iaasalpha.NewNullableString(nil)
} else if !(model.IPv4Gateway.IsUnknown() || model.IPv4Gateway.IsNull()) {
ipv4Body.Gateway = iaasalpha.NewNullableString(conversion.StringValueToPointer(model.IPv4Gateway))
}
}
currentLabels := stateModel.Labels
labels, err := conversion.ToJSONMapPartialUpdatePayload(ctx, currentLabels, model.Labels)
if err != nil {
return nil, fmt.Errorf("converting to Go map: %w", err)
}
payload := iaasalpha.PartialUpdateNetworkPayload{
Name: conversion.StringValueToPointer(model.Name),
Labels: &labels,
Ipv4: ipv4Body,
Ipv6: ipv6Body,
RoutingTableId: conversion.StringValueToPointer(model.RoutingTableID),
}
return &payload, nil
}

View file

@ -0,0 +1,707 @@
package v2network
import (
"context"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/types"
"github.com/stackitcloud/stackit-sdk-go/core/utils"
"github.com/stackitcloud/stackit-sdk-go/services/iaasalpha"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/services/iaas/network/utils/model"
)
func TestMapFields(t *testing.T) {
const testRegion = "region"
tests := []struct {
description string
state model.Model
input *iaasalpha.Network
region string
expected model.Model
isValid bool
}{
{
"id_ok",
model.Model{
ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"),
},
&iaasalpha.Network{
Id: utils.Ptr("nid"),
Ipv4: &iaasalpha.NetworkIPv4{
Gateway: iaasalpha.NewNullableString(nil),
},
},
testRegion,
model.Model{
Id: types.StringValue("pid,region,nid"),
ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"),
Name: types.StringNull(),
Nameservers: types.ListNull(types.StringType),
IPv4Nameservers: types.ListNull(types.StringType),
IPv4PrefixLength: types.Int64Null(),
IPv4Gateway: types.StringNull(),
IPv4Prefix: types.StringNull(),
Prefixes: types.ListNull(types.StringType),
IPv4Prefixes: types.ListNull(types.StringType),
IPv6Nameservers: types.ListNull(types.StringType),
IPv6PrefixLength: types.Int64Null(),
IPv6Gateway: types.StringNull(),
IPv6Prefix: types.StringNull(),
IPv6Prefixes: types.ListNull(types.StringType),
PublicIP: types.StringNull(),
Labels: types.MapNull(types.StringType),
Routed: types.BoolNull(),
Region: types.StringValue(testRegion),
},
true,
},
{
"values_ok",
model.Model{
ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"),
},
&iaasalpha.Network{
Id: utils.Ptr("nid"),
Name: utils.Ptr("name"),
Ipv4: &iaasalpha.NetworkIPv4{
Nameservers: utils.Ptr([]string{"ns1", "ns2"}),
Prefixes: utils.Ptr(
[]string{
"192.168.42.0/24",
"10.100.10.0/16",
},
),
PublicIp: utils.Ptr("publicIp"),
Gateway: iaasalpha.NewNullableString(utils.Ptr("gateway")),
},
Ipv6: &iaasalpha.NetworkIPv6{
Nameservers: utils.Ptr([]string{"ns1", "ns2"}),
Prefixes: utils.Ptr([]string{
"fd12:3456:789a:1::/64",
"fd12:3456:789b:1::/64",
}),
Gateway: iaasalpha.NewNullableString(utils.Ptr("gateway")),
},
Labels: &map[string]interface{}{
"key": "value",
},
Routed: utils.Ptr(true),
},
testRegion,
model.Model{
Id: types.StringValue("pid,region,nid"),
ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"),
Name: types.StringValue("name"),
Nameservers: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("ns1"),
types.StringValue("ns2"),
}),
IPv4Nameservers: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("ns1"),
types.StringValue("ns2"),
}),
IPv4PrefixLength: types.Int64Value(24),
Prefixes: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("192.168.42.0/24"),
types.StringValue("10.100.10.0/16"),
}),
IPv4Prefixes: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("192.168.42.0/24"),
types.StringValue("10.100.10.0/16"),
}),
IPv4Prefix: types.StringValue("192.168.42.0/24"),
IPv6Nameservers: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("ns1"),
types.StringValue("ns2"),
}),
IPv6PrefixLength: types.Int64Value(64),
IPv6Prefixes: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("fd12:3456:789a:1::/64"),
types.StringValue("fd12:3456:789b:1::/64"),
}),
IPv6Prefix: types.StringValue("fd12:3456:789a:1::/64"),
PublicIP: types.StringValue("publicIp"),
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
"key": types.StringValue("value"),
}),
Routed: types.BoolValue(true),
IPv4Gateway: types.StringValue("gateway"),
IPv6Gateway: types.StringValue("gateway"),
Region: types.StringValue(testRegion),
},
true,
},
{
"ipv4_nameservers_changed_outside_tf",
model.Model{
ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"),
Nameservers: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("ns1"),
types.StringValue("ns2"),
}),
IPv4Nameservers: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("ns1"),
types.StringValue("ns2"),
}),
},
&iaasalpha.Network{
Id: utils.Ptr("nid"),
Ipv4: &iaasalpha.NetworkIPv4{
Nameservers: utils.Ptr([]string{
"ns2",
"ns3",
}),
},
},
testRegion,
model.Model{
Id: types.StringValue("pid,region,nid"),
ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"),
Name: types.StringNull(),
IPv6Prefixes: types.ListNull(types.StringType),
IPv6Nameservers: types.ListNull(types.StringType),
Prefixes: types.ListNull(types.StringType),
IPv4Prefixes: types.ListNull(types.StringType),
Nameservers: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("ns2"),
types.StringValue("ns3"),
}),
IPv4Nameservers: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("ns2"),
types.StringValue("ns3"),
}),
Labels: types.MapNull(types.StringType),
Region: types.StringValue(testRegion),
},
true,
},
{
"ipv6_nameservers_changed_outside_tf",
model.Model{
ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"),
IPv6Nameservers: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("ns1"),
types.StringValue("ns2"),
}),
},
&iaasalpha.Network{
Id: utils.Ptr("nid"),
Ipv6: &iaasalpha.NetworkIPv6{
Nameservers: utils.Ptr([]string{
"ns2",
"ns3",
}),
},
},
testRegion,
model.Model{
Id: types.StringValue("pid,region,nid"),
ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"),
Name: types.StringNull(),
IPv6Prefixes: types.ListNull(types.StringType),
IPv4Nameservers: types.ListNull(types.StringType),
Prefixes: types.ListNull(types.StringType),
IPv4Prefixes: types.ListNull(types.StringType),
Nameservers: types.ListNull(types.StringType),
IPv6Nameservers: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("ns2"),
types.StringValue("ns3"),
}),
Labels: types.MapNull(types.StringType),
Region: types.StringValue(testRegion),
},
true,
},
{
"ipv4_prefixes_changed_outside_tf",
model.Model{
ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"),
Prefixes: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("192.168.42.0/24"),
types.StringValue("10.100.10.0/24"),
}),
},
&iaasalpha.Network{
Id: utils.Ptr("nid"),
Ipv4: &iaasalpha.NetworkIPv4{
Prefixes: utils.Ptr(
[]string{
"192.168.54.0/24",
"192.168.55.0/24",
},
),
},
},
testRegion,
model.Model{
Id: types.StringValue("pid,region,nid"),
ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"),
Name: types.StringNull(),
IPv6Nameservers: types.ListNull(types.StringType),
IPv6PrefixLength: types.Int64Null(),
IPv6Prefixes: types.ListNull(types.StringType),
Labels: types.MapNull(types.StringType),
Nameservers: types.ListNull(types.StringType),
IPv4Nameservers: types.ListNull(types.StringType),
IPv4PrefixLength: types.Int64Value(24),
IPv4Prefix: types.StringValue("192.168.54.0/24"),
Prefixes: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("192.168.54.0/24"),
types.StringValue("192.168.55.0/24"),
}),
IPv4Prefixes: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("192.168.54.0/24"),
types.StringValue("192.168.55.0/24"),
}),
Region: types.StringValue(testRegion),
},
true,
},
{
"ipv6_prefixes_changed_outside_tf",
model.Model{
ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"),
IPv6Prefixes: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("fd12:3456:789a:1::/64"),
types.StringValue("fd12:3456:789a:2::/64"),
}),
},
&iaasalpha.Network{
Id: utils.Ptr("nid"),
Ipv6: &iaasalpha.NetworkIPv6{
Prefixes: utils.Ptr(
[]string{
"fd12:3456:789a:1::/64",
"fd12:3456:789a:2::/64",
},
),
},
},
testRegion,
model.Model{
Id: types.StringValue("pid,region,nid"),
ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"),
Name: types.StringNull(),
IPv4Nameservers: types.ListNull(types.StringType),
IPv4PrefixLength: types.Int64Null(),
Prefixes: types.ListNull(types.StringType),
IPv4Prefixes: types.ListNull(types.StringType),
Labels: types.MapNull(types.StringType),
Nameservers: types.ListNull(types.StringType),
IPv6Nameservers: types.ListNull(types.StringType),
IPv6PrefixLength: types.Int64Value(64),
IPv6Prefixes: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("fd12:3456:789a:1::/64"),
types.StringValue("fd12:3456:789a:2::/64"),
}),
IPv6Prefix: types.StringValue("fd12:3456:789a:1::/64"),
Region: types.StringValue(testRegion),
},
true,
},
{
"ipv4_ipv6_gateway_nil",
model.Model{
ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"),
},
&iaasalpha.Network{
Id: utils.Ptr("nid"),
},
testRegion,
model.Model{
Id: types.StringValue("pid,region,nid"),
ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"),
Name: types.StringNull(),
Nameservers: types.ListNull(types.StringType),
IPv4Nameservers: types.ListNull(types.StringType),
IPv4PrefixLength: types.Int64Null(),
IPv4Gateway: types.StringNull(),
Prefixes: types.ListNull(types.StringType),
IPv4Prefixes: types.ListNull(types.StringType),
IPv6Nameservers: types.ListNull(types.StringType),
IPv6PrefixLength: types.Int64Null(),
IPv6Gateway: types.StringNull(),
IPv6Prefixes: types.ListNull(types.StringType),
PublicIP: types.StringNull(),
Labels: types.MapNull(types.StringType),
Routed: types.BoolNull(),
Region: types.StringValue(testRegion),
},
true,
},
{
"response_nil_fail",
model.Model{},
nil,
testRegion,
model.Model{},
false,
},
{
"no_resource_id",
model.Model{
ProjectId: types.StringValue("pid"),
},
&iaasalpha.Network{},
testRegion,
model.Model{},
false,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
err := mapFields(context.Background(), tt.input, &tt.state, tt.region)
if !tt.isValid && err == nil {
t.Fatalf("Should have failed")
}
if tt.isValid && err != nil {
t.Fatalf("Should not have failed: %v", err)
}
if tt.isValid {
diff := cmp.Diff(tt.state, tt.expected)
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
}
})
}
}
func TestToCreatePayload(t *testing.T) {
tests := []struct {
description string
input *model.Model
expected *iaasalpha.CreateNetworkPayload
isValid bool
}{
{
"default_ok",
&model.Model{
Name: types.StringValue("name"),
IPv4Nameservers: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("ns1"),
types.StringValue("ns2"),
}),
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
"key": types.StringValue("value"),
}),
Routed: types.BoolValue(false),
IPv4Gateway: types.StringValue("gateway"),
IPv4Prefix: types.StringValue("prefix"),
},
&iaasalpha.CreateNetworkPayload{
Name: utils.Ptr("name"),
Ipv4: &iaasalpha.CreateNetworkIPv4{
CreateNetworkIPv4WithPrefix: &iaasalpha.CreateNetworkIPv4WithPrefix{
Gateway: iaasalpha.NewNullableString(utils.Ptr("gateway")),
Nameservers: utils.Ptr([]string{
"ns1",
"ns2",
}),
Prefix: utils.Ptr("prefix"),
},
},
Labels: &map[string]interface{}{
"key": "value",
},
Routed: utils.Ptr(false),
},
true,
},
{
"ipv4_nameservers_okay",
&model.Model{
Name: types.StringValue("name"),
Nameservers: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("ns1"),
types.StringValue("ns2"),
}),
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
"key": types.StringValue("value"),
}),
Routed: types.BoolValue(false),
IPv4Gateway: types.StringValue("gateway"),
IPv4Prefix: types.StringValue("prefix"),
},
&iaasalpha.CreateNetworkPayload{
Name: utils.Ptr("name"),
Ipv4: &iaasalpha.CreateNetworkIPv4{
CreateNetworkIPv4WithPrefix: &iaasalpha.CreateNetworkIPv4WithPrefix{
Gateway: iaasalpha.NewNullableString(utils.Ptr("gateway")),
Nameservers: utils.Ptr([]string{
"ns1",
"ns2",
}),
Prefix: utils.Ptr("prefix"),
},
},
Labels: &map[string]interface{}{
"key": "value",
},
Routed: utils.Ptr(false),
},
true,
},
{
"ipv6_default_ok",
&model.Model{
Name: types.StringValue("name"),
IPv6Nameservers: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("ns1"),
types.StringValue("ns2"),
}),
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
"key": types.StringValue("value"),
}),
Routed: types.BoolValue(false),
IPv6Gateway: types.StringValue("gateway"),
IPv6Prefix: types.StringValue("prefix"),
},
&iaasalpha.CreateNetworkPayload{
Name: utils.Ptr("name"),
Ipv6: &iaasalpha.CreateNetworkIPv6{
CreateNetworkIPv6WithPrefix: &iaasalpha.CreateNetworkIPv6WithPrefix{
Gateway: iaasalpha.NewNullableString(utils.Ptr("gateway")),
Nameservers: utils.Ptr([]string{
"ns1",
"ns2",
}),
Prefix: utils.Ptr("prefix"),
},
},
Labels: &map[string]interface{}{
"key": "value",
},
Routed: utils.Ptr(false),
},
true,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
output, err := toCreatePayload(context.Background(), tt.input)
if !tt.isValid && err == nil {
t.Fatalf("Should have failed")
}
if tt.isValid && err != nil {
t.Fatalf("Should not have failed: %v", err)
}
if tt.isValid {
diff := cmp.Diff(output, tt.expected, cmp.AllowUnexported(iaasalpha.NullableString{}))
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
}
})
}
}
func TestToUpdatePayload(t *testing.T) {
tests := []struct {
description string
input *model.Model
state model.Model
expected *iaasalpha.PartialUpdateNetworkPayload
isValid bool
}{
{
"default_ok",
&model.Model{
Name: types.StringValue("name"),
IPv4Nameservers: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("ns1"),
types.StringValue("ns2"),
}),
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
"key": types.StringValue("value"),
}),
Routed: types.BoolValue(true),
IPv4Gateway: types.StringValue("gateway"),
},
model.Model{
ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"),
Labels: types.MapNull(types.StringType),
},
&iaasalpha.PartialUpdateNetworkPayload{
Name: utils.Ptr("name"),
Ipv4: &iaasalpha.UpdateNetworkIPv4Body{
Gateway: iaasalpha.NewNullableString(utils.Ptr("gateway")),
Nameservers: utils.Ptr([]string{
"ns1",
"ns2",
}),
},
Labels: &map[string]interface{}{
"key": "value",
},
},
true,
},
{
"ipv4_nameservers_okay",
&model.Model{
Name: types.StringValue("name"),
Nameservers: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("ns1"),
types.StringValue("ns2"),
}),
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
"key": types.StringValue("value"),
}),
Routed: types.BoolValue(true),
IPv4Gateway: types.StringValue("gateway"),
},
model.Model{
ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"),
Labels: types.MapNull(types.StringType),
},
&iaasalpha.PartialUpdateNetworkPayload{
Name: utils.Ptr("name"),
Ipv4: &iaasalpha.UpdateNetworkIPv4Body{
Gateway: iaasalpha.NewNullableString(utils.Ptr("gateway")),
Nameservers: utils.Ptr([]string{
"ns1",
"ns2",
}),
},
Labels: &map[string]interface{}{
"key": "value",
},
},
true,
},
{
"ipv4_gateway_nil",
&model.Model{
Name: types.StringValue("name"),
IPv4Nameservers: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("ns1"),
types.StringValue("ns2"),
}),
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
"key": types.StringValue("value"),
}),
Routed: types.BoolValue(true),
},
model.Model{
ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"),
Labels: types.MapNull(types.StringType),
},
&iaasalpha.PartialUpdateNetworkPayload{
Name: utils.Ptr("name"),
Ipv4: &iaasalpha.UpdateNetworkIPv4Body{
Nameservers: utils.Ptr([]string{
"ns1",
"ns2",
}),
},
Labels: &map[string]interface{}{
"key": "value",
},
},
true,
},
{
"ipv6_default_ok",
&model.Model{
Name: types.StringValue("name"),
IPv6Nameservers: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("ns1"),
types.StringValue("ns2"),
}),
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
"key": types.StringValue("value"),
}),
Routed: types.BoolValue(true),
IPv6Gateway: types.StringValue("gateway"),
},
model.Model{
ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"),
Labels: types.MapNull(types.StringType),
},
&iaasalpha.PartialUpdateNetworkPayload{
Name: utils.Ptr("name"),
Ipv6: &iaasalpha.UpdateNetworkIPv6Body{
Gateway: iaasalpha.NewNullableString(utils.Ptr("gateway")),
Nameservers: utils.Ptr([]string{
"ns1",
"ns2",
}),
},
Labels: &map[string]interface{}{
"key": "value",
},
},
true,
},
{
"ipv6_gateway_nil",
&model.Model{
Name: types.StringValue("name"),
IPv6Nameservers: types.ListValueMust(types.StringType, []attr.Value{
types.StringValue("ns1"),
types.StringValue("ns2"),
}),
Labels: types.MapValueMust(types.StringType, map[string]attr.Value{
"key": types.StringValue("value"),
}),
Routed: types.BoolValue(true),
},
model.Model{
ProjectId: types.StringValue("pid"),
NetworkId: types.StringValue("nid"),
Labels: types.MapNull(types.StringType),
},
&iaasalpha.PartialUpdateNetworkPayload{
Name: utils.Ptr("name"),
Ipv6: &iaasalpha.UpdateNetworkIPv6Body{
Nameservers: utils.Ptr([]string{
"ns1",
"ns2",
}),
},
Labels: &map[string]interface{}{
"key": "value",
},
},
true,
},
}
for _, tt := range tests {
t.Run(tt.description, func(t *testing.T) {
output, err := toUpdatePayload(context.Background(), tt.input, &tt.state)
if !tt.isValid && err == nil {
t.Fatalf("Should have failed")
}
if tt.isValid && err != nil {
t.Fatalf("Should not have failed: %v", err)
}
if tt.isValid {
diff := cmp.Diff(output, tt.expected, cmp.AllowUnexported(iaasalpha.NullableString{}))
if diff != "" {
t.Fatalf("Data does not match: %s", diff)
}
}
})
}
}

View file

@ -1,19 +1,32 @@
variable "project_id" {} variable "project_id" {}
variable "name" {} variable "name" {}
variable "ipv4_gateway" {} variable "ipv4_gateway" {}
variable "ipv4_nameservers" {} variable "ipv4_nameserver_0" {}
variable "ipv4_nameserver_1" {}
variable "ipv4_prefix" {} variable "ipv4_prefix" {}
variable "ipv4_prefix_length" {} variable "ipv4_prefix_length" {}
variable "routed" {} variable "routed" {}
variable "label" {} variable "label" {}
resource "stackit_network" "network" { resource "stackit_network" "network_prefix" {
project_id = var.project_id project_id = var.project_id
name = var.name name = var.name
ipv4_gateway = var.ipv4_gateway != "" ? var.ipv4_gateway : null ipv4_gateway = var.ipv4_gateway != "" ? var.ipv4_gateway : null
no_ipv4_gateway = var.ipv4_gateway != "" ? null : true no_ipv4_gateway = var.ipv4_gateway != "" ? null : true
ipv4_nameservers = [var.ipv4_nameservers] ipv4_nameservers = [var.ipv4_nameserver_0, var.ipv4_nameserver_1]
ipv4_prefix = var.ipv4_prefix ipv4_prefix = var.ipv4_prefix
routed = var.routed
labels = {
"acc-test" : var.label
}
}
resource "stackit_network" "network_prefix_length" {
project_id = var.project_id
name = var.name
no_ipv4_gateway = true
ipv4_nameservers = [var.ipv4_nameserver_0, var.ipv4_nameserver_1]
ipv4_prefix_length = var.ipv4_prefix_length ipv4_prefix_length = var.ipv4_prefix_length
routed = var.routed routed = var.routed
labels = { labels = {

View file

@ -0,0 +1,43 @@
variable "project_id" {}
variable "name" {}
variable "ipv4_gateway" {}
variable "ipv4_nameserver_0" {}
variable "ipv4_nameserver_1" {}
variable "ipv4_prefix" {}
variable "ipv4_prefix_length" {}
variable "routed" {}
variable "label" {}
variable "organization_id" {}
variable "network_area_id" {}
# resource "stackit_network" "network_prefix" {
# project_id = var.project_id
# name = var.name
# # ipv4_gateway = var.ipv4_gateway != "" ? var.ipv4_gateway : null
# # no_ipv4_gateway = var.ipv4_gateway != "" ? null : true
# ipv4_nameservers = [var.ipv4_nameserver_0, var.ipv4_nameserver_1]
# ipv4_prefix = var.ipv4_prefix
# routed = var.routed
# labels = {
# "acc-test" : var.label
# }
# }
resource "stackit_network" "network_prefix_length" {
project_id = var.project_id
name = var.name
# no_ipv4_gateway = true
ipv4_nameservers = [var.ipv4_nameserver_0, var.ipv4_nameserver_1]
ipv4_prefix_length = var.ipv4_prefix_length
routed = var.routed
labels = {
"acc-test" : var.label
}
routing_table_id = stackit_routing_table.routing_table.routing_table_id
}
resource "stackit_routing_table" "routing_table" {
organization_id = var.organization_id
network_area_id = var.network_area_id
name = var.name
}

View file

@ -0,0 +1,7 @@
variable "project_id" {}
variable "name" {}
resource "stackit_network" "network" {
project_id = var.project_id
name = var.name
}

View file

@ -5,6 +5,7 @@ import (
_ "embed" _ "embed"
"errors" "errors"
"fmt" "fmt"
"maps"
"net/http" "net/http"
"strings" "strings"
"sync" "sync"
@ -19,8 +20,6 @@ import (
"github.com/stackitcloud/stackit-sdk-go/services/iaasalpha" "github.com/stackitcloud/stackit-sdk-go/services/iaasalpha"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/core"
"maps"
"github.com/stackitcloud/terraform-provider-stackit/stackit/internal/testutil" "github.com/stackitcloud/terraform-provider-stackit/stackit/internal/testutil"
) )
@ -123,7 +122,7 @@ func TestAccRoutingTable(t *testing.T) {
// Creation // Creation
{ {
ConfigVariables: testConfigRoutingTableMin, ConfigVariables: testConfigRoutingTableMin,
Config: fmt.Sprintf("%s\n%s", testutil.IaaSProviderConfig(), resourceRoutingTableMinConfig), Config: fmt.Sprintf("%s\n%s", testutil.IaaSProviderConfigWithExperiments(), resourceRoutingTableMinConfig),
Check: resource.ComposeAggregateTestCheckFunc( Check: resource.ComposeAggregateTestCheckFunc(
// Routing table // Routing table
resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "organization_id", testutil.ConvertConfigVariable(testConfigRoutingTableMin["organization_id"])), resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "organization_id", testutil.ConvertConfigVariable(testConfigRoutingTableMin["organization_id"])),
@ -158,7 +157,7 @@ func TestAccRoutingTable(t *testing.T) {
network_area_id = stackit_routing_table.routing_table.network_area_id network_area_id = stackit_routing_table.routing_table.network_area_id
} }
`, `,
testutil.IaaSProviderConfig(), resourceRoutingTableMinConfig, testutil.IaaSProviderConfigWithExperiments(), resourceRoutingTableMinConfig,
), ),
Check: resource.ComposeAggregateTestCheckFunc( Check: resource.ComposeAggregateTestCheckFunc(
// Routing table // Routing table
@ -232,7 +231,7 @@ func TestAccRoutingTable(t *testing.T) {
// Update // Update
{ {
ConfigVariables: testConfigRoutingTableMinUpdated, ConfigVariables: testConfigRoutingTableMinUpdated,
Config: fmt.Sprintf("%s\n%s", testutil.IaaSProviderConfig(), resourceRoutingTableMinConfig), Config: fmt.Sprintf("%s\n%s", testutil.IaaSProviderConfigWithExperiments(), resourceRoutingTableMinConfig),
Check: resource.ComposeAggregateTestCheckFunc( Check: resource.ComposeAggregateTestCheckFunc(
// Routing table // Routing table
resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "organization_id", testutil.ConvertConfigVariable(testConfigRoutingTableMinUpdated["organization_id"])), resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "organization_id", testutil.ConvertConfigVariable(testConfigRoutingTableMinUpdated["organization_id"])),
@ -261,7 +260,7 @@ func TestAccRoutingTable(t *testing.T) {
// Creation // Creation
{ {
ConfigVariables: testConfigRoutingTableMax, ConfigVariables: testConfigRoutingTableMax,
Config: fmt.Sprintf("%s\n%s", testutil.IaaSProviderConfig(), resourceRoutingTableMaxConfig), Config: fmt.Sprintf("%s\n%s", testutil.IaaSProviderConfigWithExperiments(), resourceRoutingTableMaxConfig),
Check: resource.ComposeAggregateTestCheckFunc( Check: resource.ComposeAggregateTestCheckFunc(
// Routing table // Routing table
resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "organization_id", testutil.ConvertConfigVariable(testConfigRoutingTableMax["organization_id"])), resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "organization_id", testutil.ConvertConfigVariable(testConfigRoutingTableMax["organization_id"])),
@ -297,7 +296,7 @@ func TestAccRoutingTable(t *testing.T) {
network_area_id = stackit_routing_table.routing_table.network_area_id network_area_id = stackit_routing_table.routing_table.network_area_id
} }
`, `,
testutil.IaaSProviderConfig(), resourceRoutingTableMaxConfig, testutil.IaaSProviderConfigWithExperiments(), resourceRoutingTableMaxConfig,
), ),
Check: resource.ComposeAggregateTestCheckFunc( Check: resource.ComposeAggregateTestCheckFunc(
// Routing table // Routing table
@ -373,7 +372,7 @@ func TestAccRoutingTable(t *testing.T) {
// Update // Update
{ {
ConfigVariables: testConfigRoutingTableMaxUpdated, ConfigVariables: testConfigRoutingTableMaxUpdated,
Config: fmt.Sprintf("%s\n%s", testutil.IaaSProviderConfig(), resourceRoutingTableMaxConfig), Config: fmt.Sprintf("%s\n%s", testutil.IaaSProviderConfigWithExperiments(), resourceRoutingTableMaxConfig),
Check: resource.ComposeAggregateTestCheckFunc( Check: resource.ComposeAggregateTestCheckFunc(
// Routing table // Routing table
resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "organization_id", testutil.ConvertConfigVariable(testConfigRoutingTableMaxUpdated["organization_id"])), resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "organization_id", testutil.ConvertConfigVariable(testConfigRoutingTableMaxUpdated["organization_id"])),
@ -403,7 +402,7 @@ func TestAccRoutingTable(t *testing.T) {
// Creation // Creation
{ {
ConfigVariables: testConfigRoutingTableRouteMin, ConfigVariables: testConfigRoutingTableRouteMin,
Config: fmt.Sprintf("%s\n%s", testutil.IaaSProviderConfig(), resourceRoutingTableRouteMinConfig), Config: fmt.Sprintf("%s\n%s", testutil.IaaSProviderConfigWithExperiments(), resourceRoutingTableRouteMinConfig),
Check: resource.ComposeAggregateTestCheckFunc( Check: resource.ComposeAggregateTestCheckFunc(
// Routing table // Routing table
resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "organization_id", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMin["organization_id"])), resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "organization_id", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMin["organization_id"])),
@ -451,7 +450,7 @@ func TestAccRoutingTable(t *testing.T) {
routing_table_id = stackit_routing_table_route.route.routing_table_id routing_table_id = stackit_routing_table_route.route.routing_table_id
} }
`, `,
testutil.IaaSProviderConfig(), resourceRoutingTableRouteMinConfig, testutil.IaaSProviderConfigWithExperiments(), resourceRoutingTableRouteMinConfig,
), ),
Check: resource.ComposeAggregateTestCheckFunc( Check: resource.ComposeAggregateTestCheckFunc(
// Routing table route // Routing table route
@ -529,7 +528,7 @@ func TestAccRoutingTable(t *testing.T) {
// Update // Update
{ {
ConfigVariables: testConfigRoutingTableRouteMinUpdated, ConfigVariables: testConfigRoutingTableRouteMinUpdated,
Config: fmt.Sprintf("%s\n%s", testutil.IaaSProviderConfig(), resourceRoutingTableRouteMinConfig), Config: fmt.Sprintf("%s\n%s", testutil.IaaSProviderConfigWithExperiments(), resourceRoutingTableRouteMinConfig),
Check: resource.ComposeAggregateTestCheckFunc( Check: resource.ComposeAggregateTestCheckFunc(
// Routing table // Routing table
resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "organization_id", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMinUpdated["organization_id"])), resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "organization_id", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMinUpdated["organization_id"])),
@ -569,7 +568,7 @@ func TestAccRoutingTable(t *testing.T) {
// Creation // Creation
{ {
ConfigVariables: testConfigRoutingTableRouteMax, ConfigVariables: testConfigRoutingTableRouteMax,
Config: fmt.Sprintf("%s\n%s", testutil.IaaSProviderConfig(), resourceRoutingTableRouteMaxConfig), Config: fmt.Sprintf("%s\n%s", testutil.IaaSProviderConfigWithExperiments(), resourceRoutingTableRouteMaxConfig),
Check: resource.ComposeAggregateTestCheckFunc( Check: resource.ComposeAggregateTestCheckFunc(
// Routing table // Routing table
resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "organization_id", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMax["organization_id"])), resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "organization_id", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMax["organization_id"])),
@ -618,7 +617,7 @@ func TestAccRoutingTable(t *testing.T) {
routing_table_id = stackit_routing_table_route.route.routing_table_id routing_table_id = stackit_routing_table_route.route.routing_table_id
} }
`, `,
testutil.IaaSProviderConfig(), resourceRoutingTableRouteMaxConfig, testutil.IaaSProviderConfigWithExperiments(), resourceRoutingTableRouteMaxConfig,
), ),
Check: resource.ComposeAggregateTestCheckFunc( Check: resource.ComposeAggregateTestCheckFunc(
// Routing table route // Routing table route
@ -698,7 +697,7 @@ func TestAccRoutingTable(t *testing.T) {
// Update // Update
{ {
ConfigVariables: testConfigRoutingTableRouteMaxUpdated, ConfigVariables: testConfigRoutingTableRouteMaxUpdated,
Config: fmt.Sprintf("%s\n%s", testutil.IaaSProviderConfig(), resourceRoutingTableRouteMaxConfig), Config: fmt.Sprintf("%s\n%s", testutil.IaaSProviderConfigWithExperiments(), resourceRoutingTableRouteMaxConfig),
Check: resource.ComposeAggregateTestCheckFunc( Check: resource.ComposeAggregateTestCheckFunc(
// Routing table // Routing table
resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "organization_id", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMaxUpdated["organization_id"])), resource.TestCheckResourceAttr("stackit_routing_table.routing_table", "organization_id", testutil.ConvertConfigVariable(testConfigRoutingTableRouteMaxUpdated["organization_id"])),

View file

@ -122,13 +122,28 @@ func IaaSProviderConfig() string {
return ` return `
provider "stackit" { provider "stackit" {
default_region = "eu01" default_region = "eu01"
experiments = ["routing-tables"]
}` }`
} }
return fmt.Sprintf(` return fmt.Sprintf(`
provider "stackit" { provider "stackit" {
iaas_custom_endpoint = "%s" iaas_custom_endpoint = "%s"
experiments = ["routing-tables"] }`,
IaaSCustomEndpoint,
)
}
func IaaSProviderConfigWithExperiments() string {
if IaaSCustomEndpoint == "" {
return `
provider "stackit" {
default_region = "eu01"
experiments = [ "routing-tables", "network" ]
}`
}
return fmt.Sprintf(`
provider "stackit" {
iaas_custom_endpoint = "%s"
experiments = [ "routing-tables", "network" ]
}`, }`,
IaaSCustomEndpoint, IaaSCustomEndpoint,
) )

View file

@ -3,6 +3,7 @@ package stackit
import ( import (
"context" "context"
"fmt" "fmt"
"strings"
"github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator"
"github.com/hashicorp/terraform-plugin-framework/datasource" "github.com/hashicorp/terraform-plugin-framework/datasource"
@ -186,7 +187,7 @@ func (p *Provider) Schema(_ context.Context, _ provider.SchemaRequest, resp *pro
"service_enablement_custom_endpoint": "Custom endpoint for the Service Enablement API", "service_enablement_custom_endpoint": "Custom endpoint for the Service Enablement API",
"token_custom_endpoint": "Custom endpoint for the token API, which is used to request access tokens when using the key flow", "token_custom_endpoint": "Custom endpoint for the token API, which is used to request access tokens when using the key flow",
"enable_beta_resources": "Enable beta resources. Default is false.", "enable_beta_resources": "Enable beta resources. Default is false.",
"experiments": fmt.Sprintf("Enables experiments. These are unstable features without official support. More information can be found in the README. Available Experiments: %v", features.AvailableExperiments), "experiments": fmt.Sprintf("Enables experiments. These are unstable features without official support. More information can be found in the README. Available Experiments: %v", strings.Join(features.AvailableExperiments, ", ")),
} }
resp.Schema = schema.Schema{ resp.Schema = schema.Schema{