Automatically update documentation with Github Actions

2023-01-12
2023-10-23
6 min read
Featured Image

In some of my projects, I like to provide an accurate documentation. Which means that I want to have examples and documentation up to date.

So, when I’m updating a library or a service I’m using in my code, I need to manually find and replace all the text to reflect the changes.

For example, let say I have README.md with:

We have the following versions:

* Current is 1.0-SNAPSHOT.
* This project is tested with Elasticsearch 8.5.1.

And a .env file which I’m using to run docker compose:

STACK_VERSION=8.5.1

When you need to update a version, let say Elasticsearch to 8.5.2, you need to edit your pom.xml and also all related files like the .env file.

This can be error prone or you can just forget about a file. It would be better to just update the property somewhere and let the project automatically update the other files.

Maven resources plugin to the rescue

This can be easily done with the maven resources plugin. The only thing to write is something like that:

<properties>
  <elasticsearch.version>8.5.1</elasticsearch.version>
</properties>

<build>
  <resources>
    <resource>
      <directory>src/main/documentation</directory>
      <filtering>true</filtering>
      <targetPath>${project.basedir}</targetPath>
    </resource>
  </resources>
</build>

Any file available within src/main/documentation dir will be copy to the root project dir and filtered with the different maven properties. Define a src/main/documentation/README.md file:

We have the following versions:

* Current is ${project.version}.
* This project is tested with Elasticsearch ${elasticsearch.version}.

Define a src/main/documentation/.env file:

STACK_VERSION=${elasticsearch.version}

When processing the resources within the process-resources phase, maven will replace the maven properties by the right values:

mvn process-resources

And you will see that README.md and .env have been updated to reflect the changes if any. Actually, you won’t see anything unless you update a version. 😉

So the process is now:

  • Update the pom.xml
  • Run mvn process-resources
  • Commit the changes

Automatic update with Github Actions

To avoid having to locally checkout a branch that has been created in Github, run the maven command and git push the changes to the branch before it can be reviewed and merged, we can use Github Actions to update our resources automatically.

Let say that we already have an action .github/workflows/pr.yml file:

name: Build the pull request
on:
  pull_request:
    branches: [ main ]

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      - name: Set up JDK 17 and Maven Central Repository
        uses: actions/setup-java@v3
        with:
          java-version: '17'
          distribution: 'adopt'
          cache: 'maven'
      - name: Build the project
        run: mvn -B install

This is performing the following steps:

  • Checkout
  • Setup the JDK and Maven cache
  • Build the project

We need to add the generation of the updated files. A best practice is to run this in another workflow file. So let’s create .github/workflows/update-doc.yml:

name: Update the documentation if needed
on: [ pull_request ]
jobs:
  update-files:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      - name: Update resources with Maven
        run: mvn -B process-resources

And then we need to commit our changes. For this we will use the add-and-commit action:

- name: Update files if needed
  uses: EndBug/add-and-commit@v9
  with:
    default_author: github_actions
    message: Automatically update versions

But actually this will fail. Because we need to define that we want to have a write access to the repository:

permissions:
  contents: write
  packages: write

In the context of a PR, the checkout is by default done using another branch name than the real branch name. When the plugin commits the changes, it does not go to the branch associated with the PR.

On non-push events, such as pull_request, we need to specify the actual branch name of the pr as the ref for the checkout. We can also pass the repository name to the checkout action:

- name: Checkout code
  uses: actions/checkout@v4
  with:
    ref: ${{github.event.pull_request.head.ref}}
    repository: ${{github.event.pull_request.head.repo.full_name}}

Manually create a PR

Let’s update the pom.xml and update Elasticsearch version to 8.5.2:

<elasticsearch.version>8.5.2</elasticsearch.version>

Then commit it and push it to a branch. And finally create a PR:

Update the version manually

Update the version manually

So we have one single commit:

One single commit

One single commit

When the Github Actions workflow starts, it updates the code, commits it and pushes it to our branch which is now updated:

With the additional commit

With the additional commit

Using Dependabot to update our versions

If you are using Dependabot to automatically update your libraries, the same update process should happen. If you don’t, go to the repository settings and enable Dependabot version updates:

Enable Dependabot

Enable Dependabot

So we have the following .github/dependabot.yml file:

version: 2
updates:
  - package-ecosystem: "maven"
    directory: "/"
    schedule:
      interval: "daily"

This automatically creates a PR when a new version is available.

Dependabot and Github actions

When we commit the changes, it does not trigger another github action call. This is due to limitations set by GitHub. This prevents us from accidentally creating recursive workflow runs. But here, we want that.

So we need to create a new Personal Access Token (PAT) instead of the default GITHUB_TOKEN. Open your Github Developper Settings and create a new Personal Access Token. It needs to have the repo and workflow scopes:

Create your Personal Access Token

Create your Personal Access Token

Note the generated token and store it as a secret in the repository.

Add your new secret

Add your new secret

Then pass the new token to the checkout step:

- name: Checkout code
  uses: actions/checkout@v4
  with:
    ref: ${{github.event.pull_request.head.ref}}
    repository: ${{github.event.pull_request.head.repo.full_name}}
    token: ${{ secrets.PAT }}

Actually, when the code is running automatically from Dependabot, you can also use the github.token as the secrets.PAT is not available.

- name: Checkout code
  uses: actions/checkout@v4
  with:
    ref: ${{github.event.pull_request.head.ref}}
    repository: ${{github.event.pull_request.head.repo.full_name}}
    token: ${{ secrets.PAT || github.token }}

Here is what happens when dependabot creates a PR.

The PR

The PR

Note that few seconds after Dependabot has created the PR, it has been updated by our job:

Build details

Build details

And we can see the details of the commit that has been added by Github Actions on behalf of Dependabot:

Automatically added commit

Automatically added commit

External Pull Requests

When the pull request is created from a fork, we can not push our changes to the original repository. So we need to skip the update-doc workflow.

We can detect this by comparing github.event.pull_request.head.repo.full_name and github.event.pull_request.base.repo.full_name:

if: github.event.pull_request.base.repo.full_name == github.event.pull_request.head.repo.full_name

The full workflow

The final workflow is:

name: Update the documentation if needed
on: [ pull_request ]
jobs:
  update-files:
    if: github.event.pull_request.base.repo.full_name == github.event.pull_request.head.repo.full_name
    runs-on: ubuntu-latest
    permissions:
      contents: write
      packages: write
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          ref: ${{github.event.pull_request.head.ref}}
          repository: ${{github.event.pull_request.head.repo.full_name}}
          token: ${{ secrets.PAT || github.token }}
      - name: Update resources with Maven
        run: mvn -B process-resources
      - name: Update files if needed
        uses: EndBug/add-and-commit@v9
        with:
          default_author: github_actions
          message: Automatically update versions

You can see this code in the dadoonet/demo-automatic-doc demo repository.

Share this article on
Avatar

David Pilato

20+ years of experience, mostly in Java. Living in Cergy, France.