Add support for auto-discovery of sites from docker

This commit is contained in:
Dale Davies
2023-04-04 13:34:17 +01:00
parent c7dcf65644
commit 8aad20147b
5 changed files with 162 additions and 6 deletions

View File

@@ -84,6 +84,31 @@ else
sed -E -i 's/^(\s*)#listen \[::\]/\1listen [::]/g' /etc/nginx/nginx.conf
fi
# If we have been passed something in DOCKERSOCKET then check it
# was actually mounted and is a socket, if so then create the docker group
# with GID matching the docker socket file, then add jumpapp user to the
# group. This is to give jumpapp permission to make requests to the API.
if [ -n "${DOCKERSOCKET-}" ]; then
echo >&2 "";
echo >&2 "- Testing docker socket file was mounted correctly."
if [ -S "${DOCKERSOCKET}" ]; then
DOCKERGID=$(stat -c %g ${DOCKERSOCKET})
# Delete existing docker group if it exists.
if grep -q "docker" /etc/group; then
echo >&2 "-- Deleting existing docker group."
delgroup docker
fi
# Create a new one with correct GID.
echo >&2 "-- Creating docker group with correct GID."
addgroup -S docker -g $DOCKERGID
# Add jumpapp user to it.
echo >&2 "-- Adding jumpapp user to docker group."
addgroup jumpapp docker
else
echo >&2 "-- Docker socket file was either not mounted or is not a socket."
fi
fi
echo >&2 "";
echo >&2 "- All done! Starting nginx/php services now."
echo >&2 "";

View File

@@ -13,6 +13,7 @@
namespace Jump;
use \divineomega\array_undot;
use \Jump\Exceptions\ConfigException;
use \Jump\Exceptions\SiteNotFoundException;
use \Jump\Exceptions\TagNotFoundException;
@@ -26,6 +27,7 @@ class Sites {
private array $default;
private string $sitesfilelocation;
private array $loadedsites;
public array $tags;
/**
* Automatically load sites.json on instantiation.
@@ -41,10 +43,10 @@ class Sites {
'newtab' => false,
];
// Retrieve sites from cache. Load all sites from json file if not cached or
// the cache has expired.
// Retrieve sites from cache. Load all sites from json file and docker if not
// cached or the cache has expired.
$this->loadedsites = $this->cache->load(cachename: 'sites', callback: function() {
return $this->load_sites_from_json();
return array_merge($this->load_sites_from_json(), $this->load_sites_from_docker());
});
// Enumerate a list of unique tags from loaded sites. Again will retrieve from
@@ -60,6 +62,80 @@ class Sites {
});
}
/**
* Try to find a list of sites from correctly labelled docker containers.
*
* Throws an exception if the json response from docker cannot be
* decoded.
*
* @return array Array of Site objects sites identified from docker.
* @throws ConfigException If invalid response from docker.
*/
private function load_sites_from_docker(): array {
// Get either dockerproxy or dockersocket config and return early if
// neihter have been set.
$dockerproxy = $this->config->get('dockerproxyurl');
$dockersocket = $this->config->get('dockersocket');
if (!$dockerproxy && !$dockersocket) {
return [];
}
// Determine correct guzzle client and request options to use
// for either a docker proxy or connecting directly to the socket,
// prefer to use the proxy if both seem to have been given.
$clientopts = ['timeout' => 2.0];
$requestopts = [];
if ($dockerproxy) {
$clientopts['base_uri'] = 'http://'.rtrim($dockerproxy, '/');
} else if (file_exists($dockersocket)) {
$clientopts['base_uri'] = 'http://localhost';
$requestopts = [
'curl' => [CURLOPT_UNIX_SOCKET_PATH => '/var/run/docker.sock']
];
}
// Make a request to docker for all containers.
try {
$response = (new \GuzzleHttp\Client($clientopts))->request('GET', '/containers/json', $requestopts);
} catch (\GuzzleHttp\Exception\ConnectException $e) {
throw new ConfigException('Did not get a response from Docker API endpoint');
}
$containers = json_decode($response->getBody());
if (is_null($containers)) {
throw new ConfigException('Docker returned an invalid json response for containers');
}
// Build a new array of Site() objects based on labels that have been added to
// containers returned by docker.
$sites = [];
foreach ($containers as $container) {
$labels = (array) $container->Labels;
// We can't build a Site() without at least a name and url.
if (!isset($labels['jump.name'], $labels['jump.url'])) {
continue;
}
// Convert dot-syntax labels into a proper multidimensional array
// and just use the top-level key "jump" as our site array.
$site = array_undot($labels)['jump'];
// jump.tags will have been given as a comma separated string so make this
// into an array.
if (isset($site['tags'])) {
// Explode the comma separated string into an array and trim any elements.
$site['tags'] = array_map('trim', explode(',', $site['tags']));
}
// Convert status array to an object and also explode list of allowed status codes to array.
if (isset($site['status'])) {
$site['status'] = (object) $site['status'];
if (isset($site['status']->allowed_status_codes)) {
$site['status']->allowed_status_codes = array_map('trim', explode(',', $site['status']->allowed_status_codes));
}
}
// Finally add this to the list of sites we will return.
$sites[] = new Site($this->config, (array) $site, $this->default);
}
return $sites;
}
/**
* Try to load the list of sites from sites.json.
*

View File

@@ -18,6 +18,7 @@
"phlak/config": "^7.0",
"nette/http": "^3.1",
"guzzlehttp/guzzle": "^7.0",
"unsplash/unsplash": "3.2.1"
"unsplash/unsplash": "3.2.1",
"divineomega/array_undot": "^4.1"
}
}

46
jumpapp/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": "e91bcdf01a945f46bf29aa3a1cef02f5",
"content-hash": "8849ca2f3c80ed00c98055bf630ba1fb",
"packages": [
{
"name": "arthurhoaro/favicon",
@@ -64,6 +64,50 @@
},
"time": "2021-08-06T05:41:25+00:00"
},
{
"name": "divineomega/array_undot",
"version": "v4.1.0",
"source": {
"type": "git",
"url": "https://github.com/DivineOmega/array_undot.git",
"reference": "44aed525e775718e3821d670b08046fd84914d10"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/DivineOmega/array_undot/zipball/44aed525e775718e3821d670b08046fd84914d10",
"reference": "44aed525e775718e3821d670b08046fd84914d10",
"shasum": ""
},
"require-dev": {
"php-coveralls/php-coveralls": "^2.1",
"phpunit/phpunit": "^7.0 || ^8.0"
},
"type": "library",
"autoload": {
"files": [
"src/array_undot.php"
],
"psr-4": {
"DivineOmega\\ArrayUndot\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"LGPL-3.0-only"
],
"authors": [
{
"name": "Jordan Hall",
"email": "jordan@hall05.co.uk"
}
],
"description": "array_undot (the opposite of the array_dot helper function) expands a dot notation array into a full multi-dimensional array.",
"support": {
"issues": "https://github.com/DivineOmega/array_undot/issues",
"source": "https://github.com/DivineOmega/array_undot/tree/master"
},
"time": "2019-04-23T15:22:21+00:00"
},
{
"name": "guzzlehttp/guzzle",
"version": "7.5.0",

View File

@@ -63,5 +63,15 @@ return [
// Ping sites to determine availability (e.g. online, offline, errors).
'checkstatus' => getenv('CHECKSTATUS') ?: true,
// Duration to cache status in minutes.
'statuscache' => getenv('STATUSCACHE') ?: '5'
'statuscache' => getenv('STATUSCACHE') ?: '5',
// The URL and port on which a docker socket proxy is listening, for example
// if you have tecnativa/docker-socket-proxy named dockerproxy listening on
// port 2375 then this would be "dockerproxy:2375".
'dockerproxyurl' => getenv('DOCKERPROXYURL') ?: false,
// Docker socket path. Note the host docker socket file must be mapped as
// a volume into the container, this must match the path it has been mapped to.
// If possible please don't use this as it can be insecure, use a docker socket
// proxy instead (see above).
'dockersocket' => getenv('DOCKERSOCKET') ?: false
];