1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace Morphatic\AutoDeploy\Controllers; |
4
|
|
|
|
5
|
|
|
use Log; |
6
|
|
|
use Mail; |
7
|
|
|
use Monolog\Logger; |
8
|
|
|
use Illuminate\Http\Request; |
9
|
|
|
use Illuminate\Routing\Controller; |
10
|
|
|
use AdamBrett\ShellWrapper\Command\Builder as Command; |
11
|
|
|
use AdamBrett\ShellWrapper\Command\CommandInterface; |
12
|
|
|
use AdamBrett\ShellWrapper\Runners\Exec; |
13
|
|
|
use Monolog\Formatter\HtmlFormatter; |
14
|
|
|
use Monolog\Handler\SwiftMailerHandler; |
15
|
|
|
use Morphatic\AutoDeploy\Origins\OriginInterface; |
16
|
|
|
|
17
|
|
|
class DeployController extends Controller |
18
|
|
|
{ |
19
|
|
|
/** |
20
|
|
|
* The origin of the webhook request. |
21
|
|
|
* |
22
|
|
|
* @var Morphatic\AutoDeploy\Origins\OriginInterface |
23
|
|
|
*/ |
24
|
|
|
private $origin; |
25
|
|
|
|
26
|
|
|
/** |
27
|
|
|
* The URL of the repo to be cloned. |
28
|
|
|
* |
29
|
|
|
* @var string |
30
|
|
|
*/ |
31
|
|
|
private $repo_url; |
|
|
|
|
32
|
|
|
|
33
|
|
|
/** |
34
|
|
|
* The absolute path of the directory on the server that contains the project. |
35
|
|
|
* |
36
|
|
|
* @var string |
37
|
|
|
*/ |
38
|
|
|
private $webroot; |
39
|
|
|
|
40
|
|
|
/** |
41
|
|
|
* The absolute path of the directory where the new deployment will be set up. |
42
|
|
|
* |
43
|
|
|
* @var string |
44
|
|
|
*/ |
45
|
|
|
private $install_dir; |
|
|
|
|
46
|
|
|
|
47
|
|
|
/** |
48
|
|
|
* A log of the results of the entire deploy process. |
49
|
|
|
* |
50
|
|
|
* @var Monolog\Logger |
51
|
|
|
*/ |
52
|
|
|
private $log; |
53
|
|
|
|
54
|
|
|
/** |
55
|
|
|
* The commit ID for this commit. |
56
|
|
|
* |
57
|
|
|
* @var string |
58
|
|
|
*/ |
59
|
|
|
private $commit_id; |
|
|
|
|
60
|
|
|
|
61
|
|
|
/** |
62
|
|
|
* The commit ID for this commit. |
63
|
|
|
* |
64
|
|
|
* @var AdamBrett\ShellWrapper\Runners\Exec |
65
|
|
|
*/ |
66
|
|
|
private $shell; |
67
|
|
|
|
68
|
|
|
/** |
69
|
|
|
* The result of this commit. |
70
|
|
|
* |
71
|
|
|
* @var array |
72
|
|
|
*/ |
73
|
|
|
private $result; |
74
|
|
|
|
75
|
|
|
/** |
76
|
|
|
* Create a new DeployController instance. |
77
|
|
|
* |
78
|
|
|
* @param Morphatic\AutoDeploy\Origins\OriginInterface $origin The origin of the webhook |
|
|
|
|
79
|
|
|
* @param AdamBrett\ShellWrapper\Runners\Exec $exec The shell command execution class |
|
|
|
|
80
|
|
|
*/ |
81
|
36 |
|
public function __construct(OriginInterface $origin, Exec $exec) |
82
|
|
|
{ |
83
|
|
|
// set class variables related to the webhook origin |
84
|
36 |
|
$this->origin = $origin; |
|
|
|
|
85
|
36 |
|
$this->repo_url = $this->origin->getRepoUrl(); |
86
|
36 |
|
$this->commit_id = $this->origin->getCommitId(); |
87
|
|
|
|
88
|
|
|
// create an instance of the shell exec |
89
|
36 |
|
$this->shell = $exec; |
|
|
|
|
90
|
36 |
|
} |
91
|
|
|
|
92
|
|
|
/** |
93
|
|
|
* Handles incoming webhook requests. |
94
|
|
|
*/ |
95
|
12 |
|
public function index() |
96
|
|
|
{ |
97
|
|
|
// get the parameters for the event we're handling |
98
|
12 |
|
$config_key = "auto-deploy.{$this->origin->name}.{$this->origin->event()}"; |
|
|
|
|
99
|
12 |
|
$this->webroot = config("$config_key.webroot"); |
|
|
|
|
100
|
12 |
|
$this->install_dir = dirname($this->webroot).'/'.date('Y-m-d').'_'.$this->commit_id; |
101
|
12 |
|
$steps = config("$config_key.steps"); |
|
|
|
|
102
|
|
|
|
103
|
|
|
// set up logging to email |
104
|
12 |
|
$domain = parse_url(config('app.url'), PHP_URL_HOST); |
105
|
12 |
|
$msg = \Swift_Message::newInstance('Project Deployed') |
106
|
12 |
|
->setFrom(["do_not_reply@$domain" => 'Laravel Auto-Deploy[$domain]']) |
107
|
12 |
|
->setTo(config('auto-deploy.notify')) |
108
|
12 |
|
->setBody('', 'text/html'); |
109
|
12 |
|
$handler = new SwiftMailerHandler(Mail::getSwiftMailer(), $msg, Logger::NOTICE); |
110
|
12 |
|
$handler->setFormatter(new HtmlFormatter()); |
111
|
12 |
|
$this->log = Log::getMonolog(); |
112
|
12 |
|
$this->log->pushHandler($handler); |
113
|
|
|
|
114
|
|
|
// execute the configured steps |
115
|
12 |
|
$this->result = [ |
116
|
12 |
|
'Commit_ID' => $this->commit_id, |
117
|
12 |
|
'Timestamp' => date('r'), |
118
|
12 |
|
'output' => '', |
119
|
|
|
]; |
120
|
12 |
|
$whitelist = ['backupDatabase','pull','composer','npm','migrate','seed','deploy']; |
121
|
12 |
|
foreach ($steps as $step) { |
122
|
12 |
|
if (in_array($step, $whitelist) && !$this->{$step}()) { |
|
|
|
|
123
|
4 |
|
$this->log->error('Deploy failed.', $this->result); |
124
|
|
|
|
125
|
8 |
|
return; |
126
|
|
|
} |
127
|
5 |
|
} |
128
|
8 |
|
$this->log->notice('Deploy succeeded!', $this->result); |
129
|
8 |
|
} |
130
|
|
|
|
131
|
|
|
/** |
132
|
|
|
* Runs a shell command, logs, and handles the result. |
133
|
|
|
* |
134
|
|
|
* @param AdamBrett\ShellWrapper\CommandInterface $cmd The text of the command to be run |
|
|
|
|
135
|
|
|
* |
136
|
|
|
* @return bool True if the command was successful, false on error |
137
|
|
|
*/ |
138
|
12 |
|
private function ex(CommandInterface $cmd) |
|
|
|
|
139
|
|
|
{ |
140
|
|
|
// try to run the command |
141
|
12 |
|
$this->shell->run($cmd); |
142
|
12 |
|
$output = $this->shell->getOutput(); |
143
|
12 |
|
$return_var = $this->shell->getReturnValue(); |
|
|
|
|
144
|
|
|
|
145
|
|
|
// record the result |
146
|
12 |
|
$output = count($output) ? implode("\n", $output)."\n" : ''; |
147
|
12 |
|
$this->result['output'] .= "$cmd\n$output"; |
148
|
|
|
|
149
|
|
|
// return a boolean |
150
|
12 |
|
return 0 === $return_var; |
|
|
|
|
151
|
|
|
} |
152
|
|
|
|
153
|
|
|
/** |
154
|
|
|
* Backup the database. |
155
|
|
|
* |
156
|
|
|
* @return bool True if the database was successfully backed up. False on error. |
157
|
|
|
*/ |
158
|
12 |
|
private function backupDatabase() |
|
|
|
|
159
|
|
|
{ |
160
|
|
|
// get the name of the DB to backed up and the connection to use |
161
|
12 |
|
$dbdir = database_path(); |
162
|
12 |
|
$dbconn = config('database.default'); |
163
|
12 |
|
$dbname = config("database.connections.$dbconn.database"); |
164
|
|
|
|
165
|
|
|
// make a directory for the backup file and switch into that directory |
166
|
12 |
|
$cmd = new Command('cd'); |
167
|
12 |
|
$cmd->addParam($dbdir) |
168
|
12 |
|
->addSubCommand('&&') |
169
|
12 |
|
->addSubCommand('mkdir') |
170
|
12 |
|
->addParam('backups'); |
171
|
12 |
|
if ($this->ex($cmd)) { |
172
|
12 |
|
$cmd = new Command('cd'); |
173
|
12 |
|
$cmd->addParam($dbdir.'/backups') |
174
|
12 |
|
->addSubCommand('&&'); |
175
|
|
|
switch ($dbconn) { |
176
|
12 |
|
case 'sqlite': |
177
|
2 |
|
$cmd->addSubCommand('cp'); |
178
|
2 |
|
$cmd->addParam($dbname) |
179
|
2 |
|
->addParam('.'); |
180
|
|
|
|
181
|
2 |
|
return $this->ex($cmd); |
182
|
5 |
|
case 'mysql': |
183
|
6 |
|
$cmd->addSubCommand('mysqldump'); |
184
|
6 |
|
$cmd->addParam($dbname) |
185
|
6 |
|
->addParam('>') |
186
|
6 |
|
->addParam("$dbname.sql"); |
187
|
|
|
|
188
|
6 |
|
return $this->ex($cmd); |
189
|
3 |
|
case 'pgsql': |
190
|
2 |
|
$cmd->addSubCommand('pg_dump'); |
191
|
2 |
|
$cmd->addParam($dbname) |
192
|
2 |
|
->addParam('>') |
193
|
2 |
|
->addParam("$dbname.sql"); |
194
|
|
|
|
195
|
2 |
|
return $this->ex($cmd); |
196
|
|
|
} |
197
|
1 |
|
} |
198
|
|
|
|
199
|
2 |
|
return false; |
200
|
|
|
} |
201
|
|
|
|
202
|
|
|
/** |
203
|
|
|
* Create a new directory parallel to the webroot and clone the project into that directory. |
204
|
|
|
* |
205
|
|
|
* @return bool True if the clone is successful. False otherwise. |
206
|
|
|
*/ |
207
|
10 |
|
private function pull() |
|
|
|
|
208
|
|
|
{ |
209
|
10 |
|
if (is_writable(dirname($this->install_dir))) { |
210
|
8 |
|
$cmd = new Command('mkdir'); |
211
|
8 |
|
$cmd->addFlag('p') |
212
|
8 |
|
->addParam($this->install_dir); |
213
|
8 |
|
if ($this->ex($cmd)) { |
214
|
8 |
|
$cmd = new Command('cd'); |
215
|
8 |
|
$cmd->addParam($this->install_dir) |
216
|
8 |
|
->addSubCommand('&&') |
217
|
8 |
|
->addSubCommand('git') |
218
|
8 |
|
->addSubCommand('clone') |
219
|
8 |
|
->addParam($this->repo_url) |
220
|
8 |
|
->addParam('.'); |
221
|
|
|
|
222
|
8 |
|
return $this->ex($cmd); |
223
|
|
|
} |
224
|
|
|
} |
225
|
|
|
|
226
|
2 |
|
return false; |
227
|
|
|
} |
228
|
|
|
|
229
|
|
|
/** |
230
|
|
|
* Update composer and run composer update. |
231
|
|
|
* |
232
|
|
|
* @return bool True if the update is successful. False otherwise. |
233
|
|
|
*/ |
234
|
8 |
|
private function composer() |
|
|
|
|
235
|
|
|
{ |
236
|
8 |
|
$cmd = new Command('cd'); |
237
|
8 |
|
$cmd->addParam($this->install_dir) |
238
|
8 |
|
->addSubCommand('&&') |
239
|
8 |
|
->addSubCommand('composer') |
240
|
8 |
|
->addParam('self-update') |
241
|
8 |
|
->addSubCommand('&&') |
242
|
8 |
|
->addSubCommand('composer') |
243
|
8 |
|
->addParam('update') |
244
|
8 |
|
->addArgument('no-interaction'); |
245
|
|
|
|
246
|
8 |
|
return $this->ex($cmd); |
247
|
|
|
} |
248
|
|
|
|
249
|
|
|
/** |
250
|
|
|
* Run npm update. |
251
|
|
|
* |
252
|
|
|
* @return bool True if npm is successful. False otherwise. |
253
|
|
|
*/ |
254
|
8 |
|
private function npm() |
|
|
|
|
255
|
|
|
{ |
256
|
8 |
|
$cmd = new Command('cd'); |
257
|
8 |
|
$cmd->addParam($this->install_dir) |
258
|
8 |
|
->addSubCommand('&&') |
259
|
8 |
|
->addSubCommand('npm') |
260
|
8 |
|
->addParam('update'); |
261
|
|
|
|
262
|
8 |
|
return $this->ex($cmd); |
263
|
|
|
} |
264
|
|
|
|
265
|
|
|
/** |
266
|
|
|
* Run any necessary database migrations. |
267
|
|
|
* |
268
|
|
|
* @return bool True if the migration is successful. False otherwise. |
269
|
|
|
*/ |
270
|
8 |
|
private function migrate() |
|
|
|
|
271
|
|
|
{ |
272
|
8 |
|
$cmd = new Command('cd'); |
273
|
8 |
|
$cmd->addParam($this->install_dir) |
274
|
8 |
|
->addSubCommand('&&') |
275
|
8 |
|
->addSubCommand('php') |
276
|
8 |
|
->addSubCommand('artisan') |
277
|
8 |
|
->addParam('migrate') |
278
|
8 |
|
->addArgument('force') |
279
|
8 |
|
->addArgument('no-interaction'); |
280
|
|
|
|
281
|
8 |
|
return $this->ex($cmd); |
282
|
|
|
} |
283
|
|
|
|
284
|
|
|
/** |
285
|
|
|
* Run any necessary database migrations. |
286
|
|
|
* |
287
|
|
|
* @return bool True if the migration is successful. False otherwise. |
288
|
|
|
*/ |
289
|
8 |
|
private function seed() |
|
|
|
|
290
|
|
|
{ |
291
|
8 |
|
$cmd = new Command('cd'); |
292
|
8 |
|
$cmd->addParam($this->install_dir) |
293
|
8 |
|
->addSubCommand('&&') |
294
|
8 |
|
->addSubCommand('php') |
295
|
8 |
|
->addSubCommand('artisan') |
296
|
8 |
|
->addParam('db:seed'); |
297
|
|
|
|
298
|
8 |
|
return $this->ex($cmd); |
299
|
|
|
} |
300
|
|
|
|
301
|
|
|
/** |
302
|
|
|
* Symlinks the new deploy directory to the webroot. |
303
|
|
|
* |
304
|
|
|
* @return bool True if the symlink is successful. False otherwise. |
305
|
|
|
*/ |
306
|
8 |
|
private function deploy() |
|
|
|
|
307
|
|
|
{ |
308
|
8 |
|
$cmd = new Command('cd'); |
309
|
8 |
|
$cmd->addParam(dirname($this->webroot)) |
310
|
8 |
|
->addSubCommand('&&') |
311
|
8 |
|
->addSubCommand('ln') |
312
|
8 |
|
->addFlag('fs') |
313
|
8 |
|
->addParam($this->install_dir) |
314
|
8 |
|
->addParam($this->webroot); |
315
|
|
|
|
316
|
8 |
|
return $this->ex($cmd); |
317
|
|
|
} |
318
|
|
|
} |
319
|
|
|
|
This check examines a number of code elements and verifies that they conform to the given naming conventions.
You can set conventions for local variables, abstract classes, utility classes, constant, properties, methods, parameters, interfaces, classes, exceptions and special methods.