@tlhunter



GitHub Actions for Node.js Apps

Thomas Hunter II


Based on Distributed Systems with Node.js:
bit.ly/34SHToF

An Overview of GitHub Actions

What are GitHub Actions?

  • A way to run Continuous Integration tasks
  • Super convenient if your code is on GitHub
  • Allocate a VM and run code when triggered
  • 2,000 mins for Free, 3,000 mins for Pro, etc

Continuous Integration as Code

  • GitHub Actions are defined using YAML files
  • They live at .github/workflows/*.yml
  • Changes are committed just like your code
  • Not too different from Travis CI / Circle CI
  • No need to create a new account, auth, configure

Composability

  • Workflows > Jobs > Steps
  • Jobs can have dependencies and run in parallel
  • Steps can reference external GitHub repos
    • uses: actions/setup-node@v2.1.4
    • github.com/actions/setup-node
  • Secrets are values defined in project settings
  • Create Actions for common organization patterns

Example Workflow: Pull Request

Workflow Results are Contextual

Pull Request Boilerplate

.github/workflows/pr-lint-test.yml

name: Linter and Acceptance Tests
on:
  pull_request:
    branches:
      - main
  workflow_dispatch: # Allow manual execution
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Check Out Repo 
        uses: actions/checkout@v2

Steps: Prepare Node.js, Install Dev Deps

      - name: Set up Node.js
        uses: actions/setup-node@v2.1.4
        with:
          node-version: 14.15.3
        
      - name: Install Dependencies
        run: npm install

Step: Run a Linter

      - name: Run Linter
        run: npm run lint

Steps: Build Docker, Run Tests

  - name: Build and Start Docker Containers
    run: docker-compose -f docker-compose.yml up -d

  - name: Run Acceptance Tests
    run: TARGET=http://localhost:1337 npm test

Full Workflow Output

Example Workflow: Merge Main

Branch Merge Boilerplate

.github/workflows/main-deploy.yml

name: Deploy to Production
on:
  push:
    branches:
      - main
  workflow_dispatch: # Allow manual execution
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Check Out Repo 
        uses: actions/checkout@v2

Steps: Build and Push Docker Image

      - name: Set up Docker Builder
        uses: docker/setup-buildx-action@v1

      - name: Build and Push to Container Registry
        uses: docker/build-push-action@v2
        with:
          context: .
          push: true
          tags: |
            registry.foo.com/x/y:latest
            registry.foo.com/x/y:sha-${{github.sha}}

Deployment Part 1

  deploy: # Job
    needs: build
    runs-on: ubuntu-latest
    steps:
      - name: Deploy app to VPS
        uses: appleboy/ssh-action@v0.1.4
        with:
          host: ${{secrets.SSH_HOST}}
          username: ${{secrets.SSH_USER}}
          key: ${{secrets.SSH_KEY}}

Deployment Part 2

          script: |
            docker pull registry.foo.com/x/y:latest
            docker stop my-app || true
            docker rm my-app || true
            docker run -d \
              -e SOMEVAL=${{secrets.SOMEVAL}} \
              --name my-app \
              registry.foo.com/x/y:latest \
              node /srv/app.js

Workflow Dispatch Menu

Anatomy of an Action

actions/hello-world-javascript-action

Hello World Action: action.yml

name: 'Hello World'
description: 'Greet someone and record the time'
inputs:
  whom: # id of input
    description: 'Who to greet'
    required: false
    default: 'World'
outputs:
  time: # id of output
    description: 'The time we greeted you'
runs:
  using: 'node12'
  main: 'index.js'
          

Hello World Action: index.js

const core = require('@actions/core');
const github = require('@actions/github');

try {
  const nameToGreet = core.getInput('whom');
  console.log(`Hello ${nameToGreet}!`);

  const time = new Date().toISOString();
  core.setOutput('time', time);

  void github.context.payload; // Useful Data
} catch (error) {
  core.setFailed(error.message);
}

Hello World Action: Usage

jobs:
  hello_world:
    steps:
      - name: Hello World
        uses: actions/hello-world-javascript-action
        id: hello
        with:
          whom: 'Audience'

      - name: Echo Outputs
        run: echo ${{steps.hello.outputs.time}}

GitHub Actions for Node.js Apps