From fba8e887fa6ed4372a0fbf417aa7cb05c4b646f5 Mon Sep 17 00:00:00 2001 From: Mickael Stanislas Date: Fri, 6 Dec 2024 14:59:53 +0100 Subject: [PATCH] feat: add resource & datasource `vdcg` (#867) --- .changelog/867.txt | 7 + .golangci.yml | 3 +- docs/data-sources/vdcg.md | 33 ++ docs/resources/vdcg.md | 46 +++ .../cloudavenue_vdcg/data-source.tf | 3 + examples/resources/cloudavenue_vdcg/import.sh | 1 + .../resources/cloudavenue_vdcg/resource.tf | 6 + .../provider/common/adminorg/vdc_group.go | 1 + internal/provider/provider_datasources.go | 4 + internal/provider/provider_resources.go | 4 + internal/provider/vdcg/base.go | 5 + internal/provider/vdcg/vdcg_datasource.go | 102 ++++++ internal/provider/vdcg/vdcg_resource.go | 317 ++++++++++++++++++ internal/provider/vdcg/vdcg_schema.go | 100 ++++++ internal/provider/vdcg/vdcg_schema_test.go | 55 +++ internal/provider/vdcg/vdcg_types.go | 22 ++ internal/testsacc/acctest_datasources_test.go | 3 + internal/testsacc/acctest_resources_test.go | 3 + .../testsacc/vdc_group_datasource_test.go | 2 +- internal/testsacc/vdc_group_resource_test.go | 1 + internal/testsacc/vdcg_datasource_test.go | 57 ++++ internal/testsacc/vdcg_resource_test.go | 122 +++++++ templates/data-sources/vdcg.md.tmpl | 18 + templates/resources/vdcg.md.tmpl | 25 ++ 24 files changed, 938 insertions(+), 2 deletions(-) create mode 100644 .changelog/867.txt create mode 100644 docs/data-sources/vdcg.md create mode 100644 docs/resources/vdcg.md create mode 100644 examples/data-sources/cloudavenue_vdcg/data-source.tf create mode 100644 examples/resources/cloudavenue_vdcg/import.sh create mode 100644 examples/resources/cloudavenue_vdcg/resource.tf create mode 100644 internal/provider/vdcg/base.go create mode 100644 internal/provider/vdcg/vdcg_datasource.go create mode 100644 internal/provider/vdcg/vdcg_resource.go create mode 100644 internal/provider/vdcg/vdcg_schema.go create mode 100644 internal/provider/vdcg/vdcg_schema_test.go create mode 100644 internal/provider/vdcg/vdcg_types.go create mode 100644 internal/testsacc/vdcg_datasource_test.go create mode 100644 internal/testsacc/vdcg_resource_test.go create mode 100644 templates/data-sources/vdcg.md.tmpl create mode 100644 templates/resources/vdcg.md.tmpl diff --git a/.changelog/867.txt b/.changelog/867.txt new file mode 100644 index 00000000..5d3d1683 --- /dev/null +++ b/.changelog/867.txt @@ -0,0 +1,7 @@ +```release-note:new-resource +`resource/cloudavenue_vdcg` - New resource to manage the virtual datacenter group. (This resource replace `cloudavenue_vdc_group`) +``` + +```release-note:new-data-source +`datasource/cloudavenue_vdcg` - New data source to get information about the virtual datacenter group. (This data source replace `cloudavenue_vdc_group`) +``` \ No newline at end of file diff --git a/.golangci.yml b/.golangci.yml index bd1bb0e6..ed2441cc 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -181,5 +181,6 @@ linters-settings: "NAT", "VPN", "BMS", - "SAML" + "SAML", + "VDCG" ] diff --git a/docs/data-sources/vdcg.md b/docs/data-sources/vdcg.md new file mode 100644 index 00000000..71ccec33 --- /dev/null +++ b/docs/data-sources/vdcg.md @@ -0,0 +1,33 @@ +--- +page_title: "cloudavenue_vdcg Data Source - cloudavenue" +subcategory: "vDC Group (Virtual Datacenter Group)" +description: |- + The cloudavenue_vdcg data source allows you to retrieve informations about an existing virtual datacenter group. +--- + +# cloudavenue_vdcg (Data Source) + +The `cloudavenue_vdcg` data source allows you to retrieve informations about an existing virtual datacenter group. + +## Example Usage + +```terraform +data "cloudavenue_vdcg" "example" { + name = "example" +} +``` + + +## Schema + +### Optional + +- `id` (String) The ID of the VDC Group. Ensure that one and only one attribute from this collection is set : `name`, `id`. +- `name` (String) The name of the VDC Group. Ensure that one and only one attribute from this collection is set : `name`, `id`. + +### Read-Only + +- `description` (String) The description of the VDC Group. +- `status` (String) The status of the VDC Group. Value must be one of : `SAVING`, `SAVED`, `CONFIGURING`, `REALIZED`, `REALIZATION_FAILED`, `DELETING`, `DELETE_FAILED`, `OBJECT_NOT_FOUND`, `UNCONFIGURED`. +- `type` (String) The type of the VDC Group. Value must be one of : `LOCAL`, `UNIVERSAL`. +- `vdc_ids` (Set of String) List of VDC IDs attached to the VDC Group. diff --git a/docs/resources/vdcg.md b/docs/resources/vdcg.md new file mode 100644 index 00000000..f65279ee --- /dev/null +++ b/docs/resources/vdcg.md @@ -0,0 +1,46 @@ +--- +page_title: "cloudavenue_vdcg Resource - cloudavenue" +subcategory: "vDC Group (Virtual Datacenter Group)" +description: |- + The cloudavenue_vdcg resource allows you to manage a virtual datacenter group. +--- + +# cloudavenue_vdcg (Resource) + +The `cloudavenue_vdcg` resource allows you to manage a virtual datacenter group. + +## Example Usage + +```terraform +resource "cloudavenue_vdcg" "example" { + name = "example" + vdc_ids = [ + cloudavenue_vdc.example.id, + ] +} +``` + + +## Schema + +### Required + +- `name` (String) The name of the VDC Group. +- `vdc_ids` (Set of String) List of VDC IDs attached to the VDC Group. Set must contain at least 1 elements. + +### Optional + +- `description` (String) The description of the VDC Group. + +### Read-Only + +- `id` (String) The ID of the VDC Group. +- `status` (String) The status of the VDC Group. Value must be one of : `SAVING`, `SAVED`, `CONFIGURING`, `REALIZED`, `REALIZATION_FAILED`, `DELETING`, `DELETE_FAILED`, `OBJECT_NOT_FOUND`, `UNCONFIGURED`. +- `type` (String) The type of the VDC Group. Value must be one of : `LOCAL`, `UNIVERSAL`. + +## Import + +Import is supported using the following syntax: +```shell +terraform import cloudavenue_vdcg.example vdcGroupNameOrID +``` \ No newline at end of file diff --git a/examples/data-sources/cloudavenue_vdcg/data-source.tf b/examples/data-sources/cloudavenue_vdcg/data-source.tf new file mode 100644 index 00000000..b07f1c94 --- /dev/null +++ b/examples/data-sources/cloudavenue_vdcg/data-source.tf @@ -0,0 +1,3 @@ +data "cloudavenue_vdcg" "example" { + name = "example" +} diff --git a/examples/resources/cloudavenue_vdcg/import.sh b/examples/resources/cloudavenue_vdcg/import.sh new file mode 100644 index 00000000..273cd1c6 --- /dev/null +++ b/examples/resources/cloudavenue_vdcg/import.sh @@ -0,0 +1 @@ +terraform import cloudavenue_vdcg.example vdcGroupNameOrID \ No newline at end of file diff --git a/examples/resources/cloudavenue_vdcg/resource.tf b/examples/resources/cloudavenue_vdcg/resource.tf new file mode 100644 index 00000000..d010cb31 --- /dev/null +++ b/examples/resources/cloudavenue_vdcg/resource.tf @@ -0,0 +1,6 @@ +resource "cloudavenue_vdcg" "example" { + name = "example" + vdc_ids = [ + cloudavenue_vdc.example.id, + ] +} diff --git a/internal/provider/common/adminorg/vdc_group.go b/internal/provider/common/adminorg/vdc_group.go index 4e131f13..dc09003d 100644 --- a/internal/provider/common/adminorg/vdc_group.go +++ b/internal/provider/common/adminorg/vdc_group.go @@ -7,6 +7,7 @@ import ( ) // GetVDCGroupByNameOrID returns the VDC group using the name or ID provided in the argument. +// Deprecated: Use GetVdcGroupByName or GetVdcGroupById instead. func (ao *AdminOrg) GetVDCGroupByNameOrID(nameOrID string) (*govcd.VdcGroup, error) { if urn.IsVDCGroup(nameOrID) { return ao.GetVdcGroupById(nameOrID) diff --git a/internal/provider/provider_datasources.go b/internal/provider/provider_datasources.go index 00d2050f..6ce9df5d 100644 --- a/internal/provider/provider_datasources.go +++ b/internal/provider/provider_datasources.go @@ -17,6 +17,7 @@ import ( "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/storage" "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/vapp" "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/vdc" + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/vdcg" "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/vm" "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/vrf" ) @@ -51,6 +52,9 @@ func (p *cloudavenueProvider) DataSources(_ context.Context) []func() datasource vdc.NewVDCDataSource, vdc.NewGroupDataSource, + // * VDC GROUP + vdcg.NewVDCGDataSource, + // * VAPP vapp.NewVappDataSource, vapp.NewOrgNetworkDataSource, diff --git a/internal/provider/provider_resources.go b/internal/provider/provider_resources.go index 6dec5190..28def074 100644 --- a/internal/provider/provider_resources.go +++ b/internal/provider/provider_resources.go @@ -16,6 +16,7 @@ import ( "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/vapp" "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/vcda" "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/vdc" + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/vdcg" "github.com/orange-cloudavenue/terraform-provider-cloudavenue/internal/provider/vm" ) @@ -41,6 +42,9 @@ func (p *cloudavenueProvider) Resources(_ context.Context) []func() resource.Res vdc.NewACLResource, vdc.NewGroupResource, + // * VDC Group + vdcg.NewVDCGResource, + // * VCDA vcda.NewVCDAIPResource, diff --git a/internal/provider/vdcg/base.go b/internal/provider/vdcg/base.go new file mode 100644 index 00000000..42d1c6c4 --- /dev/null +++ b/internal/provider/vdcg/base.go @@ -0,0 +1,5 @@ +package vdcg + +const ( + categoryName = "vdcg" +) diff --git a/internal/provider/vdcg/vdcg_datasource.go b/internal/provider/vdcg/vdcg_datasource.go new file mode 100644 index 00000000..823ed5da --- /dev/null +++ b/internal/provider/vdcg/vdcg_datasource.go @@ -0,0 +1,102 @@ +package vdcg + +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/adminorg" +) + +var ( + _ datasource.DataSource = &vdcgDataSource{} + _ datasource.DataSourceWithConfigure = &vdcgDataSource{} +) + +func NewVDCGDataSource() datasource.DataSource { + return &vdcgDataSource{} +} + +type vdcgDataSource struct { + client *client.CloudAvenue + adminOrg adminorg.AdminOrg +} + +func (d *vdcgDataSource) Metadata(ctx context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_" + categoryName +} + +// Init Initializes the resource. +func (d *vdcgDataSource) Init(ctx context.Context, rm *vdcgModel) (diags diag.Diagnostics) { + d.adminOrg, diags = adminorg.Init(d.client) + return +} + +func (d *vdcgDataSource) Schema(ctx context.Context, req datasource.SchemaRequest, resp *datasource.SchemaResponse) { + resp.Schema = vdcgSchema(ctx).GetDataSource(ctx) +} + +func (d *vdcgDataSource) 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 *vdcgDataSource) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) { + defer metrics.New("data.cloudavenue_vdcg", d.client.GetOrgName(), metrics.Read)() + + config := &vdcgModel{} + + // 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 := &vdcgResource{ + client: d.client, + adminOrg: d.adminOrg, + } + + // Read data from the API + data, found, diags := s.read(ctx, config) + if !found { + resp.Diagnostics.AddError("Resource not found", fmt.Sprintf("The VDC Group %s(%s) was not found", config.Name.Get(), config.ID.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/vdcg/vdcg_resource.go b/internal/provider/vdcg/vdcg_resource.go new file mode 100644 index 00000000..7f53815a --- /dev/null +++ b/internal/provider/vdcg/vdcg_resource.go @@ -0,0 +1,317 @@ +package vdcg + +import ( + "context" + "fmt" + + "github.com/vmware/go-vcloud-director/v2/govcd" + + "github.com/hashicorp/terraform-plugin-framework/diag" + "github.com/hashicorp/terraform-plugin-framework/path" + + "github.com/hashicorp/terraform-plugin-framework/resource" + + "github.com/orange-cloudavenue/cloudavenue-sdk-go/pkg/urn" + "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/adminorg" +) + +// Ensure the implementation satisfies the expected interfaces. +var ( + _ resource.Resource = &vdcgResource{} + _ resource.ResourceWithConfigure = &vdcgResource{} + _ resource.ResourceWithImportState = &vdcgResource{} +) + +// NewvdcgResource is a helper function to simplify the provider implementation. +func NewVDCGResource() resource.Resource { + return &vdcgResource{} +} + +// vdcgResource is the resource implementation. +type vdcgResource struct { + client *client.CloudAvenue + adminOrg adminorg.AdminOrg +} + +// Init Initializes the resource. +func (r *vdcgResource) Init(ctx context.Context, rm *vdcgModel) (diags diag.Diagnostics) { + r.adminOrg, diags = adminorg.Init(r.client) + return +} + +// Metadata returns the resource type name. +func (r *vdcgResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { + resp.TypeName = req.ProviderTypeName + "_" + categoryName +} + +// Schema defines the schema for the resource. +func (r *vdcgResource) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { + resp.Schema = vdcgSchema(ctx).GetResource(ctx) +} + +func (r *vdcgResource) 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 *vdcgResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + defer metrics.New("cloudavenue_vdcg", r.client.GetOrgName(), metrics.Create)() + + plan := &vdcgModel{} + + // 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. + */ + + vdcIDs, d := plan.VDCIDs.Get(ctx) + if d.HasError() { + resp.Diagnostics.Append(d...) + return + } + + vdcGroup, err := r.adminOrg.CreateNsxtVdcGroup(plan.Name.Get(), plan.Description.Get(), vdcIDs[0], vdcIDs) + if err != nil { + resp.Diagnostics.AddError("Error creating VDC Group", err.Error()) + return + } + + plan.ID.Set(vdcGroup.VdcGroup.Id) + + // Use generic read function to refresh the state + state, _, d := r.read(ctx, plan) + 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 *vdcgResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + defer metrics.New("cloudavenue_vdcg", r.client.GetOrgName(), metrics.Read)() + + state := &vdcgModel{} + + // 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 *vdcgResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { + defer metrics.New("cloudavenue_vdcg", r.client.GetOrgName(), metrics.Update)() + + var ( + plan = &vdcgModel{} + state = &vdcgModel{} + ) + + // 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 + */ + + vdcIDs, d := plan.VDCIDs.Get(ctx) + if d.HasError() { + resp.Diagnostics.Append(d...) + return + } + + // Here use GetVdcGroupById instead of GetVdcGroupByNameOrID because we want to update the name of VDC Group + vdcGroup, err := r.adminOrg.GetVdcGroupById(state.ID.Get()) + if err != nil { + resp.Diagnostics.AddError("Error reading VDC Group", err.Error()) + return + } + + if _, err := vdcGroup.Update(plan.Name.Get(), plan.Description.Get(), vdcIDs); err != nil { + resp.Diagnostics.AddError("Error updating VDC Group", err.Error()) + return + } + + // Use generic read function to refresh the state + stateRefreshed, found, d := r.read(ctx, plan) + if !found { + resp.Diagnostics.AddError("Resource not found", fmt.Sprintf("Resource with name %s(%s) not found after update.", plan.Name.Get(), plan.ID.Get())) + 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 *vdcgResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + defer metrics.New("cloudavenue_vdcg", r.client.GetOrgName(), metrics.Delete)() + + state := &vdcgModel{} + + // 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 + */ + + vdcGroup, err := r.adminOrg.GetVdcGroupById(state.ID.Get()) + if err != nil { + resp.Diagnostics.AddError("Error reading VDC Group", err.Error()) + return + } + + if err = vdcGroup.Delete(); err != nil { + resp.Diagnostics.AddError("Error deleting VDC Group", err.Error()) + return + } +} + +func (r *vdcgResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { + defer metrics.New("cloudavenue_vdcg", r.client.GetOrgName(), metrics.Import)() + + // id format is vdcGroupIDOrName + + var d diag.Diagnostics + + r.adminOrg, d = adminorg.Init(r.client) + if d.HasError() { + resp.Diagnostics.Append(d...) + return + } + + var ( + vdcGroup *govcd.VdcGroup + err error + ) + + if urn.IsVDCGroup(req.ID) { + vdcGroup, err = r.adminOrg.GetVdcGroupById(req.ID) + } else { + vdcGroup, err = r.adminOrg.GetVdcGroupByName(req.ID) + } + if err != nil { + resp.Diagnostics.AddError("Error retrieving VDC Group", err.Error()) + return + } + + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), vdcGroup.VdcGroup.Id)...) + resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("name"), vdcGroup.VdcGroup.Name)...) +} + +// * CustomFuncs + +// read is a generic read function that can be used by the resource Create, Read and Update functions. +func (r *vdcgResource) read(ctx context.Context, planOrState *vdcgModel) (stateRefreshed *vdcgModel, found bool, diags diag.Diagnostics) { + stateRefreshed = planOrState.Copy() + + /* + Implement the resource read here + */ + + var ( + vdcGroup *govcd.VdcGroup + err error + ) + + if planOrState.ID.IsKnown() { + vdcGroup, err = r.adminOrg.GetVdcGroupById(planOrState.ID.Get()) + } else { + vdcGroup, err = r.adminOrg.GetVdcGroupByName(planOrState.Name.Get()) + } + if err != nil { + if govcd.ContainsNotFound(err) { + return nil, false, nil + } + diags.AddError("Error reading VDC Group", err.Error()) + return nil, true, diags + } + + var vdcIDs []string + for _, vdc := range vdcGroup.VdcGroup.ParticipatingOrgVdcs { + vdcIDs = append(vdcIDs, vdc.VdcRef.ID) + } + + stateRefreshed.ID.Set(vdcGroup.VdcGroup.Id) + stateRefreshed.Name.Set(vdcGroup.VdcGroup.Name) + stateRefreshed.Description.Set(vdcGroup.VdcGroup.Description) + stateRefreshed.Status.Set(vdcGroup.VdcGroup.Status) + stateRefreshed.Type.Set(vdcGroup.VdcGroup.Type) + diags.Append(stateRefreshed.VDCIDs.Set(ctx, vdcIDs)...) + + return stateRefreshed, true, diags +} diff --git a/internal/provider/vdcg/vdcg_schema.go b/internal/provider/vdcg/vdcg_schema.go new file mode 100644 index 00000000..94a745b6 --- /dev/null +++ b/internal/provider/vdcg/vdcg_schema.go @@ -0,0 +1,100 @@ +package vdcg + +import ( + "context" + + "github.com/hashicorp/terraform-plugin-framework/path" + "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-validators/setvalidator" + "github.com/hashicorp/terraform-plugin-framework-validators/stringvalidator" + + superschema "github.com/FrangipaneTeam/terraform-plugin-framework-superschema" + supertypes "github.com/FrangipaneTeam/terraform-plugin-framework-supertypes" +) + +func vdcgSchema(_ context.Context) superschema.Schema { + return superschema.Schema{ + Resource: superschema.SchemaDetails{ + MarkdownDescription: "The `cloudavenue_vdcg` resource allows you to manage a virtual datacenter group.", + }, + DataSource: superschema.SchemaDetails{ + MarkdownDescription: "The `cloudavenue_vdcg` data source allows you to retrieve informations about an existing virtual datacenter group.", + }, + Attributes: map[string]superschema.Attribute{ + "id": superschema.SuperStringAttribute{ + Common: &schemaR.StringAttribute{ + Computed: true, + MarkdownDescription: "The ID of the VDC Group.", + }, + DataSource: &schemaD.StringAttribute{ + Optional: true, + Validators: []validator.String{ + stringvalidator.ExactlyOneOf(path.MatchRoot("name"), path.MatchRoot("id")), + }, + }, + }, + "name": superschema.SuperStringAttribute{ + Common: &schemaR.StringAttribute{ + MarkdownDescription: "The name of the VDC Group.", + }, + Resource: &schemaR.StringAttribute{ + Required: true, + }, + DataSource: &schemaD.StringAttribute{ + Optional: true, + Validators: []validator.String{ + stringvalidator.ExactlyOneOf(path.MatchRoot("name"), path.MatchRoot("id")), + }, + }, + }, + "description": superschema.SuperStringAttribute{ + Common: &schemaR.StringAttribute{ + MarkdownDescription: "The description of the VDC Group.", + }, + Resource: &schemaR.StringAttribute{ + Optional: true, + }, + DataSource: &schemaD.StringAttribute{ + Computed: true, + }, + }, + "vdc_ids": superschema.SuperSetAttributeOf[string]{ + Common: &schemaR.SetAttribute{ + MarkdownDescription: "List of VDC IDs attached to the VDC Group", + ElementType: supertypes.StringType{}, + }, + Resource: &schemaR.SetAttribute{ + Required: true, + Validators: []validator.Set{ + setvalidator.SizeAtLeast(1), + }, + }, + DataSource: &schemaD.SetAttribute{ + Computed: true, + }, + }, + "status": superschema.SuperStringAttribute{ + Common: &schemaR.StringAttribute{ + MarkdownDescription: "The status of the VDC Group.", + Computed: true, + Validators: []validator.String{ + stringvalidator.OneOf("SAVING", "SAVED", "CONFIGURING", "REALIZED", "REALIZATION_FAILED", "DELETING", "DELETE_FAILED", "OBJECT_NOT_FOUND", "UNCONFIGURED"), + }, + }, + }, + "type": superschema.SuperStringAttribute{ + Common: &schemaR.StringAttribute{ + MarkdownDescription: "The type of the VDC Group.", + Computed: true, + Validators: []validator.String{ + stringvalidator.OneOf("LOCAL", "UNIVERSAL"), + }, + }, + }, + }, + } +} diff --git a/internal/provider/vdcg/vdcg_schema_test.go b/internal/provider/vdcg/vdcg_schema_test.go new file mode 100644 index 00000000..60714759 --- /dev/null +++ b/internal/provider/vdcg/vdcg_schema_test.go @@ -0,0 +1,55 @@ +package vdcg_test + +import ( + "context" + "testing" + + 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/vdcg" +) + +// Unit test for the schema of the resource cloudavenue_vdcg. +func TestVDCGResourceSchema(t *testing.T) { + t.Parallel() + + ctx := context.Background() + schemaResponse := &fwresource.SchemaResponse{} + + // Instantiate the resource.Resource and call its Schema method + vdcg.NewVDCGResource().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_vdcg. +func TestVDCGDataSourceSchema(t *testing.T) { + t.Parallel() + + ctx := context.Background() + schemaResponse := &fwdatasource.SchemaResponse{} + + // Instantiate the datasource.Datasource and call its Schema method + vdcg.NewVDCGDataSource().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/vdcg/vdcg_types.go b/internal/provider/vdcg/vdcg_types.go new file mode 100644 index 00000000..bf25cbe5 --- /dev/null +++ b/internal/provider/vdcg/vdcg_types.go @@ -0,0 +1,22 @@ +package vdcg + +import ( + supertypes "github.com/FrangipaneTeam/terraform-plugin-framework-supertypes" + + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/pkg/utils" +) + +type vdcgModel struct { + ID supertypes.StringValue `tfsdk:"id"` + Name supertypes.StringValue `tfsdk:"name"` + Description supertypes.StringValue `tfsdk:"description"` + VDCIDs supertypes.SetValueOf[string] `tfsdk:"vdc_ids"` + Type supertypes.StringValue `tfsdk:"type"` + Status supertypes.StringValue `tfsdk:"status"` +} + +func (rm *vdcgModel) Copy() *vdcgModel { + x := &vdcgModel{} + utils.ModelCopy(rm, x) + return x +} diff --git a/internal/testsacc/acctest_datasources_test.go b/internal/testsacc/acctest_datasources_test.go index eff58c79..7dabb59c 100644 --- a/internal/testsacc/acctest_datasources_test.go +++ b/internal/testsacc/acctest_datasources_test.go @@ -17,6 +17,9 @@ func GetDataSourceConfig() map[testsacc.ResourceName]func() *testsacc.ResourceCo VDCDataSourceName: testsacc.NewResourceConfig(NewVDCDataSourceTest()), VDCGroupDataSourceName: testsacc.NewResourceConfig(NewVDCGroupDataSourceTest()), + // * VDC Group + VDCGDataSourceName: testsacc.NewResourceConfig(NewVDCGDataSourceTest()), + // * Backup BackupDataSourceName: testsacc.NewResourceConfig(NewBackupDataSourceTest()), diff --git a/internal/testsacc/acctest_resources_test.go b/internal/testsacc/acctest_resources_test.go index e2656094..0be4da8f 100644 --- a/internal/testsacc/acctest_resources_test.go +++ b/internal/testsacc/acctest_resources_test.go @@ -13,6 +13,9 @@ func GetResourceConfig() map[testsacc.ResourceName]func() *testsacc.ResourceConf VDCResourceName: testsacc.NewResourceConfig(NewVDCResourceTest()), VDCGroupResourceName: testsacc.NewResourceConfig(NewVDCGroupResourceTest()), + // * VDC Group + VDCGResourceName: testsacc.NewResourceConfig(NewVDCGResourceTest()), + // * VAPP VAppResourceName: testsacc.NewResourceConfig(NewVAppResourceTest()), VAppOrgNetworkResourceName: testsacc.NewResourceConfig(NewVAppOrgNetworkResourceTest()), diff --git a/internal/testsacc/vdc_group_datasource_test.go b/internal/testsacc/vdc_group_datasource_test.go index 04ee8d0c..cff4e2ec 100644 --- a/internal/testsacc/vdc_group_datasource_test.go +++ b/internal/testsacc/vdc_group_datasource_test.go @@ -42,7 +42,7 @@ func (r *VDCGroupDataSource) Tests(ctx context.Context) map[testsacc.TestName]fu data "cloudavenue_vdc_group" "example" { name = cloudavenue_vdc_group.example.name }`, - Checks: GetResourceConfig()[VDCResourceName]().GetDefaultChecks(), + Checks: GetResourceConfig()[VDCGroupResourceName]().GetDefaultChecks(), }, } }, diff --git a/internal/testsacc/vdc_group_resource_test.go b/internal/testsacc/vdc_group_resource_test.go index 498e79b2..8ab84f05 100644 --- a/internal/testsacc/vdc_group_resource_test.go +++ b/internal/testsacc/vdc_group_resource_test.go @@ -13,6 +13,7 @@ import ( var _ testsacc.TestACC = &VDCGroupResource{} const ( + // Deprecated: Use VDCGResourceName instead. VDCGroupResourceName = testsacc.ResourceName("cloudavenue_vdc_group") ) diff --git a/internal/testsacc/vdcg_datasource_test.go b/internal/testsacc/vdcg_datasource_test.go new file mode 100644 index 00000000..c9847120 --- /dev/null +++ b/internal/testsacc/vdcg_datasource_test.go @@ -0,0 +1,57 @@ +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 = &VDCGDataSource{} + +const ( + VDCGDataSourceName = testsacc.ResourceName("data.cloudavenue_vdcg") +) + +type VDCGDataSource struct{} + +func NewVDCGDataSourceTest() testsacc.TestACC { + return &VDCGDataSource{} +} + +// GetResourceName returns the name of the resource. +func (r *VDCGDataSource) GetResourceName() string { + return VDCGDataSourceName.String() +} + +func (r *VDCGDataSource) DependenciesConfig() (resp testsacc.DependenciesConfigResponse) { + resp.Append(GetResourceConfig()[VDCGResourceName]().GetDefaultConfig) + return +} + +func (r *VDCGDataSource) 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_vdcg" "example" { + name = cloudavenue_vdcg.example.name + }`, + Checks: GetResourceConfig()[VDCGResourceName]().GetDefaultChecks(), + }, + } + }, + } +} + +func TestAccVDCGDataSource(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { TestAccPreCheck(t) }, + ProtoV6ProviderFactories: TestAccProtoV6ProviderFactories, + Steps: testsacc.GenerateTests(&VDCGDataSource{}), + }) +} diff --git a/internal/testsacc/vdcg_resource_test.go b/internal/testsacc/vdcg_resource_test.go new file mode 100644 index 00000000..753c3103 --- /dev/null +++ b/internal/testsacc/vdcg_resource_test.go @@ -0,0 +1,122 @@ +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 = &VDCGResource{} + +const ( + VDCGResourceName = testsacc.ResourceName("cloudavenue_vdcg") +) + +type VDCGResource struct{} + +func NewVDCGResourceTest() testsacc.TestACC { + return &VDCGResource{} +} + +// GetResourceName returns the name of the resource. +func (r *VDCGResource) GetResourceName() string { + return VDCGResourceName.String() +} + +func (r *VDCGResource) DependenciesConfig() (resp testsacc.DependenciesConfigResponse) { + resp.Append(GetResourceConfig()[VDCResourceName]().GetSpecificConfig("example_vdc_group_1")) + resp.Append(GetResourceConfig()[VDCResourceName]().GetSpecificConfig("example_vdc_group_2")) + return +} + +func (r *VDCGResource) 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{ + // * Test One (example) + "example": func(_ context.Context, resourceName string) testsacc.Test { + return testsacc.Test{ + CommonChecks: []resource.TestCheckFunc{ + resource.TestCheckResourceAttrWith(resourceName, "id", urn.TestIsType(urn.VDCGroup)), + resource.TestCheckResourceAttrSet(resourceName, "status"), + resource.TestCheckResourceAttrSet(resourceName, "type"), + }, + // ! Create testing + Create: testsacc.TFConfig{ + TFConfig: testsacc.GenerateFromTemplate(resourceName, ` + resource "cloudavenue_vdcg" "example" { + name = {{ generate . "name" }} + description = {{ generate . "description" }} + vdc_ids = [ + cloudavenue_vdc.example_vdc_group_1.id, + ] + }`), + Checks: []resource.TestCheckFunc{ + resource.TestCheckResourceAttr(resourceName, "name", testsacc.GetValueFromTemplate(resourceName, "name")), + resource.TestCheckResourceAttr(resourceName, "description", testsacc.GetValueFromTemplate(resourceName, "description")), + resource.TestCheckResourceAttr(resourceName, "vdc_ids.#", "1"), + }, + }, + // ! Updates testing + Updates: []testsacc.TFConfig{ + // Update description and add a new vdc_id + { + TFConfig: testsacc.GenerateFromTemplate(resourceName, ` + resource "cloudavenue_vdcg" "example" { + name = {{ get . "name" }} + description = {{ generate . "description" }} + vdc_ids = [ + cloudavenue_vdc.example_vdc_group_1.id, + cloudavenue_vdc.example_vdc_group_2.id, + ] + }`), + Checks: []resource.TestCheckFunc{ + resource.TestCheckResourceAttr(resourceName, "description", testsacc.GetValueFromTemplate(resourceName, "description")), + resource.TestCheckResourceAttr(resourceName, "name", testsacc.GetValueFromTemplate(resourceName, "name")), + resource.TestCheckResourceAttr(resourceName, "vdc_ids.#", "2"), + }, + }, + { + TFConfig: testsacc.GenerateFromTemplate(resourceName, ` + resource "cloudavenue_vdcg" "example" { + name = {{ generate . "name" }} + description = {{ get . "description" }} + vdc_ids = [ + cloudavenue_vdc.example_vdc_group_1.id, + cloudavenue_vdc.example_vdc_group_2.id, + ] + }`), + Checks: []resource.TestCheckFunc{ + resource.TestCheckResourceAttr(resourceName, "description", testsacc.GetValueFromTemplate(resourceName, "description")), + resource.TestCheckResourceAttr(resourceName, "name", testsacc.GetValueFromTemplate(resourceName, "name")), + resource.TestCheckResourceAttr(resourceName, "vdc_ids.#", "2"), + }, + }, + }, + // ! Imports testing + Imports: []testsacc.TFImport{ + { + ImportStateID: testsacc.GetValueFromTemplate(resourceName, "name"), + ImportState: true, + ImportStateVerify: true, + }, + { + ImportStateID: testsacc.GetValueFromTemplate(resourceName, "id"), + ImportState: true, + ImportStateVerify: true, + }, + }, + } + }, + } +} + +func TestAccVDCGResource(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { TestAccPreCheck(t) }, + ProtoV6ProviderFactories: TestAccProtoV6ProviderFactories, + Steps: testsacc.GenerateTests(&VDCGResource{}), + }) +} diff --git a/templates/data-sources/vdcg.md.tmpl b/templates/data-sources/vdcg.md.tmpl new file mode 100644 index 00000000..e54f2e8b --- /dev/null +++ b/templates/data-sources/vdcg.md.tmpl @@ -0,0 +1,18 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "vDC Group (Virtual Datacenter Group)" +description: |- +{{ .Description | plainmarkdown | trimspace | prefixlines " " }} +--- + +# {{.Name}} ({{.Type}}) + +{{ .Description | trimspace }} + +{{ if .HasExample -}} +## Example Usage + +{{ tffile .ExampleFile }} +{{- end }} + +{{ .SchemaMarkdown | trimspace }} diff --git a/templates/resources/vdcg.md.tmpl b/templates/resources/vdcg.md.tmpl new file mode 100644 index 00000000..d6abbc66 --- /dev/null +++ b/templates/resources/vdcg.md.tmpl @@ -0,0 +1,25 @@ +--- +page_title: "{{.Name}} {{.Type}} - {{.ProviderName}}" +subcategory: "vDC Group (Virtual Datacenter Group)" +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