Files
floci-ui/packages/api/src/service/CloudProxyService.ts
T
Zakaria eeadeef664
CI / checks (pull_request) Successful in 26m59s
fix(frontend): stop MSK breaking the Console Home resource count
Console Home counted every available service through the generic resource
list, but MSK and MSK Connect are available through their own pre-SPI pages
with no adapter behind /resources, so each answered 501 and the Resources tile
read "Resource load failed". Service descriptors now say whether the generic
list serves them (listable, true when an adapter is registered), and Console
Home counts only those; the MSK cards still link to their pages.
2026-10-25 10:09:35 -04:00

1174 lines
63 KiB
TypeScript

import type {
CloudAvailability,
CloudDescriptor,
CloudProvider,
CloudResource,
CloudServiceAdapter,
CloudServiceDescriptor,
CloudServiceType,
CloudServiceStatus,
CloudStatus,
CosmosContainer,
CosmosItem,
CosmosQueryResult,
CreateDatabaseSnapshotInput,
CreateKubernetesFargateProfileInput,
CreateKubernetesNodegroupInput,
CreateResourceInput,
AppConfigConfigurationProfile,
AppConfigDeployment,
AppConfigDeploymentStrategy,
AppConfigEnvironment,
AppConfigHostedConfigurationVersion,
ApiGatewayLambdaRouteInput,
ApiGatewayLambdaRouteResult,
ApiGatewayTestRouteInput,
ApiGatewayTestRouteResult,
ApiGatewayMethodTestInput,
ApiGatewayMethodTestResult,
ApiGatewayRoutesResult,
ApiGatewayDeleteRouteInput,
ApiGatewayCorsInput,
ApiGatewayCorsResult,
HttpApiAuthorizerInput,
HttpApiAuthorizerOverview,
HttpApiCors,
HttpApiOverview,
HttpApiRouteInput,
HttpApiRouteOverview,
HttpApiStageInput,
HttpApiStageOverview,
HttpApiWebSocketTestInput,
HttpApiWebSocketTestResult,
S3LambdaTriggerInput,
S3LambdaTriggerOverview,
S3BucketWebsiteConfiguration,
EventBridgeLambdaTriggerInput,
EventBridgeLambdaTriggerOverview,
EventBridgeRuleInput,
EventBridgeRuleOverview,
EventBridgePutEventInput,
EventBridgePutEventResult,
CloudFormationChangeSet,
CloudFormationChangeSetInput,
SqsLambdaEventSourceMappingInput,
SqsLambdaEventSourceMappingOverview,
KinesisPutRecordInput,
KinesisRecord,
LambdaEventSourceMappingInput,
LambdaEventSourceMappingOverview,
SqsMessage,
SqsMoveTaskInput,
SqsMoveTaskResult,
SqsReceiveMessagesInput,
SqsSendMessageInput,
DatabaseSnapshot,
KubernetesFargateProfile,
KubernetesNodegroup,
LogsInsightsQueryInput,
LogsInsightsQueryResult,
LogsFilterInput,
KmsDecryptInput,
KmsDecryptResult,
KmsEncryptInput,
KmsEncryptResult,
PublishNotificationInput,
PublishNotificationResult,
SnsSubscriptionInput,
SnsSubscriptionOverview,
ResourceQuery,
ServerlessInvokeResult,
SqlConnectionInput,
SqlDatabase,
SqlQueryResult,
SqlTable,
RuntimeReachability,
ServiceSchema,
StorageObjectDownload,
StorageObjectList,
UpdateResourceInput,
CreateUserPoolGroupInput,
UserPoolGroup,
UserPoolSignInTestInput,
UserPoolSignInTestResult,
UserPoolClientSettings,
DistributionInvalidation,
AlarmHistoryItem,
AlarmStateValue,
MetricSeries,
MetricSeriesQuery,
MetricSummary,
UpdateUserPoolClientOAuthInput,
UserPoolUserAction,
} from '../cloud-spi/types'
import type {
ChildCollection,
ChildItem,
CollectionPage,
DocumentStoreAdapter,
ItemStoreAdapter,
ItemQuery,
PageQuery,
} from '../cloud-spi/childCollections'
import {NotSupportedError} from '../cloud-spi/errors'
import {CloudAdapterRegistry} from '../registry/CloudAdapterRegistry'
import type {StartWorkflowExecutionInput, WorkflowExecutionDetail, WorkflowExecutionHistoryPage, WorkflowExecutionSummary} from '../cloud-spi/workflowExecutions'
import type {CodeBuildBuild, StartCodeBuildInput} from '../cloud-spi/codebuild'
import type {PipelineExecutionSummary, PipelineState} from '../cloud-spi/codepipeline'
import type {CodeDeployDeployment, StartCodeDeployDeploymentInput} from '../cloud-spi/codedeploy'
import {SERVICE_CATALOG_ENTRIES, displayNameFor, routeFor} from '../cloud-spi/serviceCatalog'
import {toHttpError} from '../cloud-spi/errors'
import {mapAwsSdkError} from '../adapter-aws/awsErrors'
import {endpointFor, runtimeProbes, type RuntimeProbe} from './runtimeProbe'
/**
* Status probes are real network calls and the console polls them on a short
* interval, so results are memoized briefly. Without this, asking for
* per-service detail turns one page load into a probe per registered service.
*/
const STATUS_TTL_MS = 5_000
export class CloudProxyService {
private readonly runtimeCache = new Map<CloudProvider, {at: number; value: {runtime: RuntimeReachability; checkedAt: string; error: string | null}}>()
private readonly serviceStatusCache = new Map<string, {at: number; value: CloudServiceStatus}>()
private readonly probes: Record<CloudProvider, RuntimeProbe>
/**
* `probes` is injectable so tests can exercise status without reaching the
* network — the probes hit real runtime endpoints, which are present on a
* developer machine and absent in CI.
*/
constructor(
private readonly registry: CloudAdapterRegistry,
probes: Record<CloudProvider, RuntimeProbe> = runtimeProbes,
) {
this.probes = probes
}
clouds(): CloudDescriptor[] {
return [
{id: 'aws', displayName: 'AWS', availability: 'available'},
{id: 'azure', displayName: 'Azure', availability: 'available'},
{id: 'gcp', displayName: 'GCP', availability: 'available'},
]
}
/**
* Derived from the service catalog and the adapter registry — never from a
* hardcoded per-cloud list. Registering an adapter is therefore the only
* thing needed to make a service appear as available in the UI.
*/
services(cloud: CloudProvider): CloudServiceDescriptor[] {
return SERVICE_CATALOG_ENTRIES.map((entry) => {
const adapter = this.registry.get(cloud, entry.service)
const override = adapter?.descriptorOverride?.() ?? {}
const derived: CloudAvailability = entry.legacyAvailability?.[cloud]
?? (adapter ? 'available' : 'coming_soon')
const availability = override.availability ?? derived
const displayName = override.displayName ?? displayNameFor(entry, cloud)
return {
cloud,
service: entry.service,
displayName,
availability,
reason: override.reason ?? unavailableReason(availability, cloud, displayName),
route: routeFor(entry, cloud),
iconKey: entry.iconKey,
group: entry.group,
order: entry.order,
listable: Boolean(adapter),
}
})
}
/**
* Only a registered adapter can describe a service. Returning a static
* schema for an unregistered pair used to make the UI render a table that
* then failed on every request.
*/
schema(cloud: CloudProvider, service: CloudServiceType): ServiceSchema | null {
return this.registry.get(cloud, service)?.schema() ?? null
}
/**
* Cloud-level reachability, probed against the runtime itself rather than
* inferred from one adapter's list call.
*
* Per-service detail is opt-in: the sidebar polls this every few seconds, so
* fanning out across every service by default would hammer the runtime.
*/
async status(cloud: CloudProvider, options: {includeServices?: boolean} = {}): Promise<CloudStatus> {
const services = this.registry.servicesFor(cloud)
const cached = await this.probeRuntime(cloud)
const base: CloudStatus = {
cloud,
adapterRegistered: services.length > 0,
runtime: cached.runtime,
endpoint: endpointFor(cloud),
checkedAt: cached.checkedAt,
error: cached.error,
}
if (!options.includeServices) return base
return {
...base,
services: await Promise.all(services.map((service) => this.serviceStatus(cloud, service))),
}
}
/** Health of a single service, so the UI can gate on the service it is showing. */
async serviceStatus(cloud: CloudProvider, service: CloudServiceType): Promise<CloudServiceStatus> {
const cacheKey = `${cloud}:${service}`
const cached = this.serviceStatusCache.get(cacheKey)
if (cached && Date.now() - cached.at < STATUS_TTL_MS) return cached.value
const value = await this.measureServiceStatus(cloud, service)
this.serviceStatusCache.set(cacheKey, {at: Date.now(), value})
return value
}
private async measureServiceStatus(cloud: CloudProvider, service: CloudServiceType): Promise<CloudServiceStatus> {
const endpoint = endpointFor(cloud)
const adapter = this.registry.get(cloud, service)
const checkedAt = new Date().toISOString()
if (!adapter) {
return {
cloud, service, adapterRegistered: false, runtime: 'coming_soon',
endpoint, checkedAt, latencyMs: null, error: null, errorCode: null,
}
}
const startedAt = performance.now()
try {
// list() is a valid probe for every current adapter; health() overrides it.
if (adapter.health) await adapter.health()
else await adapter.list()
return {
cloud, service, adapterRegistered: true, runtime: 'reachable',
endpoint, checkedAt, latencyMs: Math.round(performance.now() - startedAt),
error: null, errorCode: null,
}
} catch (error) {
// The mapped code is what lets the UI distinguish "the runtime does not
// implement this" from "the runtime is down".
const {body} = toHttpError(error, mapAwsSdkError)
return {
cloud, service, adapterRegistered: true, runtime: 'unavailable',
endpoint, checkedAt, latencyMs: Math.round(performance.now() - startedAt),
error: body.detail ?? body.message, errorCode: body.code,
}
}
}
private async probeRuntime(cloud: CloudProvider): Promise<{runtime: RuntimeReachability; checkedAt: string; error: string | null}> {
const cached = this.runtimeCache.get(cloud)
if (cached && Date.now() - cached.at < STATUS_TTL_MS) return cached.value
const checkedAt = new Date().toISOString()
let value: {runtime: RuntimeReachability; checkedAt: string; error: string | null}
try {
await this.probes[cloud]()
value = {runtime: 'reachable', checkedAt, error: null}
} catch (error) {
value = {
runtime: 'unavailable',
checkedAt,
error: error instanceof Error ? error.message : 'Runtime check failed',
}
}
this.runtimeCache.set(cloud, {at: Date.now(), value})
return value
}
async listResources(cloud: CloudProvider, service: CloudServiceType, query: ResourceQuery): Promise<CloudResource[]> {
return this.requireAdapter(cloud, service).list(query)
}
async getResource(cloud: CloudProvider, service: CloudServiceType, id: string): Promise<CloudResource | null> {
return this.requireAdapter(cloud, service).get(id)
}
async createResource(cloud: CloudProvider, service: CloudServiceType, input: CreateResourceInput): Promise<CloudResource> {
return this.requireAdapter(cloud, service).create(input)
}
async createApiGatewayLambdaRoute(cloud: CloudProvider, apiId: string, input: ApiGatewayLambdaRouteInput): Promise<ApiGatewayLambdaRouteResult> {
const adapter = this.requireAdapter(cloud, 'apigateway')
if (!adapter.createApiGatewayLambdaRoute) {
throw new NotSupportedError(`API Gateway route creation is not supported for ${cloud}`)
}
return adapter.createApiGatewayLambdaRoute(apiId, input)
}
async testApiGatewayRoute(cloud: CloudProvider, apiId: string, input: ApiGatewayTestRouteInput): Promise<ApiGatewayTestRouteResult> {
const adapter = this.requireAdapter(cloud, 'apigateway')
if (!adapter.testApiGatewayRoute) {
throw new NotSupportedError(`API Gateway route testing is not supported for ${cloud}`)
}
return adapter.testApiGatewayRoute(apiId, input)
}
async testApiGatewayMethod(cloud: CloudProvider, apiId: string, input: ApiGatewayMethodTestInput): Promise<ApiGatewayMethodTestResult> {
const adapter = this.requireAdapter(cloud, 'apigateway')
if (!adapter.testApiGatewayMethod) {
throw new NotSupportedError(`API Gateway method testing is not supported for ${cloud}`)
}
return adapter.testApiGatewayMethod(apiId, input)
}
async listApiGatewayRoutes(cloud: CloudProvider, apiId: string): Promise<ApiGatewayRoutesResult> {
const adapter = this.requireAdapter(cloud, 'apigateway')
if (!adapter.listApiGatewayRoutes) {
throw new NotSupportedError(`API Gateway route discovery is not supported for ${cloud}`)
}
return adapter.listApiGatewayRoutes(apiId)
}
async deleteApiGatewayRoute(cloud: CloudProvider, apiId: string, input: ApiGatewayDeleteRouteInput): Promise<void> {
const adapter = this.requireAdapter(cloud, 'apigateway')
if (!adapter.deleteApiGatewayRoute) throw new NotSupportedError(`API Gateway route deletion is not supported for ${cloud}`)
await adapter.deleteApiGatewayRoute(apiId, input)
}
async enableApiGatewayCors(cloud: CloudProvider, apiId: string, input: ApiGatewayCorsInput): Promise<ApiGatewayCorsResult> {
const adapter = this.requireAdapter(cloud, 'apigateway')
if (!adapter.enableApiGatewayCors) throw new NotSupportedError(`API Gateway CORS is not supported for ${cloud}`)
return adapter.enableApiGatewayCors(apiId, input)
}
async describeHttpApi(cloud: CloudProvider, apiId: string): Promise<HttpApiOverview> {
return this.httpApiAdapter(cloud, 'describeHttpApi').describeHttpApi!(apiId)
}
async createHttpApiRoute(cloud: CloudProvider, apiId: string, input: HttpApiRouteInput): Promise<HttpApiRouteOverview> {
return this.httpApiAdapter(cloud, 'createHttpApiRoute').createHttpApiRoute!(apiId, input)
}
async deleteHttpApiRoute(cloud: CloudProvider, apiId: string, routeId: string): Promise<void> {
await this.httpApiAdapter(cloud, 'deleteHttpApiRoute').deleteHttpApiRoute!(apiId, routeId)
}
async setHttpApiRouteAuthorizer(cloud: CloudProvider, apiId: string, routeId: string, authorizerId: string | null): Promise<HttpApiRouteOverview> {
return this.httpApiAdapter(cloud, 'setHttpApiRouteAuthorizer').setHttpApiRouteAuthorizer!(apiId, routeId, authorizerId)
}
async createHttpApiStage(cloud: CloudProvider, apiId: string, input: HttpApiStageInput): Promise<HttpApiStageOverview> {
return this.httpApiAdapter(cloud, 'createHttpApiStage').createHttpApiStage!(apiId, input)
}
async deleteHttpApiStage(cloud: CloudProvider, apiId: string, stageName: string): Promise<void> {
await this.httpApiAdapter(cloud, 'deleteHttpApiStage').deleteHttpApiStage!(apiId, stageName)
}
async deployHttpApi(cloud: CloudProvider, apiId: string, stageName: string): Promise<HttpApiStageOverview> {
return this.httpApiAdapter(cloud, 'deployHttpApi').deployHttpApi!(apiId, stageName)
}
async createHttpApiAuthorizer(cloud: CloudProvider, apiId: string, input: HttpApiAuthorizerInput): Promise<HttpApiAuthorizerOverview> {
return this.httpApiAdapter(cloud, 'createHttpApiAuthorizer').createHttpApiAuthorizer!(apiId, input)
}
async deleteHttpApiAuthorizer(cloud: CloudProvider, apiId: string, authorizerId: string): Promise<void> {
await this.httpApiAdapter(cloud, 'deleteHttpApiAuthorizer').deleteHttpApiAuthorizer!(apiId, authorizerId)
}
async updateHttpApiCors(cloud: CloudProvider, apiId: string, cors: HttpApiCors | null): Promise<HttpApiCors | null> {
return this.httpApiAdapter(cloud, 'updateHttpApiCors').updateHttpApiCors!(apiId, cors)
}
async testWebSocketApi(cloud: CloudProvider, apiId: string, input: HttpApiWebSocketTestInput): Promise<HttpApiWebSocketTestResult> {
return this.httpApiAdapter(cloud, 'testWebSocketApi').testWebSocketApi!(apiId, input)
}
private httpApiAdapter(cloud: CloudProvider, method: keyof CloudServiceAdapter): CloudServiceAdapter {
const adapter = this.requireAdapter(cloud, 'apigateway')
if (typeof adapter[method] !== 'function') throw new NotSupportedError(`HTTP and WebSocket APIs are not supported for ${cloud}`)
return adapter
}
async createS3LambdaTrigger(cloud: CloudProvider, bucketName: string, input: S3LambdaTriggerInput): Promise<S3LambdaTriggerOverview> {
const adapter = this.requireAdapter(cloud, 'storage')
if (!adapter.createS3LambdaTrigger) throw new NotSupportedError(`S3 Lambda triggers are not supported for ${cloud}`)
return adapter.createS3LambdaTrigger(bucketName, input)
}
async listS3LambdaTriggers(cloud: CloudProvider, bucketName: string): Promise<S3LambdaTriggerOverview[]> {
const adapter = this.requireAdapter(cloud, 'storage')
if (!adapter.listS3LambdaTriggers) throw new NotSupportedError(`S3 Lambda triggers are not supported for ${cloud}`)
return adapter.listS3LambdaTriggers(bucketName)
}
async deleteS3LambdaTrigger(cloud: CloudProvider, bucketName: string, triggerId: string): Promise<void> {
const adapter = this.requireAdapter(cloud, 'storage')
if (!adapter.deleteS3LambdaTrigger) throw new NotSupportedError(`S3 Lambda triggers are not supported for ${cloud}`)
await adapter.deleteS3LambdaTrigger(bucketName, triggerId)
}
async getS3BucketWebsite(cloud: CloudProvider, bucketName: string): Promise<S3BucketWebsiteConfiguration | null> {
const adapter = this.requireAdapter(cloud, 'storage')
if (!adapter.getS3BucketWebsite) throw new NotSupportedError(`S3 website hosting is not supported for ${cloud}`)
return adapter.getS3BucketWebsite(bucketName)
}
async putS3BucketWebsite(cloud: CloudProvider, bucketName: string, configuration: S3BucketWebsiteConfiguration): Promise<void> {
const adapter = this.requireAdapter(cloud, 'storage')
if (!adapter.putS3BucketWebsite) throw new NotSupportedError(`S3 website hosting is not supported for ${cloud}`)
await adapter.putS3BucketWebsite(bucketName, configuration)
}
async deleteS3BucketWebsite(cloud: CloudProvider, bucketName: string): Promise<void> {
const adapter = this.requireAdapter(cloud, 'storage')
if (!adapter.deleteS3BucketWebsite) throw new NotSupportedError(`S3 website hosting is not supported for ${cloud}`)
await adapter.deleteS3BucketWebsite(bucketName)
}
async createEventBridgeLambdaTrigger(cloud: CloudProvider, input: EventBridgeLambdaTriggerInput): Promise<EventBridgeLambdaTriggerOverview> {
const adapter = this.requireAdapter(cloud, 'serverless')
if (!adapter.createEventBridgeLambdaTrigger) throw new NotSupportedError(`EventBridge Lambda triggers are not supported for ${cloud}`)
return adapter.createEventBridgeLambdaTrigger(input)
}
async listEventBridgeLambdaTriggers(cloud: CloudProvider, functionName: string): Promise<EventBridgeLambdaTriggerOverview[]> {
const adapter = this.requireAdapter(cloud, 'serverless')
if (!adapter.listEventBridgeLambdaTriggers) throw new NotSupportedError(`EventBridge Lambda triggers are not supported for ${cloud}`)
return adapter.listEventBridgeLambdaTriggers(functionName)
}
async deleteEventBridgeLambdaTrigger(cloud: CloudProvider, functionName: string, triggerId: string): Promise<void> {
const adapter = this.requireAdapter(cloud, 'serverless')
if (!adapter.deleteEventBridgeLambdaTrigger) throw new NotSupportedError(`EventBridge Lambda triggers are not supported for ${cloud}`)
await adapter.deleteEventBridgeLambdaTrigger(functionName, triggerId)
}
async listEventBridgeRules(cloud: CloudProvider, eventBusId: string): Promise<EventBridgeRuleOverview[]> {
const adapter = this.requireAdapter(cloud, 'events')
if (!adapter.listEventBridgeRules) throw new NotSupportedError(`EventBridge rules are not supported for ${cloud}`)
return adapter.listEventBridgeRules(eventBusId)
}
async createEventBridgeRule(cloud: CloudProvider, eventBusId: string, input: EventBridgeRuleInput): Promise<EventBridgeRuleOverview> {
const adapter = this.requireAdapter(cloud, 'events')
if (!adapter.createEventBridgeRule) throw new NotSupportedError(`EventBridge rules are not supported for ${cloud}`)
return adapter.createEventBridgeRule(eventBusId, input)
}
async deleteEventBridgeRule(cloud: CloudProvider, eventBusId: string, ruleName: string): Promise<void> {
const adapter = this.requireAdapter(cloud, 'events')
if (!adapter.deleteEventBridgeRule) throw new NotSupportedError(`EventBridge rules are not supported for ${cloud}`)
await adapter.deleteEventBridgeRule(eventBusId, ruleName)
}
async putEventBridgeEvent(cloud: CloudProvider, eventBusId: string, input: EventBridgePutEventInput): Promise<EventBridgePutEventResult> {
const adapter = this.requireAdapter(cloud, 'events')
if (!adapter.putEventBridgeEvent) throw new NotSupportedError(`Event publishing is not supported for ${cloud}`)
return adapter.putEventBridgeEvent(eventBusId, input)
}
async listCloudFormationChangeSets(cloud: CloudProvider, stackName: string): Promise<CloudFormationChangeSet[]> {
const adapter = this.requireAdapter(cloud, 'iac')
if (!adapter.listCloudFormationChangeSets) throw new NotSupportedError(`CloudFormation change sets are not supported for ${cloud}`)
return adapter.listCloudFormationChangeSets(stackName)
}
async createCloudFormationChangeSet(cloud: CloudProvider, stackName: string, input: CloudFormationChangeSetInput): Promise<CloudFormationChangeSet> {
const adapter = this.requireAdapter(cloud, 'iac')
if (!adapter.createCloudFormationChangeSet) throw new NotSupportedError(`CloudFormation change sets are not supported for ${cloud}`)
return adapter.createCloudFormationChangeSet(stackName, input)
}
async executeCloudFormationChangeSet(cloud: CloudProvider, stackName: string, changeSetName: string): Promise<void> {
const adapter = this.requireAdapter(cloud, 'iac')
if (!adapter.executeCloudFormationChangeSet) throw new NotSupportedError(`CloudFormation change sets are not supported for ${cloud}`)
await adapter.executeCloudFormationChangeSet(stackName, changeSetName)
}
async deleteCloudFormationChangeSet(cloud: CloudProvider, stackName: string, changeSetName: string): Promise<void> {
const adapter = this.requireAdapter(cloud, 'iac')
if (!adapter.deleteCloudFormationChangeSet) throw new NotSupportedError(`CloudFormation change sets are not supported for ${cloud}`)
await adapter.deleteCloudFormationChangeSet(stackName, changeSetName)
}
async updateCloudFormationTerminationProtection(cloud: CloudProvider, stackName: string, enabled: boolean): Promise<void> {
const adapter = this.requireAdapter(cloud, 'iac')
if (!adapter.updateCloudFormationTerminationProtection) throw new NotSupportedError(`CloudFormation termination protection is not supported for ${cloud}`)
await adapter.updateCloudFormationTerminationProtection(stackName, enabled)
}
async createSqsLambdaEventSourceMapping(cloud: CloudProvider, input: SqsLambdaEventSourceMappingInput): Promise<SqsLambdaEventSourceMappingOverview> {
const adapter = this.requireAdapter(cloud, 'serverless')
if (!adapter.createSqsLambdaEventSourceMapping) throw new NotSupportedError(`SQS Lambda event source mappings are not supported for ${cloud}`)
return adapter.createSqsLambdaEventSourceMapping(input)
}
async listSqsLambdaEventSourceMappings(cloud: CloudProvider, functionName: string): Promise<SqsLambdaEventSourceMappingOverview[]> {
const adapter = this.requireAdapter(cloud, 'serverless')
if (!adapter.listSqsLambdaEventSourceMappings) throw new NotSupportedError(`SQS Lambda event source mappings are not supported for ${cloud}`)
return adapter.listSqsLambdaEventSourceMappings(functionName)
}
async deleteSqsLambdaEventSourceMapping(cloud: CloudProvider, functionName: string, mappingId: string): Promise<void> {
const adapter = this.requireAdapter(cloud, 'serverless')
if (!adapter.deleteSqsLambdaEventSourceMapping) throw new NotSupportedError(`SQS Lambda event source mappings are not supported for ${cloud}`)
await adapter.deleteSqsLambdaEventSourceMapping(functionName, mappingId)
}
async putKinesisRecord(cloud: CloudProvider, streamName: string, input: KinesisPutRecordInput): Promise<{sequenceNumber: string; shardId: string}> {
const adapter = this.requireAdapter(cloud, 'kinesis')
if (!adapter.putKinesisRecord) throw new NotSupportedError(`Kinesis record production is not supported for ${cloud}`)
return adapter.putKinesisRecord(streamName, input)
}
async listKinesisRecords(cloud: CloudProvider, streamName: string): Promise<KinesisRecord[]> {
const adapter = this.requireAdapter(cloud, 'kinesis')
if (!adapter.listKinesisRecords) throw new NotSupportedError(`Kinesis record inspection is not supported for ${cloud}`)
return adapter.listKinesisRecords(streamName)
}
async createLambdaEventSourceMapping(cloud: CloudProvider, input: LambdaEventSourceMappingInput): Promise<LambdaEventSourceMappingOverview> {
const adapter = this.requireAdapter(cloud, 'serverless')
if (!adapter.createLambdaEventSourceMapping) throw new NotSupportedError(`Lambda event source mappings are not supported for ${cloud}`)
return adapter.createLambdaEventSourceMapping(input)
}
async listLambdaEventSourceMappings(cloud: CloudProvider, filter?: {functionName?: string; eventSourceArn?: string}): Promise<LambdaEventSourceMappingOverview[]> {
const adapter = this.requireAdapter(cloud, 'serverless')
if (!adapter.listLambdaEventSourceMappings) throw new NotSupportedError(`Lambda event source mappings are not supported for ${cloud}`)
return adapter.listLambdaEventSourceMappings(filter)
}
async updateLambdaEventSourceMapping(cloud: CloudProvider, mappingId: string, input: {batchSize?: number; enabled?: boolean}): Promise<LambdaEventSourceMappingOverview> {
const adapter = this.requireAdapter(cloud, 'serverless')
if (!adapter.updateLambdaEventSourceMapping) throw new NotSupportedError(`Lambda event source mappings are not supported for ${cloud}`)
return adapter.updateLambdaEventSourceMapping(mappingId, input)
}
async deleteLambdaEventSourceMapping(cloud: CloudProvider, mappingId: string): Promise<void> {
const adapter = this.requireAdapter(cloud, 'serverless')
if (!adapter.deleteLambdaEventSourceMapping) throw new NotSupportedError(`Lambda event source mappings are not supported for ${cloud}`)
await adapter.deleteLambdaEventSourceMapping(mappingId)
}
async sendSqsMessage(cloud: CloudProvider, queueId: string, input: SqsSendMessageInput): Promise<{messageId: string}> {
const adapter = this.requireAdapter(cloud, 'messaging')
if (!adapter.sendSqsMessage) throw new NotSupportedError(`SQS message sending is not supported for ${cloud}`)
return adapter.sendSqsMessage(queueId, input)
}
async receiveSqsMessages(cloud: CloudProvider, queueId: string, input: SqsReceiveMessagesInput): Promise<SqsMessage[]> {
const adapter = this.requireAdapter(cloud, 'messaging')
if (!adapter.receiveSqsMessages) throw new NotSupportedError(`SQS message receiving is not supported for ${cloud}`)
return adapter.receiveSqsMessages(queueId, input)
}
async deleteSqsMessage(cloud: CloudProvider, queueId: string, receiptHandle: string): Promise<void> {
const adapter = this.requireAdapter(cloud, 'messaging')
if (!adapter.deleteSqsMessage) throw new NotSupportedError(`SQS message deletion is not supported for ${cloud}`)
await adapter.deleteSqsMessage(queueId, receiptHandle)
}
async purgeSqsQueue(cloud: CloudProvider, queueId: string): Promise<void> {
const adapter = this.requireAdapter(cloud, 'messaging')
if (!adapter.purgeSqsQueue) throw new NotSupportedError(`SQS queue purging is not supported for ${cloud}`)
await adapter.purgeSqsQueue(queueId)
}
async startSqsMoveTask(cloud: CloudProvider, queueId: string, input: SqsMoveTaskInput): Promise<SqsMoveTaskResult> {
const adapter = this.requireAdapter(cloud, 'messaging')
if (!adapter.startSqsMoveTask) throw new NotSupportedError(`SQS message moving is not supported for ${cloud}`)
return adapter.startSqsMoveTask(queueId, input)
}
async updateResource(cloud: CloudProvider, service: CloudServiceType, id: string, input: UpdateResourceInput): Promise<CloudResource> {
const adapter = this.requireAdapter(cloud, service)
if (!adapter.update) throw new NotSupportedError(`Resource updates are not supported for ${cloud}/${service}`)
return adapter.update(id, input)
}
async dependencies(cloud: CloudProvider, service: CloudServiceType): Promise<Record<string, unknown>> {
const adapter = this.requireAdapter(cloud, service)
if (!adapter.dependencies) throw new NotSupportedError(`Form dependencies are not supported for ${cloud}/${service}`)
return adapter.dependencies()
}
async deleteResource(cloud: CloudProvider, service: CloudServiceType, id: string): Promise<void> {
await this.requireAdapter(cloud, service).delete(id)
}
async startResource(cloud: CloudProvider, service: CloudServiceType, id: string): Promise<void> {
const adapter = this.requireAdapter(cloud, service)
if (!adapter.start) throw new NotSupportedError(`Resource start is not supported for ${cloud}/${service}`)
await adapter.start(id)
}
async stopResource(cloud: CloudProvider, service: CloudServiceType, id: string): Promise<void> {
const adapter = this.requireAdapter(cloud, service)
if (!adapter.stop) throw new NotSupportedError(`Resource stop is not supported for ${cloud}/${service}`)
await adapter.stop(id)
}
async rebootResource(cloud: CloudProvider, service: CloudServiceType, id: string): Promise<void> {
const adapter = this.requireAdapter(cloud, service)
if (!adapter.reboot) throw new NotSupportedError(`Resource reboot is not supported for ${cloud}/${service}`)
await adapter.reboot(id)
}
async encryptKms(cloud: CloudProvider, keyId: string, input: KmsEncryptInput): Promise<KmsEncryptResult> {
const adapter = this.requireAdapter(cloud, 'kms')
if (!adapter.encrypt) throw new NotSupportedError(`${cloud}/kms encrypt is not supported`)
return adapter.encrypt(keyId, input)
}
async decryptKms(cloud: CloudProvider, keyId: string, input: KmsDecryptInput): Promise<KmsDecryptResult> {
const adapter = this.requireAdapter(cloud, 'kms')
if (!adapter.decrypt) throw new NotSupportedError(`${cloud}/kms decrypt is not supported`)
return adapter.decrypt(keyId, input)
}
async invokeResource(
cloud: CloudProvider,
service: CloudServiceType,
id: string,
payload: string,
): Promise<ServerlessInvokeResult> {
const adapter = this.requireAdapter(cloud, service)
if (!adapter.invoke) throw new NotSupportedError(`${cloud}/${service} invoke is not supported`)
return adapter.invoke(id, payload)
}
async publishNotification(cloud: CloudProvider, topicArn: string, input: PublishNotificationInput): Promise<PublishNotificationResult> {
const adapter = this.requireAdapter(cloud, 'notifications')
if (!adapter.publish) throw new NotSupportedError(`Publishing notifications is not supported for ${cloud}`)
return adapter.publish(topicArn, input)
}
async createSnsSubscription(cloud: CloudProvider, topicArn: string, input: SnsSubscriptionInput): Promise<SnsSubscriptionOverview> {
const adapter = this.requireAdapter(cloud, 'notifications')
if (!adapter.createSnsSubscription) throw new NotSupportedError(`SNS subscriptions are not supported for ${cloud}`)
return adapter.createSnsSubscription(topicArn, input)
}
async listSnsSubscriptions(cloud: CloudProvider, topicArn: string): Promise<SnsSubscriptionOverview[]> {
const adapter = this.requireAdapter(cloud, 'notifications')
if (!adapter.listSnsSubscriptions) throw new NotSupportedError(`SNS subscriptions are not supported for ${cloud}`)
return adapter.listSnsSubscriptions(topicArn)
}
async deleteSnsSubscription(cloud: CloudProvider, subscriptionArn: string): Promise<void> {
const adapter = this.requireAdapter(cloud, 'notifications')
if (!adapter.deleteSnsSubscription) throw new NotSupportedError(`SNS subscriptions are not supported for ${cloud}`)
await adapter.deleteSnsSubscription(subscriptionArn)
}
async listObjects(cloud: CloudProvider, service: CloudServiceType, resourceId: string, prefix?: string): Promise<StorageObjectList> {
const adapter = this.requireAdapter(cloud, service)
if (!adapter.listObjects) throw new NotSupportedError(`Object listing is not supported for ${cloud}/${service}`)
return adapter.listObjects(resourceId, prefix)
}
async putObject(cloud: CloudProvider, service: CloudServiceType, resourceId: string, key: string, body: Uint8Array, contentType: string): Promise<void> {
const adapter = this.requireAdapter(cloud, service)
if (!adapter.putObject) throw new NotSupportedError(`Object upload is not supported for ${cloud}/${service}`)
await adapter.putObject(resourceId, key, body, contentType)
}
async getObject(cloud: CloudProvider, service: CloudServiceType, resourceId: string, key: string): Promise<StorageObjectDownload> {
const adapter = this.requireAdapter(cloud, service)
if (!adapter.getObject) throw new NotSupportedError(`Object download is not supported for ${cloud}/${service}`)
return adapter.getObject(resourceId, key)
}
async deleteObject(cloud: CloudProvider, service: CloudServiceType, resourceId: string, key: string): Promise<void> {
const adapter = this.requireAdapter(cloud, service)
if (!adapter.deleteObject) throw new NotSupportedError(`Object delete is not supported for ${cloud}/${service}`)
await adapter.deleteObject(resourceId, key)
}
async copyObject(cloud: CloudProvider, service: CloudServiceType, srcResourceId: string, srcKey: string, destKey: string, destResourceId?: string): Promise<void> {
const adapter = this.requireAdapter(cloud, service)
if (!adapter.copyObject) throw new NotSupportedError(`Object copy is not supported for ${cloud}/${service}`)
await adapter.copyObject(srcResourceId, srcKey, destKey, destResourceId)
}
// Child collections. `requireDocuments`/`requireItems` reject the wrong
// shape rather than the missing method, so a flat store asked for
// collections gets a 501 that names the actual problem.
async listChildCollections(cloud: CloudProvider, service: CloudServiceType, resourceId: string, page: PageQuery): Promise<CollectionPage<ChildCollection>> {
return this.requireDocuments(cloud, service).listCollections(resourceId, page)
}
async createChildCollection(cloud: CloudProvider, service: CloudServiceType, resourceId: string, input: CreateResourceInput): Promise<ChildCollection> {
const documents = this.requireDocuments(cloud, service)
if (!documents.createCollection) throw new NotSupportedError(`Creating collections is not supported for ${cloud}/${service}`)
return documents.createCollection(resourceId, input)
}
async deleteChildCollection(cloud: CloudProvider, service: CloudServiceType, resourceId: string, collectionId: string): Promise<void> {
const documents = this.requireDocuments(cloud, service)
if (!documents.deleteCollection) throw new NotSupportedError(`Deleting collections is not supported for ${cloud}/${service}`)
await documents.deleteCollection(resourceId, collectionId)
}
async listCollectionItems(cloud: CloudProvider, service: CloudServiceType, resourceId: string, collectionId: string, page: PageQuery): Promise<CollectionPage<ChildItem>> {
return this.requireDocuments(cloud, service).listItems(resourceId, collectionId, page)
}
async putCollectionItem(cloud: CloudProvider, service: CloudServiceType, resourceId: string, collectionId: string, body: Record<string, unknown>): Promise<ChildItem> {
const documents = this.requireDocuments(cloud, service)
if (!documents.putItem) throw new NotSupportedError(`Writing items is not supported for ${cloud}/${service}`)
return documents.putItem(resourceId, collectionId, body)
}
async deleteCollectionItem(cloud: CloudProvider, service: CloudServiceType, resourceId: string, collectionId: string, itemId: string, partitionKey?: string | null): Promise<void> {
const documents = this.requireDocuments(cloud, service)
if (!documents.deleteItem) throw new NotSupportedError(`Deleting items is not supported for ${cloud}/${service}`)
await documents.deleteItem(resourceId, collectionId, itemId, partitionKey)
}
async queryCollectionItems(cloud: CloudProvider, service: CloudServiceType, resourceId: string, collectionId: string, query: ItemQuery, page: PageQuery): Promise<CollectionPage<ChildItem>> {
const documents = this.requireDocuments(cloud, service)
if (!documents.queryItems) throw new NotSupportedError(`Querying items is not supported for ${cloud}/${service}`)
return documents.queryItems(resourceId, collectionId, query, page)
}
async listFlatItems(cloud: CloudProvider, service: CloudServiceType, resourceId: string, page: PageQuery): Promise<CollectionPage<ChildItem>> {
return this.requireItems(cloud, service).listItems(resourceId, page)
}
async getFlatItem(cloud: CloudProvider, service: CloudServiceType, resourceId: string, itemId: string): Promise<ChildItem | null> {
const items = this.requireItems(cloud, service)
if (!items.getItem) throw new NotSupportedError(`Reading one item is not supported for ${cloud}/${service}`)
return items.getItem(resourceId, itemId)
}
async putFlatItem(cloud: CloudProvider, service: CloudServiceType, resourceId: string, body: Record<string, unknown>): Promise<ChildItem> {
const items = this.requireItems(cloud, service)
if (!items.putItem) throw new NotSupportedError(`Writing items is not supported for ${cloud}/${service}`)
return items.putItem(resourceId, body)
}
async deleteFlatItem(cloud: CloudProvider, service: CloudServiceType, resourceId: string, itemId: string, partitionKey?: string | null): Promise<void> {
const items = this.requireItems(cloud, service)
if (!items.deleteItem) throw new NotSupportedError(`Deleting items is not supported for ${cloud}/${service}`)
await items.deleteItem(resourceId, itemId, partitionKey)
}
async queryFlatItems(cloud: CloudProvider, service: CloudServiceType, resourceId: string, query: ItemQuery, page: PageQuery): Promise<CollectionPage<ChildItem>> {
const items = this.requireItems(cloud, service)
if (!items.queryItems) throw new NotSupportedError(`Querying items is not supported for ${cloud}/${service}`)
return items.queryItems(resourceId, query, page)
}
private requireDocuments(cloud: CloudProvider, service: CloudServiceType): DocumentStoreAdapter {
const adapter = this.requireAdapter(cloud, service)
if (!adapter.documents) throw new NotSupportedError(`Nested collections are not supported for ${cloud}/${service}`)
return adapter.documents
}
private requireItems(cloud: CloudProvider, service: CloudServiceType): ItemStoreAdapter {
const adapter = this.requireAdapter(cloud, service)
if (!adapter.items) throw new NotSupportedError(`Flat items are not supported for ${cloud}/${service}`)
return adapter.items
}
async listCosmosContainers(cloud: CloudProvider, databaseId: string): Promise<CosmosContainer[]> {
const adapter = this.requireAdapter(cloud, 'nosql')
if (!adapter.listCosmosContainers) throw new NotSupportedError(`Cosmos containers are not supported for ${cloud}/database`)
return adapter.listCosmosContainers(databaseId)
}
async createCosmosContainer(cloud: CloudProvider, databaseId: string, input: CreateResourceInput): Promise<CosmosContainer> {
const adapter = this.requireAdapter(cloud, 'nosql')
if (!adapter.createCosmosContainer) throw new NotSupportedError(`Cosmos container creation is not supported for ${cloud}/database`)
return adapter.createCosmosContainer(databaseId, input)
}
async deleteCosmosContainer(cloud: CloudProvider, databaseId: string, containerId: string): Promise<void> {
const adapter = this.requireAdapter(cloud, 'nosql')
if (!adapter.deleteCosmosContainer) throw new NotSupportedError(`Cosmos container deletion is not supported for ${cloud}/database`)
await adapter.deleteCosmosContainer(databaseId, containerId)
}
async listCosmosItems(cloud: CloudProvider, databaseId: string, containerId: string): Promise<CosmosItem[]> {
const adapter = this.requireAdapter(cloud, 'nosql')
if (!adapter.listCosmosItems) throw new NotSupportedError(`Cosmos items are not supported for ${cloud}/database`)
return adapter.listCosmosItems(databaseId, containerId)
}
async upsertCosmosItem(cloud: CloudProvider, databaseId: string, containerId: string, document: Record<string, unknown>): Promise<CosmosItem> {
const adapter = this.requireAdapter(cloud, 'nosql')
if (!adapter.upsertCosmosItem) throw new NotSupportedError(`Cosmos item upsert is not supported for ${cloud}/database`)
return adapter.upsertCosmosItem(databaseId, containerId, document)
}
async deleteCosmosItem(cloud: CloudProvider, databaseId: string, containerId: string, itemId: string, partitionKey?: string | null): Promise<void> {
const adapter = this.requireAdapter(cloud, 'nosql')
if (!adapter.deleteCosmosItem) throw new NotSupportedError(`Cosmos item deletion is not supported for ${cloud}/database`)
await adapter.deleteCosmosItem(databaseId, containerId, itemId, partitionKey)
}
async queryCosmosItems(cloud: CloudProvider, databaseId: string, containerId: string, query: string): Promise<CosmosQueryResult> {
const adapter = this.requireAdapter(cloud, 'nosql')
if (!adapter.queryCosmosItems) throw new NotSupportedError(`Cosmos query is not supported for ${cloud}/database`)
return adapter.queryCosmosItems(databaseId, containerId, query)
}
async listDatabaseSnapshots(cloud: CloudProvider, instanceIdentifier?: string): Promise<DatabaseSnapshot[]> {
const adapter = this.requireAdapter(cloud, 'database')
if (!adapter.listDatabaseSnapshots) throw new NotSupportedError(`Snapshot listing is not supported for ${cloud}/database`)
return adapter.listDatabaseSnapshots(instanceIdentifier)
}
async createDatabaseSnapshot(cloud: CloudProvider, input: CreateDatabaseSnapshotInput): Promise<DatabaseSnapshot> {
const adapter = this.requireAdapter(cloud, 'database')
if (!adapter.createDatabaseSnapshot) throw new NotSupportedError(`Snapshot creation is not supported for ${cloud}/database`)
return adapter.createDatabaseSnapshot(input)
}
async listDatabaseOrderableInstanceClasses(cloud: CloudProvider, engine?: string): Promise<string[]> {
const adapter = this.requireAdapter(cloud, 'database')
if (!adapter.listDatabaseOrderableInstanceClasses) throw new NotSupportedError(`Orderable instance class listing is not supported for ${cloud}/database`)
return adapter.listDatabaseOrderableInstanceClasses(engine)
}
async listSqlDatabases(cloud: CloudProvider, serverId: string, connection: SqlConnectionInput): Promise<SqlDatabase[]> {
const adapter = this.requireAdapter(cloud, 'database')
if (!adapter.listSqlDatabases) throw new NotSupportedError(`SQL database browsing is not supported for ${cloud}/database`)
return adapter.listSqlDatabases(serverId, connection)
}
async listSqlTables(cloud: CloudProvider, serverId: string, connection: SqlConnectionInput): Promise<SqlTable[]> {
const adapter = this.requireAdapter(cloud, 'database')
if (!adapter.listSqlTables) throw new NotSupportedError(`SQL table browsing is not supported for ${cloud}/database`)
return adapter.listSqlTables(serverId, connection)
}
async querySql(cloud: CloudProvider, serverId: string, connection: SqlConnectionInput, query: string): Promise<SqlQueryResult> {
const adapter = this.requireAdapter(cloud, 'database')
if (!adapter.querySql) throw new NotSupportedError(`SQL query is not supported for ${cloud}/database`)
return adapter.querySql(serverId, connection, query)
}
async queryLogs(cloud: CloudProvider, logGroupName: string, input: LogsInsightsQueryInput): Promise<LogsInsightsQueryResult> {
const adapter = this.requireAdapter(cloud, 'logs')
if (!adapter.queryLogs) throw new NotSupportedError(`Logs Insights query is not supported for ${cloud}/logs`)
return adapter.queryLogs(logGroupName, input)
}
async filterLogEvents(cloud: CloudProvider, logGroupName: string, input: LogsFilterInput, page: PageQuery = {}): Promise<CollectionPage<ChildItem>> {
const adapter = this.requireAdapter(cloud, 'logs')
if (!adapter.filterLogEvents) throw new NotSupportedError(`Log event filtering is not supported for ${cloud}/logs`)
return adapter.filterLogEvents(logGroupName, input, page)
}
async updateUserPoolUser(cloud: CloudProvider, poolId: string, username: string, input: UserPoolUserAction): Promise<void> {
const adapter = this.requireAdapter(cloud, 'userpools')
if (!adapter.updateUserPoolUser) throw new NotSupportedError(`User actions are not supported for ${cloud}/userpools`)
await adapter.updateUserPoolUser(poolId, username, input)
}
async setUserPoolUserGroups(cloud: CloudProvider, poolId: string, username: string, groups: string[]): Promise<void> {
const adapter = this.requireAdapter(cloud, 'userpools')
if (!adapter.setUserPoolUserGroups) throw new NotSupportedError(`User groups are not supported for ${cloud}/userpools`)
await adapter.setUserPoolUserGroups(poolId, username, groups)
}
async listUserPoolGroups(cloud: CloudProvider, poolId: string): Promise<UserPoolGroup[]> {
const adapter = this.requireAdapter(cloud, 'userpools')
if (!adapter.listUserPoolGroups) throw new NotSupportedError(`Groups are not supported for ${cloud}/userpools`)
return adapter.listUserPoolGroups(poolId)
}
async createUserPoolGroup(cloud: CloudProvider, poolId: string, input: CreateUserPoolGroupInput): Promise<UserPoolGroup> {
const adapter = this.requireAdapter(cloud, 'userpools')
if (!adapter.createUserPoolGroup) throw new NotSupportedError(`Group creation is not supported for ${cloud}/userpools`)
return adapter.createUserPoolGroup(poolId, input)
}
async deleteUserPoolGroup(cloud: CloudProvider, poolId: string, groupName: string): Promise<void> {
const adapter = this.requireAdapter(cloud, 'userpools')
if (!adapter.deleteUserPoolGroup) throw new NotSupportedError(`Group deletion is not supported for ${cloud}/userpools`)
await adapter.deleteUserPoolGroup(poolId, groupName)
}
async testUserPoolSignIn(cloud: CloudProvider, poolId: string, input: UserPoolSignInTestInput): Promise<UserPoolSignInTestResult> {
const adapter = this.requireAdapter(cloud, 'userpools')
if (!adapter.testUserPoolSignIn) throw new NotSupportedError(`Test sign-in is not supported for ${cloud}/userpools`)
return adapter.testUserPoolSignIn(poolId, input)
}
async listMetrics(cloud: CloudProvider, query: {namespace?: string; search?: string}): Promise<MetricSummary[]> {
const adapter = this.requireAdapter(cloud, 'monitoring')
if (!adapter.listMetrics) throw new NotSupportedError(`Metrics are not supported for ${cloud}/monitoring`)
return adapter.listMetrics(query)
}
async getMetricSeries(cloud: CloudProvider, query: MetricSeriesQuery): Promise<MetricSeries> {
const adapter = this.requireAdapter(cloud, 'monitoring')
if (!adapter.getMetricSeries) throw new NotSupportedError(`Metric data is not supported for ${cloud}/monitoring`)
return adapter.getMetricSeries(query)
}
async describeAlarmHistory(cloud: CloudProvider, alarmName: string): Promise<AlarmHistoryItem[]> {
const adapter = this.requireAdapter(cloud, 'monitoring')
if (!adapter.describeAlarmHistory) throw new NotSupportedError(`Alarm history is not supported for ${cloud}/monitoring`)
return adapter.describeAlarmHistory(alarmName)
}
async setAlarmState(cloud: CloudProvider, alarmName: string, state: AlarmStateValue, reason: string): Promise<void> {
const adapter = this.requireAdapter(cloud, 'monitoring')
if (!adapter.setAlarmState) throw new NotSupportedError(`Setting alarm states is not supported for ${cloud}/monitoring`)
await adapter.setAlarmState(alarmName, state, reason)
}
async setAlarmActionsEnabled(cloud: CloudProvider, alarmName: string, enabled: boolean): Promise<void> {
const adapter = this.requireAdapter(cloud, 'monitoring')
if (!adapter.setAlarmActionsEnabled) throw new NotSupportedError(`Alarm actions are not supported for ${cloud}/monitoring`)
await adapter.setAlarmActionsEnabled(alarmName, enabled)
}
async setDistributionEnabled(cloud: CloudProvider, distributionId: string, enabled: boolean): Promise<CloudResource> {
const adapter = this.requireAdapter(cloud, 'cdn')
if (!adapter.setDistributionEnabled) throw new NotSupportedError(`Enabling distributions is not supported for ${cloud}/cdn`)
return adapter.setDistributionEnabled(distributionId, enabled)
}
async createDistributionInvalidation(cloud: CloudProvider, distributionId: string, paths: string[]): Promise<DistributionInvalidation> {
const adapter = this.requireAdapter(cloud, 'cdn')
if (!adapter.createDistributionInvalidation) throw new NotSupportedError(`Invalidations are not supported for ${cloud}/cdn`)
return adapter.createDistributionInvalidation(distributionId, paths)
}
async describeUserPoolClient(cloud: CloudProvider, poolId: string, clientId: string): Promise<UserPoolClientSettings> {
const adapter = this.requireAdapter(cloud, 'userpools')
if (!adapter.describeUserPoolClient) throw new NotSupportedError(`App client settings are not supported for ${cloud}/userpools`)
return adapter.describeUserPoolClient(poolId, clientId)
}
async updateUserPoolClientOAuth(cloud: CloudProvider, poolId: string, clientId: string, input: UpdateUserPoolClientOAuthInput): Promise<UserPoolClientSettings> {
const adapter = this.requireAdapter(cloud, 'userpools')
if (!adapter.updateUserPoolClientOAuth) throw new NotSupportedError(`App client settings are not supported for ${cloud}/userpools`)
return adapter.updateUserPoolClientOAuth(poolId, clientId, input)
}
async listKubernetesNodegroups(cloud: CloudProvider, clusterId: string): Promise<KubernetesNodegroup[]> {
const adapter = this.requireAdapter(cloud, 'k8s')
if (!adapter.listKubernetesNodegroups) throw new NotSupportedError(`Nodegroups are not supported for ${cloud}/k8s`)
return adapter.listKubernetesNodegroups(clusterId)
}
async createKubernetesNodegroup(cloud: CloudProvider, clusterId: string, input: CreateKubernetesNodegroupInput): Promise<KubernetesNodegroup> {
const adapter = this.requireAdapter(cloud, 'k8s')
if (!adapter.createKubernetesNodegroup) throw new NotSupportedError(`Nodegroup creation is not supported for ${cloud}/k8s`)
return adapter.createKubernetesNodegroup(clusterId, input)
}
async deleteKubernetesNodegroup(cloud: CloudProvider, clusterId: string, nodegroupId: string): Promise<void> {
const adapter = this.requireAdapter(cloud, 'k8s')
if (!adapter.deleteKubernetesNodegroup) throw new NotSupportedError(`Nodegroup deletion is not supported for ${cloud}/k8s`)
await adapter.deleteKubernetesNodegroup(clusterId, nodegroupId)
}
async listKubernetesFargateProfiles(cloud: CloudProvider, clusterId: string): Promise<KubernetesFargateProfile[]> {
const adapter = this.requireAdapter(cloud, 'k8s')
if (!adapter.listKubernetesFargateProfiles) throw new NotSupportedError(`Fargate profiles are not supported for ${cloud}/k8s`)
return adapter.listKubernetesFargateProfiles(clusterId)
}
async createKubernetesFargateProfile(cloud: CloudProvider, clusterId: string, input: CreateKubernetesFargateProfileInput): Promise<KubernetesFargateProfile> {
const adapter = this.requireAdapter(cloud, 'k8s')
if (!adapter.createKubernetesFargateProfile) throw new NotSupportedError(`Fargate profile creation is not supported for ${cloud}/k8s`)
return adapter.createKubernetesFargateProfile(clusterId, input)
}
async deleteKubernetesFargateProfile(cloud: CloudProvider, clusterId: string, profileId: string): Promise<void> {
const adapter = this.requireAdapter(cloud, 'k8s')
if (!adapter.deleteKubernetesFargateProfile) throw new NotSupportedError(`Fargate profile deletion is not supported for ${cloud}/k8s`)
await adapter.deleteKubernetesFargateProfile(clusterId, profileId)
}
async clearEmailInbox(cloud: CloudProvider): Promise<void> {
const adapter = this.requireAdapter(cloud, 'email')
if (!adapter.clearEmailInbox) throw new NotSupportedError(`Inbox clearing is not supported for ${cloud}/email`)
await adapter.clearEmailInbox()
}
private appConfigAdapter(cloud: CloudProvider): CloudServiceAdapter {
const adapter = this.requireAdapter(cloud, 'configuration')
const method = adapter.listAppConfigEnvironments
if (!method) throw new NotSupportedError(`AppConfig resources are not supported for ${cloud}/configuration`)
return adapter
}
async listAppConfigEnvironments(cloud: CloudProvider, applicationId: string): Promise<AppConfigEnvironment[]> {
return this.appConfigAdapter(cloud).listAppConfigEnvironments!(applicationId)
}
async createAppConfigEnvironment(cloud: CloudProvider, applicationId: string, input: CreateResourceInput): Promise<AppConfigEnvironment> {
return this.appConfigAdapter(cloud).createAppConfigEnvironment!(applicationId, input)
}
async deleteAppConfigEnvironment(cloud: CloudProvider, applicationId: string, environmentId: string): Promise<void> {
await this.appConfigAdapter(cloud).deleteAppConfigEnvironment!(applicationId, environmentId)
}
async listAppConfigConfigurationProfiles(cloud: CloudProvider, applicationId: string): Promise<AppConfigConfigurationProfile[]> {
return this.appConfigAdapter(cloud).listAppConfigConfigurationProfiles!(applicationId)
}
async createAppConfigConfigurationProfile(cloud: CloudProvider, applicationId: string, input: CreateResourceInput): Promise<AppConfigConfigurationProfile> {
return this.appConfigAdapter(cloud).createAppConfigConfigurationProfile!(applicationId, input)
}
async deleteAppConfigConfigurationProfile(cloud: CloudProvider, applicationId: string, profileId: string): Promise<void> {
await this.appConfigAdapter(cloud).deleteAppConfigConfigurationProfile!(applicationId, profileId)
}
async listAppConfigHostedConfigurationVersions(cloud: CloudProvider, applicationId: string, profileId: string): Promise<AppConfigHostedConfigurationVersion[]> {
return this.appConfigAdapter(cloud).listAppConfigHostedConfigurationVersions!(applicationId, profileId)
}
async getAppConfigHostedConfigurationVersion(cloud: CloudProvider, applicationId: string, profileId: string, versionNumber: number): Promise<AppConfigHostedConfigurationVersion | null> {
return this.appConfigAdapter(cloud).getAppConfigHostedConfigurationVersion!(applicationId, profileId, versionNumber)
}
async createAppConfigHostedConfigurationVersion(cloud: CloudProvider, applicationId: string, profileId: string, input: CreateResourceInput): Promise<AppConfigHostedConfigurationVersion> {
return this.appConfigAdapter(cloud).createAppConfigHostedConfigurationVersion!(applicationId, profileId, input)
}
async deleteAppConfigHostedConfigurationVersion(cloud: CloudProvider, applicationId: string, profileId: string, versionNumber: number): Promise<void> {
await this.appConfigAdapter(cloud).deleteAppConfigHostedConfigurationVersion!(applicationId, profileId, versionNumber)
}
async listAppConfigDeploymentStrategies(cloud: CloudProvider): Promise<AppConfigDeploymentStrategy[]> {
return this.appConfigAdapter(cloud).listAppConfigDeploymentStrategies!()
}
async createAppConfigDeploymentStrategy(cloud: CloudProvider, input: CreateResourceInput): Promise<AppConfigDeploymentStrategy> {
return this.appConfigAdapter(cloud).createAppConfigDeploymentStrategy!(input)
}
async deleteAppConfigDeploymentStrategy(cloud: CloudProvider, strategyId: string): Promise<void> {
await this.appConfigAdapter(cloud).deleteAppConfigDeploymentStrategy!(strategyId)
}
async startAppConfigDeployment(cloud: CloudProvider, applicationId: string, environmentId: string, input: CreateResourceInput): Promise<AppConfigDeployment> {
return this.appConfigAdapter(cloud).startAppConfigDeployment!(applicationId, environmentId, input)
}
async getAppConfigDeployment(cloud: CloudProvider, applicationId: string, environmentId: string, deploymentNumber: number): Promise<AppConfigDeployment | null> {
return this.appConfigAdapter(cloud).getAppConfigDeployment!(applicationId, environmentId, deploymentNumber)
}
async startWorkflowExecution(cloud: CloudProvider, stateMachineArn: string, input: StartWorkflowExecutionInput): Promise<{executionArn: string; startDate: Date}> {
return this.workflowAdapter(cloud).startExecution(stateMachineArn, input)
}
async listWorkflowExecutions(cloud: CloudProvider, stateMachineArn: string): Promise<WorkflowExecutionSummary[]> {
return this.workflowAdapter(cloud).listExecutions(stateMachineArn)
}
async describeWorkflowExecution(cloud: CloudProvider, executionArn: string): Promise<WorkflowExecutionDetail> {
return this.workflowAdapter(cloud).describeExecution(executionArn)
}
async getWorkflowExecutionHistory(cloud: CloudProvider, executionArn: string, nextToken?: string): Promise<WorkflowExecutionHistoryPage> {
return this.workflowAdapter(cloud).getExecutionHistory(executionArn, nextToken)
}
async stopWorkflowExecution(cloud: CloudProvider, executionArn: string): Promise<void> {
await this.workflowAdapter(cloud).stopExecution(executionArn)
}
async startCodeBuild(cloud: CloudProvider, projectName: string, input: StartCodeBuildInput): Promise<CodeBuildBuild> {
return this.codeBuildAdapter(cloud).startBuild!(projectName, input)
}
async listCodeBuilds(cloud: CloudProvider, projectName: string): Promise<CodeBuildBuild[]> {
return this.codeBuildAdapter(cloud).listBuilds!(projectName)
}
async getCodeBuild(cloud: CloudProvider, id: string): Promise<CodeBuildBuild | null> {
return this.codeBuildAdapter(cloud).getBuild!(id)
}
async startPipeline(cloud: CloudProvider, id: string): Promise<{id: string}> {
return this.codePipelineAdapter(cloud).startPipeline!(id)
}
async listPipelineExecutions(cloud: CloudProvider, id: string): Promise<PipelineExecutionSummary[]> {
return this.codePipelineAdapter(cloud).listPipelineExecutions!(id)
}
async getPipelineState(cloud: CloudProvider, id: string): Promise<PipelineState> {
return this.codePipelineAdapter(cloud).getPipelineState!(id)
}
async startCodeDeployDeployment(cloud: CloudProvider, applicationName: string, deploymentGroupName: string, input: StartCodeDeployDeploymentInput): Promise<CodeDeployDeployment> {
return this.codeDeployAdapter(cloud).startCodeDeployDeployment!(applicationName, deploymentGroupName, input)
}
async listCodeDeployDeployments(cloud: CloudProvider, applicationName: string, deploymentGroupName: string): Promise<CodeDeployDeployment[]> {
return this.codeDeployAdapter(cloud).listCodeDeployDeployments!(applicationName, deploymentGroupName)
}
async getCodeDeployDeployment(cloud: CloudProvider, id: string): Promise<CodeDeployDeployment> {
return this.codeDeployAdapter(cloud).getCodeDeployDeployment!(id)
}
async stopCodeDeployDeployment(cloud: CloudProvider, id: string, rollback: boolean): Promise<void> {
await this.codeDeployAdapter(cloud).stopCodeDeployDeployment!(id, rollback)
}
private workflowAdapter(cloud: CloudProvider) {
const adapter = this.requireAdapter(cloud, 'workflows')
if (!('startExecution' in adapter) || !('listExecutions' in adapter) || !('describeExecution' in adapter) || !('getExecutionHistory' in adapter) || !('stopExecution' in adapter)) {
throw new NotSupportedError(`Workflow executions are not supported for ${cloud}`)
}
return adapter as import('../adapter-aws/AwsStepFunctionsAdapter').AwsStepFunctionsAdapter
}
private codeBuildAdapter(cloud: CloudProvider) {
const adapter = this.requireAdapter(cloud, 'codebuild')
if (!adapter.startBuild || !adapter.listBuilds || !adapter.getBuild) {
throw new NotSupportedError(`CodeBuild builds are not supported for ${cloud}`)
}
return adapter
}
private codePipelineAdapter(cloud: CloudProvider) {
const adapter = this.requireAdapter(cloud, 'codepipeline')
if (!adapter.startPipeline || !adapter.listPipelineExecutions || !adapter.getPipelineState) {
throw new NotSupportedError(`CodePipeline executions are not supported for ${cloud}`)
}
return adapter
}
private codeDeployAdapter(cloud: CloudProvider) {
const adapter = this.requireAdapter(cloud, 'codedeploy')
if (!adapter.startCodeDeployDeployment || !adapter.listCodeDeployDeployments || !adapter.getCodeDeployDeployment || !adapter.stopCodeDeployDeployment) {
throw new NotSupportedError(`CodeDeploy deployments are not supported for ${cloud}`)
}
return adapter
}
private requireAdapter(cloud: CloudProvider, service: CloudServiceType) {
const adapter = this.registry.get(cloud, service)
if (!adapter) throw new NotSupportedError(`No adapter registered for ${cloud}/${service}`)
return adapter
}
}
/** Keeps the promise that every coming_soon descriptor explains itself. */
function unavailableReason(
availability: CloudAvailability,
cloud: CloudProvider,
displayName: string,
): string | undefined {
if (availability === 'available') return undefined
return `No ${cloud.toUpperCase()} adapter is registered for ${displayName} yet.`
}