Completed
Pull Request — master (#579)
by Mateusz
07:37
created

DNDeployment::getStatus()   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
dl 0
loc 3
rs 10
c 1
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
use Finite\State\StateInterface;
4
5
/**
6
 * Class representing a single deplyoment (passed or failed) at a time to a particular environment
7
 *
8
 * @property string $SHA
9
 * @property string $ResqueToken
10
 * @property string $Status
11
 *
12
 * @method DNEnvironment Environment()
13
 * @property int EnvironmentID
14
 * @method Member Deployer()
15
 * @property int DeployerID
16
 */
17
class DNDeployment extends DataObject implements Finite\StatefulInterface, HasStateMachine {
18
19
	/**
20
	 * @var array
21
	 */
22
	private static $db = array(
23
		"SHA" => "GitSHA",
24
		"ResqueToken" => "Varchar(255)",
25
		// The branch that was used to deploy this. Can't really be inferred from Git history because
26
		// the commit could appear in lots of branches that are irrelevant to the user when it comes
27
		// to deployment history, and the branch may have been deleted.
28
		"Branch" => "Varchar(255)",
29
		"State" => "Enum('New, Approved, Invalid, Queued, Deploying, Aborting, Completed, Failed', 'New')",
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
	public function getFiniteState() {
56
        return $this->State;
0 ignored issues
show
Documentation introduced by
The property State 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...
57
    }
58
59
    public function setFiniteState($state) {
60
        $this->State = $state;
0 ignored issues
show
Documentation introduced by
The property State does not exist on object<DNDeployment>. Since you implemented __set, maybe consider adding a @property annotation.

Since your code implements the magic setter _set, this function will be called for any write 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.");
        }
    }

}

Since the property has write access only, you can use the @property-write 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...
61
		$this->write();
62
    }
63
64
	public function getStatus() {
65
		return $this->State;
0 ignored issues
show
Documentation introduced by
The property State 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...
66
	}
67
68
	public function getMachine() {
69
		$loader = new Finite\Loader\ArrayLoader([
70
			'class'   => 'DNDeployment',
71
			'states'  => [
72
				'New' => ['type' => StateInterface::TYPE_INITIAL],
73
				'Approved' => ['type' => StateInterface::TYPE_NORMAL],
74
				'Invalid' => ['type' => StateInterface::TYPE_NORMAL],
75
				'Queued' => ['type' => StateInterface::TYPE_NORMAL],
76
				'Deploying' => ['type' => StateInterface::TYPE_NORMAL],
77
				'Aborting' => ['type' => StateInterface::TYPE_FINAL],
78
				'Completed' => ['type' => StateInterface::TYPE_FINAL],
79
				'Failed' => ['type' => StateInterface::TYPE_FINAL],
80
			],
81
			'transitions' => [
82
				'approve'  => ['from' => ['New'], 'to' => 'Approved'],
83
				'queue'  => ['from' => ['Approved'], 'to' => 'Queued'],
84
				'invalidate'  => ['from' => ['New','Approved'], 'to' => 'Invalid'],
85
				'deploy'  => ['from' => ['Queued'], 'to' => 'Deploying'],
86
				'abort'  => ['from' => ['Queued', 'Deploying','Aborting'], 'to' => 'Aborting'],
87
				'complete' => ['from' => ['Deploying'], 'to' => 'Completed'],
88
				'fail'  => [
89
					'from' => ['New','Approved','Queued','Invalid','Deploying','Aborting'],
90
					'to' => 'Failed'
91
				],
92
			],
93
			'callbacks' => [
94
				'after' => [
95
					['to' => ['Queued'], 'do' => [$this, 'onQueue']],
96
					['to' => ['Aborting'], 'do' => [$this, 'onAbort']],
97
				]
98
			]
99
		]);
100
		$stateMachine = new Finite\StateMachine\StateMachine($this);
101
		$loader->load($stateMachine);
102
		$stateMachine->initialize();
103
		return $stateMachine;
104
	}
105
106
107
	public function onQueue() {
108
		$log = $this->log();
109
		$token = $this->enqueueDeployment();
110
		$this->ResqueToken = $token;
111
		$this->write();
112
113
		$message = sprintf('Deploy queued as job %s (sigFile is %s)', $token, DeployJob::sig_file_for_data_object($this));
114
		$log->write($message);
115
	}
116
117
	public function onAbort() {
118
		// 2 is SIGINT - we can't use SIGINT constant in the mod_apache context.
119
		DeployJob::set_signal($this, 2);
120
	}
121
122
	public function Link() {
123
		return Controller::join_links($this->Environment()->Link(), 'deploy', $this->ID);
124
	}
125
126
	public function LogLink() {
127
		return $this->Link() . '/log';
128
	}
129
130
	public function canView($member = null) {
131
		return $this->Environment()->canView($member);
132
	}
133
134
	/**
135
	 * Return a path to the log file.
136
	 * @return string
137
	 */
138
	protected function logfile() {
139
		return sprintf(
140
			'%s.%s.log',
141
			$this->Environment()->getFullName('.'),
142
			$this->ID
143
		);
144
	}
145
146
	/**
147
	 * @return DeploynautLogFile
148
	 */
149
	public function log() {
150
		return Injector::inst()->createWithArgs('DeploynautLogFile', array($this->logfile()));
151
	}
152
153
	public function LogContent() {
154
		return $this->log()->content();
155
	}
156
157
	/**
158
	 * This remains here for backwards compatibility - we don't want to expose Resque status in here.
159
	 * Resque job (DeployJob) will change statuses as part of its execution.
160
	 *
161
	 * @return string
162
	 */
163
	public function ResqueStatus() {
164
		return $this->State;
0 ignored issues
show
Documentation introduced by
The property State 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...
165
	}
166
167
168
	/**
169
	 * Fetch the git repository
170
	 *
171
	 * @return \Gitonomy\Git\Repository|null
172
	 */
173
	public function getRepository() {
174
		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...
175
			return null;
176
		}
177
		return $this->Environment()->Project()->getRepository();
178
	}
179
180
181
	/**
182
	 * Gets the commit from source. The result is cached upstream in Repository.
183
	 *
184
	 * @return \Gitonomy\Git\Commit|null
185
	 */
186
	public function getCommit() {
187
		$repo = $this->getRepository();
188
		if($repo) {
189
			try {
190
				return $repo->getCommit($this->SHA);
191
			} catch(Gitonomy\Git\Exception\ReferenceNotFoundException $ex) {
192
				return null;
193
			}
194
		}
195
196
		return null;
197
	}
198
199
200
	/**
201
	 * Gets the commit message.
202
	 *
203
	 * @return string|null
204
	 */
205
	public function getCommitMessage() {
206
		$commit = $this->getCommit();
207
		if($commit) {
208
			try {
209
				return Convert::raw2xml($commit->getMessage());
210
			} catch(Gitonomy\Git\Exception\ReferenceNotFoundException $e) {
211
				return null;
212
			}
213
		}
214
		return null;
215
	}
216
217
	/**
218
	 * Return all tags for the deployed commit.
219
	 *
220
	 * @return ArrayList
221
	 */
222
	public function getTags() {
223
		$returnTags = array();
224
		$repo = $this->getRepository();
225
		if($repo) {
226
			$tags = $repo->getReferences()->resolveTags($this->SHA);
227
			if(!empty($tags)) {
228
				foreach($tags as $tag) {
229
					$field = Varchar::create('Tag', '255');
230
					$field->setValue($tag->getName());
231
					$returnTags[] = $field;
232
				}
233
			}
234
		}
235
		return new ArrayList($returnTags);
236
	}
237
238
	/**
239
	 * Collate the list of additional flags to affix to this deployment.
240
	 * Elements of the array will be rendered literally.
241
	 *
242
	 * @return ArrayList
243
	 */
244
	public function getFullDeployMessages() {
245
		$strategy = $this->getDeploymentStrategy();
246
		if ($strategy->getActionCode()!=='full') return null;
247
248
		$changes = $strategy->getChangesModificationNeeded();
249
		$messages = [];
250
		foreach ($changes as $change => $details) {
251
			if ($change==='Code version') continue;
252
253
			$messages[] = [
254
				'Flag' => sprintf(
255
					'<span class="label label-default full-deploy-info-item">%s</span>',
256
					$change[0]
257
				),
258
				'Text' => sprintf('%s changed', $change)
259
			];
260
		}
261
262
		if (empty($messages)) {
263
			$messages[] = [
264
				'Flag' => '',
265
				'Text' => '<i>Environment changes have been made.</i>'
266
			];
267
		}
268
269
		return new ArrayList($messages);
270
	}
271
272
	/**
273
	 * Fetches the latest tag for the deployed commit
274
	 *
275
	 * @return \Varchar|null
276
	 */
277
	public function getTag() {
278
		$tags = $this->getTags();
279
		if($tags->count() > 0) {
280
			return $tags->last();
281
		}
282
		return null;
283
	}
284
285
	/**
286
	 * @return DeploymentStrategy
287
	 */
288
	public function getDeploymentStrategy() {
289
		$environment = $this->Environment();
290
		$strategy = new DeploymentStrategy($environment);
291
		$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...
292
		return $strategy;
293
	}
294
295
	/**
296
	 * Return a list of things that are going to be deployed, such
297
	 * as the code version, and any infrastrucutral changes.
298
	 *
299
	 * @return ArrayList
300
	 */
301
	public function getChanges() {
302
		$list = new ArrayList();
303
		$strategy = $this->getDeploymentStrategy();
304
		foreach($strategy->getChanges() as $name => $change) {
305
			$changed = (isset($change['from']) && isset($change['to'])) ? $change['from'] != $change['to'] : null;
306
			$description = isset($change['description']) ? $change['description'] : '';
307
			$compareUrl = null;
308
309
			// if there is a compare URL, and a description or a change (something actually changed)
310
			// then show the URL. Otherwise don't show anything, as there is no comparison to be made.
311
			if ($changed || $description) {
312
				$compareUrl = isset($change['compareUrl']) ? $change['compareUrl'] : '';
313
			}
314
315
			$list->push(new ArrayData([
316
				'Name' => $name,
317
				'From' => isset($change['from']) ? $change['from'] : null,
318
				'To' => isset($change['to']) ? $change['to'] : null,
319
				'Description' => $description,
320
				'Changed' => $changed,
321
				'CompareUrl' => $compareUrl
322
			]));
323
		}
324
325
		return $list;
326
	}
327
328
	/**
329
	 * Start a resque job for this deployment
330
	 *
331
	 * @return string Resque token
332
	 */
333
	protected function enqueueDeployment() {
334
		$environment = $this->Environment();
335
		$project = $environment->Project();
336
		$log = $this->log();
337
338
		$args = array(
339
			'environmentName' => $environment->Name,
340
			'repository' => $project->getLocalCVSPath(),
341
			'logfile' => $this->logfile(),
342
			'projectName' => $project->Name,
343
			'env' => $project->getProcessEnv(),
344
			'deploymentID' => $this->ID,
345
			'sigFile' => DeployJob::sig_file_for_data_object($this)
346
		);
347
348
		$strategy = $this->getDeploymentStrategy();
349
		// Inject options.
350
		$args = array_merge($args, $strategy->getOptions());
351
		// Make sure we use the SHA as it was written into this DNDeployment.
352
		$args['sha'] = $this->SHA;
353
354
		if(!$this->DeployerID) {
355
			$this->DeployerID = Member::currentUserID();
356
		}
357
358 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...
359
			$deployer = $this->Deployer();
360
			$message = sprintf(
361
				'Deploy to %s initiated by %s (%s), with IP address %s',
362
				$environment->getFullName(),
363
				$deployer->getName(),
364
				$deployer->Email,
365
				Controller::curr()->getRequest()->getIP()
366
			);
367
			$log->write($message);
368
		}
369
370
		return Resque::enqueue('deploy', 'DeployJob', $args, true);
371
	}
372
}
373