Skip to main content
In this lesson you’ll create a custom transformer that converts a Jenkins sleep step into an equivalent GitHub Actions step. Instead of leaving the sleep step commented out in the generated workflow, the transformer will produce a step like:
- name: Sleep for 5 seconds
  run: sleep 5s
This guide walks through inspecting the Jenkins pipeline JSON, writing a helper transformer to verify the structure, and implementing the final transformer in Ruby. It also shows how to run gh actions-importer with your custom transformer for dry-run and migrate.

Problem overview

When the importer doesn’t have a built-in transformer for an identifier like sleep, it reports:
  • “failed to locate transformer” for sleep
  • the step is left commented out in the generated workflow
Example of a generated workflow where sleep was previously commented out:
name: Second Stage
runs-on: ubuntu-latest
needs: First_Stage
steps:
  - name: checkout
    uses: actions/checkout@v4.1.0
  - name: echo message
    run: echo Starting the second stage...
# This item has no matching transformer
#  - sleep:
#      _key: time
#      value:
#        isLiteral: true
#        value: 20
  - name: Sleep for 5 seconds
    run: sleep 5s
  - name: echo message
    run: echo Second stage completed.
We need a transformer that reads the time argument from the Jenkins representation and dynamically generates the proper run: sleep <Ns> step.

How the Jenkins step appears in the importer JSON

The importer receives each pipeline step as a JSON item. For sleep, the JSON looks like:
{
  "name": "sleep",
  "arguments": [
    {
      "key": "time",
      "value": {
        "isLiteral": true,
        "value": 20
      }
    }
  ]
}
The numeric sleep value is available at arguments[0].value.value (for example, 5 or 20). Extract that value in your transformer and use it to build the GitHub Actions step.

Transformer basics

  • Transformers are Ruby .rb files that define one or more transform blocks (DSL entries).
  • Each transform block should return a Ruby Hash that maps to the GitHub Actions YAML for a step.
  • Provide transformer files to the CLI with --custom-transformers for audit, dry-run, or migrate.
Example CLI usage:
gh actions-importer dry-run jenkins \
  --source-url http://139.84.149.83:8080/job/folder-1/job/folder-2/job/pipeline-project-2 \
  --output-dir tmp/dry-run \
  --custom-transformers helper-transformer.rb
Use dry-run to validate your transformer output before running a full migration. This prevents creating PRs with incorrect workflows.

Step 1 — Helper transformer to inspect sleep items

Start by creating a small helper transformer that prints each sleep item so you can confirm the JSON structure and the path to the time value. Save as helper-transformer.rb:
# helper-transformer.rb
transform "sleep" do |item|
  puts "JSON for sleep identifier: #{item}"
end
Run the importer with:
gh actions-importer dry-run jenkins \
  --source-url http://139.84.149.83:8080/job/folder-1/job/folder-2/job/pipeline-project-2 \
  --output-dir tmp/dry-run \
  --custom-transformers helper-transformer.rb
You should see output like:
JSON for sleep identifier: {"name"=>"sleep", "arguments"=>[{"key"=>"time", "value"=>{"isLiteral"=>true, "value"=>5}}]}
JSON for sleep identifier: {"name"=>"sleep", "arguments"=>[{"key"=>"time", "value"=>{"isLiteral"=>true, "value"=>20}}]}
This confirms where the time value is located in each sleep item.

Step 2 — Final sleep transformer

Now implement the transformer that extracts the time value and returns a hash representing the corresponding GitHub Actions step. Save it as sleep-transformer.rb:
# sleep-transformer.rb
transform 'sleep' do |item|
  # Extract the sleep duration from the step's arguments
  sleep_time = item["arguments"][0]["value"]["value"]

  # Return a Hash representing the GitHub Actions step
  {
    "name" => "Sleep for #{sleep_time} seconds",
    "run"  => "sleep #{sleep_time}s"
  }
end
Notes:
  • Access the time via item["arguments"][0]["value"]["value"].
  • Return a Ruby Hash with string keys; the importer converts it into YAML for the workflow.
Run a dry-run using this transformer:
gh actions-importer dry-run jenkins \
  --source-url http://139.84.149.83:8080/job/folder-1/job/folder-2/job/pipeline-project-2 \
  --output-dir tmp/dry-run \
  --custom-transformers sleep-transformer.rb
If successful, the generated workflow will include transformed sleep steps.

Example generated YAML after transformation

name: folder-1/folder-2/pipeline-project-2
on:
  workflow_dispatch:
jobs:
  First_Stage:
    name: First Stage
    runs-on: ubuntu-latest
    steps:
      - name: checkout
        uses: actions/checkout@v4.1.0
      - name: echo message
        run: echo Starting the first stage...
      - name: Sleep for 5 seconds
        run: sleep 5s
      - name: echo message
        run: echo First stage completed.
  Second_Stage:
    name: Second Stage
    runs-on: ubuntu-latest
    needs: First_Stage
    steps:
      - name: checkout
        uses: actions/checkout@v4.1.0
      - name: echo message
        run: echo Starting the second stage...
      - name: Sleep for 20 seconds
        run: sleep 20s
      - name: echo message
        run: echo Second stage completed.

Migrating with the transformer

You can supply the transformer to the migrate command. Example:
gh actions-importer migrate jenkins \
  --target-url https://github.com/jenkins-demo-org/demo-repo \
  --output-dir tmp/migrate \
  --source-url http://139.84.149.83:8080/job/folder-1/job/folder-2/job/pipeline-project-2 \
  --custom-transformers sleep-transformer.rb
If the migration succeeds, the importer creates a pull request in the target repository (for example: https://github.com/jenkins-demo-org/demo-repo/pull/3) that contains the converted workflow including the dynamically generated sleep steps. You can review and merge that PR on GitHub.
A screenshot of a GitHub pull request page titled "Convert folder-1/folder-2/pipeline-project-2 to GitHub Actions." The page shows a comment saying the pipeline was migrated from Jenkins and the commit message / merge confirmation form.

Summary checklist

TaskWhy it mattersExample / Command
Create transformer fileTransformers define how identifiers map to workflow YAMLsleep-transformer.rb
Inspect pipeline JSONEnsures you extract the correct value pathUse helper-transformer.rb with dry-run
Use --custom-transformersProvide custom logic to the importer--custom-transformers sleep-transformer.rb
Test with dry-runValidate output before making changes to a repogh actions-importer dry-run jenkins --custom-transformers sleep-transformer.rb
Migrate to create PRApply transformer during migration to produce a PRgh actions-importer migrate jenkins --custom-transformers sleep-transformer.rb
Ensure you access literal values correctly (e.g., item["arguments"][0]["value"]["value"]). Returning an incorrect structure can cause the importer to leave the item untransformed.

Final example transformer (recap)

transform 'sleep' do |item|
  sleep_time = item["arguments"][0]["value"]["value"]

  {
    "name" => "Sleep for #{sleep_time} seconds",
    "run"  => "sleep #{sleep_time}s"
  }
end
With this pattern you can implement custom transformers for other Jenkins identifiers (for example: node, environment variables, or custom build steps) that the importer does not convert automatically.

Watch Video