hhctl CLI and Terraform provider for Hammerhead
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
bf340cdab4
19 files changed
+2442
No files matched your search
@@ -0,0 +1,187 @@
|
||||
package provider
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/hashicorp/terraform-plugin-framework/datasource"
|
||||
dsschema "github.com/hashicorp/terraform-plugin-framework/datasource/schema"
|
||||
"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/setdefault"
|
||||
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
|
||||
"git.veillax.com/Veillax/hhctl/client"
|
||||
)
|
||||
|
||||
var (
|
||||
_ resource.Resource = &edgeResource{}
|
||||
_ resource.ResourceWithConfigure = &edgeResource{}
|
||||
_ resource.ResourceWithImportState = &edgeResource{}
|
||||
_ datasource.DataSource = &edgeData{}
|
||||
_ datasource.DataSourceWithConfigure = &edgeData{}
|
||||
)
|
||||
|
||||
func NewEdge() resource.Resource { return &edgeResource{} }
|
||||
|
||||
type edgeResource struct{ c *client.Client }
|
||||
|
||||
type edgeModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
Name types.String `tfsdk:"name"`
|
||||
TunnelAddr types.String `tfsdk:"tunnel_addr"`
|
||||
OrgIDs types.Set `tfsdk:"org_ids"`
|
||||
Token types.String `tfsdk:"token"`
|
||||
IPs types.List `tfsdk:"ips"`
|
||||
}
|
||||
|
||||
func (r *edgeResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_edge"
|
||||
}
|
||||
|
||||
func (r *edgeResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
|
||||
resp.Schema = schema.Schema{
|
||||
Description: "An edge: a public Blacktip node. Needs a server API key.",
|
||||
Attributes: map[string]schema.Attribute{
|
||||
"id": schema.StringAttribute{Computed: true, PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()}},
|
||||
"name": schema.StringAttribute{Required: true},
|
||||
"tunnel_addr": schema.StringAttribute{Required: true, Description: "The edge's public WireGuard endpoint, host:port (UDP)."},
|
||||
"org_ids": schema.SetAttribute{Optional: true, Computed: true, ElementType: types.StringType, Default: setdefault.StaticValue(setOf(nil)),
|
||||
Description: "Organizations allowed to use the edge; empty means all."},
|
||||
"token": schema.StringAttribute{Computed: true, Sensitive: true, PlanModifiers: []planmodifier.String{stringplanmodifier.UseStateForUnknown()},
|
||||
Description: "The token to give the Blacktip program."},
|
||||
"ips": schema.ListAttribute{Computed: true, ElementType: types.StringType, Description: "Addresses the edge reported it can bind."},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (r *edgeResource) Configure(_ context.Context, req resource.ConfigureRequest, _ *resource.ConfigureResponse) {
|
||||
r.c = clientFrom(req.ProviderData)
|
||||
}
|
||||
|
||||
func ipsList(ctx context.Context, ips []string) types.List {
|
||||
if ips == nil {
|
||||
ips = []string{}
|
||||
}
|
||||
l, _ := types.ListValueFrom(ctx, types.StringType, ips)
|
||||
return l
|
||||
}
|
||||
|
||||
func (r *edgeResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
var plan edgeModel
|
||||
if resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...); resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
e, err := r.c.CreateEdge(client.Edge{Name: plan.Name.ValueString(), TunnelAddr: plan.TunnelAddr.ValueString(), OrgIDs: stringsOf(ctx, plan.OrgIDs)})
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Creating edge", err.Error())
|
||||
return
|
||||
}
|
||||
plan.ID, plan.Token, plan.IPs = types.StringValue(e.ID), types.StringValue(e.Token), ipsList(ctx, e.IPs)
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||
}
|
||||
|
||||
func (r *edgeResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
||||
var state edgeModel
|
||||
if resp.Diagnostics.Append(req.State.Get(ctx, &state)...); resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
e, err := r.c.GetEdge(state.ID.ValueString())
|
||||
if client.IsNotFound(err) {
|
||||
resp.State.RemoveResource(ctx)
|
||||
return
|
||||
}
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Reading edge", err.Error())
|
||||
return
|
||||
}
|
||||
state.Name, state.TunnelAddr, state.OrgIDs, state.IPs = types.StringValue(e.Name), types.StringValue(e.TunnelAddr), setOf(e.OrgIDs), ipsList(ctx, e.IPs)
|
||||
if e.Token != "" {
|
||||
state.Token = types.StringValue(e.Token)
|
||||
}
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
|
||||
func (r *edgeResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
||||
var plan, state edgeModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
e, err := r.c.UpdateEdge(client.Edge{ID: state.ID.ValueString(), Name: plan.Name.ValueString(), TunnelAddr: plan.TunnelAddr.ValueString(), OrgIDs: stringsOf(ctx, plan.OrgIDs)})
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Updating edge", err.Error())
|
||||
return
|
||||
}
|
||||
plan.ID, plan.Token, plan.IPs = state.ID, state.Token, ipsList(ctx, e.IPs)
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...)
|
||||
}
|
||||
|
||||
func (r *edgeResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
||||
var state edgeModel
|
||||
if resp.Diagnostics.Append(req.State.Get(ctx, &state)...); resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
if err := r.c.DeleteEdge(state.ID.ValueString()); err != nil && !client.IsNotFound(err) {
|
||||
resp.Diagnostics.AddError("Deleting edge", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (r *edgeResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
|
||||
importByID(ctx, req, resp)
|
||||
}
|
||||
|
||||
// ---- data source: look an edge up by name, so ports can refer to one created elsewhere ----
|
||||
|
||||
func NewEdgeData() datasource.DataSource { return &edgeData{} }
|
||||
|
||||
type edgeData struct{ c *client.Client }
|
||||
|
||||
type edgeDataModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
Name types.String `tfsdk:"name"`
|
||||
IPs types.List `tfsdk:"ips"`
|
||||
Online types.Bool `tfsdk:"online"`
|
||||
}
|
||||
|
||||
func (d *edgeData) Metadata(_ context.Context, req datasource.MetadataRequest, resp *datasource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_edge"
|
||||
}
|
||||
|
||||
func (d *edgeData) Schema(_ context.Context, _ datasource.SchemaRequest, resp *datasource.SchemaResponse) {
|
||||
resp.Schema = dsschema.Schema{
|
||||
Description: "An existing edge, found by name. Works with an organization key.",
|
||||
Attributes: map[string]dsschema.Attribute{
|
||||
"id": dsschema.StringAttribute{Computed: true},
|
||||
"name": dsschema.StringAttribute{Required: true},
|
||||
"ips": dsschema.ListAttribute{Computed: true, ElementType: types.StringType},
|
||||
"online": dsschema.BoolAttribute{Computed: true},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (d *edgeData) Configure(_ context.Context, req datasource.ConfigureRequest, _ *datasource.ConfigureResponse) {
|
||||
d.c = clientFrom(req.ProviderData)
|
||||
}
|
||||
|
||||
func (d *edgeData) Read(ctx context.Context, req datasource.ReadRequest, resp *datasource.ReadResponse) {
|
||||
var m edgeDataModel
|
||||
if resp.Diagnostics.Append(req.Config.Get(ctx, &m)...); resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
edges, err := d.c.Edges()
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Listing edges", err.Error())
|
||||
return
|
||||
}
|
||||
for _, e := range edges {
|
||||
if e.Name == m.Name.ValueString() {
|
||||
m.ID, m.IPs, m.Online = types.StringValue(e.ID), ipsList(ctx, e.IPs), types.BoolValue(e.Online)
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &m)...)
|
||||
return
|
||||
}
|
||||
}
|
||||
resp.Diagnostics.AddError("Edge not found", "No edge named "+m.Name.ValueString()+" is visible to this key.")
|
||||
}
|
||||
Reference in new issue
Block a user