Added self-updating, better docker-compose control

* Added --self-update to check for and download new versions of
  the phar
* Added --only option which accepts a comma-separated list of
  services to pass to docker-compose pull/up
* Added --updated option to only pass the updated services to
  docker-compose pull/up
This commit is contained in:
Chris 2022-09-03 00:11:52 +02:00
parent 88f3b75383
commit 6c422eafe6
5 changed files with 77 additions and 6 deletions

View File

@ -32,3 +32,12 @@
- Bugfix: The lockfile is no longer removed automatically, but only if it was - Bugfix: The lockfile is no longer removed automatically, but only if it was
created by the current instance. created by the current instance.
**0.1.6**
- Implemented self-updating (use -U or --self-update)
**0.1.7**
- Added `--only` option, to only pull for specific services
- Added `--updated` option to only pull updated images

View File

@ -5,6 +5,16 @@ require_once __DIR__."/../vendor/autoload.php";
if (file_exists(__DIR__."/../src/version.php")) { if (file_exists(__DIR__."/../src/version.php")) {
require_once __DIR__."/../src/version.php"; require_once __DIR__."/../src/version.php";
if (PHAR::running()) {
require_once __DIR__."/../src/swup.php";
SWUP::register([
'url' => "https://files.noccylabs.info/.repo",
'channel' => 'beta',
'package' => 'fresh',
'version' => APP_VERSION,
'phar' => 'fresh.phar'
]);
}
} else { } else {
define("APP_VERSION", "DEV"); define("APP_VERSION", "DEV");
define("BUILD_DATE", "src"); define("BUILD_DATE", "src");

View File

@ -9,6 +9,8 @@ class ComposeConfiguration
private array $checks = []; private array $checks = [];
private array $services = [];
public function __construct(string $configfile) public function __construct(string $configfile)
{ {
$this->loadComposeFile($configfile); $this->loadComposeFile($configfile);
@ -23,6 +25,10 @@ class ComposeConfiguration
if ($image && !in_array($image,$this->checks)) { if ($image && !in_array($image,$this->checks)) {
$this->checks[] = $image; $this->checks[] = $image;
} }
if (!array_key_exists($image, $this->services)) {
$this->services[$image] = [];
}
$this->services[$image][] = $name;
} }
} }
@ -30,4 +36,16 @@ class ComposeConfiguration
{ {
return $this->checks; return $this->checks;
} }
public function getServicesForImages(array $images)
{
$ret = [];
foreach ($images as $ref) {
$image = (string)$ref->ref;
if (array_key_exists($image, $this->services)) {
$ret = array_merge($ret, $this->services[$image]);
}
}
return array_unique($ret);
}
} }

View File

@ -53,4 +53,9 @@ class ImageReference
{ {
return $this->tag; return $this->tag;
} }
public function __toString()
{
return sprintf("%s/%s:%s", $this->registry, $this->image, $this->tag);
}
} }

View File

@ -21,6 +21,7 @@ namespace NoccyLabs\FreshDocker;
*/ */
use GuzzleHttp\Exception\RequestException;
use NoccyLabs\FreshDocker\State\Log; use NoccyLabs\FreshDocker\State\Log;
use NoccyLabs\FreshDocker\Configuration\ComposeConfiguration; use NoccyLabs\FreshDocker\Configuration\ComposeConfiguration;
use NoccyLabs\FreshDocker\Configuration\LocalConfiguration; use NoccyLabs\FreshDocker\Configuration\LocalConfiguration;
@ -32,6 +33,7 @@ use NoccyLabs\FreshDocker\ImageReference;
use NoccyLabs\FreshDocker\Registry\RegistryV2Client; use NoccyLabs\FreshDocker\Registry\RegistryV2Client;
use NoccyLabs\FreshDocker\State\PersistentState; use NoccyLabs\FreshDocker\State\PersistentState;
use NoccyLabs\FreshDocker\State\Lockfile; use NoccyLabs\FreshDocker\State\Lockfile;
use Phar;
class Refresher class Refresher
{ {
@ -48,6 +50,8 @@ class Refresher
'path' => [ 'd:', 'dir:', "Change working directory", "FRESH_DIR" ], 'path' => [ 'd:', 'dir:', "Change working directory", "FRESH_DIR" ],
'image' => [ 'i:', 'image:', "Check a specific image instead of images from config", "FRESH_IMAGE" ], 'image' => [ 'i:', 'image:', "Check a specific image instead of images from config", "FRESH_IMAGE" ],
'pull' => [ null, 'pull', "Only pull if updated, don't up" ], 'pull' => [ null, 'pull', "Only pull if updated, don't up" ],
'only' => [ null, 'only:', "Comma-separated list of services to docker-compose pull" ],
'updated' => [ null, 'updated', "Only pass updated services to docker-compose pull" ],
'check' => [ null, 'check', "Only check for updates, set exit code" ], 'check' => [ null, 'check', "Only check for updates, set exit code" ],
'prune' => [ null, 'prune', "Prune dangling images after pull and up" ], 'prune' => [ null, 'prune', "Prune dangling images after pull and up" ],
'write-state' => [ 'w', 'write-state', "Always write updated state (only useful with --check)", null, false ], 'write-state' => [ 'w', 'write-state', "Always write updated state (only useful with --check)", null, false ],
@ -151,6 +155,13 @@ class Refresher
{ {
$tty = posix_isatty(STDOUT); $tty = posix_isatty(STDOUT);
// add the "Self-Updating" options group if we are running in a phar
if (Phar::running()) {
self::$optionsMap['Self-Updating'] = [
'update' => [ 'U', 'self-update', "Update to the latest version" ],
];
}
printf("fresh.phar v%s (%s) - (c) 2022, NoccyLabs / GPL v3 or later\n", APP_VERSION, BUILD_DATE); printf("fresh.phar v%s (%s) - (c) 2022, NoccyLabs / GPL v3 or later\n", APP_VERSION, BUILD_DATE);
printf("Check for updates to docker images or compose stacks.\n\n"); printf("Check for updates to docker images or compose stacks.\n\n");
printf("Usage:\n %s [options]\n\n", basename($GLOBALS['argv'][0])); printf("Usage:\n %s [options]\n\n", basename($GLOBALS['argv'][0]));
@ -215,7 +226,7 @@ class Refresher
if ($updated !== null) { if ($updated !== null) {
$this->callHooks($updated, 'before'); $this->callHooks($updated, 'before');
$this->callScript('before'); $this->callScript('before');
$this->doUpdate(); $this->doUpdate($updated);
$this->callHooks($updated, 'after'); $this->callHooks($updated, 'after');
$this->callScript('after'); $this->callScript('after');
} }
@ -385,8 +396,12 @@ class Refresher
} }
$client = new RegistryV2Client($reg, $credentials); $client = new RegistryV2Client($reg, $credentials);
try {
$status = $client->getImageStatus($ref->getImage(), $ref->getTag()); $status = $client->getImageStatus($ref->getImage(), $ref->getTag());
} catch (RequestException $e) {
$this->log->append(sprintf(" %s: registry returned error (%s)", $reg."/".$ref->getImage(), $e->getMessage()));
continue;
}
$image = $reg."/".$status['image'].":".$status['tag']; $image = $reg."/".$status['image'].":".$status['tag'];
$oldHash = $this->state->get($image); $oldHash = $this->state->get($image);
$newHash = $status['hash']; $newHash = $status['hash'];
@ -408,13 +423,27 @@ class Refresher
return empty($update) ? null : $update; return empty($update) ? null : $update;
} }
public function doUpdate() public function doUpdate(array $updated)
{ {
if ($this->options['only']) {
$pullSpec = explode(",", $this->options['only']);
$this->log->append("Refreshing only: " . join(", ", $pullSpec));
} elseif ($this->options['updated'] && ($this->config instanceof ComposeConfiguration)) {
$pullSpec = $this->config->getServicesForImages($updated);
if (count($pullSpec) == 0) {
$this->log->append("Fatal: failed to resolve updated services to docker-compose pull");
return;
}
$this->log->append("Refreshing updated: " . join(", ", $pullSpec));
} else {
$pullSpec = null;
}
$this->log->append("Pulling updated images..."); $this->log->append("Pulling updated images...");
$this->exec("docker-compose pull --quiet"); $this->exec("docker-compose pull --quiet ". ($pullSpec?join(" ",array_map('escapeshellarg', $pullSpec)):'') );
if (!$this->options['pull']) { if (!$this->options['pull']) {
$this->log->append("Refreshing updated containers..."); $this->log->append("Refreshing updated containers...");
$this->exec("docker-compose up -d"); $this->exec("docker-compose up -d". ($pullSpec?join(" ",array_map('escapeshellarg', $pullSpec)):'') );
if ($this->options['prune']) { if ($this->options['prune']) {
$this->log->append("Pruning dangling images..."); $this->log->append("Pruning dangling images...");
$this->exec("docker image prune -f"); $this->exec("docker image prune -f");