Skip to content

Commit

Permalink
feat(deploy): support for deploy to aws s3
Browse files Browse the repository at this point in the history
  • Loading branch information
Izak88 committed Nov 24, 2017
1 parent b90e6df commit f7b35b5
Show file tree
Hide file tree
Showing 3 changed files with 296 additions and 6 deletions.
48 changes: 48 additions & 0 deletions src/api/deploy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { Observable, Observer } from 'rxjs';
import { s3Deploy } from './deploy/aws-s3';
import { codeDeploy } from './deploy/aws-code-deploy';

export function deploy(preferences: any, container: string, variables: string[]): Observable<any> {
return new Observable((observer: Observer<any>) => {
if (preferences) {
const provider = preferences.provider;
deployProvider(provider, preferences, container, variables).subscribe(event => {
observer.next(event);
},
err => observer.error(err),
() => observer.complete());
} else {
observer.complete();
}
});
}

function deployProvider(provider, preferences, container, variables): Observable<any> {
switch (provider) {
case 's3':
return s3Deploy(preferences, container, variables);
case 'codeDeploy':
return codeDeploy(preferences, container, variables);
default:
return new Observable((observer: Observer<any>) => {
observer.error({
type: 'containerError',
data: `Deployment provider ${provider} is not supported.`
});
observer.complete();
});
}
}

export function findFromEnvVariables(variables, property) {
let value = variables.find(v => v.startsWith(property));

if (value) {
const tmp = value.split('=');
if (tmp.length > 1) {
return tmp[1];
}
}

return null;
}
228 changes: 228 additions & 0 deletions src/api/deploy/aws-s3.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
import { Observable, Observer } from 'rxjs';
import { attachExec } from '../docker';
import { CommandType } from '../config';
import { findFromEnvVariables } from '../deploy';
import * as style from 'ansi-styles';
import chalk from 'chalk';

export function s3Deploy(
preferences: any, container: string, variables: string[]): Observable<any> {
return new Observable((observer: Observer<any>) => {

// 1. check preferences
const bucket = preferences.bucket;
let accessKeyId = findFromEnvVariables(variables, 'accessKeyId');
let secretAccessKey = findFromEnvVariables(variables, 'secretAccessKey');
let region = findFromEnvVariables(variables, 'region');
let errors = false;

if (!bucket) {
const msg = chalk.red('bucket is not set in yml config file \r\n');
observer.next({ type: 'data', data: msg});
errors = true;
}

if (!accessKeyId) {
if (preferences && preferences.accessKeyId) {
accessKeyId = preferences.accessKeyId;
} else {
const msg = chalk.red('accessKeyId is not set in environment '
+ 'variables or in yml config \r\n');
observer.next({ type: 'data', data: msg});
errors = true;
}
}

if (!secretAccessKey) {
if (preferences && preferences.secretAccessKey) {
secretAccessKey = preferences.secretAccessKey;
} else {
const msg = chalk.red('secretAccessKey is not set in environment variables or'
+ ' in yml config \r\n');
observer.next({ type: 'data', data: msg});
errors = true;
}
}

if (!region) {
if (preferences && preferences.region) {
region = preferences.region;
} else {
const msg =
chalk.red('region is not set in environment variables or in yml config file \r\n');
observer.next({ type: 'data', data: msg});
errors = true;
}
}

if (!errors) {
let msg = style.yellow.open + style.bold.open + '==> deploy started' +
style.bold.close + style.yellow.close + '\r\n';
observer.next({ type: 'data', data: msg });

// 2. check if appspec.yml exists (otherwise create it)
appSpecExists(container).then(exists => {
let commands = [];
if (!exists) {
commands.push(
{ type: CommandType.deploy, command: `echo version: 0.0 >> appspec.yml` },
{ type: CommandType.deploy, command: `echo os: linux >> appspec.yml` },
{ type: CommandType.deploy, command: `echo files: >> appspec.yml` },
{ type: CommandType.deploy, command: `echo ' - source: ./' >> appspec.yml` },
{ type: CommandType.deploy, command: `echo ' destination: ./' >> appspec.yml` }
);
}

return Observable
.concat(...commands.map(command => attachExec(container, command)))
.toPromise();
})
.then(result => {
if (!(result && result.data === 0)) {
const msg = `creating appspec.yml failed`;
observer.next({ type: 'containerError', data: msg});
return Promise.reject(-1);
}

// 3. install awscli and set credentials
let command = { type: CommandType.deploy, command: 'sudo apt-get install awscli -y' };

return attachExec(container, command).toPromise();
})
.then(result => {
if (!(result && result.data === 0)) {
const msg = `apt-get install awscli failed`;
observer.next({ type: 'containerError', data: msg});
return Promise.reject(-1);
}

let command = {
type: CommandType.deploy,
command: `aws configure set aws_access_key_id ${accessKeyId}`
};

return attachExec(container, command).toPromise();
})
.then(result => {
if (!(result && result.data === 0)) {
const msg = 'aws configure aws_access_key_id failed';
observer.next({ type: 'containerError', data: msg});
return Promise.reject(-1);
}

let command = {
type: CommandType.deploy,
command: `aws configure set aws_secret_access_key ${secretAccessKey}`
};

return attachExec(container, command).toPromise();
})
.then(result => {
if (!(result && result.data === 0)) {
const msg = 'aws configure aws_secret_access_key failed';
observer.next({ type: 'containerError', data: msg});
return Promise.reject(-1);
}

let command = {
type: CommandType.deploy, command: `aws configure set region ${region}`
};

return attachExec(container, command).toPromise();
})
.then(result => {
if (!(result && result.data === 0)) {
const msg = 'aws configure region failed';
observer.next({ type: 'containerError', data: msg});
return Promise.reject(-1);
}

// 4. check if application allready exists (otherwise create it)
return applicationExists(container, preferences.bucket);
})
.then(exists => {
let application = [
{ type: CommandType.deploy, command: `aws s3 mb s3://${preferences.bucket}` }
];

if (!exists) {
let cmd = `aws deploy create-application --application-name ${preferences.bucket}`;
application.push( { type: CommandType.deploy, command: cmd } );
}

return Observable
.concat(...application.map(command => attachExec(container, command)))
.toPromise();
})
.then(result => {
if (!(result && result.data === 0)) {
const msg = `aws deploy failed`;
observer.next({ type: 'containerError', data: msg});
return Promise.reject(-1);
}

// 5. deploy
const zipName = preferences.bucket;
const deploy = {
type: CommandType.deploy,
command: `aws deploy push --application-name ${preferences.bucket}`
+ ` --s3-location s3://${preferences.bucket}/${zipName}.zip`
};

return attachExec(container, deploy).toPromise();
})
.then(result => {
if (!(result && result.data === 0)) {
const msg = `aws deploy push failed`;
observer.next({ type: 'containerError', data: msg});
return Promise.reject(-1);
}

let msg = style.yellow.open + style.bold.open + '==> deployment completed successfully!'
+ style.bold.close + style.yellow.close + '\r\n';
observer.next({ type: 'data', data: msg });
observer.complete();
})
.catch(err => {
observer.error(err);
observer.complete();
});
} else {
observer.error(-1);
observer.complete();
}
});
}

function appSpecExists(container): Promise<any> {
return new Promise((resolve, reject) => {
let appSpec = false;
attachExec(container, { type: CommandType.deploy, command: 'ls'})
.subscribe(event => {
if (event && event.data) {
if (String(event.data).indexOf('appspec.yml') != -1) {
appSpec = true;
}
}
},
err => reject(err),
() => resolve(appSpec));
});
}

function applicationExists(container, application): Promise<any> {
return new Promise((resolve, reject) => {
const getApplicationCommand = 'aws deploy list-applications';
let appExists = false;
attachExec(container, { type: CommandType.deploy, command: getApplicationCommand })
.subscribe(event => {
if (event && event.data) {
if (String(event.data).indexOf(application) != -1) {
appExists = true;
}
}
},
err => reject(err),
() => resolve(appExists));
});
}
26 changes: 20 additions & 6 deletions src/api/process.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
import * as docker from './docker';
import * as child_process from 'child_process';
import { generateRandomId, getFilePath, prepareCommands } from './utils';
import { generateRandomId, prepareCommands } from './utils';
import { getFilePath } from './setup';
import { getRepositoryByBuildId } from './db/repository';
import { Observable } from 'rxjs';
import { CommandType, Command, CommandTypePriority } from './config';
import { JobProcess } from './process-manager';
import chalk from 'chalk';
import * as style from 'ansi-styles';
import { deploy } from './deploy';

export interface Job {
status: 'queued' | 'running' | 'success' | 'failed';
Expand Down Expand Up @@ -37,7 +39,9 @@ export function startBuildProcess(
const image = proc.image_name;

const name = 'abstruse_' + proc.build_id + '_' + proc.job_id;
const envs = proc.commands.filter(cmd => cmd.command.startsWith('export'))
const envs = proc.commands.filter(cmd => {
return typeof cmd.command === 'string' && cmd.command.startsWith('export');
})
.map(cmd => cmd.command.replace('export', ''))
.reduce((acc, curr) => acc.concat(curr.split(' ')), [])
.concat(proc.env.reduce((acc, curr) => acc.concat(curr.split(' ')), []))
Expand All @@ -48,11 +52,18 @@ export function startBuildProcess(
const installTypes = [CommandType.before_install, CommandType.install];
const scriptTypes = [CommandType.before_script, CommandType.script,
CommandType.after_success, CommandType.after_failure, CommandType.after_script];
const deployTypes = [CommandType.before_deploy, CommandType.deploy, CommandType.after_deploy];
const gitCommands = prepareCommands(proc, gitTypes);
const installCommands = prepareCommands(proc, installTypes);
const scriptCommands = prepareCommands(proc, scriptTypes);
const deployCommands = prepareCommands(proc, deployTypes);
let beforeDeployCommands = prepareCommands(proc, [CommandType.before_deploy]);
const afterDeployCommands = prepareCommands(proc, [CommandType.after_deploy]);
const deployCommands = prepareCommands(proc, [CommandType.deploy]);
let deployPreferences;
if (deployCommands.length) {
deployPreferences = deployCommands
.map(p => p.command)
.reduce((a, b) => Object.assign(b, a));
}

let restoreCache: Observable<any> = Observable.empty();
let saveCache: Observable<any> = Observable.empty();
Expand Down Expand Up @@ -103,14 +114,17 @@ export function startBuildProcess(
.concat(...installCommands.map(cmd => docker.attachExec(name, cmd)))
.concat(saveCache)
.concat(...scriptCommands.map(cmd => docker.attachExec(name, cmd)))
.concat(...deployCommands.map(cmd => docker.attachExec(name, cmd)))
.concat(...beforeDeployCommands.map(cmd => docker.attachExec(name, cmd)))
.concat(deploy(deployPreferences, name, envs))
.concat(...afterDeployCommands.map(cmd => docker.attachExec(name, cmd)))
.timeoutWith(idleTimeout, Observable.throw(new Error('command timeout')))
.takeUntil(Observable.timer(jobTimeout).timeInterval().mergeMap(() => {
return Observable.throw('job timeout');
}))
.subscribe((event: ProcessOutput) => {
if (event.type === 'containerError') {
const msg = chalk.red(event.data.json.message) || chalk.red(event.data);
const msg =
chalk.red((event.data.json && event.data.json.message) || event.data);
observer.next({ type: 'exit', data: msg });
observer.error(msg);
} else if (event.type === 'containerInfo') {
Expand Down

0 comments on commit f7b35b5

Please sign in to comment.