Completed
Push — master ( 3fabf9...4f518b )
by Christian
08:32
created

InstallProjectController::runInstaller()   A

Complexity

Conditions 3
Paths 2

Size

Total Lines 9
Code Lines 5

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 9
rs 9.6666
cc 3
eloc 5
nc 2
nop 1
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\HttpFoundation\JsonResponse;
26
use Symfony\Component\HttpFoundation\Request;
27
use Symfony\Component\HttpKernel\Exception\NotAcceptableHttpException;
28
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;
29
use Tenside\CoreBundle\Security\UserInformation;
30
use Tenside\CoreBundle\Security\UserInformationInterface;
31
use Tenside\Core\Task\Composer\InstallTask;
32
use Tenside\Core\Util\JsonArray;
33
use Tenside\CoreBundle\Annotation\ApiDescription;
34
35
/**
36
 * Controller for manipulating the composer.json file.
37
 */
38
class InstallProjectController extends AbstractController
39
{
40
    /**
41
     * Configure tenside.
42
     *
43
     * @param Request $request The request.
44
     *
45
     * @return JsonResponse
46
     *
47
     * @throws NotAcceptableHttpException When the configuration is already complete.
48
     *
49
     * @ApiDoc(
50
     *   section="install",
51
     *   statusCodes = {
52
     *     201 = "When everything worked out ok"
53
     *   },
54
     * )
55
     * @ApiDescription(
56
     *   request={
57
     *     "credentials" = {
58
     *       "description" = "The credentials of the admin user.",
59
     *       "children" = {
60
     *         "secret" = {
61
     *           "dataType" = "string",
62
     *           "description" = "The secret to use for encryption and signing.",
63
     *           "required" = true
64
     *         },
65
     *         "username" = {
66
     *           "dataType" = "string",
67
     *           "description" = "The name of the admin user.",
68
     *           "required" = true
69
     *         },
70
     *         "password" = {
71
     *           "dataType" = "string",
72
     *           "description" = "The password to use for the admin.",
73
     *           "required" = false
74
     *         }
75
     *       }
76
     *     },
77
     *     "configuration" = {
78
     *       "description" = "The application configuration.",
79
     *       "children" = {
80
     *         "php_cli" = {
81
     *           "dataType" = "string",
82
     *           "description" = "The PHP interpreter to run on command line."
83
     *         },
84
     *         "php_cli_arguments" = {
85
     *           "dataType" = "string",
86
     *           "description" = "Command line arguments to add."
87
     *         }
88
     *       }
89
     *     }
90
     *   },
91
     *   response={
92
     *     "token" = {
93
     *       "dataType" = "string",
94
     *       "description" = "The API token for the created user"
95
     *     }
96
     *   }
97
     * )
98
     */
99
    public function configureAction(Request $request)
100
    {
101
        if ($this->get('tenside.status')->isTensideConfigured()) {
102
            throw new NotAcceptableHttpException('Already configured.');
103
        }
104
        $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...
105
106
        // Add tenside configuration.
107
        $tensideConfig = $this->get('tenside.config');
108
        $tensideConfig->set('secret', $inputData->get('credentials/secret'));
109
110
        if ($inputData->has('configuration/php_cli')) {
111
            $tensideConfig->set('php_cli', $inputData->get('configuration/php_cli'));
112
        }
113
114
        if ($inputData->has('configuration/php_cli_arguments')) {
115
            $tensideConfig->set('php_cli_arguments', $inputData->get('configuration/php_cli_arguments'));
116
        }
117
118
        // Add the user now.
119
        $user = new UserInformation([
120
            'username' => $inputData->get('credentials/username'),
121
            'acl'      => UserInformationInterface::ROLE_ALL
122
        ]);
123
124
        $user->set(
125
            'password',
126
            $this->get('security.password_encoder')->encodePassword($user, $inputData->get('credentials/password'))
127
        );
128
129
        $user = $this->get('tenside.user_provider')->addUser($user)->refreshUser($user);
130
131
        return new JsonResponse(
132
            [
133
                'status' => 'OK',
134
                'token'  => $this->get('tenside.jwt_authenticator')->getTokenForData($user)
135
            ],
136
            JsonResponse::HTTP_CREATED
137
        );
138
    }
139
140
    /**
141
     * Create a project.
142
     *
143
     * @param Request $request The request.
144
     *
145
     * @return JsonResponse
146
     *
147
     * @throws NotAcceptableHttpException When the installation is already complete.
148
     *
149
     * @ApiDoc(
150
     *   section="install",
151
     *   statusCodes = {
152
     *     201 = "When everything worked out ok"
153
     *   },
154
     * )
155
     * @ApiDescription(
156
     *   request={
157
     *     "project" = {
158
     *       "description" = "The project to install.",
159
     *       "children" = {
160
     *         "name" = {
161
     *           "dataType" = "string",
162
     *           "description" = "The name of the project to install.",
163
     *           "required" = true
164
     *         },
165
     *         "version" = {
166
     *           "dataType" = "string",
167
     *           "description" = "The version of the project to install (optional).",
168
     *           "required" = false
169
     *         }
170
     *       }
171
     *     }
172
     *   },
173
     *   response={
174
     *     "task" = {
175
     *       "dataType" = "string",
176
     *       "description" = "The id of the created install task"
177
     *     }
178
     *   }
179
     * )
180
     */
181
    public function createProjectAction(Request $request)
182
    {
183
        $status = $this->get('tenside.status');
184
        if ($status->isProjectPresent() || $status->isProjectInstalled()) {
185
            throw new NotAcceptableHttpException('Already configured.');
186
        }
187
188
        $this->checkUninstalled();
189
        $result = [];
190
        $header = [];
191
192
        $installDir = $this->get('tenside.home')->homeDir();
193
        $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...
194
        $taskData   = new JsonArray();
195
196
        $taskData->set(InstallTask::SETTING_DESTINATION_DIR, $installDir);
197
        $taskData->set(InstallTask::SETTING_PACKAGE, $inputData->get('project/name'));
198
        if ($version = $inputData->get('project/version')) {
199
            $taskData->set(InstallTask::SETTING_VERSION, $version);
200
        }
201
202
        $taskId             = $this->getTensideTasks()->queue('install', $taskData);
203
        $result['task']     = $taskId;
204
        $header['Location'] = $this->generateUrl(
205
            'task_get',
206
            ['taskId' => $taskId],
207
            UrlGeneratorInterface::ABSOLUTE_URL
208
        );
209
210
        return new JsonResponse(
211
            [
212
                'status' => 'OK',
213
                'task'   => $taskId
214
            ],
215
            JsonResponse::HTTP_CREATED,
216
            $header
217
        );
218
    }
219
220
    /**
221
     * This is a gateway to the self test controller available only at install time.
222
     *
223
     * This is just here as the other route is protected with login.
224
     * This method is inaccessible as soon as the installation is complete.
225
     *
226
     * @return JsonResponse
227
     *
228
     * @ApiDoc(
229
     *   section="install",
230
     *   description="Install time - self test."
231
     * )
232
     * @ApiDescription(
233
     *   response={
234
     *     "results" = {
235
     *       "actualType" = "collection",
236
     *       "subType" = "object",
237
     *       "description" = "The test results.",
238
     *       "children" = {
239
     *         "name" = {
240
     *           "dataType" = "string",
241
     *           "description" = "The name of the test"
242
     *         },
243
     *         "state" = {
244
     *           "dataType" = "choice",
245
     *           "description" = "The test result state.",
246
     *           "format" = "[FAIL|SKIPPED|SUCCESS|WARNING]"
247
     *         },
248
     *         "message" = {
249
     *           "dataType" = "string",
250
     *           "description" = "The detailed message of the test result."
251
     *         },
252
     *         "explain" = {
253
     *           "dataType" = "string",
254
     *           "description" = "Optional description that could hint any problems and/or explain the error further."
255
     *         }
256
     *       }
257
     *     }
258
     *   }
259
     * )
260
     */
261
    public function getSelfTestAction()
262
    {
263
        $this->checkUninstalled();
264
265
        return $this->forward('TensideCoreBundle:SelfTest:getAllTests');
266
    }
267
268
    /**
269
     * Install time gateway to the auto config.
270
     *
271
     * This is just here as the other route is protected with login.
272
     * This method is inaccessible as soon as the installation is complete.
273
     *
274
     * @return JsonResponse
275
     *
276
     * @ApiDoc(
277
     *   section="install",
278
     *   description="Install time - auto config."
279
     * )
280
     * @ApiDescription(
281
     *   response={
282
     *     "php_cli" = {
283
     *       "dataType" = "string",
284
     *       "description" = "The PHP interpreter to run on command line."
285
     *     },
286
     *     "php_cli_arguments" = {
287
     *       "dataType" = "string",
288
     *       "description" = "Command line arguments to add."
289
     *     }
290
     *   }
291
     * )
292
     */
293
    public function getAutoConfigAction()
294
    {
295
        $this->checkUninstalled();
296
297
        return $this->forward('TensideCoreBundle:SelfTest:getAutoConfig');
298
    }
299
300
    /**
301
     * Retrieve the available versions of a package.
302
     *
303
     * @param string $vendor  The vendor name of the package.
304
     *
305
     * @param string $project The name of the package.
306
     *
307
     * @return JsonResponse
308
     *
309
     * @ApiDoc(
310
     *   section="install",
311
     *   statusCodes = {
312
     *     200 = "When everything worked out ok"
313
     *   }
314
     * )
315
     * @ApiDescription(
316
     *   response={
317
     *     "versions" = {
318
     *       "actualType" = "collection",
319
     *       "subType" = "object",
320
     *       "description" = "The list of versions",
321
     *       "children" = {
322
     *         "name" = {
323
     *           "dataType" = "string",
324
     *           "description" = "The name of the package"
325
     *         },
326
     *         "version" = {
327
     *           "dataType" = "string",
328
     *           "description" = "The version of the package"
329
     *         },
330
     *         "version_normalized" = {
331
     *           "dataType" = "string",
332
     *           "description" = "The normalized version of the package"
333
     *         },
334
     *         "reference" = {
335
     *           "dataType" = "string",
336
     *           "description" = "The optional reference"
337
     *         }
338
     *       }
339
     *     }
340
     *   }
341
     * )
342
     */
343
    public function getProjectVersionsAction($vendor, $project)
344
    {
345
        $this->checkUninstalled();
346
347
        $url     = sprintf('https://packagist.org/packages/%s/%s.json', $vendor, $project);
348
        $rfs     = new RemoteFilesystem($this->getInputOutput());
349
        $results = $rfs->getContents($url, $url);
350
        $data    = new JsonArray($results);
0 ignored issues
show
Bug introduced by
It seems like $results defined by $rfs->getContents($url, $url) on line 349 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...
351
352
        $versions = [];
353
354
        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...
355
            $version = [
356
                'name'               => $information['name'],
357
                'version'            => $information['version'],
358
                'version_normalized' => $information['version_normalized'],
359
            ];
360
361
            $normalized = $information['version'];
362
            if ('dev-' === substr($normalized, 0, 4)) {
363
                if (isset($information['extra']['branch-alias'][$normalized])) {
364
                    $version['version_normalized'] = $information['extra']['branch-alias'][$normalized];
365
                }
366
            }
367
368
            if (isset($information['source']['reference'])) {
369
                $version['reference'] = $information['source']['reference'];
370
            } elseif (isset($information['dist']['reference'])) {
371
                $version['reference'] = $information['dist']['reference'];
372
            }
373
374
            $versions[] = $version;
375
        }
376
377
        return new JsonResponse(
378
            [
379
                'status' => 'OK',
380
                'versions' => $versions
381
            ]
382
        );
383
    }
384
385
    /**
386
     * Check if installation is new, partial or complete.
387
     *
388
     * @return JsonResponse
389
     *
390
     * @ApiDoc(
391
     *   section="install",
392
     *   description="This method provides information about the installation.",
393
     *   authentication=false,
394
     *   statusCodes = {
395
     *     200 = "When everything worked out ok"
396
     *   }
397
     * )
398
     * @ApiDescription(
399
     *   response={
400
     *     "state" = {
401
     *       "children" = {
402
     *         "tenside_configured" = {
403
     *           "dataType" = "bool",
404
     *           "description" = "Flag if tenside has been completely configured."
405
     *         },
406
     *         "project_created" = {
407
     *           "dataType" = "bool",
408
     *           "description" = "Flag determining if a composer.json is present."
409
     *         },
410
     *         "project_installed" = {
411
     *           "dataType" = "bool",
412
     *           "description" = "Flag determining if the composer project has been installed (vendor present)."
413
     *         }
414
     *       }
415
     *     },
416
     *     "status" = {
417
     *       "dataType" = "string",
418
     *       "description" = "Either OK or ERROR"
419
     *     },
420
     *     "message" = {
421
     *       "dataType" = "string",
422
     *       "description" = "The API error message if any (only present when status is ERROR)"
423
     *     }
424
     *   }
425
     * )
426
     */
427
    public function getInstallationStateAction()
428
    {
429
        $status = $this->get('tenside.status');
430
431
        return new JsonResponse(
432
            [
433
                'state'  => [
434
                    'tenside_configured' => $status->isTensideConfigured(),
435
                    'project_created'    => $status->isProjectPresent(),
436
                    'project_installed'  => $status->isProjectInstalled(),
437
                ],
438
                'status' => 'OK'
439
            ]
440
        );
441
    }
442
443
    /**
444
     * Ensure that we are not installed yet.
445
     *
446
     * @return void
447
     *
448
     * @throws NotAcceptableHttpException When the installation is already complete.
449
     */
450
    private function checkUninstalled()
451
    {
452
        if ($this->get('tenside.status')->isComplete()) {
453
            throw new NotAcceptableHttpException('Already installed in ' . $this->get('tenside.home')->homeDir());
454
        }
455
    }
456
}
457