Issues (16)

installer/src/Command/InstallCommand.php (1 issue)

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()));
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
259
        $this->processRunner->mustRun(Process::fromShellCommandline('./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(Process::fromShellCommandline('./bin/logs -t', $this->traefikCreator->hostAppPath()));
271
    }
272
}
273