From 6c422eafe680fbaf10d0accf7dcbbe475663328a Mon Sep 17 00:00:00 2001 From: Christopher Vagnetoft Date: Sat, 3 Sep 2022 00:11:52 +0200 Subject: [PATCH] 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 --- CHANGES | 9 +++++ bin/freshdocker | 10 ++++++ src/Configuration/ComposeConfiguration.php | 18 ++++++++++ src/ImageReference.php | 5 +++ src/Refresher.php | 41 ++++++++++++++++++---- 5 files changed, 77 insertions(+), 6 deletions(-) diff --git a/CHANGES b/CHANGES index 43245f3..126adcb 100644 --- a/CHANGES +++ b/CHANGES @@ -32,3 +32,12 @@ - Bugfix: The lockfile is no longer removed automatically, but only if it was 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 diff --git a/bin/freshdocker b/bin/freshdocker index 6b90e64..c020dc0 100755 --- a/bin/freshdocker +++ b/bin/freshdocker @@ -5,6 +5,16 @@ require_once __DIR__."/../vendor/autoload.php"; if (file_exists(__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 { define("APP_VERSION", "DEV"); define("BUILD_DATE", "src"); diff --git a/src/Configuration/ComposeConfiguration.php b/src/Configuration/ComposeConfiguration.php index d16d233..5b5ddb1 100644 --- a/src/Configuration/ComposeConfiguration.php +++ b/src/Configuration/ComposeConfiguration.php @@ -9,6 +9,8 @@ class ComposeConfiguration private array $checks = []; + private array $services = []; + public function __construct(string $configfile) { $this->loadComposeFile($configfile); @@ -23,6 +25,10 @@ class ComposeConfiguration if ($image && !in_array($image,$this->checks)) { $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; } + + 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); + } } \ No newline at end of file diff --git a/src/ImageReference.php b/src/ImageReference.php index 8e2b14a..b0371fa 100644 --- a/src/ImageReference.php +++ b/src/ImageReference.php @@ -53,4 +53,9 @@ class ImageReference { return $this->tag; } + + public function __toString() + { + return sprintf("%s/%s:%s", $this->registry, $this->image, $this->tag); + } } \ No newline at end of file diff --git a/src/Refresher.php b/src/Refresher.php index 50ff4f8..c67cda2 100644 --- a/src/Refresher.php +++ b/src/Refresher.php @@ -21,6 +21,7 @@ namespace NoccyLabs\FreshDocker; */ +use GuzzleHttp\Exception\RequestException; use NoccyLabs\FreshDocker\State\Log; use NoccyLabs\FreshDocker\Configuration\ComposeConfiguration; use NoccyLabs\FreshDocker\Configuration\LocalConfiguration; @@ -32,6 +33,7 @@ use NoccyLabs\FreshDocker\ImageReference; use NoccyLabs\FreshDocker\Registry\RegistryV2Client; use NoccyLabs\FreshDocker\State\PersistentState; use NoccyLabs\FreshDocker\State\Lockfile; +use Phar; class Refresher { @@ -48,6 +50,8 @@ class Refresher 'path' => [ 'd:', 'dir:', "Change working directory", "FRESH_DIR" ], 'image' => [ 'i:', 'image:', "Check a specific image instead of images from config", "FRESH_IMAGE" ], '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" ], '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 ], @@ -151,6 +155,13 @@ class Refresher { $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("Check for updates to docker images or compose stacks.\n\n"); printf("Usage:\n %s [options]\n\n", basename($GLOBALS['argv'][0])); @@ -215,7 +226,7 @@ class Refresher if ($updated !== null) { $this->callHooks($updated, 'before'); $this->callScript('before'); - $this->doUpdate(); + $this->doUpdate($updated); $this->callHooks($updated, 'after'); $this->callScript('after'); } @@ -385,8 +396,12 @@ class Refresher } $client = new RegistryV2Client($reg, $credentials); - - $status = $client->getImageStatus($ref->getImage(), $ref->getTag()); + try { + $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']; $oldHash = $this->state->get($image); $newHash = $status['hash']; @@ -408,13 +423,27 @@ class Refresher 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->exec("docker-compose pull --quiet"); + $this->exec("docker-compose pull --quiet ". ($pullSpec?join(" ",array_map('escapeshellarg', $pullSpec)):'') ); if (!$this->options['pull']) { $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']) { $this->log->append("Pruning dangling images..."); $this->exec("docker image prune -f");