Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
# Editor configuration, see http://editorconfig.org
root = true

[*]
indent_style = space
indent_size = 2
charset = utf-8
end_of_line = lf
indent_size = 4
indent_style = space
insert_final_newline = true
charset = utf-8
trim_trailing_whitespace = true

[{package.json,*.yml,*.md}]
indent_size = 2
indent_style = space

[*.md]
max_line_length = off
trim_trailing_whitespace = false
9 changes: 4 additions & 5 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
ref: ${{ github.event.release.target_commitish }}
- run: docker build --build-arg NODE_TAG=20 .

- uses: actions/checkout@v2
with:
ref: ${{ github.event.release.target_commitish }}
- run: docker build --build-arg NODE_TAG=20 .
4 changes: 4 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"singleQuote": true,
"semi": true
}
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Add the following your `bitbucket-pipelines.yml` file:

```yaml
- step:
name: "deploy service"
name: 'deploy service'
script:
- pipe: docker://aligent/nx-serverless-deploy-pipe:latest
variables:
Expand Down
88 changes: 44 additions & 44 deletions pipe/cmd.ts
Original file line number Diff line number Diff line change
@@ -1,67 +1,67 @@
import * as cp from "child_process";
import { env } from "./env";
import * as cp from 'child_process';
import { env } from './env';

// Wrap spawn in a promise
function asyncSpawn(
command: string,
args?: ReadonlyArray<string>,
options?: cp.SpawnOptionsWithoutStdio
command: string,
args?: ReadonlyArray<string>,
options?: cp.SpawnOptionsWithoutStdio
): Promise<number | null> {
return new Promise(function (resolve, reject) {
const process = cp.spawn(command, args, options);
if (env.debug)
console.log(
`ℹ️ Executing command: ${command} ${args?.join(
" "
)} with options: ${JSON.stringify(options)}`
);
return new Promise(function (resolve, reject) {
const process = cp.spawn(command, args, options);
if (env.debug)
console.log(
`ℹ️ Executing command: ${command} ${args?.join(
' '
)} with options: ${JSON.stringify(options)}`
);

process.stdout.on("data", (data) => {
console.log(data.toString());
});
process.stdout.on('data', (data) => {
console.log(data.toString());
});

process.stderr.on("data", (data) => {
console.log(`Error: ${data.toString()}`);
});
process.stderr.on('data', (data) => {
console.log(`Error: ${data.toString()}`);
});

process.on("exit", function (code) {
if (code !== 0) reject(code);
resolve(code);
});
process.on('exit', function (code) {
if (code !== 0) reject(code);
resolve(code);
});

process.on("error", function (err) {
reject(err);
process.on('error', function (err) {
reject(err);
});
});
});
}
interface Command {
command: string;
args: ReadonlyArray<string>;
command: string;
args: ReadonlyArray<string>;
}

function splitCommandAndArgs(command: string): Command {
// Split the command string at all white spaces excluding white spaces wrapped with single quotes
const cmd = command.split(/\s(?=(?:[^']*'[^']*')*[^']*$)/g);
return {
command: cmd.shift() as string,
args: cmd,
};
// Split the command string at all white spaces excluding white spaces wrapped with single quotes
const cmd = command.split(/\s(?=(?:[^']*'[^']*')*[^']*$)/g);
return {
command: cmd.shift() as string,
args: cmd,
};
}

function runCommandString(
command: string,
workDir?: string
command: string,
workDir?: string
): Promise<number | null> {
console.log(`Running command: ${command}`);
const cmd = splitCommandAndArgs(command);
return asyncSpawn(cmd.command, cmd.args, { cwd: workDir });
console.log(`Running command: ${command}`);
const cmd = splitCommandAndArgs(command);
return asyncSpawn(cmd.command, cmd.args, { cwd: workDir });
}

export async function runCLICommand(commandStr: Array<string>) {
const workDir = process.env.BITBUCKET_CLONE_DIR;
console.log(`Running commands in ${workDir}`);
const workDir = process.env.BITBUCKET_CLONE_DIR;
console.log(`Running commands in ${workDir}`);

for (const cmd of commandStr) {
await runCommandString(cmd, workDir);
}
for (const cmd of commandStr) {
await runCommandString(cmd, workDir);
}
}
102 changes: 52 additions & 50 deletions pipe/entrypoint.ts
Original file line number Diff line number Diff line change
@@ -1,66 +1,68 @@
import { glob } from "glob";
import { runCLICommand } from "./cmd";
import { env } from "./env";
import { findServerlessYaml } from "./findServerlessYaml";
import { injectCfnRole } from "./injectCfnRole";
import { uploadDeploymentBadge } from "./uploadDeploymentBadge";
import { nodeModulesDirectoryExist } from "./findNodeModules";
import { glob } from 'glob';
import { runCLICommand } from './cmd';
import { env } from './env';
import { nodeModulesDirectoryExist } from './findNodeModules';
import { findServerlessYaml } from './findServerlessYaml';
import { injectCfnRole } from './injectCfnRole';
import { uploadDeploymentBadge } from './uploadDeploymentBadge';

const cloneDir = process.env.BITBUCKET_CLONE_DIR || "";
const cloneDir = process.env.BITBUCKET_CLONE_DIR || '';

async function main() {
let deploymentStatus = false;
let deploymentStatus = false;

try {
const rootServerlessYmlFile = await glob(
`${cloneDir}/serverless.{yml,yaml}`,
{}
);
const nxProject = rootServerlessYmlFile.length == 0;
try {
const rootServerlessYmlFile = await glob(
`${cloneDir}/serverless.{yml,yaml}`,
{}
);
const nxProject = rootServerlessYmlFile.length == 0;

if (!env.awsAccessKeyId || !env.awsSecretAccessKey) {
throw new Error("AWS_ACCESS_KEY_ID or AWS_SECRET_ACCESS_KEY not set");
}
if (!env.awsAccessKeyId || !env.awsSecretAccessKey) {
throw new Error(
'AWS_ACCESS_KEY_ID or AWS_SECRET_ACCESS_KEY not set'
);
}

const servicesPath = nxProject ? env.servicesPath : "";
let serverlessFiles = await findServerlessYaml(
`${cloneDir}${servicesPath}`
);
const servicesPath = nxProject ? env.servicesPath : '';
let serverlessFiles = await findServerlessYaml(
`${cloneDir}${servicesPath}`
);

await Promise.all(
serverlessFiles.map((file) => injectCfnRole(file, env.cfnRole))
);
await Promise.all(
serverlessFiles.map((file) => injectCfnRole(file, env.cfnRole))
);

const commands = [
`npx serverless config credentials --provider aws --profile ${env.profile} --key ${env.awsAccessKeyId} --secret ${env.awsSecretAccessKey}`,
];
const commands = [
`npx serverless config credentials --provider aws --profile ${env.profile} --key ${env.awsAccessKeyId} --secret ${env.awsSecretAccessKey}`,
];

const nodeModulesExists = await nodeModulesDirectoryExist(cloneDir);
if (!nodeModulesExists) {
commands.unshift("npm ci");
}
const nodeModulesExists = await nodeModulesDirectoryExist(cloneDir);
if (!nodeModulesExists) {
commands.unshift('npm ci');
}

const nxCommand = nxProject
? `npx nx run-many -t ${env.cmd} --`
: `npx serverless ${env.cmd}`;
const verboseOption = env.debug ? "--verbose" : "";
const serverlessCommand = `${nxCommand} --stage ${env.stage} --aws-profile ${env.profile}${verboseOption}`;
const nxCommand = nxProject
? `npx nx run-many -t ${env.cmd} --`
: `npx serverless ${env.cmd}`;
const verboseOption = env.debug ? '--verbose' : '';
const serverlessCommand = `${nxCommand} --stage ${env.stage} --aws-profile ${env.profile}${verboseOption}`;

commands.push(serverlessCommand);
commands.push(serverlessCommand);

await runCLICommand(commands);
await runCLICommand(commands);

deploymentStatus = true;
} catch (error) {
console.log(
"Deployment failed! Please check the logs for more details.Error:",
error as Error
);
deploymentStatus = false;
} finally {
const statusCode = await uploadDeploymentBadge(deploymentStatus);
process.exit(statusCode);
}
deploymentStatus = true;
} catch (error) {
console.log(
'Deployment failed! Please check the logs for more details.Error:',
error as Error
);
deploymentStatus = false;
} finally {
const statusCode = await uploadDeploymentBadge(deploymentStatus);
process.exit(statusCode);
}
}

// Execute the main function
Expand Down
60 changes: 30 additions & 30 deletions pipe/env.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,35 @@
interface Env {
debug: boolean;
stage: string;
profile: string;
cmd: string;
awsAccessKeyId?: string;
awsSecretAccessKey?: string;
cfnRole?: string;
uploadBadge: boolean;
appUsername?: string;
appPassword?: string;
timezone: string;
bitbucketBranch?: string;
bitbucketRepoSlug?: string;
bitbucketWorkspace?: string;
servicesPath?: string;
debug: boolean;
stage: string;
profile: string;
cmd: string;
awsAccessKeyId?: string;
awsSecretAccessKey?: string;
cfnRole?: string;
uploadBadge: boolean;
appUsername?: string;
appPassword?: string;
timezone: string;
bitbucketBranch?: string;
bitbucketRepoSlug?: string;
bitbucketWorkspace?: string;
servicesPath?: string;
}

export const env: Env = {
debug: process.env.DEBUG === "true",
stage: process.env.STAGE || "stg",
profile: process.env.PROFILE || "bitbucket-deployer",
cmd: process.env.cmd || "deploy",
awsAccessKeyId: process.env.AWS_ACCESS_KEY_ID,
awsSecretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
cfnRole: process.env.CFN_ROLE,
uploadBadge: process.env.UPLOAD_BADGE === "true",
appUsername: process.env.APP_USERNAME,
appPassword: process.env.APP_PASSWORD,
timezone: process.env.TIMEZONE || "Australia/Adelaide",
bitbucketBranch: process.env.BITBUCKET_BRANCH,
bitbucketRepoSlug: process.env.BITBUCKET_REPO_SLUG,
bitbucketWorkspace: process.env.BITBUCKET_WORKSPACE,
servicesPath: process.env.servicesPath || "/services",
debug: process.env.DEBUG === 'true',
stage: process.env.STAGE || 'stg',
profile: process.env.PROFILE || 'bitbucket-deployer',
cmd: process.env.cmd || 'deploy',
awsAccessKeyId: process.env.AWS_ACCESS_KEY_ID,
awsSecretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
cfnRole: process.env.CFN_ROLE,
uploadBadge: process.env.UPLOAD_BADGE === 'true',
appUsername: process.env.APP_USERNAME,
appPassword: process.env.APP_PASSWORD,
timezone: process.env.TIMEZONE || 'Australia/Adelaide',
bitbucketBranch: process.env.BITBUCKET_BRANCH,
bitbucketRepoSlug: process.env.BITBUCKET_REPO_SLUG,
bitbucketWorkspace: process.env.BITBUCKET_WORKSPACE,
servicesPath: process.env.servicesPath || '/services',
};
24 changes: 12 additions & 12 deletions pipe/findNodeModules.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import * as fs from "fs";
import * as fs from 'fs';

export async function nodeModulesDirectoryExist(
directoryPath: string
directoryPath: string
): Promise<boolean> {
return fs.promises
.access(`${directoryPath}/node_modules`, fs.constants.F_OK)
.then(() => true)
.catch((error) => {
if (error.code === "ENOENT") {
return false;
} else {
throw error;
}
});
return fs.promises
.access(`${directoryPath}/node_modules`, fs.constants.F_OK)
.then(() => true)
.catch((error) => {
if (error.code === 'ENOENT') {
return false;
} else {
throw error;
}
});
}
Loading
Loading