Merge pull request #6472 from saurabhraghuvanshii/allow-dep
[Enhancement]: allow deploy from container image hash
This commit is contained in:
commit
4b08ac9b6f
8 changed files with 309 additions and 108 deletions
|
|
@ -1512,9 +1512,32 @@ private function create_application(Request $request, $type)
|
|||
if ($return instanceof \Illuminate\Http\JsonResponse) {
|
||||
return $return;
|
||||
}
|
||||
if (! $request->docker_registry_image_tag) {
|
||||
$request->offsetSet('docker_registry_image_tag', 'latest');
|
||||
// Process docker image name and tag for SHA256 digests
|
||||
$dockerImageName = $request->docker_registry_image_name;
|
||||
$dockerImageTag = $request->docker_registry_image_tag;
|
||||
|
||||
// Strip 'sha256:' prefix if user provided it in the tag
|
||||
if ($dockerImageTag) {
|
||||
$dockerImageTag = preg_replace('/^sha256:/i', '', trim($dockerImageTag));
|
||||
}
|
||||
|
||||
// Remove @sha256 from image name if user added it
|
||||
if ($dockerImageName) {
|
||||
$dockerImageName = preg_replace('/@sha256$/i', '', trim($dockerImageName));
|
||||
}
|
||||
|
||||
// Check if tag is a valid SHA256 hash (64 hex characters)
|
||||
$isSha256Hash = $dockerImageTag && preg_match('/^[a-f0-9]{64}$/i', $dockerImageTag);
|
||||
|
||||
// Append @sha256 to image name if using digest and not already present
|
||||
if ($isSha256Hash && ! str_ends_with($dockerImageName, '@sha256')) {
|
||||
$dockerImageName .= '@sha256';
|
||||
}
|
||||
|
||||
// Set processed values back to request
|
||||
$request->offsetSet('docker_registry_image_name', $dockerImageName);
|
||||
$request->offsetSet('docker_registry_image_tag', $dockerImageTag ?: 'latest');
|
||||
|
||||
$application = new Application;
|
||||
removeUnnecessaryFieldsFromRequest($request);
|
||||
|
||||
|
|
|
|||
|
|
@ -503,7 +503,12 @@ private function deploy_dockerimage_buildpack()
|
|||
} else {
|
||||
$this->dockerImageTag = $this->application->docker_registry_image_tag;
|
||||
}
|
||||
$this->application_deployment_queue->addLogEntry("Starting deployment of {$this->dockerImage}:{$this->dockerImageTag} to {$this->server->name}.");
|
||||
|
||||
// Check if this is an image hash deployment
|
||||
$isImageHash = str($this->dockerImageTag)->startsWith('sha256-');
|
||||
$displayName = $isImageHash ? "{$this->dockerImage}@sha256:".str($this->dockerImageTag)->after('sha256-') : "{$this->dockerImage}:{$this->dockerImageTag}";
|
||||
|
||||
$this->application_deployment_queue->addLogEntry("Starting deployment of {$displayName} to {$this->server->name}.");
|
||||
$this->generate_image_names();
|
||||
$this->prepare_builder_image();
|
||||
$this->generate_compose_file();
|
||||
|
|
@ -934,7 +939,13 @@ private function generate_image_names()
|
|||
$this->production_image_name = "{$this->application->uuid}:latest";
|
||||
}
|
||||
} elseif ($this->application->build_pack === 'dockerimage') {
|
||||
$this->production_image_name = "{$this->dockerImage}:{$this->dockerImageTag}";
|
||||
// Check if this is an image hash deployment
|
||||
if (str($this->dockerImageTag)->startsWith('sha256-')) {
|
||||
$hash = str($this->dockerImageTag)->after('sha256-');
|
||||
$this->production_image_name = "{$this->dockerImage}@sha256:{$hash}";
|
||||
} else {
|
||||
$this->production_image_name = "{$this->dockerImage}:{$this->dockerImageTag}";
|
||||
}
|
||||
} elseif ($this->pull_request_id !== 0) {
|
||||
if ($this->application->docker_registry_image_name) {
|
||||
$this->build_image_name = "{$this->application->docker_registry_image_name}:pr-{$this->pull_request_id}-build";
|
||||
|
|
|
|||
|
|
@ -12,7 +12,11 @@
|
|||
|
||||
class DockerImage extends Component
|
||||
{
|
||||
public string $dockerImage = '';
|
||||
public string $imageName = '';
|
||||
|
||||
public string $imageTag = '';
|
||||
|
||||
public string $imageSha256 = '';
|
||||
|
||||
public array $parameters;
|
||||
|
||||
|
|
@ -26,12 +30,41 @@ public function mount()
|
|||
|
||||
public function submit()
|
||||
{
|
||||
// Strip 'sha256:' prefix if user pasted it
|
||||
if ($this->imageSha256) {
|
||||
$this->imageSha256 = preg_replace('/^sha256:/i', '', trim($this->imageSha256));
|
||||
}
|
||||
|
||||
// Remove @sha256 from image name if user added it
|
||||
if ($this->imageName) {
|
||||
$this->imageName = preg_replace('/@sha256$/i', '', trim($this->imageName));
|
||||
}
|
||||
|
||||
$this->validate([
|
||||
'dockerImage' => 'required',
|
||||
'imageName' => ['required', 'string'],
|
||||
'imageTag' => ['nullable', 'string', 'regex:/^[a-z0-9][a-z0-9._-]*$/i'],
|
||||
'imageSha256' => ['nullable', 'string', 'regex:/^[a-f0-9]{64}$/i'],
|
||||
]);
|
||||
|
||||
// Validate that either tag or sha256 is provided, but not both
|
||||
if ($this->imageTag && $this->imageSha256) {
|
||||
$this->addError('imageTag', 'Provide either a tag or SHA256 digest, not both.');
|
||||
$this->addError('imageSha256', 'Provide either a tag or SHA256 digest, not both.');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Build the full Docker image string
|
||||
if ($this->imageSha256) {
|
||||
$dockerImage = $this->imageName.'@sha256:'.$this->imageSha256;
|
||||
} elseif ($this->imageTag) {
|
||||
$dockerImage = $this->imageName.':'.$this->imageTag;
|
||||
} else {
|
||||
$dockerImage = $this->imageName.':latest';
|
||||
}
|
||||
|
||||
$parser = new DockerImageParser;
|
||||
$parser->parse($this->dockerImage);
|
||||
$parser->parse($dockerImage);
|
||||
|
||||
$destination_uuid = $this->query['destination'];
|
||||
$destination = StandaloneDocker::where('uuid', $destination_uuid)->first();
|
||||
|
|
@ -45,6 +78,16 @@ public function submit()
|
|||
|
||||
$project = Project::where('uuid', $this->parameters['project_uuid'])->first();
|
||||
$environment = $project->load(['environments'])->environments->where('uuid', $this->parameters['environment_uuid'])->first();
|
||||
|
||||
// Determine the image tag based on whether it's a hash or regular tag
|
||||
$imageTag = $parser->isImageHash() ? 'sha256-'.$parser->getTag() : $parser->getTag();
|
||||
|
||||
// Append @sha256 to image name if using digest and not already present
|
||||
$imageName = $parser->getFullImageNameWithoutTag();
|
||||
if ($parser->isImageHash() && ! str_ends_with($imageName, '@sha256')) {
|
||||
$imageName .= '@sha256';
|
||||
}
|
||||
|
||||
$application = Application::create([
|
||||
'name' => 'docker-image-'.new Cuid2,
|
||||
'repository_project_id' => 0,
|
||||
|
|
@ -52,7 +95,7 @@ public function submit()
|
|||
'git_branch' => 'main',
|
||||
'build_pack' => 'dockerimage',
|
||||
'ports_exposes' => 80,
|
||||
'docker_registry_image_name' => $parser->getFullImageNameWithoutTag(),
|
||||
'docker_registry_image_name' => $imageName,
|
||||
'docker_registry_image_tag' => $parser->getTag(),
|
||||
'environment_id' => $environment->id,
|
||||
'destination_id' => $destination->id,
|
||||
|
|
|
|||
41
app/Rules/DockerImageFormat.php
Normal file
41
app/Rules/DockerImageFormat.php
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
namespace App\Rules;
|
||||
|
||||
use Closure;
|
||||
use Illuminate\Contracts\Validation\ValidationRule;
|
||||
|
||||
class DockerImageFormat implements ValidationRule
|
||||
{
|
||||
/**
|
||||
* Run the validation rule.
|
||||
*
|
||||
* @param \Closure(string, ?string=): \Illuminate\Translation\PotentiallyTranslatedString $fail
|
||||
*/
|
||||
public function validate(string $attribute, mixed $value, Closure $fail): void
|
||||
{
|
||||
// Check if the value contains ":sha256:" or ":sha" which is incorrect format
|
||||
if (preg_match('/:sha256?:/i', $value)) {
|
||||
$fail('The :attribute must use @ before sha256 digest (e.g., image@sha256:hash, not image:sha256:hash).');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Valid formats:
|
||||
// 1. image:tag (e.g., nginx:latest)
|
||||
// 2. registry/image:tag (e.g., ghcr.io/user/app:v1.2.3)
|
||||
// 3. image@sha256:hash (e.g., nginx@sha256:abc123...)
|
||||
// 4. registry/image@sha256:hash
|
||||
// 5. registry:port/image:tag (e.g., localhost:5000/app:latest)
|
||||
|
||||
$pattern = '/^
|
||||
(?:[a-z0-9]+(?:[._-][a-z0-9]+)*(?::[0-9]+)?\/)? # Optional registry with optional port
|
||||
[a-z0-9]+(?:[._\/-][a-z0-9]+)* # Image name (required)
|
||||
(?::[a-z0-9][a-z0-9._-]*|@sha256:[a-f0-9]{64})? # Optional :tag or @sha256:hash
|
||||
$/ix';
|
||||
|
||||
if (! preg_match($pattern, $value)) {
|
||||
$fail('The :attribute format is invalid. Use image:tag or image@sha256:hash format.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,20 +10,33 @@ class DockerImageParser
|
|||
|
||||
private string $tag = 'latest';
|
||||
|
||||
private bool $isImageHash = false;
|
||||
|
||||
public function parse(string $imageString): self
|
||||
{
|
||||
// First split by : to handle the tag, but be careful with registry ports
|
||||
$lastColon = strrpos($imageString, ':');
|
||||
$hasSlash = str_contains($imageString, '/');
|
||||
|
||||
// If the last colon appears after the last slash, it's a tag
|
||||
// Otherwise it might be a port in the registry URL
|
||||
if ($lastColon !== false && (! $hasSlash || $lastColon > strrpos($imageString, '/'))) {
|
||||
$mainPart = substr($imageString, 0, $lastColon);
|
||||
$this->tag = substr($imageString, $lastColon + 1);
|
||||
// Check for @sha256: format first (e.g., nginx@sha256:abc123...)
|
||||
if (preg_match('/^(.+)@sha256:([a-f0-9]{64})$/i', $imageString, $matches)) {
|
||||
$mainPart = $matches[1];
|
||||
$this->tag = $matches[2];
|
||||
$this->isImageHash = true;
|
||||
} else {
|
||||
$mainPart = $imageString;
|
||||
$this->tag = 'latest';
|
||||
// Split by : to handle the tag, but be careful with registry ports
|
||||
$lastColon = strrpos($imageString, ':');
|
||||
$hasSlash = str_contains($imageString, '/');
|
||||
|
||||
// If the last colon appears after the last slash, it's a tag
|
||||
// Otherwise it might be a port in the registry URL
|
||||
if ($lastColon !== false && (! $hasSlash || $lastColon > strrpos($imageString, '/'))) {
|
||||
$mainPart = substr($imageString, 0, $lastColon);
|
||||
$this->tag = substr($imageString, $lastColon + 1);
|
||||
|
||||
// Check if the tag is a SHA256 hash
|
||||
$this->isImageHash = $this->isSha256Hash($this->tag);
|
||||
} else {
|
||||
$mainPart = $imageString;
|
||||
$this->tag = 'latest';
|
||||
$this->isImageHash = false;
|
||||
}
|
||||
}
|
||||
|
||||
// Split the main part by / to handle registry and image name
|
||||
|
|
@ -41,6 +54,37 @@ public function parse(string $imageString): self
|
|||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the given string is a SHA256 hash
|
||||
*/
|
||||
private function isSha256Hash(string $hash): bool
|
||||
{
|
||||
// SHA256 hashes are 64 characters long and contain only hexadecimal characters
|
||||
return preg_match('/^[a-f0-9]{64}$/i', $hash) === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the current tag is an image hash
|
||||
*/
|
||||
public function isImageHash(): bool
|
||||
{
|
||||
return $this->isImageHash;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the full image name with hash if present
|
||||
*/
|
||||
public function getFullImageNameWithHash(): string
|
||||
{
|
||||
$imageName = $this->getFullImageNameWithoutTag();
|
||||
|
||||
if ($this->isImageHash) {
|
||||
return $imageName.'@sha256:'.$this->tag;
|
||||
}
|
||||
|
||||
return $imageName.':'.$this->tag;
|
||||
}
|
||||
|
||||
public function getFullImageNameWithoutTag(): string
|
||||
{
|
||||
if ($this->registryUrl) {
|
||||
|
|
@ -73,6 +117,10 @@ public function toString(): string
|
|||
}
|
||||
$parts[] = $this->imageName;
|
||||
|
||||
if ($this->isImageHash) {
|
||||
return implode('/', $parts).'@sha256:'.$this->tag;
|
||||
}
|
||||
|
||||
return implode('/', $parts).':'.$this->tag;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -166,12 +166,14 @@ class="underline" href="https://coolify.io/docs/knowledge-base/docker/registry"
|
|||
@if ($application->destination->server->isSwarm())
|
||||
<x-forms.input required id="application.docker_registry_image_name" label="Docker Image"
|
||||
x-bind:disabled="!canUpdate" />
|
||||
<x-forms.input id="application.docker_registry_image_tag" label="Docker Image Tag"
|
||||
<x-forms.input id="application.docker_registry_image_tag" label="Docker Image Tag or Hash"
|
||||
helper="Enter a tag (e.g., 'latest', 'v1.2.3') or SHA256 hash (e.g., 'sha256-59e02939b1bf39f16c93138a28727aec520bb916da021180ae502c61626b3cf0')"
|
||||
x-bind:disabled="!canUpdate" />
|
||||
@else
|
||||
<x-forms.input id="application.docker_registry_image_name" label="Docker Image"
|
||||
x-bind:disabled="!canUpdate" />
|
||||
<x-forms.input id="application.docker_registry_image_tag" label="Docker Image Tag"
|
||||
<x-forms.input id="application.docker_registry_image_tag" label="Docker Image Tag or Hash"
|
||||
helper="Enter a tag (e.g., 'latest', 'v1.2.3') or SHA256 hash (e.g., 'sha256-59e02939b1bf39f16c93138a28727aec520bb916da021180ae502c61626b3cf0')"
|
||||
x-bind:disabled="!canUpdate" />
|
||||
@endif
|
||||
@else
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<div>
|
||||
<div x-data x-init="$nextTick(() => { if ($refs.autofocusInput) $refs.autofocusInput.focus(); })">
|
||||
<h1>Create a new Application</h1>
|
||||
<div class="pb-4">You can deploy an existing Docker Image from any Registry.</div>
|
||||
<form wire:submit="submit">
|
||||
|
|
@ -6,6 +6,24 @@
|
|||
<h2>Docker Image</h2>
|
||||
<x-forms.button type="submit">Save</x-forms.button>
|
||||
</div>
|
||||
<x-forms.input rows="20" id="dockerImage" placeholder="nginx:latest"></x-forms.textarea>
|
||||
<div class="space-y-4">
|
||||
<x-forms.input id="imageName" label="Image Name" placeholder="nginx or ghcr.io/user/app"
|
||||
helper="Enter the Docker image name with optional registry. Examples: nginx, ghcr.io/user/app, localhost:5000/myapp"
|
||||
required autofocus />
|
||||
<div class="relative grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
<x-forms.input id="imageTag" label="Tag (optional)" placeholder="latest"
|
||||
helper="Enter a tag like 'latest' or 'v1.2.3'. Leave empty if using SHA256." />
|
||||
<div
|
||||
class="absolute left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2 hidden md:flex items-center justify-center z-10">
|
||||
<div
|
||||
class="px-2 py-1 bg-white dark:bg-coolgray-100 border border-neutral-300 dark:border-coolgray-300 rounded text-xs font-bold text-neutral-500 dark:text-neutral-400">
|
||||
OR
|
||||
</div>
|
||||
</div>
|
||||
<x-forms.input id="imageSha256" label="SHA256 Digest (optional)"
|
||||
placeholder="59e02939b1bf39f16c93138a28727aec520bb916da021180ae502c61626b3cf0"
|
||||
helper="Enter only the 64-character hex digest (without 'sha256:' prefix)" />
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,94 +1,109 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Unit;
|
||||
|
||||
use App\Services\DockerImageParser;
|
||||
use PHPUnit\Framework\Attributes\Test;
|
||||
use PHPUnit\Framework\TestCase;
|
||||
|
||||
class DockerImageParserTest extends TestCase
|
||||
{
|
||||
private DockerImageParser $parser;
|
||||
it('parses regular image with tag', function () {
|
||||
$parser = new DockerImageParser;
|
||||
$parser->parse('nginx:latest');
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->parser = new DockerImageParser;
|
||||
expect($parser->getImageName())->toBe('nginx')
|
||||
->and($parser->getTag())->toBe('latest')
|
||||
->and($parser->isImageHash())->toBeFalse()
|
||||
->and($parser->toString())->toBe('nginx:latest');
|
||||
});
|
||||
|
||||
it('parses image with sha256 hash using colon format', function () {
|
||||
$parser = new DockerImageParser;
|
||||
$hash = '59e02939b1bf39f16c93138a28727aec520bb916da021180ae502c61626b3cf0';
|
||||
$parser->parse("ghcr.io/benjaminehowe/rail-disruptions:{$hash}");
|
||||
|
||||
expect($parser->getFullImageNameWithoutTag())->toBe('ghcr.io/benjaminehowe/rail-disruptions')
|
||||
->and($parser->getTag())->toBe($hash)
|
||||
->and($parser->isImageHash())->toBeTrue()
|
||||
->and($parser->toString())->toBe("ghcr.io/benjaminehowe/rail-disruptions@sha256:{$hash}")
|
||||
->and($parser->getFullImageNameWithHash())->toBe("ghcr.io/benjaminehowe/rail-disruptions@sha256:{$hash}");
|
||||
});
|
||||
|
||||
it('parses image with sha256 hash using at sign format', function () {
|
||||
$parser = new DockerImageParser;
|
||||
$hash = '59e02939b1bf39f16c93138a28727aec520bb916da021180ae502c61626b3cf0';
|
||||
$parser->parse("nginx@sha256:{$hash}");
|
||||
|
||||
expect($parser->getImageName())->toBe('nginx')
|
||||
->and($parser->getTag())->toBe($hash)
|
||||
->and($parser->isImageHash())->toBeTrue()
|
||||
->and($parser->toString())->toBe("nginx@sha256:{$hash}")
|
||||
->and($parser->getFullImageNameWithHash())->toBe("nginx@sha256:{$hash}");
|
||||
});
|
||||
|
||||
it('parses registry image with hash', function () {
|
||||
$parser = new DockerImageParser;
|
||||
$hash = 'abc123def456789abcdef123456789abcdef123456789abcdef123456789abc1';
|
||||
$parser->parse("docker.io/library/nginx:{$hash}");
|
||||
|
||||
expect($parser->getFullImageNameWithoutTag())->toBe('docker.io/library/nginx')
|
||||
->and($parser->getTag())->toBe($hash)
|
||||
->and($parser->isImageHash())->toBeTrue()
|
||||
->and($parser->toString())->toBe("docker.io/library/nginx@sha256:{$hash}");
|
||||
});
|
||||
|
||||
it('parses image without tag defaults to latest', function () {
|
||||
$parser = new DockerImageParser;
|
||||
$parser->parse('nginx');
|
||||
|
||||
expect($parser->getImageName())->toBe('nginx')
|
||||
->and($parser->getTag())->toBe('latest')
|
||||
->and($parser->isImageHash())->toBeFalse()
|
||||
->and($parser->toString())->toBe('nginx:latest');
|
||||
});
|
||||
|
||||
it('parses registry with port', function () {
|
||||
$parser = new DockerImageParser;
|
||||
$parser->parse('registry.example.com:5000/myapp:latest');
|
||||
|
||||
expect($parser->getFullImageNameWithoutTag())->toBe('registry.example.com:5000/myapp')
|
||||
->and($parser->getTag())->toBe('latest')
|
||||
->and($parser->isImageHash())->toBeFalse();
|
||||
});
|
||||
|
||||
it('parses registry with port and hash', function () {
|
||||
$parser = new DockerImageParser;
|
||||
$hash = '1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef';
|
||||
$parser->parse("registry.example.com:5000/myapp:{$hash}");
|
||||
|
||||
expect($parser->getFullImageNameWithoutTag())->toBe('registry.example.com:5000/myapp')
|
||||
->and($parser->getTag())->toBe($hash)
|
||||
->and($parser->isImageHash())->toBeTrue()
|
||||
->and($parser->toString())->toBe("registry.example.com:5000/myapp@sha256:{$hash}");
|
||||
});
|
||||
|
||||
it('identifies valid sha256 hashes', function () {
|
||||
$validHashes = [
|
||||
'59e02939b1bf39f16c93138a28727aec520bb916da021180ae502c61626b3cf0',
|
||||
'1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef',
|
||||
'abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890',
|
||||
];
|
||||
|
||||
foreach ($validHashes as $hash) {
|
||||
$parser = new DockerImageParser;
|
||||
$parser->parse("image:{$hash}");
|
||||
expect($parser->isImageHash())->toBeTrue("Hash {$hash} should be recognized as valid SHA256");
|
||||
}
|
||||
});
|
||||
|
||||
#[Test]
|
||||
public function it_parses_simple_image_name()
|
||||
{
|
||||
$this->parser->parse('nginx');
|
||||
it('identifies invalid sha256 hashes', function () {
|
||||
$invalidHashes = [
|
||||
'latest',
|
||||
'v1.2.3',
|
||||
'abc123', // too short
|
||||
'59e02939b1bf39f16c93138a28727aec520bb916da021180ae502c61626b3cf', // too short
|
||||
'59e02939b1bf39f16c93138a28727aec520bb916da021180ae502c61626b3cf00', // too long
|
||||
'59e02939b1bf39f16c93138a28727aec520bb916da021180ae502c61626b3cfg0', // invalid char
|
||||
];
|
||||
|
||||
$this->assertEquals('', $this->parser->getRegistryUrl());
|
||||
$this->assertEquals('nginx', $this->parser->getImageName());
|
||||
$this->assertEquals('latest', $this->parser->getTag());
|
||||
foreach ($invalidHashes as $hash) {
|
||||
$parser = new DockerImageParser;
|
||||
$parser->parse("image:{$hash}");
|
||||
expect($parser->isImageHash())->toBeFalse("Hash {$hash} should not be recognized as valid SHA256");
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_parses_image_with_tag()
|
||||
{
|
||||
$this->parser->parse('nginx:1.19');
|
||||
|
||||
$this->assertEquals('', $this->parser->getRegistryUrl());
|
||||
$this->assertEquals('nginx', $this->parser->getImageName());
|
||||
$this->assertEquals('1.19', $this->parser->getTag());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_parses_image_with_organization()
|
||||
{
|
||||
$this->parser->parse('coollabs/coolify:latest');
|
||||
|
||||
$this->assertEquals('', $this->parser->getRegistryUrl());
|
||||
$this->assertEquals('coollabs/coolify', $this->parser->getImageName());
|
||||
$this->assertEquals('latest', $this->parser->getTag());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_parses_image_with_registry_url()
|
||||
{
|
||||
$this->parser->parse('ghcr.io/coollabs/coolify:v4');
|
||||
|
||||
$this->assertEquals('ghcr.io', $this->parser->getRegistryUrl());
|
||||
$this->assertEquals('coollabs/coolify', $this->parser->getImageName());
|
||||
$this->assertEquals('v4', $this->parser->getTag());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_parses_image_with_port_in_registry()
|
||||
{
|
||||
$this->parser->parse('localhost:5000/my-app:dev');
|
||||
|
||||
$this->assertEquals('localhost:5000', $this->parser->getRegistryUrl());
|
||||
$this->assertEquals('my-app', $this->parser->getImageName());
|
||||
$this->assertEquals('dev', $this->parser->getTag());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_parses_image_without_tag()
|
||||
{
|
||||
$this->parser->parse('ghcr.io/coollabs/coolify');
|
||||
|
||||
$this->assertEquals('ghcr.io', $this->parser->getRegistryUrl());
|
||||
$this->assertEquals('coollabs/coolify', $this->parser->getImageName());
|
||||
$this->assertEquals('latest', $this->parser->getTag());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_converts_back_to_string()
|
||||
{
|
||||
$originalString = 'ghcr.io/coollabs/coolify:v4';
|
||||
$this->parser->parse($originalString);
|
||||
|
||||
$this->assertEquals($originalString, $this->parser->toString());
|
||||
}
|
||||
|
||||
#[Test]
|
||||
public function it_converts_to_string_with_default_tag()
|
||||
{
|
||||
$this->parser->parse('nginx');
|
||||
$this->assertEquals('nginx:latest', $this->parser->toString());
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue