From 003902b84b3b31750f03b97b1d8f07e328fb2eeb Mon Sep 17 00:00:00 2001 From: Jake Turner Date: Fri, 16 Jan 2026 05:46:55 +0000 Subject: [PATCH] fix(Docker): improve container state management --- admin/app/services/docker_service.ts | 41 +++++++++------------------- admin/app/services/system_service.ts | 20 +++++++++----- admin/types/services.ts | 3 +- 3 files changed, 27 insertions(+), 37 deletions(-) diff --git a/admin/app/services/docker_service.ts b/admin/app/services/docker_service.ts index 7e8f581..80947d3 100644 --- a/admin/app/services/docker_service.ts +++ b/admin/app/services/docker_service.ts @@ -2,7 +2,6 @@ import Service from '#models/service' import Docker from 'dockerode' import logger from '@adonisjs/core/services/logger' import { inject } from '@adonisjs/core' -import { ServiceStatus } from '../../types/services.js' import transmit from '@adonisjs/transmit/services/main' import { doResumableDownloadWithRetry } from '../utils/downloads.js' import { join } from 'path' @@ -92,18 +91,16 @@ export class DockerService { } } + /** + * Fetches the status of all Docker containers related to Nomad services. (those prefixed with 'nomad_') + */ async getServicesStatus(): Promise< { service_name: string - status: ServiceStatus + status: string }[] > { try { - const services = await Service.query().where('installed', true) - if (!services || services.length === 0) { - return [] - } - const containers = await this.docker.listContainers({ all: true }) const containerMap = new Map() containers.forEach((container) => { @@ -113,22 +110,9 @@ export class DockerService { } }) - const getStatus = (state: string): ServiceStatus => { - switch (state) { - case 'running': - return 'running' - case 'exited': - case 'created': - case 'paused': - return 'stopped' - default: - return 'unknown' - } - } - return Array.from(containerMap.entries()).map(([name, container]) => ({ service_name: name, - status: getStatus(container.State), + status: container.State, })) } catch (error) { console.error(`Error fetching services status: ${error.message}`) @@ -189,13 +173,12 @@ export class DockerService { // } const containerConfig = this._parseContainerConfig(service.container_config) - + // Execute installation asynchronously and handle cleanup - this._createContainer(service, containerConfig) - .catch(async (error) => { - logger.error(`Installation failed for ${serviceName}: ${error.message}`) - await this._cleanupFailedInstallation(serviceName) - }) + this._createContainer(service, containerConfig).catch(async (error) => { + logger.error(`Installation failed for ${serviceName}: ${error.message}`) + await this._cleanupFailedInstallation(serviceName) + }) return { success: true, @@ -416,7 +399,9 @@ export class DockerService { this.activeInstallations.delete(serviceName) logger.info(`[DockerService] Cleaned up failed installation for ${serviceName}`) } catch (error) { - logger.error(`[DockerService] Failed to cleanup installation for ${serviceName}: ${error.message}`) + logger.error( + `[DockerService] Failed to cleanup installation for ${serviceName}: ${error.message}` + ) } } diff --git a/admin/app/services/system_service.ts b/admin/app/services/system_service.ts index acf5480..b20124b 100644 --- a/admin/app/services/system_service.ts +++ b/admin/app/services/system_service.ts @@ -223,8 +223,9 @@ export class SystemService { /** * Checks the current state of Docker containers against the database records and updates the database accordingly. - * It will mark services as not installed if their corresponding containers are not running, and can also handle cleanup of any orphaned records. - * Handles cases where a container might have been manually removed or is in an unexpected state, ensuring the database reflects the actual state of containers. + * It will mark services as not installed if their corresponding containers do not exist, regardless of their running state. + * Handles cases where a container might have been manually removed, ensuring the database reflects the actual existence of containers. + * Containers that exist but are stopped, paused, or restarting will still be considered installed. */ private async _syncContainersWithDatabase() { try { @@ -232,20 +233,25 @@ export class SystemService { const serviceStatusList = await this.dockerService.getServicesStatus() for (const service of allServices) { - const status = serviceStatusList.find((s) => s.service_name === service.service_name) + const containerExists = serviceStatusList.find( + (s) => s.service_name === service.service_name + ) + if (service.installed) { - if (!status || status.status !== 'running') { + // If marked as installed but container doesn't exist, mark as not installed + if (!containerExists) { logger.warn( - `Service ${service.service_name} is marked as installed but container is not running. Marking as not installed.` + `Service ${service.service_name} is marked as installed but container does not exist. Marking as not installed.` ) service.installed = false service.installation_status = 'idle' await service.save() } } else { - if (status && status.status === 'running') { + // If marked as not installed but container exists (any state), mark as installed + if (containerExists) { logger.warn( - `Service ${service.service_name} is marked as not installed but container is running. Marking as installed.` + `Service ${service.service_name} is marked as not installed but container exists. Marking as installed.` ) service.installed = true service.installation_status = 'idle' diff --git a/admin/types/services.ts b/admin/types/services.ts index 7b4f25d..a761ffa 100644 --- a/admin/types/services.ts +++ b/admin/types/services.ts @@ -1,6 +1,5 @@ import Service from '#models/service' -export type ServiceStatus = 'unknown' | 'running' | 'stopped' export type ServiceSlim = Pick< Service, | 'id' @@ -11,4 +10,4 @@ export type ServiceSlim = Pick< | 'friendly_name' | 'description' | 'icon' -> & { status?: ServiceStatus } +> & { status?: string }