feat: implement function and http trigger resources via CRD
This commit is contained in:
@@ -2,34 +2,282 @@ 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 = &HTTPTriggerResource{}
|
||||
|
||||
type HTTPTriggerResource struct{}
|
||||
// Изменено: 2026-04-14 20:15 UTC.
|
||||
// Resource для управления Fission HTTPTrigger через Kubernetes CRD API.
|
||||
type HTTPTriggerResource struct {
|
||||
client *client.Client
|
||||
}
|
||||
|
||||
// httpTriggerResourceModel описывает состояние terraform ресурса fission_http_trigger.
|
||||
type httpTriggerResourceModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
Name types.String `tfsdk:"name"`
|
||||
Function types.String `tfsdk:"function"`
|
||||
URL types.String `tfsdk:"url"`
|
||||
Methods types.List `tfsdk:"methods"`
|
||||
CreateIngress types.Bool `tfsdk:"create_ingress"`
|
||||
Host types.String `tfsdk:"host"`
|
||||
Namespace types.String `tfsdk:"namespace"`
|
||||
UID types.String `tfsdk:"uid"`
|
||||
}
|
||||
|
||||
// NewHTTPTriggerResource создает инстанс ресурса HTTP trigger.
|
||||
func NewHTTPTriggerResource() resource.Resource {
|
||||
return &HTTPTriggerResource{}
|
||||
}
|
||||
|
||||
// Metadata задает имя ресурса fission_http_trigger.
|
||||
func (r *HTTPTriggerResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
|
||||
resp.TypeName = req.ProviderTypeName + "_http_trigger"
|
||||
}
|
||||
|
||||
// Schema задает Terraform schema для HTTPTrigger.
|
||||
func (r *HTTPTriggerResource) 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 HTTPTrigger.",
|
||||
},
|
||||
"function": schema.StringAttribute{
|
||||
Required: true,
|
||||
Description: "Имя Fission Function, к которой привязан trigger.",
|
||||
},
|
||||
"url": schema.StringAttribute{
|
||||
Required: true,
|
||||
Description: "Относительный URL, например /hello.",
|
||||
},
|
||||
"methods": schema.ListAttribute{
|
||||
Optional: true,
|
||||
ElementType: types.StringType,
|
||||
Description: "HTTP методы trigger, например [\"GET\", \"POST\"].",
|
||||
},
|
||||
"create_ingress": schema.BoolAttribute{
|
||||
Optional: true,
|
||||
Description: "Включает создание ingress правила в Fission.",
|
||||
},
|
||||
"host": schema.StringAttribute{
|
||||
Optional: true,
|
||||
Description: "Host для ingressconfig.host при create_ingress=true.",
|
||||
},
|
||||
"namespace": schema.StringAttribute{
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Description: "Namespace, где создается HTTPTrigger. По умолчанию используется provider namespace.",
|
||||
},
|
||||
"uid": schema.StringAttribute{
|
||||
Computed: true,
|
||||
Description: "UID Kubernetes объекта HTTPTrigger.",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (r *HTTPTriggerResource) Configure(_ context.Context, _ resource.ConfigureRequest, _ *resource.ConfigureResponse) {
|
||||
// Configure получает клиент из provider.Configure().
|
||||
func (r *HTTPTriggerResource) 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 *HTTPTriggerResource) Create(_ context.Context, _ resource.CreateRequest, _ *resource.CreateResponse) {
|
||||
|
||||
// Create создает новый HTTPTrigger CRD объект.
|
||||
func (r *HTTPTriggerResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
var plan httpTriggerResourceModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
namespace := resolveNamespace(plan.Namespace, r.client.Namespace)
|
||||
triggerObject, err := httpTriggerToUnstructured(ctx, plan, namespace)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка подготовки HTTPTrigger", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
createdTrigger, err := r.client.CreateHTTPTrigger(ctx, triggerObject)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка создания Fission HTTPTrigger", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
state := unstructuredToHTTPTriggerModel(ctx, createdTrigger, plan)
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
func (r *HTTPTriggerResource) Read(_ context.Context, _ resource.ReadRequest, _ *resource.ReadResponse) {
|
||||
|
||||
// Read синхронизирует Terraform state с текущим состоянием HTTPTrigger CRD.
|
||||
func (r *HTTPTriggerResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
||||
var state httpTriggerResourceModel
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
namespace := resolveNamespace(state.Namespace, r.client.Namespace)
|
||||
triggerObject, err := r.client.GetHTTPTrigger(ctx, namespace, state.Name.ValueString())
|
||||
if err != nil {
|
||||
if client.IsNotFound(err) {
|
||||
resp.State.RemoveResource(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
resp.Diagnostics.AddError("Ошибка чтения Fission HTTPTrigger", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
updatedState := unstructuredToHTTPTriggerModel(ctx, triggerObject, state)
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &updatedState)...)
|
||||
}
|
||||
func (r *HTTPTriggerResource) Update(_ context.Context, _ resource.UpdateRequest, _ *resource.UpdateResponse) {
|
||||
|
||||
// Update обновляет существующий HTTPTrigger CRD объект.
|
||||
func (r *HTTPTriggerResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
||||
var plan httpTriggerResourceModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
namespace := resolveNamespace(plan.Namespace, r.client.Namespace)
|
||||
existingTrigger, err := r.client.GetHTTPTrigger(ctx, namespace, plan.Name.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка получения HTTPTrigger перед обновлением", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
triggerObject, err := httpTriggerToUnstructured(ctx, plan, namespace)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка подготовки HTTPTrigger", err.Error())
|
||||
return
|
||||
}
|
||||
triggerObject.SetResourceVersion(existingTrigger.GetResourceVersion())
|
||||
|
||||
updatedTrigger, err := r.client.UpdateHTTPTrigger(ctx, triggerObject)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка обновления Fission HTTPTrigger", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
state := unstructuredToHTTPTriggerModel(ctx, updatedTrigger, plan)
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
func (r *HTTPTriggerResource) Delete(_ context.Context, _ resource.DeleteRequest, _ *resource.DeleteResponse) {
|
||||
|
||||
// Delete удаляет HTTPTrigger CRD объект.
|
||||
func (r *HTTPTriggerResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
||||
var state httpTriggerResourceModel
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
namespace := resolveNamespace(state.Namespace, r.client.Namespace)
|
||||
err := r.client.DeleteHTTPTrigger(ctx, namespace, state.Name.ValueString())
|
||||
if err != nil && !client.IsNotFound(err) {
|
||||
resp.Diagnostics.AddError("Ошибка удаления Fission HTTPTrigger", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// httpTriggerToUnstructured преобразует Terraform model в Kubernetes CRD payload.
|
||||
func httpTriggerToUnstructured(ctx context.Context, model httpTriggerResourceModel, namespace string) (*unstructured.Unstructured, error) {
|
||||
methods := []string{"GET"}
|
||||
if !model.Methods.IsNull() && !model.Methods.IsUnknown() {
|
||||
if len(model.Methods.Elements()) > 0 {
|
||||
methods = []string{}
|
||||
var methodValues []string
|
||||
diags := model.Methods.ElementsAs(ctx, &methodValues, false)
|
||||
if diags.HasError() {
|
||||
return nil, fmt.Errorf("convert methods list")
|
||||
}
|
||||
methods = append(methods, methodValues...)
|
||||
}
|
||||
}
|
||||
|
||||
createIngress := false
|
||||
if !model.CreateIngress.IsNull() && !model.CreateIngress.IsUnknown() {
|
||||
createIngress = model.CreateIngress.ValueBool()
|
||||
}
|
||||
|
||||
object := map[string]interface{}{
|
||||
"apiVersion": "fission.io/v1",
|
||||
"kind": "HTTPTrigger",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": model.Name.ValueString(),
|
||||
"namespace": namespace,
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"relativeurl": model.URL.ValueString(),
|
||||
"methods": methods,
|
||||
"functionref": map[string]interface{}{
|
||||
"type": "name",
|
||||
"name": model.Function.ValueString(),
|
||||
},
|
||||
"createingress": createIngress,
|
||||
},
|
||||
}
|
||||
|
||||
host := model.Host.ValueString()
|
||||
if host != "" {
|
||||
_ = unstructured.SetNestedField(object, map[string]interface{}{"host": host}, "spec", "ingressconfig")
|
||||
}
|
||||
|
||||
return &unstructured.Unstructured{Object: object}, nil
|
||||
}
|
||||
|
||||
// unstructuredToHTTPTriggerModel читает нужные поля из CRD объекта обратно в Terraform state.
|
||||
func unstructuredToHTTPTriggerModel(ctx context.Context, triggerObject *unstructured.Unstructured, base httpTriggerResourceModel) httpTriggerResourceModel {
|
||||
urlValue, _, _ := unstructured.NestedString(triggerObject.Object, "spec", "relativeurl")
|
||||
functionName, _, _ := unstructured.NestedString(triggerObject.Object, "spec", "functionref", "name")
|
||||
hostValue, _, _ := unstructured.NestedString(triggerObject.Object, "spec", "ingressconfig", "host")
|
||||
createIngressValue, foundCreateIngress, _ := unstructured.NestedBool(triggerObject.Object, "spec", "createingress")
|
||||
methodsValue, _, _ := unstructured.NestedStringSlice(triggerObject.Object, "spec", "methods")
|
||||
|
||||
methodsList, _ := types.ListValueFrom(ctx, types.StringType, methodsValue)
|
||||
|
||||
state := base
|
||||
state.Name = types.StringValue(triggerObject.GetName())
|
||||
state.Namespace = types.StringValue(triggerObject.GetNamespace())
|
||||
state.ID = types.StringValue(fmt.Sprintf("%s/%s", triggerObject.GetNamespace(), triggerObject.GetName()))
|
||||
state.UID = types.StringValue(string(triggerObject.GetUID()))
|
||||
state.Methods = methodsList
|
||||
|
||||
if urlValue != "" {
|
||||
state.URL = types.StringValue(urlValue)
|
||||
}
|
||||
if functionName != "" {
|
||||
state.Function = types.StringValue(functionName)
|
||||
}
|
||||
if hostValue != "" {
|
||||
state.Host = types.StringValue(hostValue)
|
||||
}
|
||||
if foundCreateIngress {
|
||||
state.CreateIngress = types.BoolValue(createIngressValue)
|
||||
}
|
||||
|
||||
return state
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user