@@ -5,7 +5,7 @@ import { RemovalPolicy, ResourceEnvironment } from 'aws-cdk-lib';
55import { Key } from 'aws-cdk-lib/aws-kms' ;
66import { ILogGroup , LogGroup } from 'aws-cdk-lib/aws-logs' ;
77import { Bucket , BucketEncryption , IBucket } from 'aws-cdk-lib/aws-s3' ;
8- import { CodeBuildStep , CodePipeline } from 'aws-cdk-lib/pipelines' ;
8+ import { CodeBuildStep , CodePipeline , StageDeployment } from 'aws-cdk-lib/pipelines' ;
99import { Construct } from 'constructs' ;
1010import { SparkEmrCICDPipelineProps } from './spark-emr-cicd-pipeline-props' ;
1111import { AccessLogsBucket } from '../../../storage' ;
@@ -18,6 +18,15 @@ import {
1818} from '../../../utils' ;
1919import { DEFAULT_SPARK_IMAGE , SparkImage } from '../emr-releases' ;
2020
21+ /**
22+ * User defined CI/CD environment stages
23+ */
24+ interface CICDEnvironment {
25+ stageName : string ;
26+ account : string ;
27+ region : string ;
28+ triggerIntegTest ?: boolean ;
29+ }
2130
2231/**
2332 * A CICD Pipeline to test and deploy a Spark application on Amazon EMR in cross-account environments using CDK Pipelines.
@@ -211,36 +220,89 @@ export class SparkEmrCICDPipeline extends TrackedConstruct {
211220 } ,
212221 } ) ;
213222
214- // Create the Staging stage of the CICD
215- const staging = new ApplicationStage ( this , 'Staging' , {
216- env : this . getAccountFromContext ( 'staging' ) ,
223+ try {
224+ const environments = this . getUserDefinedEnvironmentsFromContext ( ) ;
225+
226+ for ( const e of environments ) {
227+ this . attachStageToPipeline ( e . stageName . toUpperCase ( ) , {
228+ account : e . account ,
229+ region : e . region ,
230+ } , e . triggerIntegTest || false , buildStage , props ) ;
231+ }
232+ } catch ( e ) {
233+ [ , , this . integrationTestStage ] = this . attachStageToPipeline ( 'Staging' , this . getAccountFromContext ( 'staging' ) , true , buildStage , props ) ;
234+ this . attachStageToPipeline ( 'Prod' , this . getAccountFromContext ( 'prod' ) , false , buildStage , props ) ;
235+ }
236+
237+ // // Create the Staging stage of the CICD
238+ // const staging = new ApplicationStage(this, 'Staging', {
239+ // env: this.getAccountFromContext('staging'),
240+ // applicationStackFactory: props.applicationStackFactory,
241+ // outputsEnv: props.integTestEnv,
242+ // stage: CICDStage.STAGING,
243+ // });
244+ // const stagingDeployment = this.pipeline.addStage(staging);
245+
246+ // if (props.integTestScript) {
247+ // // Extract the path and script name from the integration tests script path
248+ // const [integPath, integScript] = SparkEmrCICDPipeline.extractPath(props.integTestScript);
249+
250+ // this.integrationTestStage = new CodeBuildStep('IntegrationTests', {
251+ // input: buildStage.addOutputDirectory(integPath),
252+ // commands: [`chmod +x ${integScript} && ./${integScript}`],
253+ // envFromCfnOutputs: staging.stackOutputsEnv,
254+ // rolePolicyStatements: props.integTestPermissions,
255+ // });
256+ // // Add a post step to run the integration tests
257+ // stagingDeployment.addPost(this.integrationTestStage);
258+ // }
259+
260+ // // Create the Production stage of the CICD
261+ // this.pipeline.addStage(new ApplicationStage(this, 'Production', {
262+ // env: this.getAccountFromContext('prod'),
263+ // applicationStackFactory: props.applicationStackFactory,
264+ // stage: CICDStage.PROD,
265+ // }));
266+
267+ }
268+
269+ /**
270+ * Attaches the given stage to the pipeline
271+ * @param stageName
272+ * @param resourceEnvironment
273+ * @param attachIntegTest
274+ * @param buildStage
275+ * @param props
276+ * @returns {Array } contains array of ApplicationStage, StageDeployment, and optional integration test step
277+ */
278+ private attachStageToPipeline ( stageName : string , resourceEnvironment : ResourceEnvironment
279+ , attachIntegTest : boolean , buildStage : CodeBuildStep
280+ , props : SparkEmrCICDPipelineProps ) : [ ApplicationStage , StageDeployment , CodeBuildStep | undefined ] {
281+ const applicationStage = new ApplicationStage ( this , stageName , {
282+ env : resourceEnvironment ,
217283 applicationStackFactory : props . applicationStackFactory ,
218- outputsEnv : props . integTestEnv ,
219- stage : CICDStage . STAGING ,
284+ outputsEnv : ( attachIntegTest && props . integTestScript ) ? props . integTestEnv : undefined ,
285+ stage : CICDStage . of ( stageName . toUpperCase ( ) ) ,
220286 } ) ;
221- const stagingDeployment = this . pipeline . addStage ( staging ) ;
287+ const stageDeployment = this . pipeline . addStage ( applicationStage ) ;
222288
223- if ( props . integTestScript ) {
289+ let integrationTestStage :CodeBuildStep | undefined = undefined ;
290+
291+ if ( attachIntegTest && props . integTestScript ) {
224292 // Extract the path and script name from the integration tests script path
225293 const [ integPath , integScript ] = SparkEmrCICDPipeline . extractPath ( props . integTestScript ) ;
226294
227- this . integrationTestStage = new CodeBuildStep ( ' IntegrationTests' , {
295+ integrationTestStage = new CodeBuildStep ( ` ${ stageName } - IntegrationTests` , {
228296 input : buildStage . addOutputDirectory ( integPath ) ,
229297 commands : [ `chmod +x ${ integScript } && ./${ integScript } ` ] ,
230- envFromCfnOutputs : staging . stackOutputsEnv ,
298+ envFromCfnOutputs : applicationStage . stackOutputsEnv ,
231299 rolePolicyStatements : props . integTestPermissions ,
232300 } ) ;
233301 // Add a post step to run the integration tests
234- stagingDeployment . addPost ( this . integrationTestStage ) ;
302+ stageDeployment . addPost ( integrationTestStage ) ;
235303 }
236304
237- // Create the Production stage of the CICD
238- this . pipeline . addStage ( new ApplicationStage ( this , 'Production' , {
239- env : this . getAccountFromContext ( 'prod' ) ,
240- applicationStackFactory : props . applicationStackFactory ,
241- stage : CICDStage . PROD ,
242- } ) ) ;
243-
305+ return [ applicationStage , stageDeployment , integrationTestStage ] ;
244306 }
245307
246308 /**
@@ -251,4 +313,14 @@ export class SparkEmrCICDPipeline extends TrackedConstruct {
251313 if ( ! account ) throw new Error ( `Missing context variable ${ name } ` ) ;
252314 return account ;
253315 }
316+
317+ private getUserDefinedEnvironmentsFromContext ( ) : CICDEnvironment [ ] {
318+ const environments = this . node . tryGetContext ( 'environments' ) as CICDEnvironment [ ] ;
319+
320+ if ( ! environments ) {
321+ throw new Error ( 'Missing context variable environments' ) ;
322+ }
323+
324+ return environments ;
325+ }
254326}
0 commit comments