A Guide to Github Actions and CI/CD Workflows
Tags: github, git
Published: 2021-03-13 15:13:38

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.

```yml
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
```

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
```yml
example: 'lorem-ipsum'
example-object: 
  x: 'y',
  abc: 'pqr'
```
* Arrays/Lists
```yml
array:
  - a: a
  - b: b
```


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.


```yml
name: learn-github-actions
on: [push]
jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - run: echo 'PUSH'
```

 
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):

```zsh
git add .
git commit -m "Adding deployment workflow"
git push origin master
```

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

![Screenshot 2021-03-13 at 18.46.47](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ye9xxaamxajt1ojaee8z.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ujanj1tca3ewox6ssr6f.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/oop726ybmvz6srg3obsk.png)

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k5x87yl8wauodru4alcl.png)

###Continuous Integration and Testing
Let's implement a workflow that runs tests everytime code is pushed.

```yml
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 }}

```

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] (https://firebase.google.com/docs/hosting/github-integration). 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.

```zsh
firebase init hosting:github
```

In firebase-hosting-merge.yml:

```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
```

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] (https://github.com/marketplace?type=actions).


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 👋