Skip to content

Commit

Permalink
fix(3062): fix Dockerfile not to use npm start, and add shutdown me…
Browse files Browse the repository at this point in the history
…thod (#143)

* Add tini and fix npm start

* Add shutdown method

* delete space of param in docs

* Update plugins/shutdown.js

Co-authored-by: Tiffany K <tiffanykyi@gmail.com>

---------

Co-authored-by: Tiffany K <tiffanykyi@gmail.com>
  • Loading branch information
sakka2 and tkyi authored Mar 22, 2024
1 parent d38f922 commit ab32467
Show file tree
Hide file tree
Showing 3 changed files with 183 additions and 1 deletion.
8 changes: 7 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,11 @@ RUN ln -s /usr/src/app/node_modules/screwdriver-store/config /config
# Expose the web service port
EXPOSE 80

# Add Tini
ENV TINI_VERSION v0.19.0
ADD https://github.com/krallin/tini/releases/download/${TINI_VERSION}/tini /tini
RUN chmod +x /tini
ENTRYPOINT ["/tini", "--"]

# Run the service
CMD [ "npm", "start" ]
CMD [ "node", "./bin/server" ]
102 changes: 102 additions & 0 deletions plugins/shutdown.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
'use strict';

const joi = require('joi');
const logger = require('screwdriver-logger');

const tasks = {};
const taskSchema = joi.object({
taskname: joi.string().required(),
task: joi.func().required(),
timeout: joi.number().integer()
});

/**
* Function to return promise timeout or resolution
* whichever happens first
* @param {function} fn
* @param {string} timeout
*/
function promiseTimeout(fn, timeout) {
return Promise.race([
Promise.resolve(fn),
new Promise(resolve => {
setTimeout(() => {
resolve(`Promise timed out after ${timeout} ms`);
}, timeout);
})
]);
}

/**
* Hapi plugin to handle server graceful shutdown
* @method register
* @param {Hapi.Server} server
*/
const shutdownPlugin = {
name: 'shutdown',
async register(server) {
const terminationGracePeriod = parseInt(process.env.TERMINATION_GRACE_PERIOD, 10) || 30;

const taskHandler = async () => {
try {
await Promise.all(
Object.keys(tasks).map(async key => {
logger.info(`shutdown-> executing task ${key}`);
const item = tasks[key];

await item.task();
})
);

return Promise.resolve();
} catch (err) {
logger.error('shutdown-> Error in taskHandler %s', err);
throw err;
}
};

const gracefulStop = async () => {
try {
logger.info('shutdown-> gracefully shutting down server');
await server.stop({
timeout: 5000
});
process.exit(0);
} catch (err) {
logger.error('shutdown-> error in graceful shutdown %s', err);
process.exit(1);
}
};

const onSigterm = async () => {
try {
logger.info('shutdown-> got SIGTERM; running triggers before shutdown');
const res = await promiseTimeout(taskHandler(), terminationGracePeriod * 1000);

if (res) {
logger.error(res);
}
await gracefulStop();
} catch (err) {
logger.error('shutdown-> Error in plugin %s', err);
process.exit(1);
}
};

// catch sigterm signal
process.on('SIGTERM', onSigterm);

server.expose('handler', task => {
const res = taskSchema.validate(task);

if (res.error) {
return res.error;
}
tasks[task.taskname] = task;

return '';
});
}
};

module.exports = shutdownPlugin;
74 changes: 74 additions & 0 deletions test/plugins/shutdown.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
'use strict';

const chai = require('chai');
const { assert } = chai;
const hapi = require('@hapi/hapi');
const sinon = require('sinon');

sinon.assert.expose(assert, { prefix: '' });

describe('test shutdown plugin', () => {
let plugin;
let server;

beforeEach(async () => {
/* eslint-disable global-require */
plugin = require('../../plugins/shutdown');
/* eslint-enable global-require */

server = new hapi.Server({
port: 1234
});

await server.register({ plugin });
});

afterEach(() => {
server = null;
});

it('registers the plugin', () => {
assert.isOk(server.registrations.shutdown);
});
});

describe('test graceful shutdown', () => {
before(() => {
sinon.stub(process, 'exit');
});

after(() => {
process.exit.restore();
});

it('should catch the SIGTERM signal', () => {
/* eslint-disable global-require */
const plugin = require('../../plugins/shutdown');
/* eslint-enable global-require */
const options = {
terminationGracePeriod: 30
};
let stopCalled = false;
const server = new hapi.Server({
port: 1234
});

server.log = () => {};
server.root = {
stop: () => {
stopCalled = true;
}
};
server.expose = sinon.stub();

plugin.register(server, options, () => {});

process.exit(1);
process.exit.callsFake(() => {
assert.isTrue(stopCalled);
});
assert(process.exit.isSinonProxy);
sinon.assert.called(process.exit);
sinon.assert.calledWith(process.exit, 1);
});
});

0 comments on commit ab32467

Please sign in to comment.