diff --git a/.github/workflows/add-testlink-to-pr.yml b/.github/workflows/add-testlink-to-pr.yml index bb0fa51ac..bae10b643 100644 --- a/.github/workflows/add-testlink-to-pr.yml +++ b/.github/workflows/add-testlink-to-pr.yml @@ -1,17 +1,35 @@ name: Create test link on PR creation on: pull_request: - types: [opened] + types: + - opened + - reopened + - synchronize + - edited jobs: update_pr: runs-on: ubuntu-latest steps: - - uses: tzkhan/pr-update-action@v2 + - name: Create/update test link on DEV + if: ${{ github.base_ref == 'develop' }} + uses: tzkhan/pr-update-action@v2 with: repo-token: "${{ secrets.GITHUB_TOKEN }}" head-branch-regex: '.+' body-template: | - [Test link](https://sys-map.dev.bgdi.ch/%headbranch%/index.html) + [Test link](https://sys-map.dev.bgdi.ch/preview/%headbranch%/index.html) + body-update-action: 'suffix' + body-uppercase-head-match: false + + - name: Create/update deployment link on INT + if: ${{ github.base_ref == 'master' }} + uses: tzkhan/pr-update-action@v2 + with: + repo-token: "${{ secrets.GITHUB_TOKEN }}" + head-branch-regex: '.+' + body-template: | + + [Test link](https://sys-map.int.bgdi.ch/preview/%headbranch%/index.html) body-update-action: 'suffix' body-uppercase-head-match: false diff --git a/buildspec.yml b/buildspec.yml deleted file mode 100644 index 10110e4a4..000000000 --- a/buildspec.yml +++ /dev/null @@ -1,140 +0,0 @@ -version: 0.2 - -env: - shell: bash - variables: - AWS_DEFAULT_REGION: eu-central-1 - parameter-store: - AWS_SWISSTOPO_BGDI_ACCOUNT_ID: swisstopo-bgdi_account-id - AWS_SWISSTOPO_BGDI_DEV_ACCOUNT_ID: swisstopo-bgdi-dev_account-id - -phases: - - install: - runtime-versions: - nodejs: 14 - commands: - # updating node to v16 (so npm is >=v8.x.x) - - n 16 - # npm ci reads only the package-lock.json file (not the package.json) to be sure to have exactly the same libraries - # that were used last time npm install was done on the developer's device. - - CYPRESS_CACHE_FOLDER=/tmp/.cache npm ci - - pre_build: - commands: - - echo "=========== Configuring stuff ===================================" - - export PULL_REQUEST=${CODEBUILD_WEBHOOK_TRIGGER#pr/*} - - export GIT_BRANCH="${CODEBUILD_WEBHOOK_HEAD_REF#refs/heads/}" - - export GIT_BASE_BRANCH="${CODEBUILD_WEBHOOK_BASE_REF#refs/heads/}" - - export GIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7) - - export GIT_TAG="$(git describe --tags --dirty || echo 'unknown')" - - export GIT_DIRTY="$(git status --porcelain)" - # When build are manually triggered by a user, the CODEBUILD_WEBHOOK_HEAD_REF is not - # set resulting to an empty GIT_BRANCH. Usually Codebuild don't checkout the branch but - # the commit and is on a detached HEAD. Therefore we need to use `git describe --exact-match --all` - # that returns either the git tag of the detached HEAD if any or the branch if any or failed. - - | - if [[ -z "${GIT_BRANCH}" ]]; - then - echo "git rev-parse HEAD" - git rev-parse HEAD - echo "git show-ref" - git show-ref - GIT_BRANCH=$(git show-ref | grep "$(git rev-parse HEAD)" | awk '!/refs\/tags\// {print $2}' | head -1) - export GIT_BRANCH=${GIT_BRANCH##*/} - fi - # if this build has been triggered by a push on master (PR merge on master), we deploy on INT (otherwise everything goes to dev) - - export DEPLOY_TARGET="dev" - - export BUILD_TYPE_1=dev - - export BUILD_OUTPUT_1=development - - export BUILD_TYPE_2=dev - - export BUILD_OUTPUT_2=development - - if [[ "${GIT_BRANCH}" == "master" ]]; then - export DEPLOY_TARGET="int"; - export BUILD_TYPE_1=int; - export BUILD_OUTPUT_1=integration; - export BUILD_TYPE_2=prod; - export BUILD_OUTPUT_2=production; - elif [[ "${GIT_BRANCH}" == "develop" ]]; then - export BUILD_TYPE_2=int; - export BUILD_OUTPUT_2=integration; - fi - # if we are on DEV, we have to switch to the account "swisstopo-bgdi-dev", otherwise the account is "swisstopo-bgdi" - - export AWS_ACCOUNT_TO_USE="${AWS_SWISSTOPO_BGDI_DEV_ACCOUNT_ID}:role/BgdiDevCodebuildAccess" - - if [ "${DEPLOY_TARGET}" = "int" ] ; then - export AWS_ACCOUNT_TO_USE="${AWS_SWISSTOPO_BGDI_ACCOUNT_ID}:role/BgdiCodebuildAccess"; - fi - # Set the app version to the git tag - - export APP_VERSION=${GIT_TAG} - - echo "=== Environment Variables =======================================" - - echo CODEBUILD_INITIATOR=${CODEBUILD_INITIATOR} - - echo CODEBUILD_RESOLVED_SOURCE_VERSION=${CODEBUILD_RESOLVED_SOURCE_VERSION} - - echo CODEBUILD_SOURCE_VERSION=${CODEBUILD_SOURCE_VERSION} - - echo CODEBUILD_WEBHOOK_EVENT=${CODEBUILD_WEBHOOK_EVENT} - - echo CODEBUILD_WEBHOOK_ACTOR_ACCOUNT_ID=${CODEBUILD_WEBHOOK_ACTOR_ACCOUNT_ID} - - echo CODEBUILD_WEBHOOK_BASE_REF=${CODEBUILD_WEBHOOK_BASE_REF} - - echo CODEBUILD_WEBHOOK_HEAD_REF=${CODEBUILD_WEBHOOK_HEAD_REF=} - - echo CODEBUILD_WEBHOOK_TRIGGER=${CODEBUILD_WEBHOOK_TRIGGER} - - echo CODEBUILD_WEBHOOK_MERGE_COMMIT=${CODEBUILD_WEBHOOK_MERGE_COMMIT} - - echo CODEBUILD_WEBHOOK_PREV_COMMIT=${CODEBUILD_WEBHOOK_PREV_COMMIT} - - echo CODEBUILD_BUILD_ID=${CODEBUILD_BUILD_ID} - - echo CODEBUILD_SOURCE_REPO_URL=${CODEBUILD_SOURCE_REPO_URL} - - echo PULL_REQUEST=${PULL_REQUEST} - - echo GIT_BRANCH=${GIT_BRANCH} - - echo GIT_HASH=${GIT_HASH} - - echo GIT_TAG=${GIT_TAG} - - echo GIT_DIRTY=${GIT_DIRTY} - - echo BUILD_TYPE_1=${BUILD_TYPE_1} - - echo BUILD_TYPE_2=${BUILD_TYPE_2} - - echo BUILD_OUTPUT_2=${BUILD_OUTPUT_2} - - echo BUILD_OUTPUT_1=${BUILD_OUTPUT_1} - - echo DEPLOY_TARGET=${DEPLOY_TARGET} - - echo AWS_ACCOUNT_TO_USE=${AWS_ACCOUNT_TO_USE} - - echo APP_VERSION=${GIT_TAG} - # As the cypress/download folder is not added to git, and (somehow) not created by Cypress at startup, we create it - - mkdir -p cypress/downloads/ - - build: - commands: - - echo =================================================================== - - echo Building application for ${BUILD_TYPE_1} - - npm run build:${BUILD_TYPE_1} - - if [[ "${BUILD_TYPE_1}" != "${BUILD_TYPE_2}" ]]; then - echo -------------------------------------------------------------------; - echo Building application for ${BUILD_TYPE_2}; - npm run build:${BUILD_TYPE_2}; - fi - - - post_build: - commands: - - echo =================================================================== - - echo Unit testing... - # will build the application in dev mode before testing - - CYPRESS_CACHE_FOLDER=/tmp/.cache npm run test:unit - - - echo =================================================================== - - echo Deploying to ${DEPLOY_TARGET}... - # switching role for deploy (otherwise the S3 bucket won't be visible as it's another account) - # the application will be built by the npm target before deploying - - npm run deploy:${DEPLOY_TARGET} -- --role=arn:aws:iam::${AWS_ACCOUNT_TO_USE} --branch=${GIT_BRANCH} - - - echo =================================================================== - - echo E2E Testings... - # will build the application in dev mode before testing - - CYPRESS_CACHE_FOLDER=/tmp/.cache npm run test:e2e:ci:${BUILD_TYPE_1} - - echo DONE - - -artifacts: - # The name below is only used for manual build (not by build started by the webhook) - name: ${BUILD_TYPE_1}/web-mapviewer/${APP_VERSION} - files: - - '**/*' - base-directory: dist/${BUILD_OUTPUT_1} - secondary-artifacts: - production: - name: ${BUILD_TYPE_2}/web-mapviewer/${APP_VERSION} - files: - - '**/*' - base-directory: dist/${BUILD_OUTPUT_2} \ No newline at end of file diff --git a/package.json b/package.json index 13e7a35c7..22fe3cd5a 100644 --- a/package.json +++ b/package.json @@ -21,8 +21,6 @@ "build:dev": "npm run build -- --mode development", "build:int": "npm run build -- --mode integration", "build:prod": "npm run build -- --mode production", - "deploy:dev": "node scripts/deploy.js dev", - "deploy:int": "node scripts/deploy.js int", "update:translations": "node scripts/generate-i18n-files.js", "update:browserlist": "npx browserslist@latest --update-db" }, diff --git a/scripts/deploy.js b/scripts/deploy.js deleted file mode 100644 index 454589c8e..000000000 --- a/scripts/deploy.js +++ /dev/null @@ -1,154 +0,0 @@ -#!/usr/bin/env node - -// This is a Node.js script (requiring Node >= v12) that deploys what's inside the dist/ folder to -// a S3 bucket, defined by the target given as a script argument. - -// describing possible arguments for this script (launch with --help to see the doc) -const yargs = require('yargs') -const argv = yargs - .usage('Usage: $0 [options]') - .command('dev', 'Deploy dist/* files to DEV bucket') - .command('int', 'Deploy dist/* files to INT bucket') - .command('prod', 'Deploy dist/* files to PROD bucket') - .option('role', { - describe: 'AWS ARN for the role to use (will switch to role before uploading)', - default: null, - type: 'string', - }) - .option('region', { - describe: 'AWS Region (default is Frankfurt)', - default: 'eu-central-1', - type: 'string', - }) - .option('branch', { - describe: - 'Git branch. Will be used as the root folder in S3. This script will try to automatically find the branch name if this option is not given.', - default: null, - type: 'string', - }) - .demandCommand() - .help() - .showHelpOnFail(true, 'a target is needed').argv - -// Declaring all possible buckets (keys are targets) -const buckets = { - dev: 'web-mapviewer-dev-swisstopo', - int: 'web-mapviewer-int-swisstopo', - prod: 'web-mapviewer-prod-swisstopo', -} - -// checking that the target is valid -if (argv._.length > 1) { - console.error('To many arguments, only one target must be provided') - process.exit(1) -} -const target = argv._[0] -if (!target || !(target in buckets)) { - console.error(`Non-existing target specified: "${target}"`) - yargs.showHelp() - process.exit(1) -} - -// Load the AWS SDK for Node.js -const AWS = require('aws-sdk') -// Set the region -AWS.config.update({ region: argv.region }) - -// Loading filesystem API -const { resolve } = require('path') - -// loading external utility function to read git metadata -const gitBranch = require('git-branch') - -// Loading internal utility functions -const s3Utils = require('./s3-utils') -const fileUtils = require('./file-utils') - -// Checking if index.html file is present in dist folder (if build has been done beforehand) -if (!fileUtils.fileExists('./dist/development/index.html')) { - console.error('Please build the project before trying to deploy') - process.exit(1) -} - -// Create S3 service object -s3Utils - .getS3(argv.region, argv.role) - .then((s3) => { - ;(async () => { - // Checking current branch - let branch = argv.branch - if (!branch) { - branch = await gitBranch() - } - if (!branch) { - console.error( - 'Failed to read automatically on which git branch this script has been launched, please specify option --branch and relaunch the script' - ) - process.exit(1) - } - // lowering case for branch name - branch = branch.toLowerCase() - - // if branch is not master and target is prod, we exit - if (branch !== 'master' && target === 'prod') { - console.error( - 'It is forbidden to deploy anything else than `master` on the PROD environment' - ) - process.exit(1) - } - // if branch is develop and target is not dev, we exit (if it was permitted to deploy on INT for instance, it would - // not be a viable build as vue.config.js sets the publicPath to the root of the bucket for develop and master branch - // but the code would be deployed in a /develop folder on INT, breaking all links to JS and CSS files) - if (branch === 'develop' && target !== 'dev') { - console.error('Branch `develop` can only be deployed on DEV environment') - process.exit(1) - } - // bucket folder will be branch name expect if we are on `master` and target is INT or PROD, or if we are on `develop` and target is DEV - let bucketFolder - if ( - (branch === 'master' && (target === 'int' || target === 'prod')) || - (branch === 'develop' && target === 'dev') - ) { - bucketFolder = '' - } else { - bucketFolder = branch - } - - const distFolderRelativePath = './dist/development' - const distFolderFullPath = resolve(distFolderRelativePath) - - let countFileBeingUploaded = 0 - for await (const file of fileUtils.getFiles(distFolderRelativePath)) { - // file paths returned by fileUtils are absolute paths, we need to get rid of the project directory part to have a valid S3 path. - let bucketFilePath = bucketFolder + file.replace(distFolderFullPath, '') - countFileBeingUploaded++ - // removing any slash at the beginning so that it doesn't create a / folder (can happen if we are deploying something at the root of the bucket) - // and a slash has been left by the dist path removal). - if (bucketFilePath.startsWith('/')) { - bucketFilePath = bucketFilePath.substring(1) - } - // just to be extra careful, replacing any // by a single / in the filepath - bucketFilePath = bucketFilePath.replace('//', '/') - - s3Utils.uploadFileToS3(s3, file, buckets[target], bucketFilePath, () => { - countFileBeingUploaded-- - // checking if all files are done being uploaded - if (countFileBeingUploaded === 0) { - // outputs a URL to the index.html file hosted on the bucket in the console. - const appUrl = `https://sys-map.${target}.bgdi.ch/${ - bucketFolder + (bucketFolder !== '' ? '/' : '') - }index.html` - console.log('') - console.log( - '*************************************************************************************' - ) - console.log(`Success, your deployment is now available at : ${appUrl}`) - console.log( - '*************************************************************************************' - ) - } - }) - } - })() - }) - .catch(console.error) diff --git a/scripts/file-utils.js b/scripts/file-utils.js deleted file mode 100644 index 2fc217184..000000000 --- a/scripts/file-utils.js +++ /dev/null @@ -1,36 +0,0 @@ -// Loading filesystem API -const { resolve } = require('path') -const fs = require('fs') - -/** - * Perform a deep scan (recursive) of a directory, and returns (through an asynchronous iterator) - * all file paths found in this folder - * - * @param dir A path (relative or absolute) to a directory to be scanned - * @returns {any} An Asynchronous Iterators returning all file paths of the directory, paths will be - * absolute even if dir param is relative - */ -async function* getFiles(dir) { - const directoryEntries = await fs.promises.readdir(dir, { withFileTypes: true }) - for (const entry of directoryEntries) { - const resolvedEntry = resolve(dir, entry.name) - if (entry.isDirectory()) { - yield* getFiles(resolvedEntry) - } else { - yield resolvedEntry - } - } -} - -function fileExists(file) { - try { - return fs.existsSync(file) - } catch (err) { - return false - } -} - -module.exports = { - getFiles, - fileExists, -} diff --git a/scripts/s3-utils.js b/scripts/s3-utils.js deleted file mode 100644 index 02d7baa6d..000000000 --- a/scripts/s3-utils.js +++ /dev/null @@ -1,76 +0,0 @@ -// Load the AWS SDK for Node.js -const AWS = require('aws-sdk') - -// Loading filesystem API -const fs = require('fs') - -// Loading utility to detect mime types (and set the Content-type header accordingly) -const mime = require('mime-types') - -// loading gzipper -const { gzipSync } = require('zlib') - -/** - * Load an AWS-SDK S3 instance for the given region, switch role if roleArn is defined - * - * @param region The AWS region on which to connect to - * @param roleArn A full ARN to a role that will be used to `aws sts assume-role` for this session - * @returns {Promise} - */ -async function getS3(region, roleArn) { - console.log('loading s3 service for region', region) - const s3Options = { apiVersion: '2006-03-01' } - - // if a switch role is needed, we generate the session token before returning the S3 instance - if (roleArn) { - console.log('switching to role', roleArn) - const sts = new AWS.STS({ region }) - const switchRoleParams = { - RoleArn: roleArn, - RoleSessionName: 'SwitchRoleSession', - } - const assumeRole = await sts.assumeRole(switchRoleParams).promise() - - s3Options.accessKeyId = assumeRole.Credentials.AccessKeyId - s3Options.secretAccessKey = assumeRole.Credentials.SecretAccessKey - s3Options.sessionToken = assumeRole.Credentials.SessionToken - } - console.log('loading done for s3 service') - return new AWS.S3(s3Options) -} - -/** - * Uploads a file to a S3 bucket using the S3 instance given in param - * - * @param s3 An instance of AWS.S3 - * @param filePath A local file path to be uploaded on S3 - * @param bucket The bucket name (must be accessible and writable through the current AWS profile/role) - * @param bucketFilePath Where the file should be uploaded on the S3 bucket (relative to the root of - * the bucket) - * @param callback A callback that will be called if successful - */ -function uploadFileToS3(s3, filePath, bucket, bucketFilePath, callback) { - const data = fs.readFileSync(filePath) - const params = { - Bucket: bucket, - Key: bucketFilePath, - Body: gzipSync(data), - ACL: 'public-read', - // mime.lookup will return false if it can't detect content type - ContentType: mime.lookup(filePath) || 'application/octet-stream', - ContentEncoding: 'gzip', - } - s3.upload(params, function (s3Err, data) { - if (s3Err) { - console.error(`Error while uploading file ${bucketFilePath} to bucket ${bucket}`, s3Err) - process.exit(-1) - } - console.log(`File uploaded successfully at ${data.Location}`) - callback && callback() - }) -} - -module.exports = { - getS3, - uploadFileToS3, -} diff --git a/tests/e2e-cypress/integration/deployScript.cy.js b/tests/e2e-cypress/integration/deployScript.cy.js deleted file mode 100644 index ed9c8f429..000000000 --- a/tests/e2e-cypress/integration/deployScript.cy.js +++ /dev/null @@ -1,69 +0,0 @@ -/// - -describe('Test the deploy script', () => { - const scriptExec = 'node scripts/deploy.js' - - const checkResult = (result, expected) => { - const { stderr, stdout, code } = result - const { - firstLine: expectedFirstLine, - contains: expectContains, - exitCode: expectedExitCode = 0, - } = expected - if (expectedExitCode === 0) { - expect(stderr).to.be.empty - expect(stdout).to.not.be.empty - } else { - expect(stderr).to.not.be.empty - } - expect(code).to.be.eq(expectedExitCode) - if (expectedFirstLine) { - const firstLine = (expectedExitCode === 0 ? stdout : stderr).split('\n')[0] - expect(firstLine).to.eq(expectedFirstLine) - } - if (expectContains) { - expect(expectedExitCode === 0 ? stdout : stderr).to.contain(expectContains) - } - } - - context('help', () => { - const helpFirstLine = 'Usage: deploy.js [options]' - - it('Shows help when no target is specified', () => { - cy.exec(`${scriptExec}`, { failOnNonZeroExit: false }).then((result) => { - expect(result.stderr).to.contain('a target is needed') - expect(result.stderr).to.contain(helpFirstLine) - }) - }) - it('Shows help when --help param is used', () => { - cy.exec(`${scriptExec} --help`).then((result) => - checkResult(result, { firstLine: helpFirstLine }) - ) - }) - it('Shows target help when --help param is used with a target', () => { - cy.exec(`${scriptExec} dev --help`).then((result) => - checkResult(result, { contains: 'Deploy dist/* files to DEV bucket' }) - ) - }) - }) - context('targets', () => { - it('Fails with exit code 1 when wrong command/target is entered', () => { - cy.exec(`${scriptExec} a_totally_wrong_target`, { - failOnNonZeroExit: false, - }).then((result) => - checkResult(result, { - contains: 'Non-existing target specified', - exitCode: 1, - }) - ) - }) - it('Fails with exit code 1 if more than 1 target is given', () => { - cy.exec(`${scriptExec} dev int`, { failOnNonZeroExit: false }).then((result) => - checkResult(result, { - firstLine: 'To many arguments, only one target must be provided', - exitCode: 1, - }) - ) - }) - }) -})