Skip to content

Commit a7765b3

Browse files
Johannes SchneiderCharlesDuboisSAPgithub-actions[bot]Maven Central Release Scriptbot-sdk-js
authored
refactor: [DevOps] CI/CD Pipelines (#259)
Co-authored-by: CharlesDuboisSAP <[email protected]> Co-authored-by: Charles Dubois <[email protected]> Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com> Co-authored-by: Maven Central Release Script <[email protected]> Co-authored-by: SAP Cloud SDK Bot <[email protected]>
1 parent 9201d76 commit a7765b3

21 files changed

+1792
-2081
lines changed
+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
name: "Await Workflow"
2+
description: "Waits until a workflow run completes."
3+
4+
inputs:
5+
run-id:
6+
description: "The id of the workflow run to wait for."
7+
required: true
8+
poll-interval:
9+
description: "The interval (in seconds) to poll for the workflow run status."
10+
required: false
11+
default: "60"
12+
commit-status:
13+
description: "The commit status message. Leave empty to not create a commit status."
14+
required: false
15+
16+
outputs:
17+
succeeded:
18+
description: "Whether the triggered run succeeded."
19+
value: ${{ steps.wait-for-workflow.outputs.RUN_SUCCEEDED }}
20+
conclusion:
21+
description: "The conclusion of the triggered workflow run."
22+
value: ${{ steps.wait-for-workflow.outputs.CONCLUSION }}
23+
24+
runs:
25+
using: composite
26+
steps:
27+
- name: Print Action Input
28+
run: |
29+
echo "[DEBUG] Starting 'Await Workflow' Action; inputs = ${{ toJson(inputs) }}"
30+
shell: bash
31+
32+
- name: View Run
33+
if: ${{ inputs.commit-status != '' }}
34+
id: view-run
35+
env:
36+
GH_TOKEN: ${{ github.token }}
37+
run: |
38+
JSON=$(gh run view ${{ inputs.run-id }} --json url,headSha)
39+
echo "URL=$(echo $JSON | jq -r '.url')" >> $GITHUB_OUTPUT
40+
echo "HEAD_SHA=$(echo $JSON | jq -r '.headSha')" >> $GITHUB_OUTPUT
41+
shell: bash
42+
43+
- name: Create Commit Status
44+
if: ${{ inputs.commit-status != '' }}
45+
uses: actions/github-script@v7
46+
with:
47+
script: |
48+
github.rest.repos.createCommitStatus({
49+
owner: context.repo.owner,
50+
repo: context.repo.repo,
51+
sha: '${{ steps.view-run.outputs.HEAD_SHA }}',
52+
state: 'pending',
53+
target_url: '${{ steps.view-run.outputs.URL }}',
54+
context: '${{ inputs.commit-status }}'
55+
})
56+
57+
- name: Wait for Workflow to Complete
58+
id: wait-for-workflow
59+
env:
60+
GH_TOKEN: ${{ github.token }}
61+
run: |
62+
echo "[DEBUG] Waiting for run '${{ inputs.run-id }}' to complete..."
63+
gh run watch ${{ inputs.run-id }} --interval ${{ inputs.poll-interval }} > /dev/null
64+
CONCLUSION=$(gh run view ${{ inputs.run-id }} --json conclusion | jq -r '.conclusion')
65+
66+
echo "CONCLUSION=$CONCLUSION" >> $GITHUB_OUTPUT
67+
echo "[DEBUG] Run '${{ inputs.run-id }}' finished with conclusion '$CONCLUSION'."
68+
69+
if [[ "$CONCLUSION" != "success" ]]; then
70+
echo "RUN_SUCCEEDED=false" >> $GITHUB_OUTPUT
71+
exit 1
72+
fi
73+
74+
echo "RUN_SUCCEEDED=true" >> $GITHUB_OUTPUT
75+
shell: bash
76+
77+
- name: Determine Final Commit Status
78+
id: determine-final-commit-status
79+
if: ${{ always() && inputs.commit-status != '' }}
80+
run: |
81+
if [[ "${{ steps.wait-for-workflow.outputs.CONCLUSION }}" == "success" ]]; then
82+
echo "FINAL_COMMIT_STATUS=success" >> $GITHUB_OUTPUT
83+
else
84+
echo "FINAL_COMMIT_STATUS=failure" >> $GITHUB_OUTPUT
85+
fi
86+
shell: bash
87+
88+
- name: Update Commit Status
89+
if: ${{ always() && inputs.commit-status != '' }}
90+
uses: actions/github-script@v7
91+
with:
92+
script: |
93+
github.rest.repos.createCommitStatus({
94+
owner: context.repo.owner,
95+
repo: context.repo.repo,
96+
sha: '${{ steps.view-run.outputs.HEAD_SHA }}',
97+
state: '${{ steps.determine-final-commit-status.outputs.FINAL_COMMIT_STATUS }}',
98+
target_url: '${{ steps.view-run.outputs.URL }}',
99+
context: '${{ inputs.commit-status }}'
100+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
name: "Deploy Snapshot"
2+
description: "Deploys release artifacts produced by a given 'Continuous Integration' workflow to a provided SNAPSHOT repository."
3+
4+
inputs:
5+
ci-run-id:
6+
description: "The run ID of the 'Continuous Integration' workflow that produced the release artifacts."
7+
required: true
8+
repository-url:
9+
description: "The URL of the repository to which the release artifacts should be deployed."
10+
required: true
11+
repository-username:
12+
description: "The username to use when authenticating with the repository."
13+
required: true
14+
repository-password:
15+
description: "The password to use when authenticating with the repository."
16+
required: true
17+
release-artifact-name:
18+
description: "The name of the artifact to download from the 'Continuous Integration' workflow."
19+
required: false
20+
default: "release-artifacts"
21+
22+
runs:
23+
using: composite
24+
steps:
25+
- name: "Print Action Start"
26+
run: echo ">>>>> Starting Deploy Snapshot Action; Not printing inputs as they may contain sensitive information."
27+
shell: bash
28+
29+
- name: "Setup java"
30+
uses: actions/setup-java@v4
31+
with:
32+
distribution: "temurin"
33+
java-version: "17"
34+
server-id: artifactory-snapshots
35+
server-username: ${{ inputs.repository-username }}
36+
server-password: ${{ inputs.repository-password }}
37+
38+
- name: "Download Release Artifacts"
39+
uses: actions/download-artifact@v4
40+
with:
41+
name: ${{ inputs.release-artifact-name }}
42+
github-token: ${{ github.token }}
43+
run-id: ${{ inputs.ci-run-id }}
44+
45+
- name: "Publish Snapshot"
46+
run: >
47+
mvn
48+
--batch-mode
49+
--no-transfer-progress
50+
--fail-at-end
51+
--threads 1C
52+
-Durl=${{ inputs.repository-url }}
53+
-DrepositoryId=artifactory-snapshots
54+
-Dmaven.install.skip=true
55+
-Dmaven.test.skip
56+
-Dmaven.compiler.showCompilationChanges
57+
-Dhttp.keepAlive=false
58+
deploy
59+
shell: bash
60+
61+
- name: "Print Action End"
62+
if: always()
63+
run: echo ">>>>> Finished Deploy Snapshot Action"
64+
shell: bash
+106
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
name: "PR Is Mergeable"
2+
description: "Checks whether the provided PR is approved and all status checks either succeeded or have been skipped"
3+
4+
inputs:
5+
pr-ref:
6+
description: "The reference (i.e. either number or a branch) of the PR to check"
7+
required: true
8+
fail-on-unmergeable:
9+
description: "Whether to fail the action if the PR is not mergeable"
10+
required: false
11+
default: "true"
12+
repo:
13+
description: "The repository of the PR"
14+
required: false
15+
default: ${{ github.repository }}
16+
token:
17+
description: "The GitHub access token (with PR read permissions) to access the PR"
18+
required: false
19+
default: ${{ github.token }}
20+
excluded-check-runs:
21+
description: "A comma-separated list of workflow names that are excluded from the Check Runs check"
22+
required: false
23+
default: ${{ github.workflow }}
24+
25+
outputs:
26+
pr-number:
27+
description: "The number of the PR that was checked"
28+
value: ${{ steps.check.outputs.PR_NUMBER }}
29+
is-mergeable:
30+
description: "Whether the PR is mergeable"
31+
value: ${{ steps.check.outputs.RESULT }}
32+
33+
runs:
34+
using: composite
35+
steps:
36+
- name: "Print Action Start"
37+
run: echo ">>>>> Starting PR Is Mergeable Action; inputs = ${{ toJson(inputs) }}"
38+
shell: bash
39+
40+
- name: "Check Whether PR Is Mergeable"
41+
id: check
42+
run: |
43+
PR_JSON=$(gh pr view "${{ inputs.pr-ref }}" --repo "${{ inputs.repo }}" --json number,mergeable,reviewDecision,statusCheckRollup)
44+
45+
PR_NUMBER=$(jq -r '.number' <<< "$PR_JSON")
46+
PR_MERGEABLE=$(jq -r '.mergeable' <<< "$PR_JSON")
47+
PR_DECISION=$(jq -r '.reviewDecision' <<< "$PR_JSON")
48+
49+
echo "PR_NUMBER=$PR_NUMBER" >> $GITHUB_OUTPUT
50+
echo "[DEBUG] PR #$PR_NUMBER (in ${{ inputs.repo }}) is mergeable: $PR_MERGEABLE with decision $PR_DECISION"
51+
52+
if [[ "$PR_DECISION" != "APPROVED" ]]; then
53+
echo "PR #$PR_NUMBER (in ${{ inputs.repo }}) has not been approved."
54+
echo "RESULT=false" >> $GITHUB_OUTPUT
55+
exit 0
56+
fi
57+
58+
if [[ "$PR_MERGEABLE" != "MERGEABLE" ]]; then
59+
echo "PR #$PR_NUMBER (in ${{ inputs.repo }}) is not mergeable (i.e. there are conflicts)."
60+
echo "RESULT=false" >> $GITHUB_OUTPUT
61+
exit 0
62+
fi
63+
64+
PR_CHECKS=$(jq -r '.statusCheckRollup' <<< "$PR_JSON")
65+
66+
# check runs are things like our CI pipeline
67+
FAILED_CHECK_RUNS=$(jq -r '.[] | select(.__typename == "CheckRun" and .conclusion != "SUCCESS" and .conclusion != "NEUTRAL")' <<< "$PR_CHECKS")
68+
IFS=',' read -ra EXCLUDED_WORKFLOWS <<< "${{ inputs.excluded-check-runs }}"
69+
for EXCLUDED_WORKFLOW in "${EXCLUDED_WORKFLOWS[@]}"; do
70+
if [[ -z "$FAILED_CHECK_RUNS" ]]; then
71+
break
72+
fi
73+
74+
FAILED_CHECK_RUNS=$(jq -r 'select(.workflowName != "$EXCLUDED_WORKFLOW")' <<< "$FAILED_CHECK_RUNS")
75+
done
76+
77+
if [[ -n "$FAILED_CHECK_RUNS" ]]; then
78+
echo "PR #$PR_NUMBER (in ${{ inputs.repo }}) contains failed check runs: "
79+
echo "$FAILED_CHECK_RUNS"
80+
echo "RESULT=false" >> $GITHUB_OUTPUT
81+
exit 0
82+
fi
83+
84+
# context checks are things like the license agreement check
85+
FAILED_CONTEXT_CHECKS=$(jq -r '.[] | select(.__typename == "StatusContext" and .state != "SUCCESS" and .state != "NEUTRAL")' <<< "$PR_CHECKS")
86+
if [[ -n "$FAILED_CONTEXT_CHECKS" ]]; then
87+
echo "PR #$PR_NUMBER (in ${{ inputs.repo }}) contains failed context checks: "
88+
echo "$FAILED_CONTEXT_CHECKS"
89+
echo "RESULT=false" >> $GITHUB_OUTPUT
90+
exit 0
91+
fi
92+
93+
echo "RESULT=true" >> $GITHUB_OUTPUT
94+
shell: bash
95+
env:
96+
GH_TOKEN: ${{ inputs.token }}
97+
98+
- name: "Fail If PR Is Not Mergeable"
99+
if: ${{ inputs.fail-on-unmergeable == 'true' && steps.check.outputs.RESULT != 'true' }}
100+
run: exit 1
101+
shell: bash
102+
103+
- name: "Print Action End"
104+
if: always()
105+
run: echo "<<<<< Finished PR Is Mergeable Action"
106+
shell: bash
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: "Scan with BlackDuck"
2+
description: "Scans the project with BlackDuck"
3+
4+
runs:
5+
using: composite
6+
steps:
7+
- name: Print Action Start
8+
run: echo ">>>>> Starting Scan with BlackDuck Action; inputs = ${{ toJson(inputs) }}"
9+
shell: bash
10+
11+
- name: Get Major Version
12+
id: get-major-version
13+
run: echo "MAJOR_VERSION=$(cat latest.json | jq -r .version | cut -d '.' -f 1)" >> $GITHUB_OUTPUT
14+
shell: bash
15+
16+
- name: Determine Maven Excludes
17+
id: get-maven-excludes-for-blackduck
18+
run: python .pipeline/scripts/get-maven-excludes.py --filter-key excludeFromBlackDuckScan --filter-value True
19+
shell: bash
20+
21+
- name: BlackDuck Scan
22+
uses: SAP/project-piper-action@master
23+
with:
24+
command: detectExecuteScan
25+
flags: \
26+
--version=$PROJECT_VERSION \
27+
env:
28+
PIPER_token: ${{ secrets.BLACKDUCK_TOKEN }}
29+
DETECT_MAVEN_EXCLUDED_MODULES: ${{ steps.get-maven-excludes-for-blackduck.outputs.EXCLUDES }}
30+
DETECT_MAVEN_BUILD_COMMAND: -pl ${{ steps.get-maven-excludes-for-blackduck.outputs.PREFIXED_EXCLUDES }}
31+
DETECT_TIMEOUT: "7200"
32+
PROJECT_VERSION: ${{ steps.get-major-version.outputs.MAJOR_VERSION }}
33+
34+
- name: Print Action End
35+
if: always()
36+
run: echo "<<<<< Finished Scan with BlackDuck Action"
37+
shell: bash
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
name: "Trigger Workflow"
2+
description: "Triggers a workflow without waiting for it to complete."
3+
4+
inputs:
5+
workflow:
6+
description: "The workflow file name"
7+
required: true
8+
workflow-ref:
9+
description: "The ref (i.e. branch name, or tag name) where the workflow is located."
10+
required: true
11+
parameters:
12+
description: "The workflow parameters"
13+
required: false
14+
commit-sha:
15+
description: "The commit SHA to trigger the workflow on"
16+
required: false
17+
default: ${{ github.sha }}
18+
19+
outputs:
20+
run-id:
21+
description: "The id of the workflow run that was triggered."
22+
value: ${{ steps.trigger-workflow.outputs.RUN_ID }}
23+
run-url:
24+
description: "The url of the workflow run that was triggered."
25+
value: ${{ steps.trigger-workflow.outputs.RUN_URL }}
26+
27+
runs:
28+
using: composite
29+
steps:
30+
- name: Print Action Input
31+
run: |
32+
echo "[DEBUG] Starting 'Trigger Workflow' Action; inputs = ${{ toJson(inputs) }}"
33+
shell: bash
34+
35+
- name: Trigger Workflow
36+
id: trigger-workflow
37+
env:
38+
GH_TOKEN: ${{ github.token }}
39+
run: |
40+
PREVIOUS_RUN_ID=$(gh run list --workflow=${{ inputs.workflow}} --commit=${{ inputs.commit-sha }} --json databaseId | jq -r '.[0].databaseId')
41+
echo "[DEBUG] Previous run id = '$PREVIOUS_RUN_ID'"
42+
43+
gh workflow run "${{ inputs.workflow }}" --ref "${{ inputs.workflow-ref }}" ${{ inputs.parameters }}
44+
# allow for some initial delay as workflows take a moment to spin up
45+
sleep 20
46+
47+
for i in {0..6}; do
48+
LATEST_RUN_ID=$(gh run list --workflow=${{ inputs.workflow }} --commit=${{ inputs.commit-sha }} --json databaseId | jq -r '.[0].databaseId')
49+
50+
if [[ -z "$LATEST_RUN_ID" || "$LATEST_RUN_ID" == "$PREVIOUS_RUN_ID" ]]; then
51+
echo "[DEBUG] No new run detected. Waiting for 10 seconds."
52+
sleep 10
53+
else
54+
echo "[DEBUG] New workflow run detected: '$LATEST_RUN_ID'."
55+
56+
RUN_URL=$(gh run view $LATEST_RUN_ID --json url | jq -r '.url')
57+
echo "[DEBUG] ${{ inputs.workflow }} run #$LATEST_RUN_ID successfully triggered: $RUN_URL"
58+
echo "[${{ inputs.workflow }} run (#$LATEST_RUN_ID)]($RUN_URL)" >> $GITHUB_STEP_SUMMARY
59+
echo "RUN_ID=$LATEST_RUN_ID" >> $GITHUB_OUTPUT
60+
echo "RUN_URL=$RUN_URL" >> $GITHUB_OUTPUT
61+
exit 0
62+
fi
63+
done
64+
65+
echo "[DEBUG] Unable to detect new run of workflow '${{ inputs.workflow }}'."
66+
exit 1
67+
shell: bash

0 commit comments

Comments
 (0)