diff --git a/.github/workflows/ci-integ-test.yml b/.github/workflows/ci-integ-test.yml index 8748c3b..b04db0b 100644 --- a/.github/workflows/ci-integ-test.yml +++ b/.github/workflows/ci-integ-test.yml @@ -179,3 +179,7 @@ jobs: uses: ./.github/workflows/integ-test-detect-java-toolchains.yml with: runner-os: '["ubuntu-latest"]' + + wrapper-validation: + needs: [determine-suite, build-distribution] + uses: ./.github/workflows/integ-test-wrapper-validation.yml diff --git a/.github/workflows/integ-test-wrapper-validation.yml b/.github/workflows/integ-test-wrapper-validation.yml new file mode 100644 index 0000000..ce1df32 --- /dev/null +++ b/.github/workflows/integ-test-wrapper-validation.yml @@ -0,0 +1,68 @@ +name: Test sample Kotlin DSL project + +on: + workflow_call: + +jobs: + # Integration test for successful validation of wrappers + test-validation-success: + name: 'Test: Validation success' + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v4 + - name: Initialize integ-test + uses: ./.github/actions/init-integ-test + + - name: Run wrapper-validation-action + id: action-test + uses: ./wrapper-validation + with: + # to allow the invalid wrapper jar present in test data + allow-checksums: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 + + - name: Check outcome + env: + # Evaluate workflow expressions here as env variable values instead of inside shell script + # below to not accidentally inject code into shell script or break its syntax + FAILED_WRAPPERS: ${{ steps.action-test.outputs.failed-wrapper }} + FAILED_WRAPPERS_MATCHES: ${{ steps.action-test.outputs.failed-wrapper == '' }} + run: | + if [ "$FAILED_WRAPPERS_MATCHES" != "true" ] ; then + echo "'outputs.failed-wrapper' has unexpected content: $FAILED_WRAPPERS" + exit 1 + fi + + # Integration test for failing validation of wrappers + test-validation-error: + name: 'Test: Validation error' + runs-on: ubuntu-latest + steps: + - name: Checkout sources + uses: actions/checkout@v4 + - name: Initialize integ-test + uses: ./.github/actions/init-integ-test + + - name: Run wrapper-validation-action + id: action-test + uses: ./wrapper-validation + # Expected to fail; validated below + continue-on-error: true + + - name: Check outcome + env: + # Evaluate workflow expressions here as env variable values instead of inside shell script + # below to not accidentally inject code into shell script or break its syntax + VALIDATION_FAILED: ${{ steps.action-test.outcome == 'failure' }} + FAILED_WRAPPERS: ${{ steps.action-test.outputs.failed-wrapper }} + FAILED_WRAPPERS_MATCHES: ${{ steps.action-test.outputs.failed-wrapper == 'sources/test/jest/wrapper-validation/data/invalid/gradle-wrapper.jar|sources/test/jest/wrapper-validation/data/invalid/gradlе-wrapper.jar' }} + run: | + if [ "$VALIDATION_FAILED" != "true" ] ; then + echo "Expected validation to fail, but it didn't" + exit 1 + fi + + if [ "$FAILED_WRAPPERS_MATCHES" != "true" ] ; then + echo "'outputs.failed-wrapper' has unexpected content: $FAILED_WRAPPERS" + exit 1 + fi diff --git a/.github/workflows/update-checksums-file.js b/.github/workflows/update-checksums-file.js new file mode 100644 index 0000000..c492a8a --- /dev/null +++ b/.github/workflows/update-checksums-file.js @@ -0,0 +1,92 @@ +/* + * Updates the `wrapper-checksums.json` file + * + * This is intended to be executed by the GitHub workflow, but can also be run + * manually. + */ + +// @ts-check + +const httpm = require('typed-rest-client/HttpClient') + +const path = require('path') +const fs = require('fs') + +/** + * @returns {Promise} + */ +async function main() { + const httpc = new httpm.HttpClient( + 'gradle/wrapper-validation-action/update-checksums-workflow', + undefined, + {allowRetries: true, maxRetries: 3} + ) + + /** + * @param {string} url + * @returns {Promise} + */ + async function httpGetText(url) { + const response = await httpc.get(url) + return await response.readBody() + } + + /** + * @typedef {Object} ApiVersionEntry + * @property {string} version - version name + * @property {string=} wrapperChecksumUrl - wrapper checksum URL; not present for old versions + * @property {boolean} snapshot - whether this is a snapshot version + */ + + /** + * @returns {Promise} + */ + async function httpGetVersions() { + return JSON.parse( + await httpGetText('https://services.gradle.org/versions/all') + ) + } + + const versions = (await httpGetVersions()) + // Only include versions with checksum + .filter(e => e.wrapperChecksumUrl !== undefined) + // Ignore snapshots; they are changing frequently so no point in including them in checksums file + .filter(e => !e.snapshot) + console.info(`Got ${versions.length} relevant Gradle versions`) + + // Note: For simplicity don't sort the entries but keep the order from the API; this also has the + // advantage that the latest versions come first, so compared to appending versions at the end + // this will not cause redundant Git diff due to trailing `,` being forbidden by JSON + + /** + * @typedef {Object} FileVersionEntry + * @property {string} version + * @property {string} checksum + */ + /** @type {FileVersionEntry[]} */ + const fileVersions = [] + for (const entry of versions) { + /** @type {string} */ + // @ts-ignore + const checksumUrl = entry.wrapperChecksumUrl + const checksum = await httpGetText(checksumUrl) + fileVersions.push({version: entry.version, checksum}) + } + + const jsonPath = path.resolve( + __dirname, + '..', + '..', + 'src', + 'wrapper-checksums.json' + ) + console.info(`Writing checksums file to ${jsonPath}`) + // Write pretty-printed JSON (and add trailing line break) + fs.writeFileSync(jsonPath, JSON.stringify(fileVersions, null, 2) + '\n') +} + +main().catch(e => { + console.error(e) + // Manually set error exit code, otherwise error is logged but script exits successfully + process.exitCode = 1 +}) diff --git a/.github/workflows/update-checksums-file.yml b/.github/workflows/update-checksums-file.yml new file mode 100644 index 0000000..f5c2a96 --- /dev/null +++ b/.github/workflows/update-checksums-file.yml @@ -0,0 +1,50 @@ +name: 'Update Wrapper checksums file' + +on: + schedule: + # Run weekly (at arbitrary time) + - cron: '24 5 * * 6' + # Support running workflow manually + workflow_dispatch: + +jobs: + update-checksums: + name: Update checksums + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + cache-dependency-path: sources/package-lock.json + + - name: Install dependencies + run: | + npm install typed-rest-client@1.8.11 --no-save + + - name: Update checksums file + run: node ./.github/workflows/update-checksums-file.js + + # If there are no changes, this action will not create a pull request + - name: Create or update pull request + uses: peter-evans/create-pull-request@v6 + with: + branch: bot/wrapper-checksums-update + commit-message: Update known wrapper checksums + title: Update known wrapper checksums + # Note: Unfortunately this action cannot trigger the regular workflows for the PR automatically, see + # https://github.com/peter-evans/create-pull-request/blob/main/docs/concepts-guidelines.md#triggering-further-workflow-runs + # Therefore suggest below to close and then reopen the PR + body: | + Automatically generated pull request to update the known wrapper checksums. + + In case of conflicts, manually run the workflow from the [Actions tab](https://github.com/gradle/wrapper-validation-action/actions/workflows/update-checksums-file.yml), the changes will then be force-pushed onto this pull request branch. + Do not manually update the pull request branch; those changes might get overwritten. + + > [!IMPORTANT] + > GitHub workflows have not been executed for this pull request yet. Before merging, close and then directly reopen this pull request to trigger the workflows.