TravisCiBuildStrategy::getConfigValue()   B
last analyzed

Complexity

Conditions 5
Paths 4

Size

Total Lines 16
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 16
rs 8.8571
cc 5
eloc 8
nc 4
nop 3
1
<?php
2
/*
3
 * This file is part of JoliCi.
4
 *
5
 * (c) Joel Wurtz <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the LICENSE
8
 * file that was distributed with this source code.
9
 */
10
11
namespace Joli\JoliCi\BuildStrategy;
12
13
use Joli\JoliCi\Job;
14
use Joli\JoliCi\Builder\DockerfileBuilder;
15
use Joli\JoliCi\Filesystem\Filesystem;
16
use Joli\JoliCi\Matrix;
17
use Joli\JoliCi\Naming;
18
use Joli\JoliCi\Service;
19
use Symfony\Component\Yaml\Yaml;
20
21
/**
22
 * TravisCi implementation for build strategy
23
 *
24
 * A project must have a .travis.yml file
25
 *
26
 * @author Joel Wurtz <[email protected]>
27
 */
28
class TravisCiBuildStrategy implements BuildStrategyInterface
29
{
30
    private $languageVersionKeyMapping = array(
31
        'ruby' => 'rvm',
32
    );
33
34
    private $defaults = array(
35
        'php' => array(
36
            'before_install' => array(),
37
            'install'        => array(),
38
            'before_script'  => array(),
39
            'script'         => array('phpunit'),
40
            'env'            => array(),
41
            'default_versions' => array('5.6')
42
        ),
43
        'ruby' => array(
44
            'before_install' => array(),
45
            'install'        => array(),
46
            'before_script'  => array(),
47
            'script'         => array('bundle exec rake'),
48
            'env'            => array(),
49
            'default_versions' => array('2.1.0')
50
        ),
51
        'node_js' => array(
52
            'before_install' => array(),
53
            'install'        => array(),
54
            'before_script'  => array(),
55
            'script'         => array('npm test'),
56
            'env'            => array(),
57
            'default_versions' => array('0.10')
58
        ),
59
    );
60
61
    private $servicesMapping = array(
62
        'mongodb' => array(
63
            'repository' => 'mongo',
64
            'tag' => '2.6',
65
            'config' => array()
66
        ),
67
        'mysql'   => array(
68
            'repository' => 'mysql',
69
            'tag' => '5.5',
70
            'config' => array(
71
                'Env' => array(
72
                    'MYSQL_ROOT_PASSWORD=""',
73
                    'MYSQL_USER=travis',
74
                    'MYSQL_PASSWORD=""'
75
                )
76
            )
77
        ),
78
        'postgresql' => array(
79
            'repository' => 'postgres',
80
            'tag'        => '9.1',
81
            'config'     => array()
82
        ),
83
        'couchdb' => array(
84
            'repository' => 'fedora/couchdb',
85
            'tag' => 'latest',
86
            'config' => array()
87
        ),
88
        'rabbitmq' => array(
89
            'repository' => 'dockerfile/rabbitmq',
90
            'tag' => 'latest',
91
            'config' => array()
92
        ),
93
        'memcached' => array(
94
            'repository' => 'sylvainlasnier/memcached',
95
            'tag' => 'latest',
96
            'config' => array()
97
        ),
98
        'redis-server' => array(
99
            'repository' => 'redis',
100
            'tag' => '2.8',
101
            'config' => array()
102
        ),
103
        'cassandra' => array(
104
            'repository' => 'spotify/cassandra',
105
            'tag' => 'latest',
106
            'config' => array()
107
        ),
108
        'neo4j' => array(
109
            'repository' => 'tpires/neo4j',
110
            'tag' => 'latest',
111
            'config' => array()
112
        ),
113
        'elasticsearch' => array(
114
            'repository' => 'dockerfile/elasticsearch',
115
            'tag' => 'latest',
116
            'config' => array()
117
        ),
118
    );
119
120
    /**
121
     * @var DockerfileBuilder Builder for dockerfile
122
     */
123
    private $builder;
124
125
    /**
126
     * @var string Build path for project
127
     */
128
    private $buildPath;
129
130
    /**
131
     * @var Filesystem Filesystem service
132
     */
133
    private $filesystem;
134
135
    /**
136
     * @var \Joli\JoliCi\Naming Naming service to create docker name for images
137
     */
138
    private $naming;
139
140
    /**
141
     * @param DockerfileBuilder $builder    Twig Builder for Dockerfile
142
     * @param string            $buildPath  Directory where builds are created
143
     * @param Naming            $naming     Naming service
144
     * @param Filesystem        $filesystem Filesystem service
145
     */
146
    public function __construct(DockerfileBuilder $builder, $buildPath, Naming $naming, Filesystem $filesystem)
147
    {
148
        $this->builder    = $builder;
149
        $this->buildPath  = $buildPath;
150
        $this->naming     = $naming;
151
        $this->filesystem = $filesystem;
152
    }
153
154
    /**
155
     * {@inheritdoc}
156
     */
157
    public function getJobs($directory)
158
    {
159
        $jobs       = array();
160
        $config     = Yaml::parse(file_get_contents($directory.DIRECTORY_SEPARATOR.".travis.yml"));
161
        $matrix     = $this->createMatrix($config);
162
        $services   = $this->getServices($config);
163
        $timezone   = ini_get('date.timezone');
164
165
        foreach ($matrix->compute() as $possibility) {
166
            $parameters   = array(
167
                'language' => $possibility['language'],
168
                'version' => $possibility['version'],
169
                'environment' => $possibility['environment'],
170
            );
171
172
            $description = sprintf('%s = %s', $possibility['language'], $possibility['version']);
173
174
            if ($possibility['environment'] !== null) {
175
                $description .= sprintf(', Environment: %s', json_encode($possibility['environment']));
176
            }
177
178
            $jobs[] = new Job($this->naming->getProjectName($directory), $this->getName(), $this->naming->getUniqueKey($parameters), array(
179
                'language'       => $possibility['language'],
180
                'version'        => $possibility['version'],
181
                'before_install' => $possibility['before_install'],
182
                'install'        => $possibility['install'],
183
                'before_script'  => $possibility['before_script'],
184
                'script'         => $possibility['script'],
185
                'env'            => $possibility['environment'],
186
                'global_env'     => $possibility['global_env'],
187
                'timezone'       => $timezone,
188
                'origin'         => realpath($directory),
189
            ), $description, null, $services);
190
        }
191
192
        return $jobs;
193
    }
194
195
    /**
196
     * {@inheritdoc}
197
     */
198
    public function prepareJob(Job $job)
199
    {
200
        $parameters = $job->getParameters();
201
        $origin     = $parameters['origin'];
202
        $target     = $this->buildPath.DIRECTORY_SEPARATOR. $job->getDirectory();
203
204
        // First mirroring target
205
        $this->filesystem->mirror($origin, $target, null, array(
206
            'delete' => true,
207
            'override' => true,
208
        ));
209
210
        // Create dockerfile
211
        $this->builder->setTemplateName(sprintf("%s/Dockerfile-%s.twig", $parameters['language'], $parameters['version']));
212
        $this->builder->setVariables($parameters);
213
        $this->builder->setOutputName('Dockerfile');
214
        $this->builder->writeOnDisk($target);
215
    }
216
217
    /**
218
     * {@inheritdoc}
219
     */
220
    public function getName()
221
    {
222
        return "TravisCi";
223
    }
224
225
    /**
226
     * {@inheritdoc}
227
     */
228
    public function supportProject($directory)
229
    {
230
        return file_exists($directory.DIRECTORY_SEPARATOR.".travis.yml") && is_file($directory.DIRECTORY_SEPARATOR.".travis.yml");
231
    }
232
233
    /**
234
     * Get command lines to add for a configuration value in .travis.yml file
235
     *
236
     * @param array  $config   Configuration of travis ci parsed
237
     * @param string $language Language for getting the default value if no value is set
238
     * @param string $key      Configuration key
239
     *
240
     * @return array A list of command to add to Dockerfile
241
     */
242
    private function getConfigValue($config, $language, $key)
243
    {
244
        if (!isset($config[$key]) || empty($config[$key])) {
245
            if (isset($this->defaults[$language][$key])) {
246
                return $this->defaults[$language][$key];
247
            }
248
249
            return array();
250
        }
251
252
        if (!is_array($config[$key])) {
253
            return array($config[$key]);
254
        }
255
256
        return $config[$key];
257
    }
258
259
    /**
260
     * Create matrix of build
261
     *
262
     * @param array $config
263
     *
264
     * @return Matrix
265
     */
266
    protected function createMatrix($config)
267
    {
268
        $language = isset($config['language']) ? $config['language'] : 'ruby';
269
270
        if (!isset($this->defaults[$language])) {
271
            throw new \Exception(sprintf('Language %s not supported', $language));
272
        }
273
274
        $versionKey       = isset($this->languageVersionKeyMapping[$language]) ? $this->languageVersionKeyMapping[$language] : $language;
275
        $environmentLines = $this->getConfigValue($config, $language, "env");
276
        $environnements   = array();
277
        $globalEnv        = array();
278
        $matrixEnv        = $environmentLines;
279
        $versions         = (array) (isset($config[$versionKey]) ? $config[$versionKey] : $this->defaults[$language]['default_versions']);
280
281
        foreach ($versions as $key => $version) {
282
            if (!$this->isLanguageVersionSupported($language, $version)) {
283
                unset($versions[$key]);
284
            }
285
        }
286
287
        if (isset($environmentLines['matrix'])) {
288
            $matrixEnv = $environmentLines['matrix'];
289
        }
290
291
        if (isset($environmentLines['global'])) {
292
            foreach ($environmentLines['global'] as $environementVariable) {
293
                if (is_array($environementVariable) && array_key_exists('secure', $environementVariable)) {
294
                    continue;
295
                }
296
297
                list ($key, $value) = $this->parseEnvironementVariable($environementVariable);
298
                $globalEnv = array_merge($globalEnv, array($key => $value));
299
            }
300
301
            if (!isset($environmentLines['matrix'])) {
302
                $matrixEnv = array();
303
            }
304
        }
305
306
        // Parsing environnements
307
        foreach ($matrixEnv as $environmentLine) {
308
            $environnements[] = $this->parseEnvironmentLine($environmentLine);
309
        }
310
311
        $matrix = new Matrix();
312
        $matrix->setDimension('language', array($language));
313
        $matrix->setDimension('environment', $environnements);
314
        $matrix->setDimension('global_env', array($globalEnv));
315
        $matrix->setDimension('version', $versions);
316
        $matrix->setDimension('before_install', array($this->getConfigValue($config, $language, 'before_install')));
317
        $matrix->setDimension('install', array($this->getConfigValue($config, $language, 'install')));
318
        $matrix->setDimension('before_script', array($this->getConfigValue($config, $language, 'before_script')));
319
        $matrix->setDimension('script', array($this->getConfigValue($config, $language, 'script')));
320
321
322
        return $matrix;
323
    }
324
325
    /**
326
     * Get services list from travis ci configuration file
327
     *
328
     * @param $config
329
     *
330
     * @return Service[]
331
     */
332
    protected function getServices($config)
333
    {
334
        $services       = array();
335
        $travisServices = isset($config['services']) && is_array($config['services']) ? $config['services'] : array();
336
337
        foreach ($travisServices as $service) {
338
            if (isset($this->servicesMapping[$service])) {
339
                $services[] = new Service(
340
                    $service,
341
                    $this->servicesMapping[$service]['repository'],
342
                    $this->servicesMapping[$service]['tag'],
343
                    $this->servicesMapping[$service]['config']
344
                );
345
            }
346
        }
347
348
        return $services;
349
    }
350
351
    /**
352
     * Parse an environnement line from Travis to return an array of variables
353
     *
354
     * Transform:
355
     *   "A=B C=D"
356
     * Into:
357
     *   array('a' => 'b', 'c' => 'd')
358
     *
359
     * @param $environmentLine
360
     * @return array
361
     */
362
    private function parseEnvironmentLine($environmentLine)
363
    {
364
        $variables     = array();@
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
365
        $variableLines = explode(' ', $environmentLine ?: '');
366
367
        foreach ($variableLines as $variableLine) {
368
            if (!empty($variableLine)) {
369
                list($key, $value) = $this->parseEnvironementVariable($variableLine);
370
                $variables[$key]   = $value;
371
            }
372
        }
373
374
        return $variables;
375
    }
376
377
    /**
378
     * Parse an envar
379
     *
380
     * @param $envVar
381
     * @return array<Key, Value>
0 ignored issues
show
Documentation introduced by
The doc-type array<Key, could not be parsed: Expected ">" at position 5, but found "end of type". (view supported doc-types)

This check marks PHPDoc comments that could not be parsed by our parser. To see which comment annotations we can parse, please refer to our documentation on supported doc-types.

Loading history...
382
     */
383
    private function parseEnvironementVariable($envVar)
384
    {
385
        return explode('=', $envVar);
386
    }
387
388
    private function isLanguageVersionSupported($language, $version)
389
    {
390
        return file_exists(__DIR__
391
            . DIRECTORY_SEPARATOR . '..'
392
            . DIRECTORY_SEPARATOR . '..'
393
            . DIRECTORY_SEPARATOR . '..'
394
            . DIRECTORY_SEPARATOR . '..'
395
            . DIRECTORY_SEPARATOR . 'resources'
396
            . DIRECTORY_SEPARATOR . 'templates'
397
            . DIRECTORY_SEPARATOR . $language
398
            . DIRECTORY_SEPARATOR . 'Dockerfile-' . $version . '.twig');
399
    }
400
}
401