Completed
Pull Request — master (#838)
by Stig
03:36
created

DNDeployment::logfile()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 7
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 7
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 5
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 $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_APPROVED = 'Approved';
24
	const STATE_REJECTED = 'Rejected';
25
	const STATE_QUEUED = 'Queued';
26
	const STATE_DEPLOYING = 'Deploying';
27
	const STATE_ABORTING = 'Aborting';
28
	const STATE_COMPLETED = 'Completed';
29
	const STATE_FAILED = 'Failed';
30
31
	const TR_NEW = 'new';
32
	const TR_SUBMIT = 'submit';
33
	const TR_INVALIDATE = 'invalidate';
34
	const TR_APPROVE = 'approve';
35
	const TR_REJECT = 'reject';
36
	const TR_QUEUE = 'queue';
37
	const TR_DEPLOY = 'deploy';
38
	const TR_ABORT = 'abort';
39
	const TR_COMPLETE = 'complete';
40
	const TR_FAIL = 'fail';
41
42
	/**
43
	 * @var array
44
	 */
45
	private static $db = array(
46
		"SHA" => "GitSHA",
47
		"ResqueToken" => "Varchar(255)",
48
		// The branch that was used to deploy this. Can't really be inferred from Git history because
49
		// the commit could appear in lots of branches that are irrelevant to the user when it comes
50
		// to deployment history, and the branch may have been deleted.
51
		"Branch" => "Varchar(255)",
52
		// is it a branch, tag etc, see GitDispatcher REF_TYPE_* constants
53
		"RefType" => "Int",
54
		"State" => "Enum('New, Submitted, Invalid, Approved, Rejected, Queued, Deploying, Aborting, Completed, Failed', 'New')",
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 122 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
55
		// JSON serialised DeploymentStrategy.
56
		"Strategy" => "Text",
57
		"Title" => "Varchar(255)",
58
		"Summary" => "Text",
59
		// the date and time the deploy was queued
60
		"DeployStarted" => "SS_Datetime",
61
		// the date and time a deployment was requested to be approved
62
		"DeployRequested" => "SS_Datetime",
63
		"RejectedReason" => "Text"
64
	);
65
66
	/**
67
	 * @var array
68
	 */
69
	private static $has_one = array(
70
		"Environment" => "DNEnvironment",
71
		"Deployer" => "Member",
72
		"Approver" => "Member",
73
		"BackupDataTransfer" => "DNDataTransfer" // denotes an automated backup done for this deployment
74
	);
75
76
	private static $default_sort = '"LastEdited" DESC';
77
78
	private static $dependencies = [
79
		'stateMachineFactory' => '%$StateMachineFactory'
80
	];
81
82
	private static $summary_fields = array(
83
		'LastEdited' => 'Last Edited',
84
		'SHA' => 'SHA',
85
		'State' => 'State',
86
		'Deployer.Name' => 'Deployer'
87
	);
88
89
	public function setResqueToken($token) {
90
		$this->ResqueToken = $token;
91
	}
92
93
	public function getFiniteState() {
94
		return $this->State;
95
	}
96
97
	public function setFiniteState($state) {
98
		$this->State = $state;
99
		$this->write();
100
	}
101
102
	public function getStatus() {
103
		return $this->State;
104
	}
105
106
	public function getMachine() {
107
		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...
108
	}
109
110
	public function Link() {
111
		if ($this->Environment()->IsNewDeployEnabled()) {
112
			return \Controller::join_links($this->Environment()->Link(\EnvironmentOverview::ACTION_OVERVIEW), 'deployment', $this->ID);
0 ignored issues
show
Coding Style introduced by
This line exceeds maximum limit of 120 characters; contains 126 characters

Overly long lines are hard to read on any screen. Most code styles therefor impose a maximum limit on the number of characters in a line.

Loading history...
113
		} else {
114
			return \Controller::join_links($this->Environment()->Link(), 'deploy', $this->ID);
115
		}
116
	}
117
118
	public function LogLink() {
119
		return $this->Link() . '/log';
120
	}
121
122
	public function canView($member = null) {
123
		return $this->Environment()->canView($member);
124
	}
125
126
	/**
127
	 * Return a path to the log file.
128
	 * @return string
129
	 */
130
	protected function logfile() {
131
		return sprintf(
132
			'%s.%s.log',
133
			$this->Environment()->getFullName('.'),
134
			$this->ID
135
		);
136
	}
137
138
	/**
139
	 * @return DeploynautLogFile
140
	 */
141
	public function log() {
142
		return Injector::inst()->createWithArgs('DeploynautLogFile', array($this->logfile()));
143
	}
144
145
	public function LogContent() {
146
		return $this->log()->content();
147
	}
148
149
	/**
150
	 * This remains here for backwards compatibility - we don't want to expose Resque status in here.
151
	 * Resque job (DeployJob) will change statuses as part of its execution.
152
	 *
153
	 * @return string
154
	 */
155
	public function ResqueStatus() {
156
		return $this->State;
157
	}
158
159
	/**
160
	 * Fetch the git repository
161
	 *
162
	 * @return \Gitonomy\Git\Repository|null
163
	 */
164
	public function getRepository() {
165
		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...
166
			return null;
167
		}
168
		return $this->Environment()->Project()->getRepository();
169
	}
170
171
	/**
172
	 * Gets the commit from source. The result is cached upstream in Repository.
173
	 *
174
	 * @return \Gitonomy\Git\Commit|null
175
	 */
176 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...
177
		$repo = $this->getRepository();
178
		if($repo) {
179
			try {
180
				return $this->Environment()->getCommit($this->SHA);
181
			} catch(Gitonomy\Git\Exception\ReferenceNotFoundException $ex) {
182
				return null;
183
			}
184
		}
185
186
		return null;
187
	}
188
189
	/**
190
	 * Get the commit URL to the commit associated with this deployment.
191
	 * @return null|string
192
	 */
193
	public function getCommitURL() {
194
		$environment = $this->Environment();
195
		if (!$environment) {
196
			return null;
197
		}
198
		$project = $environment->Project();
199
		if (!$project) {
200
			return null;
201
		}
202
		$interface = $project->getRepositoryInterface();
203
		if (!$interface) {
204
			return null;
205
		}
206
		return $interface->CommitURL . '/' . $this->SHA;
207
	}
208
209
	/**
210
	 * Gets the commit message.
211
	 *
212
	 * @return string|null
213
	 */
214 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...
215
		$commit = $this->getCommit();
216
		if($commit) {
217
			try {
218
				return Convert::raw2xml($this->Environment()->getCommitMessage($commit));
219
			} catch(Gitonomy\Git\Exception\ReferenceNotFoundException $e) {
220
				return null;
221
			}
222
		}
223
		return null;
224
	}
225
226
	/**
227
	 * Gets the commit message.
228
	 *
229
	 * @return string|null
230
	 */
231 View Code Duplication
	public function getCommitSubjectMessage() {
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...
232
		$commit = $this->getCommit();
233
		if($commit) {
234
			try {
235
				return Convert::raw2xml($this->Environment()->getCommitSubjectMessage($commit));
236
			} catch(Gitonomy\Git\Exception\ReferenceNotFoundException $e) {
237
				return null;
238
			}
239
		}
240
		return null;
241
	}
242
243
	/**
244
	 * Return all tags for the deployed commit.
245
	 *
246
	 * @return ArrayList
247
	 */
248
	public function getTags() {
249
		$commit = $this->Environment()->getCommit($this->SHA);
250
		if(!$commit) {
251
			return new ArrayList([]);
252
		}
253
		$tags = $this->Environment()->getCommitTags($commit);
254
		$returnTags = [];
255
		if (!empty($tags)) {
256
			foreach($tags as $tag) {
257
				$field = Varchar::create('Tag', '255');
258
				$field->setValue($tag->getName());
259
				$returnTags[] = $field;
260
			}
261
		}
262
		return new ArrayList($returnTags);
263
	}
264
265
	/**
266
	 * Collate the list of additional flags to affix to this deployment.
267
	 * Elements of the array will be rendered literally.
268
	 *
269
	 * @return ArrayList
270
	 */
271
	public function getFullDeployMessages() {
272
		$strategy = $this->getDeploymentStrategy();
273
		if ($strategy->getActionCode()!=='full') return null;
274
275
		$changes = $strategy->getChangesModificationNeeded();
276
		$messages = [];
277
		foreach ($changes as $change => $details) {
278
			if ($change==='Code version') continue;
279
280
			$messages[] = [
281
				'Flag' => sprintf(
282
					'<span class="label label-default full-deploy-info-item">%s</span>',
283
					$change[0]
284
				),
285
				'Text' => sprintf('%s changed', $change)
286
			];
287
		}
288
289
		if (empty($messages)) {
290
			$messages[] = [
291
				'Flag' => '',
292
				'Text' => '<i>Environment changes have been made.</i>'
293
			];
294
		}
295
296
		return new ArrayList($messages);
297
	}
298
299
	/**
300
	 * Fetches the latest tag for the deployed commit
301
	 *
302
	 * @return \Varchar|null
303
	 */
304
	public function getTag() {
305
		$tags = $this->getTags();
306
		if($tags->count() > 0) {
307
			return $tags->last();
308
		}
309
		return null;
310
	}
311
312
	/**
313
	 * @return DeploymentStrategy
314
	 */
315
	public function getDeploymentStrategy() {
316
		$environment = $this->Environment();
317
		$strategy = new DeploymentStrategy($environment);
318
		$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...
319
		return $strategy;
320
	}
321
322
	/**
323
	 * Return a list of things that are going to be deployed, such
324
	 * as the code version, and any infrastructural changes.
325
	 *
326
	 * @return ArrayList
327
	 */
328
	public function getChanges() {
329
		$list = new ArrayList();
330
		$strategy = $this->getDeploymentStrategy();
331
		foreach($strategy->getChanges() as $name => $change) {
332
			$changed = (isset($change['from']) && isset($change['to'])) ? $change['from'] != $change['to'] : null;
333
			$description = isset($change['description']) ? $change['description'] : '';
334
			$compareUrl = null;
335
336
			// if there is a compare URL, and a description or a change (something actually changed)
337
			// then show the URL. Otherwise don't show anything, as there is no comparison to be made.
338
			if ($changed || $description) {
339
				$compareUrl = isset($change['compareUrl']) ? $change['compareUrl'] : '';
340
			}
341
342
			$list->push(new ArrayData([
343
				'Name' => $name,
344
				'From' => isset($change['from']) ? $change['from'] : null,
345
				'To' => isset($change['to']) ? $change['to'] : null,
346
				'Description' => $description,
347
				'Changed' => $changed,
348
				'CompareUrl' => $compareUrl
349
			]));
350
		}
351
352
		return $list;
353
	}
354
355
	/**
356
	 * Start a resque job for this deployment
357
	 *
358
	 * @return string Resque token
359
	 */
360
	public function enqueueDeployment() {
361
		$environment = $this->Environment();
362
		$project = $environment->Project();
363
		$log = $this->log();
364
365
		$args = array(
366
			'environmentName' => $environment->Name,
367
			'repository' => $project->getLocalCVSPath(),
368
			'logfile' => $this->logfile(),
369
			'projectName' => $project->Name,
370
			'env' => $project->getProcessEnv(),
371
			'deploymentID' => $this->ID,
372
			'sigFile' => $this->getSigFile(),
373
		);
374
375
		$strategy = $this->getDeploymentStrategy();
376
		// Inject options.
377
		$args = array_merge($args, $strategy->getOptions());
378
		// Make sure we use the SHA as it was written into this DNDeployment.
379
		$args['sha'] = $this->SHA;
380
381
		if(!$this->DeployerID) {
382
			$this->DeployerID = Member::currentUserID();
383
		}
384
385 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...
386
			$deployer = $this->Deployer();
387
			$message = sprintf(
388
				'Deploy to %s initiated by %s (%s), with IP address %s',
389
				$environment->getFullName(),
390
				$deployer->getName(),
391
				$deployer->Email,
392
				\Controller::curr()->getRequest()->getIP()
393
			);
394
			$log->write($message);
395
		}
396
397
		return Resque::enqueue('deploy', 'DeployJob', $args, true);
398
	}
399
400
	public function getSigFile() {
401
		$dir = DNData::inst()->getSignalDir();
402
		if (!is_dir($dir)) {
403
			`mkdir $dir`;
404
		}
405
		return sprintf(
406
			'%s/deploynaut-signal-%s-%s',
407
			DNData::inst()->getSignalDir(),
408
			$this->ClassName,
409
			$this->ID
410
		);
411
	}
412
413
	/**
414
	 * Signal the worker to self-abort. If we had a reliable way of figuring out the right PID,
415
	 * we could posix_kill directly, but Resque seems to not provide a way to find out the PID
416
	 * from the job nor worker.
417
	 */
418
	public function setSignal($signal) {
419
		$sigFile = $this->getSigFile();
420
		// 2 is SIGINT - we can't use SIGINT constant in the Apache context, only available in workers.
421
		file_put_contents($sigFile, $signal);
422
	}
423
}
424