Skip to content

Commit

Permalink
build: Custom build scripts
Browse files Browse the repository at this point in the history
  • Loading branch information
maeek committed Dec 17, 2021
1 parent bf52c16 commit fe5b6cc
Show file tree
Hide file tree
Showing 8 changed files with 346 additions and 1 deletion.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,4 @@ dist
htpasswd
local-registry

.log
**.log
65 changes: 65 additions & 0 deletions bin/build-all.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
const { getServicesToBuild } = require('./lib/utils');
const Builder = require('./lib/Builder');

const run = async () => {
console.time('\nBuild duration ');

const services = getServicesToBuild();
const builders = services.map(service => new Builder(service));
const builds = [];

builders.forEach(builder => {
builder.on('start', info => {
console.log(`[${new Date(Date.now()).toISOString()}] - ${info.name} - build started`);
console.log(' Build config:');
Object.keys(info.config).forEach(key => {
console.log(` ${key}: ${info.config[key]}`);
});
console.log('\n');
});
builder.on('progress', log => {
console.log(
log.split('\n')
.map(line => `[${new Date(Date.now()).toISOString()}] - ${builder.name} - ${line}`)
.join('\n')
);
});
builder.on('error', error => console.error(error));
builder.on('finish', obj => {
console.log(
`[${new Date(Date.now()).toISOString()}] - ${builder.name} - Build finished with exit code ${obj.code}`
);
builds.push(obj);
builder.offAll();
});
});

await Promise.all(builders.map(builder => builder.run()));

console.timeEnd('\nBuild duration ');

if (builders.some(builder => builder.hasErrors)) {
console.error('One or more of the build failed');
console.error(
'Failed builds: ',
builders.filter(builder => builder.hasErrors).map(builder => builder.name));
process.exit(1);
}

console.table(builds.map(build => ({
name: build.name,
tag: build.image.name,
isPublished: build.image.published,
imageSize: build.image.humanSize
})));

process.exit(0);
};

try {
run();
} catch (e) {
console.timeEnd('\nBuild duration ');
console.error(e);
process.exit(1);
}
84 changes: 84 additions & 0 deletions bin/lib/Builder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
const path = require('path');
const child_proc = require('child_process');
const config = require('../../config/config');
const Command = require('./Command');

const isDev = config.mode === 'development';

class Builder extends Command {

static DOCKERFILE_PATH = path.resolve(
config.basePath,
`deploy/docker/Dockerfile${isDev ? '.development' : ''}`
);

static getBuildCommandArguments = (
serviceName,
tag,
dockerfilePath = Builder.DOCKERFILE_PATH
) => [
'-f',
`${dockerfilePath}`,
'--build-arg',
`SERVICE_NAME=${serviceName}`,
'-t',
`${tag}`,
'production/'
];

constructor(name, conf = {}) {
super(name, conf);

const registryUrl = config.registryUrl ? `${config.registryUrl}/` : '';
const tag = `${registryUrl}${config.imgPrefix}${this.name}`;
const commandArgs = Builder.getBuildCommandArguments(
this.name, tag, this.config.dockerfilePath
);

this.config = {
bin: config.dockerPath || 'docker',
arguments: [
'build',
...(this.config?.buildArgs || []),
...commandArgs
],
cwd: config.basePath,
tag,
dockerfilePath: Builder.DOCKERFILE_PATH,
...conf
};

this.on('beforeFinish', (obj) => {
const transformed = {
...obj
};

if (obj.code === 0) {
const size = Builder.getImageSize(this.config.tag);

transformed.image = {
name: this.config.tag,
published: false,
size,
humanSize: Builder.humanFileSize(size)
}
}

return transformed;
});
}

static getImageSize = (tag) => {
return child_proc
.execSync(`${config.dockerPath || 'docker'} image inspect ${tag} --format='{{.Size}}'`)
.toString()
.trim();
}

static humanFileSize = (size) => {
const i = Math.floor(Math.log(size) / Math.log(1024));
return (size / Math.pow(1024, i)).toFixed(2) + ' ' + ['B', 'kB', 'MB', 'GB', 'TB'][i];
};
}

module.exports = Builder;
129 changes: 129 additions & 0 deletions bin/lib/Command.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
const child_proc = require('child_process');
const config = require('../../config/config');

class Command {
#subscribers = [];

running = false;

proc = null;

log = '';

errors = [];

constructor(name, conf = {}) {
this.name = name;
this.config = {
bin: config.dockerPath || 'docker',
arguments: [],
cwd: config.basePath,
...conf
};
}

get isRunning() {
return this.running;
}

get hasErrors() {
return this.errors.length > 0;
}

run = () => new Promise((resolve, reject) => {
try {
this.running = true;

this.proc = child_proc.spawn(
this.config.bin,
this.config.arguments,
{
cwd: this.config.cwd
}
);

this.notify('start', {
name: this.name,
config: this.config
});

this.proc.stdout.on('data', (data) => {
this.log += data.toString();
this.notify('progress', data.toString());
});

this.proc.stderr.on('data', (data) => {
this.log += data.toString();
this.errors.push(data);
this.notify('error', data.toString());
});

this.proc.on('exit', (code) => {
this.running = false;

let exitResults = {
name: this.name,
code,
config: this.config,
proc: this.proc,
log: this.log,
errors: this.errors,
}

const beforeFinishHooks = this.#subscribers.filter(subscriber => subscriber.event === 'beforeFinish');
const beforeFinishHooksData = beforeFinishHooks.reduce((acc, subscriber) => {
return {
...acc,
...(subscriber.callback({ ...acc }) || {})
};
}, { ...exitResults });

exitResults = {
...exitResults,
...beforeFinishHooksData
}

this.notify('finish', exitResults);

resolve(exitResults);
});
}
catch (e) {
this.running = false;
reject(e);
}
});

/**
*
* @param {string} event 'start' | 'progress' | 'beforeFinish' | 'finish' | 'error'
*
* @param {*} callback
*/
on(event, callback) {
this.#subscribers.push({
event,
callback
});
}

off = (event, callback) => {
this.#subscribers = this.#subscribers.filter(subscriber => {
return subscriber.event !== event && subscriber.callback !== callback;
});
}

offAll = () => {
this.#subscribers = [];
};

notify = (event, data) => {
this.#subscribers.forEach(subscriber => {
if (subscriber.event === event) {
subscriber.callback(data);
}
});
}
}

module.exports = Command;
22 changes: 22 additions & 0 deletions bin/lib/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
const fs = require('fs');
const path = require('path');
const config = require('../../config/config');

const getFolders = (source, exclude = config.excludedFolders) => {
return fs.readdirSync(path.resolve(config.basePath, source), { withFileTypes: true })
.filter(dirent => dirent.isDirectory() && !exclude.includes(dirent.name))
.map(dirent => dirent.name);
};

const getServicesToBuild = () => {
const services = getFolders(config.servicesPath);
return services.filter(service => (
!config.excludedFolders.includes(service)
&& fs.existsSync(path.resolve(config.basePath, config.servicesPath, service, 'package.json')))
);
};

module.exports = {
getFolders,
getServicesToBuild
};
21 changes: 21 additions & 0 deletions config/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
const path = require('path');

module.exports = {
// Build mode: 'development' or 'production'
mode: process.env.NODE_ENV,
// The base directory (absolute path!) for resolving the entry option
basePath: path.resolve(__dirname, '..'),
// Services folder
servicesPath: 'production',
// Registry URL to push images to
registryUrl: process.env.REGISTRY_URL || 'localhost:5000',
// Prefix for all images
imgPrefix: process.env.BUILD_IMAGE_PREFIX || '',
// Exclude folders from build
excludedFolders: [
'node_modules',
'types',
],
// Docker binary path
// dockerPath: '/usr/bin/docker',
};
Empty file removed docs/.gitkeep
Empty file.
24 changes: 24 additions & 0 deletions makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
SHELL := /bin/bash

build-all: setup_env
node bin/build-all.js | tee build.log

publish-all: setup_env
docker push

setup_env:
set -a
source config/.env

docker-compose-prod-up: setup_env
docker-compose -f deploy/docker/docker-compose.yml up --build

docker-compose-dev-up: setup_env
docker-compose -f deploy/docker/docker-compose.test.yml up --build

docker-compose-prod-down: setup_env
docker-compose -f deploy/docker/docker-compose.yml down --remove-orphans

docker-compose-dev-down: setup_env
docker-compose -f deploy/docker/docker-compose.test.yml down --remove-orphans

0 comments on commit fe5b6cc

Please sign in to comment.