Gitlab Pipeline – extends – What is extends in GitLab CI/CD?

DevOps

MOTOSHARE 🚗🏍️
Turning Idle Vehicles into Shared Rides & Earnings

From Idle to Income. From Parked to Purpose.
Earn by Sharing, Ride by Renting.
Where Owners Earn, Riders Move.
Owners Earn. Riders Move. Motoshare Connects.

With Motoshare, every parked vehicle finds a purpose. Owners earn. Renters ride.
🚀 Everyone wins.

Start Your Journey with Motoshare

The extends keyword in GitLab CI/CD allows you to reuse configuration from other job definitions or “hidden” job templates within your .gitlab-ci.yml file. This is a powerful feature for practicing DRY (Don’t Repeat Yourself) principles, making your CI/CD configuration more maintainable, consistent, and easier to manage.

“Hidden” jobs are typically used as templates. Their names start with a dot (.), and they are not processed as regular jobs by GitLab CI unless explicitly extended.


Example .gitlab-ci.yml with extends:

# .gitlab-ci.yml

stages:
  - setup
  - test
  - build

default:
  image: alpine:latest # A global default image

# 1. Define a hidden job template for common test configurations
.base_test_config:
  stage: test
  image: ruby:3.0 # Specific image for test jobs
  tags:
    - docker # Default tags for these test jobs
  variables:
    TEST_SUITE_VERSION: "v1.2"
    RAILS_ENV: "test"
  before_script:
    - echo "--- Base Test Config: before_script ---"
    - echo "Setting up common test environment..."
    - bundle install --jobs $(nproc) --path vendor/bundle # Example common setup
  script: # This is a base script; jobs extending this can add more commands
    - echo "--- Base Test Config: script ---"
    - echo "Running common pre-test checks..."
  after_script:
    - echo "--- Base Test Config: after_script ---"
    - echo "Cleaning up common test resources."

# 2. Define another hidden job template, perhaps for a specific type of test
.rspec_test_template:
  extends: .base_test_config # This template itself extends another one
  variables:
    TEST_FRAMEWORK: "RSpec" # Overrides or adds to variables from .base_test_config
  before_script: # Commands here will run AFTER commands from .base_test_config's before_script
    - echo "--- RSpec Template: before_script ---"
    - echo "Setting up RSpec specific environment..."
  script: # Commands here will run AFTER commands from .base_test_config's script
    - echo "--- RSpec Template: script ---"
    - echo "Executing RSpec tests with framework $TEST_FRAMEWORK on Rails env $RAILS_ENV"
    # Placeholder for actual rspec command

# --- Regular Jobs using 'extends' ---

# Job 1: Extends the base test configuration
unit_tests:
  extends: .base_test_config
  # This job inherits: stage, image, tags, variables (TEST_SUITE_VERSION, RAILS_ENV),
  # before_script, script, and after_script from .base_test_config.
  script: # Commands here run AFTER the script block from .base_test_config
    - echo "--- Unit Tests: script ---"
    - echo "Running unit tests for version $TEST_SUITE_VERSION..."
    - echo "Actual unit test commands would go here."

# Job 2: Extends the RSpec template (which itself extends .base_test_config)
# It also overrides a variable and adds specific script commands.
feature_tests_rspec:
  extends: .rspec_test_template
  variables:
    RAILS_ENV: "test_feature" # Overrides RAILS_ENV from .base_test_config (via .rspec_test_template)
    SPEC_FILES: "spec/features"
  script: # Commands here run AFTER script blocks from .base_test_config AND .rspec_test_template
    - echo "--- Feature Tests (RSpec): script ---"
    - echo "Running RSpec feature tests from $SPEC_FILES on Rails env $RAILS_ENV"
    - echo "Test Suite version: $TEST_SUITE_VERSION, Framework: $TEST_FRAMEWORK"
    - bundle exec rspec $SPEC_FILES # Example actual command

# Job 3: Extends the base but completely overrides its 'script' and 'before_script'
# while keeping other things like image and variables.
custom_setup_test:
  extends: .base_test_config
  before_script: # This completely replaces .base_test_config's before_script
    - echo "--- Custom Setup Test: entirely new before_script ---"
    - echo "Performing a very different setup."
  script: # This completely replaces .base_test_config's script
    - echo "--- Custom Setup Test: entirely new script ---"
    - echo "Running tests with custom setup. RAILS_ENV is $RAILS_ENV."

# Job 4: Example of a non-test job, not using extends
# This job will use the global default image 'alpine:latest'
build_documentation:
  stage: build
  script:
    - echo "Building documentation..."
    - mkdir public
    - echo "Docs built" > public/index.html
  artifacts:
    paths:
      - publicCode language: PHP (php)

Explanation:

  1. Hidden Job Templates (. prefix):
    • Jobs like .base_test_config and .rspec_test_template start with a dot (.). This makes them “hidden” jobs.
    • Hidden jobs are not run directly as part of your pipeline. They serve as templates that other regular jobs can inherit from using the extends keyword.
  2. extends: Keyword:
    • Used within a job definition to specify one or more template jobs whose configurations should be inherited.
    • extends: .base_test_config means the unit_tests job will inherit all configurations from .base_test_config.
    • extends: .rspec_test_template means feature_tests_rspec inherits from .rspec_test_template. Since .rspec_test_template itself extends .base_test_config, feature_tests_rspec effectively inherits from both, with .rspec_test_template‘s definitions taking precedence over .base_test_config where there are overlaps, and then feature_tests_rspec‘s own definitions taking precedence over both.
  3. Merge Strategy (How Inheritance Works):
    GitLab performs a “deep merge” of the configurations. The specific job’s configurations always take precedence over what’s inherited.
    • Scalars (single values like image, stage): The value from the extending job (the most specific one) is used. If the extending job doesn’t define it, the value from the template is used.
    • Hashes (key-value pairs like variables): They are merged. If both the template and the extending job define the same variable key, the value from the extending job (the most specific one) wins. New variables from either are included.
      • Example: .base_test_config defines RAILS_ENV: "test". feature_tests_rspec defines RAILS_ENV: "test_feature". So, feature_tests_rspec will use test_feature.
    • Arrays (lists like script, before_script, after_script, tags):
      • script: Commands from the extended template’s script are executed before the commands in the extending job’s script. They are effectively prepended.
      • before_script: Commands from the extended template’s before_script are executed before the commands in the extending job’s before_script. They are prepended.
      • after_script: Commands from the extended template’s after_script are executed before the commands in the extending job’s after_script. They are prepended.
      • tags: The tags array from the extending job replaces the tags array from the template.
      • Important Exception: If an extending job redefines script, before_script, or after_script completely (as shown in custom_setup_test), the template’s corresponding script block is not prepended; the job’s definition entirely replaces it for that specific script block. To achieve prepending, the job must also include the script keywords from the template if it wants to re-define only parts, which extends handles implicitly for these script arrays if the job also has a script section. Correction based on typical CI behavior: For script, before_script, and after_script, GitLab CI prepends the inherited script content to the specific job’s script content. If you want to completely override, you’d typically need to ensure the template doesn’t have that script section, or you are actually replacing a more deeply nested script. However, the common understanding and use of extends with these script arrays is that the inherited commands run, then the specific job’s commands run. My example custom_setup_test reflects this prepending for before_script and script.
      • Self-correction for clarity: The most accurate way to describe script, before_script, and after_script merging with extends is: the commands from the extended configuration are executed, then the commands from the current job’s configuration for that same key are executed. They are effectively combined in sequence.
  4. unit_tests Job:
    • Inherits stage: test, image: ruby:3.0, tags, variables, the before_script, the initial script part, and after_script from .base_test_config.
    • Its own script block adds more commands that run after the script block from .base_test_config.
  5. feature_tests_rspec Job:
    • Inherits from .rspec_test_template.
    • .rspec_test_template first inherits from .base_test_config.
    • Then, .rspec_test_template‘s own definitions (like TEST_FRAMEWORK: "RSpec" and its before_script and script) are merged.
    • Finally, feature_tests_rspec‘s own definitions (like RAILS_ENV: "test_feature" and its script) are merged, taking highest precedence.
    • The before_script commands will run in order: .base_test_config‘s -> .rspec_test_template‘s.
    • The script commands will run in order: .base_test_config‘s -> .rspec_test_template‘s -> feature_tests_rspec‘s.
  6. custom_setup_test Job:
    • This job extends .base_test_config but provides its own complete before_script and script definitions. In this case, for these specific keys, the definitions from .base_test_config are prepended to the definitions in custom_setup_test.
    • Correction/Clarification: If a job defines script and extends a template that also defines script, the inherited script runs, then the job’s script runs. The example above where I said it “replaces” was slightly off for these script arrays; the behavior is more like chaining.
  7. Extending Multiple Templates:
    • You can extend multiple templates: extends: [.template_A, .template_B].
    • Configurations are merged from left to right. .template_B‘s values would override .template_A‘s if there are conflicts, and then the current job’s values override anything from the templates.

Benefits of extends:

  • DRY (Don’t Repeat Yourself): Reduces a lot of boilerplate and repeated configuration.
  • Consistency: Ensures common settings (like Docker images, setup scripts, tags) are applied consistently across similar jobs.
  • Maintainability: If you need to change a common setting (e.g., update a Docker image version), you only need to change it in the template, and all jobs extending it will get the update.
  • Readability: Job definitions become shorter and focus on what’s unique to them.

extends is a very powerful tool for organizing your GitLab CI/CD configurations, especially as your pipelines grow in complexity. It’s preferred over older YAML anchor/alias methods for CI configurations because extends performs a more intelligent “deep merge” suitable for CI job structures.

Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
0
Would love your thoughts, please comment.x