Permalink
Browse files

feat(images): image builder

  • Loading branch information...
jkuri committed Sep 19, 2017
1 parent 6f07dee commit c03c1ce723a093f5152548fbbe4c3350cad5f432

Some generated files are not rendered by default. Learn more.

Oops, something went wrong.
@@ -82,6 +82,7 @@
"@types/dockerode": "^2.5.0",
"@types/express": "^4.0.37",
"@types/express-session": "^1.15.3",
"@types/glob": "^5.0.32",
"@types/jsonwebtoken": "^7.2.3",
"@types/knex": "0.0.61",
"@types/minimist": "^1.2.0",
@@ -91,7 +92,6 @@
"@types/node-rsa": "^0.4.0",
"@types/request": "^2.0.3",
"@types/rimraf": "2.0.2",
"@types/shelljs": "^0.7.4",
"@types/sqlite3": "^3.1.1",
"@types/temp": "^0.8.29",
"@types/uuid": "^3.4.2",
@@ -126,11 +126,13 @@
"extract-text-webpack-plugin": "^3.0.0",
"file-loader": "^0.11.2",
"fs-extra": "^4.0.2",
"glob": "^7.1.2",
"html-loader": "^0.5.1",
"html-webpack-plugin": "^2.30.1",
"jsonwebtoken": "^8.0.1",
"minimist": "^1.2.0",
"mocha": "^3.5.3",
"monaco-editor": "^0.10.0",
"multer": "^1.3.0",
"ngx-uploader": "^3.3.11",
"node-rsa": "^0.4.2",
@@ -143,7 +145,6 @@
"rxjs": "^5.4.3",
"sass-loader": "^6.0.6",
"shebang": "0.0.1",
"shelljs": "^0.7.8",
"source-map-loader": "^0.2.1",
"style-loader": "^0.18.2",
"temp": "^0.8.3",
@@ -0,0 +1,59 @@
import { docker } from './docker';
import { getFilePath } from './utils';
import * as fs from 'fs-extra';
import { logger, LogMessageType } from './logger';
import { join } from 'path';
import { Observable, Subject } from 'rxjs';
import * as glob from 'glob';

export interface ImageData {
name: string;
dockerfile: string;
initsh: string;
}

export interface ImageBuildOutput {
name: string;
output: any;
}

export function buildDockerImage(data: ImageData): Observable<ImageBuildOutput> {
return new Observable(observer => {
prepareDirectory(data).then(() => {
const folderPath = getFilePath(`images/${data.name}`);
const src = glob.sync(folderPath + '/**/*').map(filePath => filePath.split('/').pop());

docker.buildImage({ context: folderPath, src: src }, { t: data.name })
.then(output => {
output.on('data', d => observer.next({ name: data.name, output: d.toString() }));
output.on('finish', () => observer.complete());
})
.catch(err => {
observer.error(err);
observer.complete();
});
});
});
}

function prepareDirectory(data: ImageData): Promise<void> {
const folderPath = getFilePath(`images/${data.name}`);
const dockerFilePath = join(folderPath, 'Dockerfile');
const initShFilePath = join(folderPath, 'init.sh');
const essentialFolderPath = getFilePath(`docker-essential`);

return fs.remove(folderPath)
.then(() => fs.ensureDir(folderPath))
.then(() => fs.copy(essentialFolderPath, folderPath))
.then(() => fs.writeFile(dockerFilePath, data.dockerfile, 'utf8'))
.then(() => fs.writeFile(initShFilePath, data.initsh, 'utf8'))
.catch(err => {
const msg: LogMessageType = {
message: `error preparing ${folderPath} for docker image build`,
type: 'error',
notify: false
};
logger.next(msg);
});
}

@@ -63,6 +63,7 @@ export function webRoutes(): express.Router {
router.use('/js', express.static(resolve(__dirname, '../app/js'), { index: false }));
router.use('/images', express.static(resolve(__dirname, '../app/images'), { index: false }));
router.use('/css/fonts', express.static(resolve(__dirname, '../app/fonts'), { index: false }));
router.use('/monaco', express.static(resolve(__dirname, '../monaco'), { index: false }));
router.use('/avatars', express.static(getFilePath('avatars'), { index: false }));

router.get('/setup', index);
@@ -17,6 +17,7 @@ import {
terminalEvents,
getJobProcesses
} from './process-manager';
import { buildDockerImage } from './image-builder';
import { getConfig } from './utils';
import * as https from 'https';
import * as http from 'http';
@@ -108,6 +109,18 @@ export class SocketServer {
});
});
break;

case 'buildImage': {
const imageData = event.data;

buildDockerImage(imageData).subscribe(event => {
console.log(event);
conn.next({ type: 'imageBuildProgress', data: event });
});
}

break;

case 'startBuild':
startBuild({ repositories_id: event.data.repositoryId })
.then(buildId => {
@@ -50,9 +50,10 @@ export function setHome(dirPath: string): void {
export function initSetup(): Promise<null> {
return makeAbstruseDir()
.then(() => makeCacheDir())
.then(() => ensureDirectory(getFilePath('images')))
.then(() => {
const srcDir = resolve(__dirname, '../../src/files');
const destDir = getFilePath('docker-files');
const srcDir = resolve(__dirname, '../../src/files/docker-essential');
const destDir = getFilePath('docker-essential');
return copyFile(srcDir, destDir);
})
.then(() => {
@@ -63,7 +64,7 @@ export function initSetup(): Promise<null> {
}

export function appReady(): boolean {
return existsSync(getFilePath('docker-images')) && existsSync(getFilePath('docker-files'));
return existsSync(getFilePath('images')) && existsSync(getFilePath('docker-essential'));
}

export function getRootDir(): string {
@@ -33,6 +33,8 @@ import { AppUserComponent } from './components/app-user';
import { AppSettingsComponent } from './components/app-settings';
import { AppTeamComponent } from './components/app-team';
import { AppLogsComponent } from './components/app-logs';
import { AppImagesComponent } from './components/app-images';
import { AppEditorComponent } from './components/app-editor';
import { AppDashboardComponent } from './components/app-dashboard';
import { AppLineChartComponent } from './components/app-line-chart';
import { AppProgressChartComponent } from './components/app-progress-chart';
@@ -55,6 +57,8 @@ import { AppProgressChartComponent } from './components/app-progress-chart';
AppTeamComponent,
AppUserComponent,
AppLogsComponent,
AppImagesComponent,
AppEditorComponent,
AppDashboardComponent,
AppLineChartComponent,
AppProgressChartComponent,
@@ -107,6 +111,11 @@ import { AppProgressChartComponent } from './components/app-progress-chart';
component: AppTeamComponent,
canActivate: [AuthGuard]
},
{
path: 'images',
component: AppImagesComponent,
canActivate: [AuthGuard]
},
{
path: 'user/:id',
component: AppUserComponent,
@@ -0,0 +1,3 @@
<div class="editor-wrapper">
<div class="editor-container" #editorContainer></div>
</div>
@@ -0,0 +1,140 @@
import {
AfterViewInit,
Component,
ElementRef,
EventEmitter,
Input,
NgZone,
Output,
ViewChild,
Renderer2,
Inject
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
import { DOCUMENT } from '@angular/common';
import { Subscription } from 'rxjs/Subscription';
import { WindowService } from '../../services/window.service';

let loadedMonaco = false;
let loadPromise: Promise<void>;
declare const monaco: any;
declare const require: any;

@Component({
selector: 'app-editor',
templateUrl: 'app-editor.component.html',
providers: [{
provide: NG_VALUE_ACCESSOR,
useExisting: AppEditorComponent,
multi: true
}]
})
export class AppEditorComponent implements AfterViewInit, ControlValueAccessor {
@ViewChild('editorContainer') editorContainer: ElementRef;
@Output() onInit = new EventEmitter<any>();
value: string;
editor: any;
opts: any;
windowResizeSubscription: Subscription;
propagateChange = (_: any) => {};
onTouched = () => {};

constructor(
private windowService: WindowService,
private zone: NgZone,
private renderer: Renderer2,
@Inject(DOCUMENT) private document: any
) { }

writeValue(val: any): void {
this.value = val;
setTimeout(() => {
if (this.editor && val) {
this.editor.setValue(val);
}
});
}

registerOnChange(fn: any): void {
this.propagateChange = fn;
}

registerOnTouched(fn: any): void {
this.onTouched = fn;
}

@Input('options')
set options(options: string) {
this.opts = options;
if (this.editor) {
this.editor.dispose();
this.initMonaco(this.opts);
}
}

get options(): string {
return this.opts;
}

ngAfterViewInit(): void {
if (loadedMonaco) {
loadPromise.then(() => this.initMonaco(this.opts));
} else {
loadedMonaco = true;
loadPromise = new Promise<void>((resolve: any) => {
if (typeof (<any>window).monaco === 'object') {
resolve();
}

let onGotAmdLoader: any = () => {
(<any>window).require.config({ paths: { 'vs': 'monaco/vs' } });
(<any>window).require(['vs/editor/editor.main'], () => {
this.initMonaco(this.opts);
resolve();
});
};

if (!(<any>window).require) {
let loaderScript: HTMLScriptElement = this.renderer.createElement('script');
loaderScript.type = 'text/javascript';
loaderScript.src = 'monaco/vs/loader.js';
loaderScript.addEventListener('load', onGotAmdLoader);
this.document.body.appendChild(loaderScript);
} else {
onGotAmdLoader();
}
});
}
}

private initMonaco(options: any): void {
this.editor = monaco.editor.create(this.editorContainer.nativeElement, options);
if (this.value) {
this.editor.setValue(this.value);
}

this.editor.onDidChangeModelContent((e: any) => {
let val = this.editor.getValue();
this.propagateChange(val);
this.zone.run(() => this.value = val);
});

if (this.windowResizeSubscription) {
this.windowResizeSubscription.unsubscribe();
}

this.windowResizeSubscription = this.windowService.resize.subscribe(() => this.editor.layout());
this.onInit.emit(this.editor);
}

ngOnDestroy() {
if (this.windowResizeSubscription) {
this.windowResizeSubscription.unsubscribe();
}

if (this.editor) {
this.editor.dispose();
this.editor = null;
}
}
}
Oops, something went wrong.

0 comments on commit c03c1ce

Please sign in to comment.