AbstractDeployer::doDeploy()   B
last analyzed

Complexity

Conditions 2
Paths 8

Size

Total Lines 24
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 2
eloc 16
nc 8
nop 0
dl 0
loc 24
rs 8.9713
c 0
b 0
f 0
1
<?php
2
namespace Bencagri\Artisan\Deployer\Deployer;
3
4
use Bencagri\Artisan\Deployer\Configuration\ConfigurationAdapter;
5
use Bencagri\Artisan\Deployer\Configuration\Option;
6
use Bencagri\Artisan\Deployer\Context;
7
use Bencagri\Artisan\Deployer\Helper\Str;
8
use Bencagri\Artisan\Deployer\Logger;
9
use Bencagri\Artisan\Deployer\Requirement\AbstractRequirement;
10
use Bencagri\Artisan\Deployer\Server\Property;
11
use Bencagri\Artisan\Deployer\Server\Server;
12
use Bencagri\Artisan\Deployer\Server\ServerRepository;
13
use Bencagri\Artisan\Deployer\Task\Task;
14
use Bencagri\Artisan\Deployer\Task\TaskCompleted;
15
use Bencagri\Artisan\Deployer\Task\TaskRunner;
16
17
abstract class AbstractDeployer
18
{
19
    /** @var Context */
20
    private $context;
21
    /** @var TaskRunner */
22
    private $taskRunner;
23
    /** @var Logger */
24
    private $logger;
25
    /** @var ConfigurationAdapter */
26
    private $config;
27
28
    abstract public function getRequirements() : array;
29
30
    abstract public function deploy();
31
32
    abstract public function cancelDeploy();
33
34
    abstract public function rollback();
35
36
    final public function getConfig(string $name)
37
    {
38
        return $this->config->get($name);
39
    }
40
41
    final public function doDeploy() : void
42
    {
43
        try {
44
            $this->log('Executing <hook>beforeStartingDeploy</> hook');
45
            $this->beforeStartingDeploy();
46
            $this->log('<h1>Starting the deployment</>');
47
48
            $this->deploy();
49
50
            $this->log('Executing <hook>beforeFinishingDeploy</> hook');
51
            $this->beforeFinishingDeploy();
52
            $this->log('<h1>Finishing the deployment</>');
53
        } catch (\Exception $e) {
54
            $this->log('<error>[ERROR] Cancelling the deployment and reverting the changes</>');
55
            $this->log(sprintf('<error>A log file with all the error details has been generated in %s</>', $this->context->getLogFilePath()));
56
57
            $this->log('Executing <hook>beforeCancelingDeploy</> hook');
58
            $this->beforeCancelingDeploy();
59
            $this->cancelDeploy();
60
61
            throw $e;
62
        }
63
64
        $this->log(sprintf('<success>[OK] Deployment was successful</>'));
65
    }
66
67
    final public function doRollback() : void
68
    {
69
        try {
70
            $this->log('Executing <hook>beforeStartingRollback</> hook');
71
            $this->beforeStartingRollback();
72
            $this->log('<h1>Starting the rollback</>');
73
74
            $this->rollback();
75
76
            $this->log('Executing <hook>beforeFinishingRollback</> hook');
77
            $this->beforeFinishingRollback();
78
            $this->log('<h1>Finishing the rollback</>');
79
        } catch (\Exception $e) {
80
            $this->log('<error>[ERROR] The roll back failed because of the following error</>');
81
            $this->log(sprintf('<error>A log file with all the error details has been generated in %s</>', $this->context->getLogFilePath()));
82
83
            $this->log('Executing <hook>beforeCancelingRollback</> hook');
84
            $this->beforeCancelingRollback();
85
86
            throw $e;
87
        }
88
89
        $this->log(sprintf('<success>[OK] Rollback was successful</>'));
90
    }
91
92
    public function beforeStartingDeploy()
93
    {
94
        $this->log('<h3>Nothing to execute</>');
95
    }
96
97
    public function beforeCancelingDeploy()
98
    {
99
        $this->log('<h3>Nothing to execute</>');
100
    }
101
102
    public function beforeFinishingDeploy()
103
    {
104
        $this->log('<h3>Nothing to execute</>');
105
    }
106
107
    public function beforeStartingRollback()
108
    {
109
        $this->log('<h3>Nothing to execute</>');
110
    }
111
112
    public function beforeCancelingRollback()
113
    {
114
        $this->log('<h3>Nothing to execute</>');
115
    }
116
117
    public function beforeFinishingRollback()
118
    {
119
        $this->log('<h3>Nothing to execute</>');
120
    }
121
122
    public function initialize(Context $context) : void
123
    {
124
        $this->context = $context;
125
        $this->logger = new Logger($context);
126
        $this->taskRunner = new TaskRunner($this->context->isDryRun(), $this->logger);
127
        $this->log('<h1>Initializing configuration</>');
128
129
        $this->log('<h2>Processing the configuration options of the deployer class</>');
130
        $this->config = new ConfigurationAdapter($this->configure());
131
        $this->log($this->config);
132
        $this->log('<h2>Checking technical requirements</>');
133
        $this->checkRequirements();
134
    }
135
136
    abstract protected function getConfigBuilder();
137
138
    abstract protected function configure();
139
140
    final protected function getContext() : Context
141
    {
142
        return $this->context;
143
    }
144
145
    final protected function getServers() : ServerRepository
146
    {
147
        return $this->config->get('servers');
148
    }
149
150
    final protected function log(string $message) : void
151
    {
152
        $this->logger->log($message);
153
    }
154
155
    final protected function runLocal(string $command) : TaskCompleted
156
    {
157
        $task = new Task([$this->getContext()->getLocalHost()], $command, $this->getCommandEnvVars());
158
159
        return $this->taskRunner->run($task)[0];
160
    }
161
162
    /**
163
     * @return TaskCompleted[]
164
     */
165
    final protected function runRemote(string $command, array $roles = [Server::ROLE_APP]) : array
166
    {
167
        $task = new Task($this->getServers()->findByRoles($roles), $command, $this->getCommandEnvVars());
168
169
        return $this->taskRunner->run($task);
170
    }
171
172
    final protected function runOnServer(string $command, Server $server) : TaskCompleted
173
    {
174
        $task = new Task([$server], $command, $this->getCommandEnvVars());
175
176
        return $this->taskRunner->run($task)[0];
177
    }
178
179
    // this method checks that any file or directory that goes into "rm -rf" command is
180
    // relative to the project dir. This safeguard will prevent catastrophic errors
181
    // related to removing the wrong file or directory on the server.
182
    final protected function safeDelete(Server $server, array $absolutePaths) : void
183
    {
184
        $deployDir = $server->get(Property::deploy_dir);
185
        $pathsToDelete = [];
186
        foreach ($absolutePaths as $path) {
187
            if (Str::startsWith($path, $deployDir)) {
188
                $pathsToDelete[] = $path;
189
            } else {
190
                $this->log(sprintf('Skipping the unsafe deletion of "%s" because it\'s not relative to the project directory.', $path));
191
            }
192
        }
193
194
        if (empty($pathsToDelete)) {
195
            $this->log('There are no paths to delete.');
196
        }
197
198
        $this->runOnServer(sprintf('rm -rf %s', implode(' ', $pathsToDelete)), $server);
199
    }
200
201
    private function getCommandEnvVars() : array
202
    {
203
        $appEnvironment = $this->getConfig(Option::appEnvironment);
204
        $envVars = null !== $appEnvironment ? ['APP_ENV' => $appEnvironment] : [];
205
206
        return $envVars;
207
    }
208
209
    private function checkRequirements() : void
210
    {
211
        /** @var AbstractRequirement[] $requirements */
212
        $requirements = $this->getRequirements();
213
214
        if (empty($requirements)) {
215
            $this->logger->log('<h3>No requirements defined</>');
216
        }
217
218
        foreach ($requirements as $requirement) {
219
            $this->taskRunner->run($requirement->getChecker());
220
            $this->log($requirement->getMessage());
221
        }
222
    }
223
}
224