salix/db/docker.js

167 lines
5.2 KiB
JavaScript
Raw Normal View History

const exec = require('child_process').exec;
const log = require('fancy-log');
const dataSources = require('../loopback/server/datasources.json');
module.exports = class Docker {
constructor(name) {
Object.assign(this, {
id: name,
name,
isRandom: name == null,
dbConf: Object.assign({}, dataSources.vn)
});
}
/**
* Builds the database image and runs a container. It only rebuilds the
* image when fixtures have been modified or when the day on which the
* image was built is different to today. Some workarounds have been used
* to avoid a bug with OverlayFS driver on MacOS.
2020-06-11 08:37:55 +00:00
*
* @param {Boolean} ci continuous integration environment argument
*/
2020-06-11 08:37:55 +00:00
async run(ci) {
let d = new Date();
let pad = v => v < 10 ? '0' + v : v;
let stamp = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
await this.execP(`docker build --build-arg STAMP=${stamp} -t salix-db ./db`);
let dockerArgs;
if (this.isRandom)
dockerArgs = '-p 3306';
else {
try {
await this.rm();
} catch (e) {}
dockerArgs = `--name ${this.name} -p 3306:${this.dbConf.port}`;
}
let runChown = process.platform != 'linux';
let container = await this.execP(`docker run --env RUN_CHOWN=${runChown} -d ${dockerArgs} salix-db`);
this.id = container.stdout;
try {
if (this.isRandom) {
let inspect = await this.execP(`docker inspect -f "{{json .NetworkSettings}}" ${this.id}`);
let netSettings = JSON.parse(inspect.stdout);
2020-06-11 08:37:55 +00:00
if (ci)
this.dbConf.host = netSettings.Gateway;
this.dbConf.port = netSettings.Ports['3306/tcp'][0]['HostPort'];
}
if (runChown) await this.wait();
} catch (err) {
if (this.isRandom)
await this.rm();
throw err;
}
}
/**
* Does the minium effort to start the database container, if it doesn't exists
* calls the 'docker' task, if it is started does nothing. Keep in mind that when
* you do not rebuild the docker you may be using an outdated version of it.
* See the 'docker' task for more info.
*/
async start() {
let state;
try {
let result = await this.execP(`docker inspect -f "{{json .State}}" ${this.id}`);
state = JSON.parse(result.stdout);
} catch (err) {
return await this.run();
}
switch (state.Status) {
case 'running':
return;
case 'exited':
await this.execP(`docker start ${this.id}`);
await this.wait();
return;
default:
throw new Error(`Unknown docker status: ${state.Status}`);
}
}
wait() {
return new Promise((resolve, reject) => {
const mysql = require('mysql2');
let interval = 100;
let elapsedTime = 0;
let maxInterval = 4 * 60 * 1000;
let myConf = {
user: this.dbConf.username,
password: this.dbConf.password,
host: this.dbConf.host,
port: this.dbConf.port
};
log('Waiting for MySQL init process...');
async function checker() {
elapsedTime += interval;
let state;
try {
let result = await this.execP(`docker container inspect -f "{{json .State}}" ${this.id}`);
state = JSON.parse(result.stdout);
} catch (err) {
return reject(new Error(err.message));
}
if (state.Status === 'exited')
return reject(new Error('Docker exited, please see the docker logs for more info'));
let conn = mysql.createConnection(myConf);
conn.on('error', () => {});
conn.connect(err => {
conn.destroy();
if (!err) {
log('MySQL process ready.');
return resolve();
}
if (elapsedTime >= maxInterval)
reject(new Error(`MySQL not initialized whithin ${elapsedTime / 1000} secs`));
else
setTimeout(bindedChecker, interval);
});
}
let bindedChecker = checker.bind(this);
bindedChecker();
});
}
rm() {
return this.execP(`docker rm -fv ${this.id}`);
}
/**
* Promisified version of exec().
*
* @param {String} command The exec command
* @return {Promise} The promise
*/
execP(command) {
return new Promise((resolve, reject) => {
exec(command, (err, stdout, stderr) => {
if (err)
reject(err);
else {
resolve({
stdout: stdout,
stderr: stderr
});
}
});
});
}
};