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.

VagrantVms   B
last analyzed

Complexity

Total Complexity 43

Size/Duplication

Total Lines 458
Duplicated Lines 15.5 %

Coupling/Cohesion

Components 1
Dependencies 5

Importance

Changes 4
Bugs 2 Features 0
Metric Value
wmc 43
c 4
b 2
f 0
lcom 1
cbo 5
dl 71
loc 458
rs 8.3157

17 Methods

Rating   Name   Duplication   Size   Complexity  
A isRunning() 0 4 1
A __construct() 0 5 1
B checkGroupDefinition() 0 20 5
A getVagrantDir() 0 8 2
C createHost() 9 113 8
A startHost() 0 6 1
A stopHost() 0 6 1
A restartHost() 0 6 1
A powerOffHost() 0 6 1
B destroyHost() 0 24 4
A runCommandAgainstHostManager() 16 16 1
A runCommandViaHostManager() 16 16 1
A determineIpAddress() 15 15 1
A determineHostname() 15 15 1
A setVagrantBridgedInterface() 0 5 1
C determineBridgedInterface() 0 59 10
B determinePrivateKey() 0 29 3

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like VagrantVms often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use VagrantVms, and based on these observations, apply Extract Interface, too.

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