Completed
Pull Request — master (#613)
by Stig
03:37
created

DeployPlanDispatcher::getGitBranches()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 10
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 10
rs 9.4285
cc 2
eloc 7
nc 2
nop 1
1
<?php
2
3
4
class DeployPlanDispatcher extends Dispatcher {
5
6
	const ACTION_PLAN = 'plan';
7
8
	/**
9
	 * @var array
10
	 */
11
	private static $action_types = [
12
		self::ACTION_PLAN
13
	];
14
15
	/**
16
	 * @var array
17
	 */
18
	public static $allowed_actions = [
19
		'gitupdate',
20
		'gitrefs',
21
		'deploysummary'
22
	];
23
24
	/**
25
	 * @var \DNProject
26
	 */
27
	protected $project = null;
28
29
	/**
30
	 * @var \DNEnvironment
31
	 */
32
	protected $environment = null;
33
34
	public function init() {
35
		parent::init();
36
37
		$this->project = $this->getCurrentProject();
38
39
		if(!$this->project) {
40
			return $this->project404Response();
41
		}
42
43
		// Performs canView permission check by limiting visible projects
44
		$this->environment = $this->getCurrentEnvironment($this->project);
45
		if(!$this->environment) {
46
			return $this->environment404Response();
47
		}
48
	}
49
50
	/**
51
	 * @return string
52
	 */
53
	public function Link() {
54
		return \Controller::join_links($this->environment->Link(), self::ACTION_PLAN);
55
	}
56
57
	/**
58
	 *
59
	 * @param \SS_HTTPRequest $request
60
	 *
61
	 * @return \HTMLText|\SS_HTTPResponse
62
	 */
63
	public function index(\SS_HTTPRequest $request) {
64
		$this->setCurrentActionType(self::ACTION_PLAN);
65
		return $this->customise([
66
			'Environment' => $this->environment
67
		])->renderWith(['Plan', 'DNRoot']);
68
	}
69
70
	/**
71
	 * @param SS_HTTPRequest $request
72
	 * @return SS_HTTPResponse
73
	 */
74
	public function gitupdate(SS_HTTPRequest $request) {
75
		switch($request->httpMethod()) {
76
			case 'POST':
77
				$this->checkSecurityToken(sprintf('%sSecurityID', get_class($this)));
78
				return $this->createFetch();
79
			case 'GET':
80
				return $this->getFetch($this->getRequest()->param('ID'));
81
			default:
82
				return $this->getAPIResponse(['message' => 'Method not allowed, requires POST or GET/{id}'], 405);
83
		}
84
	}
85
86
	/**
87
	 * @param SS_HTTPRequest $request
88
	 *
89
	 * @return string
90
	 */
91
	public function gitrefs(\SS_HTTPRequest $request) {
92
93
		$refs = [];
94
		$order = 0;
95
		$refs[] = [
96
			'id' => ++$order,
97
			'label' => "Branch version",
98
			"description" => "Deploy the latest version of a branch",
99
			"list" => $this->getGitBranches($this->project)
100
		];
101
102
		$refs[] = [
103
			'id' => ++$order,
104
			'label' => "Tag version",
105
			"description" => "Deploy a tagged release",
106
			"list" => $this->getGitTags($this->project)
107
		];
108
109
		// @todo: the original was a tree that was keyed by environment, the
110
		// front-end dropdown needs to be changed to support that. brrrr.
111
		$prevDeploys = [];
112
		foreach($this->getGitPrevDeploys($this->project) as $env) {
113
			foreach($env as $deploy) {
114
				$prevDeploys[] = $deploy;
115
			}
116
		}
117
		$refs[] = [
118
			'id' => ++$order,
119
			'label' => "Redeploy a release that was previously deployed (to any environment",
120
			"description" => "Deploy a previous release",
121
			"list" => $prevDeploys
122
		];
123
124
		return $this->getAPIResponse(['refs' => $refs], 200);
125
	}
126
127
	/**
128
	 * @param string $name
129
	 *
130
	 * @return array
131
	 */
132
	public function getModel($name = '') {
133
		return [
134
			'namespace' => self::ACTION_PLAN,
135
			'api_endpoint' => Director::absoluteBaseURL().$this->Link(),
136
			'api_auth' => [
137
				'name' => $this->getSecurityToken()->getName(),
138
				'value' => $this->getSecurityToken()->getValue()
139
			]
140
		];
141
	}
142
143
	/**
144
	 * @param int $ID
145
	 * @return SS_HTTPResponse
146
	 */
147
	protected function getFetch($ID) {
148
		$ping = DNGitFetch::get()->byID($ID);
149
		if(!$ping) {
150
			return $this->getAPIResponse(['message' => 'Fetch not found'], 404);
151
		}
152
		$output = [
153
			'id' => $ID,
154
			'status' => $ping->ResqueStatus(),
155
			'message' => array_filter(explode(PHP_EOL, $ping->LogContent()))
156
		];
157
158
		return $this->getAPIResponse($output, 200);
159
	}
160
161
	/**
162
	 * @return SS_HTTPResponse
163
	 */
164 View Code Duplication
	protected function createFetch() {
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...
165
		/** @var DNGitFetch $fetch */
166
		$fetch = DNGitFetch::create();
167
		$fetch->ProjectID = $this->project->ID;
168
		$fetch->write();
169
		$fetch->start();
170
171
		$location = Director::absoluteBaseURL() . $this->Link() . '/gitupdate/' . $fetch->ID;
172
		$output = array(
173
			'message' => 'Fetch queued as job ' . $fetch->ResqueToken,
174
			'href' => $location,
175
		);
176
177
		$response = $this->getAPIResponse($output, 201);
178
		$response->addHeader('Location', $location);
179
		return $response;
180
	}
181
182
	/**
183
	 * @param SS_HTTPRequest $request
184
	 *
185
	 * @return SS_HTTPResponse
186
	 */
187
	public function deploysummary(SS_HTTPRequest $request) {
188
		$this->checkSecurityToken(sprintf('%sSecurityID', get_class($this)));
189
190
		// @todo permission checks?
191
192
		$options = [
193
			'sha' => $request->requestVar('sha')
194
		];
195
196
		$strategy = $this->environment->Backend()->planDeploy($this->environment, $options);
197
		$data = $strategy->toArray();
198
199
		$interface = $this->project->getRepositoryInterface();
200
		if($this->canCompareCodeVersions($interface, $data['changes'])) {
0 ignored issues
show
Bug introduced by
It seems like $interface defined by $this->project->getRepositoryInterface() on line 199 can be null; however, DeployPlanDispatcher::canCompareCodeVersions() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
201
			$compareurl = sprintf(
202
				'%s/compare/%s...%s',
203
				$interface->URL,
204
				$data['changes']['Code version']['from'],
205
				$data['changes']['Code version']['to']
206
			);
207
			$data['changes']['Code version']['compareUrl'] = $compareurl;
208
		}
209
210
		$this->extend('updateDeploySummary', $data);
211
212
		return $this->getAPIResponse($data, 201);
213
	}
214
215
	/**
216
	 * @param $project
217
	 *
218
	 * @return array
219
	 */
220
	protected function getGitBranches($project) {
221
		$branches = [];
222
		foreach($project->DNBranchList() as $branch) {
223
			$branches[] = [
224
				'key' => $branch->SHA(),
225
				'value' => $branch->Name(),
226
			];
227
		}
228
		return $branches;
229
	}
230
231
	/**
232
	 * @param $project
233
	 *
234
	 * @return array
235
	 */
236
	protected function getGitTags($project) {
237
		$tags = [];
238
		foreach($project->DNTagList()->setLimit(null) as $tag) {
239
			$tags[] = [
240
				'key' => $tag->SHA(),
241
				'value' => $tag->Name(),
242
			];
243
		}
244
		return $tags;
245
	}
246
247
	/**
248
	 * @param $project
249
	 *
250
	 * @return array
251
	 */
252
	protected function getGitPrevDeploys($project) {
253
		$redeploy = [];
254 View Code Duplication
		foreach($project->DNEnvironmentList() as $dnEnvironment) {
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...
255
			$envName = $dnEnvironment->Name;
256
			$perEnvDeploys = [];
257
			foreach($dnEnvironment->DeployHistory() as $deploy) {
258
				$sha = $deploy->SHA;
259
260
				// Check if exists to make sure the newest deployment date is used.
261
				if(!isset($perEnvDeploys[$sha])) {
262
					$pastValue = sprintf(
263
						"%s (deployed %s)",
264
						substr($sha, 0, 8),
265
						$deploy->obj('LastEdited')->Ago()
266
					);
267
					$perEnvDeploys[$sha] = [
268
						'key' => $sha,
269
						'value' => $pastValue
270
					];
271
				}
272
			}
273
			if(!empty($perEnvDeploys)) {
274
				$redeploy[$envName] = array_values($perEnvDeploys);
275
			}
276
		}
277
		return $redeploy;
278
	}
279
280
	/**
281
	 * Return a simple response with a message
282
	 *
283
	 * @param array $output
284
	 * @param int $statusCode
285
	 * @return SS_HTTPResponse
286
	 */
287
	protected function getAPIResponse($output, $statusCode) {
288
		$secToken = $this->getSecurityToken();
289
		$secToken->reset();
290
		$output = array_merge($this->getModel(), $output);
291
		$output['status_code'] = $statusCode;
292
		$body = json_encode($output, JSON_PRETTY_PRINT);
293
		$response = $this->getResponse();
294
		$response->addHeader('Content-Type', 'application/json');
295
		$response->setBody($body);
296
		$response->setStatusCode($statusCode);
297
		return $response;
298
	}
299
300
	/**
301
	 * @param ArrayData $interface
302
	 * @param $changes
303
	 *
304
	 * @return bool
305
	 *
306
	 */
307
	protected function canCompareCodeVersions(\ArrayData $interface, $changes) {
308
		if(empty($changes['Code version'])) {
309
			return false;
310
		}
311
		$codeVersion = ['Code version'];
312
		if(empty($interface)) {
313
			return false;
314
		}
315
		if(empty($interface->URL)) {
316
			return false;
317
		}
318
		if(empty($codeVersion['from']) || empty($codeVersion['to'])) {
319
			return false;
320
		}
321
		if(strlen($codeVersion['from']) !== 40 || strlen($codeVersion['to']) !== 40) {
322
			return false;
323
		}
324
		return true;
325
	}
326
}
327