GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Push — master ( 126b63...1031f8 )
by Sam
03:41
created

Module::add()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2.032

Importance

Changes 0
Metric Value
dl 0
loc 7
ccs 4
cts 5
cp 0.8
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 2
crap 2.032
1
<?php
2
declare(strict_types=1);
3
4
namespace SamIT\Yii2\PhpFpm;
5
6
use Docker\Context\Context;
7
use Docker\Context\ContextBuilder;
8
use yii\base\InvalidConfigException;
9
use yii\base\UnknownPropertyException;
10
use yii\helpers\ArrayHelper;
11
use yii\helpers\Console;
12
use yii\mutex\Mutex;
13
14
/**
15
 * Class Module
16
 * @package SamIT\Yii2\PhpFpm
17
 * @property-write string[] $additionalPackages
18
 * @property-write string[] $additionalExtensions
19
 * @property-write string|int[] $additionalPoolConfig
20
 * @property-write string[] $additionalPhpConfig
21
 * @property-write string[] $additionalFpmConfig
22
 */
23
class Module extends \yii\base\Module
24
{
25
26
    /**
27
     * @var bool Whether the container should attempt to run migrations on launch.
28
     */
29
    public $runMigrations = false;
30
31
    /**
32
     * @var bool whether migrations should acquire a lock.
33
     * It must be configured in the 'mutex' component of this module or the application
34
     * Note that this mutex must be shared between all instances of your application.
35
     * Consider using something like redis or mysql mutex.
36
     */
37
    public $migrationsUseMutex = true;
38
39
    /**
40
     * The variables will be written to /runtime/env.json as JSON, where your application can read them.
41
     * @var string[] List of required environment variables. If one is missing the container will exit.
42
     *
43
     */
44
    public $environmentVariables = [];
45
46
    /**
47
     * @var array Pool directives
48
     * @see http://php.net/manual/en/install.fpm.configuration.php
49
     *
50
     */
51
    public $poolConfig = [
52
        'user' => 'nobody',
53
        'group' => 'nobody',
54
        'listen' => 9000,
55
        'pm' => 'dynamic',
56
        'pm.max_children' => 40,
57
        'pm.start_servers' => 3,
58
        'pm.min_spare_servers' => 1,
59
        'pm.max_spare_servers' => 3,
60
        'access.log' => '/proc/self/fd/2',
61
        'clear_env' => 'yes',
62
        'catch_workers_output' => 'yes'
63
    ];
64
65
    /**
66
     * @var array PHP configuration, supplied via php_admin_value in fpm config.
67
     */
68
    public $phpConfig = [
69
        'upload_max_filesize' => '20M',
70
        'post_max_size' => '25M'
71
    ];
72
73
    /**
74
     * @var array Global directives
75
     * @see http://php.net/manual/en/install.fpm.configuration.php
76
     *
77
     */
78
    public $fpmConfig = [
79
        'error_log' => '/proc/self/fd/2',
80
        'daemonize' => 'no',
81
    ];
82
83
    /**
84
     * List of OS packages to install
85
     */
86
    public $packages = [
87
        'php7',
88
        'php7-fpm',
89
        'tini',
90
        'ca-certificates',
91
        /**
92
         * @see https://stedolan.github.io/jq/
93
         * This is used for converting the env to JSON.
94
         */
95
        'jq'
96
    ];
97
98
    /**
99
     * List of php extensions to install
100
     */
101
    public $extensions = [
102
        'ctype',
103
        'gd',
104
        'iconv',
105
        'intl',
106
        'json',
107
        'mbstring',
108
        'session',
109
        'pdo_mysql',
110
        'session',
111
        'curl'
112
    ];
113
114
    /**
115
     * @var string The name of the created image.
116
     */
117
    public $image;
118
119
    /**
120
     * @var string The tag of the created image.
121
     */
122
    public $tag = 'latest';
123
124
    /**
125
     * @var bool wheter to push successful builds.
126
     */
127
    public $push = false;
128
129
    /**
130
     * @var string Location of composer.json / composer.lock
131
     */
132
    public $composerFilePath = '@app/../';
133
    /**
134
     * @return string A PHP-FPM config file.
135
     */
136 3
    protected function createFpmConfig()
137
    {
138 3
        $config = [];
139
        // Add global directives.
140 3
        if (!empty($this->fpmConfig)) {
141 3
            $config[] = '[global]';
142 3
            foreach ($this->fpmConfig as $key => $value) {
143 3
                $config[] = "$key = $value";
144
            }
145
        }
146
147
        // Add pool directives.
148 3
        $poolConfig = $this->poolConfig;
149 3
        foreach($this->phpConfig as $key => $value) {
150 3
            $poolConfig["php_admin_value[$key]"] = $value;
151
        }
152
153 3
        if (!empty($poolConfig)) {
154 3
            $config[] = '[www]';
155 3
            foreach ($poolConfig as $key => $value) {
156 3
                $config[] = "$key = $value";
157
            }
158
        }
159
160 3
        return \implode("\n", $config);
161
    }
162
163
    /**
164
     * @return string A shell script that checks for existence of (non-empty) variables and runs php-fpm.
165
     */
166 3
    protected function createEntrypoint(): string
167
    {
168
        // Get the route.
169 3
        $route = "{$this->getUniqueId()}/migrate/up";
170 3
        $script = "/project/{$this->getConsoleEntryScript()}";
171 3
        $result = [];
172 3
        $result[] = '#!/bin/sh';
173
        // Check for variables.
174 3
        foreach($this->environmentVariables as $name) {
175
            $result[] = \strtr('if [ -z "${name}" ]; then echo "Variable \${name} is required."; exit 1; fi', [
176
                '{name}' => $name
177
            ]);
178
        }
179
180
        // Check if runtime directory is writable.
181 3
        $result[] = <<<SH
182
    su nobody -s /bin/touch /runtime/testfile && rm /runtime/testfile;
183
    if [ $? -ne 0 ]; then
184
      echo Runtime directory is not writable;
185
      exit 1
186
    fi
187
SH;
188
189
190
191
        // Check if runtime is a tmpfs.
192 3
        $message = Console::ansiFormat('/runtime should really be a tmpfs.', [Console::FG_RED]);
193 3
        $result[] = <<<SH
194
mount | grep '/runtime type tmpfs';
195
if [ $? -ne 0 ]; then
196 3
  echo $message; 
197
fi
198
SH;
199 3
        $result[] = 'jq -n env > /runtime/env.json';
200
201 3
        if ($this->runMigrations) {
202
            $result[] = <<<SH
203
ATTEMPTS=0
204
while [ \$ATTEMPTS -lt 10 ]; do
205
  # First run migrations.
206
  $script $route --interactive=0
207
  if [ $? -eq 0 ]; then
208
    echo "Migrations done";
209
    break;
210
  fi
211
  echo "Failed to run migrations, retrying in 10s.";
212
  sleep 10;
213
  let ATTEMPTS=ATTEMPTS+1
214
done
215
216
if [ \$ATTEMPTS -gt 9 ]; then
217
  echo "Migrations failed.."
218
  exit 1;
219
fi
220
SH;
221
        }
222
223 3
        $result[] = 'exec php-fpm7 --force-stderr --fpm-config /php-fpm.conf';
224 3
        return \implode("\n", $result);
225
    }
226
227 3
    public function createBuildContext(): Context
228
    {
229 3
        $builder = new ContextBuilder();
230
231
        /**
232
         * BEGIN COMPOSER
233
         */
234 3
        $builder->from('composer');
235 3
        $builder->addFile('/build/composer.json', \Yii::getAlias($this->composerFilePath) .'/composer.json');
236 3
        if (\file_exists(\Yii::getAlias($this->composerFilePath) . '/composer.lock')) {
237 3
            $builder->addFile('/build/composer.lock', \Yii::getAlias($this->composerFilePath) . '/composer.lock');
238
        }
239
240 3
        $builder->run('cd /build && composer install --no-dev --no-autoloader --ignore-platform-reqs --prefer-dist && rm -rf /root/.composer');
241
242
243
        // Add the actual source code.
244 3
        $root = \Yii::getAlias('@app');
245 3
        if (!\is_string($root)) {
246
            throw new \Exception('Alias @app must be defined.');
247
        }
248 3
        $builder->addFile('/build/' . \basename($root), $root);
249 3
        $builder->run('cd /build && composer dumpautoload -o');
250
        /**
251
         * END COMPOSER
252
         */
253
254
255 3
        $builder->from('alpine:edge');
256 3
        $packages = $this->packages;
257
258 3
        foreach ($this->extensions as $extension) {
259
            $packages[] = "php7-$extension";
260
        }
261 3
        $builder->run('apk add --update --no-cache ' . \implode(' ', $packages));
262 3
        $builder->run('mkdir /runtime && chown nobody:nobody /runtime');
263 3
        $builder->volume('/runtime');
264 3
        $builder->copy('--from=0 /build', '/project');
265 3
        $builder->add('/entrypoint.sh', $this->createEntrypoint());
266 3
        $builder->run('chmod +x /entrypoint.sh');
267 3
        $builder->add('/php-fpm.conf', $this->createFpmConfig());
268 3
        $builder->run("php-fpm7 --force-stderr --fpm-config /php-fpm.conf -t");
269 3
        $builder->entrypoint('["/sbin/tini", "--", "/entrypoint.sh"]');
270
271
        // Test if we can run a console command.
272 3
        $script = "[ -f /project/{$this->getConsoleEntryScript()} ]";
273 3
        $builder->run($script);
274
275
276 3
        return $builder->getContext();
277
    }
278
279 3
    public function getLock(int $timeout = 0)
280
    {
281 3
        if ($this->has('mutex')) {
282 1
            $mutex = $this->get('mutex');
283 1
            if ($mutex instanceof Mutex
284 1
                && $mutex->acquire(__CLASS__, $timeout)
285
            ) {
286 1
                \register_shutdown_function(function() use ($mutex): void {
287
                    $mutex->release(__CLASS__);
288 1
                });
289 1
                return true;
290
            }
291
        }
292 3
        return false;
293
    }
294
295
    /**
296
     * @throws InvalidConfigException in case the app is not configured as expected
297
     * @return string the relative path of the (console) entry script with respect to the project (not app) root.
298
     */
299 3
    public function getConsoleEntryScript(): string
300
    {
301 3
        $full = \array_slice(\debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), -1)[0]['file'];
302 3
        $relative = \strtr($full, [\dirname(\Yii::getAlias('@app')) => '']);
303 3
        if ($relative === $full){
304
            throw new InvalidConfigException("The console entry script must be located inside the @app directory.");
305
        }
306 3
        return \ltrim($relative, '/');
307
    }
308
309
310 1
    public function __set($name, $value)
311
    {
312 1
        if (strncmp($name, 'additional', 10) === 0) {
313 1
            $this->add(lcfirst(substr($name, 10)), $value);
314
        } else {
315
            parent::__set($name, $value);
316
        }
317 1
    }
318
319 1
    private function add($name, array $value)
320
    {
321 1
        if (!property_exists($this, $name)) {
322
            throw new UnknownPropertyException("Unknown property $name");
323
        }
324 1
        $this->$name = ArrayHelper::merge($this->$name, $value);
325 1
    }
326
}
327