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
|
|||
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), $this->newrelic->getApiHost()); |
||
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, ?string $api_host = null): 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(\sprintf('https://%s/deployments.xml', $api_host ?? 'api.newrelic.com'), 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 |
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.