Completed
Pull Request — master (#59)
by Julien
02:49
created

CommonQuestions::askForEnvironments()   A

Complexity

Conditions 4
Paths 5

Size

Total Lines 28
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 4
eloc 17
nc 5
nop 0
dl 0
loc 28
rs 9.7
c 0
b 0
f 0
1
<?php
2
3
4
namespace TheAentMachine\Question;
5
6
use Symfony\Component\Console\Helper\QuestionHelper;
7
use Symfony\Component\Console\Input\InputInterface;
8
use Symfony\Component\Console\Output\OutputInterface;
9
use TheAentMachine\Aenthill\Aenthill;
10
use TheAentMachine\Aenthill\CommonAents;
11
use TheAentMachine\Aenthill\CommonDependencies;
12
use TheAentMachine\Aenthill\CommonEvents;
13
use TheAentMachine\Aenthill\CommonMetadata;
14
use TheAentMachine\Aenthill\Manifest;
15
use TheAentMachine\Exception\CommonAentsException;
16
use TheAentMachine\Exception\ManifestException;
17
use TheAentMachine\Registry\RegistryClient;
18
use TheAentMachine\Registry\TagsAnalyzer;
19
use Symfony\Component\Console\Question\Question as SymfonyQuestion;
20
21
final class CommonQuestions
22
{
23
    /** @var InputInterface */
24
    private $input;
25
26
    /** @var OutputInterface */
27
    private $output;
28
29
    /** @var QuestionHelper */
30
    private $questionHelper;
31
32
    /** @var QuestionFactory */
33
    private $factory;
34
35
    public function __construct(InputInterface $input, OutputInterface $output, QuestionHelper $questionHelper)
36
    {
37
        $this->input = $input;
38
        $this->output = $output;
39
        $this->questionHelper = $questionHelper;
40
        $this->factory = new QuestionFactory($input, $output, $questionHelper);
41
    }
42
43
    public function askForDockerImageTag(string $dockerHubImage, string $applicationName = ''): string
44
    {
45
        $registryClient = new RegistryClient();
46
        $availableVersions = $registryClient->getImageTagsOnDockerHub($dockerHubImage);
47
48
        $tagsAnalyzer = new TagsAnalyzer();
49
        $proposedTags = $tagsAnalyzer->filterBestTags($availableVersions);
50
        $default = $proposedTags[0] ?? null;
51
52
        $this->output->writeln("Please choose your $applicationName version.");
53
54
        if (!empty($proposedTags)) {
55
            $this->output->writeln('Possible values include: <info>' . \implode('</info>, <info>', $proposedTags) . '</info>');
56
        }
57
58
        $this->output->writeln('Enter "v" to view all available versions, "?" for help');
59
60
        $question = new SymfonyQuestion(
61
            "Select your $applicationName version [$default]: ",
62
            $default
63
        );
64
65
        $question->setAutocompleterValues($availableVersions);
66
67
        $question->setValidator(function (string $value) use ($availableVersions, $dockerHubImage) {
68
            $value = trim($value);
69
70
            if ($value === 'v') {
71
                $this->output->writeln('Available versions: <info>' . \implode('</info>, <info>', $availableVersions) . '</info>');
72
                return 'v';
73
            }
74
75
            if ($value === '?') {
76
                $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.");
77
                return '?';
78
            }
79
80
            if (!\in_array($value, $availableVersions)) {
81
                throw new \InvalidArgumentException("Version '$value' is invalid.");
82
            }
83
84
            return $value;
85
        });
86
        do {
87
            $version = $this->questionHelper->ask($this->input, $this->output, $question);
88
        } while ($version === 'v' || $version === '?');
89
90
        $this->output->writeln("<info>Selected version: $version</info>");
91
        $this->output->writeln('');
92
93
        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...
94
    }
95
96
    public function askForServiceName(string $serviceName, string $applicationName = ''): string
97
    {
98
        return $this->factory->question("$applicationName service name")
99
            ->setDefault($serviceName)
100
            ->compulsory()
101
            ->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.')
102
            ->setValidator(CommonValidators::getAlphaValidator(['_', '.', '-']))
103
            ->ask();
104
    }
105
106
    /**
107
     * @return mixed[]|null
108
     * @throws CommonAentsException
109
     */
110
    public function askForEnvironments(): ?array
111
    {
112
        $environments = \array_unique(Aenthill::dispatchJson(CommonEvents::ENVIRONMENT_EVENT, []), SORT_REGULAR);
113
114
        if (empty($environments)) {
115
            $this->output->writeln('<error>No environments available.</error>');
116
            $this->output->writeln('Did you forget to install an orchestrator?');
117
            $this->output->writeln('<info>Available orchestrators:</info> ' . implode(', ', CommonAents::getAentsListByDependencyKey(CommonDependencies::ORCHESTRATOR_KEY)));
118
            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...
119
        }
120
121
        $environmentsStr = [];
122
        foreach ($environments as $env) {
123
            $environmentsStr[] = $env[CommonMetadata::ENV_NAME_KEY] . ' (of type '. $env[CommonMetadata::ENV_TYPE_KEY]  .')';
124
        }
125
126
        $chosen = $this->factory->choiceQuestion('Environments', $environmentsStr, false)
127
            ->askWithMultipleChoices();
128
129
        $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

129
        $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...
130
        $this->output->writeln('');
131
132
        $results = [];
133
        foreach ($chosen as $c) {
134
            $results[] = $environments[\array_search($c, $environmentsStr, true)];
135
        }
136
137
        return $results;
138
    }
139
140
    public function askForEnvType(): string
141
    {
142
        $envType = $this->factory->choiceQuestion('Environment type', [CommonMetadata::ENV_TYPE_DEV, CommonMetadata::ENV_TYPE_TEST, CommonMetadata::ENV_TYPE_PROD])
143
            ->ask();
144
        Manifest::addMetadata(CommonMetadata::ENV_TYPE_KEY, $envType);
145
146
        return $envType;
147
    }
148
149
    public function askForEnvName(?string $envType): string
150
    {
151
        $question = $this->factory->question('Environment name')
152
            ->compulsory()
153
            ->setValidator(CommonValidators::getAlphaValidator(['_', '.', '-']));
154
155
        if (null !== $envType) {
156
            $question->setDefault(\strtolower($envType));
157
        }
158
159
        $envName = $question->ask();
160
        Manifest::addMetadata(CommonMetadata::ENV_NAME_KEY, $envName);
161
162
        return $envName;
163
    }
164
165
    /**
166
     * @return string
167
     * @throws CommonAentsException
168
     * @throws ManifestException
169
     */
170
    public function askForReverseProxy(): string
171
    {
172
        $available = CommonAents::getAentsListByDependencyKey(CommonDependencies::REVERSE_PROXY_KEY);
173
        $image = $this->factory->choiceQuestion('Reverse proxy', $available)
174
            ->setDefault($available[0])
175
            ->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.')
176
            ->ask();
177
178
        $version = $this->askForDockerImageTag($image, $image);
179
180
        Manifest::addDependency("$image:$version", CommonDependencies::REVERSE_PROXY_KEY, [
181
            CommonMetadata::ENV_NAME_KEY => Manifest::mustGetMetadata(CommonMetadata::ENV_NAME_KEY),
182
            CommonMetadata::ENV_TYPE_KEY => Manifest::mustGetMetadata(CommonMetadata::ENV_TYPE_KEY)
183
        ]);
184
185
        return Manifest::mustGetDependency(CommonDependencies::REVERSE_PROXY_KEY);
186
    }
187
188
    /**
189
     * @return null|string
190
     * @throws CommonAentsException
191
     * @throws ManifestException
192
     */
193
    public function askForCI(): ?string
194
    {
195
        $envType = Manifest::mustGetMetadata(CommonMetadata::ENV_TYPE_KEY);
196
197
        if ($envType === CommonMetadata::ENV_TYPE_DEV) {
198
            return null;
199
        }
200
201
        $installCIAent = $this->factory->question('Do you use a CI/CD tool?')
202
            ->compulsory()
203
            ->yesNoQuestion()
204
            ->ask();
205
206
        if (empty($installCIAent)) {
207
            return null;
208
        }
209
210
        $available = CommonAents::getAentsListByDependencyKey(CommonDependencies::CI_KEY);
211
        $image = $this->factory->choiceQuestion('CI/CD', $available)
212
            ->setDefault($available[0])
213
            ->ask();
214
215
        $version = $this->askForDockerImageTag($image, $image);
216
217
        Manifest::addDependency("$image:$version", CommonDependencies::REVERSE_PROXY_KEY, [
218
            CommonMetadata::ENV_NAME_KEY => Manifest::mustGetMetadata(CommonMetadata::ENV_NAME_KEY),
219
            CommonMetadata::ENV_TYPE_KEY => Manifest::mustGetMetadata(CommonMetadata::ENV_TYPE_KEY)
220
        ]);
221
222
        return Manifest::mustGetDependency(CommonDependencies::REVERSE_PROXY_KEY);
223
    }
224
}
225