Skip to content

Commit 608eada

Browse files
committed
feat: #265, #266, #268 allow to create and destroy review apps
1 parent 8c117ec commit 608eada

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

51 files changed

+1219
-168
lines changed

_ide_helper_actions.php

+18-18
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,15 @@ class OverrideTeamQuotas {}
3030
*/
3131
class InitCluster {}
3232
/**
33-
* @method static \Lorisleiva\Actions\Decorators\JobDecorator|\Lorisleiva\Actions\Decorators\UniqueJobDecorator makeJob(\App\Models\Team $team, \App\Models\NodeTaskGroup $taskGroup, \App\Models\Deployment $deployment)
34-
* @method static \Lorisleiva\Actions\Decorators\UniqueJobDecorator makeUniqueJob(\App\Models\Team $team, \App\Models\NodeTaskGroup $taskGroup, \App\Models\Deployment $deployment)
35-
* @method static \Illuminate\Foundation\Bus\PendingDispatch dispatch(\App\Models\Team $team, \App\Models\NodeTaskGroup $taskGroup, \App\Models\Deployment $deployment)
36-
* @method static \Illuminate\Foundation\Bus\PendingDispatch|\Illuminate\Support\Fluent dispatchIf(bool $boolean, \App\Models\Team $team, \App\Models\NodeTaskGroup $taskGroup, \App\Models\Deployment $deployment)
37-
* @method static \Illuminate\Foundation\Bus\PendingDispatch|\Illuminate\Support\Fluent dispatchUnless(bool $boolean, \App\Models\Team $team, \App\Models\NodeTaskGroup $taskGroup, \App\Models\Deployment $deployment)
38-
* @method static dispatchSync(\App\Models\Team $team, \App\Models\NodeTaskGroup $taskGroup, \App\Models\Deployment $deployment)
39-
* @method static dispatchNow(\App\Models\Team $team, \App\Models\NodeTaskGroup $taskGroup, \App\Models\Deployment $deployment)
40-
* @method static dispatchAfterResponse(\App\Models\Team $team, \App\Models\NodeTaskGroup $taskGroup, \App\Models\Deployment $deployment)
41-
* @method static mixed run(\App\Models\Team $team, \App\Models\NodeTaskGroup $taskGroup, \App\Models\Deployment $deployment)
33+
* @method static \Lorisleiva\Actions\Decorators\JobDecorator|\Lorisleiva\Actions\Decorators\UniqueJobDecorator makeJob(\App\Models\Team $team, \App\Models\NodeTaskGroup $taskGroup)
34+
* @method static \Lorisleiva\Actions\Decorators\UniqueJobDecorator makeUniqueJob(\App\Models\Team $team, \App\Models\NodeTaskGroup $taskGroup)
35+
* @method static \Illuminate\Foundation\Bus\PendingDispatch dispatch(\App\Models\Team $team, \App\Models\NodeTaskGroup $taskGroup)
36+
* @method static \Illuminate\Foundation\Bus\PendingDispatch|\Illuminate\Support\Fluent dispatchIf(bool $boolean, \App\Models\Team $team, \App\Models\NodeTaskGroup $taskGroup)
37+
* @method static \Illuminate\Foundation\Bus\PendingDispatch|\Illuminate\Support\Fluent dispatchUnless(bool $boolean, \App\Models\Team $team, \App\Models\NodeTaskGroup $taskGroup)
38+
* @method static dispatchSync(\App\Models\Team $team, \App\Models\NodeTaskGroup $taskGroup)
39+
* @method static dispatchNow(\App\Models\Team $team, \App\Models\NodeTaskGroup $taskGroup)
40+
* @method static dispatchAfterResponse(\App\Models\Team $team, \App\Models\NodeTaskGroup $taskGroup)
41+
* @method static mixed run(\App\Models\Team $team, \App\Models\NodeTaskGroup $taskGroup)
4242
*/
4343
class RebuildCaddy {}
4444

@@ -57,15 +57,15 @@ class RebuildCaddy {}
5757
*/
5858
class CreateService {}
5959
/**
60-
* @method static \Lorisleiva\Actions\Decorators\JobDecorator|\Lorisleiva\Actions\Decorators\UniqueJobDecorator makeJob(\App\Models\User $user, \App\Models\Service $service, \App\Models\DeploymentData $deploymentData)
61-
* @method static \Lorisleiva\Actions\Decorators\UniqueJobDecorator makeUniqueJob(\App\Models\User $user, \App\Models\Service $service, \App\Models\DeploymentData $deploymentData)
62-
* @method static \Illuminate\Foundation\Bus\PendingDispatch dispatch(\App\Models\User $user, \App\Models\Service $service, \App\Models\DeploymentData $deploymentData)
63-
* @method static \Illuminate\Foundation\Bus\PendingDispatch|\Illuminate\Support\Fluent dispatchIf(bool $boolean, \App\Models\User $user, \App\Models\Service $service, \App\Models\DeploymentData $deploymentData)
64-
* @method static \Illuminate\Foundation\Bus\PendingDispatch|\Illuminate\Support\Fluent dispatchUnless(bool $boolean, \App\Models\User $user, \App\Models\Service $service, \App\Models\DeploymentData $deploymentData)
65-
* @method static dispatchSync(\App\Models\User $user, \App\Models\Service $service, \App\Models\DeploymentData $deploymentData)
66-
* @method static dispatchNow(\App\Models\User $user, \App\Models\Service $service, \App\Models\DeploymentData $deploymentData)
67-
* @method static dispatchAfterResponse(\App\Models\User $user, \App\Models\Service $service, \App\Models\DeploymentData $deploymentData)
68-
* @method static \App\Models\Deployment run(\App\Models\User $user, \App\Models\Service $service, \App\Models\DeploymentData $deploymentData)
60+
* @method static \Lorisleiva\Actions\Decorators\JobDecorator|\Lorisleiva\Actions\Decorators\UniqueJobDecorator makeJob(\App\Models\NodeTaskGroup $taskGroup, \App\Models\Service $service, \App\Models\DeploymentData $deploymentData, mixed $reviewAppId = null)
61+
* @method static \Lorisleiva\Actions\Decorators\UniqueJobDecorator makeUniqueJob(\App\Models\NodeTaskGroup $taskGroup, \App\Models\Service $service, \App\Models\DeploymentData $deploymentData, mixed $reviewAppId = null)
62+
* @method static \Illuminate\Foundation\Bus\PendingDispatch dispatch(\App\Models\NodeTaskGroup $taskGroup, \App\Models\Service $service, \App\Models\DeploymentData $deploymentData, mixed $reviewAppId = null)
63+
* @method static \Illuminate\Foundation\Bus\PendingDispatch|\Illuminate\Support\Fluent dispatchIf(bool $boolean, \App\Models\NodeTaskGroup $taskGroup, \App\Models\Service $service, \App\Models\DeploymentData $deploymentData, mixed $reviewAppId = null)
64+
* @method static \Illuminate\Foundation\Bus\PendingDispatch|\Illuminate\Support\Fluent dispatchUnless(bool $boolean, \App\Models\NodeTaskGroup $taskGroup, \App\Models\Service $service, \App\Models\DeploymentData $deploymentData, mixed $reviewAppId = null)
65+
* @method static dispatchSync(\App\Models\NodeTaskGroup $taskGroup, \App\Models\Service $service, \App\Models\DeploymentData $deploymentData, mixed $reviewAppId = null)
66+
* @method static dispatchNow(\App\Models\NodeTaskGroup $taskGroup, \App\Models\Service $service, \App\Models\DeploymentData $deploymentData, mixed $reviewAppId = null)
67+
* @method static dispatchAfterResponse(\App\Models\NodeTaskGroup $taskGroup, \App\Models\Service $service, \App\Models\DeploymentData $deploymentData, mixed $reviewAppId = null)
68+
* @method static \App\Models\Deployment run(\App\Models\NodeTaskGroup $taskGroup, \App\Models\Service $service, \App\Models\DeploymentData $deploymentData, mixed $reviewAppId = null)
6969
*/
7070
class StartDeployment {}
7171

api-nodes/Http/Controllers/EventController.php

+3-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ public function started(Node $node, AgentStartedEventData $data)
3939

4040
$publicAddr = $data->node->host->getPublicIpv4() ?? $data->node->host->getPrivateIpv4() ?? '127.0.0.1';
4141

42-
StartDeployment::run($node->team->owner, $demoService, DeploymentData::validateAndCreate([
42+
$taskGroup = NodeTaskGroup::createForUser($node->team->owner, $node->team, NodeTaskGroupType::LaunchService);
43+
44+
StartDeployment::run($taskGroup, $demoService, DeploymentData::validateAndCreate([
4345
'networkName' => 'ptah_net',
4446
'internalDomain' => 'demo.ptah.local',
4547
'processes' => [

app/Actions/HouseKeeping/PruneStaleDockerData.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212

1313
class PruneStaleDockerData
1414
{
15-
public function __invoke(): void
15+
public function prune(): void
1616
{
1717
$imagesToKeep = [];
1818

app/Actions/Nodes/InitCluster.php

+3-1
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,9 @@ private function createCaddyService(Swarm $swarm, Network $network, Node $node,
179179

180180
$deploymentData = $this->createCaddyDeploymentData($network, $node);
181181

182-
StartDeployment::run($caddyService->team->owner, $caddyService, $deploymentData);
182+
$taskGroup = NodeTaskGroup::createForUser($caddyService->team->owner, $caddyService->team, NodeTaskGroupType::LaunchService);
183+
184+
StartDeployment::run($taskGroup, $caddyService, $deploymentData);
183185
}
184186

185187
private function createCaddyDeploymentData(Network $network, Node $node): DeploymentData

app/Actions/Nodes/RebuildCaddy.php

+27-5
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace App\Actions\Nodes;
44

5+
use App;
56
use App\Models\Deployment;
67
use App\Models\DeploymentData\Caddy;
78
use App\Models\DeploymentData\EnvVar;
@@ -10,13 +11,36 @@
1011
use App\Models\NodeTasks\ApplyCaddyConfig\ApplyCaddyConfigMeta;
1112
use App\Models\NodeTaskType;
1213
use App\Models\Team;
14+
use Closure;
1315
use InvalidArgumentException;
1416
use Lorisleiva\Actions\Action;
1517

1618
class RebuildCaddy extends Action
1719
{
18-
// TODO: make the deployment optional? Remove it altogether?
19-
public function handle(Team $team, NodeTaskGroup $taskGroup, Deployment $deployment)
20+
public static function onceAfter(Team $team, NodeTaskGroup $taskGroup, Closure $callback): void
21+
{
22+
$lockKey = 'lock.'.RebuildCaddy::class.'.'.$taskGroup->id;
23+
24+
$shouldRun = ! App::has($lockKey);
25+
26+
if ($shouldRun) {
27+
App::scoped($lockKey, 'rebuild-caddy');
28+
}
29+
30+
try {
31+
$callback();
32+
} finally {
33+
if ($shouldRun) {
34+
App::forgetScopedInstances($lockKey);
35+
}
36+
}
37+
38+
if ($shouldRun) {
39+
self::run($team, $taskGroup);
40+
}
41+
}
42+
43+
public function handle(Team $team, NodeTaskGroup $taskGroup)
2044
{
2145
$caddyHandlers = [];
2246

@@ -208,9 +232,7 @@ public function handle(Team $team, NodeTaskGroup $taskGroup, Deployment $deploym
208232

209233
$taskGroup->tasks()->create([
210234
'type' => NodeTaskType::ApplyCaddyConfig,
211-
'meta' => ApplyCaddyConfigMeta::from([
212-
'deploymentId' => $deployment->id,
213-
]),
235+
'meta' => ApplyCaddyConfigMeta::from([]),
214236
'payload' => [
215237
'caddy' => $caddy,
216238
],

app/Actions/Services/CreateService.php

+5-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
namespace App\Actions\Services;
44

55
use App\Models\DeploymentData;
6+
use App\Models\NodeTaskGroup;
7+
use App\Models\NodeTaskGroupType;
68
use App\Models\Service;
79
use App\Models\Team;
810
use App\Models\User;
@@ -40,7 +42,9 @@ public function handle(User $user, Team $team, string $name, DeploymentData $dep
4042

4143
$service->save();
4244

43-
StartDeployment::run($user, $service, $deploymentData);
45+
$taskGroup = NodeTaskGroup::createForUser($user, $team, NodeTaskGroupType::LaunchService);
46+
47+
StartDeployment::run($taskGroup, $service, $deploymentData);
4448

4549
return $service;
4650
});
+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
namespace App\Actions\Services;
4+
5+
use App\Actions\Nodes\RebuildCaddy;
6+
use App\Models\NodeTaskGroup;
7+
use App\Models\ReviewApp;
8+
use Illuminate\Support\Facades\DB;
9+
10+
class DestroyReviewApp
11+
{
12+
public function destroy(NodeTaskGroup $taskGroup, ReviewApp $reviewApp): void
13+
{
14+
DB::transaction(function () use ($reviewApp, $taskGroup) {
15+
RebuildCaddy::onceAfter($reviewApp->team, $taskGroup, function () use ($reviewApp, $taskGroup) {
16+
(new UndeployDeployment)->undeploy($reviewApp->latestDeployment, $taskGroup);
17+
});
18+
19+
$reviewApp->delete();
20+
});
21+
}
22+
}
+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?php
2+
3+
namespace App\Actions\Services;
4+
5+
use App\Actions\Nodes\RebuildCaddy;
6+
use App\Models\NodeTaskGroup;
7+
use App\Models\ReviewApp;
8+
use App\Models\Service;
9+
use Illuminate\Support\Facades\DB;
10+
11+
class DestroyService
12+
{
13+
public function destroy(NodeTaskGroup $taskGroup, Service $service): void
14+
{
15+
DB::transaction(function () use ($taskGroup, $service) {
16+
RebuildCaddy::onceAfter($service->team, $taskGroup, function () use ($service, $taskGroup) {
17+
$service->reviewApps->each(function (ReviewApp $reviewApp) use ($taskGroup) {
18+
(new DestroyReviewApp)->destroy($taskGroup, $reviewApp);
19+
});
20+
21+
(new UndeployDeployment)->undeploy($service->latestDeployment, $taskGroup);
22+
});
23+
24+
$service->delete();
25+
});
26+
}
27+
}
+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
<?php
2+
3+
namespace App\Actions\Services;
4+
5+
use App\Models\DeploymentData;
6+
use App\Models\DeploymentData\Caddy;
7+
use App\Models\NodeTaskGroup;
8+
use App\Models\ReviewApp;
9+
use App\Models\ReviewApps\ReviewAppMeta;
10+
use App\Models\Service;
11+
use Illuminate\Support\Facades\DB;
12+
13+
class LaunchReviewApp
14+
{
15+
public function launch(NodeTaskGroup $taskGroup, Service $service, ReviewAppMeta $meta, array $process, array $worker): ReviewApp
16+
{
17+
$taskGroup->team->quotas()->reviewApps->ensureQuota();
18+
19+
return DB::transaction(function () use ($taskGroup, $service, $meta, $process, $worker) {
20+
$latestDeployment = $service->latestDeployment->data;
21+
22+
$deploymentData = $latestDeployment->fork($meta->ref, $process, $worker);
23+
24+
$reviewApp = $service->reviewApps()->updateOrCreate([
25+
'team_id' => $service->team_id,
26+
'process' => $process['name'],
27+
'worker' => $worker['name'],
28+
'ref' => $meta->ref,
29+
], [
30+
'ref_url' => $meta->refUrl,
31+
'visit_url' => $this->getVisitUrl($deploymentData, $process),
32+
]);
33+
34+
StartDeployment::run($taskGroup, $service, $deploymentData, $reviewApp->id);
35+
36+
return $reviewApp;
37+
});
38+
}
39+
40+
private function getVisitUrl(DeploymentData $deploymentData, array $process): ?string
41+
{
42+
$caddys = $deploymentData->findProcessByName($process['name'])->caddy;
43+
if (count($caddys) > 0) {
44+
/** @var Caddy $caddy */
45+
$caddy = $caddys[0];
46+
47+
$schema = $caddy->publishedPort === 80 ? 'http' : 'https';
48+
49+
return "{$schema}://{$caddy->domain}";
50+
}
51+
52+
return null;
53+
}
54+
}

app/Actions/Services/StartDeployment.php

+11-15
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
use App\Models\NodeTaskGroup;
99
use App\Models\NodeTaskGroupType;
1010
use App\Models\Service;
11-
use App\Models\User;
1211
use Illuminate\Support\Facades\DB;
1312
use Illuminate\Support\Facades\Request;
1413
use Lorisleiva\Actions\ActionRequest;
@@ -29,30 +28,25 @@ public function authorize(ActionRequest $request): bool
2928
return true;
3029
}
3130

32-
public function handle(User $user, Service $service, DeploymentData $deploymentData): Deployment
31+
public function handle(NodeTaskGroup $taskGroup, Service $service, DeploymentData $deploymentData, $reviewAppId = null): Deployment
3332
{
34-
// TODO: Call this in Authorize? In rules?
35-
$service->team->quotas()->deployments->ensureQuota();
36-
37-
return DB::transaction(function () use ($service, $deploymentData, $user) {
38-
$taskGroup = NodeTaskGroup::create([
39-
'swarm_id' => $service->swarm_id,
40-
'team_id' => $service->team_id,
41-
'invoker_id' => $user->id,
42-
'type' => NodeTaskGroupType::LaunchService,
43-
]);
33+
$taskGroup->team->quotas()->deployments->ensureQuota();
4434

35+
return DB::transaction(function () use ($taskGroup, $service, $deploymentData, $reviewAppId) {
4536
$deployment = $service->deployments()->create([
4637
'team_id' => $service->team_id,
4738
'data' => $deploymentData,
48-
'configured_by_id' => $user->id,
39+
'configured_by_id' => $taskGroup->invoker_id,
40+
'review_app_id' => $reviewAppId,
4941
]);
5042

5143
$deployment->taskGroups()->attach($taskGroup);
5244

5345
$taskGroup->tasks()->createMany($deployment->asNodeTasks());
5446

55-
RebuildCaddy::run($service->team, $taskGroup, $deployment);
47+
RebuildCaddy::onceAfter($service->team, $taskGroup, function () {
48+
// Intentionally left blank
49+
});
5650

5751
return $deployment;
5852
});
@@ -62,7 +56,9 @@ public function asController(Service $service, ActionRequest $request): Response
6256
{
6357
$deploymentData = DeploymentData::make($request->validated());
6458

65-
$this->handle($request->user(), $service, $deploymentData);
59+
$taskGroup = NodeTaskGroup::createForUser($request->user(), $service->team, NodeTaskGroupType::LaunchService);
60+
61+
$this->handle($taskGroup, $service, $deploymentData, null);
6662

6763
return to_route('services.deployments', $service);
6864
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
namespace App\Actions\Services;
4+
5+
use App\Actions\Nodes\RebuildCaddy;
6+
use App\Models\Deployment;
7+
use App\Models\DeploymentData\Process;
8+
use App\Models\DeploymentData\Worker;
9+
use App\Models\NodeTaskGroup;
10+
use App\Models\NodeTasks\DeleteService\DeleteServiceMeta;
11+
use App\Models\NodeTaskType;
12+
use Illuminate\Support\Facades\DB;
13+
14+
class UndeployDeployment
15+
{
16+
public function undeploy(Deployment $deployment, NodeTaskGroup $taskGroup): void
17+
{
18+
DB::transaction(function () use ($deployment, $taskGroup) {
19+
$tasks = collect($deployment->data->processes)->flatMap(function (Process $process) use ($deployment) {
20+
return collect($process->workers)->map(function (Worker $worker) use ($deployment) {
21+
return [
22+
'type' => NodeTaskType::DeleteService,
23+
'meta' => new DeleteServiceMeta($deployment->service->id, $worker->name, $deployment->service->name),
24+
'payload' => [
25+
'ServiceName' => $worker->dockerName,
26+
],
27+
];
28+
});
29+
})->toArray();
30+
31+
$taskGroup->tasks()->createMany($tasks);
32+
33+
RebuildCaddy::onceAfter($deployment->team, $taskGroup, function () {
34+
// Intentionally left blank
35+
});
36+
});
37+
}
38+
}

0 commit comments

Comments
 (0)