-
Notifications
You must be signed in to change notification settings - Fork 3
/
pantheon-sites-accounts-rename
executable file
·268 lines (227 loc) · 11 KB
/
pantheon-sites-accounts-rename
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
#!/usr/bin/php
<?php
/** @noinspection NotOptimalIfConditionsInspection */
/**
* @file
* Iterate over all a given list of sites and rename usernames/email addresses.
*/
# Instructions.
$CSV = $argv[1];
if (!$CSV) {
$CSV = readline("CSV filename (with a header row, and columns for: \"existing username\", \"new username\", \"existing email address\", \"new email address\") (or next time, include this as an argument)?");
}
# Can we access/read the file?
if (!file_exists($CSV)) {
echo "$CSV does not exist";
die(1);
}
if (($stream = fopen($CSV, 'rb')) === false) {
echo "$CSV is not readable";
die(1);
}
# Skip the header row.
fgetcsv($stream);
# Confirmations.
$row = 0;
$email_conversions = [];
$d7sql = '';
$d8sql = '';
$wpsql = [];
while (($account = fgetcsv($stream)) !== FALSE) {
$row++;
echo "ACCOUNT:" . PHP_EOL;
echo " existing username: " . $account[0] . PHP_EOL;
echo " new username: " . $account[1] . PHP_EOL;
echo " existing email: " . $account[2] . PHP_EOL;
echo " new email: " . $account[3] . PHP_EOL;
$email_conversions[$account[2]] = $account[3];
// Build SQL (there's no drush commands for this stuff).
$d7sql .= " UPDATE IGNORE users SET mail='" . $account[3] . "' WHERE mail='" . $account[2] . "' LIMIT 1; ";
$d7sql .= " UPDATE IGNORE users SET name='" . $account[1] . "' WHERE name='" . $account[0] . "' LIMIT 1; ";
$d8sql .= " UPDATE IGNORE users_field_data SET mail='" . $account[3] . "' WHERE mail='" . $account[2] . "' LIMIT 1; ";
$d8sql .= " UPDATE IGNORE users_field_data SET name='" . $account[1] . "' WHERE name='" . $account[0] . "' LIMIT 1; ";
// WP won't let us run compound SQL statements. Instead we need to run multiple times.
$wpsql[] = " UPDATE IGNORE wp_users SET user_email='" . $account[3] . "' WHERE user_email='" . $account[2] . "' LIMIT 1";
$wpsql[] = " UPDATE IGNORE wp_users SET user_login='" . $account[1] . "' WHERE user_login='" . $account[0] . "' LIMIT 1";
$wpsql[] = " UPDATE IGNORE wp_users SET user_nicename='" . $account[1] . "' WHERE user_nicename='" . $account[0] . "' LIMIT 1";
}
fclose($stream);
echo PHP_EOL . "$row accounts to convert." . PHP_EOL;
$CONFIRM = readline("Account email address and username, and security notification emails will be converted. Confirm (y/N)");
if (!in_array($CONFIRM, ['y', 'Y'])) {
echo "abort";
exit;
}
echo PHP_EOL;
$SITES = $argv[2];
if (!$SITES) {
$SITES = readline('List of sites to convert (comma-separated). You could obtain this with something like `terminus site:list --format=list --field=name --filter="frozen!=1" --org=Advomatic | sort | gsed -z "s/\n/,/g"` (or next time, include this as the second argument)?');
}
$SITES = array_filter(explode(',', $SITES), 'trim');
echo count($SITES) . " sites to convert: " . implode(', ', $SITES) . PHP_EOL . PHP_EOL;
$CONFIRM_AUTO_DEPLOY = readline("Any Drupal 8/9 sites will need to deploy config changes from dev->test->live. Can we assume that there is no undeployed work on these sites that will be accidentally included? (Y/n)");
$CONFIRM_AUTO_DEPLOY = !$CONFIRM_AUTO_DEPLOY || in_array($CONFIRM_AUTO_DEPLOY, ['y', 'Y']);
if (!$CONFIRM_AUTO_DEPLOY) {
echo "Okay, we'll ask you to confirm each deploy (if there are any)." . PHP_EOL;
}
echo "This will take a really long time..." . PHP_EOL . PHP_EOL;
## Debug
## D7
//$sites=["aftorg"];
## D9
//$sites=["columbia-sps"];
## WP
//$sites=["advomatic"];
foreach ($SITES as $site) {
$framework = trim(shell_exec("terminus site:info --field=framework $site"));
echo PHP_EOL . "**Converting $framework site: $site**" . PHP_EOL;
$environments= array_filter(explode(',', preg_replace('![\r\n]!', ',', shell_exec("terminus env:list $site --filter='initialized=1' --format=list --field=id"))), 'trim');
## Debug
// $environments = ['dev'];
foreach ($environments as $environment) {
echo "Converting $framework site: $site.$environment" . PHP_EOL;
switch ($framework) {
case "drupal":
// Accounts.
shell_exec_bail("echo \"$d7sql\" | terminus drush $site.$environment -- sql-cli 2>&1");
// Search for other unblocked usernames that start with "advo".
$unblocked_advoteam = trim(shell_exec("echo \"SELECT name, mail FROM users WHERE (name LIKE 'advo%' or mail LIKE '%@advomatic.com') AND status = 1\" | terminus drush $site.$environment sql-cli 2>&1 | grep -v \"This environment is in read-only Git mode\" | grep -v \" drush sql-cli \""));
if ($unblocked_advoteam) {
echo " UNBLOCKED ADVOTEAM ACCOUNTS. Probably test accounts. Please review" . PHP_EOL;
echo $unblocked_advoteam . PHP_EOL;
}
// Security update notification.
// Need to dump stderr because terminus is so chatty, and we don't want
// to mix its messages with our JSON. This _will_ hide errors.
$security_emails = trim(shell_exec("terminus drush $site.$environment -- variable-get --format=json update_notify_emails 2>/dev/null"));
if (!$security_emails) {
die('Could not retrieve update_notify_emails');
}
$converted_email_count = convert_emails($security_emails, $email_conversions);
if ($converted_email_count) {
$security_emails = json_encode($security_emails, JSON_THROW_ON_ERROR);
shell_exec_bail("terminus drush $site.$environment -- variable-set --format=json update_notify_emails '$security_emails' 2>&1");
}
else {
echo " notice: no security update emails to convert." . PHP_EOL;
}
break;
case "drupal8":
// Security update notification.
if ($environment === 'dev') {
// We're going to write/deploy config later, so pull a fresh copy of
// the database, as long as this site's live environment actually
// exists.
if (trim(shell_exec("terminus env:info $site.$environment --fields=initialized --format=string"))) {
echo " Pulling a fresh DB to $site.$environment. " . PHP_EOL;
shell_exec_bail("terminus -q -y env:clone-content $site.live $environment 2>&1");
}
// Need to dump stderr because terminus is so chatty, and we don't
// want to mix its messages with our JSON. This _will_ hide errors.
$security_emails = trim(shell_exec("terminus drush $site.$environment -- config-get --format=json update.settings notification.emails 2>/dev/null"));
if (!$security_emails) {
die('Could not retrieve update_notify_emails');
}
$converted_email_count = convert_emails($security_emails, $email_conversions);
if ($converted_email_count) {
echo " Converting $converted_email_count security update emails. " . PHP_EOL;
$security_emails = json_encode($security_emails, JSON_THROW_ON_ERROR);
// For some reason the JSON format is double-encoding quotes
// (at least on Drush 9). So we use YAML instead. Luckily the
// two are similar (enough) with simple arrays.
shell_exec_bail("terminus drush $site.$environment -- config-set -y --input-format=yaml update.settings notification.emails '$security_emails' 2>&1");
// Export config.
echo " Exporting config. " . PHP_EOL;
shell_exec_bail("terminus -q connection:set $site.$environment sftp 2>&1");
shell_exec("terminus drush $site.$environment -- config-export -y");
$commit_message = 'feat: Updated security notification emails to 4k equivalents.';
shell_exec_bail("terminus -y env:commit $site.$environment --message='$commit_message' --force 2>&1");
shell_exec_bail("terminus -q connection:set $site.$environment git 2>&1");
// Deploy.
echo " Deploy. " . PHP_EOL;
if (!$CONFIRM_AUTO_DEPLOY) {
readline("Security notification config on $site.$environment has been changed. Please open a new terminal to deploy this (maybe using `pantheon-quick-deploy`). Ready to continue? (Y)");
}
else {
shell_exec(__DIR__ . "/pantheon-quick-deploy $site '$commit_message'");
}
}
else {
echo "notice: no security update emails to convert." . PHP_EOL;
}
}
// Accounts.
shell_exec_bail("echo \"$d8sql\" | terminus drush $site.$environment -- sql-cli 2>&1");
// Search for other unblocked usernames that start with "advo".
$unblocked_advoteam = trim(shell_exec("echo \"SELECT name, mail FROM users_field_data WHERE (name LIKE 'advo%' or mail LIKE '%@advomatic.com') AND status = 1\" | terminus drush $site.$environment sql-cli 2>&1 | grep -v \"This environment is in read-only Git mode\" | grep -v \" drush sql-cli \""));
if ($unblocked_advoteam) {
echo " UNBLOCKED ADVOTEAM ACCOUNTS. Test accounts in non-live environments can probably be ignored. Please review" . PHP_EOL;
echo $unblocked_advoteam . PHP_EOL;
}
break;
case "wordpress":
// Accounts.
foreach ($wpsql as $sql) {
shell_exec_bail("terminus wp $site.$environment -- db query \"$sql\" 2>&1");
}
break;
default:
echo "I don't know how to handle framework $framework" . PHP_EOL;
die(1);
}
}
}
echo "Thanks. All done." . PHP_EOL;
/**
* Run shell_exec() and bail if the output contains the word "error".
*
* PHP doesn't have an easy way to run external commands that can both capture
* output _and_ the return code. This is a happy medium.
*
* @param string $command
*/
function shell_exec_bail(string $command): void {
$result = shell_exec($command);
if (strpos($result, 'error') !== FALSE) {
echo $result . PHP_EOL;
die(1);
}
}
/**
* Convert a list of email addresses.
*
* @param string|array &$emails_to_convert: array
* Either a simple array of email addresses,
* or a string that can be json-decoded as such.
* Conversions happen in-place. Will always be an array in the end.
* @param array $email_conversions
* The conversions that we want to happen in the format
* old => new
*
* @return int
* The number of conversions.
*/
function convert_emails(&$emails_to_convert, array $email_conversions): int {
if (is_string($emails_to_convert)) {
try {
$array_emails_to_convert = json_decode($emails_to_convert, TRUE, 512, JSON_THROW_ON_ERROR);
}
catch (JsonException $e) {
echo "JSON decode exception: " . $emails_to_convert;
die(1);
}
if (!$emails_to_convert) {
echo "Cannot decode JSON: " . $emails_to_convert;
die(1);
}
$emails_to_convert = $array_emails_to_convert['update_notify_emails'] ?? $array_emails_to_convert['update.settings:notification.emails'] ?? $array_emails_to_convert;
}
$conversion_count = 0;
foreach ($emails_to_convert as &$email_to_convert) {
if (isset($email_conversions[$email_to_convert])) {
$email_to_convert = $email_conversions[$email_to_convert];
$conversion_count ++;
}
}
return $conversion_count;
}