feat(docker): enhance Docker image handling with new validation and parsing logic

- Refactored DockerImage component to use separate properties for image name, tag, and SHA256 digest.
- Introduced DockerImageFormat validation rule to enforce correct image format.
- Updated DockerImageParser to handle new parsing logic for image tags and SHA256 hashes.
- Enhanced UI to separate input fields for image name, tag, and SHA256 digest, improving user experience.
- Added comprehensive tests for DockerImageParser to ensure accurate parsing and validation of image formats.
This commit is contained in:
Andras Bacsai committed 2025-10-03 11:31:00 +02:00
1 parent 0e02eff4a1
commit 590de8ce37
7 files changed
+209 -133

No files matched your search

+26 -3
View File
@@ -12,7 +12,11 @@ use Visus\Cuid2\Cuid2;
class DockerImage extends Component
{
public string $dockerImage = '';
public string $imageName = '';
public string $imageTag = '';
public string $imageSha256 = '';
public array $parameters;
@@ -27,11 +31,30 @@ class DockerImage extends Component
public function submit()
{
$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();
+41
View 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.');
}
}
}
+22 -15
View File
@@ -14,22 +14,29 @@ class DockerImageParser
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 if the tag is a SHA256 hash
$this->isImageHash = $this->isSha256Hash($this->tag);
// 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';
$this->isImageHash = false;
// 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