feat: multi deployments

This commit is contained in:
Andras Bacsai
2024-02-07 14:55:06 +01:00
parent 5bdbab7276
commit 9e1a7d5d9a
28 changed files with 714 additions and 290 deletions

View File

@@ -248,13 +248,14 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
dispatch(new ContainerStatusJob($this->server));
}
// Otherwise built image needs to be pushed before from the build server.
if (!$this->use_build_server) {
if ($this->application->additional_networks->count() > 0) {
$this->push_to_docker_registry(forceFail: true);
} else {
$this->push_to_docker_registry();
}
}
// ray($this->use_build_server);
// if (!$this->use_build_server) {
// if ($this->application->additional_servers->count() > 0) {
// $this->push_to_docker_registry(forceFail: true);
// } else {
// $this->push_to_docker_registry();
// }
// }
$this->next(ApplicationDeploymentStatus::FINISHED->value);
if ($this->pull_request_id !== 0) {
if ($this->application->is_github_based()) {
@@ -292,155 +293,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
ApplicationStatusChanged::dispatch(data_get($this->application, 'environment.project.team.id'));
}
}
private function write_deployment_configurations()
{
if (isset($this->docker_compose_base64)) {
if ($this->use_build_server) {
$this->server = $this->original_server;
}
$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(
[
"mkdir -p $this->configuration_dir"
],
[
"echo '{$this->docker_compose_base64}' | base64 -d > $composeFileName",
],
[
"echo '{$readme}' > $this->configuration_dir/README.md",
]
);
if ($this->use_build_server) {
$this->server = $this->build_server;
}
}
}
private function push_to_docker_registry($forceFail = false)
{
if (
$this->application->docker_registry_image_name &&
$this->application->build_pack !== 'dockerimage' &&
!$this->application->destination->server->isSwarm() &&
!$this->restart_only &&
!(str($this->saved_outputs->get('local_image_found'))->isNotEmpty() && !$this->application->isConfigurationChanged())
) {
try {
instant_remote_process(["docker images --format '{{json .}}' {$this->production_image_name}"], $this->server);
$this->application_deployment_queue->addLogEntry("----------------------------------------");
$this->application_deployment_queue->addLogEntry("Pushing image to docker registry ({$this->production_image_name}).");
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, "docker push {$this->production_image_name}"), 'hidden' => true
],
);
if ($this->application->docker_registry_image_tag) {
// Tag image with latest
$this->application_deployment_queue->addLogEntry("Tagging and pushing image with latest tag.");
$this->execute_remote_command(
[
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->application_deployment_queue->addLogEntry("Image pushed to docker registry.");
} catch (Exception $e) {
$this->application_deployment_queue->addLogEntry("Failed to push image to docker registry. Please check debug logs for more information.");
if ($forceFail) {
throw new RuntimeException($e->getMessage(), 69420);
}
ray($e);
}
}
}
private function generate_image_names()
{
if ($this->application->dockerfile) {
if ($this->application->docker_registry_image_name) {
$this->build_image_name = Str::lower("{$this->application->docker_registry_image_name}:build");
$this->production_image_name = Str::lower("{$this->application->docker_registry_image_name}:latest");
} else {
$this->build_image_name = Str::lower("{$this->application->uuid}:build");
$this->production_image_name = Str::lower("{$this->application->uuid}:latest");
}
} else if ($this->application->build_pack === 'dockerimage') {
$this->production_image_name = Str::lower("{$this->dockerImage}:{$this->dockerImageTag}");
} else if ($this->pull_request_id !== 0) {
if ($this->application->docker_registry_image_name) {
$this->build_image_name = Str::lower("{$this->application->docker_registry_image_name}:pr-{$this->pull_request_id}-build");
$this->production_image_name = Str::lower("{$this->application->docker_registry_image_name}:pr-{$this->pull_request_id}");
} else {
$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}");
}
} else {
$this->dockerImageTag = str($this->commit)->substr(0, 128);
if ($this->application->docker_registry_image_name) {
$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}");
}
}
}
private function just_restart()
{
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch} to {$this->server->name}.");
$this->prepare_builder_image();
$this->check_git_if_build_needed();
$this->set_base_dir();
$this->generate_image_names();
$this->check_image_locally_or_remotely();
if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty()) {
$this->application_deployment_queue->addLogEntry("Image found ({$this->production_image_name}) with the same Git Commit SHA. Restarting container.");
$this->create_workdir();
$this->generate_compose_file();
$this->rolling_update();
return;
}
throw new RuntimeException('Cannot find image anywhere. Please redeploy the application.');
}
private function check_image_locally_or_remotely()
{
$this->execute_remote_command([
"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([
"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()
{
$envs = collect([]);
if ($this->pull_request_id !== 0) {
foreach ($this->application->environment_variables_preview as $env) {
$envs->push($env->key . '=' . $env->real_value);
}
} else {
foreach ($this->application->environment_variables as $env) {
$envs->push($env->key . '=' . $env->real_value);
}
}
$envs_base64 = base64_encode($envs->implode("\n"));
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, "echo '$envs_base64' | base64 -d > $this->workdir/.env")
],
);
}
private function deploy_simple_dockerfile()
{
if ($this->use_build_server) {
@@ -461,6 +313,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->create_workdir();
$this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped.");
$this->generate_compose_file();
$this->push_to_docker_registry();
$this->rolling_update();
return;
}
@@ -469,9 +322,9 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->generate_build_env_variables();
$this->add_build_env_variables_to_dockerfile();
$this->build_image();
$this->push_to_docker_registry();
$this->rolling_update();
}
private function deploy_dockerimage_buildpack()
{
$this->dockerImage = $this->application->docker_registry_image_name;
@@ -576,6 +429,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->create_workdir();
$this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped.");
$this->generate_compose_file();
$this->push_to_docker_registry();
$this->rolling_update();
return;
}
@@ -586,6 +440,7 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->generate_build_env_variables();
$this->add_build_env_variables_to_dockerfile();
$this->build_image();
$this->push_to_docker_registry();
$this->rolling_update();
}
private function deploy_nixpacks_buildpack()
@@ -604,6 +459,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->create_workdir();
$this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped.");
$this->generate_compose_file();
ray('pushing to docker registry');
$this->push_to_docker_registry();
$this->rolling_update();
return;
}
@@ -616,8 +473,8 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->generate_nixpacks_confs();
$this->generate_compose_file();
$this->generate_build_env_variables();
// $this->add_build_env_variables_to_dockerfile();
$this->build_image();
$this->push_to_docker_registry();
$this->rolling_update();
}
private function deploy_static_buildpack()
@@ -636,17 +493,191 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->create_workdir();
$this->application_deployment_queue->addLogEntry("No configuration changed & image found ({$this->production_image_name}) with the same Git Commit SHA. Build step skipped.");
$this->generate_compose_file();
$this->push_to_docker_registry();
$this->rolling_update();
return;
}
}
$this->clone_repository();
$this->cleanup_git();
$this->build_image();
$this->generate_compose_file();
$this->build_image();
$this->push_to_docker_registry();
$this->rolling_update();
}
private function write_deployment_configurations()
{
if (isset($this->docker_compose_base64)) {
if ($this->use_build_server) {
$this->server = $this->original_server;
}
$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(
[
"mkdir -p $this->configuration_dir"
],
[
"echo '{$this->docker_compose_base64}' | base64 -d > $composeFileName",
],
[
"echo '{$readme}' > $this->configuration_dir/README.md",
]
);
if ($this->use_build_server) {
$this->server = $this->build_server;
}
}
}
private function push_to_docker_registry()
{
$forceFail = false;
if (str($this->application->docker_registry_image_name)->isEmpty()) {
ray('empty docker_registry_image_name');
return;
}
if ($this->restart_only) {
ray('restart_only');
return;
}
if ($this->application->build_pack === 'dockerimage') {
ray('dockerimage');
return;
}
if ($this->use_build_server) {
ray('use_build_server');
$forceFail = true;
}
if ($this->server->isSwarm() && $this->build_pack !== 'dockerimage') {
ray('isSwarm');
$forceFail = true;
}
if ($this->application->additional_servers->count() > 0) {
ray('additional_servers');
$forceFail = true;
}
if ($this->application->additional_servers()->wherePivot('server_id', $this->server->id)->count() > 0) {
ray('this is an additional_servers, no pushy pushy');
return;
}
ray('push_to_docker_registry noww: ' . $this->production_image_name);
try {
instant_remote_process(["docker images --format '{{json .}}' {$this->production_image_name}"], $this->server);
$this->application_deployment_queue->addLogEntry("----------------------------------------");
$this->application_deployment_queue->addLogEntry("Pushing image to docker registry ({$this->production_image_name}).");
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, "docker push {$this->production_image_name}"), 'hidden' => true
],
);
if ($this->application->docker_registry_image_tag) {
// Tag image with latest
$this->application_deployment_queue->addLogEntry("Tagging and pushing image with latest tag.");
$this->execute_remote_command(
[
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->application_deployment_queue->addLogEntry("Image pushed to docker registry.");
} catch (Exception $e) {
$this->application_deployment_queue->addLogEntry("Failed to push image to docker registry. Please check debug logs for more information.");
if ($forceFail) {
throw new RuntimeException($e->getMessage(), 69420);
}
ray($e);
}
}
private function generate_image_names()
{
if ($this->application->dockerfile) {
if ($this->application->docker_registry_image_name) {
$this->build_image_name = Str::lower("{$this->application->docker_registry_image_name}:build");
$this->production_image_name = Str::lower("{$this->application->docker_registry_image_name}:latest");
} else {
$this->build_image_name = Str::lower("{$this->application->uuid}:build");
$this->production_image_name = Str::lower("{$this->application->uuid}:latest");
}
} else if ($this->application->build_pack === 'dockerimage') {
$this->production_image_name = Str::lower("{$this->dockerImage}:{$this->dockerImageTag}");
} else if ($this->pull_request_id !== 0) {
if ($this->application->docker_registry_image_name) {
$this->build_image_name = Str::lower("{$this->application->docker_registry_image_name}:pr-{$this->pull_request_id}-build");
$this->production_image_name = Str::lower("{$this->application->docker_registry_image_name}:pr-{$this->pull_request_id}");
} else {
$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}");
}
} else {
$this->dockerImageTag = str($this->commit)->substr(0, 128);
if ($this->application->docker_registry_image_name) {
$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}");
}
}
}
private function just_restart()
{
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->customRepository}:{$this->application->git_branch} to {$this->server->name}.");
$this->prepare_builder_image();
$this->check_git_if_build_needed();
$this->set_base_dir();
$this->generate_image_names();
$this->check_image_locally_or_remotely();
if (str($this->saved_outputs->get('local_image_found'))->isNotEmpty()) {
$this->application_deployment_queue->addLogEntry("Image found ({$this->production_image_name}) with the same Git Commit SHA. Restarting container.");
$this->create_workdir();
$this->generate_compose_file();
$this->rolling_update();
return;
}
throw new RuntimeException('Cannot find image anywhere. Please redeploy the application.');
}
private function check_image_locally_or_remotely()
{
$this->execute_remote_command([
"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([
"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()
{
$envs = collect([]);
if ($this->pull_request_id !== 0) {
foreach ($this->application->environment_variables_preview as $env) {
$envs->push($env->key . '=' . $env->real_value);
}
} else {
foreach ($this->application->environment_variables as $env) {
$envs->push($env->key . '=' . $env->real_value);
}
}
$envs_base64 = base64_encode($envs->implode("\n"));
$this->execute_remote_command(
[
executeInDocker($this->deployment_uuid, "echo '$envs_base64' | base64 -d > $this->workdir/.env")
],
);
}
private function framework_based_notification()
{
// Laravel old env variables
@@ -664,9 +695,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
private function rolling_update()
{
if ($this->server->isSwarm()) {
if ($this->build_pack !== 'dockerimage') {
$this->push_to_docker_registry(forceFail: true);
}
$this->application_deployment_queue->addLogEntry("Rolling update started.");
$this->execute_remote_command(
[
@@ -676,7 +704,6 @@ class ApplicationDeploymentJob implements ShouldQueue, ShouldBeEncrypted
$this->application_deployment_queue->addLogEntry("Rolling update completed.");
} else {
if ($this->use_build_server) {
$this->push_to_docker_registry(forceFail: true);
$this->write_deployment_configurations();
$this->server = $this->original_server;
}