mirror of
https://github.com/ershisan99/coolify.git
synced 2025-12-18 12:33:06 +00:00
Compare commits
19 Commits
v4.0.0-bet
...
v4.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e0adc3777 | ||
|
|
7d06fc4403 | ||
|
|
0e1bcceb8e | ||
|
|
a922f2fedf | ||
|
|
1e0226c8ed | ||
|
|
5c45908087 | ||
|
|
aefdc76805 | ||
|
|
b3c8c881b7 | ||
|
|
9ab5a1f7bd | ||
|
|
23968e7886 | ||
|
|
390d24b6d7 | ||
|
|
e4296345b3 | ||
|
|
bcffbe418b | ||
|
|
bfbee4e78f | ||
|
|
2bdb44dac3 | ||
|
|
4daa1b8c16 | ||
|
|
febc399568 | ||
|
|
9b9d4f9941 | ||
|
|
f49b87870c |
@@ -104,13 +104,15 @@ class General extends Component
|
||||
}
|
||||
public function mount()
|
||||
{
|
||||
$this->is_static = $this->application->settings->is_static;
|
||||
$this->is_git_submodules_enabled = $this->application->settings->is_git_submodules_enabled;
|
||||
$this->is_git_lfs_enabled = $this->application->settings->is_git_lfs_enabled;
|
||||
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
|
||||
$this->is_preview_deployments_enabled = $this->application->settings->is_preview_deployments_enabled;
|
||||
$this->is_auto_deploy_enabled = $this->application->settings->is_auto_deploy_enabled;
|
||||
$this->is_force_https_enabled = $this->application->settings->is_force_https_enabled;
|
||||
if (data_get($this->application,'settings')) {
|
||||
$this->is_static = $this->application->settings->is_static;
|
||||
$this->is_git_submodules_enabled = $this->application->settings->is_git_submodules_enabled;
|
||||
$this->is_git_lfs_enabled = $this->application->settings->is_git_lfs_enabled;
|
||||
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
|
||||
$this->is_preview_deployments_enabled = $this->application->settings->is_preview_deployments_enabled;
|
||||
$this->is_auto_deploy_enabled = $this->application->settings->is_auto_deploy_enabled;
|
||||
$this->is_force_https_enabled = $this->application->settings->is_force_https_enabled;
|
||||
}
|
||||
}
|
||||
|
||||
public function submit()
|
||||
|
||||
@@ -72,8 +72,7 @@ class Previews extends Component
|
||||
public function stop(int $pull_request_id)
|
||||
{
|
||||
try {
|
||||
$container_name = generateApplicationContainerName($this->application);
|
||||
ray('Stopping container: ' . $container_name);
|
||||
$container_name = generateApplicationContainerName($this->application, $pull_request_id);
|
||||
|
||||
instant_remote_process(["docker rm -f $container_name"], $this->application->destination->server, throwError: false);
|
||||
ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->delete();
|
||||
|
||||
@@ -10,7 +10,7 @@ class CreateScheduledBackup extends Component
|
||||
public $database;
|
||||
public $frequency;
|
||||
public bool $enabled = true;
|
||||
public bool $save_s3 = true;
|
||||
public bool $save_s3 = false;
|
||||
public $s3_storage_id;
|
||||
public $s3s;
|
||||
|
||||
|
||||
@@ -26,6 +26,10 @@ class PublicGitRepository extends Component
|
||||
public string $git_branch = 'main';
|
||||
public int $rate_limit_remaining = 0;
|
||||
public $rate_limit_reset = 0;
|
||||
private object $repository_url_parsed;
|
||||
public GithubApp|GitlabApp|null $git_source = null;
|
||||
public string $git_host;
|
||||
public string $git_repository;
|
||||
protected $rules = [
|
||||
'repository_url' => 'required|url',
|
||||
'port' => 'required|numeric',
|
||||
@@ -38,10 +42,6 @@ class PublicGitRepository extends Component
|
||||
'is_static' => 'static',
|
||||
'publish_directory' => 'publish directory',
|
||||
];
|
||||
private object $repository_url_parsed;
|
||||
private GithubApp|GitlabApp|null $git_source = null;
|
||||
private string $git_host;
|
||||
private string $git_repository;
|
||||
|
||||
public function mount()
|
||||
{
|
||||
@@ -76,6 +76,7 @@ class PublicGitRepository extends Component
|
||||
$this->get_branch();
|
||||
$this->selected_branch = $this->git_branch;
|
||||
} catch (\Throwable $e) {
|
||||
ray($e->getMessage());
|
||||
if (!$this->branch_found && $this->git_branch == 'main') {
|
||||
try {
|
||||
$this->git_branch = 'master';
|
||||
@@ -123,9 +124,6 @@ class PublicGitRepository extends Component
|
||||
$project_uuid = $this->parameters['project_uuid'];
|
||||
$environment_name = $this->parameters['environment_name'];
|
||||
|
||||
$this->get_git_source();
|
||||
$this->git_branch = $this->selected_branch ?? $this->git_branch;
|
||||
|
||||
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
|
||||
if (!$destination) {
|
||||
$destination = SwarmDocker::where('uuid', $destination_uuid)->first();
|
||||
|
||||
@@ -45,6 +45,9 @@ CMD ["nginx", "-g", "daemon off;"]
|
||||
$environment = $project->load(['environments'])->environments->where('name', $this->parameters['environment_name'])->first();
|
||||
|
||||
$port = get_port_from_dockerfile($this->dockerfile);
|
||||
if (!$port) {
|
||||
$port = 80;
|
||||
}
|
||||
$application = Application::create([
|
||||
'name' => 'dockerfile-' . new Cuid2(7),
|
||||
'repository_project_id' => 0,
|
||||
@@ -56,6 +59,7 @@ CMD ["nginx", "-g", "daemon off;"]
|
||||
'environment_id' => $environment->id,
|
||||
'destination_id' => $destination->id,
|
||||
'destination_type' => $destination_class,
|
||||
'health_check_enabled' => false,
|
||||
'source_id' => 0,
|
||||
'source_type' => GithubApp::class
|
||||
]);
|
||||
|
||||
@@ -9,6 +9,7 @@ class HealthChecks extends Component
|
||||
|
||||
public $resource;
|
||||
protected $rules = [
|
||||
'resource.health_check_enabled' => 'boolean',
|
||||
'resource.health_check_path' => 'string',
|
||||
'resource.health_check_port' => 'nullable|string',
|
||||
'resource.health_check_host' => 'string',
|
||||
@@ -22,12 +23,19 @@ class HealthChecks extends Component
|
||||
'resource.health_check_start_period' => 'integer',
|
||||
|
||||
];
|
||||
public function instantSave()
|
||||
{
|
||||
$this->resource->save();
|
||||
$this->emit('success', 'Health check updated.');
|
||||
|
||||
|
||||
}
|
||||
public function submit()
|
||||
{
|
||||
try {
|
||||
$this->validate();
|
||||
$this->resource->save();
|
||||
$this->emit('saved');
|
||||
$this->emit('success', 'Health check updated.');
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->build_workdir = "{$this->workdir}" . rtrim($this->application->base_directory, '/');
|
||||
$this->is_debug_enabled = $this->application->settings->is_debug_enabled;
|
||||
|
||||
$this->container_name = generateApplicationContainerName($this->application);
|
||||
$this->container_name = generateApplicationContainerName($this->application, $this->pull_request_id);
|
||||
savePrivateKeyToFs($this->server);
|
||||
$this->saved_outputs = collect();
|
||||
|
||||
@@ -97,7 +97,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
if ($this->pull_request_id !== 0) {
|
||||
$this->preview = ApplicationPreview::findPreviewByApplicationAndPullId($this->application->id, $this->pull_request_id);
|
||||
if ($this->application->fqdn) {
|
||||
$preview_fqdn = getFqdnWithoutPort(data_get($this->preview, 'fqdn'));
|
||||
if (data_get($this->preview, 'fqdn')) {
|
||||
$preview_fqdn = getFqdnWithoutPort(data_get($this->preview, 'fqdn'));
|
||||
}
|
||||
$template = $this->application->preview_url_template;
|
||||
$url = Url::fromString($this->application->fqdn);
|
||||
$host = $url->getHost();
|
||||
@@ -284,7 +286,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
|
||||
private function rolling_update()
|
||||
{
|
||||
if (count($this->application->ports_mappings_array) > 0){
|
||||
if (count($this->application->ports_mappings_array) > 0) {
|
||||
$this->execute_remote_command(
|
||||
["echo -n 'Application has ports mapped to the host system, rolling update is not supported. Stopping current container.'"],
|
||||
);
|
||||
@@ -301,6 +303,10 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
}
|
||||
private function health_check()
|
||||
{
|
||||
if ($this->application->isHealthcheckDisabled()) {
|
||||
$this->newVersionIsHealthy = true;
|
||||
return;
|
||||
}
|
||||
ray('New container name: ', $this->container_name);
|
||||
if ($this->container_name) {
|
||||
$counter = 0;
|
||||
@@ -399,7 +405,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
{
|
||||
$this->execute_remote_command(
|
||||
[
|
||||
"echo -n 'Importing {$this->application->git_repository}:{$this->application->git_branch} to {$this->workdir}. '"
|
||||
"echo -n 'Importing {$this->application->git_repository}:{$this->application->git_branch} (commit sha {$this->application->git_commit_sha}) to {$this->workdir}. '"
|
||||
],
|
||||
[
|
||||
$this->importing_git_repository()
|
||||
@@ -571,6 +577,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
||||
]
|
||||
]
|
||||
];
|
||||
if ($this->application->isHealthcheckDisabled()) {
|
||||
data_forget($docker_compose, 'services.' . $this->container_name . '.healthcheck');
|
||||
}
|
||||
if (count($this->application->ports_mappings_array) > 0 && $this->pull_request_id === 0) {
|
||||
$docker_compose['services'][$this->container_name]['ports'] = $this->application->ports_mappings_array;
|
||||
}
|
||||
|
||||
@@ -108,9 +108,9 @@ class ContainerStatusJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$labelId = data_get($labels, 'coolify.applicationId');
|
||||
if ($labelId) {
|
||||
if (str_contains($labelId, '-pr-')) {
|
||||
$previewId = (int) Str::after($labelId, '-pr-');
|
||||
$pullRequestId = data_get($labels, 'coolify.pullRequestId');
|
||||
$applicationId = (int) Str::before($labelId, '-pr-');
|
||||
$preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id', $previewId)->first();
|
||||
$preview = ApplicationPreview::where('application_id', $applicationId)->where('pull_request_id', $pullRequestId)->first();
|
||||
if ($preview) {
|
||||
$foundApplicationPreviews[] = $preview->id;
|
||||
$statusFromDb = $preview->status;
|
||||
|
||||
@@ -61,7 +61,8 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
|
||||
public function handle(): void
|
||||
{
|
||||
try {
|
||||
if (data_get($this->database, 'status') !== 'running') {
|
||||
$status = Str::of(data_get($this->database, 'status'));
|
||||
if (!$status->startsWith('running') && $this->database->id !== 0) {
|
||||
ray('database not running');
|
||||
return;
|
||||
}
|
||||
@@ -89,7 +90,6 @@ class DatabaseBackupJob implements ShouldQueue, ShouldBeEncrypted
|
||||
$this->upload_to_s3();
|
||||
}
|
||||
$this->save_backup_logs();
|
||||
// TODO: Notify user
|
||||
} catch (\Throwable $e) {
|
||||
ray($e->getMessage());
|
||||
send_internal_notification('DatabaseBackupJob failed with: ' . $e->getMessage());
|
||||
|
||||
@@ -231,4 +231,12 @@ class Application extends BaseModel
|
||||
}
|
||||
return true;
|
||||
}
|
||||
public function isHealthcheckDisabled(): bool
|
||||
{
|
||||
if (data_get($this, 'dockerfile') || data_get($this, 'build_pack') === 'dockerfile' || data_get($this,'health_check_enabled') === false) {
|
||||
ray('dockerfile');
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,16 +104,16 @@ function getContainerStatus(Server $server, string $container_id, bool $all_data
|
||||
return data_get($container[0], 'State.Status', 'exited');
|
||||
}
|
||||
|
||||
function generateApplicationContainerName(Application $application)
|
||||
function generateApplicationContainerName(Application $application, $pull_request_id = 0)
|
||||
{
|
||||
$now = now()->format('Hisu');
|
||||
if ($application->pull_request_id !== 0 && $application->pull_request_id !== null) {
|
||||
return $application->uuid . '-pr-' . $application->pull_request_id;
|
||||
if ($pull_request_id !== 0 && $pull_request_id !== null) {
|
||||
return $application->uuid . '-pr-' . $pull_request_id;
|
||||
} else {
|
||||
return $application->uuid . '-' . $now;
|
||||
}
|
||||
}
|
||||
function get_port_from_dockerfile($dockerfile): int
|
||||
function get_port_from_dockerfile($dockerfile): int|null
|
||||
{
|
||||
$dockerfile_array = explode("\n", $dockerfile);
|
||||
$found_exposed_port = null;
|
||||
@@ -127,7 +127,7 @@ function get_port_from_dockerfile($dockerfile): int
|
||||
if ($found_exposed_port) {
|
||||
return (int)$found_exposed_port->value();
|
||||
}
|
||||
return 80;
|
||||
return null;
|
||||
}
|
||||
|
||||
function defaultLabels($id, $name, $pull_request_id = 0, string $type = 'application', $subType = null, $subId = null)
|
||||
@@ -207,10 +207,10 @@ function generateLabelsApplication(Application $application, ?ApplicationPreview
|
||||
{
|
||||
|
||||
$pull_request_id = data_get($preview, 'pull_request_id', 0);
|
||||
$container_name = generateApplicationContainerName($application);
|
||||
$container_name = generateApplicationContainerName($application, $pull_request_id);
|
||||
$appId = $application->id;
|
||||
if ($pull_request_id !== 0) {
|
||||
$appId = $appId . '-pr-' . $application->pull_request_id;
|
||||
if ($pull_request_id !== 0 && $pull_request_id !== null) {
|
||||
$appId = $appId . '-pr-' . $pull_request_id;
|
||||
}
|
||||
$labels = collect([]);
|
||||
$labels = $labels->merge(defaultLabels($appId, $container_name, $pull_request_id));
|
||||
|
||||
@@ -7,7 +7,7 @@ return [
|
||||
|
||||
// The release version of your application
|
||||
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
|
||||
'release' => '4.0.0-beta.51',
|
||||
'release' => '4.0.0-beta.56',
|
||||
// When left empty or `null` the Laravel environment will be used
|
||||
'environment' => config('app.env'),
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
<?php
|
||||
|
||||
return '4.0.0-beta.51';
|
||||
return '4.0.0-beta.56';
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
use Illuminate\Database\Migrations\Migration;
|
||||
use Illuminate\Database\Schema\Blueprint;
|
||||
use Illuminate\Support\Facades\Schema;
|
||||
|
||||
return new class extends Migration
|
||||
{
|
||||
/**
|
||||
* Run the migrations.
|
||||
*/
|
||||
public function up(): void
|
||||
{
|
||||
Schema::table('applications', function (Blueprint $table) {
|
||||
$table->boolean('health_check_enabled')->default(true);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the migrations.
|
||||
*/
|
||||
public function down(): void
|
||||
{
|
||||
Schema::table('applications', function (Blueprint $table) {
|
||||
$table->dropColumn('health_check_enabled');
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -54,11 +54,11 @@
|
||||
<div class="flex flex-col p-4 bg-coolgray-200">
|
||||
<div class="flex gap-2">PR #{{ data_get($preview, 'pull_request_id') }} |
|
||||
@if (Str::of(data_get($preview, 'status'))->startsWith('running'))
|
||||
<x-status.running :status="$status" />
|
||||
<x-status.running :status="data_get($preview, 'status')" />
|
||||
@elseif(Str::of(data_get($preview, 'status'))->startsWith('restarting'))
|
||||
<x-status.restarting :status="$status" />
|
||||
<x-status.restarting :status="data_get($preview, 'status')" />
|
||||
@else
|
||||
<x-status.stopped :status="$status" />
|
||||
<x-status.stopped :status="data_get($preview, 'status')" />
|
||||
@endif
|
||||
@if (data_get($preview, 'status') !== 'exited')
|
||||
| <a target="_blank" href="{{ data_get($preview, 'fqdn') }}">Open Preview
|
||||
|
||||
@@ -5,24 +5,27 @@
|
||||
</div>
|
||||
<div class="pb-4">Define how your resource's health should be checked.</div>
|
||||
<div class="flex flex-col gap-4">
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input id="resource.health_check_method" placeholder="GET" label="Method" required />
|
||||
<div class="w-32">
|
||||
<x-forms.checkbox instantSave id="resource.health_check_enabled" label="Enabled" />
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input id="resource.health_check_method" placeholder="GET" label="Method" required />
|
||||
|
||||
<x-forms.input id="resource.health_check_scheme" placeholder="http" label="Scheme" required />
|
||||
<x-forms.input id="resource.health_check_host" placeholder="localhost" label="Host" required />
|
||||
<x-forms.input id="resource.health_check_port"
|
||||
helper="If no port is defined, the first exposed port will be used." placeholder="80" label="Port" />
|
||||
<x-forms.input id="resource.health_check_path" placeholder="/health" label="Path" required />
|
||||
<x-forms.input id="resource.health_check_scheme" placeholder="http" label="Scheme" required />
|
||||
<x-forms.input id="resource.health_check_host" placeholder="localhost" label="Host" required />
|
||||
<x-forms.input id="resource.health_check_port"
|
||||
helper="If no port is defined, the first exposed port will be used." placeholder="80" label="Port" />
|
||||
<x-forms.input id="resource.health_check_path" placeholder="/health" label="Path" required />
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input id="resource.health_check_return_code" placeholder="200" label="Return Code" required />
|
||||
<x-forms.input id="resource.health_check_response_text" placeholder="OK" label="Response Text" />
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input id="resource.health_check_interval" placeholder="30" label="Interval" required />
|
||||
<x-forms.input id="resource.health_check_timeout" placeholder="30" label="Timeout" required />
|
||||
<x-forms.input id="resource.health_check_retries" placeholder="3" label="Retries" required />
|
||||
<x-forms.input id="resource.health_check_start_period" placeholder="30" label="Start Period" required />
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input id="resource.health_check_return_code" placeholder="200" label="Return Code" required />
|
||||
<x-forms.input id="resource.health_check_response_text" placeholder="OK" label="Response Text" />
|
||||
</div>
|
||||
<div class="flex gap-2">
|
||||
<x-forms.input id="resource.health_check_interval" placeholder="30" label="Interval" required />
|
||||
<x-forms.input id="resource.health_check_timeout" placeholder="30" label="Timeout" required />
|
||||
<x-forms.input id="resource.health_check_retries" placeholder="3" label="Retries" required />
|
||||
<x-forms.input id="resource.health_check_start_period" placeholder="30" label="Start Period" required />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
@@ -111,13 +111,17 @@ Route::post('/source/github/events', function () {
|
||||
$applications = Application::where('repository_project_id', $id)->whereRelation('source', 'is_public', false);
|
||||
if ($x_github_event === 'push') {
|
||||
$applications = $applications->where('git_branch', $branch)->get();
|
||||
if ($applications->isEmpty()) {
|
||||
return response("Nothing to do. No applications found with branch '$branch'.");
|
||||
}
|
||||
}
|
||||
if ($x_github_event === 'pull_request') {
|
||||
$applications = $applications->where('git_branch', $base_branch)->get();
|
||||
if ($applications->isEmpty()) {
|
||||
return response("Nothing to do. No applications found with branch '$base_branch'.");
|
||||
}
|
||||
}
|
||||
if ($applications->isEmpty()) {
|
||||
return response("Nothing to do. No applications found with branch '$base_branch'.");
|
||||
}
|
||||
|
||||
foreach ($applications as $application) {
|
||||
$isFunctional = $application->destination->server->isFunctional();
|
||||
if (!$isFunctional) {
|
||||
@@ -168,9 +172,9 @@ Route::post('/source/github/events', function () {
|
||||
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
||||
if ($found) {
|
||||
$found->delete();
|
||||
$container_name = generateApplicationContainerName($application);
|
||||
ray('Stopping container: ' . $container_name);
|
||||
remote_process(["docker rm -f $container_name"], $application->destination->server);
|
||||
$container_name = generateApplicationContainerName($application,$pull_request_id);
|
||||
// ray('Stopping container: ' . $container_name);
|
||||
instant_remote_process(["docker rm -f $container_name"], $application->destination->server);
|
||||
return response('Preview Deployment closed.');
|
||||
}
|
||||
return response('Nothing to do. No Preview Deployment found');
|
||||
@@ -323,7 +327,7 @@ Route::post('/payments/stripe/events', function () {
|
||||
}
|
||||
if ($alreadyCancelAtPeriodEnd !== $cancelAtPeriodEnd) {
|
||||
if ($cancelAtPeriodEnd) {
|
||||
send_internal_notification('Subscription cancelled at period end for team: ' . $subscription->team->id);
|
||||
// send_internal_notification('Subscription cancelled at period end for team: ' . $subscription->team->id);
|
||||
} else {
|
||||
send_internal_notification('Subscription resumed for team: ' . $subscription->team->id);
|
||||
}
|
||||
@@ -342,7 +346,7 @@ Route::post('/payments/stripe/events', function () {
|
||||
'stripe_invoice_paid' => false,
|
||||
'stripe_trial_already_ended' => true,
|
||||
]);
|
||||
send_internal_notification('Subscription cancelled: ' . $subscription->team->id);
|
||||
// send_internal_notification('Subscription cancelled: ' . $subscription->team->id);
|
||||
break;
|
||||
case 'customer.subscription.trial_will_end':
|
||||
$customerId = data_get($data, 'customer');
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"version": "3.12.36"
|
||||
},
|
||||
"v4": {
|
||||
"version": "4.0.0-beta.51"
|
||||
"version": "4.0.0-beta.56"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user