Files
fission-console/terraform/provider/internal/resources/environment_resource.go
T
Naeel fd236d87ea feat: provider audit — builder support, function tuning, source archive
Аудит провайдера vs каноничный Fission. Добавлено:

Environment:
- builder_image, builder_command для Go и языков с build step

Package:
- deploy_type (literal/source) для переключения deployment/source archive
- loadPackageSourceArchive() — zip-упаковка source_dir
- Убран пустой source:{} из literal mode

Function:
- executor_type (poolmgr/newdeploy/container)
- function_timeout, idle_timeout
- min_scale, max_scale для ExecutionStrategy

Все 21 тест пройден. Обратная совместимость проверена.
Документация: doc/AUDIT_PROVIDER_VS_FISSION_2026-06-03.md
2026-04-15 17:46:47 +03:00

292 lines
10 KiB
Go

package resources
import (
"context"
"fmt"
"github.com/hashicorp/terraform-plugin-framework/path"
"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{}
var _ resource.ResourceWithImportState = &EnvironmentResource{}
// Изменено: 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"`
BuilderImage types.String `tfsdk:"builder_image"`
BuilderCommand types.String `tfsdk:"builder_command"`
Namespace types.String `tfsdk:"namespace"`
UID types.String `tfsdk:"uid"`
}
func NewEnvironmentResource() resource.Resource {
return &EnvironmentResource{}
}
func (r *EnvironmentResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_environment"
}
func (r *EnvironmentResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
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 контейнеров.",
},
"builder_image": schema.StringAttribute{
Optional: true,
Description: "Builder image для Environment (например ghcr.io/fission/go-builder). Нужен для языков с build step (Go и др.).",
},
"builder_command": schema.StringAttribute{
Optional: true,
Description: "Команда сборки в builder контейнере (например 'build').",
},
"namespace": schema.StringAttribute{
Optional: true,
Computed: true,
Description: "Namespace, где создается Environment. По умолчанию используется provider namespace.",
},
"uid": schema.StringAttribute{
Computed: true,
Description: "UID Kubernetes объекта Environment.",
},
},
}
}
// 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())
}
}
// ImportState поддерживает импорт ресурса в формате namespace/name.
func (r *EnvironmentResource) ImportState(ctx context.Context, req resource.ImportStateRequest, resp *resource.ImportStateResponse) {
namespace, name, err := parseImportID(req.ID)
if err != nil {
resp.Diagnostics.AddError("Некорректный формат import ID", err.Error())
return
}
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("id"), req.ID)...)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("namespace"), namespace)...)
resp.Diagnostics.Append(resp.State.SetAttribute(ctx, path.Root("name"), name)...)
}
// environmentToUnstructured преобразует Terraform model в Kubernetes CRD payload.
func environmentToUnstructured(model environmentResourceModel, namespace string) *unstructured.Unstructured {
spec := map[string]interface{}{
"version": model.Version.ValueInt64(),
"runtime": map[string]interface{}{
"image": model.Image.ValueString(),
},
"poolsize": model.PoolSize.ValueInt64(),
}
builderImage := model.BuilderImage.ValueString()
if builderImage != "" {
builder := map[string]interface{}{
"image": builderImage,
}
builderCmd := model.BuilderCommand.ValueString()
if builderCmd != "" {
builder["command"] = builderCmd
}
spec["builder"] = builder
}
return &unstructured.Unstructured{Object: map[string]interface{}{
"apiVersion": "fission.io/v1",
"kind": "Environment",
"metadata": map[string]interface{}{
"name": model.Name.ValueString(),
"namespace": namespace,
},
"spec": spec,
}}
}
// 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")
builderImage, _, _ := unstructured.NestedString(environmentObject.Object, "spec", "builder", "image")
builderCommand, _, _ := unstructured.NestedString(environmentObject.Object, "spec", "builder", "command")
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)
}
if builderImage != "" {
state.BuilderImage = types.StringValue(builderImage)
}
if builderCommand != "" {
state.BuilderCommand = types.StringValue(builderCommand)
}
return state
}