internal/provider/application_compute_vm_resource.go (227 lines of code) (raw):
package provider
import (
"context"
"fmt"
"github.com/antihax/optional"
backupdr "github.com/umeshkumhar/backupdr-client"
"github.com/hashicorp/terraform-plugin-framework/attr"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/types"
)
// Ensure the implementation satisfies the expected interfaces.
var (
_ resource.Resource = &applicationComputeVMsResource{}
_ resource.ResourceWithConfigure = &applicationComputeVMsResource{}
_ resource.ResourceWithImportState = &applicationComputeVMsResource{}
)
// NewApplicationComputeVMsResource to create vCenter Host
func NewApplicationComputeVMsResource() resource.Resource {
return &applicationComputeVMsResource{}
}
// applicationComputeVMsResource is the resource implementation.
type applicationComputeVMsResource struct {
client *backupdr.APIClient
authCtx context.Context
}
// tf go model
type applicationComputeVMsResourceModel struct {
CloudCredential types.String `tfsdk:"cloudcredential"`
ApplianceClusterID types.String `tfsdk:"appliance_clusterid"`
VMIds []types.String `tfsdk:"vmids"`
Region types.String `tfsdk:"region"`
ProjectID types.String `tfsdk:"projectid"`
Status types.String `tfsdk:"status"`
Applications types.List `tfsdk:"applications"`
}
// Metadata returns the resource type name.
func (r *applicationComputeVMsResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_application_compute_vm"
}
// Schema defines the schema for the resource.
func (r *applicationComputeVMsResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
MarkdownDescription: "You can use this resource to onboard GCE VMs as an application into the Backup and DR Service. After you onboard the application, you can perform backup or restore operations.",
Attributes: map[string]schema.Attribute{
"cloudcredential": schema.StringAttribute{
Required: true,
MarkdownDescription: "Provide the ID of the cloud credential.",
},
"appliance_clusterid": schema.StringAttribute{
Required: true,
MarkdownDescription: "Provide the ID of the backup/recovery appliance.",
},
"projectid": schema.StringAttribute{
Required: true,
MarkdownDescription: "Provide the ID of the project in which the resource belongs. If it is not provided, the provider project is used.",
},
"region": schema.StringAttribute{
Required: true,
MarkdownDescription: "Provide the region to create the cloud credential.",
},
"vmids": schema.ListAttribute{
Required: true,
ElementType: types.StringType,
MarkdownDescription: "Provide the list of GCP instance IDs.",
},
"status": schema.StringAttribute{
Computed: true,
MarkdownDescription: "It displays the status of the request.",
},
"applications": schema.ListAttribute{
Computed: true,
ElementType: types.StringType,
MarkdownDescription: "It displays the list of Application IDs.",
},
},
}
}
// Configure adds the provider configured client to the resource.
func (r *applicationComputeVMsResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
if req.ProviderData == nil {
return
}
r.client = req.ProviderData.(*backupdrProvider).client
r.authCtx = req.ProviderData.(*backupdrProvider).authCtx
}
// Create a new resource.
func (r *applicationComputeVMsResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
// Retrieve values from plan
var plan applicationComputeVMsResourceModel
var listVMs []string
diags := req.Plan.Get(ctx, &plan)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
for _, vm := range plan.VMIds {
listVMs = append(listVMs, vm.ValueString())
}
reqCloudAddVMs := backupdr.CloudVmDiscoveryRest{
Region: plan.Region.ValueString(),
ProjectId: plan.ProjectID.ValueString(),
Vmids: listVMs,
ListOnly: false,
}
reqCloudAddVMs.Cluster = &backupdr.ClusterRest{Clusterid: plan.ApplianceClusterID.ValueString()}
// Generate API request body from plan
reqBody := backupdr.DefaultApiAddVmOpts{
Body: optional.NewInterface(reqCloudAddVMs),
}
// Add new Cloud VMs
respObject, err := r.client.DefaultApi.AddVm(r.authCtx, plan.CloudCredential.ValueString(), &reqBody)
if err != nil {
resp.Diagnostics.AddError(
"Error adding Cloud VM",
"Could not add Cloud VMs, unexpected error: "+err.Error(),
)
return
}
if respObject.StatusCode == 200 || respObject.StatusCode == 204 {
// Map response body to schema and populate Computed attribute values
plan.Status = types.StringValue(respObject.Status)
applicationIDs := make([]attr.Value, 0)
for _, application := range listVMs {
filter := backupdr.ApplicationApiListApplicationsOpts{
Filter: optional.NewString(fmt.Sprintf("uniquename:==%s", application)),
}
// fetch application ID with filter
lsApps, res, err := r.client.ApplicationApi.ListApplications(r.authCtx, &filter)
if err != nil {
resp.Diagnostics.AddError(
"Error listing applications",
"Could not list applications, unexpected error: "+res.Status+" : "+err.Error(),
)
return
}
// capture the id of filtered application
if lsApps.Items != nil && len(lsApps.Items) > 0 {
applicationIDs = append(applicationIDs, types.StringValue(lsApps.Items[0].Id))
}
}
if len(applicationIDs) > 0 {
plan.Applications, diags = types.ListValue(types.StringType, applicationIDs)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
} else {
plan.Status = types.StringValue(respObject.Status)
resp.Diagnostics.AddError(
"Error adding Cloud VM",
"Could not add Cloud VMs, unexpected error: "+err.Error(),
)
return
}
// Set state to fully populated data
diags = resp.State.Set(ctx, plan)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
// Read resource information.
func (r *applicationComputeVMsResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
// Does not support Read
return
}
func (r *applicationComputeVMsResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
// Retrieve values from plan
var plan applicationComputeVMsResourceModel
var listVMs []string
diags := req.Plan.Get(ctx, &plan)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
for _, vm := range plan.VMIds {
listVMs = append(listVMs, vm.ValueString())
}
reqCloudAddVMs := backupdr.CloudVmDiscoveryRest{
Region: plan.Region.ValueString(),
ProjectId: plan.ProjectID.ValueString(),
Vmids: listVMs,
}
reqCloudAddVMs.Cluster = &backupdr.ClusterRest{Clusterid: plan.ApplianceClusterID.ValueString()}
// Generate API request body from plan
reqBody := backupdr.DefaultApiAddVmOpts{
Body: optional.NewInterface(reqCloudAddVMs),
}
// Add new Cloud VMs
respObject, err := r.client.DefaultApi.AddVm(r.authCtx, plan.CloudCredential.ValueString(), &reqBody)
if err != nil {
resp.Diagnostics.AddError(
"Error updating Cloud VM",
"Could not update Cloud VMs, unexpected error: "+err.Error(),
)
return
}
if respObject.StatusCode == 200 || respObject.StatusCode == 204 {
// Map response body to schema and populate Computed attribute values
plan.Status = types.StringValue(respObject.Status)
applicationIDs := make([]attr.Value, 0)
applicationIDs = append(applicationIDs, plan.Applications.Elements()...)
for _, application := range listVMs {
filter := backupdr.ApplicationApiListApplicationsOpts{
Filter: optional.NewString(fmt.Sprintf("uniquename:==%s", application)),
}
// fetch application ID with filter
lsApps, res, err := r.client.ApplicationApi.ListApplications(r.authCtx, &filter)
if err != nil {
resp.Diagnostics.AddError(
"Error listing applications",
"Could not list applications, unexpected error: "+res.Status+" : "+err.Error(),
)
return
}
// capture the id of filtered application
if lsApps.Items != nil && len(lsApps.Items) > 0 {
applicationIDs = append(applicationIDs, types.StringValue(lsApps.Items[0].Id))
}
}
if len(applicationIDs) > 0 {
plan.Applications, diags = types.ListValue(types.StringType, applicationIDs)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
} else {
plan.Status = types.StringValue(respObject.Status)
resp.Diagnostics.AddError(
"Error updating Cloud VM",
"Could not update Cloud VMs, unexpected error: "+err.Error(),
)
return
}
diags = resp.State.Set(ctx, plan)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
func (r *applicationComputeVMsResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
// Does not support Deletion of VM.. TODO. implement Application deletion.
return
}
func (r *applicationComputeVMsResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
// Retrieve import ID and save to id attribute
resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
}