1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
|
4
|
|
|
namespace TheAentMachine\Helper; |
5
|
|
|
|
6
|
|
|
use Symfony\Component\Console\Formatter\OutputFormatterStyle; |
7
|
|
|
use Symfony\Component\Console\Helper\FormatterHelper; |
8
|
|
|
use Symfony\Component\Console\Helper\QuestionHelper; |
9
|
|
|
use Symfony\Component\Console\Input\InputInterface; |
10
|
|
|
use Symfony\Component\Console\Output\OutputInterface; |
11
|
|
|
use Symfony\Component\Console\Question\Question as SymfonyQuestion; |
12
|
|
|
use TheAentMachine\Aenthill\Aenthill; |
13
|
|
|
use TheAentMachine\Exception\ManifestException; |
14
|
|
|
use TheAentMachine\Exception\MissingEnvironmentVariableException; |
15
|
|
|
use TheAentMachine\Aenthill\Manifest; |
16
|
|
|
use TheAentMachine\Aenthill\Metadata; |
17
|
|
|
use TheAentMachine\Registry\RegistryClient; |
18
|
|
|
use TheAentMachine\Registry\TagsAnalyzer; |
19
|
|
|
|
20
|
|
|
/** |
21
|
|
|
* A helper class for the most common questions asked in the console. |
22
|
|
|
*/ |
23
|
|
|
class AentHelper |
24
|
|
|
{ |
25
|
|
|
/** @var InputInterface */ |
26
|
|
|
private $input; |
27
|
|
|
|
28
|
|
|
/** @var OutputInterface */ |
29
|
|
|
private $output; |
30
|
|
|
|
31
|
|
|
/** @var QuestionHelper */ |
32
|
|
|
private $questionHelper; |
33
|
|
|
|
34
|
|
|
/** @var FormatterHelper */ |
35
|
|
|
private $formatterHelper; |
36
|
|
|
|
37
|
|
|
public function __construct(InputInterface $input, OutputInterface $output, QuestionHelper $questionHelper, FormatterHelper $formatterHelper) |
38
|
|
|
{ |
39
|
|
|
$this->input = $input; |
40
|
|
|
$this->output = $output; |
41
|
|
|
$this->questionHelper = $questionHelper; |
42
|
|
|
$this->formatterHelper = $formatterHelper; |
43
|
|
|
} |
44
|
|
|
|
45
|
|
|
private function registerStyle(): void |
46
|
|
|
{ |
47
|
|
|
$outputStyle = new OutputFormatterStyle('black', 'cyan', ['bold']); |
48
|
|
|
$this->output->getFormatter()->setStyle('title', $outputStyle); |
49
|
|
|
} |
50
|
|
|
|
51
|
|
|
/** |
52
|
|
|
* Displays text in a big block |
53
|
|
|
*/ |
54
|
|
|
public function title(string $title): void |
55
|
|
|
{ |
56
|
|
|
$this->registerStyle(); |
57
|
|
|
$this->output->writeln($this->formatterHelper->formatBlock($title, 'title', true)); |
58
|
|
|
} |
59
|
|
|
|
60
|
|
|
/** |
61
|
|
|
* Displays text in a small block |
62
|
|
|
*/ |
63
|
|
|
public function subTitle(string $title): void |
64
|
|
|
{ |
65
|
|
|
$this->registerStyle(); |
66
|
|
|
$this->output->writeln($this->formatterHelper->formatBlock($title, 'title', false)); |
67
|
|
|
} |
68
|
|
|
|
69
|
|
|
public function spacer(): void |
70
|
|
|
{ |
71
|
|
|
$this->output->writeln(''); |
72
|
|
|
} |
73
|
|
|
|
74
|
|
|
public function question(string $question): Question |
75
|
|
|
{ |
76
|
|
|
return new Question($this->questionHelper, $this->input, $this->output, $question); |
77
|
|
|
} |
78
|
|
|
|
79
|
|
|
/** |
80
|
|
|
* @param string[] $choices |
81
|
|
|
* @return ChoiceQuestion |
82
|
|
|
*/ |
83
|
|
|
public function choiceQuestion(string $question, array $choices): ChoiceQuestion |
84
|
|
|
{ |
85
|
|
|
return new ChoiceQuestion($this->questionHelper, $this->input, $this->output, $question, $choices); |
86
|
|
|
} |
87
|
|
|
|
88
|
|
|
public function askForEnvName(): string |
89
|
|
|
{ |
90
|
|
|
$envName = $this->question('Environment name') |
91
|
|
|
->compulsory() |
92
|
|
|
->setValidator(function (string $value) { |
93
|
|
|
$value = trim($value); |
94
|
|
|
if (!\preg_match('/^[a-zA-Z0-9_.-]+$/', $value)) { |
95
|
|
|
throw new \InvalidArgumentException('Invalid environment name "' . $value . '". Environment names can contain alphanumeric characters, and "_", ".", "-".'); |
96
|
|
|
} |
97
|
|
|
return $value; |
98
|
|
|
}) |
99
|
|
|
->ask(); |
100
|
|
|
$this->output->writeln("<info>Environment name: $envName</info>"); |
101
|
|
|
$this->spacer(); |
102
|
|
|
Manifest::addMetadata(Metadata::ENV_NAME_KEY, $envName); |
103
|
|
|
return $envName; |
104
|
|
|
} |
105
|
|
|
|
106
|
|
|
public function askForEnvType(): string |
107
|
|
|
{ |
108
|
|
|
$envType = $this->choiceQuestion('Environment type', [Metadata::ENV_TYPE_DEV, Metadata::ENV_TYPE_TEST, Metadata::ENV_TYPE_PROD]) |
109
|
|
|
->askSingleChoiceQuestion(); |
110
|
|
|
$this->output->writeln("<info>Environment type: $envType</info>"); |
111
|
|
|
$this->spacer(); |
112
|
|
|
Manifest::addMetadata(Metadata::ENV_TYPE_KEY, $envType); |
113
|
|
|
return $envType; |
114
|
|
|
} |
115
|
|
|
|
116
|
|
|
/** |
117
|
|
|
* @return string |
118
|
|
|
* @throws MissingEnvironmentVariableException |
119
|
|
|
* @throws ManifestException |
120
|
|
|
*/ |
121
|
|
|
public function askForCICD(): string |
122
|
|
|
{ |
123
|
|
|
$ci = $this->choiceQuestion('CI/CD', ['gitlab-ci', 'travis-ci', 'circle-ci']) |
124
|
|
|
->askSingleChoiceQuestion(); |
125
|
|
|
$this->output->writeln("<info>CI/CD: $ci</info>"); |
126
|
|
|
$this->spacer(); |
127
|
|
|
Manifest::addDependency("theaentmachine/aent-$ci", Metadata::CI_KEY, [ |
128
|
|
|
Metadata::ENV_NAME_KEY => Manifest::getMetadata(Metadata::ENV_NAME_KEY), |
129
|
|
|
Metadata::ENV_TYPE_KEY => Manifest::getMetadata(Metadata::ENV_TYPE_KEY) |
130
|
|
|
]); |
131
|
|
|
return Manifest::getDependency(Metadata::CI_KEY); |
132
|
|
|
} |
133
|
|
|
|
134
|
|
|
/** |
135
|
|
|
* @return string |
136
|
|
|
* @throws MissingEnvironmentVariableException |
137
|
|
|
* @throws ManifestException |
138
|
|
|
*/ |
139
|
|
|
/*public function registerReverseProxy(): string |
140
|
|
|
{ |
141
|
|
|
$reverseProxy = $this->choiceQuestion('Reverse proxy', ['traefik', 'nginx', 'ingress']) |
142
|
|
|
->askSingleChoiceQuestion(); |
143
|
|
|
$this->output->writeln("<info>Reverse proxy: $reverseProxy</info>"); |
144
|
|
|
$this->spacer(); |
145
|
|
|
Manifest::addDependency("theaentmachine/aent-$reverseProxy", Metadata::REVERSE_PROXY_KEY, [ |
146
|
|
|
Metadata::ENV_NAME_KEY => Manifest::getMetadata(Metadata::ENV_NAME_KEY), |
147
|
|
|
Metadata::ENV_TYPE_KEY => Manifest::getMetadata(Metadata::ENV_TYPE_KEY) |
148
|
|
|
]); |
149
|
|
|
return Manifest::getDependency(Metadata::REVERSE_PROXY_KEY); |
150
|
|
|
}*/ |
151
|
|
|
|
152
|
|
|
public function askForEnvironments(): array |
153
|
|
|
{ |
154
|
|
|
$environments = Aenthill::dispatch('ENVIRONMENT'); |
155
|
|
|
$choosen = $this->choiceQuestion('Environments', array_keys($environments)) |
156
|
|
|
->askMultipleChoiceQuestion(); |
157
|
|
|
$this->output->writeln('<info>Environments: ' . implode($choosen, ', ') . '</info>'); |
|
|
|
|
158
|
|
|
$this->spacer(); |
159
|
|
|
return array_filter($environments, function (string $key) use ($choosen) { |
160
|
|
|
return isset($choosen[$key]); |
161
|
|
|
}, ARRAY_FILTER_USE_KEY); |
162
|
|
|
} |
163
|
|
|
|
164
|
|
|
public function askForTag(string $dockerHubImage, string $applicationName = ''): string |
165
|
|
|
{ |
166
|
|
|
$registryClient = new RegistryClient(); |
167
|
|
|
$availableVersions = $registryClient->getImageTagsOnDockerHub($dockerHubImage); |
168
|
|
|
|
169
|
|
|
$tagsAnalyzer = new TagsAnalyzer(); |
170
|
|
|
$proposedTags = $tagsAnalyzer->filterBestTags($availableVersions); |
171
|
|
|
$default = $proposedTags[0] ?? null; |
172
|
|
|
$this->output->writeln("Please choose your $applicationName version."); |
173
|
|
|
if (!empty($proposedTags)) { |
174
|
|
|
$this->output->writeln('Possible values include: <info>' . \implode('</info>, <info>', $proposedTags) . '</info>'); |
175
|
|
|
} |
176
|
|
|
$this->output->writeln('Enter "v" to view all available versions, "?" for help'); |
177
|
|
|
$question = new SymfonyQuestion( |
178
|
|
|
"Select your $applicationName version [$default]: ", |
179
|
|
|
$default |
180
|
|
|
); |
181
|
|
|
$question->setAutocompleterValues($availableVersions); |
182
|
|
|
$question->setValidator(function (string $value) use ($availableVersions, $dockerHubImage) { |
183
|
|
|
$value = trim($value); |
184
|
|
|
|
185
|
|
|
if ($value === 'v') { |
186
|
|
|
$this->output->writeln('Available versions: <info>' . \implode('</info>, <info>', $availableVersions) . '</info>'); |
187
|
|
|
return 'v'; |
188
|
|
|
} |
189
|
|
|
|
190
|
|
|
if ($value === '?') { |
191
|
|
|
$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."); |
192
|
|
|
return '?'; |
193
|
|
|
} |
194
|
|
|
|
195
|
|
|
if (!\in_array($value, $availableVersions)) { |
196
|
|
|
throw new \InvalidArgumentException("Version '$value' is invalid."); |
197
|
|
|
} |
198
|
|
|
|
199
|
|
|
return $value; |
200
|
|
|
}); |
201
|
|
|
do { |
202
|
|
|
$version = $this->questionHelper->ask($this->input, $this->output, $question); |
203
|
|
|
} while ($version === 'v' || $version === '?'); |
204
|
|
|
|
205
|
|
|
$this->output->writeln("<info>Selected version: $version</info>"); |
206
|
|
|
$this->spacer(); |
207
|
|
|
|
208
|
|
|
return $version; |
|
|
|
|
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
public function askForServiceName(string $serviceName, string $applicationName = ''): string |
212
|
|
|
{ |
213
|
|
|
$answer = $this->question("$applicationName service name") |
214
|
|
|
->setDefault($serviceName) |
215
|
|
|
->compulsory() |
216
|
|
|
->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.') |
217
|
|
|
->setValidator(function (string $value) { |
218
|
|
|
$value = trim($value); |
219
|
|
|
if (!\preg_match('/^[a-zA-Z0-9_.-]+$/', $value)) { |
220
|
|
|
throw new \InvalidArgumentException('Invalid service name "' . $value . '". Service names can contain alphanumeric characters, and "_", ".", "-".'); |
221
|
|
|
} |
222
|
|
|
return $value; |
223
|
|
|
}) |
224
|
|
|
->ask(); |
225
|
|
|
|
226
|
|
|
$this->output->writeln("<info>Service name: $answer</info>"); |
227
|
|
|
$this->spacer(); |
228
|
|
|
|
229
|
|
|
return $answer; |
230
|
|
|
} |
231
|
|
|
} |
232
|
|
|
|
This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.
If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.