feat: implement environment and package resources via fission CRD
This commit is contained in:
@@ -2,14 +2,34 @@ 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/int64default"
|
||||
"github.com/hashicorp/terraform-plugin-framework/types"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
|
||||
"terraform-provider-fission/internal/client"
|
||||
)
|
||||
|
||||
var _ resource.Resource = &EnvironmentResource{}
|
||||
|
||||
type EnvironmentResource struct{}
|
||||
// Изменено: 2026-04-14 19:15 UTC.
|
||||
// Resource для управления Fission Environment через Kubernetes CRD API.
|
||||
type EnvironmentResource struct {
|
||||
client *client.Client
|
||||
}
|
||||
|
||||
type environmentResourceModel struct {
|
||||
ID types.String `tfsdk:"id"`
|
||||
Name types.String `tfsdk:"name"`
|
||||
Image types.String `tfsdk:"image"`
|
||||
Version types.Int64 `tfsdk:"version"`
|
||||
PoolSize types.Int64 `tfsdk:"poolsize"`
|
||||
Namespace types.String `tfsdk:"namespace"`
|
||||
UID types.String `tfsdk:"uid"`
|
||||
}
|
||||
|
||||
func NewEnvironmentResource() resource.Resource {
|
||||
return &EnvironmentResource{}
|
||||
@@ -20,11 +40,205 @@ func (r *EnvironmentResource) Metadata(_ context.Context, req resource.MetadataR
|
||||
}
|
||||
|
||||
func (r *EnvironmentResource) 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 Environment.",
|
||||
},
|
||||
"image": schema.StringAttribute{
|
||||
Required: true,
|
||||
Description: "Runtime image для Environment (например ghcr.io/fission/python-env).",
|
||||
},
|
||||
"version": schema.Int64Attribute{
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Default: int64default.StaticInt64(3),
|
||||
Description: "Версия спецификации Environment.",
|
||||
},
|
||||
"poolsize": schema.Int64Attribute{
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Default: int64default.StaticInt64(3),
|
||||
Description: "Размер пула pre-warmed контейнеров.",
|
||||
},
|
||||
"namespace": schema.StringAttribute{
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Description: "Namespace, где создается Environment. По умолчанию используется provider namespace.",
|
||||
},
|
||||
"uid": schema.StringAttribute{
|
||||
Computed: true,
|
||||
Description: "UID Kubernetes объекта Environment.",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (r *EnvironmentResource) Configure(_ context.Context, _ resource.ConfigureRequest, _ *resource.ConfigureResponse) {}
|
||||
func (r *EnvironmentResource) Create(_ context.Context, _ resource.CreateRequest, _ *resource.CreateResponse) {}
|
||||
func (r *EnvironmentResource) Read(_ context.Context, _ resource.ReadRequest, _ *resource.ReadResponse) {}
|
||||
func (r *EnvironmentResource) Update(_ context.Context, _ resource.UpdateRequest, _ *resource.UpdateResponse) {}
|
||||
func (r *EnvironmentResource) Delete(_ context.Context, _ resource.DeleteRequest, _ *resource.DeleteResponse) {}
|
||||
// Configure получает клиент из provider.Configure().
|
||||
func (r *EnvironmentResource) 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
|
||||
}
|
||||
|
||||
// Create создает новый Environment CRD объект.
|
||||
func (r *EnvironmentResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
|
||||
var plan environmentResourceModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
namespace := plan.Namespace.ValueString()
|
||||
if namespace == "" {
|
||||
namespace = r.client.Namespace
|
||||
}
|
||||
|
||||
environmentObject := environmentToUnstructured(plan, namespace)
|
||||
createdEnvironment, err := r.client.CreateEnvironment(ctx, environmentObject)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка создания Fission Environment", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
state := unstructuredToEnvironmentModel(createdEnvironment, plan)
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
|
||||
// Read синхронизирует Terraform state с текущим состоянием CRD.
|
||||
func (r *EnvironmentResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
|
||||
var state environmentResourceModel
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
namespace := state.Namespace.ValueString()
|
||||
if namespace == "" {
|
||||
namespace = r.client.Namespace
|
||||
}
|
||||
|
||||
environmentObject, err := r.client.GetEnvironment(ctx, namespace, state.Name.ValueString())
|
||||
if err != nil {
|
||||
if client.IsNotFound(err) {
|
||||
resp.State.RemoveResource(ctx)
|
||||
return
|
||||
}
|
||||
|
||||
resp.Diagnostics.AddError("Ошибка чтения Fission Environment", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
updatedState := unstructuredToEnvironmentModel(environmentObject, state)
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &updatedState)...)
|
||||
}
|
||||
|
||||
// Update обновляет существующий Environment CRD объект.
|
||||
func (r *EnvironmentResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
|
||||
var plan environmentResourceModel
|
||||
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
namespace := plan.Namespace.ValueString()
|
||||
if namespace == "" {
|
||||
namespace = r.client.Namespace
|
||||
}
|
||||
|
||||
environmentObject := environmentToUnstructured(plan, namespace)
|
||||
existingEnvironment, err := r.client.GetEnvironment(ctx, namespace, plan.Name.ValueString())
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка получения Environment перед обновлением", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
environmentObject.SetResourceVersion(existingEnvironment.GetResourceVersion())
|
||||
updatedEnvironment, err := r.client.UpdateEnvironment(ctx, environmentObject)
|
||||
if err != nil {
|
||||
resp.Diagnostics.AddError("Ошибка обновления Fission Environment", err.Error())
|
||||
return
|
||||
}
|
||||
|
||||
state := unstructuredToEnvironmentModel(updatedEnvironment, plan)
|
||||
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
|
||||
}
|
||||
|
||||
// Delete удаляет Environment CRD объект.
|
||||
func (r *EnvironmentResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
|
||||
var state environmentResourceModel
|
||||
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
|
||||
if resp.Diagnostics.HasError() {
|
||||
return
|
||||
}
|
||||
|
||||
namespace := state.Namespace.ValueString()
|
||||
if namespace == "" {
|
||||
namespace = r.client.Namespace
|
||||
}
|
||||
|
||||
err := r.client.DeleteEnvironment(ctx, namespace, state.Name.ValueString())
|
||||
if err != nil && !client.IsNotFound(err) {
|
||||
resp.Diagnostics.AddError("Ошибка удаления Fission Environment", err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// environmentToUnstructured преобразует Terraform model в Kubernetes CRD payload.
|
||||
func environmentToUnstructured(model environmentResourceModel, namespace string) *unstructured.Unstructured {
|
||||
return &unstructured.Unstructured{Object: map[string]interface{}{
|
||||
"apiVersion": "fission.io/v1",
|
||||
"kind": "Environment",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": model.Name.ValueString(),
|
||||
"namespace": namespace,
|
||||
},
|
||||
"spec": map[string]interface{}{
|
||||
"version": model.Version.ValueInt64(),
|
||||
"runtime": map[string]interface{}{
|
||||
"image": model.Image.ValueString(),
|
||||
},
|
||||
"poolsize": model.PoolSize.ValueInt64(),
|
||||
},
|
||||
}}
|
||||
}
|
||||
|
||||
// unstructuredToEnvironmentModel читает нужные поля из CRD объекта обратно в Terraform state.
|
||||
func unstructuredToEnvironmentModel(environmentObject *unstructured.Unstructured, base environmentResourceModel) environmentResourceModel {
|
||||
imageValue, _, _ := unstructured.NestedString(environmentObject.Object, "spec", "runtime", "image")
|
||||
versionValue, _, _ := unstructured.NestedInt64(environmentObject.Object, "spec", "version")
|
||||
poolsizeValue, _, _ := unstructured.NestedInt64(environmentObject.Object, "spec", "poolsize")
|
||||
|
||||
state := base
|
||||
state.Name = types.StringValue(environmentObject.GetName())
|
||||
state.Namespace = types.StringValue(environmentObject.GetNamespace())
|
||||
state.ID = types.StringValue(fmt.Sprintf("%s/%s", environmentObject.GetNamespace(), environmentObject.GetName()))
|
||||
state.UID = types.StringValue(string(environmentObject.GetUID()))
|
||||
|
||||
if imageValue != "" {
|
||||
state.Image = types.StringValue(imageValue)
|
||||
}
|
||||
if versionValue != 0 {
|
||||
state.Version = types.Int64Value(versionValue)
|
||||
}
|
||||
if poolsizeValue != 0 {
|
||||
state.PoolSize = types.Int64Value(poolsizeValue)
|
||||
}
|
||||
|
||||
return state
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user