diff --git a/docs/data-sources/network.md b/docs/data-sources/network.md index 16c3665d..6e1bfaf0 100644 --- a/docs/data-sources/network.md +++ b/docs/data-sources/network.md @@ -30,9 +30,19 @@ data "stackit_network" "example" { ### Read-Only - `id` (String) Terraform's internal resource ID. It is structured as "`project_id`,`network_id`". +- `ipv4_gateway` (String) The IPv4 gateway of a network. If not specified, the first IP of the network will be assigned as the gateway. +- `ipv4_nameservers` (List of String) The IPv4 nameservers of the network. +- `ipv4_prefix` (String) The IPv4 prefix of the network (CIDR). - `ipv4_prefix_length` (Number) The IPv4 prefix length of the network. +- `ipv4_prefixes` (List of String) The IPv4 prefixes of the network. +- `ipv6_gateway` (String) The IPv6 gateway of a network. If not specified, the first IP of the network will be assigned as the gateway. +- `ipv6_nameservers` (List of String) The IPv6 nameservers of the network. +- `ipv6_prefix` (String) The IPv6 prefix of the network (CIDR). +- `ipv6_prefix_length` (Number) The IPv6 prefix length of the network. +- `ipv6_prefixes` (List of String) The IPv6 prefixes of the network. - `labels` (Map of String) Labels are key-value string pairs which can be attached to a resource container - `name` (String) The name of the network. -- `nameservers` (List of String) The nameservers of the network. -- `prefixes` (List of String) The prefixes of the 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. +- `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. +- `routed` (Boolean) Shows if the network is routed and therefore accessible from other networks. diff --git a/docs/resources/network.md b/docs/resources/network.md index 7806f22e..9f5083ca 100644 --- a/docs/resources/network.md +++ b/docs/resources/network.md @@ -13,14 +13,31 @@ Network resource schema. Must have a `region` specified in the provider configur ## Example Usage ```terraform -resource "stackit_network" "example" { - project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" - name = "example-network" - nameservers = ["1.2.3.4", "5.6.7.8"] - ipv4_prefix_length = 24 +resource "stackit_network" "example_with_name" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + name = "example-with-name" +} + +resource "stackit_network" "example_routed_network" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + name = "example-routed-network" labels = { "key" = "value" } + routed = true +} + +resource "stackit_network" "example_non_routed_network" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + name = "example-non-routed-network" + ipv4_nameservers = ["1.2.3.4", "5.6.7.8"] + ipv4_prefix_length = 24 + ipv4_gateway = "10.1.2.3" + ipv4_prefix = "10.1.2.0/24" + labels = { + "key" = "value" + } + routed = false } ``` @@ -34,13 +51,25 @@ resource "stackit_network" "example" { ### Optional +- `ipv4_gateway` (String) The IPv4 gateway of a network. If not specified, the first IP of the network will be assigned as the gateway. +- `ipv4_nameservers` (List of String) The IPv4 nameservers of the network. +- `ipv4_prefix` (String) The IPv4 prefix of the network (CIDR). - `ipv4_prefix_length` (Number) The IPv4 prefix length of the network. +- `ipv6_gateway` (String) The IPv6 gateway of a network. If not specified, the first IP of the network will be assigned as the gateway. +- `ipv6_nameservers` (List of String) The IPv6 nameservers of the network. +- `ipv6_prefix` (String) The IPv6 prefix of the network (CIDR). +- `ipv6_prefix_length` (Number) The IPv6 prefix length of the network. - `labels` (Map of String) Labels are key-value string pairs which can be attached to a resource container -- `nameservers` (List of String) The nameservers of the 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. +- `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. +- `routed` (Boolean) If set to `true`, the network is routed and therefore accessible from other networks. ### Read-Only - `id` (String) Terraform's internal resource ID. It is structured as "`project_id`,`network_id`". +- `ipv4_prefixes` (List of String) The IPv4 prefixes of the network. +- `ipv6_prefixes` (List of String) The IPv6 prefixes of the network. - `network_id` (String) The network ID. -- `prefixes` (List of String) The prefixes of the network. +- `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. diff --git a/examples/resources/stackit_network/resource.tf b/examples/resources/stackit_network/resource.tf index 213e180c..6782b88a 100644 --- a/examples/resources/stackit_network/resource.tf +++ b/examples/resources/stackit_network/resource.tf @@ -1,9 +1,26 @@ -resource "stackit_network" "example" { - project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" - name = "example-network" - nameservers = ["1.2.3.4", "5.6.7.8"] - ipv4_prefix_length = 24 +resource "stackit_network" "example_with_name" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + name = "example-with-name" +} + +resource "stackit_network" "example_routed_network" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + name = "example-routed-network" labels = { "key" = "value" } + routed = true } + +resource "stackit_network" "example_non_routed_network" { + project_id = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" + name = "example-non-routed-network" + ipv4_nameservers = ["1.2.3.4", "5.6.7.8"] + ipv4_prefix_length = 24 + ipv4_gateway = "10.1.2.3" + ipv4_prefix = "10.1.2.0/24" + labels = { + "key" = "value" + } + routed = false +} \ No newline at end of file diff --git a/stackit/internal/services/iaas/iaas_acc_test.go b/stackit/internal/services/iaas/iaas_acc_test.go index dd50aa05..bef29e09 100644 --- a/stackit/internal/services/iaas/iaas_acc_test.go +++ b/stackit/internal/services/iaas/iaas_acc_test.go @@ -29,6 +29,9 @@ var networkResource = map[string]string{ "ipv4_prefix_length": "24", "nameserver0": "1.2.3.4", "nameserver1": "5.6.7.8", + "ipv4_gateway": "10.1.2.1", + "ipv4_prefix": "10.1.2.1/24", + "routed": "false", } var networkAreaResource = map[string]string{ @@ -113,13 +116,19 @@ func networkResourceConfig(name, nameservers string) string { project_id = "%s" name = "%s" ipv4_prefix_length = "%s" - nameservers = %s + ipv4_nameservers = %s + ipv4_gateway = "%s" + ipv4_prefix = "%s" + routed = "%s" } `, networkResource["project_id"], name, networkResource["ipv4_prefix_length"], nameservers, + networkResource["ipv4_gateway"], + networkResource["ipv4_prefix"], + networkResource["routed"], ) } @@ -616,6 +625,9 @@ func TestAccServer(t *testing.T) { resource.TestCheckResourceAttr("stackit_network.network", "name", networkResource["name"]), resource.TestCheckResourceAttr("stackit_network.network", "nameservers.#", "1"), resource.TestCheckResourceAttr("stackit_network.network", "nameservers.0", networkResource["nameserver0"]), + resource.TestCheckResourceAttr("stackit_network.network", "ipv4_gateway", networkResource["ipv4_gateway"]), + resource.TestCheckResourceAttr("stackit_network.network", "ipv4_prefix", networkResource["ipv4_prefix"]), + resource.TestCheckResourceAttr("stackit_network.network", "routed", networkResource["routed"]), // Server resource.TestCheckResourceAttr("stackit_server.server", "project_id", serverResource["project_id"]), @@ -718,6 +730,9 @@ func TestAccServer(t *testing.T) { ), resource.TestCheckResourceAttr("data.stackit_network.network", "name", networkResource["name"]), resource.TestCheckResourceAttr("data.stackit_network.network", "nameservers.0", networkResource["nameserver0"]), + resource.TestCheckResourceAttr("data.stackit_network.network", "ipv4_gateway", networkResource["ipv4_gateway"]), + resource.TestCheckResourceAttr("data.stackit_network.network", "ipv4_prefix", networkResource["ipv4_prefix"]), + resource.TestCheckResourceAttr("data.stackit_network.network", "routed", networkResource["routed"]), // Server resource.TestCheckResourceAttr("data.stackit_server.server", "project_id", serverResource["project_id"]), @@ -879,6 +894,8 @@ func TestAccServer(t *testing.T) { resource.TestCheckResourceAttr("stackit_network.network", "nameservers.#", "2"), resource.TestCheckResourceAttr("stackit_network.network", "nameservers.0", networkResource["nameserver0"]), resource.TestCheckResourceAttr("stackit_network.network", "nameservers.1", networkResource["nameserver1"]), + resource.TestCheckResourceAttr("stackit_network.network", "ipv4_gateway", networkResource["ipv4_gateway"]), + resource.TestCheckResourceAttr("stackit_network.network", "ipv4_prefix", networkResource["ipv4_prefix"]), // Server resource.TestCheckResourceAttr("stackit_server.server", "project_id", serverResource["project_id"]), diff --git a/stackit/internal/services/iaas/network/datasource.go b/stackit/internal/services/iaas/network/datasource.go index bb35d7b2..3c26d3dc 100644 --- a/stackit/internal/services/iaas/network/datasource.go +++ b/stackit/internal/services/iaas/network/datasource.go @@ -107,16 +107,58 @@ func (d *networkDataSource) Schema(_ context.Context, _ datasource.SchemaRequest }, }, "nameservers": schema.ListAttribute{ - Description: "The nameservers of the network.", + Description: "The nameservers of the network. This field is deprecated and will be removed soon, use `ipv4_nameservers` to configure the nameservers for IPv4.", + DeprecationMessage: "Use `ipv4_nameservers` to configure the nameservers for IPv4.", + Computed: true, + ElementType: types.StringType, + }, + "ipv4_gateway": schema.StringAttribute{ + Description: "The IPv4 gateway of a network. If not specified, the first IP of the network will be assigned as the gateway.", + Computed: true, + }, + "ipv4_nameservers": schema.ListAttribute{ + Description: "The IPv4 nameservers of the network.", Computed: true, ElementType: types.StringType, }, + "ipv4_prefix": schema.StringAttribute{ + Description: "The IPv4 prefix of the network (CIDR).", + Computed: true, + }, "ipv4_prefix_length": schema.Int64Attribute{ Description: "The IPv4 prefix length of the network.", Computed: true, }, "prefixes": schema.ListAttribute{ - Description: "The prefixes of the network.", + 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.", + DeprecationMessage: "Use `ipv4_prefixes` to read the prefixes of the IPv4 networks.", + Computed: true, + ElementType: types.StringType, + }, + "ipv4_prefixes": schema.ListAttribute{ + Description: "The IPv4 prefixes of the network.", + Computed: true, + ElementType: types.StringType, + }, + "ipv6_gateway": schema.StringAttribute{ + Description: "The IPv6 gateway of a network. If not specified, the first IP of the network will be assigned as the gateway.", + Computed: true, + }, + "ipv6_nameservers": schema.ListAttribute{ + Description: "The IPv6 nameservers of the network.", + Computed: true, + ElementType: types.StringType, + }, + "ipv6_prefix": schema.StringAttribute{ + Description: "The IPv6 prefix of the network (CIDR).", + Computed: true, + }, + "ipv6_prefix_length": schema.Int64Attribute{ + Description: "The IPv6 prefix length of the network.", + Computed: true, + }, + "ipv6_prefixes": schema.ListAttribute{ + Description: "The IPv6 prefixes of the network.", Computed: true, ElementType: types.StringType, }, @@ -129,6 +171,10 @@ func (d *networkDataSource) Schema(_ context.Context, _ datasource.SchemaRequest ElementType: types.StringType, Computed: true, }, + "routed": schema.BoolAttribute{ + Description: "Shows if the network is routed and therefore accessible from other networks.", + Computed: true, + }, }, } } diff --git a/stackit/internal/services/iaas/network/resource.go b/stackit/internal/services/iaas/network/resource.go index 0b4cbad0..febf5856 100644 --- a/stackit/internal/services/iaas/network/resource.go +++ b/stackit/internal/services/iaas/network/resource.go @@ -6,11 +6,14 @@ import ( "net/http" "strings" + "github.com/hashicorp/terraform-plugin-framework-validators/resourcevalidator" "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + "github.com/hashicorp/terraform-plugin-framework/attr" "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/path" "github.com/hashicorp/terraform-plugin-framework/resource" "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/listplanmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" @@ -40,10 +43,22 @@ type Model struct { 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. @@ -98,6 +113,32 @@ func (r *networkResource) Configure(ctx context.Context, req resource.ConfigureR tflog.Info(ctx, "IaaS client configured") } +func (r networkResource) ValidateConfig(ctx context.Context, req resource.ValidateConfigRequest, resp *resource.ValidateConfigResponse) { + var model Model + resp.Diagnostics.Append(req.Config.Get(ctx, &model)...) + if resp.Diagnostics.HasError() { + return + } + + if !model.Nameservers.IsUnknown() && !model.IPv4Nameservers.IsUnknown() && !model.Nameservers.IsNull() && !model.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.") + } +} + +// ConfigValidators validates the resource configuration +func (r *networkResource) ConfigValidators(_ context.Context) []resource.ConfigValidator { + return []resource.ConfigValidator{ + resourcevalidator.Conflicting( + path.MatchRoot("no_ipv4_gateway"), + path.MatchRoot("ipv4_gateway"), + ), + resourcevalidator.Conflicting( + path.MatchRoot("no_ipv6_gateway"), + path.MatchRoot("ipv6_gateway"), + ), + } +} + // Schema defines the schema for the resource. func (r *networkResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ @@ -141,32 +182,128 @@ func (r *networkResource) Schema(_ context.Context, _ resource.SchemaRequest, re }, }, "nameservers": schema.ListAttribute{ - Description: "The nameservers of the network.", + Description: "The nameservers of the network. This field is deprecated and will be removed soon, use `ipv4_nameservers` to configure the nameservers for IPv4.", + DeprecationMessage: "Use `ipv4_nameservers` to configure the nameservers for IPv4.", + Optional: true, + Computed: true, + ElementType: types.StringType, + }, + "no_ipv4_gateway": schema.BoolAttribute{ + Description: "If set to `true`, the network doesn't have a gateway.", + Optional: true, + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.UseStateForUnknown(), + }, + }, + "ipv4_gateway": schema.StringAttribute{ + Description: "The IPv4 gateway of a network. If not specified, the first IP of the network will be assigned as the gateway.", Optional: true, Computed: true, - PlanModifiers: []planmodifier.List{ - listplanmodifier.UseStateForUnknown(), + Validators: []validator.String{ + validate.IP(), }, + }, + "ipv4_nameservers": schema.ListAttribute{ + Description: "The IPv4 nameservers of the network.", + Optional: true, + Computed: true, ElementType: types.StringType, }, + "ipv4_prefix": schema.StringAttribute{ + Description: "The IPv4 prefix of the network (CIDR).", + Optional: true, + Validators: []validator.String{ + validate.CIDR(), + }, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, "ipv4_prefix_length": schema.Int64Attribute{ Description: "The IPv4 prefix length of the network.", Optional: true, }, "prefixes": schema.ListAttribute{ - Description: "The prefixes of the network.", + 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.", + DeprecationMessage: "Use `ipv4_prefixes` to read the prefixes of the IPv4 networks.", + Computed: true, + ElementType: types.StringType, + PlanModifiers: []planmodifier.List{ + listplanmodifier.UseStateForUnknown(), + }, + }, + "ipv4_prefixes": schema.ListAttribute{ + Description: "The IPv4 prefixes of the network.", Computed: true, ElementType: types.StringType, + PlanModifiers: []planmodifier.List{ + listplanmodifier.UseStateForUnknown(), + }, + }, + "no_ipv6_gateway": schema.BoolAttribute{ + Description: "If set to `true`, the network doesn't have a gateway.", + Optional: true, + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.UseStateForUnknown(), + }, + }, + "ipv6_gateway": schema.StringAttribute{ + Description: "The IPv6 gateway of a network. If not specified, the first IP of the network will be assigned as the gateway.", + Optional: true, + Computed: true, + Validators: []validator.String{ + validate.IP(), + }, + }, + "ipv6_nameservers": schema.ListAttribute{ + Description: "The IPv6 nameservers of the network.", + Optional: true, + Computed: true, + ElementType: types.StringType, + }, + "ipv6_prefix": schema.StringAttribute{ + Description: "The IPv6 prefix of the network (CIDR).", + Optional: true, + Validators: []validator.String{ + validate.CIDR(), + }, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + "ipv6_prefix_length": schema.Int64Attribute{ + Description: "The IPv6 prefix length of the network.", + Optional: true, + }, + "ipv6_prefixes": schema.ListAttribute{ + Description: "The IPv6 prefixes of the network.", + Computed: true, + ElementType: types.StringType, + PlanModifiers: []planmodifier.List{ + listplanmodifier.UseStateForUnknown(), + }, }, "public_ip": schema.StringAttribute{ Description: "The public IP of the network.", Computed: true, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.UseStateForUnknown(), + }, }, "labels": schema.MapAttribute{ Description: "Labels are key-value string pairs which can be attached to a resource container", ElementType: types.StringType, Optional: true, }, + "routed": schema.BoolAttribute{ + Description: "If set to `true`, the network is routed and therefore accessible from other networks.", + Optional: true, + Computed: true, + PlanModifiers: []planmodifier.Bool{ + boolplanmodifier.UseStateForUnknown(), + boolplanmodifier.RequiresReplace(), + }, + }, }, } } @@ -285,7 +422,7 @@ func (r *networkResource) Update(ctx context.Context, req resource.UpdateRequest } // Generate API request body from model - payload, err := toUpdatePayload(ctx, &model, stateModel.Labels) + 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 @@ -407,27 +544,41 @@ func mapFields(ctx context.Context, networkResp *iaas.Network, model *Model) err labels = types.MapNull(types.StringType) } + // 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) @@ -436,12 +587,59 @@ func mapFields(ctx context.Context, networkResp *iaas.Network, model *Model) err } 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)) + } + + 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 } @@ -450,14 +648,60 @@ func toCreatePayload(ctx context.Context, model *Model) (*iaas.CreateNetworkPayl if model == nil { return nil, fmt.Errorf("nil model") } + addressFamily := &iaas.CreateNetworkAddressFamily{} - modelNameservers := []string{} - for _, ns := range model.Nameservers.Elements() { - nameserverString, ok := ns.(types.String) + modelIPv6Nameservers := []string{} + for _, ipv6ns := range model.IPv6Nameservers.Elements() { + ipv6NameserverString, ok := ipv6ns.(types.String) if !ok { return nil, fmt.Errorf("type assertion failed") } - modelNameservers = append(modelNameservers, nameserverString.ValueString()) + 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) @@ -465,44 +709,88 @@ func toCreatePayload(ctx context.Context, model *Model) (*iaas.CreateNetworkPayl return nil, fmt.Errorf("converting to Go map: %w", err) } - return &iaas.CreateNetworkPayload{ - Name: conversion.StringValueToPointer(model.Name), - AddressFamily: &iaas.CreateNetworkAddressFamily{ - Ipv4: &iaas.CreateNetworkIPv4Body{ - PrefixLength: conversion.Int64ValueToPointer(model.IPv4PrefixLength), - Nameservers: &modelNameservers, - }, - }, + payload := iaas.CreateNetworkPayload{ + Name: conversion.StringValueToPointer(model.Name), Labels: &labels, - }, nil + Routed: conversion.BoolValueToPointer(model.Routed), + } + + if addressFamily.Ipv6 != nil || addressFamily.Ipv4 != nil { + payload.AddressFamily = addressFamily + } + + return &payload, nil } -func toUpdatePayload(ctx context.Context, model *Model, currentLabels types.Map) (*iaas.PartialUpdateNetworkPayload, error) { +func toUpdatePayload(ctx context.Context, model, stateModel *Model) (*iaas.PartialUpdateNetworkPayload, error) { if model == nil { return nil, fmt.Errorf("nil model") } + addressFamily := &iaas.UpdateNetworkAddressFamily{} - modelNameservers := []string{} - for _, ns := range model.Nameservers.Elements() { - nameserverString, ok := ns.(types.String) + modelIPv6Nameservers := []string{} + for _, ipv6ns := range model.IPv6Nameservers.Elements() { + ipv6NameserverString, ok := ipv6ns.(types.String) if !ok { return nil, fmt.Errorf("type assertion failed") } - modelNameservers = append(modelNameservers, nameserverString.ValueString()) + 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) } - return &iaas.PartialUpdateNetworkPayload{ - Name: conversion.StringValueToPointer(model.Name), - AddressFamily: &iaas.UpdateNetworkAddressFamily{ - Ipv4: &iaas.UpdateNetworkIPv4Body{ - Nameservers: &modelNameservers, - }, - }, + payload := iaas.PartialUpdateNetworkPayload{ + Name: conversion.StringValueToPointer(model.Name), Labels: &labels, - }, nil + } + + if addressFamily.Ipv6 != nil || addressFamily.Ipv4 != nil { + payload.AddressFamily = addressFamily + } + + return &payload, nil } diff --git a/stackit/internal/services/iaas/network/resource_test.go b/stackit/internal/services/iaas/network/resource_test.go index 4986b016..b237379a 100644 --- a/stackit/internal/services/iaas/network/resource_test.go +++ b/stackit/internal/services/iaas/network/resource_test.go @@ -27,6 +27,7 @@ func TestMapFields(t *testing.T) { }, &iaas.Network{ NetworkId: utils.Ptr("nid"), + Gateway: iaas.NewNullableString(nil), }, Model{ Id: types.StringValue("pid,nid"), @@ -34,10 +35,20 @@ func TestMapFields(t *testing.T) { 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(), }, true, }, @@ -58,10 +69,21 @@ func TestMapFields(t *testing.T) { "prefix1", "prefix2", }, + NameserversV6: &[]string{ + "ns1", + "ns2", + }, + PrefixesV6: &[]string{ + "prefix1", + "prefix2", + }, PublicIp: utils.Ptr("publicIp"), Labels: &map[string]interface{}{ "key": "value", }, + Routed: utils.Ptr(true), + Gateway: iaas.NewNullableString(utils.Ptr("gateway")), + Gatewayv6: iaas.NewNullableString(utils.Ptr("gateway")), }, Model{ Id: types.StringValue("pid,nid"), @@ -72,20 +94,40 @@ func TestMapFields(t *testing.T) { types.StringValue("ns1"), types.StringValue("ns2"), }), + IPv4Nameservers: types.ListValueMust(types.StringType, []attr.Value{ + types.StringValue("ns1"), + types.StringValue("ns2"), + }), IPv4PrefixLength: types.Int64Null(), Prefixes: types.ListValueMust(types.StringType, []attr.Value{ types.StringValue("prefix1"), types.StringValue("prefix2"), }), + IPv4Prefixes: types.ListValueMust(types.StringType, []attr.Value{ + types.StringValue("prefix1"), + types.StringValue("prefix2"), + }), + IPv6Nameservers: types.ListValueMust(types.StringType, []attr.Value{ + types.StringValue("ns1"), + types.StringValue("ns2"), + }), + IPv6PrefixLength: types.Int64Null(), + IPv6Prefixes: types.ListValueMust(types.StringType, []attr.Value{ + types.StringValue("prefix1"), + types.StringValue("prefix2"), + }), 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"), }, true, }, { - "nameservers_changed_outside_tf", + "ipv4_nameservers_changed_outside_tf", Model{ ProjectId: types.StringValue("pid"), NetworkId: types.StringValue("nid"), @@ -93,6 +135,10 @@ func TestMapFields(t *testing.T) { types.StringValue("ns1"), types.StringValue("ns2"), }), + IPv4Nameservers: types.ListValueMust(types.StringType, []attr.Value{ + types.StringValue("ns1"), + types.StringValue("ns2"), + }), }, &iaas.Network{ NetworkId: utils.Ptr("nid"), @@ -102,21 +148,63 @@ func TestMapFields(t *testing.T) { }, }, Model{ - Id: types.StringValue("pid,nid"), - ProjectId: types.StringValue("pid"), - NetworkId: types.StringValue("nid"), - Name: types.StringNull(), - Prefixes: types.ListNull(types.StringType), + Id: types.StringValue("pid,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), }, true, }, { - "prefixes_changed_outisde_tf", + "ipv6_nameservers_changed_outside_tf", + Model{ + ProjectId: types.StringValue("pid"), + NetworkId: types.StringValue("nid"), + IPv6Nameservers: types.ListValueMust(types.StringType, []attr.Value{ + types.StringValue("ns1"), + types.StringValue("ns2"), + }), + }, + &iaas.Network{ + NetworkId: utils.Ptr("nid"), + NameserversV6: &[]string{ + "ns2", + "ns3", + }, + }, + Model{ + Id: types.StringValue("pid,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), + }, + true, + }, + { + "ipv4_prefixes_changed_outside_tf", Model{ ProjectId: types.StringValue("pid"), NetworkId: types.StringValue("nid"), @@ -137,13 +225,88 @@ func TestMapFields(t *testing.T) { 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.Int64Null(), Prefixes: types.ListValueMust(types.StringType, []attr.Value{ types.StringValue("prefix2"), types.StringValue("prefix3"), }), - Labels: types.MapNull(types.StringType), + IPv4Prefixes: types.ListValueMust(types.StringType, []attr.Value{ + types.StringValue("prefix2"), + types.StringValue("prefix3"), + }), + }, + true, + }, + { + "ipv6_prefixes_changed_outside_tf", + Model{ + ProjectId: types.StringValue("pid"), + NetworkId: types.StringValue("nid"), + IPv6Prefixes: types.ListValueMust(types.StringType, []attr.Value{ + types.StringValue("prefix1"), + types.StringValue("prefix2"), + }), + }, + &iaas.Network{ + NetworkId: utils.Ptr("nid"), + PrefixesV6: &[]string{ + "prefix2", + "prefix3", + }, + }, + Model{ + Id: types.StringValue("pid,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.Int64Null(), + IPv6Prefixes: types.ListValueMust(types.StringType, []attr.Value{ + types.StringValue("prefix2"), + types.StringValue("prefix3"), + }), + }, + true, + }, + { + "ipv4_ipv6_gateway_nil", + Model{ + ProjectId: types.StringValue("pid"), + NetworkId: types.StringValue("nid"), + }, + &iaas.Network{ + NetworkId: utils.Ptr("nid"), + }, + Model{ + Id: types.StringValue("pid,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(), }, true, }, @@ -194,7 +357,7 @@ func TestToCreatePayload(t *testing.T) { "default_ok", &Model{ Name: types.StringValue("name"), - Nameservers: types.ListValueMust(types.StringType, []attr.Value{ + IPv4Nameservers: types.ListValueMust(types.StringType, []attr.Value{ types.StringValue("ns1"), types.StringValue("ns2"), }), @@ -202,6 +365,9 @@ func TestToCreatePayload(t *testing.T) { 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"), }, &iaas.CreateNetworkPayload{ Name: utils.Ptr("name"), @@ -212,11 +378,86 @@ func TestToCreatePayload(t *testing.T) { "ns2", }, PrefixLength: utils.Ptr(int64(24)), + Gateway: iaas.NewNullableString(utils.Ptr("gateway")), + Prefix: utils.Ptr("prefix"), }, }, Labels: &map[string]interface{}{ "key": "value", }, + Routed: utils.Ptr(false), + }, + true, + }, + { + "ipv4_nameservers_okay", + &Model{ + Name: types.StringValue("name"), + Nameservers: types.ListValueMust(types.StringType, []attr.Value{ + types.StringValue("ns1"), + types.StringValue("ns2"), + }), + IPv4PrefixLength: types.Int64Value(24), + 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"), + }, + &iaas.CreateNetworkPayload{ + Name: utils.Ptr("name"), + AddressFamily: &iaas.CreateNetworkAddressFamily{ + Ipv4: &iaas.CreateNetworkIPv4Body{ + Nameservers: &[]string{ + "ns1", + "ns2", + }, + PrefixLength: utils.Ptr(int64(24)), + Gateway: iaas.NewNullableString(utils.Ptr("gateway")), + Prefix: utils.Ptr("prefix"), + }, + }, + Labels: &map[string]interface{}{ + "key": "value", + }, + Routed: utils.Ptr(false), + }, + true, + }, + { + "ipv6_default_ok", + &Model{ + Name: types.StringValue("name"), + IPv6Nameservers: types.ListValueMust(types.StringType, []attr.Value{ + types.StringValue("ns1"), + types.StringValue("ns2"), + }), + IPv6PrefixLength: types.Int64Value(24), + 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"), + }, + &iaas.CreateNetworkPayload{ + Name: utils.Ptr("name"), + AddressFamily: &iaas.CreateNetworkAddressFamily{ + Ipv6: &iaas.CreateNetworkIPv6Body{ + Nameservers: &[]string{ + "ns1", + "ns2", + }, + PrefixLength: utils.Ptr(int64(24)), + Gateway: iaas.NewNullableString(utils.Ptr("gateway")), + Prefix: utils.Ptr("prefix"), + }, + }, + Labels: &map[string]interface{}{ + "key": "value", + }, + Routed: utils.Ptr(false), }, true, }, @@ -231,7 +472,7 @@ func TestToCreatePayload(t *testing.T) { t.Fatalf("Should not have failed: %v", err) } if tt.isValid { - diff := cmp.Diff(output, tt.expected) + diff := cmp.Diff(output, tt.expected, cmp.AllowUnexported(iaas.NullableString{})) if diff != "" { t.Fatalf("Data does not match: %s", diff) } @@ -244,11 +485,48 @@ func TestToUpdatePayload(t *testing.T) { tests := []struct { description string input *Model + state Model expected *iaas.PartialUpdateNetworkPayload isValid bool }{ { "default_ok", + &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{ + ProjectId: types.StringValue("pid"), + NetworkId: types.StringValue("nid"), + Labels: types.MapNull(types.StringType), + }, + &iaas.PartialUpdateNetworkPayload{ + Name: utils.Ptr("name"), + AddressFamily: &iaas.UpdateNetworkAddressFamily{ + Ipv4: &iaas.UpdateNetworkIPv4Body{ + Nameservers: &[]string{ + "ns1", + "ns2", + }, + Gateway: iaas.NewNullableString(utils.Ptr("gateway")), + }, + }, + Labels: &map[string]interface{}{ + "key": "value", + }, + }, + true, + }, + { + "ipv4_nameservers_okay", &Model{ Name: types.StringValue("name"), Nameservers: types.ListValueMust(types.StringType, []attr.Value{ @@ -258,6 +536,48 @@ func TestToUpdatePayload(t *testing.T) { Labels: types.MapValueMust(types.StringType, map[string]attr.Value{ "key": types.StringValue("value"), }), + Routed: types.BoolValue(true), + IPv4Gateway: types.StringValue("gateway"), + }, + Model{ + ProjectId: types.StringValue("pid"), + NetworkId: types.StringValue("nid"), + Labels: types.MapNull(types.StringType), + }, + &iaas.PartialUpdateNetworkPayload{ + Name: utils.Ptr("name"), + AddressFamily: &iaas.UpdateNetworkAddressFamily{ + Ipv4: &iaas.UpdateNetworkIPv4Body{ + Nameservers: &[]string{ + "ns1", + "ns2", + }, + Gateway: iaas.NewNullableString(utils.Ptr("gateway")), + }, + }, + Labels: &map[string]interface{}{ + "key": "value", + }, + }, + true, + }, + { + "ipv4_gateway_nil", + &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{ + ProjectId: types.StringValue("pid"), + NetworkId: types.StringValue("nid"), + Labels: types.MapNull(types.StringType), }, &iaas.PartialUpdateNetworkPayload{ Name: utils.Ptr("name"), @@ -275,10 +595,80 @@ func TestToUpdatePayload(t *testing.T) { }, true, }, + { + "ipv6_default_ok", + &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{ + ProjectId: types.StringValue("pid"), + NetworkId: types.StringValue("nid"), + Labels: types.MapNull(types.StringType), + }, + &iaas.PartialUpdateNetworkPayload{ + Name: utils.Ptr("name"), + AddressFamily: &iaas.UpdateNetworkAddressFamily{ + Ipv6: &iaas.UpdateNetworkIPv6Body{ + Nameservers: &[]string{ + "ns1", + "ns2", + }, + Gateway: iaas.NewNullableString(utils.Ptr("gateway")), + }, + }, + Labels: &map[string]interface{}{ + "key": "value", + }, + }, + true, + }, + { + "ipv6_gateway_nil", + &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{ + ProjectId: types.StringValue("pid"), + NetworkId: types.StringValue("nid"), + Labels: types.MapNull(types.StringType), + }, + &iaas.PartialUpdateNetworkPayload{ + Name: utils.Ptr("name"), + AddressFamily: &iaas.UpdateNetworkAddressFamily{ + Ipv6: &iaas.UpdateNetworkIPv6Body{ + Nameservers: &[]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, types.MapNull(types.StringType)) + output, err := toUpdatePayload(context.Background(), tt.input, &tt.state) if !tt.isValid && err == nil { t.Fatalf("Should have failed") } @@ -286,7 +676,7 @@ func TestToUpdatePayload(t *testing.T) { t.Fatalf("Should not have failed: %v", err) } if tt.isValid { - diff := cmp.Diff(output, tt.expected) + diff := cmp.Diff(output, tt.expected, cmp.AllowUnexported(iaas.NullableString{})) if diff != "" { t.Fatalf("Data does not match: %s", diff) }