Completed
Pull Request — master (#489)
by Helpful
03:51
created
code/model/DNTag.php 1 patch
Indentation   +51 added lines, -51 removed lines patch added patch discarded remove patch
@@ -5,66 +5,66 @@
 block discarded – undo
5 5
 class DNTag extends ViewableData
6 6
 {
7 7
 
8
-    /**
9
-     * @var Gitonomy\Git\Reference\Tag
10
-     */
11
-    protected $tag = null;
8
+	/**
9
+	 * @var Gitonomy\Git\Reference\Tag
10
+	 */
11
+	protected $tag = null;
12 12
 
13
-    protected $project = null;
13
+	protected $project = null;
14 14
 
15
-    protected $data = null;
15
+	protected $data = null;
16 16
 
17
-    protected $name = null;
17
+	protected $name = null;
18 18
 
19
-    protected $references = null;
19
+	protected $references = null;
20 20
 
21
-    private static $casting = array(
22
-        'Name' => 'Text',
23
-        'SHA' => 'Text'
24
-    );
21
+	private static $casting = array(
22
+		'Name' => 'Text',
23
+		'SHA' => 'Text'
24
+	);
25 25
 
26
-    /**
27
-     * @param Tag $tag
28
-     * @param DNProject $project
29
-     * @param DNData $data
30
-     */
31
-    public function __construct(Tag $tag, DNProject $project, DNData $data)
32
-    {
33
-        $this->tag = $tag;
34
-        $this->project = $project;
35
-        $this->data = $data;
36
-    }
26
+	/**
27
+	 * @param Tag $tag
28
+	 * @param DNProject $project
29
+	 * @param DNData $data
30
+	 */
31
+	public function __construct(Tag $tag, DNProject $project, DNData $data)
32
+	{
33
+		$this->tag = $tag;
34
+		$this->project = $project;
35
+		$this->data = $data;
36
+	}
37 37
 
38
-    /**
39
-     * @return string
40
-     */
41
-    public function Name()
42
-    {
43
-        return htmlentities($this->tag->getName());
44
-    }
38
+	/**
39
+	 * @return string
40
+	 */
41
+	public function Name()
42
+	{
43
+		return htmlentities($this->tag->getName());
44
+	}
45 45
 
46
-    /**
47
-     * @return string
48
-     */
49
-    public function SHA()
50
-    {
51
-        return htmlentities($this->tag->getCommitHash());
52
-    }
46
+	/**
47
+	 * @return string
48
+	 */
49
+	public function SHA()
50
+	{
51
+		return htmlentities($this->tag->getCommitHash());
52
+	}
53 53
 
54
-    /**
55
-     * @return SS_Datetime
56
-     */
57
-    public function Created()
58
-    {
59
-        $created = $this->tag->getCommit()->getCommitterDate();
54
+	/**
55
+	 * @return SS_Datetime
56
+	 */
57
+	public function Created()
58
+	{
59
+		$created = $this->tag->getCommit()->getCommitterDate();
60 60
 
61
-        // gitonomy sets the time to UTC, so now we set the timezone to
62
-        // whatever PHP is set to (date.timezone). This will change in the future if each
63
-        // deploynaut user has their own timezone
64
-        $created->setTimezone(new DateTimeZone(date_default_timezone_get()));
61
+		// gitonomy sets the time to UTC, so now we set the timezone to
62
+		// whatever PHP is set to (date.timezone). This will change in the future if each
63
+		// deploynaut user has their own timezone
64
+		$created->setTimezone(new DateTimeZone(date_default_timezone_get()));
65 65
 
66
-        $d = new SS_Datetime();
67
-        $d->setValue($created->format('Y-m-d H:i:s'));
68
-        return $d;
69
-    }
66
+		$d = new SS_Datetime();
67
+		$d->setValue($created->format('Y-m-d H:i:s'));
68
+		return $d;
69
+	}
70 70
 }
Please login to merge, or discard this patch.
code/model/GitonomyCache.php 1 patch
Indentation   +9 added lines, -9 removed lines patch added patch discarded remove patch
@@ -5,14 +5,14 @@
 block discarded – undo
5 5
  */
6 6
 class GitonomyCache
7 7
 {
8
-    public static $cache = array();
8
+	public static $cache = array();
9 9
 
10
-    public static function getIncludingBranches($commit)
11
-    {
12
-        $cacheKey = 'getIncludingBranches-' . $commit->getRepository()->getPath() . '-' . $commit->gethash();
13
-        if (!isset(self::$cache[$cacheKey])) {
14
-            self::$cache[$cacheKey] = $commit->getIncludingBranches();
15
-        }
16
-        return self::$cache[$cacheKey];
17
-    }
10
+	public static function getIncludingBranches($commit)
11
+	{
12
+		$cacheKey = 'getIncludingBranches-' . $commit->getRepository()->getPath() . '-' . $commit->gethash();
13
+		if (!isset(self::$cache[$cacheKey])) {
14
+			self::$cache[$cacheKey] = $commit->getIncludingBranches();
15
+		}
16
+		return self::$cache[$cacheKey];
17
+	}
18 18
 }
Please login to merge, or discard this patch.
code/model/Pipeline.php 1 patch
Indentation   +1079 added lines, -1079 removed lines patch added patch discarded remove patch
@@ -114,1083 +114,1083 @@
 block discarded – undo
114 114
 class Pipeline extends DataObject implements PipelineData
115 115
 {
116 116
 
117
-    /**
118
-     * Messages
119
-     */
120
-    const ALERT_ABORT = 'Abort';
121
-    const ALERT_SUCCESS = 'Success';
122
-    const ALERT_FAILURE = 'Failure';
123
-    const ALERT_ROLLBACK_STARTED = 'RollbackStarted';
124
-    const ALERT_ROLLBACK_SUCCESS = 'RollbackSuccess';
125
-    const ALERT_ROLLBACK_FAILURE = 'RollbackFailure';
126
-
127
-    /**
128
-     * - Status: Current status of this Pipeline. Running means 'currently executing a {@link PipelineStep}'.
129
-     *           See the {@link PipelineControllerTask} class for why this is important.
130
-     * - SHA:    This is the Git SHA that the pipeline is acting on. This is passed into the {@link PipelineStep}
131
-     *           objects so that the steps know what to smoketest, deploy, etc.
132
-     *
133
-     * @var array
134
-     */
135
-    private static $db = array(
136
-        'Status' => 'Enum("Running,Complete,Failed,Aborted,Rollback,Queued", "Queued")',
137
-        'Config' => 'Text', // serialized array of configuration for this pipeline
138
-        'SHA' => 'Varchar(255)',
139
-        'DryRun' => 'Boolean', // Try if this deployment is a test dryrun
140
-        'LastMessageSent' => 'Varchar(255)' // ID of last message sent
141
-    );
142
-
143
-    /**
144
-     * - Author:      The {@link Member} object that started this pipeline running.
145
-     * - Environment: The {@link DNEnvironment} that this Pipeline is associated to.
146
-     * - CurrentStep: The current {@link PipelineStep} object that is keeping this pipeline alive. This should be
147
-     *                cleared when the last step is complete.
148
-     *
149
-     * @var array
150
-     */
151
-    private static $has_one = array(
152
-        'Author' => 'Member',
153
-        'Environment' => 'DNEnvironment',
154
-        'CurrentStep' => 'PipelineStep',
155
-        // to be used for rollbacks
156
-        "PreviousSnapshot" => "DNDataTransfer",
157
-        "PreviousDeployment" => 'DNDeployment',
158
-        "CurrentDeployment" => "DNDeployment",
159
-        "RollbackStep1" => "PipelineStep",
160
-        "RollbackStep2" => "PipelineStep"
161
-    );
162
-
163
-    /**
164
-     * - Steps: These are ordered by the `PipelineStep`.`Order` attribute.
165
-     *
166
-     * @var array
167
-     */
168
-    private static $has_many = array(
169
-        'Steps' => 'PipelineStep'
170
-    );
171
-
172
-    /**
173
-     * @var array
174
-     */
175
-    private static $summary_fields = array(
176
-        'ID' => 'ID',
177
-        'Status' => 'Status',
178
-        'SHA' => 'SHA',
179
-        'Author.Title' => 'Author',
180
-        'CurrentStep.Name' => 'Current Step',
181
-        'Created' => 'Created',
182
-        'LastEdited' => 'Last Updated'
183
-    );
184
-
185
-    /**
186
-     * @var string
187
-     */
188
-    private static $default_sort = '"Created" DESC';
189
-
190
-    /**
191
-     * @var array
192
-     */
193
-    private static $cast = array(
194
-        'RunningDescription' => 'HTMLText'
195
-    );
196
-
197
-    /**
198
-     * @config
199
-     * @var array
200
-     */
201
-    private static $dependencies = array(
202
-        'MessagingService' => '%$ConfirmationMessagingService'
203
-    );
204
-
205
-    /**
206
-     * Currently assigned messaging service
207
-     *
208
-     * @var ConfirmationMessagingService
209
-     */
210
-    private $messagingService = null;
211
-
212
-    /**
213
-     * @param ConfirmationMessagingService $service
214
-     */
215
-    public function setMessagingService(ConfirmationMessagingService $service)
216
-    {
217
-        $this->messagingService = $service;
218
-    }
219
-
220
-    /**
221
-     * @return ConfirmationMessagingService
222
-     */
223
-    public function getMessagingService()
224
-    {
225
-        return $this->messagingService;
226
-    }
227
-
228
-    public function __isset($property)
229
-    {
230
-        // Workaround fixed in https://github.com/silverstripe/silverstripe-framework/pull/3201
231
-        // Remove this once we update to a version of framework which supports this
232
-        if ($property === 'MessagingService') {
233
-            return !empty($this->messagingService);
234
-        }
235
-        return parent::__isset($property);
236
-    }
237
-
238
-    /**
239
-     * Retrieve message template replacements
240
-     *
241
-     * @return array
242
-     */
243
-    public function getReplacements()
244
-    {
245
-        // Get member who began this request
246
-        $author = $this->Author();
247
-        $environment = $this->Environment();
248
-        return array(
249
-            '<abortlink>' => Director::absoluteURL($this->Environment()->Link()),
250
-            '<pipelinelink>' => Director::absoluteURL($this->Link()),
251
-            '<requester>' => $author->Title,
252
-            '<requester-email>' => $author->Email,
253
-            '<environment>' => $environment->Name,
254
-            '<project>' => $environment->Project()->Name,
255
-            '<commitsha>' => $this->SHA
256
-        );
257
-    }
258
-
259
-    /**
260
-     * Title of this step
261
-     *
262
-     * @return string
263
-     */
264
-    public function getTitle()
265
-    {
266
-        return "Pipeline {$this->ID} (Status: {$this->Status})";
267
-    }
268
-
269
-    /**
270
-     * @param Member $member
271
-     */
272
-    public function canAbort($member = null)
273
-    {
274
-        // Owner can abort
275
-        $member = $member ?: Member::currentUser();
276
-        if (!$member) {
277
-            return false;
278
-        }
279
-        if ($member->ID == $this->AuthorID) {
280
-            return true;
281
-        }
282
-
283
-        // Check environment permission
284
-        return $this->Environment()->canAbort($member);
285
-    }
286
-
287
-    /**
288
-     * Get status of currently running step
289
-     *
290
-     * @return string Status description (html format)
291
-     */
292
-    public function getRunningDescription()
293
-    {
294
-        if (!$this->isActive()) {
295
-            return 'This pipeline is not currently running';
296
-        }
297
-        $result = '';
298
-        if ($step = $this->CurrentStep()) {
299
-            $result = $step->getRunningDescription();
300
-        }
301
-        return $result ?: 'This pipeline is currently running';
302
-    }
303
-
304
-    /**
305
-     * Get options for the currently running pipeline, if and only if it is currently running
306
-     *
307
-     * @return ArrayList List of items with a Link and Title attribute
308
-     */
309
-    public function RunningOptions()
310
-    {
311
-        if (!$this->isActive()) {
312
-            return null;
313
-        }
314
-        $actions = array();
315
-
316
-        // Let current step update the current list of options
317
-        if (($step = $this->CurrentStep()) && ($step->isRunning())) {
318
-            $actions = $step->allowedActions();
319
-        }
320
-        return new ArrayList($actions);
321
-    }
322
-
323
-    /**
324
-     * Get possible logs for the currently pipeline
325
-     *
326
-     * @return ArrayList List of logs with a Link and Title attribute
327
-     */
328
-    public function LogOptions()
329
-    {
330
-        if (!$this->isActive()) {
331
-            return null;
332
-        }
333
-
334
-        $logs = array();
335
-
336
-        $logs[] = array(
337
-            'ButtonText' => 'Pipeline Log',
338
-            'Link' => $this->Link()
339
-        );
340
-
341
-        if ($this->PreviousSnapshotID > 0) {
342
-            $logs[] = array(
343
-                'ButtonText' => 'Snapshot Log',
344
-                'Link' => $this->PreviousSnapshot()->Link()
345
-            );
346
-        }
347
-
348
-        if ($this->CurrentDeploymentID > 0) {
349
-            $logs[] = array(
350
-                'ButtonText' => 'Deployment Log',
351
-                'Link' => $this->CurrentDeployment()->Link()
352
-            );
353
-        }
354
-
355
-        // Get logs from rollback steps (only for RollbackSteps).
356
-        $rollbackSteps = array($this->RollbackStep1(), $this->RollbackStep2());
357
-        foreach ($rollbackSteps as $rollback) {
358
-            if ($rollback->exists() && $rollback->ClassName == 'RollbackStep') {
359
-                if ($rollback->RollbackDeploymentID > 0) {
360
-                    $logs[] = array(
361
-                        'ButtonText' => 'Rollback Log',
362
-                        'Link' => $rollback->RollbackDeployment()->Link()
363
-                    );
364
-                }
365
-
366
-                if ($rollback->RollbackDatabaseID > 0) {
367
-                    $logs[] = array(
368
-                        'ButtonText' => 'Rollback DB Log',
369
-                        'Link' => $rollback->RollbackDatabase()->Link()
370
-                    );
371
-                }
372
-            }
373
-        }
374
-
375
-        return new ArrayList($logs);
376
-    }
377
-
378
-    /**
379
-     * Cached of config merged with defaults
380
-     *
381
-     * @var array|null
382
-     */
383
-    protected $mergedConfig;
384
-
385
-    /**
386
-     * Get this pipeline configuration. If the configuration has been serialized
387
-     * and saved into the Config field, it'll use that. If that field is empty,
388
-     * it'll read the YAML file directly and return that instead.
389
-     *
390
-     * @return array
391
-     * @throws Exception
392
-     */
393
-    public function getConfigData()
394
-    {
395
-        // Lazy load if necessary
396
-        $data = null;
397
-        if (!$this->Config && ($data = $this->Environment()->loadPipelineConfig())) {
398
-            $this->Config = serialize($data);
399
-        }
400
-
401
-        // Merge with defaults
402
-        if ($this->Config) {
403
-            if (!$this->mergedConfig) {
404
-                $this->mergedConfig = $data ?: unserialize($this->Config);
405
-                if ($default = self::config()->default_config) {
406
-                    Config::merge_array_low_into_high($this->mergedConfig, $default);
407
-                }
408
-            }
409
-            return $this->mergedConfig;
410
-        }
411
-
412
-        // Fail if no data available
413
-        $path = $this->Environment()->getPipelineFilename();
414
-        throw new Exception(sprintf('YAML configuration for pipeline not found at path "%s"', $path));
415
-    }
416
-
417
-    public function setConfig($data)
418
-    {
419
-        $this->mergedConfig = null;
420
-        return parent::setField('Config', $data);
421
-    }
422
-
423
-    /**
424
-     * Retrieve the value of a specific config setting
425
-     *
426
-     * @param string $setting Settings
427
-     * @return mixed Value of setting, or null if not set
428
-     */
429
-    public function getConfigSetting($setting)
430
-    {
431
-        $source = $this->getConfigData();
432
-
433
-        foreach (func_get_args() as $setting) {
434
-            if (empty($source[$setting])) {
435
-                return null;
436
-            }
437
-            $source = $source[$setting];
438
-        }
439
-
440
-        return $source;
441
-    }
442
-
443
-    /**
444
-     * @return FieldList
445
-     */
446
-    public function getCMSFields()
447
-    {
448
-        $fields = new FieldList(new TabSet('Root'));
449
-
450
-        // Main fields
451
-        $fields->addFieldsToTab('Root.Main', array(
452
-            TextField::create('SHA')
453
-                ->setDescription('SHA of the commit this pipeline is running against')
454
-                ->performReadonlyTransformation(),
455
-            TextField::create('AuthorName', 'Author', ($author = $this->Author()) ? $author->Title : null)
456
-                ->setDescription('Person who initiated this pipeline')
457
-                ->performReadonlyTransformation(),
458
-            DropdownField::create('Status', 'Status', $this->dbObject('Status')->enumValues()),
459
-            DropdownField::create('CurrentStepID', 'Current Step', $this->Steps()->map('ID', 'TreeTitle')),
460
-            TextField::create(
461
-                'CurrentDeployment_Label',
462
-                'Current Deployment',
463
-                $this->CurrentDeployment()->getTitle()
464
-            )    ->setDescription('Deployment generated by this pipeline')
465
-                ->performReadonlyTransformation(),
466
-        ));
467
-
468
-        // Backup fields
469
-        $fields->addFieldsToTab('Root.Backups', array(
470
-            TextField::create(
471
-                'PreviousDeployment_Label',
472
-                'Previous Deployment',
473
-                $this->PreviousDeployment()->getTitle()
474
-            )    ->setDescription('Prior deployment to revert to if this pipeline fails')
475
-                ->performReadonlyTransformation(),
476
-            TextField::create(
477
-                'PreviousSnapshot_Label',
478
-                'Previous DB Snapshot',
479
-                $this->PreviousSnapshot()->getTitle()
480
-            )    ->setDescription('Database backup to revert to if this pipeline fails')
481
-                ->performReadonlyTransformation()
482
-        ));
483
-
484
-        if ($log = $this->LogContent()) {
485
-            $fields->addFieldToTab(
486
-                'Root.Main',
487
-                ToggleCompositeField::create(
488
-                    'PipelineLog',
489
-                    'Pipeline Log',
490
-                    LiteralField::create('LogText', nl2br(Convert::raw2xml($log)))
491
-                )
492
-            );
493
-        }
494
-
495
-        // Steps
496
-        $stepConfig = GridFieldConfig_RecordEditor::create();
497
-        $steps = GridField::create('Steps', 'Pipeline Steps', $this->Steps(), $stepConfig);
498
-        $fields->addFieldsToTab('Root.PipelineSteps', $steps);
499
-
500
-        return $fields;
501
-    }
502
-
503
-    /**
504
-     * Return a dependent {@link DNEnvironment} based on this pipeline's dependent environment configuration.
505
-     *
506
-     * @return DNEnvironment
507
-     */
508
-    public function getDependentEnvironment()
509
-    {
510
-        // dependent environment not available
511
-        $projectName = $this->getConfigSetting('PipelineConfig', 'DependsOnProject');
512
-        $environmentName = $this->getConfigSetting('PipelineConfig', 'DependsOnEnvironment');
513
-        if (empty($projectName) || empty($environmentName)) {
514
-            return null;
515
-        }
516
-
517
-        $project = DNProject::get()->filter('Name', $projectName)->first();
518
-        if (!($project && $project->exists())) {
519
-            throw new Exception(sprintf('Could not find dependent project "%s"', $projectName));
520
-        }
521
-
522
-        $environment = DNEnvironment::get()->filter(array(
523
-            'ProjectID' => $project->ID,
524
-            'Name' => $environmentName
525
-        ))->first();
526
-
527
-        if (!($environment && $environment->exists())) {
528
-            throw new Exception(sprintf(
529
-                'Could not find dependent environment "%s" in project "%s"',
530
-                $environmentName,
531
-                $projectName
532
-            ));
533
-        }
534
-
535
-        return $environment;
536
-    }
537
-
538
-    /**
539
-     * Generate a step from a name, config, and sort order
540
-     *
541
-     * @throws Exception
542
-     * @param string $name
543
-     * @param array $stepConfig
544
-     * @param int $order
545
-     * @return PipelineStep
546
-     */
547
-    protected function generateStep($name, $stepConfig, $order = 0)
548
-    {
549
-        $stepClass = isset($stepConfig['Class']) ? $stepConfig['Class'] : $stepConfig;
550
-
551
-        if (empty($stepClass)) {
552
-            throw new Exception(
553
-                sprintf('Missing or empty Class specifier for step "%s"', $name)
554
-            );
555
-        }
556
-
557
-        if (!is_subclass_of($stepClass, 'PipelineStep')) {
558
-            throw new Exception(
559
-                sprintf('%s is not a valid "Class" field name for step "%s"', var_export($stepClass, true), $name)
560
-            );
561
-        }
562
-
563
-        $step = $stepClass::create();
564
-        $step->Name = $name;
565
-        $step->PipelineID = $this->ID;
566
-        $step->Order = $order;
567
-        $step->Status = 'Queued';
568
-        $step->Config = serialize($stepConfig);
569
-        $step->write();
570
-
571
-        return $step;
572
-    }
573
-
574
-    /**
575
-     * Starts the pipeline process.
576
-     *
577
-     * Reads a YAML configuration from the linked {@link DNEnvironment}
578
-     * and builds the {@link PipelineStep} objects and runs them.
579
-     *
580
-     * Note that this method doesn't actually start any {@link PipelineStep} objects, that is handled by
581
-     * {@link self::checkPipelineStatus()}, and the daemon running the process.
582
-     *
583
-     * @throws LogicException
584
-     * @return boolean
585
-     */
586
-    public function start()
587
-    {
588
-        // Ensure there are no other running {@link Pipeline} objects for this {@link DNEnvironment}
589
-        // Requires that $this->EnvironmentID has been set
590
-        $env = $this->Environment();
591
-        if (!($env && $env->exists())) {
592
-            throw new LogicException("This pipeline needs a valid environment to run on.");
593
-        }
594
-
595
-        if ($env->HasCurrentPipeline()) {
596
-            throw new LogicException("You can only run one pipeline at a time on this environment.");
597
-        }
598
-
599
-        $this->write(); // ensure we've written this record first
600
-
601
-        // Instantiate steps.
602
-        foreach ($this->getConfigSetting('Steps') as $name => $stepConfig) {
603
-            $this->pushPipelineStep($name, $stepConfig);
604
-        }
605
-
606
-        $this->Status = 'Running';
607
-        $this->write();
608
-
609
-        $this->log('Started logging for this pipeline!');
610
-
611
-        return true;
612
-    }
613
-
614
-    /**
615
-     * Mark this Pipeline as completed.
616
-     */
617
-    public function markComplete()
618
-    {
619
-        $this->Status = "Complete";
620
-        $this->log("Pipeline completed successfully.");
621
-        $this->write();
622
-        // Some steps may pre-emptively send a success message before the pipeline itself has completed
623
-        if ($this->LastMessageSent !== self::ALERT_SUCCESS) {
624
-            $this->sendMessage(self::ALERT_SUCCESS);
625
-        }
626
-    }
627
-
628
-    /**
629
-     * @return bool true if this Pipeline has successfully completed all {@link PipelineStep} steps already.
630
-     */
631
-    public function isComplete()
632
-    {
633
-        return $this->Status == "Complete";
634
-    }
635
-
636
-    /**
637
-     * True if the pipeline is running but NOT doing a rollback
638
-     *
639
-     * @return bool
640
-     */
641
-    public function isRunning()
642
-    {
643
-        return $this->Status == "Running";
644
-    }
645
-
646
-    /**
647
-     * True if the pipeline is running or doing a rollback
648
-     *
649
-     * @return bool
650
-     */
651
-    public function isActive()
652
-    {
653
-        return $this->isRunning() || $this->isRollback();
654
-    }
655
-
656
-    /**
657
-     * Push a step to the end of a pipeline
658
-     *
659
-     * @param string $name
660
-     * @param array $stepConfig
661
-     * @return PipelineStep
662
-     */
663
-    private function pushPipelineStep($name, $stepConfig)
664
-    {
665
-        $lastStep = $this->Steps()->sort("Order DESC")->first();
666
-        $order = $lastStep ? $lastStep->Order + 1 : 1;
667
-        return $this->generateStep($name, $stepConfig, $order);
668
-    }
669
-
670
-    /**
671
-     * The rollback has finished - close the pipeline and send relevant messages.
672
-     */
673
-    protected function finaliseRollback()
674
-    {
675
-
676
-        // Figure out the status by inspecting specific rollback steps.
677
-        $success = true;
678
-        $rollback1 = $this->RollbackStep1();
679
-        $rollback2 = $this->RollbackStep2();
680
-        if (!empty($rollback1) && $rollback1->Status == 'Failed') {
681
-            $success = false;
682
-        }
683
-        if (!empty($rollback2) && $rollback2->Status == 'Failed') {
684
-            $success = false;
685
-        }
686
-
687
-        // Send messages.
688
-        if ($success) {
689
-            $this->log("Pipeline failed, but rollback completed successfully.");
690
-            $this->sendMessage(self::ALERT_ROLLBACK_SUCCESS);
691
-        } else {
692
-            $this->log("Pipeline failed, rollback failed.");
693
-            $this->sendMessage(self::ALERT_ROLLBACK_FAILURE);
694
-        }
695
-
696
-        // Finish off the pipeline - rollback will only be triggered on a failed pipeline.
697
-        $this->Status = 'Failed';
698
-        $this->write();
699
-    }
700
-
701
-    /**
702
-     * Initiate a rollback. Moves the pipeline to the 'Rollback' status.
703
-     */
704
-    protected function beginRollback()
705
-    {
706
-        $this->log("Beginning rollback...");
707
-        $this->sendMessage(self::ALERT_ROLLBACK_STARTED);
708
-
709
-        // Add rollback step.
710
-        $configRollback1 = $this->getConfigSetting('RollbackStep1');
711
-        $stepRollback1 = $this->pushPipelineStep('RollbackStep1', $configRollback1);
712
-        $this->RollbackStep1ID = $stepRollback1->ID;
713
-        $this->CurrentStepID = $stepRollback1->ID;
714
-        $this->Status = 'Rollback';
715
-
716
-        // Add smoke test step, if available, for later processing.
717
-        $configRollback2 = $this->getConfigSetting('RollbackStep2');
718
-        if ($configRollback2) {
719
-            $stepRollback2 = $this->pushPipelineStep('RollbackStep2', $configRollback2);
720
-            $this->RollbackStep2ID = $stepRollback2->ID;
721
-        }
722
-
723
-        $this->write();
724
-
725
-        $stepRollback1->start();
726
-    }
727
-
728
-    /**
729
-     * Check if pipeline currently permits a rollback.
730
-     * This could be influenced by both the current state and by the specific configuration.
731
-     *
732
-     * @return boolean
733
-     */
734
-    protected function canStartRollback()
735
-    {
736
-        // The rollback cannot run twice.
737
-        if ($this->isRollback()) {
738
-            return false;
739
-        }
740
-
741
-        // Rollbacks must be configured.
742
-        if (!$this->getConfigSetting('RollbackStep1')) {
743
-            return false;
744
-        }
745
-
746
-        // On dryrun let rollback run
747
-        if ($this->DryRun) {
748
-            return true;
749
-        }
750
-
751
-        // Pipeline must have ran a deployment to be able to rollback.
752
-        $deploy = $this->CurrentDeployment();
753
-        $previous = $this->PreviousDeployment();
754
-        if (!$deploy->exists() || !$previous->exists()) {
755
-            return false;
756
-        }
757
-
758
-        return true;
759
-    }
760
-
761
-    /**
762
-     * Notify Pipeline that a step has failed and failure processing should kick in. If rollback steps are present
763
-     * the pipeline will be put into 'Rollback' state. After rollback is complete, regardless of the rollback result,
764
-     * the pipeline will be failed.
765
-     *
766
-     * @param bool $notify Set to false to disable notifications for this failure
767
-     */
768
-    public function markFailed($notify = true)
769
-    {
770
-        // Abort all running or queued steps.
771
-        $steps = $this->Steps();
772
-        foreach ($steps as $step) {
773
-            if ($step->isQueued() || $step->isRunning()) {
774
-                $step->abort();
775
-            }
776
-        }
777
-
778
-        if ($this->canStartRollback()) {
779
-            $this->beginRollback();
780
-        } elseif ($this->isRollback()) {
781
-            $this->finaliseRollback();
782
-        } else {
783
-            // Not able to roll back - fail immediately.
784
-            $this->Status = 'Failed';
785
-            $this->log("Pipeline failed, not running rollback (not configured or not applicable yet).");
786
-            $this->write();
787
-            if ($notify) {
788
-                $this->sendMessage(self::ALERT_FAILURE);
789
-            }
790
-        }
791
-    }
792
-
793
-    /**
794
-     * @return bool true if this Pipeline failed to execute all {@link PipelineStep} steps successfully
795
-     */
796
-    public function isFailed()
797
-    {
798
-        return $this->Status == "Failed";
799
-    }
800
-
801
-    /**
802
-     * @return bool true if this Pipeline is rolling back.
803
-     */
804
-    public function isRollback()
805
-    {
806
-        return $this->Status == "Rollback";
807
-    }
808
-
809
-    /**
810
-     * Mark this Pipeline as aborted
811
-     */
812
-    public function markAborted()
813
-    {
814
-        $this->Status = 'Aborted';
815
-        $logMessage = sprintf(
816
-            "Pipeline processing aborted. %s (%s) aborted the pipeline",
817
-            Member::currentUser()->Name,
818
-            Member::currentUser()->Email
819
-        );
820
-        $this->log($logMessage);
821
-        $this->write();
822
-
823
-        // Abort all running or queued steps.
824
-        $steps = $this->Steps();
825
-        foreach ($steps as $step) {
826
-            if ($step->isQueued() || $step->isRunning()) {
827
-                $step->abort();
828
-            }
829
-        }
830
-
831
-        // Send notification to users about this event
832
-        $this->sendMessage(self::ALERT_ABORT);
833
-    }
834
-
835
-    /**
836
-     * Finds a message template for a given role and message
837
-     *
838
-     * @param string $messageID Message ID
839
-     * @return array Resulting array(subject, message)
840
-     */
841
-    protected function generateMessageTemplate($messageID)
842
-    {
843
-        $subject = $this->getConfigSetting('PipelineConfig', 'Subjects', $messageID);
844
-        $message = $this->getConfigSetting('PipelineConfig', 'Messages', $messageID);
845
-        $substitutions = $this->getReplacements();
846
-        return $this->injectMessageReplacements($message, $subject, $substitutions);
847
-    }
848
-
849
-    /**
850
-     * Substitute templated variables into the given message and subject
851
-     *
852
-     * @param string $message
853
-     * @param string $subject
854
-     * @param array $substitutions
855
-     * @return array Resulting array(subject, message)
856
-     */
857
-    public function injectMessageReplacements($message, $subject, $substitutions)
858
-    {
859
-        // Handle empty messages
860
-        if (empty($subject) && empty($message)) {
861
-            return array(null, null);
862
-        }
863
-
864
-        // Check if there's a role specific message
865
-        $subjectText = str_replace(
866
-            array_keys($substitutions),
867
-            array_values($substitutions),
868
-            $subject ?: $message
869
-        );
870
-        $messageText = str_replace(
871
-            array_keys($substitutions),
872
-            array_values($substitutions),
873
-            $message ?: $subject
874
-        );
875
-
876
-
877
-        return array($subjectText, $messageText);
878
-    }
879
-
880
-    /**
881
-     * Sends a specific message to all marked recipients, including the author of this pipeline
882
-     *
883
-     * @param string $messageID Message ID. One of 'Abort', 'Success', or 'Failure', or some custom message
884
-     * @return boolean|null True if successful
885
-     */
886
-    public function sendMessage($messageID)
887
-    {
888
-        // Check message, subject, and additional arguments to include
889
-        list($subject, $message) = $this->generateMessageTemplate($messageID);
890
-        if (empty($subject) || empty($message)) {
891
-            $this->log("Skipping sending message. None configured for $messageID");
892
-            return true;
893
-        }
894
-
895
-        // Save last sent message
896
-        $this->LastMessageSent = $messageID;
897
-        $this->write();
898
-
899
-        // Setup messaging arguments
900
-        $arguments = array_merge(
901
-            $this->getConfigSetting('PipelineConfig', 'ServiceArguments') ?: array(),
902
-            array('subject' => $subject)
903
-        );
904
-
905
-        // Send message to author
906
-        if ($author = $this->Author()) {
907
-            $this->log("Pipeline sending $messageID message to {$author->Email}");
908
-            $this->messagingService->sendMessage($this, $message, $author, $arguments);
909
-        } else {
910
-            $this->log("Skipping sending message to missing author");
911
-        }
912
-
913
-        // Get additional recipients
914
-        $recipients = $this->getConfigSetting('PipelineConfig', 'Recipients', $messageID);
915
-        if (empty($recipients)) {
916
-            $this->log("Skipping sending message to empty recipients");
917
-        } else {
918
-            $recipientsStr = is_array($recipients) ? implode(',', $recipients) : $recipients;
919
-            $this->log("Pipeline sending $messageID message to $recipientsStr");
920
-            $this->messagingService->sendMessage($this, $message, $recipients, $arguments);
921
-        }
922
-    }
923
-
924
-    /**
925
-     * @return bool true if this Pipeline has been aborted
926
-     */
927
-    public function isAborted()
928
-    {
929
-        return $this->Status === "Aborted";
930
-    }
931
-
932
-    /**
933
-     * This method should be called only by the {@link CheckPipelineStatus} controller. It iterates through all the
934
-     * {@link PipelineStep} objects associated with this Pipeline, and finds a place where the pipeline has stalled
935
-     * (where one step has completed, but the next one has yet to start). It will then start the next step if required.
936
-     *
937
-     * We check here whether the {@link PipelineStep} finished successfully, and will mark the Pipeline as Failed if
938
-     * the step failed, but this is only a fallback, and should not be relied upon. The individual {@link PipelineStep}
939
-     * should mark itself as failed and then call {@link Pipeline::markFailed()} directly.
940
-     *
941
-     * If the Pipeline has run out of steps, then it will mark the pipeline as completed.
942
-     */
943
-    public function checkPipelineStatus()
944
-    {
945
-        $message = "";
946
-
947
-        if (!$this->isActive()) {
948
-            $message = "Pipeline::checkPipelineStatus() should only be called on running or rolling back pipelines.";
949
-        }
950
-
951
-        if (!$this->ID || !$this->isInDB()) {
952
-            $message = "Pipeline::checkPipelineStatus() can only be called on pipelines already saved.";
953
-        }
954
-
955
-        $currentStep = ($this->CurrentStep() && $this->CurrentStep()->isInDB())
956
-            ? $this->CurrentStep()
957
-            : null;
958
-
959
-        if ($currentStep && $currentStep->PipelineID != $this->ID) {
960
-            $message = sprintf(
961
-                "The current step (#%d) has a pipeline ID (#%d) that doesn't match this pipeline's ID (#%d).",
962
-                $currentStep->ID,
963
-                $currentStep->PipelineID,
964
-                $this->ID
965
-            );
966
-        }
967
-
968
-        if ($message) {
969
-            $this->log($message);
970
-            throw new LogicException($message);
971
-        }
972
-
973
-        // Fallback check only: this shouldn't be called unless a {@link PipelineStep} has been implemented incorrectly
974
-        if ($currentStep && $currentStep->isFailed() && !$this->isFailed() && !$this->isRollback()) {
975
-            $this->log(sprintf("Marking pipeline step (#%d) as failed - this pipeline step needs to be amended to mark"
976
-                . " the pipeline (as well as itself) as failed to ensure consistency.",
977
-                $this->CurrentStep()->ID
978
-            ));
979
-
980
-            $this->markFailed();
981
-            return;
982
-        }
983
-
984
-        // If this is the first time the Pipeline is run, then we don't have a CurrentStep, so set it,
985
-        // start it running, and return
986
-        if (!$currentStep) {
987
-            $step = $this->Steps()->first();
988
-            $this->CurrentStepID = $step->ID;
989
-            $this->write();
990
-
991
-            $this->log("Starting first pipeline step...");
992
-            $step->start();
993
-        } elseif ($currentStep->isFinished()) {
994
-            // Sort through the list of {@link PipelineStep} objects to find the next step we need to start.
995
-            $this->log("Finding next step to execute...");
996
-            $nextStep = $this->findNextStep();
997
-
998
-            if (!$nextStep) {
999
-
1000
-                // Special handling, since the main pipeline has already failed at this stage.
1001
-                if ($this->isRollback()) {
1002
-                    $this->finaliseRollback();
1003
-                    return false;
1004
-                }
1005
-
1006
-                // Double check for any steps that failed, but didn't notify the pipeline via markFailed.
1007
-                $failedSteps = PipelineStep::get()->filter(array(
1008
-                    'PipelineID' => $this->ID,
1009
-                    'Status' => 'Failed'
1010
-                ))->count();
1011
-                if ($failedSteps) {
1012
-                    $this->log('At least one of the steps has failed marking the pipeline as failed');
1013
-                    $this->markFailed();
1014
-                    return false;
1015
-                }
1016
-
1017
-                // We've reached the end of this pipeline successfully!
1018
-                $this->markComplete();
1019
-                return;
1020
-            } else {
1021
-                $this->CurrentStepID = $nextStep->ID;
1022
-                $this->write();
1023
-                // Otherwise, kick off the next step
1024
-                $this->log(sprintf("Found the next step (#%s), starting it now...", $nextStep->Name));
1025
-                $nextStep->start();
1026
-            }
1027
-        // if the current step is failing run it again
1028
-        } elseif ($step = $this->CurrentStep()) {
1029
-            $step->start();
1030
-        }
1031
-    }
1032
-
1033
-    /**
1034
-     * Finds the next {@link PipelineStep} that needs to execute. Relies on $this->CurrentStep() being a valid step.
1035
-     *
1036
-     * @return DataObject|null The next step in the pipeline, or null if none remain.
1037
-     */
1038
-    protected function findNextStep()
1039
-    {
1040
-        // otherwise get next step in chain
1041
-        $currentStep = $this->CurrentStep();
1042
-
1043
-        return $this
1044
-            ->Steps()
1045
-            ->filter("Status", "Queued")
1046
-            ->filter("Order:GreaterThanOrEqual", $currentStep->Order)
1047
-            ->exclude("ID", $currentStep->ID)
1048
-            ->sort("Order ASC")
1049
-            ->first();
1050
-    }
1051
-
1052
-    /**
1053
-     * Finds the previous {@link PipelineStep} that executed. Relies on $this->CurrentStep() being a valid step.
1054
-     *
1055
-     * @return DataObject|null The previous step in the pipeline, or null if this is the first.
1056
-     */
1057
-    public function findPreviousStep()
1058
-    {
1059
-        // otherwise get previous step in chain
1060
-        $currentStep = $this->CurrentStep();
1061
-
1062
-        return $this
1063
-            ->Steps()
1064
-            ->filter("Status", "Finished")
1065
-            ->filter("Order:LessThanOrEqual", $currentStep->Order)
1066
-            ->exclude("ID", $currentStep->ID)
1067
-            ->sort("Order DESC")
1068
-            ->first();
1069
-    }
1070
-
1071
-    /**
1072
-     * Write to a common log file. This log file will be the same regardless of how often this pipeline is re-created
1073
-     * from the database. To this end, it needs to know the database ID of this pipeline instance, so that it can
1074
-     * generate the correct filename to open.
1075
-     *
1076
-     * This also includes the calling class and method name that called ->log() in the first place, so we can trace
1077
-     * back where it was written from.
1078
-     *
1079
-     * @param string $message The message to log
1080
-     * @throws LogicException Thrown if we can't log yet because we don't know what to log to (no db record yet).
1081
-     */
1082
-    public function log($message = "")
1083
-    {
1084
-        $log = $this->getLogger();
1085
-
1086
-        // Taken from Debug::caller(), amended for our purposes to filter out the intermediate call to
1087
-        // PipelineStep->log(), so that our log message shows where the log message was actually created from.
1088
-        $bt = debug_backtrace();
1089
-
1090
-        $index = ($bt[1]['class'] == 'PipelineStep') ? 2 : 1;
1091
-
1092
-        $caller = $bt[$index];
1093
-        $caller['line'] = $bt[($index - 1)]['line']; // Overwrite line and file to be the the line/file that actually
1094
-        $caller['file'] = $bt[($index - 1)]['file']; // called the function, not where the function is defined.
1095
-        // In case it wasn't called from a class
1096
-        if (!isset($caller['class'])) {
1097
-            $caller['class'] = '';
1098
-        }
1099
-        // In case it doesn't have a type (wasn't called from class)
1100
-        if (!isset($caller['type'])) {
1101
-            $caller['type'] = '';
1102
-        }
1103
-
1104
-        $log->write(sprintf(
1105
-            "[%s::%s() (line %d)] %s",
1106
-            $caller['class'],
1107
-            $caller['function'],
1108
-            $caller['line'],
1109
-            $message
1110
-        ));
1111
-    }
1112
-
1113
-    /**
1114
-     * Returns the {@link DeploynautLogFile} instance that will actually write to this log file.
1115
-     *
1116
-     * @return DeploynautLogFile
1117
-     * @throws RuntimeException
1118
-     */
1119
-    public function getLogger()
1120
-    {
1121
-        if (!$this->isInDB()) {
1122
-            throw new RuntimeException("Can't write to a log file until we know the database ID.");
1123
-        }
1124
-
1125
-        if (!$this->Environment()) {
1126
-            throw new RuntimeException("Can't write to a log file until we have an Environment.");
1127
-        }
1128
-
1129
-        if ($this->Environment() && !$this->Environment()->Project()) {
1130
-            throw new RuntimeException("Can't write to a log file until we have the Environment's project.");
1131
-        }
1132
-
1133
-        $environment = $this->Environment();
1134
-        $filename = sprintf('%s.pipeline.%d.log', $environment->getFullName('.'), $this->ID);
1135
-
1136
-        return Injector::inst()->createWithArgs('DeploynautLogFile', array($filename));
1137
-    }
1138
-
1139
-    /**
1140
-     * @return bool
1141
-     */
1142
-    public function getDryRun()
1143
-    {
1144
-        return $this->getField('DryRun');
1145
-    }
1146
-
1147
-    /**
1148
-     * @param string|null $action
1149
-     *
1150
-     * @return string
1151
-     */
1152
-    public function Link($action = null)
1153
-    {
1154
-        return Controller::join_links($this->Environment()->Link(), 'pipeline', $this->ID, $action);
1155
-    }
1156
-
1157
-    /**
1158
-     * Link to an action on the current step
1159
-     *
1160
-     * @param string|null $action
1161
-     * @return string
1162
-     */
1163
-    public function StepLink($action = null)
1164
-    {
1165
-        return Controller::join_links($this->Link('step'), $action);
1166
-    }
1167
-
1168
-    /**
1169
-     * @return string
1170
-     */
1171
-    public function AbortLink()
1172
-    {
1173
-        return $this->Link('abort');
1174
-    }
1175
-
1176
-    /**
1177
-     * @return string
1178
-     */
1179
-    public function LogLink()
1180
-    {
1181
-        return $this->Link('log');
1182
-    }
1183
-
1184
-    /**
1185
-     * @return string
1186
-     */
1187
-    public function LogContent()
1188
-    {
1189
-        if ($this->exists() && $this->Environment()) {
1190
-            $logger = $this->getLogger();
1191
-            if ($logger->exists()) {
1192
-                return $logger->content();
1193
-            }
1194
-        }
1195
-    }
117
+	/**
118
+	 * Messages
119
+	 */
120
+	const ALERT_ABORT = 'Abort';
121
+	const ALERT_SUCCESS = 'Success';
122
+	const ALERT_FAILURE = 'Failure';
123
+	const ALERT_ROLLBACK_STARTED = 'RollbackStarted';
124
+	const ALERT_ROLLBACK_SUCCESS = 'RollbackSuccess';
125
+	const ALERT_ROLLBACK_FAILURE = 'RollbackFailure';
126
+
127
+	/**
128
+	 * - Status: Current status of this Pipeline. Running means 'currently executing a {@link PipelineStep}'.
129
+	 *           See the {@link PipelineControllerTask} class for why this is important.
130
+	 * - SHA:    This is the Git SHA that the pipeline is acting on. This is passed into the {@link PipelineStep}
131
+	 *           objects so that the steps know what to smoketest, deploy, etc.
132
+	 *
133
+	 * @var array
134
+	 */
135
+	private static $db = array(
136
+		'Status' => 'Enum("Running,Complete,Failed,Aborted,Rollback,Queued", "Queued")',
137
+		'Config' => 'Text', // serialized array of configuration for this pipeline
138
+		'SHA' => 'Varchar(255)',
139
+		'DryRun' => 'Boolean', // Try if this deployment is a test dryrun
140
+		'LastMessageSent' => 'Varchar(255)' // ID of last message sent
141
+	);
142
+
143
+	/**
144
+	 * - Author:      The {@link Member} object that started this pipeline running.
145
+	 * - Environment: The {@link DNEnvironment} that this Pipeline is associated to.
146
+	 * - CurrentStep: The current {@link PipelineStep} object that is keeping this pipeline alive. This should be
147
+	 *                cleared when the last step is complete.
148
+	 *
149
+	 * @var array
150
+	 */
151
+	private static $has_one = array(
152
+		'Author' => 'Member',
153
+		'Environment' => 'DNEnvironment',
154
+		'CurrentStep' => 'PipelineStep',
155
+		// to be used for rollbacks
156
+		"PreviousSnapshot" => "DNDataTransfer",
157
+		"PreviousDeployment" => 'DNDeployment',
158
+		"CurrentDeployment" => "DNDeployment",
159
+		"RollbackStep1" => "PipelineStep",
160
+		"RollbackStep2" => "PipelineStep"
161
+	);
162
+
163
+	/**
164
+	 * - Steps: These are ordered by the `PipelineStep`.`Order` attribute.
165
+	 *
166
+	 * @var array
167
+	 */
168
+	private static $has_many = array(
169
+		'Steps' => 'PipelineStep'
170
+	);
171
+
172
+	/**
173
+	 * @var array
174
+	 */
175
+	private static $summary_fields = array(
176
+		'ID' => 'ID',
177
+		'Status' => 'Status',
178
+		'SHA' => 'SHA',
179
+		'Author.Title' => 'Author',
180
+		'CurrentStep.Name' => 'Current Step',
181
+		'Created' => 'Created',
182
+		'LastEdited' => 'Last Updated'
183
+	);
184
+
185
+	/**
186
+	 * @var string
187
+	 */
188
+	private static $default_sort = '"Created" DESC';
189
+
190
+	/**
191
+	 * @var array
192
+	 */
193
+	private static $cast = array(
194
+		'RunningDescription' => 'HTMLText'
195
+	);
196
+
197
+	/**
198
+	 * @config
199
+	 * @var array
200
+	 */
201
+	private static $dependencies = array(
202
+		'MessagingService' => '%$ConfirmationMessagingService'
203
+	);
204
+
205
+	/**
206
+	 * Currently assigned messaging service
207
+	 *
208
+	 * @var ConfirmationMessagingService
209
+	 */
210
+	private $messagingService = null;
211
+
212
+	/**
213
+	 * @param ConfirmationMessagingService $service
214
+	 */
215
+	public function setMessagingService(ConfirmationMessagingService $service)
216
+	{
217
+		$this->messagingService = $service;
218
+	}
219
+
220
+	/**
221
+	 * @return ConfirmationMessagingService
222
+	 */
223
+	public function getMessagingService()
224
+	{
225
+		return $this->messagingService;
226
+	}
227
+
228
+	public function __isset($property)
229
+	{
230
+		// Workaround fixed in https://github.com/silverstripe/silverstripe-framework/pull/3201
231
+		// Remove this once we update to a version of framework which supports this
232
+		if ($property === 'MessagingService') {
233
+			return !empty($this->messagingService);
234
+		}
235
+		return parent::__isset($property);
236
+	}
237
+
238
+	/**
239
+	 * Retrieve message template replacements
240
+	 *
241
+	 * @return array
242
+	 */
243
+	public function getReplacements()
244
+	{
245
+		// Get member who began this request
246
+		$author = $this->Author();
247
+		$environment = $this->Environment();
248
+		return array(
249
+			'<abortlink>' => Director::absoluteURL($this->Environment()->Link()),
250
+			'<pipelinelink>' => Director::absoluteURL($this->Link()),
251
+			'<requester>' => $author->Title,
252
+			'<requester-email>' => $author->Email,
253
+			'<environment>' => $environment->Name,
254
+			'<project>' => $environment->Project()->Name,
255
+			'<commitsha>' => $this->SHA
256
+		);
257
+	}
258
+
259
+	/**
260
+	 * Title of this step
261
+	 *
262
+	 * @return string
263
+	 */
264
+	public function getTitle()
265
+	{
266
+		return "Pipeline {$this->ID} (Status: {$this->Status})";
267
+	}
268
+
269
+	/**
270
+	 * @param Member $member
271
+	 */
272
+	public function canAbort($member = null)
273
+	{
274
+		// Owner can abort
275
+		$member = $member ?: Member::currentUser();
276
+		if (!$member) {
277
+			return false;
278
+		}
279
+		if ($member->ID == $this->AuthorID) {
280
+			return true;
281
+		}
282
+
283
+		// Check environment permission
284
+		return $this->Environment()->canAbort($member);
285
+	}
286
+
287
+	/**
288
+	 * Get status of currently running step
289
+	 *
290
+	 * @return string Status description (html format)
291
+	 */
292
+	public function getRunningDescription()
293
+	{
294
+		if (!$this->isActive()) {
295
+			return 'This pipeline is not currently running';
296
+		}
297
+		$result = '';
298
+		if ($step = $this->CurrentStep()) {
299
+			$result = $step->getRunningDescription();
300
+		}
301
+		return $result ?: 'This pipeline is currently running';
302
+	}
303
+
304
+	/**
305
+	 * Get options for the currently running pipeline, if and only if it is currently running
306
+	 *
307
+	 * @return ArrayList List of items with a Link and Title attribute
308
+	 */
309
+	public function RunningOptions()
310
+	{
311
+		if (!$this->isActive()) {
312
+			return null;
313
+		}
314
+		$actions = array();
315
+
316
+		// Let current step update the current list of options
317
+		if (($step = $this->CurrentStep()) && ($step->isRunning())) {
318
+			$actions = $step->allowedActions();
319
+		}
320
+		return new ArrayList($actions);
321
+	}
322
+
323
+	/**
324
+	 * Get possible logs for the currently pipeline
325
+	 *
326
+	 * @return ArrayList List of logs with a Link and Title attribute
327
+	 */
328
+	public function LogOptions()
329
+	{
330
+		if (!$this->isActive()) {
331
+			return null;
332
+		}
333
+
334
+		$logs = array();
335
+
336
+		$logs[] = array(
337
+			'ButtonText' => 'Pipeline Log',
338
+			'Link' => $this->Link()
339
+		);
340
+
341
+		if ($this->PreviousSnapshotID > 0) {
342
+			$logs[] = array(
343
+				'ButtonText' => 'Snapshot Log',
344
+				'Link' => $this->PreviousSnapshot()->Link()
345
+			);
346
+		}
347
+
348
+		if ($this->CurrentDeploymentID > 0) {
349
+			$logs[] = array(
350
+				'ButtonText' => 'Deployment Log',
351
+				'Link' => $this->CurrentDeployment()->Link()
352
+			);
353
+		}
354
+
355
+		// Get logs from rollback steps (only for RollbackSteps).
356
+		$rollbackSteps = array($this->RollbackStep1(), $this->RollbackStep2());
357
+		foreach ($rollbackSteps as $rollback) {
358
+			if ($rollback->exists() && $rollback->ClassName == 'RollbackStep') {
359
+				if ($rollback->RollbackDeploymentID > 0) {
360
+					$logs[] = array(
361
+						'ButtonText' => 'Rollback Log',
362
+						'Link' => $rollback->RollbackDeployment()->Link()
363
+					);
364
+				}
365
+
366
+				if ($rollback->RollbackDatabaseID > 0) {
367
+					$logs[] = array(
368
+						'ButtonText' => 'Rollback DB Log',
369
+						'Link' => $rollback->RollbackDatabase()->Link()
370
+					);
371
+				}
372
+			}
373
+		}
374
+
375
+		return new ArrayList($logs);
376
+	}
377
+
378
+	/**
379
+	 * Cached of config merged with defaults
380
+	 *
381
+	 * @var array|null
382
+	 */
383
+	protected $mergedConfig;
384
+
385
+	/**
386
+	 * Get this pipeline configuration. If the configuration has been serialized
387
+	 * and saved into the Config field, it'll use that. If that field is empty,
388
+	 * it'll read the YAML file directly and return that instead.
389
+	 *
390
+	 * @return array
391
+	 * @throws Exception
392
+	 */
393
+	public function getConfigData()
394
+	{
395
+		// Lazy load if necessary
396
+		$data = null;
397
+		if (!$this->Config && ($data = $this->Environment()->loadPipelineConfig())) {
398
+			$this->Config = serialize($data);
399
+		}
400
+
401
+		// Merge with defaults
402
+		if ($this->Config) {
403
+			if (!$this->mergedConfig) {
404
+				$this->mergedConfig = $data ?: unserialize($this->Config);
405
+				if ($default = self::config()->default_config) {
406
+					Config::merge_array_low_into_high($this->mergedConfig, $default);
407
+				}
408
+			}
409
+			return $this->mergedConfig;
410
+		}
411
+
412
+		// Fail if no data available
413
+		$path = $this->Environment()->getPipelineFilename();
414
+		throw new Exception(sprintf('YAML configuration for pipeline not found at path "%s"', $path));
415
+	}
416
+
417
+	public function setConfig($data)
418
+	{
419
+		$this->mergedConfig = null;
420
+		return parent::setField('Config', $data);
421
+	}
422
+
423
+	/**
424
+	 * Retrieve the value of a specific config setting
425
+	 *
426
+	 * @param string $setting Settings
427
+	 * @return mixed Value of setting, or null if not set
428
+	 */
429
+	public function getConfigSetting($setting)
430
+	{
431
+		$source = $this->getConfigData();
432
+
433
+		foreach (func_get_args() as $setting) {
434
+			if (empty($source[$setting])) {
435
+				return null;
436
+			}
437
+			$source = $source[$setting];
438
+		}
439
+
440
+		return $source;
441
+	}
442
+
443
+	/**
444
+	 * @return FieldList
445
+	 */
446
+	public function getCMSFields()
447
+	{
448
+		$fields = new FieldList(new TabSet('Root'));
449
+
450
+		// Main fields
451
+		$fields->addFieldsToTab('Root.Main', array(
452
+			TextField::create('SHA')
453
+				->setDescription('SHA of the commit this pipeline is running against')
454
+				->performReadonlyTransformation(),
455
+			TextField::create('AuthorName', 'Author', ($author = $this->Author()) ? $author->Title : null)
456
+				->setDescription('Person who initiated this pipeline')
457
+				->performReadonlyTransformation(),
458
+			DropdownField::create('Status', 'Status', $this->dbObject('Status')->enumValues()),
459
+			DropdownField::create('CurrentStepID', 'Current Step', $this->Steps()->map('ID', 'TreeTitle')),
460
+			TextField::create(
461
+				'CurrentDeployment_Label',
462
+				'Current Deployment',
463
+				$this->CurrentDeployment()->getTitle()
464
+			)    ->setDescription('Deployment generated by this pipeline')
465
+				->performReadonlyTransformation(),
466
+		));
467
+
468
+		// Backup fields
469
+		$fields->addFieldsToTab('Root.Backups', array(
470
+			TextField::create(
471
+				'PreviousDeployment_Label',
472
+				'Previous Deployment',
473
+				$this->PreviousDeployment()->getTitle()
474
+			)    ->setDescription('Prior deployment to revert to if this pipeline fails')
475
+				->performReadonlyTransformation(),
476
+			TextField::create(
477
+				'PreviousSnapshot_Label',
478
+				'Previous DB Snapshot',
479
+				$this->PreviousSnapshot()->getTitle()
480
+			)    ->setDescription('Database backup to revert to if this pipeline fails')
481
+				->performReadonlyTransformation()
482
+		));
483
+
484
+		if ($log = $this->LogContent()) {
485
+			$fields->addFieldToTab(
486
+				'Root.Main',
487
+				ToggleCompositeField::create(
488
+					'PipelineLog',
489
+					'Pipeline Log',
490
+					LiteralField::create('LogText', nl2br(Convert::raw2xml($log)))
491
+				)
492
+			);
493
+		}
494
+
495
+		// Steps
496
+		$stepConfig = GridFieldConfig_RecordEditor::create();
497
+		$steps = GridField::create('Steps', 'Pipeline Steps', $this->Steps(), $stepConfig);
498
+		$fields->addFieldsToTab('Root.PipelineSteps', $steps);
499
+
500
+		return $fields;
501
+	}
502
+
503
+	/**
504
+	 * Return a dependent {@link DNEnvironment} based on this pipeline's dependent environment configuration.
505
+	 *
506
+	 * @return DNEnvironment
507
+	 */
508
+	public function getDependentEnvironment()
509
+	{
510
+		// dependent environment not available
511
+		$projectName = $this->getConfigSetting('PipelineConfig', 'DependsOnProject');
512
+		$environmentName = $this->getConfigSetting('PipelineConfig', 'DependsOnEnvironment');
513
+		if (empty($projectName) || empty($environmentName)) {
514
+			return null;
515
+		}
516
+
517
+		$project = DNProject::get()->filter('Name', $projectName)->first();
518
+		if (!($project && $project->exists())) {
519
+			throw new Exception(sprintf('Could not find dependent project "%s"', $projectName));
520
+		}
521
+
522
+		$environment = DNEnvironment::get()->filter(array(
523
+			'ProjectID' => $project->ID,
524
+			'Name' => $environmentName
525
+		))->first();
526
+
527
+		if (!($environment && $environment->exists())) {
528
+			throw new Exception(sprintf(
529
+				'Could not find dependent environment "%s" in project "%s"',
530
+				$environmentName,
531
+				$projectName
532
+			));
533
+		}
534
+
535
+		return $environment;
536
+	}
537
+
538
+	/**
539
+	 * Generate a step from a name, config, and sort order
540
+	 *
541
+	 * @throws Exception
542
+	 * @param string $name
543
+	 * @param array $stepConfig
544
+	 * @param int $order
545
+	 * @return PipelineStep
546
+	 */
547
+	protected function generateStep($name, $stepConfig, $order = 0)
548
+	{
549
+		$stepClass = isset($stepConfig['Class']) ? $stepConfig['Class'] : $stepConfig;
550
+
551
+		if (empty($stepClass)) {
552
+			throw new Exception(
553
+				sprintf('Missing or empty Class specifier for step "%s"', $name)
554
+			);
555
+		}
556
+
557
+		if (!is_subclass_of($stepClass, 'PipelineStep')) {
558
+			throw new Exception(
559
+				sprintf('%s is not a valid "Class" field name for step "%s"', var_export($stepClass, true), $name)
560
+			);
561
+		}
562
+
563
+		$step = $stepClass::create();
564
+		$step->Name = $name;
565
+		$step->PipelineID = $this->ID;
566
+		$step->Order = $order;
567
+		$step->Status = 'Queued';
568
+		$step->Config = serialize($stepConfig);
569
+		$step->write();
570
+
571
+		return $step;
572
+	}
573
+
574
+	/**
575
+	 * Starts the pipeline process.
576
+	 *
577
+	 * Reads a YAML configuration from the linked {@link DNEnvironment}
578
+	 * and builds the {@link PipelineStep} objects and runs them.
579
+	 *
580
+	 * Note that this method doesn't actually start any {@link PipelineStep} objects, that is handled by
581
+	 * {@link self::checkPipelineStatus()}, and the daemon running the process.
582
+	 *
583
+	 * @throws LogicException
584
+	 * @return boolean
585
+	 */
586
+	public function start()
587
+	{
588
+		// Ensure there are no other running {@link Pipeline} objects for this {@link DNEnvironment}
589
+		// Requires that $this->EnvironmentID has been set
590
+		$env = $this->Environment();
591
+		if (!($env && $env->exists())) {
592
+			throw new LogicException("This pipeline needs a valid environment to run on.");
593
+		}
594
+
595
+		if ($env->HasCurrentPipeline()) {
596
+			throw new LogicException("You can only run one pipeline at a time on this environment.");
597
+		}
598
+
599
+		$this->write(); // ensure we've written this record first
600
+
601
+		// Instantiate steps.
602
+		foreach ($this->getConfigSetting('Steps') as $name => $stepConfig) {
603
+			$this->pushPipelineStep($name, $stepConfig);
604
+		}
605
+
606
+		$this->Status = 'Running';
607
+		$this->write();
608
+
609
+		$this->log('Started logging for this pipeline!');
610
+
611
+		return true;
612
+	}
613
+
614
+	/**
615
+	 * Mark this Pipeline as completed.
616
+	 */
617
+	public function markComplete()
618
+	{
619
+		$this->Status = "Complete";
620
+		$this->log("Pipeline completed successfully.");
621
+		$this->write();
622
+		// Some steps may pre-emptively send a success message before the pipeline itself has completed
623
+		if ($this->LastMessageSent !== self::ALERT_SUCCESS) {
624
+			$this->sendMessage(self::ALERT_SUCCESS);
625
+		}
626
+	}
627
+
628
+	/**
629
+	 * @return bool true if this Pipeline has successfully completed all {@link PipelineStep} steps already.
630
+	 */
631
+	public function isComplete()
632
+	{
633
+		return $this->Status == "Complete";
634
+	}
635
+
636
+	/**
637
+	 * True if the pipeline is running but NOT doing a rollback
638
+	 *
639
+	 * @return bool
640
+	 */
641
+	public function isRunning()
642
+	{
643
+		return $this->Status == "Running";
644
+	}
645
+
646
+	/**
647
+	 * True if the pipeline is running or doing a rollback
648
+	 *
649
+	 * @return bool
650
+	 */
651
+	public function isActive()
652
+	{
653
+		return $this->isRunning() || $this->isRollback();
654
+	}
655
+
656
+	/**
657
+	 * Push a step to the end of a pipeline
658
+	 *
659
+	 * @param string $name
660
+	 * @param array $stepConfig
661
+	 * @return PipelineStep
662
+	 */
663
+	private function pushPipelineStep($name, $stepConfig)
664
+	{
665
+		$lastStep = $this->Steps()->sort("Order DESC")->first();
666
+		$order = $lastStep ? $lastStep->Order + 1 : 1;
667
+		return $this->generateStep($name, $stepConfig, $order);
668
+	}
669
+
670
+	/**
671
+	 * The rollback has finished - close the pipeline and send relevant messages.
672
+	 */
673
+	protected function finaliseRollback()
674
+	{
675
+
676
+		// Figure out the status by inspecting specific rollback steps.
677
+		$success = true;
678
+		$rollback1 = $this->RollbackStep1();
679
+		$rollback2 = $this->RollbackStep2();
680
+		if (!empty($rollback1) && $rollback1->Status == 'Failed') {
681
+			$success = false;
682
+		}
683
+		if (!empty($rollback2) && $rollback2->Status == 'Failed') {
684
+			$success = false;
685
+		}
686
+
687
+		// Send messages.
688
+		if ($success) {
689
+			$this->log("Pipeline failed, but rollback completed successfully.");
690
+			$this->sendMessage(self::ALERT_ROLLBACK_SUCCESS);
691
+		} else {
692
+			$this->log("Pipeline failed, rollback failed.");
693
+			$this->sendMessage(self::ALERT_ROLLBACK_FAILURE);
694
+		}
695
+
696
+		// Finish off the pipeline - rollback will only be triggered on a failed pipeline.
697
+		$this->Status = 'Failed';
698
+		$this->write();
699
+	}
700
+
701
+	/**
702
+	 * Initiate a rollback. Moves the pipeline to the 'Rollback' status.
703
+	 */
704
+	protected function beginRollback()
705
+	{
706
+		$this->log("Beginning rollback...");
707
+		$this->sendMessage(self::ALERT_ROLLBACK_STARTED);
708
+
709
+		// Add rollback step.
710
+		$configRollback1 = $this->getConfigSetting('RollbackStep1');
711
+		$stepRollback1 = $this->pushPipelineStep('RollbackStep1', $configRollback1);
712
+		$this->RollbackStep1ID = $stepRollback1->ID;
713
+		$this->CurrentStepID = $stepRollback1->ID;
714
+		$this->Status = 'Rollback';
715
+
716
+		// Add smoke test step, if available, for later processing.
717
+		$configRollback2 = $this->getConfigSetting('RollbackStep2');
718
+		if ($configRollback2) {
719
+			$stepRollback2 = $this->pushPipelineStep('RollbackStep2', $configRollback2);
720
+			$this->RollbackStep2ID = $stepRollback2->ID;
721
+		}
722
+
723
+		$this->write();
724
+
725
+		$stepRollback1->start();
726
+	}
727
+
728
+	/**
729
+	 * Check if pipeline currently permits a rollback.
730
+	 * This could be influenced by both the current state and by the specific configuration.
731
+	 *
732
+	 * @return boolean
733
+	 */
734
+	protected function canStartRollback()
735
+	{
736
+		// The rollback cannot run twice.
737
+		if ($this->isRollback()) {
738
+			return false;
739
+		}
740
+
741
+		// Rollbacks must be configured.
742
+		if (!$this->getConfigSetting('RollbackStep1')) {
743
+			return false;
744
+		}
745
+
746
+		// On dryrun let rollback run
747
+		if ($this->DryRun) {
748
+			return true;
749
+		}
750
+
751
+		// Pipeline must have ran a deployment to be able to rollback.
752
+		$deploy = $this->CurrentDeployment();
753
+		$previous = $this->PreviousDeployment();
754
+		if (!$deploy->exists() || !$previous->exists()) {
755
+			return false;
756
+		}
757
+
758
+		return true;
759
+	}
760
+
761
+	/**
762
+	 * Notify Pipeline that a step has failed and failure processing should kick in. If rollback steps are present
763
+	 * the pipeline will be put into 'Rollback' state. After rollback is complete, regardless of the rollback result,
764
+	 * the pipeline will be failed.
765
+	 *
766
+	 * @param bool $notify Set to false to disable notifications for this failure
767
+	 */
768
+	public function markFailed($notify = true)
769
+	{
770
+		// Abort all running or queued steps.
771
+		$steps = $this->Steps();
772
+		foreach ($steps as $step) {
773
+			if ($step->isQueued() || $step->isRunning()) {
774
+				$step->abort();
775
+			}
776
+		}
777
+
778
+		if ($this->canStartRollback()) {
779
+			$this->beginRollback();
780
+		} elseif ($this->isRollback()) {
781
+			$this->finaliseRollback();
782
+		} else {
783
+			// Not able to roll back - fail immediately.
784
+			$this->Status = 'Failed';
785
+			$this->log("Pipeline failed, not running rollback (not configured or not applicable yet).");
786
+			$this->write();
787
+			if ($notify) {
788
+				$this->sendMessage(self::ALERT_FAILURE);
789
+			}
790
+		}
791
+	}
792
+
793
+	/**
794
+	 * @return bool true if this Pipeline failed to execute all {@link PipelineStep} steps successfully
795
+	 */
796
+	public function isFailed()
797
+	{
798
+		return $this->Status == "Failed";
799
+	}
800
+
801
+	/**
802
+	 * @return bool true if this Pipeline is rolling back.
803
+	 */
804
+	public function isRollback()
805
+	{
806
+		return $this->Status == "Rollback";
807
+	}
808
+
809
+	/**
810
+	 * Mark this Pipeline as aborted
811
+	 */
812
+	public function markAborted()
813
+	{
814
+		$this->Status = 'Aborted';
815
+		$logMessage = sprintf(
816
+			"Pipeline processing aborted. %s (%s) aborted the pipeline",
817
+			Member::currentUser()->Name,
818
+			Member::currentUser()->Email
819
+		);
820
+		$this->log($logMessage);
821
+		$this->write();
822
+
823
+		// Abort all running or queued steps.
824
+		$steps = $this->Steps();
825
+		foreach ($steps as $step) {
826
+			if ($step->isQueued() || $step->isRunning()) {
827
+				$step->abort();
828
+			}
829
+		}
830
+
831
+		// Send notification to users about this event
832
+		$this->sendMessage(self::ALERT_ABORT);
833
+	}
834
+
835
+	/**
836
+	 * Finds a message template for a given role and message
837
+	 *
838
+	 * @param string $messageID Message ID
839
+	 * @return array Resulting array(subject, message)
840
+	 */
841
+	protected function generateMessageTemplate($messageID)
842
+	{
843
+		$subject = $this->getConfigSetting('PipelineConfig', 'Subjects', $messageID);
844
+		$message = $this->getConfigSetting('PipelineConfig', 'Messages', $messageID);
845
+		$substitutions = $this->getReplacements();
846
+		return $this->injectMessageReplacements($message, $subject, $substitutions);
847
+	}
848
+
849
+	/**
850
+	 * Substitute templated variables into the given message and subject
851
+	 *
852
+	 * @param string $message
853
+	 * @param string $subject
854
+	 * @param array $substitutions
855
+	 * @return array Resulting array(subject, message)
856
+	 */
857
+	public function injectMessageReplacements($message, $subject, $substitutions)
858
+	{
859
+		// Handle empty messages
860
+		if (empty($subject) && empty($message)) {
861
+			return array(null, null);
862
+		}
863
+
864
+		// Check if there's a role specific message
865
+		$subjectText = str_replace(
866
+			array_keys($substitutions),
867
+			array_values($substitutions),
868
+			$subject ?: $message
869
+		);
870
+		$messageText = str_replace(
871
+			array_keys($substitutions),
872
+			array_values($substitutions),
873
+			$message ?: $subject
874
+		);
875
+
876
+
877
+		return array($subjectText, $messageText);
878
+	}
879
+
880
+	/**
881
+	 * Sends a specific message to all marked recipients, including the author of this pipeline
882
+	 *
883
+	 * @param string $messageID Message ID. One of 'Abort', 'Success', or 'Failure', or some custom message
884
+	 * @return boolean|null True if successful
885
+	 */
886
+	public function sendMessage($messageID)
887
+	{
888
+		// Check message, subject, and additional arguments to include
889
+		list($subject, $message) = $this->generateMessageTemplate($messageID);
890
+		if (empty($subject) || empty($message)) {
891
+			$this->log("Skipping sending message. None configured for $messageID");
892
+			return true;
893
+		}
894
+
895
+		// Save last sent message
896
+		$this->LastMessageSent = $messageID;
897
+		$this->write();
898
+
899
+		// Setup messaging arguments
900
+		$arguments = array_merge(
901
+			$this->getConfigSetting('PipelineConfig', 'ServiceArguments') ?: array(),
902
+			array('subject' => $subject)
903
+		);
904
+
905
+		// Send message to author
906
+		if ($author = $this->Author()) {
907
+			$this->log("Pipeline sending $messageID message to {$author->Email}");
908
+			$this->messagingService->sendMessage($this, $message, $author, $arguments);
909
+		} else {
910
+			$this->log("Skipping sending message to missing author");
911
+		}
912
+
913
+		// Get additional recipients
914
+		$recipients = $this->getConfigSetting('PipelineConfig', 'Recipients', $messageID);
915
+		if (empty($recipients)) {
916
+			$this->log("Skipping sending message to empty recipients");
917
+		} else {
918
+			$recipientsStr = is_array($recipients) ? implode(',', $recipients) : $recipients;
919
+			$this->log("Pipeline sending $messageID message to $recipientsStr");
920
+			$this->messagingService->sendMessage($this, $message, $recipients, $arguments);
921
+		}
922
+	}
923
+
924
+	/**
925
+	 * @return bool true if this Pipeline has been aborted
926
+	 */
927
+	public function isAborted()
928
+	{
929
+		return $this->Status === "Aborted";
930
+	}
931
+
932
+	/**
933
+	 * This method should be called only by the {@link CheckPipelineStatus} controller. It iterates through all the
934
+	 * {@link PipelineStep} objects associated with this Pipeline, and finds a place where the pipeline has stalled
935
+	 * (where one step has completed, but the next one has yet to start). It will then start the next step if required.
936
+	 *
937
+	 * We check here whether the {@link PipelineStep} finished successfully, and will mark the Pipeline as Failed if
938
+	 * the step failed, but this is only a fallback, and should not be relied upon. The individual {@link PipelineStep}
939
+	 * should mark itself as failed and then call {@link Pipeline::markFailed()} directly.
940
+	 *
941
+	 * If the Pipeline has run out of steps, then it will mark the pipeline as completed.
942
+	 */
943
+	public function checkPipelineStatus()
944
+	{
945
+		$message = "";
946
+
947
+		if (!$this->isActive()) {
948
+			$message = "Pipeline::checkPipelineStatus() should only be called on running or rolling back pipelines.";
949
+		}
950
+
951
+		if (!$this->ID || !$this->isInDB()) {
952
+			$message = "Pipeline::checkPipelineStatus() can only be called on pipelines already saved.";
953
+		}
954
+
955
+		$currentStep = ($this->CurrentStep() && $this->CurrentStep()->isInDB())
956
+			? $this->CurrentStep()
957
+			: null;
958
+
959
+		if ($currentStep && $currentStep->PipelineID != $this->ID) {
960
+			$message = sprintf(
961
+				"The current step (#%d) has a pipeline ID (#%d) that doesn't match this pipeline's ID (#%d).",
962
+				$currentStep->ID,
963
+				$currentStep->PipelineID,
964
+				$this->ID
965
+			);
966
+		}
967
+
968
+		if ($message) {
969
+			$this->log($message);
970
+			throw new LogicException($message);
971
+		}
972
+
973
+		// Fallback check only: this shouldn't be called unless a {@link PipelineStep} has been implemented incorrectly
974
+		if ($currentStep && $currentStep->isFailed() && !$this->isFailed() && !$this->isRollback()) {
975
+			$this->log(sprintf("Marking pipeline step (#%d) as failed - this pipeline step needs to be amended to mark"
976
+				. " the pipeline (as well as itself) as failed to ensure consistency.",
977
+				$this->CurrentStep()->ID
978
+			));
979
+
980
+			$this->markFailed();
981
+			return;
982
+		}
983
+
984
+		// If this is the first time the Pipeline is run, then we don't have a CurrentStep, so set it,
985
+		// start it running, and return
986
+		if (!$currentStep) {
987
+			$step = $this->Steps()->first();
988
+			$this->CurrentStepID = $step->ID;
989
+			$this->write();
990
+
991
+			$this->log("Starting first pipeline step...");
992
+			$step->start();
993
+		} elseif ($currentStep->isFinished()) {
994
+			// Sort through the list of {@link PipelineStep} objects to find the next step we need to start.
995
+			$this->log("Finding next step to execute...");
996
+			$nextStep = $this->findNextStep();
997
+
998
+			if (!$nextStep) {
999
+
1000
+				// Special handling, since the main pipeline has already failed at this stage.
1001
+				if ($this->isRollback()) {
1002
+					$this->finaliseRollback();
1003
+					return false;
1004
+				}
1005
+
1006
+				// Double check for any steps that failed, but didn't notify the pipeline via markFailed.
1007
+				$failedSteps = PipelineStep::get()->filter(array(
1008
+					'PipelineID' => $this->ID,
1009
+					'Status' => 'Failed'
1010
+				))->count();
1011
+				if ($failedSteps) {
1012
+					$this->log('At least one of the steps has failed marking the pipeline as failed');
1013
+					$this->markFailed();
1014
+					return false;
1015
+				}
1016
+
1017
+				// We've reached the end of this pipeline successfully!
1018
+				$this->markComplete();
1019
+				return;
1020
+			} else {
1021
+				$this->CurrentStepID = $nextStep->ID;
1022
+				$this->write();
1023
+				// Otherwise, kick off the next step
1024
+				$this->log(sprintf("Found the next step (#%s), starting it now...", $nextStep->Name));
1025
+				$nextStep->start();
1026
+			}
1027
+		// if the current step is failing run it again
1028
+		} elseif ($step = $this->CurrentStep()) {
1029
+			$step->start();
1030
+		}
1031
+	}
1032
+
1033
+	/**
1034
+	 * Finds the next {@link PipelineStep} that needs to execute. Relies on $this->CurrentStep() being a valid step.
1035
+	 *
1036
+	 * @return DataObject|null The next step in the pipeline, or null if none remain.
1037
+	 */
1038
+	protected function findNextStep()
1039
+	{
1040
+		// otherwise get next step in chain
1041
+		$currentStep = $this->CurrentStep();
1042
+
1043
+		return $this
1044
+			->Steps()
1045
+			->filter("Status", "Queued")
1046
+			->filter("Order:GreaterThanOrEqual", $currentStep->Order)
1047
+			->exclude("ID", $currentStep->ID)
1048
+			->sort("Order ASC")
1049
+			->first();
1050
+	}
1051
+
1052
+	/**
1053
+	 * Finds the previous {@link PipelineStep} that executed. Relies on $this->CurrentStep() being a valid step.
1054
+	 *
1055
+	 * @return DataObject|null The previous step in the pipeline, or null if this is the first.
1056
+	 */
1057
+	public function findPreviousStep()
1058
+	{
1059
+		// otherwise get previous step in chain
1060
+		$currentStep = $this->CurrentStep();
1061
+
1062
+		return $this
1063
+			->Steps()
1064
+			->filter("Status", "Finished")
1065
+			->filter("Order:LessThanOrEqual", $currentStep->Order)
1066
+			->exclude("ID", $currentStep->ID)
1067
+			->sort("Order DESC")
1068
+			->first();
1069
+	}
1070
+
1071
+	/**
1072
+	 * Write to a common log file. This log file will be the same regardless of how often this pipeline is re-created
1073
+	 * from the database. To this end, it needs to know the database ID of this pipeline instance, so that it can
1074
+	 * generate the correct filename to open.
1075
+	 *
1076
+	 * This also includes the calling class and method name that called ->log() in the first place, so we can trace
1077
+	 * back where it was written from.
1078
+	 *
1079
+	 * @param string $message The message to log
1080
+	 * @throws LogicException Thrown if we can't log yet because we don't know what to log to (no db record yet).
1081
+	 */
1082
+	public function log($message = "")
1083
+	{
1084
+		$log = $this->getLogger();
1085
+
1086
+		// Taken from Debug::caller(), amended for our purposes to filter out the intermediate call to
1087
+		// PipelineStep->log(), so that our log message shows where the log message was actually created from.
1088
+		$bt = debug_backtrace();
1089
+
1090
+		$index = ($bt[1]['class'] == 'PipelineStep') ? 2 : 1;
1091
+
1092
+		$caller = $bt[$index];
1093
+		$caller['line'] = $bt[($index - 1)]['line']; // Overwrite line and file to be the the line/file that actually
1094
+		$caller['file'] = $bt[($index - 1)]['file']; // called the function, not where the function is defined.
1095
+		// In case it wasn't called from a class
1096
+		if (!isset($caller['class'])) {
1097
+			$caller['class'] = '';
1098
+		}
1099
+		// In case it doesn't have a type (wasn't called from class)
1100
+		if (!isset($caller['type'])) {
1101
+			$caller['type'] = '';
1102
+		}
1103
+
1104
+		$log->write(sprintf(
1105
+			"[%s::%s() (line %d)] %s",
1106
+			$caller['class'],
1107
+			$caller['function'],
1108
+			$caller['line'],
1109
+			$message
1110
+		));
1111
+	}
1112
+
1113
+	/**
1114
+	 * Returns the {@link DeploynautLogFile} instance that will actually write to this log file.
1115
+	 *
1116
+	 * @return DeploynautLogFile
1117
+	 * @throws RuntimeException
1118
+	 */
1119
+	public function getLogger()
1120
+	{
1121
+		if (!$this->isInDB()) {
1122
+			throw new RuntimeException("Can't write to a log file until we know the database ID.");
1123
+		}
1124
+
1125
+		if (!$this->Environment()) {
1126
+			throw new RuntimeException("Can't write to a log file until we have an Environment.");
1127
+		}
1128
+
1129
+		if ($this->Environment() && !$this->Environment()->Project()) {
1130
+			throw new RuntimeException("Can't write to a log file until we have the Environment's project.");
1131
+		}
1132
+
1133
+		$environment = $this->Environment();
1134
+		$filename = sprintf('%s.pipeline.%d.log', $environment->getFullName('.'), $this->ID);
1135
+
1136
+		return Injector::inst()->createWithArgs('DeploynautLogFile', array($filename));
1137
+	}
1138
+
1139
+	/**
1140
+	 * @return bool
1141
+	 */
1142
+	public function getDryRun()
1143
+	{
1144
+		return $this->getField('DryRun');
1145
+	}
1146
+
1147
+	/**
1148
+	 * @param string|null $action
1149
+	 *
1150
+	 * @return string
1151
+	 */
1152
+	public function Link($action = null)
1153
+	{
1154
+		return Controller::join_links($this->Environment()->Link(), 'pipeline', $this->ID, $action);
1155
+	}
1156
+
1157
+	/**
1158
+	 * Link to an action on the current step
1159
+	 *
1160
+	 * @param string|null $action
1161
+	 * @return string
1162
+	 */
1163
+	public function StepLink($action = null)
1164
+	{
1165
+		return Controller::join_links($this->Link('step'), $action);
1166
+	}
1167
+
1168
+	/**
1169
+	 * @return string
1170
+	 */
1171
+	public function AbortLink()
1172
+	{
1173
+		return $this->Link('abort');
1174
+	}
1175
+
1176
+	/**
1177
+	 * @return string
1178
+	 */
1179
+	public function LogLink()
1180
+	{
1181
+		return $this->Link('log');
1182
+	}
1183
+
1184
+	/**
1185
+	 * @return string
1186
+	 */
1187
+	public function LogContent()
1188
+	{
1189
+		if ($this->exists() && $this->Environment()) {
1190
+			$logger = $this->getLogger();
1191
+			if ($logger->exists()) {
1192
+				return $logger->content();
1193
+			}
1194
+		}
1195
+	}
1196 1196
 }
Please login to merge, or discard this patch.
code/model/PipelineData.php 1 patch
Indentation   +12 added lines, -12 removed lines patch added patch discarded remove patch
@@ -3,17 +3,17 @@
 block discarded – undo
3 3
 interface PipelineData
4 4
 {
5 5
 
6
-    /**
7
-     * Is this a dry run?
8
-     *
9
-     * @return bool
10
-     */
11
-    public function getDryRun();
6
+	/**
7
+	 * Is this a dry run?
8
+	 *
9
+	 * @return bool
10
+	 */
11
+	public function getDryRun();
12 12
 
13
-    /**
14
-     * Log message
15
-     *
16
-     * @param string $message The message to log
17
-     */
18
-    public function log($message);
13
+	/**
14
+	 * Log message
15
+	 *
16
+	 * @param string $message The message to log
17
+	 */
18
+	public function log($message);
19 19
 }
Please login to merge, or discard this patch.
code/model/jobs/DNCreateEnvironment.php 1 patch
Indentation   +193 added lines, -193 removed lines patch added patch discarded remove patch
@@ -16,197 +16,197 @@
 block discarded – undo
16 16
 class DNCreateEnvironment extends DataObject
17 17
 {
18 18
 
19
-    /**
20
-     * @var array
21
-     */
22
-    private static $db = array(
23
-        'Data' => 'Text',
24
-        'ResqueToken' => 'Varchar(255)',
25
-        "Status" => "Enum('Queued, Started, Finished, Failed, n/a', 'n/a')",
26
-        'IsInitialEnvironment' => 'Boolean',
27
-    );
28
-
29
-    /**
30
-     * @var array
31
-     */
32
-    private static $has_one = array(
33
-        'Project' => 'DNProject',
34
-        'Creator' => 'Member'
35
-    );
36
-
37
-    /**
38
-     *
39
-     * @param int $int
40
-     * @return string
41
-     */
42
-    public static function map_resque_status($int)
43
-    {
44
-        $remap = array(
45
-            Resque_Job_Status::STATUS_WAITING => "Queued",
46
-            Resque_Job_Status::STATUS_RUNNING => "Running",
47
-            Resque_Job_Status::STATUS_FAILED => "Failed",
48
-            Resque_Job_Status::STATUS_COMPLETE => "Complete",
49
-            false => "Invalid",
50
-        );
51
-        return $remap[$int];
52
-    }
53
-
54
-    /**
55
-     * @return string
56
-     */
57
-    public function Name()
58
-    {
59
-        $data = unserialize($this->Data);
60
-        return !empty($data['Name']) ? Convert::raw2xml($data['Name']) : '';
61
-    }
62
-
63
-    /**
64
-     * @return string
65
-     */
66
-    public function Link()
67
-    {
68
-        return Controller::join_links($this->Project()->Link(), 'createenv', $this->ID);
69
-    }
70
-
71
-    /**
72
-     * @return string
73
-     */
74
-    public function LogLink()
75
-    {
76
-        return $this->Link() . '/log';
77
-    }
78
-
79
-    /**
80
-     * @return boolean
81
-     */
82
-    public function canView($member = null)
83
-    {
84
-        return $this->Project()->canView($member);
85
-    }
86
-
87
-    /**
88
-     * Return a path to the log file.
89
-     * @return string
90
-     */
91
-    protected function logfile()
92
-    {
93
-        return sprintf(
94
-            '%s.createenv.%s.log',
95
-            $this->Project()->Name,
96
-            $this->ID
97
-        );
98
-    }
99
-
100
-    /**
101
-     * @return DeploynautLogFile
102
-     */
103
-    public function log()
104
-    {
105
-        return Injector::inst()->createWithArgs('DeploynautLogFile', array($this->logfile()));
106
-    }
107
-
108
-    public function LogContent()
109
-    {
110
-        return $this->log()->content();
111
-    }
112
-
113
-    /**
114
-     * Returns the status of the resque job
115
-     *
116
-     * @return string
117
-     */
118
-    public function ResqueStatus()
119
-    {
120
-        $status = new Resque_Job_Status($this->ResqueToken);
121
-        $statusCode = $status->get();
122
-        // The Resque job can no longer be found, fallback to the DNDeployment.Status
123
-        if ($statusCode === false) {
124
-            // Translate from the DNDeployment.Status to the Resque job status for UI purposes
125
-            switch ($this->Status) {
126
-                case 'Finished':
127
-                    return 'Complete';
128
-                case 'Started':
129
-                    return 'Running';
130
-                default:
131
-                    return $this->Status;
132
-            }
133
-        }
134
-        return self::map_resque_status($statusCode);
135
-    }
136
-
137
-    /**
138
-     * Start a resque job for this creation.
139
-     *
140
-     * @return string Resque token
141
-     */
142
-    protected function enqueueCreation()
143
-    {
144
-        $project = $this->Project();
145
-        $log = $this->log();
146
-
147
-        $args = array(
148
-            'createID' => $this->ID,
149
-            'logfile' => $this->logfile(),
150
-            'projectName' => $project->Name
151
-        );
152
-
153
-        if (!$this->CreatorID) {
154
-            $this->CreatorID = Member::currentUserID();
155
-        }
156
-
157
-        if ($this->CreatorID) {
158
-            $creator = $this->Creator();
159
-            $message = sprintf(
160
-                'Environment creation for project %s initiated by %s (%s), with IP address %s',
161
-                $project->Name,
162
-                $creator->getName(),
163
-                $creator->Email,
164
-                Controller::curr()->getRequest()->getIP()
165
-            );
166
-            $log->write($message);
167
-        }
168
-
169
-        return Resque::enqueue('create', 'CreateEnvJob', $args, true);
170
-    }
171
-
172
-    public function start()
173
-    {
174
-        $log = $this->log();
175
-        $token = $this->enqueueCreation();
176
-        $this->ResqueToken = $token;
177
-        $this->Status = 'Queued';
178
-        $this->write();
179
-
180
-        $message = sprintf('Environment creation queued as job %s', $token);
181
-        $log->write($message);
182
-    }
183
-
184
-    public function createEnvironment()
185
-    {
186
-        $backend = $this->getBackend();
187
-        if ($backend) {
188
-            return $backend->createEnvironment($this);
189
-        }
190
-        throw new Exception("Unable to find backend.");
191
-    }
192
-
193
-    /**
194
-     * Fetches the EnvironmentCreateBackend based on the EnvironmentType saved to this job.
195
-     *
196
-     * @return EnvironmentCreateBackend|null
197
-     * @throws Exception
198
-     */
199
-    public function getBackend()
200
-    {
201
-        $data = unserialize($this->Data);
202
-        if (isset($data['EnvironmentType']) && class_exists($data['EnvironmentType'])) {
203
-            $env = Injector::inst()->get($data['EnvironmentType']);
204
-            if ($env instanceof EnvironmentCreateBackend) {
205
-                return $env;
206
-            } else {
207
-                throw new Exception("Invalid backend: " . $data['EnvironmentType']);
208
-            }
209
-        }
210
-        return null;
211
-    }
19
+	/**
20
+	 * @var array
21
+	 */
22
+	private static $db = array(
23
+		'Data' => 'Text',
24
+		'ResqueToken' => 'Varchar(255)',
25
+		"Status" => "Enum('Queued, Started, Finished, Failed, n/a', 'n/a')",
26
+		'IsInitialEnvironment' => 'Boolean',
27
+	);
28
+
29
+	/**
30
+	 * @var array
31
+	 */
32
+	private static $has_one = array(
33
+		'Project' => 'DNProject',
34
+		'Creator' => 'Member'
35
+	);
36
+
37
+	/**
38
+	 *
39
+	 * @param int $int
40
+	 * @return string
41
+	 */
42
+	public static function map_resque_status($int)
43
+	{
44
+		$remap = array(
45
+			Resque_Job_Status::STATUS_WAITING => "Queued",
46
+			Resque_Job_Status::STATUS_RUNNING => "Running",
47
+			Resque_Job_Status::STATUS_FAILED => "Failed",
48
+			Resque_Job_Status::STATUS_COMPLETE => "Complete",
49
+			false => "Invalid",
50
+		);
51
+		return $remap[$int];
52
+	}
53
+
54
+	/**
55
+	 * @return string
56
+	 */
57
+	public function Name()
58
+	{
59
+		$data = unserialize($this->Data);
60
+		return !empty($data['Name']) ? Convert::raw2xml($data['Name']) : '';
61
+	}
62
+
63
+	/**
64
+	 * @return string
65
+	 */
66
+	public function Link()
67
+	{
68
+		return Controller::join_links($this->Project()->Link(), 'createenv', $this->ID);
69
+	}
70
+
71
+	/**
72
+	 * @return string
73
+	 */
74
+	public function LogLink()
75
+	{
76
+		return $this->Link() . '/log';
77
+	}
78
+
79
+	/**
80
+	 * @return boolean
81
+	 */
82
+	public function canView($member = null)
83
+	{
84
+		return $this->Project()->canView($member);
85
+	}
86
+
87
+	/**
88
+	 * Return a path to the log file.
89
+	 * @return string
90
+	 */
91
+	protected function logfile()
92
+	{
93
+		return sprintf(
94
+			'%s.createenv.%s.log',
95
+			$this->Project()->Name,
96
+			$this->ID
97
+		);
98
+	}
99
+
100
+	/**
101
+	 * @return DeploynautLogFile
102
+	 */
103
+	public function log()
104
+	{
105
+		return Injector::inst()->createWithArgs('DeploynautLogFile', array($this->logfile()));
106
+	}
107
+
108
+	public function LogContent()
109
+	{
110
+		return $this->log()->content();
111
+	}
112
+
113
+	/**
114
+	 * Returns the status of the resque job
115
+	 *
116
+	 * @return string
117
+	 */
118
+	public function ResqueStatus()
119
+	{
120
+		$status = new Resque_Job_Status($this->ResqueToken);
121
+		$statusCode = $status->get();
122
+		// The Resque job can no longer be found, fallback to the DNDeployment.Status
123
+		if ($statusCode === false) {
124
+			// Translate from the DNDeployment.Status to the Resque job status for UI purposes
125
+			switch ($this->Status) {
126
+				case 'Finished':
127
+					return 'Complete';
128
+				case 'Started':
129
+					return 'Running';
130
+				default:
131
+					return $this->Status;
132
+			}
133
+		}
134
+		return self::map_resque_status($statusCode);
135
+	}
136
+
137
+	/**
138
+	 * Start a resque job for this creation.
139
+	 *
140
+	 * @return string Resque token
141
+	 */
142
+	protected function enqueueCreation()
143
+	{
144
+		$project = $this->Project();
145
+		$log = $this->log();
146
+
147
+		$args = array(
148
+			'createID' => $this->ID,
149
+			'logfile' => $this->logfile(),
150
+			'projectName' => $project->Name
151
+		);
152
+
153
+		if (!$this->CreatorID) {
154
+			$this->CreatorID = Member::currentUserID();
155
+		}
156
+
157
+		if ($this->CreatorID) {
158
+			$creator = $this->Creator();
159
+			$message = sprintf(
160
+				'Environment creation for project %s initiated by %s (%s), with IP address %s',
161
+				$project->Name,
162
+				$creator->getName(),
163
+				$creator->Email,
164
+				Controller::curr()->getRequest()->getIP()
165
+			);
166
+			$log->write($message);
167
+		}
168
+
169
+		return Resque::enqueue('create', 'CreateEnvJob', $args, true);
170
+	}
171
+
172
+	public function start()
173
+	{
174
+		$log = $this->log();
175
+		$token = $this->enqueueCreation();
176
+		$this->ResqueToken = $token;
177
+		$this->Status = 'Queued';
178
+		$this->write();
179
+
180
+		$message = sprintf('Environment creation queued as job %s', $token);
181
+		$log->write($message);
182
+	}
183
+
184
+	public function createEnvironment()
185
+	{
186
+		$backend = $this->getBackend();
187
+		if ($backend) {
188
+			return $backend->createEnvironment($this);
189
+		}
190
+		throw new Exception("Unable to find backend.");
191
+	}
192
+
193
+	/**
194
+	 * Fetches the EnvironmentCreateBackend based on the EnvironmentType saved to this job.
195
+	 *
196
+	 * @return EnvironmentCreateBackend|null
197
+	 * @throws Exception
198
+	 */
199
+	public function getBackend()
200
+	{
201
+		$data = unserialize($this->Data);
202
+		if (isset($data['EnvironmentType']) && class_exists($data['EnvironmentType'])) {
203
+			$env = Injector::inst()->get($data['EnvironmentType']);
204
+			if ($env instanceof EnvironmentCreateBackend) {
205
+				return $env;
206
+			} else {
207
+				throw new Exception("Invalid backend: " . $data['EnvironmentType']);
208
+			}
209
+		}
210
+		return null;
211
+	}
212 212
 }
Please login to merge, or discard this patch.
code/model/jobs/DNDataTransfer.php 1 patch
Indentation   +270 added lines, -270 removed lines patch added patch discarded remove patch
@@ -34,274 +34,274 @@
 block discarded – undo
34 34
 class DNDataTransfer extends DataObject
35 35
 {
36 36
 
37
-    private static $db = array(
38
-        "ResqueToken" => "Varchar(255)",
39
-        // Observe that this is not the same as Resque status, since ResqueStatus is not persistent.
40
-        "Status" => "Enum('Queued, Started, Finished, Failed, n/a', 'n/a')",
41
-        "Direction" => "Enum('get, push', 'get')",
42
-        "Mode" => "Enum('all, assets, db', '')",
43
-        "Origin" => "Enum('EnvironmentTransfer,ManualUpload', 'EnvironmentTransfer')",
44
-    );
45
-
46
-    private static $has_one = array(
47
-        "Environment" => "DNEnvironment",
48
-        "Author" => "Member",
49
-        "DataArchive" => "DNDataArchive",
50
-        "BackupDataTransfer" => "DNDataTransfer" // denotes an automated backup done for a push of this data transfer
51
-    );
52
-
53
-    private static $singular_name = 'Data Transfer';
54
-
55
-    private static $plural_name = 'Data Transfers';
56
-
57
-    private static $summary_fields = array(
58
-        'Created' => 'Created',
59
-        'Author.Title' => 'Author',
60
-        'Environment.Project.Name' => 'Project',
61
-        'Environment.Name' => 'Environment',
62
-        'Status' => 'Status',
63
-        'Origin' => 'Origin',
64
-    );
65
-
66
-    private static $searchable_fields = array(
67
-        'Environment.Project.Name' => array(
68
-            'title' => 'Project',
69
-        ),
70
-        'Environment.Name' => array(
71
-            'title' => 'Environment',
72
-        ),
73
-        'Status' => array(
74
-            'title' => 'Status',
75
-        ),
76
-        'Origin' => array(
77
-            'title' => 'Origin',
78
-        ),
79
-        'Mode' => array(
80
-            'title' => 'Mode',
81
-        ),
82
-        'Direction' => array(
83
-            'title' => 'Direction',
84
-        ),
85
-    );
86
-
87
-    /**
88
-     * When running the transfer, should a backup be performed before pushing the data?
89
-     * @var bool
90
-     */
91
-    protected $backupBeforePush = true;
92
-
93
-    /**
94
-     * @param int $int
95
-     * @return string
96
-     */
97
-    public static function map_resque_status($int)
98
-    {
99
-        $remap = array(
100
-            Resque_Job_Status::STATUS_WAITING => "Queued",
101
-            Resque_Job_Status::STATUS_RUNNING => "Running",
102
-            Resque_Job_Status::STATUS_FAILED => "Failed",
103
-            Resque_Job_Status::STATUS_COMPLETE => "Complete",
104
-            false => "Invalid",
105
-        );
106
-        return $remap[$int];
107
-    }
108
-
109
-    /**
110
-     * @param boolean $value
111
-     */
112
-    public function setBackupBeforePush($value)
113
-    {
114
-        $this->backupBeforePush = $value;
115
-    }
116
-
117
-    public function getTitle()
118
-    {
119
-        return $this->dbObject('Created')->Nice() . " (Status: {$this->Status})";
120
-    }
121
-
122
-    public function Link()
123
-    {
124
-        return Controller::join_links($this->Environment()->Project()->Link(), 'transfer', $this->ID);
125
-    }
126
-
127
-    public function LogLink()
128
-    {
129
-        return Controller::join_links($this->Link(), 'log');
130
-    }
131
-
132
-    public function getDefaultSearchContext()
133
-    {
134
-        $context = parent::getDefaultSearchContext();
135
-        $context->getFields()->dataFieldByName('Status')->setHasEmptyDefault(true);
136
-        $context->getFields()->dataFieldByName('Origin')->setHasEmptyDefault(true);
137
-
138
-        return $context;
139
-    }
140
-
141
-    public function getCMSFields()
142
-    {
143
-        $fields = parent::getCMSFields();
144
-        $fields->removeByName('EnvironmentID');
145
-        $fields->removeByName('ArchiveFile');
146
-        $fields->addFieldsToTab(
147
-            'Root.Main',
148
-            array(
149
-                new ReadonlyField('ProjectName', 'Project', $this->Environment()->Project()->Name),
150
-                new ReadonlyField('EnvironmentName', 'Environment', $this->Environment()->Name),
151
-                new ReadonlyField(
152
-                    'DataArchive',
153
-                    'Archive File',
154
-                    sprintf(
155
-                        '<a href="%s">%s</a>',
156
-                        $this->DataArchive()->ArchiveFile()->AbsoluteURL,
157
-                        $this->DataArchive()->ArchiveFile()->Filename
158
-                    )
159
-                ),
160
-            )
161
-        );
162
-        $fields = $fields->makeReadonly();
163
-
164
-        return $fields;
165
-    }
166
-
167
-    /**
168
-     * Queue a transfer job
169
-     */
170
-    public function start()
171
-    {
172
-        $env = $this->Environment();
173
-        $log = $this->log();
174
-
175
-        $args = array(
176
-            'dataTransferID' => $this->ID,
177
-            'logfile' => $this->logfile(),
178
-            'backupBeforePush' => $this->backupBeforePush
179
-        );
180
-
181
-        if (!$this->AuthorID) {
182
-            $this->AuthorID = Member::currentUserID();
183
-        }
184
-
185
-        if ($this->AuthorID) {
186
-            $author = $this->Author();
187
-            $message = sprintf(
188
-                'Data transfer on %s (%s, %s) initiated by %s (%s), with IP address %s',
189
-                $env->getFullName(),
190
-                $this->Direction,
191
-                $this->Mode,
192
-                $author->getName(),
193
-                $author->Email,
194
-                Controller::curr()->getRequest()->getIP()
195
-            );
196
-            $log->write($message);
197
-        }
198
-
199
-        $token = Resque::enqueue('git', 'DataTransferJob', $args, true);
200
-        $this->ResqueToken = $token;
201
-        $this->write();
202
-
203
-        $message = sprintf('Data transfer queued as job %s', $token);
204
-        $log->write($message);
205
-    }
206
-
207
-    /**
208
-     * @param Member|null $member
209
-     * @return bool
210
-     */
211
-    public function canView($member = null)
212
-    {
213
-        return $this->Environment()->canView($member);
214
-    }
215
-
216
-    /**
217
-     * Return a path to the log file.
218
-     * @return string
219
-     */
220
-    protected function logfile()
221
-    {
222
-        return sprintf(
223
-            '%s.datatransfer.%s.log',
224
-            $this->Environment()->getFullName('.'),
225
-            $this->ID
226
-        );
227
-    }
228
-
229
-    /**
230
-     * @return \DeploynautLogFile
231
-     */
232
-    public function log()
233
-    {
234
-        return new DeploynautLogFile($this->logfile());
235
-    }
236
-
237
-    /**
238
-     * @return string
239
-     */
240
-    public function LogContent()
241
-    {
242
-        return $this->log()->content();
243
-    }
244
-
245
-    public function getDescription()
246
-    {
247
-        $envName = $this->Environment()->getFullName();
248
-        if ($this->Direction == 'get') {
249
-            if ($this->Origin == 'ManualUpload') {
250
-                $description = 'Manual upload of ' . $this->getModeNice() . ' to ' . $envName;
251
-            } elseif ($this->IsBackupDataTransfer()) {
252
-                $description = 'Automated backup of ' . $this->getModeNice() . ' from ' . $envName;
253
-            } else {
254
-                $description = 'Backup of ' . $this->getModeNice() . ' to ' . $envName;
255
-            }
256
-        } else {
257
-            $description = 'Restore ' . $this->getModeNice() . ' to ' . $envName;
258
-        }
259
-
260
-        return $description;
261
-    }
262
-
263
-    public function getModeNice()
264
-    {
265
-        if ($this->Mode == 'all') {
266
-            return 'database and assets';
267
-        } else {
268
-            return $this->Mode;
269
-        }
270
-    }
271
-
272
-    /**
273
-     * Is this transfer an automated backup of a push transfer?
274
-     * @return boolean
275
-     */
276
-    public function IsBackupDataTransfer()
277
-    {
278
-        return DB::query(sprintf(
279
-            'SELECT COUNT("ID") FROM "DNDataTransfer" WHERE "BackupDataTransferID" = %d',
280
-            $this->ID
281
-        ))->value();
282
-    }
283
-
284
-    /**
285
-     * Returns the status of the resque job
286
-     *
287
-     * @return string
288
-     */
289
-    public function ResqueStatus()
290
-    {
291
-        $status = new Resque_Job_Status($this->ResqueToken);
292
-        $statusCode = $status->get();
293
-        // The Resque job can no longer be found, fallback to the DNDataTransfer.Status
294
-        if ($statusCode === false) {
295
-            // Translate from the DNDataTransfer.Status to the Resque job status for UI purposes
296
-            switch ($this->Status) {
297
-                case 'Finished':
298
-                    return 'Complete';
299
-                case 'Started':
300
-                    return 'Running';
301
-                default:
302
-                    return $this->Status;
303
-            }
304
-        }
305
-        return self::map_resque_status($statusCode);
306
-    }
37
+	private static $db = array(
38
+		"ResqueToken" => "Varchar(255)",
39
+		// Observe that this is not the same as Resque status, since ResqueStatus is not persistent.
40
+		"Status" => "Enum('Queued, Started, Finished, Failed, n/a', 'n/a')",
41
+		"Direction" => "Enum('get, push', 'get')",
42
+		"Mode" => "Enum('all, assets, db', '')",
43
+		"Origin" => "Enum('EnvironmentTransfer,ManualUpload', 'EnvironmentTransfer')",
44
+	);
45
+
46
+	private static $has_one = array(
47
+		"Environment" => "DNEnvironment",
48
+		"Author" => "Member",
49
+		"DataArchive" => "DNDataArchive",
50
+		"BackupDataTransfer" => "DNDataTransfer" // denotes an automated backup done for a push of this data transfer
51
+	);
52
+
53
+	private static $singular_name = 'Data Transfer';
54
+
55
+	private static $plural_name = 'Data Transfers';
56
+
57
+	private static $summary_fields = array(
58
+		'Created' => 'Created',
59
+		'Author.Title' => 'Author',
60
+		'Environment.Project.Name' => 'Project',
61
+		'Environment.Name' => 'Environment',
62
+		'Status' => 'Status',
63
+		'Origin' => 'Origin',
64
+	);
65
+
66
+	private static $searchable_fields = array(
67
+		'Environment.Project.Name' => array(
68
+			'title' => 'Project',
69
+		),
70
+		'Environment.Name' => array(
71
+			'title' => 'Environment',
72
+		),
73
+		'Status' => array(
74
+			'title' => 'Status',
75
+		),
76
+		'Origin' => array(
77
+			'title' => 'Origin',
78
+		),
79
+		'Mode' => array(
80
+			'title' => 'Mode',
81
+		),
82
+		'Direction' => array(
83
+			'title' => 'Direction',
84
+		),
85
+	);
86
+
87
+	/**
88
+	 * When running the transfer, should a backup be performed before pushing the data?
89
+	 * @var bool
90
+	 */
91
+	protected $backupBeforePush = true;
92
+
93
+	/**
94
+	 * @param int $int
95
+	 * @return string
96
+	 */
97
+	public static function map_resque_status($int)
98
+	{
99
+		$remap = array(
100
+			Resque_Job_Status::STATUS_WAITING => "Queued",
101
+			Resque_Job_Status::STATUS_RUNNING => "Running",
102
+			Resque_Job_Status::STATUS_FAILED => "Failed",
103
+			Resque_Job_Status::STATUS_COMPLETE => "Complete",
104
+			false => "Invalid",
105
+		);
106
+		return $remap[$int];
107
+	}
108
+
109
+	/**
110
+	 * @param boolean $value
111
+	 */
112
+	public function setBackupBeforePush($value)
113
+	{
114
+		$this->backupBeforePush = $value;
115
+	}
116
+
117
+	public function getTitle()
118
+	{
119
+		return $this->dbObject('Created')->Nice() . " (Status: {$this->Status})";
120
+	}
121
+
122
+	public function Link()
123
+	{
124
+		return Controller::join_links($this->Environment()->Project()->Link(), 'transfer', $this->ID);
125
+	}
126
+
127
+	public function LogLink()
128
+	{
129
+		return Controller::join_links($this->Link(), 'log');
130
+	}
131
+
132
+	public function getDefaultSearchContext()
133
+	{
134
+		$context = parent::getDefaultSearchContext();
135
+		$context->getFields()->dataFieldByName('Status')->setHasEmptyDefault(true);
136
+		$context->getFields()->dataFieldByName('Origin')->setHasEmptyDefault(true);
137
+
138
+		return $context;
139
+	}
140
+
141
+	public function getCMSFields()
142
+	{
143
+		$fields = parent::getCMSFields();
144
+		$fields->removeByName('EnvironmentID');
145
+		$fields->removeByName('ArchiveFile');
146
+		$fields->addFieldsToTab(
147
+			'Root.Main',
148
+			array(
149
+				new ReadonlyField('ProjectName', 'Project', $this->Environment()->Project()->Name),
150
+				new ReadonlyField('EnvironmentName', 'Environment', $this->Environment()->Name),
151
+				new ReadonlyField(
152
+					'DataArchive',
153
+					'Archive File',
154
+					sprintf(
155
+						'<a href="%s">%s</a>',
156
+						$this->DataArchive()->ArchiveFile()->AbsoluteURL,
157
+						$this->DataArchive()->ArchiveFile()->Filename
158
+					)
159
+				),
160
+			)
161
+		);
162
+		$fields = $fields->makeReadonly();
163
+
164
+		return $fields;
165
+	}
166
+
167
+	/**
168
+	 * Queue a transfer job
169
+	 */
170
+	public function start()
171
+	{
172
+		$env = $this->Environment();
173
+		$log = $this->log();
174
+
175
+		$args = array(
176
+			'dataTransferID' => $this->ID,
177
+			'logfile' => $this->logfile(),
178
+			'backupBeforePush' => $this->backupBeforePush
179
+		);
180
+
181
+		if (!$this->AuthorID) {
182
+			$this->AuthorID = Member::currentUserID();
183
+		}
184
+
185
+		if ($this->AuthorID) {
186
+			$author = $this->Author();
187
+			$message = sprintf(
188
+				'Data transfer on %s (%s, %s) initiated by %s (%s), with IP address %s',
189
+				$env->getFullName(),
190
+				$this->Direction,
191
+				$this->Mode,
192
+				$author->getName(),
193
+				$author->Email,
194
+				Controller::curr()->getRequest()->getIP()
195
+			);
196
+			$log->write($message);
197
+		}
198
+
199
+		$token = Resque::enqueue('git', 'DataTransferJob', $args, true);
200
+		$this->ResqueToken = $token;
201
+		$this->write();
202
+
203
+		$message = sprintf('Data transfer queued as job %s', $token);
204
+		$log->write($message);
205
+	}
206
+
207
+	/**
208
+	 * @param Member|null $member
209
+	 * @return bool
210
+	 */
211
+	public function canView($member = null)
212
+	{
213
+		return $this->Environment()->canView($member);
214
+	}
215
+
216
+	/**
217
+	 * Return a path to the log file.
218
+	 * @return string
219
+	 */
220
+	protected function logfile()
221
+	{
222
+		return sprintf(
223
+			'%s.datatransfer.%s.log',
224
+			$this->Environment()->getFullName('.'),
225
+			$this->ID
226
+		);
227
+	}
228
+
229
+	/**
230
+	 * @return \DeploynautLogFile
231
+	 */
232
+	public function log()
233
+	{
234
+		return new DeploynautLogFile($this->logfile());
235
+	}
236
+
237
+	/**
238
+	 * @return string
239
+	 */
240
+	public function LogContent()
241
+	{
242
+		return $this->log()->content();
243
+	}
244
+
245
+	public function getDescription()
246
+	{
247
+		$envName = $this->Environment()->getFullName();
248
+		if ($this->Direction == 'get') {
249
+			if ($this->Origin == 'ManualUpload') {
250
+				$description = 'Manual upload of ' . $this->getModeNice() . ' to ' . $envName;
251
+			} elseif ($this->IsBackupDataTransfer()) {
252
+				$description = 'Automated backup of ' . $this->getModeNice() . ' from ' . $envName;
253
+			} else {
254
+				$description = 'Backup of ' . $this->getModeNice() . ' to ' . $envName;
255
+			}
256
+		} else {
257
+			$description = 'Restore ' . $this->getModeNice() . ' to ' . $envName;
258
+		}
259
+
260
+		return $description;
261
+	}
262
+
263
+	public function getModeNice()
264
+	{
265
+		if ($this->Mode == 'all') {
266
+			return 'database and assets';
267
+		} else {
268
+			return $this->Mode;
269
+		}
270
+	}
271
+
272
+	/**
273
+	 * Is this transfer an automated backup of a push transfer?
274
+	 * @return boolean
275
+	 */
276
+	public function IsBackupDataTransfer()
277
+	{
278
+		return DB::query(sprintf(
279
+			'SELECT COUNT("ID") FROM "DNDataTransfer" WHERE "BackupDataTransferID" = %d',
280
+			$this->ID
281
+		))->value();
282
+	}
283
+
284
+	/**
285
+	 * Returns the status of the resque job
286
+	 *
287
+	 * @return string
288
+	 */
289
+	public function ResqueStatus()
290
+	{
291
+		$status = new Resque_Job_Status($this->ResqueToken);
292
+		$statusCode = $status->get();
293
+		// The Resque job can no longer be found, fallback to the DNDataTransfer.Status
294
+		if ($statusCode === false) {
295
+			// Translate from the DNDataTransfer.Status to the Resque job status for UI purposes
296
+			switch ($this->Status) {
297
+				case 'Finished':
298
+					return 'Complete';
299
+				case 'Started':
300
+					return 'Running';
301
+				default:
302
+					return $this->Status;
303
+			}
304
+		}
305
+		return self::map_resque_status($statusCode);
306
+	}
307 307
 }
Please login to merge, or discard this patch.
code/model/jobs/DNDeployment.php 1 patch
Indentation   +340 added lines, -340 removed lines patch added patch discarded remove patch
@@ -15,344 +15,344 @@
 block discarded – undo
15 15
 class DNDeployment extends DataObject
16 16
 {
17 17
 
18
-    /**
19
-     * @var array
20
-     */
21
-    private static $db = array(
22
-        "SHA" => "GitSHA",
23
-        "ResqueToken" => "Varchar(255)",
24
-        // Observe that this is not the same as Resque status, since ResqueStatus is not persistent
25
-        // It's used for finding successful deployments and displaying that in history views in the frontend
26
-        "Status" => "Enum('Queued, Started, Finished, Failed, n/a', 'n/a')",
27
-        // JSON serialised DeploymentStrategy.
28
-        "Strategy" => "Text"
29
-    );
30
-
31
-    /**
32
-     * @var array
33
-     */
34
-    private static $has_one = array(
35
-        "Environment" => "DNEnvironment",
36
-        "Deployer" => "Member",
37
-    );
38
-
39
-    private static $default_sort = '"LastEdited" DESC';
40
-
41
-    public function getTitle()
42
-    {
43
-        return "#{$this->ID}: {$this->SHA} (Status: {$this->Status})";
44
-    }
45
-
46
-    private static $summary_fields = array(
47
-        'LastEdited' => 'Last Edited',
48
-        'SHA' => 'SHA',
49
-        'Status' => 'Status',
50
-        'Deployer.Name' => 'Deployer'
51
-    );
52
-
53
-    /**
54
-     * @param int $int
55
-     * @return string
56
-     */
57
-    public static function map_resque_status($int)
58
-    {
59
-        $remap = array(
60
-            Resque_Job_Status::STATUS_WAITING => "Queued",
61
-            Resque_Job_Status::STATUS_RUNNING => "Running",
62
-            Resque_Job_Status::STATUS_FAILED => "Failed",
63
-            Resque_Job_Status::STATUS_COMPLETE => "Complete",
64
-            false => "Invalid",
65
-        );
66
-        return $remap[$int];
67
-    }
68
-
69
-
70
-    public function Link()
71
-    {
72
-        return Controller::join_links($this->Environment()->Link(), 'deploy', $this->ID);
73
-    }
74
-
75
-    public function LogLink()
76
-    {
77
-        return $this->Link() . '/log';
78
-    }
79
-
80
-    public function canView($member = null)
81
-    {
82
-        return $this->Environment()->canView($member);
83
-    }
84
-
85
-    /**
86
-     * Return a path to the log file.
87
-     * @return string
88
-     */
89
-    protected function logfile()
90
-    {
91
-        return sprintf(
92
-            '%s.%s.log',
93
-            $this->Environment()->getFullName('.'),
94
-            $this->ID
95
-        );
96
-    }
97
-
98
-    /**
99
-     * @return DeploynautLogFile
100
-     */
101
-    public function log()
102
-    {
103
-        return Injector::inst()->createWithArgs('DeploynautLogFile', array($this->logfile()));
104
-    }
105
-
106
-    public function LogContent()
107
-    {
108
-        return $this->log()->content();
109
-    }
110
-
111
-    /**
112
-     * Returns the status of the resque job
113
-     *
114
-     * @return string
115
-     */
116
-    public function ResqueStatus()
117
-    {
118
-        $status = new Resque_Job_Status($this->ResqueToken);
119
-        $statusCode = $status->get();
120
-        // The Resque job can no longer be found, fallback to the DNDeployment.Status
121
-        if ($statusCode === false) {
122
-            // Translate from the DNDeployment.Status to the Resque job status for UI purposes
123
-            switch ($this->Status) {
124
-                case 'Finished':
125
-                    return 'Complete';
126
-                case 'Started':
127
-                    return 'Running';
128
-                default:
129
-                    return $this->Status;
130
-            }
131
-        }
132
-        return self::map_resque_status($statusCode);
133
-    }
134
-
135
-
136
-    /**
137
-     * Fetch the git repository
138
-     *
139
-     * @return \Gitonomy\Git\Repository|null
140
-     */
141
-    public function getRepository()
142
-    {
143
-        if (!$this->SHA) {
144
-            return null;
145
-        }
146
-        return $this->Environment()->Project()->getRepository();
147
-    }
148
-
149
-
150
-    /**
151
-     * Gets the commit from source. The result is cached upstream in Repository.
152
-     *
153
-     * @return \Gitonomy\Git\Commit|null
154
-     */
155
-    public function getCommit()
156
-    {
157
-        $repo = $this->getRepository();
158
-        if ($repo) {
159
-            try {
160
-                return $repo->getCommit($this->SHA);
161
-            } catch (Gitonomy\Git\Exception\ReferenceNotFoundException $ex) {
162
-                return null;
163
-            }
164
-        }
165
-
166
-        return null;
167
-    }
168
-
169
-
170
-    /**
171
-     * Gets the commit message.
172
-     *
173
-     * @return string|null
174
-     */
175
-    public function getCommitMessage()
176
-    {
177
-        $commit = $this->getCommit();
178
-        if ($commit) {
179
-            try {
180
-                return Convert::raw2xml($commit->getMessage());
181
-            } catch (Gitonomy\Git\Exception\ReferenceNotFoundException $e) {
182
-                return null;
183
-            }
184
-        }
185
-        return null;
186
-    }
187
-
188
-    /**
189
-     * Return all tags for the deployed commit.
190
-     *
191
-     * @return ArrayList
192
-     */
193
-    public function getTags()
194
-    {
195
-        $returnTags = array();
196
-        $repo = $this->getRepository();
197
-        if ($repo) {
198
-            $tags = $repo->getReferences()->resolveTags($this->SHA);
199
-            if (!empty($tags)) {
200
-                foreach ($tags as $tag) {
201
-                    $field = Varchar::create('Tag', '255');
202
-                    $field->setValue($tag->getName());
203
-                    $returnTags[] = $field;
204
-                }
205
-            }
206
-        }
207
-        return new ArrayList($returnTags);
208
-    }
209
-
210
-    /**
211
-     * Collate the list of additional flags to affix to this deployment.
212
-     * Elements of the array will be rendered literally.
213
-     *
214
-     * @return ArrayList
215
-     */
216
-    public function getFullDeployMessages()
217
-    {
218
-        $strategy = $this->getDeploymentStrategy();
219
-        if ($strategy->getActionCode()!=='full') {
220
-            return null;
221
-        }
222
-
223
-        $changes = $strategy->getChangesModificationNeeded();
224
-        $messages = [];
225
-        foreach ($changes as $change => $details) {
226
-            if ($change==='Code version') {
227
-                continue;
228
-            }
229
-
230
-            $messages[] = [
231
-                'Flag' => sprintf(
232
-                    '<span class="label label-default full-deploy-info-item">%s</span>',
233
-                    $change[0]
234
-                ),
235
-                'Text' => sprintf('%s changed', $change)
236
-            ];
237
-        }
238
-
239
-        if (empty($messages)) {
240
-            $messages[] = [
241
-                'Flag' => '',
242
-                'Text' => '<i>Environment changes have been made.</i>'
243
-            ];
244
-        }
245
-
246
-        return new ArrayList($messages);
247
-    }
248
-
249
-    /**
250
-     * Fetches the latest tag for the deployed commit
251
-     *
252
-     * @return \Varchar|null
253
-     */
254
-    public function getTag()
255
-    {
256
-        $tags = $this->getTags();
257
-        if ($tags->count() > 0) {
258
-            return $tags->last();
259
-        }
260
-        return null;
261
-    }
262
-
263
-    /**
264
-     * @return DeploymentStrategy
265
-     */
266
-    public function getDeploymentStrategy()
267
-    {
268
-        $environment = $this->Environment();
269
-        $strategy = new DeploymentStrategy($environment);
270
-        $strategy->fromJSON($this->Strategy);
271
-        return $strategy;
272
-    }
273
-
274
-    /**
275
-     * Return a list of things that are going to be deployed, such
276
-     * as the code version, and any infrastrucutral changes.
277
-     *
278
-     * @return ArrayList
279
-     */
280
-    public function getChanges()
281
-    {
282
-        $list = new ArrayList();
283
-        $strategy = $this->getDeploymentStrategy();
284
-        foreach ($strategy->getChanges() as $name => $change) {
285
-            if (empty($change['to'])) {
286
-                continue;
287
-            }
288
-
289
-            $list->push(new ArrayData([
290
-                'Name' => $name,
291
-                'From' => $change['from'],
292
-                'To' => $change['to'],
293
-                'Description' => isset($change['description']) ? $change['description'] : '',
294
-                'Changed' => $change['from'] != $change['to'],
295
-                'CompareUrl' => isset($change['compareUrl']) ? $change['compareUrl'] : ''
296
-            ]));
297
-        }
298
-
299
-        return $list;
300
-    }
301
-
302
-    /**
303
-     * Start a resque job for this deployment
304
-     *
305
-     * @return string Resque token
306
-     */
307
-    protected function enqueueDeployment()
308
-    {
309
-        $environment = $this->Environment();
310
-        $project = $environment->Project();
311
-        $log = $this->log();
312
-
313
-        $args = array(
314
-            'environmentName' => $environment->Name,
315
-            'repository' => $project->getLocalCVSPath(),
316
-            'logfile' => $this->logfile(),
317
-            'projectName' => $project->Name,
318
-            'env' => $project->getProcessEnv(),
319
-            'deploymentID' => $this->ID
320
-        );
321
-
322
-        $strategy = $this->getDeploymentStrategy();
323
-        // Inject options.
324
-        $args = array_merge($args, $strategy->getOptions());
325
-        // Make sure we use the SHA as it was written into this DNDeployment.
326
-        $args['sha'] = $this->SHA;
327
-
328
-        if (!$this->DeployerID) {
329
-            $this->DeployerID = Member::currentUserID();
330
-        }
331
-
332
-        if ($this->DeployerID) {
333
-            $deployer = $this->Deployer();
334
-            $message = sprintf(
335
-                'Deploy to %s initiated by %s (%s), with IP address %s',
336
-                $environment->getFullName(),
337
-                $deployer->getName(),
338
-                $deployer->Email,
339
-                Controller::curr()->getRequest()->getIP()
340
-            );
341
-            $log->write($message);
342
-        }
343
-
344
-        return Resque::enqueue('deploy', 'DeployJob', $args, true);
345
-    }
346
-
347
-    public function start()
348
-    {
349
-        $log = $this->log();
350
-        $token = $this->enqueueDeployment();
351
-        $this->ResqueToken = $token;
352
-        $this->Status = 'Queued';
353
-        $this->write();
354
-
355
-        $message = sprintf('Deploy queued as job %s', $token);
356
-        $log->write($message);
357
-    }
18
+	/**
19
+	 * @var array
20
+	 */
21
+	private static $db = array(
22
+		"SHA" => "GitSHA",
23
+		"ResqueToken" => "Varchar(255)",
24
+		// Observe that this is not the same as Resque status, since ResqueStatus is not persistent
25
+		// It's used for finding successful deployments and displaying that in history views in the frontend
26
+		"Status" => "Enum('Queued, Started, Finished, Failed, n/a', 'n/a')",
27
+		// JSON serialised DeploymentStrategy.
28
+		"Strategy" => "Text"
29
+	);
30
+
31
+	/**
32
+	 * @var array
33
+	 */
34
+	private static $has_one = array(
35
+		"Environment" => "DNEnvironment",
36
+		"Deployer" => "Member",
37
+	);
38
+
39
+	private static $default_sort = '"LastEdited" DESC';
40
+
41
+	public function getTitle()
42
+	{
43
+		return "#{$this->ID}: {$this->SHA} (Status: {$this->Status})";
44
+	}
45
+
46
+	private static $summary_fields = array(
47
+		'LastEdited' => 'Last Edited',
48
+		'SHA' => 'SHA',
49
+		'Status' => 'Status',
50
+		'Deployer.Name' => 'Deployer'
51
+	);
52
+
53
+	/**
54
+	 * @param int $int
55
+	 * @return string
56
+	 */
57
+	public static function map_resque_status($int)
58
+	{
59
+		$remap = array(
60
+			Resque_Job_Status::STATUS_WAITING => "Queued",
61
+			Resque_Job_Status::STATUS_RUNNING => "Running",
62
+			Resque_Job_Status::STATUS_FAILED => "Failed",
63
+			Resque_Job_Status::STATUS_COMPLETE => "Complete",
64
+			false => "Invalid",
65
+		);
66
+		return $remap[$int];
67
+	}
68
+
69
+
70
+	public function Link()
71
+	{
72
+		return Controller::join_links($this->Environment()->Link(), 'deploy', $this->ID);
73
+	}
74
+
75
+	public function LogLink()
76
+	{
77
+		return $this->Link() . '/log';
78
+	}
79
+
80
+	public function canView($member = null)
81
+	{
82
+		return $this->Environment()->canView($member);
83
+	}
84
+
85
+	/**
86
+	 * Return a path to the log file.
87
+	 * @return string
88
+	 */
89
+	protected function logfile()
90
+	{
91
+		return sprintf(
92
+			'%s.%s.log',
93
+			$this->Environment()->getFullName('.'),
94
+			$this->ID
95
+		);
96
+	}
97
+
98
+	/**
99
+	 * @return DeploynautLogFile
100
+	 */
101
+	public function log()
102
+	{
103
+		return Injector::inst()->createWithArgs('DeploynautLogFile', array($this->logfile()));
104
+	}
105
+
106
+	public function LogContent()
107
+	{
108
+		return $this->log()->content();
109
+	}
110
+
111
+	/**
112
+	 * Returns the status of the resque job
113
+	 *
114
+	 * @return string
115
+	 */
116
+	public function ResqueStatus()
117
+	{
118
+		$status = new Resque_Job_Status($this->ResqueToken);
119
+		$statusCode = $status->get();
120
+		// The Resque job can no longer be found, fallback to the DNDeployment.Status
121
+		if ($statusCode === false) {
122
+			// Translate from the DNDeployment.Status to the Resque job status for UI purposes
123
+			switch ($this->Status) {
124
+				case 'Finished':
125
+					return 'Complete';
126
+				case 'Started':
127
+					return 'Running';
128
+				default:
129
+					return $this->Status;
130
+			}
131
+		}
132
+		return self::map_resque_status($statusCode);
133
+	}
134
+
135
+
136
+	/**
137
+	 * Fetch the git repository
138
+	 *
139
+	 * @return \Gitonomy\Git\Repository|null
140
+	 */
141
+	public function getRepository()
142
+	{
143
+		if (!$this->SHA) {
144
+			return null;
145
+		}
146
+		return $this->Environment()->Project()->getRepository();
147
+	}
148
+
149
+
150
+	/**
151
+	 * Gets the commit from source. The result is cached upstream in Repository.
152
+	 *
153
+	 * @return \Gitonomy\Git\Commit|null
154
+	 */
155
+	public function getCommit()
156
+	{
157
+		$repo = $this->getRepository();
158
+		if ($repo) {
159
+			try {
160
+				return $repo->getCommit($this->SHA);
161
+			} catch (Gitonomy\Git\Exception\ReferenceNotFoundException $ex) {
162
+				return null;
163
+			}
164
+		}
165
+
166
+		return null;
167
+	}
168
+
169
+
170
+	/**
171
+	 * Gets the commit message.
172
+	 *
173
+	 * @return string|null
174
+	 */
175
+	public function getCommitMessage()
176
+	{
177
+		$commit = $this->getCommit();
178
+		if ($commit) {
179
+			try {
180
+				return Convert::raw2xml($commit->getMessage());
181
+			} catch (Gitonomy\Git\Exception\ReferenceNotFoundException $e) {
182
+				return null;
183
+			}
184
+		}
185
+		return null;
186
+	}
187
+
188
+	/**
189
+	 * Return all tags for the deployed commit.
190
+	 *
191
+	 * @return ArrayList
192
+	 */
193
+	public function getTags()
194
+	{
195
+		$returnTags = array();
196
+		$repo = $this->getRepository();
197
+		if ($repo) {
198
+			$tags = $repo->getReferences()->resolveTags($this->SHA);
199
+			if (!empty($tags)) {
200
+				foreach ($tags as $tag) {
201
+					$field = Varchar::create('Tag', '255');
202
+					$field->setValue($tag->getName());
203
+					$returnTags[] = $field;
204
+				}
205
+			}
206
+		}
207
+		return new ArrayList($returnTags);
208
+	}
209
+
210
+	/**
211
+	 * Collate the list of additional flags to affix to this deployment.
212
+	 * Elements of the array will be rendered literally.
213
+	 *
214
+	 * @return ArrayList
215
+	 */
216
+	public function getFullDeployMessages()
217
+	{
218
+		$strategy = $this->getDeploymentStrategy();
219
+		if ($strategy->getActionCode()!=='full') {
220
+			return null;
221
+		}
222
+
223
+		$changes = $strategy->getChangesModificationNeeded();
224
+		$messages = [];
225
+		foreach ($changes as $change => $details) {
226
+			if ($change==='Code version') {
227
+				continue;
228
+			}
229
+
230
+			$messages[] = [
231
+				'Flag' => sprintf(
232
+					'<span class="label label-default full-deploy-info-item">%s</span>',
233
+					$change[0]
234
+				),
235
+				'Text' => sprintf('%s changed', $change)
236
+			];
237
+		}
238
+
239
+		if (empty($messages)) {
240
+			$messages[] = [
241
+				'Flag' => '',
242
+				'Text' => '<i>Environment changes have been made.</i>'
243
+			];
244
+		}
245
+
246
+		return new ArrayList($messages);
247
+	}
248
+
249
+	/**
250
+	 * Fetches the latest tag for the deployed commit
251
+	 *
252
+	 * @return \Varchar|null
253
+	 */
254
+	public function getTag()
255
+	{
256
+		$tags = $this->getTags();
257
+		if ($tags->count() > 0) {
258
+			return $tags->last();
259
+		}
260
+		return null;
261
+	}
262
+
263
+	/**
264
+	 * @return DeploymentStrategy
265
+	 */
266
+	public function getDeploymentStrategy()
267
+	{
268
+		$environment = $this->Environment();
269
+		$strategy = new DeploymentStrategy($environment);
270
+		$strategy->fromJSON($this->Strategy);
271
+		return $strategy;
272
+	}
273
+
274
+	/**
275
+	 * Return a list of things that are going to be deployed, such
276
+	 * as the code version, and any infrastrucutral changes.
277
+	 *
278
+	 * @return ArrayList
279
+	 */
280
+	public function getChanges()
281
+	{
282
+		$list = new ArrayList();
283
+		$strategy = $this->getDeploymentStrategy();
284
+		foreach ($strategy->getChanges() as $name => $change) {
285
+			if (empty($change['to'])) {
286
+				continue;
287
+			}
288
+
289
+			$list->push(new ArrayData([
290
+				'Name' => $name,
291
+				'From' => $change['from'],
292
+				'To' => $change['to'],
293
+				'Description' => isset($change['description']) ? $change['description'] : '',
294
+				'Changed' => $change['from'] != $change['to'],
295
+				'CompareUrl' => isset($change['compareUrl']) ? $change['compareUrl'] : ''
296
+			]));
297
+		}
298
+
299
+		return $list;
300
+	}
301
+
302
+	/**
303
+	 * Start a resque job for this deployment
304
+	 *
305
+	 * @return string Resque token
306
+	 */
307
+	protected function enqueueDeployment()
308
+	{
309
+		$environment = $this->Environment();
310
+		$project = $environment->Project();
311
+		$log = $this->log();
312
+
313
+		$args = array(
314
+			'environmentName' => $environment->Name,
315
+			'repository' => $project->getLocalCVSPath(),
316
+			'logfile' => $this->logfile(),
317
+			'projectName' => $project->Name,
318
+			'env' => $project->getProcessEnv(),
319
+			'deploymentID' => $this->ID
320
+		);
321
+
322
+		$strategy = $this->getDeploymentStrategy();
323
+		// Inject options.
324
+		$args = array_merge($args, $strategy->getOptions());
325
+		// Make sure we use the SHA as it was written into this DNDeployment.
326
+		$args['sha'] = $this->SHA;
327
+
328
+		if (!$this->DeployerID) {
329
+			$this->DeployerID = Member::currentUserID();
330
+		}
331
+
332
+		if ($this->DeployerID) {
333
+			$deployer = $this->Deployer();
334
+			$message = sprintf(
335
+				'Deploy to %s initiated by %s (%s), with IP address %s',
336
+				$environment->getFullName(),
337
+				$deployer->getName(),
338
+				$deployer->Email,
339
+				Controller::curr()->getRequest()->getIP()
340
+			);
341
+			$log->write($message);
342
+		}
343
+
344
+		return Resque::enqueue('deploy', 'DeployJob', $args, true);
345
+	}
346
+
347
+	public function start()
348
+	{
349
+		$log = $this->log();
350
+		$token = $this->enqueueDeployment();
351
+		$this->ResqueToken = $token;
352
+		$this->Status = 'Queued';
353
+		$this->write();
354
+
355
+		$message = sprintf('Deploy queued as job %s', $token);
356
+		$log->write($message);
357
+	}
358 358
 }
Please login to merge, or discard this patch.
code/model/jobs/DNGitFetch.php 1 patch
Indentation   +138 added lines, -138 removed lines patch added patch discarded remove patch
@@ -14,142 +14,142 @@
 block discarded – undo
14 14
 class DNGitFetch extends DataObject
15 15
 {
16 16
 
17
-    /**
18
-     * @var array
19
-     */
20
-    private static $db = array(
21
-        "ResqueToken" => "Varchar(255)",
22
-        // Observe that this is not the same as Resque status, since ResqueStatus is not persistent
23
-        // It's used for finding successful deployments and displaying that in history views in the frontend
24
-        "Status" => "Enum('Queued, Started, Finished, Failed, n/a', 'n/a')",
25
-    );
26
-
27
-    /**
28
-     * @var array
29
-     */
30
-    private static $has_one = array(
31
-        "Project" => "DNProject",
32
-        "Deployer" => "Member"
33
-    );
34
-
35
-    /**
36
-     * @param int $int
37
-     * @return string
38
-     */
39
-    public static function map_resque_status($int)
40
-    {
41
-        $remap = array(
42
-            Resque_Job_Status::STATUS_WAITING => "Queued",
43
-            Resque_Job_Status::STATUS_RUNNING => "Running",
44
-            Resque_Job_Status::STATUS_FAILED => "Failed",
45
-            Resque_Job_Status::STATUS_COMPLETE => "Complete",
46
-            false => "Invalid",
47
-        );
48
-        return $remap[$int];
49
-    }
50
-
51
-    /**
52
-     * Queue a fetch job
53
-     * @param bool $forceClone Force repository to be re-cloned
54
-     */
55
-    public function start($forceClone = false)
56
-    {
57
-        $project = $this->Project();
58
-        $log = $this->log();
59
-
60
-        if (!$this->DeployerID) {
61
-            $this->DeployerID = Member::currentUserID();
62
-        }
63
-
64
-        if ($this->DeployerID) {
65
-            $deployer = $this->Deployer();
66
-            $message = sprintf(
67
-                'Update repository job for %s initiated by %s (%s)',
68
-                $project->Name,
69
-                $deployer->getName(),
70
-                $deployer->Email
71
-            );
72
-            $log->write($message);
73
-        }
74
-
75
-        // write first, so we have the ID. We have to write again
76
-        // later once we have the resque token.
77
-        $this->write();
78
-
79
-        $args = array(
80
-            'projectID' => $project->ID,
81
-            'logfile' => $this->logfile(),
82
-            'fetchID' => $this->ID,
83
-            'forceClone' => $forceClone
84
-        );
85
-
86
-        $token = Resque::enqueue('git', 'FetchJob', $args, true);
87
-        $this->ResqueToken = $token;
88
-        $this->write();
89
-
90
-        $message = sprintf('Fetch queued as job %s', $token);
91
-        $log->write($message);
92
-    }
93
-
94
-    /**
95
-     * @param Member|null $member
96
-     * @return bool
97
-     */
98
-    public function canView($member = null)
99
-    {
100
-        return $this->Project()->canView($member);
101
-    }
102
-
103
-    /**
104
-     * Return a path to the log file.
105
-     * @return string
106
-     */
107
-    protected function logfile()
108
-    {
109
-        return sprintf(
110
-            '%s.fetch.%s.log',
111
-            $this->Project()->Name,
112
-            $this->ID
113
-        );
114
-    }
115
-
116
-    /**
117
-     * @return \DeploynautLogFile
118
-     */
119
-    public function log()
120
-    {
121
-        return new DeploynautLogFile($this->logfile());
122
-    }
123
-
124
-    /**
125
-     * @return string
126
-     */
127
-    public function LogContent()
128
-    {
129
-        return $this->log()->content();
130
-    }
131
-
132
-    /**
133
-     * Returns the status of the resque job
134
-     *
135
-     * @return string
136
-     */
137
-    public function ResqueStatus()
138
-    {
139
-        $status = new Resque_Job_Status($this->ResqueToken);
140
-        $statusCode = $status->get();
141
-        // The Resque job can no longer be found, fallback to the DNDeployment.Status
142
-        if ($statusCode === false) {
143
-            // Translate from the DNDeployment.Status to the Resque job status for UI purposes
144
-            switch ($this->Status) {
145
-                case 'Finished':
146
-                    return 'Complete';
147
-                case 'Started':
148
-                    return 'Running';
149
-                default:
150
-                    return $this->Status;
151
-            }
152
-        }
153
-        return self::map_resque_status($statusCode);
154
-    }
17
+	/**
18
+	 * @var array
19
+	 */
20
+	private static $db = array(
21
+		"ResqueToken" => "Varchar(255)",
22
+		// Observe that this is not the same as Resque status, since ResqueStatus is not persistent
23
+		// It's used for finding successful deployments and displaying that in history views in the frontend
24
+		"Status" => "Enum('Queued, Started, Finished, Failed, n/a', 'n/a')",
25
+	);
26
+
27
+	/**
28
+	 * @var array
29
+	 */
30
+	private static $has_one = array(
31
+		"Project" => "DNProject",
32
+		"Deployer" => "Member"
33
+	);
34
+
35
+	/**
36
+	 * @param int $int
37
+	 * @return string
38
+	 */
39
+	public static function map_resque_status($int)
40
+	{
41
+		$remap = array(
42
+			Resque_Job_Status::STATUS_WAITING => "Queued",
43
+			Resque_Job_Status::STATUS_RUNNING => "Running",
44
+			Resque_Job_Status::STATUS_FAILED => "Failed",
45
+			Resque_Job_Status::STATUS_COMPLETE => "Complete",
46
+			false => "Invalid",
47
+		);
48
+		return $remap[$int];
49
+	}
50
+
51
+	/**
52
+	 * Queue a fetch job
53
+	 * @param bool $forceClone Force repository to be re-cloned
54
+	 */
55
+	public function start($forceClone = false)
56
+	{
57
+		$project = $this->Project();
58
+		$log = $this->log();
59
+
60
+		if (!$this->DeployerID) {
61
+			$this->DeployerID = Member::currentUserID();
62
+		}
63
+
64
+		if ($this->DeployerID) {
65
+			$deployer = $this->Deployer();
66
+			$message = sprintf(
67
+				'Update repository job for %s initiated by %s (%s)',
68
+				$project->Name,
69
+				$deployer->getName(),
70
+				$deployer->Email
71
+			);
72
+			$log->write($message);
73
+		}
74
+
75
+		// write first, so we have the ID. We have to write again
76
+		// later once we have the resque token.
77
+		$this->write();
78
+
79
+		$args = array(
80
+			'projectID' => $project->ID,
81
+			'logfile' => $this->logfile(),
82
+			'fetchID' => $this->ID,
83
+			'forceClone' => $forceClone
84
+		);
85
+
86
+		$token = Resque::enqueue('git', 'FetchJob', $args, true);
87
+		$this->ResqueToken = $token;
88
+		$this->write();
89
+
90
+		$message = sprintf('Fetch queued as job %s', $token);
91
+		$log->write($message);
92
+	}
93
+
94
+	/**
95
+	 * @param Member|null $member
96
+	 * @return bool
97
+	 */
98
+	public function canView($member = null)
99
+	{
100
+		return $this->Project()->canView($member);
101
+	}
102
+
103
+	/**
104
+	 * Return a path to the log file.
105
+	 * @return string
106
+	 */
107
+	protected function logfile()
108
+	{
109
+		return sprintf(
110
+			'%s.fetch.%s.log',
111
+			$this->Project()->Name,
112
+			$this->ID
113
+		);
114
+	}
115
+
116
+	/**
117
+	 * @return \DeploynautLogFile
118
+	 */
119
+	public function log()
120
+	{
121
+		return new DeploynautLogFile($this->logfile());
122
+	}
123
+
124
+	/**
125
+	 * @return string
126
+	 */
127
+	public function LogContent()
128
+	{
129
+		return $this->log()->content();
130
+	}
131
+
132
+	/**
133
+	 * Returns the status of the resque job
134
+	 *
135
+	 * @return string
136
+	 */
137
+	public function ResqueStatus()
138
+	{
139
+		$status = new Resque_Job_Status($this->ResqueToken);
140
+		$statusCode = $status->get();
141
+		// The Resque job can no longer be found, fallback to the DNDeployment.Status
142
+		if ($statusCode === false) {
143
+			// Translate from the DNDeployment.Status to the Resque job status for UI purposes
144
+			switch ($this->Status) {
145
+				case 'Finished':
146
+					return 'Complete';
147
+				case 'Started':
148
+					return 'Running';
149
+				default:
150
+					return $this->Status;
151
+			}
152
+		}
153
+		return self::map_resque_status($statusCode);
154
+	}
155 155
 }
Please login to merge, or discard this patch.
code/model/jobs/DNPing.php 1 patch
Indentation   +134 added lines, -134 removed lines patch added patch discarded remove patch
@@ -1,140 +1,140 @@
 block discarded – undo
1 1
 <?php
2 2
 /**
3
- * This class will queue a ping job and also proxy to the log file of that output
4
- *
5
- * @property string $ResqueToken
6
- *
7
- * @method DNEnvironment Environment()
8
- * @property int EnvironmentID
9
- * @method Member Deployer()
10
- * @property int DeployerID
11
- */
3
+	 * This class will queue a ping job and also proxy to the log file of that output
4
+	 *
5
+	 * @property string $ResqueToken
6
+	 *
7
+	 * @method DNEnvironment Environment()
8
+	 * @property int EnvironmentID
9
+	 * @method Member Deployer()
10
+	 * @property int DeployerID
11
+	 */
12 12
 class DNPing extends DataObject
13 13
 {
14 14
 
15
-    /**
16
-     * @var array
17
-     */
18
-    private static $db = array(
19
-        "ResqueToken" => "Varchar(255)",
20
-    );
21
-
22
-    /**
23
-     * @var array
24
-     */
25
-    private static $has_one = array(
26
-        "Environment" => "DNEnvironment",
27
-        "Deployer" =>"Member",
28
-    );
29
-
30
-    /**
31
-     * @return string
32
-     */
33
-    public function Link()
34
-    {
35
-        return Controller::join_links($this->Environment()->Link(), 'ping', $this->ID);
36
-    }
37
-
38
-    /**
39
-     * @return string
40
-     */
41
-    public function LogLink()
42
-    {
43
-        return $this->Link() . '/log';
44
-    }
45
-
46
-    /**
47
-     * @param Member|null $member
48
-     * @return bool
49
-     */
50
-    public function canView($member = null)
51
-    {
52
-        return $this->Environment()->canView($member);
53
-    }
54
-
55
-    /**
56
-     * Return a path to the log file.
57
-     * @return string
58
-     */
59
-    protected function logfile()
60
-    {
61
-        return sprintf(
62
-            '%s.ping.%s.log',
63
-            $this->Environment()->getFullName('.'),
64
-            $this->ID
65
-        );
66
-    }
67
-
68
-    /**
69
-     * @return \DeploynautLogFile
70
-     */
71
-    public function log()
72
-    {
73
-        return new DeploynautLogFile($this->logfile());
74
-    }
75
-
76
-    /**
77
-     * @return string
78
-     */
79
-    public function LogContent()
80
-    {
81
-        return $this->log()->content();
82
-    }
83
-
84
-    /**
85
-     * @return string
86
-     */
87
-    public function ResqueStatus()
88
-    {
89
-        $status = new Resque_Job_Status($this->ResqueToken);
90
-
91
-        $remap = array(
92
-            Resque_Job_Status::STATUS_WAITING => "Queued",
93
-            Resque_Job_Status::STATUS_RUNNING => "Running",
94
-            Resque_Job_Status::STATUS_FAILED => "Failed",
95
-            Resque_Job_Status::STATUS_COMPLETE => "Complete",
96
-            false => "Invalid",
97
-        );
98
-
99
-        return $remap[$status->get()];
100
-    }
101
-
102
-    /**
103
-     * Queue a ping job
104
-     */
105
-    public function start()
106
-    {
107
-        $environment = $this->Environment();
108
-        $project = $environment->Project();
109
-        $log = $this->log();
110
-
111
-        $args = array(
112
-            'environmentName' => $environment->Name,
113
-            'logfile' => $this->logfile(),
114
-            'projectName' => $project->Name,
115
-            'env' => $project->getProcessEnv()
116
-        );
117
-
118
-        if (!$this->DeployerID) {
119
-            $this->DeployerID = Member::currentUserID();
120
-        }
121
-
122
-        if ($this->DeployerID) {
123
-            $deployer = $this->Deployer();
124
-            $message = sprintf(
125
-                'Ping to %s initiated by %s (%s)',
126
-                $environment->getFullName(),
127
-                $deployer->getName(),
128
-                $deployer->Email
129
-            );
130
-            $log->write($message);
131
-        }
132
-
133
-        $token = Resque::enqueue('deploy', 'PingJob', $args, true);
134
-        $this->ResqueToken = $token;
135
-        $this->write();
136
-
137
-        $message = sprintf('Ping queued as job %s', $token);
138
-        $log->write($message);
139
-    }
15
+	/**
16
+	 * @var array
17
+	 */
18
+	private static $db = array(
19
+		"ResqueToken" => "Varchar(255)",
20
+	);
21
+
22
+	/**
23
+	 * @var array
24
+	 */
25
+	private static $has_one = array(
26
+		"Environment" => "DNEnvironment",
27
+		"Deployer" =>"Member",
28
+	);
29
+
30
+	/**
31
+	 * @return string
32
+	 */
33
+	public function Link()
34
+	{
35
+		return Controller::join_links($this->Environment()->Link(), 'ping', $this->ID);
36
+	}
37
+
38
+	/**
39
+	 * @return string
40
+	 */
41
+	public function LogLink()
42
+	{
43
+		return $this->Link() . '/log';
44
+	}
45
+
46
+	/**
47
+	 * @param Member|null $member
48
+	 * @return bool
49
+	 */
50
+	public function canView($member = null)
51
+	{
52
+		return $this->Environment()->canView($member);
53
+	}
54
+
55
+	/**
56
+	 * Return a path to the log file.
57
+	 * @return string
58
+	 */
59
+	protected function logfile()
60
+	{
61
+		return sprintf(
62
+			'%s.ping.%s.log',
63
+			$this->Environment()->getFullName('.'),
64
+			$this->ID
65
+		);
66
+	}
67
+
68
+	/**
69
+	 * @return \DeploynautLogFile
70
+	 */
71
+	public function log()
72
+	{
73
+		return new DeploynautLogFile($this->logfile());
74
+	}
75
+
76
+	/**
77
+	 * @return string
78
+	 */
79
+	public function LogContent()
80
+	{
81
+		return $this->log()->content();
82
+	}
83
+
84
+	/**
85
+	 * @return string
86
+	 */
87
+	public function ResqueStatus()
88
+	{
89
+		$status = new Resque_Job_Status($this->ResqueToken);
90
+
91
+		$remap = array(
92
+			Resque_Job_Status::STATUS_WAITING => "Queued",
93
+			Resque_Job_Status::STATUS_RUNNING => "Running",
94
+			Resque_Job_Status::STATUS_FAILED => "Failed",
95
+			Resque_Job_Status::STATUS_COMPLETE => "Complete",
96
+			false => "Invalid",
97
+		);
98
+
99
+		return $remap[$status->get()];
100
+	}
101
+
102
+	/**
103
+	 * Queue a ping job
104
+	 */
105
+	public function start()
106
+	{
107
+		$environment = $this->Environment();
108
+		$project = $environment->Project();
109
+		$log = $this->log();
110
+
111
+		$args = array(
112
+			'environmentName' => $environment->Name,
113
+			'logfile' => $this->logfile(),
114
+			'projectName' => $project->Name,
115
+			'env' => $project->getProcessEnv()
116
+		);
117
+
118
+		if (!$this->DeployerID) {
119
+			$this->DeployerID = Member::currentUserID();
120
+		}
121
+
122
+		if ($this->DeployerID) {
123
+			$deployer = $this->Deployer();
124
+			$message = sprintf(
125
+				'Ping to %s initiated by %s (%s)',
126
+				$environment->getFullName(),
127
+				$deployer->getName(),
128
+				$deployer->Email
129
+			);
130
+			$log->write($message);
131
+		}
132
+
133
+		$token = Resque::enqueue('deploy', 'PingJob', $args, true);
134
+		$this->ResqueToken = $token;
135
+		$this->write();
136
+
137
+		$message = sprintf('Ping queued as job %s', $token);
138
+		$log->write($message);
139
+	}
140 140
 }
Please login to merge, or discard this patch.