Add tf-a-lts-patch-stack-watcher.yaml
This job will trigger Allow-CI job, Allow-CI+2, by default,
if a new patch/patch stack is submitted to the lts branches.
It also monitors the existing unmerged patch/patch stack,
if the submit requirement is met, it will merge it. [1]
[1]: https://linaro.atlassian.net/browse/TFC-546
Signed-off-by: Arthur She <arthur.she@linaro.org>
Change-Id: I59e62182573ae4ef67989ccb119806890a30da6c
diff --git a/scripts/tf-a-lts-patch-stack-watcher.sh b/scripts/tf-a-lts-patch-stack-watcher.sh
new file mode 100755
index 0000000..a97af63
--- /dev/null
+++ b/scripts/tf-a-lts-patch-stack-watcher.sh
@@ -0,0 +1,131 @@
+#!/bin/bash
+set -ex
+
+echo "########################################################################"
+echo " Gerrit Environment"
+env |grep '^GERRIT'
+echo "########################################################################"
+SSH_PARAMS="-o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -o PubkeyAcceptedKeyTypes=+ssh-rsa -p 29418 -i ${CI_BOT_KEY}"
+GERRIT_URL="review.trustedfirmware.org"
+GERRIT_CHANGE_URL_BASE=${GERRIT_CHANGE_URL%/*}
+GERRIT_QUERY_PARAMS="--dependencies --current-patch-set --format=JSON change:"
+QUERY_DEPENDENCY_CMD="${SSH_PARAMS} ${CI_BOT_USERNAME}@${GERRIT_URL} gerrit query ${GERRIT_QUERY_PARAMS}"
+ALLOW_CI_COMMENT="Trigger Allow-CI job by ${BUILD_URL}"
+VOTE_ALLOW_CI_CMD="${SSH_PARAMS} ${CI_BOT_USERNAME}@${GERRIT_URL} gerrit review --label ${ALLOW_CI_JOB} -m \"${ALLOW_CI_COMMENT}\" ${GERRIT_PATCHSET_REVISION}"
+SUBMIT_COMMENT="Submit patch by ${BUILD_URL}"
+SUBMIT_CMD="${SSH_PARAMS} ${CI_BOT_USERNAME}@${GERRIT_URL} gerrit review -m \"${SUBMIT_COMMENT}\" --submit"
+err_msg=$(mktemp)
+
+function get_top_patch() {
+ # Get the top of the patch stack
+ # return: change_no,commit_revision
+ local change_no=$1
+
+ patch_info=$(ssh ${QUERY_DEPENDENCY_CMD}${change_no} 2>/dev/null| jq -n 'input')
+ revision=$(echo ${patch_info} | jq -r '.currentPatchSet.revision')
+ ret=${change_no},${revision}
+
+ neededBy=$(echo ${patch_info} | jq -c 'select(.neededBy)')
+ while [ -n "${neededBy}" ];
+ do
+ change_no=$(echo ${neededBy} | jq -r '.neededBy[0].number')
+ revision=$(echo ${neededBy} | jq -r '.neededBy[0].revision')
+ ret=${change_no},${revision}
+ neededBy=$(ssh ${QUERY_DEPENDENCY_CMD}${change_no} 2>/dev/null | jq -c 'select(.neededBy)')
+ done
+
+ echo ${ret}
+}
+
+function check_ok_to_submit() {
+ # Check if this patch meets the submit requirement
+ # The submit requirement is
+ # Verified: 1
+ # Code-Owner-Review: 1
+ # Maintainer-Review: 1
+ # return:
+ # "True": if the submit requirement is met
+ # "False: if the submit requirement is not met
+ # "MERGED": if the patch has been merged
+ local change_no=$1
+
+ patch_info=$(ssh ${QUERY_DEPENDENCY_CMD}${change_no} 2>/dev/null | jq -n 'input')
+ patch_votes=$(echo ${patch_info} | jq -c 'select(.currentPatchSet.approvals)')
+
+ if [ $(echo ${patch_info} | jq -r '.status') == "MERGED" ]; then
+ echo "MERGED"
+ elif test -n "${patch_votes}" &&
+ jq -e '.currentPatchSet.approvals[] | select(.type == "Code-Owner-Review" and .value == "1")' <<< "${patch_votes}" > /dev/null &&
+ jq -e '.currentPatchSet.approvals[] | select(.type == "Maintainer-Review" and .value == "1")' <<< "${patch_votes}" > /dev/null &&
+ jq -e '.currentPatchSet.approvals[] | select(.type == "Verified" and .value == "1")' <<< "${patch_votes}" > /dev/null; then
+ echo "True"
+ else
+ echo "${GERRIT_CHANGE_URL_BASE}/${change_no} doesn't meet the submit requirement" >> /dev/stderr
+ echo "False"
+ fi
+}
+
+function submit_patch_stack() {
+ # Check the whole patch stack from top to bottom
+ # to see if all patches meet the submit requirements
+ local change_no=$1
+ local can_merge="True"
+
+ top_patch=$(get_top_patch ${change_no})
+ top_patch_no=$(echo ${top_patch} | cut -d ',' -f 1)
+ top_patch_rev=$(echo ${top_patch} | cut -d ',' -f 2)
+
+ patch_to_be_checked=${top_patch_no}
+ while [ -n "${patch_to_be_checked}" ];
+ do
+ set +x # disable debugging log to prevent comtaminate the message we want to keep
+ can_submit=$(check_ok_to_submit ${patch_to_be_checked} 2>> ${err_msg})
+ set -x
+ # The patch that we just checked was merged, exit the loop
+ [ "${can_submit}" == "MERGED" ] && break
+ [ "${can_merge}" == "True" ] && can_merge=${can_submit}
+ patch_to_be_checked=$(ssh ${QUERY_DEPENDENCY_CMD}${patch_to_be_checked} 2>/dev/null | jq 'select(.dependsOn) | .dependsOn[].number' )
+ done
+ if [ "${can_merge}" == "True" ]; then
+ # Check the patch stack agein to ensure it hasn't been merged yet
+ if [ $(ssh ${QUERY_DEPENDENCY_CMD}${top_patch_no} | jq -r 'select(.status)|.status') != "MERGED" ];then
+ echo "The whole patch stack meets the submit requirements, merge it"
+ ssh ${SUBMIT_CMD} ${top_patch_rev} 2> /dev/null
+ else
+ echo "The whole patch stack has been merged!"
+ fi
+ else
+ echo "The patch stack can not be merged!"
+ cat ${err_msg}
+ fi
+}
+
+function trigger_allow_ci_on_top_of_patch_stack() {
+ # This function will set ${ALLOW_CI_JOB} on the
+ # top of the patch stack or a single patch
+ local change_no=$1
+
+ neededBy=$(ssh ${QUERY_DEPENDENCY_CMD}${change_no} 2>/dev/null | jq -c 'select(.neededBy)')
+ if [ -z "${neededBy}" ]; then
+ echo -e "Trigger Allow-CI job on ${GERRIT_CHANGE_URL}"
+ ssh ${VOTE_ALLOW_CI_CMD} 2>/dev/null
+ else
+ echo "This patch is not on the top of a patch stack"
+ fi
+}
+
+case ${GERRIT_EVENT_TYPE} in
+ "comment-added")
+ echo "Triggered by comment-added"
+ # Check eack patch. if all patches meet the submit requirements
+ # merge it
+ submit_patch_stack ${GERRIT_CHANGE_NUMBER}
+ ;;
+ "patchset-created")
+ echo "Triggered by patchset-created"
+ # New patch / patch stack is created
+ # Set Allow-CI on the top of it
+ trigger_allow_ci_on_top_of_patch_stack ${GERRIT_CHANGE_NUMBER}
+ ;;
+esac
+rm -f ${err_msg}
diff --git a/tf-a-lts-patch-stack-watcher.yaml b/tf-a-lts-patch-stack-watcher.yaml
new file mode 100644
index 0000000..350ea32
--- /dev/null
+++ b/tf-a-lts-patch-stack-watcher.yaml
@@ -0,0 +1,62 @@
+- job:
+ name: tf-a-lts-patch-stack-watcher
+ node: master
+ project-type: freestyle
+ concurrent: false
+ description:
+ This job will trigger Allow-CI job whenever a new patch is created.
+ It also monitors all patches, if submit requirement is met, it
+ will merge the patch
+ disabled: false
+ parameters:
+ - string:
+ name: ALLOW_CI_JOB
+ description: |
+ To trigger Allow-CI+1 or Allow-CI+2 job when a new patch is created
+ default: 'Allow-CI=2'
+ builders:
+ - shell:
+ !include-raw: scripts/tf-a-lts-patch-stack-watcher.sh
+ properties:
+ - build-discarder:
+ days-to-keep: 60
+ num-to-keep: 120
+ triggers:
+ - gerrit:
+ silent: true
+ server-name: 'review.trustedfirmware.org'
+ projects:
+ - branches:
+ - branch-compare-type: REG_EXP
+ branch-pattern: 'lts-v.*'
+ project-compare-type: PLAIN
+ project-pattern: 'TF-A/trusted-firmware-a'
+ trigger-on:
+ - comment-added-event:
+ approval-category: "Verified"
+ approval-value: 1
+ - comment-added-event:
+ approval-category: "Code-Owner-Review"
+ approval-value: 1
+ - comment-added-event:
+ approval-category: "Allow-CI"
+ approval-value: 1
+ - comment-added-event:
+ approval-category: "Maintainer-Review"
+ approval-value: 1
+ - patchset-created-event:
+ exclude-drafts: false
+ exclude-trivial-rebase: false
+ exclude-no-code-change: false
+ exclude-private: false
+ exclude-wip: false
+ wrappers:
+ - timestamps
+ - credentials-binding:
+ - ssh-user-private-key:
+ credential-id: TFA_CI_BOT_USER_SSH_KEY
+ key-file-variable: CI_BOT_KEY
+ username-variable: CI_BOT_USERNAME
+ passphrase-variable: ''
+ publishers:
+ - workspace-cleanup