Completed
Pull Request — master (#858)
by Mateusz
03:50
created

CapistranoDeploymentBackend::getPackageGenerator()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 3
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
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
		$error = null;
90
91
		$deploySuccessful = $command->isSuccessful();
92
		if ($deploySuccessful) {
93
			// Deployment automatically removes .htaccess, i.e. disables maintenance. Fine to smoketest.
94
			$deploySuccessful = $this->smokeTest($environment, $log);
95
		}
96
97
		if (!$deploySuccessful) {
98
			$this->enableMaintenance($environment, $log, $project);
99
100
			$rollbackSuccessful = $this->deployRollback($environment, $log, $project, $options, $args);
101
			if ($rollbackSuccessful) {
102
				// Again, .htaccess removed, maintenance off.
103
				$rollbackSuccessful = $this->smokeTest($environment, $log);
104
			}
105
106
			if (!$rollbackSuccessful) {
107
				$this->enableMaintenance($environment, $log, $project);
108
				$this->extend('deployRollbackFailure', $environment, $currentBuild->SHA, $log, $project);
0 ignored issues
show
Bug introduced by
The variable $currentBuild does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
109
				$log->write('Rollback failed');
110
111
				$error = $command->getErrorOutput();
112
			} else {
113
				$error = 'Rollback successful';
114
			}
115
		}
116
117
		// Regardless of outcome, try to clean up.
118
		$cleanup = $this->getCommand('deploy:cleanup', 'web', $environment, $args, $log);
119
		$cleanup->run(function($type, $buffer) use($log) {
120
			$log->write($buffer);
121
		});
122
		if(!$cleanup->isSuccessful()) {
123
			$this->extend('cleanupFailure', $environment, $sha, $log, $project);
124
			$log->write('Warning: Cleanup failed, but fine to continue. Needs manual cleanup sometime.');
125
		}
126
127
		$this->extend('deployEnd', $environment, $sha, $log, $project);
128
129
		if ($error) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $error of type string|null is loosely compared to true; 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...
130
			throw new RuntimeException($error);
131
		}
132
133
		$log->write(sprintf('Deploy of "%s" to "%s" finished', $sha, $name));
134
	}
135
136
	/**
137
	 * Deployment cleanup. We assume it is always safe to run this at the end, regardless of the outcome.
138
	 */
139
	private function deployCleanup(
140
		\DNEnvironment $environment,
0 ignored issues
show
Unused Code introduced by
The parameter $environment is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
141
		\DeploynautLogFile $log,
0 ignored issues
show
Unused Code introduced by
The parameter $log is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
142
		\DNProject $project,
0 ignored issues
show
Unused Code introduced by
The parameter $project is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
143
		$sha,
0 ignored issues
show
Unused Code introduced by
The parameter $sha is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
144
		$args
0 ignored issues
show
Unused Code introduced by
The parameter $args is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
145
	) {
146
147
	}
148
149
	private function deployRollback(
150
		\DNEnvironment $environment,
151
		\DeploynautLogFile $log,
152
		\DNProject $project,
153
		$options,
154
		$args
155
	) {
156
		$sha = $options['sha'];
157
158
		$this->deployCleanup($environment, $log, $project, $sha, $args);
0 ignored issues
show
Unused Code introduced by
The call to the method CapistranoDeploymentBackend::deployCleanup() seems un-needed as the method has no side-effects.

PHP Analyzer performs a side-effects analysis of your code. A side-effect is basically anything that might be visible after the scope of the method is left.

Let’s take a look at an example:

class User
{
    private $email;

    public function getEmail()
    {
        return $this->email;
    }

    public function setEmail($email)
    {
        $this->email = $email;
    }
}

If we look at the getEmail() method, we can see that it has no side-effect. Whether you call this method or not, no future calls to other methods are affected by this. As such code as the following is useless:

$user = new User();
$user->getEmail(); // This line could safely be removed as it has no effect.

On the hand, if we look at the setEmail(), this method _has_ side-effects. In the following case, we could not remove the method call:

$user = new User();
$user->setEmail('email@domain'); // This line has a side-effect (it changes an
                                 // instance variable).
Loading history...
159
		$this->extend('deployFailure', $environment, $sha, $log, $project);
160
161
		$currentBuild = $environment->CurrentBuild();
162
		if (empty($currentBuild) || (!empty($options['no_rollback']) && $options['no_rollback'] !== 'false')) {
163
			throw new RuntimeException($command->getErrorOutput());
164
		}
165
166
		// re-run deploy with the current build sha to rollback
167
		$log->write('Deploy failed. Rolling back');
168
		$rollbackArgs = array_merge($args, ['branch' => $currentBuild->SHA]);
169
		$command = $this->getCommand('deploy', 'web', $environment, $rollbackArgs, $log);
170
		$command->run(function($type, $buffer) use($log) {
171
			$log->write($buffer);
172
		});
173
174
		return $command->isSuccessful();
175
	}
176
177
	/**
178
	 * @param \DNEnvironment $environment
179
	 * @return ArrayList
180
	 */
181
	public function getDeployOptions(\DNEnvironment $environment) {
182
		return new ArrayList([
183
			new PredeployBackupOption($environment->Usage === DNEnvironment::PRODUCTION),
184
			new NoRollbackDeployOption()
185
		]);
186
	}
187
188
	/**
189
	 * Enable a maintenance page for the given environment using the maintenance:enable Capistrano task.
190
	 */
191 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...
192
		$name = $environment->getFullName();
193
		$command = $this->getCommand('maintenance:enable', 'web', $environment, null, $log);
194
		$command->run(function($type, $buffer) use($log) {
195
			$log->write($buffer);
196
		});
197
		if(!$command->isSuccessful()) {
198
			$this->extend('maintenanceEnableFailure', $environment, $log);
199
			throw new RuntimeException($command->getErrorOutput());
200
		}
201
		$log->write(sprintf('Maintenance page enabled on "%s"', $name));
202
	}
203
204
	/**
205
	 * Disable the maintenance page for the given environment using the maintenance:disable Capistrano task.
206
	 */
207 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...
208
		$name = $environment->getFullName();
209
		$command = $this->getCommand('maintenance:disable', 'web', $environment, null, $log);
210
		$command->run(function($type, $buffer) use($log) {
211
			$log->write($buffer);
212
		});
213
		if(!$command->isSuccessful()) {
214
			$this->extend('maintenanceDisableFailure', $environment, $log);
215
			throw new RuntimeException($command->getErrorOutput());
216
		}
217
		$log->write(sprintf('Maintenance page disabled on "%s"', $name));
218
	}
219
220
	/**
221
	 * Check the status using the deploy:check capistrano method
222
	 */
223
	public function ping(\DNEnvironment $environment, \DeploynautLogFile $log, \DNProject $project) {
224
		$command = $this->getCommand('deploy:check', 'web', $environment, null, $log);
225
		$command->run(function($type, $buffer) use($log) {
226
			$log->write($buffer);
227
			echo $buffer;
228
		});
229
	}
230
231
	/**
232
	 * @inheritdoc
233
	 */
234
	public function dataTransfer(\DNDataTransfer $dataTransfer, \DeploynautLogFile $log) {
235
		if($dataTransfer->Direction == 'get') {
236
			$this->dataTransferBackup($dataTransfer, $log);
237
		} else {
238
			$environment = $dataTransfer->Environment();
239
			$project = $environment->Project();
240
			$workingDir = TEMP_FOLDER . DIRECTORY_SEPARATOR . 'deploynaut-transfer-' . $dataTransfer->ID;
241
			$archive = $dataTransfer->DataArchive();
242
243
			// extract the sspak contents, we'll need these so capistrano can restore that content
244
			try {
245
				$archive->extractArchive($workingDir);
246
			} catch(Exception $e) {
247
				$log->write($e->getMessage());
248
				throw new RuntimeException($e->getMessage());
249
			}
250
251
			// validate the contents match the requested transfer mode
252
			$result = $archive->validateArchiveContents($dataTransfer->Mode);
253
			if(!$result->valid()) {
254
				// do some cleaning, get rid of the extracted archive lying around
255
				$process = new AbortableProcess(sprintf('rm -rf %s', escapeshellarg($workingDir)));
256
				$process->setTimeout(120);
257
				$process->run();
258
259
				// log the reason why we can't restore the snapshot and halt the process
260
				$log->write($result->message());
261
				throw new RuntimeException($result->message());
262
			}
263
264
			// Put up a maintenance page during a restore of db or assets.
265
			$this->enableMaintenance($environment, $log, $project);
266
			$this->dataTransferRestore($workingDir, $dataTransfer, $log);
267
			$this->disableMaintenance($environment, $log, $project);
268
		}
269
	}
270
271
	/**
272
	 * @param string $action Capistrano action to be executed
273
	 * @param string $roles Defining a server role is required to target only the required servers.
274
	 * @param \DNEnvironment $environment
275
	 * @param array<string>|null $args Additional arguments for process
276
	 * @param \DeploynautLogFile $log
277
	 * @return \Symfony\Component\Process\Process
278
	 */
279
	public function getCommand($action, $roles, \DNEnvironment $environment, $args = null, \DeploynautLogFile $log) {
280
		$name = $environment->getFullName();
281
		$env = $environment->Project()->getProcessEnv();
282
283
		if(!$args) {
284
			$args = array();
285
		}
286
		$args['history_path'] = realpath(DEPLOYNAUT_LOG_PATH . '/');
287
		$args['environment_id'] = $environment->ID;
288
289
		// Inject env string directly into the command.
290
		// Capistrano doesn't like the $process->setEnv($env) we'd normally do below.
291
		$envString = '';
292
		if(!empty($env)) {
293
			$envString .= 'env ';
294
			foreach($env as $key => $value) {
295
				$envString .= "$key=\"$value\" ";
296
			}
297
		}
298
299
		$data = DNData::inst();
300
		// Generate a capfile from a template
301
		$capTemplate = file_get_contents(BASE_PATH . '/deploynaut/Capfile.template');
302
		$cap = str_replace(
303
			array('<config root>', '<ssh key>', '<base path>'),
304
			array($data->getEnvironmentDir(), DEPLOYNAUT_SSH_KEY, BASE_PATH),
305
			$capTemplate
306
		);
307
308
		if(defined('DEPLOYNAUT_CAPFILE')) {
309
			$capFile = DEPLOYNAUT_CAPFILE;
310
		} else {
311
			$capFile = ASSETS_PATH . '/Capfile';
312
		}
313
		file_put_contents($capFile, $cap);
314
315
		$command = "{$envString}cap -f " . escapeshellarg($capFile) . " -vv $name $action ROLES=$roles";
316
		foreach($args as $argName => $argVal) {
317
			$command .= ' -s ' . escapeshellarg($argName) . '=' . escapeshellarg($argVal);
318
		}
319
320
		$log->write(sprintf('Running command: %s', $command));
321
322
		$process = new AbortableProcess($command);
323
		$process->setTimeout(3600);
324
		return $process;
325
	}
326
327
	/**
328
	 * Backs up database and/or assets to a designated folder,
329
	 * and packs up the files into a single sspak.
330
	 *
331
	 * @param \DNDataTransfer    $dataTransfer
332
	 * @param DeploynautLogFile $log
333
	 */
334
	protected function dataTransferBackup(\DNDataTransfer $dataTransfer, \DeploynautLogFile $log) {
335
		$environment = $dataTransfer->Environment();
336
		$name = $environment->getFullName();
337
338
		// Associate a new archive with the transfer.
339
		// Doesn't retrieve a filepath just yet, need to generate the files first.
340
		$dataArchive = DNDataArchive::create();
341
		$dataArchive->Mode = $dataTransfer->Mode;
342
		$dataArchive->AuthorID = $dataTransfer->AuthorID;
343
		$dataArchive->OriginalEnvironmentID = $environment->ID;
344
		$dataArchive->EnvironmentID = $environment->ID;
345
		$dataArchive->IsBackup = $dataTransfer->IsBackupDataTransfer();
346
347
		// Generate directory structure with strict permissions (contains very sensitive data)
348
		$filepathBase = $dataArchive->generateFilepath($dataTransfer);
349
		mkdir($filepathBase, 0700, true);
350
351
		$databasePath = $filepathBase . DIRECTORY_SEPARATOR . 'database.sql.gz';
352
353
		// Backup database
354 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...
355
			$log->write(sprintf('Backup of database from "%s" started', $name));
356
			$command = $this->getCommand('data:getdb', 'db', $environment, array('data_path' => $databasePath), $log);
357
			$command->run(function($type, $buffer) use($log) {
358
				$log->write($buffer);
359
			});
360
			if(!$command->isSuccessful()) {
361
				$this->extend('dataTransferFailure', $environment, $log);
362
				throw new RuntimeException($command->getErrorOutput());
363
			}
364
			$log->write(sprintf('Backup of database from "%s" done', $name));
365
		}
366
367
		// Backup assets
368 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...
369
			$log->write(sprintf('Backup of assets from "%s" started', $name));
370
			$command = $this->getCommand('data:getassets', 'web', $environment, array('data_path' => $filepathBase), $log);
371
			$command->run(function($type, $buffer) use($log) {
372
				$log->write($buffer);
373
			});
374
			if(!$command->isSuccessful()) {
375
				$this->extend('dataTransferFailure', $environment, $log);
376
				throw new RuntimeException($command->getErrorOutput());
377
			}
378
			$log->write(sprintf('Backup of assets from "%s" done', $name));
379
		}
380
381
		// ensure the database connection is re-initialised, which is needed if the transfer
382
		// above took a really long time because the handle to the db may have become invalid.
383
		global $databaseConfig;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
384
		DB::connect($databaseConfig);
385
386
		$log->write('Creating sspak...');
387
388
		$sspakFilename = sprintf('%s.sspak', $dataArchive->generateFilename($dataTransfer));
389
		$sspakFilepath = $filepathBase . DIRECTORY_SEPARATOR . $sspakFilename;
390
391
		try {
392
			$dataArchive->attachFile($sspakFilepath, $dataTransfer);
393
			$dataArchive->setArchiveFromFiles($filepathBase);
394
		} catch(Exception $e) {
395
			$log->write($e->getMessage());
396
			throw new RuntimeException($e->getMessage());
397
		}
398
399
		// Remove any assets and db files lying around, they're not longer needed as they're now part
400
		// of the sspak file we just generated. Use --force to avoid errors when files don't exist,
401
		// e.g. when just an assets backup has been requested and no database.sql exists.
402
		$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...
403
		$process->setTimeout(120);
404
		$process->run();
405
		if(!$process->isSuccessful()) {
406
			$log->write('Could not delete temporary files');
407
			throw new RuntimeException($process->getErrorOutput());
408
		}
409
410
		$log->write(sprintf('Creating sspak file done: %s', $dataArchive->ArchiveFile()->getAbsoluteURL()));
411
	}
412
413
	/**
414
	 * Utility function for triggering the db rebuild and flush.
415
	 * Also cleans up and generates new error pages.
416
	 * @param DeploynautLogFile $log
417
	 */
418 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...
419
		$name = $environment->getFullName();
420
		$command = $this->getCommand('deploy:migrate', 'web', $environment, null, $log);
421
		$command->run(function($type, $buffer) use($log) {
422
			$log->write($buffer);
423
		});
424
		if(!$command->isSuccessful()) {
425
			$log->write(sprintf('Rebuild of "%s" failed: %s', $name, $command->getErrorOutput()));
426
			throw new RuntimeException($command->getErrorOutput());
427
		}
428
		$log->write(sprintf('Rebuild of "%s" done', $name));
429
	}
430
431
	/**
432
	 * Extracts a *.sspak file referenced through the passed in $dataTransfer
433
	 * and pushes it to the environment referenced in $dataTransfer.
434
	 *
435
	 * @param string $workingDir Directory for the unpacked files.
436
	 * @param DNDataTransfer $dataTransfer
437
	 * @param DeploynautLogFile $log
438
	 */
439
	protected function dataTransferRestore($workingDir, \DNDataTransfer $dataTransfer, \DeploynautLogFile $log) {
440
		$environment = $dataTransfer->Environment();
441
		$name = $environment->getFullName();
442
443
		// Rollback cleanup.
444
		$self = $this;
445 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...
446
			// Rebuild makes sense even if failed - maybe we can at least partly recover.
447
			$self->rebuild($environment, $log);
448
			$process = new AbortableProcess(sprintf('rm -rf %s', escapeshellarg($workingDir)));
449
			$process->setTimeout(120);
450
			$process->run();
451
		};
452
453
		// Restore database into target environment
454 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...
455
			$log->write(sprintf('Restore of database to "%s" started', $name));
456
			$args = array('data_path' => $workingDir . DIRECTORY_SEPARATOR . 'database.sql');
457
			$command = $this->getCommand('data:pushdb', 'db', $environment, $args, $log);
458
			$command->run(function($type, $buffer) use($log) {
459
				$log->write($buffer);
460
			});
461
			if(!$command->isSuccessful()) {
462
				$cleanupFn();
463
				$log->write(sprintf('Restore of database to "%s" failed: %s', $name, $command->getErrorOutput()));
464
				$this->extend('dataTransferFailure', $environment, $log);
465
				throw new RuntimeException($command->getErrorOutput());
466
			}
467
			$log->write(sprintf('Restore of database to "%s" done', $name));
468
		}
469
470
		// Restore assets into target environment
471 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...
472
			$log->write(sprintf('Restore of assets to "%s" started', $name));
473
			$args = array('data_path' => $workingDir . DIRECTORY_SEPARATOR . 'assets');
474
			$command = $this->getCommand('data:pushassets', 'web', $environment, $args, $log);
475
			$command->run(function($type, $buffer) use($log) {
476
				$log->write($buffer);
477
			});
478
			if(!$command->isSuccessful()) {
479
				$cleanupFn();
480
				$log->write(sprintf('Restore of assets to "%s" failed: %s', $name, $command->getErrorOutput()));
481
				$this->extend('dataTransferFailure', $environment, $log);
482
				throw new RuntimeException($command->getErrorOutput());
483
			}
484
			$log->write(sprintf('Restore of assets to "%s" done', $name));
485
		}
486
487
		$log->write('Rebuilding and cleaning up');
488
		$cleanupFn();
489
	}
490
491
	/**
492
	 * This is mostly copy-pasted from Anthill/Smoketest.
493
	 *
494
	 * @param \DNEnvironment $environment
495
	 * @param \DeploynautLogFile $log
496
	 * @return bool
497
	 */
498
	protected function smokeTest(\DNEnvironment $environment, \DeploynautLogFile $log) {
499
		$url = $environment->getBareURL();
500
		$timeout = 600;
501
		$tick = 60;
502
503
		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...
504
			$log->write('Skipping site accessible check: no URL found.');
505
			return true;
506
		}
507
508
		$start = time();
509
		$infoTick = time() + $tick;
510
511
		$log->write(sprintf(
512
			'Waiting for "%s" to become accessible... (timeout: %smin)',
513
			$url,
514
			$timeout / 60
515
		));
516
517
		// configure curl so that curl_exec doesn't wait a long time for a response
518
		$ch = curl_init();
519
		curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);
520
		curl_setopt($ch, CURLOPT_TIMEOUT, 5);
521
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
522
		curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
523
		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...
524
		curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);
525
		curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
526
		curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
527
		curl_setopt($ch, CURLOPT_URL, $url);
528
		curl_setopt($ch, CURLOPT_USERAGENT, 'Rainforest');
529
		$success = false;
530
531
		// query the site every second. Note that if the URL doesn't respond,
532
		// curl_exec will take 5 seconds to timeout (see CURLOPT_CONNECTTIMEOUT and CURLOPT_TIMEOUT above)
533
		do {
534
			if(time() > $start + $timeout) {
535
				$log->write(sprintf(' * Failed: check for %s timed out after %smin', $url, $timeout / 60));
536
				return false;
537
			}
538
539
			$response = curl_exec($ch);
540
541
			// check the HTTP response code for HTTP protocols
542
			$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
543
			if($status && !in_array($status, [500, 501, 502, 503, 504])) {
544
				$success = true;
545
			}
546
547
			// check for any curl errors, mostly for checking the response state of non-HTTP protocols,
548
			// but applies to checks of any protocol
549
			if($response && !curl_errno($ch)) {
550
				$success = true;
551
			}
552
553
			// Produce an informational ticker roughly every $tick
554
			if (time() > $infoTick) {
555
				$message = [];
556
557
				// Collect status information from different sources.
558
				if ($status) {
559
					$message[] = sprintf('HTTP status code is %s', $status);
560
				}
561
				if (!$response) {
562
					$message[] = 'response is empty';
563
				}
564
				if ($error = curl_error($ch)) {
565
					$message[] = sprintf('request error: %s', $error);
566
				}
567
568
				$log->write(sprintf(
569
					' * Still waiting: %s...',
570
					implode(', ', $message)
571
				));
572
573
				$infoTick = time() + $tick;
574
			}
575
576
			sleep(1);
577
		} while(!$success);
578
579
		curl_close($ch);
580
		$log->write(' * Success: site is accessible!');
581
		return true;
582
	}
583
584
}
585