Newer
Older
use Henrywhitaker3\Healthchecks\Exceptions\HealthchecksAccountLimitReachedException;
use Henrywhitaker3\Healthchecks\Exceptions\HealthchecksFailureException;
use Henrywhitaker3\Healthchecks\Exceptions\HealthchecksUnauthorisedException;
use Henrywhitaker3\Healthchecks\Exceptions\HealthchecksUuidNotFoundException;
use Henrywhitaker3\Healthchecks\Healthchecks;
use Henrywhitaker3\Healthchecks\HealthchecksManager;
use LakeDrops\Component\Composer\BaseHandler;
use LakeDrops\DockerTraefik\Traefik;
use LakeDrops\DrupalEnvironment\Handler as DrupalEnvironment;

jurgenhaas
committed
use Symfony\Component\Yaml\Yaml;

jurgenhaas
committed
* Handler for Docker4Drupal.
class Handler extends BaseHandler {
public function configId(): string {
return 'docker4drupal';
}
/**
* {@inheritdoc}
*/
protected function configDefault(): array {

jurgenhaas
committed
$dockerImageProxy = getenv('DOCKER_IMAGE_PREFIX');

jurgenhaas
committed
if (!empty($dockerImageProxy)) {
$dockerImageProxy = str_replace('//', '/', $dockerImageProxy . '/');
}
$projectname = getenv('COMPOSE_PROJECT_NAME');
if (empty($projectname)) {
$projectname = str_replace([' ', '-', '_', '.'], '', basename(getcwd()));
$this->env->put('COMPOSE_PROJECT_NAME', $projectname);
}
$traefik_env = [];
$i = 1;
while ($item = $this->env->receive('traefik_env_' . $i, '', '0')) {
[$key, $value] = explode(':', $item);
$traefik_env[$key] = $value;
$i++;
}

jurgenhaas
committed
'docker_image_prefix' => $dockerImageProxy,
'projectname' => $projectname,
'ci_home' => '/home/gitlab-runner',
'docker0' => [

jurgenhaas
committed
'ip' => ($this->isCiContext() || $this->isLocalDevMode()) ? $this->getDockerGateway() : $this->getLocalIpv4('docker0'),
'proxy' => ($this->isCiContext() || $this->isLocalDevMode()) ? $this->getDockerProxy() : FALSE,
'traefik' => [
'domain' => $this->env->receive('traefik_domain', '', 'docker.localhost'),
'usessl' => $this->env->receive('traefik_usessl', '', '0'),
'port' => $this->env->receive('traefik_port', '', '8000'),
'ports' => $this->env->receive('traefik_ports', '', '8443'),

jurgenhaas
committed
'cert' => $this->env->receive('traefik_cert', ''),
'key' => $this->env->receive('traefik_key', ''),
'portainer' => $this->env->receive('traefik_portainer', '', '0'),

jurgenhaas
committed
'hub_token' => $this->env->receive('traefik_hub_token', ''),
'dns_challenge' => $this->env->receive('traefik_dns_challenge', '', '0'),

jurgenhaas
committed
'dns_challenge_provider' => $this->env->receive('traefik_dns_challenge_provider', ''),
'dns_challenge_resolver' => $this->env->receive('traefik_dns_challenge_resolver', ''),
'live' => [
'root' => '',
'uri' => '',
'host' => '',
'user' => $this->env->receive('live_host_username', 'Remote username for host of the live site', getenv('USER')),
],
'drush' => [
'sql' => [
'tables' => [
'structure' => [
'cache',
'cachetags',
'cache_*',
'history',
'search_*',
'sessions',
'watchdog',
],
'skip' => [
'migration_*',
],
],
],
],
'drupal' => [
'version' => $this->env->receiveGlobal('PHP_VERSION', 'PHP version', '8.2'),
'xdebug' => $this->env->receiveGlobal('PHP_DEBUG', 'PHP debug', '0'),
'coverage' => $this->env->receiveGlobal('PHP_COVERAGE', 'PHP coverage', '0'),
'profiler' => $this->env->receiveGlobal('PHP_PROFILER', 'PHP PROFILER', '0'),

jurgenhaas
committed
'localip' => $this->env->receiveGlobal('LOCAL_IP', 'Local IP', '0'),

jurgenhaas
committed
'dbserver' => [
'type' => 'mariadb',
'version' => '11.2',

jurgenhaas
committed
],
'webserver' => [
'type' => 'apache',
'overwriteconfig' => FALSE,

jurgenhaas
committed
'responseheader' => [
'server' => '',
'strict_transport_security' => 'max-age=31536000; includeSubDomains',
'referrer_policy' => 'same-origin',
'permissions_policy' => 'accelerometer=(), camera=(), geolocation=(), gyroscope=(), microphone=(), payment=(), usb=()',

jurgenhaas
committed
'cross_origin_embedder_policy' => 'credentialless',

jurgenhaas
committed
'cross_origin_opener_policy' => 'same-origin',
'cross_origin_resource_policy' => 'cross-origin',
'x_permitted_cross_domain_policies' => 'none',
],
'enable' => (getenv('LAKEDROPS_BUILD_NG') === 'yes' && in_array(getenv('CI_COMMIT_REF_SLUG'), ['master', 'main'], TRUE)) ? 0 : 1,
'routed' => (getenv('LAKEDROPS_BUILD_NG') === 'yes') ? 0 : 1,
'host' => $this->env->receiveGlobal('MAILPIT_HOST', 'MailPit Host', 'smtp.freesmtpservers.com'),
'port' => $this->env->receiveGlobal('MAILPIT_PORT', 'MailPit Port', '25'),
'username' => $this->env->receiveGlobal('MAILPIT_USERNAME', 'MailPit Username'),
'password' => $this->env->receiveGlobal('MAILPIT_PASSWORD', 'MailPit Password'),
'starttls' => $this->env->receiveGlobal('MAILPIT_STARTTLS', 'MailPit StartTLS'),
'allowinsecure' => $this->env->receiveGlobal('MAILPIT_ALLOW_INSECURE', 'MailPit allow insecure'),
'auth' => $this->env->receiveGlobal('MAILPIT_AUTH', 'MailPit Auth (none|plain|login|cram-md5)'),
'secret' => $this->env->receiveGlobal('MAILPIT_SECRET', 'MailPit Secret'),
'returnpath' => $this->env->receiveGlobal('MAILPIT_RETURNPATH', 'MailPit Bounce Address'),
'recipientallowlist' => $this->env->receiveGlobal('MAILPIT_RECIPIENT_ALLOW_LIST', 'MailPit Regex for allowed recipients'),
],
'symfony_mailer' => [
'enable' => 0,
'host' => '',
'port' => 25,
'username' => '',
'password' => '',
],
'varnish' => [
'enable' => 0,
],
'redis' => [

jurgenhaas
committed
'version' => '7',
'max_memory' => '1GB',
],
'dbbrowser' => [
'type' => 'pma',
],
'solr' => [
'enable' => 0,
'version' => '4.8.0',
],
'node' => [
'enable' => 0,
'key' => '',
'path' => '',
],
'memcached' => [
'enable' => 0,
],
'rsyslog' => [
'enable' => 0,
],
'athenapdf' => [
'enable' => 0,
'key' => '',
],
'blackfire' => [
'enable' => 0,
'id' => '',
'token' => '',
],
'webgrind' => [
'enable' => 0,
],
'selenium' => [
'enable' => 0,
],
'elasticsearch' => [
'enable' => 0,
],
'wkhtmltox' => [
'enable' => 0,
],
'www-data' => [],
'version' => '1.8',
'crontime' => '50 */6 * * *',
'crontimecheck' => '30 23 1 * *',
'crontimecompact' => '30 23 5 * *',
'remoterepo' => FALSE,
'retention' => [
'hourly' => 2,
'daily' => 7,
'weekly' => 8,
'monthly' => 12,
'yearly' => 30,
],
],
'cypress' => [
'enable' => $this->env->receiveGlobal('CYPRESS', 'Cypress', '0'),
'unlighthouse' => [
'enable' => $this->env->receiveGlobal('UNLIGHTHOUSE', 'Unlighthouse', '0'),
'invoiceninja' => [
'enable' => 0,
],

jurgenhaas
committed
'tests' => [

jurgenhaas
committed
],
}
/**
* {@inheritdoc}
*/
protected function postInit(): void {
$this->config->setValue('docker', [
'compose' => [
'version' => $this->readDockerComposeVersion(),
],
], FALSE);
$this->env->put('PHP_VERSION', $this->config->readValue(['php', 'version']), TRUE);
if ($this->isCiContext() || $this->isLocalDevMode()) {
$projectRoot = $this->getDockerMountSource(getenv('CI_PROJECT_DIR'));
}
else {
$projectRoot = getcwd();
}
$php = $this->config->readValue('php');
$traefik = $this->config->readValue('traefik');
// Check if SSH auth sockets are supported.
$ssh_auth_sock = getenv('SSH_AUTH_SOCK');
$php['ssh'] = !empty($ssh_auth_sock);
if ($php['ssh']) {
$php['ssh_auth_sock'] = ($this->isCiContext() || $this->isLocalDevMode()) ?
$this->getDockerMountSource('/ssh-agent') :
'$SSH_AUTH_SOCK';
}
$this->config->setValue('projectroot', $projectRoot, FALSE);
$this->config->setValue('projectrootfiles', $projectRoot, FALSE);
$this->config->setValue('projectrootinvoiceninja', $projectRoot . '/in', FALSE);
$this->config->setValue('projectrootmailpit', $projectRoot . '/mailpit', FALSE);
$this->config->setValue('projectdomain', str_replace('_', '-', $this->config->readValue('projectname')) . '.' . $traefik['domain'], FALSE);
$this->config->setValue('projectprotocol', 'http' . ($traefik['usessl'] ? 's' : ''), FALSE);
$this->config->setValue('socketprotocol', 'ws' . ($traefik['usessl'] ? 's' : ''), FALSE);
$projectport = '';
if ($traefik['usessl'] && (int) $traefik['ports'] !== 443) {
$projectport = ':' . $traefik['ports'];
elseif (!$traefik['usessl'] && (int) $traefik['port'] !== 80) {
$projectport = ':' . $traefik['port'];
$this->config->setValue('projectport', $projectport, FALSE);
$relatedprojectdomains = [];
foreach ($php['related_subdomains'] as $related_subdomain) {
$relatedprojectdomains[] = $related_subdomain . '.' . $traefik['domain'];
}
$this->config->setValue('relatedprojectdomains', $relatedprojectdomains, FALSE);

jurgenhaas
committed
if (!empty($relatedprojectdomains)) {

jurgenhaas
committed
$php['localip'] = TRUE;

jurgenhaas
committed
}

jurgenhaas
committed
$this->config->setValue('php', $php, FALSE);

jurgenhaas
committed
// Get ID of Docker group.
$docker_group_id = trim(shell_exec('stat -c "%g" /var/run/docker.sock'));
$this->config->setValue('docker_group_id', $docker_group_id, FALSE);
if ($alerta_api_key = getenv('ALERTA_APIKEY')) {
$alerta = [
'apikey' => $alerta_api_key,
];
if ($value = getenv('ALERTA_ENVIRONMENT')) {
$alerta['environment'] = $value;
}
if ($value = getenv('ALERTA_PROJECT_ID')) {
$alerta['project_id'] = $value;
}
$this->config->setValue('alerta', $alerta, FALSE);
}
// Turn off basic auth in local dev environment.
if ($this->isLocalDevMode()) {
$this->config->setValue('basicauth', ['enable' => FALSE], FALSE);
}

jurgenhaas
committed
/**
* Update Drupal Project for Docker.
*/
public function updateProject(): void {
// We only do the fancy stuff for developers.
if (!$this->isDevMode()) {
return;
}
$this->io->info('You may have to update your Docker4Drupal environment by running "composer lakedrops:docker4drupal".');
}
public function configureProject(): void {
$this->init();
if ($this->getPackage('lakedrops/drupal-environment')) {
$handler = new DrupalEnvironment($this->composer, $this->io);
$handler->setupLakeDropsProject();
// Update config for production build.
if (getenv('LAKEDROPS_BUILD_NG') === 'yes') {
$config = $handler->getConfig();
$isStaging = !in_array(getenv('CI_COMMIT_REF_SLUG'), ['master', 'main'], TRUE);
$root = '/drupal/' . getenv('CI_PROJECT_ID') . '/' . getenv('CI_COMMIT_BRANCH');
$drupal = $this->config->readValue('drupal');
$drupal['live'] = $config->readValue('live');
$traefik = $this->config->readValue('traefik');
$traefik['usessl'] = 1;
$traefik['ports'] = 443;
$crontabs = $this->config->readValue('crontabs');
$crontabs['enable'] = (array_sum(array_map('count', $crontabs)) > 0);

jurgenhaas
committed
if (!$isStaging) {

jurgenhaas
committed
if ($this->config->readValue(['backup', 'enable'])) {
$crontabs['borgmatic']['Backup'] = [
'schedule' => $this->config->readValue(['backup', 'crontime']),
'command' => 'backup',
];
$crontabs['borgmatic']['Check backup'] = [
'schedule' => $this->config->readValue(['backup', 'crontimecheck']),
'command' => 'check',
];
$crontabs['borgmatic']['Compact backup'] = [
'schedule' => $this->config->readValue(['backup', 'crontimecompact']),
'command' => 'compact',
];

jurgenhaas
committed
}

jurgenhaas
committed
$crontabs['www-data']['MySQL backup'] = [
'schedule' => '5 0 * * *',
'command' => 'cd /var/www/html && /var/www/html/vendor/bin/drush sql:dump --result-file=/var/backups/mysql/drupal.sql',

jurgenhaas
committed
];

jurgenhaas
committed
}
$this->config->setValue('crontabs', $crontabs, FALSE);

jurgenhaas
committed
'staging' => $isStaging,
'docker0' => [
'ip' => 'TRAEFIK-IP-PLACEHOLDER',
'proxy' => 'TRAEFIK-IP-PLACEHOLDER',
],
'drupal' => $drupal,
'traefik' => $traefik,
'projectroot' => $root . '/app',
'projectrootdb' => $root . '/db',
'projectrootfiles' => $root . '/files',
'projectrootinvoiceninja' => $root . '/in',
'projectrootmailpit' => $root . '/mailpit',
'projectname' => 'drupal_' . getenv('PROJECT_NAME') . '_' . getenv('CI_COMMIT_REF_SLUG'),
'projectdomain' => str_replace('_', '-', $this->config->readValue('domain') ?? ''),
'projectprotocol' => 'https',
'projectport' => '',
'extradomains' => $this->config->readValue('aliases') ?? [],
$this->config->setValue($key, $value, FALSE);
}
}
}
$fs = new Filesystem();
$installationManager = $this->composer->getInstallationManager();
$webRoot = $this->config->readValue('webroot');
if ($webRoot !== NULL) {
if (!$fs->exists($webRoot)) {
return;
}
}
else {
$drupalCorePackage = $this->getDrupalCorePackage();
if (!$drupalCorePackage) {
// We are called too early, Drupal core is not available yet.
return;
}
$corePath = $installationManager->getInstallPath($drupalCorePackage);
$webRoot = basename(dirname($corePath));
$this->config->setValue('webRoot', $webRoot, FALSE);
$pluginRoot = $installationManager->getInstallPath($this->getPackage('lakedrops/docker4drupal'));
// If the d8-project-scaffold or d9-project-scaffold plugin is present we
// only execute this one if $force is TRUE. This way we can make sure that
// we get executed after d8-project-scaffold or d9-project-scaffold.

jurgenhaas
committed
$settingsPath = $webRoot . '/sites/default';
if ($this->getPackage('lakedrops/d8-project-scaffold') ||
$this->getPackage('lakedrops/d9-project-scaffold') ||
$this->getPackage('lakedrops/drupal-environment') ||
$this->getPackage('lakedrops/drupal-development-environment')) {

jurgenhaas
committed
if (!$fs->exists($projectRoot . '/settings/default')) {
return;
}
$settingsPath = 'settings/default';
}
foreach ($this->getFiles($projectRoot, $webRoot, $settingsPath) as $template => $def) {
if (isset($def['condition']) && !$def['condition']) {
continue;
}
if (!$fs->exists($def['dest'])) {
$fs->mkdir($def['dest']);
}
$filename = $this->config->render($template, $template);
$file = $def['dest'] . '/' . $filename;
if (!empty($def['delete'])) {
if ($fs->exists($file)) {
$fs->remove($file);
}
continue;
}
if (empty($def['add2git']) || !$fs->exists($file)) {
$source = isset($def['source']) && is_file($pluginRoot . '/templates/' . $def['source']) ?
$pluginRoot . '/templates/' . $def['source'] :
$pluginRoot . '/templates/' . ($def['source'] ?? '') . $template . '.twig';
if (isset($def['options'])) {
$this->config->setValue('loopoptions', $def['options'], FALSE);
}
$rendered = $this->config->render($filename, file_get_contents($source));
$extraOptions = $this->config->readValue($filename);
if (!empty($def['add2yaml']) && $extraOptions !== NULL) {
$yaml = Yaml::parse($rendered);

jurgenhaas
committed
/* @noinspection SlowArrayOperationsInLoopInspection */
$yaml = array_merge_recursive($yaml, $extraOptions);
$rendered = Yaml::dump($yaml, 9, 2);

jurgenhaas
committed
// Render string again so that custom content can also use variables.
$rendered = $this->config->render($filename, $rendered);
elseif ($extraOptions !== NULL) {
$rendered .= $extraOptions;
}
continue;
}
$orig_file = $file . '.orig';
if ($fs->exists($orig_file)) {
$fs->remove($orig_file);
}
$fs->rename($file, $orig_file);
if (!$orig_ignored) {
if (empty($def['add2git'])) {

jurgenhaas
committed
if (file_exists($file)) {
$fs->chmod($file, 0664);
}

jurgenhaas
committed
if (empty($def['add2git'])) {
$fs->chmod($file, 0444);
}
if (isset($def['link']) && ($def['link'] !== $settingsPath)) {
$link = $def['link'] . '/' . $filename;
$rel = substr($fs->makePathRelative($file, $projectRoot . '/' . $link), 3, -1);
// Make sure that settings.docker.php gets called from settings.php.

jurgenhaas
committed
$settingsPhpFile = $settingsPath . '/settings.php';

jurgenhaas
committed
$settingsPhp = file_get_contents($settingsPhpFile);
if (!str_contains($settingsPhp, 'settings.docker.php')) {

jurgenhaas
committed
$settingsPhp .= "\n\nif (file_exists(__DIR__ . '/settings.docker.php')) {\n include __DIR__ . '/settings.docker.php';\n}\n";

jurgenhaas
committed
$fs->chmod($settingsPhpFile, 0664);

jurgenhaas
committed
file_put_contents($settingsPhpFile, $settingsPhp);

jurgenhaas
committed
$fs->chmod($settingsPhpFile, 0444);

jurgenhaas
committed
}
$this->gitIgnore('tests/backstop/backstop.json');
$this->gitIgnore('tests/backstop/backstop-script-*');
$this->gitIgnore('tests/backstop/backstop_data/bitmaps_test');
$this->gitIgnore('tests/backstop/backstop_data/html_report');

jurgenhaas
committed
// Ignore some Cypress directories.
$this->gitIgnore('tests/cypress/downloads');
$this->gitIgnore('tests/cypress/screenshots');
$this->gitIgnore('tests/cypress/videos');

jurgenhaas
committed
// Ignore some Unlighthouse directories.
$this->gitIgnore('tests/unlighthouse');
if ($this->config->readValue(['invoiceninja', 'enable'])) {
// Ignore Invoice Ninja directory.
$this->gitIgnore('/in/');
}
if ($this->config->readValue(['mailpit', 'enable'])) {
// Ignore mailpit directory.
$this->gitIgnore('/mailpit/');
}
if (getenv('LAKEDROPS_BUILD_NG') !== 'yes') {
$this->updateTraefik();
}

jurgenhaas
committed
// Set permissions.
// @see https://wodby.com/stacks/drupal/docs/local/permissions
exec('setfacl -dR -m u:$(whoami):rwX -m u:82:rwX -m u:100:rX -m g::rwX ' . $projectRoot . ' >/dev/null 2>&1');
exec('setfacl -R -m u:$(whoami):rwX -m u:82:rwX -m u:100:rX -m g::rwX ' . $projectRoot . ' >/dev/null 2>&1');
}
/**
* Configure Traefik on the host for all projects.
*/

jurgenhaas
committed
public function configureTraefik(): void {
if (!$this->isDevMode()) {
return;
}
$this->init();

jurgenhaas
committed
$this->updateTraefik();
}
/**

jurgenhaas
committed
* Update Traefik.

jurgenhaas
committed
private function updateTraefik(): void {
if (getenv('TRAEFIK_HOST') === 'yes') {

jurgenhaas
committed
// Traefik is already available & controlled by the host. Don't touch it.
return;
}
$traefik = new Traefik(
$this->config->readValue('projectname'),
$this->config->readValue(['traefik', 'domain']),
(int) $this->config->readValue(['traefik', 'port']),
(int) $this->config->readValue(['traefik', 'ports']),
$this->config->readValue(['traefik', 'cert']),
$this->config->readValue(['traefik', 'key']),
$this->config->readValue(['traefik', 'env']),
(bool) $this->config->readValue(['traefik', 'usessl']),
(bool) $this->config->readValue(['traefik', 'dns_challenge']),
$this->config->readValue(['traefik', 'dns_challenge_provider']),
$this->config->readValue(['traefik', 'dns_challenge_resolver'])

jurgenhaas
committed
if ($this->config->readValue(['traefik', 'portainer'])) {
$traefik->setAddonPortainer(TRUE);
}
if ($hub_token = $this->config->readValue(['traefik', 'hub_token'])) {
$traefik->setHubToken($hub_token);
}

jurgenhaas
committed
$traefik->update();
/**
* List of files and settings on how to handle them.
*
* @param string $projectRoot
* Name of the project's root directory.
* @param string $webRoot
* Name of the web's root directory.
* @param string $settingsPath
* Name of the settings directory.
*
* @return array
* List of files.
*/
protected function getFiles(string $projectRoot, string $webRoot, string $settingsPath): array {

jurgenhaas
committed
'dest' => $projectRoot . '/' . $settingsPath,
'add2yaml' => TRUE,

jurgenhaas
committed
'drushrc.php' => [
'dest' => $projectRoot . '/drush',
],
'default.site.yml' => [
'dest' => $projectRoot . '/drush/sites',
'add2yaml' => TRUE,
'stage.site.yml' => [
'dest' => $projectRoot . '/drush/sites',
'add2yaml' => TRUE,
],
'drush.yml' => [
'dest' => $projectRoot . '/drush',
'add2yaml' => TRUE,
'wkhtmltox.sh' => [
'dest' => $projectRoot . '/.docker-init',
],
'backstop.json' => [
'source' => 'tests/backstop/',
'dest' => $projectRoot . '/tests/backstop',
'add2yaml' => TRUE,
],
'backstop-script-before' => [
'source' => 'tests/backstop/',
'dest' => $projectRoot . '/tests/backstop',
'add2yaml' => TRUE,
'mode' => 0775,
],
'backstop-script-after' => [
'source' => 'tests/backstop/',
'dest' => $projectRoot . '/tests/backstop',
'add2yaml' => TRUE,
'mode' => 0775,
],
'vhost.conf' => [
'dest' => $projectRoot . '/apache',
'condition' => $this->config->readValue(['webserver', 'overwriteconfig']),
'mailpitout.yml' => [
'dest' => $projectRoot . '/mailpit',
'in-vhost.conf' => [
'dest' => $projectRoot . '/nginx',
'condition' => $this->config->readValue(['invoiceninja', 'enable']),
$files['cypress.config.js'] = [
'source' => 'tests/',
'dest' => $projectRoot . '/tests',
'add2git' => TRUE,
];
$files['cypress-run'] = [
'source' => 'tests/',
'dest' => $projectRoot . '/tests',
'add2git' => FALSE,
'mode' => 0775,
];
$files['commands.js'] = [
'source' => 'tests/cypress/support/',
'dest' => $projectRoot . '/tests/cypress/support',
'add2git' => TRUE,
];
$files['e2e.js'] = [
'source' => 'tests/cypress/support/',
'dest' => $projectRoot . '/tests/cypress/support',
'add2git' => TRUE,
];
$files['unlighthouse.config.ts'] = [
'source' => 'tests/',
'dest' => $projectRoot . '/tests',
'add2git' => FALSE,
];

jurgenhaas
committed
if ($this->config->readValue(['backup', 'enable'])) {
$files['config.yaml'] = [
'source' => 'backup/',
'dest' => $projectRoot . '/backup',
];
$files['crontab.txt'] = [
'source' => 'backup/',
'dest' => $projectRoot . '/backup',
];
}
$hj_api_key = getenv('HEALTHCHECK_API_KEY');
$hj_api_channels = getenv('HEALTHCHECK_API_CHANNELS');
$hj_project = $this->config->readValue('projectname');
$hj_branch = getenv('CI_COMMIT_BRANCH');
$hj_timezone = $this->env->receiveGlobal('HEALTHCHECK_API_TIMEZONE', '', 'Europe/Berlin');
$hj_checks = [];
$hj_manager = NULL;
if (!empty($hj_url) && !empty($hj_api_key)) {
$hj_manager = new HealthchecksManager($hj_api_key, $hj_url . '/api/v1/');
try {
$hj_checks = $hj_manager->listChecks();
}
catch (HealthchecksFailureException | HealthchecksUnauthorisedException) {
// Ignoring this for now.
}
}
foreach ($this->config->readValue('crontabs') ?? [] as $user => $tasks) {
if (!is_array($tasks)) {
continue;
}
$disabled = !empty($task['disabled']);
if (isset($hj_manager)) {
unset($task['disabled'], $task['command']);
$task['channels'] = $hj_api_channels;
$task['tags'] = implode(' ', ['d4d', $hj_project, $hj_branch]);
if (count(explode(' ', $task['schedule'])) === 6) {
// Schedule contains seconds, they need to be removed.
$task['schedule'] = substr($task['schedule'], strpos($task['schedule'], ' ') + 1);
}
$check = NULL;
foreach ($hj_checks as $existing) {
$tags = explode(' ', $existing['tags']);
if ($task['name'] === $existing['name'] && in_array('d4d', $tags, TRUE) && in_array($hj_project, $tags, TRUE) && in_array($hj_branch, $tags, TRUE)) {
$check = $existing;
break;
}
}
if (!$check) {
if ($disabled) {
// This task is disabled as it doesn't exist yet, nothing to do.
continue;
}
try {
$check = $hj_manager->createCheck($task);
$parts = explode('/', $check['ping_url']);
$uuid = array_pop($parts);
$hj_check = new Healthchecks($uuid, $hj_url . '/ping/');
}
catch (HealthchecksFailureException | HealthchecksUuidNotFoundException | HealthchecksAccountLimitReachedException | HealthchecksUnauthorisedException) {
// Ignoring this for now.
}
}
else {
$changed = FALSE;
foreach ($task as $key => $value) {
if (!isset($check[$key]) || $check[$key] !== $value) {
$changed = TRUE;
}
}
if ($changed) {
$parts = explode('/', $check['ping_url']);
$uuid = array_pop($parts);
try {
$check = $hj_manager->updateCheck($uuid, $task);
}
catch (HealthchecksFailureException | HealthchecksUuidNotFoundException | HealthchecksAccountLimitReachedException | HealthchecksUnauthorisedException) {
// Ignoring this for now.
}
}
}
if ($check) {
}
}
if (!$disabled) {
if (count(explode(' ', $schedule)) === 5) {
if ($user === 'borgmatic') {
$backup = $this->config->readValue('backup');
$backup['healthckeck_url'][$command] = $ping_url;
$this->config->setValue('backup', $backup, FALSE);
}
else {
$activeTasks[] = [
'name' => $task['name'],
'schedule' => $schedule,
'command' => $command,
'ping_url' => $ping_url,
'user' => $user,
];
}
}
}
if (!empty($activeTasks)) {
$files['jobs.ini'] = [
'source' => 'crontabs/template.twig',
'dest' => $projectRoot . '/crontabs',
'options' => $activeTasks,
/**
* Determine local ipv4 address.
*
* @param string|null $interface
* The name of the interface for which to determine the ipv4 address.
*
* @return string|array
* The ipv4 address(es).
*/
private function getLocalIpv4(?string $interface = NULL): array|string {
$out = explode(PHP_EOL, shell_exec('LC_ALL=C /sbin/ifconfig'));

jurgenhaas
committed
$local_addrs = [];
$ifname = 'unknown';
foreach ($out as $str) {

jurgenhaas
committed
$matches = [];
if (preg_match('/^([a-z0-9]+)(:\d{1,2})?(\s)+Link/', $str, $matches)) {
$ifname = $matches[1];
}
elseif (preg_match('/inet addr:((?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)(?:[.](?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|\d)){3})\s/', $str, $matches)) {
$local_addrs[$ifname] = $matches[1];
}
}
if (!isset($interface)) {
return $local_addrs;
}
return $local_addrs[$interface] ?? '127.0.0.1';

jurgenhaas
committed
* Get Docker gateway IP from docker inspect.
*
*/
private function getDockerGateway(): string {
$container = $this->readContainerConfig();
return $container['NetworkSettings']['Gateway'];
}

jurgenhaas
committed
* Get Docker Proxy IP from docker inspect.
*

jurgenhaas
committed
* The proxy IP.
private function getDockerProxy(): string {

jurgenhaas
committed
if (isset($container['Name']) && in_array($container['Name'], [
'traefik',
'traefik_traefik_1',
'traefik-traefik-1',
])) {
return explode('/', $container['IPv4Address'])[0];
}
}
return '127.0.0.1';
}

jurgenhaas
committed
* Get Docker mount source from docker inspect.
*
* @param string $projectRoot
* The project root directory.
*
* @return string

jurgenhaas
committed
* The mount source.

jurgenhaas
committed
private function getDockerMountSource(string $projectRoot): string {
$currentDir = getcwd();
$container = $this->readContainerConfig();
foreach ($container['Mounts'] as $mount) {
if (empty($projectRoot)) {
if ($currentDir === $mount['Destination']) {
return $mount['Source'];
}
}
elseif (str_starts_with($projectRoot, $mount['Destination'])) {
return $mount['Source'] . substr($projectRoot, strlen($mount['Destination']));
}
}
return getcwd();
}

jurgenhaas
committed
* Get container details from docker inspect.
*

jurgenhaas
committed
* The container details.
*/
private function readContainerConfig(): array {
$testString = 'This is a test file for LakeDrops GitLab CI';

jurgenhaas
committed
$filename = '/tmp/' . random_int(100, 999) . '.test';
file_put_contents($filename, $testString);
exec('docker ps -q', $output);
$id = NULL;
foreach ($output as $id) {
$output = [];
exec('docker exec ' . $id . ' cat ' . $filename . ' 2>&1', $output);
if (reset($output) === $testString) {
// Found the container.
break;
}
}
unlink($filename);
if ($id !== NULL) {
$output = [];
exec('docker container inspect ' . $id, $output);
return json_decode(implode('', $output), TRUE, 512, JSON_THROW_ON_ERROR)[0];
// Ignore.
}
return [
'NetworkSettings' => [
'Gateway' => '127.0.0.1',
],
'Mounts' => [],
];

jurgenhaas
committed
* Get network configuration from docker inspect.
*

jurgenhaas
committed
* The network configuration.

jurgenhaas
committed
return json_decode(implode('', $output), TRUE, 512, JSON_THROW_ON_ERROR)[0];
// Ignore.
}
return [
'Containers' => [],
];
}
/**
* Get the version from docker compose.
*
* @return string
* The docker compose version.
*/
private function readDockerComposeVersion(): string {
try {
$output = [];
exec('docker compose version --short', $output);
return implode('', $output);
}
catch (\Exception) {
// Ignore.
}
return '0.0.0';
}

jurgenhaas
committed
* Get default configuration for backstop.