skip to content

In a GitLab CI/CD component, what does a spec:inputs header give you that configuring an included template through global variables does not?

level: middleimportance: nice to knowfreq 34%

answer

  1. a declared contract, not a naming convention
  2. header document above the three dashes
  3. substituted before the YAML is parsed
  4. options and regex checked at creation time
  5. scoped to one include, not the pipeline

basics

~20 s

spec:inputs declares typed, validated parameters with defaults and allowed values, interpolated into the template as $[[ inputs.name ]] before the configuration is parsed. Invalid or missing values fail at pipeline creation, and the values are scoped to that one include rather than leaking into every job.

solid answer

~50 s

A template configured by global variables has no contract: the consumer sets `SCAN_SEVERITY` somewhere, hopes the template reads that exact name, and a typo produces a job that runs with a wrong or empty value. `spec:inputs` makes the contract explicit. The component file starts with a `spec:` header, separated from the configuration by `---`, declaring each input with a `default`, a `description`, a `type`, and optionally `options` or a `regex`. The consumer passes them under `inputs:` on the include, and GitLab interpolates `$[[ inputs.name ]]` **before** the YAML is parsed — so an input can supply a job name, a stage, or any key, not just a value. Anything missing, mistyped or outside `options` is a pipeline-creation error with a clear message. Inputs are also scoped to that include, so two includes of the same component with different inputs do not collide the way two global variables would.

code

yaml · 14 lines
yaml
spec:
  inputs:
    stage:
      default: test
      description: Stage the scan job runs in
    severity:
      type: string
      default: high
      options: [low, medium, high]
---
scan:
  stage: $[[ inputs.stage ]]
  script:
    - scanner run --fail-on $[[ inputs.severity ]]

go deeper

for a junior

Know that a component declares its parameters in a spec:inputs header and the consumer passes them under inputs: on the include, instead of setting global variables and hoping the names line up.

for a middle

Explain interpolation versus variables: $[[ inputs.x ]] is substituted before parsing so it can set a stage or job name, while $VAR is expanded by the runner. Name the validation features — type, default, options, regex — and say that violations fail pipeline creation.

for a senior

Show why this changes operations: a typo becomes a creation-time error instead of a ten-minute job failure, two includes of the same component no longer collide, and pinning @1.2.0 makes template behaviour reproducible.

for a principal

Own the component contract across the organisation: versioning policy for inputs, how a required input is added without breaking consumers, and whether teams consume catalog components or a template repository pinned by tag.

## The problem with variable-configured templates The classic GitLab shared template takes its configuration from CI/CD variables: ```yaml # in the template scan: script: - scanner run --fail-on "$SCAN_SEVERITY" ``` The consumer sets `SCAN_SEVERITY` in their `variables:` block, in project settings, or not at all. Every failure mode here is silent: a typo gives an empty string; a value the scanner does not accept is discovered when the job runs, minutes later; the variable is global, so it also lands in every unrelated job's environment; and including the template twice with different settings is impossible, because there is one variable namespace. ## What `spec:inputs` changes A component or template file may begin with a **spec header**, a separate YAML document ended by `---`: ```yaml spec: inputs: stage: default: test description: Stage the scan job runs in severity: type: string default: high options: [low, medium, high] --- scan: stage: $[[ inputs.stage ]] script: - scanner run --fail-on $[[ inputs.severity ]] ``` The consumer supplies values on the include itself: ```yaml include: - component: $CI_SERVER_FQDN/platform/ci-components/[email protected] inputs: severity: medium ``` Five differences matter. **It is interpolation, not a variable.** `$[[ inputs.severity ]]` is substituted into the YAML text *before* GitLab parses the configuration. That is why an input can decide a stage name, a job name, an `image:`, or whether a whole block exists — places where a `$VARIABLE` would be meaningless because the value is needed at configuration time, not at runtime. Note the distinct syntax: `$[[ ... ]]` is interpolation, `$VAR` is a runtime variable, and they are resolved at different moments. **It is validated.** `type` (string, number, boolean, and array in newer versions), `options` for an enumeration, and `regex` for a string pattern are all checked while the pipeline is being created. A bad value produces a configuration error on the pipeline page — no jobs start — instead of a job that fails ten minutes in. **It has declared defaults and required inputs.** An input with no `default` is mandatory: omit it and pipeline creation fails naming the input. That turns "which variables does this template need?" from tribal knowledge into a machine-checked contract, and `description` makes the file self-documenting. **It is scoped to the include.** Two includes of the same component with different inputs are independent, because nothing is written into the global variable namespace. Variable-configured templates cannot do this at all. **It composes with versioning.** `include:component` addresses a component published in the project's `templates/` directory and released to the CI/CD catalog, pinned by version: `@1.2.0` for an exact release, a branch or commit for development, or `@~latest` for the newest release. A component's inputs are therefore versioned along with its behaviour, which is what makes a breaking change to a template a *semver* event rather than a surprise. ## Inputs are not exclusive to components A common misconception: `inputs:` also works with `include:local`, `include:project` and `include:remote` — any included file that declares a `spec:` header can be given `inputs:`. Components add the catalog, the versioning scheme and a discoverable published surface; the parameter mechanism itself is shared. ## Where it still bites Interpolation happens before parsing, so an input value containing YAML-significant characters can change the shape of the document — quote where it matters. Interpolation also only applies inside the file that declared the spec; you cannot reach `inputs` from an unrelated file. And because everything is resolved at pipeline creation, an input cannot depend on anything a job computes; that remains the job of `rules:` or a child pipeline. ## The interview shape Asked as "how do you make a shared template configurable without a wall of global variables?", the expected answer is: declare a spec header with typed inputs, interpolate them with `$[[ inputs.x ]]`, publish the file as a versioned component, and let consumers pass `inputs:` on the include. The follow-up is usually about migration — you can add a spec header to an existing template and keep reading the old variables for a release or two while consumers move.

  • How does $[[ inputs.x ]] differ from $X inside a component file?
    `$[[ inputs.x ]]` is interpolated into the YAML text before GitLab parses the configuration, so it can supply a stage name, a job name or an image. `$X` is a CI/CD variable expanded by the runner when the job executes, so it can only ever appear inside a value that is used at runtime. Different mechanisms, different moments.
  • What happens if a consumer omits an input that has no default?
    Pipeline creation fails with a configuration error naming the missing input, and no jobs run. That is the point: an input without a `default` is a required parameter, so the template's requirements are enforced by GitLab rather than documented in a README nobody reads.
  • Do you have to publish a component to use typed inputs?
    No. Any included file — local, project or remote — can declare a `spec:` header and receive `inputs:` on the include. Publishing to the CI/CD catalog adds discovery and version pinning with `@1.2.0` or `@~latest`, which is what makes a breaking input change a semver event rather than a surprise for every consumer.

saying these in an interview costs you the question

  • Thinking inputs are just CI/CD variables with nicer syntax
  • Believing inputs only work with catalog components
  • Expecting $[[ inputs.x ]] to resolve when the job runs
  • Assuming a wrong input value fails inside the job
  • Passing configuration as global variables that leak everywhere

context