GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — develop ( 02d64f...613a7f )
by Stuart
05:34
created

VagrantVms::stopHost()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 2

Duplication

Lines 0
Ratio 0 %
Metric Value
dl 0
loc 6
rs 9.4286
cc 1
eloc 2
nc 1
nop 1
1
<?php
2
3
/**
4
 * Copyright (c) 2011-present Mediasift Ltd
5
 * All rights reserved.
6
 *
7
 * Redistribution and use in source and binary forms, with or without
8
 * modification, are permitted provided that the following conditions
9
 * are met:
10
 *
11
 *   * Redistributions of source code must retain the above copyright
12
 *     notice, this list of conditions and the following disclaimer.
13
 *
14
 *   * Redistributions in binary form must reproduce the above copyright
15
 *     notice, this list of conditions and the following disclaimer in
16
 *     the documentation and/or other materials provided with the
17
 *     distribution.
18
 *
19
 *   * Neither the names of the copyright holders nor the names of his
20
 *     contributors may be used to endorse or promote products derived
21
 *     from this software without specific prior written permission.
22
 *
23
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
24
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
25
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
26
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
27
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
28
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
29
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
30
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
31
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
33
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
34
 * POSSIBILITY OF SUCH DAMAGE.
35
 *
36
 * @category  Libraries
37
 * @package   Storyplayer/HostLib
38
 * @author    Stuart Herbert <[email protected]>
39
 * @copyright 2011-present Mediasift Ltd www.datasift.com
40
 * @license   http://www.opensource.org/licenses/bsd-license.php  BSD License
41
 * @link      http://datasift.github.io/storyplayer
42
 */
43
44
namespace DataSift\Storyplayer\HostLib;
45
46
use DataSift\Storyplayer\CommandLib\CommandRunner;
47
use DataSift\Storyplayer\CommandLib\CommandResult;
48
use DataSift\Storyplayer\OsLib;
49
use DataSift\Storyplayer\PlayerLib\StoryTeller;
50
use DataSift\Stone\ObjectLib\BaseObject;
51
use Exception;
52
use Storyplayer\SPv2\Modules\Exceptions;
53
use Storyplayer\SPv2\Modules\Shell;
54
55
/**
56
 * the things you can do / learn about a group of Vagrant virtual machines
57
 *
58
 * @category  Libraries
59
 * @package   Storyplayer/HostLib
60
 * @author    Stuart Herbert <[email protected]>
61
 * @copyright 2011-present Mediasift Ltd www.datasift.com
62
 * @license   http://www.opensource.org/licenses/bsd-license.php  BSD License
63
 * @link      http://datasift.github.io/storyplayer
64
 */
65
class VagrantVms implements SupportedHost
66
{
67
    /**
68
     *
69
     * @var StoryTeller
70
     */
71
    protected $st;
72
73
    /**
74
     *
75
     * @param StoryTeller $st
76
     */
77
    public function __construct(StoryTeller $st)
78
    {
79
        // remember
80
        $this->st = $st;
81
    }
82
83
    /**
84
     * Check environmental details
85
     *
86
     * @param  stdClass $groupDef
87
     */
88
    protected function checkGroupDefinition($groupDef)
89
    {
90
        // make sure we like the provided details
91
        if (!isset($groupDef->details)) {
92
            throw Exceptions::newActionFailedException(__METHOD__, "missing groupDef->details");
93
        }
94
        if (!isset($groupDef->details->machines)) {
95
            throw Exceptions::newActionFailedException(__METHOD__, "missing groupDef->details->machines");
96
        }
97
        if (empty($groupDef->details->machines)) {
98
            throw Exceptions::newActionFailedException(__METHOD__, "groupDef->details->machines cannot be empty");
99
        }
100
101
        // make sure we have a Vagrantfile
102
        $expectedVagrantfile = $this->getVagrantDir($groupDef) . "/Vagrantfile";
103
        if (!file_exists($expectedVagrantfile)) {
104
            throw Exceptions::newActionFailedException(__METHOD__, "no Vagrantfile; expected it to be here: {$expectedVagrantfile}");
105
        }
106
107
    }
108
109
    /**
110
     * Get the Vagrant directory
111
     *
112
     * @param  stdClass $groupDef
113
     *
114
     * @return string
115
     */
116
    protected function getVagrantDir($groupDef)
117
    {
118
        if (isset($groupDef->baseFolder)) {
119
            return $groupDef->baseFolder;
120
        }
121
122
        return getcwd();
123
    }
124
125
    /**
126
     *
127
     * @param  stdClass $groupDef
128
     * @param  array $provisioningVars
129
     * @return void
130
     */
131
    public function createHost($groupDef, $provisioningVars = array())
132
    {
133
        // what are we doing?
134
        $log = usingLog()->startAction('create new VM');
135
136
        // make sure we're happy with this group
137
        $this->checkGroupDefinition($groupDef);
138
139
        // where is the action?
140
        $baseFolder = $this->getVagrantDir($groupDef);
141
142
        // make sure we're happy with details about the machine
143 View Code Duplication
        foreach($groupDef->details->machines as $hostId => $machine) {
144
            // TODO: it would be great to autodetect this one day
145
            if (!isset($machine->osName)) {
146
                throw Exceptions::newActionFailedException(__METHOD__, "missing groupDef->details->machines['$hostId']->osName");
147
            }
148
            if (!isset($machine->roles)) {
149
                throw Exceptions::newActionFailedException(__METHOD__, "missing groupDef->details->machines['$hostId']->roles");
150
            }
151
        }
152
153
        // make sure the VM is stopped, if it is running
154
        $log->addStep('stop vagrant VM in '.$baseFolder.' if already running', function() use($baseFolder) {
155
            $command = "vagrant destroy --force";
156
            $this->runCommandAgainstHostManager($baseFolder, $command);
157
        });
158
159
        // remove any existing hosts table entry
160
        foreach ($groupDef->details->machines as $hostId => $machine) {
161
            // remove any roles
162
            usingRolesTable()->removeHostFromAllRoles($hostId);
163
164
            // now drop the host
165
            usingHostsTable()->removeHost($hostId);
166
        }
167
        $this->st->saveRuntimeConfig();
168
169
        // work out which network interface to use
170
        $this->setVagrantBridgedInterface();
171
172
        // let's start the VM
173
        $command = "vagrant up";
174
        $result = $log->addStep('create vagrant VM(s) in '.$baseFolder, function() use($baseFolder, $command) {
175
            return $this->runCommandAgainstHostManager($baseFolder, $command);
176
        });
177
178
        // did it work?
179
        if ($result->returnCode != 0) {
180
            $log->endAction("VM failed to start or provision :(");
181
            throw Exceptions::newActionFailedException(__METHOD__);
182
        }
183
184
        // yes it did!!
185
186
        // store the details
187
        foreach($groupDef->details->machines as $hostId => $machine)
188
        {
189
            // we want all the details from the config file
190
            $vmDetails = clone $machine;
191
192
            // this allows the story to perform actions against a single
193
            // machine if required
194
            $vmDetails->type        = 'VagrantVm';
195
196
            // new in v2.x:
197
            //
198
            // when provisioning a folder of vagrant vms, we now use
199
            // the same name for the VM that vagrant uses
200
            $vmDetails->hostId      = $hostId;
201
202
            // remember where the machine lives
203
            $vmDetails->dir         = $baseFolder;
204
205
            // we need to remember how to SSH into the box
206
            $vmDetails->sshUsername = 'vagrant';
207
            $vmDetails->sshKeyFile  = $this->determinePrivateKey($vmDetails);
208
            $vmDetails->sshOptions  = [
209
                "-i '" . $vmDetails->sshKeyFile . "'",
210
                "-o StrictHostKeyChecking=no",
211
                "-o UserKnownHostsFile=/dev/null",
212
                "-o LogLevel=quiet",
213
            ];
214
            $vmDetails->scpOptions = [
215
                "-i '" . $vmDetails->sshKeyFile . "'",
216
                "-o StrictHostKeyChecking=no",
217
            ];
218
219
            // remember how to connect to the machine via the network
220
            $vmDetails->ipAddress   = $this->determineIpAddress($vmDetails);
221
            $vmDetails->hostname    = $this->determineHostname($vmDetails);
222
223
            // mark the box as provisioned
224
            // we will use this in stopBox() to avoid destroying VMs that failed
225
            // to provision
226
            $vmDetails->provisioned = true;
227
228
            // remember this vm, now that it is running
229
            usingHostsTable()->addHost($vmDetails->hostId, $vmDetails);
230
            foreach ($vmDetails->roles as $role) {
231
                usingRolesTable()->addHostToRole($vmDetails, $role);
232
            }
233
234
            // now, let's get this VM into our SSH known_hosts file, to avoid
235
            // prompting people when we try and provision this VM
236
            $log->addStep("get the VM into the SSH known_hosts file", function() use($vmDetails) {
237
                Shell::onHost($vmDetails->hostId)->runCommand("ls");
238
            });
239
        }
240
241
        // all done
242
        $log->endAction();
243
    }
244
245
    /**
246
     *
247
     * @param  stdClass $envDetails
248
     * @return void
249
     */
250
    public function startHost($envDetails)
251
    {
252
        // if you really want to do this from your story, use
253
        // $st->usingVagrantVm()->startHost()
254
        throw Exceptions::newActionFailedException(__METHOD__, "unsupported operation");
255
    }
256
257
    /**
258
     *
259
     * @param  stdClass $envDetails
260
     * @return void
261
     */
262
    public function stopHost($envDetails)
263
    {
264
        // if you really want to do this from your story, use
265
        // $st->usingVagrantVm()->stopHost()
266
        throw Exceptions::newActionFailedException(__METHOD__, "unsupported operation");
267
    }
268
269
    /**
270
     *
271
     * @param  stdClass $envDetails
272
     * @return void
273
     */
274
    public function restartHost($envDetails)
275
    {
276
        // if you really want to do this from your story, use
277
        // $st->usingVagrantVm()->restartHost()
278
        throw Exceptions::newActionFailedException(__METHOD__, "unsupported operation");
279
    }
280
281
    /**
282
     *
283
     * @param  stdClass $envDetails
284
     * @return void
285
     */
286
    public function powerOffHost($envDetails)
287
    {
288
        // if you really want to do this from your story, use
289
        // $st->usingVagrantVm()->powerOffHost()
290
        throw Exceptions::newActionFailedException(__METHOD__, "unsupported operation");
291
    }
292
293
    /**
294
     *
295
     * @param  stdClass $groupDef
296
     * @return void
297
     */
298
    public function destroyHost($groupDef)
299
    {
300
        // what are we doing?
301
        $log = usingLog()->startAction("destroy VM(s)");
302
303
        // stop all the VMs, one by one
304
        foreach ($groupDef->details->machines as $hostId => $machine) {
305
            // get the machine details
306
            $vmDetails = fromHostsTable()->getDetailsForHost($hostId);
307
            if ($vmDetails) {
308
                // is the VM actually running?
309
                if (fromHost($hostId)->getHostIsRunning()) {
310
                    // delete the VM from disk
311
                    //
312
                    // this will also deregister the host from the
313
                    // HostsTable and RolesTable
314
                    usingVagrant()->destroyVm($hostId);
315
                }
316
            }
317
        }
318
319
        // all done
320
        $log->endAction();
321
    }
322
323
    /**
324
     *
325
     * @param  string $baseFolder
326
     * @param  string $command
327
     * @return CommandResult
328
     */
329 View Code Duplication
    public function runCommandAgainstHostManager($baseFolder, $command)
330
    {
331
        // what are we doing?
332
        $log = usingLog()->startAction("run vagrant command '{$command}'");
333
334
        // build the command
335
        $fullCommand = "cd '{$baseFolder}' && $command 2>&1";
336
337
        // run the command
338
        $commandRunner = new CommandRunner();
339
        $result = $commandRunner->runSilently($fullCommand);
340
341
        // all done
342
        $log->endAction("return code was '{$result->returnCode}'");
343
        return $result;
344
    }
345
346
    /**
347
     * @param  string $baseFolder
348
     * @param  string $command
349
     * @return CommandResult
350
     */
351 View Code Duplication
    public function runCommandViaHostManager($baseFolder, $command)
352
    {
353
        // what are we doing?
354
        $log = usingLog()->startAction("run vagrant command '{$command}'");
355
356
        // build the command
357
        $fullCommand = "cd '{$baseFolder}' && vagrant ssh -c \"$command\"";
358
359
        // run the command
360
        $commandRunner = new CommandRunner();
361
        $result = $commandRunner->runSilently($fullCommand);
362
363
        // all done
364
        $log->endAction("return code was '{$result->returnCode}'");
365
        return $result;
366
    }
367
368
    /**
369
     *
370
     * @param  stdClass $envDetails
371
     * @return boolean
372
     */
373
    public function isRunning($envDetails)
374
    {
375
        throw Exceptions::newActionFailedException(__METHOD__, "unsupported operation");
376
    }
377
378
    /**
379
     *
380
     * @param  stdClass $vmDetails
381
     * @return string
382
     */
383 View Code Duplication
    public function determineIpAddress($vmDetails)
384
    {
385
        // what are we doing?
386
        $log = usingLog()->startAction("determine IP address of Vagrant VM '{$vmDetails->hostId}'");
387
388
        // create an adapter to talk to the host operating system
389
        $host = OsLib::getHostAdapter($this->st, $vmDetails->osName);
390
391
        // get the IP address
392
        $ipAddress = $host->determineIpAddress($vmDetails, new VagrantVm($this->st));
393
394
        // all done
395
        $log->endAction("IP address is '{$ipAddress}'");
396
        return $ipAddress;
397
    }
398
399
    /**
400
     *
401
     * @param  stdClass $vmDetails
402
     * @return string
403
     */
404 View Code Duplication
    public function determineHostname($vmDetails)
405
    {
406
        // what are we doing?
407
        $log = usingLog()->startAction("determine hostname of Vagrant VM '{$vmDetails->hostId}'");
408
409
        // create an adapter to talk to the host operating system
410
        $host = OsLib::getHostAdapter($this->st, $vmDetails->osName);
411
412
        // get the hostname
413
        $hostname = $host->determineHostname($vmDetails, new VagrantVm($this->st));
414
415
        // all done
416
        $log->endAction("hostname is '{$hostname}'");
417
        return $hostname;
418
    }
419
420
    /**
421
     * Set the VAGRANT_BRIDGE_ADAPTER and VIRTUALBOX_BRIDGE_ADAPTER
422
     * environmental variables.
423
     */
424
    public function setVagrantBridgedInterface() {
425
        $bridgedIface = $this->determineBridgedInterface();
426
        putenv('VAGRANT_BRIDGE_ADAPTER='.$bridgedIface);
427
        putenv('VIRTUALBOX_BRIDGE_ADAPTER='.$bridgedIface);
428
    }
429
430
    /**
431
     * @return string
432
     */
433
    public function determineBridgedInterface()
434
    {
435
        // what are we doing?
436
        $log = usingLog()->startAction("determine bridged network interface for Vagrant VM");
437
438
        try {
439
            // 1. try to load Vagrant settings from storyplayer.json
440
            // e.g.: "moduleSettings":{"vagrant":{"bridgedIface":"eth0"}}
441
            $vagrantSettings = fromStoryplayer()->getModuleSetting('vagrant');
442
            if (!empty($vagrantSettings->bridgedIface)) {
443
                $log->endAction('Returning configured '.$vagrantSettings->bridgedIface.' interface');
444
                return $vagrantSettings->bridgedIface;
445
            }
446
        } catch (Exception $e) {
447
            // ignore errors as this setting may not exist
448
        }
449
450
        // 2. check if VirtualBox (VBoxManage) is installed
451
        $command = 'which VBoxManage';
452
        $commandRunner = new CommandRunner();
453
        $result = $commandRunner->runSilently($command);
454
        if ($result->returnCode !== 0) {
455
            // VBoxManage is not installed, we are probably using another provider
456
            // like OpenStack that do not require this setting
457
            $log->endAction('VBoxManage is not installed: returning default eth0 interface');
458
            return 'eth0';
459
        }
460
461
        // 3. VBoxManage can actually tell us what we need to know
462
        $command = 'VBoxManage list bridgedifs';
463
        $commandRunner = new CommandRunner();
464
        $result = $commandRunner->runSilently($command);
465
        if ($result->returnCode != 0) {
466
            $log->endAction('unable to get list of bridgable network interfaces from VBoxManage :(');
467
            throw Exceptions::newActionFailedException(__METHOD__);
468
        }
469
470
        // now we just need to make sense of it all
471
        $lines = explode("\n", $result->output);
472
        $iface = null;
473
        foreach($lines as $line) {
474
            $matches = [];
475
            if (preg_match("|Name:[\s]+(.*)|", $line, $matches)) {
476
                $iface = $matches[1];
477
            }
478
            else if ($iface !== null && preg_match("|IPAddress:[\s]+(.*)|", $line, $matches)) {
479
                // our network interface contains an IPAddress - it is likely
480
                // to be one that works
481
                if ($matches[1] != '0.0.0.0') {
482
                    $log->endAction($iface);
483
                    return $iface;
484
                }
485
            }
486
        }
487
488
        // if we get here, then we haven't found a network interface to use
489
        $log->endAction("no bridgeable network interface found :(");
490
        throw Exceptions::newActionFailedException(__METHOD__);
491
    }
492
493 View Code Duplication
    public function determinePrivateKey($vmDetails)
494
    {
495
        // what are we doing?
496
        $log = usingLog()->startAction("determine private key for Vagrant VM '{$vmDetails->hostId}'");
497
498
        // the key will be in one of two places, in this order:
499
        //
500
        // <test environment folder>/.vagrant/machines/:name/virtualbox/private_key
501
        // $HOME/.vagrant.d/insecure_private_key
502
        //
503
        // we use the first that we can find
504
        $keyFilenames = [
505
            $vmDetails->dir . "/.vagrant/machines/{$vmDetails->hostId}/virtualbox/private_key",
506
            getenv("HOME") . "/.vagrant.d/insecure_private_key"
507
        ];
508
509
        foreach ($keyFilenames as $keyFilename)
510
        {
511
            usingLog()->writeToLog("checking if {$keyFilename} exists");
512
            if (file_exists($keyFilename)) {
513
                $log->endAction($keyFilename);
514
                return $keyFilename;
515
            }
516
        }
517
518
        // if we get here, then we do not know where the private key is
519
        $log->endAction("unable to find Vagrant private key for VM");
520
        throw Exceptions::newActionFailedException(__METHOD__);
521
    }
522
}
523