Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions cloudant-change-listener/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
FROM registry.access.redhat.com/ubi9/nodejs-22:latest AS build-env
FROM registry.access.redhat.com/ubi9/nodejs-24:latest AS build-env
WORKDIR /opt/app-root/src
COPY --chown=default:root job/* .
RUN npm install

# Use a small distroless image for as runtime image
FROM gcr.io/distroless/nodejs22
FROM gcr.io/distroless/nodejs24
COPY --from=build-env /opt/app-root/src /app
WORKDIR /app
ENTRYPOINT ["job.mjs"]
CMD ["job.mjs"]
35 changes: 15 additions & 20 deletions cloudant-change-listener/job/ce-api-utils.mjs
Original file line number Diff line number Diff line change
@@ -1,25 +1,20 @@
import log4js from "log4js";
import { IamAuthenticator } from "ibm-cloud-sdk-core";
import CodeEngineV2 from "@ibm-cloud/ibm-code-engine-sdk/dist/code-engine/v2.js";
import winston from "winston";
const { combine, json } = winston.format;

//
// use a formatted logger to have timestamps in the log output
log4js.configure({
appenders: {
out: { type: "stdout" },
},
categories: {
default: { appenders: ["out"], level: process.env.LOGLEVEL || "debug" },
},
const logger = winston.createLogger({
level: "debug",
transports: [new winston.transports.Console()],
format: combine(json()),
});
const logger = log4js.getLogger();

let codeEngineApi;
function getCodeEngineApi() {
if (!codeEngineApi) {
// Extracting the region from the CE_DOMAIN (us-south.codeengine.appdomain.cloud)
const region = process.env.CE_DOMAIN.substring(0, process.env.CE_DOMAIN.indexOf("."));
logger.trace(`CE_DOMAIN: '${process.env.CE_DOMAIN}', region: '${region}'`);
logger.debug(`CE_DOMAIN: '${process.env.CE_DOMAIN}', region: '${region}'`);

// Construct the Code Engine client using the IAM authenticator.
// see: https://cloud.ibm.com/apidocs/codeengine/v2?code=node#authentication
Expand All @@ -36,17 +31,17 @@ function getCodeEngineApi() {

export async function getJobConfig(projectId, configMapName) {
const fn = "getJobConfig ";
logger.trace(`${fn}> configMapName: '${configMapName}'`);
logger.debug(`${fn}> configMapName: '${configMapName}'`);
const configMap = await getConfigMap(projectId, configMapName);
logger.trace(`${fn}< configmap '${configMapName}' exists `);
logger.debug(`${fn}< configmap '${configMapName}' exists `);
return configMap;
}

export async function updateJobConfig(projectId, configMapName, configDataToUpdate) {
const fn = "updateJobConfig ";
logger.trace(`${fn}> configMapName: '${configMapName}', configDataToUpdate: '${JSON.stringify(configDataToUpdate)}'`);
logger.debug(`${fn}> configMapName: '${configMapName}', configDataToUpdate: '${JSON.stringify(configDataToUpdate)}'`);
const updatedConfigMap = await updateConfigMap(projectId, configMapName, configDataToUpdate);
logger.trace(`${fn}< updated configmap '${JSON.stringify(updatedConfigMap)}'`);
logger.debug(`${fn}< updated configmap '${JSON.stringify(updatedConfigMap)}'`);
}

export async function isJobAlreadyRunning(projectId, jobName) {
Expand All @@ -55,7 +50,7 @@ export async function isJobAlreadyRunning(projectId, jobName) {

try {
const jobRunList = await listJobRuns(projectId, jobName);
logger.trace(`${fn}- jobRunList: '${JSON.stringify(jobRunList)}'`);
logger.debug(`${fn}- jobRunList: '${JSON.stringify(jobRunList)}'`);

if (!jobRunList || !Array.isArray(jobRunList)) {
logger.debug(`${fn}< false - unexpected response`);
Expand Down Expand Up @@ -115,7 +110,7 @@ async function listJobRuns(projectId, jobName) {
logger.debug(`Successfully listed all runs of job '${jobName}' - duration: ${Date.now() - startTime}ms`);
return allResults;
} catch (err) {
logger.error(`Failed to list runs of job '${jobName}', err =`, err);
logger.error(`Failed to list runs of job '${jobName}'`, err);
throw err;
}
}
Expand All @@ -142,7 +137,7 @@ async function updateConfigMap(projectId, name, configData) {
logger.debug(`Successfully updated ConfigMap '${name}' - duration: ${Date.now() - startTime}ms`);
return res.result;
} catch (err) {
logger.error(`Failed to update ConfigMap '${JSON.stringify(params)}', err =`, err);
logger.error(`Failed to update ConfigMap '${JSON.stringify(params)}'`, err);
throw err;
}
}
Expand All @@ -166,7 +161,7 @@ async function getConfigMap(projectId, name) {
logger.debug(`Successfully read ConfigMap '${name}' - duration: ${Date.now() - startTime}ms`);
return res.result;
} catch (err) {
logger.error(`Failed to read ConfigMap '${JSON.stringify(params)}', err =`, err);
logger.error(`Failed to read ConfigMap '${JSON.stringify(params)}'`, err);
throw err;
}
}
44 changes: 20 additions & 24 deletions cloudant-change-listener/job/job.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
* - CLOUDANTNOSQLDB_PORT (optional) DB port number (default: 443)
* - CLOUDANTNOSQLDB_APIKEY The IBM Cloud IAM APIkey if using IAMAuthentication on DB connection
* - DB_NAME The name of the DB to listen on. e.g "MyTestDB"
* - DB_LAST_SEQ (optional) last_seq value to use as start identifier for db changes feed.
* - DB_LAST_SEQ (optional) lastSeq value to use as start identifier for db changes feed.
* - CE_TARGET Full URL of the target Code Engine function or application that should receive events
* - DB_POST_CHANGES_TIMEOUT Max wait-time in milliseconds on each long-polling call to the DB (default: 8000)
* The poll timeout can be used to adapt the listening timeout to the settings defined on the cloudant DB
Expand All @@ -73,21 +73,17 @@
*************************************************************************************/
import { CloudantV1 } from "@ibm-cloud/cloudant";
import { BasicAuthenticator, IamAuthenticator } from "ibm-cloud-sdk-core";
import log4js from "log4js";
import { LRUCache } from "lru-cache";
import { isJobAlreadyRunning, updateJobConfig, getJobConfig } from "./ce-api-utils.mjs";
import winston from "winston";
const { combine, json } = winston.format;

//
// use a formatted logger to have timestamps in the log output
log4js.configure({
appenders: {
out: { type: "stdout" },
},
categories: {
default: { appenders: ["out"], level: process.env.LOGLEVEL || "info" },
},
const logger = winston.createLogger({
level: "debug",
transports: [new winston.transports.Console()],
format: combine(json()),
});
const logger = log4js.getLogger();


logger.info('Starting Cloudant DB change listener ...');

Expand Down Expand Up @@ -358,7 +354,7 @@ function dbChangeHandler(change) {
logger.info(`Successfully called CE URL of app or function with response: '${httpRes.status}' - duration: ${Date.now() - startTime}ms`);
})
.catch((err) => {
logger.error(`Failed to call CE URL of app or function'${targetCEUrl}', err =`, err);
logger.error(`Failed to call CE URL of app or function '${targetCEUrl}'`, err);
});
}

Expand Down Expand Up @@ -433,7 +429,7 @@ async function doListen() {
//* change from which the listening starts. This since option is controlled in
//* this loop by following rules:
//* - use DB_LAST_SEQ value on first cycle of loop
//* - if waitForDbChanges() call provides a valid last_seq value in response, then
//* - if waitForDbChanges() call provides a valid lastSeq value in response, then
//* use this value for the next run of the loop
//* - if waitForDbChanges() call fails with timeout or retryalbe error,
//* then re-run with same seq value as in the previous loop cycle
Expand All @@ -454,7 +450,7 @@ async function doListen() {

//
// Response exist when waitForDbChanges returns OK
// If response.result.results == 0 = { "results": [] , "last_seq" : value } means timeout occur
// If response.result.results == 0 = { "results": [] , "lastSeq" : value } means timeout occur
// If response = <value> Doc change received ( feed.on(data,..))
if (Object.keys(response).length === 0) {
logger.info("Cloudant-SDK provided an unexpected empty response object on postChanges() call. Continue listening");
Expand All @@ -464,15 +460,15 @@ async function doListen() {

if (!response || !response.result) {
sinceToken = "now";
logger.info(`Got Ok postChanges result, but not a valid last_seq value. Start fresh by pulling only new changes.`);
logger.info(`Got Ok postChanges result, but not a valid lastSeq value. Start fresh by pulling only new changes.`);
continue; // run waitForDbChanges with new since token
}

if (Array.isArray(response.result.results) && response.result.results.length === 0 && response.result.last_seq) {
if (Array.isArray(response.result.results) && response.result.results.length === 0 && response.result.lastSeq) {
//
// Wait timed out and delivered the lastSeq value as start point for the next loop
sinceToken = response.result.last_seq;
lastHandledSeq = response.result.last_seq;
sinceToken = response.result.lastSeq;
lastHandledSeq = response.result.lastSeq;
logger.info(`No changes detected in the given wait period. Assigned new since token from result.`);
continue; // run waitForDbChanges with updated since token
}
Expand All @@ -486,9 +482,9 @@ async function doListen() {
});

//
// Get the last_seq value to use in the next postChanges() query
sinceToken = response.result.last_seq;
lastHandledSeq = response.result.last_seq;
// Get the lastSeq value to use in the next postChanges() query
sinceToken = response.result.lastSeq;
lastHandledSeq = response.result.lastSeq;
continue; // run waitForDbChanges with updated since token
}

Expand All @@ -497,7 +493,7 @@ async function doListen() {
// valid lastSeq number then continue the wrapper loop with
// since = now
sinceToken = "now";
logger.info(`Got Ok postChanges result, but not a valid last_seq value. Start fresh by pulling only new changes.`);
logger.info(`Got Ok postChanges result, but not a valid lastSeq value. Start fresh by pulling only new changes.`);
continue;
} catch (err) {
// Handle how to proceed loop in case of WRAPPER_TIMEOUT
Expand All @@ -512,7 +508,7 @@ async function doListen() {
continue;
}

logger.error("Error in waitForDbChanges loop, err = ", err);
logger.error("Error in waitForDbChanges loop", err);

// In case of unexpected error occur, then continue
// with wrapper loop and since = 'now'
Expand Down
12 changes: 6 additions & 6 deletions cloudant-change-listener/job/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"description": "Change event listener for cloudant DB",
"main": "job.mjs",
"engines": {
"node": "^22",
"node": "^24",
"npm": "^11"
},
"scripts": {
Expand All @@ -14,10 +14,10 @@
"author": "",
"license": "ISC",
"dependencies": {
"@ibm-cloud/cloudant": "^0.12.7",
"@ibm-cloud/ibm-code-engine-sdk": "^4.0.0",
"ibm-cloud-sdk-core": "^5.4.2",
"lru-cache": "^10.0.3",
"log4js": "^6.9.1"
"@ibm-cloud/cloudant": "^0.12.16",
"@ibm-cloud/ibm-code-engine-sdk": "^5.0.0",
"ibm-cloud-sdk-core": "^5.4.9",
"lru-cache": "^11.2.7",
"winston": "^3.19.0"
}
}
2 changes: 1 addition & 1 deletion fotobox/frontend-app/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,6 @@ RUN npm ci --omit dev

EXPOSE 3000

ENTRYPOINT [ "node", "build" ]
CMD [ "node", "build" ]


Loading