Completed
Push — master ( 22a067...88d837 )
by Julien
12s
created

CommonQuestions::askForCI()   B

Complexity

Conditions 6
Paths 5

Size

Total Lines 48
Code Lines 34

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 34
nc 5
nop 0
dl 0
loc 48
rs 8.7537
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 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
    private 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
                    $this->spacer();
196
                    $version = null;
197
                }
198
            } while ($version === null);
199
        }
200
201
        Manifest::addDependency("$image:$version", CommonDependencies::REVERSE_PROXY_KEY, [
202
            CommonMetadata::ENV_NAME_KEY => Manifest::mustGetMetadata(CommonMetadata::ENV_NAME_KEY),
203
            CommonMetadata::ENV_TYPE_KEY => Manifest::mustGetMetadata(CommonMetadata::ENV_TYPE_KEY)
204
        ]);
205
206
        return Manifest::mustGetDependency(CommonDependencies::REVERSE_PROXY_KEY);
207
    }
208
209
    /**
210
     * @return null|string
211
     * @throws CommonAentsException
212
     * @throws ManifestException
213
     */
214
    public function askForCI(): ?string
215
    {
216
        $envType = Manifest::mustGetMetadata(CommonMetadata::ENV_TYPE_KEY);
217
218
        if ($envType === CommonMetadata::ENV_TYPE_DEV) {
219
            return null;
220
        }
221
222
        $installCIAent = $this->factory->question('Do you use a CI/CD tool?')
223
            ->compulsory()
224
            ->yesNoQuestion()
225
            ->ask();
226
        $this->spacer();
227
228
        if (empty($installCIAent)) {
229
            return null;
230
        }
231
232
        $available = CommonAents::getAentsListByDependencyKey(CommonDependencies::CI_KEY);
233
        $available[] = 'other';
234
        $image = $this->factory->choiceQuestion('CI/CD', $available)
235
            ->setDefault($available[0])
236
            ->ask();
237
        $this->spacer();
238
239
        $version = null;
240
        if ($image === 'other') {
241
            do {
242
                $image = $this->factory->question('Name of your CI image (without tag)')
243
                    ->compulsory()
244
                    ->setValidator(CommonValidators::getDockerImageWithoutTagValidator())
245
                    ->ask();
246
                $this->spacer();
247
                try {
248
                    $version = $this->askForDockerImageTag($image, $image);
249
                } catch (GuzzleException $e) {
250
                    $this->output->writeln("<error>It seems that $image does not exist in the docker hub, please try again.</error>");
251
                    $this->spacer();
252
                }
253
            } while ($version === null);
254
        }
255
256
        Manifest::addDependency("$image:$version", CommonDependencies::CI_KEY, [
257
            CommonMetadata::ENV_NAME_KEY => Manifest::mustGetMetadata(CommonMetadata::ENV_NAME_KEY),
258
            CommonMetadata::ENV_TYPE_KEY => Manifest::mustGetMetadata(CommonMetadata::ENV_TYPE_KEY)
259
        ]);
260
261
        return Manifest::mustGetDependency(CommonDependencies::CI_KEY);
262
    }
263
}
264