GitHub Actions has quickly become one of the most popular Continuous Integration (CI) and Continuous Delivery (CD) service platforms. It provides many great features for automating your software development workflow as well as great integrations with GitHub itself. GitHub has long been the largest SCM provider available for many reasons. They have many great features that make the software development process easier, and more robust, tons of great integrations with other tools and services, as well as having a very good uptime record. Now, GitHub has also added the ability to control your CI/CD processes directly within GitHub itself without having to rely on another service or constantly swap screens between the multiple services. In this post, we will go over how to improve your DevOps workflow with GitHub Actions!
Current State of CI/CD Platforms
CI/CD services rely on adding configuration files to your project repository. These files are config files that will tell the CI/CD service exactly what you want it to do and when to do those actions. Actions can be configured to trigger upon every commit that is pushed to the repo, only when merge requests are merged into a certain branch, as well as many other options. The reason we put these config files in the repo itself is for a few reasons.
- You can keep track of all changes to the configuration files. So in the case any errors or issues arise after making changes, you can revert to the previous configuration easily
- You can easily copy a configuration file to another project repository and make small changes if the project is similar enough to the original project. This cuts down on having to manually go through a UI form and set the same values over and over which is time-consuming and error-prone
Now, let’s see how we can improve our DevOps workflow with GitHub Actions!
GitHub Actions
GitHub Actions is no different in that it requires a specific directory structure to be in place for the different parts GitHub Actions provides. The different sections of GitHub Actions are as follows:
- Workflows are under
.github/workflows/
- Actions are under
.github/actions/
Workflows are the total combined steps you wish to perform upon a certain action being taken on the repo (commit push, pull request merged, etc). Actions are more specific steps for a particular piece of the workflow that is complex enough and used in multiple places so that it is better to be separated into their own action that can be referenced in multiple workflow files instead of repeating the same steps in multiple places. This cuts down on the problem where when something changes, you may miss updating certain places which can cause issues in any subsequent builds.
Language Options
GitHub Actions supports a multitude of language options for builds. The list of all languages supported by GitHub Actions can be found here which includes almost all (if not all) of the most popular languages being used today.
Setting Up GitHub Actions
We will start off with the customary “Hello World!” example. First, create a directory in the root of your GitHub repo called .github/workflows
. Inside that directory, create a file named example.yml
. Inside that file, add the following:
name: example
# Run this workflow every time a new commit pushed to your repository
on: push
jobs:
# Set the job key. The key is displayed as the job name
# when a job name is not provided
test:
# Name the Job
name: Run Example
# Set the type of machine to run on
runs-on: ubuntu-latest
steps:
# Checks out a copy of your repository on the ubuntu-latest machine
- name: Checkout code
uses: actions/checkout@v2
- name: Say 'Hello World'
run: 'echo "Hello World!"'
Let’s step through this line by line:
- Line 1: This is just the name of the workflow. You can put anything you want here. Just make it something that makes sense for what this workflow does
- Line 4:
on: push
tells GitHub Actions that this workflow is to be run anytime a commit is pushed to any branch in this repository. You can customize this to set it to only run on for certain branches etc. See additional options here. - Line 6: jobs are groupings of steps that are performed in sequence. Jobs run in parallel by default, but can be configured to be run sequentially
- Lines 8 and 10: Set the name and what OS to run the steps on for the test job.
- Line 12: steps is the grouping of individual actions that are run on the specified runner. You can use shell commands, external scripts, or predefined/shard GitHub Actions for steps
- Lines 14-17: Lines 14-15 are specifying a GitHub Action to use to check out the code for the repo onto the system running our workflow. We provide a name for it so it shows us as an understandable step in our workflow in the GitHub Actions UI. When specifying a GitHub Action, we use the term
uses:
instead ofrun:
which is used for shell commands as seen in lines 16-17
Testing Out GitHub Actions
Now, add these files to a new commit and push them to your repo on GitHub. You may not see a run of these the first time you push since the configuration wasn’t present when you pushed this commit. So try adding a simple change to a file such as a README.md and push that commit to your repo.
Now go to your repo in GitHub and click on the Actions tab which should be beside the Pull Requests tab. Click on the name of the workflow you provided in your configuration file (you can also just leave it on All workflows for now if you wish). You should see something similar to the below screen.
Click on the top workflow run entry in your list. You should now see a screen similar to this.
Now click on Run Example in the grey area to open up the actual steps performed. You should now see a screen similar to this
If you click on the Say ‘Hello World’ entry, you should see an output of Hello World to the console. Congrats! You have now successfully configured your repository to use GitHub Actions!
You can check out this example here.
Improving What We Already Have
Let’s improve this a little bit. Let’s say you want to add the “Hello World!” step to multiple workflows. Awesome, we can just copy that step name and command over to all other workflow files, right? Well, we could… But what if we instead wanted each workflow to say “Good [Morning|Afternoon|Evening] World!”? We would have to go and change every workflow file that we had copied that step over to.
A better way would be to create a custom GitHub Action under .github/actions/
and then reference that action in all of our workflow files. That way, when we needed to update what we output in each workflow, we just update it in a single place and all workflow files are automatically updated! That’s much better don’t you think?!
Ok. So let’s create a new file .github/actions/greeting/action.yml
. In that file, add the following:
name: "Greeting"
description: "Outputs a greeting in workflow output"
outputs:
greeting: # id of output
description: "Final Greeting"
runs:
using: 'node12'
main: 'greeting.js'
Now, create a new file named greeting.js in the same directory as the action.yml
file above. Add the following to the file:
const core = require('@actions/core');
const github = require('@actions/github');
try {
let timeOfDay = null;
const date = new Date();
if (date.getHours() <= 11) {
timeOfDay = 'Morning';
} else if (date.getHours() > 11 && date.getHours() <= 17) {
timeOfDay = 'Afternoon';
} else {
timeOfDay = 'Evening';
}
console.log(`Good ${timeOfDay} World!`);
core.setOutput("greeting", `Good ${timeOfDay} World!`);
} catch (error) {
core.setFailed(error.message);
}
Update the workflow file under .github/workflows/
and make it match the following:
name: example
# Run this workflow every time a new commit pushed to your repository
on: push
jobs:
# Set the job key. The key is displayed as the job name
# when a job name is not provided
test:
# Name the Job
name: Run Example
# Set the type of machine to run on
runs-on: ubuntu-latest
steps:
# Checks out a copy of your repository on the ubuntu-latest machine
- name: Checkout code
uses: actions/checkout@v2
- name: Install dependencies for custom actions
run: npm i @actions/core @actions/github
- name: Display Greeting
uses: ./.github/actions/greeting
Now when your workflow runs, it will print a greeting based on what time of day it is! You can find this example configuration here.
Conclusion
That’s it! You have now created a customized GitHub Action that you used in a custom GitHub Action Workflow! You are now ready to improve your DevOps workflow with GitHub Actions! For more information on GitHub Actions and all the other options available as well as some security tips for Actions, check out GitHub Actions docs.