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