CI/CD - CodeBuild + CodeDeploy + CodePipeline

This commit is contained in:
Zakaria
2026-10-01 09:18:04 -04:00
parent aa02cdb7f6
commit 7f0d707578
41 changed files with 1817 additions and 78 deletions
@@ -27,3 +27,25 @@ describe('CloudProxyService API Gateway CORS', () => {
}}])
})
})
describe('CloudProxyService resource lifecycle', () => {
test('forwards lifecycle actions to the registered adapter', async () => {
const calls: string[] = []
const adapter: CloudServiceAdapter = {
cloud: 'aws', service: 'apigateway', schema: awsApiGatewaySchema,
list: async () => [], get: async () => null, create: async () => ({
id: 'api', name: 'api', cloud: 'aws', service: 'apigateway', type: 'rest-api', region: null, createdAt: null, metadata: {},
}), delete: async () => {},
start: async (id) => { calls.push(`start:${id}`) },
stop: async (id) => { calls.push(`stop:${id}`) },
reboot: async (id) => { calls.push(`reboot:${id}`) },
}
const service = new CloudProxyService(new CloudAdapterRegistry([adapter]))
await service.startResource('aws', 'apigateway', 'api-123')
await service.stopResource('aws', 'apigateway', 'api-123')
await service.rebootResource('aws', 'apigateway', 'api-123')
expect(calls).toEqual(['start:api-123', 'stop:api-123', 'reboot:api-123'])
})
})
@@ -86,6 +86,9 @@ import type {
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'
@@ -482,6 +485,24 @@ export class CloudProxyService {
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`)
@@ -852,6 +873,46 @@ export class CloudProxyService {
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)) {
@@ -860,6 +921,30 @@ export class CloudProxyService {
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}`)