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

AentHelper::askForCICD()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 8
nc 1
nop 0
dl 0
loc 11
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
    public function askForEnvironments(): array
153
    {
154
        $environments = Aenthill::dispatch('ENVIRONMENT');
155
        $choosen = $this->choiceQuestion('Environments', array_keys($environments))
156
            ->askMultipleChoiceQuestion();
157
        $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

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