commit
efd2899ae3
@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
use App\Models\InstanceSettings;
|
use App\Models\InstanceSettings;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
|
use Illuminate\Auth\AuthenticationException;
|
||||||
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
|
||||||
use Sentry\Laravel\Integration;
|
use Sentry\Laravel\Integration;
|
||||||
use Sentry\State\Scope;
|
use Sentry\State\Scope;
|
||||||
@ -40,6 +41,13 @@ class Handler extends ExceptionHandler
|
|||||||
];
|
];
|
||||||
private InstanceSettings $settings;
|
private InstanceSettings $settings;
|
||||||
|
|
||||||
|
protected function unauthenticated($request, AuthenticationException $exception)
|
||||||
|
{
|
||||||
|
if ($request->is('api/*') || $request->expectsJson() || $this->shouldReturnJson($request, $exception)) {
|
||||||
|
return response()->json(['message' => $exception->getMessage()], 401);
|
||||||
|
}
|
||||||
|
return redirect()->guest($exception->redirectTo() ?? route('login'));
|
||||||
|
}
|
||||||
/**
|
/**
|
||||||
* Register the exception handling callbacks for the application.
|
* Register the exception handling callbacks for the application.
|
||||||
*/
|
*/
|
||||||
@ -47,6 +55,7 @@ public function register(): void
|
|||||||
{
|
{
|
||||||
$this->reportable(function (Throwable $e) {
|
$this->reportable(function (Throwable $e) {
|
||||||
if (isDev()) {
|
if (isDev()) {
|
||||||
|
ray($e);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$this->settings = InstanceSettings::get();
|
$this->settings = InstanceSettings::get();
|
||||||
|
@ -8,9 +8,27 @@ class Webhooks extends Component
|
|||||||
{
|
{
|
||||||
public $resource;
|
public $resource;
|
||||||
public ?string $deploywebhook = null;
|
public ?string $deploywebhook = null;
|
||||||
|
public ?string $githubManualWebhook = null;
|
||||||
|
public ?string $gitlabManualWebhook = null;
|
||||||
|
protected $rules = [
|
||||||
|
'resource.manual_webhook_secret_github' => 'nullable|string',
|
||||||
|
'resource.manual_webhook_secret_gitlab' => 'nullable|string',
|
||||||
|
];
|
||||||
|
public function saveSecret()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$this->validate();
|
||||||
|
$this->resource->save();
|
||||||
|
$this->emit('success','Secret Saved.');
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return handleError($e, $this);
|
||||||
|
}
|
||||||
|
}
|
||||||
public function mount()
|
public function mount()
|
||||||
{
|
{
|
||||||
$this->deploywebhook = generateDeployWebhook($this->resource);
|
$this->deploywebhook = generateDeployWebhook($this->resource);
|
||||||
|
$this->githubManualWebhook = generateGitManualWebhook($this->resource, 'github');
|
||||||
|
$this->gitlabManualWebhook = generateGitManualWebhook($this->resource, 'gitlab');
|
||||||
}
|
}
|
||||||
public function render()
|
public function render()
|
||||||
{
|
{
|
||||||
|
@ -53,6 +53,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
|
|||||||
private StandaloneDocker|SwarmDocker $destination;
|
private StandaloneDocker|SwarmDocker $destination;
|
||||||
private Server $server;
|
private Server $server;
|
||||||
private ?ApplicationPreview $preview = null;
|
private ?ApplicationPreview $preview = null;
|
||||||
|
private ?string $git_type = null;
|
||||||
|
|
||||||
private string $container_name;
|
private string $container_name;
|
||||||
private ?string $currently_running_container_name = null;
|
private ?string $currently_running_container_name = null;
|
||||||
@ -99,6 +100,8 @@ public function __construct(int $application_deployment_queue_id)
|
|||||||
$this->force_rebuild = $this->application_deployment_queue->force_rebuild;
|
$this->force_rebuild = $this->application_deployment_queue->force_rebuild;
|
||||||
$this->restart_only = $this->application_deployment_queue->restart_only;
|
$this->restart_only = $this->application_deployment_queue->restart_only;
|
||||||
|
|
||||||
|
$this->git_type = data_get($this->application_deployment_queue, 'git_type');
|
||||||
|
|
||||||
$source = data_get($this->application, 'source');
|
$source = data_get($this->application, 'source');
|
||||||
if ($source) {
|
if ($source) {
|
||||||
$this->source = $source->getMorphClass()::where('id', $this->application->source->id)->first();
|
$this->source = $source->getMorphClass()::where('id', $this->application->source->id)->first();
|
||||||
@ -218,12 +221,16 @@ public function handle(): void
|
|||||||
} finally {
|
} finally {
|
||||||
if (isset($this->docker_compose_base64)) {
|
if (isset($this->docker_compose_base64)) {
|
||||||
$readme = generate_readme_file($this->application->name, $this->application_deployment_queue->updated_at);
|
$readme = generate_readme_file($this->application->name, $this->application_deployment_queue->updated_at);
|
||||||
|
$composeFileName = "$this->configuration_dir/docker-compose.yml";
|
||||||
|
if ($this->pull_request_id !== 0) {
|
||||||
|
$composeFileName = "$this->configuration_dir/docker-compose-pr-{$this->pull_request_id}.yml";
|
||||||
|
}
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[
|
[
|
||||||
"mkdir -p $this->configuration_dir"
|
"mkdir -p $this->configuration_dir"
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"echo '{$this->docker_compose_base64}' | base64 -d > $this->configuration_dir/docker-compose.yml",
|
"echo '{$this->docker_compose_base64}' | base64 -d > $composeFileName",
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
"echo '{$readme}' > $this->configuration_dir/README.md",
|
"echo '{$readme}' > $this->configuration_dir/README.md",
|
||||||
@ -349,7 +356,7 @@ private function deploy_simple_dockerfile()
|
|||||||
$this->prepare_builder_image();
|
$this->prepare_builder_image();
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[
|
[
|
||||||
executeInDocker($this->deployment_uuid, "echo '$dockerfile_base64' | base64 -d > $this->workdir/Dockerfile")
|
executeInDocker($this->deployment_uuid, "echo '$dockerfile_base64' | base64 -d > $this->workdir$this->dockerfile_location")
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
$this->generate_image_names();
|
$this->generate_image_names();
|
||||||
@ -652,7 +659,7 @@ private function generate_git_import_commands()
|
|||||||
}
|
}
|
||||||
if ($this->pull_request_id !== 0) {
|
if ($this->pull_request_id !== 0) {
|
||||||
$this->branch = "pull/{$this->pull_request_id}/head:$pr_branch_name";
|
$this->branch = "pull/{$this->pull_request_id}/head:$pr_branch_name";
|
||||||
$commands->push(executeInDocker($this->deployment_uuid, "cd {$this->basedir} && git fetch origin pull/{$this->pull_request_id}/head:$pr_branch_name && git checkout $pr_branch_name"));
|
$commands->push(executeInDocker($this->deployment_uuid, "cd {$this->basedir} && git fetch origin $this->branch && git checkout $pr_branch_name"));
|
||||||
}
|
}
|
||||||
return $commands->implode(' && ');
|
return $commands->implode(' && ');
|
||||||
}
|
}
|
||||||
@ -664,14 +671,28 @@ private function generate_git_import_commands()
|
|||||||
throw new Exception('Private key not found. Please add a private key to the application and try again.');
|
throw new Exception('Private key not found. Please add a private key to the application and try again.');
|
||||||
}
|
}
|
||||||
$private_key = base64_encode($private_key);
|
$private_key = base64_encode($private_key);
|
||||||
$git_clone_command = "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$this->customPort} -o Port={$this->customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$git_clone_command} {$this->customRepository} {$this->basedir}";
|
$git_clone_command_base = "GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$this->customPort} -o Port={$this->customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" {$git_clone_command} {$this->customRepository} {$this->basedir}";
|
||||||
$git_clone_command = $this->set_git_import_settings($git_clone_command);
|
$git_clone_command = $this->set_git_import_settings($git_clone_command_base);
|
||||||
$commands = collect([
|
$commands = collect([
|
||||||
executeInDocker($this->deployment_uuid, "mkdir -p /root/.ssh"),
|
executeInDocker($this->deployment_uuid, "mkdir -p /root/.ssh"),
|
||||||
executeInDocker($this->deployment_uuid, "echo '{$private_key}' | base64 -d > /root/.ssh/id_rsa"),
|
executeInDocker($this->deployment_uuid, "echo '{$private_key}' | base64 -d > /root/.ssh/id_rsa"),
|
||||||
executeInDocker($this->deployment_uuid, "chmod 600 /root/.ssh/id_rsa"),
|
executeInDocker($this->deployment_uuid, "chmod 600 /root/.ssh/id_rsa"),
|
||||||
executeInDocker($this->deployment_uuid, $git_clone_command)
|
|
||||||
]);
|
]);
|
||||||
|
if ($this->pull_request_id !== 0) {
|
||||||
|
ray($this->git_type);
|
||||||
|
if ($this->git_type === 'gitlab') {
|
||||||
|
$this->branch = "merge-requests/{$this->pull_request_id}/head:$pr_branch_name";
|
||||||
|
$commands->push(executeInDocker($this->deployment_uuid, "echo 'Checking out $this->branch'"));
|
||||||
|
$git_clone_command = "{$git_clone_command} && cd {$this->basedir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$this->customPort} -o Port={$this->customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $this->branch && git checkout $pr_branch_name";
|
||||||
|
}
|
||||||
|
if ($this->git_type === 'github') {
|
||||||
|
$this->branch = "pull/{$this->pull_request_id}/head:$pr_branch_name";
|
||||||
|
$commands->push(executeInDocker($this->deployment_uuid, "echo 'Checking out $this->branch'"));
|
||||||
|
$git_clone_command = "{$git_clone_command} && cd {$this->basedir} && GIT_SSH_COMMAND=\"ssh -o ConnectTimeout=30 -p {$this->customPort} -o Port={$this->customPort} -o LogLevel=ERROR -o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -i /root/.ssh/id_rsa\" git fetch origin $this->branch && git checkout $pr_branch_name";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$commands->push(executeInDocker($this->deployment_uuid, $git_clone_command));
|
||||||
return $commands->implode(' && ');
|
return $commands->implode(' && ');
|
||||||
}
|
}
|
||||||
if ($this->application->deploymentType() === 'other') {
|
if ($this->application->deploymentType() === 'other') {
|
||||||
@ -992,7 +1013,7 @@ private function build_image()
|
|||||||
}");
|
}");
|
||||||
} else {
|
} else {
|
||||||
$this->execute_remote_command([
|
$this->execute_remote_command([
|
||||||
executeInDocker($this->deployment_uuid, "docker build $this->buildTarget $this->addHosts --network host -f {$this->workdir}/{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->build_image_name {$this->workdir}"), "hidden" => true
|
executeInDocker($this->deployment_uuid, "docker build $this->buildTarget $this->addHosts --network host -f {$this->workdir}{$this->dockerfile_location} {$this->build_args} --progress plain -t $this->build_image_name {$this->workdir}"), "hidden" => true
|
||||||
]);
|
]);
|
||||||
|
|
||||||
$dockerfile = base64_encode("FROM {$this->application->static_image}
|
$dockerfile = base64_encode("FROM {$this->application->static_image}
|
||||||
@ -1104,7 +1125,7 @@ private function generate_build_env_variables()
|
|||||||
private function add_build_env_variables_to_dockerfile()
|
private function add_build_env_variables_to_dockerfile()
|
||||||
{
|
{
|
||||||
$this->execute_remote_command([
|
$this->execute_remote_command([
|
||||||
executeInDocker($this->deployment_uuid, "cat {$this->workdir}/{$this->dockerfile_location}"), "hidden" => true, "save" => 'dockerfile'
|
executeInDocker($this->deployment_uuid, "cat {$this->workdir}{$this->dockerfile_location}"), "hidden" => true, "save" => 'dockerfile'
|
||||||
]);
|
]);
|
||||||
$dockerfile = collect(Str::of($this->saved_outputs->get('dockerfile'))->trim()->explode("\n"));
|
$dockerfile = collect(Str::of($this->saved_outputs->get('dockerfile'))->trim()->explode("\n"));
|
||||||
|
|
||||||
@ -1113,7 +1134,7 @@ private function add_build_env_variables_to_dockerfile()
|
|||||||
}
|
}
|
||||||
$dockerfile_base64 = base64_encode($dockerfile->implode("\n"));
|
$dockerfile_base64 = base64_encode($dockerfile->implode("\n"));
|
||||||
$this->execute_remote_command([
|
$this->execute_remote_command([
|
||||||
executeInDocker($this->deployment_uuid, "echo '{$dockerfile_base64}' | base64 -d > {$this->workdir}/{$this->dockerfile_location}"),
|
executeInDocker($this->deployment_uuid, "echo '{$dockerfile_base64}' | base64 -d > {$this->workdir}{$this->dockerfile_location}"),
|
||||||
"hidden" => true
|
"hidden" => true
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
@ -85,6 +85,18 @@ public function gitBranchLocation(): Attribute
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function gitWebhook(): Attribute
|
||||||
|
{
|
||||||
|
return Attribute::make(
|
||||||
|
get: function () {
|
||||||
|
if (!is_null($this->source?->html_url) && !is_null($this->git_repository) && !is_null($this->git_branch)) {
|
||||||
|
return "{$this->source->html_url}/{$this->git_repository}/settings/hooks";
|
||||||
|
}
|
||||||
|
return $this->git_repository;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
public function gitCommits(): Attribute
|
public function gitCommits(): Attribute
|
||||||
{
|
{
|
||||||
return Attribute::make(
|
return Attribute::make(
|
||||||
|
@ -29,6 +29,10 @@ public function send(SendsEmail $notifiable, Notification $notification): void
|
|||||||
->html((string)$mailMessage->render())
|
->html((string)$mailMessage->render())
|
||||||
);
|
);
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
|
$error = $e->getMessage();
|
||||||
|
if ($error === 'No email settings found.') {
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
ray($e->getMessage());
|
ray($e->getMessage());
|
||||||
$message = "EmailChannel error: {$e->getMessage()}. Failed to send email to:";
|
$message = "EmailChannel error: {$e->getMessage()}. Failed to send email to:";
|
||||||
if (isset($recepients)) {
|
if (isset($recepients)) {
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
use App\Models\Application;
|
use App\Models\Application;
|
||||||
use App\Models\ApplicationDeploymentQueue;
|
use App\Models\ApplicationDeploymentQueue;
|
||||||
|
|
||||||
function queue_application_deployment(int $application_id, string $deployment_uuid, int | null $pull_request_id = 0, string $commit = 'HEAD', bool $force_rebuild = false, bool $is_webhook = false, bool $restart_only = false)
|
function queue_application_deployment(int $application_id, string $deployment_uuid, int | null $pull_request_id = 0, string $commit = 'HEAD', bool $force_rebuild = false, bool $is_webhook = false, bool $restart_only = false, ?string $git_type = null)
|
||||||
{
|
{
|
||||||
$deployment = ApplicationDeploymentQueue::create([
|
$deployment = ApplicationDeploymentQueue::create([
|
||||||
'application_id' => $application_id,
|
'application_id' => $application_id,
|
||||||
@ -14,6 +14,7 @@ function queue_application_deployment(int $application_id, string $deployment_uu
|
|||||||
'is_webhook' => $is_webhook,
|
'is_webhook' => $is_webhook,
|
||||||
'restart_only' => $restart_only,
|
'restart_only' => $restart_only,
|
||||||
'commit' => $commit,
|
'commit' => $commit,
|
||||||
|
'git_type' => $git_type
|
||||||
]);
|
]);
|
||||||
$queued_deployments = ApplicationDeploymentQueue::where('application_id', $application_id)->where('status', 'queued')->get()->sortByDesc('created_at');
|
$queued_deployments = ApplicationDeploymentQueue::where('application_id', $application_id)->where('status', 'queued')->get()->sortByDesc('created_at');
|
||||||
$running_deployments = ApplicationDeploymentQueue::where('application_id', $application_id)->where('status', 'in_progress')->get()->sortByDesc('created_at');
|
$running_deployments = ApplicationDeploymentQueue::where('application_id', $application_id)->where('status', 'in_progress')->get()->sortByDesc('created_at');
|
||||||
|
@ -50,8 +50,11 @@ function generate_github_jwt_token(GithubApp $source)
|
|||||||
return $issuedToken;
|
return $issuedToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
function githubApi(GithubApp|GitlabApp $source, string $endpoint, string $method = 'get', array|null $data = null, bool $throwError = true)
|
function githubApi(GithubApp|GitlabApp|null $source, string $endpoint, string $method = 'get', array|null $data = null, bool $throwError = true)
|
||||||
{
|
{
|
||||||
|
if (is_null($source)) {
|
||||||
|
throw new \Exception('Not implemented yet.');
|
||||||
|
}
|
||||||
if ($source->getMorphClass() == 'App\Models\GithubApp') {
|
if ($source->getMorphClass() == 'App\Models\GithubApp') {
|
||||||
if ($source->is_public) {
|
if ($source->is_public) {
|
||||||
$response = Http::github($source->api_url)->$method($endpoint);
|
$response = Http::github($source->api_url)->$method($endpoint);
|
||||||
|
@ -510,6 +510,17 @@ function generateDeployWebhook($resource)
|
|||||||
$url = $api . $endpoint . "?uuid=$uuid&force=false";
|
$url = $api . $endpoint . "?uuid=$uuid&force=false";
|
||||||
return $url;
|
return $url;
|
||||||
}
|
}
|
||||||
|
function generateGitManualWebhook($resource, $type) {
|
||||||
|
if ($resource->source_id !== 0) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if ($resource->getMorphClass() === 'App\Models\Application') {
|
||||||
|
$baseUrl = base_url();
|
||||||
|
$api = Url::fromString($baseUrl) . "/webhooks/source/$type/events/manual";
|
||||||
|
return $api;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
function removeAnsiColors($text)
|
function removeAnsiColors($text)
|
||||||
{
|
{
|
||||||
return preg_replace('/\e[[][A-Za-z0-9];?[0-9]*m?/', '', $text);
|
return preg_replace('/\e[[][A-Za-z0-9];?[0-9]*m?/', '', $text);
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
// The release version of your application
|
// The release version of your application
|
||||||
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
|
// Example with dynamic git hash: trim(exec('git --git-dir ' . base_path('.git') . ' log --pretty="%h" -n1 HEAD'))
|
||||||
'release' => '4.0.0-beta.131',
|
'release' => '4.0.0-beta.132',
|
||||||
// When left empty or `null` the Laravel environment will be used
|
// When left empty or `null` the Laravel environment will be used
|
||||||
'environment' => config('app.env'),
|
'environment' => config('app.env'),
|
||||||
|
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
return '4.0.0-beta.131';
|
return '4.0.0-beta.132';
|
||||||
|
@ -0,0 +1,30 @@
|
|||||||
|
<?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->string('manual_webhook_secret_github')->nullable();
|
||||||
|
$table->string('manual_webhook_secret_gitlab')->nullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('applications', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('manual_webhook_secret_github');
|
||||||
|
$table->dropColumn('manual_webhook_secret_gitlab');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
34
database/migrations/2023_11_14_121416_add_git_type.php
Normal file
34
database/migrations/2023_11_14_121416_add_git_type.php
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
<?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('application_previews', function (Blueprint $table) {
|
||||||
|
$table->string('git_type')->nullable();
|
||||||
|
});
|
||||||
|
Schema::table('application_deployment_queues', function (Blueprint $table) {
|
||||||
|
$table->string('git_type')->nullable();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('application_previews', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('git_type');
|
||||||
|
});
|
||||||
|
Schema::table('application_deployment_queues', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('git_type');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
@ -69,10 +69,12 @@
|
|||||||
<div class="flex flex-col gap-2 xl:flex-row">
|
<div class="flex flex-col gap-2 xl:flex-row">
|
||||||
<x-forms.input placeholder="/" id="application.base_directory" label="Base Directory"
|
<x-forms.input placeholder="/" id="application.base_directory" label="Base Directory"
|
||||||
helper="Directory to use as root. Useful for monorepos." />
|
helper="Directory to use as root. Useful for monorepos." />
|
||||||
@if ($application->build_pack === 'dockerfile')
|
@if ($application->build_pack === 'dockerfile' && !$application->dockerfile)
|
||||||
<x-forms.input placeholder="/Dockerfile" id="application.dockerfile_location"
|
<x-forms.input placeholder="/Dockerfile" id="application.dockerfile_location"
|
||||||
label="Dockerfile Location"
|
label="Dockerfile Location"
|
||||||
helper="It is calculated together with the Base Directory: {{ Str::start($application->base_directory . $application->dockerfile_location, '/') }}" />
|
helper="It is calculated together with the Base Directory: {{ Str::start($application->base_directory . $application->dockerfile_location, '/') }}" />
|
||||||
|
@endif
|
||||||
|
@if ($application->build_pack === 'dockerfile')
|
||||||
<x-forms.input id="application.dockerfile_target_build" label="Docker Build Stage Target"
|
<x-forms.input id="application.dockerfile_target_build" label="Docker Build Stage Target"
|
||||||
helper="Useful if you have multi-staged dockerfile." />
|
helper="Useful if you have multi-staged dockerfile." />
|
||||||
@endif
|
@endif
|
||||||
|
@ -1,10 +1,41 @@
|
|||||||
<div>
|
<div class="flex flex-col gap-2">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<h2>Webhooks</h2>
|
<h2>Webhooks</h2>
|
||||||
<x-helper
|
<x-helper
|
||||||
helper="For more details goto our <a class='text-white underline' href='https://coolify.io/docs/api-endpoints' target='_blank'>docs</a>." />
|
helper="For more details goto our <a class='text-white underline' href='https://coolify.io/docs/api-endpoints' target='_blank'>docs</a>." />
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<x-forms.input readonly label="Deploy Webhook (auth required)" id="deploywebhook"></x-forms.input>
|
<x-forms.input readonly
|
||||||
|
helper="See details in our <a target='_blank' class='text-white underline' href='https://coolify.io/docs/api-authentication'>documentation</a>."
|
||||||
|
label="Deploy Webhook (auth required)" id="deploywebhook"></x-forms.input>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<h3>Manual Git Webhooks</h3>
|
||||||
|
@if ($githubManualWebhook && $gitlabManualWebhook)
|
||||||
|
<form wire:submit.prevent='saveSecret' class="flex flex-col gap-2">
|
||||||
|
<div class="flex items-end gap-2">
|
||||||
|
<x-forms.input helper="Content Type in GitHub configuration could be json or form-urlencoded."
|
||||||
|
readonly label="GitHub" id="githubManualWebhook"></x-forms.input>
|
||||||
|
<x-forms.input type="password"
|
||||||
|
helper="Need to set a secret to be able to use this webhook. It should match with the secret in GitHub."
|
||||||
|
label="GitHub Webhook Secret" id="resource.manual_webhook_secret_github"></x-forms.input>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
<a target="_blank" class="flex hover:no-underline" href="{{ $resource?->gitWebhook }}">
|
||||||
|
<x-forms.button>Webhook Configuration on GitHub
|
||||||
|
<x-external-link />
|
||||||
|
</x-forms.button>
|
||||||
|
</a>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<x-forms.input readonly label="GitLab" id="gitlabManualWebhook"></x-forms.input>
|
||||||
|
<x-forms.input type="password"
|
||||||
|
helper="Need to set a secret to be able to use this webhook. It should match with the secret in GitLab."
|
||||||
|
label="GitLab Webhook Secret" id="resource.manual_webhook_secret_gitlab"></x-forms.input>
|
||||||
|
</div>
|
||||||
|
<x-forms.button type="submit">Save</x-forms.button>
|
||||||
|
</form>
|
||||||
|
@else
|
||||||
|
You are using an official Git App. You do not need manual webhooks.
|
||||||
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -63,6 +63,244 @@
|
|||||||
return handleError($e);
|
return handleError($e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
Route::post('/source/gitlab/events/manual', function () {
|
||||||
|
try {
|
||||||
|
$payload = request()->collect();
|
||||||
|
$headers = request()->headers->all();
|
||||||
|
ray($payload, $headers);
|
||||||
|
$x_gitlab_token = data_get($headers, 'x-gitlab-token.0');
|
||||||
|
$x_gitlab_event = data_get($payload, 'object_kind');
|
||||||
|
if ($x_gitlab_event === 'push') {
|
||||||
|
$branch = data_get($payload, 'ref');
|
||||||
|
$full_name = data_get($payload, 'project.path_with_namespace');
|
||||||
|
if (Str::isMatch('/refs\/heads\/*/', $branch)) {
|
||||||
|
$branch = Str::after($branch, 'refs/heads/');
|
||||||
|
}
|
||||||
|
if (!$branch) {
|
||||||
|
return response('Nothing to do. No branch found in the request.');
|
||||||
|
}
|
||||||
|
ray('Manual Webhook GitLab Push Event with branch: ' . $branch);
|
||||||
|
}
|
||||||
|
if ($x_gitlab_event === 'merge_request') {
|
||||||
|
$action = data_get($payload, 'object_attributes.action');
|
||||||
|
ray($action);
|
||||||
|
$branch = data_get($payload, 'object_attributes.source_branch');
|
||||||
|
$base_branch = data_get($payload, 'object_attributes.target_branch');
|
||||||
|
$full_name = data_get($payload, 'project.path_with_namespace');
|
||||||
|
$pull_request_id = data_get($payload, 'object_attributes.iid');
|
||||||
|
$pull_request_html_url = data_get($payload, 'object_attributes.url');
|
||||||
|
if (!$branch) {
|
||||||
|
return response('Nothing to do. No branch found in the request.');
|
||||||
|
}
|
||||||
|
ray('Webhook GitHub Pull Request Event with branch: ' . $branch . ' and base branch: ' . $base_branch . ' and pull request id: ' . $pull_request_id);
|
||||||
|
}
|
||||||
|
$applications = Application::where('git_repository', 'like', "%$full_name%");
|
||||||
|
if ($x_gitlab_event === 'push') {
|
||||||
|
$applications = $applications->where('git_branch', $branch)->get();
|
||||||
|
if ($applications->isEmpty()) {
|
||||||
|
return response("Nothing to do. No applications found with deploy key set, branch is '$branch' and Git Repository name has $full_name.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($x_gitlab_event === 'merge_request') {
|
||||||
|
$applications = $applications->where('git_branch', $base_branch)->get();
|
||||||
|
if ($applications->isEmpty()) {
|
||||||
|
return response("Nothing to do. No applications found with branch '$base_branch'.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
foreach ($applications as $application) {
|
||||||
|
$webhook_secret = data_get($application, 'manual_webhook_secret_gitlab');
|
||||||
|
if ($webhook_secret !== $x_gitlab_token) {
|
||||||
|
ray('Invalid signature');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$isFunctional = $application->destination->server->isFunctional();
|
||||||
|
if (!$isFunctional) {
|
||||||
|
ray('Server is not functional: ' . $application->destination->server->name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($x_gitlab_event === 'push') {
|
||||||
|
if ($application->isDeployable()) {
|
||||||
|
ray('Deploying ' . $application->name . ' with branch ' . $branch);
|
||||||
|
$deployment_uuid = new Cuid2(7);
|
||||||
|
queue_application_deployment(
|
||||||
|
application_id: $application->id,
|
||||||
|
deployment_uuid: $deployment_uuid,
|
||||||
|
force_rebuild: false,
|
||||||
|
is_webhook: true
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ray('Deployments disabled for ' . $application->name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($x_gitlab_event === 'merge_request') {
|
||||||
|
if ($action === 'opened' || $action === 'synchronize' || $action === 'reopened' || $action === 'reopen' || $action === 'update') {
|
||||||
|
if ($application->isPRDeployable()) {
|
||||||
|
$deployment_uuid = new Cuid2(7);
|
||||||
|
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
||||||
|
if (!$found) {
|
||||||
|
ApplicationPreview::create([
|
||||||
|
'git_type' => 'gitlab',
|
||||||
|
'application_id' => $application->id,
|
||||||
|
'pull_request_id' => $pull_request_id,
|
||||||
|
'pull_request_html_url' => $pull_request_html_url,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
queue_application_deployment(
|
||||||
|
application_id: $application->id,
|
||||||
|
pull_request_id: $pull_request_id,
|
||||||
|
deployment_uuid: $deployment_uuid,
|
||||||
|
force_rebuild: false,
|
||||||
|
is_webhook: true,
|
||||||
|
git_type: 'gitlab'
|
||||||
|
);
|
||||||
|
ray('Deploying preview for ' . $application->name . ' with branch ' . $branch . ' and base branch ' . $base_branch . ' and pull request id ' . $pull_request_id);
|
||||||
|
return response('Preview Deployment queued.');
|
||||||
|
} else {
|
||||||
|
ray('Preview deployments disabled for ' . $application->name);
|
||||||
|
return response('Nothing to do. Preview Deployments disabled.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($action === 'closed') {
|
||||||
|
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
||||||
|
if ($found) {
|
||||||
|
$found->delete();
|
||||||
|
$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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
ray($e->getMessage());
|
||||||
|
return handleError($e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Route::post('/source/github/events/manual', function () {
|
||||||
|
try {
|
||||||
|
$x_github_event = Str::lower(request()->header('X-GitHub-Event'));
|
||||||
|
$x_hub_signature_256 = Str::after(request()->header('X-Hub-Signature-256'), 'sha256=');
|
||||||
|
$content_type = request()->header('Content-Type');
|
||||||
|
$payload = request()->collect();
|
||||||
|
if ($x_github_event === 'ping') {
|
||||||
|
// Just pong
|
||||||
|
return response('pong');
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($content_type !== 'application/json') {
|
||||||
|
$payload = json_decode(data_get($payload, 'payload'), true);
|
||||||
|
}
|
||||||
|
if ($x_github_event === 'push') {
|
||||||
|
$branch = data_get($payload, 'ref');
|
||||||
|
$full_name = data_get($payload, 'repository.full_name');
|
||||||
|
if (Str::isMatch('/refs\/heads\/*/', $branch)) {
|
||||||
|
$branch = Str::after($branch, 'refs/heads/');
|
||||||
|
}
|
||||||
|
ray('Manual Webhook GitHub Push Event with branch: ' . $branch);
|
||||||
|
}
|
||||||
|
if ($x_github_event === 'pull_request') {
|
||||||
|
$action = data_get($payload, 'action');
|
||||||
|
$full_name = data_get($payload, 'repository.full_name');
|
||||||
|
$pull_request_id = data_get($payload, 'number');
|
||||||
|
$pull_request_html_url = data_get($payload, 'pull_request.html_url');
|
||||||
|
$branch = data_get($payload, 'pull_request.head.ref');
|
||||||
|
$base_branch = data_get($payload, 'pull_request.base.ref');
|
||||||
|
ray('Webhook GitHub Pull Request Event with branch: ' . $branch . ' and base branch: ' . $base_branch . ' and pull request id: ' . $pull_request_id);
|
||||||
|
}
|
||||||
|
if (!$branch) {
|
||||||
|
return response('Nothing to do. No branch found in the request.');
|
||||||
|
}
|
||||||
|
$applications = Application::where('git_repository', 'like', "%$full_name%");
|
||||||
|
if ($x_github_event === 'push') {
|
||||||
|
$applications = $applications->where('git_branch', $branch)->get();
|
||||||
|
if ($applications->isEmpty()) {
|
||||||
|
return response("Nothing to do. No applications found with deploy key set, branch is '$branch' and Git Repository name has $full_name.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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'.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ray($applications);
|
||||||
|
foreach ($applications as $application) {
|
||||||
|
ray($application);
|
||||||
|
$webhook_secret = data_get($application, 'manual_webhook_secret_github');
|
||||||
|
ray($webhook_secret);
|
||||||
|
$hmac = hash_hmac('sha256', request()->getContent(), $webhook_secret);
|
||||||
|
ray($hmac, $x_hub_signature_256);
|
||||||
|
if (!hash_equals($x_hub_signature_256, $hmac)) {
|
||||||
|
ray('Invalid signature');
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
$isFunctional = $application->destination->server->isFunctional();
|
||||||
|
if (!$isFunctional) {
|
||||||
|
ray('Server is not functional: ' . $application->destination->server->name);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if ($x_github_event === 'push') {
|
||||||
|
if ($application->isDeployable()) {
|
||||||
|
ray('Deploying ' . $application->name . ' with branch ' . $branch);
|
||||||
|
$deployment_uuid = new Cuid2(7);
|
||||||
|
queue_application_deployment(
|
||||||
|
application_id: $application->id,
|
||||||
|
deployment_uuid: $deployment_uuid,
|
||||||
|
force_rebuild: false,
|
||||||
|
is_webhook: true
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
ray('Deployments disabled for ' . $application->name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($x_github_event === 'pull_request') {
|
||||||
|
if ($action === 'opened' || $action === 'synchronize' || $action === 'reopened') {
|
||||||
|
if ($application->isPRDeployable()) {
|
||||||
|
$deployment_uuid = new Cuid2(7);
|
||||||
|
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
||||||
|
if (!$found) {
|
||||||
|
ApplicationPreview::create([
|
||||||
|
'git_type' => 'github',
|
||||||
|
'application_id' => $application->id,
|
||||||
|
'pull_request_id' => $pull_request_id,
|
||||||
|
'pull_request_html_url' => $pull_request_html_url,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
queue_application_deployment(
|
||||||
|
application_id: $application->id,
|
||||||
|
pull_request_id: $pull_request_id,
|
||||||
|
deployment_uuid: $deployment_uuid,
|
||||||
|
force_rebuild: false,
|
||||||
|
is_webhook: true,
|
||||||
|
git_type: 'github'
|
||||||
|
);
|
||||||
|
ray('Deploying preview for ' . $application->name . ' with branch ' . $branch . ' and base branch ' . $base_branch . ' and pull request id ' . $pull_request_id);
|
||||||
|
return response('Preview Deployment queued.');
|
||||||
|
} else {
|
||||||
|
ray('Preview deployments disabled for ' . $application->name);
|
||||||
|
return response('Nothing to do. Preview Deployments disabled.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($action === 'closed') {
|
||||||
|
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
||||||
|
if ($found) {
|
||||||
|
$found->delete();
|
||||||
|
$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');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception $e) {
|
||||||
|
ray($e->getMessage());
|
||||||
|
return handleError($e);
|
||||||
|
}
|
||||||
|
});
|
||||||
Route::post('/source/github/events', function () {
|
Route::post('/source/github/events', function () {
|
||||||
try {
|
try {
|
||||||
$id = null;
|
$id = null;
|
||||||
@ -150,6 +388,7 @@
|
|||||||
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
||||||
if (!$found) {
|
if (!$found) {
|
||||||
ApplicationPreview::create([
|
ApplicationPreview::create([
|
||||||
|
'git_type' => 'github',
|
||||||
'application_id' => $application->id,
|
'application_id' => $application->id,
|
||||||
'pull_request_id' => $pull_request_id,
|
'pull_request_id' => $pull_request_id,
|
||||||
'pull_request_html_url' => $pull_request_html_url,
|
'pull_request_html_url' => $pull_request_html_url,
|
||||||
@ -160,7 +399,8 @@
|
|||||||
pull_request_id: $pull_request_id,
|
pull_request_id: $pull_request_id,
|
||||||
deployment_uuid: $deployment_uuid,
|
deployment_uuid: $deployment_uuid,
|
||||||
force_rebuild: false,
|
force_rebuild: false,
|
||||||
is_webhook: true
|
is_webhook: true,
|
||||||
|
git_type: 'github'
|
||||||
);
|
);
|
||||||
ray('Deploying preview for ' . $application->name . ' with branch ' . $branch . ' and base branch ' . $base_branch . ' and pull request id ' . $pull_request_id);
|
ray('Deploying preview for ' . $application->name . ' with branch ' . $branch . ' and base branch ' . $base_branch . ' and pull request id ' . $pull_request_id);
|
||||||
return response('Preview Deployment queued.');
|
return response('Preview Deployment queued.');
|
||||||
@ -169,7 +409,7 @@
|
|||||||
return response('Nothing to do. Preview Deployments disabled.');
|
return response('Nothing to do. Preview Deployments disabled.');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if ($action === 'closed') {
|
if ($action === 'closed' || $action === 'close') {
|
||||||
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
$found = ApplicationPreview::where('application_id', $application->id)->where('pull_request_id', $pull_request_id)->first();
|
||||||
if ($found) {
|
if ($found) {
|
||||||
$found->delete();
|
$found->delete();
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
"version": "3.12.36"
|
"version": "3.12.36"
|
||||||
},
|
},
|
||||||
"v4": {
|
"v4": {
|
||||||
"version": "4.0.0-beta.131"
|
"version": "4.0.0-beta.132"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user