├── .dependabot └── config.yml ├── .editorconfig ├── .gitattributes ├── .gitignore ├── .gitmodules ├── .travis.yml ├── CHANGELOG.md ├── LICENSE.txt ├── README.md ├── appveyor.yml ├── build.gradle ├── gradle.properties ├── gradle ├── cdeliveryboy-release.gradle ├── gpg-key.asc.enc ├── install-jdk.sh └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src ├── funcTest ├── groovy │ └── io │ │ └── codearte │ │ └── gradle │ │ └── nexus │ │ └── functional │ │ ├── BaseNexusStagingFunctionalSpec.groovy │ │ ├── FunctionalSpecHelperTrait.groovy │ │ ├── GradleVersionFuncSpec.groovy │ │ ├── MockedFunctionalSpec.groovy │ │ ├── MockedResponseErrorHandlingSpec.groovy │ │ ├── PasswordFunctionalSpec.groovy │ │ └── e2e │ │ ├── BasicPublishSmokeE2ESpec.groovy │ │ ├── BasicSmokeE2ESpec.groovy │ │ ├── E2ESpecConstants.groovy │ │ ├── E2ESpecHelperTrait.groovy │ │ └── ExploratoryE2ESpec.groovy └── resources │ └── sampleProjects │ └── uploadArchives │ ├── build-property.gradle │ ├── build.gradle │ ├── gradle.properties │ ├── settings.gradle │ └── src │ ├── main │ └── groovy │ │ └── Library.groovy │ └── test │ └── groovy │ └── LibraryTest.groovy ├── main ├── groovy │ └── io │ │ └── codearte │ │ └── gradle │ │ └── nexus │ │ ├── ApplyOnlyOnRootProjectEnforcer.groovy │ │ ├── BaseStagingTask.groovy │ │ ├── CloseRepositoryTask.groovy │ │ ├── CreateRepositoryTask.groovy │ │ ├── GetStagingProfileTask.groovy │ │ ├── GradleUtil.groovy │ │ ├── GradleVersionEnforcer.groovy │ │ ├── NexusStagingExtension.groovy │ │ ├── NexusStagingPlugin.groovy │ │ ├── ReleaseRepositoryTask.groovy │ │ ├── exception │ │ ├── RepositoryInTransitionException.groovy │ │ ├── UnexpectedRepositoryState.groovy │ │ └── UnsupportedRepositoryState.groovy │ │ ├── infra │ │ ├── NexusHttpResponseException.groovy │ │ ├── NexusStagingException.groovy │ │ ├── SimplifiedHttpJsonRestClient.groovy │ │ ├── WrongNumberOfRepositories.groovy │ │ └── WrongNumberOfStagingProfiles.groovy │ │ ├── legacy │ │ ├── NexusUploadStagingPlugin.groovy │ │ └── PointUploadArchivesToExplicitRepositoryTask.groovy │ │ └── logic │ │ ├── AbstractRepositoryTransitioner.groovy │ │ ├── BaseOperationExecutor.groovy │ │ ├── OperationRetrier.groovy │ │ ├── RepositoryCloser.groovy │ │ ├── RepositoryCreator.groovy │ │ ├── RepositoryDropper.groovy │ │ ├── RepositoryFetcher.groovy │ │ ├── RepositoryReleaser.groovy │ │ ├── RepositoryState.groovy │ │ ├── RepositoryStateFetcher.groovy │ │ ├── RepositoryTransition.groovy │ │ ├── RetryingRepositoryTransitioner.groovy │ │ └── StagingProfileFetcher.groovy └── resources │ └── META-INF │ └── gradle-plugins │ ├── io.codearte.nexus-staging.properties │ └── io.codearte.nexus-upload-staging.properties └── test ├── groovy └── io │ └── codearte │ └── gradle │ └── nexus │ ├── ApplyOnlyOnRootProjectSpec.groovy │ └── logic │ ├── BaseOperationExecutorSpec.groovy │ ├── FetcherResponseTrait.groovy │ ├── OperationRetrierSpec.groovy │ ├── RepositoryCloserSpec.groovy │ ├── RepositoryDropperSpec.groovy │ ├── RepositoryFetcherSpec.groovy │ ├── RepositoryReleaserSpec.groovy │ ├── RepositoryStateFetcherSpec.groovy │ └── StagingProfileFetcherSpec.groovy └── resources ├── .gitkeep └── io └── codearte └── gradle └── nexus └── logic ├── 2stagingProfilesShrunkResponse.json ├── commonStagingRepositoryRequest.json └── openRepositoryShrunkResponse.json /.dependabot/config.yml: -------------------------------------------------------------------------------- 1 | version: 1 2 | 3 | update_configs: 4 | - package_manager: "java:gradle" 5 | directory: "/" 6 | update_schedule: "daily" 7 | target_branch: "devel" 8 | 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig: http://EditorConfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 4 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.json] 13 | indent_size = 2 14 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Allow gradle.bat to retain CRLF 5 | gradlew.bat text eol=crlf 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | build/ 3 | 4 | .idea/ 5 | *.iml 6 | out/ 7 | 8 | .poject 9 | .settings 10 | .classpath 11 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "src/funcTest/resources/sampleProjects/nexus-at-minimal"] 2 | path = src/funcTest/resources/sampleProjects/nexus-at-minimal 3 | url = https://gitlab.com/nexus-at/nexus-at-minimal.git 4 | [submodule "src/funcTest/resources/sampleProjects/nexus-at-minimal-publish"] 5 | path = src/funcTest/resources/sampleProjects/nexus-at-minimal-publish 6 | url = https://gitlab.com/nexus-at/nexus-at-minimal.git 7 | branch = publish 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: groovy 2 | 3 | install: 4 | # Secrets (including encrypted PGP key) are not available in PR builds - skip release configuration to do not fail build 5 | # Also skip on MacOS as there is problem with gpg1 and secring.gpg 6 | - | 7 | if [[ "$TRAVIS_SECURE_ENV_VARS" == "true" && "$TRAVIS_OS_NAME" == "linux" ]]; then 8 | # There is problem with aborting build on command in if failure - http://steven.casagrande.io/articles/travis-ci-and-if-statements/ 9 | openssl aes-256-cbc -K $encrypted_21cd6bba12a0_key -iv $encrypted_21cd6bba12a0_iv -in gradle/gpg-key.asc.enc -d | gpg --fast-import || travis_terminate 1 10 | # Environment variables cannot have "." and it's problematic to pass them with "ORG_GRADLE_PROJECT_" 11 | # In addition GRADLE_OPTS doesn't seem to be passed to Gradle started with Nebula (for AT/E2E tests) 12 | mkdir -p ~/.gradle 13 | echo "signing.keyIdAT=0694F057" >> ~/.gradle/gradle.properties 14 | echo "signing.secretKeyRingFileAT=$HOME/.gnupg/secring.gpg" >> ~/.gradle/gradle.properties 15 | echo "signing.passwordAT=" >> ~/.gradle/gradle.properties 16 | # Enable E2E tests if desired in given build variant and not in PR - #56 17 | # Setting environment variables in "env" seems to be too primite to support it inline 18 | if [ "$NEXUS_AT_ENABLE_E2E_TESTS_IN_VARIANT" == "true" ]; then export NEXUS_AT_ENABLE_E2E_TESTS=true; fi; 19 | # regular releasing (not AT/E2E) 20 | export GRADLE_OPTS='-Dorg.gradle.project.signing.keyId=0694F057 -Dorg.gradle.project.signing.secretKeyRingFile=$HOME/.gnupg/secring.gpg -Dorg.gradle.project.signing.password= -Dorg.gradle.project.gradle.publish.key=$PLUGIN_PORTAL_API_KEY -Dorg.gradle.project.gradle.publish.secret=$PLUGIN_PORTAL_API_SECRET' 21 | fi; 22 | - "export TRAVIS_COMMIT_MSG=$(git log --format=%B -n 1 $TRAVIS_COMMIT)" 23 | - git config user.email "oss@codearte.io" 24 | - git config user.name "Codearte Continuous Delivery Bot" 25 | # Chandler for release notes synchronization 26 | - ruby --version 27 | - rvm install 2.4.1 28 | - ruby --version 29 | - gem install --no-document chandler -v 0.7.0 30 | 31 | script: 32 | - ./gradlew assemble -s 33 | # Temporarily disable animal sniffer due to: https://github.com/mojohaus/animal-sniffer/issues/76 34 | - ./gradlew prepareForCiBuild -s -i -Prelease.disableChecks && ./gradlew funcTest ciBuild -s -i -PcompatibilityDISABLED -Prelease.disableChecks -PpluginPortal -PchangelogSync 35 | 36 | before_cache: 37 | - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock 38 | - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ 39 | - rm -fr $HOME/.gradle/caches/*/scripts/ 40 | - rm -fr $HOME/.gradle/caches/*/scripts-remapped/ 41 | - rm -fr $HOME/.gradle/caches/*/fileHashes/ 42 | - rm -f $HOME/.gradle/caches/*/fileContent/*.lock 43 | - rm -f $HOME/.gradle/caches/*/javaCompile/*.lock 44 | - rm -f $HOME/.gradle/caches/*/executionHistory/*.lock 45 | - rm -f $HOME/.gradle/caches/*/generated-gradle-jars/*.lock 46 | - rm -f $HOME/.gradle/caches/transforms-1/transforms-1.lock 47 | - rm -f $HOME/.gradle/caches/journal-1/file-access.bin 48 | - rm -f $HOME/.gradle/caches/journal-1/*.lock 49 | cache: 50 | directories: 51 | - $HOME/.gradle/caches/ 52 | - $HOME/.gradle/wrapper/ 53 | - $HOME/.rvm/ 54 | 55 | env: 56 | global: 57 | - secure: g95pWjS+RIusyf6t26YnH/HhL6j7aQb3nUpiF0i+9eumn1sT2g8c8X9bdDrncXetAs0+I50cP7jER2IDBG67wbqE2LD2TbCPMA/hJIqtWTatjKT1dUjVL4mxtZ7GsbHZyRM9nUbtOqBBaL+DqzkecO8lHMKB5nej7NdkFT1JO6c= 58 | - secure: dgqSFhIO5JuL6X2HgKt7RJLacl+MSZK4VV9710W0NolPAZlurSgg1vVeoI/4XFpdnt5KSfDUTZL2XS8kU5OgXMqTQcltXusk4fop21m1Z9X3NajqwXbnqDFPbsp2Rsz3B9Cwd5usFW9sdYXtvei5Qa26LnZMM7tIJiI2nxIJEtw= 59 | - secure: DR1o+9sl7HMkkMPNSDw74B7BrenOhBf3fI0M8arNpX+I4zcMV/qX+PSoi+JJksTRRU8KKETXpEvz4dujqPrkvWpQ8WzeuM9cKhX3wz88TNTopptlDkPwNiEl406a8/PIOnuuSs6JfUvVinumOj5kH3BvsPv6DjB+qpWG69EZXNM= 60 | - secure: Qn67rhyd3Zxf66pxXgTkIe4NpGNlY/OURVxNFFf6TR7Jn5iNVbyMgqS3mwZCbURnQhf3MHzfGQo6Y+625p76WeoovuQHg/FBoEUaRlDaMNWZsG6/ANrLrNKlCmhpkuOgCf/hyeo/O/I0c9g7eRb4lbPYfE6Z1pN4THh4Ht3xsHc= 61 | - secure: BbhKYieYFifcqKl+iRTzUyB5huD5Dpxb3c/3iW6+9nFm3C3mAxya/aIV6Ue938noVULPgZmIeCQ+SZ1j/YDuRsw+Ou8g2Dks0EUFgycrEEaKKEjAtv9mB1KD1Y+U21JeyRGxSL+TNby/Zdu6VRtAIdD1Q53LWrKm7ZV1vyKPgzw= 62 | - secure: ZBwOcMY2D+3C+r1R3m5dINJ9G552TyVQgSyvFWVpC1Og8xwg1oCdavORCOn1ulJkkMjeRSr2ghzLB42dx4q0Jp6cu/4pxsvhX7oDVweKdK67UqdeGuq/MHqvYCzOxPNQ0XCdKz4rdxx2zxJsN1NsVcv8hhvzhy3c1+eSleZS7sA= 63 | - secure: SnNdz7Sf1PXnhOshWwwfgN0soiZK989dnUFvcB3jJ8vNXi4c6vOxvy7l/KheyNge0n6eMKBg/cppMCKK5c6firEvnU0G1NlzMZ06KllJmU3kM+UF9FD7Aw0sKU5w715FB+GqlgX8T/cpHkUSo4D8ELT4Qlj5fv475n7ajDSs1b4= 64 | - secure: WbKPmXCd0N3wiwFspIYJhqRp3jagVt0w7lFSUIlnzhN3crt1IubtydgqhhcFYsLb2/iNwibU7mO8cwOYuoi1+SKuEdogAP05ndV81qq93HV8P3zESV75BnG7U1AVBaZjFdi+kYNhSALdWUmbZjcdogINgJMmezbqdL+tPOXEKaA= 65 | matrix: 66 | include: 67 | - os: linux 68 | jdk: openjdk8 69 | env: NEXUS_AT_ENABLE_E2E_TESTS_IN_VARIANT=true 70 | - os: linux 71 | jdk: openjdk11 72 | env: SKIP_RELEASE=true NEXUS_AT_ENABLE_E2E_TESTS_IN_VARIANT=true 73 | - os: linux 74 | jdk: openjdk15 75 | env: SKIP_RELEASE=true 76 | - os: linux 77 | env: SKIP_RELEASE=true JDK="OpenJ9 11" 78 | before_install: 79 | - . gradle/install-jdk.sh -W ~/custom-java --url "https://api.adoptopenjdk.net/v2/binary/releases/openjdk11?openjdk_impl=openj9&os=linux&arch=x64&release=latest&type=jdk&heap_size=normal" 80 | - ./gradlew --version 81 | - os: osx 82 | jdk: openjdk11 83 | env: SKIP_RELEASE=true 84 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # gradle-nexus-staging-plugin changelog 2 | 3 | ## 0.30.0 - 2021-02-26 4 | 5 | - Replace from old unsupported HTTP Builder to OkHttp 4 - [#188](https://github.com/Codearte/gradle-nexus-staging-plugin/pull/188) - PR by [anuraaga](https://github.com/anuraaga) 6 | - **Switch project development to maintenance mode** - see below 7 | - Switch build to Gradle 6.8.3 8 | - Remove deprecated since 0.8.0 `promoteRepository` and `closeAndPromoteRepository` tasks 9 | 10 | **Backward compatibility note**. Due to the internal HTTP client library change, the plugin might start behaving slightly different in certain situations. 11 | 12 | **PROJECT DEVELOPMENT SWITCHED TO THE MAINTENANCE MODE**. To make releasing to Maven Central even easier, I and Marc Phillip (the author of nexus-publish-plugin) combined forces to create a next generation, unified 2-in-1 plugin - [gradle-nexus-publish-plugin](https://github.com/gradle-nexus/publish-plugin/). It is a recommended solution, as our development effort will be put in that new plugin. See my [blog post](https://blog.solidsoft.pl/2021/02/26/unified-gradle-projects-releasing-to-maven-central-in-2021-migration-guide/) and the official [migration guide](https://github.com/gradle-nexus/publish-plugin/wiki/Migration-from-gradle_nexus_staging-plugin---nexus_publish-plugin-duo). 13 | 14 | Thank you for over 5 years of releasing with my plugin! 15 | 16 | ## 0.22.0 - 2020-08-17 17 | 18 | - Change default retrying time to 5 minutes - a value recommended by Sonatype (suggestion by [Mikhail Yakushin](https://github.com/driver733)) 19 | - Switch build to Gradle 6.6 20 | - Bump some dependencies 21 | - Check basic compatibility with Gradle up to 6.6 22 | - CI server sanity check for Java 14 compatibility 23 | 24 | ## 0.21.2 - 2019-12-23 25 | 26 | - Workaround incompatibility with Gradle 6.0 caused by a Gradle bug ([#11466](https://github.com/gradle/gradle/issues/11466)) - [#141](https://github.com/Codearte/gradle-nexus-staging-plugin/issues/141) - PR by [Lars Grefer](https://github.com/larsgrefer) 27 | 28 | ## 0.21.1 - 2019-09-05 29 | 30 | - Fix incompatibility of unsupported releasing with legacy upload task with Gradle 5 31 | - Precise minimal supported Gradle version to 4.9 32 | - Improve error message when applying on root project - [#122](https://github.com/Codearte/gradle-nexus-staging-plugin/pull/122/) - PR by [Patrik Greco](https://github.com/sikevux) 33 | 34 | ## 0.21.0 - 2019-05-19 35 | 36 | - Restore ability to override ban on applying plugin on subprojects - [#116](https://github.com/Codearte/gradle-nexus-staging-plugin/issues/116) 37 | - Workaround Gradle [limitations](https://github.com/gradle/gradle/issues/9386) with precompiled script plugin accessors in Kotlin - [#117](https://github.com/Codearte/gradle-nexus-staging-plugin/pull/117) - contribution by [@Vampire](https://github.com/Vampire) 38 | - Decrease retrying messages verbosity - [#82](https://github.com/Codearte/gradle-nexus-staging-plugin/issues/82) 39 | - Execute e2e tests on Travis also for Java 11 - [#74](https://github.com/Codearte/gradle-nexus-staging-plugin/issues/74) 40 | - Automatically upgrade dependencies with Dependabot - [#79](https://github.com/Codearte/gradle-nexus-staging-plugin/issues/79) 41 | - CI server sanity check for Java 12 compatibility 42 | - CI server sanity check for OpenJ9 11 compatibility 43 | 44 | ## 0.20.0 - 2019-01-05 45 | 46 | - Reuse explicitly created staging repository ID if provided by external plugin - [#77](https://github.com/Codearte/gradle-nexus-staging-plugin/issues/77) 47 | - Fix releasing from Travis - workaround Gradle limitation with Nexus stating repositories with external plugin - [nexus-publish-plugin](https://github.com/marcphilipp/nexus-publish-plugin/) [#76](https://github.com/Codearte/gradle-nexus-staging-plugin/issues/76) 48 | - Raise minimal required Gradle version to 4.8 due to internals modernisation 49 | - Runtime compatibility with Gradle 5.0 and 5.1 50 | 51 | **Deprecation note**. Support for implicitly created staging repositories is deprecated. It has been always problematic, slow and error prone 52 | to find a proper staging repository and the recent [changes](https://github.com/travis-ci/travis-ci/issues/9555) in Travis just emphasised that. 53 | Thanks to the new [nexus-publish-plugin](https://github.com/marcphilipp/nexus-publish-plugin/) plugin by 54 | [Marc Philipp](https://github.com/marcphilipp) which seamlessly integrates with the `gradle-nexus-staging` plugin it should straightforward to use 55 | explicitly created staging repositories in Nexus. At least in a case of using `maven-publish` (`publish...` tasks). If you still use the old `maven` 56 | plugin (the `uploadArchives` task) please refer to [that issue](https://github.com/marcphilipp/nexus-publish-plugin/issues/8). 57 | 58 | The original code has not been removed and *should* still work for the time being (if you don't use Travis), but it is no longer officially 59 | supported (e.g. the E2E tests has been switched to the new approach). 60 | 61 | ## 0.12.0 - 2018-09-29 62 | 63 | - Java 11 compatibility (basic path verified by e2e tests) - [#73](https://github.com/Codearte/gradle-nexus-staging-plugin/issues/73) 64 | - Fix crash on non-upload task named 'uploadArchives' - [#67](https://github.com/szpak/CDeliveryBoy/issues/#67) 65 | - Drop support for Java 7 66 | - Drop Gradle 2.x support (not supported by used plugins) 67 | - Upgrade project dependencies 68 | - Upgrade Gradle to 4.10.2 69 | 70 | ## 0.11.0 - 2017-08-18 71 | 72 | - Fail when applied on non-root project - [#47](https://github.com/Codearte/gradle-nexus-staging-plugin/issues/47) 73 | - Less confusing log output without "info" logging enabled - [#60](https://github.com/Codearte/gradle-nexus-staging-plugin/issues/60) 74 | - Upgrade project dependencies 75 | - Upgrade Gradle to 4.1 (compatibility with Gradle 2.0+ should be still maintained) 76 | 77 | ## 0.10.0 - 2017-08-18 78 | 79 | - Configurable repository description in close/release operation - [#63](https://github.com/Codearte/gradle-nexus-staging-plugin/pull/63) - contribution by [akomakom](https://github.com/akomakom) 80 | 81 | ## 0.9.0 - 2017-06-05 82 | 83 | This release provides no new features or bugfixes. It is focused on acceptance E2E testing and Continuous Delivery 84 | with [CDeliveryBoy](https://travis-ci.org/szpak/CDeliveryBoy). 85 | 86 | - Acceptance tests at Gradle level run before release against real Nexus - [#40](https://github.com/Codearte/gradle-nexus-staging-plugin/issues/40) 87 | - Automatic `CHANGELOG.md` synchronization with GitHub releases - [#52](https://github.com/Codearte/gradle-nexus-staging-plugin/issues/52) 88 | - Switch releasing to Continuous Delivery with CDeliveryBoy - [#54](https://github.com/Codearte/gradle-nexus-staging-plugin/issues/54) 89 | 90 | ## 0.8.0 - 2017-04-08 91 | 92 | - Auto drop repository after release - [#37](https://github.com/Codearte/gradle-nexus-staging-plugin/issues/37) 93 | - Rename "promote" operation to "release" - [#50](https://github.com/Codearte/gradle-nexus-staging-plugin/issues/50) 94 | - Upgrade project dependencies to 2017 - [#43](https://github.com/Codearte/gradle-nexus-staging-plugin/issues/43) 95 | - Separate functional tests from unit tests - [#48](https://github.com/Codearte/gradle-nexus-staging-plugin/issues/48) 96 | - Make functional tests work also on Windows - [#39](https://github.com/Codearte/gradle-nexus-staging-plugin/issues/39) 97 | 98 | **Deprecation note**. The ~~promoteRepository~~ and ~~closeAndPromoteRepository~~ tasks are marked as deprecated and will be removed 99 | in one of the future versions. `releaseRepository` and `closeAndReleaseRepository` can be used as drop-in replacements. 100 | 101 | ## 0.7.0 - 2017-03-27 102 | 103 | - Verify that repository has been really closed - [#21](https://github.com/Codearte/gradle-nexus-staging-plugin/issues/21) 104 | - Re-enable sharing stagingRepositoryId between close and promote - [#46](https://github.com/Codearte/gradle-nexus-staging-plugin/issues/46) 105 | - Basic functional tests with different Gradle versions - [#41](https://github.com/Codearte/gradle-nexus-staging-plugin/issues/41) 106 | - Suggest longer timeout if failed on time related operations even without `--info` enabled - [#34](https://github.com/Codearte/gradle-nexus-staging-plugin/issues/34) 107 | - Longer default retry period - [#12](https://github.com/Codearte/gradle-nexus-staging-plugin/issues/12) 108 | 109 | ## 0.6.1 - 2017-03-20 110 | 111 | - Reusing `stagingRepositoryId` from close task bypasses retry mechanism and fails - [#44](https://github.com/Codearte/gradle-nexus-staging-plugin/issues/44) - reusing `stagingRepositoryId` is temporary disabled 112 | 113 | ## 0.6.0 - 2017-03-19 114 | 115 | - Consider state trying to find just one repository in given state - [#36](https://github.com/Codearte/gradle-nexus-staging-plugin/issues/36) - contribution by [strelok1](https://github.com/strelok1) 116 | - Better error message in case of HTTP request failure - [#5](https://github.com/Codearte/gradle-nexus-staging-plugin/issues/5) - contribution by [deanhiller](https://github.com/deanhiller) 117 | - Add EditorConfig configuration to better deal with spaces vs tabs - [#33](https://github.com/Codearte/gradle-nexus-staging-plugin/issues/33) 118 | 119 | ## 0.5.3 - 2015-06-13 120 | 121 | - `packageGroup` should be taken from project.group by default - [#11](https://github.com/Codearte/gradle-nexus-staging-plugin/issues/11) 122 | 123 | ## 0.5.2 - 2015-06-09 124 | 125 | - Provide single task to close and promote repository - [#9](https://github.com/Codearte/gradle-nexus-staging-plugin/issues/9) 126 | - `getStagingProfile` task should display output without `--info` switch - [#8](https://github.com/Codearte/gradle-nexus-staging-plugin/issues/8) 127 | 128 | ## 0.5.1 - 2015-03-08 129 | 130 | - Credentials should be automatically fetched from configured deployer - [#7](https://github.com/Codearte/gradle-nexus-staging-plugin/issues/7) 131 | - Credentials should be automatically fetched from Gradle properties (when available) - [#6](https://github.com/Codearte/gradle-nexus-staging-plugin/issues/6) 132 | 133 | ## 0.5.0 - 2015-03-02 134 | 135 | - Wait given time period when repositories are not yet available - [#3](https://github.com/Codearte/gradle-nexus-staging-plugin/issues/3) 136 | - Use configured stagingProfileId when available - [#2](https://github.com/Codearte/gradle-nexus-staging-plugin/issues/2) 137 | - nexusUrl by default should use Sonatype OSSRH - [#1](https://github.com/Codearte/gradle-nexus-staging-plugin/issues/1) 138 | 139 | ## 0.4.0 - 2015-02-27 140 | 141 | - Initial release 142 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gradle Nexus Staging plugin 2 | [![Build Status](https://travis-ci.org/Codearte/gradle-nexus-staging-plugin.svg?branch=master)](https://travis-ci.org/Codearte/gradle-nexus-staging-plugin) 3 | [![Windows Build Status](https://ci.appveyor.com/api/projects/status/github/Codearte/gradle-nexus-staging-plugin?branch=master&svg=true)](https://ci.appveyor.com/project/szpak/gradle-nexus-staging-plugin/) 4 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.codearte.gradle.nexus/gradle-nexus-staging-plugin/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.codearte.gradle.nexus/gradle-nexus-staging-plugin) 5 | [![Plugin Portal](https://img.shields.io/maven-metadata/v?label=Plugin&color=blue&metadataUrl=https://plugins.gradle.org/m2/io/codearte/nexus-staging/io.codearte.nexus-staging.gradle.plugin/maven-metadata.xml)](https://plugins.gradle.org/plugin/io.codearte.nexus-staging) 6 | 7 | A gradle plugin providing tasks to close and promote/release staged repositories. It allows to do a full artifacts release to Maven Central through 8 | [Sonatype OSSRH](http://central.sonatype.org/pages/ossrh-guide.html) (Open Source Software Repository Hosting) without the need to use Nexus GUI (to close and release 9 | artifacts/repository). 10 | 11 | ## MAINTENANCE MODE 12 | 13 | **IMPORTANT. To make releasing to Maven Central even easier, I and Marc Phillip (the author of nexus-publish-plugin) combined forces to create a next generation, unified, 2-in-1 plugin - [gradle-nexus-publish-plugin](https://github.com/gradle-nexus/publish-plugin/). It is a recommended solution, as our development effort will be put in that new plugin. See my [blog post](https://blog.solidsoft.pl/2021/02/26/unified-gradle-projects-releasing-to-maven-central-in-2021-migration-guide/) and the official [migration guide](https://github.com/gradle-nexus/publish-plugin/wiki/Migration-from-gradle_nexus_staging-plugin---nexus_publish-plugin-duo)**. 14 | 15 | Thank you for over 5 years of releasing with my plugin! 16 | 17 | ## Quick start 18 | 19 | Add gradle-nexus-staging-plugin to the `buildscript` dependencies in your build.gradle file for root project: 20 | 21 | buildscript { 22 | repositories { 23 | mavenCentral() 24 | //Needed only for SNAPSHOT versions 25 | //maven { url "http://oss.sonatype.org/content/repositories/snapshots/" } 26 | } 27 | dependencies { 28 | classpath "io.codearte.gradle.nexus:gradle-nexus-staging-plugin:0.30.0" 29 | } 30 | } 31 | 32 | Apply the plugin: 33 | 34 | apply plugin: 'io.codearte.nexus-staging' 35 | 36 | Configure it: 37 | 38 | nexusStaging { 39 | serverUrl = "https://s01.oss.sonatype.org/service/local/" //required only for projects registered in Sonatype after 2021-02-24 40 | packageGroup = "org.mycompany.myproject" //optional if packageGroup == project.getGroup() 41 | stagingProfileId = "yourStagingProfileId" //when not defined will be got from server using "packageGroup" 42 | } 43 | 44 | After successful archives upload (with [`maven`](https://gradle.org/docs/current/userguide/maven_plugin.html), 45 | [`maven-publish`](https://gradle.org/docs/current/userguide/publishing_maven.html) or 46 | [`nexus`](https://github.com/bmuschko/gradle-nexus-plugin/) plugin) to Sonatype OSSRH call: 47 | 48 | ./gradlew closeAndReleaseRepository 49 | 50 | to close staging repository and promote/release it and its artifacts. If a synchronization with Maven Central was enabled the artifacts should 51 | automatically appear into Maven Central within several minutes. 52 | 53 | ### New plugin syntax 54 | 55 | In addition to Maven Central the plugin is available also from the [Plugin Portal](https://plugins.gradle.org/plugin/io.codearte.nexus-staging) and (in most cases) can be applied in a simplified way: 56 | 57 | plugins { 58 | id 'io.codearte.nexus-staging' version '0.30.0' 59 | } 60 | 61 | Buildscript and `apply plugin` sections can be ommited in that case. 62 | 63 | ### Multi-project build 64 | 65 | The plugin itself does not upload any artifacts. It only closes/promotes a repository with all already uploaded using the `maven` or `maven-publish` plugin artifacts (in the same or previous Gradle execution). Therefore it is enough to apply `io.codearte.nexus-staging` only on the root project in a multi-project build. 66 | 67 | ### Travis configuration 68 | 69 | Struggling with errors while releasing from Travis? See [this FAQ point](#2-why-my-release-build-on-travis-suddenly-started-to-fail-with-wrong-number-of-received-repositories) for explaination and [my blog post](https://solidsoft.wordpress.com/2019/02/22/reliable-releasing-to-maven-central-from-travis-using-gradle-2019-edition/) for sample configuration. 70 | 71 | ## Tasks 72 | 73 | The plugin provides three main tasks: 74 | 75 | - `closeRepository` - closes an open repository with the uploaded artifacts. There should be just one open repository available in the staging 76 | profile (possible old/broken repositories can be dropped with Nexus GUI) 77 | - `releaseRepository` - releases a closed repository (required to put artifacts to Maven Central aka The Central Repository) 78 | - `closeAndReleaseRepository` - closes and releases a repository (an equivalent to `closeRepository releaseRepository`) 79 | 80 | And one additional: 81 | 82 | - `getStagingProfile` - gets and displays a staging profile id for a given package group. This is a diagnostic task to get the value and put it 83 | into the configuration closure as `stagingProfileId`. 84 | 85 | It has to be mentioned that calling Nexus REST API ends immediately, but closing/release operations takes a moment. Therefore, to make it possible 86 | to call `closeRepository releaseRepository` together (or use `closeAndReleaseRepository`) there is a built-in retry mechanism. 87 | 88 | **Deprecation note**. Starting with version 0.8.0 `promoteRepository` and `closeAndPromoteRepository` are marked as deprecated and will be removed 89 | in the one of the future versions. `releaseRepository` and `closeAndReleaseRepository` can be used as drop-in replacements. The reasons behind that 90 | change can be found in the corresponding [issue](https://github.com/Codearte/gradle-nexus-staging-plugin/issues/50). 91 | 92 | ## Configuration 93 | 94 | The plugin defines the following configuration properties in the `nexusStaging` closure: 95 | 96 | - `serverUrl` (optional) - the stable release repository URL - by default Sonatype OSSRH - `https://oss.sonatype.org/service/local/` 97 | 98 | > **Important** - Users registered in Sonatype after 24 February 2021 need to customize the server URL: 99 | ``serverUrl = "https://s01.oss.sonatype.org/service/local/"`` 100 | 101 | - `username` (optional) - the username to the server 102 | - `password` (optional) - the password to the server (an auth token [can be used](https://solidsoft.wordpress.com/2015/09/08/deploy-to-maven-central-using-api-key-aka-auth-token/) instead) 103 | - `packageGroup` (optional) - the package group as registered in Nexus staging profile - by default set to a project group (has to be overridden 104 | if packageGroup in Nexus was requested for a few packages in the same domain) 105 | - `stagingProfileId` (optional) - the staging profile used to release given project - can be get with the `getStagingProfile` task - when not set 106 | one additional request is send to the Nexus server to determine the value using `packageGroup` 107 | - `numberOfRetries` (optional) - the number of retries when waiting for a repository state transition to finish - by default `20` 108 | - `delayBetweenRetriesInMillis` (optional) - the delay between retries - by default `2000` milliseconds 109 | - `repositoryDescription` (optional) - staging repository description in close/release operations (see [#63](https://github.com/Codearte/gradle-nexus-staging-plugin/pull/63) for more information) 110 | - `stagingRepositoryId` (optional, since 0.20.0) - the explicitly created staging repository with artifacts to improve build reliability - 111 | requires external mechanism (e.g. [nexus-publish-plugin](https://github.com/marcphilipp/nexus-publish-plugin/)) to enhance a Gradle task 112 | to use it for uploading/publishing artifacts (see [#77](https://github.com/Codearte/gradle-nexus-staging-plugin/issues/77)) 113 | 114 | For the sensible configuration example see the plugin's own release [configuration](gradle/cdeliveryboy-release.gradle). 115 | 116 | ## Server credentials 117 | 118 | Production Nexus instances usually require an user to authenticate before perform staging operations. In the nexus-staging plugin there are few 119 | ways to provide credentials: 120 | - manually set an username and a password in the `nexusStaging` configuration closure (probably reading them from Gradle or system properties) 121 | - provide the authentication section in `MavenDeployer` (from the Gradle `maven` plugin) - it will be reused by the nexus-staging plugin 122 | - set the Gradle properties `nexusUsername` abd `nexusPassword` (via a command line or `~/.gradle/gradle.properties`) - properties with these 123 | names are also used by [gradle-nexus-plugin](https://github.com/bmuschko/gradle-nexus-plugin/). 124 | 125 | The first matching strategy win. If you need to set an empty password use `''` (an empty string) instead of null. 126 | 127 | ## FAQ 128 | 129 | ### 1. Why do I get `Wrong number of received repositories in state 'open'. Expected 1, received 2`? 130 | 131 | There may be a few reasons to get this. 132 | 133 | 1. Ensure using the [Nexus UI](https://oss.sonatype.org/) that there are no old open staging repositories from the previous executions. If yes, just 134 | drop them suing the UI and try again. This is quite common during the initial experiments with the plugin. 135 | 136 | 2. It takes some time to close and/or promote a staging repository in Nexus, especially with multiple artifacts. The plugin has a built-in retry 137 | mechanism, however, the default value can be too [low](https://github.com/Codearte/gradle-nexus-staging-plugin/issues/12), especially for 138 | the multiproject build. To confirm that enable logging at the info level in Gradle (using the `--info` or `-i` build parameter). You should see log 139 | messages similar to `Attempt 8/8 failed.`. If yes, increase the timeout using the `numberOfRetries` or `delayBetweenRetriesInMillis` configuration 140 | parameters. 141 | 142 | 3. An another reason to get the aforementioned error is releasing more than one project using the same Nexus staging repository simultaneously 143 | (usually automatically from a Continuous Delivery pipeline from a Continuous Integration server). Unfortunately Gradle does not provide a mechanism 144 | to track/manage staging repository where the artifacts are being uploaded. Therefore, it is hard to distinguish on closing the own/current repository 145 | from the one created by our another project. There is an [idea](https://github.com/Codearte/gradle-nexus-staging-plugin/issues/29) how it could be 146 | handled using the Nexus API. Please comment in that [issue](https://github.com/Codearte/gradle-nexus-staging-plugin/issues/29) if you are in that 147 | situation. 148 | 149 | 4. You are releasing from Travis. See the next point. 150 | 151 | ### 2. Why my release build on Travis suddenly started to fail with `wrong number of received repositories...`'? 152 | 153 | If your Travis build started to fail around autumn 2018 it's probably a problem reported in [#76](https://github.com/Codearte/gradle-nexus-staging-plugin/issues/76). 154 | To cut a long story short: 155 | - Gradle does [not support](https://github.com/gradle/gradle/issues/5711) uploading/publishing to explicitly created staging repositories in Nexus 156 | - gradle-nexus-staging-plugin had been using heuristics to find the right implicitly created staging repository in Nexus (which - with some limitations - 157 | worked fine in most cases) 158 | - Travis changed their infrastructure in autumn 2018 which [resulted](https://github.com/travis-ci/travis-ci/issues/9555#issuecomment-428799836) 159 | in using different IP addresses for the same build and - as a result - creation of multiple implicitly created staging repositories on upload/publishing for the same build 160 | - Marc Philipp created [nexus-publish-plugin](https://github.com/marcphilipp/nexus-publish-plugin/) to enhance publishing in Gradle which seamlessly 161 | integrates with gradle-nexus-staging-plugin and "fixes" a problem 162 | 163 | For releasing from Travis (and in general) it's recommended to add [nexus-publish-plugin](https://github.com/marcphilipp/nexus-publish-plugin/) to your project 164 | and use its `publishToNexus` task to upload/publish artifacts to Nexus (instead of vanilla `publish...` from Gradle). It integrates seamlessly with 165 | gradle-nexus-staging-plugin to release to Maven Central (especially with 0.20.0+) - noother changes are required. What's more, with that 166 | [enhancement](https://github.com/marcphilipp/nexus-publish-plugin/issues/11) implemented the releasing to Nexus will be even more reliable 167 | (e.g. an ability to run multiple releases for the same staging profile). See [my blog post](https://solidsoft.wordpress.com/2019/02/22/reliable-releasing-to-maven-central-from-travis-using-gradle-2019-edition/) for sample configuration. 168 | 169 | However, there is one caveat. `uploadArchives` from the `maven` plugin is [not supported](https://github.com/marcphilipp/nexus-publish-plugin/issues/8) 170 | by nexus-publish-plugin (only `publish...` from `maven-publish`). 171 | 172 | ### 3. Why do I get the error ``403: Forbidden`` when trying to close or release the repository? 173 | 174 | First thing is to make sure that your credentials are correctly set, using one of the methods explained in the `Server Credentials` section. 175 | 176 | If your credentials are correct and you still get this error, most likely it is happenning because the repository server for you account is different. As of 24 February 2021, accounts created after this date are assigned to a new nexus repository server (https://s01.oss.sonatype.org/). 177 | 178 | To fix this, you need to set `serverUrl = "https://s01.oss.sonatype.org/service/local/"` on the ``nexusStaging`` block on your gradle build file. 179 | 180 | ## Notable users 181 | 182 | The plugin is used by [hundreds of projects](https://github.com/search?q=io.codearte.nexus-staging&type=Code&utf8=%E2%9C%93) around the web. 183 | 184 | Just to mention a few FOSS projects which leverage the plugin to automatize releasing and Continuous Delivery: 185 | [Frege](https://github.com/Frege/frege-interpreter), 186 | [Geb](https://github.com/geb/geb), 187 | [Grails](https://github.com/grails/grails-core), 188 | [Javers](https://github.com/javers/javers), 189 | [JSON Assert](https://github.com/marcingrzejszczak/jsonassert), 190 | [logback-android](https://github.com/tony19/logback-android), 191 | [Micronaut](https://github.com/micronaut-projects/micronaut-aws), 192 | [mini2Dx](https://github.com/mini2Dx/minibus), 193 | [Nextflow](https://github.com/nextflow-io/nextflow) and 194 | [TestNG](https://github.com/cbeust/testng). 195 | 196 | The plugin is also used by the tools and the libraries created by various more or less known companies including: 197 | [Allegro](https://github.com/allegro/hermes), 198 | [Braintree](https://github.com/braintree/braintree_android), 199 | [Google](https://github.com/google/FreeBuilder), 200 | [IBM](https://github.com/IBM-UrbanCode/groovy-plugin-utils), 201 | [PayPal](https://github.com/paypal/PayPal-Java-SDK), 202 | [Schibsted Spain](https://github.com/scm-spain/karyon-rest-router), 203 | [TouK](https://github.com/TouK/bubble) and 204 | [Zalando](https://github.com/zalando-incubator/straw). 205 | 206 | ## Additional information 207 | 208 | [gradle-nexus-staging-plugin](https://github.com/Codearte/gradle-nexus-staging-plugin) was written by Marcin Zajączkowski 209 | with the help of the [contributors](https://github.com/Codearte/gradle-nexus-staging-plugin/graphs/contributors). 210 | The author can be contacted directly via email: `mszpak ATT wp DOTT pl`. 211 | There is also Marcin's blog available: [Solid Soft](http://blog.solidsoft.info) - working code is not enough. 212 | 213 | The PoC leading to the initial version of the plugin was brought to life during one of the hackathons held at [Codearte](http://codearte.io/). 214 | 215 | The first version of the project has been released in 2015 and the plugin seems to be quite stable. Nevertheless, documentation for the Nexus 216 | staging REST API and in addition Gradle support for uploading artifacts to selected Nexus staging repositories leaves much to be desired. 217 | Therefore, the current plugin version is still before 1.0.0. 218 | 219 | The project [changelog](https://github.com/Codearte/gradle-nexus-staging-plugin/releases). 220 | 221 | The plugin is licensed under the terms of [the Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0.txt). 222 | 223 | ![Stat Counter stats](https://c.statcounter.com/10347937/0/98ac55b0/0/) 224 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: "{branch} {build}" 2 | 3 | build: 4 | verbosity: detailed 5 | 6 | before_build: 7 | - SET PATH=%JAVA_HOME%\bin;%PATH% 8 | - java -version -Xmx32m 9 | 10 | build_script: 11 | - gradlew.bat assemble -s 12 | 13 | test_script: 14 | # Temporarily disable animal sniffer due to: https://github.com/mojohaus/animal-sniffer/issues/76 15 | - gradlew.bat check funcTest -PcompatibilityDISABLED -i -s --continue 16 | 17 | environment: 18 | matrix: 19 | - JAVA_HOME: C:\Program Files\Java\jdk1.8.0 20 | - JAVA_HOME: C:\Program Files\Java\jdk11 21 | 22 | matrix: 23 | fast_finish: false 24 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'groovy' 2 | apply from: "$rootDir/gradle/cdeliveryboy-release.gradle" 3 | apply plugin: 'ru.vyarus.animalsniffer' 4 | apply plugin: 'com.toomuchcoding.uptodate' 5 | 6 | buildscript { 7 | repositories { 8 | mavenCentral() 9 | maven { 10 | url 'https://plugins.gradle.org/m2/' //for plugin-publish-plugin 11 | } 12 | } 13 | dependencies { 14 | classpath 'info.solidsoft.gradle:cdeliveryboy:0.8.0' 15 | classpath 'io.codearte.gradle.nexus:gradle-nexus-staging-plugin:0.22.0' 16 | classpath 'ru.vyarus:gradle-animalsniffer-plugin:1.5.3' 17 | classpath 'com.gradle.publish:plugin-publish-plugin:0.13.0' 18 | classpath 'com.toomuchcoding:uptodate-gradle-plugin:1.0.1' 19 | } 20 | } 21 | 22 | sourceCompatibility = 1.8 23 | 24 | repositories { 25 | mavenCentral() 26 | } 27 | 28 | sourceSets { 29 | funcTest { 30 | java.srcDir file('src/funcTest/java') 31 | groovy.srcDir file('src/funcTest/groovy') 32 | resources.srcDir file('src/funcTest/resources') 33 | } 34 | } 35 | 36 | dependencies { 37 | compile gradleApi() 38 | compile localGroovy() 39 | compile 'com.squareup.okhttp3:okhttp:4.9.1' 40 | 41 | testCompile('org.spockframework:spock-core:1.3-groovy-2.5') { 42 | //groovy 2.3.x or 2.4.x is already provided by Gradle itself 43 | exclude group: 'org.codehaus.groovy' 44 | } 45 | testCompile 'info.solidsoft.spock:spock-global-unroll:0.5.1' 46 | testCompile 'org.objenesis:objenesis:3.1' //for mocking classes with Spock 47 | testCompile 'net.bytebuddy:byte-buddy:1.10.21' //for Spying with Spock 48 | 49 | testCompile 'junit:junit:4.13.2' 50 | 51 | funcTestCompile sourceSets.main.output //to make production plugin classes visible in functional tests (it's not in testCompile configuration) 52 | funcTestCompile sourceSets.test.output //to depends on common unit and integration helper classes 53 | funcTestCompile configurations.testCompile 54 | funcTestRuntime configurations.testRuntime 55 | funcTestCompile('com.netflix.nebula:nebula-test:7.10.2') { 56 | exclude group: 'org.codehaus.groovy' 57 | } 58 | funcTestCompile 'com.github.tomakehurst:wiremock:1.50' //1.54+ makes GroovyBuilder to fail with parsing empty response on POST 59 | 60 | signature 'org.codehaus.mojo.signature:java18:1.0@signature' 61 | } 62 | 63 | //noinspection GroovyAssignabilityCheck 64 | task funcTest(type: Test) { 65 | description = 'Run the functional tests.' 66 | group = 'Verification' 67 | 68 | testClassesDirs = sourceSets.funcTest.output.classesDirs 69 | classpath = sourceSets.funcTest.runtimeClasspath 70 | 71 | if (!project.hasProperty('enableE2ETests') && System.getenv().NEXUS_AT_ENABLE_E2E_TESTS == null) { 72 | logger.lifecycle("E2E tests execution is disabled.") 73 | exclude '**/*E2ESpec.*' 74 | } 75 | 76 | reports.html { 77 | destination = file("${reporting.baseDir}/funcTests") 78 | } 79 | 80 | systemProperty("ignoreDeprecations", "true") //TODO: Remove once Gradle 5 deprecation warnings are fixed - https://github.com/Codearte/gradle-nexus-staging-plugin/issues/81 81 | } 82 | 83 | funcTest.shouldRunAfter test 84 | check.shouldRunAfter funcTest 85 | check.dependsOn funcTestClasses 86 | uploadArchives.dependsOn funcTest, check 87 | 88 | task testReport(type: TestReport) { 89 | destinationDir = file("$buildDir/reports/allTests") 90 | reportOn test, funcTest 91 | } 92 | 93 | tasks.withType(ru.vyarus.gradle.plugin.animalsniffer.AnimalSniffer) { 94 | onlyIf { project.hasProperty('compatibility') } 95 | } 96 | 97 | animalsniffer { 98 | sourceSets = [project.sourceSets.main] //just for production classes - Animal Sniffer fails with: 'Undefined reference: void for Spock tests' 99 | //https://github.com/mojohaus/animal-sniffer/issues/27 100 | } 101 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | gnsp.disableLegacyWarning= 2 | -------------------------------------------------------------------------------- /gradle/cdeliveryboy-release.gradle: -------------------------------------------------------------------------------- 1 | //Continuous Delivery release configuration with CDeliveryBoy 2 | 3 | apply plugin: 'info.solidsoft.cdeliveryboy' 4 | apply plugin: 'io.codearte.nexus-upload-staging' 5 | 6 | cDeliveryBoy { 7 | tasks { 8 | uploadArchivesTask = "uploadArchivesStaging" 9 | promoteRepositoryTask = "releaseRepository" 10 | } 11 | nexus { 12 | autoPromote = true 13 | } 14 | } 15 | 16 | nexusStaging { 17 | packageGroup = "io.codearte" 18 | stagingProfileId = "93c08fdebde1ff" 19 | } 20 | 21 | scmVersion { 22 | versionIncrementer 'incrementMinor' 23 | hooks { 24 | pre 'fileUpdate', [file : 'README.md', pattern: { previousVersion, context -> /"io\.codearte\.gradle\.nexus:gradle-nexus-staging-plugin:$previousVersion"/ }, 25 | replacement: { currentVersion, context -> "\"io.codearte.gradle.nexus:gradle-nexus-staging-plugin:$currentVersion\"" }] 26 | pre 'fileUpdate', [file : 'README.md', pattern: { previousVersion, context -> /id\ 'io\.codearte\.nexus-staging'\ version\ '$previousVersion'/ }, 27 | replacement: { currentVersion, context -> "id 'io.codearte.nexus-staging' version '$currentVersion'" }] 28 | pre 'fileUpdate', [file : 'CHANGELOG.md', pattern: { previousVersion, context -> /^##\ ${context.currentVersion}\ -\ Unreleased$/ }, 29 | replacement: { currentVersion, context -> "## $currentVersion - ${new Date().format( 'yyyy-MM-dd' )}" }] 30 | } 31 | } 32 | 33 | group = 'io.codearte.gradle.nexus' 34 | //Beware: All release/version related changes should be put before that line which triggers (lazy) version evaluation 35 | project.version = scmVersion.version 36 | 37 | String repoSlug = "Codearte/gradle-nexus-staging-plugin" 38 | modifyPom { 39 | project { 40 | name 'Gradle Nexus staging plugin' 41 | description 'Gradle Nexus staging plugin' 42 | url "https://github.com/${repoSlug}" 43 | inceptionYear '2015' 44 | 45 | scm { 46 | url "https://github.com/${repoSlug}" 47 | connection "scm:https://github.com/${repoSlug}.git" 48 | developerConnection "scm:git://github.com/${repoSlug}.git" 49 | } 50 | 51 | licenses { 52 | license { 53 | name 'The Apache Software License, Version 2.0' 54 | url 'http://www.apache.org/licenses/LICENSE-2.0.txt' 55 | distribution 'repo' 56 | } 57 | } 58 | 59 | developers { 60 | developer { 61 | id 'szpak' 62 | name 'Marcin Zajączkowski' 63 | email 'mszpak ATT wp DOTT pl' 64 | roles { 65 | role 'developer' 66 | role 'despot' 67 | } 68 | } 69 | } 70 | } 71 | } 72 | 73 | if (hasProperty('pluginPortal')) { 74 | 75 | apply plugin: 'com.gradle.plugin-publish' 76 | 77 | pluginBundle { 78 | website = "https://github.com/${repoSlug}/" 79 | vcsUrl = "https://github.com/${repoSlug}/" 80 | 81 | plugins { 82 | plugin { 83 | id = 'io.codearte.nexus-staging' 84 | displayName = 'gradle-nexus-staging-plugin' 85 | description = 'Releasing to Maven Central from Gradle without dealing with Nexus UI' 86 | tags = ['maven-central', 'nexus', 'release', 'binary', 'repository', 'staging', 'codearte'] 87 | } 88 | uploadStagingPlugin { 89 | id = 'io.codearte.nexus-upload-staging' 90 | displayName = 'gradle-nexus-upload-staging-plugin' 91 | description = 'DO NOT USE. Internal technical plugin for gradle-nexus-staging-plugin - see: io.codearte.nexus-staging' 92 | tags = ['internal'] 93 | } 94 | } 95 | 96 | mavenCoordinates { 97 | groupId = project.group 98 | artifactId = project.name 99 | } 100 | } 101 | 102 | publishPlugins { 103 | onlyIf { ciBuild.inReleaseMode } 104 | mustRunAfter releaseRepository 105 | } 106 | ciBuild.dependsOn publishPlugins 107 | } 108 | 109 | if (hasProperty('changelogSync')) { 110 | 111 | task syncChangelog(type: Exec) { 112 | doFirst { logger.info("Synchronizing changelog with GitHub for version ${project.version}") } 113 | commandLine 'chandler', 'push', "release/${project.version}", '--tag-prefix=release/' 114 | } 115 | 116 | syncChangelog { 117 | onlyIf { ciBuild.inReleaseMode } 118 | mustRunAfter releaseRepository 119 | if (project.tasks.findByName("publishPlugins")) { 120 | mustRunAfter publishPlugins 121 | } 122 | } 123 | ciBuild.dependsOn syncChangelog 124 | } 125 | 126 | 127 | //Some workarounds on CDeliveryBoy limitations - #3 and #13 (see below) 128 | 129 | //Note. Referring non built-in types in external build script is problematic: https://github.com/gradle/gradle/issues/1262 130 | //Note2. Task dependency removal is problematic due to: https://github.com/gradle/gradle/pull/6143/ 131 | ["uploadArchives", "createRepository", "pointUploadArchivesToExplicitRepository"].each { String taskName -> 132 | tasks.named(taskName).configure { 133 | onlyIf { 134 | boolean onlyIfValue = resolveOnlyIfValueForUploadTasks() 135 | if (!onlyIfValue) { 136 | logger.lifecycle("Task disabled due to environment settings for ciBuild") 137 | } 138 | return onlyIfValue 139 | } 140 | } 141 | } 142 | 143 | boolean resolveOnlyIfValueForUploadTasks() { 144 | return !isSnapshotBuildWithSkipReleaseOnCI() && !isExtraBuildOfReleaseTagOnCI() 145 | } 146 | //Workaround on https://github.com/szpak/CDeliveryBoy/issues/3 - do not upload snapshots from other Java versions on CI server 147 | boolean isSnapshotBuildWithSkipReleaseOnCI() { 148 | return (project.version as String).endsWith("-SNAPSHOT") && System.env.SKIP_RELEASE == "true" && isCiBuildTaskCalled() 149 | } 150 | //Workaround on https://github.com/szpak/CDeliveryBoy/issues/13 - do not upload artifacts on extra build on CI server (e.g. from cron) of release tag 151 | boolean isExtraBuildOfReleaseTagOnCI() { 152 | return !(project.version as String).endsWith("-SNAPSHOT") && !ciBuild.inReleaseMode && isCiBuildTaskCalled() 153 | } 154 | boolean isCiBuildTaskCalled() { 155 | //It covers only basic (idiomatic) case that "ciBuild" task has been called directly. Examining taskGraph would be better, however, more problematic due to late preparation. 156 | return gradle.startParameter.taskNames.contains("ciBuild") 157 | } 158 | -------------------------------------------------------------------------------- /gradle/gpg-key.asc.enc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codearte/gradle-nexus-staging-plugin/bbb2ba969e9da89943c5878261eebacba8a7b695/gradle/gpg-key.asc.enc -------------------------------------------------------------------------------- /gradle/install-jdk.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # 4 | # Install JDK for Linux and Mac OS 5 | # 6 | # This script determines the most recent early-access build number, 7 | # downloads the JDK archive to the user home directory and extracts 8 | # it there. 9 | # 10 | # Exported environment variables (when sourcing this script) 11 | # 12 | # JAVA_HOME is set to the extracted JDK directory 13 | # PATH is prepended with ${JAVA_HOME}/bin 14 | # 15 | # (C) 2019 Christian Stein 16 | # 17 | # https://github.com/sormuras/bach/blob/master/install-jdk.sh 18 | # 19 | 20 | set -o errexit 21 | #set -o nounset # https://github.com/travis-ci/travis-ci/issues/5434 22 | #set -o xtrace 23 | 24 | function initialize() { 25 | readonly script_name="$(basename "${BASH_SOURCE[0]}")" 26 | readonly script_version='2019-05-02' 27 | 28 | dry=false 29 | silent=false 30 | verbose=false 31 | emit_java_home=false 32 | 33 | feature='ea' 34 | license='GPL' 35 | os='?' 36 | url='?' 37 | workspace="${HOME}" 38 | target='?' 39 | cacerts=false 40 | } 41 | 42 | function usage() { 43 | cat << EOF 44 | Usage: ${script_name} [OPTION]... 45 | Download and extract the latest-and-greatest JDK from java.net or Oracle. 46 | 47 | Version: ${script_version} 48 | Options: 49 | -h|--help Displays this help 50 | -d|--dry-run Activates dry-run mode 51 | -s|--silent Displays no output 52 | -e|--emit-java-home Print value of "JAVA_HOME" to stdout (ignores silent mode) 53 | -v|--verbose Displays verbose output 54 | 55 | -f|--feature 9|10|...|ea JDK feature release number, defaults to "ea" 56 | -l|--license GPL|BCL License defaults to "GPL", BCL also indicates OTN-LA for Oracle Java SE 57 | -o|--os linux-x64|osx-x64 Operating system identifier (works best with GPL license) 58 | -u|--url "https://..." Use custom JDK archive (provided as .tar.gz file) 59 | -w|--workspace PATH Working directory defaults to \${HOME} [${HOME}] 60 | -t|--target PATH Target directory, defaults to first component of the tarball 61 | -c|--cacerts Link system CA certificates (currently only Debian/Ubuntu is supported) 62 | EOF 63 | } 64 | 65 | function script_exit() { 66 | if [[ $# -eq 1 ]]; then 67 | printf '%s\n' "$1" 68 | exit 0 69 | fi 70 | 71 | if [[ $# -eq 2 && $2 =~ ^[0-9]+$ ]]; then 72 | printf '%b\n' "$1" 73 | exit "$2" 74 | fi 75 | 76 | script_exit 'Invalid arguments passed to script_exit()!' 2 77 | } 78 | 79 | function say() { 80 | if [[ ${silent} != true ]]; then 81 | echo "$@" 82 | fi 83 | } 84 | 85 | function verbose() { 86 | if [[ ${verbose} == true ]]; then 87 | echo "$@" 88 | fi 89 | } 90 | 91 | function parse_options() { 92 | local option 93 | while [[ $# -gt 0 ]]; do 94 | option="$1" 95 | shift 96 | case ${option} in 97 | -h|-H|--help) 98 | usage 99 | exit 0 100 | ;; 101 | -v|-V|--verbose) 102 | verbose=true 103 | ;; 104 | -s|-S|--silent) 105 | silent=true 106 | verbose "Silent mode activated" 107 | ;; 108 | -d|-D|--dry-run) 109 | dry=true 110 | verbose "Dry-run mode activated" 111 | ;; 112 | -e|-E|--emit-java-home) 113 | emit_java_home=true 114 | verbose "Emitting JAVA_HOME" 115 | ;; 116 | -f|-F|--feature) 117 | feature="$1" 118 | verbose "feature=${feature}" 119 | shift 120 | ;; 121 | -l|-L|--license) 122 | license="$1" 123 | verbose "license=${license}" 124 | shift 125 | ;; 126 | -o|-O|--os) 127 | os="$1" 128 | verbose "os=${os}" 129 | shift 130 | ;; 131 | -u|-U|--url) 132 | url="$1" 133 | verbose "url=${url}" 134 | shift 135 | ;; 136 | -w|-W|--workspace) 137 | workspace="$1" 138 | verbose "workspace=${workspace}" 139 | shift 140 | ;; 141 | -t|-T|--target) 142 | target="$1" 143 | verbose "target=${target}" 144 | shift 145 | ;; 146 | -c|-C|--cacerts) 147 | cacerts=true 148 | verbose "Linking system CA certificates" 149 | ;; 150 | *) 151 | script_exit "Invalid argument was provided: ${option}" 2 152 | ;; 153 | esac 154 | done 155 | } 156 | 157 | function determine_latest_jdk() { 158 | local number 159 | local curl_result 160 | local url 161 | 162 | verbose "Determine latest JDK feature release number" 163 | number=9 164 | while [[ ${number} != 99 ]] 165 | do 166 | url=http://jdk.java.net/${number} 167 | curl_result=$(curl -o /dev/null --silent --head --write-out %{http_code} ${url}) 168 | if [[ ${curl_result} -ge 400 ]]; then 169 | break 170 | fi 171 | verbose " Found ${url} [${curl_result}]" 172 | latest_jdk=${number} 173 | number=$[$number +1] 174 | done 175 | 176 | verbose "Latest JDK feature release number is: ${latest_jdk}" 177 | } 178 | 179 | function perform_sanity_checks() { 180 | if [[ ${feature} == '?' ]] || [[ ${feature} == 'ea' ]]; then 181 | feature=${latest_jdk} 182 | fi 183 | if [[ ${feature} -lt 9 ]] || [[ ${feature} -gt ${latest_jdk} ]]; then 184 | script_exit "Expected feature release number in range of 9 to ${latest_jdk}, but got: ${feature}" 3 185 | fi 186 | if [[ ${feature} -gt 11 ]] && [[ ${license} == 'BCL' ]]; then 187 | script_exit "BCL licensed downloads are only supported up to JDK 11, but got: ${feature}" 3 188 | fi 189 | if [[ -d "$target" ]]; then 190 | script_exit "Target directory must not exist, but it does: $(du -hs '${target}')" 3 191 | fi 192 | } 193 | 194 | function determine_url() { 195 | local DOWNLOAD='https://download.java.net/java' 196 | local ORACLE='http://download.oracle.com/otn-pub/java/jdk' 197 | 198 | # Archived feature or official GA build? 199 | case "${feature}-${license}" in 200 | 9-GPL) url="${DOWNLOAD}/GA/jdk9/9.0.4/binaries/openjdk-9.0.4_${os}_bin.tar.gz"; return;; 201 | 9-BCL) url="${ORACLE}/9.0.4+11/c2514751926b4512b076cc82f959763f/jdk-9.0.4_${os}_bin.tar.gz"; return;; 202 | 10-GPL) url="${DOWNLOAD}/GA/jdk10/10.0.2/19aef61b38124481863b1413dce1855f/13/openjdk-10.0.2_${os}_bin.tar.gz"; return;; 203 | 10-BCL) url="${ORACLE}/10.0.2+13/19aef61b38124481863b1413dce1855f/jdk-10.0.2_${os}_bin.tar.gz"; return;; 204 | 11-GPL) url="${DOWNLOAD}/GA/jdk11/9/GPL/openjdk-11.0.2_${os}_bin.tar.gz"; return;; 205 | 11-BCL) url="${ORACLE}/11.0.2+9/f51449fcd52f4d52b93a989c5c56ed3c/jdk-11.0.2_${os}_bin.tar.gz"; return;; 206 | 12-GPL) url="${DOWNLOAD}/GA/jdk12.0.1/69cfe15208a647278a19ef0990eea691/12/GPL/openjdk-12.0.1_${os}_bin.tar.gz"; return;; 207 | esac 208 | 209 | # EA or RC or GA build? 210 | local JAVA_NET="http://jdk.java.net/${feature}" 211 | local candidates=$(wget --quiet --output-document - ${JAVA_NET} | grep -Eo 'href[[:space:]]*=[[:space:]]*"[^\"]+"' | grep -Eo '(http|https)://[^"]+') 212 | url=$(echo "${candidates}" | grep -Eo "${DOWNLOAD}/.+/jdk${feature}/.*${license}/.*jdk-${feature}.+${os}_bin(.tar.gz|.zip)$" || true) 213 | 214 | if [[ -z ${url} ]]; then 215 | script_exit "Couldn't determine a download url for ${feature}-${license} on ${os}" 1 216 | fi 217 | } 218 | 219 | function prepare_variables() { 220 | if [[ ${os} == '?' ]]; then 221 | if [[ "$OSTYPE" == "darwin"* ]]; then 222 | os='osx-x64' 223 | else 224 | os='linux-x64' 225 | fi 226 | fi 227 | if [[ ${url} == '?' ]]; then 228 | determine_latest_jdk 229 | perform_sanity_checks 230 | determine_url 231 | else 232 | feature='' 233 | license='' 234 | os='' 235 | fi 236 | archive="${workspace}/$(basename ${url})" 237 | status=$(curl -o /dev/null --silent --head --write-out %{http_code} ${url}) 238 | } 239 | 240 | function print_variables() { 241 | cat << EOF 242 | Variables: 243 | feature = ${feature} 244 | license = ${license} 245 | os = ${os} 246 | url = ${url} 247 | status = ${status} 248 | archive = ${archive} 249 | EOF 250 | } 251 | 252 | function download_and_extract_and_set_target() { 253 | local quiet='--quiet'; if [[ ${verbose} == true ]]; then quiet=''; fi 254 | local local="--directory-prefix ${workspace}" 255 | local remote='--timestamping --continue' 256 | local wget_options="${quiet} ${local} ${remote}" 257 | local tar_options="--file ${archive}" 258 | 259 | say "Downloading JDK from ${url}..." 260 | verbose "Using wget options: ${wget_options}" 261 | if [[ ${license} == 'GPL' ]]; then 262 | wget ${wget_options} ${url} 263 | else 264 | wget ${wget_options} --header "Cookie: oraclelicense=accept-securebackup-cookie" ${url} 265 | fi 266 | 267 | if [[ ${os} == 'windows-x64' ]]; then 268 | script_exit "Extracting archives on Windows isn't supported, yet" 4 269 | fi 270 | 271 | verbose "Using tar options: ${tar_options}" 272 | if [[ ${target} == '?' ]]; then 273 | tar --extract ${tar_options} -C "${workspace}" 274 | if [[ "$OSTYPE" != "darwin"* ]]; then 275 | target="${workspace}"/$(tar --list ${tar_options} | grep 'bin/javac' | tr '/' '\n' | tail -3 | head -1) 276 | else 277 | target="${workspace}"/$(tar --list ${tar_options} | head -2 | tail -1 | cut -f 2 -d '/' -)/Contents/Home 278 | fi 279 | else 280 | if [[ "$OSTYPE" != "darwin"* ]]; then 281 | mkdir --parents "${target}" 282 | tar --extract ${tar_options} -C "${target}" --strip-components=1 283 | else 284 | mkdir -p "${target}" 285 | tar --extract ${tar_options} -C "${target}" --strip-components=4 # . / / Contents / Home 286 | fi 287 | fi 288 | 289 | if [[ ${verbose} == true ]]; then 290 | echo "Set target to: ${target}" 291 | echo "Content of target directory:" 292 | ls "${target}" 293 | echo "Content of release file:" 294 | [[ ! -f "${target}/release" ]] || cat "${target}/release" 295 | fi 296 | 297 | # Link to system certificates 298 | # http://openjdk.java.net/jeps/319 299 | # https://bugs.openjdk.java.net/browse/JDK-8196141 300 | if [[ ${cacerts} == true ]]; then 301 | mv "${target}/lib/security/cacerts" "${target}/lib/security/cacerts.jdk" 302 | ln -s /etc/ssl/certs/java/cacerts "${target}/lib/security/cacerts" 303 | fi 304 | } 305 | 306 | function main() { 307 | initialize 308 | parse_options "$@" 309 | 310 | say "$script_name $script_version" 311 | prepare_variables 312 | 313 | if [[ ${silent} == false ]]; then print_variables; fi 314 | if [[ ${dry} == true ]]; then exit 0; fi 315 | 316 | download_and_extract_and_set_target 317 | 318 | export JAVA_HOME=$(cd "${target}"; pwd) 319 | export PATH=${JAVA_HOME}/bin:$PATH 320 | 321 | if [[ ${silent} == false ]]; then java -version; fi 322 | if [[ ${emit_java_home} == true ]]; then echo "${JAVA_HOME}"; fi 323 | } 324 | 325 | main "$@" 326 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codearte/gradle-nexus-staging-plugin/bbb2ba969e9da89943c5878261eebacba8a7b695/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | # https://gradle.org/release-checksums/ 4 | distributionSha256Sum=9af5c8e7e2cd1a3b0f694a4ac262b9f38c75262e74a9e8b5101af302a6beadd7 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.3-all.zip 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'gradle-nexus-staging-plugin' 2 | -------------------------------------------------------------------------------- /src/funcTest/groovy/io/codearte/gradle/nexus/functional/BaseNexusStagingFunctionalSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.codearte.gradle.nexus.functional 2 | 3 | import groovy.transform.CompileStatic 4 | import nebula.test.IntegrationSpec 5 | 6 | @CompileStatic 7 | abstract class BaseNexusStagingFunctionalSpec extends IntegrationSpec { 8 | 9 | void setup() { 10 | fork = true //to prevent ClassCastException: org.apache.xerces.parsers.XIncludeAwareParserConfiguration cannot be cast to org.apache.xerces.xni.parser.XMLParserConfiguration 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/funcTest/groovy/io/codearte/gradle/nexus/functional/FunctionalSpecHelperTrait.groovy: -------------------------------------------------------------------------------- 1 | package io.codearte.gradle.nexus.functional 2 | 3 | import groovy.transform.PackageScope 4 | 5 | @PackageScope 6 | trait FunctionalSpecHelperTrait { 7 | 8 | String nexusPassword 9 | String nexusUsername 10 | String packageGroup 11 | 12 | void setup() { 13 | nexusUsername = 'nexus-at' 14 | nexusPassword = '' 15 | packageGroup = 'io.gitlab.nexus-at' 16 | } 17 | 18 | String getApplyPluginBlock() { 19 | return """ 20 | apply plugin: 'io.codearte.nexus-staging' 21 | 22 | buildscript { 23 | repositories { 24 | mavenCentral() 25 | } 26 | } 27 | """ 28 | } 29 | 30 | String getDefaultConfigurationClosure() { 31 | return """ 32 | nexusStaging { 33 | username = '$nexusUsername' 34 | password = '$nexusPassword' 35 | packageGroup = '$packageGroup' 36 | } 37 | """ 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/funcTest/groovy/io/codearte/gradle/nexus/functional/GradleVersionFuncSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.codearte.gradle.nexus.functional 2 | 3 | import com.google.common.base.Predicate 4 | import com.google.common.base.Predicates 5 | import io.codearte.gradle.nexus.NexusStagingPlugin 6 | import nebula.test.functional.ExecutionResult 7 | import nebula.test.functional.GradleRunner 8 | import org.gradle.internal.jvm.Jvm 9 | import org.gradle.util.GradleVersion 10 | import spock.lang.Issue 11 | import spock.util.Exceptions 12 | 13 | import java.util.regex.Pattern 14 | 15 | /** 16 | * Verifies that plugin doesn't fail during Gradle initialization (e.g. due to ClassCastException error) with different "supported" Gradle versions. 17 | */ 18 | class GradleVersionFuncSpec extends BaseNexusStagingFunctionalSpec implements FunctionalSpecHelperTrait { 19 | 20 | //Officially 5.0, but 4.10.2 works fine with that plugin 21 | private static final GradleVersion MINIMAL_STABLE_JAVA11_COMPATIBLE_GRADLE_VERSION = GradleVersion.version("4.10.2") 22 | private static final GradleVersion MINIMAL_STABLE_JAVA14_COMPATIBLE_GRADLE_VERSION = GradleVersion.version("6.3") 23 | private static final GradleVersion LATEST_GRADLE5_VERSION = GradleVersion.version("5.6.4") 24 | private static final GradleVersion LATEST_GRADLE_VERSION = GradleVersion.version("6.8.3") 25 | 26 | def "should not fail on #legacyModeMessage plugin logic initialization issue with Gradle #requestedGradleVersion"() { 27 | given: 28 | gradleVersion = requestedGradleVersion.version 29 | classpathFilter = Predicates.and(GradleRunner.CLASSPATH_DEFAULT, FILTER_SPOCK_JAR) 30 | memorySafeMode = true //shutdown Daemon after a few seconds of inactivity 31 | and: 32 | buildFile << """ 33 | ${getApplyPluginBlock()} 34 | ${getPluginConfigurationWithNotExistingNexusServer()} 35 | 36 | ${getLegacyModeConfigurationIfRequested(isInLegacyMode as boolean)} //"as" for Idea 37 | 38 | //following to cover regression in @ToString on Extension - https://github.com/Codearte/gradle-nexus-staging-plugin/issues/141 39 | println nexusStaging 40 | """.stripIndent() 41 | when: 42 | ExecutionResult result = runTasksWithFailure('getStagingProfile') 43 | then: 44 | result.wasExecuted(':getStagingProfile') 45 | and: 46 | result.failure.cause.message.contains("Execution failed for task ':getStagingProfile'") 47 | Exceptions.getRootCause(result.failure).with { 48 | getClass() == ConnectException 49 | message.contains("Connection refused") 50 | } 51 | where: 52 | [requestedGradleVersion, isInLegacyMode] << [applyJavaCompatibilityAdjustment(resolveRequestedGradleVersions()).unique(), [false, true]].combinations() 53 | legacyModeMessage = isInLegacyMode ? "(legacy)" : "" 54 | } 55 | 56 | @Issue("https://github.com/Codearte/gradle-nexus-staging-plugin/issues/141") //Gradle bug https://github.com/gradle/gradle/issues/11466 - fixed in 6.4 57 | def "should not fail on @ToString for extension class in Gradle 6.x"() { 58 | given: 59 | gradleVersion = LATEST_GRADLE_VERSION.version 60 | and: 61 | buildFile << """ 62 | import groovy.transform.ToString 63 | 64 | @ToString(includeFields = true, includeNames = true, includePackage = false) 65 | class ToStringBugDemonstrationExtension { 66 | final Property bugId 67 | 68 | ToStringBugDemonstrationExtension(Project project) { 69 | bugId = project.getObjects().property(String) 70 | } 71 | } 72 | 73 | class ToStringBugDemonstrationPlugin implements Plugin { 74 | void apply(Project project) { 75 | def extension = project.extensions.create("toStringExtension", ToStringBugDemonstrationExtension, project) 76 | project.task("printBugId") { 77 | doLast { 78 | println "Bug ID: \${extension.bugId}" 79 | } 80 | } 81 | } 82 | } 83 | 84 | apply plugin: ToStringBugDemonstrationPlugin 85 | 86 | println "ToString: " + toStringExtension //in fact not needed in that case 87 | """ 88 | expect: 89 | runTasksSuccessfully("printBugId") 90 | } 91 | 92 | private String getPluginConfigurationWithNotExistingNexusServer() { 93 | return """ 94 | nexusStaging { 95 | packageGroup = "fake.one" 96 | serverUrl = "http://localhost:61942/" 97 | } 98 | """ 99 | } 100 | 101 | private String getLegacyModeConfigurationIfRequested(boolean isInLegacyMode) { 102 | return """ 103 | apply plugin: 'com.bmuschko.nexus' 104 | apply plugin: 'io.codearte.nexus-upload-staging' 105 | 106 | buildscript { 107 | dependencies { 108 | classpath 'com.bmuschko:gradle-nexus-plugin:2.3.1' 109 | } 110 | } 111 | """ 112 | } 113 | 114 | //To prevent failure when Spock for Groovy 2.4 is run with Groovy 2.3 delivered with Gradle <2.8 115 | //Spock is not needed in this artificial project - just the test classpath leaks to Gradle instance started by Nebula 116 | private static final Pattern SPOCK_JAR_PATTERN = Pattern.compile(".*spock-core-1\\..*.jar") 117 | private static final Predicate FILTER_SPOCK_JAR = { URL url -> 118 | return !url.toExternalForm().matches(SPOCK_JAR_PATTERN) 119 | } as Predicate 120 | 121 | private List resolveRequestedGradleVersions() { 122 | return [GradleVersion.version(NexusStagingPlugin.MINIMAL_SUPPORTED_GRADLE_VERSION), MINIMAL_STABLE_JAVA11_COMPATIBLE_GRADLE_VERSION, 123 | MINIMAL_STABLE_JAVA14_COMPATIBLE_GRADLE_VERSION, LATEST_GRADLE5_VERSION, LATEST_GRADLE_VERSION].unique() 124 | } 125 | 126 | //Java 9 testing mechanism taken after pitest-gradle-plugin - https://github.com/szpak/gradle-pitest-plugin 127 | 128 | //Jvm class from Spock doesn't work with Java 9 stable releases - otherwise @IgnoreIf could be used - TODO: check with Spock 1.2 129 | private List applyJavaCompatibilityAdjustment(List requestedGradleVersions) { 130 | if (!Jvm.current().javaVersion.isJava9Compatible()) { 131 | //All supported versions should be Java 8 compatible 132 | return requestedGradleVersions 133 | } 134 | if ((Jvm.current().javaVersion.getMajorVersion() as Integer) >= 14) { 135 | return leaveCurrentJavaCompatibleGradleVersionsOnly(requestedGradleVersions, MINIMAL_STABLE_JAVA14_COMPATIBLE_GRADLE_VERSION) 136 | } else { 137 | return leaveCurrentJavaCompatibleGradleVersionsOnly(requestedGradleVersions, MINIMAL_STABLE_JAVA11_COMPATIBLE_GRADLE_VERSION) 138 | } 139 | 140 | } 141 | 142 | private List leaveCurrentJavaCompatibleGradleVersionsOnly(List requestedGradleVersions, 143 | GradleVersion minimalSupportedGradleVersion) { 144 | List currentJavaCompatibleGradleVersions = requestedGradleVersions.findAll { 145 | it >= minimalSupportedGradleVersion 146 | } 147 | if (currentJavaCompatibleGradleVersions.size() < 1) { 148 | currentJavaCompatibleGradleVersions.add(minimalSupportedGradleVersion) 149 | } 150 | return currentJavaCompatibleGradleVersions 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/funcTest/groovy/io/codearte/gradle/nexus/functional/MockedResponseErrorHandlingSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.codearte.gradle.nexus.functional 2 | 3 | import com.github.tomakehurst.wiremock.junit.WireMockRule 4 | import groovy.transform.NotYetImplemented 5 | import io.codearte.gradle.nexus.infra.NexusHttpResponseException 6 | import io.codearte.gradle.nexus.infra.SimplifiedHttpJsonRestClient 7 | import io.codearte.gradle.nexus.logic.RepositoryCloser 8 | import okhttp3.OkHttpClient 9 | import org.junit.Rule 10 | import spock.lang.Specification 11 | 12 | import static com.github.tomakehurst.wiremock.client.WireMock.aResponse 13 | import static com.github.tomakehurst.wiremock.client.WireMock.containing 14 | import static com.github.tomakehurst.wiremock.client.WireMock.equalTo 15 | import static com.github.tomakehurst.wiremock.client.WireMock.post 16 | import static com.github.tomakehurst.wiremock.client.WireMock.stubFor 17 | import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo 18 | 19 | class MockedResponseErrorHandlingSpec extends Specification { 20 | 21 | private static final String TEST_MOCKED_USERNAME = '' 22 | private static final String TEST_MOCKED_PASSWORD = '' 23 | private static final String TEST_MOCKED_STAGING_PROFILE_ID = "5027d084a01a3a" 24 | private static final String TEST_MOCKED_NOT_EXISTING_REPOSITORY_ID = "xxx" 25 | private static final String TEST_MOCKED_REPOSITORY_DESCRIPTION = "Mocked repository description" 26 | private static final String TEST_MOCKED_SERVER_ERROR_JSON_RESPONSE = """ 27 | { 28 | "errors": [ 29 | { 30 | "id": "*", 31 | "msg": "Unhandled: Missing staging repository: $TEST_MOCKED_NOT_EXISTING_REPOSITORY_ID" 32 | } 33 | ] 34 | } 35 | """.stripIndent() 36 | 37 | @Rule 38 | public WireMockRule wireMockRule = new WireMockRule(MockedFunctionalSpec.WIREMOCK_RANDOM_PORT) 39 | 40 | def "should present response body on 500 server error"() { 41 | given: 42 | SimplifiedHttpJsonRestClient client = new SimplifiedHttpJsonRestClient(new OkHttpClient(), TEST_MOCKED_USERNAME, TEST_MOCKED_PASSWORD) 43 | RepositoryCloser closer = new RepositoryCloser(client, getMockedUrl(), TEST_MOCKED_REPOSITORY_DESCRIPTION) 44 | and: 45 | stubFor(post(urlEqualTo("/staging/bulk/close")) 46 | .withHeader("Content-Type", equalTo("application/json")) 47 | .withHeader("Accept", containing("application/json")) 48 | .willReturn(aResponse() 49 | .withStatus(500) 50 | .withBody(TEST_MOCKED_SERVER_ERROR_JSON_RESPONSE) 51 | .withHeader("Content-Type", "application/json"))) 52 | when: 53 | closer.closeRepositoryWithIdAndStagingProfileId(TEST_MOCKED_NOT_EXISTING_REPOSITORY_ID, TEST_MOCKED_STAGING_PROFILE_ID) 54 | then: 55 | NexusHttpResponseException e = thrown() 56 | e.statusCode == 500 57 | e.message.contains("Missing staging repository: $TEST_MOCKED_NOT_EXISTING_REPOSITORY_ID") 58 | } 59 | 60 | private String getMockedUrl() { 61 | return "http://localhost:${wireMockRule.port()}/" 62 | } 63 | 64 | @NotYetImplemented 65 | def "should present response body on 400 or 500 errors with plain text response"() {} 66 | } 67 | -------------------------------------------------------------------------------- /src/funcTest/groovy/io/codearte/gradle/nexus/functional/PasswordFunctionalSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.codearte.gradle.nexus.functional 2 | 3 | import nebula.test.functional.ExecutionResult 4 | import org.codehaus.groovy.runtime.StackTraceUtils 5 | import org.gradle.api.logging.LogLevel 6 | import spock.lang.Issue 7 | 8 | //Cannot be tested with ProjectBuilder as it doesn't trigger taskGraph.whenReady callback 9 | class PasswordFunctionalSpec extends BaseNexusStagingFunctionalSpec implements FunctionalSpecHelperTrait { 10 | 11 | def "should read password from repository configured in uploadArchives"() { 12 | given: 13 | copyResources("sampleProjects//uploadArchives", "") 14 | file("gradle.properties") << "\ntestSonatypeUsername=testUsername" << "\ntestSonatypePassword=testPassword" 15 | when: 16 | ExecutionResult result = runTasksWithFailure('getStagingProfile') 17 | then: 18 | assertFailureWithConnectionRefused(result.failure) 19 | and: 20 | result.standardOutput.contains("Using username 'testUsername' and password from repository 'Test repo'") 21 | } 22 | 23 | def "should read username and password from property when available"() { 24 | given: 25 | copyResources("sampleProjects//uploadArchives", "") 26 | file("gradle.properties") << "\nnexusUsername=nexusTestUsername" << "\nnexusPassword=nexusTestPassword" 27 | when: 28 | ExecutionResult result = runTasksWithFailure('getStagingProfile', '--build-file', 'build-property.gradle') 29 | then: 30 | assertFailureWithConnectionRefused(result.failure) 31 | and: 32 | //Cannot assert precisely as those values can be overridden by user's ~/.gradle/gradle.properties 33 | result.standardOutput.contains("Using password '*****' from Gradle property") 34 | } 35 | 36 | def "should not try read username and password when release tasks are not executed"() { 37 | given: 38 | copyResources("sampleProjects//uploadArchives", "") 39 | and: 40 | logLevel = LogLevel.DEBUG 41 | when: 42 | //'tasks' was initially chose unhappily as 'check' exposes issue with super class assignment with "DefaultTask" - issue #67 43 | ExecutionResult result = runTasksSuccessfully('check', '--build-file', 'build-property.gradle') 44 | then: 45 | result.standardOutput.contains("No staging task will be executed - skipping determination of Nexus credentials") 46 | } 47 | 48 | @Issue("https://github.com/Codearte/gradle-nexus-staging-plugin/issues/67") 49 | def "should try to determine credentials and not fail with custom uploadArchives task when staging task is requested"() { 50 | given: 51 | copyResources("sampleProjects//uploadArchives", "") 52 | file("gradle.properties") << "\nnexusUsername=nexusTestUsername" << "\nnexusPassword=nexusTestPassword" 53 | and: 54 | file("build-property.gradle") << """ 55 | task uploadArchives {} 56 | """.stripIndent() 57 | when: 58 | ExecutionResult result = runTasksWithFailure('getStagingProfile', '--build-file', 'build-property.gradle') 59 | then: 60 | assertFailureWithConnectionRefused(result.failure) 61 | and: 62 | !result.standardOutput.contains("No staging task will be executed - skipping determination of Nexus credentials") 63 | } 64 | 65 | private static void assertFailureWithConnectionRefused(Throwable failure) { 66 | Throwable rootCause = StackTraceUtils.extractRootCause(failure) 67 | assert rootCause.getClass() == ConnectException 68 | assert rootCause.message.contains("Connection refused") 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/funcTest/groovy/io/codearte/gradle/nexus/functional/e2e/BasicPublishSmokeE2ESpec.groovy: -------------------------------------------------------------------------------- 1 | package io.codearte.gradle.nexus.functional.e2e 2 | 3 | import io.codearte.gradle.nexus.functional.BaseNexusStagingFunctionalSpec 4 | import nebula.test.functional.ExecutionResult 5 | import spock.lang.Stepwise 6 | 7 | //TODO: Remove duplication with tests for 'maven' - @Stepwise works with abstract tests in super class? 8 | @Stepwise 9 | class BasicPublishSmokeE2ESpec extends BaseNexusStagingFunctionalSpec implements E2ESpecHelperTrait { 10 | 11 | def "should get staging profile"() { 12 | given: 13 | copyResources("sampleProjects//nexus-at-minimal-publish", "") 14 | when: 15 | ExecutionResult result = runTasksSuccessfully('getStagingProfile') 16 | then: 17 | result.wasExecuted(':getStagingProfile') 18 | and: 19 | // println result.standardOutput //TODO: How to redirect stdout to show on console (works with 2.2.1) 20 | result.standardOutput.contains("Received staging profile id: $E2E_STAGING_PROFILE_ID") 21 | } 22 | 23 | def "should publish artifacts to explicitly created stating repository and close and release that particular repository reusing set ID"() { 24 | given: 25 | copyResources("sampleProjects//nexus-at-minimal-publish", "") 26 | when: 27 | ExecutionResult result = runTasksSuccessfully('clean', 'publish', 'closeAndReleaseRepository') 28 | then: 29 | with(result) { 30 | verifyAll { 31 | //TODO: How to verify task execution in order? 32 | wasExecuted("initializeSonatypeStagingRepository") 33 | wasExecuted("publishMavenJavaPublicationToSonatypeRepository") 34 | //and 35 | standardOutput.contains("to repository 'sonatype' (https://oss.sonatype.org/service/local/staging/deployByRepositoryId/iogitlabnexus-at-") 36 | //and 37 | wasExecuted("closeRepository") 38 | wasExecuted("releaseRepository") 39 | wasExecuted("closeAndReleaseRepository") 40 | //and 41 | standardOutput.contains('has been effectively released') 42 | //and reuse provided staging profile in both close and release 43 | standardOutput.contains("Reusing staging repository id: iogitlabnexus-at") 44 | !standardOutput.contains("DEPRECATION WARNING. The staging repository ID is not provided.") 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/funcTest/groovy/io/codearte/gradle/nexus/functional/e2e/BasicSmokeE2ESpec.groovy: -------------------------------------------------------------------------------- 1 | package io.codearte.gradle.nexus.functional.e2e 2 | 3 | import io.codearte.gradle.nexus.functional.BaseNexusStagingFunctionalSpec 4 | import nebula.test.functional.ExecutionResult 5 | import spock.lang.Stepwise 6 | 7 | @Stepwise 8 | class BasicSmokeE2ESpec extends BaseNexusStagingFunctionalSpec implements E2ESpecHelperTrait { 9 | 10 | def "should get staging profile"() { 11 | given: 12 | copyResources("sampleProjects//nexus-at-minimal", "") 13 | when: 14 | ExecutionResult result = runTasksSuccessfully('getStagingProfile') 15 | then: 16 | result.wasExecuted(':getStagingProfile') 17 | and: 18 | // println result.standardOutput //TODO: How to redirect stdout to show on console (works with 2.2.1) 19 | result.standardOutput.contains("Received staging profile id: $E2E_STAGING_PROFILE_ID") 20 | } 21 | 22 | def "should upload artifacts to server and reuse explicitly created staging repository id in both close and release "() { 23 | given: 24 | copyResources("sampleProjects//nexus-at-minimal", "") 25 | when: 26 | ExecutionResult result = runTasksSuccessfully('clean', 'uploadArchivesStaging', 'closeAndReleaseRepository') 27 | then: 28 | with(result) { 29 | verifyAll { 30 | wasExecuted("createRepository") 31 | wasExecuted("uploadArchives") 32 | wasExecuted("uploadArchivesStaging") 33 | //and 34 | standardOutput.contains('Uploading: io/gitlab/nexus-at/minimal/nexus-at-minimal/') 35 | //and 36 | wasExecuted("closeRepository") 37 | wasExecuted("releaseRepository") 38 | wasExecuted("closeAndReleaseRepository") 39 | //and 40 | standardOutput.contains("Reusing staging repository id: iogitlabnexus-at") 41 | !standardOutput.contains("DEPRECATION WARNING. The staging repository ID is not provided.") 42 | } 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/funcTest/groovy/io/codearte/gradle/nexus/functional/e2e/E2ESpecConstants.groovy: -------------------------------------------------------------------------------- 1 | package io.codearte.gradle.nexus.functional.e2e 2 | 3 | import groovy.transform.PackageScope 4 | 5 | //Separate interface as there is problem with constants visibility in traits 6 | @PackageScope 7 | interface E2ESpecConstants { 8 | 9 | public static final String E2E_SERVER_BASE_PATH = "https://oss.sonatype.org/service/local/" 10 | public static final String E2E_PACKAGE_GROUP = 'io.gitlab.nexus-at' 11 | public static final String E2E_STAGING_PROFILE_ID = "5027d084a01a3a" 12 | public static final String E2E_REPOSITORY_DESCRIPTION = "Automatically released in E2E tests" 13 | } 14 | -------------------------------------------------------------------------------- /src/funcTest/groovy/io/codearte/gradle/nexus/functional/e2e/E2ESpecHelperTrait.groovy: -------------------------------------------------------------------------------- 1 | package io.codearte.gradle.nexus.functional.e2e 2 | 3 | import groovy.transform.PackageScope 4 | import org.slf4j.Logger 5 | import org.slf4j.LoggerFactory 6 | 7 | import java.lang.invoke.MethodHandles 8 | 9 | //@CompileStatic //as getNexus*AT() is called from static context, but unfortunately making them static results in: 10 | //MissingMethodException: No signature of method: static io.codearte.gradle.nexus.E2EFunctionalTestHelperTrait.getNexusUsernameAT() 11 | @PackageScope 12 | trait E2ESpecHelperTrait implements E2ESpecConstants { 13 | 14 | private static final Logger logT = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass()); 15 | 16 | private static final String GRADLE_ENVIRONMENT_VARIABLE_PREFIX = 'ORG_GRADLE_PROJECT_' 17 | private static final String NEXUS_USERNAME_AT_ENVIRONMENT_VARIABLE_NAME = 'nexusUsernameAT' 18 | private static final String NEXUS_PASSWORD_AT_ENVIRONMENT_VARIABLE_NAME = 'nexusPasswordAT' 19 | 20 | static String nexusUsernameAT 21 | static String nexusPasswordAT 22 | 23 | void setupSpec() { 24 | nexusUsernameAT = readNexusUsernameAT() 25 | nexusPasswordAT = tryToReadNexusPasswordAT() 26 | } 27 | 28 | //Cannot be private due to limitation of trait & Spock combination 29 | String readNexusUsernameAT() { 30 | return System.getenv(NEXUS_USERNAME_AT_ENVIRONMENT_VARIABLE_NAME) ?: 31 | tryToReadPropertyFromGradleEnvironmentVariable(NEXUS_USERNAME_AT_ENVIRONMENT_VARIABLE_NAME) ?: 32 | tryToReadPropertyFromGradlePropertiesFile(NEXUS_USERNAME_AT_ENVIRONMENT_VARIABLE_NAME) ?: 33 | 'nexus-at' 34 | } 35 | 36 | String tryToReadNexusPasswordAT() { 37 | //Will not work with empty password. However, support for it would complicate '?;' statement 38 | return System.getenv(NEXUS_PASSWORD_AT_ENVIRONMENT_VARIABLE_NAME) ?: 39 | tryToReadPropertyFromGradleEnvironmentVariable(NEXUS_PASSWORD_AT_ENVIRONMENT_VARIABLE_NAME) ?: 40 | tryToReadPropertyFromGradlePropertiesFile(NEXUS_PASSWORD_AT_ENVIRONMENT_VARIABLE_NAME) ?: 41 | { throw new RuntimeException("Nexus password for AT tests is not set in 'gradle.properties' nor system variable " + 42 | "'$NEXUS_PASSWORD_AT_ENVIRONMENT_VARIABLE_NAME' although E2E tests execution is enabled") }() 43 | } 44 | 45 | //TODO: It would be good to not reimplement Gradle logic 46 | private String tryToReadPropertyFromGradleEnvironmentVariable(String propertyName) { 47 | return System.getenv("$GRADLE_ENVIRONMENT_VARIABLE_PREFIX$propertyName") 48 | } 49 | 50 | private String tryToReadPropertyFromGradlePropertiesFile(String propertyName) { 51 | Properties props = new Properties() 52 | File gradlePropertiesFile = new File(new File(System.getProperty('user.home'), '.gradle'), 'gradle.properties') 53 | if (!gradlePropertiesFile.exists()) { 54 | logT.warn("$gradlePropertiesFile does not exist while reading '$propertyName' value") 55 | return null 56 | } 57 | gradlePropertiesFile.withInputStream { props.load(it) } 58 | return props.getProperty(propertyName) 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/funcTest/groovy/io/codearte/gradle/nexus/functional/e2e/ExploratoryE2ESpec.groovy: -------------------------------------------------------------------------------- 1 | package io.codearte.gradle.nexus.functional.e2e 2 | 3 | import groovy.transform.NotYetImplemented 4 | import io.codearte.gradle.nexus.functional.BaseNexusStagingFunctionalSpec 5 | import io.codearte.gradle.nexus.infra.SimplifiedHttpJsonRestClient 6 | import io.codearte.gradle.nexus.logic.OperationRetrier 7 | import io.codearte.gradle.nexus.logic.RepositoryCloser 8 | import io.codearte.gradle.nexus.logic.RepositoryCreator 9 | import io.codearte.gradle.nexus.logic.RepositoryDropper 10 | import io.codearte.gradle.nexus.logic.RepositoryFetcher 11 | import io.codearte.gradle.nexus.logic.RepositoryReleaser 12 | import io.codearte.gradle.nexus.logic.RepositoryState 13 | import io.codearte.gradle.nexus.logic.RepositoryStateFetcher 14 | import io.codearte.gradle.nexus.logic.RetryingRepositoryTransitioner 15 | import io.codearte.gradle.nexus.logic.StagingProfileFetcher 16 | import nebula.test.functional.ExecutionResult 17 | import okhttp3.OkHttpClient 18 | import spock.lang.Ignore 19 | import spock.lang.Stepwise 20 | 21 | //TODO: Duplication with BasicFunctionalSpec done at Gradle level - decide which tests are better/easier to use and maintain 22 | @Stepwise 23 | class ExploratoryE2ESpec extends BaseNexusStagingFunctionalSpec implements E2ESpecHelperTrait { 24 | 25 | private SimplifiedHttpJsonRestClient client 26 | private RepositoryStateFetcher repoStateFetcher 27 | private OperationRetrier retrier 28 | 29 | private static String resolvedStagingRepositoryId 30 | 31 | def setup() { 32 | client = new SimplifiedHttpJsonRestClient(new OkHttpClient(), nexusUsernameAT, nexusPasswordAT) 33 | repoStateFetcher = new RepositoryStateFetcher(client, E2E_SERVER_BASE_PATH) 34 | retrier = new OperationRetrier<>() 35 | } 36 | 37 | @NotYetImplemented 38 | def "remove all staging repositories if exist as clean up"() {} 39 | 40 | //TODO: Remove "should" prefix, it's default by convention 41 | def "should get staging profile id from server e2e"() { 42 | given: 43 | StagingProfileFetcher fetcher = new StagingProfileFetcher(client, E2E_SERVER_BASE_PATH) 44 | when: 45 | String stagingProfileId = fetcher.getStagingProfileIdForPackageGroup(E2E_PACKAGE_GROUP) 46 | then: 47 | stagingProfileId == E2E_STAGING_PROFILE_ID 48 | } 49 | 50 | def "should create staging repository explicitly e2e"() { 51 | given: 52 | RepositoryCreator creator = new RepositoryCreator(client, E2E_SERVER_BASE_PATH) 53 | when: 54 | String stagingRepositoryId = creator.createStagingRepositoryAndReturnId(E2E_STAGING_PROFILE_ID) 55 | then: 56 | println stagingRepositoryId 57 | stagingRepositoryId.startsWith("iogitlabnexus-at") 58 | and: 59 | propagateStagingRepositoryIdToAnotherTest(stagingRepositoryId) 60 | } 61 | 62 | def "should upload artifacts to server e2e"() { 63 | given: 64 | assert resolvedStagingRepositoryId 65 | and: 66 | copyResources("sampleProjects//nexus-at-minimal", "") 67 | when: 68 | ExecutionResult result = runTasksSuccessfully('uploadArchives', 69 | "-Pe2eRepositoryUrl=${E2E_SERVER_BASE_PATH}staging/deployByRepositoryId/${resolvedStagingRepositoryId}") 70 | then: 71 | result.standardOutput.contains('Uploading: io/gitlab/nexus-at/minimal/nexus-at-minimal/') 72 | } 73 | 74 | //TODO: Adjust to (optionally) just get repository ID in getNonTransitioningRepositoryStateById() 75 | @Ignore("Not executed by default as explicit stagingRepositoryId should used for parallel test execution on CI") 76 | def "should get open repository id from server e2e"() { 77 | given: 78 | RepositoryFetcher fetcher = new RepositoryFetcher(client, E2E_SERVER_BASE_PATH) 79 | when: 80 | String stagingRepositoryId = fetcher.getRepositoryIdWithGivenStateForStagingProfileId(E2E_STAGING_PROFILE_ID, RepositoryState.OPEN) 81 | then: 82 | println stagingRepositoryId 83 | stagingRepositoryId == resolvedStagingRepositoryId 84 | } 85 | 86 | def "should get not in transition open repository state by repository id from server e2e"() { 87 | given: 88 | assert resolvedStagingRepositoryId 89 | when: 90 | RepositoryState receivedRepoState = repoStateFetcher.getNonTransitioningRepositoryStateById(resolvedStagingRepositoryId) 91 | then: 92 | receivedRepoState == RepositoryState.OPEN 93 | } 94 | 95 | def "should close open repository waiting for transition to finish e2e"() { 96 | given: 97 | assert resolvedStagingRepositoryId 98 | and: 99 | RepositoryCloser closer = new RepositoryCloser(client, E2E_SERVER_BASE_PATH, E2E_REPOSITORY_DESCRIPTION) 100 | RetryingRepositoryTransitioner retryingCloser = new RetryingRepositoryTransitioner(closer, repoStateFetcher, retrier) 101 | when: 102 | retryingCloser.performWithRepositoryIdAndStagingProfileId(resolvedStagingRepositoryId, E2E_STAGING_PROFILE_ID) 103 | then: 104 | noExceptionThrown() 105 | and: 106 | RepositoryState receivedRepoState = repoStateFetcher.getNonTransitioningRepositoryStateById(resolvedStagingRepositoryId) 107 | then: 108 | receivedRepoState == RepositoryState.CLOSED 109 | } 110 | 111 | @Ignore('Not the base path') 112 | def "should drop open repository e2e"() { 113 | given: 114 | assert resolvedStagingRepositoryId 115 | and: 116 | RepositoryDropper dropper = new RepositoryDropper(client, E2E_SERVER_BASE_PATH, E2E_REPOSITORY_DESCRIPTION) 117 | RetryingRepositoryTransitioner retryingDropper = new RetryingRepositoryTransitioner(dropper, repoStateFetcher, retrier) 118 | when: 119 | retryingDropper.performWithRepositoryIdAndStagingProfileId(resolvedStagingRepositoryId, E2E_STAGING_PROFILE_ID) 120 | then: 121 | noExceptionThrown() 122 | when: 123 | RepositoryState receivedRepoState = repoStateFetcher.getNonTransitioningRepositoryStateById(resolvedStagingRepositoryId) 124 | then: 125 | receivedRepoState == RepositoryState.NOT_FOUND 126 | } 127 | 128 | def "should release closed repository e2e"() { 129 | given: 130 | assert resolvedStagingRepositoryId 131 | and: 132 | RepositoryReleaser releaser = new RepositoryReleaser(client, E2E_SERVER_BASE_PATH, E2E_REPOSITORY_DESCRIPTION) 133 | RetryingRepositoryTransitioner retryingReleaser = new RetryingRepositoryTransitioner(releaser, repoStateFetcher, retrier) 134 | when: 135 | retryingReleaser.performWithRepositoryIdAndStagingProfileId(resolvedStagingRepositoryId, E2E_STAGING_PROFILE_ID) 136 | then: 137 | noExceptionThrown() 138 | } 139 | 140 | def "repository after release should be dropped immediately e2e"() { 141 | given: 142 | assert resolvedStagingRepositoryId 143 | when: 144 | RepositoryState receivedRepoState = repoStateFetcher.getNonTransitioningRepositoryStateById(resolvedStagingRepositoryId) 145 | then: 146 | receivedRepoState == RepositoryState.NOT_FOUND 147 | } 148 | 149 | private void propagateStagingRepositoryIdToAnotherTest(String stagingRepositoryId) { 150 | resolvedStagingRepositoryId = stagingRepositoryId 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /src/funcTest/resources/sampleProjects/uploadArchives/build-property.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'groovy' 2 | apply plugin: 'io.codearte.nexus-staging' 3 | 4 | repositories { 5 | mavenCentral() 6 | } 7 | 8 | dependencies { 9 | compile 'org.codehaus.groovy:groovy-all:2.3.7' 10 | 11 | testCompile 'org.spockframework:spock-core:0.7-groovy-2.0' 12 | testCompile 'junit:junit:4.11' 13 | } 14 | 15 | artifacts { 16 | archives jar 17 | } 18 | 19 | nexusStaging { 20 | serverUrl = 'http://localhost:8089/nexus/' 21 | packageGroup = "io.codearte" 22 | } 23 | -------------------------------------------------------------------------------- /src/funcTest/resources/sampleProjects/uploadArchives/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'groovy' 2 | apply plugin: 'maven' 3 | 4 | apply plugin: 'io.codearte.nexus-staging' 5 | 6 | repositories { 7 | mavenCentral() 8 | } 9 | 10 | dependencies { 11 | compile 'org.codehaus.groovy:groovy-all:2.3.7' 12 | 13 | testCompile 'org.spockframework:spock-core:0.7-groovy-2.0' 14 | testCompile 'junit:junit:4.11' 15 | } 16 | 17 | artifacts { 18 | archives jar 19 | } 20 | 21 | uploadArchives { 22 | repositories { 23 | mavenDeployer { 24 | repository(id: 'test-repo-staging', url: "http://localhost:8089/nexus/content/repositories/internal/") { 25 | authentication(userName: testSonatypeUsername, password: testSonatypePassword) 26 | } 27 | snapshotRepository(id: 'test-repo-snapshots', url: 'http://localhost:8089/nexus/content/repositories/internal-snapshots/') { 28 | authentication(userName: 'someOtherTestUsername', password: 'someOtherTestPassword') 29 | } 30 | name = 'Test repo' 31 | } 32 | } 33 | } 34 | 35 | nexusStaging { 36 | serverUrl = 'http://localhost:8089/nexus/' 37 | packageGroup = "io.codearte" 38 | } 39 | -------------------------------------------------------------------------------- /src/funcTest/resources/sampleProjects/uploadArchives/gradle.properties: -------------------------------------------------------------------------------- 1 | group=com.example.upload1 2 | version=0.0.1-SNAPSHOT 3 | -------------------------------------------------------------------------------- /src/funcTest/resources/sampleProjects/uploadArchives/settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'uploadArchives' 2 | -------------------------------------------------------------------------------- /src/funcTest/resources/sampleProjects/uploadArchives/src/main/groovy/Library.groovy: -------------------------------------------------------------------------------- 1 | class Library { 2 | boolean someLibraryMethod() { 3 | true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /src/funcTest/resources/sampleProjects/uploadArchives/src/test/groovy/LibraryTest.groovy: -------------------------------------------------------------------------------- 1 | import spock.lang.Specification 2 | 3 | class LibraryTest extends Specification{ 4 | def "someLibraryMethod returns true"() { 5 | setup: 6 | Library lib = new Library() 7 | when: 8 | def result = lib.someLibraryMethod() 9 | then: 10 | result == true 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/groovy/io/codearte/gradle/nexus/ApplyOnlyOnRootProjectEnforcer.groovy: -------------------------------------------------------------------------------- 1 | package io.codearte.gradle.nexus 2 | 3 | import groovy.transform.CompileStatic 4 | import groovy.transform.PackageScope 5 | import groovy.util.logging.Slf4j 6 | import org.gradle.api.GradleException 7 | import org.gradle.api.Incubating 8 | import org.gradle.api.Project 9 | 10 | @CompileStatic 11 | @Slf4j 12 | @Incubating 13 | class ApplyOnlyOnRootProjectEnforcer { 14 | 15 | @PackageScope //for testing 16 | static final String DISABLE_APPLY_ONLY_ON_ROOT_PROJECT_ENFORCEMENT_PROPERTY_NAME = 'gnsp.disableApplyOnlyOnRootProjectEnforcement' 17 | 18 | private final String propertyNameToDisable 19 | 20 | //TODO: Switch to @TuppleConstructor(defaults = false) while completely migrated to Gradle 5.x (with Groovy 2.5) 21 | ApplyOnlyOnRootProjectEnforcer() { 22 | this.propertyNameToDisable = DISABLE_APPLY_ONLY_ON_ROOT_PROJECT_ENFORCEMENT_PROPERTY_NAME 23 | } 24 | 25 | void failBuildWithMeaningfulErrorIfAppliedNotOnRootProject(Project project) { 26 | if ((project != project.rootProject) && !isPartOfDeterminingPrecompiledScriptPluginAccessorsBuild(project)) { 27 | if (GradleUtil.isPropertyNotDefinedOrFalse(project, propertyNameToDisable)) { //See https://github.com/Codearte/gradle-nexus-staging-plugin/issues/116 28 | throw new GradleException("Nexus staging plugin should ONLY be applied on the ROOT project in a build. " + 29 | "See https://github.com/Codearte/gradle-nexus-staging-plugin/issues/47 for explanation. If you really know what you are doing " + 30 | "it can be overridden with setting '${DISABLE_APPLY_ONLY_ON_ROOT_PROJECT_ENFORCEMENT_PROPERTY_NAME}' property.") 31 | } else { 32 | log.info("Overriding protection against applying on non-root project. It may cause execution errors if used improperly.") 33 | } 34 | } 35 | } 36 | 37 | /** 38 | * For precompiled Kotlin script plugins the plugin is automatically applied to a non-root project to 39 | * determine which extensions and conventions it adds to a project. This needs to be allowed and 40 | * currently cannot controlled by some project property as those are not forwarded to this build. 41 | * 42 | * https://github.com/Codearte/gradle-nexus-staging-plugin/issues/47#issuecomment-491474045 43 | * 44 | * @param project the project to test 45 | * @return whether the given project is part of a build to determine the precompiled script plugin accessors 46 | */ 47 | private isPartOfDeterminingPrecompiledScriptPluginAccessorsBuild(Project project) { 48 | project.projectDir.absolutePath =~ '([\\\\/])build\\1tmp\\1generatePrecompiledScriptPluginAccessors\\1' 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/groovy/io/codearte/gradle/nexus/BaseStagingTask.groovy: -------------------------------------------------------------------------------- 1 | package io.codearte.gradle.nexus 2 | 3 | import groovy.transform.CompileStatic 4 | import groovy.transform.PackageScope 5 | import io.codearte.gradle.nexus.infra.SimplifiedHttpJsonRestClient 6 | import io.codearte.gradle.nexus.logic.OperationRetrier 7 | import io.codearte.gradle.nexus.logic.RepositoryCloser 8 | import io.codearte.gradle.nexus.logic.RepositoryFetcher 9 | import io.codearte.gradle.nexus.logic.RepositoryReleaser 10 | import io.codearte.gradle.nexus.logic.RepositoryState 11 | import io.codearte.gradle.nexus.logic.RepositoryStateFetcher 12 | import io.codearte.gradle.nexus.logic.StagingProfileFetcher 13 | import okhttp3.OkHttpClient 14 | import org.gradle.api.DefaultTask 15 | import org.gradle.api.Incubating 16 | import org.gradle.api.Project 17 | import org.gradle.api.model.ObjectFactory 18 | import org.gradle.api.provider.Property 19 | import org.gradle.api.tasks.Input 20 | import org.gradle.api.tasks.Optional 21 | 22 | import javax.inject.Inject 23 | 24 | @CompileStatic 25 | @SuppressWarnings("UnstableApiUsage") 26 | abstract class BaseStagingTask extends DefaultTask { 27 | 28 | @Input 29 | String serverUrl 30 | 31 | @Input 32 | @Optional 33 | String username 34 | 35 | @Input 36 | @Optional 37 | String password 38 | 39 | @Input 40 | String packageGroup 41 | 42 | @Input 43 | @Optional 44 | String stagingProfileId 45 | 46 | @Input 47 | Integer numberOfRetries 48 | 49 | @Input 50 | Integer delayBetweenRetriesInMillis 51 | 52 | @Input 53 | String repositoryDescription 54 | 55 | @Input 56 | @Optional 57 | @Incubating 58 | final Property stagingRepositoryId 59 | 60 | private SimplifiedHttpJsonRestClient restClient 61 | 62 | @Inject 63 | BaseStagingTask(Project project, NexusStagingExtension extension) { 64 | ObjectFactory objectFactory = project.getObjects(); 65 | stagingRepositoryId = objectFactory.property(String) 66 | stagingRepositoryId.set(extension.getStagingRepositoryId()) 67 | } 68 | 69 | @PackageScope 70 | synchronized SimplifiedHttpJsonRestClient createClient() { 71 | if (restClient == null) { 72 | restClient = new SimplifiedHttpJsonRestClient(new OkHttpClient(), getUsername(), getPassword()) 73 | } 74 | return restClient 75 | } 76 | 77 | protected StagingProfileFetcher createProfileFetcherWithGivenClient(SimplifiedHttpJsonRestClient client) { 78 | return new StagingProfileFetcher(client, getServerUrl()) 79 | } 80 | 81 | protected RepositoryFetcher createRepositoryFetcherWithGivenClient(SimplifiedHttpJsonRestClient client) { 82 | return new RepositoryFetcher(client, getServerUrl()) 83 | } 84 | 85 | protected RepositoryStateFetcher createRepositoryStateFetcherWithGivenClient(SimplifiedHttpJsonRestClient client) { 86 | return new RepositoryStateFetcher(client, getServerUrl()) 87 | } 88 | 89 | protected RepositoryCloser createRepositoryCloserWithGivenClient(SimplifiedHttpJsonRestClient client) { 90 | return new RepositoryCloser(client, getServerUrl(), getRepositoryDescription()) 91 | } 92 | 93 | protected RepositoryReleaser createRepositoryReleaserWithGivenClient(SimplifiedHttpJsonRestClient client) { 94 | return new RepositoryReleaser(client, getServerUrl(), getRepositoryDescription()) 95 | } 96 | 97 | protected OperationRetrier createOperationRetrier() { 98 | return new OperationRetrier(getNumberOfRetries(), getDelayBetweenRetriesInMillis()) 99 | } 100 | 101 | protected String getConfiguredStagingProfileIdOrFindAndCacheOne(StagingProfileFetcher stagingProfileFetcher) { 102 | String configuredStagingProfileId = getStagingProfileId() 103 | if (configuredStagingProfileId != null) { 104 | logger.info("Using configured staging profile id: $configuredStagingProfileId") 105 | return configuredStagingProfileId 106 | } else { 107 | String receivedStagingProfileId = stagingProfileFetcher.getStagingProfileIdForPackageGroup(getPackageGroup()) 108 | //TODO: Get from and set in plugin extension instead of in task directly 109 | setStagingProfileId(receivedStagingProfileId) 110 | return receivedStagingProfileId 111 | } 112 | } 113 | 114 | protected String getConfiguredRepositoryIdForStagingProfileOrFindAndCacheOneInGivenState(String stagingProfileId, RepositoryState repositoryState) { 115 | return tryToGetConfiguredRepositoryId().orElseGet { 116 | String repositoryId = findOneRepositoryIdInGivenStateForStagingProfileIdWithRetrying(repositoryState, stagingProfileId, 117 | createRepositoryFetcherWithGivenClient(createClient())) 118 | savePassedRepositoryIdForReusingInInOtherTasks(repositoryId) 119 | return repositoryId 120 | } 121 | } 122 | 123 | private java.util.Optional tryToGetConfiguredRepositoryId() { 124 | //Provider doesn't not provide orElseGet() 125 | if (getStagingRepositoryId().isPresent()) { 126 | String reusedStagingRepositoryId = getStagingRepositoryId().get() 127 | logger.info("Reusing staging repository id: $reusedStagingRepositoryId") 128 | return java.util.Optional.of(reusedStagingRepositoryId) 129 | } else { 130 | return java.util.Optional.empty() 131 | } 132 | } 133 | 134 | private String findOneRepositoryIdInGivenStateForStagingProfileIdWithRetrying(RepositoryState repositoryState, String stagingProfileId, 135 | RepositoryFetcher repositoryFetcher) { 136 | logger.warn("DEPRECATION WARNING. The staging repository ID is not provided. The fallback mode may impact release reliability and is deprecated. " + 137 | "Please consult the project FAQ how it can be fixed.") 138 | OperationRetrier retrier = createOperationRetrier() 139 | return retrier.doWithRetry { repositoryFetcher.getRepositoryIdWithGivenStateForStagingProfileId(stagingProfileId, repositoryState) } 140 | } 141 | 142 | protected void savePassedRepositoryIdForReusingInInOtherTasks(String repositoryId) { 143 | logger.info("Saving repository ID $repositoryId for reusing in other tasks") 144 | NexusStagingExtension extension = getProject().getExtensions().getByType(NexusStagingExtension) 145 | extension.stagingRepositoryId.set(repositoryId) 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/main/groovy/io/codearte/gradle/nexus/CloseRepositoryTask.groovy: -------------------------------------------------------------------------------- 1 | package io.codearte.gradle.nexus 2 | 3 | import groovy.transform.CompileStatic 4 | import groovy.transform.InheritConstructors 5 | import io.codearte.gradle.nexus.logic.OperationRetrier 6 | import io.codearte.gradle.nexus.logic.RepositoryCloser 7 | import io.codearte.gradle.nexus.logic.RepositoryState 8 | import io.codearte.gradle.nexus.logic.RepositoryStateFetcher 9 | import io.codearte.gradle.nexus.logic.RetryingRepositoryTransitioner 10 | import org.gradle.api.tasks.TaskAction 11 | 12 | @CompileStatic 13 | @InheritConstructors(constructorAnnotations = true) 14 | class CloseRepositoryTask extends BaseStagingTask { 15 | 16 | @SuppressWarnings("unused") 17 | @TaskAction 18 | void closeRepository() { 19 | String stagingProfileId = getConfiguredStagingProfileIdOrFindAndCacheOne(createProfileFetcherWithGivenClient(createClient())) 20 | String repositoryId = getConfiguredRepositoryIdForStagingProfileOrFindAndCacheOneInGivenState(stagingProfileId, RepositoryState.OPEN) 21 | 22 | closeRepositoryByIdAndProfileIdWithRetrying(repositoryId, stagingProfileId) 23 | } 24 | 25 | private void closeRepositoryByIdAndProfileIdWithRetrying(String repositoryId, String stagingProfileId) { 26 | RepositoryCloser repositoryCloser = createRepositoryCloserWithGivenClient(createClient()) 27 | RepositoryStateFetcher repositoryStateFetcher = createRepositoryStateFetcherWithGivenClient(createClient()) 28 | OperationRetrier retrier = createOperationRetrier() 29 | RetryingRepositoryTransitioner retryingCloser = new RetryingRepositoryTransitioner(repositoryCloser, repositoryStateFetcher, retrier) 30 | 31 | retryingCloser.performWithRepositoryIdAndStagingProfileId(repositoryId, stagingProfileId) 32 | logger.info("Repository '$repositoryId' has been effectively closed") 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/groovy/io/codearte/gradle/nexus/CreateRepositoryTask.groovy: -------------------------------------------------------------------------------- 1 | package io.codearte.gradle.nexus 2 | 3 | import groovy.transform.CompileStatic 4 | import groovy.transform.InheritConstructors 5 | import io.codearte.gradle.nexus.infra.SimplifiedHttpJsonRestClient 6 | import io.codearte.gradle.nexus.logic.RepositoryCreator 7 | import org.gradle.api.Incubating 8 | import org.gradle.api.tasks.TaskAction 9 | 10 | @Incubating 11 | @CompileStatic 12 | @InheritConstructors(constructorAnnotations = true) 13 | class CreateRepositoryTask extends BaseStagingTask { 14 | 15 | @TaskAction 16 | void createStagingRepositoryAndSaveItsId() { 17 | String stagingProfileId = getConfiguredStagingProfileIdOrFindAndCacheOne(createProfileFetcherWithGivenClient(createClient())) 18 | createRepositoryAndSaveItsId(stagingProfileId) 19 | } 20 | 21 | private String createRepositoryAndSaveItsId(String stagingProfileId) { 22 | RepositoryCreator repositoryCreator = createRepositoryCreatorWithGivenClient(createClient()); 23 | String createdStagingRepositoryId = repositoryCreator.createStagingRepositoryAndReturnId(stagingProfileId); 24 | savePassedRepositoryIdForReusingInInOtherTasks(createdStagingRepositoryId) 25 | return createdStagingRepositoryId 26 | } 27 | 28 | private RepositoryCreator createRepositoryCreatorWithGivenClient(SimplifiedHttpJsonRestClient client) { 29 | return new RepositoryCreator(client, getServerUrl()) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/groovy/io/codearte/gradle/nexus/GetStagingProfileTask.groovy: -------------------------------------------------------------------------------- 1 | package io.codearte.gradle.nexus 2 | 3 | import groovy.transform.CompileStatic 4 | import groovy.transform.InheritConstructors 5 | import io.codearte.gradle.nexus.logic.StagingProfileFetcher 6 | import org.gradle.api.tasks.TaskAction 7 | 8 | @CompileStatic 9 | @InheritConstructors(constructorAnnotations = true) 10 | class GetStagingProfileTask extends BaseStagingTask { 11 | 12 | @TaskAction 13 | void doAction() { 14 | StagingProfileFetcher stagingProfileFetcher = createProfileFetcherWithGivenClient(createClient()) 15 | String receivedStagingProfileId = stagingProfileFetcher.getStagingProfileIdForPackageGroup(getPackageGroup()) 16 | logger.lifecycle("Received staging profile id: $receivedStagingProfileId") 17 | setStagingProfileId(receivedStagingProfileId) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/groovy/io/codearte/gradle/nexus/GradleUtil.groovy: -------------------------------------------------------------------------------- 1 | package io.codearte.gradle.nexus 2 | 3 | import groovy.transform.CompileStatic 4 | import org.gradle.api.Project 5 | 6 | @CompileStatic 7 | class GradleUtil { 8 | 9 | static boolean isPropertyNotDefinedOrFalse(Project project, String propertyName) { 10 | return !project.hasProperty(propertyName) || project.findProperty(propertyName) == "false" || project.findProperty(propertyName) == false 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/groovy/io/codearte/gradle/nexus/GradleVersionEnforcer.groovy: -------------------------------------------------------------------------------- 1 | package io.codearte.gradle.nexus 2 | 3 | import groovy.transform.CompileStatic 4 | import groovy.util.logging.Slf4j 5 | import org.gradle.api.GradleException 6 | import org.gradle.api.Incubating 7 | import org.gradle.api.Project 8 | import org.gradle.util.GradleVersion 9 | 10 | @CompileStatic 11 | @Slf4j 12 | @Incubating 13 | //TODO: Improve testability - current Gradle version cannot be easily mocked 14 | class GradleVersionEnforcer { 15 | 16 | private static final String DISABLE_GRADLE_VERSION_ENFORCEMENT_PROPERTY_NAME = 'gnsp.disableGradleVersionEnforcement' 17 | 18 | private final GradleVersion minimalSupportedVersion 19 | private final String propertyNameToDisable 20 | 21 | //TODO: Switch to @TuppleConstructor(defaults = false) while completely migrated to Gradle 5.x (with Groovy 2.5) 22 | private GradleVersionEnforcer(GradleVersion minimalSupportedVersion, String propertyNameToDisable) { 23 | this.minimalSupportedVersion = minimalSupportedVersion 24 | this.propertyNameToDisable = propertyNameToDisable 25 | } 26 | 27 | static GradleVersionEnforcer defaultEnforcer(GradleVersion minimalSupportedVersion) { 28 | return new GradleVersionEnforcer(minimalSupportedVersion, DISABLE_GRADLE_VERSION_ENFORCEMENT_PROPERTY_NAME) 29 | } 30 | 31 | void failBuildWithMeaningfulErrorIfAppliedOnTooOldGradleVersion(Project project) { 32 | if (GradleVersion.current() < minimalSupportedVersion) { 33 | log.warn("WARNING. The 'io.codearte.nexus-staging' plugin requires ${minimalSupportedVersion.version} to run properly " + 34 | "(detected: ${GradleVersion.current()}). Please upgrade your Gradle or downgrade the plugin version.") 35 | 36 | if (GradleUtil.isPropertyNotDefinedOrFalse(project, propertyNameToDisable)) { 37 | log.warn("Aborting the build with the meaningful error message to prevent confusion. If you are sure it is an error, " + 38 | "please report it in the plugin issue tracker and in the meantime use '-D${propertyNameToDisable}' to disable this check") 39 | 40 | throw new GradleException("'io.codearte.nexus-staging' requires Gradle ${minimalSupportedVersion.version}") 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/groovy/io/codearte/gradle/nexus/NexusStagingExtension.groovy: -------------------------------------------------------------------------------- 1 | package io.codearte.gradle.nexus 2 | 3 | import groovy.transform.CompileStatic 4 | import groovy.util.logging.Slf4j 5 | import org.gradle.api.Incubating 6 | import org.gradle.api.Project 7 | import org.gradle.api.model.ObjectFactory 8 | import org.gradle.api.provider.Property 9 | 10 | @SuppressWarnings("UnstableApiUsage") 11 | @CompileStatic 12 | @Slf4j 13 | //TODO: Re-enable once https://github.com/gradle/gradle/issues/11466 is fixed in Gradle 14 | //@ToString(includeFields = true, includeNames = true, includePackage = false) 15 | class NexusStagingExtension { 16 | 17 | String serverUrl 18 | String username 19 | String password 20 | String packageGroup 21 | String stagingProfileId 22 | @Incubating Integer numberOfRetries 23 | @Incubating Integer delayBetweenRetriesInMillis 24 | @Incubating String repositoryDescription //since 0.10.0 25 | 26 | @Incubating 27 | final Property stagingRepositoryId //since 0.20.0 28 | 29 | NexusStagingExtension(Project project) { 30 | ObjectFactory objectFactory = project.getObjects() 31 | stagingRepositoryId = objectFactory.property(String) 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/groovy/io/codearte/gradle/nexus/NexusStagingPlugin.groovy: -------------------------------------------------------------------------------- 1 | package io.codearte.gradle.nexus 2 | 3 | import io.codearte.gradle.nexus.logic.OperationRetrier 4 | import org.gradle.api.Plugin 5 | import org.gradle.api.Project 6 | import org.gradle.api.Task 7 | import org.gradle.api.artifacts.maven.MavenDeployer 8 | import org.gradle.api.execution.TaskExecutionGraph 9 | import org.gradle.api.logging.Logger 10 | import org.gradle.api.logging.Logging 11 | import org.gradle.api.tasks.Internal 12 | import org.gradle.api.tasks.Upload 13 | import org.gradle.util.GradleVersion 14 | 15 | import java.lang.invoke.MethodHandles 16 | 17 | @SuppressWarnings("UnstableApiUsage") 18 | class NexusStagingPlugin implements Plugin { 19 | 20 | @Internal //TODO: Switch type to GradleVersion 21 | public static final String MINIMAL_SUPPORTED_GRADLE_VERSION = "4.9" //public as used also in regression tests 22 | 23 | private final static Logger log = Logging.getLogger(MethodHandles.lookup().lookupClass()) 24 | 25 | private static final String GET_STAGING_PROFILE_TASK_NAME = "getStagingProfile" //TODO: Move to classes with task? 26 | private static final String CREATE_REPOSITORY_TASK_NAME = "createRepository" 27 | private static final String CLOSE_REPOSITORY_TASK_NAME = "closeRepository" 28 | private static final String RELEASE_REPOSITORY_TASK_NAME = "releaseRepository" 29 | private static final String CLOSE_AND_RELEASE_REPOSITORY_TASK_NAME = "closeAndReleaseRepository" 30 | 31 | private static final Set STAGING_TASK_CLASSES = [GetStagingProfileTask, CreateRepositoryTask, CloseRepositoryTask, 32 | ReleaseRepositoryTask] 33 | 34 | private static final String NEXUS_USERNAME_PROPERTY = 'nexusUsername' 35 | private static final String NEXUS_PASSWORD_PROPERTY = 'nexusPassword' 36 | 37 | private static final String DEFAULT_BASE_NEXUS_SERVER_URL = 'https://oss.sonatype.org/service/local/' 38 | private static final String DEFAULT_REPOSITORY_DESCRIPTION = 'Automatically released/promoted with gradle-nexus-staging-plugin!' 39 | 40 | private final GradleVersionEnforcer gradleVersionEnforcer 41 | private final ApplyOnlyOnRootProjectEnforcer applyOnlyOnRootProjectEnforcer 42 | 43 | private Project project 44 | private NexusStagingExtension extension 45 | 46 | NexusStagingPlugin() { 47 | this.gradleVersionEnforcer = GradleVersionEnforcer.defaultEnforcer(GradleVersion.version(MINIMAL_SUPPORTED_GRADLE_VERSION)) 48 | this.applyOnlyOnRootProjectEnforcer = new ApplyOnlyOnRootProjectEnforcer() 49 | } 50 | 51 | @Override 52 | void apply(Project project) { 53 | this.project = project 54 | this.extension = createAndConfigureExtension(project) 55 | gradleVersionEnforcer.failBuildWithMeaningfulErrorIfAppliedOnTooOldGradleVersion(project) 56 | applyOnlyOnRootProjectEnforcer.failBuildWithMeaningfulErrorIfAppliedNotOnRootProject(project) 57 | createAndConfigureGetStagingProfileTask(project) 58 | createAndConfigureCreateRepositoryTask(project) 59 | def closeRepositoryTask = createAndConfigureCloseRepositoryTask(project) 60 | def releaseRepositoryTask = createAndConfigureReleaseRepositoryTask(project) 61 | releaseRepositoryTask.mustRunAfter(closeRepositoryTask) 62 | def closeAndReleaseRepositoryTask = createAndConfigureCloseAndReleaseRepositoryTask(project) 63 | closeAndReleaseRepositoryTask.dependsOn(closeRepositoryTask, releaseRepositoryTask) 64 | tryToDetermineCredentials(project, extension) 65 | } 66 | 67 | private NexusStagingExtension createAndConfigureExtension(Project project) { 68 | NexusStagingExtension extension = project.extensions.create("nexusStaging", NexusStagingExtension, project) 69 | extension.with { 70 | serverUrl = DEFAULT_BASE_NEXUS_SERVER_URL 71 | numberOfRetries = OperationRetrier.DEFAULT_NUMBER_OF_RETRIES 72 | delayBetweenRetriesInMillis = OperationRetrier.DEFAULT_DELAY_BETWEEN_RETRIES_IN_MILLIS 73 | repositoryDescription = DEFAULT_REPOSITORY_DESCRIPTION 74 | } 75 | return extension 76 | } 77 | 78 | private void createAndConfigureGetStagingProfileTask(Project project) { 79 | GetStagingProfileTask task = project.tasks.create(GET_STAGING_PROFILE_TASK_NAME, GetStagingProfileTask, project, extension) 80 | setTaskDescriptionAndGroup(task, "Gets a staging profile id in Nexus - a diagnostic task") 81 | setTaskDefaultsAndDescription(task) 82 | } 83 | 84 | private void createAndConfigureCreateRepositoryTask(Project project) { 85 | CreateRepositoryTask task = project.tasks.create(CREATE_REPOSITORY_TASK_NAME, CreateRepositoryTask, project, extension) 86 | setTaskDescriptionAndGroup(task, "Internal task. Should not be used directly. Explicitly creates a staging repository in Nexus") 87 | setTaskDefaultsAndDescription(task) 88 | } 89 | 90 | private CloseRepositoryTask createAndConfigureCloseRepositoryTask(Project project) { 91 | CloseRepositoryTask task = project.tasks.create(CLOSE_REPOSITORY_TASK_NAME, CloseRepositoryTask, project, extension) 92 | setTaskDescriptionAndGroup(task, "Closes an open artifacts repository in Nexus") 93 | setTaskDefaultsAndDescription(task) 94 | return task 95 | } 96 | 97 | private ReleaseRepositoryTask createAndConfigureReleaseRepositoryTask(Project project) { 98 | ReleaseRepositoryTask task = project.tasks.create(RELEASE_REPOSITORY_TASK_NAME, ReleaseRepositoryTask, project, extension) 99 | setTaskDescriptionAndGroup(task, "Releases a closed artifacts repository in Nexus") 100 | setTaskDefaultsAndDescription(task) 101 | return task 102 | } 103 | 104 | private Task createAndConfigureCloseAndReleaseRepositoryTask(Project project) { 105 | Task task = project.tasks.create(CLOSE_AND_RELEASE_REPOSITORY_TASK_NAME) 106 | setTaskDescriptionAndGroup(task, "Closes and releases an artifacts repository in Nexus") 107 | return task 108 | } 109 | 110 | private void setTaskDescriptionAndGroup(Task task, String taskDescription) { 111 | task.with { 112 | description = taskDescription 113 | group = "release" 114 | } 115 | } 116 | 117 | private void setTaskDefaultsAndDescription(BaseStagingTask task) { 118 | task.conventionMapping.with { 119 | serverUrl = { extension.serverUrl } 120 | username = { extension.username } 121 | password = { extension.password } 122 | packageGroup = { 123 | if (extension.packageGroup) { 124 | return extension.packageGroup 125 | } else { 126 | return getProjectGroupOrNull(project) 127 | } 128 | } 129 | stagingProfileId = { extension.stagingProfileId } 130 | numberOfRetries = { extension.numberOfRetries } 131 | delayBetweenRetriesInMillis = { extension.delayBetweenRetriesInMillis } 132 | repositoryDescription = { extension.repositoryDescription } 133 | } 134 | } 135 | 136 | private String getProjectGroupOrNull(Project project) { 137 | log.debug("project.group: '{}', class: {}", project.getGroup(), project.getGroup()?.class) 138 | return project.getGroup() ?: null 139 | } 140 | 141 | //TODO: Extract to separate class 142 | private void tryToDetermineCredentials(Project project, NexusStagingExtension extension) { 143 | project.afterEvaluate { 144 | project.gradle.taskGraph.whenReady { TaskExecutionGraph taskGraph -> 145 | if (isAnyOfStagingTasksInTaskGraph(taskGraph)) { 146 | tryToGetCredentialsFromUploadArchivesTask(project, extension) 147 | tryToGetCredentialsFromGradleProperties(project, extension) 148 | } else { 149 | project.logger.debug("No staging task will be executed - skipping determination of Nexus credentials") 150 | } 151 | } 152 | } 153 | } 154 | 155 | private boolean isAnyOfStagingTasksInTaskGraph(TaskExecutionGraph taskGraph) { 156 | return taskGraph.allTasks.find { Task task -> 157 | return STAGING_TASK_CLASSES.find { Class stagingTaskClass -> 158 | //GetStagingProfileTask_Decorated is not assignable from GetStagingProfileTask, but its superclass is GetStagingProfileTask... 159 | return stagingTaskClass.isAssignableFrom(task.getClass().superclass) 160 | } 161 | } 162 | } 163 | 164 | @SuppressWarnings("GroovyUnnecessaryReturn") 165 | private void tryToGetCredentialsFromUploadArchivesTask(Project project, NexusStagingExtension extension) { 166 | if (extension.username != null && extension.password != null) { 167 | return //username and password already set 168 | } 169 | 170 | Task uploadTask = project.tasks.findByPath("uploadArchives") 171 | if (uploadTask instanceof Upload) { 172 | 173 | uploadTask?.repositories?.withType(MavenDeployer)?.each { MavenDeployer deployer -> 174 | project.logger.debug("Trying to read credentials from repository '${deployer.name}'") 175 | def authentication = deployer.repository?.authentication //Not to use class names as maven-ant-task is not on classpath when plugin is executed 176 | if (authentication?.userName != null) { 177 | extension.username = authentication.userName 178 | extension.password = authentication.password 179 | project.logger.info("Using username '${extension.username}' and password from repository '${deployer.name}'") 180 | return //from each 181 | } 182 | } 183 | } 184 | } 185 | 186 | private void tryToGetCredentialsFromGradleProperties(Project project, NexusStagingExtension extension) { 187 | if (extension.username == null && project.hasProperty(NEXUS_USERNAME_PROPERTY)) { 188 | extension.username = project.property(NEXUS_USERNAME_PROPERTY) 189 | project.logger.info("Using username '${extension.username}' from Gradle property '${NEXUS_USERNAME_PROPERTY}'") 190 | } 191 | if (extension.password == null && project.hasProperty(NEXUS_PASSWORD_PROPERTY)) { 192 | extension.password = project.property(NEXUS_PASSWORD_PROPERTY) 193 | project.logger.info("Using password '*****' from Gradle property '${NEXUS_PASSWORD_PROPERTY}'") 194 | } 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /src/main/groovy/io/codearte/gradle/nexus/ReleaseRepositoryTask.groovy: -------------------------------------------------------------------------------- 1 | package io.codearte.gradle.nexus 2 | 3 | import groovy.transform.CompileStatic 4 | import groovy.transform.InheritConstructors 5 | import io.codearte.gradle.nexus.logic.OperationRetrier 6 | import io.codearte.gradle.nexus.logic.RepositoryReleaser 7 | import io.codearte.gradle.nexus.logic.RepositoryState 8 | import io.codearte.gradle.nexus.logic.RepositoryStateFetcher 9 | import io.codearte.gradle.nexus.logic.RetryingRepositoryTransitioner 10 | import org.gradle.api.tasks.TaskAction 11 | 12 | @CompileStatic 13 | @InheritConstructors(constructorAnnotations = true) 14 | class ReleaseRepositoryTask extends BaseStagingTask { 15 | 16 | @TaskAction 17 | void releaseRepository() { 18 | //TODO: Remove once stagingProfileId migrated to plugin extension 19 | tryToTakeStagingProfileIdFromCloseRepositoryTask() 20 | 21 | String stagingProfileId = getConfiguredStagingProfileIdOrFindAndCacheOne(createProfileFetcherWithGivenClient(createClient())) 22 | String repositoryId = getConfiguredRepositoryIdForStagingProfileOrFindAndCacheOneInGivenState(stagingProfileId, RepositoryState.CLOSED) 23 | 24 | releaseRepositoryByIdAndProfileIdWithRetrying(repositoryId, stagingProfileId) 25 | } 26 | 27 | private void tryToTakeStagingProfileIdFromCloseRepositoryTask() { 28 | if (getStagingProfileId() != null) { 29 | return 30 | } 31 | String stagingProfileIdFromCloseRepositoryTask = getCloseRepositoryTask().stagingProfileId 32 | 33 | if (stagingProfileIdFromCloseRepositoryTask != null) { 34 | logger.debug("Reusing staging profile id from closeRepository task: $stagingProfileIdFromCloseRepositoryTask") 35 | setStagingProfileId(stagingProfileIdFromCloseRepositoryTask) 36 | } 37 | } 38 | 39 | private CloseRepositoryTask getCloseRepositoryTask() { 40 | return project.tasks.withType(CloseRepositoryTask)[0] 41 | } 42 | 43 | private void releaseRepositoryByIdAndProfileIdWithRetrying(String repositoryId, String stagingProfileId) { 44 | RepositoryReleaser repositoryReleaser = createRepositoryReleaserWithGivenClient(createClient()) 45 | RepositoryStateFetcher repositoryStateFetcher = createRepositoryStateFetcherWithGivenClient(createClient()) 46 | OperationRetrier retrier = createOperationRetrier() 47 | RetryingRepositoryTransitioner retryingReleaser = new RetryingRepositoryTransitioner(repositoryReleaser, repositoryStateFetcher, retrier) 48 | 49 | retryingReleaser.performWithRepositoryIdAndStagingProfileId(repositoryId, stagingProfileId) 50 | logger.info("Repository '$repositoryId' has been effectively released") 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/groovy/io/codearte/gradle/nexus/exception/RepositoryInTransitionException.groovy: -------------------------------------------------------------------------------- 1 | package io.codearte.gradle.nexus.exception 2 | 3 | import groovy.transform.CompileStatic 4 | import io.codearte.gradle.nexus.infra.NexusStagingException 5 | 6 | @CompileStatic 7 | class RepositoryInTransitionException extends NexusStagingException { 8 | 9 | final String repositoryId 10 | final String state 11 | 12 | RepositoryInTransitionException(String repositoryId, String state) { 13 | super("Repository '$repositoryId' (in '$state' state) is in transition. Check again later.") 14 | this.repositoryId = repositoryId 15 | this.state = state 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/groovy/io/codearte/gradle/nexus/exception/UnexpectedRepositoryState.groovy: -------------------------------------------------------------------------------- 1 | package io.codearte.gradle.nexus.exception 2 | 3 | import groovy.transform.CompileStatic 4 | import io.codearte.gradle.nexus.infra.NexusStagingException 5 | import io.codearte.gradle.nexus.logic.RepositoryState 6 | 7 | @CompileStatic 8 | class UnexpectedRepositoryState extends NexusStagingException { 9 | 10 | final String repoId 11 | final String actualState 12 | final String expectedStates 13 | 14 | UnexpectedRepositoryState(String repoId, RepositoryState actualState, List expectedStates) { 15 | super("Wrong '$repoId' repository state '$actualState' after transition (expected $expectedStates). " + 16 | "Possible staging rules violation. Check repository status using Nexus UI.") 17 | this.expectedStates = expectedStates 18 | this.actualState = actualState 19 | this.repoId = repoId 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/groovy/io/codearte/gradle/nexus/exception/UnsupportedRepositoryState.groovy: -------------------------------------------------------------------------------- 1 | package io.codearte.gradle.nexus.exception 2 | 3 | import groovy.transform.CompileStatic 4 | import io.codearte.gradle.nexus.infra.NexusStagingException 5 | import io.codearte.gradle.nexus.logic.RepositoryState 6 | 7 | @CompileStatic 8 | class UnsupportedRepositoryState extends NexusStagingException { 9 | 10 | final String unsupportedState 11 | 12 | UnsupportedRepositoryState(String unsupportedState) { 13 | super("Unsupported repository state '${unsupportedState}'. Supported values: ${RepositoryState.values()}") 14 | this.unsupportedState = unsupportedState 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/groovy/io/codearte/gradle/nexus/infra/NexusHttpResponseException.groovy: -------------------------------------------------------------------------------- 1 | package io.codearte.gradle.nexus.infra 2 | 3 | import groovy.transform.CompileStatic 4 | 5 | /** 6 | * Custom exception to propagate server errors. 7 | * 8 | * Created as OkHttp does not throw exceptions for HTTP errors (e.g. Server Error) 9 | * which in many cases is crucial to determine the resons why error was returned. 10 | * 11 | * It may be made redundant once migrated to other HTTP library. 12 | */ 13 | @CompileStatic 14 | class NexusHttpResponseException extends NexusStagingException { 15 | 16 | final int statusCode 17 | 18 | NexusHttpResponseException(int statusCode, String message) { 19 | super(message) 20 | this.statusCode = statusCode 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/groovy/io/codearte/gradle/nexus/infra/NexusStagingException.groovy: -------------------------------------------------------------------------------- 1 | package io.codearte.gradle.nexus.infra 2 | 3 | import groovy.transform.CompileStatic 4 | import groovy.transform.InheritConstructors 5 | 6 | @InheritConstructors 7 | @CompileStatic 8 | class NexusStagingException extends RuntimeException { 9 | } 10 | -------------------------------------------------------------------------------- /src/main/groovy/io/codearte/gradle/nexus/infra/SimplifiedHttpJsonRestClient.groovy: -------------------------------------------------------------------------------- 1 | package io.codearte.gradle.nexus.infra 2 | 3 | import groovy.json.JsonOutput 4 | import groovy.json.JsonSlurper 5 | import groovy.transform.CompileStatic 6 | import groovy.util.logging.Slf4j 7 | import java.util.concurrent.TimeUnit 8 | import javax.annotation.concurrent.ThreadSafe 9 | import okhttp3.Credentials 10 | import okhttp3.Interceptor 11 | import okhttp3.OkHttpClient 12 | import okhttp3.Request 13 | import okhttp3.RequestBody 14 | import okhttp3.Response 15 | import org.jetbrains.annotations.NotNull 16 | 17 | /** 18 | * Specialized REST client to communicate with Nexus. 19 | */ 20 | @CompileStatic 21 | @Slf4j 22 | @ThreadSafe 23 | class SimplifiedHttpJsonRestClient { 24 | 25 | private static final String CONTENT_TYPE_JSON = "application/json" 26 | 27 | private final OkHttpClient restClient 28 | 29 | SimplifiedHttpJsonRestClient(OkHttpClient restClient, String username, String password) { 30 | OkHttpClient.Builder clientBuilder = createClientBuilderWithDefaultTimeout(restClient) 31 | .addInterceptor(new Interceptor() { 32 | @Override 33 | Response intercept(@NotNull Interceptor.Chain chain) throws IOException { 34 | Request.Builder request = chain.request().newBuilder() 35 | .header("Content-Type", CONTENT_TYPE_JSON) 36 | .header("Accept", CONTENT_TYPE_JSON) 37 | 38 | if (username != null && password != null) { 39 | request.header("Authorization", Credentials.basic(username, password)) 40 | } 41 | 42 | return chain.proceed(request.build()) 43 | } 44 | }) 45 | this.restClient = clientBuilder.build() 46 | } 47 | 48 | private OkHttpClient.Builder createClientBuilderWithDefaultTimeout(OkHttpClient restClient) { 49 | //Could be made parameterized if needed 50 | return restClient.newBuilder() 51 | .connectTimeout(300, TimeUnit.SECONDS) 52 | .readTimeout(300, TimeUnit.SECONDS) 53 | .callTimeout(300, TimeUnit.SECONDS) 54 | .writeTimeout(300, TimeUnit.SECONDS) 55 | } 56 | 57 | Map get(String uri) { 58 | Request request = new Request.Builder() 59 | .url(uri) 60 | .build() 61 | return sendRequestHandlingErrors(request) 62 | } 63 | 64 | Map post(String uri, Map content) { 65 | Request request = new Request.Builder() 66 | .post(RequestBody.create(JsonOutput.toJson(content), null)) 67 | .url(uri) 68 | .build() 69 | return sendRequestHandlingErrors(request) 70 | } 71 | 72 | private Map sendRequestHandlingErrors(Request request) { 73 | try { 74 | return restClient.newCall(request).execute().withCloseable { 75 | if (!it.successful) { 76 | String message = "${it.code()}: ${it.message()}, body: ${it.body().string() ?: ''}" 77 | //TODO: Suppress error message on 404 if waiting for drop? 78 | log.warn("${request.method()} request failed. ${message}") 79 | throw new NexusHttpResponseException(it.code(), message) 80 | } 81 | if (it.body().contentLength() == 0) { 82 | return [:] 83 | } else { 84 | (Map) new JsonSlurper().parse(it.body().byteStream()) 85 | } 86 | } 87 | } catch (IOException e) { 88 | throw new NexusStagingException("Could not connect to Nexus.", e) 89 | } 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/main/groovy/io/codearte/gradle/nexus/infra/WrongNumberOfRepositories.groovy: -------------------------------------------------------------------------------- 1 | package io.codearte.gradle.nexus.infra 2 | 3 | import groovy.transform.CompileStatic 4 | 5 | @CompileStatic 6 | class WrongNumberOfRepositories extends NexusStagingException { 7 | 8 | final int numberOfRepositories 9 | final String state 10 | 11 | WrongNumberOfRepositories(int numberOfRepositories, String state) { 12 | super("Wrong number of received repositories in state '$state'. Expected 1, received $numberOfRepositories") 13 | this.numberOfRepositories = numberOfRepositories 14 | this.state = state 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/groovy/io/codearte/gradle/nexus/infra/WrongNumberOfStagingProfiles.groovy: -------------------------------------------------------------------------------- 1 | package io.codearte.gradle.nexus.infra 2 | 3 | import groovy.transform.CompileStatic 4 | 5 | @CompileStatic 6 | class WrongNumberOfStagingProfiles extends NexusStagingException { 7 | 8 | final int numberOfProfiles 9 | final String packageGroup 10 | 11 | WrongNumberOfStagingProfiles(int numberOfProfiles, String packageGroup) { 12 | super("Wrong number of received staging profiles for '$packageGroup'. Expected 1, received $numberOfProfiles. " + 13 | "Have you configured 'packageGroup' correctly?") 14 | this.numberOfProfiles = numberOfProfiles 15 | this.packageGroup = packageGroup 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/groovy/io/codearte/gradle/nexus/legacy/NexusUploadStagingPlugin.groovy: -------------------------------------------------------------------------------- 1 | package io.codearte.gradle.nexus.legacy 2 | 3 | import groovy.transform.CompileDynamic 4 | import groovy.transform.CompileStatic 5 | import groovy.util.logging.Slf4j 6 | import io.codearte.gradle.nexus.CloseRepositoryTask 7 | import io.codearte.gradle.nexus.CreateRepositoryTask 8 | import io.codearte.gradle.nexus.GradleUtil 9 | import io.codearte.gradle.nexus.GradleVersionEnforcer 10 | import io.codearte.gradle.nexus.NexusStagingExtension 11 | import io.codearte.gradle.nexus.NexusStagingPlugin 12 | import org.gradle.api.Incubating 13 | import org.gradle.api.Plugin 14 | import org.gradle.api.Project 15 | import org.gradle.api.Task 16 | import org.gradle.api.artifacts.maven.MavenDeployer 17 | import org.gradle.api.plugins.MavenPlugin 18 | import org.gradle.api.tasks.TaskCollection 19 | import org.gradle.api.tasks.TaskProvider 20 | import org.gradle.api.tasks.Upload 21 | import org.gradle.plugins.signing.Sign 22 | import org.gradle.util.GradleVersion 23 | 24 | import javax.annotation.Nonnull 25 | 26 | /** 27 | * Plugin for internal use only with "legacy" UploadArchives mechanism. It's most likely not something that you are looking for. 28 | */ 29 | @CompileStatic 30 | @Incubating 31 | @Slf4j 32 | @SuppressWarnings("UnstableApiUsage") 33 | class NexusUploadStagingPlugin implements Plugin { 34 | 35 | private static final String UPLOAD_TO_NEXUS_STAGING_TASK_NAME = "uploadArchivesStaging" 36 | private static final String POINT_UPLOAD_ARCHIVES_TO_EXPLICIT_REPOSITORY = "pointUploadArchivesToExplicitRepository" 37 | 38 | private static final String DISABLE_LEGACY_WARNING_PROPERTY_NAME = "gnsp.disableLegacyWarning" 39 | 40 | private static final String MINIMAL_SUPPORTED_GRADLE_VERSION = NexusStagingPlugin.MINIMAL_SUPPORTED_GRADLE_VERSION 41 | 42 | private final GradleVersionEnforcer gradleVersionEnforcer 43 | 44 | NexusUploadStagingPlugin() { 45 | this.gradleVersionEnforcer = GradleVersionEnforcer.defaultEnforcer(GradleVersion.version(MINIMAL_SUPPORTED_GRADLE_VERSION)) 46 | } 47 | 48 | @Override 49 | void apply(@Nonnull Project project) { 50 | gradleVersionEnforcer.failBuildWithMeaningfulErrorIfAppliedOnTooOldGradleVersion(project) 51 | 52 | project.getPluginManager().apply(MavenPlugin) 53 | project.getPluginManager().apply(NexusStagingPlugin) 54 | 55 | displayVerboseWarningMessage(project) 56 | 57 | TaskProvider pointUploadArchivesToExplicitRepository = project.tasks 58 | .register(POINT_UPLOAD_ARCHIVES_TO_EXPLICIT_REPOSITORY, PointUploadArchivesToExplicitRepositoryTask, project, 59 | (NexusStagingExtension) project.getExtensions().getByType(NexusStagingExtension)) //casting due to Idea warning 60 | pointUploadArchivesToExplicitRepository.configure { Task task -> 61 | task.setDescription("LEGACY WARNING. Do not use this tasks. Points uploadArchives tasks to explicitly created staging repository in Nexus") 62 | task.setGroup("release") 63 | } 64 | 65 | TaskProvider uploadStagingTask = project.tasks.register(UPLOAD_TO_NEXUS_STAGING_TASK_NAME) { Task task -> 66 | task.setDescription("LEGACY WARNING. Do not use this tasks. Uploads artifacts to explicitly created staging repository in Nexus") 67 | task.setGroup("release") 68 | } 69 | 70 | configureTaskDependencies(project, pointUploadArchivesToExplicitRepository, uploadStagingTask) 71 | } 72 | 73 | private void displayVerboseWarningMessage(Project project) { 74 | if (GradleUtil.isPropertyNotDefinedOrFalse(project, DISABLE_LEGACY_WARNING_PROPERTY_NAME)) { 75 | log.warn(""" 76 | WARNING. The 'io.codearte.nexus-upload-staging' (sub)plugin was created only 77 | for internal usage to support releasing with CDeliveryBoy from Travis. 78 | It is completely not supported and it is recommended to upgrade your 79 | project to use 'maven-publish' plugin with `nexus-publish-plugin`. 80 | """.stripIndent()) 81 | } 82 | } 83 | 84 | @CompileDynamic 85 | void configureTaskDependencies(@Nonnull Project project, TaskProvider pointUploadArchivesToExplicitRepositoryTask, 86 | TaskProvider uploadStagingTask) { 87 | 88 | project.afterEvaluate { Project evaluatedProject -> 89 | TaskCollection createRepositoryTasks = project.tasks.withType(CreateRepositoryTask) 90 | 91 | project.tasks 92 | .withType(Upload) 93 | // .matching(uploadTask -> hasMatchingRepositoryUrl(serverUrl) //Is everything already set/updated here? 94 | .matching { uploadTask -> !uploadTask.repositories.withType(MavenDeployer).isEmpty() } //Exclude "install" task 95 | .configureEach { Upload uploadTask -> 96 | log.info("Creating extra stating dependencies for task ${uploadTask}") 97 | uploadTask.mustRunAfter(pointUploadArchivesToExplicitRepositoryTask) 98 | // //Eager evaluation as TaskProvider.configure() doesn't work with Gradle 5+ in the embedded way: 99 | // // > DefaultTaskContainer#NamedDomainObjectProvider.configure(Action) on task set cannot be executed in the current context. 100 | // uploadStagingTask.configure { task -> task.dependsOn(uploadTask) } 101 | uploadStagingTask.get().dependsOn(uploadTask) 102 | } 103 | 104 | //TODO: createRepository should be executed right before uploadArchives - not before compileJava... 105 | pointUploadArchivesToExplicitRepositoryTask.configure { task -> task.dependsOn(createRepositoryTasks) } 106 | uploadStagingTask.configure { task -> 107 | task.dependsOn(createRepositoryTasks, pointUploadArchivesToExplicitRepositoryTask) 108 | } 109 | 110 | TaskCollection closeRepositoryTasks = project.tasks.withType(CloseRepositoryTask) 111 | closeRepositoryTasks*.mustRunAfter(uploadStagingTask) 112 | 113 | createRepositoryTasks*.mustRunAfter(project.tasks.withType(Sign)) //to prevent it's execute before compilation where things can easily fail. Sign is not perfect 114 | createRepositoryTasks*.onlyIf { isInNonSnapshotVersion(project) } 115 | pointUploadArchivesToExplicitRepositoryTask.configure { task -> task.onlyIf { isInNonSnapshotVersion(project) } } 116 | } 117 | } 118 | 119 | private boolean isInNonSnapshotVersion(Project project) { 120 | //TODO: Should configurable (e.g. disabled on demand) in extension, but there is no separate extension for NexusUploadStagingPlugin 121 | // and it would pollute that from NexusStagingPlugin 122 | return !project.getVersion().toString().endsWith("-SNAPSHOT") 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /src/main/groovy/io/codearte/gradle/nexus/legacy/PointUploadArchivesToExplicitRepositoryTask.groovy: -------------------------------------------------------------------------------- 1 | package io.codearte.gradle.nexus.legacy 2 | 3 | import groovy.transform.CompileDynamic 4 | import groovy.transform.CompileStatic 5 | import groovy.transform.PackageScope 6 | import io.codearte.gradle.nexus.NexusStagingExtension 7 | import org.gradle.api.DefaultTask 8 | import org.gradle.api.Incubating 9 | import org.gradle.api.Project 10 | import org.gradle.api.artifacts.maven.MavenDeployer 11 | import org.gradle.api.model.ObjectFactory 12 | import org.gradle.api.provider.Property 13 | import org.gradle.api.tasks.Input 14 | import org.gradle.api.tasks.Optional 15 | import org.gradle.api.tasks.SkipWhenEmpty 16 | import org.gradle.api.tasks.TaskAction 17 | import org.gradle.api.tasks.Upload 18 | 19 | import javax.inject.Inject 20 | 21 | /** 22 | * Task for internal use only with "legacy" UploadArchives mechanism. It's most likely not something that you are looking for. 23 | */ 24 | @CompileStatic 25 | @Incubating 26 | @SuppressWarnings("UnstableApiUsage") 27 | class PointUploadArchivesToExplicitRepositoryTask extends DefaultTask { 28 | 29 | @Input 30 | @Optional 31 | @SkipWhenEmpty 32 | @Incubating 33 | final Property stagingRepositoryId 34 | 35 | @Input 36 | @Incubating 37 | final Property serverUrl 38 | 39 | @Inject 40 | PointUploadArchivesToExplicitRepositoryTask(Project project, NexusStagingExtension extension) { 41 | ObjectFactory objectFactory = project.getObjects(); 42 | stagingRepositoryId = objectFactory.property(String) 43 | stagingRepositoryId.set(extension.stagingRepositoryId) 44 | serverUrl = objectFactory.property(String) 45 | serverUrl.set(project.provider({ extension.serverUrl })) 46 | } 47 | 48 | @TaskAction 49 | @CompileDynamic 50 | void updateDeploymentUrlInUploadTasks() { 51 | assert stagingRepositoryId.isPresent(), "For not provided stagingRepositoryId task should be skipped" 52 | 53 | project.tasks.withType(Upload) { task -> 54 | task.repositories?.withType(MavenDeployer)?.each { MavenDeployer deployer -> 55 | 56 | logger.debug("Processing MavenDeployer with repository: ${deployer.repository}") 57 | if (!deployer.repository) { 58 | logger.warn("Skipping MavenDeployer (${deployer}) with no repository. It can result in errors in other tasks.") 59 | return //from closure for given MavenDeployer 60 | } 61 | 62 | logger.info("Processing MavenDeployer repository with repository url: ${deployer.repository.url}") 63 | if (deployer.repository.url.startsWith(getServerUrl().get())) { 64 | deployer.repository.url = "${removeTrailingSlashIfAvailable(getServerUrl().get())}/staging/deployByRepositoryId/${stagingRepositoryId.get()}" 65 | } else { 66 | logger.info("Skipping adjusting Upload task with non matching URL: ${deployer.repository.url}") 67 | } 68 | } 69 | } 70 | } 71 | 72 | //TODO: Duplication with BaseOperationExecutor, consider extract to some Util class 73 | @PackageScope 74 | //Cannot be private due to execution in Closure 75 | String removeTrailingSlashIfAvailable(String nexusUrl) { 76 | return nexusUrl.endsWith("/") ? nexusUrl[0..-2] : nexusUrl 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/groovy/io/codearte/gradle/nexus/logic/AbstractRepositoryTransitioner.groovy: -------------------------------------------------------------------------------- 1 | package io.codearte.gradle.nexus.logic 2 | 3 | import groovy.transform.CompileStatic 4 | import groovy.transform.InheritConstructors 5 | import io.codearte.gradle.nexus.infra.SimplifiedHttpJsonRestClient 6 | 7 | @InheritConstructors 8 | @CompileStatic 9 | abstract class AbstractRepositoryTransitioner extends BaseOperationExecutor implements RepositoryTransition { 10 | 11 | protected final String repositoryDescription 12 | 13 | protected AbstractRepositoryTransitioner(SimplifiedHttpJsonRestClient client, String nexusUrl, String repositoryDescription) { 14 | super(client, nexusUrl) 15 | this.repositoryDescription = repositoryDescription 16 | } 17 | 18 | protected Map prepareStagingPostContentWithGivenRepositoryIdAndStagingId(String repositoryId, String stagingProfileId) { 19 | return [data: [ 20 | stagedRepositoryIds: [repositoryId], 21 | description: repositoryDescription, 22 | autoDropAfterRelease: true 23 | // stagingProfileGroup: stagingProfileId, //Cannot be set as it triggers "promote" command not "release" 24 | 25 | ]] as Map //coercion required to prevent: Incompatible generic argument types. Cannot assign java.util.LinkedHashMap 26 | // to: java.util.Map in Groovy 2.4.10 27 | } 28 | 29 | protected String pathForGivenBulkOperation(String operationName) { 30 | return "${nexusUrl}/staging/bulk/$operationName" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/groovy/io/codearte/gradle/nexus/logic/BaseOperationExecutor.groovy: -------------------------------------------------------------------------------- 1 | package io.codearte.gradle.nexus.logic 2 | 3 | import groovy.transform.CompileStatic 4 | import io.codearte.gradle.nexus.infra.SimplifiedHttpJsonRestClient 5 | 6 | //@TupleConstructor //TODO: Does not want to work 7 | @CompileStatic 8 | abstract class BaseOperationExecutor { 9 | 10 | protected final SimplifiedHttpJsonRestClient client 11 | protected final String nexusUrl 12 | 13 | BaseOperationExecutor(SimplifiedHttpJsonRestClient client, String nexusUrl) { 14 | this.client = client 15 | this.nexusUrl = removeTrailingSlashIfAvailable(nexusUrl) 16 | } 17 | 18 | private String removeTrailingSlashIfAvailable(String nexusUrl) { 19 | return nexusUrl.endsWith("/") ? nexusUrl[0..-2] : nexusUrl 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/groovy/io/codearte/gradle/nexus/logic/OperationRetrier.groovy: -------------------------------------------------------------------------------- 1 | package io.codearte.gradle.nexus.logic 2 | 3 | import groovy.transform.PackageScope 4 | import groovy.util.logging.Slf4j 5 | import io.codearte.gradle.nexus.exception.RepositoryInTransitionException 6 | import io.codearte.gradle.nexus.infra.WrongNumberOfRepositories 7 | 8 | @Slf4j 9 | class OperationRetrier { 10 | 11 | public static final int DEFAULT_NUMBER_OF_RETRIES = 60 12 | public static final int DEFAULT_DELAY_BETWEEN_RETRIES_IN_MILLIS = 5000 13 | 14 | private final int numberOfRetries 15 | private final int delayBetweenRetriesInMillis 16 | 17 | OperationRetrier(int numberOfRetries = DEFAULT_NUMBER_OF_RETRIES, int delayBetweenRetriesInMillis = DEFAULT_DELAY_BETWEEN_RETRIES_IN_MILLIS) { 18 | this.numberOfRetries = numberOfRetries 19 | this.delayBetweenRetriesInMillis = delayBetweenRetriesInMillis 20 | } 21 | 22 | T doWithRetry(Closure operation) { 23 | int counter = 0 24 | int numberOfAttempts = numberOfRetries + 1 25 | while (true) { 26 | try { 27 | counter++ 28 | log.debug("Attempt $counter/$numberOfAttempts...") 29 | T result = operation() 30 | if (counter > 1) { 31 | //To do not leave build with "GET request failed. 404: Not Found" message as the last one without "info" logging enabled 32 | //See https://github.com/Codearte/gradle-nexus-staging-plugin/issues/60 33 | log.warn("Requested operation was executed successfully in attempt $counter (maximum allowed $numberOfAttempts)") 34 | } 35 | return result 36 | } catch (WrongNumberOfRepositories | RepositoryInTransitionException e) { //Exceptions to catch could be configurable if needed 37 | String message = "Attempt $counter/$numberOfAttempts failed. ${e.getClass().getSimpleName()} was thrown with message '${e.message}'" 38 | if (counter >= numberOfAttempts) { 39 | //TODO: Switch to Gradle logger and use lifecycle level 40 | log.warn("$message. Giving up. Configure longer timeout if necessary.") 41 | //Maybe wrap exception with retrying exception suggesting timeout issues (and original message appended at the end)? 42 | throw e 43 | } else { 44 | if (counter == 1) { 45 | log.info("Requested operation wasn't successful in first try. ${formatMaximumRetryingPeriodMessage()}.") 46 | } 47 | log.info("$message. Waiting $delayBetweenRetriesInMillis ms before next retry.") 48 | waitBeforeNextAttempt() 49 | } 50 | } 51 | } 52 | } 53 | 54 | //visible for testing 55 | @PackageScope 56 | void waitBeforeNextAttempt() { 57 | //sleep() hangs the thread, but in that case it doesn't matter - switch to https://github.com/nurkiewicz/async-retry/ if really needed 58 | sleep(delayBetweenRetriesInMillis) 59 | } 60 | 61 | private String formatMaximumRetryingPeriodMessage() { 62 | return "Retrying maximum $numberOfRetries times with ${delayBetweenRetriesInMillis / 1000} seconds delay between attempts" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/groovy/io/codearte/gradle/nexus/logic/RepositoryCloser.groovy: -------------------------------------------------------------------------------- 1 | package io.codearte.gradle.nexus.logic 2 | 3 | import groovy.transform.CompileStatic 4 | import groovy.util.logging.Slf4j 5 | import io.codearte.gradle.nexus.infra.SimplifiedHttpJsonRestClient 6 | 7 | @CompileStatic 8 | @Slf4j 9 | class RepositoryCloser extends AbstractRepositoryTransitioner { 10 | 11 | RepositoryCloser(SimplifiedHttpJsonRestClient client, String nexusUrl, String repositoryDescription) { 12 | super(client, nexusUrl, repositoryDescription) 13 | } 14 | 15 | @Deprecated 16 | void closeRepositoryWithIdAndStagingProfileId(String repositoryId, String stagingProfileId) { 17 | performWithRepositoryIdAndStagingProfileId(repositoryId, stagingProfileId) 18 | } 19 | 20 | @Override 21 | void performWithRepositoryIdAndStagingProfileId(String repositoryId, String stagingProfileId) { 22 | log.info("Closing repository '$repositoryId' with staging profile '$stagingProfileId'") 23 | Map postContent = prepareStagingPostContentWithGivenRepositoryIdAndStagingId(repositoryId, stagingProfileId) 24 | client.post(pathForGivenBulkOperation("close"), postContent) 25 | log.info("Repository '$repositoryId' with staging profile '$stagingProfileId' has been accepted by server to be closed") 26 | } 27 | 28 | @Override 29 | List desiredAfterTransitionRepositoryState() { 30 | return [RepositoryState.CLOSED] 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/groovy/io/codearte/gradle/nexus/logic/RepositoryCreator.groovy: -------------------------------------------------------------------------------- 1 | package io.codearte.gradle.nexus.logic 2 | 3 | import groovy.transform.CompileDynamic 4 | import groovy.transform.CompileStatic 5 | import groovy.transform.InheritConstructors 6 | import groovy.util.logging.Slf4j 7 | 8 | @CompileStatic 9 | @InheritConstructors 10 | @Slf4j 11 | class RepositoryCreator extends BaseOperationExecutor { 12 | 13 | String createStagingRepositoryAndReturnId(String stagingProfileId) { 14 | log.info("Creating staging repository for staging profile '$stagingProfileId'") 15 | //TODO: Pass package group (and root project name + version?) as description 16 | Map responseAsMap = client.post(nexusUrl + "/staging/profiles/${stagingProfileId}/start", 17 | [data: [description: "Explicitly created by gradle-nexus-staging-plugin"]]) 18 | 19 | String repositoryId = getStagingRepositoryIdFromResponseMap(responseAsMap) 20 | log.info("Created staging repository with id: $repositoryId") 21 | return repositoryId 22 | } 23 | 24 | @CompileDynamic 25 | private String getStagingRepositoryIdFromResponseMap(Map responseAsMap) { 26 | return responseAsMap.data.stagedRepositoryId 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/groovy/io/codearte/gradle/nexus/logic/RepositoryDropper.groovy: -------------------------------------------------------------------------------- 1 | package io.codearte.gradle.nexus.logic 2 | 3 | import groovy.transform.CompileStatic 4 | import groovy.util.logging.Slf4j 5 | import io.codearte.gradle.nexus.infra.SimplifiedHttpJsonRestClient 6 | import org.gradle.api.Incubating 7 | 8 | @CompileStatic 9 | @Slf4j 10 | @Incubating 11 | class RepositoryDropper extends AbstractRepositoryTransitioner { 12 | 13 | RepositoryDropper(SimplifiedHttpJsonRestClient client, String nexusUrl, String repositoryDescription) { 14 | super(client, nexusUrl, repositoryDescription) 15 | } 16 | 17 | @Override 18 | void performWithRepositoryIdAndStagingProfileId(String repositoryId, String stagingProfileId) { 19 | log.info("Droping repository '$repositoryId' with staging profile '$stagingProfileId'") 20 | Map postContent = prepareStagingPostContentWithGivenRepositoryIdAndStagingId(repositoryId, stagingProfileId) 21 | client.post(pathForGivenBulkOperation("drop"), postContent) 22 | log.info("Repository '$repositoryId' with staging profile '$stagingProfileId' has been accepted by server to be dropped") 23 | } 24 | 25 | @Override 26 | List desiredAfterTransitionRepositoryState() { 27 | throw new UnsupportedOperationException("Not implemented yet") 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/groovy/io/codearte/gradle/nexus/logic/RepositoryFetcher.groovy: -------------------------------------------------------------------------------- 1 | package io.codearte.gradle.nexus.logic 2 | 3 | import groovy.transform.CompileStatic 4 | import groovy.transform.InheritConstructors 5 | import groovy.util.logging.Slf4j 6 | import io.codearte.gradle.nexus.infra.WrongNumberOfRepositories 7 | 8 | @InheritConstructors 9 | @CompileStatic 10 | @Slf4j 11 | class RepositoryFetcher extends BaseOperationExecutor { 12 | 13 | String getRepositoryIdWithGivenStateForStagingProfileId(String stagingProfileId, RepositoryState state) { 14 | log.info("Getting '$state' repository for staging profile '$stagingProfileId'") 15 | Map allStagingRepositoriesResponseAsMap = client.get(nexusUrl + "/staging/profile_repositories/$stagingProfileId") //TODO: Constant 16 | return parseResponseAndGetRepositoryIdInGivenState(allStagingRepositoriesResponseAsMap, state.toString()) 17 | } 18 | 19 | private String parseResponseAndGetRepositoryIdInGivenState(Map allStagingRepositoriesResponseAsMap, String repositoryState) { 20 | Map repository = verifyThatOneRepositoryAndReturnIt(allStagingRepositoriesResponseAsMap, repositoryState) 21 | log.debug("Received 1 '$repositoryState' repository with id: ${repository.repositoryId}") 22 | return repository.repositoryId 23 | } 24 | 25 | private Map verifyThatOneRepositoryAndReturnIt(Map responseAsMap, String repositoryState) { 26 | Closure repositoryInGivenState = { Map repository -> repository.type == repositoryState } 27 | int numberOfRepositories = (Integer) responseAsMap.data.count(repositoryInGivenState) 28 | if (numberOfRepositories != 1) { 29 | throw new WrongNumberOfRepositories(numberOfRepositories, repositoryState) 30 | } 31 | return responseAsMap.data.find(repositoryInGivenState) as Map 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/groovy/io/codearte/gradle/nexus/logic/RepositoryReleaser.groovy: -------------------------------------------------------------------------------- 1 | package io.codearte.gradle.nexus.logic 2 | 3 | import groovy.transform.CompileStatic 4 | import groovy.util.logging.Slf4j 5 | import io.codearte.gradle.nexus.infra.SimplifiedHttpJsonRestClient 6 | 7 | @CompileStatic 8 | @Slf4j 9 | class RepositoryReleaser extends AbstractRepositoryTransitioner { 10 | 11 | RepositoryReleaser(SimplifiedHttpJsonRestClient client, String nexusUrl, String repositoryDescription) { 12 | super(client, nexusUrl, repositoryDescription) 13 | } 14 | private static final String RELEASE_OPERATION_NAME_IN_NEXUS = "promote" //promote and release use the same operation, used parameters matter 15 | 16 | @Override 17 | void performWithRepositoryIdAndStagingProfileId(String repositoryId, String stagingProfileId) { 18 | log.info("Releasing repository '$repositoryId' with staging profile '$stagingProfileId'") 19 | Map postContent = prepareStagingPostContentWithGivenRepositoryIdAndStagingId(repositoryId, stagingProfileId) 20 | client.post(pathForGivenBulkOperation(RELEASE_OPERATION_NAME_IN_NEXUS), postContent) 21 | log.info("Repository '$repositoryId' with staging profile '$stagingProfileId' has been accepted by server to be released") 22 | } 23 | 24 | @Override 25 | List desiredAfterTransitionRepositoryState() { 26 | return [RepositoryState.RELEASED, RepositoryState.NOT_FOUND] //depending if "auto drop after release" is enabled 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/groovy/io/codearte/gradle/nexus/logic/RepositoryState.groovy: -------------------------------------------------------------------------------- 1 | package io.codearte.gradle.nexus.logic 2 | 3 | import groovy.transform.CompileStatic 4 | import io.codearte.gradle.nexus.exception.UnsupportedRepositoryState 5 | 6 | @CompileStatic 7 | enum RepositoryState { 8 | //TODO: PROMOTED and RELEASED is confusing 9 | OPEN, 10 | CLOSED, 11 | /** @deprecated */ 12 | PROMOTED, 13 | RELEASED, 14 | NOT_FOUND 15 | 16 | @Override 17 | String toString() { 18 | return name().toLowerCase() 19 | } 20 | 21 | static RepositoryState parseString(String stateAsString) { 22 | try { 23 | return valueOf(stateAsString?.toUpperCase()) 24 | } catch (IllegalArgumentException | NullPointerException ignored) { 25 | throw new UnsupportedRepositoryState(stateAsString) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/groovy/io/codearte/gradle/nexus/logic/RepositoryStateFetcher.groovy: -------------------------------------------------------------------------------- 1 | package io.codearte.gradle.nexus.logic 2 | 3 | import groovy.transform.CompileStatic 4 | import groovy.transform.InheritConstructors 5 | import groovy.util.logging.Slf4j 6 | import io.codearte.gradle.nexus.exception.RepositoryInTransitionException 7 | import io.codearte.gradle.nexus.infra.NexusHttpResponseException 8 | 9 | @InheritConstructors 10 | @CompileStatic 11 | @Slf4j 12 | class RepositoryStateFetcher extends BaseOperationExecutor { 13 | 14 | private static final int NOT_FOUND_RESPONSE_CODE = 404 15 | 16 | RepositoryState getNonTransitioningRepositoryStateById(String repoId) { 17 | try { 18 | return getRepoAssertingAndReturningState(repoId) 19 | } catch (NexusHttpResponseException e) { 20 | return handleGivenRepositoryNotFoundOrRethrowException(repoId, e) 21 | } 22 | } 23 | 24 | private RepositoryState getRepoAssertingAndReturningState(String repoId) { 25 | Map repoResponseAsMap = getRepositoryWithId(repoId) 26 | parseResponseAndThrowExceptionIfInTransition(repoResponseAsMap, repoId) 27 | return parseRepoStateFromRepsponse(repoResponseAsMap) 28 | } 29 | 30 | private Map getRepositoryWithId(String repoId) { 31 | log.info("Getting repository '$repoId'") 32 | Map repoResponseAsMap = client.get(nexusUrl + "/staging/repository/$repoId") 33 | return repoResponseAsMap 34 | } 35 | 36 | private void parseResponseAndThrowExceptionIfInTransition(Map repoResponseAsMap, String repoId) { 37 | if (repoResponseAsMap.transitioning == false) { 38 | return 39 | } 40 | throw new RepositoryInTransitionException(repoId, (String)repoResponseAsMap.type) 41 | } 42 | 43 | private RepositoryState parseRepoStateFromRepsponse(Map repoResponseAsMap) { 44 | return RepositoryState.parseString((String)repoResponseAsMap.type) 45 | } 46 | 47 | private RepositoryState handleGivenRepositoryNotFoundOrRethrowException(String repoId, NexusHttpResponseException responseException) { 48 | if (responseException.statusCode == NOT_FOUND_RESPONSE_CODE && isErrorMessageForGivenNotFoundRepository(repoId, responseException.message)) { 49 | return RepositoryState.NOT_FOUND 50 | } else { 51 | throw responseException; 52 | } 53 | } 54 | 55 | private boolean isErrorMessageForGivenNotFoundRepository(String repoId, String message) { 56 | return message.contains("No such repository") && message.contains(repoId) 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/groovy/io/codearte/gradle/nexus/logic/RepositoryTransition.groovy: -------------------------------------------------------------------------------- 1 | package io.codearte.gradle.nexus.logic 2 | 3 | interface RepositoryTransition { 4 | 5 | void performWithRepositoryIdAndStagingProfileId(String repositoryId, String stagingProfileId) 6 | 7 | List desiredAfterTransitionRepositoryState() 8 | } 9 | -------------------------------------------------------------------------------- /src/main/groovy/io/codearte/gradle/nexus/logic/RetryingRepositoryTransitioner.groovy: -------------------------------------------------------------------------------- 1 | package io.codearte.gradle.nexus.logic 2 | 3 | import groovy.transform.CompileStatic 4 | import groovy.util.logging.Slf4j 5 | import io.codearte.gradle.nexus.exception.UnexpectedRepositoryState 6 | 7 | @CompileStatic 8 | @Slf4j 9 | class RetryingRepositoryTransitioner { 10 | 11 | private final AbstractRepositoryTransitioner repositoryTransitioner 12 | private final RepositoryStateFetcher repositoryStateFetcher 13 | private final OperationRetrier retrier 14 | 15 | RetryingRepositoryTransitioner(AbstractRepositoryTransitioner repositoryTransitioner, RepositoryStateFetcher repositoryStateFetcher, 16 | OperationRetrier retrier) { 17 | this.repositoryTransitioner = repositoryTransitioner 18 | this.repositoryStateFetcher = repositoryStateFetcher 19 | this.retrier = retrier 20 | } 21 | 22 | void performWithRepositoryIdAndStagingProfileId(String repoId, String stagingProfileId) { 23 | repositoryTransitioner.performWithRepositoryIdAndStagingProfileId(repoId, stagingProfileId) 24 | RepositoryState state = retrier.doWithRetry { repositoryStateFetcher.getNonTransitioningRepositoryStateById(repoId) } 25 | if (!repositoryTransitioner.desiredAfterTransitionRepositoryState().contains(state)) { 26 | throw new UnexpectedRepositoryState(repoId, state, repositoryTransitioner.desiredAfterTransitionRepositoryState()) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/groovy/io/codearte/gradle/nexus/logic/StagingProfileFetcher.groovy: -------------------------------------------------------------------------------- 1 | package io.codearte.gradle.nexus.logic 2 | 3 | import groovy.transform.InheritConstructors 4 | import groovy.util.logging.Slf4j 5 | import io.codearte.gradle.nexus.infra.WrongNumberOfStagingProfiles 6 | 7 | @InheritConstructors 8 | @Slf4j 9 | class StagingProfileFetcher extends BaseOperationExecutor { 10 | 11 | String getStagingProfileIdForPackageGroup(String packageGroup) { 12 | log.info("Getting staging profile for package group '$packageGroup'") 13 | Map responseAsMap = client.get(nexusUrl + "/staging/profiles") //TODO: Constant 14 | return parseResponseAndGetStagingProfileIdForPackageGroup(responseAsMap, packageGroup) 15 | } 16 | 17 | private Object parseResponseAndGetStagingProfileIdForPackageGroup(Map responseAsMap, String packageGroup) { 18 | def profileIds = responseAsMap.data.findAll { profile -> 19 | profile.name == packageGroup 20 | }.collect { profile -> 21 | profile.id 22 | } 23 | if (profileIds.isEmpty() || profileIds.size() > 1) { 24 | throw new WrongNumberOfStagingProfiles(profileIds.size(), packageGroup) 25 | } 26 | log.debug("Received 1 staging profile with id: ${profileIds[0]}") 27 | return profileIds[0] 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/gradle-plugins/io.codearte.nexus-staging.properties: -------------------------------------------------------------------------------- 1 | implementation-class=io.codearte.gradle.nexus.NexusStagingPlugin -------------------------------------------------------------------------------- /src/main/resources/META-INF/gradle-plugins/io.codearte.nexus-upload-staging.properties: -------------------------------------------------------------------------------- 1 | implementation-class=io.codearte.gradle.nexus.legacy.NexusUploadStagingPlugin 2 | -------------------------------------------------------------------------------- /src/test/groovy/io/codearte/gradle/nexus/ApplyOnlyOnRootProjectSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.codearte.gradle.nexus 2 | 3 | import org.gradle.api.Project 4 | import org.gradle.api.internal.plugins.PluginApplicationException 5 | import org.gradle.testfixtures.ProjectBuilder 6 | import org.junit.Rule 7 | import org.junit.rules.TemporaryFolder 8 | import spock.lang.Issue 9 | import spock.lang.Specification 10 | import spock.util.Exceptions 11 | 12 | @Issue("https://github.com/Codearte/gradle-nexus-staging-plugin/issues/116") 13 | class ApplyOnlyOnRootProjectSpec extends Specification { //ProjectSpec from nebula could be used if moved to funcTest 14 | 15 | @Rule 16 | public TemporaryFolder tmpProjectDir = new TemporaryFolder() 17 | 18 | private Project project 19 | 20 | def setup() { 21 | project = ProjectBuilder.builder().withProjectDir(tmpProjectDir.root).build() 22 | } 23 | 24 | def "successfully apply on root project in single module project"() { 25 | when: 26 | project.apply(plugin: NexusStagingPlugin) 27 | then: 28 | noExceptionThrown() 29 | } 30 | 31 | def "successfully apply on root project in multi module project"() { 32 | given: 33 | createAddAndReturnSubproject(project) 34 | when: 35 | project.apply(plugin: NexusStagingPlugin) 36 | then: 37 | noExceptionThrown() 38 | } 39 | 40 | def "fail if applied on subproject in default configuration"() { 41 | given: 42 | Project subproject = createAddAndReturnSubproject(project) 43 | when: 44 | subproject.apply(plugin: NexusStagingPlugin) 45 | then: 46 | PluginApplicationException e = thrown() 47 | String rootCauseMessage = Exceptions.getRootCause(e).message 48 | rootCauseMessage.contains("Nexus staging plugin should ONLY be applied on the ROOT project in a build.") 49 | and: 50 | rootCauseMessage.contains(ApplyOnlyOnRootProjectEnforcer.DISABLE_APPLY_ONLY_ON_ROOT_PROJECT_ENFORCEMENT_PROPERTY_NAME) 51 | } 52 | 53 | def "not fail if applied on subproject project with override switch turned on (#propertyValue)"() { 54 | given: 55 | Project subproject = createAddAndReturnSubproject(project) 56 | and: 57 | project.extensions.extraProperties.set(ApplyOnlyOnRootProjectEnforcer.DISABLE_APPLY_ONLY_ON_ROOT_PROJECT_ENFORCEMENT_PROPERTY_NAME, propertyValue) 58 | when: 59 | subproject.apply(plugin: NexusStagingPlugin) 60 | then: 61 | noExceptionThrown() 62 | where: 63 | propertyValue << [true, "true", 1, "anything"] 64 | } 65 | 66 | //TODO: It should be rather test for more generic GradleUtil.isPropertyNotDefinedOrFalse() mechanism 67 | def "fail if applied on subproject project with override switch explicitly disabled (#propertyValue)"() { 68 | given: 69 | Project subproject = createAddAndReturnSubproject(project) 70 | and: 71 | project.extensions.extraProperties.set(ApplyOnlyOnRootProjectEnforcer.DISABLE_APPLY_ONLY_ON_ROOT_PROJECT_ENFORCEMENT_PROPERTY_NAME, propertyValue) 72 | when: 73 | subproject.apply(plugin: NexusStagingPlugin) 74 | then: 75 | PluginApplicationException e = thrown() 76 | Exceptions.getRootCause(e).message.contains("Nexus staging plugin should ONLY be applied on the ROOT project in a build.") 77 | where: 78 | propertyValue << ["false", false] 79 | } 80 | 81 | private Project createAddAndReturnSubproject(Project parentProject, String name = "subproject") { 82 | Project subproject = ProjectBuilder.builder().withName(name).withProjectDir(new File(tmpProjectDir.root, name)).withParent(parentProject).build() 83 | parentProject.subprojects.add(subproject) 84 | return subproject 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/test/groovy/io/codearte/gradle/nexus/logic/BaseOperationExecutorSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.codearte.gradle.nexus.logic 2 | 3 | import spock.lang.Specification 4 | 5 | abstract class BaseOperationExecutorSpec extends Specification { 6 | 7 | protected static final String TEST_STAGING_PROFILE_ID = "5027d084a01a3a" 8 | protected static final String TEST_REPOSITORY_ID = "iocodearte-1011" 9 | protected static final String TEST_REPOSITORY_DESCRIPTION = "Test repository description" 10 | protected static final String MOCK_SERVER_HOST = "https://mock.server" 11 | 12 | protected static String pathForGivenBulkOperation(String operationName) { 13 | return "${MOCK_SERVER_HOST}/staging/bulk/$operationName" 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/test/groovy/io/codearte/gradle/nexus/logic/FetcherResponseTrait.groovy: -------------------------------------------------------------------------------- 1 | package io.codearte.gradle.nexus.logic 2 | 3 | import groovy.transform.CompileStatic 4 | import groovy.transform.PackageScope 5 | 6 | @CompileStatic 7 | @PackageScope 8 | trait FetcherResponseTrait { 9 | 10 | Map createResponseMapWithGivenRepos(List repositories) { 11 | return [data: repositories] 12 | } 13 | 14 | @SuppressWarnings("GrDeprecatedAPIUsage") 15 | Map aRepoInStateAndId(String id, RepositoryState state, boolean isTransitioning = false) { 16 | return aRepoInStateAndId(id, state.toString(), isTransitioning) 17 | } 18 | 19 | @Deprecated //Variant with "state" as String only to reproduce issues with unsupported states 20 | Map aRepoInStateAndId(String id, String state, boolean isTransitioning = false) { 21 | return [ 22 | created: "2017-03-25T15:44:11.248Z", 23 | createdDate: "Sat Mar 25 15:44:11 UTC 2017", 24 | createdTimestamp: 1490456651248, 25 | description: "Implicitly created (auto staging).", 26 | ipAddress: "127.0.0.2", 27 | notifications: 1, 28 | policy: "release", 29 | profileId: "5027d084a01a3a", 30 | profileName: "io.gitlab.nexus-at", 31 | profileType: "repository", 32 | provider: "maven2", 33 | releaseRepositoryId: "no-sync-releases", 34 | releaseRepositoryName: "No-Sync-Releases", 35 | repositoryId: id, 36 | repositoryURI: "https://oss.sonatype.org/content/repositories/iogitlabnexus-at-1018", 37 | transitioning: isTransitioning, 38 | type: state, 39 | updated: "2017-03-25T15:48:07.342Z", 40 | updatedDate: "Sat Mar 25 15:48:07 UTC 2017", 41 | updatedTimestamp: 1490456887342, 42 | userAgent: "Aether", 43 | userId: "nexus-at" 44 | ] 45 | } 46 | 47 | Map aNotFoundRepo(String id) { 48 | return [ 49 | errors: [ 50 | [id: "*", msg: "No such repository: $id"] 51 | ] 52 | ] 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/test/groovy/io/codearte/gradle/nexus/logic/OperationRetrierSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.codearte.gradle.nexus.logic 2 | 3 | import io.codearte.gradle.nexus.exception.RepositoryInTransitionException 4 | import io.codearte.gradle.nexus.infra.WrongNumberOfRepositories 5 | import spock.lang.Specification 6 | 7 | class OperationRetrierSpec extends Specification { 8 | 9 | private static final String TEST_PROFILE_ID = "profileId" 10 | 11 | private OperationRetrier retrier 12 | 13 | void setup() { 14 | retrier = new OperationRetrier(2, 0) 15 | } 16 | 17 | def "should retry operation and pass returned value on #exceptionToThrow.class.simpleName"() { 18 | given: 19 | RepositoryFetcher fetcherMock = Mock() 20 | int counter = 0 21 | when: 22 | String returnedValue = retrier.doWithRetry { 23 | fetcherMock.getRepositoryIdWithGivenStateForStagingProfileId(TEST_PROFILE_ID, RepositoryState.CLOSED) 24 | } 25 | then: 26 | 2 * fetcherMock.getRepositoryIdWithGivenStateForStagingProfileId(TEST_PROFILE_ID, RepositoryState.CLOSED) >> { 27 | if (counter++ == 0) { 28 | throw exceptionToThrow 29 | } else { 30 | return "valueToReturn" 31 | } 32 | } 33 | and: 34 | returnedValue == "valueToReturn" 35 | where: 36 | exceptionToThrow << [new WrongNumberOfRepositories(0, "open"), 37 | new RepositoryInTransitionException('repoId', 'open')] 38 | } 39 | 40 | def "should propagate original exception on too many retry attempts"() { 41 | given: 42 | RepositoryFetcher fetcherMock = Mock() 43 | when: 44 | retrier.doWithRetry { fetcherMock.getRepositoryIdWithGivenStateForStagingProfileId(TEST_PROFILE_ID, RepositoryState.CLOSED) } 45 | then: 46 | 3 * fetcherMock.getRepositoryIdWithGivenStateForStagingProfileId(TEST_PROFILE_ID, RepositoryState.CLOSED) >> { 47 | throw new WrongNumberOfRepositories(0, "closed") 48 | } 49 | and: 50 | thrown(WrongNumberOfRepositories) 51 | } 52 | 53 | def "should fail immediately on other exception"() { 54 | given: 55 | RepositoryFetcher fetcherMock = Mock() 56 | when: 57 | retrier.doWithRetry { fetcherMock.getRepositoryIdWithGivenStateForStagingProfileId(TEST_PROFILE_ID, RepositoryState.CLOSED) } 58 | then: 59 | 1 * fetcherMock.getRepositoryIdWithGivenStateForStagingProfileId(TEST_PROFILE_ID, RepositoryState.CLOSED) >> { 60 | throw new NullPointerException() 61 | } 62 | and: 63 | thrown(NullPointerException) 64 | } 65 | 66 | def "should honor delay between retries"() { 67 | given: 68 | OperationRetrier spiedRetrier = Spy() 69 | and: 70 | def fetcherMock = Mock(RepositoryFetcher) 71 | int counter = 0 72 | when: 73 | String returnedValue = spiedRetrier.doWithRetry { fetcherMock.getRepositoryIdWithGivenStateForStagingProfileId(TEST_PROFILE_ID, RepositoryState.CLOSED) } 74 | then: 75 | 1 * spiedRetrier.waitBeforeNextAttempt() >> { /* do nothing */ } 76 | and: 77 | 2 * fetcherMock.getRepositoryIdWithGivenStateForStagingProfileId(TEST_PROFILE_ID, RepositoryState.CLOSED) >> { 78 | if (counter++ == 0) { 79 | throw new WrongNumberOfRepositories(0, "closed") 80 | } else { 81 | return "repoId" 82 | } 83 | } 84 | and: 85 | returnedValue == "repoId" 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/test/groovy/io/codearte/gradle/nexus/logic/RepositoryCloserSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.codearte.gradle.nexus.logic 2 | 3 | import groovy.json.JsonSlurper 4 | import io.codearte.gradle.nexus.infra.SimplifiedHttpJsonRestClient 5 | 6 | class RepositoryCloserSpec extends BaseOperationExecutorSpec { 7 | 8 | private static final String CLOSE_REPOSITORY_FULL_URL = pathForGivenBulkOperation("close") 9 | 10 | def "should close open repository"() { 11 | given: 12 | SimplifiedHttpJsonRestClient client = Mock() 13 | RepositoryCloser closer = new RepositoryCloser(client, MOCK_SERVER_HOST, TEST_REPOSITORY_DESCRIPTION) 14 | when: 15 | closer.performWithRepositoryIdAndStagingProfileId(TEST_REPOSITORY_ID, TEST_STAGING_PROFILE_ID) 16 | then: 17 | 1 * client.post(CLOSE_REPOSITORY_FULL_URL, _) >> { uri, content -> 18 | assert content == new JsonSlurper().parse(this.getClass().getResource("commonStagingRepositoryRequest.json")) 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/test/groovy/io/codearte/gradle/nexus/logic/RepositoryDropperSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.codearte.gradle.nexus.logic 2 | 3 | import groovy.json.JsonSlurper 4 | import io.codearte.gradle.nexus.infra.SimplifiedHttpJsonRestClient 5 | 6 | class RepositoryDropperSpec extends BaseOperationExecutorSpec { 7 | 8 | private static final String DROP_REPOSITORY_FULL_URL = pathForGivenBulkOperation("drop") 9 | 10 | def "should drop repository"() { 11 | given: 12 | SimplifiedHttpJsonRestClient client = Mock() 13 | RepositoryDropper dropper = new RepositoryDropper(client, MOCK_SERVER_HOST, TEST_REPOSITORY_DESCRIPTION) 14 | when: 15 | dropper.performWithRepositoryIdAndStagingProfileId(TEST_REPOSITORY_ID, TEST_STAGING_PROFILE_ID) 16 | then: 17 | 1 * client.post(DROP_REPOSITORY_FULL_URL, _) >> { uri, content -> 18 | assert content == new JsonSlurper().parse(this.getClass().getResource("commonStagingRepositoryRequest.json")) 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/test/groovy/io/codearte/gradle/nexus/logic/RepositoryFetcherSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.codearte.gradle.nexus.logic 2 | 3 | import io.codearte.gradle.nexus.infra.SimplifiedHttpJsonRestClient 4 | import io.codearte.gradle.nexus.infra.WrongNumberOfRepositories 5 | 6 | class RepositoryFetcherSpec extends BaseOperationExecutorSpec implements FetcherResponseTrait { 7 | 8 | private static final String GET_REPOSITORY_ID_PATH = "/staging/profile_repositories/" 9 | private static final String GET_REPOSITORY_ID_FULL_URL = MOCK_SERVER_HOST + GET_REPOSITORY_ID_PATH + TEST_STAGING_PROFILE_ID 10 | 11 | private SimplifiedHttpJsonRestClient client 12 | private RepositoryFetcher fetcher 13 | 14 | void setup() { 15 | client = Mock() 16 | fetcher = new RepositoryFetcher(client, MOCK_SERVER_HOST) 17 | } 18 | 19 | def "should get open repository id from server"() { 20 | given: 21 | client.get(GET_REPOSITORY_ID_FULL_URL) >> { createResponseMapWithGivenRepos([anOpenRepo()]) } 22 | when: 23 | String repositoryId = fetcher.getRepositoryIdWithGivenStateForStagingProfileId(TEST_STAGING_PROFILE_ID, RepositoryState.OPEN) 24 | then: 25 | repositoryId == TEST_REPOSITORY_ID 26 | } 27 | 28 | def "should get closed repository id from server"() { 29 | given: 30 | client.get(GET_REPOSITORY_ID_FULL_URL) >> { createResponseMapWithGivenRepos([aClosedRepo()]) } 31 | when: 32 | String repositoryId = fetcher.getRepositoryIdWithGivenStateForStagingProfileId(TEST_STAGING_PROFILE_ID, RepositoryState.CLOSED) 33 | then: 34 | repositoryId == TEST_REPOSITORY_ID 35 | } 36 | 37 | def "should fail with meaningful exception on lack of repositories in given state #state"() { 38 | given: 39 | client.get(GET_REPOSITORY_ID_FULL_URL) >> { createResponseMapWithGivenRepos([]) } 40 | when: 41 | fetcher.getRepositoryIdWithGivenStateForStagingProfileId(TEST_STAGING_PROFILE_ID, state) 42 | then: 43 | WrongNumberOfRepositories e = thrown() 44 | e.message == "Wrong number of received repositories in state '$state'. Expected 1, received 0".toString() 45 | e.numberOfRepositories == 0 46 | e.state == state.toString() 47 | where: 48 | state << [RepositoryState.OPEN, RepositoryState.CLOSED] 49 | } 50 | 51 | def "should fail with meaningful exception on too many repositories in given state #state"() { 52 | given: 53 | client.get(GET_REPOSITORY_ID_FULL_URL) >> { 54 | createResponseMapWithGivenRepos([aRepoInStateAndId(TEST_REPOSITORY_ID, state), 55 | aRepoInStateAndId(TEST_REPOSITORY_ID + "2", state)]) 56 | } 57 | when: 58 | fetcher.getRepositoryIdWithGivenStateForStagingProfileId(TEST_STAGING_PROFILE_ID, state) 59 | then: 60 | WrongNumberOfRepositories e = thrown() 61 | e.message == "Wrong number of received repositories in state '$state'. Expected 1, received 2".toString() 62 | e.numberOfRepositories == 2 63 | e.state == state.toString() 64 | where: 65 | state << [RepositoryState.OPEN, RepositoryState.CLOSED] 66 | } 67 | 68 | def "should fail with meaningful exception on wrong repo state returned from server"() { 69 | given: 70 | client.get(GET_REPOSITORY_ID_FULL_URL) >> { createResponseMapWithGivenRepos([aRepoInState(receivedState)]) } 71 | when: 72 | fetcher.getRepositoryIdWithGivenStateForStagingProfileId(TEST_STAGING_PROFILE_ID, expectedState) 73 | then: 74 | WrongNumberOfRepositories e = thrown() 75 | e.message == "Wrong number of received repositories in state '$expectedState'. Expected 1, received 0".toString() 76 | where: 77 | expectedState | receivedState 78 | RepositoryState.OPEN | RepositoryState.CLOSED 79 | RepositoryState.CLOSED | RepositoryState.OPEN 80 | } 81 | 82 | private Map anOpenRepo() { 83 | return aRepoInStateAndId(TEST_REPOSITORY_ID, RepositoryState.OPEN) 84 | } 85 | 86 | private Map aClosedRepo() { 87 | return aRepoInStateAndId(TEST_REPOSITORY_ID, RepositoryState.CLOSED) 88 | } 89 | 90 | private Map aRepoInState(RepositoryState state) { 91 | return aRepoInStateAndId(TEST_REPOSITORY_ID, state) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/test/groovy/io/codearte/gradle/nexus/logic/RepositoryReleaserSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.codearte.gradle.nexus.logic 2 | 3 | import groovy.json.JsonSlurper 4 | import io.codearte.gradle.nexus.infra.SimplifiedHttpJsonRestClient 5 | 6 | class RepositoryReleaserSpec extends BaseOperationExecutorSpec { 7 | 8 | private static final String RELEASE_OPERATION_NAME = "promote" //promote and release use the same operation, used parameters matter 9 | private static final String RELEASE_REPOSITORY_FULL_URL = pathForGivenBulkOperation(RELEASE_OPERATION_NAME) 10 | 11 | def "should release repository"() { 12 | given: 13 | SimplifiedHttpJsonRestClient client = Mock() 14 | RepositoryReleaser releaser = new RepositoryReleaser(client, MOCK_SERVER_HOST, TEST_REPOSITORY_DESCRIPTION) 15 | when: 16 | releaser.performWithRepositoryIdAndStagingProfileId(TEST_REPOSITORY_ID, TEST_STAGING_PROFILE_ID) 17 | then: 18 | 1 * client.post(RELEASE_REPOSITORY_FULL_URL, _) >> { uri, content -> 19 | assert content == new JsonSlurper().parse(this.getClass().getResource("commonStagingRepositoryRequest.json")) 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/test/groovy/io/codearte/gradle/nexus/logic/RepositoryStateFetcherSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.codearte.gradle.nexus.logic 2 | 3 | import io.codearte.gradle.nexus.exception.UnsupportedRepositoryState 4 | import io.codearte.gradle.nexus.infra.NexusHttpResponseException 5 | import io.codearte.gradle.nexus.infra.SimplifiedHttpJsonRestClient 6 | 7 | class RepositoryStateFetcherSpec extends BaseOperationExecutorSpec implements FetcherResponseTrait { 8 | 9 | private static final String GET_REPOSITORY_STATE_PATH = "/staging/repository/" 10 | 11 | private SimplifiedHttpJsonRestClient client 12 | private RepositoryStateFetcher repoStateFetcher 13 | 14 | void setup() { 15 | client = Mock(SimplifiedHttpJsonRestClient) 16 | repoStateFetcher = new RepositoryStateFetcher(client, MOCK_SERVER_HOST) 17 | } 18 | 19 | @SuppressWarnings("GrDeprecatedAPIUsage") 20 | def "should return state received from server mapped to enum"() { 21 | given: 22 | client.get(getGetRepositoryStateFullUrlForRepoId(TEST_REPOSITORY_ID)) >> { aRepoInStateAndId(TEST_REPOSITORY_ID, 'closed') } 23 | when: 24 | RepositoryState repoState = repoStateFetcher.getNonTransitioningRepositoryStateById(TEST_REPOSITORY_ID) 25 | then: 26 | repoState == RepositoryState.CLOSED 27 | } 28 | 29 | @SuppressWarnings("GrDeprecatedAPIUsage") 30 | def "should throw exception with meaningful message if unsupported or missing state"() { 31 | given: 32 | client.get(getGetRepositoryStateFullUrlForRepoId(TEST_REPOSITORY_ID)) >> { aRepoInStateAndId(TEST_REPOSITORY_ID, unsupportedState) } 33 | when: 34 | repoStateFetcher.getNonTransitioningRepositoryStateById(TEST_REPOSITORY_ID) 35 | then: 36 | UnsupportedRepositoryState e = thrown() 37 | e.unsupportedState == unsupportedState 38 | where: 39 | unsupportedState << ['completely wrong', null] 40 | } 41 | 42 | def "should map 404 error from server to NOT_FOUND state"() { 43 | given: 44 | client.get(getGetRepositoryStateFullUrlForRepoId(TEST_REPOSITORY_ID)) >> { 45 | throw new NexusHttpResponseException(404, "404: Not Found, body: ${aNotFoundRepo(TEST_REPOSITORY_ID)}") 46 | } 47 | when: 48 | RepositoryState repoState = repoStateFetcher.getNonTransitioningRepositoryStateById(TEST_REPOSITORY_ID) 49 | then: 50 | repoState == RepositoryState.NOT_FOUND 51 | } 52 | 53 | private String getGetRepositoryStateFullUrlForRepoId(String repoId) { 54 | return MOCK_SERVER_HOST + GET_REPOSITORY_STATE_PATH + repoId 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/test/groovy/io/codearte/gradle/nexus/logic/StagingProfileFetcherSpec.groovy: -------------------------------------------------------------------------------- 1 | package io.codearte.gradle.nexus.logic 2 | 3 | import groovy.json.JsonSlurper 4 | import io.codearte.gradle.nexus.infra.SimplifiedHttpJsonRestClient 5 | import io.codearte.gradle.nexus.infra.WrongNumberOfStagingProfiles 6 | 7 | class StagingProfileFetcherSpec extends BaseOperationExecutorSpec { 8 | 9 | private static final String GET_STAGING_PROFILES_PATH = "/staging/profiles" 10 | private static final String GET_STAGING_PROFILES_FULL_URL = MOCK_SERVER_HOST + GET_STAGING_PROFILES_PATH 11 | 12 | def "should get staging profile id from server"() { 13 | given: 14 | SimplifiedHttpJsonRestClient client = Mock() 15 | client.get(GET_STAGING_PROFILES_FULL_URL) >> { 16 | new JsonSlurper().parse(this.getClass().getResource("2stagingProfilesShrunkResponse.json")) 17 | } 18 | StagingProfileFetcher fetcher = new StagingProfileFetcher(client, MOCK_SERVER_HOST) 19 | when: 20 | String stagingProfileId = fetcher.getStagingProfileIdForPackageGroup("io.codearte") 21 | then: 22 | stagingProfileId == TEST_STAGING_PROFILE_ID 23 | } 24 | 25 | def "should throw meaningful exception for not matching profiles for package group"() { 26 | given: 27 | SimplifiedHttpJsonRestClient client = Mock() 28 | client.get(GET_STAGING_PROFILES_FULL_URL) >> { 29 | [data: [[id: 1, name: "other1"], [id: 2, name: "other2"]]] 30 | } 31 | StagingProfileFetcher fetcher = new StagingProfileFetcher(client, MOCK_SERVER_HOST) 32 | when: 33 | fetcher.getStagingProfileIdForPackageGroup("wrongGroup") 34 | then: 35 | WrongNumberOfStagingProfiles e = thrown() 36 | e.packageGroup == "wrongGroup" 37 | e.numberOfProfiles == 0 38 | } 39 | 40 | def "should throw meaningful exception for too many matching profiles for package group"() { 41 | given: 42 | SimplifiedHttpJsonRestClient client = Mock() 43 | client.get(GET_STAGING_PROFILES_FULL_URL) >> { 44 | [data: [[id: 1, name: "tooMuch"], [id: 2, name: "tooMuch"]]] 45 | } 46 | StagingProfileFetcher fetcher = new StagingProfileFetcher(client, MOCK_SERVER_HOST) 47 | when: 48 | fetcher.getStagingProfileIdForPackageGroup("tooMuch") 49 | then: 50 | WrongNumberOfStagingProfiles e = thrown() 51 | e.packageGroup == "tooMuch" 52 | e.numberOfProfiles == 2 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/test/resources/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Codearte/gradle-nexus-staging-plugin/bbb2ba969e9da89943c5878261eebacba8a7b695/src/test/resources/.gitkeep -------------------------------------------------------------------------------- /src/test/resources/io/codearte/gradle/nexus/logic/2stagingProfilesShrunkResponse.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "deployURI": "https://oss.sonatype.org/service/local/staging/deploy/maven2", 5 | "id": "430bafbe963e40", 6 | "inProgress": false, 7 | "mode": "BOTH", 8 | "name": "eu.codearte", 9 | "order": 6445, 10 | "promotionTargetRepository": "releases", 11 | "repositoryType": "maven2", 12 | "resourceURI": "https://oss.sonatype.org/service/local/staging/profiles/430bafbe963e40", 13 | "targetGroups": ["staging"] 14 | }, 15 | { 16 | "deployURI": "https://oss.sonatype.org/service/local/staging/deploy/maven2", 17 | "id": "5027d084a01a3a", 18 | "inProgress": false, 19 | "mode": "BOTH", 20 | "name": "io.codearte", 21 | "order": 8981, 22 | "promotionTargetRepository": "releases", 23 | "repositoryType": "maven2", 24 | "resourceURI": "https://oss.sonatype.org/service/local/staging/profiles/5027d084a01a3a", 25 | "targetGroups": ["staging"] 26 | } 27 | ] 28 | } 29 | -------------------------------------------------------------------------------- /src/test/resources/io/codearte/gradle/nexus/logic/commonStagingRepositoryRequest.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": { 3 | "stagedRepositoryIds": ["iocodearte-1011"], 4 | "description": "Test repository description", 5 | "autoDropAfterRelease": true 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /src/test/resources/io/codearte/gradle/nexus/logic/openRepositoryShrunkResponse.json: -------------------------------------------------------------------------------- 1 | { 2 | "data": [ 3 | { 4 | "policy": "release", 5 | "profileId": "5027d084a01a3a", 6 | "profileName": "io.codearte", 7 | "profileType": "repository", 8 | "releaseRepositoryId": "releases", 9 | "releaseRepositoryName": "Releases", 10 | "repositoryId": "iocodearte-1011", 11 | "type": "open" 12 | } 13 | ] 14 | } 15 | --------------------------------------------------------------------------------