2025-11-20 16:31:07 +00:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace App\Services;
|
|
|
|
|
|
|
|
|
|
use Illuminate\Support\Collection;
|
feat: add validation for YAML parsing, integer parameters, and Docker Compose custom fields
This commit adds comprehensive validation improvements and DRY principles for handling Coolify's custom Docker Compose extensions.
## Changes
### 1. Created Reusable stripCoolifyCustomFields() Function
- Added shared helper in bootstrap/helpers/docker.php
- Removes all Coolify custom fields (exclude_from_hc, content, isDirectory, is_directory)
- Handles both long syntax (arrays) and short syntax (strings) for volumes
- Well-documented with comprehensive docblock
- Follows DRY principle for consistent field stripping
### 2. Fixed Docker Compose Modal Validation
- Updated validateComposeFile() to use stripCoolifyCustomFields()
- Now removes ALL custom fields before Docker validation (previously only removed content)
- Fixes validation errors when using templates with custom fields (e.g., traccar.yaml)
- Users can now validate compose files with Coolify extensions in UI
### 3. Enhanced YAML Validation in CalculatesExcludedStatus
- Added proper exception handling with ParseException vs generic Exception
- Added structure validation (checks if parsed result and services are arrays)
- Comprehensive logging with context (error message, line number, snippet)
- Maintains safe fallback behavior (returns empty collection on error)
### 4. Added Integer Validation to ContainerStatusAggregator
- Validates maxRestartCount parameter in both aggregateFromStrings() and aggregateFromContainers()
- Corrects negative values to 0 with warning log
- Logs warnings for suspiciously high values (> 1000)
- Prevents logic errors in crash loop detection
### 5. Comprehensive Unit Tests
- tests/Unit/StripCoolifyCustomFieldsTest.php (NEW) - 9 tests, 43 assertions
- tests/Unit/ContainerStatusAggregatorTest.php - Added 6 tests for integer validation
- tests/Unit/ExcludeFromHealthCheckTest.php - Added 4 tests for YAML validation
- All tests passing with proper Log facade mocking
### 6. Documentation
- Added comprehensive Docker Compose extensions documentation to .ai/core/deployment-architecture.md
- Documents all custom fields: exclude_from_hc, content, isDirectory/is_directory
- Includes examples, use cases, implementation details, and test references
- Updated .ai/README.md with navigation links to new documentation
## Benefits
- Better UX: Users can validate compose files with custom fields
- Better Debugging: Comprehensive logging for errors
- Better Code Quality: DRY principle with reusable validation
- Better Reliability: Prevents logic errors from invalid parameters
- Better Maintainability: Easy to add new custom fields in future
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-20 17:34:49 +00:00
|
|
|
use Illuminate\Support\Facades\Log;
|
2025-11-20 16:31:07 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Container Status Aggregator Service
|
|
|
|
|
*
|
|
|
|
|
* Centralized service for aggregating container statuses into a single status string.
|
|
|
|
|
* Uses a priority-based state machine to determine the overall status from multiple containers.
|
|
|
|
|
*
|
|
|
|
|
* Output Format: Colon-separated (e.g., "running:healthy", "degraded:unhealthy")
|
|
|
|
|
* This format is used throughout the backend for consistency and machine-readability.
|
|
|
|
|
* UI components transform this to human-readable format (e.g., "Running (Healthy)").
|
|
|
|
|
*
|
|
|
|
|
* State Priority (highest to lowest):
|
2025-12-02 20:47:15 +00:00
|
|
|
* 1. Degraded (from sub-resources) → degraded:unhealthy
|
2025-12-03 07:23:35 +00:00
|
|
|
* 2. Restarting → degraded:unhealthy (or restarting:unknown if preserveRestarting=true)
|
2025-12-02 20:47:15 +00:00
|
|
|
* 3. Crash Loop (exited with restarts) → degraded:unhealthy
|
|
|
|
|
* 4. Mixed (running + exited) → degraded:unhealthy
|
|
|
|
|
* 5. Mixed (running + starting) → starting:unknown
|
|
|
|
|
* 6. Running → running:healthy/unhealthy/unknown
|
|
|
|
|
* 7. Dead/Removing → degraded:unhealthy
|
|
|
|
|
* 8. Paused → paused:unknown
|
|
|
|
|
* 9. Starting/Created → starting:unknown
|
|
|
|
|
* 10. Exited → exited
|
2025-12-03 07:23:35 +00:00
|
|
|
*
|
|
|
|
|
* The $preserveRestarting parameter controls whether "restarting" containers should be
|
|
|
|
|
* reported as "restarting:unknown" (true) or "degraded:unhealthy" (false, default).
|
|
|
|
|
* - Use preserveRestarting=true for individual sub-resources (ServiceApplication/ServiceDatabase)
|
|
|
|
|
* so they show "Restarting" in the UI.
|
|
|
|
|
* - Use preserveRestarting=false for overall Service status aggregation where any restarting
|
|
|
|
|
* container should mark the entire service as "Degraded".
|
2025-11-20 16:31:07 +00:00
|
|
|
*/
|
|
|
|
|
class ContainerStatusAggregator
|
|
|
|
|
{
|
|
|
|
|
/**
|
|
|
|
|
* Aggregate container statuses from status strings into a single status.
|
|
|
|
|
*
|
|
|
|
|
* @param Collection $containerStatuses Collection of status strings (e.g., "running (healthy)", "running:healthy")
|
|
|
|
|
* @param int $maxRestartCount Maximum restart count across containers (for crash loop detection)
|
2025-12-03 07:23:35 +00:00
|
|
|
* @param bool $preserveRestarting If true, "restarting" containers return "restarting:unknown" instead of "degraded:unhealthy"
|
2025-11-20 16:31:07 +00:00
|
|
|
* @return string Aggregated status in colon format (e.g., "running:healthy")
|
|
|
|
|
*/
|
2025-12-03 07:23:35 +00:00
|
|
|
public function aggregateFromStrings(Collection $containerStatuses, int $maxRestartCount = 0, bool $preserveRestarting = false): string
|
2025-11-20 16:31:07 +00:00
|
|
|
{
|
feat: add validation for YAML parsing, integer parameters, and Docker Compose custom fields
This commit adds comprehensive validation improvements and DRY principles for handling Coolify's custom Docker Compose extensions.
## Changes
### 1. Created Reusable stripCoolifyCustomFields() Function
- Added shared helper in bootstrap/helpers/docker.php
- Removes all Coolify custom fields (exclude_from_hc, content, isDirectory, is_directory)
- Handles both long syntax (arrays) and short syntax (strings) for volumes
- Well-documented with comprehensive docblock
- Follows DRY principle for consistent field stripping
### 2. Fixed Docker Compose Modal Validation
- Updated validateComposeFile() to use stripCoolifyCustomFields()
- Now removes ALL custom fields before Docker validation (previously only removed content)
- Fixes validation errors when using templates with custom fields (e.g., traccar.yaml)
- Users can now validate compose files with Coolify extensions in UI
### 3. Enhanced YAML Validation in CalculatesExcludedStatus
- Added proper exception handling with ParseException vs generic Exception
- Added structure validation (checks if parsed result and services are arrays)
- Comprehensive logging with context (error message, line number, snippet)
- Maintains safe fallback behavior (returns empty collection on error)
### 4. Added Integer Validation to ContainerStatusAggregator
- Validates maxRestartCount parameter in both aggregateFromStrings() and aggregateFromContainers()
- Corrects negative values to 0 with warning log
- Logs warnings for suspiciously high values (> 1000)
- Prevents logic errors in crash loop detection
### 5. Comprehensive Unit Tests
- tests/Unit/StripCoolifyCustomFieldsTest.php (NEW) - 9 tests, 43 assertions
- tests/Unit/ContainerStatusAggregatorTest.php - Added 6 tests for integer validation
- tests/Unit/ExcludeFromHealthCheckTest.php - Added 4 tests for YAML validation
- All tests passing with proper Log facade mocking
### 6. Documentation
- Added comprehensive Docker Compose extensions documentation to .ai/core/deployment-architecture.md
- Documents all custom fields: exclude_from_hc, content, isDirectory/is_directory
- Includes examples, use cases, implementation details, and test references
- Updated .ai/README.md with navigation links to new documentation
## Benefits
- Better UX: Users can validate compose files with custom fields
- Better Debugging: Comprehensive logging for errors
- Better Code Quality: DRY principle with reusable validation
- Better Reliability: Prevents logic errors from invalid parameters
- Better Maintainability: Easy to add new custom fields in future
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-20 17:34:49 +00:00
|
|
|
// Validate maxRestartCount parameter
|
|
|
|
|
if ($maxRestartCount < 0) {
|
|
|
|
|
Log::warning('Negative maxRestartCount corrected to 0', [
|
|
|
|
|
'original_value' => $maxRestartCount,
|
|
|
|
|
]);
|
|
|
|
|
$maxRestartCount = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($maxRestartCount > 1000) {
|
|
|
|
|
Log::warning('High maxRestartCount detected', [
|
|
|
|
|
'maxRestartCount' => $maxRestartCount,
|
|
|
|
|
'containers' => $containerStatuses->count(),
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-20 16:31:07 +00:00
|
|
|
if ($containerStatuses->isEmpty()) {
|
2025-11-24 08:09:08 +00:00
|
|
|
return 'exited';
|
2025-11-20 16:31:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Initialize state flags
|
|
|
|
|
$hasRunning = false;
|
|
|
|
|
$hasRestarting = false;
|
|
|
|
|
$hasUnhealthy = false;
|
|
|
|
|
$hasUnknown = false;
|
|
|
|
|
$hasExited = false;
|
|
|
|
|
$hasStarting = false;
|
|
|
|
|
$hasPaused = false;
|
|
|
|
|
$hasDead = false;
|
2025-12-02 20:47:15 +00:00
|
|
|
$hasDegraded = false;
|
2025-11-20 16:31:07 +00:00
|
|
|
|
|
|
|
|
// Parse each status string and set flags
|
|
|
|
|
foreach ($containerStatuses as $status) {
|
2025-12-02 20:47:15 +00:00
|
|
|
if (str($status)->contains('degraded')) {
|
|
|
|
|
$hasDegraded = true;
|
|
|
|
|
if (str($status)->contains('unhealthy')) {
|
|
|
|
|
$hasUnhealthy = true;
|
|
|
|
|
}
|
|
|
|
|
} elseif (str($status)->contains('restarting')) {
|
2025-11-20 16:31:07 +00:00
|
|
|
$hasRestarting = true;
|
|
|
|
|
} elseif (str($status)->contains('running')) {
|
|
|
|
|
$hasRunning = true;
|
|
|
|
|
if (str($status)->contains('unhealthy')) {
|
|
|
|
|
$hasUnhealthy = true;
|
|
|
|
|
}
|
|
|
|
|
if (str($status)->contains('unknown')) {
|
|
|
|
|
$hasUnknown = true;
|
|
|
|
|
}
|
|
|
|
|
} elseif (str($status)->contains('exited')) {
|
|
|
|
|
$hasExited = true;
|
|
|
|
|
} elseif (str($status)->contains('created') || str($status)->contains('starting')) {
|
|
|
|
|
$hasStarting = true;
|
|
|
|
|
} elseif (str($status)->contains('paused')) {
|
|
|
|
|
$hasPaused = true;
|
|
|
|
|
} elseif (str($status)->contains('dead') || str($status)->contains('removing')) {
|
|
|
|
|
$hasDead = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Priority-based status resolution
|
|
|
|
|
return $this->resolveStatus(
|
|
|
|
|
$hasRunning,
|
|
|
|
|
$hasRestarting,
|
|
|
|
|
$hasUnhealthy,
|
|
|
|
|
$hasUnknown,
|
|
|
|
|
$hasExited,
|
|
|
|
|
$hasStarting,
|
|
|
|
|
$hasPaused,
|
|
|
|
|
$hasDead,
|
2025-12-02 20:47:15 +00:00
|
|
|
$hasDegraded,
|
2025-12-03 07:23:35 +00:00
|
|
|
$maxRestartCount,
|
|
|
|
|
$preserveRestarting
|
2025-11-20 16:31:07 +00:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Aggregate container statuses from Docker container objects.
|
|
|
|
|
*
|
|
|
|
|
* @param Collection $containers Collection of Docker container objects with State property
|
|
|
|
|
* @param int $maxRestartCount Maximum restart count across containers (for crash loop detection)
|
2025-12-03 07:23:35 +00:00
|
|
|
* @param bool $preserveRestarting If true, "restarting" containers return "restarting:unknown" instead of "degraded:unhealthy"
|
2025-11-20 16:31:07 +00:00
|
|
|
* @return string Aggregated status in colon format (e.g., "running:healthy")
|
|
|
|
|
*/
|
2025-12-03 07:23:35 +00:00
|
|
|
public function aggregateFromContainers(Collection $containers, int $maxRestartCount = 0, bool $preserveRestarting = false): string
|
2025-11-20 16:31:07 +00:00
|
|
|
{
|
feat: add validation for YAML parsing, integer parameters, and Docker Compose custom fields
This commit adds comprehensive validation improvements and DRY principles for handling Coolify's custom Docker Compose extensions.
## Changes
### 1. Created Reusable stripCoolifyCustomFields() Function
- Added shared helper in bootstrap/helpers/docker.php
- Removes all Coolify custom fields (exclude_from_hc, content, isDirectory, is_directory)
- Handles both long syntax (arrays) and short syntax (strings) for volumes
- Well-documented with comprehensive docblock
- Follows DRY principle for consistent field stripping
### 2. Fixed Docker Compose Modal Validation
- Updated validateComposeFile() to use stripCoolifyCustomFields()
- Now removes ALL custom fields before Docker validation (previously only removed content)
- Fixes validation errors when using templates with custom fields (e.g., traccar.yaml)
- Users can now validate compose files with Coolify extensions in UI
### 3. Enhanced YAML Validation in CalculatesExcludedStatus
- Added proper exception handling with ParseException vs generic Exception
- Added structure validation (checks if parsed result and services are arrays)
- Comprehensive logging with context (error message, line number, snippet)
- Maintains safe fallback behavior (returns empty collection on error)
### 4. Added Integer Validation to ContainerStatusAggregator
- Validates maxRestartCount parameter in both aggregateFromStrings() and aggregateFromContainers()
- Corrects negative values to 0 with warning log
- Logs warnings for suspiciously high values (> 1000)
- Prevents logic errors in crash loop detection
### 5. Comprehensive Unit Tests
- tests/Unit/StripCoolifyCustomFieldsTest.php (NEW) - 9 tests, 43 assertions
- tests/Unit/ContainerStatusAggregatorTest.php - Added 6 tests for integer validation
- tests/Unit/ExcludeFromHealthCheckTest.php - Added 4 tests for YAML validation
- All tests passing with proper Log facade mocking
### 6. Documentation
- Added comprehensive Docker Compose extensions documentation to .ai/core/deployment-architecture.md
- Documents all custom fields: exclude_from_hc, content, isDirectory/is_directory
- Includes examples, use cases, implementation details, and test references
- Updated .ai/README.md with navigation links to new documentation
## Benefits
- Better UX: Users can validate compose files with custom fields
- Better Debugging: Comprehensive logging for errors
- Better Code Quality: DRY principle with reusable validation
- Better Reliability: Prevents logic errors from invalid parameters
- Better Maintainability: Easy to add new custom fields in future
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-20 17:34:49 +00:00
|
|
|
// Validate maxRestartCount parameter
|
|
|
|
|
if ($maxRestartCount < 0) {
|
|
|
|
|
Log::warning('Negative maxRestartCount corrected to 0', [
|
|
|
|
|
'original_value' => $maxRestartCount,
|
|
|
|
|
]);
|
|
|
|
|
$maxRestartCount = 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ($maxRestartCount > 1000) {
|
|
|
|
|
Log::warning('High maxRestartCount detected', [
|
|
|
|
|
'maxRestartCount' => $maxRestartCount,
|
|
|
|
|
'containers' => $containers->count(),
|
|
|
|
|
]);
|
|
|
|
|
}
|
|
|
|
|
|
2025-11-20 16:31:07 +00:00
|
|
|
if ($containers->isEmpty()) {
|
2025-11-24 08:09:08 +00:00
|
|
|
return 'exited';
|
2025-11-20 16:31:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Initialize state flags
|
|
|
|
|
$hasRunning = false;
|
|
|
|
|
$hasRestarting = false;
|
|
|
|
|
$hasUnhealthy = false;
|
|
|
|
|
$hasUnknown = false;
|
|
|
|
|
$hasExited = false;
|
|
|
|
|
$hasStarting = false;
|
|
|
|
|
$hasPaused = false;
|
|
|
|
|
$hasDead = false;
|
|
|
|
|
|
|
|
|
|
// Parse each container object and set flags
|
|
|
|
|
foreach ($containers as $container) {
|
|
|
|
|
$state = data_get($container, 'State.Status', 'exited');
|
|
|
|
|
$health = data_get($container, 'State.Health.Status');
|
|
|
|
|
|
|
|
|
|
if ($state === 'restarting') {
|
|
|
|
|
$hasRestarting = true;
|
|
|
|
|
} elseif ($state === 'running') {
|
|
|
|
|
$hasRunning = true;
|
|
|
|
|
if ($health === 'unhealthy') {
|
|
|
|
|
$hasUnhealthy = true;
|
|
|
|
|
} elseif (is_null($health) || $health === 'starting') {
|
|
|
|
|
$hasUnknown = true;
|
|
|
|
|
}
|
|
|
|
|
} elseif ($state === 'exited') {
|
|
|
|
|
$hasExited = true;
|
|
|
|
|
} elseif ($state === 'created' || $state === 'starting') {
|
|
|
|
|
$hasStarting = true;
|
|
|
|
|
} elseif ($state === 'paused') {
|
|
|
|
|
$hasPaused = true;
|
|
|
|
|
} elseif ($state === 'dead' || $state === 'removing') {
|
|
|
|
|
$hasDead = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Priority-based status resolution
|
|
|
|
|
return $this->resolveStatus(
|
|
|
|
|
$hasRunning,
|
|
|
|
|
$hasRestarting,
|
|
|
|
|
$hasUnhealthy,
|
|
|
|
|
$hasUnknown,
|
|
|
|
|
$hasExited,
|
|
|
|
|
$hasStarting,
|
|
|
|
|
$hasPaused,
|
|
|
|
|
$hasDead,
|
2025-12-02 20:47:15 +00:00
|
|
|
false, // $hasDegraded - not applicable for container objects, only for status strings
|
2025-12-03 07:23:35 +00:00
|
|
|
$maxRestartCount,
|
|
|
|
|
$preserveRestarting
|
2025-11-20 16:31:07 +00:00
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Resolve the aggregated status based on state flags (priority-based state machine).
|
|
|
|
|
*
|
|
|
|
|
* @param bool $hasRunning Has at least one running container
|
|
|
|
|
* @param bool $hasRestarting Has at least one restarting container
|
|
|
|
|
* @param bool $hasUnhealthy Has at least one unhealthy container
|
|
|
|
|
* @param bool $hasUnknown Has at least one container with unknown health
|
|
|
|
|
* @param bool $hasExited Has at least one exited container
|
|
|
|
|
* @param bool $hasStarting Has at least one starting/created container
|
|
|
|
|
* @param bool $hasPaused Has at least one paused container
|
|
|
|
|
* @param bool $hasDead Has at least one dead/removing container
|
2025-12-02 20:47:15 +00:00
|
|
|
* @param bool $hasDegraded Has at least one degraded container
|
2025-11-20 16:31:07 +00:00
|
|
|
* @param int $maxRestartCount Maximum restart count (for crash loop detection)
|
2025-12-03 07:23:35 +00:00
|
|
|
* @param bool $preserveRestarting If true, return "restarting:unknown" instead of "degraded:unhealthy" for restarting containers
|
2025-11-20 16:31:07 +00:00
|
|
|
* @return string Status in colon format (e.g., "running:healthy")
|
|
|
|
|
*/
|
|
|
|
|
private function resolveStatus(
|
|
|
|
|
bool $hasRunning,
|
|
|
|
|
bool $hasRestarting,
|
|
|
|
|
bool $hasUnhealthy,
|
|
|
|
|
bool $hasUnknown,
|
|
|
|
|
bool $hasExited,
|
|
|
|
|
bool $hasStarting,
|
|
|
|
|
bool $hasPaused,
|
|
|
|
|
bool $hasDead,
|
2025-12-02 20:47:15 +00:00
|
|
|
bool $hasDegraded,
|
2025-12-03 07:23:35 +00:00
|
|
|
int $maxRestartCount,
|
|
|
|
|
bool $preserveRestarting = false
|
2025-11-20 16:31:07 +00:00
|
|
|
): string {
|
2025-12-02 20:47:15 +00:00
|
|
|
// Priority 1: Degraded containers from sub-resources (highest priority)
|
|
|
|
|
// If any service/application within a service stack is degraded, the entire stack is degraded
|
|
|
|
|
if ($hasDegraded) {
|
|
|
|
|
return 'degraded:unhealthy';
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-03 07:23:35 +00:00
|
|
|
// Priority 2: Restarting containers
|
|
|
|
|
// When preserveRestarting is true (for individual sub-resources), keep as "restarting"
|
|
|
|
|
// When false (for overall service status), mark as "degraded"
|
2025-11-20 16:31:07 +00:00
|
|
|
if ($hasRestarting) {
|
2025-12-03 07:23:35 +00:00
|
|
|
return $preserveRestarting ? 'restarting:unknown' : 'degraded:unhealthy';
|
2025-11-20 16:31:07 +00:00
|
|
|
}
|
|
|
|
|
|
2025-12-02 20:47:15 +00:00
|
|
|
// Priority 3: Crash loop detection (exited with restart count > 0)
|
2025-11-20 16:31:07 +00:00
|
|
|
if ($hasExited && $maxRestartCount > 0) {
|
|
|
|
|
return 'degraded:unhealthy';
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-02 20:47:15 +00:00
|
|
|
// Priority 4: Mixed state (some running, some exited = degraded)
|
2025-11-20 16:31:07 +00:00
|
|
|
if ($hasRunning && $hasExited) {
|
|
|
|
|
return 'degraded:unhealthy';
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-02 20:47:15 +00:00
|
|
|
// Priority 5: Mixed state (some running, some starting = still starting)
|
|
|
|
|
// If any component is still starting, the entire service stack is not fully ready
|
|
|
|
|
if ($hasRunning && $hasStarting) {
|
|
|
|
|
return 'starting:unknown';
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Priority 6: Running containers (check health status)
|
2025-11-20 16:31:07 +00:00
|
|
|
if ($hasRunning) {
|
|
|
|
|
if ($hasUnhealthy) {
|
|
|
|
|
return 'running:unhealthy';
|
|
|
|
|
} elseif ($hasUnknown) {
|
|
|
|
|
return 'running:unknown';
|
|
|
|
|
} else {
|
|
|
|
|
return 'running:healthy';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-02 20:47:15 +00:00
|
|
|
// Priority 7: Dead or removing containers
|
2025-11-20 16:31:07 +00:00
|
|
|
if ($hasDead) {
|
|
|
|
|
return 'degraded:unhealthy';
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-02 20:47:15 +00:00
|
|
|
// Priority 8: Paused containers
|
2025-11-20 16:31:07 +00:00
|
|
|
if ($hasPaused) {
|
|
|
|
|
return 'paused:unknown';
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-02 20:47:15 +00:00
|
|
|
// Priority 9: Starting/created containers
|
2025-11-20 16:31:07 +00:00
|
|
|
if ($hasStarting) {
|
|
|
|
|
return 'starting:unknown';
|
|
|
|
|
}
|
|
|
|
|
|
2025-12-02 20:47:15 +00:00
|
|
|
// Priority 10: All containers exited (no restart count = truly stopped)
|
2025-11-24 08:09:08 +00:00
|
|
|
return 'exited';
|
2025-11-20 16:31:07 +00:00
|
|
|
}
|
|
|
|
|
}
|