first commit
This commit is contained in:
@@ -0,0 +1,603 @@
|
||||
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,
|
||||
DatabaseSnapshot,
|
||||
KubernetesFargateProfile,
|
||||
KubernetesNodegroup,
|
||||
LogsInsightsQueryInput,
|
||||
LogsInsightsQueryResult,
|
||||
KmsDecryptInput,
|
||||
KmsDecryptResult,
|
||||
KmsEncryptInput,
|
||||
KmsEncryptResult,
|
||||
NoSqlItem,
|
||||
ResourceQuery,
|
||||
ServerlessInvokeResult,
|
||||
SqlConnectionInput,
|
||||
SqlDatabase,
|
||||
SqlQueryResult,
|
||||
SqlTable,
|
||||
RuntimeReachability,
|
||||
ServiceSchema,
|
||||
StorageObjectDownload,
|
||||
StorageObjectList,
|
||||
UpdateResourceInput,
|
||||
} from '../cloud-spi/types'
|
||||
import type {
|
||||
ChildCollection,
|
||||
ChildItem,
|
||||
CollectionPage,
|
||||
DocumentStoreAdapter,
|
||||
ItemStoreAdapter,
|
||||
PageQuery,
|
||||
} from '../cloud-spi/childCollections'
|
||||
import {NotSupportedError} from '../cloud-spi/errors'
|
||||
import {CloudAdapterRegistry} from '../registry/CloudAdapterRegistry'
|
||||
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,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 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 deleteResource(cloud: CloudProvider, service: CloudServiceType, id: string): Promise<void> {
|
||||
await this.requireAdapter(cloud, service).delete(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 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: string): 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)
|
||||
}
|
||||
|
||||
async listFlatItems(cloud: CloudProvider, service: CloudServiceType, resourceId: string, page: PageQuery): Promise<CollectionPage<ChildItem>> {
|
||||
return this.requireItems(cloud, service).listItems(resourceId, page)
|
||||
}
|
||||
|
||||
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: string): 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)
|
||||
}
|
||||
|
||||
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 listNoSqlItems(cloud: CloudProvider, resourceId: string): Promise<NoSqlItem[]> {
|
||||
const adapter = this.requireAdapter(cloud, 'nosql')
|
||||
if (!adapter.listNoSqlItems) throw new NotSupportedError(`Item listing is not supported for ${cloud}/nosql`)
|
||||
return adapter.listNoSqlItems(resourceId)
|
||||
}
|
||||
|
||||
async putNoSqlItem(cloud: CloudProvider, resourceId: string, document: Record<string, unknown>): Promise<NoSqlItem> {
|
||||
const adapter = this.requireAdapter(cloud, 'nosql')
|
||||
if (!adapter.putNoSqlItem) throw new NotSupportedError(`Item creation is not supported for ${cloud}/nosql`)
|
||||
return adapter.putNoSqlItem(resourceId, document)
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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.`
|
||||
}
|
||||
Reference in New Issue
Block a user