A Guide to Github Actions and CI/CD Workflows

akashshyam

Akash Shyam

Posted on March 13, 2021

A Guide to Github Actions and CI/CD Workflows

Hey guys πŸ–, Today I'm going to tell you all about github actions and we'll setup a CI/CD workflow

Github Actions

This is tool made by Github that helps us to automate tasks.

Before github actions, we had to push the code, run the tests manually, fix the bugs, test again, fix more bugs.... you know the drill. Github actions help us to automate this workflow. Let's look at CI/CD in detail:

Continous Integration

This involves merging code with existing codebase.
The developers push to a git repo and test suites are run automatically. If there is an error, then the next stage won't start. However, if all goes well, we move on to the next stage(you guessed it.... continuous deployment).

Continous Deployment

This involves sending code to hosting provider.
The pushed code(already tested and hopefully bug free) is pushed to the deployment server which reflects the changes in the live site. For example, after the CI stage, the code is pushed to heroku(hosting service in this example).

Terminology and Concepts

Workflows are automated procedures that live in your GitHub repository. This is the configuration for specifying how the automation works and it’s done with a YAML file.

name: Deploy to Firebase Hosting
on: push
jobs:
  build_and_preview:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - run: npm ci && npm run build
      - uses: FirebaseExtended/action-hosting-deploy@v0
Enter fullscreen mode Exit fullscreen mode

This is a sample of a configuration file. Don't worry, we'll go through everything in detail later.

  • Actions
    Actions are standalone commands like npm run test or npm run build. Actions are the building blocks of a workflow

  • Steps
    Running an action is called a Step. An action is a single step. One step is to install dependencies, the next is to build app, then the last step could be to deploy to hosting provider.

  • Jobs & Runners
    All of these steps create a Job. Jobs execute on a server called a runner. For most cases the runner is hosted on GitHub. You can host your own, but I have no idea how that works, so this blog post only refers to using GitHub hosted runners. When configuring the workflow YAML file you’ll see a spot for Jobs.

  • Steps
    Steps are tasks that can be can be executed by a job.

  • Job
    A job is made up of multiple steps and runs in an instance of the virtual environment. Jobs can run independently of each other or sequential if the current job depends on the previous job to be successful.

That's a lot of terminology... let's get our hands dirty with some code.

First Github Action

Github actions are created in .github/workflows folder in your root directory. If you want, you can follow along with your own project and we'll automate the hell out of it. You're free to call it anything you want. The actions will show up in the actions tab in github.

Each action has 3 compulsory properties:

  • name - name of the action()
  • on - when to run this action(eg. on pushing to repo)
  • jobs - work to be done when action is called(eg run test suite)

YAML/YML

If you don't know about yaml, yaml is basically JSON with brackets. Yaml relies heavily on indentation, make sure you have the right number of spaces/tabs. Let me give you a quick rundown of yaml syntax.

  • Indentation
example: 'lorem-ipsum'
example-object: 
  x: 'y',
  abc: 'pqr'
Enter fullscreen mode Exit fullscreen mode
  • Arrays/Lists
array:
  - a: a
  - b: b
Enter fullscreen mode Exit fullscreen mode

Let's create a new file deployment.yml. For the purpose of this demo, we will be creating an action that deploys to firebase hosting when code is pushed to master branch.

name: learn-github-actions
on: [push]
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - run: echo 'PUSH'
Enter fullscreen mode Exit fullscreen mode

We have added the following code into our deployment.yml file. Let's walk through each line step by step.

  1. name: The name of the workflow as it will appear in the Actions tab of the GitHub repo.

  2. on: The event to listen to(eg push, pull request etc)

  3. jobs: An array of all the jobs

  4. deploy: Name of 1st job, can add more jobs in similar fashion

  5. runs-on: Configures the job to run on an Ubuntu Linux runner of the given version.

  6. steps: Array of steps that run in the deploy job. Each item in this array is an action or a command

To push the changes, run the following commands in your terminal(after setting up remote origin):

git add .
git commit -m "Adding deployment workflow"
git push origin master
Enter fullscreen mode Exit fullscreen mode

After pushing the changes, go to the actions tab of your repo

Screenshot 2021-03-13 at 18.46.47

In the actions tab, if you have done everything correctly, you will find a workflow(It will have a title of the commit message) with a name of learn-github-actions(name of the workflow in yml file). Click on it.

Alt Text

Once you click on the workflow, you should see our yml file name and the deploy job in the workflow.

Screenshot 2021-03-13 at 18.54.28

Now, click on the deploy job. After doing this, you will be able to see the various steps in our job. If you click on one of the steps you can see how it was run in the terminal

Alt Text

Continuous Integration and Testing

Let's implement a workflow that runs tests everytime code is pushed.

name: NodeJS Tests
on: ['push']
jobs:
  test_node:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        node-version: [12.x, 14.x]
    steps:
      - uses: actions/checkout@v2
      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v1
        with:
          node-version: ${{ matrix.node-version }}
      - run: npm install
      - run: npm run build --if-present
      - run: npm run coverage

      - name: Coveralls
        uses: coverallsapp/github-action@master
        env:
          COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }}
          COVERALLS_GIT_BRANCH: ${{ github.ref }}
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}

Enter fullscreen mode Exit fullscreen mode

I've called it Node Tests and it runs on pull requests. I've created a job called test_node which runs on the latest version of ubuntu. Since we need our source code in our VM, we will use the actions/checkout@v2 community action to bring the code into the current working directory. We'll need node to execute our apps so we'll use another community action called actions/setup-node@v1 and specify our node version.

We're ready to start running our own commands. I've started with installing my dependencies using npm ci. Then, I'm running my test suite and building the application to make sure the build compiles properly.

If you have a more complex test suite like cypress, there's probably a community action for this.

Adding Continuous Deployment

Let's implement deployment to firebase hosting on merge and pull requests. The basic concepts of actions are the same, these can be used along with various hosting providers.

Most of these providers will have specific does on how to use github actions along with their product. For example, firebase has it's docs about here. There probably is a community action for every popular hosting service.

After running the following command, agree to the options. In the build command section, add a build command if required. Check out the files generated by firebase for me and we'll walk through the code.

firebase init hosting:github
Enter fullscreen mode Exit fullscreen mode

In firebase-hosting-merge.yml:

name: Deploy to Firebase Hosting on merge
'on':
  push:
    branches:
      - master
jobs:
  build_and_deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - run: npm ci && npm run build
      - uses: FirebaseExtended/action-hosting-deploy@v0
        with:
          repoToken: '${{ secrets.GITHUB_TOKEN }}'
          firebaseServiceAccount: '${{ secrets.auto_generated }}'
          channelId: live
          projectId: auto_generated_variable
        env:
          FIREBASE_CLI_PREVIEWS: auto_generated_variable
Enter fullscreen mode Exit fullscreen mode

In places where I've written auto_generated those are private variables given by firebase which I cannot disclose(otherwise you could access my app).

name is nothing new... we've looked at this before. on is set to push. Here we have added a branches array with the master branch.

Then we have the jobs, in which there's a build_and_deploy job. We've specified runs-on which is set to ubuntu-latest. Again, nothing new here.

The uses keyword tells the job to get v2 of the community action named actions/checkout@v2. This is an action that checks our repository and downloads it into the runner, allowing you to run actions against your code (such as testing tools).

The with provides input arguments that are needed by the action and env provides environmental variables.

The other file firebase-hosting-pull-request has almost identical code. Checkout various community developed actions
here.

Github actions does not stop here. There are a lot more things we can do with actions. Like AI based pull request reviewing, auto code fixing for starters.

Uses for actions

  1. Publish NPM package
    As soon as code is pushed to a branch, we can deploy our package to NPM

  2. Push to master branch
    Run test/deployment on pushing to the master/main branch

  3. Scheduled background jobs
    Used to backup database data(mostly firebase as it is not built-in) to cloud storage buckets

That's all for now, I hope you guys liked this post. Like the post and follow me if you did. Bye πŸ‘‹

πŸ’– πŸ’ͺ πŸ™… 🚩
akashshyam
Akash Shyam

Posted on March 13, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related