DeploymentUtils   A
last analyzed

Complexity

Total Complexity 24

Size/Duplication

Total Lines 466
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 15

Test Coverage

Coverage 86.63%

Importance

Changes 37
Bugs 9 Features 9
Metric Value
wmc 24
c 37
b 9
f 9
lcom 1
cbo 15
dl 0
loc 466
ccs 162
cts 187
cp 0.8663
rs 9.1666

13 Methods

Rating   Name   Duplication   Size   Complexity  
B createServices() 0 33 4
A determineDeploymentSlice() 0 58 3
A login() 0 11 1
A logout() 0 11 1
B cleanUp() 0 30 2
B deploy() 0 28 2
B runCommand() 0 29 2
A deploySteps() 0 19 2
A isInitialDeploy() 0 4 1
B setEnvironmentVariables() 0 24 3
B addRoute() 0 26 1
A startApplication() 0 23 1
A renderTargetName() 0 4 1
1
<?php
2
/**
3
 * Utils for a successful CF deployment.
4
 */
5
6
namespace Graviton\Deployment\Services\CloudFoundry;
7
8
use Graviton\Deployment\Deployment;
9
use Graviton\Deployment\Steps\CloudFoundry\StepApp;
10
use Graviton\Deployment\Steps\CloudFoundry\StepBindService;
11
use Graviton\Deployment\Steps\CloudFoundry\StepCreateService;
12
use Graviton\Deployment\Steps\CloudFoundry\StepDelete;
13
use Graviton\Deployment\Steps\CloudFoundry\StepLogin;
14
use Graviton\Deployment\Steps\CloudFoundry\StepLogout;
15
use Graviton\Deployment\Steps\CloudFoundry\StepPush;
16
use Graviton\Deployment\Steps\CloudFoundry\StepRoute;
17
use Graviton\Deployment\Steps\CloudFoundry\StepSetEnv;
18
use Graviton\Deployment\Steps\CloudFoundry\StepStart;
19
use Graviton\Deployment\Steps\CloudFoundry\StepStop;
20
use Graviton\Deployment\Steps\StepInterface;
21
use Symfony\Component\Console\Output\OutputInterface;
22
use Symfony\Component\Process\Exception\ProcessFailedException;
23
24
/**
25
 * @author  List of contributors <https://github.com/libgraviton/deploy-scripts/graphs/contributors>
26
 * @license http://opensource.org/licenses/gpl-license.php GNU Public License
27
 * @link    http://swisscom.ch
28
 */
29
final class DeploymentUtils
30
{
31
    /** @var array slices for "Blue/Green-Deployment" mechanism */
32
    private static $slices = ['blue', 'green'];
33
34
    /** @var bool Indicator to signal an initial deployment */
35
    private static $isInitial = false;
36
37
    /**
38
     * Creates mandatory services on CF.
39
     *
40
     * @param Deployment      $deploy          Command handler.
41
     * @param OutputInterface $output          Output of the command
42
     * @param array           $configuration   Application configuration (read from deploy.yml)
43
     * @param string          $applicationName Application to be used
44
     * @param string          $slice           deployment location in blue/green deployment.
45
     *
46
     * @return void
47
     */
48 4
    public static function createServices(
49
        Deployment $deploy,
50
        OutputInterface $output,
51
        array $configuration,
52
        $applicationName,
53
        $slice
54
    ) {
55 4
        if (empty($configuration['cf_services'])) {
56
            $output->writeln('No services defined in configuration. Skipping!');
57
58
            return;
59
        }
60
61 4
        $steps = [];
62 4
        foreach ($configuration['cf_services'] as $service => $plan) {
63 4
            $name = null;
64 4
            $type = $service;
65 4
            if (is_array($plan)) {
66 3
                $name = $service;
67 3
                $type = $plan['service'];
68 3
                $plan = $plan['plan'];
69 3
            }
70 4
            $steps[] = new StepCreateService($configuration, $applicationName, $type, $plan, $name);
71 4
            $steps[] = new StepBindService($configuration, $applicationName, $slice, $service);
72 4
        }
73
74 4
        self::deploySteps(
75 4
            $deploy,
76 4
            $output,
77 4
            $steps,
78
            'Creating services'
79 4
        );
80 4
    }
81
82
    /**
83
     * Determines what slice to be used on blue/green deployment.
84
     *
85
     * **NOTICE**
86
     * Behavior on different szenarios
87
     *
88
     * trail    | blue  | green | deployed
89
     * ===================================
90
     * 1st      | n/a   | n/a   |  blue
91
     * 2nd      | avail | n/a   | green (blue is dropped)
92
     * 3rd      | n/a   | avail | blue (green is dropped)
93
     * manually | avail | avail | green (blue will be dropped)
94
     * altered  |       |       |
95
     *
96
     * @param Deployment      $deploy          Command handler.
97
     * @param OutputInterface $output          Output of the command
98
     * @param array           $configuration   Application configuration (read from deploy.yml).
99
     * @param string          $applicationName Application to be cleaned up
100
     *
101
     * @return array
102
     */
103 5
    public static function determineDeploymentSlice(
104
        Deployment $deploy,
105
        OutputInterface $output,
106
        array $configuration,
107
        $applicationName
108
    ) {
109
        try {
110 5
            self::deploySteps(
111 5
                $deploy,
112 5
                $output,
113
                [
114 5
                    new StepApp($configuration, $applicationName, self::$slices[0])
115 5
                ],
116 5
                'Determining which application slice to be deployed',
117 5
                '',
118
                false
119 5
            );
120 4
            $slice = self::$slices[1];
121 4
            $oldSlice = self::$slices[0];
122 5
        } catch (ProcessFailedException $e) {
123 1
            $slice = self::$slices[0];
124 1
            $oldSlice = self::$slices[1];
125
        }
126
127 5
        $output->writeln('... <fg=yellow>done</fg=yellow>');
128 5
        $startMsg = sprintf(
129 5
            '... found. Using slice <fg=cyan>%s</fg=cyan> as deployment target.',
130 5
            self::renderTargetName($applicationName, $slice)
131 5
        );
132
133
        try {
134
            // check, if there is an »old« application as well
135 5
            self::deploySteps(
136 5
                $deploy,
137 5
                $output,
138
                [
139 5
                    new StepApp($configuration, $applicationName, $oldSlice),
140 5
                ],
141 5
                'Trying to find deployment slice ('.$oldSlice.')',
142 5
                $startMsg,
143
                false
144 5
            );
145 4
            self::$isInitial = false;
146 5
        } catch (ProcessFailedException $e) {
147 1
            $slice = self::$slices[0];
148 1
            $oldSlice = self::$slices[1];
149
150 1
            $output->writeln(
151
                '... not found. Using slice <fg=cyan>'.
152 1
                self::renderTargetName($applicationName, $slice).
153
                '</fg=cyan> as deployment target.'
154 1
            );
155 1
            $output->writeln('Initial Deploy, remember to set up the DB');
156 1
            self::$isInitial = true;
157
        }
158
159 5
        return [$slice, $oldSlice];
160
    }
161
162
    /**
163
     * Logs in to CF.
164
     *
165
     * @param Deployment      $deploy        Command handler.
166
     * @param OutputInterface $output        Output of the command
167
     * @param array           $configuration Application configuration (read from deploy.yml).
168
     *
169
     * @return void
170
     */
171 4
    public static function login(Deployment $deploy, OutputInterface $output, array $configuration)
172
    {
173 4
        self::deploySteps(
174 4
            $deploy,
175 4
            $output,
176 4
            [new StepLogin($configuration)],
177 4
            'Trying to login',
178 4
            '... <fg=yellow>done</fg=yellow>',
179
            false
180 4
        );
181 4
    }
182
183
    /**
184
     * Logs off from CF.
185
     *
186
     * @param Deployment      $deploy        Command handler.
187
     * @param OutputInterface $output        Output of the command
188
     * @param array           $configuration Application configuration (read from deploy.yml).
189
     *
190
     * @return void
191
     */
192 4
    public static function logout(Deployment $deploy, OutputInterface $output, array $configuration)
193
    {
194 4
        self::deploySteps(
195 4
            $deploy,
196 4
            $output,
197 4
            [new StepLogout($configuration)],
198 4
            'Logging out',
199 4
            '... <fg=yellow>bye</fg=yellow>',
200
            false
201 4
        );
202 4
    }
203
204
    /**
205
     * Removes instances of the application not needed anymore.
206
     *
207
     * @param Deployment      $deploy          Command handler.
208
     * @param OutputInterface $output          Output of the command
209
     * @param array           $configuration   Application configuration (read from deploy.yml).
210
     * @param string          $applicationName Application to be cleaned up
211
     * @param string          $route           Used a the subdomain for the application route.
212
     * @param string          $slice           Slice to be removed.
213
     *
214
     * @return void
215
     */
216 4
    public static function cleanUp(
217
        Deployment $deploy,
218
        OutputInterface $output,
219
        array $configuration,
220
        $applicationName,
221
        $route,
222
        $slice
223
    ) {
224 4
        $target = self::renderTargetName($applicationName, $slice);
225
        $steps = [
226 4
            new StepRoute($configuration, $applicationName, $target, $route, 'unmap'),
227 4
            new StepStop($configuration, $applicationName, $slice),
228 4
            new StepDelete($configuration, $applicationName, $slice, true),
229 4
        ];
230
231
232
        try {
233
            // remove 'old' deployment
234 4
            self::deploySteps(
235 4
                $deploy,
236 4
                $output,
237 4
                $steps,
238 4
                'Removing <fg=cyan>'.$target.'</fg=cyan> from Cloud Foundry.'
239 4
            );
240 4
        } catch (ProcessFailedException $e) {
241
            $output->writeln(
242
                PHP_EOL.'<error>Unable to cleanUp old instances: '.PHP_EOL.$e->getProcess()->getOutput().'</error>'
243
            );
244
        }
245 4
    }
246
247
    /**
248
     * Removes instances of the application not needed anymore.
249
     *
250
     * @param Deployment      $deploy          Command handler.
251
     * @param OutputInterface $output          Output of the command
252
     * @param array           $configuration   Application configuration (read from deploy.yml).
253
     * @param string          $applicationName Application to be cleaned up
254
     * @param string          $route           Used a the subdomain for the application route.
255
     * @param string          $slice           Slice to be deployed.
256
     * @param boolean         $start           Start the deploy automagically
257
     *
258
     * @return void
259
     */
260 4
    public static function deploy(
261
        Deployment $deploy,
262
        OutputInterface $output,
263
        array $configuration,
264
        $applicationName,
265
        $route,
266
        $slice,
267
        $start = true
268
    ) {
269 4
        $target = self::renderTargetName($applicationName, $slice);
270 4
        $output->writeln('Will deploy application: <fg=cyan>'.$target.'</fg=cyan>.');
271
        $steps = [
272 4
            new StepPush($configuration, $applicationName, $slice, $start),
273 4
        ];
274 4
        if ($start) {
275 1
            $steps[] = new StepRoute($configuration, $applicationName, $target, $route, 'map');
276 1
        }
277
278 4
        self::deploySteps(
279 4
            $deploy,
280 4
            $output,
281 4
            $steps,
282 4
            'Pushing '.$target.' to Cloud Foundry.'.PHP_EOL,
283 4
            '... <fg=yellow>done</fg=yellow>',
284 4
            true,
285
            true
286 4
        );
287 4
    }
288
289
    /**
290
     * Runs a command on a Cloud Foundry instance
291
     *
292
     * @param Deployment      $deploy          deploy handler
293
     * @param OutputInterface $output          output interface
294
     * @param array           $configuration   the configuration params
295
     * @param string          $command         the command to be executed
296
     * @param string          $applicationName the application name
297
     *
298
     * @return void
299
     */
300
    public static function runCommand(
301
        Deployment $deploy,
302
        OutputInterface $output,
303
        array $configuration,
304
        $command,
305
        $applicationName
306
    ) {
307
        $id = uniqid();
308
        $output->writeln('Will run: <fg=cyan>' . $command . '</fg=cyan> on ' . $applicationName . '-run-' . $id);
309
        $steps = [
310
            new StepPush($configuration, $applicationName, 'run-' . $id, false, true, $command),
311
        ];
312
313
        foreach ($configuration['cf_services'] as $service => $val) {
314
            array_push($steps, new StepBindService($configuration, $applicationName, 'run-' . $id, $service));
315
        }
316
317
        array_push($steps, new StepPush($configuration, $applicationName, 'run-' . $id, true, true, $command));
318
319
        self::deploySteps(
320
            $deploy,
321
            $output,
322
            $steps,
323
            'Executing <fg=cyan>' . $command .'</fg=cyan>' . PHP_EOL,
324
            '<fg=green>Command Finished</fg=green>',
325
            true,
326
            true
327
        );
328
    }
329
330
    /**
331
     * Initializes a single
332
     *
333
     * @param Deployment      $deploy               Command handler.
334
     * @param OutputInterface $output               Output of the command
335
     * @param StepInterface[] $steps                Process step to be executed.
336
     * @param string          $startMsg             Message to  be shown on start.
337
     * @param string          $endMsg               Message to be shown on end.
338
     * @param bool            $returnProcessMessage Include message from process in output..
339
     * @param bool            $forceImmediateOutput Forces the process to send every output to stdout immediately.
340
     *
341
     * @return void
342
     */
343 13
    private static function deploySteps(
344
        Deployment $deploy,
345
        OutputInterface $output,
346
        array $steps,
347
        $startMsg,
348
        $endMsg = '... <fg=yellow>done</fg=yellow>',
349
        $returnProcessMessage = true,
350
        $forceImmediateOutput = false
351
    ) {
352 13
        $output->write($startMsg);
353 13
        $msg = $deploy->resetSteps()
354 13
            ->registerSteps($steps)
355 13
            ->deploy($forceImmediateOutput);
356 12
        $output->writeln($endMsg);
357
358 12
        if (true === $returnProcessMessage) {
359 9
            $output->writeln('<fg=white>' . $msg . '</fg=white>');
360 9
        }
361 12
    }
362
363
    /**
364
     * Determine if the slice to be deploy
365
     *
366
     * @return bool
367
     */
368 4
    public static function isInitialDeploy()
369
    {
370 4
        return self::$isInitial;
371
    }
372
373
    /**
374
     * Injects specific environment vars to the Cloud Foundry setup.
375
     *
376
     * @param Deployment      $deploy        Command handler.
377
     * @param OutputInterface $output        Output of the command
378
     * @param array           $configuration Application configuration (read from deploy.yml).
379
     * @param string          $application   Application the env vars to be defined in
380
     *
381
     * @return void
382
     */
383 4
    public static function setEnvironmentVariables(
384
        Deployment $deploy,
385
        OutputInterface $output,
386
        array $configuration,
387
        $application
388
    ) {
389 4
        if (empty($configuration['cf_environment_vars'])) {
390
            $output->writeln('No environment vars defined in configuration. Skipping!');
391
392
            return;
393
        }
394
395 4
        $steps = [];
396 4
        foreach ($configuration['cf_environment_vars'] as $envName => $envValue) {
397 4
            $steps[] = new StepSetEnv($configuration, $application, $envName, $envValue);
398 4
        }
399
400 4
        self::deploySteps(
401 4
            $deploy,
402 4
            $output,
403 4
            $steps,
404
            'Defining environment variables'
405 4
        );
406 4
    }
407
408
    /**
409
     * Adds a new route to the named application
410
     *
411
     * @param Deployment      $deploy          Command handler.
412
     * @param OutputInterface $output          Output of the command
413
     * @param array           $configuration   Application configuration (read from deploy.yml).
414
     * @param string          $applicationName Application to be cleaned up
415
     * @param string          $slice           Slice to be deployed.
416
     * @param string          $route           Used a the subdomain for the application route.
417
     *
418
     * @return void
419
     */
420 4
    public static function addRoute(
421
        Deployment $deploy,
422
        OutputInterface $output,
423
        array $configuration,
424
        $applicationName,
425
        $slice,
426
        $route
427
    ) {
428 4
        $targetName = DeploymentUtils::renderTargetName($applicationName, $slice);
429 4
        $startMessage = sprintf(
430 4
            'Adding route (%s) to application (%s).'.PHP_EOL,
431 4
            $route,
432
            $applicationName
433 4
        );
434 4
        self::deploySteps(
435 4
            $deploy,
436 4
            $output,
437
            [
438 4
                new StepRoute($configuration, $applicationName, $targetName, $route, 'map'),
439 4
            ],
440 4
            $startMessage,
441 4
            '... <fg=yellow>done</fg=yellow>',
442 4
            true,
443
            true
444 4
        );
445 4
    }
446
447
    /**
448
     * Starts the named slice of the provided application.
449
     *
450
     * @param Deployment      $deploy          Command handler.
451
     * @param OutputInterface $output          Output of the command
452
     * @param array           $configuration   Application configuration (read from deploy.yml).
453
     * @param string          $applicationName Application to be cleaned up
454
     * @param string          $slice           Slice to be deployed..
455
     *
456
     * @return void
457
     */
458 4
    public static function startApplication(
459
        Deployment $deploy,
460
        OutputInterface $output,
461
        array $configuration,
462
        $applicationName,
463
        $slice
464
    ) {
465 4
        $startMessage = sprintf(
466 4
            'Starting application (%s).'.PHP_EOL,
467
            $applicationName
468 4
        );
469 4
        self::deploySteps(
470 4
            $deploy,
471 4
            $output,
472
            [
473 4
                new StepStart($configuration, $applicationName, $slice),
474 4
            ],
475 4
            $startMessage,
476 4
            '... <fg=yellow>done</fg=yellow>',
477 4
            true,
478
            true
479 4
        );
480 4
    }
481
482
    /**
483
     * Provides the name of the target to used.
484
     *
485
     * @param string $application Name of the current application
486
     * @param string $slice       Slice to be used (from blue/green deployment)
487
     *
488
     * @return string
489
     */
490 8
    private static function renderTargetName($application, $slice)
491
    {
492 8
        return sprintf('%s-%s', $application, $slice);
493
    }
494
}
495