Getting started with GitHub Actions
Tags: cicd, github, githubactions, softwaredevelopment
Published: 2024-01-25 10:11:05

In this tutorial, you will learn about GitHub Actions and how to use it to automate your development workflow. 

### What you will accomplish

In this tutorial, you will:
* Learn about GitHub Actions
* Understand workflow, jobs, steps and actions
* Create your first GitHub Action

GitHub Actions is a continuous integration and continuous delivery (CI/CD) platform that allows you to automate your software development workflows by defining and running automated workflows within your GitHub repository. These workflows can handle various tasks from building, testing, packaging, releasing, and even deployment.

You can configure your workflow to run automatically when certain events happen, such as when you push code to a specific branch, open a pull request or schedule it to run at a specific time. When the workflow is triggered, GitHub Actions runs each action in the defined order on a virtual machine called a runner.

### Overview

- **Workflow** - Workflow is customizable sequences of tasks using YAML files. Workflows are defined in `.yml` files in the `.github/workflows` directory of your repository.

- **Action** - An action is a custom application for the GitHub Actions platform that performs a complex but frequently repeated task.

- **Event** - An event is a specific activity that triggers a workflow. For example, an activity that occurs on GitHub, such as opening a pull request or pushing a commit.

- **Jobs** - A job is a group of steps within a workflow that runs on runners (virtual machines).

- **Steps** - A step is an individual task like running commands or actions e.g `actions/checkout@v2`. Each step in a job executes on the same runner and allows direct file sharing.

```yaml
name: CI # name of the workflow

on: [push] #     # watch for pull requests into the main branch


jobs:
  build: # name of the job
    runs-on: ubuntu-latest # runs-on is the type of machine to run the job on - runner
    steps: # steps are the individual tasks that make up a job
      - uses: actions/checkout@v2
      - name: Run a one-line script # name is the name of the step
        run: echo Hello, world!
```

### Triggers

The `on` keyword is used for triggering events within the workflow. It can be triggered by a push, pull request or a schedule.

```yaml
on:
  push:
    branches: [main]
  pull_request:
    branches: [main]
```

#### Triggers

- The above example triggers the workflow when files in the `src/index.js` are changed.

```yaml
on:
  push:
    paths:
      - "src/index.js"
```

- The above example triggers the workflow when files within the `.js` and `.css` extensions are changed.

```yaml
on:
  push:
    paths:
      - "**.js"
      - "**.css"
  pull_request:
    paths:
      - "**.js"
```

- The above example triggers the workflow when files in the `src` directory are changed.

```yaml
on:
  push:
    paths:
      - "src/**"
```

-   This `workflow_dispatch` allows you to run workflow manually from the Actions tab

```yaml
on:
  workflow_dispatch:
```

### Services

Services are external resources that can be used within your workflow to help test and operate your application. Services like `redis` or `postgres` or any of your preferred databases can be used within our workflow. We can also run a service in a container and custom images all within our workflow.

```yaml
services:
  redis:
    image: redis 
    ports:
      - 6379:6379 

  mongodb:
    image: mongo
    ports:
      - 27017:27017

  sqlite:
    image: sqlite
    ports:
      - 3306:3306
```

### Using result from one step in another step

We can use the result from one step in another step using the `id` keyword.

```yaml
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: get previous
        id: get_version
        run: echo ::set-output name=version::1.0.0

      - name: Use the previous version
        run: echo ${{ steps.get_version.outputs.version }} # {{ steps.<step_id>.outputs.<output_name> }}
```

### Jobs

We can have multiple jobs within our workflow and each runs in parallel by default. Using the `needs` keyword, we can run jobs sequentially and each step runs on the same runner.

```yaml
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Get the version
        run: echo "Hello World"

  test:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - name: Get the version
        run: echo "Hello World"
```

### GitHub context

The `github` context is available to you in any workflow or action you create on GitHub. You can use the context to get information about the workflow run, the repository, the event that triggered the workflow like pull request number, etc.

```yml
name: GitHub Actions
run-name: ${{ github.actor }} is testing out GitHub Actions 🚀
on: [push]
jobs:
  Explore-GitHub-Actions:
    runs-on: ubuntu-latest
    steps:
      - run: echo "🎉 The job was automatically triggered by a ${{ github.event_name }} event."
      - run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by GitHub!"
      - run: echo "🔎 The name of your branch is ${{ github.ref }} and your repository is ${{ github.repository }}."
      - name: Check out repository code
        uses: actions/checkout@v4
      - run: echo "💡 The ${{ github.repository }} repository has been cloned to the runner."
      - run: echo "🖥️ The workflow is now ready to test your code on the runner."
      - name: List files in the repository
        run: |
          ls ${{ github.workspace }}
      - run: echo "🍏 This job's status is ${{ job.status }}."

```

#### Useful properties of GitHub Context

- `github.event_name` - The name of the webhook event that triggered the workflow.
- `github.sha` - The commit SHA that triggered the workflow.
- `github.ref` - The branch or tag ref that triggered the workflow.
- `github.repository` - The owner and repository name. For example, `sadiqful/actions`.
- `github.actor` - The name of the person or app that initiated the workflow.
- `github.job` - The name of the job that's currently running.
- `github.run_number` - A unique number for each run of a particular workflow in a repository. This number begins at 1 for the workflow's first run, and increments with each new run. This number does not change if you re-run the workflow run.
- `github.run_id` - A unique number for each run of any workflow in a repository. This number begins at 1 for the workflow's first run, and increments with each new run. This number does not change if you re-run the workflow run.
- `github.workflow` - The name of the workflow.
- `github.action` - The unique identifier (id) of the action.
- `github.event` - The event payload. For example, the issue or pull request object that triggered the workflow run.

### Environment variables

We can set environment variables in the workflow file using the `env` keyword.

```yaml
env:
  DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }}
  DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
```

### Secrets

Sensitive data like API keys, passwords, etc can be stored in the repository settings and can be accessed using the `secrets` context.

```yaml
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Get the version
        run: echo ${{ secrets.SECRET_KEY }}
```

### Matrix

Matrix is used for running a job on multiple versions of a tool. It can also be used to run a job on multiple operating systems.

```yaml
jobs:
  example_matrix:
    strategy:
      matrix:
        os: [ubuntu-22.04, ubuntu-20.04]
        version: [14, 16, 18]
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.version }}
```

### Outputs

We can use the `outputs` keyword to output data from a job. We can use the output from one job in another job using the `needs` keyword.

```yaml
jobs:
  deploy:
    runs-on: ubuntu-latest
    outputs:
      url: ${{ steps.deploy-preview.outputs.url }}
    steps:
      - uses: actions/checkout@v3
      - id: deploy-preview
        run: echo "url=preview_url" >> $GITHUB_OUTPUT

    data:
      runs-on: ubuntu-latest
      needs: deploy
      steps:
        - name: load JSON files
          run: echo ${{ needs.deploy.outputs.url }}
```
### Conclusion

GitHub Actions play a key role in automating significant activities during a Software Development cycle. GitHub Actions allows you to automate your build, test and deployment pipeline by creating workflows that are triggered by events.

### Resources

- [GitHub Actions](https://docs.github.com/en/actions)
- [GitHub Actions: Getting Started](https://docs.github.com/en/actions/learn-github-actions/introduction-to-github-actions)