From 08a6739af5d6ff60baaf52ef19b738557068f620 Mon Sep 17 00:00:00 2001 From: Pawel Pasterz Date: Wed, 25 Nov 2020 16:12:52 +0100 Subject: [PATCH 01/14] Implement cron job for full suite IT --- .github/it_failed_template.md | 10 +++ .../full_suite_integration_tests_cron.yml | 62 +++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 .github/it_failed_template.md create mode 100644 .github/workflows/full_suite_integration_tests_cron.yml diff --git a/.github/it_failed_template.md b/.github/it_failed_template.md new file mode 100644 index 0000000000..0c714817a7 --- /dev/null +++ b/.github/it_failed_template.md @@ -0,0 +1,10 @@ +--- +name: Full suite IT test report +about: Report failed IT tests +title: Full Suite integration tests failed on master [{{ env.RUN_DATE }}] +labels: bug +--- +### Integration Test failed on master +**Timestamp:** {{ env.RUN_DATE }} +**Buildscan url for [{{ env.RUN_ID }}](https://github.com/Flank/flank/actions/runs/{{ env.RUN_ID }})** +{{ env.BUILD_SCAN_URL }} diff --git a/.github/workflows/full_suite_integration_tests_cron.yml b/.github/workflows/full_suite_integration_tests_cron.yml new file mode 100644 index 0000000000..251f940434 --- /dev/null +++ b/.github/workflows/full_suite_integration_tests_cron.yml @@ -0,0 +1,62 @@ +name: Full Suite Integration Tests Cron + +on: + schedule: + - cron: '0 0 * * *' # At 00:00 everyday + workflow_dispatch: # or manually + +jobs: + run_it_full_suite: + runs-on: ubuntu-latest + steps: + - name: Cancel Previous Runs + uses: styfle/cancel-workflow-action@0.5.0 + with: + access_token: '${{ secrets.GITHUB_TOKEN }}' + + - name: Checkout code + uses: actions/checkout@v2 + with: + submodules: true + + - uses: actions/cache@v2 + with: + path: ~/.gradle/caches + key: ubuntu-gradle-${{ hashFiles('**/*.gradle*') }} + restore-keys: | + ubuntu-gradle- + + - name: Gradle integration tests + uses: eskatos/gradle-command-action@v1 + id: build + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + HEAD_REF: ${{ github.ref }} + with: + arguments: "clean build -x test" + + - name: Get current time + uses: 1466587594/get-current-time@v2 + id: current-time + with: + format: 'YYYY-MM-DD HH:mm' + utcOffset: "+00:00" + + - name: Gradle integration tests + uses: eskatos/gradle-command-action@v1 + id: run_it + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + HEAD_REF: ${{ github.ref }} + with: + arguments: "integrationTests" + + - uses: JasonEtco/create-an-issue@v2 + if: failure() + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RUN_DATE: ${{ steps.current-time.outputs.formattedTime }} + RUN_ID: ${{ github.run_id }} + BUILD_SCAN_URL: ${{ steps.run_it.outputs.build-scan-url }} + with: + filename: .github/it_failed_template.md From adb62bea82e06fe3bce61d0a1677a373d0924098 Mon Sep 17 00:00:00 2001 From: Pawel Pasterz Date: Thu, 26 Nov 2020 12:19:29 +0100 Subject: [PATCH 02/14] Implement feture to skip issue creation if there is at least alredy opened --- .github/it_failed_template.md | 2 +- .../full_suite_integration_tests_cron.yml | 14 +++++++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/.github/it_failed_template.md b/.github/it_failed_template.md index 0c714817a7..ae6168cb47 100644 --- a/.github/it_failed_template.md +++ b/.github/it_failed_template.md @@ -2,7 +2,7 @@ name: Full suite IT test report about: Report failed IT tests title: Full Suite integration tests failed on master [{{ env.RUN_DATE }}] -labels: bug +labels: bug, IT_Failed --- ### Integration Test failed on master **Timestamp:** {{ env.RUN_DATE }} diff --git a/.github/workflows/full_suite_integration_tests_cron.yml b/.github/workflows/full_suite_integration_tests_cron.yml index 251f940434..696f11b671 100644 --- a/.github/workflows/full_suite_integration_tests_cron.yml +++ b/.github/workflows/full_suite_integration_tests_cron.yml @@ -51,8 +51,20 @@ jobs: with: arguments: "integrationTests" + - name: Check for opened issues + if: ${{ failure() }} + id: check-opened + run: | + resp=$(curl -H "Accept: application/vnd.github.v3+json" "https://api.github.com/repos/Flank/flank/issues?creator=github-actions[bot]&state=open&labels=IT_Failed" | jq .) + echo $resp + SHOULD_RUN='false' + if [ $resp = "[]" ]; then + SHOULD_RUN='true' + fi + echo "::set-output name=should_create::${SHOULD_RUN}" + - uses: JasonEtco/create-an-issue@v2 - if: failure() + if: ${{ failure() && steps.check-opened.outputs.should_create == 'true' }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} RUN_DATE: ${{ steps.current-time.outputs.formattedTime }} From 2777aa952b0abab3992f3452509499d23f5fc849 Mon Sep 17 00:00:00 2001 From: Pawel Pasterz Date: Tue, 1 Dec 2020 09:26:30 +0100 Subject: [PATCH 03/14] Refactor --- .github/it_failed_template.md | 10 - .../full_suite_integration_tests_cron.yml | 173 +++++++++++++++--- 2 files changed, 145 insertions(+), 38 deletions(-) delete mode 100644 .github/it_failed_template.md diff --git a/.github/it_failed_template.md b/.github/it_failed_template.md deleted file mode 100644 index ae6168cb47..0000000000 --- a/.github/it_failed_template.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -name: Full suite IT test report -about: Report failed IT tests -title: Full Suite integration tests failed on master [{{ env.RUN_DATE }}] -labels: bug, IT_Failed ---- -### Integration Test failed on master -**Timestamp:** {{ env.RUN_DATE }} -**Buildscan url for [{{ env.RUN_ID }}](https://github.com/Flank/flank/actions/runs/{{ env.RUN_ID }})** -{{ env.BUILD_SCAN_URL }} diff --git a/.github/workflows/full_suite_integration_tests_cron.yml b/.github/workflows/full_suite_integration_tests_cron.yml index 696f11b671..25487cc756 100644 --- a/.github/workflows/full_suite_integration_tests_cron.yml +++ b/.github/workflows/full_suite_integration_tests_cron.yml @@ -1,13 +1,16 @@ -name: Full Suite Integration Tests Cron +name: Full Suite Integration Tests on: schedule: - - cron: '0 0 * * *' # At 00:00 everyday - workflow_dispatch: # or manually + - cron: '0 0 * * 1-5' # At 00:00 on every day-of-week from Monday through Friday + workflow_dispatch: # or manually jobs: - run_it_full_suite: + run-it-full-suite: runs-on: ubuntu-latest + outputs: + build-scan-url: ${{ steps.run-it.outputs.build-scan-url }} + it-status: ${{ steps.status.outputs.it-status }} steps: - name: Cancel Previous Runs uses: styfle/cancel-workflow-action@0.5.0 @@ -26,7 +29,7 @@ jobs: restore-keys: | ubuntu-gradle- - - name: Gradle integration tests + - name: Gradle clean build uses: eskatos/gradle-command-action@v1 id: build env: @@ -35,40 +38,154 @@ jobs: with: arguments: "clean build -x test" - - name: Get current time - uses: 1466587594/get-current-time@v2 - id: current-time - with: - format: 'YYYY-MM-DD HH:mm' - utcOffset: "+00:00" - - name: Gradle integration tests uses: eskatos/gradle-command-action@v1 - id: run_it + id: run-it env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} HEAD_REF: ${{ github.ref }} with: arguments: "integrationTests" + - name: Set status + if: always() + id: status + run: | + echo "::set-output name=it-status::${{ job.status }}" + + prepare-message: + if: always() + runs-on: ubuntu-latest + needs: [ run-it-full-suite ] + outputs: + issue-number: ${{ steps.check-opened.outputs.issue-number }} + message-body: ${{ steps.body.outputs.body }} + next-step: ${{ steps.next-step.outputs.next-step }} + steps: + - name: Get current time + uses: 1466587594/get-current-time@v2 + id: current-time + with: + format: 'YYYY-MM-DD HH:mm' + utcOffset: "+00:00" + - name: Check for opened issues - if: ${{ failure() }} id: check-opened run: | - resp=$(curl -H "Accept: application/vnd.github.v3+json" "https://api.github.com/repos/Flank/flank/issues?creator=github-actions[bot]&state=open&labels=IT_Failed" | jq .) - echo $resp - SHOULD_RUN='false' - if [ $resp = "[]" ]; then - SHOULD_RUN='true' + resp=$(curl -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" "https://api.github.com/repos/Flank/flank/issues?creator=github-actions[bot]&state=open&labels=IT_Failed" | jq '.[0] | .number' -r) + echo "::set-output name=issue-number::${resp}" + + - name: Date of last run + id: last-run-date + run: | + resp=$(curl -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" "https://api.github.com/repos/Flank/flank/actions/workflows/it_cron.yml/runs?per_page=2&page=1" | jq ' [.workflow_runs | .[] | select(.status != "in_progress") | select(.conclusion != "cancelled")][0] | .created_at' -r) + echo "::set-output name=last-run-date::${resp}" + + - name: Create failed message + if: ${{ needs.run-it-full-suite.outputs.it-status == 'failure' }} + run: | + echo "### Full suite IT run :x: FAILED :x:" >> message.txt + echo "**Timestamp:** ${{ steps.current-time.outputs.formattedTime }}" >> message.txt + echo "**Job run:** [${{ github.run_id }}](https://github.com/Flank/flank/actions/runs/${{ github.run_id }})" >> message.txt + echo "**Build scan URL:** ${{ needs.run-it-full-suite.outputs.build-scan-url }}" >> message.txt + echo "**Commits since the last run:**" >> message.txt + list=$(curl -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" "https://api.github.com/repos/Flank/flank/commits?since=${{ steps.last-run-date.outputs.last-run-date }}" | jq ' .[] | .sha' -r) + if [ -z "$list" ] + then + echo "No new commits" >> message.txt + else + echo "|commit SHA|PR|" >> message.txt + echo "|---|:---:|" >> message.txt + while IFS= read -r commit; do + resp=$(curl -H "Accept: application/vnd.github.groot-preview+json" -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" "https://api.github.com/repos/Flank/flank/commits/${commit}/pulls" | jq '.[] | {html_url, title}') + if [ -z "$resp" ]; then + echo "|${commit}|-|" >> message.txt + else + echo "|${commit}|[PR-$(echo $resp | jq .title -r)]($(echo $resp | jq .html_url -r))|" >> message.txt + fi + done <<< "$list" fi - echo "::set-output name=should_create::${SHOULD_RUN}" - - uses: JasonEtco/create-an-issue@v2 - if: ${{ failure() && steps.check-opened.outputs.should_create == 'true' }} - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - RUN_DATE: ${{ steps.current-time.outputs.formattedTime }} - RUN_ID: ${{ github.run_id }} - BUILD_SCAN_URL: ${{ steps.run_it.outputs.build-scan-url }} + - name: Create success message + if: ${{ needs.run-it-full-suite.outputs.it-status == 'success' }} + run: | + echo "### Full suite IT run :white_check_mark: SUCCEEDED :white_check_mark:" >> message.txt + echo "**Timestamp:** ${{ steps.current-time.outputs.formattedTime }}" >> message.txt + echo "**Job run:** [${{ github.run_id }}](https://github.com/Flank/flank/actions/runs/${{ github.run_id }})" >> message.txt + echo "**Build scan URL:** ${{ needs.run-it-full-suite.outputs.build-scan-url }}" >> message.txt + echo "**Closing issue**" >> message.txt + + - name: Make message body + id: body + run: | + body=$(cat message.txt) + body="${body//'%'/'%25'}" + body="${body//$'\n'/'%0A'}" + body="${body//$'\r'/'%0D'}" + echo "::set-output name=body::${body}" + + - name: Decide on next step + id: next-step + run: | + next_step='' + status=${{ needs.run-it-full-suite.outputs.it-status }} + issue_number=${{ steps.check-opened.outputs.issue-number }} + if [ "$status" == 'success' ]; then + if [ "$issue_number" == null ]; then + next_step='do-nothing' + else + next_step='close' + fi + else + if [ "$issue_number" == null ]; then + next_step='create' + else + next_step='comment' + fi + fi + echo "::set-output name=next-step::${next_step}" + + create-issue: + needs: [ prepare-message ] + if: always() && needs.prepare-message.outputs.next-step == 'create' + runs-on: ubuntu-latest + steps: + - name: Prepare template + run: | + echo "### Integration Test failed on master" >> issue.md + + - name: Create Issue From File + id: create-issue + uses: peter-evans/create-issue-from-file@v2 + with: + title: Full Suite integration tests failed on master + content-filepath: ./issue.md + labels: bug, IT_Failed + + - name: Add comment to existing issue + uses: peter-evans/create-or-update-comment@v1 + with: + issue-number: ${{ steps.create-issue.outputs.issue-number }} + body: ${{ needs.prepare-message.outputs.message-body }} + + close-issue: + needs: [ prepare-message ] + if: always() && needs.prepare-message.outputs.next-step == 'close' + runs-on: ubuntu-latest + steps: + - name: Close issue + uses: peter-evans/close-issue@v1 + with: + issue-number: ${{ needs.prepare-message.outputs.issue-number }} + comment: ${{ needs.prepare-message.outputs.message-body }} + + add-comment: + needs: [ prepare-message ] + if: always() && needs.prepare-message.outputs.next-step == 'comment' + runs-on: ubuntu-latest + steps: + - name: Add comment to existing issue + uses: peter-evans/create-or-update-comment@v1 with: - filename: .github/it_failed_template.md + issue-number: ${{ needs.prepare-message.outputs.issue-number }} + body: ${{ needs.prepare-message.outputs.message-body }} From 95d3c8e289740a93857be8058bf99d7f783d7ce7 Mon Sep 17 00:00:00 2001 From: Pawel Pasterz Date: Tue, 1 Dec 2020 14:02:40 +0100 Subject: [PATCH 04/14] Use github context to get repository --- .../workflows/full_suite_integration_tests_cron.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/full_suite_integration_tests_cron.yml b/.github/workflows/full_suite_integration_tests_cron.yml index 25487cc756..5a2f7b9fbe 100644 --- a/.github/workflows/full_suite_integration_tests_cron.yml +++ b/.github/workflows/full_suite_integration_tests_cron.yml @@ -72,13 +72,13 @@ jobs: - name: Check for opened issues id: check-opened run: | - resp=$(curl -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" "https://api.github.com/repos/Flank/flank/issues?creator=github-actions[bot]&state=open&labels=IT_Failed" | jq '.[0] | .number' -r) + resp=$(curl -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" "https://api.github.com/repos/${{ github.repository }}/issues?creator=github-actions[bot]&state=open&labels=IT_Failed" | jq '.[0] | .number' -r) echo "::set-output name=issue-number::${resp}" - name: Date of last run id: last-run-date run: | - resp=$(curl -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" "https://api.github.com/repos/Flank/flank/actions/workflows/it_cron.yml/runs?per_page=2&page=1" | jq ' [.workflow_runs | .[] | select(.status != "in_progress") | select(.conclusion != "cancelled")][0] | .created_at' -r) + resp=$(curl -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" "https://api.github.com/repos/${{ github.repository }}/actions/workflows/it_cron.yml/runs?per_page=2&page=1" | jq ' [.workflow_runs | .[] | select(.status != "in_progress") | select(.conclusion != "cancelled")][0] | .created_at' -r) echo "::set-output name=last-run-date::${resp}" - name: Create failed message @@ -86,10 +86,10 @@ jobs: run: | echo "### Full suite IT run :x: FAILED :x:" >> message.txt echo "**Timestamp:** ${{ steps.current-time.outputs.formattedTime }}" >> message.txt - echo "**Job run:** [${{ github.run_id }}](https://github.com/Flank/flank/actions/runs/${{ github.run_id }})" >> message.txt + echo "**Job run:** [${{ github.run_id }}](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})" >> message.txt echo "**Build scan URL:** ${{ needs.run-it-full-suite.outputs.build-scan-url }}" >> message.txt echo "**Commits since the last run:**" >> message.txt - list=$(curl -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" "https://api.github.com/repos/Flank/flank/commits?since=${{ steps.last-run-date.outputs.last-run-date }}" | jq ' .[] | .sha' -r) + list=$(curl -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" "https://api.github.com/repos/${{ github.repository }}/commits?since=${{ steps.last-run-date.outputs.last-run-date }}" | jq ' .[] | .sha' -r) if [ -z "$list" ] then echo "No new commits" >> message.txt @@ -97,7 +97,7 @@ jobs: echo "|commit SHA|PR|" >> message.txt echo "|---|:---:|" >> message.txt while IFS= read -r commit; do - resp=$(curl -H "Accept: application/vnd.github.groot-preview+json" -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" "https://api.github.com/repos/Flank/flank/commits/${commit}/pulls" | jq '.[] | {html_url, title}') + resp=$(curl -H "Accept: application/vnd.github.groot-preview+json" -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" "https://api.github.com/repos/${{ github.repository }}/commits/${commit}/pulls" | jq '.[] | {html_url, title}') if [ -z "$resp" ]; then echo "|${commit}|-|" >> message.txt else @@ -111,7 +111,7 @@ jobs: run: | echo "### Full suite IT run :white_check_mark: SUCCEEDED :white_check_mark:" >> message.txt echo "**Timestamp:** ${{ steps.current-time.outputs.formattedTime }}" >> message.txt - echo "**Job run:** [${{ github.run_id }}](https://github.com/Flank/flank/actions/runs/${{ github.run_id }})" >> message.txt + echo "**Job run:** [${{ github.run_id }}](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})" >> message.txt echo "**Build scan URL:** ${{ needs.run-it-full-suite.outputs.build-scan-url }}" >> message.txt echo "**Closing issue**" >> message.txt From 8cc1b92a5649cfcc1f162f70332face7c0c71508 Mon Sep 17 00:00:00 2001 From: Pawel Pasterz Date: Mon, 7 Dec 2020 07:38:41 +0100 Subject: [PATCH 05/14] Update GH API --- .../flank/scripts/exceptions/FlankScriptsExceptionMappers.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/flank-scripts/src/main/kotlin/flank/scripts/exceptions/FlankScriptsExceptionMappers.kt b/flank-scripts/src/main/kotlin/flank/scripts/exceptions/FlankScriptsExceptionMappers.kt index 95eda9a178..1658911eb9 100644 --- a/flank-scripts/src/main/kotlin/flank/scripts/exceptions/FlankScriptsExceptionMappers.kt +++ b/flank-scripts/src/main/kotlin/flank/scripts/exceptions/FlankScriptsExceptionMappers.kt @@ -14,6 +14,8 @@ fun Result.ma fun Result.mapClientErrorToGithubException() = mapClientError { it.toGithubException() } +fun Result.mapErrorToGithubException() = mapClientError { it.toGithubException() } + fun FuelError.toGithubException() = GitHubException(response.body().asString(APPLICATION_JSON_CONTENT_TYPE).toObject()) fun FuelError.toBugsnagException() = From b06788ae604dfa03ae8f3ffa204273a4dfdf46f6 Mon Sep 17 00:00:00 2001 From: Pawel Pasterz Date: Mon, 7 Dec 2020 07:42:36 +0100 Subject: [PATCH 06/14] Add integration logic --- ...n.yml => full_suite_integration_tests.yml} | 0 .../src/main/kotlin/flank/scripts/Main.kt | 4 +- .../flank/scripts/integration/CommitList.kt | 33 +++++ .../scripts/integration/IntegrationCommand.kt | 14 +++ .../flank/scripts/integration/IssueList.kt | 22 ++++ .../scripts/integration/PrepareMessage.kt | 49 ++++++++ .../integration/ProcessResultCommand.kt | 115 ++++++++++++++++++ .../scripts/integration/WorkflowSummary.kt | 48 ++++++++ 8 files changed, 284 insertions(+), 1 deletion(-) rename .github/workflows/{full_suite_integration_tests_cron.yml => full_suite_integration_tests.yml} (100%) create mode 100644 flank-scripts/src/main/kotlin/flank/scripts/integration/CommitList.kt create mode 100644 flank-scripts/src/main/kotlin/flank/scripts/integration/IntegrationCommand.kt create mode 100644 flank-scripts/src/main/kotlin/flank/scripts/integration/IssueList.kt create mode 100644 flank-scripts/src/main/kotlin/flank/scripts/integration/PrepareMessage.kt create mode 100644 flank-scripts/src/main/kotlin/flank/scripts/integration/ProcessResultCommand.kt create mode 100644 flank-scripts/src/main/kotlin/flank/scripts/integration/WorkflowSummary.kt diff --git a/.github/workflows/full_suite_integration_tests_cron.yml b/.github/workflows/full_suite_integration_tests.yml similarity index 100% rename from .github/workflows/full_suite_integration_tests_cron.yml rename to .github/workflows/full_suite_integration_tests.yml diff --git a/flank-scripts/src/main/kotlin/flank/scripts/Main.kt b/flank-scripts/src/main/kotlin/flank/scripts/Main.kt index 69a188e2c8..f2135b2b72 100644 --- a/flank-scripts/src/main/kotlin/flank/scripts/Main.kt +++ b/flank-scripts/src/main/kotlin/flank/scripts/Main.kt @@ -4,6 +4,7 @@ import com.github.ajalt.clikt.core.CliktCommand import com.github.ajalt.clikt.core.subcommands import flank.scripts.ci.CiCommand import flank.scripts.dependencies.DependenciesCommand +import flank.scripts.integration.IntegrationCommand import flank.scripts.pullrequest.PullRequestCommand import flank.scripts.release.ReleaseCommand import flank.scripts.shell.ShellCommand @@ -21,6 +22,7 @@ fun main(args: Array) { DependenciesCommand, TestArtifactsCommand(), ShellCommand, - PullRequestCommand + PullRequestCommand, + IntegrationCommand ).main(args) } diff --git a/flank-scripts/src/main/kotlin/flank/scripts/integration/CommitList.kt b/flank-scripts/src/main/kotlin/flank/scripts/integration/CommitList.kt new file mode 100644 index 0000000000..d118f91fe0 --- /dev/null +++ b/flank-scripts/src/main/kotlin/flank/scripts/integration/CommitList.kt @@ -0,0 +1,33 @@ +package flank.scripts.integration + +import com.github.kittinunf.result.getOrElse +import com.github.kittinunf.result.onError +import flank.scripts.github.getGitHubCommitList +import flank.scripts.github.getPrDetailsByCommit +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.flow.asFlow +import kotlinx.coroutines.flow.flatMapMerge +import kotlinx.coroutines.flow.flow +import kotlinx.coroutines.flow.toList +import kotlinx.coroutines.launch + +@FlowPreview +suspend fun getCommitListSinceDate(token: String, since: String) = coroutineScope { + getGitHubCommitList(token, listOf("since" to since)) + .onError { println(it.message) } + .getOrElse { emptyList() } + .asFlow() + .flatMapMerge { + flow { + launch { emit(it.sha to getPrDetailsByCommit(token, it.sha).get()) } + } + } + .flatMapMerge { (commit, prs) -> + flow { + if (prs.isEmpty()) emit(commit to null) + else prs.forEach { emit(commit to it) } + } + } + .toList() +} diff --git a/flank-scripts/src/main/kotlin/flank/scripts/integration/IntegrationCommand.kt b/flank-scripts/src/main/kotlin/flank/scripts/integration/IntegrationCommand.kt new file mode 100644 index 0000000000..e0cb5593c2 --- /dev/null +++ b/flank-scripts/src/main/kotlin/flank/scripts/integration/IntegrationCommand.kt @@ -0,0 +1,14 @@ +package flank.scripts.integration + +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.core.subcommands + +object IntegrationCommand : CliktCommand(name = "integration") { + + init { + subcommands(ProcessResultCommand) + } + + @Suppress("EmptyFunctionBlock") + override fun run() {} +} diff --git a/flank-scripts/src/main/kotlin/flank/scripts/integration/IssueList.kt b/flank-scripts/src/main/kotlin/flank/scripts/integration/IssueList.kt new file mode 100644 index 0000000000..2a43d2685c --- /dev/null +++ b/flank-scripts/src/main/kotlin/flank/scripts/integration/IssueList.kt @@ -0,0 +1,22 @@ +package flank.scripts.integration + +import com.github.kittinunf.result.getOrElse +import com.github.kittinunf.result.onError +import flank.scripts.github.getGitHubIssueList + +suspend fun checkForOpenedITIssues(token: String) = getGitHubIssueList( + githubToken = token, + parameters = listOf( +// "creator" to "github-actions[bot]", + "creator" to "pawelpasterz", + "state" to "open", + "labels" to "IT_Failed" + ) +) + .onError { println(it.message) } + .getOrElse { emptyList() } + .getOrNull(0) + .also { + if (it != null) println("** Issue found: ${it.htmlUrl}") + else println("** No opened issue") + } diff --git a/flank-scripts/src/main/kotlin/flank/scripts/integration/PrepareMessage.kt b/flank-scripts/src/main/kotlin/flank/scripts/integration/PrepareMessage.kt new file mode 100644 index 0000000000..5fedffe690 --- /dev/null +++ b/flank-scripts/src/main/kotlin/flank/scripts/integration/PrepareMessage.kt @@ -0,0 +1,49 @@ +package flank.scripts.integration + +import flank.scripts.github.objects.GithubPullRequest + +private val successTemplate = { lastRun: String, runId: String, url: String -> + """ + |### Full suite IT run :white_check_mark: SUCCEEDED :white_check_mark: + |**Timestamp:** $lastRun + |**Job run:** [$runId](https://github.com/Flank/flank/actions/runs/$runId + |**Build scan URL:** $url + |**Closing issue** +""".trimMargin() +} + +private val failureTemplate = { lastRun: String, runId: String, url: String -> + """ + |### Full suite IT run :x: FAILED :x: + |**Timestamp:** $lastRun + |**Job run:** [$runId](https://github.com/Flank/flank/actions/runs/$runId + |**Build scan URL:** $url +""".trimMargin() +} + +sealed class CommentMessage +object Success : CommentMessage() +object Failure : CommentMessage() + +fun prepareSuccessMessage( + lastRun: String, + runId: String, + url: String +): String = successTemplate(lastRun, runId, url) + +fun prepareFailureMessage( + lastRun: String, + runId: String, + url: String, + prs: List> +): String = buildString { + appendLine(failureTemplate(lastRun, runId, url)) + if (prs.isEmpty()) appendLine("No new commits") + else { + appendLine("|commit SHA|PR|") + appendLine("|---|:---:|") + prs.forEach { (commit, pr) -> + appendLine("|$commit|${pr?.let { "[${it.title}](${it.htmlUrl})" } ?: "---"}") + } + } +} diff --git a/flank-scripts/src/main/kotlin/flank/scripts/integration/ProcessResultCommand.kt b/flank-scripts/src/main/kotlin/flank/scripts/integration/ProcessResultCommand.kt new file mode 100644 index 0000000000..0305a9866d --- /dev/null +++ b/flank-scripts/src/main/kotlin/flank/scripts/integration/ProcessResultCommand.kt @@ -0,0 +1,115 @@ +package flank.scripts.integration + +import com.github.ajalt.clikt.core.CliktCommand +import com.github.ajalt.clikt.parameters.options.option +import com.github.ajalt.clikt.parameters.options.required +import com.github.ajalt.clikt.parameters.types.enum +import com.github.kittinunf.result.getOrNull +import com.github.kittinunf.result.onError +import flank.scripts.github.objects.GitHubCreateIssueCommentRequest +import flank.scripts.github.objects.GitHubCreateIssueRequest +import flank.scripts.github.objects.GitHubUpdateIssueRequest +import flank.scripts.github.objects.IssueState +import flank.scripts.github.patchIssue +import flank.scripts.github.postNewIssue +import flank.scripts.github.postNewIssueComment +import flank.scripts.utils.toJson +import kotlinx.coroutines.FlowPreview +import kotlinx.coroutines.coroutineScope +import kotlinx.coroutines.runBlocking +import kotlin.system.exitProcess + +@FlowPreview +object ProcessResultCommand : CliktCommand(name = "processResults") { + + private val itResult by option(help = "IT run job status", names = arrayOf("--result")) + .enum(ignoreCase = true) + .required() + + private val buildScanURL by option(help = "Gradle build scan URL", names = arrayOf("--url")) + .required() + + private val githubToken by option(help = "Git Token").required() + + private val runID by option(help = "Workflow job ID").required() + + override fun run() { + println( + """ + ** Parameters: + token: $githubToken + result: $itResult + url: $buildScanURL + runID: $runID + """.trimIndent() + ) + runBlocking { + val openedIssue = checkForOpenedITIssues(githubToken) + val lastRun = getLastITWorkflowRunDate(githubToken) + + when { + itResult == ITResults.FAILURE && openedIssue == null -> createNewIssue(lastRun) + itResult == ITResults.FAILURE && openedIssue != null -> postFailureComment(openedIssue.number, lastRun) + itResult == ITResults.SUCCESS && openedIssue != null -> closeIssue(openedIssue.number, lastRun) + else -> return@runBlocking + } + } + } + + private suspend fun createNewIssue(lastRun: String) = coroutineScope { + println("** Creating new issue") + val issuePayload = GitHubCreateIssueRequest( + title = "Full Suite integration tests failed on master", + body = "### Integration Test failed on master", + labels = listOf("IT_Failed", "bug") + ) + println(issuePayload.toJson()) + val issue = postNewIssue(githubToken, issuePayload) + .onError { + println(it.message) + } + .getOrNull() + if (issue == null) { + println("** There were problems with issue creation") + exitProcess(-1) + } + println( + """ + ** Issue created: + url: ${issue.htmlUrl} + number: ${issue.number} + """.trimIndent() + ) + postFailureComment(issue.number, lastRun) + } + + private suspend fun postFailureComment(issueNumber: Int, lastRun: String) = + coroutineScope { postComment(issueNumber, lastRun, Failure) } + + private suspend fun postSuccessComment(issueNumber: Int, lastRun: String) = + coroutineScope { postComment(issueNumber, lastRun, Success) } + + private suspend inline fun postComment(issueNumber: Int, lastRun: String, type: CommentMessage) = coroutineScope { + val message = when (type) { + is Success -> prepareSuccessMessage(lastRun, runID, buildScanURL) + is Failure -> { + val commitList = getCommitListSinceDate(githubToken, lastRun) + prepareFailureMessage(lastRun, runID, buildScanURL, commitList) + } + } + val payload = GitHubCreateIssueCommentRequest(message) + println("** Comment created") + println(payload.toJson()) + postNewIssueComment(githubToken, issueNumber, payload) + } + + private suspend fun closeIssue(issueNumber: Int, lastRun: String) = coroutineScope { + postSuccessComment(issueNumber, lastRun) + println("** Closing issue") + patchIssue(githubToken, issueNumber, GitHubUpdateIssueRequest(state = IssueState.CLOSED)) + } + + private enum class ITResults { + SUCCESS, FAILURE + } +} diff --git a/flank-scripts/src/main/kotlin/flank/scripts/integration/WorkflowSummary.kt b/flank-scripts/src/main/kotlin/flank/scripts/integration/WorkflowSummary.kt new file mode 100644 index 0000000000..bea79fa6e2 --- /dev/null +++ b/flank-scripts/src/main/kotlin/flank/scripts/integration/WorkflowSummary.kt @@ -0,0 +1,48 @@ +package flank.scripts.integration + +import com.github.kittinunf.result.getOrNull +import flank.scripts.github.getGitHubWorkflowRunsSummary +import java.time.Instant +import java.time.format.DateTimeFormatter +import kotlinx.coroutines.coroutineScope + +suspend fun getLastITWorkflowRunDate(token: String) = coroutineScope { + getLastWorkflowRunDate( + token = token, + workflowFileName = "it_cron.yml" + ) +} + +suspend fun getLastWorkflowRunDate( + token: String, + workflowName: String? = null, + workflowFileName: String? = null +): String = getGitHubWorkflowRunsSummary( + githubToken = token, + workflow = requireNotNull( + workflowName ?: workflowFileName + ) { "** Missing either workflow name or workflow file name. Both can not be null" }, + parameters = listOf( + "per_page" to 20, + "page" to 1 + ) +).getOrNull() + ?.run { + workflowRuns + .filter { it.status != "in_progress" } + .filter { it.conclusion != "cancelled" } + .getOrNull(0) + ?.also { + println( + """ + ** Last workflow run: + name: ${it.name} + last run: ${it.createdAt} + url: ${it.htmlUrl} + """.trimIndent() + ) + }?.createdAt.run { DateTimeFormatter.ISO_INSTANT.format(Instant.parse(this)) } + } ?: run { + println("** No workflow run found for ${workflowName ?: workflowFileName}") + DateTimeFormatter.ISO_INSTANT.format(Instant.now()) +} From c615b6a2436b482b8447fcf9e6d601199b3c5a19 Mon Sep 17 00:00:00 2001 From: Pawel Pasterz Date: Thu, 10 Dec 2020 15:24:58 +0100 Subject: [PATCH 07/14] Rewrite to flank scripts --- .../full_suite_integration_tests.yml | 150 +----------------- flank-scripts/build.gradle.kts | 2 +- .../flank/scripts/integration/CommitList.kt | 9 +- .../flank/scripts/integration/Extensions.kt | 66 ++++++++ .../scripts/integration/IntegrationContext.kt | 13 ++ .../flank/scripts/integration/IssueList.kt | 5 +- .../integration/ProcessResultCommand.kt | 120 ++++---------- .../scripts/integration/WorkflowSummary.kt | 11 +- .../flank/scripts/GithubMockServerHandler.kt | 32 +++- .../scripts/integration/ProcessResultTest.kt | 88 ++++++++++ 10 files changed, 247 insertions(+), 249 deletions(-) create mode 100644 flank-scripts/src/main/kotlin/flank/scripts/integration/Extensions.kt create mode 100644 flank-scripts/src/main/kotlin/flank/scripts/integration/IntegrationContext.kt create mode 100644 flank-scripts/src/test/kotlin/flank/scripts/integration/ProcessResultTest.kt diff --git a/.github/workflows/full_suite_integration_tests.yml b/.github/workflows/full_suite_integration_tests.yml index 5a2f7b9fbe..398febc1b4 100644 --- a/.github/workflows/full_suite_integration_tests.yml +++ b/.github/workflows/full_suite_integration_tests.yml @@ -22,6 +22,11 @@ jobs: with: submodules: true + - name: Download flankScripts and add it to PATH + run: | + ./gradlew :flank-scripts:download + echo "./flank-scripts/bash" >> $GITHUB_PATH + - uses: actions/cache@v2 with: path: ~/.gradle/caches @@ -36,7 +41,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} HEAD_REF: ${{ github.ref }} with: - arguments: "clean build -x test" + arguments: "clean assemble" - name: Gradle integration tests uses: eskatos/gradle-command-action@v1 @@ -47,145 +52,6 @@ jobs: with: arguments: "integrationTests" - - name: Set status - if: always() - id: status - run: | - echo "::set-output name=it-status::${{ job.status }}" - - prepare-message: - if: always() - runs-on: ubuntu-latest - needs: [ run-it-full-suite ] - outputs: - issue-number: ${{ steps.check-opened.outputs.issue-number }} - message-body: ${{ steps.body.outputs.body }} - next-step: ${{ steps.next-step.outputs.next-step }} - steps: - - name: Get current time - uses: 1466587594/get-current-time@v2 - id: current-time - with: - format: 'YYYY-MM-DD HH:mm' - utcOffset: "+00:00" - - - name: Check for opened issues - id: check-opened + - name: Process IT results run: | - resp=$(curl -H "Accept: application/vnd.github.v3+json" -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" "https://api.github.com/repos/${{ github.repository }}/issues?creator=github-actions[bot]&state=open&labels=IT_Failed" | jq '.[0] | .number' -r) - echo "::set-output name=issue-number::${resp}" - - - name: Date of last run - id: last-run-date - run: | - resp=$(curl -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" "https://api.github.com/repos/${{ github.repository }}/actions/workflows/it_cron.yml/runs?per_page=2&page=1" | jq ' [.workflow_runs | .[] | select(.status != "in_progress") | select(.conclusion != "cancelled")][0] | .created_at' -r) - echo "::set-output name=last-run-date::${resp}" - - - name: Create failed message - if: ${{ needs.run-it-full-suite.outputs.it-status == 'failure' }} - run: | - echo "### Full suite IT run :x: FAILED :x:" >> message.txt - echo "**Timestamp:** ${{ steps.current-time.outputs.formattedTime }}" >> message.txt - echo "**Job run:** [${{ github.run_id }}](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})" >> message.txt - echo "**Build scan URL:** ${{ needs.run-it-full-suite.outputs.build-scan-url }}" >> message.txt - echo "**Commits since the last run:**" >> message.txt - list=$(curl -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" "https://api.github.com/repos/${{ github.repository }}/commits?since=${{ steps.last-run-date.outputs.last-run-date }}" | jq ' .[] | .sha' -r) - if [ -z "$list" ] - then - echo "No new commits" >> message.txt - else - echo "|commit SHA|PR|" >> message.txt - echo "|---|:---:|" >> message.txt - while IFS= read -r commit; do - resp=$(curl -H "Accept: application/vnd.github.groot-preview+json" -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" "https://api.github.com/repos/${{ github.repository }}/commits/${commit}/pulls" | jq '.[] | {html_url, title}') - if [ -z "$resp" ]; then - echo "|${commit}|-|" >> message.txt - else - echo "|${commit}|[PR-$(echo $resp | jq .title -r)]($(echo $resp | jq .html_url -r))|" >> message.txt - fi - done <<< "$list" - fi - - - name: Create success message - if: ${{ needs.run-it-full-suite.outputs.it-status == 'success' }} - run: | - echo "### Full suite IT run :white_check_mark: SUCCEEDED :white_check_mark:" >> message.txt - echo "**Timestamp:** ${{ steps.current-time.outputs.formattedTime }}" >> message.txt - echo "**Job run:** [${{ github.run_id }}](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }})" >> message.txt - echo "**Build scan URL:** ${{ needs.run-it-full-suite.outputs.build-scan-url }}" >> message.txt - echo "**Closing issue**" >> message.txt - - - name: Make message body - id: body - run: | - body=$(cat message.txt) - body="${body//'%'/'%25'}" - body="${body//$'\n'/'%0A'}" - body="${body//$'\r'/'%0D'}" - echo "::set-output name=body::${body}" - - - name: Decide on next step - id: next-step - run: | - next_step='' - status=${{ needs.run-it-full-suite.outputs.it-status }} - issue_number=${{ steps.check-opened.outputs.issue-number }} - if [ "$status" == 'success' ]; then - if [ "$issue_number" == null ]; then - next_step='do-nothing' - else - next_step='close' - fi - else - if [ "$issue_number" == null ]; then - next_step='create' - else - next_step='comment' - fi - fi - echo "::set-output name=next-step::${next_step}" - - create-issue: - needs: [ prepare-message ] - if: always() && needs.prepare-message.outputs.next-step == 'create' - runs-on: ubuntu-latest - steps: - - name: Prepare template - run: | - echo "### Integration Test failed on master" >> issue.md - - - name: Create Issue From File - id: create-issue - uses: peter-evans/create-issue-from-file@v2 - with: - title: Full Suite integration tests failed on master - content-filepath: ./issue.md - labels: bug, IT_Failed - - - name: Add comment to existing issue - uses: peter-evans/create-or-update-comment@v1 - with: - issue-number: ${{ steps.create-issue.outputs.issue-number }} - body: ${{ needs.prepare-message.outputs.message-body }} - - close-issue: - needs: [ prepare-message ] - if: always() && needs.prepare-message.outputs.next-step == 'close' - runs-on: ubuntu-latest - steps: - - name: Close issue - uses: peter-evans/close-issue@v1 - with: - issue-number: ${{ needs.prepare-message.outputs.issue-number }} - comment: ${{ needs.prepare-message.outputs.message-body }} - - add-comment: - needs: [ prepare-message ] - if: always() && needs.prepare-message.outputs.next-step == 'comment' - runs-on: ubuntu-latest - steps: - - name: Add comment to existing issue - uses: peter-evans/create-or-update-comment@v1 - with: - issue-number: ${{ needs.prepare-message.outputs.issue-number }} - body: ${{ needs.prepare-message.outputs.message-body }} + flankScripts integration processResults --result ${{ job.status }} --url ${{ steps.run-it.outputs.build-scan-url }} --github-token=${{ secrets.GITHUB_TOKEN }} --run-id ${{ github.run_id }} diff --git a/flank-scripts/build.gradle.kts b/flank-scripts/build.gradle.kts index 396957f89a..1a0489f350 100644 --- a/flank-scripts/build.gradle.kts +++ b/flank-scripts/build.gradle.kts @@ -28,7 +28,7 @@ shadowJar.apply { } } // .. -version = "1.1.5" +version = "1.1.6" group = "com.github.flank" application { diff --git a/flank-scripts/src/main/kotlin/flank/scripts/integration/CommitList.kt b/flank-scripts/src/main/kotlin/flank/scripts/integration/CommitList.kt index d118f91fe0..c642071296 100644 --- a/flank-scripts/src/main/kotlin/flank/scripts/integration/CommitList.kt +++ b/flank-scripts/src/main/kotlin/flank/scripts/integration/CommitList.kt @@ -1,26 +1,27 @@ +@file:Suppress("EXPERIMENTAL_API_USAGE") + package flank.scripts.integration import com.github.kittinunf.result.getOrElse import com.github.kittinunf.result.onError import flank.scripts.github.getGitHubCommitList import flank.scripts.github.getPrDetailsByCommit -import kotlinx.coroutines.FlowPreview import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.asFlow +import kotlinx.coroutines.flow.channelFlow import kotlinx.coroutines.flow.flatMapMerge import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.toList import kotlinx.coroutines.launch -@FlowPreview suspend fun getCommitListSinceDate(token: String, since: String) = coroutineScope { getGitHubCommitList(token, listOf("since" to since)) .onError { println(it.message) } .getOrElse { emptyList() } .asFlow() .flatMapMerge { - flow { - launch { emit(it.sha to getPrDetailsByCommit(token, it.sha).get()) } + channelFlow { + launch { send(it.sha to getPrDetailsByCommit(it.sha, token).get()) } } } .flatMapMerge { (commit, prs) -> diff --git a/flank-scripts/src/main/kotlin/flank/scripts/integration/Extensions.kt b/flank-scripts/src/main/kotlin/flank/scripts/integration/Extensions.kt new file mode 100644 index 0000000000..d6254b38e2 --- /dev/null +++ b/flank-scripts/src/main/kotlin/flank/scripts/integration/Extensions.kt @@ -0,0 +1,66 @@ +package flank.scripts.integration + +import com.github.kittinunf.result.getOrNull +import com.github.kittinunf.result.onError +import flank.scripts.github.objects.GitHubCreateIssueCommentRequest +import flank.scripts.github.objects.GitHubCreateIssueRequest +import flank.scripts.github.objects.GitHubCreateIssueResponse +import flank.scripts.github.objects.GitHubUpdateIssueRequest +import flank.scripts.github.objects.IssueState +import flank.scripts.github.patchIssue +import flank.scripts.github.postNewIssue +import flank.scripts.github.postNewIssueComment +import flank.scripts.utils.toJson +import kotlinx.coroutines.coroutineScope +import kotlin.system.exitProcess + +internal suspend fun IntegrationContext.createNewIssue() = coroutineScope { + println("** Creating new issue") + val issuePayload = GitHubCreateIssueRequest( + title = "Full Suite integration tests failed on master", + body = "### Integration Test failed on master", + labels = listOf("IT_Failed", "bug") + ) + println(issuePayload.toJson()) + val issue = postNewIssue(token, issuePayload) + .onError { + println(it.message) + } + .getOrNull() + if (issue == null) { + println("** Issue created but empty response returned. Terminating.") + exitProcess(-1) + } + logIssueCreated(issue) + copy(openedIssue = issue.number) +}.postComment() + +internal suspend fun IntegrationContext.postComment() = createCommentPayload().also { payload -> + postNewIssueComment(token, issueNumber, payload) + println("** Comment posted") + println(payload.toJson()) +} + +private suspend fun IntegrationContext.createCommentPayload() = coroutineScope { + val message = when (result) { + ITResults.SUCCESS -> prepareSuccessMessage(lastRun, runID, url) + ITResults.FAILURE -> { + val commitList = getCommitListSinceDate(token, lastRun) + prepareFailureMessage(lastRun, runID, url, commitList) + } + } + GitHubCreateIssueCommentRequest(message) +} + +internal suspend fun IntegrationContext.closeIssue() = postComment().also { + println("** Closing issue") + patchIssue(token, issueNumber, GitHubUpdateIssueRequest(state = IssueState.CLOSED)) +} + +private fun logIssueCreated(issue: GitHubCreateIssueResponse) = println( + """ +** Issue created: + url: ${issue.htmlUrl} + number: ${issue.number} +""".trimIndent() +) diff --git a/flank-scripts/src/main/kotlin/flank/scripts/integration/IntegrationContext.kt b/flank-scripts/src/main/kotlin/flank/scripts/integration/IntegrationContext.kt new file mode 100644 index 0000000000..772982f19c --- /dev/null +++ b/flank-scripts/src/main/kotlin/flank/scripts/integration/IntegrationContext.kt @@ -0,0 +1,13 @@ +package flank.scripts.integration + +data class IntegrationContext( + val result: ITResults, + val token: String, + val url: String, + val runID: String, + val lastRun: String, + private val openedIssue: Int?, +) { + val issueNumber: Int + get() = requireNotNull(openedIssue) +} diff --git a/flank-scripts/src/main/kotlin/flank/scripts/integration/IssueList.kt b/flank-scripts/src/main/kotlin/flank/scripts/integration/IssueList.kt index 2a43d2685c..7a432c2b17 100644 --- a/flank-scripts/src/main/kotlin/flank/scripts/integration/IssueList.kt +++ b/flank-scripts/src/main/kotlin/flank/scripts/integration/IssueList.kt @@ -7,8 +7,7 @@ import flank.scripts.github.getGitHubIssueList suspend fun checkForOpenedITIssues(token: String) = getGitHubIssueList( githubToken = token, parameters = listOf( -// "creator" to "github-actions[bot]", - "creator" to "pawelpasterz", + "creator" to "github-actions[bot]", "state" to "open", "labels" to "IT_Failed" ) @@ -19,4 +18,4 @@ suspend fun checkForOpenedITIssues(token: String) = getGitHubIssueList( .also { if (it != null) println("** Issue found: ${it.htmlUrl}") else println("** No opened issue") - } + }?.number diff --git a/flank-scripts/src/main/kotlin/flank/scripts/integration/ProcessResultCommand.kt b/flank-scripts/src/main/kotlin/flank/scripts/integration/ProcessResultCommand.kt index 0305a9866d..aab8a74215 100644 --- a/flank-scripts/src/main/kotlin/flank/scripts/integration/ProcessResultCommand.kt +++ b/flank-scripts/src/main/kotlin/flank/scripts/integration/ProcessResultCommand.kt @@ -1,25 +1,13 @@ +@file:Suppress("EXPERIMENTAL_API_USAGE") + package flank.scripts.integration import com.github.ajalt.clikt.core.CliktCommand import com.github.ajalt.clikt.parameters.options.option import com.github.ajalt.clikt.parameters.options.required import com.github.ajalt.clikt.parameters.types.enum -import com.github.kittinunf.result.getOrNull -import com.github.kittinunf.result.onError -import flank.scripts.github.objects.GitHubCreateIssueCommentRequest -import flank.scripts.github.objects.GitHubCreateIssueRequest -import flank.scripts.github.objects.GitHubUpdateIssueRequest -import flank.scripts.github.objects.IssueState -import flank.scripts.github.patchIssue -import flank.scripts.github.postNewIssue -import flank.scripts.github.postNewIssueComment -import flank.scripts.utils.toJson -import kotlinx.coroutines.FlowPreview -import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.runBlocking -import kotlin.system.exitProcess -@FlowPreview object ProcessResultCommand : CliktCommand(name = "processResults") { private val itResult by option(help = "IT run job status", names = arrayOf("--result")) @@ -33,83 +21,43 @@ object ProcessResultCommand : CliktCommand(name = "processResults") { private val runID by option(help = "Workflow job ID").required() - override fun run() { - println( - """ - ** Parameters: - token: $githubToken - result: $itResult - url: $buildScanURL - runID: $runID - """.trimIndent() - ) - runBlocking { - val openedIssue = checkForOpenedITIssues(githubToken) - val lastRun = getLastITWorkflowRunDate(githubToken) + private val openedIssue by lazy { runBlocking { checkForOpenedITIssues(githubToken) } } - when { - itResult == ITResults.FAILURE && openedIssue == null -> createNewIssue(lastRun) - itResult == ITResults.FAILURE && openedIssue != null -> postFailureComment(openedIssue.number, lastRun) - itResult == ITResults.SUCCESS && openedIssue != null -> closeIssue(openedIssue.number, lastRun) - else -> return@runBlocking - } - } - } + private val lastRun by lazy { runBlocking { getLastITWorkflowRunDate(githubToken) } } - private suspend fun createNewIssue(lastRun: String) = coroutineScope { - println("** Creating new issue") - val issuePayload = GitHubCreateIssueRequest( - title = "Full Suite integration tests failed on master", - body = "### Integration Test failed on master", - labels = listOf("IT_Failed", "bug") - ) - println(issuePayload.toJson()) - val issue = postNewIssue(githubToken, issuePayload) - .onError { - println(it.message) - } - .getOrNull() - if (issue == null) { - println("** There were problems with issue creation") - exitProcess(-1) - } - println( - """ - ** Issue created: - url: ${issue.htmlUrl} - number: ${issue.number} - """.trimIndent() - ) - postFailureComment(issue.number, lastRun) - } - - private suspend fun postFailureComment(issueNumber: Int, lastRun: String) = - coroutineScope { postComment(issueNumber, lastRun, Failure) } - - private suspend fun postSuccessComment(issueNumber: Int, lastRun: String) = - coroutineScope { postComment(issueNumber, lastRun, Success) } - - private suspend inline fun postComment(issueNumber: Int, lastRun: String, type: CommentMessage) = coroutineScope { - val message = when (type) { - is Success -> prepareSuccessMessage(lastRun, runID, buildScanURL) - is Failure -> { - val commitList = getCommitListSinceDate(githubToken, lastRun) - prepareFailureMessage(lastRun, runID, buildScanURL, commitList) + override fun run() { + runBlocking { + logArgs() + with(makeContext()) { + when { + itResult == ITResults.FAILURE && openedIssue == null -> createNewIssue() + itResult == ITResults.FAILURE && openedIssue != null -> postComment() + itResult == ITResults.SUCCESS && openedIssue != null -> closeIssue() + else -> return@runBlocking + } } } - val payload = GitHubCreateIssueCommentRequest(message) - println("** Comment created") - println(payload.toJson()) - postNewIssueComment(githubToken, issueNumber, payload) } - private suspend fun closeIssue(issueNumber: Int, lastRun: String) = coroutineScope { - postSuccessComment(issueNumber, lastRun) - println("** Closing issue") - patchIssue(githubToken, issueNumber, GitHubUpdateIssueRequest(state = IssueState.CLOSED)) - } + private fun makeContext() = IntegrationContext( + result = itResult, + token = githubToken, + url = buildScanURL, + runID = runID, + lastRun = lastRun, + openedIssue = openedIssue + ) + + private fun logArgs() = println( + """ + ** Parameters: + result: $itResult + url: $buildScanURL + runID: $runID + """.trimIndent() + ) +} - private enum class ITResults { - SUCCESS, FAILURE - } +enum class ITResults { + SUCCESS, FAILURE } diff --git a/flank-scripts/src/main/kotlin/flank/scripts/integration/WorkflowSummary.kt b/flank-scripts/src/main/kotlin/flank/scripts/integration/WorkflowSummary.kt index bea79fa6e2..3f9e4e8f9d 100644 --- a/flank-scripts/src/main/kotlin/flank/scripts/integration/WorkflowSummary.kt +++ b/flank-scripts/src/main/kotlin/flank/scripts/integration/WorkflowSummary.kt @@ -4,14 +4,11 @@ import com.github.kittinunf.result.getOrNull import flank.scripts.github.getGitHubWorkflowRunsSummary import java.time.Instant import java.time.format.DateTimeFormatter -import kotlinx.coroutines.coroutineScope -suspend fun getLastITWorkflowRunDate(token: String) = coroutineScope { - getLastWorkflowRunDate( - token = token, - workflowFileName = "it_cron.yml" - ) -} +suspend fun getLastITWorkflowRunDate(token: String) = getLastWorkflowRunDate( + token = token, + workflowFileName = "full_suite_integration_tests.yml" +) suspend fun getLastWorkflowRunDate( token: String, diff --git a/flank-scripts/src/test/kotlin/flank/scripts/GithubMockServerHandler.kt b/flank-scripts/src/test/kotlin/flank/scripts/GithubMockServerHandler.kt index ff1c986781..6bc4ecf23d 100644 --- a/flank-scripts/src/test/kotlin/flank/scripts/GithubMockServerHandler.kt +++ b/flank-scripts/src/test/kotlin/flank/scripts/GithubMockServerHandler.kt @@ -7,6 +7,7 @@ import flank.scripts.github.objects.GitHubCommit import flank.scripts.github.objects.GitHubCreateIssueCommentResponse import flank.scripts.github.objects.GitHubCreateIssueResponse import flank.scripts.github.objects.GitHubLabel +import flank.scripts.github.objects.GitHubWorkflowRun import flank.scripts.github.objects.GitHubWorkflowRunsSummary import flank.scripts.github.objects.GithubPullRequest import flank.scripts.github.objects.GithubUser @@ -14,15 +15,22 @@ import flank.scripts.utils.toJson import kotlinx.serialization.encodeToString import kotlinx.serialization.json.Json +// fixme: needs small refactor to increase readability, will do it in a following PR @Suppress("ComplexMethod") fun handleGithubMockRequest(url: String, request: Request) = when { request.isFailedGithubRequest() -> request.buildResponse(githubErrorBody, 422) - url ends """/actions/workflows/[a-zA-Z.-]*/runs""" -> request.buildResponse(workflowSummary, 200) - url ends """/issues/\d*/comments""" && request.isGET -> request.buildResponse( + url ends """/actions/workflows/[a-zA-Z_.-]*/runs""" -> request.buildResponse(workflowSummary, 200) + url ends """/issues/[0-9]*/comments""" && request.isGET -> request.buildResponse( testGithubIssueCommentList, 200 ) + url ends """/issues\?creator=github-actions""" -> request.buildResponse( + if (request.noIssueHeader()) "[]" else githubPullRequestTest.toJson(), + 200 + ) + url ends """/commits/[a-zA-Z0-9]*/pulls""" -> request.buildResponse(Json.encodeToString(githubPullRequestTest), 200) url ends """/issues/\d*/comments""" && request.isPOST -> request.buildResponse(createComment, 200) + url ends """/commits\?since=*""" -> request.buildResponse(testGithubIssueList, 200) url.endsWith("/git/refs/tags/success") -> request.buildResponse("", 200) url.endsWith("/releases/latest") && request.containsSuccessHeader() -> request.buildResponse(GitHubRelease("v20.08.0").toJson(), 200) @@ -36,7 +44,7 @@ fun handleGithubMockRequest(url: String, request: Request) = when { url.endsWith("/labels") && request.containsSuccessHeader() -> request.buildResponse(testGithubLabels.toJson(), 200) (url.contains("/pulls/") || url.contains("/issues/")) && request.containsSuccessHeader() -> request.buildResponse(githubPullRequestTest.first().toJson(), 200) - else -> error("Not supported request") + else -> error("Not supported request: $request") } private infix fun String.ends(suffix: String) = suffix.toRegex().containsMatchIn(this) @@ -47,9 +55,10 @@ private fun Request.isFailedGithubRequest() = url.toString() == "https://api.github.com/repos/Flank/flank/git/refs/tags/failure" || request.headers["Authorization"].contains("token failure") +private fun Request.noIssueHeader() = request.headers["Authorization"].contains("token no-issue") + private val testGithubIssueList = listOf( - GitHubCommit("123abc"), - GitHubCommit("456def") + GitHubCommit("aaaaaaaaa") ).toJson() private val testGithubIssueCommentList = listOf( @@ -63,7 +72,18 @@ private val Request.isPOST: Boolean private val Request.isGET: Boolean get() = method == Method.GET -private val workflowSummary = GitHubWorkflowRunsSummary(1, emptyList()).toJson() +private val workflowSummary = GitHubWorkflowRunsSummary( + totalCount = 1, + workflowRuns = listOf( + GitHubWorkflowRun( + status = "completed", + conclusion = "success", + createdAt = "2020-12-10T09:51:56.797534Z", + htmlUrl = "http://workflow.run/123", + name = "any-name" + ) + ) +).toJson() private val createIssue = GitHubCreateIssueResponse(1, "https://bla.org", "any body", 123).toJson() private val createComment = GitHubCreateIssueCommentResponse(2, "https://bla.org", "any body").toJson() diff --git a/flank-scripts/src/test/kotlin/flank/scripts/integration/ProcessResultTest.kt b/flank-scripts/src/test/kotlin/flank/scripts/integration/ProcessResultTest.kt new file mode 100644 index 0000000000..035309e67f --- /dev/null +++ b/flank-scripts/src/test/kotlin/flank/scripts/integration/ProcessResultTest.kt @@ -0,0 +1,88 @@ +package flank.scripts.integration + +import com.google.common.truth.Truth.assertThat +import flank.scripts.FuelTestRunner +import kotlinx.coroutines.runBlocking +import org.junit.Rule +import org.junit.Test +import org.junit.contrib.java.lang.system.SystemOutRule +import org.junit.runner.RunWith + +@RunWith(FuelTestRunner::class) +class ProcessResultTest { + + @get:Rule + val output: SystemOutRule = SystemOutRule().enableLog() + + private val ctx: IntegrationContext + get() = IntegrationContext( + result = ITResults.FAILURE, + token = "success", + url = "http://any.url", + runID = "123abc", + lastRun = "1999-09-19", + openedIssue = null + ) + + + @Test + fun `should create new issue for failed result`() { + runBlocking { + ctx.createNewIssue() + + assertThat(output.log).contains(issueCreated) + } + } + + @Test + fun `should post new comment`() { + runBlocking { + ctx.copy(openedIssue = 123).postComment() + + assertThat(output.log).contains(commentPosted) + } + } + + @Test + fun `should close issue`() { + runBlocking { + ctx.copy(openedIssue = 123, result = ITResults.SUCCESS).closeIssue() + + assertThat(output.log).contains(issueClosed) + } + } +} + +private val issueCreated = """ + ** Creating new issue + { + "title": "Full Suite integration tests failed on master", + "body": "### Integration Test failed on master", + "labels": [ + "IT_Failed", + "bug" + ] + } + ** Issue created: + url: https://bla.org + number: 123 + ** Comment posted + { + "body": "### Full suite IT run :x: FAILED :x:\n**Timestamp:** 1999-09-19\n**Job run:** [123abc](https://github.com/Flank/flank/actions/runs/123abc\n**Build scan URL:** http://any.url\n|commit SHA|PR|\n|---|:---:|\n|aaaaaaaaa|[feat: new Feature](www.pull.request)\n" + } +""".trimIndent() + +private val commentPosted = """ + ** Comment posted + { + "body": "### Full suite IT run :x: FAILED :x:\n**Timestamp:** 1999-09-19\n**Job run:** [123abc](https://github.com/Flank/flank/actions/runs/123abc\n**Build scan URL:** http://any.url\n|commit SHA|PR|\n|---|:---:|\n|aaaaaaaaa|[feat: new Feature](www.pull.request)\n" + } +""".trimIndent() + +private val issueClosed = """ + ** Comment posted + { + "body": "### Full suite IT run :white_check_mark: SUCCEEDED :white_check_mark:\n**Timestamp:** 1999-09-19\n**Job run:** [123abc](https://github.com/Flank/flank/actions/runs/123abc\n**Build scan URL:** http://any.url\n**Closing issue**" + } + ** Closing issue +""".trimIndent() From e7dc4df6c1f4d642c90d199ed815124bbdd6f2d4 Mon Sep 17 00:00:00 2001 From: Pawel Pasterz Date: Thu, 10 Dec 2020 16:02:16 +0100 Subject: [PATCH 08/14] Refactor and update tests --- .../full_suite_integration_tests.yml | 2 ++ .../FlankScriptsExceptionMappers.kt | 2 -- .../scripts/integration/PrepareMessage.kt | 15 ++++++++---- .../integration/ProcessResultCommand.kt | 3 --- .../scripts/integration/WorkflowSummary.kt | 24 +++++++++++-------- .../scripts/integration/ProcessResultTest.kt | 8 +++---- 6 files changed, 30 insertions(+), 24 deletions(-) diff --git a/.github/workflows/full_suite_integration_tests.yml b/.github/workflows/full_suite_integration_tests.yml index 398febc1b4..8157251dfb 100644 --- a/.github/workflows/full_suite_integration_tests.yml +++ b/.github/workflows/full_suite_integration_tests.yml @@ -4,6 +4,8 @@ on: schedule: - cron: '0 0 * * 1-5' # At 00:00 on every day-of-week from Monday through Friday workflow_dispatch: # or manually + branches: + - 'master' jobs: run-it-full-suite: diff --git a/flank-scripts/src/main/kotlin/flank/scripts/exceptions/FlankScriptsExceptionMappers.kt b/flank-scripts/src/main/kotlin/flank/scripts/exceptions/FlankScriptsExceptionMappers.kt index 1658911eb9..95eda9a178 100644 --- a/flank-scripts/src/main/kotlin/flank/scripts/exceptions/FlankScriptsExceptionMappers.kt +++ b/flank-scripts/src/main/kotlin/flank/scripts/exceptions/FlankScriptsExceptionMappers.kt @@ -14,8 +14,6 @@ fun Result.ma fun Result.mapClientErrorToGithubException() = mapClientError { it.toGithubException() } -fun Result.mapErrorToGithubException() = mapClientError { it.toGithubException() } - fun FuelError.toGithubException() = GitHubException(response.body().asString(APPLICATION_JSON_CONTENT_TYPE).toObject()) fun FuelError.toBugsnagException() = diff --git a/flank-scripts/src/main/kotlin/flank/scripts/integration/PrepareMessage.kt b/flank-scripts/src/main/kotlin/flank/scripts/integration/PrepareMessage.kt index 5fedffe690..5d2cef255b 100644 --- a/flank-scripts/src/main/kotlin/flank/scripts/integration/PrepareMessage.kt +++ b/flank-scripts/src/main/kotlin/flank/scripts/integration/PrepareMessage.kt @@ -1,11 +1,15 @@ package flank.scripts.integration import flank.scripts.github.objects.GithubPullRequest +import java.time.Instant +import java.time.LocalDateTime +import java.time.ZoneOffset +import java.time.format.DateTimeFormatter private val successTemplate = { lastRun: String, runId: String, url: String -> """ |### Full suite IT run :white_check_mark: SUCCEEDED :white_check_mark: - |**Timestamp:** $lastRun + |**Timestamp:** ${makeHumanFriendly(lastRun)} |**Job run:** [$runId](https://github.com/Flank/flank/actions/runs/$runId |**Build scan URL:** $url |**Closing issue** @@ -15,15 +19,16 @@ private val successTemplate = { lastRun: String, runId: String, url: String -> private val failureTemplate = { lastRun: String, runId: String, url: String -> """ |### Full suite IT run :x: FAILED :x: - |**Timestamp:** $lastRun + |**Timestamp:** ${makeHumanFriendly(lastRun)} |**Job run:** [$runId](https://github.com/Flank/flank/actions/runs/$runId |**Build scan URL:** $url """.trimMargin() } -sealed class CommentMessage -object Success : CommentMessage() -object Failure : CommentMessage() +private fun makeHumanFriendly(date: String) = + LocalDateTime + .ofInstant(Instant.from(DateTimeFormatter.ISO_INSTANT.parse(date)), ZoneOffset.UTC) + .format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss")) fun prepareSuccessMessage( lastRun: String, diff --git a/flank-scripts/src/main/kotlin/flank/scripts/integration/ProcessResultCommand.kt b/flank-scripts/src/main/kotlin/flank/scripts/integration/ProcessResultCommand.kt index aab8a74215..b1892bdfcc 100644 --- a/flank-scripts/src/main/kotlin/flank/scripts/integration/ProcessResultCommand.kt +++ b/flank-scripts/src/main/kotlin/flank/scripts/integration/ProcessResultCommand.kt @@ -18,11 +18,8 @@ object ProcessResultCommand : CliktCommand(name = "processResults") { .required() private val githubToken by option(help = "Git Token").required() - private val runID by option(help = "Workflow job ID").required() - private val openedIssue by lazy { runBlocking { checkForOpenedITIssues(githubToken) } } - private val lastRun by lazy { runBlocking { getLastITWorkflowRunDate(githubToken) } } override fun run() { diff --git a/flank-scripts/src/main/kotlin/flank/scripts/integration/WorkflowSummary.kt b/flank-scripts/src/main/kotlin/flank/scripts/integration/WorkflowSummary.kt index 3f9e4e8f9d..0ac62ee675 100644 --- a/flank-scripts/src/main/kotlin/flank/scripts/integration/WorkflowSummary.kt +++ b/flank-scripts/src/main/kotlin/flank/scripts/integration/WorkflowSummary.kt @@ -2,6 +2,7 @@ package flank.scripts.integration import com.github.kittinunf.result.getOrNull import flank.scripts.github.getGitHubWorkflowRunsSummary +import flank.scripts.github.objects.GitHubWorkflowRun import java.time.Instant import java.time.format.DateTimeFormatter @@ -29,17 +30,20 @@ suspend fun getLastWorkflowRunDate( .filter { it.status != "in_progress" } .filter { it.conclusion != "cancelled" } .getOrNull(0) - ?.also { - println( - """ - ** Last workflow run: - name: ${it.name} - last run: ${it.createdAt} - url: ${it.htmlUrl} - """.trimIndent() - ) - }?.createdAt.run { DateTimeFormatter.ISO_INSTANT.format(Instant.parse(this)) } + .logRun() + ?.createdAt.run { DateTimeFormatter.ISO_INSTANT.format(Instant.parse(this)) } } ?: run { println("** No workflow run found for ${workflowName ?: workflowFileName}") DateTimeFormatter.ISO_INSTANT.format(Instant.now()) } + +private fun GitHubWorkflowRun?.logRun() = this?.also { + println( + """ +** Last workflow run: + name: ${it.name} + last run: ${it.createdAt} + url: ${it.htmlUrl} +""".trimIndent() + ) +} diff --git a/flank-scripts/src/test/kotlin/flank/scripts/integration/ProcessResultTest.kt b/flank-scripts/src/test/kotlin/flank/scripts/integration/ProcessResultTest.kt index 035309e67f..7b3806465e 100644 --- a/flank-scripts/src/test/kotlin/flank/scripts/integration/ProcessResultTest.kt +++ b/flank-scripts/src/test/kotlin/flank/scripts/integration/ProcessResultTest.kt @@ -20,7 +20,7 @@ class ProcessResultTest { token = "success", url = "http://any.url", runID = "123abc", - lastRun = "1999-09-19", + lastRun = "2000-10-10T12:33:17Z", openedIssue = null ) @@ -68,21 +68,21 @@ private val issueCreated = """ number: 123 ** Comment posted { - "body": "### Full suite IT run :x: FAILED :x:\n**Timestamp:** 1999-09-19\n**Job run:** [123abc](https://github.com/Flank/flank/actions/runs/123abc\n**Build scan URL:** http://any.url\n|commit SHA|PR|\n|---|:---:|\n|aaaaaaaaa|[feat: new Feature](www.pull.request)\n" + "body": "### Full suite IT run :x: FAILED :x:\n**Timestamp:** 2000-10-10 12:33:17\n**Job run:** [123abc](https://github.com/Flank/flank/actions/runs/123abc\n**Build scan URL:** http://any.url\n|commit SHA|PR|\n|---|:---:|\n|aaaaaaaaa|[feat: new Feature](www.pull.request)\n" } """.trimIndent() private val commentPosted = """ ** Comment posted { - "body": "### Full suite IT run :x: FAILED :x:\n**Timestamp:** 1999-09-19\n**Job run:** [123abc](https://github.com/Flank/flank/actions/runs/123abc\n**Build scan URL:** http://any.url\n|commit SHA|PR|\n|---|:---:|\n|aaaaaaaaa|[feat: new Feature](www.pull.request)\n" + "body": "### Full suite IT run :x: FAILED :x:\n**Timestamp:** 2000-10-10 12:33:17\n**Job run:** [123abc](https://github.com/Flank/flank/actions/runs/123abc\n**Build scan URL:** http://any.url\n|commit SHA|PR|\n|---|:---:|\n|aaaaaaaaa|[feat: new Feature](www.pull.request)\n" } """.trimIndent() private val issueClosed = """ ** Comment posted { - "body": "### Full suite IT run :white_check_mark: SUCCEEDED :white_check_mark:\n**Timestamp:** 1999-09-19\n**Job run:** [123abc](https://github.com/Flank/flank/actions/runs/123abc\n**Build scan URL:** http://any.url\n**Closing issue**" + "body": "### Full suite IT run :white_check_mark: SUCCEEDED :white_check_mark:\n**Timestamp:** 2000-10-10 12:33:17\n**Job run:** [123abc](https://github.com/Flank/flank/actions/runs/123abc\n**Build scan URL:** http://any.url\n**Closing issue**" } ** Closing issue """.trimIndent() From d0eaafa39f8ec62b665ab5eba2ef92761cd1bf5c Mon Sep 17 00:00:00 2001 From: Pawel Pasterz Date: Thu, 10 Dec 2020 16:09:00 +0100 Subject: [PATCH 09/14] Change workflow yml --- .github/workflows/full_suite_integration_tests.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/full_suite_integration_tests.yml b/.github/workflows/full_suite_integration_tests.yml index 8157251dfb..bf74b3767b 100644 --- a/.github/workflows/full_suite_integration_tests.yml +++ b/.github/workflows/full_suite_integration_tests.yml @@ -4,11 +4,10 @@ on: schedule: - cron: '0 0 * * 1-5' # At 00:00 on every day-of-week from Monday through Friday workflow_dispatch: # or manually - branches: - - 'master' jobs: run-it-full-suite: + if: github.ref == 'refs/heads/master' # for now we want this workflow to run only on master, to be removed once support for different branches is implemented runs-on: ubuntu-latest outputs: build-scan-url: ${{ steps.run-it.outputs.build-scan-url }} From 079e9ad05e26aa210a44258273afe0444e6a2fc7 Mon Sep 17 00:00:00 2001 From: Pawel Pasterz Date: Thu, 10 Dec 2020 17:09:31 +0100 Subject: [PATCH 10/14] Change formatter --- .../src/main/kotlin/flank/scripts/integration/PrepareMessage.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/flank-scripts/src/main/kotlin/flank/scripts/integration/PrepareMessage.kt b/flank-scripts/src/main/kotlin/flank/scripts/integration/PrepareMessage.kt index 5d2cef255b..dcb639817b 100644 --- a/flank-scripts/src/main/kotlin/flank/scripts/integration/PrepareMessage.kt +++ b/flank-scripts/src/main/kotlin/flank/scripts/integration/PrepareMessage.kt @@ -28,7 +28,7 @@ private val failureTemplate = { lastRun: String, runId: String, url: String -> private fun makeHumanFriendly(date: String) = LocalDateTime .ofInstant(Instant.from(DateTimeFormatter.ISO_INSTANT.parse(date)), ZoneOffset.UTC) - .format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss")) + .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) fun prepareSuccessMessage( lastRun: String, From 5f131ddc937e8a91f67d26ca58a168e31f2a81fb Mon Sep 17 00:00:00 2001 From: Pawel Pasterz Date: Fri, 11 Dec 2020 09:41:25 +0100 Subject: [PATCH 11/14] Address CR comments --- flank-scripts/build.gradle.kts | 2 +- .../flank/scripts/integration/CommitList.kt | 3 +- .../flank/scripts/integration/Extensions.kt | 31 ++++++++++--------- .../flank/scripts/integration/IssueList.kt | 2 +- 4 files changed, 21 insertions(+), 17 deletions(-) diff --git a/flank-scripts/build.gradle.kts b/flank-scripts/build.gradle.kts index 1a0489f350..6b05fb5231 100644 --- a/flank-scripts/build.gradle.kts +++ b/flank-scripts/build.gradle.kts @@ -28,7 +28,7 @@ shadowJar.apply { } } // .. -version = "1.1.6" +version = "1.2.0" group = "com.github.flank" application { diff --git a/flank-scripts/src/main/kotlin/flank/scripts/integration/CommitList.kt b/flank-scripts/src/main/kotlin/flank/scripts/integration/CommitList.kt index c642071296..bbb3f69c79 100644 --- a/flank-scripts/src/main/kotlin/flank/scripts/integration/CommitList.kt +++ b/flank-scripts/src/main/kotlin/flank/scripts/integration/CommitList.kt @@ -21,7 +21,8 @@ suspend fun getCommitListSinceDate(token: String, since: String) = coroutineScop .asFlow() .flatMapMerge { channelFlow { - launch { send(it.sha to getPrDetailsByCommit(it.sha, token).get()) } + launch { + send(it.sha to getPrDetailsByCommit(it.sha, token).getOrElse { emptyList() }) } } } .flatMapMerge { (commit, prs) -> diff --git a/flank-scripts/src/main/kotlin/flank/scripts/integration/Extensions.kt b/flank-scripts/src/main/kotlin/flank/scripts/integration/Extensions.kt index d6254b38e2..87086d9c37 100644 --- a/flank-scripts/src/main/kotlin/flank/scripts/integration/Extensions.kt +++ b/flank-scripts/src/main/kotlin/flank/scripts/integration/Extensions.kt @@ -1,6 +1,5 @@ package flank.scripts.integration -import com.github.kittinunf.result.getOrNull import com.github.kittinunf.result.onError import flank.scripts.github.objects.GitHubCreateIssueCommentRequest import flank.scripts.github.objects.GitHubCreateIssueRequest @@ -14,7 +13,21 @@ import flank.scripts.utils.toJson import kotlinx.coroutines.coroutineScope import kotlin.system.exitProcess -internal suspend fun IntegrationContext.createNewIssue() = coroutineScope { +internal suspend fun IntegrationContext.createNewIssue() = createAndPostNewIssue().postComment() + +private suspend fun IntegrationContext.createAndPostNewIssue(payload: GitHubCreateIssueRequest = createIssuePayload()) = + postNewIssue(token, payload) + .onError { + println(it.message) + exitProcess(-1) + } + .get() + .run { + logIssueCreated(this) + copy(openedIssue = number) + } + +private fun createIssuePayload(): GitHubCreateIssueRequest { println("** Creating new issue") val issuePayload = GitHubCreateIssueRequest( title = "Full Suite integration tests failed on master", @@ -22,18 +35,8 @@ internal suspend fun IntegrationContext.createNewIssue() = coroutineScope { labels = listOf("IT_Failed", "bug") ) println(issuePayload.toJson()) - val issue = postNewIssue(token, issuePayload) - .onError { - println(it.message) - } - .getOrNull() - if (issue == null) { - println("** Issue created but empty response returned. Terminating.") - exitProcess(-1) - } - logIssueCreated(issue) - copy(openedIssue = issue.number) -}.postComment() + return issuePayload +} internal suspend fun IntegrationContext.postComment() = createCommentPayload().also { payload -> postNewIssueComment(token, issueNumber, payload) diff --git a/flank-scripts/src/main/kotlin/flank/scripts/integration/IssueList.kt b/flank-scripts/src/main/kotlin/flank/scripts/integration/IssueList.kt index 7a432c2b17..4edad68613 100644 --- a/flank-scripts/src/main/kotlin/flank/scripts/integration/IssueList.kt +++ b/flank-scripts/src/main/kotlin/flank/scripts/integration/IssueList.kt @@ -14,7 +14,7 @@ suspend fun checkForOpenedITIssues(token: String) = getGitHubIssueList( ) .onError { println(it.message) } .getOrElse { emptyList() } - .getOrNull(0) + .firstOrNull() .also { if (it != null) println("** Issue found: ${it.htmlUrl}") else println("** No opened issue") From 7502808b0dede7fbf2e1d10654f5a6613a67e8ac Mon Sep 17 00:00:00 2001 From: Pawel Pasterz Date: Fri, 11 Dec 2020 10:00:36 +0100 Subject: [PATCH 12/14] Update flank-scripts readme file --- flank-scripts/README.md | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/flank-scripts/README.md b/flank-scripts/README.md index a33dcdb23e..a1a4a1069e 100644 --- a/flank-scripts/README.md +++ b/flank-scripts/README.md @@ -264,3 +264,22 @@ or | --github-token | GitHub Token | | --zenhub-token | ZenHub api Token | | --pr-number | Pull request number | + +### integration (currently, available only on the master branch) +To show all available commands for `integration` use: +`flankScripts integration` + +Available commands are: Process results from `full_suite_integration_tests` workflow +- `processResults` + +#### `processResults` +If IT ended up with failure a new issue is created with some basic info: build scan URL, commit list, timestamp. +If there is an issue already created comment is posted to the existing one (so we avoid multiple copies of the same ticket). +Once integration tests end with success issue is closed. + +| Option | Description | +|----------------|---------------------| +| --github-token | GitHub Token | +| --result | Status of IT step from workflow. Can be either `success` or `failure`| +| --url | Build scan url from IT step| +| --run-id | `job.run_id` value from workflow context. Used for comment posting| From 4ee87fd2d6919cf84b3dec98bfceae6d18e1614b Mon Sep 17 00:00:00 2001 From: Pawel Pasterz Date: Fri, 11 Dec 2020 11:41:47 +0100 Subject: [PATCH 13/14] Remove flow --- .../flank/scripts/integration/CommitList.kt | 27 +++++++------------ 1 file changed, 9 insertions(+), 18 deletions(-) diff --git a/flank-scripts/src/main/kotlin/flank/scripts/integration/CommitList.kt b/flank-scripts/src/main/kotlin/flank/scripts/integration/CommitList.kt index bbb3f69c79..c89cf88358 100644 --- a/flank-scripts/src/main/kotlin/flank/scripts/integration/CommitList.kt +++ b/flank-scripts/src/main/kotlin/flank/scripts/integration/CommitList.kt @@ -1,35 +1,26 @@ -@file:Suppress("EXPERIMENTAL_API_USAGE") - package flank.scripts.integration import com.github.kittinunf.result.getOrElse import com.github.kittinunf.result.onError import flank.scripts.github.getGitHubCommitList import flank.scripts.github.getPrDetailsByCommit +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll import kotlinx.coroutines.coroutineScope -import kotlinx.coroutines.flow.asFlow -import kotlinx.coroutines.flow.channelFlow -import kotlinx.coroutines.flow.flatMapMerge -import kotlinx.coroutines.flow.flow -import kotlinx.coroutines.flow.toList -import kotlinx.coroutines.launch suspend fun getCommitListSinceDate(token: String, since: String) = coroutineScope { getGitHubCommitList(token, listOf("since" to since)) .onError { println(it.message) } .getOrElse { emptyList() } - .asFlow() - .flatMapMerge { - channelFlow { - launch { - send(it.sha to getPrDetailsByCommit(it.sha, token).getOrElse { emptyList() }) } + .map { + async { + it.sha to getPrDetailsByCommit(it.sha, token).getOrElse { emptyList() } } } - .flatMapMerge { (commit, prs) -> - flow { - if (prs.isEmpty()) emit(commit to null) - else prs.forEach { emit(commit to it) } - } + .awaitAll() + .flatMap { (commit, prs) -> + if (prs.isEmpty()) listOf(commit to null) + else prs.map { commit to it } } .toList() } From a256556ee1e4b8e2bd263bd5c6db72fc68e491e1 Mon Sep 17 00:00:00 2001 From: Pawel Pasterz Date: Fri, 11 Dec 2020 15:49:46 +0100 Subject: [PATCH 14/14] Linting --- .../full_suite_integration_tests.yml | 6 ++- .../flank/scripts/integration/CommitList.kt | 6 ++- .../flank/scripts/integration/Extensions.kt | 27 ++++++----- .../flank/scripts/integration/IssueList.kt | 2 +- .../scripts/integration/PrepareMessage.kt | 46 +++++++++---------- 5 files changed, 49 insertions(+), 38 deletions(-) diff --git a/.github/workflows/full_suite_integration_tests.yml b/.github/workflows/full_suite_integration_tests.yml index bf74b3767b..9ffc68ec56 100644 --- a/.github/workflows/full_suite_integration_tests.yml +++ b/.github/workflows/full_suite_integration_tests.yml @@ -55,4 +55,8 @@ jobs: - name: Process IT results run: | - flankScripts integration processResults --result ${{ job.status }} --url ${{ steps.run-it.outputs.build-scan-url }} --github-token=${{ secrets.GITHUB_TOKEN }} --run-id ${{ github.run_id }} + flankScripts integration processResults \ + --result ${{ job.status }} \ + --url ${{ steps.run-it.outputs.build-scan-url }} \ + --github-token ${{ secrets.GITHUB_TOKEN }} \ + --run-id ${{ github.run_id }} diff --git a/flank-scripts/src/main/kotlin/flank/scripts/integration/CommitList.kt b/flank-scripts/src/main/kotlin/flank/scripts/integration/CommitList.kt index c89cf88358..347ddb76f3 100644 --- a/flank-scripts/src/main/kotlin/flank/scripts/integration/CommitList.kt +++ b/flank-scripts/src/main/kotlin/flank/scripts/integration/CommitList.kt @@ -4,11 +4,15 @@ import com.github.kittinunf.result.getOrElse import com.github.kittinunf.result.onError import flank.scripts.github.getGitHubCommitList import flank.scripts.github.getPrDetailsByCommit +import flank.scripts.github.objects.GithubPullRequest import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.coroutineScope -suspend fun getCommitListSinceDate(token: String, since: String) = coroutineScope { +suspend fun getCommitListSinceDate( + token: String, + since: String +): List> = coroutineScope { getGitHubCommitList(token, listOf("since" to since)) .onError { println(it.message) } .getOrElse { emptyList() } diff --git a/flank-scripts/src/main/kotlin/flank/scripts/integration/Extensions.kt b/flank-scripts/src/main/kotlin/flank/scripts/integration/Extensions.kt index 87086d9c37..784b3350ce 100644 --- a/flank-scripts/src/main/kotlin/flank/scripts/integration/Extensions.kt +++ b/flank-scripts/src/main/kotlin/flank/scripts/integration/Extensions.kt @@ -13,7 +13,21 @@ import flank.scripts.utils.toJson import kotlinx.coroutines.coroutineScope import kotlin.system.exitProcess -internal suspend fun IntegrationContext.createNewIssue() = createAndPostNewIssue().postComment() +internal suspend fun IntegrationContext.createNewIssue(): GitHubCreateIssueCommentRequest = + createAndPostNewIssue().postComment() + +internal suspend fun IntegrationContext.postComment(): GitHubCreateIssueCommentRequest = + createCommentPayload().also { payload -> + postNewIssueComment(token, issueNumber, payload) + println("** Comment posted") + println(payload.toJson()) + } + +internal suspend fun IntegrationContext.closeIssue(): ByteArray = + postComment().run { + println("** Closing issue") + patchIssue(token, issueNumber, GitHubUpdateIssueRequest(state = IssueState.CLOSED)).get() + } private suspend fun IntegrationContext.createAndPostNewIssue(payload: GitHubCreateIssueRequest = createIssuePayload()) = postNewIssue(token, payload) @@ -38,12 +52,6 @@ private fun createIssuePayload(): GitHubCreateIssueRequest { return issuePayload } -internal suspend fun IntegrationContext.postComment() = createCommentPayload().also { payload -> - postNewIssueComment(token, issueNumber, payload) - println("** Comment posted") - println(payload.toJson()) -} - private suspend fun IntegrationContext.createCommentPayload() = coroutineScope { val message = when (result) { ITResults.SUCCESS -> prepareSuccessMessage(lastRun, runID, url) @@ -55,11 +63,6 @@ private suspend fun IntegrationContext.createCommentPayload() = coroutineScope { GitHubCreateIssueCommentRequest(message) } -internal suspend fun IntegrationContext.closeIssue() = postComment().also { - println("** Closing issue") - patchIssue(token, issueNumber, GitHubUpdateIssueRequest(state = IssueState.CLOSED)) -} - private fun logIssueCreated(issue: GitHubCreateIssueResponse) = println( """ ** Issue created: diff --git a/flank-scripts/src/main/kotlin/flank/scripts/integration/IssueList.kt b/flank-scripts/src/main/kotlin/flank/scripts/integration/IssueList.kt index 4edad68613..c3e29a123f 100644 --- a/flank-scripts/src/main/kotlin/flank/scripts/integration/IssueList.kt +++ b/flank-scripts/src/main/kotlin/flank/scripts/integration/IssueList.kt @@ -4,7 +4,7 @@ import com.github.kittinunf.result.getOrElse import com.github.kittinunf.result.onError import flank.scripts.github.getGitHubIssueList -suspend fun checkForOpenedITIssues(token: String) = getGitHubIssueList( +suspend fun checkForOpenedITIssues(token: String): Int? = getGitHubIssueList( githubToken = token, parameters = listOf( "creator" to "github-actions[bot]", diff --git a/flank-scripts/src/main/kotlin/flank/scripts/integration/PrepareMessage.kt b/flank-scripts/src/main/kotlin/flank/scripts/integration/PrepareMessage.kt index dcb639817b..1445d60f4a 100644 --- a/flank-scripts/src/main/kotlin/flank/scripts/integration/PrepareMessage.kt +++ b/flank-scripts/src/main/kotlin/flank/scripts/integration/PrepareMessage.kt @@ -6,6 +6,29 @@ import java.time.LocalDateTime import java.time.ZoneOffset import java.time.format.DateTimeFormatter +fun prepareSuccessMessage( + lastRun: String, + runId: String, + url: String +): String = successTemplate(lastRun, runId, url) + +fun prepareFailureMessage( + lastRun: String, + runId: String, + url: String, + prs: List> +): String = buildString { + appendLine(failureTemplate(lastRun, runId, url)) + if (prs.isEmpty()) appendLine("No new commits") + else { + appendLine("|commit SHA|PR|") + appendLine("|---|:---:|") + prs.forEach { (commit, pr) -> + appendLine("|$commit|${pr?.let { "[${it.title}](${it.htmlUrl})" } ?: "---"}") + } + } +} + private val successTemplate = { lastRun: String, runId: String, url: String -> """ |### Full suite IT run :white_check_mark: SUCCEEDED :white_check_mark: @@ -29,26 +52,3 @@ private fun makeHumanFriendly(date: String) = LocalDateTime .ofInstant(Instant.from(DateTimeFormatter.ISO_INSTANT.parse(date)), ZoneOffset.UTC) .format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) - -fun prepareSuccessMessage( - lastRun: String, - runId: String, - url: String -): String = successTemplate(lastRun, runId, url) - -fun prepareFailureMessage( - lastRun: String, - runId: String, - url: String, - prs: List> -): String = buildString { - appendLine(failureTemplate(lastRun, runId, url)) - if (prs.isEmpty()) appendLine("No new commits") - else { - appendLine("|commit SHA|PR|") - appendLine("|---|:---:|") - prs.forEach { (commit, pr) -> - appendLine("|$commit|${pr?.let { "[${it.title}](${it.htmlUrl})" } ?: "---"}") - } - } -}