-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfunctions.php
More file actions
308 lines (270 loc) · 10.5 KB
/
Copy pathfunctions.php
File metadata and controls
308 lines (270 loc) · 10.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
<?php
namespace Deployer;
use Deployer\Exception\RunException;
use MoveElevator\DeployerTools\Utility\FeatureUtility;
use Symfony\Component\Console\Output\OutputInterface;
/**
* @param $message
* @return void
*/
function debug($message): void
{
if (isVerbose() && !empty($message)) {
writeln("<fg=yellow;options=bold>debug</> " . parse($message));
}
}
/**
* @return bool
*/
function isVerbose(): bool
{
return in_array(output()->getVerbosity(), [OutputInterface::VERBOSITY_VERBOSE, OutputInterface::VERBOSITY_VERY_VERBOSE, OutputInterface::VERBOSITY_DEBUG], true);
}
/**
* @return void
*/
function checkVerbosity(): void
{
if (!empty(getenv('DEPLOYER_CONFIG_VERBOSE'))) {
output()->setVerbosity(OutputInterface::VERBOSITY_VERBOSE);
}
}
/**
* Whether a feature instance was addressed via --feature.
*
* A blank --feature= counts as absent: it would otherwise resolve to the base
* instance path and let the feature scaffolding write into the reference stage.
* Only null and blank strings qualify — empty() would also swallow the perfectly
* valid feature name "0". The option itself only exists once the feature recipe
* is loaded, hence hasOption().
*
* @return bool
*/
function featureRequested(): bool
{
if (!input()->hasOption('feature')) {
return false;
}
$feature = input()->getOption('feature');
return null !== $feature && '' !== trim((string)$feature);
}
/**
* Normalize a feature identifier into the flat instance name used for the deploy
* directory, the public url segment, the url shortener symlink and the database name.
*
* Branch names may carry path separators ("feature/ABC-12"), which would otherwise
* nest the instance directory and break listing, cleanup and deletion. Identifiers
* that already consist of allowed characters only are returned unchanged.
*
* @param ?string $feature
* @return string
*/
function getFeatureName(?string $feature = null): string
{
if (null === $feature || '' === trim($feature)) {
$feature = featureRequested() ? (string)input()->getOption('feature') : '';
}
return FeatureUtility::normalize($feature);
}
/**
* Extend the deployer configuration with available environment variables (starting with "DEPLOYER_CONFIG_"):
*
* ENVIRONMENT_VARIABLE => deployer_configuration
* e.g.
* DEPLOYER_CONFIG_DATABASE_PASSWORD => database_password
*
* @return void
* @throws \Deployer\Exception\Exception
*/
function prepareDeployerConfiguration(): void {
$environmentVariables = getenv();
debug('Extending deployer configuration with environment variables');
foreach ($environmentVariables as $key => $value) {
if (str_starts_with($key, 'DEPLOYER_CONFIG_')) {
$configName = strtolower(str_replace('DEPLOYER_CONFIG_', '', $key));
if (has($configName)) {
debug("Attention: overwriting existing deployer configuration '$configName' with environment variable '$key' ");
}
set($configName, $value);
}
}
}
/**
* Check if a subcommand is available, e.g. "php bin/console", "ckeditor:install"
*
* @param string $command
* @param string $subcommand
* @return bool
* @throws \Deployer\Exception\Exception
* @throws \Deployer\Exception\RunException
* @throws \Deployer\Exception\TimeoutException
*/
function commandSupportSubcommand(string $command, string $subcommand): bool
{
$check = run("( $command list 2>&1 || $command --list) | grep -- $subcommand || true");
if (empty($check)) {
return false;
}
return str_contains($check, $subcommand);
}
/**
* Check if command exists locally
*
* @throws RunException
*/
function commandExistLocally(string $command): bool
{
return testLocally("hash $command 2>/dev/null");
}
/**
* Checks whether a local path is executable. Unlike commandExistLocally()/hash (built
* for PATH lookups), this reliably resolves a relative path containing a slash, e.g.
* vendor/bin/sync-tool.
*/
function isExecutableLocally(string $path): bool
{
return testLocally('[ -x ' . escapeshellarg($path) . ' ]');
}
/**
* Resolves the sync tool binary to use: prefers the Composer-installed php-sync-tool
* (https://github.com/konradmichalik/php-sync-tool) when present locally, falling back
* to the legacy PATH-installed Python tool (db-sync-tool/file-sync-tool) otherwise.
*
* Lets each downstream project migrate independently by running
* `composer require --dev konradmichalik/php-sync-tool` - no deploy.php opt-in needed.
*/
function resolveSyncTool(string $legacyBinary, string $phpBinary = 'vendor/bin/sync-tool'): string
{
return isExecutableLocally($phpBinary) ? $phpBinary : $legacyBinary;
}
/**
* Whether a resolved sync tool binary is php-sync-tool rather than the legacy tool.
* Compared against the known php-sync-tool path, not path-shape - a downstream
* project's legacy binary can itself be configured as an absolute or relative path,
* which would otherwise be misclassified.
*/
function usingPhpSyncTool(string $resolvedBinary, string $phpBinary = 'vendor/bin/sync-tool'): bool
{
return $resolvedBinary === $phpBinary;
}
/**
* Checks whether a resolved sync tool binary is actually available locally: a path
* (containing a slash) via isExecutableLocally(), a bare PATH command via
* commandExistLocally().
*/
function syncToolAvailableLocally(string $binary): bool
{
return str_contains($binary, '/')
? isExecutableLocally($binary)
: commandExistLocally($binary);
}
/**
* Resolves the sync tool binary and verifies it is actually available locally, or
* throws. Used by the dev:* tasks, which have no "disabled" concept and must hard-fail
* rather than silently skip when the tool is missing.
*
* @throws \RuntimeException if db_sync_tool was disabled or is not available locally
*/
function requireSyncTool(string $action): string
{
$dbSyncTool = get('db_sync_tool');
if (false === $dbSyncTool) {
throw new \RuntimeException("db_sync_tool was disabled, cannot $action.");
}
if (!syncToolAvailableLocally($dbSyncTool)) {
throw new \RuntimeException("Sync tool \"$dbSyncTool\" not available locally.");
}
return $dbSyncTool;
}
/**
* Runs a remote command with the possibility to overwrite the default command options
*/
function runExtended(string $command, ?array $options = [], ?int $timeout = null, ?int $idle_timeout = null, ?string $secret = null, ?array $env = null, ?bool $real_time_output = null, ?bool $no_throw = null): string
{
return run(
$command,
$options,
$timeout ?? (int)get('run_timeout'),
$idle_timeout ?? (int)get('run_idle_timeout'),
$secret ?? (string)get('run_secret'),
$env ?? (array)get('run_env'),
$real_time_output ?? (bool)get('run_real_time_output'),
$no_throw ?? (bool)get('run_no_throw')
);
}
function getRecentDatabaseCacheDumpPath(): string
{
return get('dev_tr_db_dump_dir') . '/' . getRecentDatabaseCacheDumpFilename() . '.sql';
}
function getRecentDatabaseCacheDumpFilename(): string
{
return date('Y-m-d');
}
function recentDatabaseCacheDumpExists(): bool
{
return test('[ -f ' . getRecentDatabaseCacheDumpPath() . ' ]');
}
function cleanUpDatabaseCacheDumps(int $days = 7): void
{
$dbDumpDir = get('dev_tr_db_dump_dir');
run("mkdir -p $dbDumpDir");
// cleanup beforehand: delete all dump files with the above naming scheme older than 7 days
run("find $dbDumpDir -name '*.sql' -mtime +$days -delete");
}
/**
* Applies standardized file and directory permissions for a deployment.
*
* Handles:
* - var/cache creation and writable permissions
* - SGID-aware directory permissions for group inheritance
* - Restrictive file permissions
* - Shared directory baseline and writable permissions
*
* Config keys (with defaults):
* - writable_chmod_mode_files: '644'
* - writable_chmod_mode_dirs: '2755'
* - writable_chmod_mode_writable_dirs: '2775'
*/
function applyDeployPermissions(): void
{
$modeFiles = has('writable_chmod_mode_files') ? get('writable_chmod_mode_files') : '644';
$modeDirs = has('writable_chmod_mode_dirs') ? get('writable_chmod_mode_dirs') : '2755';
$modeWritableDirs = has('writable_chmod_mode_writable_dirs') ? get('writable_chmod_mode_writable_dirs') : '2775';
// Ensure var/cache exists before any cache operation
runExtended("cd {{ release_path }} && mkdir -p var/cache");
// Set SGID + writable permissions on var directories
runExtended("cd {{ release_path }} && chmod $modeWritableDirs var var/cache");
// Set proper directory permissions with SGID for group inheritance, skip var
runExtended("cd {{ release_path }} && find . -path \"./var\" -prune -o -type d -exec chmod $modeDirs {} +");
// Set restrictive file permissions
runExtended("cd {{ release_path }} && find . -type f -exec chmod $modeFiles {} +");
// var/cache directories need recursive SGID+writable permissions; files need group-writable
runExtended("cd {{ release_path }} && find var/cache -type d -exec chmod $modeWritableDirs {} +");
runExtended("cd {{ release_path }} && find var/cache -type f -exec chmod 664 {} +");
// Restore writable permissions on release-path-only writable dirs (not shared, not var)
$sharedDirs = has('shared_dirs') ? get('shared_dirs') : [];
foreach (has('writable_dirs') ? get('writable_dirs') : [] as $dir) {
if (str_starts_with($dir, 'var') || in_array($dir, $sharedDirs)) {
continue;
}
if (test("[ -d {{ release_path }}/$dir ]")) {
runExtended("find {{ release_path }}/$dir -type d -exec chmod $modeWritableDirs {} +");
runExtended("find {{ release_path }}/$dir -type f -exec chmod 664 {} +");
}
}
// Fix shared directory permissions (baseline)
runExtended("cd {{ deploy_path }}/shared && find . -type d -exec chmod $modeDirs {} +");
runExtended("cd {{ deploy_path }}/shared && find . -type f -exec chmod $modeFiles {} +");
// Shared writable directories need broader permissions (recursive for subdirectories)
foreach (get('shared_dirs') as $dir) {
if (test("[ -d {{ deploy_path }}/shared/$dir ]")) {
runExtended("find {{ deploy_path }}/shared/$dir -type d -exec chmod $modeWritableDirs {} +");
}
}
// Tighten permissions on shared sensitive files (.env, credentials)
foreach (has('shared_files') ? get('shared_files') : [] as $file) {
if (test("[ -f {{ deploy_path }}/shared/$file ]")) {
runExtended("chmod 640 {{ deploy_path }}/shared/$file");
}
}
}