Dan Munro - Dev-Sec-Ops Engineer   News  Archives  Recommendations

CI/CD On A Budget For Open Source Projects

TL;DR - an example project with all the moving components hooked up.

I love to learn. I also love to build and automate. These are activities that provide natural, intrinsic joy for me. Writing, on the other hand, is a skill to be consciously practiced. So, to that end, I’m documenting the relevant parts for setting up a build and deploy pipeline using the following technologies:

The Tech

One of the biggest benefits to the above pipeline is cost. For open source projects, everything except the droplet in Digital Ocean is completely free.

Digital Ocean starts at a $5/mo buy in.

Setting up Gradle

Below are portions of a build.gradle configuration which add JaCoCo support, and SonarQube configured to send reports to a SonarCloud project.

plugins {
    id 'jacoco'

    id 'org.sonarqube' version '2.8'
}

apply plugin: 'jacoco'

jacoco {
    toolVersion = "0.8.5"
}

subprojects {
    test.useTestNG()
}

jacocoTestReport {
    reports {
        xml.enabled = true
    }
}

def env = System.getenv()

sonarqube {
    properties {
        property "sonar.projectKey", "<< SONARCLOUD PROJECT KEY >>"
        property "sonar.organization", "<< SONARCLOUD ORGANIZATION >>"
        property "sonar.host.url", "https://sonarcloud.io"
        property "sonar.login", env["SONARCLOUD"]
        property "sonar.java.coveragePlugin", "jacoco"
        property "sonar.coverage.jacoco.xmlReportPaths", file("${rootProject.projectDir}/build/reports/jacoco/test/jacocoTestReport.xml")
    }
}

jacocoTestReport.dependsOn {
    subprojects*.test
}

GitHub Actions

GitHub Actions are a relatively new feature of GitHub, which allow automating workflows. The example workflow below is configured to run whenever a commit is pushed (line two, on: push). The steps roughly follow:

name: Run Gradle Tests
on: push
jobs:
  gradle:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v1
      - uses: actions/setup-java@v1
        with:
          java-version: 11
      - name: Gradle Tests
        uses: eskatos/gradle-command-action@v1
        env:
          ENV: ci
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
        with:
          arguments: clean test jacocoTestReport sonarqube -Dsonar.login=${{ secrets.SONARCLOUD }}
      - name: Publish to Quay
        uses: HurricanKai/Publish-Docker-Github-Action@master
        if: contains(github.ref, 'refs/tags/v')
        with:
          name: << DOCKER REPOSITORY URL >>
          username: ${{ secrets.DOCKER_USERNAME }}
          password: ${{ secrets.DOCKER_PASSWORD }}
          registry: quay.io
          tagging: true
      - name: Restart service
        uses: appleboy/ssh-action@master
        if: contains(github.ref, 'refs/tags/v')
        with:
          host: ${{ secrets.HOST }}
          USERNAME: ${{ secrets.USERNAME }}
          PORT: ${{ secrets.PORT }}
          KEY: ${{ secrets.SSHKEY }}
          script: |
            systemctl stop << MY SERVICE >>
            /snap/bin/docker system prune -af
            systemctl start << MY SERVICE >>

With the above snippet, add relevant secrets to the GitHub:

# if using Quay
DOCKER_USERNAME=
DOCKER_PASSWORD=

# if deploying to Digital Ocean
HOST=
USERNAME=
PORT=
SSHKEY=

Also replace << PLACEHOLDER >> values with actual values, and your project should be good to go.

Written on May 30, 2020.