mirror of
https://github.com/tiennm99/coolify.git
synced 2026-10-14 10:18:20 +00:00
Merge remote-tracking branch 'origin/next' into audit-policies
# Conflicts: # app/Http/Controllers/Api/SecurityController.php # app/Http/Controllers/Api/ServersController.php # app/Livewire/Admin/Index.php # app/Livewire/Destination/Show.php # app/Livewire/NavbarDeleteTeam.php # app/Livewire/Project/Application/Previews.php # app/Livewire/Project/DeleteProject.php # app/Livewire/Project/Shared/ResourceOperations.php # app/Livewire/Server/Resources.php # app/Livewire/Server/ValidateAndInstall.php # app/Livewire/Storage/Show.php # resources/views/livewire/dashboard.blade.php # resources/views/livewire/project/application/heading.blade.php # resources/views/livewire/project/database/heading.blade.php # resources/views/livewire/project/service/heading.blade.php # resources/views/livewire/project/shared/environment-variable/show.blade.php # resources/views/livewire/project/shared/scheduled-task/show.blade.php # tests/Feature/Security/TrustHostsMiddlewareTest.php
This commit is contained in:
commit
43f33d94ad
685 files changed
+41904
-5674
No files matched your search
@@ -3,12 +3,14 @@
|
||||
namespace App\Livewire\Project\Application;
|
||||
|
||||
use App\Actions\Application\GenerateConfig;
|
||||
use App\Jobs\ApplicationDeploymentJob;
|
||||
use App\Models\Application;
|
||||
use App\Support\ValidationPatterns;
|
||||
use Illuminate\Auth\Access\AuthorizationException;
|
||||
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||
use Illuminate\Support\Collection;
|
||||
use Livewire\Attributes\Validate;
|
||||
use Livewire\Component;
|
||||
use Livewire\Features\SupportEvents\Event;
|
||||
use Spatie\Url\Url;
|
||||
use Visus\Cuid2\Cuid2;
|
||||
|
||||
@@ -22,136 +24,95 @@ class General extends Component
|
||||
|
||||
public Collection $services;
|
||||
|
||||
#[Validate('required|regex:/^[a-zA-Z0-9\s\-_.\/:()]+$/')]
|
||||
public string $name;
|
||||
|
||||
#[Validate(['string', 'nullable'])]
|
||||
public ?string $description = null;
|
||||
|
||||
#[Validate(['nullable'])]
|
||||
public ?string $fqdn = null;
|
||||
|
||||
#[Validate(['required'])]
|
||||
public string $gitRepository;
|
||||
|
||||
#[Validate(['required'])]
|
||||
public string $gitBranch;
|
||||
|
||||
#[Validate(['string', 'nullable'])]
|
||||
public ?string $gitCommitSha = null;
|
||||
|
||||
#[Validate(['string', 'nullable'])]
|
||||
public ?string $installCommand = null;
|
||||
|
||||
#[Validate(['string', 'nullable'])]
|
||||
public ?string $buildCommand = null;
|
||||
|
||||
#[Validate(['string', 'nullable'])]
|
||||
public ?string $startCommand = null;
|
||||
|
||||
#[Validate(['required'])]
|
||||
public string $buildPack;
|
||||
|
||||
#[Validate(['required'])]
|
||||
public string $staticImage;
|
||||
|
||||
#[Validate(['required'])]
|
||||
public string $baseDirectory;
|
||||
|
||||
#[Validate(['string', 'nullable'])]
|
||||
public ?string $publishDirectory = null;
|
||||
|
||||
#[Validate(['string', 'nullable'])]
|
||||
public ?string $portsExposes = null;
|
||||
|
||||
#[Validate(['string', 'nullable'])]
|
||||
public ?string $portsMappings = null;
|
||||
|
||||
#[Validate(['string', 'nullable'])]
|
||||
public ?string $customNetworkAliases = null;
|
||||
|
||||
#[Validate(['string', 'nullable'])]
|
||||
public ?string $dockerfile = null;
|
||||
|
||||
#[Validate(['string', 'nullable', 'max:255', 'regex:/^\/[a-zA-Z0-9._\-\/]+$/'])]
|
||||
public ?string $dockerfileLocation = null;
|
||||
|
||||
#[Validate(['string', 'nullable'])]
|
||||
public ?string $dockerfileTargetBuild = null;
|
||||
|
||||
#[Validate(['string', 'nullable'])]
|
||||
public ?string $dockerRegistryImageName = null;
|
||||
|
||||
#[Validate(['string', 'nullable'])]
|
||||
public ?string $dockerRegistryImageTag = null;
|
||||
|
||||
#[Validate(['string', 'nullable', 'max:255', 'regex:/^\/[a-zA-Z0-9._\-\/]+$/'])]
|
||||
public ?string $dockerComposeLocation = null;
|
||||
|
||||
#[Validate(['string', 'nullable'])]
|
||||
public ?string $dockerCompose = null;
|
||||
|
||||
#[Validate(['string', 'nullable'])]
|
||||
public ?string $dockerComposeRaw = null;
|
||||
|
||||
#[Validate(['string', 'nullable'])]
|
||||
public ?string $dockerComposeCustomStartCommand = null;
|
||||
|
||||
#[Validate(['string', 'nullable'])]
|
||||
public ?string $dockerComposeCustomBuildCommand = null;
|
||||
|
||||
#[Validate(['string', 'nullable'])]
|
||||
public ?string $customDockerRunOptions = null;
|
||||
|
||||
#[Validate(['string', 'nullable'])]
|
||||
// Security: pre/post deployment commands are intentionally arbitrary shell — users need full
|
||||
// flexibility (e.g. "php artisan migrate"). Access is gated by team authentication/authorization.
|
||||
// Commands execute inside the application's own container, not on the host.
|
||||
public ?string $preDeploymentCommand = null;
|
||||
|
||||
#[Validate(['string', 'nullable'])]
|
||||
public ?string $preDeploymentCommandContainer = null;
|
||||
|
||||
#[Validate(['string', 'nullable'])]
|
||||
public ?string $postDeploymentCommand = null;
|
||||
|
||||
#[Validate(['string', 'nullable'])]
|
||||
public ?string $postDeploymentCommandContainer = null;
|
||||
|
||||
#[Validate(['string', 'nullable'])]
|
||||
public ?string $customNginxConfiguration = null;
|
||||
|
||||
#[Validate(['boolean', 'required'])]
|
||||
public bool $isStatic = false;
|
||||
|
||||
#[Validate(['boolean', 'required'])]
|
||||
public bool $isSpa = false;
|
||||
|
||||
#[Validate(['boolean', 'required'])]
|
||||
public bool $isBuildServerEnabled = false;
|
||||
|
||||
#[Validate(['boolean', 'required'])]
|
||||
public bool $isPreserveRepositoryEnabled = false;
|
||||
|
||||
#[Validate(['boolean', 'required'])]
|
||||
public bool $isContainerLabelEscapeEnabled = true;
|
||||
|
||||
#[Validate(['boolean', 'required'])]
|
||||
public bool $isContainerLabelReadonlyEnabled = false;
|
||||
|
||||
#[Validate(['boolean', 'required'])]
|
||||
public bool $isHttpBasicAuthEnabled = false;
|
||||
|
||||
#[Validate(['string', 'nullable'])]
|
||||
public ?string $httpBasicAuthUsername = null;
|
||||
|
||||
#[Validate(['string', 'nullable'])]
|
||||
public ?string $httpBasicAuthPassword = null;
|
||||
|
||||
#[Validate(['nullable'])]
|
||||
public ?string $watchPaths = null;
|
||||
|
||||
#[Validate(['string', 'required'])]
|
||||
public string $redirect;
|
||||
|
||||
#[Validate(['nullable'])]
|
||||
public $customLabels;
|
||||
|
||||
public bool $labelsChanged = false;
|
||||
@@ -184,33 +145,33 @@ class General extends Component
|
||||
'fqdn' => 'nullable',
|
||||
'gitRepository' => 'required',
|
||||
'gitBranch' => 'required',
|
||||
'gitCommitSha' => 'nullable',
|
||||
'installCommand' => 'nullable',
|
||||
'buildCommand' => 'nullable',
|
||||
'startCommand' => 'nullable',
|
||||
'gitCommitSha' => ['nullable', 'string', 'regex:/^[a-zA-Z0-9][a-zA-Z0-9._\-\/]*$/'],
|
||||
'installCommand' => ValidationPatterns::shellSafeCommandRules(),
|
||||
'buildCommand' => ValidationPatterns::shellSafeCommandRules(),
|
||||
'startCommand' => ValidationPatterns::shellSafeCommandRules(),
|
||||
'buildPack' => 'required',
|
||||
'staticImage' => 'required',
|
||||
'baseDirectory' => 'required',
|
||||
'publishDirectory' => 'nullable',
|
||||
'portsExposes' => 'required',
|
||||
'portsMappings' => 'nullable',
|
||||
'baseDirectory' => array_merge(['required'], array_slice(ValidationPatterns::directoryPathRules(), 1)),
|
||||
'publishDirectory' => ValidationPatterns::directoryPathRules(),
|
||||
'portsExposes' => ['required', 'string', 'regex:/^(\d+)(,\d+)*$/'],
|
||||
'portsMappings' => ValidationPatterns::portMappingRules(),
|
||||
'customNetworkAliases' => 'nullable',
|
||||
'dockerfile' => 'nullable',
|
||||
'dockerRegistryImageName' => 'nullable',
|
||||
'dockerRegistryImageTag' => 'nullable',
|
||||
'dockerfileLocation' => 'nullable',
|
||||
'dockerComposeLocation' => 'nullable',
|
||||
'dockerfileLocation' => ValidationPatterns::filePathRules(),
|
||||
'dockerComposeLocation' => ValidationPatterns::filePathRules(),
|
||||
'dockerCompose' => 'nullable',
|
||||
'dockerComposeRaw' => 'nullable',
|
||||
'dockerfileTargetBuild' => 'nullable',
|
||||
'dockerComposeCustomStartCommand' => 'nullable',
|
||||
'dockerComposeCustomBuildCommand' => 'nullable',
|
||||
'dockerfileTargetBuild' => ValidationPatterns::dockerTargetRules(),
|
||||
'dockerComposeCustomStartCommand' => ValidationPatterns::shellSafeCommandRules(),
|
||||
'dockerComposeCustomBuildCommand' => ValidationPatterns::shellSafeCommandRules(),
|
||||
'customLabels' => 'nullable',
|
||||
'customDockerRunOptions' => 'nullable',
|
||||
'customDockerRunOptions' => ValidationPatterns::shellSafeCommandRules(2000),
|
||||
'preDeploymentCommand' => 'nullable',
|
||||
'preDeploymentCommandContainer' => 'nullable',
|
||||
'preDeploymentCommandContainer' => ['nullable', ...ValidationPatterns::containerNameRules()],
|
||||
'postDeploymentCommand' => 'nullable',
|
||||
'postDeploymentCommandContainer' => 'nullable',
|
||||
'postDeploymentCommandContainer' => ['nullable', ...ValidationPatterns::containerNameRules()],
|
||||
'customNginxConfiguration' => 'nullable',
|
||||
'isStatic' => 'boolean|required',
|
||||
'isSpa' => 'boolean|required',
|
||||
@@ -231,6 +192,19 @@ class General extends Component
|
||||
return array_merge(
|
||||
ValidationPatterns::combinedMessages(),
|
||||
[
|
||||
...ValidationPatterns::filePathMessages('dockerfileLocation', 'Dockerfile'),
|
||||
...ValidationPatterns::filePathMessages('dockerComposeLocation', 'Docker Compose'),
|
||||
'baseDirectory.regex' => 'The base directory must be a valid path starting with / and containing only safe characters.',
|
||||
'publishDirectory.regex' => 'The publish directory must be a valid path starting with / and containing only safe characters.',
|
||||
'dockerfileTargetBuild.regex' => 'The Dockerfile target build must contain only alphanumeric characters, dots, hyphens, and underscores.',
|
||||
'dockerComposeCustomStartCommand.regex' => 'The Docker Compose start command contains invalid characters. Shell operators like ;, |, $, and backticks are not allowed.',
|
||||
'dockerComposeCustomBuildCommand.regex' => 'The Docker Compose build command contains invalid characters. Shell operators like ;, |, $, and backticks are not allowed.',
|
||||
'customDockerRunOptions.regex' => 'The custom Docker run options contain invalid characters. Shell operators like ;, |, $, and backticks are not allowed.',
|
||||
'installCommand.regex' => 'The install command contains invalid characters. Shell operators like ;, |, $, and backticks are not allowed.',
|
||||
'buildCommand.regex' => 'The build command contains invalid characters. Shell operators like ;, |, $, and backticks are not allowed.',
|
||||
'startCommand.regex' => 'The start command contains invalid characters. Shell operators like ;, |, $, and backticks are not allowed.',
|
||||
'preDeploymentCommandContainer.regex' => 'The pre-deployment command container name must contain only alphanumeric characters, dots, hyphens, and underscores.',
|
||||
'postDeploymentCommandContainer.regex' => 'The post-deployment command container name must contain only alphanumeric characters, dots, hyphens, and underscores.',
|
||||
'name.required' => 'The Name field is required.',
|
||||
'gitRepository.required' => 'The Git Repository field is required.',
|
||||
'gitBranch.required' => 'The Git Branch field is required.',
|
||||
@@ -238,6 +212,8 @@ class General extends Component
|
||||
'staticImage.required' => 'The Static Image field is required.',
|
||||
'baseDirectory.required' => 'The Base Directory field is required.',
|
||||
'portsExposes.required' => 'The Exposed Ports field is required.',
|
||||
'portsExposes.regex' => 'Ports exposes must be a comma-separated list of port numbers (e.g. 3000,3001).',
|
||||
...ValidationPatterns::portMappingMessages(),
|
||||
'isStatic.required' => 'The Static setting is required.',
|
||||
'isStatic.boolean' => 'The Static setting must be true or false.',
|
||||
'isSpa.required' => 'The SPA setting is required.',
|
||||
@@ -320,7 +296,7 @@ class General extends Component
|
||||
$this->authorize('update', $this->application);
|
||||
$this->application->fqdn = null;
|
||||
$this->application->settings->save();
|
||||
} catch (\Illuminate\Auth\Access\AuthorizationException $e) {
|
||||
} catch (AuthorizationException $e) {
|
||||
// User doesn't have update permission, just continue without saving
|
||||
}
|
||||
}
|
||||
@@ -341,7 +317,7 @@ class General extends Component
|
||||
$this->customLabels = str(implode('|coolify|', generateLabelsApplication($this->application)))->replace('|coolify|', "\n");
|
||||
$this->application->custom_labels = base64_encode($this->customLabels);
|
||||
$this->application->save();
|
||||
} catch (\Illuminate\Auth\Access\AuthorizationException $e) {
|
||||
} catch (AuthorizationException $e) {
|
||||
// User doesn't have update permission, just use existing labels
|
||||
// $this->customLabels = str(implode('|coolify|', generateLabelsApplication($this->application)))->replace('|coolify|', "\n");
|
||||
}
|
||||
@@ -353,7 +329,7 @@ class General extends Component
|
||||
$this->authorize('update', $this->application);
|
||||
$this->initLoadingCompose = true;
|
||||
$this->dispatch('info', 'Loading docker compose file.');
|
||||
} catch (\Illuminate\Auth\Access\AuthorizationException $e) {
|
||||
} catch (AuthorizationException $e) {
|
||||
// User doesn't have update permission, skip loading compose file
|
||||
}
|
||||
}
|
||||
@@ -619,7 +595,7 @@ class General extends Component
|
||||
// Check if user has permission to update
|
||||
try {
|
||||
$this->authorize('update', $this->application);
|
||||
} catch (\Illuminate\Auth\Access\AuthorizationException $e) {
|
||||
} catch (AuthorizationException $e) {
|
||||
// User doesn't have permission, revert the change and return
|
||||
$this->application->refresh();
|
||||
$this->syncData();
|
||||
@@ -644,7 +620,7 @@ class General extends Component
|
||||
$this->fqdn = null;
|
||||
$this->application->fqdn = null;
|
||||
$this->application->settings->save();
|
||||
} catch (\Illuminate\Auth\Access\AuthorizationException $e) {
|
||||
} catch (AuthorizationException $e) {
|
||||
// User doesn't have update permission, just continue without saving
|
||||
}
|
||||
}
|
||||
@@ -761,6 +737,7 @@ class General extends Component
|
||||
$this->authorize('update', $this->application);
|
||||
|
||||
try {
|
||||
$this->application->redirect = $this->redirect;
|
||||
$has_www = collect($this->application->fqdns)->filter(fn ($fqdn) => str($fqdn)->contains('www.'))->count();
|
||||
if ($has_www === 0 && $this->application->redirect === 'www') {
|
||||
$this->dispatch('error', 'You want to redirect to www, but you do not have a www domain set.<br><br>Please add www to your domain list and as an A DNS record (if applicable).');
|
||||
@@ -781,6 +758,12 @@ class General extends Component
|
||||
$this->authorize('update', $this->application);
|
||||
|
||||
$this->resetErrorBag();
|
||||
|
||||
$this->portsExposes = str($this->portsExposes)->replace(' ', '')->trim()->toString();
|
||||
if ($this->portsMappings) {
|
||||
$this->portsMappings = str($this->portsMappings)->replace(' ', '')->trim()->toString();
|
||||
}
|
||||
|
||||
$this->validate();
|
||||
|
||||
$oldPortsExposes = $this->application->ports_exposes;
|
||||
@@ -841,7 +824,7 @@ class General extends Component
|
||||
restoreBaseDirectory: $oldBaseDirectory,
|
||||
restoreDockerComposeLocation: $oldDockerComposeLocation
|
||||
);
|
||||
if ($compose_return instanceof \Livewire\Features\SupportEvents\Event) {
|
||||
if ($compose_return instanceof Event) {
|
||||
// Validation failed - restore original values to component properties
|
||||
$this->baseDirectory = $oldBaseDirectory;
|
||||
$this->dockerComposeLocation = $oldDockerComposeLocation;
|
||||
@@ -971,7 +954,7 @@ class General extends Component
|
||||
$command = injectDockerComposeFlags(
|
||||
$this->dockerComposeCustomBuildCommand,
|
||||
".{$normalizedBase}{$this->dockerComposeLocation}",
|
||||
\App\Jobs\ApplicationDeploymentJob::BUILD_TIME_ENV_PATH
|
||||
ApplicationDeploymentJob::BUILD_TIME_ENV_PATH
|
||||
);
|
||||
|
||||
// Inject build args if not using build secrets
|
||||
|
||||
@@ -35,8 +35,17 @@ class Previews extends Component
|
||||
|
||||
public array $previewFqdns = [];
|
||||
|
||||
public array $previewDockerTags = [];
|
||||
|
||||
public ?int $manualPullRequestId = null;
|
||||
|
||||
public ?string $manualDockerTag = null;
|
||||
|
||||
protected $rules = [
|
||||
'previewFqdns.*' => 'string|nullable',
|
||||
'previewDockerTags.*' => 'string|nullable',
|
||||
'manualPullRequestId' => 'integer|min:1|nullable',
|
||||
'manualDockerTag' => 'string|nullable',
|
||||
];
|
||||
|
||||
public function mount()
|
||||
@@ -53,12 +62,17 @@ class Previews extends Component
|
||||
$preview = $this->application->previews->get($key);
|
||||
if ($preview) {
|
||||
$preview->fqdn = $fqdn;
|
||||
if ($this->application->build_pack === 'dockerimage') {
|
||||
$preview->docker_registry_image_tag = $this->previewDockerTags[$key] ?? null;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$this->previewFqdns = [];
|
||||
$this->previewDockerTags = [];
|
||||
foreach ($this->application->previews as $key => $preview) {
|
||||
$this->previewFqdns[$key] = $preview->fqdn;
|
||||
$this->previewDockerTags[$key] = $preview->docker_registry_image_tag;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -174,7 +188,7 @@ class Previews extends Component
|
||||
}
|
||||
}
|
||||
|
||||
public function add(int $pull_request_id, ?string $pull_request_html_url = null)
|
||||
public function add(int $pull_request_id, ?string $pull_request_html_url = null, ?string $docker_registry_image_tag = null)
|
||||
{
|
||||
try {
|
||||
$this->authorize('update', $this->application);
|
||||
@@ -195,13 +209,18 @@ class Previews extends Component
|
||||
} else {
|
||||
$this->setDeploymentUuid();
|
||||
$found = ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->first();
|
||||
if (! $found && ! is_null($pull_request_html_url)) {
|
||||
if (! $found && (! is_null($pull_request_html_url) || ($this->application->build_pack === 'dockerimage' && str($docker_registry_image_tag)->isNotEmpty()))) {
|
||||
$found = ApplicationPreview::create([
|
||||
'application_id' => $this->application->id,
|
||||
'pull_request_id' => $pull_request_id,
|
||||
'pull_request_html_url' => $pull_request_html_url,
|
||||
'pull_request_html_url' => $pull_request_html_url ?? '',
|
||||
'docker_registry_image_tag' => $docker_registry_image_tag,
|
||||
]);
|
||||
}
|
||||
if ($found && $this->application->build_pack === 'dockerimage' && str($docker_registry_image_tag)->isNotEmpty()) {
|
||||
$found->docker_registry_image_tag = $docker_registry_image_tag;
|
||||
$found->save();
|
||||
}
|
||||
$found->generate_preview_fqdn();
|
||||
$this->application->refresh();
|
||||
$this->syncData(false);
|
||||
@@ -218,43 +237,56 @@ class Previews extends Component
|
||||
try {
|
||||
$this->authorize('deploy', $this->application);
|
||||
|
||||
$this->deploy($pull_request_id, $pull_request_html_url, force_rebuild: true);
|
||||
$dockerRegistryImageTag = null;
|
||||
if ($this->application->build_pack === 'dockerimage') {
|
||||
$dockerRegistryImageTag = $this->application->previews()
|
||||
->where('pull_request_id', $pull_request_id)
|
||||
->value('docker_registry_image_tag');
|
||||
}
|
||||
|
||||
$this->deploy($pull_request_id, $pull_request_html_url, force_rebuild: true, docker_registry_image_tag: $dockerRegistryImageTag);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function add_and_deploy(int $pull_request_id, ?string $pull_request_html_url = null)
|
||||
public function add_and_deploy(int $pull_request_id, ?string $pull_request_html_url = null, ?string $docker_registry_image_tag = null)
|
||||
{
|
||||
try {
|
||||
$this->authorize('deploy', $this->application);
|
||||
|
||||
$this->add($pull_request_id, $pull_request_html_url);
|
||||
$this->deploy($pull_request_id, $pull_request_html_url);
|
||||
$this->add($pull_request_id, $pull_request_html_url, $docker_registry_image_tag);
|
||||
$this->deploy($pull_request_id, $pull_request_html_url, force_rebuild: false, docker_registry_image_tag: $docker_registry_image_tag);
|
||||
} catch (\Throwable $e) {
|
||||
return handleError($e, $this);
|
||||
}
|
||||
}
|
||||
|
||||
public function deploy(int $pull_request_id, ?string $pull_request_html_url = null, bool $force_rebuild = false)
|
||||
public function deploy(int $pull_request_id, ?string $pull_request_html_url = null, bool $force_rebuild = false, ?string $docker_registry_image_tag = null)
|
||||
{
|
||||
try {
|
||||
$this->authorize('deploy', $this->application);
|
||||
$this->setDeploymentUuid();
|
||||
$found = ApplicationPreview::where('application_id', $this->application->id)->where('pull_request_id', $pull_request_id)->first();
|
||||
if (! $found && ! is_null($pull_request_html_url)) {
|
||||
ApplicationPreview::create([
|
||||
if (! $found && (! is_null($pull_request_html_url) || ($this->application->build_pack === 'dockerimage' && str($docker_registry_image_tag)->isNotEmpty()))) {
|
||||
$found = ApplicationPreview::create([
|
||||
'application_id' => $this->application->id,
|
||||
'pull_request_id' => $pull_request_id,
|
||||
'pull_request_html_url' => $pull_request_html_url,
|
||||
'pull_request_html_url' => $pull_request_html_url ?? '',
|
||||
'docker_registry_image_tag' => $docker_registry_image_tag,
|
||||
]);
|
||||
}
|
||||
if ($found && $this->application->build_pack === 'dockerimage' && str($docker_registry_image_tag)->isNotEmpty()) {
|
||||
$found->docker_registry_image_tag = $docker_registry_image_tag;
|
||||
$found->save();
|
||||
}
|
||||
$result = queue_application_deployment(
|
||||
application: $this->application,
|
||||
deployment_uuid: $this->deployment_uuid,
|
||||
force_rebuild: $force_rebuild,
|
||||
pull_request_id: $pull_request_id,
|
||||
git_type: $found->git_type ?? null,
|
||||
docker_registry_image_tag: $docker_registry_image_tag,
|
||||
);
|
||||
if ($result['status'] === 'queue_full') {
|
||||
$this->dispatch('error', 'Deployment queue full', $result['message']);
|
||||
@@ -284,6 +316,32 @@ class Previews extends Component
|
||||
$this->parameters['deployment_uuid'] = $this->deployment_uuid;
|
||||
}
|
||||
|
||||
public function addDockerImagePreview()
|
||||
{
|
||||
$this->authorize('deploy', $this->application);
|
||||
$this->validateOnly('manualPullRequestId');
|
||||
$this->validateOnly('manualDockerTag');
|
||||
|
||||
if ($this->application->build_pack !== 'dockerimage') {
|
||||
$this->dispatch('error', 'Manual Docker Image previews are only available for Docker Image applications.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($this->manualPullRequestId === null || str($this->manualDockerTag)->isEmpty()) {
|
||||
$this->dispatch('error', 'Both pull request id and docker tag are required.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$dockerTag = str($this->manualDockerTag)->trim()->value();
|
||||
|
||||
$this->add_and_deploy($this->manualPullRequestId, null, $dockerTag);
|
||||
|
||||
$this->manualPullRequestId = null;
|
||||
$this->manualDockerTag = null;
|
||||
}
|
||||
|
||||
private function stopContainers(array $containers, $server)
|
||||
{
|
||||
$containersToStop = collect($containers)->pluck('Names')->toArray();
|
||||
|
||||
@@ -50,6 +50,8 @@ class Rollback extends Component
|
||||
{
|
||||
$this->authorize('deploy', $this->application);
|
||||
|
||||
$commit = validateGitRef($commit, 'rollback commit');
|
||||
|
||||
$deployment_uuid = new Cuid2;
|
||||
|
||||
$result = queue_application_deployment(
|
||||
|
||||
@@ -30,7 +30,7 @@ class Source extends Component
|
||||
#[Validate(['required', 'string'])]
|
||||
public string $gitBranch;
|
||||
|
||||
#[Validate(['nullable', 'string'])]
|
||||
#[Validate(['nullable', 'string', 'regex:/^[a-zA-Z0-9][a-zA-Z0-9._\-\/]*$/'])]
|
||||
public ?string $gitCommitSha = null;
|
||||
|
||||
#[Locked]
|
||||
|
||||
Reference in new issue
Block a user