package resources import ( "context" "fmt" "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" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "terraform-provider-fission/internal/client" ) var _ resource.Resource = &CronTriggerResource{} // CronTriggerResource управляет Fission TimeTrigger (CRON). type CronTriggerResource struct { client *client.Client } type cronTriggerResourceModel struct { ID types.String `tfsdk:"id"` Name types.String `tfsdk:"name"` Namespace types.String `tfsdk:"namespace"` Function types.String `tfsdk:"function"` Cron types.String `tfsdk:"cron"` UID types.String `tfsdk:"uid"` } func NewCronTriggerResource() resource.Resource { return &CronTriggerResource{} } func (r *CronTriggerResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) { resp.TypeName = req.ProviderTypeName + "_cron_trigger" } func (r *CronTriggerResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { resp.Schema = schema.Schema{ Description: "Fission TimeTrigger — запускает функцию по CRON-расписанию.", Attributes: map[string]schema.Attribute{ "id": schema.StringAttribute{ Computed: true, Description: "Идентификатор ресурса (namespace/name).", }, "name": schema.StringAttribute{ Required: true, Description: "Имя TimeTrigger.", PlanModifiers: []planmodifier.String{ stringplanmodifier.RequiresReplace(), }, }, "namespace": schema.StringAttribute{ Optional: true, Computed: true, Description: "Kubernetes namespace.", PlanModifiers: []planmodifier.String{ stringplanmodifier.RequiresReplace(), }, }, "function": schema.StringAttribute{ Required: true, Description: "Имя Fission Function для запуска.", }, "cron": schema.StringAttribute{ Required: true, Description: "CRON-расписание, например \"*/10 * * * *\".", }, "uid": schema.StringAttribute{ Computed: true, Description: "UID объекта в Kubernetes.", }, }, } } func (r *CronTriggerResource) 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("Неверный тип провайдера", fmt.Sprintf("ожидался *client.Client, получен %T", req.ProviderData)) return } r.client = c } func (r *CronTriggerResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) { var plan cronTriggerResourceModel resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) if resp.Diagnostics.HasError() { return } ns := plan.Namespace.ValueString() if ns == "" { ns = r.client.Namespace } obj := buildTimeTrigger(plan.Name.ValueString(), ns, plan.Function.ValueString(), plan.Cron.ValueString()) created, err := r.client.CreateTimeTrigger(ctx, obj) if err != nil { resp.Diagnostics.AddError("Ошибка создания CRON trigger", err.Error()) return } plan.ID = types.StringValue(ns + "/" + plan.Name.ValueString()) plan.Namespace = types.StringValue(ns) plan.UID = types.StringValue(string(created.GetUID())) resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) } func (r *CronTriggerResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) { var state cronTriggerResourceModel resp.Diagnostics.Append(req.State.Get(ctx, &state)...) if resp.Diagnostics.HasError() { return } obj, err := r.client.GetTimeTrigger(ctx, state.Namespace.ValueString(), state.Name.ValueString()) if err != nil { if client.IsNotFound(err) { resp.State.RemoveResource(ctx) return } resp.Diagnostics.AddError("Ошибка чтения CRON trigger", err.Error()) return } spec, _, _ := unstructured.NestedMap(obj.Object, "spec") if cron, ok := spec["cron"].(string); ok { state.Cron = types.StringValue(cron) } if fnRef, ok := spec["functionref"].(map[string]any); ok { if name, ok := fnRef["name"].(string); ok { state.Function = types.StringValue(name) } } state.UID = types.StringValue(string(obj.GetUID())) resp.Diagnostics.Append(resp.State.Set(ctx, &state)...) } func (r *CronTriggerResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) { var plan cronTriggerResourceModel resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...) if resp.Diagnostics.HasError() { return } existing, err := r.client.GetTimeTrigger(ctx, plan.Namespace.ValueString(), plan.Name.ValueString()) if err != nil { resp.Diagnostics.AddError("Ошибка получения CRON trigger для обновления", err.Error()) return } if err := unstructured.SetNestedField(existing.Object, plan.Cron.ValueString(), "spec", "cron"); err != nil { resp.Diagnostics.AddError("Ошибка обновления cron", err.Error()) return } if err := unstructured.SetNestedField(existing.Object, plan.Function.ValueString(), "spec", "functionref", "name"); err != nil { resp.Diagnostics.AddError("Ошибка обновления function", err.Error()) return } updated, err := r.client.UpdateTimeTrigger(ctx, existing) if err != nil { resp.Diagnostics.AddError("Ошибка обновления CRON trigger", err.Error()) return } plan.UID = types.StringValue(string(updated.GetUID())) resp.Diagnostics.Append(resp.State.Set(ctx, &plan)...) } func (r *CronTriggerResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) { var state cronTriggerResourceModel resp.Diagnostics.Append(req.State.Get(ctx, &state)...) if resp.Diagnostics.HasError() { return } if err := r.client.DeleteTimeTrigger(ctx, state.Namespace.ValueString(), state.Name.ValueString()); err != nil { resp.Diagnostics.AddError("Ошибка удаления CRON trigger", err.Error()) } } func buildTimeTrigger(name, ns, functionName, cron string) *unstructured.Unstructured { return &unstructured.Unstructured{ Object: map[string]any{ "apiVersion": "fission.io/v1", "kind": "TimeTrigger", "metadata": map[string]any{ "name": name, "namespace": ns, }, "spec": map[string]any{ "cron": cron, "functionref": map[string]any{ "type": "name", "name": functionName, }, }, }, } }