Skip to content

Commit

Permalink
feat: Github Action to deploy spartacus to Upscale Hosting service (S…
Browse files Browse the repository at this point in the history
  • Loading branch information
hackergil authored Feb 26, 2021
1 parent ac15538 commit 6c1b72b
Show file tree
Hide file tree
Showing 15 changed files with 549 additions and 36 deletions.
1 change: 1 addition & 0 deletions .github/hs-deploy-action/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.js
13 changes: 13 additions & 0 deletions .github/hs-deploy-action/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
FROM node:12

COPY package.json /
COPY yarn.lock /

RUN yarn

COPY src/*.ts /
COPY tsconfig.json /

RUN ["yarn", "build"]

ENTRYPOINT ["node", "/index.js"]
3 changes: 3 additions & 0 deletions .github/hs-deploy-action/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Hosting service deployment bot

Deploys Spartacus to Upscale hosting service
5 changes: 5 additions & 0 deletions .github/hs-deploy-action/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
name: 'Hosting service deployment'
description: 'Hosting service deployment bot'
runs:
using: 'docker'
image: 'Dockerfile'
19 changes: 19 additions & 0 deletions .github/hs-deploy-action/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
{
"name": "deploy-action",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"build": "tsc index.ts"
},
"author": "",
"license": "Apache-2.0",
"dependencies": {
"@actions/exec": "^1.0.4",
"@actions/github": "^4.0.0",
"@types/node": "^12.11.1"
},
"devDependencies": {
"typescript": "^4.1.3"
}
}
67 changes: 67 additions & 0 deletions .github/hs-deploy-action/src/deploy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import * as exec from '@actions/exec';
import { ExecOptions } from '@actions/exec';
import { addComment, getBundleId } from './functions';

/**
* Deploys an app to the hosting service
* @param github Github object
* @param octoKit Octokit object
*/
export async function deploy(github: any, octoKit: any) {
const context = github.context;
const branch = context.payload.pull_request.head.ref;

console.log(`--> Deploying branch ${branch}`);

const bundleId = getBundleId(branch);
const command = `upp application deploy -b ${bundleId} -t spartacus -s ./dist/storefrontapp -e stage`;

const exp = /https\:\/\/\w+\.cloudfront\.net/;

const options: ExecOptions = {};
options.listeners = {
stdout: (data: Buffer) => {
const line = data.toString();
const match = line.match(exp);
if (match && match.length > 0) {
const body = `:rocket: Spartacus deployed to [${match}](${match})`;
console.log(body);
addComment(context, octoKit, body);
}
},
stderr: (data: Buffer) => {
console.log(`upp deploy exited with error: ${data.toString()}`);
},
};

await exec.exec(command, [], options);
}

/**
* Undeploys an app from the Upscale hosting service
* @param branch Name of the branch to undeploy
*/
export async function undeploy(branch: any) {
const bundleId = getBundleId(branch);
const command = `upp application undeploy -b ${bundleId} -e stage`;

const options: ExecOptions = {
input: Buffer.from('confirm\n'),
};
options.listeners = {
stdout: (data: Buffer) => {
console.log(data.toString());
},

stderr: (data: Buffer) => {
console.log(`upp deploy exited with error: ${data.toString()}`);
},
};

console.log(`Starting Hosting Service Undeployment of PR branch ${branch}`);

//run sh to get CLI and prep
await exec.exec(command, [], options);

console.log('Application undeployed from Hosting service successfully');
}
74 changes: 74 additions & 0 deletions .github/hs-deploy-action/src/functions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import * as exec from '@actions/exec';

export async function build() {
//build libs and app
await exec.exec('yarn', ['install']);
await exec.exec('yarn', ['build:libs']);
await exec.exec('yarn', ['build']);
}

/**
* Adds hosting service deployment URL as a comment to the corresponding github pull request
* @param context Github context
* @param octoKit Github octokit object
* @param comment Comment body
*/
export async function addComment(context: any, octoKit: any, comment: string) {
const COMMENT_HEADER = '## Hosting service deployment';
const issueNumber = context.payload.pull_request.number;
const owner = context.payload.repository.owner.login;
const repo = context.payload.repository.name;
const body = `${COMMENT_HEADER}\n${comment}`;

const comments = await octoKit.issues.listComments({
issue_number: issueNumber,
owner,
repo,
});

const botComment = comments.data.filter(
(comment: any) =>
comment.body.includes(COMMENT_HEADER) &&
comment.user.login === 'github-actions[bot]'
);

if (botComment && botComment.length) {
await octoKit.issues.deleteComment({
comment_id: botComment[0].id,
owner,
repo,
});
}

await octoKit.issues.createComment({
issue_number: issueNumber,
owner,
repo,
body,
});
}

/**
* Generates a UPP valid bundle Id based on the branch name
* replaces slashes with -s and other chars with -d
* @param branch Branch name
*/
export function getBundleId(branch: string) {
let bundleId = '';
const regex = /(\-\d)/;
branch
.toLowerCase()
.replace(/\//g, '-s')
.replace(/\./g, '-d')
.replace(/_/g, '-')
.split(regex)
.forEach((s: string) => {
if (s.match(regex)) {
bundleId += s.substring(0, 1) + 'i' + s.substring(1, 2);
} else {
bundleId += s;
}
});
console.log(`--> Generated bundle ID: ${bundleId}`);
return bundleId;
}
40 changes: 40 additions & 0 deletions .github/hs-deploy-action/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import * as github from '@actions/github';
import * as exec from '@actions/exec';
import { build } from './functions';
import { deploy, undeploy } from './deploy';

async function run() {
const GITHUB_TOKEN = process.env.GITHUB_TOKEN;
const UPP_ACTION = process.env.UPP_ACTION;

if (!GITHUB_TOKEN) {
throw new Error('Github token missing in action');
}

const context = github.context;
const pr = context.payload.pull_request;

if (!pr) {
throw new Error(
'Missing pull request context! Make sure to run this action only for pull_requests.'
);
}

const branch = pr.head.ref;
const octoKit = github.getOctokit(GITHUB_TOKEN);

console.log(`Starting Hosting service deployment of PR branch ${branch}.`);

//run sh to get CLI and prep
await exec.exec('sh', ['./.github/hs-deploy-action/upp-cli-setup.sh']);

if (UPP_ACTION === 'deploy') {
await build();
await deploy(github, octoKit);
console.log('--> Hosting service deployment done');
} else if (UPP_ACTION === 'undeploy') {
await undeploy(branch);
}
}

run();
34 changes: 34 additions & 0 deletions .github/hs-deploy-action/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"compilerOptions": {
/* Basic Options */
"target": "es2019" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */,
"module": "commonjs" /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */,
"lib": [
"es2019",
"esnext.bigint",
"es2020.string",
"es2020.symbol.wellknown"
] /* Specify library files to be included in the compilation. */,
"allowJs": false /* Allow javascript files to be compiled. */,
"checkJs": false /* Report errors in .js files. */,
"declaration": false /* Generates corresponding '.d.ts' file. */,
"declarationMap": false /* Generates a sourcemap for each corresponding '.d.ts' file. */,
"sourceMap": false /* Generates corresponding '.map' file. */,

/* Strict Type-Checking Options */
"strict": true /* Enable all strict type-checking options. */,

/* Additional Checks */
"noUnusedLocals": true /* Report errors on unused locals. */,
"noUnusedParameters": true /* Report errors on unused parameters. */,
"noImplicitReturns": true /* Report error when not all code paths in function return a value. */,
"noFallthroughCasesInSwitch": true /* Report errors for fallthrough cases in switch statement. */,

/* Module Resolution Options */
"moduleResolution": "node" /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */,
"esModuleInterop": false /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */,

/* Advanced Options */
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
}
}
39 changes: 39 additions & 0 deletions .github/hs-deploy-action/upp-cli-setup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/usr/bin/env bash
set -o errexit
set -o nounset

APP="upp-cli"

echo "-----"
echo "Downloading upp cli zip"

# TODO pull fron NPM once the CLI is published. For now, we can only get it from github releases
curl -u ${GHT_USER}:${GHT_TOKEN} -L -H "Accept: application/octet-stream" \
"https://github.tools.sap/api/v3/repos/cx-commerce/upscale-partner-platform-cli/releases/assets/7203" -o ${APP}.zip

if [ ! -s ${APP}.zip ]; then
echo "Error downloading upp CLI zip. Check url and configs"
exit 1
fi

echo "-----"
echo "Installing upp cli (dependencies)"
unzip -o ${APP}.zip -d ${APP}
cd ${APP}
npm install
chmod -R 777 /github/home/.npm
chmod -R 777 .

echo "-----"
echo "Installing UPP CLI"
npm run install-cli

cd ..

echo "-----"
echo "Configuring cli"

upp config -z -t ${UPP_TENANT} -c ${UPP_CLIENT} -s ${UPP_SECRET} -r us10 -i 3 -a us10.stage.upp.upscalecommerce.com

echo "-----"
echo "UPP CLI installed and ready."
Loading

0 comments on commit 6c1b72b

Please sign in to comment.