@codefresh-io/service-base 7.4.1 → 8.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/index.js CHANGED
@@ -1,5 +1,5 @@
1
-
2
-
1
+ /* eslint-disable global-require */
2
+ /* eslint-disable import/order */
3
3
  const config = require('./infra/config');
4
4
  const service = require('./infra');
5
5
  const {
@@ -50,6 +50,7 @@ enabledComponents.forEach((key) => {
50
50
  throw new Error(`Component '${key}'' is dependent on component '${dependency}' which is missing.`);
51
51
  }
52
52
  });
53
+ // eslint-disable-next-line import/no-dynamic-require
53
54
  exportedComponents[component.name || key] = require(`./infra/${key}`);
54
55
  });
55
56
 
@@ -6,21 +6,26 @@ const mongoClient = require('../mongo');
6
6
  * @type {MongoMemoryServer}
7
7
  */
8
8
  let mongod;
9
+
9
10
  async function clientSetupByUriAndDbName() {
10
11
  const uri = mongod.getUri();
11
12
  const { port, dbPath, dbName } = mongod.instanceInfo;
12
- console.log(`using port ${port}`);
13
- console.log(`dbPath ${dbPath}`);
14
- console.log(`dbName ${dbName}`);
15
13
 
16
14
  await mongoClient.init({ mongo: { uri, dbName } });
17
15
  return { dbName, dbPath, uri, port };
18
16
  }
19
17
 
20
18
  describe('mongo init compatibility code', () => {
19
+ beforeAll(async () => {
20
+ // Dummy warmup to download MongoDB binaries before running tests, to avoid timeout on the first test
21
+ const warmUpInstance = await MongoMemoryServer.create();
22
+ await warmUpInstance.stop();
23
+ }, 60 * 1000);
24
+
21
25
  beforeEach(async () => {
22
26
  mongod = await MongoMemoryServer.create();
23
27
  });
28
+
24
29
  afterEach(async () => {
25
30
  await mongoClient.stop();
26
31
  await mongod.stop();
package/infra/config.js CHANGED
@@ -63,7 +63,9 @@ base.postgres = {
63
63
  base.mongo = {
64
64
  uri: process.env.MONGO_URI || `mongodb://${APPLICATION_DOMAIN}/${name}`,
65
65
  /** @type {import('mongodb').MongoClientOptions} */
66
- options: {},
66
+ options: {
67
+ monitorCommands: true,
68
+ },
67
69
  };
68
70
 
69
71
  if (process.env.MTLS_CERT_PATH) {
@@ -110,7 +112,7 @@ base.logger = {
110
112
  correlationId: () => {
111
113
  try {
112
114
  return getRequestId();
113
- } catch (err) {
115
+ } catch {
114
116
  return {};
115
117
  }
116
118
  },
@@ -121,18 +123,13 @@ base.logger = {
121
123
  authEntity.activeAccount = _.omit(authEntity.activeAccount, 'features');
122
124
  }
123
125
  return authEntity;
124
- } catch (err) {
126
+ } catch {
125
127
  return {};
126
128
  }
127
129
  },
128
130
  },
129
131
  };
130
132
 
131
- base.httpLogger = {
132
- level: process.env.HTTP_LOGGER_LEVEL || 'debug',
133
- format: 'dev',
134
- };
135
-
136
133
  base.httpQueryParser = {
137
134
  allowPrototypes: true,
138
135
  arrayLimit: process.env.URI_QUERY_ARRAY_MAX_LENGTH || 100,
@@ -1,8 +1,5 @@
1
- const { randomUUID } = require('node:crypto');
2
1
  const _ = require('lodash');
3
- const Promise = require('bluebird');
4
- const crypto = require('crypto');
5
-
2
+ const crypto = require('node:crypto');
6
3
  const config = require('../config');
7
4
  const mongoClient = require('../mongo');
8
5
 
@@ -43,7 +40,7 @@ function getOrCreateSafe(safeId) {
43
40
 
44
41
  const newSafe = {
45
42
  _id: safeId,
46
- key: (Buffer.from(randomUUID())).toString('base64'), // eslint-disable-line
43
+ key: (Buffer.from(crypto.randomUUID())).toString('base64'),
47
44
  };
48
45
  return collection.insertOne(newSafe)
49
46
  .then(() => new Safe(newSafe))
@@ -56,7 +53,7 @@ function getOrCreateSafe(safeId) {
56
53
  // decrypt cipher text: try to decrypt with crypto library first
57
54
  // if did not find CRYPTO_PREFIX prefix, try to use old triplesec decryption
58
55
  Safe.prototype.read_crypto = function (ciphertext) { // eslint-disable-line
59
- const deferred = Promise.defer();
56
+ const deferred = Promise.withResolvers();
60
57
 
61
58
  const key = Buffer.alloc(32, _.get(config, 'safe.secret'));
62
59
  const iv = Buffer.alloc(16, this.safeModel.key);
@@ -85,7 +82,7 @@ Safe.prototype.read_crypto = function (ciphertext) { // eslint-disable-line
85
82
 
86
83
  // encrypt text and add CRYPTO_PREFIX prefix (to mark the new encryption)
87
84
  Safe.prototype.write_crypto = function (plaintext) { // eslint-disable-line
88
- const deferred = Promise.defer();
85
+ const deferred = Promise.withResolvers();
89
86
  const key = Buffer.alloc(32, _.get(config, 'safe.secret'));
90
87
  const iv = Buffer.alloc(16, this.safeModel.key);
91
88
 
package/infra/eventbus.js CHANGED
@@ -1,118 +1,114 @@
1
- const Promise = require('bluebird');
1
+ // @ts-check
2
2
  const eventBus = require('@codefresh-io/eventbus');
3
3
  const monitor = require('@codefresh-io/cf-monitor');
4
- const CFError = require('cf-errors');
4
+ const { globalLogger } = require('./global-logger');
5
5
 
6
6
  class Eventbus {
7
+ #logger = globalLogger.child(this.constructor.name);
8
+
7
9
  constructor() {
8
10
  this.eventbusInitialized = false;
9
11
  }
10
12
 
11
13
  /**
12
- * starts the connection to eventbus
13
- * @returns {*}
14
+ * Starts the connection to eventbus
15
+ * @param {any} config
16
+ * @returns {Promise<Eventbus>}
14
17
  */
15
18
  init(config) {
16
- const logger = require('cf-logs').Logger('codefresh:infra:eventbus'); // eslint-disable-line
17
- this.logger = logger;
18
- return Promise.resolve()
19
- .then(() => {
20
- this.config = config;
21
-
22
- const deferred = Promise.defer();
23
-
24
- // TODO a fallback for case where rabbitmq is not up. should be removed once rabbitmq is fully used
25
- setTimeout(() => {
26
- deferred.resolve(this);
27
- }, 30000);
28
-
29
- eventBus.init({
30
- bus: {
31
- url: this.config.eventbus.uri,
32
- reconnectInterval: 5,
33
- },
34
- store: {
35
- host: this.config.postgres.host,
36
- port: this.config.postgres.port,
37
- database: this.config.postgres.database,
38
- user: this.config.postgres.user,
39
- password: this.config.postgres.password,
40
- ssl: this.config.postgres.ssl,
41
- },
42
- microServiceName: this.config.eventbus.serviceName,
43
- });
19
+ this.config = config;
20
+ this.#logger.info('Initializing Eventbus connection');
21
+ /** @type {PromiseWithResolvers<Eventbus>} */
22
+ const deferred = Promise.withResolvers();
23
+
24
+ // TODO a fallback for case where rabbitmq is not up. should be removed once rabbitmq is fully used
25
+ setTimeout(() => {
26
+ deferred.resolve(this);
27
+ }, 30000);
28
+
29
+ eventBus.init({
30
+ bus: {
31
+ url: this.config.eventbus.uri,
32
+ reconnectInterval: 5,
33
+ },
34
+ store: {
35
+ host: this.config.postgres.host,
36
+ port: this.config.postgres.port,
37
+ database: this.config.postgres.database,
38
+ user: this.config.postgres.user,
39
+ password: this.config.postgres.password,
40
+ ssl: this.config.postgres.ssl,
41
+ },
42
+ microServiceName: this.config.eventbus.serviceName,
43
+ });
44
44
 
45
- eventBus.on('ready', () => {
46
- logger.info('Eventbus ready');
47
- this.eventbusInitialized = true;
48
- deferred.resolve(this);
49
- });
45
+ eventBus.on('ready', () => {
46
+ this.#logger.info('Eventbus ready');
47
+ this.eventbusInitialized = true;
48
+ deferred.resolve(this);
49
+ });
50
50
 
51
- eventBus.on('error', (err) => {
52
- const error = new Error(`Eventbus error: ${err.stack}`);
53
- logger.error(error.stack);
54
- monitor.noticeError(error);
55
- });
51
+ eventBus.on('error', (err) => {
52
+ this.#logger.error('Eventbus error', err);
53
+ monitor.noticeError(new Error(`Eventbus error: ${err.stack}`));
54
+ });
56
55
 
57
- return deferred.promise;
58
- });
56
+ return deferred.promise;
59
57
  }
60
58
 
61
59
  /**
62
60
  * stops the connection to eventbus
63
- * @returns {*}
61
+ * @returns {Promise<void>}
64
62
  */
65
63
  stop() {
66
- const logger = this.logger; // eslint-disable-line
67
- if (!this.eventbusInitialized) {
68
- return Promise.resolve();
69
- }
70
-
71
- const deferred = Promise.defer();
64
+ if (!this.eventbusInitialized) return Promise.resolve();
72
65
 
66
+ /** @type {PromiseWithResolvers<void>} */
67
+ const deferred = Promise.withResolvers();
73
68
  eventBus.on('close', () => {
74
- logger.info('Eventbus client closed');
69
+ this.#logger.info('Eventbus client closed');
75
70
  deferred.resolve();
76
71
  });
77
-
78
72
  eventBus.quit();
79
-
80
73
  return deferred.promise;
81
74
  }
82
75
 
83
- subscribe(eventName, handler, options) {
84
- const logger = this.logger; // eslint-disable-line
85
- return eventBus.subscribe(eventName, handler, options)
86
- .then((listener) => {
87
- logger.info(`Listening on event ${eventName}`);
88
- listener.on('error', (err) => {
89
- logger.error(`${eventName} handler failed: ${err.stack}`);
90
- monitor.noticeError(err);
91
- return listener;
92
- });
93
- });
76
+ /**
77
+ *
78
+ * @param {unknown} eventName
79
+ * @param {unknown} handler
80
+ * @param {unknown} options
81
+ * @returns {Promise<void>}
82
+ */
83
+ async subscribe(eventName, handler, options) {
84
+ const logger = this.#logger.child('subscribe');
85
+ const listener = await eventBus.subscribe(eventName, handler, options);
86
+ logger.info(`Subscribed to event "${eventName}"`);
87
+ listener.on('error', (/** @type {unknown} */ err) => {
88
+ logger.error(`Handler for event "${eventName}" failed`, err);
89
+ monitor.noticeError(err);
90
+ });
94
91
  }
95
92
 
96
- publish(eventName, data, returnPromise = false) { // eslint-disable-line
97
- const logger = this.logger; // eslint-disable-line
98
-
99
- logger.debug(`publishing event: ${eventName}`);
100
- const promise = eventBus.publish(eventName, data);
101
-
102
- if (returnPromise) {
103
- return promise;
104
- }
105
- promise
106
- .then(() => {
107
- logger.debug(`event: ${eventName} published successfully`);
108
- })
109
- .catch((err) => {
110
- const error = new CFError({
111
- cause: err,
112
- message: `Failed to publish event ${eventName}`,
113
- });
114
- logger.error(error.stack);
115
- monitor.noticeError(error);
93
+ /**
94
+ * @param {unknown} eventName
95
+ * @param {unknown} data
96
+ * @param {boolean} [shouldReturnPromise]
97
+ * @returns {Promise<void>|void}
98
+ */
99
+ // eslint-disable-next-line consistent-return
100
+ publish(eventName, data, shouldReturnPromise = false) {
101
+ const logger = this.#logger.child('publish');
102
+ logger.debug(`Publishing event "${eventName}"`);
103
+ const publishResultPromise = eventBus.publish(eventName, data);
104
+
105
+ if (shouldReturnPromise) return publishResultPromise;
106
+
107
+ publishResultPromise
108
+ .then(() => logger.debug(`Event "${eventName}" published successfully`))
109
+ .catch((error) => {
110
+ logger.error(`Failed to publish event "${eventName}"`, error);
111
+ monitor.noticeError(new Error(`Failed to publish event "${eventName}"`, { cause: error }));
116
112
  });
117
113
  }
118
114
  }
package/infra/express.js CHANGED
@@ -1,161 +1,171 @@
1
- const Promise = require('bluebird');
1
+ // @ts-check
2
+ const { httpLoggerMiddlewareFactory } = require('@codefresh-io/cf-telemetry/logs/express');
3
+ const { Logger } = require('@codefresh-io/cf-telemetry/logs');
2
4
  const express = require('express');
3
5
  const qs = require('qs');
4
6
  const compression = require('compression');
5
7
  const bodyParser = require('body-parser');
6
8
  const methodOverride = require('method-override');
7
9
  const cookieParser = require('cookie-parser');
8
- const morgan = require('morgan');
9
10
  const monitor = require('@codefresh-io/cf-monitor');
11
+ // @ts-ignore
10
12
  const { newDomainMiddleware } = require('@codefresh-io/http-infra');
11
13
  const { openapi } = require('@codefresh-io/cf-openapi');
12
14
  const CFError = require('cf-errors');
15
+ const { globalLogger } = require('./global-logger');
16
+
17
+ /**
18
+ * @typedef {import('express').Application } ExpressApp
19
+ * @typedef {import('express').RequestHandler} RequestHandler
20
+ * @typedef {import('express').ErrorRequestHandler} ErrorRequestHandler
21
+ * @typedef {import('express').Request} Request
22
+ * @typedef {import('express').Response} Response
23
+ * @typedef {import('express').NextFunction} NextFunction
24
+ * @typedef {import('node:http').Server} HttpServer
25
+ */
13
26
 
14
27
  class Express {
28
+ #logger = globalLogger.child(this.constructor.name);
29
+
15
30
  constructor() {
16
- this.expressApp = express(); // the express app definition
17
- this.expressServer = undefined; // the express server instance
31
+ /** @type {ExpressApp} */
32
+ this.expressApp = express();
33
+ /** @type {HttpServer|undefined} */
34
+ this.expressServer = undefined;
35
+ /** @type {boolean} */
18
36
  this.healthy = true;
19
37
  }
20
38
 
21
- stop() {
22
- return Promise.resolve()
23
- .then(() => this.expressServer.close());
39
+ /**
40
+ * @returns {Promise<void>}
41
+ */
42
+ async stop() {
43
+ this.#logger.info('Stopping Express server');
44
+ if (!this.expressServer) return;
45
+ this.expressServer.close();
24
46
  }
25
47
 
26
48
  /**
27
- * starts the connection to mongo
28
- * @returns {*}
49
+ * Initializes the Express server.
50
+ * @param {any} config
51
+ * @param {any} createRoutes
52
+ * @param {any} [opt]
53
+ * @returns {Promise<void>}
29
54
  */
30
- init(config, createRoutes, opt = {}) {
31
- const logger = require('cf-logs').Logger('codefresh:infra:express'); // eslint-disable-line
32
- this.logger = logger;
55
+ async init(config, createRoutes, opt = {}) {
33
56
  this.options = opt;
34
- return Promise.resolve()
35
- .then(() => {
36
- this.config = config;
37
- this.createRoutes = createRoutes;
38
- return this._create()
39
- .then(() => this._start(this.expressApp))
40
- .then((expressServer) => {
41
- this.expressServer = expressServer;
42
- })
43
- .then(() => {
44
- openapi.events().subscribe();
45
- openapi.events().publish();
46
- });
47
- });
57
+ this.config = config;
58
+ this.createRoutes = createRoutes;
59
+ await this._create();
60
+ this.expressServer = await this._start(this.expressApp);
61
+ openapi.events().subscribe();
62
+ openapi.events().publish();
48
63
  }
49
64
 
50
- _create() {
51
- const logger = this.logger; // eslint-disable-line
52
- return Promise.resolve()
53
- .then(() => {
54
- const app = this.expressApp;
55
- app.set('query parser', (str) => qs.parse(str, this.config.httpQueryParser));
56
-
57
- app.use(newDomainMiddleware());
58
-
59
- app.use(cookieParser());
60
- app.use(compression());
61
-
62
- openapi.endpoints().registerUtilityMiddleware(app);
63
- app.use(bodyParser.json());
64
-
65
- app.use(bodyParser.urlencoded({ extended: true }));
66
- app.use(methodOverride());
67
-
68
- if (this.config.httpLogger) {
69
- app.use(morgan(this.config.httpLogger.format, {
70
- skip: (req, res) => {
71
- if (this.config.httpLogger.level === 'debug') {
72
- return false;
73
- }
74
- const code = res.statusCode;
75
- return code < 400;
76
- },
77
- stream: {
78
- write: (str) => {
79
- logger.info(str.substring(0, str.length - 1));
80
- },
81
- },
82
- }));
65
+ async _create() {
66
+ /** Should be the first middleware */
67
+ this.expressApp.use(httpLoggerMiddlewareFactory(new Logger('http')));
68
+
69
+ this.expressApp.set('query parser', (/** @type {string} */ str) => qs.parse(str, this.config.httpQueryParser));
70
+
71
+ this.expressApp.use(newDomainMiddleware());
72
+
73
+ this.expressApp.use(cookieParser());
74
+ this.expressApp.use(compression());
75
+
76
+ openapi.endpoints().registerUtilityMiddleware(this.expressApp);
77
+ this.expressApp.use(bodyParser.json());
78
+
79
+ this.expressApp.use(bodyParser.urlencoded({ extended: true }));
80
+ this.expressApp.use(methodOverride());
81
+
82
+ openapi.endpoints().register(this.expressApp);
83
+ await this.createRoutes(this.expressApp);
84
+
85
+ this.expressApp.get('/api/ping', (req, res) => {
86
+ res.status(200).send();
87
+ });
88
+
89
+ this.expressApp.get('/api/ready', (req, res) => {
90
+ if (this.options.isReady()) {
91
+ res.status(200).send();
92
+ } else {
93
+ res.status(503).send();
94
+ }
95
+ });
96
+
97
+ this.expressApp.get('/api/health', (req, res) => {
98
+ if (this.options.isHealthy()) {
99
+ res.status(200).send();
100
+ } else {
101
+ res.status(503).send();
102
+ }
103
+ });
104
+
105
+ /** @type {ErrorRequestHandler} */
106
+ const errorHandler = (err, _req, res, next) => {
107
+ this.#logger.error('Request handler error', err);
108
+
109
+ if (res.headersSent) {
110
+ next(err);
111
+ return;
112
+ }
113
+
114
+ if (err instanceof CFError) {
115
+ if (!err.getFirstValue('recognized')) {
116
+ monitor.noticeError(err);
83
117
  }
84
- openapi.endpoints().register(app);
85
- return this.createRoutes(app)
86
- .then(() => {
87
- app.get('/api/ping', (req, res) => {
88
- res.status(200).send();
89
- });
90
-
91
- app.get('/api/ready', (req, res) => {
92
- if (this.options.isReady()) {
93
- res.status(200).send();
94
- } else {
95
- res.status(503).send();
96
- }
97
- });
98
-
99
- app.get('/api/health', (req, res) => {
100
- if (this.options.isHealthy()) {
101
- res.status(200).send();
102
- } else {
103
- res.status(503).send();
104
- }
105
- });
106
-
107
- // the last error handler
108
- app.use((err, req, res, next) => { // eslint-disable-line
109
- logger.error(err.stack);
110
-
111
- if (res.headersSent) {
112
- return next(err);
113
- }
114
-
115
- if (err instanceof CFError) {
116
- if (!err.getFirstValue('recognized')) {
117
- monitor.noticeError(err);
118
- }
119
- } else {
120
- monitor.noticeError(err);
121
- }
122
-
123
- const statusCode = err.statusCode || 500;
124
- // check if err object has overridden toString method
125
- // before sending toString() response to prevent [object Object] responses
126
- const message = err.toString === Object.prototype.toString
127
- ? (err.message || 'Internal server error')
128
- : err.toString();
129
- res.status(statusCode).send({ message });
130
- });
131
- });
132
- });
118
+ } else {
119
+ monitor.noticeError(err);
120
+ }
121
+
122
+ const statusCode = err.statusCode || 500;
123
+ // check if err object has overridden toString method
124
+ // before sending toString() response to prevent [object Object] responses
125
+ const message = err.toString === Object.prototype.toString
126
+ ? (err.message || 'Internal server error')
127
+ : err.toString();
128
+ res.status(statusCode).send({ message });
129
+ };
130
+ /** Should be the last middleware */
131
+ this.expressApp.use(errorHandler);
133
132
  }
134
133
 
134
+ /**
135
+ * @param {ExpressApp} app
136
+ * @returns {Promise<HttpServer>}
137
+ */
135
138
  _start(app) {
136
- const logger = this.logger; // eslint-disable-line
139
+ this.#logger.info('Starting Express server');
140
+
137
141
  openapi.dependencies().fetch();
138
- return new Promise((resolve, reject) => {
139
- const server = app.listen(this.config.port, (err) => {
140
- if (err) {
141
- logger.info(`Failed to load service with message ${err.message}`);
142
- reject(err);
143
- } else {
144
- logger.info(`Express server listening on port ${this.config.port}, in mode ${this.config.env}`);
145
- resolve(server);
146
- }
147
- });
142
+ /** @type {PromiseWithResolvers<HttpServer>} */
143
+ const deferred = Promise.withResolvers();
144
+ const server = app.listen(this.config.port, (err) => {
145
+ if (err) {
146
+ this.#logger.error('Failed to start Express server', err);
147
+ deferred.reject(err);
148
+ return;
149
+ }
150
+ this.#logger.info(`Express server listening on port "${this.config.port}", in mode "${this.config.env}"`);
151
+ deferred.resolve(server);
148
152
  });
153
+ return deferred.promise;
149
154
  }
150
155
 
151
- makeEndpoint(fn) { // eslint-disable-line
152
- return function (req, res, next) { // eslint-disable-line
153
- Promise.resolve()
154
- .then(() => fn(req, res))
155
- .then((ret) => {
156
- res.send(ret);
157
- })
158
- .catch((err) => next(err));
156
+ /**
157
+ * @param {(req: Request, res: Response) => Promise<any>} fn
158
+ * @returns {RequestHandler}
159
+ */
160
+ makeEndpoint(fn) {
161
+ this.#logger.debug(`Wrapping request handler "${fn?.name}" with error handling`);
162
+ return async function routeHandler(req, res, next) {
163
+ try {
164
+ const resp = await fn(req, res);
165
+ res.send(resp);
166
+ } catch (error) {
167
+ next(error);
168
+ }
159
169
  };
160
170
  }
161
171
  }
@@ -0,0 +1,7 @@
1
+ const { Logger } = require('@codefresh-io/cf-telemetry/logs');
2
+
3
+ const globalLogger = new Logger('@codefresh-io/service-base');
4
+
5
+ module.exports = {
6
+ globalLogger,
7
+ };
package/infra/helper.js CHANGED
@@ -1,12 +1,11 @@
1
1
  const splitUriBySlash = (uri) => {
2
2
  const parts = uri.split('/');
3
- const lastIndex = parts.length - 1;
4
3
  return {
5
- prefix: parts.slice(0, -1)
6
- .join('/'),
7
- dbName: parts[lastIndex].split('?')[0],
4
+ prefix: parts.slice(0, -1).join('/'),
5
+ dbName: parts.at(-1).split('?')[0],
8
6
  };
9
7
  };
10
8
 
11
9
  const getDbNameFromUri = (uri) => splitUriBySlash(uri).dbName;
10
+
12
11
  module.exports = { splitUriBySlash, getDbNameFromUri };
package/infra/index.js CHANGED
@@ -1,8 +1,7 @@
1
1
  const monitor = require('@codefresh-io/cf-monitor');
2
2
 
3
3
  monitor.init();
4
- const Promise = require('bluebird'); // jshint ignore:line
5
- const cflogs = require('cf-logs');
4
+ const Bluebird = require('bluebird');
6
5
  const { openapi } = require('@codefresh-io/cf-openapi');
7
6
  const config = require('./config');
8
7
  const eventbus = require('./eventbus');
@@ -12,10 +11,11 @@ const express = require('./express');
12
11
  const logging = require('./logging');
13
12
  const redis = require('./redis');
14
13
  const { publishInterface, subscribeInterface } = require('./openapi-events');
15
-
16
- let logger;
14
+ const { globalLogger } = require('./global-logger');
17
15
 
18
16
  class Microservice {
17
+ #logger = globalLogger.child(this.constructor.name);
18
+
19
19
  constructor() {
20
20
  this._ready = false;
21
21
  this._healthy = false;
@@ -30,26 +30,28 @@ class Microservice {
30
30
  }
31
31
 
32
32
  markAsReady() {
33
- logger.info('Service marked as ready');
33
+ this.#logger.info('Service marked as ready');
34
34
  this._ready = true;
35
35
  }
36
36
 
37
37
  markAsNotReady() {
38
- logger.info('Service marked as not ready');
38
+ this.#logger.info('Service marked as not ready');
39
39
  this._ready = false;
40
40
  }
41
41
 
42
42
  markAsHealthy() {
43
- logger.info('Service marked as healthy');
43
+ this.#logger.info('Service marked as healthy');
44
44
  this._healthy = true;
45
45
  }
46
46
 
47
47
  markAsUnhealthy() {
48
- logger.info('Service marked as unhealthy');
48
+ this.#logger.info('Service marked as unhealthy');
49
49
  this._healthy = false;
50
50
  }
51
51
 
52
52
  init(initFn) {
53
+ const logger = this.#logger.child('init');
54
+ logger.info('Initializing service');
53
55
  const enabledComponents = config.getConfigArray('enabledComponents');
54
56
  const opt = {
55
57
  isReady: this.isReady.bind(this),
@@ -57,7 +59,6 @@ class Microservice {
57
59
  };
58
60
  return logging.init(config)
59
61
  .then(() => {
60
- logger = cflogs.Logger('codefresh:infra:index');
61
62
  let sigintCount = 0;
62
63
  this._validateGraceTimers();
63
64
  return processEvents.init(config)
@@ -91,7 +92,7 @@ class Microservice {
91
92
  this.markAsHealthy();
92
93
  })
93
94
  .catch((err) => {
94
- console.error(`Initialization error: ${err.stack}`);
95
+ logger.error('Initialization error', err);
95
96
  process.exit(1);
96
97
  });
97
98
  }
@@ -100,7 +101,8 @@ class Microservice {
100
101
  // - first phase need to make sure to not accept any new requests/events
101
102
  // - then a decent amount of time will be given to clear all on-going contexts
102
103
  // - second phase will close all dependencies connections like mongo, postgres etc
103
- stop() { // eslint-disable-line
104
+ stop() {
105
+ const logger = this.#logger.child('stop');
104
106
  const enabledComponents = config.getConfigArray('enabledComponents');
105
107
  const gracePeriod = config.gracePeriodTimers.totalPeriod;
106
108
 
@@ -129,24 +131,24 @@ class Microservice {
129
131
  logger.info('About to stop redis');
130
132
  promises.push(redis.stop.bind(redis));
131
133
  }
132
- return Promise
134
+ return Bluebird
133
135
  .resolve()
134
136
  .then(() => {
135
137
  this.markAsNotReady();
136
138
  logger.info(`Waiting more ${incomingHttpRequests} ms to accept more request`);
137
- return Promise.resolve()
139
+ return Bluebird.resolve()
138
140
  .delay(incomingHttpRequests);
139
141
  })
140
142
  .then(() => {
141
143
  logger.info(`Waiting more ${ongoingHttpRequest} ms to process all ongoing requests`);
142
- return Promise.resolve()
144
+ return Bluebird.resolve()
143
145
  .delay(ongoingHttpRequest)
144
146
  .then(() => express.stop());
145
147
  })
146
148
  .then(() => {
147
149
  logger.info(`Setting up ${infraDependencies} ms to finish all core service connections timeout`);
148
- return Promise.resolve()
149
- .then(() => Promise.all(promises))
150
+ return Bluebird.resolve()
151
+ .then(() => Bluebird.all(promises))
150
152
  .timeout(infraDependencies);
151
153
  })
152
154
  .timeout(gracePeriod)
@@ -155,21 +157,21 @@ class Microservice {
155
157
  process.exit();
156
158
  })
157
159
  .catch((error) => {
158
- console.error(`error during shutdown: ${error.stack}`);
160
+ this.#logger.error('Error during shutdown', error);
159
161
  process.exit();
160
162
  });
161
163
  }
162
164
 
163
- _validateGraceTimers() { // eslint-disable-line
165
+ _validateGraceTimers() {
164
166
  const gracePeriod = config.gracePeriodTimers.totalPeriod;
165
167
  const incomingHttpRequests = config.gracePeriodTimers.secondsToAcceptAdditionalRequests;
166
168
  const ongoingHttpRequest = config.gracePeriodTimers.secondsToProcessOngoingRequests;
167
169
  const infraDependencies = config.gracePeriodTimers.secondsToCloseInfraConnections;
168
170
 
169
171
  if (gracePeriod < (incomingHttpRequests + ongoingHttpRequest + infraDependencies)) {
170
- const message = `Total grace period: ${gracePeriod}, the service needs at least ${incomingHttpRequests + ongoingHttpRequest + infraDependencies} to perform graceful shuwdown. Check service configuration`; // eslint-disable-line
172
+ const message = `Total grace period: ${gracePeriod}, the service needs at least ${incomingHttpRequests + ongoingHttpRequest + infraDependencies} to perform graceful shutdown. Check service configuration`; // eslint-disable-line
171
173
  if (!config.gracePeriodTimers.skipGraceTimersValidation) {
172
- logger.warn(message);
174
+ this.#logger.warn(message);
173
175
  } else {
174
176
  throw new Error(message);
175
177
  }
package/infra/logging.js CHANGED
@@ -1,32 +1,39 @@
1
- const Promise = require('bluebird');
1
+ // @ts-check
2
+ const { Logger } = require('@codefresh-io/cf-telemetry/logs');
2
3
  const cflogs = require('cf-logs');
3
- const { name: serviceName } = require('./config');
4
-
5
- let logger;
4
+ const { globalLogger } = require('./global-logger');
6
5
 
6
+ /**
7
+ * Legacy shim for logging.
8
+ * Feel free to remove once dependent services are using logger from `@codefresh-io/cf-telemetry` directly.
9
+ */
7
10
  class Logging {
8
- getLogger(namespace) { // eslint-disable-line
9
- return cflogs.Logger(`codefresh:${serviceName}${namespace ? `:${namespace}` : ''}`);
11
+ /**
12
+ * @param {string} scope
13
+ * @returns {Logger}
14
+ * @deprecated Import logger from `@codefresh-io/cf-telemetry` directly instead of this wrapper.
15
+ */
16
+ getLogger(scope) { // eslint-disable-line
17
+ return new Logger(scope);
10
18
  }
11
19
 
12
- init(config) { // eslint-disable-line
13
- return Promise.resolve()
14
- .then(() => {
15
- cflogs.init(config.logger);
16
- logger = cflogs.Logger('codefresh');
17
- // override the default console.log
18
- console.log = (message) => {
19
- logger.log('info', message);
20
- };
21
- console.error = (message) => {
22
- logger.log('error', message);
23
- };
24
- });
20
+ /**
21
+ * @param {any} config
22
+ */
23
+ // eslint-disable-next-line class-methods-use-this
24
+ async init(config) {
25
+ // Init for legacy logger still used by some services. Feel free to remove if dependent services are not using `cf-logs` anymore.
26
+ cflogs.init(config.logger);
27
+ // override the default console.log
28
+ const consoleOverride = globalLogger.child('console-override');
29
+ console.debug = consoleOverride.debug.bind(consoleOverride);
30
+ console.log = consoleOverride.info.bind(consoleOverride);
31
+ console.warn = consoleOverride.warn.bind(consoleOverride);
32
+ console.error = consoleOverride.error.bind(consoleOverride);
25
33
  }
26
34
 
27
- stop() { // eslint-disable-line
28
- return Promise.resolve();
29
- }
35
+ // eslint-disable-next-line no-empty-function, class-methods-use-this
36
+ async stop() {}
30
37
  }
31
38
 
32
39
  module.exports = new Logging();
package/infra/mongo.js CHANGED
@@ -1,50 +1,70 @@
1
+ // @ts-check
1
2
  const { MongoClient, ObjectId } = require('mongodb');
3
+ const { monitorMongoDBClient } = require('@codefresh-io/cf-telemetry/metrics/mongodb');
4
+ const { globalLogger } = require('./global-logger');
2
5
  const { getDbNameFromUri } = require('./helper');
3
6
 
7
+ /**
8
+ * @typedef {object} MongoConfig
9
+ * @property {string} uri MongoDB connection URI.
10
+ * @property {import('mongodb').MongoClientOptions} [options] MongoDB client options.
11
+ * @property {string} [dbName] Database name.
12
+ *
13
+ * @typedef {object} Config
14
+ * @property {MongoConfig} mongo MongoDB configuration.
15
+ */
16
+
4
17
  class Mongo {
18
+ #logger = globalLogger.child(this.constructor.name);
19
+
5
20
  constructor() {
6
21
  this.db = undefined;
7
22
  this.ObjectId = ObjectId;
8
23
  }
9
24
 
10
25
  /**
11
- * starts the connection to mongo
26
+ * Initializes the MongoDB client and connects to the database.
27
+ * @param {Config} config
28
+ * @returns {Promise<void>}
12
29
  */
13
30
  async init(config) {
14
- const clientSettings = { ...config.mongo.options };
15
- const logger = require('cf-logs').Logger('codefresh:infra:mongo'); // eslint-disable-line
16
- this.logger = logger;
17
-
18
- const { uri } = config.mongo;
19
- const dbName = config.mongo.dbName || getDbNameFromUri(uri);
20
- const client = new MongoClient(uri, clientSettings);
21
- logger.info(`Mongo db name ${dbName}`);
22
-
23
31
  try {
24
- await client.connect();
25
- logger.info('Mongo driver connected');
32
+ this.#logger.info('Initializing MongoDB connection');
33
+ const { uri, options } = config.mongo;
34
+ const dbName = config.mongo.dbName || getDbNameFromUri(uri);
35
+ const client = new MongoClient(uri, options);
36
+ monitorMongoDBClient(client);
37
+ this.client = await client.connect();
38
+ this.db = this.client.db(dbName);
39
+ this.#logger.info('MongoDB client connected');
26
40
  } catch (error) {
27
- logger.error('Error connecting to MongoDB:', error);
41
+ this.#logger.error('Unable to connect to MongoDB', error);
28
42
  throw error;
29
43
  }
30
-
31
- this.client = client;
32
- this.db = this.client.db(dbName);
33
- logger.info('Mongo db initialized');
34
44
  }
35
45
 
36
46
  /**
37
- * stops the connection to mongo
47
+ * Closes the MongoDB client connection.
48
+ * @returns {Promise<void>}
38
49
  */
39
50
  async stop() {
40
- if (!this.db) {
41
- return;
51
+ try {
52
+ this.#logger.info('Closing MongoDB connection');
53
+ await this.client?.close();
54
+ this.#logger.info('MongoDB connection closed');
55
+ } catch (error) {
56
+ this.#logger.error('Unable to close MongoDB connection', error);
57
+ throw error;
42
58
  }
43
- await this.client.close();
44
59
  }
45
60
 
61
+ /**
62
+ * Returns the specified collection.
63
+ * @param {string} collectionName
64
+ * @returns {import('mongodb').Collection | undefined} It only returns `undefined` if the client was not initialized yet.
65
+ */
46
66
  collection(collectionName) {
47
- return this.db.collection(collectionName);
67
+ return this.db?.collection(collectionName);
48
68
  }
49
69
  }
50
70
 
@@ -1,36 +1,35 @@
1
- const EventEmitter = require('events');
2
- const Promise = require('bluebird');
1
+ // @ts-check
2
+ const EventEmitter = require('node:events');
3
+ const { globalLogger } = require('./global-logger');
3
4
 
4
5
  class ProcessEvents extends EventEmitter {
6
+ #logger = globalLogger.child(this.constructor.name);
7
+
5
8
  /**
6
- * starts listening on process events
7
- * @param config
9
+ * Starts listening on process events
10
+ * @param {unknown} config
8
11
  */
9
- init(config) {
10
- const logger = require('cf-logs').Logger('codefresh:infra:process-events'); // eslint-disable-line
11
- return Promise.resolve()
12
- .then(() => {
13
- this.config = config;
12
+ async init(config) {
13
+ this.config = config;
14
14
 
15
- // graceful shutdown
16
- process.on('SIGTERM', () => {
17
- logger.info('SIGTERM received');
18
- this.emit('SIGTERM');
19
- });
15
+ // graceful shutdown
16
+ process.on('SIGTERM', () => {
17
+ this.#logger.info('SIGTERM received');
18
+ this.emit('SIGTERM');
19
+ });
20
20
 
21
- process.on('SIGINT', () => {
22
- logger.info('SIGINT received');
23
- this.emit('SIGINT');
24
- });
21
+ process.on('SIGINT', () => {
22
+ this.#logger.info('SIGINT received');
23
+ this.emit('SIGINT');
24
+ });
25
25
 
26
- process.on('unhandledRejection', (err) => {
27
- logger.error(`Unhandled rejection ${err.stack}`);
28
- });
26
+ process.on('unhandledRejection', (err) => {
27
+ this.#logger.error('Unhandled rejection', err);
28
+ });
29
29
 
30
- process.on('uncaughtException', (err) => {
31
- logger.error(`Uncaught Exception: ${err.stack}`);
32
- });
33
- });
30
+ process.on('uncaughtException', (err) => {
31
+ this.#logger.error('Uncaught Exception', err);
32
+ });
34
33
  }
35
34
  }
36
35
 
package/infra/redis.js CHANGED
@@ -1,8 +1,17 @@
1
- const Promise = require('bluebird');
1
+ // @ts-check
2
2
  const monitor = require('@codefresh-io/cf-monitor');
3
3
  const redis = require('redis');
4
+ const { globalLogger } = require('./global-logger');
5
+
6
+ /**
7
+ * @typedef {Partial<import('redis').ClientOpts>} RedisConfig
8
+ * @typedef {object} Config
9
+ * @property {RedisConfig} redis Redis configuration.
10
+ */
4
11
 
5
12
  class Redis {
13
+ #logger = globalLogger.child(this.constructor.name);
14
+
6
15
  constructor() {
7
16
  this.client = undefined;
8
17
  this.redisInitialized = false;
@@ -10,12 +19,14 @@ class Redis {
10
19
 
11
20
  /**
12
21
  * starts the connection to redis
13
- * @returns {*}
22
+ * @param {Config} config
23
+ * @returns {Promise<void>}
14
24
  */
15
25
  init(config) {
16
- const logger = require('cf-logs').Logger('codefresh:infra:redis'); // eslint-disable-line
17
- this.logger = logger;
18
- const deferred = Promise.defer();
26
+ this.#logger.info('Initializing Redis connection');
27
+
28
+ /** @type {PromiseWithResolvers<void>} */
29
+ const deferred = Promise.withResolvers();
19
30
 
20
31
  // TODO a fallback for case where redis is not up. should be removed once redis is fully used
21
32
  setTimeout(() => {
@@ -31,21 +42,21 @@ class Redis {
31
42
  });
32
43
 
33
44
  this.client.on('ready', () => {
34
- logger.info('Redis client ready');
45
+ this.#logger.info('Redis client ready');
35
46
  this.redisInitialized = true;
36
47
  deferred.resolve();
37
48
  });
38
49
 
39
50
  this.client.on('connect', () => {
40
- logger.info('Redis client connecting');
51
+ this.#logger.info('Redis client connecting');
41
52
  });
42
53
 
43
54
  this.client.on('reconnecting', () => {
44
- logger.info('Redis client reconnecting');
55
+ this.#logger.info('Redis client reconnecting');
45
56
  });
46
57
 
47
58
  this.client.on('error', (error) => {
48
- logger.error(error.message);
59
+ this.#logger.error('Redis client error', error);
49
60
  monitor.noticeError(error);
50
61
  });
51
62
 
@@ -54,23 +65,20 @@ class Redis {
54
65
 
55
66
  /**
56
67
  * stops the connection to redis
57
- * @returns {*}
68
+ * @returns {Promise<void>}
58
69
  */
59
- stop() {
60
- const logger = this.logger; // eslint-disable-line
70
+ async stop() {
61
71
  if (!this.redisInitialized) {
62
72
  return Promise.resolve();
63
73
  }
64
74
 
65
- const deferred = Promise.defer();
66
-
67
- this.client.on('end', () => {
68
- logger.info('Redis client ended');
75
+ /** @type {PromiseWithResolvers<void>} */
76
+ const deferred = Promise.withResolvers();
77
+ this.client?.on('end', () => {
78
+ this.#logger.info('Redis client connection closed');
69
79
  deferred.resolve();
70
80
  });
71
-
72
- this.client.quit();
73
-
81
+ this.client?.quit();
74
82
  return deferred.promise;
75
83
  }
76
84
  }
@@ -3,6 +3,9 @@ const Promise = require('bluebird');
3
3
  const Joi = require('joi').extend(require('@wegolook/joi-objectid'));
4
4
  const YAML = require('js-yaml');
5
5
  const Ajv = require('ajv');
6
+ const { globalLogger } = require('./global-logger');
7
+
8
+ const logger = globalLogger.child('validation');
6
9
 
7
10
  const customYamlJoi = Joi.extend((joi) => ({ // eslint-disable-line
8
11
  name: 'yaml',
@@ -11,7 +14,8 @@ const customYamlJoi = Joi.extend((joi) => ({ // eslint-disable-line
11
14
  try {
12
15
  YAML.safeLoad(value);
13
16
  return value;
14
- } catch (err) {
17
+ } catch (error) {
18
+ logger.debug('YAML validation error', error);
15
19
  return this.createError('yaml', { v: value }, state, options);
16
20
  }
17
21
  },
@@ -29,6 +33,7 @@ const customJsonSchemaStringJoi = Joi.extend((joi) => ({ // eslint-disable-line
29
33
  ajv.compile(JSON.parse(value));
30
34
  return value;
31
35
  } catch (err) {
36
+ logger.debug('JSON Schema validation error', err);
32
37
  return this.createError('jsonSchemaString.pre', { v: value, err: err.toString().replace('Error: schema is invalid: ', '') }, state, options); // eslint-disable-line max-len
33
38
  }
34
39
  },
package/package.json CHANGED
@@ -1,22 +1,22 @@
1
1
  {
2
2
  "name": "@codefresh-io/service-base",
3
- "version": "7.4.1",
3
+ "version": "8.0.0",
4
4
  "main": "index.js",
5
5
  "description": "",
6
6
  "bin": {
7
7
  "cf-openapi": "./node_modules/cf-openapi/bin/cf-openapi"
8
8
  },
9
9
  "engines": {
10
- "node": "^20.0.0 || ^22.0.0"
10
+ "node": ">=22 <=24"
11
11
  },
12
12
  "files": [
13
13
  "index.js",
14
14
  "infra/**/*"
15
15
  ],
16
16
  "scripts": {
17
- "test": "(find . -not -path './node_modules/*' -path '*/*.spec.js' | grep -v '__tests__' | NODE_ENV=test xargs mocha) && jest",
17
+ "test": "(find . -not -path './node_modules/*' -path '*/*.spec.js' | grep -v '__tests__' | NODE_ENV=test xargs mocha) && CF_TELEMETRY_LOGS_PRETTIFY=single-line jest",
18
18
  "start": "node index.js",
19
- "lint": "eslint infra/**",
19
+ "lint": "eslint .",
20
20
  "build": "echo 'No build step'"
21
21
  },
22
22
  "repository": {
@@ -29,7 +29,6 @@
29
29
  "url": "https://github.com/codefresh-io/service-base/issues"
30
30
  },
31
31
  "resolutions": {
32
- "eslint/**/ansi-regex": "^4.0.0",
33
32
  "js-yaml": "^3.13.1",
34
33
  "json-schema": "^0.4.0",
35
34
  "get-func-name": "^2.0.1"
@@ -48,7 +47,7 @@
48
47
  "bluebird": "^3.5.3",
49
48
  "body-parser": "^1.19.2",
50
49
  "cf-errors": "^0.1.15",
51
- "cf-logs": "^1.1.26",
50
+ "cf-logs": "^1.1.28",
52
51
  "chai": "4.3.10",
53
52
  "compression": "^1.7.4",
54
53
  "cookie-parser": "^1.4.4",
@@ -57,25 +56,29 @@
57
56
  "js-yaml": "^3.13.1",
58
57
  "lodash": "^4.17.21",
59
58
  "method-override": "^3.0.0",
60
- "mongodb": "^6.15.0",
61
- "morgan": "^1.9.1",
62
- "proxyquire": "^1.8.0",
59
+ "mongodb": "^6.21.0",
60
+ "qs": "^6.14.1",
63
61
  "queue": "^4.2.1",
64
- "qs":"^6.14.1",
65
62
  "redis": "^3.1.0",
66
63
  "request": "2.88.2",
67
64
  "request-promise": "4.2.6"
68
65
  },
69
66
  "devDependencies": {
70
- "@shelf/jest-mongodb": "^4.2.0",
71
- "eslint": "^8.52.0",
67
+ "@codefresh-io/cf-telemetry": "^3.9.1",
68
+ "@eslint/eslintrc": "^3.3.1",
69
+ "eslint": "^9.32.0",
72
70
  "eslint-config-airbnb-base": "^15.0.0",
73
- "eslint-plugin-import": "^2.31.0",
74
- "eslint-plugin-jest": "^27.6.3",
75
- "eslint-plugin-mocha": "^4.12.1",
76
- "jest": "^29.7.0",
71
+ "eslint-import-resolver-typescript": "^4.4.4",
72
+ "eslint-plugin-import": "^2.32.0",
73
+ "eslint-plugin-jest": "^28.11.0",
74
+ "eslint-plugin-mocha": "^10.5.0",
75
+ "jest": "^30.2.0",
77
76
  "mocha": "^8.2.1",
78
- "mongodb-memory-server": "^9.1.6"
77
+ "mongodb-memory-server": "^11.0.1",
78
+ "proxyquire": "^1.8.0"
79
+ },
80
+ "peerDependencies": {
81
+ "@codefresh-io/cf-telemetry": "^3.9.1"
79
82
  },
80
83
  "packageManager": "yarn@1.22.22+sha512.a6b2f7906b721bba3d67d4aff083df04dad64c399707841b7acf00f6b133b7ac24255f2652fa22ae3534329dc6180534e98d17432037ff6fd140556e2bb3137e"
81
84
  }