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\Exception\ManifestException; |
13
|
|
|
use TheAentMachine\Exception\MissingEnvironmentVariableException; |
14
|
|
|
use TheAentMachine\Aenthill\Manifest; |
15
|
|
|
use TheAentMachine\Aenthill\Metadata; |
16
|
|
|
use TheAentMachine\Registry\RegistryClient; |
17
|
|
|
use TheAentMachine\Registry\TagsAnalyzer; |
18
|
|
|
|
19
|
|
|
/** |
20
|
|
|
* A helper class for the most common questions asked in the console. |
21
|
|
|
*/ |
22
|
|
|
class AentHelper |
23
|
|
|
{ |
24
|
|
|
/** @var InputInterface */ |
25
|
|
|
private $input; |
26
|
|
|
|
27
|
|
|
/** @var OutputInterface */ |
28
|
|
|
private $output; |
29
|
|
|
|
30
|
|
|
/** @var QuestionHelper */ |
31
|
|
|
private $questionHelper; |
32
|
|
|
|
33
|
|
|
/** @var FormatterHelper */ |
34
|
|
|
private $formatterHelper; |
35
|
|
|
|
36
|
|
|
public function __construct(InputInterface $input, OutputInterface $output, QuestionHelper $questionHelper, FormatterHelper $formatterHelper) |
37
|
|
|
{ |
38
|
|
|
$this->input = $input; |
39
|
|
|
$this->output = $output; |
40
|
|
|
$this->questionHelper = $questionHelper; |
41
|
|
|
$this->formatterHelper = $formatterHelper; |
42
|
|
|
} |
43
|
|
|
|
44
|
|
|
private function registerStyle(): void |
45
|
|
|
{ |
46
|
|
|
$outputStyle = new OutputFormatterStyle('black', 'cyan', ['bold']); |
47
|
|
|
$this->output->getFormatter()->setStyle('title', $outputStyle); |
48
|
|
|
} |
49
|
|
|
|
50
|
|
|
/** |
51
|
|
|
* Displays text in a big block |
52
|
|
|
*/ |
53
|
|
|
public function title(string $title): void |
54
|
|
|
{ |
55
|
|
|
$this->registerStyle(); |
56
|
|
|
$this->output->writeln($this->formatterHelper->formatBlock($title, 'title', true)); |
57
|
|
|
} |
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* Displays text in a small block |
61
|
|
|
*/ |
62
|
|
|
public function subTitle(string $title): void |
63
|
|
|
{ |
64
|
|
|
$this->registerStyle(); |
65
|
|
|
$this->output->writeln($this->formatterHelper->formatBlock($title, 'title', false)); |
66
|
|
|
} |
67
|
|
|
|
68
|
|
|
public function askForServiceName(string $serviceName, string $applicationName = ''): string |
69
|
|
|
{ |
70
|
|
|
$answer = $this->question("$applicationName service name") |
71
|
|
|
->setDefault($serviceName) |
72
|
|
|
->compulsory() |
73
|
|
|
->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.') |
74
|
|
|
->setValidator(function (string $value) { |
75
|
|
|
$value = trim($value); |
76
|
|
|
if (!\preg_match('/^[a-zA-Z0-9_.-]+$/', $value)) { |
77
|
|
|
throw new \InvalidArgumentException('Invalid service name "' . $value . '". Service names can contain alphanumeric characters, and "_", ".", "-".'); |
78
|
|
|
} |
79
|
|
|
return $value; |
80
|
|
|
}) |
81
|
|
|
->ask(); |
82
|
|
|
|
83
|
|
|
$this->output->writeln("<info>Service name: $answer</info>"); |
84
|
|
|
$this->spacer(); |
85
|
|
|
|
86
|
|
|
return $answer; |
87
|
|
|
} |
88
|
|
|
|
89
|
|
|
public function spacer(): void |
90
|
|
|
{ |
91
|
|
|
$this->output->writeln(''); |
92
|
|
|
} |
93
|
|
|
|
94
|
|
|
public function askForTag(string $dockerHubImage, string $applicationName = ''): string |
95
|
|
|
{ |
96
|
|
|
$registryClient = new RegistryClient(); |
97
|
|
|
$availableVersions = $registryClient->getImageTagsOnDockerHub($dockerHubImage); |
98
|
|
|
|
99
|
|
|
$tagsAnalyzer = new TagsAnalyzer(); |
100
|
|
|
$proposedTags = $tagsAnalyzer->filterBestTags($availableVersions); |
101
|
|
|
$default = $proposedTags[0] ?? null; |
102
|
|
|
$this->output->writeln("Please choose your $applicationName version."); |
103
|
|
|
if (!empty($proposedTags)) { |
104
|
|
|
$this->output->writeln('Possible values include: <info>' . \implode('</info>, <info>', $proposedTags) . '</info>'); |
105
|
|
|
} |
106
|
|
|
$this->output->writeln('Enter "v" to view all available versions, "?" for help'); |
107
|
|
|
$question = new SymfonyQuestion( |
108
|
|
|
"Select your $applicationName version [$default]: ", |
109
|
|
|
$default |
110
|
|
|
); |
111
|
|
|
$question->setAutocompleterValues($availableVersions); |
112
|
|
|
$question->setValidator(function (string $value) use ($availableVersions, $dockerHubImage) { |
113
|
|
|
$value = trim($value); |
114
|
|
|
|
115
|
|
|
if ($value === 'v') { |
116
|
|
|
$this->output->writeln('Available versions: <info>' . \implode('</info>, <info>', $availableVersions) . '</info>'); |
117
|
|
|
return 'v'; |
118
|
|
|
} |
119
|
|
|
|
120
|
|
|
if ($value === '?') { |
121
|
|
|
$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."); |
122
|
|
|
return '?'; |
123
|
|
|
} |
124
|
|
|
|
125
|
|
|
if (!\in_array($value, $availableVersions)) { |
126
|
|
|
throw new \InvalidArgumentException("Version '$value' is invalid."); |
127
|
|
|
} |
128
|
|
|
|
129
|
|
|
return $value; |
130
|
|
|
}); |
131
|
|
|
do { |
132
|
|
|
$version = $this->questionHelper->ask($this->input, $this->output, $question); |
133
|
|
|
} while ($version === 'v' || $version === '?'); |
134
|
|
|
|
135
|
|
|
$this->output->writeln("<info>Selected version: $version</info>"); |
136
|
|
|
$this->spacer(); |
137
|
|
|
|
138
|
|
|
return $version; |
|
|
|
|
139
|
|
|
} |
140
|
|
|
|
141
|
|
|
public function question(string $question): Question |
142
|
|
|
{ |
143
|
|
|
return new Question($this->questionHelper, $this->input, $this->output, $question); |
144
|
|
|
} |
145
|
|
|
|
146
|
|
|
/** |
147
|
|
|
* @param string[] $choices |
148
|
|
|
* @return ChoiceQuestion |
149
|
|
|
*/ |
150
|
|
|
public function choiceQuestion(string $question, array $choices): ChoiceQuestion |
151
|
|
|
{ |
152
|
|
|
return new ChoiceQuestion($this->questionHelper, $this->input, $this->output, $question, $choices); |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
public function setEnvType(): string |
156
|
|
|
{ |
157
|
|
|
$envType = $this->choiceQuestion('Select your environment type', [Metadata::ENV_TYPE_DEV, Metadata::ENV_TYPE_TEST, Metadata::ENV_TYPE_PROD]) |
158
|
|
|
->askSingleChoiceQuestion(); |
159
|
|
|
$this->output->writeln("<info>Selected environment type: $envType</info>"); |
160
|
|
|
$this->spacer(); |
161
|
|
|
|
162
|
|
|
Manifest::addMetadata(Metadata::ENV_TYPE_KEY, $envType); |
163
|
|
|
return $envType; |
164
|
|
|
} |
165
|
|
|
|
166
|
|
|
/** |
167
|
|
|
* @return string |
168
|
|
|
* @throws MissingEnvironmentVariableException |
169
|
|
|
* @throws ManifestException |
170
|
|
|
*/ |
171
|
|
|
public function registerCI(): string |
172
|
|
|
{ |
173
|
|
|
$ci = $this->choiceQuestion('Select your CI/CD:', ['gitlab-ci', 'travis-ci', 'circle-ci']) |
174
|
|
|
->askSingleChoiceQuestion(); |
175
|
|
|
$this->output->writeln("<info>Your CI: $ci</info>"); |
176
|
|
|
$this->spacer(); |
177
|
|
|
|
178
|
|
|
Manifest::addDependency("theaentmachine/aent-$ci", Metadata::CI_KEY, [ |
179
|
|
|
Metadata::ENV_NAME_KEY => Manifest::getMetadata(Metadata::ENV_NAME_KEY), |
180
|
|
|
Metadata::ENV_TYPE_KEY => Manifest::getMetadata(Metadata::ENV_TYPE_KEY) |
181
|
|
|
]); |
182
|
|
|
|
183
|
|
|
return Manifest::getDependency(Metadata::CI_KEY); |
184
|
|
|
} |
185
|
|
|
|
186
|
|
|
/** |
187
|
|
|
* @return string |
188
|
|
|
* @throws MissingEnvironmentVariableException |
189
|
|
|
* @throws ManifestException |
190
|
|
|
*/ |
191
|
|
|
public function registerReverseProxy(): string |
192
|
|
|
{ |
193
|
|
|
$reverseProxy = $this->choiceQuestion('Select your reverse proxy:', ['traefik', 'nginx', 'ingress']) |
194
|
|
|
->askSingleChoiceQuestion(); |
195
|
|
|
$this->output->writeln("<info>Your reverse proxy: $reverseProxy</info>"); |
196
|
|
|
$this->spacer(); |
197
|
|
|
|
198
|
|
|
Manifest::addDependency("theaentmachine/aent-$reverseProxy", Metadata::REVERSE_PROXY_KEY, [ |
199
|
|
|
Metadata::ENV_NAME_KEY => Manifest::getMetadata(Metadata::ENV_NAME_KEY), |
200
|
|
|
Metadata::ENV_TYPE_KEY => Manifest::getMetadata(Metadata::ENV_TYPE_KEY) |
201
|
|
|
]); |
202
|
|
|
|
203
|
|
|
return Manifest::getDependency(Metadata::REVERSE_PROXY_KEY); |
204
|
|
|
} |
205
|
|
|
} |
206
|
|
|
|