Completed
Push — master ( 88d837...6447b1 )
by Julien
12s
created

CommonQuestions::spacer()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
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 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 askForDockerImageTag(string $dockerHubImage, string $applicationName = ''): string
45
    {
46
        $registryClient = new RegistryClient();
47
        $availableVersions = $registryClient->getImageTagsOnDockerHub($dockerHubImage);
48
49
        $tagsAnalyzer = new TagsAnalyzer();
50
        $proposedTags = $tagsAnalyzer->filterBestTags($availableVersions);
51
        $default = $proposedTags[0] ?? $availableVersions[0];
52
53
        $this->output->writeln("Please choose your $applicationName version.");
54
55
        if (!empty($proposedTags)) {
56
            $this->output->writeln('Possible values include: <info>' . \implode('</info>, <info>', $proposedTags) . '</info>');
57
        }
58
        $this->output->writeln('Enter "v" to view all available versions, "?" for help');
59
60
        $question = new SymfonyQuestion("Select your $applicationName version [$default]: ", $default);
61
62
        $question->setAutocompleterValues($availableVersions);
63
        $question->setValidator(function (string $value) use ($availableVersions, $dockerHubImage) {
64
            $value = trim($value);
65
66
            if ($value === 'v') {
67
                $this->output->writeln('Available versions: <info>' . \implode('</info>, <info>', $availableVersions) . '</info>');
68
                return 'v';
69
            }
70
71
            if ($value === '?') {
72
                $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.");
73
                return '?';
74
            }
75
76
            if (!\in_array($value, $availableVersions, true)) {
77
                throw new \InvalidArgumentException("Version '$value' is invalid.");
78
            }
79
80
            return $value;
81
        });
82
        do {
83
            $version = $this->questionHelper->ask($this->input, $this->output, $question);
84
        } while ($version === 'v' || $version === '?');
85
86
        $this->output->writeln("<info>Selected version: $version</info>");
87
        $this->output->writeln('');
88
89
        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...
90
    }
91
92
    public function askForServiceName(string $serviceName, string $applicationName = ''): string
93
    {
94
        return $this->factory->question("$applicationName service name")
95
            ->setDefault($serviceName)
96
            ->compulsory()
97
            ->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.')
98
            ->setValidator(CommonValidators::getAlphaValidator(['_', '.', '-']))
99
            ->ask();
100
    }
101
102
    /**
103
     * @return mixed[]|null
104
     * @throws CommonAentsException
105
     */
106
    public function askForEnvironments(): ?array
107
    {
108
        $environments = \array_unique(Aenthill::dispatchJson(CommonEvents::ENVIRONMENT_EVENT, []), SORT_REGULAR);
109
110
        if (empty($environments)) {
111
            $this->output->writeln('<error>No environments available.</error>');
112
            $this->output->writeln('Did you forget to install an orchestrator?');
113
            $this->output->writeln('<info>Available orchestrators:</info> ' . implode(', ', CommonAents::getAentsListByDependencyKey(CommonDependencies::ORCHESTRATOR_KEY)));
114
            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...
115
        }
116
117
        $environmentsStr = [];
118
        foreach ($environments as $env) {
119
            $environmentsStr[] = $env[CommonMetadata::ENV_NAME_KEY] . ' (of type '. $env[CommonMetadata::ENV_TYPE_KEY]  .')';
120
        }
121
122
        $chosen = $this->factory->choiceQuestion('Environments', $environmentsStr, false)
123
            ->askWithMultipleChoices();
124
125
        $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

125
        $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...
126
        $this->output->writeln('');
127
128
        $results = [];
129
        foreach ($chosen as $c) {
130
            $results[] = $environments[\array_search($c, $environmentsStr, true)];
131
        }
132
133
        return $results;
134
    }
135
136
    public function askForEnvType(): string
137
    {
138
        $envType = $this->factory->choiceQuestion('Environment type', [CommonMetadata::ENV_TYPE_DEV, CommonMetadata::ENV_TYPE_TEST, CommonMetadata::ENV_TYPE_PROD])
139
            ->ask();
140
        Manifest::addMetadata(CommonMetadata::ENV_TYPE_KEY, $envType);
141
        return $envType;
142
    }
143
144
    public function askForEnvName(?string $envType): string
145
    {
146
        $question = $this->factory->question('Environment name')
147
            ->compulsory()
148
            ->setValidator(CommonValidators::getAlphaValidator(['_', '.', '-']));
149
150
        if (null !== $envType) {
151
            $question->setDefault(\strtolower($envType));
152
        }
153
154
        $envName = $question->ask();
155
        Manifest::addMetadata(CommonMetadata::ENV_NAME_KEY, $envName);
156
        return $envName;
157
    }
158
159
    /**
160
     * @return string
161
     * @throws CommonAentsException
162
     * @throws ManifestException
163
     */
164
    public function askForReverseProxy(): string
165
    {
166
        $available = CommonAents::getAentsListByDependencyKey(CommonDependencies::REVERSE_PROXY_KEY);
167
        $available[] = 'other';
168
        $image = $this->factory->choiceQuestion('Reverse proxy', $available)
169
            ->setDefault($available[0])
170
            ->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.')
171
            ->ask();
172
173
        $version = null;
174
        if ($image === 'other') {
175
            do {
176
                $image = $this->factory->question('Name of your reverse proxy image (without tag)')
177
                    ->compulsory()
178
                    ->setValidator(CommonValidators::getDockerImageWithoutTagValidator())
179
                    ->ask();
180
                try {
181
                    $version = $this->askForDockerImageTag($image, $image);
182
                } catch (GuzzleException $e) {
183
                    $this->output->writeln("<error>It seems that your image $image does not exist in the docker hub, please try again.</error>");
184
                    $this->output->writeln('');
185
                }
186
            } while ($version === null);
187
        } else {
188
            $version = $this->askForDockerImageTag($image, $image);
189
        }
190
191
        Manifest::addDependency("$image:$version", CommonDependencies::REVERSE_PROXY_KEY, [
192
            CommonMetadata::ENV_NAME_KEY => Manifest::mustGetMetadata(CommonMetadata::ENV_NAME_KEY),
193
            CommonMetadata::ENV_TYPE_KEY => Manifest::mustGetMetadata(CommonMetadata::ENV_TYPE_KEY)
194
        ]);
195
196
        return Manifest::mustGetDependency(CommonDependencies::REVERSE_PROXY_KEY);
197
    }
198
199
    /**
200
     * @return null|string
201
     * @throws CommonAentsException
202
     * @throws ManifestException
203
     */
204
    public function askForCI(): ?string
205
    {
206
        $envType = Manifest::mustGetMetadata(CommonMetadata::ENV_TYPE_KEY);
207
208
        if ($envType === CommonMetadata::ENV_TYPE_DEV) {
209
            return null;
210
        }
211
212
        $installCIAent = $this->factory->question('Do you use a CI/CD tool?')
213
            ->compulsory()
214
            ->yesNoQuestion()
215
            ->ask();
216
217
        if (empty($installCIAent)) {
218
            return null;
219
        }
220
221
        $available = CommonAents::getAentsListByDependencyKey(CommonDependencies::CI_KEY);
222
        $available[] = 'other';
223
        $image = $this->factory->choiceQuestion('CI/CD', $available)
224
            ->setDefault($available[0])
225
            ->ask();
226
227
        $version = null;
228
        if ($image === 'other') {
229
            do {
230
                $image = $this->factory->question('Name of your CI image (without tag)')
231
                    ->compulsory()
232
                    ->setValidator(CommonValidators::getDockerImageWithoutTagValidator())
233
                    ->ask();
234
                try {
235
                    $version = $this->askForDockerImageTag($image, $image);
236
                } catch (GuzzleException $e) {
237
                    $this->output->writeln("<error>It seems that $image does not exist in the docker hub, please try again.</error>");
238
                    $this->output->writeln('');
239
                }
240
            } while ($version === null);
241
        } else {
242
            $version = $this->askForDockerImageTag($image, $image);
243
        }
244
245
        Manifest::addDependency("$image:$version", CommonDependencies::CI_KEY, [
246
            CommonMetadata::ENV_NAME_KEY => Manifest::mustGetMetadata(CommonMetadata::ENV_NAME_KEY),
247
            CommonMetadata::ENV_TYPE_KEY => Manifest::mustGetMetadata(CommonMetadata::ENV_TYPE_KEY)
248
        ]);
249
250
        return Manifest::mustGetDependency(CommonDependencies::CI_KEY);
251
    }
252
}
253