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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
*~
node_modules
dist
*.log
*.log
.idea
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,11 @@ dc-outdated
If the compose file contains outdated docker images, the programm will list the outdated image names. Besides the image name, the application will also output the **current** image version (as specified in the _docker-compose.yml_ file), the next **wanted** version (max possible version according to the image's current version caret range) and the **latest** version of the image.

```
Image Current Wanted[^] Latest
-------------------- --------- ----------- ------------
library/rabbitmq 3.6.16 3.7.11 3.8.0-beta.2
library/mongo 3.4.16 3.7.9 4.1.7
library/influxdb 0.13.0 1.7.3 1.7.3
Image Current Wanted[~] Wanted[^] Latest
-------------------- --------- ----------- ----------- ------------
library/rabbitmq 3.6.16 3.6.19 3.7.11 3.8.0-beta.2
library/mongo 3.4.16 3.4.19 3.7.9 4.1.7
library/influxdb 0.13.0 0.13.5 1.7.3 1.7.3
```

For advanced usage, command line flags can be used to change the default behavoir of the application:
Expand Down
39 changes: 20 additions & 19 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,28 +28,29 @@
"check-formatting": "prettier --check ."
},
"dependencies": {
"axios": "^0.21.1",
"cli-progress": "^3.9.0",
"commander": "^8.1.0",
"easy-table": "^1.1.1",
"inquirer": "^8.1.2",
"axios": "^0.27.2",
"cli-progress": "^3.11.1",
"commander": "^9.2.0",
"easy-table": "^1.2.0",
"fastq": "^1.13.0",
"inquirer": "^8.2.4",
"js-yaml": "^4.1.0",
"lodash": "^4.17.11",
"semver": "^7.3.5",
"tslib": "^2.3.1"
"lodash": "^4.17.21",
"semver": "^7.3.7",
"tslib": "^2.4.0"
},
"devDependencies": {
"@types/cli-progress": "^3.9.2",
"@types/easy-table": "^0.0.33",
"@types/inquirer": "^7.3.3",
"@types/js-yaml": "^4.0.3",
"@types/lodash": "^4.14.172",
"@types/node": "^12.20.21",
"@types/semver": "^7.3.8",
"conventional-changelog-cli": "^2.1.1",
"prettier": "^2.3.2",
"@types/cli-progress": "^3.11.0",
"@types/easy-table": "^1.2.0",
"@types/inquirer": "^8.2.1",
"@types/js-yaml": "^4.0.5",
"@types/lodash": "^4.14.182",
"@types/node": "^12.20.52",
"@types/semver": "^7.3.9",
"conventional-changelog-cli": "^2.2.2",
"prettier": "^2.6.2",
"rimraf": "^3.0.2",
"typescript": "^4.3.5",
"yarn-deduplicate": "^3.1.0"
"typescript": "^4.7.2",
"yarn-deduplicate": "^5.0.0"
}
}
21 changes: 13 additions & 8 deletions src/lib/docker-utils.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import axios from 'axios';
import { last as _last, merge as _merge } from 'lodash';
import { last as _last } from 'lodash';
import { compare as semverCompare, maxSatisfying as semverMaxSatisfying, valid as semverValid } from 'semver';

import { Credentials, CredentialsStore } from './credentials';

export const DOCKER_REGISTRY_HOST = 'docker.io';
export const DOCKER_REGISTRY_HOST = 'registry-1.docker.io';

export interface DockerImage {
name: string;
Expand Down Expand Up @@ -147,8 +147,9 @@ export async function getLatestImageVersion(
export async function getImageUpdateTags(
credentialsStore: CredentialsStore,
dockerImage: DockerImage
): Promise<{ wanted: string | undefined; latest: string | undefined }> {
let wanted: string | undefined;
): Promise<{ wantedPatch: string | undefined; wantedMinor: string | undefined; latest: string | undefined }> {
let wantedPatch: string | undefined;
let wantedMinor: string | undefined;
let latest: string | undefined;
const tags = await listTags(credentialsStore, dockerImage);
if (tags) {
Expand All @@ -157,12 +158,16 @@ export async function getImageUpdateTags(
latest = _last(validTags);

if (dockerImage.tag && semverValid(dockerImage.tag)) {
wanted = semverMaxSatisfying(validTags, `^${dockerImage.tag}`) ?? undefined;
if (!wanted) {
wanted = dockerImage.tag;
wantedMinor = semverMaxSatisfying(validTags, `^${dockerImage.tag}`) ?? undefined;
if (!wantedMinor) {
wantedMinor = dockerImage.tag;
}
wantedPatch = semverMaxSatisfying(validTags, `~${dockerImage.tag}`) ?? undefined;
if (!wantedPatch) {
wantedPatch = dockerImage.tag;
}
}
}

return { wanted, latest };
return { wantedPatch, wantedMinor, latest };
}
67 changes: 38 additions & 29 deletions src/lib/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Bar as CliProgressBar, Presets as CliProgressBarPresets } from 'cli-progress';
import EasyTable from 'easy-table';
import { diff as semverDiff, valid as semverValid } from 'semver';
import { orderBy as _orderBy } from 'lodash';
import { promise as queueAsPromise } from 'fastq';

import { CredentialsLoader, CredentialsStore } from './credentials';
import {
Expand Down Expand Up @@ -55,7 +57,8 @@ export interface Options {

export interface OutdatedImage {
image: DockerImage;
wantedVersion: string;
wantedPatchVersion: string;
wantedMinorVersion: string;
latestVersion: string;
}

Expand All @@ -65,7 +68,7 @@ class ProgressBarWrapper {
private progressInformation: { total: number; current: number } | undefined;

constructor() {
this.progressBar = new CliProgressBar({}, CliProgressBarPresets.shades_classic);
this.progressBar = new CliProgressBar({ clearOnComplete: true }, CliProgressBarPresets.shades_classic);
}

public start(total: number, initialValue: number): void {
Expand Down Expand Up @@ -141,40 +144,46 @@ export async function listOutdated(options: Options): Promise<OutdatedImage[]> {
const outdatedImages: OutdatedImage[] = [];
progressBar.start(filteredImages.length, 0);

try {
for (const image of filteredImages) {
const { latest, wanted } = await getImageUpdateTags(credentials, image);

if (image.tag) {
const wantedDiff = wanted && semverDiff(image.tag, wanted);
const latestDiff = latest && semverDiff(image.tag, latest);
if (wantedDiff || latestDiff) {
outdatedImages.push({
image,
wantedVersion: wanted || 'NA',
latestVersion: latest || 'NA'
});
}
} else {
console.warn(`Skipping image '${image.name}' since we cannot determine its tag!`);
const queue = queueAsPromise<DockerImage>(async image => {
const { latest, wantedMinor, wantedPatch } = await getImageUpdateTags(credentials, image);

if (image.tag) {
const wantedPatchDiff = wantedPatch && semverDiff(image.tag, wantedPatch);
const wantedMinorDiff = wantedMinor && semverDiff(image.tag, wantedMinor);
const latestDiff = latest && semverDiff(image.tag, latest);
if (wantedPatchDiff || wantedMinorDiff || latestDiff) {
outdatedImages.push({
image,
wantedPatchVersion: wantedPatch || 'NA',
wantedMinorVersion: wantedMinor || 'NA',
latestVersion: latest || 'NA'
});
}
progressBar.increment(1);
} else {
console.warn(`Skipping image '${image.name}' since we cannot determine its tag!`);
}
progressBar.increment(1);
}, 10);

try {
filteredImages.forEach(image => queue.push(image));
await queue.drained();
} finally {
progressBar.stop();
} catch (err) {
progressBar.stop();
throw err;
}

const table = new EasyTable();

outdatedImages.forEach(({ image, wantedVersion, latestVersion }) => {
table.cell('Image', image.name);
table.cell('Current', image.tag);
table.cell('Wanted[^]', wantedVersion);
table.cell('Latest', latestVersion);
table.newRow();
});
_orderBy(outdatedImages, 'image', 'asc').forEach(
({ image, wantedPatchVersion, wantedMinorVersion, latestVersion }) => {
table.cell('Image', image.name);
table.cell('Current', image.tag);
table.cell('Wanted[~]', wantedPatchVersion);
table.cell('Wanted[^]', wantedMinorVersion);
table.cell('Latest', latestVersion);
table.newRow();
}
);

console.log(table.toString());

Expand Down
Loading