Workflow Triggering Events and Event Actions
Tags: beginners, tutorial, devops, github
Published: 2023-09-16 14:22:53

## Understanding Repository Events
One of the more useful things we can do with GitHub Actions is to initiate workflows based on specific events. We can configure our workflows to run when a specific activity occurs within the repository, at a predetermined period, or when an event outside of GitHub occurs.

We can, for example, perform a workflow when someone pushes code to a branch, submits a pull request, or creates a new issue. But what happens when an event triggers a workflow? Before we discuss that, we need to brush up on two Git related concepts, the commit SHA, and the Git ref.

#### The commit SHA
The commit SHA is a unique identifier that is associated with each commit in the repository. The commit data, including the current state of the repository, are passed through the SHA-1 hash function to generate a unique 40-character hexadecimal number. This will be the commit SHA. It's a way to keep track of specific changes that were included in that commit.

#### The Git ref
The Git ref, short for reference, is a human-readable name that points to a specific commit in the Git repo. It's essentially an alias for a commit hash, providing a more human-friendly way to reference commits. Commonly is either a Git branch name, such as `master` or `dev`, or a Git tag, such as `v0.1` or `v1.2`.

With that out of the way, let's see how GitHub Actions handles events under the hood.

### The Workflow Trigger Pipeline
First, an event occurs in the repository. These can be various things, like a `push` to the repository, opening or closing a `pull request`, creating an `issue`, etc. Every event is associated with a specific commit SHA and Git ref. This ties the event to the state of the repository at a specific point in time and allows GitHub (and us) to know exactly what version of the codebase the event occurred on.

Next, GitHub will look through the `.github/workflows` directory for any workflow files that are associated with that event. It then runs any workflows it finds there are set up to respond to that type of event. For example, if we push a commit to the `main` branch of our repository, GitHub will look in the `.github/workflows` directory on the `main` branch at the commit we just pushed. It then will run any workflow files it finds there that are set up to run on a `push` event.

Each time a workflow runs, GitHub will automatically set the `GITHUB_SHA` and the `GITHUB_REF` environment variables in the runner machine.

## Diving Deeper: Push, Pull Request and Gollum Events
Now that we have an understanding of how events trigger workflows and how they're processed by GitHub Actions, let's take a closer look at two commonly utilized events: `push` and `pull_request`. We are also going to take a look at the `gollum` event because it is weird and honestly, I really like weird stuff.

You can find a list of all available triggering events [here](https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows).

### The Push Event
The push event triggers a workflow run whenever someone pushes changes to the repository. This means that when you push changes to a branch, or even when you create a new branch or a tag, the event will trigger. 

The push workflow is typically used in branch-level CI/CD actions. For example, when you push code changes to a repository, GitHub Actions can automatically build the code, run the code through a linter to check for guideline inconsistencies and run the related unit tests.

The push event will carry the commit SHA of the **commit that created this event** and the Git ref of the **branch where the push occurred**.

Let's create a new workflow that will run on push. It will run a small custom script that will display some environment variables, such as the name of the machine and the running OS, and also the commit SHA and Git ref carried by the event.

```yaml
name: Environment Variables on Push
on: push
jobs:
    show-info:
        runs-on: ubuntu-latest
        steps:
        - name: Display Environment Info
          run: |
            echo "Runner Machine Name : $(hostname)"
            echo "Operating System: $(uname)"
            echo "Commit SHA: $GITHUB_SHA"
            echo "Git Ref: $GITHUB_REF"
```

Now let's run this workflow. To do so, just push some code on the repository and see what happens.

When I pushed a code change in the master branch, Git created a new commit. The commit SHA is `7fadb0f` and the branch is `master`. This also triggered my `push-workflow`. 

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ek5ojs6e2gdhrjjr2hxl.png)

Upon opening the workflow run and locating the `Display Environment Info` step, you’ll notice that the commit SHA and Git ref match those of the triggering commit.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9e384zpli8201bc5ul1s.png)

Let's now try something else that will trigger this workflow. Let's create a branch. I am going to create a branch using the command line since it is easier to see the steps hidden behind the UI.

To create a branch using the command line, let's run the following commands:
```shell
# Create a new branch named 'a-branch'
git branch a-branch

# Switch to the new branch
git checkout a-branch

# Push the new branch to the remote repository
git push -u origin a-branch
```

The last command, `git push -u origin a-branch` will trigger our workflow, since it is a push command. But, there will be a small difference from our previous run. The Git ref should change, and display `a-branch` since we made the push to a new branch. But, since there is no actual change in the repository's state, the commit SHA should stay the same.

Upon opening the workflow run, you will notice that the commit SHA is the same, and the Git ref now points to the new branch.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/03zo15nepjv85w7a83gj.png)

### The Pull Request Event
The `pull_request` event triggers a workflow run whenever some activity related to a pull request occurs. This includes activities, such as opening, closing, editing and synchronizing pull requests, unlike the `push` event that doesn't have specific activities.

The `pull_request` event, is quite versatile and can be used in many core CI/CD actions. These can include running automated tests, auto-labeling or categorizing pull requests based on criteria and sending notifications to team members when a pull request is opened or merged.

The push event will carry the commit SHA of the **commit that GitHub created to test if the changes in the pull request can be merged into the base branch** and the Git ref of the **reference created by GitHub to keep track of what would happen if a pull request was merged**. You can think of such a reference as a ["future" commit](https://stackoverflow.com/questions/63594658/git-refs-merge-vs-head-in-pull-request). GitHub uses this to determine whether the pull request will merge successfully with no merge conflicts and with all the checks passing.

Let's create a new workflow that will run on the opening of a pull request. It will again run a small custom script that will display some environment variables, such as the name of the machine and the running OS, and also the commit SHA and Git ref carried by the event. By default, this workflow will trigger when a pull request is `opened`, `synchronized` or `reopened`, so we don't need to do anything special with our `on` tag. These are called activity types and we will learn more about them in a moment.

```yaml
name: Environment Variables on PR Opened
on: pull_request
jobs:
    show-info:
        runs-on: ubuntu-latest
        steps:
        - name: Display Environment Info
          run: |
            echo "Runner Machine Name : $(hostname)"
            echo "Operating System: $(uname)"
            echo "Commit SHA: $GITHUB_SHA"
            echo "Git Ref: $GITHUB_REF"
```

I've pushed the YAML file for our workflow in the `a-branch` we created earlier and then created a pull request to the `master` branch. This created a pull request that is currently open.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/icv83qawf8l6awvof6da.png)

Upon opening the workflow run and locating the `Display Environment Info` step, you’ll notice that the commit SHA and Git ref match those of the triggering commit.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ggjp0xk3v46wa0dnv75n.png)

Now you will notice that the commit SHA and the Git Ref values don't match anything in the repository. There is no commit starting with `0288de86` and no branch named `1`.

Remember what we discussed previously. The commit we are viewing here is a special commit, named a *merge commit*.  A merge commit is a special kind of commit that has two or more parent commits, and it represents the result of merging two or more branches. This commit will become available **when and if** the PR is merged successfully, so we can't see it right now, since our PR is still open. 

The Git Ref is a PR merge branch reference. A PR merge branch `refs/pull/:prNumber/merge` is a reference created by GitHub to keep track of what would happen if a pull request was merged. This is our first PR in this repo, so the `:prNumber` is `1`, and the `refs/pull/1/merge` is the branch GitHub created to merge `a-branch` to `master`.

### The Gollum Event
And now it's time for arguably the most weirdly named event in GitHub Actions. The `gollum` event is named after the Gollum creature from The Lord of the Rings. Gollum was obsessed with the One Ring and called it "precious". This is a reference to the Ruby library also named Gollum, that GitHub wikies are based on. The name Gollum was selected by the developers as a joke since Wiki pages are like the precious One Ring that needs to be protected and updated.

The `gollum` event triggers a workflow run whenever someone makes changes to a Wiki page. This means that when you create or update a Wiki page, the event will trigger. 

The `gollum` event is less commonly used compared to other events like `push` or `pull_request`, but it can be valuable for maintaining documentation, and tracking changes to important repository-related information.

The `gollum` event will carry the commit SHA of the **last commit made on the default branch** and the Git ref of the **default branch**. The default branch is the branch that was created when we created the repository on GitHub.

Let's create a new workflow that will run when we mess around the repository wiki. It will again run a small custom script that will display some environment variables, such as the name of the machine and the running OS, and also the commit SHA and Git ref carried by the event. 

```yaml
name: Environment Variables on Gollum
on: gollum
jobs:
    show-info:
        runs-on: ubuntu-latest
        steps:
        - name: Display Environment Info
          run: |
            echo "Runner Machine Name : $(hostname)"
            echo "Operating System: $(uname)"
            echo "Commit SHA: $GITHUB_SHA"
            echo "Git Ref: $GITHUB_REF"
```

Now let's run this workflow. To do so, just make a change on your project's wiki.

Upon opening the workflow run and locating the `Display Environment Info` step, you’ll notice that the commit SHA and Git ref match those of the last commit made on `master` and a reference to `master` respectively.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/6rx1vik452lg16wmrwaw.png)

## Understanding Event Activity Types

Event activity types are a way of specifying what kind of activity occurred for a certain event. Event activity types provide additional context and specificity to GitHub Actions workflows by categorizing the specific action that occurred within a given event trigger. This means that we can filter and respond to events based on specific activities we're interested in, making the workflows more precise and efficient.

We can use activity types to trigger different workflows based on the activity type of the event. For instance, we can run some tests and security auditing when a pull request is opened, and deploy the code when a pull request is closed and merged.

Let's create a workflow that when a pull request is closed it will add a comment "Thank you for contributing!". This workflow might come in handy with Hacktoberfest just around the corner!

```yaml
name: Thank contributors

on:
  pull_request_target:
    types:
      - closed

jobs:
  thank:
    if: github.event.pull_request.merged == true
    runs-on: ubuntu-latest
    steps:
      - name: Comment on the pull request
        uses: peter-evans/create-or-update-comment@v3
        with:
          issue-number: ${{ github.event.pull_request.number }}
          body: |
            Thank you for contributing! 🎉
```

This workflow will run when a pull request is closed. As you can see, the `on` target is `pull_request`, and we define the `types` to be `closed`. This means that the workflow will triggered by a `pull_request` event that has `closed` as its activity type and not just any `pull_request` event.

Next the workflow checks if the pull request is merged. A pull request can close even if the code is not merged in the codebase. Since we consider that someone has contributed if their code is merged with the codebase, then we add this check. 

If the code is merged, the [`create-or-update-comment`](https://github.com/peter-evans/create-or-update-comment) action is used to comment on the pull request. 

I have created a pull request and it is currently open. It is the third PR since I messed up the previous workflow...

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sv38g1au8lmty70nu9ym.png)

Upon clicking Merge pull request, the workflow will run, and then we will see the comment.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4dsrg1srr3uumkt5wew7.png)

After tweaking around, and failing a bunch of workflow runs, I realized that you need to provide read and write permissions to your GitHub actions for it to work, so take that into consideration if you are using it outside of a playground repo.

## Filtering Events
Up to this point, we have seen how to create workflows based on events, such as `push`, `pull_request`, `gollum`, etc., and then react to specific actions in these events, such as created, edited, deleted, etc. But what if you want more fine control? What if you want to react to specific events and actions on specific repositories, branches, or tags? How can you skip or block a workflow from running under certain circumstances?

In this section, we will take a look at how to use different types of filters in GitHub Actions.

### Branch Filters
The `push` and `pull_request` events (along with the `pull_request_target` event), allows us to configure a workflow to run only when specific branches are targeted. These events also allow us to skip the workflow if these events target specific branches.

#### Including Branches
Suppose we are working on a project with multiple branches, including `main` (the production-ready branch), `develop`, (the development branch), various release branches for previous versions of the project (e.g., `release/1.0`, `release/1.1`, `release/1.2`, etc.). We also have various feature branches, for the current release (e.g., `feature/user-auth`, `feature/payment-integration`, etc.). Finally we have several hotfix branches, that we use for fixing issues with previous versions of the project (e.g., `hotfix/1.2/fix-deadlock-in-docker-instances`, etc.).

In this scenario, we want to setup a workflow that automatically runs tests whenever changes are pushed to feature branches but not on every branch push, since this would be unnecessary for most other branches.

Luckily, the `push`, `pull_request` and `pull_request_target` events allow us to filter a workflow based on the branch this event took place. Let's create a GitHub action that will run on pushing code only to the `feature` branches.

```yaml
name: Feature Branch Build Workflow
on:
    push:
      branches:
        - 'feature/**'
jobs:
    build:
        runs-on: ubuntu-latest
        steps:
        - name: Checkout code
          uses: actions/checkout@v2
          
        - name: Setup Node.js
          uses: actions/setup-node@v2
          with:
            node-version: '14'
            
        - name: Install dependencies
          run: npm install
          
        - name: Run tests
          run: npm test
          
        - name: Run linter
          run: npm run linter
```

The `branches` key indicates which branches will trigger the workflow. So this workflow will run whenever a push event occurs on any branch that matches the pattern `feature/**` in the repository.

The `feature/**` is a glob expression that matches any branch name that starts with `feature/` and has any number of characters after it. For example, it will match `feature/new-feature` or `feature/123` but not `master`, `dev` or `feature`.

#### Excluding Branches
Keeping with our previous application, let's say that we want to run test coverage audits on our product, but we don't want to have a test coverage on the hotfix branches, since changes there are captured by the existing tests of the project. One way to do that is to ignore the `release/**` branches using the `branches-ignore` key. 

Lets create a workflow that will get the coverage when someone pushes to a feature branch or opens a pull request to `dev` or `master`.

```yaml
name: Feature Branch Build Workflow
on:
    push:
      branches-ignore:
        - 'hotfix/**'

    pull_request:
      branches-ignore:
        - 'hotfix/**'

jobs:
    build:
        runs-on: ubuntu-latest
        
        steps:
        - name: Checkout code
          uses: actions/checkout@v2

        - name: Setup Node.js
          uses: actions/setup-node@v2
          with:
            node-version: '14'
  
        - name: Install dependencies
          run: npm install

        - name: Run tests
          run: npm test
  
        - name: Upload coverage to Coveralls
          env:
            CI: true
          run: npx nyc report --reporter=text-lcov | npx coveralls
```

The `branches-ognore` key indicates which branches will trigger the workflow. So this workflow will run whenever a push event or a pull request event, occurs on any branch that does not match the pattern `hotfix/**` in the repository.

It is important to note that we can't use both `branch` and `branch-ignore` in the same workflow.
### Tag Filters
The `push` and `pull_request` events (along with the `pull_request_target` event), allows us to configure a workflow to run only when specific tags are targeted. These events also allow us to skip the workflow if these events target specific tags.

The process is similar to the branch filters, so let's update our workflows to run on specific branches or specific tags. So, for including specific tags we can do something like the following:

```yaml
name: Feature Branch Build Workflow
on:
    push:
      branches:
        - 'feature/**'
      tags:
	    - 'v*'
jobs:
    build:
        runs-on: ubuntu-latest
        steps:
        - name: Checkout code
          uses: actions/checkout@v2
          
        - name: Setup Node.js
          uses: actions/setup-node@v2
          with:
            node-version: '14'
            
        - name: Install dependencies
          run: npm install
          
        - name: Run tests
          run: npm test
          
        - name: Run linter
          run: npm run linter
```
In this configuration, this workflow will run on pushes on any `feature/**` branch **OR** on pushes with a tag.

The same principles apply to the `tags-ignore` key:
```yaml
name: Feature Branch Build Workflow
on:
    push:
      branches-ignore:
        - 'hotfix/**'
      tags-ignore:
        - 'beta*'

    pull_request:
      branches-ignore:
        - 'hotfix/**'
      tags-ignore:
        - 'beta*'

jobs:
    build:
        runs-on: ubuntu-latest
        
        steps:
        - name: Checkout code
          uses: actions/checkout@v2

        - name: Setup Node.js
          uses: actions/setup-node@v2
          with:
            node-version: '14'
  
        - name: Install dependencies
          run: npm install

        - name: Run tests
          run: npm test
  
        - name: Upload coverage to Coveralls
          env:
            CI: true
          run: npx nyc report --reporter=text-lcov | npx coveralls
```
In this configuration, this workflow will run on pushes on any branch or tag, except the `hotfix/**` branches **OR** tags that start with `beta`.

As of with the `branches` and `branches-ignore` we can't have `tags` and `tags-ignore` in the same workflow.

## Conclusion
In this post, we have explored what are workflow-triggering events, which are the conditions that initiate a workflow. We have discussed some of the common events, such as `push`, `pull_request`, and `gollum`, and how they can be configured with different options. 

We have also learned what are actions, which are the individual steps that perform the tasks in a workflow. We have seen how to use both built-in and external actions, and how to pass inputs and outputs between them. 

We have created a simple workflow that comments on merged pull requests to thank the contributors. And finally, we briefly discussed filtering workflows on specific branches and tags.

And don’t forget, if you liked this post, please show some love with a heart. ❤️ Have fun, and see you on the next iteration where we are continuing our journey in the inner workings of GitHub Actions.