Completed
Pull Request — master (#575)
by Mateusz
12:05
created

DNDeployment::isAborting()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 3
rs 10
cc 1
eloc 2
nc 1
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 $Status
9
 *
10
 * @method DNEnvironment Environment()
11
 * @property int EnvironmentID
12
 * @method Member Deployer()
13
 * @property int DeployerID
14
 */
15
class DNDeployment extends DataObject {
16
17
	/**
18
	 * @var array
19
	 */
20
	private static $db = array(
21
		"SHA" => "GitSHA",
22
		"ResqueToken" => "Varchar(255)",
23
		// The branch that was used to deploy this. Can't really be inferred from Git history because
24
		// the commit could appear in lots of branches that are irrelevant to the user when it comes
25
		// to deployment history, and the branch may have been deleted.
26
		"Branch" => "Varchar(255)",
27
		// Observe that this is not the same as Resque status, since ResqueStatus is not persistent
28
		// It's used for finding successful deployments and displaying that in history views in the frontend
29
		"Status" => "Enum('Queued, Started, Aborting, Finished, Failed, n/a', 'n/a')",
30
		// JSON serialised DeploymentStrategy.
31
		"Strategy" => "Text"
32
	);
33
34
	/**
35
	 * @var array
36
	 */
37
	private static $has_one = array(
38
		"Environment" => "DNEnvironment",
39
		"Deployer" => "Member",
40
	);
41
42
	private static $default_sort = '"LastEdited" DESC';
43
44
	public function getTitle() {
45
		return "#{$this->ID}: {$this->SHA} (Status: {$this->Status})";
46
	}
47
48
	private static $summary_fields = array(
49
		'LastEdited' => 'Last Edited',
50
		'SHA' => 'SHA',
51
		'Status' => 'Status',
52
		'Deployer.Name' => 'Deployer'
53
	);
54
55
	/**
56
	 * @param int $int
57
	 * @return string
58
	 */
59 View Code Duplication
	public static function map_resque_status($int) {
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...
60
		$remap = array(
61
			Resque_Job_Status::STATUS_WAITING => "Queued",
62
			Resque_Job_Status::STATUS_RUNNING => "Running",
63
			Resque_Job_Status::STATUS_FAILED => "Failed",
64
			Resque_Job_Status::STATUS_COMPLETE => "Complete",
65
			false => "Invalid",
66
		);
67
		return $remap[$int];
68
	}
69
70
71
	public function Link() {
72
		return Controller::join_links($this->Environment()->Link(), 'deploy', $this->ID);
73
	}
74
75
	public function LogLink() {
76
		return $this->Link() . '/log';
77
	}
78
79
	public function canView($member = null) {
80
		return $this->Environment()->canView($member);
81
	}
82
83
	/**
84
	 * Return a path to the log file.
85
	 * @return string
86
	 */
87
	protected function logfile() {
88
		return sprintf(
89
			'%s.%s.log',
90
			$this->Environment()->getFullName('.'),
91
			$this->ID
92
		);
93
	}
94
95
	/**
96
	 * @return DeploynautLogFile
97
	 */
98
	public function log() {
99
		return Injector::inst()->createWithArgs('DeploynautLogFile', array($this->logfile()));
100
	}
101
102
	public function LogContent() {
103
		return $this->log()->content();
104
	}
105
106
	/**
107
	 * Returns the status of the resque job
108
	 *
109
	 * @return string
110
	 */
111 View Code Duplication
	public function ResqueStatus() {
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...
112
		$status = new Resque_Job_Status($this->ResqueToken);
0 ignored issues
show
Security Bug introduced by
It seems like $this->ResqueToken can also be of type false; however, Resque_Job_Status::__construct() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
113
		$statusCode = $status->get();
114
		// The Resque job can no longer be found, fallback to the DNDeployment.Status
115
		if($statusCode === false) {
116
			// Translate from the DNDeployment.Status to the Resque job status for UI purposes
117
			switch($this->Status) {
118
				case 'Finished':
119
					return 'Complete';
120
				case 'Started':
121
					return 'Running';
122
				case 'Aborting':
123
					return 'Running';
124
				default:
125
					return $this->Status;
126
			}
127
		}
128
		return self::map_resque_status($statusCode);
129
	}
130
131
132
	/**
133
	 * Fetch the git repository
134
	 *
135
	 * @return \Gitonomy\Git\Repository|null
136
	 */
137
	public function getRepository() {
138
		if(!$this->SHA) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->SHA 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...
139
			return null;
140
		}
141
		return $this->Environment()->Project()->getRepository();
142
	}
143
144
145
	/**
146
	 * Gets the commit from source. The result is cached upstream in Repository.
147
	 *
148
	 * @return \Gitonomy\Git\Commit|null
149
	 */
150
	public function getCommit() {
151
		$repo = $this->getRepository();
152
		if($repo) {
153
			try {
154
				return $repo->getCommit($this->SHA);
155
			} catch(Gitonomy\Git\Exception\ReferenceNotFoundException $ex) {
156
				return null;
157
			}
158
		}
159
160
		return null;
161
	}
162
163
164
	/**
165
	 * Gets the commit message.
166
	 *
167
	 * @return string|null
168
	 */
169
	public function getCommitMessage() {
170
		$commit = $this->getCommit();
171
		if($commit) {
172
			try {
173
				return Convert::raw2xml($commit->getMessage());
174
			} catch(Gitonomy\Git\Exception\ReferenceNotFoundException $e) {
175
				return null;
176
			}
177
		}
178
		return null;
179
	}
180
181
	/**
182
	 * Return all tags for the deployed commit.
183
	 *
184
	 * @return ArrayList
185
	 */
186
	public function getTags() {
187
		$returnTags = array();
188
		$repo = $this->getRepository();
189
		if($repo) {
190
			$tags = $repo->getReferences()->resolveTags($this->SHA);
191
			if(!empty($tags)) {
192
				foreach($tags as $tag) {
193
					$field = Varchar::create('Tag', '255');
194
					$field->setValue($tag->getName());
195
					$returnTags[] = $field;
196
				}
197
			}
198
		}
199
		return new ArrayList($returnTags);
200
	}
201
202
	/**
203
	 * Collate the list of additional flags to affix to this deployment.
204
	 * Elements of the array will be rendered literally.
205
	 *
206
	 * @return ArrayList
207
	 */
208
	public function getFullDeployMessages() {
209
		$strategy = $this->getDeploymentStrategy();
210
		if ($strategy->getActionCode()!=='full') return null;
211
212
		$changes = $strategy->getChangesModificationNeeded();
213
		$messages = [];
214
		foreach ($changes as $change => $details) {
215
			if ($change==='Code version') continue;
216
217
			$messages[] = [
218
				'Flag' => sprintf(
219
					'<span class="label label-default full-deploy-info-item">%s</span>',
220
					$change[0]
221
				),
222
				'Text' => sprintf('%s changed', $change)
223
			];
224
		}
225
226
		if (empty($messages)) {
227
			$messages[] = [
228
				'Flag' => '',
229
				'Text' => '<i>Environment changes have been made.</i>'
230
			];
231
		}
232
233
		return new ArrayList($messages);
234
	}
235
236
	/**
237
	 * Fetches the latest tag for the deployed commit
238
	 *
239
	 * @return \Varchar|null
240
	 */
241
	public function getTag() {
242
		$tags = $this->getTags();
243
		if($tags->count() > 0) {
244
			return $tags->last();
245
		}
246
		return null;
247
	}
248
249
	/**
250
	 * @return DeploymentStrategy
251
	 */
252
	public function getDeploymentStrategy() {
253
		$environment = $this->Environment();
254
		$strategy = new DeploymentStrategy($environment);
255
		$strategy->fromJSON($this->Strategy);
0 ignored issues
show
Documentation introduced by
The property Strategy does not exist on object<DNDeployment>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
256
		return $strategy;
257
	}
258
259
	/**
260
	 * Return a list of things that are going to be deployed, such
261
	 * as the code version, and any infrastrucutral changes.
262
	 *
263
	 * @return ArrayList
264
	 */
265
	public function getChanges() {
266
		$list = new ArrayList();
267
		$strategy = $this->getDeploymentStrategy();
268
		foreach($strategy->getChanges() as $name => $change) {
269
			$changed = (isset($change['from']) && isset($change['to'])) ? $change['from'] != $change['to'] : null;
270
			$description = isset($change['description']) ? $change['description'] : '';
271
			$compareUrl = null;
272
273
			// if there is a compare URL, and a description or a change (something actually changed)
274
			// then show the URL. Otherwise don't show anything, as there is no comparison to be made.
275
			if ($changed || $description) {
276
				$compareUrl = isset($change['compareUrl']) ? $change['compareUrl'] : '';
277
			}
278
279
			$list->push(new ArrayData([
280
				'Name' => $name,
281
				'From' => isset($change['from']) ? $change['from'] : null,
282
				'To' => isset($change['to']) ? $change['to'] : null,
283
				'Description' => $description,
284
				'Changed' => $changed,
285
				'CompareUrl' => $compareUrl
286
			]));
287
		}
288
289
		return $list;
290
	}
291
292
	/**
293
	 * Start a resque job for this deployment
294
	 *
295
	 * @return string Resque token
296
	 */
297
	protected function enqueueDeployment() {
298
		$environment = $this->Environment();
299
		$project = $environment->Project();
300
		$log = $this->log();
301
302
		$args = array(
303
			'environmentName' => $environment->Name,
304
			'repository' => $project->getLocalCVSPath(),
305
			'logfile' => $this->logfile(),
306
			'projectName' => $project->Name,
307
			'env' => $project->getProcessEnv(),
308
			'deploymentID' => $this->ID,
309
			'sigFile' => DeployJob::sig_file_for_data_object($this)
310
		);
311
312
		$strategy = $this->getDeploymentStrategy();
313
		// Inject options.
314
		$args = array_merge($args, $strategy->getOptions());
315
		// Make sure we use the SHA as it was written into this DNDeployment.
316
		$args['sha'] = $this->SHA;
317
318
		if(!$this->DeployerID) {
319
			$this->DeployerID = Member::currentUserID();
320
		}
321
322 View Code Duplication
		if($this->DeployerID) {
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...
323
			$deployer = $this->Deployer();
324
			$message = sprintf(
325
				'Deploy to %s initiated by %s (%s), with IP address %s',
326
				$environment->getFullName(),
327
				$deployer->getName(),
328
				$deployer->Email,
329
				Controller::curr()->getRequest()->getIP()
330
			);
331
			$log->write($message);
332
		}
333
334
		return Resque::enqueue('deploy', 'DeployJob', $args, true);
335
	}
336
337
	public function start() {
338
		$log = $this->log();
339
		$token = $this->enqueueDeployment();
340
		$this->ResqueToken = $token;
341
		$this->Status = 'Queued';
342
		$this->write();
343
344
		$message = sprintf('Deploy queued as job %s (sigFile is %s)', $token, DeployJob::sig_file_for_data_object($this));
345
		$log->write($message);
346
	}
347
348
	public function abort() {
349
		$this->Status = 'Aborting';
350
		$this->write();
351
		// 2 is SIGINT - we can't use SIGINT constant in the mod_apache context.
352
		DeployJob::set_signal($this, 2);
353
	}
354
355
	public function isAborting() {
356
		return $this->Status=='Aborting';
357
	}
358
}
359