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 b14fe8606..01929893a 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 @@ -41,7 +41,22 @@ export class DockerManifestService { cacheData ??= await this.dockerPhpService.readCachedUpdateStatus(); const containerData = cacheData[taggedRef]; if (!containerData) return null; - return containerData.status?.toLowerCase() === 'true'; + + const normalize = (digest?: string | null) => { + const value = digest?.trim().toLowerCase(); + return value && value !== 'undef' ? value : null; + }; + + const localDigest = normalize(containerData.local); + const remoteDigest = normalize(containerData.remote); + if (localDigest && remoteDigest) { + return localDigest !== remoteDigest; + } + + const status = containerData.status?.toLowerCase(); + if (status === 'true') return true; + if (status === 'false') return false; + return null; } /** diff --git a/api/src/unraid-api/graph/resolvers/docker/docker.service.spec.ts b/api/src/unraid-api/graph/resolvers/docker/docker.service.spec.ts index af905205f..a688a6ac6 100644 --- a/api/src/unraid-api/graph/resolvers/docker/docker.service.spec.ts +++ b/api/src/unraid-api/graph/resolvers/docker/docker.service.spec.ts @@ -8,6 +8,7 @@ import { beforeEach, describe, expect, it, vi } from 'vitest'; // Import the mocked pubsub parts import { pubsub, PUBSUB_CHANNEL } from '@app/core/pubsub.js'; import { DockerConfigService } from '@app/unraid-api/graph/resolvers/docker/docker-config.service.js'; +import { DockerManifestService } from '@app/unraid-api/graph/resolvers/docker/docker-manifest.service.js'; import { DockerTemplateScannerService } from '@app/unraid-api/graph/resolvers/docker/docker-template-scanner.service.js'; import { ContainerState, DockerContainer } from '@app/unraid-api/graph/resolvers/docker/docker.model.js'; import { DockerService } from '@app/unraid-api/graph/resolvers/docker/docker.service.js'; @@ -27,30 +28,40 @@ interface DockerError extends NodeJS.ErrnoException { address: string; } -const mockContainer = { - start: vi.fn(), - stop: vi.fn(), -}; +const { mockDockerInstance, mockListContainers, mockGetContainer, mockListNetworks, mockContainer } = + vi.hoisted(() => { + const mockContainer = { + start: vi.fn(), + stop: vi.fn(), + }; -// Create properly typed mock functions -const mockListContainers = vi.fn(); -const mockGetContainer = vi.fn().mockReturnValue(mockContainer); -const mockListNetworks = vi.fn(); + const mockListContainers = vi.fn(); + const mockGetContainer = vi.fn().mockReturnValue(mockContainer); + const mockListNetworks = vi.fn(); -const mockDockerInstance = { - getContainer: mockGetContainer, - listContainers: mockListContainers, - listNetworks: mockListNetworks, - modem: { - Promise: Promise, - protocol: 'http', - socketPath: '/var/run/docker.sock', - headers: {}, - sshOptions: { - agentForward: undefined, - }, - }, -} as unknown as Docker; + const mockDockerInstance = { + getContainer: mockGetContainer, + listContainers: mockListContainers, + listNetworks: mockListNetworks, + modem: { + Promise: Promise, + protocol: 'http', + socketPath: '/var/run/docker.sock', + headers: {}, + sshOptions: { + agentForward: undefined, + }, + }, + } as unknown as Docker; + + return { + mockDockerInstance, + mockListContainers, + mockGetContainer, + mockListNetworks, + mockContainer, + }; + }); vi.mock('dockerode', () => { return { @@ -113,6 +124,10 @@ const mockDockerTemplateScannerService = { syncMissingContainers: vi.fn().mockResolvedValue(false), }; +const mockDockerManifestService = { + refreshDigests: vi.fn().mockResolvedValue(true), +}; + // Mock NotificationsService const mockNotificationsService = { notifyIfUnique: vi.fn().mockResolvedValue(null), @@ -141,6 +156,8 @@ describe('DockerService', () => { }); mockDockerTemplateScannerService.bootstrapScan.mockResolvedValue(undefined); mockDockerTemplateScannerService.syncMissingContainers.mockResolvedValue(false); + mockDockerManifestService.refreshDigests.mockReset(); + mockDockerManifestService.refreshDigests.mockResolvedValue(true); const module: TestingModule = await Test.createTestingModule({ providers: [ @@ -157,6 +174,10 @@ describe('DockerService', () => { provide: DockerTemplateScannerService, useValue: mockDockerTemplateScannerService, }, + { + provide: DockerManifestService, + useValue: mockDockerManifestService, + }, { provide: NotificationsService, useValue: mockNotificationsService, diff --git a/api/src/unraid-api/graph/resolvers/docker/docker.service.ts b/api/src/unraid-api/graph/resolvers/docker/docker.service.ts index 597d66ab5..4217cabe8 100644 --- a/api/src/unraid-api/graph/resolvers/docker/docker.service.ts +++ b/api/src/unraid-api/graph/resolvers/docker/docker.service.ts @@ -12,6 +12,7 @@ import { catchHandlers } from '@app/core/utils/misc/catch-handlers.js'; import { sleep } from '@app/core/utils/misc/sleep.js'; import { getters } from '@app/store/index.js'; import { DockerConfigService } from '@app/unraid-api/graph/resolvers/docker/docker-config.service.js'; +import { DockerManifestService } from '@app/unraid-api/graph/resolvers/docker/docker-manifest.service.js'; import { ContainerPortType, ContainerState, @@ -50,7 +51,8 @@ export class DockerService { constructor( @Inject(CACHE_MANAGER) private cacheManager: Cache, private readonly dockerConfigService: DockerConfigService, - private readonly notificationsService: NotificationsService + private readonly notificationsService: NotificationsService, + private readonly dockerManifestService: DockerManifestService ) { this.client = this.getDockerClient(); } @@ -458,7 +460,9 @@ export class DockerService { this.logger.debug(`Invalidated container caches after updating ${id}`); const updatedContainers = await this.getContainers({ skipCache: true }); - const updatedContainer = updatedContainers.find((c) => c.id === id); + const updatedContainer = updatedContainers.find( + (c) => c.names?.some((name) => name.replace(/^\//, '') === containerName) || c.id === id + ); if (!updatedContainer) { throw new Error(`Container ${id} not found after update`); }