Automate all the things with CI/CD in GitHub Actions Rob Allen, April 2024
A presentation at PHPSW, April 2024 in April 2024 in Bristol, UK by Rob Allen
 
                Automate all the things with CI/CD in GitHub Actions Rob Allen, April 2024
 
                How do we test and release software? Rob Allen | social.akrabat.com/rob
 
                Workflow to accept a code change 1. 2. 3. 4. 5. 6. 7. Checkout the source code Install dependencies Compile (or create container) Run code style checks Run tests Send artifacts (logs, test output, etc.) to dev for debugging Tell dev that it worked (or failed) Rob Allen | social.akrabat.com/rob
 
                Workflow to release a new version 1. 2. 3. 4. 5. 6. Checkout the source code Compile (or create container) Upload container to registry (exe to Release) Deploy to container orchestration platform Publish release Notify Slack Rob Allen | social.akrabat.com/rob
 
                We never get this right every time! Rob Allen | social.akrabat.com/rob
 
                Humans are bad at repetitive tasks Rob Allen | social.akrabat.com/rob
 
                Humans are bad at repetitive tasks That’s why we invented computers Rob Allen | social.akrabat.com/rob
 
                Tests ensure our software works CI ensures that we run them CD releases it reliably Rob Allen | social.akrabat.com/rob
 
                Our repository is the centre of our development world Rob Allen | social.akrabat.com/rob
 
                GitHub Actions runs scripts when an event happens Rob Allen | social.akrabat.com/rob
 
                YAML all the way down! sorry! Rob Allen | social.akrabat.com/rob
 
                .github/workflows/ci.yml name: CI on: [push, pull_request] jobs: qa: name: QA checks runs-on: ubuntu-latest steps: - name: “Say Hello” run: echo “Hello World” - name: “Say Goodbye” run: echo “All done” Rob Allen | social.akrabat.com/rob
 
                .github/workflows/ci.yml name: CI on: [push, pull_request] jobs: qa: name: QA checks runs-on: ubuntu-latest steps: - name: “Say Hello” run: echo “Hello World” - name: “Say Goodbye” run: echo “All done” Rob Allen | social.akrabat.com/rob
 
                .github/workflows/ci.yml name: CI on: [push, pull_request] jobs: qa: name: QA checks runs-on: ubuntu-latest steps: - name: “Say Hello” run: echo “Hello World” - name: “Say Goodbye” run: echo “All done” Rob Allen | social.akrabat.com/rob
 
                Events Rob Allen | social.akrabat.com/rob
 
                .github/workflows/ci.yml name: CI on: [push, pull_request] jobs: qa: name: QA checks runs-on: ubuntu-latest steps: - name: “Say Hello” run: echo “Hello World” - name: “Say Goodbye” run: echo “All done” Rob Allen | social.akrabat.com/rob
 
                .github/workflows/ci.yml name: CI on: [push, pull_request] jobs: qa: name: QA checks runs-on: ubuntu-latest steps: - name: “Say Hello” run: echo “Hello World” - name: “Say Goodbye” run: echo “All done” Rob Allen | social.akrabat.com/rob
 
                Success Rob Allen | social.akrabat.com/rob
 
                Failure Rob Allen | social.akrabat.com/rob
 
                PHP quality checks Rob Allen | social.akrabat.com/rob
 
                Set up the pipeline name: PHP Checks on: [pull_request] jobs: php-checks: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 - name: Create .env file run: cp .env.ci .env Rob Allen | social.akrabat.com/rob
 
                Set up the pipeline name: PHP Checks on: [pull_request] jobs: php-checks: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 - name: Create .env file run: cp .env.ci .env Rob Allen | social.akrabat.com/rob
 
                Set up the pipeline name: PHP Checks on: [pull_request] jobs: php-checks: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v3 - name: Create .env file run: cp .env.ci .env Rob Allen | social.akrabat.com/rob
 
                Grab PHP - name: Install PHP uses: “shivammathur/setup-php@v2” with: coverage: “pcov” php-version: “8.3.4” tools: composer:v2, cs2pr Rob Allen | social.akrabat.com/rob
 
                Dependencies - name: Run composer run: composer install —prefer-dist —no-progress —no-ansi —no-interaction - name: Install npm run: npm install Rob Allen | social.akrabat.com/rob
 
                Code quality - name: Check code style run: vendor/bin/phpcs -q —report=checkstyle | cs2pr Rob Allen | social.akrabat.com/rob
 
                Code quality - name: Check code style run: vendor/bin/phpcs -q —report=checkstyle | cs2pr - name: Run static analysis checks run: vendor/bin/phpstan analyse Rob Allen | social.akrabat.com/rob
 
                Code quality - name: Check code style run: vendor/bin/phpcs -q —report=checkstyle | cs2pr - name: Run static analysis checks run: vendor/bin/phpstan analyse - name: Run unit tests run: vendor/bin/phpunit -c phpunit-ci.xml —testsuite=unit Rob Allen | social.akrabat.com/rob
 
                Other checks - name: Check licenses of PHP dependencies # (see akrabat.com/check-licenses-of-composer-dependencies) run: php bin/check-licenses.php Rob Allen | social.akrabat.com/rob
 
                Other checks - name: Check licenses of PHP dependencies # (see akrabat.com/check-licenses-of-composer-dependencies) run: php bin/check-licenses.php - name: Check we can cache routes run: php bash -c “php artisan route:cache && php artisan route:clear” Rob Allen | social.akrabat.com/rob
 
                Other checks - name: Check licenses of PHP dependencies # (see akrabat.com/check-licenses-of-composer-dependencies) run: php bin/check-licenses.php - name: Check we can cache routes run: php bash -c “php artisan route:cache && php artisan route:clear” - name: Check tailwind-build has been run. run: npm run tailwind-build && [ -z “$(git status —porcelain)” ] Rob Allen | social.akrabat.com/rob
 
                Use Docker? Run in Docker! - name: Docker Compose Pull run: docker compose pull Rob Allen | social.akrabat.com/rob
 
                Use Docker? Run in Docker! - name: Docker Compose Pull run: docker compose pull # Cache Docker layers - uses: jpribyl/action-docker-layer-caching@v0.1.1 continue-on-error: true Rob Allen | social.akrabat.com/rob
 
                Use Docker? Run in Docker! - name: Docker Compose Pull run: docker compose pull # Cache Docker layers - uses: jpribyl/action-docker-layer-caching@v0.1.1 continue-on-error: true - name: Start the containers run: docker compose up —build -d Rob Allen | social.akrabat.com/rob
 
                Tests that need the database - name: Ensure MySQL is available # (uses raphaelahrens/wait-for-it) run: docker-compose exec -T php ./wait-for-it -t 10 db:3306 Rob Allen | social.akrabat.com/rob
 
                Tests that need the database - name: Ensure MySQL is available # (uses raphaelahrens/wait-for-it) run: docker-compose exec -T php ./wait-for-it -t 10 db:3306 - name: Run migrations run: docker-compose exec -T php bash -c “php artisan migrate:fresh —seed” Rob Allen | social.akrabat.com/rob
 
                Tests that need the database - name: Ensure MySQL is available # (uses raphaelahrens/wait-for-it) run: docker-compose exec -T php ./wait-for-it -t 10 db:3306 - name: Run migrations run: docker-compose exec -T php bash -c “php artisan migrate:fresh —seed” - name: Execute tests run: docker-compose exec -T vendor/bin/phpunit -c phpunit-ci.xml —testsuite=integration Rob Allen | social.akrabat.com/rob
 
                Upload assets - name: Upload test output uses: actions/upload-artifact@v2 if: failure() with: name: failed-tests path: tests/output retention-days: 8 Rob Allen | social.akrabat.com/rob
 
                Upload assets - name: Upload test output uses: actions/upload-artifact@v4 if: failure() with: name: failed-tests path: tests/output retention-days: 8 Rob Allen | social.akrabat.com/rob
 
                Upload assets - name: Upload test output uses: actions/upload-artifact@v4 if: failure() with: name: failed-tests path: tests/output retention-days: 8 Rob Allen | social.akrabat.com/rob
 
                Everything we run in CI we also run locally Rob Allen | social.akrabat.com/rob
 
                Tag and Release Rob Allen | social.akrabat.com/rob
 
                When a milestone is closed… on: milestone: types: [closed] Rob Allen | social.akrabat.com/rob
 
                do a full checkout… steps: - name: Checkout code uses: actions/checkout@v3 with: ref: master fetch-depth: 0 Rob Allen | social.akrabat.com/rob
 
                so we can create & push a tag… - name: Create Tag uses: rickstaa/action-create-tag@v1 id: create-tag with: tag: “${{ github.event.milestone.title }}” message: “Tag ${{ github.event.milestone.title }}” Rob Allen | social.akrabat.com/rob
 
                and create a GitHub Release - name: Create GitHub Release uses: actions/github-script@v6 with: script: | await github.rest.repos.createRelease({ generate_release_notes: true, name: “${{github.event.milestone.title}}”, tag_name: “${{github.event.milestone.title}}” }); Rob Allen | social.akrabat.com/rob
 
                along with a new Milestone - name: ‘Get next minor version’ id: semvers uses: “WyriHaximus/github-action-next-semvers@v1” with: version: ${{github.event.milestone.title}} - name: ‘Create new milestone’ uses: “WyriHaximus/github-action-create-milestone@v1” with: title: ${{ steps.semvers.outputs.patch }} env: GITHUB_TOKEN: “${{ secrets.GITHUB_TOKEN }}” Rob Allen | social.akrabat.com/rob
 
                along with a new Milestone - name: ‘Get next minor version’ id: semvers uses: “WyriHaximus/github-action-next-semvers@v1” with: version: ${{github.event.milestone.title}} - name: ‘Create new milestone’ uses: “WyriHaximus/github-action-create-milestone@v1” with: title: ${{ steps.semvers.outputs.patch }} env: GITHUB_TOKEN: “${{ secrets.GITHUB_TOKEN }}” Rob Allen | social.akrabat.com/rob
 
                along with a new Milestone - name: ‘Get next minor version’ id: semvers uses: “WyriHaximus/github-action-next-semvers@v1” with: version: ${{github.event.milestone.title}} - name: ‘Create new milestone’ uses: “WyriHaximus/github-action-create-milestone@v1” with: title: ${{ steps.semvers.outputs.patch }} env: GITHUB_TOKEN: “${{ secrets.GITHUB_TOKEN }}” Rob Allen | social.akrabat.com/rob
 
                Compile & upload binaries Rob Allen | social.akrabat.com/rob
 
                When a release is published… on: release: types: - published Rob Allen | social.akrabat.com/rob
 
                build the binaries… steps: # checkout, setup Go etc… - name: Build the Rodeo executables # (akrabat.com/building-go-binaries-for-different-platforms) run: ./build-exes.sh ${{ github.ref_name }} Rob Allen | social.akrabat.com/rob
 
                and upload them - name: Upload the Rodeo binaries uses: actions/svenstaro/upload-release-action@v2 with: repo_token: ${{ secrets.GITHUB_TOKEN }} tag: ${{ github.ref }} file: ./release/rodeo-* file_glob: true Rob Allen | social.akrabat.com/rob
 
                Build and push to ECR Rob Allen | social.akrabat.com/rob
 
                Build container… on: release: types: - published Rob Allen | social.akrabat.com/rob
 
                Build container… on: release: types: - published steps: # checkout, etc…
 
                and push to ECR - name: Push to ECR uses: jwalton/gh-ecr-push@v1 with: access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} region: us-east-2 local-image: img-name:${{ github.ref_name }} image: img-name:${{ github.ref_name }}, img-name:latest Rob Allen | social.akrabat.com/rob
 
                and push to ECR - name: Push to ECR uses: jwalton/gh-ecr-push@v1 with: access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} region: us-east-2 local-image: img-name:${{ github.ref_name }} image: img-name:${{ github.ref_name }}, img-name:latest Rob Allen | social.akrabat.com/rob
 
                and push to ECR - name: Push to ECR uses: jwalton/gh-ecr-push@v1 with: access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} region: us-east-2 local-image: img-name:${{ github.ref_name }} image: img-name:${{ github.ref_name }}, img-name:latest Rob Allen | social.akrabat.com/rob
 
                More! Rob Allen | social.akrabat.com/rob
 
                More! • • • • • • Secrets live in GitHub, not git! Use conditionals to save time & resources Don’t like bash? Use Python with shell: python The GitHub cli (gh) is preinstalled Building a library? Use matrices to test on multiple PHPs Pre-built: https://github.com/marketplace?type=actions Rob Allen | social.akrabat.com/rob
 
                To sum up Rob Allen | social.akrabat.com/rob
 
                “a deployment pipeline is an automated manifestation of your process for getting software from version control into the hands of your users.” David Farley Rob Allen | social.akrabat.com/rob
 
                Thank you! Rob Allen | social.akrabat.com/rob
