Skip to content

Automating the Laravel 8 schema dump using GitHub Actions

Posted by author

The release of Laravel 8 introduced us to many great new features, and I think it is one of the best releases we have ever had! 🎉 😍

However, to me, the schema dump command is far by the most useful for me in some personal projects and for the large projects I work on every day at work. I couldn't wait to delete the migrations from some project where we had hundreds of migration files. In this post I will take you through the challenges faced when I decided to add schema:dump into our release process and how we automated it using GitHub Actions.

This post assumes that you have some working knowledge of GitHub Actions, their yaml workflow files, and that you know what the schema command does. If not please check the Laravel documentation first.

When to trigger the schema dump workflow?

This will probably be different from project to project and how you manage your releases. At work, we use GitHub releases for both non-prod (test and staging) and for production. Our nonprod release tags usually contain the words alpha or beta, for example, 2.4.11-alpha.4 or 2.4.11-beta.12 where the final number is a release candidate identifier. When we create these nonprod releases in github we tick the This is a pre-release checkbox and within a GitHub action you are able to check whether a published release is a pre-release or not.

Shows the GitHub release page pre-release checkbox

I knew from the start that we wanted to run schema:dump whenever we published a release but not when the published release was marked as a pre-release. This made setting up the first part of the schema-dump.yml easy:

1name: "Schema Dump"
2 
3on:
4 release:
5 types: [published]
6 
7jobs:
8 dump:
9 if: github.event.release.prerelease != true
10 # ...

Running the schema dump command

Once I had copied all the boilerplate MySQL, PHP, Composer, Cache and Laravel setup from our existing Run tests action it was time to add the step to actually run the schema:dump command. As you can see in the below snippet it runs the migrate command first to go through all the current migrations.

Then we run the schema:dump command, the --prune argument tells the command to delete any migration files as the generated mysql-schema.sql file will contain an absolute version of your database structure, so the migration files are no longer needed.

1- name: Run migration and schema dump
2 run: |
3 php artisan migrate --force
4 php artisan schema:dump --prune
5 env:
6 DB_HOST: 127.0.0.1
7 DB_PORT: ${{ job.services.mysql.ports[3306] }}

Automatically creating a pull request from the changes

Fortunately, the GitHub Actions has an action for almost anything you need and there is an action created specifically to make creating a PR from local changes really simple. The Create Pull Request action allows you to customise anything you can think of from the PR title, body, commit message, the new branch and much more.

1- name: Create Pull Request
2 uses: peter-evans/create-pull-request@v3
3 with:
4 base: master
5 branch: schema-dump
6 branch-suffix: short-commit-hash
7 commit-message: "Run schema dump to clean up migrations"
8 title: "Run schema dump to clean up migrations"
9 body: "This PR was generated by the schema-dump GitHub Action"

If the schema:dump has made any changes then the above step will create a branch with a name like schema-dump-ae234ab where the suffix is from the commit hash of the changes. The other properties are self-explanatory please see the action for the documentation.

Generated PRs won't run other workflows 💥

I assume, in an attempt to prevent someone from accidentally creating an infinite loop, the above step successfully creates the pull request, but it does not trigger my other workflows that will run tests using the newly generated mysql-schema.sql file.

Assuming those previously using the 'create pull request action' will have encountered this I searched the Issues on that action and came across an answer which linked to some documentation the maintainer has provided on the various options available to make the PR trigger further workflow runs.

Creating a Schema Dumper GitHub App and using it in the workflow

I quickly decided that the best option was to create a GitHub App and install on my organization so that it can be used in multiple projects. Again, the create pull request maintainer has graciously provided documentation on how to use this, and the changes required to make it work within the workflow which means I don't need to go through those steps in this post.

1- name: Generate new token using app
2 uses: tibdex/github-app-token@v1
3 id: generate-token
4 with:
5 app_id: ${{ secrets.SCHEMA_DUMPER_APP_ID }}
6 private_key: ${{ secrets.SCHEMA_DUMPER_APP_PRIVATE_KEY }}
7 
8- name: Create Pull Request
9 uses: peter-evans/create-pull-request@v3
10 with:
11 base: master
12 branch: schema-dump
13 branch-suffix: short-commit-hash
14 commit-message: "Run schema dump to clean up migrations"
15 title: "Run schema dump to clean up migrations"
16 body: "This PR was generated by the schema-dump GitHub Action"
17 token: ${{ steps.generate-token.outputs.token }}

Once this was in place the PRs were being created and the Run tests workflows were running automatically on those PRs. The schema-dumper bot was now the author of the PR which rounds it off nicely.

Shows the generated GitHub pull request

Conclusion

I hope this post has inspired you to automate your schema dumps or perhaps try automating some other parts of your release process. Let me know on Twitter if you find any issues with what I have described or if you want to talk about it. Please find my full schema-dump.yml workflow file below.

1name: "Schema Dump"
2 
3on:
4 release:
5 types: [published]
6 
7jobs:
8 test:
9 
10 if: github.event.release.prerelease != true
11 
12 runs-on: ubuntu-latest
13 
14 name: Schema Dump (${{ github.event.release.tag_name }})
15 
16 services:
17 mysql:
18 image: mysql:8.0
19 env:
20 MYSQL_ALLOW_EMPTY_PASSWORD: yes
21 MYSQL_DATABASE: services_test
22 ports:
23 - 3306
24 options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3
25 
26 steps:
27 - name: Checkout code
28 uses: actions/checkout@v2
29 
30 - name: Cache composer dependencies
31 uses: actions/cache@v1
32 with:
33 path: ~/.composer/cache/files
34 key: dependencies-php-7.4-composer-${{ hashFiles('composer.lock') }}
35 restore-keys: |
36 dependencies-php-7.4-composer-
37 
38 - name: Setup PHP
39 uses: shivammathur/setup-php@v2
40 with:
41 php-version: 7.4
42 extensions: curl, dom, fileinfo, filter, iconv, json, libxml, mbstring, openssl, pcntl, pcre, pdo, simplexml, sqlite, pdo_sqlite, tokenizer, xml, xmlwriter, zip
43 
44 - name: Install PHP dependencies (composer)
45 run: |
46 composer config "http-basic.nova.laravel.com" "${{ secrets.NOVA_USERNAME }}" "${{ secrets.NOVA_PASSWORD }}"
47 composer install --no-interaction --no-suggest
48 
49 - name: Prepare Laravel Application
50 run: cp .env.testing .env
51 
52 - name: Run migration and schema dump
53 run: |
54 php artisan migrate --force
55 php artisan schema:dump --prune
56 env:
57 DB_HOST: 127.0.0.1
58 DB_PORT: ${{ job.services.mysql.ports[3306] }}
59 
60 - name: Generate new token using app
61 uses: tibdex/github-app-token@v1
62 id: generate-token
63 with:
64 app_id: ${{ secrets.SCHEMA_DUMPER_APP_ID }}
65 private_key: ${{ secrets.SCHEMA_DUMPER_APP_PRIVATE_KEY }}
66 
67 - name: Create Pull Request
68 uses: peter-evans/create-pull-request@v3
69 with:
70 base: master
71 branch: schema-dump
72 branch-suffix: short-commit-hash
73 commit-message: "Run schema dump to clean up migrations"
74 title: "Run schema dump to clean up migrations"
75 body: "This PR was generated by the schema-dump GitHub Action"
76 token: ${{ steps.generate-token.outputs.token }}

Syntax highlighting by Torchlight.dev

End of article