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 $Status |
9
|
|
|
* |
10
|
|
|
* @method DNEnvironment Environment() |
11
|
|
|
* @property int EnvironmentID |
12
|
|
|
* @method Member Deployer() |
13
|
|
|
* @property int DeployerID |
14
|
|
|
*/ |
15
|
|
|
class DNDeployment extends DataObject { |
16
|
|
|
|
17
|
|
|
/** |
18
|
|
|
* @var array |
19
|
|
|
*/ |
20
|
|
|
private static $db = array( |
21
|
|
|
"SHA" => "GitSHA", |
22
|
|
|
"ResqueToken" => "Varchar(255)", |
23
|
|
|
// The branch that was used to deploy this. Can't really be inferred from Git history because |
24
|
|
|
// the commit could appear in lots of branches that are irrelevant to the user when it comes |
25
|
|
|
// to deployment history, and the branch may have been deleted. |
26
|
|
|
"Branch" => "Varchar(255)", |
27
|
|
|
// Observe that this is not the same as Resque status, since ResqueStatus is not persistent |
28
|
|
|
// It's used for finding successful deployments and displaying that in history views in the frontend |
29
|
|
|
"Status" => "Enum('Queued, Started, Finished, Failed, n/a', 'n/a')", |
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
|
|
|
/** |
56
|
|
|
* @param int $int |
57
|
|
|
* @return string |
58
|
|
|
*/ |
59
|
|
|
public static function map_resque_status($int) { |
60
|
|
|
$remap = array( |
61
|
|
|
Resque_Job_Status::STATUS_WAITING => "Queued", |
62
|
|
|
Resque_Job_Status::STATUS_RUNNING => "Running", |
63
|
|
|
Resque_Job_Status::STATUS_FAILED => "Failed", |
64
|
|
|
Resque_Job_Status::STATUS_COMPLETE => "Complete", |
65
|
|
|
false => "Invalid", |
66
|
|
|
); |
67
|
|
|
return $remap[$int]; |
68
|
|
|
} |
69
|
|
|
|
70
|
|
|
|
71
|
|
|
public function Link() { |
72
|
|
|
return Controller::join_links($this->Environment()->Link(), 'deploy', $this->ID); |
73
|
|
|
} |
74
|
|
|
|
75
|
|
|
public function LogLink() { |
76
|
|
|
return $this->Link() . '/log'; |
77
|
|
|
} |
78
|
|
|
|
79
|
|
|
public function canView($member = null) { |
80
|
|
|
return $this->Environment()->canView($member); |
81
|
|
|
} |
82
|
|
|
|
83
|
|
|
/** |
84
|
|
|
* Return a path to the log file. |
85
|
|
|
* @return string |
86
|
|
|
*/ |
87
|
|
|
protected function logfile() { |
88
|
|
|
return sprintf( |
89
|
|
|
'%s.%s.log', |
90
|
|
|
$this->Environment()->getFullName('.'), |
91
|
|
|
$this->ID |
92
|
|
|
); |
93
|
|
|
} |
94
|
|
|
|
95
|
|
|
/** |
96
|
|
|
* @return DeploynautLogFile |
97
|
|
|
*/ |
98
|
|
|
public function log() { |
99
|
|
|
return Injector::inst()->createWithArgs('DeploynautLogFile', array($this->logfile())); |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
public function LogContent() { |
103
|
|
|
return $this->log()->content(); |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
/** |
107
|
|
|
* Returns the status of the resque job |
108
|
|
|
* |
109
|
|
|
* @return string |
110
|
|
|
*/ |
111
|
|
View Code Duplication |
public function ResqueStatus() { |
|
|
|
|
112
|
|
|
$status = new Resque_Job_Status($this->ResqueToken); |
|
|
|
|
113
|
|
|
$statusCode = $status->get(); |
114
|
|
|
// The Resque job can no longer be found, fallback to the DNDeployment.Status |
115
|
|
|
if($statusCode === false) { |
116
|
|
|
// Translate from the DNDeployment.Status to the Resque job status for UI purposes |
117
|
|
|
switch($this->Status) { |
118
|
|
|
case 'Finished': |
119
|
|
|
return 'Complete'; |
120
|
|
|
case 'Started': |
121
|
|
|
return 'Running'; |
122
|
|
|
default: |
123
|
|
|
return $this->Status; |
124
|
|
|
} |
125
|
|
|
} |
126
|
|
|
return self::map_resque_status($statusCode); |
127
|
|
|
} |
128
|
|
|
|
129
|
|
|
|
130
|
|
|
/** |
131
|
|
|
* Fetch the git repository |
132
|
|
|
* |
133
|
|
|
* @return \Gitonomy\Git\Repository|null |
134
|
|
|
*/ |
135
|
|
|
public function getRepository() { |
136
|
|
|
if(!$this->SHA) { |
|
|
|
|
137
|
|
|
return null; |
138
|
|
|
} |
139
|
|
|
return $this->Environment()->Project()->getRepository(); |
140
|
|
|
} |
141
|
|
|
|
142
|
|
|
|
143
|
|
|
/** |
144
|
|
|
* Gets the commit from source. The result is cached upstream in Repository. |
145
|
|
|
* |
146
|
|
|
* @return \Gitonomy\Git\Commit|null |
147
|
|
|
*/ |
148
|
|
|
public function getCommit() { |
149
|
|
|
$repo = $this->getRepository(); |
150
|
|
|
if($repo) { |
151
|
|
|
try { |
152
|
|
|
return $repo->getCommit($this->SHA); |
153
|
|
|
} catch(Gitonomy\Git\Exception\ReferenceNotFoundException $ex) { |
154
|
|
|
return null; |
155
|
|
|
} |
156
|
|
|
} |
157
|
|
|
|
158
|
|
|
return null; |
159
|
|
|
} |
160
|
|
|
|
161
|
|
|
|
162
|
|
|
/** |
163
|
|
|
* Gets the commit message. |
164
|
|
|
* |
165
|
|
|
* @return string|null |
166
|
|
|
*/ |
167
|
|
|
public function getCommitMessage() { |
168
|
|
|
$commit = $this->getCommit(); |
169
|
|
|
if($commit) { |
170
|
|
|
try { |
171
|
|
|
return Convert::raw2xml($commit->getMessage()); |
172
|
|
|
} catch(Gitonomy\Git\Exception\ReferenceNotFoundException $e) { |
173
|
|
|
return null; |
174
|
|
|
} |
175
|
|
|
} |
176
|
|
|
return null; |
177
|
|
|
} |
178
|
|
|
|
179
|
|
|
/** |
180
|
|
|
* Return all tags for the deployed commit. |
181
|
|
|
* |
182
|
|
|
* @return ArrayList |
183
|
|
|
*/ |
184
|
|
|
public function getTags() { |
185
|
|
|
$returnTags = array(); |
186
|
|
|
$repo = $this->getRepository(); |
187
|
|
|
if($repo) { |
188
|
|
|
$tags = $repo->getReferences()->resolveTags($this->SHA); |
189
|
|
|
if(!empty($tags)) { |
190
|
|
|
foreach($tags as $tag) { |
191
|
|
|
$field = Varchar::create('Tag', '255'); |
192
|
|
|
$field->setValue($tag->getName()); |
193
|
|
|
$returnTags[] = $field; |
194
|
|
|
} |
195
|
|
|
} |
196
|
|
|
} |
197
|
|
|
return new ArrayList($returnTags); |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
/** |
201
|
|
|
* Collate the list of additional flags to affix to this deployment. |
202
|
|
|
* Elements of the array will be rendered literally. |
203
|
|
|
* |
204
|
|
|
* @return ArrayList |
205
|
|
|
*/ |
206
|
|
|
public function getFullDeployMessages() { |
207
|
|
|
$strategy = $this->getDeploymentStrategy(); |
208
|
|
|
if ($strategy->getActionCode()!=='full') return null; |
209
|
|
|
|
210
|
|
|
$changes = $strategy->getChangesModificationNeeded(); |
211
|
|
|
$messages = []; |
212
|
|
|
foreach ($changes as $change => $details) { |
213
|
|
|
if ($change==='Code version') continue; |
214
|
|
|
|
215
|
|
|
$messages[] = [ |
216
|
|
|
'Flag' => sprintf( |
217
|
|
|
'<span class="label label-default full-deploy-info-item">%s</span>', |
218
|
|
|
$change[0] |
219
|
|
|
), |
220
|
|
|
'Text' => sprintf('%s changed', $change) |
221
|
|
|
]; |
222
|
|
|
} |
223
|
|
|
|
224
|
|
|
if (empty($messages)) { |
225
|
|
|
$messages[] = [ |
226
|
|
|
'Flag' => '', |
227
|
|
|
'Text' => '<i>Environment changes have been made.</i>' |
228
|
|
|
]; |
229
|
|
|
} |
230
|
|
|
|
231
|
|
|
return new ArrayList($messages); |
232
|
|
|
} |
233
|
|
|
|
234
|
|
|
/** |
235
|
|
|
* Fetches the latest tag for the deployed commit |
236
|
|
|
* |
237
|
|
|
* @return \Varchar|null |
238
|
|
|
*/ |
239
|
|
|
public function getTag() { |
240
|
|
|
$tags = $this->getTags(); |
241
|
|
|
if($tags->count() > 0) { |
242
|
|
|
return $tags->last(); |
243
|
|
|
} |
244
|
|
|
return null; |
245
|
|
|
} |
246
|
|
|
|
247
|
|
|
/** |
248
|
|
|
* @return DeploymentStrategy |
249
|
|
|
*/ |
250
|
|
|
public function getDeploymentStrategy() { |
251
|
|
|
$environment = $this->Environment(); |
252
|
|
|
$strategy = new DeploymentStrategy($environment); |
253
|
|
|
$strategy->fromJSON($this->Strategy); |
|
|
|
|
254
|
|
|
return $strategy; |
255
|
|
|
} |
256
|
|
|
|
257
|
|
|
/** |
258
|
|
|
* Return a list of things that are going to be deployed, such |
259
|
|
|
* as the code version, and any infrastrucutral changes. |
260
|
|
|
* |
261
|
|
|
* @return ArrayList |
262
|
|
|
*/ |
263
|
|
|
public function getChanges() { |
264
|
|
|
$list = new ArrayList(); |
265
|
|
|
$strategy = $this->getDeploymentStrategy(); |
266
|
|
|
foreach($strategy->getChanges() as $name => $change) { |
267
|
|
|
$changed = (isset($change['from']) && isset($change['to'])) ? $change['from'] != $change['to'] : null; |
268
|
|
|
$description = isset($change['description']) ? $change['description'] : ''; |
269
|
|
|
$compareUrl = null; |
270
|
|
|
|
271
|
|
|
// if there is a compare URL, and a description or a change (something actually changed) |
272
|
|
|
// then show the URL. Otherwise don't show anything, as there is no comparison to be made. |
273
|
|
|
if ($changed || $description) { |
274
|
|
|
$compareUrl = isset($change['compareUrl']) ? $change['compareUrl'] : ''; |
275
|
|
|
} |
276
|
|
|
|
277
|
|
|
$list->push(new ArrayData([ |
278
|
|
|
'Name' => $name, |
279
|
|
|
'From' => isset($change['from']) ? $change['from'] : null, |
280
|
|
|
'To' => isset($change['to']) ? $change['to'] : null, |
281
|
|
|
'Description' => $description, |
282
|
|
|
'Changed' => $changed, |
283
|
|
|
'CompareUrl' => $compareUrl |
284
|
|
|
])); |
285
|
|
|
} |
286
|
|
|
|
287
|
|
|
return $list; |
288
|
|
|
} |
289
|
|
|
|
290
|
|
|
/** |
291
|
|
|
* Start a resque job for this deployment |
292
|
|
|
* |
293
|
|
|
* @return string Resque token |
294
|
|
|
*/ |
295
|
|
|
protected function enqueueDeployment() { |
296
|
|
|
$environment = $this->Environment(); |
297
|
|
|
$project = $environment->Project(); |
298
|
|
|
$log = $this->log(); |
299
|
|
|
|
300
|
|
|
$args = array( |
301
|
|
|
'environmentName' => $environment->Name, |
302
|
|
|
'repository' => $project->getLocalCVSPath(), |
303
|
|
|
'logfile' => $this->logfile(), |
304
|
|
|
'projectName' => $project->Name, |
305
|
|
|
'env' => $project->getProcessEnv(), |
306
|
|
|
'deploymentID' => $this->ID |
307
|
|
|
); |
308
|
|
|
|
309
|
|
|
$strategy = $this->getDeploymentStrategy(); |
310
|
|
|
// Inject options. |
311
|
|
|
$args = array_merge($args, $strategy->getOptions()); |
312
|
|
|
// Make sure we use the SHA as it was written into this DNDeployment. |
313
|
|
|
$args['sha'] = $this->SHA; |
314
|
|
|
|
315
|
|
|
if(!$this->DeployerID) { |
316
|
|
|
$this->DeployerID = Member::currentUserID(); |
317
|
|
|
} |
318
|
|
|
|
319
|
|
View Code Duplication |
if($this->DeployerID) { |
|
|
|
|
320
|
|
|
$deployer = $this->Deployer(); |
321
|
|
|
$message = sprintf( |
322
|
|
|
'Deploy to %s initiated by %s (%s), with IP address %s', |
323
|
|
|
$environment->getFullName(), |
324
|
|
|
$deployer->getName(), |
325
|
|
|
$deployer->Email, |
326
|
|
|
Controller::curr()->getRequest()->getIP() |
327
|
|
|
); |
328
|
|
|
$log->write($message); |
329
|
|
|
} |
330
|
|
|
|
331
|
|
|
return Resque::enqueue('deploy', 'DeployJob', $args, true); |
332
|
|
|
} |
333
|
|
|
|
334
|
|
View Code Duplication |
public function start() { |
|
|
|
|
335
|
|
|
$log = $this->log(); |
336
|
|
|
$token = $this->enqueueDeployment(); |
337
|
|
|
$this->ResqueToken = $token; |
338
|
|
|
$this->Status = 'Queued'; |
339
|
|
|
$this->write(); |
340
|
|
|
|
341
|
|
|
$message = sprintf('Deploy queued as job %s', $token); |
342
|
|
|
$log->write($message); |
343
|
|
|
} |
344
|
|
|
} |
345
|
|
|
|
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.