Passed
Push — master ( bf6a9d...dfd885 )
by Tobias
02:21
created

NotifyDeploymentCommand::execute()   B

Complexity

Conditions 8
Paths 7

Size

Total Lines 36

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 8
nc 7
nop 2
dl 0
loc 36
rs 8.0995
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/*
6
 * This file is part of Ekino New Relic bundle.
7
 *
8
 * (c) Ekino - Thomas Rabaix <[email protected]>
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Ekino\NewRelicBundle\Command;
15
16
use Ekino\NewRelicBundle\NewRelic\Config;
17
use Symfony\Component\Console\Command\Command;
18
use Symfony\Component\Console\Input\InputInterface;
19
use Symfony\Component\Console\Input\InputOption;
20
use Symfony\Component\Console\Output\OutputInterface;
21
22
class NotifyDeploymentCommand extends Command
23
{
24
    const EXIT_NO_APP_NAMES = 1;
25
    const EXIT_UNAUTHORIZED = 2;
26
    const EXIT_HTTP_ERROR = 3;
27
28
    protected static $defaultName = 'newrelic:notify-deployment';
29
30
    private $newrelic;
31
32
    public function __construct(Config $newrelic)
33
    {
34
        $this->newrelic = $newrelic;
35
36
        parent::__construct();
37
    }
38
39
    protected function configure(): void
40
    {
41
        $this
42
            ->setDefinition([
43
                new InputOption(
44
                    'user', null, InputOption::VALUE_OPTIONAL,
45
                    'The name of the user/process that triggered this deployment', null
46
                ),
47
                new InputOption(
48
                    'revision', null, InputOption::VALUE_OPTIONAL,
49
                    'A revision number (e.g., git commit SHA)', null
50
                ),
51
                new InputOption(
52
                    'changelog', null, InputOption::VALUE_OPTIONAL,
53
                    'A list of changes for this deployment', null
54
                ),
55
                new InputOption(
56
                    'description', null, InputOption::VALUE_OPTIONAL,
57
                    'Text annotation for the deployment — notes for you', null
58
                ),
59
            ])
60
            ->setDescription('Notifies New Relic that a new deployment has been made')
61
        ;
62
    }
63
64
    protected function execute(InputInterface $input, OutputInterface $output): int
65
    {
66
        $appNames = $this->newrelic->getDeploymentNames();
67
68
        if (!$appNames) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $appNames of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
69
            $output->writeLn('<error>No deployment application configured.</error>');
70
71
            return self::EXIT_NO_APP_NAMES;
72
        }
73
74
        $exitCode = 0;
75
76
        foreach ($appNames as $appName) {
77
            $response = $this->performRequest($this->newrelic->getApiKey(), $this->createPayload($appName, $input));
78
79
            switch ($response['status']) {
80
                case 200:
81
                case 201:
82
                    $output->writeLn(\sprintf("Recorded deployment to '%s' (%s)", $appName, ($input->getOption('description') ? $input->getOption('description') : \date('r'))));
83
                    break;
84
                case 403:
85
                    $output->writeLn(\sprintf("<error>Deployment not recorded to '%s': API key invalid</error>", $appName));
86
                    $exitCode = self::EXIT_UNAUTHORIZED;
87
                    break;
88
                case null:
89
                    $output->writeLn(\sprintf("<error>Deployment not recorded to '%s': Did not understand response</error>", $appName));
90
                    $exitCode = self::EXIT_HTTP_ERROR;
91
                    break;
92
                default:
93
                    $output->writeLn(\sprintf("<error>Deployment not recorded to '%s': Received HTTP status %d</error>", $appName, $response['status']));
94
                    $exitCode = self::EXIT_HTTP_ERROR;
95
                    break;
96
            }
97
        }
98
99
        return $exitCode;
100
    }
101
102
    public function performRequest(string $api_key, string $payload): array
103
    {
104
        $headers = [
105
            \sprintf('x-api-key: %s', $api_key),
106
            'Content-type: application/x-www-form-urlencoded',
107
        ];
108
109
        $context = [
110
            'http' => [
111
                'method' => 'POST',
112
                'header' => \implode("\r\n", $headers),
113
                'content' => $payload,
114
                'ignore_errors' => true,
115
            ],
116
        ];
117
118
        $level = \error_reporting(0);
119
        $content = \file_get_contents('https://api.newrelic.com/deployments.xml', false, \stream_context_create($context));
120
        \error_reporting($level);
121
        if (false === $content) {
122
            $error = \error_get_last();
123
            throw new \RuntimeException($error['message']);
124
        }
125
126
        $response = [
127
            'status' => null,
128
            'error' => null,
129
        ];
130
131
        if (isset($http_response_header[0])) {
132
            \preg_match('/^HTTP\/1.\d (\d+)/', $http_response_header[0], $matches);
133
134
            if (isset($matches[1])) {
135
                $status = $matches[1];
136
137
                $response['status'] = $status;
138
139
                \preg_match('/<error>(.*?)<\/error>/', $content, $matches);
140
141
                if (isset($matches[1])) {
142
                    $response['error'] = $matches[1];
143
                }
144
            }
145
        }
146
147
        return $response;
148
    }
149
150
    private function createPayload(string $appName, InputInterface $input): string
151
    {
152
        $content_array = [
153
            'deployment[app_name]' => $appName,
154
        ];
155
156
        if (($user = $input->getOption('user'))) {
157
            $content_array['deployment[user]'] = $user;
158
        }
159
160
        if (($revision = $input->getOption('revision'))) {
161
            $content_array['deployment[revision]'] = $revision;
162
        }
163
164
        if (($changelog = $input->getOption('changelog'))) {
165
            $content_array['deployment[changelog]'] = $changelog;
166
        }
167
168
        if (($description = $input->getOption('description'))) {
169
            $content_array['deployment[description]'] = $description;
170
        }
171
172
        return \http_build_query($content_array);
173
    }
174
}
175