Repository: wayfair-incubator/pygitops (language: python)

Issue:
Create versioned documentation
Update the docs so that they can be filtered based on version as was done in the [Columbo docs](https://wayfair-incubator.github.io/columbo/latest/).


Discussion / hints:
There is documentation on how to implement versions in mkdocs [here](https://squidfunk.github.io/mkdocs-material/setup/setting-up-versioning/).

Fix patch:
diff --git a/.github/actions/publish-docs-with-mike/action.sh b/.github/actions/publish-docs-with-mike/action.sh
new file mode 100755
index 0000000..96e1d57
--- /dev/null
+++ b/.github/actions/publish-docs-with-mike/action.sh
@@ -0,0 +1,30 @@
+#!/usr/bin/env bash
+
+set -eo pipefail
+
+echo "::group::Configure Git User"
+"${GITHUB_ACTION_PATH}/configure_git_user.sh"
+echo "::endgroup::"
+
+echo "::group::Pull down latest docs commit"
+git fetch --no-tags --prune --progress --no-recurse-submodules --depth=1 origin gh-pages
+echo "::endgroup::"
+
+echo "::group::Publish documentation"
+if [[ "${NEW_VERSION}" == "false" ]]; then
+  if [[ "${VERSION_NAME}" == "" ]]; then
+    echo "::error::'version_name' must be specified when 'NEW_VERSION' is false."
+    exit 1
+  fi
+  echo "mike deploy \"${VERSION_NAME}\""
+  mike deploy "${VERSION_NAME}"
+elif [[ "${GITHUB_EVENT_NAME:-}" != "release" ]]; then
+  echo "::error::new_version can only be used for release events."
+  exit 1
+else
+  # drop leading "v" from tag name to have just the version number
+  "${GITHUB_ACTION_PATH}/update_docs_for_version.sh" "${RELEASE_TAG:1}"
+fi
+echo "git push origin gh-pages"
+git push origin gh-pages
+echo "::endgroup::"
diff --git a/.github/actions/publish-docs-with-mike/action.yml b/.github/actions/publish-docs-with-mike/action.yml
new file mode 100644
index 0000000..648b92a
--- /dev/null
+++ b/.github/actions/publish-docs-with-mike/action.yml
@@ -0,0 +1,37 @@
+name: "Publish Docs with Mike"
+description: |
+  Publish versioned documentation with Mike.
+  Requires a python environment to already be setup with mike and any other documentation dependencies installed.
+inputs:
+  version_name:
+    description: |
+      Name a version to publish.
+      Required when new_version is false.
+    required: false
+    default: ""
+  new_version:
+    description: |
+      If true, publish a new docs version. If an existing version uses the alias/tile "latest",
+      update the records so that the new version becomes the latest version.
+      If version_name is given, that value will be used. Otherwise the release tag will be used.
+    required: false
+    default: "false"
+  commit_user_name:
+    description: "User name to use for commits. When omitted, the event values will be inspected to derive the name."
+    default: ""
+    required: false
+  commit_user_email:
+    description: "User email to use for commits. When omitted, the event values will be inspected to derive the email."
+    default: ""
+    required: false
+runs:
+  using: "composite"
+  steps:
+    - run: "$GITHUB_ACTION_PATH/action.sh"
+      shell: "bash"
+      env:
+        USER_NAME: ${{ inputs.name }}
+        USER_EMAIL: ${{ inputs.email }}
+        VERSION_NAME: ${{ inputs.version_name }}
+        NEW_VERSION: ${{ inputs.new_version }}
+        RELEASE_TAG: ${{ github.event.release.tag_name }}
diff --git a/.github/actions/publish-docs-with-mike/configure_git_user.sh b/.github/actions/publish-docs-with-mike/configure_git_user.sh
new file mode 100755
index 0000000..7be0e72
--- /dev/null
+++ b/.github/actions/publish-docs-with-mike/configure_git_user.sh
@@ -0,0 +1,45 @@
+#!/usr/bin/env bash
+
+set -euo pipefail
+
+NO_REPLY_SUFFIX="@users.noreply.github.com"
+
+function set_and_exit() {
+  local name="${USER_NAME:-$1}"
+  local email="${USER_EMAIL:-$2}"
+  git config --local user.name "${name}"
+  git config --local user.email "${email}"
+  exit 0
+}
+
+function json_query() {
+  jq -e "${1}" "${GITHUB_EVENT_PATH}"
+}
+
+if [[ "${USER_NAME}" != "" && "${USER_EMAIL}" != "" ]]; then
+  set_and_exit
+fi
+
+echo "::debug::Attempting push event pusher"
+if json_query ".push.pusher" > /dev/null ; then
+  echo "::debug::Found push event pusher"
+  set_and_exit "$(json_query '.push.pusher.name')" "$(json_query '.push.pusher.email')"
+fi
+
+echo "::debug::Attempting merge merged by"
+if json_query ".pull_request.merged_by" > /dev/null ; then
+  echo "::debug::Found pull request event merged by"
+  LOGIN="$(json_query '.pull_request.merged_by.login')"
+  set_and_exit "${LOGIN}" "${LOGIN}${NO_REPLY_SUFFIX}"
+fi
+
+echo "::debug::Attempting event sender"
+if json_query ".sender" > /dev/null ; then
+  echo "::debug::Found pull event sender"
+  LOGIN="$(json_query '.sender.login')"
+  set_and_exit "${LOGIN}" "${LOGIN}${NO_REPLY_SUFFIX}"
+fi
+
+echo "::debug::Falling back to GITHUB_ACTOR"
+LOGIN="${GITHUB_ACTOR:-github_action}"
+set_and_exit "${LOGIN}" "${LOGIN}${NO_REPLY_SUFFIX}"
diff --git a/.github/actions/publish-docs-with-mike/update_docs_for_version.sh b/.github/actions/publish-docs-with-mike/update_docs_for_version.sh
new file mode 100755
index 0000000..4f77d9f
--- /dev/null
+++ b/.github/actions/publish-docs-with-mike/update_docs_for_version.sh
@@ -0,0 +1,15 @@
+#!/usr/bin/env bash
+
+set -eo pipefail
+
+NEW_VERSION="${1}"
+PREV_LATEST="$(mike list --json | jq --raw-output '.[] | select(.aliases == ["latest"]) | .version')"
+
+if [[ "${PREV_LATEST}" == "" ]]; then
+  echo "No previous version found using the latest alias. Nothing to retitle."
+else
+  echo "mike retitle --message \"Remove latest from title of ${PREV_LATEST}\" \"${PREV_LATEST}\" \"${PREV_LATEST}\""
+  mike retitle --message "Remove latest from title of ${PREV_LATEST}" "${PREV_LATEST}" "${PREV_LATEST}"
+fi
+echo "mike deploy --update-aliases --title \"${NEW_VERSION} (latest)\" \"${NEW_VERSION}\" \"latest\""
+mike deploy --update-aliases --title "${NEW_VERSION} (latest)" "${NEW_VERSION}" "latest"
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
index 8011e6e..3d1f84c 100644
--- a/.github/workflows/main.yml
+++ b/.github/workflows/main.yml
@@ -1,8 +1,11 @@
 name: CI
 
-# Triggers the workflow on push or pull request
-# events, but only for the main branch
-on: [push, pull_request]
+# Workflow does NOT trigger on a feature branch until a pull request is created.
+# Workflow will always run when a pull request is merged to the default branch.
+on:
+  pull_request: {}
+  push:
+      branches: ["main"]
 
 env:
   PYTHON_VERSION: "3.9.1"
@@ -132,31 +135,50 @@ jobs:
             file: coverage-${{ matrix.python-version }}.xml
             fail_ci_if_error: true
 
-
   build-docs:
     runs-on: ubuntu-latest
-    if: github.event_name == 'push'
+    if: github.event_name == 'pull_request'
     steps:
       - name: Check out code
         uses: actions/checkout@v2
 
+      - uses: actions/setup-python@v2
+        with:
+          python-version: ${{ env.PYTHON_VERSION }}
+
+      - name: Install dependencies
+        uses: ./.github/actions/install-dependencies
+        with:
+          requirements: "true"
+          test-requirements: "true"
+
       - name: Build Docs
-        run: docker-compose run --rm mkdocs build --strict
+        run: mkdocs build --strict
 
-      # publish dev docs
-      - name: Publish (dev)
-        if: github.ref != 'refs/heads/main'
+      - name: Upload docs as an artifact
         uses: actions/upload-artifact@v2
         with:
           name: docs-site
           path: site/
 
-      # publish prod docs
-      - name: Publish (prod)
-        if: github.ref == 'refs/heads/main'
-        uses: JamesIves/github-pages-deploy-action@releases/v3
+  update-dev-docs:
+    runs-on: ubuntu-latest
+    if: github.event_name == 'push' && github.ref == 'refs/heads/main'
+    steps:
+      - name: Check out code
+        uses: actions/checkout@v2
+
+      - uses: actions/setup-python@v2
+        with:
+          python-version: ${{ env.PYTHON_VERSION }}
+
+      - name: Install dependencies
+        uses: ./.github/actions/install-dependencies
         with:
-          ACCESS_TOKEN: ${{ secrets.ACCESS_TOKEN }}
-          BRANCH: gh-pages
-          FOLDER: site
+          requirements: "true"
+          test-requirements: "true"
 
+      - name: Push documentation changes
+        uses: ./.github/actions/publish-docs-with-mike
+        with:
+          version_name: dev
diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml
index a2c3556..f0f27d4 100644
--- a/.github/workflows/python-publish.yml
+++ b/.github/workflows/python-publish.yml
@@ -8,6 +8,27 @@ on:
     types: [published]
 
 jobs:
+  update_docs_for_version_bump:
+    runs-on: ubuntu-latest
+    steps:
+      - name: Check out code
+        uses: actions/checkout@v2
+
+      - uses: actions/setup-python@v2
+        with:
+          python-version: ${{ env.PYTHON_VERSION }}
+
+      - name: Install dependencies
+        uses: ./.github/actions/install-dependencies
+        with:
+          requirements: "true"
+          test-requirements: "true"
+
+      - name: Push documentation changes
+        uses: ./.github/actions/publish-docs-with-mike
+        with:
+          new_version: true
+
   build-and-publish-wheel-to-pypi:
     runs-on: ubuntu-latest
     environment: "Publish Release"
diff --git a/docker-compose.yaml b/docker-compose.yaml
index eafe327..93ad777 100644
--- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -9,6 +9,7 @@ services:
     image: "wayfair/pygitops-devbox"
     volumes:
       - ./:/app
+      - ~/.gitconfig:/home/pygitops/.gitconfig
 
   # run all the tests and linting locally
   # - black & isort will format code to address issues
@@ -23,3 +24,7 @@ services:
     ports:
       - "8000:8000"
     command: ["serve", "--dev-addr=0.0.0.0:8000"]
+
+  mike:
+    <<: *mkdocs
+    entrypoint: "mike"
diff --git a/docker/update_docs_for_version.sh b/docker/update_docs_for_version.sh
new file mode 100755
index 0000000..be1fca0
--- /dev/null
+++ b/docker/update_docs_for_version.sh
@@ -0,0 +1,41 @@
+#!/usr/bin/env bash
+
+set -eo pipefail
+
+NEW_IS_DEV="false"
+OLD_IS_DEV="false"
+
+function usage() {
+  echo "usage: update_docs_for_version.sh OLD_VERSION NEW_VERSION"
+  echo ""
+  echo " OLD_VERSION : Version being updated from"
+  echo " NEW_VERSION : Version being updated to"
+  exit 1
+}
+
+function is_dev() {
+  [[ "${1}" =~ .+-dev$ ]]
+}
+
+if [[ $# != 2 ]]; then
+  usage
+fi
+
+OLD_VERSION="${1}"
+NEW_VERSION="${2}"
+
+if is_dev "${OLD_VERSION}"; then
+  OLD_IS_DEV="true"
+fi
+if is_dev "${NEW_VERSION}"; then
+  NEW_IS_DEV="true"
+fi
+
+if [[ "${NEW_IS_DEV}" == "true" ]]; then
+  mike deploy dev
+else
+  if [[ "${OLD_IS_DEV}" != "true" ]]; then
+    mike retitle --message "Remove latest from title of ${OLD_VERSION}" "${OLD_VERSION}" "${OLD_VERSION}"
+  fi
+  mike deploy --update-aliases --title "${NEW_VERSION} (latest)" "${NEW_VERSION}" "latest"
+fi
diff --git a/mkdocs.yml b/mkdocs.yml
index 05c6d1e..20fe5d4 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -31,3 +31,6 @@ plugins:
             show_root_full_path: False
 extra_css:
   - css/mkdocstrings.css
+extra:
+  version:
+    provider: mike


Test patch:
diff --git a/requirements-test.txt b/requirements-test.txt
index 20ec594..55d8a57 100644
--- a/requirements-test.txt
+++ b/requirements-test.txt
@@ -9,3 +9,4 @@ pytest==6.2.4
 pytest-cov==2.11.1
 pytest-mock==3.6.1
 pdbpp
+mike==1.0.0