|
| 1 | +name: skeema-diff |
| 2 | + |
| 3 | +on: |
| 4 | + pull_request: |
| 5 | + branches: |
| 6 | + - master |
| 7 | + |
| 8 | +jobs: |
| 9 | + skeema-labels-validation: |
| 10 | + # Some of the Action automation, the skeema automation, and the interaction of the two, is done by PR labels. |
| 11 | + # Break down of the labels: |
| 12 | + # - migration:skeema:diff: indicates this PR has a schema diff (as opposed to a PR that doesn't change the schema) |
| 13 | + # - migration:skeefree:detected: skeefree has detected this PR. This avoids excessive searches. |
| 14 | + # - migration:skeefree:queued: skeefree has queued the migrations for execution. This is a point of no return. We're committed to the migration. |
| 15 | + # - migration:late:commits: new commits came in after the migration was queued. We won't consider further schema changes. Humans should look into why these commits came in at this stage. |
| 16 | + runs-on: ubuntu-latest |
| 17 | + steps: |
| 18 | + - name: Fetch hub |
| 19 | + run: | |
| 20 | + ( |
| 21 | + mkdir -p /tmp/skeema-ci/ |
| 22 | + cd /tmp/skeema-ci/ |
| 23 | + curl -s -L https://github.com/github/hub/releases/download/v2.12.3/hub-linux-amd64-2.12.3.tgz > hub-linux-amd64-2.12.3.tgz |
| 24 | + tar xzf hub-linux-amd64-2.12.3.tgz hub-linux-amd64-2.12.3/bin/hub |
| 25 | + mv hub-linux-amd64-2.12.3/bin/hub hub |
| 26 | + ) |
| 27 | + - name: validate labels |
| 28 | + env: |
| 29 | + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
| 30 | + GITHUB_USER: github |
| 31 | + run: | |
| 32 | + for label_hint in "migration:skeema:diff 0000cc" "migration:skeefree:queued ffb800" "migration:skeefree:detected 9ab116" "migration:late:commits cc0000" "migration:for:review 8050e0" ; do |
| 33 | + read -r label color <<< "$label_hint" |
| 34 | + /tmp/skeema-ci/hub api "repos/${GITHUB_REPOSITORY}/labels/${label}" > /dev/null 2>&1 || /tmp/skeema-ci/hub api "repos/${GITHUB_REPOSITORY}/labels" --raw-field "name=${label}" --raw-field "color=${color}" |
| 35 | + done |
| 36 | +
|
| 37 | + skeema-diff: |
| 38 | + # This actions does a lot of things, but the gist of it: |
| 39 | + # - Identify whether there is a schema diff in this PR |
| 40 | + # - If so, what's the diff? Infer the CREATE/DROP/ALTER statements |
| 41 | + # - Update PR's body and comments to present the changes |
| 42 | + # - Add/remove labels so as to indicate status of this PR |
| 43 | + # - Otherwise add informative comments on this PR |
| 44 | + runs-on: ubuntu-latest |
| 45 | + steps: |
| 46 | + - uses: actions/checkout@v2 |
| 47 | + with: |
| 48 | + ref: ${{ github.event.pull_request.base.sha }} |
| 49 | + # We checkout `master`. We will apply ("push") the `master` schema into local MySQL |
| 50 | + - name: Find skeema file |
| 51 | + run: | |
| 52 | + mkdir -p /tmp/skeema-ci |
| 53 | + skeema_path_hint_file=".github/skeema-diff.cfg" |
| 54 | + if [ -f $skeema_path_hint_file ] ; then |
| 55 | + skeema_path="$(egrep '^skeema_path' $skeema_path_hint_file | cut -d'=' -f2-)" |
| 56 | + fi |
| 57 | + skeema_file="$(find ${skeema_path:-.} skeema schemas schema db -name .skeema -maxdepth 1 2> /dev/null | head -1)" |
| 58 | + if [ -n "$skeema_file" ] ; then |
| 59 | + echo "$skeema_file" > /tmp/skeema-ci/skeema-file |
| 60 | + dirname "$skeema_file" > /tmp/skeema-ci/skeema-dir |
| 61 | +
|
| 62 | + echo "skeema file is $skeema_file" |
| 63 | + fi |
| 64 | + - name: Fetch skeema |
| 65 | + run: | |
| 66 | + if [ ! -f /tmp/skeema-ci/skeema-file ] ; then |
| 67 | + exit 0 |
| 68 | + fi |
| 69 | + ( |
| 70 | + mkdir -p /tmp/skeema-ci |
| 71 | + cd /tmp/skeema-ci |
| 72 | +
|
| 73 | + curl -s -L https://github.com/github/skeema/releases/download/v1.3.0-gh/skeema_1.3.0_linux_amd64.tar.gz > skeema.tar.gz |
| 74 | + tar xzf skeema.tar.gz skeema |
| 75 | + ) |
| 76 | + - name: Fetch hub |
| 77 | + run: | |
| 78 | + ( |
| 79 | + mkdir -p /tmp/skeema-ci |
| 80 | + cd /tmp/skeema-ci |
| 81 | + curl -s -L https://github.com/github/hub/releases/download/v2.12.3/hub-linux-amd64-2.12.3.tgz > hub-linux-amd64-2.12.3.tgz |
| 82 | + tar xzf hub-linux-amd64-2.12.3.tgz hub-linux-amd64-2.12.3/bin/hub |
| 83 | + mv hub-linux-amd64-2.12.3/bin/hub hub |
| 84 | + ) |
| 85 | + - name: push master schema to MySQL |
| 86 | + # The data in MySQL persists throughout the steps on the job. |
| 87 | + env: |
| 88 | + MYSQL_PWD: root |
| 89 | + run: | |
| 90 | + if [ ! -f /tmp/skeema-ci/skeema-file ] ; then |
| 91 | + exit 0 |
| 92 | + fi |
| 93 | + cd $(cat /tmp/skeema-ci/skeema-dir) |
| 94 | + /tmp/skeema-ci/skeema push skeema-diff-ci |
| 95 | + - uses: actions/checkout@v2 |
| 96 | + with: |
| 97 | + ref: ${{ github.event.pull_request.head.sha }} |
| 98 | + - name: skeema diff |
| 99 | + # Now that we've checked out the PR's branch, we can compare the schema in our PR to that we pushed into MySQL. |
| 100 | + # |
| 101 | + # `skeema push skeema-diff-ci --ddl-wrapper` is a hack to add special markers before & after each DDL statement. If curious, |
| 102 | + # see discussion on https://github.com/skeema/skeema/pull/98 |
| 103 | + # TL;DR the `push` does not actually make changes, just generates output. |
| 104 | + # skeefree later uses those special markers to reliably identify the DDL statements. |
| 105 | + env: |
| 106 | + MYSQL_PWD: root |
| 107 | + run: | |
| 108 | + if [ ! -f /tmp/skeema-ci/skeema-file ] ; then |
| 109 | + touch /tmp/skeema-ci/skeema-diff.sql |
| 110 | + exit 0 |
| 111 | + fi |
| 112 | + set -o pipefail |
| 113 | + cd $(cat /tmp/skeema-ci/skeema-dir) |
| 114 | + /tmp/skeema-ci/skeema push skeema-diff-ci --allow-unsafe --ddl-wrapper='echo "\n-- skeema:ddl:begin\n"{DDL}";\n-- skeema:ddl:end"' | sed -e 's/^USE /-- skeema:ddl:use /g' | sed -n '/^-- skeema:ddl:use /p;/^-- skeema:ddl:begin/,/^-- skeema:ddl:end/p' | tee /tmp/skeema-ci/skeema-diff.sql |
| 115 | + - name: validate diff |
| 116 | + run: | |
| 117 | + count_schemas_changed="$(egrep -c '^-- skeema:ddl:use' /tmp/skeema-ci/skeema-diff.sql || :)" |
| 118 | + echo "diff validation: ${count_schemas_changed} schemas changed." |
| 119 | +
|
| 120 | + if [ $count_schemas_changed -gt 1 ] ; then |
| 121 | + echo "Multiple schemas changed. Not supported!" |
| 122 | + exit 1 |
| 123 | + fi |
| 124 | + - name: check label |
| 125 | + # See if `migration:skeema:diff` already exists. This helps with later bookkeeping. |
| 126 | + env: |
| 127 | + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
| 128 | + GITHUB_USER: github |
| 129 | + PR_NUMBER: ${{ github.event.pull_request.number }} |
| 130 | + run: | |
| 131 | + /tmp/skeema-ci/hub api "repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/labels" | jq '.[].name' -r | grep -q "migration:skeema:diff" && touch /tmp/skeema-ci/skeema-diff-label-detected.hint || : |
| 132 | + - name: validate migration:skeefree:queued status |
| 133 | + # Did this commit arrive after ` migration:skeefree:queued` label has been assigned? Not good -- we're already committed to this migration. |
| 134 | + # Otherwise, this new commit may contain new schema changes, or remvoe old ones; we remove the `migration:skeefree:detected` label and later may add it. |
| 135 | + env: |
| 136 | + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
| 137 | + GITHUB_USER: github |
| 138 | + PR_NUMBER: ${{ github.event.pull_request.number }} |
| 139 | + run: | |
| 140 | + if ! /tmp/skeema-ci/hub api "repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/labels" | jq '.[].name' -r | grep -q "migration:skeefree:queued" ; then |
| 141 | + # "migration:skeefree:queued" label not found. We're still able to make changes. |
| 142 | +
|
| 143 | + # Aggressively remove 'migration:skeefree:detected' label. Reasoning is that a new commit came in. |
| 144 | + # Maybe that commit changed the schema, maybe not. We'll be on the safe side and cause `skeefree` to detect the change again. |
| 145 | +
|
| 146 | + /tmp/skeema-ci/hub api --method DELETE "repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/labels/migration:skeefree:detected" || : |
| 147 | +
|
| 148 | + exit 0 |
| 149 | + fi |
| 150 | +
|
| 151 | + # "migration:skeefree:queued" label found. This means we're commited to this migration. We warn about further commits. |
| 152 | +
|
| 153 | + curl -X POST -u ${GITHUB_USER}:${GITHUB_TOKEN} \ |
| 154 | + "https://api.github.com/repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/labels" \ |
| 155 | + -H "Content-type: application/json" -H "Accept: application/json" -d '["migration:late:commits"]' |
| 156 | +
|
| 157 | + message="This migration has already been queued, and should not see any further commits" |
| 158 | + /tmp/skeema-ci/hub api "repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" --raw-field "body=$message" |
| 159 | + echo "$message" |
| 160 | +
|
| 161 | + exit 1 |
| 162 | + - name: label PR |
| 163 | + # Is there a schema diff? Was there previously a diff? Indicate the situation with labels. |
| 164 | + # Also, generate flag files to be used later for bookkeeping. |
| 165 | + env: |
| 166 | + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
| 167 | + GITHUB_USER: github |
| 168 | + PR_NUMBER: ${{ github.event.pull_request.number }} |
| 169 | + run: | |
| 170 | + skeema_diff="$(cat /tmp/skeema-ci/skeema-diff.sql)" |
| 171 | + if [ -n "$skeema_diff" ] ; then |
| 172 | + # There is a schema change |
| 173 | + if [ ! -f /tmp/skeema-ci/skeema-diff-label-detected.hint ] ; then |
| 174 | + curl -X POST -u ${GITHUB_USER}:${GITHUB_TOKEN} \ |
| 175 | + "https://api.github.com/repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/labels" \ |
| 176 | + -H "Content-type: application/json" -H "Accept: application/json" -d '["migration:skeema:diff"]' |
| 177 | + touch /tmp/skeema-ci/skeema-diff-label-created.hint |
| 178 | + fi |
| 179 | + else |
| 180 | + if [ -f /tmp/skeema-ci/skeema-diff-label-detected.hint ] ; then |
| 181 | + # remove existing label |
| 182 | + /tmp/skeema-ci/hub api --method DELETE "repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/labels/migration:skeema:diff" || : |
| 183 | + touch /tmp/skeema-ci/skeema-diff-label-removed.hint |
| 184 | + fi |
| 185 | + fi |
| 186 | + - name: comment PR |
| 187 | + # Do two things: |
| 188 | + # - Add a new comment reflecting the change in this PR (the schema change if any, or lack thereof) |
| 189 | + # - Update the PR body (the original comment) to reflect the change in this PR (the schema change if any, or lack thereof) |
| 190 | + # This requires some manipulation of the body. We don't want to destroy the developer's comment; we append to it, overwriting |
| 191 | + # any previously text generated by this Action. |
| 192 | + env: |
| 193 | + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
| 194 | + GITHUB_USER: github |
| 195 | + PR_NUMBER: ${{ github.event.pull_request.number }} |
| 196 | + run: | |
| 197 | + skeema_diff="$(cat /tmp/skeema-ci/skeema-diff.sql)" |
| 198 | +
|
| 199 | + if [ -z "${skeema_diff}" ] && [ ! -f /tmp/skeema-ci/skeema-diff-label-detected.hint ] ; then |
| 200 | + # There is no schema change; there hasn't been one previously. |
| 201 | + exit 0 |
| 202 | + fi |
| 203 | +
|
| 204 | + magic_comment_hint="<!-- skeema:magic:comment -->" |
| 205 | + magic_comment_id=$(/tmp/skeema-ci/hub api "repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments?per_page=100" | jq -r ".[] | select(.body | startswith(\"${magic_comment_hint}\")) | .id" | head -n 1) |
| 206 | +
|
| 207 | + if [ -f /tmp/skeema-ci/skeema-diff-label-removed.hint ] ; then |
| 208 | + # There used to be a schema change, now there isn't |
| 209 | + comment_body="$(printf -- "$magic_comment_hint\ndiff cleared")" |
| 210 | + else |
| 211 | + # There is a skeema diff |
| 212 | + comment_body="$(printf -- "$magic_comment_hint\n-- skeema:diff\n$skeema_diff")" |
| 213 | + fi |
| 214 | +
|
| 215 | + if [ -z "$magic_comment_id" ] ; then |
| 216 | + # First time, add new comment |
| 217 | + /tmp/skeema-ci/hub api "repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" --raw-field "body=${comment_body}" |
| 218 | + else |
| 219 | + # Magic comment exists. Edit it. |
| 220 | + /tmp/skeema-ci/hub api --method PATCH "repos/${GITHUB_REPOSITORY}/issues/comments/${magic_comment_id}" --raw-field "body=${comment_body}" |
| 221 | + fi |
| 222 | +
|
| 223 | + - name: comment suggestion for review |
| 224 | + env: |
| 225 | + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} |
| 226 | + GITHUB_USER: github |
| 227 | + PR_NUMBER: ${{ github.event.pull_request.number }} |
| 228 | + run: | |
| 229 | + skeema_diff="$(cat /tmp/skeema-ci/skeema-diff.sql)" |
| 230 | +
|
| 231 | + if [ -z "${skeema_diff}" ] ; then |
| 232 | + exit 0 |
| 233 | + fi |
| 234 | +
|
| 235 | + magic_comment_hint="<!-- skeema:magic:review:comment -->" |
| 236 | + magic_comment_id=$(/tmp/skeema-ci/hub api "repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments?per_page=100" | jq -r ".[] | select(.body | startswith(\"${magic_comment_hint}\")) | .id" | head -n 1) |
| 237 | +
|
| 238 | + if [ -z "$magic_comment_id" ] ; then |
| 239 | + author="$(/tmp/skeema-ci/hub api "repos/${GITHUB_REPOSITORY}/pulls/${PR_NUMBER}" | jq -r '.user.login')" |
| 240 | + comment_body="$(printf -- "$magic_comment_hint\n@${author} it looks like you are making schema changes. When ready for production review & migration, please add the label \`migration:for:review\`.")" |
| 241 | + /tmp/skeema-ci/hub api "repos/${GITHUB_REPOSITORY}/issues/${PR_NUMBER}/comments" --raw-field "body=${comment_body}" |
| 242 | + fi |
0 commit comments