Completed
Pull Request — master (#595)
by Mateusz
03:07
created

CapistranoDeploymentBackend::smokeTest()   C

Complexity

Conditions 12
Paths 38

Size

Total Lines 85
Code Lines 52

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 85
rs 5.034
cc 12
eloc 52
nc 38
nop 2

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
use \Symfony\Component\Process\Process;
3
4
class CapistranoDeploymentBackend extends Object implements DeploymentBackend {
5
6
	protected $packageGenerator;
7
8
	public function getPackageGenerator() {
9
		return $this->packageGenerator;
10
	}
11
12
	public function setPackageGenerator(PackageGenerator $packageGenerator) {
13
		$this->packageGenerator = $packageGenerator;
14
	}
15
16
	/**
17
	 * Create a deployment strategy.
18
	 *
19
	 * @param DNEnvironment $environment
20
	 * @param array $options
21
	 *
22
	 * @return DeploymentStrategy
23
	 */
24
	public function planDeploy(DNEnvironment $environment, $options) {
25
		$strategy = new DeploymentStrategy($environment, $options);
26
27
		$currentBuild = $environment->CurrentBuild();
28
		$currentSha = $currentBuild ? $currentBuild->SHA : '-';
29
		if($currentSha !== $options['sha']) {
30
			$strategy->setChange('Code version', $currentSha, $options['sha']);
31
		}
32
		$strategy->setActionTitle('Confirm deployment');
33
		$strategy->setActionCode('fast');
34
		$strategy->setEstimatedTime('2');
35
36
		return $strategy;
37
	}
38
39
	/**
40
	 * Deploy the given build to the given environment.
41
	 *
42
	 * @param DNEnvironment $environment
43
	 * @param DeploynautLogFile $log
44
	 * @param DNProject $project
45
	 * @param array $options
46
	 */
47
	public function deploy(
48
		DNEnvironment $environment,
49
		DeploynautLogFile $log,
50
		DNProject $project,
51
		$options
52
	) {
53
		$name = $environment->getFullName();
54
		$repository = $project->getLocalCVSPath();
55
		$sha = $options['sha'];
56
57
		$args = array(
58
			'branch' => $sha,
59
			'repository' => $repository,
60
		);
61
62
		$this->extend('deployStart', $environment, $sha, $log, $project);
63
64
		$log->write(sprintf('Deploying "%s" to "%s"', $sha, $name));
65
66
		$this->enableMaintenance($environment, $log, $project);
67
68
		// Use a package generator if specified, otherwise run a direct deploy, which is the default behaviour
69
		// if build_filename isn't specified
70
		if($this->packageGenerator) {
71
			$log->write(sprintf('Using package generator "%s"', get_class($this->packageGenerator)));
72
73
			try {
74
				$args['build_filename'] = $this->packageGenerator->getPackageFilename($project->Name, $sha, $repository, $log);
75
			} catch (Exception $e) {
76
				$log->write($e->getMessage());
77
				throw $e;
78
			}
79
80
			if(empty($args['build_filename'])) {
81
				throw new RuntimeException('Failed to generate package.');
82
			}
83
		}
84
85
		$command = $this->getCommand('deploy', 'web', $environment, $args, $log);
86
		$command->run(function($type, $buffer) use($log) {
87
			$log->write($buffer);
88
		});
89
90
		// Deployment cleanup. We assume it is always safe to run this at the end, regardless of the outcome.
91
		$self = $this;
92
		$cleanupFn = function() use($self, $environment, $args, $log, $sha, $project) {
93
			$command = $self->getCommand('deploy:cleanup', 'web', $environment, $args, $log);
94
			$command->run(function($type, $buffer) use($log) {
95
				$log->write($buffer);
96
			});
97
98
			if(!$command->isSuccessful()) {
99
				$self->extend('cleanupFailure', $environment, $sha, $log, $project);
100
				$log->write('Warning: Cleanup failed, but fine to continue. Needs manual cleanup sometime.');
101
			}
102
		};
103
104
		// Once the deployment has run it's necessary to update the maintenance page status
105
		if(!empty($options['leaveMaintenancePage'])) {
106
			$this->enableMaintenance($environment, $log, $project);
107
		}
108
109
		if(!$command->isSuccessful() || !$this->smokeTest($environment, $log)) {
110
			$cleanupFn();
111
			$this->extend('deployFailure', $environment, $sha, $log, $project);
112
			throw new RuntimeException($command->getErrorOutput());
113
		}
114
115
		// Check if maintenance page should be removed
116
		if(empty($options['leaveMaintenancePage'])) {
117
			$this->disableMaintenance($environment, $log, $project);
118
		}
119
120
		$cleanupFn();
121
122
		$log->write(sprintf('Deploy of "%s" to "%s" finished', $sha, $name));
123
124
		$this->extend('deployEnd', $environment, $sha, $log, $project);
125
	}
126
127
	/**
128
	 * Enable a maintenance page for the given environment using the maintenance:enable Capistrano task.
129
	 */
130 View Code Duplication
	public function enableMaintenance(DNEnvironment $environment, DeploynautLogFile $log, DNProject $project) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
131
		$name = $environment->getFullName();
132
		$command = $this->getCommand('maintenance:enable', 'web', $environment, null, $log);
133
		$command->run(function($type, $buffer) use($log) {
134
			$log->write($buffer);
135
		});
136
		if(!$command->isSuccessful()) {
137
			$this->extend('maintenanceEnableFailure', $environment, $log);
138
			throw new RuntimeException($command->getErrorOutput());
139
		}
140
		$log->write(sprintf('Maintenance page enabled on "%s"', $name));
141
	}
142
143
	/**
144
	 * Disable the maintenance page for the given environment using the maintenance:disable Capistrano task.
145
	 */
146 View Code Duplication
	public function disableMaintenance(DNEnvironment $environment, DeploynautLogFile $log, DNProject $project) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
147
		$name = $environment->getFullName();
148
		$command = $this->getCommand('maintenance:disable', 'web', $environment, null, $log);
149
		$command->run(function($type, $buffer) use($log) {
150
			$log->write($buffer);
151
		});
152
		if(!$command->isSuccessful()) {
153
			$this->extend('maintenanceDisableFailure', $environment, $log);
154
			throw new RuntimeException($command->getErrorOutput());
155
		}
156
		$log->write(sprintf('Maintenance page disabled on "%s"', $name));
157
	}
158
159
	/**
160
	 * Check the status using the deploy:check capistrano method
161
	 */
162
	public function ping(DNEnvironment $environment, DeploynautLogFile $log, DNProject $project) {
163
		$command = $this->getCommand('deploy:check', 'web', $environment, null, $log);
164
		$command->run(function($type, $buffer) use($log) {
165
			$log->write($buffer);
166
			echo $buffer;
167
		});
168
	}
169
170
	/**
171
	 * @inheritdoc
172
	 */
173
	public function dataTransfer(DNDataTransfer $dataTransfer, DeploynautLogFile $log) {
174
		if($dataTransfer->Direction == 'get') {
175
			$this->dataTransferBackup($dataTransfer, $log);
176
		} else {
177
			$environment = $dataTransfer->Environment();
178
			$project = $environment->Project();
179
			$workingDir = TEMP_FOLDER . DIRECTORY_SEPARATOR . 'deploynaut-transfer-' . $dataTransfer->ID;
180
			$archive = $dataTransfer->DataArchive();
181
182
			// extract the sspak contents, we'll need these so capistrano can restore that content
183
			try {
184
				$archive->extractArchive($workingDir);
185
			} catch(Exception $e) {
186
				$log->write($e->getMessage());
187
				throw new RuntimeException($e->getMessage());
188
			}
189
190
			// validate the contents match the requested transfer mode
191
			$result = $archive->validateArchiveContents($dataTransfer->Mode);
192
			if(!$result->valid()) {
193
				// do some cleaning, get rid of the extracted archive lying around
194
				$process = new Process(sprintf('rm -rf %s', escapeshellarg($workingDir)));
195
				$process->setTimeout(120);
196
				$process->run();
197
198
				// log the reason why we can't restore the snapshot and halt the process
199
				$log->write($result->message());
200
				throw new RuntimeException($result->message());
201
			}
202
203
			// Put up a maintenance page during a restore of db or assets.
204
			$this->enableMaintenance($environment, $log, $project);
205
			$this->dataTransferRestore($workingDir, $dataTransfer, $log);
206
			$this->disableMaintenance($environment, $log, $project);
207
		}
208
	}
209
210
	/**
211
	 * @param string $action Capistrano action to be executed
212
	 * @param string $roles Defining a server role is required to target only the required servers.
213
	 * @param DNEnvironment $environment
214
	 * @param array<string>|null $args Additional arguments for process
215
	 * @param DeploynautLogFile $log
216
	 * @return \Symfony\Component\Process\Process
217
	 */
218
	public function getCommand($action, $roles, DNEnvironment $environment, $args = null, DeploynautLogFile $log) {
219
		$name = $environment->getFullName();
220
		$env = $environment->Project()->getProcessEnv();
221
222
		if(!$args) {
223
			$args = array();
224
		}
225
		$args['history_path'] = realpath(DEPLOYNAUT_LOG_PATH . '/');
226
		$args['environment_id'] = $environment->ID;
227
228
		// Inject env string directly into the command.
229
		// Capistrano doesn't like the $process->setEnv($env) we'd normally do below.
230
		$envString = '';
231
		if(!empty($env)) {
232
			$envString .= 'env ';
233
			foreach($env as $key => $value) {
234
				$envString .= "$key=\"$value\" ";
235
			}
236
		}
237
238
		$data = DNData::inst();
239
		// Generate a capfile from a template
240
		$capTemplate = file_get_contents(BASE_PATH . '/deploynaut/Capfile.template');
241
		$cap = str_replace(
242
			array('<config root>', '<ssh key>', '<base path>'),
243
			array($data->getEnvironmentDir(), DEPLOYNAUT_SSH_KEY, BASE_PATH),
244
			$capTemplate
245
		);
246
247
		if(defined('DEPLOYNAUT_CAPFILE')) {
248
			$capFile = DEPLOYNAUT_CAPFILE;
249
		} else {
250
			$capFile = ASSETS_PATH . '/Capfile';
251
		}
252
		file_put_contents($capFile, $cap);
253
254
		$command = "{$envString}cap -f " . escapeshellarg($capFile) . " -vv $name $action ROLES=$roles";
255
		foreach($args as $argName => $argVal) {
256
			$command .= ' -s ' . escapeshellarg($argName) . '=' . escapeshellarg($argVal);
257
		}
258
259
		$log->write(sprintf('Running command: %s', $command));
260
261
		$process = new Process($command);
262
		$process->setTimeout(3600);
263
		return $process;
264
	}
265
266
	/**
267
	 * Backs up database and/or assets to a designated folder,
268
	 * and packs up the files into a single sspak.
269
	 *
270
	 * @param DNDataTransfer    $dataTransfer
271
	 * @param DeploynautLogFile $log
272
	 */
273
	protected function dataTransferBackup(DNDataTransfer $dataTransfer, DeploynautLogFile $log) {
274
		$environment = $dataTransfer->Environment();
275
		$name = $environment->getFullName();
276
277
		// Associate a new archive with the transfer.
278
		// Doesn't retrieve a filepath just yet, need to generate the files first.
279
		$dataArchive = DNDataArchive::create();
280
		$dataArchive->Mode = $dataTransfer->Mode;
281
		$dataArchive->AuthorID = $dataTransfer->AuthorID;
282
		$dataArchive->OriginalEnvironmentID = $environment->ID;
283
		$dataArchive->EnvironmentID = $environment->ID;
284
		$dataArchive->IsBackup = $dataTransfer->IsBackupDataTransfer();
285
286
		// Generate directory structure with strict permissions (contains very sensitive data)
287
		$filepathBase = $dataArchive->generateFilepath($dataTransfer);
288
		mkdir($filepathBase, 0700, true);
289
290
		$databasePath = $filepathBase . DIRECTORY_SEPARATOR . 'database.sql';
291
292
		// Backup database
293 View Code Duplication
		if(in_array($dataTransfer->Mode, array('all', 'db'))) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
294
			$log->write(sprintf('Backup of database from "%s" started', $name));
295
			$command = $this->getCommand('data:getdb', 'db', $environment, array('data_path' => $databasePath), $log);
296
			$command->run(function($type, $buffer) use($log) {
297
				$log->write($buffer);
298
			});
299
			if(!$command->isSuccessful()) {
300
				$this->extend('dataTransferFailure', $environment, $log);
301
				throw new RuntimeException($command->getErrorOutput());
302
			}
303
			$log->write(sprintf('Backup of database from "%s" done', $name));
304
		}
305
306
		// Backup assets
307 View Code Duplication
		if(in_array($dataTransfer->Mode, array('all', 'assets'))) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
308
			$log->write(sprintf('Backup of assets from "%s" started', $name));
309
			$command = $this->getCommand('data:getassets', 'web', $environment, array('data_path' => $filepathBase), $log);
310
			$command->run(function($type, $buffer) use($log) {
311
				$log->write($buffer);
312
			});
313
			if(!$command->isSuccessful()) {
314
				$this->extend('dataTransferFailure', $environment, $log);
315
				throw new RuntimeException($command->getErrorOutput());
316
			}
317
			$log->write(sprintf('Backup of assets from "%s" done', $name));
318
		}
319
320
		// ensure the database connection is re-initialised, which is needed if the transfer
321
		// above took a really long time because the handle to the db may have become invalid.
322
		global $databaseConfig;
323
		DB::connect($databaseConfig);
324
325
		$log->write('Creating sspak...');
326
327
		$sspakFilename = sprintf('%s.sspak', $dataArchive->generateFilename($dataTransfer));
328
		$sspakFilepath = $filepathBase . DIRECTORY_SEPARATOR . $sspakFilename;
329
330
		try {
331
			$dataArchive->attachFile($sspakFilepath, $dataTransfer);
332
			$dataArchive->setArchiveFromFiles($filepathBase);
333
		} catch(Exception $e) {
334
			$log->write($e->getMessage());
335
			throw new RuntimeException($e->getMessage());
336
		}
337
338
		// Remove any assets and db files lying around, they're not longer needed as they're now part
339
		// of the sspak file we just generated. Use --force to avoid errors when files don't exist,
340
		// e.g. when just an assets backup has been requested and no database.sql exists.
341
		$process = new Process(sprintf('rm -rf %s/assets && rm -f %s', escapeshellarg($filepathBase), escapeshellarg($databasePath)));
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 128 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
342
		$process->setTimeout(120);
343
		$process->run();
344
		if(!$process->isSuccessful()) {
345
			$log->write('Could not delete temporary files');
346
			throw new RuntimeException($process->getErrorOutput());
347
		}
348
349
		$log->write(sprintf('Creating sspak file done: %s', $dataArchive->ArchiveFile()->getAbsoluteURL()));
350
	}
351
352
	/**
353
	 * Utility function for triggering the db rebuild and flush.
354
	 * Also cleans up and generates new error pages.
355
	 * @param DeploynautLogFile $log
356
	 */
357 View Code Duplication
	public function rebuild(DNEnvironment $environment, $log) {
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
358
		$name = $environment->getFullName();
359
		$command = $this->getCommand('deploy:migrate', 'web', $environment, null, $log);
360
		$command->run(function($type, $buffer) use($log) {
361
			$log->write($buffer);
362
		});
363
		if(!$command->isSuccessful()) {
364
			$log->write(sprintf('Rebuild of "%s" failed: %s', $name, $command->getErrorOutput()));
365
			throw new RuntimeException($command->getErrorOutput());
366
		}
367
		$log->write(sprintf('Rebuild of "%s" done', $name));
368
	}
369
370
	/**
371
	 * Extracts a *.sspak file referenced through the passed in $dataTransfer
372
	 * and pushes it to the environment referenced in $dataTransfer.
373
	 *
374
	 * @param string $workingDir Directory for the unpacked files.
375
	 * @param DNDataTransfer $dataTransfer
376
	 * @param DeploynautLogFile $log
377
	 */
378
	protected function dataTransferRestore($workingDir, DNDataTransfer $dataTransfer, DeploynautLogFile $log) {
379
		$environment = $dataTransfer->Environment();
380
		$name = $environment->getFullName();
381
382
		// Rollback cleanup.
383
		$self = $this;
384 View Code Duplication
		$cleanupFn = function() use($self, $workingDir, $environment, $log) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
385
			// Rebuild makes sense even if failed - maybe we can at least partly recover.
386
			$self->rebuild($environment, $log);
387
			$process = new Process(sprintf('rm -rf %s', escapeshellarg($workingDir)));
388
			$process->setTimeout(120);
389
			$process->run();
390
		};
391
392
		// Restore database into target environment
393 View Code Duplication
		if(in_array($dataTransfer->Mode, array('all', 'db'))) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
394
			$log->write(sprintf('Restore of database to "%s" started', $name));
395
			$args = array('data_path' => $workingDir . DIRECTORY_SEPARATOR . 'database.sql');
396
			$command = $this->getCommand('data:pushdb', 'db', $environment, $args, $log);
397
			$command->run(function($type, $buffer) use($log) {
398
				$log->write($buffer);
399
			});
400
			if(!$command->isSuccessful()) {
401
				$cleanupFn();
402
				$log->write(sprintf('Restore of database to "%s" failed: %s', $name, $command->getErrorOutput()));
403
				$this->extend('dataTransferFailure', $environment, $log);
404
				throw new RuntimeException($command->getErrorOutput());
405
			}
406
			$log->write(sprintf('Restore of database to "%s" done', $name));
407
		}
408
409
		// Restore assets into target environment
410 View Code Duplication
		if(in_array($dataTransfer->Mode, array('all', 'assets'))) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
411
			$log->write(sprintf('Restore of assets to "%s" started', $name));
412
			$args = array('data_path' => $workingDir . DIRECTORY_SEPARATOR . 'assets');
413
			$command = $this->getCommand('data:pushassets', 'web', $environment, $args, $log);
414
			$command->run(function($type, $buffer) use($log) {
415
				$log->write($buffer);
416
			});
417
			if(!$command->isSuccessful()) {
418
				$cleanupFn();
419
				$log->write(sprintf('Restore of assets to "%s" failed: %s', $name, $command->getErrorOutput()));
420
				$this->extend('dataTransferFailure', $environment, $log);
421
				throw new RuntimeException($command->getErrorOutput());
422
			}
423
			$log->write(sprintf('Restore of assets to "%s" done', $name));
424
		}
425
426
		$log->write('Rebuilding and cleaning up');
427
		$cleanupFn();
428
	}
429
430
	/**
431
	 * This is mostly copy-pasted from Anthill/Smoketest.
432
	 *
433
	 * @param DNEnvironment $environment
434
	 * @param DeploynautLogFile $log
435
	 * @return bool
436
	 */
437
	protected function smokeTest(DNEnvironment $environment, DeploynautLogFile $log) {
438
		$url = $environment->getBareURL();
439
		$timeout = 600;
440
		$tick = 60;
441
442
		if(!$url) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $url of type string|null is loosely compared to false; this is ambiguous if the string can be empty. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
443
			$log->write('Skipping site accessible check: no URL found.');
444
			return true;
445
		}
446
447
		$start = time();
448
		$infoTick = time() + $tick;
449
450
		$log->write(sprintf(
451
			'Waiting for "%s" to become accessible... (timeout: %smin)',
452
			$url,
453
			$timeout / 60
454
		));
455
456
		// configure curl so that curl_exec doesn't wait a long time for a response
457
		$ch = curl_init();
458
		curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
459
		curl_setopt($ch, CURLOPT_TIMEOUT, 5);
460
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
461
		curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
462
		curl_setopt($ch, CURLOPT_MAXREDIRS, 10); // set a high number of max redirects (but not infinite amount) to avoid a potential infinite loop
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 141 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
463
		curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
464
		curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
465
		curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
466
		curl_setopt($ch, CURLOPT_URL, $url);
467
		curl_setopt($ch, CURLOPT_USERAGENT, 'Rainforest');
468
		$success = false;
469
470
		// query the site every second. Note that if the URL doesn't respond,
471
		// curl_exec will take 5 seconds to timeout (see CURLOPT_CONNECTTIMEOUT and CURLOPT_TIMEOUT above)
472
		do {
473
			if(time() > $start + $timeout) {
474
				$log->write(sprintf(' * Failed: check for %s timed out after %smin', $url, $timeout / 60));
475
				return false;
476
			}
477
478
			$response = curl_exec($ch);
479
480
			// check the HTTP response code for HTTP protocols
481
			$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
482
			if($status && !in_array($status, [500, 501, 502, 503, 504])) {
483
				$success = true;
484
			}
485
486
			// check for any curl errors, mostly for checking the response state of non-HTTP protocols,
487
			// but applies to checks of any protocol
488
			if($response && !curl_errno($ch)) {
489
				$success = true;
490
			}
491
492
			// Produce an informational ticker roughly every $tick
493
			if (time() > $infoTick) {
494
				$message = [];
495
496
				// Collect status information from different sources.
497
				if ($status) {
498
					$message[] = sprintf('HTTP status code is %s', $status);
499
				}
500
				if (!$response) {
501
					$message[] = 'response is empty';
502
				}
503
				if ($error = curl_error($ch)) {
504
					$message[] = sprintf('request error: %s', $error);
505
				}
506
507
				$log->write(sprintf(
508
					' * Still waiting: %s...',
509
					implode(', ', $message)
510
				));
511
512
				$infoTick = time() + $tick;
513
			}
514
515
			sleep(1);
516
		} while(!$success);
517
518
		curl_close($ch);
519
		$log->write(' * Success: site is accessible!');
520
		return true;
521
	}
522
523
}
524