Getting started with GitHub Actions
GitHub rolled out GitHub Actions for everyone in November 2019. GitHub Actions are free to use (up to 2000 minutes per month) for public and private repositories and execute a workflow every time a defined event is triggered on GitHub. GitHub provides quite a lot of webhook events that can trigger a workflow.
So let's explore how to automate the steps of our development workflow when working with Laravel and Laravel Nova.
Workflow file
To get started, you need to create the basic YAML configuration file. This file is located in the repository in the .github/workflows
folder.
TL;DR: View the whole workflow file on GitHub.
name: Tests
on: [push]
jobs:
tests:
name: Run tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
The keyword on
defines when the action will be executed. In the example above, we listen for the [push]
event. It is also possible to chain multiple events [push, pull_request]
or restrict the workflow to a specific branch.
The jobs
section can hold multiple steps
. The first step loads an action actions/checkout@v1
provided by GitHub, imported with the keyword uses
.
Workflows run on Linux, macOS, Windows, and even Docker container images. If you're interested in a setup with a custom Docker container image, read the post Using Github Actions to setup CI/CD with Laravel by Luis Dalmolin.
Services
Before adding additional steps to our workflow we want to include a service.
In most cases, Laravel Nova needs a database. For this reason, we're adding the MySQL service to the workflow. My application is still running on MySQL 5.7 so I'm pulling in the official MySQL Docker image for MySQL 5.7 (the image also supports other tags like MySQL 8) that will spin up a MySQL server in the container. Make sure to define the env
variables and the port
as it will not work without these parameters!
services:
mysql:
image: mysql:5.7
env:
MYSQL_DATABASE: database_ci
MYSQL_USER: user
MYSQL_PASSWORD: secret
MYSQL_ROOT_PASSWORD: secretroot
ports:
- 33306:3306
options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
To not confuse the environments I usually create a separate .env
file for the CI environment. Based on the .env.example
file create a new file with the name .env.github
which is holding the MySQL credentials from above.
You can also find the contents of the file on GitHub.
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=33306
DB_DATABASE=database_ci
DB_USERNAME=user
DB_PASSWORD=secret
Verifying the MySQL connection
We're ready to add the first step. To make sure we've set up everything correctly we start with a step that tests if the MySQL connection is working. This step is optional and not related to the remaining workflow but I find it very helpful to make sure the MySQL connection is actually working.
- name: Verify MySQL connection
run: |
mysql --version
sudo apt-get install -y mysql-client
mysql --host 127.0.0.1 --port ${{ job.services.mysql.ports['3306'] }} -uuser -psecret -e "SHOW DATABASES"
If you're having trouble setting up the MySQL connection jump to the troubleshooting section below.
Installing dependencies
Now we're good to go to set up the world of our application. In this step, we're going to download and install all of the required dependencies via Composer. To authenticate Nova in the GitHub CI environment add your NOVA_USERNAME
and NOVA_PASSWORD
to the GitHub Secrets in the repository. The secrets are environment variables that are encrypted and not shared in forked repositories.
You can set your secrets here: GitHub Repository › Settings › Secrets
- name: Install dependencies
run: |
php --version
composer config "http-basic.nova.laravel.com" "${{ secrets.NOVA_USERNAME }}" "${{ secrets.NOVA_PASSWORD }}"
composer install -n --prefer-dist
Booting Laravel
Next, we're going to boot the main Laravel application including Laravel Nova and all other dependencies. Therefore, we copy our CI credentials from the .env.github
file to the .env
file. After generating an encryption key we can run other artisan
commands.
- name: Boot Laravel application
run: |
cp .env.github .env
php artisan key:generate
php artisan --version
Migrating the database
Run the available migrations to initiate the main structure of the database.
Append the --seed
flag in case you'd like to seed the database with default entries e.g. roles or settings. The seed should not be necessary for your unit or feature tests, but may be helpful for other tests.
- name: Migrate database
run: |
mysql --version
php artisan migrate:fresh --seed
Running the build process
To make use of HTTP (feature) tests in our test suite we need to have a fully working application. This requires us to have access to all compiled assets in the CI environment. To compile the assets I'm using yarn
but it works with npm
as well.
- name: Run yarn
run: |
yarn --version
yarn && yarn dev
Running the test suite
Finally, we are ready to run the PHPUnit test suite:
- name: Run tests
run: |
./vendor/bin/phpunit --version
./vendor/bin/phpunit
Be aware of the two different environments:
- the CI environment:
ci
defined in the.env.github
file, copied to the.env
file - the testing environment:
testing
defined in thephpunit.xml
file
Laravel 6 uses an in-memory SQLite database for testing (config on GitHub).
If you'd like to run your tests against a MySQL database, set the DB_CONNECTION
to mysql
and add the name of the database to DB_DATABASE
. Make sure that the database actually exists and is empty.
If you're having trouble setting up the MySQL connection jump to the troubleshooting section below.
Adding security checks
To check if the application uses dependencies with known security vulnerabilities we load and run the open source Symfony Security Checker. I like this step in particular as it adds additional value to the automation workflow.
- name: Run security checks
run: |
test -d security-checker || git clone https://github.com/sensiolabs/security-checker.git
cd security-checker
composer install
php security-checker security:check ../composer.lock
Logs & Caching
We can cache the dependencies from Composer and Yarn. Therefore we use the actions/cache@v1
GitHub Action and a hash of the .lock
-file as an identifier.
Ruben Van Assche covered these steps quite well in his post Getting started with GitHub Actions and Laravel so I'm not going to illuminate these steps anymore. The steps are also included in the workflow file on GitHub.
In case of an application error, you can download the logfiles from GitHub.
Running the complete workflow
FYI: Download the workflow file from GitHub.
After we commit and push the workflow file to our repository on GitHub the action runs (see on: [push]
above). Booting up the CI server including all dependencies and executing all the steps takes about two to three minutes (up to 2000 minutes per month are free which makes the CI service basically free to use).
Watch the workflow in action in the video below:
Troubleshooting
Failed to initialize, mysql service is unhealthy.
There is a problem with the MySQL port. If not defined, the port number is a random number. You can get the port number from the job service. Use the env
key to assign the dynamic MySQL port number to a step manually.
- name: Run tests
run: ./vendor/bin/phpunit -v
env:
DB_PORT: ${{ job.services.mysql.ports['3306'] }}
SQLSTATE[HY000] [2002] php_network_getaddresses: getaddrinfo failed
The MySQL credentials are incorrect. Make sure to define the MYSQL_USER
, MYSQL_PASSWORD
and MYSQL_ROOT_PASSWORD
in your .env
file.
Read this answer on Stack Overflow for an in-depth explanation.
QueryException: SQLSTATE[HY000] [1045] Access denied for user 'user'@'localhost' (using password: YES)
The database does not exist or the database credentials are invalid. Check the .env
file and make sure to clear the config cache after a change:
php artisan config:clear
RuntimeException: No application encryption key has been specified.
This error will pop up if the APP_KEY
is not set. Make sure to generate an APP_KEY
using the php artisan key:generate
command before executing any commands using artisan
.
Where to go from here?
The future of automation is now. Explore the awesome actions repository and use the available actions to create a release, send a notification or run a deployment. Or even better: Create your own actions!
Update: Freek Van der Herten shared and explained the GitHub workflow file for Ignition in his post Using GitHub actions to run the tests of Laravel projects and packages which contains a test matrix (php
, laravel
, dependency-version
and os
) as well as Slack notifications. Make sure to read the post as well!