Skip to content

Commit a164d47

Browse files
committed
feat: #54 allow to add simplified workers to services
1 parent 03d7ae9 commit a164d47

File tree

10 files changed

+279
-15
lines changed

10 files changed

+279
-15
lines changed

app/Http/Controllers/SwarmTaskController.php

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,8 @@ public function initCluster(InitClusterFormRequest $request)
9999
'Labels' => dockerize_labels([
100100
'network.id' => $network->id
101101
]),
102+
'Scope' => 'swarm',
103+
'Attachable' => true
102104
],
103105
],
104106
],
@@ -115,14 +117,15 @@ public function initCluster(InitClusterFormRequest $request)
115117
'placementNodeId' => null,
116118
'processes' => [
117119
[
118-
'name' => 'caddy',
120+
'name' => 'svc',
119121
'launchMode' => LaunchMode::Daemon->value,
120122
'dockerRegistryId' => null,
121123
'dockerImage' => 'caddy:2.8-alpine',
122124
'releaseCommand' => [
123125
'command' => null,
124126
],
125127
'command' => 'sh /start.sh',
128+
'workers' => [],
126129
'envVars' => [
127130
[
128131
'name' => 'CADDY_ADMIN',

app/Models/DeploymentData.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ public static function make(array $attributes): static
3838
'command' => null,
3939
]),
4040
'command' => '',
41+
'workers' => [],
4142
'launchMode' => LaunchMode::Daemon->value,
4243
'envVars' => [],
4344
'secretVars' => SecretVars::from([

app/Models/DeploymentData/Process.php

Lines changed: 133 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@
77
use App\Models\NodeTasks\CreateConfig\CreateConfigMeta;
88
use App\Models\NodeTasks\CreateSecret\CreateSecretMeta;
99
use App\Models\NodeTasks\CreateService\CreateServiceMeta;
10+
use App\Models\NodeTasks\DeleteService\DeleteServiceMeta;
1011
use App\Models\NodeTasks\PullDockerImage\PullDockerImageMeta;
1112
use App\Models\NodeTasks\UpdateService\UpdateServiceMeta;
1213
use App\Models\NodeTaskType;
1314
use App\Rules\RequiredIfArrayHas;
15+
use Illuminate\Support\Str;
1416
use Spatie\LaravelData\Attributes\DataCollectionOf;
1517
use Spatie\LaravelData\Attributes\Validation\Enum;
1618
use Spatie\LaravelData\Attributes\Validation\Rule;
@@ -25,6 +27,9 @@ public function __construct(
2527
public string $dockerImage,
2628
public ReleaseCommand $releaseCommand,
2729
public ?string $command,
30+
#[DataCollectionOf(Worker::class)]
31+
/* @var Worker[] */
32+
public array $workers,
2833
#[Enum(LaunchMode::class)]
2934
public string $launchMode,
3035
#[DataCollectionOf(EnvVar::class)]
@@ -74,6 +79,26 @@ public function asNodeTasks(Deployment $deployment): array
7479

7580
$tasks = [];
7681

82+
$previousWorkers = $previous?->workers ?? [];
83+
foreach ($previousWorkers as $worker) {
84+
if ($this->findWorker($worker->dockerName) === null) {
85+
$tasks[] = [
86+
'type' => NodeTaskType::DeleteService,
87+
'meta' => new DeleteServiceMeta($deployment->service_id, $worker->dockerName, $deployment->service->name),
88+
'payload' => [
89+
'ServiceName' => $worker->dockerName,
90+
],
91+
];
92+
}
93+
}
94+
95+
foreach ($this->workers as $worker) {
96+
if (!$worker->dockerName) {
97+
// TODO: add validation - allow only unique worker commands
98+
$worker->dockerName = $this->makeResourceName('wkr_' . $worker->name);
99+
}
100+
}
101+
77102
foreach ($this->configFiles as $configFile) {
78103
$previousConfig = $previous?->findConfigFile($configFile->path);
79104
if ($previousConfig && $configFile->sameAs($previousConfig)) {
@@ -174,13 +199,15 @@ public function asNodeTasks(Deployment $deployment): array
174199

175200
$serviceTaskMeta = [
176201
'deploymentId' => $deployment->id,
177-
'processName' => $this->dockerName,
202+
'dockerName' => $this->dockerName,
178203
'serviceId' => $deployment->service_id,
179204
'serviceName' => $deployment->service->name,
180205
];
181206

182207
// FIXME: this is going to work wrong if the initial deployment is pending.
183-
$actionUpdate = $deployment->service->tasks()->ofType(NodeTaskType::CreateService)->completed()->exists();
208+
// Don't allow to schedule deployments if the service has not been created yet?
209+
// This code is duplicated in the next block
210+
$actionUpdate = $deployment->service->tasks()->ofType(NodeTaskType::CreateService)->where('meta__docker_name', $this->dockerName)->completed()->exists();
184211

185212
$tasks[] = [
186213
'type' => $actionUpdate ? NodeTaskType::UpdateService : NodeTaskType::CreateService,
@@ -261,6 +288,83 @@ public function asNodeTasks(Deployment $deployment): array
261288
],
262289
];
263290

291+
foreach ($this->workers as $worker) {
292+
$actionUpdate = $deployment->service->tasks()->ofType(NodeTaskType::CreateService)->where('meta__docker_name', $worker->dockerName)->completed()->exists();
293+
294+
$workerTaskMeta = [
295+
...$serviceTaskMeta,
296+
'dockerName' => $worker->dockerName,
297+
];
298+
299+
$tasks[] = [
300+
'type' => $actionUpdate ? NodeTaskType::UpdateService : NodeTaskType::CreateService,
301+
'meta' => $actionUpdate ? UpdateServiceMeta::from($workerTaskMeta) : CreateServiceMeta::from($workerTaskMeta),
302+
'payload' => [
303+
'AuthConfigName' => $this->dockerRegistry,
304+
'ReleaseCommand' => (object) [],
305+
'SecretVars' => (object) $this->getWorkerSecretVars($worker, $labels),
306+
'SwarmServiceSpec' => [
307+
'Name' => $worker->dockerName,
308+
'Labels' => $labels,
309+
'TaskTemplate' => [
310+
'ContainerSpec' => [
311+
'Image' => $this->dockerImage,
312+
'Labels' => $labels,
313+
// 'Command' => ['sh -c "' . Str::replace('"', '\\"', $worker->command) . '"'],
314+
'Command' => ['sh', '-c'],
315+
'Args' => [
316+
$worker->command,
317+
],
318+
'Hostname' => "dpl-{$deployment->id}.{$worker->name}.{$internalDomain}",
319+
'Env' => collect($this->envVars)->map(fn(EnvVar $var) => "{$var->name}={$var->value}")->toArray(),
320+
'Mounts' => [],
321+
'Hosts' => [
322+
"{$worker->name}.{$internalDomain}",
323+
],
324+
'Secrets' => collect($this->secretFiles)->map(fn(ConfigFile $secretFile) => [
325+
'File' => [
326+
'Name' => $secretFile->path,
327+
// TODO: figure out better permissions settings (if any)
328+
'UID' => "0",
329+
"GID" => "0",
330+
"Mode" => 0777
331+
],
332+
'SecretName' => $secretFile->dockerName,
333+
])->values()->toArray(),
334+
'Configs' => collect($this->configFiles)->map(fn(ConfigFile $configFile) => [
335+
'File' => [
336+
'Name' => $configFile->path,
337+
// TODO: figure out better permissions settings (if any)
338+
'UID' => "0",
339+
"GID" => "0",
340+
"Mode" => 0777
341+
],
342+
'ConfigName' => $configFile->dockerName,
343+
])->values()->toArray(),
344+
'Placement' => [],
345+
],
346+
'Networks' => [
347+
[
348+
'Target' => $deployment->data->networkName,
349+
'Aliases' => [
350+
"{$worker->name}.{$internalDomain}",
351+
],
352+
]
353+
],
354+
],
355+
'Mode' => [
356+
'Replicated' => [
357+
'Replicas' => $worker->replicas,
358+
],
359+
],
360+
'EndpointSpec' => [
361+
'Ports' => []
362+
]
363+
]
364+
],
365+
];
366+
}
367+
264368
return $tasks;
265369
}
266370

@@ -295,6 +399,24 @@ protected function getSecretVars(?Process $previous, $labels): array|object
295399
return $data;
296400
}
297401

402+
private function getWorkerSecretVars(Worker $worker, array $labels): array|object
403+
{
404+
if (empty($this->data->secretVars->vars)) {
405+
return (object) [];
406+
}
407+
408+
return [
409+
'ConfigName' => $this->data->secretVars->dockerName . '_wkr_' . $worker->name,
410+
'ConfigLabels' => dockerize_labels([
411+
...$labels,
412+
'kind' => 'secret-env-vars',
413+
]),
414+
'Values' => [],
415+
'Preserve' => collect($this->data->secretVars->vars)->map(fn(EnvVar $var) => $var->name)->toArray(),
416+
'PreserveFromConfig' => $this->secretVars->dockerName,
417+
];
418+
}
419+
298420

299421
public function makeResourceName(string $name): string
300422
{
@@ -322,4 +444,13 @@ private function getReleaseCommandPayload(Deployment $deployment, array $labels)
322444
'Command' => $this->releaseCommand->command,
323445
];
324446
}
447+
448+
private function findWorker(?string $dockerName): ?Worker
449+
{
450+
if (!$dockerName) {
451+
return null;
452+
}
453+
454+
return collect($this->workers)->first(fn(Worker $worker) => $worker->dockerName === $dockerName);
455+
}
325456
}

app/Models/DeploymentData/Worker.php

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
namespace App\Models\DeploymentData;
4+
5+
use Spatie\LaravelData\Data;
6+
7+
class Worker extends Data
8+
{
9+
10+
public function __construct(
11+
public string $name,
12+
public ?string $dockerName,
13+
public string $command,
14+
public int $replicas
15+
)
16+
{
17+
//
18+
}
19+
}

app/Models/NodeTasks/CreateService/CreateServiceMeta.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@ public function __construct(
1010
public int $deploymentId,
1111
public int $serviceId,
1212
public string $serviceName,
13-
public string $processName
13+
public string $dockerName
1414
)
1515
{
1616
//
1717
}
1818

1919
public function formattedHtml(): string
2020
{
21-
return "Create Docker Service <code>$this->serviceName</code> for process <code>{$this->processName}</code>";
21+
return "Create Docker Service <code>$this->dockerName</code> of <code>{$this->serviceName}</code>";
2222
}
2323
}

app/Models/NodeTasks/DeleteService/DeleteServiceMeta.php

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
class DeleteServiceMeta extends AbstractTaskMeta
88
{
99
public function __construct(
10-
public int $serviceId,
11-
public string $processName,
10+
public int $serviceId,
11+
public string $dockerName,
1212
public string $serviceName,
1313
)
1414
{
@@ -17,6 +17,6 @@ public function __construct(
1717

1818
public function formattedHtml(): string
1919
{
20-
return "Delete process <code>{$this->processName}</code> of <code>{$this->serviceName}</code>";
20+
return "Delete Docker Service <code>{$this->dockerName}</code> of <code>{$this->serviceName}</code>";
2121
}
2222
}

app/Models/NodeTasks/UpdateService/UpdateServiceMeta.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,14 @@ public function __construct(
1010
public int $deploymentId,
1111
public int $serviceId,
1212
public string $serviceName,
13-
public string $processName
13+
public string $dockerName
1414
)
1515
{
1616
//
1717
}
1818

1919
public function formattedHtml(): string
2020
{
21-
return "Update Docker process <code>{$this->processName}</code> in <code>$this->serviceName</code>";
21+
return "Update Docker Service <code>{$this->dockerName}</code> of <code>$this->serviceName</code>";
2222
}
2323
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
use Illuminate\Database\Migrations\Migration;
4+
use Illuminate\Database\Schema\Blueprint;
5+
use Illuminate\Support\Facades\Schema;
6+
7+
return new class extends Migration
8+
{
9+
/**
10+
* Run the migrations.
11+
*/
12+
public function up(): void
13+
{
14+
Schema::table('node_tasks', function (Blueprint $table) {
15+
$table->string('meta__docker_name')
16+
->nullable()
17+
->storedAs('("meta"->>\'dockerName\')::text');
18+
});
19+
}
20+
21+
/**
22+
* Reverse the migrations.
23+
*/
24+
public function down(): void
25+
{
26+
Schema::table('node_tasks', function (Blueprint $table) {
27+
$table->dropColumn('meta__docker_name');
28+
});
29+
}
30+
};
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<script setup>
2+
import SecondaryButton from "@/Components/SecondaryButton.vue";
3+
</script>
4+
5+
<template>
6+
<SecondaryButton tabindex="-1">
7+
<svg class="w-4 h-4 text-gray-800 dark:text-white" aria-hidden="true"
8+
xmlns="http://www.w3.org/2000/svg" width="24" height="24" fill="none" viewBox="0 0 24 24">
9+
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
10+
d="M5 12h14"/>
11+
</svg>
12+
</SecondaryButton>
13+
</template>

0 commit comments

Comments
 (0)