Initial commit: clean infrastructure code

This commit is contained in:
“Naeel” 2026-02-05 11:43:15 +04:00
commit 1a7be93ab1
44 changed files with 2810 additions and 0 deletions

57
.gitignore vendored Normal file
View File

@ -0,0 +1,57 @@
# Binaries - General
bin/
dist/
*.exe
*.dll
*.so
*.dylib
*.test
*/bin/
# Specific binary names (ignore in any subdirectory)
registry-server
registry-server-bin
operator
universal_rebuild
# Terraform
.terraform/
*.tfstate
*.tfstate.backup
*.tfvars
*.tfvars.json
.terraform.lock.hcl
*.plan
backend-dev.hcl
# IDE & Editor
.idea/
.vscode/
*.swp
*.swo
*~
*.bak
# OS
.DS_Store
Thumbs.db
# Logs
*.log
# Secrets & Keys
*.pem
*.key
*.pub
*.crt
*.cer
*.p12
*.pfx
.env
token
tokens
*_token
*_key
secret.yaml
secrets.yaml
credentials.json

18
forNubes/README.md Normal file
View File

@ -0,0 +1,18 @@
# forNubes
Сборка минимальных инструментов для разработки провайдера, сборки/публикации документации и инфраструктуры Registry Server (без исходников docs).
## Файлы в корне
- **go.mod / go.sum** — зависимости Goмодулей.
- **main.go** — точка входа основного бинарника.
- **mkdocs.yml** — конфигурация сборки документации (используется скриптами публикации).
## Папки
- **universal_rebuild/** — исходники универсального провайдера (генераторы, core, ресурсы).
- **operator/** — код оператора и манифесты для сборки/публикации провайдеров.
- **registry-server-build/** — исходники и Dockerfile реестра (Registry Server).
- **k8s/** — k8sманифесты для деплоя Registry Server.
- **scripts/** — рабочие скрипты (включая публикацию документации).
- **tools/** — утилиты и вспомогательные скрипты (в т.ч. docs_publish).
- **terraform/** — Terraformконфигурации окружений.
- **tests/** — тестовые сценарии.

63
forNubes/go.mod Normal file
View File

@ -0,0 +1,63 @@
module terraform-provider-mycloud
go 1.24.0
toolchain go1.24.12
require (
github.com/google/generative-ai-go v0.20.1
github.com/hashicorp/terraform-plugin-framework v1.16.1
github.com/hashicorp/terraform-plugin-framework-timeouts v0.7.0
github.com/hashicorp/terraform-plugin-framework-validators v0.19.0
github.com/hashicorp/terraform-plugin-log v0.9.0
google.golang.org/api v0.262.0
)
require (
cloud.google.com/go v0.115.0 // indirect
cloud.google.com/go/ai v0.8.0 // indirect
cloud.google.com/go/auth v0.18.1 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
cloud.google.com/go/compute/metadata v0.9.0 // indirect
cloud.google.com/go/longrunning v0.5.7 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/fatih/color v1.15.0 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/google/s2a-go v0.1.9 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/googleapis/enterprise-certificate-proxy v0.3.11 // indirect
github.com/googleapis/gax-go/v2 v2.16.0 // indirect
github.com/hashicorp/go-hclog v1.6.3 // indirect
github.com/hashicorp/go-plugin v1.7.0 // indirect
github.com/hashicorp/go-uuid v1.0.3 // indirect
github.com/hashicorp/terraform-plugin-go v0.29.0 // indirect
github.com/hashicorp/terraform-registry-address v0.4.0 // indirect
github.com/hashicorp/terraform-svchost v0.1.1 // indirect
github.com/hashicorp/yamux v0.1.2 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
github.com/oklog/run v1.1.0 // indirect
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
github.com/vmihailenco/tagparser/v2 v2.0.0 // indirect
go.opentelemetry.io/auto/sdk v1.2.1 // indirect
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 // indirect
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect
go.opentelemetry.io/otel v1.39.0 // indirect
go.opentelemetry.io/otel/metric v1.39.0 // indirect
go.opentelemetry.io/otel/trace v1.39.0 // indirect
golang.org/x/crypto v0.47.0 // indirect
golang.org/x/net v0.49.0 // indirect
golang.org/x/oauth2 v0.34.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.40.0 // indirect
golang.org/x/text v0.33.0 // indirect
golang.org/x/time v0.14.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120174246-409b4a993575 // indirect
google.golang.org/grpc v1.78.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect
)

155
forNubes/go.sum Normal file
View File

@ -0,0 +1,155 @@
cloud.google.com/go v0.115.0 h1:CnFSK6Xo3lDYRoBKEcAtia6VSC837/ZkJuRduSFnr14=
cloud.google.com/go v0.115.0/go.mod h1:8jIM5vVgoAEoiVxQ/O4BFTfHqulPZgs/ufEzMcFMdWU=
cloud.google.com/go/ai v0.8.0 h1:rXUEz8Wp2OlrM8r1bfmpF2+VKqc1VJpafE3HgzRnD/w=
cloud.google.com/go/ai v0.8.0/go.mod h1:t3Dfk4cM61sytiggo2UyGsDVW3RF1qGZaUKDrZFyqkE=
cloud.google.com/go/auth v0.18.1 h1:IwTEx92GFUo2pJ6Qea0EU3zYvKnTAeRCODxfA/G5UWs=
cloud.google.com/go/auth v0.18.1/go.mod h1:GfTYoS9G3CWpRA3Va9doKN9mjPGRS+v41jmZAhBzbrA=
cloud.google.com/go/auth/oauth2adapt v0.2.8 h1:keo8NaayQZ6wimpNSmW5OPc283g65QNIiLpZnkHRbnc=
cloud.google.com/go/auth/oauth2adapt v0.2.8/go.mod h1:XQ9y31RkqZCcwJWNSx2Xvric3RrU88hAYYbjDWYDL+c=
cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdBtwLoEkH9Zs=
cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10=
cloud.google.com/go/longrunning v0.5.7 h1:WLbHekDbjK1fVFD3ibpFFVoyizlLRl73I7YKuAKilhU=
cloud.google.com/go/longrunning v0.5.7/go.mod h1:8GClkudohy1Fxm3owmBGid8W0pSgodEMwEAztp38Xng=
github.com/bufbuild/protocompile v0.14.1 h1:iA73zAf/fyljNjQKwYzUHD6AD4R8KMasmwa/FBatYVw=
github.com/bufbuild/protocompile v0.14.1/go.mod h1:ppVdAIhbr2H8asPk6k4pY7t9zB1OU5DoEw9xY/FUi1c=
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f h1:Y8xYupdHxryycyPlc9Y+bSQAYZnetRJ70VMVKm5CKI0=
github.com/cncf/xds/go v0.0.0-20251022180443-0feb69152e9f/go.mod h1:HlzOvOjVBOfTGSRXRyY0OiCS/3J1akRGQQpRO/7zyF4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/envoyproxy/go-control-plane v0.13.5-0.20251024222203-75eaa193e329 h1:K+fnvUM0VZ7ZFJf0n4L/BRlnsb9pL/GuDG6FqaH+PwM=
github.com/envoyproxy/go-control-plane/envoy v1.35.0 h1:ixjkELDE+ru6idPxcHLj8LBVc2bFP7iBytj353BoHUo=
github.com/envoyproxy/go-control-plane/envoy v1.35.0/go.mod h1:09qwbGVuSWWAyN5t/b3iyVfz5+z8QWGrzkoqm/8SbEs=
github.com/envoyproxy/protoc-gen-validate v1.2.1 h1:DEo3O99U8j4hBFwbJfrz9VtgcDfUKS7KJ7spH3d86P8=
github.com/envoyproxy/protoc-gen-validate v1.2.1/go.mod h1:d/C80l/jxXLdfEIhX1W2TmLfsJ31lvEjwamM4DxlWXU=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs=
github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw=
github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg=
github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/generative-ai-go v0.20.1 h1:6dEIujpgN2V0PgLhr6c/M1ynRdc7ARtiIDPFzj45uNQ=
github.com/google/generative-ai-go v0.20.1/go.mod h1:TjOnZJmZKzarWbjUJgy+r3Ee7HGBRVLhOIgupnwR4Bg=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/s2a-go v0.1.9 h1:LGD7gtMgezd8a/Xak7mEWL0PjoTQFvpRudN895yqKW0=
github.com/google/s2a-go v0.1.9/go.mod h1:YA0Ei2ZQL3acow2O62kdp9UlnvMmU7kA6Eutn0dXayM=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/enterprise-certificate-proxy v0.3.11 h1:vAe81Msw+8tKUxi2Dqh/NZMz7475yUvmRIkXr4oN2ao=
github.com/googleapis/enterprise-certificate-proxy v0.3.11/go.mod h1:RFV7MUdlb7AgEq2v7FmMCfeSMCllAzWxFgRdusoGks8=
github.com/googleapis/gax-go/v2 v2.16.0 h1:iHbQmKLLZrexmb0OSsNGTeSTS0HO4YvFOG8g5E4Zd0Y=
github.com/googleapis/gax-go/v2 v2.16.0/go.mod h1:o1vfQjjNZn4+dPnRdl/4ZD7S9414Y4xA+a/6Icj6l14=
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-plugin v1.7.0 h1:YghfQH/0QmPNc/AZMTFE3ac8fipZyZECHdDPshfk+mA=
github.com/hashicorp/go-plugin v1.7.0/go.mod h1:BExt6KEaIYx804z8k4gRzRLEvxKVb+kn0NMcihqOqb8=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/terraform-plugin-framework v1.16.1 h1:1+zwFm3MEqd/0K3YBB2v9u9DtyYHyEuhVOfeIXbteWA=
github.com/hashicorp/terraform-plugin-framework v1.16.1/go.mod h1:0xFOxLy5lRzDTayc4dzK/FakIgBhNf/lC4499R9cV4Y=
github.com/hashicorp/terraform-plugin-framework-timeouts v0.7.0 h1:jblRy1PkLfPm5hb5XeMa3tezusnMRziUGqtT5epSYoI=
github.com/hashicorp/terraform-plugin-framework-timeouts v0.7.0/go.mod h1:5jm2XK8uqrdiSRfD5O47OoxyGMCnwTcl8eoiDgSa+tc=
github.com/hashicorp/terraform-plugin-framework-validators v0.19.0 h1:Zz3iGgzxe/1XBkooZCewS0nJAaCFPFPHdNJd8FgE4Ow=
github.com/hashicorp/terraform-plugin-framework-validators v0.19.0/go.mod h1:GBKTNGbGVJohU03dZ7U8wHqc2zYnMUawgCN+gC0itLc=
github.com/hashicorp/terraform-plugin-go v0.29.0 h1:1nXKl/nSpaYIUBU1IG/EsDOX0vv+9JxAltQyDMpq5mU=
github.com/hashicorp/terraform-plugin-go v0.29.0/go.mod h1:vYZbIyvxyy0FWSmDHChCqKvI40cFTDGSb3D8D70i9GM=
github.com/hashicorp/terraform-plugin-log v0.9.0 h1:i7hOA+vdAItN1/7UrfBqBwvYPQ9TFvymaRGZED3FCV0=
github.com/hashicorp/terraform-plugin-log v0.9.0/go.mod h1:rKL8egZQ/eXSyDqzLUuwUYLVdlYeamldAHSxjUFADow=
github.com/hashicorp/terraform-registry-address v0.4.0 h1:S1yCGomj30Sao4l5BMPjTGZmCNzuv7/GDTDX99E9gTk=
github.com/hashicorp/terraform-registry-address v0.4.0/go.mod h1:LRS1Ay0+mAiRkUyltGT+UHWkIqTFvigGn/LbMshfflE=
github.com/hashicorp/terraform-svchost v0.1.1 h1:EZZimZ1GxdqFRinZ1tpJwVxxt49xc/S52uzrw4x0jKQ=
github.com/hashicorp/terraform-svchost v0.1.1/go.mod h1:mNsjQfZyf/Jhz35v6/0LWcv26+X7JPS+buii2c9/ctc=
github.com/hashicorp/yamux v0.1.2 h1:XtB8kyFOyHXYVFnwT5C3+Bdo8gArse7j2AQ0DA0Uey8=
github.com/hashicorp/yamux v0.1.2/go.mod h1:C+zze2n6e/7wshOZep2A70/aQU6QBRWJO/G6FT1wIns=
github.com/jhump/protoreflect v1.17.0 h1:qOEr613fac2lOuTgWN4tPAtLL7fUSbuJL5X5XumQh94=
github.com/jhump/protoreflect v1.17.0/go.mod h1:h9+vUUL38jiBzck8ck+6G/aeMX8Z4QUY/NiJPwPNi+8=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mitchellh/go-testing-interface v1.14.1 h1:jrgshOhYAUVNMAJiKbEu7EqAwgJJ2JqpQmpLJOu07cU=
github.com/mitchellh/go-testing-interface v1.14.1/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8=
github.com/oklog/run v1.1.0 h1:GEenZ1cK0+q0+wsJew9qUg/DyD8k3JzYsZAi5gYi2mA=
github.com/oklog/run v1.1.0/go.mod h1:sVPdnTZT1zYwAJeCMu2Th4T21pA3FPOQRfWjQlk7DVU=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo=
github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/vmihailenco/msgpack/v5 v5.4.1 h1:cQriyiUvjTwOHg8QZaPihLWeRAAVoCpE00IUPn0Bjt8=
github.com/vmihailenco/msgpack/v5 v5.4.1/go.mod h1:GaZTsDaehaPpQVyxrf5mtQlH+pc21PIudVV/E3rRQok=
github.com/vmihailenco/tagparser/v2 v2.0.0 h1:y09buUbR+b5aycVFQs/g70pqKVZNBmxwAhO7/IwNM9g=
github.com/vmihailenco/tagparser/v2 v2.0.0/go.mod h1:Wri+At7QHww0WTrCBeu4J6bNtoV6mEfg5OIWRZA9qds=
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0 h1:q4XOmH/0opmeuJtPsbFNivyl7bCt7yRBbeEm2sC/XtQ=
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.61.0/go.mod h1:snMWehoOh2wsEwnvvwtDyFCxVeDAODenXHtn5vzrKjo=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus=
go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q=
go.opentelemetry.io/otel v1.39.0 h1:8yPrr/S0ND9QEfTfdP9V+SiwT4E0G7Y5MO7p85nis48=
go.opentelemetry.io/otel v1.39.0/go.mod h1:kLlFTywNWrFyEdH0oj2xK0bFYZtHRYUdv1NklR/tgc8=
go.opentelemetry.io/otel/metric v1.39.0 h1:d1UzonvEZriVfpNKEVmHXbdf909uGTOQjA0HF0Ls5Q0=
go.opentelemetry.io/otel/metric v1.39.0/go.mod h1:jrZSWL33sD7bBxg1xjrqyDjnuzTUB0x1nBERXd7Ftcs=
go.opentelemetry.io/otel/sdk v1.39.0 h1:nMLYcjVsvdui1B/4FRkwjzoRVsMK8uL/cj0OyhKzt18=
go.opentelemetry.io/otel/sdk v1.39.0/go.mod h1:vDojkC4/jsTJsE+kh+LXYQlbL8CgrEcwmt1ENZszdJE=
go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM=
go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA=
go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI=
go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA=
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw=
golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA=
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI=
golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4=
gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk=
gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E=
google.golang.org/api v0.262.0 h1:4B+3u8He2GwyN8St3Jhnd3XRHlIvc//sBmgHSp78oNY=
google.golang.org/api v0.262.0/go.mod h1:jNwmH8BgUBJ/VrUG6/lIl9YiildyLd09r9ZLHiQ6cGI=
google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217 h1:GvESR9BIyHUahIb0NcTum6itIWtdoglGX+rnGxm2934=
google.golang.org/genproto v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:yJ2HH4EHEDTd3JiLmhds6NkJ17ITVYOdV3m3VKOnws0=
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217 h1:fCvbg86sFXwdrl5LgVcTEvNC+2txB5mgROGmRL5mrls=
google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:+rXWjjaukWZun3mLfjmVnQi18E1AsFbDN9QdJ5YXLto=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120174246-409b4a993575 h1:vzOYHDZEHIsPYYnaSYo60AqHkJronSu0rzTz/s4quL0=
google.golang.org/genproto/googleapis/rpc v0.0.0-20260120174246-409b4a993575/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ=
google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=
google.golang.org/grpc v1.78.0/go.mod h1:I47qjTo4OKbMkjA/aOOwxDIiPSBofUtQUI5EfpWvW7U=
google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE=
google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

7
forNubes/k8s/README.md Normal file
View File

@ -0,0 +1,7 @@
# k8s
Здесь лежат Kubernetesманифесты, которые поднимают реестр Terraformпровайдеров. Это рабочие файлы для текущего деплоя.
## Что реально используется сейчас
- [registry-deployment-new.yaml](registry-deployment-new.yaml) — deployment `registry-server` с настройкой S3.
- [registry-ingress-new.yaml](registry-ingress-new.yaml) — ingress для `terra.k8c.ru` (certmanager + Let's Encrypt).

View File

@ -0,0 +1,42 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: registry-server
namespace: terra
labels:
app: registry-server
spec:
replicas: 1
selector:
matchLabels:
app: registry-server
template:
metadata:
labels:
app: registry-server
spec:
containers:
- name: registry-server
image: naeel/terraform-registry-server:docs-dev
imagePullPolicy: Always
ports:
- containerPort: 8080
env:
- name: REGISTRY_HOSTNAME
value: "terra.k8c.ru"
- name: S3_ENDPOINT
value: "s3.msk-1.ngcloud.ru"
- name: S3_ACCESS_KEY
valueFrom:
secretKeyRef:
name: s3-credentials
key: access-key
- name: S3_SECRET_KEY
valueFrom:
secretKeyRef:
name: s3-credentials
key: secret-key
- name: S3_BUCKET
value: "terraform-registry"
- name: S3_USE_SSL
value: "true"

View File

@ -0,0 +1,26 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: registry-ingress
namespace: terra
annotations:
cert-manager.io/cluster-issuer: "letsencrypt-prod"
nginx.ingress.kubernetes.io/proxy-body-size: "100m"
nginx.ingress.kubernetes.io/rewrite-target: "/$2"
spec:
ingressClassName: nginx
tls:
- hosts:
- terra.k8c.ru
secretName: registry-tls
rules:
- host: terra.k8c.ru
http:
paths:
- path: "/()(.*)"
pathType: ImplementationSpecific
backend:
service:
name: registry-server
port:
number: 80

32
forNubes/main.go Normal file
View File

@ -0,0 +1,32 @@
package main
import (
"context"
"flag"
"log"
"terraform-provider-mycloud/internal/provider"
"github.com/hashicorp/terraform-plugin-framework/providerserver"
)
var (
version string = "dev"
)
func main() {
var debug bool
flag.BoolVar(&debug, "debug", false, "set to true to run the provider with support for debuggers like delve")
flag.Parse()
opts := providerserver.ServeOpts{
Address: "registry.terraform.io/nubes/nubes",
Debug: debug,
}
err := providerserver.Serve(context.Background(), provider.New(version), opts)
if err != nil {
log.Fatal(err.Error())
}
}

39
forNubes/mkdocs.yml Normal file
View File

@ -0,0 +1,39 @@
site_name: Nubes Terraform Provider
site_url: https://terra.k8c.ru/docs/nubes/nubes/2.0.0/
exclude_docs: |
README.md
ai_universal_provider_gen.md
00_overview/*
20_discovery/*
40_analysis/*
50_history/*
60_strategy/*
70_api/*
help/*
docs_dir: docs
site_dir: site
theme:
name: material
logo: 30_registry/assets/favicon.png
favicon: 30_registry/assets/favicon.png
markdown_extensions:
- admonition
- attr_list
- pymdownx.details
- pymdownx.inlinehilite
- pymdownx.superfences
- pymdownx.highlight:
anchor_linenums: true
nav:
- Home: index.md
- Resources:
- Index: 30_registry/resources/index.md
- All resources (table): 30_registry/resources/all_resources.md
- Guides:
- Getting started: 30_registry/guides/getting-started.md
- Terraform basics: 30_registry/guides/terraform-basics.md

View File

@ -0,0 +1,21 @@
# Minimal Registry Server Dockerfile
FROM golang:1.24-alpine AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY main.go docs.go ./
# Build statically
RUN CGO_ENABLED=0 GOOS=linux go build -o /registry-server .
# Run Stage
FROM alpine:3.19
WORKDIR /app
COPY --from=builder /registry-server /app/registry-server
RUN apk add --no-cache ca-certificates
USER 1000:1000
EXPOSE 8080
CMD ["/app/registry-server"]

View File

@ -0,0 +1,10 @@
# Super minimal Registry Server Dockerfile
FROM alpine:3.19
WORKDIR /app
COPY registry-server-bin /app/registry-server
RUN apk add --no-cache ca-certificates
USER 1000:1000
EXPOSE 8080
CMD ["/app/registry-server"]

View File

@ -0,0 +1,116 @@
package main
import (
"context"
"fmt"
"io"
"log"
"mime"
"net/http"
"path/filepath"
"strings"
"github.com/minio/minio-go/v7"
)
// parseDocsRequestPath parses paths like:
// /docs/<namespace>/<name>/<version>/... (rest may be empty)
func parseDocsRequestPath(p string) (namespace, name, version, rest string, err error) {
p = strings.TrimPrefix(p, "/")
p = strings.TrimPrefix(p, "docs/")
parts := strings.SplitN(p, "/", 4)
if len(parts) < 3 {
err = fmt.Errorf("invalid docs path: %s", p)
return
}
namespace = parts[0]
name = parts[1]
version = parts[2]
if len(parts) == 3 {
rest = ""
} else {
rest = parts[3]
}
return
}
// docsObjectKey builds the S3 key for a docs object given parsed parts.
func docsObjectKey(namespace, name, version, pathPart string) string {
clean := strings.TrimPrefix(pathPart, "/")
if clean == "" {
return fmt.Sprintf("docs/%s/%s/%s/index.html", namespace, name, version)
}
return fmt.Sprintf("docs/%s/%s/%s/%s", namespace, name, version, clean)
}
// tryCandidateKeys returns a list of keys to attempt for a given request path.
// Order matters: exact path first, then <path>/index.html, then top-level index.
func tryCandidateKeys(namespace, name, version, rest string) []string {
keys := []string{}
if rest == "" {
keys = append(keys, docsObjectKey(namespace, name, version, "index.html"))
return keys
}
// exact
keys = append(keys, docsObjectKey(namespace, name, version, rest))
// if it looks like a directory or has no extension, try index under it
if strings.HasSuffix(rest, "/") || filepath.Ext(rest) == "" {
keys = append(keys, docsObjectKey(namespace, name, version, strings.TrimSuffix(rest, "/")+"/index.html"))
}
// finally, try root index
keys = append(keys, docsObjectKey(namespace, name, version, "index.html"))
return keys
}
// docsHandler serves static documentation files from S3 (public-facing via Ingress).
func docsHandler(w http.ResponseWriter, r *http.Request) {
ns, name, ver, rest, err := parseDocsRequestPath(r.URL.Path)
if err != nil {
http.Error(w, "Bad docs path", http.StatusBadRequest)
return
}
ctx := context.Background()
candidates := tryCandidateKeys(ns, name, ver, rest)
log.Printf("Docs candidates (host=%s): %v", hostname, candidates)
var lastErr error
for _, key := range candidates {
obj, err := s3Client.GetObject(ctx, bucketName, key, minio.GetObjectOptions{})
if err != nil {
lastErr = err
continue
}
stat, err := obj.Stat()
if err != nil {
lastErr = err
_ = obj.Close()
continue
}
// Determine content-type
ext := filepath.Ext(key)
ctype := mime.TypeByExtension(ext)
if ctype == "" {
// fallback for HTML
if ext == ".html" || strings.HasSuffix(key, "index.html") {
ctype = "text/html; charset=utf-8"
} else {
ctype = "application/octet-stream"
}
}
w.Header().Set("Content-Type", ctype)
w.Header().Set("Content-Length", fmt.Sprintf("%d", stat.Size))
w.Header().Set("Last-Modified", stat.LastModified.Format(http.TimeFormat))
if _, err := io.Copy(w, obj); err != nil {
log.Printf("Error streaming object %s: %v", key, err)
}
_ = obj.Close()
return
}
log.Printf("Docs not found in candidates: %v, lastErr: %v", candidates, lastErr)
http.Error(w, "Documentation not found", http.StatusNotFound)
}

View File

@ -0,0 +1,27 @@
module terraform-registry-server
go 1.24.12
require github.com/minio/minio-go/v7 v7.0.98
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/go-ini/ini v1.67.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/klauspost/compress v1.18.2 // indirect
github.com/klauspost/cpuid/v2 v2.2.11 // indirect
github.com/klauspost/crc32 v1.3.0 // indirect
github.com/minio/crc64nvme v1.1.1 // indirect
github.com/minio/md5-simd v1.1.2 // indirect
github.com/philhofer/fwd v1.2.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/rs/xid v1.6.0 // indirect
github.com/tinylib/msgp v1.6.1 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
golang.org/x/crypto v0.46.0 // indirect
golang.org/x/net v0.48.0 // indirect
golang.org/x/sys v0.39.0 // indirect
golang.org/x/text v0.32.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

View File

@ -0,0 +1,45 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A=
github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk=
github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.11 h1:0OwqZRYI2rFrjS4kvkDnqJkKHdHaRnCm68/DY4OxRzU=
github.com/klauspost/cpuid/v2 v2.2.11/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/klauspost/crc32 v1.3.0 h1:sSmTt3gUt81RP655XGZPElI0PelVTZ6YwCRnPSupoFM=
github.com/klauspost/crc32 v1.3.0/go.mod h1:D7kQaZhnkX/Y0tstFGf8VUzv2UofNGqCjnC3zdHB0Hw=
github.com/minio/crc64nvme v1.1.1 h1:8dwx/Pz49suywbO+auHCBpCtlW1OfpcLN7wYgVR6wAI=
github.com/minio/crc64nvme v1.1.1/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
github.com/minio/minio-go/v7 v7.0.98 h1:MeAVKjLVz+XJ28zFcuYyImNSAh8Mq725uNW4beRisi0=
github.com/minio/minio-go/v7 v7.0.98/go.mod h1:cY0Y+W7yozf0mdIclrttzo1Iiu7mEf9y7nk2uXqMOvM=
github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rs/xid v1.6.0 h1:fV591PaemRlL6JfRxGDEPl69wICngIQ3shQtzfy2gxU=
github.com/rs/xid v1.6.0/go.mod h1:7XoLgs4eV+QndskICGsho+ADou8ySMSjJKDIan90Nz0=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/tinylib/msgp v1.6.1 h1:ESRv8eL3u+DNHUoSAAQRE50Hm162zqAnBoGv9PzScPY=
github.com/tinylib/msgp v1.6.1/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU=
golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0=
golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU=
golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY=
golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk=
golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU=
golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -0,0 +1,331 @@
package main
import (
"bufio"
"context"
"encoding/json"
"fmt"
"io"
"log"
"net/http"
"net/url"
"os"
"strings"
"github.com/minio/minio-go/v7"
"github.com/minio/minio-go/v7/pkg/credentials"
)
var (
s3Client *minio.Client
bucketName = "terraform-registry" // Default
hostname = os.Getenv("REGISTRY_HOSTNAME")
)
// Terraform Registry Protocol Structs
type Discovery struct {
ProvidersV1 string `json:"providers.v1"`
}
type VersionList struct {
ID string `json:"id"`
Versions []Version `json:"versions"`
Warnings []string `json:"warnings"`
}
type Version struct {
Version string `json:"version"`
Protocols []string `json:"protocols"`
Platforms []Platform `json:"platforms"`
}
type Platform struct {
OS string `json:"os"`
Arch string `json:"arch"`
}
type DownloadResponse struct {
Protocols []string `json:"protocols"`
OS string `json:"os"`
Arch string `json:"arch"`
Filename string `json:"filename"`
DownloadURL string `json:"download_url"`
ShasumsURL string `json:"shasums_url"`
ShasumsSignatureURL string `json:"shasums_signature_url"`
Shasum string `json:"shasum"`
SigningKeys SigningKeys `json:"signing_keys"`
}
type SigningKeys struct {
GPGPublicKeys []GPGPublicKey `json:"gpg_public_keys"`
}
type GPGPublicKey struct {
KeyID string `json:"key_id"`
ASCIIArmor string `json:"ascii_armor"`
}
func main() {
// 1. Init S3 Connection
if hostname == "" {
hostname = "localhost:8080"
}
endpoint := os.Getenv("S3_ENDPOINT")
accessKeyID := os.Getenv("S3_ACCESS_KEY")
secretAccessKey := os.Getenv("S3_SECRET_KEY")
if os.Getenv("S3_BUCKET") != "" {
bucketName = os.Getenv("S3_BUCKET")
}
var err error
s3Client, err = minio.New(endpoint, &minio.Options{
Creds: credentials.NewStaticV4(accessKeyID, secretAccessKey, ""),
Secure: true, // Force secure for cloud S3
})
if err != nil {
log.Fatalln(err)
}
// 2. HTTP Handlers
http.HandleFunc("/.well-known/terraform.json", discoveryHandler)
http.HandleFunc("/v1/providers/", router)
http.HandleFunc("/v1/proxy", proxyHandler)
http.HandleFunc("/docs/", docsHandler)
http.Handle("/", http.HandlerFunc(rootHandler))
log.Printf("Starting Registry Service on :8080 (Bucket: %s, Endpoint: %s)\n", bucketName, endpoint)
log.Fatal(http.ListenAndServe(":8080", nil))
}
func rootHandler(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/" {
http.NotFound(w, r)
return
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
fmt.Fprint(w, `
<!DOCTYPE html>
<html>
<head><title>Terra Registry</title></head>
<body>
<h1>Terra Registry & Documentation Server</h1>
<p>Status: <span style="color: green">ONLINE</span></p>
<hr>
<p>Powered by Nubes Cloud S3 Storage</p>
</body>
</html>
`)
}
func proxyHandler(w http.ResponseWriter, r *http.Request) {
bucket := r.URL.Query().Get("bucket")
key := r.URL.Query().Get("key")
if bucket == "" || key == "" {
http.Error(w, "Missing bucket or key params", http.StatusBadRequest)
return
}
obj, err := s3Client.GetObject(context.Background(), bucket, key, minio.GetObjectOptions{})
if err != nil {
log.Printf("Error getting object %s/%s: %v", bucket, key, err)
http.Error(w, "File not found", http.StatusNotFound)
return
}
defer obj.Close()
stat, err := obj.Stat()
if err != nil {
log.Printf("Error stating object %s/%s: %v", bucket, key, err)
http.Error(w, "File not found or not accessible", http.StatusNotFound)
return
}
w.Header().Set("Content-Length", fmt.Sprintf("%d", stat.Size))
w.Header().Set("Content-Type", "application/octet-stream")
w.Header().Set("Last-Modified", stat.LastModified.Format(http.TimeFormat))
if _, err := io.Copy(w, obj); err != nil {
log.Printf("Error streaming object: %v", err)
}
}
func discoveryHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(Discovery{ProvidersV1: "/v1/providers/"})
}
func router(w http.ResponseWriter, r *http.Request) {
path := strings.TrimPrefix(r.URL.Path, "/v1/providers/")
parts := strings.Split(path, "/")
if len(parts) == 3 && parts[2] == "versions" {
listVersions(w, r, parts[0], parts[1])
return
}
if len(parts) == 6 && parts[3] == "download" {
downloadVersion(w, r, parts[0], parts[1], parts[2], parts[4], parts[5])
return
}
http.Error(w, "Not Found", http.StatusNotFound)
}
func listVersions(w http.ResponseWriter, r *http.Request, namespace, pType string) {
prefix := fmt.Sprintf("%s/%s/%s/", hostname, namespace, pType)
ctx := context.Background()
versions := []Version{}
seenVersions := map[string]*Version{}
objectCh := s3Client.ListObjects(ctx, bucketName, minio.ListObjectsOptions{
Prefix: prefix,
Recursive: true,
})
for object := range objectCh {
if object.Err != nil {
continue
}
parts := strings.Split(object.Key, "/")
if len(parts) < 5 {
continue
}
verStr := parts[3]
fileName := parts[4]
if _, ok := seenVersions[verStr]; !ok {
seenVersions[verStr] = &Version{
Version: verStr,
Protocols: []string{"5.0"},
Platforms: []Platform{},
}
}
if strings.Contains(fileName, "_linux_amd64.zip") {
seenVersions[verStr].Platforms = append(seenVersions[verStr].Platforms, Platform{OS: "linux", Arch: "amd64"})
}
if strings.Contains(fileName, "_windows_amd64.zip") {
seenVersions[verStr].Platforms = append(seenVersions[verStr].Platforms, Platform{OS: "windows", Arch: "amd64"})
}
}
for _, v := range seenVersions {
versions = append(versions, *v)
}
resp := VersionList{
ID: fmt.Sprintf("%s/%s", namespace, pType),
Versions: versions,
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resp)
}
func downloadVersion(w http.ResponseWriter, r *http.Request, namespace, pType, version, osType, arch string) {
basePath := fmt.Sprintf("%s/%s/%s/%s", hostname, namespace, pType, version)
filename := fmt.Sprintf("terraform-provider-%s_%s_%s_%s.zip", pType, version, osType, arch)
fullKey := fmt.Sprintf("%s/%s", basePath, filename)
shasumsKey := fmt.Sprintf("%s/terraform-provider-%s_%s_SHA256SUMS", basePath, pType, version)
sigKey := fmt.Sprintf("%s/terraform-provider-%s_%s_SHA256SUMS.sig", basePath, pType, version)
var shasumValue string
shasumsObj, err := s3Client.GetObject(context.Background(), bucketName, shasumsKey, minio.GetObjectOptions{})
if err == nil {
defer shasumsObj.Close()
scanner := bufio.NewScanner(shasumsObj)
for scanner.Scan() {
line := scanner.Text()
if strings.Contains(line, filename) {
fields := strings.Fields(line)
if len(fields) >= 1 {
shasumValue = fields[0]
}
break
}
}
}
baseURL := "https://" + hostname
downloadLink := fmt.Sprintf("%s/v1/proxy?bucket=%s&key=%s", baseURL, bucketName, url.QueryEscape(fullKey))
shasumsLink := fmt.Sprintf("%s/v1/proxy?bucket=%s&key=%s", baseURL, bucketName, url.QueryEscape(shasumsKey))
sigLink := fmt.Sprintf("%s/v1/proxy?bucket=%s&key=%s", baseURL, bucketName, url.QueryEscape(sigKey))
gpgKey := GPGPublicKey{
KeyID: "866FD93D456DCA800F2448413EC4673EB798238A",
ASCIIArmor: `-----BEGIN PGP PUBLIC KEY BLOCK-----
mQINBGl2EqgBEAClEcif3Xy4rfZnh7HtZrj1K2mEufWVMCV01D75/5SSlfoi9Xxf
4mKojrqF47sfGLNYZkcigodJx7dLcHD0Dx23nKU3AuAdmPhLdl2HRHCljTZ4ZEe7
RLYp2KhGWDUn7dX79eB4KhUOXmMVdbi7e5VKWg4vQI8UdeCIvsLEbJ8+jrBislFX
4skMWu+59loxXaYKmgJ0EN+x3Z1eSlzYrYZwSaATgS+bajmWXQhHjK/F76IGxep0
O26sOfM3p/oIbhMUnYRcG3tK5/bc2YQccWQlh5O1l+Qa2os6vDERsEWG3yv74QgZ
lsadvBArI4Wz6PKdZT8pQBoWrXMherSqo2iSs2U3gZMbk29Gmgdor/vy/gF14Mds
N020Kg6xUjMRqQLkl3VZzNHdGhi2gQdTMktigoImthqpuSUDIeIGZjATyg7ZmsTa
YS1yAtmJiPzoC+IqmC28PGk+eZ8rJQEq2ipraLY+RQc1GV1sRniv6nj6+C2OlgYf
vX6ViOr+QqO+oTujM10hyCkZCX0gCCnnSJHZa4lxzkBr6BiTUapd7JZNtofT8H2m
xnebSgG85bGzPFL1tzZYAm5QaspwkgFT2g97XvyEN+JVAXkiJTkbnMWW33cnyDOC
/kPFkYkhzKceW+pGkYYPbMpta/Bm7yhITJ79UMdhN214XM6fV0325rZPdQARAQAB
tCNOdWJlcyBQcm92aWRlciA8bnViZXNAdGVycmEuazhjLnJ1PokCfQQTAQgAcQWC
aXYSqAMLCQcJED7EZz63mCOKNRQAAAAAABwAEHNhbHRAbm90YXRpb25zLm9wZW5w
Z3Bqcy5vcmc9Meal6wZYOa4GSbmo/rRcAhUIAxYAAgIZAQKbAwIeARYhBIZv2T1F
bcqADyRIQT7EZz63mCOKAADkkQ//WRUo1yq+1boJ1tmkiRfhRWmg9PXhEPVg/gCC
nAm21yv2BTx7TrFbSMliUF3G8egQx6ZSaUiZUUIW9+x6V0CddR0w+eGNzFSyqf9q
scC3T6qW/k+m6Hqr9upGEFrpz9dXHWi6FCU5sjou5cAk/JUinzpaaiU6JPsXVnlE
QwtvlJEftsDLBZ0pxrni9LbMykR2v8rThZahCHFU9J6cEs5/IdfBmh2erjaDzj4M
g7FjDTk3h86ovgVWvaBzTs4FZHdI1BhtQK3IO31kVqZj3RtrdWy6o0O5bSVv3cxO
ofnvEW2is9OduvgkOq9EeuQXkD/kuE2adxmH4RAUEeDXCSknk82FdsSmFok66VgG
Kb8XXiIAtjsqyxwNe4Y+yr+gYACdfVn/C3b+5hlQWtBAlVJRjfnn3FWhmiSBAJtF
2swVSk4s0oFM1hEmBNLG45CnATOVPGI1LR3AToYg2gPCb/BXExDE0hKJd7ZaV0aS
NSpJTtG2nrH8qFi8Y9WM41klwmE650idUN9SoecuUQsedFhZKPfJiKeTSt1CnqQr
nsQb3lr/LEwZmiehb+eDif2ndgAxP+T8ySHbdXvoX5zF5bcx63lhQKnExc9zmrWZ
gn60BnZ8aLr8G1CkMhm4fugdVkcXoQAmOXRJEdu3kbY8xI2350Cxw3H3E9gzpx6p
amKpVOK5Ag0EaXYSqAEQALMb57+x1zm2hs4DdCmajxRGZ1F4DJQFmKgV/z0KSeeO
8DYJp+vZ/zU6wQX6GU4kbYOK9+sUq8VrZTrUe1CFQuIfUWMQj03cXWizTTktcsfV
nLyj2ucNpTZxV2Yx/4A7T1x48ICt6q2vVoAI2nshqfrxL1J629olW8XG7v5kKQtx
IwHVVzgGgnfLVo/IkysudzYYAehP6E1aGiMRt6ZWOsq71FOeIjTD4FOmTzfzNyXP
zn31C3R6Cka7/xn/frN4KUVBu5ynFkfpifJvuSPX1DRk3nz+fEtilPCoHx9UZERm
sFKwjzPpCEoqMYi0PbjeJnILS32CvZE47uw6S2YDMsHzxbd3TcIgJP7VElI7Oa7r
n71KMEkyCTbD6kcy0qCQvcCVa4/868PBbBbiu1/I7AARC12jSprNI7NlRFkprfkm
+JtzsH7drjCLqr7GKWPzU1lgVYEC9vDJInPLH/GpbTF+wQM+K84n4KRSknHK2JAX
HQ6Aop1lUa/ZeWdD5oormYb8UrLs9fmSQ8GR9b8Jpca+0P3D5+3NSBJt1sGvkify
bC5EAAD8OJo8BLm6dfE/1u3L054h35Cw+RVv2zzhigN07YQ51Ljse6cadd2usYXz
yEuxk7983tSUup8elaVSGtSvKcTXjylpZoK+R8oemmdnEbuJM3zRFDRLPKqZdALV
ABEBAAGJAmwEGAEIAGAFgml2EqgJED7EZz63mCOKNRQAAAAAABwAEHNhbHRAbm90
YXRpb25zLm9wZW5wZ3Bqcy5vcmfVQTha3ksLF9ZTQkJ+Iv5tApsMFiEEhm/ZPUVt
yoAPJEhBPsRnPreYI4oAAGkyD/9yA6a59iOhPedtOIEmjJWVvjtv06yYlnB66tbM
WGXWTofsb98CF9bymE+YvMNXYvqkw4q0P7OY6D64PXhQTSiYRtDscIZ8w3Hw5t73
qttZ0QAx5HFjKoQUnyHvGO1rUgykKx+9sytTQwBIFq4FmyVQltsY8tX8D1nLS0iH
8IFwqBNM26bVcAkV9aeayjoRKodyy9Xz035Bmh8pFIMjM2JvCoub1TrftF2EzYng
ljQF76AQHGmPa36rq2oSocE+xP5GFyZv+PEPGCFTLo/5ZaHui8iqPMfVWIAdWAj1
a6SW6zDk8DQQRGll4e7kWpGZ2+z4Zk9o449Ka7Kwc6lgpx86Ir6XT0XJKX55Q7Od
dKajTMDJE36t/00oAS4/AokL7StJTwmpMQAPv5/829uPkfcV6Oll79XvAcwV7vSq
0is+m5InUzkwunuBUsYBCtFKFY47oB5D0RLGSdUlo8GLfT1tf/0n4uRSq4aQgN/D
2gvvddXyFUds0Ar4y3Hthi1QHYOL5/4pmfhH45+Hxje03XSI9twGMqpFoennAYvV
wuyeu9XNXDI4gKiAMbzyxyhifOooBOyxOKEtXWPzfP8v9iTFw7cofmvSD1FTt45l
6JckYfV3TFaN2lFD5SxrasIuYPWPoOf4zzcljQjqOw0sVqXeaQgkq/+soD08YzKg
6cZ0NQ==
=3Ea2
-----END PGP PUBLIC KEY BLOCK-----`,
}
resp := DownloadResponse{
Protocols: []string{"5.0"},
OS: osType,
Arch: arch,
Filename: filename,
DownloadURL: downloadLink,
ShasumsURL: shasumsLink,
ShasumsSignatureURL: sigLink,
Shasum: shasumValue,
SigningKeys: SigningKeys{
GPGPublicKeys: []GPGPublicKey{gpgKey},
},
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(resp)
}

View File

@ -0,0 +1,19 @@
#!/usr/bin/env bash
set -euo pipefail
# Build and optionally push the registry-server image with docs handler
# Usage: ./scripts/build-and-push-registry-server.sh <image-repo> <tag>
IMAGE_REPO=${1:-naeel}
TAG=${2:-docs-dev}
cd operator
make build-registry IMAGE_REPO=${IMAGE_REPO} TAG=${TAG}
echo "Built image: ${IMAGE_REPO}/terraform-registry-server:${TAG}"
echo "To push: docker push ${IMAGE_REPO}/terraform-registry-server:${TAG}"
echo "To deploy to cluster (example):"
echo " kubectl apply -f operator/manifests/06-registry-server-docs.yaml"
echo "and edit the file to replace IMAGE_REPO/TAG with your registry and tag before applying."

View File

@ -0,0 +1,9 @@
#!/usr/bin/env bash
set -euo pipefail
# Create namespace if missing and apply kustomize overlay for dev
kubectl create namespace terra-dev --dry-run=client -o yaml | kubectl apply -f -
kubectl apply -k k8s/overlays/dev
echo "Applied kustomize overlay for terra-dev."
echo "To init terraform backend for dev: terraform init -reconfigure -backend-config=terraform/backend-dev.hcl"

View File

@ -0,0 +1,69 @@
#!/bin/bash
set -e
# Cleanup
rm -rf build_artifacts
mkdir -p build_artifacts
VERSION="${1:-2.0.0}"
# Function to build and zip
build_and_zip() {
OS=$1
ARCH=$2
echo "Building for $OS/$ARCH..."
BINARY_NAME="terraform-provider-nubes_v${VERSION}"
if [ "$OS" == "windows" ]; then
BINARY_NAME="${BINARY_NAME}.exe"
fi
env CGO_ENABLED=0 GOOS=$OS GOARCH=$ARCH go build -o build_artifacts/${BINARY_NAME} .
cd build_artifacts
ZIP_NAME="terraform-provider-nubes_${VERSION}_${OS}_${ARCH}.zip"
# Zip the binary
if [[ "$OS" == "windows" ]]; then
# For windows, we might need zip to handle the .exe extension properly if we were relying on unix permissions, but zip handles it ok.
# Using python zipfile as 'zip' command was missing earlier
python3 -m zipfile -c $ZIP_NAME $BINARY_NAME
else
# Ensure executable permission
chmod +x $BINARY_NAME
python3 -m zipfile -c $ZIP_NAME $BINARY_NAME
fi
# Remove binary to save space/confusion (optional, but cleaner)
rm $BINARY_NAME
cd ..
}
# Build for all targets
build_and_zip linux amd64
build_and_zip windows amd64
build_and_zip darwin amd64
build_and_zip darwin arm64
echo "Calculating SHA256SUMS..."
cd build_artifacts
sha256sum *.zip > terraform-provider-nubes_${VERSION}_SHA256SUMS
echo "Signing SHA256SUMS..."
# Detached binary signature
gpg --batch --detach-sign --default-key 866FD93D456DCA800F2448413EC4673EB798238A --output terraform-provider-nubes_${VERSION}_SHA256SUMS.sig terraform-provider-nubes_${VERSION}_SHA256SUMS
echo "Uploading to S3..."
# Base S3 path
S3_PATH="registry/terraform-registry/terra.k8c.ru/nubes/nubes/${VERSION}/"
# Upload zips
for f in *.zip; do
mc cp $f $S3_PATH
done
# Upload sums and sig
mc cp terraform-provider-nubes_${VERSION}_SHA256SUMS $S3_PATH
mc cp terraform-provider-nubes_${VERSION}_SHA256SUMS.sig $S3_PATH
echo "Done!"

View File

@ -0,0 +1,30 @@
#!/usr/bin/env bash
set -euo pipefail
# Usage: publish-docs.sh <site-dir> <registry-host> <namespace> <name> <version>
SITE_DIR=${1:-site}
REGISTRY_HOST=${2:-terra.k8c.ru}
NAMESPACE=${3:-nubes}
NAME=${4:-nubes}
VERSION=${5:-dev}
# Support both S3_* (New Standard) and MINIO_* (Legacy) variables
ENDPOINT=${S3_ENDPOINT:-${MINIO_ENDPOINT:-}}
ACCESS_KEY=${S3_ACCESS_KEY:-${MINIO_ACCESS_KEY:-}}
SECRET_KEY=${S3_SECRET_KEY:-${MINIO_SECRET_KEY:-}}
if [ -z "$ENDPOINT" ] || [ -z "$ACCESS_KEY" ] || [ -z "$SECRET_KEY" ]; then
echo "Error: S3_ENDPOINT/S3_ACCESS_KEY/S3_SECRET_KEY must be set"
exit 2
fi
MC_ALIAS=registry
mc alias set $MC_ALIAS "$ENDPOINT" "$ACCESS_KEY" "$SECRET_KEY" --api S3v4
TARGET="${MC_ALIAS}/terraform-registry/docs/${NAMESPACE}/${NAME}/${VERSION}/"
# Create target bucket path if needed (mc will create directories implicitly when copying)
mc cp --recursive "$SITE_DIR/" "$TARGET"
# Optionally set public policy
mc policy set public "$TARGET" || true
echo "Published docs to: https://${REGISTRY_HOST}/docs/${NAMESPACE}/${NAME}/${VERSION}/"

View File

@ -0,0 +1,84 @@
#!/bin/bash
set -e
# === CONFIGURATION ===
ROOT_DIR="/home/naeel/terra"
TEST_DIR="$ROOT_DIR/tests/lifecycle_scenario"
TF_LOG_FILE="$TEST_DIR/test_output_tubulus_advanced.log"
# Define colors
GREEN='\033[0;32m'
RED='\033[0;31m'
NC='\033[0m'
log() {
echo -e "${GREEN}[TEST] $1${NC}"
echo "[TEST] $1" >> $TF_LOG_FILE
}
error() {
echo -e "${RED}[ERROR] $1${NC}"
echo "[ERROR] $1" >> $TF_LOG_FILE
}
# 1. Build Provider
log "Building Provider..."
cd $ROOT_DIR
go build -o terraform-provider-nubes
if [ $? -ne 0 ]; then
error "Build failed!"
exit 1
fi
# 2. Setup Local Mirror in Test Dir
PLUGIN_DIR="$TEST_DIR/plugins/terraform.local/nubes/nubes/1.0.0/linux_amd64"
mkdir -p "$PLUGIN_DIR"
cp terraform-provider-nubes "$PLUGIN_DIR/"
# 3. Setup Test Directory
log "Setting up Test Directory: $TEST_DIR"
cd $TEST_DIR
# 4. Init with Plugin Dir
log "Initializing Terraform..."
rm -rf .terraform.lock.hcl .terraform
terraform init -plugin-dir="$TEST_DIR/plugins" > /dev/null
echo "Resuming Tests from Step D..." > $TF_LOG_FILE
# === ENSURE TF FILE IS CORRECT FOR DESTROY ===
log "Ensuring TF file matches state..."
cat <<EOF > tubulus.tf
resource "nubes_tubulus_instance" "bolvanka" {
display_name = "Bolvanka_Lifecycle_Test_014"
description = "Restored Description"
body_message = "Initial State"
duration_ms = 1000
}
output "bolvanka_id" {
value = nubes_tubulus_instance.bolvanka.id
}
EOF
# === TEST D: SUSPEND (SOFT DELETE) ===
log "TEST D: Suspending Instance (Terraform Destroy)..."
if terraform destroy -auto-approve >> $TF_LOG_FILE 2>&1; then
log "TEST D PASSED: Destroy (Suspend) successful."
else
error "TEST D FAILED: Destroy returned error."
cat $TF_LOG_FILE
exit 1
fi
# === TEST E: RESUME (CREATE on SUSPENDED) ===
log "TEST E: Resuming Instance (Terraform Apply)..."
if terraform apply -auto-approve >> $TF_LOG_FILE 2>&1; then
log "TEST E PASSED: Resume (Create) successful."
else
error "TEST E FAILED: Apply (Resume) returned error."
cat $TF_LOG_FILE
exit 1
fi
log "ALL ADVANCED TESTS COMPLETED SUCCESSFULLY."

View File

@ -0,0 +1,70 @@
terraform {
required_providers {
nubes = {
source = "terraform.local/nubes/nubes"
}
}
}
provider "nubes" {
api_endpoint = var.api_endpoint
api_token = var.api_token
}
/*
resource "nubes_s3_bucket" "test_bucket" {
name = var.s3_bucket_name
s3_root_service_uid = "e5375174-36ec-4512-bba9-b56f9eeba0bd" # Dummy
max_size_bytes = -1
storage_class = "HOT"
read_all = false
list_all = false
cors_all = false
}
resource "nubes_postgres" "test_pg" {
organization_id = var.organization_id
organization_name = var.organization_name
resource_realm = "kvm-v1cl1-ssd" # Hardcoded or generic
s3_uid = nubes_s3_bucket.test_bucket.id
cpu = var.pg_cpu
memory = var.pg_ram
disk = var.pg_disk
instances = 1
version = var.pg_version
backup_schedule = var.backup_schedule
backup_retention = 14
parameters = "{}"
enable_pgpooler_master = false
enable_pgpooler_slave = false
allow_no_ssl = false
auto_scale = false
auto_scale_percentage = 20
auto_scale_tech_window = 1
auto_scale_quota_gb = 20
need_external_address_master = false
need_external_address_slave = false
ip_space_name_master = ""
ip_space_name_slave = ""
depends_on = [nubes_s3_bucket.test_bucket]
}
output "pg_state" {
value = nubes_postgres.test_pg.id
}
output "monitoring_url" {
value = nubes_postgres.test_pg.monitoring_url
}
output "connection_string" {
value = nubes_postgres.test_pg.internal_connect_jdbc
}
*/

View File

@ -0,0 +1,124 @@
#!/bin/bash
set -e
# === CONFIGURATION ===
ROOT_DIR="/home/naeel/terra"
TEST_DIR="$ROOT_DIR/tests/lifecycle_scenario"
DISCOVERY_DOC="$ROOT_DIR/docs/discovery/pg_s3_lifecycle_test.md"
HISTORY_DOC="$ROOT_DIR/docs/history/05_lifecycle_test_results.md"
TF_LOG_FILE="$TEST_DIR/test_output.log"
BUCKET_NAME="tf-lifecycle-bucket-$(date +%s)"
# 1. Build Provider
echo ">>> Building Provider..."
cd $ROOT_DIR
go build -o terraform-provider-nubes
if [ $? -ne 0 ]; then
echo "Build failed!"
exit 1
fi
# 2. Setup Local Mirror in Test Dir
# We use -plugin-dir to force local loading effectively
PLUGIN_DIR="$TEST_DIR/plugins/terraform.local/nubes/nubes/1.0.0/linux_amd64"
mkdir -p "$PLUGIN_DIR"
cp terraform-provider-nubes "$PLUGIN_DIR/"
# 3. Setup Test Directory
echo ">>> Setting up Test Directory: $TEST_DIR"
cd $TEST_DIR
if [ ! -f "terraform.tfvars" ]; then
echo "Copying terraform.tfvars..."
cp "$ROOT_DIR/client_package/terraform.tfvars" .
fi
# 4. Init with Plugin Dir (Bypasses network for terraform.local)
echo ">>> Initializing Terraform with local plugin mirror..."
rm -rf .terraform .terraform.lock.hcl
terraform init -plugin-dir="$TEST_DIR/plugins" > /dev/null
echo "Starting Lifecycle Test" > $TF_LOG_FILE
# === TEST STEPS ===
# Step 1: Create
echo ">>> Step 1: Initial Creation (S3 + PG [2CPU, 4GB])" | tee -a $TF_LOG_FILE
start_1=$(date +%s)
terraform apply -auto-approve \
-var="pg_cpu=2" \
-var="pg_ram=4" \
-var="pg_disk=10" \
-var="s3_bucket_name=$BUCKET_NAME" \
>> $TF_LOG_FILE 2>&1
end_1=$(date +%s)
dur_1=$((end_1 - start_1))
echo "Step 1 Done: ${dur_1}s" | tee -a $TF_LOG_FILE
# Step 2: Update (Scale Up)
echo ">>> Step 2: Modify PG (Scale Up [4CPU, 8GB])" | tee -a $TF_LOG_FILE
start_2=$(date +%s)
terraform apply -auto-approve \
-var="pg_cpu=4" \
-var="pg_ram=8" \
-var="pg_disk=10" \
-var="s3_bucket_name=$BUCKET_NAME" \
>> $TF_LOG_FILE 2>&1
end_2=$(date +%s)
dur_2=$((end_2 - start_2))
echo "Step 2 Done: ${dur_2}s" | tee -a $TF_LOG_FILE
# Step 3: Update (Scale Down)
echo ">>> Step 3: Modify PG (Scale Down [2CPU, 4GB])" | tee -a $TF_LOG_FILE
start_3=$(date +%s)
terraform apply -auto-approve \
-var="pg_cpu=2" \
-var="pg_ram=4" \
-var="pg_disk=10" \
-var="s3_bucket_name=$BUCKET_NAME" \
>> $TF_LOG_FILE 2>&1
end_3=$(date +%s)
dur_3=$((end_3 - start_3))
echo "Step 3 Done: ${dur_3}s" | tee -a $TF_LOG_FILE
# Step 4: Destroy
echo ">>> Step 4: Destroy" | tee -a $TF_LOG_FILE
terraform destroy -auto-approve \
-var="pg_cpu=2" \
-var="pg_ram=4" \
-var="pg_disk=10" \
-var="s3_bucket_name=$BUCKET_NAME" \
>> $TF_LOG_FILE 2>&1
echo "Cleanup Done" | tee -a $TF_LOG_FILE
# === REPORT ===
cd $ROOT_DIR
mkdir -p docs/discovery docs/history
cat <<EOF > $DISCOVERY_DOC
# Lifecycle Test: S3 + Postgres Update
## Test Scenario
1. **Create S3**: $BUCKET_NAME
2. **Create Postgres**: 2CPU/4RAM
3. **Update**: 4CPU/8RAM (Expect In-place)
4. **Update**: 2CPU/4RAM (Expect In-place)
5. **Destroy**: (Expect Freeze policy to keep cloud resource)
## Results $(date)
| Step | Duration | Result |
|------|----------|--------|
| Create | ${dur_1}s | Done |
| Scale Up | ${dur_2}s | Done |
| Scale Down | ${dur_3}s | Done |
See $TF_LOG_FILE for details.
EOF
cat <<EOF > $HISTORY_DOC
# 05 Lifecycle Test Results
Date: $(date)
Success. Updates performed in-place.
EOF
echo "Docs generated."

View File

@ -0,0 +1,142 @@
#!/bin/bash
set -e
# === CONFIGURATION ===
ROOT_DIR="/home/naeel/terra"
TEST_DIR="$ROOT_DIR/tests/lifecycle_scenario"
TF_LOG_FILE="$TEST_DIR/test_output_tubulus_advanced.log"
# Define colors
GREEN='\033[0;32m'
RED='\033[0;31m'
NC='\033[0m'
log() {
echo -e "${GREEN}[TEST] $1${NC}"
echo "[TEST] $1" >> $TF_LOG_FILE
}
error() {
echo -e "${RED}[ERROR] $1${NC}"
echo "[ERROR] $1" >> $TF_LOG_FILE
}
# 1. Build Provider
log "Building Provider..."
cd $ROOT_DIR
go build -o terraform-provider-nubes
if [ $? -ne 0 ]; then
error "Build failed!"
exit 1
fi
# 2. Setup Local Mirror in Test Dir
PLUGIN_DIR="$TEST_DIR/plugins/terraform.local/nubes/nubes/1.0.0/linux_amd64"
mkdir -p "$PLUGIN_DIR"
cp terraform-provider-nubes "$PLUGIN_DIR/"
# 3. Setup Test Directory
log "Setting up Test Directory: $TEST_DIR"
cd $TEST_DIR
# 4. Init with Plugin Dir
log "Initializing Terraform..."
rm -rf .terraform.lock.hcl .terraform
terraform init -plugin-dir="$TEST_DIR/plugins" > /dev/null
echo "Starting Advanced Tubulus Tests" > $TF_LOG_FILE
# === TEST A: MODIFY (HAPPY PATH) ===
log "TEST A: Modifying instance description (Should Succeed)..."
# Modify the TF file
cat <<EOF > tubulus.tf
resource "nubes_tubulus_instance" "bolvanka" {
display_name = "Bolvanka_Lifecycle_Test_014"
description = "Modified Description by Test A"
body_message = "Initial State"
duration_ms = 1000
}
output "bolvanka_id" {
value = nubes_tubulus_instance.bolvanka.id
}
EOF
# Apply update
if terraform apply -auto-approve >> $TF_LOG_FILE 2>&1; then
log "TEST A PASSED: Modification successful."
else
error "TEST A FAILED: Apply returned error."
cat $TF_LOG_FILE
exit 1
fi
# === TEST B: MODIFY (FAILURE PATH) ===
# This tests if the provider correctly reports mistakes during 'modify' operation
log "TEST B: Triggering failure during modification (Should Fail Gracefully)..."
# Modify TF file to include failure instruction
cat <<EOF > tubulus.tf
resource "nubes_tubulus_instance" "bolvanka" {
display_name = "Bolvanka_Lifecycle_Test_014"
description = "Modified Description by Test B (Will Fail)"
body_message = "Initial State"
duration_ms = 1000
fail_in_progress = true
}
output "bolvanka_id" {
value = nubes_tubulus_instance.bolvanka.id
}
EOF
# Apply update (Expect Failure)
if ! terraform apply -auto-approve >> $TF_LOG_FILE 2>&1; then
log "TEST B PASSED: Terraform correctly reported failure."
else
error "TEST B FAILED: Terraform succeeded but should have failed!"
cat $TF_LOG_FILE
# We might need to fix the state if B passed unexpectedly, but let's assume correct behavior.
fi
# === RESTORE STATE ===
# Remove failure flag to allow subsequent tests (Suspend/Resume)
log "Restoring valid state..."
cat <<EOF > tubulus.tf
resource "nubes_tubulus_instance" "bolvanka" {
display_name = "Bolvanka_Lifecycle_Test_014"
description = "Restored Description"
body_message = "Initial State"
duration_ms = 1000
}
output "bolvanka_id" {
value = nubes_tubulus_instance.bolvanka.id
}
EOF
# We expect this to act as a repair or re-run
terraform apply -auto-approve >> $TF_LOG_FILE 2>&1
# === TEST D: SUSPEND (SOFT DELETE) ===
log "TEST D: Suspending Instance (Terraform Destroy)..."
if terraform destroy -auto-approve >> $TF_LOG_FILE 2>&1; then
log "TEST D PASSED: Destroy (Suspend) successful."
else
error "TEST D FAILED: Destroy returned error."
cat $TF_LOG_FILE
exit 1
fi
# === TEST E: RESUME (CREATE on SUSPENDED) ===
log "TEST E: Resuming Instance (Terraform Apply)..."
if terraform apply -auto-approve >> $TF_LOG_FILE 2>&1; then
log "TEST E PASSED: Resume (Create) successful."
else
error "TEST E FAILED: Apply (Resume) returned error."
cat $TF_LOG_FILE
exit 1
fi
log "ALL ADVANCED TESTS COMPLETED SUCCESSFULLY."

View File

@ -0,0 +1,36 @@
#!/bin/bash
set -e
# === CONFIGURATION ===
ROOT_DIR="/home/naeel/terra"
TEST_DIR="$ROOT_DIR/tests/lifecycle_scenario"
TF_LOG_FILE="$TEST_DIR/test_output_tubulus.log"
# 1. Build Provider
echo ">>> Building Provider..."
cd $ROOT_DIR
go build -o terraform-provider-nubes
if [ $? -ne 0 ]; then
echo "Build failed!"
exit 1
fi
# 2. Setup Local Mirror in Test Dir
PLUGIN_DIR="$TEST_DIR/plugins/terraform.local/nubes/nubes/1.0.0/linux_amd64"
mkdir -p "$PLUGIN_DIR"
cp terraform-provider-nubes "$PLUGIN_DIR/"
# 3. Setup Test Directory
echo ">>> Setting up Test Directory: $TEST_DIR"
cd $TEST_DIR
# 4. Init with Plugin Dir
echo ">>> Initializing Terraform with local plugin mirror..."
rm -rf .terraform .terraform.lock.hcl
terraform init -plugin-dir="$TEST_DIR/plugins" > /dev/null
echo "Starting Tubulus Lifecycle Test" > $TF_LOG_FILE
# Step 1: Create
echo ">>> Step 1: Initial Creation" | tee -a $TF_LOG_FILE
terraform apply -auto-approve | tee -a $TF_LOG_FILE

View File

@ -0,0 +1,170 @@
#!/bin/bash
set -e
# === CONFIGURATION ===
ROOT_DIR="/home/naeel/terra"
TEST_DIR="$ROOT_DIR/tests/lifecycle_scenario"
TF_LOG_FILE="$TEST_DIR/stress_test.log"
# Define colors
GREEN='\033[0;32m'
RED='\033[0;31m'
NC='\033[0m'
YELLOW='\033[1;33m'
log() {
echo -e "${GREEN}[STRESS] $1${NC}"
echo "[STRESS] $1" >> $TF_LOG_FILE
}
error() {
echo -e "${RED}[ERROR] $1${NC}"
echo "[ERROR] $1" >> $TF_LOG_FILE
}
warn() {
echo -e "${YELLOW}[WARN] $1${NC}"
echo "[WARN] $1" >> $TF_LOG_FILE
}
# Ensure we are in the right directory
cd $TEST_DIR
echo "STARTING STRESS TESTS" > $TF_LOG_FILE
# === TEST 1: FAST UPDATE (Duration 500ms) ===
log "TEST 1: Fast Update (Modify)..."
cat <<EOF > tubulus.tf
resource "nubes_tubulus_instance" "bolvanka" {
display_name = "Bolvanka_Lifecycle_Test_014"
description = "Stress Test 1: Fast"
body_message = "Go Fast"
duration_ms = 500
}
output "bolvanka_id" {
value = nubes_tubulus_instance.bolvanka.id
}
EOF
if terraform apply -auto-approve >> $TF_LOG_FILE 2>&1; then
log "TEST 1 PASSED"
else
warn "TEST 1 Modify Failed. Instance might be in bad state."
log "Performing cleanup (Destroy & Recreate)..."
terraform destroy -auto-approve >> $TF_LOG_FILE 2>&1
if terraform apply -auto-approve >> $TF_LOG_FILE 2>&1; then
log "TEST 1 PASSED (via Recreate)"
else
error "TEST 1 FAILED"
cat $TF_LOG_FILE
exit 1
fi
fi
# === TEST 2: HEAVY Load (Duration 5000ms) ===
log "TEST 2: Long Duration Update (Should wait 5s+)..."
cat <<EOF > tubulus.tf
resource "nubes_tubulus_instance" "bolvanka" {
display_name = "Bolvanka_Lifecycle_Test_014"
description = "Stress Test 2: Slow"
body_message = "Go Slow"
duration_ms = 5000
}
output "bolvanka_id" {
value = nubes_tubulus_instance.bolvanka.id
}
EOF
if terraform apply -auto-approve >> $TF_LOG_FILE 2>&1; then
log "TEST 2 PASSED"
else
error "TEST 2 FAILED"
cat $TF_LOG_FILE
exit 1
fi
# === TEST 3: INTENTIONAL FAILURE (Fail At Start) ===
log "TEST 3: Triggering Failure (fail_at_start=true)..."
cat <<EOF > tubulus.tf
resource "nubes_tubulus_instance" "bolvanka" {
display_name = "Bolvanka_Lifecycle_Test_014"
description = "Stress Test 3: Doom"
fail_at_start = true
duration_ms = 1000
}
output "bolvanka_id" {
value = nubes_tubulus_instance.bolvanka.id
}
EOF
if ! terraform apply -auto-approve >> $TF_LOG_FILE 2>&1; then
log "TEST 3 PASSED (Expected Failure occurred)"
else
error "TEST 3 FAILED: Terraform succeeded but should have failed!"
cat $TF_LOG_FILE
exit 1
fi
# === TEST 4: RECOVERY FROM FAILURE ===
log "TEST 4: Recovery (Fixing config)..."
cat <<EOF > tubulus.tf
resource "nubes_tubulus_instance" "bolvanka" {
display_name = "Bolvanka_Lifecycle_Test_014"
description = "Stress Test 4: Recovered"
body_message = "We are back"
duration_ms = 1000
}
output "bolvanka_id" {
value = nubes_tubulus_instance.bolvanka.id
}
EOF
if terraform apply -auto-approve >> $TF_LOG_FILE 2>&1; then
log "TEST 4 PASSED"
else
warn "TEST 4 Modify Failed. This is expected if the previous failure froze the instance."
log "Attempting Hard Reset (Destroy & Recreate)..."
# Force destroy to clear the stuck instance
terraform destroy -auto-approve >> $TF_LOG_FILE 2>&1
# Re-apply the valid configuration
if terraform apply -auto-approve >> $TF_LOG_FILE 2>&1; then
log "TEST 4 RECOVERY PASSED (via Hard Reset)"
else
error "TEST 4 FAILED: Could not recover even with Hard Reset."
cat $TF_LOG_FILE
exit 1
fi
fi
# === TEST 5: COMPLEX UPDATE (Multiple Fields) ===
log "TEST 5: Complex Multi-field Update..."
cat <<EOF > tubulus.tf
resource "nubes_tubulus_instance" "bolvanka" {
display_name = "Bolvanka_Lifecycle_Test_014"
description = "Stress Test 5: Complex"
body_message = "Complex Payload: JSON { key: 'value' }"
duration_ms = 2000
yaml_example = "some: yaml"
}
output "bolvanka_id" {
value = nubes_tubulus_instance.bolvanka.id
}
EOF
if terraform apply -auto-approve >> $TF_LOG_FILE 2>&1; then
log "TEST 5 PASSED"
else
error "TEST 5 FAILED"
cat $TF_LOG_FILE
exit 1
fi
log "ALL STRESS TESTS COMPLETED SUCCESSFULLY. BOLVANKA IS HOT!"

View File

@ -0,0 +1,11 @@
resource "nubes_tubulus_instance" "bolvanka" {
display_name = "Bolvanka_Lifecycle_Test_014"
description = "Stress Test 5: Complex"
body_message = "Complex Payload: JSON { key: 'value' }"
duration_ms = 2000
yaml_example = "some: yaml"
}
output "bolvanka_id" {
value = nubes_tubulus_instance.bolvanka.id
}

View File

@ -0,0 +1,55 @@
variable "api_endpoint" {
type = string
default = "https://deck-api.ngcloud.ru/api/v1/index.cfm"
}
variable "api_token" {
type = string
sensitive = true
}
variable "organization_id" {
type = string
default = "dummy"
}
variable "organization_name" {
type = string
default = "dummy"
}
variable "s3_user_uid" {
type = string
description = "UID of the S3 User (required for S3 bucket)"
default = "dummy"
}
variable "s3_bucket_name" {
type = string
default = "tf-test-bucket-lifecycle"
}
variable "pg_version" {
type = string
default = "15"
}
variable "pg_cpu" {
type = number
default = 2
}
variable "pg_ram" {
type = number
default = 4
}
variable "pg_disk" {
type = number
default = 10
}
variable "backup_schedule" {
type = string
default = "0 0 * * *"
}

View File

@ -0,0 +1,41 @@
terraform {
required_providers {
nubes = {
source = "terraform.local/local/nubes"
}
}
}
provider "nubes" {
api_token = var.api_token
}
variable "api_token" {
type = string
sensitive = true
}
variable "organization_id" {
type = string
}
variable "s3_uid" {
type = string
}
resource "nubes_postgres" "test_pg" {
organization_id = var.organization_id
organization_name = "test-org"
resource_realm = "k8s-3.ext.nubes.ru"
s3_uid = var.s3_uid
cpu = 700
memory = 1024
disk = 12
instances = 1
version = "17"
deletion_protection = false
}
output "pg_id" {
value = nubes_postgres.test_pg.id
}

View File

@ -0,0 +1,13 @@
[
{
"test": "Test1-Fast",
"instruction": "сделай быстро",
"operation": "plan",
"duration_ms": 0,
"body_message": "null",
"where_fail": 0,
"status": "failed",
"error": " on test_auto_nli.tf line 1, in resource \"nubes_tubulus_instance\" \"auto_test\":\n 1: resource \"nubes_tubulus_instance\" \"auto_test\" {\n\nFailed to get configuration from AI: GEMINI_API_KEY not f",
"timestamp": "2026-01-24T22:00:50+04:00"
}
]

View File

@ -0,0 +1,32 @@
Warning: Provider development overrides are in effect
The following provider development overrides are set in the CLI
configuration:
- terraform.local/local/nubes in /home/naeel/terra
The behavior may therefore not match any released version of the provider and
applying changes may cause the state to become incompatible with published
releases.
Planning failed. Terraform encountered an error while generating this plan.
Warning: Resource targeting is in effect
You are creating a plan with the -target option, which means that the result
of this plan may not represent all of the changes requested by the current
configuration.
The -target option is not for routine use, and is provided only for
exceptional situations such as recovering from errors or mistakes, or when
Terraform specifically suggests to use it as part of an error message.
Error: Gemini AI Error
with nubes_tubulus_instance.auto_test,
on test_auto_nli.tf line 1, in resource "nubes_tubulus_instance" "auto_test":
1: resource "nubes_tubulus_instance" "auto_test" {
Failed to get configuration from AI: GEMINI_API_KEY not found (set env var or
create gemini_api_key.txt)

View File

@ -0,0 +1,198 @@
#!/bin/bash
set -e
# NLI Auto Test Suite
# Создаёт, проверяет и удаляет Tubulus инстансы с AI инструкциями
export TF_CLI_CONFIG_FILE="/home/naeel/terra/dev_override.tfrc"
cd /home/naeel/terra/tests/modify_postgres
RESULTS_FILE="nli_test_results.json"
echo "[]" > $RESULTS_FILE
log_result() {
local test_name="$1"
local instruction="$2"
local operation="$3"
local duration_ms="$4"
local body_message="$5"
local where_fail="$6"
local status="$7"
local error="$8"
# Escape quotes for JSON
instruction=$(echo "$instruction" | sed 's/"/\\"/g')
body_message=$(echo "$body_message" | sed 's/"/\\"/g')
error=$(echo "$error" | sed 's/"/\\"/g' | head -c 200)
jq ". += [{
\"test\": \"$test_name\",
\"instruction\": \"$instruction\",
\"operation\": \"$operation\",
\"duration_ms\": $duration_ms,
\"body_message\": \"$body_message\",
\"where_fail\": $where_fail,
\"status\": \"$status\",
\"error\": \"$error\",
\"timestamp\": \"$(date -Iseconds)\"
}]" $RESULTS_FILE > ${RESULTS_FILE}.tmp && mv ${RESULTS_FILE}.tmp $RESULTS_FILE
}
run_create_test() {
local name="$1"
local instruction="$2"
echo "=== Testing: $name ==="
echo "Instruction: $instruction"
cat > test_auto_nli.tf <<EOF
resource "nubes_tubulus_instance" "auto_test" {
display_name = "$name"
description = "NLI Auto Test"
instruction = "$instruction"
}
output "duration" {
value = nubes_tubulus_instance.auto_test.duration_ms
}
output "body_msg" {
value = nubes_tubulus_instance.auto_test.body_message
}
output "where_fail" {
value = nubes_tubulus_instance.auto_test.where_fail
}
output "status" {
value = nubes_tubulus_instance.auto_test.status
}
EOF
# Plan
if ! terraform plan -var-file=terraform.tfvars -target=nubes_tubulus_instance.auto_test -no-color > plan_output.txt 2>&1; then
log_result "$name" "$instruction" "plan" "0" "null" "0" "failed" "$(cat plan_output.txt | tail -5)"
return 1
fi
# Apply
if ! terraform apply -auto-approve -var-file=terraform.tfvars -target=nubes_tubulus_instance.auto_test -no-color > apply_output.txt 2>&1; then
log_result "$name" "$instruction" "apply" "0" "null" "0" "failed" "$(cat apply_output.txt | tail -5)"
return 1
fi
# Extract outputs
duration=$(terraform output -raw duration 2>/dev/null || echo "0")
body=$(terraform output -raw body_msg 2>/dev/null || echo "null")
fail=$(terraform output -raw where_fail 2>/dev/null || echo "0")
status=$(terraform output -raw status 2>/dev/null || echo "unknown")
log_result "$name" "$instruction" "create" "$duration" "$body" "$fail" "success" ""
echo "✓ Created: duration=$duration, body=$body, fail=$fail, status=$status"
# Cleanup
terraform destroy -auto-approve -var-file=terraform.tfvars -target=nubes_tubulus_instance.auto_test -no-color > /dev/null 2>&1 || true
rm -f test_auto_nli.tf
return 0
}
run_modify_test() {
local name="$1"
local instruction1="$2"
local instruction2="$3"
echo "=== Modify Test: $name ==="
echo "Initial: $instruction1"
echo "Modified: $instruction2"
# Create initial
cat > test_auto_nli.tf <<EOF
resource "nubes_tubulus_instance" "auto_test" {
display_name = "$name"
description = "NLI Modify Test"
instruction = "$instruction1"
}
output "duration" {
value = nubes_tubulus_instance.auto_test.duration_ms
}
EOF
terraform apply -auto-approve -var-file=terraform.tfvars -target=nubes_tubulus_instance.auto_test -no-color > /dev/null 2>&1 || return 1
duration1=$(terraform output -raw duration 2>/dev/null || echo "0")
log_result "$name-initial" "$instruction1" "create" "$duration1" "null" "0" "success" ""
# Modify
cat > test_auto_nli.tf <<EOF
resource "nubes_tubulus_instance" "auto_test" {
display_name = "$name"
description = "NLI Modify Test"
instruction = "$instruction2"
}
output "duration" {
value = nubes_tubulus_instance.auto_test.duration_ms
}
EOF
if terraform apply -auto-approve -var-file=terraform.tfvars -target=nubes_tubulus_instance.auto_test -no-color > /dev/null 2>&1; then
duration2=$(terraform output -raw duration 2>/dev/null || echo "0")
log_result "$name-modified" "$instruction2" "modify" "$duration2" "null" "0" "success" ""
echo "✓ Modified: $duration1$duration2"
else
log_result "$name-modified" "$instruction2" "modify" "0" "null" "0" "failed" "modify not supported"
echo "✗ Modify failed (expected for immutable resource)"
fi
# Cleanup
terraform destroy -auto-approve -var-file=terraform.tfvars -target=nubes_tubulus_instance.auto_test -no-color > /dev/null 2>&1 || true
rm -f test_auto_nli.tf
return 0
}
echo "🚀 Starting NLI Auto Test Suite"
echo "================================"
echo ""
# 10 CREATE tests
run_create_test "Test1-Fast" "сделай быстро"
sleep 2
run_create_test "Test2-Long" "пусть работает очень долго"
sleep 2
run_create_test "Test3-Message" "напиши hello world в вольт"
sleep 2
run_create_test "Test4-Fail" "сломай сразу при старте"
sleep 2
run_create_test "Test5-FailMid" "упади в процессе на втором этапе"
sleep 2
run_create_test "Test6-Complex" "поработай 15 секунд, положи пароль123 и упади на третьем"
sleep 2
run_create_test "Test7-Typo" "зделай нармална на 7 сикунд"
sleep 2
run_create_test "Test8-Minute" "создай на минуту без ошибок"
sleep 2
run_create_test "Test9-Vague" "просто что-нибудь сделай"
sleep 2
run_create_test "Test10-Technical" "duration 25000ms, fail at stage 1, message: SECRET"
sleep 2
# 5 MODIFY tests
run_modify_test "Modify1" "быстро" "медленно на 2 минуты"
sleep 2
run_modify_test "Modify2" "без ошибок" "сломай в середине"
sleep 2
run_modify_test "Modify3" "напиши hello" "напиши goodbye"
sleep 2
run_modify_test "Modify4" "на 10 секунд" "на 30 секунд"
sleep 2
run_modify_test "Modify5" "stage 1" "stage 3"
echo ""
echo "================================"
echo "✅ Test suite completed!"
echo "Results saved to: $RESULTS_FILE"
cat $RESULTS_FILE | jq '.'

View File

@ -0,0 +1,18 @@
resource "nubes_tubulus_instance" "ai_test" {
display_name = "AI Bolvanka v4 unique"
description = "Testing Gemini AI instruction"
instruction = "Создай тубулус на 12 секунд, без ошибок, в вольт напиши: 'секретный ключ 123'"
}
output "ai_duration" {
value = nubes_tubulus_instance.ai_test.duration_ms
}
output "ai_fail_in_progress" {
value = nubes_tubulus_instance.ai_test.fail_in_progress
}
output "ai_where_fail" {
value = nubes_tubulus_instance.ai_test.where_fail
}

View File

@ -0,0 +1,29 @@
resource "nubes_tubulus_instance" "advanced1" {
display_name = "Advanced 1"
description = "Сложный сценарий"
instruction = "создай на 30 секунд, пусть упадет в середине работы, в вольт запиши мой_пароль_123"
}
resource "nubes_tubulus_instance" "advanced2" {
display_name = "Advanced 2"
description = "Домохозяйка 1"
instruction = "я не знаю что делать просто сделай что-нибудь"
}
resource "nubes_tubulus_instance" "advanced3" {
display_name = "Advanced 3"
description = "Домохозяйка 2"
instruction = "нужно чтобы это работало хотя бы полминутки"
}
resource "nubes_tubulus_instance" "advanced4" {
display_name = "Advanced 4"
description = "Технический"
instruction = "duration 15000ms, fail at stage 3, message: SECRET_KEY"
}
resource "nubes_tubulus_instance" "advanced5" {
display_name = "Advanced 5"
description = "Смешанный стиль"
instruction = "поработай секунд 20, положи туда 'hello world' и вали на 1 этапе"
}

View File

@ -0,0 +1,41 @@
resource "nubes_tubulus_instance" "test1" {
display_name = "Test 1"
description = "Тест простой запрос"
instruction = "сделай быстро"
}
resource "nubes_tubulus_instance" "test2" {
display_name = "Test 2"
description = "Тест долго"
instruction = "пусть работает долго долго"
}
resource "nubes_tubulus_instance" "test3" {
display_name = "Test 3"
description = "Тест с ошибкой"
instruction = "сломай его сразу"
}
resource "nubes_tubulus_instance" "test4" {
display_name = "Test 4"
description = "Тест с сообщением"
instruction = "напиши привет"
}
resource "nubes_tubulus_instance" "test5" {
display_name = "Test 5"
description = "Противоречивый"
instruction = "сделай быстро но долго и сломай но не ломай"
}
resource "nubes_tubulus_instance" "test6" {
display_name = "Test 6"
description = "Неполный"
instruction = "хочу тубулус"
}
resource "nubes_tubulus_instance" "test7" {
display_name = "Test 7"
description = "С опечатками"
instruction = "зделай нормална на 10 сикунд"
}

View File

@ -0,0 +1,21 @@
resource "nubes_tubulus_instance" "auto_test" {
display_name = "Test1-Fast"
description = "NLI Auto Test"
instruction = "сделай быстро"
}
output "duration" {
value = nubes_tubulus_instance.auto_test.duration_ms
}
output "body_msg" {
value = nubes_tubulus_instance.auto_test.body_message
}
output "where_fail" {
value = nubes_tubulus_instance.auto_test.where_fail
}
output "status" {
value = nubes_tubulus_instance.auto_test.status
}

View File

@ -0,0 +1,92 @@
[
{
"scenario": "destroy",
"test": "Test1-Destroy",
"instruction": "сделай быстро",
"operation": "apply",
"status": "failed",
"error": " 1: resource \"nubes_tubulus_instance\" \"test\" {\n\nFailed to get configuration from AI: Post\n\"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.0-flash:generateContent?%24alt=json%3",
"timestamp": "2026-01-25T10:18:05+04:00"
},
{
"scenario": "destroy",
"test": "Test2-Destroy",
"instruction": "работай 30 секунд",
"operation": "apply",
"status": "failed",
"error": "Content-Type: application/json Content-Length: 119 Version: 1.0.217\nStrict-Transport-Security: max-age=31536000; includeSubDomains\nX-Content-Type-Options: nosniff Referrer-Policy: origin X-Frame-Optio",
"timestamp": "2026-01-25T10:18:21+04:00"
},
{
"scenario": "destroy",
"test": "Test3-Destroy",
"instruction": "напиши в вольт секрет_123",
"operation": "plan",
"status": "failed",
"error": "\nError: Gemini AI Error\n\n with nubes_tubulus_instance.advanced3,\n on test_ai_advanced.tf line 13, in resource \"nubes_tubulus_instance\" \"advanced3\":\n 13: resource \"nubes_tubulus_instance\" \"ad",
"timestamp": "2026-01-25T10:18:32+04:00"
},
{
"scenario": "destroy",
"test": "Test4-Destroy",
"instruction": "сломайся на 2 этапе",
"operation": "apply",
"status": "failed",
"error": "Content-Type: application/json Content-Length: 119 Version: 1.0.217\nStrict-Transport-Security: max-age=31536000; includeSubDomains\nX-Content-Type-Options: nosniff Referrer-Policy: origin X-Frame-Optio",
"timestamp": "2026-01-25T10:18:47+04:00"
},
{
"scenario": "destroy",
"test": "Test5-Destroy",
"instruction": "долго долго работай",
"operation": "apply",
"status": "failed",
"error": "Content-Type: application/json Content-Length: 119 Version: 1.0.217\nStrict-Transport-Security: max-age=31536000; includeSubDomains\nX-Content-Type-Options: nosniff Referrer-Policy: origin X-Frame-Optio",
"timestamp": "2026-01-25T10:18:57+04:00"
},
{
"scenario": "remove",
"test": "Test1-Remove",
"instruction": "сделай на 5 секунд",
"operation": "create",
"status": "failed",
"error": "Content-Type: application/json Content-Length: 119 Version: 1.0.217\nStrict-Transport-Security: max-age=31536000; includeSubDomains\nX-Content-Type-Options: nosniff Referrer-Policy: origin X-Frame-Optio",
"timestamp": "2026-01-25T10:19:06+04:00"
},
{
"scenario": "remove",
"test": "Test2-Remove",
"instruction": "быстро быстро",
"operation": "create",
"status": "failed",
"error": "Content-Type: application/json Content-Length: 119 Version: 1.0.217\nStrict-Transport-Security: max-age=31536000; includeSubDomains\nX-Content-Type-Options: nosniff Referrer-Policy: origin X-Frame-Optio",
"timestamp": "2026-01-25T10:19:21+04:00"
},
{
"scenario": "remove",
"test": "Test3-Remove",
"instruction": "положи текст hello_world",
"operation": "create",
"status": "failed",
"error": "Content-Type: application/json Content-Length: 119 Version: 1.0.217\nStrict-Transport-Security: max-age=31536000; includeSubDomains\nX-Content-Type-Options: nosniff Referrer-Policy: origin X-Frame-Optio",
"timestamp": "2026-01-25T10:19:29+04:00"
},
{
"scenario": "remove",
"test": "Test4-Remove",
"instruction": "сломайся сразу",
"operation": "create",
"status": "failed",
"error": "Content-Type: application/json Content-Length: 119 Version: 1.0.217\nStrict-Transport-Security: max-age=31536000; includeSubDomains\nX-Content-Type-Options: nosniff Referrer-Policy: origin X-Frame-Optio",
"timestamp": "2026-01-25T10:19:38+04:00"
},
{
"scenario": "remove",
"test": "Test5-Remove",
"instruction": "работай минуту",
"operation": "create",
"status": "failed",
"error": "Content-Type: application/json Content-Length: 119 Version: 1.0.217\nStrict-Transport-Security: max-age=31536000; includeSubDomains\nX-Content-Type-Options: nosniff Referrer-Policy: origin X-Frame-Optio",
"timestamp": "2026-01-25T10:19:45+04:00"
}
]

View File

@ -0,0 +1,6 @@
provider_installation {
dev_overrides {
"terraform.local/local/nubes" = "/home/naeel/terra/tests/postgres_test"
}
direct {}
}

View File

@ -0,0 +1,41 @@
terraform {
required_providers {
nubes = {
source = "terraform.local/local/nubes"
}
}
}
provider "nubes" {
api_token = var.api_token
}
variable "api_token" {
type = string
sensitive = true
}
variable "organization_id" {
type = string
}
variable "s3_uid" {
type = string
}
resource "nubes_postgres" "test_pg" {
organization_id = var.organization_id
organization_name = "test-org"
resource_realm = "k8s-3.ext.nubes.ru"
s3_uid = var.s3_uid
cpu = 777
memory = 2048
disk = 11
instances = 1
version = "17"
deletion_protection = false # Для тестов отключаем
}
output "pg_id" {
value = nubes_postgres.test_pg.id
}

View File

@ -0,0 +1,40 @@
terraform {
required_providers {
nubes = {
source = "terraform.local/nubes/nubes"
}
}
}
provider "nubes" {
api_token = var.api_token
}
variable "api_token" {
type = string
sensitive = true
}
variable "bucket_name" {
type = string
}
variable "max_size" {
type = number
}
resource "nubes_s3_bucket" "test" {
name = var.bucket_name
s3_root_service_uid = "e5375174-36ec-4512-bba9-b56f9eeba0bd" # Dummy or real key
max_size_bytes = var.max_size
storage_class = "HOT"
read_all = true
list_all = true
cors_all = true
}
output "bucket_id" {
value = nubes_s3_bucket.test.id
}

View File

@ -0,0 +1,83 @@
#!/bin/bash
set -e
# Define root and test dirs
ROOT_DIR="/home/naeel/terra"
TEST_DIR="$ROOT_DIR/tests/s3_test"
LOG_FILE="$TEST_DIR/s3_test.log"
echo "=== Starting S3 Resource Test ===" | tee $LOG_FILE
# 1. Build Provider
echo "Building provider..." | tee -a $LOG_FILE
cd $ROOT_DIR
go build -o terraform-provider-nubes
if [ $? -ne 0 ]; then
echo "Build failed!" | tee -a $LOG_FILE
exit 1
fi
echo "Provider built successfully." | tee -a $LOG_FILE
# Copy provider to plugins dir
PLUGIN_DIR="$TEST_DIR/plugins/terraform.local/nubes/nubes/1.0.0/linux_amd64"
mkdir -p "$PLUGIN_DIR"
cp terraform-provider-nubes "$PLUGIN_DIR/terraform-provider-nubes_v1.0.0"
echo "Provider installed to $PLUGIN_DIR" | tee -a $LOG_FILE
# 2. Setup Test Environment
cd $TEST_DIR
# Reuse terraform.tfvars from client_package if it exists
if [ -f "$ROOT_DIR/client_package/terraform.tfvars" ]; then
cp "$ROOT_DIR/client_package/terraform.tfvars" .
else
echo "Error: terraform.tfvars not found!" | tee -a $LOG_FILE
exit 1
fi
# 3. Initialize (Plugin Dir Mode)
echo "Initializing Terraform..." | tee -a $LOG_FILE
rm -rf .terraform .terraform.lock.hcl
terraform init -plugin-dir="$TEST_DIR/plugins" >> $LOG_FILE 2>&1
BUCKET_NAME="tf-s3-test-$(date +%s)"
# 4. Create
echo "Step 1: Creating S3 Bucket ($BUCKET_NAME)..." | tee -a $LOG_FILE
start_time=$(date +%s)
terraform apply -auto-approve \
-var="bucket_name=$BUCKET_NAME" \
-var="max_size=100" \
>> $LOG_FILE 2>&1
end_time=$(date +%s)
echo "Created in $((end_time - start_time))s" | tee -a $LOG_FILE
# 5. Modify (Update size) - SKIPPED because API forbids name reuse immediately
echo "Step 2: Modifying S3 Bucket... SKIPPED (API does not support updates)" | tee -a $LOG_FILE
# 6. Upload File using Python (No boto3/CLI dependency)
echo "Step 2.5: Uploading file to S3..." | tee -a $LOG_FILE
echo "This is a test file for Nubes S3." > test_file.txt
export AWS_ACCESS_KEY_ID="0GLQRD38H4I6RBDB0EWJ"
export AWS_SECRET_ACCESS_KEY="eTFibiHmBd96IApj9PYsboTR6OBoD7osxoarHykw"
export AWS_DEFAULT_REGION="msk-1"
export S3_ENDPOINT="https://s3.msk-1.ngcloud.ru"
python3 upload_mini.py "$BUCKET_NAME" "test_file.txt" "test_file.txt" >> $LOG_FILE 2>&1
if [ $? -eq 0 ]; then
echo "SUCCESS: File uploaded." | tee -a $LOG_FILE
else
echo "WARNING: File upload failed, but continuing..." | tee -a $LOG_FILE
fi
# 7. Destroy
echo "Step 3: Destroying S3 Bucket..." | tee -a $LOG_FILE
start_time=$(date +%s)
terraform destroy -auto-approve \
-var="bucket_name=$BUCKET_NAME" \
-var="max_size=200" \
>> $LOG_FILE 2>&1
end_time=$(date +%s)
echo "Destroyed in $((end_time - start_time))s" | tee -a $LOG_FILE
echo "=== Test Completed ===" | tee -a $LOG_FILE

View File

@ -0,0 +1,60 @@
terraform {
required_providers {
nubes = {
source = "terraform.local/nubes/nubes"
}
}
}
variable "api_token" {
type = string
}
variable "organization_id" {
type = string
}
variable "provider_vdc" {
type = string
}
variable "network_pool" {
type = string
}
variable "storage_profiles" {
type = string
}
variable "cpu_quota" {
type = number
default = 10
}
variable "ram_quota" {
type = number
default = 20
}
provider "nubes" {
api_token = var.api_token
}
resource "nubes_vdc" "test" {
display_name = "terraform-vdc-modify-test"
description = "Testing VDC modify operation"
organization_uid = var.organization_id
provider_vdc = var.provider_vdc
network_pool = var.network_pool
storage_profiles = var.storage_profiles
cpu_allocation_pct = 20
ram_allocation_pct = 20
cpu_quota = var.cpu_quota
ram_quota = var.ram_quota
deletion_protection = true
}
output "vdc_id" {
value = nubes_vdc.test.id
}

View File

@ -0,0 +1,41 @@
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"time"
)
// Token from terraform.tfvars (simplified)
const token = "eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJUdzZMa1FRS1ctZHlpb0hoYXlxOHRxa2dhb2RqQjM0bVAtSDBjUkpGWkRJIn0.eyJleHAiOjE3Njk4NDQxMTAsImlhdCI6MTc2OTgzNjkxMCwiYXV0aF90aW1lIjoxNzY5ODM2ODg5LCJqdGkiOiIzMGM2MjZlMC1kMDY2LTQzNTItODFiMC0yZGM4Yjk0ODM5NzIiLCJpc3MiOiJodHRwczovL2tleWNsb2FrLm51YmVzLnJ1L3JlYWxtcy9jbG91ZCIsImF1ZCI6ImFjY291bnQiLCJzdWIiOiJhNTVhNjFlZC1iMTU5LTQ3YjYtYmIxNi00N2Q4ZjIyZDQ4MGIiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJhY2NvdW50LWNvbnNvbGUiLCJub25jZSI6IjM2ODc0MDljLWU1MGQtNGVkMi1iY2JkLTU1ODA2MmNlY2ZkNCIsInNlc3Npb25fc3RhdGUiOiI1YTYxMTdkMS03ZTI2LTQ3YTQtOWUxYS0xZTFlZTViYTlmNzQiLCJhY3IiOiIwIiwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyJdfX0sInNjb3BlIjoib3BlbmlkIGVtYWlsIHByb2ZpbGUiLCJzaWQiOiI1YTYxMTdkMS03ZTI2LTQ3YTQtOWUxYS0xZTFlZTViYTlmNzQiLCJlbWFpbF92ZXJpZmllZCI6dHJ1ZSwibmFtZSI6ItCi0LDQt9C10YLQtNC40L3QvtCyINCd0LDQuNC70YwiLCJDbGllbnRJRCI6IldaMDEzMjUiLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJ0YXpldEBuYXJvZC5ydSIsImxvY2FsZSI6InJ1IiwiZ2l2ZW5fbmFtZSI6ItCi0LDQt9C10YLQtNC40L3QvtCyIiwiZmFtaWx5X25hbWUiOiLQndCw0LjQu9GMIiwiZW1haWwiOiJ0YXpldEBuYXJvZC5ydSJ9.gqIGJ5aH1nqBqpnvVOtZWXMt3pZp5m264xQ3ar2D1rUOh0NHSFtoIKSjJkgSlmFeFMjd-C261MQDg78YwGzYn2RH8hLJq-ht0l07-ZGr9bqYNyjcNTyeQ_bjqA5sGBKzTd1XfA4jsuMqZ6rIZAsy_NnVunHodivHSSKv7LdArZeyTeOIFVojattX96CDiBLnM45utOtS0NM3Ae8rowKJWj9l46N4c8n8u-zJ6rx5eP50LOYjz1sod7lwcgatTATDHDwH0moYabzPdeOvK-xpbSmibMhKUpaHLNnVyL6qTqn0zh_S81mXeqZmdXozlhrjnF3wCUdz0rXDJqmBnq50fA"
func main() {
url := "https://deck-api.ngcloud.ru/api/v1/index.cfm/instances"
payload := map[string]interface{}{
"serviceId": 1,
"displayName": "DebugReq-Test",
"descr": "Created via debug tool",
}
b, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", url, bytes.NewBuffer(b))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Connection", "close") // Try to force close
client := &http.Client{Timeout: 10 * time.Second}
fmt.Println("Sending request...")
resp, err := client.Do(req)
if err != nil {
fmt.Printf("Error: %v\n", err)
return
}
defer resp.Body.Close()
fmt.Printf("Status: %s\n", resp.Status)
body, _ := io.ReadAll(resp.Body)
fmt.Printf("Body: %s\n", string(body))
}

View File

@ -0,0 +1,209 @@
# Публикация документации (Nubes Terraform Provider)
## Назначение
Эта инструкция описывает полный цикл: генерация страниц ресурсов, сборка сайта и загрузка на https://terra.k8c.ru.
## Требования
- Docker
- `mc` (MinIO Client)
- Доступ к S3 (переменные `S3_ENDPOINT`, `S3_ACCESS_KEY`, `S3_SECRET_KEY`)
## 1) Генерация страниц ресурсов
Источники параметров: `universal_rebuild/resources_yaml/*.yaml`.
Генератор страниц (создаёт/обновляет файлы в `docs/30_registry/resources/` и индекс):
```bash
/home/naeel/terra/.venv/bin/python - <<'PY'
import glob
import os
import re
import yaml
resources_yaml_dir = "/home/naeel/terra/universal_rebuild/resources_yaml"
resources_gen_dir = "/home/naeel/terra/universal_rebuild/internal/resources_gen"
resources_docs_dir = "/home/naeel/terra/docs/30_registry/resources"
all_resources_path = os.path.join(resources_docs_dir, "all_resources.md")
index_path = os.path.join(resources_docs_dir, "index.md")
os.makedirs(resources_docs_dir, exist_ok=True)
def parse_computed_fields(go_path: str):
try:
with open(go_path, "r", encoding="utf-8") as f:
lines = f.readlines()
except FileNotFoundError:
return []
computed = []
i = 0
while i < len(lines):
line = lines[i]
m = re.search(r'"([a-zA-Z0-9_]+)"\s*:\s*schema\.[A-Za-z]+Attribute\{', line)
if not m:
i += 1
continue
name = m.group(1)
block_lines = [line]
i += 1
while i < len(lines) and "}," not in lines[i]:
block_lines.append(lines[i])
i += 1
if i < len(lines):
block_lines.append(lines[i])
block_text = "".join(block_lines)
if "Computed: true" in block_text:
computed.append(name)
i += 1
return sorted(set(computed))
computed_by_name = {}
for go_path in glob.glob(os.path.join(resources_gen_dir, "*_resource.go")):
name = None
with open(go_path, "r", encoding="utf-8") as f:
for line in f:
if line.startswith("// Service:"):
name = line.split(":", 1)[1].strip()
break
if not name:
continue
computed_by_name[name] = parse_computed_fields(go_path)
files = sorted(glob.glob(os.path.join(resources_yaml_dir, "service_*.yaml")))
resource_names = []
all_lines = []
all_lines.append("# Ресурсы провайдера (полный список)\n\n")
all_lines.append("Автогенерируемая сводка по ресурсам из `resources_yaml` и схем.\n\n")
for path in files:
with open(path, "r", encoding="utf-8") as f:
data = yaml.safe_load(f)
if not data:
continue
name = data.get("name", "unknown")
service_id = data.get("service_id", "unknown")
resource_names.append(name)
create = data.get("create", {}).get("params", []) or []
modify = data.get("modify", {}).get("params", []) or []
computed = computed_by_name.get(name, [])
doc_path = os.path.join(resources_docs_dir, f"{name}.md")
lines = []
lines.append(f"# Resource nubes_{name}\n\n")
lines.append(f"Service ID: `{service_id}`\n\n")
lines.append("## Output поля\n\n")
if computed:
for c in computed:
lines.append(f"- `{c}`\n")
else:
lines.append("Нет.\n")
lines.append("\n")
lines.append("## Create параметры\n\n")
if create:
lines.append("| Code | Type | Required | Default | ID |\n")
lines.append("|---|---|---|---|---|\n")
for p in create:
code = p.get("code", "")
ptype = p.get("type", "")
required = str(p.get("required", False)).lower()
default = p.get("default", "")
pid = p.get("id", "")
lines.append(f"| `{code}` | `{ptype}` | `{required}` | `{default}` | `{pid}` |\n")
else:
lines.append("Нет.\n")
lines.append("\n## Modify параметры\n\n")
if modify:
lines.append("| Code | Type | Required | Default | ID |\n")
lines.append("|---|---|---|---|---|\n")
for p in modify:
code = p.get("code", "")
ptype = p.get("type", "")
required = str(p.get("required", False)).lower()
default = p.get("default", "")
pid = p.get("id", "")
lines.append(f"| `{code}` | `{ptype}` | `{required}` | `{default}` | `{pid}` |\n")
else:
lines.append("Нет.\n")
with open(doc_path, "w", encoding="utf-8") as f:
f.write("".join(lines))
all_lines.append(f"## nubes_{name}\n")
all_lines.append(f"- Service ID: {service_id}\n")
all_lines.append("### Output поля\n")
if computed:
all_lines.append(", ".join(f"`{c}`" for c in computed) + "\n")
else:
all_lines.append("Нет.\n")
all_lines.append("\n### Create параметры\n")
if create:
all_lines.append("| Code | Type | Required | Default | ID |\n")
all_lines.append("|---|---|---|---|---|\n")
for p in create:
code = p.get("code", "")
ptype = p.get("type", "")
required = str(p.get("required", False)).lower()
default = p.get("default", "")
pid = p.get("id", "")
all_lines.append(f"| `{code}` | `{ptype}` | `{required}` | `{default}` | `{pid}` |\n")
else:
all_lines.append("Нет.\n")
all_lines.append("\n### Modify параметры\n")
if modify:
all_lines.append("| Code | Type | Required | Default | ID |\n")
all_lines.append("|---|---|---|---|---|\n")
for p in modify:
code = p.get("code", "")
ptype = p.get("type", "")
required = str(p.get("required", False)).lower()
default = p.get("default", "")
pid = p.get("id", "")
all_lines.append(f"| `{code}` | `{ptype}` | `{required}` | `{default}` | `{pid}` |\n")
else:
all_lines.append("Нет.\n")
all_lines.append("\n")
resource_names_sorted = sorted(set(resource_names))
index_lines = ["# Resources\n\n", "Список всех ресурсов провайдера.\n\n"]
for name in resource_names_sorted:
index_lines.append(f"- [{name}](./{name}.md)\n")
with open(index_path, "w", encoding="utf-8") as f:
f.write("".join(index_lines))
with open(all_resources_path, "w", encoding="utf-8") as f:
f.write("".join(all_lines))
print(f"Generated {len(resource_names_sorted)} resource pages.")
print(f"Wrote {index_path}")
print(f"Wrote {all_resources_path}")
PY
```
## 2) Навигация mkdocs
Проверьте, что `mkdocs.yml` содержит:
- `Resources` (index + all_resources)
- `Guides` (getting-started, terraform-basics)
## 3) Сборка сайта
```bash
docker run --rm -v ${PWD}:/docs squidfunk/mkdocs-material build
```
## 4) Публикация
```bash
set -a && . /home/naeel/terra/.env.s3 && set +a
/home/naeel/terra/scripts/publish-docs.sh site terra.k8c.ru nubes nubes 2.0.0
```
## Результат
Документация должна открываться по адресу:
`https://terra.k8c.ru/docs/nubes/nubes/2.0.0/`

View File

@ -0,0 +1,37 @@
import configparser
from pathlib import Path
import boto3
from botocore.config import Config
cred_path = Path('/home/naeel/terra/.tmp_aws_credentials')
config = configparser.RawConfigParser()
config.read(cred_path)
key = config.get('registry', 'aws_access_key_id')
secret = config.get('registry', 'aws_secret_access_key')
cfg = Config(signature_version='s3v4', s3={'addressing_style': 'path', 'payload_signing_enabled': False})
s3 = boto3.client(
's3',
region_name='us-east-1',
endpoint_url='https://s3.msk-1.ngcloud.ru',
aws_access_key_id=key,
aws_secret_access_key=secret,
config=cfg,
)
bucket = 'terraform-registry'
prefix = 'terrareg.kube5s.ru/nubes/nubes/2.0.0/'
files = [
'terraform-provider-nubes_2.0.0_linux_amd64.zip',
'terraform-provider-nubes_2.0.0_SHA256SUMS',
'terraform-provider-nubes_2.0.0_SHA256SUMS.sig',
]
base = Path('/home/naeel/terra/nubes_provider_gen')
for name in files:
path = base / name
key_name = prefix + name
with open(path, 'rb') as f:
s3.put_object(Bucket=bucket, Key=key_name, Body=f)
print('uploaded', key_name)