1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
|
4
|
|
|
namespace TheAentMachine\Question; |
5
|
|
|
|
6
|
|
|
use GuzzleHttp\Exception\GuzzleException; |
7
|
|
|
use Symfony\Component\Console\Helper\QuestionHelper; |
8
|
|
|
use Symfony\Component\Console\Input\InputInterface; |
9
|
|
|
use Symfony\Component\Console\Output\OutputInterface; |
10
|
|
|
use TheAentMachine\Aenthill\Aenthill; |
11
|
|
|
use TheAentMachine\Aenthill\CommonAents; |
12
|
|
|
use TheAentMachine\Aenthill\CommonDependencies; |
13
|
|
|
use TheAentMachine\Aenthill\CommonEvents; |
14
|
|
|
use TheAentMachine\Aenthill\CommonMetadata; |
15
|
|
|
use TheAentMachine\Aenthill\Manifest; |
16
|
|
|
use TheAentMachine\Exception\CommonAentsException; |
17
|
|
|
use TheAentMachine\Exception\ManifestException; |
18
|
|
|
use TheAentMachine\Registry\RegistryClient; |
19
|
|
|
use TheAentMachine\Registry\TagsAnalyzer; |
20
|
|
|
use Symfony\Component\Console\Question\Question as SymfonyQuestion; |
21
|
|
|
|
22
|
|
|
final class CommonQuestions |
23
|
|
|
{ |
24
|
|
|
/** @var InputInterface */ |
25
|
|
|
private $input; |
26
|
|
|
|
27
|
|
|
/** @var OutputInterface */ |
28
|
|
|
private $output; |
29
|
|
|
|
30
|
|
|
/** @var QuestionHelper */ |
31
|
|
|
private $questionHelper; |
32
|
|
|
|
33
|
|
|
/** @var QuestionFactory */ |
34
|
|
|
private $factory; |
35
|
|
|
|
36
|
|
|
public function __construct(InputInterface $input, OutputInterface $output, QuestionHelper $questionHelper) |
37
|
|
|
{ |
38
|
|
|
$this->input = $input; |
39
|
|
|
$this->output = $output; |
40
|
|
|
$this->questionHelper = $questionHelper; |
41
|
|
|
$this->factory = new QuestionFactory($input, $output, $questionHelper); |
42
|
|
|
} |
43
|
|
|
|
44
|
|
|
public function askForDockerImageTag(string $dockerHubImage, string $applicationName = ''): string |
45
|
|
|
{ |
46
|
|
|
$registryClient = new RegistryClient(); |
47
|
|
|
$availableVersions = $registryClient->getImageTagsOnDockerHub($dockerHubImage); |
48
|
|
|
|
49
|
|
|
$tagsAnalyzer = new TagsAnalyzer(); |
50
|
|
|
$proposedTags = $tagsAnalyzer->filterBestTags($availableVersions); |
51
|
|
|
$default = $proposedTags[0] ?? $availableVersions[0]; |
52
|
|
|
|
53
|
|
|
$this->output->writeln("Please choose your $applicationName version."); |
54
|
|
|
|
55
|
|
|
if (!empty($proposedTags)) { |
56
|
|
|
$this->output->writeln('Possible values include: <info>' . \implode('</info>, <info>', $proposedTags) . '</info>'); |
57
|
|
|
} |
58
|
|
|
$this->output->writeln('Enter "v" to view all available versions, "?" for help'); |
59
|
|
|
|
60
|
|
|
$question = new SymfonyQuestion("Select your $applicationName version [$default]: ", $default); |
61
|
|
|
|
62
|
|
|
$question->setAutocompleterValues($availableVersions); |
63
|
|
|
$question->setValidator(function (string $value) use ($availableVersions, $dockerHubImage) { |
64
|
|
|
$value = trim($value); |
65
|
|
|
|
66
|
|
|
if ($value === 'v') { |
67
|
|
|
$this->output->writeln('Available versions: <info>' . \implode('</info>, <info>', $availableVersions) . '</info>'); |
68
|
|
|
return 'v'; |
69
|
|
|
} |
70
|
|
|
|
71
|
|
|
if ($value === '?') { |
72
|
|
|
$this->output->writeln("Please choose the version (i.e. the tag) of the $dockerHubImage image you are about to install. Press 'v' to view the list of available tags."); |
73
|
|
|
return '?'; |
74
|
|
|
} |
75
|
|
|
|
76
|
|
|
if (!\in_array($value, $availableVersions, true)) { |
77
|
|
|
throw new \InvalidArgumentException("Version '$value' is invalid."); |
78
|
|
|
} |
79
|
|
|
|
80
|
|
|
return $value; |
81
|
|
|
}); |
82
|
|
|
do { |
83
|
|
|
$version = $this->questionHelper->ask($this->input, $this->output, $question); |
84
|
|
|
} while ($version === 'v' || $version === '?'); |
85
|
|
|
|
86
|
|
|
$this->output->writeln("<info>Selected version: $version</info>"); |
87
|
|
|
$this->output->writeln(''); |
88
|
|
|
|
89
|
|
|
return $version; |
|
|
|
|
90
|
|
|
} |
91
|
|
|
|
92
|
|
|
public function askForServiceName(string $serviceName, string $applicationName = ''): string |
93
|
|
|
{ |
94
|
|
|
return $this->factory->question("$applicationName service name") |
95
|
|
|
->setDefault($serviceName) |
96
|
|
|
->compulsory() |
97
|
|
|
->setHelpText('The "service name" is used as an identifier for the container you are creating. It is also bound in Docker internal network DNS and can be used from other containers to reference your container.') |
98
|
|
|
->setValidator(CommonValidators::getAlphaValidator(['_', '.', '-'])) |
99
|
|
|
->ask(); |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
/** |
103
|
|
|
* Return an array of {"ENV_NAME": "foo", "ENV_TYPE": "bar"}, chosen by the user |
104
|
|
|
* @return mixed[]|null |
105
|
|
|
* @throws CommonAentsException |
106
|
|
|
*/ |
107
|
|
|
public function askForEnvironments(): ?array |
108
|
|
|
{ |
109
|
|
|
$environments = \array_unique(Aenthill::dispatchJson(CommonEvents::ENVIRONMENT_EVENT, []), SORT_REGULAR); |
110
|
|
|
|
111
|
|
|
if (empty($environments)) { |
112
|
|
|
$this->output->writeln('<error>No environments available.</error>'); |
113
|
|
|
$this->output->writeln('Did you forget to install an orchestrator?'); |
114
|
|
|
$this->output->writeln('<info>Available orchestrators:</info> ' . implode(', ', CommonAents::getAentsListByDependencyKey(CommonDependencies::ORCHESTRATOR_KEY))); |
115
|
|
|
exit(1); |
|
|
|
|
116
|
|
|
} |
117
|
|
|
|
118
|
|
|
$environmentsStr = []; |
119
|
|
|
foreach ($environments as $env) { |
120
|
|
|
$environmentsStr[] = $env[CommonMetadata::ENV_NAME_KEY] . ' (of type '. $env[CommonMetadata::ENV_TYPE_KEY] .')'; |
121
|
|
|
} |
122
|
|
|
|
123
|
|
|
$chosen = $this->factory->choiceQuestion('Environments', $environmentsStr, false) |
124
|
|
|
->askWithMultipleChoices(); |
125
|
|
|
|
126
|
|
|
$this->output->writeln('<info>Environments: ' . \implode(', ', $chosen) . '</info>'); |
127
|
|
|
$this->output->writeln(''); |
128
|
|
|
|
129
|
|
|
$results = []; |
130
|
|
|
foreach ($chosen as $c) { |
131
|
|
|
$results[] = $environments[\array_search($c, $environmentsStr, true)]; |
132
|
|
|
} |
133
|
|
|
|
134
|
|
|
return $results; |
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
public function askForEnvType(): string |
138
|
|
|
{ |
139
|
|
|
$envType = $this->factory->choiceQuestion('Environment type', [CommonMetadata::ENV_TYPE_DEV, CommonMetadata::ENV_TYPE_TEST, CommonMetadata::ENV_TYPE_PROD]) |
140
|
|
|
->ask(); |
141
|
|
|
Manifest::addMetadata(CommonMetadata::ENV_TYPE_KEY, $envType); |
142
|
|
|
return $envType; |
143
|
|
|
} |
144
|
|
|
|
145
|
|
|
public function askForEnvName(?string $envType): string |
146
|
|
|
{ |
147
|
|
|
$question = $this->factory->question('Environment name') |
148
|
|
|
->compulsory() |
149
|
|
|
->setValidator(CommonValidators::getAlphaValidator(['_', '.', '-'])); |
150
|
|
|
|
151
|
|
|
if (null !== $envType) { |
152
|
|
|
$question->setDefault(\strtolower($envType)); |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
$envName = $question->ask(); |
156
|
|
|
Manifest::addMetadata(CommonMetadata::ENV_NAME_KEY, $envName); |
157
|
|
|
return $envName; |
158
|
|
|
} |
159
|
|
|
|
160
|
|
|
/** |
161
|
|
|
* @return string |
162
|
|
|
* @throws CommonAentsException |
163
|
|
|
* @throws ManifestException |
164
|
|
|
*/ |
165
|
|
|
public function askForReverseProxy(): string |
166
|
|
|
{ |
167
|
|
|
$available = CommonAents::getAentsListByDependencyKey(CommonDependencies::REVERSE_PROXY_KEY); |
168
|
|
|
$available[] = 'other'; |
169
|
|
|
$image = $this->factory->choiceQuestion('Reverse proxy', $available) |
170
|
|
|
->setDefault($available[0]) |
171
|
|
|
->setHelpText('A reverse proxy is useful for public facing services with a domain name. It handles the incoming requests and forward them to the correct container.') |
172
|
|
|
->ask(); |
173
|
|
|
|
174
|
|
|
$version = null; |
175
|
|
|
if ($image === 'other') { |
176
|
|
|
do { |
177
|
|
|
$image = $this->factory->question('Docker image of your reverse proxy (without tag)') |
178
|
|
|
->compulsory() |
179
|
|
|
->setValidator(CommonValidators::getDockerImageWithoutTagValidator()) |
180
|
|
|
->ask(); |
181
|
|
|
try { |
182
|
|
|
$version = $this->askForDockerImageTag($image, $image); |
183
|
|
|
} catch (GuzzleException $e) { |
184
|
|
|
$this->output->writeln("<error>It seems that your image $image does not exist in the docker hub, please try again.</error>"); |
185
|
|
|
$this->output->writeln(''); |
186
|
|
|
} |
187
|
|
|
} while ($version === null); |
188
|
|
|
} else { |
189
|
|
|
$version = $this->askForDockerImageTag($image, $image); |
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
Manifest::addDependency("$image:$version", CommonDependencies::REVERSE_PROXY_KEY, [ |
193
|
|
|
CommonMetadata::ENV_NAME_KEY => Manifest::mustGetMetadata(CommonMetadata::ENV_NAME_KEY), |
194
|
|
|
CommonMetadata::ENV_TYPE_KEY => Manifest::mustGetMetadata(CommonMetadata::ENV_TYPE_KEY) |
195
|
|
|
]); |
196
|
|
|
|
197
|
|
|
return Manifest::mustGetDependency(CommonDependencies::REVERSE_PROXY_KEY); |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
/** |
201
|
|
|
* @return null|string |
202
|
|
|
* @throws CommonAentsException |
203
|
|
|
* @throws ManifestException |
204
|
|
|
*/ |
205
|
|
|
public function askForCI(): ?string |
206
|
|
|
{ |
207
|
|
|
$envType = Manifest::mustGetMetadata(CommonMetadata::ENV_TYPE_KEY); |
208
|
|
|
|
209
|
|
|
if ($envType === CommonMetadata::ENV_TYPE_DEV) { |
210
|
|
|
return null; |
211
|
|
|
} |
212
|
|
|
|
213
|
|
|
$available = CommonAents::getAentsListByDependencyKey(CommonDependencies::CI_KEY); |
214
|
|
|
$available[] = 'other'; |
215
|
|
|
$image = $this->factory->choiceQuestion('CI/CD', $available) |
216
|
|
|
->setDefault($available[0]) |
217
|
|
|
->ask(); |
218
|
|
|
|
219
|
|
|
$version = null; |
220
|
|
|
if ($image === 'other') { |
221
|
|
|
do { |
222
|
|
|
$image = $this->factory->question('Docker image of your CI tool (without tag)') |
223
|
|
|
->compulsory() |
224
|
|
|
->setValidator(CommonValidators::getDockerImageWithoutTagValidator()) |
225
|
|
|
->ask(); |
226
|
|
|
try { |
227
|
|
|
$version = $this->askForDockerImageTag($image, $image); |
228
|
|
|
} catch (GuzzleException $e) { |
229
|
|
|
$this->output->writeln("<error>It seems that $image does not exist in the docker hub, please try again.</error>"); |
230
|
|
|
$this->output->writeln(''); |
231
|
|
|
} |
232
|
|
|
} while ($version === null); |
233
|
|
|
} else { |
234
|
|
|
$version = $this->askForDockerImageTag($image, $image); |
235
|
|
|
} |
236
|
|
|
|
237
|
|
|
Manifest::addDependency("$image:$version", CommonDependencies::CI_KEY, [ |
238
|
|
|
CommonMetadata::ENV_NAME_KEY => Manifest::mustGetMetadata(CommonMetadata::ENV_NAME_KEY), |
239
|
|
|
CommonMetadata::ENV_TYPE_KEY => Manifest::mustGetMetadata(CommonMetadata::ENV_TYPE_KEY) |
240
|
|
|
]); |
241
|
|
|
|
242
|
|
|
return Manifest::mustGetDependency(CommonDependencies::CI_KEY); |
243
|
|
|
} |
244
|
|
|
|
245
|
|
|
/** |
246
|
|
|
* @return null|string |
247
|
|
|
* @throws CommonAentsException |
248
|
|
|
* @throws ManifestException |
249
|
|
|
*/ |
250
|
|
|
public function askForImageBuilder(): ?string |
251
|
|
|
{ |
252
|
|
|
$envType = Manifest::mustGetMetadata(CommonMetadata::ENV_TYPE_KEY); |
253
|
|
|
|
254
|
|
|
if ($envType === CommonMetadata::ENV_TYPE_DEV) { |
255
|
|
|
return null; |
256
|
|
|
} |
257
|
|
|
|
258
|
|
|
$available = CommonAents::getAentsListByDependencyKey(CommonDependencies::IMAGE_BUILDER_KEY); |
259
|
|
|
$available[] = 'other'; |
260
|
|
|
$image = $this->factory->choiceQuestion('Image builder', $available) |
261
|
|
|
->setDefault($available[0]) |
262
|
|
|
->setHelpText('An image builder can generate Dockerfiles, which then can be used to build images of your project.') |
263
|
|
|
->ask(); |
264
|
|
|
|
265
|
|
|
$version = null; |
266
|
|
|
if ($image === 'other') { |
267
|
|
|
do { |
268
|
|
|
$image = $this->factory->question('Docker image of your image builder (without tag)') |
269
|
|
|
->compulsory() |
270
|
|
|
->setValidator(CommonValidators::getDockerImageWithoutTagValidator()) |
271
|
|
|
->ask(); |
272
|
|
|
try { |
273
|
|
|
$version = $this->askForDockerImageTag($image, $image); |
274
|
|
|
} catch (GuzzleException $e) { |
275
|
|
|
$this->output->writeln("<error>It seems that $image does not exist in the docker hub, please try again.</error>"); |
276
|
|
|
$this->output->writeln(''); |
277
|
|
|
} |
278
|
|
|
} while ($version === null); |
279
|
|
|
} else { |
280
|
|
|
$version = $this->askForDockerImageTag($image, $image); |
281
|
|
|
} |
282
|
|
|
|
283
|
|
|
Manifest::addDependency("$image:$version", CommonDependencies::IMAGE_BUILDER_KEY, [ |
284
|
|
|
CommonMetadata::ENV_NAME_KEY => Manifest::mustGetMetadata(CommonMetadata::ENV_NAME_KEY), |
285
|
|
|
CommonMetadata::ENV_TYPE_KEY => Manifest::mustGetMetadata(CommonMetadata::ENV_TYPE_KEY) |
286
|
|
|
]); |
287
|
|
|
|
288
|
|
|
return Manifest::mustGetDependency(CommonDependencies::IMAGE_BUILDER_KEY); |
289
|
|
|
} |
290
|
|
|
} |
291
|
|
|
|