-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathfeature_sync.php
More file actions
158 lines (136 loc) · 5.59 KB
/
Copy pathfeature_sync.php
File metadata and controls
158 lines (136 loc) · 5.59 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
<?php
namespace Deployer;
require_once('feature_init.php');
/**
* Verifies that the database host is reachable from the remote server before syncing.
* This is necessary because DNS for newly created databases (e.g. Mittwald) can be
* intermittent, and the time between feature:setup and feature:sync may cause DNS flapping.
*
* After confirming reachability, the hostname is resolved to its IP address and replaced
* in the shared .env file to eliminate DNS dependency for all subsequent commands.
*/
function waitForDatabaseHost(): void
{
if (!has('database_host')
|| '127.0.0.1' === get('database_host')
|| 'localhost' === get('database_host')
) {
return;
}
$hostname = get('database_host');
$port = (int) get('database_port', 3306);
$waitingTime = (int) get('mittwald_database_wait', 30);
$maxRetries = (int) get('mittwald_database_retries', 20);
while ($maxRetries > 0) {
try {
info("Verifying database host {$hostname}:{$port} before sync, remaining attempts: {$maxRetries}");
$check = sprintf(
'echo @fsockopen("%s", %d, $errno, $errstr, 5) ? "1" : "";',
$hostname,
$port
);
$result = run("php -r " . escapeshellarg($check));
if ('1' === trim($result)) {
info("Database host {$hostname}:{$port} is reachable.");
resolveDatabaseHostToIp($hostname);
return;
}
} catch (\Throwable $e) {
debug("Pre-sync connectivity check failed: " . $e->getMessage());
}
if ($maxRetries > 1) {
sleep($waitingTime);
}
$maxRetries--;
}
throw new \RuntimeException(
"Database host {$hostname}:{$port} is not reachable before sync after all attempts."
);
}
/**
* Resolves the database hostname to its IP address and replaces it in the shared .env file.
* This eliminates DNS dependency for all subsequent remote commands (TYPO3 CLI, etc.),
* working around DNS flapping for newly created databases on Mittwald infrastructure.
*
* Background: After Mittwald creates a new database, the DNS entry for the host
* (e.g. mysql-xyz.pg-s-xxx.db.project.host) is intermittently resolvable for several
* minutes - it resolves, then fails, then resolves again. A TCP connectivity check
* passing does not guarantee that the next process can resolve the hostname seconds later.
* By resolving the hostname to its IP once and writing the IP to .env, all subsequent
* commands (db_sync_tool, typo3 database:updateschema, etc.) bypass DNS entirely.
*
* This workaround can be removed if Mittwald provides a stable DNS propagation status
* via their API or resolves the underlying DNS flapping issue.
*
* @see MittwaldApi::checkDatabaseHostReachable() for the initial TCP connectivity check
*/
function resolveDatabaseHostToIp(string $hostname): void
{
if (!get('mittwald_resolve_host_to_ip', false)) {
debug("Hostname-to-IP resolution disabled, keeping host {$hostname} in .env.");
return;
}
$resolveCmd = sprintf('echo gethostbyname("%s");', $hostname);
$ip = trim(run("php -r " . escapeshellarg($resolveCmd)));
if ($ip === $hostname) {
debug("Could not resolve {$hostname} to IP, skipping .env replacement.");
return;
}
$envPath = get('deploy_path') . '/shared/.env';
if (!test("[ -f {$envPath} ]")) {
debug("No .env file found at {$envPath}, skipping hostname replacement.");
return;
}
$escapedHostname = str_replace('.', '\\.', $hostname);
run(sprintf("sed -i 's/%s/%s/g' %s", $escapedHostname, $ip, $envPath));
set('database_host', $ip);
info("Resolved database host {$hostname} to {$ip} in .env");
}
task('feature:wait_for_database', function () {
if ((has('feature_setup') && !get('feature_setup')) || !featureRequested()) return;
waitForDatabaseHost();
})
->select('type=feature-branch-deployment')
->once()
->desc('Wait for database host to be reachable')
;
task('feature:sync', function () {
if ((has('feature_setup') && !get('feature_setup')) || !featureRequested()) return;
$feature = initFeature();
$synced = false;
$optionalVerbose = isVerbose() ? '-v' : '';
/*
* db_sync_tool
* https://github.com/jackd248/db-sync-tool
*/
if (get('db_sync_tool') !== false) {
if (commandExistLocally("{{db_sync_tool}}")) {
info('Synching database');
runLocally("{{db_sync_tool}} -f {{feature_sync_config}} --target-path {{feature_sync_target_path}} --use-rsync -y $optionalVerbose");
$synced = true;
} else {
debug("Skipping database sync, command \”{{db_sync_tool}}\” not available");
}
} else {
debug("Skipping database sync, db_sync_tool was disabled");
}
/*
* file_sync_tool
* https://github.com/jackd248/file-sync-tool
*/
if (get('file_sync_tool') !== false) {
if (commandExistLocally("{{file_sync_tool}}")) {
info('Synching files');
runLocally("{{file_sync_tool}} -f {{feature_sync_config}} --files-target {{feature_sync_target_path_files}} $optionalVerbose");
$synced = true;
} else {
debug("Skipping file sync, command \”{{file_sync_tool}}\” not available");
}
} else {
debug("Skipping file sync, file_sync_tool was disabled");
}
if ($synced) info("feature branch <fg=magenta;options=bold>$feature</> was successfully synced");
})
->select('type=feature-branch-deployment')
->desc('Sync a feature branch')
;