Github Actions. A simple example for a confident acquaintance


Content

Introduction

1. Workflow files

2. Actions

3. My example

4.Secrets

Conclusion

Introduction

Here I will talk about my setup experience CI/CD with the help GitHub Actions.

This article will help those who want to set up automatic deployment for a personal / school project to their remote server using the free GitHub Actions services. Moreover, this service can be used for free even with a private repository (at the time of this writing).

I focus on those points that for me were not the most obvious, reading quick guide from Github.

It is assumed that you already know how to use Github. By and large, it doesn’t matter what programming language or stack you have: the main thing is to understand how Github Actions work and be able to apply it to any project.

1. Workflow files

Let’s start by creating workflow file that Github uses to run Actions. On the tab Actions on the repository page based on your code Github offers different templates workflow file, start with simple workflow.

Sample "simple workflow"
Template “Simple workflow”

After pressing the button Configure you will see the contents of the file.

Sample basic workflow file simple.yml

After creation name-of-your-wokflow-file.yml file, in the repository you will have a folder with the file .github/workflows/name-of-your-wokflow-file.yml. You can create multiple workflow files.

So, the point is to start processes at certain actions on the branch you choose.

For example, “make a deploy” means we need to start the processes:

  • checking code with linter,

  • running project tests,

  • receiving changes in files in the project folder on a remote server, restarting some project services (containers, overwriting folders / files) on a remote server for the changes to take effect.

And if you write with workflow file, in more detail this sequence will be as follows:

  • Github builds an operating system (no UI)

  • Checks your repository: its presence, git, necessary branches, authorization.

  • Copies your repository to this operating system after a successful checkout

  • Runs code review, tests.

  • Sends the changes you’ve made to your repository to your remote server.

  • Performs actions for your changes to take effect on your site.

Usually code review, test run and deploy divided into separate jobthat run individual runners. A runner is a server that runs one job.

2. Actions

The most interesting thing in the workflow file – actions in line uses. In the simplest example above, this is – actions/checkout@v3. You can see in the source code what does action. But it’s easier to look at the progress page jobafter you run it:

  • copies variables inside the container

  • checks the version gitcreates the folders you need, writes the settings file

  • checks out the repository

  • is authorized

  • copies the repository inside the container

  • goes to branch main

Started processes actions/checkout@v3
Started processes actions/checkout@v3

There are many actionscreated by developers, which can be used by selecting the desired one on Github Marketplace.

For example, in my needs I used D3rHase/ssh-command-action@v0.2.2which runs my console command via ssh on a remote server.

3. My example

My example project is created by the Ruby on Rails generator because examples without toppings are not examples. I chose a template workflow file from those offered by Github and added it to my needs.

My rough list of required actions for CI/CD. In the rubyonrails.yml file, the sequence of processes is as follows:

  1. I checked the code with a linter.

  2. Ran the tests.

  3. Actions on the remote server:

    1. Go to the project folder.

    2. Getting changes from the repository using git-pull.

    3. Recreation docker containers with a project

Your actions may be different. For example, if the project is a browser client application, then you need to generate final files with the command npm run build and copy the files to a specific folder on the remote server, from which the already configured nginx. In this case, it would be suitable for copying garygrossgarten/github-action-scp@v1.0

My example .github/workflows/rubyonrails.yml
# This workflow will install a prebuilt Ruby version, install dependencies, and
# run tests and linters. Then it pulls new features from my repo and
# rebuild containers on remote server through ssh.

name: "Ruby on Rails CI"
on:
  push:
    branches: ["main"]
  pull_request:
    branches: ["main"]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v3
      - name: Install Ruby and gems
        uses: ruby/setup-ruby@ee2113536afb7f793eed4ce60e8d3b26db912da4 # v1.127.0
        with:
          bundler-cache: true
      - name: Lint Ruby files
        run: bundle exec rubocop

  test:
    needs: lint
    runs-on: ubuntu-latest
    services:
      postgres:
        image: postgres:14
        ports:
          - "5432:5432"
        env:
          POSTGRES_DB: rails_test
          POSTGRES_USER: rails
          POSTGRES_PASSWORD: password
    env:
      POSTGRES_DB: rails_test
      POSTGRES_USER: rails
      POSTGRES_PASSWORD: password
      RAILS_ENV: test
      DATABASE_URL: "postgres://rails:password@localhost:5432/rails_test"
    steps:
      - name: Checkout code
        uses: actions/checkout@v3
      - name: Install Ruby and gems
        uses: ruby/setup-ruby@ee2113536afb7f793eed4ce60e8d3b26db912da4 # v1.127.0
        with:
          bundler-cache: true
      - name: Set up database schema
        run: bin/rails db:schema:load
      - name: Run tests
        run: |
          bundle exec rake db:drop db:create db:migrate db:seed;
          bin/rake test;

  deploy:
    needs: test
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    steps:
      - name: Checkout code
        uses: actions/checkout@v3
      - name: Install Ruby and gems
        uses: ruby/setup-ruby@ee2113536afb7f793eed4ce60e8d3b26db912da4 # v1.127.0
        with:
          bundler-cache: true
      - name: Run command on remote server
        uses: D3rHase/ssh-command-action@v0.2.2
        with:
          host: ${{secrets.SSH_HOST}}
          user: ${{secrets.SSH_USER}}
          private_key: ${{secrets.SSH_PRIVATE_KEY}}
          command: |
            cd ${{ secrets.PROJECT_FOLDER }};
            git checkout main;
            git pull;
            docker-compose --file docker-compose.prod.yml down;
            docker-compose --file docker-compose.prod.yml up -d;
            docker system prune --all --force;

Workflow the file is run when a change is made or a Pull Request is created on a branch main. doing 3 jobs with titles lint, test and deploy Each runner runs on Ubuntu, checks the repository with actions/checkout@v3installs Ruby and required libraries with ruby/setup-ruby@ee2113536afb7f793eed4ce60e8d3b26db912da4. Linter checks the code using the library rubocop. For tests I need a database, it is started by a service postgres with test environment variables. This step also checks the database schema, runs seeds and, in fact, the tests themselves. Step deploy I only run on the `main` branch. Here you can see which console commands are being executed on the remote server with action D3rHase/ssh-command-action@v0.2.2. Next, I go to the folder with the project on the remote server, go to the branch mainpull changes from repository, restart docker services, I clean the excess related to docker.

As you understand, my remote server is preconfigured with git, docker, docker-composeGithub is friends with the server using an SSH key.

Runners are executed sequentially, for this the keyword is used needs in body job.

Visually, the runners on Github look pretty and intuitive.

View from the interface to jobs
View from the interface to jobs

You can go into each rectangle and see in more detail what is happening there. If something goes wrong, inside you can read what didn’t work out, you can also write visual dividers in scripts for yourself or output some data, such as a list of files.

Job example at the process setup stage
- name: Run command on remote server
   uses: D3rHase/ssh-command-action@v0.2.2
   with:
      host: ${{secrets.SSH_HOST}}
      user: ${{secrets.SSH_USER}}
      private_key: ${{secrets.SSH_PRIVATE_KEY}}
      command: |
        echo '--- START WORK ON REMOTE SERVER ---';
        cd ${{ secrets.PROJECT_FOLDER }};
        echo '--- LIST OF FILES ---';
        ls -al;
        echo '--- GIT INFORMATION ---'
        git co dev;
        git pull;
        echo '--- DOCKER OPERATIONS ---';
        docker-compose down;
        echo '--- LIST OF DOCKER CONTAINERS AFTER STOPING DOCKER CONTAINERS ---';
        docker ps;
        docker-compose --file docker-compose.prod.yml up -d;
        docker system prune --all --force;
        echo '--- LIST OF DOCKER CONTAINERS AFTER STARTING DOCKER CONTAINERS ---';
        docker ps;

After setting up the CI / CD process, you can remove the excess.

4.Secrets

In the rubyinrails.yml file, you may have noticed the variables that are called from the object secrets. These variables are needed in order to make friends with your remote server with the containers created on Github for the duration of the action. To do this, I took the following steps:

  1. Generated an SSH key on a remote server:

cd ~/.ssh; ssh-keygen -t ed25519 -C "your_email@example.com"

Let’s say I named the created files test and got 2 files – private and public keys.

Public and private ssh keys
Public and private ssh keys
  1. I copied the contents of the private key into a variable in SSH_PRIVATE_KEY tab Settings -> Secrets -> Actions:

  2. Created more variables SSH_HOST and SSH_USER, PROJECT_FOLDER with relevant content.

Secret repository variables for actions
Secret repository variables for actions

Conclusion

Total for the article is shown:

Enjoy programming!

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *