Completed
Pull Request — master (#630)
by Sean
04:25 queued 01:20
created

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