Completed
Pull Request — master (#488)
by Helpful
1295:51 queued 1292:33
created

DNDeployment::getChanges()   B

Complexity

Conditions 5
Paths 3

Size

Total Lines 18
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 18
rs 8.8571
cc 5
eloc 13
nc 3
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
		// Observe that this is not the same as Resque status, since ResqueStatus is not persistent
24
		// It's used for finding successful deployments and displaying that in history views in the frontend
25
		"Status" => "Enum('Queued, Started, Finished, Failed, n/a', 'n/a')",
26
		// JSON serialised DeploymentStrategy.
27
		"Strategy" => "Text"
28
	);
29
30
	/**
31
	 * @var array
32
	 */
33
	private static $has_one = array(
34
		"Environment" => "DNEnvironment",
35
		"Deployer" => "Member",
36
	);
37
38
	private static $default_sort = '"LastEdited" DESC';
39
40
	public function getTitle() {
41
		return "#{$this->ID}: {$this->SHA} (Status: {$this->Status})";
42
	}
43
44
	private static $summary_fields = array(
45
		'LastEdited' => 'Last Edited',
46
		'SHA' => 'SHA',
47
		'Status' => 'Status',
48
		'Deployer.Name' => 'Deployer'
49
	);
50
51
	/**
52
	 * @param int $int
53
	 * @return string
54
	 */
55 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...
56
		$remap = array(
57
			Resque_Job_Status::STATUS_WAITING => "Queued",
58
			Resque_Job_Status::STATUS_RUNNING => "Running",
59
			Resque_Job_Status::STATUS_FAILED => "Failed",
60
			Resque_Job_Status::STATUS_COMPLETE => "Complete",
61
			false => "Invalid",
62
		);
63
		return $remap[$int];
64
	}
65
66
67
	public function Link() {
68
		return Controller::join_links($this->Environment()->Link(), 'deploy', $this->ID);
69
	}
70
71
	public function LogLink() {
72
		return $this->Link() . '/log';
73
	}
74
75
	public function canView($member = null) {
76
		return $this->Environment()->canView($member);
77
	}
78
79
	/**
80
	 * Return a path to the log file.
81
	 * @return string
82
	 */
83
	protected function logfile() {
84
		return sprintf(
85
			'%s.%s.log',
86
			$this->Environment()->getFullName('.'),
87
			$this->ID
88
		);
89
	}
90
91
	/**
92
	 * @return DeploynautLogFile
93
	 */
94
	public function log() {
95
		return Injector::inst()->createWithArgs('DeploynautLogFile', array($this->logfile()));
96
	}
97
98
	public function LogContent() {
99
		return $this->log()->content();
100
	}
101
102
	/**
103
	 * Returns the status of the resque job
104
	 *
105
	 * @return string
106
	 */
107 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...
108
		$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...
109
		$statusCode = $status->get();
110
		// The Resque job can no longer be found, fallback to the DNDeployment.Status
111
		if($statusCode === false) {
112
			// Translate from the DNDeployment.Status to the Resque job status for UI purposes
113
			switch($this->Status) {
114
				case 'Finished':
115
					return 'Complete';
116
				case 'Started':
117
					return 'Running';
118
				default:
119
					return $this->Status;
120
			}
121
		}
122
		return self::map_resque_status($statusCode);
123
	}
124
125
126
	/**
127
	 * Fetch the git repository
128
	 *
129
	 * @return \Gitonomy\Git\Repository|null
130
	 */
131
	public function getRepository() {
132
		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...
133
			return null;
134
		}
135
		return $this->Environment()->Project()->getRepository();
136
	}
137
138
139
	/**
140
	 * Gets the commit from source. The result is cached upstream in Repository.
141
	 *
142
	 * @return \Gitonomy\Git\Commit|null
143
	 */
144
	public function getCommit() {
145
		$repo = $this->getRepository();
146
		if($repo) {
147
			try {
148
				return $repo->getCommit($this->SHA);
149
			} catch(Gitonomy\Git\Exception\ReferenceNotFoundException $ex) {
150
				return null;
151
			}
152
		}
153
154
		return null;
155
	}
156
157
158
	/**
159
	 * Gets the commit message.
160
	 *
161
	 * @return string|null
162
	 */
163
	public function getCommitMessage() {
164
		$commit = $this->getCommit();
165
		if($commit) {
166
			try {
167
				return Convert::raw2xml($commit->getMessage());
168
			} catch(Gitonomy\Git\Exception\ReferenceNotFoundException $e) {
169
				return null;
170
			}
171
		}
172
		return null;
173
	}
174
175
	/**
176
	 * Return all tags for the deployed commit.
177
	 *
178
	 * @return ArrayList
179
	 */
180
	public function getTags() {
181
		$returnTags = array();
182
		$repo = $this->getRepository();
183
		if($repo) {
184
			$tags = $repo->getReferences()->resolveTags($this->SHA);
185
			if(!empty($tags)) {
186
				foreach($tags as $tag) {
187
					$field = Varchar::create('Tag', '255');
188
					$field->setValue($tag->getName());
189
					$returnTags[] = $field;
190
				}
191
			}
192
		}
193
		return new ArrayList($returnTags);
194
	}
195
196
	/**
197
	 * Collate the list of additional flags to affix to this deployment.
198
	 * Elements of the array will be rendered literally.
199
	 *
200
	 * @return ArrayList
201
	 */
202
	public function getFullDeployMessages() {
203
		$strategy = $this->getDeploymentStrategy();
204
		if ($strategy->getActionCode()!=='full') return null;
205
206
		$changes = $strategy->getChangesModificationNeeded();
207
		$messages = [];
208
		foreach ($changes as $change => $details) {
209
			if ($change==='Code version') continue;
210
211
			$messages[] = [
212
				'Flag' => sprintf(
213
					'<span class="label label-default full-deploy-info-item">%s</span>',
214
					$change[0]
215
				),
216
				'Text' => sprintf('%s changed', $change)
217
			];
218
		}
219
220
		if (empty($messages)) {
221
			$messages[] = [
222
				'Flag' => '',
223
				'Text' => '<i>Environment changes have been made.</i>'
224
			];
225
		}
226
227
		return new ArrayList($messages);
228
	}
229
230
	/**
231
	 * Fetches the latest tag for the deployed commit
232
	 *
233
	 * @return \Varchar|null
234
	 */
235
	public function getTag() {
236
		$tags = $this->getTags();
237
		if($tags->count() > 0) {
238
			return $tags->last();
239
		}
240
		return null;
241
	}
242
243
	/**
244
	 * @return DeploymentStrategy
245
	 */
246
	public function getDeploymentStrategy() {
247
		$environment = $this->Environment();
248
		$strategy = new DeploymentStrategy($environment);
249
		$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...
250
		return $strategy;
251
	}
252
253
	/**
254
	 * Return a list of things that are going to be deployed, such
255
	 * as the code version, and any infrastrucutral changes.
256
	 *
257
	 * @return ArrayList
258
	 */
259
	public function getChanges() {
260
		$list = new ArrayList();
261
		$strategy = $this->getDeploymentStrategy();
262
		foreach($strategy->getChanges() as $name => $change) {
263
			if(empty($change['to'])) continue;
264
265
			$list->push(new ArrayData([
266
				'Name' => $name,
267
				'From' => $change['from'],
268
				'To' => $change['to'],
269
				'Description' => isset($change['description']) ? $change['description'] : '',
270
				'Changed' => $change['from'] != $change['to'],
271
				'CompareUrl' => isset($change['compareUrl']) ? $change['compareUrl'] : ''
272
			]));
273
		}
274
275
		return $list;
276
	}
277
278
	/**
279
	 * Start a resque job for this deployment
280
	 *
281
	 * @return string Resque token
282
	 */
283
	protected function enqueueDeployment() {
284
		$environment = $this->Environment();
285
		$project = $environment->Project();
286
		$log = $this->log();
287
288
		$args = array(
289
			'environmentName' => $environment->Name,
290
			'repository' => $project->getLocalCVSPath(),
291
			'logfile' => $this->logfile(),
292
			'projectName' => $project->Name,
293
			'env' => $project->getProcessEnv(),
294
			'deploymentID' => $this->ID
295
		);
296
297
		$strategy = $this->getDeploymentStrategy();
298
		// Inject options.
299
		$args = array_merge($args, $strategy->getOptions());
300
		// Make sure we use the SHA as it was written into this DNDeployment.
301
		$args['sha'] = $this->SHA;
302
303
		if(!$this->DeployerID) {
304
			$this->DeployerID = Member::currentUserID();
305
		}
306
307 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...
308
			$deployer = $this->Deployer();
309
			$message = sprintf(
310
				'Deploy to %s initiated by %s (%s), with IP address %s',
311
				$environment->getFullName(),
312
				$deployer->getName(),
313
				$deployer->Email,
314
				Controller::curr()->getRequest()->getIP()
315
			);
316
			$log->write($message);
317
		}
318
319
		return Resque::enqueue('deploy', 'DeployJob', $args, true);
320
	}
321
322 View Code Duplication
	public function start() {
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...
323
		$log = $this->log();
324
		$token = $this->enqueueDeployment();
325
		$this->ResqueToken = $token;
326
		$this->Status = 'Queued';
327
		$this->write();
328
329
		$message = sprintf('Deploy queued as job %s', $token);
330
		$log->write($message);
331
	}
332
}
333