Skip to content

Commit

Permalink
Improve readablity of the code, change entity icons, catch the case w…
Browse files Browse the repository at this point in the history
…hen its trying to update itself #9
  • Loading branch information
MichelFR committed Feb 25, 2023
1 parent 5b6636b commit 6e23019
Show file tree
Hide file tree
Showing 7 changed files with 268 additions and 221 deletions.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "mqdockerup",
"version": "0.9.0",
"version": "0.10.0",
"description": "A Node.js application that pushes information about running Docker containers to an MQTT server.",
"main": "index.js",
"scripts": {
Expand Down
31 changes: 7 additions & 24 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,14 +31,10 @@ const checkAndPublishUpdates = async (): Promise<void> => {
const currentTags = imageInfo.RepoTags.map((tag) => tag.split(":")[1]);

for (const currentTag of currentTags) {
const response = await axios.get(
`https://registry.hub.docker.com/v2/repositories/${imageWithoutTags}/tags?name=${currentTag}`
);
const response = await axios.get(`https://registry.hub.docker.com/v2/repositories/${imageWithoutTags}/tags?name=${currentTag}`);
if (response.data.results[0].images) {
const newDigest = response.data.results[0].digest;
const previousDigest = imageInfo.RepoDigests.find((d) =>
d.endsWith(`:${currentTag}`)
);
const previousDigest = imageInfo.RepoDigests.find((d) => d.endsWith(`:${currentTag}`));

if (!imageInfo.RepoDigests.find((d) => d.endsWith(`@${newDigest}`))) {
console.debug(`🚨 New version available for image ${image}`);
Expand All @@ -54,9 +50,7 @@ const checkAndPublishUpdates = async (): Promise<void> => {
console.debug(`🟢 Image ${image}:${currentTag} is up-to-date`);
}
} else {
console.debug(
`🔍 No information found for image: ${image}:${currentTag}`
);
console.debug(`🔍 No information found for image: ${image}:${currentTag}`);
}
}
}
Expand All @@ -66,20 +60,13 @@ const checkAndPublishUpdates = async (): Promise<void> => {
}

console.debug("🔍 Finished checking for image updates");
console.debug(
`🕒 Next check in ${TimeService.formatDuration(
TimeService.parseDuration(config.main.interval)
)}`
);
console.debug(`🕒 Next check in ${TimeService.formatDuration(TimeService.parseDuration(config.main.interval))}`);
};

let intervalId: NodeJS.Timeout;

const startInterval = async () => {
intervalId = setInterval(
checkAndPublishUpdates,
TimeService.parseDuration(config.main.interval)
);
intervalId = setInterval(checkAndPublishUpdates, TimeService.parseDuration(config.main.interval));
console.debug(`🔁 Checking for image updates every ${config.main.interval}`);
};

Expand All @@ -103,8 +90,7 @@ client.on("message", async (topic: string, message: any) => {
const containerId = data?.containerId;
const image = data?.image;

if ((topic = "mqdockerup/update" && containerId)) {

if ((topic = "mqdockerup/update" && containerId)) {
console.log(`🚀 Got update message for ${image}`);
client.publish(
`${config.mqtt.topic}/${image}/update`,
Expand Down Expand Up @@ -137,7 +123,6 @@ client.on("message", async (topic: string, message: any) => {
);
console.log("🚀 Updated container ");


await checkAndPublishUpdates();
}
});
Expand All @@ -146,9 +131,7 @@ const exitHandler = async (exitCode: number) => {
HomeassistantService.publishAvailability(client, false);

const now = new Date().toLocaleString();
const message = exitCode === 0
? `🛑 MqDockerUp gracefully stopped at ${now}`
: `💥 MqDockerUp stopped due to an error at ${now}`;
const message = exitCode === 0 ? `🛑 MqDockerUp gracefully stopped at ${now}` : `💥 MqDockerUp stopped due to an error at ${now}`;

console.debug(message);
process.exit(exitCode);
Expand Down
14 changes: 9 additions & 5 deletions src/services/ConfigService.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
import yaml from "yaml";
import fs from "fs";

/**
* ConfigService class that provides access to the application configuration settings.
*/
export default class ConfigService {
/**
* Gets the configuration settings.
* @returns {any} The merged configuration settings.
*/
public static getConfig(): any {
try {
// Define the default values
Expand All @@ -23,7 +30,6 @@ export default class ConfigService {
},
};

// Parse the config file
const config = yaml.parse(fs.readFileSync("config.yaml", "utf8"));

// Override the config values with the environment variables
Expand All @@ -33,14 +39,12 @@ export default class ConfigService {

// Override the main values with the environment variables
for (const key of Object.keys(config.main)) {
config.main[key] =
process.env[`MAIN_${key.toUpperCase()}`] ?? config.main[key];
config.main[key] = process.env[`MAIN_${key.toUpperCase()}`] ?? config.main[key];
}

// Override the mqtt values with the environment variables
for (const key of Object.keys(config.mqtt)) {
config.mqtt[key] =
process.env[`MQTT_${key.toUpperCase()}`] ?? config.mqtt[key];
config.mqtt[key] = process.env[`MQTT_${key.toUpperCase()}`] ?? config.mqtt[key];
}

// Merge the config values with the default values
Expand Down
179 changes: 134 additions & 45 deletions src/services/DockerService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,64 +2,80 @@ import Docker from "dockerode";
import { ContainerInspectInfo } from "dockerode";
import axios from "axios";

/**
* Represents a Docker service for managing Docker containers and images.
*/
export default class DockerService {
private static docker = new Docker();

/**
* Returns a list of inspect information for all containers.
*
* @returns A promise that resolves to an array of `ContainerInspectInfo`.
*/
public static async listContainers(): Promise<ContainerInspectInfo[]> {
const containers = await DockerService.docker.listContainers();

return Promise.all(
containers.map(async (container) => {
const containerInfo = await DockerService.docker
.getContainer(container.Id)
.inspect();
const containerInfo = await DockerService.docker.getContainer(container.Id).inspect();
return containerInfo;
})
);
}

public static async getImageRegistry(
imageName: string,
tag: string
): Promise<{ registry: any; response: any }> {
/**
* Gets the Docker image registry and response information for the specified image name and tag.
*
* @param imageName - The name of the Docker image.
* @param tag - The tag of the Docker image.
* @returns A promise that resolves to an object with `registry` and `response` properties.
*/
public static async getImageRegistry(imageName: string, tag: string): Promise<{ registry: any; response: any }> {
try {
const response = await axios.get(`https://registry.hub.docker.com/v2/repositories/${imageName}/tags?name=${tag}`);
if (response.status === 200) {
return { registry: 'Docker Hub', response };
return { registry: "Docker Hub", response };
}
} catch (error) {}

const registryList = [
{ name: 'eu.gcr.io', displayName: 'Google Cloud Registry (EU)', checkEndsWith: true },
{ name: 'asia.gcr.io', displayName: 'Google Cloud Registry (Asia)', checkEndsWith: true },
{ name: 'us.gcr.io', displayName: 'Google Cloud Registry (US)', checkEndsWith: true },
{ name: 'docker.pkg.airfocus.io', displayName: 'Airfocus Container Registry' },
{ name: 'quay.io', displayName: 'Quay.io' },
{ name: 'gcr.io', displayName: 'Google Container Registry' },
{ name: 'registry.access.redhat.com', displayName: 'Red Hat Registry' },
{ name: 'ghcr.io', displayName: 'GitHub Container Registry' },
{ name: 'docker.io', displayName: 'Docker Hub' },
{ name: 'amazonaws.com', displayName: 'Amazon Elastic Container Registry', checkEndsWith: true },
{ name: 'mcr.microsoft.com', displayName: 'Microsoft Container Registry' },
{ name: 'docker.pkg.github.com', displayName: 'GitHub Packages Container Registry' },
{ name: 'harbor.domain.com', displayName: 'VMware Harbor Registry' },
{ name: 'docker.elastic.co', displayName: 'Elastic Container Registry' },
{ name: 'registry.gitlab.com', displayName: 'GitLab Container Registry' },
{ name: 'k8s.gcr.io', displayName: 'Google Kubernetes Engine Registry' },
{ name: 'docker.pkg.digitalocean.com', displayName: 'DigitalOcean Container Registry' },
{ name: "eu.gcr.io", displayName: "Google Cloud Registry (EU)", checkEndsWith: true },
{ name: "asia.gcr.io", displayName: "Google Cloud Registry (Asia)", checkEndsWith: true },
{ name: "us.gcr.io", displayName: "Google Cloud Registry (US)", checkEndsWith: true },
{ name: "docker.pkg.airfocus.io", displayName: "Airfocus Container Registry" },
{ name: "quay.io", displayName: "Quay.io" },
{ name: "gcr.io", displayName: "Google Container Registry" },
{ name: "registry.access.redhat.com", displayName: "Red Hat Registry" },
{ name: "ghcr.io", displayName: "GitHub Container Registry" },
{ name: "docker.io", displayName: "Docker Hub" },
{ name: "amazonaws.com", displayName: "Amazon Elastic Container Registry", checkEndsWith: true },
{ name: "mcr.microsoft.com", displayName: "Microsoft Container Registry" },
{ name: "docker.pkg.github.com", displayName: "GitHub Packages Container Registry" },
{ name: "harbor.domain.com", displayName: "VMware Harbor Registry" },
{ name: "docker.elastic.co", displayName: "Elastic Container Registry" },
{ name: "registry.gitlab.com", displayName: "GitLab Container Registry" },
{ name: "k8s.gcr.io", displayName: "Google Kubernetes Engine Registry" },
{ name: "docker.pkg.digitalocean.com", displayName: "DigitalOcean Container Registry" },
];

for (const registry of registryList) {
if (registry.checkEndsWith && imageName.endsWith(`${registry.name}`)) {
return { registry: registry.displayName, response: null };
} else if (!registry.checkEndsWith && imageName.startsWith(`${registry.name}`)) {
return { registry: registry.displayName, response: null };
}
}
return { registry: 'No registry (self-built?)', response: null };

return { registry: "No registry (self-built?)", response: null };
}

/**
* Gets the private registry for the specified image name.
*
* @param imageName - The name of the Docker image.
* @returns The private registry or `null` if it is not found.
*/
public static getPrivateRegistry(imageName: string): string | null {
const parts = imageName.split("/");
if (parts.length >= 2) {
Expand All @@ -68,24 +84,35 @@ export default class DockerService {
return null;
}

public static async getImageInfo(
imageId: string
): Promise<Docker.ImageInspectInfo> {
/**
* Gets the inspect information for the specified Docker image.
*
* @param imageId - The ID of the Docker image.
* @returns A promise that resolves to an `ImageInspectInfo` object.
*/
public static async getImageInfo(imageId: string): Promise<Docker.ImageInspectInfo> {
return await DockerService.docker.getImage(imageId).inspect();
}

/**
* Updates a Docker container with the latest image.
*
* @param containerId - The ID of the Docker container to update.
* @returns A promise that resolves to the new Docker container.
*/
public static async updateContainer(containerId: string) {
// TODO: Catch the case if its trying to update MqDockerUp itself

// Get the old container and its information
const container = DockerService.docker.getContainer(containerId);
const info = await container.inspect();

// Pull the latest image for the new container
const imageName = info.Config.Image;

// Catch the case if its trying to update MqDockerUp itself
if (imageName.toLowerCase() === "mqdockerup") {
console.error("You cannot update MqDockerUp from within MqDockerUp. Please update MqDockerUp manually.");
return;
}

await DockerService.docker.pull(imageName);

// Create the configuration for the new container
const containerConfig: any = {
...info,
...info.Config,
Expand All @@ -95,32 +122,94 @@ export default class DockerService {
Image: imageName,
};

// Map the volumes from the old container to the new container
const mounts = info.Mounts;
const binds = mounts.map((mount) => `${mount.Source}:${mount.Destination}`);
containerConfig.HostConfig.Binds = binds;

// Stop and remove the old container
await container.stop();
await container.remove();

// Create and start the new container
const newContainer = await DockerService.docker.createContainer(
containerConfig
);
const newContainer = await DockerService.docker.createContainer(containerConfig);
await newContainer.start();

// Return the new container
return newContainer;
}

/**
* Stops a Docker container.
*
* @param containerId - The ID of the Docker container to stop.
*/
public static async stopContainer(containerId: string) {
const container = DockerService.docker.getContainer(containerId);
await container.stop();
}

/**
* Starts a Docker container.
*
* @param containerId - The ID of the Docker container to start.
*/
public static async startContainer(containerId: string) {
const container = DockerService.docker.getContainer(containerId);
await container.start();
}

/**
* Removes a Docker container.
* @param containerId - The ID of the Docker container to remove.
*/
public static async removeContainer(containerId: string) {
const container = DockerService.docker.getContainer(containerId);
await container.remove();
}

/**
* Pauses a Docker container.
* @param containerId - The ID of the Docker container to pause.
* @returns A promise that resolves when the container is paused.
*/
public static async pauseContainer(containerId: string) {
const container = DockerService.docker.getContainer(containerId);
await container.pause();
}

/**
* Unpauses a Docker container.
* @param containerId - The ID of the Docker container to unpause.
* @returns A promise that resolves when the container is unpaused.
*/
public static async unpauseContainer(containerId: string) {
const container = DockerService.docker.getContainer(containerId);
await container.unpause();
}

/**
* Restarts a Docker container.
* @param containerId - The ID of the Docker container to restart.
* @returns A promise that resolves when the container is restarted.
*/
public static async restartContainer(containerId: string) {
const container = DockerService.docker.getContainer(containerId);
await container.restart();
await container.wait();
}

/**
* Creates a Docker container.
* @param imageName - The name of the Docker image to use.
* @param containerName - The name of the Docker container to create.
* @param containerConfig - The configuration for the Docker container.
* @returns A promise that resolves to the new Docker container.
* @throws An error if the container could not be created.
*/
public static async createContainer(imageName: string, containerName: string, containerConfig: any): Promise<Docker.Container> {
const container = await DockerService.docker.createContainer({
Image: imageName,
name: containerName,
...containerConfig,
});

return container;
}
}
Loading

0 comments on commit 6e23019

Please sign in to comment.