247 lines
7.6 KiB
PHP
247 lines
7.6 KiB
PHP
<?php
|
|
|
|
namespace classes;
|
|
|
|
use RuntimeException;
|
|
|
|
class coolify_api_client
|
|
{
|
|
private string $baseUrl;
|
|
private string $token;
|
|
private int $timeoutSeconds;
|
|
|
|
public function __construct(string $baseUrl, string $token, int $timeoutSeconds = 4)
|
|
{
|
|
$this->baseUrl = self::normalizeBaseUrl($baseUrl);
|
|
$this->token = trim($token);
|
|
$this->timeoutSeconds = max(1, $timeoutSeconds);
|
|
if ($this->baseUrl === '' || $this->token === '') {
|
|
throw new RuntimeException('Coolify base URL and API token are required.');
|
|
}
|
|
}
|
|
|
|
public static function normalizeBaseUrl(string $baseUrl): string
|
|
{
|
|
$baseUrl = rtrim(trim($baseUrl), '/');
|
|
if ($baseUrl === '') {
|
|
return '';
|
|
}
|
|
|
|
if (preg_match('#/api/v[0-9]+$#i', $baseUrl) === 1) {
|
|
return $baseUrl;
|
|
}
|
|
|
|
return $baseUrl . '/api/v1';
|
|
}
|
|
|
|
public function healthcheck(): array
|
|
{
|
|
return $this->request('GET', '/health', null, false);
|
|
}
|
|
|
|
public function version(): array
|
|
{
|
|
return $this->request('GET', '/version');
|
|
}
|
|
|
|
public function listServers(): array
|
|
{
|
|
return $this->request('GET', '/servers');
|
|
}
|
|
|
|
public function listProjects(): array
|
|
{
|
|
return $this->request('GET', '/projects');
|
|
}
|
|
|
|
public function listProjectEnvironments(string $projectUuid): array
|
|
{
|
|
return $this->request('GET', '/projects/' . rawurlencode($projectUuid) . '/environments');
|
|
}
|
|
|
|
public function listServices(): array
|
|
{
|
|
return $this->request('GET', '/services');
|
|
}
|
|
|
|
public function listGithubApps(): array
|
|
{
|
|
return $this->request('GET', '/github-apps');
|
|
}
|
|
|
|
public function getService(string $uuid): array
|
|
{
|
|
return $this->request('GET', '/services/' . rawurlencode($uuid));
|
|
}
|
|
|
|
public function createService(array $payload): array
|
|
{
|
|
return $this->request('POST', '/services', $payload);
|
|
}
|
|
|
|
public function createPrivateGithubAppApplication(array $payload): array
|
|
{
|
|
return $this->request('POST', '/applications/private-github-app', $payload);
|
|
}
|
|
|
|
public function getApplication(string $uuid): array
|
|
{
|
|
return $this->request('GET', '/applications/' . rawurlencode($uuid));
|
|
}
|
|
|
|
public function updateApplication(string $uuid, array $payload): array
|
|
{
|
|
return $this->request('PATCH', '/applications/' . rawurlencode($uuid), $payload);
|
|
}
|
|
|
|
public function updateService(string $uuid, array $payload): array
|
|
{
|
|
return $this->request('PATCH', '/services/' . rawurlencode($uuid), $payload);
|
|
}
|
|
|
|
public function updateServiceEnvsBulk(string $uuid, array $env): array
|
|
{
|
|
$data = [];
|
|
foreach ($env as $key => $value) {
|
|
$data[] = [
|
|
'key' => (string)$key,
|
|
'value' => (string)$value,
|
|
'is_preview' => false,
|
|
'is_literal' => true,
|
|
'is_multiline' => str_contains((string)$value, "\n"),
|
|
'is_shown_once' => false,
|
|
];
|
|
}
|
|
|
|
if ($data === []) {
|
|
return [];
|
|
}
|
|
|
|
return $this->request('PATCH', '/services/' . rawurlencode($uuid) . '/envs/bulk', ['data' => $data]);
|
|
}
|
|
|
|
public function deployResource(string $uuid, bool $force = false): array
|
|
{
|
|
$path = '/deploy?uuid=' . rawurlencode($uuid) . '&force=' . ($force ? 'true' : 'false');
|
|
return $this->request('GET', $path);
|
|
}
|
|
|
|
public function startService(string $uuid): array
|
|
{
|
|
return $this->request('GET', '/services/' . rawurlencode($uuid) . '/start');
|
|
}
|
|
|
|
public function restartService(string $uuid): array
|
|
{
|
|
return $this->request('GET', '/services/' . rawurlencode($uuid) . '/restart');
|
|
}
|
|
|
|
public function restartApplication(string $uuid): array
|
|
{
|
|
return $this->request('GET', '/applications/' . rawurlencode($uuid) . '/restart');
|
|
}
|
|
|
|
public function deleteService(string $uuid): array
|
|
{
|
|
return $this->request('DELETE', '/services/' . rawurlencode($uuid));
|
|
}
|
|
|
|
public function listDeployments(): array
|
|
{
|
|
return $this->request('GET', '/deployments');
|
|
}
|
|
|
|
protected function request(string $method, string $path, ?array $payload = null, bool $versionedApi = true): array
|
|
{
|
|
$url = ($versionedApi ? $this->baseUrl : $this->apiRootUrl()) . '/' . ltrim($path, '/');
|
|
$curl = curl_init($url);
|
|
if ($curl === false) {
|
|
throw new RuntimeException('Could not initialize Coolify API request.');
|
|
}
|
|
|
|
$headers = [
|
|
'Accept: application/json',
|
|
'Authorization: Bearer ' . $this->token,
|
|
];
|
|
|
|
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
|
|
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, strtoupper($method));
|
|
curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, min(2, $this->timeoutSeconds));
|
|
curl_setopt($curl, CURLOPT_TIMEOUT, $this->timeoutSeconds);
|
|
curl_setopt($curl, CURLOPT_NOSIGNAL, true);
|
|
curl_setopt($curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
|
|
|
|
if ($payload !== null) {
|
|
$body = json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
|
if ($body === false) {
|
|
throw new RuntimeException('Could not encode Coolify API payload.');
|
|
}
|
|
$headers[] = 'Content-Type: application/json';
|
|
curl_setopt($curl, CURLOPT_POSTFIELDS, $body);
|
|
}
|
|
|
|
curl_setopt($curl, CURLOPT_HTTPHEADER, $headers);
|
|
|
|
$raw = curl_exec($curl);
|
|
$error = curl_error($curl);
|
|
$status = (int)curl_getinfo($curl, CURLINFO_HTTP_CODE);
|
|
curl_close($curl);
|
|
|
|
if ($raw === false) {
|
|
throw new RuntimeException('Coolify API request failed: ' . $error);
|
|
}
|
|
|
|
$decoded = null;
|
|
if (trim((string)$raw) !== '') {
|
|
$decoded = json_decode((string)$raw, true);
|
|
if (!is_array($decoded)) {
|
|
$decoded = ['raw' => (string)$raw];
|
|
}
|
|
}
|
|
|
|
if ($status < 200 || $status >= 300) {
|
|
$message = is_array($decoded)
|
|
? (string)($decoded['message'] ?? $decoded['error'] ?? ('HTTP ' . $status))
|
|
: ('HTTP ' . $status);
|
|
if (is_array($decoded)) {
|
|
$details = self::validationErrorSummary($decoded);
|
|
if ($details !== '') {
|
|
$message .= ': ' . $details;
|
|
}
|
|
}
|
|
throw new RuntimeException('Coolify API request failed: ' . $message);
|
|
}
|
|
|
|
return is_array($decoded) ? $decoded : [];
|
|
}
|
|
|
|
private static function validationErrorSummary(array $decoded): string
|
|
{
|
|
$errors = $decoded['errors'] ?? $decoded['data']['errors'] ?? null;
|
|
if (!is_array($errors)) {
|
|
return '';
|
|
}
|
|
|
|
$parts = [];
|
|
foreach ($errors as $field => $messages) {
|
|
$fieldName = trim((string)$field);
|
|
$fieldPrefix = $fieldName !== '' ? $fieldName . ': ' : '';
|
|
if (is_array($messages)) {
|
|
$messages = implode(', ', array_filter(array_map(static fn(mixed $message): string => trim((string)$message), $messages)));
|
|
} else {
|
|
$messages = trim((string)$messages);
|
|
}
|
|
if ($messages !== '') {
|
|
$parts[] = $fieldPrefix . $messages;
|
|
}
|
|
}
|
|
|
|
return implode('; ', array_slice($parts, 0, 5));
|
|
}
|
|
|
|
private function apiRootUrl(): string
|
|
{
|
|
return preg_replace('#/v[0-9]+$#i', '', $this->baseUrl) ?: $this->baseUrl;
|
|
}
|
|
}
|