Аудит провайдера 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
292 lines
10 KiB
Go
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
|
|
}
|