612 lines
23 KiB
Markdown
612 lines
23 KiB
Markdown
# Coolify Application Architecture
|
|
|
|
## Laravel Project Structure
|
|
|
|
### **Core Application Directory** ([app/](mdc:app))
|
|
|
|
```
|
|
app/
|
|
├── Actions/ # Business logic actions (Action pattern)
|
|
├── Console/ # Artisan commands
|
|
├── Contracts/ # Interface definitions
|
|
├── Data/ # Data Transfer Objects (Spatie Laravel Data)
|
|
├── Enums/ # Enumeration classes
|
|
├── Events/ # Event classes
|
|
├── Exceptions/ # Custom exception classes
|
|
├── Helpers/ # Utility helper classes
|
|
├── Http/ # HTTP layer (Controllers, Middleware, Requests)
|
|
├── Jobs/ # Background job classes
|
|
├── Listeners/ # Event listeners
|
|
├── Livewire/ # Livewire components (Frontend)
|
|
├── Models/ # Eloquent models (Domain entities)
|
|
├── Notifications/ # Notification classes
|
|
├── Policies/ # Authorization policies
|
|
├── Providers/ # Service providers
|
|
├── Repositories/ # Repository pattern implementations
|
|
├── Services/ # Service layer classes
|
|
├── Traits/ # Reusable trait classes
|
|
└── View/ # View composers and creators
|
|
```
|
|
|
|
## Core Domain Models
|
|
|
|
### **Infrastructure Management**
|
|
|
|
#### **[Server.php](mdc:app/Models/Server.php)** (46KB, 1343 lines)
|
|
- **Purpose**: Physical/virtual server management
|
|
- **Key Relationships**:
|
|
- `hasMany(Application::class)` - Deployed applications
|
|
- `hasMany(StandalonePostgresql::class)` - Database instances
|
|
- `belongsTo(Team::class)` - Team ownership
|
|
- **Key Features**:
|
|
- SSH connection management
|
|
- Resource monitoring
|
|
- Proxy configuration (Traefik/Caddy)
|
|
- Docker daemon interaction
|
|
|
|
#### **[Application.php](mdc:app/Models/Application.php)** (74KB, 1734 lines)
|
|
- **Purpose**: Application deployment and management
|
|
- **Key Relationships**:
|
|
- `belongsTo(Server::class)` - Deployment target
|
|
- `belongsTo(Environment::class)` - Environment context
|
|
- `hasMany(ApplicationDeploymentQueue::class)` - Deployment history
|
|
- **Key Features**:
|
|
- Git repository integration
|
|
- Docker build and deployment
|
|
- Environment variable management
|
|
- SSL certificate handling
|
|
|
|
#### **[Service.php](mdc:app/Models/Service.php)** (58KB, 1325 lines)
|
|
- **Purpose**: Multi-container service orchestration
|
|
- **Key Relationships**:
|
|
- `hasMany(ServiceApplication::class)` - Service components
|
|
- `hasMany(ServiceDatabase::class)` - Service databases
|
|
- `belongsTo(Environment::class)` - Environment context
|
|
- **Key Features**:
|
|
- Docker Compose generation
|
|
- Service dependency management
|
|
- Health check configuration
|
|
|
|
### **Team & Project Organization**
|
|
|
|
#### **[Team.php](mdc:app/Models/Team.php)** (8.9KB, 308 lines)
|
|
- **Purpose**: Multi-tenant team management
|
|
- **Key Relationships**:
|
|
- `hasMany(User::class)` - Team members
|
|
- `hasMany(Project::class)` - Team projects
|
|
- `hasMany(Server::class)` - Team servers
|
|
- **Key Features**:
|
|
- Resource limits and quotas
|
|
- Team-based access control
|
|
- Subscription management
|
|
|
|
#### **[Project.php](mdc:app/Models/Project.php)** (4.3KB, 156 lines)
|
|
- **Purpose**: Project organization and grouping
|
|
- **Key Relationships**:
|
|
- `hasMany(Environment::class)` - Project environments
|
|
- `belongsTo(Team::class)` - Team ownership
|
|
- **Key Features**:
|
|
- Environment isolation
|
|
- Resource organization
|
|
|
|
#### **[Environment.php](mdc:app/Models/Environment.php)**
|
|
- **Purpose**: Environment-specific configuration
|
|
- **Key Relationships**:
|
|
- `hasMany(Application::class)` - Environment applications
|
|
- `hasMany(Service::class)` - Environment services
|
|
- `belongsTo(Project::class)` - Project context
|
|
|
|
### **Database Management Models**
|
|
|
|
#### **Standalone Database Models**
|
|
- **[StandalonePostgresql.php](mdc:app/Models/StandalonePostgresql.php)** (11KB, 351 lines)
|
|
- **[StandaloneMysql.php](mdc:app/Models/StandaloneMysql.php)** (11KB, 351 lines)
|
|
- **[StandaloneMariadb.php](mdc:app/Models/StandaloneMariadb.php)** (10KB, 337 lines)
|
|
- **[StandaloneMongodb.php](mdc:app/Models/StandaloneMongodb.php)** (12KB, 370 lines)
|
|
- **[StandaloneRedis.php](mdc:app/Models/StandaloneRedis.php)** (12KB, 394 lines)
|
|
- **[StandaloneKeydb.php](mdc:app/Models/StandaloneKeydb.php)** (11KB, 347 lines)
|
|
- **[StandaloneDragonfly.php](mdc:app/Models/StandaloneDragonfly.php)** (11KB, 347 lines)
|
|
- **[StandaloneClickhouse.php](mdc:app/Models/StandaloneClickhouse.php)** (10KB, 336 lines)
|
|
|
|
**Common Features**:
|
|
- Database configuration management
|
|
- Backup scheduling and execution
|
|
- Connection string generation
|
|
- Health monitoring
|
|
|
|
### **Configuration & Settings**
|
|
|
|
#### **[EnvironmentVariable.php](mdc:app/Models/EnvironmentVariable.php)** (7.6KB, 219 lines)
|
|
- **Purpose**: Application environment variable management
|
|
- **Key Features**:
|
|
- Encrypted value storage
|
|
- Build-time vs runtime variables
|
|
- Shared variable inheritance
|
|
|
|
#### **[InstanceSettings.php](mdc:app/Models/InstanceSettings.php)** (3.2KB, 124 lines)
|
|
- **Purpose**: Global Coolify instance configuration
|
|
- **Key Features**:
|
|
- FQDN and port configuration
|
|
- Auto-update settings
|
|
- Security configurations
|
|
|
|
## Architectural Patterns
|
|
|
|
### **Action Pattern** ([app/Actions/](mdc:app/Actions))
|
|
|
|
Using [lorisleiva/laravel-actions](mdc:composer.json) for business logic encapsulation:
|
|
|
|
```php
|
|
// Example Action structure
|
|
class DeployApplication extends Action
|
|
{
|
|
public function handle(Application $application): void
|
|
{
|
|
// Business logic for deployment
|
|
}
|
|
|
|
public function asJob(Application $application): void
|
|
{
|
|
// Queue job implementation
|
|
}
|
|
}
|
|
```
|
|
|
|
**Key Action Categories**:
|
|
- **Application/**: Deployment and management actions
|
|
- **Database/**: Database operations
|
|
- **Server/**: Server management actions
|
|
- **Service/**: Service orchestration actions
|
|
|
|
### **Repository Pattern** ([app/Repositories/](mdc:app/Repositories))
|
|
|
|
Data access abstraction layer:
|
|
- Encapsulates database queries
|
|
- Provides testable data layer
|
|
- Abstracts complex query logic
|
|
|
|
### **Service Layer** ([app/Services/](mdc:app/Services))
|
|
|
|
Business logic services:
|
|
- External API integrations
|
|
- Complex business operations
|
|
- Cross-cutting concerns
|
|
|
|
## Data Flow Architecture
|
|
|
|
### **Request Lifecycle**
|
|
|
|
1. **HTTP Request** → [routes/web.php](mdc:routes/web.php)
|
|
2. **Middleware** → Authentication, authorization
|
|
3. **Livewire Component** → [app/Livewire/](mdc:app/Livewire)
|
|
4. **Action/Service** → Business logic execution
|
|
5. **Model/Repository** → Data persistence
|
|
6. **Response** → Livewire reactive update
|
|
|
|
### **Background Processing**
|
|
|
|
1. **Job Dispatch** → Queue system (Redis)
|
|
2. **Job Processing** → [app/Jobs/](mdc:app/Jobs)
|
|
3. **Action Execution** → Business logic
|
|
4. **Event Broadcasting** → Real-time updates
|
|
5. **Notification** → User feedback
|
|
|
|
## Security Architecture
|
|
|
|
### **Multi-Tenant Isolation**
|
|
|
|
```php
|
|
// Team-based query scoping
|
|
class Application extends Model
|
|
{
|
|
public function scopeOwnedByCurrentTeam($query)
|
|
{
|
|
return $query->whereHas('environment.project.team', function ($q) {
|
|
$q->where('id', currentTeam()->id);
|
|
});
|
|
}
|
|
}
|
|
```
|
|
|
|
### **Authorization Layers**
|
|
|
|
1. **Team Membership** → User belongs to team
|
|
2. **Resource Ownership** → Resource belongs to team
|
|
3. **Policy Authorization** → [app/Policies/](mdc:app/Policies)
|
|
4. **Environment Isolation** → Project/environment boundaries
|
|
|
|
### **Data Protection**
|
|
|
|
- **Environment Variables**: Encrypted at rest
|
|
- **SSH Keys**: Secure storage and transmission
|
|
- **API Tokens**: Sanctum-based authentication
|
|
- **Audit Logging**: [spatie/laravel-activitylog](mdc:composer.json)
|
|
|
|
## Configuration Hierarchy
|
|
|
|
### **Global Configuration**
|
|
- **[InstanceSettings](mdc:app/Models/InstanceSettings.php)**: System-wide settings
|
|
- **[config/](mdc:config)**: Laravel configuration files
|
|
|
|
### **Team Configuration**
|
|
- **[Team](mdc:app/Models/Team.php)**: Team-specific settings
|
|
- **[ServerSetting](mdc:app/Models/ServerSetting.php)**: Server configurations
|
|
|
|
### **Project Configuration**
|
|
- **[ProjectSetting](mdc:app/Models/ProjectSetting.php)**: Project settings
|
|
- **[Environment](mdc:app/Models/Environment.php)**: Environment variables
|
|
|
|
### **Application Configuration**
|
|
- **[ApplicationSetting](mdc:app/Models/ApplicationSetting.php)**: App-specific settings
|
|
- **[EnvironmentVariable](mdc:app/Models/EnvironmentVariable.php)**: Runtime configuration
|
|
|
|
## Event-Driven Architecture
|
|
|
|
### **Event Broadcasting** ([app/Events/](mdc:app/Events))
|
|
|
|
Real-time updates using Laravel Echo and WebSockets:
|
|
|
|
```php
|
|
// Example event structure
|
|
class ApplicationDeploymentStarted implements ShouldBroadcast
|
|
{
|
|
public function broadcastOn(): array
|
|
{
|
|
return [
|
|
new PrivateChannel("team.{$this->application->team->id}"),
|
|
];
|
|
}
|
|
}
|
|
```
|
|
|
|
### **Event Listeners** ([app/Listeners/](mdc:app/Listeners))
|
|
|
|
- Deployment status updates
|
|
- Resource monitoring alerts
|
|
- Notification dispatching
|
|
- Audit log creation
|
|
|
|
## Database Design Patterns
|
|
|
|
### **Polymorphic Relationships**
|
|
|
|
```php
|
|
// Environment variables can belong to multiple resource types
|
|
class EnvironmentVariable extends Model
|
|
{
|
|
public function resource(): MorphTo
|
|
{
|
|
return $this->morphTo();
|
|
}
|
|
}
|
|
```
|
|
|
|
### **Team-Based Soft Scoping**
|
|
|
|
All major resources include team-based query scoping with request-level caching:
|
|
|
|
```php
|
|
// ✅ CORRECT - Use cached methods (request-level cache via once())
|
|
$applications = Application::ownedByCurrentTeamCached();
|
|
$servers = Server::ownedByCurrentTeamCached();
|
|
|
|
// ✅ CORRECT - Filter cached collection in memory
|
|
$activeServers = Server::ownedByCurrentTeamCached()->where('is_active', true);
|
|
|
|
// Only use query builder when you need eager loading or fresh data
|
|
$projects = Project::ownedByCurrentTeam()->with('environments')->get();
|
|
```
|
|
|
|
See [Database Patterns](.ai/patterns/database-patterns.md#request-level-caching-with-ownedbycurrentteamcached) for full documentation.
|
|
|
|
### **Configuration Inheritance**
|
|
|
|
Environment variables cascade from:
|
|
1. **Shared Variables** → Team-wide defaults
|
|
2. **Project Variables** → Project-specific overrides
|
|
3. **Application Variables** → Application-specific values
|
|
|
|
## Integration Patterns
|
|
|
|
### **Git Provider Integration**
|
|
|
|
Abstracted git operations supporting:
|
|
- **GitHub**: [app/Models/GithubApp.php](mdc:app/Models/GithubApp.php)
|
|
- **GitLab**: [app/Models/GitlabApp.php](mdc:app/Models/GitlabApp.php)
|
|
- **Bitbucket**: Webhook integration
|
|
- **Gitea**: Self-hosted Git support
|
|
|
|
### **Docker Integration**
|
|
|
|
- **Container Management**: Direct Docker API communication
|
|
- **Image Building**: Dockerfile and Buildpack support
|
|
- **Network Management**: Custom Docker networks
|
|
- **Volume Management**: Persistent storage handling
|
|
|
|
### **SSH Communication**
|
|
|
|
- **[phpseclib/phpseclib](mdc:composer.json)**: Secure SSH connections
|
|
- **Multiplexing**: Connection pooling for efficiency
|
|
- **Key Management**: [PrivateKey](mdc:app/Models/PrivateKey.php) model
|
|
|
|
## Testing Architecture
|
|
|
|
### **Test Structure** ([tests/](mdc:tests))
|
|
|
|
```
|
|
tests/
|
|
├── Feature/ # Integration tests
|
|
├── Unit/ # Unit tests
|
|
├── Browser/ # Dusk browser tests
|
|
├── Traits/ # Test helper traits
|
|
├── Pest.php # Pest configuration
|
|
└── TestCase.php # Base test case
|
|
```
|
|
|
|
### **Testing Patterns**
|
|
|
|
- **Feature Tests**: Full request lifecycle testing
|
|
- **Unit Tests**: Individual class/method testing
|
|
- **Browser Tests**: End-to-end user workflows
|
|
- **Database Testing**: Factories and seeders
|
|
|
|
## Performance Considerations
|
|
|
|
### **Query Optimization**
|
|
|
|
- **Eager Loading**: Prevent N+1 queries
|
|
- **Query Scoping**: Team-based filtering
|
|
- **Database Indexing**: Optimized for common queries
|
|
|
|
### **Caching Strategy**
|
|
|
|
- **Redis**: Session and cache storage
|
|
- **Model Caching**: Frequently accessed data
|
|
- **Query Caching**: Expensive query results
|
|
|
|
### **Background Processing**
|
|
|
|
- **Queue Workers**: Horizon-managed job processing
|
|
- **Job Batching**: Related job grouping
|
|
- **Failed Job Handling**: Automatic retry logic
|
|
|
|
## Container Status Monitoring System
|
|
|
|
### **Overview**
|
|
|
|
Container health status is monitored and updated through **multiple independent paths**. When modifying status logic, **ALL paths must be updated** to ensure consistency.
|
|
|
|
### **Critical Implementation Locations**
|
|
|
|
#### **1. SSH-Based Status Updates (Scheduled)**
|
|
**File**: [app/Actions/Docker/GetContainersStatus.php](mdc:app/Actions/Docker/GetContainersStatus.php)
|
|
**Method**: `aggregateApplicationStatus()` (lines 487-540)
|
|
**Trigger**: Scheduled job or manual refresh
|
|
**Frequency**: Every minute (via `ServerCheckJob`)
|
|
|
|
**Status Aggregation Logic**:
|
|
```php
|
|
// Tracks multiple status flags
|
|
$hasRunning = false;
|
|
$hasRestarting = false;
|
|
$hasUnhealthy = false;
|
|
$hasUnknown = false; // ⚠️ CRITICAL: Must track unknown
|
|
$hasExited = false;
|
|
// ... more states
|
|
|
|
// Priority: restarting > degraded > running (unhealthy > unknown > healthy)
|
|
if ($hasRunning) {
|
|
if ($hasUnhealthy) return 'running (unhealthy)';
|
|
elseif ($hasUnknown) return 'running (unknown)';
|
|
else return 'running (healthy)';
|
|
}
|
|
```
|
|
|
|
#### **2. Sentinel-Based Status Updates (Real-time)**
|
|
**File**: [app/Jobs/PushServerUpdateJob.php](mdc:app/Jobs/PushServerUpdateJob.php)
|
|
**Method**: `aggregateMultiContainerStatuses()` (lines 269-298)
|
|
**Trigger**: Sentinel push updates from remote servers
|
|
**Frequency**: Every ~30 seconds (real-time)
|
|
|
|
**Status Aggregation Logic**:
|
|
```php
|
|
// ⚠️ MUST match GetContainersStatus logic
|
|
$hasRunning = false;
|
|
$hasUnhealthy = false;
|
|
$hasUnknown = false; // ⚠️ CRITICAL: Added to fix bug
|
|
|
|
foreach ($relevantStatuses as $status) {
|
|
if (str($status)->contains('running')) {
|
|
$hasRunning = true;
|
|
if (str($status)->contains('unhealthy')) $hasUnhealthy = true;
|
|
if (str($status)->contains('unknown')) $hasUnknown = true; // ⚠️ CRITICAL
|
|
}
|
|
}
|
|
|
|
// Priority: unhealthy > unknown > healthy
|
|
if ($hasRunning) {
|
|
if ($hasUnhealthy) $aggregatedStatus = 'running (unhealthy)';
|
|
elseif ($hasUnknown) $aggregatedStatus = 'running (unknown)';
|
|
else $aggregatedStatus = 'running (healthy)';
|
|
}
|
|
```
|
|
|
|
#### **3. Multi-Server Status Aggregation**
|
|
**File**: [app/Actions/Shared/ComplexStatusCheck.php](mdc:app/Actions/Shared/ComplexStatusCheck.php)
|
|
**Method**: `resource()` (lines 48-210)
|
|
**Purpose**: Aggregates status across multiple servers for applications
|
|
**Used by**: Applications with multiple destinations
|
|
|
|
**Key Features**:
|
|
- Aggregates statuses from main + additional servers
|
|
- Handles excluded containers (`:excluded` suffix)
|
|
- Calculates overall application health from all containers
|
|
|
|
**Status Format with Excluded Containers**:
|
|
```php
|
|
// When all containers excluded from health checks:
|
|
return 'running:unhealthy:excluded'; // Container running but unhealthy, monitoring disabled
|
|
return 'running:unknown:excluded'; // Container running, health unknown, monitoring disabled
|
|
return 'running:healthy:excluded'; // Container running and healthy, monitoring disabled
|
|
return 'degraded:excluded'; // Some containers down, monitoring disabled
|
|
return 'exited:excluded'; // All containers stopped, monitoring disabled
|
|
```
|
|
|
|
#### **4. Service-Level Status Aggregation**
|
|
**File**: [app/Models/Service.php](mdc:app/Models/Service.php)
|
|
**Method**: `complexStatus()` (lines 176-288)
|
|
**Purpose**: Aggregates status for multi-container services
|
|
**Used by**: Docker Compose services
|
|
|
|
**Status Calculation**:
|
|
```php
|
|
// Aggregates status from all service applications and databases
|
|
// Handles excluded containers separately
|
|
// Returns status with :excluded suffix when all containers excluded
|
|
if (!$hasNonExcluded && $complexStatus === null && $complexHealth === null) {
|
|
// All services excluded - calculate from excluded containers
|
|
return "{$excludedStatus}:excluded";
|
|
}
|
|
```
|
|
|
|
### **Status Flow Diagram**
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ Container Status Sources │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
│
|
|
┌────────────────────┼────────────────────┐
|
|
│ │ │
|
|
▼ ▼ ▼
|
|
┌───────────────┐ ┌─────────────────┐ ┌──────────────┐
|
|
│ SSH-Based │ │ Sentinel-Based │ │ Multi-Server │
|
|
│ (Scheduled) │ │ (Real-time) │ │ Aggregation │
|
|
├───────────────┤ ├─────────────────┤ ├──────────────┤
|
|
│ ServerCheck │ │ PushServerUp- │ │ ComplexStatus│
|
|
│ Job │ │ dateJob │ │ Check │
|
|
│ │ │ │ │ │
|
|
│ Every ~1min │ │ Every ~30sec │ │ On demand │
|
|
└───────┬───────┘ └────────┬────────┘ └──────┬───────┘
|
|
│ │ │
|
|
└────────────────────┼────────────────────┘
|
|
│
|
|
▼
|
|
┌───────────────────────┐
|
|
│ Application/Service │
|
|
│ Status Property │
|
|
└───────────────────────┘
|
|
│
|
|
▼
|
|
┌───────────────────────┐
|
|
│ UI Display (Livewire) │
|
|
└───────────────────────┘
|
|
```
|
|
|
|
### **Status Priority System**
|
|
|
|
All status aggregation locations **MUST** follow the same priority:
|
|
|
|
**For Running Containers**:
|
|
1. **unhealthy** - Container has failing health checks
|
|
2. **unknown** - Container health status cannot be determined
|
|
3. **healthy** - Container is healthy
|
|
|
|
**For Non-Running States**:
|
|
1. **restarting** → `degraded (unhealthy)`
|
|
2. **running + exited** → `degraded (unhealthy)`
|
|
3. **dead/removing** → `degraded (unhealthy)`
|
|
4. **paused** → `paused`
|
|
5. **created/starting** → `starting`
|
|
6. **exited** → `exited (unhealthy)`
|
|
|
|
### **Excluded Containers**
|
|
|
|
When containers have `exclude_from_hc: true` flag or `restart: no`:
|
|
|
|
**Behavior**:
|
|
- Status is still calculated from container state
|
|
- `:excluded` suffix is appended to indicate monitoring disabled
|
|
- UI shows "(Monitoring Disabled)" badge
|
|
- Action buttons respect the actual container state
|
|
|
|
**Format**: `{actual-status}:excluded`
|
|
**Examples**: `running:unknown:excluded`, `degraded:excluded`, `exited:excluded`
|
|
|
|
**All-Excluded Scenario**:
|
|
When ALL containers are excluded from health checks:
|
|
- All three status update paths (PushServerUpdateJob, GetContainersStatus, ComplexStatusCheck) **MUST** calculate status from excluded containers
|
|
- Status is returned with `:excluded` suffix (e.g., `running:healthy:excluded`)
|
|
- **NEVER** skip status updates - always calculate from excluded containers
|
|
- This ensures consistent status regardless of which update mechanism runs
|
|
- Shared logic is in `app/Traits/CalculatesExcludedStatus.php`
|
|
|
|
### **Important Notes for Developers**
|
|
|
|
✅ **Container Status Aggregation Service**:
|
|
|
|
The container status aggregation logic is centralized in `App\Services\ContainerStatusAggregator`.
|
|
|
|
**Status Format Standard**:
|
|
- **Backend/Storage**: Colon format (`running:healthy`, `degraded:unhealthy`)
|
|
- **UI/Display**: Transform to human format (`Running (Healthy)`, `Degraded (Unhealthy)`)
|
|
|
|
1. **Using the ContainerStatusAggregator Service**:
|
|
- Import `App\Services\ContainerStatusAggregator` in any class needing status aggregation
|
|
- Two methods available:
|
|
- `aggregateFromStrings(Collection $statusStrings, int $maxRestartCount = 0)` - For pre-formatted status strings
|
|
- `aggregateFromContainers(Collection $containers, int $maxRestartCount = 0)` - For raw Docker container objects
|
|
- Returns colon format: `running:healthy`, `degraded:unhealthy`, etc.
|
|
- Automatically handles crash loop detection via `$maxRestartCount` parameter
|
|
|
|
2. **State Machine Priority** (handled by service):
|
|
- Restarting → `degraded:unhealthy` (highest priority)
|
|
- Crash loop (exited with restarts) → `degraded:unhealthy`
|
|
- Mixed state (running + exited) → `degraded:unhealthy`
|
|
- Running → `running:unhealthy` / `running:unknown` / `running:healthy`
|
|
- Dead/Removing → `degraded:unhealthy`
|
|
- Paused → `paused:unknown`
|
|
- Starting/Created → `starting:unknown`
|
|
- Exited → `exited:unhealthy` (lowest priority)
|
|
|
|
3. **Test both update paths**:
|
|
- Run unit tests: `./vendor/bin/pest tests/Unit/ContainerStatusAggregatorTest.php`
|
|
- Run integration tests: `./vendor/bin/pest tests/Unit/`
|
|
- Test SSH updates (manual refresh)
|
|
- Test Sentinel updates (wait 30 seconds)
|
|
|
|
4. **Handle excluded containers**:
|
|
- All containers excluded (`exclude_from_hc: true`) - Use `CalculatesExcludedStatus` trait
|
|
- Mixed excluded/non-excluded containers - Filter then use `ContainerStatusAggregator`
|
|
- Containers with `restart: no` - Treated same as `exclude_from_hc: true`
|
|
|
|
5. **Use shared trait for excluded containers**:
|
|
- Import `App\Traits\CalculatesExcludedStatus` in status calculation classes
|
|
- Use `getExcludedContainersFromDockerCompose()` to parse exclusions
|
|
- Use `calculateExcludedStatus()` for full Docker inspect objects (ComplexStatusCheck)
|
|
- Use `calculateExcludedStatusFromStrings()` for status strings (PushServerUpdateJob, GetContainersStatus)
|
|
|
|
### **Related Tests**
|
|
|
|
- **[tests/Unit/ContainerStatusAggregatorTest.php](mdc:tests/Unit/ContainerStatusAggregatorTest.php)**: Core state machine logic (42 comprehensive tests)
|
|
- **[tests/Unit/ContainerHealthStatusTest.php](mdc:tests/Unit/ContainerHealthStatusTest.php)**: Health status aggregation integration
|
|
- **[tests/Unit/PushServerUpdateJobStatusAggregationTest.php](mdc:tests/Unit/PushServerUpdateJobStatusAggregationTest.php)**: Sentinel update logic
|
|
- **[tests/Unit/ExcludeFromHealthCheckTest.php](mdc:tests/Unit/ExcludeFromHealthCheckTest.php)**: Excluded container handling
|
|
|
|
### **Common Bugs to Avoid**
|
|
|
|
✅ **Prevented by ContainerStatusAggregator Service**:
|
|
- ❌ **Old Bug**: Forgetting to track `$hasUnknown` flag → ✅ Now centralized in service
|
|
- ❌ **Old Bug**: Inconsistent priority across paths → ✅ Single source of truth
|
|
- ❌ **Old Bug**: Forgetting to update all 4 locations → ✅ Only one location to update
|
|
|
|
**Still Relevant**:
|
|
|
|
❌ **Bug**: Forgetting to filter excluded containers before aggregation
|
|
✅ **Fix**: Always use `CalculatesExcludedStatus` trait to filter before calling `ContainerStatusAggregator`
|
|
|
|
❌ **Bug**: Not passing `$maxRestartCount` for crash loop detection
|
|
✅ **Fix**: Calculate max restart count from containers and pass to `aggregateFromStrings()`/`aggregateFromContainers()`
|
|
|
|
❌ **Bug**: Not handling excluded containers with `:excluded` suffix
|
|
✅ **Fix**: Check for `:excluded` suffix in UI logic and button visibility
|