Significant improvements
This commit is contained in:
parent
e7594723bc
commit
307976917f
7 changed files with 229 additions and 63 deletions
2
.github/workflows/bouncer.yml
vendored
2
.github/workflows/bouncer.yml
vendored
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -1 +0,0 @@
|
|||
vendor
|
||||
|
|
@ -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 && \
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
146
bouncer/bouncer
146
bouncer/bouncer
|
|
@ -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()) {
|
||||
|
|
|
|||
|
|
@ -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
127
bouncer/composer.lock
generated
|
|
@ -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",
|
||||
|
|
|
|||
Loading…
Reference in a new issue