Skip to content

Commit 92e0279

Browse files
authored
Merge pull request #4 from J-T-McC/feature/deployment-naming
Feature/deployment naming
2 parents 0b0f893 + a8ac1ba commit 92e0279

File tree

9 files changed

+291
-16
lines changed

9 files changed

+291
-16
lines changed

README.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ return [
4545

4646
/**
4747
* Symbolic link to the current deployed build
48-
* This path should be used for schedules and setting your web root.
48+
* This path will be used for schedules and setting your web root.
4949
*/
5050
'deployment-link' => env('ATM_DEPLOYMENT_LINK'),
5151

@@ -85,6 +85,16 @@ return [
8585
*/
8686
'deployment-class' => \JTMcC\AtomicDeployments\Services\Deployment::class,
8787

88+
/**
89+
* Logic used when creating a deployment directory
90+
*
91+
* Default => git - uses hash for current HEAD
92+
* Options: [ git, rand ]
93+
*
94+
* If your build does not use git, use rand.
95+
*/
96+
'directory-naming' => 'git'
97+
8898
];
8999
```
90100

config/atomic-deployments.php

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
/**
66
* Symbolic link to the current deployed build
7-
* This path should be used for schedules and setting your web root.
7+
* This path will be used for schedules and setting your web root.
88
*/
99
'deployment-link' => env('ATM_DEPLOYMENT_LINK'),
1010

@@ -28,13 +28,14 @@
2828
'build-limit' => 10,
2929

3030
/**
31-
* Migrate files|folders from the outgoing production build to your new release using a relative path and pattern.
31+
* Logic used when creating a deployment directory.
3232
*
33-
* @see https://www.php.net/manual/en/function.glob.php
33+
* Default => git - uses hash for current HEAD
34+
* Options: [ git, rand ]
35+
*
36+
* If your build does not use git, use rand.
3437
*/
35-
'migrate' => [
36-
// 'storage/framework/sessions/*',
37-
],
38+
'directory-naming' => 'git',
3839

3940
/**
4041
* Deployment class used.
@@ -44,4 +45,13 @@
4445
*/
4546
'deployment-class' => \JTMcC\AtomicDeployments\Services\Deployment::class,
4647

48+
/**
49+
* Migrate files|folders from the outgoing production build to your new release using a relative path and pattern.
50+
*
51+
* @see https://www.php.net/manual/en/function.glob.php
52+
*/
53+
'migrate' => [
54+
// 'storage/framework/sessions/*',
55+
],
56+
4757
];

src/Helpers/FileHelper.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@
33
namespace JTMcC\AtomicDeployments\Helpers;
44

55
use Illuminate\Support\Facades\File;
6+
use JTMcC\AtomicDeployments\Exceptions\ExecuteFailedException;
67
use JTMcC\AtomicDeployments\Exceptions\InvalidPathException;
8+
use JTMcC\AtomicDeployments\Services\Exec;
9+
use RecursiveDirectoryIterator;
10+
use RecursiveIteratorIterator;
711

812
class FileHelper
913
{
@@ -24,4 +28,27 @@ public static function confirmPathsExist(string ...$paths): bool
2428

2529
return true;
2630
}
31+
32+
/**
33+
* Recursively update symbolic links with new endpoint.
34+
*
35+
* @param $from
36+
* @param $to
37+
*
38+
* @throws ExecuteFailedException
39+
*/
40+
public static function recursivelyUpdateSymlinks($from, $to)
41+
{
42+
$dir = new RecursiveDirectoryIterator($to);
43+
foreach (new RecursiveIteratorIterator($dir) as $file) {
44+
if (is_link($file)) {
45+
$link = $file->getPathName();
46+
$target = $file->getLinkTarget();
47+
$newPath = str_replace($from, $to, $target);
48+
if ($target !== $newPath) {
49+
Exec::ln($link, $newPath);
50+
}
51+
}
52+
}
53+
}
2754
}

src/Services/AtomicDeploymentService.php

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use JTMcC\AtomicDeployments\Events\DeploymentFailed;
1111
use JTMcC\AtomicDeployments\Events\DeploymentSuccessful;
1212
use JTMcC\AtomicDeployments\Exceptions\ExecuteFailedException;
13+
use JTMcC\AtomicDeployments\Helpers\FileHelper;
1314
use JTMcC\AtomicDeployments\Interfaces\DeploymentInterface;
1415
use JTMcC\AtomicDeployments\Models\AtomicDeployment as Model;
1516
use JTMcC\AtomicDeployments\Models\Enums\DeploymentStatus;
@@ -89,6 +90,7 @@ public function deploy(?Closure $success = null, ?Closure $failed = null): void
8990
$this->createDeploymentDirectory();
9091
$this->copyDeploymentContents();
9192
$this->copyMigrationContents();
93+
$this->updateSymlinks();
9294
$this->linkDeployment();
9395
$this->confirmSymbolicLink();
9496

@@ -123,7 +125,7 @@ public function updateDeploymentStatus(int $status): void
123125

124126
public function linkDeployment(): void
125127
{
126-
Output::info("Creating web root symbolic link: {$this->deployment->getDeploymentLink()} -> {$this->deployment->getDeploymentPath()}");
128+
Output::info("Creating symbolic link: {$this->deployment->getDeploymentLink()} -> {$this->deployment->getDeploymentPath()}");
127129
if ($this->isDryRun()) {
128130
Output::warn('Dry run - Skipping symbolic link deployment');
129131

@@ -237,6 +239,27 @@ public function copyMigrationContents(): void
237239
}
238240
}
239241

242+
/**
243+
* @throws ExecuteFailedException
244+
*/
245+
public function updateSymlinks()
246+
{
247+
Output::info('Correcting old symlinks that still reference the build directory');
248+
249+
if ($this->isDryRun()) {
250+
Output::warn('Dry run - skipping symlink corrections');
251+
252+
return;
253+
}
254+
255+
FileHelper::recursivelyUpdateSymlinks(
256+
$this->getDeployment()->getBuildPath(),
257+
$this->getDeployment()->getDeploymentPath()
258+
);
259+
260+
Output::info('Finished correcting symlinks');
261+
}
262+
240263
public function rollback(): void
241264
{
242265
Output::warn('Atomic deployment rollback has been requested');
@@ -249,22 +272,21 @@ public function rollback(): void
249272
$this->initialDeploymentPath &&
250273
$this->initialDeploymentPath !== $currentPath
251274
) {
252-
Output::emergency('Atomic deployment rollback has been requested');
253-
Output::emergency("Attempting to link web root to {$this->initialDeploymentPath}");
275+
Output::emergency("Attempting to link deployment at {$this->initialDeploymentPath}");
254276

255277
try {
256278
//attempt to revert link to our original path
257279
Exec::ln($this->deployment->getDeploymentLink(), $this->initialDeploymentPath);
258280
if ($this->deployment->getCurrentDeploymentPath() === $this->initialDeploymentPath) {
259-
Output::info('Successfully rolled back symbolic web root');
281+
Output::info('Successfully rolled back symbolic link');
260282

261283
return;
262284
}
263285
} catch (ExecuteFailedException $e) {
264286
Output::throwable($e);
265287
}
266288

267-
Output::emergency('Failed to roll back symbolic web root');
289+
Output::emergency('Failed to roll back symbolic link');
268290

269291
return;
270292
}

src/Services/Deployment.php

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
namespace JTMcC\AtomicDeployments\Services;
66

77
use Illuminate\Support\Facades\File;
8+
use Illuminate\Support\Str;
89
use JTMcC\AtomicDeployments\Exceptions\ExecuteFailedException;
910
use JTMcC\AtomicDeployments\Exceptions\InvalidPathException;
1011
use JTMcC\AtomicDeployments\Helpers\FileHelper;
@@ -18,6 +19,7 @@ class Deployment implements DeploymentInterface
1819
protected string $buildPath;
1920
protected string $deploymentLink;
2021
protected string $deploymentsPath;
22+
protected string $directoryNaming;
2123

2224
protected string $deploymentPath = '';
2325
protected string $deploymentDirectory = '';
@@ -32,6 +34,7 @@ public function __construct(AtomicDeployment $model)
3234
$this->deploymentLink = config('atomic-deployments.deployment-link');
3335
$this->deploymentsPath = config('atomic-deployments.deployments-path');
3436
$this->buildPath = config('atomic-deployments.build-path');
37+
$this->directoryNaming = config('atomic-deployments.directory-naming');
3538
$this->model = $model;
3639

3740
if ($this->model->deployment_path) {
@@ -67,6 +70,11 @@ public function setDeploymentDirectory(string $name = ''): void
6770
$this->setDeploymentPath();
6871
}
6972

73+
public function getDeploymentDirectory(): string
74+
{
75+
return $this->deploymentDirectory;
76+
}
77+
7078
/**
7179
* Get the current symlinked deployment path.
7280
*
@@ -84,6 +92,22 @@ public function getCurrentDeploymentPath(): string
8492
return $result;
8593
}
8694

95+
/**
96+
* @throws ExecuteFailedException
97+
*
98+
* @return string
99+
*/
100+
public function getDirectoryName()
101+
{
102+
switch ($this->directoryNaming) {
103+
case 'rand':
104+
return Str::random(5).time();
105+
case 'git':
106+
default:
107+
return Exec::getGitHash();
108+
}
109+
}
110+
87111
/**
88112
* Sets full path for deployment.
89113
*
@@ -93,9 +117,7 @@ public function getCurrentDeploymentPath(): string
93117
public function setDeploymentPath(): void
94118
{
95119
if (empty(trim($this->deploymentDirectory))) {
96-
//default directory name to current HEAD hash
97-
//TODO add configurable option for alternatives
98-
$this->setDeploymentDirectory(Exec::getGitHash());
120+
$this->setDeploymentDirectory($this->getDirectoryName());
99121
}
100122

101123
if (strpos($this->deploymentsPath, $this->buildPath) !== false) {

tests/Integration/Services/AtomicDeploymentServiceTest.php

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
namespace Tests\Integration\Services;
44

55
use Illuminate\Foundation\Testing\RefreshDatabase;
6+
use JTMcC\AtomicDeployments\Events\DeploymentFailed;
7+
use JTMcC\AtomicDeployments\Events\DeploymentSuccessful;
68
use JTMcC\AtomicDeployments\Exceptions\InvalidPathException;
79
use JTMcC\AtomicDeployments\Models\AtomicDeployment;
810
use JTMcC\AtomicDeployments\Models\Enums\DeploymentStatus;
@@ -90,4 +92,56 @@ public function it_doesnt_allow_deployments_folder_to_be_subdirectory_of_build_f
9092
$atomicDeployment = self::getAtomicDeployment();
9193
$atomicDeployment->createDeploymentDirectory();
9294
}
95+
96+
/**
97+
* @test
98+
*/
99+
public function it_rolls_back_symbolic_link_to_deployment_detected_on_boot()
100+
{
101+
$atomicDeployment1 = self::getAtomicDeployment();
102+
$atomicDeployment1->createDeploymentDirectory();
103+
$atomicDeployment1->linkDeployment();
104+
$this->assertTrue($atomicDeployment1->getDeployment()->isDeployed());
105+
106+
$atomicDeployment2 = self::getAtomicDeployment('abc123');
107+
$atomicDeployment2->createDeploymentDirectory();
108+
$atomicDeployment2->linkDeployment();
109+
110+
$this->assertTrue($atomicDeployment2->getDeployment()->isDeployed());
111+
$this->assertFalse($atomicDeployment1->getDeployment()->isDeployed());
112+
113+
$atomicDeployment2->rollback();
114+
115+
$this->assertTrue($atomicDeployment1->getDeployment()->isDeployed());
116+
}
117+
118+
/**
119+
* @test
120+
*/
121+
public function it_calls_closure_on_success()
122+
{
123+
$this->expectsEvents(DeploymentSuccessful::class);
124+
$success = false;
125+
self::getAtomicDeployment()->deploy(function () use (&$success) {
126+
$success = true;
127+
});
128+
$this->assertTrue($success);
129+
}
130+
131+
/**
132+
* @test
133+
*/
134+
public function it_calls_closure_on_failure()
135+
{
136+
$this->app['config']->set('atomic-deployments.build-path', $this->buildPath);
137+
$this->app['config']->set('atomic-deployments.deployments-path', $this->buildPath.'/deployments');
138+
$this->expectsEvents(DeploymentFailed::class);
139+
$this->expectException(InvalidPathException::class);
140+
$failed = false;
141+
$atomicDeployment = self::getAtomicDeployment();
142+
$atomicDeployment->deploy(fn () => '', function () use (&$failed) {
143+
$failed = true;
144+
});
145+
$this->assertTrue($failed);
146+
}
93147
}

0 commit comments

Comments
 (0)