Completed
Push — master ( d38950...0c7493 )
by Jindun
11s
created

CommonQuestions::getAvailableVersions()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 4
rs 10
c 0
b 0
f 0
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 Symfony\Component\Console\Question\Question as SymfonyQuestion;
11
use TheAentMachine\Aenthill\Aenthill;
12
use TheAentMachine\Aenthill\CommonAents;
13
use TheAentMachine\Aenthill\CommonDependencies;
14
use TheAentMachine\Aenthill\CommonEvents;
15
use TheAentMachine\Aenthill\CommonMetadata;
16
use TheAentMachine\Aenthill\Manifest;
17
use TheAentMachine\Exception\CommonAentsException;
18
use TheAentMachine\Exception\ManifestException;
19
use TheAentMachine\Registry\RegistryClient;
20
use TheAentMachine\Registry\TagsAnalyzer;
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
    /** @return string[] */
45
    private function getAvailableVersions(string $dockerHubImage): array
46
    {
47
        $registryClient = new RegistryClient();
48
        return $registryClient->getImageTagsOnDockerHub($dockerHubImage);
49
    }
50
51
    public function askForDockerImageTag(string $dockerHubImage, string $applicationName = ''): string
52
    {
53
        $availableVersions = $this->getAvailableVersions($dockerHubImage);
54
55
        $tagsAnalyzer = new TagsAnalyzer();
56
        $proposedTags = $tagsAnalyzer->filterBestTags($availableVersions);
57
        $default = $proposedTags[0] ?? $availableVersions[0];
58
59
        $this->output->writeln("Please choose your $applicationName version.");
60
61
        if (!empty($proposedTags)) {
62
            $this->output->writeln('Possible values include: <info>' . \implode('</info>, <info>', $proposedTags) . '</info>');
63
        }
64
        $this->output->writeln('Enter "v" to view all available versions, "?" for help');
65
66
        $question = new SymfonyQuestion("Select your $applicationName version [$default]: ", $default);
67
68
        $question->setAutocompleterValues($availableVersions);
69
        $question->setValidator(function (string $value) use ($availableVersions, $dockerHubImage) {
70
            $value = trim($value);
71
72
            if ($value === 'v') {
73
                $this->output->writeln('Available versions: <info>' . \implode('</info>, <info>', $availableVersions) . '</info>');
74
                return 'v';
75
            }
76
77
            if ($value === '?') {
78
                $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.");
79
                return '?';
80
            }
81
82
            if (!\in_array($value, $availableVersions, true)) {
83
                throw new \InvalidArgumentException("Version '$value' is invalid.");
84
            }
85
86
            return $value;
87
        });
88
        do {
89
            $version = $this->questionHelper->ask($this->input, $this->output, $question);
90
        } while ($version === 'v' || $version === '?');
91
92
        $this->output->writeln("<info>Selected version: $version</info>");
93
        $this->output->writeln('');
94
95
        return $version;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $version could return the type null|boolean which is incompatible with the type-hinted return string. Consider adding an additional type-check to rule them out.
Loading history...
96
    }
97
98
    public function askForServiceName(string $serviceName, string $applicationName = ''): string
99
    {
100
        return $this->factory->question("$applicationName service name")
101
            ->setDefault($serviceName)
102
            ->compulsory()
103
            ->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.')
104
            ->setValidator(CommonValidators::getAlphaValidator(['_', '.', '-']))
105
            ->ask();
106
    }
107
108
    /**
109
     * Return an array of {"ENV_NAME": "foo", "ENV_TYPE": "bar"}, chosen by the user
110
     * @return mixed[]|null
111
     * @throws CommonAentsException
112
     */
113
    public function askForEnvironments(): ?array
114
    {
115
        $environments = \array_unique(Aenthill::dispatchJson(CommonEvents::ENVIRONMENT_EVENT, []), SORT_REGULAR);
116
117
        if (empty($environments)) {
118
            $this->output->writeln('<error>No environments available.</error>');
119
            $this->output->writeln('Did you forget to install an orchestrator?');
120
            $this->output->writeln('<info>Available orchestrators:</info> ' . implode(', ', CommonAents::getAentsListByDependencyKey(CommonDependencies::ORCHESTRATOR_KEY)));
121
            exit(1);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
122
        }
123
124
        $environmentsStr = [];
125
        foreach ($environments as $env) {
126
            $environmentsStr[] = $env[CommonMetadata::ENV_NAME_KEY] . ' (of type ' . $env[CommonMetadata::ENV_TYPE_KEY] . ')';
127
        }
128
129
        $chosen = $this->factory->choiceQuestion('Environments', $environmentsStr, false)
130
            ->askWithMultipleChoices();
131
132
        $this->output->writeln('<info>Environments: ' . \implode(', ', $chosen) . '</info>');
133
        $this->output->writeln('');
134
135
        $results = [];
136
        foreach ($chosen as $c) {
137
            $results[] = $environments[\array_search($c, $environmentsStr, true)];
138
        }
139
140
        return $results;
141
    }
142
143
    public function askForEnvType(): string
144
    {
145
        $envType = $this->factory->choiceQuestion('Environment type', [CommonMetadata::ENV_TYPE_DEV, CommonMetadata::ENV_TYPE_TEST, CommonMetadata::ENV_TYPE_PROD])
146
            ->ask();
147
        Manifest::addMetadata(CommonMetadata::ENV_TYPE_KEY, $envType);
148
        return $envType;
149
    }
150
151
    public function askForEnvName(?string $envType): string
152
    {
153
        $question = $this->factory->question('Environment name')
154
            ->compulsory()
155
            ->setValidator(CommonValidators::getAlphaValidator(['_', '.', '-']));
156
157
        if (null !== $envType) {
158
            $question->setDefault(\strtolower($envType));
159
        }
160
161
        $envName = $question->ask();
162
        Manifest::addMetadata(CommonMetadata::ENV_NAME_KEY, $envName);
163
        return $envName;
164
    }
165
166
    /**
167
     * @return string
168
     * @throws CommonAentsException
169
     * @throws ManifestException
170
     */
171
    public function askForReverseProxy(): string
172
    {
173
        $available = CommonAents::getAentsListByDependencyKey(CommonDependencies::REVERSE_PROXY_KEY);
174
        $available[] = 'Enter my own image';
175
        $image = $this->factory->choiceQuestion('Reverse proxy', $available)
176
            ->setDefault($available[0])
177
            ->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.')
178
            ->ask();
179
180
        $version = null;
181
        if ($image === 'Enter my own image') {
182
            do {
183
                $image = $this->factory->question('Docker image of your reverse proxy (without tag)')
184
                    ->compulsory()
185
                    ->setValidator(CommonValidators::getDockerImageWithoutTagValidator())
186
                    ->ask();
187
                try {
188
                    $version = $this->askForDockerImageTag($image, $image);
189
                } catch (GuzzleException $e) {
190
                    $this->output->writeln("<error>It seems that your image $image does not exist in the docker hub, please try again.</error>");
191
                    $this->output->writeln('');
192
                }
193
            } while ($version === null);
194
        } else {
195
            $availableVersions = $this->getAvailableVersions($image);
196
            if (count($availableVersions) === 1) {
197
                $version = $availableVersions[0];
198
            } else {
199
                $version = $this->askForDockerImageTag($image, $image);
200
            }
201
        }
202
203
        Manifest::addDependency("$image:$version", CommonDependencies::REVERSE_PROXY_KEY, [
204
            CommonMetadata::ENV_NAME_KEY => Manifest::mustGetMetadata(CommonMetadata::ENV_NAME_KEY),
205
            CommonMetadata::ENV_TYPE_KEY => Manifest::mustGetMetadata(CommonMetadata::ENV_TYPE_KEY)
206
        ]);
207
208
        return Manifest::mustGetDependency(CommonDependencies::REVERSE_PROXY_KEY);
209
    }
210
211
    /**
212
     * @return null|string
213
     * @throws CommonAentsException
214
     * @throws ManifestException
215
     */
216
    public function askForCI(): ?string
217
    {
218
        $envType = Manifest::mustGetMetadata(CommonMetadata::ENV_TYPE_KEY);
219
220
        if ($envType === CommonMetadata::ENV_TYPE_DEV) {
221
            return null;
222
        }
223
224
        $available = CommonAents::getAentsListByDependencyKey(CommonDependencies::CI_KEY);
225
        $available[] = 'Enter my own image';
226
227
        $image = $this->factory->choiceQuestion('CI/CD', $available)
228
            ->setDefault($available[0])
229
            ->ask();
230
231
        $version = null;
232
        if ($image === 'Enter my own image') {
233
            do {
234
                $image = $this->factory->question('Docker image of your CI tool (without tag)')
235
                    ->compulsory()
236
                    ->setValidator(CommonValidators::getDockerImageWithoutTagValidator())
237
                    ->ask();
238
                try {
239
                    $version = $this->askForDockerImageTag($image, $image);
240
                } catch (GuzzleException $e) {
241
                    $this->output->writeln("<error>It seems that $image does not exist in the docker hub, please try again.</error>");
242
                    $this->output->writeln('');
243
                }
244
            } while ($version === null);
245
        } else {
246
            $availableVersions = $this->getAvailableVersions($image);
247
            if (count($availableVersions) === 1) {
248
                $version = $availableVersions[0];
249
            } else {
250
                $version = $this->askForDockerImageTag($image, $image);
251
            }
252
        }
253
254
        Manifest::addDependency("$image:$version", CommonDependencies::CI_KEY, [
255
            CommonMetadata::ENV_NAME_KEY => Manifest::mustGetMetadata(CommonMetadata::ENV_NAME_KEY),
256
            CommonMetadata::ENV_TYPE_KEY => Manifest::mustGetMetadata(CommonMetadata::ENV_TYPE_KEY)
257
        ]);
258
259
        return Manifest::mustGetDependency(CommonDependencies::CI_KEY);
260
    }
261
262
    /**
263
     * @return null|string
264
     * @throws CommonAentsException
265
     * @throws ManifestException
266
     */
267
    public function askForImageBuilder(): ?string
268
    {
269
        $envType = Manifest::mustGetMetadata(CommonMetadata::ENV_TYPE_KEY);
270
271
        if ($envType === CommonMetadata::ENV_TYPE_DEV) {
272
            return null;
273
        }
274
275
        $availableImageBuilders = CommonAents::getAentsListByDependencyKey(CommonDependencies::IMAGE_BUILDER_KEY);
276
        $availableImageBuilders[] = 'Enter my own image';
277
278
        $image = $this->factory->choiceQuestion('Image builder', $availableImageBuilders)
279
            ->setDefault($availableImageBuilders[0])
280
            ->setHelpText('An image builder can generate Dockerfiles, which then can be used to build images of your project.')
281
            ->ask();
282
        $version = null;
283
284
        if ($image === 'Enter my own image') {
285
            do {
286
                $image = $this->factory->question('Docker image of your image builder (without tag)')
287
                    ->compulsory()
288
                    ->setValidator(CommonValidators::getDockerImageWithoutTagValidator())
289
                    ->ask();
290
                try {
291
                    $version = $this->askForDockerImageTag($image, $image);
292
                } catch (GuzzleException $e) {
293
                    $this->output->writeln("<error>It seems that $image does not exist in the docker hub, please try again.</error>");
294
                    $this->output->writeln('');
295
                }
296
            } while ($version === null);
297
        } else {
298
            $availableVersions = $this->getAvailableVersions($image);
299
            if (count($availableVersions) === 1) {
300
                $version = $availableVersions[0];
301
            } else {
302
                $version = $this->askForDockerImageTag($image, $image);
303
            }
304
        }
305
306
        Manifest::addDependency("$image:$version", CommonDependencies::IMAGE_BUILDER_KEY, [
307
            CommonMetadata::ENV_NAME_KEY => Manifest::mustGetMetadata(CommonMetadata::ENV_NAME_KEY),
308
            CommonMetadata::ENV_TYPE_KEY => Manifest::mustGetMetadata(CommonMetadata::ENV_TYPE_KEY)
309
        ]);
310
311
        return Manifest::mustGetDependency(CommonDependencies::IMAGE_BUILDER_KEY);
312
    }
313
}
314