311 lines
10 KiB
Go
311 lines
10 KiB
Go
package provider
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/DevFW-CICD/terraform-provider-edge-connect/internal/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"
|
|
"github.com/hashicorp/terraform-plugin-log/tflog"
|
|
)
|
|
|
|
// Ensure the implementation satisfies the expected interfaces.
|
|
var (
|
|
_ resource.Resource = &appResource{}
|
|
_ resource.ResourceWithConfigure = &appResource{}
|
|
_ resource.ResourceWithImportState = &appResource{}
|
|
)
|
|
|
|
// NewAppResource is a helper function to simplify the provider implementation.
|
|
func NewAppResource() resource.Resource {
|
|
return &appResource{}
|
|
}
|
|
|
|
// appResource is the resource implementation.
|
|
type appResource struct {
|
|
client *client.Client
|
|
}
|
|
|
|
// appResourceModel maps the resource schema data.
|
|
type appResourceModel struct {
|
|
ID types.String `tfsdk:"id"`
|
|
Region types.String `tfsdk:"region"`
|
|
Organization types.String `tfsdk:"organization"`
|
|
Name types.String `tfsdk:"name"`
|
|
Version types.String `tfsdk:"version"`
|
|
ImageType types.String `tfsdk:"image_type"`
|
|
ImagePath types.String `tfsdk:"image_path"`
|
|
DefaultFlavor types.String `tfsdk:"default_flavor"`
|
|
Deployment types.String `tfsdk:"deployment"`
|
|
DeploymentManifest types.String `tfsdk:"deployment_manifest"`
|
|
AccessPorts types.String `tfsdk:"access_ports"`
|
|
Annotations types.String `tfsdk:"annotations"`
|
|
CreatedAt types.String `tfsdk:"created_at"`
|
|
UpdatedAt types.String `tfsdk:"updated_at"`
|
|
}
|
|
|
|
// Metadata returns the resource type name.
|
|
func (r *appResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
|
resp.TypeName = req.ProviderTypeName + "_app"
|
|
}
|
|
|
|
// Schema defines the schema for the resource.
|
|
func (r *appResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
|
|
resp.Schema = schema.Schema{
|
|
Description: "Manages an Edge Connect application specification.",
|
|
Attributes: map[string]schema.Attribute{
|
|
"id": schema.StringAttribute{
|
|
Description: "The unique identifier for the app (format: region/organization/name/version).",
|
|
Computed: true,
|
|
PlanModifiers: []planmodifier.String{
|
|
stringplanmodifier.UseStateForUnknown(),
|
|
},
|
|
},
|
|
"region": schema.StringAttribute{
|
|
Description: "The region where the app is deployed (e.g., 'EU').",
|
|
Required: true,
|
|
PlanModifiers: []planmodifier.String{
|
|
stringplanmodifier.RequiresReplace(),
|
|
},
|
|
},
|
|
"organization": schema.StringAttribute{
|
|
Description: "The organization that owns the app.",
|
|
Required: true,
|
|
PlanModifiers: []planmodifier.String{
|
|
stringplanmodifier.RequiresReplace(),
|
|
},
|
|
},
|
|
"name": schema.StringAttribute{
|
|
Description: "The name of the application.",
|
|
Required: true,
|
|
PlanModifiers: []planmodifier.String{
|
|
stringplanmodifier.RequiresReplace(),
|
|
},
|
|
},
|
|
"version": schema.StringAttribute{
|
|
Description: "The version of the application.",
|
|
Required: true,
|
|
PlanModifiers: []planmodifier.String{
|
|
stringplanmodifier.RequiresReplace(),
|
|
},
|
|
},
|
|
"image_type": schema.StringAttribute{
|
|
Description: "The type of image (e.g., 'Docker').",
|
|
Required: true,
|
|
},
|
|
"image_path": schema.StringAttribute{
|
|
Description: "The path to the container image.",
|
|
Required: true,
|
|
},
|
|
"default_flavor": schema.StringAttribute{
|
|
Description: "The default flavor for the app (e.g., 'EU.small', 'EU.medium', 'EU.big', 'EU.large').",
|
|
Optional: true,
|
|
},
|
|
"deployment": schema.StringAttribute{
|
|
Description: "The deployment type (e.g., 'kubernetes').",
|
|
Required: true,
|
|
},
|
|
"deployment_manifest": schema.StringAttribute{
|
|
Description: "The Kubernetes deployment manifest (YAML).",
|
|
Optional: true,
|
|
},
|
|
"access_ports": schema.StringAttribute{
|
|
Description: "The access ports in format 'protocol:port' (e.g., 'tcp:80,tcp:443').",
|
|
Optional: true,
|
|
},
|
|
"annotations": schema.StringAttribute{
|
|
Description: "Annotations for the app.",
|
|
Optional: true,
|
|
},
|
|
"created_at": schema.StringAttribute{
|
|
Description: "The timestamp when the app was created.",
|
|
Computed: true,
|
|
},
|
|
"updated_at": schema.StringAttribute{
|
|
Description: "The timestamp when the app was last updated.",
|
|
Computed: true,
|
|
},
|
|
},
|
|
}
|
|
}
|
|
|
|
// Configure adds the provider configured client to the resource.
|
|
func (r *appResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
|
|
if req.ProviderData == nil {
|
|
return
|
|
}
|
|
|
|
client, ok := req.ProviderData.(*client.Client)
|
|
if !ok {
|
|
resp.Diagnostics.AddError(
|
|
"Unexpected Resource Configure Type",
|
|
fmt.Sprintf("Expected *client.Client, 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 *appResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
|
var plan appResourceModel
|
|
diags := req.Plan.Get(ctx, &plan)
|
|
resp.Diagnostics.Append(diags...)
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
|
|
// Create new app
|
|
app := client.App{
|
|
Region: plan.Region.ValueString(),
|
|
ImageType: plan.ImageType.ValueString(),
|
|
ImagePath: plan.ImagePath.ValueString(),
|
|
DefaultFlavor: plan.DefaultFlavor.ValueString(),
|
|
Deployment: plan.Deployment.ValueString(),
|
|
DeploymentManifest: plan.DeploymentManifest.ValueString(),
|
|
AccessPorts: plan.AccessPorts.ValueString(),
|
|
Annotations: plan.Annotations.ValueString(),
|
|
}
|
|
app.Key.Organization = plan.Organization.ValueString()
|
|
app.Key.Name = plan.Name.ValueString()
|
|
app.Key.Version = plan.Version.ValueString()
|
|
|
|
createdApp, err := r.client.CreateApp(plan.Region.ValueString(), app)
|
|
if err != nil {
|
|
resp.Diagnostics.AddError(
|
|
"Error creating app",
|
|
"Could not create app: "+err.Error(),
|
|
)
|
|
return
|
|
}
|
|
|
|
// Map response body to schema and populate Computed attribute values
|
|
plan.ID = types.StringValue(fmt.Sprintf("%s/%s/%s/%s",
|
|
plan.Region.ValueString(),
|
|
createdApp.Key.Organization,
|
|
createdApp.Key.Name,
|
|
createdApp.Key.Version))
|
|
plan.CreatedAt = types.StringValue(createdApp.CreatedAt)
|
|
plan.UpdatedAt = types.StringValue(createdApp.UpdatedAt)
|
|
|
|
tflog.Trace(ctx, "created app resource")
|
|
|
|
diags = resp.State.Set(ctx, plan)
|
|
resp.Diagnostics.Append(diags...)
|
|
}
|
|
|
|
// Read refreshes the Terraform state with the latest data.
|
|
func (r *appResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
|
var state appResourceModel
|
|
diags := req.State.Get(ctx, &state)
|
|
resp.Diagnostics.Append(diags...)
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
|
|
// Get app from API
|
|
app := client.App{}
|
|
app.Key.Organization = state.Organization.ValueString()
|
|
app.Key.Name = state.Name.ValueString()
|
|
app.Key.Version = state.Version.ValueString()
|
|
|
|
readApp, err := r.client.GetApp(state.Region.ValueString(), app)
|
|
if err != nil {
|
|
resp.Diagnostics.AddError(
|
|
"Error reading app",
|
|
"Could not read app: "+err.Error(),
|
|
)
|
|
return
|
|
}
|
|
|
|
// Map response to state
|
|
state.ImageType = types.StringValue(readApp.ImageType)
|
|
state.ImagePath = types.StringValue(readApp.ImagePath)
|
|
state.DefaultFlavor = types.StringValue(readApp.DefaultFlavor)
|
|
state.Deployment = types.StringValue(readApp.Deployment)
|
|
state.DeploymentManifest = types.StringValue(readApp.DeploymentManifest)
|
|
state.AccessPorts = types.StringValue(readApp.AccessPorts)
|
|
state.Annotations = types.StringValue(readApp.Annotations)
|
|
state.CreatedAt = types.StringValue(readApp.CreatedAt)
|
|
state.UpdatedAt = types.StringValue(readApp.UpdatedAt)
|
|
|
|
diags = resp.State.Set(ctx, &state)
|
|
resp.Diagnostics.Append(diags...)
|
|
}
|
|
|
|
// Update updates the resource and sets the updated Terraform state on success.
|
|
func (r *appResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
|
var plan appResourceModel
|
|
diags := req.Plan.Get(ctx, &plan)
|
|
resp.Diagnostics.Append(diags...)
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
|
|
// Update app
|
|
app := client.App{
|
|
Region: plan.Region.ValueString(),
|
|
ImageType: plan.ImageType.ValueString(),
|
|
ImagePath: plan.ImagePath.ValueString(),
|
|
DefaultFlavor: plan.DefaultFlavor.ValueString(),
|
|
Deployment: plan.Deployment.ValueString(),
|
|
DeploymentManifest: plan.DeploymentManifest.ValueString(),
|
|
AccessPorts: plan.AccessPorts.ValueString(),
|
|
Annotations: plan.Annotations.ValueString(),
|
|
}
|
|
app.Key.Organization = plan.Organization.ValueString()
|
|
app.Key.Name = plan.Name.ValueString()
|
|
app.Key.Version = plan.Version.ValueString()
|
|
|
|
updatedApp, err := r.client.UpdateApp(plan.Region.ValueString(), app)
|
|
if err != nil {
|
|
resp.Diagnostics.AddError(
|
|
"Error updating app",
|
|
"Could not update app: "+err.Error(),
|
|
)
|
|
return
|
|
}
|
|
|
|
// Update computed attributes
|
|
plan.UpdatedAt = types.StringValue(updatedApp.UpdatedAt)
|
|
|
|
diags = resp.State.Set(ctx, plan)
|
|
resp.Diagnostics.Append(diags...)
|
|
}
|
|
|
|
// Delete deletes the resource and removes the Terraform state on success.
|
|
func (r *appResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
|
var state appResourceModel
|
|
diags := req.State.Get(ctx, &state)
|
|
resp.Diagnostics.Append(diags...)
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
|
|
// Delete app
|
|
app := client.App{}
|
|
app.Key.Organization = state.Organization.ValueString()
|
|
app.Key.Name = state.Name.ValueString()
|
|
app.Key.Version = state.Version.ValueString()
|
|
|
|
err := r.client.DeleteApp(state.Region.ValueString(), app)
|
|
if err != nil {
|
|
resp.Diagnostics.AddError(
|
|
"Error deleting app",
|
|
"Could not delete app: "+err.Error(),
|
|
)
|
|
return
|
|
}
|
|
}
|
|
|
|
// ImportState imports the resource state.
|
|
func (r *appResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
|
|
// Import format: region/organization/name/version
|
|
resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp)
|
|
}
|