diff --git a/app/Livewire/Project/New/DockerImage.php b/app/Livewire/Project/New/DockerImage.php index 942924437..7d68ce068 100644 --- a/app/Livewire/Project/New/DockerImage.php +++ b/app/Livewire/Project/New/DockerImage.php @@ -6,6 +6,7 @@ use App\Models\Application; use App\Models\Project; use App\Models\StandaloneDocker; use App\Models\SwarmDocker; +use App\Services\DockerImageParser; use Livewire\Component; use Visus\Cuid2\Cuid2; @@ -28,12 +29,10 @@ class DockerImage extends Component $this->validate([ 'dockerImage' => 'required', ]); - $image = str($this->dockerImage)->before(':'); - if (str($this->dockerImage)->contains(':')) { - $tag = str($this->dockerImage)->after(':'); - } else { - $tag = 'latest'; - } + + $parser = new DockerImageParser; + $parser->parse($this->dockerImage); + $destination_uuid = $this->query['destination']; $destination = StandaloneDocker::where('uuid', $destination_uuid)->first(); if (! $destination) { @@ -53,8 +52,8 @@ class DockerImage extends Component 'git_branch' => 'main', 'build_pack' => 'dockerimage', 'ports_exposes' => 80, - 'docker_registry_image_name' => $image, - 'docker_registry_image_tag' => $tag, + 'docker_registry_image_name' => $parser->getFullImageNameWithoutTag(), + 'docker_registry_image_tag' => $parser->getTag(), 'environment_id' => $environment->id, 'destination_id' => $destination->id, 'destination_type' => $destination_class, diff --git a/app/Services/DockerImageParser.php b/app/Services/DockerImageParser.php new file mode 100644 index 000000000..4987f953d --- /dev/null +++ b/app/Services/DockerImageParser.php @@ -0,0 +1,74 @@ + strrpos($imageString, '/'))) { + $mainPart = substr($imageString, 0, $lastColon); + $this->tag = substr($imageString, $lastColon + 1); + } else { + $mainPart = $imageString; + $this->tag = 'latest'; + } + + // Split the main part by / to handle registry and image name + $pathParts = explode('/', $mainPart); + + // If we have more than one part and the first part contains a dot or colon + // it's likely a registry URL + if (count($pathParts) > 1 && (str_contains($pathParts[0], '.') || str_contains($pathParts[0], ':'))) { + $this->registryUrl = array_shift($pathParts); + $this->imageName = implode('/', $pathParts); + } else { + $this->imageName = $mainPart; + } + + return $this; + } + + public function getFullImageNameWithoutTag(): string + { + return $this->registryUrl.'/'.$this->imageName; + } + + public function getRegistryUrl(): string + { + return $this->registryUrl; + } + + public function getImageName(): string + { + return $this->imageName; + } + + public function getTag(): string + { + return $this->tag; + } + + public function toString(): string + { + $parts = []; + if ($this->registryUrl) { + $parts[] = $this->registryUrl; + } + $parts[] = $this->imageName; + + return implode('/', $parts).':'.$this->tag; + } +} diff --git a/tests/Unit/Services/DockerImageParserTest.php b/tests/Unit/Services/DockerImageParserTest.php new file mode 100644 index 000000000..876d68ef0 --- /dev/null +++ b/tests/Unit/Services/DockerImageParserTest.php @@ -0,0 +1,94 @@ +parser = new DockerImageParser; + } + + #[Test] + public function it_parses_simple_image_name() + { + $this->parser->parse('nginx'); + + $this->assertEquals('', $this->parser->getRegistryUrl()); + $this->assertEquals('nginx', $this->parser->getImageName()); + $this->assertEquals('latest', $this->parser->getTag()); + } + + #[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()); + } +}