Completed
Push — master ( fe98f7...8d5106 )
by David
24s queued 11s
created

src/Question/CommonQuestions.php (1 issue)

1
<?php
2
3
4
namespace TheAentMachine\Question;
5
6
use GuzzleHttp\Exception\ClientException;
7
use GuzzleHttp\Exception\GuzzleException;
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\Aenthill\CommonAents;
14
use TheAentMachine\Aenthill\CommonDependencies;
15
use TheAentMachine\Aenthill\CommonEvents;
16
use TheAentMachine\Aenthill\CommonMetadata;
17
use TheAentMachine\Aenthill\Manifest;
18
use TheAentMachine\Exception\CommonAentsException;
19
use TheAentMachine\Exception\ManifestException;
20
use TheAentMachine\Registry\RegistryClient;
21
use TheAentMachine\Registry\TagsAnalyzer;
22
23
final class CommonQuestions
24
{
25
    /** @var InputInterface */
26
    private $input;
27
28
    /** @var OutputInterface */
29
    private $output;
30
31
    /** @var QuestionHelper */
32
    private $questionHelper;
33
34
    /** @var QuestionFactory */
35
    private $factory;
36
37
    public function __construct(InputInterface $input, OutputInterface $output, QuestionHelper $questionHelper)
38
    {
39
        $this->input = $input;
40
        $this->output = $output;
41
        $this->questionHelper = $questionHelper;
42
        $this->factory = new QuestionFactory($input, $output, $questionHelper);
43
    }
44
45
    /** @return string[] */
46
    private function getAvailableVersions(string $dockerHubImage): array
47
    {
48
        $registryClient = new RegistryClient();
49
        return $registryClient->getImageTagsOnDockerHub($dockerHubImage);
50
    }
51
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;
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[]
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
Bug Best Practice introduced by
In this branch, the function will implicitly return null which is incompatible with the type-hinted return array. Consider adding a return statement or allowing null as return value.

For hinted functions/methods where all return statements with the correct type are only reachable via conditions, ?null? gets implicitly returned which may be incompatible with the hinted type. Let?s take a look at an example:

interface ReturnsInt {
    public function returnsIntHinted(): int;
}

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