How to add `--health-cmd='mysqladmin ping --silent'` and `--health-interval='1s'` options to dockerode's createContainer() function? - node.js

I want to use node.js to manipulate docker containers. And I found a module called dockerode, which seems like the tool I need.
However, I haven't found any way to add --health-cmd and --health-interval options to createContainer function.
These 2 options are used in docker run command. You can find them by docker run --help | grep health:
--health-cmd string Command to run to check health
--health-interval duration Time between running the check (ms|s|m|h) (default 0s)
I've tried the following code, but the 2 options don't work inside the code.
const Docker = require('dockerode')
const docker = new Docker({socketPath: '/var/run/docker.sock'})
docker.createContainer({
Image: 'mysql:5.7',
name: 'myContainer',
env: ['MYSQL_ROOT_PASSWORD=mypassword'],
'health-cmd': 'mysqladmin ping --silent',
'health-interval': '1s',
HostConfig: {
PortBindings: {
'3306/tcp': [
{
HostPort: '3306'
}
]
}
}
}, (err, container) => {
if (err) {
console.error(`create err:${err}`)
} else {
container.start((err, data) => {
if (err) {
console.error(`start err: ${err}`)
} else {
console.log(`data: ${data}`)
}
})
}
})

Thanks to the help of this post. I have found the document of Docker Engine API and figured out the correct way to add the 2 options:
const Docker = require('dockerode')
const docker = new Docker({socketPath: '/var/run/docker.sock'})
docker.createContainer({
Image: 'mysql:5.7',
name: 'myContainer',
env: ['MYSQL_ROOT_PASSWORD=mypassword'],
Healthcheck: {
Test: ['CMD-SHELL', 'mysqladmin ping'],
Interval: 1000000000
},
HostConfig: {
PortBindings: {
'3306/tcp': [
{
HostPort: '3306'
}
]
}
}
}, (err, container) => {
if (err) {
console.error(`create err:${err}`)
} else {
container.start((err, data) => {
if (err) {
console.error(`start err: ${err}`)
} else {
console.log(`data: ${data}`)
}
})
}
})
You can find that createContainer calls POST /containers/create from the source code located in node_modules/dockerode/lib/docker.js.

Related

Node.js/child_process: I am having trouble successfully killing a given process to allow me to delete the given directory

The following code is meant to kill a given process and allow me to remove a directory and update it:
private async updateRepo(): Promise<void> {
try {
log.standard("Updating repo...");
if (this.active[this.repo]) {
while (!this.killed) {
this.active[this.repo].kill("SIGINT");
rmSync(`${__dirname}/scripts/${this.repo}`, { force: true, recursive: true });
};
delete this.active[this.repo];
await this.addRepo();
};
} catch(e) {
log.error(`Error updating ${this.repo}!`, e);
};
};
The following code is how I deploy and establish a given process:
private deploy(): void {
try {
log.standard(`Deploying ${this.repo}...`);
const thread = spawn(`${process.platform === "win32" ? "npm.cmd" : "npm"} install && ${process.platform === "win32" ? "npm.cmd" : "npm"} ${process.env.COMMAND != "" ? process.env.COMMAND : "start"}`, { cwd: `${__dirname}/scripts/${this.repo}`, shell: true });
log.success(`Successfully deployed ${this.repo}!`);
thread.stdout.on("data", (data: string) => {
this.active[this.repo] = thread;
log.repo(this.repo, data.toString());
});
thread.on("close", async () => {
this.killed = true;
log.standard(`${this.repo} has been killed!`);
});
thread.stderr.on("data", (data: string) => {
log.repo(this.repo, data.toString());
});
} catch(e) {
log.error(`Error deploying ${this.repo}!`, e);
};
};
this.active being of type active: Record<string, ChildProcessWithoutNullStreams> = {};.
On update my code is freezing on kill and not killing the process this is the output:
[LOG] 20:53:29 Checking for test-repo updates...
[LOG] 20:53:29 Update detected for test-repo!
[LOG] 20:53:29 Updating repo...
What I'm not understanding is why a given process cannot be promptly killed properly without freezing because I cannot remove the directory and update it without the given process being killed.

How to load fastify-env environments synchronously?

I'm working on fastify microservice and would like to use the fastify-env library to validate my env inputs and provide defaults throughout the whole app.
const fastify = require('fastify')()
fastify.register(require('fastify-env'), {
schema: {
type: 'object',
properties: {
PORT: { type: 'string', default: 3000 }
}
}
})
console.log(fastify.config) // undefined
const start = async opts => {
try {
console.log('config', fastify.config) // config undefined
await fastify.listen(3000, '::')
console.log('after', fastify.config) // after { PORT: '3000' }
} catch (err) {
fastify.log.error(err)
process.exit(1)
}
}
start()
How can I use the fastify.config object before the server starts?
Use ready() https://www.fastify.io/docs/latest/Server/#ready to wait for all plugins to be loaded. Then call listen() with your config variable.
try {
await fastify.ready(); // will load all plugins
await fastify.listen(...);
} catch (err) {
fastify.log.error(err);
process.exit(1);
}
fastify.register loads plugins asynchronously AFAIK. If you'd like to immediately use things from a specific plugin use:
fastify
.register(plugin)
.after(() => {
// This particular plugin is ready!
});

NodeJS VM2 proper way to access console when set to 'redirect'

I'm using the VM2 package to run user code. I'm trying to intercept console output and have set the NodeVM object's console property to 'redirect':
// Create a new sandbox VM for this request
const vm = new NodeVM( {
console: 'redirect',
timeout: 30000,
sandbox: { request, state, response },
require: {
external: true
}
});
According to the documentation that redirects console output to 'events'. I'm new to NodeJS, how do I hook into those events to capture the console.log messages executed inside the Sandbox?
After digging through the source code, I found this file where the event emit is occuring:
sandbox.js
if (vm.options.console === 'inherit') {
global.console = Contextify.readonly(host.console);
} else if (vm.options.console === 'redirect') {
global.console = {
log(...args) {
vm.emit('console.log', ...Decontextify.arguments(args));
return null;
},
info(...args) {
vm.emit('console.info', ...Decontextify.arguments(args));
return null;
},
warn(...args) {
vm.emit('console.warn', ...Decontextify.arguments(args));
return null;
},
error(...args) {
vm.emit('console.error', ...Decontextify.arguments(args));
return null;
},
dir(...args) {
vm.emit('console.dir', ...Decontextify.arguments(args));
return null;
},
time: () => {},
timeEnd: () => {},
trace(...args) {
vm.emit('console.trace', ...Decontextify.arguments(args));
return null;
}
};
}
All you need to do to listen to these events is to bind an event listener on the vm you've created:
// Create a new sandbox VM for this request
const vm = new NodeVM( {
console: 'redirect',
require: {
external: ['request']
}
});
vm.on('console.log', (data) => {
console.log(`VM stdout: ${data}`);
});
Likewise, you can bind to console.log, console.info, console.warn, console.error, console.dir, and console.trace. Hopefully this will save someone else some time.

Nightwatch not terminating after browser.end()

I'm running Nightwatch after launching a child process that starts up my local servers. Nightwatch runs the tests, they complete successfully, and the browser windows all close, but the nightwatch process continues to run after printing the message "OK. 10 total assertions passed.".
I thought it may have something to do with how I'm watching events on the nightwatch process, but as far as I can tell I am watching all events that would indicate Nightwatch is exiting.
The method shutdown() in runner.js is never called. How do I get Nightwatch to terminate when the tests finish?
Update
If I remove the last test in sign-in.js then Nightwatch exits as expected.
runner.js
import spawn from 'cross-spawn'
// 1. start the dev server using production config
process.env.NODE_ENV = 'testing'
let servers
function shutdown (result) {
console.log('HERE', result)
try {
// Passing a negative PID to kill will terminate all child processes, not just the parent
if (servers) process.kill(-servers.pid)
} catch (e) {
console.error('Unable to shutdown servers, may need to be killed manually')
}
if (result) {
console.error(result)
process.exit(1)
} else {
process.exit(0)
}
}
function watch (child) {
child.on('close', shutdown)
child.on('disconnect', shutdown)
child.on('error', shutdown)
child.on('exit', shutdown)
child.on('uncaughtException', shutdown)
}
try {
servers = spawn('yarn', ['run', 'dev-all'], { cwd: '..', stdio: 'inherit', detached: true })
watch(servers)
// 2. run the nightwatch test suite against it
// to run in additional browsers:
// 1. add an entry in test/e2e/nightwatch.conf.json under "test_settings"
// 2. add it to the --env flag below
// or override the environment flag, for example: `npm run e2e -- --env chrome,firefox`
// For more information on Nightwatch's config file, see
// http://nightwatchjs.org/guide#settings-file
var opts = process.argv.slice(2)
if (opts.indexOf('--config') === -1) {
opts = opts.concat(['--config', 'e2e/nightwatch.conf.js'])
}
if (opts.indexOf('--env') === -1) {
opts = opts.concat(['--env', 'chrome'])
}
var runner = spawn('./node_modules/.bin/nightwatch', opts, { stdio: 'inherit' })
watch(runner)
watch(process)
} catch (error) {
shutdown(error)
}
nightwatch.conf.js
require('babel-register')
var config = require('../../frontend/config')
// http://nightwatchjs.org/guide#settings-file
module.exports = {
src_folders: ['e2e/specs'],
output_folder: 'e2e/reports',
custom_assertions_path: ['e2e/custom-assertions'],
selenium: {
start_process: true,
server_path: 'node_modules/selenium-server/lib/runner/selenium-server-standalone-3.0.1.jar',
host: '127.0.0.1',
port: 4444,
cli_args: {
'webdriver.chrome.driver': require('chromedriver').path
}
},
test_settings: {
default: {
selenium_port: 4444,
selenium_host: 'localhost',
silent: true,
globals: {
devServerURL: 'http://localhost:' + (process.env.PORT || config.dev.port)
}
},
chrome: {
desiredCapabilities: {
browserName: 'chrome',
javascriptEnabled: true,
acceptSslCerts: true
}
},
firefox: {
desiredCapabilities: {
browserName: 'firefox',
javascriptEnabled: true,
acceptSslCerts: true
}
}
}
}
sign-in.js (one of the tests)
import firebase from 'firebase-admin'
import uuid from 'uuid'
import * as firebaseSettings from '../../../backend/src/firebase-settings'
const PASSWORD = 'toomanysecrets'
function createUser (user) {
console.log('Creating user', user.uid)
let db = firebase.database()
return Promise.all([
firebase.auth().createUser({
uid: user.uid,
email: user.email,
emailVerified: true,
displayName: user.fullName,
password: PASSWORD
}),
db.ref('users').child(user.uid).set({
email: user.email,
fullName: user.fullName
}),
db.ref('roles').child(user.uid).set({
instructor: false
})
])
}
function destroyUser (user) {
if (!user) return
console.log('Removing user', user.uid)
let db = firebase.database()
try { db.ref('roles').child(user.uid).remove() } catch (e) {}
try { db.ref('users').child(user.uid).remove() } catch (e) {}
try { firebase.auth().deleteUser(user.uid) } catch (e) {}
}
module.exports = {
'Sign In links exist': browser => {
// automatically uses dev Server port from /config.index.js
// default: http://localhost:8080
// see nightwatch.conf.js
const devServer = browser.globals.devServerURL
browser
.url(devServer)
.waitForElementVisible('#container', 5000)
browser.expect.element('.main-nav').to.be.present
browser.expect.element('.main-nav a[href^=\'https://oauth.ais.msu.edu/oauth/authorize\']').to.be.present
browser.expect.element('.main-nav a[href^=\'/email-sign-in\']').to.be.present
browser.end()
},
'Successful Sign In with Email shows dashboard': browser => {
const devServer = browser.globals.devServerURL
firebase.initializeApp(firebaseSettings.appConfig)
let userId = uuid.v4()
let user = {
uid: userId,
email: `${userId}#test.com`,
fullName: 'Test User'
}
createUser(user)
browser.url(devServer)
.waitForElementVisible('.main-nav a[href^=\'/email-sign-in\']', 5000)
.click('.main-nav a[href^=\'/email-sign-in\']')
.waitForElementVisible('button', 5000)
.setValue('input[type=text]', user.email)
.setValue('input[type=password]', PASSWORD)
.click('button')
.waitForElementVisible('.main-nav a[href^=\'/sign-out\']', 5000)
.end(() => {
destroyUser(user)
})
}
}
After the tests complete successfully, I see the following:
grimlock:backend egillespie$ ps -ef | grep nightwatch
501 13087 13085 0 1:51AM ttys000 0:02.18 node ./node_modules/.bin/nightwatch --presets es2015,stage-0 --config e2e/nightwatch.conf.js --env chrome
I was not explicitly closing the Firebase connection. This caused the last test to hang indefinitely.
Here's how I am closing the connection after doing test cleanup:
browser.end(() => {
destroyUser(user).then(() => {
firebase.app().delete()
})
})
The destroyUser function now looks like this:
function destroyUser (user) {
if (!user) return Promise.resolve()
let db = firebase.database()
return Promise.all([
db.ref('roles').child(user.uid).remove(),
db.ref('users').child(user.uid).remove(),
firebase.auth().deleteUser(user.uid)
])
}
In my case (nightwatch with vue/vuetify) after each test like so:
afterEach:function(browser,done){
done();
}
AfterAll(async()=>{
await closeSession();
await stopWebDriver();
}
place this in the config file #Erik Gillespie
Nightwatch still have this issue with the browser.end()
If you run Nightwatch with node.js you can stop the process
by doing something like that:
browser.end(() => {
process.exit();
});
It will close the browser and end the process.
I have tried the following method:
In "nightwatch.conf.js",
"test_settings" {
"default" {
"silent": true,
...
},
...
}
I set "silent" from true to false.
It lead to becoming verbose in the console. And the chromedriver.exe will exit peacefully after running the tests
I was using the vue template from: https://github.com/vuejs-templates/pwa
My platform:
Windows 7 (64bit)
node v8.1.3
"nightwatch": "^0.9.16",
"selenium-server": "^3.6.0",
"chromedriver": "^2.33.1"

Null port bindings from inspect, the first time. a second time good. why?

I'm using https://github.com/apocas/dockerode to manage docker containers from a node.js app and have written the following function:
run: function(opts) {
var self = this;
return self.createContainer(opts).catch(function(e) {
return self.pull(opts.Image).then(function(stream) {
return self.followProgress(stream);
}).then(function() {
return self.createContainer(opts);
});
}).then(function(o) {
Bluebird.promisify(o.start, {context: o})();
return o;
}).then(function(o) {;
var inspect = Bluebird.promisify(
o.inspect, {context: o}
);
return inspect();
});
},
which I call like this:
bitcoind: function(opts) {
return this.run({
name: 'bitcoind',
Image: 'seegno/bitcoind:latest',
Env: ['BITCOIN_DATA=/data'],
Binds: [process.cwd() + '/bitcoind:/data'],
HostConfig: {
PortBindings: {
'8332/tcp': [{ HostPort: '8332' }]
}
},
Cmd: [
'-server',
'-rpcuser=' + opts.user,
'-rpcpassword=' + opts.pass,
'-printtoconsole'
]
});
},
and everything works perfectly, almost. if I start my bitcoind server like this:
docker.bitcoind(config.bitcoind).then(function(info) {
var p = info.NetworkSettings.Ports;
console.log(p);
});
the console shows "null". the ports didn't get set. the command line shows me proper values. running
$ docker inspect bitcoind
I see:
"Ports": {
"18332/tcp": null,
"18333/tcp": null,
"18444/tcp": null,
"8332/tcp": [
{
"HostIp": "0.0.0.0",
"HostPort": "8332"
}
],
"8333/tcp": null
},
which is what I expected. so out of curiosity I then ran:
docker.inspect('693f32482b46').then(function(info) {
console.log(info);
});
and that contains the port information! so I'm puzzled as to why the inspection I ran at the end of the run() would return everything but the port mappings. had I failed to wait correctly for the process to start, inspection would have returned an empty value.
thoughts as to what might be going on here or what I should do differently?
ok, the answer is I must wait for the start method to complete

Resources