Přeskočit na obsah

Cloud Connector

Cloud Connector zřizuje a spravuje cloudovou infrastrukturu napříč více poskytovateli. Provisioning probíhá přes přímá volání provider SDK/REST API, ne přes Infrastructure-as-Code — dřívější návrh na bázi CDKTF (Terraform CDK) byl kompletně odstraněn (ADR-001/M10, dependency cleanup SEC-231). Závislost cdktf/constructs už v kódové základně vůbec není.

  • Port: 4321 (pouze API server — worker proces nemá žádný HTTP povrch)
  • Tech: Express 4, TypeScript, TypeORM + PostgreSQL, amqplib (RabbitMQ), Winston, Axios
  • Databáze: PostgreSQL (cloud-connector-db, dev port 5434)
  • Messaging: RabbitMQ, exchange cloud.events (topic) — jediný kanonický exchange dle ADR-001
  • Provisioning: Vždy asynchronní — nikdy synchronní volání provider API přímo z HTTP request cesty

Adaptéry žijí v src/adapters/*.adapter.ts a implementují rozhraní ICloudProvider.

ProviderStavSoubor(y) adaptéru
AWSPlně implementovánoaws.adapter.ts, aws-iam.adapter.ts, aws-network.adapter.ts
Google CloudPlně implementovánogcp.adapter.ts, gcp-iam.adapter.ts
AzurePlně implementovánoazure.adapter.ts, azure-iam.adapter.ts, azure-network.adapter.ts (~1256 řádků jen v hlavním adaptéru)
HetznerPlně implementovánohetzner.adapter.ts
ScalewayPlně implementovánoscaleway.adapter.ts
OVHcloudPlně implementovánoovhcloud.adapter.ts
UpCloudPlně implementovánoupcloud.adapter.ts
DigitalOceanZatím neimplementováno — v src/adapters/ neexistuje žádný soubor adaptéru

Azure byl ve starších poznámkách historicky veden jako „rozpracováno (BYOC)” — to je zastaralá informace, sada Azure adaptérů je kompletní a na úrovni AWS/GCP. DigitalOcean naproti tomu zatím nemá žádný adaptér, přestože se objevuje jako podporovaná hodnota providera ve schématu jobů (viz níže) — berte ho jako rezervovaný/plánovaný, ne funkční.

Kanonická cesta (ADR-001) nemá žádný Terraform/CDKTF krok ani žádnou HTTP bridge službu:

Strapi: cloud-instance lifecycle hook (create/update/destroy)
webhook-publisher (zabalí event do { event, data: { jobId, type, cloudInstanceId, credential_id?, payload: { provider, region, config } } })
RabbitMQ: exchange cloud.events (topic)
routing key: cloud-instance.provision | cloud-instance.update | cloud-instance.destroy
cloud-connector-worker (dist/worker.js, samostatná Docker Compose služba — konzumuje přímo, bez HTTP mezikroku)
worker.ts rozbalí obálku webhook-publisheru, dohledá BYOC credential
Provider adaptér (přímé SDK/REST volání — AWS SDK, @google-cloud, Azure SDK, Hetzner REST, …)
POST /api/cloud-instances/:documentId/status-callback (Strapi)
hlavička: X-Service-Secret: CLOUD_CALLBACK_SECRET
Strapi aktualizuje stav CloudInstance → frontend vidí nový stav

Existuje i admin/sync cesta: POST /v1/provision na API serveru přijme provisioning request přímo a publikuje ho na stejný exchange cloud.events, místo aby dělal cokoliv inline.

Adresář: src/adapters/

src/adapters/
├── aws.adapter.ts
├── aws-iam.adapter.ts
├── aws-network.adapter.ts
├── azure.adapter.ts
├── azure-iam.adapter.ts
├── azure-network.adapter.ts
├── base-resource.adapter.ts
├── gcp.adapter.ts
├── gcp-iam.adapter.ts
├── hetzner.adapter.ts
├── multi-region.adapter.ts
├── ovhcloud.adapter.ts
├── scaleway.adapter.ts
├── tier-aware-adapter-factory.ts
└── upcloud.adapter.ts

Každý provider adaptér:

  • Implementuje rozhraní ICloudProvider
  • Volá SDK/REST API poskytovatele přímo (žádné generované IaC, žádný terraform apply)
  • Vrací detaily instance (IP, SSH klíč/credentials, provider-nativní ID instance)

Schéma vlastní TypeORM migrace v src/database/migrations/ (v repozitáři aktuálně neexistuje žádný adresář src/database/entities/ — stav jobu/instance je modelován v src/models/job.model.ts a aplikován přes migraci, např. CreateJobsTable). Základní tabulka jobs sleduje provisioning joby:

jobs
├── id (uuid)
├── type (enum: provision | update | destroy | heartbeat)
├── provider (enum: aws | gcp | azure | digitalocean | scaleway | ovhcloud | hetzner | upcloud)
├── region
├── config (jsonb)
├── status (enum: PENDING | RUNNING | SUCCESS | FAILED)
├── createdAt / updatedAt
├── startedAt / completedAt
├── error (text)
├── result (jsonb)
├── retryCount / maxRetries
├── instanceId
└── metadata (jsonb)

Přesné názvy sloupců/tabulek si ověřte přímo v src/database/migrations/ a src/models/ před tím, než se na ně v kódu spolehnete — výše je popsána aktuální baseline migrace, ne ručně udržovaný entity soubor.

Neupravujte aplikované migrace ručně — pro změny schématu přidejte novou migraci.

.env proměnné (výběr):

Terminál
# API server
PORT=4321
DATABASE_HOST=cloud-connector-db
RABBITMQ_URL=amqp://admin:admin@sencai-mq:5672
STRAPI_BASE_URL=http://backend:1337
STRAPI_API_TOKEN=<token>
CLOUD_CALLBACK_SECRET=<sdílený secret pro status-callback>
DEPLOYMENT_STRATEGY=single-region
# AWS
AWS_REGION=eu-central-1
AWS_ACCESS_KEY_ID=<key>
AWS_SECRET_ACCESS_KEY=<secret>
# Google Cloud
GCP_PROJECT_ID=<project>
GOOGLE_APPLICATION_CREDENTIALS=/data/gcp-key.json
# Azure
AZURE_SUBSCRIPTION_ID=<id>
AZURE_TENANT_ID=<id>
AZURE_CLIENT_ID=<id>
AZURE_CLIENT_SECRET=<secret>
# Hetzner
HETZNER_TOKEN=<token>
# Scaleway / OVHcloud / UpCloud
SCALEWAY_ACCESS_KEY=<key>
SCALEWAY_SECRET_KEY=<secret>
OVH_APPLICATION_KEY=<key>
OVH_APPLICATION_SECRET=<secret>
UPCLOUD_USERNAME=<username>
UPCLOUD_PASSWORD=<password>

Většina produkčních credentials se dodává per-tenant jako BYOC (bring-your-own-cloud) credential, ne jako globální hodnoty v .env — pořadí resolvování najdete v cloud-connector/CLAUDE.md v sekci o BYOC credentials.

API server (src/index.ts) vystavuje výrazně širší sadu routes, než je zdokumentováno zde (mimo jiné DNS, CDN, WAF, monitoring, endpointy pro přepínání deploymentu); níže jsou jen ty, které se týkají provisioning/Strapi integrace:

Terminál
GET /health # Health check
GET /v1/providers/health # Stav zdraví jednotlivých providerů
POST /v1/provision # Přijme provisioning job, publikuje na cloud.events
GET /v1/jobs/:jobId # Dohledání stavu jobu
GET /v1/jobs/stats # Agregované statistiky jobů
GET /v1/strapi/status # Kompletní stav pro Strapi dashboard
GET /v1/strapi/health # Health check přizpůsobený pro Strapi
POST /v1/strapi/webhook # Příchozí webhook ze Strapi
POST /v1/strapi/force-update # Vynucená aktualizace stavu
POST /v1/strapi/cleanup # Úklid zastaralých jobů

Kompletní seznam routes si ověřte přímo v src/index.ts — existují další endpointy pro DNS zóny, CDN distribuce, WAF ACL, sledování DDoS zdrojů a blue/green přepínání deploymentu, které jsou mimo rozsah této stránky.

Cloud Connector konzumuje z exchange cloud.events (topic, jediný kanonický exchange dle ADR-001):

Routing keyQueueAkce
cloud-instance.provisioncloud.connector.provisionZřízení nové instance
cloud-instance.updatecloud.connector.updateAktualizace existující instance
cloud-instance.destroycloud.connector.destroyZrušení instance
inventory.scancloud.connector.inventory-scanRead-only discovery zdrojů (adapter.discoverResources()), dávkově posláno zpět na /api/cloud-assets/scan-callback

Neúspěšné joby se neztrácí, ale dead-letterují: exchange cloud.connector.dead (topic) + queue cloud.connector.dead.q, s TTL 14 dní.

Tvar payloadu jobu (rozbalený z obálky webhook-publisheru přes worker.ts):

{
"jobId": "uuid",
"type": "provision",
"cloudInstanceId": "documentId",
"credential_id": "optional-byoc-credential-id",
"payload": {
"provider": "aws",
"region": "eu-central-1",
"config": { "instanceType": "medium", "os": "ubuntu" }
}
}
  1. Vytvořte adaptér: src/adapters/<provider>.adapter.ts
export class MyProviderAdapter implements ICloudProvider {
async provision(config: InstanceConfig): Promise<InstanceDetails> {
// Přímé SDK/REST volání poskytovateli — žádné generované IaC
// Vrátí { ip, sshKey, credentials, ... }
}
async update(instanceId: string, config: Partial<InstanceConfig>): Promise<void> { /* ... */ }
async destroy(instanceId: string): Promise<void> { /* ... */ }
}
  1. Zaregistrujte adaptér v provider factory (src/adapters/tier-aware-adapter-factory.ts nebo v příslušném config modulu).
  2. Přidejte ENV proměnné do .env.example a orchestration/.env.example.
  3. Pokud tam ještě není, přidejte hodnotu providera do jobs_provider_enum novou migrací.
  4. Otestujte end-to-end: vytvořte instanci z frontendu a ověřte, že job projde PENDING → RUNNING → SUCCESS a že status-callback dorazí do Strapi.

Q: Provisioning zaseklý ve stavu “provisioning”/RUNNING

A: Zkontrolujte logy workeru (docker logs cloud-connector-worker, ne kontejner API serveru). Hledejte chyby provider API, timeouty, nebo dead-letterovaný job (cloud.connector.dead.q).

Q: Instance vytvořena, ale stav v Sencai je stále “error”

A: Status-callback do Strapi mohl selhat (zkontrolujte, že X-Service-Secret/CLOUD_CALLBACK_SECRET sedí na obou stranách), nebo job skončil v dead-letter po vyčerpání pokusů o opakování. Před ruční úpravou stavu instance zkontrolujte logy Strapi a dead-letter queue.

Q: Nelze se připojit k instanci přes SSH

A: Ověřte:

  • Veřejná IP je správná
  • SSH port 22 je otevřený v security group/firewallu poskytovatele
  • SSH klíč odpovídá instanci
  • OS instance hlásí připravenost v konzoli poskytovatele

Q: Credentials poskytovatele jsou odmítnuty

A: Ověřte, že se pro daný job správně dohledaly BYOC credentials (per-tenant credential, ne jen globální fallback z .env), pak je otestujte ručně proti CLI/SDK poskytovatele, např.:

Terminál
# AWS
aws ec2 describe-instances --region eu-central-1
# Google Cloud
gcloud auth list
gcloud compute instances list
# Azure
az vm list --output table