From b235a480b2d6b0ebb2a90af3f6787ce67cb9e261 Mon Sep 17 00:00:00 2001 From: Andras Bacsai <5845193+andrasbacsai@users.noreply.github.com> Date: Sun, 4 Oct 2026 13:39:19 +0200 Subject: [PATCH] fix(service): detect registry-prefixed Grafana server images (#10617) --- app/Models/Service.php | 19 +++++++++++++++++-- tests/Feature/ServiceExtraFieldsTest.php | 22 ++++++++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/app/Models/Service.php b/app/Models/Service.php index e3f343375c..ad28ad8139 100644 --- a/app/Models/Service.php +++ b/app/Models/Service.php @@ -3,6 +3,7 @@ namespace App\Models; use App\Services\ContainerStatusAggregator; +use App\Services\DockerImageParser; use App\Support\DomainPortOverrides; use App\Support\ResourceStartActivity; use App\Traits\Auditable; @@ -642,7 +643,7 @@ class Service extends BaseModel } $fields->put('Unleash', $data->toArray()); break; - case $this->isGrafanaImage($image->toString()): + case $this->isGrafanaServerImage($application->image): $data = collect([]); $admin_password = $this->environment_variables()->where('key', 'SERVICE_PASSWORD_GRAFANA')->first(); $data = $data->merge([ @@ -1417,8 +1418,22 @@ class Service extends BaseModel return $fields; } - private function isGrafanaImage(string $image): bool + /** + * Determine whether the given image is an actual Grafana server image + * (grafana/grafana, grafana/grafana-oss, grafana/grafana-enterprise), + * optionally prefixed by a registry host. Other Grafana-published images + * such as grafana/loki, grafana/promtail and grafana/tempo are excluded. + */ + private function isGrafanaServerImage(string $image): bool { + $parsedImage = (new DockerImageParser)->parse($image); + $image = $parsedImage->getImageName(); + + // The parser recognizes registry hosts with dots or ports, but not bare localhost. + if ($parsedImage->getRegistryUrl() === '' && str_starts_with($image, 'localhost/')) { + $image = substr($image, strlen('localhost/')); + } + return in_array($image, [ 'grafana/grafana', 'grafana/grafana-oss', diff --git a/tests/Feature/ServiceExtraFieldsTest.php b/tests/Feature/ServiceExtraFieldsTest.php index 4274de43fe..765825a72d 100644 --- a/tests/Feature/ServiceExtraFieldsTest.php +++ b/tests/Feature/ServiceExtraFieldsTest.php @@ -44,6 +44,28 @@ it('only adds Grafana extra fields for Grafana server images', function (string 'loki' => ['grafana/loki:latest', false], 'promtail' => ['grafana/promtail:latest', false], 'tempo' => ['grafana/tempo:latest', false], + 'registry.example.com grafana' => ['registry.example.com/grafana/grafana:latest', true], + 'registry.example.com grafana-oss' => ['registry.example.com/grafana/grafana-oss:latest', true], + 'registry.example.com grafana-enterprise' => ['registry.example.com/grafana/grafana-enterprise:latest', true], + 'registry.example.com loki' => ['registry.example.com/grafana/loki:latest', false], + 'registry.example.com promtail' => ['registry.example.com/grafana/promtail:latest', false], + 'registry.example.com tempo' => ['registry.example.com/grafana/tempo:latest', false], + 'registry.example.com:5000 grafana' => ['registry.example.com:5000/grafana/grafana:latest', true], + 'registry.example.com:5000 grafana-oss' => ['registry.example.com:5000/grafana/grafana-oss:latest', true], + 'registry.example.com:5000 grafana-enterprise' => ['registry.example.com:5000/grafana/grafana-enterprise:latest', true], + 'registry.example.com:5000 loki' => ['registry.example.com:5000/grafana/loki:latest', false], + 'registry.example.com:5000 promtail' => ['registry.example.com:5000/grafana/promtail:latest', false], + 'registry.example.com:5000 tempo' => ['registry.example.com:5000/grafana/tempo:latest', false], + 'localhost registry' => ['localhost/grafana/grafana:latest', true], + 'unrelated namespace' => ['other/grafana/grafana:latest', false], + 'localhost as registry namespace' => ['registry.example.com/localhost/grafana/grafana:latest', false], + 'nested registry namespace' => ['registry.example.com/team/grafana/grafana:latest', false], + 'nested registry namespace oss' => ['registry.example.com:5000/team/grafana/grafana-oss:latest', false], + 'nested registry namespace enterprise' => ['registry.example.com/team/grafana/grafana-enterprise:latest', false], + 'similar repository name' => ['grafana/grafana-custom:latest', false], + 'registry port without tag' => ['registry.example.com:5000/grafana/grafana', true], + 'digest' => ['grafana/grafana@sha256:'.str_repeat('a', 64), true], + 'registry port with digest' => ['registry.example.com:5000/grafana/grafana@sha256:'.str_repeat('a', 64), true], ]); it('exposes Jean Server authentication and access settings', function () {