2020-06-04 19:09:06 +00:00
|
|
|
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-04 19:09:06 +00:00
|
|
|
*/
|
2020-06-11 08:37:55 +00:00
|
|
|
async run(ci) {
|
2020-06-04 19:09:06 +00:00
|
|
|
let d = new Date();
|
|
|
|
let pad = v => v < 10 ? '0' + v : v;
|
|
|
|
let stamp = `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
|
2022-05-12 13:44:11 +00:00
|
|
|
|
|
|
|
log('Building container image...');
|
2020-06-04 19:09:06 +00:00
|
|
|
await this.execP(`docker build --build-arg STAMP=${stamp} -t salix-db ./db`);
|
2022-05-12 13:56:34 +00:00
|
|
|
log('Image built.');
|
2020-06-04 19:09:06 +00:00
|
|
|
|
|
|
|
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';
|
|
|
|
|
2022-05-12 13:44:11 +00:00
|
|
|
log('Starting container...');
|
2020-06-29 12:26:27 +00:00
|
|
|
const container = await this.execP(`docker run --env RUN_CHOWN=${runChown} -d ${dockerArgs} salix-db`);
|
2020-10-13 10:28:58 +00:00
|
|
|
this.id = container.stdout.trim();
|
2020-06-04 19:09:06 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2020-06-04 19:09:06 +00:00
|
|
|
this.dbConf.port = netSettings.Ports['3306/tcp'][0]['HostPort'];
|
|
|
|
}
|
|
|
|
|
2020-09-28 08:40:50 +00:00
|
|
|
await this.wait();
|
2020-06-04 19:09:06 +00:00
|
|
|
} 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}`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-06-16 10:27:17 +00:00
|
|
|
waitForHealthy() {
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
let interval = 100;
|
|
|
|
let elapsedTime = 0;
|
|
|
|
let maxInterval = 4 * 60 * 1000;
|
|
|
|
|
|
|
|
log('Waiting for MySQL init process...');
|
|
|
|
|
|
|
|
async function checker() {
|
|
|
|
elapsedTime += interval;
|
|
|
|
let status;
|
|
|
|
|
|
|
|
try {
|
|
|
|
let result = await this.execP(`docker inspect -f "{{.State.Health.Status}}" ${this.id}`);
|
|
|
|
status = result.stdout.trimEnd();
|
|
|
|
} catch (err) {
|
|
|
|
return reject(new Error(err.message));
|
|
|
|
}
|
|
|
|
|
|
|
|
if (status == 'unhealthy')
|
|
|
|
return reject(new Error('Docker exited, please see the docker logs for more info'));
|
|
|
|
|
|
|
|
if (status == 'healthy') {
|
|
|
|
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();
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2020-06-04 19:09:06 +00:00
|
|
|
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,
|
2022-01-20 14:33:53 +00:00
|
|
|
port: this.dbConf.port,
|
|
|
|
connectTimeout: maxInterval
|
2020-06-04 19:09:06 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
log('Waiting for MySQL init process...');
|
|
|
|
|
|
|
|
async function checker() {
|
|
|
|
elapsedTime += interval;
|
|
|
|
let state;
|
|
|
|
|
|
|
|
try {
|
2020-09-28 08:40:50 +00:00
|
|
|
let result = await this.execP(`docker inspect -f "{{json .State}}" ${this.id}`);
|
2020-06-04 19:09:06 +00:00
|
|
|
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);
|
2022-05-12 11:24:19 +00:00
|
|
|
|
2020-06-04 19:09:06 +00:00
|
|
|
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() {
|
2020-10-13 10:28:58 +00:00
|
|
|
return this.execP(`docker stop ${this.id} && docker rm -v ${this.id}`);
|
2020-06-04 19:09:06 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* 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
|
|
|
|
});
|
|
|
|
}
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|