UNPKG

@mesh-tech/mesh-cli

Version:

CLI for Mesh platform development utilities

214 lines (213 loc) 9.13 kB
import { logInfo, logSuccess } from "../../utils/log.js"; import { MeshCliError } from "../../utils/errors.js"; export const LOCAL_TENANT = "local"; export const LOCAL_ENV = "dev"; export const LOCAL_AWS_ENDPOINT = "http://localhost:4566"; export const LOCAL_AWS_REGION = "us-east-2"; export const ARTIFACTS_BUCKET = "mesh-local-artifacts"; export const DATA_BUCKET = "mesh-local-data"; export const APP_TENANTS_PARAM = `/mesh-platform/${LOCAL_TENANT}/${LOCAL_ENV}/app-tenants`; const DATA_BUCKET_SEEDS = [ { key: "samples/pipes/fragment-0001.json", body: JSON.stringify({ id: "sample-0001", kind: "pipes-fragment", account: "0001", asOf: "2026-01-01", lines: [{ amount: "125.00", currency: "USD", description: "Sample credit" }], }, null, 2), }, ]; export const LOCAL_AWS_CONFIG = { endpoint: LOCAL_AWS_ENDPOINT, region: LOCAL_AWS_REGION, credentials: { accessKeyId: "test", secretAccessKey: "test" }, }; const AWS_CONFIG = LOCAL_AWS_CONFIG; export function localAwsEnv() { return { AWS_ENDPOINT_URL: LOCAL_AWS_ENDPOINT, AWS_REGION: LOCAL_AWS_REGION, AWS_ACCESS_KEY_ID: "test", AWS_SECRET_ACCESS_KEY: "test", }; } export function buildAppTenantsSeed() { const tenantExport = { albGroupName: LOCAL_TENANT, certificateArn: "", baseDomain: "localhost", deployerRoleArn: "arn:aws:iam::000000000000:role/local-dev-deployer", artifactsBucket: ARTIFACTS_BUCKET, spicedb: { endpoint: "localhost:50051", presharedKeySecret: "local-dev-key", }, zitadel: { orgId: "local", domain: "localhost:8080", issuer: "http://localhost:8080", }, }; return { hubTenant: LOCAL_TENANT, primaryTenant: tenantExport, sharedTenants: {}, }; } export const FABRIC_CHECK_PATH = `/mesh-platform/${LOCAL_TENANT}/${LOCAL_ENV}/.fabric-check`; export const TEMPORAL_NAMESPACE = `${LOCAL_TENANT}-${LOCAL_ENV}`; export const TEMPORAL_ADDRESS = "localhost:7233"; export async function seedTemporalNamespace() { await ensureTemporalNamespace(TEMPORAL_NAMESPACE, `Mesh local tenant '${LOCAL_TENANT}' env '${LOCAL_ENV}' (seeded by mesh start)`); } export async function ensureTemporalNamespace(namespace, description) { const { Connection } = await import("@temporalio/client"); const connection = await Connection.connect({ address: TEMPORAL_ADDRESS }); try { await connection.workflowService.registerNamespace({ namespace, description: description ?? `Mesh local namespace '${namespace}'`, workflowExecutionRetentionPeriod: { seconds: (3 * 24 * 60 * 60) }, }); logInfo(`Registered Temporal namespace '${namespace}' — waiting for it to become active…`); const deadline = Date.now() + 20_000; while (Date.now() < deadline) { try { await connection.workflowService.describeTaskQueue({ namespace, taskQueue: { name: "namespace-propagation-probe" }, taskQueueType: 1, }); break; } catch { await new Promise((resolve) => setTimeout(resolve, 1_000)); } } logSuccess(`Temporal namespace '${namespace}' is active`); } catch (err) { if (err?.name === "NamespaceAlreadyExistsError" || /already exists/i.test(err?.message ?? "")) { logInfo(`Temporal namespace '${namespace}' already exists`); } else { throw err; } } finally { await connection.close(); } } export function buildFabricCheckPayload(targetBytes = 8_000) { const base = { probe: "advanced-tier-round-trip", tenant: LOCAL_TENANT, env: LOCAL_ENV, pad: "" }; const overhead = JSON.stringify(base).length; return JSON.stringify({ ...base, pad: "x".repeat(Math.max(0, targetBytes - overhead)) }); } export async function verifyFabric() { const { SSMClient, PutParameterCommand, GetParameterCommand, GetParametersByPathCommand, DeleteParametersCommand } = await import("@aws-sdk/client-ssm"); const ssm = new SSMClient(AWS_CONFIG); const payload = buildFabricCheckPayload(); const mainParam = FABRIC_CHECK_PATH; const childParams = [`${FABRIC_CHECK_PATH}/vpc`, `${FABRIC_CHECK_PATH}/eks`]; try { await ssm.send(new PutParameterCommand({ Name: mainParam, Type: "String", Tier: "Advanced", Overwrite: true, Value: payload, })); for (const name of childParams) { await ssm.send(new PutParameterCommand({ Name: name, Type: "String", Overwrite: true, Value: `{"probe":"${name}"}` })); } const roundTrip = await ssm.send(new GetParameterCommand({ Name: mainParam })); if (roundTrip.Parameter?.Value !== payload) { throw new MeshCliError(`Local AWS fabric failed the ${payload.length}-byte Advanced-tier SSM round-trip (got ${roundTrip.Parameter?.Value?.length ?? 0} bytes back).`, { remediation: { docs: "libs/mesh-cli/stack/docker-compose.yml — swap the ministack image for motoserver/moto (design §2.5 fallback)", }, }); } const byPath = await ssm.send(new GetParametersByPathCommand({ Path: FABRIC_CHECK_PATH, Recursive: true })); const returned = new Set((byPath.Parameters ?? []).map((p) => p.Name)); const missing = childParams.filter((name) => !returned.has(name)); if (missing.length > 0) { throw new MeshCliError(`Local AWS fabric's GetParametersByPath missed child parameter(s): ${missing.join(", ")} (splitKeys exports would not resolve).`, { remediation: { docs: "libs/mesh-cli/stack/docker-compose.yml — swap the ministack image for motoserver/moto (design §2.5 fallback)", }, }); } logSuccess(`Fabric check passed: ${payload.length}-byte Advanced-tier round-trip + GetParametersByPath`); } finally { await ssm .send(new DeleteParametersCommand({ Names: [mainParam, ...childParams] })) .catch(() => { }); } } export async function registerTenantEnv(tenant, opts = {}) { const { SSMClient, PutParameterCommand } = await import("@aws-sdk/client-ssm"); const ssm = new SSMClient(AWS_CONFIG); await ssm.send(new PutParameterCommand({ Name: `/mesh-platform/${tenant}`, Type: "String", Overwrite: true, Value: JSON.stringify({ name: tenant, tier: opts.tier ?? "shared", subdomain: tenant }), Description: `Tenant registration (seeded by mesh CLI, local platform)`, })); await ssm.send(new PutParameterCommand({ Name: `/mesh-platform/${tenant}/${LOCAL_ENV}`, Type: "String", Overwrite: true, Value: JSON.stringify({ env: LOCAL_ENV, region: LOCAL_AWS_REGION, baseDomain: "localhost", services: { temporal: true, zitadel: true, monitoring: true }, }), Description: `Environment registration (seeded by mesh CLI, local platform)`, })); } export async function seedLocalPlatform() { const { SSMClient, PutParameterCommand } = await import("@aws-sdk/client-ssm"); const ssm = new SSMClient(AWS_CONFIG); await ssm.send(new PutParameterCommand({ Name: APP_TENANTS_PARAM, Type: "String", Overwrite: true, Value: JSON.stringify(buildAppTenantsSeed()), Description: `TenantEnvironments exports for ${LOCAL_TENANT}-${LOCAL_ENV} (seeded by mesh start)`, })); await registerTenantEnv(LOCAL_TENANT, { tier: "hub" }); logSuccess(`Registered tenant '${LOCAL_TENANT}' env '${LOCAL_ENV}' → ${APP_TENANTS_PARAM}`); const { S3Client, CreateBucketCommand, PutObjectCommand } = await import("@aws-sdk/client-s3"); const s3 = new S3Client({ ...AWS_CONFIG, forcePathStyle: true }); for (const bucket of [ARTIFACTS_BUCKET, DATA_BUCKET]) { try { await s3.send(new CreateBucketCommand({ Bucket: bucket })); logSuccess(`Created bucket s3://${bucket}`); } catch (err) { if (err?.name === "BucketAlreadyOwnedByYou" || err?.name === "BucketAlreadyExists") { logInfo(`Bucket s3://${bucket} already exists`); } else { throw err; } } } for (const seed of DATA_BUCKET_SEEDS) { await s3.send(new PutObjectCommand({ Bucket: DATA_BUCKET, Key: seed.key, Body: seed.body, ContentType: "application/json", })); } logSuccess(`Seeded s3://${DATA_BUCKET} with ${DATA_BUCKET_SEEDS.length} sample object(s)`); await seedTemporalNamespace(); return { parameter: APP_TENANTS_PARAM, bucket: ARTIFACTS_BUCKET }; }