internal/provider/host_resource.go (432 lines of code) (raw):
package provider
import (
"context"
"github.com/antihax/optional"
backupdr "github.com/umeshkumhar/backupdr-client"
"github.com/hashicorp/terraform-plugin-framework/path"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/types"
)
// Ensure the implementation satisfies the expected interfaces.
var (
_ resource.Resource = &hostResource{}
_ resource.ResourceWithConfigure = &hostResource{}
_ resource.ResourceWithImportState = &hostResource{}
)
// NewHostResource to create vCenter Host
func NewHostResource() resource.Resource {
return &hostResource{}
}
// hostResource is the resource implementation.
type hostResource struct {
client *backupdr.APIClient
authCtx context.Context
}
// Metadata returns the resource type name.
func (r *hostResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_host"
}
// Schema defines the schema for the resource.
func (r *hostResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
MarkdownDescription: "Manages an vCenter Host.",
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
MarkdownDescription: "It displays the unique application ID of this resource.",
},
"href": schema.StringAttribute{
Computed: true,
MarkdownDescription: "It displays the API URI for host.",
},
"friendlypath": schema.StringAttribute{
Optional: true,
MarkdownDescription: "Provide the FQDN or alias name for vCenter address.",
},
"hostname": schema.StringAttribute{
Required: true,
MarkdownDescription: "Provide the fully qualified domain name of the vCenter host.",
},
"hosttype": schema.StringAttribute{
Required: true,
MarkdownDescription: "Provide the host type as vCenter.",
},
"ipaddress": schema.StringAttribute{
Required: true,
MarkdownDescription: "Provide the IP address of the host.",
},
"autoupgrade": schema.StringAttribute{
Computed: true,
MarkdownDescription: "It displays the autoupgrade values.",
},
"cert_revoked": schema.BoolAttribute{
Computed: true,
MarkdownDescription: "It displays the certificate revoked values as true or false.",
},
"clusterid": schema.StringAttribute{
Computed: true,
MarkdownDescription: "It displays the cluster ID.",
},
"dbauthentication": schema.BoolAttribute{
Computed: true,
MarkdownDescription: "This is not applicable for vCenter hosts.",
},
"diskpref": schema.StringAttribute{
Computed: true,
MarkdownDescription: "This is not applicable for vCenter hosts.",
},
"hasagent": schema.BoolAttribute{
Computed: true,
MarkdownDescription: "This is not applicable for vCenter hosts.",
},
"isclusternode": schema.BoolAttribute{
Computed: true,
MarkdownDescription: "It displays if this is a cluster node or not - true or false.",
},
"isshadowhost": schema.BoolAttribute{
Computed: true,
MarkdownDescription: "It displays if this is a shadow host or not - true or false.",
},
"isclusterhost": schema.BoolAttribute{
Computed: true,
MarkdownDescription: "It displays if this is a cluster host or not - true or false.",
},
"isesxhost": schema.BoolAttribute{
Computed: true,
MarkdownDescription: "It displays if this is a ESXi host type or not - true or false.",
},
"isproxyhost": schema.BoolAttribute{
Computed: true,
MarkdownDescription: "It displays if this is a proxy host or not - true or false.",
},
"isvcenterhost": schema.BoolAttribute{
Computed: true,
MarkdownDescription: "It displays if this is a vCenter host or not - true or false.",
},
"isvm": schema.BoolAttribute{
Computed: true,
MarkdownDescription: "It displays if this is a VM host type or not - true or false.",
},
"maxjobs": schema.Int64Attribute{
Computed: true,
MarkdownDescription: "It displays the maximum number of concurrent jobs.",
},
"modifydate": schema.Int64Attribute{
Computed: true,
MarkdownDescription: "It displays the last modified using epoch format.",
},
"multiregion": schema.StringAttribute{
Optional: true,
MarkdownDescription: "Provide the region or regions where the host is created.",
},
"name": schema.StringAttribute{
Computed: true,
MarkdownDescription: "It displays the name of the host.",
},
"originalhostid": schema.StringAttribute{
Computed: true,
MarkdownDescription: "It displays the host ID on the backup/recovery appliance.",
},
"ostype_special": schema.StringAttribute{
Optional: true,
MarkdownDescription: "Provide the OS type for the host.",
},
"pki_state": schema.StringAttribute{
Computed: true,
MarkdownDescription: "It is used for VMs with agents.",
},
"sourcecluster": schema.StringAttribute{
Computed: true,
MarkdownDescription: "It displays the appliance ID where host is added.",
},
"srcid": schema.StringAttribute{
Computed: true,
MarkdownDescription: "It displays the applications ID on the appliance.",
},
"svcname": schema.StringAttribute{
Computed: true,
MarkdownDescription: "It displays the host storage naming convention for backup appliance API.",
},
"transport": schema.StringAttribute{
Computed: true,
MarkdownDescription: "It displays the storage transport mode - nfs or san (nfs is default for Google cloud VMware Engine).",
},
"uniquename": schema.StringAttribute{
Computed: true,
MarkdownDescription: "It displays the vCenter uuid - The unique vm identity as per vcenter or esxi host.",
},
"zone": schema.StringAttribute{
Computed: true,
MarkdownDescription: "It displays the zone used for VMs.",
},
"alternateip": schema.ListAttribute{
Optional: true,
ElementType: types.StringType,
MarkdownDescription: "Provide the alternate IP address of the host.",
},
"hypervisoragent": schema.SingleNestedAttribute{
Optional: true,
Attributes: map[string]schema.Attribute{
"username": schema.StringAttribute{
Required: true,
MarkdownDescription: "Provide the username of the vCenter host. Ideally this should be a solution-user-XX@gve.local account, rather than CloudOwner account.",
},
"password": schema.StringAttribute{
Optional: true,
Sensitive: true,
MarkdownDescription: "Provide the password of the vCenter host.",
},
"haspassword": schema.BoolAttribute{
Computed: true,
MarkdownDescription: "It displays true or false if the vCenter host has a password or not.",
},
"hasalternatekey": schema.BoolAttribute{
Computed: true,
MarkdownDescription: "It displays true or false if the vCenter host has an alternate key or not.",
},
"agenttype": schema.StringAttribute{
Computed: true,
MarkdownDescription: "It displays the type of agent",
},
},
},
"appliance_clusterid": schema.StringAttribute{
Required: true,
MarkdownDescription: "Provide the appliance cluster ID that host is added to.",
},
// "sources": schema.ListNestedAttribute{
// Optional: true,
// NestedObject: schema.NestedAttributeObject{
// Attributes: map[string]schema.Attribute{
// "id": schema.StringAttribute{
// Computed: true,
// },
// "href": schema.StringAttribute{
// Computed: true,
// },
// "clusterid": schema.StringAttribute{
// Required: true,
// },
// },
// },
// },
// "agents": schema.ListNestedAttribute{
// Optional: true,
// NestedObject: schema.NestedAttributeObject{
// Attributes: map[string]schema.Attribute{
// "agenttype": schema.StringAttribute{
// Computed: true,
// },
// "hasalternatekey": schema.BoolAttribute{
// Computed: true,
// },
// "haspassword": schema.BoolAttribute{
// Computed: true,
// },
// "password": schema.StringAttribute{
// Computed: true,
// Optional: true,
// Sensitive: true,
// },
// "username": schema.StringAttribute{
// Computed: true,
// },
// },
// },
// },
// "esxlist": schema.ListNestedAttribute{
// Computed: true,
// NestedObject: schema.NestedAttributeObject{
// Attributes: map[string]schema.Attribute{
// "id": schema.StringAttribute{
// Computed: true,
// },
// "href": schema.StringAttribute{
// Computed: true,
// },
// "clusterid": schema.StringAttribute{
// Computed: true,
// },
// },
// },
// },
},
}
}
// Configure adds the provider configured client to the resource.
func (r *hostResource) 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 *hostResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
// Retrieve values from plan
var plan vcenterHostRest
diags := req.Plan.Get(ctx, &plan)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
reqVcenterHost := backupdr.HostRest{
Hostname: plan.Hostname.ValueString(),
Hosttype: plan.Hosttype.ValueString(),
Friendlypath: plan.Friendlypath.ValueString(),
Ipaddress: plan.Ipaddress.ValueString(),
}
if plan.Hypervisoragent != nil {
reqVcenterHost.Hypervisoragent = &backupdr.AgentRest{
Username: plan.Hypervisoragent.Username.ValueString(),
Password: plan.Hypervisoragent.Password.ValueString(),
}
}
reqVcenterHost.Sources = append(reqVcenterHost.Sources, backupdr.HostRest{
Clusterid: plan.ApplianceClusterID.ValueString(),
})
// Generate API request body from plan
reqBody := backupdr.HostApiCreateHostOpts{
Body: optional.NewInterface(reqVcenterHost),
}
// Create new vCenter Host
respObject, _, err := r.client.HostApi.CreateHost(r.authCtx, &reqBody)
if err != nil {
resp.Diagnostics.AddError(
"Error creating vCenter Host",
"Could not create vCenter Host, unexpected error: "+err.Error(),
)
return
}
// Map response body to schema and populate Computed attribute values
plan.ID = types.StringValue(respObject.Id)
plan.Href = types.StringValue(respObject.Href)
// plan.Agents = types.BoolValue(respObject.Stale)
plan.Modifydate = types.Int64Value(respObject.Modifydate)
plan.Autoupgrade = types.StringValue(respObject.Autoupgrade)
plan.CertRevoked = types.BoolValue(respObject.CertRevoked)
plan.Clusterid = types.StringValue(respObject.Clusterid)
plan.Dbauthentication = types.BoolValue(respObject.Dbauthentication)
plan.Diskpref = types.StringValue(respObject.Diskpref)
plan.Friendlypath = types.StringValue(respObject.Friendlypath)
plan.Hasagent = types.BoolValue(respObject.Hasagent)
plan.Hostname = types.StringValue(respObject.Hostname)
plan.Hosttype = types.StringValue(respObject.Hosttype)
plan.Ipaddress = types.StringValue(respObject.Ipaddress)
plan.IsClusterNode = types.BoolValue(respObject.IsClusterNode)
plan.Isclusterhost = types.BoolValue(respObject.Isclusterhost)
plan.IsShadowHost = types.BoolValue(respObject.IsShadowHost)
plan.Isesxhost = types.BoolValue(respObject.Isesxhost)
plan.Isproxyhost = types.BoolValue(respObject.Isproxyhost)
plan.Isvcenterhost = types.BoolValue(respObject.Isvcenterhost)
plan.Isvm = types.BoolValue(respObject.Isvm)
plan.Maxjobs = types.Int64Value(int64(respObject.Maxjobs))
plan.Name = types.StringValue(respObject.Name)
plan.Originalhostid = types.StringValue(respObject.Originalhostid)
plan.PkiState = types.StringValue(respObject.PkiState)
plan.Sourcecluster = types.StringValue(respObject.Sourcecluster)
plan.Srcid = types.StringValue(respObject.Srcid)
plan.Svcname = types.StringValue(respObject.Svcname)
plan.Transport = types.StringValue(respObject.Transport)
plan.Uniquename = types.StringValue(respObject.Uniquename)
plan.Zone = types.StringValue(respObject.Zone)
plan.Hypervisoragent.Haspassword = types.BoolValue(respObject.Hypervisoragent.Haspassword)
plan.Hypervisoragent.Agenttype = types.StringValue(respObject.Hypervisoragent.Agenttype)
plan.Hypervisoragent.Hasalternatekey = types.BoolValue(respObject.Hypervisoragent.Hasalternatekey)
// plan.Agents = []types.Object{{
// Agenttype: types.StringValue(respObject.Agents[0].Agenttype),
// Hasalternatekey: types.BoolValue(respObject.Agents[0].Hasalternatekey),
// Haspassword: types.BoolValue(respObject.Agents[0].Haspassword),
// Username: types.StringValue(respObject.Agents[0].Username),
// Password: types.StringNull(),
// }}
// plan.Sources[0].ID = types.StringValue(respObject.Sources[0].Id)
// plan.Sources[0].Href = types.StringValue(respObject.Sources[0].Href)
// plan.Appliance = &ClusterRestRef{
// // Clusterid: types.StringValue(respObject.Appliance.Clusterid),
// ID: types.StringValue(respObject.Appliance.Id),
// Href: types.StringValue(respObject.Appliance.Href),
// }
// 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 *hostResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
// Get current state
var state vcenterHostRest
diags := req.State.Get(ctx, &state)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
// Get refreshed values
respObject, _, err := r.client.HostApi.GetHost(r.authCtx, state.ID.ValueString(), nil)
if err != nil {
resp.Diagnostics.AddError(
"Error Reading vCenter Host",
"Could not read vCenter Host with ID "+state.ID.ValueString()+": "+err.Error(),
)
return
}
// Overwrite items with refreshed state
// Map response body to schema and populate Computed attribute values
state.ID = types.StringValue(respObject.Id)
state.Href = types.StringValue(respObject.Href)
// plan.Agents = types.BoolValue(respObject.Stale)
state.Modifydate = types.Int64Value(respObject.Modifydate)
state.Autoupgrade = types.StringValue(respObject.Autoupgrade)
state.CertRevoked = types.BoolValue(respObject.CertRevoked)
state.Clusterid = types.StringValue(respObject.Clusterid)
state.Dbauthentication = types.BoolValue(respObject.Dbauthentication)
state.Diskpref = types.StringValue(respObject.Diskpref)
state.Friendlypath = types.StringValue(respObject.Friendlypath)
state.Hasagent = types.BoolValue(respObject.Hasagent)
state.Hostname = types.StringValue(respObject.Hostname)
state.Hosttype = types.StringValue(respObject.Hosttype)
state.Ipaddress = types.StringValue(respObject.Ipaddress)
state.IsClusterNode = types.BoolValue(respObject.IsClusterNode)
state.Isclusterhost = types.BoolValue(respObject.Isclusterhost)
state.IsShadowHost = types.BoolValue(respObject.IsShadowHost)
state.Isesxhost = types.BoolValue(respObject.Isesxhost)
state.Isproxyhost = types.BoolValue(respObject.Isproxyhost)
state.Isvcenterhost = types.BoolValue(respObject.Isvcenterhost)
state.Isvm = types.BoolValue(respObject.Isvm)
state.Maxjobs = types.Int64Value(int64(respObject.Maxjobs))
state.Name = types.StringValue(respObject.Name)
state.Originalhostid = types.StringValue(respObject.Originalhostid)
state.PkiState = types.StringValue(respObject.PkiState)
state.Sourcecluster = types.StringValue(respObject.Sourcecluster)
state.Srcid = types.StringValue(respObject.Srcid)
state.Svcname = types.StringValue(respObject.Svcname)
state.Transport = types.StringValue(respObject.Transport)
state.Uniquename = types.StringValue(respObject.Uniquename)
state.Zone = types.StringValue(respObject.Zone)
state.Hypervisoragent.Haspassword = types.BoolValue(respObject.Hypervisoragent.Haspassword)
state.Hypervisoragent.Agenttype = types.StringValue(respObject.Hypervisoragent.Agenttype)
state.Hypervisoragent.Hasalternatekey = types.BoolValue(respObject.Hypervisoragent.Hasalternatekey)
// state.Agents = []AgentRest{{
// Agenttype: types.StringValue(respObject.Agents[0].Agenttype),
// Hasalternatekey: types.BoolValue(respObject.Agents[0].Hasalternatekey),
// Haspassword: types.BoolValue(respObject.Agents[0].Haspassword),
// Username: types.StringValue(respObject.Agents[0].Username),
// Password: types.StringNull(),
// }}
// state.Sources[0].ID = types.StringValue(respObject.Sources[0].Id)
// state.Sources[0].Href = types.StringValue(respObject.Sources[0].Href)
// state.Appliance = &ClusterRestRef{
// // Clusterid: types.StringValue(respObject.Appliance.Clusterid),
// ID: types.StringValue(respObject.Appliance.Id),
// Href: types.StringValue(respObject.Appliance.Href),
// }
// Set refreshed state
diags = resp.State.Set(ctx, &state)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
func (r *hostResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
// Retrieve values from plan
var plan vcenterHostRest
diags := req.Plan.Get(ctx, &plan)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
reqVcenterHost := backupdr.HostRest{
Hostname: plan.Hostname.ValueString(),
Hosttype: plan.Hosttype.ValueString(),
Friendlypath: plan.Friendlypath.ValueString(),
Ipaddress: plan.Ipaddress.ValueString(),
}
if plan.Hypervisoragent != nil {
reqVcenterHost.Hypervisoragent = &backupdr.AgentRest{
Username: plan.Hypervisoragent.Username.ValueString(),
Password: plan.Hypervisoragent.Password.ValueString(),
}
}
reqVcenterHost.Sources = append(reqVcenterHost.Sources, backupdr.HostRest{
Clusterid: plan.ApplianceClusterID.ValueString(),
})
// Generate API request body from plan
reqBody := backupdr.HostApiUpdateHostOpts{
Body: optional.NewInterface(reqVcenterHost),
}
// Update vCenter Host
respObject, res, err := r.client.HostApi.UpdateHost(r.authCtx, plan.ID.ValueString(), &reqBody)
if err != nil {
resp.Diagnostics.AddError(
"Error updating vCenter Host",
"Could not updating vCenter Host, unexpected error: "+err.Error(),
)
return
}
if res.StatusCode != 200 {
resp.Diagnostics.AddError(
"Unable to Update vCenter Host ",
"An unexpected error occurred when creating the BackupDR API client. "+
"If the error is not clear, please contact the provider developers.\n\n"+
"BackupDR Client Error: "+res.Status,
)
}
// Map response body to schema and populate Computed attribute values
plan.ID = types.StringValue(respObject.Id)
plan.Href = types.StringValue(respObject.Href)
// plan.Agents = types.BoolValue(respObject.Stale)
plan.Modifydate = types.Int64Value(respObject.Modifydate)
plan.Autoupgrade = types.StringValue(respObject.Autoupgrade)
plan.CertRevoked = types.BoolValue(respObject.CertRevoked)
plan.Clusterid = types.StringValue(respObject.Clusterid)
plan.Dbauthentication = types.BoolValue(respObject.Dbauthentication)
plan.Diskpref = types.StringValue(respObject.Diskpref)
plan.Friendlypath = types.StringValue(respObject.Friendlypath)
plan.Hasagent = types.BoolValue(respObject.Hasagent)
plan.Hostname = types.StringValue(respObject.Hostname)
plan.Hosttype = types.StringValue(respObject.Hosttype)
plan.Ipaddress = types.StringValue(respObject.Ipaddress)
plan.IsClusterNode = types.BoolValue(respObject.IsClusterNode)
plan.Isclusterhost = types.BoolValue(respObject.Isclusterhost)
plan.IsShadowHost = types.BoolValue(respObject.IsShadowHost)
plan.Isesxhost = types.BoolValue(respObject.Isesxhost)
plan.Isproxyhost = types.BoolValue(respObject.Isproxyhost)
plan.Isvcenterhost = types.BoolValue(respObject.Isvcenterhost)
plan.Isvm = types.BoolValue(respObject.Isvm)
plan.Maxjobs = types.Int64Value(int64(respObject.Maxjobs))
plan.Name = types.StringValue(respObject.Name)
plan.Originalhostid = types.StringValue(respObject.Originalhostid)
plan.PkiState = types.StringValue(respObject.PkiState)
plan.Sourcecluster = types.StringValue(respObject.Sourcecluster)
plan.Srcid = types.StringValue(respObject.Srcid)
plan.Svcname = types.StringValue(respObject.Svcname)
plan.Transport = types.StringValue(respObject.Transport)
plan.Uniquename = types.StringValue(respObject.Uniquename)
plan.Zone = types.StringValue(respObject.Zone)
plan.Hypervisoragent.Haspassword = types.BoolValue(respObject.Hypervisoragent.Haspassword)
plan.Hypervisoragent.Agenttype = types.StringValue(respObject.Hypervisoragent.Agenttype)
plan.Hypervisoragent.Hasalternatekey = types.BoolValue(respObject.Hypervisoragent.Hasalternatekey)
// plan.Agents = []AgentRest{{
// Agenttype: types.StringValue(respObject.Agents[0].Agenttype),
// Hasalternatekey: types.BoolValue(respObject.Agents[0].Hasalternatekey),
// Haspassword: types.BoolValue(respObject.Agents[0].Haspassword),
// Username: types.StringValue(respObject.Agents[0].Username),
// Password: types.StringNull(),
// }}
// plan.Sources[0].ID = types.StringValue(respObject.Sources[0].Id)
// plan.Sources[0].Href = types.StringValue(respObject.Sources[0].Href)
// plan.Appliance, _ = types.ObjectValue(ClusterRestRef{
// // Clusterid: types.StringValue(respObject.Appliance.Clusterid),
// ID: types.StringValue(respObject.Appliance.Id),
// Href: types.StringValue(respObject.Appliance.Href),
// })
diags = resp.State.Set(ctx, plan)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
}
func (r *hostResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
// Retrieve values from state
var state vcenterHostRest
diags := req.State.Get(ctx, &state)
resp.Diagnostics.Append(diags...)
if resp.Diagnostics.HasError() {
return
}
// Delete existing vCenter Host
_, err := r.client.HostApi.DeleteHost(r.authCtx, state.ID.ValueString())
if err != nil {
resp.Diagnostics.AddError(
"Error Deleting vCenter Host",
"Could not delete vCenter Host, unexpected error: "+err.Error(),
)
return
}
}
func (r *hostResource) 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)
}