How to Set Up GitHub Actions for Continuous Integration
Tags: devops, tutorial, github, architecture
Published: 2024-05-23 13:20:37

## Introduction
In the fast-paced world of software development, ensuring that code is consistently tested and integrated can be a challenging task. This is where **Continuous Integration (CI)** comes to play.

> CI is a development practice that requires developers to integrate code into a shared repository frequently.

CI automates the process of building, testing and integrating code changes regularly, helping developers to catch issues early and maintain a stable codebase.

GitHub Actions, a powerful feature introduced by GitHub, has revolutionized the way developers automate their workflows. We will explore how to use this tool for setting up CI in your projects.

Here's what we will cover
* Understanding CI and it's benefits
* Introduction to GitHub Actions
* Building CI workflows with Github Actions
* Advanced Techniques and Integration with External Tools


## Benefits of Continuous Integration
There are several benefits to Continuous Integration. These include
* **Early Detection of Bugs**
Continuous Integration facilitates the early detection of bugs by automatically running tests with each code integration, allowing developers to identify and fix issues before they escalate, resulting in more stable software releases.

* **Improved Code Quality**
By ensuring consistent coding standards and practices through automated testing and integration, code quality is checked. This reduces technical debt and ensures the maintainability of the codebase

* **Faster Feedback Loop**
With Continuous Integration, developers receive rapid feedback on their code, enabling them to iterate quickly and address issues promptly leading to faster development cycles.

These are some of the benefits derived from Continuous Integration. Now let's look at using GitHub actions for CI.

## Introduction to GitHub Actions
GitHub Actions serves as a platform for continuous integration and continuous delivery (CI/CD), enabling you to automate the process of building, testing, and deploying your software pipeline. 

Here are some uses of GitHub Actions
* You can set up workflows that automatically build and test each pull request submitted to your repository.
* Deploy successfully merged pull requests to your production environment.
* Actions can also be used to package or release projects into the pipeline.

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. It has gained popularity for its simplicity and the fact that it is integrated directory into your repository.

Now let's look at the components that make up a GitHub workflow

## Understanding a Github Action Workflow
A workflow is a configurable automated process that will run one or more jobs. A GitHub Actions workflow is defined by a YAML file typically located in the `.github/workflows` directory within your repository.

### Components of a workflow
**Triggers**
These define the event that trigger the workflow. Example, the workflow can start when a user:
* Creates a pull request
* Opens an issue
* Pushes a commit to the repository
etc

**Jobs**
These specify the individual tasks to be executed as part of the workflow. Each job runs sequentially or in parallel and can include multiple steps.

**Steps**
Define the individual actions or commands to be executed within a job. Steps can include tasks like checking out code, running tests, or deploying artifacts.

**Actions**
Actions are reusable units of code that perform specific tasks within a workflow. They can be authored by GitHub or by the community and can be easily integrated into workflows. An action can pull your git repository from GitHub, set up the correct toolchain for your build environment

**Runners**
A runner is a server that runs your workflows when they're triggered. Each runner can run a single job at a time. GitHub provides three runners to run your workflows: _Ubuntu Linux_, _Microsoft Windows_ and _macOS_.

## Build an example workflow
In this section we would set up a simple workflow that **_runs linters_** for Python using pycodestyle. This will help ensure that your Python code adheres to PEP 8 style guidelines and is free of common errors.

### Step-By-Step Guide
1. Create the `.github/workflows` directory at the root of your repository.
2. Inside the directory, create a file `lint.yml`. Go to **Add file** and then write the directory as shown in the image below

![Creating the directory and the file](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ni2vrd64wmtsf7i8dcjx.png)

After this, commit the changes for it to take effect.

3. Open the `lint.yml` file and add the following code

```yaml
name: Lint Python Code

on: [push, pull_request]

jobs:
  lint:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout code
      uses: actions/checkout@v3

    - name: Set up Python
      uses: actions/setup-python@v4
      with:
        python-version: '3.x'

    - name: Install Pycodestyle
      run: |
        pip install pycodestyle

    - name: Lint Python files with Pycodestyle
      run: |
        pycodestyle .
```
### Explanation of the code
* `name`: Defines the name of the workflow as "Lint Python Code".
* `on`: Triggers the workflow on push or pull request events to the main branch. Hence, anytime there's a pull request to merge code or pushing to main branch, this workflow would be triggered.
* `jobs`: Defines a single job named **_lint_** that executes the linting steps.
* `runs-on`: Specifies the environment which would run the workflow, in our case, `ubuntu-latest`.
* `steps`: This section defines the sequential steps within the job:
    * `Checkout Code`: Uses the _actions/checkout_ action to check 
       out the repository code.
    * `Set up Python`: Uses the actions/setup-python action to set up 
       a Python environment.
    * `Install Pycodestyle`: Installs the Pycodestyle linter for 
       Python files.
    * `Lint Python Files`: Runs Pycodestyle on the entire repository 
       to check for PEP 8 compliance.

This workflow ensures that every time code is pushed or a pull request is created, Pycodestyle is automatically run to check for style issues in Python code.

Now let's create a simple python file `hello.py` in our repository with the following code

```python
#!/usr/bin/python3
"""
This is a function file
"""


def hello():
    print("hello World")


if __name__ == '__main__':
    hello()
```

Now commit and push this code to your repository. The workflow would automatically be triggered since we specified a **push** in the trigger. You would see something equivalent to the image below in the actions tab

![Full set of job hisory](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/miywqfvj2kznszrnlc5o.png)

Now when there's an error in the workflow (which normally reflects an error in the codebase), it would be marked **red** otherwise, it would check and you'll get something like the image below.

![Checked Image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vh177vcgk0407wfahitg.png)

## Debugging a workflow
Here's another python code which should fail in the workflow. We would then learn how to debug or troubleshoot a workflow.

```python
#!/usr/bin/python3
"""
This is a function file
"""

def hello():
    print("hello World")

def new():
  print("This is me")

if __name__ == '__main__':
    hello()
    new()
```

This is what we get from the workflow

![History of workflow](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ilmbgvfz2q9z4gbgp8c8.png)

Now click on the failed workflow to troubleshoot

![Description of failed workflow](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3e2088u0jej2g3lts1n8.png)

The logs rightly shows what needs to be done to get the linter to check correctly.

```
./python_with_error.py:6:1: E302 expected 2 blank lines, found 1
./python_with_error.py:9:1: E302 expected 2 blank lines, found 1
./python_with_error.py:10:3: E111 indentation is not a multiple of 4
./python_with_error.py:12:1: E305 expected 2 blank lines after class or function definition, found 1
```
As shown above, there needs to be blank lines added and indentation issues. This gives you a sense of what exactly didn't allow the code to flow in the pipeline. 

After fixing the issues with pycodestyle, you should have the equivalent of these

![Fixed Issue](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rp21txruxziegrbwky3i.png)


![Checked issue](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8h2xb415eatn6nh17v4o.png)

## Advanced CI Workflow Techniques
There are other useful configurations when building a workflow. Let's look at three of them.

### Variables
Variables store non-sensitive information that can change depending on the environment or the context of the workflow.

To add a new variable, navigate to **Settings > Secrets and Variables > Actions**


![Variables](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pel98kb2mvyxj078v3z7.png)


![Add Variables](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4z0nw5w6dfwv02zhwnhv.png)

**Use Cases**
* Setting environment-specific configurations, such as the target 
  environment (development, staging, production).
* Storing file paths or URLs that might differ between environments.

### Secrets
Secrets store sensitive information securely, such as API keys, passwords, and tokens. They ensure that this information is not exposed in the workflow configuration or logs.

To add a new secret, navigate to **Settings > Secrets and Variables > Actions**


![Secrets](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/puz99s6wh83xe17m1y7k.png)

![Add Secrets](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hfd2hbe74bogx46edqcq.png)

**Use Cases**
* Stores access tokens for third-party services like AWS, Docker Hub, 
  or email services.
* Database passwords or credentials for accessing secure services.

### Webhooks
Webhooks trigger actions in response to specific events, such as sending notifications to external services.

To add a new webhook, navigate to **Settings > Webhooks**

![Webhooks](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rqg46shobva4kuy8symz.png)

![Add Webhook](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/iq5hhh45fgnqmhut9tyw.png)

**Use Cases**
* Sending notifications to Slack, email, or SMS services when a build 
  fails or succeeds.
* Initiating deployments to environments such as staging or production 
  when certain conditions are met.

Now let's add a notification to our workflow such that, a SMS is automatically sent to us when our pipeline fails.

Edit the `yml` file with the following code snippet

```yaml
name: CI Pipeline

on: [push, pull_request]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
    - name: Checkout code
      uses: actions/checkout@v3

    - name: Set up Python
      uses: actions/setup-python@v4
      with:
        python-version: '3.x'

    - name: Install dependencies
      run: |
        pip install requests pycodestyle

    - name: Lint Python files with Pycodestyle
      run: |
        pycodestyle .

    - name: Send SMS Notification
      if: failure()
      run: |
        python - <<EOF

        import requests
        import json

        quicksend_url = "https://uellosend.com/quicksend/"

        data = {
            'api_key': '${{ secrets.SMS_API_KEY }}',
            'sender_id': 'GitHub',
            'message': 'CI Pipeline has failed, check the details',
            'recipient': '${{ secrets.SMS_RECIPIENT }}'
        }

        headers = {'Content-type': 'application/json'}

        response = requests.post(quicksend_url, headers=headers, json=data)

        with open('sms_response.json', 'w') as f:
            json.dump(response.json(), f, indent=4)
        EOF
```

### Explanation of Code
* We check for a failure of the task to be run then we send aSMS to the user.
* We use [UelloSend](https://uellosend.com/index.php) for the SMS service.
* Secrets are configured for the services in the secrets file within the repository.

Below is the SMS that would be sent should the run or job fail

![Image of the error](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/3cu3e5amjqk5977szia2.jpg)

## Helpful links
- [Learn Continuous Integration](https://www.atlassian.com/continuous-delivery/continuous-integration)
- [Learn more about GitHub Actions](https://docs.github.com/en/actions/learn-github-actions)
- [UelloSend API Documentation](https://uellosend.com/developer.php)


## Conclusion
In this article, we explored how GitHub Actions can streamline and enhance your Continuous Integration (CI) workflows. By leveraging GitHub Actions, you can automate various aspects of your development process, ensuring code quality and improving collaboration among team members. 

By implementing these techniques, you can create robust CI workflows that not only automate code checks and tests but also keep you informed about the status of your builds in real-time. This enhances the overall development process, making it more efficient and reliable.

What did you think? Share your thoughts and feedback in the comments below!

Your input helps me deliver insightful and informative content in the future. Let's keep the conversation going!

Connect with me:

* [GitHub](https://github.com/angelotheman)
* [X](https://x.com/_angelotheman)
* [LinkedIn](https://linkedin.com/in/angelotheman)

Happy learning 🚀