Completed
Push — master ( 580d7e...d38950 )
by Jindun
13s queued 11s
created

CommonQuestions::askForCI()   A

Complexity

Conditions 5
Paths 4

Size

Total Lines 38
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 27
nc 4
nop 0
dl 0
loc 38
rs 9.1768
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 an array of {"ENV_NAME": "foo", "ENV_TYPE": "bar"}, chosen by the user
104
     * @return mixed[]|null
105
     * @throws CommonAentsException
106
     */
107
    public function askForEnvironments(): ?array
108
    {
109
        $environments = \array_unique(Aenthill::dispatchJson(CommonEvents::ENVIRONMENT_EVENT, []), SORT_REGULAR);
110
111
        if (empty($environments)) {
112
            $this->output->writeln('<error>No environments available.</error>');
113
            $this->output->writeln('Did you forget to install an orchestrator?');
114
            $this->output->writeln('<info>Available orchestrators:</info> ' . implode(', ', CommonAents::getAentsListByDependencyKey(CommonDependencies::ORCHESTRATOR_KEY)));
115
            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...
116
        }
117
118
        $environmentsStr = [];
119
        foreach ($environments as $env) {
120
            $environmentsStr[] = $env[CommonMetadata::ENV_NAME_KEY] . ' (of type '. $env[CommonMetadata::ENV_TYPE_KEY]  .')';
121
        }
122
123
        $chosen = $this->factory->choiceQuestion('Environments', $environmentsStr, false)
124
            ->askWithMultipleChoices();
125
126
        $this->output->writeln('<info>Environments: ' . \implode(', ', $chosen) . '</info>');
127
        $this->output->writeln('');
128
129
        $results = [];
130
        foreach ($chosen as $c) {
131
            $results[] = $environments[\array_search($c, $environmentsStr, true)];
132
        }
133
134
        return $results;
135
    }
136
137
    public function askForEnvType(): string
138
    {
139
        $envType = $this->factory->choiceQuestion('Environment type', [CommonMetadata::ENV_TYPE_DEV, CommonMetadata::ENV_TYPE_TEST, CommonMetadata::ENV_TYPE_PROD])
140
            ->ask();
141
        Manifest::addMetadata(CommonMetadata::ENV_TYPE_KEY, $envType);
142
        return $envType;
143
    }
144
145
    public function askForEnvName(?string $envType): string
146
    {
147
        $question = $this->factory->question('Environment name')
148
            ->compulsory()
149
            ->setValidator(CommonValidators::getAlphaValidator(['_', '.', '-']));
150
151
        if (null !== $envType) {
152
            $question->setDefault(\strtolower($envType));
153
        }
154
155
        $envName = $question->ask();
156
        Manifest::addMetadata(CommonMetadata::ENV_NAME_KEY, $envName);
157
        return $envName;
158
    }
159
160
    /**
161
     * @return string
162
     * @throws CommonAentsException
163
     * @throws ManifestException
164
     */
165
    public function askForReverseProxy(): string
166
    {
167
        $available = CommonAents::getAentsListByDependencyKey(CommonDependencies::REVERSE_PROXY_KEY);
168
        $available[] = 'other';
169
        $image = $this->factory->choiceQuestion('Reverse proxy', $available)
170
            ->setDefault($available[0])
171
            ->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.')
172
            ->ask();
173
174
        $version = null;
175
        if ($image === 'other') {
176
            do {
177
                $image = $this->factory->question('Docker image of your reverse proxy (without tag)')
178
                    ->compulsory()
179
                    ->setValidator(CommonValidators::getDockerImageWithoutTagValidator())
180
                    ->ask();
181
                try {
182
                    $version = $this->askForDockerImageTag($image, $image);
183
                } catch (GuzzleException $e) {
184
                    $this->output->writeln("<error>It seems that your image $image does not exist in the docker hub, please try again.</error>");
185
                    $this->output->writeln('');
186
                }
187
            } while ($version === null);
188
        } else {
189
            $version = $this->askForDockerImageTag($image, $image);
190
        }
191
192
        Manifest::addDependency("$image:$version", CommonDependencies::REVERSE_PROXY_KEY, [
193
            CommonMetadata::ENV_NAME_KEY => Manifest::mustGetMetadata(CommonMetadata::ENV_NAME_KEY),
194
            CommonMetadata::ENV_TYPE_KEY => Manifest::mustGetMetadata(CommonMetadata::ENV_TYPE_KEY)
195
        ]);
196
197
        return Manifest::mustGetDependency(CommonDependencies::REVERSE_PROXY_KEY);
198
    }
199
200
    /**
201
     * @return null|string
202
     * @throws CommonAentsException
203
     * @throws ManifestException
204
     */
205
    public function askForCI(): ?string
206
    {
207
        $envType = Manifest::mustGetMetadata(CommonMetadata::ENV_TYPE_KEY);
208
209
        if ($envType === CommonMetadata::ENV_TYPE_DEV) {
210
            return null;
211
        }
212
213
        $available = CommonAents::getAentsListByDependencyKey(CommonDependencies::CI_KEY);
214
        $available[] = 'other';
215
        $image = $this->factory->choiceQuestion('CI/CD', $available)
216
            ->setDefault($available[0])
217
            ->ask();
218
219
        $version = null;
220
        if ($image === 'other') {
221
            do {
222
                $image = $this->factory->question('Docker image of your CI tool (without tag)')
223
                    ->compulsory()
224
                    ->setValidator(CommonValidators::getDockerImageWithoutTagValidator())
225
                    ->ask();
226
                try {
227
                    $version = $this->askForDockerImageTag($image, $image);
228
                } catch (GuzzleException $e) {
229
                    $this->output->writeln("<error>It seems that $image does not exist in the docker hub, please try again.</error>");
230
                    $this->output->writeln('');
231
                }
232
            } while ($version === null);
233
        } else {
234
            $version = $this->askForDockerImageTag($image, $image);
235
        }
236
237
        Manifest::addDependency("$image:$version", CommonDependencies::CI_KEY, [
238
            CommonMetadata::ENV_NAME_KEY => Manifest::mustGetMetadata(CommonMetadata::ENV_NAME_KEY),
239
            CommonMetadata::ENV_TYPE_KEY => Manifest::mustGetMetadata(CommonMetadata::ENV_TYPE_KEY)
240
        ]);
241
242
        return Manifest::mustGetDependency(CommonDependencies::CI_KEY);
243
    }
244
245
    /**
246
     * @return null|string
247
     * @throws CommonAentsException
248
     * @throws ManifestException
249
     */
250
    public function askForImageBuilder(): ?string
251
    {
252
        $envType = Manifest::mustGetMetadata(CommonMetadata::ENV_TYPE_KEY);
253
254
        if ($envType === CommonMetadata::ENV_TYPE_DEV) {
255
            return null;
256
        }
257
258
        $available = CommonAents::getAentsListByDependencyKey(CommonDependencies::IMAGE_BUILDER_KEY);
259
        $available[] = 'other';
260
        $image = $this->factory->choiceQuestion('Image builder', $available)
261
            ->setDefault($available[0])
262
            ->setHelpText('An image builder can generate Dockerfiles, which then can be used to build images of your project.')
263
            ->ask();
264
265
        $version = null;
266
        if ($image === 'other') {
267
            do {
268
                $image = $this->factory->question('Docker image of your image builder (without tag)')
269
                    ->compulsory()
270
                    ->setValidator(CommonValidators::getDockerImageWithoutTagValidator())
271
                    ->ask();
272
                try {
273
                    $version = $this->askForDockerImageTag($image, $image);
274
                } catch (GuzzleException $e) {
275
                    $this->output->writeln("<error>It seems that $image does not exist in the docker hub, please try again.</error>");
276
                    $this->output->writeln('');
277
                }
278
            } while ($version === null);
279
        } else {
280
            $version = $this->askForDockerImageTag($image, $image);
281
        }
282
283
        Manifest::addDependency("$image:$version", CommonDependencies::IMAGE_BUILDER_KEY, [
284
            CommonMetadata::ENV_NAME_KEY => Manifest::mustGetMetadata(CommonMetadata::ENV_NAME_KEY),
285
            CommonMetadata::ENV_TYPE_KEY => Manifest::mustGetMetadata(CommonMetadata::ENV_TYPE_KEY)
286
        ]);
287
288
        return Manifest::mustGetDependency(CommonDependencies::IMAGE_BUILDER_KEY);
289
    }
290
}
291