feat: implement function and http trigger resources via CRD

This commit is contained in:
Naeel
2026-04-14 22:40:11 +03:00
parent 8842ec070e
commit cf55828ba6
5 changed files with 624 additions and 14 deletions
@@ -2,34 +2,234 @@ 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/types"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"terraform-provider-fission/internal/client"
)
var _ resource.Resource = &FunctionResource{}
type FunctionResource struct{}
// Изменено: 2026-04-14 20:00 UTC.
// Resource для управления Fission Function через Kubernetes CRD API.
type FunctionResource struct {
client *client.Client
}
// functionResourceModel описывает состояние terraform ресурса fission_function.
type functionResourceModel struct {
ID types.String `tfsdk:"id"`
Name types.String `tfsdk:"name"`
Environment types.String `tfsdk:"environment"`
PackageName types.String `tfsdk:"package_name"`
Entrypoint types.String `tfsdk:"entrypoint"`
Namespace types.String `tfsdk:"namespace"`
UID types.String `tfsdk:"uid"`
}
// NewFunctionResource создает инстанс ресурса функции.
func NewFunctionResource() resource.Resource {
return &FunctionResource{}
}
// Metadata задает имя ресурса fission_function.
func (r *FunctionResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_function"
}
// Schema задает Terraform schema для Function.
func (r *FunctionResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{}
resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Computed: true,
Description: "Идентификатор ресурса (namespace/name).",
},
"name": schema.StringAttribute{
Required: true,
Description: "Имя Fission Function.",
},
"environment": schema.StringAttribute{
Required: true,
Description: "Имя Environment, в котором выполняется функция.",
},
"package_name": schema.StringAttribute{
Required: true,
Description: "Имя Fission Package с кодом функции.",
},
"entrypoint": schema.StringAttribute{
Required: true,
Description: "Имя точки входа в пакете (например main.main).",
},
"namespace": schema.StringAttribute{
Optional: true,
Computed: true,
Description: "Namespace, где создается Function. По умолчанию используется provider namespace.",
},
"uid": schema.StringAttribute{
Computed: true,
Description: "UID Kubernetes объекта Function.",
},
},
}
}
func (r *FunctionResource) Configure(_ context.Context, _ resource.ConfigureRequest, _ *resource.ConfigureResponse) {
// Configure получает клиент из provider.Configure().
func (r *FunctionResource) Configure(_ context.Context, req resource.ConfigureRequest, resp *resource.ConfigureResponse) {
if req.ProviderData == nil {
return
}
fissionClient, ok := req.ProviderData.(*client.Client)
if !ok {
resp.Diagnostics.AddError(
"Некорректный тип provider data",
fmt.Sprintf("Ожидался *client.Client, получен: %T", req.ProviderData),
)
return
}
r.client = fissionClient
}
func (r *FunctionResource) Create(_ context.Context, _ resource.CreateRequest, _ *resource.CreateResponse) {
// Create создает новый Function CRD объект.
func (r *FunctionResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var plan functionResourceModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
if resp.Diagnostics.HasError() {
return
}
namespace := resolveNamespace(plan.Namespace, r.client.Namespace)
functionObject := functionToUnstructured(plan, namespace)
createdFunction, err := r.client.CreateFunction(ctx, functionObject)
if err != nil {
resp.Diagnostics.AddError("Ошибка создания Fission Function", err.Error())
return
}
state := unstructuredToFunctionModel(createdFunction, plan)
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
}
func (r *FunctionResource) Read(_ context.Context, _ resource.ReadRequest, _ *resource.ReadResponse) {
// Read синхронизирует Terraform state с текущим состоянием Function CRD.
func (r *FunctionResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var state functionResourceModel
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
namespace := resolveNamespace(state.Namespace, r.client.Namespace)
functionObject, err := r.client.GetFunction(ctx, namespace, state.Name.ValueString())
if err != nil {
if client.IsNotFound(err) {
resp.State.RemoveResource(ctx)
return
}
resp.Diagnostics.AddError("Ошибка чтения Fission Function", err.Error())
return
}
updatedState := unstructuredToFunctionModel(functionObject, state)
resp.Diagnostics.Append(resp.State.Set(ctx, &updatedState)...)
}
func (r *FunctionResource) Update(_ context.Context, _ resource.UpdateRequest, _ *resource.UpdateResponse) {
// Update обновляет существующий Function CRD объект.
func (r *FunctionResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var plan functionResourceModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
if resp.Diagnostics.HasError() {
return
}
namespace := resolveNamespace(plan.Namespace, r.client.Namespace)
existingFunction, err := r.client.GetFunction(ctx, namespace, plan.Name.ValueString())
if err != nil {
resp.Diagnostics.AddError("Ошибка получения Function перед обновлением", err.Error())
return
}
functionObject := functionToUnstructured(plan, namespace)
functionObject.SetResourceVersion(existingFunction.GetResourceVersion())
updatedFunction, err := r.client.UpdateFunction(ctx, functionObject)
if err != nil {
resp.Diagnostics.AddError("Ошибка обновления Fission Function", err.Error())
return
}
state := unstructuredToFunctionModel(updatedFunction, plan)
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
}
func (r *FunctionResource) Delete(_ context.Context, _ resource.DeleteRequest, _ *resource.DeleteResponse) {
// Delete удаляет Function CRD объект.
func (r *FunctionResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var state functionResourceModel
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
namespace := resolveNamespace(state.Namespace, r.client.Namespace)
err := r.client.DeleteFunction(ctx, namespace, state.Name.ValueString())
if err != nil && !client.IsNotFound(err) {
resp.Diagnostics.AddError("Ошибка удаления Fission Function", err.Error())
}
}
// functionToUnstructured преобразует Terraform model в Kubernetes CRD payload.
func functionToUnstructured(model functionResourceModel, namespace string) *unstructured.Unstructured {
return &unstructured.Unstructured{Object: map[string]interface{}{
"apiVersion": "fission.io/v1",
"kind": "Function",
"metadata": map[string]interface{}{
"name": model.Name.ValueString(),
"namespace": namespace,
},
"spec": map[string]interface{}{
"environment": map[string]interface{}{
"name": model.Environment.ValueString(),
"namespace": namespace,
},
"package": map[string]interface{}{
"packageref": map[string]interface{}{
"name": model.PackageName.ValueString(),
"namespace": namespace,
},
"functionName": model.Entrypoint.ValueString(),
},
},
}}
}
// unstructuredToFunctionModel читает нужные поля из CRD объекта обратно в Terraform state.
func unstructuredToFunctionModel(functionObject *unstructured.Unstructured, base functionResourceModel) functionResourceModel {
environmentName, _, _ := unstructured.NestedString(functionObject.Object, "spec", "environment", "name")
packageName, _, _ := unstructured.NestedString(functionObject.Object, "spec", "package", "packageref", "name")
entrypoint, _, _ := unstructured.NestedString(functionObject.Object, "spec", "package", "functionName")
state := base
state.Name = types.StringValue(functionObject.GetName())
state.Namespace = types.StringValue(functionObject.GetNamespace())
state.ID = types.StringValue(fmt.Sprintf("%s/%s", functionObject.GetNamespace(), functionObject.GetName()))
state.UID = types.StringValue(string(functionObject.GetUID()))
if environmentName != "" {
state.Environment = types.StringValue(environmentName)
}
if packageName != "" {
state.PackageName = types.StringValue(packageName)
}
if entrypoint != "" {
state.Entrypoint = types.StringValue(entrypoint)
}
return state
}