> ## Documentation Index
> Fetch the complete documentation index at: https://notes.kodekloud.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Demo Custom Transformer Trivy Vulnerability Scanner

> Migrating a Jenkins Trivy vulnerability scanner stage to an equivalent GitHub Actions job using aquasecurity/trivy-action and a Ruby transformer to preserve behavior and upload HTML reports

This lesson shows how to migrate a Jenkins pipeline stage that runs the Trivy vulnerability scanner into an equivalent GitHub Actions job. The objective is to preserve behavior:

* Scan the built Docker image,
* Fail the pipeline when CRITICAL vulnerabilities are found,
* Publish an HTML report for inspection.

Below we start with the relevant Jenkins stages, explain what they do, show the equivalent GitHub Actions step, and present a sample custom transformer (Ruby) used by migration tooling to automate the conversion.

## Jenkins pipeline (source)

Here is the consolidated, relevant portion of the original `Jenkinsfile`. It includes three stages: Code Coverage, Build/Publish Image, and Trivy Vulnerability Scanner.

```groovy theme={null}
stage('Code Coverage') {
    agent {
        docker {
            image 'node:24'
            args '-u root:root'
        }
    }
    steps {
        catchError(buildResult: 'SUCCESS', message: 'Oops! it will be fixed in future releases', stageResult: 'UNSTABLE') {
            sh 'npm run coverage'
        }
        publishHTML([
            allowMissing: true,
            alwaysLinkToLastBuild: true,
            keepAll: true,
            reportDir: 'coverage/lcov-report',
            reportFiles: 'index.html',
            reportName: 'Coverage'
        ])
    }
}

stage('Build Publish Image') {
    steps {
        sh 'docker build -t siddharth67/solar-system:$GIT_COMMIT .'
        withDockerRegistry(credentialsId: 'docker-hub-credentials', url: "") {
            sh 'docker push siddharth67/solar-system:$GIT_COMMIT'
        }
    }
}

stage('Trivy Vulnerability Scanner') {
    steps {
        sh '''trivy image siddharth67/solar-system:$GIT_COMMIT --severity CRITICAL --exit-code 1 --quiet --format json -o trivy-image-CRITICAL-results.json'''
        sh '''trivy convert --format template --template "@/usr/local/share/trivy/templates/html.tpl" --output trivy-image-CRITICAL-results.html trivy-image-CRITICAL-results.json'''
        publishHTML([
            allowMissing: true,
            alwaysLinkToLastBuild: true,
            keepAll: true,
            reportDir: './',
            reportFiles: 'trivy-image-CRITICAL-results.html',
            reportName: 'Trivy CRITICAL'
        ])
    }
}
```

What these Jenkins `sh` steps do:

* The `trivy image` command scans `siddharth67/solar-system:$GIT_COMMIT` with:
  * `--severity CRITICAL` (filter to CRITICAL)
  * `--exit-code 1` (non-zero exit if CRITICAL vuln found)
  * `--quiet` (less noisy)
  * `--format json -o <file>` (JSON output saved)
* The `trivy convert` command converts JSON into an HTML report using a template.

## Why use the official Trivy GitHub Action?

The official action from Aqua Security (`aquasecurity/trivy-action`) can run Trivy on GitHub Actions runners and produce templated output directly (avoiding a separate `convert` step). It supports inputs such as `format`, `template`, `output`, `severity`, `exit-code`, and `hide-progress`.

<Callout icon="lightbulb" color="#1CB2FE">
  Using `aquasecurity/trivy-action` lets you:

  * run Trivy without installing it manually on runners,
  * request templated HTML output directly (via `format: template` and `template`),
  * set `exit-code` so the job fails conditionally based on severity.
</Callout>

See the action documentation: [https://github.com/aquasecurity/trivy-action](https://github.com/aquasecurity/trivy-action)

<Frame>
  <img src="https://mintcdn.com/kodekloud-c4ac6d9a/iRlNGmU-DLlt2Ghu/images/Migrating-Jenkins-Pipelines-to-GitHub-Actions/Automate-Migration-From-Jenkins-to-GitHub-Actions/Demo-Custom-Transformer-Trivy-Vulnerability-Scanner/aqua-trivy-action-inputs-table.jpg?fit=max&auto=format&n=iRlNGmU-DLlt2Ghu&q=85&s=b690da92a4be64866753f665ca6ac432" alt="A dark-themed screenshot of a GitHub Marketplace / Actions page showing a table of inputs for the Aqua Security Trivy action, with columns for option name, type, default and description. The table lists parameters like vuln-type, severity, skip-dirs, cache-dir and a highlighted &#x22;hide-progress&#x22; row." width="1920" height="1080" data-path="images/Migrating-Jenkins-Pipelines-to-GitHub-Actions/Automate-Migration-From-Jenkins-to-GitHub-Actions/Demo-Custom-Transformer-Trivy-Vulnerability-Scanner/aqua-trivy-action-inputs-table.jpg" />
</Frame>

## Mapping Jenkins flags to Trivy Action inputs

Use this quick reference when converting the Jenkins `trivy` shell invocation into action inputs.

| Jenkins option / behavior                                     |                               Trivy Action input | Example                                                                                                      |
| ------------------------------------------------------------- | -----------------------------------------------: | ------------------------------------------------------------------------------------------------------------ |
| `--severity CRITICAL`                                         |                                       `severity` | `CRITICAL`                                                                                                   |
| `--exit-code 1`                                               |                                      `exit-code` | `1`                                                                                                          |
| `--quiet`                                                     |                                  `hide-progress` | `true`                                                                                                       |
| `--format json -o file.json` + `trivy convert`                | `format: template`, `template: <path>`, `output` | `format: template`, `template: "@$HOME/.local/bin/trivy-bin/contrib/html.tpl"`, `output: trivy-results.html` |
| Image reference (e.g. `siddharth67/solar-system:$GIT_COMMIT`) |                                      `image-ref` | `${{ vars.DOCKERHUB_USERNAME }}/${{ vars.IMAGE_NAME }}:${{ github.sha }}`                                    |

Notes:

* Wrap special GitHub expressions and variables in backticks when showing in docs (e.g. `${{ github.sha }}`).
* Use `format: template` to make the action emit HTML directly, avoiding a separate conversion step.

## Minimal GitHub Actions step equivalent

A compact Actions step that mirrors the Jenkins behavior and outputs an HTML report:

```yaml theme={null}
- name: Run Trivy vulnerability scanner
  uses: aquasecurity/trivy-action@v0.30.0
  with:
    image-ref: "docker.io/my-organization/my-app:${{ github.sha }}"
    format: template
    template: "@$HOME/.local/bin/trivy-bin/contrib/html.tpl"
    output: trivy-results.html
    exit-code: '1'
    severity: "CRITICAL"
    hide-progress: true
```

Note: the `template` path above references the template bundled by the action. You can also provide a custom template file in your repository and point to it.

## Automating the conversion: custom transformer (Ruby)

To automate migrating Jenkins `sh` steps to GitHub Actions, you can create a transformer that:

* Detects `sh` calls that invoke `trivy image`,
* Extracts the image reference, severity, format, output file, exit code, quiet flag,
* Normalizes image references into repository variables (e.g. `vars.DOCKERHUB_USERNAME`, `vars.IMAGE_NAME`) and substitutes `${{ github.sha }}`,
* Emits a `aquasecurity/trivy-action` step plus an `actions/upload-artifact` step that runs with `if: ${{ always() }}` so the report is uploaded even if the scan fails.

Example transformer used with migration tooling:

```ruby theme={null}
# ss-pipeline-transformer.rb
# Example transformer that converts Jenkins 'sh' trivy commands into GH Actions steps.
transform "sh" do |item|
  script = item.dig("arguments", 0, "value", "value").to_s.strip
  next nil unless script.downcase.start_with?("trivy image")

  # Extract image reference
  image_match = script.match(/trivy image (\S+)/i)
  image_ref = image_match ? image_match[1] : nil
  next nil unless image_ref

  # Extract other flags (fault-tolerant)
  severity = script.match(/--severity\s+(\S+)/i)&.captures&.first || "CRITICAL"
  format   = script.match(/--format\s+(\S+)/i)&.captures&.first || "json"
  output   = script.match(/-o\s+(\S+)/i)&.captures&.first || "trivy-results.json"
  exit_code = (script.match(/--exit-code\s+(\d+)/i)&.captures&.first || "1").to_i
  quiet = script.include?("--quiet") || script.include?("--quiet=true")

  # Normalize the image reference if it matches the original pattern
  if image_ref.include?("siddharth67/solar-system") || image_ref.include?("siddharth67/solar-system:$GIT_COMMIT")
    normalized_image_ref = "${{ vars.DOCKERHUB_USERNAME }}/${{ vars.IMAGE_NAME }}:${{ github.sha }}"
  else
    normalized_image_ref = image_ref
  end

  # Decide template-based output (we want HTML directly)
  # Use the Trivy action with template format and set the output to an HTML file
  trivy_output_html = output.end_with?(".html") ? output : "trivy-results.html"

  [
    {
      "name" => "Trivy Security Scan",
      "uses" => "aquasecurity/trivy-action@v0.30.0",
      "with" => {
        "image-ref" => normalized_image_ref,
        "severity"  => severity,
        "format"    => "template",
        "template"  => "@$HOME/.local/bin/trivy-bin/contrib/html.tpl",
        "output"    => trivy_output_html,
        "exit-code" => exit_code,
        "hide-progress" => quiet
      }
    },
    {
      "name" => "Upload Scan Report",
      "if" => "${{ always() }}",
      "uses" => "actions/upload-artifact@v4",
      "with" => {
        "name" => "Trivy Report",
        "path" => trivy_output_html
      }
    }
  ]
end
```

When the transformer runs as part of the migration tool, it replaces the Jenkins `sh` Trivy commands with the two GitHub Actions steps shown below.

## Generated GitHub Actions job (migrated)

Example of the generated job after running the transformer:

```yaml theme={null}
Trivy_Vulnerability_Scanner:
  name: Trivy Vulnerability Scanner
  runs-on:
    - ubuntu-latest
  needs: Build_Publish_Image
  steps:
    - name: checkout
      uses: actions/checkout@v4.1.0
    - name: Trivy Security Scan
      uses: aquasecurity/trivy-action@v0.30.0
      with:
        image-ref: "${{ vars.DOCKERHUB_USERNAME }}/${{ vars.IMAGE_NAME }}:${{ github.sha }}"
        severity: CRITICAL
        format: template
        template: "@$HOME/.local/bin/trivy-bin/contrib/html.tpl"
        output: trivy-results.html
        exit-code: 1
        hide-progress: true
    - name: Upload Scan Report
      if: "${{ always() }}"
      uses: actions/upload-artifact@v4
      with:
        name: Trivy Report
        path: trivy-results.html
```

After the migrated workflow runs:

* The Trivy action installs the Trivy binary, runs the scan, and writes `trivy-results.html`.
* If no CRITICAL vulnerabilities are found, the job succeeds and the report is uploaded.
* If CRITICAL vulnerabilities are found, the action exits with code `1` (job fails), but the upload step still runs because of `if: ${{ always() }}` so you can download the report.

<Frame>
  <img src="https://mintcdn.com/kodekloud-c4ac6d9a/iRlNGmU-DLlt2Ghu/images/Migrating-Jenkins-Pipelines-to-GitHub-Actions/Automate-Migration-From-Jenkins-to-GitHub-Actions/Demo-Custom-Transformer-Trivy-Vulnerability-Scanner/trivy-scan-solar-system-alpine-nodepkg.jpg?fit=max&auto=format&n=iRlNGmU-DLlt2Ghu&q=85&s=debb4da11ca1bf2a03e75a208f6438d1" alt="A browser screenshot of a Trivy scan results page for &#x22;siddharth67/solar-system&#x22; showing &#x22;alpine&#x22; and &#x22;node-pkg&#x22; with &#x22;No Vulnerabilities found&#x22; and &#x22;No Misconfigurations found.&#x22; A small download notification for &#x22;Trivy Report.zip&#x22; is visible in the top-right." width="1920" height="1080" data-path="images/Migrating-Jenkins-Pipelines-to-GitHub-Actions/Automate-Migration-From-Jenkins-to-GitHub-Actions/Demo-Custom-Transformer-Trivy-Vulnerability-Scanner/trivy-scan-solar-system-alpine-nodepkg.jpg" />
</Frame>

## Best practices & tips

* Keep `exit-code` small integer values (`0` or `1`) to clearly control job success/failure.
* Use `format: template` + `template:` to generate user-friendly HTML directly from the action.
* Use variables for image references in migrated workflows: `${{ vars.DOCKERHUB_USERNAME }}`, `${{ vars.IMAGE_NAME }}`, and `${{ github.sha }}` improve portability.
* Always upload scan reports with `if: ${{ always() }}` so artifacts are available regardless of job status.
* Consult the action docs for advanced inputs and templates: [https://github.com/aquasecurity/trivy-action](https://github.com/aquasecurity/trivy-action)
* For artifact uploads, see `actions/upload-artifact`: [https://github.com/actions/upload-artifact](https://github.com/actions/upload-artifact) and GitHub Expressions docs: [https://docs.github.com/en/actions/learn-github-actions/expressions#always](https://docs.github.com/en/actions/learn-github-actions/expressions#always)

## Summary

* The Jenkins `trivy image` + `trivy convert` pipeline can be mapped directly to `aquasecurity/trivy-action` in GitHub Actions to simplify the workflow and avoid manual conversions.
* A custom transformer can detect `trivy` shell commands and emit equivalent action steps while normalizing image references and preserving behavior (severity, exit-code, quiet).
* Upload reports using `actions/upload-artifact@v4` with `if: ${{ always() }}` so you can inspect results even on failures.

This approach keeps the original pipeline intent intact while producing a cleaner, maintainable workflow native to GitHub Actions.

<CardGroup>
  <Card title="Watch Video" icon="video" cta="Learn more" href="https://learn.kodekloud.com/user/courses/migrating-jenkins-pipelines-to-github-actions/module/3b5e500f-482a-4860-9f2c-d5f9fbc95159/lesson/24ef3b09-e929-467f-9424-1586f820f703" />

  <Card title="Practice Lab" icon="flask-conical" cta="Learn more" href="https://learn.kodekloud.com/user/courses/migrating-jenkins-pipelines-to-github-actions/module/3b5e500f-482a-4860-9f2c-d5f9fbc95159/lesson/807211cc-a4ed-4974-98a5-a850cdeaee5f" />
</CardGroup>
