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
4 changes: 4 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
root = true

[*]
end_of_line = lf
6 changes: 4 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
# Common Stage
FROM node:23-slim as base

ARG WUD_VERSION=unknown

LABEL maintainer="fmartinou"
LABEL org.opencontainers.image.authors="fmartinou"
LABEL org.opencontainers.image.version="$WUD_VERSION"
EXPOSE 3000

ARG WUD_VERSION=unknown

ENV WORKDIR=/home/node/app
ENV WUD_LOG_FORMAT=text
ENV WUD_VERSION=$WUD_VERSION
Expand Down
1 change: 1 addition & 0 deletions app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"main": "index.js",
"scripts": {
"start": "nodemon index.js | bunyan -L -o short",
"debug": "nodemon --inspect index.js | bunyan -L -o long",
"doc": ".docsify serve ./docs",
"format": "prettier --write .",
"lint:fix": "prettier --write . && eslint '**/*.js'",
Expand Down
18 changes: 9 additions & 9 deletions app/tag/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,22 @@
const semver = require('semver');
const log = require('../log');

function coerce(rawVersion) {
return semver.coerce(rawVersion);
}

/**
* Parse a string to a semver (return null is it cannot be parsed as a valid semver).
* @param rawVersion
* @returns {*|SemVer}
*/
function parse(rawVersion) {
const rawVersionCleaned = semver.clean(rawVersion, { loose: true });
const rawVersionSemver = semver.parse(
rawVersionCleaned !== null ? rawVersionCleaned : rawVersion,
semver.clean(rawVersion, { loose: true }) || rawVersion,
);
// Hurrah!
if (rawVersionSemver !== null) {
return rawVersionSemver;
}

// Last chance; try to coerce (all data behind patch digit will be lost).
return semver.coerce(rawVersion);
// Coerce means that all data behind patch digit will be lost
return rawVersionSemver || semver.coerce(rawVersion);
}

/**
Expand All @@ -36,7 +35,7 @@ function isGreater(version1, version2) {
if (version1Semver === null || version2Semver === null) {
return false;
}
return semver.gte(version1Semver, version2Semver);
return semver.gt(version1Semver, version2Semver);
}

/**
Expand Down Expand Up @@ -96,6 +95,7 @@ function transform(transformFormula, originalTag) {
}

module.exports = {
coerce,
parse,
isGreater,
diff,
Expand Down
128 changes: 95 additions & 33 deletions app/watchers/providers/docker/Docker.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const cron = require('node-cron');
const parse = require('parse-docker-image-name');
const debounce = require('just-debounce');
const {
coerce: coerceSemver,
parse: parseSemver,
isGreater: isGreaterSemver,
transform: transformTag,
Expand All @@ -21,6 +22,7 @@ const {
wudDisplayIcon,
wudTriggerInclude,
wudTriggerExclude,
wudInspectTagPath,
} = require('./label');
const storeContainer = require('../../../store/container');
const log = require('../../../log');
Expand Down Expand Up @@ -240,6 +242,46 @@ function isDigestToWatch(wudWatchDigestLabelValue, isSemver) {
return result;
}

/**
* Extract image semver from locally installed image.
* @param containerData
* @param logger
* @param inspectPath
* @param inspectRegex
* @returns {string}
*/
function getSemverOfInstalledImage(
containerData,
logger,
inspectPath = 'Config/Labels/org.opencontainers.image.version',
) {
try {
const rawValue = getContainerValueByPath(containerData, inspectPath);
return coerceSemver(rawValue)?.version;
} catch (err) {
logger.warn(err);
}
}

function getContainerValueByPath(obj, path) {
const parts = path.split('/');
let currentValue = obj;

for (let part of parts) {
if (Array.isArray(currentValue)) {
const found = currentValue.find((e) => e.startsWith(part + '='));
if (found) return found.split('=')[1];
return undefined;
} else if (currentValue && part in currentValue) {
currentValue = currentValue[part];
} else {
return undefined;
}
}

return currentValue;
}

/**
* Docker Watcher Component.
*/
Expand Down Expand Up @@ -377,40 +419,47 @@ class Docker extends Component {
* @return {Promise<void>}
*/
async onDockerEvent(dockerEventChunk) {
const dockerEvent = JSON.parse(dockerEventChunk.toString());
const action = dockerEvent.Action;
const containerId = dockerEvent.id;
try {
const dockerEvent = JSON.parse(dockerEventChunk.toString());
const action = dockerEvent.Action;
const containerId = dockerEvent.id;

// If the container was created or destroyed => perform a watch
if (action === 'destroy' || action === 'create') {
await this.watchCronDebounced();
} else {
// Update container state in db if so
try {
const container =
await this.dockerApi.getContainer(containerId);
const containerInspect = await container.inspect();
const newStatus = containerInspect.State.Status;
const containerFound = storeContainer.getContainer(containerId);
if (containerFound) {
// Child logger for the container to process
const logContainer = this.log.child({
container: fullName(containerFound),
});
const oldStatus = containerFound.status;
containerFound.status = newStatus;
if (oldStatus !== newStatus) {
storeContainer.updateContainer(containerFound);
logContainer.info(
`Status changed from ${oldStatus} to ${newStatus}`,
);
// If the container was created or destroyed => perform a watch
if (action === 'destroy' || action === 'create') {
await this.watchCronDebounced();
} else {
// Update container state in db if so
try {
const container =
await this.dockerApi.getContainer(containerId);
const containerInspect = await container.inspect();
const newStatus = containerInspect.State.Status;
const containerFound =
storeContainer.getContainer(containerId);
if (containerFound) {
// Child logger for the container to process
const logContainer = this.log.child({
container: fullName(containerFound),
});
const oldStatus = containerFound.status;
containerFound.status = newStatus;
if (oldStatus !== newStatus) {
storeContainer.updateContainer(containerFound);
logContainer.info(
`Status changed from ${oldStatus} to ${newStatus}`,
);
}
}
} catch (e) {
this.log.debug(
`Unable to get container details for container id=[${containerId}] (${e.message})`,
);
}
} catch (e) {
this.log.debug(
`Unable to get container details for container id=[${containerId}] (${e.message})`,
);
}
} catch (e) {
this.log.debug(
`Unable to parse docker event chunk=[${dockerEventChunk}] (${e.message})`,
);
}
}

Expand Down Expand Up @@ -576,7 +625,6 @@ class Docker extends Component {
/**
* Find new version for a Container.
*/

async findNewVersion(container, logContainer) {
const registryProvider = getRegistry(container.image.registry.name);
const result = { tag: container.image.tag.value };
Expand Down Expand Up @@ -681,7 +729,13 @@ class Docker extends Component {
}

// Get container image details
const image = await this.dockerApi.getImage(container.Image).inspect();
let image;
try {
image = await this.dockerApi.getImage(container.Image).inspect();
} catch (e) {
this.log.warn('Cannot get image inspect', e);
return Promise.reject();
}

// Get useful properties
const containerName = getContainerName(container);
Expand All @@ -706,7 +760,15 @@ class Docker extends Component {
[imageNameToParse] = image.RepoTags;
}
const parsedImage = parse(imageNameToParse);
const tagName = parsedImage.tag || 'latest';
const tagName = container.Labels[wudInspectTagPath]
? getSemverOfInstalledImage(
await this.dockerApi.getContainer(containerId).inspect(),
this.log,
container.Labels[wudInspectTagPath],
) ||
parsedImage.tag ||
'latest'
: parsedImage.tag || 'latest';
const parsedTag = parseSemver(transformTag(transformTags, tagName));
const isSemver = parsedTag !== null && parsedTag !== undefined;
const watchDigest = isDigestToWatch(
Expand Down
5 changes: 5 additions & 0 deletions app/watchers/providers/docker/label.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,11 @@ module.exports = {
*/
wudTagTransform: 'wud.tag.transform',

/**
* Optional path of docker inspect object where to find current semver.
*/
wudInspectTagPath: 'wud.inspect.tag.path',

/**
* Should container digest be tracked? (true | false).
*/
Expand Down