Resolve !reference tags passed as CI/CD input values

What does this MR do and why?

A YAML tag such as !reference passed as a CI/CD input value (in include: inputs:) was never resolved. YAML tag resolution runs after input interpolation in Gitlab::Ci::Config#build_config, but the input value was stringified during interpolation, so the unresolved Gitlab::Ci::Config::Yaml::Tags::Reference object leaked into the configuration, for example:

echo '[#<Gitlab::Ci::Config::Yaml::Tags::Reference:0x... @data={...}>, my-specific]'

This MR preserves the tag through input interpolation so the existing tag resolution pass (Config::Yaml::Tags::Resolver) resolves it against the fully merged configuration. It covers scalar (string) and array inputs, both when the input is the whole value of a node and when it is embedded inside a larger string.

The behavior is behind the ci_resolve_reference_tags_in_inputs feature flag, disabled by default. When the flag is disabled, behavior is unchanged.

Resolves #606739 (closed)

Implementation

  • lib/ci/inputs/string_input.rb: coerced_value no longer calls to_s on a YAML tag object (flag-gated), so a scalar tag survives interpolation and reaches the resolver.
  • lib/gitlab/ci/config/interpolation/deferred_string.rb (new): when a string node interpolates a value that still carries an unresolved tag, the substitution is deferred and emitted as an object that the tag resolution pass resolves and then stringifies.
  • lib/gitlab/ci/config/interpolation/template.rb: interpolate_string_node! emits a DeferredString when a block value carries an unresolved tag (flag-gated; the flag is only evaluated when a tag is present, to avoid overhead on the common path).
  • lib/ci/inputs/base_input.rb: run_validations skips options and regex validation when the coerced value still carries an unresolved tag, because the value cannot be validated until the tag is resolved.

Feature flag

  • ci_resolve_reference_tags_in_inputs (gitlab_com_derisk, disabled by default).
  • Both enabled and disabled states are tested.

How to set up and validate locally

# list.yml
.my-shared-list:
  - value1
  - value2

# template.yml
spec:
  inputs:
    a:
      type: array
---
job:
  script:
    - echo '$[[ inputs.a ]]' > my_script.sh

# .gitlab-ci.yml
include:
  - local: /list.yml
  - local: /template.yml
    inputs:
      a:
        - !reference [.my-shared-list]
        - my-specific

With the flag enabled, the Full Configuration tab shows the resolved values instead of a dumped Reference object.

Documentation

  • doc/ci/inputs/_index.md (Array type): describes that resolving a !reference tag in an input value is available behind the feature flag.
  • doc/ci/yaml/yaml_optimization.md (!reference tags): corrects the description of when !reference paths are read relative to input interpolation, and notes the flag-gated support.

Test coverage

  • DeferredString unit spec (detection and resolution).
  • Interpolator integration spec (scalar and array, whole value and embedded, flag enabled and disabled).
  • End-to-end Gitlab::Ci::Config spec with real local includes, including a string input that defines options:, and the flag-disabled fallback.

MR acceptance checklist

  • Feature flag added, disabled by default; both states tested.
  • Unit, integration, and end-to-end tests.
  • RuboCop, markdownlint, Vale, and fast-spec-helper validation passing.
  • Technical Writer review of the documentation changes (can be post-merge).
Edited by Oleg Yakovenko

Merge request reports

Loading
Loading