Completed
Pull Request — master (#99)
by Jindun
02:35
created

CommonQuestions::askForCI()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 44
Code Lines 31

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 31
nc 5
nop 0
dl 0
loc 44
rs 8.8017
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
    /** @throws GuzzleException */
45
    private function getAvailableVersions(string $dockerHubImage): array
46
    {
47
        $registryClient = new RegistryClient();
48
        return $registryClient->getImageTagsOnDockerHub($dockerHubImage);
49
    }
50
51
    /** @throws GuzzleException */
52
    public function askForDockerImageTag(string $dockerHubImage, string $applicationName = ''): string
53
    {
54
        $availableVersions = $this->getAvailableVersions($dockerHubImage);
55
56
        $tagsAnalyzer = new TagsAnalyzer();
57
        $proposedTags = $tagsAnalyzer->filterBestTags($availableVersions);
58
        $default = $proposedTags[0] ?? $availableVersions[0];
59
60
        $this->output->writeln("Please choose your $applicationName version.");
61
62
        if (!empty($proposedTags)) {
63
            $this->output->writeln('Possible values include: <info>' . \implode('</info>, <info>', $proposedTags) . '</info>');
64
        }
65
        $this->output->writeln('Enter "v" to view all available versions, "?" for help');
66
67
        $question = new SymfonyQuestion("Select your $applicationName version [$default]: ", $default);
68
69
        $question->setAutocompleterValues($availableVersions);
70
        $question->setValidator(function (string $value) use ($availableVersions, $dockerHubImage) {
71
            $value = trim($value);
72
73
            if ($value === 'v') {
74
                $this->output->writeln('Available versions: <info>' . \implode('</info>, <info>', $availableVersions) . '</info>');
75
                return 'v';
76
            }
77
78
            if ($value === '?') {
79
                $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.");
80
                return '?';
81
            }
82
83
            if (!\in_array($value, $availableVersions, true)) {
84
                throw new \InvalidArgumentException("Version '$value' is invalid.");
85
            }
86
87
            return $value;
88
        });
89
        do {
90
            $version = $this->questionHelper->ask($this->input, $this->output, $question);
91
        } while ($version === 'v' || $version === '?');
92
93
        $this->output->writeln("<info>Selected version: $version</info>");
94
        $this->output->writeln('');
95
96
        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...
97
    }
98
99
    public function askForServiceName(string $serviceName, string $applicationName = ''): string
100
    {
101
        return $this->factory->question("$applicationName service name")
102
            ->setDefault($serviceName)
103
            ->compulsory()
104
            ->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.')
105
            ->setValidator(CommonValidators::getAlphaValidator(['_', '.', '-']))
106
            ->ask();
107
    }
108
109
    /**
110
     * Return an array of {"ENV_NAME": "foo", "ENV_TYPE": "bar"}, chosen by the user
111
     * @return mixed[]|null
112
     * @throws CommonAentsException
113
     */
114
    public function askForEnvironments(): ?array
115
    {
116
        $environments = \array_unique(Aenthill::dispatchJson(CommonEvents::ENVIRONMENT_EVENT, []), SORT_REGULAR);
117
118
        if (empty($environments)) {
119
            $this->output->writeln('<error>No environments available.</error>');
120
            $this->output->writeln('Did you forget to install an orchestrator?');
121
            $this->output->writeln('<info>Available orchestrators:</info> ' . implode(', ', CommonAents::getAentsListByDependencyKey(CommonDependencies::ORCHESTRATOR_KEY)));
122
            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...
123
        }
124
125
        $environmentsStr = [];
126
        foreach ($environments as $env) {
127
            $environmentsStr[] = $env[CommonMetadata::ENV_NAME_KEY] . ' (of type ' . $env[CommonMetadata::ENV_TYPE_KEY] . ')';
128
        }
129
130
        $chosen = $this->factory->choiceQuestion('Environments', $environmentsStr, false)
131
            ->askWithMultipleChoices();
132
133
        $this->output->writeln('<info>Environments: ' . \implode(', ', $chosen) . '</info>');
134
        $this->output->writeln('');
135
136
        $results = [];
137
        foreach ($chosen as $c) {
138
            $results[] = $environments[\array_search($c, $environmentsStr, true)];
139
        }
140
141
        return $results;
142
    }
143
144
    public function askForEnvType(): string
145
    {
146
        $envType = $this->factory->choiceQuestion('Environment type', [CommonMetadata::ENV_TYPE_DEV, CommonMetadata::ENV_TYPE_TEST, CommonMetadata::ENV_TYPE_PROD])
147
            ->ask();
148
        Manifest::addMetadata(CommonMetadata::ENV_TYPE_KEY, $envType);
149
        return $envType;
150
    }
151
152
    public function askForEnvName(?string $envType): string
153
    {
154
        $question = $this->factory->question('Environment name')
155
            ->compulsory()
156
            ->setValidator(CommonValidators::getAlphaValidator(['_', '.', '-']));
157
158
        if (null !== $envType) {
159
            $question->setDefault(\strtolower($envType));
160
        }
161
162
        $envName = $question->ask();
163
        Manifest::addMetadata(CommonMetadata::ENV_NAME_KEY, $envName);
164
        return $envName;
165
    }
166
167
    /**
168
     * @return string
169
     * @throws CommonAentsException
170
     * @throws ManifestException
171
     * @throws GuzzleException
172
     */
173
    public function askForReverseProxy(): string
174
    {
175
        $available = CommonAents::getAentsListByDependencyKey(CommonDependencies::REVERSE_PROXY_KEY);
176
        $available[] = 'Enter my own image';
177
        $image = $this->factory->choiceQuestion('Reverse proxy', $available)
178
            ->setDefault($available[0])
179
            ->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.')
180
            ->ask();
181
182
        $version = null;
183
        if ($image === 'Enter my own image') {
184
            do {
185
                $image = $this->factory->question('Docker image of your reverse proxy (without tag)')
186
                    ->compulsory()
187
                    ->setValidator(CommonValidators::getDockerImageWithoutTagValidator())
188
                    ->ask();
189
                try {
190
                    $version = $this->askForDockerImageTag($image, $image);
191
                } catch (GuzzleException $e) {
192
                    $this->output->writeln("<error>It seems that your image $image does not exist in the docker hub, please try again.</error>");
193
                    $this->output->writeln('');
194
                }
195
            } while ($version === null);
196
        } else {
197
            $availableVersions = $this->getAvailableVersions($image);
198
            if (count($availableVersions) === 1) {
199
                $version = $availableVersions[0];
200
            } else {
201
                $version = $this->askForDockerImageTag($image, $image);
202
            }
203
        }
204
205
        Manifest::addDependency("$image:$version", CommonDependencies::REVERSE_PROXY_KEY, [
206
            CommonMetadata::ENV_NAME_KEY => Manifest::mustGetMetadata(CommonMetadata::ENV_NAME_KEY),
207
            CommonMetadata::ENV_TYPE_KEY => Manifest::mustGetMetadata(CommonMetadata::ENV_TYPE_KEY)
208
        ]);
209
210
        return Manifest::mustGetDependency(CommonDependencies::REVERSE_PROXY_KEY);
211
    }
212
213
    /**
214
     * @return null|string
215
     * @throws CommonAentsException
216
     * @throws ManifestException
217
     * @throws GuzzleException
218
     */
219
    public function askForCI(): ?string
220
    {
221
        $envType = Manifest::mustGetMetadata(CommonMetadata::ENV_TYPE_KEY);
222
223
        if ($envType === CommonMetadata::ENV_TYPE_DEV) {
224
            return null;
225
        }
226
227
        $available = CommonAents::getAentsListByDependencyKey(CommonDependencies::CI_KEY);
228
        $available[] = 'Enter my own image';
229
230
        $image = $this->factory->choiceQuestion('CI/CD', $available)
231
            ->setDefault($available[0])
232
            ->ask();
233
234
        $version = null;
235
        if ($image === 'Enter my own image') {
236
            do {
237
                $image = $this->factory->question('Docker image of your CI tool (without tag)')
238
                    ->compulsory()
239
                    ->setValidator(CommonValidators::getDockerImageWithoutTagValidator())
240
                    ->ask();
241
                try {
242
                    $version = $this->askForDockerImageTag($image, $image);
243
                } catch (GuzzleException $e) {
244
                    $this->output->writeln("<error>It seems that $image does not exist in the docker hub, please try again.</error>");
245
                    $this->output->writeln('');
246
                }
247
            } while ($version === null);
248
        } else {
249
            $availableVersions = $this->getAvailableVersions($image);
250
            if (count($availableVersions) === 1) {
251
                $version = $availableVersions[0];
252
            } else {
253
                $version = $this->askForDockerImageTag($image, $image);
254
            }
255
        }
256
257
        Manifest::addDependency("$image:$version", CommonDependencies::CI_KEY, [
258
            CommonMetadata::ENV_NAME_KEY => Manifest::mustGetMetadata(CommonMetadata::ENV_NAME_KEY),
259
            CommonMetadata::ENV_TYPE_KEY => Manifest::mustGetMetadata(CommonMetadata::ENV_TYPE_KEY)
260
        ]);
261
262
        return Manifest::mustGetDependency(CommonDependencies::CI_KEY);
263
    }
264
265
    /**
266
     * @return null|string
267
     * @throws CommonAentsException
268
     * @throws ManifestException
269
     * @throws GuzzleException
270
     */
271
    public function askForImageBuilder(): ?string
272
    {
273
        $envType = Manifest::mustGetMetadata(CommonMetadata::ENV_TYPE_KEY);
274
275
        if ($envType === CommonMetadata::ENV_TYPE_DEV) {
276
            return null;
277
        }
278
279
        $availableImageBuilders = CommonAents::getAentsListByDependencyKey(CommonDependencies::IMAGE_BUILDER_KEY);
280
        $availableImageBuilders[] = 'Enter my own image';
281
282
        $image = $this->factory->choiceQuestion('Image builder', $availableImageBuilders)
283
            ->setDefault($availableImageBuilders[0])
284
            ->setHelpText('An image builder can generate Dockerfiles, which then can be used to build images of your project.')
285
            ->ask();
286
        $version = null;
287
288
        if ($image === 'Enter my own image') {
289
            do {
290
                $image = $this->factory->question('Docker image of your image builder (without tag)')
291
                    ->compulsory()
292
                    ->setValidator(CommonValidators::getDockerImageWithoutTagValidator())
293
                    ->ask();
294
                try {
295
                    $version = $this->askForDockerImageTag($image, $image);
296
                } catch (GuzzleException $e) {
297
                    $this->output->writeln("<error>It seems that $image does not exist in the docker hub, please try again.</error>");
298
                    $this->output->writeln('');
299
                }
300
            } while ($version === null);
301
        } else {
302
            $availableVersions = $this->getAvailableVersions($image);
303
            if (count($availableVersions) === 1) {
304
                $version = $availableVersions[0];
305
            } else {
306
                $version = $this->askForDockerImageTag($image, $image);
307
            }
308
        }
309
310
        Manifest::addDependency("$image:$version", CommonDependencies::IMAGE_BUILDER_KEY, [
311
            CommonMetadata::ENV_NAME_KEY => Manifest::mustGetMetadata(CommonMetadata::ENV_NAME_KEY),
312
            CommonMetadata::ENV_TYPE_KEY => Manifest::mustGetMetadata(CommonMetadata::ENV_TYPE_KEY)
313
        ]);
314
315
        return Manifest::mustGetDependency(CommonDependencies::IMAGE_BUILDER_KEY);
316
    }
317
}
318