Skip to main content
This lesson demonstrates a dry-run migration of a Jenkins Pipeline job to GitHub Actions. It continues from the dry-run and production migration concepts typically used for Jenkins Freestyle projects and shows how the gh actions-importer tool translates pipeline stages and steps into a GitHub Actions workflow file.
A presentation slide with the title "Perform Dry-run Migration of a Jenkins Job - 2" centered on a blue-green gradient background. The bottom-left corner shows "© Copyright KodeKloud."

Source Jenkins Pipeline (pipeline portion of config.xml)

For this demo we used the Pipeline Project 2 located under Folder One → Folder Two. Below is the pipeline stage structure (scripted/declarative) from the job’s config.xml:
stages {
    stage('First Stage') {
        steps {
            echo 'Starting the first stage...'
            sleep 5
            echo 'First stage completed.'
        }
    }

    stage('Second Stage') {
        steps {
            echo 'Starting the second stage...'
            sleep 2
            echo 'Second stage completed.'
        }
    }
}

Dry-run vs Migrate

  • Dry-run: translates and writes workflow files locally without modifying the target repository or creating pull requests.
  • Migrate: performs a full migration and can push changes to a target repo or open PRs.
Use the gh actions-importer subcommands for both operations.

Example: Full migration command

gh actions-importer migrate jenkins \
  --target-url https://github.com/:owner/:repo \
  --output-dir tmp/migrate \
  --source-url my-jenkins-project
Replace :owner and :repo with your GitHub organization/user and repository.

Example: Dry-run command used in this lesson

This dry-run writes translated workflows to tmp/dry-run without pushing anything:
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

Actual dry-run invocation (console output)

The demo’s console output shows the dry-run invocation and the generated output path:
root@jenkins in /home
# run dry-run
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

[2025-05-22 10:36:34] Logs: 'tmp/dry-run/log/valet-20250522-103634.log'
[2025-05-22 10:36:35] Output file(s):
tmp/dry-run/folder-1/folder-2/pipeline-project-2/.github/workflows/pipeline-project-2.yml

root@jenkins in /home took 23s

Generated workflow (abridged)

After the dry-run, the importer created a GitHub Actions workflow YAML. The converter successfully mapped echo statements to run steps and injected a checkout step. Jenkins sleep calls did not have a built-in transformer and are emitted as commented placeholders:
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...
      # This item has no matching transformer
      # - sleep:
      #   - key: time
      #     value:
      #       isLiteral: true
      #       value: 5
      - 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...
      # This item has no matching transformer
      # - sleep:
      #   - key: time
      #     value:
      #       isLiteral: true
      #       value: 2
      - name: echo message
        run: echo Second stage completed.

What the importer did (summary)

Jenkins constructResult in converted workflowAction required
echoConverted to - run: echo ... stepsNo action needed
checkout (implicit)Added via actions/checkout@v4.1.0No action needed
sleepEmitted as commented placeholder (# This item has no matching transformer)Create a custom transformer or manually update workflow
The dry-run log reveals these internal steps: the importer fetched the job’s config.xml, converted XML to an intermediate JSON representation, applied configured transformers, and emitted workflow YAML. Transformers were found for echo but not for sleep, so sleep entries appear as comments.
The importer emits commented placeholders for Jenkins constructs that lack matching transformers. To retain or convert such steps (for example, sleep), create a custom transformer that maps the Jenkins keyword/function to an equivalent GitHub Actions step (for instance, run: sleep 5). Custom transformers let you tailor conversions for team-specific pipeline usage.
Before running a full migrate:
  • Validate the dry-run output under tmp/dry-run and manually review any commented placeholders.
  • Add or author transformers for unsupported keywords if you need automated conversions.
  • Only push or open PRs after confirming the generated workflows are functionally equivalent.

Next steps

  1. Implement custom transformers for unsupported Jenkins constructs (e.g., sleeprun: sleep <time>).
  2. Re-run the dry-run to verify the new transformers produce the desired workflow YAML.
  3. When satisfied, perform a full migrate to apply workflows to your target GitHub repository or open PRs.
  • GitHub CLI (gh)
  • GitHub Actions workflows documentation: https://docs.github.com/actions
  • If you need a starting point for custom transformers, consult your actions-importer plugin docs or source repository for transformer templates.
That’s all for this lesson — use the dry-run to iterate safely until all steps convert cleanly.

Watch Video

Practice Lab