From 5f728c06f7fff33c0d494a4a3151b979bbcdab1b Mon Sep 17 00:00:00 2001 From: Pujit Mehrotra Date: Wed, 20 Aug 2025 13:21:02 -0400 Subject: [PATCH] refactor: improve code organization --- .../resolvers/docker/container-status.job.ts | 8 ++-- .../docker/docker-container.resolver.ts | 7 +-- .../docker/docker-manifest.service.ts | 44 +++++++++---------- .../resolvers/docker/docker-php.service.ts | 33 +++++++------- 4 files changed, 43 insertions(+), 49 deletions(-) diff --git a/api/src/unraid-api/graph/resolvers/docker/container-status.job.ts b/api/src/unraid-api/graph/resolvers/docker/container-status.job.ts index e87e0dbb3..0903ed8cb 100644 --- a/api/src/unraid-api/graph/resolvers/docker/container-status.job.ts +++ b/api/src/unraid-api/graph/resolvers/docker/container-status.job.ts @@ -1,15 +1,15 @@ import { Injectable } from '@nestjs/common'; import { Cron, CronExpression, Timeout } from '@nestjs/schedule'; -import { DockerPhpService } from '@app/unraid-api/graph/resolvers/docker/docker-php.service.js'; +import { DockerManifestService } from '@app/unraid-api/graph/resolvers/docker/docker-manifest.service.js'; @Injectable() export class ContainerStatusJob { - constructor(private readonly dockerPhpService: DockerPhpService) {} + constructor(private readonly dockerManifestService: DockerManifestService) {} @Cron(CronExpression.EVERY_DAY_AT_6AM) async refreshContainerDigests() { - await this.dockerPhpService.refreshDigests(); + await this.dockerManifestService.refreshDigests(); } /** @@ -17,6 +17,6 @@ export class ContainerStatusJob { */ @Timeout(5_000) async refreshContainerDigestsAfterStartup() { - await this.dockerPhpService.refreshDigests(); + await this.dockerManifestService.refreshDigests(); } } diff --git a/api/src/unraid-api/graph/resolvers/docker/docker-container.resolver.ts b/api/src/unraid-api/graph/resolvers/docker/docker-container.resolver.ts index c0cb2fa03..2a4f054e5 100644 --- a/api/src/unraid-api/graph/resolvers/docker/docker-container.resolver.ts +++ b/api/src/unraid-api/graph/resolvers/docker/docker-container.resolver.ts @@ -25,10 +25,7 @@ import { OrganizerV1, ResolvedOrganizerV1 } from '@app/unraid-api/organizer/orga @Resolver(() => DockerContainer) export class DockerContainerResolver { private readonly logger = new Logger(DockerContainerResolver.name); - constructor( - private readonly dockerManifestService: DockerManifestService, - private readonly dockerPhpService: DockerPhpService - ) {} + constructor(private readonly dockerManifestService: DockerManifestService) {} @UsePermissions({ action: AuthActionVerb.READ, @@ -62,6 +59,6 @@ export class DockerContainerResolver { }) @Mutation(() => Boolean) public async refreshDockerDigests() { - return this.dockerPhpService.refreshDigests(); + return this.dockerManifestService.refreshDigests(); } } diff --git a/api/src/unraid-api/graph/resolvers/docker/docker-manifest.service.ts b/api/src/unraid-api/graph/resolvers/docker/docker-manifest.service.ts index edc33770a..87ffcf9c6 100644 --- a/api/src/unraid-api/graph/resolvers/docker/docker-manifest.service.ts +++ b/api/src/unraid-api/graph/resolvers/docker/docker-manifest.service.ts @@ -1,38 +1,36 @@ -import { Injectable, Logger } from '@nestjs/common'; -import { readFile } from 'fs/promises'; - -import { ExtendOptions, Got, got as gotClient, OptionsOfTextResponseBody } from 'got'; +import { Injectable } from '@nestjs/common'; import { docker } from '@app/core/utils/index.js'; - -/** Accept header for Docker API manifest listing */ -const ACCEPT_MANIFEST = - 'application/vnd.docker.distribution.manifest.list.v2+json,application/vnd.docker.distribution.manifest.v2+json,application/vnd.oci.image.index.v1+json'; - -export type CachedStatusEntry = { - /** sha256 digest - "sha256:..." */ - local: string; - /** sha256 digest - "sha256:..." */ - remote: string; - /** whether update is available (true), not available (false), or unknown (null) */ - status: 'true' | 'false' | null; -}; +import { + CachedStatusEntry, + DockerPhpService, +} from '@app/unraid-api/graph/resolvers/docker/docker-php.service.js'; @Injectable() export class DockerManifestService { - constructor() {} + constructor(private readonly dockerPhpService: DockerPhpService) {} - async readCachedUpdateStatus(cacheFile = '/var/lib/docker/unraid-update-status.json') { - const cache = await readFile(cacheFile, 'utf8'); - const cacheData = JSON.parse(cache); - return cacheData as Record; + private readonly refreshDigestsMutex = new AsyncMutex(() => { + return this.dockerPhpService.refreshDigestsViaPhp(); + }); + + /** + * Recomputes local/remote docker container digests and writes them to /var/lib/docker/unraid-update-status.json + * @param mutex - Optional mutex to use for the operation. If not provided, a default mutex will be used. + * @param dockerUpdatePath - Optional path to the DockerUpdate.php file. If not provided, the default path will be used. + * @returns True if the digests were refreshed, false if the operation failed + */ + async refreshDigests(mutex = this.refreshDigestsMutex, dockerUpdatePath?: string) { + return mutex.do(() => { + return this.dockerPhpService.refreshDigestsViaPhp(dockerUpdatePath); + }); } async isUpdateAvailableCached(imageRef: string, cacheData?: Record) { let taggedRef = imageRef; if (!taggedRef.includes(':')) taggedRef += ':latest'; - cacheData ??= await this.readCachedUpdateStatus(); + cacheData ??= await this.dockerPhpService.readCachedUpdateStatus(); const containerData = cacheData[taggedRef]; if (!containerData) return null; return containerData.status?.toLowerCase() === 'true'; diff --git a/api/src/unraid-api/graph/resolvers/docker/docker-php.service.ts b/api/src/unraid-api/graph/resolvers/docker/docker-php.service.ts index 558658af1..1089d8c60 100644 --- a/api/src/unraid-api/graph/resolvers/docker/docker-php.service.ts +++ b/api/src/unraid-api/graph/resolvers/docker/docker-php.service.ts @@ -1,4 +1,5 @@ import { Injectable } from '@nestjs/common'; +import { readFile } from 'fs/promises'; import { AsyncMutex } from '@unraid/shared/util/processing.js'; @@ -9,37 +10,35 @@ type ExplicitStatusItem = { name: string; updateStatus: 'up to date' | 'update available' | 'rebuild ready' | 'unknown'; }; +export type CachedStatusEntry = { + /** sha256 digest - "sha256:..." */ + local: string; + /** sha256 digest - "sha256:..." */ + remote: string; + /** whether update is available (true), not available (false), or unknown (null) */ + status: 'true' | 'false' | null; +}; @Injectable() export class DockerPhpService { constructor() {} + async readCachedUpdateStatus(cacheFile = '/var/lib/docker/unraid-update-status.json') { + const cache = await readFile(cacheFile, 'utf8'); + const cacheData = JSON.parse(cache); + return cacheData as Record; + } + /**---------------------- * Refresh Container Digests *------------------------**/ - private readonly refreshDigestsMutex = new AsyncMutex(() => { - return this.refreshDigestsViaPhp(); - }); - - /** - * Recomputes local/remote docker container digests and writes them to /var/lib/docker/unraid-update-status.json - * @param mutex - Optional mutex to use for the operation. If not provided, a default mutex will be used. - * @param dockerUpdatePath - Optional path to the DockerUpdate.php file. If not provided, the default path will be used. - * @returns True if the digests were refreshed, false if the operation failed - */ - async refreshDigests(mutex = this.refreshDigestsMutex, dockerUpdatePath?: string) { - return mutex.do(() => { - return this.refreshDigestsViaPhp(dockerUpdatePath); - }); - } - /** * Recomputes local/remote digests by triggering `DockerTemplates->getAllInfo(true)` via DockerUpdate.php * @param dockerUpdatePath - Path to the DockerUpdate.php file * @returns True if the digests were refreshed, false if the file is not found or the operation failed */ - private async refreshDigestsViaPhp( + async refreshDigestsViaPhp( dockerUpdatePath = '/usr/local/emhttp/plugins/dynamix.docker.manager/include/DockerUpdate.php' ) { try {