Completed
Pull Request — master (#583)
by Sean
03:03
created

DNDeployment::getRepository()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 6
rs 9.4285
cc 2
eloc 4
nc 2
nop 0
1
<?php
2
3
/**
4
 * Class representing a single deplyoment (passed or failed) at a time to a particular environment
5
 *
6
 * @property string $SHA
7
 * @property string $ResqueToken
8
 * @property string $State
9
 *
10
 * @method DNEnvironment Environment()
11
 * @property int EnvironmentID
12
 * @method Member Deployer()
13
 * @property int DeployerID
14
 */
15
class DNDeployment extends DataObject implements Finite\StatefulInterface, HasStateMachine {
16
17
	const STATE_NEW = 'New';
18
	const STATE_SUBMITTED = 'Submitted';
19
	const STATE_INVALID = 'Invalid';
20
	const STATE_QUEUED = 'Queued';
21
	const STATE_DEPLOYING = 'Deploying';
22
	const STATE_ABORTING = 'Aborting';
23
	const STATE_COMPLETED = 'Completed';
24
	const STATE_FAILED = 'Failed';
25
26
	const TR_SUBMIT = 'submit';
27
	const TR_INVALIDATE = 'invalidate';
28
	const TR_QUEUE = 'queue';
29
	const TR_DEPLOY = 'deploy';
30
	const TR_ABORT = 'abort';
31
	const TR_COMPLETE = 'complete';
32
	const TR_FAIL = 'fail';
33
34
	/**
35
	 * @var array
36
	 */
37
	private static $db = array(
38
		"SHA" => "GitSHA",
39
		"ResqueToken" => "Varchar(255)",
40
		// The branch that was used to deploy this. Can't really be inferred from Git history because
41
		// the commit could appear in lots of branches that are irrelevant to the user when it comes
42
		// to deployment history, and the branch may have been deleted.
43
		"Branch" => "Varchar(255)",
44
		"State" => "Enum('New, Submitted, Invalid, Queued, Deploying, Aborting, Completed, Failed', 'New')",
45
		// JSON serialised DeploymentStrategy.
46
		"Strategy" => "Text",
47
		"DeploymentDate" => "SS_Datetime"
48
	);
49
50
	/**
51
	 * @var array
52
	 */
53
	private static $has_one = array(
54
		"Environment" => "DNEnvironment",
55
		"Deployer" => "Member",
56
		"Approver" => "Member"
57
	);
58
59
	private static $default_sort = '"LastEdited" DESC';
60
61
	private static $dependencies = [
62
		'stateMachineFactory' => '%$StateMachineFactory'
63
	];
64
65
	public function getTitle() {
66
		return "#{$this->ID}: {$this->SHA} (Status: {$this->Status})";
67
	}
68
69
	private static $summary_fields = array(
70
		'LastEdited' => 'Last Edited',
71
		'SHA' => 'SHA',
72
		'State' => 'State',
73
		'Deployer.Name' => 'Deployer'
74
	);
75
76
	public function getFiniteState() {
77
		return $this->State;
78
	}
79
80
	public function setFiniteState($state) {
81
		$this->State = $state;
82
		$this->write();
83
	}
84
85
	public function getStatus() {
86
		return $this->State;
87
	}
88
89
	public function getMachine() {
90
<<<<<<< Updated upstream
0 ignored issues
show
Bug introduced by
This code did not parse for me. Apparently, there is an error somewhere around this line:

Syntax error, unexpected T_SL
Loading history...
91
		return $this->stateMachineFactory->forDNDeployment($this);
92
||||||| merged common ancestors
93
		$loader = new Finite\Loader\ArrayLoader([
94
			'class'   => 'DNDeployment',
95
			'states'  => [
96
				self::STATE_NEW => ['type' => StateInterface::TYPE_INITIAL],
97
				self::STATE_SUBMITTED => ['type' => StateInterface::TYPE_NORMAL],
98
				self::STATE_INVALID => ['type' => StateInterface::TYPE_NORMAL],
99
				self::STATE_QUEUED => ['type' => StateInterface::TYPE_NORMAL],
100
				self::STATE_DEPLOYING => ['type' => StateInterface::TYPE_NORMAL],
101
				self::STATE_ABORTING => ['type' => StateInterface::TYPE_NORMAL],
102
				self::STATE_COMPLETED => ['type' => StateInterface::TYPE_FINAL],
103
				self::STATE_FAILED => ['type' => StateInterface::TYPE_FINAL],
104
			],
105
			'transitions' => [
106
				self::TR_SUBMIT => ['from' => [self::STATE_NEW], 'to' => self::STATE_SUBMITTED],
107
				self::TR_QUEUE => ['from' => [self::STATE_SUBMITTED], 'to' => self::STATE_QUEUED],
108
				self::TR_INVALIDATE  => [
109
					'from' => [self::STATE_NEW, self::STATE_SUBMITTED],
110
					'to' => self::STATE_INVALID
111
				],
112
				self::TR_DEPLOY  => ['from' => [self::STATE_QUEUED], 'to' => self::STATE_DEPLOYING],
113
				self::TR_ABORT => [
114
					'from' => [
115
						self::STATE_QUEUED,
116
						self::STATE_DEPLOYING,
117
						self::STATE_ABORTING
118
					],
119
					'to' => self::STATE_ABORTING
120
				],
121
				self::TR_COMPLETE => ['from' => [self::STATE_DEPLOYING], 'to' => self::STATE_COMPLETED],
122
				self::TR_FAIL  => [
123
					'from' => [
124
						self::STATE_NEW,
125
						self::STATE_SUBMITTED,
126
						self::STATE_QUEUED,
127
						self::STATE_INVALID,
128
						self::STATE_DEPLOYING,
129
						self::STATE_ABORTING
130
					],
131
					'to' => self::STATE_FAILED
132
				],
133
			],
134
			'callbacks' => [
135
				'after' => [
136
					['to' => [self::STATE_QUEUED], 'do' => [$this, 'onQueue']],
137
					['to' => [self::STATE_ABORTING], 'do' => [$this, 'onAbort']],
138
				]
139
			]
140
		]);
141
		$stateMachine = new Finite\StateMachine\StateMachine($this);
142
		$loader->load($stateMachine);
143
		$stateMachine->initialize();
144
		return $stateMachine;
145
	}
146
147
148
	public function onQueue() {
149
		$log = $this->log();
150
		$token = $this->enqueueDeployment();
151
		$this->ResqueToken = $token;
152
		$this->write();
153
154
		$message = sprintf('Deploy queued as job %s (sigFile is %s)', $token, DeployJob::sig_file_for_data_object($this));
155
		$log->write($message);
156
	}
157
158
	public function onAbort() {
159
		// 2 is SIGINT - we can't use SIGINT constant in the mod_apache context.
160
		DeployJob::set_signal($this, 2);
161
=======
162
		$notificationService = Injector::inst()->get('DeploymentNotificationService');
163
164
		$loader = new Finite\Loader\ArrayLoader([
165
			'class'   => 'DNDeployment',
166
			'states'  => [
167
				self::STATE_NEW => ['type' => StateInterface::TYPE_INITIAL],
168
				self::STATE_SUBMITTED => ['type' => StateInterface::TYPE_NORMAL],
169
				self::STATE_INVALID => ['type' => StateInterface::TYPE_NORMAL],
170
				self::STATE_QUEUED => ['type' => StateInterface::TYPE_NORMAL],
171
				self::STATE_DEPLOYING => ['type' => StateInterface::TYPE_NORMAL],
172
				self::STATE_ABORTING => ['type' => StateInterface::TYPE_NORMAL],
173
				self::STATE_COMPLETED => ['type' => StateInterface::TYPE_FINAL],
174
				self::STATE_FAILED => ['type' => StateInterface::TYPE_FINAL],
175
			],
176
			'transitions' => [
177
				self::TR_SUBMIT => ['from' => [self::STATE_NEW], 'to' => self::STATE_SUBMITTED],
178
				self::TR_QUEUE => ['from' => [self::STATE_SUBMITTED], 'to' => self::STATE_QUEUED],
179
				self::TR_INVALIDATE  => [
180
					'from' => [self::STATE_NEW, self::STATE_SUBMITTED],
181
					'to' => self::STATE_INVALID
182
				],
183
				self::TR_DEPLOY  => ['from' => [self::STATE_QUEUED], 'to' => self::STATE_DEPLOYING],
184
				self::TR_ABORT => [
185
					'from' => [
186
						self::STATE_QUEUED,
187
						self::STATE_DEPLOYING,
188
						self::STATE_ABORTING
189
					],
190
					'to' => self::STATE_ABORTING
191
				],
192
				self::TR_COMPLETE => ['from' => [self::STATE_DEPLOYING], 'to' => self::STATE_COMPLETED],
193
				self::TR_FAIL  => [
194
					'from' => [
195
						self::STATE_NEW,
196
						self::STATE_SUBMITTED,
197
						self::STATE_QUEUED,
198
						self::STATE_INVALID,
199
						self::STATE_DEPLOYING,
200
						self::STATE_ABORTING
201
					],
202
					'to' => self::STATE_FAILED
203
				],
204
			],
205
			'callbacks' => [
206
				'after' => [
207
					['to' => [self::STATE_QUEUED], 'do' => [$this, 'onQueue']],
208
					['to' => [self::STATE_ABORTING], 'do' => [$this, 'onAbort']],
209
					['to' => [self::STATE_SUBMITTED], 'do' => [$notificationService, 'onSubmitted']]
210
				]
211
			]
212
		]);
213
		$stateMachine = new Finite\StateMachine\StateMachine($this);
214
		$loader->load($stateMachine);
215
		$stateMachine->initialize();
216
		return $stateMachine;
217
	}
218
219
220
	public function onQueue() {
221
		$log = $this->log();
222
		$token = $this->enqueueDeployment();
223
		$this->ResqueToken = $token;
224
		$this->write();
225
226
		$message = sprintf('Deploy queued as job %s (sigFile is %s)', $token, DeployJob::sig_file_for_data_object($this));
227
		$log->write($message);
228
	}
229
230
	public function onAbort() {
231
		// 2 is SIGINT - we can't use SIGINT constant in the mod_apache context.
232
		DeployJob::set_signal($this, 2);
233
>>>>>>> Stashed changes
234
	}
235
236
	public function Link() {
237
		return Controller::join_links($this->Environment()->Link(), 'deploy', $this->ID);
238
	}
239
240
	public function LogLink() {
241
		return $this->Link() . '/log';
242
	}
243
244
	public function canView($member = null) {
245
		return $this->Environment()->canView($member);
246
	}
247
248
	/**
249
	 * Return a path to the log file.
250
	 * @return string
251
	 */
252
	protected function logfile() {
253
		return sprintf(
254
			'%s.%s.log',
255
			$this->Environment()->getFullName('.'),
256
			$this->ID
257
		);
258
	}
259
260
	/**
261
	 * @return DeploynautLogFile
262
	 */
263
	public function log() {
264
		return Injector::inst()->createWithArgs('DeploynautLogFile', array($this->logfile()));
265
	}
266
267
	public function LogContent() {
268
		return $this->log()->content();
269
	}
270
271
	/**
272
	 * This remains here for backwards compatibility - we don't want to expose Resque status in here.
273
	 * Resque job (DeployJob) will change statuses as part of its execution.
274
	 *
275
	 * @return string
276
	 */
277
	public function ResqueStatus() {
278
		return $this->State;
279
	}
280
281
282
	/**
283
	 * Fetch the git repository
284
	 *
285
	 * @return \Gitonomy\Git\Repository|null
286
	 */
287
	public function getRepository() {
288
		if(!$this->SHA) {
289
			return null;
290
		}
291
		return $this->Environment()->Project()->getRepository();
292
	}
293
294
295
	/**
296
	 * Gets the commit from source. The result is cached upstream in Repository.
297
	 *
298
	 * @return \Gitonomy\Git\Commit|null
299
	 */
300
	public function getCommit() {
301
		$repo = $this->getRepository();
302
		if($repo) {
303
			try {
304
				return $repo->getCommit($this->SHA);
305
			} catch(Gitonomy\Git\Exception\ReferenceNotFoundException $ex) {
306
				return null;
307
			}
308
		}
309
310
		return null;
311
	}
312
313
314
	/**
315
	 * Gets the commit message.
316
	 *
317
	 * @return string|null
318
	 */
319
	public function getCommitMessage() {
320
		$commit = $this->getCommit();
321
		if($commit) {
322
			try {
323
				return Convert::raw2xml($commit->getMessage());
324
			} catch(Gitonomy\Git\Exception\ReferenceNotFoundException $e) {
325
				return null;
326
			}
327
		}
328
		return null;
329
	}
330
331
	/**
332
	 * Return all tags for the deployed commit.
333
	 *
334
	 * @return ArrayList
335
	 */
336
	public function getTags() {
337
		$returnTags = array();
338
		$repo = $this->getRepository();
339
		if($repo) {
340
			$tags = $repo->getReferences()->resolveTags($this->SHA);
341
			if(!empty($tags)) {
342
				foreach($tags as $tag) {
343
					$field = Varchar::create('Tag', '255');
344
					$field->setValue($tag->getName());
345
					$returnTags[] = $field;
346
				}
347
			}
348
		}
349
		return new ArrayList($returnTags);
350
	}
351
352
	/**
353
	 * Collate the list of additional flags to affix to this deployment.
354
	 * Elements of the array will be rendered literally.
355
	 *
356
	 * @return ArrayList
357
	 */
358
	public function getFullDeployMessages() {
359
		$strategy = $this->getDeploymentStrategy();
360
		if ($strategy->getActionCode()!=='full') return null;
361
362
		$changes = $strategy->getChangesModificationNeeded();
363
		$messages = [];
364
		foreach ($changes as $change => $details) {
365
			if ($change==='Code version') continue;
366
367
			$messages[] = [
368
				'Flag' => sprintf(
369
					'<span class="label label-default full-deploy-info-item">%s</span>',
370
					$change[0]
371
				),
372
				'Text' => sprintf('%s changed', $change)
373
			];
374
		}
375
376
		if (empty($messages)) {
377
			$messages[] = [
378
				'Flag' => '',
379
				'Text' => '<i>Environment changes have been made.</i>'
380
			];
381
		}
382
383
		return new ArrayList($messages);
384
	}
385
386
	/**
387
	 * Fetches the latest tag for the deployed commit
388
	 *
389
	 * @return \Varchar|null
390
	 */
391
	public function getTag() {
392
		$tags = $this->getTags();
393
		if($tags->count() > 0) {
394
			return $tags->last();
395
		}
396
		return null;
397
	}
398
399
	/**
400
	 * @return DeploymentStrategy
401
	 */
402
	public function getDeploymentStrategy() {
403
		$environment = $this->Environment();
404
		$strategy = new DeploymentStrategy($environment);
405
		$strategy->fromJSON($this->Strategy);
406
		return $strategy;
407
	}
408
409
	/**
410
	 * Return a list of things that are going to be deployed, such
411
	 * as the code version, and any infrastrucutral changes.
412
	 *
413
	 * @return ArrayList
414
	 */
415
	public function getChanges() {
416
		$list = new ArrayList();
417
		$strategy = $this->getDeploymentStrategy();
418
		foreach($strategy->getChanges() as $name => $change) {
419
			$changed = (isset($change['from']) && isset($change['to'])) ? $change['from'] != $change['to'] : null;
420
			$description = isset($change['description']) ? $change['description'] : '';
421
			$compareUrl = null;
422
423
			// if there is a compare URL, and a description or a change (something actually changed)
424
			// then show the URL. Otherwise don't show anything, as there is no comparison to be made.
425
			if ($changed || $description) {
426
				$compareUrl = isset($change['compareUrl']) ? $change['compareUrl'] : '';
427
			}
428
429
			$list->push(new ArrayData([
430
				'Name' => $name,
431
				'From' => isset($change['from']) ? $change['from'] : null,
432
				'To' => isset($change['to']) ? $change['to'] : null,
433
				'Description' => $description,
434
				'Changed' => $changed,
435
				'CompareUrl' => $compareUrl
436
			]));
437
		}
438
439
		return $list;
440
	}
441
442
	/**
443
	 * Start a resque job for this deployment
444
	 *
445
	 * @return string Resque token
446
	 */
447
	public function enqueueDeployment() {
448
		$environment = $this->Environment();
449
		$project = $environment->Project();
450
		$log = $this->log();
451
452
		$args = array(
453
			'environmentName' => $environment->Name,
454
			'repository' => $project->getLocalCVSPath(),
455
			'logfile' => $this->logfile(),
456
			'projectName' => $project->Name,
457
			'env' => $project->getProcessEnv(),
458
			'deploymentID' => $this->ID,
459
			'sigFile' => DeployJob::sig_file_for_data_object($this)
460
		);
461
462
		$strategy = $this->getDeploymentStrategy();
463
		// Inject options.
464
		$args = array_merge($args, $strategy->getOptions());
465
		// Make sure we use the SHA as it was written into this DNDeployment.
466
		$args['sha'] = $this->SHA;
467
468
		if(!$this->DeployerID) {
469
			$this->DeployerID = Member::currentUserID();
470
		}
471
472
		if($this->DeployerID) {
473
			$deployer = $this->Deployer();
474
			$message = sprintf(
475
				'Deploy to %s initiated by %s (%s), with IP address %s',
476
				$environment->getFullName(),
477
				$deployer->getName(),
478
				$deployer->Email,
479
				Controller::curr()->getRequest()->getIP()
480
			);
481
			$log->write($message);
482
		}
483
484
		return Resque::enqueue('deploy', 'DeployJob', $args, true);
485
	}
486
}
487