Completed
Push — master ( c2ccc4...c53954 )
by Benjamin
03:56
created

DockerChrome::initDefaultConfig()   B

Complexity

Conditions 2
Paths 2

Size

Total Lines 26
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 26
ccs 11
cts 11
cp 1
rs 8.8571
c 0
b 0
f 0
cc 2
eloc 10
nc 2
nop 0
crap 2
1
<?php
2
/**
3
 * This file is part of the teamneusta/codeception-docker-chrome package.
4
 *
5
 * Copyright (c) 2017 neusta GmbH | Ein team neusta Unternehmen
6
 *
7
 * For the full copyright and license information, please view the LICENSE file that was distributed with this source code.
8
 *
9
 * @license http://www.opensource.org/licenses/mit-license.html  MIT License
10
 */
11
12
namespace Codeception\Extension;
13
14
use Codeception\Exception\ExtensionException;
15
use Codeception\Platform\Extension;
16
use Symfony\Component\Process\Process;
17
use Symfony\Component\Yaml\Yaml;
18
19
class DockerChrome extends Extension
20
{
21
    /**
22
     * events
23
     *
24
     * @var array
25
     */
26
    static public $events = [
27
        'module.init' => 'moduleInit',
28
    ];
29
30
    /**
31
     * process
32
     *
33
     * @var \Symfony\Component\Process\Process
34
     */
35
    private $process;
36
37
    /**
38
     * dockerStarted
39
     *
40
     * @var bool
41
     */
42
    private $dockerStarted = false;
43
44
    /**
45
     * dockerComposePath
46
     *
47
     * @var string
48
     */
49
    private $dockerComposePath;
50
51
    /**
52
     * DockerChrome constructor.
53
     *
54
     * @param array $config
55
     * @param array $options
56
     * @param \Symfony\Component\Process\Process|null $process
57
     * @param string $defaultDockerComposePath
58
     */
59 16
    public function __construct(
60
        array $config,
61
        array $options,
62
        Process $process = null,
63
        string $defaultDockerComposePath = '/usr/local/bin/docker-compose'
64
    ) {
65
66
        // Set default https proxy
67 16
        if (!isset($options['silent'])) {
68 16
            $options['silent'] = false;
69
        }
70
71 16
        $this->dockerComposePath = $defaultDockerComposePath;
72
73 16
        parent::__construct($config, $options);
74
75 16
        $this->initDefaultConfig();
76 16
        $command = $this->getCommand();
77 16
        $this->process = $process ?: new Process($command, realpath(__DIR__), null, null, 3600);
78 16
    }
79
80
    /**
81
     * initDefaultConfig
82
     *
83
     * @return void
84
     * @throws \Codeception\Exception\ExtensionException
85
     */
86 16
    protected function initDefaultConfig()
87
    {
88 16
        $this->config['path'] = $this->config['path'] ?? $this->dockerComposePath;
89
90
        // Set default WebDriver port
91 16
        $this->config['port'] = $this->config['port'] ?? 4444;
92
93
        // Set default debug mode
94 16
        $this->config['debug'] = $this->config['debug'] ?? false;
95
96
        // Set default extra_hosts mode
97 16
        $this->config['extra_hosts'] = $this->config['extra_hosts'] ?? false;
98
99
        // Set default http proxy
100 16
        $this->config['http_proxy'] = $this->config['http_proxy'] ?? '';
101
102
        // Set default https proxy
103 16
        $this->config['https_proxy'] = $this->config['https_proxy'] ?? '';
104
105
        // Set default https proxy
106 16
        $this->config['no_proxy'] = $this->config['no_proxy'] ?? '';
107
108 16
        if (!file_exists($this->config['path'])) {
109 2
            throw new ExtensionException($this, "File not found: {$this->config['path']}.");
110
        }
111 16
    }
112
113
    /**
114
     * getCommand
115
     *
116
     * @return string
117
     */
118 16
    private function getCommand(): string
119
    {
120 16
        return 'exec ' . escapeshellarg(realpath($this->config['path'])) . ' up';
121
    }
122
123
    /**
124
     * getConfig
125
     *
126
     * @return array
127
     */
128 7
    public function getConfig(): array
129
    {
130 7
        return $this->config;
131
    }
132
133
    /**
134
     *
135
     */
136 10
    public function __destruct()
137
    {
138 10
        $this->stopServer();
139 10
    }
140
141
    /**
142
     * stopServer
143
     *
144
     * @return void
145
     */
146 10
    private function stopServer()
147
    {
148 10
        if ($this->process && $this->process->isRunning()) {
149 1
            $this->write('Stopping Docker Server');
150
151 1
            $this->process->signal(2);
152 1
            $this->process->wait();
153
        }
154 10
    }
155
156
    /**
157
     * moduleInit
158
     *
159
     * @param \Codeception\Event\SuiteEvent $e
160
     * @return void
161
     */
162 6
    public function moduleInit(\Codeception\Event\SuiteEvent $e)
163
    {
164 6
        if (!$this->suiteAllowed($e)) {
165 1
            return;
166
        }
167
168 5
        $this->overrideWithModuleConfig($e);
169 5
        $this->generateYaml();
170 5
        $this->startServer();
171 4
    }
172
173
    /**
174
     * suiteAllowed
175
     *
176
     * @param \Codeception\Event\SuiteEvent $e
177
     * @return bool
178
     */
179 6
    protected function suiteAllowed(\Codeception\Event\SuiteEvent $e): bool
180
    {
181 6
        $allowed = true;
182 6
        if (isset($this->config['suites'])) {
183 1
            $suites = (array)$this->config['suites'];
184
185 1
            $e->getSuite()->getBaseName();
186
187 1
            if (!in_array($e->getSuite()->getBaseName(), $suites)
188 1
                && !in_array($e->getSuite()->getName(), $suites)
189
            ) {
190 1
                $allowed = false;
191
            }
192
        }
193
194 6
        return $allowed;
195
    }
196
197
    /**
198
     * overrideWithModuleConfig
199
     *
200
     * @param \Codeception\Event\SuiteEvent $e
201
     * @return void
202
     */
203 5
    protected function overrideWithModuleConfig(\Codeception\Event\SuiteEvent $e)
204
    {
205 5
        $modules = array_filter($e->getSettings()['modules']['enabled']);
206 5
        foreach ($modules as $module) {
207 3
            if (is_array($module)) {
208 3
                $moduleSettings = current($module);
209 3
                $this->config['port'] = $moduleSettings['port'] ?? $this->config['port'];
210 3
                $this->config['http_proxy'] = $moduleSettings['capabilities']['httpProxy'] ?? $this->config['http_proxy'];
211 3
                $this->config['https_proxy'] = $moduleSettings['capabilities']['sslProxy'] ?? $this->config['https_proxy'];
212 3
                $this->config['no_proxy'] = $moduleSettings['capabilities']['noProxy'] ?? $this->config['no_proxy'];
213
            }
214
        }
215 5
    }
216
217
    /**
218
     * generateYaml
219
     *
220
     * @return void
221
     */
222 5
    protected function generateYaml()
223
    {
224 5
        $environment = [];
225
226 5
        if (!empty($this->config['http_proxy'])) {
227 2
            $environment[] = 'http_proxy=' . $this->config['http_proxy'];
228
        }
229 5
        if (!empty($this->config['https_proxy'])) {
230 2
            $environment[] = 'https_proxy=' . $this->config['https_proxy'];
231
        }
232 5
        if (!empty($this->config['no_proxy'])) {
233 1
            $environment[] = 'no_proxy=' . $this->config['no_proxy'];
234
        }
235
236 5
        $registryPrefix = !empty($this->config['private-registry']) ? $this->config['private-registry'] . '/' : '';
237
238
        $dockerYaml = [
239 5
            'hub'    => [
240 5
                'image'       => $registryPrefix . 'selenium/hub',
241 5
                'ports'       => [$this->config['port'] . ':4444'],
242 5
                'environment' => $environment
243
            ],
244
            'chrome' => [
245
                'volumes'     => ['/dev/shm:/dev/shm'],
246 5
                'image'       => $registryPrefix . 'selenium/node-chrome',
247
                'links'       => ['hub'],
248 5
                'environment' => $environment
249
            ]
250
        ];
251
252 5
        if ($this->config['extra_hosts']) {
253 1
            $dockerYaml['hub']['extra_hosts'] = $this->config['extra_hosts'];
254 1
            $dockerYaml['chrome']['extra_hosts'] = $this->config['extra_hosts'];
255
        }
256
257 5
        file_put_contents(__DIR__ . '/docker-compose.yml', Yaml::dump($dockerYaml));
258 5
    }
259
260
    /**
261
     * startServer
262
     *
263
     * @return void
264
     * @throws \Codeception\Exception\ExtensionException
265
     */
266 5
    private function startServer()
267
    {
268 5
        if ($this->config['debug']) {
269 5
            $this->writeln(['Generated Docker Command:', $this->process->getCommandLine()]);
270
        }
271 5
        $this->writeln('Starting Docker Server');
272 5
        $this->process->start(function($type, $buffer) {
273 4
            if (strpos($buffer, 'Registered a node') !== false) {
274 4
                $this->dockerStarted = true;
275
            }
276 5
        });
277
278
        // wait until docker is finished to start
279 5
        while ($this->process->isRunning() && !$this->dockerStarted) {
280
        }
281
282 5
        if (!$this->process->isRunning()) {
283 1
            throw new ExtensionException($this, 'Failed to start Docker server.');
284
        }
285 4
        $this->writeln(['', 'Docker server now accessible']);
286 4
    }
287
}
288