feat: push locally built image to docker registry
ui: fixes here and there
This commit is contained in:
parent
e33fec0e1a
commit
f88e3c5b29
@ -38,10 +38,10 @@ public function rollbackImage($commit)
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function loadImages()
|
public function loadImages($showToast = false)
|
||||||
{
|
{
|
||||||
try {
|
try {
|
||||||
$image = $this->application->uuid;
|
$image = $this->application->docker_registry_image_name ?? $this->application->uuid;
|
||||||
if ($this->application->destination->server->isFunctional()) {
|
if ($this->application->destination->server->isFunctional()) {
|
||||||
$output = instant_remote_process([
|
$output = instant_remote_process([
|
||||||
"docker inspect --format='{{.Config.Image}}' {$this->application->uuid}",
|
"docker inspect --format='{{.Config.Image}}' {$this->application->uuid}",
|
||||||
@ -66,6 +66,7 @@ public function loadImages()
|
|||||||
];
|
];
|
||||||
})->toArray();
|
})->toArray();
|
||||||
}
|
}
|
||||||
|
$showToast && $this->emit('success', 'Images loaded.');
|
||||||
return [];
|
return [];
|
||||||
} catch (\Throwable $e) {
|
} catch (\Throwable $e) {
|
||||||
return handleError($e, $this);
|
return handleError($e, $this);
|
||||||
|
@ -24,6 +24,7 @@
|
|||||||
use Illuminate\Queue\SerializesModels;
|
use Illuminate\Queue\SerializesModels;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Str;
|
use Illuminate\Support\Str;
|
||||||
|
use RuntimeException;
|
||||||
use Spatie\Url\Url;
|
use Spatie\Url\Url;
|
||||||
use Symfony\Component\Yaml\Yaml;
|
use Symfony\Component\Yaml\Yaml;
|
||||||
use Throwable;
|
use Throwable;
|
||||||
@ -197,6 +198,12 @@ public function handle(): void
|
|||||||
try {
|
try {
|
||||||
if ($this->restart_only && $this->application->build_pack !== 'dockerimage') {
|
if ($this->restart_only && $this->application->build_pack !== 'dockerimage') {
|
||||||
$this->just_restart();
|
$this->just_restart();
|
||||||
|
if ($this->server->isProxyShouldRun()) {
|
||||||
|
dispatch(new ContainerStatusJob($this->server));
|
||||||
|
}
|
||||||
|
$this->next(ApplicationDeploymentStatus::FINISHED->value);
|
||||||
|
$this->application->isConfigurationChanged(true);
|
||||||
|
return;
|
||||||
} else if ($this->application->dockerfile) {
|
} else if ($this->application->dockerfile) {
|
||||||
$this->deploy_simple_dockerfile();
|
$this->deploy_simple_dockerfile();
|
||||||
} else if ($this->application->build_pack === 'dockerimage') {
|
} else if ($this->application->build_pack === 'dockerimage') {
|
||||||
@ -215,6 +222,9 @@ public function handle(): void
|
|||||||
if ($this->server->isProxyShouldRun()) {
|
if ($this->server->isProxyShouldRun()) {
|
||||||
dispatch(new ContainerStatusJob($this->server));
|
dispatch(new ContainerStatusJob($this->server));
|
||||||
}
|
}
|
||||||
|
if ($this->application->docker_registry_image_name) {
|
||||||
|
$this->push_to_docker_registry();
|
||||||
|
}
|
||||||
$this->next(ApplicationDeploymentStatus::FINISHED->value);
|
$this->next(ApplicationDeploymentStatus::FINISHED->value);
|
||||||
$this->application->isConfigurationChanged(true);
|
$this->application->isConfigurationChanged(true);
|
||||||
} catch (Exception $e) {
|
} catch (Exception $e) {
|
||||||
@ -255,7 +265,38 @@ public function handle(): void
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
private function push_to_docker_registry()
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
instant_remote_process(["docker images --format '{{json .}}' {$this->production_image_name}"], $this->server);
|
||||||
|
$this->execute_remote_command(
|
||||||
|
[
|
||||||
|
"echo '\n----------------------------------------'",
|
||||||
|
],
|
||||||
|
["echo -n 'Pushing image to docker registry ({$this->production_image_name}).'"],
|
||||||
|
[
|
||||||
|
executeInDocker($this->deployment_uuid, "docker push {$this->production_image_name}"), 'ignore_errors' => true, 'hidden' => true
|
||||||
|
],
|
||||||
|
);
|
||||||
|
if ($this->application->docker_registry_image_tag) {
|
||||||
|
// Tag image with latest
|
||||||
|
$this->execute_remote_command(
|
||||||
|
['echo -n "Tagging and pushing image with latest tag."'],
|
||||||
|
[
|
||||||
|
executeInDocker($this->deployment_uuid, "docker tag {$this->production_image_name} {$this->application->docker_registry_image_name}:{$this->application->docker_registry_image_tag}"), 'ignore_errors' => true, 'hidden' => true
|
||||||
|
],
|
||||||
|
[
|
||||||
|
executeInDocker($this->deployment_uuid, "docker push {$this->application->docker_registry_image_name}:{$this->application->docker_registry_image_tag}"), 'ignore_errors' => true, 'hidden' => true
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
$this->execute_remote_command([
|
||||||
|
"echo -n 'Image pushed to docker registry.'"
|
||||||
|
]);
|
||||||
|
} catch (Exception $e) {
|
||||||
|
ray($e);
|
||||||
|
}
|
||||||
|
}
|
||||||
// private function deploy_docker_compose()
|
// private function deploy_docker_compose()
|
||||||
// {
|
// {
|
||||||
// $dockercompose_base64 = base64_encode($this->application->dockercompose);
|
// $dockercompose_base64 = base64_encode($this->application->dockercompose);
|
||||||
@ -303,12 +344,14 @@ private function generate_image_names()
|
|||||||
$this->build_image_name = Str::lower("{$this->application->uuid}:pr-{$this->pull_request_id}-build");
|
$this->build_image_name = Str::lower("{$this->application->uuid}:pr-{$this->pull_request_id}-build");
|
||||||
$this->production_image_name = Str::lower("{$this->application->uuid}:pr-{$this->pull_request_id}");
|
$this->production_image_name = Str::lower("{$this->application->uuid}:pr-{$this->pull_request_id}");
|
||||||
} else {
|
} else {
|
||||||
$tag = Str::of("{$this->commit}-{$this->application->id}-{$this->pull_request_id}");
|
$this->dockerImageTag = str($this->commit)->substr(0, 128);
|
||||||
if (strlen($tag) > 128) {
|
if ($this->application->docker_registry_image_name) {
|
||||||
$tag = $tag->substr(0, 128);
|
$this->build_image_name = Str::lower("{$this->application->docker_registry_image_name}:{$this->dockerImageTag}-build");
|
||||||
|
$this->production_image_name = Str::lower("{$this->application->docker_registry_image_name}:{$this->dockerImageTag}");
|
||||||
|
} else {
|
||||||
|
$this->build_image_name = Str::lower("{$this->application->uuid}:{$this->dockerImageTag}-build");
|
||||||
|
$this->production_image_name = Str::lower("{$this->application->uuid}:{$this->dockerImageTag}");
|
||||||
}
|
}
|
||||||
$this->build_image_name = Str::lower("{$this->application->uuid}:{$tag}-build");
|
|
||||||
$this->production_image_name = Str::lower("{$this->application->uuid}:{$tag}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
private function just_restart()
|
private function just_restart()
|
||||||
@ -322,17 +365,29 @@ private function just_restart()
|
|||||||
$this->check_git_if_build_needed();
|
$this->check_git_if_build_needed();
|
||||||
$this->set_base_dir();
|
$this->set_base_dir();
|
||||||
$this->generate_image_names();
|
$this->generate_image_names();
|
||||||
$this->execute_remote_command([
|
$this->check_image_locally_or_remotely();
|
||||||
"docker images -q {$this->production_image_name} 2>/dev/null", "hidden" => true, "save" => "local_image_found"
|
|
||||||
]);
|
|
||||||
if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty()) {
|
if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty()) {
|
||||||
$this->generate_compose_file();
|
$this->generate_compose_file();
|
||||||
$this->rolling_update();
|
$this->rolling_update();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
throw new RuntimeException('Cannot find image anywhere. Please redeploy the application.');
|
||||||
|
}
|
||||||
|
private function check_image_locally_or_remotely()
|
||||||
|
{
|
||||||
$this->execute_remote_command([
|
$this->execute_remote_command([
|
||||||
"echo 'Cannot find image {$this->production_image_name} locally. Please redeploy the application.'",
|
"docker images -q {$this->production_image_name} 2>/dev/null", "hidden" => true, "save" => "local_image_found"
|
||||||
]);
|
]);
|
||||||
|
if (str($this->saved_outputs->get('local_image_found'))->isEmpty() && $this->application->docker_registry_image_name) {
|
||||||
|
$this->execute_remote_command([
|
||||||
|
"echo 'Cannot find image locally. Pulling from docker registry.'", 'type' => 'err'
|
||||||
|
], [
|
||||||
|
"docker pull {$this->production_image_name} 2>/dev/null", "ignore_errors" => true, "hidden" => true
|
||||||
|
]);
|
||||||
|
$this->execute_remote_command([
|
||||||
|
"docker images -q {$this->production_image_name} 2>/dev/null", "hidden" => true, "save" => "local_image_found"
|
||||||
|
]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
private function save_environment_variables()
|
private function save_environment_variables()
|
||||||
{
|
{
|
||||||
@ -419,12 +474,10 @@ private function deploy_nixpacks_buildpack()
|
|||||||
$this->set_base_dir();
|
$this->set_base_dir();
|
||||||
$this->generate_image_names();
|
$this->generate_image_names();
|
||||||
if (!$this->force_rebuild) {
|
if (!$this->force_rebuild) {
|
||||||
$this->execute_remote_command([
|
$this->check_image_locally_or_remotely();
|
||||||
"docker images -q {$this->production_image_name} 2>/dev/null", "hidden" => true, "save" => "local_image_found"
|
if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) {
|
||||||
]);
|
|
||||||
if (Str::of($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged()) {
|
|
||||||
$this->execute_remote_command([
|
$this->execute_remote_command([
|
||||||
"echo 'No configuration changed & Docker Image found locally with the same Git Commit SHA {$this->application->uuid}:{$this->commit}. Build step skipped.'",
|
"echo 'No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped.'",
|
||||||
]);
|
]);
|
||||||
$this->generate_compose_file();
|
$this->generate_compose_file();
|
||||||
$this->rolling_update();
|
$this->rolling_update();
|
||||||
@ -467,12 +520,18 @@ private function rolling_update()
|
|||||||
{
|
{
|
||||||
if (count($this->application->ports_mappings_array) > 0) {
|
if (count($this->application->ports_mappings_array) > 0) {
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
|
[
|
||||||
|
"echo '\n----------------------------------------'",
|
||||||
|
],
|
||||||
["echo -n 'Application has ports mapped to the host system, rolling update is not supported.'"],
|
["echo -n 'Application has ports mapped to the host system, rolling update is not supported.'"],
|
||||||
);
|
);
|
||||||
$this->stop_running_container(force: true);
|
$this->stop_running_container(force: true);
|
||||||
$this->start_by_compose_file();
|
$this->start_by_compose_file();
|
||||||
} else {
|
} else {
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
|
[
|
||||||
|
"echo '\n----------------------------------------'",
|
||||||
|
],
|
||||||
["echo -n 'Rolling update started.'"],
|
["echo -n 'Rolling update started.'"],
|
||||||
);
|
);
|
||||||
$this->start_by_compose_file();
|
$this->start_by_compose_file();
|
||||||
@ -488,10 +547,10 @@ private function health_check()
|
|||||||
}
|
}
|
||||||
// ray('New container name: ', $this->container_name);
|
// ray('New container name: ', $this->container_name);
|
||||||
if ($this->container_name) {
|
if ($this->container_name) {
|
||||||
$counter = 0;
|
$counter = 1;
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[
|
[
|
||||||
"echo 'Waiting for healthcheck to pass on the new version of your application.'"
|
"echo 'Waiting for healthcheck to pass on the new container.'"
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
if ($this->full_healthcheck_url) {
|
if ($this->full_healthcheck_url) {
|
||||||
@ -503,9 +562,6 @@ private function health_check()
|
|||||||
}
|
}
|
||||||
while ($counter < $this->application->health_check_retries) {
|
while ($counter < $this->application->health_check_retries) {
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[
|
|
||||||
"echo 'Attempt {$counter} of {$this->application->health_check_retries}'"
|
|
||||||
],
|
|
||||||
[
|
[
|
||||||
"docker inspect --format='{{json .State.Health.Status}}' {$this->container_name}",
|
"docker inspect --format='{{json .State.Health.Status}}' {$this->container_name}",
|
||||||
"hidden" => true,
|
"hidden" => true,
|
||||||
@ -515,17 +571,17 @@ private function health_check()
|
|||||||
);
|
);
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[
|
[
|
||||||
"echo 'New version healthcheck status: {$this->saved_outputs->get('health_check')}'"
|
"echo 'Attempt {$counter} of {$this->application->health_check_retries} | Healthcheck status: {$this->saved_outputs->get('health_check')}'"
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
if (Str::of($this->saved_outputs->get('health_check'))->contains('healthy')) {
|
if (Str::of($this->saved_outputs->get('health_check'))->contains('healthy')) {
|
||||||
$this->newVersionIsHealthy = true;
|
$this->newVersionIsHealthy = true;
|
||||||
|
$this->application->update(['status' => 'running']);
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[
|
[
|
||||||
"echo 'Rolling update completed.'"
|
"echo 'New container is healthy.'"
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
$this->application->update(['status' => 'running']);
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
$counter++;
|
$counter++;
|
||||||
@ -588,6 +644,7 @@ private function set_base_dir()
|
|||||||
[
|
[
|
||||||
"echo -n 'Setting base directory to {$this->workdir}.'"
|
"echo -n 'Setting base directory to {$this->workdir}.'"
|
||||||
],
|
],
|
||||||
|
["echo '\n----------------------------------------'"]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
private function check_git_if_build_needed()
|
private function check_git_if_build_needed()
|
||||||
@ -630,6 +687,9 @@ private function clone_repository()
|
|||||||
{
|
{
|
||||||
$importCommands = $this->generate_git_import_commands();
|
$importCommands = $this->generate_git_import_commands();
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
|
[
|
||||||
|
"echo '\n----------------------------------------'",
|
||||||
|
],
|
||||||
[
|
[
|
||||||
"echo -n 'Importing {$this->customRepository}:{$this->application->git_branch} (commit sha {$this->application->git_commit_sha}) to {$this->basedir}. '"
|
"echo -n 'Importing {$this->customRepository}:{$this->application->git_branch} (commit sha {$this->application->git_commit_sha}) to {$this->basedir}. '"
|
||||||
],
|
],
|
||||||
@ -677,7 +737,7 @@ private function generate_git_import_commands()
|
|||||||
$this->fullRepoUrl = $this->customRepository;
|
$this->fullRepoUrl = $this->customRepository;
|
||||||
$private_key = data_get($this->application, 'private_key.private_key');
|
$private_key = data_get($this->application, 'private_key.private_key');
|
||||||
if (is_null($private_key)) {
|
if (is_null($private_key)) {
|
||||||
throw new Exception('Private key not found. Please add a private key to the application and try again.');
|
throw new RuntimeException('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_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_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}";
|
||||||
@ -736,16 +796,10 @@ private function cleanup_git()
|
|||||||
|
|
||||||
private function generate_nixpacks_confs()
|
private function generate_nixpacks_confs()
|
||||||
{
|
{
|
||||||
|
|
||||||
$this->execute_remote_command(
|
|
||||||
[
|
|
||||||
"echo -n 'Generating nixpacks configuration.'",
|
|
||||||
]
|
|
||||||
);
|
|
||||||
$nixpacks_command = $this->nixpacks_build_cmd();
|
$nixpacks_command = $this->nixpacks_build_cmd();
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
[
|
[
|
||||||
"echo -n Running: $nixpacks_command",
|
"echo -n 'Generating nixpacks configuration with: $nixpacks_command'",
|
||||||
],
|
],
|
||||||
[executeInDocker($this->deployment_uuid, $nixpacks_command)],
|
[executeInDocker($this->deployment_uuid, $nixpacks_command)],
|
||||||
[executeInDocker($this->deployment_uuid, "cp {$this->workdir}/.nixpacks/Dockerfile {$this->workdir}/Dockerfile")],
|
[executeInDocker($this->deployment_uuid, "cp {$this->workdir}/.nixpacks/Dockerfile {$this->workdir}/Dockerfile")],
|
||||||
@ -884,7 +938,6 @@ private function generate_compose_file()
|
|||||||
];
|
];
|
||||||
if (data_get($this->application, 'settings.gpu_count')) {
|
if (data_get($this->application, 'settings.gpu_count')) {
|
||||||
$count = data_get($this->application, 'settings.gpu_count');
|
$count = data_get($this->application, 'settings.gpu_count');
|
||||||
ray($count);
|
|
||||||
if ($count === 'all') {
|
if ($count === 'all') {
|
||||||
$docker_compose['services'][$this->container_name]['deploy']['resources']['reservations']['devices'][0]['count'] = $count;
|
$docker_compose['services'][$this->container_name]['deploy']['resources']['reservations']['devices'][0]['count'] = $count;
|
||||||
} else {
|
} else {
|
||||||
@ -912,7 +965,6 @@ private function generate_compose_file()
|
|||||||
// 'dockerfile' => $this->workdir . $this->dockerfile_location,
|
// 'dockerfile' => $this->workdir . $this->dockerfile_location,
|
||||||
// ];
|
// ];
|
||||||
// }
|
// }
|
||||||
ray($docker_compose);
|
|
||||||
$this->docker_compose = Yaml::dump($docker_compose, 10);
|
$this->docker_compose = Yaml::dump($docker_compose, 10);
|
||||||
$this->docker_compose_base64 = base64_encode($this->docker_compose);
|
$this->docker_compose_base64 = base64_encode($this->docker_compose);
|
||||||
$this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d > {$this->workdir}/docker-compose.yml"), "hidden" => true]);
|
$this->execute_remote_command([executeInDocker($this->deployment_uuid, "echo '{$this->docker_compose_base64}' | base64 -d > {$this->workdir}/docker-compose.yml"), "hidden" => true]);
|
||||||
@ -1021,9 +1073,12 @@ private function build_image()
|
|||||||
"echo -n 'Static deployment. Copying static assets to the image.'",
|
"echo -n 'Static deployment. Copying static assets to the image.'",
|
||||||
]);
|
]);
|
||||||
} else {
|
} else {
|
||||||
$this->execute_remote_command([
|
$this->execute_remote_command(
|
||||||
"echo -n 'Building docker image for your application. To check the current progress, click on Show Debug Logs.'",
|
[
|
||||||
]);
|
"echo -n 'Building docker image started.'",
|
||||||
|
],
|
||||||
|
["echo -n 'To check the current progress, click on Show Debug Logs.'"]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($this->application->settings->is_static || $this->application->build_pack === 'static') {
|
if ($this->application->settings->is_static || $this->application->build_pack === 'static') {
|
||||||
@ -1105,12 +1160,14 @@ private function build_image()
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
$this->execute_remote_command([
|
||||||
|
"echo -n 'Building docker image completed.'",
|
||||||
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function stop_running_container(bool $force = false)
|
private function stop_running_container(bool $force = false)
|
||||||
{
|
{
|
||||||
$this->execute_remote_command(["echo -n 'Removing old version of your application.'"]);
|
$this->execute_remote_command(["echo -n 'Removing old container.'"]);
|
||||||
|
|
||||||
if ($this->newVersionIsHealthy || $force) {
|
if ($this->newVersionIsHealthy || $force) {
|
||||||
$containers = getCurrentApplicationContainerStatus($this->server, $this->application->id, $this->pull_request_id);
|
$containers = getCurrentApplicationContainerStatus($this->server, $this->application->id, $this->pull_request_id);
|
||||||
if ($this->pull_request_id !== 0) {
|
if ($this->pull_request_id !== 0) {
|
||||||
@ -1128,9 +1185,14 @@ private function stop_running_container(bool $force = false)
|
|||||||
[executeInDocker($this->deployment_uuid, "docker rm -f $containerName >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true],
|
[executeInDocker($this->deployment_uuid, "docker rm -f $containerName >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true],
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
$this->execute_remote_command(
|
||||||
|
[
|
||||||
|
"echo 'Rolling update completed.'"
|
||||||
|
],
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
["echo -n 'New version is not healthy, rolling back to the old version.'"],
|
["echo -n 'New container is not healthy, rolling back to the old container.'"],
|
||||||
[executeInDocker($this->deployment_uuid, "docker rm -f $this->container_name >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true],
|
[executeInDocker($this->deployment_uuid, "docker rm -f $this->container_name >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1142,12 +1204,10 @@ private function start_by_compose_file()
|
|||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
["echo -n 'Pulling latest images from the registry.'"],
|
["echo -n 'Pulling latest images from the registry.'"],
|
||||||
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} pull"), "hidden" => true],
|
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} pull"), "hidden" => true],
|
||||||
["echo -n 'Starting application (could take a while).'"],
|
|
||||||
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up --build -d"), "hidden" => true],
|
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up --build -d"), "hidden" => true],
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
["echo -n 'Starting application (could take a while).'"],
|
|
||||||
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up --build -d"), "hidden" => true],
|
[executeInDocker($this->deployment_uuid, "docker compose --project-directory {$this->workdir} up --build -d"), "hidden" => true],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -1206,9 +1266,9 @@ private function next(string $status)
|
|||||||
public function failed(Throwable $exception): void
|
public function failed(Throwable $exception): void
|
||||||
{
|
{
|
||||||
$this->execute_remote_command(
|
$this->execute_remote_command(
|
||||||
["echo 'Oops something is not okay, are you okay? 😢'"],
|
["echo 'Oops something is not okay, are you okay? 😢'", 'type' => 'err'],
|
||||||
["echo '{$exception->getMessage()}'"],
|
["echo '{$exception->getMessage()}'", 'type' => 'err'],
|
||||||
["echo -n 'Deployment failed. Removing the new version of your application.'"],
|
["echo -n 'Deployment failed. Removing the new version of your application.'", 'type' => 'err'],
|
||||||
[executeInDocker($this->deployment_uuid, "docker rm -f $this->container_name >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true]
|
[executeInDocker($this->deployment_uuid, "docker rm -f $this->container_name >/dev/null 2>&1"), "hidden" => true, "ignore_errors" => true]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -32,16 +32,20 @@ public function execute_remote_command(...$commands)
|
|||||||
throw new \RuntimeException('Command is not set');
|
throw new \RuntimeException('Command is not set');
|
||||||
}
|
}
|
||||||
$hidden = data_get($single_command, 'hidden', false);
|
$hidden = data_get($single_command, 'hidden', false);
|
||||||
|
$customType = data_get($single_command, 'type');
|
||||||
$ignore_errors = data_get($single_command, 'ignore_errors', false);
|
$ignore_errors = data_get($single_command, 'ignore_errors', false);
|
||||||
$this->save = data_get($single_command, 'save');
|
$this->save = data_get($single_command, 'save');
|
||||||
|
|
||||||
$remote_command = generateSshCommand($this->server, $command);
|
$remote_command = generateSshCommand($this->server, $command);
|
||||||
$process = Process::timeout(3600)->idleTimeout(3600)->start($remote_command, function (string $type, string $output) use ($command, $hidden) {
|
$process = Process::timeout(3600)->idleTimeout(3600)->start($remote_command, function (string $type, string $output) use ($command, $hidden, $customType) {
|
||||||
$output = Str::of($output)->trim();
|
$output = Str::of($output)->trim();
|
||||||
|
if ($output->startsWith('╔')) {
|
||||||
|
$output = "\n" . $output;
|
||||||
|
}
|
||||||
$new_log_entry = [
|
$new_log_entry = [
|
||||||
'command' => $command,
|
'command' => remove_iip($command),
|
||||||
'output' => $output,
|
'output' => remove_iip($output),
|
||||||
'type' => $type === 'err' ? 'stderr' : 'stdout',
|
'type' => $customType ?? $type === 'err' ? 'stderr' : 'stdout',
|
||||||
'timestamp' => Carbon::now('UTC'),
|
'timestamp' => Carbon::now('UTC'),
|
||||||
'hidden' => $hidden,
|
'hidden' => $hidden,
|
||||||
'batch' => static::$batch_counter,
|
'batch' => static::$batch_counter,
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
|
const REDACTED = '<REDACTED>';
|
||||||
const DATABASE_TYPES = ['postgresql', 'redis', 'mongodb', 'mysql', 'mariadb'];
|
const DATABASE_TYPES = ['postgresql', 'redis', 'mongodb', 'mysql', 'mariadb'];
|
||||||
const VALID_CRON_STRINGS = [
|
const VALID_CRON_STRINGS = [
|
||||||
'every_minute' => '* * * * *',
|
'every_minute' => '* * * * *',
|
||||||
|
@ -170,10 +170,13 @@ function decode_remote_command_output(?ApplicationDeploymentQueue $application_d
|
|||||||
$i['timestamp'] = Carbon::parse($i['timestamp'])->format('Y-M-d H:i:s.u');
|
$i['timestamp'] = Carbon::parse($i['timestamp'])->format('Y-M-d H:i:s.u');
|
||||||
return $i;
|
return $i;
|
||||||
});
|
});
|
||||||
|
|
||||||
return $formatted;
|
return $formatted;
|
||||||
}
|
}
|
||||||
|
function remove_iip($text)
|
||||||
|
{
|
||||||
|
$text = preg_replace('/x-access-token:.*?(?=@)/', "x-access-token:" . REDACTED, $text);
|
||||||
|
return preg_replace('/\x1b\[[0-9;]*m/', '', $text);
|
||||||
|
}
|
||||||
function refresh_server_connection(?PrivateKey $private_key = null)
|
function refresh_server_connection(?PrivateKey $private_key = null)
|
||||||
{
|
{
|
||||||
if (is_null($private_key)) {
|
if (is_null($private_key)) {
|
||||||
|
@ -48,9 +48,8 @@ class="fixed top-4 right-16" x-on:click="toggleScroll"><svg class="icon" viewBox
|
|||||||
@foreach (decode_remote_command_output($application_deployment_queue) as $line)
|
@foreach (decode_remote_command_output($application_deployment_queue) as $line)
|
||||||
<div @class([
|
<div @class([
|
||||||
'font-mono whitespace-pre-line',
|
'font-mono whitespace-pre-line',
|
||||||
'text-white' => $line['type'] == 'stdout',
|
|
||||||
'text-error' => $line['type'] == 'stderr',
|
|
||||||
'text-warning' => $line['hidden'],
|
'text-warning' => $line['hidden'],
|
||||||
|
'text-error' => $line['type'] == 'stderr',
|
||||||
])>[{{ $line['timestamp'] }}] @if ($line['hidden'])
|
])>[{{ $line['timestamp'] }}] @if ($line['hidden'])
|
||||||
<br>COMMAND: <br>{{ $line['command'] }} <br><br>OUTPUT:
|
<br>COMMAND: <br>{{ $line['command'] }} <br><br>OUTPUT:
|
||||||
@endif{{ $line['output'] }}@if ($line['hidden'])
|
@endif{{ $line['output'] }}@if ($line['hidden'])
|
||||||
|
@ -40,12 +40,33 @@
|
|||||||
</div>
|
</div>
|
||||||
@if ($application->could_set_build_commands())
|
@if ($application->could_set_build_commands())
|
||||||
<div class="w-64">
|
<div class="w-64">
|
||||||
<x-forms.checkbox instantSave id="application.settings.is_static" label="Is it a static site?"
|
<x-forms.checkbox instantSave id="application.settings.is_static"
|
||||||
|
label="Is it a static site?"
|
||||||
helper="If your application is a static site or the final build assets should be served as a static site, enable this." />
|
helper="If your application is a static site or the final build assets should be served as a static site, enable this." />
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
<h3>Docker Registry</h3>
|
||||||
|
@if ($application->build_pack !== 'dockerimage')
|
||||||
|
<div>Push the built image to a docker registry. More info <a class="underline"
|
||||||
|
href="https://coolify.io/docs/" target="_blank">here</a>.</div>
|
||||||
|
@endif
|
||||||
|
<div class="flex flex-col gap-2 xl:flex-row">
|
||||||
|
@if ($application->build_pack === 'dockerimage')
|
||||||
|
<x-forms.input id="application.docker_registry_image_name" label="Docker Image" />
|
||||||
|
<x-forms.input id="application.docker_registry_image_tag" label="Docker Image Tag" />
|
||||||
|
@else
|
||||||
|
<x-forms.input id="application.docker_registry_image_name"
|
||||||
|
helper="Empty means it won't push the image to a docker registry."
|
||||||
|
placeholder="Empty means it won't push the image to a docker registry." label="Docker Image" />
|
||||||
|
<x-forms.input id="application.docker_registry_image_tag"
|
||||||
|
placeholder="Empty means only push commit sha tag."
|
||||||
|
helper="If set, it will tag the built image with this tag too. <br><br>Example: If you set it to 'latest', it will push the image with the commit sha tag + with the latest tag."
|
||||||
|
label="Docker Image Tag" />
|
||||||
|
@endif
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
@if ($application->build_pack !== 'dockerimage')
|
@if ($application->build_pack !== 'dockerimage')
|
||||||
<h3>Build</h3>
|
<h3>Build</h3>
|
||||||
@ -64,8 +85,6 @@
|
|||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
|
|
||||||
<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." />
|
||||||
@ -88,11 +107,6 @@
|
|||||||
@endif
|
@endif
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
@else
|
|
||||||
<div class="flex flex-col gap-2 xl:flex-row">
|
|
||||||
<x-forms.input id="application.docker_registry_image_name" label="Docker Image" />
|
|
||||||
<x-forms.input id="application.docker_registry_image_tag" label="Docker Image Tag" />
|
|
||||||
</div>
|
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
@if ($application->dockerfile)
|
@if ($application->dockerfile)
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
<div x-init="$wire.loadImages">
|
<div x-init="$wire.loadImages">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<h2>Rollback</h2>
|
<h2>Rollback</h2>
|
||||||
<x-forms.button wire:click='loadImages'>Reload Available Images</x-forms.button>
|
<x-forms.button wire:click='loadImages(true)'>Reload Available Images</x-forms.button>
|
||||||
</div>
|
</div>
|
||||||
<div class="pb-4 ">You can easily rollback to a previously built image quickly.</div>
|
<div class="pb-4 ">You can easily rollback to a previously built <span class="text-warning">(local)</span> images quickly.</div>
|
||||||
<div wire:target='loadImages'>
|
<div wire:target='loadImages'>
|
||||||
<div class="flex flex-wrap">
|
<div class="flex flex-wrap">
|
||||||
@foreach ($images as $image)
|
@forelse ($images as $image)
|
||||||
<div class="w-2/4 p-2">
|
<div class="w-2/4 p-2">
|
||||||
<div class="rounded shadow-lg bg-coolgray-200">
|
<div class="rounded shadow-lg bg-coolgray-200">
|
||||||
<div class="p-2">
|
<div class="p-2">
|
||||||
@ -25,14 +25,16 @@
|
|||||||
Rollback
|
Rollback
|
||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
@else
|
@else
|
||||||
<x-forms.button wire:click="rollbackImage('{{ data_get($image, 'tag') }}')">
|
<x-forms.button class="bg-coolgray-100" wire:click="rollbackImage('{{ data_get($image, 'tag') }}')">
|
||||||
Rollback
|
Rollback
|
||||||
</x-forms.button>
|
</x-forms.button>
|
||||||
@endif
|
@endif
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@endforeach
|
@empty
|
||||||
|
<div>No images found locally.</div>
|
||||||
|
@endforelse
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -36,7 +36,7 @@
|
|||||||
@endif
|
@endif
|
||||||
@if ($application->build_pack !== 'static')
|
@if ($application->build_pack !== 'static')
|
||||||
<a :class="activeTab === 'health' && 'text-white'"
|
<a :class="activeTab === 'health' && 'text-white'"
|
||||||
@click.prevent="activeTab = 'health'; window.location.hash = 'health'" href="#">Health Checks
|
@click.prevent="activeTab = 'health'; window.location.hash = 'health'" href="#">Healthchecks
|
||||||
</a>
|
</a>
|
||||||
@endif
|
@endif
|
||||||
<a :class="activeTab === 'rollback' && 'text-white'"
|
<a :class="activeTab === 'rollback' && 'text-white'"
|
||||||
|
Loading…
Reference in New Issue
Block a user