Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve mavenLicenseCheck workflow #372

Closed
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 11 additions & 9 deletions .github/actions/maven-license-check-action/action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,20 +29,22 @@ runs:
shell: bash {0} # do not fail-fast
run: |
mvnArgs="-U -B -ntp org.eclipse.dash:license-tool-plugin:license-check -Ddash.fail=true -Dtycho.target.eager=true --settings $GITHUB_ACTION_PATH/licenseCheckMavenSettings.xml"
if [[ ${{ inputs.project-id }} != "" ]]; then
mvnArgs+=" -Ddash.repo=https://github.com/${{ github.repository }}"

if [[ "${{ inputs.project-id }}" != "" ]]; then
mvnArgs+=" -Ddash.projectId=${{ inputs.project-id }}"
fi
if [ ${{ inputs.request-review }} ]; then
if [ "${{ inputs.request-review }}" != "" ]; then
mvn ${mvnArgs} -Ddash.iplab.token=$GITLAB_API_TOKEN
if [[ $? == 0 ]]; then # All licenses are vetted
echo "build-succeeded=1" >> $GITHUB_OUTPUT
else
echo "build-succeeded=0" >> $GITHUB_OUTPUT
fi
else
mvn ${mvnArgs}
if [[ $? != 0 ]]; then
fi

if [[ $? == 0 ]]; then # All licenses are vetted
echo "build-succeeded=1" >> $GITHUB_OUTPUT
else
if [[ "${{ inputs.request-review }}" = "" ]]; then
echo "Committers can request a review by commenting '/request-license-review'"
exit 1
fi
echo "build-succeeded=0" >> $GITHUB_OUTPUT
fi
218 changes: 154 additions & 64 deletions .github/workflows/mavenLicenseCheck.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,29 +50,43 @@ on:
required: false

jobs:
check-licenses:
if: github.event_name != 'issue_comment' || ( github.event.issue.pull_request != '' && (github.event.comment.body == '/request-license-review') )
check-request:
# Run on all non-comment events specified by the calling workflow and for comments on PRs that have a corresponding body.
if: >
github.event_name != 'issue_comment' ||
(github.event.issue.pull_request &&
(contains(github.event.comment.body, '/request-license-review') ||
contains(github.event.comment.body, '/license-check')))
netomi marked this conversation as resolved.
Show resolved Hide resolved
runs-on: ubuntu-latest
permissions:
pull-requests: write
outputs:
request-review: ${{ steps.request-review.outputs.request-review }}
license-check: ${{ steps.license-check.outputs.license-check }}
steps:

- name: Check dependabot PR
run: echo "isDependabotPR=1" >> $GITHUB_ENV
- name: Check dependabot PR
if: >
github.event_name == 'pull_request' && (github.event.action == 'opened' || github.event.action == 'synchronize' || github.event.action == 'reopened')
github.event_name == 'pull_request'
&& (github.event.action == 'opened' || github.event.action == 'synchronize' || github.event.action == 'reopened')
&& github.actor == 'dependabot[bot]' && github.actor_id == '49699333'
# For 'issue_comment'-events this job only runs if a comment was added to a PR with body specified above
run: echo "isDependabotPR=1" >> $GITHUB_ENV

# For 'issue_comment'-events this job only runs if a comment was added to a PR with body specified above
- name: Set review request
run: echo "request-review=1" >> $GITHUB_ENV
if: github.event_name == 'issue_comment' || env.isDependabotPR
# For 'issue_comment'-events this job only runs if a comment was added to a PR with body specified above
id: request-review
if: (github.event_name == 'issue_comment' && contains(github.event.comment.body, '/request-license-review')) || env.isDependabotPR
run: |
echo "request-review=1" >> "$GITHUB_OUTPUT"

- name: Set license check
id: license-check
if: (github.event_name == 'issue_comment' && contains(github.event.comment.body, '/license-check')) || env.isDependabotPR
run: |
echo "license-check=1" >> "$GITHUB_OUTPUT"

- name: Process license-vetting request
if: env.request-review && (!env.isDependabotPR)
uses: actions/github-script@v7
if: |
(steps.request-review.outputs.request-review || steps.license-check.outputs.license-check)
&& (!env.isDependabotPR)
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
script: |
const payload = await github.rest.repos.getCollaboratorPermissionLevel({
Expand All @@ -90,27 +104,40 @@ jobs:
...context.repo, comment_id: context.payload?.comment?.id, content: reaction
});

# By default the git-ref checked out for events triggered by comments to PRs is 'refs/heads/master'
check-licenses:
needs: check-request
if: ${{needs.check-request.outputs.license-check == ''}}
runs-on: ubuntu-latest
permissions:
pull-requests: write
env:
request-review: ${{ needs.check-request.outputs.request-review }}
license-check: ${{ needs.check-request.outputs.license-check }}
comment-header: '<!-- tag-license-comment -->'
steps:
# By default, the git-ref checked out for events triggered by comments to PRs is 'refs/heads/master'
# and for events triggered by PR creation/updates the ref is 'refs/pull/<PR-number>/merge'.
# So by default only the master-branch would be considered when requesting license-reviews, but we want the PR's state.
# Unless the PR is closed, then we want the master-branch, which allows subsequent license review requests.
- uses: actions/checkout@v4
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
# use default ref 'refs/pull/<PR-number>/merge' for PR-events and 'refs/heads/master' for comments if the PR is closed
if: github.event.issue.pull_request == '' || github.event.issue.state != 'open'
with:
submodules: ${{ inputs.submodules }}
- uses: actions/checkout@v4

- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
if: github.event.issue.pull_request != '' && github.event.issue.state == 'open'
with:
ref: 'refs/pull/${{ github.event.issue.number }}/merge'
submodules: ${{ inputs.submodules }}
if: github.event.issue.pull_request != '' && github.event.issue.state == 'open'

- uses: actions/setup-java@v4
- uses: actions/setup-java@2dfa2011c5b2a0f1489bf9e433881c92c1631f88 # v4.3.0
with:
java-version: ${{ inputs.javaVersion }}
distribution: 'temurin'

- name: Cache local Maven repository
uses: actions/cache@v4
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
with:
path: ~/.m2/repository
# re-cache on changes in the pom and target files
Expand All @@ -124,8 +151,8 @@ jobs:
maven-version: ${{ inputs.mavenVersion }}

- name: Prepare for license check
run: ${{ inputs.setupScript }}
if: inputs.setupScript !=''
run: ${{ inputs.setupScript }}

- name: Check license vetting status (and ask for review if requested)
id: check-license-vetting
Expand All @@ -137,62 +164,125 @@ jobs:
GITLAB_API_TOKEN: ${{ secrets.gitlabAPIToken }}

- name: Process license check results
if: env.request-review
uses: actions/github-script@v7
id: process-results
if: always()
uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
with:
result-encoding: string
script: |
const fs = require('fs')

const licenesVetted = ${{ steps.check-license-vetting.outputs.licenses-vetted }}
let commentBody = ''
// if context.payload.comment is empty, this is an explicit review-request through a comment, if not an automated one, e.g. for dependabot PRs
if ( context.payload.comment ) {
commentBody += '> ' + context.payload.comment.body + '\n\n'
} else if ( licenesVetted ){
core.info('License review request made automatically but all licenses are already vetted.')
return; // Don't create a comment in this case, the checks in the UI indicate the state already.
} else {
// This run encountered pending reviews, which have been requested automatically, e.g. for dependabot PRs
core.setFailed("Some dependencies must be vetted and their review was requested. Rerun this check once these reviews succeeded.")
}

if( licenesVetted ) {
commentBody += ':heavy_check_mark: All licenses already successfully vetted.\n'
const reviewRequested = ${{ env.request-review || false }}
const updateRequested = reviewRequested || ${{ env.license-check || false }}
const licensesVetted = ${{ steps.check-license-vetting.outputs.licenses-vetted }}

let commentBody = "### License summary\n"
if (licensesVetted) {
if (updateRequested) {
commentBody += ':heavy_check_mark: All licenses already successfully vetted.\n'
} else {
// Do not comment if all licenses are vetted and no update was requested
core.info('All licenses are already vetted.')
return;
}
} else {

const reviewSummaryFile = process.env.GITHUB_WORKSPACE + '/target/dash/review-summary'
core.info("Read review summary at " + reviewSummaryFile)
// Print dependency info
const dependencySummaryFile = 'target/dash/summary'
core.info("Read dependency summary at " + dependencySummaryFile)
let content = "";
if ( fs.existsSync( reviewSummaryFile )) {
content = fs.readFileSync( reviewSummaryFile, {encoding: 'utf8'}).trim();
if (fs.existsSync(dependencySummaryFile)) {
content = fs.readFileSync(dependencySummaryFile, { encoding: 'utf8' }).trim();
}

if ( content ) { // not empty
commentBody += 'License review requests:\n'

if (content) { // not empty
commentBody += ":x: Not yet vetted dependencies:\n"
commentBody += "| Dependency | License | Status | Ticket |\n"
commentBody += "|------------|---------|--------|--------|\n"
const lines = content.split('\n')
for(var line = 0; line < lines.length; line++){
commentBody += ('- ' + lines[line] + '\n')
let notVettedDependencies = 0
for (const line of lines) {
if (line.includes('restricted')) {
commentBody += `| ${line.split(", ").join(" | ")} |\n`
notVettedDependencies++
}
}
core.setFailed(`${notVettedDependencies} dependencies are not vetted yet.`)
} else {
commentBody += ':warning: Failed to process summary.\n'
core.setFailed('Failed to process dash summary')
}

if (reviewRequested) {
const reviewSummaryFile = "target/dash/review-summary"
let reviews = "";
if (fs.existsSync(reviewSummaryFile)) {
reviews = fs.readFileSync(reviewSummaryFile, { encoding: 'utf8' }).trim();
}
if (reviews) { // not empty
commentBody += "\n### :rocket: Requested reviews:\n"
const lines = reviews.split('\n')
for (const line of lines) {
commentBody += `- ${line}\n`
}
} else {
core.setFailed("License vetting build failed, but no reviews are created")
commentBody += ':warning: Failed to request review of not vetted licenses.\n'
}
commentBody += '\n'
commentBody += 'After all reviews have concluded, re-run the license-vetting check from the Github Actions web-interface to update its status.\n'

} else {
core.setFailed("License vetting build failed, but no reviews are created")
commentBody += ':warning: Failed to request review of not vetted licenses.\n'
commentBody += '\n\n- Committers can request a license review via by commenting `/request-license-review`.\n- After all reviews have concluded, Committers can re-run the license-vetting check by commenting `/license-check`\n'
}
}
commentBody += '\n'
commentBody += 'Workflow run (with attached summary files):\n'
commentBody += context.serverUrl + "/" + process.env.GITHUB_REPOSITORY + "/actions/runs/" + context.runId

github.rest.issues.createComment({
issue_number: context.issue.number, ...context.repo, body: commentBody
})

- uses: actions/upload-artifact@v4
if: always() && env.request-review

commentBody += `\nWorkflow run (with attached summary files):\n${context.serverUrl}/${process.env.GITHUB_REPOSITORY}/actions/runs/${context.runId}`
return commentBody

- name: Adding comment to job summary
if: always()
run: |
echo '${{steps.process-results.outputs.result}}' >> $GITHUB_STEP_SUMMARY

# Adjust the comment header based on the requested action to ensure that request review comments
# do not get hidden by license checks.
- name: Determine comment header
if: ${{env.request-review}}
run: echo "comment-header='<!-- tag-review-request-comment -->'" >> "$GITHUB_ENV"

# Add the process result as comment to the PR if an update has been requested
# or if the PR is not coming from a fork (in which case we don't have write tokens)
- name: Adding comment to PR
uses: marocchino/sticky-pull-request-comment@331f8f5b4215f0445d3c07b4967662a32a2d3e31 # v2.9.0
if: |
always()
&& (github.event_name == 'issue_comment' || github.event.pull_request.head.repo.full_name == github.repository)
with:
header: ${{env.comment-header}}
hide_and_recreate: true
hide_classify: "OUTDATED"
number: ${{github.event.issue.number}}
message: |
${{steps.process-results.outputs.result}}

- uses: actions/upload-artifact@50769540e7f4bd5e21e526ee35c689e35e0d6874 # v4.4.0
if: always()
with:
name: '${{ inputs.projectId }}-license-vetting-summary'
path: |
target/dash/review-summary
target/dash/summary

# If a rerun is requested, trigger a rerun of the check for the HEAD SHA of the PR
# The reason we do that is because only workflows runs with trigger "pull_request"
# are displayed in the checks tab of a PR.
rerun-check:
needs: check-request
if: ${{needs.check-request.outputs.license-check == '1'}}
runs-on: ubuntu-latest
permissions:
actions: write
steps:
- name: Rerun license check
run: |
HEAD_SHA=$(gh api repos/${{ github.repository }}/pulls/${{ github.event.issue.number }} | jq -r '.head | .sha')
RUNID=$(gh api repos/${{ github.repository }}/commits/${HEAD_SHA}/check-runs | jq -r '.check_runs[] | select(.name | endswith("check-licenses")) | .html_url | capture("/runs/(?<number>[0-9]+)/job") | .number' | sed 's/"//g' | head -n 1)
gh run rerun ${RUNID} --repo ${{ github.repository }}
env:
GH_TOKEN: ${{ github.token }}
11 changes: 8 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -624,6 +624,11 @@ on:
jobs:
call-license-check:
uses: eclipse-dash/dash-licenses/.github/workflows/mavenLicenseCheck.yml@master
permissions:
contents: write
issues: write
pull-requests: write
actions: write
with:
projectId: <PROJECT-ID>
secrets:
Expand All @@ -632,17 +637,17 @@ jobs:
Projects that have to be set up in advance can use the `setupScript` parameter to pass a script that is executed before the license-check build is started.
Projects that want their git submodules to be checked out and processed can use the 'submodule' parameter.

On each pull-reqest event (i.e. a new PR is created or a new commit for it is pushed) the license-status of all project dependencies is checked automatically and in case unvetted licenses are found the check fails.
On each pull-request event (i.e. a new PR is created or a new commit for it is pushed) the license-status of all project dependencies is checked automatically and in case unvetted licenses are found the check fails.
Committers of that project can request a review from the IP team, by simply adding a comment with body `/request-license-review`.
The github-actions bot reacts with a 'rocket' to indicate the request was understood and is processed.
Attempts to request license review by non-committers are rejected with a thumps-down reaction.
After the license-review build has terminated the github-action bot will reply with a comment to show the result of the license review request.
Committers can later re-run this license-check workflow from the Github actions web-interface to check for license-status changes.
Committers can later re-run this license-check workflow from the GitHub actions web-interface to check for license-status changes of by adding a comment with body `/license-check`.

#### Requirements
- Maven based build
- Root pom.xml must reside in the repository root
- An [authentication token (scope: api) from gitlab.eclipse.org](README.md#automatic-ip-team-review-requests) has to be stored in the repositories secret store(Settings -> Scrects -> Actions) with name `M2E_GITLAB_API_TOKEN`.
- An [authentication token (scope: api) from gitlab.eclipse.org](README.md#automatic-ip-team-review-requests) has to be stored in the repositories secret store(Settings -> Secrets -> Actions) with name `M2E_GITLAB_API_TOKEN`.

## Advanced Scenarios

Expand Down
Loading