Significant improvements

This commit is contained in:
Greyscale 2024-01-05 18:15:51 +01:00
parent e7594723bc
commit 307976917f
No known key found for this signature in database
GPG key ID: 74BAFF55434DA4B2
7 changed files with 229 additions and 63 deletions

View file

@ -42,6 +42,8 @@ jobs:
pull: true
push: true
target: bouncer
build-args: |
GIT_SHA=${{ github.sha }}
tags: |
benzine/bouncer
ghcr.io/benzine-framework/bouncer:latest

View file

@ -1 +0,0 @@
vendor

View file

@ -1,4 +1,6 @@
FROM benzine/php:cli-8.1 as bouncer
ARG GIT_SHA
ENV GIT_SHA=${GIT_SHA}
LABEL maintainer="Matthew Baggett <matthew@baggett.me>" \
org.label-schema.vcs-url="https://github.com/benzine-framework/docker" \
org.opencontainers.image.source="https://github.com/benzine-framework/docker"
@ -50,6 +52,7 @@ COPY NginxTemplate.twig /app/
RUN sed -i '1s;^;daemon off\;\n;' /etc/nginx/nginx.conf
RUN sed -i 's|include /etc/nginx/sites-enabled/*|include /etc/nginx/sites-enabled/*.conf|g' /etc/nginx/nginx.conf
COPY bouncer /app
COPY vendor /app/vendor
COPY composer.* /app/
COPY public /app/public
RUN composer install && \

View file

@ -1,11 +1,16 @@
all: fix build-n-push
php-cs-fixer:
docker run --rm -v $(PWD):/data cytopia/php-cs-fixer fix --config=.php-cs-fixer.php bouncer
docker run --rm -v $(shell pwd):/data cytopia/php-cs-fixer fix --config=.php-cs-fixer.php bouncer
fix: php-cs-fixer
build-n-push:
docker build --tag benzine/bouncer --tag ghcr.io/benzine-framework/bouncer --target bouncer .
docker build \
--build-arg GIT_SHA=$(shell git rev-parse HEAD) \
--tag benzine/bouncer \
--tag ghcr.io/benzine-framework/bouncer \
--target bouncer \
.
#docker push benzine/bouncer
docker push ghcr.io/benzine-framework/bouncer
@ -45,4 +50,4 @@ start_bouncer:
clean:
-docker service rm bouncer test-app-a test-app-b
#-docker network rm bouncer-test
#-docker image rm test-app-a test-app-b bouncer
#-docker image rm test-app-a test-app-b bouncer

View file

@ -14,8 +14,10 @@ use League\Flysystem\FileAttributes;
use League\Flysystem\Filesystem;
use League\Flysystem\Local\LocalFilesystemAdapter;
use Monolog\Handler\StreamHandler;
use Monolog\Level;
use Monolog\Logger;
use Spatie\Emoji\Emoji;
use Symfony\Component\Yaml\Yaml;
use Twig\Environment;
use Twig\Loader\FilesystemLoader;
@ -326,14 +328,12 @@ class Bouncer
private ?Filesystem $certificateStoreRemote = null;
private Filesystem $providedCertificateStore;
private Logger $logger;
private string $instanceContainerStateHash = '';
private array $previousContainerState = [];
private string $instanceSwarmStateHash = '';
private array $previousSwarmState = [];
private array $fileHashes;
private bool $swarmMode = false;
private bool $useGlobalCert = false;
private int $forcedUpdateIntervalSeconds = 600;
private int $forcedUpdateIntervalSeconds = 0;
private ?int $lastUpdateEpoch = null;
private int $maximumNginxConfigCreationNotices = 15;
@ -343,9 +343,13 @@ class Bouncer
ksort($this->environment);
$this->logger = new Monolog\Logger('bouncer');
$this->logger->pushHandler(new StreamHandler('/var/log/bouncer.log', Logger::DEBUG));
$stdout = new StreamHandler('php://stdout', Logger::DEBUG);
$stdout->setFormatter(new ColoredLineFormatter(null, "%level_name%: %message% \n"));
$this->logger->pushHandler(new StreamHandler('/var/log/bouncer.log', Level::Debug));
$stdout = new StreamHandler('php://stdout', Level::Debug);
$stdout->setFormatter(new ColoredLineFormatter(
format: "%level_name%: %message% \n",
allowInlineLineBreaks: true,
ignoreEmptyContextAndExtra: true,
));
$this->logger->pushHandler($stdout);
if (isset($this->environment['DOCKER_HOST'])) {
@ -591,7 +595,8 @@ class Bouncer
public function run(): void
{
$this->logger->info(sprintf('%s Starting Bouncer...', Emoji::CHARACTER_TIMER_CLOCK));
$gitHash = substr($this->environment['GIT_SHA'], 0, 7);
$this->logger->info(sprintf('%s Starting Bouncer git=%s...', Emoji::CHARACTER_TIMER_CLOCK, $gitHash));
try {
$this->stateHasChanged();
@ -676,10 +681,18 @@ class Bouncer
*/
private function stateHasChanged(): bool
{
if ($this->lastUpdateEpoch === null || $this->lastUpdateEpoch <= time() - $this->forcedUpdateIntervalSeconds) {
$this->logger->debug(sprintf('%s Forced update interval has been reached, forcing update.', Emoji::watch()));
return true;
$isTainted = false;
if ($this->lastUpdateEpoch === null) {
$isTainted = true;
} elseif ($this->forcedUpdateIntervalSeconds > 0 && $this->lastUpdateEpoch <= time() - $this->forcedUpdateIntervalSeconds) {
$this->logger->warning(sprintf('%s Forced update interval of %d seconds has been reached, forcing update.', Emoji::watch(), $this->forcedUpdateIntervalSeconds));
$isTainted = true;
} elseif ($this->previousContainerState === []) {
$this->logger->warning(sprintf('%s Initial state has not been set, forcing update.', Emoji::watch()));
$isTainted = true;
} elseif ($this->previousSwarmState === []) {
$this->logger->warning(sprintf('%s Initial swarm state has not been set, forcing update.', Emoji::watch()));
$isTainted = true;
}
// Standard Containers
@ -687,33 +700,36 @@ class Bouncer
$containers = $this->dockerGetContainers();
foreach ($containers as $container) {
$inspect = $this->dockerGetContainer($container['Id']);
$newContainerState[$inspect['Id']] = [
$inspect['Name'],
$inspect['Created'],
$inspect['Image'],
$inspect['State']['Status'],
$inspect['Config']['Env'],
$name = ltrim($inspect['Name'],"/");
$newContainerState[$name] = [
'name' => $name,
'created' => $inspect['Created'],
'image' => $inspect['Image'],
'status' => $inspect['State']['Status'],
'env' => array_filter(array_map(function ($env) {
if (stripos($env, '=') !== false) {
[$envKey, $envVal] = explode('=', $env, 2);
if (strlen($envVal) > 35) {
return sprintf('%s=CRC32(%s)', $envKey, crc32($envVal));
}
return sprintf('%s=%s', $envKey, $envVal);
}
}, $inspect['Config']['Env'])),
];
ksort($newContainerState[$inspect['Id']]);
sort($newContainerState[$inspect['Name']]['env']);
}
ksort($newContainerState);
// Calculate Container State Hash
$newContainerStateHash = sha1(json_encode($newContainerState));
if ($this->instanceContainerStateHash != $newContainerStateHash) {
\Kint::dump(array_diff(
message: 'Swarm State has changed',
previous: $this->previousContainerState,
new: $newContainerState,
diff: array_diff($this->previousContainerState, $newContainerState),
previousHash: $this->instanceContainerStateHash,
newHash: $newContainerStateHash,
));
$this->instanceContainerStateHash = $newContainerStateHash;
$this->logger->debug(sprintf('%s Container state has changed', Emoji::warning()));
return true;
$containerStateDiff = $this->diff($this->previousContainerState, $newContainerState);
if (!$isTainted && !empty($containerStateDiff)) {
$this->logger->warning(sprintf('%s Container state has changed', Emoji::warning()));
$this->logger->debug($containerStateDiff);
$isTainted = true;
}
$this->previousContainerState = $newContainerState;
// Swarm Services
$newSwarmState = [];
@ -733,26 +749,29 @@ class Bouncer
ksort($newSwarmState);
// Calculate Swarm State Hash, if applicable
if ($this->isSwarmMode()) {
$newSwarmStateHash = sha1(json_encode($newSwarmState));
if ($this->instanceSwarmStateHash != $newSwarmStateHash) {
\Kint::dump(array_diff(
message: 'Swarm State has changed',
previous: $this->previousSwarmState,
new: $newSwarmState,
diff: array_diff($this->previousSwarmState, $newSwarmState),
previousHash: $this->instanceSwarmStateHash,
newHash: $newSwarmStateHash,
));
$this->instanceSwarmStateHash = $newSwarmStateHash;
$this->previousSwarmState = $newSwarmState;
$this->logger->debug(sprintf('%s Swarm state has changed', Emoji::warning()));
return true;
}
$swarmStateDiff = $this->diff($this->previousSwarmState, $newSwarmState);
if ($this->isSwarmMode() && !$isTainted && !empty($swarmStateDiff)) {
$this->logger->warning(sprintf('%s Swarm state has changed', Emoji::warning()));
$this->logger->debug($swarmStateDiff);
$isTainted = true;
}
$this->previousSwarmState = $newSwarmState;
return false;
return $isTainted;
}
private function diff($a, $b)
{
return (new Diff(
explode(
"\n",
Yaml::dump(input: $a, inline: 5, indent: 2)
),
explode(
"\n",
Yaml::dump(input: $b, inline: 5, indent: 2)
)
))->render(new Diff_Renderer_Text_Unified());
}
private function runLoop(): void
@ -782,6 +801,15 @@ class Bouncer
)
);
// Use some bs to sort the targets by domain from right to left.
$sortedTargets = [];
foreach ($targets as $target) {
$sortedTargets[strrev($target->getName())] = $target;
}
ksort($sortedTargets);
$targets = array_values($sortedTargets);
// Wipe configs and rebuild
$this->wipeNginxConfig();
$this->logger->info(sprintf('%s Found %d services with BOUNCER_DOMAIN set', Emoji::CHARACTER_MAGNIFYING_GLASS_TILTED_LEFT, count($targets)));
@ -799,7 +827,6 @@ class Bouncer
sleep(5);
}
$this->lastUpdateEpoch = time();
$this->logger->info(sprintf('%s Host Container state has changed', Emoji::CHARACTER_WARNING));
}
private function s3Enabled(): bool
@ -883,10 +910,25 @@ class Bouncer
*/
private function generateNginxConfigs(array $targets): self
{
// get the length of the longest name...
foreach ($targets as $target) {
$longestName[] = strlen($target->getName());
}
$longestName = max($longestName);
foreach ($targets as $target) {
$this->generateNginxConfig($target);
if (count($targets) <= $this->getMaximumNginxConfigCreationNotices()) {
$this->logger->info(sprintf('%s Created Nginx config for http://%s', Emoji::pencil(), $target->getName()));
$this->logger->info(sprintf(
'%s Created Nginx config for %s',
Emoji::pencil(),
str_pad(
'http://'.$target->getName(),
$longestName + strlen('http://'),
' ',
STR_PAD_LEFT
)
));
}
}
if (count($targets) > $this->getMaximumNginxConfigCreationNotices()) {

View file

@ -18,7 +18,9 @@
"league/flysystem": "^2.5",
"league/flysystem-aws-s3-v3": "^2.5",
"monolog/monolog": "^3.5",
"phpspec/php-diff": "^1.1",
"spatie/emoji": "^2.3",
"symfony/yaml": "^6.4",
"twig/twig": "^3.8"
},
"authors": [

127
bouncer/composer.lock generated
View file

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "99f852481be86ad83a01d895a2f462e5",
"content-hash": "e83348641b873c4c8f40fa2d8e0a7df1",
"packages": [
{
"name": "adambrett/shell-wrapper",
@ -107,16 +107,16 @@
},
{
"name": "aws/aws-sdk-php",
"version": "3.295.5",
"version": "3.295.6",
"source": {
"type": "git",
"url": "https://github.com/aws/aws-sdk-php.git",
"reference": "cd9d48ebfdfc8fb5f6df9fe95dced622287f3412"
"reference": "9b0b2daf46d5e6a4600575917cea0764e4dbb6b5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/cd9d48ebfdfc8fb5f6df9fe95dced622287f3412",
"reference": "cd9d48ebfdfc8fb5f6df9fe95dced622287f3412",
"url": "https://api.github.com/repos/aws/aws-sdk-php/zipball/9b0b2daf46d5e6a4600575917cea0764e4dbb6b5",
"reference": "9b0b2daf46d5e6a4600575917cea0764e4dbb6b5",
"shasum": ""
},
"require": {
@ -196,9 +196,9 @@
"support": {
"forum": "https://forums.aws.amazon.com/forum.jspa?forumID=80",
"issues": "https://github.com/aws/aws-sdk-php/issues",
"source": "https://github.com/aws/aws-sdk-php/tree/3.295.5"
"source": "https://github.com/aws/aws-sdk-php/tree/3.295.6"
},
"time": "2024-01-03T19:12:43+00:00"
"time": "2024-01-04T19:43:02+00:00"
},
{
"name": "bramus/ansi-php",
@ -1073,6 +1073,47 @@
},
"time": "2023-08-25T10:54:48+00:00"
},
{
"name": "phpspec/php-diff",
"version": "v1.1.3",
"source": {
"type": "git",
"url": "https://github.com/phpspec/php-diff.git",
"reference": "fc1156187f9f6c8395886fe85ed88a0a245d72e9"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/phpspec/php-diff/zipball/fc1156187f9f6c8395886fe85ed88a0a245d72e9",
"reference": "fc1156187f9f6c8395886fe85ed88a0a245d72e9",
"shasum": ""
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-0": {
"Diff": "lib/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"BSD-3-Clause"
],
"authors": [
{
"name": "Chris Boulton",
"homepage": "http://github.com/chrisboulton"
}
],
"description": "A comprehensive library for generating differences between two hashable objects (strings or arrays).",
"support": {
"source": "https://github.com/phpspec/php-diff/tree/v1.1.3"
},
"time": "2020-09-18T13:47:07+00:00"
},
{
"name": "psr/http-client",
"version": "1.0.3",
@ -1708,6 +1749,78 @@
],
"time": "2023-01-26T09:26:14+00:00"
},
{
"name": "symfony/yaml",
"version": "v6.4.0",
"source": {
"type": "git",
"url": "https://github.com/symfony/yaml.git",
"reference": "4f9237a1bb42455d609e6687d2613dde5b41a587"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/yaml/zipball/4f9237a1bb42455d609e6687d2613dde5b41a587",
"reference": "4f9237a1bb42455d609e6687d2613dde5b41a587",
"shasum": ""
},
"require": {
"php": ">=8.1",
"symfony/deprecation-contracts": "^2.5|^3",
"symfony/polyfill-ctype": "^1.8"
},
"conflict": {
"symfony/console": "<5.4"
},
"require-dev": {
"symfony/console": "^5.4|^6.0|^7.0"
},
"bin": [
"Resources/bin/yaml-lint"
],
"type": "library",
"autoload": {
"psr-4": {
"Symfony\\Component\\Yaml\\": ""
},
"exclude-from-classmap": [
"/Tests/"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Fabien Potencier",
"email": "fabien@symfony.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Loads and dumps YAML files",
"homepage": "https://symfony.com",
"support": {
"source": "https://github.com/symfony/yaml/tree/v6.4.0"
},
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2023-11-06T11:00:25+00:00"
},
{
"name": "twig/twig",
"version": "v3.8.0",