Passed
Push — code-quality-badges ( 1c308c...b32c3e )
by Christian
02:02 queued 26s
created

InstallCommand::configureNetworking()   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 0
dl 0
loc 6
rs 9.4285
c 0
b 0
f 0
1
<?php declare(strict_types=1);
2
3
namespace Cocotte\Command;
4
5
use Cocotte\Console\AbstractCommand;
6
use Cocotte\Console\DocumentedCommand;
7
use Cocotte\Console\Style;
8
use Cocotte\DigitalOcean\ApiToken;
9
use Cocotte\DigitalOcean\ApiTokenOptionProvider;
10
use Cocotte\DigitalOcean\NetworkingConfigurator;
11
use Cocotte\Environment\LazyEnvironment;
12
use Cocotte\Help\DefaultExamples;
13
use Cocotte\Help\FromEnvExamples;
14
use Cocotte\Host\HostMount;
15
use Cocotte\Host\HostMountRequired;
16
use Cocotte\Machine\MachineCreator;
17
use Cocotte\Machine\MachineIp;
18
use Cocotte\Machine\MachineName;
19
use Cocotte\Machine\MachineNameOptionProvider;
20
use Cocotte\Machine\MachineStoragePath;
21
use Cocotte\Shell\ProcessRunner;
22
use Cocotte\Template\Traefik\TraefikCreator;
23
use Cocotte\Template\Traefik\TraefikDeploymentValidator;
24
use Cocotte\Template\Traefik\TraefikHostname;
25
use Cocotte\Template\Traefik\TraefikHostnameOptionProvider;
26
use Cocotte\Template\Traefik\TraefikPassword;
27
use Cocotte\Template\Traefik\TraefikPasswordOptionProvider;
28
use Cocotte\Template\Traefik\TraefikUsername;
29
use Cocotte\Template\Traefik\TraefikUsernameOptionProvider;
30
use Symfony\Component\Console\Input\InputInterface;
31
use Symfony\Component\Console\Input\InputOption;
32
use Symfony\Component\Console\Output\OutputInterface;
33
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
34
use Symfony\Component\Process\Process;
35
36
final class InstallCommand extends AbstractCommand implements LazyEnvironment, HostMountRequired, DocumentedCommand
37
{
38
    /**
39
     * @var MachineCreator
40
     */
41
    private $machineCreator;
42
43
    /**
44
     * @var TraefikCreator
45
     */
46
    private $traefikCreator;
47
48
    /**
49
     * @var Style
50
     */
51
    private $style;
52
53
    /**
54
     * @var MachineName
55
     */
56
    private $machineName;
57
58
    /**
59
     * @var TraefikHostname
60
     */
61
    private $traefikHostname;
62
63
    /**
64
     * @var EventDispatcherInterface
65
     */
66
    private $eventDispatcher;
67
68
    /**
69
     * @var NetworkingConfigurator
70
     */
71
    private $networkingConfigurator;
72
73
    /**
74
     * @var ProcessRunner
75
     */
76
    private $processRunner;
77
78
    /**
79
     * @var HostMount
80
     */
81
    private $hostMount;
82
83
    /**
84
     * @var TraefikDeploymentValidator
85
     */
86
    private $traefikDeploymentValidator;
87
88
    /**
89
     * @var MachineIp
90
     */
91
    private $machineIp;
92
    /**
93
     * @var FromEnvExamples
94
     */
95
    private $fromEnvExamples;
96
97
    public function __construct(
98
        MachineCreator $machineCreator,
99
        TraefikCreator $traefikCreator,
100
        Style $style,
101
        MachineName $machineName,
102
        TraefikHostname $traefikHostname,
103
        EventDispatcherInterface $eventDispatcher,
104
        NetworkingConfigurator $networkingConfigurator,
105
        ProcessRunner $processRunner,
106
        HostMount $hostMount,
107
        TraefikDeploymentValidator $traefikDeploymentValidator,
108
        MachineIp $machineIp,
109
        FromEnvExamples $fromEnvExamples
110
    ) {
111
        $this->machineCreator = $machineCreator;
112
        $this->traefikCreator = $traefikCreator;
113
        $this->style = $style;
114
        $this->machineName = $machineName;
115
        $this->traefikHostname = $traefikHostname;
116
        $this->eventDispatcher = $eventDispatcher;
117
        $this->networkingConfigurator = $networkingConfigurator;
118
        $this->processRunner = $processRunner;
119
        $this->hostMount = $hostMount;
120
        $this->traefikDeploymentValidator = $traefikDeploymentValidator;
121
        $this->machineIp = $machineIp;
122
        $this->fromEnvExamples = $fromEnvExamples;
123
        parent::__construct();
124
    }
125
126
    public function lazyEnvironmentValues(): array
127
    {
128
        return [
129
            ApiToken::class,
130
            MachineName::class,
131
            MachineStoragePath::class,
132
            TraefikHostname::class,
133
            TraefikPassword::class,
134
            TraefikUsername::class,
135
        ];
136
    }
137
138
    public function optionProviders(): array
139
    {
140
        return [
141
            ApiTokenOptionProvider::class,
142
            MachineNameOptionProvider::class,
143
            TraefikHostnameOptionProvider::class,
144
            TraefikUsernameOptionProvider::class,
145
            TraefikPasswordOptionProvider::class,
146
        ];
147
    }
148
149
    protected function eventDispatcher(): EventDispatcherInterface
150
    {
151
        return $this->eventDispatcher;
152
    }
153
154
    protected function doConfigure(): void
155
    {
156
        $this->setName('install')
157
            ->addOption('dry-run',
158
                null,
159
                InputOption::VALUE_NONE,
160
                'Validate all options but do not proceed with installation.')
161
            ->setDescription($this->description())
162
            ->setHelp(
163
                $this->formatHelp($this->description(),
164
                    (new DefaultExamples)->install(),
165
                    (new DefaultExamples)->installInteractive()
166
                )
167
            );
168
    }
169
170
    protected function doExecute(InputInterface $input, OutputInterface $output)
171
    {
172
        if ($input->getOption('dry-run')) {
173
            $this->style->writeln(
174
                "Would have created a Docker machine named '{$this->machineName}' on Digital Ocean."
175
            );
176
177
            return;
178
        }
179
180
        $this->confirm();
181
        $this->createMachine();
182
        $this->createTraefikTemplate();
183
        $this->configureNetworking();
184
        $this->deployTraefik();
185
        $this->waitForTraefikReady();
186
187
        $this->processRunner->mustRun(new Process('./bin/logs -t', $this->traefikCreator->hostAppPath()));
188
189
        $this->style->complete($this->completeMessage());
190
        $this->style->writeln($this->command());
191
    }
192
193
    private function confirm(): void
194
    {
195
        if (!$this->style->confirm(
196
            "You are about to create a Docker machine named '<options=bold>{$this->machineName->toString()}</>' on Digital Ocean \n".
197
            " and install the Traefik reverse proxy on it with hostname '<options=bold>{$this->traefikHostname->toString()}</>'.\n".
198
            " This action may take a few minutes."
199
        )) {
200
            throw new \Exception('Cancelled');
201
        };
202
    }
203
204
    private function description(): string
205
    {
206
        return 'Create a <options=bold>Docker</> machine on <options=bold>Digital Ocean</> and '.
207
            'install the <options=bold>Traefik</> reverse proxy on it.';
208
    }
209
210
    private function completeMessage(): array
211
    {
212
        return [
213
            "Installation successful.",
214
            "You can now:\n".
215
            "- Visit your Traefik UI at <options=bold>https://{$this->traefikHostname->toString()}</>\n".
216
            "- Use docker-machine commands (e.g. <options=bold>docker-machine -s machine ssh {$this->machineName}</>)\n".
217
            "- Deploy a static website to your cloud machine with the command below.",
218
        ];
219
    }
220
221
    private function command(): string
222
    {
223
        $command = $this->fromEnvExamples->staticSite(
224
            null,
225
            'site1',
226
            'site1.'.$this->traefikHostname->domainName()
227
        );
228
229
        return <<<EOF
230
<options=bold,underscore>Run this command to create a static site:</>
231
{$command}
232
233
EOF;
234
    }
235
236
    private function createMachine(): void
237
    {
238
        $this->style->writeln("Creating a Docker machine named '{$this->machineName}' on Digital Ocean.");
239
        $this->machineCreator->create();
240
    }
241
242
    private function createTraefikTemplate(): void
243
    {
244
        $this->style->writeln("Creating Traefik template in {$this->hostMount->sourcePath()}/traefik");
245
        $this->traefikCreator->create();
246
    }
247
248
    private function configureNetworking(): void
249
    {
250
        $this->style->writeln("Configuring networking for {$this->traefikHostname->toString()}");
251
        $this->networkingConfigurator->configure(
252
            $this->traefikHostname->toHostnameCollection(),
253
            $this->machineIp->toIP()
254
        );
255
    }
256
257
    private function deployTraefik(): void
258
    {
259
        $this->style->writeln('Deploying Traefik to cloud machine');
260
//        $this->processRunner->run(new Process('./bin/reset-prod 2>/dev/stdout', $this->traefikCreator->hostAppPath()));
261
        $this->processRunner->mustRun(new Process('./bin/prod 2>/dev/stdout', $this->traefikCreator->hostAppPath()));
262
    }
263
264
    private function waitForTraefikReady(): void
265
    {
266
        $this->style->writeln('Waiting for Traefik to start');
267
        $this->traefikDeploymentValidator->validate();
268
    }
269
}