Completed
Pull Request — master (#4)
by Andreas
02:50
created

getInstallationStateAction()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 15
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 15
rs 9.4285
cc 1
eloc 8
nc 1
nop 0
1
<?php
2
3
/**
4
 * This file is part of tenside/core-bundle.
5
 *
6
 * (c) Christian Schiffler <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 *
11
 * This project is provided in good faith and hope to be usable by anyone.
12
 *
13
 * @package    tenside/core-bundle
14
 * @author     Christian Schiffler <[email protected]>
15
 * @copyright  2015 Christian Schiffler <[email protected]>
16
 * @license    https://github.com/tenside/core-bundle/blob/master/LICENSE MIT
17
 * @link       https://github.com/tenside/core-bundle
18
 * @filesource
19
 */
20
21
namespace Tenside\CoreBundle\Controller;
22
23
use Composer\Util\RemoteFilesystem;
24
use Nelmio\ApiDocBundle\Annotation\ApiDoc;
25
use Symfony\Component\Filesystem\Filesystem;
26
use Symfony\Component\HttpFoundation\JsonResponse;
27
use Symfony\Component\HttpFoundation\Request;
28
use Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException;
29
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
30
use Tenside\CoreBundle\Security\UserInformation;
31
use Tenside\CoreBundle\Security\UserInformationInterface;
32
use Tenside\Core\Task\Composer\InstallTask;
33
use Tenside\Core\Util\JsonArray;
34
use Tenside\CoreBundle\Annotation\ApiDescription;
35
36
/**
37
 * Controller for manipulating the composer.json file.
38
 */
39
class InstallProjectController extends AbstractController
40
{
41
    /**
42
     * Configure tenside.
43
     *
44
     * @param Request $request The request.
45
     *
46
     * @return JsonResponse
47
     *
48
     * @throws NotAcceptableHttpException When the configuration is already complete.
49
     *
50
     * @ApiDoc(
51
     *   section="install",
52
     *   statusCodes = {
53
     *     201 = "When everything worked out ok"
54
     *   },
55
     * )
56
     * @ApiDescription(
57
     *   request={
58
     *     "credentials" = {
59
     *       "description" = "The credentials of the admin user.",
60
     *       "children" = {
61
     *         "secret" = {
62
     *           "dataType" = "string",
63
     *           "description" = "The secret to use for encryption and signing.",
64
     *           "required" = true
65
     *         },
66
     *         "username" = {
67
     *           "dataType" = "string",
68
     *           "description" = "The name of the admin user.",
69
     *           "required" = true
70
     *         },
71
     *         "password" = {
72
     *           "dataType" = "string",
73
     *           "description" = "The password to use for the admin.",
74
     *           "required" = false
75
     *         }
76
     *       }
77
     *     },
78
     *     "configuration" = {
79
     *       "description" = "The application configuration.",
80
     *       "children" = {
81
     *         "php_cli" = {
82
     *           "dataType" = "string",
83
     *           "description" = "The PHP interpreter to run on command line."
84
     *         },
85
     *         "php_cli_arguments" = {
86
     *           "dataType" = "string",
87
     *           "description" = "Command line arguments to add."
88
     *         }
89
     *       }
90
     *     }
91
     *   },
92
     *   response={
93
     *     "token" = {
94
     *       "dataType" = "string",
95
     *       "description" = "The API token for the created user"
96
     *     }
97
     *   }
98
     * )
99
     */
100
    public function configureAction(Request $request)
101
    {
102
        if ($this->get('tenside.status')->isTensideConfigured()) {
103
            throw new NotAcceptableHttpException('Already configured.');
104
        }
105
        $inputData = new JsonArray($request->getContent());
0 ignored issues
show
Bug introduced by
It seems like $request->getContent() targeting Symfony\Component\HttpFo...n\Request::getContent() can also be of type resource; however, Tenside\Core\Util\JsonArray::__construct() does only seem to accept string|array, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
106
107
        // Add tenside configuration.
108
        $tensideConfig = $this->get('tenside.config');
109
        $tensideConfig->set('secret', $inputData->get('credentials/secret'));
110
111
        if ($inputData->has('configuration/php_cli')) {
112
            $tensideConfig->set('php_cli', $inputData->get('configuration/php_cli'));
113
        } elseif ('' !== PHP_BINARY && file_exists(PHP_BINARY)) {
114
            $tensideConfig->set('php_cli', PHP_BINARY);
115
        } elseif (file_exists(PHP_BINDIR . '/php')) {
116
            $tensideConfig->set('php_cli', PHP_BINDIR . '/php');
117
        }
118
119
        if ($inputData->has('configuration/php_cli_arguments')) {
120
            $tensideConfig->set('php_cli_arguments', $inputData->get('configuration/php_cli_arguments'));
121
        }
122
123
        // Add the user now.
124
        $user = new UserInformation([
125
            'username' => $inputData->get('credentials/username'),
126
            'acl'      => UserInformationInterface::ROLE_ALL
127
        ]);
128
129
        $user->set(
130
            'password',
131
            $this->get('security.password_encoder')->encodePassword($user, $inputData->get('credentials/password'))
132
        );
133
134
        $user = $this->get('tenside.user_provider')->addUser($user)->refreshUser($user);
135
136
        return new JsonResponse(
137
            [
138
                'status' => 'OK',
139
                'token'  => $this->get('tenside.jwt_authenticator')->getTokenForData($user)
140
            ],
141
            JsonResponse::HTTP_CREATED
142
        );
143
    }
144
145
    /**
146
     * Create a project.
147
     *
148
     * @param Request $request The request.
149
     *
150
     * @return JsonResponse
151
     *
152
     * @throws NotAcceptableHttpException When the installation is already complete.
153
     *
154
     * @ApiDoc(
155
     *   section="install",
156
     *   statusCodes = {
157
     *     201 = "When everything worked out ok"
158
     *   },
159
     * )
160
     * @ApiDescription(
161
     *   request={
162
     *     "project" = {
163
     *       "description" = "The project to install.",
164
     *       "children" = {
165
     *         "name" = {
166
     *           "dataType" = "string",
167
     *           "description" = "The name of the project to install.",
168
     *           "required" = true
169
     *         },
170
     *         "version" = {
171
     *           "dataType" = "string",
172
     *           "description" = "The version of the project to install (optional).",
173
     *           "required" = false
174
     *         }
175
     *       }
176
     *     }
177
     *   },
178
     *   response={
179
     *     "task" = {
180
     *       "dataType" = "string",
181
     *       "description" = "The id of the created install task"
182
     *     }
183
     *   }
184
     * )
185
     */
186
    public function createProjectAction(Request $request)
187
    {
188
        $status = $this->get('tenside.status');
189
        if ($status->isProjectPresent() || $status->isProjectInstalled()) {
190
            throw new NotAcceptableHttpException('Already configured.');
191
        }
192
193
        $this->checkUninstalled();
194
        $header = [];
195
196
        $installDir = $this->get('tenside.home')->homeDir();
197
        $inputData  = new JsonArray($request->getContent());
0 ignored issues
show
Bug introduced by
It seems like $request->getContent() targeting Symfony\Component\HttpFo...n\Request::getContent() can also be of type resource; however, Tenside\Core\Util\JsonArray::__construct() does only seem to accept string|array, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
198
        $taskData   = new JsonArray();
199
200
        $taskData->set(InstallTask::SETTING_DESTINATION_DIR, $installDir);
201
        $taskData->set(InstallTask::SETTING_PACKAGE, $inputData->get('project/name'));
202
        if ($version = $inputData->get('project/version')) {
203
            $taskData->set(InstallTask::SETTING_VERSION, $version);
204
        }
205
206
        $taskId             = $this->getTensideTasks()->queue('install', $taskData);
207
        $header['Location'] = $this->generateUrl(
208
            'task_get',
209
            ['taskId' => $taskId],
210
            UrlGeneratorInterface::ABSOLUTE_URL
211
        );
212
213
        return new JsonResponse(
214
            [
215
                'status' => 'OK',
216
                'task'   => $taskId
217
            ],
218
            JsonResponse::HTTP_CREATED,
219
            $header
220
        );
221
    }
222
223
    /**
224
     * This is a gateway to the self test controller available only at install time.
225
     *
226
     * This is just here as the other route is protected with login.
227
     * This method is inaccessible as soon as the installation is complete.
228
     *
229
     * @return JsonResponse
230
     *
231
     * @ApiDoc(
232
     *   section="install",
233
     *   description="Install time - self test."
234
     * )
235
     * @ApiDescription(
236
     *   response={
237
     *     "results" = {
238
     *       "actualType" = "collection",
239
     *       "subType" = "object",
240
     *       "description" = "The test results.",
241
     *       "children" = {
242
     *         "name" = {
243
     *           "dataType" = "string",
244
     *           "description" = "The name of the test"
245
     *         },
246
     *         "state" = {
247
     *           "dataType" = "choice",
248
     *           "description" = "The test result state.",
249
     *           "format" = "[FAIL|SKIPPED|SUCCESS|WARNING]"
250
     *         },
251
     *         "message" = {
252
     *           "dataType" = "string",
253
     *           "description" = "The detailed message of the test result."
254
     *         },
255
     *         "explain" = {
256
     *           "dataType" = "string",
257
     *           "description" = "Optional description that could hint any problems and/or explain the error further."
258
     *         }
259
     *       }
260
     *     }
261
     *   }
262
     * )
263
     */
264
    public function getSelfTestAction()
265
    {
266
        $this->checkUninstalled();
267
268
        return $this->forward('TensideCoreBundle:SelfTest:getAllTests');
269
    }
270
271
    /**
272
     * Install time gateway to the auto config.
273
     *
274
     * This is just here as the other route is protected with login.
275
     * This method is inaccessible as soon as the installation is complete.
276
     *
277
     * @return JsonResponse
278
     *
279
     * @ApiDoc(
280
     *   section="install",
281
     *   description="Install time - auto config."
282
     * )
283
     * @ApiDescription(
284
     *   response={
285
     *     "php_cli" = {
286
     *       "dataType" = "string",
287
     *       "description" = "The PHP interpreter to run on command line."
288
     *     },
289
     *     "php_cli_arguments" = {
290
     *       "dataType" = "string",
291
     *       "description" = "Command line arguments to add."
292
     *     }
293
     *   }
294
     * )
295
     */
296
    public function getAutoConfigAction()
297
    {
298
        $this->checkUninstalled();
299
300
        return $this->forward('TensideCoreBundle:SelfTest:getAutoConfig');
301
    }
302
303
    /**
304
     * Retrieve the available versions of a package.
305
     *
306
     * @param string $vendor  The vendor name of the package.
307
     *
308
     * @param string $project The name of the package.
309
     *
310
     * @return JsonResponse
311
     *
312
     * @ApiDoc(
313
     *   section="install",
314
     *   statusCodes = {
315
     *     200 = "When everything worked out ok"
316
     *   }
317
     * )
318
     * @ApiDescription(
319
     *   response={
320
     *     "versions" = {
321
     *       "actualType" = "collection",
322
     *       "subType" = "object",
323
     *       "description" = "The list of versions",
324
     *       "children" = {
325
     *         "name" = {
326
     *           "dataType" = "string",
327
     *           "description" = "The name of the package"
328
     *         },
329
     *         "version" = {
330
     *           "dataType" = "string",
331
     *           "description" = "The version of the package"
332
     *         },
333
     *         "version_normalized" = {
334
     *           "dataType" = "string",
335
     *           "description" = "The normalized version of the package"
336
     *         },
337
     *         "reference" = {
338
     *           "dataType" = "string",
339
     *           "description" = "The optional reference"
340
     *         }
341
     *       }
342
     *     }
343
     *   }
344
     * )
345
     */
346
    public function getProjectVersionsAction($vendor, $project)
347
    {
348
        $this->checkUninstalled();
349
350
        $url     = sprintf('https://packagist.org/packages/%s/%s.json', $vendor, $project);
351
        $rfs     = new RemoteFilesystem($this->getInputOutput());
352
        $results = $rfs->getContents($url, $url);
353
        $data    = new JsonArray($results);
0 ignored issues
show
Bug introduced by
It seems like $results defined by $rfs->getContents($url, $url) on line 352 can also be of type boolean; however, Tenside\Core\Util\JsonArray::__construct() does only seem to accept string|array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
354
355
        $versions = [];
356
357
        foreach ($data->get('package/versions') as $information) {
0 ignored issues
show
Bug introduced by
The expression $data->get('package/versions') of type array|string|integer|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
358
            $version = [
359
                'name'               => $information['name'],
360
                'version'            => $information['version'],
361
                'version_normalized' => $information['version_normalized'],
362
            ];
363
364
            $normalized = $information['version'];
365
            if ('dev-' === substr($normalized, 0, 4)) {
366
                if (isset($information['extra']['branch-alias'][$normalized])) {
367
                    $version['version_normalized'] = $information['extra']['branch-alias'][$normalized];
368
                }
369
            }
370
371
            if (isset($information['source']['reference'])) {
372
                $version['reference'] = $information['source']['reference'];
373
            } elseif (isset($information['dist']['reference'])) {
374
                $version['reference'] = $information['dist']['reference'];
375
            }
376
377
            $versions[] = $version;
378
        }
379
380
        return new JsonResponse(
381
            [
382
                'status' => 'OK',
383
                'versions' => $versions
384
            ]
385
        );
386
    }
387
388
    /**
389
     * Check if installation is new, partial or complete.
390
     *
391
     * @return JsonResponse
392
     *
393
     * @ApiDoc(
394
     *   section="install",
395
     *   description="This method provides information about the installation.",
396
     *   authentication=false,
397
     *   statusCodes = {
398
     *     200 = "When everything worked out ok"
399
     *   }
400
     * )
401
     * @ApiDescription(
402
     *   response={
403
     *     "state" = {
404
     *       "children" = {
405
     *         "tenside_configured" = {
406
     *           "dataType" = "bool",
407
     *           "description" = "Flag if tenside has been completely configured."
408
     *         },
409
     *         "project_created" = {
410
     *           "dataType" = "bool",
411
     *           "description" = "Flag determining if a composer.json is present."
412
     *         },
413
     *         "project_installed" = {
414
     *           "dataType" = "bool",
415
     *           "description" = "Flag determining if the composer project has been installed (vendor present)."
416
     *         }
417
     *       }
418
     *     },
419
     *     "status" = {
420
     *       "dataType" = "string",
421
     *       "description" = "Either OK or ERROR"
422
     *     },
423
     *     "message" = {
424
     *       "dataType" = "string",
425
     *       "description" = "The API error message if any (only present when status is ERROR)"
426
     *     }
427
     *   }
428
     * )
429
     */
430
    public function getInstallationStateAction()
431
    {
432
        $status = $this->get('tenside.status');
433
434
        return new JsonResponse(
435
            [
436
                'state'  => [
437
                    'tenside_configured' => $status->isTensideConfigured(),
438
                    'project_created'    => $status->isProjectPresent(),
439
                    'project_installed'  => $status->isProjectInstalled(),
440
                ],
441
                'status' => 'OK'
442
            ]
443
        );
444
    }
445
446
    /**
447
     * Ensure that we are not installed yet.
448
     *
449
     * @return void
450
     *
451
     * @throws NotAcceptableHttpException When the installation is already complete.
452
     */
453
    private function checkUninstalled()
454
    {
455
        if ($this->get('tenside.status')->isComplete()) {
456
            throw new NotAcceptableHttpException('Already installed in ' . $this->get('tenside.home')->homeDir());
457
        }
458
    }
459
460
    /**
461
     * Run the given task and return a response when an error occurred or null if it worked out.
462
     *
463
     * @param string $taskId The task id.
464
     *
465
     * @return void
466
     *
467
     * @throws \RuntimeException When the process could not be started.
468
     */
469
    private function runInstaller($taskId)
0 ignored issues
show
Unused Code introduced by
This method is not used, and could be removed.
Loading history...
470
    {
471
        $runnerResponse = $this->forward('TensideCoreBundle:TaskRunner:run');
472
473
        $runnerStarted = json_decode($runnerResponse->getContent(), true);
474
        if ($runnerStarted['status'] !== 'OK' || $runnerStarted['task'] !== $taskId) {
475
            throw new \RuntimeException('Status was not ok');
476
        }
477
    }
478
}
479