GitHub Actions Workshop

In this workshop, we will utilize a tool called super-linter, which is a tool provided by GitHub that is a combination of various linters to help validate our source code. We will use the Closet Service repo to create our action and watch it run on our forked repository.

Goals

  • High level understanding of GitHub Actions
  • Hands on implementation of an action
  • Knowledge of creating workflows and some best practices
  • Linting action implemented for closet service to help streamline code reviews

Prerequisites

  • Fork Closet Service

Introduction

GitHub Actions give developers the ability to automate their workflows across issues, pull requests, and more—plus native CI/CD functionality.

GitHub Actions is a CI/CD tool for the GitHub flow. You can use it to integrate and deploy code changes to a third-party cloud application platform as well as test, track, and manage code changes. GitHub Actions also supports third-party CI/CD tools, the container platform Docker, and other automation platforms.

Why having a GitHub-native CI/CD tool is helpful: The most basic answer is simplicity—if you’re already hosting a project on GitHub, you have a built-in CI/CD tool that works right alongside your code.

Table of Contents

  1. Step 1: Workflow Yaml File
  2. Step 2: Naming Workflow
  3. Step 3: Starting Job
  4. Step 4: Load Steps
  5. Step 5: Checking out code
  6. Step 6: Running Linter
  7. Step 7: Adding Env variables
  8. Step 8: Opening your Pull Request
  9. Step 9: Watching Jobs in GitHub UI
  10. Step 10: Add Badge to repo
  11. Recap
  12. Secuirty Considerations

Step 1: Workflow Yaml

In your repository you should have a .github/workflows. In that directory, create lint-code-base.yml: .github/workflows/lint-code-base.yml

Screen Shot 2022-08-09 at 1 54 57 PM

Best Practices

  • A workflow is a configurable automated process made up of one or more Jobs; this is represented as a single YAML file and is triggered by one or more Git Events
  • Set the Workflow filename to the same as the Workflow name
  • The first 3 lines within the Workflow file should be commented lines with
    • A concise description outlining what the Workflow is running and when (trigger + branch)
    • The Author or List of Collaborators for this Workflow
    • The Current Version of the Workflow
    Screen Shot 2022-08-09 at 2 04 41 PM

Step 2: Naming Workflow

Create the name of the Workflow in lint-code-base.yml by defining the name: key and set it to lint-code-base: name: lint-code-base

Screen Shot 2022-08-11 at 12 16 40 PM

Best Practices

  • For Workflow name use kebab-case
    • this should be a unique name across all Workflows for a particular repository
    • the name should relate to the trigger event and the branch, such as ‘push-feature-branch’
    • as stated above this will also be the Workflow file basename

Step 3: Starting Job

A Job is event based meaning it is triggered on some Git event, such as ‘pull request opened', or ‘push’; this may or may not be further defined to one or more specific branches

Tell GitHub how to start the Job in lint-code-base.yml by defining the on block which follows the name key

Screen Shot 2022-08-09 at 2 12 15 PM

Best Practices

  • There should be only one Action Workflow per Git Event + Branch type, however there are no issues with having multiple branches / multiple Git events in a single workflow

Step 4: Load Steps

A Job is a collection of sequential Steps to run that should perform a discrete piece of work in the CICD pipeline (e.g. Lint, Build, Test, Deploy, etc.)

Create a Job using the Jobs key in lint-code-base.yml following the on event defined in step 3:

Screen Shot 2022-08-09 at 2 26 00 PM

Best Practices

  • Each Job should be related to a single discrete piece of work in the CICD pipeline; for example, in a Workflow called ‘pull-request-open-on-main’, there may be the following Jobs:
    • build-for-staging
    • deploy-to-staging
    • integration-tests
    • performance-tests
  • For Job names use kebab-case
    • Job names need not be unique across all Workflows, but must be unique within the same Workflow

Step 5: Checking out code

In this next part, we will define the Steps that belong to the Job. Usually the first step is to checkout the code from the repo. A step is an individual task within a Job; this may be a Public Action, a Custom Action, or a Run Step

Steps is the next key down from runs-on and is a list usually with the following keys (among others): name, uses, with

In lint-code-base.yml under Jobs -> Steps add the following to checkout the codebase:

Screen Shot 2022-08-09 at 2 45 36 PM

NOTE: step ids are used to reference the step in contexts if needed

Best Pracitces

  • For Step Names you can have spaces but keep it simple and avoid the use of non-alpha characters
  • For Step Ids use kebab-case

Step 6: Running Linter

The second "Step" in our Steps section is to actually run the linter. Under Steps in lint-code-base.yml add a new list following the code checkout step as seen here:

Screen Shot 2022-08-09 at 2 48 53 PM

NOTE: There is no need to set the GitHub Secret as it is automatically set by GitHub, it only needs to be passed to the action. If you pass the Environment variable GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} in your workflow, then the GitHub Super-Linter will mark the status of each individual linter run in the Checks section of a pull request. Without this you will only see the overall status of the full run.

Best Practices

  • Same as step 5

Step 7: Adding Env variables

Env vars can be scoped to the Workflow level, Job level and Step level. Because super linter comes with a myriad of programming languages, we want to set java to true at the step level. We also want to exclude generated files, files ignored by git and scope down linting to the src file. Super linter provides a number of env vars for this step so please check documentation for reference.

Add the following env vars to the env block of the step 6:

Screen Shot 2022-08-09 at 3 15 42 PM

Note: All the VALIDATE_[LANGUAGE] variables behave in a very specific way:

  • If none of them are passed, then they all default to true.
  • If any one of the variables are set to true, we default to leaving any unset variable to false (only validate those languages).
  • If any one of the variables are set to false, we default to leaving any unset variable to true (only exclude those languages).
  • If there are VALIDATE_[LANGUAGE] variables set to both true and false. It will fail.

Best Practices

  • Again, Env vars can be scoped to the Workflow level, Job level and Step level
  • Use environment variables within the narrowest scope possible
  • Be aware that if you create new environment variables and append them to the $GitHub_ENV variable then they will be available to all subsequent Steps within that same Job
  • For Environment Variables use SNAKE_CASE (All Caps) unless a particular Action requires otherwise (environment variables are generally case-sensitive)

Step 8: Opening your Pull Request

For this GitHub action to run, it will need to go through a pr process. Open a pr and you should see the new "check" running at the bottom of the pr. Then, head to the next step to see the linter run in the workflow!

Note: We configured this job in step 3 to trigger on a pr and a merge (of non main branches).

Step 9: Watching jobs in GitHub UI

There are a few ways to view the workflow running. Either on the pr, under the "Checks" section or you can click on the actions tab at the top of the repo and find your workflow running there

Click the "Actions" tab at the top of the repo Screen Shot 2022-08-10 at 2 09 21 PM

Click the "lint code base" tab under "Workflows" Screen Shot 2022-08-10 at 2 13 24 PM

Click into the workflow at the top

Step 10: Add Badge to repo

Click Actions at the top, then pick the workflow you want to add a badge for. Click on the three dots to the right of the search bar at the top and click "create status badge"

Screen Shot 2022-08-10 at 2 16 00 PM

You will see a modal similar to this:

Screen Shot 2022-08-11 at 12 05 40 PM

Choose the branch you want to display the badge for (usually main or master) and leave Event set to Default.

Copy the Markdown provided and paste it at the top of your readme

For more info on badges in repos please see this documentation

Recap

In this workshop we created a GitHub action using super linter. We created the Workflow, defined the Job and Steps to do durning that Job. You can see that in a few lines of yaml you can begin to build an entire CI process for your project. You can even do deployments right from GitHub Actions and there is an entire Marketplace with predefined actions all ready to use for your project!

Keep an eye on the best practices documentation found here

Security Considerations

Where does the Job actually run?

GitHub Action Jobs by default run on Virtual Machines managed by GitHub themselves. These are referred to as GitHub-hosted Runners.

The runners are located within GitHub's own Microsoft Azure account and can access any resource that is public facing. For those resources that are not publicly accessible, due to security policies or the like, then there is the option to use a 'self-hosted' runner.
These are virtual machines that are built, hosted and maintained by your own company, and are generally located within the environment / infrastructure that they need to operate within.

For example, you may have a 'build & deliver' Job that updates a Kubernetes Cluster inside your own Data Centre.
Using a GitHub-hosted runner would require the networking team to open up the Kubernetes Cluster to the public Internet, something that would violate the company security policies.
So instead you would build a VM with the requisite software and tools, host this VM from within the Data Centre, and then using the Agent software (the software used by the runners to talk back to the GitHub Services), join the VM to the relevant repository that you would like to use that runner on.
If desired, you can instead register the VM at the Organization level with GitHub Enterprise.

Links of interest:

  • Runner Images: this repository has the scripts that are used by GitHub to create their own runners, and with minimal effort you can use these scripts yourself to create your 'self-hosted' runner VMs
  • GitHub-hosted Runners information

Secrets inside the Workflow

Just like you wouldn't hardcode sensitive information inside your application code, neither should you hardcode sensitive information inside your Workflows.
One solution is to use GitHub's inbuilt mechanism to securely inject secrets directly into your Workspaces.
You only need to create the Secret, either at the Organization level or at the Repository level, then reference the secret via an automatically created environment variable, which the Workflow will have access to.
This secret environment variable is masked in all logs produced but it is available to the Steps / Actions you choose to expose the Secret to, so you do need to be careful as to how you pass your secret on.

Using the GitHub Secret is a convenient way to inject Secrets into your workflow.
Another solution you can use is to access other Secret Services, such as Hashicorp's Vault service.
The Vault Service even has its own Action available in the Marketplace which minimizes the work required.

Links of interest: