feat: add fission_simple_function composite resource

This commit is contained in:
Naeel
2026-04-20 13:35:57 +03:00
parent 01df1498d6
commit ec23ddc0a6
6 changed files with 794 additions and 0 deletions
+85
View File
@@ -0,0 +1,85 @@
# 2026-04-20 — fission_simple_function
## Задача
Добавить составной ресурс `fission_simple_function`, который позволяет описать serverless-функцию одним HCL-блоком вместо четырёх отдельных ресурсов.
## Мотивация
До: пользователь обязан вручную объявлять `fission_environment` + `fission_package` + `fission_function` + `fission_http_trigger`.
После: достаточно одного блока:
```hcl
resource "fission_simple_function" "health" {
name = "health"
runtime = "nodejs"
code_dir = "./code/health"
url = "/ecom/health"
methods = ["GET"]
}
```
## Реализация
**Файл:** `terraform/provider/internal/resources/simple_function_resource.go`
### Схема ресурса
| Атрибут | Тип | Поведение |
|---------|-----|-----------|
| `name` | required string | RequiresReplace |
| `runtime` | required string | RequiresReplace; допустимые: nodejs, python, go, ruby, php, perl |
| `code_dir` | required string | путь к директории с исходниками |
| `url` | optional string | если задан — создаётся HTTPTrigger |
| `methods` | optional+computed list(string) | дефолт `["GET"]` |
| `namespace` | optional+computed string | дефолт из провайдера |
| `environment_name` | computed string | имя созданного/найденного env |
| `package_name` | computed string | `<name>-pkg` |
| `trigger_name` | computed string | `<name>-trigger` или "" |
| `code_hash` | computed string | SHA-256 кода из code_dir |
### Логика Create
1. `findOrCreateSimpleEnv` — ищет `simple-<runtime>-env`; если нет — создаёт с параметрами из `runtimeRegistry`.
2. `loadPackageContent(code_dir, "", deployType)` — загружает код (JS получает ESM zip, Go — source zip, остальные — literal bytes).
3. `CreatePackage``CreateFunction``CreateHTTPTrigger` (опционально).
4. Rollback при ошибке: если Function не создалась — удаляет Package; если Trigger — удаляет Function + Package.
### runtimeRegistry
| Runtime | deployType | entrypoint | builderImage |
|---------|-----------|------------|-------------|
| nodejs | literal | "" (default export) | — |
| python | literal | main.main | — |
| go | source | Handler | ghcr.io/fission/go-builder:latest |
| ruby | literal | main.main | — |
| php | literal | main.main | — |
| perl | literal | main.main | — |
### ModifyPlan
Пересчитывает `code_hash` при каждом plan, чтобы Terraform обнаруживал изменения кода без force-replace.
### Update
- Если `code_hash` изменился → `UpdatePackage` (patch content, preserve resourceVersion).
- URL добавлен/удалён → `CreateHTTPTrigger` / `DeleteHTTPTrigger`.
- URL изменился → `UpdateHTTPTrigger`.
### Delete
Порядок: HTTPTrigger → Function → Package. Environment намеренно НЕ удаляется (shared).
### Import
Формат: `namespace/name`. После import читается из Kubernetes через `Read`.
## Регистрация
`terraform/provider/internal/provider/provider.go` — добавлено `resources.NewSimpleFunctionResource` в `Resources()`.
## Статус
- [x] Реализация написана и скомпилирована (`go build ./...` — OK)
- [ ] Тест на real cluster
@@ -0,0 +1,12 @@
package main
import (
"net/http"
)
// Handler — точка входа для fission go-env.
func Handler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
w.WriteHeader(http.StatusOK)
_, _ = w.Write([]byte(`{"status":"processed"}`))
}
@@ -0,0 +1,2 @@
def main():
return {"status": "ok", "metrics": {}}
+34
View File
@@ -0,0 +1,34 @@
# ──────────────────────────────────────────────────────────────────────────────
# simple_functions.tf — примеры fission_simple_function (добавлено 2026-04-20)
#
# Демонстрирует использование нового составного ресурса, который заменяет
# отдельные fission_environment + fission_package + fission_function + fission_http_trigger
# одним блоком.
#
# Environment simple-<runtime>-env создаётся автоматически при первом apply.
# ──────────────────────────────────────────────────────────────────────────────
# ─── health-check (nodejs, с HTTPTrigger) ────────────────────────────────────
resource "fission_simple_function" "health" {
name = "ecom-health"
runtime = "nodejs"
code_dir = "${path.module}/code/node-health"
url = "/ecom/health"
methods = ["GET"]
}
# ─── metrics (python, без HTTPTrigger) ───────────────────────────────────────
resource "fission_simple_function" "metrics" {
name = "ecom-metrics"
runtime = "python"
code_dir = "${path.module}/code/python-metrics"
}
# ─── go-processor (go, с HTTPTrigger, PUT/POST) ───────────────────────────────
resource "fission_simple_function" "processor" {
name = "ecom-processor"
runtime = "go"
code_dir = "${path.module}/code/go-processor"
url = "/ecom/process"
methods = ["POST", "PUT"]
}
@@ -98,6 +98,7 @@ func (p *FissionProvider) Resources(_ context.Context) []func() resource.Resourc
resources.NewPackageResource,
resources.NewFunctionResource,
resources.NewHTTPTriggerResource,
resources.NewSimpleFunctionResource,
}
}
@@ -0,0 +1,660 @@
package resources
// simple_function_resource.go — составной ресурс fission_simple_function.
//
// Позволяет объявить serverless-функцию одним блоком HCL:
//
// resource "fission_simple_function" "health" {
// name = "health"
// runtime = "nodejs"
// code_dir = "./code/health"
// url = "/ecom/health"
// methods = ["GET"]
// }
//
// Провайдер сам:
// 1. Ищет environment simple-<runtime>-env, если не найден — создаёт.
// 2. Создаёт Package (<name>-pkg) с кодом из code_dir.
// 3. Создаёт Function (<name>).
// 4. Создаёт HTTPTrigger (<name>-trigger), если указан url.
//
// Добавлено: 2026-04-20
import (
"context"
"crypto/sha256"
"encoding/base64"
"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/planmodifier"
"github.com/hashicorp/terraform-plugin-framework/resource/schema/stringplanmodifier"
"github.com/hashicorp/terraform-plugin-framework/types"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"terraform-provider-fission/internal/client"
)
var _ resource.Resource = &SimpleFunctionResource{}
var _ resource.ResourceWithImportState = &SimpleFunctionResource{}
var _ resource.ResourceWithModifyPlan = &SimpleFunctionResource{}
// runtimeMeta содержит параметры окружения для каждого поддерживаемого runtime.
type runtimeMeta struct {
image string // образ среды выполнения
builderImage string // образ сборщика (пустая строка если не нужен)
deployType string // "literal" или "source"
entrypoint string // точка входа функции
poolSize int64 // размер пула
}
// runtimeRegistry — маппинг имени runtime → параметры среды выполнения.
var runtimeRegistry = map[string]runtimeMeta{
"nodejs": {
image: "ghcr.io/fission/node-env:latest",
builderImage: "",
deployType: "literal",
entrypoint: "", // JS runtime использует default export (пустая строка)
poolSize: 3,
},
"python": {
image: "ghcr.io/fission/python-env:latest",
builderImage: "",
deployType: "literal",
entrypoint: "main.main",
poolSize: 3,
},
"go": {
image: "ghcr.io/fission/go-env:latest",
builderImage: "ghcr.io/fission/go-builder:latest",
deployType: "source",
entrypoint: "Handler",
poolSize: 3,
},
"ruby": {
image: "ghcr.io/fission/ruby-env:latest",
builderImage: "",
deployType: "literal",
entrypoint: "main.main",
poolSize: 3,
},
"php": {
image: "ghcr.io/fission/php-env:latest",
builderImage: "",
deployType: "literal",
entrypoint: "main.main",
poolSize: 3,
},
"perl": {
image: "ghcr.io/fission/perl-env:latest",
builderImage: "",
deployType: "literal",
entrypoint: "main.main",
poolSize: 3,
},
}
// SimpleFunctionResource — составной ресурс: Environment + Package + Function + HTTPTrigger.
type SimpleFunctionResource struct {
client *client.Client
}
// simpleFunctionModel описывает Terraform state ресурса fission_simple_function.
type simpleFunctionModel struct {
ID types.String `tfsdk:"id"`
Name types.String `tfsdk:"name"`
Runtime types.String `tfsdk:"runtime"`
CodeDir types.String `tfsdk:"code_dir"`
URL types.String `tfsdk:"url"`
Methods types.List `tfsdk:"methods"`
Namespace types.String `tfsdk:"namespace"`
EnvironmentName types.String `tfsdk:"environment_name"`
PackageName types.String `tfsdk:"package_name"`
TriggerName types.String `tfsdk:"trigger_name"`
CodeHash types.String `tfsdk:"code_hash"`
}
// NewSimpleFunctionResource создаёт инстанс составного ресурса.
func NewSimpleFunctionResource() resource.Resource {
return &SimpleFunctionResource{}
}
func (r *SimpleFunctionResource) Metadata(_ context.Context, req resource.MetadataRequest, resp *resource.MetadataResponse) {
resp.TypeName = req.ProviderTypeName + "_simple_function"
}
func (r *SimpleFunctionResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{
Description: "Составной ресурс: создаёт Environment (при необходимости), Package, Function и HTTPTrigger одним блоком.",
Attributes: map[string]schema.Attribute{
"id": schema.StringAttribute{
Computed: true,
Description: "Идентификатор ресурса (namespace/name).",
},
"name": schema.StringAttribute{
Required: true,
Description: "Имя функции. Используется как база для имён Package и HTTPTrigger.",
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"runtime": schema.StringAttribute{
Required: true,
Description: "Runtime функции: nodejs, python, go, ruby, php, perl.",
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"code_dir": schema.StringAttribute{
Required: true,
Description: "Путь к директории с исходным кодом (main.js, main.py, main.go и т.д.).",
},
"url": schema.StringAttribute{
Optional: true,
Description: "Относительный URL для HTTPTrigger. Если не задан — HTTPTrigger не создаётся.",
},
"methods": schema.ListAttribute{
Optional: true,
Computed: true,
ElementType: types.StringType,
Description: "HTTP методы для HTTPTrigger. По умолчанию [\"GET\"].",
},
"namespace": schema.StringAttribute{
Optional: true,
Computed: true,
Description: "Namespace для ресурсов. По умолчанию используется namespace провайдера.",
},
"environment_name": schema.StringAttribute{
Computed: true,
Description: "Имя Environment, который был использован или создан (simple-<runtime>-env).",
},
"package_name": schema.StringAttribute{
Computed: true,
Description: "Имя созданного Package (<name>-pkg).",
},
"trigger_name": schema.StringAttribute{
Computed: true,
Description: "Имя созданного HTTPTrigger (<name>-trigger). Пусто, если url не задан.",
},
"code_hash": schema.StringAttribute{
Computed: true,
Description: "SHA-256 хеш кода из code_dir. Terraform обнаруживает изменения кода по этому полю.",
},
},
}
}
// ModifyPlan пересчитывает code_hash из code_dir, чтобы Terraform видел изменения кода.
func (r *SimpleFunctionResource) ModifyPlan(ctx context.Context, req resource.ModifyPlanRequest, resp *resource.ModifyPlanResponse) {
if req.Plan.Raw.IsNull() {
return
}
var plan simpleFunctionModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
if resp.Diagnostics.HasError() {
return
}
if plan.CodeDir.IsNull() || plan.CodeDir.IsUnknown() || plan.CodeDir.ValueString() == "" {
return
}
deployType := "literal"
if !plan.Runtime.IsNull() && !plan.Runtime.IsUnknown() {
if meta, ok := runtimeRegistry[plan.Runtime.ValueString()]; ok {
deployType = meta.deployType
}
}
codeBytes, err := loadPackageContent(plan.CodeDir.ValueString(), "", deployType)
if err != nil {
// Файл может отсутствовать при первом plan — не ошибка.
return
}
sum := sha256.Sum256(codeBytes)
plan.CodeHash = types.StringValue(fmt.Sprintf("%x", sum))
resp.Diagnostics.Append(resp.Plan.Set(ctx, &plan)...)
}
// Configure получает клиент из provider.Configure().
func (r *SimpleFunctionResource) 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 (при необходимости), Package, Function и HTTPTrigger.
func (r *SimpleFunctionResource) Create(ctx context.Context, req resource.CreateRequest, resp *resource.CreateResponse) {
var plan simpleFunctionModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
if resp.Diagnostics.HasError() {
return
}
namespace := resolveNamespace(plan.Namespace, r.client.Namespace)
meta, ok := runtimeRegistry[plan.Runtime.ValueString()]
if !ok {
resp.Diagnostics.AddError(
"Неподдерживаемый runtime",
fmt.Sprintf("Runtime %q не поддерживается. Допустимые значения: nodejs, python, go, ruby, php, perl.", plan.Runtime.ValueString()),
)
return
}
// 1. Найти или создать environment.
envName, err := findOrCreateSimpleEnv(ctx, r.client, namespace, plan.Runtime.ValueString(), meta)
if err != nil {
resp.Diagnostics.AddError("Ошибка подготовки Environment", err.Error())
return
}
// 2. Загрузить код из code_dir.
codeBytes, err := loadPackageContent(plan.CodeDir.ValueString(), "", meta.deployType)
if err != nil {
resp.Diagnostics.AddError("Ошибка чтения исходного кода", err.Error())
return
}
sum := sha256.Sum256(codeBytes)
codeHash := fmt.Sprintf("%x", sum)
// 3. Создать Package.
pkgName := plan.Name.ValueString() + "-pkg"
pkgObj := buildSimplePackage(pkgName, envName, namespace, codeBytes, meta.deployType)
if _, err = r.client.CreatePackage(ctx, pkgObj); err != nil {
resp.Diagnostics.AddError("Ошибка создания Package", err.Error())
return
}
// 4. Создать Function.
fnObj := buildSimpleFunction(plan.Name.ValueString(), envName, pkgName, meta.entrypoint, namespace)
if _, err = r.client.CreateFunction(ctx, fnObj); err != nil {
_ = r.client.DeletePackage(ctx, namespace, pkgName)
resp.Diagnostics.AddError("Ошибка создания Function", err.Error())
return
}
// 5. Создать HTTPTrigger (только если задан url).
triggerName := ""
if url := plan.URL.ValueString(); url != "" {
triggerName = plan.Name.ValueString() + "-trigger"
methods := simpleMethods(ctx, plan.Methods)
triggerObj := buildSimpleTrigger(triggerName, plan.Name.ValueString(), url, methods, namespace)
if _, err = r.client.CreateHTTPTrigger(ctx, triggerObj); err != nil {
_ = r.client.DeleteFunction(ctx, namespace, plan.Name.ValueString())
_ = r.client.DeletePackage(ctx, namespace, pkgName)
resp.Diagnostics.AddError("Ошибка создания HTTPTrigger", err.Error())
return
}
}
defaultMethods, _ := types.ListValueFrom(ctx, types.StringType, []string{"GET"})
methods := plan.Methods
if methods.IsNull() || methods.IsUnknown() {
methods = defaultMethods
}
state := simpleFunctionModel{
ID: types.StringValue(fmt.Sprintf("%s/%s", namespace, plan.Name.ValueString())),
Name: plan.Name,
Runtime: plan.Runtime,
CodeDir: plan.CodeDir,
URL: plan.URL,
Methods: methods,
Namespace: types.StringValue(namespace),
EnvironmentName: types.StringValue(envName),
PackageName: types.StringValue(pkgName),
TriggerName: types.StringValue(triggerName),
CodeHash: types.StringValue(codeHash),
}
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
}
// Read синхронизирует state с Kubernetes.
func (r *SimpleFunctionResource) Read(ctx context.Context, req resource.ReadRequest, resp *resource.ReadResponse) {
var state simpleFunctionModel
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
namespace := resolveNamespace(state.Namespace, r.client.Namespace)
// Основной ресурс — Function.
if _, err := r.client.GetFunction(ctx, namespace, state.Name.ValueString()); err != nil {
if client.IsNotFound(err) {
resp.State.RemoveResource(ctx)
return
}
resp.Diagnostics.AddError("Ошибка чтения Function", err.Error())
return
}
// Синхронизировать url/methods из реального состояния HTTPTrigger.
if triggerName := state.TriggerName.ValueString(); triggerName != "" {
triggerObj, err := r.client.GetHTTPTrigger(ctx, namespace, triggerName)
if err != nil && !client.IsNotFound(err) {
resp.Diagnostics.AddError("Ошибка чтения HTTPTrigger", err.Error())
return
}
if err == nil {
if urlVal, _, _ := unstructured.NestedString(triggerObj.Object, "spec", "relativeurl"); urlVal != "" {
state.URL = types.StringValue(urlVal)
}
if methodsVal, _, _ := unstructured.NestedStringSlice(triggerObj.Object, "spec", "methods"); len(methodsVal) > 0 {
methodsList, _ := types.ListValueFrom(ctx, types.StringType, methodsVal)
state.Methods = methodsList
}
}
}
resp.Diagnostics.Append(resp.State.Set(ctx, &state)...)
}
// Update обновляет Package (если код изменился) и HTTPTrigger (если url/methods изменились).
func (r *SimpleFunctionResource) Update(ctx context.Context, req resource.UpdateRequest, resp *resource.UpdateResponse) {
var plan simpleFunctionModel
var state simpleFunctionModel
resp.Diagnostics.Append(req.Plan.Get(ctx, &plan)...)
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
namespace := resolveNamespace(plan.Namespace, r.client.Namespace)
meta := runtimeRegistry[plan.Runtime.ValueString()]
// Обновить Package, если изменился код.
newCodeBytes, err := loadPackageContent(plan.CodeDir.ValueString(), "", meta.deployType)
if err != nil {
resp.Diagnostics.AddError("Ошибка чтения исходного кода", err.Error())
return
}
sum := sha256.Sum256(newCodeBytes)
newHash := fmt.Sprintf("%x", sum)
if newHash != state.CodeHash.ValueString() {
pkgName := state.PackageName.ValueString()
existingPkg, err := r.client.GetPackage(ctx, namespace, pkgName)
if err != nil {
resp.Diagnostics.AddError("Ошибка получения Package перед обновлением", err.Error())
return
}
newPkg := buildSimplePackage(pkgName, state.EnvironmentName.ValueString(), namespace, newCodeBytes, meta.deployType)
newPkg.SetResourceVersion(existingPkg.GetResourceVersion())
if _, err = r.client.UpdatePackage(ctx, newPkg); err != nil {
resp.Diagnostics.AddError("Ошибка обновления Package", err.Error())
return
}
}
// Обновить HTTPTrigger при изменении url или methods.
oldURL := state.URL.ValueString()
newURL := plan.URL.ValueString()
triggerName := state.TriggerName.ValueString()
switch {
case oldURL == "" && newURL != "":
triggerName = plan.Name.ValueString() + "-trigger"
methods := simpleMethods(ctx, plan.Methods)
triggerObj := buildSimpleTrigger(triggerName, plan.Name.ValueString(), newURL, methods, namespace)
if _, err = r.client.CreateHTTPTrigger(ctx, triggerObj); err != nil {
resp.Diagnostics.AddError("Ошибка создания HTTPTrigger", err.Error())
return
}
case oldURL != "" && newURL == "":
if triggerName != "" {
if err := r.client.DeleteHTTPTrigger(ctx, namespace, triggerName); err != nil && !client.IsNotFound(err) {
resp.Diagnostics.AddError("Ошибка удаления HTTPTrigger", err.Error())
return
}
}
triggerName = ""
case oldURL != "" && newURL != "":
if triggerName == "" {
triggerName = plan.Name.ValueString() + "-trigger"
}
methods := simpleMethods(ctx, plan.Methods)
existing, err := r.client.GetHTTPTrigger(ctx, namespace, triggerName)
if err != nil {
resp.Diagnostics.AddError("Ошибка получения HTTPTrigger перед обновлением", err.Error())
return
}
newTrigger := buildSimpleTrigger(triggerName, plan.Name.ValueString(), newURL, methods, namespace)
newTrigger.SetResourceVersion(existing.GetResourceVersion())
if _, err = r.client.UpdateHTTPTrigger(ctx, newTrigger); err != nil {
resp.Diagnostics.AddError("Ошибка обновления HTTPTrigger", err.Error())
return
}
}
defaultMethods, _ := types.ListValueFrom(ctx, types.StringType, []string{"GET"})
methods := plan.Methods
if methods.IsNull() || methods.IsUnknown() {
methods = defaultMethods
}
newState := state
newState.CodeDir = plan.CodeDir
newState.URL = plan.URL
newState.Methods = methods
newState.CodeHash = types.StringValue(newHash)
newState.TriggerName = types.StringValue(triggerName)
resp.Diagnostics.Append(resp.State.Set(ctx, &newState)...)
}
// Delete удаляет HTTPTrigger, Function и Package (Environment не удаляется).
func (r *SimpleFunctionResource) Delete(ctx context.Context, req resource.DeleteRequest, resp *resource.DeleteResponse) {
var state simpleFunctionModel
resp.Diagnostics.Append(req.State.Get(ctx, &state)...)
if resp.Diagnostics.HasError() {
return
}
namespace := resolveNamespace(state.Namespace, r.client.Namespace)
if triggerName := state.TriggerName.ValueString(); triggerName != "" {
if err := r.client.DeleteHTTPTrigger(ctx, namespace, triggerName); err != nil && !client.IsNotFound(err) {
resp.Diagnostics.AddError("Ошибка удаления HTTPTrigger", err.Error())
return
}
}
if err := r.client.DeleteFunction(ctx, namespace, state.Name.ValueString()); err != nil && !client.IsNotFound(err) {
resp.Diagnostics.AddError("Ошибка удаления Function", err.Error())
return
}
if pkgName := state.PackageName.ValueString(); pkgName != "" {
if err := r.client.DeletePackage(ctx, namespace, pkgName); err != nil && !client.IsNotFound(err) {
resp.Diagnostics.AddError("Ошибка удаления Package", err.Error())
}
}
}
// ImportState поддерживает импорт в формате namespace/name.
func (r *SimpleFunctionResource) 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)...)
}
// ---------------------------------------------------------------------------
// Вспомогательные функции
// ---------------------------------------------------------------------------
// findOrCreateSimpleEnv ищет environment simple-<runtime>-env; при отсутствии создаёт.
func findOrCreateSimpleEnv(ctx context.Context, fissionClient *client.Client, namespace, runtime string, meta runtimeMeta) (string, error) {
envName := fmt.Sprintf("simple-%s-env", runtime)
_, err := fissionClient.GetEnvironment(ctx, namespace, envName)
if err == nil {
return envName, nil
}
if !client.IsNotFound(err) {
return "", fmt.Errorf("проверка environment %q: %w", envName, err)
}
spec := map[string]interface{}{
"version": int64(2),
"runtime": map[string]interface{}{
"image": meta.image,
},
"poolsize": meta.poolSize,
}
if meta.builderImage != "" {
spec["builder"] = map[string]interface{}{
"image": meta.builderImage,
}
}
envObj := &unstructured.Unstructured{Object: map[string]interface{}{
"apiVersion": "fission.io/v1",
"kind": "Environment",
"metadata": map[string]interface{}{
"name": envName,
"namespace": namespace,
},
"spec": spec,
}}
if _, err = fissionClient.CreateEnvironment(ctx, envObj); err != nil {
return "", fmt.Errorf("создание environment %q: %w", envName, err)
}
return envName, nil
}
// buildSimplePackage формирует CRD объект Package с кодом в base64.
func buildSimplePackage(pkgName, envName, namespace string, codeBytes []byte, deployType string) *unstructured.Unstructured {
literalSource := base64.StdEncoding.EncodeToString(codeBytes)
spec := map[string]interface{}{
"environment": map[string]interface{}{
"name": envName,
"namespace": namespace,
},
}
if deployType == "source" {
spec["source"] = map[string]interface{}{
"type": "literal",
"literal": literalSource,
}
} else {
spec["deployment"] = map[string]interface{}{
"type": "literal",
"literal": literalSource,
}
}
return &unstructured.Unstructured{Object: map[string]interface{}{
"apiVersion": "fission.io/v1",
"kind": "Package",
"metadata": map[string]interface{}{
"name": pkgName,
"namespace": namespace,
},
"spec": spec,
}}
}
// buildSimpleFunction формирует CRD объект Function.
func buildSimpleFunction(name, envName, pkgName, entrypoint, namespace string) *unstructured.Unstructured {
return &unstructured.Unstructured{Object: map[string]interface{}{
"apiVersion": "fission.io/v1",
"kind": "Function",
"metadata": map[string]interface{}{
"name": name,
"namespace": namespace,
},
"spec": map[string]interface{}{
"environment": map[string]interface{}{
"name": envName,
"namespace": namespace,
},
"InvokeStrategy": map[string]interface{}{
"ExecutionStrategy": map[string]interface{}{
"ExecutorType": "poolmgr",
},
"StrategyType": "execution",
},
"package": map[string]interface{}{
"packageref": map[string]interface{}{
"name": pkgName,
"namespace": namespace,
},
"functionName": entrypoint,
},
},
}}
}
// buildSimpleTrigger формирует CRD объект HTTPTrigger.
func buildSimpleTrigger(triggerName, functionName, url string, methods []string, namespace string) *unstructured.Unstructured {
if len(methods) == 0 {
methods = []string{"GET"}
}
return &unstructured.Unstructured{Object: map[string]interface{}{
"apiVersion": "fission.io/v1",
"kind": "HTTPTrigger",
"metadata": map[string]interface{}{
"name": triggerName,
"namespace": namespace,
},
"spec": map[string]interface{}{
"relativeurl": url,
"methods": methods,
"functionref": map[string]interface{}{
"type": "name",
"name": functionName,
},
"createingress": false,
},
}}
}
// simpleMethods извлекает []string из types.List, возвращая ["GET"] по умолчанию.
func simpleMethods(ctx context.Context, methodsList types.List) []string {
if methodsList.IsNull() || methodsList.IsUnknown() || len(methodsList.Elements()) == 0 {
return []string{"GET"}
}
var methods []string
if diags := methodsList.ElementsAs(ctx, &methods, false); diags.HasError() {
return []string{"GET"}
}
if len(methods) == 0 {
return []string{"GET"}
}
return methods
}