From 358e6565eb45a970d894f9ce134384b0e8459a76 Mon Sep 17 00:00:00 2001 From: Duong Pham Date: Tue, 1 Sep 2020 20:33:05 +0700 Subject: [PATCH] chore: #2519 deploy pipeline for web-components --- packages/web-components/package.json | 6 +- .../scripts/release-serverless.js | 16 +- .../web-components/serverless.example.yml | 200 ------------------ packages/web-components/serverless.yml | 127 +++++++++++ scripts/release/fetch-artifact.js | 12 +- scripts/release/upload-artifact.js | 41 ++-- scripts/release/utils.js | 66 +++--- 7 files changed, 197 insertions(+), 271 deletions(-) delete mode 100644 packages/web-components/serverless.example.yml create mode 100644 packages/web-components/serverless.yml diff --git a/packages/web-components/package.json b/packages/web-components/package.json index a6555299bd..9af9895aa3 100644 --- a/packages/web-components/package.json +++ b/packages/web-components/package.json @@ -73,8 +73,10 @@ "lint:fix": "eslint --cache --ext=ts,tsx,js src --fix", "svelte:check": "npx svelte-check", "fetch-config": "yarn config-manager --namespace cloud --entity web-components --name local --mode fetch", - "release:development": "node ./scripts/release-serverless.js --stage dev", - "release:production": "node ./scripts/release-serverless.js --stage prod", + "release:development": "serverless deploy --stage dev", + "release:production": "serverless deploy --stage prod", + "release:serverless:development": "node ./scripts/release-serverless.js --stage dev", + "release:serverless:production": "node ./scripts/release-serverless.js --stage prod", "remove:development": "serverless remove --stage dev", "remove:production": "serverless remove --stage dev", "snyk-protect": "snyk protect", diff --git a/packages/web-components/scripts/release-serverless.js b/packages/web-components/scripts/release-serverless.js index 51c9a1ac55..6c39a125fd 100644 --- a/packages/web-components/scripts/release-serverless.js +++ b/packages/web-components/scripts/release-serverless.js @@ -1,5 +1,6 @@ const yargs = require('yargs') const { spawn } = require('child_process') +const { runCommand } = require('../../../scripts/release/utils') const stage = yargs.argv.stage const name = yargs.argv.name @@ -22,20 +23,7 @@ const listServerless = [ const deployServerlessList = () => { // This will run when run particular widget by yarn release:development --name in web-components folder if (name) { - const deploy = spawn('serverless', ['deploy', '--config', `src/${name}/server/serverless.yml`, '--stage', stage]) - - deploy.stderr.on('data', function(data) { - console.error('stderr: ' + data.toString()) - }) - - deploy.on('exit', function(code) { - console.info(`Deploying ${name} exited with code ${code.toString()}`) - }) - - deploy.on('error', function(err) { - console.error(`An error happened \n${err}`) - process.exit(1) - }) + runCommand('serverless', ['deploy', '--config', `src/${name}/server/serverless.yml`, '--stage', stage]) return } diff --git a/packages/web-components/serverless.example.yml b/packages/web-components/serverless.example.yml deleted file mode 100644 index 5b22d5f926..0000000000 --- a/packages/web-components/serverless.example.yml +++ /dev/null @@ -1,200 +0,0 @@ -service: cloud-web-components -plugins: - - serverless-single-page-app-plugin - - serverless-webpack - - serverless-offline - - serverless-plugin-ifelse - - serverless-s3-deploy - - serverless-deployment-bucket - - serverless-s3-remover - - -custom: - s3WebAppBucket: cloud-web-components-${opt:stage, 'dev'} - s3CloudFormBucket: cloud-deployment-cloudform-templates-${opt:stage, 'dev'} - env: ${file(./config.json)} - remover: - buckets: - - ${self:custom.s3WebAppBucket} - webpack: - webpackConfig: 'webpack.config.js' - includeModules: true - packager: 'yarn' - packagerOptions: - noFrozenLockFile: false - excludeFiles: src/**/*.test.ts - keepOutputDirectory: true - allowedHeaders: - - Content-Type - - X-Amz-Date - - Authorization - - X-Api-Key - - X-Amz-Security-Token - - X-Amz-User-Agent - - reapit-customer - -provider: - name: aws - runtime: nodejs12.x - stage: ${opt:stage, 'dev'} - region: eu-west-2 - apiGateway: - restApiId: ${self:custom.env.GATEWAY_REST_API_ID} - restApiRootResourceId: ${self:custom.env.GATEWAY_REST_API_ROOT_RESOURCE_ID} - deploymentBucket: - name: ${self:custom.s3CloudFormBucket} - - environment: - NODE_ENV: ${self:custom.env.NODE_ENV} - APP_ENV: ${self:custom.env.APP_ENV} - PLATFORM_API_BASE_URL: ${self:custom.env.PLATFORM_API_BASE_URL} - COGNITO_CLIENT_SECRET_SEARCH_WIDGET: ${self:custom.env.COGNITO_CLIENT_SECRET_SEARCH_WIDGET} - COGNITO_CLIENT_ID_SEARCH_WIDGET: ${self:custom.env.COGNITO_CLIENT_ID_SEARCH_WIDGET} - COGNITO_OAUTH_URL: ${self:custom.env.COGNITO_OAUTH_URL} -package: - individually: true - include: - - dist/** - exclude: - - package.json - - node_modules/** - - error.log - - info.log - - jest.config.js - - serverless.yml - - src/** - - tsconfig.json - - yml-helpers.js - - public/** - -functions: - searchWidgetHandler: - handler: src/search-widget/server/core/index.searchWidgetHandler - events: - - http: - path: properties - method: get - cors: - origin: '*' - headers: ${self:custom.allowedHeaders} - allowCredentials: false - private: true - request: - parameters: - headers: - api-version: false - Authorization: false - reapit-customer: false - - http: - path: properties/{id} - method: get - cors: - origin: '*' - headers: ${self:custom.allowedHeaders} - allowCredentials: false - private: true - request: - parameters: - headers: - api-version: false - Authorization: false - reapit-customer: false - - http: - path: propertyImages - method: get - cors: - origin: '*' - headers: ${self:custom.allowedHeaders} - allowCredentials: false - private: true - request: - parameters: - headers: - api-version: false - Authorization: false - reapit-customer: false - -resources: - Resources: - ## Specifying the S3 Bucket - WebAppS3Bucket: - Type: AWS::S3::Bucket - Properties: - BucketName: ${self:custom.s3WebAppBucket} - AccessControl: PublicRead - WebsiteConfiguration: - IndexDocument: index.html - ErrorDocument: error.html - - ## Specifying the policies to make sure all files inside the Bucket are available to CloudFront - WebAppS3BucketPolicy: - Type: AWS::S3::BucketPolicy - Properties: - Bucket: - Ref: WebAppS3Bucket - PolicyDocument: - Statement: - - Sid: PublicReadGetObject - Effect: Allow - Principal: '*' - Action: - - s3:GetObject - Resource: - Fn::Join: ['', ['arn:aws:s3:::', { 'Ref': 'WebAppS3Bucket' }, '/*']] - - ## Specifying the CloudFront Distribution to server your Web Application - WebAppCloudFrontDistribution: - Type: AWS::CloudFront::Distribution - Properties: - DistributionConfig: - Origins: - - DomainName: - Fn::Join: ['', [{ 'Ref': 'WebAppS3Bucket' }, '.s3.amazonaws.com']] - Id: S3-${self:custom.s3WebAppBucket} - CustomOriginConfig: - HTTPPort: 80 - HTTPSPort: 443 - OriginProtocolPolicy: https-only - ## In case you want to restrict the bucket access use S3OriginConfig and remove CustomOriginConfig - # S3OriginConfig: - # OriginAccessIdentity: origin-access-identity/cloudfront/E127EXAMPLE51Z - Enabled: 'true' - ## Uncomment the following section in case you are using a custom domain - # Aliases: - # If: '"${opt:stage, 'dev'}" == "dev"' - # Set: - # - dev.web-components.reapit.cloud - # ElseSet: - # - web-components.reapit.cloud - DefaultRootObject: index.html - CustomErrorResponses: - - ErrorCode: 404 - ResponseCode: 200 - ResponsePagePath: /index.html - - ErrorCode: 403 - ResponseCode: 200 - ResponsePagePath: /index.html - - ErrorCode: 400 - ResponseCode: 200 - ResponsePagePath: /index.html - DefaultCacheBehavior: - AllowedMethods: - - GET - - HEAD - - OPTIONS - TargetOriginId: S3-${self:custom.s3WebAppBucket} - ForwardedValues: - QueryString: 'false' - Cookies: - Forward: none - ViewerProtocolPolicy: redirect-to-https - ViewerCertificate: - CloudFrontDefaultCertificate: 'true' - # Logging: - # IncludeCookies: 'false' - # Bucket: mylogs.s3.amazonaws.com - # Prefix: myprefix - Outputs: - WebAppCloudFrontDistributionOutput: - Value: - 'Fn::GetAtt': [WebAppCloudFrontDistribution, DomainName] diff --git a/packages/web-components/serverless.yml b/packages/web-components/serverless.yml new file mode 100644 index 0000000000..b9404dfab5 --- /dev/null +++ b/packages/web-components/serverless.yml @@ -0,0 +1,127 @@ +# https://www.npmjs.com/package/serverless-single-page-app-plugin +service: cloud-web-components + +plugins: + - serverless-single-page-app-plugin + - serverless-plugin-ifelse + - serverless-deployment-bucket + - serverless-s3-remover + - serverless-s3-deploy + +custom: + s3WebAppBucket: cloud-web-components-web-app-${opt:stage, 'dev'} + s3CloudFormBucket: cloud-deployment-cloudform-templates-${opt:stage, 'dev'} + remover: + buckets: + - ${self:custom.s3WebAppBucket} + assets: + auto: true + targets: + - bucket: ${self:custom.s3WebAppBucket} + files: + - source: ./public/dist/ + globs: '**/*' + +provider: + name: aws + runtime: nodejs12.x + stage: ${opt:stage, 'dev'} + region: eu-west-2 + deploymentBucket: + name: ${self:custom.s3CloudFormBucket} + +resources: + Resources: + ## Specifying the S3 Bucket + WebAppS3Bucket: + Type: AWS::S3::Bucket + Properties: + BucketName: ${self:custom.s3WebAppBucket} + AccessControl: PublicRead + WebsiteConfiguration: + IndexDocument: index.html + ErrorDocument: error.html + + ## Specifying the policies to make sure all files inside the Bucket are available to CloudFront + WebAppS3BucketPolicy: + Type: AWS::S3::BucketPolicy + Properties: + Bucket: + Ref: WebAppS3Bucket + PolicyDocument: + Statement: + - Sid: PublicReadGetObject + Effect: Allow + Principal: "*" + Action: + - s3:GetObject + Resource: + Fn::Join: [ + "", [ + "arn:aws:s3:::", + { "Ref": "WebAppS3Bucket" }, + "/*" + ] + ] + + ## Specifying the CloudFront Distribution to server your Web Application + WebAppCloudFrontDistribution: + Type: AWS::CloudFront::Distribution + Properties: + DistributionConfig: + Origins: + - DomainName: + Fn::Join: [ + "", [ + { "Ref": "WebAppS3Bucket" }, + ".s3.amazonaws.com" + ] + ] + Id: S3-${self:custom.s3WebAppBucket} + CustomOriginConfig: + HTTPPort: 80 + HTTPSPort: 443 + OriginProtocolPolicy: https-only + ## In case you want to restrict the bucket access use S3OriginConfig and remove CustomOriginConfig + # S3OriginConfig: + # OriginAccessIdentity: origin-access-identity/cloudfront/E127EXAMPLE51Z + Enabled: 'true' + ## Uncomment the following section in case you are using a custom domain + # Aliases: + # If: '"${opt:stage, 'dev'}" == "dev"' + # Set: + # - dev.admin-portal.reapit.cloud + # ElseSet: + # - admin-portal.reapit.cloud + DefaultRootObject: index.html + CustomErrorResponses: + - ErrorCode: 404 + ResponseCode: 200 + ResponsePagePath: /index.html + - ErrorCode: 403 + ResponseCode: 200 + ResponsePagePath: /index.html + - ErrorCode: 400 + ResponseCode: 200 + ResponsePagePath: /index.html + DefaultCacheBehavior: + AllowedMethods: + - GET + - HEAD + - OPTIONS + TargetOriginId: S3-${self:custom.s3WebAppBucket} + ForwardedValues: + QueryString: 'false' + Cookies: + Forward: none + ViewerProtocolPolicy: redirect-to-https + ViewerCertificate: + CloudFrontDefaultCertificate: 'true' + # Logging: + # IncludeCookies: 'false' + # Bucket: mylogs.s3.amazonaws.com + # Prefix: myprefix + Outputs: + WebAppCloudFrontDistributionOutput: + Value: + 'Fn::GetAtt': [ WebAppCloudFrontDistribution, DomainName ] diff --git a/scripts/release/fetch-artifact.js b/scripts/release/fetch-artifact.js index ac3b1a4e9a..dab04caa81 100644 --- a/scripts/release/fetch-artifact.js +++ b/scripts/release/fetch-artifact.js @@ -1,5 +1,5 @@ const { WEB_APPS, sendMessageToSlack } = require('./utils') -const execSync = require('child_process').execSync +const { runCommand } = require('./utils') const fetchCachedTarFile = async () => { const [, , ...args] = process.argv @@ -12,10 +12,12 @@ const fetchCachedTarFile = async () => { await sendMessageToSlack( `Pulling the artifact \`${currentTag}\` from S3 bucket \`cloud-deployments-releases-cache-prod\``, ) - const copyArtifactResult = execSync( - `aws s3 cp s3://cloud-deployments-releases-cache-prod/${fileName} ./packages/${packageName}/public`, - ).toString() - console.info(copyArtifactResult) + runCommand('aws', [ + 's3', + 'cp', + `s3://cloud-deployments-releases-cache-prod/${fileName}`, + `./packages/${packageName}/public`, + ]) } catch (err) { console.error('fetchArtifact', err) throw new Error(err) diff --git a/scripts/release/upload-artifact.js b/scripts/release/upload-artifact.js index 561dff73a9..c8f95e77fe 100644 --- a/scripts/release/upload-artifact.js +++ b/scripts/release/upload-artifact.js @@ -1,4 +1,4 @@ -const execSync = require('child_process').execSync +const { runCommand } = require('./utils') const { getVersionTag, WEB_APPS, sendMessageToSlack } = require('./utils') const uploadArtifact = async () => { @@ -9,23 +9,30 @@ const uploadArtifact = async () => { if (packageName === 'elements') { workspaceName = '@reapit/elements' } + if (packageName === 'web-components') { + workspaceName = '@reapit/web-components' + } try { - const fetchConfigResult = execSync(`yarn workspace ${workspaceName} fetch-config --name production`).toString() - console.info(fetchConfigResult) - const lintResult = execSync(`yarn workspace ${workspaceName} lint`).toString() - console.info(lintResult) - const testResult = execSync(`yarn workspace ${workspaceName} test:ci`).toString() - console.info(testResult) - const buildResult = execSync(`yarn workspace ${workspaceName} build:prod`).toString() - console.info(buildResult) - const resultTarFile = execSync( - `tar -C ./packages/${packageName}/public -czvf ${fileName} --exclude='config.json' dist`, - ).toString() - console.info(resultTarFile) - const copyS3Result = execSync( - `aws s3 cp ${fileName} s3://cloud-deployments-releases-cache-prod --grants read=uri=http://acs.amazonaws.com/groups/global/AllUsers`, - ).toString() - console.info(copyS3Result) + runCommand('yarn', ['workspace', workspaceName, 'fetch-config', '--name', 'production']) + runCommand('yarn', ['workspace', workspaceName, 'lint']) + runCommand('yarn', ['workspace', workspaceName, 'test:ci']) + runCommand('yarn', ['workspace', workspaceName, 'build:prod']) + runCommand('tar', [ + '-C', + `./packages/${packageName}/public`, + '-czvf', + fileName, + '--exclude="config.json"', + 'dist', + ]) + runCommand('aws', [ + 's3', + 'cp', + fileName, + 's3://cloud-deployments-releases-cache-prod', + '--grants', + 'read=uri=http://acs.amazonaws.com/groups/global/AllUsers', + ]) await sendMessageToSlack(`Finish build \`${packageName}\` with file \`${fileName}\``) } catch (err) { console.error(err) diff --git a/scripts/release/utils.js b/scripts/release/utils.js index 3fd638dea0..25a1e44b93 100644 --- a/scripts/release/utils.js +++ b/scripts/release/utils.js @@ -1,6 +1,5 @@ require('isomorphic-fetch') const spawn = require('child_process').spawnSync -const execSync = require('child_process').execSync const path = require('path') const removeUnuseChar = value => { @@ -67,10 +66,12 @@ const sendMessageToSlack = async message => { const extractTarFile = async ({ tagName, packageName }) => { try { const fileName = `${tagName}.tar.gz` - const tarDistResult = execSync( - `tar -C ./packages/${packageName}/public -xzvf ./packages/${packageName}/public/${fileName}`, - ).toString() - console.info(tarDistResult) + runCommand('tar', [ + '-C', + `./packages/${packageName}/public`, + '-xzvf', + `./packages/${packageName}/public/${fileName}`, + ]) } catch (err) { console.error('releaseWebApp', err) throw new Error(err) @@ -80,30 +81,34 @@ const extractTarFile = async ({ tagName, packageName }) => { const copyConfig = ({ packageName }) => { const destinationFolder = `${process.cwd()}/packages/${packageName}/public/dist` const configFilePath = `${process.cwd()}/packages/${packageName}/config.json` - const copyConfigResult = execSync(`cp ${configFilePath} ${destinationFolder}`).toString() - console.info(copyConfigResult) + runCommand('cp', [configFilePath, destinationFolder]) } const runReleaseCommand = async ({ packageName, tagName, env }) => { await sendMessageToSlack(`Deploying for web app \`${packageName}\` with version \`${tagName}\``) - const realeaseResult = execSync(`yarn workspace ${packageName} release:${env}`).toString() - console.info(realeaseResult) + runCommand('yarn', ['workspace', packageName, `release:${env}`]) await sendMessageToSlack(`Finish the deployment for web app \`${packageName}\` with version \`${tagName}\``) } const runTestCyPress = async ({ packageName, tagName, env }) => { await sendMessageToSlack(`Testing cypress for web app \`${packageName}\` with version \`${tagName}\``) - const cypressTest = execSync( - `yarn workspace cloud-alert cypress:ci --env ENVIRONMENT=${env},PACKAGE_NAME=${packageName}`, - ).toString() - console.log(cypressTest) + runCommand('yarn', [ + 'workspace', + 'cloud-alert', + 'cypress:ci', + '--env', + `ENVIRONMENT=${env},PACKAGE_NAME=${packageName}`, + ]) await sendMessageToSlack(`Finish testing cypress for web app \`${packageName}\` with version \`${tagName}\``) } const releaseWebApp = async ({ tagName, packageName, env }) => { try { await extractTarFile({ tagName, packageName }) - await copyConfig({ packageName }) + // Ignore copy config for web-components + if (packageName !== 'web-components') { + await copyConfig({ packageName }) + } await runReleaseCommand({ packageName, tagName, env }) await runTestCyPress({ packageName, tagName, env }) } catch (err) { @@ -112,6 +117,12 @@ const releaseWebApp = async ({ tagName, packageName, env }) => { } } +const runReleaseCommandForWebComponents = async ({ packageName, tagName, env }) => { + await sendMessageToSlack(`Deploying for web app \`${packageName}\` with version \`${tagName}\``) + runCommand('yarn', ['workspace', '@reapit/web-components', `release:serverless:${env}`, '--name', packageName]) + await sendMessageToSlack(`Finish the deployment for web app \`${packageName}\` with version \`${tagName}\``) +} + const releaseServerless = async ({ tagName, packageName, env }) => { // This is temporary fix for deployment to new prod and old prod env if (env === 'staging') { @@ -119,11 +130,10 @@ const releaseServerless = async ({ tagName, packageName, env }) => { } try { await sendMessageToSlack(`Checking out for \`${packageName}\` with version \`${tagName}\``) - const checkoutResult = execSync(`git checkout ${tagName}`).toString() - console.info(checkoutResult) + runCommand('git', ['checkout', tagName]) const isReleaseWebComponentPackage = WEB_COMPONENTS_SERVERLESS_APPS.includes(packageName) if (isReleaseWebComponentPackage) { - await runReleaseCommand({ packageName: '@reapit/web-components', tagName, env }) + await runReleaseCommandForWebComponents({ packageName: packageName, tagName, env }) await runTestCyPress({ packageName, tagName, env }) return } @@ -139,23 +149,13 @@ const releaseServerless = async ({ tagName, packageName, env }) => { const releaseNpm = async ({ tagName, packageName }) => { try { await sendMessageToSlack(`Checking out for \`${packageName}\` with version \`${tagName}\``) - const checkoutResult = execSync(`git checkout ${tagName}`).toString() - console.info(checkoutResult) + runCommand('git', ['checkout', tagName]) await sendMessageToSlack(`Releasing for npm \`${packageName}\` with version \`${tagName}\``) - const setGitHubUseSSHResult = execSync( - 'git config --global url.ssh://git@github.com/.insteadOf https://github.com/', - ).toString() - console.info(setGitHubUseSSHResult) - const setUserEmailResult = execSync( - `git config --global user.email "${process.env.GITHUB_ACTOR}@email.com"`, - ).toString() - console.info(setUserEmailResult) - const setUserNameResult = execSync(`git config --global user.name "${process.env.GITHUB_ACTOR}"`).toString() - console.info(setUserNameResult) - const buildResult = execSync(`yarn workspace ${packageName} build:prod`) - console.info(buildResult) - const publishResult = execSync(`yarn workspace ${packageName} publish`).toString() - console.info(publishResult) + runCommand('git', ['config', '--global', 'url.ssh://git@github.com/.insteadOf https://github.com/']) + runCommand('git', ['config', '--global', 'user.email', `"${process.env.GITHUB_ACTOR}@email.com"`]).toString() + runCommand('git', ['config', ' --global', 'user.name', `"${process.env.GITHUB_ACTOR}"`]) + runCommand('yarn', ['workspace', packageName, 'build:prod']) + runCommand('yarn', ['workspace', packageName, 'publish']) await sendMessageToSlack(`Finish the release for npm \`${packageName}\` with version \`${tagName}\``) } catch (err) { console.error('releaseNpm', err)