Add tests and methods for enhanced Coolify app handling, include isolated stack support and schema updates
This commit is contained in:
@@ -246,6 +246,7 @@ class coolify_manager
|
||||
if ($instanceId <= 0) {
|
||||
$instanceId = $this->defaultInstanceId();
|
||||
}
|
||||
$isolatedStack = $this->isIsolatedStackTargetRequest($input);
|
||||
$instance = $this->getInstance($instanceId);
|
||||
$input = $this->applyCoolifyDeploymentDefaults($input, $instance);
|
||||
$input = $this->applyCoolifyPortDefaults($kind, $input, $instance);
|
||||
@@ -293,11 +294,14 @@ class coolify_manager
|
||||
|
||||
$targetId = $this->insertId();
|
||||
$this->attachTargetToReplicationHost($kind, $replicationHostId, $targetId, $instanceId);
|
||||
$this->ensureFailoverEnabled($kind);
|
||||
if (!$isolatedStack) {
|
||||
$this->ensureFailoverEnabled($kind);
|
||||
}
|
||||
$this->audit($targetId, $instanceId, $replicationHostId, 'target_created', $actorUserId, 'info', [
|
||||
'kind' => $kind,
|
||||
'role' => $role,
|
||||
'resource_name' => $resourceName,
|
||||
'isolated_stack' => $isolatedStack,
|
||||
]);
|
||||
|
||||
$target = $this->getTarget($targetId);
|
||||
@@ -424,6 +428,22 @@ class coolify_manager
|
||||
$provision = null;
|
||||
|
||||
if ($hostId > 0) {
|
||||
if ($this->targetSkipsReplicationProvisioning($target)) {
|
||||
$provision = [
|
||||
'ok' => true,
|
||||
'skipped' => true,
|
||||
'status' => 'isolated_stack_empty_data_service',
|
||||
'message' => 'Isolated stack data services are intentionally not attached to production replication.',
|
||||
];
|
||||
|
||||
return [
|
||||
'ok' => true,
|
||||
'reconcile' => $reconcile,
|
||||
'provision' => $provision,
|
||||
'target' => $this->publicTarget($this->getTarget($targetId)),
|
||||
];
|
||||
}
|
||||
|
||||
$reconcileAction = (string)($reconcile['status'] ?? $reconcile['context']['action'] ?? '');
|
||||
if (in_array($reconcileAction, ['created', 'updated'], true)) {
|
||||
$provision = (string)($target['kind'] ?? '') === 'minio'
|
||||
@@ -1251,6 +1271,7 @@ class coolify_manager
|
||||
|
||||
private function composeInputFromRequest(string $kind, array $input, array $instance): array
|
||||
{
|
||||
$composeRole = $this->isIsolatedStackTargetRequest($input) ? 'primary' : 'replica';
|
||||
$hostPort = (int)($input['host_port'] ?? $input['port'] ?? match ($kind) {
|
||||
'database' => 3307,
|
||||
'redis' => 6380,
|
||||
@@ -1259,7 +1280,7 @@ class coolify_manager
|
||||
|
||||
$base = [
|
||||
'kind' => $kind,
|
||||
'role' => 'replica',
|
||||
'role' => $composeRole,
|
||||
'service_name' => $input['service_name'] ?? $input['resource_name'] ?? null,
|
||||
'host_port' => $hostPort,
|
||||
];
|
||||
@@ -1291,6 +1312,7 @@ class coolify_manager
|
||||
if ($host === '') {
|
||||
throw new RuntimeException('Target host is required so replication can reach the Coolify-managed container.');
|
||||
}
|
||||
$isolatedStack = $this->isIsolatedStackTargetRequest($input);
|
||||
|
||||
$payload = [
|
||||
'label' => trim((string)($input['label'] ?? $credentials['label'] ?? $template['service_name'] ?? '')),
|
||||
@@ -1307,9 +1329,19 @@ class coolify_manager
|
||||
$payload['replication_username'] = (string)($credentials['replication_username'] ?? $input['replication_username'] ?? 'replication');
|
||||
$payload['replication_password'] = (string)($credentials['replication_password'] ?? $input['replication_password'] ?? '');
|
||||
$payload['ssl_mode'] = (string)($credentials['ssl_mode'] ?? $input['ssl_mode'] ?? 'DISABLED');
|
||||
$payload['options'] = ['allow_preseeded_replica' => true];
|
||||
$payload['options'] = [
|
||||
'allow_preseeded_replica' => !$isolatedStack,
|
||||
'isolated_stack' => $isolatedStack,
|
||||
'skip_replication_provisioning' => $isolatedStack,
|
||||
'production_data_attached' => false,
|
||||
];
|
||||
} elseif ($kind === 'redis') {
|
||||
$payload['database'] = (int)($credentials['database'] ?? $input['database'] ?? 0);
|
||||
$payload['options'] = [
|
||||
'isolated_stack' => $isolatedStack,
|
||||
'skip_replication_provisioning' => $isolatedStack,
|
||||
'production_data_attached' => false,
|
||||
];
|
||||
} else {
|
||||
$payload['scheme'] = (string)($credentials['scheme'] ?? $input['scheme'] ?? 'http');
|
||||
$payload['buckets'] = $credentials['buckets'] ?? $this->normalizeBuckets($input['buckets'] ?? null);
|
||||
@@ -1323,6 +1355,9 @@ class coolify_manager
|
||||
'console_port' => $payload['console_port'],
|
||||
'replication_transfer_limit' => $payload['replication_transfer_limit'],
|
||||
'space_headroom_percent' => (float)($credentials['space_headroom_percent'] ?? 20.0),
|
||||
'isolated_stack' => $isolatedStack,
|
||||
'skip_replication_provisioning' => $isolatedStack,
|
||||
'production_data_attached' => false,
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1336,7 +1371,7 @@ class coolify_manager
|
||||
$credentials = $this->hostCredentials($host);
|
||||
$input = is_array($options['compose_input'] ?? null) ? $options['compose_input'] : [];
|
||||
$input['kind'] = $kind;
|
||||
$input['role'] = 'replica';
|
||||
$input['role'] = $this->targetComposeRole($target, $options);
|
||||
$input['service_name'] = $target['resource_name'] ?? $target['label'] ?? null;
|
||||
$input['host_port'] = (int)($host['port'] ?? $input['host_port'] ?? 0);
|
||||
|
||||
@@ -1456,8 +1491,10 @@ class coolify_manager
|
||||
|
||||
private function targetOptions(array $input, array $template, array $composeInput): array
|
||||
{
|
||||
return [
|
||||
$isolatedStack = $this->isIsolatedStackTargetRequest($input);
|
||||
$options = [
|
||||
'compose_input' => $composeInput,
|
||||
'compose_role' => (string)($composeInput['role'] ?? 'replica'),
|
||||
'compose_service_name' => (string)($template['service_name'] ?? ''),
|
||||
'engine' => (string)($template['engine'] ?? ''),
|
||||
'coolify_docs' => [
|
||||
@@ -1465,6 +1502,49 @@ class coolify_manager
|
||||
'envs_bulk_endpoint' => '/api/v1/services/{uuid}/envs/bulk',
|
||||
],
|
||||
];
|
||||
|
||||
if ($isolatedStack) {
|
||||
$options['isolated_stack'] = true;
|
||||
$options['skip_replication_provisioning'] = true;
|
||||
$options['production_data_attached'] = false;
|
||||
}
|
||||
|
||||
return $options;
|
||||
}
|
||||
|
||||
private function isIsolatedStackTargetRequest(array $input): bool
|
||||
{
|
||||
$options = is_array($input['options'] ?? null) ? $input['options'] : [];
|
||||
|
||||
return $this->toBool(
|
||||
$input['isolated_stack']
|
||||
?? $input['isolated_empty_service']
|
||||
?? $input['skip_replication_provisioning']
|
||||
?? $options['isolated_stack']
|
||||
?? $options['skip_replication_provisioning']
|
||||
?? false,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
||||
private function targetComposeRole(array $target, array $options): string
|
||||
{
|
||||
$composeInput = is_array($options['compose_input'] ?? null) ? $options['compose_input'] : [];
|
||||
$composeRole = strtolower(trim((string)($options['compose_role'] ?? $composeInput['role'] ?? '')));
|
||||
if (in_array($composeRole, ['primary', 'replica'], true)) {
|
||||
return $composeRole;
|
||||
}
|
||||
|
||||
return $this->toBool($options['isolated_stack'] ?? $options['skip_replication_provisioning'] ?? false, false)
|
||||
? 'primary'
|
||||
: 'replica';
|
||||
}
|
||||
|
||||
private function targetSkipsReplicationProvisioning(array $target): bool
|
||||
{
|
||||
$options = self::jsonDecode($target['options_json'] ?? null);
|
||||
|
||||
return $this->toBool($options['skip_replication_provisioning'] ?? $options['isolated_stack'] ?? false, false);
|
||||
}
|
||||
|
||||
private function attachTargetToReplicationHost(string $kind, int $hostId, int $targetId, int $instanceId): void
|
||||
|
||||
Reference in New Issue
Block a user