package provider import ( "context" "errors" "fmt" "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" ) var _ resource.Resource = &AgentResource{} var _ resource.ResourceWithImportState = &AgentResource{} // AgentResource defines the resource implementation. type AgentResource struct { client *Client } // AgentResourceModel describes the resource data model. type AgentResourceModel struct { ID types.String `tfsdk:"id"` Name types.String `tfsdk:"name"` Token types.String `tfsdk:"token"` InsertedAt types.String `tfsdk:"inserted_at"` } // NewAgentResource creates a new agent resource. func NewAgentResource() resource.Resource { return &AgentResource{} } func (r *AgentResource) Metadata(ctx context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { resp.TypeName = req.ProviderTypeName + "_agent" } func (r *AgentResource) Schema(ctx context.Context, req resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ Description: "Manages a TowerOps agent token. Agents are deployed on customer networks to poll devices via SNMP, ping, and SSH.", Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ Description: "The unique identifier of the agent.", Computed: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.UseStateForUnknown(), }, }, "name": schema.StringAttribute{ Description: "The name of the agent. Changing this forces a new resource to be created.", Required: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.RequiresReplace(), }, }, "token": schema.StringAttribute{ Description: "The bearer token for this agent. Only available after creation and cannot be retrieved again.", Computed: true, Sensitive: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.UseStateForUnknown(), }, }, "inserted_at": schema.StringAttribute{ Description: "The timestamp when the agent was created.", Computed: true, PlanModifiers: []planmodifier.String{ stringplanmodifier.UseStateForUnknown(), }, }, }, } } func (r *AgentResource) Configure(ctx context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) { if req.ProviderData == nil { return } client, ok := req.ProviderData.(*Client) if !ok { resp.Diagnostics.AddError( "Unexpected Resource Configure Type", fmt.Sprintf("Expected *Client, got: %T", req.ProviderData), ) return } r.client = client } func (r *AgentResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { var data AgentResourceModel resp.Diagnostics.Append(req.Plan.Get(ctx, &data)...) if resp.Diagnostics.HasError() { return } agent := Agent{ Name: data.Name.ValueString(), } created, err := r.client.CreateAgent(agent) if err != nil { resp.Diagnostics.AddError("Failed to create agent", err.Error()) return } data.ID = types.StringValue(created.ID) data.Token = types.StringValue(created.Token) data.InsertedAt = types.StringValue(created.InsertedAt) resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } func (r *AgentResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { var data AgentResourceModel resp.Diagnostics.Append(req.State.Get(ctx, &data)...) if resp.Diagnostics.HasError() { return } agent, err := r.client.GetAgent(data.ID.ValueString()) if err != nil { if errors.Is(err, ErrNotFound) { resp.State.RemoveResource(ctx) return } resp.Diagnostics.AddError("Failed to read agent", err.Error()) return } data.Name = types.StringValue(agent.Name) data.InsertedAt = types.StringValue(agent.InsertedAt) // Token is not returned by GET, preserve existing state value resp.Diagnostics.Append(resp.State.Set(ctx, &data)...) } func (r *AgentResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { // Agents only support name changes via the API, but the REST API doesn't have // an update endpoint. For now, if name changes, we must destroy and recreate. // This is handled by Terraform's ForceNew-like behavior since all mutable // attributes are required. resp.Diagnostics.AddError( "Agent Update Not Supported", "Agent tokens cannot be updated. Delete and recreate the agent to change its name.", ) } func (r *AgentResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { var data AgentResourceModel resp.Diagnostics.Append(req.State.Get(ctx, &data)...) if resp.Diagnostics.HasError() { return } err := r.client.DeleteAgent(data.ID.ValueString()) if err != nil { resp.Diagnostics.AddError("Failed to delete agent", err.Error()) return } } func (r *AgentResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) { resource.ImportStatePassthroughID(ctx, path.Root("id"), req, resp) }