GitHub actions are a powerful tool used for CI/CD and other development workflows. But did you know you can also run scheduled actions, similar to a cron job?

As a proof-of-concept I was curious if it would be possible to automatically keep one of my repositories in sync with another repo. For this experiment I had a branch that added a Docker configuration to Laravel projects at ractoon/laravel-docker. As Laravel released updates this branch would be missing the latest changes.

The workflow below is located at .github/workflows/laravel-sync.yml in the ractoon/laravel-docker repo:

name: Laravel Sync

on:
  schedule:
    - cron: "17 * * * *"

  workflow_dispatch:

jobs:
  sync:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout
        uses: actions/checkout@v2

      - name: Set Env
        run: |
          echo "current_version=$(curl -Ls https://api.github.com/repos/ractoon/laravel-docker/tags | jq -r '.[0].name')" >> $GITHUB_ENV
          echo "laravel_version=$(curl -Ls https://api.github.com/repos/laravel/laravel/tags | jq -r '.[0].name')" >> $GITHUB_ENV
      - name: Sync Latest Laravel
        run: |
          echo "Checking Laravel repo..."
          if [[ "${{ env.current_version }}" != "${{ env.laravel_version }}" ]]; then
            curl -Ls https://api.github.com/repos/laravel/laravel/tarball -o ${{ runner.temp }}/laravel.tar
            echo "Extracting and updating repo files..."
            find . -maxdepth 1 ! -name .git ! -name .github ! -name .gitignore ! -name laravel.tar ! -name "docker" ! -name "cmd" ! -name "docker-compose.yml" ! -name . -exec rm -r {} \; && tar xzf ${{ runner.temp }}/laravel.tar --strip-components 1 --exclude='README.md' -C . && sed -i -e 's/^DB_HOST.*$/DB_HOST=mysql/g' ".env.example" && sed -i -e 's/^REDIS_HOST.*$/REDIS_HOST=redis/g' ".env.example"
            if git show-ref --tags "${{ env.laravel_version }}" --quiet; then
              echo "Tag ${{ env.laravel_version }} exists."
            else
              echo "Updating files to ${{ env.laravel_version }}"
              git config user.name github-actions
              git config user.email github-actions@github.com
              git add .
              if [[ `git status --porcelain` ]]; then
                echo "Committing changes..."
                git commit -m "Auto-update Laravel ${{ env.laravel_version }}"
                git tag ${{ env.laravel_version }}
                git push --atomic origin master ${{ env.laravel_version }}
              else
                echo "No changes to commit."
              fi
            fi
          else
            echo "Up to date."
          fi

Breaking this down here's what's going on.

on:
  schedule:
    - cron: "17 * * * *"

Schedule this action to run at 17 minutes past the hour, every hour.

- name: Checkout
  uses: actions/checkout@v2

Use the checkout action to grab the required repos.

- name: Set Env
  run: |
    echo "current_version=$(curl -Ls https://api.github.com/repos/ractoon/laravel-docker/tags | jq -r '.[0].name')" >> $GITHUB_ENV
    echo "laravel_version=$(curl -Ls https://api.github.com/repos/laravel/laravel/tags | jq -r '.[0].name')" >> $GITHUB_ENV

Set a couple environment variables for my repos current tagged version, and the current tagged version from the Laravel repo.

- name: Sync Latest Laravel
  run: |
    echo "Checking Laravel repo..."
    if [[ "${{ env.current_version }}" != "${{ env.laravel_version }}" ]]; then
      ...
    else
      echo "Up to date."
    fi

Check if the latest Laravel version is different from my repo. If it's the same we're up to date. If not we need to do some additional work.

curl -Ls https://api.github.com/repos/laravel/laravel/tarball -o ${{ runner.temp }}/laravel.tar
echo "Extracting and updating repo files..."
find . -maxdepth 1 ! -name .git ! -name .github ! -name .gitignore ! -name laravel.tar ! -name "docker" ! -name "cmd" ! -name "docker-compose.yml" ! -name . -exec rm -r {} \; && tar xzf ${{ runner.temp }}/laravel.tar --strip-components 1 --exclude='README.md' -C . && sed -i -e 's/^DB_HOST.*$/DB_HOST=mysql/g' ".env.example" && sed -i -e 's/^REDIS_HOST.*$/REDIS_HOST=redis/g' ".env.example"

First we need to download the Laravel repo files and extract them. Then perform some cleanup on the files retrieved so we can preserve our changes, including adding Docker and some configuration.

if git show-ref --tags "${{ env.laravel_version }}" --quiet; then
  echo "Tag ${{ env.laravel_version }} exists."
else
  ...
fi

If this repo already has a tagged release matching that of the latest Larvel version, then don't do anything. Otherwise we need to update the files.

echo "Updating files to ${{ env.laravel_version }}"
git config user.name github-actions
git config user.email github-actions@github.com
git add .

Set the git configuration and add changes to be committed.

if [[ `git status --porcelain` ]]; then
  echo "Committing changes..."
  git commit -m "Auto-update Laravel ${{ env.laravel_version }}"
  git tag ${{ env.laravel_version }}
  git push --atomic origin master ${{ env.laravel_version }}
else
  echo "No changes to commit."
fi

The --porcelain flag here makes the output easier to parse in scripts, which makes it easier to use in the boolean condition here. If there are changes to commit then they are committed here with a message saying which version of Laravel the updates belong to. A tag is added to the repo for the current Laravel version to keep things matched up. Then changes are pushed to the repo.