Permalink
Browse files

feat(): repository environment variables

  • Loading branch information...
Izak88 committed Sep 4, 2017
1 parent f23f952 commit f6cdf8d46d075e8a660ad51034651e88a550294b
@@ -0,0 +1,16 @@
import { EnvironmentVariable } from './model';

export function insertEnvironmentVariable(data: any): Promise<any> {
return new Promise((resolve, reject) => {
new EnvironmentVariable().save(data, { method: 'insert' })
.then(buildRun => !buildRun ? reject() : resolve(buildRun.toJSON()));
});
}

export function removeEnvironmentVariable(id: number): Promise<any> {
return new Promise((resolve, reject) => {
new EnvironmentVariable({ id: id }).destroy()
.then(() => resolve(true))
.catch(() => reject());
});
}
@@ -105,6 +105,14 @@ export function create(): Promise<null> {
t.boolean('read').notNullable().defaultTo(false);
t.timestamps();
}))
.then(() => schema.createTableIfNotExists('environment_variables', (t: knex.TableBuilder) => {
t.increments('id').unsigned().primary();
t.integer('repositories_id').notNullable();
t.foreign('repositories_id').references('repositories.id');
t.string('name').notNullable();
t.string('value').notNullable();
t.timestamps();
}))
.then(() => resolve())
.catch(err => {
console.error(err);
@@ -19,6 +19,7 @@ export class Repository extends Bookshelf.Model<any> {
access_token() { return this.belongsTo(AccessToken, 'access_tokens_id'); }
builds() { return this.hasMany(Build, 'repositories_id'); }
permissions() { return this.hasMany(Permission, 'repositories_id'); }
variables() { return this.hasMany(EnvironmentVariable, 'repositories_id'); }
}

export class Build extends Bookshelf.Model<any> {
@@ -63,3 +64,9 @@ export class Log extends Bookshelf.Model<any> {
get tableName() { return 'logs'; }
get hasTimestamps() { return true; }
}

export class EnvironmentVariable extends Bookshelf.Model<any> {
get tableName() { return 'environment_variables'; }
get hasTimestamps() { return true; }
repository() { return this.belongsTo(Repository, 'repositories_id'); }
}
@@ -5,7 +5,7 @@ import { addRepositoryPermissionToEveryone } from './permission';
export function getRepository(id: number): Promise<any> {
return new Promise((resolve, reject) => {
new Repository({ id: id })
.fetch({ withRelated: ['access_token'] })
.fetch({ withRelated: ['access_token', 'variables'] })
.then(repo => !repo ? reject(repo) : resolve(repo.toJSON()))
.catch(err => reject(err));
});
@@ -109,7 +109,7 @@ export function getRepositoryByBuildId(buildId: number): Promise<any> {
new Repository().query(qb => {
qb.leftJoin('builds', 'repositories.id', 'builds.repositories_id');
qb.where('builds.id', '=', buildId);
}).fetch().then(repo => {
}).fetch({ withRelated: ['access_token', 'variables'] }).then(repo => {
if (!repo) {
reject();
} else {
@@ -4,7 +4,7 @@ import { insertBuild, updateBuild, getBuild, getBuildStatus, getLastRunId } from
import { insertBuildRun, updateBuildRun } from './db/build-run';
import * as dbJob from './db/job';
import * as dbJobRuns from './db/job-run';
import { getRepositoryOnly } from './db/repository';
import { getRepositoryOnly, getRepositoryByBuildId } from './db/repository';
import { getRemoteParsedConfig, JobsAndEnv, CommandType } from './config';
import { killContainer } from './docker';
import * as logger from './logger';
@@ -170,8 +170,10 @@ export function startBuild(data: any): Promise<any> {

export function startJob(proc: JobProcess): Promise<void> {
return Promise.resolve()
.then(() => {
startBuildProcess(proc, 'abstruse')
.then(() => getRepositoryByBuildId(proc.build_id))
.then(repo => {
let envVariables = repo.variables.map(v => `${v.name}=${v.value}`);
startBuildProcess(proc, 'abstruse', envVariables)
.subscribe(event => {
const msg: JobProcessEvent = {
build_id: proc.build_id,
@@ -30,7 +30,8 @@ export interface ProcessOutput {

export function startBuildProcess(
proc: JobProcess,
image: string
image: string,
variables: string[]
): Observable<ProcessOutput> {
return new Observable(observer => {
const name = 'abstruse_' + proc.build_id + '_' + proc.job_id;
@@ -39,7 +40,8 @@ export function startBuildProcess(
.reduce((acc, curr) => {
return acc.concat(curr.split(' '));
}, [])
.concat(proc.env.reduce((acc, curr) => acc.concat(['-e', curr]), []));
.concat(proc.env.reduce((acc, curr) => acc.concat(['-e', curr]), []))
.concat(variables.reduce((acc, curr) => acc.concat(['-e', curr]), []));
proc.commands = proc.commands.filter(cmd => !cmd.command.startsWith('export'));

let debug: Observable<any> = Observable.empty();
@@ -51,7 +53,7 @@ export function startBuildProcess(
'sleep 3 && sudo /etc/init.d/openbox start'),
executeInContainer(name, 'export DISPLAY=:99 && ' +
'x11vnc -xkb -noxrecord -noxfixes -noxdamage -display :99 ' +
'-forever -bg -rfbauth /etc/x11vnc.pass -rfbport 5900'),
'-forever -bg -rfbauth /etc/x11vnc.pass -rfbport 5900'),
getContainerExposedPort(name, 5900)
]);
}
@@ -73,8 +75,21 @@ export function startBuildProcess(
observer.complete();
stopContainer(name).subscribe((event: ProcessOutput) => {
observer.next(event);
}, err => {
sub.unsubscribe();
observer.error(err);
stopContainer(name).subscribe((event: ProcessOutput) => {
observer.next(event);
observer.next({ type: 'data', data: err });
});
}, () => {
sub.unsubscribe();
observer.complete();
stopContainer(name).subscribe((event: ProcessOutput) => {
observer.next(event);
});
});
});
});
});
}

@@ -35,6 +35,7 @@ import {
getUserBuildPermissions,
getUserJobPermissions
} from './db/permission';
import { insertEnvironmentVariable, removeEnvironmentVariable } from './db/environment-variable';
import { imageExists } from './docker';
import { checkApiRequestAuth } from './security';
import * as multer from 'multer';
@@ -154,11 +155,9 @@ export function userRoutes(): express.Router {
});

router.post('/create', (req: express.Request, res: express.Response) => {
createUser(req.body).then(() => {
return res.status(200).json({ status: true });
}).catch(err => {
return res.status(200).json({ status: false });
});
createUser(req.body)
.then(() => res.status(200).json({ status: true }))
.catch(err => res.status(200).json({ status: false }));
});

router.post('/save', (req: express.Request, res: express.Response) => {
@@ -276,9 +275,11 @@ export function repositoryRoutes(): express.Router {
});

router.post('/permission', (req: express.Request, res: express.Response) => {
updatePermission(req.body)
.then(() => res.status(200).json({ data: true }))
.catch(() => res.status(200).json({ data: false }));
checkApiRequestAuth(req).then(() => {
updatePermission(req.body)
.then(() => res.status(200).json({ data: true }))
.catch(() => res.status(200).json({ data: false }));
}).catch(err => res.status(401).json({ data: 'Not Authorized' }));
});

return router;
@@ -402,6 +403,28 @@ export function permissionRoutes(): express.Router {
return router;
}

export function environmentVariableRoutes(): express.Router {
const router = express.Router();

router.post('/add', (req: express.Request, res: express.Response) => {
checkApiRequestAuth(req).then(() => {
insertEnvironmentVariable(req.body)
.then(() => res.status(200).json({ data: true }))
.catch(() => res.status(200).json({ data: false }));
}).catch(err => res.status(401).json({ data: 'Not Authorized' }));
});

router.get('/remove/:id', (req: express.Request, res: express.Response) => {
checkApiRequestAuth(req).then(() => {
removeEnvironmentVariable(req.params.id)
.then(() => res.status(200).json({ data: true }))
.catch(() => res.status(200).json({ data: false }));
}).catch(err => res.status(401).json({ data: 'Not Authorized' }));
});

return router;
}

function index(req: express.Request, res: express.Response): void {
return res.status(200).sendFile(resolve(__dirname, '../app/index.html'));
}
@@ -44,6 +44,7 @@ export class ExpressServer implements IExpressServer {
app.use('/api/builds', routes.buildRoutes());
app.use('/api/jobs', routes.jobRoutes());
app.use('/api/permissions', routes.permissionRoutes());
app.use('/api/variables', routes.environmentVariableRoutes());
app.use('/badge', routes.badgeRoutes());
app.use(routes.webRoutes());

@@ -11,8 +11,8 @@ <h1 class="mr15">{{ repo?.full_name }}</h1>
<div class="nav-center"></div>
<div class="nav-right">
<div class="group-buttons" *ngIf="userId">
<button class="group-button" [class.is-active]="tab === 'builds'" (click)="tab = 'builds'">Builds</button>
<button class="group-button" [class.is-active]="tab === 'settings'" (click)="tab = 'settings'">Settings</button>
<button class="group-button" name="btn-builds" [class.is-active]="tab === 'builds'" (click)="tab = 'builds'">Builds</button>
<button class="group-button" name="btn-settings" [class.is-active]="tab === 'settings'" (click)="tab = 'settings'">Settings</button>
</div>
</div>
</div>
@@ -92,6 +92,48 @@ <h2>Settings</h2>
</div>
</div>

<div class="column is-12" *ngIf="tab === 'settings'">
<div class="columns">
<div class="column is-12">
<div class="settings-container">
<h2>Environment Variables</h2>

<div class="add-variable">
<h3>Add New Variable:</h3>
<form class="control-form" (ngSubmit)="addEnvironmentVariable()" #addForm="ngForm">
<div class="form-field">
<label class="form-label">Name</label>
<input class="form-input" type="text" placeholder="Name" name="name" required [(ngModel)]="environmentVariableForm.name">
</div>
<div class="form-field">
<label class="form-label">Value</label>
<input class="form-input" type="text" placeholder="Value" name="value" required [(ngModel)]="environmentVariableForm.value">
</div>
<div class="form-field">
<button type="submit" class="button green float-right" name="btn-add-variable" [disabled]="!addForm.form.valid" required>Add New Variable</button>
</div>
</form>
</div>

<div class="variables" *ngIf="repo?.variables?.length">
<h3>Repository Environment Variables:</h3>
<div class="columns list-item list-item-slim" *ngFor="let v of repo?.variables; let i = index;">
<div class="column is-10">
<span class="bold">{{ v.name }}: {{ v.value }}</span>
</div>
<div class="column is-2 justify-center">
<span class="icon" name="remove-variable" (click)="removeVariable(v.id)">
<img src="images/icons/remove.svg">
</span>
</div>
</div>
</div>

</div>
</div>
</div>
</div>

</div>
</div>
</div>
@@ -13,6 +13,12 @@ export interface IRepoForm {
access_tokens_id: any;
}

export interface VariableForm {
name: string;
value: string;
repositories_id: number;
}

@Component({
selector: 'app-repository',
templateUrl: 'app-repository.component.html'
@@ -34,6 +40,7 @@ export class AppRepositoryComponent implements OnInit, OnDestroy {
hideMoreButton: boolean;
userData: any;
userId: string | null;
environmentVariableForm: VariableForm;

constructor(
private route: ActivatedRoute,
@@ -47,6 +54,7 @@ export class AppRepositoryComponent implements OnInit, OnDestroy {
this.fetching = false;
this.limit = 5;
this.offset = 0;
this.environmentVariableForm = { name: null, value: null, repositories_id: null };
}

ngOnInit() {
@@ -222,4 +230,17 @@ export class AppRepositoryComponent implements OnInit, OnDestroy {
}
});
}

addEnvironmentVariable(): void {
this.environmentVariableForm.repositories_id = this.repo.id;
this.api.addNewEnvironmentVariable(this.environmentVariableForm)
.subscribe(() => this.fetch());

this.environmentVariableForm = { name: null, value: null, repositories_id: null };
}

removeVariable(id: number): void {
this.api.removeNewEnvironmentVariable(id)
.subscribe(() => this.fetch());
}
}
@@ -155,6 +155,14 @@ export class ApiService {
return this.http.get(`https://api.github.com/users/${username}`);
}

addNewEnvironmentVariable(data: any): Observable<any> {
return this.post(`${this.url}/variables/add`, data, true);
}

removeNewEnvironmentVariable(id: number): Observable<any> {
return this.get(`${this.url}/variables/remove/${id}`, null, true);
}

private get(url: string, searchParams: URLSearchParams = null, auth = false): Observable<any> {
let headers = new Headers();
if (auth) {

0 comments on commit f6cdf8d

Please sign in to comment.