feat: provider v0.1.9 — namespace removed from resources, moved to provider block

This commit is contained in:
“Naeel”
2026-03-09 15:12:07 +04:00
parent 0aaeb47b3b
commit 17d32fcb39
23 changed files with 138 additions and 141 deletions
-2
View File
@@ -14,7 +14,6 @@ data "archive_file" "handler_http" {
} }
resource "sless_function" "hello_http" { resource "sless_function" "hello_http" {
namespace = "default"
name = "hello-http" name = "hello-http"
runtime = "nodejs20" runtime = "nodejs20"
entrypoint = "handler-http.handle" entrypoint = "handler-http.handle"
@@ -28,7 +27,6 @@ resource "sless_function" "hello_http" {
} }
resource "sless_trigger" "hello_http" { resource "sless_trigger" "hello_http" {
namespace = "default"
name = "hello-http-trigger" name = "hello-http-trigger"
type = "http" type = "http"
function = sless_function.hello_http.name function = sless_function.hello_http.name
-2
View File
@@ -16,7 +16,6 @@ data "archive_file" "handler_job" {
} }
resource "sless_function" "hello_job" { resource "sless_function" "hello_job" {
namespace = "default"
name = "hello-job" name = "hello-job"
runtime = "nodejs20" runtime = "nodejs20"
entrypoint = "handler-job.handle" entrypoint = "handler-job.handle"
@@ -31,7 +30,6 @@ resource "sless_function" "hello_job" {
# Одноразовый запуск. Все поля immutable — изменение любого пересоздаёт джоб. # Одноразовый запуск. Все поля immutable — изменение любого пересоздаёт джоб.
# run_id: 0 = не запускать, 1+ = запустить. Для повторного запуска увеличь run_id (1→2→3...). # run_id: 0 = не запускать, 1+ = запустить. Для повторного запуска увеличь run_id (1→2→3...).
resource "sless_job" "hello_run" { resource "sless_job" "hello_run" {
namespace = "default"
name = "hello-run" name = "hello-run"
function = sless_function.hello_job.name function = sless_function.hello_job.name
event_json = jsonencode({ numbers = [100, 200, 300] }) event_json = jsonencode({ numbers = [100, 200, 300] })
+4 -3
View File
@@ -8,7 +8,7 @@ terraform {
required_providers { required_providers {
sless = { sless = {
source = "terra.k8c.ru/naeel/sless" source = "terra.k8c.ru/naeel/sless"
version = "~> 0.1.7" version = "~> 0.1.9"
} }
archive = { archive = {
source = "hashicorp/archive" source = "hashicorp/archive"
@@ -18,7 +18,8 @@ terraform {
} }
provider "sless" { provider "sless" {
endpoint = "https://sless-api.kube5s.ru" endpoint = "https://sless-api.kube5s.ru"
token = "dev-token-change-me" token = "dev-token-change-me"
namespace = "default"
} }
-2
View File
@@ -14,7 +14,6 @@
# Джоб создания таблицы notes. # Джоб создания таблицы notes.
# CREATE TABLE IF NOT EXISTS — безопасно запускать повторно, таблица не пересоздаётся. # CREATE TABLE IF NOT EXISTS — безопасно запускать повторно, таблица не пересоздаётся.
resource "sless_job" "notes_table_init" { resource "sless_job" "notes_table_init" {
namespace = "default"
name = "notes-create-table" name = "notes-create-table"
function = sless_function.sql_runner.name function = sless_function.sql_runner.name
wait_timeout_sec = 120 wait_timeout_sec = 120
@@ -32,7 +31,6 @@ resource "sless_job" "notes_table_init" {
resource "sless_job" "notes_index_init" { resource "sless_job" "notes_index_init" {
depends_on = [sless_job.notes_table_init] depends_on = [sless_job.notes_table_init]
namespace = "default"
name = "notes-create-index" name = "notes-create-index"
function = sless_function.sql_runner.name function = sless_function.sql_runner.name
wait_timeout_sec = 60 wait_timeout_sec = 60
+4 -3
View File
@@ -14,7 +14,7 @@ terraform {
# Провайдер для управления serverless функциями через sless API # Провайдер для управления serverless функциями через sless API
sless = { sless = {
source = "terra.k8c.ru/naeel/sless" source = "terra.k8c.ru/naeel/sless"
version = "~> 0.1.7" version = "~> 0.1.9"
} }
# hashicorp/archive — для упаковки исходников в zip перед загрузкой # hashicorp/archive — для упаковки исходников в zip перед загрузкой
archive = { archive = {
@@ -27,6 +27,7 @@ terraform {
# sless провайдер подключается к API кластера. # sless провайдер подключается к API кластера.
# В продакшне token следует передавать через TF_VAR или secrets. # В продакшне token следует передавать через TF_VAR или secrets.
provider "sless" { provider "sless" {
endpoint = "https://sless-api.kube5s.ru" endpoint = "https://sless-api.kube5s.ru"
token = "dev-token-change-me" token = "dev-token-change-me"
namespace = "default"
} }
-2
View File
@@ -18,7 +18,6 @@ data "archive_file" "notes_list_zip" {
# Read-only функция в кластере. # Read-only функция в кластере.
# entrypoint = "notes_list.list_notes" → файл notes_list.py, функция list_notes(). # entrypoint = "notes_list.list_notes" → файл notes_list.py, функция list_notes().
resource "sless_function" "notes_list" { resource "sless_function" "notes_list" {
namespace = "default"
name = "notes-list" name = "notes-list"
runtime = "python3.11" runtime = "python3.11"
entrypoint = "notes_list.list_notes" entrypoint = "notes_list.list_notes"
@@ -36,7 +35,6 @@ resource "sless_function" "notes_list" {
# HTTP-триггер для read-only функции. # HTTP-триггер для read-only функции.
# Создаёт Ingress, URL доступен в outputs.tf. # Создаёт Ingress, URL доступен в outputs.tf.
resource "sless_trigger" "notes_list_http" { resource "sless_trigger" "notes_list_http" {
namespace = "default"
name = "notes-list-http" name = "notes-list-http"
type = "http" type = "http"
function = sless_function.notes_list.name function = sless_function.notes_list.name
-2
View File
@@ -20,7 +20,6 @@ data "archive_file" "notes_crud_zip" {
# CRUD функция в кластере. # CRUD функция в кластере.
# entrypoint = "notes_crud.crud" → файл notes_crud.py, функция crud(). # entrypoint = "notes_crud.crud" → файл notes_crud.py, функция crud().
resource "sless_function" "notes_crud" { resource "sless_function" "notes_crud" {
namespace = "default"
name = "notes" name = "notes"
runtime = "python3.11" runtime = "python3.11"
entrypoint = "notes_crud.crud" entrypoint = "notes_crud.crud"
@@ -39,7 +38,6 @@ resource "sless_function" "notes_crud" {
# Создаёт Ingress в кластере, URL доступен в outputs.tf. # Создаёт Ingress в кластере, URL доступен в outputs.tf.
# Базовый URL: https://sless-api.kube5s.ru/fn/default/notes # Базовый URL: https://sless-api.kube5s.ru/fn/default/notes
resource "sless_trigger" "notes_crud_http" { resource "sless_trigger" "notes_crud_http" {
namespace = "default"
name = "notes-http" name = "notes-http"
type = "http" type = "http"
function = sless_function.notes_crud.name function = sless_function.notes_crud.name
-1
View File
@@ -17,7 +17,6 @@ data "archive_file" "sql_runner_zip" {
# entrypoint = "sql_runner.run_sql" → файл sql_runner.py, функция run_sql(). # entrypoint = "sql_runner.run_sql" → файл sql_runner.py, функция run_sql().
# memory_mb=128 достаточно — DDL запросы не требуют памяти на вычисления. # memory_mb=128 достаточно — DDL запросы не требуют памяти на вычисления.
resource "sless_function" "sql_runner" { resource "sless_function" "sql_runner" {
namespace = "default"
name = "sql-runner" name = "sql-runner"
runtime = "python3.11" runtime = "python3.11"
entrypoint = "sql_runner.run_sql" entrypoint = "sql_runner.run_sql"
+4 -3
View File
@@ -6,7 +6,7 @@ terraform {
required_providers { required_providers {
sless = { sless = {
source = "terra.k8c.ru/naeel/sless" source = "terra.k8c.ru/naeel/sless"
version = "~> 0.1.7" version = "~> 0.1.9"
} }
archive = { archive = {
source = "hashicorp/archive" source = "hashicorp/archive"
@@ -16,6 +16,7 @@ terraform {
} }
provider "sless" { provider "sless" {
endpoint = "https://sless-api.kube5s.ru" endpoint = "https://sless-api.kube5s.ru"
token = "dev-token-change-me" token = "dev-token-change-me"
namespace = "default"
} }
-2
View File
@@ -13,7 +13,6 @@ data "archive_file" "handler_pg_query" {
} }
resource "sless_function" "pg_query" { resource "sless_function" "pg_query" {
namespace = "default"
name = "pg-query" name = "pg-query"
runtime = "python3.11" runtime = "python3.11"
entrypoint = "handler.handle" entrypoint = "handler.handle"
@@ -31,7 +30,6 @@ resource "sless_function" "pg_query" {
} }
resource "sless_trigger" "pg_query_http" { resource "sless_trigger" "pg_query_http" {
namespace = "default"
name = "pg-query-http" name = "pg-query-http"
type = "http" type = "http"
function = sless_function.pg_query.name function = sless_function.pg_query.name
+20 -6
View File
@@ -1,13 +1,26 @@
# Создано: 2026-03-09 # Создано: 2026-03-09
# main.tf — точка входа для примера simple-node. # main.tf — пример: запустить один раз скрипт при деплое и передать его результат в функцию.
# Демонстрирует цепочку: sless_job (one-shot) → sless_function (http). # То же самое что simple-python, но на Node.js 20.
# Аналог simple-python, но на Node.js 20. #
# Как это работает:
# 1. При «terraform apply» запускается скрипт-джоб (time_getter)
# 2. Скрипт возвращает JSON с текущим временем
# 3. Terraform подхватывает этот JSON и передаёт в переменную окружения HTTP-функции (time_display)
# 4. Функция отдаёт время при каждом запросе
#
# Зачем такое нужно:
# Если данные нужны функции, но считаются один раз при деплое —
# напишите логику в джоб, а результат передайте через env_vars.
# Например: получить токен, версию схемы БД, время деплоя и т.д.
#
# namespace задаётся один раз здесь, в блоке provider.
# В ресурсах (sless_function, sless_trigger, sless_job) namespace НЕ указывается.
terraform { terraform {
required_providers { required_providers {
sless = { sless = {
source = "terra.k8c.ru/naeel/sless" source = "terra.k8c.ru/naeel/sless"
version = "~> 0.1.8" version = "~> 0.1.9"
} }
archive = { archive = {
source = "hashicorp/archive" source = "hashicorp/archive"
@@ -17,6 +30,7 @@ terraform {
} }
provider "sless" { provider "sless" {
endpoint = "https://sless-api.kube5s.ru" endpoint = "https://sless-api.kube5s.ru"
token = "dev-token-change-me" token = "dev-token-change-me"
namespace = "default"
} }
+5 -3
View File
@@ -1,12 +1,14 @@
# Создано: 2026-03-09 # Создано: 2026-03-09
# outputs.tf — полезные значения после terraform apply. # outputs.tf — что выводит terraform после apply.
# Адрес вашей функции — откройте в браузере или вставьте в curl
output "display_url" { output "display_url" {
description = "URL HTTP-триггера функции time_display" description = "URL функции time_display"
value = sless_trigger.display_http.url value = sless_trigger.display_http.url
} }
# Что вернул скрипт-джоб — именно это передано в функцию как JOB_TIME
output "job_result" { output "job_result" {
description = "Stdout джоба (return value функции getTime)" description = "Результат выполнения скрипта time_getter"
value = sless_job.run_getter.message value = sless_job.run_getter.message
} }
+11 -11
View File
@@ -1,35 +1,35 @@
# Создано: 2026-03-09 # Создано: 2026-03-09
# time-display.tf — постоянная HTTP-функция, получающая данные от джоба. # time-display.tf — HTTP-функция, доступная по URL после apply.
# Получает результат джоба (из time-getter.tf) через переменную окружения JOB_TIME.
# Упаковываем код функции в zip # Упаковываем код функции в zip для загрузки
data "archive_file" "time_display_zip" { data "archive_file" "time_display_zip" {
type = "zip" type = "zip"
source_dir = "${path.module}/code/time_display" source_dir = "${path.module}/code/time_display"
output_path = "${path.module}/dist/time_display.zip" output_path = "${path.module}/dist/time_display.zip"
} }
# HTTP-функция: постоянный Deployment, читает JOB_TIME из env # HTTP-функция — отвечает на запросы по URL из outputs.tf
resource "sless_function" "time_display" { resource "sless_function" "time_display" {
namespace = "default" name = "simple-node-time-display" # уникальное имя в namespace
name = "simple-node-time-display"
runtime = "nodejs20" runtime = "nodejs20"
entrypoint = "time_display.showTime" entrypoint = "time_display.showTime" # файл.функция в code/time_display/
memory_mb = 64 memory_mb = 64
# Значение вычислено джобом при apply и зафиксировано в state # Передаём результат джоба в функцию через переменную окружения.
# В коде функции: process.env.JOB_TIME
env_vars = { env_vars = {
JOB_TIME = sless_job.run_getter.message JOB_TIME = sless_job.run_getter.message
} }
code_path = data.archive_file.time_display_zip.output_path code_path = data.archive_file.time_display_zip.output_path
code_hash = filesha256("${path.module}/code/time_display/time_display.js") code_hash = filesha256("${path.module}/code/time_display/time_display.js") # для пересборки при изменении кода
depends_on = [sless_job.run_getter] depends_on = [sless_job.run_getter] # ждём завершения джоба перед деплоем функции
} }
# HTTP-триггер — публикует функцию по URL # Публикуем функцию по HTTP — URL будет в outputs.tf
resource "sless_trigger" "display_http" { resource "sless_trigger" "display_http" {
namespace = "default"
name = "simple-node-display-http" name = "simple-node-display-http"
type = "http" type = "http"
function = sless_function.time_display.name function = sless_function.time_display.name
+13 -14
View File
@@ -1,36 +1,35 @@
# Создано: 2026-03-09 # Создано: 2026-03-09
# time-getter.tf — одноразовая функция + джоб запускающий её при apply. # time-getter.tf — скрипт который запускается ОДИН РАЗ при terraform apply.
# sless_job.run_getter.message после apply содержит stdout runner-а: # После запуска его результат доступен через: sless_job.run_getter.message
# {"time":"2026-03-09T12:34:56.789Z"} # Смотри time-display.tf — там этот результат передаётся в функцию.
# Это значение terraform записывает в env JOB_TIME функции time_display.
# Упаковываем код функции в zip # Упаковываем код скрипта в zip для загрузки
data "archive_file" "time_getter_zip" { data "archive_file" "time_getter_zip" {
type = "zip" type = "zip"
source_dir = "${path.module}/code/time_getter" source_dir = "${path.module}/code/time_getter"
output_path = "${path.module}/dist/time_getter.zip" output_path = "${path.module}/dist/time_getter.zip"
} }
# Функция-вычислитель: запускается только джобом, не имеет HTTP-триггера # Функция для скрипта — без HTTP-триггера, вызывается только через джоб ниже
resource "sless_function" "time_getter" { resource "sless_function" "time_getter" {
namespace = "default" name = "simple-node-time-getter" # уникальное имя в namespace
name = "simple-node-time-getter"
runtime = "nodejs20" runtime = "nodejs20"
entrypoint = "time_getter.getTime" entrypoint = "time_getter.getTime" # файл.функция в code/time_getter/
memory_mb = 64 memory_mb = 64
code_path = data.archive_file.time_getter_zip.output_path code_path = data.archive_file.time_getter_zip.output_path
code_hash = filesha256("${path.module}/code/time_getter/time_getter.js") code_hash = filesha256("${path.module}/code/time_getter/time_getter.js") # для пересборки при изменении кода
} }
# Джоб: запускает time_getter один раз при terraform apply. # Джоб запускает функцию time_getter один раз прямо при apply.
# run_id = 1 означает «запустить». Если увеличить (2, 3...) — запустится снова.
# После завершения: sless_job.run_getter.message = то что вернула функция
resource "sless_job" "run_getter" { resource "sless_job" "run_getter" {
namespace = "default"
name = "simple-node-getter-run" name = "simple-node-getter-run"
function = sless_function.time_getter.name function = sless_function.time_getter.name
run_id = 1 run_id = 1
wait_timeout_sec = 120 wait_timeout_sec = 120 # сколько секунд ждать завершения скрипта
event_json = "{}" event_json = "{}" # входные данные для скрипта (пусто — данные не нужны)
depends_on = [sless_function.time_getter] depends_on = [sless_function.time_getter]
} }
+19 -7
View File
@@ -1,14 +1,25 @@
# Создано: 2026-03-09 # Создано: 2026-03-09
# main.tf — точка входа для примера simple-python. # main.tf — пример: запустить один раз скрипт при деплое и передать его результат в функцию.
# Демонстрирует цепочку: sless_job (one-shot) → sless_function (http). #
# Джоб запускается при terraform apply, его stdout (JSON) попадает в # Как это работает:
# sless_job.run_getter.message и передаётся функции через env_vars. # 1. При «terraform apply» запускается скрипт-джоб (time_getter)
# 2. Скрипт возвращает JSON с текущим временем
# 3. Terraform подхватывает этот JSON и передаёт в переменную окружения HTTP-функции (time_display)
# 4. Функция отдаёт время при каждом запросе
#
# Зачем такое нужно:
# Если данные нужны функции, но считаются один раз при деплое —
# напишите логику в джоб, а результат передайте через env_vars.
# Например: получить токен, версию схемы БД, время деплоя и т.д.
#
# namespace задаётся один раз здесь, в блоке provider.
# В ресурсах (sless_function, sless_trigger, sless_job) namespace НЕ указывается.
terraform { terraform {
required_providers { required_providers {
sless = { sless = {
source = "terra.k8c.ru/naeel/sless" source = "terra.k8c.ru/naeel/sless"
version = "~> 0.1.8" version = "~> 0.1.9"
} }
archive = { archive = {
source = "hashicorp/archive" source = "hashicorp/archive"
@@ -18,6 +29,7 @@ terraform {
} }
provider "sless" { provider "sless" {
endpoint = "https://sless-api.kube5s.ru" endpoint = "https://sless-api.kube5s.ru"
token = "dev-token-change-me" token = "dev-token-change-me"
namespace = "default"
} }
+5 -5
View File
@@ -1,14 +1,14 @@
# Создано: 2026-03-09 # Создано: 2026-03-09
# outputs.tf — полезные значения после terraform apply. # outputs.tf — что выводит terraform после apply.
# URL HTTP-триггера для тестирования функции display # Адрес вашей функции — откройте в браузере или вставьте в curl
output "display_url" { output "display_url" {
description = "URL HTTP-триггера функции time_display" description = "URL функции time_display"
value = sless_trigger.display_http.url value = sless_trigger.display_http.url
} }
# Результат джобаJSON строка {"time": "..."} из stdout функции get_time() # Что вернул скрипт-джоб — именно это передано в функцию как JOB_TIME
output "job_result" { output "job_result" {
description = "Stdout джоба (return value функции get_time)" description = "Результат выполнения скрипта time_getter"
value = sless_job.run_getter.message value = sless_job.run_getter.message
} }
+11 -15
View File
@@ -1,39 +1,35 @@
# Создано: 2026-03-09 # Создано: 2026-03-09
# time-display.tf — постоянная HTTP-функция, получающая данные от джоба. # time-display.tf — HTTP-функция, доступная по URL после apply.
# JOB_TIME берётся из sless_job.run_getter.message (stdout джоба) — # Получает результат джоба (из time-getter.tf) через переменную окружения JOB_TIME.
# это JSON строка {"time": "..."}, terraform передаёт её в env целиком.
# Функция парсит её через os.environ, а не через event — демонстрирует
# паттерн "данные вычислены один раз при деплое, используются на каждый запрос".
# Упаковываем код функции в zip # Упаковываем код функции в zip для загрузки
data "archive_file" "time_display_zip" { data "archive_file" "time_display_zip" {
type = "zip" type = "zip"
source_dir = "${path.module}/code/time_display" source_dir = "${path.module}/code/time_display"
output_path = "${path.module}/dist/time_display.zip" output_path = "${path.module}/dist/time_display.zip"
} }
# HTTP-функция: постоянный Deployment, читает JOB_TIME из env # HTTP-функция — отвечает на запросы по URL из outputs.tf
resource "sless_function" "time_display" { resource "sless_function" "time_display" {
namespace = "default" name = "simple-py-time-display" # уникальное имя в namespace
name = "simple-py-time-display"
runtime = "python3.11" runtime = "python3.11"
entrypoint = "time_display.show_time" entrypoint = "time_display.show_time" # файл.функция в code/time_display/
memory_mb = 64 memory_mb = 64
# Значение вычислено джобом при apply и зафиксировано в state # Передаём результат джоба в функцию через переменную окружения.
# В коде функции: os.environ.get("JOB_TIME")
env_vars = { env_vars = {
JOB_TIME = sless_job.run_getter.message JOB_TIME = sless_job.run_getter.message
} }
code_path = data.archive_file.time_display_zip.output_path code_path = data.archive_file.time_display_zip.output_path
code_hash = filesha256("${path.module}/code/time_display/time_display.py") code_hash = filesha256("${path.module}/code/time_display/time_display.py") # для пересборки при изменении кода
depends_on = [sless_job.run_getter] depends_on = [sless_job.run_getter] # ждём завершения джоба перед деплоем функции
} }
# HTTP-триггер — публикует функцию по URL # Публикуем функцию по HTTP — URL будет в outputs.tf
resource "sless_trigger" "display_http" { resource "sless_trigger" "display_http" {
namespace = "default"
name = "simple-py-display-http" name = "simple-py-display-http"
type = "http" type = "http"
function = sless_function.time_display.name function = sless_function.time_display.name
+13 -16
View File
@@ -1,38 +1,35 @@
# Создано: 2026-03-09 # Создано: 2026-03-09
# time-getter.tf — одноразовая функция + джоб запускающий её при apply. # time-getter.tf — скрипт который запускается ОДИН РАЗ при terraform apply.
# sless_job.run_getter.message после apply содержит stdout runner-а: # После запуска его результат доступен через: sless_job.run_getter.message
# {"time": "2026-03-09T12:34:56.789012+00:00"} # Смотри time-display.tf — там этот результат передаётся в функцию.
# Это значение terraform записывает в env JOB_TIME функции time_display.
# Упаковываем код функции в zip # Упаковываем код скрипта в zip для загрузки
data "archive_file" "time_getter_zip" { data "archive_file" "time_getter_zip" {
type = "zip" type = "zip"
source_dir = "${path.module}/code/time_getter" source_dir = "${path.module}/code/time_getter"
output_path = "${path.module}/dist/time_getter.zip" output_path = "${path.module}/dist/time_getter.zip"
} }
# Функция-вычислитель: запускается только джобом, не имеет HTTP-триггера # Функция для скрипта — без HTTP-триггера, вызывается только через джоб ниже
resource "sless_function" "time_getter" { resource "sless_function" "time_getter" {
namespace = "default" name = "simple-py-time-getter" # уникальное имя в namespace
name = "simple-py-time-getter"
runtime = "python3.11" runtime = "python3.11"
entrypoint = "time_getter.get_time" entrypoint = "time_getter.get_time" # файл.функция в code/time_getter/
memory_mb = 64 memory_mb = 64
code_path = data.archive_file.time_getter_zip.output_path code_path = data.archive_file.time_getter_zip.output_path
code_hash = filesha256("${path.module}/code/time_getter/time_getter.py") code_hash = filesha256("${path.module}/code/time_getter/time_getter.py") # для пересборки при изменении кода
} }
# Джоб: запускает time_getter один раз при terraform apply. # Джоб запускает функцию time_getter один раз прямо при apply.
# run_id > 0 — разрешение на запуск (run_id=0 пропускается оператором). # run_id = 1 означает «запустить». Если увеличить (2, 3...) — запустится снова.
# После завершения message = stdout пода = json возвращённый get_time(). # После завершения: sless_job.run_getter.message = то что вернула функция
resource "sless_job" "run_getter" { resource "sless_job" "run_getter" {
namespace = "default"
name = "simple-py-getter-run" name = "simple-py-getter-run"
function = sless_function.time_getter.name function = sless_function.time_getter.name
run_id = 1 run_id = 1
wait_timeout_sec = 120 wait_timeout_sec = 120 # сколько секунд ждать завершения скрипта
event_json = "{}" event_json = "{}" # входные данные для скрипта (пусто — данные не нужны)
depends_on = [sless_function.time_getter] depends_on = [sless_function.time_getter]
} }
+3 -1
View File
@@ -23,14 +23,16 @@ type Client struct {
httpClient *http.Client httpClient *http.Client
endpoint string endpoint string
token string token string
Namespace string // берётся из provider {}, пользователь не касается
} }
// New создаёт клиент. endpoint — базовый URL оператора (без trailing slash). // New создаёт клиент. endpoint — базовый URL оператора (без trailing slash).
func New(endpoint, token string) *Client { func New(endpoint, token, namespace string) *Client {
return &Client{ return &Client{
httpClient: &http.Client{Timeout: 30 * time.Second}, httpClient: &http.Client{Timeout: 30 * time.Second},
endpoint: endpoint, endpoint: endpoint,
token: token, token: token,
Namespace: namespace,
} }
} }
@@ -28,8 +28,9 @@ type SlessProvider struct {
// SlessProviderModel — конфигурация блока provider {} в .tf файле. // SlessProviderModel — конфигурация блока provider {} в .tf файле.
type SlessProviderModel struct { type SlessProviderModel struct {
Endpoint types.String `tfsdk:"endpoint"` Endpoint types.String `tfsdk:"endpoint"`
Token types.String `tfsdk:"token"` Token types.String `tfsdk:"token"`
Namespace types.String `tfsdk:"namespace"`
} }
// New возвращает фабрику провайдера — точная копия паттерна nubes. // New возвращает фабрику провайдера — точная копия паттерна nubes.
@@ -56,6 +57,12 @@ func (p *SlessProvider) Schema(_ context.Context, _ provider.SchemaRequest, resp
Optional: true, Optional: true,
Sensitive: true, Sensitive: true,
}, },
// namespace — окружение/проект в кластере. Задаётся один раз здесь.
// В ресурсах (sless_function, sless_trigger, sless_job) namespace НЕ указывается.
"namespace": schema.StringAttribute{
MarkdownDescription: "Пространство имён (окружение). Задаётся один раз для всех ресурсов.",
Required: true,
},
}, },
} }
} }
@@ -87,7 +94,9 @@ func (p *SlessProvider) Configure(ctx context.Context, req provider.ConfigureReq
} }
} }
c := client.New(endpoint, token) namespace := config.Namespace.ValueString()
c := client.New(endpoint, token, namespace)
resp.ResourceData = c resp.ResourceData = c
resp.DataSourceData = c resp.DataSourceData = c
} }
@@ -51,7 +51,6 @@ func NewFunctionResource() resource.Resource {
// FunctionModel — модель состояния terraform для sless_function. // FunctionModel — модель состояния terraform для sless_function.
type FunctionModel struct { type FunctionModel struct {
Namespace types.String `tfsdk:"namespace"`
Name types.String `tfsdk:"name"` Name types.String `tfsdk:"name"`
Runtime types.String `tfsdk:"runtime"` Runtime types.String `tfsdk:"runtime"`
Entrypoint types.String `tfsdk:"entrypoint"` Entrypoint types.String `tfsdk:"entrypoint"`
@@ -75,13 +74,7 @@ func (r *FunctionResource) Metadata(_ context.Context, req resource.MetadataRequ
func (r *FunctionResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { func (r *FunctionResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{ resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{ Attributes: map[string]schema.Attribute{
// namespace + name — immutable, смена требует пересоздания ресурса // name — immutable, смена требует пересоздания ресурса
"namespace": schema.StringAttribute{
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"name": schema.StringAttribute{ "name": schema.StringAttribute{
Required: true, Required: true,
PlanModifiers: []planmodifier.String{ PlanModifiers: []planmodifier.String{
@@ -175,7 +168,7 @@ func (r *FunctionResource) Create(ctx context.Context, req resource.CreateReques
return return
} }
ns := plan.Namespace.ValueString() ns := r.client.Namespace
envVars, d := mapToStringMap(ctx, plan.EnvVars) envVars, d := mapToStringMap(ctx, plan.EnvVars)
resp.Diagnostics.Append(d...) resp.Diagnostics.Append(d...)
if resp.Diagnostics.HasError() { if resp.Diagnostics.HasError() {
@@ -221,7 +214,7 @@ func (r *FunctionResource) Read(ctx context.Context, req resource.ReadRequest, r
return return
} }
fn, err := r.client.GetFunction(ctx, state.Namespace.ValueString(), state.Name.ValueString()) fn, err := r.client.GetFunction(ctx, r.client.Namespace, state.Name.ValueString())
if err != nil { if err != nil {
resp.Diagnostics.AddError("read function", err.Error()) resp.Diagnostics.AddError("read function", err.Error())
return return
@@ -243,7 +236,7 @@ func (r *FunctionResource) Update(ctx context.Context, req resource.UpdateReques
return return
} }
ns := plan.Namespace.ValueString() ns := r.client.Namespace
name := plan.Name.ValueString() name := plan.Name.ValueString()
envVars, d := mapToStringMap(ctx, plan.EnvVars) envVars, d := mapToStringMap(ctx, plan.EnvVars)
@@ -293,7 +286,7 @@ func (r *FunctionResource) Delete(ctx context.Context, req resource.DeleteReques
return return
} }
if err := r.client.DeleteFunction(ctx, state.Namespace.ValueString(), state.Name.ValueString()); err != nil { if err := r.client.DeleteFunction(ctx, r.client.Namespace, state.Name.ValueString()); err != nil {
resp.Diagnostics.AddError("delete function", err.Error()) resp.Diagnostics.AddError("delete function", err.Error())
} }
} }
@@ -307,7 +300,6 @@ func fnToModel(plan FunctionModel, fn *client.FunctionResponse) FunctionModel {
buildTimeoutSec = types.Int64Value(defaultBuildTimeoutSec) buildTimeoutSec = types.Int64Value(defaultBuildTimeoutSec)
} }
return FunctionModel{ return FunctionModel{
Namespace: types.StringValue(fn.Namespace),
Name: types.StringValue(fn.Name), Name: types.StringValue(fn.Name),
Runtime: types.StringValue(fn.Runtime), Runtime: types.StringValue(fn.Runtime),
Entrypoint: types.StringValue(fn.Entrypoint), Entrypoint: types.StringValue(fn.Entrypoint),
@@ -49,7 +49,6 @@ func NewJobResource() resource.Resource {
// JobModel — модель состояния terraform для sless_job. // JobModel — модель состояния terraform для sless_job.
type JobModel struct { type JobModel struct {
Namespace types.String `tfsdk:"namespace"`
Name types.String `tfsdk:"name"` Name types.String `tfsdk:"name"`
Function types.String `tfsdk:"function"` Function types.String `tfsdk:"function"`
EventJSON types.String `tfsdk:"event_json"` EventJSON types.String `tfsdk:"event_json"`
@@ -73,12 +72,7 @@ func (r *JobResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *
MarkdownDescription: "Одноразовый запуск serverless функции. terraform apply блокируется до завершения джоба.", MarkdownDescription: "Одноразовый запуск serverless функции. terraform apply блокируется до завершения джоба.",
Attributes: map[string]schema.Attribute{ Attributes: map[string]schema.Attribute{
// Все input-поля immutable — джоб нельзя "изменить", только пересоздать. // Все input-поля immutable — джоб нельзя "изменить", только пересоздать.
"namespace": schema.StringAttribute{
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"name": schema.StringAttribute{ "name": schema.StringAttribute{
Required: true, Required: true,
PlanModifiers: []planmodifier.String{ PlanModifiers: []planmodifier.String{
@@ -160,7 +154,7 @@ func (r *JobResource) Create(ctx context.Context, req resource.CreateRequest, re
return return
} }
ns := plan.Namespace.ValueString() ns := r.client.Namespace
eventJSON := plan.EventJSON.ValueString() eventJSON := plan.EventJSON.ValueString()
if eventJSON == "" { if eventJSON == "" {
eventJSON = "{}" eventJSON = "{}"
@@ -188,7 +182,6 @@ func (r *JobResource) Create(ctx context.Context, req resource.CreateRequest, re
waitTimeoutSec = types.Int64Value(defaultWaitTimeoutSec) waitTimeoutSec = types.Int64Value(defaultWaitTimeoutSec)
} }
resp.Diagnostics.Append(resp.State.Set(ctx, JobModel{ resp.Diagnostics.Append(resp.State.Set(ctx, JobModel{
Namespace: types.StringValue(plan.Namespace.ValueString()),
Name: types.StringValue(plan.Name.ValueString()), Name: types.StringValue(plan.Name.ValueString()),
Function: types.StringValue(plan.Function.ValueString()), Function: types.StringValue(plan.Function.ValueString()),
EventJSON: plan.EventJSON, EventJSON: plan.EventJSON,
@@ -223,7 +216,7 @@ func (r *JobResource) Read(ctx context.Context, req resource.ReadRequest, resp *
return return
} }
j, err := r.client.GetJob(ctx, state.Namespace.ValueString(), state.Name.ValueString()) j, err := r.client.GetJob(ctx, r.client.Namespace, state.Name.ValueString())
if err != nil { if err != nil {
resp.Diagnostics.AddError("read job", err.Error()) resp.Diagnostics.AddError("read job", err.Error())
return return
@@ -250,7 +243,7 @@ func (r *JobResource) Delete(ctx context.Context, req resource.DeleteRequest, re
return return
} }
if err := r.client.DeleteJob(ctx, state.Namespace.ValueString(), state.Name.ValueString()); err != nil { if err := r.client.DeleteJob(ctx, r.client.Namespace, state.Name.ValueString()); err != nil {
resp.Diagnostics.AddError("delete job", err.Error()) resp.Diagnostics.AddError("delete job", err.Error())
} }
} }
@@ -262,7 +255,6 @@ func jobToModel(plan JobModel, j *client.JobResponse) JobModel {
waitTimeoutSec = types.Int64Value(defaultWaitTimeoutSec) waitTimeoutSec = types.Int64Value(defaultWaitTimeoutSec)
} }
return JobModel{ return JobModel{
Namespace: types.StringValue(j.Namespace),
Name: types.StringValue(j.Name), Name: types.StringValue(j.Name),
Function: types.StringValue(j.FunctionRef), Function: types.StringValue(j.FunctionRef),
EventJSON: plan.EventJSON, EventJSON: plan.EventJSON,
@@ -35,7 +35,6 @@ func NewTriggerResource() resource.Resource {
// TriggerModel — модель состояния terraform для sless_trigger. // TriggerModel — модель состояния terraform для sless_trigger.
type TriggerModel struct { type TriggerModel struct {
Namespace types.String `tfsdk:"namespace"`
Name types.String `tfsdk:"name"` Name types.String `tfsdk:"name"`
Type types.String `tfsdk:"type"` Type types.String `tfsdk:"type"`
FunctionRef types.String `tfsdk:"function"` FunctionRef types.String `tfsdk:"function"`
@@ -54,12 +53,6 @@ func (r *TriggerResource) Metadata(_ context.Context, req resource.MetadataReque
func (r *TriggerResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) { func (r *TriggerResource) Schema(_ context.Context, _ resource.SchemaRequest, resp *resource.SchemaResponse) {
resp.Schema = schema.Schema{ resp.Schema = schema.Schema{
Attributes: map[string]schema.Attribute{ Attributes: map[string]schema.Attribute{
"namespace": schema.StringAttribute{
Required: true,
PlanModifiers: []planmodifier.String{
stringplanmodifier.RequiresReplace(),
},
},
"name": schema.StringAttribute{ "name": schema.StringAttribute{
Required: true, Required: true,
PlanModifiers: []planmodifier.String{ PlanModifiers: []planmodifier.String{
@@ -136,7 +129,7 @@ func (r *TriggerResource) Create(ctx context.Context, req resource.CreateRequest
return return
} }
tr, err := r.client.CreateTrigger(ctx, plan.Namespace.ValueString(), client.TriggerRequest{ tr, err := r.client.CreateTrigger(ctx, r.client.Namespace, client.TriggerRequest{
Name: plan.Name.ValueString(), Name: plan.Name.ValueString(),
Type: plan.Type.ValueString(), Type: plan.Type.ValueString(),
FunctionRef: plan.FunctionRef.ValueString(), FunctionRef: plan.FunctionRef.ValueString(),
@@ -151,7 +144,7 @@ func (r *TriggerResource) Create(ctx context.Context, req resource.CreateRequest
// Контроллер заполняет status.URL асинхронно — ждём до 30 секунд. // Контроллер заполняет status.URL асинхронно — ждём до 30 секунд.
// Без этого URL остаётся пустым в state и требует ручного terraform refresh. // Без этого URL остаётся пустым в state и требует ручного terraform refresh.
if tr.URL == "" && plan.Type.ValueString() == "http" { if tr.URL == "" && plan.Type.ValueString() == "http" {
ns := plan.Namespace.ValueString() ns := r.client.Namespace
name := plan.Name.ValueString() name := plan.Name.ValueString()
deadline := time.Now().Add(30 * time.Second) deadline := time.Now().Add(30 * time.Second)
for time.Now().Before(deadline) { for time.Now().Before(deadline) {
@@ -174,7 +167,7 @@ func (r *TriggerResource) Read(ctx context.Context, req resource.ReadRequest, re
return return
} }
tr, err := r.client.GetTrigger(ctx, state.Namespace.ValueString(), state.Name.ValueString()) tr, err := r.client.GetTrigger(ctx, r.client.Namespace, state.Name.ValueString())
if err != nil { if err != nil {
resp.Diagnostics.AddError("read trigger", err.Error()) resp.Diagnostics.AddError("read trigger", err.Error())
return return
@@ -198,7 +191,7 @@ func (r *TriggerResource) Update(ctx context.Context, req resource.UpdateRequest
} }
enabled := plan.Enabled.ValueBool() enabled := plan.Enabled.ValueBool()
tr, err := r.client.UpdateTrigger(ctx, plan.Namespace.ValueString(), plan.Name.ValueString(), client.TriggerUpdateRequest{ tr, err := r.client.UpdateTrigger(ctx, r.client.Namespace, plan.Name.ValueString(), client.TriggerUpdateRequest{
Enabled: &enabled, Enabled: &enabled,
}) })
if err != nil { if err != nil {
@@ -216,7 +209,7 @@ func (r *TriggerResource) Delete(ctx context.Context, req resource.DeleteRequest
return return
} }
if err := r.client.DeleteTrigger(ctx, state.Namespace.ValueString(), state.Name.ValueString()); err != nil { if err := r.client.DeleteTrigger(ctx, r.client.Namespace, state.Name.ValueString()); err != nil {
resp.Diagnostics.AddError("delete trigger", err.Error()) resp.Diagnostics.AddError("delete trigger", err.Error())
} }
} }
@@ -230,7 +223,6 @@ func trToModel(tr *client.TriggerResponse) TriggerModel {
schedule = types.StringValue(tr.Schedule) schedule = types.StringValue(tr.Schedule)
} }
return TriggerModel{ return TriggerModel{
Namespace: types.StringValue(tr.Namespace),
Name: types.StringValue(tr.Name), Name: types.StringValue(tr.Name),
Type: types.StringValue(tr.Type), Type: types.StringValue(tr.Type),
FunctionRef: types.StringValue(tr.FunctionRef), FunctionRef: types.StringValue(tr.FunctionRef),