From 547994dc55f83d5e6233d295bc90cf18c43e0411 Mon Sep 17 00:00:00 2001 From: Naeel Date: Thu, 26 Mar 2026 08:33:03 +0300 Subject: [PATCH] examples: DEVfromGround vc_org, NODEJS, VM, POSTGRES updates --- .gitignore | 2 + doc/errors/log.md | 56 ++++++ examples/DEVfromGround/main.tf | 2 +- examples/DEVfromGround/vc_org.tf | 10 +- examples/NODEJS/main.tf | 32 ++++ examples/NODEJS/nodejs.tf | 22 +++ examples/POSTGRES/code/calc-node/handler.js | 115 +---------- examples/POSTGRES/code/calc-python/handler.py | 181 ++++++++---------- examples/POSTGRES/functions.tf | 8 +- examples/POSTGRES/main.tf | 2 +- examples/VM/vm.tf | 3 +- 11 files changed, 216 insertions(+), 217 deletions(-) create mode 100644 examples/NODEJS/main.tf create mode 100644 examples/NODEJS/nodejs.tf diff --git a/.gitignore b/.gitignore index 9ae5e6b..7e3b6f7 100644 --- a/.gitignore +++ b/.gitignore @@ -69,3 +69,5 @@ event-dispatcher # build artifacts /sless examples/POSTGRES/stress_log*.txt +examples/VM/vm_key +examples/VM/vm_key.pub diff --git a/doc/errors/log.md b/doc/errors/log.md index 3cf97e7..7be93a2 100644 --- a/doc/errors/log.md +++ b/doc/errors/log.md @@ -4,6 +4,62 @@ --- +## 2026-03-26 — БАГ ГЕНЕРАТОРА: modify-only поля помечаются Required в schema ресурса + +``` +Error: Missing required argument + on vc_org.tf line 5, in resource "nubes_vc_org" "dev_org": + 5: resource "nubes_vc_org" "dev_org" { +The argument "v_i_p_configure" is required, but no definition was found. +The argument "resource_name" is required, but no definition was found. +``` + +### Причина + +Генератор (`~/terra/terraform/devops/`) при создании Go-кода ресурса (`19_vc_org_resource.go`) +помечает **все операции ресурса** как `Required` в схеме, включая поля, +которые нужны только для операции `modify` (не для `create`). + +Конкретный пример: `vIPConfigure` (код параметра 662) — это поле операции `modify`, +но попадает в schema с `Required: true`: + +```go +"v_i_p_configure": schema.StringAttribute{Required: true}, +``` + +В YAML-описании сервиса (19_vc_org.yaml) `vIPConfigure` объявлен только под +`operations.modify.params`, а не под `operations.create.params`. + +### Что нужно исправить в генераторе + +В `~/terra/terraform/devops/` (файлы `02_generate_resources_and_docs*.go/sh`): + +Поля, принадлежащие только операции `modify` (или другим не-create операциям), +должны генерироваться как **`Optional: true, Computed: true`**, а не `Required: true`. + +Логика: +- поле в `operations[create].params` → `Required: true` +- поле только в `operations[modify].params` → `Optional: true, Computed: true` +- поле только в state (read-only) → `Computed: true` + +### Временный workaround (действующий) + +В `terraform.tf`-манифесте указывать пустую строку: + +```hcl +v_i_p_configure = "" # modify-only поле; при create не отправляется в API +``` + +Провайдер при Create не передаёт это поле в API (строка 418/556 params), +но schema.Required требует non-null значение в плане. + +### Файлы для правки + +- `~/terra/terraform/devops/profiles/test/generated/go/19_vc_org_resource.go` — сгенерированный, не менять вручную +- **Править нужно шаблоны/генераторы** в `~/terra/terraform/devops/` + +--- + ## 2026-03-22 — БАГ: CreateService/CreateFunction возвращает 409 при `terraform apply -replace` (ИСПРАВЛЕН) ### Симптом diff --git a/examples/DEVfromGround/main.tf b/examples/DEVfromGround/main.tf index 1d1f939..e17eacc 100644 --- a/examples/DEVfromGround/main.tf +++ b/examples/DEVfromGround/main.tf @@ -24,5 +24,5 @@ variable "resource_realm" { provider "nubes" { api_token = var.api_token - api_endpoint = "https://deck-api-dev.ngcloud.ru/api/v1" + api_endpoint = "https://deck-api-dev.ngcloud.ru/api/v1/index.cfm" } diff --git a/examples/DEVfromGround/vc_org.tf b/examples/DEVfromGround/vc_org.tf index 75d2bb2..ee696aa 100644 --- a/examples/DEVfromGround/vc_org.tf +++ b/examples/DEVfromGround/vc_org.tf @@ -3,15 +3,19 @@ // resource_realm задаётся через переменную (terraform.tfvars или -var). resource "nubes_vc_org" "dev_org" { + resource_name = "vcOrg-2" resource_realm = var.resource_realm # organization_type "iaas" — единственный вариант с доступом к организации. # Значение по умолчанию "iaas", явно прописано для читаемости. organization_type = "iaas" - # adopt_existing_on_create = true позволяет взять существующий инстанс под управление. - # Для DEV-стенда оставляем false — при apply создаётся новый; ошибка если уже есть. - adopt_existing_on_create = false + # v_i_p_configure — JSON-список ipSpaces для операции modify. + # При create провайдер не передаёт его в API, но требует non-null значение в плане. + v_i_p_configure = "" + + # adopt_existing_on_create = true — берёт существующий инстанс (dev-org-sless-demo уже создан с null realm от предыдущей попытки). + adopt_existing_on_create = true # suspend_on_destroy = true (по умолчанию) — при destroy инстанс уходит в Suspend, не удаляется. suspend_on_destroy = true diff --git a/examples/NODEJS/main.tf b/examples/NODEJS/main.tf new file mode 100644 index 0000000..442dfba --- /dev/null +++ b/examples/NODEJS/main.tf @@ -0,0 +1,32 @@ +// Создано: 2026-03-23 +// main.tf — провайдер Nubes + переменные для примера NODEJS. +// Ресурс nubes_nodejs: managed Node.js приложение в облаке (не sless-функция). + +terraform { + required_providers { + nubes = { + source = "terra.k8c.ru/nubes/nubes" + version = "5.0.19" + } + } +} + +variable "api_token" { + type = string + sensitive = true +} + +variable "realm" { + type = string + description = "resource_realm — зона размещения ресурса (например: k8s-3-sandbox-nubes-ru)" +} + +variable "git_path" { + type = string + description = "URL git-репозитория с кодом приложения" +} + +provider "nubes" { + api_token = var.api_token + api_endpoint = "https://deck-api-test.ngcloud.ru/api/v1/index.cfm" +} diff --git a/examples/NODEJS/nodejs.tf b/examples/NODEJS/nodejs.tf new file mode 100644 index 0000000..0e1102a --- /dev/null +++ b/examples/NODEJS/nodejs.tf @@ -0,0 +1,22 @@ +# Создано: 2026-03-23 +# nodejs.tf — ресурс nubes_nodejs: managed Node.js приложение. +# Параметры взяты из документации terra.k8c.ru/docs/nubes/nubes/5.0.19/30_registry/resources/nodejs_params_create/ + +resource "nubes_nodejs" "app" { + resource_name = "nodejsdemo1" + domain = "domma" + resource_realm = var.realm + git_path = var.git_path + app_version = "23" + resource_c_p_u = 500 + resource_memory = 1024 + resource_instances = 1 + json_env = jsonencode({}) + adopt_existing_on_create = true + # health_path не задан — используется дефолтный / +} + +output "nodejs_domain" { + description = "Домен развёрнутого Node.js приложения" + value = nubes_nodejs.app.domain +} diff --git a/examples/POSTGRES/code/calc-node/handler.js b/examples/POSTGRES/code/calc-node/handler.js index 7ee3462..29bed1f 100644 --- a/examples/POSTGRES/code/calc-node/handler.js +++ b/examples/POSTGRES/code/calc-node/handler.js @@ -1,116 +1,9 @@ // Создано: 2026-04-10 -// Простой калькулятор — возвращает HTML страницу с кнопками. -// GET → HTML; POST с {a, op, b} → вычисление с результатом на странице. -// Рантайм nodejs20 v0.1.3+ поддерживает HTML-ответ (строка начинающаяся с '<'). +// Демо-функция: возвращает текущее время сервера. +// Юзер меняет код под себя и перебилдит через terraform apply. 'use strict'; -const HTML = ` - - - - -Калькулятор (Node.js) - - - -
-

Калькулятор

-
Node.js 20
-
0
-
RESULT_LINE
-
- - - - - - - - - - - - - - - - - -
-
- - -`; - -// Форматирует число — убирает лишний .0 для целых -function fmt(n) { return n === Math.trunc(n) ? String(Math.trunc(n)) : String(n); } - -function buildPage(resultLine) { - return HTML.replace('RESULT_LINE', resultLine || ''); -} - -function compute(event) { - // POST: тело приходит как JSON; поля a, op, b — строки из form - const a = parseFloat(event.a); - const b = parseFloat(event.b); - const op = event.op; - if (isNaN(a) || isNaN(b)) return buildPage('Некорректное число'); - let res; - switch (op) { - case '+': res = a + b; break; - case '-': res = a - b; break; - case '*': res = a * b; break; - case '/': - if (b === 0) return buildPage('Деление на ноль'); - res = a / b; break; - default: return buildPage('Неизвестный оператор'); - } - return buildPage(`${fmt(a)} ${op} ${fmt(b)} = ${fmt(res)}`); -} - -module.exports.handler = async function handler(event) { - if (event._method === 'POST') return compute(event); - return buildPage(''); +module.exports.handler = function handler(event) { + return `Текущее время: ${new Date().toISOString()}`; }; diff --git a/examples/POSTGRES/code/calc-python/handler.py b/examples/POSTGRES/code/calc-python/handler.py index 57179b3..f775f35 100644 --- a/examples/POSTGRES/code/calc-python/handler.py +++ b/examples/POSTGRES/code/calc-python/handler.py @@ -1,120 +1,105 @@ # Создано: 2026-04-10 -# Простой калькулятор — возвращает HTML страницу с кнопками. -# Роутинг: GET → HTML страница; POST с {a, op, b} → вычисление и редирект с результатом. -# _method приходит от python3.11 рантайма в event. +# Изменено: 2026-03-23 — упрощён до поля ввода выражения (демонстрация деплоя). +# Принимает произвольное математическое выражение: "2+2*(3-1)", "(10/3)**2" и т.д. +# GET → HTML страница с формой; POST с {expr} → вычисление через безопасный eval. +# Безопасность eval: __builtins__=None, только math-функции в locals. -_HTML = """ +import math + +_PAGE = """ -Калькулятор (Python) +Калькулятор — Python 3.11 -
+

Калькулятор

-
Python 3.11
-
0
-
{result_line}
-
- - - - - - - - - - - - - - - - - -
+
Python 3.11 · runtime: sless
+ + +
""" +# Разрешённые math-функции в eval — без __builtins__ нет доступа к exec/open/etc. +_MATH_LOCALS = {k: getattr(math, k) for k in dir(math) if not k.startswith('_')} + + def handler(event): - # POST: тело приходит как JSON от рантайма if event.get('_method') == 'POST': - return _compute(event) - # GET: показываем чистую страницу - return _html() + expr = str(event.get('expr', '')).strip() + return _compute(expr) + # GET → HTML страница + return _PAGE -def _html(result_line='', fresh_js='true'): - return _HTML.replace('{result_line}', result_line).replace('{fresh_js}', fresh_js) - - -def _compute(event): +def _compute(expr): + if not expr: + return {'error': 'Введите выражение'} try: - a = float(event.get('a', 0)) - b = float(event.get('b', 0)) - op = event.get('op', '') - if op == '+': - res = a + b - elif op == '-': - res = a - b - elif op == '*': - res = a * b - elif op == '/': - if b == 0: - return _html('Деление на ноль', 'false') - res = a / b - else: - return _html('Неизвестный оператор', 'false') - # Убираем лишний .0 для целых результатов - res_str = str(int(res)) if res == int(res) else str(res) - return _html(f'{int(a) if a == int(a) else a} {op} {int(b) if b == int(b) else b} = {res_str}', 'false') - except (ValueError, TypeError) as exc: - return _html(f'Ошибка: {exc}', 'false') + result = eval(expr, {'__builtins__': None}, _MATH_LOCALS) # noqa: S307 + if not isinstance(result, (int, float)): + return {'error': 'Результат не является числом'} + return {'expr': expr, 'result': result} + except ZeroDivisionError: + return {'error': 'Деление на ноль'} + except Exception as exc: + return {'error': f'Ошибка: {exc}'} + + +def _esc(s): + # Экранируем HTML-спецсимволы — безопасный вывод в атрибут и тело. + return s.replace('&', '&').replace('<', '<').replace('>', '>').replace('"', '"') diff --git a/examples/POSTGRES/functions.tf b/examples/POSTGRES/functions.tf index b61f5b3..89263cf 100644 --- a/examples/POSTGRES/functions.tf +++ b/examples/POSTGRES/functions.tf @@ -9,7 +9,9 @@ resource "sless_service" "calc_python" { name = "calc-python" runtime = "python3.11" entrypoint = "handler.handler" - source_dir = "${path.module}/code/calc-python" + memory_mb = 128 + timeout_sec = 30 + source_dir = "${path.module}/code/calc-python" } output "calc_python_url" { @@ -23,7 +25,9 @@ resource "sless_service" "calc_node" { name = "calc-node" runtime = "nodejs20" entrypoint = "handler.handler" - source_dir = "${path.module}/code/calc-node" + memory_mb = 128 + timeout_sec = 30 + source_dir = "${path.module}/code/calc-node" } output "calc_node_url" { diff --git a/examples/POSTGRES/main.tf b/examples/POSTGRES/main.tf index 26cb6d7..834387f 100644 --- a/examples/POSTGRES/main.tf +++ b/examples/POSTGRES/main.tf @@ -4,7 +4,7 @@ terraform { required_providers { nubes = { source = "terra.k8c.ru/nubes/nubes" - version = "5.0.19" + version = "5.0.31" } sless = { source = "terra.k8c.ru/naeel/sless" diff --git a/examples/VM/vm.tf b/examples/VM/vm.tf index 0746817..1a22c93 100644 --- a/examples/VM/vm.tf +++ b/examples/VM/vm.tf @@ -6,7 +6,8 @@ resource "nubes_vc_vm_v3" "vm" { resource_name = "vm-sless-demo" vapp_uid = nubes_vapp.vapp.id # ссылка на vApp. Не изменяется после создания. - image_vm = "Ubuntu 22.04 LTS" # Не изменяется после создания. + image_vm = "Ubuntu_22-20G" # Не изменяется после создания. +# image_vm = "Ubuntu 22.04 LTS" # Не изменяется после создания. ip_space_name = "internet-ipv4-v1" user_login = "ubuntu"