Passed
Push — master ( 325a65...e46d77 )
by Christian
02:09
created

InstallCommand::echoTraefikLogs()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
rs 10
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
        $this->echoTraefikLogs();
187
        $this->style->complete($this->completeMessage());
188
        $this->style->writeln($this->command());
189
    }
190
191
    private function confirm(): void
192
    {
193
        if (!$this->style->confirm(
194
            "You are about to create a Docker machine named '<options=bold>{$this->machineName->toString()}</>' on Digital Ocean \n".
195
            " and install the Traefik reverse proxy on it with hostname '<options=bold>{$this->traefikHostname->toString()}</>'.\n".
196
            " This action may take a few minutes."
197
        )) {
198
            throw new \Exception('Cancelled');
199
        };
200
    }
201
202
    private function description(): string
203
    {
204
        return 'Create a <options=bold>Docker</> machine on <options=bold>Digital Ocean</> and '.
205
            'install the <options=bold>Traefik</> reverse proxy on it.';
206
    }
207
208
    private function completeMessage(): array
209
    {
210
        return [
211
            "Installation successful.",
212
            "You can now:\n".
213
            "- Visit your Traefik UI at <options=bold>https://{$this->traefikHostname->toString()}</>\n".
214
            "- Use docker-machine commands (e.g. <options=bold>docker-machine -s machine ssh {$this->machineName}</>)\n".
215
            "- Deploy a static website to your cloud machine with the command below.",
216
        ];
217
    }
218
219
    private function command(): string
220
    {
221
        $command = $this->fromEnvExamples->staticSite(
222
            null,
223
            'site1',
224
            'site1.'.$this->traefikHostname->domainName()
225
        );
226
227
        return <<<EOF
228
<options=bold,underscore>Run this command to create a static site:</>
229
{$command}
230
231
EOF;
232
    }
233
234
    private function createMachine(): void
235
    {
236
        $this->style->writeln("Creating a Docker machine named '{$this->machineName}' on Digital Ocean.");
237
        $this->machineCreator->create();
238
    }
239
240
    private function createTraefikTemplate(): void
241
    {
242
        $this->style->writeln("Creating Traefik template in {$this->hostMount->sourcePath()}/traefik");
243
        $this->traefikCreator->create();
244
    }
245
246
    private function configureNetworking(): void
247
    {
248
        $this->style->writeln("Configuring networking for {$this->traefikHostname->toString()}");
249
        $this->networkingConfigurator->configure(
250
            $this->traefikHostname->toHostnameCollection(),
251
            $this->machineIp->toIP()
252
        );
253
    }
254
255
    private function deployTraefik(): void
256
    {
257
        $this->style->writeln('Deploying Traefik to cloud machine');
258
//        $this->processRunner->run(new Process('./bin/reset-prod 2>/dev/stdout', $this->traefikCreator->hostAppPath()));
259
        $this->processRunner->mustRun(new Process('./bin/prod 2>/dev/stdout', $this->traefikCreator->hostAppPath()));
260
    }
261
262
    private function waitForTraefikReady(): void
263
    {
264
        $this->style->writeln('Waiting for Traefik to start');
265
        $this->traefikDeploymentValidator->validate();
266
    }
267
268
    private function echoTraefikLogs(): void
269
    {
270
        $this->processRunner->mustRun(new Process('./bin/logs -t', $this->traefikCreator->hostAppPath()));
271
    }
272
}