Skip to content

Commit 5dd7698

Browse files
committed
feat(sshd): enable ssh into containers
1 parent 7d2b780 commit 5dd7698

File tree

6 files changed

+97
-9
lines changed

6 files changed

+97
-9
lines changed

src/api/process-manager.ts

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ export function queueSetupDockerImage(name: string): Observable<any> {
240240
return jobOutput;
241241
}
242242

243-
function queueJob(buildId: number, jobId: number): Promise<void> {
243+
function queueJob(buildId: number, jobId: number, ssh = false): Promise<void> {
244244
let commands;
245245
return dbJob.updateJob({ id: jobId, start_time: new Date(), status: 'queued' })
246246
.then(jobData => {
@@ -252,7 +252,7 @@ function queueJob(buildId: number, jobId: number): Promise<void> {
252252
build_id: buildId,
253253
job_id: jobId,
254254
status: 'queued',
255-
job: prepareJob(buildId, jobId, JSON.parse(commands)),
255+
job: prepareJob(buildId, jobId, JSON.parse(commands), ssh),
256256
log: []
257257
};
258258

@@ -331,6 +331,40 @@ export function restartJob(jobId: number): Promise<void> {
331331
});
332332
}
333333

334+
export function restartJobWithSSH(jobId: number): Promise<void> {
335+
return getJobProcesses()
336+
.then(procs => {
337+
const jobProcess = procs.find(job => job.job_id === jobId);
338+
if (jobProcess) {
339+
let jobData;
340+
return dbJob.resetJob(jobId)
341+
.then(job => {
342+
jobData = job;
343+
jobEvents.next({
344+
type: 'process',
345+
build_id: job.builds_id,
346+
job_id: job.id,
347+
data: 'jobRestarted'
348+
});
349+
350+
return stopJob(jobId);
351+
})
352+
.then(() => queueJob(jobData.builds_id, jobData.id, true));
353+
} else {
354+
return dbJob.getJob(jobId).then(job => {
355+
jobEvents.next({
356+
type: 'process',
357+
build_id: job.builds_id,
358+
job_id: job.id,
359+
data: 'jobRestarted'
360+
});
361+
362+
return queueJob(job.builds_id, job.id, true);
363+
});
364+
}
365+
});
366+
}
367+
334368
export function stopJob(jobId: number): Promise<any> {
335369
return new Promise(resolve => {
336370
getJobProcesses()
@@ -376,9 +410,10 @@ export function stopJob(jobId: number): Promise<any> {
376410
});
377411
}
378412

379-
function prepareJob(buildId: number, jobId: number, cmds: any): Observable<JobMessage> {
413+
function prepareJob(buildId: number, jobId: number,
414+
cmds: any, ssh = false): Observable<JobMessage> {
380415
return new Observable(observer => {
381-
const job = startBuildProcess(buildId, jobId, cmds, 'abstruse');
416+
const job = startBuildProcess(buildId, jobId, cmds, 'abstruse', ssh);
382417

383418
job.subscribe(output => {
384419
const message: JobMessage = {

src/api/process.ts

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@ export interface SpawnedProcessOutput {
2323
}
2424

2525
export interface ProcessOutput {
26-
type: 'data' | 'exit' | 'container';
26+
type: 'data' | 'exit' | 'container' | 'exposedPort';
2727
data: string;
2828
}
2929

3030
export function startBuildProcess(buildId: number, jobId: number,
31-
commands: string[], image: string): Observable<ProcessOutput> {
31+
commands: string[], image: string, ssh = false): Observable<ProcessOutput> {
3232
return new Observable(observer => {
3333
const name = 'abstruse_' + buildId + '_' + jobId;
3434
const vars = commands.filter(cmd => cmd.startsWith('export'))
@@ -39,6 +39,8 @@ export function startBuildProcess(buildId: number, jobId: number,
3939
commands = commands.filter(cmd => !cmd.startsWith('export'));
4040

4141
startContainer(name, image, vars)
42+
.concat(ssh ? executeInContainer(name, 'sudo /etc/init.d/ssh start') : null)
43+
.concat(ssh ? getContainerExposedPort(name, 22) : null)
4244
.concat(...commands.map(command => executeInContainer(name, command)))
4345
.subscribe((event: ProcessOutput) => {
4446
observer.next(event);
@@ -100,7 +102,7 @@ function executeInContainer(name: string, command: string): Observable<ProcessOu
100102

101103
function startContainer(name: string, image: string, vars = []): Observable<ProcessOutput> {
102104
return new Observable(observer => {
103-
const args = ['run', '--privileged', '-dit'].concat(vars).concat('--name', name, image);
105+
const args = ['run', '--privileged', '-dit', '-P'].concat(vars).concat('--name', name, image);
104106
const process = nodePty.spawn('docker', args);
105107

106108
process.on('exit', exitCode => {
@@ -137,6 +139,19 @@ function stopContainer(name: string): Observable<ProcessOutput> {
137139
});
138140
}
139141

142+
function getContainerExposedPort(name: string, port: number): Observable<ProcessOutput> {
143+
return new Observable(observer => {
144+
const process = nodePty.spawn('docker', [
145+
'port',
146+
name,
147+
port
148+
]);
149+
150+
process.on('data', data => observer.next({ type: 'exposedPort', data: data.split(':')[1] }));
151+
process.on('exit', () => observer.complete());
152+
});
153+
}
154+
140155
export function startDockerImageSetupJob(name: string): Job {
141156
let pty = new PtyInstance();
142157
let job: Job = {

src/api/socket.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
findDockerImageBuildJob,
1111
jobEvents,
1212
restartJob,
13+
restartJobWithSSH,
1314
stopJob,
1415
restartBuild,
1516
stopBuild,
@@ -106,6 +107,12 @@ export class SocketServer {
106107
conn.next({ type: 'jobRestarted', data: event.data.jobId });
107108
});
108109
break;
110+
case 'restartJobWithSSH':
111+
restartJobWithSSH(parseInt(event.data.jobId, 10))
112+
.then(() => {
113+
conn.next({ type: 'jobRestarted', data: event.data.jobId });
114+
});
115+
break;
109116
case 'stopJob':
110117
stopJob(event.data.jobId)
111118
.then(() => {

src/app/components/app-job/app-job.component.html

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
<div class="build-top-container" [ngClass]="{ green: job?.status === 'success', yellow: job?.status === 'running', red: job?.status === 'failed' }">
1414
<div class="build-top-content">
1515
<div class="columns is-multiline">
16-
<div class="column is-9">
16+
<div class="column is-2">
1717
<h1 class="bold">
1818
<span><a [routerLink]="['/repo', job?.build.repository.id]">{{ job?.build.head_full_name }}</a></span>
1919
<span class="build-icon">
@@ -24,7 +24,18 @@ <h1 class="bold">
2424
</span>
2525
</h1>
2626
</div>
27-
<div class="column is-3 justify-end right-buttons-top">
27+
<div class="column is-5">
28+
<code *ngIf="sshd" class="ssh">ssh {{ sshd.split(':')[0] }} -p {{ sshd.split(':')[1] }} -l abstruse</code>
29+
</div>
30+
<div class="column is-5 justify-end right-buttons-top">
31+
<button class="button is-fullwidth" name="btn-restart-ssh" type="button" (click)="restartJobWithSSH($event)" [disabled]="processing">
32+
<div class="centered">
33+
<span class="icon">
34+
<img src="images/icons/restart.svg">
35+
</span>
36+
<span>Restart (enable SSH)</span>
37+
</div>
38+
</button>
2839
<button class="button is-fullwidth" name="btn-restart" type="button" (click)="restartJob($event)" [disabled]="processing">
2940
<div class="centered">
3041
<span class="icon">

src/app/components/app-job/app-job.component.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ export class AppJobComponent implements OnInit, OnDestroy {
2525
terminalInput: any;
2626
timeWords: string;
2727
processing: boolean;
28+
sshd: string;
2829

2930
constructor(
3031
private socketService: SocketService,
@@ -55,6 +56,8 @@ export class AppJobComponent implements OnInit, OnDestroy {
5556
this.processing = false;
5657
} else if (event.type === 'jobRestarted' && event.data === this.id) {
5758
this.processing = false;
59+
} else if (event.type === 'exposedPort') {
60+
this.sshd = `${document.location.hostname}:${event.data}`;
5861
}
5962
});
6063

@@ -105,6 +108,14 @@ export class AppJobComponent implements OnInit, OnDestroy {
105108
this.socketService.emit({ type: 'restartJob', data: { jobId: this.id } });
106109
}
107110

111+
restartJobWithSSH(e: MouseEvent): void {
112+
e.preventDefault();
113+
e.stopPropagation();
114+
this.terminalInput = { clear: true };
115+
this.processing = true;
116+
this.socketService.emit({ type: 'restartJobWithSSH', data: { jobId: this.id } });
117+
}
118+
108119
stopJob(e: MouseEvent): void {
109120
e.preventDefault();
110121
e.stopPropagation();

src/app/styles/build-details.sass

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,15 @@
1717
padding: 20px
1818
background: linear-gradient(to bottom, $background, $background-secondary)
1919

20+
.ssh
21+
padding: 5px 10px
22+
background: $background
23+
color: $white
24+
display: block
25+
margin: 5px 10px 0 10px
26+
font-size: 13px
27+
border: 1px solid $divider
28+
2029
.avatar-img
2130
border-radius: 50%
2231
margin: 4px 5px 0 0

0 commit comments

Comments
 (0)