- TriggerSpec.Enabled bool (default=true): enabled=false масштабирует Deployment до 0
- FunctionJobSpec.RunID int64 (default=0): run_id=0 = skip, >0 = run
- API: PATCH /v1/namespaces/{ns}/triggers/{name} (UpdateTrigger)
- Provider: enabled attribute (Optional, Computed, in-place update)
- Provider: run_id attribute (Optional, Computed, default=0, RequiresReplace)
- operator image: naeel/sless-operator:v0.1.6
- provider: terra.k8c.ru/naeel/sless v0.1.4
226 lines
7.8 KiB
Go
226 lines
7.8 KiB
Go
// 2026-03-08
|
|
// trigger_resource.go — Terraform ресурс sless_trigger.
|
|
// Поддерживает type=http (создаёт Service+Ingress) и type=cron (запускает по schedule).
|
|
// enabled=false: масштабирует Deployment функции до 0 (не принимает запросы, не потребляет ресурсы).
|
|
// enabled не требует RequiresReplace — поддерживает in-place обновление через PATCH.
|
|
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/booldefault"
|
|
"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"`
|
|
// Enabled — false = Deployment масштабируется до 0, функция не потребляет ресурсы.
|
|
// Не требует пересоздания - изменяется in-place через PATCH.
|
|
Enabled types.Bool `tfsdk:"enabled"`
|
|
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(),
|
|
},
|
|
},
|
|
// enabled: false → Deployment масштабируется до 0 (не удаляет ресурс).
|
|
// Не имеет RequiresReplace: применяется in-place через PATCH.
|
|
"enabled": schema.BoolAttribute{
|
|
Optional: true,
|
|
Computed: true,
|
|
Default: booldefault.StaticBool(true),
|
|
},
|
|
// 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(),
|
|
Enabled: boolPtr(plan.Enabled.ValueBool()),
|
|
})
|
|
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 — обновляет enabled через PATCH без пересоздания.
|
|
// единственное поле без RequiresReplace, поэтому Update срабатывает только если enabled изменился.
|
|
func (r *TriggerResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
|
var plan, state TriggerModel
|
|
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
|
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
|
if resp.Diagnostics.HasError() {
|
|
return
|
|
}
|
|
|
|
enabled := plan.Enabled.ValueBool()
|
|
tr, err := r.client.UpdateTrigger(ctx, plan.Namespace.ValueString(), plan.Name.ValueString(), client.TriggerUpdateRequest{
|
|
Enabled: &enabled,
|
|
})
|
|
if err != nil {
|
|
resp.Diagnostics.AddError("update trigger", err.Error())
|
|
return
|
|
}
|
|
|
|
resp.Diagnostics.Append(resp.State.Set(ctx, trToModel(tr))...)
|
|
}
|
|
|
|
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 модель.
|
|
// schedule: возвращаем Null если пустая строка — terraform требует null для Optional-полей,
|
|
// которые не заданы в конфиге (иначе план: null → apply: "" → "inconsistent result").
|
|
func trToModel(tr *client.TriggerResponse) TriggerModel {
|
|
schedule := types.StringNull()
|
|
if tr.Schedule != "" {
|
|
schedule = types.StringValue(tr.Schedule)
|
|
}
|
|
return TriggerModel{
|
|
Namespace: types.StringValue(tr.Namespace),
|
|
Name: types.StringValue(tr.Name),
|
|
Type: types.StringValue(tr.Type),
|
|
FunctionRef: types.StringValue(tr.FunctionRef),
|
|
Schedule: schedule,
|
|
Enabled: types.BoolValue(tr.Enabled),
|
|
Active: types.BoolValue(tr.Active),
|
|
URL: types.StringValue(tr.URL),
|
|
}
|
|
}
|
|
|
|
// boolPtr — хелпер для получения указателя на bool (API требует *bool).
|
|
func boolPtr(v bool) *bool {
|
|
return &v
|
|
}
|