Files
sless/terraform/provider/internal/resources/trigger_resource.go
T
“Naeel” 19c9e016a8 feat: Terraform провайдер sless v0.1.0 + обновление doc/progress.md
Независимый Go-модуль terraform/provider/ для управления функциями через REST API.

Ресурсы:
- sless_function: CRUD + upload zip + WaitReady (kaniko build 5 мин)
- sless_trigger: CRUD (http/cron)

Компоненты:
- internal/client/client.go   — HTTP-клиент, готов к переносу в nubes
- internal/provider/provider.go — паттерн идентичен nubes NubesProvider
- internal/resources/           — function_resource.go, trigger_resource.go
- hack/build-and-publish.sh    — GPG-подпись + mc upload в S3

Опубликовано: terra.k8c.ru/naeel/sless v0.1.0
Проверено: terraform init успешен
2026-03-07 10:23:52 +04:00

186 lines
6.0 KiB
Go

// 2026-03-07
// trigger_resource.go — Terraform ресурс sless_trigger.
// Поддерживает type=http (создаёт Service+Ingress) и type=cron (запускает по schedule).
// Все ключевые поля имеют RequiresReplace — API не поддерживает обновление триггеров.
// URL функции (для http-триггера) — computed, пишется оператором в status.url.
package resources
import (
"context"
"fmt"
"terraform-provider-sless/internal/client"
"github.com/hashicorp/terraform-plugin-framework/resource"
"github.com/hashicorp/terraform-plugin-framework/resource/schema"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/boolplanmodifier"
"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 = &TriggerResource{}
type TriggerResource struct {
client *client.Client
}
func NewTriggerResource() resource.Resource {
return &TriggerResource{}
}
// TriggerModel — модель состояния terraform для sless_trigger.
type TriggerModel struct {
Namespace types.String `tfsdk:"namespace"`
Name types.String `tfsdk:"name"`
Type types.String `tfsdk:"type"`
FunctionRef types.String `tfsdk:"function"`
Schedule types.String `tfsdk:"schedule"`
Active types.Bool `tfsdk:"active"`
URL types.String `tfsdk:"url"`
}
func (r *TriggerResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_trigger"
}
func (r *TriggerResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
"namespace": schema.StringAttribute{
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"name": schema.StringAttribute{
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
// type: "http" или "cron"
"type": schema.StringAttribute{
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
// function: имя Function CRD в том же namespace
"function": schema.StringAttribute{
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
// schedule: только для type=cron, формат cron ("*/5 * * * *")
"schedule": schema.StringAttribute{
Optional: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
// active, url — только для чтения, вычисляются оператором
"active": schema.BoolAttribute{
Computed: true,
PlanModifiers: []planmodifier.Bool{
boolplanmodifier.UseStateForUnknown(),
},
},
"url": schema.StringAttribute{
Computed: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.UseStateForUnknown(),
},
},
},
}
}
func (r *TriggerResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
if req.ProviderData == nil {
return
}
c, ok := req.ProviderData.(*client.Client)
if !ok {
resp.Diagnostics.AddError(
"unexpected provider data",
fmt.Sprintf("expected *client.Client, got: %T", req.ProviderData),
)
return
}
r.client = c
}
func (r *TriggerResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var plan TriggerModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
if resp.Diagnostics.HasError() {
return
}
tr, err := r.client.CreateTrigger(ctx, plan.Namespace.ValueString(), client.TriggerRequest{
Name: plan.Name.ValueString(),
Type: plan.Type.ValueString(),
FunctionRef: plan.FunctionRef.ValueString(),
Schedule: plan.Schedule.ValueString(),
})
if err != nil {
resp.Diagnostics.AddError("create trigger", err.Error())
return
}
resp.Diagnostics.Append(resp.State.Set(ctx, trToModel(tr))...)
}
func (r *TriggerResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var state TriggerModel
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
tr, err := r.client.GetTrigger(ctx, state.Namespace.ValueString(), state.Name.ValueString())
if err != nil {
resp.Diagnostics.AddError("read trigger", err.Error())
return
}
if tr == nil {
resp.State.RemoveResource(ctx)
return
}
resp.Diagnostics.Append(resp.State.Set(ctx, trToModel(tr))...)
}
// Update — никогда не вызывается: все поля имеют RequiresReplace.
// Метод обязателен интерфейсом resource.Resource.
func (r *TriggerResource) Update(_ context.Context, _ resource.UpdateRequest, resp *resource.UpdateResponse) {
resp.Diagnostics.AddError("update not supported", "all trigger fields require replacement")
}
func (r *TriggerResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var state TriggerModel
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
if err := r.client.DeleteTrigger(ctx, state.Namespace.ValueString(), state.Name.ValueString()); err != nil {
resp.Diagnostics.AddError("delete trigger", err.Error())
}
}
// trToModel конвертирует API-ответ → state модель.
func trToModel(tr *client.TriggerResponse) TriggerModel {
return TriggerModel{
Namespace: types.StringValue(tr.Namespace),
Name: types.StringValue(tr.Name),
Type: types.StringValue(tr.Type),
FunctionRef: types.StringValue(tr.FunctionRef),
Schedule: types.StringValue(tr.Schedule),
Active: types.BoolValue(tr.Active),
URL: types.StringValue(tr.URL),
}
}