From 13aa29770d2102c821cd23319a592f7f64cd36c5 Mon Sep 17 00:00:00 2001 From: David MICHENEAU Date: Fri, 29 Sep 2023 10:05:37 +0200 Subject: [PATCH] feat: Add resource `cloudavenue_backup` --- internal/provider/backup/backup_resource.go | 110 +++++++++++++----- internal/provider/backup/backup_schema.go | 23 ++-- .../provider/backup/backup_schema_test.go | 2 +- internal/provider/backup/backup_types.go | 43 ++++++- internal/provider/backup/base.go | 4 +- 5 files changed, 132 insertions(+), 50 deletions(-) diff --git a/internal/provider/backup/backup_resource.go b/internal/provider/backup/backup_resource.go index 23d1f0568..7b0b18870 100644 --- a/internal/provider/backup/backup_resource.go +++ b/internal/provider/backup/backup_resource.go @@ -1,9 +1,11 @@ -package netbackup +package backup import ( "context" "fmt" + "github.com/orange-cloudavenue/netbackup-sdk-go/netbackupclient" + "github.com/hashicorp/terraform-plugin-framework/diag" "github.com/hashicorp/terraform-plugin-framework/resource" @@ -13,36 +15,35 @@ import ( // Ensure the implementation satisfies the expected interfaces. var ( - _ resource.Resource = &BackupResource{} - _ resource.ResourceWithConfigure = &BackupResource{} - _ resource.ResourceWithImportState = &BackupResource{} - // _ resource.ResourceWithModifyPlan = &BackupResource{} - // _ resource.ResourceWithUpgradeState = &BackupResource{} - // _ resource.ResourceWithValidateConfig = &BackupResource{}. + _ resource.Resource = &backupResource{} + _ resource.ResourceWithConfigure = &backupResource{} + _ resource.ResourceWithImportState = &backupResource{} + // _ resource.ResourceWithModifyPlan = &backupResource{} + // _ resource.ResourceWithUpgradeState = &backupResource{} + // _ resource.ResourceWithValidateConfig = &backupResource{}. ) -// NewBackupResource is a helper function to simplify the provider implementation. +// NewbackupResource is a helper function to simplify the provider implementation. func NewBackupResource() resource.Resource { - return &BackupResource{} + return &backupResource{} } -// BackupResource is the resource implementation. -type BackupResource struct { +// backupResource is the resource implementation. +type backupResource struct { client *client.CloudAvenue - // Uncomment the following lines if you need to access the resource's. // org org.Org - // vdc vdc.VDC + // vdc vdc.VDC // vapp vapp.VAPP } // If the resource don't have same schema/structure as the data source, you can use the following code: -// type BackupResourceModel struct { +// type backupResourceModel struct { // ID types.String `tfsdk:"id"` // } // Init Initializes the resource. -func (r *BackupResource) Init(ctx context.Context, rm *BackupModel) (diags diag.Diagnostics) { +func (r *backupResource) Init(ctx context.Context, rm *backupModel) (diags diag.Diagnostics) { // Uncomment the following lines if you need to access to the Org // r.org, diags = org.Init(r.client) // if diags.HasError() { @@ -62,16 +63,16 @@ func (r *BackupResource) Init(ctx context.Context, rm *BackupModel) (diags diag. } // Metadata returns the resource type name. -func (r *BackupResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { +func (r *backupResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { resp.TypeName = req.ProviderTypeName + "_" + categoryName } // Schema defines the schema for the resource. -func (r *BackupResource) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { +func (r *backupResource) Schema(ctx context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = backupSchema(ctx).GetResource(ctx) } -func (r *BackupResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { +func (r *backupResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { // Prevent panic if the provider has not been configured. if req.ProviderData == nil { return @@ -89,8 +90,8 @@ func (r *BackupResource) Configure(ctx context.Context, req resource.ConfigureRe } // Create creates the resource and sets the initial Terraform state. -func (r *BackupResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { - plan := &BackupModel{} +func (r *backupResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { + plan := &backupModel{} // Retrieve values from plan resp.Diagnostics.Append(req.Plan.Get(ctx, plan)...) @@ -108,20 +109,65 @@ func (r *BackupResource) Create(ctx context.Context, req resource.CreateRequest, Implement the resource creation logic here. */ - // Use generic read function to refresh the state - state, _, d := r.read(ctx, plan) + // Define if the target is a VDC ID or a VDC Name + var nameOrID string + if plan.TargetID.IsNull() { + nameOrID = plan.TargetName.Get() + } else { + nameOrID = plan.TargetID.Get() + } + + // for policies extract values from the plan + policies, d := plan.GetPolicies(ctx) if d.HasError() { resp.Diagnostics.Append(d...) return } + // switch on the type of the backup + switch plan.Type.Get() { + case "vdc": + vdc, err := r.client.NetBackupClient.VCloud.GetVdcByNameOrIdentifier(nameOrID) + if err != nil { + resp.Diagnostics.AddError("Error getting vCloud Director Virtual Data Center", err.Error()) + return + } + // for each policy, protect the VDC + for _, policy := range policies { + if policy.Enabled.Get() { + job, err := vdc.Protect(netbackupclient.ProtectUnprotectRequest{ + ProtectionLevelName: policy.PolicyName.Get(), + ProtectionLevelID: policy.PolicyID.GetIntPtr(), + }) + if err != nil { + resp.Diagnostics.AddError("Error protecting vCloud Director Virtual Data Center", err.Error()) + return + } + if err := job.Wait(1, 15); err != nil { + resp.Diagnostics.AddError("Error waiting for job", err.Error()) + } + } + } + case "vapp": + // TODO: Create a backup for a VAPP + case "vm": + // TODO: Create a backup for a VM + } + + // 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)...) + resp.Diagnostics.Append(resp.State.Set(ctx, plan)...) } // Read refreshes the Terraform state with the latest data. -func (r *BackupResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { - state := &BackupModel{} +func (r *backupResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { + state := &backupModel{} // Get current state resp.Diagnostics.Append(req.State.Get(ctx, state)...) @@ -151,10 +197,10 @@ func (r *BackupResource) Read(ctx context.Context, req resource.ReadRequest, res } // Update updates the resource and sets the updated Terraform state on success. -func (r *BackupResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { +func (r *backupResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { var ( - plan = &BackupModel{} - state = &BackupModel{} + plan = &backupModel{} + state = &backupModel{} ) // Get current plan and state @@ -186,8 +232,8 @@ func (r *BackupResource) Update(ctx context.Context, req resource.UpdateRequest, } // Delete deletes the resource and removes the Terraform state on success. -func (r *BackupResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { - state := &BackupModel{} +func (r *backupResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { + state := &backupModel{} // Get current state resp.Diagnostics.Append(req.State.Get(ctx, state)...) @@ -206,7 +252,7 @@ func (r *BackupResource) Delete(ctx context.Context, req resource.DeleteRequest, */ } -func (r *BackupResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { +func (r *backupResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { // * Import basic // resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) @@ -228,7 +274,7 @@ func (r *BackupResource) ImportState(ctx context.Context, req resource.ImportSta // * CustomFuncs // read is a generic read function that can be used by the resource Create, Read and Update functions. -func (r *BackupResource) read(ctx context.Context, planOrState *BackupModel) (stateRefreshed *BackupModel, found bool, diags diag.Diagnostics) { +func (r *backupResource) read(ctx context.Context, planOrState *backupModel) (stateRefreshed *backupModel, found bool, diags diag.Diagnostics) { // TODO : Remove the comment line after you have run the types generator // stateRefreshed is commented because the Copy function is not before run the types generator // stateRefreshed = planOrState.Copy() diff --git a/internal/provider/backup/backup_schema.go b/internal/provider/backup/backup_schema.go index 4db8def06..5c7dca673 100644 --- a/internal/provider/backup/backup_schema.go +++ b/internal/provider/backup/backup_schema.go @@ -1,4 +1,4 @@ -package netbackup +package backup import ( "context" @@ -94,7 +94,7 @@ func backupSchema(_ context.Context) superschema.Schema { Resource: &schemaR.StringAttribute{ Required: true, Validators: []validator.String{ - stringvalidator.OneOf("vdc", "vapp", "vm"), + stringvalidator.OneOf("vdc", "VDC", "VAPP", "vapp", "VM", "vm"), }, }, DataSource: &schemaD.StringAttribute{ @@ -147,16 +147,17 @@ func backupSchema(_ context.Context) superschema.Schema { Computed: true, }, Attributes: map[string]superschema.Attribute{ - "policy_id": superschema.SuperStringAttribute{ - Common: &schemaR.StringAttribute{ + "policy_id": superschema.SuperInt64Attribute{ + Common: &schemaR.Int64Attribute{ MarkdownDescription: "The ID of the backup policy.", Computed: true, }, - Resource: &schemaR.StringAttribute{ + Resource: &schemaR.Int64Attribute{ Optional: true, - Validators: []validator.String{ - stringvalidator.ExactlyOneOf(path.MatchRoot("policy_id"), path.MatchRoot("policy_name")), - }, + // Validators: []validator.Int64{ + // int64validator.NullIfAttributeIsSet(path.MatchRoot("policy_name")), + // // int64validator.ExactlyOneOf(path.MatchRoot("policy_id"), path.MatchRoot("policy_name")), + // }, }, }, "policy_name": superschema.SuperStringAttribute{ @@ -166,9 +167,9 @@ func backupSchema(_ context.Context) superschema.Schema { }, Resource: &schemaR.StringAttribute{ Optional: true, - Validators: []validator.String{ - stringvalidator.ExactlyOneOf(path.MatchRoot("policy_id"), path.MatchRoot("policy_name")), - }, + // Validators: []validator.String{ + // stringvalidator.ExactlyOneOf(path.MatchRoot("policy_id"), path.MatchRoot("policy_name")), + // }, }, }, "enabled": superschema.SuperBoolAttribute{ diff --git a/internal/provider/backup/backup_schema_test.go b/internal/provider/backup/backup_schema_test.go index df63dfeec..d639121f2 100644 --- a/internal/provider/backup/backup_schema_test.go +++ b/internal/provider/backup/backup_schema_test.go @@ -1,4 +1,4 @@ -package netbackup_test +package backup_test import ( "context" diff --git a/internal/provider/backup/backup_types.go b/internal/provider/backup/backup_types.go index 83aa24351..1564620dc 100644 --- a/internal/provider/backup/backup_types.go +++ b/internal/provider/backup/backup_types.go @@ -1,7 +1,42 @@ -package netbackup +package backup -import "github.com/hashicorp/terraform-plugin-framework/types" +import ( + "context" -type BackupModel struct { - ID types.String `tfsdk:"id"` + "github.com/hashicorp/terraform-plugin-framework/diag" + + supertypes "github.com/FrangipaneTeam/terraform-plugin-framework-supertypes" + + "github.com/orange-cloudavenue/terraform-provider-cloudavenue/pkg/utils" +) + +type backupModel struct { + ID supertypes.StringValue `tfsdk:"id"` + Policies supertypes.SetNestedValue `tfsdk:"policies"` + TargetID supertypes.StringValue `tfsdk:"target_id"` + TargetName supertypes.StringValue `tfsdk:"target_name"` + Type supertypes.StringValue `tfsdk:"type"` +} + +// * Policies. +type backupModelPolicies []backupModelPolicy + +// * Policies. +type backupModelPolicy struct { + Enabled supertypes.BoolValue `tfsdk:"enabled"` + PolicyID supertypes.Int64Value `tfsdk:"policy_id"` + PolicyName supertypes.StringValue `tfsdk:"policy_name"` +} + +func (rm *backupModel) Copy() *backupModel { + x := &backupModel{} + utils.ModelCopy(rm, x) + return x +} + +// GetPolicies returns the value of the Policies field. +func (rm *backupModel) GetPolicies(ctx context.Context) (values backupModelPolicies, diags diag.Diagnostics) { + values = make(backupModelPolicies, 0) + d := rm.Policies.Get(ctx, &values, false) + return values, d } diff --git a/internal/provider/backup/base.go b/internal/provider/backup/base.go index d9f01b9ee..7d25f2bb0 100644 --- a/internal/provider/backup/base.go +++ b/internal/provider/backup/base.go @@ -1,5 +1,5 @@ -package netbackup +package backup const ( - categoryName = "netbackup" + categoryName = "backup" )