# Use custom transformers to customize GitHub Actions Importer's behavior In this lab you will build upon the `dry-run` command to override GitHub Actions Importer's default behavior and customize the converted workflow using "custom transformers." Custom transformers can be used to: 1. Convert items that are not automatically converted. 2. Convert items that were automatically converted using different actions. 3. Convert environment variable values differently. 4. Convert references to runners to use a different runner name in Actions. ## Prerequisites 1. Followed the steps [here](./readme.md#configure-your-codespace) to set up your GitHub Codespaces environment and start a Jenkins server. 2. Completed the [configure lab](./1-configure.md#configuring-credentials). 3. Completed the [dry-run lab](./4-dry-run.md). ## Perform a dry run You will be performing a `dry-run` command to inspect the workflow that is converted by default. Run the following command within the codespace terminal: ```bash gh actions-importer dry-run jenkins --source-url http://localhost:8080/job/test_pipeline --output-dir tmp/dry-run ``` The converted workflow that is generated by the above command can be seen below:
Converted workflow 👇 ```yaml name: test_pipeline on: push: paths: "*" schedule: - cron: 0-29/10 * * * * env: DISABLE_AUTH: 'true' DB_ENGINE: sqlite jobs: build: runs-on: - self-hosted - TeamARunner steps: - name: checkout uses: actions/checkout@v2 - name: echo message run: echo "Database engine is ${{ env.DB_ENGINE }}" # # This item has no matching transformer # - sleep: # - key: time # value: # isLiteral: true # value: 80 - name: echo message run: echo "DISABLE_AUTH is ${{ env.DISABLE_AUTH }}" test: runs-on: - self-hosted - TeamARunner needs: build steps: - name: checkout uses: actions/checkout@v2 - name: Publish test results uses: EnricoMi/publish-unit-test-result-action@v1.7 if: always() with: files: "**/target/*.xml" ```
_Note_: You can refer to the previous [lab](./4-dry-run.md) to learn about the fundamentals of the `dry-run` command. ## Custom transformers for an unknown step The converted workflow above contains a `sleep` step that was not automatically converted. Answer the following questions before writing a custom transformer: 1. What is the "identifier" of the step to customize? - __sleep__. The identifier will be the key of a key value pair within the step of a Jenkinsfile. 2. What is the desired Actions syntax to use instead? - After some research, you have determined that the following bash script will provide similar functionality: ```yaml - name: Sleep for 80 seconds run: sleep 80s shell: bash ``` Now you can begin to write the custom transformer. Custom transformers use a DSL built on top of Ruby and should be defined in a file with the `.rb` file extension. You can create this file by running the following command in your codespace terminal: ```bash touch transformers.rb && code transformers.rb ``` Next, you will define a `transform` method for the `sleep` identifier by adding the following code to `transformers.rb`: ```ruby transform "sleep" do |item| wait_time = item["arguments"][0]["value"]["value"] { "name": "Sleep for #{wait_time} seconds", "run": "sleep #{wait_time}s", "shell": "bash" } end ``` This method can use any valid Ruby syntax and should return a `Hash` that represents the YAML that should be generated for a given step. GitHub Actions Importer will use this method to convert a step with the provided identifier and will use the `item` parameter for the original values configured in Jenkins. Now you can perform another `dry-run` command and use the `--custom-transformers` CLI option to provide this custom transformer. Run the following command within your codespace terminal: ```bash gh actions-importer dry-run jenkins --source-url http://localhost:8080/job/test_pipeline --output-dir tmp/dry-run --custom-transformers transformers.rb ``` Open the workflow that is generated and inspect the contents. Now the `sleep` step is converted and uses the customized behavior. ```diff - # # This item has no matching transformer - # - sleep: - # - key: time - # value: - # isLiteral: true - # value: 80 + - name: Sleep for 80 seconds + run: sleep 80s + shell: bash ``` ## Custom transformers for a known step You can also override GitHub Actions Importer's default behavior. In this scenario, you may not want to use the third-party action for publishing junit test results that is used by default. Again, answer the following questions before writing a custom transformer: 1. What is the "identifier" of the step to customize? - __junit__ 2. What is the desired Actions syntax to use instead? - After some research, you have determined that uploading test results as an artifact will be suitable: ```yaml - uses: actions/upload-artifact@v3 with: name: junit-artifact path: path/to/artifact/world.txt ``` You will build this custom transformer similar to the previous custom transformer, however, you first need to inspect the `item` keyword to programmatically use the file path to junit's test results in the `actions/upload-artifact@v3` step. To do this, you will print `item` to the console. You can achieve this by adding the following custom transformer to `transformers.rb`: ```ruby transform "junit" do |item| puts "This is the item: #{item}" end ``` Now, you can perform another `dry-run` command with the `--custom-transformers` CLI option. The output of the `dry-run` command should look similar to this: ```console $ gh actions-importer dry-run jenkins --source-url http://localhost:8080/job/test_pipeline --output-dir tmp/dry-run --custom-transformers transformers.rb [2022-08-20 22:08:20] Logs: 'tmp/dry-run/log/actions-importer-20220916-022628.log' This is the item: {"name"=>"junit", "arguments"=>[{"key"=>"testResults", "value"=>{"isLiteral"=>true, "value"=>"**/target/*.xml"}}]} [2022-08-20 22:08:20] Output file(s): [2022-08-20 22:08:20] tmp/dry-run/test_pipeline/.github/workflows/test_pipeline.yml ``` Now that you know the data structure of `item`, you can access the file path programmatically by editing the custom transformer to the following: ```ruby transform "junit" do |item| test_results = item["arguments"].find{ |a| a["key"] == "testResults" } file_path = test_results.dig("value", "value") { "uses" => "actions/upload-artifact@v3", "with" => { "name" => "junit-artifact", "path" => file_path } } end ``` _Note_: `transformers.rb` should contain a `transform` method for both `sleep` and `junit`. Now you can perform another `dry-run` command with the `--custom-transformers` CLI option. When you open the converted workflow the `EnricoMi/publish-unit-test-result-action@v1.7` action will have been replaced with the customized steps. ```diff - - name: Publish test results - uses: EnricoMi/publish-unit-test-result-action@v1.7 - if: always() - with: - files: "**/target/*.xml" + - uses: actions/upload-artifact@v3 + with: + name: junit-artifact + path: "**/target/*.xml" ``` ## Custom transformers for environment variables You can also use custom transformers to edit the values of environment variables in converted workflows. In this example, you will be updating the `DB_ENGINE` environment variable to be `mongodb` instead of `sqlite`. To do this, add the following code at the top of the `transformers.rb` file. ```ruby env "DB_ENGINE", "mongodb" ``` In this example, the first parameter to the `env` method is the environment variable name and the second is the updated value. Now you can perform another `dry-run` command with the `--custom-transformers` CLI option. When you open the converted workflow the `DB_ENGINE` environment variable will be set to `mongodb`: ```diff env: DISABLE_AUTH: 'true' - DB_ENGINE: sqlite + DB_ENGINE: mongodb ``` ## Custom transformers for runners Finally, you can use custom transformers to dictate which runners the converted workflows should use. To do this, answer the following questions: 1. What is the label of the runner in Jenkins to update? - __TeamARunner__ 2. What is the label of the runner in Actions to use instead? - __ubuntu-latest__ With these questions answered, you can add the following code to the `transformers.rb` file: ```ruby runner "TeamARunner", "ubuntu-latest" ``` In this example, the first parameter to the `runner` method is the Jenkins label and the second is the Actions runner label. Now you can perform another `dry-run` command with the `--custom-transformers` CLI option. When you open the converted workflow the `runs-on` statement will use the customized runner label: ```diff runs-on: - - self-hosted - - TeamARunner + - ubuntu-latest ``` At this point the file contents of `transformers.rb` should match this:
Custom transformers 👇 ```ruby runner "TeamARunner", "ubuntu-latest" env "DB_ENGINE", "mongodb" transform "sleep" do |item| wait_time = item["arguments"][0]["value"]["value"] { "name": "Sleep for #{wait_time} seconds", "run": "sleep #{wait_time}s", "shell": "bash" } end transform "junit" do |item| test_results = item["arguments"].find{ |a| a["key"] == "testResults" } file_path = test_results.dig("value", "value") [ { "uses": "actions/upload-artifact@v3", "with": { "name": "my-artifact", "path": file_path } } ] end ```
That's it. Congratulations, you have overridden GitHub Actions Importer's default behavior by customizing the conversion of: - Unknown steps - Known steps - Environment variables - Runners ## Next lab [Perform a production migration of a Jenkins pipeline](6-migrate.md)