@@ -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
}