From f9e20e777076145c24fe4f52add2f19b10319663 Mon Sep 17 00:00:00 2001 From: Earl Warren Date: Mon, 27 May 2024 16:57:05 +0200 Subject: [PATCH] actions: add automerge example --- .editorconfig | 9 +- actions/actions.sh | 2 +- .../.forgejo/workflows/test.yml | 13 +++ actions/example-automerge/run.sh | 69 +++++++++++ actions/example-automerge/setup.sh | 1 + lib/api.sh | 108 ++++++++++++++++++ lib/lib.sh | 2 + 7 files changed, 202 insertions(+), 2 deletions(-) create mode 100644 actions/example-automerge/.forgejo/workflows/test.yml create mode 100755 actions/example-automerge/run.sh create mode 100755 actions/example-automerge/setup.sh create mode 100644 lib/api.sh diff --git a/.editorconfig b/.editorconfig index c17e595..669640d 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,3 +1,10 @@ root = true + [*] -tab_width: 8 +indent_style = space +indent_size = 4 +tab_width = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/actions/actions.sh b/actions/actions.sh index 32b110b..dd38510 100755 --- a/actions/actions.sh +++ b/actions/actions.sh @@ -114,7 +114,7 @@ function test_actions() { fi if dpkg --compare-versions $version ge 7.1 ; then - for example in post-7-0-schedule ; do + for example in automerge post-7-0-schedule ; do run actions_verify_example $example done fi diff --git a/actions/example-automerge/.forgejo/workflows/test.yml b/actions/example-automerge/.forgejo/workflows/test.yml new file mode 100644 index 0000000..3ce5da0 --- /dev/null +++ b/actions/example-automerge/.forgejo/workflows/test.yml @@ -0,0 +1,13 @@ +on: + pull_request: + +jobs: + test: + runs-on: docker + container: + image: code.forgejo.org/oci/node:20-bookworm + options: "--volume /srv/example:/srv/example" + + steps: + - run: | + ${{ vars.SCRIPT }} diff --git a/actions/example-automerge/run.sh b/actions/example-automerge/run.sh new file mode 100755 index 0000000..e7f5395 --- /dev/null +++ b/actions/example-automerge/run.sh @@ -0,0 +1,69 @@ +TMPDIR=$(mktemp -d) + +trap "rm -fr $TMPDIR" EXIT + +source $EXAMPLE_DIR/../../lib/lib.sh + +api=$url/api/v1 +export d=/srv/example/automerge + +PROOF='some proof' + +function main() { + # + # repository with a pull_request event workflow that always succeeds + # + mkdir -p $d + + forgejo-test-helper.sh push_workflow actions/example-$example $url root example-$example setup-forgejo $token + + local repo=root/example-automerge + + forgejo-curl.sh api_json -X DELETE $api/repos/$repo/actions/variables/SCRIPT >&/dev/null || true + forgejo-curl.sh api_json -X POST --data-raw '{"value":"true"}' $api/repos/$repo/actions/variables/SCRIPT + + ( + cd $d + git clone $url/$repo + cd example-automerge + git checkout -b other + git config user.email root@example.com + git config user.name username + touch file-unique-to-the-pr-branch + echo other $PROOF >>README + git add . + git commit -m 'other change' + git push --force -u origin other + ) + + # + # make sure the runner won't race with the sequence that follows + # + forgejo-runner.sh teardown + # + # create a PR and schedule it for automerge when the workflow succeeds + # + api_pr_delete_all $api $repo + forgejo-curl.sh api_json --data-raw '{"title":"PR title","base":"main","head":"other"}' $api/repos/$repo/pulls >$TMPDIR/pr.json + local pr=$(jq -r .number <$TMPDIR/pr.json) + forgejo-curl.sh api_json --data-raw '{"Do":"merge","merge_when_checks_succeed":true}' $api/repos/$repo/pulls/$pr/merge + if api_pr_is_merged $api $repo $pr; then + echo pull request already merged although it should not be + return 1 + fi + # + # run the workflow + # + forgejo-runner.sh run + local sha=$(api_branch_tip $api $repo other) + api_pr_wait_success $api $repo $sha + # + # verify the PR was automerged + # + if ! retry api_pr_is_merged $api $repo $pr; then + echo pull request is not automerged as expected + return 1 + fi +} + +main diff --git a/actions/example-automerge/setup.sh b/actions/example-automerge/setup.sh new file mode 100755 index 0000000..9476a67 --- /dev/null +++ b/actions/example-automerge/setup.sh @@ -0,0 +1 @@ +mkdir -p /srv/example/automerge diff --git a/lib/api.sh b/lib/api.sh new file mode 100644 index 0000000..b58984a --- /dev/null +++ b/lib/api.sh @@ -0,0 +1,108 @@ +#!/bin/bash +# Copyright 2024 The Forgejo Authors +# SPDX-License-Identifier: MIT + +API_TMPDIR=$(mktemp -d) + +function api_branch_tip() { + local api="$1" + local repo="$2" + local branch="$3" + + retry forgejo-curl.sh api_json $api/repos/$repo/branches/$branch >&/dev/null + forgejo-curl.sh api_json $api/repos/$repo/branches/$branch | jq --raw-output .commit.id +} + +function api_pr_is_merged() { + local api="$1" + local repo="$2" + local pr="$3" + + forgejo-curl.sh api_json $api/repos/$repo/pulls/$pr >$API_TMPDIR/pr.json + $(jq -r .merged <$API_TMPDIR/pr.json) +} + +function api_pr_delete_all() { + local api="$1" + local repo="$2" + + forgejo-curl.sh api_json $api/repos/${repo}/pulls | jq --raw-output '.[] | .number' | while read pr; do + forgejo-curl.sh api_json -X DELETE $api/repos/${repo}/issues/$pr + done +} + +function api_pr_get_status() { + local api="$1" + local repo="$2" + local sha="$3" + + forgejo-curl.sh api_json $api/repos/$repo/commits/$sha/status +} + +function api_pr_check_status() { + local api="$1" + local repo="$2" + local sha="$3" + local expected_status="$4" + local expected_description="$5" + + api_pr_get_status $api $repo $sha >$API_TMPDIR/status.json + local status="$(jq --raw-output .state <$API_TMPDIR/status.json)" + local description="$(jq --raw-output .statuses[0].description <$API_TMPDIR/status.json)" + if test "$status" = "$expected_status" && test -z "$expected_description" -o "$description" = "$expected_description"; then + echo OK + elif test "$status" = "failure" -o "$status" = "success"; then + echo NOK + else + echo RETRY + fi +} + +function api_pr_wait_success() { + api_pr_wait_status success "$@" +} + +function api_pr_wait_failure() { + api_pr_wait_status failure "$@" +} + +function api_pr_wait_running() { + api_pr_wait_status pending "$@" "Has started running" +} + +function api_pr_wait_log() { + local sha="$1" expected_status="$2" expected_description="$3" + local status="$(jq --raw-output .state <$API_TMPDIR/status.json)" + local description="$(jq --raw-output .statuses[0].description <$API_TMPDIR/status.json)" + if test "$expected_description"; then + expected_description=" '$expected_description'" + fi + log_info "$sha status waiting '$expected_status'$expected_description, currently '$status' '$description'" +} + +# default loop delay is 3600 sec (1 hour) +: ${API_LOOPS:=100} +: ${API_LOOP_DELAY:=36} + +function api_pr_wait_status() { + local status="$1" + local api="$2" + local repo="$3" + local sha="$4" + local description="$5" + + for i in $(seq $API_LOOPS); do + if test $(api_pr_check_status "$api" "$repo" "$sha" "$status" "$description") != RETRY; then + break + fi + api_pr_wait_log "$sha" "$status" "$description" + sleep $API_LOOP_DELAY + done + if test $(api_pr_check_status "$api" "$repo" "$sha" "$status" "$description") = "OK"; then + log_info "$sha status OK" + else + api_pr_get_status $api $repo $sha | jq .statuses + log_info "$sha status NOK" + return 1 + fi +} diff --git a/lib/lib.sh b/lib/lib.sh index 2daa32e..dc1a319 100644 --- a/lib/lib.sh +++ b/lib/lib.sh @@ -4,6 +4,8 @@ LIB_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +source $LIB_DIR/api.sh + if ${VERBOSE:-false} ; then set -ex PS4='${BASH_SOURCE[0]}:$LINENO: ${FUNCNAME[0]}: '