Completed
Pull Request — master (#658)
by Stig
05:30
created

DNDeployment::getCommitMessage()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 11
Code Lines 8

Duplication

Lines 11
Ratio 100 %

Importance

Changes 0
Metric Value
dl 11
loc 11
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 8
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 $State
9
 * @property int $RefType
10
 * @property SS_Datetime $DeployStarted
11
 * @property SS_Datetime $DeployRequested
12
 *
13
 * @method DNEnvironment Environment()
14
 * @property int EnvironmentID
15
 * @method Member Deployer()
16
 * @property int DeployerID
17
 */
18
class DNDeployment extends DataObject implements Finite\StatefulInterface, HasStateMachine {
0 ignored issues
show
Coding Style introduced by
The property $has_one is not named in camelCase.

This check marks property names that have not been written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection string becomes databaseConnectionString.

Loading history...
Coding Style introduced by
The property $default_sort is not named in camelCase.

This check marks property names that have not been written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection string becomes databaseConnectionString.

Loading history...
Coding Style introduced by
The property $summary_fields is not named in camelCase.

This check marks property names that have not been written in camelCase.

In camelCase names are written without any punctuation, the start of each new word being marked by a capital letter. Thus the name database connection string becomes databaseConnectionString.

Loading history...
19
20
	const STATE_NEW = 'New';
21
	const STATE_SUBMITTED = 'Submitted';
22
	const STATE_INVALID = 'Invalid';
23
	const STATE_QUEUED = 'Queued';
24
	const STATE_DEPLOYING = 'Deploying';
25
	const STATE_ABORTING = 'Aborting';
26
	const STATE_COMPLETED = 'Completed';
27
	const STATE_FAILED = 'Failed';
28
29
	const TR_SUBMIT = 'submit';
30
	const TR_INVALIDATE = 'invalidate';
31
	const TR_QUEUE = 'queue';
32
	const TR_DEPLOY = 'deploy';
33
	const TR_ABORT = 'abort';
34
	const TR_COMPLETE = 'complete';
35
	const TR_FAIL = 'fail';
36
37
	/**
38
	 * @var array
39
	 */
40
	private static $db = array(
41
		"SHA" => "GitSHA",
42
		"ResqueToken" => "Varchar(255)",
43
		// The branch that was used to deploy this. Can't really be inferred from Git history because
44
		// the commit could appear in lots of branches that are irrelevant to the user when it comes
45
		// to deployment history, and the branch may have been deleted.
46
		"Branch" => "Varchar(255)",
47
		// is it a branch, tag etc, see GitDispatcher REF_TYPE_* constants
48
		"RefType" => "Int",
49
		"State" => "Enum('New, Submitted, Invalid, Queued, Deploying, Aborting, Completed, Failed', 'New')",
50
		// JSON serialised DeploymentStrategy.
51
		"Strategy" => "Text",
52
		"Summary" => "Text",
53
		// the date and time the deploy was queued
54
		"DeployStarted" => "SS_Datetime",
55
		"DeployRequested" => "SS_Datetime"
56
	);
57
58
	/**
59
	 * @var array
60
	 */
61
	private static $has_one = array(
62
		"Environment" => "DNEnvironment",
63
		"Deployer" => "Member",
64
		"Approver" => "Member",
65
		"BackupDataTransfer" => "DNDataTransfer" // denotes an automated backup done for this deployment
66
	);
67
68
	private static $default_sort = '"LastEdited" DESC';
69
70
	private static $dependencies = [
71
		'stateMachineFactory' => '%$StateMachineFactory'
72
	];
73
74
	public function getTitle() {
75
		return "#{$this->ID}: {$this->SHA} (Status: {$this->Status})";
0 ignored issues
show
Documentation introduced by
The property Status 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...
76
	}
77
78
	private static $summary_fields = array(
79
		'LastEdited' => 'Last Edited',
80
		'SHA' => 'SHA',
81
		'State' => 'State',
82
		'Deployer.Name' => 'Deployer'
83
	);
84
85
	public function setResqueToken($token) {
86
		$this->ResqueToken = $token;
87
	}
88
89
	public function getFiniteState() {
90
		return $this->State;
91
	}
92
93
	public function setFiniteState($state) {
94
		$this->State = $state;
95
		$this->write();
96
	}
97
98
	public function getStatus() {
99
		return $this->State;
100
	}
101
102
	public function getMachine() {
103
		return $this->stateMachineFactory->forDNDeployment($this);
0 ignored issues
show
Documentation introduced by
The property stateMachineFactory 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...
104
	}
105
106
	public function Link() {
107
		return Controller::join_links($this->Environment()->Link(), 'deploy', $this->ID);
108
	}
109
110
	public function LogLink() {
111
		return $this->Link() . '/log';
112
	}
113
114
	public function canView($member = null) {
115
		return $this->Environment()->canView($member);
116
	}
117
118
	/**
119
	 * Return a path to the log file.
120
	 * @return string
121
	 */
122
	protected function logfile() {
123
		return sprintf(
124
			'%s.%s.log',
125
			$this->Environment()->getFullName('.'),
126
			$this->ID
127
		);
128
	}
129
130
	/**
131
	 * @return DeploynautLogFile
132
	 */
133
	public function log() {
134
		return Injector::inst()->createWithArgs('DeploynautLogFile', array($this->logfile()));
135
	}
136
137
	public function LogContent() {
138
		return $this->log()->content();
139
	}
140
141
	/**
142
	 * This remains here for backwards compatibility - we don't want to expose Resque status in here.
143
	 * Resque job (DeployJob) will change statuses as part of its execution.
144
	 *
145
	 * @return string
146
	 */
147
	public function ResqueStatus() {
148
		return $this->State;
149
	}
150
151
	/**
152
	 * Fetch the git repository
153
	 *
154
	 * @return \Gitonomy\Git\Repository|null
155
	 */
156
	public function getRepository() {
157
		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...
158
			return null;
159
		}
160
		return $this->Environment()->Project()->getRepository();
161
	}
162
163
	/**
164
	 * Gets the commit from source. The result is cached upstream in Repository.
165
	 *
166
	 * @return \Gitonomy\Git\Commit|null
167
	 */
168 View Code Duplication
	public function getCommit() {
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...
169
		$repo = $this->getRepository();
170
		if($repo) {
171
			try {
172
				return $this->Environment()->getCommit($this->SHA);
173
			} catch(Gitonomy\Git\Exception\ReferenceNotFoundException $ex) {
174
				return null;
175
			}
176
		}
177
178
		return null;
179
	}
180
181
	/**
182
	 * Get the commit URL to the commit associated with this deployment.
183
	 * @return null|string
184
	 */
185
	public function getCommitURL() {
186
		$environment = $this->Environment();
187
		if (!$environment) {
188
			return null;
189
		}
190
		$project = $environment->Project();
191
		if (!$project) {
192
			return null;
193
		}
194
		$interface = $project->getRepositoryInterface();
195
		if (!$interface) {
196
			return null;
197
		}
198
		return $interface->CommitURL . '/' . $this->SHA;
199
	}
200
201
	/**
202
	 * Gets the commit message.
203
	 *
204
	 * @return string|null
205
	 */
206 View Code Duplication
	public function getCommitMessage() {
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...
207
		$commit = $this->getCommit();
208
		if($commit) {
209
			try {
210
				return Convert::raw2xml($this->Environment()->getCommitMessage($commit));
211
			} catch(Gitonomy\Git\Exception\ReferenceNotFoundException $e) {
212
				return null;
213
			}
214
		}
215
		return null;
216
	}
217
218
	/**
219
	 * Return all tags for the deployed commit.
220
	 *
221
	 * @return ArrayList
222
	 */
223
	public function getTags() {
224
		$commit = $this->Environment()->getCommit($this->SHA);
225
		$this->Environment()->getCommitTags($commit);
226
		$returnTags = [];
227
		if (!empty($tags)) {
0 ignored issues
show
Bug introduced by
The variable $tags seems to never exist, and therefore empty should always return true. Did you maybe rename this variable?

This check looks for calls to isset(...) or empty() on variables that are yet undefined. These calls will always produce the same result and can be removed.

This is most likely caused by the renaming of a variable or the removal of a function/method parameter.

Loading history...
228
			foreach($tags as $tag) {
229
				$field = Varchar::create('Tag', '255');
230
				$field->setValue($tag->getName());
231
				$returnTags[] = $field;
232
			}
233
		}
234
		return new ArrayList($returnTags);
235
	}
236
237
	/**
238
	 * Collate the list of additional flags to affix to this deployment.
239
	 * Elements of the array will be rendered literally.
240
	 *
241
	 * @return ArrayList
242
	 */
243
	public function getFullDeployMessages() {
244
		$strategy = $this->getDeploymentStrategy();
245
		if ($strategy->getActionCode()!=='full') return null;
246
247
		$changes = $strategy->getChangesModificationNeeded();
248
		$messages = [];
249
		foreach ($changes as $change => $details) {
250
			if ($change==='Code version') continue;
251
252
			$messages[] = [
253
				'Flag' => sprintf(
254
					'<span class="label label-default full-deploy-info-item">%s</span>',
255
					$change[0]
256
				),
257
				'Text' => sprintf('%s changed', $change)
258
			];
259
		}
260
261
		if (empty($messages)) {
262
			$messages[] = [
263
				'Flag' => '',
264
				'Text' => '<i>Environment changes have been made.</i>'
265
			];
266
		}
267
268
		return new ArrayList($messages);
269
	}
270
271
	/**
272
	 * Fetches the latest tag for the deployed commit
273
	 *
274
	 * @return \Varchar|null
275
	 */
276
	public function getTag() {
277
		$tags = $this->getTags();
278
		if($tags->count() > 0) {
279
			return $tags->last();
280
		}
281
		return null;
282
	}
283
284
	/**
285
	 * @return DeploymentStrategy
286
	 */
287
	public function getDeploymentStrategy() {
288
		$environment = $this->Environment();
289
		$strategy = new DeploymentStrategy($environment);
290
		$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...
291
		return $strategy;
292
	}
293
294
	/**
295
	 * Return a list of things that are going to be deployed, such
296
	 * as the code version, and any infrastructural changes.
297
	 *
298
	 * @return ArrayList
299
	 */
300
	public function getChanges() {
301
		$list = new ArrayList();
302
		$strategy = $this->getDeploymentStrategy();
303
		foreach($strategy->getChanges() as $name => $change) {
304
			$changed = (isset($change['from']) && isset($change['to'])) ? $change['from'] != $change['to'] : null;
305
			$description = isset($change['description']) ? $change['description'] : '';
306
			$compareUrl = null;
307
308
			// if there is a compare URL, and a description or a change (something actually changed)
309
			// then show the URL. Otherwise don't show anything, as there is no comparison to be made.
310
			if ($changed || $description) {
311
				$compareUrl = isset($change['compareUrl']) ? $change['compareUrl'] : '';
312
			}
313
314
			$list->push(new ArrayData([
315
				'Name' => $name,
316
				'From' => isset($change['from']) ? $change['from'] : null,
317
				'To' => isset($change['to']) ? $change['to'] : null,
318
				'Description' => $description,
319
				'Changed' => $changed,
320
				'CompareUrl' => $compareUrl
321
			]));
322
		}
323
324
		return $list;
325
	}
326
327
	/**
328
	 * Start a resque job for this deployment
329
	 *
330
	 * @return string Resque token
331
	 */
332
	public function enqueueDeployment() {
333
		$environment = $this->Environment();
334
		$project = $environment->Project();
335
		$log = $this->log();
336
337
		$args = array(
338
			'environmentName' => $environment->Name,
339
			'repository' => $project->getLocalCVSPath(),
340
			'logfile' => $this->logfile(),
341
			'projectName' => $project->Name,
342
			'env' => $project->getProcessEnv(),
343
			'deploymentID' => $this->ID,
344
			'sigFile' => $this->getSigFile(),
345
		);
346
347
		$strategy = $this->getDeploymentStrategy();
348
		// Inject options.
349
		$args = array_merge($args, $strategy->getOptions());
350
		// Make sure we use the SHA as it was written into this DNDeployment.
351
		$args['sha'] = $this->SHA;
352
353
		if(!$this->DeployerID) {
354
			$this->DeployerID = Member::currentUserID();
355
		}
356
357 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...
358
			$deployer = $this->Deployer();
359
			$message = sprintf(
360
				'Deploy to %s initiated by %s (%s), with IP address %s',
361
				$environment->getFullName(),
362
				$deployer->getName(),
363
				$deployer->Email,
364
				Controller::curr()->getRequest()->getIP()
365
			);
366
			$log->write($message);
367
		}
368
369
		return Resque::enqueue('deploy', 'DeployJob', $args, true);
370
	}
371
372
	public function getSigFile() {
373
		$dir = DNData::inst()->getSignalDir();
374
		if (!is_dir($dir)) {
375
			`mkdir $dir`;
376
		}
377
		return sprintf(
378
			'%s/deploynaut-signal-%s-%s',
379
			DNData::inst()->getSignalDir(),
380
			$this->ClassName,
381
			$this->ID
382
		);
383
	}
384
385
	/**
386
	 * Signal the worker to self-abort. If we had a reliable way of figuring out the right PID,
387
	 * we could posix_kill directly, but Resque seems to not provide a way to find out the PID
388
	 * from the job nor worker.
389
	 */
390
	public function setSignal($signal) {
391
		$sigFile = $this->getSigFile();
392
		// 2 is SIGINT - we can't use SIGINT constant in the Apache context, only available in workers.
393
		file_put_contents($sigFile, $signal);
394
	}
395
}
396