git revert changes from CI pipeline
At times, you might want to undo changes introduced by a pull request (PR) in a Git repository. This can happen for various reasons, such as if the changes introduced by the PR are found to be problematic, introduce bugs, or if they are no longer needed. This guide shows an example where a pipeline is triggered based on a pull request, runs security scans, and if the test fails, the pipeline reverts the last commit introduced by the PR.
Prerequisites
- A Harness account with access to the Continuous Integration module. If you are new to Harness, you can sign up for free.
- A GitHub account and a sample repository. Any other SCM provider should be fine, but this guide uses GitHub as an example.
- A GitHub Personal Access Token (PAT) with read/write access to the repository and read/write access to webhook creations. You can create a PAT from your GitHub account's Settings > Developer settings > Personal access tokens.
- A Docker Hub Personal Access Token (PAT). You can create a PAT from your Docker Hub account's Account Settings > Security > New Access Token.
Design
When a pull request is merged, a CI pipeline is triggered based on the commit SHA. The pipeline runs security scans, and if any test fails, a native Plugin step automatically reverts the git commit(s) introduced by the PR.
Setup Harness secret and connectors
Create a Harness secret for your GitHub Personal Access Token (PAT).
Create a Harness GitHub Connector and enable API access. API access is required for any operations that require Harness to call GitHub APIs, such as using the Harness Git Experience, cloning codebases from PRs, automatically detecting branch names when you manually run pipelines, using Git webhook triggers, and updating Git statuses. Select Connect through Harness Platform.
Create a Docker connector to be able to pull docker images in the CI pipeline.
Pipeline to revert git commit based on security scans
If this is the first time you're using Harness CI, please check out CI onboarding guide. Create a new pipeline using the following YAML:
pipeline:
  name: git_revert_pipeline
  identifier: git_revert_pipeline
  projectIdentifier: default_project
  orgIdentifier: default
  tags: {}
  stages:
    - stage:
        name: git-revert-stage
        identifier: clone
        description: ""
        type: CI
        spec:
          cloneCodebase: false
          execution:
            steps:
              - step:
                  type: GitClone
                  name: GitClone
                  identifier: GitClone
                  spec:
                    connectorRef: YOUR_GITHUB_CONNECTOR_ID
                    build:
                      type: commitSha
                      spec:
                        commitSha: <+trigger.commitSha>
              - step:
                  type: Owasp
                  name: Owasp Scan
                  identifier: Owasp_Scan
                  spec:
                    mode: orchestration
                    config: default
                    target:
                      type: repository
                      workspace: /harness/YOUR_GIT_REPO
                      detection: manual
                      name: YOUR_GIT_REPO
                      variant: main
                    advanced:
                      log:
                        level: info
                      fail_on_severity: high
              - step:
                  type: Plugin
                  name: Git Revert Commit
                  identifier: git_revert_commit
                  spec:
                    connectorRef: YOUR_IMAGE_REGISTRY_CONNECTOR
                    image: plugins/git-revert-commit:linux-amd64
                    settings:
                      git_pat: <+secrets.getValue("YOUR_GIT_PAT_SECRET")>
                      commit_sha: <+codebase.commitSha>
                  when:
                    stageStatus: Failure
          platform:
            os: Linux
            arch: Amd64
          runtime:
            type: Cloud
            spec: {}
          caching:
            enabled: false
            paths: []
Trigger the pipeline based on Git events
Add a trigger to your pipeline that will execute the pipeline when a pull request is merged in your target repository. Use the following trigger definition:
trigger:
  name: trigger_on_pr_merge
  identifier: trigger_on_pr_merge
  enabled: true
  encryptedWebhookSecretIdentifier: ""
  description: ""
  tags: {}
  orgIdentifier: default
  stagesToExecute: []
  projectIdentifier: default_project
  pipelineIdentifier: git_revert_pipeline
  source:
    type: Webhook
    spec:
      type: Github
      spec:
        type: PullRequest
        spec:
          connectorRef: YOUR_GITHUB_CONNECTOR_ID
          autoAbortPreviousExecutions: false
          payloadConditions:
            - key: targetBranch
              operator: Equals
              value: main
          headerConditions: []
          actions:
            - Close
  inputYaml: |
    pipeline:
      identifier: git_revert_pipeline
      properties:
        ci:
          codebase:
            build:
              type: PR
              spec:
                number: <+trigger.prNumber>
Test the setup
This guide uses OWASP Dependency-check Scanner as a sample test step. You can set different severity levels for this scanner. In this example, fail_on_severity is set to high which means if the scan finds any vulnerability in the code with high or critical  severity levels, the OWASP Scan and the pipeline fail automatically.
The Plugin step executes if the security scan fails which then reverts the Git commit that triggered the pipeline. This example shows the Git revert of a single commit but you can modify the script to revert multiple commits.
Create a pull request in your target Git repo with some known high/critical vulnerabilities and then merge the PR. This will trigger the pipeline, the OWASP Scan will find these vulnerabilities, and the Plugin step will revert the commit that triggered the pipeline. You can check the pipeline execution logs to see the output of the Plugin step. The pipeline will continue to run after the Plugin step and you can add more steps to the pipeline as needed.