From 555293ab614498df76a5e0661a1bac6bbeb0b363 Mon Sep 17 00:00:00 2001 From: Tony Messias Date: Fri, 3 May 2024 16:28:05 -0300 Subject: [PATCH 1/6] Tweaks how arm only images are filtered (also includes aarch64 as a valid ARM arch) --- app/Shell/DockerTags.php | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/app/Shell/DockerTags.php b/app/Shell/DockerTags.php index 6b977a66..58b08411 100644 --- a/app/Shell/DockerTags.php +++ b/app/Shell/DockerTags.php @@ -11,6 +11,7 @@ class DockerTags { protected $guzzle; protected $service; + protected $armArchitectures = ['arm64', 'aarch64']; public function __construct(Client $guzzle, BaseService $service) { @@ -46,8 +47,8 @@ public function getTags(): Collection $platform = $this->platform(); [$numericTags, $alphaTags] = collect($response['results']) - ->when($platform === 'arm64', $this->armSupportedImagesOnlyFilter()) - ->when($platform !== 'arm64', $this->nonArmOnlySupportImagesFilter()) + ->when(in_array($platform, $this->armArchitectures, true), $this->armSupportedImagesOnlyFilter()) + ->when(! in_array($platform, $this->armArchitectures, true), $this->nonArmOnlySupportImagesFilter()) ->pluck('name') ->partition(function ($tag) { return is_numeric($tag[0]); @@ -73,9 +74,15 @@ protected function armSupportedImagesOnlyFilter() { return function ($tags) { return $tags->filter(function ($tag) { - return collect($tag['images']) - ->pluck('architecture') - ->contains('arm64'); + $supportedArchs = collect($tag['images'])->pluck('architecture'); + + foreach ($this->armArchitectures as $arch) { + if ($supportedArchs->contains($arch)) { + return true; + } + } + + return false; }); }; } @@ -98,7 +105,7 @@ protected function nonArmOnlySupportImagesFilter() // still be other options in the supported architectures // so we can consider that the tag is not arm-only. - return $supportedArchitectures->diff(['arm64'])->count() > 0; + return $supportedArchitectures->diff($this->armArchitectures)->count() > 0; }); }; } From 4287c572ed9653a400fc584e68e3d25554550f59 Mon Sep 17 00:00:00 2001 From: Tony Messias Date: Fri, 3 May 2024 17:48:32 -0300 Subject: [PATCH 2/6] Adds a version comparator class --- app/Shell/VersionComparator.php | 67 +++++++++++++++++++++++++ tests/Feature/VersionComparatorTest.php | 32 ++++++++++++ 2 files changed, 99 insertions(+) create mode 100644 app/Shell/VersionComparator.php create mode 100644 tests/Feature/VersionComparatorTest.php diff --git a/app/Shell/VersionComparator.php b/app/Shell/VersionComparator.php new file mode 100644 index 00000000..d5efa70d --- /dev/null +++ b/app/Shell/VersionComparator.php @@ -0,0 +1,67 @@ +isSemver($a) && $this->isSemver($b)) { + return $this->compareSemver($a, $b); + } + + // Bump "a" if it is semver and "b" is not... + if ($this->isSemver($a)) { + return -1; + } + + // Bump "b" if it is semver and "a" is not... + if ($this->isSemver($b)) { + return 1; + } + + // Otherwise, compare alphabetically... + return strcmp($a, $b); + } + + private function isSemver(string $version): bool + { + return preg_match('/^[\d.]+$/', $version); + } + + private function compareSemver($a, $b) + { + // We're going to split up the versions in parts and we'll compare each + // part individually. If any of the version doesn't contain the part, + // we'll consider any missing part as higher then the specific one. + + $aParts = explode('.', $a); + $bParts = explode('.', $b); + + $maxParts = max(count($aParts), count($bParts)); + + foreach (range(0, $maxParts) as $depth) { + $aValue = isset($aParts[$depth]) ? intval($aParts[$depth]) : PHP_INT_MAX; + $bValue = isset($bParts[$depth]) ? intval($bParts[$depth]) : PHP_INT_MAX; + + if ($aValue === $bValue) { + continue; + } + + return $aValue > $bValue ? -1 : 1; + } + + return 0; + } +} diff --git a/tests/Feature/VersionComparatorTest.php b/tests/Feature/VersionComparatorTest.php new file mode 100644 index 00000000..4a53e396 --- /dev/null +++ b/tests/Feature/VersionComparatorTest.php @@ -0,0 +1,32 @@ +assertEquals($expectedOrder, $this->sort($versions)); + } + + private function sort(array $versions): array + { + return collect($versions) + ->sort(new VersionComparator) + ->values() + ->all(); + } + +} From e5517b685d5412c193129e5881fb4569a5fc2c3f Mon Sep 17 00:00:00 2001 From: Tony Messias Date: Fri, 3 May 2024 18:32:42 -0300 Subject: [PATCH 3/6] wip --- app/Shell/DockerTags.php | 17 +++------------- app/Shell/VersionComparator.php | 27 +++++++++++++++++++++++++ tests/Feature/DockerTagsTest.php | 2 +- tests/Feature/VersionComparatorTest.php | 1 + 4 files changed, 32 insertions(+), 15 deletions(-) diff --git a/app/Shell/DockerTags.php b/app/Shell/DockerTags.php index 58b08411..dcc0bf94 100644 --- a/app/Shell/DockerTags.php +++ b/app/Shell/DockerTags.php @@ -46,23 +46,12 @@ public function getTags(): Collection $response = json_decode($this->getTagsResponse()->getContents(), true); $platform = $this->platform(); - [$numericTags, $alphaTags] = collect($response['results']) + return collect($response['results']) ->when(in_array($platform, $this->armArchitectures, true), $this->armSupportedImagesOnlyFilter()) ->when(! in_array($platform, $this->armArchitectures, true), $this->nonArmOnlySupportImagesFilter()) ->pluck('name') - ->partition(function ($tag) { - return is_numeric($tag[0]); - }); - - $sortedTags = $alphaTags->sortDesc(SORT_NATURAL) - ->concat($numericTags->sortDesc(SORT_NATURAL)); - - if ($sortedTags->contains('latest')) { - $sortedTags->splice($sortedTags->search('latest'), 1); - $sortedTags->prepend('latest'); - } - - return $sortedTags->values()->filter(); + ->sort(new VersionComparator) + ->values(); } /** diff --git a/app/Shell/VersionComparator.php b/app/Shell/VersionComparator.php index d5efa70d..ecf97eb4 100644 --- a/app/Shell/VersionComparator.php +++ b/app/Shell/VersionComparator.php @@ -16,11 +16,18 @@ public function __invoke(string $a, string $b): int return 1; } + $a = preg_replace('/^v/', '', $a); + $b = preg_replace('/^v/', '', $b); + // If both versions are regular semver versions (numbers and dots only), we'll compare them as semver... if ($this->isSemver($a) && $this->isSemver($b)) { return $this->compareSemver($a, $b); } + if ($this->startsAsSemver($a) && $this->startsAsSemver($b)) { + return $this->compareSemver($this->semverFragment($a), $this->semverFragment($b)); + } + // Bump "a" if it is semver and "b" is not... if ($this->isSemver($a)) { return -1; @@ -35,6 +42,26 @@ public function __invoke(string $a, string $b): int return strcmp($a, $b); } + private function startsAsSemver(string $version): bool + { + return preg_match('/^[\d.]+/', $version); + } + + private function semverFragment(string $version): string + { + // When comparing semver version with those containing letters, we'll + // append a ".99" to the ones with letters so the actual semver ones + // will be ranked higher than the ones with letters. + + preg_match('/^[\d.]+/', $version, $matches); + + if ($matches[0] === $version) { + return $version; + } + + return $matches[0] . ".99"; + } + private function isSemver(string $version): bool { return preg_match('/^[\d.]+$/', $version); diff --git a/tests/Feature/DockerTagsTest.php b/tests/Feature/DockerTagsTest.php index 3197e30b..83e274f4 100644 --- a/tests/Feature/DockerTagsTest.php +++ b/tests/Feature/DockerTagsTest.php @@ -42,7 +42,7 @@ function it_sorts_the_versions_naturally() $tags = collect($dockerTags->getTags()); $this->assertEquals('latest', $tags->shift()); - $this->assertEquals('bullseye', $tags->shift()); + $this->assertEquals('16', $tags->shift()); } /** @test */ diff --git a/tests/Feature/VersionComparatorTest.php b/tests/Feature/VersionComparatorTest.php index 4a53e396..c718aa03 100644 --- a/tests/Feature/VersionComparatorTest.php +++ b/tests/Feature/VersionComparatorTest.php @@ -15,6 +15,7 @@ class VersionComparatorTest extends TestCase * [["5.0", "8.0", "latest"], ["latest", "8.0", "5.0"]] * [["8.0.43", "8.0", "latest"], ["latest", "8.0", "8.0.43"]] * [["8.0.4-oraclelinux8", "8.0.4", "latest"], ["latest", "8.0.4", "8.0.4-oraclelinux8"]] + * [["8.0.4-oraclelinux8", "5.0.1", "8.0.4", "latest", "5.0.1-oraclelinux8"], ["latest", "8.0.4", "8.0.4-oraclelinux8", "5.0.1", "5.0.1-oraclelinux8"]] */ public function compares($versions, $expectedOrder) { From 01ce5c7fafc7ae2962a2d299913e42a3d38b1ce7 Mon Sep 17 00:00:00 2001 From: Tony Messias Date: Sat, 4 May 2024 11:07:23 -0300 Subject: [PATCH 4/6] Adds composer/semver to the version comparator --- app/Shell/VersionComparator.php | 71 +++++-------------------- composer.json | 1 + tests/Feature/VersionComparatorTest.php | 4 +- 3 files changed, 16 insertions(+), 60 deletions(-) diff --git a/app/Shell/VersionComparator.php b/app/Shell/VersionComparator.php index ecf97eb4..8609c58d 100644 --- a/app/Shell/VersionComparator.php +++ b/app/Shell/VersionComparator.php @@ -2,6 +2,8 @@ namespace App\Shell; +use Composer\Semver\Comparator; + class VersionComparator { public function __invoke(string $a, string $b): int @@ -16,79 +18,32 @@ public function __invoke(string $a, string $b): int return 1; } - $a = preg_replace('/^v/', '', $a); - $b = preg_replace('/^v/', '', $b); - - // If both versions are regular semver versions (numbers and dots only), we'll compare them as semver... - if ($this->isSemver($a) && $this->isSemver($b)) { - return $this->compareSemver($a, $b); + if ($this->startsAsSemver($a) && ! $this->startsAsSemver($b)) { + return -1; } - if ($this->startsAsSemver($a) && $this->startsAsSemver($b)) { - return $this->compareSemver($this->semverFragment($a), $this->semverFragment($b)); + if ($this->startsAsSemver($b) && ! $this->startsAsSemver($a)) { + return 1; } - // Bump "a" if it is semver and "b" is not... - if ($this->isSemver($a)) { + if ($this->stableSemver($a) && ! $this->stableSemver($b)) { return -1; } - // Bump "b" if it is semver and "a" is not... - if ($this->isSemver($b)) { + if ($this->stableSemver($b) && ! $this->stableSemver($a)) { return 1; } - // Otherwise, compare alphabetically... - return strcmp($a, $b); - } - - private function startsAsSemver(string $version): bool - { - return preg_match('/^[\d.]+/', $version); + return Comparator::greaterThan(preg_replace('/^v/', '', $a), preg_replace('/^v/', '', $b)) ? -1 : 1; } - private function semverFragment(string $version): string + private function stableSemver(string $version): bool { - // When comparing semver version with those containing letters, we'll - // append a ".99" to the ones with letters so the actual semver ones - // will be ranked higher than the ones with letters. - - preg_match('/^[\d.]+/', $version, $matches); - - if ($matches[0] === $version) { - return $version; - } - - return $matches[0] . ".99"; + return preg_match('/^v?[\d.]+$/', $version); } - private function isSemver(string $version): bool - { - return preg_match('/^[\d.]+$/', $version); - } - - private function compareSemver($a, $b) + private function startsAsSemver(string $version): bool { - // We're going to split up the versions in parts and we'll compare each - // part individually. If any of the version doesn't contain the part, - // we'll consider any missing part as higher then the specific one. - - $aParts = explode('.', $a); - $bParts = explode('.', $b); - - $maxParts = max(count($aParts), count($bParts)); - - foreach (range(0, $maxParts) as $depth) { - $aValue = isset($aParts[$depth]) ? intval($aParts[$depth]) : PHP_INT_MAX; - $bValue = isset($bParts[$depth]) ? intval($bParts[$depth]) : PHP_INT_MAX; - - if ($aValue === $bValue) { - continue; - } - - return $aValue > $bValue ? -1 : 1; - } - - return 0; + return preg_match('/^v?[\d.]+/', $version); } } diff --git a/composer.json b/composer.json index 26a9c96b..702d9367 100644 --- a/composer.json +++ b/composer.json @@ -20,6 +20,7 @@ "ext-json": "*", "ext-pcntl": "*", "ext-posix": "*", + "composer/semver": "^3.4", "guzzlehttp/psr7": "^2.6" }, "require-dev": { diff --git a/tests/Feature/VersionComparatorTest.php b/tests/Feature/VersionComparatorTest.php index c718aa03..a36c096d 100644 --- a/tests/Feature/VersionComparatorTest.php +++ b/tests/Feature/VersionComparatorTest.php @@ -13,9 +13,9 @@ class VersionComparatorTest extends TestCase * @testWith [["latest", "8.0"], ["latest", "8.0"]] * [["8.0", "latest"], ["latest", "8.0"]] * [["5.0", "8.0", "latest"], ["latest", "8.0", "5.0"]] - * [["8.0.43", "8.0", "latest"], ["latest", "8.0", "8.0.43"]] + * [["8.0.43", "8.0", "latest"], ["latest", "8.0.43", "8.0"]] * [["8.0.4-oraclelinux8", "8.0.4", "latest"], ["latest", "8.0.4", "8.0.4-oraclelinux8"]] - * [["8.0.4-oraclelinux8", "5.0.1", "8.0.4", "latest", "5.0.1-oraclelinux8"], ["latest", "8.0.4", "8.0.4-oraclelinux8", "5.0.1", "5.0.1-oraclelinux8"]] + * [["8.0.4-oraclelinux8", "5.0.1", "8.0.4", "latest", "5.0.1-oraclelinux8"], ["latest", "8.0.4", "5.0.1", "8.0.4-oraclelinux8", "5.0.1-oraclelinux8"]] */ public function compares($versions, $expectedOrder) { From f44cd17ef635a743c66b469fb7b7f26fbc9a54ed Mon Sep 17 00:00:00 2001 From: Tony Messias Date: Sat, 4 May 2024 11:09:47 -0300 Subject: [PATCH 5/6] Use the version comparator --- app/Services/MailDev.php | 12 ------------ app/Shell/DockerTags.php | 4 ++-- app/Shell/ElasticDockerTags.php | 7 ++++++- app/Shell/GitHubDockerTags.php | 4 +++- app/Shell/MicrosoftDockerTags.php | 2 +- app/Shell/MinioDockerTags.php | 24 +++++++----------------- app/Shell/MongoDockerTags.php | 7 ++++--- app/Shell/QuayDockerTags.php | 5 +---- tests/Feature/DockerTagsTest.php | 2 +- 9 files changed, 25 insertions(+), 42 deletions(-) diff --git a/app/Services/MailDev.php b/app/Services/MailDev.php index fe3134de..3bd42753 100644 --- a/app/Services/MailDev.php +++ b/app/Services/MailDev.php @@ -9,18 +9,6 @@ class MailDev extends BaseService protected $organization = 'maildev'; protected $imageName = 'maildev'; protected $defaultPort = 1025; - protected $defaultPrompts = [ - [ - 'shortname' => 'port', - 'prompt' => 'Which host port would you like %s to use?', - // Default is set in the constructor - ], - [ - 'shortname' => 'tag', - 'prompt' => 'Which tag (version) of %s would you like to use?', - 'default' => '2.0.5', - ], - ]; protected $prompts = [ [ 'shortname' => 'web_port', diff --git a/app/Shell/DockerTags.php b/app/Shell/DockerTags.php index dcc0bf94..0c8bc015 100644 --- a/app/Shell/DockerTags.php +++ b/app/Shell/DockerTags.php @@ -30,8 +30,8 @@ public function resolveTag($tag): string public function getLatestTag(): string { - $numericTags = $this->getTags()->reject(function ($tag) { - return ! is_numeric($tag[0]); + $numericTags = $this->getTags()->filter(function ($tag) { + return preg_match('/^v?\d/', $tag); }); if ($numericTags->isEmpty()) { diff --git a/app/Shell/ElasticDockerTags.php b/app/Shell/ElasticDockerTags.php index 75b426b1..2efc750f 100644 --- a/app/Shell/ElasticDockerTags.php +++ b/app/Shell/ElasticDockerTags.php @@ -14,7 +14,12 @@ public function getTags(): Collection ->reverse() ->filter(function ($tag) { return ! Str::contains($tag, 'SNAPSHOT'); - }); + }) + ->filter(function ($tag) { + return ! Str::startsWith($tag, 'sha256-'); + }) + ->sort(new VersionComparator) + ->values(); } protected function getAuthResponse(): StreamInterface diff --git a/app/Shell/GitHubDockerTags.php b/app/Shell/GitHubDockerTags.php index b9a64e8e..63602ec6 100644 --- a/app/Shell/GitHubDockerTags.php +++ b/app/Shell/GitHubDockerTags.php @@ -10,7 +10,9 @@ class GitHubDockerTags extends DockerTags { public function getTags(): Collection { - return collect(json_decode($this->getTagsResponse(), true)['tags']); + return collect(json_decode($this->getTagsResponse(), true)['tags']) + ->sort(new VersionComparator) + ->values(); } public function getLatestTag(): string diff --git a/app/Shell/MicrosoftDockerTags.php b/app/Shell/MicrosoftDockerTags.php index 6197676c..054ee5d1 100644 --- a/app/Shell/MicrosoftDockerTags.php +++ b/app/Shell/MicrosoftDockerTags.php @@ -14,7 +14,7 @@ public function getLatestTag(): string public function getTags(): Collection { return collect(json_decode($this->getTagsResponse(), true)['tags']) - ->reverse() + ->sort(new VersionComparator) ->values(); } diff --git a/app/Shell/MinioDockerTags.php b/app/Shell/MinioDockerTags.php index 04f0bfc4..616283bc 100644 --- a/app/Shell/MinioDockerTags.php +++ b/app/Shell/MinioDockerTags.php @@ -29,23 +29,13 @@ public function getLatestTag(): string public function getTags(): Collection { $response = json_decode($this->getTagsResponse()->getContents(), true); - $tags = collect($response['results'])->map->name->reject(function ($tag) { - return Str::endsWith($tag, 'fips'); - }); - - [$releaseTags, $otherTags] = $tags - ->partition(function ($tag) { - return Str::startsWith($tag, 'RELEASE.'); - }); - - $sortedTags = $releaseTags->sortDesc(SORT_NATURAL) - ->concat($otherTags->sortDesc(SORT_NATURAL)); - - if ($sortedTags->contains('latest')) { - $sortedTags->splice($sortedTags->search('latest'), 1); - $sortedTags->prepend('latest'); - } - return $sortedTags; + return collect($response['results']) + ->pluck('name') + ->reject(function ($tag) { + return Str::endsWith($tag, 'fips'); + }) + ->sort(new VersionComparator) + ->values(); } } diff --git a/app/Shell/MongoDockerTags.php b/app/Shell/MongoDockerTags.php index 8e83a2ff..21dc8340 100644 --- a/app/Shell/MongoDockerTags.php +++ b/app/Shell/MongoDockerTags.php @@ -11,10 +11,11 @@ public function getTags(): Collection { $response = json_decode($this->getTagsResponse()->getContents(), true); return collect($response['results']) - ->map - ->name + ->pluck('name') + ->sort(new VersionComparator) ->filter(function ($tag) { return ! Str::contains($tag, 'windowsservercore'); - }); + }) + ->values(); } } diff --git a/app/Shell/QuayDockerTags.php b/app/Shell/QuayDockerTags.php index efbabb3f..98c09710 100644 --- a/app/Shell/QuayDockerTags.php +++ b/app/Shell/QuayDockerTags.php @@ -20,10 +20,7 @@ public function getLatestTag(): string public function getTags(): Collection { return collect(json_decode($this->getTagsResponse()->getContents(), true)['tags']) - ->map(function ($release) { - return $release['name']; - }) - ; + ->pluck('name'); } protected function tagsUrlTemplate(): string diff --git a/tests/Feature/DockerTagsTest.php b/tests/Feature/DockerTagsTest.php index 83e274f4..64c54a0c 100644 --- a/tests/Feature/DockerTagsTest.php +++ b/tests/Feature/DockerTagsTest.php @@ -42,7 +42,7 @@ function it_sorts_the_versions_naturally() $tags = collect($dockerTags->getTags()); $this->assertEquals('latest', $tags->shift()); - $this->assertEquals('16', $tags->shift()); + $this->assertEquals('16.2', $tags->shift()); } /** @test */ From 797b1d0081406f6449b0930b3fc85e328677a764 Mon Sep 17 00:00:00 2001 From: Tony Messias Date: Sat, 4 May 2024 11:12:39 -0300 Subject: [PATCH 6/6] Remove space --- tests/Feature/VersionComparatorTest.php | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/Feature/VersionComparatorTest.php b/tests/Feature/VersionComparatorTest.php index a36c096d..0c771986 100644 --- a/tests/Feature/VersionComparatorTest.php +++ b/tests/Feature/VersionComparatorTest.php @@ -29,5 +29,4 @@ private function sort(array $versions): array ->values() ->all(); } - }