diff --git a/cdk/resources/WebAppCD.ts b/cdk/resources/WebAppCD.ts index ea3d3ab9..1a48fe6a 100644 --- a/cdk/resources/WebAppCD.ts +++ b/cdk/resources/WebAppCD.ts @@ -2,36 +2,40 @@ import * as CloudFormation from '@aws-cdk/core' import * as IAM from '@aws-cdk/aws-iam' import * as CodeBuild from '@aws-cdk/aws-codebuild' import * as CodePipeline from '@aws-cdk/aws-codepipeline' -import * as SSM from '@aws-cdk/aws-ssm' import * as S3 from '@aws-cdk/aws-s3' +export const BuildActionCodeBuild = { + category: 'Build', + owner: 'AWS', + version: '1', + provider: 'CodeBuild', +} + /** * This sets up the continuous delivery for a web-app */ export class WebAppCD extends CloudFormation.Construct { + public readonly codeBuildProject: CodeBuild.CfnProject public constructor( parent: CloudFormation.Stack, id: string, { bifravstStackId, - bifravstAWS, - webApp, - githubToken, buildSpec, description, + sourceCodeActions, }: { - bifravstAWS: { - owner: string - repo: string - branch: string - } - webApp: { - owner: string - repo: string - branch: string + sourceCodeActions: { + bifravst: { + action: CodePipeline.CfnPipeline.ActionDeclarationProperty + outputName: string + } + webApp: { + action: CodePipeline.CfnPipeline.ActionDeclarationProperty + outputName: string + } } bifravstStackId: string - githubToken: SSM.IStringParameter buildSpec: string description: string }, @@ -52,7 +56,7 @@ export class WebAppCD extends CloudFormation.Construct { }, }) - const project = new CodeBuild.CfnProject(this, 'CodeBuildProject', { + this.codeBuildProject = new CodeBuild.CfnProject(this, 'CodeBuildProject', { name: id, description, source: { @@ -75,7 +79,7 @@ export class WebAppCD extends CloudFormation.Construct { ], }, }) - project.node.addDependency(codeBuildRole) + this.codeBuildProject.node.addDependency(codeBuildRole) const bucket = new S3.Bucket(this, 'bucket', { removalPolicy: CloudFormation.RemovalPolicy.RETAIN, @@ -87,7 +91,7 @@ export class WebAppCD extends CloudFormation.Construct { controlCodeBuild: new IAM.PolicyDocument({ statements: [ new IAM.PolicyStatement({ - resources: [project.attrArn], + resources: [this.codeBuildProject.attrArn], actions: ['codebuild:*'], }), ], @@ -114,46 +118,8 @@ export class WebAppCD extends CloudFormation.Construct { { name: 'Source', actions: [ - { - name: 'BifravstAWSSourceCode', - actionTypeId: { - category: 'Source', - owner: 'ThirdParty', - version: '1', - provider: 'GitHub', - }, - outputArtifacts: [ - { - name: 'BifravstAWS', - }, - ], - configuration: { - Branch: bifravstAWS.branch, - Owner: bifravstAWS.owner, - Repo: bifravstAWS.repo, - OAuthToken: githubToken.stringValue, - }, - }, - { - name: 'WebAppSourceCode', - actionTypeId: { - category: 'Source', - owner: 'ThirdParty', - version: '1', - provider: 'GitHub', - }, - outputArtifacts: [ - { - name: 'WebApp', - }, - ], - configuration: { - Branch: webApp.branch, - Owner: webApp.owner, - Repo: webApp.repo, - OAuthToken: githubToken.stringValue, - }, - }, + sourceCodeActions.bifravst.action, + sourceCodeActions.webApp.action, ], }, { @@ -161,16 +127,18 @@ export class WebAppCD extends CloudFormation.Construct { actions: [ { name: 'DeployWebApp', - inputArtifacts: [{ name: 'BifravstAWS' }, { name: 'WebApp' }], - actionTypeId: { - category: 'Build', - owner: 'AWS', - version: '1', - provider: 'CodeBuild', - }, + inputArtifacts: [ + { + name: sourceCodeActions.bifravst.outputName, + }, + { + name: sourceCodeActions.webApp.outputName, + }, + ], + actionTypeId: BuildActionCodeBuild, configuration: { - ProjectName: project.name, - PrimarySource: 'BifravstAWS', + ProjectName: this.codeBuildProject.name, + PrimarySource: sourceCodeActions.bifravst.outputName, }, outputArtifacts: [ { @@ -192,12 +160,12 @@ export class WebAppCD extends CloudFormation.Construct { filters: [ { jsonPath: '$.ref', - matchEquals: `refs/heads/${webApp.branch}`, + matchEquals: `refs/heads/${sourceCodeActions.webApp.action.configuration.Branch}`, }, ], authentication: 'GITHUB_HMAC', authenticationConfiguration: { - secretToken: githubToken.stringValue, + secretToken: sourceCodeActions.webApp.action.configuration.OAuthToken, }, registerWithThirdParty: false, }) diff --git a/cdk/stacks/ContinuousDeployment.ts b/cdk/stacks/ContinuousDeployment.ts index 832be039..24a48138 100644 --- a/cdk/stacks/ContinuousDeployment.ts +++ b/cdk/stacks/ContinuousDeployment.ts @@ -4,7 +4,7 @@ import * as CodeBuild from '@aws-cdk/aws-codebuild' import * as CodePipeline from '@aws-cdk/aws-codepipeline' import * as SSM from '@aws-cdk/aws-ssm' import * as S3 from '@aws-cdk/aws-s3' -import { WebAppCD } from '../resources/WebAppCD' +import { BuildActionCodeBuild, WebAppCD } from '../resources/WebAppCD' /** * This is the CloudFormation stack sets up the continuous deployment of the project. @@ -80,13 +80,117 @@ export class ContinuousDeploymentStack extends CloudFormation.Stack { }) project.node.addDependency(codeBuildRole) + const githubToken = SSM.StringParameter.fromStringParameterAttributes( + this, + 'ghtoken', + { + parameterName: '/codebuild/github-token', + version: 1, + }, + ) + + const sourceCodeAction = ({ + name, + outputName, + Branch, + Owner, + Repo, + githubToken, + }: { + name: string + outputName: string + Branch: string + Owner: string + Repo: string + githubToken: SSM.IStringParameter + }) => ({ + outputName, + action: { + name, + actionTypeId: { + category: 'Source', + owner: 'ThirdParty', + version: '1', + provider: 'GitHub', + }, + outputArtifacts: [ + { + name: outputName, + }, + ], + configuration: { + Branch, + Owner, + Repo, + OAuthToken: githubToken.stringValue, + }, + }, + }) + + const bifravstSourceCodeAction = sourceCodeAction({ + name: 'BifravstAWSSourceCode', + outputName: 'BifravstAWS', + Branch: bifravstAWS.branch, + Owner: bifravstAWS.owner, + Repo: bifravstAWS.repo, + githubToken, + }) + + const webAppSourceCodeAction = sourceCodeAction({ + name: 'WebAppSourceCode', + outputName: 'WebApp', + Branch: webApp.branch, + Owner: webApp.owner, + Repo: webApp.repo, + githubToken, + }) + + const deviceUISourceCodeAction = sourceCodeAction({ + name: 'DeviceUISourceCode', + outputName: 'DeviceUI', + Branch: deviceUI.branch, + Owner: deviceUI.owner, + Repo: deviceUI.repo, + githubToken, + }) + + // Sets up the continuous deployment for the web app + const webAppCd = new WebAppCD(this, `${id}-webAppCD`, { + description: 'Continuously deploys the Bifravst Web App', + sourceCodeActions: { + bifravst: bifravstSourceCodeAction, + webApp: webAppSourceCodeAction, + }, + bifravstStackId, + buildSpec: 'continuous-deployment-web-app.yml', + }) + + // Sets up the continuous deployment for the device UI + const deviceUICD = new WebAppCD(this, `${id}-deviceUICD`, { + description: 'Continuously deploys the Bifravst Device UI', + sourceCodeActions: { + bifravst: bifravstSourceCodeAction, + webApp: deviceUISourceCodeAction, + }, + bifravstStackId, + buildSpec: 'continuous-deployment-device-ui-app.yml', + }) + + // Set up the continuous deployment for Bifravst. + // This will also run the deployment of the WebApp and DeviceUI after a deploy + // (in case some outputs have changed and need to be made available to the apps). + const codePipelineRole = new IAM.Role(this, 'CodePipelineRole', { assumedBy: new IAM.ServicePrincipal('codepipeline.amazonaws.com'), inlinePolicies: { controlCodeBuild: new IAM.PolicyDocument({ statements: [ new IAM.PolicyStatement({ - resources: [project.attrArn], + resources: [ + project.attrArn, + webAppCd.codeBuildProject.attrArn, + deviceUICD.codeBuildProject.attrArn, + ], actions: ['codebuild:*'], }), ], @@ -102,15 +206,6 @@ export class ContinuousDeploymentStack extends CloudFormation.Stack { }, }) - const githubToken = SSM.StringParameter.fromStringParameterAttributes( - this, - 'ghtoken', - { - parameterName: '/codebuild/github-token', - version: 1, - }, - ) - const pipeline = new CodePipeline.CfnPipeline(this, 'CodePipeline', { roleArn: codePipelineRole.roleArn, artifactStore: { @@ -121,49 +216,76 @@ export class ContinuousDeploymentStack extends CloudFormation.Stack { stages: [ { name: 'Source', + actions: [ + bifravstSourceCodeAction.action, + webAppSourceCodeAction.action, + deviceUISourceCodeAction.action, + ], + }, + { + name: 'Deploy', actions: [ { - name: 'BifravstAWSSourceCode', - actionTypeId: { - category: 'Source', - owner: 'ThirdParty', - version: '1', - provider: 'GitHub', + name: 'DeployBifravst', + inputArtifacts: [ + { + name: bifravstSourceCodeAction.outputName, + }, + ], + actionTypeId: BuildActionCodeBuild, + configuration: { + ProjectName: project.name, }, outputArtifacts: [ { - name: 'BifravstAWS', + name: 'BifravstBuildId', }, ], + runOrder: 1, + }, + { + name: 'DeployWebApp', + inputArtifacts: [ + { + name: bifravstSourceCodeAction.outputName, + }, + { + name: webAppSourceCodeAction.outputName, + }, + ], + actionTypeId: BuildActionCodeBuild, configuration: { - Branch: bifravstAWS.branch, - Owner: bifravstAWS.owner, - Repo: bifravstAWS.repo, - OAuthToken: githubToken.stringValue, + ProjectName: webAppCd.codeBuildProject.name, + PrimarySource: bifravstSourceCodeAction.outputName, }, + outputArtifacts: [ + { + name: 'WebAppBuildId', + }, + ], + runOrder: 2, }, - ], - }, - { - name: 'Deploy', - actions: [ { - name: 'DeployBifravst', - inputArtifacts: [{ name: 'BifravstAWS' }], - actionTypeId: { - category: 'Build', - owner: 'AWS', - version: '1', - provider: 'CodeBuild', - }, + name: 'DeployDeviceUI', + inputArtifacts: [ + { + name: bifravstSourceCodeAction.outputName, + }, + { + name: deviceUISourceCodeAction.outputName, + }, + ], + actionTypeId: BuildActionCodeBuild, configuration: { - ProjectName: project.name, + ProjectName: deviceUICD.codeBuildProject.name, + PrimarySource: bifravstSourceCodeAction.outputName, }, outputArtifacts: [ { - name: 'BuildId', + name: 'DeviceUIBuildId', }, ], + runOrder: 2, }, ], }, @@ -188,25 +310,5 @@ export class ContinuousDeploymentStack extends CloudFormation.Stack { }, registerWithThirdParty: false, }) - - // Sets up the continuous deployment for the web app - new WebAppCD(this, `${id}-webAppCD`, { - description: 'Continuously deploys the Bifravst Web App', - bifravstAWS, - webApp, - githubToken, - bifravstStackId, - buildSpec: 'continuous-deployment-web-app.yml', - }) - - // Sets up the continuous deployment for the device UI - new WebAppCD(this, `${id}-deviceUICD`, { - description: 'Continuously deploys the Bifravst Device UI', - bifravstAWS, - webApp: deviceUI, - githubToken, - bifravstStackId, - buildSpec: 'continuous-deployment-device-ui-app.yml', - }) } } diff --git a/continuous-deployment-device-ui-app.yml b/continuous-deployment-device-ui-app.yml index a70a72b5..b6d46dd8 100644 --- a/continuous-deployment-device-ui-app.yml +++ b/continuous-deployment-device-ui-app.yml @@ -9,9 +9,9 @@ phases: - npx tsc build: commands: - - ./cli.js react-config > $CODEBUILD_SRC_DIR_WebApp/.env.production.local - - cat $CODEBUILD_SRC_DIR_WebApp/.env.production.local - - export $(cat $CODEBUILD_SRC_DIR_WebApp/.env.production.local | xargs) - - cd $CODEBUILD_SRC_DIR_WebApp/; npm ci --no-audit; npm run build; - - aws s3 cp $CODEBUILD_SRC_DIR_WebApp/build s3://$REACT_APP_DEVICE_UI_BUCKET_NAME --recursive --metadata-directive REPLACE --cache-control 'public,max-age=600' --expires '' + - ./cli.js react-config > $CODEBUILD_SRC_DIR_DeviceUI/.env.production.local + - cat $CODEBUILD_SRC_DIR_DeviceUI/.env.production.local + - export $(cat $CODEBUILD_SRC_DIR_DeviceUI/.env.production.local | xargs) + - cd $CODEBUILD_SRC_DIR_DeviceUI/; npm ci --no-audit; npm run build; + - aws s3 cp $CODEBUILD_SRC_DIR_DeviceUI/build s3://$REACT_APP_DEVICE_UI_BUCKET_NAME --recursive --metadata-directive REPLACE --cache-control 'public,max-age=600' --expires '' - aws cloudfront create-invalidation --distribution-id $REACT_APP_CLOUDFRONT_DISTRIBUTION_ID_DEVICE_UI --paths /,/index.html