Skip to content

Github Action for automatic validation of DB migrations in a CI pipeline #1

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Jun 22, 2021
Merged
2 changes: 2 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
FROM alpine:3.13

RUN apk add --no-cache curl jq

COPY entrypoint.sh /entrypoint.sh

ENTRYPOINT ["/entrypoint.sh"]
15 changes: 13 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
# Migration CI action
# DB Migration checker action

A GitHub action to run DB migrations with the DLE
A GitHub action to run DB migrations with Database Lab Engine (DLE)

## Overview
**Database Lab DB migration checker** is a tool to automatically validate migrations in the CI pipeline.

## Database Lab DB migration checker's benefits:
- Check migrations as a part of a standard pipeline
- Protect DLE from data stealing - run migrations in a protected environment
- Protect logs and artifacts from being revealed

## How to use
Check out the [Database Lab DB migrations checker documentation](https://postgres.ai/docs/db-migration-checker)
57 changes: 54 additions & 3 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,62 @@ inputs:
owner:
description: 'The owner of the repository to check'
required: true
default: ${{ github.repository_owner }}
repo:
description: 'The repo to check'
required: true
default: ${{ github.event.repository.name }}
ref:
description: 'The input represents a ref parameter, which can be a SHA, branch, or tag'
required: true
default: ${{ github.ref }}
pull_request:
description: 'Link to the pull request'
required: false
default: ${{ github.event.pull_request.html_url }}
compare:
description: 'The compare link'
required: false
default: ${{ github.event.compare }}
commit_sha:
description: 'Commit SHA'
required: false
default: ${{ github.event.after }}
author_name:
description: 'Author name'
required: false
default: ${{ github.event.head_commit.author.name }}
commands:
description: 'List of commands to run DB migrations'
required: true
dbname:
description: 'The database that the workflow is running with'
required: false
default: ""
migration_envs:
description: 'List of commands to run DB migrations'
required: false
download_artifacts:
description: 'Option to download artifacts'
required: false
default: "false"

observation_interval:
description: 'Observation interval'
required: false
default: "10"
max_lock_duration:
description: 'Max lock duration'
required: false
default: "10"
max_duration:
description: 'Max duration'
required: false
default: "3600"

outputs:
status:
response:
description: 'The result of CI checks'
runs:
using: 'docker'
image: 'Dockerfile'
args:
- ${{ inputs.owner }}
90 changes: 87 additions & 3 deletions entrypoint.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,90 @@
#!/bin/sh -l

echo "Owner $1"
KEEP_CLONE=false

status="OK"
echo "::set-output name=status::$status"
if [[ "${INPUT_DOWNLOAD_ARTIFACTS}" == "true" ]]; then
KEEP_CLONE=true
fi

JSON_DATA=$(jq -n -c \
--arg owner "$INPUT_OWNER" \
--arg repo "$INPUT_REPO" \
--arg ref "$INPUT_REF" \
--arg commands "$INPUT_COMMANDS" \
--arg db_name "$INPUT_DBNAME" \
--arg username "$GITHUB_ACTOR" \
--arg username_full "$INPUT_AUTHOR_NAME" \
--arg username_link "${GITHUB_SERVER_URL}/$GITHUB_ACTOR" \
--arg branch "${GITHUB_HEAD_REF:-${GITHUB_REF##*/}}" \
--arg branch_link "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/tree/${GITHUB_HEAD_REF:-${GITHUB_REF##*/}}" \
--arg commit "${INPUT_COMMIT_SHA}" \
--arg commit_link "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}/commit/${INPUT_COMMIT_SHA}" \
--arg request_link "${INPUT_PULL_REQUEST}" \
--arg diff_link "${INPUT_COMPARE}" \
--arg migration_envs "$INPUT_MIGRATION_ENVS" \
--arg observation_interval "$INPUT_OBSERVATION_INTERVAL" \
--arg max_lock_duration "$INPUT_MAX_LOCK_DURATION" \
--arg max_duration "$INPUT_MAX_DURATION" \
--argjson keep_clone "$KEEP_CLONE" \
'{source: {owner: $owner, repo: $repo, ref: $ref, branch: $branch, branch_link: $branch_link, commit: $commit, commit_link: $commit_link, request_link: $request_link, diff_link: $diff_link}, username: $username, username_full: $username_full, username_link: $username_link, db_name: $db_name, commands: $commands | rtrimstr("\n") | split("\n"), migration_envs: $migration_envs | rtrimstr("\n") | split("\n"), observation_config: { observation_interval: $observation_interval|tonumber, max_lock_duration: $max_lock_duration|tonumber, max_duration: $max_duration|tonumber}, keep_clone: $keep_clone}')

echo $JSON_DATA

response_code=$(curl --show-error --silent --location --request POST "${CI_ENDPOINT}/migration/run" --write-out "%{http_code}" \
--header "Verification-Token: ${SECRET_TOKEN}" \
--header 'Content-Type: application/json' \
--output response.json \
--data "${JSON_DATA}")

jq . response.json

if [[ $response_code -ne 200 ]]; then
echo "Invalid status code given: ${response_code}"
exit 1
fi

status=$(jq -r '.session.result.status' response.json)

if [[ $status != "passed" ]]; then
echo "Invalid status given: ${status}"
exit 1
fi

echo "::set-output name=response::$(cat response.json)"

clone_id=$(jq -r '.clone_id' response.json)
session_id=$(jq -r '.session.session_id' response.json)

if [[ ! $KEEP_CLONE ]]; then
exit 0
fi

# Download artifacts
mkdir artifacts

download_artifacts() {
artifact_code=$(curl --show-error --silent "${CI_ENDPOINT}/artifact/download?artifact_type=$1&session_id=$2&clone_id=$3" --write-out "%{http_code}" \
--header "Verification-Token: ${SECRET_TOKEN}" \
--header 'Content-Type: application/json' \
--output artifacts/$1)

if [[ $artifact_code -ne 200 ]]; then
echo "Downloading $1, invalid status code given: ${artifact_code}"
return
fi

echo "Artifact \"$1\" has been downloaded to the artifacts directory"
}

cat response.json | jq -c -r '.session.artifacts[]' | while read artifact; do
download_artifacts $artifact $session_id $clone_id
done

# Stop the running clone
response_code=$(curl --show-error --silent "${CI_ENDPOINT}/artifact/stop?clone_id=${clone_id}" --write-out "%{http_code}" \
--header "Verification-Token: ${SECRET_TOKEN}" \
--header 'Content-Type: application/json')

if [[ $response_code -ne 200 ]]; then
echo "Invalid status code given on destroy clone: ${artifact_code}"
fi