Recently, I've been interacting with a lot of open-source repositories on GitHub and most of them have automated builds setup with a built-in feature of GitHub namely GitHub Actions
Let's explore how we can setup a full-fledged a CI/CD pipeline using GitHub Actions. We'll be creating a sample WebAPI and deploying it to Azure App Service
GitHub Actions is a CI/CD platform that allows you to automate your build, test, and deployment pipeline. You can create workflows that build and test every pull request to your repository or deploy merged pull requests to production.
GitHub Actions goes beyond just DevOps and lets you run workflows when other events happen in your repository. For example, you can run a workflow to automatically add the appropriate labels whenever someone creates a new issue in your repository.
GitHub provides Linux, Windows, and macOS virtual machines to run your workflows, or you can host your own self-hosted runners in your own data center or cloud infrastructure.
There are several components to a GitHub Actions namely:
- Workflows
- Events
- Jobs
- Actions
- Runners
Let's deep dive into Workflows.
A workflow is a configurable automated process that will run one or more jobs. Workflows are defined by a YAML file checked in to your repository and will run when triggered by an event in your repository, or they can be triggered manually, or at a defined schedule.
Workflows are defined in the .github/workflows
directory in a repository, and a repository can have multiple workflows, each of which can perform a different set of tasks. For example, you can have one workflow to build and test pull requests, another workflow to deploy your application every time a release is created, and still another workflow that adds a label every time someone opens a new issue.
You can reference a workflow within another workflow.
I've used a boilerplate .NET Core WebAPI 8 template and pushed the code to this repository.
- Azure WebApp
- Setup Environment Variables
- Enable SCM Basic Auth Publishing Credentials - This will enable us to be able to publish our .zip from Github Actions
- Store Publish Profile in Github Repository Secrets - Since publish profile is considered as a sensitive information, it is ideal to store it in a secret and reference it within the pipeline later from here
Once these settings are done, we can proceed with creating our first workflow!
Go to the Actions tab of the repository and click on New Workflow
There are a lot of built-in workflows available on the Github Actions. If they fit your need, you can use them. Since we're learning to build one from the scratch, I'll selected Setup a workflow yourself
Using on
, we can specify when do we want this workflow to be triggered? For now, I've configured it to work in two ways:
push
- This will trigger the workflow when the code is pushed to the main branch
workflow_dispatch
- This will give us a manual way to trigger the pipeline from the GitHub Actions UI
name: Build 🚀
on:
workflow_dispatch:
push:
branches:
- main
If you're required to use any environment variables in the pipeline, that can be declared next. Here, I'm declaring an environment variable namely .NET version and I'm setting it to be 8.0.x
# Setup environment variables
env:
DOTNET_VERSION: 8.0.x
Within the next steps of the pipeline, we've build steps. We're requesting our job to run on the ubuntu-latest
.
Then, we need to define the steps required to be run as a part of this job.
We've used the below steps:
actions/checkout@v3
- This is to ensure our code is checked out from the main branch
actions/setup-dotnet@v3
- This is to download the specified version of .NET (In our case 8.0.x) within the VM
dotnet restore ./Starter.sln
- Restore the solution
Checking NuGet Vulnerabilities
- This is a DevSecOps approach which most of the enterprises follow these days to ensure they catch any vulnerabilities earlier in their development stage
- Within this step, we're usng the
dotnet list package
to list the Vulnerabilities in the project files we're using and pushing it to a file namelyvulnerabilities.txt
- If there are any vulnerabilities found, the pipeline would be blocked and no further steps would be executed
dotnet build
- Building the projects.
--configuration Release
switch will build the project withrelease
configuration--no-restore
switch will instruct the compiler to build the project without restoring (Since we've explicitly restored the project earlier) and we can save time here
dotnet test
- We would like to run the tests and along with that also publsh the code coverage report.
Create Code Coverage
- Create code coverage report
Publish Coverage report
- Publish the code coverage report as output of the Github summary
Publish Artifact
- Using this step, the artifacts created in the build job will be exported so that we could import and use it in the next step
jobs:
build:
runs-on: ubuntu-latest
steps:
# Checkout code
- uses: actions/checkout@v3
- name: Setup .NET
uses: actions/setup-dotnet@v3
with:
dotnet-version: ${{env.DOTNET_VERSION}}
- name: Dotnet Restore
run: dotnet restore ./Starter.sln
- name: Checking NuGet vulnerabilites
run: |
dotnet list package --vulnerable --include-transitive > vulnerabilities.txt
# Check if vulnerabilities are found in vulnerabilities.txt
if grep -q "Vulnerabilities found" vulnerabilities.txt; then
echo "Vulnerabilities found. Blocking pipeline."
exit 1
else
echo "No vulnerabilities found."
fi
# Specifying no-restore flag since we're already restoring in earlier.
- name: Dotnet Build
run: dotnet build ./Starter.sln --configuration Release --no-restore
# Specifying no-restore and no-build to speed up the process
- name: Dotnet Test
run: dotnet test ./Starter.sln --configuration Release --no-restore --no-build --collect:"XPlat Code Coverage" --logger trx --results-directory coverage
- name: Dotnet Publish
run: dotnet publish ./Starter/Starter.WebAPI/Starter.WebAPI.csproj --configuration Release --no-build --output '${{ env.AZURE_WEBAPP_PUBLISH_PATH }}/myapp'
- name: Code Coverage Summary Report
uses: irongut/[email protected]
with:
filename: 'coverage/*/coverage.cobertura.xml'
badge: true
format: 'markdown'
output: 'both'
- name: Publish Code Coverage
run: cat code-coverage-results.md >> $GITHUB_STEP_SUMMARY
- name: Publish artifact
uses: actions/upload-artifact@v4
with:
name: myapp
path: .
This is where we would be deploying the artifact generated in the previous step to Azure App Service. Web App and the App Service plan are already provisioned.
Within the deploy
job, we're doing the following:
We've used the below steps:
actions/download-artifact@v4
- This will download the artifact generated as the outcome of the build job
azure/webapps-deploy@v2
- This is where we connect to Azure App Service and uploading the .zip file contents using the publish profile
- Publish profile is generally considered as a secret so instead of hardcoding it here, it's better to reference it from Actions > Repository secrets
deploy:
runs-on: ubuntu-latest
needs: build
steps:
- name: Download artifact from build job
uses: actions/download-artifact@v4
with:
name: myapp
path: .
- name: Deploy to App Service
uses: azure/webapps-deploy@v2
with:
app-name: ${{ env.AZURE_WEBAPP_NAME }}
publish-profile: ${{ secrets.AZURE_PUBLISH_PROFILE }}
package: "${{ env.AZURE_WEBAPP_PUBLISH_PATH }}/myapp"
As you can see both the build and the deploy jobs have been successfully completed
After navigating to this URL, we're able to see our API deployed successfully
Feel free to request an issue on github if you find bugs or request a new feature. Your valuable feedback is much appreciated to better improve this project. If you find this useful, please give it a star to show your support for this project.