Passed
Pull Request — master (#43)
by Julien
02:23
created

AentHelper::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 4
nc 1
nop 4
dl 0
loc 6
rs 10
c 0
b 0
f 0
1
<?php
2
3
4
namespace TheAentMachine\Helper;
5
6
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
7
use Symfony\Component\Console\Helper\FormatterHelper;
8
use Symfony\Component\Console\Helper\QuestionHelper;
9
use Symfony\Component\Console\Input\InputInterface;
10
use Symfony\Component\Console\Output\OutputInterface;
11
use Symfony\Component\Console\Question\Question as SymfonyQuestion;
12
use TheAentMachine\Aenthill\Aenthill;
13
use TheAentMachine\Exception\ManifestException;
14
use TheAentMachine\Exception\MissingEnvironmentVariableException;
15
use TheAentMachine\Aenthill\Manifest;
16
use TheAentMachine\Aenthill\Metadata;
17
use TheAentMachine\Registry\RegistryClient;
18
use TheAentMachine\Registry\TagsAnalyzer;
19
20
/**
21
 * A helper class for the most common questions asked in the console.
22
 */
23
class AentHelper
24
{
25
    /** @var InputInterface */
26
    private $input;
27
28
    /** @var OutputInterface */
29
    private $output;
30
31
    /** @var QuestionHelper */
32
    private $questionHelper;
33
34
    /** @var FormatterHelper */
35
    private $formatterHelper;
36
37
    public function __construct(InputInterface $input, OutputInterface $output, QuestionHelper $questionHelper, FormatterHelper $formatterHelper)
38
    {
39
        $this->input = $input;
40
        $this->output = $output;
41
        $this->questionHelper = $questionHelper;
42
        $this->formatterHelper = $formatterHelper;
43
    }
44
45
    private function registerStyle(): void
46
    {
47
        $outputStyle = new OutputFormatterStyle('black', 'cyan', ['bold']);
48
        $this->output->getFormatter()->setStyle('title', $outputStyle);
49
    }
50
51
    /**
52
     * Displays text in a big block
53
     */
54
    public function title(string $title): void
55
    {
56
        $this->registerStyle();
57
        $this->output->writeln($this->formatterHelper->formatBlock($title, 'title', true));
58
    }
59
60
    /**
61
     * Displays text in a small block
62
     */
63
    public function subTitle(string $title): void
64
    {
65
        $this->registerStyle();
66
        $this->output->writeln($this->formatterHelper->formatBlock($title, 'title', false));
67
    }
68
69
    public function spacer(): void
70
    {
71
        $this->output->writeln('');
72
    }
73
74
    public function question(string $question): Question
75
    {
76
        return new Question($this->questionHelper, $this->input, $this->output, $question);
77
    }
78
79
    /**
80
     * @param string[] $choices
81
     * @return ChoiceQuestion
82
     */
83
    public function choiceQuestion(string $question, array $choices): ChoiceQuestion
84
    {
85
        return new ChoiceQuestion($this->questionHelper, $this->input, $this->output, $question, $choices);
86
    }
87
88
    public function askForEnvName(): string
89
    {
90
        $envName = $this->question('Environment name')
91
            ->compulsory()
92
            ->setValidator(function (string $value) {
93
                $value = trim($value);
94
                if (!\preg_match('/^[a-zA-Z0-9_.-]+$/', $value)) {
95
                    throw new \InvalidArgumentException('Invalid environment name "' . $value . '". Environment names can contain alphanumeric characters, and "_", ".", "-".');
96
                }
97
                return $value;
98
            })
99
            ->ask();
100
        $this->output->writeln("<info>Environment name: $envName</info>");
101
        $this->spacer();
102
        Manifest::addMetadata(Metadata::ENV_NAME_KEY, $envName);
103
        return $envName;
104
    }
105
106
    public function askForEnvType(): string
107
    {
108
        $envType = $this->choiceQuestion('Environment type', [Metadata::ENV_TYPE_DEV, Metadata::ENV_TYPE_TEST, Metadata::ENV_TYPE_PROD])
109
            ->askSingleChoiceQuestion();
110
        $this->output->writeln("<info>Environment type: $envType</info>");
111
        $this->spacer();
112
        Manifest::addMetadata(Metadata::ENV_TYPE_KEY, $envType);
113
        return $envType;
114
    }
115
116
    /**
117
     * @return string
118
     * @throws MissingEnvironmentVariableException
119
     * @throws ManifestException
120
     */
121
    public function askForCICD(): string
122
    {
123
        $ci = $this->choiceQuestion('CI/CD', ['gitlab-ci', 'travis-ci', 'circle-ci'])
124
            ->askSingleChoiceQuestion();
125
        $this->output->writeln("<info>CI/CD: $ci</info>");
126
        $this->spacer();
127
        Manifest::addDependency("theaentmachine/aent-$ci", Metadata::CI_KEY, [
128
            Metadata::ENV_NAME_KEY => Manifest::getMetadata(Metadata::ENV_NAME_KEY),
129
            Metadata::ENV_TYPE_KEY => Manifest::getMetadata(Metadata::ENV_TYPE_KEY)
130
        ]);
131
        return Manifest::getDependency(Metadata::CI_KEY);
132
    }
133
134
    /**
135
     * @return string
136
     * @throws MissingEnvironmentVariableException
137
     * @throws ManifestException
138
     */
139
    /*public function registerReverseProxy(): string
140
    {
141
        $reverseProxy = $this->choiceQuestion('Reverse proxy', ['traefik', 'nginx', 'ingress'])
142
            ->askSingleChoiceQuestion();
143
        $this->output->writeln("<info>Reverse proxy: $reverseProxy</info>");
144
        $this->spacer();
145
        Manifest::addDependency("theaentmachine/aent-$reverseProxy", Metadata::REVERSE_PROXY_KEY, [
146
            Metadata::ENV_NAME_KEY => Manifest::getMetadata(Metadata::ENV_NAME_KEY),
147
            Metadata::ENV_TYPE_KEY => Manifest::getMetadata(Metadata::ENV_TYPE_KEY)
148
        ]);
149
        return Manifest::getDependency(Metadata::REVERSE_PROXY_KEY);
150
    }*/
151
152
    /**
153
     * @return mixed[]
154
     */
155
    public function askForEnvironments(): array
156
    {
157
        $environments = Aenthill::dispatch('ENVIRONMENT');
158
        $choosen = $this->choiceQuestion('Environments', array_keys($environments))
159
            ->askMultipleChoiceQuestion();
160
        $this->output->writeln('<info>Environments: ' . implode($choosen, ', ') . '</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

160
        $this->output->writeln('<info>Environments: ' . /** @scrutinizer ignore-call */ implode($choosen, ', ') . '</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...
161
        $this->spacer();
162
        return array_filter($environments, function (string $key) use ($choosen) {
163
            return isset($choosen[$key]);
164
        }, ARRAY_FILTER_USE_KEY);
165
    }
166
167
    public function askForTag(string $dockerHubImage, string $applicationName = ''): string
168
    {
169
        $registryClient = new RegistryClient();
170
        $availableVersions = $registryClient->getImageTagsOnDockerHub($dockerHubImage);
171
172
        $tagsAnalyzer = new TagsAnalyzer();
173
        $proposedTags = $tagsAnalyzer->filterBestTags($availableVersions);
174
        $default = $proposedTags[0] ?? null;
175
        $this->output->writeln("Please choose your $applicationName version.");
176
        if (!empty($proposedTags)) {
177
            $this->output->writeln('Possible values include: <info>' . \implode('</info>, <info>', $proposedTags) . '</info>');
178
        }
179
        $this->output->writeln('Enter "v" to view all available versions, "?" for help');
180
        $question = new SymfonyQuestion(
181
            "Select your $applicationName version [$default]: ",
182
            $default
183
        );
184
        $question->setAutocompleterValues($availableVersions);
185
        $question->setValidator(function (string $value) use ($availableVersions, $dockerHubImage) {
186
            $value = trim($value);
187
188
            if ($value === 'v') {
189
                $this->output->writeln('Available versions: <info>' . \implode('</info>, <info>', $availableVersions) . '</info>');
190
                return 'v';
191
            }
192
193
            if ($value === '?') {
194
                $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.");
195
                return '?';
196
            }
197
198
            if (!\in_array($value, $availableVersions)) {
199
                throw new \InvalidArgumentException("Version '$value' is invalid.");
200
            }
201
202
            return $value;
203
        });
204
        do {
205
            $version = $this->questionHelper->ask($this->input, $this->output, $question);
206
        } while ($version === 'v' || $version === '?');
207
208
        $this->output->writeln("<info>Selected version: $version</info>");
209
        $this->spacer();
210
211
        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...
212
    }
213
214
    public function askForServiceName(string $serviceName, string $applicationName = ''): string
215
    {
216
        $answer = $this->question("$applicationName service name")
217
            ->setDefault($serviceName)
218
            ->compulsory()
219
            ->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.')
220
            ->setValidator(function (string $value) {
221
                $value = trim($value);
222
                if (!\preg_match('/^[a-zA-Z0-9_.-]+$/', $value)) {
223
                    throw new \InvalidArgumentException('Invalid service name "' . $value . '". Service names can contain alphanumeric characters, and "_", ".", "-".');
224
                }
225
                return $value;
226
            })
227
            ->ask();
228
229
        $this->output->writeln("<info>Service name: $answer</info>");
230
        $this->spacer();
231
232
        return $answer;
233
    }
234
}
235