1 | <?php |
||
37 | class ImportCommand extends ImportCommandAbstract |
||
38 | { |
||
39 | |||
40 | /** |
||
41 | * @var HttpClient |
||
42 | */ |
||
43 | private $client; |
||
44 | |||
45 | /** |
||
46 | * @var FrontMatter |
||
47 | */ |
||
48 | private $frontMatter; |
||
49 | |||
50 | /** |
||
51 | * @var Parser |
||
52 | */ |
||
53 | private $parser; |
||
54 | |||
55 | /** |
||
56 | * @var VarCloner |
||
57 | */ |
||
58 | private $cloner; |
||
59 | |||
60 | /** |
||
61 | * @var Dumper |
||
62 | */ |
||
63 | private $dumper; |
||
64 | |||
65 | /** |
||
66 | * Count of errors |
||
67 | * @var array |
||
68 | */ |
||
69 | private $errors = []; |
||
70 | |||
71 | /** |
||
72 | * Header basic auth |
||
73 | * @var string |
||
74 | */ |
||
75 | private $headerBasicAuth; |
||
76 | |||
77 | /** |
||
78 | * Header for custom variables |
||
79 | * @var array |
||
80 | */ |
||
81 | private $customHeaders; |
||
82 | |||
83 | /** |
||
84 | * @param Logger $logger logger |
||
85 | * @param HttpClient $client Grv HttpClient guzzle http client |
||
86 | * @param Finder $finder symfony/finder instance |
||
87 | * @param FrontMatter $frontMatter frontmatter parser |
||
88 | * @param Parser $parser yaml/json parser |
||
89 | * @param VarCloner $cloner var cloner for dumping reponses |
||
90 | * @param Dumper $dumper dumper for outputing responses |
||
91 | */ |
||
92 | 5 | public function __construct( |
|
111 | |||
112 | /** |
||
113 | * Configures the current command. |
||
114 | * |
||
115 | * @return void |
||
116 | */ |
||
117 | 5 | protected function configure() |
|
118 | { |
||
119 | $this |
||
120 | 5 | ->setName('graviton:import') |
|
121 | 5 | ->setDescription('Import files from a folder or file.') |
|
122 | 5 | ->addOption( |
|
123 | 5 | 'rewrite-host', |
|
124 | 5 | 'r', |
|
125 | 5 | InputOption::VALUE_OPTIONAL, |
|
126 | 5 | 'Replace the value of this option with the <host> value before importing.', |
|
127 | 5 | 'http://localhost' |
|
128 | ) |
||
129 | 5 | ->addOption( |
|
130 | 5 | 'rewrite-to', |
|
131 | 5 | 't', |
|
132 | 5 | InputOption::VALUE_OPTIONAL, |
|
133 | 5 | 'String to use as the replacement value for the [REWRITE-HOST] string.', |
|
134 | 5 | '<host>' |
|
135 | ) |
||
136 | 5 | ->addOption( |
|
137 | 5 | 'sync-requests', |
|
138 | 5 | 's', |
|
139 | 5 | InputOption::VALUE_NONE, |
|
140 | 5 | 'Send requests synchronously' |
|
141 | ) |
||
142 | 5 | ->addOption( |
|
143 | 5 | 'no-overwrite', |
|
144 | 5 | 'o', |
|
145 | 5 | InputOption::VALUE_NONE, |
|
146 | 5 | 'If set, we will check for record existence and not overwrite existing ones.' |
|
147 | ) |
||
148 | 5 | ->addOption( |
|
149 | 5 | 'headers-basic-auth', |
|
150 | 5 | 'a', |
|
151 | 5 | InputOption::VALUE_OPTIONAL, |
|
152 | 5 | 'Header user:password for Basic auth' |
|
153 | ) |
||
154 | 5 | ->addOption( |
|
155 | 5 | 'custom-headers', |
|
156 | 5 | 'c', |
|
157 | 5 | InputOption::VALUE_OPTIONAL | InputOption::VALUE_IS_ARRAY, |
|
158 | 5 | 'Custom Header variable(s), -c{key:value} and multiple is optional.' |
|
159 | ) |
||
160 | 5 | ->addOption( |
|
161 | 5 | 'input-file', |
|
162 | 5 | 'i', |
|
163 | 5 | InputOption::VALUE_REQUIRED, |
|
164 | 5 | 'If provided, the list of files to load will be loaded from this file, one file per line.' |
|
165 | ) |
||
166 | 5 | ->addArgument( |
|
167 | 5 | 'host', |
|
168 | 5 | InputArgument::REQUIRED, |
|
169 | 5 | 'Protocol and host to load data into (ie. https://graviton.nova.scapp.io)' |
|
170 | ) |
||
171 | 5 | ->addArgument( |
|
172 | 5 | 'file', |
|
173 | 5 | InputArgument::IS_ARRAY, |
|
174 | 5 | 'Directories or files to load' |
|
175 | ); |
||
176 | 5 | } |
|
177 | |||
178 | /** |
||
179 | * Executes the current command. |
||
180 | * |
||
181 | * @param Finder $finder Finder |
||
182 | * @param InputInterface $input User input on console |
||
183 | * @param OutputInterface $output Output of the command |
||
184 | * |
||
185 | * @return integer |
||
186 | */ |
||
187 | 5 | protected function doImport(Finder $finder, InputInterface $input, OutputInterface $output) |
|
188 | { |
||
189 | 5 | $exitCode = 0; |
|
190 | 5 | $host = $input->getArgument('host'); |
|
191 | 5 | $rewriteHost = $input->getOption('rewrite-host'); |
|
192 | 5 | $rewriteTo = $input->getOption('rewrite-to'); |
|
193 | 5 | $this->headerBasicAuth = $input->getOption('headers-basic-auth'); |
|
194 | 5 | $this->customHeaders = $input->getOption('custom-headers'); |
|
|
|||
195 | 5 | if ($rewriteTo === $this->getDefinition()->getOption('rewrite-to')->getDefault()) { |
|
196 | 5 | $rewriteTo = $host; |
|
197 | } |
||
198 | 5 | $noOverwrite = $input->getOption('no-overwrite'); |
|
199 | |||
200 | 5 | $this->importPaths($finder, $output, $host, $rewriteHost, $rewriteTo, $noOverwrite); |
|
201 | |||
202 | // Error exit |
||
203 | 4 | if (empty($this->errors)) { |
|
204 | // No errors |
||
205 | 3 | $this->logger->info('No errors'); |
|
206 | } else { |
||
207 | // Yes, there was errors |
||
208 | 1 | $this->logger->error( |
|
209 | 1 | 'There were import errors', |
|
210 | [ |
||
211 | 1 | 'errorCount' => count($this->errors), |
|
212 | 1 | 'errors' => $this->errors |
|
213 | ] |
||
214 | ); |
||
215 | 1 | $exitCode = 1; |
|
216 | } |
||
217 | 4 | return $exitCode; |
|
218 | } |
||
219 | |||
220 | /** |
||
221 | * @param Finder $finder finder primmed with files to import |
||
222 | * @param OutputInterface $output output interfac |
||
223 | * @param string $host host to import into |
||
224 | * @param string $rewriteHost string to replace with value from $rewriteTo during loading |
||
225 | * @param string $rewriteTo string to replace value from $rewriteHost with during loading |
||
226 | * @param boolean $noOverwrite should we not overwrite existing records? |
||
227 | * |
||
228 | * @return void |
||
229 | * |
||
230 | * @throws MissingTargetException |
||
231 | */ |
||
232 | 5 | protected function importPaths( |
|
233 | Finder $finder, |
||
234 | OutputInterface $output, |
||
235 | $host, |
||
236 | $rewriteHost, |
||
237 | $rewriteTo, |
||
238 | $noOverwrite = false |
||
239 | ) { |
||
240 | /** @var SplFileInfo $file */ |
||
241 | 5 | foreach ($finder as $file) { |
|
242 | 5 | $doc = $this->frontMatter->parse($file->getContents()); |
|
243 | |||
244 | 5 | $this->logger->info("Loading data from ${file}"); |
|
245 | |||
246 | 5 | if (!array_key_exists('target', $doc->getData())) { |
|
247 | 1 | throw new MissingTargetException('Missing target in \'' . $file . '\''); |
|
248 | } |
||
249 | |||
250 | 4 | $targetUrl = sprintf('%s%s', $host, $doc->getData()['target']); |
|
251 | |||
252 | 4 | $this->importResource( |
|
253 | 4 | $targetUrl, |
|
254 | 4 | (string) $file, |
|
255 | 4 | $output, |
|
256 | 4 | $doc, |
|
257 | 4 | $rewriteHost, |
|
258 | 4 | $rewriteTo, |
|
259 | 4 | $noOverwrite |
|
260 | ); |
||
261 | } |
||
262 | } |
||
263 | |||
264 | /** |
||
265 | * @param string $targetUrl target url to import resource into |
||
266 | * @param string $file path to file being loaded |
||
267 | * @param OutputInterface $output output of the command |
||
268 | * @param Document $doc document to load |
||
269 | * @param string $rewriteHost string to replace with value from $host during loading |
||
270 | * @param string $rewriteTo string to replace value from $rewriteHost with during loading |
||
271 | * @param boolean $noOverwrite should we not overwrite existing records? |
||
272 | * |
||
273 | * @return Promise\PromiseInterface|null |
||
274 | */ |
||
275 | protected function importResource( |
||
276 | $targetUrl, |
||
277 | $file, |
||
278 | OutputInterface $output, |
||
279 | Document $doc, |
||
280 | $rewriteHost, |
||
281 | $rewriteTo, |
||
282 | $noOverwrite = false |
||
283 | ) { |
||
284 | 4 | $content = str_replace($rewriteHost, $rewriteTo, $doc->getContent()); |
|
285 | 4 | $uploadFile = $this->validateUploadFile($doc, $file); |
|
286 | |||
287 | $data = [ |
||
288 | 4 | 'json' => $this->parseContent($content, $file), |
|
289 | 4 | 'upload' => $uploadFile, |
|
290 | 'headers'=> [] |
||
291 | ]; |
||
292 | |||
293 | // Authentication or custom headers. |
||
294 | 4 | if ($this->headerBasicAuth) { |
|
295 | $data['headers']['Authorization'] = 'Basic '. base64_encode($this->headerBasicAuth); |
||
296 | } |
||
297 | 4 | if ($this->customHeaders) { |
|
298 | foreach ($this->customHeaders as $headers) { |
||
299 | list($key, $value) = explode(':', $headers); |
||
300 | $data['headers'][$key] = $value; |
||
301 | } |
||
302 | } |
||
303 | 4 | if (empty($data['headers'])) { |
|
304 | 4 | unset($data['headers']); |
|
305 | } |
||
306 | |||
307 | // skip if no overwriting has been requested |
||
308 | 4 | if ($noOverwrite) { |
|
309 | $response = $this->client->request('GET', $targetUrl, array_merge($data, ['http_errors' => false])); |
||
310 | if ($response->getStatusCode() == 200) { |
||
311 | $this->logger->info( |
||
312 | sprintf( |
||
313 | 'Skipping <%s> as "no overwrite" is activated and it does exist.', |
||
314 | $targetUrl |
||
315 | ) |
||
316 | ); |
||
317 | return; |
||
318 | } |
||
319 | } |
||
320 | |||
321 | try { |
||
322 | 4 | if ($uploadFile) { |
|
323 | 1 | unset($this->errors[$file]); |
|
324 | try { |
||
325 | 1 | $this->client->request('DELETE', $targetUrl, $data); |
|
326 | 1 | $this->logger->info("File deleted: ${targetUrl}"); |
|
327 | } catch (\Exception $e) { |
||
328 | $this->logger->error( |
||
329 | sprintf( |
||
330 | 'Failed to delete <%s> with message \'%s\'', |
||
331 | $targetUrl, |
||
332 | $e->getMessage() |
||
333 | ), |
||
334 | ['exception' => $e] |
||
335 | ); |
||
336 | } |
||
337 | } |
||
338 | |||
339 | 4 | $response = $this->client->request( |
|
340 | 4 | 'PUT', |
|
341 | 4 | $targetUrl, |
|
342 | 4 | $data |
|
343 | ); |
||
344 | |||
345 | 3 | $this->logger->info('Wrote ' . $response->getHeader('Link')[0]); |
|
346 | 1 | } catch (\Exception $e) { |
|
347 | 1 | $this->errors[$file] = $e->getMessage(); |
|
348 | 1 | $this->logger->error( |
|
349 | 1 | sprintf( |
|
350 | 1 | 'Failed to write <%s> from \'%s\' with message \'%s\'', |
|
351 | 1 | $e->getRequest()->getUri(), |
|
352 | 1 | $file, |
|
353 | 1 | $e->getMessage() |
|
354 | ), |
||
355 | 1 | ['exception' => $e] |
|
356 | ); |
||
357 | 1 | if ($output->getVerbosity() >= OutputInterface::VERBOSITY_VERBOSE) { |
|
358 | 1 | $this->dumper->dump( |
|
359 | 1 | $this->cloner->cloneVar( |
|
360 | 1 | $this->parser->parse($e->getResponse()->getBody(), Yaml::PARSE_OBJECT_FOR_MAP) |
|
361 | ), |
||
362 | function ($line, $depth) use ($output) { |
||
363 | 1 | if ($depth > 0) { |
|
364 | 1 | $output->writeln( |
|
365 | 1 | '<error>' . str_pad(str_repeat(' ', $depth) . $line, 140, ' ') . '</error>' |
|
366 | ); |
||
367 | } |
||
368 | 1 | } |
|
369 | ); |
||
370 | } |
||
371 | } |
||
372 | 4 | } |
|
373 | |||
374 | /** |
||
375 | * parse contents of a file depending on type |
||
376 | * |
||
377 | * @param string $content contents part of file |
||
378 | * @param string $file full path to file |
||
379 | * |
||
380 | * @return mixed |
||
381 | * @throws UnknownFileTypeException |
||
382 | * @throws ParseException |
||
383 | */ |
||
384 | protected function parseContent($content, $file) |
||
385 | { |
||
386 | 4 | if (substr($file, -5) == '.json') { |
|
387 | 3 | $data = json_decode($content); |
|
388 | 3 | if (json_last_error() !== JSON_ERROR_NONE) { |
|
389 | throw new ParseException( |
||
390 | sprintf( |
||
391 | '%s in %s', |
||
392 | json_last_error_msg(), |
||
393 | 3 | $file |
|
394 | ) |
||
395 | ); |
||
396 | } |
||
397 | 1 | } elseif (substr($file, -4) == '.yml') { |
|
398 | try { |
||
399 | 1 | $data = $this->parser->parse($content); |
|
400 | } catch (\Exception $e) { |
||
401 | throw new ParseException( |
||
402 | sprintf( |
||
403 | 'YAML parse error in file %s, message = %s', |
||
404 | $file, |
||
405 | $e->getMessage() |
||
406 | ), |
||
407 | 0, |
||
408 | 1 | $e |
|
409 | ); |
||
410 | } |
||
411 | } else { |
||
412 | throw new UnknownFileTypeException($file); |
||
413 | } |
||
414 | |||
415 | 4 | return $data; |
|
416 | } |
||
417 | |||
418 | /** |
||
419 | * Checks if file exists and return qualified fileName location |
||
420 | * |
||
421 | * @param Document $doc Data source for import data |
||
422 | * @param string $originFile Original full filename used toimport |
||
423 | * @return bool|mixed |
||
424 | */ |
||
425 | private function validateUploadFile(Document $doc, $originFile) |
||
442 | } |
||
443 |
Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.
Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..