Skip to content

Commit f7b35b5

Browse files
committed
feat(deploy): support for deploy to aws s3
1 parent b90e6df commit f7b35b5

File tree

3 files changed

+296
-6
lines changed

3 files changed

+296
-6
lines changed

src/api/deploy.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { Observable, Observer } from 'rxjs';
2+
import { s3Deploy } from './deploy/aws-s3';
3+
import { codeDeploy } from './deploy/aws-code-deploy';
4+
5+
export function deploy(preferences: any, container: string, variables: string[]): Observable<any> {
6+
return new Observable((observer: Observer<any>) => {
7+
if (preferences) {
8+
const provider = preferences.provider;
9+
deployProvider(provider, preferences, container, variables).subscribe(event => {
10+
observer.next(event);
11+
},
12+
err => observer.error(err),
13+
() => observer.complete());
14+
} else {
15+
observer.complete();
16+
}
17+
});
18+
}
19+
20+
function deployProvider(provider, preferences, container, variables): Observable<any> {
21+
switch (provider) {
22+
case 's3':
23+
return s3Deploy(preferences, container, variables);
24+
case 'codeDeploy':
25+
return codeDeploy(preferences, container, variables);
26+
default:
27+
return new Observable((observer: Observer<any>) => {
28+
observer.error({
29+
type: 'containerError',
30+
data: `Deployment provider ${provider} is not supported.`
31+
});
32+
observer.complete();
33+
});
34+
}
35+
}
36+
37+
export function findFromEnvVariables(variables, property) {
38+
let value = variables.find(v => v.startsWith(property));
39+
40+
if (value) {
41+
const tmp = value.split('=');
42+
if (tmp.length > 1) {
43+
return tmp[1];
44+
}
45+
}
46+
47+
return null;
48+
}

src/api/deploy/aws-s3.ts

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
import { Observable, Observer } from 'rxjs';
2+
import { attachExec } from '../docker';
3+
import { CommandType } from '../config';
4+
import { findFromEnvVariables } from '../deploy';
5+
import * as style from 'ansi-styles';
6+
import chalk from 'chalk';
7+
8+
export function s3Deploy(
9+
preferences: any, container: string, variables: string[]): Observable<any> {
10+
return new Observable((observer: Observer<any>) => {
11+
12+
// 1. check preferences
13+
const bucket = preferences.bucket;
14+
let accessKeyId = findFromEnvVariables(variables, 'accessKeyId');
15+
let secretAccessKey = findFromEnvVariables(variables, 'secretAccessKey');
16+
let region = findFromEnvVariables(variables, 'region');
17+
let errors = false;
18+
19+
if (!bucket) {
20+
const msg = chalk.red('bucket is not set in yml config file \r\n');
21+
observer.next({ type: 'data', data: msg});
22+
errors = true;
23+
}
24+
25+
if (!accessKeyId) {
26+
if (preferences && preferences.accessKeyId) {
27+
accessKeyId = preferences.accessKeyId;
28+
} else {
29+
const msg = chalk.red('accessKeyId is not set in environment '
30+
+ 'variables or in yml config \r\n');
31+
observer.next({ type: 'data', data: msg});
32+
errors = true;
33+
}
34+
}
35+
36+
if (!secretAccessKey) {
37+
if (preferences && preferences.secretAccessKey) {
38+
secretAccessKey = preferences.secretAccessKey;
39+
} else {
40+
const msg = chalk.red('secretAccessKey is not set in environment variables or'
41+
+ ' in yml config \r\n');
42+
observer.next({ type: 'data', data: msg});
43+
errors = true;
44+
}
45+
}
46+
47+
if (!region) {
48+
if (preferences && preferences.region) {
49+
region = preferences.region;
50+
} else {
51+
const msg =
52+
chalk.red('region is not set in environment variables or in yml config file \r\n');
53+
observer.next({ type: 'data', data: msg});
54+
errors = true;
55+
}
56+
}
57+
58+
if (!errors) {
59+
let msg = style.yellow.open + style.bold.open + '==> deploy started' +
60+
style.bold.close + style.yellow.close + '\r\n';
61+
observer.next({ type: 'data', data: msg });
62+
63+
// 2. check if appspec.yml exists (otherwise create it)
64+
appSpecExists(container).then(exists => {
65+
let commands = [];
66+
if (!exists) {
67+
commands.push(
68+
{ type: CommandType.deploy, command: `echo version: 0.0 >> appspec.yml` },
69+
{ type: CommandType.deploy, command: `echo os: linux >> appspec.yml` },
70+
{ type: CommandType.deploy, command: `echo files: >> appspec.yml` },
71+
{ type: CommandType.deploy, command: `echo ' - source: ./' >> appspec.yml` },
72+
{ type: CommandType.deploy, command: `echo ' destination: ./' >> appspec.yml` }
73+
);
74+
}
75+
76+
return Observable
77+
.concat(...commands.map(command => attachExec(container, command)))
78+
.toPromise();
79+
})
80+
.then(result => {
81+
if (!(result && result.data === 0)) {
82+
const msg = `creating appspec.yml failed`;
83+
observer.next({ type: 'containerError', data: msg});
84+
return Promise.reject(-1);
85+
}
86+
87+
// 3. install awscli and set credentials
88+
let command = { type: CommandType.deploy, command: 'sudo apt-get install awscli -y' };
89+
90+
return attachExec(container, command).toPromise();
91+
})
92+
.then(result => {
93+
if (!(result && result.data === 0)) {
94+
const msg = `apt-get install awscli failed`;
95+
observer.next({ type: 'containerError', data: msg});
96+
return Promise.reject(-1);
97+
}
98+
99+
let command = {
100+
type: CommandType.deploy,
101+
command: `aws configure set aws_access_key_id ${accessKeyId}`
102+
};
103+
104+
return attachExec(container, command).toPromise();
105+
})
106+
.then(result => {
107+
if (!(result && result.data === 0)) {
108+
const msg = 'aws configure aws_access_key_id failed';
109+
observer.next({ type: 'containerError', data: msg});
110+
return Promise.reject(-1);
111+
}
112+
113+
let command = {
114+
type: CommandType.deploy,
115+
command: `aws configure set aws_secret_access_key ${secretAccessKey}`
116+
};
117+
118+
return attachExec(container, command).toPromise();
119+
})
120+
.then(result => {
121+
if (!(result && result.data === 0)) {
122+
const msg = 'aws configure aws_secret_access_key failed';
123+
observer.next({ type: 'containerError', data: msg});
124+
return Promise.reject(-1);
125+
}
126+
127+
let command = {
128+
type: CommandType.deploy, command: `aws configure set region ${region}`
129+
};
130+
131+
return attachExec(container, command).toPromise();
132+
})
133+
.then(result => {
134+
if (!(result && result.data === 0)) {
135+
const msg = 'aws configure region failed';
136+
observer.next({ type: 'containerError', data: msg});
137+
return Promise.reject(-1);
138+
}
139+
140+
// 4. check if application allready exists (otherwise create it)
141+
return applicationExists(container, preferences.bucket);
142+
})
143+
.then(exists => {
144+
let application = [
145+
{ type: CommandType.deploy, command: `aws s3 mb s3://${preferences.bucket}` }
146+
];
147+
148+
if (!exists) {
149+
let cmd = `aws deploy create-application --application-name ${preferences.bucket}`;
150+
application.push( { type: CommandType.deploy, command: cmd } );
151+
}
152+
153+
return Observable
154+
.concat(...application.map(command => attachExec(container, command)))
155+
.toPromise();
156+
})
157+
.then(result => {
158+
if (!(result && result.data === 0)) {
159+
const msg = `aws deploy failed`;
160+
observer.next({ type: 'containerError', data: msg});
161+
return Promise.reject(-1);
162+
}
163+
164+
// 5. deploy
165+
const zipName = preferences.bucket;
166+
const deploy = {
167+
type: CommandType.deploy,
168+
command: `aws deploy push --application-name ${preferences.bucket}`
169+
+ ` --s3-location s3://${preferences.bucket}/${zipName}.zip`
170+
};
171+
172+
return attachExec(container, deploy).toPromise();
173+
})
174+
.then(result => {
175+
if (!(result && result.data === 0)) {
176+
const msg = `aws deploy push failed`;
177+
observer.next({ type: 'containerError', data: msg});
178+
return Promise.reject(-1);
179+
}
180+
181+
let msg = style.yellow.open + style.bold.open + '==> deployment completed successfully!'
182+
+ style.bold.close + style.yellow.close + '\r\n';
183+
observer.next({ type: 'data', data: msg });
184+
observer.complete();
185+
})
186+
.catch(err => {
187+
observer.error(err);
188+
observer.complete();
189+
});
190+
} else {
191+
observer.error(-1);
192+
observer.complete();
193+
}
194+
});
195+
}
196+
197+
function appSpecExists(container): Promise<any> {
198+
return new Promise((resolve, reject) => {
199+
let appSpec = false;
200+
attachExec(container, { type: CommandType.deploy, command: 'ls'})
201+
.subscribe(event => {
202+
if (event && event.data) {
203+
if (String(event.data).indexOf('appspec.yml') != -1) {
204+
appSpec = true;
205+
}
206+
}
207+
},
208+
err => reject(err),
209+
() => resolve(appSpec));
210+
});
211+
}
212+
213+
function applicationExists(container, application): Promise<any> {
214+
return new Promise((resolve, reject) => {
215+
const getApplicationCommand = 'aws deploy list-applications';
216+
let appExists = false;
217+
attachExec(container, { type: CommandType.deploy, command: getApplicationCommand })
218+
.subscribe(event => {
219+
if (event && event.data) {
220+
if (String(event.data).indexOf(application) != -1) {
221+
appExists = true;
222+
}
223+
}
224+
},
225+
err => reject(err),
226+
() => resolve(appExists));
227+
});
228+
}

src/api/process.ts

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
import * as docker from './docker';
22
import * as child_process from 'child_process';
3-
import { generateRandomId, getFilePath, prepareCommands } from './utils';
3+
import { generateRandomId, prepareCommands } from './utils';
4+
import { getFilePath } from './setup';
45
import { getRepositoryByBuildId } from './db/repository';
56
import { Observable } from 'rxjs';
67
import { CommandType, Command, CommandTypePriority } from './config';
78
import { JobProcess } from './process-manager';
89
import chalk from 'chalk';
910
import * as style from 'ansi-styles';
11+
import { deploy } from './deploy';
1012

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

3941
const name = 'abstruse_' + proc.build_id + '_' + proc.job_id;
40-
const envs = proc.commands.filter(cmd => cmd.command.startsWith('export'))
42+
const envs = proc.commands.filter(cmd => {
43+
return typeof cmd.command === 'string' && cmd.command.startsWith('export');
44+
})
4145
.map(cmd => cmd.command.replace('export', ''))
4246
.reduce((acc, curr) => acc.concat(curr.split(' ')), [])
4347
.concat(proc.env.reduce((acc, curr) => acc.concat(curr.split(' ')), []))
@@ -48,11 +52,18 @@ export function startBuildProcess(
4852
const installTypes = [CommandType.before_install, CommandType.install];
4953
const scriptTypes = [CommandType.before_script, CommandType.script,
5054
CommandType.after_success, CommandType.after_failure, CommandType.after_script];
51-
const deployTypes = [CommandType.before_deploy, CommandType.deploy, CommandType.after_deploy];
5255
const gitCommands = prepareCommands(proc, gitTypes);
5356
const installCommands = prepareCommands(proc, installTypes);
5457
const scriptCommands = prepareCommands(proc, scriptTypes);
55-
const deployCommands = prepareCommands(proc, deployTypes);
58+
let beforeDeployCommands = prepareCommands(proc, [CommandType.before_deploy]);
59+
const afterDeployCommands = prepareCommands(proc, [CommandType.after_deploy]);
60+
const deployCommands = prepareCommands(proc, [CommandType.deploy]);
61+
let deployPreferences;
62+
if (deployCommands.length) {
63+
deployPreferences = deployCommands
64+
.map(p => p.command)
65+
.reduce((a, b) => Object.assign(b, a));
66+
}
5667

5768
let restoreCache: Observable<any> = Observable.empty();
5869
let saveCache: Observable<any> = Observable.empty();
@@ -103,14 +114,17 @@ export function startBuildProcess(
103114
.concat(...installCommands.map(cmd => docker.attachExec(name, cmd)))
104115
.concat(saveCache)
105116
.concat(...scriptCommands.map(cmd => docker.attachExec(name, cmd)))
106-
.concat(...deployCommands.map(cmd => docker.attachExec(name, cmd)))
117+
.concat(...beforeDeployCommands.map(cmd => docker.attachExec(name, cmd)))
118+
.concat(deploy(deployPreferences, name, envs))
119+
.concat(...afterDeployCommands.map(cmd => docker.attachExec(name, cmd)))
107120
.timeoutWith(idleTimeout, Observable.throw(new Error('command timeout')))
108121
.takeUntil(Observable.timer(jobTimeout).timeInterval().mergeMap(() => {
109122
return Observable.throw('job timeout');
110123
}))
111124
.subscribe((event: ProcessOutput) => {
112125
if (event.type === 'containerError') {
113-
const msg = chalk.red(event.data.json.message) || chalk.red(event.data);
126+
const msg =
127+
chalk.red((event.data.json && event.data.json.message) || event.data);
114128
observer.next({ type: 'exit', data: msg });
115129
observer.error(msg);
116130
} else if (event.type === 'containerInfo') {

0 commit comments

Comments
 (0)