Completed
Pull Request — master (#65)
by Jindun
02:27
created

CommonQuestions   A

Complexity

Total Complexity 26

Size/Duplication

Total Lines 239
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 135
dl 0
loc 239
rs 10
c 0
b 0
f 0
wmc 26

9 Methods

Rating   Name   Duplication   Size   Complexity  
A askForEnvType() 0 8 1
A spacer() 0 3 1
A askForEnvName() 0 14 2
B askForCI() 0 48 6
A __construct() 0 6 1
A askForServiceName() 0 10 1
B askForDockerImageTag() 0 46 6
A askForEnvironments() 0 28 4
A askForReverseProxy() 0 32 4
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 TheAentMachine\Aenthill\Aenthill;
11
use TheAentMachine\Aenthill\CommonAents;
12
use TheAentMachine\Aenthill\CommonDependencies;
13
use TheAentMachine\Aenthill\CommonEvents;
14
use TheAentMachine\Aenthill\CommonMetadata;
15
use TheAentMachine\Aenthill\Manifest;
16
use TheAentMachine\Exception\CommonAentsException;
17
use TheAentMachine\Exception\ManifestException;
18
use TheAentMachine\Registry\RegistryClient;
19
use TheAentMachine\Registry\TagsAnalyzer;
20
use Symfony\Component\Console\Question\Question as SymfonyQuestion;
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
    public function spacer(): void
45
    {
46
        $this->output->writeln('');
47
    }
48
49
    public function askForDockerImageTag(string $dockerHubImage, string $applicationName = ''): string
50
    {
51
        $registryClient = new RegistryClient();
52
        $availableVersions = $registryClient->getImageTagsOnDockerHub($dockerHubImage);
53
54
        $tagsAnalyzer = new TagsAnalyzer();
55
        $proposedTags = $tagsAnalyzer->filterBestTags($availableVersions);
56
        $default = $proposedTags[0] ?? $availableVersions[0];
57
58
        $this->output->writeln("Please choose your $applicationName version.");
59
60
        if (!empty($proposedTags)) {
61
            $this->output->writeln('Possible values include: <info>' . \implode('</info>, <info>', $proposedTags) . '</info>');
62
        }
63
        $this->output->writeln('Enter "v" to view all available versions, "?" for help');
64
65
        $question = new SymfonyQuestion("Select your $applicationName version [$default]: ", $default);
66
67
        $question->setAutocompleterValues($availableVersions);
68
        $question->setValidator(function (string $value) use ($availableVersions, $dockerHubImage) {
69
            $value = trim($value);
70
71
            if ($value === 'v') {
72
                $this->output->writeln('Available versions: <info>' . \implode('</info>, <info>', $availableVersions) . '</info>');
73
                return 'v';
74
            }
75
76
            if ($value === '?') {
77
                $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.");
78
                return '?';
79
            }
80
81
            if (!\in_array($value, $availableVersions, true)) {
82
                throw new \InvalidArgumentException("Version '$value' is invalid.");
83
            }
84
85
            return $value;
86
        });
87
        do {
88
            $version = $this->questionHelper->ask($this->input, $this->output, $question);
89
        } while ($version === 'v' || $version === '?');
90
91
        $this->output->writeln("<info>Selected version: $version</info>");
92
        $this->spacer();
93
94
        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...
95
    }
96
97
    public function askForServiceName(string $serviceName, string $applicationName = ''): string
98
    {
99
        $answer = $this->factory->question("$applicationName service name")
100
            ->setDefault($serviceName)
101
            ->compulsory()
102
            ->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.')
103
            ->setValidator(CommonValidators::getAlphaValidator(['_', '.', '-']))
104
            ->ask();
105
        $this->spacer();
106
        return $answer;
107
    }
108
109
    /**
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>');
0 ignored issues
show
Unused Code introduced by
The call to implode() has too many arguments starting with ', '. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

132
        $this->output->writeln('<info>Environments: ' . /** @scrutinizer ignore-call */ \implode($chosen, ', ') . '</info>');

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.

Loading history...
133
        $this->spacer();
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
        $this->spacer();
148
        Manifest::addMetadata(CommonMetadata::ENV_TYPE_KEY, $envType);
149
150
        return $envType;
151
    }
152
153
    public function askForEnvName(?string $envType): string
154
    {
155
        $question = $this->factory->question('Environment name')
156
            ->compulsory()
157
            ->setValidator(CommonValidators::getAlphaValidator(['_', '.', '-']));
158
159
        if (null !== $envType) {
160
            $question->setDefault(\strtolower($envType));
161
        }
162
163
        $envName = $question->ask();
164
        $this->spacer();
165
        Manifest::addMetadata(CommonMetadata::ENV_NAME_KEY, $envName);
166
        return $envName;
167
    }
168
169
    /**
170
     * @return string
171
     * @throws CommonAentsException
172
     * @throws ManifestException
173
     */
174
    public function askForReverseProxy(): string
175
    {
176
        $available = CommonAents::getAentsListByDependencyKey(CommonDependencies::REVERSE_PROXY_KEY);
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
        $this->spacer();
182
183
        $version = null;
184
        if ($image === 'other') {
185
            do {
186
                $image = $this->factory->question('Name of your reverse proxy image (without tag)')
187
                    ->compulsory()
188
                    ->setValidator(CommonValidators::getDockerImageWithoutTagValidator())
189
                    ->ask();
190
                $this->spacer();
191
                try {
192
                    $version = $this->askForDockerImageTag($image, $image);
193
                } catch (GuzzleException $e) {
194
                    $this->output->writeln("<error>It seems that your image $image does not exist in the docker hub, please try again.</error>");
195
                    $version = null;
196
                }
197
            } while ($version === null);
198
        }
199
200
        Manifest::addDependency("$image:$version", CommonDependencies::REVERSE_PROXY_KEY, [
201
            CommonMetadata::ENV_NAME_KEY => Manifest::mustGetMetadata(CommonMetadata::ENV_NAME_KEY),
202
            CommonMetadata::ENV_TYPE_KEY => Manifest::mustGetMetadata(CommonMetadata::ENV_TYPE_KEY)
203
        ]);
204
205
        return Manifest::mustGetDependency(CommonDependencies::REVERSE_PROXY_KEY);
206
    }
207
208
    /**
209
     * @return null|string
210
     * @throws CommonAentsException
211
     * @throws ManifestException
212
     */
213
    public function askForCI(): ?string
214
    {
215
        $envType = Manifest::mustGetMetadata(CommonMetadata::ENV_TYPE_KEY);
216
217
        if ($envType === CommonMetadata::ENV_TYPE_DEV) {
218
            return null;
219
        }
220
221
        $installCIAent = $this->factory->question('Do you use a CI/CD tool?')
222
            ->compulsory()
223
            ->yesNoQuestion()
224
            ->ask();
225
        $this->spacer();
226
227
        if (empty($installCIAent)) {
228
            return null;
229
        }
230
231
        $available = CommonAents::getAentsListByDependencyKey(CommonDependencies::CI_KEY);
232
        $available[] = 'other';
233
        $image = $this->factory->choiceQuestion('CI/CD', $available)
234
            ->setDefault($available[0])
235
            ->ask();
236
        $this->spacer();
237
238
        $version = null;
239
        if ($image === 'other') {
240
            do {
241
                $image = $this->factory->question('Name of your CI image (without tag)')
242
                    ->compulsory()
243
                    ->setValidator(CommonValidators::getDockerImageWithoutTagValidator())
244
                    ->ask();
245
                $this->spacer();
246
                try {
247
                    $version = $this->askForDockerImageTag($image, $image);
248
                } catch (GuzzleException $e) {
249
                    $this->output->writeln("<error>It seems that $image does not exist in the docker hub, please try again.</error>");
250
                    $this->spacer();
251
                }
252
            } while ($version === null);
253
        }
254
255
        Manifest::addDependency("$image:$version", CommonDependencies::CI_KEY, [
256
            CommonMetadata::ENV_NAME_KEY => Manifest::mustGetMetadata(CommonMetadata::ENV_NAME_KEY),
257
            CommonMetadata::ENV_TYPE_KEY => Manifest::mustGetMetadata(CommonMetadata::ENV_TYPE_KEY)
258
        ]);
259
260
        return Manifest::mustGetDependency(CommonDependencies::CI_KEY);
261
    }
262
}
263