Skip to content

Commit

Permalink
Merge pull request #341 from tighten/tm/version-sorting
Browse files Browse the repository at this point in the history
Fix Images Version Sorting
  • Loading branch information
tonysm committed May 5, 2024
2 parents af2abb3 + 797b1d0 commit c9ca14d
Show file tree
Hide file tree
Showing 12 changed files with 123 additions and 62 deletions.
12 changes: 0 additions & 12 deletions app/Services/MailDev.php
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down
40 changes: 18 additions & 22 deletions app/Shell/DockerTags.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ class DockerTags
{
protected $guzzle;
protected $service;
protected $armArchitectures = ['arm64', 'aarch64'];

public function __construct(Client $guzzle, BaseService $service)
{
Expand All @@ -29,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()) {
Expand All @@ -45,23 +46,12 @@ public function getTags(): Collection
$response = json_decode($this->getTagsResponse()->getContents(), true);
$platform = $this->platform();

[$numericTags, $alphaTags] = collect($response['results'])
->when($platform === 'arm64', $this->armSupportedImagesOnlyFilter())
->when($platform !== 'arm64', $this->nonArmOnlySupportImagesFilter())
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();
}

/**
Expand All @@ -73,9 +63,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;
});
};
}
Expand All @@ -98,7 +94,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;
});
};
}
Expand Down
7 changes: 6 additions & 1 deletion app/Shell/ElasticDockerTags.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion app/Shell/GitHubDockerTags.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion app/Shell/MicrosoftDockerTags.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}

Expand Down
24 changes: 7 additions & 17 deletions app/Shell/MinioDockerTags.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
7 changes: 4 additions & 3 deletions app/Shell/MongoDockerTags.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
}
}
5 changes: 1 addition & 4 deletions app/Shell/QuayDockerTags.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
49 changes: 49 additions & 0 deletions app/Shell/VersionComparator.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

namespace App\Shell;

use Composer\Semver\Comparator;

class VersionComparator
{
public function __invoke(string $a, string $b): int
{
// Bump "a" if it is "latest"...
if ($a === 'latest') {
return -1;
}

// Bump "b" if it is "latest"...
if ($b === 'latest') {
return 1;
}

if ($this->startsAsSemver($a) && ! $this->startsAsSemver($b)) {
return -1;
}

if ($this->startsAsSemver($b) && ! $this->startsAsSemver($a)) {
return 1;
}

if ($this->stableSemver($a) && ! $this->stableSemver($b)) {
return -1;
}

if ($this->stableSemver($b) && ! $this->stableSemver($a)) {
return 1;
}

return Comparator::greaterThan(preg_replace('/^v/', '', $a), preg_replace('/^v/', '', $b)) ? -1 : 1;
}

private function stableSemver(string $version): bool
{
return preg_match('/^v?[\d.]+$/', $version);
}

private function startsAsSemver(string $version): bool
{
return preg_match('/^v?[\d.]+/', $version);
}
}
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"ext-json": "*",
"ext-pcntl": "*",
"ext-posix": "*",
"composer/semver": "^3.4",
"guzzlehttp/psr7": "^2.6"
},
"require-dev": {
Expand Down
2 changes: 1 addition & 1 deletion tests/Feature/DockerTagsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.2', $tags->shift());
}

/** @test */
Expand Down
32 changes: 32 additions & 0 deletions tests/Feature/VersionComparatorTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<?php

namespace Tests\Feature;

use App\Shell\VersionComparator;
use Tests\TestCase;

class VersionComparatorTest extends TestCase
{
/**
* @test
*
* @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.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", "5.0.1", "8.0.4-oraclelinux8", "5.0.1-oraclelinux8"]]
*/
public function compares($versions, $expectedOrder)
{
$this->assertEquals($expectedOrder, $this->sort($versions));
}

private function sort(array $versions): array
{
return collect($versions)
->sort(new VersionComparator)
->values()
->all();
}
}

0 comments on commit c9ca14d

Please sign in to comment.