Cloud Connector
Cloud Connector
Section titled “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í.
Klíčové údaje
Section titled “Klíčové údaje”- 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
Podporovaní poskytovatelé
Section titled “Podporovaní poskytovatelé”Adaptéry žijí v src/adapters/*.adapter.ts a implementují rozhraní ICloudProvider.
| Provider | Stav | Soubor(y) adaptéru |
|---|---|---|
| AWS | Plně implementováno | aws.adapter.ts, aws-iam.adapter.ts, aws-network.adapter.ts |
| Google Cloud | Plně implementováno | gcp.adapter.ts, gcp-iam.adapter.ts |
| Azure | Plně implementováno | azure.adapter.ts, azure-iam.adapter.ts, azure-network.adapter.ts (~1256 řádků jen v hlavním adaptéru) |
| Hetzner | Plně implementováno | hetzner.adapter.ts |
| Scaleway | Plně implementováno | scaleway.adapter.ts |
| OVHcloud | Plně implementováno | ovhcloud.adapter.ts |
| UpCloud | Plně implementováno | upcloud.adapter.ts |
| DigitalOcean | Zatí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í.
Provisioning flow
Section titled “Provisioning flow”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ý stavExistuje 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.
Architektura
Section titled “Architektura”Provider adaptéry
Section titled “Provider adaptéry”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.tsKaž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)
Databáze (TypeORM + PostgreSQL)
Section titled “Databáze (TypeORM + PostgreSQL)”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.
Konfigurace
Section titled “Konfigurace”.env proměnné (výběr):
# API serverPORT=4321DATABASE_HOST=cloud-connector-dbRABBITMQ_URL=amqp://admin:admin@sencai-mq:5672STRAPI_BASE_URL=http://backend:1337STRAPI_API_TOKEN=<token>CLOUD_CALLBACK_SECRET=<sdílený secret pro status-callback>DEPLOYMENT_STRATEGY=single-region
# AWSAWS_REGION=eu-central-1AWS_ACCESS_KEY_ID=<key>AWS_SECRET_ACCESS_KEY=<secret>
# Google CloudGCP_PROJECT_ID=<project>GOOGLE_APPLICATION_CREDENTIALS=/data/gcp-key.json
# AzureAZURE_SUBSCRIPTION_ID=<id>AZURE_TENANT_ID=<id>AZURE_CLIENT_ID=<id>AZURE_CLIENT_SECRET=<secret>
# HetznerHETZNER_TOKEN=<token>
# Scaleway / OVHcloud / UpCloudSCALEWAY_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.
Endpointy
Section titled “Endpointy”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:
GET /health # Health checkGET /v1/providers/health # Stav zdraví jednotlivých providerůPOST /v1/provision # Přijme provisioning job, publikuje na cloud.eventsGET /v1/jobs/:jobId # Dohledání stavu jobuGET /v1/jobs/stats # Agregované statistiky jobů
GET /v1/strapi/status # Kompletní stav pro Strapi dashboardGET /v1/strapi/health # Health check přizpůsobený pro StrapiPOST /v1/strapi/webhook # Příchozí webhook ze StrapiPOST /v1/strapi/force-update # Vynucená aktualizace stavuPOST /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.
RabbitMQ eventy
Section titled “RabbitMQ eventy”Cloud Connector konzumuje z exchange cloud.events (topic, jediný kanonický exchange dle ADR-001):
| Routing key | Queue | Akce |
|---|---|---|
cloud-instance.provision | cloud.connector.provision | Zřízení nové instance |
cloud-instance.update | cloud.connector.update | Aktualizace existující instance |
cloud-instance.destroy | cloud.connector.destroy | Zrušení instance |
inventory.scan | cloud.connector.inventory-scan | Read-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" } }}Přidání nového poskytovatele
Section titled “Přidání nového poskytovatele”- 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> { /* ... */ }}- Zaregistrujte adaptér v provider factory (
src/adapters/tier-aware-adapter-factory.tsnebo v příslušném config modulu). - Přidejte ENV proměnné do
.env.exampleaorchestration/.env.example. - Pokud tam ještě není, přidejte hodnotu providera do
jobs_provider_enumnovou migrací. - Otestujte end-to-end: vytvořte instanci z frontendu a ověřte, že job projde
PENDING → RUNNING → SUCCESSa že status-callback dorazí do Strapi.
Troubleshooting
Section titled “Troubleshooting”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ř.:
# AWSaws ec2 describe-instances --region eu-central-1
# Google Cloudgcloud auth listgcloud compute instances list
# Azureaz vm list --output table