Super Mario of Github Actions: The beginning

Petros Zavrakas
REWRITE TECH by diconium
11 min readAug 31, 2023

Time had stopped! I couldn’t hear the sounds around me anymore. It was as if someone had muted life itself. My hands started shaking, and sweat dripped onto the keyboard. I couldn’t take my eyes off the failing action on GitHub’s CI/CD pipeline. The error message terrified me: “Your actions caused Earth to run into a timeout error. Please consider leaving the planet.” Or was it “Timeout error. Please try again in a few minutes”? I’m not sure anymore; the memories are so blurry.

However, I clearly remember wondering if I was going to get fired, if I would ever find a job again, and what I would tell Nana?!

Alright, I might have overreacted a little bit! What happened was that due to server hiccups, the job hit a timeout error. I asked my colleague; he said to re-run the job. I re-ran the job, the job passed, all was good. I didn’t leave the planet.

That was my very first time working on the pipeline where I had to make a small adjustment. But my understanding of CI/CD pipelines at that point was close to none. So, when I saw the pipeline failing and combined it with the fact that the CI/CD topic in my head was still a big, ugly, scary monster, you can imagine why my first reaction was to leave planet Earth!

From that moment until today, I’ve met many developers telling me they had the very similar terrifying feeling about CI/CD in the beginning as well! But after I started working on it and got familiar with it, I realized that the monster isn’t so scary.

If you approach the topic gradually, taking small steps and initially learning the basics, you’ll come to realize that its logic is very easy to follow and understand.

So, let’s..

Get familiar with the monster!

The very first step I wanted to take was to simplify, in my mind, the process my code undergoes in a CI/CD pipeline. I ended up thinking that I have my code base on one side; I’m pushing it through a pipe, where the code gets adjusted and prepared, and it reaches the other end of the pipe, ready to hit production.

And that immediately reminded me of Super Mario! Right?! Super Mario jumps into a green pipe, and he comes out from the other side of the lava pit, ready to get the flag (at least the old-school Super Mario had to lower the flag to finish the level. Old school, like the Nintendo NES era)!

Cool, this approach isn’t scary at all! Yes, but…

What’s happening inside the pipe?

The first thing Super Mario probably (because we’ll never know for sure) would have done after jumping into the pipe is to look at his blueprints (the guy is a plumber; I’m pretty sure he had blueprints) to find out exactly where he should go through.

The same thing we should do with GitHub pipelines. First, we need to look at its blueprints to learn how it works and find our way to set up our own CI/CD pipeline.

And the magic word for that is..

GitHub Actions

GitHub Actions is a CI/CD platform that GitHub provides to us, allowing us to automate our pipelines.

Let’s see how it looks. Open your repository on GitHub, in the tabs menu, you’ll find the “Actions” item.

Go ahead and click it. Don’t worry; nothing will happen. Yet…

GitHub structures pipelines in an organized and efficient manner using the following key components: Workflows, Jobs, and Steps. The language employed by GitHub to define these integral components is YAML. Now, let’s look at these components…

To start, it’s important to note that GitHub requires a specific repository structure. At the root level of the repository, there should be a folder named .github/, containing two subfolders: workflows/ and actions/.

Let’s start with..

Workflows

The initial step to activate the pipeline is triggering a workflow. You can think of workflows as containers that include distinct sets of tasks intended for execution on our codebase. These tasks are referred to as “Jobs,” and workflows can execute one or more of these jobs. Apart from jobs, a workflow can also call other workflows. We’ll get into that in a moment.

First, it’s a good idea to give the workflow a name, which will appear in the “Actions” tab of our GitHub repository.

name: QA

Next, let’s define when this workflow should trigger. For that, we need to define the on property followed by some event.

name: QA
on:

GitHub also provides numerous events to trigger a workflow, but in this article, since we’re sticking to the basics, we’ll only see a few of them, the most common ones.

push

Let’s start with the push event, by configuring this event, the workflow will initiate whenever a push operation is executed on any branch.

on: push

We could also configure the workflow to run only when we push to specific branch(es).

on:
push:
branches:
- 'main'
- 'releases/***'

For instance, with this configuration, the workflow will trigger upon pushing to either the main branch or a branch whose name commences with release/. It’s noteworthy that when specifying particular branches, wildcard characters can also be utilized.

workflow_call

Another frequently used event is workflow_call, designed to trigger the workflow solely when it’s invoked by another workflow. This functionality allows us to create intricate workflow configurations where one workflow can trigger the execution of others.

on: workflow_call

Contained within this primary workflow, the option exists to invoke another workflow, which has been defined within the scope of various jobs.

name: Main

on: push

jobs:
secondary-workflow:
name: The secondary workflow
uses: .github/workflows/secondary.yml

This secondary workflow will be triggered only if it is called.

name: Secondary

on:
workflow_call:

workflow_dispatch

Often, we’ll need to run a workflow manually as needed.

on: workflow_dispatch

This adds a button in the GitHub UI, allowing us to trigger the workflow manually.

Schedule

A workflow can also be triggered by the schedule event, followed by a cron job that triggers the workflow at a specific UTC time using POSIX cron syntax.

on:
schedule:
- cron: '30 5 * * *

For more information about the cron syntax refer to the documentation.

Combination

GitHub Actions also allows us to combine different events.

on:
workflow_dispatch
schedule:
- cron: '30 5 * * *

For example, a workflow could be triggered manually or automatically based on a cron schedule.

Alright, now that we have some ways to trigger our workflows, depending on our needs, let’s see how we can define jobs within a workflow.

Jobs

As we mentioned earlier, within workflows, we can define one or more jobs to run on our code.

Name

Each job should be given a name, which will appear in the GitHub UI when we open a workflow to see the running jobs.

jobs:
lint:
name: Lint

In this example, we defined a job named Lint which we will run later to validate our source code.

Runs-on

We also need to define the type of OS (GitHub-hosted runners) the job should be executed.

runs-on: ubuntu-latestya

You can find a list of available Github-hosted runners.

Needs

In many cases, you’ll need to define the order in which your jobs should be executed. You might want to ensure that a previous job completes successfully before moving on to the next one.

This is done by defining dependencies between jobs using the needs property.

job1:
...
job2:
needs: job1
job3:
needs: [job1, job2]

In the above example, “job2” is scheduled to commence solely upon the successful completion of “job1,” while “job3” is set to run exclusively after both “job1” and “job2” have successfully concluded. As illustrated, the configuration allows for the definition of either a single job or an array of jobs.

Steps

In the steps section, we define what the job should do.

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

- name: Build NPM
uses: ./.github/actions/build-npm

- name: Run linter
run: npm run lint

Each step can be given a name, but it’s not necessary. For instance, in the first step, you can simply define the uses property.

- uses: actions/checkout@v3

The uses property defines a composite action that can be reused throughout your workflow. It can be a custom action you’ve implemented or one provided by GitHub. Actions using the path actions/some-name means the action is provided by GitHub.

In the next steps, you can build on this foundation to perform tasks such as building, testing, and deploying your application.

In this case, we are utilising a GitHub action that was defined by the GitHub team (checkout@v3), which checks out the repository to access the source code.

In this particular step, it will check out the repository, granting us access to our source code.

Moving to the second step, “Build NPM” is a custom composite action we have implemented to facilitate building NPM whenever it is required. More details about composite actions will be discussed shortly.

In the third and final step, as seen, we execute a bash command to initiate linting.

Composite Action

Composite actions are a useful feature of GitHub that offers both reusability and readability.

Reusability
You can define specific tasks you want to execute multiple times in multiple workflows and reuse those composite actions by calling them.

Readability
Composite actions enhance the readability of your workflows. They help keep your pipeline scripts clean and organized, similar to how modular code enhances the readability of a program.

Example
A simplified example of a composite action could be the build of NPM. Let’s see an implementation of such an action.

name: BuildNPM
description: Build npm

runs:
using: composite
steps:
- uses: actions/setup-node@v3

- run: npm config set registry https://registry.npmjs.org
shell: bash

- run: npm ci --ignore-scripts --prefer-offline
shell: bash

As you can see, we start by providing a name and a description of what this action precisely accomplishes. Afterward, we define the properties runs: and using:, which are quite standard when defining a composite action. Directly after that, we outline the steps that we intend this composite action to execute.

First, we set up Node by utilizing the composite action setup-node already provided by GitHub. Then, we execute a bash command to configure the NPM registry, and finally, we install NPM.

Please take note that whenever we wish to run a bash command within a composite action, we need to explicitly specify the shell.

shell: bash

Composite actions need to be stored in a file named action.yml inside the .github/actions directory. For the above example, it could be something like: .github/actions/build-npm/action.yml

Then, when we need to use the action we can call it inside steps as follow

steps:
- name: Build NPM
uses: ./.github/actions/build-npm

Please note that when we define the path to the composite action, there's no need to include the action.yml file in the path. This is because GitHub will automatically search the directory for a file named action.yml by default.

Now that we’ve covered the basics of GitHub Actions, let’s take the leap into the pipelines and become the Super Mario of GitHub pipelines.

The Super Mario of the GitHub pipelines

It’s Monday morning! I’m still waiting for the breakfast mushrooms (supposedly Super Mario’s favorite food is mushrooms) to reach my brain and prod it into action. While I was struggling to remember how I got back home last night, Luigi stormed into my room in a panic.

Luigi: Mario! Oh, I’m glad you’re awake! We have an emergency!

Mario: Hey, do you know how I got back home yesterday? The last thing I remember was you arguing with Peach about something.

L: What? Mario, you’re not listening to me! King Bowser and his Koopa Troopas have invaded the Mushroom Kingdom!
Consider Mushroom Kingdom as the production environment

M: Something tells me, I’m not going to get an answer from you today.

L: Mario, please concentrate! King Bowser cast a spell and transformed all the Toads into inanimate objects. It’s a terrible sight to wake up to!
And this is the bug we found in the production

M: Alright, alright. I’m listening. Just keep your voice down. My head is still buzzing from last night.
Damn you Bowser! Always at the most inconvenient times!
Did anyone analyze the spell?

L: Yes! It’s a common “I’m jealous of your Mushroom Kingdom” spell. Princess Peach found an antidote after the last attack.

We have her recipe
this is the fix of the bug
and all needed ingredients. We just need to prepare it and pour it into our pipes.
Yes, you’re right, this would be the deployment of a hotfix in production.

M: Ok, then follow me to the pipes
Let’s go to GitHub actions in the repository

While we were rushing towards the main pipe to rescue the Mushroom Kingdom’s Toads, a worried feeling tried to warn me that it couldn’t be so easy. King Bowser’s evil plans had always been hard to stop. I chose to push this thought aside, at least for now, and focus on one problem at a time. I needed to rescue the Toads.

M: Alright, here we are.
The first workflow that will be triggered is ci.yml, which, as you can see below, we defined to run on every push.

name: CI

on:
push:

jobs:

L: Mario, should we first check that all the ingredients are good to use?

M: Good idea Luigi!
The first job that we define is to call the cqa workflow which will run the linter on our code and at the end the unit tests.

# cqa.yml workflow contains all tests we want to run before building the app
qa:
name: QA
uses: ./.github/workflows/cqa.yml

L: Everything looks good, Mario. Should we mix the ingredients and prepare the antidote?

M: Yes let’s do that.
Now we are ready to build the application and have it ready for deployment

name: BuildNPM
description: Sets up the project for faster re-use

runs:
using: composite
steps:
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: 14

- run: npm config set registry https://registry.npmjs.org
shell: bash

- name: Install dependencies
run: npm ci --ignore-scripts --prefer-offline
shell: bash

M: Ok Luigi, I think we’re ready to pour the antidote into the pipes! I hope the pure toads will feel better after that.
As you will see in the next workflow, we have set it up to run on each release.

name: CD

on:
release:

jobs:
qa:
name: QA
uses: ./.github/workflows/cqa.yml

First, we run the tests workflow, and finally, the “Deploy to Heroku” job, which is an existing composite action found in the GitHub Marketplace.

deploy-heroku:
name: Deploy to heroku
runs-on: ubuntu-latest
needs: [qa]
steps:
- name: Checkout
uses: actions/checkout@v3

- name: Dpl to heroku
uses: tiagogouvea/github-dpl-action@master
with:
provider: 'heroku'
app: 'your app name'
api-key: 'your-key'

To create a new release, from the main page of the GitHub repository, choose “Releases” and then “Draft a new release.” Type a new tag, which will likely be your release version. Choose the target branch from which the new release should be created. Add your release notes and click the “Publish release” button.

As the Toads woke up from the spell, the Mushroom Kingdom came back to life. Everyone was happy that King Bowser’s evil plan to conquer the Mushroom Kingdom had failed once again.

Mario: So.. How did I get back home yesterday?

Luigi: I was hoping you would answer me the same question.

M: I guess we should ask Peach. Where is she, by the way?

L: I don’t know. I thought you and she got me back home yesterday!

Luigi’s worried gaze brought to the surface the thought that troubled me moments ago. Of course, it wouldn’t have been so easy! It was just a distraction! The realization of Bowser’s evil plan hit me at the same time as terrifying voices echoed from all over the kingdom: “Princess is missing! Princess is missing!”
The pipelines are failing, the pipelines are failing!

A familiar feeling threatened to overwhelm my thoughts… Leave planet Earth… Now!

--

--