Files
floci-ui/packages/api/src/routes/clouds.ts
T
Zakaria 26b635db0b
CI / checks (pull_request) Successful in 28m26s
feat(api): add EFS file systems to the cloud explorer
Create file systems (with a mount target in each default subnet unless asked
not to), add and remove mount targets, create and delete access points, and
attach a file system to a Lambda function through an access point. A function
not yet in a VPC is put in the file system's mount target subnets with the
default security group, as Lambda requires. TheCloud's mounts are real, so two
functions on one access point share files.

Deleting a file system removes its access points and mount targets first, and
is refused while a function still mounts it.
2026-10-25 17:33:19 -04:00

1965 lines
100 KiB
TypeScript

import {Hono} from 'hono'
import type {Context} from 'hono'
import type {
AlarmStateValue,
AutoScalingCapacityInput,
AccessPointInput,
FileSystemAttachmentInput,
CertificateRevocationReason,
CloudProvider,
CloudServiceType,
CreateDatabaseSnapshotInput,
KmsEncryptionAlgorithm,
S3BucketWebsiteConfiguration,
SqlConnectionInput,
} from '../cloud-spi/types'
import {clampLimit, type PageQuery} from '../cloud-spi/childCollections'
import {CloudError, toHttpError, ValidationError} from '../cloud-spi/errors'
import {isServiceType} from '../cloud-spi/serviceCatalog'
import {mapAwsSdkError} from '../adapter-aws/awsErrors'
import {serviceForAccount} from '../cloudProxy'
import {CloudProxyService} from '../service/CloudProxyService'
import {runtimeDiagnostics} from '../service/runtimeDiagnostics'
import {deployLightsailNodeApplication, deployLightsailStaticFiles} from '../services/lightsailDeployment'
// Header (and query-param fallback for direct links such as object downloads)
// used by the frontend to scope every request to an AWS account.
export const ACCOUNT_HEADER = 'x-floci-account-id'
interface KmsEncryptRequest {
plaintextBase64?: unknown
encryptionAlgorithm?: unknown
encryptionContext?: unknown
}
interface KmsDecryptRequest {
ciphertextBlobBase64?: unknown
encryptionAlgorithm?: unknown
encryptionContext?: unknown
}
const KMS_ENCRYPTION_ALGORITHMS = new Set<KmsEncryptionAlgorithm>([
'SYMMETRIC_DEFAULT',
'RSAES_OAEP_SHA_1',
'RSAES_OAEP_SHA_256',
])
const MAX_PLAINTEXT_BASE64_LENGTH = 5_464
const MAX_CIPHERTEXT_BASE64_LENGTH = 8_192
export function createCloudRoutes(injectedService?: CloudProxyService) {
const app = new Hono()
// Resolve the account-scoped service per request. An explicitly injected
// service (used by tests) always wins and ignores the account header.
const svc = (c: Context): CloudProxyService =>
injectedService ?? serviceForAccount(c.req.header(ACCOUNT_HEADER) ?? c.req.query('account'))
app.get('/', (c) => c.json(svc(c).clouds()))
app.get('/:cloud/services', (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return c.json(svc(c).services(cloud))
})
app.get('/:cloud/status', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
// Per-service detail is opt-in; the connection indicator polls this often.
const includeServices = c.req.query('services') === 'all'
return c.json(await svc(c).status(cloud, {includeServices}))
})
app.get('/:cloud/diagnostics', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return c.json(await runtimeDiagnostics(cloud, svc(c)))
})
app.get('/:cloud/services/:service/status', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
const serviceType = c.req.param('service') as CloudServiceType
if (!isCloudProvider(cloud) || !isServiceType(serviceType)) return c.json({error: 'Unknown cloud or service'}, 404)
return c.json(await svc(c).serviceStatus(cloud, serviceType))
})
app.get('/:cloud/services/:service/schema', (c) => {
const cloud = c.req.param('cloud') as CloudProvider
const serviceType = c.req.param('service') as CloudServiceType
if (!isCloudProvider(cloud) || !isServiceType(serviceType)) return c.json({error: 'Unknown cloud or service'}, 404)
const schema = svc(c).schema(cloud, serviceType)
if (!schema) {
return c.json({error: 'Schema not available'}, 404)
}
return c.json(schema)
})
app.get('/:cloud/services/:service/dependencies', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
const serviceType = c.req.param('service') as CloudServiceType
if (!isCloudProvider(cloud) || !isServiceType(serviceType)) return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => c.json(await svc(c).dependencies(cloud, serviceType)))
})
app.get('/:cloud/services/database/snapshots', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => {
const snapshots = await svc(c).listDatabaseSnapshots(cloud, c.req.query('instanceIdentifier'))
return c.json(snapshots)
})
})
app.post('/:cloud/services/database/snapshots', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => {
const input = await c.req.json<CreateDatabaseSnapshotInput>()
const snapshot = await svc(c).createDatabaseSnapshot(cloud, input)
return c.json(snapshot, 201)
})
})
app.get('/:cloud/services/database/orderable-classes', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => {
const classes = await svc(c).listDatabaseOrderableInstanceClasses(cloud, c.req.query('engine'))
return c.json(classes)
})
})
app.get('/:cloud/services/:service/resources', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
const serviceType = c.req.param('service') as CloudServiceType
if (!isCloudProvider(cloud) || !isServiceType(serviceType)) return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
const resources = await svc(c).listResources(cloud, serviceType, {
search: c.req.query('search'),
state: c.req.query('state'),
})
return c.json(resources)
})
})
app.post('/aws/services/lightsail/resources/:id/static-files', async (c) => {
return withRuntime(c, async () => {
const form = await c.req.formData()
const files = form.getAll('files').filter((entry): entry is File => entry instanceof File)
if (files.length === 0 || files.length !== form.getAll('files').length) {
throw new ValidationError('At least one file is required.')
}
const upload = new FormData()
for (const file of files) {
upload.append('files', file, file.name)
}
return c.json(await deployLightsailStaticFiles(c.req.param('id'), upload))
})
})
app.post('/aws/services/lightsail/resources/:id/node-application', async (c) => {
return withRuntime(c, async () => {
const form = await c.req.formData()
const files = form.getAll('files').filter((entry): entry is File => entry instanceof File)
if (files.length === 0 || files.length !== form.getAll('files').length) {
throw new ValidationError('At least one file or zip archive is required.')
}
const portValue = form.get('port')
const port = typeof portValue === 'string' && /^\d+$/.test(portValue) ? Number(portValue) : 3000
if (port < 1 || port > 65535) throw new ValidationError('Port must be between 1 and 65535.')
const upload = new FormData()
for (const file of files) upload.append('files', file, file.name)
const startCommand = form.get('startCommand')
if (typeof startCommand === 'string' && startCommand.trim()) upload.append('startCommand', startCommand.trim())
upload.append('port', String(port))
return c.json(await deployLightsailNodeApplication(c.req.param('id'), upload))
})
})
app.get('/:cloud/services/nosql/resources/:id/containers', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => {
const containers = await svc(c).listCosmosContainers(cloud, c.req.param('id'))
return c.json(containers)
})
})
app.post('/:cloud/services/nosql/resources/:id/containers', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => {
const values = await c.req.json<Record<string, unknown>>()
const container = await svc(c).createCosmosContainer(cloud, c.req.param('id'), {values})
return c.json(container, 201)
})
})
app.delete('/:cloud/services/nosql/resources/:id/containers/:containerId', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => {
await svc(c).deleteCosmosContainer(cloud, c.req.param('id'), c.req.param('containerId'))
return c.json({ok: true})
})
})
app.get('/:cloud/services/nosql/resources/:id/containers/:containerId/items', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => {
const items = await svc(c).listCosmosItems(cloud, c.req.param('id'), c.req.param('containerId'))
return c.json(items)
})
})
app.post('/:cloud/services/nosql/resources/:id/containers/:containerId/items', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => {
const document = await c.req.json<Record<string, unknown>>()
const item = await svc(c).upsertCosmosItem(cloud, c.req.param('id'), c.req.param('containerId'), document)
return c.json(item, 201)
})
})
app.delete('/:cloud/services/nosql/resources/:id/containers/:containerId/items/:itemId', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => {
await svc(c).deleteCosmosItem(cloud, c.req.param('id'), c.req.param('containerId'), c.req.param('itemId'), c.req.query('partitionKey') ?? null)
return c.json({ok: true})
})
})
app.post('/:cloud/services/nosql/resources/:id/containers/:containerId/query', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => {
const body = await c.req.json<{query?: string}>()
const result = await svc(c).queryCosmosItems(cloud, c.req.param('id'), c.req.param('containerId'), body.query ?? '')
return c.json(result)
})
})
app.post('/:cloud/services/database/resources/:id/sql/databases', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => {
const connection = await c.req.json<SqlConnectionInput>()
const databases = await svc(c).listSqlDatabases(cloud, c.req.param('id'), connection)
return c.json(databases)
})
})
app.post('/:cloud/services/database/resources/:id/sql/tables', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => {
const connection = await c.req.json<SqlConnectionInput>()
const tables = await svc(c).listSqlTables(cloud, c.req.param('id'), connection)
return c.json(tables)
})
})
app.post('/:cloud/services/database/resources/:id/sql/query', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => {
const body = await c.req.json<SqlConnectionInput & {query?: string}>()
const {query = '', ...connection} = body
const result = await svc(c).querySql(cloud, c.req.param('id'), connection, query)
return c.json(result)
})
})
app.post('/:cloud/services/logs/resources/:id/query', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => {
const body = await c.req.json<{queryString?: unknown; startTime?: unknown; endTime?: unknown; limit?: unknown}>()
if (typeof body.queryString !== 'string') throw new ValidationError('queryString must be a string.')
if (typeof body.startTime !== 'number') throw new ValidationError('startTime must be a number (epoch seconds).')
if (typeof body.endTime !== 'number') throw new ValidationError('endTime must be a number (epoch seconds).')
if (body.limit !== undefined && typeof body.limit !== 'number') throw new ValidationError('limit must be a number.')
const result = await svc(c).queryLogs(cloud, c.req.param('id'), {
queryString: body.queryString,
startTime: body.startTime,
endTime: body.endTime,
limit: body.limit,
})
return c.json(result)
})
})
app.post('/:cloud/services/logs/resources/:id/filter', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => {
const body = await c.req.json<{filterPattern?: unknown; logStreamNames?: unknown; startTime?: unknown; endTime?: unknown}>()
if (body.filterPattern !== undefined && typeof body.filterPattern !== 'string') throw new ValidationError('filterPattern must be a string.')
if (body.logStreamNames !== undefined && (!Array.isArray(body.logStreamNames) || body.logStreamNames.some((name) => typeof name !== 'string'))) throw new ValidationError('logStreamNames must be an array of strings.')
if (body.startTime !== undefined && typeof body.startTime !== 'number') throw new ValidationError('startTime must be a number (epoch milliseconds).')
if (body.endTime !== undefined && typeof body.endTime !== 'number') throw new ValidationError('endTime must be a number (epoch milliseconds).')
if (typeof body.startTime === 'number' && typeof body.endTime === 'number' && body.startTime > body.endTime) throw new ValidationError('startTime must not be after endTime.')
return c.json(await svc(c).filterLogEvents(cloud, c.req.param('id'), {
filterPattern: body.filterPattern,
logStreamNames: body.logStreamNames as string[] | undefined,
startTime: body.startTime,
endTime: body.endTime,
}, pageQuery(c)))
})
})
app.post('/:cloud/services/messaging/resources/:id/messages', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
const body = await c.req.json<{messageBody?: unknown; messageAttributes?: unknown; messageGroupId?: unknown; messageDeduplicationId?: unknown}>()
if (typeof body.messageBody !== 'string') throw new ValidationError('messageBody must be a string.')
return c.json(await svc(c).sendSqsMessage(cloud, c.req.param('id'), {
messageBody: body.messageBody,
messageAttributes: body.messageAttributes as never,
messageGroupId: typeof body.messageGroupId === 'string' ? body.messageGroupId : undefined,
messageDeduplicationId: typeof body.messageDeduplicationId === 'string' ? body.messageDeduplicationId : undefined,
}), 201)
})
})
app.post('/:cloud/services/messaging/resources/:id/messages/receive', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
const body = await c.req.json<{maxNumberOfMessages?: unknown}>()
if (body.maxNumberOfMessages !== undefined && typeof body.maxNumberOfMessages !== 'number') throw new ValidationError('maxNumberOfMessages must be a number.')
return c.json(await svc(c).receiveSqsMessages(cloud, c.req.param('id'), {maxNumberOfMessages: body.maxNumberOfMessages}))
})
})
app.post('/:cloud/services/messaging/resources/:id/messages/delete', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
const body = await c.req.json<{receiptHandle?: unknown}>()
if (typeof body.receiptHandle !== 'string' || !body.receiptHandle) throw new ValidationError('receiptHandle is required.')
await svc(c).deleteSqsMessage(cloud, c.req.param('id'), body.receiptHandle)
return c.json({ok: true})
})
})
app.delete('/:cloud/services/messaging/resources/:id/messages', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
await svc(c).purgeSqsQueue(cloud, c.req.param('id'))
return c.json({ok: true})
})
})
app.post('/:cloud/services/messaging/resources/:id/move-tasks', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
const body = await c.req.json<{destinationArn?: unknown}>()
if (body.destinationArn !== undefined && typeof body.destinationArn !== 'string') throw new ValidationError('destinationArn must be a string.')
return c.json(await svc(c).startSqsMoveTask(cloud, c.req.param('id'), {destinationArn: body.destinationArn}))
})
})
app.delete('/:cloud/services/email/inbox', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => {
await svc(c).clearEmailInbox(cloud)
return c.json({ok: true})
})
})
app.post('/:cloud/services/userpools/resources/:id/users/:username/actions', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => {
await svc(c).updateUserPoolUser(cloud, c.req.param('id'), c.req.param('username'), await c.req.json())
return c.json({ok: true})
})
})
app.put('/:cloud/services/userpools/resources/:id/users/:username/groups', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => {
const body = await c.req.json<{groups?: string[]}>()
await svc(c).setUserPoolUserGroups(cloud, c.req.param('id'), c.req.param('username'), body.groups as string[])
return c.json({ok: true})
})
})
app.get('/:cloud/services/userpools/resources/:id/groups', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => c.json(await svc(c).listUserPoolGroups(cloud, c.req.param('id'))))
})
app.post('/:cloud/services/userpools/resources/:id/groups', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => {
const group = await svc(c).createUserPoolGroup(cloud, c.req.param('id'), await c.req.json())
return c.json(group, 201)
})
})
app.delete('/:cloud/services/userpools/resources/:id/groups/:groupName', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => {
await svc(c).deleteUserPoolGroup(cloud, c.req.param('id'), c.req.param('groupName'))
return c.json({ok: true})
})
})
app.post('/:cloud/services/userpools/resources/:id/sign-in-test', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => c.json(await svc(c).testUserPoolSignIn(cloud, c.req.param('id'), await c.req.json())))
})
app.get('/:cloud/services/userpools/resources/:id/clients/:clientId', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => c.json(await svc(c).describeUserPoolClient(cloud, c.req.param('id'), c.req.param('clientId'))))
})
app.put('/:cloud/services/userpools/resources/:id/clients/:clientId/oauth', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => c.json(await svc(c).updateUserPoolClientOAuth(cloud, c.req.param('id'), c.req.param('clientId'), await c.req.json())))
})
app.get('/:cloud/services/monitoring/metrics', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => c.json(await svc(c).listMetrics(cloud, {namespace: c.req.query('namespace') || undefined, search: c.req.query('search') || undefined})))
})
app.post('/:cloud/services/monitoring/metric-data', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => c.json(await svc(c).getMetricSeries(cloud, await c.req.json())))
})
app.get('/:cloud/services/monitoring/resources/:id/history', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => c.json(await svc(c).describeAlarmHistory(cloud, c.req.param('id'))))
})
app.put('/:cloud/services/monitoring/resources/:id/state', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => {
const body = await c.req.json<{state?: AlarmStateValue; reason?: string}>()
await svc(c).setAlarmState(cloud, c.req.param('id'), body.state as AlarmStateValue, body.reason ?? '')
return c.json({ok: true})
})
})
app.put('/:cloud/services/monitoring/resources/:id/actions', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => {
const body = await c.req.json<{enabled?: boolean}>()
await svc(c).setAlarmActionsEnabled(cloud, c.req.param('id'), body.enabled as boolean)
return c.json({ok: true})
})
})
app.put('/:cloud/services/cdn/resources/:id/enabled', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => {
const body = await c.req.json<{enabled?: boolean}>()
return c.json(await svc(c).setDistributionEnabled(cloud, c.req.param('id'), body.enabled as boolean))
})
})
app.post('/:cloud/services/cdn/resources/:id/invalidations', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => {
const body = await c.req.json<{paths?: string[]}>()
return c.json(await svc(c).createDistributionInvalidation(cloud, c.req.param('id'), body.paths as string[]), 201)
})
})
app.get('/:cloud/services/certificates/resources/:id/pem', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => c.json(await svc(c).getCertificatePem(cloud, c.req.param('id'))))
})
app.post('/:cloud/services/certificates/resources/:id/export', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => {
const body = await c.req.json<{passphrase?: string}>()
return c.json(await svc(c).exportCertificate(cloud, c.req.param('id'), body.passphrase as string))
})
})
app.post('/:cloud/services/certificates/resources/:id/revoke', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => {
const body = await c.req.json<{reason?: CertificateRevocationReason}>()
return c.json(await svc(c).revokeCertificate(cloud, c.req.param('id'), body.reason ?? 'UNSPECIFIED'))
})
})
app.post('/:cloud/services/filesystems/resources/:id/mount-targets', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => {
const body = await c.req.json<{subnetId?: string}>()
return c.json(await svc(c).createMountTarget(cloud, c.req.param('id'), body.subnetId ?? ''), 201)
})
})
app.delete('/:cloud/services/filesystems/resources/:id/mount-targets/:mountTargetId', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => c.json(await svc(c).deleteMountTarget(cloud, c.req.param('id'), c.req.param('mountTargetId'))))
})
app.post('/:cloud/services/filesystems/resources/:id/access-points', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => c.json(await svc(c).createAccessPoint(cloud, c.req.param('id'), await c.req.json<AccessPointInput>()), 201))
})
app.delete('/:cloud/services/filesystems/resources/:id/access-points/:accessPointId', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => c.json(await svc(c).deleteAccessPoint(cloud, c.req.param('id'), c.req.param('accessPointId'))))
})
app.post('/:cloud/services/filesystems/resources/:id/functions', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => c.json(await svc(c).attachFileSystemToFunction(cloud, c.req.param('id'), await c.req.json<FileSystemAttachmentInput>())))
})
app.delete('/:cloud/services/filesystems/resources/:id/functions/:functionName', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => c.json(await svc(c).detachFileSystemFromFunction(cloud, c.req.param('id'), c.req.param('functionName'))))
})
app.put('/:cloud/services/autoscaling/resources/:id/capacity', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => c.json(await svc(c).setAutoScalingCapacity(cloud, c.req.param('id'), await c.req.json<AutoScalingCapacityInput>())))
})
app.post('/:cloud/services/autoscaling/resources/:id/instances/:instanceId/terminate', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => {
const body = await c.req.json<{replace?: boolean}>()
await svc(c).terminateAutoScalingInstance(cloud, c.req.param('id'), c.req.param('instanceId'), body.replace === true)
return c.json({ok: true})
})
})
app.get('/:cloud/services/k8s/resources/:id/nodegroups', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => {
const nodegroups = await svc(c).listKubernetesNodegroups(cloud, c.req.param('id'))
return c.json(nodegroups)
})
})
app.post('/:cloud/services/k8s/resources/:id/nodegroups', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => {
const nodegroup = await svc(c).createKubernetesNodegroup(cloud, c.req.param('id'), await c.req.json())
return c.json(nodegroup, 201)
})
})
app.delete('/:cloud/services/k8s/resources/:id/nodegroups/:nodegroupId', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => {
await svc(c).deleteKubernetesNodegroup(cloud, c.req.param('id'), c.req.param('nodegroupId'))
return c.json({ok: true})
})
})
app.get('/:cloud/services/k8s/resources/:id/fargate-profiles', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => {
const profiles = await svc(c).listKubernetesFargateProfiles(cloud, c.req.param('id'))
return c.json(profiles)
})
})
app.post('/:cloud/services/k8s/resources/:id/fargate-profiles', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => {
const profile = await svc(c).createKubernetesFargateProfile(cloud, c.req.param('id'), await c.req.json())
return c.json(profile, 201)
})
})
app.delete('/:cloud/services/k8s/resources/:id/fargate-profiles/:profileId', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => {
await svc(c).deleteKubernetesFargateProfile(cloud, c.req.param('id'), c.req.param('profileId'))
return c.json({ok: true})
})
})
app.get('/:cloud/services/configuration/resources/:id/environments', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => {
return c.json(await svc(c).listAppConfigEnvironments(cloud, c.req.param('id')))
})
})
app.post('/:cloud/services/configuration/resources/:id/environments', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => {
const values = await c.req.json<Record<string, unknown>>()
return c.json(await svc(c).createAppConfigEnvironment(cloud, c.req.param('id'), {values}), 201)
})
})
app.delete('/:cloud/services/configuration/resources/:id/environments/:environmentId', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => {
await svc(c).deleteAppConfigEnvironment(cloud, c.req.param('id'), c.req.param('environmentId'))
return c.json({ok: true})
})
})
app.get('/:cloud/services/configuration/resources/:id/configuration-profiles', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => {
return c.json(await svc(c).listAppConfigConfigurationProfiles(cloud, c.req.param('id')))
})
})
app.post('/:cloud/services/configuration/resources/:id/configuration-profiles', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => {
const values = await c.req.json<Record<string, unknown>>()
return c.json(await svc(c).createAppConfigConfigurationProfile(cloud, c.req.param('id'), {values}), 201)
})
})
app.delete('/:cloud/services/configuration/resources/:id/configuration-profiles/:profileId', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => {
await svc(c).deleteAppConfigConfigurationProfile(cloud, c.req.param('id'), c.req.param('profileId'))
return c.json({ok: true})
})
})
app.get('/:cloud/services/configuration/resources/:id/configuration-profiles/:profileId/hosted-configuration-versions', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => {
return c.json(await svc(c).listAppConfigHostedConfigurationVersions(cloud, c.req.param('id'), c.req.param('profileId')))
})
})
app.post('/:cloud/services/configuration/resources/:id/configuration-profiles/:profileId/hosted-configuration-versions', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => {
const values = await c.req.json<Record<string, unknown>>()
return c.json(await svc(c).createAppConfigHostedConfigurationVersion(cloud, c.req.param('id'), c.req.param('profileId'), {values}), 201)
})
})
const hostedVersionNumber = (raw: string): number | null => {
const value = Number(raw)
return Number.isInteger(value) && value > 0 ? value : null
}
app.get('/:cloud/services/configuration/resources/:id/configuration-profiles/:profileId/hosted-configuration-versions/:versionNumber', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
const versionNumber = hostedVersionNumber(c.req.param('versionNumber'))
if (!isCloudProvider(cloud) || versionNumber === null) return c.json({error: 'Unknown cloud or version'}, 404)
return withRuntime(c, async () => {
const version = await svc(c).getAppConfigHostedConfigurationVersion(cloud, c.req.param('id'), c.req.param('profileId'), versionNumber)
if (!version) return c.json({error: 'Hosted configuration version not found'}, 404)
return c.json(version)
})
})
app.delete('/:cloud/services/configuration/resources/:id/configuration-profiles/:profileId/hosted-configuration-versions/:versionNumber', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
const versionNumber = hostedVersionNumber(c.req.param('versionNumber'))
if (!isCloudProvider(cloud) || versionNumber === null) return c.json({error: 'Unknown cloud or version'}, 404)
return withRuntime(c, async () => {
await svc(c).deleteAppConfigHostedConfigurationVersion(cloud, c.req.param('id'), c.req.param('profileId'), versionNumber)
return c.json({ok: true})
})
})
app.get('/:cloud/services/configuration/deployment-strategies', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => {
return c.json(await svc(c).listAppConfigDeploymentStrategies(cloud))
})
})
app.post('/:cloud/services/configuration/deployment-strategies', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => {
const values = await c.req.json<Record<string, unknown>>()
return c.json(await svc(c).createAppConfigDeploymentStrategy(cloud, {values}), 201)
})
})
app.delete('/:cloud/services/configuration/deployment-strategies/:strategyId', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => {
await svc(c).deleteAppConfigDeploymentStrategy(cloud, c.req.param('strategyId'))
return c.json({ok: true})
})
})
app.post('/:cloud/services/configuration/resources/:id/environments/:environmentId/deployments', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => {
const values = await c.req.json<Record<string, unknown>>()
return c.json(await svc(c).startAppConfigDeployment(cloud, c.req.param('id'), c.req.param('environmentId'), {values}), 201)
})
})
app.get('/:cloud/services/configuration/resources/:id/environments/:environmentId/deployments/:deploymentNumber', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
const deploymentNumber = Number(c.req.param('deploymentNumber'))
if (!isCloudProvider(cloud) || !Number.isInteger(deploymentNumber) || deploymentNumber <= 0) {
return c.json({error: 'Unknown cloud or deployment'}, 404)
}
return withRuntime(c, async () => {
const deployment = await svc(c).getAppConfigDeployment(cloud, c.req.param('id'), c.req.param('environmentId'), deploymentNumber)
if (!deployment) return c.json({error: 'Deployment not found'}, 404)
return c.json(deployment)
})
})
// Child collections, parameterised by service. All the literal-segment
// routes above (Cosmos containers, SQL, NoSQL items, email inbox, k8s
// nodegroups/fargate profiles) must stay registered before these, or the
// `:service` param below would swallow their requests first.
app.post('/:cloud/services/apigateway/resources/:id/routes', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
const input = await c.req.json<Record<string, unknown>>()
const route = await svc(c).createApiGatewayLambdaRoute(cloud, c.req.param('id'), {
path: typeof input.path === 'string' ? input.path : '',
parentResourceId: typeof input.parentResourceId === 'string' ? input.parentResourceId : undefined,
pathPart: typeof input.pathPart === 'string' ? input.pathPart : undefined,
method: typeof input.method === 'string' ? input.method : '',
stage: typeof input.stage === 'string' ? input.stage : '',
functionName: typeof input.functionName === 'string' ? input.functionName : '',
})
return c.json(route, 201)
})
})
app.get('/:cloud/services/apigateway/resources/:id/routes', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => c.json(await svc(c).listApiGatewayRoutes(cloud, c.req.param('id'))))
})
app.delete('/:cloud/services/apigateway/resources/:id/routes/:resourceId/:method', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
await svc(c).deleteApiGatewayRoute(cloud, c.req.param('id'), {
resourceId: c.req.param('resourceId'),
path: c.req.query('path') ?? '',
method: c.req.param('method'),
stage: c.req.query('stage') ?? '',
})
return c.json({ok: true})
})
})
app.post('/:cloud/services/apigateway/resources/:id/cors', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
const input = await c.req.json<Record<string, unknown>>()
const result = await svc(c).enableApiGatewayCors(cloud, c.req.param('id'), {
resourceId: typeof input.resourceId === 'string' ? input.resourceId : '',
stage: typeof input.stage === 'string' ? input.stage : '',
origin: typeof input.origin === 'string' ? input.origin : '',
headers: typeof input.headers === 'string' ? input.headers : '',
methods: Array.isArray(input.methods) ? input.methods.filter((method): method is string => typeof method === 'string') : [],
})
return c.json(result)
})
})
// HTTP and WebSocket APIs (API Gateway v2) live on the same page as REST APIs.
app.get('/:cloud/services/apigateway/resources/:id/http-api', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => c.json(await svc(c).describeHttpApi(cloud, c.req.param('id'))))
})
app.post('/:cloud/services/apigateway/resources/:id/http-api/routes', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
const input = await c.req.json<Record<string, unknown>>()
const route = await svc(c).createHttpApiRoute(cloud, c.req.param('id'), {
routeKey: typeof input.routeKey === 'string' ? input.routeKey : '',
functionName: typeof input.functionName === 'string' ? input.functionName : undefined,
httpUrl: typeof input.httpUrl === 'string' ? input.httpUrl : undefined,
authorizerId: typeof input.authorizerId === 'string' ? input.authorizerId : undefined,
returnsResponse: input.returnsResponse === true,
})
return c.json(route, 201)
})
})
app.delete('/:cloud/services/apigateway/resources/:id/http-api/routes/:routeId', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
await svc(c).deleteHttpApiRoute(cloud, c.req.param('id'), c.req.param('routeId'))
return c.json({ok: true})
})
})
app.put('/:cloud/services/apigateway/resources/:id/http-api/routes/:routeId/authorizer', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
const input = await c.req.json<{authorizerId?: unknown}>()
const authorizerId = typeof input.authorizerId === 'string' && input.authorizerId ? input.authorizerId : null
return c.json(await svc(c).setHttpApiRouteAuthorizer(cloud, c.req.param('id'), c.req.param('routeId'), authorizerId))
})
})
app.post('/:cloud/services/apigateway/resources/:id/http-api/stages', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
const input = await c.req.json<Record<string, unknown>>()
const stage = await svc(c).createHttpApiStage(cloud, c.req.param('id'), {
stageName: typeof input.stageName === 'string' ? input.stageName : '',
autoDeploy: input.autoDeploy === true,
})
return c.json(stage, 201)
})
})
app.delete('/:cloud/services/apigateway/resources/:id/http-api/stages/:stageName', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
await svc(c).deleteHttpApiStage(cloud, c.req.param('id'), c.req.param('stageName'))
return c.json({ok: true})
})
})
app.post('/:cloud/services/apigateway/resources/:id/http-api/stages/:stageName/deployments', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => c.json(await svc(c).deployHttpApi(cloud, c.req.param('id'), c.req.param('stageName')), 201))
})
app.post('/:cloud/services/apigateway/resources/:id/http-api/authorizers', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
const input = await c.req.json<Record<string, unknown>>()
const authorizer = await svc(c).createHttpApiAuthorizer(cloud, c.req.param('id'), {
name: typeof input.name === 'string' ? input.name : '',
userPoolId: typeof input.userPoolId === 'string' ? input.userPoolId : undefined,
userPoolClientId: typeof input.userPoolClientId === 'string' ? input.userPoolClientId : undefined,
issuer: typeof input.issuer === 'string' ? input.issuer : undefined,
audience: Array.isArray(input.audience) ? input.audience.filter((value): value is string => typeof value === 'string') : undefined,
})
return c.json(authorizer, 201)
})
})
app.delete('/:cloud/services/apigateway/resources/:id/http-api/authorizers/:authorizerId', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
await svc(c).deleteHttpApiAuthorizer(cloud, c.req.param('id'), c.req.param('authorizerId'))
return c.json({ok: true})
})
})
app.put('/:cloud/services/apigateway/resources/:id/http-api/cors', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
const input = await c.req.json<{cors?: Record<string, unknown> | null}>()
const cors = input.cors ? {
allowOrigins: stringList(input.cors.allowOrigins),
allowMethods: stringList(input.cors.allowMethods),
allowHeaders: stringList(input.cors.allowHeaders),
exposeHeaders: stringList(input.cors.exposeHeaders),
maxAgeSeconds: typeof input.cors.maxAgeSeconds === 'number' ? input.cors.maxAgeSeconds : null,
allowCredentials: input.cors.allowCredentials === true,
} : null
return c.json({cors: await svc(c).updateHttpApiCors(cloud, c.req.param('id'), cors)})
})
})
app.post('/:cloud/services/apigateway/resources/:id/http-api/websocket-test', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
const input = await c.req.json<Record<string, unknown>>()
return c.json(await svc(c).testWebSocketApi(cloud, c.req.param('id'), {
stage: typeof input.stage === 'string' ? input.stage : '',
message: typeof input.message === 'string' ? input.message : '',
query: typeof input.query === 'string' ? input.query : undefined,
}))
})
})
app.post('/:cloud/services/apigateway/resources/:id/routes/test', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
const input = await c.req.json<Record<string, unknown>>()
const result = await svc(c).testApiGatewayRoute(cloud, c.req.param('id'), {
path: typeof input.path === 'string' ? input.path : '',
method: typeof input.method === 'string' ? input.method : '',
stage: typeof input.stage === 'string' ? input.stage : '',
body: typeof input.body === 'string' ? input.body : '',
headers: stringHeaders(input.headers),
query: typeof input.query === 'string' ? input.query : '',
})
return c.json(result)
})
})
app.post('/:cloud/services/apigateway/resources/:id/methods/test', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
const input = await c.req.json<Record<string, unknown>>()
const result = await svc(c).testApiGatewayMethod(cloud, c.req.param('id'), {
resourceId: typeof input.resourceId === 'string' ? input.resourceId : '',
httpMethod: typeof input.httpMethod === 'string' ? input.httpMethod : '',
pathWithQueryString: typeof input.pathWithQueryString === 'string' ? input.pathWithQueryString : '',
body: typeof input.body === 'string' ? input.body : '',
headers: stringHeaders(input.headers),
multiValueHeaders: stringArrayHeaders(input.multiValueHeaders),
stageVariables: stringHeaders(input.stageVariables),
})
return c.json(result)
})
})
app.get('/:cloud/services/storage/resources/:id/lambda-triggers', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => c.json(await svc(c).listS3LambdaTriggers(cloud, c.req.param('id'))))
})
app.post('/:cloud/services/storage/resources/:id/lambda-triggers', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
const input = await c.req.json<Record<string, unknown>>()
const trigger = await svc(c).createS3LambdaTrigger(cloud, c.req.param('id'), {
functionName: typeof input.functionName === 'string' ? input.functionName : '',
events: Array.isArray(input.events) ? input.events.filter((event): event is string => typeof event === 'string') : [],
prefix: typeof input.prefix === 'string' ? input.prefix : undefined,
suffix: typeof input.suffix === 'string' ? input.suffix : undefined,
})
return c.json(trigger, 201)
})
})
app.delete('/:cloud/services/storage/resources/:id/lambda-triggers/:triggerId', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
await svc(c).deleteS3LambdaTrigger(cloud, c.req.param('id'), c.req.param('triggerId'))
return c.json({ok: true})
})
})
app.get('/:cloud/services/storage/resources/:id/website', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => c.json(await svc(c).getS3BucketWebsite(cloud, c.req.param('id'))))
})
app.put('/:cloud/services/storage/resources/:id/website', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
const input = await jsonBody<{mode?: unknown; indexDocument?: unknown; errorDocument?: unknown; hostName?: unknown; protocol?: unknown}>(c)
const configuration = s3WebsiteConfiguration(input)
await svc(c).putS3BucketWebsite(cloud, c.req.param('id'), configuration)
return c.json({ok: true})
})
})
app.delete('/:cloud/services/storage/resources/:id/website', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
await svc(c).deleteS3BucketWebsite(cloud, c.req.param('id'))
return c.json({ok: true})
})
})
app.get('/:cloud/services/serverless/resources/:id/eventbridge-triggers', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => c.json(await svc(c).listEventBridgeLambdaTriggers(cloud, c.req.param('id'))))
})
app.post('/:cloud/services/serverless/resources/:id/eventbridge-triggers', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
const input = await c.req.json<Record<string, unknown>>()
const trigger = await svc(c).createEventBridgeLambdaTrigger(cloud, {
ruleName: typeof input.ruleName === 'string' ? input.ruleName : '',
scheduleExpression: typeof input.scheduleExpression === 'string' ? input.scheduleExpression : '',
functionName: c.req.param('id'),
})
return c.json(trigger, 201)
})
})
app.delete('/:cloud/services/serverless/resources/:id/eventbridge-triggers/:triggerId', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
await svc(c).deleteEventBridgeLambdaTrigger(cloud, c.req.param('id'), c.req.param('triggerId'))
return c.json({ok: true})
})
})
app.get('/:cloud/services/events/resources/:id/rules', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => c.json(await svc(c).listEventBridgeRules(cloud, c.req.param('id'))))
})
app.post('/:cloud/services/events/resources/:id/events', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
const input = await c.req.json<Record<string, unknown>>()
const result = await svc(c).putEventBridgeEvent(cloud, c.req.param('id'), {
source: typeof input.source === 'string' ? input.source : '', detailType: typeof input.detailType === 'string' ? input.detailType : '', detail: typeof input.detail === 'string' ? input.detail : '',
resources: Array.isArray(input.resources) ? input.resources.filter((resource): resource is string => typeof resource === 'string') : undefined,
})
return c.json(result, result.errorCode ? 400 : 201)
})
})
app.get('/:cloud/services/iac/resources/:id/change-sets', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => c.json(await svc(c).listCloudFormationChangeSets(cloud, c.req.param('id'))))
})
app.post('/:cloud/services/iac/resources/:id/change-sets', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => { const input = await c.req.json<{name?: string; templateBody?: string; parameters?: Record<string, string>; capabilities?: string[]}>(); return c.json(await svc(c).createCloudFormationChangeSet(cloud, c.req.param('id'), {name: input.name ?? '', templateBody: input.templateBody ?? '', parameters: input.parameters, capabilities: input.capabilities}), 201) })
})
app.post('/:cloud/services/iac/resources/:id/change-sets/:name/execute', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => { await svc(c).executeCloudFormationChangeSet(cloud, c.req.param('id'), c.req.param('name')); return c.json({ok: true}) })
})
app.delete('/:cloud/services/iac/resources/:id/change-sets/:name', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => { await svc(c).deleteCloudFormationChangeSet(cloud, c.req.param('id'), c.req.param('name')); return c.json({ok: true}) })
})
app.put('/:cloud/services/iac/resources/:id/termination-protection', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => { const input = await c.req.json<{enabled?: boolean}>(); await svc(c).updateCloudFormationTerminationProtection(cloud, c.req.param('id'), input.enabled === true); return c.json({ok: true}) })
})
app.post('/:cloud/services/events/resources/:id/rules', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
const input = await c.req.json<Record<string, unknown>>()
const rule = await svc(c).createEventBridgeRule(cloud, c.req.param('id'), {
name: typeof input.name === 'string' ? input.name : '',
scheduleExpression: typeof input.scheduleExpression === 'string' ? input.scheduleExpression : undefined,
eventPattern: typeof input.eventPattern === 'string' ? input.eventPattern : undefined,
state: input.state === 'DISABLED' ? 'DISABLED' : input.state === 'ENABLED' ? 'ENABLED' : undefined,
description: typeof input.description === 'string' ? input.description : undefined,
targets: Array.isArray(input.targets) ? input.targets.flatMap((target) => {
if (!target || typeof target !== 'object' || Array.isArray(target)) return []
const value = target as {id?: unknown; arn?: unknown}
return typeof value.id === 'string' && typeof value.arn === 'string' ? [{id: value.id, arn: value.arn}] : []
}) : [],
})
return c.json(rule, 201)
})
})
app.delete('/:cloud/services/events/resources/:id/rules/:ruleName', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
await svc(c).deleteEventBridgeRule(cloud, c.req.param('id'), c.req.param('ruleName'))
return c.json({ok: true})
})
})
app.post('/:cloud/services/workflows/resources/:id/executions', async (c) => {
if (c.req.param('cloud') !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
const body = await jsonBody<{input?: unknown; name?: unknown}>(c)
if (typeof body.input !== 'string') throw new ValidationError('input must be a JSON string')
try {
JSON.parse(body.input)
} catch {
throw new ValidationError('input must be valid JSON')
}
if (body.name !== undefined && (typeof body.name !== 'string' || !body.name.trim())) throw new ValidationError('name must be a non-empty string')
return c.json(await svc(c).startWorkflowExecution('aws', c.req.param('id'), {input: body.input, ...(typeof body.name === 'string' ? {name: body.name} : {})}), 201)
})
})
app.get('/:cloud/services/workflows/resources/:id/executions', async (c) => {
if (c.req.param('cloud') !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => c.json(await svc(c).listWorkflowExecutions('aws', c.req.param('id'))))
})
app.get('/:cloud/services/workflows/resources/:id/executions/:executionArn', async (c) => {
if (c.req.param('cloud') !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => c.json(await svc(c).describeWorkflowExecution('aws', c.req.param('executionArn'))))
})
app.get('/:cloud/services/workflows/resources/:id/executions/:executionArn/history', async (c) => {
if (c.req.param('cloud') !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => c.json(await svc(c).getWorkflowExecutionHistory('aws', c.req.param('executionArn'), c.req.query('nextToken'))))
})
app.post('/:cloud/services/workflows/resources/:id/executions/:executionArn/stop', async (c) => {
if (c.req.param('cloud') !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
await svc(c).stopWorkflowExecution('aws', c.req.param('executionArn'))
return c.json({ok: true})
})
})
app.post('/:cloud/services/codebuild/resources/:id/builds', async (c) => {
if (c.req.param('cloud') !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
const body = await jsonBody<{environmentVariablesOverride?: unknown; buildspecOverride?: unknown}>(c)
const overrides = body.environmentVariablesOverride
if (overrides !== undefined && (!Array.isArray(overrides) || overrides.some((item) => !item || typeof item !== 'object' || typeof (item as {name?: unknown}).name !== 'string' || typeof (item as {value?: unknown}).value !== 'string' || ((item as {type?: unknown}).type !== undefined && !['PLAINTEXT', 'PARAMETER_STORE', 'SECRETS_MANAGER'].includes((item as {type: string}).type))))) {
throw new ValidationError('environmentVariablesOverride must contain name, value, and an optional supported type')
}
if (body.buildspecOverride !== undefined && typeof body.buildspecOverride !== 'string') {
throw new ValidationError('buildspecOverride must be a string')
}
const buildspecOverride = typeof body.buildspecOverride === 'string' && body.buildspecOverride.trim()
? body.buildspecOverride
: undefined
return c.json(await svc(c).startCodeBuild('aws', c.req.param('id'), {environmentVariablesOverride: overrides as never, buildspecOverride}), 201)
})
})
app.get('/:cloud/services/codebuild/resources/:id/builds', async (c) => {
if (c.req.param('cloud') !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => c.json(await svc(c).listCodeBuilds('aws', c.req.param('id'))))
})
app.get('/:cloud/services/codebuild/resources/:id/builds/:buildId', async (c) => {
if (c.req.param('cloud') !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
const build = await svc(c).getCodeBuild('aws', c.req.param('buildId'))
return build ? c.json(build) : c.json({error: 'Build not found'}, 404)
})
})
app.post('/:cloud/services/codepipeline/resources/:id/executions', async (c) => {
if (c.req.param('cloud') !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => c.json(await svc(c).startPipeline('aws', c.req.param('id')), 201))
})
app.post('/:cloud/services/codedeploy/resources/:id/deployments', async (c) => {
if (c.req.param('cloud') !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
const body = await jsonBody<{functionName?: unknown; alias?: unknown; currentVersion?: unknown; targetVersion?: unknown; beforeAllowTrafficHook?: unknown; afterAllowTrafficHook?: unknown}>(c)
const [applicationName, deploymentGroupName] = codeDeployGroupId(c.req.param('id'))
if (typeof body.functionName !== 'string' || typeof body.alias !== 'string' || typeof body.currentVersion !== 'string' || typeof body.targetVersion !== 'string') throw new ValidationError('functionName, alias, currentVersion, and targetVersion are required')
return c.json(await svc(c).startCodeDeployDeployment('aws', applicationName, deploymentGroupName, {functionName: body.functionName, alias: body.alias, currentVersion: body.currentVersion, targetVersion: body.targetVersion, beforeAllowTrafficHook: typeof body.beforeAllowTrafficHook === 'string' ? body.beforeAllowTrafficHook : undefined, afterAllowTrafficHook: typeof body.afterAllowTrafficHook === 'string' ? body.afterAllowTrafficHook : undefined}), 201)
})
})
app.get('/:cloud/services/codedeploy/resources/:id/deployments', async (c) => {
if (c.req.param('cloud') !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => { const [applicationName, deploymentGroupName] = codeDeployGroupId(c.req.param('id')); return c.json(await svc(c).listCodeDeployDeployments('aws', applicationName, deploymentGroupName)) })
})
app.get('/:cloud/services/codedeploy/deployments/:deploymentId', async (c) => {
if (c.req.param('cloud') !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => c.json(await svc(c).getCodeDeployDeployment('aws', c.req.param('deploymentId'))))
})
app.post('/:cloud/services/codedeploy/deployments/:deploymentId/stop', async (c) => {
if (c.req.param('cloud') !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => { const body = await jsonBody<{rollback?: unknown}>(c); await svc(c).stopCodeDeployDeployment('aws', c.req.param('deploymentId'), body.rollback === true); return c.json({ok: true}) })
})
app.get('/:cloud/services/codepipeline/resources/:id/executions', async (c) => {
if (c.req.param('cloud') !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => c.json(await svc(c).listPipelineExecutions('aws', c.req.param('id'))))
})
app.get('/:cloud/services/codepipeline/resources/:id/state', async (c) => {
if (c.req.param('cloud') !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => c.json(await svc(c).getPipelineState('aws', c.req.param('id'))))
})
app.get('/:cloud/services/serverless/resources/:id/sqs-event-source-mappings', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => c.json(await svc(c).listSqsLambdaEventSourceMappings(cloud, c.req.param('id'))))
})
app.post('/:cloud/services/serverless/resources/:id/sqs-event-source-mappings', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
const input = await c.req.json<Record<string, unknown>>()
const trigger = await svc(c).createSqsLambdaEventSourceMapping(cloud, {
functionName: c.req.param('id'),
queueArn: typeof input.queueArn === 'string' ? input.queueArn : '',
batchSize: typeof input.batchSize === 'number' ? input.batchSize : undefined,
})
return c.json(trigger, 201)
})
})
app.delete('/:cloud/services/serverless/resources/:id/sqs-event-source-mappings/:mappingId', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
await svc(c).deleteSqsLambdaEventSourceMapping(cloud, c.req.param('id'), c.req.param('mappingId'))
return c.json({ok: true})
})
})
app.post('/:cloud/services/kinesis/resources/:id/records', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
const input = await jsonBody<{partitionKey?: unknown; data?: unknown}>(c)
return c.json(await svc(c).putKinesisRecord(cloud, c.req.param('id'), {
partitionKey: typeof input.partitionKey === 'string' ? input.partitionKey : '',
data: typeof input.data === 'string' ? input.data : '',
}), 201)
})
})
app.get('/:cloud/services/kinesis/resources/:id/records', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => c.json(await svc(c).listKinesisRecords(cloud, c.req.param('id'))))
})
app.get('/:cloud/services/serverless/event-source-mappings', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => c.json(await svc(c).listLambdaEventSourceMappings(cloud, {
functionName: c.req.query('functionName'), eventSourceArn: c.req.query('eventSourceArn'),
})))
})
app.post('/:cloud/services/serverless/event-source-mappings', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
const input = await jsonBody<{functionName?: unknown; eventSourceArn?: unknown; batchSize?: unknown; startingPosition?: unknown}>(c)
return c.json(await svc(c).createLambdaEventSourceMapping(cloud, {
functionName: typeof input.functionName === 'string' ? input.functionName : '',
eventSourceArn: typeof input.eventSourceArn === 'string' ? input.eventSourceArn : '',
batchSize: typeof input.batchSize === 'number' ? input.batchSize : undefined,
startingPosition: input.startingPosition === 'TRIM_HORIZON' || input.startingPosition === 'LATEST' ? input.startingPosition : undefined,
}), 201)
})
})
app.patch('/:cloud/services/serverless/event-source-mappings/:mappingId', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
const input = await jsonBody<{batchSize?: unknown; enabled?: unknown}>(c)
return c.json(await svc(c).updateLambdaEventSourceMapping(cloud, c.req.param('mappingId'), {
batchSize: typeof input.batchSize === 'number' ? input.batchSize : undefined,
enabled: typeof input.enabled === 'boolean' ? input.enabled : undefined,
}))
})
})
app.delete('/:cloud/services/serverless/event-source-mappings/:mappingId', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (cloud !== 'aws') return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => { await svc(c).deleteLambdaEventSourceMapping(cloud, c.req.param('mappingId')); return c.json({ok: true}) })
})
app.get('/:cloud/services/:service/resources/:id/collections', async (c) => {
const target = childTarget(c)
if (!target) return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
const page = await svc(c).listChildCollections(target.cloud, target.service, c.req.param('id'), pageQuery(c))
return c.json(page)
})
})
app.post('/:cloud/services/:service/resources/:id/collections', async (c) => {
const target = childTarget(c)
if (!target) return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
const values = await c.req.json<Record<string, unknown>>()
const collection = await svc(c).createChildCollection(target.cloud, target.service, c.req.param('id'), {values})
return c.json(collection, 201)
})
})
app.delete('/:cloud/services/:service/resources/:id/collections/:cid', async (c) => {
const target = childTarget(c)
if (!target) return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
await svc(c).deleteChildCollection(target.cloud, target.service, c.req.param('id'), c.req.param('cid'))
return c.json({ok: true})
})
})
app.get('/:cloud/services/:service/resources/:id/collections/:cid/items', async (c) => {
const target = childTarget(c)
if (!target) return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
const page = await svc(c).listCollectionItems(target.cloud, target.service, c.req.param('id'), c.req.param('cid'), pageQuery(c))
return c.json(page)
})
})
app.post('/:cloud/services/:service/resources/:id/collections/:cid/items', async (c) => {
const target = childTarget(c)
if (!target) return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
const body = await c.req.json<Record<string, unknown>>()
const item = await svc(c).putCollectionItem(target.cloud, target.service, c.req.param('id'), c.req.param('cid'), body)
return c.json(item, 201)
})
})
app.delete('/:cloud/services/:service/resources/:id/collections/:cid/items/:itemId', async (c) => {
const target = childTarget(c)
if (!target) return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
await svc(c).deleteCollectionItem(target.cloud, target.service, c.req.param('id'), c.req.param('cid'), c.req.param('itemId'), c.req.query('partitionKey') ?? null)
return c.json({ok: true})
})
})
app.post('/:cloud/services/:service/resources/:id/collections/:cid/query', async (c) => {
const target = childTarget(c)
if (!target) return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
const body = await c.req.json<{query?: string | Record<string, unknown>}>()
const page = await svc(c).queryCollectionItems(target.cloud, target.service, c.req.param('id'), c.req.param('cid'), body.query ?? '', pageQuery(c))
return c.json(page)
})
})
app.get('/:cloud/services/:service/resources/:id/items', async (c) => {
const target = childTarget(c)
if (!target) return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
const page = await svc(c).listFlatItems(target.cloud, target.service, c.req.param('id'), pageQuery(c))
return c.json(page)
})
})
app.post('/:cloud/services/:service/resources/:id/items', async (c) => {
const target = childTarget(c)
if (!target) return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
const body = await c.req.json<Record<string, unknown>>()
const item = await svc(c).putFlatItem(target.cloud, target.service, c.req.param('id'), body)
return c.json(item, 201)
})
})
app.get('/:cloud/services/:service/resources/:id/items/:itemId', async (c) => {
const target = childTarget(c)
if (!target) return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
const item = await svc(c).getFlatItem(target.cloud, target.service, c.req.param('id'), c.req.param('itemId'))
return item ? c.json(item) : c.json({error: 'Item not found'}, 404)
})
})
app.delete('/:cloud/services/:service/resources/:id/items/:itemId', async (c) => {
const target = childTarget(c)
if (!target) return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
await svc(c).deleteFlatItem(target.cloud, target.service, c.req.param('id'), c.req.param('itemId'), c.req.query('partitionKey') ?? null)
return c.json({ok: true})
})
})
app.post('/:cloud/services/:service/resources/:id/query', async (c) => {
const target = childTarget(c)
if (!target) return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
const body = await c.req.json<{query?: string | Record<string, unknown>}>()
const page = await svc(c).queryFlatItems(target.cloud, target.service, c.req.param('id'), body.query ?? '', pageQuery(c))
return c.json(page)
})
})
app.get('/:cloud/services/:service/resources/:id', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
const serviceType = c.req.param('service') as CloudServiceType
if (!isCloudProvider(cloud) || !isServiceType(serviceType)) return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
const resource = await svc(c).getResource(cloud, serviceType, c.req.param('id'))
if (!resource) return c.json({error: 'Resource not found'}, 404)
return c.json(resource)
})
})
app.get('/:cloud/services/:service/resources/:id/objects', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
const serviceType = c.req.param('service') as CloudServiceType
if (!isCloudProvider(cloud) || !isServiceType(serviceType)) return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
const objects = await svc(c).listObjects(cloud, serviceType, c.req.param('id'), c.req.query('prefix') ?? '')
return c.json(objects)
})
})
app.put('/:cloud/services/:service/resources/:id/object', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
const serviceType = c.req.param('service') as CloudServiceType
if (!isCloudProvider(cloud) || !isServiceType(serviceType)) return c.json({error: 'Unknown cloud or service'}, 404)
const key = c.req.query('key') ?? ''
if (!key) return c.json({error: 'Object key is required'}, 400)
const body = new Uint8Array(await c.req.arrayBuffer())
const contentType = c.req.header('content-type') ?? 'application/octet-stream'
return withRuntime(c, async () => {
await svc(c).putObject(cloud, serviceType, c.req.param('id'), key, body, contentType)
return c.json({ok: true})
})
})
app.get('/:cloud/services/:service/resources/:id/object', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
const serviceType = c.req.param('service') as CloudServiceType
if (!isCloudProvider(cloud) || !isServiceType(serviceType)) return c.json({error: 'Unknown cloud or service'}, 404)
const key = c.req.query('key') ?? ''
if (!key) return c.json({error: 'Object key is required'}, 400)
return withRuntime(c, async () => {
const object = await svc(c).getObject(cloud, serviceType, c.req.param('id'), key)
return new Response(object.body, {
headers: {
'content-type': object.contentType,
...(object.contentLength === null ? {} : {'content-length': String(object.contentLength)}),
'content-disposition': `attachment; filename="${key.split('/').pop() ?? key}"`,
},
})
})
})
app.delete('/:cloud/services/:service/resources/:id/object', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
const serviceType = c.req.param('service') as CloudServiceType
if (!isCloudProvider(cloud) || !isServiceType(serviceType)) return c.json({error: 'Unknown cloud or service'}, 404)
const key = c.req.query('key') ?? ''
if (!key) return c.json({error: 'Object key is required'}, 400)
return withRuntime(c, async () => {
await svc(c).deleteObject(cloud, serviceType, c.req.param('id'), key)
return c.json({ok: true})
})
})
app.post('/:cloud/services/:service/resources/:id/object/copy', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
const serviceType = c.req.param('service') as CloudServiceType
if (!isCloudProvider(cloud) || !isServiceType(serviceType)) return c.json({error: 'Unknown cloud or service'}, 404)
const {srcKey, destKey, destResourceId} = await c.req.json<{srcKey: string; destKey: string; destResourceId?: string}>()
if (!srcKey || !destKey) return c.json({error: 'srcKey and destKey are required'}, 400)
return withRuntime(c, async () => {
await svc(c).copyObject(cloud, serviceType, c.req.param('id'), srcKey, destKey, destResourceId)
return c.json({ok: true})
})
})
app.post('/:cloud/services/:service/resources/:id/invoke', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
const serviceType = c.req.param('service') as CloudServiceType
if (!isCloudProvider(cloud) || !isServiceType(serviceType)) {
return c.json({error: 'Unknown cloud or service'}, 404)
}
return withRuntime(c, async () => {
const body: {payload?: string} = await c.req.json<{payload?: string}>().catch(() => ({}))
const result = await svc(c).invokeResource(
cloud,
serviceType,
c.req.param('id'),
body.payload ?? '{}',
)
return c.json(result)
})
})
app.post('/:cloud/services/:service/resources/:id/start', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
const serviceType = c.req.param('service') as CloudServiceType
if (!isCloudProvider(cloud) || !isServiceType(serviceType)) return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
await svc(c).startResource(cloud, serviceType, c.req.param('id'))
return c.json({ok: true})
})
})
app.post('/:cloud/services/:service/resources/:id/stop', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
const serviceType = c.req.param('service') as CloudServiceType
if (!isCloudProvider(cloud) || !isServiceType(serviceType)) return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
await svc(c).stopResource(cloud, serviceType, c.req.param('id'))
return c.json({ok: true})
})
})
app.post('/:cloud/services/:service/resources/:id/reboot', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
const serviceType = c.req.param('service') as CloudServiceType
if (!isCloudProvider(cloud) || !isServiceType(serviceType)) return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
await svc(c).rebootResource(cloud, serviceType, c.req.param('id'))
return c.json({ok: true})
})
})
app.post('/:cloud/services/notifications/resources/:id/publish', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => {
const body = await jsonBody<{message?: unknown; subject?: unknown; messageAttributes?: unknown}>(c)
if (typeof body.message !== 'string' || !body.message) {
throw new ValidationError('message is required')
}
if (body.subject !== undefined && typeof body.subject !== 'string') {
throw new ValidationError('subject must be a string')
}
if (body.messageAttributes !== undefined && !isStringRecord(body.messageAttributes)) {
throw new ValidationError('messageAttributes must be an object of string values')
}
const result = await svc(c).publishNotification(cloud, c.req.param('id'), {
message: body.message,
...(body.subject ? {subject: body.subject} : {}),
...(body.messageAttributes ? {messageAttributes: body.messageAttributes} : {}),
})
return c.json(result)
})
})
app.get('/:cloud/services/notifications/resources/:id/subscriptions', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => c.json(await svc(c).listSnsSubscriptions(cloud, c.req.param('id'))))
})
app.post('/:cloud/services/notifications/resources/:id/subscriptions', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => {
const body = await jsonBody<{protocol?: unknown; endpoint?: unknown}>(c)
if (typeof body.protocol !== 'string' || !body.protocol.trim()) throw new ValidationError('protocol is required')
if (typeof body.endpoint !== 'string' || !body.endpoint.trim()) throw new ValidationError('endpoint is required')
const subscription = await svc(c).createSnsSubscription(cloud, c.req.param('id'), {
protocol: body.protocol,
endpoint: body.endpoint,
})
return c.json(subscription, 201)
})
})
app.delete('/:cloud/services/notifications/resources/:id/subscriptions/:subscriptionArn', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withRuntime(c, async () => {
await svc(c).deleteSnsSubscription(cloud, c.req.param('subscriptionArn'))
return c.json({ok: true})
})
})
app.post('/:cloud/services/kms/resources/:id/encrypt', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withSensitiveRuntime(c, async () => {
const body = await jsonBody<KmsEncryptRequest>(c)
const result = await svc(c).encryptKms(cloud, c.req.param('id'), {
plaintext: decodeBase64(body.plaintextBase64, 'plaintextBase64', MAX_PLAINTEXT_BASE64_LENGTH),
encryptionAlgorithm: encryptionAlgorithm(body.encryptionAlgorithm),
encryptionContext: encryptionContext(body.encryptionContext),
})
return c.json({
ciphertextBlobBase64: Buffer.from(result.ciphertextBlob).toString('base64'),
keyId: result.keyId,
encryptionAlgorithm: result.encryptionAlgorithm,
})
})
})
app.post('/:cloud/services/kms/resources/:id/decrypt', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
if (!isCloudProvider(cloud)) return c.json({error: 'Unknown cloud'}, 404)
return withSensitiveRuntime(c, async () => {
const body = await jsonBody<KmsDecryptRequest>(c)
const result = await svc(c).decryptKms(cloud, c.req.param('id'), {
ciphertextBlob: decodeBase64(
body.ciphertextBlobBase64,
'ciphertextBlobBase64',
MAX_CIPHERTEXT_BASE64_LENGTH,
),
encryptionAlgorithm: encryptionAlgorithm(body.encryptionAlgorithm),
encryptionContext: encryptionContext(body.encryptionContext),
})
return c.json({
plaintextBase64: Buffer.from(result.plaintext).toString('base64'),
keyId: result.keyId,
encryptionAlgorithm: result.encryptionAlgorithm,
})
})
})
app.post('/:cloud/services/:service/resources', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
const serviceType = c.req.param('service') as CloudServiceType
if (!isCloudProvider(cloud) || !isServiceType(serviceType)) return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
const values = await c.req.json<Record<string, unknown>>()
const resource = await svc(c).createResource(cloud, serviceType, {values})
return c.json(resource, 201)
})
})
app.patch('/:cloud/services/:service/resources/:id', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
const serviceType = c.req.param('service') as CloudServiceType
if (!isCloudProvider(cloud) || !isServiceType(serviceType)) return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
const values = await c.req.json<Record<string, unknown>>()
const resource = await svc(c).updateResource(cloud, serviceType, c.req.param('id'), {values})
return c.json(resource, 200)
})
})
app.delete('/:cloud/services/:service/resources/:id', async (c) => {
const cloud = c.req.param('cloud') as CloudProvider
const serviceType = c.req.param('service') as CloudServiceType
if (!isCloudProvider(cloud) || !isServiceType(serviceType)) return c.json({error: 'Unknown cloud or service'}, 404)
return withRuntime(c, async () => {
await svc(c).deleteResource(cloud, serviceType, c.req.param('id'))
return c.json({ok: true})
})
})
return app
}
function isCloudProvider(value: string): value is CloudProvider {
return value === 'aws' || value === 'azure' || value === 'gcp'
}
function s3WebsiteConfiguration(input: {mode?: unknown; indexDocument?: unknown; errorDocument?: unknown; hostName?: unknown; protocol?: unknown}): S3BucketWebsiteConfiguration {
if (input.mode === 'index') {
if (typeof input.indexDocument !== 'string' || !input.indexDocument.trim()) {
throw new ValidationError('indexDocument is required for index website hosting')
}
if (input.errorDocument !== undefined && typeof input.errorDocument !== 'string' && input.errorDocument !== null) {
throw new ValidationError('errorDocument must be a string or null')
}
return {
mode: 'index',
indexDocument: input.indexDocument.trim(),
errorDocument: typeof input.errorDocument === 'string' && input.errorDocument.trim() ? input.errorDocument.trim() : null,
}
}
if (input.mode === 'redirect') {
if (typeof input.hostName !== 'string' || !input.hostName.trim()) {
throw new ValidationError('hostName is required for redirect website hosting')
}
if (input.protocol !== undefined && input.protocol !== null && input.protocol !== 'http' && input.protocol !== 'https') {
throw new ValidationError('protocol must be http, https, or null')
}
return {
mode: 'redirect',
hostName: input.hostName.trim(),
protocol: input.protocol === 'http' || input.protocol === 'https' ? input.protocol : null,
}
}
throw new ValidationError('mode must be index or redirect')
}
async function jsonBody<T>(c: Context): Promise<T> {
try {
const body = await c.req.json<unknown>()
if (!body || typeof body !== 'object' || Array.isArray(body)) {
throw new ValidationError('Request body must be a JSON object')
}
return body as T
} catch (error) {
if (error instanceof ValidationError) throw error
throw new ValidationError('Request body must be valid JSON')
}
}
function encryptionAlgorithm(value: unknown): KmsEncryptionAlgorithm {
if (typeof value !== 'string' || !KMS_ENCRYPTION_ALGORITHMS.has(value as KmsEncryptionAlgorithm)) {
throw new ValidationError('encryptionAlgorithm must be a supported KMS encryption algorithm')
}
return value as KmsEncryptionAlgorithm
}
function encryptionContext(value: unknown): Record<string, string> | undefined {
if (value === undefined) return undefined
if (!value || typeof value !== 'object' || Array.isArray(value)) {
throw new ValidationError('encryptionContext must be an object of string values')
}
const entries = Object.entries(value)
if (entries.some(([, entryValue]) => typeof entryValue !== 'string')) {
throw new ValidationError('encryptionContext must be an object of string values')
}
return entries.length === 0 ? undefined : Object.fromEntries(entries) as Record<string, string>
}
function isStringRecord(value: unknown): value is Record<string, string> {
return typeof value === 'object'
&& value !== null
&& !Array.isArray(value)
&& Object.values(value).every((item) => typeof item === 'string')
}
function stringList(value: unknown): string[] {
return Array.isArray(value) ? value.filter((item): item is string => typeof item === 'string') : []
}
function stringHeaders(value: unknown): Record<string, string> {
if (value === undefined) return {}
if (!value || typeof value !== 'object' || Array.isArray(value)) {
throw new ValidationError('headers must be an object of string values')
}
const entries = Object.entries(value)
if (entries.some(([, headerValue]) => typeof headerValue !== 'string')) {
throw new ValidationError('headers must be an object of string values')
}
return Object.fromEntries(entries) as Record<string, string>
}
function stringArrayHeaders(value: unknown): Record<string, string[]> {
if (!value || typeof value !== 'object' || Array.isArray(value)) return {}
return Object.fromEntries(Object.entries(value).flatMap(([name, values]) =>
Array.isArray(values) && values.every((header) => typeof header === 'string') ? [[name, values]] : [],
))
}
function decodeBase64(value: unknown, field: string, maxLength: number): Uint8Array {
if (typeof value !== 'string') throw new ValidationError(`${field} must be a base64 string`)
if (value.length > maxLength) throw new ValidationError(`${field} is too large`)
if (value.length % 4 !== 0 || !/^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/.test(value)) {
throw new ValidationError(`${field} must be canonical base64`)
}
const decoded = Buffer.from(value, 'base64')
if (decoded.toString('base64') !== value) throw new ValidationError(`${field} must be canonical base64`)
return decoded
}
/**
* Resolve and validate the cloud/service pair for a child-collection route.
* Returns null when either is unknown, which the caller turns into a 404.
*/
function childTarget(c: Context): {cloud: CloudProvider; service: CloudServiceType} | null {
const cloud = c.req.param('cloud') as CloudProvider
const service = c.req.param('service') as CloudServiceType
if (!isCloudProvider(cloud) || !isServiceType(service)) return null
return {cloud, service}
}
/** Parse paging params. An out-of-range limit raises and becomes a 400. */
function pageQuery(c: Context): PageQuery {
return {cursor: c.req.query('cursor'), limit: clampLimit(c.req.query('limit'))}
}
function codeDeployGroupId(id: string): [string, string] {
const separator = id.indexOf('/')
if (separator <= 0 || separator === id.length - 1) throw new ValidationError('Deployment group id must be applicationName/deploymentGroupName')
return [id.slice(0, separator), id.slice(separator + 1)]
}
async function withRuntime(c: Context, handler: () => Promise<Response>): Promise<Response> {
try {
return await handler()
} catch (err) {
const {status, body} = toHttpError(err, mapAwsSdkError)
return c.json(body, status)
}
}
async function withSensitiveRuntime(c: Context, handler: () => Promise<Response>): Promise<Response> {
c.header('cache-control', 'no-store')
try {
return await handler()
} catch (err) {
const {status, body} = toHttpError(err, mapAwsSdkError)
if (err instanceof CloudError) return c.json(body, status)
const message = body.code === 'invalid_request' ? 'KMS rejected the request' : body.error
return c.json({error: message, code: body.code, message}, status)
}
}
export default createCloudRoutes()