Permalink
Browse files

feat(sshd): enable ssh into containers

  • Loading branch information...
jkuri committed Aug 4, 2017
1 parent 7d2b780 commit 5dd76988c657257bb5f0533cfc236502e35ef8c9
@@ -240,7 +240,7 @@ export function queueSetupDockerImage(name: string): Observable<any> {
return jobOutput;
}

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

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

export function restartJobWithSSH(jobId: number): Promise<void> {
return getJobProcesses()
.then(procs => {
const jobProcess = procs.find(job => job.job_id === jobId);
if (jobProcess) {
let jobData;
return dbJob.resetJob(jobId)
.then(job => {
jobData = job;
jobEvents.next({
type: 'process',
build_id: job.builds_id,
job_id: job.id,
data: 'jobRestarted'
});

return stopJob(jobId);
})
.then(() => queueJob(jobData.builds_id, jobData.id, true));
} else {
return dbJob.getJob(jobId).then(job => {
jobEvents.next({
type: 'process',
build_id: job.builds_id,
job_id: job.id,
data: 'jobRestarted'
});

return queueJob(job.builds_id, job.id, true);
});
}
});
}

export function stopJob(jobId: number): Promise<any> {
return new Promise(resolve => {
getJobProcesses()
@@ -376,9 +410,10 @@ export function stopJob(jobId: number): Promise<any> {
});
}

function prepareJob(buildId: number, jobId: number, cmds: any): Observable<JobMessage> {
function prepareJob(buildId: number, jobId: number,
cmds: any, ssh = false): Observable<JobMessage> {
return new Observable(observer => {
const job = startBuildProcess(buildId, jobId, cmds, 'abstruse');
const job = startBuildProcess(buildId, jobId, cmds, 'abstruse', ssh);

job.subscribe(output => {
const message: JobMessage = {
@@ -23,12 +23,12 @@ export interface SpawnedProcessOutput {
}

export interface ProcessOutput {
type: 'data' | 'exit' | 'container';
type: 'data' | 'exit' | 'container' | 'exposedPort';
data: string;
}

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

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

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

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

function getContainerExposedPort(name: string, port: number): Observable<ProcessOutput> {
return new Observable(observer => {
const process = nodePty.spawn('docker', [
'port',
name,
port
]);

process.on('data', data => observer.next({ type: 'exposedPort', data: data.split(':')[1] }));
process.on('exit', () => observer.complete());
});
}

export function startDockerImageSetupJob(name: string): Job {
let pty = new PtyInstance();
let job: Job = {
@@ -10,6 +10,7 @@ import {
findDockerImageBuildJob,
jobEvents,
restartJob,
restartJobWithSSH,
stopJob,
restartBuild,
stopBuild,
@@ -106,6 +107,12 @@ export class SocketServer {
conn.next({ type: 'jobRestarted', data: event.data.jobId });
});
break;
case 'restartJobWithSSH':
restartJobWithSSH(parseInt(event.data.jobId, 10))
.then(() => {
conn.next({ type: 'jobRestarted', data: event.data.jobId });
});
break;
case 'stopJob':
stopJob(event.data.jobId)
.then(() => {
@@ -13,7 +13,7 @@
<div class="build-top-container" [ngClass]="{ green: job?.status === 'success', yellow: job?.status === 'running', red: job?.status === 'failed' }">
<div class="build-top-content">
<div class="columns is-multiline">
<div class="column is-9">
<div class="column is-2">
<h1 class="bold">
<span><a [routerLink]="['/repo', job?.build.repository.id]">{{ job?.build.head_full_name }}</a></span>
<span class="build-icon">
@@ -24,7 +24,18 @@ <h1 class="bold">
</span>
</h1>
</div>
<div class="column is-3 justify-end right-buttons-top">
<div class="column is-5">
<code *ngIf="sshd" class="ssh">ssh {{ sshd.split(':')[0] }} -p {{ sshd.split(':')[1] }} -l abstruse</code>
</div>
<div class="column is-5 justify-end right-buttons-top">
<button class="button is-fullwidth" name="btn-restart-ssh" type="button" (click)="restartJobWithSSH($event)" [disabled]="processing">
<div class="centered">
<span class="icon">
<img src="images/icons/restart.svg">
</span>
<span>Restart (enable SSH)</span>
</div>
</button>
<button class="button is-fullwidth" name="btn-restart" type="button" (click)="restartJob($event)" [disabled]="processing">
<div class="centered">
<span class="icon">
@@ -25,6 +25,7 @@ export class AppJobComponent implements OnInit, OnDestroy {
terminalInput: any;
timeWords: string;
processing: boolean;
sshd: string;

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

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

restartJobWithSSH(e: MouseEvent): void {
e.preventDefault();
e.stopPropagation();
this.terminalInput = { clear: true };
this.processing = true;
this.socketService.emit({ type: 'restartJobWithSSH', data: { jobId: this.id } });
}

stopJob(e: MouseEvent): void {
e.preventDefault();
e.stopPropagation();
@@ -17,6 +17,15 @@
padding: 20px
background: linear-gradient(to bottom, $background, $background-secondary)

.ssh
padding: 5px 10px
background: $background
color: $white
display: block
margin: 5px 10px 0 10px
font-size: 13px
border: 1px solid $divider

.avatar-img
border-radius: 50%
margin: 4px 5px 0 0

0 comments on commit 5dd7698

Please sign in to comment.