DAP flow execution image: registry prefix with path (e.g., Harbor proxy) treated as full image reference
What does this MR do and why?
duo_workflows_default_image_registry (Admin > GitLab Duo > Change configuration > Flow execution > Image registry) controls where foundational flows pull their runtime image from. Today:
- A bare host or
host:portresolves to<host>/<IMAGE_PATH>. - Any value containing
/is used as-is as a full image reference (tagged, digest-pinned, or untagged, which the runner pulls as:latest).
That breaks registry path prefixes, such as a Harbor proxy cache project harbor.example.com/proxygitlab. The value is used as-is, IMAGE_PATH is never appended, and the flow fails to resolve its image. This was reported by a GitLab Dedicated customer.
This MR makes a trailing / mark a registry path prefix: harbor.example.com/proxygitlab/ resolves to harbor.example.com/proxygitlab/<IMAGE_PATH>. Every other value behaves exactly as before.
ee/app/services/ai/duo_workflows/start_workflow_service.rb: one early return ininstance_imagefor a trailing slash.ee/app/models/ee/application_setting.rb: the format validation allows a single trailing/after the host, port, or a path segment. It is still rejected after a tag or digest, and empty segments (//) are still rejected. The validation error message mentions the new form. It is a plain string, not externalised.
Why a trailing slash instead of inferring the prefix
harbor.example.com/proxygitlab (a prefix) and registry.example.com/org/image (a valid untagged full reference) have the same shape, so there's no reliable way to tell them apart. An earlier version of this MR treated every untagged path as a prefix, but review pointed out that would break valid existing settings.
Why this is safe for existing settings
The previous validation rejected a trailing /: each path segment must be non-empty, and the pattern is anchored at the end. No saved value can end in /, so no existing configuration changes behaviour.
Why there are no docs or UI help text changes
To keep the backport to 19.4 and 19.3 clean. Changing externalised strings on stable branches drops translations. The affected customer is on Dedicated and gets the fix in the next 19.3 patch; they'll be told to add the trailing slash to their setting. Help text and docs can follow on master, along with a longer-term explicit setting that separates "registry prefix" from "full image override" (related to #597252 (closed)).
References
- Resolves #630865 (closed)
- Related: #597252 (closed)
Screenshots or screen recordings
No UI change. The table shows the resolved image for each setting value. <IMAGE_PATH> is gitlab-org/duo-workflow/default-docker-image/workflow-generic-image:v0.0.17.
| Setting | Before | After |
|---|---|---|
| blank | registry.gitlab.com/<IMAGE_PATH> |
same |
registry.example.com |
registry.example.com/<IMAGE_PATH> |
same |
registry.example.com:5000 |
registry.example.com:5000/<IMAGE_PATH> |
same |
harbor.example.com/proxygitlab/ |
rejected by validation | harbor.example.com/proxygitlab/<IMAGE_PATH> |
harbor.example.com:5000/proxygitlab/ |
rejected by validation | harbor.example.com:5000/proxygitlab/<IMAGE_PATH> |
harbor.example.com/nested/proxy/ |
rejected by validation | harbor.example.com/nested/proxy/<IMAGE_PATH> |
registry.example.com/my-org/my-image |
used as-is | used as-is |
harbor.example.com:5000/my-team/custom-image |
used as-is | used as-is |
registry.example.com/org/image:v1.0 |
used as-is | used as-is |
registry.example.com/org/image@sha256:<digest> |
used as-is | used as-is |
registry.example.com/org/image:v1.0/ |
rejected | rejected |
harbor.example.com/proxygitlab// |
rejected | rejected |
How to set up and validate locally
-
Open a Rails console:
gdk rails console -
Paste the following. It prints the resolved image, or the validation error, for each value, then resets the setting to blank:
svc = Ai::DuoWorkflows::StartWorkflowService.allocate digest = "sha256:#{'a' * 64}" values = [ nil, 'registry.example.com', 'registry.example.com:5000', 'harbor.example.com/proxygitlab/', 'harbor.example.com:5000/proxygitlab/', 'harbor.example.com/nested/proxy/', 'registry.example.com/my-org/my-image', 'harbor.example.com:5000/my-team/custom-image', 'registry.example.com/my-image', 'registry.example.com/org/image:v1.0', "registry.example.com/org/image@#{digest}", 'registry.example.com/org/image:v1.0/', 'harbor.example.com/proxygitlab//' ] logger, ActiveRecord::Base.logger = ActiveRecord::Base.logger, nil rows = values.map do |value| setting = ApplicationSetting.current setting.duo_workflows_default_image_registry = value next [value.inspect, "INVALID: #{setting.errors.full_messages.first}"] unless setting.save Gitlab::CurrentSettings.expire_current_application_settings [value.inspect, svc.send(:instance_image)] end ApplicationSetting.current.update!(duo_workflows_default_image_registry: nil) ActiveRecord::Base.logger = logger width = rows.map { |r| r[0].length }.max rows.each { |setting, image| puts "#{setting.ljust(width)} => #{image}" }; nil -
Check the output matches the After column above.
MR acceptance checklist
Evaluate this MR against the MR acceptance checklist. It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.
- Quality: specs cover trailing-slash prefixes (plain, nested, with a port), full references used as-is (untagged, tagged, digest), and the validation edge cases (
/after a tag, empty segment). The new cases fail againstmaster; existing cases pass unchanged. - Performance: no impact. One string check and a regex change.
- Reliability: backward compatible. The previous validation rejected a trailing
/, so no existing setting changes behaviour. - Security: validation is only relaxed to allow one trailing
/. Values are still limited to host, port, and path characters. - Maintainability: one early return in the service and one regex alternative in the model.
- Backport: no feature flag, migration, or externalised string changes, so it can be picked cleanly into 19.4 and 19.3.