Compare commits
175 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 89c815032c | |||
| ff055cf07a | |||
| b08a06aa76 | |||
| 465d80c162 | |||
| 4992e8ce62 | |||
| c772daab92 | |||
| 45a4a89960 | |||
| b43c7e927f | |||
| 50d23622d0 | |||
| 2c2603ba0f | |||
| f7f53ea2b7 | |||
| e0f8c711d8 | |||
| e0843aabb1 | |||
| 26bb60c567 | |||
| c44230ba24 | |||
| 8d2fb6858f | |||
| 7756247c3a | |||
| 3387149a0f | |||
| 8b74bf9742 | |||
| 40abf87ae6 | |||
| 1c014eb512 | |||
| 8c69699458 | |||
| fc10d5fc29 | |||
| c79ebf4edf | |||
| a8d6c1d21f | |||
| 7c7f9380c7 | |||
| 888715bbb2 | |||
| 13feb87171 | |||
| faf9fb7337 | |||
| f33d7253b6 | |||
| db74e9d128 | |||
| 4559d269e0 | |||
| 627809d2dc | |||
| 4b1d7e9479 | |||
| d9980a4dfe | |||
| 8eb7e589eb | |||
| 0d7dce9d71 | |||
| da09788be9 | |||
| ead587c597 | |||
| 02459919cc | |||
| 57d212ddca | |||
| 539b17f6da | |||
| 37e1ad27fe | |||
| 043fdd6f03 | |||
| 3234e05085 | |||
| f957e3c051 | |||
| d66ad22652 | |||
| 7a860e1568 | |||
| 0d3c3c676d | |||
| 0f64d4c091 | |||
| 56a6133b20 | |||
| aaf20e7b61 | |||
| 148756076c | |||
| 6f321a27c4 | |||
| 539443b4be | |||
| 28424f8ae5 | |||
| a7d21f1b34 | |||
| d3a024786b | |||
| fa4c5449e7 | |||
| 2a3845a7ed | |||
| ff96a7b5ad | |||
| c37ff42234 | |||
| 0a00d84838 | |||
| 96c6751a1d | |||
| 8ee9c32788 | |||
| 7a5fb9eaf7 | |||
| 8cbb972cef | |||
| 539a11d0a4 | |||
| e97c5a533b | |||
| e0fa644b08 | |||
| 592db9180d | |||
| aca3fc2412 | |||
| 07f9afe057 | |||
| d7077b441a | |||
| 101f11ba94 | |||
| 18b903f6e3 | |||
| 038266a94f | |||
| cf559ab224 | |||
| 649428b49a | |||
| 0355e960d7 | |||
| fdad14af63 | |||
| 077c9e0b3e | |||
| dc648cf79f | |||
| c2d2914a4f | |||
| a40f73521c | |||
| 70863952ae | |||
| af95be34c6 | |||
| 71478c2534 | |||
| b289d12aad | |||
| e2361c5c79 | |||
| 8b9c8ca96c | |||
| 44e672d969 | |||
| 330f0f050d | |||
| e45dcb3ab2 | |||
| d3a451fffb | |||
| 9d378103b0 | |||
| 2031f101dc | |||
| 2a2c2dd209 | |||
| fa0e06ebdc | |||
| 6724627b50 | |||
| 4aab7b06a9 | |||
| d0d4d58c48 | |||
| 29be60d035 | |||
| 5024103b07 | |||
| d0327807a7 | |||
| f880f1ba26 | |||
| 71dafc0c06 | |||
| 7d9357b871 | |||
| 0b02bbecd1 | |||
| 932a5cc8b4 | |||
| e4a321511a | |||
| 4ee5800ec9 | |||
| c67632225d | |||
| 6e45a376cd | |||
| 70b67cd2f1 | |||
| 304e89041c | |||
| f1a76efc2d | |||
| 963251314b | |||
| ee061f3659 | |||
| 440ffce2eb | |||
| 47871562ca | |||
| f0c548cee7 | |||
| 1a32b399e6 | |||
| e9f3da94fc | |||
| 4fb6f936a7 | |||
| c431b5479e | |||
| fc03950afd | |||
| 516b5fed4a | |||
| 165e29e585 | |||
| 0be3e86b21 | |||
| 96d229a6d1 | |||
| 5098cad9cf | |||
| 883c480af0 | |||
| 0f83e40427 | |||
| ad6ef6e4c7 | |||
| 94201e46f1 | |||
| 910e198cee | |||
| 45a88fc391 | |||
| 8f01efb9e3 | |||
| f53c65b3a0 | |||
| 0be772ff5b | |||
| ac990afa5d | |||
| 55cccbf727 | |||
| a095ea75a2 | |||
| a041e7c943 | |||
| 4f3072b3d9 | |||
| 13c467734a | |||
| c01b2b946b | |||
| 549f9dab64 | |||
| 58090c37ea | |||
| 38344e8de6 | |||
| 6f319f0b13 | |||
| 4e71b26ea4 | |||
| 04f3bbcefa | |||
| c67ee6f2a8 | |||
| 4b590ce31f | |||
| 5902b46e9b | |||
| 62449d6fa2 | |||
| aa06088077 | |||
| 2a60776c8a | |||
| f411ecada2 | |||
| 8eeacf4a36 | |||
| 20c252982e | |||
| 6c5ac0d8ec | |||
| e1cb375fbf | |||
| e7e3f06044 | |||
| 994e93741b | |||
| 04e1a11e35 | |||
| 547056d5cc | |||
| 1fa35fb9a8 | |||
| a49914e631 | |||
| 783a43ea35 | |||
| 07a176d11e | |||
| c664fbc1a3 | |||
| af412c4d65 |
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"index_name": "security-docs",
|
||||
"start_urls": [
|
||||
"https://docs.spring.io/spring-security/reference/"
|
||||
],
|
||||
"selectors": {
|
||||
"lvl0": {
|
||||
"selector": "//nav[@class='crumbs']//li[@class='crumb'][last()-1]",
|
||||
"type": "xpath",
|
||||
"global": true,
|
||||
"default_value": "Home"
|
||||
},
|
||||
"lvl1": ".doc h1",
|
||||
"lvl2": ".doc h2",
|
||||
"lvl3": ".doc h3",
|
||||
"lvl4": ".doc h4",
|
||||
"text": ".doc p, .doc td.content, .doc th.tableblock"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
HOST="$1"
|
||||
HOST_PATH="$2"
|
||||
SSH_PRIVATE_KEY="$3"
|
||||
SSH_KNOWN_HOST="$4"
|
||||
|
||||
|
||||
if [ "$#" -ne 4 ]; then
|
||||
echo -e "not enough arguments USAGE:\n\n$0 \$HOST \$HOST_PATH \$SSH_PRIVATE_KEY \$SSH_KNOWN_HOSTS \n\n" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Use a non-default path to avoid overriding when testing locally
|
||||
SSH_PRIVATE_KEY_PATH=~/.ssh/github-actions-docs
|
||||
install -m 600 -D /dev/null "$SSH_PRIVATE_KEY_PATH"
|
||||
echo "$SSH_PRIVATE_KEY" > "$SSH_PRIVATE_KEY_PATH"
|
||||
echo "$SSH_KNOWN_HOST" > ~/.ssh/known_hosts
|
||||
rsync --delete -avze "ssh -i $SSH_PRIVATE_KEY_PATH" docs/build/site/ "$HOST:$HOST_PATH"
|
||||
rm -f "$SSH_PRIVATE_KEY_PATH"
|
||||
@@ -1,21 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
###
|
||||
# Docs
|
||||
# config.json https://docsearch.algolia.com/docs/config-file
|
||||
# Run the crawler https://docsearch.algolia.com/docs/run-your-own/#run-the-crawl-from-the-docker-image
|
||||
|
||||
### USAGE
|
||||
if [ "$#" -ne 3 ]; then
|
||||
echo -e "not enough arguments USAGE:\n\n$0 \$ALGOLIA_APPLICATION_ID \$ALGOLIA_API_KEY \$CONFIG_FILE\n\n" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Script Parameters
|
||||
APPLICATION_ID=$1
|
||||
API_KEY=$2
|
||||
CONFIG_FILE=$3
|
||||
|
||||
#### Script
|
||||
script_dir=$(dirname $0)
|
||||
docker run -e "APPLICATION_ID=$APPLICATION_ID" -e "API_KEY=$API_KEY" -e "CONFIG=$(cat $CONFIG_FILE | jq -r tostring)" algolia/docsearch-scraper
|
||||
@@ -1,16 +0,0 @@
|
||||
name: Update Algolia Index
|
||||
|
||||
on:
|
||||
schedule:
|
||||
- cron: '0 10 * * *' # Once per day at 10am UTC
|
||||
workflow_dispatch: # Manual trigger
|
||||
|
||||
jobs:
|
||||
update:
|
||||
name: Update Algolia Index
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Source
|
||||
uses: actions/checkout@v2
|
||||
- name: Update Index
|
||||
run: ${GITHUB_WORKSPACE}/.github/actions/algolia-docsearch-scraper.sh "${{ secrets.ALGOLIA_APPLICATION_ID }}" "${{ secrets.ALGOLIA_WRITE_API_KEY }}" "${GITHUB_WORKSPACE}/.github/actions/algolia-config.json"
|
||||
@@ -1,19 +1,30 @@
|
||||
name: Generate Antora Files and Request Build
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'gh-pages'
|
||||
tags: '**'
|
||||
|
||||
env:
|
||||
GH_ACTIONS_REPO_TOKEN: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository == 'spring-projects/spring-security' }}
|
||||
steps:
|
||||
- name: Checkout Source
|
||||
uses: actions/checkout@v2
|
||||
- name: Set up gradle
|
||||
uses: spring-io/spring-gradle-build-action@v1
|
||||
with:
|
||||
java-version: '11'
|
||||
distribution: 'adopt'
|
||||
- name: Generate antora.yml
|
||||
run: ./gradlew :spring-security-docs:generateAntora
|
||||
- name: Extract Branch Name
|
||||
|
||||
@@ -3,9 +3,15 @@ on:
|
||||
schedule:
|
||||
- cron: '0 10 * * *' # Once per day at 10am UTC
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
main:
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository == 'spring-projects/spring-security' }}
|
||||
permissions:
|
||||
contents: none
|
||||
steps:
|
||||
- name: Delete artifacts in cron job
|
||||
env:
|
||||
|
||||
@@ -2,6 +2,8 @@ name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '**'
|
||||
schedule:
|
||||
- cron: '0 10 * * *' # Once per day at 10am UTC
|
||||
workflow_dispatch: # Manual trigger
|
||||
@@ -16,19 +18,28 @@ env:
|
||||
STRUCTURE101_LICENSEID: ${{ secrets.STRUCTURE101_LICENSEID }}
|
||||
ARTIFACTORY_USERNAME: ${{ secrets.ARTIFACTORY_USERNAME }}
|
||||
ARTIFACTORY_PASSWORD: ${{ secrets.ARTIFACTORY_PASSWORD }}
|
||||
RUN_JOBS: ${{ github.repository == 'spring-projects/spring-security' }}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
prerequisites:
|
||||
name: Pre-requisites for building
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository == 'spring-projects/spring-security' }}
|
||||
outputs:
|
||||
runjobs: ${{ steps.continue.outputs.runjobs }}
|
||||
project_version: ${{ steps.continue.outputs.project_version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- id: continue
|
||||
name: Determine if should continue
|
||||
if: env.RUN_JOBS == 'true'
|
||||
run: echo "::set-output name=runjobs::true"
|
||||
run: |
|
||||
# Run jobs if in upstream repository
|
||||
echo "::set-output name=runjobs::true"
|
||||
# Extract version from gradle.properties
|
||||
version=$(cat gradle.properties | grep "version=" | awk -F'=' '{print $2}')
|
||||
echo "::set-output name=project_version::$version"
|
||||
build_jdk_11:
|
||||
name: Build JDK 11
|
||||
needs: [prerequisites]
|
||||
@@ -40,18 +51,14 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v1
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: '11'
|
||||
- name: Setup gradle user name
|
||||
run: |
|
||||
mkdir -p ~/.gradle
|
||||
echo 'systemProp.user.name=spring-builds+github' >> ~/.gradle/gradle.properties
|
||||
- name: Cache Gradle packages
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.gradle/caches
|
||||
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
|
||||
distribution: 'adopt'
|
||||
- name: Set up Gradle
|
||||
uses: gradle/gradle-build-action@v2
|
||||
- name: Set up gradle user name
|
||||
run: echo 'systemProp.user.name=spring-builds+github' >> gradle.properties
|
||||
- name: Build with Gradle
|
||||
env:
|
||||
GRADLE_ENTERPRISE_CACHE_USERNAME: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }}
|
||||
@@ -65,14 +72,11 @@ jobs:
|
||||
if: needs.prerequisites.outputs.runjobs
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v1
|
||||
- name: Set up gradle
|
||||
uses: spring-io/spring-gradle-build-action@v1
|
||||
with:
|
||||
java-version: '11'
|
||||
- name: Setup gradle user name
|
||||
run: |
|
||||
mkdir -p ~/.gradle
|
||||
echo 'systemProp.user.name=spring-builds+github' >> ~/.gradle/gradle.properties
|
||||
distribution: 'adopt'
|
||||
- name: Snapshot Tests
|
||||
run: |
|
||||
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
|
||||
@@ -86,20 +90,23 @@ jobs:
|
||||
if: needs.prerequisites.outputs.runjobs
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v1
|
||||
- name: Set up gradle
|
||||
uses: spring-io/spring-gradle-build-action@v1
|
||||
with:
|
||||
java-version: '11'
|
||||
- name: Setup gradle user name
|
||||
run: |
|
||||
mkdir -p ~/.gradle
|
||||
echo 'systemProp.user.name=spring-builds+github' >> ~/.gradle/gradle.properties
|
||||
distribution: 'adopt'
|
||||
- name: Check samples project
|
||||
env:
|
||||
LOCAL_REPOSITORY_PATH: ${{ github.workspace }}/build/publications/repos
|
||||
SAMPLES_DIR: ../spring-security-samples
|
||||
VERSION: ${{ needs.prerequisites.outputs.project_version }}
|
||||
run: |
|
||||
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
|
||||
export GRADLE_ENTERPRISE_CACHE_PASSWORD="$GRADLE_ENTERPRISE_CACHE_PASSWORD"
|
||||
export GRADLE_ENTERPRISE_ACCESS_KEY="$GRADLE_ENTERPRISE_SECRET_ACCESS_KEY"
|
||||
./gradlew checkSamples --stacktrace
|
||||
./gradlew publishMavenJavaPublicationToLocalRepository
|
||||
./gradlew cloneSamples -PcloneOutputDirectory="$SAMPLES_DIR"
|
||||
./gradlew --project-dir "$SAMPLES_DIR" --init-script spring-security-ci.gradle -PlocalRepositoryPath="$LOCAL_REPOSITORY_PATH" -PspringSecurityVersion="$VERSION" :runAllTests
|
||||
check_tangles:
|
||||
name: Check for Package Tangles
|
||||
needs: [ prerequisites ]
|
||||
@@ -107,14 +114,11 @@ jobs:
|
||||
if: needs.prerequisites.outputs.runjobs
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v1
|
||||
- name: Set up gradle
|
||||
uses: spring-io/spring-gradle-build-action@v1
|
||||
with:
|
||||
java-version: '11'
|
||||
- name: Setup gradle user name
|
||||
run: |
|
||||
mkdir -p ~/.gradle
|
||||
echo 'systemProp.user.name=spring-builds+github' >> ~/.gradle/gradle.properties
|
||||
distribution: 'adopt'
|
||||
- name: Check for package tangles
|
||||
run: |
|
||||
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
|
||||
@@ -127,14 +131,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v1
|
||||
- name: Set up gradle
|
||||
uses: spring-io/spring-gradle-build-action@v1
|
||||
with:
|
||||
java-version: '11'
|
||||
- name: Setup gradle user name
|
||||
run: |
|
||||
mkdir -p ~/.gradle
|
||||
echo 'systemProp.user.name=spring-builds+github' >> ~/.gradle/gradle.properties
|
||||
distribution: 'adopt'
|
||||
- name: Deploy artifacts
|
||||
run: |
|
||||
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
|
||||
@@ -154,14 +155,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v1
|
||||
- name: Set up gradle
|
||||
uses: spring-io/spring-gradle-build-action@v1
|
||||
with:
|
||||
java-version: '11'
|
||||
- name: Setup gradle user name
|
||||
run: |
|
||||
mkdir -p ~/.gradle
|
||||
echo 'systemProp.user.name=spring-builds+github' >> ~/.gradle/gradle.properties
|
||||
distribution: 'adopt'
|
||||
- name: Deploy Docs
|
||||
run: |
|
||||
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
|
||||
@@ -178,14 +176,11 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK
|
||||
uses: actions/setup-java@v1
|
||||
- name: Set up gradle
|
||||
uses: spring-io/spring-gradle-build-action@v1
|
||||
with:
|
||||
java-version: '11'
|
||||
- name: Setup gradle user name
|
||||
run: |
|
||||
mkdir -p ~/.gradle
|
||||
echo 'systemProp.user.name=spring-builds+github' >> ~/.gradle/gradle.properties
|
||||
distribution: 'adopt'
|
||||
- name: Deploy Schema
|
||||
run: |
|
||||
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
|
||||
@@ -196,14 +191,121 @@ jobs:
|
||||
DOCS_USERNAME: ${{ secrets.DOCS_USERNAME }}
|
||||
DOCS_SSH_KEY: ${{ secrets.DOCS_SSH_KEY }}
|
||||
DOCS_HOST: ${{ secrets.DOCS_HOST }}
|
||||
perform_release:
|
||||
name: Perform release
|
||||
needs: [prerequisites, deploy_artifacts, deploy_docs, deploy_schema]
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
timeout-minutes: 90
|
||||
if: ${{ !endsWith(needs.prerequisites.outputs.project_version, '-SNAPSHOT') }}
|
||||
env:
|
||||
REPO: ${{ github.repository }}
|
||||
BRANCH: ${{ github.ref_name }}
|
||||
TOKEN: ${{ github.token }}
|
||||
VERSION: ${{ needs.prerequisites.outputs.project_version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
with:
|
||||
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
|
||||
- name: Set up gradle
|
||||
uses: spring-io/spring-gradle-build-action@v1
|
||||
with:
|
||||
java-version: '11'
|
||||
distribution: 'adopt'
|
||||
- name: Wait for Artifactory Artifacts
|
||||
if: ${{ contains(needs.prerequisites.outputs.project_version, '-RC') || contains(needs.prerequisites.outputs.project_version, '-M') }}
|
||||
run: |
|
||||
echo "Wait for artifacts of $REPO@$VERSION to appear on Artifactory."
|
||||
until curl -f -s https://repo.spring.io/artifactory/milestone/org/springframework/security/spring-security-core/$VERSION/ > /dev/null
|
||||
do
|
||||
sleep 30
|
||||
echo "."
|
||||
done
|
||||
echo "Artifacts for $REPO@$VERSION have been released to Artifactory."
|
||||
- name: Wait for Maven Central Artifacts
|
||||
if: ${{ !contains(needs.prerequisites.outputs.project_version, '-RC') && !contains(needs.prerequisites.outputs.project_version, '-M') }}
|
||||
run: |
|
||||
echo "Wait for artifacts of $REPO@$VERSION to appear on Maven Central."
|
||||
until curl -f -s https://repo1.maven.org/maven2/org/springframework/security/spring-security-core/$VERSION/ > /dev/null
|
||||
do
|
||||
sleep 30
|
||||
echo "."
|
||||
done
|
||||
echo "Artifacts for $REPO@$VERSION have been released to Maven Central."
|
||||
- name: Create GitHub Release
|
||||
run: |
|
||||
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
|
||||
export GRADLE_ENTERPRISE_CACHE_PASSWORD="$GRADLE_ENTERPRISE_CACHE_PASSWORD"
|
||||
export GRADLE_ENTERPRISE_ACCESS_KEY="$GRADLE_ENTERPRISE_SECRET_ACCESS_KEY"
|
||||
echo "Tagging and publishing $REPO@$VERSION release on GitHub."
|
||||
./gradlew createGitHubRelease -PnextVersion=$VERSION -Pbranch=$BRANCH -PcreateRelease=true -PgitHubAccessToken=$TOKEN
|
||||
- name: Announce Release on Slack
|
||||
id: spring-security-announcing
|
||||
uses: slackapi/slack-github-action@v1.19.0
|
||||
with:
|
||||
payload: |
|
||||
{
|
||||
"text": "spring-security-announcing `${{ env.VERSION }}` is available now",
|
||||
"blocks": [
|
||||
{
|
||||
"type": "section",
|
||||
"text": {
|
||||
"type": "mrkdwn",
|
||||
"text": "spring-security-announcing `${{ env.VERSION }}` is available now"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SPRING_RELEASE_SLACK_WEBHOOK_URL }}
|
||||
SLACK_WEBHOOK_TYPE: INCOMING_WEBHOOK
|
||||
- name: Setup git config
|
||||
run: |
|
||||
git config user.name 'github-actions[bot]'
|
||||
git config user.email 'github-actions[bot]@users.noreply.github.com'
|
||||
- name: Update to next Snapshot Version
|
||||
run: |
|
||||
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
|
||||
export GRADLE_ENTERPRISE_CACHE_PASSWORD="$GRADLE_ENTERPRISE_CACHE_PASSWORD"
|
||||
export GRADLE_ENTERPRISE_ACCESS_KEY="$GRADLE_ENTERPRISE_SECRET_ACCESS_KEY"
|
||||
echo "Updating $REPO@$VERSION to next snapshot version."
|
||||
./gradlew :updateToSnapshotVersion
|
||||
git commit -am "Next development version"
|
||||
git push
|
||||
perform_post_release:
|
||||
name: Perform post-release
|
||||
needs: [prerequisites, deploy_artifacts, deploy_docs, deploy_schema]
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
timeout-minutes: 90
|
||||
if: ${{ endsWith(needs.prerequisites.outputs.project_version, '-SNAPSHOT') }}
|
||||
env:
|
||||
TOKEN: ${{ github.token }}
|
||||
VERSION: ${{ needs.prerequisites.outputs.project_version }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up gradle
|
||||
uses: spring-io/spring-gradle-build-action@v1
|
||||
with:
|
||||
java-version: '11'
|
||||
distribution: 'adopt'
|
||||
- name: Schedule next release (if not already scheduled)
|
||||
run: ./gradlew scheduleNextRelease -PnextVersion=$VERSION -PgitHubAccessToken=$TOKEN
|
||||
notify_result:
|
||||
name: Check for failures
|
||||
needs: [build_jdk_11, snapshot_tests, check_samples, check_tangles, deploy_artifacts, deploy_docs, deploy_schema]
|
||||
needs: [perform_release, perform_post_release]
|
||||
if: failure()
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
steps:
|
||||
- name: Send Slack message
|
||||
uses: Gamesight/slack-workflow-status@v1.0.1
|
||||
# Workaround while waiting for Gamesight/slack-workflow-status#38 to be fixed
|
||||
# See https://github.com/Gamesight/slack-workflow-status/issues/38
|
||||
uses: sjohnr/slack-workflow-status@v1-beta
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
slack_webhook_url: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
name: Build & Deploy Reference
|
||||
|
||||
on:
|
||||
repository_dispatch:
|
||||
types: request-build-reference
|
||||
schedule:
|
||||
- cron: '0 10 * * *' # Once per day at 10am UTC
|
||||
workflow_dispatch: # Manual trigger
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
name: deploy
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up JDK 11
|
||||
uses: actions/setup-java@v2
|
||||
with:
|
||||
java-version: '11'
|
||||
distribution: 'adopt'
|
||||
cache: gradle
|
||||
- name: Validate Gradle wrapper
|
||||
uses: gradle/wrapper-validation-action@e6e38bacfdf1a337459f332974bb2327a31aaf4b
|
||||
- name: Build with Gradle
|
||||
run: ./gradlew :spring-security-docs:antora --stacktrace
|
||||
- name: Cleanup Gradle Cache
|
||||
# Remove some files from the Gradle cache, so they aren't cached by GitHub Actions.
|
||||
# Restoring these files from a GitHub Actions cache might cause problems for future builds.
|
||||
run: |
|
||||
rm -f ~/.gradle/caches/modules-2/modules-2.lock
|
||||
rm -f ~/.gradle/caches/modules-2/gc.properties
|
||||
- name: Deploy
|
||||
run: ${GITHUB_WORKSPACE}/.github/actions/algolia-deploy.sh "${{ secrets.DOCS_USERNAME }}@${{ secrets.DOCS_HOST }}" "/opt/www/domains/spring.io/docs/htdocs/spring-security/reference/" "${{ secrets.DOCS_SSH_KEY }}" "${{ secrets.DOCS_SSH_HOST_KEY }}"
|
||||
@@ -5,10 +5,15 @@ on:
|
||||
env:
|
||||
DUE_ON: ${{ github.event.milestone.due_on }}
|
||||
TITLE: ${{ github.event.milestone.title }}
|
||||
permissions:
|
||||
contents: read
|
||||
jobs:
|
||||
spring-releasetrain-checks:
|
||||
name: Check DueOn is on a Release Date
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository == 'spring-projects/spring-security' }}
|
||||
permissions:
|
||||
contents: none
|
||||
steps:
|
||||
- name: Print Milestone Being Checked
|
||||
run: echo "Validating DueOn '$DUE_ON' for milestone '$TITLE'"
|
||||
@@ -23,6 +28,8 @@ jobs:
|
||||
needs: [spring-releasetrain-checks]
|
||||
if: failure()
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: read
|
||||
steps:
|
||||
- name: Send Slack message
|
||||
uses: Gamesight/slack-workflow-status@v1.0.1
|
||||
|
||||
@@ -2,27 +2,20 @@ name: PR Build
|
||||
|
||||
on: pull_request
|
||||
|
||||
env:
|
||||
RUN_JOBS: ${{ github.repository == 'spring-projects/spring-security' }}
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ github.repository == 'spring-projects/spring-security' }}
|
||||
steps:
|
||||
- if: env.RUN_JOBS == 'true'
|
||||
uses: actions/checkout@v2
|
||||
- name: Set up JDK
|
||||
if: env.RUN_JOBS == 'true'
|
||||
uses: actions/setup-java@v1
|
||||
- uses: actions/checkout@v2
|
||||
- name: Set up gradle
|
||||
uses: spring-io/spring-gradle-build-action@v1
|
||||
with:
|
||||
java-version: '11'
|
||||
- name: Cache Gradle packages
|
||||
if: env.RUN_JOBS == 'true'
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: ~/.gradle/caches
|
||||
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
|
||||
distribution: 'adopt'
|
||||
- name: Build with Gradle
|
||||
if: env.RUN_JOBS == 'true'
|
||||
run: ./gradlew clean build --continue --scan
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
name: Update Scheduled Release Version
|
||||
|
||||
on:
|
||||
workflow_dispatch: # Manual trigger only. Triggered by release-scheduler.yml on main.
|
||||
|
||||
env:
|
||||
SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
GRADLE_ENTERPRISE_CACHE_USER: ${{ secrets.GRADLE_ENTERPRISE_CACHE_USER }}
|
||||
GRADLE_ENTERPRISE_CACHE_PASSWORD: ${{ secrets.GRADLE_ENTERPRISE_CACHE_PASSWORD }}
|
||||
GRADLE_ENTERPRISE_SECRET_ACCESS_KEY: ${{ secrets.GRADLE_ENTERPRISE_SECRET_ACCESS_KEY }}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
update_scheduled_release_version:
|
||||
name: Initiate Release If Scheduled
|
||||
if: ${{ github.repository == 'spring-projects/spring-security' }}
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
actions: read
|
||||
steps:
|
||||
- id: checkout-source
|
||||
name: Checkout Source Code
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
token: ${{ secrets.GH_ACTIONS_REPO_TOKEN }}
|
||||
- name: Set up gradle
|
||||
uses: spring-io/spring-gradle-build-action@v1
|
||||
with:
|
||||
java-version: '11'
|
||||
distribution: 'adopt'
|
||||
- id: check-release-due
|
||||
name: Check Release Due
|
||||
run: |
|
||||
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
|
||||
export GRADLE_ENTERPRISE_CACHE_PASSWORD="$GRADLE_ENTERPRISE_CACHE_PASSWORD"
|
||||
export GRADLE_ENTERPRISE_ACCESS_KEY="$GRADLE_ENTERPRISE_SECRET_ACCESS_KEY"
|
||||
./gradlew gitHubCheckNextVersionDueToday
|
||||
echo "::set-output name=is_due_today::$(cat build/github/milestones/is-due-today)"
|
||||
- id: check-open-issues
|
||||
name: Check for open issues
|
||||
if: steps.check-release-due.outputs.is_due_today == 'true'
|
||||
run: |
|
||||
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
|
||||
export GRADLE_ENTERPRISE_CACHE_PASSWORD="$GRADLE_ENTERPRISE_CACHE_PASSWORD"
|
||||
export GRADLE_ENTERPRISE_ACCESS_KEY="$GRADLE_ENTERPRISE_SECRET_ACCESS_KEY"
|
||||
./gradlew gitHubCheckMilestoneHasNoOpenIssues
|
||||
echo "::set-output name=is_open_issues::$(cat build/github/milestones/is-open-issues)"
|
||||
- id: validate-release-state
|
||||
name: Validate State of Release
|
||||
if: steps.check-release-due.outputs.is_due_today == 'true' && steps.check-open-issues.outputs.is_open_issues == 'true'
|
||||
run: |
|
||||
echo "The release is due today but there are open issues"
|
||||
exit 1
|
||||
- id: update-version-and-push
|
||||
name: Update version and push
|
||||
if: steps.check-release-due.outputs.is_due_today == 'true' && steps.check-open-issues.outputs.is_open_issues == 'false'
|
||||
run: |
|
||||
export GRADLE_ENTERPRISE_CACHE_USERNAME="$GRADLE_ENTERPRISE_CACHE_USER"
|
||||
export GRADLE_ENTERPRISE_CACHE_PASSWORD="$GRADLE_ENTERPRISE_CACHE_PASSWORD"
|
||||
export GRADLE_ENTERPRISE_ACCESS_KEY="$GRADLE_ENTERPRISE_SECRET_ACCESS_KEY"
|
||||
git config user.name 'github-actions[bot]'
|
||||
git config user.email 'github-actions[bot]@users.noreply.github.com'
|
||||
./gradlew :updateProjectVersion
|
||||
updatedVersion=$(cat gradle.properties | grep "version=" | awk -F'=' '{print $2}')
|
||||
git commit -am "Release $updatedVersion"
|
||||
git tag $updatedVersion
|
||||
git push
|
||||
git push origin $updatedVersion
|
||||
- id: send-slack-notification
|
||||
name: Send Slack message
|
||||
if: failure()
|
||||
uses: Gamesight/slack-workflow-status@v1.0.1
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
slack_webhook_url: ${{ secrets.SLACK_WEBHOOK_URL }}
|
||||
channel: '#spring-security-ci'
|
||||
name: 'CI Notifier'
|
||||
@@ -0,0 +1,6 @@
|
||||
# Use sdkman to run "sdk env" to initialize with correct JDK version
|
||||
# Enable auto-env through the sdkman_auto_env config
|
||||
# See https://sdkman.io/usage#config
|
||||
# A summary is to add the following to ~/.sdkman/etc/config
|
||||
# sdkman_auto_env=true
|
||||
java=11.0.14-tem
|
||||
+9
-2
@@ -43,9 +43,12 @@ If you're considering anything more than correcting a typo or fixing a minor bug
|
||||
|
||||
If you have not previously done so, please fill out and submit the https://cla.pivotal.io/sign/spring[Contributor License Agreement].
|
||||
|
||||
= Create your branch from main
|
||||
= Create your branch from oldest maintenance branch
|
||||
|
||||
Create your topic branch to be submitted as a pull request from main. The Spring team will consider your pull request for backporting on a case-by-case basis; you don't need to worry about submitting anything for backporting.
|
||||
Create your topic branch to be submitted as a pull request from the oldest impacted and supported maintenance branch.
|
||||
You can find the supported versions by looking at the https://github.com/spring-projects/spring-security/milestones[milestones page].
|
||||
Switch to a branch named `<major>.<minor>.x` from the smallest milestone in the format of `<major>.<minor>.<patch>(-<prerelease>)`.
|
||||
The spring team will ensure the code gets merged forward into additional branches.
|
||||
|
||||
= Use short branch names
|
||||
|
||||
@@ -133,6 +136,10 @@ Search the codebase to find related unit tests and add additional `@Test` method
|
||||
|
||||
Update the https://www.relaxng.org[RELAX NG] schema `spring-security-x.y.rnc` instead of `spring-security-x.y.xsd` if you contribute changes to supported XML configuration. The XML schema file can be generated the following Gradle task:
|
||||
|
||||
----
|
||||
./gradlew :spring-security-config:rncToXsd
|
||||
----
|
||||
|
||||
Changes to the XML schema will be overwritten by the Gradle build task.
|
||||
|
||||
= Squash commits
|
||||
|
||||
+12
-2
@@ -78,6 +78,16 @@ Alternatively, you can manually check using https://github.com/spring-projects/s
|
||||
|
||||
Update the version number in `gradle.properties` for the release, for example `5.5.0-M1`, `5.5.0-RC1`, `5.5.0`
|
||||
|
||||
= Update Antora Version
|
||||
|
||||
You will need to update the antora.yml version.
|
||||
If you are unsure of what the values should be, the following task will instruct you what the expected values are:
|
||||
|
||||
[source,bash]
|
||||
----
|
||||
./gradlew :spring-security-docs:antoraCheckVersion
|
||||
----
|
||||
|
||||
= Build Locally
|
||||
|
||||
Run the build using
|
||||
@@ -119,13 +129,13 @@ git push origin 5.4.0-RC1
|
||||
|
||||
== 7. Update to Next Development Version
|
||||
|
||||
* Update `gradle.properties` version to next `+SNAPSHOT+` version and then push
|
||||
* Update `gradle.properties` version to next `+SNAPSHOT+` version, update antora.yml, and then push
|
||||
|
||||
== 8. Update version on project page
|
||||
|
||||
The following command will update https://spring.io/projects/spring-security#learn with the new release version using the following parameters
|
||||
|
||||
<github-personal-access-token> - Replace with a https://github.com/settings/tokens[GitHub personal access token] that has a scope of `public_repo`
|
||||
<github-personal-access-token> - Replace with a https://github.com/settings/tokens[GitHub personal access token] that has a scope of `read:org` as https://spring.io/restdocs/index.html#authentication[documented for spring.io api]
|
||||
<next-version> - Replace with the milestone you are releasing now (i.e. 5.5.0-RC1)
|
||||
<previous-version> - Replace with the previous release which will be removed from the listed versions (i.e. 5.5.0-M3)
|
||||
|
||||
|
||||
@@ -73,8 +73,8 @@ import org.springframework.util.Assert;
|
||||
* one in <tt>lookupObjectIdentities</tt>. These are built from the same select and "order
|
||||
* by" clause, using a different where clause in each case. In order to use custom schema
|
||||
* or column names, each of these SQL clauses can be customized, but they must be
|
||||
* consistent with each other and with the expected result set generated by the the
|
||||
* default values.
|
||||
* consistent with each other and with the expected result set generated by the default
|
||||
* values.
|
||||
*
|
||||
* @author Ben Alex
|
||||
*/
|
||||
|
||||
+59
-8
@@ -1,3 +1,5 @@
|
||||
import io.spring.gradle.IncludeRepoTask
|
||||
|
||||
buildscript {
|
||||
dependencies {
|
||||
classpath "io.spring.javaformat:spring-javaformat-gradle-plugin:$springJavaformatVersion"
|
||||
@@ -16,12 +18,13 @@ apply plugin: 'io.spring.nohttp'
|
||||
apply plugin: 'locks'
|
||||
apply plugin: 's101'
|
||||
apply plugin: 'io.spring.convention.root'
|
||||
apply plugin: 'io.spring.convention.include-check-remote'
|
||||
apply plugin: 'org.jetbrains.kotlin.jvm'
|
||||
apply plugin: 'org.springframework.security.update-dependencies'
|
||||
apply plugin: 'org.springframework.security.update-version'
|
||||
apply plugin: 'org.springframework.security.sagan'
|
||||
apply plugin: 'org.springframework.github.milestone'
|
||||
apply plugin: 'org.springframework.github.changelog'
|
||||
apply plugin: 'org.springframework.github.release'
|
||||
|
||||
group = 'org.springframework.security'
|
||||
description = 'Spring Security'
|
||||
@@ -35,7 +38,7 @@ repositories {
|
||||
}
|
||||
|
||||
tasks.named("saganCreateRelease") {
|
||||
referenceDocUrl = "https://docs.spring.io/spring-security/site/docs/{version}/reference/html5/"
|
||||
referenceDocUrl = "https://docs.spring.io/spring-security/reference/{version}/index.html"
|
||||
apiDocUrl = "https://docs.spring.io/spring-security/site/docs/{version}/api/"
|
||||
}
|
||||
|
||||
@@ -46,6 +49,43 @@ tasks.named("gitHubCheckMilestoneHasNoOpenIssues") {
|
||||
}
|
||||
}
|
||||
|
||||
tasks.named("gitHubNextReleaseMilestone") {
|
||||
repository {
|
||||
owner = "spring-projects"
|
||||
name = "spring-security"
|
||||
}
|
||||
}
|
||||
|
||||
tasks.named("gitHubCheckNextVersionDueToday") {
|
||||
repository {
|
||||
owner = "spring-projects"
|
||||
name = "spring-security"
|
||||
}
|
||||
}
|
||||
|
||||
tasks.named("scheduleNextRelease") {
|
||||
repository {
|
||||
owner = "spring-projects"
|
||||
name = "spring-security"
|
||||
}
|
||||
weekOfMonth = 3
|
||||
dayOfWeek = 1
|
||||
}
|
||||
|
||||
tasks.named("createGitHubRelease") {
|
||||
repository {
|
||||
owner = "spring-projects"
|
||||
name = "spring-security"
|
||||
}
|
||||
}
|
||||
|
||||
tasks.named("dispatchGitHubWorkflow") {
|
||||
repository {
|
||||
owner = "spring-projects"
|
||||
name = "spring-security"
|
||||
}
|
||||
}
|
||||
|
||||
tasks.named("updateDependencies") {
|
||||
// we aren't Gradle 7 compatible yet
|
||||
checkForGradleUpdate = false
|
||||
@@ -95,6 +135,18 @@ updateDependenciesSettings {
|
||||
selection.reject("org.opensaml maintains two different versions, so it must be updated manually");
|
||||
}
|
||||
}
|
||||
components.withModule("io.spring.javaformat:spring-javaformat-gradle-plugin") { selection ->
|
||||
ModuleComponentIdentifier candidate = selection.getCandidate();
|
||||
if (!candidate.getVersion().equals(selection.getCurrentVersion())) {
|
||||
selection.reject("spring-javaformat-gradle-plugin updates break checkstyle");
|
||||
}
|
||||
}
|
||||
components.withModule("io.spring.javaformat:spring-javaformat-checkstyle") { selection ->
|
||||
ModuleComponentIdentifier candidate = selection.getCandidate();
|
||||
if (!candidate.getVersion().equals(selection.getCurrentVersion())) {
|
||||
selection.reject("spring-javaformat-checkstyle updates break checkstyle");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -106,6 +158,7 @@ subprojects {
|
||||
tasks.withType(JavaCompile) {
|
||||
options.encoding = "UTF-8"
|
||||
options.compilerArgs.add("-parameters")
|
||||
options.release = 8
|
||||
}
|
||||
}
|
||||
|
||||
@@ -149,12 +202,10 @@ nohttp {
|
||||
|
||||
}
|
||||
|
||||
tasks.register('checkSamples') {
|
||||
includeCheckRemote {
|
||||
repository = 'spring-projects/spring-security-samples'
|
||||
ref = samplesBranch
|
||||
}
|
||||
dependsOn checkRemote
|
||||
tasks.register('cloneSamples', IncludeRepoTask) {
|
||||
repository = 'spring-projects/spring-security-samples'
|
||||
ref = samplesBranch
|
||||
outputDirectory = project.hasProperty("cloneOutputDirectory") ? project.file("$cloneOutputDirectory") : project.file("build/samples")
|
||||
}
|
||||
|
||||
s101 {
|
||||
|
||||
+20
-4
@@ -5,7 +5,6 @@ plugins {
|
||||
id 'com.apollographql.apollo' version '2.4.5'
|
||||
}
|
||||
|
||||
|
||||
sourceCompatibility = 1.8
|
||||
|
||||
repositories {
|
||||
@@ -27,6 +26,10 @@ sourceSets {
|
||||
|
||||
gradlePlugin {
|
||||
plugins {
|
||||
checkAntoraVersion {
|
||||
id = "org.springframework.antora.check-version"
|
||||
implementationClass = "org.springframework.gradle.antora.AntoraVersionPlugin"
|
||||
}
|
||||
trang {
|
||||
id = "trang"
|
||||
implementationClass = "trang.TrangPlugin"
|
||||
@@ -43,6 +46,10 @@ gradlePlugin {
|
||||
id = "org.springframework.security.update-dependencies"
|
||||
implementationClass = "org.springframework.security.convention.versions.UpdateDependenciesPlugin"
|
||||
}
|
||||
updateProjectVersion {
|
||||
id = "org.springframework.security.update-version"
|
||||
implementationClass = "org.springframework.security.convention.versions.UpdateProjectVersionPlugin"
|
||||
}
|
||||
sagan {
|
||||
id = "org.springframework.security.sagan"
|
||||
implementationClass = "org.springframework.gradle.sagan.SaganPlugin"
|
||||
@@ -55,6 +62,10 @@ gradlePlugin {
|
||||
id = "org.springframework.github.changelog"
|
||||
implementationClass = "org.springframework.gradle.github.changelog.GitHubChangelogPlugin"
|
||||
}
|
||||
githubRelease {
|
||||
id = "org.springframework.github.release"
|
||||
implementationClass = "org.springframework.gradle.github.release.GitHubReleasePlugin"
|
||||
}
|
||||
s101 {
|
||||
id = "s101"
|
||||
implementationClass = "s101.S101Plugin"
|
||||
@@ -72,10 +83,11 @@ dependencies {
|
||||
implementation 'com.google.code.gson:gson:2.8.6'
|
||||
implementation 'com.thaiopensource:trang:20091111'
|
||||
implementation 'net.sourceforge.saxon:saxon:9.1.0.8'
|
||||
implementation 'org.yaml:snakeyaml:1.30'
|
||||
implementation localGroovy()
|
||||
|
||||
implementation 'io.github.gradle-nexus:publish-plugin:1.1.0'
|
||||
implementation 'io.projectreactor:reactor-core:3.4.13'
|
||||
implementation 'io.projectreactor:reactor-core:3.4.24'
|
||||
implementation 'gradle.plugin.org.gretty:gretty:3.0.1'
|
||||
implementation 'com.apollographql.apollo:apollo-runtime:2.4.5'
|
||||
implementation 'com.github.ben-manes:gradle-versions-plugin:0.38.0'
|
||||
@@ -84,7 +96,7 @@ dependencies {
|
||||
implementation 'io.spring.nohttp:nohttp-gradle:0.0.10'
|
||||
implementation 'net.sourceforge.htmlunit:htmlunit:2.37.0'
|
||||
implementation 'org.hidetake:gradle-ssh-plugin:2.10.1'
|
||||
implementation 'org.jfrog.buildinfo:build-info-extractor-gradle:4.24.20'
|
||||
implementation 'org.jfrog.buildinfo:build-info-extractor-gradle:4.29.0'
|
||||
implementation 'org.sonarsource.scanner.gradle:sonarqube-gradle-plugin:2.7.1'
|
||||
|
||||
testImplementation platform('org.junit:junit-bom:5.8.2')
|
||||
@@ -99,7 +111,11 @@ dependencies {
|
||||
}
|
||||
|
||||
|
||||
test {
|
||||
tasks.named('test', Test).configure {
|
||||
onlyIf { !project.hasProperty("buildSrc.skipTests") }
|
||||
useJUnitPlatform()
|
||||
jvmArgs(
|
||||
'--add-opens', 'java.base/java.lang=ALL-UNNAMED',
|
||||
'--add-opens', 'java.base/java.util=ALL-UNNAMED'
|
||||
)
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ abstract class IncludeRepoTask extends DefaultTask {
|
||||
* Directory where the project template should be copied.
|
||||
*/
|
||||
@OutputDirectory
|
||||
final File outputDirectory = project.file("$project.buildDir/$name")
|
||||
File outputDirectory = project.file("$project.buildDir/$name")
|
||||
|
||||
@TaskAction
|
||||
void checkoutAndCopy() {
|
||||
|
||||
@@ -17,6 +17,7 @@ package io.spring.gradle.convention
|
||||
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.publish.maven.plugins.MavenPublishPlugin
|
||||
|
||||
class ArtifactoryPlugin implements Plugin<Project> {
|
||||
|
||||
@@ -36,8 +37,14 @@ class ArtifactoryPlugin implements Plugin<Project> {
|
||||
password = artifactoryPassword
|
||||
}
|
||||
}
|
||||
defaults {
|
||||
publications('mavenJava')
|
||||
}
|
||||
}
|
||||
project.plugins.withType(MavenPublishPlugin) {
|
||||
project.artifactory {
|
||||
publish {
|
||||
defaults {
|
||||
publications('mavenJava')
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package io.spring.gradle.convention
|
||||
|
||||
import io.spring.gradle.IncludeRepoTask
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.tasks.GradleBuild
|
||||
import org.gradle.api.tasks.TaskProvider
|
||||
|
||||
/**
|
||||
* Adds a set of tasks that make easy to clone a remote repository and perform some task
|
||||
*
|
||||
* @author Marcus Da Coregio
|
||||
*/
|
||||
class IncludeCheckRemotePlugin implements Plugin<Project> {
|
||||
@Override
|
||||
void apply(Project project) {
|
||||
IncludeCheckRemoteExtension extension = project.extensions.create('includeCheckRemote', IncludeCheckRemoteExtension)
|
||||
TaskProvider<IncludeRepoTask> includeRepoTask = project.tasks.register('includeRepo', IncludeRepoTask) { IncludeRepoTask it ->
|
||||
it.repository = extension.repository
|
||||
it.ref = extension.ref
|
||||
}
|
||||
project.tasks.register('checkRemote', GradleBuild) {
|
||||
it.dependsOn 'includeRepo'
|
||||
it.dir = includeRepoTask.get().outputDirectory
|
||||
it.tasks = extension.getTasks()
|
||||
}
|
||||
}
|
||||
|
||||
abstract static class IncludeCheckRemoteExtension {
|
||||
|
||||
/**
|
||||
* Git repository to clone
|
||||
*/
|
||||
String repository;
|
||||
|
||||
/**
|
||||
* Git ref to checkout
|
||||
*/
|
||||
String ref
|
||||
|
||||
/**
|
||||
* Task to run in the repository
|
||||
*/
|
||||
List<String> tasks = ['check']
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,6 +21,7 @@ import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.api.plugins.BasePlugin
|
||||
import org.gradle.api.plugins.PluginManager
|
||||
import org.springframework.gradle.classpath.CheckProhibitedDependenciesLifecyclePlugin
|
||||
import org.springframework.gradle.maven.SpringNexusPublishPlugin
|
||||
|
||||
class RootProjectPlugin implements Plugin<Project> {
|
||||
@@ -32,6 +33,8 @@ class RootProjectPlugin implements Plugin<Project> {
|
||||
pluginManager.apply(SchemaPlugin)
|
||||
pluginManager.apply(NoHttpPlugin)
|
||||
pluginManager.apply(SpringNexusPublishPlugin)
|
||||
pluginManager.apply(CheckProhibitedDependenciesLifecyclePlugin)
|
||||
pluginManager.apply(ArtifactoryPlugin)
|
||||
pluginManager.apply("org.sonarqube")
|
||||
|
||||
project.repositories.mavenCentral()
|
||||
|
||||
@@ -20,6 +20,7 @@ import org.gradle.api.Project
|
||||
import org.gradle.api.plugins.JavaLibraryPlugin;
|
||||
import org.gradle.api.plugins.MavenPlugin;
|
||||
import org.gradle.api.plugins.PluginManager
|
||||
import org.springframework.gradle.classpath.CheckClasspathForProhibitedDependenciesPlugin;
|
||||
import org.springframework.gradle.maven.SpringMavenPlugin;
|
||||
|
||||
/**
|
||||
@@ -32,6 +33,7 @@ class SpringModulePlugin extends AbstractSpringJavaPlugin {
|
||||
PluginManager pluginManager = project.getPluginManager();
|
||||
pluginManager.apply(JavaLibraryPlugin.class)
|
||||
pluginManager.apply(SpringMavenPlugin.class);
|
||||
pluginManager.apply(CheckClasspathForProhibitedDependenciesPlugin.class);
|
||||
pluginManager.apply("io.spring.convention.jacoco");
|
||||
|
||||
def deployArtifacts = project.task("deployArtifacts")
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
package org.springframework.gradle.antora;
|
||||
|
||||
import org.gradle.api.Action;
|
||||
import org.gradle.api.GradleException;
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.Task;
|
||||
import org.gradle.api.tasks.TaskProvider;
|
||||
import org.gradle.language.base.plugins.LifecycleBasePlugin;
|
||||
|
||||
public class AntoraVersionPlugin implements Plugin<Project> {
|
||||
public static final String ANTORA_CHECK_VERSION_TASK_NAME = "antoraCheckVersion";
|
||||
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
TaskProvider<CheckAntoraVersionTask> antoraCheckVersion = project.getTasks().register(ANTORA_CHECK_VERSION_TASK_NAME, CheckAntoraVersionTask.class, new Action<CheckAntoraVersionTask>() {
|
||||
@Override
|
||||
public void execute(CheckAntoraVersionTask antoraCheckVersion) {
|
||||
antoraCheckVersion.setGroup(LifecycleBasePlugin.VERIFICATION_GROUP);
|
||||
antoraCheckVersion.setDescription("Checks the antora.yml version properties match the Gradle version");
|
||||
antoraCheckVersion.getAntoraVersion().convention(project.provider(() -> getDefaultAntoraVersion(project)));
|
||||
antoraCheckVersion.getAntoraPrerelease().convention(project.provider(() -> getDefaultAntoraPrerelease(project)));
|
||||
antoraCheckVersion.getAntoraDisplayVersion().convention(project.provider(() -> getDefaultAntoraDisplayVersion(project)));
|
||||
antoraCheckVersion.getAntoraYmlFile().fileProvider(project.provider(() -> project.file("antora.yml")));
|
||||
}
|
||||
});
|
||||
project.getPlugins().withType(LifecycleBasePlugin.class, new Action<LifecycleBasePlugin>() {
|
||||
@Override
|
||||
public void execute(LifecycleBasePlugin lifecycleBasePlugin) {
|
||||
project.getTasks().named(LifecycleBasePlugin.CHECK_TASK_NAME).configure(new Action<Task>() {
|
||||
@Override
|
||||
public void execute(Task check) {
|
||||
check.dependsOn(antoraCheckVersion);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
project.getTasks().register("antoraUpdateVersion", UpdateAntoraVersionTask.class, new Action<UpdateAntoraVersionTask>() {
|
||||
@Override
|
||||
public void execute(UpdateAntoraVersionTask antoraUpdateVersion) {
|
||||
antoraUpdateVersion.setGroup("Release");
|
||||
antoraUpdateVersion.setDescription("Updates the antora.yml version properties to match the Gradle version");
|
||||
antoraUpdateVersion.getAntoraYmlFile().fileProvider(project.provider(() -> project.file("antora.yml")));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static String getDefaultAntoraVersion(Project project) {
|
||||
String projectVersion = getProjectVersion(project);
|
||||
return AntoraVersionUtils.getDefaultAntoraVersion(projectVersion);
|
||||
}
|
||||
|
||||
private static String getDefaultAntoraPrerelease(Project project) {
|
||||
String projectVersion = getProjectVersion(project);
|
||||
return AntoraVersionUtils.getDefaultAntoraPrerelease(projectVersion);
|
||||
}
|
||||
|
||||
private static String getDefaultAntoraDisplayVersion(Project project) {
|
||||
String projectVersion = getProjectVersion(project);
|
||||
return AntoraVersionUtils.getDefaultAntoraDisplayVersion(projectVersion);
|
||||
}
|
||||
|
||||
private static String getProjectVersion(Project project) {
|
||||
Object projectVersion = project.getVersion();
|
||||
if (projectVersion == null) {
|
||||
throw new GradleException("Please define antoraVersion and antoraPrerelease on " + ANTORA_CHECK_VERSION_TASK_NAME + " or provide a Project version so they can be defaulted");
|
||||
}
|
||||
return String.valueOf(projectVersion);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2019-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.gradle.antora;
|
||||
|
||||
public class AntoraVersionUtils {
|
||||
|
||||
public static String getDefaultAntoraVersion(String projectVersion) {
|
||||
int preReleaseIndex = getSnapshotIndex(projectVersion);
|
||||
return isSnapshot(projectVersion) ? projectVersion.substring(0, preReleaseIndex) : projectVersion;
|
||||
}
|
||||
|
||||
public static String getDefaultAntoraPrerelease(String projectVersion) {
|
||||
if (isSnapshot(projectVersion)) {
|
||||
int preReleaseIndex = getSnapshotIndex(projectVersion);
|
||||
return projectVersion.substring(preReleaseIndex);
|
||||
}
|
||||
if (isPreRelease(projectVersion)) {
|
||||
return Boolean.TRUE.toString();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static String getDefaultAntoraDisplayVersion(String projectVersion) {
|
||||
if (!isSnapshot(projectVersion) && isPreRelease(projectVersion)) {
|
||||
return getDefaultAntoraVersion(projectVersion);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static boolean isSnapshot(String projectVersion) {
|
||||
return getSnapshotIndex(projectVersion) >= 0;
|
||||
}
|
||||
|
||||
private static int getSnapshotIndex(String projectVersion) {
|
||||
return projectVersion.lastIndexOf("-SNAPSHOT");
|
||||
}
|
||||
|
||||
private static boolean isPreRelease(String projectVersion) {
|
||||
return projectVersion.lastIndexOf("-") >= 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package org.springframework.gradle.antora;
|
||||
|
||||
import org.gradle.api.DefaultTask;
|
||||
import org.gradle.api.GradleException;
|
||||
import org.gradle.api.file.RegularFileProperty;
|
||||
import org.gradle.api.provider.Property;
|
||||
import org.gradle.api.tasks.Input;
|
||||
import org.gradle.api.tasks.InputFile;
|
||||
import org.gradle.api.tasks.Optional;
|
||||
import org.gradle.api.tasks.TaskAction;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
import org.yaml.snakeyaml.constructor.Constructor;
|
||||
import org.yaml.snakeyaml.representer.Representer;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
|
||||
public abstract class CheckAntoraVersionTask extends DefaultTask {
|
||||
|
||||
@TaskAction
|
||||
public void check() throws FileNotFoundException {
|
||||
File antoraYmlFile = getAntoraYmlFile().getAsFile().get();
|
||||
String expectedAntoraVersion = getAntoraVersion().get();
|
||||
String expectedAntoraPrerelease = getAntoraPrerelease().getOrElse(null);
|
||||
String expectedAntoraDisplayVersion = getAntoraDisplayVersion().getOrElse(null);
|
||||
|
||||
Representer representer = new Representer();
|
||||
representer.getPropertyUtils().setSkipMissingProperties(true);
|
||||
|
||||
Yaml yaml = new Yaml(new Constructor(AntoraYml.class), representer);
|
||||
AntoraYml antoraYml = yaml.load(new FileInputStream(antoraYmlFile));
|
||||
|
||||
String actualAntoraPrerelease = antoraYml.getPrerelease();
|
||||
boolean preReleaseMatches = antoraYml.getPrerelease() == null && expectedAntoraPrerelease == null ||
|
||||
(actualAntoraPrerelease != null && actualAntoraPrerelease.equals(expectedAntoraPrerelease));
|
||||
String actualAntoraDisplayVersion = antoraYml.getDisplay_version();
|
||||
boolean displayVersionMatches = antoraYml.getDisplay_version() == null && expectedAntoraDisplayVersion == null ||
|
||||
(actualAntoraDisplayVersion != null && actualAntoraDisplayVersion.equals(expectedAntoraDisplayVersion));
|
||||
String actualAntoraVersion = antoraYml.getVersion();
|
||||
if (!preReleaseMatches ||
|
||||
!displayVersionMatches ||
|
||||
!expectedAntoraVersion.equals(actualAntoraVersion)) {
|
||||
throw new GradleException("The Gradle version of '" + getProject().getVersion() + "' should have version: '"
|
||||
+ expectedAntoraVersion + "' prerelease: '" + expectedAntoraPrerelease + "' display_version: '"
|
||||
+ expectedAntoraDisplayVersion + "' defined in " + antoraYmlFile + " but got version: '"
|
||||
+ actualAntoraVersion + "' prerelease: '" + actualAntoraPrerelease + "' display_version: '" + actualAntoraDisplayVersion + "'");
|
||||
}
|
||||
}
|
||||
|
||||
@InputFile
|
||||
public abstract RegularFileProperty getAntoraYmlFile();
|
||||
|
||||
@Input
|
||||
public abstract Property<String> getAntoraVersion();
|
||||
|
||||
@Input
|
||||
@Optional
|
||||
public abstract Property<String> getAntoraPrerelease();
|
||||
|
||||
@Input
|
||||
@Optional
|
||||
public abstract Property<String> getAntoraDisplayVersion();
|
||||
|
||||
public static class AntoraYml {
|
||||
private String version;
|
||||
|
||||
private String prerelease;
|
||||
|
||||
private String display_version;
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(String version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public String getPrerelease() {
|
||||
return prerelease;
|
||||
}
|
||||
|
||||
public void setPrerelease(String prerelease) {
|
||||
this.prerelease = prerelease;
|
||||
}
|
||||
|
||||
public String getDisplay_version() {
|
||||
return display_version;
|
||||
}
|
||||
|
||||
public void setDisplay_version(String display_version) {
|
||||
this.display_version = display_version;
|
||||
}
|
||||
}
|
||||
}
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
* Copyright 2019-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.gradle.antora;
|
||||
|
||||
import org.gradle.api.DefaultTask;
|
||||
import org.gradle.api.GradleException;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.file.RegularFileProperty;
|
||||
import org.gradle.api.provider.Property;
|
||||
import org.gradle.api.tasks.Input;
|
||||
import org.gradle.api.tasks.InputFile;
|
||||
import org.gradle.api.tasks.Optional;
|
||||
import org.gradle.api.tasks.OutputFile;
|
||||
import org.gradle.api.tasks.TaskAction;
|
||||
import org.yaml.snakeyaml.DumperOptions;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
import org.yaml.snakeyaml.constructor.Constructor;
|
||||
import org.yaml.snakeyaml.nodes.NodeTuple;
|
||||
import org.yaml.snakeyaml.nodes.Tag;
|
||||
import org.yaml.snakeyaml.representer.Representer;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.gradle.github.milestones.NextVersionYml;
|
||||
|
||||
public abstract class UpdateAntoraVersionTask extends DefaultTask {
|
||||
|
||||
@TaskAction
|
||||
public void update() throws IOException {
|
||||
String projectVersion = getProject().getVersion().toString();
|
||||
File antoraYmlFile = getAntoraYmlFile().getAsFile().get();
|
||||
String updatedAntoraVersion = AntoraVersionUtils.getDefaultAntoraVersion(projectVersion);
|
||||
String updatedAntoraPrerelease = AntoraVersionUtils.getDefaultAntoraPrerelease(projectVersion);
|
||||
String updatedAntoraDisplayVersion = AntoraVersionUtils.getDefaultAntoraDisplayVersion(projectVersion);
|
||||
|
||||
Representer representer = new Representer();
|
||||
representer.getPropertyUtils().setSkipMissingProperties(true);
|
||||
|
||||
Yaml yaml = new Yaml(new Constructor(AntoraYml.class), representer);
|
||||
AntoraYml antoraYml = yaml.load(new FileInputStream(antoraYmlFile));
|
||||
|
||||
System.out.println("Updating the version parameters in " + antoraYmlFile.getName() + " to version: "
|
||||
+ updatedAntoraVersion + ", prerelease: " + updatedAntoraPrerelease + ", display_version: "
|
||||
+ updatedAntoraDisplayVersion);
|
||||
antoraYml.setVersion(updatedAntoraVersion);
|
||||
antoraYml.setPrerelease(updatedAntoraPrerelease);
|
||||
antoraYml.setDisplay_version(updatedAntoraDisplayVersion);
|
||||
|
||||
FileWriter outputWriter = new FileWriter(antoraYmlFile);
|
||||
getYaml().dump(antoraYml, outputWriter);
|
||||
}
|
||||
|
||||
@InputFile
|
||||
public abstract RegularFileProperty getAntoraYmlFile();
|
||||
|
||||
public static class AntoraYml {
|
||||
|
||||
private String name;
|
||||
|
||||
private String version;
|
||||
|
||||
private String prerelease;
|
||||
|
||||
private String display_version;
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(String version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public String getPrerelease() {
|
||||
return prerelease;
|
||||
}
|
||||
|
||||
public void setPrerelease(String prerelease) {
|
||||
this.prerelease = prerelease;
|
||||
}
|
||||
|
||||
public String getDisplay_version() {
|
||||
return display_version;
|
||||
}
|
||||
|
||||
public void setDisplay_version(String display_version) {
|
||||
this.display_version = display_version;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private Yaml getYaml() {
|
||||
Representer representer = new Representer() {
|
||||
@Override
|
||||
protected NodeTuple representJavaBeanProperty(Object javaBean,
|
||||
org.yaml.snakeyaml.introspector.Property property, Object propertyValue, Tag customTag) {
|
||||
// Don't write out null values
|
||||
if (propertyValue == null) {
|
||||
return null;
|
||||
}
|
||||
else {
|
||||
return super.representJavaBeanProperty(javaBean, property, propertyValue, customTag);
|
||||
}
|
||||
}
|
||||
};
|
||||
representer.addClassTag(AntoraYml.class, Tag.MAP);
|
||||
DumperOptions ymlOptions = new DumperOptions();
|
||||
ymlOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
|
||||
ymlOptions.setDefaultScalarStyle(DumperOptions.ScalarStyle.SINGLE_QUOTED);
|
||||
return new Yaml(representer, ymlOptions);
|
||||
}
|
||||
|
||||
}
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright 2012-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.gradle.classpath;
|
||||
|
||||
import org.gradle.api.DefaultTask;
|
||||
import org.gradle.api.GradleException;
|
||||
import org.gradle.api.Task;
|
||||
import org.gradle.api.artifacts.Configuration;
|
||||
import org.gradle.api.artifacts.ModuleVersionIdentifier;
|
||||
import org.gradle.api.artifacts.ResolvedConfiguration;
|
||||
import org.gradle.api.file.FileCollection;
|
||||
import org.gradle.api.tasks.Classpath;
|
||||
import org.gradle.api.tasks.TaskAction;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.TreeSet;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* A {@link Task} for checking the classpath for prohibited dependencies.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class CheckClasspathForProhibitedDependencies extends DefaultTask {
|
||||
|
||||
private Configuration classpath;
|
||||
|
||||
public CheckClasspathForProhibitedDependencies() {
|
||||
getOutputs().upToDateWhen((task) -> true);
|
||||
}
|
||||
|
||||
public void setClasspath(Configuration classpath) {
|
||||
this.classpath = classpath;
|
||||
}
|
||||
|
||||
@Classpath
|
||||
public FileCollection getClasspath() {
|
||||
return this.classpath;
|
||||
}
|
||||
|
||||
@TaskAction
|
||||
public void checkForProhibitedDependencies() throws IOException {
|
||||
ResolvedConfiguration resolvedConfiguration = this.classpath.getResolvedConfiguration();
|
||||
TreeSet<String> prohibited = resolvedConfiguration.getResolvedArtifacts().stream()
|
||||
.map((artifact) -> artifact.getModuleVersion().getId()).filter(this::prohibited)
|
||||
.map((id) -> id.getGroup() + ":" + id.getName()).collect(Collectors.toCollection(TreeSet::new));
|
||||
if (!prohibited.isEmpty()) {
|
||||
StringBuilder message = new StringBuilder(String.format("Found prohibited dependencies in '%s':%n", this.classpath.getName()));
|
||||
for (String dependency : prohibited) {
|
||||
message.append(String.format(" %s%n", dependency));
|
||||
}
|
||||
throw new GradleException(message.toString());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean prohibited(ModuleVersionIdentifier id) {
|
||||
String group = id.getGroup();
|
||||
if (group.equals("javax.batch")) {
|
||||
return false;
|
||||
}
|
||||
if (group.equals("javax.cache")) {
|
||||
return false;
|
||||
}
|
||||
if (group.equals("javax.money")) {
|
||||
return false;
|
||||
}
|
||||
if (group.startsWith("javax")) {
|
||||
return true;
|
||||
}
|
||||
if (group.equals("commons-logging")) {
|
||||
return true;
|
||||
}
|
||||
if (group.equals("org.slf4j") && id.getName().equals("jcl-over-slf4j")) {
|
||||
return true;
|
||||
}
|
||||
if (group.startsWith("org.jboss.spec")) {
|
||||
return true;
|
||||
}
|
||||
if (group.equals("org.apache.geronimo.specs")) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
+67
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright 2012-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.gradle.classpath;
|
||||
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.artifacts.Configuration;
|
||||
import org.gradle.api.artifacts.ConfigurationContainer;
|
||||
import org.gradle.api.plugins.JavaBasePlugin;
|
||||
import org.gradle.api.tasks.SourceSetContainer;
|
||||
import org.gradle.api.tasks.TaskProvider;
|
||||
import org.gradle.language.base.plugins.LifecycleBasePlugin;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* @author Andy Wilkinson
|
||||
* @author Rob Winch
|
||||
*/
|
||||
public class CheckClasspathForProhibitedDependenciesPlugin implements Plugin<Project> {
|
||||
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
project.getPlugins().apply(CheckProhibitedDependenciesLifecyclePlugin.class);
|
||||
project.getPlugins().withType(JavaBasePlugin.class, javaBasePlugin -> {
|
||||
configureProhibitedDependencyChecks(project);
|
||||
});
|
||||
}
|
||||
|
||||
private void configureProhibitedDependencyChecks(Project project) {
|
||||
SourceSetContainer sourceSets = project.getExtensions().getByType(SourceSetContainer.class);
|
||||
sourceSets.all((sourceSet) -> createProhibitedDependenciesChecks(project,
|
||||
sourceSet.getCompileClasspathConfigurationName(), sourceSet.getRuntimeClasspathConfigurationName()));
|
||||
}
|
||||
|
||||
private void createProhibitedDependenciesChecks(Project project, String... configurationNames) {
|
||||
ConfigurationContainer configurations = project.getConfigurations();
|
||||
for (String configurationName : configurationNames) {
|
||||
Configuration configuration = configurations.getByName(configurationName);
|
||||
createProhibitedDependenciesCheck(configuration, project);
|
||||
}
|
||||
}
|
||||
|
||||
private void createProhibitedDependenciesCheck(Configuration classpath, Project project) {
|
||||
String taskName = "check" + StringUtils.capitalize(classpath.getName() + "ForProhibitedDependencies");
|
||||
TaskProvider<CheckClasspathForProhibitedDependencies> checkClasspathTask = project.getTasks().register(taskName,
|
||||
CheckClasspathForProhibitedDependencies.class, checkClasspath -> {
|
||||
checkClasspath.setGroup(LifecycleBasePlugin.CHECK_TASK_NAME);
|
||||
checkClasspath.setDescription("Checks " + classpath.getName() + " for prohibited dependencies");
|
||||
checkClasspath.setClasspath(classpath);
|
||||
});
|
||||
project.getTasks().named(CheckProhibitedDependenciesLifecyclePlugin.CHECK_PROHIBITED_DEPENDENCIES_TASK_NAME, checkProhibitedTask -> checkProhibitedTask.dependsOn(checkClasspathTask));
|
||||
}
|
||||
}
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2012-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.gradle.classpath;
|
||||
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.Task;
|
||||
import org.gradle.api.plugins.JavaBasePlugin;
|
||||
import org.gradle.api.tasks.TaskProvider;
|
||||
|
||||
/**
|
||||
* @author Rob Winch
|
||||
*/
|
||||
public class CheckProhibitedDependenciesLifecyclePlugin implements Plugin<Project> {
|
||||
public static final String CHECK_PROHIBITED_DEPENDENCIES_TASK_NAME = "checkForProhibitedDependencies";
|
||||
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
TaskProvider<Task> checkProhibitedDependencies = project.getTasks().register(CheckProhibitedDependenciesLifecyclePlugin.CHECK_PROHIBITED_DEPENDENCIES_TASK_NAME, task -> {
|
||||
task.setGroup(JavaBasePlugin.VERIFICATION_GROUP);
|
||||
task.setDescription("Checks both the compile/runtime classpath of every SourceSet for prohibited dependencies");
|
||||
});
|
||||
project.getTasks().named(JavaBasePlugin.CHECK_TASK_NAME, checkTask -> {
|
||||
checkTask.dependsOn(checkProhibitedDependencies);
|
||||
});
|
||||
}
|
||||
}
|
||||
+9
-4
@@ -1,10 +1,16 @@
|
||||
package org.springframework.gradle.github.milestones;
|
||||
public class RepositoryRef {
|
||||
package org.springframework.gradle.github;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
public class RepositoryRef implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 7151218536746822797L;
|
||||
|
||||
private String owner;
|
||||
|
||||
private String name;
|
||||
|
||||
RepositoryRef() {
|
||||
public RepositoryRef() {
|
||||
}
|
||||
|
||||
public RepositoryRef(String owner, String name) {
|
||||
@@ -62,4 +68,3 @@ public class RepositoryRef {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
-4
@@ -16,6 +16,9 @@
|
||||
|
||||
package org.springframework.gradle.github.changelog;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import org.gradle.api.Action;
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
@@ -28,12 +31,10 @@ import org.gradle.api.artifacts.repositories.IvyArtifactRepository;
|
||||
import org.gradle.api.artifacts.repositories.IvyPatternRepositoryLayout;
|
||||
import org.gradle.api.tasks.JavaExec;
|
||||
|
||||
import java.io.File;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
public class GitHubChangelogPlugin implements Plugin<Project> {
|
||||
|
||||
public static final String CHANGELOG_GENERATOR_CONFIGURATION_NAME = "changelogGenerator";
|
||||
public static final String RELEASE_NOTES_PATH = "changelog/release-notes.md";
|
||||
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
@@ -42,7 +43,7 @@ public class GitHubChangelogPlugin implements Plugin<Project> {
|
||||
project.getTasks().register("generateChangelog", JavaExec.class, new Action<JavaExec>() {
|
||||
@Override
|
||||
public void execute(JavaExec generateChangelog) {
|
||||
File outputFile = project.file(Paths.get(project.getBuildDir().getPath(), "changelog/release-notes.md"));
|
||||
File outputFile = project.file(Paths.get(project.getBuildDir().getPath(), RELEASE_NOTES_PATH));
|
||||
outputFile.getParentFile().mkdirs();
|
||||
generateChangelog.setGroup("Release");
|
||||
generateChangelog.setDescription("Generates the changelog");
|
||||
|
||||
+176
-19
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2019-2020 the original author or authors.
|
||||
* Copyright 2019-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,22 +16,35 @@
|
||||
|
||||
package org.springframework.gradle.github.milestones;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import com.google.common.reflect.TypeToken;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import okhttp3.Interceptor;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import org.springframework.gradle.github.RepositoryRef;
|
||||
|
||||
public class GitHubMilestoneApi {
|
||||
private String baseUrl = "https://api.github.com";
|
||||
|
||||
private OkHttpClient client;
|
||||
|
||||
private Gson gson = new Gson();
|
||||
private final Gson gson = new GsonBuilder()
|
||||
.registerTypeAdapter(LocalDate.class, new LocalDateAdapter().nullSafe())
|
||||
.registerTypeAdapter(LocalDateTime.class, new LocalDateTimeAdapter().nullSafe())
|
||||
.create();
|
||||
|
||||
public GitHubMilestoneApi() {
|
||||
this.client = new OkHttpClient.Builder().build();
|
||||
@@ -48,26 +61,30 @@ public class GitHubMilestoneApi {
|
||||
}
|
||||
|
||||
public long findMilestoneNumberByTitle(RepositoryRef repositoryRef, String milestoneTitle) {
|
||||
List<Milestone> milestones = this.getMilestones(repositoryRef);
|
||||
for (Milestone milestone : milestones) {
|
||||
if (milestoneTitle.equals(milestone.getTitle())) {
|
||||
return milestone.getNumber();
|
||||
}
|
||||
}
|
||||
if (milestones.size() <= 100) {
|
||||
throw new RuntimeException("Could not find open milestone with title " + milestoneTitle + " for repository " + repositoryRef + " Got " + milestones);
|
||||
}
|
||||
throw new RuntimeException("It is possible there are too many open milestones (only 100 are supported). Could not find open milestone with title " + milestoneTitle + " for repository " + repositoryRef + " Got " + milestones);
|
||||
}
|
||||
|
||||
public List<Milestone> getMilestones(RepositoryRef repositoryRef) {
|
||||
String url = this.baseUrl + "/repos/" + repositoryRef.getOwner() + "/" + repositoryRef.getName() + "/milestones?per_page=100";
|
||||
Request request = new Request.Builder().get().url(url)
|
||||
.build();
|
||||
try {
|
||||
Response response = this.client.newCall(request).execute();
|
||||
if (!response.isSuccessful()) {
|
||||
throw new RuntimeException("Could not find milestone with title " + milestoneTitle + " for repository " + repositoryRef + ". Response " + response);
|
||||
throw new RuntimeException("Could not retrieve milestones for repository " + repositoryRef + ". Response " + response);
|
||||
}
|
||||
List<Milestone> milestones = this.gson.fromJson(response.body().charStream(), new TypeToken<List<Milestone>>(){}.getType());
|
||||
for (Milestone milestone : milestones) {
|
||||
if (milestoneTitle.equals(milestone.getTitle())) {
|
||||
return milestone.getNumber();
|
||||
}
|
||||
}
|
||||
if (milestones.size() <= 100) {
|
||||
throw new RuntimeException("Could not find open milestone with title " + milestoneTitle + " for repository " + repositoryRef + " Got " + milestones);
|
||||
}
|
||||
throw new RuntimeException("It is possible there are too many open milestones open (only 100 are supported). Could not find open milestone with title " + milestoneTitle + " for repository " + repositoryRef + " Got " + milestones);
|
||||
return this.gson.fromJson(response.body().charStream(), new TypeToken<List<Milestone>>(){}.getType());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Could not find open milestone with title " + milestoneTitle + " for repository " + repositoryRef, e);
|
||||
throw new RuntimeException("Could not retrieve milestones for repository " + repositoryRef, e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,10 +104,150 @@ public class GitHubMilestoneApi {
|
||||
}
|
||||
}
|
||||
|
||||
// public boolean isOpenIssuesForMilestoneName(String owner, String repository, String milestoneName) {
|
||||
//
|
||||
// }
|
||||
/**
|
||||
* Check if the given milestone is due today or past due.
|
||||
*
|
||||
* @param repositoryRef The repository owner/name
|
||||
* @param milestoneTitle The title of the milestone whose due date should be checked
|
||||
* @return true if the given milestone is due today or past due, false otherwise
|
||||
*/
|
||||
public boolean isMilestoneDueToday(RepositoryRef repositoryRef, String milestoneTitle) {
|
||||
String url = this.baseUrl + "/repos/" + repositoryRef.getOwner() + "/" + repositoryRef.getName()
|
||||
+ "/milestones?per_page=100";
|
||||
Request request = new Request.Builder().get().url(url).build();
|
||||
try {
|
||||
Response response = this.client.newCall(request).execute();
|
||||
if (!response.isSuccessful()) {
|
||||
throw new RuntimeException("Could not find milestone with title " + milestoneTitle + " for repository "
|
||||
+ repositoryRef + ". Response " + response);
|
||||
}
|
||||
List<Milestone> milestones = this.gson.fromJson(response.body().charStream(),
|
||||
new TypeToken<List<Milestone>>() {
|
||||
}.getType());
|
||||
for (Milestone milestone : milestones) {
|
||||
if (milestoneTitle.equals(milestone.getTitle())) {
|
||||
LocalDate today = LocalDate.now();
|
||||
return milestone.getDueOn() != null && today.compareTo(milestone.getDueOn().toLocalDate()) >= 0;
|
||||
}
|
||||
}
|
||||
if (milestones.size() <= 100) {
|
||||
throw new RuntimeException("Could not find open milestone with title " + milestoneTitle
|
||||
+ " for repository " + repositoryRef + " Got " + milestones);
|
||||
}
|
||||
throw new RuntimeException(
|
||||
"It is possible there are too many open milestones open (only 100 are supported). Could not find open milestone with title "
|
||||
+ milestoneTitle + " for repository " + repositoryRef + " Got " + milestones);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new RuntimeException(
|
||||
"Could not find open milestone with title " + milestoneTitle + " for repository " + repositoryRef,
|
||||
e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the next release version based on the current version.
|
||||
*
|
||||
* The current version must conform to the pattern MAJOR.MINOR.PATCH-SNAPSHOT. If the
|
||||
* current version is a snapshot of a patch release, then the patch release will be
|
||||
* returned. For example, if the current version is 5.6.1-SNAPSHOT, then 5.6.1 will be
|
||||
* returned. If the current version is a snapshot of a version that is not GA (i.e the
|
||||
* PATCH segment is 0), then GitHub will be queried to find the next milestone or
|
||||
* release candidate. If no pre-release versions are found, then the next version will
|
||||
* be assumed to be the GA.
|
||||
* @param repositoryRef The repository owner/name
|
||||
* @param currentVersion The current project version
|
||||
* @return the next matching milestone/release candidate or null if none exist
|
||||
*/
|
||||
public String getNextReleaseMilestone(RepositoryRef repositoryRef, String currentVersion) {
|
||||
Pattern snapshotPattern = Pattern.compile("^([0-9]+)\\.([0-9]+)\\.([0-9]+)-SNAPSHOT$");
|
||||
Matcher snapshotVersion = snapshotPattern.matcher(currentVersion);
|
||||
|
||||
if (snapshotVersion.find()) {
|
||||
String patchSegment = snapshotVersion.group(3);
|
||||
String currentVersionNoIdentifier = currentVersion.replace("-SNAPSHOT", "");
|
||||
if (patchSegment.equals("0")) {
|
||||
String nextPreRelease = getNextPreRelease(repositoryRef, currentVersionNoIdentifier);
|
||||
return nextPreRelease != null ? nextPreRelease : currentVersionNoIdentifier;
|
||||
}
|
||||
else {
|
||||
return currentVersionNoIdentifier;
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new IllegalStateException(
|
||||
"Cannot calculate next release version because the current project version does not conform to the expected format");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the next pre-release version (milestone or release candidate) based on
|
||||
* the current version.
|
||||
*
|
||||
* The current version must conform to the pattern MAJOR.MINOR.PATCH. If no matching
|
||||
* milestone or release candidate is found in GitHub then it will return null.
|
||||
* @param repositoryRef The repository owner/name
|
||||
* @param currentVersionNoIdentifier The current project version without any
|
||||
* identifier
|
||||
* @return the next matching milestone/release candidate or null if none exist
|
||||
*/
|
||||
private String getNextPreRelease(RepositoryRef repositoryRef, String currentVersionNoIdentifier) {
|
||||
String url = this.baseUrl + "/repos/" + repositoryRef.getOwner() + "/" + repositoryRef.getName()
|
||||
+ "/milestones?per_page=100";
|
||||
Request request = new Request.Builder().get().url(url).build();
|
||||
try {
|
||||
Response response = this.client.newCall(request).execute();
|
||||
if (!response.isSuccessful()) {
|
||||
throw new RuntimeException(
|
||||
"Could not get milestones for repository " + repositoryRef + ". Response " + response);
|
||||
}
|
||||
List<Milestone> milestones = this.gson.fromJson(response.body().charStream(),
|
||||
new TypeToken<List<Milestone>>() {
|
||||
}.getType());
|
||||
Optional<String> nextPreRelease = milestones.stream().map(Milestone::getTitle)
|
||||
.filter(m -> m.startsWith(currentVersionNoIdentifier + "-"))
|
||||
.min((m1, m2) -> {
|
||||
Pattern preReleasePattern = Pattern.compile("^.*-([A-Z]+)([0-9]+)$");
|
||||
Matcher matcher1 = preReleasePattern.matcher(m1);
|
||||
Matcher matcher2 = preReleasePattern.matcher(m2);
|
||||
matcher1.find();
|
||||
matcher2.find();
|
||||
if (!matcher1.group(1).equals(matcher2.group(1))) {
|
||||
return m1.compareTo(m2);
|
||||
}
|
||||
else {
|
||||
return Integer.valueOf(matcher1.group(2)).compareTo(Integer.valueOf(matcher2.group(2)));
|
||||
}
|
||||
});
|
||||
return nextPreRelease.orElse(null);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new RuntimeException("Could not find open milestones with for repository " + repositoryRef, e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a milestone.
|
||||
*
|
||||
* @param repository The repository owner/name
|
||||
* @param milestone The milestone containing a title and due date
|
||||
*/
|
||||
public void createMilestone(RepositoryRef repository, Milestone milestone) {
|
||||
String url = this.baseUrl + "/repos/" + repository.getOwner() + "/" + repository.getName() + "/milestones";
|
||||
String json = this.gson.toJson(milestone);
|
||||
RequestBody body = RequestBody.create(MediaType.parse("application/json"), json);
|
||||
Request request = new Request.Builder().url(url).post(body).build();
|
||||
try {
|
||||
Response response = this.client.newCall(request).execute();
|
||||
if (!response.isSuccessful()) {
|
||||
throw new RuntimeException(String.format("Could not create milestone %s for repository %s/%s. Got response %s",
|
||||
milestone.getTitle(), repository.getOwner(), repository.getName(), response));
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
throw new RuntimeException(String.format("Could not create release %s for repository %s/%s",
|
||||
milestone.getTitle(), repository.getOwner(), repository.getName()), ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static class AuthorizationInterceptor implements Interceptor {
|
||||
|
||||
|
||||
+42
-6
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2019-2020 the original author or authors.
|
||||
* Copyright 2019-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,30 +17,66 @@ package org.springframework.gradle.github.milestones;
|
||||
|
||||
import org.gradle.api.Action;
|
||||
import org.gradle.api.DefaultTask;
|
||||
import org.gradle.api.file.RegularFileProperty;
|
||||
import org.gradle.api.tasks.Input;
|
||||
import org.gradle.api.tasks.InputFile;
|
||||
import org.gradle.api.tasks.Optional;
|
||||
import org.gradle.api.tasks.OutputFile;
|
||||
import org.gradle.api.tasks.TaskAction;
|
||||
import org.gradle.work.DisableCachingByDefault;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
import org.yaml.snakeyaml.constructor.Constructor;
|
||||
|
||||
public class GitHubMilestoneHasNoOpenIssuesTask extends DefaultTask {
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import org.springframework.gradle.github.RepositoryRef;
|
||||
|
||||
@DisableCachingByDefault(because = "the due date needs to be checked every time in case it changes")
|
||||
public abstract class GitHubMilestoneHasNoOpenIssuesTask extends DefaultTask {
|
||||
@Input
|
||||
private RepositoryRef repository = new RepositoryRef();
|
||||
|
||||
@Input
|
||||
@Input @Optional
|
||||
private String milestoneTitle;
|
||||
|
||||
@InputFile @Optional
|
||||
public abstract RegularFileProperty getNextVersionFile();
|
||||
|
||||
@Input @Optional
|
||||
private String gitHubAccessToken;
|
||||
|
||||
@OutputFile
|
||||
public abstract RegularFileProperty getIsOpenIssuesFile();
|
||||
|
||||
private GitHubMilestoneApi milestones = new GitHubMilestoneApi();
|
||||
|
||||
@TaskAction
|
||||
public void checkHasNoOpenIssues() {
|
||||
public void checkHasNoOpenIssues() throws IOException {
|
||||
if (this.milestoneTitle == null) {
|
||||
File nextVersionFile = getNextVersionFile().getAsFile().get();
|
||||
Yaml yaml = new Yaml(new Constructor(NextVersionYml.class));
|
||||
NextVersionYml nextVersionYml = yaml.load(new FileInputStream(nextVersionFile));
|
||||
String nextVersion = nextVersionYml.getVersion();
|
||||
if (nextVersion == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"Could not find version property in provided file " + nextVersionFile.getName());
|
||||
}
|
||||
this.milestoneTitle = nextVersion;
|
||||
}
|
||||
long milestoneNumber = this.milestones.findMilestoneNumberByTitle(this.repository, this.milestoneTitle);
|
||||
boolean isOpenIssues = this.milestones.isOpenIssuesForMilestoneNumber(this.repository, milestoneNumber);
|
||||
Path isOpenIssuesPath = getIsOpenIssuesFile().getAsFile().get().toPath();
|
||||
Files.write(isOpenIssuesPath, String.valueOf(isOpenIssues).getBytes());
|
||||
if (isOpenIssues) {
|
||||
throw new IllegalStateException("The repository " + this.repository + " has open issues for milestone with the title " + this.milestoneTitle + " and number " + milestoneNumber);
|
||||
System.out.println("The repository " + this.repository + " has open issues for milestone with the title " + this.milestoneTitle + " and number " + milestoneNumber);
|
||||
}
|
||||
else {
|
||||
System.out.println("The repository " + this.repository + " has no open issues for milestone with the title " + this.milestoneTitle + " and number " + milestoneNumber);
|
||||
}
|
||||
System.out.println("The repository " + this.repository + " has no open issues for milestone with the title " + this.milestoneTitle + " and number " + milestoneNumber);
|
||||
}
|
||||
|
||||
public RepositoryRef getRepository() {
|
||||
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
* Copyright 2019-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.gradle.github.milestones;
|
||||
|
||||
import org.gradle.api.Action;
|
||||
import org.gradle.api.DefaultTask;
|
||||
import org.gradle.api.file.RegularFileProperty;
|
||||
import org.gradle.api.tasks.Input;
|
||||
import org.gradle.api.tasks.Optional;
|
||||
import org.gradle.api.tasks.OutputFile;
|
||||
import org.gradle.api.tasks.TaskAction;
|
||||
import org.yaml.snakeyaml.DumperOptions;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
import org.yaml.snakeyaml.nodes.Tag;
|
||||
import org.yaml.snakeyaml.representer.Representer;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
|
||||
import org.springframework.gradle.github.RepositoryRef;
|
||||
|
||||
public abstract class GitHubMilestoneNextReleaseTask extends DefaultTask {
|
||||
|
||||
@Input
|
||||
private RepositoryRef repository = new RepositoryRef();
|
||||
|
||||
@Input
|
||||
@Optional
|
||||
private String gitHubAccessToken;
|
||||
|
||||
private GitHubMilestoneApi milestones = new GitHubMilestoneApi();
|
||||
|
||||
@TaskAction
|
||||
public void calculateNextReleaseMilestone() throws IOException {
|
||||
String currentVersion = getProject().getVersion().toString();
|
||||
String nextPreRelease = this.milestones.getNextReleaseMilestone(this.repository, currentVersion);
|
||||
System.out.println("The next release milestone is: " + nextPreRelease);
|
||||
NextVersionYml nextVersionYml = new NextVersionYml();
|
||||
nextVersionYml.setVersion(nextPreRelease);
|
||||
File outputFile = getNextReleaseFile().get().getAsFile();
|
||||
FileWriter outputWriter = new FileWriter(outputFile);
|
||||
Yaml yaml = getYaml();
|
||||
yaml.dump(nextVersionYml, outputWriter);
|
||||
}
|
||||
|
||||
@OutputFile
|
||||
public abstract RegularFileProperty getNextReleaseFile();
|
||||
|
||||
public RepositoryRef getRepository() {
|
||||
return repository;
|
||||
}
|
||||
|
||||
public void repository(Action<RepositoryRef> repository) {
|
||||
repository.execute(this.repository);
|
||||
}
|
||||
|
||||
public void setRepository(RepositoryRef repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
public String getGitHubAccessToken() {
|
||||
return gitHubAccessToken;
|
||||
}
|
||||
|
||||
public void setGitHubAccessToken(String gitHubAccessToken) {
|
||||
this.gitHubAccessToken = gitHubAccessToken;
|
||||
this.milestones = new GitHubMilestoneApi(gitHubAccessToken);
|
||||
}
|
||||
|
||||
private Yaml getYaml() {
|
||||
Representer representer = new Representer();
|
||||
representer.addClassTag(NextVersionYml.class, Tag.MAP);
|
||||
DumperOptions ymlOptions = new DumperOptions();
|
||||
ymlOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
|
||||
return new Yaml(representer, ymlOptions);
|
||||
}
|
||||
|
||||
}
|
||||
+102
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright 2019-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.gradle.github.milestones;
|
||||
|
||||
import org.gradle.api.Action;
|
||||
import org.gradle.api.DefaultTask;
|
||||
import org.gradle.api.file.RegularFileProperty;
|
||||
import org.gradle.api.tasks.Input;
|
||||
import org.gradle.api.tasks.InputFile;
|
||||
import org.gradle.api.tasks.Optional;
|
||||
import org.gradle.api.tasks.OutputFile;
|
||||
import org.gradle.api.tasks.TaskAction;
|
||||
import org.gradle.work.DisableCachingByDefault;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
import org.yaml.snakeyaml.constructor.Constructor;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
|
||||
import org.springframework.gradle.github.RepositoryRef;
|
||||
|
||||
@DisableCachingByDefault(because = "the due date needs to be checked every time in case it changes")
|
||||
public abstract class GitHubMilestoneNextVersionDueTodayTask extends DefaultTask {
|
||||
|
||||
@Input
|
||||
private RepositoryRef repository = new RepositoryRef();
|
||||
|
||||
@Input
|
||||
@Optional
|
||||
private String gitHubAccessToken;
|
||||
|
||||
@InputFile
|
||||
public abstract RegularFileProperty getNextVersionFile();
|
||||
|
||||
@OutputFile
|
||||
public abstract RegularFileProperty getIsDueTodayFile();
|
||||
|
||||
private GitHubMilestoneApi milestones = new GitHubMilestoneApi();
|
||||
|
||||
@TaskAction
|
||||
public void checkReleaseDueToday() throws IOException {
|
||||
File nextVersionFile = getNextVersionFile().getAsFile().get();
|
||||
Yaml yaml = new Yaml(new Constructor(NextVersionYml.class));
|
||||
NextVersionYml nextVersionYml = yaml.load(new FileInputStream(nextVersionFile));
|
||||
String nextVersion = nextVersionYml.getVersion();
|
||||
if (nextVersion == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"Could not find version property in provided file " + nextVersionFile.getName());
|
||||
}
|
||||
boolean milestoneDueToday = this.milestones.isMilestoneDueToday(this.repository, nextVersion);
|
||||
Path isDueTodayPath = getIsDueTodayFile().getAsFile().get().toPath();
|
||||
Files.writeString(isDueTodayPath, String.valueOf(milestoneDueToday));
|
||||
if (milestoneDueToday) {
|
||||
System.out.println("The milestone with the title " + nextVersion + " in the repository " + this.repository
|
||||
+ " is due today");
|
||||
}
|
||||
else {
|
||||
System.out.println("The milestone with the title " + nextVersion + " in the repository "
|
||||
+ this.repository + " is not due yet");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public RepositoryRef getRepository() {
|
||||
return repository;
|
||||
}
|
||||
|
||||
public void repository(Action<RepositoryRef> repository) {
|
||||
repository.execute(this.repository);
|
||||
}
|
||||
|
||||
public void setRepository(RepositoryRef repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
public String getGitHubAccessToken() {
|
||||
return gitHubAccessToken;
|
||||
}
|
||||
|
||||
public void setGitHubAccessToken(String gitHubAccessToken) {
|
||||
this.gitHubAccessToken = gitHubAccessToken;
|
||||
this.milestones = new GitHubMilestoneApi(gitHubAccessToken);
|
||||
}
|
||||
|
||||
}
|
||||
+43
-11
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2019-2020 the original author or authors.
|
||||
* Copyright 2019-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,23 +16,55 @@
|
||||
|
||||
package org.springframework.gradle.github.milestones;
|
||||
|
||||
import org.gradle.api.Action;
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.tasks.TaskProvider;
|
||||
|
||||
public class GitHubMilestonePlugin implements Plugin<Project> {
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
project.getTasks().register("gitHubCheckMilestoneHasNoOpenIssues", GitHubMilestoneHasNoOpenIssuesTask.class, new Action<GitHubMilestoneHasNoOpenIssuesTask>() {
|
||||
@Override
|
||||
public void execute(GitHubMilestoneHasNoOpenIssuesTask githubCheckMilestoneHasNoOpenIssues) {
|
||||
githubCheckMilestoneHasNoOpenIssues.setGroup("Release");
|
||||
githubCheckMilestoneHasNoOpenIssues.setDescription("Checks if there are any open issues for the specified repository and milestone");
|
||||
githubCheckMilestoneHasNoOpenIssues.setMilestoneTitle((String) project.findProperty("nextVersion"));
|
||||
if (project.hasProperty("githubAccessToken")) {
|
||||
githubCheckMilestoneHasNoOpenIssues.setGitHubAccessToken((String) project.findProperty("gitHubAccessToken"));
|
||||
}
|
||||
TaskProvider<GitHubMilestoneNextReleaseTask> nextReleaseMilestoneTask = project.getTasks().register("gitHubNextReleaseMilestone", GitHubMilestoneNextReleaseTask.class, (gitHubMilestoneNextReleaseTask) -> {
|
||||
gitHubMilestoneNextReleaseTask.doNotTrackState("API call to GitHub needs to check for new milestones every time");
|
||||
gitHubMilestoneNextReleaseTask.setGroup("Release");
|
||||
gitHubMilestoneNextReleaseTask.setDescription("Calculates the next release version based on the current version and outputs it to a yaml file");
|
||||
gitHubMilestoneNextReleaseTask.getNextReleaseFile()
|
||||
.fileProvider(project.provider(() -> project.file("next-release.yml")));
|
||||
if (project.hasProperty("gitHubAccessToken")) {
|
||||
gitHubMilestoneNextReleaseTask
|
||||
.setGitHubAccessToken((String) project.findProperty("gitHubAccessToken"));
|
||||
}
|
||||
});
|
||||
project.getTasks().register("gitHubCheckMilestoneHasNoOpenIssues", GitHubMilestoneHasNoOpenIssuesTask.class, (githubCheckMilestoneHasNoOpenIssues) -> {
|
||||
githubCheckMilestoneHasNoOpenIssues.setGroup("Release");
|
||||
githubCheckMilestoneHasNoOpenIssues.setDescription("Checks if there are any open issues for the specified repository and milestone");
|
||||
githubCheckMilestoneHasNoOpenIssues.getIsOpenIssuesFile().value(project.getLayout().getBuildDirectory().file("github/milestones/is-open-issues"));
|
||||
githubCheckMilestoneHasNoOpenIssues.setMilestoneTitle((String) project.findProperty("nextVersion"));
|
||||
if (!project.hasProperty("nextVersion")) {
|
||||
githubCheckMilestoneHasNoOpenIssues.getNextVersionFile().convention(
|
||||
nextReleaseMilestoneTask.flatMap(GitHubMilestoneNextReleaseTask::getNextReleaseFile));
|
||||
}
|
||||
if (project.hasProperty("gitHubAccessToken")) {
|
||||
githubCheckMilestoneHasNoOpenIssues.setGitHubAccessToken((String) project.findProperty("gitHubAccessToken"));
|
||||
}
|
||||
});
|
||||
project.getTasks().register("gitHubCheckNextVersionDueToday", GitHubMilestoneNextVersionDueTodayTask.class, (gitHubMilestoneNextVersionDueTodayTask) -> {
|
||||
gitHubMilestoneNextVersionDueTodayTask.setGroup("Release");
|
||||
gitHubMilestoneNextVersionDueTodayTask.setDescription("Checks if the next release version is due today or past due, will fail if the next version is not due yet");
|
||||
gitHubMilestoneNextVersionDueTodayTask.getIsDueTodayFile().value(project.getLayout().getBuildDirectory().file("github/milestones/is-due-today"));
|
||||
gitHubMilestoneNextVersionDueTodayTask.getNextVersionFile().convention(
|
||||
nextReleaseMilestoneTask.flatMap(GitHubMilestoneNextReleaseTask::getNextReleaseFile));
|
||||
if (project.hasProperty("gitHubAccessToken")) {
|
||||
gitHubMilestoneNextVersionDueTodayTask
|
||||
.setGitHubAccessToken((String) project.findProperty("gitHubAccessToken"));
|
||||
}
|
||||
});
|
||||
project.getTasks().register("scheduleNextRelease", ScheduleNextReleaseTask.class, (scheduleNextRelease) -> {
|
||||
scheduleNextRelease.doNotTrackState("API call to GitHub needs to check for new milestones every time");
|
||||
scheduleNextRelease.setGroup("Release");
|
||||
scheduleNextRelease.setDescription("Schedule the next release (even months only) or release train (series of milestones starting in January or July) based on the current version");
|
||||
|
||||
scheduleNextRelease.setVersion((String) project.findProperty("nextVersion"));
|
||||
scheduleNextRelease.setGitHubAccessToken((String) project.findProperty("gitHubAccessToken"));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
package org.springframework.gradle.github.milestones;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDate;
|
||||
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
||||
/**
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
class LocalDateAdapter extends TypeAdapter<LocalDate> {
|
||||
@Override
|
||||
public void write(JsonWriter jsonWriter, LocalDate localDate) throws IOException {
|
||||
jsonWriter.value(localDate.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalDate read(JsonReader jsonReader) throws IOException {
|
||||
return LocalDate.parse(jsonReader.nextString());
|
||||
}
|
||||
}
|
||||
+25
@@ -0,0 +1,25 @@
|
||||
package org.springframework.gradle.github.milestones;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneOffset;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
|
||||
import com.google.gson.TypeAdapter;
|
||||
import com.google.gson.stream.JsonReader;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
|
||||
/**
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
class LocalDateTimeAdapter extends TypeAdapter<LocalDateTime> {
|
||||
@Override
|
||||
public void write(JsonWriter jsonWriter, LocalDateTime localDateTime) throws IOException {
|
||||
jsonWriter.value(localDateTime.atOffset(ZoneOffset.UTC).format(DateTimeFormatter.ISO_ZONED_DATE_TIME));
|
||||
}
|
||||
|
||||
@Override
|
||||
public LocalDateTime read(JsonReader jsonReader) throws IOException {
|
||||
return LocalDateTime.parse(jsonReader.nextString(), DateTimeFormatter.ISO_ZONED_DATE_TIME);
|
||||
}
|
||||
}
|
||||
@@ -1,9 +1,35 @@
|
||||
/*
|
||||
* Copyright 2019-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.gradle.github.milestones;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
public class Milestone {
|
||||
private String title;
|
||||
|
||||
private long number;
|
||||
private Long number;
|
||||
|
||||
@SerializedName("due_on")
|
||||
private LocalDateTime dueOn;
|
||||
|
||||
public String getTitle() {
|
||||
return title;
|
||||
@@ -13,19 +39,28 @@ public class Milestone {
|
||||
this.title = title;
|
||||
}
|
||||
|
||||
public long getNumber() {
|
||||
public Long getNumber() {
|
||||
return number;
|
||||
}
|
||||
|
||||
public void setNumber(long number) {
|
||||
public void setNumber(Long number) {
|
||||
this.number = number;
|
||||
}
|
||||
|
||||
public LocalDateTime getDueOn() {
|
||||
return dueOn;
|
||||
}
|
||||
|
||||
public void setDueOn(LocalDateTime dueOn) {
|
||||
this.dueOn = dueOn;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Milestone{" +
|
||||
"title='" + title + '\'' +
|
||||
", number=" + number +
|
||||
", number='" + number + '\'' +
|
||||
", dueOn='" + dueOn + '\'' +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright 2019-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.gradle.github.milestones;
|
||||
|
||||
public class NextVersionYml {
|
||||
private String version;
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(String version) {
|
||||
this.version = version;
|
||||
}
|
||||
}
|
||||
+147
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.gradle.github.milestones;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
|
||||
import org.gradle.api.Action;
|
||||
import org.gradle.api.DefaultTask;
|
||||
import org.gradle.api.tasks.Input;
|
||||
import org.gradle.api.tasks.TaskAction;
|
||||
|
||||
import org.springframework.gradle.github.RepositoryRef;
|
||||
|
||||
/**
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
public class ScheduleNextReleaseTask extends DefaultTask {
|
||||
@Input
|
||||
private RepositoryRef repository = new RepositoryRef();
|
||||
|
||||
@Input
|
||||
private String gitHubAccessToken;
|
||||
|
||||
@Input
|
||||
private String version;
|
||||
|
||||
@Input
|
||||
private Integer weekOfMonth;
|
||||
|
||||
@Input
|
||||
private Integer dayOfWeek;
|
||||
|
||||
@TaskAction
|
||||
public void scheduleNextRelease() {
|
||||
GitHubMilestoneApi gitHubMilestoneApi = new GitHubMilestoneApi(this.gitHubAccessToken);
|
||||
String nextReleaseMilestone = gitHubMilestoneApi.getNextReleaseMilestone(this.repository, this.version);
|
||||
|
||||
// If the next release contains a dash (e.g. 5.6.0-RC1), it is already scheduled
|
||||
if (nextReleaseMilestone.contains("-")) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check to see if a scheduled GA version already exists
|
||||
boolean hasExistingMilestone = gitHubMilestoneApi.getMilestones(this.repository).stream()
|
||||
.anyMatch(milestone -> nextReleaseMilestone.equals(milestone.getTitle()));
|
||||
if (hasExistingMilestone) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Next milestone is either a patch version or minor version
|
||||
// Note: Major versions will be handled like minor and get a release
|
||||
// train which can be manually updated to match the desired schedule.
|
||||
if (nextReleaseMilestone.endsWith(".0")) {
|
||||
// Create M1, M2, M3, RC1 and GA milestones for release train
|
||||
getReleaseTrain(nextReleaseMilestone).getTrainDates().forEach((milestoneTitle, dueOn) -> {
|
||||
Milestone milestone = new Milestone();
|
||||
milestone.setTitle(milestoneTitle);
|
||||
// Note: GitHub seems to store full date/time as UTC then displays
|
||||
// as a date (no time) in your timezone, which means the date will
|
||||
// not always be the same date as we intend.
|
||||
// Using 12pm/noon UTC allows GitHub to schedule and display the
|
||||
// correct date.
|
||||
milestone.setDueOn(dueOn.atTime(LocalTime.NOON));
|
||||
gitHubMilestoneApi.createMilestone(this.repository, milestone);
|
||||
});
|
||||
} else {
|
||||
// Create GA milestone for patch release on the next even month
|
||||
LocalDate startDate = LocalDate.now();
|
||||
LocalDate dueOn = getReleaseTrain(nextReleaseMilestone).getNextReleaseDate(startDate);
|
||||
Milestone milestone = new Milestone();
|
||||
milestone.setTitle(nextReleaseMilestone);
|
||||
milestone.setDueOn(dueOn.atTime(LocalTime.NOON));
|
||||
gitHubMilestoneApi.createMilestone(this.repository, milestone);
|
||||
}
|
||||
}
|
||||
|
||||
private SpringReleaseTrain getReleaseTrain(String nextReleaseMilestone) {
|
||||
SpringReleaseTrainSpec releaseTrainSpec =
|
||||
SpringReleaseTrainSpec.builder()
|
||||
.nextTrain()
|
||||
.version(nextReleaseMilestone)
|
||||
.weekOfMonth(this.weekOfMonth)
|
||||
.dayOfWeek(this.dayOfWeek)
|
||||
.build();
|
||||
|
||||
return new SpringReleaseTrain(releaseTrainSpec);
|
||||
}
|
||||
|
||||
public RepositoryRef getRepository() {
|
||||
return this.repository;
|
||||
}
|
||||
|
||||
public void repository(Action<RepositoryRef> repository) {
|
||||
repository.execute(this.repository);
|
||||
}
|
||||
|
||||
public void setRepository(RepositoryRef repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
public String getGitHubAccessToken() {
|
||||
return this.gitHubAccessToken;
|
||||
}
|
||||
|
||||
public void setGitHubAccessToken(String gitHubAccessToken) {
|
||||
this.gitHubAccessToken = gitHubAccessToken;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return this.version;
|
||||
}
|
||||
|
||||
public void setVersion(String version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public Integer getWeekOfMonth() {
|
||||
return weekOfMonth;
|
||||
}
|
||||
|
||||
public void setWeekOfMonth(Integer weekOfMonth) {
|
||||
this.weekOfMonth = weekOfMonth;
|
||||
}
|
||||
|
||||
public Integer getDayOfWeek() {
|
||||
return dayOfWeek;
|
||||
}
|
||||
|
||||
public void setDayOfWeek(Integer dayOfWeek) {
|
||||
this.dayOfWeek = dayOfWeek;
|
||||
}
|
||||
}
|
||||
+136
@@ -0,0 +1,136 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.gradle.github.milestones;
|
||||
|
||||
import java.time.DayOfWeek;
|
||||
import java.time.LocalDate;
|
||||
import java.time.Month;
|
||||
import java.time.Year;
|
||||
import java.time.temporal.TemporalAdjuster;
|
||||
import java.time.temporal.TemporalAdjusters;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Spring release train generator based on rules contained in a specification.
|
||||
* <p>
|
||||
* The rules are:
|
||||
* <ol>
|
||||
* <li>Train 1 (January-May) or 2 (July-November)</li>
|
||||
* <li>Version number (e.g. 0.1.2, 1.0.0, etc.)</li>
|
||||
* <li>Week of month (1st, 2nd, 3rd, 4th)</li>
|
||||
* <li>Day of week (Monday-Friday)</li>
|
||||
* <li>Year (e.g. 2020, 2021, etc.)</li>
|
||||
* </ol>
|
||||
*
|
||||
* The release train generated will contain M1, M2, M3, RC1 and GA versions
|
||||
* mapped to their respective dates in the train.
|
||||
*
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
public final class SpringReleaseTrain {
|
||||
private final SpringReleaseTrainSpec releaseTrainSpec;
|
||||
|
||||
public SpringReleaseTrain(SpringReleaseTrainSpec releaseTrainSpec) {
|
||||
this.releaseTrainSpec = releaseTrainSpec;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate release train dates based on the release train specification.
|
||||
*
|
||||
* @return A mapping of release milestones to scheduled release dates
|
||||
*/
|
||||
public Map<String, LocalDate> getTrainDates() {
|
||||
Map<String, LocalDate> releaseDates = new LinkedHashMap<>();
|
||||
switch (this.releaseTrainSpec.getTrain()) {
|
||||
case ONE:
|
||||
addTrainDate(releaseDates, "M1", Month.JANUARY);
|
||||
addTrainDate(releaseDates, "M2", Month.FEBRUARY);
|
||||
addTrainDate(releaseDates, "M3", Month.MARCH);
|
||||
addTrainDate(releaseDates, "RC1", Month.APRIL);
|
||||
addTrainDate(releaseDates, null, Month.MAY);
|
||||
break;
|
||||
case TWO:
|
||||
addTrainDate(releaseDates, "M1", Month.JULY);
|
||||
addTrainDate(releaseDates, "M2", Month.AUGUST);
|
||||
addTrainDate(releaseDates, "M3", Month.SEPTEMBER);
|
||||
addTrainDate(releaseDates, "RC1", Month.OCTOBER);
|
||||
addTrainDate(releaseDates, null, Month.NOVEMBER);
|
||||
break;
|
||||
}
|
||||
|
||||
return releaseDates;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine if a given date matches the due date of given version.
|
||||
*
|
||||
* @param version The version number (e.g. 5.6.0-M1, 5.6.0, etc.)
|
||||
* @param expectedDate The expected date
|
||||
* @return true if the given date matches the due date of the given version, false otherwise
|
||||
*/
|
||||
public boolean isTrainDate(String version, LocalDate expectedDate) {
|
||||
return expectedDate.isEqual(getTrainDates().get(version));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate the next release date following the given date.
|
||||
* <p>
|
||||
* The next release date is always on an even month so that a patch release
|
||||
* is the month after the GA version of a release train. This method does
|
||||
* not consider the year of the release train, only the given start date.
|
||||
*
|
||||
* @param startDate The start date
|
||||
* @return The next release date following the given date
|
||||
*/
|
||||
public LocalDate getNextReleaseDate(LocalDate startDate) {
|
||||
LocalDate trainDate;
|
||||
LocalDate currentDate = startDate;
|
||||
do {
|
||||
trainDate = calculateReleaseDate(
|
||||
Year.of(currentDate.getYear()),
|
||||
currentDate.getMonth(),
|
||||
this.releaseTrainSpec.getDayOfWeek().getDayOfWeek(),
|
||||
this.releaseTrainSpec.getWeekOfMonth().getDayOffset()
|
||||
);
|
||||
currentDate = currentDate.plusMonths(1);
|
||||
} while (!trainDate.isAfter(startDate) || trainDate.getMonthValue() % 2 != 0);
|
||||
|
||||
return trainDate;
|
||||
}
|
||||
|
||||
private void addTrainDate(Map<String, LocalDate> releaseDates, String milestone, Month month) {
|
||||
LocalDate releaseDate = calculateReleaseDate(
|
||||
this.releaseTrainSpec.getYear(),
|
||||
month,
|
||||
this.releaseTrainSpec.getDayOfWeek().getDayOfWeek(),
|
||||
this.releaseTrainSpec.getWeekOfMonth().getDayOffset()
|
||||
);
|
||||
String suffix = (milestone == null) ? "" : "-" + milestone;
|
||||
releaseDates.put(this.releaseTrainSpec.getVersion() + suffix, releaseDate);
|
||||
}
|
||||
|
||||
private static LocalDate calculateReleaseDate(Year year, Month month, DayOfWeek dayOfWeek, int dayOffset) {
|
||||
TemporalAdjuster nextMonday = TemporalAdjusters.nextOrSame(DayOfWeek.MONDAY);
|
||||
TemporalAdjuster nextDayOfWeek = TemporalAdjusters.nextOrSame(dayOfWeek);
|
||||
|
||||
LocalDate firstDayOfMonth = year.atMonth(month).atDay(1);
|
||||
LocalDate firstMondayOfMonth = firstDayOfMonth.with(nextMonday);
|
||||
|
||||
return firstMondayOfMonth.with(nextDayOfWeek).plusDays(dayOffset);
|
||||
}
|
||||
}
|
||||
+205
@@ -0,0 +1,205 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.gradle.github.milestones;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.Month;
|
||||
import java.time.Year;
|
||||
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A specification for a release train.
|
||||
*
|
||||
* @author Steve Riesenberg
|
||||
* @see SpringReleaseTrain
|
||||
*/
|
||||
public final class SpringReleaseTrainSpec {
|
||||
private final Train train;
|
||||
private final String version;
|
||||
private final WeekOfMonth weekOfMonth;
|
||||
private final DayOfWeek dayOfWeek;
|
||||
private final Year year;
|
||||
|
||||
public SpringReleaseTrainSpec(Train train, String version, WeekOfMonth weekOfMonth, DayOfWeek dayOfWeek, Year year) {
|
||||
this.train = train;
|
||||
this.version = version;
|
||||
this.weekOfMonth = weekOfMonth;
|
||||
this.dayOfWeek = dayOfWeek;
|
||||
this.year = year;
|
||||
}
|
||||
|
||||
public Train getTrain() {
|
||||
return train;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public WeekOfMonth getWeekOfMonth() {
|
||||
return weekOfMonth;
|
||||
}
|
||||
|
||||
public DayOfWeek getDayOfWeek() {
|
||||
return dayOfWeek;
|
||||
}
|
||||
|
||||
public Year getYear() {
|
||||
return year;
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
public enum WeekOfMonth {
|
||||
FIRST(0), SECOND(7), THIRD(14), FOURTH(21);
|
||||
|
||||
private final int dayOffset;
|
||||
|
||||
WeekOfMonth(int dayOffset) {
|
||||
this.dayOffset = dayOffset;
|
||||
}
|
||||
|
||||
public int getDayOffset() {
|
||||
return dayOffset;
|
||||
}
|
||||
}
|
||||
|
||||
public enum DayOfWeek {
|
||||
MONDAY(java.time.DayOfWeek.MONDAY),
|
||||
TUESDAY(java.time.DayOfWeek.TUESDAY),
|
||||
WEDNESDAY(java.time.DayOfWeek.WEDNESDAY),
|
||||
THURSDAY(java.time.DayOfWeek.THURSDAY),
|
||||
FRIDAY(java.time.DayOfWeek.FRIDAY);
|
||||
|
||||
private final java.time.DayOfWeek dayOfWeek;
|
||||
|
||||
DayOfWeek(java.time.DayOfWeek dayOfWeek) {
|
||||
this.dayOfWeek = dayOfWeek;
|
||||
}
|
||||
|
||||
public java.time.DayOfWeek getDayOfWeek() {
|
||||
return dayOfWeek;
|
||||
}
|
||||
}
|
||||
|
||||
public enum Train {
|
||||
ONE, TWO
|
||||
}
|
||||
|
||||
public static class Builder {
|
||||
private Train train;
|
||||
private String version;
|
||||
private WeekOfMonth weekOfMonth;
|
||||
private DayOfWeek dayOfWeek;
|
||||
private Year year;
|
||||
|
||||
private Builder() {
|
||||
}
|
||||
|
||||
public Builder train(int train) {
|
||||
switch (train) {
|
||||
case 1: this.train = Train.ONE; break;
|
||||
case 2: this.train = Train.TWO; break;
|
||||
default: throw new IllegalArgumentException("Invalid train: " + train);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder train(Train train) {
|
||||
this.train = train;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder nextTrain() {
|
||||
// Search for next train starting with this month
|
||||
return nextTrain(LocalDate.now().withDayOfMonth(1));
|
||||
}
|
||||
|
||||
public Builder nextTrain(LocalDate startDate) {
|
||||
Train nextTrain = null;
|
||||
|
||||
// Search for next train from a given start date
|
||||
LocalDate currentDate = startDate;
|
||||
while (nextTrain == null) {
|
||||
if (currentDate.getMonth() == Month.JANUARY) {
|
||||
nextTrain = Train.ONE;
|
||||
} else if (currentDate.getMonth() == Month.JULY) {
|
||||
nextTrain = Train.TWO;
|
||||
}
|
||||
|
||||
currentDate = currentDate.plusMonths(1);
|
||||
}
|
||||
|
||||
return train(nextTrain).year(currentDate.getYear());
|
||||
}
|
||||
|
||||
public Builder version(String version) {
|
||||
this.version = version;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder weekOfMonth(int weekOfMonth) {
|
||||
switch (weekOfMonth) {
|
||||
case 1: this.weekOfMonth = WeekOfMonth.FIRST; break;
|
||||
case 2: this.weekOfMonth = WeekOfMonth.SECOND; break;
|
||||
case 3: this.weekOfMonth = WeekOfMonth.THIRD; break;
|
||||
case 4: this.weekOfMonth = WeekOfMonth.FOURTH; break;
|
||||
default: throw new IllegalArgumentException("Invalid weekOfMonth: " + weekOfMonth);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder weekOfMonth(WeekOfMonth weekOfMonth) {
|
||||
this.weekOfMonth = weekOfMonth;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder dayOfWeek(int dayOfWeek) {
|
||||
switch (dayOfWeek) {
|
||||
case 1: this.dayOfWeek = DayOfWeek.MONDAY; break;
|
||||
case 2: this.dayOfWeek = DayOfWeek.TUESDAY; break;
|
||||
case 3: this.dayOfWeek = DayOfWeek.WEDNESDAY; break;
|
||||
case 4: this.dayOfWeek = DayOfWeek.THURSDAY; break;
|
||||
case 5: this.dayOfWeek = DayOfWeek.FRIDAY; break;
|
||||
default: throw new IllegalArgumentException("Invalid dayOfWeek: " + dayOfWeek);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder dayOfWeek(DayOfWeek dayOfWeek) {
|
||||
this.dayOfWeek = dayOfWeek;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder year(int year) {
|
||||
this.year = Year.of(year);
|
||||
return this;
|
||||
}
|
||||
|
||||
public SpringReleaseTrainSpec build() {
|
||||
Assert.notNull(train, "train cannot be null");
|
||||
Assert.notNull(version, "version cannot be null");
|
||||
Assert.notNull(weekOfMonth, "weekOfMonth cannot be null");
|
||||
Assert.notNull(dayOfWeek, "dayOfWeek cannot be null");
|
||||
Assert.notNull(year, "year cannot be null");
|
||||
return new SpringReleaseTrainSpec(train, version, weekOfMonth, dayOfWeek, year);
|
||||
}
|
||||
}
|
||||
}
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.gradle.github.release;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
import org.gradle.api.Action;
|
||||
import org.gradle.api.DefaultTask;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.tasks.Input;
|
||||
import org.gradle.api.tasks.Optional;
|
||||
import org.gradle.api.tasks.TaskAction;
|
||||
|
||||
import org.springframework.gradle.github.RepositoryRef;
|
||||
import org.springframework.gradle.github.changelog.GitHubChangelogPlugin;
|
||||
|
||||
/**
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
public class CreateGitHubReleaseTask extends DefaultTask {
|
||||
@Input
|
||||
private RepositoryRef repository = new RepositoryRef();
|
||||
|
||||
@Input @Optional
|
||||
private String gitHubAccessToken;
|
||||
|
||||
@Input
|
||||
private String version;
|
||||
|
||||
@Input @Optional
|
||||
private String branch = "main";
|
||||
|
||||
@Input
|
||||
private boolean createRelease = false;
|
||||
|
||||
@TaskAction
|
||||
public void createGitHubRelease() {
|
||||
String body = readReleaseNotes();
|
||||
Release release = Release.tag(this.version)
|
||||
.commit(this.branch)
|
||||
.name(this.version)
|
||||
.body(body)
|
||||
.preRelease(this.version.contains("-"))
|
||||
.build();
|
||||
|
||||
System.out.printf("%sCreating GitHub release for %s/%s@%s\n",
|
||||
this.createRelease ? "" : "[DRY RUN] ",
|
||||
this.repository.getOwner(),
|
||||
this.repository.getName(),
|
||||
this.version
|
||||
);
|
||||
System.out.printf(" Release Notes:\n\n----\n%s\n----\n\n", body.trim());
|
||||
|
||||
if (this.createRelease) {
|
||||
GitHubReleaseApi github = new GitHubReleaseApi(this.gitHubAccessToken);
|
||||
github.publishRelease(this.repository, release);
|
||||
}
|
||||
}
|
||||
|
||||
private String readReleaseNotes() {
|
||||
Project project = getProject();
|
||||
File inputFile = project.file(Paths.get(project.getBuildDir().getPath(), GitHubChangelogPlugin.RELEASE_NOTES_PATH));
|
||||
try {
|
||||
return Files.readString(inputFile.toPath());
|
||||
} catch (IOException ex) {
|
||||
throw new RuntimeException("Unable to read release notes from " + inputFile, ex);
|
||||
}
|
||||
}
|
||||
|
||||
public RepositoryRef getRepository() {
|
||||
return repository;
|
||||
}
|
||||
|
||||
public void repository(Action<RepositoryRef> repository) {
|
||||
repository.execute(this.repository);
|
||||
}
|
||||
|
||||
public void setRepository(RepositoryRef repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
public String getGitHubAccessToken() {
|
||||
return gitHubAccessToken;
|
||||
}
|
||||
|
||||
public void setGitHubAccessToken(String gitHubAccessToken) {
|
||||
this.gitHubAccessToken = gitHubAccessToken;
|
||||
}
|
||||
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
public void setVersion(String version) {
|
||||
this.version = version;
|
||||
}
|
||||
|
||||
public String getBranch() {
|
||||
return branch;
|
||||
}
|
||||
|
||||
public void setBranch(String branch) {
|
||||
this.branch = branch;
|
||||
}
|
||||
|
||||
public boolean isCreateRelease() {
|
||||
return createRelease;
|
||||
}
|
||||
|
||||
public void setCreateRelease(boolean createRelease) {
|
||||
this.createRelease = createRelease;
|
||||
}
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.gradle.github.release;
|
||||
|
||||
import org.gradle.api.Action;
|
||||
import org.gradle.api.DefaultTask;
|
||||
import org.gradle.api.tasks.Input;
|
||||
import org.gradle.api.tasks.TaskAction;
|
||||
|
||||
import org.springframework.gradle.github.RepositoryRef;
|
||||
|
||||
/**
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
public class DispatchGitHubWorkflowTask extends DefaultTask {
|
||||
@Input
|
||||
private RepositoryRef repository = new RepositoryRef();
|
||||
|
||||
@Input
|
||||
private String gitHubAccessToken;
|
||||
|
||||
@Input
|
||||
private String branch;
|
||||
|
||||
@Input
|
||||
private String workflowId;
|
||||
|
||||
@TaskAction
|
||||
public void dispatchGitHubWorkflow() {
|
||||
GitHubActionsApi gitHubActionsApi = new GitHubActionsApi(this.gitHubAccessToken);
|
||||
WorkflowDispatch workflowDispatch = new WorkflowDispatch(this.branch, null);
|
||||
gitHubActionsApi.dispatchWorkflow(this.repository, this.workflowId, workflowDispatch);
|
||||
}
|
||||
|
||||
public RepositoryRef getRepository() {
|
||||
return repository;
|
||||
}
|
||||
|
||||
public void repository(Action<RepositoryRef> repository) {
|
||||
repository.execute(this.repository);
|
||||
}
|
||||
|
||||
public void setRepository(RepositoryRef repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
public String getGitHubAccessToken() {
|
||||
return gitHubAccessToken;
|
||||
}
|
||||
|
||||
public void setGitHubAccessToken(String gitHubAccessToken) {
|
||||
this.gitHubAccessToken = gitHubAccessToken;
|
||||
}
|
||||
|
||||
public String getBranch() {
|
||||
return branch;
|
||||
}
|
||||
|
||||
public void setBranch(String branch) {
|
||||
this.branch = branch;
|
||||
}
|
||||
|
||||
public String getWorkflowId() {
|
||||
return workflowId;
|
||||
}
|
||||
|
||||
public void setWorkflowId(String workflowId) {
|
||||
this.workflowId = workflowId;
|
||||
}
|
||||
}
|
||||
+98
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.gradle.github.release;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import okhttp3.Interceptor;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
|
||||
import org.springframework.gradle.github.RepositoryRef;
|
||||
|
||||
/**
|
||||
* Manage GitHub Actions.
|
||||
*
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
public class GitHubActionsApi {
|
||||
private String baseUrl = "https://api.github.com";
|
||||
|
||||
private final OkHttpClient client;
|
||||
|
||||
private final Gson gson = new GsonBuilder().create();
|
||||
|
||||
public GitHubActionsApi() {
|
||||
this.client = new OkHttpClient.Builder().build();
|
||||
}
|
||||
|
||||
public GitHubActionsApi(String gitHubToken) {
|
||||
this.client = new OkHttpClient.Builder()
|
||||
.addInterceptor(new AuthorizationInterceptor(gitHubToken))
|
||||
.build();
|
||||
}
|
||||
|
||||
public void setBaseUrl(String baseUrl) {
|
||||
this.baseUrl = baseUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a workflow dispatch event.
|
||||
*
|
||||
* @param repository The repository owner/name
|
||||
* @param workflowId The ID of the workflow or the name of the workflow file name
|
||||
* @param workflowDispatch The workflow dispatch containing a ref (branch) and optional inputs
|
||||
*/
|
||||
public void dispatchWorkflow(RepositoryRef repository, String workflowId, WorkflowDispatch workflowDispatch) {
|
||||
String url = this.baseUrl + "/repos/" + repository.getOwner() + "/" + repository.getName() + "/actions/workflows/" + workflowId + "/dispatches";
|
||||
String json = this.gson.toJson(workflowDispatch);
|
||||
RequestBody body = RequestBody.create(MediaType.parse("application/json"), json);
|
||||
Request request = new Request.Builder().url(url).post(body).build();
|
||||
try {
|
||||
Response response = this.client.newCall(request).execute();
|
||||
if (!response.isSuccessful()) {
|
||||
throw new RuntimeException(String.format("Could not create workflow dispatch %s for repository %s/%s. Got response %s",
|
||||
workflowId, repository.getOwner(), repository.getName(), response));
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
throw new RuntimeException(String.format("Could not create workflow dispatch %s for repository %s/%s",
|
||||
workflowId, repository.getOwner(), repository.getName()), ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static class AuthorizationInterceptor implements Interceptor {
|
||||
private final String token;
|
||||
|
||||
public AuthorizationInterceptor(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response intercept(Chain chain) throws IOException {
|
||||
Request request = chain.request().newBuilder()
|
||||
.addHeader("Authorization", "Bearer " + this.token)
|
||||
.build();
|
||||
|
||||
return chain.proceed(request);
|
||||
}
|
||||
}
|
||||
}
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.gradle.github.release;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import okhttp3.Interceptor;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.RequestBody;
|
||||
import okhttp3.Response;
|
||||
|
||||
import org.springframework.gradle.github.RepositoryRef;
|
||||
|
||||
/**
|
||||
* Manage GitHub releases.
|
||||
*
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
public class GitHubReleaseApi {
|
||||
private String baseUrl = "https://api.github.com";
|
||||
|
||||
private final OkHttpClient httpClient;
|
||||
private Gson gson = new Gson();
|
||||
|
||||
public GitHubReleaseApi(String gitHubAccessToken) {
|
||||
this.httpClient = new OkHttpClient.Builder()
|
||||
.addInterceptor(new AuthorizationInterceptor(gitHubAccessToken))
|
||||
.build();
|
||||
}
|
||||
|
||||
public void setBaseUrl(String baseUrl) {
|
||||
this.baseUrl = baseUrl;
|
||||
}
|
||||
|
||||
/**
|
||||
* Publish a release with no binary attachments.
|
||||
*
|
||||
* @param repository The repository owner/name
|
||||
* @param release The contents of the release
|
||||
*/
|
||||
public void publishRelease(RepositoryRef repository, Release release) {
|
||||
String url = this.baseUrl + "/repos/" + repository.getOwner() + "/" + repository.getName() + "/releases";
|
||||
String json = this.gson.toJson(release);
|
||||
RequestBody body = RequestBody.create(MediaType.parse("application/json"), json);
|
||||
Request request = new Request.Builder().url(url).post(body).build();
|
||||
try {
|
||||
Response response = this.httpClient.newCall(request).execute();
|
||||
if (!response.isSuccessful()) {
|
||||
throw new RuntimeException(String.format("Could not create release %s for repository %s/%s. Got response %s",
|
||||
release.getName(), repository.getOwner(), repository.getName(), response));
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
throw new RuntimeException(String.format("Could not create release %s for repository %s/%s",
|
||||
release.getName(), repository.getOwner(), repository.getName()), ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static class AuthorizationInterceptor implements Interceptor {
|
||||
private final String token;
|
||||
|
||||
public AuthorizationInterceptor(String token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response intercept(Chain chain) throws IOException {
|
||||
Request request = chain.request().newBuilder()
|
||||
.addHeader("Authorization", "Bearer " + this.token)
|
||||
.build();
|
||||
|
||||
return chain.proceed(request);
|
||||
}
|
||||
}
|
||||
}
|
||||
+54
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.gradle.github.release;
|
||||
|
||||
import groovy.lang.MissingPropertyException;
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
|
||||
/**
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
public class GitHubReleasePlugin implements Plugin<Project> {
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
project.getTasks().register("createGitHubRelease", CreateGitHubReleaseTask.class, (createGitHubRelease) -> {
|
||||
createGitHubRelease.setGroup("Release");
|
||||
createGitHubRelease.setDescription("Create a github release");
|
||||
createGitHubRelease.dependsOn("generateChangelog");
|
||||
|
||||
createGitHubRelease.setCreateRelease("true".equals(project.findProperty("createRelease")));
|
||||
createGitHubRelease.setVersion((String) project.findProperty("nextVersion"));
|
||||
if (project.hasProperty("branch")) {
|
||||
createGitHubRelease.setBranch((String) project.findProperty("branch"));
|
||||
}
|
||||
createGitHubRelease.setGitHubAccessToken((String) project.findProperty("gitHubAccessToken"));
|
||||
if (createGitHubRelease.isCreateRelease() && createGitHubRelease.getGitHubAccessToken() == null) {
|
||||
throw new MissingPropertyException("Please provide an access token with -PgitHubAccessToken=...");
|
||||
}
|
||||
});
|
||||
|
||||
project.getTasks().register("dispatchGitHubWorkflow", DispatchGitHubWorkflowTask.class, (dispatchGitHubWorkflow) -> {
|
||||
dispatchGitHubWorkflow.setGroup("Release");
|
||||
dispatchGitHubWorkflow.setDescription("Create a workflow_dispatch event on a given branch");
|
||||
|
||||
dispatchGitHubWorkflow.setBranch((String) project.findProperty("branch"));
|
||||
dispatchGitHubWorkflow.setWorkflowId((String) project.findProperty("workflowId"));
|
||||
dispatchGitHubWorkflow.setGitHubAccessToken((String) project.findProperty("gitHubAccessToken"));
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.gradle.github.release;
|
||||
|
||||
import com.google.gson.annotations.SerializedName;
|
||||
|
||||
/**
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
public class Release {
|
||||
@SerializedName("tag_name")
|
||||
private final String tag;
|
||||
|
||||
@SerializedName("target_commitish")
|
||||
private final String commit;
|
||||
|
||||
@SerializedName("name")
|
||||
private final String name;
|
||||
|
||||
@SerializedName("body")
|
||||
private final String body;
|
||||
|
||||
@SerializedName("draft")
|
||||
private final boolean draft;
|
||||
|
||||
@SerializedName("prerelease")
|
||||
private final boolean preRelease;
|
||||
|
||||
@SerializedName("generate_release_notes")
|
||||
private final boolean generateReleaseNotes;
|
||||
|
||||
private Release(String tag, String commit, String name, String body, boolean draft, boolean preRelease, boolean generateReleaseNotes) {
|
||||
this.tag = tag;
|
||||
this.commit = commit;
|
||||
this.name = name;
|
||||
this.body = body;
|
||||
this.draft = draft;
|
||||
this.preRelease = preRelease;
|
||||
this.generateReleaseNotes = generateReleaseNotes;
|
||||
}
|
||||
|
||||
public String getTag() {
|
||||
return tag;
|
||||
}
|
||||
|
||||
public String getCommit() {
|
||||
return commit;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public String getBody() {
|
||||
return body;
|
||||
}
|
||||
|
||||
public boolean isDraft() {
|
||||
return draft;
|
||||
}
|
||||
|
||||
public boolean isPreRelease() {
|
||||
return preRelease;
|
||||
}
|
||||
|
||||
public boolean isGenerateReleaseNotes() {
|
||||
return generateReleaseNotes;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Release{" +
|
||||
"tag='" + tag + '\'' +
|
||||
", commit='" + commit + '\'' +
|
||||
", name='" + name + '\'' +
|
||||
", body='" + body + '\'' +
|
||||
", draft=" + draft +
|
||||
", preRelease=" + preRelease +
|
||||
", generateReleaseNotes=" + generateReleaseNotes +
|
||||
'}';
|
||||
}
|
||||
|
||||
public static Builder tag(String tag) {
|
||||
return new Builder().tag(tag);
|
||||
}
|
||||
|
||||
public static Builder commit(String commit) {
|
||||
return new Builder().commit(commit);
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
private String tag;
|
||||
private String commit;
|
||||
private String name;
|
||||
private String body;
|
||||
private boolean draft;
|
||||
private boolean preRelease;
|
||||
private boolean generateReleaseNotes;
|
||||
|
||||
private Builder() {
|
||||
}
|
||||
|
||||
public Builder tag(String tag) {
|
||||
this.tag = tag;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder commit(String commit) {
|
||||
this.commit = commit;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder name(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder body(String body) {
|
||||
this.body = body;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder draft(boolean draft) {
|
||||
this.draft = draft;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder preRelease(boolean preRelease) {
|
||||
this.preRelease = preRelease;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder generateReleaseNotes(boolean generateReleaseNotes) {
|
||||
this.generateReleaseNotes = generateReleaseNotes;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Release build() {
|
||||
return new Release(tag, commit, name, body, draft, preRelease, generateReleaseNotes);
|
||||
}
|
||||
}
|
||||
}
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.gradle.github.release;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
public class WorkflowDispatch {
|
||||
private String ref;
|
||||
private Map<String, Object> inputs;
|
||||
|
||||
public WorkflowDispatch() {
|
||||
}
|
||||
|
||||
public WorkflowDispatch(String ref, Map<String, Object> inputs) {
|
||||
this.ref = ref;
|
||||
this.inputs = inputs;
|
||||
}
|
||||
|
||||
public String getRef() {
|
||||
return ref;
|
||||
}
|
||||
|
||||
public void setRef(String ref) {
|
||||
this.ref = ref;
|
||||
}
|
||||
|
||||
public Map<String, Object> getInputs() {
|
||||
return inputs;
|
||||
}
|
||||
|
||||
public void setInputs(Map<String, Object> inputs) {
|
||||
this.inputs = inputs;
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2019-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.convention.versions;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.Scanner;
|
||||
|
||||
class CommandLineUtils {
|
||||
static void runCommand(File dir, String... args) {
|
||||
try {
|
||||
Process process = new ProcessBuilder()
|
||||
.directory(dir)
|
||||
.command(args)
|
||||
.start();
|
||||
writeLinesTo(process.getInputStream(), System.out);
|
||||
writeLinesTo(process.getErrorStream(), System.out);
|
||||
if (process.waitFor() != 0) {
|
||||
new RuntimeException("Failed to run " + Arrays.toString(args));
|
||||
}
|
||||
} catch (IOException | InterruptedException e) {
|
||||
throw new RuntimeException("Failed to run " + Arrays.toString(args), e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeLinesTo(InputStream input, PrintStream out) {
|
||||
Scanner scanner = new Scanner(input);
|
||||
while(scanner.hasNextLine()) {
|
||||
out.println(scanner.nextLine());
|
||||
}
|
||||
}
|
||||
}
|
||||
+49
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
* Copyright 2019-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.convention.versions;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.util.function.Function;
|
||||
|
||||
class FileUtils {
|
||||
static void replaceFileText(File file, Function<String, String> replaceText) {
|
||||
String buildFileText = readString(file);
|
||||
String updatedBuildFileText = replaceText.apply(buildFileText);
|
||||
writeString(file, updatedBuildFileText);
|
||||
}
|
||||
|
||||
static String readString(File file) {
|
||||
try {
|
||||
byte[] bytes = Files.readAllBytes(file.toPath());
|
||||
return new String(bytes);
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeString(File file, String text) {
|
||||
try {
|
||||
Files.write(file.toPath(), text.getBytes());
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
+5
-57
@@ -33,13 +33,8 @@ import org.gradle.api.artifacts.component.ModuleComponentIdentifier;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.PrintStream;
|
||||
import java.nio.file.Files;
|
||||
import java.time.Duration;
|
||||
import java.util.*;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
@@ -168,7 +163,7 @@ public class UpdateDependenciesPlugin implements Plugin<Project> {
|
||||
Integer issueNumber = gitHubApi.createIssue(createIssueResult.getRepositoryId(), title, createIssueResult.getLabelIds(), createIssueResult.getMilestoneId(), createIssueResult.getAssigneeId()).delayElement(Duration.ofSeconds(1)).block();
|
||||
commitMessage += "\n\nCloses gh-" + issueNumber;
|
||||
}
|
||||
runCommand(rootDir, "git", "commit", "-am", commitMessage);
|
||||
CommandLineUtils.runCommand(rootDir, "git", "commit", "-am", commitMessage);
|
||||
}
|
||||
|
||||
private Mono<GitHubApi.FindCreateIssueResult> createIssueResultMono(UpdateDependenciesExtension updateDependenciesExtension) {
|
||||
@@ -187,7 +182,7 @@ public class UpdateDependenciesPlugin implements Plugin<Project> {
|
||||
if (current.compareTo(running) > 0) {
|
||||
String title = "Update Gradle to " + current.getVersion();
|
||||
System.out.println(title);
|
||||
runCommand(project.getRootDir(), "./gradlew", "wrapper", "--gradle-version", current.getVersion(), "--no-daemon");
|
||||
CommandLineUtils.runCommand(project.getRootDir(), "./gradlew", "wrapper", "--gradle-version", current.getVersion(), "--no-daemon");
|
||||
afterGroup(updateDependenciesSettings, project.getRootDir(), title, createIssueResultMono(updateDependenciesSettings));
|
||||
}
|
||||
}
|
||||
@@ -204,30 +199,6 @@ public class UpdateDependenciesPlugin implements Plugin<Project> {
|
||||
};
|
||||
}
|
||||
|
||||
static void runCommand(File dir, String... args) {
|
||||
try {
|
||||
Process process = new ProcessBuilder()
|
||||
.directory(dir)
|
||||
.command(args)
|
||||
.start();
|
||||
writeLinesTo(process.getInputStream(), System.out);
|
||||
writeLinesTo(process.getErrorStream(), System.out);
|
||||
if (process.waitFor() != 0) {
|
||||
new RuntimeException("Failed to run " + Arrays.toString(args));
|
||||
}
|
||||
} catch (IOException | InterruptedException e) {
|
||||
throw new RuntimeException("Failed to run " + Arrays.toString(args), e);
|
||||
}
|
||||
}
|
||||
|
||||
static void writeLinesTo(InputStream input, PrintStream out) {
|
||||
Scanner scanner = new Scanner(input);
|
||||
while(scanner.hasNextLine()) {
|
||||
out.println(scanner.nextLine());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static Action<ComponentSelectionWithCurrent> excludeWithRegex(String regex, String reason) {
|
||||
Pattern pattern = Pattern.compile(regex);
|
||||
return (selection) -> {
|
||||
@@ -242,40 +213,17 @@ public class UpdateDependenciesPlugin implements Plugin<Project> {
|
||||
String ga = dependency.getGroup() + ":" + dependency.getName() + ":";
|
||||
String originalDependency = ga + dependency.getVersion();
|
||||
String replacementDependency = ga + updatedVersion(dependency);
|
||||
replaceFileText(buildFile, buildFileText -> buildFileText.replace(originalDependency, replacementDependency));
|
||||
}
|
||||
|
||||
static void replaceFileText(File file, Function<String, String> replaceText) {
|
||||
String buildFileText = readString(file);
|
||||
String updatedBuildFileText = replaceText.apply(buildFileText);
|
||||
writeString(file, updatedBuildFileText);
|
||||
}
|
||||
|
||||
private static String readString(File file) {
|
||||
try {
|
||||
byte[] bytes = Files.readAllBytes(file.toPath());
|
||||
return new String(bytes);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static void writeString(File file, String text) {
|
||||
try {
|
||||
Files.write(file.toPath(), text.getBytes());
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
FileUtils.replaceFileText(buildFile, buildFileText -> buildFileText.replace(originalDependency, replacementDependency));
|
||||
}
|
||||
|
||||
static void updateDependencyWithVersionVariable(File scanFile, File gradlePropertiesFile, DependencyOutdated dependency) {
|
||||
if (!gradlePropertiesFile.exists()) {
|
||||
return;
|
||||
}
|
||||
replaceFileText(gradlePropertiesFile, (gradlePropertiesText) -> {
|
||||
FileUtils.replaceFileText(gradlePropertiesFile, (gradlePropertiesText) -> {
|
||||
String ga = dependency.getGroup() + ":" + dependency.getName() + ":";
|
||||
Pattern pattern = Pattern.compile("\"" + ga + "\\$\\{?([^'\"]+?)\\}?\"");
|
||||
String buildFileText = readString(scanFile);
|
||||
String buildFileText = FileUtils.readString(scanFile);
|
||||
Matcher matcher = pattern.matcher(buildFileText);
|
||||
while (matcher.find()) {
|
||||
String versionVariable = matcher.group(1);
|
||||
|
||||
+44
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 2019-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.convention.versions;
|
||||
|
||||
import org.gradle.api.Action;
|
||||
import org.gradle.api.Plugin;
|
||||
import org.gradle.api.Project;
|
||||
|
||||
public class UpdateProjectVersionPlugin implements Plugin<Project> {
|
||||
@Override
|
||||
public void apply(Project project) {
|
||||
project.getTasks().register("updateProjectVersion", UpdateProjectVersionTask.class, new Action<UpdateProjectVersionTask>() {
|
||||
@Override
|
||||
public void execute(UpdateProjectVersionTask updateProjectVersionTask) {
|
||||
updateProjectVersionTask.setGroup("Release");
|
||||
updateProjectVersionTask.setDescription("Updates the project version to the next release in gradle.properties");
|
||||
updateProjectVersionTask.dependsOn("gitHubNextReleaseMilestone");
|
||||
updateProjectVersionTask.getNextVersionFile().fileProvider(project.provider(() -> project.file("next-release.yml")));
|
||||
}
|
||||
});
|
||||
project.getTasks().register("updateToSnapshotVersion", UpdateToSnapshotVersionTask.class, new Action<UpdateToSnapshotVersionTask>() {
|
||||
@Override
|
||||
public void execute(UpdateToSnapshotVersionTask updateToSnapshotVersionTask) {
|
||||
updateToSnapshotVersionTask.setGroup("Release");
|
||||
updateToSnapshotVersionTask.setDescription(
|
||||
"Updates the project version to the next snapshot in gradle.properties");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright 2019-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.convention.versions;
|
||||
|
||||
import org.gradle.api.DefaultTask;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.file.RegularFileProperty;
|
||||
import org.gradle.api.tasks.Input;
|
||||
import org.gradle.api.tasks.InputFile;
|
||||
import org.gradle.api.tasks.Optional;
|
||||
import org.gradle.api.tasks.TaskAction;
|
||||
import org.yaml.snakeyaml.Yaml;
|
||||
import org.yaml.snakeyaml.constructor.Constructor;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
|
||||
import org.springframework.gradle.github.milestones.NextVersionYml;
|
||||
|
||||
public abstract class UpdateProjectVersionTask extends DefaultTask {
|
||||
|
||||
@InputFile
|
||||
public abstract RegularFileProperty getNextVersionFile();
|
||||
|
||||
@TaskAction
|
||||
public void checkReleaseDueToday() throws FileNotFoundException {
|
||||
File nextVersionFile = getNextVersionFile().getAsFile().get();
|
||||
Yaml yaml = new Yaml(new Constructor(NextVersionYml.class));
|
||||
NextVersionYml nextVersionYml = yaml.load(new FileInputStream(nextVersionFile));
|
||||
String nextVersion = nextVersionYml.getVersion();
|
||||
if (nextVersion == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"Could not find version property in provided file " + nextVersionFile.getName());
|
||||
}
|
||||
String currentVersion = getProject().getVersion().toString();
|
||||
File gradlePropertiesFile = getProject().getRootProject().file(Project.GRADLE_PROPERTIES);
|
||||
if (!gradlePropertiesFile.exists()) {
|
||||
return;
|
||||
}
|
||||
System.out.println("Updating the project version in " + Project.GRADLE_PROPERTIES + " from " + currentVersion
|
||||
+ " to " + nextVersion);
|
||||
FileUtils.replaceFileText(gradlePropertiesFile, (gradlePropertiesText) -> {
|
||||
gradlePropertiesText = gradlePropertiesText.replace("version=" + currentVersion, "version=" + nextVersion);
|
||||
return gradlePropertiesText;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
+68
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
* Copyright 2019-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.convention.versions;
|
||||
|
||||
import org.gradle.api.DefaultTask;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.tasks.TaskAction;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
public abstract class UpdateToSnapshotVersionTask extends DefaultTask {
|
||||
|
||||
private static final String RELEASE_VERSION_PATTERN = "^([0-9]+)\\.([0-9]+)\\.([0-9]+)(-M\\d+|-RC\\d+)?$";
|
||||
|
||||
@TaskAction
|
||||
public void updateToSnapshotVersion() {
|
||||
String currentVersion = getProject().getVersion().toString();
|
||||
File gradlePropertiesFile = getProject().getRootProject().file(Project.GRADLE_PROPERTIES);
|
||||
if (!gradlePropertiesFile.exists()) {
|
||||
return;
|
||||
}
|
||||
String nextVersion = calculateNextSnapshotVersion(currentVersion);
|
||||
System.out.println("Updating the project version in " + Project.GRADLE_PROPERTIES + " from " + currentVersion
|
||||
+ " to " + nextVersion);
|
||||
FileUtils.replaceFileText(gradlePropertiesFile, (gradlePropertiesText) -> {
|
||||
gradlePropertiesText = gradlePropertiesText.replace("version=" + currentVersion, "version=" + nextVersion);
|
||||
return gradlePropertiesText;
|
||||
});
|
||||
}
|
||||
|
||||
private String calculateNextSnapshotVersion(String currentVersion) {
|
||||
Pattern releaseVersionPattern = Pattern.compile(RELEASE_VERSION_PATTERN);
|
||||
Matcher releaseVersion = releaseVersionPattern.matcher(currentVersion);
|
||||
|
||||
if (releaseVersion.find()) {
|
||||
String majorSegment = releaseVersion.group(1);
|
||||
String minorSegment = releaseVersion.group(2);
|
||||
String patchSegment = releaseVersion.group(3);
|
||||
String modifier = releaseVersion.group(4);
|
||||
if (modifier == null) {
|
||||
patchSegment = String.valueOf(Integer.parseInt(patchSegment) + 1);
|
||||
}
|
||||
System.out.println("modifier = " + modifier);
|
||||
return String.format("%s.%s.%s-SNAPSHOT", majorSegment, minorSegment, patchSegment);
|
||||
}
|
||||
else {
|
||||
throw new IllegalStateException(
|
||||
"Cannot calculate next snapshot version because the current project version does not conform to the expected format");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
-1
@@ -1 +0,0 @@
|
||||
implementation-class=io.spring.gradle.convention.IncludeCheckRemotePlugin
|
||||
@@ -1,111 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
||||
* use this file except in compliance with the License. You may obtain a copy of
|
||||
* the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
* License for the specific language governing permissions and limitations under
|
||||
* the License.
|
||||
*/
|
||||
|
||||
package io.spring.gradle.convention;
|
||||
|
||||
import io.spring.gradle.IncludeRepoTask;
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.tasks.GradleBuild;
|
||||
import org.gradle.testfixtures.ProjectBuilder;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
class IncludeCheckRemotePluginTest {
|
||||
|
||||
Project rootProject;
|
||||
|
||||
@AfterEach
|
||||
public void cleanup() throws Exception {
|
||||
if (rootProject != null) {
|
||||
FileUtils.deleteDirectory(rootProject.getProjectDir());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void applyWhenExtensionPropertiesNoTasksThenCreateCheckRemoteTaskWithDefaultTask() {
|
||||
this.rootProject = ProjectBuilder.builder().build();
|
||||
this.rootProject.getPluginManager().apply(IncludeCheckRemotePlugin.class);
|
||||
this.rootProject.getExtensions().configure(IncludeCheckRemotePlugin.IncludeCheckRemoteExtension.class,
|
||||
(includeCheckRemoteExtension) -> {
|
||||
includeCheckRemoteExtension.setProperty("repository", "my-project/my-repository");
|
||||
includeCheckRemoteExtension.setProperty("ref", "main");
|
||||
});
|
||||
|
||||
GradleBuild checkRemote = (GradleBuild) this.rootProject.getTasks().named("checkRemote").get();
|
||||
assertThat(checkRemote.getTasks()).containsExactly("check");
|
||||
}
|
||||
|
||||
@Test
|
||||
void applyWhenExtensionPropertiesTasksThenCreateCheckRemoteWithProvidedTasks() {
|
||||
this.rootProject = ProjectBuilder.builder().build();
|
||||
this.rootProject.getPluginManager().apply(IncludeCheckRemotePlugin.class);
|
||||
this.rootProject.getExtensions().configure(IncludeCheckRemotePlugin.IncludeCheckRemoteExtension.class,
|
||||
(includeCheckRemoteExtension) -> {
|
||||
includeCheckRemoteExtension.setProperty("repository", "my-project/my-repository");
|
||||
includeCheckRemoteExtension.setProperty("ref", "main");
|
||||
includeCheckRemoteExtension.setProperty("tasks", Arrays.asList("clean", "build", "test"));
|
||||
});
|
||||
|
||||
GradleBuild checkRemote = (GradleBuild) this.rootProject.getTasks().named("checkRemote").get();
|
||||
assertThat(checkRemote.getTasks()).containsExactly("clean", "build", "test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void applyWhenExtensionPropertiesThenRegisterIncludeRepoTaskWithExtensionProperties() {
|
||||
this.rootProject = ProjectBuilder.builder().build();
|
||||
this.rootProject.getPluginManager().apply(IncludeCheckRemotePlugin.class);
|
||||
this.rootProject.getExtensions().configure(IncludeCheckRemotePlugin.IncludeCheckRemoteExtension.class,
|
||||
(includeCheckRemoteExtension) -> {
|
||||
includeCheckRemoteExtension.setProperty("repository", "my-project/my-repository");
|
||||
includeCheckRemoteExtension.setProperty("ref", "main");
|
||||
});
|
||||
|
||||
IncludeRepoTask includeRepo = (IncludeRepoTask) this.rootProject.getTasks().named("includeRepo").get();
|
||||
assertThat(includeRepo).isNotNull();
|
||||
assertThat(includeRepo.getRepository().get()).isEqualTo("my-project/my-repository");
|
||||
assertThat(includeRepo.getRef().get()).isEqualTo("main");
|
||||
}
|
||||
|
||||
@Test
|
||||
void applyWhenRegisterTasksThenCheckRemoteDirSameAsIncludeRepoOutputDir() {
|
||||
this.rootProject = ProjectBuilder.builder().build();
|
||||
this.rootProject.getPluginManager().apply(IncludeCheckRemotePlugin.class);
|
||||
this.rootProject.getExtensions().configure(IncludeCheckRemotePlugin.IncludeCheckRemoteExtension.class,
|
||||
(includeCheckRemoteExtension) -> {
|
||||
includeCheckRemoteExtension.setProperty("repository", "my-project/my-repository");
|
||||
includeCheckRemoteExtension.setProperty("ref", "main");
|
||||
});
|
||||
IncludeRepoTask includeRepo = (IncludeRepoTask) this.rootProject.getTasks().named("includeRepo").get();
|
||||
GradleBuild checkRemote = (GradleBuild) this.rootProject.getTasks().named("checkRemote").get();
|
||||
assertThat(checkRemote.getDir()).isEqualTo(includeRepo.getOutputDirectory());
|
||||
}
|
||||
|
||||
@Test
|
||||
void applyWhenNoExtensionPropertiesThenRegisterTasks() {
|
||||
this.rootProject = ProjectBuilder.builder().build();
|
||||
this.rootProject.getPluginManager().apply(IncludeCheckRemotePlugin.class);
|
||||
IncludeRepoTask includeRepo = (IncludeRepoTask) this.rootProject.getTasks().named("includeRepo").get();
|
||||
GradleBuild checkRemote = (GradleBuild) this.rootProject.getTasks().named("checkRemote").get();
|
||||
assertThat(includeRepo).isNotNull();
|
||||
assertThat(checkRemote).isNotNull();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -58,7 +58,7 @@ public class SaganApiTests {
|
||||
Release release = new Release();
|
||||
release.setVersion("5.6.0-SNAPSHOT");
|
||||
release.setApiDocUrl("https://docs.spring.io/spring-security/site/docs/{version}/api/");
|
||||
release.setReferenceDocUrl("https://docs.spring.io/spring-security/site/docs/{version}/reference/html5/");
|
||||
release.setReferenceDocUrl("https://docs.spring.io/spring-security/reference/{version}/index.html");
|
||||
this.sagan.createReleaseForProject(release, "spring-security");
|
||||
RecordedRequest request = this.server.takeRequest(1, TimeUnit.SECONDS);
|
||||
assertThat(request.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/projects/spring-security/releases");
|
||||
@@ -67,7 +67,7 @@ public class SaganApiTests {
|
||||
assertThat(request.getBody().readString(Charset.defaultCharset())).isEqualToIgnoringWhitespace("{\n" +
|
||||
" \"version\":\"5.6.0-SNAPSHOT\",\n" +
|
||||
" \"current\":false,\n" +
|
||||
" \"referenceDocUrl\":\"https://docs.spring.io/spring-security/site/docs/{version}/reference/html5/\",\n" +
|
||||
" \"referenceDocUrl\":\"https://docs.spring.io/spring-security/reference/{version}/index.html\",\n" +
|
||||
" \"apiDocUrl\":\"https://docs.spring.io/spring-security/site/docs/{version}/api/\"\n" +
|
||||
"}");
|
||||
}
|
||||
|
||||
-388
@@ -1,388 +0,0 @@
|
||||
package io.spring.gradle.github.milestones;
|
||||
|
||||
import okhttp3.mockwebserver.MockResponse;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
import okhttp3.mockwebserver.RecordedRequest;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.gradle.github.milestones.GitHubMilestoneApi;
|
||||
import org.springframework.gradle.github.milestones.RepositoryRef;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
||||
|
||||
public class GitHubMilestoneApiTests {
|
||||
private GitHubMilestoneApi github;
|
||||
|
||||
private RepositoryRef repositoryRef = RepositoryRef.owner("spring-projects").repository("spring-security").build();
|
||||
|
||||
private MockWebServer server;
|
||||
|
||||
private String baseUrl;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() throws Exception {
|
||||
this.server = new MockWebServer();
|
||||
this.server.start();
|
||||
this.github = new GitHubMilestoneApi("mock-oauth-token");
|
||||
this.baseUrl = this.server.url("/api").toString();
|
||||
this.github.setBaseUrl(this.baseUrl);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void cleanup() throws Exception {
|
||||
this.server.shutdown();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findMilestoneNumberByTitleWhenFoundThenSuccess() throws Exception {
|
||||
String responseJson = "[\n" +
|
||||
" {\n" +
|
||||
" \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207\",\n" +
|
||||
" \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/207\",\n" +
|
||||
" \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207/labels\",\n" +
|
||||
" \"id\":6611880,\n" +
|
||||
" \"node_id\":\"MDk6TWlsZXN0b25lNjYxMTg4MA==\",\n" +
|
||||
" \"number\":207,\n" +
|
||||
" \"title\":\"5.6.x\",\n" +
|
||||
" \"description\":\"\",\n" +
|
||||
" \"creator\":{\n" +
|
||||
" \"login\":\"jgrandja\",\n" +
|
||||
" \"id\":10884212,\n" +
|
||||
" \"node_id\":\"MDQ6VXNlcjEwODg0MjEy\",\n" +
|
||||
" \"avatar_url\":\"https://avatars.githubusercontent.com/u/10884212?v=4\",\n" +
|
||||
" \"gravatar_id\":\"\",\n" +
|
||||
" \"url\":\"https://api.github.com/users/jgrandja\",\n" +
|
||||
" \"html_url\":\"https://github.com/jgrandja\",\n" +
|
||||
" \"followers_url\":\"https://api.github.com/users/jgrandja/followers\",\n" +
|
||||
" \"following_url\":\"https://api.github.com/users/jgrandja/following{/other_user}\",\n" +
|
||||
" \"gists_url\":\"https://api.github.com/users/jgrandja/gists{/gist_id}\",\n" +
|
||||
" \"starred_url\":\"https://api.github.com/users/jgrandja/starred{/owner}{/repo}\",\n" +
|
||||
" \"subscriptions_url\":\"https://api.github.com/users/jgrandja/subscriptions\",\n" +
|
||||
" \"organizations_url\":\"https://api.github.com/users/jgrandja/orgs\",\n" +
|
||||
" \"repos_url\":\"https://api.github.com/users/jgrandja/repos\",\n" +
|
||||
" \"events_url\":\"https://api.github.com/users/jgrandja/events{/privacy}\",\n" +
|
||||
" \"received_events_url\":\"https://api.github.com/users/jgrandja/received_events\",\n" +
|
||||
" \"type\":\"User\",\n" +
|
||||
" \"site_admin\":false\n" +
|
||||
" },\n" +
|
||||
" \"open_issues\":1,\n" +
|
||||
" \"closed_issues\":0,\n" +
|
||||
" \"state\":\"open\",\n" +
|
||||
" \"created_at\":\"2021-03-31T11:29:17Z\",\n" +
|
||||
" \"updated_at\":\"2021-03-31T11:30:47Z\",\n" +
|
||||
" \"due_on\":null,\n" +
|
||||
" \"closed_at\":null\n" +
|
||||
" },\n" +
|
||||
" {\n" +
|
||||
" \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191\",\n" +
|
||||
" \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/191\",\n" +
|
||||
" \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191/labels\",\n" +
|
||||
" \"id\":5884208,\n" +
|
||||
" \"node_id\":\"MDk6TWlsZXN0b25lNTg4NDIwOA==\",\n" +
|
||||
" \"number\":191,\n" +
|
||||
" \"title\":\"5.5.0-RC1\",\n" +
|
||||
" \"description\":\"\",\n" +
|
||||
" \"creator\":{\n" +
|
||||
" \"login\":\"jzheaux\",\n" +
|
||||
" \"id\":3627351,\n" +
|
||||
" \"node_id\":\"MDQ6VXNlcjM2MjczNTE=\",\n" +
|
||||
" \"avatar_url\":\"https://avatars.githubusercontent.com/u/3627351?v=4\",\n" +
|
||||
" \"gravatar_id\":\"\",\n" +
|
||||
" \"url\":\"https://api.github.com/users/jzheaux\",\n" +
|
||||
" \"html_url\":\"https://github.com/jzheaux\",\n" +
|
||||
" \"followers_url\":\"https://api.github.com/users/jzheaux/followers\",\n" +
|
||||
" \"following_url\":\"https://api.github.com/users/jzheaux/following{/other_user}\",\n" +
|
||||
" \"gists_url\":\"https://api.github.com/users/jzheaux/gists{/gist_id}\",\n" +
|
||||
" \"starred_url\":\"https://api.github.com/users/jzheaux/starred{/owner}{/repo}\",\n" +
|
||||
" \"subscriptions_url\":\"https://api.github.com/users/jzheaux/subscriptions\",\n" +
|
||||
" \"organizations_url\":\"https://api.github.com/users/jzheaux/orgs\",\n" +
|
||||
" \"repos_url\":\"https://api.github.com/users/jzheaux/repos\",\n" +
|
||||
" \"events_url\":\"https://api.github.com/users/jzheaux/events{/privacy}\",\n" +
|
||||
" \"received_events_url\":\"https://api.github.com/users/jzheaux/received_events\",\n" +
|
||||
" \"type\":\"User\",\n" +
|
||||
" \"site_admin\":false\n" +
|
||||
" },\n" +
|
||||
" \"open_issues\":21,\n" +
|
||||
" \"closed_issues\":23,\n" +
|
||||
" \"state\":\"open\",\n" +
|
||||
" \"created_at\":\"2020-09-16T13:28:03Z\",\n" +
|
||||
" \"updated_at\":\"2021-04-06T23:47:10Z\",\n" +
|
||||
" \"due_on\":\"2021-04-12T07:00:00Z\",\n" +
|
||||
" \"closed_at\":null\n" +
|
||||
" }\n" +
|
||||
"]";
|
||||
this.server.enqueue(new MockResponse().setBody(responseJson));
|
||||
|
||||
long milestoneNumberByTitle = this.github.findMilestoneNumberByTitle(this.repositoryRef, "5.5.0-RC1");
|
||||
|
||||
RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS);
|
||||
assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("get");
|
||||
assertThat(recordedRequest.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/milestones?per_page=100");
|
||||
|
||||
assertThat(milestoneNumberByTitle).isEqualTo(191);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void findMilestoneNumberByTitleWhenNotFoundThenException() throws Exception {
|
||||
String responseJson = "[\n" +
|
||||
" {\n" +
|
||||
" \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207\",\n" +
|
||||
" \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/207\",\n" +
|
||||
" \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207/labels\",\n" +
|
||||
" \"id\":6611880,\n" +
|
||||
" \"node_id\":\"MDk6TWlsZXN0b25lNjYxMTg4MA==\",\n" +
|
||||
" \"number\":207,\n" +
|
||||
" \"title\":\"5.6.x\",\n" +
|
||||
" \"description\":\"\",\n" +
|
||||
" \"creator\":{\n" +
|
||||
" \"login\":\"jgrandja\",\n" +
|
||||
" \"id\":10884212,\n" +
|
||||
" \"node_id\":\"MDQ6VXNlcjEwODg0MjEy\",\n" +
|
||||
" \"avatar_url\":\"https://avatars.githubusercontent.com/u/10884212?v=4\",\n" +
|
||||
" \"gravatar_id\":\"\",\n" +
|
||||
" \"url\":\"https://api.github.com/users/jgrandja\",\n" +
|
||||
" \"html_url\":\"https://github.com/jgrandja\",\n" +
|
||||
" \"followers_url\":\"https://api.github.com/users/jgrandja/followers\",\n" +
|
||||
" \"following_url\":\"https://api.github.com/users/jgrandja/following{/other_user}\",\n" +
|
||||
" \"gists_url\":\"https://api.github.com/users/jgrandja/gists{/gist_id}\",\n" +
|
||||
" \"starred_url\":\"https://api.github.com/users/jgrandja/starred{/owner}{/repo}\",\n" +
|
||||
" \"subscriptions_url\":\"https://api.github.com/users/jgrandja/subscriptions\",\n" +
|
||||
" \"organizations_url\":\"https://api.github.com/users/jgrandja/orgs\",\n" +
|
||||
" \"repos_url\":\"https://api.github.com/users/jgrandja/repos\",\n" +
|
||||
" \"events_url\":\"https://api.github.com/users/jgrandja/events{/privacy}\",\n" +
|
||||
" \"received_events_url\":\"https://api.github.com/users/jgrandja/received_events\",\n" +
|
||||
" \"type\":\"User\",\n" +
|
||||
" \"site_admin\":false\n" +
|
||||
" },\n" +
|
||||
" \"open_issues\":1,\n" +
|
||||
" \"closed_issues\":0,\n" +
|
||||
" \"state\":\"open\",\n" +
|
||||
" \"created_at\":\"2021-03-31T11:29:17Z\",\n" +
|
||||
" \"updated_at\":\"2021-03-31T11:30:47Z\",\n" +
|
||||
" \"due_on\":null,\n" +
|
||||
" \"closed_at\":null\n" +
|
||||
" },\n" +
|
||||
" {\n" +
|
||||
" \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191\",\n" +
|
||||
" \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/191\",\n" +
|
||||
" \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191/labels\",\n" +
|
||||
" \"id\":5884208,\n" +
|
||||
" \"node_id\":\"MDk6TWlsZXN0b25lNTg4NDIwOA==\",\n" +
|
||||
" \"number\":191,\n" +
|
||||
" \"title\":\"5.5.0-RC1\",\n" +
|
||||
" \"description\":\"\",\n" +
|
||||
" \"creator\":{\n" +
|
||||
" \"login\":\"jzheaux\",\n" +
|
||||
" \"id\":3627351,\n" +
|
||||
" \"node_id\":\"MDQ6VXNlcjM2MjczNTE=\",\n" +
|
||||
" \"avatar_url\":\"https://avatars.githubusercontent.com/u/3627351?v=4\",\n" +
|
||||
" \"gravatar_id\":\"\",\n" +
|
||||
" \"url\":\"https://api.github.com/users/jzheaux\",\n" +
|
||||
" \"html_url\":\"https://github.com/jzheaux\",\n" +
|
||||
" \"followers_url\":\"https://api.github.com/users/jzheaux/followers\",\n" +
|
||||
" \"following_url\":\"https://api.github.com/users/jzheaux/following{/other_user}\",\n" +
|
||||
" \"gists_url\":\"https://api.github.com/users/jzheaux/gists{/gist_id}\",\n" +
|
||||
" \"starred_url\":\"https://api.github.com/users/jzheaux/starred{/owner}{/repo}\",\n" +
|
||||
" \"subscriptions_url\":\"https://api.github.com/users/jzheaux/subscriptions\",\n" +
|
||||
" \"organizations_url\":\"https://api.github.com/users/jzheaux/orgs\",\n" +
|
||||
" \"repos_url\":\"https://api.github.com/users/jzheaux/repos\",\n" +
|
||||
" \"events_url\":\"https://api.github.com/users/jzheaux/events{/privacy}\",\n" +
|
||||
" \"received_events_url\":\"https://api.github.com/users/jzheaux/received_events\",\n" +
|
||||
" \"type\":\"User\",\n" +
|
||||
" \"site_admin\":false\n" +
|
||||
" },\n" +
|
||||
" \"open_issues\":21,\n" +
|
||||
" \"closed_issues\":23,\n" +
|
||||
" \"state\":\"open\",\n" +
|
||||
" \"created_at\":\"2020-09-16T13:28:03Z\",\n" +
|
||||
" \"updated_at\":\"2021-04-06T23:47:10Z\",\n" +
|
||||
" \"due_on\":\"2021-04-12T07:00:00Z\",\n" +
|
||||
" \"closed_at\":null\n" +
|
||||
" }\n" +
|
||||
"]";
|
||||
this.server.enqueue(new MockResponse().setBody(responseJson));
|
||||
|
||||
assertThatExceptionOfType(RuntimeException.class)
|
||||
.isThrownBy(() -> this.github.findMilestoneNumberByTitle(this.repositoryRef, "missing"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isOpenIssuesForMilestoneNumberWhenAllClosedThenFalse() throws Exception {
|
||||
String responseJson = "[]";
|
||||
long milestoneNumber = 202;
|
||||
this.server.enqueue(new MockResponse().setBody(responseJson));
|
||||
|
||||
assertThat(this.github.isOpenIssuesForMilestoneNumber(this.repositoryRef, milestoneNumber)).isFalse();
|
||||
|
||||
RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS);
|
||||
assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("get");
|
||||
assertThat(recordedRequest.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/issues?per_page=1&milestone=" + milestoneNumber);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isOpenIssuesForMilestoneNumberWhenOpenIssuesThenTrue() throws Exception {
|
||||
String responseJson = "[\n" +
|
||||
" {\n" +
|
||||
" \"url\":\"https://api.github.com/repos/spring-projects/spring-security/issues/9562\",\n" +
|
||||
" \"repository_url\":\"https://api.github.com/repos/spring-projects/spring-security\",\n" +
|
||||
" \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/issues/9562/labels{/name}\",\n" +
|
||||
" \"comments_url\":\"https://api.github.com/repos/spring-projects/spring-security/issues/9562/comments\",\n" +
|
||||
" \"events_url\":\"https://api.github.com/repos/spring-projects/spring-security/issues/9562/events\",\n" +
|
||||
" \"html_url\":\"https://github.com/spring-projects/spring-security/pull/9562\",\n" +
|
||||
" \"id\":851886504,\n" +
|
||||
" \"node_id\":\"MDExOlB1bGxSZXF1ZXN0NjEwMjMzMDcw\",\n" +
|
||||
" \"number\":9562,\n" +
|
||||
" \"title\":\"Add package-list\",\n" +
|
||||
" \"user\":{\n" +
|
||||
" \"login\":\"jzheaux\",\n" +
|
||||
" \"id\":3627351,\n" +
|
||||
" \"node_id\":\"MDQ6VXNlcjM2MjczNTE=\",\n" +
|
||||
" \"avatar_url\":\"https://avatars.githubusercontent.com/u/3627351?v=4\",\n" +
|
||||
" \"gravatar_id\":\"\",\n" +
|
||||
" \"url\":\"https://api.github.com/users/jzheaux\",\n" +
|
||||
" \"html_url\":\"https://github.com/jzheaux\",\n" +
|
||||
" \"followers_url\":\"https://api.github.com/users/jzheaux/followers\",\n" +
|
||||
" \"following_url\":\"https://api.github.com/users/jzheaux/following{/other_user}\",\n" +
|
||||
" \"gists_url\":\"https://api.github.com/users/jzheaux/gists{/gist_id}\",\n" +
|
||||
" \"starred_url\":\"https://api.github.com/users/jzheaux/starred{/owner}{/repo}\",\n" +
|
||||
" \"subscriptions_url\":\"https://api.github.com/users/jzheaux/subscriptions\",\n" +
|
||||
" \"organizations_url\":\"https://api.github.com/users/jzheaux/orgs\",\n" +
|
||||
" \"repos_url\":\"https://api.github.com/users/jzheaux/repos\",\n" +
|
||||
" \"events_url\":\"https://api.github.com/users/jzheaux/events{/privacy}\",\n" +
|
||||
" \"received_events_url\":\"https://api.github.com/users/jzheaux/received_events\",\n" +
|
||||
" \"type\":\"User\",\n" +
|
||||
" \"site_admin\":false\n" +
|
||||
" },\n" +
|
||||
" \"labels\":[\n" +
|
||||
" {\n" +
|
||||
" \"id\":322225043,\n" +
|
||||
" \"node_id\":\"MDU6TGFiZWwzMjIyMjUwNDM=\",\n" +
|
||||
" \"url\":\"https://api.github.com/repos/spring-projects/spring-security/labels/in:%20build\",\n" +
|
||||
" \"name\":\"in: build\",\n" +
|
||||
" \"color\":\"e8f9de\",\n" +
|
||||
" \"default\":false,\n" +
|
||||
" \"description\":\"An issue in the build\"\n" +
|
||||
" },\n" +
|
||||
" {\n" +
|
||||
" \"id\":322225079,\n" +
|
||||
" \"node_id\":\"MDU6TGFiZWwzMjIyMjUwNzk=\",\n" +
|
||||
" \"url\":\"https://api.github.com/repos/spring-projects/spring-security/labels/type:%20bug\",\n" +
|
||||
" \"name\":\"type: bug\",\n" +
|
||||
" \"color\":\"e3d9fc\",\n" +
|
||||
" \"default\":false,\n" +
|
||||
" \"description\":\"A general bug\"\n" +
|
||||
" }\n" +
|
||||
" ],\n" +
|
||||
" \"state\":\"open\",\n" +
|
||||
" \"locked\":false,\n" +
|
||||
" \"assignee\":{\n" +
|
||||
" \"login\":\"rwinch\",\n" +
|
||||
" \"id\":362503,\n" +
|
||||
" \"node_id\":\"MDQ6VXNlcjM2MjUwMw==\",\n" +
|
||||
" \"avatar_url\":\"https://avatars.githubusercontent.com/u/362503?v=4\",\n" +
|
||||
" \"gravatar_id\":\"\",\n" +
|
||||
" \"url\":\"https://api.github.com/users/rwinch\",\n" +
|
||||
" \"html_url\":\"https://github.com/rwinch\",\n" +
|
||||
" \"followers_url\":\"https://api.github.com/users/rwinch/followers\",\n" +
|
||||
" \"following_url\":\"https://api.github.com/users/rwinch/following{/other_user}\",\n" +
|
||||
" \"gists_url\":\"https://api.github.com/users/rwinch/gists{/gist_id}\",\n" +
|
||||
" \"starred_url\":\"https://api.github.com/users/rwinch/starred{/owner}{/repo}\",\n" +
|
||||
" \"subscriptions_url\":\"https://api.github.com/users/rwinch/subscriptions\",\n" +
|
||||
" \"organizations_url\":\"https://api.github.com/users/rwinch/orgs\",\n" +
|
||||
" \"repos_url\":\"https://api.github.com/users/rwinch/repos\",\n" +
|
||||
" \"events_url\":\"https://api.github.com/users/rwinch/events{/privacy}\",\n" +
|
||||
" \"received_events_url\":\"https://api.github.com/users/rwinch/received_events\",\n" +
|
||||
" \"type\":\"User\",\n" +
|
||||
" \"site_admin\":false\n" +
|
||||
" },\n" +
|
||||
" \"assignees\":[\n" +
|
||||
" {\n" +
|
||||
" \"login\":\"rwinch\",\n" +
|
||||
" \"id\":362503,\n" +
|
||||
" \"node_id\":\"MDQ6VXNlcjM2MjUwMw==\",\n" +
|
||||
" \"avatar_url\":\"https://avatars.githubusercontent.com/u/362503?v=4\",\n" +
|
||||
" \"gravatar_id\":\"\",\n" +
|
||||
" \"url\":\"https://api.github.com/users/rwinch\",\n" +
|
||||
" \"html_url\":\"https://github.com/rwinch\",\n" +
|
||||
" \"followers_url\":\"https://api.github.com/users/rwinch/followers\",\n" +
|
||||
" \"following_url\":\"https://api.github.com/users/rwinch/following{/other_user}\",\n" +
|
||||
" \"gists_url\":\"https://api.github.com/users/rwinch/gists{/gist_id}\",\n" +
|
||||
" \"starred_url\":\"https://api.github.com/users/rwinch/starred{/owner}{/repo}\",\n" +
|
||||
" \"subscriptions_url\":\"https://api.github.com/users/rwinch/subscriptions\",\n" +
|
||||
" \"organizations_url\":\"https://api.github.com/users/rwinch/orgs\",\n" +
|
||||
" \"repos_url\":\"https://api.github.com/users/rwinch/repos\",\n" +
|
||||
" \"events_url\":\"https://api.github.com/users/rwinch/events{/privacy}\",\n" +
|
||||
" \"received_events_url\":\"https://api.github.com/users/rwinch/received_events\",\n" +
|
||||
" \"type\":\"User\",\n" +
|
||||
" \"site_admin\":false\n" +
|
||||
" }\n" +
|
||||
" ],\n" +
|
||||
" \"milestone\":{\n" +
|
||||
" \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191\",\n" +
|
||||
" \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/191\",\n" +
|
||||
" \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191/labels\",\n" +
|
||||
" \"id\":5884208,\n" +
|
||||
" \"node_id\":\"MDk6TWlsZXN0b25lNTg4NDIwOA==\",\n" +
|
||||
" \"number\":191,\n" +
|
||||
" \"title\":\"5.5.0-RC1\",\n" +
|
||||
" \"description\":\"\",\n" +
|
||||
" \"creator\":{\n" +
|
||||
" \"login\":\"jzheaux\",\n" +
|
||||
" \"id\":3627351,\n" +
|
||||
" \"node_id\":\"MDQ6VXNlcjM2MjczNTE=\",\n" +
|
||||
" \"avatar_url\":\"https://avatars.githubusercontent.com/u/3627351?v=4\",\n" +
|
||||
" \"gravatar_id\":\"\",\n" +
|
||||
" \"url\":\"https://api.github.com/users/jzheaux\",\n" +
|
||||
" \"html_url\":\"https://github.com/jzheaux\",\n" +
|
||||
" \"followers_url\":\"https://api.github.com/users/jzheaux/followers\",\n" +
|
||||
" \"following_url\":\"https://api.github.com/users/jzheaux/following{/other_user}\",\n" +
|
||||
" \"gists_url\":\"https://api.github.com/users/jzheaux/gists{/gist_id}\",\n" +
|
||||
" \"starred_url\":\"https://api.github.com/users/jzheaux/starred{/owner}{/repo}\",\n" +
|
||||
" \"subscriptions_url\":\"https://api.github.com/users/jzheaux/subscriptions\",\n" +
|
||||
" \"organizations_url\":\"https://api.github.com/users/jzheaux/orgs\",\n" +
|
||||
" \"repos_url\":\"https://api.github.com/users/jzheaux/repos\",\n" +
|
||||
" \"events_url\":\"https://api.github.com/users/jzheaux/events{/privacy}\",\n" +
|
||||
" \"received_events_url\":\"https://api.github.com/users/jzheaux/received_events\",\n" +
|
||||
" \"type\":\"User\",\n" +
|
||||
" \"site_admin\":false\n" +
|
||||
" },\n" +
|
||||
" \"open_issues\":21,\n" +
|
||||
" \"closed_issues\":23,\n" +
|
||||
" \"state\":\"open\",\n" +
|
||||
" \"created_at\":\"2020-09-16T13:28:03Z\",\n" +
|
||||
" \"updated_at\":\"2021-04-06T23:47:10Z\",\n" +
|
||||
" \"due_on\":\"2021-04-12T07:00:00Z\",\n" +
|
||||
" \"closed_at\":null\n" +
|
||||
" },\n" +
|
||||
" \"comments\":0,\n" +
|
||||
" \"created_at\":\"2021-04-06T23:47:10Z\",\n" +
|
||||
" \"updated_at\":\"2021-04-07T17:00:00Z\",\n" +
|
||||
" \"closed_at\":null,\n" +
|
||||
" \"author_association\":\"MEMBER\",\n" +
|
||||
" \"active_lock_reason\":null,\n" +
|
||||
" \"pull_request\":{\n" +
|
||||
" \"url\":\"https://api.github.com/repos/spring-projects/spring-security/pulls/9562\",\n" +
|
||||
" \"html_url\":\"https://github.com/spring-projects/spring-security/pull/9562\",\n" +
|
||||
" \"diff_url\":\"https://github.com/spring-projects/spring-security/pull/9562.diff\",\n" +
|
||||
" \"patch_url\":\"https://github.com/spring-projects/spring-security/pull/9562.patch\"\n" +
|
||||
" },\n" +
|
||||
" \"body\":\"Closes gh-9528\\r\\n\\r\\n<!--\\r\\nFor Security Vulnerabilities, please use https://pivotal.io/security#reporting\\r\\n-->\\r\\n\\r\\n<!--\\r\\nBefore creating new features, we recommend creating an issue to discuss the feature. This ensures that everyone is on the same page before extensive work is done.\\r\\n\\r\\nThanks for contributing to Spring Security. Please provide a brief description of your pull-request and reference any related issue numbers (prefix references with gh-).\\r\\n-->\\r\\n\",\n" +
|
||||
" \"performed_via_github_app\":null\n" +
|
||||
" }\n" +
|
||||
"]";
|
||||
long milestoneNumber = 191;
|
||||
this.server.enqueue(new MockResponse().setBody(responseJson));
|
||||
|
||||
assertThat(this.github.isOpenIssuesForMilestoneNumber(this.repositoryRef, milestoneNumber)).isTrue();
|
||||
|
||||
RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS);
|
||||
assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("get");
|
||||
assertThat(recordedRequest.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/issues?per_page=1&milestone=" + milestoneNumber);
|
||||
}
|
||||
|
||||
}
|
||||
+270
@@ -0,0 +1,270 @@
|
||||
package org.springframework.gradle.antora;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.gradle.api.GradleException;
|
||||
import org.gradle.api.Project;
|
||||
import org.gradle.api.Task;
|
||||
import org.gradle.testfixtures.ProjectBuilder;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
import static org.assertj.core.api.Assertions.assertThatIOException;
|
||||
|
||||
class AntoraVersionPluginTests {
|
||||
|
||||
@Test
|
||||
void defaultsPropertiesWhenSnapshot() {
|
||||
String expectedVersion = "1.0.0-SNAPSHOT";
|
||||
Project project = ProjectBuilder.builder().build();
|
||||
project.setVersion(expectedVersion);
|
||||
project.getPluginManager().apply(AntoraVersionPlugin.class);
|
||||
|
||||
Task task = project.getTasks().findByName(AntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
|
||||
|
||||
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
|
||||
|
||||
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
|
||||
assertThat(checkAntoraVersionTask.getAntoraVersion().get()).isEqualTo("1.0.0");
|
||||
assertThat(checkAntoraVersionTask.getAntoraPrerelease().get()).isEqualTo("-SNAPSHOT");
|
||||
assertThat(checkAntoraVersionTask.getAntoraDisplayVersion().isPresent()).isFalse();
|
||||
assertThat(checkAntoraVersionTask.getAntoraYmlFile().getAsFile().get()).isEqualTo(project.file("antora.yml"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultsPropertiesWhenMilestone() {
|
||||
String expectedVersion = "1.0.0-M1";
|
||||
Project project = ProjectBuilder.builder().build();
|
||||
project.setVersion(expectedVersion);
|
||||
project.getPluginManager().apply(AntoraVersionPlugin.class);
|
||||
|
||||
Task task = project.getTasks().findByName(AntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
|
||||
|
||||
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
|
||||
|
||||
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
|
||||
assertThat(checkAntoraVersionTask.getAntoraVersion().get()).isEqualTo("1.0.0-M1");
|
||||
assertThat(checkAntoraVersionTask.getAntoraPrerelease().get()).isEqualTo("true");
|
||||
assertThat(checkAntoraVersionTask.getAntoraDisplayVersion().get()).isEqualTo(checkAntoraVersionTask.getAntoraVersion().get());
|
||||
assertThat(checkAntoraVersionTask.getAntoraYmlFile().getAsFile().get()).isEqualTo(project.file("antora.yml"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultsPropertiesWhenRc() {
|
||||
String expectedVersion = "1.0.0-RC1";
|
||||
Project project = ProjectBuilder.builder().build();
|
||||
project.setVersion(expectedVersion);
|
||||
project.getPluginManager().apply(AntoraVersionPlugin.class);
|
||||
|
||||
Task task = project.getTasks().findByName(AntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
|
||||
|
||||
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
|
||||
|
||||
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
|
||||
assertThat(checkAntoraVersionTask.getAntoraVersion().get()).isEqualTo("1.0.0-RC1");
|
||||
assertThat(checkAntoraVersionTask.getAntoraPrerelease().get()).isEqualTo("true");
|
||||
assertThat(checkAntoraVersionTask.getAntoraDisplayVersion().get()).isEqualTo(checkAntoraVersionTask.getAntoraVersion().get());
|
||||
assertThat(checkAntoraVersionTask.getAntoraYmlFile().getAsFile().get()).isEqualTo(project.file("antora.yml"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaultsPropertiesWhenRelease() {
|
||||
String expectedVersion = "1.0.0";
|
||||
Project project = ProjectBuilder.builder().build();
|
||||
project.setVersion(expectedVersion);
|
||||
project.getPluginManager().apply(AntoraVersionPlugin.class);
|
||||
|
||||
Task task = project.getTasks().findByName(AntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
|
||||
|
||||
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
|
||||
|
||||
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
|
||||
assertThat(checkAntoraVersionTask.getAntoraVersion().get()).isEqualTo("1.0.0");
|
||||
assertThat(checkAntoraVersionTask.getAntoraPrerelease().isPresent()).isFalse();
|
||||
assertThat(checkAntoraVersionTask.getAntoraDisplayVersion().isPresent()).isFalse();
|
||||
assertThat(checkAntoraVersionTask.getAntoraYmlFile().getAsFile().get()).isEqualTo(project.file("antora.yml"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void explicitProperties() {
|
||||
Project project = ProjectBuilder.builder().build();
|
||||
project.getPluginManager().apply(AntoraVersionPlugin.class);
|
||||
|
||||
Task task = project.getTasks().findByName(AntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
|
||||
|
||||
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
|
||||
checkAntoraVersionTask.getAntoraVersion().set("1.0.0");
|
||||
checkAntoraVersionTask.getAntoraPrerelease().set("-SNAPSHOT");
|
||||
assertThat(checkAntoraVersionTask.getAntoraVersion().get()).isEqualTo("1.0.0");
|
||||
assertThat(checkAntoraVersionTask.getAntoraPrerelease().get()).isEqualTo("-SNAPSHOT");
|
||||
assertThat(checkAntoraVersionTask.getAntoraDisplayVersion().isPresent()).isFalse();
|
||||
assertThat(checkAntoraVersionTask.getAntoraYmlFile().getAsFile().get()).isEqualTo(project.file("antora.yml"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void versionNotDefined() throws Exception {
|
||||
Project project = ProjectBuilder.builder().build();
|
||||
File rootDir = project.getRootDir();
|
||||
IOUtils.write("version: '1.0.0'", new FileOutputStream(new File(rootDir, "antora.yml")), StandardCharsets.UTF_8);
|
||||
project.getPluginManager().apply(AntoraVersionPlugin.class);
|
||||
|
||||
Task task = project.getTasks().findByName(AntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
|
||||
|
||||
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
|
||||
|
||||
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
|
||||
assertThatExceptionOfType(GradleException.class).isThrownBy(() -> checkAntoraVersionTask.check());
|
||||
}
|
||||
|
||||
@Test
|
||||
void antoraFileNotFound() throws Exception {
|
||||
String expectedVersion = "1.0.0-SNAPSHOT";
|
||||
Project project = ProjectBuilder.builder().build();
|
||||
project.setVersion(expectedVersion);
|
||||
project.getPluginManager().apply(AntoraVersionPlugin.class);
|
||||
|
||||
Task task = project.getTasks().findByName(AntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
|
||||
|
||||
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
|
||||
|
||||
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
|
||||
assertThatIOException().isThrownBy(() -> checkAntoraVersionTask.check());
|
||||
}
|
||||
|
||||
@Test
|
||||
void actualAntoraPrereleaseNull() throws Exception {
|
||||
String expectedVersion = "1.0.0-SNAPSHOT";
|
||||
Project project = ProjectBuilder.builder().build();
|
||||
File rootDir = project.getRootDir();
|
||||
IOUtils.write("version: '1.0.0'", new FileOutputStream(new File(rootDir, "antora.yml")), StandardCharsets.UTF_8);
|
||||
project.setVersion(expectedVersion);
|
||||
project.getPluginManager().apply(AntoraVersionPlugin.class);
|
||||
|
||||
Task task = project.getTasks().findByName(AntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
|
||||
|
||||
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
|
||||
|
||||
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
|
||||
assertThatExceptionOfType(GradleException.class).isThrownBy(() -> checkAntoraVersionTask.check());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchesWhenSnapshot() throws Exception {
|
||||
String expectedVersion = "1.0.0-SNAPSHOT";
|
||||
Project project = ProjectBuilder.builder().build();
|
||||
File rootDir = project.getRootDir();
|
||||
IOUtils.write("version: '1.0.0'\nprerelease: '-SNAPSHOT'", new FileOutputStream(new File(rootDir, "antora.yml")), StandardCharsets.UTF_8);
|
||||
project.setVersion(expectedVersion);
|
||||
project.getPluginManager().apply(AntoraVersionPlugin.class);
|
||||
|
||||
Task task = project.getTasks().findByName(AntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
|
||||
|
||||
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
|
||||
|
||||
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
|
||||
checkAntoraVersionTask.check();
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchesWhenMilestone() throws Exception {
|
||||
String expectedVersion = "1.0.0-M1";
|
||||
Project project = ProjectBuilder.builder().build();
|
||||
File rootDir = project.getRootDir();
|
||||
IOUtils.write("version: '1.0.0-M1'\nprerelease: 'true'\ndisplay_version: '1.0.0-M1'", new FileOutputStream(new File(rootDir, "antora.yml")), StandardCharsets.UTF_8);
|
||||
project.setVersion(expectedVersion);
|
||||
project.getPluginManager().apply(AntoraVersionPlugin.class);
|
||||
|
||||
Task task = project.getTasks().findByName(AntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
|
||||
|
||||
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
|
||||
|
||||
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
|
||||
checkAntoraVersionTask.check();
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchesWhenRc() throws Exception {
|
||||
String expectedVersion = "1.0.0-RC1";
|
||||
Project project = ProjectBuilder.builder().build();
|
||||
File rootDir = project.getRootDir();
|
||||
IOUtils.write("version: '1.0.0-RC1'\nprerelease: 'true'\ndisplay_version: '1.0.0-RC1'", new FileOutputStream(new File(rootDir, "antora.yml")), StandardCharsets.UTF_8);
|
||||
project.setVersion(expectedVersion);
|
||||
project.getPluginManager().apply(AntoraVersionPlugin.class);
|
||||
|
||||
Task task = project.getTasks().findByName(AntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
|
||||
|
||||
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
|
||||
|
||||
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
|
||||
checkAntoraVersionTask.check();
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchesWhenReleaseAndPrereleaseUndefined() throws Exception {
|
||||
String expectedVersion = "1.0.0";
|
||||
Project project = ProjectBuilder.builder().build();
|
||||
File rootDir = project.getRootDir();
|
||||
IOUtils.write("version: '1.0.0'", new FileOutputStream(new File(rootDir, "antora.yml")), StandardCharsets.UTF_8);
|
||||
project.setVersion(expectedVersion);
|
||||
project.getPluginManager().apply(AntoraVersionPlugin.class);
|
||||
|
||||
Task task = project.getTasks().findByName(AntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
|
||||
|
||||
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
|
||||
|
||||
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
|
||||
checkAntoraVersionTask.check();
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchesWhenExplicitRelease() throws Exception {
|
||||
Project project = ProjectBuilder.builder().build();
|
||||
File rootDir = project.getRootDir();
|
||||
IOUtils.write("version: '1.0.0'", new FileOutputStream(new File(rootDir, "antora.yml")), StandardCharsets.UTF_8);
|
||||
project.getPluginManager().apply(AntoraVersionPlugin.class);
|
||||
|
||||
Task task = project.getTasks().findByName(AntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
|
||||
|
||||
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
|
||||
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
|
||||
((CheckAntoraVersionTask) task).getAntoraVersion().set("1.0.0");
|
||||
checkAntoraVersionTask.check();
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchesWhenExplicitPrerelease() throws Exception {
|
||||
Project project = ProjectBuilder.builder().build();
|
||||
File rootDir = project.getRootDir();
|
||||
IOUtils.write("version: '1.0.0'\nprerelease: '-SNAPSHOT'", new FileOutputStream(new File(rootDir, "antora.yml")), StandardCharsets.UTF_8);
|
||||
project.getPluginManager().apply(AntoraVersionPlugin.class);
|
||||
|
||||
Task task = project.getTasks().findByName(AntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
|
||||
|
||||
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
|
||||
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
|
||||
((CheckAntoraVersionTask) task).getAntoraVersion().set("1.0.0");
|
||||
((CheckAntoraVersionTask) task).getAntoraPrerelease().set("-SNAPSHOT");
|
||||
checkAntoraVersionTask.check();
|
||||
}
|
||||
|
||||
@Test
|
||||
void matchesWhenMissingPropertyDefined() throws Exception {
|
||||
Project project = ProjectBuilder.builder().build();
|
||||
File rootDir = project.getRootDir();
|
||||
IOUtils.write("name: 'ROOT'\nversion: '1.0.0'", new FileOutputStream(new File(rootDir, "antora.yml")), StandardCharsets.UTF_8);
|
||||
project.getPluginManager().apply(AntoraVersionPlugin.class);
|
||||
|
||||
Task task = project.getTasks().findByName(AntoraVersionPlugin.ANTORA_CHECK_VERSION_TASK_NAME);
|
||||
|
||||
assertThat(task).isInstanceOf(CheckAntoraVersionTask.class);
|
||||
CheckAntoraVersionTask checkAntoraVersionTask = (CheckAntoraVersionTask) task;
|
||||
((CheckAntoraVersionTask) task).getAntoraVersion().set("1.0.0");
|
||||
checkAntoraVersionTask.check();
|
||||
}
|
||||
|
||||
}
|
||||
+840
-1
@@ -1,5 +1,12 @@
|
||||
package org.springframework.gradle.github.milestones;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import okhttp3.mockwebserver.MockResponse;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
import okhttp3.mockwebserver.RecordedRequest;
|
||||
@@ -7,7 +14,7 @@ import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.springframework.gradle.github.RepositoryRef;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
@@ -383,4 +390,836 @@ public class GitHubMilestoneApiTests {
|
||||
assertThat(recordedRequest.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/issues?per_page=1&milestone=" + milestoneNumber);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isMilestoneDueTodayWhenNotFoundThenException() throws Exception {
|
||||
String responseJson = "[\n" +
|
||||
" {\n" +
|
||||
" \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207\",\n" +
|
||||
" \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/207\",\n" +
|
||||
" \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207/labels\",\n" +
|
||||
" \"id\":6611880,\n" +
|
||||
" \"node_id\":\"MDk6TWlsZXN0b25lNjYxMTg4MA==\",\n" +
|
||||
" \"number\":207,\n" +
|
||||
" \"title\":\"5.6.x\",\n" +
|
||||
" \"description\":\"\",\n" +
|
||||
" \"creator\":{\n" +
|
||||
" \"login\":\"jgrandja\",\n" +
|
||||
" \"id\":10884212,\n" +
|
||||
" \"node_id\":\"MDQ6VXNlcjEwODg0MjEy\",\n" +
|
||||
" \"avatar_url\":\"https://avatars.githubusercontent.com/u/10884212?v=4\",\n" +
|
||||
" \"gravatar_id\":\"\",\n" +
|
||||
" \"url\":\"https://api.github.com/users/jgrandja\",\n" +
|
||||
" \"html_url\":\"https://github.com/jgrandja\",\n" +
|
||||
" \"followers_url\":\"https://api.github.com/users/jgrandja/followers\",\n" +
|
||||
" \"following_url\":\"https://api.github.com/users/jgrandja/following{/other_user}\",\n" +
|
||||
" \"gists_url\":\"https://api.github.com/users/jgrandja/gists{/gist_id}\",\n" +
|
||||
" \"starred_url\":\"https://api.github.com/users/jgrandja/starred{/owner}{/repo}\",\n" +
|
||||
" \"subscriptions_url\":\"https://api.github.com/users/jgrandja/subscriptions\",\n" +
|
||||
" \"organizations_url\":\"https://api.github.com/users/jgrandja/orgs\",\n" +
|
||||
" \"repos_url\":\"https://api.github.com/users/jgrandja/repos\",\n" +
|
||||
" \"events_url\":\"https://api.github.com/users/jgrandja/events{/privacy}\",\n" +
|
||||
" \"received_events_url\":\"https://api.github.com/users/jgrandja/received_events\",\n" +
|
||||
" \"type\":\"User\",\n" +
|
||||
" \"site_admin\":false\n" +
|
||||
" },\n" +
|
||||
" \"open_issues\":1,\n" +
|
||||
" \"closed_issues\":0,\n" +
|
||||
" \"state\":\"open\",\n" +
|
||||
" \"created_at\":\"2021-03-31T11:29:17Z\",\n" +
|
||||
" \"updated_at\":\"2021-03-31T11:30:47Z\",\n" +
|
||||
" \"due_on\":null,\n" +
|
||||
" \"closed_at\":null\n" +
|
||||
" },\n" +
|
||||
" {\n" +
|
||||
" \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191\",\n" +
|
||||
" \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/191\",\n" +
|
||||
" \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191/labels\",\n" +
|
||||
" \"id\":5884208,\n" +
|
||||
" \"node_id\":\"MDk6TWlsZXN0b25lNTg4NDIwOA==\",\n" +
|
||||
" \"number\":191,\n" +
|
||||
" \"title\":\"5.5.0-RC1\",\n" +
|
||||
" \"description\":\"\",\n" +
|
||||
" \"creator\":{\n" +
|
||||
" \"login\":\"jzheaux\",\n" +
|
||||
" \"id\":3627351,\n" +
|
||||
" \"node_id\":\"MDQ6VXNlcjM2MjczNTE=\",\n" +
|
||||
" \"avatar_url\":\"https://avatars.githubusercontent.com/u/3627351?v=4\",\n" +
|
||||
" \"gravatar_id\":\"\",\n" +
|
||||
" \"url\":\"https://api.github.com/users/jzheaux\",\n" +
|
||||
" \"html_url\":\"https://github.com/jzheaux\",\n" +
|
||||
" \"followers_url\":\"https://api.github.com/users/jzheaux/followers\",\n" +
|
||||
" \"following_url\":\"https://api.github.com/users/jzheaux/following{/other_user}\",\n" +
|
||||
" \"gists_url\":\"https://api.github.com/users/jzheaux/gists{/gist_id}\",\n" +
|
||||
" \"starred_url\":\"https://api.github.com/users/jzheaux/starred{/owner}{/repo}\",\n" +
|
||||
" \"subscriptions_url\":\"https://api.github.com/users/jzheaux/subscriptions\",\n" +
|
||||
" \"organizations_url\":\"https://api.github.com/users/jzheaux/orgs\",\n" +
|
||||
" \"repos_url\":\"https://api.github.com/users/jzheaux/repos\",\n" +
|
||||
" \"events_url\":\"https://api.github.com/users/jzheaux/events{/privacy}\",\n" +
|
||||
" \"received_events_url\":\"https://api.github.com/users/jzheaux/received_events\",\n" +
|
||||
" \"type\":\"User\",\n" +
|
||||
" \"site_admin\":false\n" +
|
||||
" },\n" +
|
||||
" \"open_issues\":21,\n" +
|
||||
" \"closed_issues\":23,\n" +
|
||||
" \"state\":\"open\",\n" +
|
||||
" \"created_at\":\"2020-09-16T13:28:03Z\",\n" +
|
||||
" \"updated_at\":\"2021-04-06T23:47:10Z\",\n" +
|
||||
" \"due_on\":\"2021-04-12T07:00:00Z\",\n" +
|
||||
" \"closed_at\":null\n" +
|
||||
" }\n" +
|
||||
"]";
|
||||
this.server.enqueue(new MockResponse().setBody(responseJson));
|
||||
|
||||
assertThatExceptionOfType(RuntimeException.class)
|
||||
.isThrownBy(() -> this.github.isMilestoneDueToday(this.repositoryRef, "missing"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isMilestoneDueTodayWhenPastDueThenTrue() throws Exception {
|
||||
String responseJson = "[\n" +
|
||||
" {\n" +
|
||||
" \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207\",\n" +
|
||||
" \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/207\",\n" +
|
||||
" \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207/labels\",\n" +
|
||||
" \"id\":6611880,\n" +
|
||||
" \"node_id\":\"MDk6TWlsZXN0b25lNjYxMTg4MA==\",\n" +
|
||||
" \"number\":207,\n" +
|
||||
" \"title\":\"5.6.x\",\n" +
|
||||
" \"description\":\"\",\n" +
|
||||
" \"creator\":{\n" +
|
||||
" \"login\":\"jgrandja\",\n" +
|
||||
" \"id\":10884212,\n" +
|
||||
" \"node_id\":\"MDQ6VXNlcjEwODg0MjEy\",\n" +
|
||||
" \"avatar_url\":\"https://avatars.githubusercontent.com/u/10884212?v=4\",\n" +
|
||||
" \"gravatar_id\":\"\",\n" +
|
||||
" \"url\":\"https://api.github.com/users/jgrandja\",\n" +
|
||||
" \"html_url\":\"https://github.com/jgrandja\",\n" +
|
||||
" \"followers_url\":\"https://api.github.com/users/jgrandja/followers\",\n" +
|
||||
" \"following_url\":\"https://api.github.com/users/jgrandja/following{/other_user}\",\n" +
|
||||
" \"gists_url\":\"https://api.github.com/users/jgrandja/gists{/gist_id}\",\n" +
|
||||
" \"starred_url\":\"https://api.github.com/users/jgrandja/starred{/owner}{/repo}\",\n" +
|
||||
" \"subscriptions_url\":\"https://api.github.com/users/jgrandja/subscriptions\",\n" +
|
||||
" \"organizations_url\":\"https://api.github.com/users/jgrandja/orgs\",\n" +
|
||||
" \"repos_url\":\"https://api.github.com/users/jgrandja/repos\",\n" +
|
||||
" \"events_url\":\"https://api.github.com/users/jgrandja/events{/privacy}\",\n" +
|
||||
" \"received_events_url\":\"https://api.github.com/users/jgrandja/received_events\",\n" +
|
||||
" \"type\":\"User\",\n" +
|
||||
" \"site_admin\":false\n" +
|
||||
" },\n" +
|
||||
" \"open_issues\":1,\n" +
|
||||
" \"closed_issues\":0,\n" +
|
||||
" \"state\":\"open\",\n" +
|
||||
" \"created_at\":\"2021-03-31T11:29:17Z\",\n" +
|
||||
" \"updated_at\":\"2021-03-31T11:30:47Z\",\n" +
|
||||
" \"due_on\":null,\n" +
|
||||
" \"closed_at\":null\n" +
|
||||
" },\n" +
|
||||
" {\n" +
|
||||
" \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191\",\n" +
|
||||
" \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/191\",\n" +
|
||||
" \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191/labels\",\n" +
|
||||
" \"id\":5884208,\n" +
|
||||
" \"node_id\":\"MDk6TWlsZXN0b25lNTg4NDIwOA==\",\n" +
|
||||
" \"number\":191,\n" +
|
||||
" \"title\":\"5.5.0-RC1\",\n" +
|
||||
" \"description\":\"\",\n" +
|
||||
" \"creator\":{\n" +
|
||||
" \"login\":\"jzheaux\",\n" +
|
||||
" \"id\":3627351,\n" +
|
||||
" \"node_id\":\"MDQ6VXNlcjM2MjczNTE=\",\n" +
|
||||
" \"avatar_url\":\"https://avatars.githubusercontent.com/u/3627351?v=4\",\n" +
|
||||
" \"gravatar_id\":\"\",\n" +
|
||||
" \"url\":\"https://api.github.com/users/jzheaux\",\n" +
|
||||
" \"html_url\":\"https://github.com/jzheaux\",\n" +
|
||||
" \"followers_url\":\"https://api.github.com/users/jzheaux/followers\",\n" +
|
||||
" \"following_url\":\"https://api.github.com/users/jzheaux/following{/other_user}\",\n" +
|
||||
" \"gists_url\":\"https://api.github.com/users/jzheaux/gists{/gist_id}\",\n" +
|
||||
" \"starred_url\":\"https://api.github.com/users/jzheaux/starred{/owner}{/repo}\",\n" +
|
||||
" \"subscriptions_url\":\"https://api.github.com/users/jzheaux/subscriptions\",\n" +
|
||||
" \"organizations_url\":\"https://api.github.com/users/jzheaux/orgs\",\n" +
|
||||
" \"repos_url\":\"https://api.github.com/users/jzheaux/repos\",\n" +
|
||||
" \"events_url\":\"https://api.github.com/users/jzheaux/events{/privacy}\",\n" +
|
||||
" \"received_events_url\":\"https://api.github.com/users/jzheaux/received_events\",\n" +
|
||||
" \"type\":\"User\",\n" +
|
||||
" \"site_admin\":false\n" +
|
||||
" },\n" +
|
||||
" \"open_issues\":21,\n" +
|
||||
" \"closed_issues\":23,\n" +
|
||||
" \"state\":\"open\",\n" +
|
||||
" \"created_at\":\"2020-09-16T13:28:03Z\",\n" +
|
||||
" \"updated_at\":\"2021-04-06T23:47:10Z\",\n" +
|
||||
" \"due_on\":\"2021-04-12T07:00:00Z\",\n" +
|
||||
" \"closed_at\":null\n" +
|
||||
" }\n" +
|
||||
"]";
|
||||
this.server.enqueue(new MockResponse().setBody(responseJson));
|
||||
|
||||
boolean dueToday = this.github.isMilestoneDueToday(this.repositoryRef, "5.5.0-RC1");
|
||||
|
||||
RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS);
|
||||
assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("get");
|
||||
assertThat(recordedRequest.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/milestones?per_page=100");
|
||||
|
||||
assertThat(dueToday).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isMilestoneDueTodayWhenDueTodayThenTrue() throws Exception {
|
||||
String responseJson = "[\n" +
|
||||
" {\n" +
|
||||
" \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207\",\n" +
|
||||
" \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/207\",\n" +
|
||||
" \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207/labels\",\n" +
|
||||
" \"id\":6611880,\n" +
|
||||
" \"node_id\":\"MDk6TWlsZXN0b25lNjYxMTg4MA==\",\n" +
|
||||
" \"number\":207,\n" +
|
||||
" \"title\":\"5.6.x\",\n" +
|
||||
" \"description\":\"\",\n" +
|
||||
" \"creator\":{\n" +
|
||||
" \"login\":\"jgrandja\",\n" +
|
||||
" \"id\":10884212,\n" +
|
||||
" \"node_id\":\"MDQ6VXNlcjEwODg0MjEy\",\n" +
|
||||
" \"avatar_url\":\"https://avatars.githubusercontent.com/u/10884212?v=4\",\n" +
|
||||
" \"gravatar_id\":\"\",\n" +
|
||||
" \"url\":\"https://api.github.com/users/jgrandja\",\n" +
|
||||
" \"html_url\":\"https://github.com/jgrandja\",\n" +
|
||||
" \"followers_url\":\"https://api.github.com/users/jgrandja/followers\",\n" +
|
||||
" \"following_url\":\"https://api.github.com/users/jgrandja/following{/other_user}\",\n" +
|
||||
" \"gists_url\":\"https://api.github.com/users/jgrandja/gists{/gist_id}\",\n" +
|
||||
" \"starred_url\":\"https://api.github.com/users/jgrandja/starred{/owner}{/repo}\",\n" +
|
||||
" \"subscriptions_url\":\"https://api.github.com/users/jgrandja/subscriptions\",\n" +
|
||||
" \"organizations_url\":\"https://api.github.com/users/jgrandja/orgs\",\n" +
|
||||
" \"repos_url\":\"https://api.github.com/users/jgrandja/repos\",\n" +
|
||||
" \"events_url\":\"https://api.github.com/users/jgrandja/events{/privacy}\",\n" +
|
||||
" \"received_events_url\":\"https://api.github.com/users/jgrandja/received_events\",\n" +
|
||||
" \"type\":\"User\",\n" +
|
||||
" \"site_admin\":false\n" +
|
||||
" },\n" +
|
||||
" \"open_issues\":1,\n" +
|
||||
" \"closed_issues\":0,\n" +
|
||||
" \"state\":\"open\",\n" +
|
||||
" \"created_at\":\"2021-03-31T11:29:17Z\",\n" +
|
||||
" \"updated_at\":\"2021-03-31T11:30:47Z\",\n" +
|
||||
" \"due_on\":null,\n" +
|
||||
" \"closed_at\":null\n" +
|
||||
" },\n" +
|
||||
" {\n" +
|
||||
" \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191\",\n" +
|
||||
" \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/191\",\n" +
|
||||
" \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191/labels\",\n" +
|
||||
" \"id\":5884208,\n" +
|
||||
" \"node_id\":\"MDk6TWlsZXN0b25lNTg4NDIwOA==\",\n" +
|
||||
" \"number\":191,\n" +
|
||||
" \"title\":\"5.5.0-RC1\",\n" +
|
||||
" \"description\":\"\",\n" +
|
||||
" \"creator\":{\n" +
|
||||
" \"login\":\"jzheaux\",\n" +
|
||||
" \"id\":3627351,\n" +
|
||||
" \"node_id\":\"MDQ6VXNlcjM2MjczNTE=\",\n" +
|
||||
" \"avatar_url\":\"https://avatars.githubusercontent.com/u/3627351?v=4\",\n" +
|
||||
" \"gravatar_id\":\"\",\n" +
|
||||
" \"url\":\"https://api.github.com/users/jzheaux\",\n" +
|
||||
" \"html_url\":\"https://github.com/jzheaux\",\n" +
|
||||
" \"followers_url\":\"https://api.github.com/users/jzheaux/followers\",\n" +
|
||||
" \"following_url\":\"https://api.github.com/users/jzheaux/following{/other_user}\",\n" +
|
||||
" \"gists_url\":\"https://api.github.com/users/jzheaux/gists{/gist_id}\",\n" +
|
||||
" \"starred_url\":\"https://api.github.com/users/jzheaux/starred{/owner}{/repo}\",\n" +
|
||||
" \"subscriptions_url\":\"https://api.github.com/users/jzheaux/subscriptions\",\n" +
|
||||
" \"organizations_url\":\"https://api.github.com/users/jzheaux/orgs\",\n" +
|
||||
" \"repos_url\":\"https://api.github.com/users/jzheaux/repos\",\n" +
|
||||
" \"events_url\":\"https://api.github.com/users/jzheaux/events{/privacy}\",\n" +
|
||||
" \"received_events_url\":\"https://api.github.com/users/jzheaux/received_events\",\n" +
|
||||
" \"type\":\"User\",\n" +
|
||||
" \"site_admin\":false\n" +
|
||||
" },\n" +
|
||||
" \"open_issues\":21,\n" +
|
||||
" \"closed_issues\":23,\n" +
|
||||
" \"state\":\"open\",\n" +
|
||||
" \"created_at\":\"2020-09-16T13:28:03Z\",\n" +
|
||||
" \"updated_at\":\"2021-04-06T23:47:10Z\",\n" +
|
||||
" \"due_on\":\"" + LocalDate.now().atStartOfDay(ZoneId.systemDefault()).toInstant().toString() + "\",\n" +
|
||||
" \"closed_at\":null\n" +
|
||||
" }\n" +
|
||||
"]";
|
||||
this.server.enqueue(new MockResponse().setBody(responseJson));
|
||||
|
||||
boolean dueToday = this.github.isMilestoneDueToday(this.repositoryRef, "5.5.0-RC1");
|
||||
|
||||
RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS);
|
||||
assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("get");
|
||||
assertThat(recordedRequest.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/milestones?per_page=100");
|
||||
|
||||
assertThat(dueToday).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isMilestoneDueTodayWhenNoDueDateThenFalse() throws Exception {
|
||||
String responseJson = "[\n" +
|
||||
" {\n" +
|
||||
" \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207\",\n" +
|
||||
" \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/207\",\n" +
|
||||
" \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207/labels\",\n" +
|
||||
" \"id\":6611880,\n" +
|
||||
" \"node_id\":\"MDk6TWlsZXN0b25lNjYxMTg4MA==\",\n" +
|
||||
" \"number\":207,\n" +
|
||||
" \"title\":\"5.6.x\",\n" +
|
||||
" \"description\":\"\",\n" +
|
||||
" \"creator\":{\n" +
|
||||
" \"login\":\"jgrandja\",\n" +
|
||||
" \"id\":10884212,\n" +
|
||||
" \"node_id\":\"MDQ6VXNlcjEwODg0MjEy\",\n" +
|
||||
" \"avatar_url\":\"https://avatars.githubusercontent.com/u/10884212?v=4\",\n" +
|
||||
" \"gravatar_id\":\"\",\n" +
|
||||
" \"url\":\"https://api.github.com/users/jgrandja\",\n" +
|
||||
" \"html_url\":\"https://github.com/jgrandja\",\n" +
|
||||
" \"followers_url\":\"https://api.github.com/users/jgrandja/followers\",\n" +
|
||||
" \"following_url\":\"https://api.github.com/users/jgrandja/following{/other_user}\",\n" +
|
||||
" \"gists_url\":\"https://api.github.com/users/jgrandja/gists{/gist_id}\",\n" +
|
||||
" \"starred_url\":\"https://api.github.com/users/jgrandja/starred{/owner}{/repo}\",\n" +
|
||||
" \"subscriptions_url\":\"https://api.github.com/users/jgrandja/subscriptions\",\n" +
|
||||
" \"organizations_url\":\"https://api.github.com/users/jgrandja/orgs\",\n" +
|
||||
" \"repos_url\":\"https://api.github.com/users/jgrandja/repos\",\n" +
|
||||
" \"events_url\":\"https://api.github.com/users/jgrandja/events{/privacy}\",\n" +
|
||||
" \"received_events_url\":\"https://api.github.com/users/jgrandja/received_events\",\n" +
|
||||
" \"type\":\"User\",\n" +
|
||||
" \"site_admin\":false\n" +
|
||||
" },\n" +
|
||||
" \"open_issues\":1,\n" +
|
||||
" \"closed_issues\":0,\n" +
|
||||
" \"state\":\"open\",\n" +
|
||||
" \"created_at\":\"2021-03-31T11:29:17Z\",\n" +
|
||||
" \"updated_at\":\"2021-03-31T11:30:47Z\",\n" +
|
||||
" \"due_on\":null,\n" +
|
||||
" \"closed_at\":null\n" +
|
||||
" },\n" +
|
||||
" {\n" +
|
||||
" \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191\",\n" +
|
||||
" \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/191\",\n" +
|
||||
" \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191/labels\",\n" +
|
||||
" \"id\":5884208,\n" +
|
||||
" \"node_id\":\"MDk6TWlsZXN0b25lNTg4NDIwOA==\",\n" +
|
||||
" \"number\":191,\n" +
|
||||
" \"title\":\"5.5.0-RC1\",\n" +
|
||||
" \"description\":\"\",\n" +
|
||||
" \"creator\":{\n" +
|
||||
" \"login\":\"jzheaux\",\n" +
|
||||
" \"id\":3627351,\n" +
|
||||
" \"node_id\":\"MDQ6VXNlcjM2MjczNTE=\",\n" +
|
||||
" \"avatar_url\":\"https://avatars.githubusercontent.com/u/3627351?v=4\",\n" +
|
||||
" \"gravatar_id\":\"\",\n" +
|
||||
" \"url\":\"https://api.github.com/users/jzheaux\",\n" +
|
||||
" \"html_url\":\"https://github.com/jzheaux\",\n" +
|
||||
" \"followers_url\":\"https://api.github.com/users/jzheaux/followers\",\n" +
|
||||
" \"following_url\":\"https://api.github.com/users/jzheaux/following{/other_user}\",\n" +
|
||||
" \"gists_url\":\"https://api.github.com/users/jzheaux/gists{/gist_id}\",\n" +
|
||||
" \"starred_url\":\"https://api.github.com/users/jzheaux/starred{/owner}{/repo}\",\n" +
|
||||
" \"subscriptions_url\":\"https://api.github.com/users/jzheaux/subscriptions\",\n" +
|
||||
" \"organizations_url\":\"https://api.github.com/users/jzheaux/orgs\",\n" +
|
||||
" \"repos_url\":\"https://api.github.com/users/jzheaux/repos\",\n" +
|
||||
" \"events_url\":\"https://api.github.com/users/jzheaux/events{/privacy}\",\n" +
|
||||
" \"received_events_url\":\"https://api.github.com/users/jzheaux/received_events\",\n" +
|
||||
" \"type\":\"User\",\n" +
|
||||
" \"site_admin\":false\n" +
|
||||
" },\n" +
|
||||
" \"open_issues\":21,\n" +
|
||||
" \"closed_issues\":23,\n" +
|
||||
" \"state\":\"open\",\n" +
|
||||
" \"created_at\":\"2020-09-16T13:28:03Z\",\n" +
|
||||
" \"updated_at\":\"2021-04-06T23:47:10Z\",\n" +
|
||||
" \"due_on\":null,\n" +
|
||||
" \"closed_at\":null\n" +
|
||||
" }\n" +
|
||||
"]";
|
||||
this.server.enqueue(new MockResponse().setBody(responseJson));
|
||||
|
||||
boolean dueToday = this.github.isMilestoneDueToday(this.repositoryRef, "5.5.0-RC1");
|
||||
|
||||
RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS);
|
||||
assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("get");
|
||||
assertThat(recordedRequest.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/milestones?per_page=100");
|
||||
|
||||
assertThat(dueToday).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isMilestoneDueTodayWhenDueDateInFutureThenFalse() throws Exception {
|
||||
String responseJson = "[\n" +
|
||||
" {\n" +
|
||||
" \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207\",\n" +
|
||||
" \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/207\",\n" +
|
||||
" \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207/labels\",\n" +
|
||||
" \"id\":6611880,\n" +
|
||||
" \"node_id\":\"MDk6TWlsZXN0b25lNjYxMTg4MA==\",\n" +
|
||||
" \"number\":207,\n" +
|
||||
" \"title\":\"5.6.x\",\n" +
|
||||
" \"description\":\"\",\n" +
|
||||
" \"creator\":{\n" +
|
||||
" \"login\":\"jgrandja\",\n" +
|
||||
" \"id\":10884212,\n" +
|
||||
" \"node_id\":\"MDQ6VXNlcjEwODg0MjEy\",\n" +
|
||||
" \"avatar_url\":\"https://avatars.githubusercontent.com/u/10884212?v=4\",\n" +
|
||||
" \"gravatar_id\":\"\",\n" +
|
||||
" \"url\":\"https://api.github.com/users/jgrandja\",\n" +
|
||||
" \"html_url\":\"https://github.com/jgrandja\",\n" +
|
||||
" \"followers_url\":\"https://api.github.com/users/jgrandja/followers\",\n" +
|
||||
" \"following_url\":\"https://api.github.com/users/jgrandja/following{/other_user}\",\n" +
|
||||
" \"gists_url\":\"https://api.github.com/users/jgrandja/gists{/gist_id}\",\n" +
|
||||
" \"starred_url\":\"https://api.github.com/users/jgrandja/starred{/owner}{/repo}\",\n" +
|
||||
" \"subscriptions_url\":\"https://api.github.com/users/jgrandja/subscriptions\",\n" +
|
||||
" \"organizations_url\":\"https://api.github.com/users/jgrandja/orgs\",\n" +
|
||||
" \"repos_url\":\"https://api.github.com/users/jgrandja/repos\",\n" +
|
||||
" \"events_url\":\"https://api.github.com/users/jgrandja/events{/privacy}\",\n" +
|
||||
" \"received_events_url\":\"https://api.github.com/users/jgrandja/received_events\",\n" +
|
||||
" \"type\":\"User\",\n" +
|
||||
" \"site_admin\":false\n" +
|
||||
" },\n" +
|
||||
" \"open_issues\":1,\n" +
|
||||
" \"closed_issues\":0,\n" +
|
||||
" \"state\":\"open\",\n" +
|
||||
" \"created_at\":\"2021-03-31T11:29:17Z\",\n" +
|
||||
" \"updated_at\":\"2021-03-31T11:30:47Z\",\n" +
|
||||
" \"due_on\":null,\n" +
|
||||
" \"closed_at\":null\n" +
|
||||
" },\n" +
|
||||
" {\n" +
|
||||
" \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191\",\n" +
|
||||
" \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/191\",\n" +
|
||||
" \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191/labels\",\n" +
|
||||
" \"id\":5884208,\n" +
|
||||
" \"node_id\":\"MDk6TWlsZXN0b25lNTg4NDIwOA==\",\n" +
|
||||
" \"number\":191,\n" +
|
||||
" \"title\":\"5.5.0-RC1\",\n" +
|
||||
" \"description\":\"\",\n" +
|
||||
" \"creator\":{\n" +
|
||||
" \"login\":\"jzheaux\",\n" +
|
||||
" \"id\":3627351,\n" +
|
||||
" \"node_id\":\"MDQ6VXNlcjM2MjczNTE=\",\n" +
|
||||
" \"avatar_url\":\"https://avatars.githubusercontent.com/u/3627351?v=4\",\n" +
|
||||
" \"gravatar_id\":\"\",\n" +
|
||||
" \"url\":\"https://api.github.com/users/jzheaux\",\n" +
|
||||
" \"html_url\":\"https://github.com/jzheaux\",\n" +
|
||||
" \"followers_url\":\"https://api.github.com/users/jzheaux/followers\",\n" +
|
||||
" \"following_url\":\"https://api.github.com/users/jzheaux/following{/other_user}\",\n" +
|
||||
" \"gists_url\":\"https://api.github.com/users/jzheaux/gists{/gist_id}\",\n" +
|
||||
" \"starred_url\":\"https://api.github.com/users/jzheaux/starred{/owner}{/repo}\",\n" +
|
||||
" \"subscriptions_url\":\"https://api.github.com/users/jzheaux/subscriptions\",\n" +
|
||||
" \"organizations_url\":\"https://api.github.com/users/jzheaux/orgs\",\n" +
|
||||
" \"repos_url\":\"https://api.github.com/users/jzheaux/repos\",\n" +
|
||||
" \"events_url\":\"https://api.github.com/users/jzheaux/events{/privacy}\",\n" +
|
||||
" \"received_events_url\":\"https://api.github.com/users/jzheaux/received_events\",\n" +
|
||||
" \"type\":\"User\",\n" +
|
||||
" \"site_admin\":false\n" +
|
||||
" },\n" +
|
||||
" \"open_issues\":21,\n" +
|
||||
" \"closed_issues\":23,\n" +
|
||||
" \"state\":\"open\",\n" +
|
||||
" \"created_at\":\"2020-09-16T13:28:03Z\",\n" +
|
||||
" \"updated_at\":\"2021-04-06T23:47:10Z\",\n" +
|
||||
" \"due_on\":\"3000-04-12T07:00:00Z\",\n" +
|
||||
" \"closed_at\":null\n" +
|
||||
" }\n" +
|
||||
"]";
|
||||
this.server.enqueue(new MockResponse().setBody(responseJson));
|
||||
|
||||
boolean dueToday = this.github.isMilestoneDueToday(this.repositoryRef, "5.5.0-RC1");
|
||||
|
||||
RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS);
|
||||
assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("get");
|
||||
assertThat(recordedRequest.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/milestones?per_page=100");
|
||||
|
||||
assertThat(dueToday).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void calculateNextReleaseMilestoneWhenCurrentVersionIsNotSnapshotThenException() {
|
||||
assertThatExceptionOfType(RuntimeException.class)
|
||||
.isThrownBy(() -> this.github.getNextReleaseMilestone(this.repositoryRef, "5.5.0-RC1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void calculateNextReleaseMilestoneWhenPatchSegmentGreaterThan0ThenReturnsVersionWithoutSnapshot() {
|
||||
String nextVersion = this.github.getNextReleaseMilestone(this.repositoryRef, "5.5.1-SNAPSHOT");
|
||||
|
||||
assertThat(nextVersion).isEqualTo("5.5.1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void calculateNextReleaseMilestoneWhenMilestoneAndRcExistThenReturnsMilestone() throws Exception {
|
||||
String responseJson = "[\n" +
|
||||
" {\n" +
|
||||
" \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207\",\n" +
|
||||
" \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/207\",\n" +
|
||||
" \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207/labels\",\n" +
|
||||
" \"id\":6611880,\n" +
|
||||
" \"node_id\":\"MDk6TWlsZXN0b25lNjYxMTg4MA==\",\n" +
|
||||
" \"number\":207,\n" +
|
||||
" \"title\":\"5.5.0-M1\",\n" +
|
||||
" \"description\":\"\",\n" +
|
||||
" \"creator\":{\n" +
|
||||
" \"login\":\"jgrandja\",\n" +
|
||||
" \"id\":10884212,\n" +
|
||||
" \"node_id\":\"MDQ6VXNlcjEwODg0MjEy\",\n" +
|
||||
" \"avatar_url\":\"https://avatars.githubusercontent.com/u/10884212?v=4\",\n" +
|
||||
" \"gravatar_id\":\"\",\n" +
|
||||
" \"url\":\"https://api.github.com/users/jgrandja\",\n" +
|
||||
" \"html_url\":\"https://github.com/jgrandja\",\n" +
|
||||
" \"followers_url\":\"https://api.github.com/users/jgrandja/followers\",\n" +
|
||||
" \"following_url\":\"https://api.github.com/users/jgrandja/following{/other_user}\",\n" +
|
||||
" \"gists_url\":\"https://api.github.com/users/jgrandja/gists{/gist_id}\",\n" +
|
||||
" \"starred_url\":\"https://api.github.com/users/jgrandja/starred{/owner}{/repo}\",\n" +
|
||||
" \"subscriptions_url\":\"https://api.github.com/users/jgrandja/subscriptions\",\n" +
|
||||
" \"organizations_url\":\"https://api.github.com/users/jgrandja/orgs\",\n" +
|
||||
" \"repos_url\":\"https://api.github.com/users/jgrandja/repos\",\n" +
|
||||
" \"events_url\":\"https://api.github.com/users/jgrandja/events{/privacy}\",\n" +
|
||||
" \"received_events_url\":\"https://api.github.com/users/jgrandja/received_events\",\n" +
|
||||
" \"type\":\"User\",\n" +
|
||||
" \"site_admin\":false\n" +
|
||||
" },\n" +
|
||||
" \"open_issues\":1,\n" +
|
||||
" \"closed_issues\":0,\n" +
|
||||
" \"state\":\"open\",\n" +
|
||||
" \"created_at\":\"2021-03-31T11:29:17Z\",\n" +
|
||||
" \"updated_at\":\"2021-03-31T11:30:47Z\",\n" +
|
||||
" \"due_on\":null,\n" +
|
||||
" \"closed_at\":null\n" +
|
||||
" },\n" +
|
||||
" {\n" +
|
||||
" \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191\",\n" +
|
||||
" \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/191\",\n" +
|
||||
" \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191/labels\",\n" +
|
||||
" \"id\":5884208,\n" +
|
||||
" \"node_id\":\"MDk6TWlsZXN0b25lNTg4NDIwOA==\",\n" +
|
||||
" \"number\":191,\n" +
|
||||
" \"title\":\"5.5.0-RC1\",\n" +
|
||||
" \"description\":\"\",\n" +
|
||||
" \"creator\":{\n" +
|
||||
" \"login\":\"jzheaux\",\n" +
|
||||
" \"id\":3627351,\n" +
|
||||
" \"node_id\":\"MDQ6VXNlcjM2MjczNTE=\",\n" +
|
||||
" \"avatar_url\":\"https://avatars.githubusercontent.com/u/3627351?v=4\",\n" +
|
||||
" \"gravatar_id\":\"\",\n" +
|
||||
" \"url\":\"https://api.github.com/users/jzheaux\",\n" +
|
||||
" \"html_url\":\"https://github.com/jzheaux\",\n" +
|
||||
" \"followers_url\":\"https://api.github.com/users/jzheaux/followers\",\n" +
|
||||
" \"following_url\":\"https://api.github.com/users/jzheaux/following{/other_user}\",\n" +
|
||||
" \"gists_url\":\"https://api.github.com/users/jzheaux/gists{/gist_id}\",\n" +
|
||||
" \"starred_url\":\"https://api.github.com/users/jzheaux/starred{/owner}{/repo}\",\n" +
|
||||
" \"subscriptions_url\":\"https://api.github.com/users/jzheaux/subscriptions\",\n" +
|
||||
" \"organizations_url\":\"https://api.github.com/users/jzheaux/orgs\",\n" +
|
||||
" \"repos_url\":\"https://api.github.com/users/jzheaux/repos\",\n" +
|
||||
" \"events_url\":\"https://api.github.com/users/jzheaux/events{/privacy}\",\n" +
|
||||
" \"received_events_url\":\"https://api.github.com/users/jzheaux/received_events\",\n" +
|
||||
" \"type\":\"User\",\n" +
|
||||
" \"site_admin\":false\n" +
|
||||
" },\n" +
|
||||
" \"open_issues\":21,\n" +
|
||||
" \"closed_issues\":23,\n" +
|
||||
" \"state\":\"open\",\n" +
|
||||
" \"created_at\":\"2020-09-16T13:28:03Z\",\n" +
|
||||
" \"updated_at\":\"2021-04-06T23:47:10Z\",\n" +
|
||||
" \"due_on\":\"3000-04-12T07:00:00Z\",\n" +
|
||||
" \"closed_at\":null\n" +
|
||||
" }\n" +
|
||||
"]";
|
||||
this.server.enqueue(new MockResponse().setBody(responseJson));
|
||||
|
||||
String nextVersion = this.github.getNextReleaseMilestone(this.repositoryRef, "5.5.0-SNAPSHOT");
|
||||
|
||||
RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS);
|
||||
assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("get");
|
||||
assertThat(recordedRequest.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/milestones?per_page=100");
|
||||
|
||||
assertThat(nextVersion).isEqualTo("5.5.0-M1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void calculateNextReleaseMilestoneWhenTwoMilestonesExistThenReturnsSmallerMilestone() throws Exception {
|
||||
String responseJson = "[\n" +
|
||||
" {\n" +
|
||||
" \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207\",\n" +
|
||||
" \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/207\",\n" +
|
||||
" \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207/labels\",\n" +
|
||||
" \"id\":6611880,\n" +
|
||||
" \"node_id\":\"MDk6TWlsZXN0b25lNjYxMTg4MA==\",\n" +
|
||||
" \"number\":207,\n" +
|
||||
" \"title\":\"5.5.0-M9\",\n" +
|
||||
" \"description\":\"\",\n" +
|
||||
" \"creator\":{\n" +
|
||||
" \"login\":\"jgrandja\",\n" +
|
||||
" \"id\":10884212,\n" +
|
||||
" \"node_id\":\"MDQ6VXNlcjEwODg0MjEy\",\n" +
|
||||
" \"avatar_url\":\"https://avatars.githubusercontent.com/u/10884212?v=4\",\n" +
|
||||
" \"gravatar_id\":\"\",\n" +
|
||||
" \"url\":\"https://api.github.com/users/jgrandja\",\n" +
|
||||
" \"html_url\":\"https://github.com/jgrandja\",\n" +
|
||||
" \"followers_url\":\"https://api.github.com/users/jgrandja/followers\",\n" +
|
||||
" \"following_url\":\"https://api.github.com/users/jgrandja/following{/other_user}\",\n" +
|
||||
" \"gists_url\":\"https://api.github.com/users/jgrandja/gists{/gist_id}\",\n" +
|
||||
" \"starred_url\":\"https://api.github.com/users/jgrandja/starred{/owner}{/repo}\",\n" +
|
||||
" \"subscriptions_url\":\"https://api.github.com/users/jgrandja/subscriptions\",\n" +
|
||||
" \"organizations_url\":\"https://api.github.com/users/jgrandja/orgs\",\n" +
|
||||
" \"repos_url\":\"https://api.github.com/users/jgrandja/repos\",\n" +
|
||||
" \"events_url\":\"https://api.github.com/users/jgrandja/events{/privacy}\",\n" +
|
||||
" \"received_events_url\":\"https://api.github.com/users/jgrandja/received_events\",\n" +
|
||||
" \"type\":\"User\",\n" +
|
||||
" \"site_admin\":false\n" +
|
||||
" },\n" +
|
||||
" \"open_issues\":1,\n" +
|
||||
" \"closed_issues\":0,\n" +
|
||||
" \"state\":\"open\",\n" +
|
||||
" \"created_at\":\"2021-03-31T11:29:17Z\",\n" +
|
||||
" \"updated_at\":\"2021-03-31T11:30:47Z\",\n" +
|
||||
" \"due_on\":null,\n" +
|
||||
" \"closed_at\":null\n" +
|
||||
" },\n" +
|
||||
" {\n" +
|
||||
" \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191\",\n" +
|
||||
" \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/191\",\n" +
|
||||
" \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191/labels\",\n" +
|
||||
" \"id\":5884208,\n" +
|
||||
" \"node_id\":\"MDk6TWlsZXN0b25lNTg4NDIwOA==\",\n" +
|
||||
" \"number\":191,\n" +
|
||||
" \"title\":\"5.5.0-M10\",\n" +
|
||||
" \"description\":\"\",\n" +
|
||||
" \"creator\":{\n" +
|
||||
" \"login\":\"jzheaux\",\n" +
|
||||
" \"id\":3627351,\n" +
|
||||
" \"node_id\":\"MDQ6VXNlcjM2MjczNTE=\",\n" +
|
||||
" \"avatar_url\":\"https://avatars.githubusercontent.com/u/3627351?v=4\",\n" +
|
||||
" \"gravatar_id\":\"\",\n" +
|
||||
" \"url\":\"https://api.github.com/users/jzheaux\",\n" +
|
||||
" \"html_url\":\"https://github.com/jzheaux\",\n" +
|
||||
" \"followers_url\":\"https://api.github.com/users/jzheaux/followers\",\n" +
|
||||
" \"following_url\":\"https://api.github.com/users/jzheaux/following{/other_user}\",\n" +
|
||||
" \"gists_url\":\"https://api.github.com/users/jzheaux/gists{/gist_id}\",\n" +
|
||||
" \"starred_url\":\"https://api.github.com/users/jzheaux/starred{/owner}{/repo}\",\n" +
|
||||
" \"subscriptions_url\":\"https://api.github.com/users/jzheaux/subscriptions\",\n" +
|
||||
" \"organizations_url\":\"https://api.github.com/users/jzheaux/orgs\",\n" +
|
||||
" \"repos_url\":\"https://api.github.com/users/jzheaux/repos\",\n" +
|
||||
" \"events_url\":\"https://api.github.com/users/jzheaux/events{/privacy}\",\n" +
|
||||
" \"received_events_url\":\"https://api.github.com/users/jzheaux/received_events\",\n" +
|
||||
" \"type\":\"User\",\n" +
|
||||
" \"site_admin\":false\n" +
|
||||
" },\n" +
|
||||
" \"open_issues\":21,\n" +
|
||||
" \"closed_issues\":23,\n" +
|
||||
" \"state\":\"open\",\n" +
|
||||
" \"created_at\":\"2020-09-16T13:28:03Z\",\n" +
|
||||
" \"updated_at\":\"2021-04-06T23:47:10Z\",\n" +
|
||||
" \"due_on\":\"3000-04-12T07:00:00Z\",\n" +
|
||||
" \"closed_at\":null\n" +
|
||||
" }\n" +
|
||||
"]";
|
||||
this.server.enqueue(new MockResponse().setBody(responseJson));
|
||||
|
||||
String nextVersion = this.github.getNextReleaseMilestone(this.repositoryRef, "5.5.0-SNAPSHOT");
|
||||
|
||||
RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS);
|
||||
assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("get");
|
||||
assertThat(recordedRequest.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/milestones?per_page=100");
|
||||
|
||||
assertThat(nextVersion).isEqualTo("5.5.0-M9");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void calculateNextReleaseMilestoneWhenTwoRcsExistThenReturnsSmallerRc() throws Exception {
|
||||
String responseJson = "[\n" +
|
||||
" {\n" +
|
||||
" \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207\",\n" +
|
||||
" \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/207\",\n" +
|
||||
" \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207/labels\",\n" +
|
||||
" \"id\":6611880,\n" +
|
||||
" \"node_id\":\"MDk6TWlsZXN0b25lNjYxMTg4MA==\",\n" +
|
||||
" \"number\":207,\n" +
|
||||
" \"title\":\"5.5.0-RC9\",\n" +
|
||||
" \"description\":\"\",\n" +
|
||||
" \"creator\":{\n" +
|
||||
" \"login\":\"jgrandja\",\n" +
|
||||
" \"id\":10884212,\n" +
|
||||
" \"node_id\":\"MDQ6VXNlcjEwODg0MjEy\",\n" +
|
||||
" \"avatar_url\":\"https://avatars.githubusercontent.com/u/10884212?v=4\",\n" +
|
||||
" \"gravatar_id\":\"\",\n" +
|
||||
" \"url\":\"https://api.github.com/users/jgrandja\",\n" +
|
||||
" \"html_url\":\"https://github.com/jgrandja\",\n" +
|
||||
" \"followers_url\":\"https://api.github.com/users/jgrandja/followers\",\n" +
|
||||
" \"following_url\":\"https://api.github.com/users/jgrandja/following{/other_user}\",\n" +
|
||||
" \"gists_url\":\"https://api.github.com/users/jgrandja/gists{/gist_id}\",\n" +
|
||||
" \"starred_url\":\"https://api.github.com/users/jgrandja/starred{/owner}{/repo}\",\n" +
|
||||
" \"subscriptions_url\":\"https://api.github.com/users/jgrandja/subscriptions\",\n" +
|
||||
" \"organizations_url\":\"https://api.github.com/users/jgrandja/orgs\",\n" +
|
||||
" \"repos_url\":\"https://api.github.com/users/jgrandja/repos\",\n" +
|
||||
" \"events_url\":\"https://api.github.com/users/jgrandja/events{/privacy}\",\n" +
|
||||
" \"received_events_url\":\"https://api.github.com/users/jgrandja/received_events\",\n" +
|
||||
" \"type\":\"User\",\n" +
|
||||
" \"site_admin\":false\n" +
|
||||
" },\n" +
|
||||
" \"open_issues\":1,\n" +
|
||||
" \"closed_issues\":0,\n" +
|
||||
" \"state\":\"open\",\n" +
|
||||
" \"created_at\":\"2021-03-31T11:29:17Z\",\n" +
|
||||
" \"updated_at\":\"2021-03-31T11:30:47Z\",\n" +
|
||||
" \"due_on\":null,\n" +
|
||||
" \"closed_at\":null\n" +
|
||||
" },\n" +
|
||||
" {\n" +
|
||||
" \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191\",\n" +
|
||||
" \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/191\",\n" +
|
||||
" \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191/labels\",\n" +
|
||||
" \"id\":5884208,\n" +
|
||||
" \"node_id\":\"MDk6TWlsZXN0b25lNTg4NDIwOA==\",\n" +
|
||||
" \"number\":191,\n" +
|
||||
" \"title\":\"5.5.0-RC10\",\n" +
|
||||
" \"description\":\"\",\n" +
|
||||
" \"creator\":{\n" +
|
||||
" \"login\":\"jzheaux\",\n" +
|
||||
" \"id\":3627351,\n" +
|
||||
" \"node_id\":\"MDQ6VXNlcjM2MjczNTE=\",\n" +
|
||||
" \"avatar_url\":\"https://avatars.githubusercontent.com/u/3627351?v=4\",\n" +
|
||||
" \"gravatar_id\":\"\",\n" +
|
||||
" \"url\":\"https://api.github.com/users/jzheaux\",\n" +
|
||||
" \"html_url\":\"https://github.com/jzheaux\",\n" +
|
||||
" \"followers_url\":\"https://api.github.com/users/jzheaux/followers\",\n" +
|
||||
" \"following_url\":\"https://api.github.com/users/jzheaux/following{/other_user}\",\n" +
|
||||
" \"gists_url\":\"https://api.github.com/users/jzheaux/gists{/gist_id}\",\n" +
|
||||
" \"starred_url\":\"https://api.github.com/users/jzheaux/starred{/owner}{/repo}\",\n" +
|
||||
" \"subscriptions_url\":\"https://api.github.com/users/jzheaux/subscriptions\",\n" +
|
||||
" \"organizations_url\":\"https://api.github.com/users/jzheaux/orgs\",\n" +
|
||||
" \"repos_url\":\"https://api.github.com/users/jzheaux/repos\",\n" +
|
||||
" \"events_url\":\"https://api.github.com/users/jzheaux/events{/privacy}\",\n" +
|
||||
" \"received_events_url\":\"https://api.github.com/users/jzheaux/received_events\",\n" +
|
||||
" \"type\":\"User\",\n" +
|
||||
" \"site_admin\":false\n" +
|
||||
" },\n" +
|
||||
" \"open_issues\":21,\n" +
|
||||
" \"closed_issues\":23,\n" +
|
||||
" \"state\":\"open\",\n" +
|
||||
" \"created_at\":\"2020-09-16T13:28:03Z\",\n" +
|
||||
" \"updated_at\":\"2021-04-06T23:47:10Z\",\n" +
|
||||
" \"due_on\":\"3000-04-12T07:00:00Z\",\n" +
|
||||
" \"closed_at\":null\n" +
|
||||
" }\n" +
|
||||
"]";
|
||||
this.server.enqueue(new MockResponse().setBody(responseJson));
|
||||
|
||||
String nextVersion = this.github.getNextReleaseMilestone(this.repositoryRef, "5.5.0-SNAPSHOT");
|
||||
|
||||
RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS);
|
||||
assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("get");
|
||||
assertThat(recordedRequest.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/milestones?per_page=100");
|
||||
|
||||
assertThat(nextVersion).isEqualTo("5.5.0-RC9");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void calculateNextReleaseMilestoneWhenNoPreReleaseThenReturnsVersionWithoutSnapshot() throws Exception {
|
||||
String responseJson = "[\n" +
|
||||
" {\n" +
|
||||
" \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207\",\n" +
|
||||
" \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/207\",\n" +
|
||||
" \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/207/labels\",\n" +
|
||||
" \"id\":6611880,\n" +
|
||||
" \"node_id\":\"MDk6TWlsZXN0b25lNjYxMTg4MA==\",\n" +
|
||||
" \"number\":207,\n" +
|
||||
" \"title\":\"5.6.x\",\n" +
|
||||
" \"description\":\"\",\n" +
|
||||
" \"creator\":{\n" +
|
||||
" \"login\":\"jgrandja\",\n" +
|
||||
" \"id\":10884212,\n" +
|
||||
" \"node_id\":\"MDQ6VXNlcjEwODg0MjEy\",\n" +
|
||||
" \"avatar_url\":\"https://avatars.githubusercontent.com/u/10884212?v=4\",\n" +
|
||||
" \"gravatar_id\":\"\",\n" +
|
||||
" \"url\":\"https://api.github.com/users/jgrandja\",\n" +
|
||||
" \"html_url\":\"https://github.com/jgrandja\",\n" +
|
||||
" \"followers_url\":\"https://api.github.com/users/jgrandja/followers\",\n" +
|
||||
" \"following_url\":\"https://api.github.com/users/jgrandja/following{/other_user}\",\n" +
|
||||
" \"gists_url\":\"https://api.github.com/users/jgrandja/gists{/gist_id}\",\n" +
|
||||
" \"starred_url\":\"https://api.github.com/users/jgrandja/starred{/owner}{/repo}\",\n" +
|
||||
" \"subscriptions_url\":\"https://api.github.com/users/jgrandja/subscriptions\",\n" +
|
||||
" \"organizations_url\":\"https://api.github.com/users/jgrandja/orgs\",\n" +
|
||||
" \"repos_url\":\"https://api.github.com/users/jgrandja/repos\",\n" +
|
||||
" \"events_url\":\"https://api.github.com/users/jgrandja/events{/privacy}\",\n" +
|
||||
" \"received_events_url\":\"https://api.github.com/users/jgrandja/received_events\",\n" +
|
||||
" \"type\":\"User\",\n" +
|
||||
" \"site_admin\":false\n" +
|
||||
" },\n" +
|
||||
" \"open_issues\":1,\n" +
|
||||
" \"closed_issues\":0,\n" +
|
||||
" \"state\":\"open\",\n" +
|
||||
" \"created_at\":\"2021-03-31T11:29:17Z\",\n" +
|
||||
" \"updated_at\":\"2021-03-31T11:30:47Z\",\n" +
|
||||
" \"due_on\":null,\n" +
|
||||
" \"closed_at\":null\n" +
|
||||
" },\n" +
|
||||
" {\n" +
|
||||
" \"url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191\",\n" +
|
||||
" \"html_url\":\"https://github.com/spring-projects/spring-security/milestone/191\",\n" +
|
||||
" \"labels_url\":\"https://api.github.com/repos/spring-projects/spring-security/milestones/191/labels\",\n" +
|
||||
" \"id\":5884208,\n" +
|
||||
" \"node_id\":\"MDk6TWlsZXN0b25lNTg4NDIwOA==\",\n" +
|
||||
" \"number\":191,\n" +
|
||||
" \"title\":\"5.4.3\",\n" +
|
||||
" \"description\":\"\",\n" +
|
||||
" \"creator\":{\n" +
|
||||
" \"login\":\"jzheaux\",\n" +
|
||||
" \"id\":3627351,\n" +
|
||||
" \"node_id\":\"MDQ6VXNlcjM2MjczNTE=\",\n" +
|
||||
" \"avatar_url\":\"https://avatars.githubusercontent.com/u/3627351?v=4\",\n" +
|
||||
" \"gravatar_id\":\"\",\n" +
|
||||
" \"url\":\"https://api.github.com/users/jzheaux\",\n" +
|
||||
" \"html_url\":\"https://github.com/jzheaux\",\n" +
|
||||
" \"followers_url\":\"https://api.github.com/users/jzheaux/followers\",\n" +
|
||||
" \"following_url\":\"https://api.github.com/users/jzheaux/following{/other_user}\",\n" +
|
||||
" \"gists_url\":\"https://api.github.com/users/jzheaux/gists{/gist_id}\",\n" +
|
||||
" \"starred_url\":\"https://api.github.com/users/jzheaux/starred{/owner}{/repo}\",\n" +
|
||||
" \"subscriptions_url\":\"https://api.github.com/users/jzheaux/subscriptions\",\n" +
|
||||
" \"organizations_url\":\"https://api.github.com/users/jzheaux/orgs\",\n" +
|
||||
" \"repos_url\":\"https://api.github.com/users/jzheaux/repos\",\n" +
|
||||
" \"events_url\":\"https://api.github.com/users/jzheaux/events{/privacy}\",\n" +
|
||||
" \"received_events_url\":\"https://api.github.com/users/jzheaux/received_events\",\n" +
|
||||
" \"type\":\"User\",\n" +
|
||||
" \"site_admin\":false\n" +
|
||||
" },\n" +
|
||||
" \"open_issues\":21,\n" +
|
||||
" \"closed_issues\":23,\n" +
|
||||
" \"state\":\"open\",\n" +
|
||||
" \"created_at\":\"2020-09-16T13:28:03Z\",\n" +
|
||||
" \"updated_at\":\"2021-04-06T23:47:10Z\",\n" +
|
||||
" \"due_on\":\"2021-04-12T07:00:00Z\",\n" +
|
||||
" \"closed_at\":null\n" +
|
||||
" }\n" +
|
||||
"]";
|
||||
this.server.enqueue(new MockResponse().setBody(responseJson));
|
||||
|
||||
String nextVersion = this.github.getNextReleaseMilestone(this.repositoryRef, "5.5.0-SNAPSHOT");
|
||||
|
||||
RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS);
|
||||
assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("get");
|
||||
assertThat(recordedRequest.getRequestUrl().toString()).isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/milestones?per_page=100");
|
||||
|
||||
assertThat(nextVersion).isEqualTo("5.5.0");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createMilestoneWhenValidParametersThenSuccess() throws Exception {
|
||||
this.server.enqueue(new MockResponse().setResponseCode(204));
|
||||
Milestone milestone = new Milestone();
|
||||
milestone.setTitle("1.0.0");
|
||||
milestone.setDueOn(LocalDate.of(2022, 5, 4).atTime(LocalTime.NOON));
|
||||
this.github.createMilestone(this.repositoryRef, milestone);
|
||||
|
||||
RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS);
|
||||
assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("post");
|
||||
assertThat(recordedRequest.getRequestUrl().toString())
|
||||
.isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/milestones");
|
||||
assertThat(recordedRequest.getBody().readString(Charset.defaultCharset()))
|
||||
.isEqualTo("{\"title\":\"1.0.0\",\"due_on\":\"2022-05-04T12:00:00Z\"}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void createMilestoneWhenErrorResponseThenException() throws Exception {
|
||||
this.server.enqueue(new MockResponse().setResponseCode(400));
|
||||
assertThatExceptionOfType(RuntimeException.class)
|
||||
.isThrownBy(() -> this.github.createMilestone(this.repositoryRef, new Milestone()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+245
@@ -0,0 +1,245 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.gradle.github.milestones;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.Year;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.CsvSource;
|
||||
|
||||
import org.springframework.gradle.github.milestones.SpringReleaseTrainSpec.Train;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
/**
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
public class SpringReleaseTrainTests {
|
||||
@ParameterizedTest
|
||||
@CsvSource({
|
||||
"2019-12-31, ONE, 2020",
|
||||
"2020-01-01, ONE, 2020",
|
||||
"2020-01-31, ONE, 2020",
|
||||
"2020-02-01, TWO, 2020",
|
||||
"2020-07-31, TWO, 2020",
|
||||
"2020-08-01, ONE, 2021"
|
||||
})
|
||||
public void nextTrainWhenBoundaryConditionsThenSuccess(LocalDate startDate, Train expectedTrain, Year expectedYear) {
|
||||
SpringReleaseTrainSpec releaseTrainSpec =
|
||||
SpringReleaseTrainSpec.builder()
|
||||
.nextTrain(startDate)
|
||||
.version("1.0.0")
|
||||
.weekOfMonth(2)
|
||||
.dayOfWeek(2)
|
||||
.build();
|
||||
assertThat(releaseTrainSpec.getTrain()).isEqualTo(expectedTrain);
|
||||
assertThat(releaseTrainSpec.getYear()).isEqualTo(expectedYear);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTrainDatesWhenTrainOneIsSecondTuesdayOf2020ThenSuccess() {
|
||||
SpringReleaseTrainSpec releaseTrainSpec =
|
||||
SpringReleaseTrainSpec.builder()
|
||||
.train(1)
|
||||
.version("1.0.0")
|
||||
.weekOfMonth(2)
|
||||
.dayOfWeek(2)
|
||||
.year(2020)
|
||||
.build();
|
||||
|
||||
SpringReleaseTrain releaseTrain = new SpringReleaseTrain(releaseTrainSpec);
|
||||
Map<String, LocalDate> trainDates = releaseTrain.getTrainDates();
|
||||
assertThat(trainDates).hasSize(5);
|
||||
assertThat(trainDates.get("1.0.0-M1")).isEqualTo(LocalDate.of(2020, 1, 14));
|
||||
assertThat(trainDates.get("1.0.0-M2")).isEqualTo(LocalDate.of(2020, 2, 11));
|
||||
assertThat(trainDates.get("1.0.0-M3")).isEqualTo(LocalDate.of(2020, 3, 10));
|
||||
assertThat(trainDates.get("1.0.0-RC1")).isEqualTo(LocalDate.of(2020, 4, 14));
|
||||
assertThat(trainDates.get("1.0.0")).isEqualTo(LocalDate.of(2020, 5, 12));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTrainDatesWhenTrainTwoIsSecondTuesdayOf2020ThenSuccess() {
|
||||
SpringReleaseTrainSpec releaseTrainSpec =
|
||||
SpringReleaseTrainSpec.builder()
|
||||
.train(2)
|
||||
.version("1.0.0")
|
||||
.weekOfMonth(2)
|
||||
.dayOfWeek(2)
|
||||
.year(2020)
|
||||
.build();
|
||||
|
||||
SpringReleaseTrain releaseTrain = new SpringReleaseTrain(releaseTrainSpec);
|
||||
Map<String, LocalDate> trainDates = releaseTrain.getTrainDates();
|
||||
assertThat(trainDates).hasSize(5);
|
||||
assertThat(trainDates.get("1.0.0-M1")).isEqualTo(LocalDate.of(2020, 7, 14));
|
||||
assertThat(trainDates.get("1.0.0-M2")).isEqualTo(LocalDate.of(2020, 8, 11));
|
||||
assertThat(trainDates.get("1.0.0-M3")).isEqualTo(LocalDate.of(2020, 9, 15));
|
||||
assertThat(trainDates.get("1.0.0-RC1")).isEqualTo(LocalDate.of(2020, 10, 13));
|
||||
assertThat(trainDates.get("1.0.0")).isEqualTo(LocalDate.of(2020, 11, 10));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTrainDatesWhenTrainOneIsSecondTuesdayOf2022ThenSuccess() {
|
||||
SpringReleaseTrainSpec releaseTrainSpec =
|
||||
SpringReleaseTrainSpec.builder()
|
||||
.train(1)
|
||||
.version("1.0.0")
|
||||
.weekOfMonth(2)
|
||||
.dayOfWeek(2)
|
||||
.year(2022)
|
||||
.build();
|
||||
|
||||
SpringReleaseTrain releaseTrain = new SpringReleaseTrain(releaseTrainSpec);
|
||||
Map<String, LocalDate> trainDates = releaseTrain.getTrainDates();
|
||||
assertThat(trainDates).hasSize(5);
|
||||
assertThat(trainDates.get("1.0.0-M1")).isEqualTo(LocalDate.of(2022, 1, 11));
|
||||
assertThat(trainDates.get("1.0.0-M2")).isEqualTo(LocalDate.of(2022, 2, 15));
|
||||
assertThat(trainDates.get("1.0.0-M3")).isEqualTo(LocalDate.of(2022, 3, 15));
|
||||
assertThat(trainDates.get("1.0.0-RC1")).isEqualTo(LocalDate.of(2022, 4, 12));
|
||||
assertThat(trainDates.get("1.0.0")).isEqualTo(LocalDate.of(2022, 5, 10));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTrainDatesWhenTrainTwoIsSecondTuesdayOf2022ThenSuccess() {
|
||||
SpringReleaseTrainSpec releaseTrainSpec =
|
||||
SpringReleaseTrainSpec.builder()
|
||||
.train(2)
|
||||
.version("1.0.0")
|
||||
.weekOfMonth(2)
|
||||
.dayOfWeek(2)
|
||||
.year(2022)
|
||||
.build();
|
||||
|
||||
SpringReleaseTrain releaseTrain = new SpringReleaseTrain(releaseTrainSpec);
|
||||
Map<String, LocalDate> trainDates = releaseTrain.getTrainDates();
|
||||
assertThat(trainDates).hasSize(5);
|
||||
assertThat(trainDates.get("1.0.0-M1")).isEqualTo(LocalDate.of(2022, 7, 12));
|
||||
assertThat(trainDates.get("1.0.0-M2")).isEqualTo(LocalDate.of(2022, 8, 9));
|
||||
assertThat(trainDates.get("1.0.0-M3")).isEqualTo(LocalDate.of(2022, 9, 13));
|
||||
assertThat(trainDates.get("1.0.0-RC1")).isEqualTo(LocalDate.of(2022, 10, 11));
|
||||
assertThat(trainDates.get("1.0.0")).isEqualTo(LocalDate.of(2022, 11, 15));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTrainDatesWhenTrainOneIsThirdMondayOf2022ThenSuccess() {
|
||||
SpringReleaseTrainSpec releaseTrainSpec =
|
||||
SpringReleaseTrainSpec.builder()
|
||||
.train(1)
|
||||
.version("1.0.0")
|
||||
.weekOfMonth(3)
|
||||
.dayOfWeek(1)
|
||||
.year(2022)
|
||||
.build();
|
||||
|
||||
SpringReleaseTrain releaseTrain = new SpringReleaseTrain(releaseTrainSpec);
|
||||
Map<String, LocalDate> trainDates = releaseTrain.getTrainDates();
|
||||
assertThat(trainDates).hasSize(5);
|
||||
assertThat(trainDates.get("1.0.0-M1")).isEqualTo(LocalDate.of(2022, 1, 17));
|
||||
assertThat(trainDates.get("1.0.0-M2")).isEqualTo(LocalDate.of(2022, 2, 21));
|
||||
assertThat(trainDates.get("1.0.0-M3")).isEqualTo(LocalDate.of(2022, 3, 21));
|
||||
assertThat(trainDates.get("1.0.0-RC1")).isEqualTo(LocalDate.of(2022, 4, 18));
|
||||
assertThat(trainDates.get("1.0.0")).isEqualTo(LocalDate.of(2022, 5, 16));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getTrainDatesWhenTrainTwoIsThirdMondayOf2022ThenSuccess() {
|
||||
SpringReleaseTrainSpec releaseTrainSpec =
|
||||
SpringReleaseTrainSpec.builder()
|
||||
.train(2)
|
||||
.version("1.0.0")
|
||||
.weekOfMonth(3)
|
||||
.dayOfWeek(1)
|
||||
.year(2022)
|
||||
.build();
|
||||
|
||||
SpringReleaseTrain releaseTrain = new SpringReleaseTrain(releaseTrainSpec);
|
||||
Map<String, LocalDate> trainDates = releaseTrain.getTrainDates();
|
||||
assertThat(trainDates).hasSize(5);
|
||||
assertThat(trainDates.get("1.0.0-M1")).isEqualTo(LocalDate.of(2022, 7, 18));
|
||||
assertThat(trainDates.get("1.0.0-M2")).isEqualTo(LocalDate.of(2022, 8, 15));
|
||||
assertThat(trainDates.get("1.0.0-M3")).isEqualTo(LocalDate.of(2022, 9, 19));
|
||||
assertThat(trainDates.get("1.0.0-RC1")).isEqualTo(LocalDate.of(2022, 10, 17));
|
||||
assertThat(trainDates.get("1.0.0")).isEqualTo(LocalDate.of(2022, 11, 21));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isTrainDateWhenTrainOneIsThirdMondayOf2022ThenSuccess() {
|
||||
SpringReleaseTrainSpec releaseTrainSpec =
|
||||
SpringReleaseTrainSpec.builder()
|
||||
.train(1)
|
||||
.version("1.0.0")
|
||||
.weekOfMonth(3)
|
||||
.dayOfWeek(1)
|
||||
.year(2022)
|
||||
.build();
|
||||
|
||||
SpringReleaseTrain releaseTrain = new SpringReleaseTrain(releaseTrainSpec);
|
||||
for (int dayOfMonth = 1; dayOfMonth <= 31; dayOfMonth++) {
|
||||
assertThat(releaseTrain.isTrainDate("1.0.0-M1", LocalDate.of(2022, 1, dayOfMonth))).isEqualTo(dayOfMonth == 17);
|
||||
}
|
||||
for (int dayOfMonth = 1; dayOfMonth <= 28; dayOfMonth++) {
|
||||
assertThat(releaseTrain.isTrainDate("1.0.0-M2", LocalDate.of(2022, 2, dayOfMonth))).isEqualTo(dayOfMonth == 21);
|
||||
}
|
||||
for (int dayOfMonth = 1; dayOfMonth <= 31; dayOfMonth++) {
|
||||
assertThat(releaseTrain.isTrainDate("1.0.0-M3", LocalDate.of(2022, 3, dayOfMonth))).isEqualTo(dayOfMonth == 21);
|
||||
}
|
||||
for (int dayOfMonth = 1; dayOfMonth <= 30; dayOfMonth++) {
|
||||
assertThat(releaseTrain.isTrainDate("1.0.0-RC1", LocalDate.of(2022, 4, dayOfMonth))).isEqualTo(dayOfMonth == 18);
|
||||
}
|
||||
for (int dayOfMonth = 1; dayOfMonth <= 31; dayOfMonth++) {
|
||||
assertThat(releaseTrain.isTrainDate("1.0.0", LocalDate.of(2022, 5, dayOfMonth))).isEqualTo(dayOfMonth == 16);
|
||||
}
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@CsvSource({
|
||||
"2022-01-01, 2022-02-21",
|
||||
"2022-02-01, 2022-02-21",
|
||||
"2022-02-21, 2022-04-18",
|
||||
"2022-03-01, 2022-04-18",
|
||||
"2022-04-01, 2022-04-18",
|
||||
"2022-04-18, 2022-06-20",
|
||||
"2022-05-01, 2022-06-20",
|
||||
"2022-06-01, 2022-06-20",
|
||||
"2022-06-20, 2022-08-15",
|
||||
"2022-07-01, 2022-08-15",
|
||||
"2022-08-01, 2022-08-15",
|
||||
"2022-08-15, 2022-10-17",
|
||||
"2022-09-01, 2022-10-17",
|
||||
"2022-10-01, 2022-10-17",
|
||||
"2022-10-17, 2022-12-19",
|
||||
"2022-11-01, 2022-12-19",
|
||||
"2022-12-01, 2022-12-19",
|
||||
"2022-12-19, 2023-02-20"
|
||||
})
|
||||
public void getNextReleaseDateWhenBoundaryConditionsThenSuccess(LocalDate startDate, LocalDate expectedDate) {
|
||||
SpringReleaseTrainSpec releaseTrainSpec =
|
||||
SpringReleaseTrainSpec.builder()
|
||||
.train(1)
|
||||
.version("1.0.0")
|
||||
.weekOfMonth(3)
|
||||
.dayOfWeek(1)
|
||||
.year(2022)
|
||||
.build();
|
||||
|
||||
SpringReleaseTrain releaseTrain = new SpringReleaseTrain(releaseTrainSpec);
|
||||
assertThat(releaseTrain.getNextReleaseDate(startDate)).isEqualTo(expectedDate);
|
||||
}
|
||||
}
|
||||
+89
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.gradle.github.release;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import okhttp3.mockwebserver.MockResponse;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
import okhttp3.mockwebserver.RecordedRequest;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.gradle.github.RepositoryRef;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
||||
/**
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
public class GitHubActionsApiTests {
|
||||
private GitHubActionsApi gitHubActionsApi;
|
||||
|
||||
private MockWebServer server;
|
||||
|
||||
private String baseUrl;
|
||||
|
||||
private RepositoryRef repository;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() throws Exception {
|
||||
this.server = new MockWebServer();
|
||||
this.server.start();
|
||||
this.baseUrl = this.server.url("/api").toString();
|
||||
this.gitHubActionsApi = new GitHubActionsApi("mock-oauth-token");
|
||||
this.gitHubActionsApi.setBaseUrl(this.baseUrl);
|
||||
this.repository = new RepositoryRef("spring-projects", "spring-security");
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void cleanup() throws Exception {
|
||||
this.server.shutdown();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dispatchWorkflowWhenValidParametersThenSuccess() throws Exception {
|
||||
this.server.enqueue(new MockResponse().setResponseCode(204));
|
||||
|
||||
Map<String, Object> inputs = new LinkedHashMap<>();
|
||||
inputs.put("input-1", "value");
|
||||
inputs.put("input-2", false);
|
||||
WorkflowDispatch workflowDispatch = new WorkflowDispatch("main", inputs);
|
||||
this.gitHubActionsApi.dispatchWorkflow(this.repository, "test-workflow.yml", workflowDispatch);
|
||||
|
||||
RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS);
|
||||
assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("post");
|
||||
assertThat(recordedRequest.getRequestUrl().toString())
|
||||
.isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/actions/workflows/test-workflow.yml/dispatches");
|
||||
assertThat(recordedRequest.getBody().readString(Charset.defaultCharset()))
|
||||
.isEqualTo("{\"ref\":\"main\",\"inputs\":{\"input-1\":\"value\",\"input-2\":false}}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dispatchWorkflowWhenErrorResponseThenException() throws Exception {
|
||||
this.server.enqueue(new MockResponse().setResponseCode(400));
|
||||
|
||||
WorkflowDispatch workflowDispatch = new WorkflowDispatch("main", null);
|
||||
assertThatExceptionOfType(RuntimeException.class)
|
||||
.isThrownBy(() -> this.gitHubActionsApi.dispatchWorkflow(this.repository, "test-workflow.yml", workflowDispatch));
|
||||
}
|
||||
}
|
||||
+155
@@ -0,0 +1,155 @@
|
||||
/*
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.gradle.github.release;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import okhttp3.mockwebserver.MockResponse;
|
||||
import okhttp3.mockwebserver.MockWebServer;
|
||||
import okhttp3.mockwebserver.RecordedRequest;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.gradle.github.RepositoryRef;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
|
||||
/**
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
public class GitHubReleaseApiTests {
|
||||
private GitHubReleaseApi gitHubReleaseApi;
|
||||
|
||||
private MockWebServer server;
|
||||
|
||||
private String baseUrl;
|
||||
|
||||
private RepositoryRef repository;
|
||||
|
||||
@BeforeEach
|
||||
public void setup() throws Exception {
|
||||
this.server = new MockWebServer();
|
||||
this.server.start();
|
||||
this.baseUrl = this.server.url("/api").toString();
|
||||
this.gitHubReleaseApi = new GitHubReleaseApi("mock-oauth-token");
|
||||
this.gitHubReleaseApi.setBaseUrl(this.baseUrl);
|
||||
this.repository = new RepositoryRef("spring-projects", "spring-security");
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void cleanup() throws Exception {
|
||||
this.server.shutdown();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void publishReleaseWhenValidParametersThenSuccess() throws Exception {
|
||||
String responseJson = "{\n" +
|
||||
" \"url\": \"https://api.github.com/spring-projects/spring-security/releases/1\",\n" +
|
||||
" \"html_url\": \"https://github.com/spring-projects/spring-security/releases/tags/v1.0.0\",\n" +
|
||||
" \"assets_url\": \"https://api.github.com/spring-projects/spring-security/releases/1/assets\",\n" +
|
||||
" \"upload_url\": \"https://uploads.github.com/spring-projects/spring-security/releases/1/assets{?name,label}\",\n" +
|
||||
" \"tarball_url\": \"https://api.github.com/spring-projects/spring-security/tarball/v1.0.0\",\n" +
|
||||
" \"zipball_url\": \"https://api.github.com/spring-projects/spring-security/zipball/v1.0.0\",\n" +
|
||||
" \"discussion_url\": \"https://github.com/spring-projects/spring-security/discussions/90\",\n" +
|
||||
" \"id\": 1,\n" +
|
||||
" \"node_id\": \"MDc6UmVsZWFzZTE=\",\n" +
|
||||
" \"tag_name\": \"v1.0.0\",\n" +
|
||||
" \"target_commitish\": \"main\",\n" +
|
||||
" \"name\": \"v1.0.0\",\n" +
|
||||
" \"body\": \"Description of the release\",\n" +
|
||||
" \"draft\": false,\n" +
|
||||
" \"prerelease\": false,\n" +
|
||||
" \"created_at\": \"2013-02-27T19:35:32Z\",\n" +
|
||||
" \"published_at\": \"2013-02-27T19:35:32Z\",\n" +
|
||||
" \"author\": {\n" +
|
||||
" \"login\": \"sjohnr\",\n" +
|
||||
" \"id\": 1,\n" +
|
||||
" \"node_id\": \"MDQ6VXNlcjE=\",\n" +
|
||||
" \"avatar_url\": \"https://github.com/images/avatar.gif\",\n" +
|
||||
" \"gravatar_id\": \"\",\n" +
|
||||
" \"url\": \"https://api.github.com/users/sjohnr\",\n" +
|
||||
" \"html_url\": \"https://github.com/sjohnr\",\n" +
|
||||
" \"followers_url\": \"https://api.github.com/users/sjohnr/followers\",\n" +
|
||||
" \"following_url\": \"https://api.github.com/users/sjohnr/following{/other_user}\",\n" +
|
||||
" \"gists_url\": \"https://api.github.com/users/sjohnr/gists{/gist_id}\",\n" +
|
||||
" \"starred_url\": \"https://api.github.com/users/sjohnr/starred{/owner}{/repo}\",\n" +
|
||||
" \"subscriptions_url\": \"https://api.github.com/users/sjohnr/subscriptions\",\n" +
|
||||
" \"organizations_url\": \"https://api.github.com/users/sjohnr/orgs\",\n" +
|
||||
" \"repos_url\": \"https://api.github.com/users/sjohnr/repos\",\n" +
|
||||
" \"events_url\": \"https://api.github.com/users/sjohnr/events{/privacy}\",\n" +
|
||||
" \"received_events_url\": \"https://api.github.com/users/sjohnr/received_events\",\n" +
|
||||
" \"type\": \"User\",\n" +
|
||||
" \"site_admin\": false\n" +
|
||||
" },\n" +
|
||||
" \"assets\": [\n" +
|
||||
" {\n" +
|
||||
" \"url\": \"https://api.github.com/spring-projects/spring-security/releases/assets/1\",\n" +
|
||||
" \"browser_download_url\": \"https://github.com/spring-projects/spring-security/releases/download/v1.0.0/example.zip\",\n" +
|
||||
" \"id\": 1,\n" +
|
||||
" \"node_id\": \"MDEyOlJlbGVhc2VBc3NldDE=\",\n" +
|
||||
" \"name\": \"example.zip\",\n" +
|
||||
" \"label\": \"short description\",\n" +
|
||||
" \"state\": \"uploaded\",\n" +
|
||||
" \"content_type\": \"application/zip\",\n" +
|
||||
" \"size\": 1024,\n" +
|
||||
" \"download_count\": 42,\n" +
|
||||
" \"created_at\": \"2013-02-27T19:35:32Z\",\n" +
|
||||
" \"updated_at\": \"2013-02-27T19:35:32Z\",\n" +
|
||||
" \"uploader\": {\n" +
|
||||
" \"login\": \"sjohnr\",\n" +
|
||||
" \"id\": 1,\n" +
|
||||
" \"node_id\": \"MDQ6VXNlcjE=\",\n" +
|
||||
" \"avatar_url\": \"https://github.com/images/avatar.gif\",\n" +
|
||||
" \"gravatar_id\": \"\",\n" +
|
||||
" \"url\": \"https://api.github.com/users/sjohnr\",\n" +
|
||||
" \"html_url\": \"https://github.com/sjohnr\",\n" +
|
||||
" \"followers_url\": \"https://api.github.com/users/sjohnr/followers\",\n" +
|
||||
" \"following_url\": \"https://api.github.com/users/sjohnr/following{/other_user}\",\n" +
|
||||
" \"gists_url\": \"https://api.github.com/users/sjohnr/gists{/gist_id}\",\n" +
|
||||
" \"starred_url\": \"https://api.github.com/users/sjohnr/starred{/owner}{/repo}\",\n" +
|
||||
" \"subscriptions_url\": \"https://api.github.com/users/sjohnr/subscriptions\",\n" +
|
||||
" \"organizations_url\": \"https://api.github.com/users/sjohnr/orgs\",\n" +
|
||||
" \"repos_url\": \"https://api.github.com/users/sjohnr/repos\",\n" +
|
||||
" \"events_url\": \"https://api.github.com/users/sjohnr/events{/privacy}\",\n" +
|
||||
" \"received_events_url\": \"https://api.github.com/users/sjohnr/received_events\",\n" +
|
||||
" \"type\": \"User\",\n" +
|
||||
" \"site_admin\": false\n" +
|
||||
" }\n" +
|
||||
" }\n" +
|
||||
" ]\n" +
|
||||
"}";
|
||||
this.server.enqueue(new MockResponse().setBody(responseJson));
|
||||
this.gitHubReleaseApi.publishRelease(this.repository, Release.tag("1.0.0").build());
|
||||
|
||||
RecordedRequest recordedRequest = this.server.takeRequest(1, TimeUnit.SECONDS);
|
||||
assertThat(recordedRequest.getMethod()).isEqualToIgnoringCase("post");
|
||||
assertThat(recordedRequest.getRequestUrl().toString())
|
||||
.isEqualTo(this.baseUrl + "/repos/spring-projects/spring-security/releases");
|
||||
assertThat(recordedRequest.getBody().readString(Charset.defaultCharset()))
|
||||
.isEqualTo("{\"tag_name\":\"1.0.0\",\"draft\":false,\"prerelease\":false,\"generate_release_notes\":false}");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void publishReleaseWhenErrorResponseThenException() throws Exception {
|
||||
this.server.enqueue(new MockResponse().setResponseCode(400));
|
||||
assertThatExceptionOfType(RuntimeException.class)
|
||||
.isThrownBy(() -> this.gitHubReleaseApi.publishRelease(this.repository, Release.tag("1.0.0").build()));
|
||||
}
|
||||
}
|
||||
@@ -9,6 +9,6 @@ repositories {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
optional 'javax.servlet:javax.servlet-api:3.1.0'
|
||||
optional 'jakarta.servlet:jakarta.servlet-api:3.1.0'
|
||||
testCompile 'junit:junit:4.12'
|
||||
}
|
||||
@@ -4,7 +4,10 @@ dependencies {
|
||||
management platform(project(":spring-security-dependencies"))
|
||||
api project(':spring-security-core')
|
||||
api project(':spring-security-web')
|
||||
api 'org.jasig.cas.client:cas-client-core'
|
||||
api('org.jasig.cas.client:cas-client-core') {
|
||||
exclude group: 'org.glassfish.jaxb', module: 'jaxb-core'
|
||||
exclude group: 'javax.xml.bind', module: 'jaxb-api'
|
||||
}
|
||||
api 'org.springframework:spring-beans'
|
||||
api 'org.springframework:spring-context'
|
||||
api 'org.springframework:spring-core'
|
||||
@@ -13,7 +16,7 @@ dependencies {
|
||||
optional 'com.fasterxml.jackson.core:jackson-databind'
|
||||
optional 'net.sf.ehcache:ehcache'
|
||||
|
||||
provided 'javax.servlet:javax.servlet-api'
|
||||
provided 'jakarta.servlet:jakarta.servlet-api'
|
||||
|
||||
testImplementation "org.assertj:assertj-core"
|
||||
testImplementation "org.junit.jupiter:junit-jupiter-api"
|
||||
|
||||
@@ -37,9 +37,9 @@ dependencies {
|
||||
optional'org.springframework:spring-websocket'
|
||||
optional 'org.jetbrains.kotlin:kotlin-reflect'
|
||||
optional 'org.jetbrains.kotlin:kotlin-stdlib-jdk8'
|
||||
optional 'javax.annotation:jsr250-api'
|
||||
optional 'jakarta.annotation:jakarta.annotation-api'
|
||||
|
||||
provided 'javax.servlet:javax.servlet-api'
|
||||
provided 'jakarta.servlet:jakarta.servlet-api'
|
||||
|
||||
testImplementation project(':spring-security-aspects')
|
||||
testImplementation project(':spring-security-cas')
|
||||
@@ -62,8 +62,10 @@ dependencies {
|
||||
testImplementation 'ch.qos.logback:logback-classic'
|
||||
testImplementation 'io.projectreactor.netty:reactor-netty'
|
||||
testImplementation 'io.rsocket:rsocket-transport-netty'
|
||||
testImplementation 'javax.annotation:jsr250-api:1.0'
|
||||
testImplementation 'javax.xml.bind:jaxb-api'
|
||||
testImplementation 'jakarta.annotation:jakarta.annotation-api:1.0'
|
||||
testImplementation "jakarta.inject:jakarta.inject-api"
|
||||
testImplementation "jakarta.transaction:jakarta.transaction-api"
|
||||
testImplementation 'jakarta.xml.bind:jakarta.xml.bind-api'
|
||||
testImplementation 'ldapsdk:ldapsdk:4.1'
|
||||
testImplementation('net.sourceforge.htmlunit:htmlunit') {
|
||||
exclude group: 'commons-logging', module: 'commons-logging'
|
||||
@@ -75,12 +77,18 @@ dependencies {
|
||||
testImplementation "org.apache.directory.server:apacheds-server-jndi"
|
||||
testImplementation 'org.apache.directory.shared:shared-ldap'
|
||||
testImplementation 'org.eclipse.persistence:javax.persistence'
|
||||
testImplementation 'org.hibernate:hibernate-entitymanager'
|
||||
testImplementation('org.hibernate:hibernate-entitymanager') {
|
||||
exclude group: 'javax.activation', module: 'javax.activation-api'
|
||||
exclude group: 'javax.persistence', module: 'javax.persistence-api'
|
||||
exclude group: 'javax.xml.bind', module: 'jaxb-api'
|
||||
exclude group: 'org.jboss.spec.javax.transaction', module: 'jboss-transaction-api_1.2_spec'
|
||||
}
|
||||
testImplementation 'org.hsqldb:hsqldb'
|
||||
testImplementation 'org.mockito:mockito-core'
|
||||
testImplementation "org.mockito:mockito-inline"
|
||||
testImplementation ('org.openid4java:openid4java-nodeps') {
|
||||
exclude group: 'com.google.code.guice', module: 'guice'
|
||||
exclude group: 'commons-logging', module: 'commons-logging'
|
||||
}
|
||||
testImplementation('org.seleniumhq.selenium:htmlunit-driver') {
|
||||
exclude group: 'commons-logging', module: 'commons-logging'
|
||||
@@ -89,7 +97,6 @@ dependencies {
|
||||
exclude group: 'commons-logging', module: 'commons-logging'
|
||||
exclude group: 'io.netty', module: 'netty'
|
||||
}
|
||||
testImplementation 'org.slf4j:jcl-over-slf4j'
|
||||
testImplementation 'org.springframework.ldap:spring-ldap-core'
|
||||
testImplementation 'org.springframework:spring-expression'
|
||||
testImplementation 'org.springframework:spring-jdbc'
|
||||
|
||||
+1
-1
@@ -65,7 +65,7 @@ public class NamespaceLdapAuthenticationProviderTestsConfigs {
|
||||
.ldif("classpath:users.xldif") // ldap-server@ldif
|
||||
.managerDn("uid=admin,ou=system") // ldap-server@manager-dn
|
||||
.managerPassword("secret") // ldap-server@manager-password
|
||||
.port(33399) // ldap-server@port
|
||||
.port(0) // ldap-server@port
|
||||
.root("dc=springframework,dc=org"); // ldap-server@root
|
||||
// .url("ldap://localhost:33389/dc-springframework,dc=org") this overrides root and port and is used for external
|
||||
// @formatter:on
|
||||
|
||||
+10
-3
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -39,6 +39,7 @@ import org.springframework.core.annotation.AnnotationAwareOrderComparator;
|
||||
import org.springframework.core.log.LogMessage;
|
||||
import org.springframework.security.authentication.AuthenticationEventPublisher;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
|
||||
import org.springframework.security.config.annotation.ObjectPostProcessor;
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||
import org.springframework.security.config.annotation.authentication.configurers.provisioning.InMemoryUserDetailsManagerConfigurer;
|
||||
@@ -79,8 +80,7 @@ public class AuthenticationConfiguration {
|
||||
public AuthenticationManagerBuilder authenticationManagerBuilder(ObjectPostProcessor<Object> objectPostProcessor,
|
||||
ApplicationContext context) {
|
||||
LazyPasswordEncoder defaultPasswordEncoder = new LazyPasswordEncoder(context);
|
||||
AuthenticationEventPublisher authenticationEventPublisher = getBeanOrNull(context,
|
||||
AuthenticationEventPublisher.class);
|
||||
AuthenticationEventPublisher authenticationEventPublisher = getAuthenticationEventPublisher(context);
|
||||
DefaultPasswordEncoderAuthenticationManagerBuilder result = new DefaultPasswordEncoderAuthenticationManagerBuilder(
|
||||
objectPostProcessor, defaultPasswordEncoder);
|
||||
if (authenticationEventPublisher != null) {
|
||||
@@ -142,6 +142,13 @@ public class AuthenticationConfiguration {
|
||||
this.objectPostProcessor = objectPostProcessor;
|
||||
}
|
||||
|
||||
private AuthenticationEventPublisher getAuthenticationEventPublisher(ApplicationContext context) {
|
||||
if (context.getBeanNamesForType(AuthenticationEventPublisher.class).length > 0) {
|
||||
return context.getBean(AuthenticationEventPublisher.class);
|
||||
}
|
||||
return this.objectPostProcessor.postProcess(new DefaultAuthenticationEventPublisher());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T lazyBean(Class<T> interfaceName) {
|
||||
LazyInitTargetSource lazyTargetSource = new LazyInitTargetSource();
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
|
||||
+14
-8
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -3283,20 +3283,26 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
|
||||
*/
|
||||
public final class MvcMatchersRequestMatcherConfigurer extends RequestMatcherConfigurer {
|
||||
|
||||
private final List<MvcRequestMatcher> mvcMatchers;
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
* @param context the {@link ApplicationContext} to use
|
||||
* @param matchers the {@link MvcRequestMatcher} instances to set the servlet path
|
||||
* on if {@link #servletPath(String)} is set.
|
||||
* @param mvcMatchers the {@link MvcRequestMatcher} instances to set the servlet
|
||||
* path on if {@link #servletPath(String)} is set.
|
||||
* @param allMatchers the {@link RequestMatcher} instances to continue the
|
||||
* configuration
|
||||
*/
|
||||
private MvcMatchersRequestMatcherConfigurer(ApplicationContext context, List<MvcRequestMatcher> matchers) {
|
||||
private MvcMatchersRequestMatcherConfigurer(ApplicationContext context, List<MvcRequestMatcher> mvcMatchers,
|
||||
List<RequestMatcher> allMatchers) {
|
||||
super(context);
|
||||
this.matchers = new ArrayList<>(matchers);
|
||||
this.mvcMatchers = new ArrayList<>(mvcMatchers);
|
||||
this.matchers = allMatchers;
|
||||
}
|
||||
|
||||
public RequestMatcherConfigurer servletPath(String servletPath) {
|
||||
for (RequestMatcher matcher : this.matchers) {
|
||||
((MvcRequestMatcher) matcher).setServletPath(servletPath);
|
||||
for (MvcRequestMatcher matcher : this.mvcMatchers) {
|
||||
matcher.setServletPath(servletPath);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
@@ -3321,7 +3327,7 @@ public final class HttpSecurity extends AbstractConfiguredSecurityBuilder<Defaul
|
||||
public MvcMatchersRequestMatcherConfigurer mvcMatchers(HttpMethod method, String... mvcPatterns) {
|
||||
List<MvcRequestMatcher> mvcMatchers = createMvcMatchers(method, mvcPatterns);
|
||||
setMatchers(mvcMatchers);
|
||||
return new MvcMatchersRequestMatcherConfigurer(getContext(), mvcMatchers);
|
||||
return new MvcMatchersRequestMatcherConfigurer(getContext(), mvcMatchers, this.matchers);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+57
-4
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -20,6 +20,7 @@ import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.ServletContext;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
@@ -33,6 +34,7 @@ import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.access.PermissionEvaluator;
|
||||
import org.springframework.security.access.expression.SecurityExpressionHandler;
|
||||
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
|
||||
import org.springframework.security.authorization.AuthorizationManager;
|
||||
import org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder;
|
||||
import org.springframework.security.config.annotation.ObjectPostProcessor;
|
||||
import org.springframework.security.config.annotation.SecurityBuilder;
|
||||
@@ -47,9 +49,12 @@ import org.springframework.security.web.DefaultSecurityFilterChain;
|
||||
import org.springframework.security.web.FilterChainProxy;
|
||||
import org.springframework.security.web.FilterInvocation;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.access.AuthorizationManagerWebInvocationPrivilegeEvaluator;
|
||||
import org.springframework.security.web.access.DefaultWebInvocationPrivilegeEvaluator;
|
||||
import org.springframework.security.web.access.RequestMatcherDelegatingWebInvocationPrivilegeEvaluator;
|
||||
import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator;
|
||||
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
|
||||
import org.springframework.security.web.access.intercept.AuthorizationFilter;
|
||||
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
|
||||
import org.springframework.security.web.debug.DebugFilter;
|
||||
import org.springframework.security.web.firewall.HttpFirewall;
|
||||
@@ -57,7 +62,9 @@ import org.springframework.security.web.firewall.RequestRejectedHandler;
|
||||
import org.springframework.security.web.firewall.StrictHttpFirewall;
|
||||
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||
import org.springframework.security.web.util.matcher.RequestMatcherEntry;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.context.ServletContextAware;
|
||||
import org.springframework.web.filter.DelegatingFilterProxy;
|
||||
|
||||
/**
|
||||
@@ -81,7 +88,7 @@ import org.springframework.web.filter.DelegatingFilterProxy;
|
||||
* @see WebSecurityConfiguration
|
||||
*/
|
||||
public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter, WebSecurity>
|
||||
implements SecurityBuilder<Filter>, ApplicationContextAware {
|
||||
implements SecurityBuilder<Filter>, ApplicationContextAware, ServletContextAware {
|
||||
|
||||
private final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
@@ -108,6 +115,8 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
|
||||
private Runnable postBuildAction = () -> {
|
||||
};
|
||||
|
||||
private ServletContext servletContext;
|
||||
|
||||
/**
|
||||
* Creates a new instance
|
||||
* @param objectPostProcessor the {@link ObjectPostProcessor} to use
|
||||
@@ -252,7 +261,10 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
|
||||
* {@link WebSecurityConfigurerAdapter}.
|
||||
* @param securityInterceptor the {@link FilterSecurityInterceptor} to use
|
||||
* @return the {@link WebSecurity} for further customizations
|
||||
* @deprecated Use {@link #privilegeEvaluator(WebInvocationPrivilegeEvaluator)}
|
||||
* instead
|
||||
*/
|
||||
@Deprecated
|
||||
public WebSecurity securityInterceptor(FilterSecurityInterceptor securityInterceptor) {
|
||||
this.filterSecurityInterceptor = securityInterceptor;
|
||||
return this;
|
||||
@@ -278,11 +290,24 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
|
||||
+ ".addSecurityFilterChainBuilder directly");
|
||||
int chainSize = this.ignoredRequests.size() + this.securityFilterChainBuilders.size();
|
||||
List<SecurityFilterChain> securityFilterChains = new ArrayList<>(chainSize);
|
||||
List<RequestMatcherEntry<List<WebInvocationPrivilegeEvaluator>>> requestMatcherPrivilegeEvaluatorsEntries = new ArrayList<>();
|
||||
for (RequestMatcher ignoredRequest : this.ignoredRequests) {
|
||||
securityFilterChains.add(new DefaultSecurityFilterChain(ignoredRequest));
|
||||
WebSecurity.this.logger.warn("You are asking Spring Security to ignore " + ignoredRequest
|
||||
+ ". This is not recommended -- please use permitAll via HttpSecurity#authorizeHttpRequests instead.");
|
||||
SecurityFilterChain securityFilterChain = new DefaultSecurityFilterChain(ignoredRequest);
|
||||
securityFilterChains.add(securityFilterChain);
|
||||
requestMatcherPrivilegeEvaluatorsEntries
|
||||
.add(getRequestMatcherPrivilegeEvaluatorsEntry(securityFilterChain));
|
||||
}
|
||||
for (SecurityBuilder<? extends SecurityFilterChain> securityFilterChainBuilder : this.securityFilterChainBuilders) {
|
||||
securityFilterChains.add(securityFilterChainBuilder.build());
|
||||
SecurityFilterChain securityFilterChain = securityFilterChainBuilder.build();
|
||||
securityFilterChains.add(securityFilterChain);
|
||||
requestMatcherPrivilegeEvaluatorsEntries
|
||||
.add(getRequestMatcherPrivilegeEvaluatorsEntry(securityFilterChain));
|
||||
}
|
||||
if (this.privilegeEvaluator == null) {
|
||||
this.privilegeEvaluator = new RequestMatcherDelegatingWebInvocationPrivilegeEvaluator(
|
||||
requestMatcherPrivilegeEvaluatorsEntries);
|
||||
}
|
||||
FilterChainProxy filterChainProxy = new FilterChainProxy(securityFilterChains);
|
||||
if (this.httpFirewall != null) {
|
||||
@@ -306,6 +331,29 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
|
||||
return result;
|
||||
}
|
||||
|
||||
private RequestMatcherEntry<List<WebInvocationPrivilegeEvaluator>> getRequestMatcherPrivilegeEvaluatorsEntry(
|
||||
SecurityFilterChain securityFilterChain) {
|
||||
List<WebInvocationPrivilegeEvaluator> privilegeEvaluators = new ArrayList<>();
|
||||
for (Filter filter : securityFilterChain.getFilters()) {
|
||||
if (filter instanceof FilterSecurityInterceptor) {
|
||||
DefaultWebInvocationPrivilegeEvaluator defaultWebInvocationPrivilegeEvaluator = new DefaultWebInvocationPrivilegeEvaluator(
|
||||
(FilterSecurityInterceptor) filter);
|
||||
defaultWebInvocationPrivilegeEvaluator.setServletContext(this.servletContext);
|
||||
privilegeEvaluators.add(defaultWebInvocationPrivilegeEvaluator);
|
||||
continue;
|
||||
}
|
||||
if (filter instanceof AuthorizationFilter) {
|
||||
AuthorizationManager<HttpServletRequest> authorizationManager = ((AuthorizationFilter) filter)
|
||||
.getAuthorizationManager();
|
||||
AuthorizationManagerWebInvocationPrivilegeEvaluator evaluator = new AuthorizationManagerWebInvocationPrivilegeEvaluator(
|
||||
authorizationManager);
|
||||
evaluator.setServletContext(this.servletContext);
|
||||
privilegeEvaluators.add(evaluator);
|
||||
}
|
||||
}
|
||||
return new RequestMatcherEntry<>(securityFilterChain::matches, privilegeEvaluators);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
this.defaultWebSecurityExpressionHandler.setApplicationContext(applicationContext);
|
||||
@@ -333,6 +381,11 @@ public final class WebSecurity extends AbstractConfiguredSecurityBuilder<Filter,
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setServletContext(ServletContext servletContext) {
|
||||
this.servletContext = servletContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* An {@link IgnoredRequestConfigurer} that allows optionally configuring the
|
||||
* {@link MvcRequestMatcher#setMethod(HttpMethod)}
|
||||
|
||||
+11
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -24,7 +24,9 @@ import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.security.authentication.AuthenticationEventPublisher;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
|
||||
import org.springframework.security.config.annotation.ObjectPostProcessor;
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
|
||||
@@ -82,6 +84,7 @@ class HttpSecurityConfiguration {
|
||||
AuthenticationManagerBuilder authenticationBuilder = new WebSecurityConfigurerAdapter.DefaultPasswordEncoderAuthenticationManagerBuilder(
|
||||
this.objectPostProcessor, passwordEncoder);
|
||||
authenticationBuilder.parentAuthenticationManager(authenticationManager());
|
||||
authenticationBuilder.authenticationEventPublisher(getAuthenticationEventPublisher());
|
||||
HttpSecurity http = new HttpSecurity(this.objectPostProcessor, authenticationBuilder, createSharedObjects());
|
||||
// @formatter:off
|
||||
http
|
||||
@@ -105,6 +108,13 @@ class HttpSecurityConfiguration {
|
||||
: this.authenticationConfiguration.getAuthenticationManager();
|
||||
}
|
||||
|
||||
private AuthenticationEventPublisher getAuthenticationEventPublisher() {
|
||||
if (this.context.getBeanNamesForType(AuthenticationEventPublisher.class).length > 0) {
|
||||
return this.context.getBean(AuthenticationEventPublisher.class);
|
||||
}
|
||||
return this.objectPostProcessor.postProcess(new DefaultAuthenticationEventPublisher());
|
||||
}
|
||||
|
||||
private Map<Class<?>, Object> createSharedObjects() {
|
||||
Map<Class<?>, Object> sharedObjects = new HashMap<>();
|
||||
sharedObjects.put(ApplicationContext.class, this.context);
|
||||
|
||||
+2
-2
@@ -128,8 +128,8 @@ public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAwa
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates the {@link WebInvocationPrivilegeEvaluator} that is necessary for the JSP
|
||||
* tag support.
|
||||
* Creates the {@link WebInvocationPrivilegeEvaluator} that is necessary to evaluate
|
||||
* privileges for a given web URI
|
||||
* @return the {@link WebInvocationPrivilegeEvaluator}
|
||||
*/
|
||||
@Bean
|
||||
|
||||
+1
-2
@@ -297,8 +297,7 @@ public final class Saml2LoginConfigurer<B extends HttpSecurityBuilder<B>>
|
||||
if (version != null) {
|
||||
return version;
|
||||
}
|
||||
return Version.class.getModule().getDescriptor().version().map(Object::toString)
|
||||
.orElseThrow(() -> new IllegalStateException("cannot determine OpenSAML version"));
|
||||
return Version.getVersion();
|
||||
}
|
||||
|
||||
private void registerDefaultAuthenticationProvider(B http) {
|
||||
|
||||
+1
-2
@@ -319,8 +319,7 @@ public final class Saml2LogoutConfigurer<H extends HttpSecurityBuilder<H>>
|
||||
if (version != null) {
|
||||
return version;
|
||||
}
|
||||
return Version.class.getModule().getDescriptor().version().map(Object::toString)
|
||||
.orElseThrow(() -> new IllegalStateException("cannot determine OpenSAML version"));
|
||||
return Version.getVersion();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+50
@@ -34,8 +34,10 @@ import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.security.access.annotation.Secured;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.security.authentication.AuthenticationEventPublisher;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.authentication.DefaultAuthenticationEventPublisher;
|
||||
import org.springframework.security.authentication.TestAuthentication;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
@@ -51,6 +53,7 @@ import org.springframework.security.config.annotation.web.servlet.configuration.
|
||||
import org.springframework.security.config.test.SpringTestContext;
|
||||
import org.springframework.security.config.test.SpringTestContextExtension;
|
||||
import org.springframework.security.config.users.AuthenticationTestConfiguration;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
@@ -62,6 +65,7 @@ import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
||||
import org.springframework.test.util.ReflectionTestUtils;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
@@ -294,6 +298,28 @@ public class AuthenticationConfigurationTests {
|
||||
assertThatExceptionOfType(AlreadyBuiltException.class).isThrownBy(ap::build);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configureWhenDefaultsThenDefaultAuthenticationEventPublisher() {
|
||||
this.spring.register(AuthenticationConfiguration.class, ObjectPostProcessorConfiguration.class).autowire();
|
||||
AuthenticationManagerBuilder authenticationManagerBuilder = this.spring.getContext()
|
||||
.getBean(AuthenticationManagerBuilder.class);
|
||||
AuthenticationEventPublisher eventPublisher = (AuthenticationEventPublisher) ReflectionTestUtils
|
||||
.getField(authenticationManagerBuilder, "eventPublisher");
|
||||
assertThat(eventPublisher).isInstanceOf(DefaultAuthenticationEventPublisher.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void configureWhenCustomAuthenticationEventPublisherThenCustomAuthenticationEventPublisher() {
|
||||
this.spring.register(AuthenticationConfiguration.class, ObjectPostProcessorConfiguration.class,
|
||||
CustomAuthenticationEventPublisherConfig.class).autowire();
|
||||
AuthenticationManagerBuilder authenticationManagerBuilder = this.spring.getContext()
|
||||
.getBean(AuthenticationManagerBuilder.class);
|
||||
AuthenticationEventPublisher eventPublisher = (AuthenticationEventPublisher) ReflectionTestUtils
|
||||
.getField(authenticationManagerBuilder, "eventPublisher");
|
||||
assertThat(eventPublisher)
|
||||
.isInstanceOf(CustomAuthenticationEventPublisherConfig.MyAuthenticationEventPublisher.class);
|
||||
}
|
||||
|
||||
@EnableGlobalMethodSecurity(securedEnabled = true)
|
||||
static class GlobalMethodSecurityAutowiredConfig {
|
||||
|
||||
@@ -346,6 +372,30 @@ public class AuthenticationConfigurationTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class CustomAuthenticationEventPublisherConfig {
|
||||
|
||||
@Bean
|
||||
AuthenticationEventPublisher eventPublisher() {
|
||||
return new MyAuthenticationEventPublisher();
|
||||
}
|
||||
|
||||
static class MyAuthenticationEventPublisher implements AuthenticationEventPublisher {
|
||||
|
||||
@Override
|
||||
public void publishAuthenticationSuccess(Authentication authentication) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publishAuthenticationFailure(AuthenticationException exception, Authentication authentication) {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
interface Service {
|
||||
|
||||
void run();
|
||||
|
||||
+102
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.security.config.annotation.web.configuration;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
@@ -27,12 +29,19 @@ import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.event.EventListener;
|
||||
import org.springframework.mock.web.MockHttpSession;
|
||||
import org.springframework.security.access.AccessDeniedException;
|
||||
import org.springframework.security.authentication.AuthenticationEventPublisher;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.authentication.event.AbstractAuthenticationEvent;
|
||||
import org.springframework.security.authentication.event.AbstractAuthenticationFailureEvent;
|
||||
import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.test.SpringTestContext;
|
||||
import org.springframework.security.config.test.SpringTestContextExtension;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
@@ -48,6 +57,7 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.springframework.security.config.Customizer.withDefaults;
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin;
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
|
||||
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
|
||||
@@ -200,6 +210,48 @@ public class HttpSecurityConfigurationTests {
|
||||
this.mockMvc.perform(get("/login?logout")).andExpect(status().isOk());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loginWhenUsingDefaultThenAuthenticationEventPublished() throws Exception {
|
||||
this.spring
|
||||
.register(SecurityEnabledConfig.class, UserDetailsConfig.class, AuthenticationEventListenerConfig.class)
|
||||
.autowire();
|
||||
AuthenticationEventListenerConfig.clearEvents();
|
||||
this.mockMvc.perform(formLogin()).andExpect(status().is3xxRedirection());
|
||||
assertThat(AuthenticationEventListenerConfig.EVENTS).isNotEmpty();
|
||||
assertThat(AuthenticationEventListenerConfig.EVENTS).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loginWhenUsingDefaultAndNoUserDetailsServiceThenAuthenticationEventPublished() throws Exception {
|
||||
this.spring
|
||||
.register(SecurityEnabledConfig.class, UserDetailsConfig.class, AuthenticationEventListenerConfig.class)
|
||||
.autowire();
|
||||
AuthenticationEventListenerConfig.clearEvents();
|
||||
this.mockMvc.perform(formLogin()).andExpect(status().is3xxRedirection());
|
||||
assertThat(AuthenticationEventListenerConfig.EVENTS).isNotEmpty();
|
||||
assertThat(AuthenticationEventListenerConfig.EVENTS).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loginWhenUsingCustomAuthenticationEventPublisherThenAuthenticationEventPublished() throws Exception {
|
||||
this.spring.register(SecurityEnabledConfig.class, UserDetailsConfig.class,
|
||||
CustomAuthenticationEventPublisherConfig.class).autowire();
|
||||
CustomAuthenticationEventPublisherConfig.clearEvents();
|
||||
this.mockMvc.perform(formLogin()).andExpect(status().is3xxRedirection());
|
||||
assertThat(CustomAuthenticationEventPublisherConfig.EVENTS).isNotEmpty();
|
||||
assertThat(CustomAuthenticationEventPublisherConfig.EVENTS).hasSize(1);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loginWhenUsingCustomAuthenticationEventPublisherAndNoUserDetailsServiceThenAuthenticationEventPublished()
|
||||
throws Exception {
|
||||
this.spring.register(SecurityEnabledConfig.class, CustomAuthenticationEventPublisherConfig.class).autowire();
|
||||
CustomAuthenticationEventPublisherConfig.clearEvents();
|
||||
this.mockMvc.perform(formLogin()).andExpect(status().is3xxRedirection());
|
||||
assertThat(CustomAuthenticationEventPublisherConfig.EVENTS).isNotEmpty();
|
||||
assertThat(CustomAuthenticationEventPublisherConfig.EVENTS).hasSize(1);
|
||||
}
|
||||
|
||||
@RestController
|
||||
static class NameController {
|
||||
|
||||
@@ -270,6 +322,55 @@ public class HttpSecurityConfigurationTests {
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class CustomAuthenticationEventPublisherConfig {
|
||||
|
||||
static List<Authentication> EVENTS = new ArrayList<>();
|
||||
|
||||
static void clearEvents() {
|
||||
EVENTS.clear();
|
||||
}
|
||||
|
||||
@Bean
|
||||
AuthenticationEventPublisher publisher() {
|
||||
return new AuthenticationEventPublisher() {
|
||||
|
||||
@Override
|
||||
public void publishAuthenticationSuccess(Authentication authentication) {
|
||||
EVENTS.add(authentication);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void publishAuthenticationFailure(AuthenticationException exception,
|
||||
Authentication authentication) {
|
||||
EVENTS.add(authentication);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration
|
||||
static class AuthenticationEventListenerConfig {
|
||||
|
||||
static List<AbstractAuthenticationEvent> EVENTS = new ArrayList<>();
|
||||
|
||||
static void clearEvents() {
|
||||
EVENTS.clear();
|
||||
}
|
||||
|
||||
@EventListener
|
||||
void onAuthenticationSuccessEvent(AuthenticationSuccessEvent event) {
|
||||
EVENTS.add(event);
|
||||
}
|
||||
|
||||
@EventListener
|
||||
void onAuthenticationFailureEvent(AbstractAuthenticationFailureEvent event) {
|
||||
EVENTS.add(event);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@RestController
|
||||
static class BaseController {
|
||||
|
||||
|
||||
+191
-5
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2020 the original author or authors.
|
||||
* Copyright 2002-2021 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -33,6 +33,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
@@ -62,7 +63,7 @@ import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.web.FilterChainProxy;
|
||||
import org.springframework.security.web.FilterInvocation;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.access.DefaultWebInvocationPrivilegeEvaluator;
|
||||
import org.springframework.security.web.access.RequestMatcherDelegatingWebInvocationPrivilegeEvaluator;
|
||||
import org.springframework.security.web.access.WebInvocationPrivilegeEvaluator;
|
||||
import org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
@@ -84,6 +85,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
||||
* @author Rob Winch
|
||||
* @author Joe Grandja
|
||||
* @author Evgeniy Cheban
|
||||
* @author Marcus Da Coregio
|
||||
*/
|
||||
@ExtendWith(SpringTestContextExtension.class)
|
||||
public class WebSecurityConfigurationTests {
|
||||
@@ -218,10 +220,10 @@ public class WebSecurityConfigurationTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadConfigWhenDefaultWebInvocationPrivilegeEvaluatorThenDefaultIsRegistered() {
|
||||
public void loadConfigWhenDefaultWebInvocationPrivilegeEvaluatorThenRequestMatcherIsRegistered() {
|
||||
this.spring.register(WebInvocationPrivilegeEvaluatorDefaultsConfig.class).autowire();
|
||||
assertThat(this.spring.getContext().getBean(WebInvocationPrivilegeEvaluator.class))
|
||||
.isInstanceOf(DefaultWebInvocationPrivilegeEvaluator.class);
|
||||
.isInstanceOf(RequestMatcherDelegatingWebInvocationPrivilegeEvaluator.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -229,7 +231,7 @@ public class WebSecurityConfigurationTests {
|
||||
this.spring.register(AuthorizeRequestsFilterChainConfig.class).autowire();
|
||||
|
||||
assertThat(this.spring.getContext().getBean(WebInvocationPrivilegeEvaluator.class))
|
||||
.isInstanceOf(DefaultWebInvocationPrivilegeEvaluator.class);
|
||||
.isInstanceOf(RequestMatcherDelegatingWebInvocationPrivilegeEvaluator.class);
|
||||
}
|
||||
|
||||
// SEC-2303
|
||||
@@ -375,6 +377,69 @@ public class WebSecurityConfigurationTests {
|
||||
assertThat(filterChains.get(1).matches(request)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadConfigWhenTwoSecurityFilterChainsThenRequestMatcherDelegatingWebInvocationPrivilegeEvaluator() {
|
||||
this.spring.register(TwoSecurityFilterChainConfig.class).autowire();
|
||||
assertThat(this.spring.getContext().getBean(WebInvocationPrivilegeEvaluator.class))
|
||||
.isInstanceOf(RequestMatcherDelegatingWebInvocationPrivilegeEvaluator.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void loadConfigWhenTwoSecurityFilterChainDebugThenRequestMatcherDelegatingWebInvocationPrivilegeEvaluator() {
|
||||
this.spring.register(TwoSecurityFilterChainConfig.class).autowire();
|
||||
assertThat(this.spring.getContext().getBean(WebInvocationPrivilegeEvaluator.class))
|
||||
.isInstanceOf(RequestMatcherDelegatingWebInvocationPrivilegeEvaluator.class);
|
||||
}
|
||||
|
||||
// gh-10554
|
||||
@Test
|
||||
public void loadConfigWhenMultipleSecurityFilterChainsThenWebInvocationPrivilegeEvaluatorApplySecurity() {
|
||||
this.spring.register(MultipleSecurityFilterChainConfig.class).autowire();
|
||||
WebInvocationPrivilegeEvaluator privilegeEvaluator = this.spring.getContext()
|
||||
.getBean(WebInvocationPrivilegeEvaluator.class);
|
||||
assertUserPermissions(privilegeEvaluator);
|
||||
assertAdminPermissions(privilegeEvaluator);
|
||||
assertAnotherUserPermission(privilegeEvaluator);
|
||||
}
|
||||
|
||||
// gh-10554
|
||||
@Test
|
||||
public void loadConfigWhenMultipleSecurityFilterChainAndIgnoringThenWebInvocationPrivilegeEvaluatorAcceptsNullAuthenticationOnIgnored() {
|
||||
this.spring.register(MultipleSecurityFilterChainIgnoringConfig.class).autowire();
|
||||
WebInvocationPrivilegeEvaluator privilegeEvaluator = this.spring.getContext()
|
||||
.getBean(WebInvocationPrivilegeEvaluator.class);
|
||||
assertUserPermissions(privilegeEvaluator);
|
||||
assertAdminPermissions(privilegeEvaluator);
|
||||
assertAnotherUserPermission(privilegeEvaluator);
|
||||
// null authentication
|
||||
assertThat(privilegeEvaluator.isAllowed("/user", null)).isFalse();
|
||||
assertThat(privilegeEvaluator.isAllowed("/admin", null)).isFalse();
|
||||
assertThat(privilegeEvaluator.isAllowed("/another", null)).isFalse();
|
||||
assertThat(privilegeEvaluator.isAllowed("/ignoring1", null)).isTrue();
|
||||
assertThat(privilegeEvaluator.isAllowed("/ignoring1/child", null)).isTrue();
|
||||
}
|
||||
|
||||
private void assertAnotherUserPermission(WebInvocationPrivilegeEvaluator privilegeEvaluator) {
|
||||
Authentication anotherUser = new TestingAuthenticationToken("anotherUser", "password", "ROLE_ANOTHER");
|
||||
assertThat(privilegeEvaluator.isAllowed("/user", anotherUser)).isFalse();
|
||||
assertThat(privilegeEvaluator.isAllowed("/admin", anotherUser)).isFalse();
|
||||
assertThat(privilegeEvaluator.isAllowed("/another", anotherUser)).isTrue();
|
||||
}
|
||||
|
||||
private void assertAdminPermissions(WebInvocationPrivilegeEvaluator privilegeEvaluator) {
|
||||
Authentication admin = new TestingAuthenticationToken("admin", "password", "ROLE_ADMIN");
|
||||
assertThat(privilegeEvaluator.isAllowed("/user", admin)).isFalse();
|
||||
assertThat(privilegeEvaluator.isAllowed("/admin", admin)).isTrue();
|
||||
assertThat(privilegeEvaluator.isAllowed("/another", admin)).isTrue();
|
||||
}
|
||||
|
||||
private void assertUserPermissions(WebInvocationPrivilegeEvaluator privilegeEvaluator) {
|
||||
Authentication user = new TestingAuthenticationToken("user", "password", "ROLE_USER");
|
||||
assertThat(privilegeEvaluator.isAllowed("/user", user)).isTrue();
|
||||
assertThat(privilegeEvaluator.isAllowed("/admin", user)).isFalse();
|
||||
assertThat(privilegeEvaluator.isAllowed("/another", user)).isTrue();
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
@Import(AuthenticationTestConfiguration.class)
|
||||
static class SortedWebSecurityConfigurerAdaptersConfig {
|
||||
@@ -1008,4 +1073,125 @@ public class WebSecurityConfigurationTests {
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class TwoSecurityFilterChainConfig {
|
||||
|
||||
@Bean
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
public SecurityFilterChain path1(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.requestMatchers((requests) -> requests.antMatchers("/path1/**"))
|
||||
.authorizeRequests((requests) -> requests.anyRequest().authenticated());
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(Ordered.LOWEST_PRECEDENCE)
|
||||
public SecurityFilterChain permitAll(HttpSecurity http) throws Exception {
|
||||
http.authorizeRequests((requests) -> requests.anyRequest().permitAll());
|
||||
return http.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity(debug = true)
|
||||
static class TwoSecurityFilterChainDebugConfig {
|
||||
|
||||
@Bean
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
public SecurityFilterChain path1(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.requestMatchers((requests) -> requests.antMatchers("/path1/**"))
|
||||
.authorizeRequests((requests) -> requests.anyRequest().authenticated());
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(Ordered.LOWEST_PRECEDENCE)
|
||||
public SecurityFilterChain permitAll(HttpSecurity http) throws Exception {
|
||||
http.authorizeRequests((requests) -> requests.anyRequest().permitAll());
|
||||
return http.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
@Import(AuthenticationTestConfiguration.class)
|
||||
static class MultipleSecurityFilterChainConfig {
|
||||
|
||||
@Bean
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
public SecurityFilterChain notAuthorized(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.requestMatchers((requests) -> requests.antMatchers("/user"))
|
||||
.authorizeRequests((requests) -> requests.anyRequest().hasRole("USER"));
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE + 1)
|
||||
public SecurityFilterChain path1(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.requestMatchers((requests) -> requests.antMatchers("/admin"))
|
||||
.authorizeRequests((requests) -> requests.anyRequest().hasRole("ADMIN"));
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(Ordered.LOWEST_PRECEDENCE)
|
||||
public SecurityFilterChain permitAll(HttpSecurity http) throws Exception {
|
||||
http.authorizeRequests((requests) -> requests.anyRequest().permitAll());
|
||||
return http.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
@Import(AuthenticationTestConfiguration.class)
|
||||
static class MultipleSecurityFilterChainIgnoringConfig {
|
||||
|
||||
@Bean
|
||||
public WebSecurityCustomizer webSecurityCustomizer() {
|
||||
return (web) -> web.ignoring().antMatchers("/ignoring1/**");
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
public SecurityFilterChain notAuthorized(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.requestMatchers((requests) -> requests.antMatchers("/user"))
|
||||
.authorizeRequests((requests) -> requests.anyRequest().hasRole("USER"));
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE + 1)
|
||||
public SecurityFilterChain admin(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.requestMatchers((requests) -> requests.antMatchers("/admin"))
|
||||
.authorizeRequests((requests) -> requests.anyRequest().hasRole("ADMIN"));
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Order(Ordered.LOWEST_PRECEDENCE)
|
||||
public SecurityFilterChain permitAll(HttpSecurity http) throws Exception {
|
||||
http.authorizeRequests((requests) -> requests.anyRequest().permitAll());
|
||||
return http.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+80
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -21,17 +21,22 @@ import org.junit.jupiter.api.extension.ExtendWith;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.security.config.annotation.ObjectPostProcessor;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.security.config.test.SpringTestContext;
|
||||
import org.springframework.security.config.test.SpringTestContextExtension;
|
||||
import org.springframework.security.web.PortMapperImpl;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.access.channel.ChannelDecisionManagerImpl;
|
||||
import org.springframework.security.web.access.channel.ChannelProcessingFilter;
|
||||
import org.springframework.security.web.access.channel.InsecureChannelProcessor;
|
||||
import org.springframework.security.web.access.channel.SecureChannelProcessor;
|
||||
import org.springframework.test.web.servlet.MockMvc;
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.spy;
|
||||
@@ -44,6 +49,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @author Eleftheria Stein
|
||||
* @author Onur Kagan Ozcan
|
||||
*/
|
||||
@ExtendWith(SpringTestContextExtension.class)
|
||||
public class ChannelSecurityConfigurerTests {
|
||||
@@ -93,6 +99,24 @@ public class ChannelSecurityConfigurerTests {
|
||||
this.mvc.perform(get("/")).andExpect(redirectedUrl("https://localhost/"));
|
||||
}
|
||||
|
||||
// gh-10956
|
||||
@Test
|
||||
public void requestWhenRequiresChannelWithMultiMvcMatchersThenRedirectsToHttps() throws Exception {
|
||||
this.spring.register(RequiresChannelMultiMvcMatchersConfig.class).autowire();
|
||||
this.mvc.perform(get("/test-1")).andExpect(redirectedUrl("https://localhost/test-1"));
|
||||
this.mvc.perform(get("/test-2")).andExpect(redirectedUrl("https://localhost/test-2"));
|
||||
this.mvc.perform(get("/test-3")).andExpect(redirectedUrl("https://localhost/test-3"));
|
||||
}
|
||||
|
||||
// gh-10956
|
||||
@Test
|
||||
public void requestWhenRequiresChannelWithMultiMvcMatchersInLambdaThenRedirectsToHttps() throws Exception {
|
||||
this.spring.register(RequiresChannelMultiMvcMatchersInLambdaConfig.class).autowire();
|
||||
this.mvc.perform(get("/test-1")).andExpect(redirectedUrl("https://localhost/test-1"));
|
||||
this.mvc.perform(get("/test-2")).andExpect(redirectedUrl("https://localhost/test-2"));
|
||||
this.mvc.perform(get("/test-3")).andExpect(redirectedUrl("https://localhost/test-3"));
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
static class ObjectPostProcessorConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@@ -155,4 +179,59 @@ public class ChannelSecurityConfigurerTests {
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
@EnableWebMvc
|
||||
static class RequiresChannelMultiMvcMatchersConfig {
|
||||
|
||||
@Bean
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.portMapper()
|
||||
.portMapper(new PortMapperImpl())
|
||||
.and()
|
||||
.requiresChannel()
|
||||
.mvcMatchers("/test-1")
|
||||
.requiresSecure()
|
||||
.mvcMatchers("/test-2")
|
||||
.requiresSecure()
|
||||
.mvcMatchers("/test-3")
|
||||
.requiresSecure()
|
||||
.anyRequest()
|
||||
.requiresInsecure();
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
@EnableWebMvc
|
||||
static class RequiresChannelMultiMvcMatchersInLambdaConfig {
|
||||
|
||||
@Bean
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.portMapper((port) -> port
|
||||
.portMapper(new PortMapperImpl())
|
||||
)
|
||||
.requiresChannel((channel) -> channel
|
||||
.mvcMatchers("/test-1")
|
||||
.requiresSecure()
|
||||
.mvcMatchers("/test-2")
|
||||
.requiresSecure()
|
||||
.mvcMatchers("/test-3")
|
||||
.requiresSecure()
|
||||
.anyRequest()
|
||||
.requiresInsecure()
|
||||
);
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+132
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -23,7 +23,10 @@ import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.core.annotation.Order;
|
||||
import org.springframework.mock.web.MockFilterChain;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
@@ -33,6 +36,7 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.security.web.FilterChainProxy;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
|
||||
@@ -167,6 +171,38 @@ public class HttpSecurityRequestMatchersTests {
|
||||
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestMatcherWhenMultiMvcMatcherInLambdaThenAllPathsAreDenied() throws Exception {
|
||||
loadConfig(MultiMvcMatcherInLambdaConfig.class);
|
||||
this.request.setRequestURI("/test-1");
|
||||
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
|
||||
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
setup();
|
||||
this.request.setRequestURI("/test-2");
|
||||
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
|
||||
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
setup();
|
||||
this.request.setRequestURI("/test-3");
|
||||
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
|
||||
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestMatcherWhenMultiMvcMatcherThenAllPathsAreDenied() throws Exception {
|
||||
loadConfig(MultiMvcMatcherConfig.class);
|
||||
this.request.setRequestURI("/test-1");
|
||||
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
|
||||
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
setup();
|
||||
this.request.setRequestURI("/test-2");
|
||||
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
|
||||
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
setup();
|
||||
this.request.setRequestURI("/test-3");
|
||||
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
|
||||
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_UNAUTHORIZED);
|
||||
}
|
||||
|
||||
public void loadConfig(Class<?>... configs) {
|
||||
this.context = new AnnotationConfigWebApplicationContext();
|
||||
this.context.register(configs);
|
||||
@@ -175,6 +211,101 @@ public class HttpSecurityRequestMatchersTests {
|
||||
this.context.getAutowireCapableBeanFactory().autowireBean(this);
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
static class MultiMvcMatcherInLambdaConfig {
|
||||
|
||||
@Bean
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
SecurityFilterChain first(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.requestMatchers((requests) -> requests
|
||||
.mvcMatchers("/test-1")
|
||||
.mvcMatchers("/test-2")
|
||||
.mvcMatchers("/test-3")
|
||||
)
|
||||
.authorizeRequests((authorize) -> authorize.anyRequest().denyAll())
|
||||
.httpBasic(withDefaults());
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain second(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.requestMatchers((requests) -> requests
|
||||
.mvcMatchers("/test-1")
|
||||
)
|
||||
.authorizeRequests((authorize) -> authorize
|
||||
.anyRequest().permitAll()
|
||||
);
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@RestController
|
||||
static class PathController {
|
||||
|
||||
@RequestMapping({ "/test-1", "/test-2", "/test-3" })
|
||||
String path() {
|
||||
return "path";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
static class MultiMvcMatcherConfig {
|
||||
|
||||
@Bean
|
||||
@Order(Ordered.HIGHEST_PRECEDENCE)
|
||||
SecurityFilterChain first(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.requestMatchers()
|
||||
.mvcMatchers("/test-1")
|
||||
.mvcMatchers("/test-2")
|
||||
.mvcMatchers("/test-3")
|
||||
.and()
|
||||
.authorizeRequests()
|
||||
.anyRequest().denyAll()
|
||||
.and()
|
||||
.httpBasic(withDefaults());
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain second(HttpSecurity http) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.requestMatchers()
|
||||
.mvcMatchers("/test-1")
|
||||
.and()
|
||||
.authorizeRequests()
|
||||
.anyRequest().permitAll();
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@RestController
|
||||
static class PathController {
|
||||
|
||||
@RequestMapping({ "/test-1", "/test-2", "/test-3" })
|
||||
String path() {
|
||||
return "path";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
|
||||
+5
-7
@@ -21,11 +21,13 @@ import java.security.cert.Certificate;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
import javax.security.auth.x500.X500Principal;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.bouncycastle.asn1.x500.X500Name;
|
||||
import org.bouncycastle.asn1.x500.style.BCStyle;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import sun.security.x509.X500Name;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@@ -240,12 +242,8 @@ public class NamespaceHttpX509Tests {
|
||||
}
|
||||
|
||||
private String extractCommonName(X509Certificate certificate) {
|
||||
try {
|
||||
return ((X500Name) certificate.getSubjectDN()).getCommonName();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalArgumentException(ex);
|
||||
}
|
||||
X500Principal principal = certificate.getSubjectX500Principal();
|
||||
return new X500Name(principal.getName()).getRDNs(BCStyle.CN)[0].getFirst().getValue().toString();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+77
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2015 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package org.springframework.security.config.annotation.web.configurers;
|
||||
|
||||
import java.util.Base64;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
@@ -23,16 +25,24 @@ import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.mock.web.MockFilterChain;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.mock.web.MockServletContext;
|
||||
import org.springframework.security.config.Customizer;
|
||||
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
|
||||
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
||||
import org.springframework.security.web.FilterChainProxy;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
|
||||
@@ -125,6 +135,35 @@ public class UrlAuthorizationConfigurerTests {
|
||||
loadConfig(AnonymousUrlAuthorizationConfig.class);
|
||||
}
|
||||
|
||||
// gh-10956
|
||||
@Test
|
||||
public void multiMvcMatchersConfig() throws Exception {
|
||||
loadConfig(MultiMvcMatcherConfig.class);
|
||||
this.request.addHeader("Authorization",
|
||||
"Basic " + new String(Base64.getEncoder().encode("user:password".getBytes())));
|
||||
this.request.setRequestURI("/test-1");
|
||||
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
|
||||
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_FORBIDDEN);
|
||||
setup();
|
||||
this.request.addHeader("Authorization",
|
||||
"Basic " + new String(Base64.getEncoder().encode("user:password".getBytes())));
|
||||
this.request.setRequestURI("/test-2");
|
||||
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
|
||||
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_FORBIDDEN);
|
||||
setup();
|
||||
this.request.addHeader("Authorization",
|
||||
"Basic " + new String(Base64.getEncoder().encode("user:password".getBytes())));
|
||||
this.request.setRequestURI("/test-3");
|
||||
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
|
||||
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_FORBIDDEN);
|
||||
setup();
|
||||
this.request.addHeader("Authorization",
|
||||
"Basic " + new String(Base64.getEncoder().encode("user:password".getBytes())));
|
||||
this.request.setRequestURI("/test-x");
|
||||
this.springSecurityFilterChain.doFilter(this.request, this.response, this.chain);
|
||||
assertThat(this.response.getStatus()).isEqualTo(HttpServletResponse.SC_OK);
|
||||
}
|
||||
|
||||
public void loadConfig(Class<?>... configs) {
|
||||
this.context = new AnnotationConfigWebApplicationContext();
|
||||
this.context.register(configs);
|
||||
@@ -228,4 +267,41 @@ public class UrlAuthorizationConfigurerTests {
|
||||
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
@EnableWebMvc
|
||||
static class MultiMvcMatcherConfig {
|
||||
|
||||
@Bean
|
||||
SecurityFilterChain security(HttpSecurity http, ApplicationContext context) throws Exception {
|
||||
// @formatter:off
|
||||
http
|
||||
.httpBasic(Customizer.withDefaults())
|
||||
.apply(new UrlAuthorizationConfigurer<>(context)).getRegistry()
|
||||
.mvcMatchers("/test-1").hasRole("ADMIN")
|
||||
.mvcMatchers("/test-2").hasRole("ADMIN")
|
||||
.mvcMatchers("/test-3").hasRole("ADMIN")
|
||||
.anyRequest().hasRole("USER");
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
UserDetailsService userDetailsService() {
|
||||
UserDetails user = User.withDefaultPasswordEncoder().username("user").password("password").roles("USER")
|
||||
.build();
|
||||
return new InMemoryUserDetailsManager(user);
|
||||
}
|
||||
|
||||
@RestController
|
||||
static class PathController {
|
||||
|
||||
@RequestMapping({ "/test-1", "/test-2", "/test-3", "/test-x" })
|
||||
String path() {
|
||||
return "path";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ dependencies {
|
||||
|
||||
optional 'com.fasterxml.jackson.core:jackson-databind'
|
||||
optional 'io.projectreactor:reactor-core'
|
||||
optional 'javax.annotation:jsr250-api'
|
||||
optional 'jakarta.annotation:jakarta.annotation-api'
|
||||
optional 'net.sf.ehcache:ehcache'
|
||||
optional 'org.aspectj:aspectjrt'
|
||||
optional 'org.springframework:spring-jdbc'
|
||||
@@ -31,7 +31,6 @@ dependencies {
|
||||
testImplementation "org.mockito:mockito-junit-jupiter"
|
||||
testImplementation "org.springframework:spring-test"
|
||||
testImplementation 'org.skyscreamer:jsonassert'
|
||||
testImplementation 'org.slf4j:jcl-over-slf4j'
|
||||
testImplementation 'org.springframework:spring-test'
|
||||
|
||||
testRuntimeOnly 'org.hsqldb:hsqldb'
|
||||
|
||||
+14
-4
@@ -18,6 +18,7 @@ package org.springframework.security.core;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
@@ -60,15 +61,24 @@ public class SpringSecurityCoreVersionTests {
|
||||
|
||||
@BeforeEach
|
||||
public void setup() throws Exception {
|
||||
Field logger = ReflectionUtils.findField(SpringSecurityCoreVersion.class, "logger");
|
||||
StaticFinalReflectionUtils.setField(logger, this.logger);
|
||||
setFinalStaticField(SpringSecurityCoreVersion.class, "logger", this.logger);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void cleanup() throws Exception {
|
||||
System.clearProperty(getDisableChecksProperty());
|
||||
Field logger = ReflectionUtils.findField(SpringSecurityCoreVersion.class, "logger");
|
||||
StaticFinalReflectionUtils.setField(logger, LogFactory.getLog(SpringSecurityCoreVersion.class));
|
||||
setFinalStaticField(SpringSecurityCoreVersion.class, "logger",
|
||||
LogFactory.getLog(SpringSecurityCoreVersion.class));
|
||||
}
|
||||
|
||||
private static void setFinalStaticField(Class<?> clazz, String fieldName, Object value)
|
||||
throws ReflectiveOperationException {
|
||||
Field field = clazz.getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
Field modifiers = Field.class.getDeclaredField("modifiers");
|
||||
modifiers.setAccessible(true);
|
||||
modifiers.setInt(field, field.getModifiers() & ~Modifier.FINAL);
|
||||
field.set(null, value);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -1,115 +0,0 @@
|
||||
/*
|
||||
* Copyright 2008 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.security.core;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.security.AccessController;
|
||||
import java.security.PrivilegedAction;
|
||||
|
||||
import sun.misc.Unsafe;
|
||||
|
||||
import org.springframework.objenesis.instantiator.util.UnsafeUtils;
|
||||
|
||||
/**
|
||||
* Used for setting static variables even if they are private static final.
|
||||
*
|
||||
* The code in this class has been adopted from Powermock's <a href=
|
||||
* "https://github.com/noushadali/powermock/blob/powermock-1.5.4/reflect/src/main/java/org/powermock/reflect/internal/WhiteboxImpl.java#L326">WhiteboxImpl</a>.
|
||||
*
|
||||
* @author Rob Winch
|
||||
*/
|
||||
final class StaticFinalReflectionUtils {
|
||||
|
||||
/**
|
||||
* Used to support setting static fields that are final using Java's Unsafe. If the
|
||||
* field is not static final, use
|
||||
* {@link org.springframework.test.util.ReflectionTestUtils}.
|
||||
* @param field the field to set
|
||||
* @param newValue the new value
|
||||
*/
|
||||
static void setField(final Field field, final Object newValue) {
|
||||
try {
|
||||
field.setAccessible(true);
|
||||
int fieldModifiersMask = field.getModifiers();
|
||||
boolean isFinalModifierPresent = (fieldModifiersMask & Modifier.FINAL) == Modifier.FINAL;
|
||||
if (isFinalModifierPresent) {
|
||||
AccessController.doPrivileged(new PrivilegedAction<Object>() {
|
||||
@Override
|
||||
public Object run() {
|
||||
try {
|
||||
Unsafe unsafe = UnsafeUtils.getUnsafe();
|
||||
long offset = unsafe.staticFieldOffset(field);
|
||||
Object base = unsafe.staticFieldBase(field);
|
||||
setFieldUsingUnsafe(base, field.getType(), offset, newValue, unsafe);
|
||||
return null;
|
||||
}
|
||||
catch (Throwable thrown) {
|
||||
throw new RuntimeException(thrown);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
field.set(null, newValue);
|
||||
}
|
||||
}
|
||||
catch (SecurityException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
catch (IllegalAccessException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
catch (IllegalArgumentException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static void setFieldUsingUnsafe(Object base, Class type, long offset, Object newValue, Unsafe unsafe) {
|
||||
if (type == Integer.TYPE) {
|
||||
unsafe.putInt(base, offset, ((Integer) newValue));
|
||||
}
|
||||
else if (type == Short.TYPE) {
|
||||
unsafe.putShort(base, offset, ((Short) newValue));
|
||||
}
|
||||
else if (type == Long.TYPE) {
|
||||
unsafe.putLong(base, offset, ((Long) newValue));
|
||||
}
|
||||
else if (type == Byte.TYPE) {
|
||||
unsafe.putByte(base, offset, ((Byte) newValue));
|
||||
}
|
||||
else if (type == Boolean.TYPE) {
|
||||
unsafe.putBoolean(base, offset, ((Boolean) newValue));
|
||||
}
|
||||
else if (type == Float.TYPE) {
|
||||
unsafe.putFloat(base, offset, ((Float) newValue));
|
||||
}
|
||||
else if (type == Double.TYPE) {
|
||||
unsafe.putDouble(base, offset, ((Double) newValue));
|
||||
}
|
||||
else if (type == Character.TYPE) {
|
||||
unsafe.putChar(base, offset, ((Character) newValue));
|
||||
}
|
||||
else {
|
||||
unsafe.putObject(base, offset, newValue);
|
||||
}
|
||||
}
|
||||
|
||||
private StaticFinalReflectionUtils() {
|
||||
}
|
||||
|
||||
}
|
||||
@@ -526,35 +526,47 @@ public class BCrypt {
|
||||
* @param safety bit 16 is set when the safety measure is requested
|
||||
* @return an array containing the binary hashed password
|
||||
*/
|
||||
private byte[] crypt_raw(byte password[], byte salt[], int log_rounds, boolean sign_ext_bug, int safety) {
|
||||
int rounds, i, j;
|
||||
private byte[] crypt_raw(byte password[], byte salt[], int log_rounds, boolean sign_ext_bug, int safety,
|
||||
boolean for_check) {
|
||||
int cdata[] = bf_crypt_ciphertext.clone();
|
||||
int clen = cdata.length;
|
||||
byte ret[];
|
||||
|
||||
long rounds;
|
||||
if (log_rounds < 4 || log_rounds > 31) {
|
||||
throw new IllegalArgumentException("Bad number of rounds");
|
||||
if (!for_check) {
|
||||
throw new IllegalArgumentException("Bad number of rounds");
|
||||
}
|
||||
if (log_rounds != 0) {
|
||||
throw new IllegalArgumentException("Bad number of rounds");
|
||||
}
|
||||
rounds = 0;
|
||||
}
|
||||
rounds = 1 << log_rounds;
|
||||
else {
|
||||
rounds = roundsForLogRounds(log_rounds);
|
||||
if (rounds < 16 || rounds > 2147483648L) {
|
||||
throw new IllegalArgumentException("Bad number of rounds");
|
||||
}
|
||||
}
|
||||
|
||||
if (salt.length != BCRYPT_SALT_LEN) {
|
||||
throw new IllegalArgumentException("Bad salt length");
|
||||
}
|
||||
|
||||
init_key();
|
||||
ekskey(salt, password, sign_ext_bug, safety);
|
||||
for (i = 0; i < rounds; i++) {
|
||||
for (int i = 0; i < rounds; i++) {
|
||||
key(password, sign_ext_bug, safety);
|
||||
key(salt, false, safety);
|
||||
}
|
||||
|
||||
for (i = 0; i < 64; i++) {
|
||||
for (j = 0; j < (clen >> 1); j++) {
|
||||
for (int i = 0; i < 64; i++) {
|
||||
for (int j = 0; j < (clen >> 1); j++) {
|
||||
encipher(cdata, j << 1);
|
||||
}
|
||||
}
|
||||
|
||||
ret = new byte[clen * 4];
|
||||
for (i = 0, j = 0; i < clen; i++) {
|
||||
byte[] ret = new byte[clen * 4];
|
||||
for (int i = 0, j = 0; i < clen; i++) {
|
||||
ret[j++] = (byte) ((cdata[i] >> 24) & 0xff);
|
||||
ret[j++] = (byte) ((cdata[i] >> 16) & 0xff);
|
||||
ret[j++] = (byte) ((cdata[i] >> 8) & 0xff);
|
||||
@@ -563,6 +575,10 @@ public class BCrypt {
|
||||
return ret;
|
||||
}
|
||||
|
||||
private static String hashpwforcheck(byte[] passwordb, String salt) {
|
||||
return hashpw(passwordb, salt, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hash a password using the OpenBSD bcrypt scheme
|
||||
* @param password the password to hash
|
||||
@@ -584,6 +600,10 @@ public class BCrypt {
|
||||
* @return the hashed password
|
||||
*/
|
||||
public static String hashpw(byte passwordb[], String salt) {
|
||||
return hashpw(passwordb, salt, false);
|
||||
}
|
||||
|
||||
private static String hashpw(byte passwordb[], String salt, boolean for_check) {
|
||||
BCrypt B;
|
||||
String real_salt;
|
||||
byte saltb[], hashed[];
|
||||
@@ -633,7 +653,7 @@ public class BCrypt {
|
||||
}
|
||||
|
||||
B = new BCrypt();
|
||||
hashed = B.crypt_raw(passwordb, saltb, rounds, minor == 'x', minor == 'a' ? 0x10000 : 0);
|
||||
hashed = B.crypt_raw(passwordb, saltb, rounds, minor == 'x', minor == 'a' ? 0x10000 : 0, for_check);
|
||||
|
||||
rs.append("$2");
|
||||
if (minor >= 'a') {
|
||||
@@ -740,7 +760,8 @@ public class BCrypt {
|
||||
* @return true if the passwords match, false otherwise
|
||||
*/
|
||||
public static boolean checkpw(String plaintext, String hashed) {
|
||||
return equalsNoEarlyReturn(hashed, hashpw(plaintext, hashed));
|
||||
byte[] passwordb = plaintext.getBytes(StandardCharsets.UTF_8);
|
||||
return equalsNoEarlyReturn(hashed, hashpwforcheck(passwordb, hashed));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -751,7 +772,7 @@ public class BCrypt {
|
||||
* @since 5.3
|
||||
*/
|
||||
public static boolean checkpw(byte[] passwordb, String hashed) {
|
||||
return equalsNoEarlyReturn(hashed, hashpw(passwordb, hashed));
|
||||
return equalsNoEarlyReturn(hashed, hashpwforcheck(passwordb, hashed));
|
||||
}
|
||||
|
||||
static boolean equalsNoEarlyReturn(String a, String b) {
|
||||
|
||||
+14
@@ -208,4 +208,18 @@ public class BCryptPasswordEncoderTests {
|
||||
assertThatIllegalArgumentException().isThrownBy(() -> encoder.matches(null, "does-not-matter"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void upgradeWhenNoRoundsThenTrue() {
|
||||
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
|
||||
assertThat(encoder.upgradeEncoding("$2a$00$9N8N35BVs5TLqGL3pspAte5OWWA2a2aZIs.EGp7At7txYakFERMue")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkWhenNoRoundsThenTrue() {
|
||||
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
|
||||
assertThat(encoder.matches("password", "$2a$00$9N8N35BVs5TLqGL3pspAte5OWWA2a2aZIs.EGp7At7txYakFERMue"))
|
||||
.isTrue();
|
||||
assertThat(encoder.matches("wrong", "$2a$00$9N8N35BVs5TLqGL3pspAte5OWWA2a2aZIs.EGp7At7txYakFERMue")).isFalse();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -456,4 +456,11 @@ public class BCryptTests {
|
||||
assertThat(BCrypt.equalsNoEarlyReturn("test", "pass")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void checkpwWhenZeroRoundsThenMatches() {
|
||||
String password = "$2a$00$9N8N35BVs5TLqGL3pspAte5OWWA2a2aZIs.EGp7At7txYakFERMue";
|
||||
assertThat(BCrypt.checkpw("password", password)).isTrue();
|
||||
assertThat(BCrypt.checkpw("wrong", password)).isFalse();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ apply plugin: 'io.spring.convention.spring-module'
|
||||
dependencies {
|
||||
management platform(project(":spring-security-dependencies"))
|
||||
api project(':spring-security-core')
|
||||
api 'javax.xml.bind:jaxb-api'
|
||||
api 'jakarta.xml.bind:jakarta.xml.bind-api'
|
||||
api 'org.springframework.data:spring-data-commons'
|
||||
api 'org.springframework:spring-core'
|
||||
|
||||
|
||||
+22
-2
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2019 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -17,7 +17,13 @@
|
||||
package org.springframework.security.data.repository.query;
|
||||
|
||||
import org.springframework.data.spel.spi.EvaluationContextExtension;
|
||||
import org.springframework.security.access.PermissionEvaluator;
|
||||
import org.springframework.security.access.expression.DenyAllPermissionEvaluator;
|
||||
import org.springframework.security.access.expression.SecurityExpressionRoot;
|
||||
import org.springframework.security.access.hierarchicalroles.NullRoleHierarchy;
|
||||
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
|
||||
import org.springframework.security.authentication.AuthenticationTrustResolver;
|
||||
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
@@ -77,12 +83,21 @@ import org.springframework.security.core.context.SecurityContextHolder;
|
||||
* it.
|
||||
*
|
||||
* @author Rob Winch
|
||||
* @author Evgeniy Cheban
|
||||
* @since 4.0
|
||||
*/
|
||||
public class SecurityEvaluationContextExtension implements EvaluationContextExtension {
|
||||
|
||||
private Authentication authentication;
|
||||
|
||||
private AuthenticationTrustResolver trustResolver = new AuthenticationTrustResolverImpl();
|
||||
|
||||
private RoleHierarchy roleHierarchy = new NullRoleHierarchy();
|
||||
|
||||
private PermissionEvaluator permissionEvaluator = new DenyAllPermissionEvaluator();
|
||||
|
||||
private String defaultRolePrefix = "ROLE_";
|
||||
|
||||
/**
|
||||
* Creates a new instance that uses the current {@link Authentication} found on the
|
||||
* {@link org.springframework.security.core.context.SecurityContextHolder}.
|
||||
@@ -106,8 +121,13 @@ public class SecurityEvaluationContextExtension implements EvaluationContextExte
|
||||
@Override
|
||||
public SecurityExpressionRoot getRootObject() {
|
||||
Authentication authentication = getAuthentication();
|
||||
return new SecurityExpressionRoot(authentication) {
|
||||
SecurityExpressionRoot root = new SecurityExpressionRoot(authentication) {
|
||||
};
|
||||
root.setTrustResolver(this.trustResolver);
|
||||
root.setRoleHierarchy(this.roleHierarchy);
|
||||
root.setPermissionEvaluator(this.permissionEvaluator);
|
||||
root.setDefaultRolePrefix(this.defaultRolePrefix);
|
||||
return root;
|
||||
}
|
||||
|
||||
private Authentication getAuthentication() {
|
||||
|
||||
+15
-1
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2002-2016 the original author or authors.
|
||||
* Copyright 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -20,7 +20,10 @@ import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.security.access.expression.DenyAllPermissionEvaluator;
|
||||
import org.springframework.security.access.expression.SecurityExpressionRoot;
|
||||
import org.springframework.security.access.hierarchicalroles.NullRoleHierarchy;
|
||||
import org.springframework.security.authentication.AuthenticationTrustResolverImpl;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
|
||||
@@ -69,6 +72,17 @@ public class SecurityEvaluationContextExtensionTests {
|
||||
assertThat(getRoot().getAuthentication()).isSameAs(explicit);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getRootObjectWhenAdditionalFieldsNotSetThenVerifyDefaults() {
|
||||
TestingAuthenticationToken explicit = new TestingAuthenticationToken("explicit", "password", "ROLE_EXPLICIT");
|
||||
this.securityExtension = new SecurityEvaluationContextExtension(explicit);
|
||||
SecurityExpressionRoot root = getRoot();
|
||||
assertThat(root).extracting("trustResolver").isInstanceOf(AuthenticationTrustResolverImpl.class);
|
||||
assertThat(root).extracting("roleHierarchy").isInstanceOf(NullRoleHierarchy.class);
|
||||
assertThat(root).extracting("permissionEvaluator").isInstanceOf(DenyAllPermissionEvaluator.class);
|
||||
assertThat(root).extracting("defaultRolePrefix").isEqualTo("ROLE_");
|
||||
}
|
||||
|
||||
private SecurityExpressionRoot getRoot() {
|
||||
return this.securityExtension.getRootObject();
|
||||
}
|
||||
|
||||
+20
-20
@@ -8,15 +8,15 @@ javaPlatform {
|
||||
|
||||
dependencies {
|
||||
api platform("org.springframework:spring-framework-bom:$springFrameworkVersion")
|
||||
api platform("io.projectreactor:reactor-bom:2020.0.14")
|
||||
api platform("io.rsocket:rsocket-bom:1.1.1")
|
||||
api platform("io.projectreactor:reactor-bom:2020.0.24")
|
||||
api platform("io.rsocket:rsocket-bom:1.1.3")
|
||||
api platform("org.junit:junit-bom:5.8.2")
|
||||
api platform("org.springframework.data:spring-data-bom:2021.1.0")
|
||||
api platform("org.springframework.data:spring-data-bom:2021.1.8")
|
||||
api platform("org.jetbrains.kotlin:kotlin-bom:$kotlinVersion")
|
||||
api platform("org.jetbrains.kotlinx:kotlinx-coroutines-bom:1.5.2")
|
||||
api platform("com.fasterxml.jackson:jackson-bom:2.13.1")
|
||||
api platform("com.fasterxml.jackson:jackson-bom:2.13.4.20221012")
|
||||
constraints {
|
||||
api "ch.qos.logback:logback-classic:1.2.9"
|
||||
api "ch.qos.logback:logback-classic:1.2.11"
|
||||
api "com.google.inject:guice:3.0"
|
||||
api "com.nimbusds:nimbus-jose-jwt:9.14"
|
||||
api "com.nimbusds:oauth2-oidc-sdk:9.19"
|
||||
@@ -25,14 +25,15 @@ dependencies {
|
||||
api "com.unboundid:unboundid-ldapsdk:4.0.14"
|
||||
api "commons-codec:commons-codec:1.15"
|
||||
api "commons-collections:commons-collections:3.2.2"
|
||||
api "commons-logging:commons-logging:1.2"
|
||||
api "io.mockk:mockk:1.12.1"
|
||||
api "io.mockk:mockk:1.12.4"
|
||||
api "io.projectreactor.tools:blockhound:1.0.6.RELEASE"
|
||||
api "javax.annotation:jsr250-api:1.0"
|
||||
api "javax.servlet.jsp.jstl:javax.servlet.jsp.jstl-api:1.2.2"
|
||||
api "javax.servlet.jsp:javax.servlet.jsp-api:2.3.3"
|
||||
api "javax.servlet:javax.servlet-api:4.0.1"
|
||||
api "javax.xml.bind:jaxb-api:2.3.1"
|
||||
api "jakarta.inject:jakarta.inject-api:1.0.5"
|
||||
api "jakarta.annotation:jakarta.annotation-api:1.3.5"
|
||||
api "jakarta.servlet.jsp.jstl:jakarta.servlet.jsp.jstl-api:1.2.7"
|
||||
api "jakarta.servlet.jsp:jakarta.servlet.jsp-api:2.3.6"
|
||||
api "jakarta.servlet:jakarta.servlet-api:4.0.4"
|
||||
api "jakarta.transaction:jakarta.transaction-api:1.3.3"
|
||||
api "jakarta.xml.bind:jakarta.xml.bind-api:2.3.3"
|
||||
api "ldapsdk:ldapsdk:4.1"
|
||||
api "net.sf.ehcache:ehcache:2.10.9.2"
|
||||
api "net.sourceforge.htmlunit:htmlunit:2.54.0"
|
||||
@@ -49,11 +50,11 @@ dependencies {
|
||||
api "org.assertj:assertj-core:3.21.0"
|
||||
api "org.bouncycastle:bcpkix-jdk15on:1.69"
|
||||
api "org.bouncycastle:bcprov-jdk15on:1.69"
|
||||
api "org.eclipse.jetty:jetty-server:9.4.44.v20210927"
|
||||
api "org.eclipse.jetty:jetty-servlet:9.4.44.v20210927"
|
||||
api "org.eclipse.jetty:jetty-server:9.4.49.v20220914"
|
||||
api "org.eclipse.jetty:jetty-servlet:9.4.49.v20220914"
|
||||
api "org.eclipse.persistence:javax.persistence:2.2.1"
|
||||
api "org.hamcrest:hamcrest:2.2"
|
||||
api "org.hibernate:hibernate-entitymanager:5.6.3.Final"
|
||||
api "org.hibernate:hibernate-entitymanager:5.6.12.Final"
|
||||
api "org.hsqldb:hsqldb:2.6.1"
|
||||
api "org.jasig.cas.client:cas-client-core:3.6.4"
|
||||
api "org.mockito:mockito-core:3.12.4"
|
||||
@@ -67,11 +68,10 @@ dependencies {
|
||||
api "org.seleniumhq.selenium:htmlunit-driver:2.54.0"
|
||||
api "org.seleniumhq.selenium:selenium-java:3.141.59"
|
||||
api "org.seleniumhq.selenium:selenium-support:3.141.59"
|
||||
api "org.skyscreamer:jsonassert:1.5.0"
|
||||
api "org.slf4j:jcl-over-slf4j:1.7.32"
|
||||
api "org.slf4j:log4j-over-slf4j:1.7.32"
|
||||
api "org.slf4j:slf4j-api:1.7.32"
|
||||
api "org.springframework.ldap:spring-ldap-core:2.3.5.RELEASE"
|
||||
api "org.skyscreamer:jsonassert:1.5.1"
|
||||
api "org.slf4j:log4j-over-slf4j:1.7.36"
|
||||
api "org.slf4j:slf4j-api:1.7.36"
|
||||
api "org.springframework.ldap:spring-ldap-core:2.3.8.RELEASE"
|
||||
api "org.synchronoss.cloud:nio-multipart-parser:1.1.0"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
/package-lock.json
|
||||
/node_modules/
|
||||
@@ -0,0 +1,53 @@
|
||||
'use strict'
|
||||
|
||||
/* Copyright (c) 2002-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
const { promises: fsp } = require('fs')
|
||||
const ospath = require('path')
|
||||
|
||||
/**
|
||||
* Rewrites local content sources to support the use of linked worktrees.
|
||||
*
|
||||
* @author Dan Allen <dan@opendevise.com>
|
||||
*/
|
||||
module.exports.register = function () {
|
||||
this.once('playbookBuilt', async ({ playbook }) => {
|
||||
const expandPath = this.require('@antora/expand-path-helper')
|
||||
for (const contentSource of playbook.content.sources) {
|
||||
const { url, branches } = contentSource
|
||||
if (url.charAt() !== '.') continue
|
||||
const absdir = expandPath(url, { dot: playbook.dir })
|
||||
const gitfile = ospath.join(absdir, '.git')
|
||||
if (await fsp.stat(gitfile).then((stat) => !stat.isDirectory(), () => false)) {
|
||||
const worktreeGitdir = await fsp.readFile(gitfile, 'utf8')
|
||||
.then((contents) => contents.trimRight().substr(8))
|
||||
const worktreeBranch = await fsp.readFile(ospath.join(worktreeGitdir, 'HEAD'), 'utf8')
|
||||
.then((contents) => contents.trimRight().replace(/^ref: (?:refs\/heads\/)?/, ''))
|
||||
const reldir = ospath.relative(
|
||||
playbook.dir,
|
||||
await fsp.readFile(ospath.join(worktreeGitdir, 'commondir'), 'utf8')
|
||||
.then((contents) => {
|
||||
const gitdir = ospath.join(worktreeGitdir, contents.trimRight())
|
||||
return ospath.basename(gitdir) === '.git' ? ospath.dirname(gitdir) : gitdir
|
||||
})
|
||||
)
|
||||
contentSource.url = reldir ? `.${ospath.sep}${reldir}` : '.'
|
||||
if (!branches) continue
|
||||
contentSource.branches = (branches.constructor === Array ? branches : [branches])
|
||||
.map((pattern) => pattern.replaceAll('HEAD', worktreeBranch))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
site:
|
||||
title: Spring Security
|
||||
url: https://docs.spring.io/spring-security/reference/
|
||||
asciidoc:
|
||||
attributes:
|
||||
page-pagination: true
|
||||
content:
|
||||
sources:
|
||||
- url: https://github.com/spring-io/spring-generated-docs
|
||||
branches: [spring-projects/spring-security/*]
|
||||
- url: https://github.com/spring-projects/spring-security
|
||||
branches: [main,5.6.x]
|
||||
start_path: docs
|
||||
urls:
|
||||
latest_version_segment_strategy: redirect:to
|
||||
latest_version_segment: ''
|
||||
redirect_facility: httpd
|
||||
ui:
|
||||
bundle:
|
||||
url: https://github.com/spring-io/antora-ui-spring/releases/download/latest/ui-bundle.zip
|
||||
snapshot: true
|
||||
|
||||
pipeline:
|
||||
extensions:
|
||||
- require: ./antora/extensions/major-minor-segment.js
|
||||
- require: ./antora/extensions/root-component-name.js
|
||||
+11
-2
@@ -1,3 +1,12 @@
|
||||
name: ROOT
|
||||
version: '5.6.1'
|
||||
prerelease: '-SNAPSHOT'
|
||||
version: true
|
||||
title: Documentation
|
||||
nav:
|
||||
- modules/ROOT/nav.adoc
|
||||
ext:
|
||||
collector:
|
||||
run:
|
||||
command: gradlew -q -PbuildSrc.skipTests=true "-Dorg.gradle.jvmargs=-Xmx3g -XX:+HeapDumpOnOutOfMemoryError" :spring-security-docs:generateAntora
|
||||
local: true
|
||||
scan:
|
||||
dir: ./build/generateAntora
|
||||
|
||||
@@ -1,200 +0,0 @@
|
||||
// https://gitlab.com/antora/antora/-/issues/132#note_712132072
|
||||
'use strict'
|
||||
|
||||
const { posix: path } = require('path')
|
||||
|
||||
module.exports.register = (pipeline, { config }) => {
|
||||
pipeline.on('contentClassified', ({ contentCatalog }) => {
|
||||
contentCatalog.getComponents().forEach(component => {
|
||||
const componentName = component.name;
|
||||
const generationToVersion = new Map();
|
||||
component.versions.forEach(version => {
|
||||
const generation = getGeneration(version.version);
|
||||
const original = generationToVersion.get(generation);
|
||||
if (original === undefined || (original.prerelease && !version.prerelease)) {
|
||||
generationToVersion.set(generation, version);
|
||||
}
|
||||
});
|
||||
|
||||
const versionToGeneration = Array.from(generationToVersion.entries()).reduce((acc, entry) => {
|
||||
const [ generation, version ] = entry;
|
||||
acc.set(version.version, generation);
|
||||
return acc;
|
||||
}, new Map());
|
||||
|
||||
contentCatalog.findBy({ component: componentName }).forEach((file) => {
|
||||
const candidateVersion = file.src.version;
|
||||
if (versionToGeneration.has(candidateVersion)) {
|
||||
const generation = versionToGeneration.get(candidateVersion);
|
||||
if (file.out) {
|
||||
if (file.out) {
|
||||
file.out.dirname = file.out.dirname.replace(candidateVersion, generation)
|
||||
file.out.path = file.out.path.replace(candidateVersion, generation);
|
||||
}
|
||||
}
|
||||
if (file.pub) {
|
||||
file.pub.url = file.pub.url.replace(candidateVersion, generation)
|
||||
}
|
||||
}
|
||||
});
|
||||
versionToGeneration.forEach((generation, mappedVersion) => {
|
||||
contentCatalog.getComponent(componentName).versions.filter(version => version.version === mappedVersion).forEach((version) => {
|
||||
version.url = version.url.replace(mappedVersion, generation);
|
||||
})
|
||||
const symbolicVersionAlias = createSymbolicVersionAlias(
|
||||
componentName,
|
||||
mappedVersion,
|
||||
generation,
|
||||
'redirect:to'
|
||||
)
|
||||
symbolicVersionAlias.src.version = generation;
|
||||
contentCatalog.addFile(symbolicVersionAlias);
|
||||
});
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function createSymbolicVersionAlias (component, version, symbolicVersionSegment, strategy) {
|
||||
if (symbolicVersionSegment == null || symbolicVersionSegment === version) return
|
||||
const family = 'alias'
|
||||
const baseVersionAliasSrc = { component, module: 'ROOT', family, relative: '', basename: '', stem: '', extname: '' }
|
||||
const symbolicVersionAliasSrc = Object.assign({}, baseVersionAliasSrc, { version: symbolicVersionSegment })
|
||||
const symbolicVersionAlias = {
|
||||
src: symbolicVersionAliasSrc,
|
||||
pub: computePub(
|
||||
symbolicVersionAliasSrc,
|
||||
computeOut(symbolicVersionAliasSrc, family, symbolicVersionSegment),
|
||||
family
|
||||
),
|
||||
}
|
||||
const originalVersionAliasSrc = Object.assign({}, baseVersionAliasSrc, { version })
|
||||
const originalVersionSegment = computeVersionSegment(component, version, 'original')
|
||||
const originalVersionAlias = {
|
||||
src: originalVersionAliasSrc,
|
||||
pub: computePub(
|
||||
originalVersionAliasSrc,
|
||||
computeOut(originalVersionAliasSrc, family, originalVersionSegment),
|
||||
family
|
||||
),
|
||||
}
|
||||
if (strategy === 'redirect:to') {
|
||||
originalVersionAlias.out = undefined
|
||||
originalVersionAlias.rel = symbolicVersionAlias
|
||||
return originalVersionAlias
|
||||
} else {
|
||||
symbolicVersionAlias.out = undefined
|
||||
symbolicVersionAlias.rel = originalVersionAlias
|
||||
return symbolicVersionAlias
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function computeOut (src, family, version, htmlUrlExtensionStyle) {
|
||||
let { component, module: module_, basename, extname, relative, stem } = src
|
||||
if (module_ === 'ROOT') module_ = ''
|
||||
let indexifyPathSegment = ''
|
||||
let familyPathSegment = ''
|
||||
|
||||
if (family === 'page') {
|
||||
if (stem !== 'index' && htmlUrlExtensionStyle === 'indexify') {
|
||||
basename = 'index.html'
|
||||
indexifyPathSegment = stem
|
||||
} else if (extname === '.adoc') {
|
||||
basename = stem + '.html'
|
||||
}
|
||||
} else if (family === 'image') {
|
||||
familyPathSegment = '_images'
|
||||
} else if (family === 'attachment') {
|
||||
familyPathSegment = '_attachments'
|
||||
}
|
||||
const modulePath = path.join(component, version, module_)
|
||||
const dirname = path.join(modulePath, familyPathSegment, path.dirname(relative), indexifyPathSegment)
|
||||
const path_ = path.join(dirname, basename)
|
||||
const moduleRootPath = path.relative(dirname, modulePath) || '.'
|
||||
const rootPath = path.relative(dirname, '') || '.'
|
||||
|
||||
return { dirname, basename, path: path_, moduleRootPath, rootPath }
|
||||
}
|
||||
|
||||
function computePub (src, out, family, version, htmlUrlExtensionStyle) {
|
||||
const pub = {}
|
||||
let url
|
||||
if (family === 'nav') {
|
||||
const urlSegments = version ? [src.component, version] : [src.component]
|
||||
if (src.module && src.module !== 'ROOT') urlSegments.push(src.module)
|
||||
// an artificial URL used for resolving page references in navigation model
|
||||
url = '/' + urlSegments.join('/') + '/'
|
||||
pub.moduleRootPath = '.'
|
||||
} else if (family === 'page') {
|
||||
const urlSegments = out.path.split('/')
|
||||
const lastUrlSegmentIdx = urlSegments.length - 1
|
||||
if (htmlUrlExtensionStyle === 'drop') {
|
||||
// drop just the .html extension or, if the filename is index.html, the whole segment
|
||||
const lastUrlSegment = urlSegments[lastUrlSegmentIdx]
|
||||
urlSegments[lastUrlSegmentIdx] =
|
||||
lastUrlSegment === 'index.html' ? '' : lastUrlSegment.substr(0, lastUrlSegment.length - 5)
|
||||
} else if (htmlUrlExtensionStyle === 'indexify') {
|
||||
urlSegments[lastUrlSegmentIdx] = ''
|
||||
}
|
||||
url = '/' + urlSegments.join('/')
|
||||
} else {
|
||||
url = '/' + out.path
|
||||
if (family === 'alias' && !src.relative.length) pub.splat = true
|
||||
}
|
||||
|
||||
pub.url = ~url.indexOf(' ') ? url.replace(SPACE_RX, '%20') : url
|
||||
|
||||
if (out) {
|
||||
pub.moduleRootPath = out.moduleRootPath
|
||||
pub.rootPath = out.rootPath
|
||||
}
|
||||
|
||||
return pub
|
||||
}
|
||||
|
||||
function computeVersionSegment (name, version, mode) {
|
||||
if (mode === 'original') return !version || version === 'master' ? '' : version
|
||||
const strategy = this.latestVersionUrlSegmentStrategy
|
||||
// NOTE: special exception; revisit in Antora 3
|
||||
if (!version || version === 'master') {
|
||||
if (mode !== 'alias') return ''
|
||||
if (strategy === 'redirect:to') return
|
||||
}
|
||||
if (strategy === 'redirect:to' || strategy === (mode === 'alias' ? 'redirect:from' : 'replace')) {
|
||||
const component = this.getComponent(name)
|
||||
const componentVersion = component && this.getComponentVersion(component, version)
|
||||
if (componentVersion) {
|
||||
const segment =
|
||||
componentVersion === component.latest
|
||||
? this.latestVersionUrlSegment
|
||||
: componentVersion === component.latestPrerelease
|
||||
? this.latestPrereleaseVersionUrlSegment
|
||||
: undefined
|
||||
return segment == null ? version : segment
|
||||
}
|
||||
}
|
||||
return version
|
||||
}
|
||||
|
||||
function getGeneration(version) {
|
||||
if (!version) return version;
|
||||
const firstIndex = version.indexOf('.')
|
||||
if (firstIndex < 0) {
|
||||
return version;
|
||||
}
|
||||
const secondIndex = version.indexOf('.', firstIndex + 1);
|
||||
const result = version.substr(0, secondIndex);
|
||||
return result;
|
||||
}
|
||||
|
||||
function out(args) {
|
||||
console.log(JSON.stringify(args, no_data, 2));
|
||||
}
|
||||
|
||||
|
||||
function no_data(key, value) {
|
||||
if (key == "data" || key == "files") {
|
||||
return value ? "__data__" : value;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
@@ -1,40 +0,0 @@
|
||||
// https://gitlab.com/antora/antora/-/issues/132#note_712132072
|
||||
'use strict'
|
||||
|
||||
const { posix: path } = require('path')
|
||||
|
||||
module.exports.register = (pipeline, { config }) => {
|
||||
pipeline.on('contentClassified', ({ contentCatalog }) => {
|
||||
const rootComponentName = config.rootComponentName || 'ROOT'
|
||||
const rootComponentNameLength = rootComponentName.length
|
||||
contentCatalog.findBy({ component: rootComponentName }).forEach((file) => {
|
||||
if (file.out) {
|
||||
file.out.dirname = file.out.dirname.substr(rootComponentNameLength)
|
||||
file.out.path = file.out.path.substr(rootComponentNameLength + 1)
|
||||
file.out.rootPath = fixPath(file.out.rootPath)
|
||||
}
|
||||
if (file.pub) {
|
||||
file.pub.url = file.pub.url.substr(rootComponentNameLength + 1)
|
||||
if (file.pub.rootPath) {
|
||||
file.pub.rootPath = fixPath(file.pub.rootPath)
|
||||
}
|
||||
}
|
||||
if (file.rel) {
|
||||
if (file.rel.pub) {
|
||||
file.rel.pub.url = file.rel.pub.url.substr(rootComponentNameLength + 1)
|
||||
file.rel.pub.rootPath = fixPath(file.rel.pub.rootPath);
|
||||
}
|
||||
}
|
||||
})
|
||||
const rootComponent = contentCatalog.getComponent(rootComponentName)
|
||||
rootComponent?.versions?.forEach((version) => {
|
||||
version.url = version.url.substr(rootComponentName.length + 1)
|
||||
})
|
||||
// const siteStartPage = contentCatalog.getById({ component: '', version: '', module: '', family: 'alias', relative: 'index.adoc' })
|
||||
// if (siteStartPage) delete siteStartPage.out
|
||||
})
|
||||
|
||||
function fixPath(path) {
|
||||
return path.split('/').slice(1).join('/') || '.'
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user