155 lines
4.7 KiB
PHP
Executable File
155 lines
4.7 KiB
PHP
Executable File
#!/usr/bin/env php
|
|
<?php
|
|
|
|
|
|
use NoccyLabs\FreshDocker\Configuration\DockerComposeConfiguration;
|
|
use NoccyLabs\FreshDocker\Configuration\LocalConfiguration;
|
|
use NoccyLabs\FreshDocker\Credentials\BasicCredentialsLoader;
|
|
use NoccyLabs\FreshDocker\Hooks\SlackHook;
|
|
use NoccyLabs\FreshDocker\ImageReference;
|
|
use NoccyLabs\FreshDocker\Registry\RegistryV2Client;
|
|
use NoccyLabs\FreshDocker\State\PersistentState;
|
|
|
|
require_once __DIR__."/../vendor/autoload.php";
|
|
|
|
$opts = getopt("hd:c:i:qv", [ "help", "dir:", "config:", "image:", "pull", "credentials:", "check", "slack:", "quiet", "prune" ]);
|
|
|
|
if (array_key_exists('h', $opts) || array_key_exists('help', $opts)) {
|
|
|
|
printf("fresh.phar - (c) 2022, NoccyLabs / GPL v3 or later\n");
|
|
printf("Check for updates to docker images or compose stacks.\n\n");
|
|
printf("Usage:\n %s [options]\n\n", basename($argv[0]));
|
|
printf("Options:\n");
|
|
printf(" General:\n");
|
|
foreach([
|
|
'-h,--help' => "Show this help",
|
|
'-q,--quiet' => "Don't show any output (except dockers)",
|
|
'-d,--dir DIR' => "Change working directory to DIR",
|
|
'-i,--image REF' => "Check a specific image instead of using config/docker-compose",
|
|
'--pull' => "Only pull if new, don't up",
|
|
'--check' => "Only check, set exit code 1 if not fresh",
|
|
'--prune' => "Prune dangling images after pull and up",
|
|
] as $a=>$b) printf(" %-20s %s\n", $a, $b);
|
|
|
|
printf(" Webhooks:\n");
|
|
foreach([
|
|
'--slack URL' => "Notify a Slack webhook when updating",
|
|
] as $a=>$b) printf(" %-20s %s\n", $a, $b);
|
|
|
|
printf(" Configuration:\n");
|
|
foreach([
|
|
'-c,--config FILE' => "Use a custom configuration (not implemented)",
|
|
'--credentials TYPE' => "Select credentials loader (auto or basic)",
|
|
] as $a=>$b) printf(" %-20s %s\n", $a, $b);
|
|
|
|
|
|
exit(0);
|
|
|
|
}
|
|
|
|
$credentialsLoaderType = $opts['credentials'] ?? 'auto';
|
|
$path = realpath($opts['d'] ?? ($opts['dir'] ?? getcwd()));
|
|
$onlyPull = array_key_exists('p', $opts) || array_key_exists('pull', $opts);
|
|
$onlyCheck = array_key_exists('check', $opts);
|
|
$quiet = array_key_exists('q', $opts) || array_key_exists('quiet', $opts);
|
|
$verbose = array_key_exists('v', $opts);
|
|
$prune = array_key_exists('prune', $opts);
|
|
|
|
$slackUrl = array_key_exists('slack', $opts) ? $opts['slack'] : null;
|
|
$slackHook = null;
|
|
if ($slackUrl) {
|
|
$slackHook = new SlackHook([
|
|
'url' => $slackUrl
|
|
]);
|
|
}
|
|
|
|
if (!is_dir($path)) {
|
|
fwrite(STDERR, "error: invalid path for --dir/-d\n");
|
|
exit(2);
|
|
}
|
|
chdir($path);
|
|
|
|
$path = getcwd();
|
|
if (!file_exists($path."/docker-compose.yml")) {
|
|
fwrite(STDERR, "error: no docker-compose.yml in the current directory\n");
|
|
exit(2);
|
|
}
|
|
|
|
switch ($credentialsLoaderType) {
|
|
case 'auto':
|
|
case 'basic':
|
|
$credentialsLoader = new BasicCredentialsLoader();
|
|
break;
|
|
default:
|
|
fwrite(STDERR, "error: invalid credentials loader {$credentialsLoaderType}\n");
|
|
exit(2);
|
|
}
|
|
|
|
//$configuration = new LocalConfiguration(getcwd()."/freshdocker.conf");
|
|
$configuration = new DockerComposeConfiguration("{$path}/docker-compose.yml");
|
|
$state = new PersistentState("{$path}/fresh.yml");
|
|
$checks = $configuration->getChecks();
|
|
if (count($checks) === 0) {
|
|
fwrite(STDERR, "error: couldn't find any images to check\n");
|
|
exit(2);
|
|
}
|
|
$update = false;
|
|
|
|
$log = [];
|
|
foreach ($checks as $check) {
|
|
|
|
$ref = new ImageReference($check);
|
|
$reg = $ref->getRegistry();
|
|
|
|
$credentials = $credentialsLoader->getCredentials($reg);
|
|
if (!$credentials) {
|
|
if ($verbose) printf("%s: missing credentials for %s\n", $ref->getImage(), $ref->getRegistry());
|
|
continue;
|
|
}
|
|
$client = new RegistryV2Client($reg, $credentials);
|
|
|
|
|
|
$status = $client->getImageStatus($ref->getImage(), $ref->getTag());
|
|
$image = $status['image'].":".$status['tag'];
|
|
$oldHash = $state->get($image);
|
|
$newHash = $status['hash'];
|
|
$state->set($image, $newHash);
|
|
|
|
if ($oldHash != $newHash) {
|
|
if (!$quiet) printf("%s: %s → %s\n", $image, truncate($oldHash), truncate($newHash));
|
|
$log[] = sprintf("%s: %s → %s\n", $image, truncate($oldHash), truncate($newHash));
|
|
$update = true;
|
|
} else {
|
|
if (!$quiet) printf("%s: %s\n", $image, truncate($newHash));
|
|
}
|
|
}
|
|
|
|
|
|
if ($onlyCheck) {
|
|
exit($update?1:0);
|
|
}
|
|
|
|
if ($update) {
|
|
if ($slackHook) {
|
|
$msg = "Deploying new container versions:\n* ".join("\n* ", $log);
|
|
$slackHook->sendMessage($msg, []);
|
|
}
|
|
passthru("docker-compose pull");
|
|
if (!$onlyPull) {
|
|
passthru("docker-compose up -d");
|
|
if ($prune) {
|
|
passthru("docker image prune -f");
|
|
}
|
|
}
|
|
}
|
|
|
|
exit($update?1:0);
|
|
|
|
function truncate(?string $hash): string
|
|
{
|
|
if ($hash === null) return '*';
|
|
return substr($hash, 0, 4) . ".." . substr($hash, -4, 4);
|
|
}
|
|
|
|
|