diff --git a/.changelog/879.txt b/.changelog/879.txt new file mode 100644 index 00000000..1d604b51 --- /dev/null +++ b/.changelog/879.txt @@ -0,0 +1,7 @@ +```release-note:new-resource +`resource/cloudavenue_vdc_network_isolated` - Added a new resource to manage isolated networks in a VDC. This resource replace the deprecated `cloudavenue_network_isolated` resource. +``` + +```release-note:new-data-source +`datasource/cloudavenue_vdc_network_isolated` - Added a new data source to fetch information about an isolated network in a VDC. This data source replace the deprecated `cloudavenue_network_isolated` data source. +``` \ No newline at end of file diff --git a/docs/data-sources/vdc_network_isolated.md b/docs/data-sources/vdc_network_isolated.md new file mode 100644 index 00000000..07af3957 --- /dev/null +++ b/docs/data-sources/vdc_network_isolated.md @@ -0,0 +1,48 @@ +--- +page_title: "cloudavenue_vdc_network_isolated Data Source - cloudavenue" +subcategory: "vDC (Virtual Datacenter)" +description: |- + The cloudavenue_vdc_network_isolated data source allows you to retrieve information about an isolated network in a VDC. +--- + +# cloudavenue_vdc_network_isolated (Data Source) + +The `cloudavenue_vdc_network_isolated` data source allows you to retrieve information about an isolated network in a `VDC`. + +## Example Usage + +```terraform +data "cloudavenue_vdc_network_isolated" "example" { + vdc = cloudavenue_vdc.example.name + name = "my-isolated-network" +} +``` + + +## Schema + +### Required + +- `name` (String) The name of the network. This value must be unique within the `VDC` that owns the network. +- `vdc` (String) The name of vDC to use. + +### Read-Only + +- `description` (String) A description of the network. +- `dns1` (String) The primary DNS server IP address for the network. +- `dns2` (String) The secondary DNS server IP address for the network. +- `dns_suffix` (String) The DNS suffix for the network. +- `gateway` (String) The gateway IP address for the network. This value define also the network IP range with the prefix length. +- `guest_vlan_allowed` (Boolean) Indicates if the network allows guest VLANs. +- `id` (String) The ID of the isolated network. +- `prefix_length` (Number) The prefix length for the network. This value must be a valid prefix length for the network IP range. (e.g. /24 for netmask 255.255.255.0). +- `static_ip_pool` (Attributes Set) A set of static IP pools to be used for this network. (see [below for nested schema](#nestedatt--static_ip_pool)) + + +### Nested Schema for `static_ip_pool` + +Read-Only: + +- `end_address` (String) The end address of the IP pool. This value must be a valid IP address in the network IP range. +- `start_address` (String) The start address of the IP pool. This value must be a valid IP address in the network IP range. + diff --git a/docs/resources/vdc_network_isolated.md b/docs/resources/vdc_network_isolated.md new file mode 100644 index 00000000..22e57885 --- /dev/null +++ b/docs/resources/vdc_network_isolated.md @@ -0,0 +1,95 @@ +--- +page_title: "cloudavenue_vdc_network_isolated Resource - cloudavenue" +subcategory: "vDC (Virtual Datacenter)" +description: |- + The cloudavenue_vdc_network_isolated resource allows you to manage an isolated network in a VDC. +--- + +# cloudavenue_vdc_network_isolated (Resource) + +The `cloudavenue_vdc_network_isolated` resource allows you to manage an isolated network in a `VDC`. + +## Example Usage + +```terraform +resource "cloudavenue_vdc_network_isolated" "example" { + name = "my-isolated-network" + vdc = cloudavenue_vdc.example.name + + gateway = "192.168.0.1" + prefix_length = 24 + + dns1 = "192.168.0.2" + dns2 = "192.168.0.3" + dns_suffix = "example.local" +} +``` + + +## Schema + +### Required + +- `gateway` (String) (ForceNew) The gateway IP address for the network. This value define also the network IP range with the prefix length. Must be a valid IP with net.ParseIP. +- `name` (String) The name of the network. This value must be unique within the `VDC` that owns the network. +- `prefix_length` (Number) (ForceNew) The prefix length for the network. This value must be a valid prefix length for the network IP range. (e.g. /24 for netmask 255.255.255.0). Value must be between 1 and 32. +- `vdc` (String) (ForceNew) The name of vDC to use. + +### Optional + +- `description` (String) A description of the network. +- `dns1` (String) The primary DNS server IP address for the network. Must be a valid IP with net.ParseIP. +- `dns2` (String) The secondary DNS server IP address for the network. Must be a valid IP with net.ParseIP. +- `dns_suffix` (String) The DNS suffix for the network. +- `guest_vlan_allowed` (Boolean) Indicates if the network allows guest VLANs. Value defaults to `false`. +- `static_ip_pool` (Attributes Set) A set of static IP pools to be used for this network. (see [below for nested schema](#nestedatt--static_ip_pool)) + +### Read-Only + +- `id` (String) The ID of the isolated network. + + +### Nested Schema for `static_ip_pool` + +Required: + +- `end_address` (String) The end address of the IP pool. This value must be a valid IP address in the network IP range. Must be a valid IP with net.ParseIP. +- `start_address` (String) The start address of the IP pool. This value must be a valid IP address in the network IP range. Must be a valid IP with net.ParseIP. + +## Advanced Usage + +Define `static_ip_pool` as a list of objects to create multiple IP pools. + +```hcl +resource "cloudavenue_vdc_network_isolated" "example" { + name = "my-isolated-network" + vdc = cloudavenue_vdc.example.name + + gateway = "192.168.0.1" + prefix_length = 24 + + dns1 = "192.168.0.2" + dns2 = "192.168.0.3" + dns_suffix = "example.local" + + static_ip_pool = [ + { + start_address = "192.168.0.10" + end_address = "192.168.0.20" + }, + { + start_address = "192.168.0.100" + end_address = "192.168.0.130" + } + ] +} + +``` + +## Import + +Import is supported using the following syntax: +```shell +# VDC Network isolated can be imported using the VDC name and the network name or ID. +terraform import cloudavenue_vdc_network_isolated.example vdc.networkNameOrId +``` diff --git a/examples/data-sources/cloudavenue_vdc_network_isolated/data-source.tf b/examples/data-sources/cloudavenue_vdc_network_isolated/data-source.tf new file mode 100644 index 00000000..496afe92 --- /dev/null +++ b/examples/data-sources/cloudavenue_vdc_network_isolated/data-source.tf @@ -0,0 +1,4 @@ +data "cloudavenue_vdc_network_isolated" "example" { + vdc = cloudavenue_vdc.example.name + name = "my-isolated-network" +} diff --git a/examples/resources/cloudavenue_vdc_network_isolated/import.sh b/examples/resources/cloudavenue_vdc_network_isolated/import.sh new file mode 100644 index 00000000..2e480afb --- /dev/null +++ b/examples/resources/cloudavenue_vdc_network_isolated/import.sh @@ -0,0 +1,2 @@ +# VDC Network isolated can be imported using the VDC name and the network name or ID. +terraform import cloudavenue_vdc_network_isolated.example vdc.networkNameOrId \ No newline at end of file diff --git a/examples/resources/cloudavenue_vdc_network_isolated/resource.tf b/examples/resources/cloudavenue_vdc_network_isolated/resource.tf new file mode 100644 index 00000000..ec35f77f --- /dev/null +++ b/examples/resources/cloudavenue_vdc_network_isolated/resource.tf @@ -0,0 +1,11 @@ +resource "cloudavenue_vdc_network_isolated" "example" { + name = "my-isolated-network" + vdc = cloudavenue_vdc.example.name + + gateway = "192.168.0.1" + prefix_length = 24 + + dns1 = "192.168.0.2" + dns2 = "192.168.0.3" + dns_suffix = "example.local" +} diff --git a/go.mod b/go.mod index 4f5d136b..c7e6754a 100644 --- a/go.mod +++ b/go.mod @@ -23,7 +23,7 @@ require ( github.com/hashicorp/terraform-plugin-log v0.9.0 github.com/hashicorp/terraform-plugin-sdk/v2 v2.35.0 github.com/iancoleman/strcase v0.3.0 - github.com/orange-cloudavenue/cloudavenue-sdk-go v0.14.0 + github.com/orange-cloudavenue/cloudavenue-sdk-go v0.14.1-0.20241212084109-0015a69a610a github.com/orange-cloudavenue/common-go/utils v0.0.0-20240119163616-66b473d92339 github.com/rs/zerolog v1.33.0 github.com/thanhpk/randstr v1.0.6 @@ -45,6 +45,7 @@ require ( github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de // indirect github.com/armon/go-radix v1.0.0 // indirect + github.com/avast/retry-go/v4 v4.6.0 // indirect github.com/bgentry/speakeasy v0.1.0 // indirect github.com/bmatcuk/doublestar/v4 v4.7.1 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect @@ -56,7 +57,7 @@ require ( github.com/go-chi/chi v4.1.2+incompatible // indirect github.com/go-chi/render v1.0.3 // indirect github.com/go-redis/redis/v8 v8.11.5 // indirect - github.com/go-resty/resty/v2 v2.16.0 // indirect + github.com/go-resty/resty/v2 v2.16.2 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/go-cmp v0.6.0 // indirect github.com/hashicorp/cli v1.1.6 // indirect diff --git a/go.sum b/go.sum index 16cc678f..db351940 100644 --- a/go.sum +++ b/go.sum @@ -35,6 +35,8 @@ github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de h1:FxWPpzIjnTlhP github.com/araddon/dateparse v0.0.0-20210429162001-6b43995a97de/go.mod h1:DCaWoUhZrYW9p1lxo/cm8EmUOOzAPSEZNGF2DK1dJgw= github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/avast/retry-go/v4 v4.6.0 h1:K9xNA+KeB8HHc2aWFuLb25Offp+0iVRXEvFx8IinRJA= +github.com/avast/retry-go/v4 v4.6.0/go.mod h1:gvWlPhBVsvBbLkVGDg/KwvBv0bEkCOLRRSHKIr2PyOE= github.com/aws/aws-sdk-go v1.31.9/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= github.com/aws/aws-sdk-go v1.55.5 h1:KKUZBfBoyqy5d3swXyiC7Q76ic40rYcbqH7qjh59kzU= github.com/aws/aws-sdk-go v1.55.5/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= @@ -93,8 +95,8 @@ github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34 github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-redis/redis/v8 v8.11.5 h1:AcZZR7igkdvfVmQTPnu9WE37LRrO/YrBH5zWyjDC0oI= github.com/go-redis/redis/v8 v8.11.5/go.mod h1:gREzHqY1hg6oD9ngVRbLStwAWKhA0FEgq8Jd4h5lpwo= -github.com/go-resty/resty/v2 v2.16.0 h1:qpKalHWI2bpp9BIKlyT8TYWEJXOk1NuKbfiT3RRnzWc= -github.com/go-resty/resty/v2 v2.16.0/go.mod h1:0fHAoK7JoBy/Ch36N8VFeMsK7xQOHhvWaC3iOktwmIU= +github.com/go-resty/resty/v2 v2.16.2 h1:CpRqTjIzq/rweXUt9+GxzzQdlkqMdt8Lm/fuK/CAbAg= +github.com/go-resty/resty/v2 v2.16.2/go.mod h1:0fHAoK7JoBy/Ch36N8VFeMsK7xQOHhvWaC3iOktwmIU= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68= github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA= @@ -250,8 +252,8 @@ github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/gomega v1.18.1 h1:M1GfJqGRrBrrGGsbxzV5dqM2U2ApXefZCQpkukxYRLE= github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs= -github.com/orange-cloudavenue/cloudavenue-sdk-go v0.14.0 h1:IHqwdPRAEfw/xP6I0COzE9hsju7Hk+TLjd3xzJ6AOqM= -github.com/orange-cloudavenue/cloudavenue-sdk-go v0.14.0/go.mod h1:CTQO1VIVFvImvmKoR4ntL3HxBi5REm6ssLzUj3kv1sc= +github.com/orange-cloudavenue/cloudavenue-sdk-go v0.14.1-0.20241212084109-0015a69a610a h1:z2os66FLZiYX+SogPZUwlRALepmnx4HE3Nf9JM7is6Q= +github.com/orange-cloudavenue/cloudavenue-sdk-go v0.14.1-0.20241212084109-0015a69a610a/go.mod h1:c/V9npNz22QjlTXEsx0GWoxhzMFYiLynlCUPQOImd2w= github.com/orange-cloudavenue/common-go/utils v0.0.0-20240119163616-66b473d92339 h1:DEKcWLGbEhu/I6kn9NAXhVCFrbPhR+Ef7oLmpLVnnPM= github.com/orange-cloudavenue/common-go/utils v0.0.0-20240119163616-66b473d92339/go.mod h1:11JAFfGWVmhoT4AAORKsIC5M6nI+uDGSEOScMzavgPA= github.com/peterhellberg/link v1.2.0 h1:UA5pg3Gp/E0F2WdX7GERiNrPQrM1K6CVJUUWfHa4t6c= diff --git a/internal/provider/provider_datasources.go b/internal/provider/provider_datasources.go index 6ce9df5d..99f98eb5 100644 --- a/internal/provider/provider_datasources.go +++ b/internal/provider/provider_datasources.go @@ -51,6 +51,7 @@ func (p *cloudavenueProvider) DataSources(_ context.Context) []func() datasource vdc.NewVDCsDataSource, vdc.NewVDCDataSource, vdc.NewGroupDataSource, + vdc.NewNetworkIsolatedDataSource, // * VDC GROUP vdcg.NewVDCGDataSource, diff --git a/internal/provider/provider_resources.go b/internal/provider/provider_resources.go index 28def074..6b248a56 100644 --- a/internal/provider/provider_resources.go +++ b/internal/provider/provider_resources.go @@ -41,6 +41,7 @@ func (p *cloudavenueProvider) Resources(_ context.Context) []func() resource.Res vdc.NewVDCResource, vdc.NewACLResource, vdc.NewGroupResource, + vdc.NewNetworkIsolatedResource, // * VDC Group vdcg.NewVDCGResource, diff --git a/internal/provider/vdc/network_isolated_datasource.go b/internal/provider/vdc/network_isolated_datasource.go new file mode 100644 index 00000000..97b5fb4a --- /dev/null +++ b/internal/provider/vdc/network_isolated_datasource.go @@ -0,0 +1,102 @@ +package vdc + +import ( + "context" + "fmt" + + "github.com/hashicorp/terraform-plugin-framework/diag" + + "github.com/hashicorp/terraform-plugin-framework/datasource" + + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/client" + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/metrics" + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/common/vdc" +) + +var ( + _ datasource.DataSource = &NetworkIsolatedDataSource{} + _ datasource.DataSourceWithConfigure = &NetworkIsolatedDataSource{} +) + +func NewNetworkIsolatedDataSource() datasource.DataSource { + return &NetworkIsolatedDataSource{} +} + +type NetworkIsolatedDataSource struct { + client *client.CloudAvenue + vdc vdc.VDC +} + +func (d *NetworkIsolatedDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_" + categoryName + "_network_isolated" +} + +// Init Initializes the resource. +func (d *NetworkIsolatedDataSource) Init(ctx context.Context, rm *networkIsolatedModel) (diags diag.Diagnostics) { + d.vdc, diags = vdc.Init(d.client, rm.VDC.StringValue) + return +} + +func (d *NetworkIsolatedDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = networkIsolatedSchema(ctx).GetDataSource(ctx) +} + +func (d *NetworkIsolatedDataSource) Configure(ctx context.Context, req datasource.ConfigureRequest, resp *datasource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*client.CloudAvenue) + + if !ok { + resp.Diagnostics.AddError( + "Unexpected Data Source Configure Type", + fmt.Sprintf("Expected *client.CloudAvenue, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + + return + } + + d.client = client +} + +func (d *NetworkIsolatedDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + defer metrics.New("data.cloudavenue_vdc_network_isolated", d.client.GetOrgName(), metrics.Read)() + + config := &networkIsolatedModel{} + + // Read Terraform configuration data into the model + resp.Diagnostics.Append(req.Config.Get(ctx, config)...) + if resp.Diagnostics.HasError() { + return + } + // Init the resource + resp.Diagnostics.Append(d.Init(ctx, config)...) + if resp.Diagnostics.HasError() { + return + } + + /* + Implement the data source read logic here. + */ + + s := &NetworkIsolatedResource{ + client: d.client, + vdc: d.vdc, + } + + // Read data from the API + data, found, diags := s.read(ctx, config) + if !found { + resp.Diagnostics.AddError("Resource not found", fmt.Sprintf("The isolated network '%s' was not found in the VDC '%s'.", config.Name.Get(), config.VDC.Get())) + return + } + resp.Diagnostics.Append(diags...) + if resp.Diagnostics.HasError() { + return + } + + // Save data into Terraform state + resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) +} diff --git a/internal/provider/vdc/network_isolated_resource.go b/internal/provider/vdc/network_isolated_resource.go new file mode 100644 index 00000000..494d56e0 --- /dev/null +++ b/internal/provider/vdc/network_isolated_resource.go @@ -0,0 +1,360 @@ +package vdc + +import ( + "context" + "fmt" + "strings" + + "github.com/vmware/go-vcloud-director/v2/govcd" + + "github.com/hashicorp/terraform-plugin-framework/diag" + + "github.com/hashicorp/terraform-plugin-framework/resource" + + supertypes "github.com/FrangipaneTeam/terraform-plugin-framework-supertypes" + + "github.com/orange-cloudavenue/cloudavenue-sdk-go/pkg/urn" + v1 "github.com/orange-cloudavenue/cloudavenue-sdk-go/v1" + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/client" + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/metrics" + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/common/vdc" +) + +// Ensure the implementation satisfies the expected interfaces. +var ( + _ resource.Resource = &NetworkIsolatedResource{} + _ resource.ResourceWithConfigure = &NetworkIsolatedResource{} + _ resource.ResourceWithImportState = &NetworkIsolatedResource{} +) + +// NewNetworkIsolatedResource is a helper function to simplify the provider implementation. +func NewNetworkIsolatedResource() resource.Resource { + return &NetworkIsolatedResource{} +} + +// NetworkIsolatedResource is the resource implementation. +type NetworkIsolatedResource struct { + client *client.CloudAvenue + vdc vdc.VDC +} + +// Init Initializes the resource. +func (r *NetworkIsolatedResource) Init(ctx context.Context, rm *networkIsolatedModel) (diags diag.Diagnostics) { + r.vdc, diags = vdc.Init(r.client, rm.VDC.StringValue) + return +} + +// Metadata returns the resource type name. +func (r *NetworkIsolatedResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_" + categoryName + "_network_isolated" +} + +// Schema defines the schema for the resource. +func (r *NetworkIsolatedResource) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = networkIsolatedSchema(ctx).GetResource(ctx) +} + +func (r *NetworkIsolatedResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { + // Prevent panic if the provider has not been configured. + if req.ProviderData == nil { + return + } + + client, ok := req.ProviderData.(*client.CloudAvenue) + if !ok { + resp.Diagnostics.AddError( + "Unexpected Resource Configure Type", + fmt.Sprintf("Expected *client.CloudAvenue, got: %T. Please report this issue to the provider developers.", req.ProviderData), + ) + return + } + r.client = client +} + +// Create creates the resource and sets the initial Terraform state. +func (r *NetworkIsolatedResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + defer metrics.New("cloudavenue_vdc_network_isolated", r.client.GetOrgName(), metrics.Create)() + + plan := &networkIsolatedModel{} + + // Retrieve values from plan + resp.Diagnostics.Append(req.Plan.Get(ctx, plan)...) + if resp.Diagnostics.HasError() { + return + } + + // Init the resource + resp.Diagnostics.Append(r.Init(ctx, plan)...) + if resp.Diagnostics.HasError() { + return + } + + /* + Implement the resource creation logic here. + */ + + values, d := plan.ToSDK(ctx) + if d.HasError() { + resp.Diagnostics.Append(d...) + return + } + + networkIsolated, err := r.vdc.CreateNetworkIsolated(values) + if err != nil { + resp.Diagnostics.AddError("Error creating isolated network", err.Error()) + return + } + + plan.ID.Set(networkIsolated.ID) + + // Use generic read function to refresh the state + state, found, d := r.read(ctx, plan) + if !found { + resp.State.RemoveResource(ctx) + resp.Diagnostics.AddError("Resource not found after creation", "The resource was not found after creation.") + return + } + if d.HasError() { + resp.Diagnostics.Append(d...) + return + } + + // Set state to fully populated data + resp.Diagnostics.Append(resp.State.Set(ctx, state)...) +} + +// Read refreshes the Terraform state with the latest data. +func (r *NetworkIsolatedResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + defer metrics.New("cloudavenue_vdc_network_isolated", r.client.GetOrgName(), metrics.Read)() + + state := &networkIsolatedModel{} + + // Get current state + resp.Diagnostics.Append(req.State.Get(ctx, state)...) + if resp.Diagnostics.HasError() { + return + } + + // Init the resource + resp.Diagnostics.Append(r.Init(ctx, state)...) + if resp.Diagnostics.HasError() { + return + } + + // Refresh the state + stateRefreshed, found, d := r.read(ctx, state) + if !found { + resp.State.RemoveResource(ctx) + return + } + if d.HasError() { + resp.Diagnostics.Append(d...) + return + } + + // Set refreshed state + resp.Diagnostics.Append(resp.State.Set(ctx, stateRefreshed)...) +} + +// Update updates the resource and sets the updated Terraform state on success. +func (r *NetworkIsolatedResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + defer metrics.New("cloudavenue_vdc_network_isolated", r.client.GetOrgName(), metrics.Update)() + + var ( + plan = &networkIsolatedModel{} + state = &networkIsolatedModel{} + ) + + // Get current plan and state + resp.Diagnostics.Append(req.Plan.Get(ctx, plan)...) + resp.Diagnostics.Append(req.State.Get(ctx, state)...) + if resp.Diagnostics.HasError() { + return + } + + // Init the resource + resp.Diagnostics.Append(r.Init(ctx, state)...) + if resp.Diagnostics.HasError() { + return + } + + /* + Implement the resource update here + */ + + values, d := plan.ToSDK(ctx) + if d.HasError() { + resp.Diagnostics.Append(d...) + return + } + + net, err := r.vdc.GetNetworkIsolated(state.ID.Get()) + if err != nil { + resp.Diagnostics.AddError("Error getting isolated network", err.Error()) + return + } + + values.ID = state.ID.Get() + + // Update the network + if err := net.Update(values); err != nil { + resp.Diagnostics.AddError("Error updating isolated network", err.Error()) + return + } + + // Use generic read function to refresh the state + stateRefreshed, found, d := r.read(ctx, state) + if !found { + resp.State.RemoveResource(ctx) + resp.Diagnostics.AddError("Resource not found after update", "The resource was not found after update. Please refresh the state.") + return + } + if d.HasError() { + resp.Diagnostics.Append(d...) + return + } + + // Set state to fully populated data + resp.Diagnostics.Append(resp.State.Set(ctx, stateRefreshed)...) +} + +// Delete deletes the resource and removes the Terraform state on success. +func (r *NetworkIsolatedResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + defer metrics.New("cloudavenue_vdc_network_isolated", r.client.GetOrgName(), metrics.Delete)() + + state := &networkIsolatedModel{} + + // Get current state + resp.Diagnostics.Append(req.State.Get(ctx, state)...) + if resp.Diagnostics.HasError() { + return + } + + // Init the resource + resp.Diagnostics.Append(r.Init(ctx, state)...) + if resp.Diagnostics.HasError() { + return + } + + /* + Implement the resource deletion here + */ + + net, err := r.vdc.GetNetworkIsolated(state.ID.Get()) + if err != nil { + resp.Diagnostics.AddError("Error getting isolated network", err.Error()) + return + } + + // Delete the network + if err := net.Delete(); err != nil { + resp.Diagnostics.AddError("Error deleting isolated network", err.Error()) + return + } +} + +func (r *NetworkIsolatedResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + defer metrics.New("cloudavenue_vdc_network_isolated", r.client.GetOrgName(), metrics.Import)() + + // * Import with custom logic + idParts := strings.Split(req.ID, ".") + + if len(idParts) != 2 { + resp.Diagnostics.AddError( + "Unexpected Import Identifier", + fmt.Sprintf("Expected import identifier with format: vdc.networkNameOrID Got: %q", req.ID), + ) + return + } + + x := &networkIsolatedModel{ + ID: supertypes.NewStringNull(), + Name: supertypes.NewStringNull(), + VDC: supertypes.NewStringNull(), + } + + x.VDC.Set(idParts[0]) + + if urn.IsNetwork(idParts[1]) { + x.ID.Set(idParts[1]) + } else { + x.Name.Set(idParts[1]) + } + + resp.Diagnostics.Append(r.Init(ctx, x)...) + if resp.Diagnostics.HasError() { + return + } + + stateRefreshed, found, d := r.read(ctx, x) + if !found { + resp.State.RemoveResource(ctx) + return + } + if d.HasError() { + resp.Diagnostics.Append(d...) + return + } + + // Set refreshed state + resp.Diagnostics.Append(resp.State.Set(ctx, stateRefreshed)...) +} + +// * CustomFuncs + +// read is a generic read function that can be used by the resource Create, Read and Update functions. +func (r *NetworkIsolatedResource) read(ctx context.Context, planOrState *networkIsolatedModel) (stateRefreshed *networkIsolatedModel, found bool, diags diag.Diagnostics) { + stateRefreshed = planOrState.Copy() + + /* + Implement the resource read here + */ + + var ( + net *v1.VDCNetworkIsolated + err error + ) + + if urn.IsNetwork(planOrState.ID.Get()) { + net, err = r.vdc.GetNetworkIsolated(planOrState.ID.Get()) + } else { + net, err = r.vdc.GetNetworkIsolated(planOrState.Name.Get()) + } + if err != nil { + if govcd.ContainsNotFound(err) { + return nil, false, nil + } + diags.AddError("Error getting isolated network", err.Error()) + return + } + + // Populate the state with the network data + stateRefreshed.ID.Set(net.ID) + stateRefreshed.Name.Set(net.Name) + stateRefreshed.Description.Set(net.Description) + stateRefreshed.VDC.Set(r.vdc.GetName()) + stateRefreshed.Gateway.Set(net.Subnet.Gateway) + stateRefreshed.PrefixLength.SetInt(net.Subnet.PrefixLength) + stateRefreshed.DNS1.Set(net.Subnet.DNSServer1) + stateRefreshed.DNS2.Set(net.Subnet.DNSServer2) + stateRefreshed.DNSSuffix.Set(net.Subnet.DNSSuffix) + stateRefreshed.GuestVLANAllowed.SetPtr(net.GuestVLANTaggingAllowed) + + x := []*networkIsolatedModelStaticIPPool{} + for _, ipRange := range net.Subnet.IPRanges { + n := &networkIsolatedModelStaticIPPool{ + StartAddress: supertypes.NewStringNull(), + EndAddress: supertypes.NewStringNull(), + } + n.StartAddress.Set(ipRange.StartAddress) + n.EndAddress.Set(ipRange.EndAddress) + x = append(x, n) + } + + diags.Append(stateRefreshed.StaticIPPool.Set(ctx, x)...) + if diags.HasError() { + return nil, true, diags + } + + return stateRefreshed, true, nil +} diff --git a/internal/provider/vdc/network_isolated_schema.go b/internal/provider/vdc/network_isolated_schema.go new file mode 100644 index 00000000..272778ca --- /dev/null +++ b/internal/provider/vdc/network_isolated_schema.go @@ -0,0 +1,192 @@ +package vdc + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/schema/validator" + + schemaD "github.com/hashicorp/terraform-plugin-framework/datasource/schema" + schemaR "github.com/hashicorp/terraform-plugin-framework/resource/schema" + + "github.com/hashicorp/terraform-plugin-framework/resource/schema/booldefault" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/int64planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier" + "github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier" + + "github.com/hashicorp/terraform-plugin-framework-validators/int64validator" + + superschema "github.com/FrangipaneTeam/terraform-plugin-framework-superschema" + fstringvalidator "github.com/FrangipaneTeam/terraform-plugin-framework-validators/stringvalidator" +) + +func networkIsolatedSchema(_ context.Context) superschema.Schema { + return superschema.Schema{ + Resource: superschema.SchemaDetails{ + MarkdownDescription: "The `cloudavenue_vdc_network_isolated` resource allows you to manage an isolated network in a `VDC`.", + }, + DataSource: superschema.SchemaDetails{ + MarkdownDescription: "The `cloudavenue_vdc_network_isolated` data source allows you to retrieve information about an isolated network in a `VDC`.", + }, + Attributes: map[string]superschema.Attribute{ + "id": superschema.SuperStringAttribute{ + Common: &schemaR.StringAttribute{ + Computed: true, + MarkdownDescription: "The ID of the isolated network.", + }, + }, + "name": superschema.SuperStringAttribute{ + Common: &schemaR.StringAttribute{ + MarkdownDescription: "The name of the network. This value must be unique within the `VDC` that owns the network.", + Required: true, + }, + }, + "description": superschema.SuperStringAttribute{ + Common: &schemaR.StringAttribute{ + MarkdownDescription: "A description of the network.", + }, + Resource: &schemaR.StringAttribute{ + Optional: true, + }, + DataSource: &schemaD.StringAttribute{ + Computed: true, + }, + }, + "vdc": superschema.SuperStringAttribute{ + Common: &schemaR.StringAttribute{ + MarkdownDescription: "The name of vDC to use", + Required: true, + }, + Resource: &schemaR.StringAttribute{ + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + }, + "gateway": superschema.SuperStringAttribute{ + Common: &schemaR.StringAttribute{ + MarkdownDescription: "The gateway IP address for the network. This value define also the network IP range with the prefix length.", + }, + Resource: &schemaR.StringAttribute{ + Required: true, + Validators: []validator.String{ + fstringvalidator.IsIP(), + }, + PlanModifiers: []planmodifier.String{ + stringplanmodifier.RequiresReplace(), + }, + }, + DataSource: &schemaD.StringAttribute{ + Computed: true, + }, + }, + "guest_vlan_allowed": superschema.SuperBoolAttribute{ + Common: &schemaR.BoolAttribute{ + MarkdownDescription: "Indicates if the network allows guest VLANs.", + Computed: true, + }, + Resource: &schemaR.BoolAttribute{ + Optional: true, + Default: booldefault.StaticBool(false), + }, + }, + "prefix_length": superschema.SuperInt64Attribute{ + Common: &schemaR.Int64Attribute{ + MarkdownDescription: "The prefix length for the network. This value must be a valid prefix length for the network IP range. (e.g. /24 for netmask 255.255.255.0)", + }, + Resource: &schemaR.Int64Attribute{ + Required: true, + Validators: []validator.Int64{ + int64validator.Between(1, 32), + }, + PlanModifiers: []planmodifier.Int64{ + int64planmodifier.RequiresReplace(), + }, + }, + DataSource: &schemaD.Int64Attribute{ + Computed: true, + }, + }, + "dns1": superschema.SuperStringAttribute{ + Common: &schemaR.StringAttribute{ + MarkdownDescription: "The primary DNS server IP address for the network.", + }, + Resource: &schemaR.StringAttribute{ + Optional: true, + Validators: []validator.String{ + fstringvalidator.IsIP(), + }, + }, + DataSource: &schemaD.StringAttribute{ + Computed: true, + }, + }, + "dns2": superschema.SuperStringAttribute{ + Common: &schemaR.StringAttribute{ + MarkdownDescription: "The secondary DNS server IP address for the network.", + }, + Resource: &schemaR.StringAttribute{ + Optional: true, + Validators: []validator.String{ + fstringvalidator.IsIP(), + }, + }, + DataSource: &schemaD.StringAttribute{ + Computed: true, + }, + }, + "dns_suffix": superschema.SuperStringAttribute{ + Common: &schemaR.StringAttribute{ + MarkdownDescription: "The DNS suffix for the network.", + }, + Resource: &schemaR.StringAttribute{ + Optional: true, + }, + DataSource: &schemaD.StringAttribute{ + Computed: true, + }, + }, + "static_ip_pool": superschema.SuperSetNestedAttributeOf[networkIsolatedModelStaticIPPool]{ + Common: &schemaR.SetNestedAttribute{ + MarkdownDescription: "A set of static IP pools to be used for this network.", + }, + Resource: &schemaR.SetNestedAttribute{ + Optional: true, + Computed: true, + }, + DataSource: &schemaD.SetNestedAttribute{ + Computed: true, + }, + Attributes: map[string]superschema.Attribute{ + "start_address": superschema.SuperStringAttribute{ + Common: &schemaR.StringAttribute{ + MarkdownDescription: "The start address of the IP pool. This value must be a valid IP address in the network IP range.", + }, + Resource: &schemaR.StringAttribute{ + Required: true, + Validators: []validator.String{ + fstringvalidator.IsIP(), + }, + }, + DataSource: &schemaD.StringAttribute{ + Computed: true, + }, + }, + "end_address": superschema.SuperStringAttribute{ + Common: &schemaR.StringAttribute{ + MarkdownDescription: "The end address of the IP pool. This value must be a valid IP address in the network IP range.", + }, + Resource: &schemaR.StringAttribute{ + Required: true, + Validators: []validator.String{ + fstringvalidator.IsIP(), + }, + }, + DataSource: &schemaD.StringAttribute{ + Computed: true, + }, + }, + }, + }, + }, + } +} diff --git a/internal/provider/vdc/network_isolated_schema_test.go b/internal/provider/vdc/network_isolated_schema_test.go new file mode 100644 index 00000000..db7558b3 --- /dev/null +++ b/internal/provider/vdc/network_isolated_schema_test.go @@ -0,0 +1,57 @@ +package vdc_test + +import ( + "context" + "testing" + + // fwdatasource "github.com/hashicorp/terraform-plugin-framework/datasource". + fwdatasource "github.com/hashicorp/terraform-plugin-framework/datasource" + fwresource "github.com/hashicorp/terraform-plugin-framework/resource" + + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/vdc" +) + +// Unit test for the schema of the resource cloudavenue_vdc_NetworkIsolated. +func TestNetworkIsolatedResourceSchema(t *testing.T) { + t.Parallel() + + ctx := context.Background() + schemaResponse := &fwresource.SchemaResponse{} + + // Instantiate the resource.Resource and call its Schema method + vdc.NewNetworkIsolatedResource().Schema(ctx, fwresource.SchemaRequest{}, schemaResponse) + + if schemaResponse.Diagnostics.HasError() { + t.Fatalf("Schema method diagnostics: %+v", schemaResponse.Diagnostics) + } + + // Validate the schema + diagnostics := schemaResponse.Schema.ValidateImplementation(ctx) + + if diagnostics.HasError() { + t.Fatalf("Schema validation diagnostics: %+v", diagnostics) + } +} + +// Unit test for the schema of the datasource cloudavenue_vdc_NetworkIsolated + +func TestNetworkIsolatedDataSourceSchema(t *testing.T) { + t.Parallel() + + ctx := context.Background() + schemaResponse := &fwdatasource.SchemaResponse{} + + // Instantiate the datasource.Datasource and call its Schema method + vdc.NewNetworkIsolatedDataSource().Schema(ctx, fwdatasource.SchemaRequest{}, schemaResponse) + + if schemaResponse.Diagnostics.HasError() { + t.Fatalf("Schema method diagnostics: %+v", schemaResponse.Diagnostics) + } + + // Validate the schema + diagnostics := schemaResponse.Schema.ValidateImplementation(ctx) + + if diagnostics.HasError() { + t.Fatalf("Schema validation diagnostics: %+v", diagnostics) + } +} diff --git a/internal/provider/vdc/network_isolated_types.go b/internal/provider/vdc/network_isolated_types.go new file mode 100644 index 00000000..c23c2733 --- /dev/null +++ b/internal/provider/vdc/network_isolated_types.go @@ -0,0 +1,76 @@ +package vdc + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/diag" + + supertypes "github.com/FrangipaneTeam/terraform-plugin-framework-supertypes" + + v1 "github.com/orange-cloudavenue/cloudavenue-sdk-go/v1" + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/pkg/utils" +) + +type ( + networkIsolatedModel struct { + ID supertypes.StringValue `tfsdk:"id"` + Name supertypes.StringValue `tfsdk:"name"` + Description supertypes.StringValue `tfsdk:"description"` + VDC supertypes.StringValue `tfsdk:"vdc"` + Gateway supertypes.StringValue `tfsdk:"gateway"` + PrefixLength supertypes.Int64Value `tfsdk:"prefix_length"` + DNS1 supertypes.StringValue `tfsdk:"dns1"` + DNS2 supertypes.StringValue `tfsdk:"dns2"` + DNSSuffix supertypes.StringValue `tfsdk:"dns_suffix"` + StaticIPPool supertypes.SetNestedObjectValueOf[networkIsolatedModelStaticIPPool] `tfsdk:"static_ip_pool"` + GuestVLANAllowed supertypes.BoolValue `tfsdk:"guest_vlan_allowed"` + } + + networkIsolatedModelStaticIPPool struct { + StartAddress supertypes.StringValue `tfsdk:"start_address"` + EndAddress supertypes.StringValue `tfsdk:"end_address"` + } +) + +func (rm *networkIsolatedModel) Copy() *networkIsolatedModel { + x := &networkIsolatedModel{} + utils.ModelCopy(rm, x) + return x +} + +// ToSDK converts the model to the SDK model. +func (rm *networkIsolatedModel) ToSDK(ctx context.Context) (values *v1.VDCNetworkIsolatedModel, diags diag.Diagnostics) { + values = &v1.VDCNetworkIsolatedModel{ + ID: rm.ID.Get(), + Name: rm.Name.Get(), + Description: rm.Description.Get(), + Subnet: func() v1.VDCNetworkModelSubnet { + return v1.VDCNetworkModelSubnet{ + Gateway: rm.Gateway.Get(), + PrefixLength: rm.PrefixLength.GetInt(), + DNSServer1: rm.DNS1.Get(), + DNSServer2: rm.DNS2.Get(), + DNSSuffix: rm.DNSSuffix.Get(), + IPRanges: func() v1.VDCNetworkModelSubnetIPRanges { + var ipRanges v1.VDCNetworkModelSubnetIPRanges + + ipPools, d := rm.StaticIPPool.Get(ctx) + if d.HasError() { + diags.Append(d...) + return ipRanges + } + + for _, ipRange := range ipPools { + ipRanges = append(ipRanges, v1.VDCNetworkModelSubnetIPRange{ + StartAddress: ipRange.StartAddress.Get(), + EndAddress: ipRange.EndAddress.Get(), + }) + } + return ipRanges + }(), + } + }(), + } + + return values, diags +} diff --git a/internal/testsacc/acctest_datasources_test.go b/internal/testsacc/acctest_datasources_test.go index 7dabb59c..6b1a7fba 100644 --- a/internal/testsacc/acctest_datasources_test.go +++ b/internal/testsacc/acctest_datasources_test.go @@ -14,8 +14,9 @@ func GetDataSourceConfig() map[testsacc.ResourceName]func() *testsacc.ResourceCo Tier0VRFDataSourceName: testsacc.NewResourceConfig(NewTier0VRFDataSourceTest()), // * VDC - VDCDataSourceName: testsacc.NewResourceConfig(NewVDCDataSourceTest()), - VDCGroupDataSourceName: testsacc.NewResourceConfig(NewVDCGroupDataSourceTest()), + VDCDataSourceName: testsacc.NewResourceConfig(NewVDCDataSourceTest()), + VDCGroupDataSourceName: testsacc.NewResourceConfig(NewVDCGroupDataSourceTest()), + VDCNetworkIsolatedDataSourceName: testsacc.NewResourceConfig(NewVDCNetworkIsolatedDataSourceTest()), // * VDC Group VDCGDataSourceName: testsacc.NewResourceConfig(NewVDCGDataSourceTest()), diff --git a/internal/testsacc/acctest_resources_test.go b/internal/testsacc/acctest_resources_test.go index 0be4da8f..15981c81 100644 --- a/internal/testsacc/acctest_resources_test.go +++ b/internal/testsacc/acctest_resources_test.go @@ -10,8 +10,9 @@ func GetResourceConfig() map[testsacc.ResourceName]func() *testsacc.ResourceConf CatalogVAppTemplateDataSourceName: testsacc.NewResourceConfig(NewCatalogVAppTemplateDataSourceTest()), // * VDC - VDCResourceName: testsacc.NewResourceConfig(NewVDCResourceTest()), - VDCGroupResourceName: testsacc.NewResourceConfig(NewVDCGroupResourceTest()), + VDCResourceName: testsacc.NewResourceConfig(NewVDCResourceTest()), + VDCGroupResourceName: testsacc.NewResourceConfig(NewVDCGroupResourceTest()), + VDCNetworkIsolatedResourceName: testsacc.NewResourceConfig(NewVDCNetworkIsolatedResourceTest()), // * VDC Group VDCGResourceName: testsacc.NewResourceConfig(NewVDCGResourceTest()), diff --git a/internal/testsacc/vdc_network_isolated_datasource_test.go b/internal/testsacc/vdc_network_isolated_datasource_test.go new file mode 100644 index 00000000..cd8dbc53 --- /dev/null +++ b/internal/testsacc/vdc_network_isolated_datasource_test.go @@ -0,0 +1,58 @@ +package testsacc + +import ( + "context" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/helpers/testsacc" +) + +var _ testsacc.TestACC = &VDCNetworkIsolatedDataSource{} + +const ( + VDCNetworkIsolatedDataSourceName = testsacc.ResourceName("data.cloudavenue_vdc_network_isolated") +) + +type VDCNetworkIsolatedDataSource struct{} + +func NewVDCNetworkIsolatedDataSourceTest() testsacc.TestACC { + return &VDCNetworkIsolatedDataSource{} +} + +// GetResourceName returns the name of the resource. +func (r *VDCNetworkIsolatedDataSource) GetResourceName() string { + return VDCNetworkIsolatedDataSourceName.String() +} + +func (r *VDCNetworkIsolatedDataSource) DependenciesConfig() (resp testsacc.DependenciesConfigResponse) { + resp.Append(GetResourceConfig()[VDCNetworkIsolatedResourceName]().GetDefaultConfig) + return +} + +func (r *VDCNetworkIsolatedDataSource) Tests(ctx context.Context) map[testsacc.TestName]func(ctx context.Context, resourceName string) testsacc.Test { + return map[testsacc.TestName]func(ctx context.Context, resourceName string) testsacc.Test{ + "example": func(_ context.Context, _ string) testsacc.Test { + return testsacc.Test{ + // ! Create testing + Create: testsacc.TFConfig{ + TFConfig: ` + data "cloudavenue_vdc_network_isolated" "example" { + vdc = cloudavenue_vdc.example.name + name = cloudavenue_vdc_network_isolated.example.name + }`, + Checks: GetResourceConfig()[VDCNetworkIsolatedResourceName]().GetDefaultChecks(), + }, + } + }, + } +} + +func TestAccVDCNetworkIsolatedDataSource(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { TestAccPreCheck(t) }, + ProtoV6ProviderFactories: TestAccProtoV6ProviderFactories, + Steps: testsacc.GenerateTests(&VDCNetworkIsolatedDataSource{}), + }) +} diff --git a/internal/testsacc/vdc_network_isolated_resource_test.go b/internal/testsacc/vdc_network_isolated_resource_test.go new file mode 100644 index 00000000..ccc5ef6e --- /dev/null +++ b/internal/testsacc/vdc_network_isolated_resource_test.go @@ -0,0 +1,303 @@ +package testsacc + +import ( + "context" + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + + "github.com/orange-cloudavenue/cloudavenue-sdk-go/pkg/urn" + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/helpers/testsacc" +) + +var _ testsacc.TestACC = &VDCNetworkIsolatedResource{} + +const ( + VDCNetworkIsolatedResourceName = testsacc.ResourceName("cloudavenue_vdc_network_isolated") +) + +type VDCNetworkIsolatedResource struct{} + +func NewVDCNetworkIsolatedResourceTest() testsacc.TestACC { + return &VDCNetworkIsolatedResource{} +} + +// GetResourceName returns the name of the resource. +func (r *VDCNetworkIsolatedResource) GetResourceName() string { + return VDCNetworkIsolatedResourceName.String() +} + +func (r *VDCNetworkIsolatedResource) DependenciesConfig() (resp testsacc.DependenciesConfigResponse) { + resp.Append(GetResourceConfig()[VDCResourceName]().GetDefaultConfig) + return +} + +func (r *VDCNetworkIsolatedResource) Tests(ctx context.Context) map[testsacc.TestName]func(ctx context.Context, resourceName string) testsacc.Test { + return map[testsacc.TestName]func(ctx context.Context, resourceName string) testsacc.Test{ + "example": func(_ context.Context, resourceName string) testsacc.Test { + return testsacc.Test{ + CommonChecks: []resource.TestCheckFunc{ + resource.TestCheckResourceAttrWith(resourceName, "id", urn.TestIsType(urn.Network)), + resource.TestCheckResourceAttrSet(resourceName, "vdc"), + }, + // ! Create testing + Create: testsacc.TFConfig{ + TFConfig: testsacc.GenerateFromTemplate(resourceName, ` + resource "cloudavenue_vdc_network_isolated" "example" { + name = {{ generate . "name" }} + description = {{ generate . "description" }} + vdc = cloudavenue_vdc.example.name + + gateway = "192.168.0.1" + prefix_length = 24 + + dns1 = "1.1.1.1" + dns2 = "1.0.0.1" + dns_suffix = "example.com" + + static_ip_pool = [ + { + start_address = "192.168.0.10" + end_address = "192.168.0.20" + }, + { + start_address = "192.168.0.100" + end_address = "192.168.0.130" + } + ] + }`), + Checks: []resource.TestCheckFunc{ + resource.TestCheckResourceAttr(resourceName, "name", testsacc.GetValueFromTemplate(resourceName, "name")), + resource.TestCheckResourceAttr(resourceName, "description", testsacc.GetValueFromTemplate(resourceName, "description")), + resource.TestCheckResourceAttr(resourceName, "gateway", "192.168.0.1"), + resource.TestCheckResourceAttr(resourceName, "prefix_length", "24"), + resource.TestCheckResourceAttr(resourceName, "dns1", "1.1.1.1"), + resource.TestCheckResourceAttr(resourceName, "dns2", "1.0.0.1"), + resource.TestCheckResourceAttr(resourceName, "dns_suffix", "example.com"), + resource.TestCheckResourceAttr(resourceName, "static_ip_pool.#", "2"), + resource.TestCheckResourceAttr(resourceName, "guest_vlan_allowed", "false"), // Default value + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "static_ip_pool.*", map[string]string{ + "start_address": "192.168.0.10", + "end_address": "192.168.0.20", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "static_ip_pool.*", map[string]string{ + "start_address": "192.168.0.100", + "end_address": "192.168.0.130", + }), + }, + }, + // ! Updates testing + // * Update name + Updates: []testsacc.TFConfig{ + { + TFConfig: testsacc.GenerateFromTemplate(resourceName, ` + resource "cloudavenue_vdc_network_isolated" "example" { + name = {{ generate . "name" }} + description = {{ get . "description" }} + vdc = cloudavenue_vdc.example.name + + gateway = "192.168.0.1" + prefix_length = 24 + + dns1 = "1.1.1.1" + dns2 = "1.0.0.1" + dns_suffix = "example.com" + + static_ip_pool = [ + { + start_address = "192.168.0.10" + end_address = "192.168.0.20" + }, + { + start_address = "192.168.0.100" + end_address = "192.168.0.130" + } + ] + }`), + Checks: []resource.TestCheckFunc{ + resource.TestCheckResourceAttr(resourceName, "name", testsacc.GetValueFromTemplate(resourceName, "name")), + resource.TestCheckResourceAttr(resourceName, "description", testsacc.GetValueFromTemplate(resourceName, "description")), + resource.TestCheckResourceAttr(resourceName, "gateway", "192.168.0.1"), + resource.TestCheckResourceAttr(resourceName, "prefix_length", "24"), + resource.TestCheckResourceAttr(resourceName, "dns1", "1.1.1.1"), + resource.TestCheckResourceAttr(resourceName, "dns2", "1.0.0.1"), + resource.TestCheckResourceAttr(resourceName, "dns_suffix", "example.com"), + resource.TestCheckResourceAttr(resourceName, "static_ip_pool.#", "2"), + resource.TestCheckResourceAttr(resourceName, "guest_vlan_allowed", "false"), // Default value + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "static_ip_pool.*", map[string]string{ + "start_address": "192.168.0.10", + "end_address": "192.168.0.20", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "static_ip_pool.*", map[string]string{ + "start_address": "192.168.0.100", + "end_address": "192.168.0.130", + }), + }, + }, + // * Update description + { + TFConfig: testsacc.GenerateFromTemplate(resourceName, ` + resource "cloudavenue_vdc_network_isolated" "example" { + name = {{ get . "name" }} + description = {{ generate . "description" }} + vdc = cloudavenue_vdc.example.name + + gateway = "192.168.0.1" + prefix_length = 24 + + dns1 = "1.1.1.1" + dns2 = "1.0.0.1" + dns_suffix = "example.com" + + static_ip_pool = [ + { + start_address = "192.168.0.10" + end_address = "192.168.0.20" + }, + { + start_address = "192.168.0.100" + end_address = "192.168.0.130" + } + ] + }`), + Checks: []resource.TestCheckFunc{ + resource.TestCheckResourceAttr(resourceName, "name", testsacc.GetValueFromTemplate(resourceName, "name")), + resource.TestCheckResourceAttr(resourceName, "description", testsacc.GetValueFromTemplate(resourceName, "description")), + resource.TestCheckResourceAttr(resourceName, "gateway", "192.168.0.1"), + resource.TestCheckResourceAttr(resourceName, "prefix_length", "24"), + resource.TestCheckResourceAttr(resourceName, "dns1", "1.1.1.1"), + resource.TestCheckResourceAttr(resourceName, "dns2", "1.0.0.1"), + resource.TestCheckResourceAttr(resourceName, "dns_suffix", "example.com"), + resource.TestCheckResourceAttr(resourceName, "static_ip_pool.#", "2"), + resource.TestCheckResourceAttr(resourceName, "guest_vlan_allowed", "false"), // Default value + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "static_ip_pool.*", map[string]string{ + "start_address": "192.168.0.10", + "end_address": "192.168.0.20", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "static_ip_pool.*", map[string]string{ + "start_address": "192.168.0.100", + "end_address": "192.168.0.130", + }), + }, + }, + // * Update DNS + { + TFConfig: testsacc.GenerateFromTemplate(resourceName, ` + resource "cloudavenue_vdc_network_isolated" "example" { + name = {{ get . "name" }} + description = {{ generate . "description" }} + vdc = cloudavenue_vdc.example.name + + gateway = "192.168.0.1" + prefix_length = 24 + + dns1 = "208.67.222.222" + dns2 = "208.67.220.220" + dns_suffix = "example.local" + + static_ip_pool = [ + { + start_address = "192.168.0.10" + end_address = "192.168.0.20" + }, + { + start_address = "192.168.0.100" + end_address = "192.168.0.130" + } + ] + }`), + Checks: []resource.TestCheckFunc{ + resource.TestCheckResourceAttr(resourceName, "name", testsacc.GetValueFromTemplate(resourceName, "name")), + resource.TestCheckResourceAttr(resourceName, "description", testsacc.GetValueFromTemplate(resourceName, "description")), + resource.TestCheckResourceAttr(resourceName, "gateway", "192.168.0.1"), + resource.TestCheckResourceAttr(resourceName, "prefix_length", "24"), + resource.TestCheckResourceAttr(resourceName, "dns1", "208.67.222.222"), + resource.TestCheckResourceAttr(resourceName, "dns2", "208.67.220.220"), + resource.TestCheckResourceAttr(resourceName, "dns_suffix", "example.local"), + resource.TestCheckResourceAttr(resourceName, "static_ip_pool.#", "2"), + resource.TestCheckResourceAttr(resourceName, "guest_vlan_allowed", "false"), // Default value + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "static_ip_pool.*", map[string]string{ + "start_address": "192.168.0.10", + "end_address": "192.168.0.20", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "static_ip_pool.*", map[string]string{ + "start_address": "192.168.0.100", + "end_address": "192.168.0.130", + }), + }, + }, + // * Update Static IP Pool + { + TFConfig: testsacc.GenerateFromTemplate(resourceName, ` + resource "cloudavenue_vdc_network_isolated" "example" { + name = {{ get . "name" }} + description = {{ generate . "description" }} + vdc = cloudavenue_vdc.example.name + + gateway = "192.168.0.1" + prefix_length = 24 + + dns1 = "208.67.222.222" + dns2 = "208.67.220.220" + dns_suffix = "example.local" + + static_ip_pool = [ + { + start_address = "192.168.0.40" + end_address = "192.168.0.60" + }, + { + start_address = "192.168.0.100" + end_address = "192.168.0.130" + }, + { + start_address = "192.168.0.200" + end_address = "192.168.0.220" + } + ] + }`), + Checks: []resource.TestCheckFunc{ + resource.TestCheckResourceAttr(resourceName, "name", testsacc.GetValueFromTemplate(resourceName, "name")), + resource.TestCheckResourceAttr(resourceName, "description", testsacc.GetValueFromTemplate(resourceName, "description")), + resource.TestCheckResourceAttr(resourceName, "gateway", "192.168.0.1"), + resource.TestCheckResourceAttr(resourceName, "prefix_length", "24"), + resource.TestCheckResourceAttr(resourceName, "dns1", "208.67.222.222"), + resource.TestCheckResourceAttr(resourceName, "dns2", "208.67.220.220"), + resource.TestCheckResourceAttr(resourceName, "dns_suffix", "example.local"), + resource.TestCheckResourceAttr(resourceName, "static_ip_pool.#", "3"), + resource.TestCheckResourceAttr(resourceName, "guest_vlan_allowed", "false"), // Default value + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "static_ip_pool.*", map[string]string{ + "start_address": "192.168.0.40", + "end_address": "192.168.0.60", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "static_ip_pool.*", map[string]string{ + "start_address": "192.168.0.100", + "end_address": "192.168.0.130", + }), + resource.TestCheckTypeSetElemNestedAttrs(resourceName, "static_ip_pool.*", map[string]string{ + "start_address": "192.168.0.200", + "end_address": "192.168.0.220", + }), + }, + }, + }, + // ! Imports testing + Imports: []testsacc.TFImport{ + { + ImportStateIDBuilder: []string{"vdc", "name"}, + ImportState: true, + ImportStateVerify: true, + }, + }, + } + }, + } +} + +func TestAccVDCNetworkIsolatedResource(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { TestAccPreCheck(t) }, + ProtoV6ProviderFactories: TestAccProtoV6ProviderFactories, + Steps: testsacc.GenerateTests(&VDCNetworkIsolatedResource{}), + }) +} diff --git a/templates/data-sources/vdc_network_isolated.md.tmpl b/templates/data-sources/vdc_network_isolated.md.tmpl new file mode 100644 index 00000000..7e1b24ac --- /dev/null +++ b/templates/data-sources/vdc_network_isolated.md.tmpl @@ -0,0 +1,25 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "vDC (Virtual Datacenter)" +description: |- +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +{{ if .HasExample -}} +## Example Usage + +{{ tffile .ExampleFile }} +{{- end }} + +{{ .SchemaMarkdown | trimspace }} + +{{ if .HasImport -}} +## Import + +Import is supported using the following syntax: +{{ codefile "shell" .ImportFile }} +{{- end }} \ No newline at end of file diff --git a/templates/resources/vdc_network_isolated.md.tmpl b/templates/resources/vdc_network_isolated.md.tmpl new file mode 100644 index 00000000..37c8353f --- /dev/null +++ b/templates/resources/vdc_network_isolated.md.tmpl @@ -0,0 +1,55 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "vDC (Virtual Datacenter)" +description: |- +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +{{ if .HasExample -}} +## Example Usage + +{{ tffile .ExampleFile }} +{{- end }} + +{{ .SchemaMarkdown | trimspace }} + +## Advanced Usage + +Define `static_ip_pool` as a list of objects to create multiple IP pools. + +```hcl +resource "cloudavenue_vdc_network_isolated" "example" { + name = "my-isolated-network" + vdc = cloudavenue_vdc.example.name + + gateway = "192.168.0.1" + prefix_length = 24 + + dns1 = "192.168.0.2" + dns2 = "192.168.0.3" + dns_suffix = "example.local" + + static_ip_pool = [ + { + start_address = "192.168.0.10" + end_address = "192.168.0.20" + }, + { + start_address = "192.168.0.100" + end_address = "192.168.0.130" + } + ] +} + +``` + +{{ if .HasImport -}} +## Import + +Import is supported using the following syntax: +{{ codefile "shell" .ImportFile }} +{{- end }}