Completed
Push — master ( 539f62...852c44 )
by Mateusz
03:41 queued 39s
created

CapistranoDeploymentBackend::deploy()   D

Complexity

Conditions 13
Paths 14

Size

Total Lines 107
Code Lines 59

Duplication

Lines 0
Ratio 0 %

Importance

Changes 5
Bugs 1 Features 0
Metric Value
c 5
b 1
f 0
dl 0
loc 107
rs 4.9922
cc 13
eloc 59
nc 14
nop 4

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
3
class CapistranoDeploymentBackend extends Object implements DeploymentBackend {
4
5
	protected $packageGenerator;
6
7
	public function getPackageGenerator() {
8
		return $this->packageGenerator;
9
	}
10
11
	public function setPackageGenerator(PackageGenerator $packageGenerator) {
12
		$this->packageGenerator = $packageGenerator;
13
	}
14
15
	/**
16
	 * Create a deployment strategy.
17
	 *
18
	 * @param DNEnvironment $environment
19
	 * @param array $options
20
	 *
21
	 * @return DeploymentStrategy
22
	 */
23
	public function planDeploy(DNEnvironment $environment, $options) {
24
		$strategy = new DeploymentStrategy($environment, $options);
25
26
		$currentBuild = $environment->CurrentBuild();
27
		$currentSha = $currentBuild ? $currentBuild->SHA : '-';
28
		if($currentSha !== $options['sha']) {
29
			$strategy->setChange('Code version', $currentSha, $options['sha']);
30
		}
31
		$strategy->setActionTitle('Confirm deployment');
32
		$strategy->setActionCode('fast');
33
		$strategy->setEstimatedTime('2');
34
35
		return $strategy;
36
	}
37
38
	/**
39
	 * Deploy the given build to the given environment.
40
	 *
41
	 * @param DNEnvironment $environment
42
	 * @param DeploynautLogFile $log
43
	 * @param DNProject $project
44
	 * @param array $options
45
	 */
46
	public function deploy(
47
		DNEnvironment $environment,
48
		DeploynautLogFile $log,
49
		DNProject $project,
50
		$options
51
	) {
52
		$name = $environment->getFullName();
53
		$repository = $project->getLocalCVSPath();
54
		$sha = $options['sha'];
55
56
		$args = array(
57
			'branch' => $sha,
58
			'repository' => $repository,
59
		);
60
61
		$this->extend('deployStart', $environment, $sha, $log, $project);
62
63
		$log->write(sprintf('Deploying "%s" to "%s"', $sha, $name));
64
65
		$this->enableMaintenance($environment, $log, $project);
66
67
		// Use a package generator if specified, otherwise run a direct deploy, which is the default behaviour
68
		// if build_filename isn't specified
69
		if($this->packageGenerator) {
70
			$log->write(sprintf('Using package generator "%s"', get_class($this->packageGenerator)));
71
72
			try {
73
				$args['build_filename'] = $this->packageGenerator->getPackageFilename($project->Name, $sha, $repository, $log);
74
			} catch (Exception $e) {
75
				$log->write($e->getMessage());
76
				throw $e;
77
			}
78
79
			if(empty($args['build_filename'])) {
80
				throw new RuntimeException('Failed to generate package.');
81
			}
82
		}
83
84
		$command = $this->getCommand('deploy', 'web', $environment, $args, $log);
85
		$command->run(function($type, $buffer) use($log) {
86
			$log->write($buffer);
87
		});
88
89
		// Deployment cleanup. We assume it is always safe to run this at the end, regardless of the outcome.
90
		$self = $this;
91
		$cleanupFn = function() use($self, $environment, $args, $log, $sha, $project) {
92
			$command = $self->getCommand('deploy:cleanup', 'web', $environment, $args, $log);
93
			$command->run(function($type, $buffer) use($log) {
94
				$log->write($buffer);
95
			});
96
97
			if(!$command->isSuccessful()) {
98
				$self->extend('cleanupFailure', $environment, $sha, $log, $project);
99
				$log->write('Warning: Cleanup failed, but fine to continue. Needs manual cleanup sometime.');
100
			}
101
		};
102
103
		// Once the deployment has run it's necessary to update the maintenance page status
104
		// as deploying removes .htaccess
105
		$this->enableMaintenance($environment, $log, $project);
106
107
		$rolledBack = null;
108
		if(!$command->isSuccessful() || !$this->smokeTest($environment, $log)) {
109
			$cleanupFn();
110
			$this->extend('deployFailure', $environment, $sha, $log, $project);
111
112
			$currentBuild = $environment->CurrentBuild();
113
			if (empty($currentBuild) || (!empty($options['no_rollback']) && $options['no_rollback'] !== 'false')) {
114
				throw new RuntimeException($command->getErrorOutput());
115
			}
116
117
			// re-run deploy with the current build sha to rollback
118
			$log->write('Deploy failed. Rolling back');
119
			$rollbackArgs = array_merge($args, ['branch' => $currentBuild->SHA]);
120
			$command = $this->getCommand('deploy', 'web', $environment, $rollbackArgs, $log);
121
			$command->run(function($type, $buffer) use($log) {
122
				$log->write($buffer);
123
			});
124
125
			// Once the deployment has run it's necessary to update the maintenance page status
126
			// as deploying removes .htaccess
127
			$this->enableMaintenance($environment, $log, $project);
128
129
			if (!$command->isSuccessful() || !$this->smokeTest($environment, $log)) {
130
				$this->extend('deployRollbackFailure', $environment, $currentBuild->SHA, $log, $project);
131
				$log->write('Rollback failed');
132
				throw new RuntimeException($command->getErrorOutput());
133
			}
134
135
			// By getting here, it means we have successfully rolled back without any errors
136
			$rolledBack = true;
137
		}
138
139
		$this->disableMaintenance($environment, $log, $project);
140
141
		$cleanupFn();
142
143
		// Rolling back means the rollback succeeded, but ultimately the deployment
144
		// has failed. Throw an exception so the job is marked as failed accordingly.
145
		if ($rolledBack === true) {
146
			throw new RuntimeException('Rollback successful');
147
		}
148
149
		$log->write(sprintf('Deploy of "%s" to "%s" finished', $sha, $name));
150
151
		$this->extend('deployEnd', $environment, $sha, $log, $project);
152
	}
153
154
	/**
155
	 * @return array
156
	 */
157
	public function getDeployOptions() {
158
		return [
159
			new PredeployBackupOption(true),
160
			new NoRollbackDeployOption()
161
		];
162
	}
163
164
	/**
165
	 * Enable a maintenance page for the given environment using the maintenance:enable Capistrano task.
166
	 */
167 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...
168
		$name = $environment->getFullName();
169
		$command = $this->getCommand('maintenance:enable', 'web', $environment, null, $log);
170
		$command->run(function($type, $buffer) use($log) {
171
			$log->write($buffer);
172
		});
173
		if(!$command->isSuccessful()) {
174
			$this->extend('maintenanceEnableFailure', $environment, $log);
175
			throw new RuntimeException($command->getErrorOutput());
176
		}
177
		$log->write(sprintf('Maintenance page enabled on "%s"', $name));
178
	}
179
180
	/**
181
	 * Disable the maintenance page for the given environment using the maintenance:disable Capistrano task.
182
	 */
183 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...
184
		$name = $environment->getFullName();
185
		$command = $this->getCommand('maintenance:disable', 'web', $environment, null, $log);
186
		$command->run(function($type, $buffer) use($log) {
187
			$log->write($buffer);
188
		});
189
		if(!$command->isSuccessful()) {
190
			$this->extend('maintenanceDisableFailure', $environment, $log);
191
			throw new RuntimeException($command->getErrorOutput());
192
		}
193
		$log->write(sprintf('Maintenance page disabled on "%s"', $name));
194
	}
195
196
	/**
197
	 * Check the status using the deploy:check capistrano method
198
	 */
199
	public function ping(DNEnvironment $environment, DeploynautLogFile $log, DNProject $project) {
200
		$command = $this->getCommand('deploy:check', 'web', $environment, null, $log);
201
		$command->run(function($type, $buffer) use($log) {
202
			$log->write($buffer);
203
			echo $buffer;
204
		});
205
	}
206
207
	/**
208
	 * @inheritdoc
209
	 */
210
	public function dataTransfer(DNDataTransfer $dataTransfer, DeploynautLogFile $log) {
211
		if($dataTransfer->Direction == 'get') {
212
			$this->dataTransferBackup($dataTransfer, $log);
213
		} else {
214
			$environment = $dataTransfer->Environment();
215
			$project = $environment->Project();
216
			$workingDir = TEMP_FOLDER . DIRECTORY_SEPARATOR . 'deploynaut-transfer-' . $dataTransfer->ID;
217
			$archive = $dataTransfer->DataArchive();
218
219
			// extract the sspak contents, we'll need these so capistrano can restore that content
220
			try {
221
				$archive->extractArchive($workingDir);
222
			} catch(Exception $e) {
223
				$log->write($e->getMessage());
224
				throw new RuntimeException($e->getMessage());
225
			}
226
227
			// validate the contents match the requested transfer mode
228
			$result = $archive->validateArchiveContents($dataTransfer->Mode);
229
			if(!$result->valid()) {
230
				// do some cleaning, get rid of the extracted archive lying around
231
				$process = new AbortableProcess(sprintf('rm -rf %s', escapeshellarg($workingDir)));
232
				$process->setTimeout(120);
233
				$process->run();
234
235
				// log the reason why we can't restore the snapshot and halt the process
236
				$log->write($result->message());
237
				throw new RuntimeException($result->message());
238
			}
239
240
			// Put up a maintenance page during a restore of db or assets.
241
			$this->enableMaintenance($environment, $log, $project);
242
			$this->dataTransferRestore($workingDir, $dataTransfer, $log);
243
			$this->disableMaintenance($environment, $log, $project);
244
		}
245
	}
246
247
	/**
248
	 * @param string $action Capistrano action to be executed
249
	 * @param string $roles Defining a server role is required to target only the required servers.
250
	 * @param DNEnvironment $environment
251
	 * @param array<string>|null $args Additional arguments for process
252
	 * @param DeploynautLogFile $log
253
	 * @return \Symfony\Component\Process\Process
254
	 */
255
	public function getCommand($action, $roles, DNEnvironment $environment, $args = null, DeploynautLogFile $log) {
256
		$name = $environment->getFullName();
257
		$env = $environment->Project()->getProcessEnv();
258
259
		if(!$args) {
260
			$args = array();
261
		}
262
		$args['history_path'] = realpath(DEPLOYNAUT_LOG_PATH . '/');
263
		$args['environment_id'] = $environment->ID;
264
265
		// Inject env string directly into the command.
266
		// Capistrano doesn't like the $process->setEnv($env) we'd normally do below.
267
		$envString = '';
268
		if(!empty($env)) {
269
			$envString .= 'env ';
270
			foreach($env as $key => $value) {
271
				$envString .= "$key=\"$value\" ";
272
			}
273
		}
274
275
		$data = DNData::inst();
276
		// Generate a capfile from a template
277
		$capTemplate = file_get_contents(BASE_PATH . '/deploynaut/Capfile.template');
278
		$cap = str_replace(
279
			array('<config root>', '<ssh key>', '<base path>'),
280
			array($data->getEnvironmentDir(), DEPLOYNAUT_SSH_KEY, BASE_PATH),
281
			$capTemplate
282
		);
283
284
		if(defined('DEPLOYNAUT_CAPFILE')) {
285
			$capFile = DEPLOYNAUT_CAPFILE;
286
		} else {
287
			$capFile = ASSETS_PATH . '/Capfile';
288
		}
289
		file_put_contents($capFile, $cap);
290
291
		$command = "{$envString}cap -f " . escapeshellarg($capFile) . " -vv $name $action ROLES=$roles";
292
		foreach($args as $argName => $argVal) {
293
			$command .= ' -s ' . escapeshellarg($argName) . '=' . escapeshellarg($argVal);
294
		}
295
296
		$log->write(sprintf('Running command: %s', $command));
297
298
		$process = new AbortableProcess($command);
299
		$process->setTimeout(3600);
300
		return $process;
301
	}
302
303
	/**
304
	 * Backs up database and/or assets to a designated folder,
305
	 * and packs up the files into a single sspak.
306
	 *
307
	 * @param DNDataTransfer    $dataTransfer
308
	 * @param DeploynautLogFile $log
309
	 */
310
	protected function dataTransferBackup(DNDataTransfer $dataTransfer, DeploynautLogFile $log) {
311
		$environment = $dataTransfer->Environment();
312
		$name = $environment->getFullName();
313
314
		// Associate a new archive with the transfer.
315
		// Doesn't retrieve a filepath just yet, need to generate the files first.
316
		$dataArchive = DNDataArchive::create();
317
		$dataArchive->Mode = $dataTransfer->Mode;
318
		$dataArchive->AuthorID = $dataTransfer->AuthorID;
319
		$dataArchive->OriginalEnvironmentID = $environment->ID;
320
		$dataArchive->EnvironmentID = $environment->ID;
321
		$dataArchive->IsBackup = $dataTransfer->IsBackupDataTransfer();
322
323
		// Generate directory structure with strict permissions (contains very sensitive data)
324
		$filepathBase = $dataArchive->generateFilepath($dataTransfer);
325
		mkdir($filepathBase, 0700, true);
326
327
		$databasePath = $filepathBase . DIRECTORY_SEPARATOR . 'database.sql';
328
329
		// Backup database
330 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...
331
			$log->write(sprintf('Backup of database from "%s" started', $name));
332
			$command = $this->getCommand('data:getdb', 'db', $environment, array('data_path' => $databasePath), $log);
333
			$command->run(function($type, $buffer) use($log) {
334
				$log->write($buffer);
335
			});
336
			if(!$command->isSuccessful()) {
337
				$this->extend('dataTransferFailure', $environment, $log);
338
				throw new RuntimeException($command->getErrorOutput());
339
			}
340
			$log->write(sprintf('Backup of database from "%s" done', $name));
341
		}
342
343
		// Backup assets
344 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...
345
			$log->write(sprintf('Backup of assets from "%s" started', $name));
346
			$command = $this->getCommand('data:getassets', 'web', $environment, array('data_path' => $filepathBase), $log);
347
			$command->run(function($type, $buffer) use($log) {
348
				$log->write($buffer);
349
			});
350
			if(!$command->isSuccessful()) {
351
				$this->extend('dataTransferFailure', $environment, $log);
352
				throw new RuntimeException($command->getErrorOutput());
353
			}
354
			$log->write(sprintf('Backup of assets from "%s" done', $name));
355
		}
356
357
		// ensure the database connection is re-initialised, which is needed if the transfer
358
		// above took a really long time because the handle to the db may have become invalid.
359
		global $databaseConfig;
360
		DB::connect($databaseConfig);
361
362
		$log->write('Creating sspak...');
363
364
		$sspakFilename = sprintf('%s.sspak', $dataArchive->generateFilename($dataTransfer));
365
		$sspakFilepath = $filepathBase . DIRECTORY_SEPARATOR . $sspakFilename;
366
367
		try {
368
			$dataArchive->attachFile($sspakFilepath, $dataTransfer);
369
			$dataArchive->setArchiveFromFiles($filepathBase);
370
		} catch(Exception $e) {
371
			$log->write($e->getMessage());
372
			throw new RuntimeException($e->getMessage());
373
		}
374
375
		// Remove any assets and db files lying around, they're not longer needed as they're now part
376
		// of the sspak file we just generated. Use --force to avoid errors when files don't exist,
377
		// e.g. when just an assets backup has been requested and no database.sql exists.
378
		$process = new AbortableProcess(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 137 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...
379
		$process->setTimeout(120);
380
		$process->run();
381
		if(!$process->isSuccessful()) {
382
			$log->write('Could not delete temporary files');
383
			throw new RuntimeException($process->getErrorOutput());
384
		}
385
386
		$log->write(sprintf('Creating sspak file done: %s', $dataArchive->ArchiveFile()->getAbsoluteURL()));
387
	}
388
389
	/**
390
	 * Utility function for triggering the db rebuild and flush.
391
	 * Also cleans up and generates new error pages.
392
	 * @param DeploynautLogFile $log
393
	 */
394 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...
395
		$name = $environment->getFullName();
396
		$command = $this->getCommand('deploy:migrate', 'web', $environment, null, $log);
397
		$command->run(function($type, $buffer) use($log) {
398
			$log->write($buffer);
399
		});
400
		if(!$command->isSuccessful()) {
401
			$log->write(sprintf('Rebuild of "%s" failed: %s', $name, $command->getErrorOutput()));
402
			throw new RuntimeException($command->getErrorOutput());
403
		}
404
		$log->write(sprintf('Rebuild of "%s" done', $name));
405
	}
406
407
	/**
408
	 * Extracts a *.sspak file referenced through the passed in $dataTransfer
409
	 * and pushes it to the environment referenced in $dataTransfer.
410
	 *
411
	 * @param string $workingDir Directory for the unpacked files.
412
	 * @param DNDataTransfer $dataTransfer
413
	 * @param DeploynautLogFile $log
414
	 */
415
	protected function dataTransferRestore($workingDir, DNDataTransfer $dataTransfer, DeploynautLogFile $log) {
416
		$environment = $dataTransfer->Environment();
417
		$name = $environment->getFullName();
418
419
		// Rollback cleanup.
420
		$self = $this;
421 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...
422
			// Rebuild makes sense even if failed - maybe we can at least partly recover.
423
			$self->rebuild($environment, $log);
424
			$process = new AbortableProcess(sprintf('rm -rf %s', escapeshellarg($workingDir)));
425
			$process->setTimeout(120);
426
			$process->run();
427
		};
428
429
		// Restore database into target environment
430 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...
431
			$log->write(sprintf('Restore of database to "%s" started', $name));
432
			$args = array('data_path' => $workingDir . DIRECTORY_SEPARATOR . 'database.sql');
433
			$command = $this->getCommand('data:pushdb', 'db', $environment, $args, $log);
434
			$command->run(function($type, $buffer) use($log) {
435
				$log->write($buffer);
436
			});
437
			if(!$command->isSuccessful()) {
438
				$cleanupFn();
439
				$log->write(sprintf('Restore of database to "%s" failed: %s', $name, $command->getErrorOutput()));
440
				$this->extend('dataTransferFailure', $environment, $log);
441
				throw new RuntimeException($command->getErrorOutput());
442
			}
443
			$log->write(sprintf('Restore of database to "%s" done', $name));
444
		}
445
446
		// Restore assets into target environment
447 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...
448
			$log->write(sprintf('Restore of assets to "%s" started', $name));
449
			$args = array('data_path' => $workingDir . DIRECTORY_SEPARATOR . 'assets');
450
			$command = $this->getCommand('data:pushassets', 'web', $environment, $args, $log);
451
			$command->run(function($type, $buffer) use($log) {
452
				$log->write($buffer);
453
			});
454
			if(!$command->isSuccessful()) {
455
				$cleanupFn();
456
				$log->write(sprintf('Restore of assets to "%s" failed: %s', $name, $command->getErrorOutput()));
457
				$this->extend('dataTransferFailure', $environment, $log);
458
				throw new RuntimeException($command->getErrorOutput());
459
			}
460
			$log->write(sprintf('Restore of assets to "%s" done', $name));
461
		}
462
463
		$log->write('Rebuilding and cleaning up');
464
		$cleanupFn();
465
	}
466
467
	/**
468
	 * This is mostly copy-pasted from Anthill/Smoketest.
469
	 *
470
	 * @param DNEnvironment $environment
471
	 * @param DeploynautLogFile $log
472
	 * @return bool
473
	 */
474
	protected function smokeTest(DNEnvironment $environment, DeploynautLogFile $log) {
475
		$url = $environment->getBareURL();
476
		$timeout = 600;
477
		$tick = 60;
478
479
		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...
480
			$log->write('Skipping site accessible check: no URL found.');
481
			return true;
482
		}
483
484
		$start = time();
485
		$infoTick = time() + $tick;
486
487
		$log->write(sprintf(
488
			'Waiting for "%s" to become accessible... (timeout: %smin)',
489
			$url,
490
			$timeout / 60
491
		));
492
493
		// configure curl so that curl_exec doesn't wait a long time for a response
494
		$ch = curl_init();
495
		curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
496
		curl_setopt($ch, CURLOPT_TIMEOUT, 5);
497
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
498
		curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
499
		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...
500
		curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
501
		curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
502
		curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
503
		curl_setopt($ch, CURLOPT_URL, $url);
504
		curl_setopt($ch, CURLOPT_USERAGENT, 'Rainforest');
505
		$success = false;
506
507
		// query the site every second. Note that if the URL doesn't respond,
508
		// curl_exec will take 5 seconds to timeout (see CURLOPT_CONNECTTIMEOUT and CURLOPT_TIMEOUT above)
509
		do {
510
			if(time() > $start + $timeout) {
511
				$log->write(sprintf(' * Failed: check for %s timed out after %smin', $url, $timeout / 60));
512
				return false;
513
			}
514
515
			$response = curl_exec($ch);
516
517
			// check the HTTP response code for HTTP protocols
518
			$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
519
			if($status && !in_array($status, [500, 501, 502, 503, 504])) {
520
				$success = true;
521
			}
522
523
			// check for any curl errors, mostly for checking the response state of non-HTTP protocols,
524
			// but applies to checks of any protocol
525
			if($response && !curl_errno($ch)) {
526
				$success = true;
527
			}
528
529
			// Produce an informational ticker roughly every $tick
530
			if (time() > $infoTick) {
531
				$message = [];
532
533
				// Collect status information from different sources.
534
				if ($status) {
535
					$message[] = sprintf('HTTP status code is %s', $status);
536
				}
537
				if (!$response) {
538
					$message[] = 'response is empty';
539
				}
540
				if ($error = curl_error($ch)) {
541
					$message[] = sprintf('request error: %s', $error);
542
				}
543
544
				$log->write(sprintf(
545
					' * Still waiting: %s...',
546
					implode(', ', $message)
547
				));
548
549
				$infoTick = time() + $tick;
550
			}
551
552
			sleep(1);
553
		} while(!$success);
554
555
		curl_close($ch);
556
		$log->write(' * Success: site is accessible!');
557
		return true;
558
	}
559
560
}
561