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 ( 9a387d...a8e4c8 )
by Sam
13:14
created

Module::add()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 2.0625

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 0
loc 7
ccs 3
cts 4
cp 0.75
rs 10
cc 2
nc 2
nop 2
crap 2.0625
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
    /**
135
     * @var string[] List of console commands that are executed upon container launch.
136
     */
137
    public $initializationCommands = [];
138
    /**
139
     * @return string A PHP-FPM config file.
140
     */
141 3
    protected function createFpmConfig()
142
    {
143 3
        $config = [];
144
        // Add global directives.
145 3
        $config[] = '[global]';
146 3
        foreach ($this->fpmConfig as $key => $value) {
147 3
            $config[] = "$key = $value";
148
        }
149
150
        // Add pool directives.
151 3
        $poolConfig = $this->poolConfig;
152 3
        foreach($this->phpConfig as $key => $value) {
153 3
            $poolConfig["php_admin_value[$key]"] = $value;
154
        }
155
156 3
        $config[] = '[www]';
157 3
        foreach ($poolConfig as $key => $value) {
158 3
            $config[] = "$key = $value";
159
        }
160
161 3
        return \implode("\n", $config);
162
    }
163
164
    /**
165
     * @return string A shell script that checks for existence of (non-empty) variables and runs php-fpm.
166
     */
167 3
    protected function createEntrypoint(): string
168
    {
169
        // Get the route.
170 3
        $route = "{$this->getUniqueId()}/migrate/up";
171 3
        $script = "/project/{$this->getConsoleEntryScript()}";
172 3
        $result = [];
173 3
        $result[] = '#!/bin/sh';
174
        // Check for variables.
175 3
        foreach($this->environmentVariables as $name) {
176
            $result[] = \strtr('if [ -z "${name}" ]; then echo "Variable \${name} is required."; exit 1; fi', [
177
                '{name}' => $name
178
            ]);
179
        }
180
181
        // Check if runtime directory is writable.
182 3
        $result[] = <<<SH
183
    su nobody -s /bin/touch /runtime/testfile && rm /runtime/testfile;
184
    if [ $? -ne 0 ]; then
185
      echo Runtime directory is not writable;
186
      exit 1
187
    fi
188
SH;
189
190
191
192
        // Check if runtime is a tmpfs.
193 3
        $message = Console::ansiFormat('/runtime should really be a tmpfs.', [Console::FG_RED]);
194 3
        $result[] = <<<SH
195
mount | grep '/runtime type tmpfs';
196
if [ $? -ne 0 ]; then
197 3
  echo $message; 
198
fi
199
SH;
200 3
        $result[] = 'jq -n env > /runtime/env.json';
201
202 3
        if ($this->runMigrations) {
203
            $result[] = <<<SH
204
ATTEMPTS=0
205
while [ \$ATTEMPTS -lt 10 ]; do
206
  # First run migrations.
207
  $script $route --interactive=0
208
  if [ $? -eq 0 ]; then
209
    echo "Migrations done";
210
    break;
211
  fi 
212
  echo "Failed to run migrations, retrying in 10s.";
213
  sleep 10;
214
  let ATTEMPTS=ATTEMPTS+1
215
done
216
217
if [ \$ATTEMPTS -gt 9 ]; then
218
  echo "Migrations failed.."
219
  exit 1;
220
fi
221
SH;
222
        }
223
224 3
        foreach($this->initializationCommands as $route) {
225
            $result[] = "$script $route --interactive=0 || exit";
226
        }
227 3
        $result[] = 'exec php-fpm7 --force-stderr --fpm-config /php-fpm.conf';
228 3
        return \implode("\n", $result);
229
    }
230
231
    /**
232
     * @param string $version This is stored in the VERSION environment variable.
233
     * @return Context
234
     * @throws InvalidConfigException
235
     */
236 3
    public function createBuildContext(string $version): Context
237
    {
238 3
        $builder = new ContextBuilder();
239
240
        /**
241
         * BEGIN COMPOSER
242
         */
243 3
        $builder->from('composer');
244 3
        $builder->addFile('/build/composer.json', \Yii::getAlias($this->composerFilePath) .'/composer.json');
245 3
        if (\file_exists(\Yii::getAlias($this->composerFilePath) . '/composer.lock')) {
246 3
            $builder->addFile('/build/composer.lock', \Yii::getAlias($this->composerFilePath) . '/composer.lock');
247
        }
248
249 3
        $builder->run('composer global require hirak/prestissimo');
250
251 3
        $builder->run('cd /build && composer install --no-dev --no-autoloader --ignore-platform-reqs --prefer-dist && rm -rf /root/.composer');
252
253
254
        // Add the actual source code.
255 3
        $root = \Yii::getAlias('@app');
256 3
        if (!\is_string($root)) {
257
            throw new \Exception('Alias @app must be defined.');
258
        }
259
260 3
        $builder->addFile('/build/' . \basename($root), $root);
261 3
        $builder->run('cd /build && composer dumpautoload -o');
262
        /**
263
         * END COMPOSER
264
         */
265
266
267 3
        $builder->from('alpine:edge');
268 3
        $packages = $this->packages;
269
270 3
        foreach ($this->extensions as $extension) {
271
            $packages[] = "php7-$extension";
272
        }
273 3
        $builder->run('apk add --update --no-cache ' . \implode(' ', $packages));
274 3
        $builder->run('mkdir /runtime && chown nobody:nobody /runtime');
275 3
        $builder->volume('/runtime');
276 3
        $builder->copy('--from=0 /build', '/project');
277 3
        $builder->add('/entrypoint.sh', $this->createEntrypoint());
278 3
        $builder->run('chmod +x /entrypoint.sh');
279 3
        $builder->add('/php-fpm.conf', $this->createFpmConfig());
280 3
        $builder->run("php-fpm7 --force-stderr --fpm-config /php-fpm.conf -t");
281 3
        $builder->entrypoint('["/sbin/tini", "--", "/entrypoint.sh"]');
282 3
        $builder->env('VERSION', $version);
283
284
        // Test if we can run a console command.
285 3
        if (\stripos($this->getConsoleEntryScript(), 'codecept') === false) {
286
            $script = "[ -f /project/{$this->getConsoleEntryScript()} ]";
287
            $builder->run($script);
288
        }
289 3
        return $builder->getContext();
290
    }
291
292 3
    public function getLock(int $timeout = 0)
293
    {
294 3
        if ($this->has('mutex')) {
295 1
            $mutex = $this->get('mutex');
296 1
            if ($mutex instanceof Mutex
297 1
                && $mutex->acquire(__CLASS__, $timeout)
298
            ) {
299
                \register_shutdown_function(function() use ($mutex): void {
300
                    $mutex->release(__CLASS__);
301 1
                });
302 1
                return true;
303
            }
304
        }
305 3
        return false;
306
    }
307
308
    /**
309
     * @throws InvalidConfigException in case the app is not configured as expected
310
     * @return string the relative path of the (console) entry script with respect to the project (not app) root.
311
     */
312 3
    public function getConsoleEntryScript(): string
313
    {
314 3
        $full = \array_slice(\debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), -1)[0]['file'];
315 3
        $relative = \strtr($full, [\dirname(\Yii::getAlias('@app')) => '']);
316 3
        if ($relative === $full){
317
            throw new InvalidConfigException("The console entry script must be located inside the @app directory.");
318
        }
319 3
        return \ltrim($relative, '/');
320
    }
321
322
323 1
    public function __set($name, $value): void
324
    {
325 1
        if (\strncmp($name, 'additional', 10) === 0) {
326 1
            $this->add(\lcfirst(\substr($name, 10)), $value);
327
        } else {
328
            parent::__set($name, $value);
329
        }
330
    }
331
332 1
    private function add($name, array $value): void
333
    {
334 1
        if (!\property_exists($this, $name)) {
335
            throw new UnknownPropertyException("Unknown property $name");
336
        }
337 1
        $this->$name = ArrayHelper::merge($this->$name, $value);
338
    }
339
}
340