├── .editorconfig ├── .github ├── dependabot.yml └── workflows │ ├── build.yml │ ├── bump_dependencies.yml │ ├── pr.yml │ ├── release.yml │ └── release_info.sh ├── .gitignore ├── LICENSE.txt ├── OWNERS.md ├── README.md ├── build.gradle ├── codestyle.xml ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle ├── spinnaker-extensions ├── README.md ├── build.gradle.kts └── src │ ├── functionaltest │ └── kotlin │ │ └── com │ │ └── netflix │ │ └── spinnaker │ │ └── gradle │ │ └── extension │ │ ├── SpinnakerExtensionGradlePluginFunctionalTest.kt │ │ └── fixture.kt │ ├── main │ ├── kotlin │ │ └── com │ │ │ └── netflix │ │ │ └── spinnaker │ │ │ └── gradle │ │ │ └── extension │ │ │ ├── PluginObjectMapper.kt │ │ │ ├── Plugins.kt │ │ │ ├── ProjectHelper.kt │ │ │ ├── SpinnakerExtensionsBundlerPlugin.kt │ │ │ ├── SpinnakerServiceExtensionPlugin.kt │ │ │ ├── SpinnakerUIExtensionPlugin.kt │ │ │ ├── compatibility │ │ │ ├── CompatibilityTestResult.kt │ │ │ ├── CompatibilityTestTask.kt │ │ │ ├── SpinnakerCompatibilityTestRunnerPlugin.kt │ │ │ └── SpinnakerVersionsClient.kt │ │ │ ├── extensions │ │ │ ├── SpinnakerBundleExtension.kt │ │ │ ├── SpinnakerCompatibilityExtension.kt │ │ │ ├── SpinnakerPluginExtension.kt │ │ │ └── dsl.kt │ │ │ └── tasks │ │ │ ├── AssembleJavaPluginZipTask.kt │ │ │ ├── AssembleUIPluginTask.kt │ │ │ ├── BundlePluginsTask.kt │ │ │ ├── CreatePluginInfoTask.kt │ │ │ └── RegistrationTask.kt │ └── resources │ │ └── META-INF │ │ └── gradle-plugins │ │ ├── io.spinnaker.plugin.bundler.properties │ │ ├── io.spinnaker.plugin.compatibility-test-runner.properties │ │ ├── io.spinnaker.plugin.service-extension.properties │ │ └── io.spinnaker.plugin.ui-extension.properties │ └── test │ └── kotlin │ └── com │ └── netflix │ └── spinnaker │ └── gradle │ └── extension │ ├── SpinnakerCompatibilityTestRunnerPluginTest.kt │ └── SpinnakerExtensionGradlePluginTest.kt └── spinnaker-project-plugin ├── build.gradle └── src └── main ├── groovy └── com │ └── netflix │ └── spinnaker │ └── gradle │ ├── Flags.groovy │ ├── application │ ├── SpinnakerApplicationPlugin.groovy │ └── SpinnakerPackagePlugin.groovy │ ├── baseproject │ ├── SpinnakerBaseProjectConventionsPlugin.groovy │ └── SpinnakerBaseProjectPlugin.groovy │ ├── codestyle │ ├── SpinnakerCodeStyle.groovy │ └── SpinnakerCodeStylePlugin.groovy │ ├── idea │ ├── SpinnakerIdeaConfigPlugin.groovy │ └── SpinnakerNewIdeaProjectPlugin.java │ ├── license │ └── SpinnakerLicenseReportPlugin.groovy │ ├── project │ └── SpinnakerProjectPlugin.groovy │ └── publishing │ ├── PublishingPlugin.groovy │ ├── artifactregistry │ ├── ArtifactRegistryDebPublishTask.java │ ├── ArtifactRegistryPublishExtension.groovy │ └── ArtifactRegistryPublishPlugin.groovy │ └── nexus │ ├── NexusPublishExtension.groovy │ ├── NexusPublishPlugin.groovy │ └── README.md └── resources ├── license-normalizer-bundle.json └── pre-commit /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | end_of_line = lf 6 | insert_final_newline = true 7 | trim_trailing_whitespace = true 8 | indent_style = space 9 | indent_size = 2 10 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | # Maintain dependencies for GitHub Actions 4 | - package-ecosystem: "github-actions" 5 | directory: "/" 6 | schedule: 7 | interval: "monthly" 8 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Branch Build 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - version-* 8 | 9 | env: 10 | GRADLE_OPTS: -Dorg.gradle.daemon=false -Xmx2g -Xms2g 11 | 12 | jobs: 13 | branch-build: 14 | # Only run this on repositories in the 'spinnaker' org, not on forks. 15 | if: startsWith(github.repository, 'spinnaker/') 16 | runs-on: ubuntu-latest 17 | steps: 18 | - uses: actions/checkout@v4 19 | - uses: actions/setup-java@v4 20 | with: 21 | java-version: 11 22 | distribution: 'zulu' 23 | cache: 'gradle' 24 | - name: Build 25 | run: ./gradlew build --stacktrace 26 | -------------------------------------------------------------------------------- /.github/workflows/bump_dependencies.yml: -------------------------------------------------------------------------------- 1 | name: Bump Dependencies 2 | 3 | on: 4 | repository_dispatch: 5 | types: [bump-dependencies] 6 | 7 | jobs: 8 | bump-dependencies: 9 | if: startsWith(github.repository, 'spinnaker/') 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: spinnaker/bumpdeps@master 13 | with: 14 | ref: ${{ github.event.client_payload.ref }} 15 | key: spinnakerGradleVersion 16 | repositories: clouddriver,deck,echo,fiat,front50,gate,halyard,igor,kayenta,keel,kork,orca,rosco,swabbie 17 | mavenRepositoryUrl: https://plugins.gradle.org/m2 18 | groupId: io.spinnaker.project 19 | artifactId: io.spinnaker.project.gradle.plugin 20 | env: 21 | GITHUB_OAUTH: ${{ secrets.SPINNAKER_GITHUB_TOKEN }} 22 | -------------------------------------------------------------------------------- /.github/workflows/pr.yml: -------------------------------------------------------------------------------- 1 | name: PR Build 2 | 3 | on: [ pull_request ] 4 | 5 | env: 6 | GRADLE_OPTS: -Dorg.gradle.daemon=false -Xmx2g -Xms2g 7 | 8 | jobs: 9 | build: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/checkout@v4 13 | - uses: actions/setup-java@v4 14 | with: 15 | java-version: 11 16 | distribution: 'zulu' 17 | cache: 'gradle' 18 | - name: Build 19 | run: ./gradlew build 20 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | tags: 6 | - "v[0-9]+.[0-9]+.[0-9]+" 7 | - "v[0-9]+.[0-9]+.[0-9]+-rc.[0-9]+" 8 | 9 | env: 10 | GRADLE_OPTS: -Dorg.gradle.daemon=false -Xmx2g -Xms2g 11 | 12 | jobs: 13 | release: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - uses: actions/checkout@v4 17 | - run: git fetch --prune --unshallow 18 | - uses: actions/setup-java@v4 19 | with: 20 | java-version: 11 21 | distribution: 'zulu' 22 | cache: 'gradle' 23 | - name: Assemble release info 24 | id: release_info 25 | env: 26 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 27 | run: | 28 | . .github/workflows/release_info.sh ${{ github.event.repository.full_name }} 29 | echo CHANGELOG=$(echo -e "${CHANGELOG}") >> $GITHUB_OUTPUT 30 | echo SKIP_RELEASE="${SKIP_RELEASE}" >> $GITHUB_OUTPUT 31 | echo IS_CANDIDATE="${IS_CANDIDATE}" >> $GITHUB_OUTPUT 32 | echo RELEASE_VERSION="${RELEASE_VERSION}" >> $GITHUB_OUTPUT 33 | - name: Release build 34 | env: 35 | GRADLE_PUBLISH_KEY: ${{ secrets.GRADLE_PUBLISH_KEY }} 36 | GRADLE_PUBLISH_SECRET: ${{ secrets.GRADLE_PUBLISH_SECRET }} 37 | RELEASE_VERSION: ${{ steps.release_info.outputs.RELEASE_VERSION }} 38 | run: | 39 | ./gradlew --info -Pversion="${RELEASE_VERSION}" -Pgradle.publish.key="${GRADLE_PUBLISH_KEY}" -Pgradle.publish.secret="${GRADLE_PUBLISH_SECRET}" publishPlugins 40 | - name: Create release 41 | if: steps.release_info.outputs.SKIP_RELEASE == 'false' 42 | uses: softprops/action-gh-release@v2 43 | with: 44 | body: | 45 | ${{ steps.release_info.outputs.CHANGELOG }} 46 | draft: false 47 | name: ${{ github.event.repository.name }} ${{ github.ref_name }} 48 | prerelease: ${{ steps.release_info.outputs.IS_CANDIDATE }} 49 | tag_name: ${{ github.ref }} 50 | token: ${{ secrets.GITHUB_TOKEN }} 51 | - name: Pause before dependency bump 52 | if: steps.release_info.outputs.IS_CANDIDATE == 'false' 53 | run: sleep 300 54 | - name: Trigger dependency bump workflow 55 | if: steps.release_info.outputs.IS_CANDIDATE == 'false' 56 | uses: peter-evans/repository-dispatch@v3 57 | with: 58 | token: ${{ secrets.SPINNAKER_GITHUB_TOKEN }} 59 | event-type: bump-dependencies 60 | client-payload: '{"ref": "${{ github.ref }}"}' 61 | -------------------------------------------------------------------------------- /.github/workflows/release_info.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -x 2 | 3 | # Only look to the latest release to determine the previous tag -- this allows us to skip unsupported tag formats (like `version-1.0.0`) 4 | export PREVIOUS_TAG=`curl --silent "https://api.github.com/repos/$1/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/'` 5 | echo "PREVIOUS_TAG=$PREVIOUS_TAG" 6 | export NEW_TAG=${GITHUB_REF/refs\/tags\//} 7 | echo "NEW_TAG=$NEW_TAG" 8 | export CHANGELOG=`git log $NEW_TAG...$PREVIOUS_TAG --oneline` 9 | echo "CHANGELOG=$CHANGELOG" 10 | 11 | #Format the changelog so it's markdown compatible 12 | CHANGELOG="${CHANGELOG//$'%'/%25}" 13 | CHANGELOG="${CHANGELOG//$'\n'/%0A}" 14 | CHANGELOG="${CHANGELOG//$'\r'/%0D}" 15 | 16 | # If the previous release tag is the same as this tag the user likely cut a release (and in the process created a tag), which means we can skip the need to create a release 17 | export SKIP_RELEASE=`[[ "$PREVIOUS_TAG" = "$NEW_TAG" ]] && echo "true" || echo "false"` 18 | 19 | # https://github.com/fsaintjacques/semver-tool/blob/master/src/semver#L5-L14 20 | NAT='0|[1-9][0-9]*' 21 | ALPHANUM='[0-9]*[A-Za-z-][0-9A-Za-z-]*' 22 | IDENT="$NAT|$ALPHANUM" 23 | FIELD='[0-9A-Za-z-]+' 24 | SEMVER_REGEX="\ 25 | ^[vV]?\ 26 | ($NAT)\\.($NAT)\\.($NAT)\ 27 | (\\-(${IDENT})(\\.(${IDENT}))*)?\ 28 | (\\+${FIELD}(\\.${FIELD})*)?$" 29 | 30 | # Used in downstream steps to determine if the release should be marked as a "prerelease" and if the build should build candidate release artifacts 31 | export IS_CANDIDATE=`[[ $NEW_TAG =~ $SEMVER_REGEX && ! -z ${BASH_REMATCH[4]} ]] && echo "true" || echo "false"` 32 | 33 | # This is the version string we will pass to the build, trim off leading 'v' if present 34 | export RELEASE_VERSION=`[[ $NEW_TAG =~ $SEMVER_REGEX ]] && echo "${NEW_TAG:1}" || echo "${NEW_TAG}"` 35 | echo "RELEASE_VERSION=$RELEASE_VERSION" 36 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.idea/ 2 | *.ipr 3 | *.iml 4 | *.iws 5 | build/ 6 | /.gradle/ 7 | out/ 8 | /.gradletasknamecache 9 | 10 | codestyle.xml 11 | -------------------------------------------------------------------------------- /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 2014 Netflix 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 | 204 | -------------------------------------------------------------------------------- /OWNERS.md: -------------------------------------------------------------------------------- 1 | cfieber 2 | robzienert 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # spinnaker-gradle-project 2 | 3 | [![Build Status](https://travis-ci.org/spinnaker/spinnaker-gradle-project.svg)](https://travis-ci.org/spinnaker/spinnaker-gradle-project) 4 | 5 | Build conventions for spinnaker Gradle projects 6 | 7 | ## Usage 8 | 9 | ### Applying the Plugin 10 | 11 | To include, add the following to your build.gradle 12 | 13 | ```groovy 14 | plugins { 15 | id 'io.spinnaker.project' version "$spinnakerGradleVersion" apply false 16 | } 17 | 18 | allprojects { 19 | apply plugin: 'io.spinnaker.project' 20 | } 21 | ``` 22 | 23 | ### Extensions Provided 24 | 25 | **spinnaker** 26 | 27 | The spinnaker extension exposes dependency resolution utilities. By default the artifact 28 | 'com.netflix.spinnaker:spinnaker-dependencies:latest.release@yml' is resolved and used as 29 | common dependency configuration (this can be overridden by setting `dependenciesYaml` or 30 | `dependenciesVersion` on the spinnaker extension) 31 | 32 | The dependency yaml format supports three sections: 33 | 34 | - versions - a map of name to version string 35 | - dependencies - a map of name to Gradle dependency notation, supporting Groovy simple templating 36 | - groups - a map of name to a map of configuration name to a list of dependency names 37 | 38 | Usage looks like: 39 | 40 | ```groovy 41 | dependencies { 42 | spinnaker.group("bootWeb") 43 | compile spinnaker.dependency("bootActuator") 44 | compile "org.springframework:spring-context:${spinnaker.version('spring')}" 45 | } 46 | ``` 47 | 48 | ### Overriding resolved dependencies 49 | 50 | Values from the `ExtraPropertiesExtension` override values read from the `dependenciesYaml`. 51 | 52 | For example this: 53 | 54 | ``` 55 | ext { 56 | versions = [ 57 | kork : '1.70.0' 58 | ] 59 | } 60 | ``` 61 | 62 | Would pin the version of the `kork` library to `1.70.0` regardless of what version is defined 63 | in the dependency file. 64 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id "com.diffplug.spotless" version "6.23.2" apply false 3 | id "com.gradle.plugin-publish" version "0.12.0" apply false 4 | } 5 | 6 | subprojects { 7 | repositories { 8 | gradlePluginPortal() 9 | } 10 | 11 | apply plugin: 'com.diffplug.spotless' 12 | apply plugin: 'idea' 13 | apply plugin: 'java-gradle-plugin' 14 | apply plugin: 'com.gradle.plugin-publish' 15 | apply plugin: 'maven-publish' 16 | 17 | spotless { 18 | java { 19 | eclipse().configFile(rootProject.file('codestyle.xml')) 20 | } 21 | } 22 | 23 | group = 'io.spinnaker.gradle' 24 | sourceCompatibility = JavaVersion.VERSION_11 25 | targetCompatibility = JavaVersion.VERSION_11 26 | 27 | dependencies { 28 | implementation gradleApi() 29 | implementation localGroovy() 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/spinnaker/spinnaker-gradle-project/bbbc3ef92ddcd4ac433c6c42a6789d40c57b3fa1/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.1-bin.zip 4 | networkTimeout=10000 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original 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 POSIX generated by Gradle. 22 | # 23 | # Important for running: 24 | # 25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 26 | # noncompliant, but you have some other compliant shell such as ksh or 27 | # bash, then to run this script, type that shell name before the whole 28 | # command line, like: 29 | # 30 | # ksh Gradle 31 | # 32 | # Busybox and similar reduced shells will NOT work, because this script 33 | # requires all of these POSIX shell features: 34 | # * functions; 35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 37 | # * compound commands having a testable exit status, especially «case»; 38 | # * various built-in commands including «command», «set», and «ulimit». 39 | # 40 | # Important for patching: 41 | # 42 | # (2) This script targets any POSIX shell, so it avoids extensions provided 43 | # by Bash, Ksh, etc; in particular arrays are avoided. 44 | # 45 | # The "traditional" practice of packing multiple parameters into a 46 | # space-separated string is a well documented source of bugs and security 47 | # problems, so this is (mostly) avoided, by progressively accumulating 48 | # options in "$@", and eventually passing that to Java. 49 | # 50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 52 | # see the in-line comments for details. 53 | # 54 | # There are tweaks for specific operating systems such as AIX, CygWin, 55 | # Darwin, MinGW, and NonStop. 56 | # 57 | # (3) This script is generated from the Groovy template 58 | # https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 59 | # within the Gradle project. 60 | # 61 | # You can find Gradle at https://github.com/gradle/gradle/. 62 | # 63 | ############################################################################## 64 | 65 | # Attempt to set APP_HOME 66 | 67 | # Resolve links: $0 may be a link 68 | app_path=$0 69 | 70 | # Need this for daisy-chained symlinks. 71 | while 72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 73 | [ -h "$app_path" ] 74 | do 75 | ls=$( ls -ld "$app_path" ) 76 | link=${ls#*' -> '} 77 | case $link in #( 78 | /*) app_path=$link ;; #( 79 | *) app_path=$APP_HOME$link ;; 80 | esac 81 | done 82 | 83 | # This is normally unused 84 | # shellcheck disable=SC2034 85 | APP_BASE_NAME=${0##*/} 86 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit 87 | 88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 137 | 138 | Please set the JAVA_HOME variable in your environment to match the 139 | location of your Java installation." 140 | fi 141 | 142 | # Increase the maximum file descriptors if we can. 143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 144 | case $MAX_FD in #( 145 | max*) 146 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 147 | # shellcheck disable=SC3045 148 | MAX_FD=$( ulimit -H -n ) || 149 | warn "Could not query maximum file descriptor limit" 150 | esac 151 | case $MAX_FD in #( 152 | '' | soft) :;; #( 153 | *) 154 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 155 | # shellcheck disable=SC3045 156 | ulimit -n "$MAX_FD" || 157 | warn "Could not set maximum file descriptor limit to $MAX_FD" 158 | esac 159 | fi 160 | 161 | # Collect all arguments for the java command, stacking in reverse order: 162 | # * args from the command line 163 | # * the main class name 164 | # * -classpath 165 | # * -D...appname settings 166 | # * --module-path (only if needed) 167 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 168 | 169 | # For Cygwin or MSYS, switch paths to Windows format before running java 170 | if "$cygwin" || "$msys" ; then 171 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 172 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 173 | 174 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 175 | 176 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 177 | for arg do 178 | if 179 | case $arg in #( 180 | -*) false ;; # don't mess with options #( 181 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 182 | [ -e "$t" ] ;; #( 183 | *) false ;; 184 | esac 185 | then 186 | arg=$( cygpath --path --ignore --mixed "$arg" ) 187 | fi 188 | # Roll the args list around exactly as many times as the number of 189 | # args, so each arg winds up back in the position where it started, but 190 | # possibly modified. 191 | # 192 | # NB: a `for` loop captures its iteration list before it begins, so 193 | # changing the positional parameters here affects neither the number of 194 | # iterations, nor the values presented in `arg`. 195 | shift # remove old arg 196 | set -- "$@" "$arg" # push replacement arg 197 | done 198 | fi 199 | 200 | # Collect all arguments for the java command; 201 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of 202 | # shell script including quotes and variable substitutions, so put them in 203 | # double quotes to make sure that they get re-expanded; and 204 | # * put everything else in single quotes, so that it's not re-expanded. 205 | 206 | set -- \ 207 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 208 | -classpath "$CLASSPATH" \ 209 | org.gradle.wrapper.GradleWrapperMain \ 210 | "$@" 211 | 212 | # Stop when "xargs" is not available. 213 | if ! command -v xargs >/dev/null 2>&1 214 | then 215 | die "xargs is not available" 216 | fi 217 | 218 | # Use "xargs" to parse quoted args. 219 | # 220 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 221 | # 222 | # In Bash we could simply go: 223 | # 224 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 225 | # set -- "${ARGS[@]}" "$@" 226 | # 227 | # but POSIX shell has neither arrays nor command substitution, so instead we 228 | # post-process each arg (as a line of input to sed) to backslash-escape any 229 | # character that might be a shell metacharacter, then use eval to reverse 230 | # that process (while maintaining the separation between arguments), and wrap 231 | # the whole thing up as a single "set" statement. 232 | # 233 | # This will of course break if any of these variables contains a newline or 234 | # an unmatched quote. 235 | # 236 | 237 | eval "set -- $( 238 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 239 | xargs -n1 | 240 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 241 | tr '\n' ' ' 242 | )" '"$@"' 243 | 244 | exec "$JAVACMD" "$@" 245 | -------------------------------------------------------------------------------- /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 | @rem This is normally unused 30 | set APP_BASE_NAME=%~n0 31 | set APP_HOME=%DIRNAME% 32 | 33 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 34 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 35 | 36 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 37 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 38 | 39 | @rem Find java.exe 40 | if defined JAVA_HOME goto findJavaFromJavaHome 41 | 42 | set JAVA_EXE=java.exe 43 | %JAVA_EXE% -version >NUL 2>&1 44 | if %ERRORLEVEL% equ 0 goto execute 45 | 46 | echo. 47 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 48 | echo. 49 | echo Please set the JAVA_HOME variable in your environment to match the 50 | echo location of your Java installation. 51 | 52 | goto fail 53 | 54 | :findJavaFromJavaHome 55 | set JAVA_HOME=%JAVA_HOME:"=% 56 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 57 | 58 | if exist "%JAVA_EXE%" goto execute 59 | 60 | echo. 61 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 62 | echo. 63 | echo Please set the JAVA_HOME variable in your environment to match the 64 | echo location of your Java installation. 65 | 66 | goto fail 67 | 68 | :execute 69 | @rem Setup the command line 70 | 71 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 72 | 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if %ERRORLEVEL% equ 0 goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | set EXIT_CODE=%ERRORLEVEL% 85 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 86 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 87 | exit /b %EXIT_CODE% 88 | 89 | :mainEnd 90 | if "%OS%"=="Windows_NT" endlocal 91 | 92 | :omega 93 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'spinnaker-gradle' 2 | 3 | include 'spinnaker-project-plugin' 4 | include 'spinnaker-extensions' 5 | -------------------------------------------------------------------------------- /spinnaker-extensions/README.md: -------------------------------------------------------------------------------- 1 | # Spinnaker Extensions 2 | 3 | This project provides a set of Gradle plugins to support developers in bundling, publishing, and registering **_ 4 | Spinnaker Plugins_** with Spinnaker. 5 | 6 | ## Table of Contents 7 | 8 | - [Plugins](#plugins) 9 | - [Bundler](#bundler-plugin) 10 | - [UI Extension](#ui-extension-plugin) 11 | - [Service Extension](#service-extension-plugin) 12 | - [Compatibility Test Runner](#compatibility-test-runner) 13 | - [Project Structure](#project-structure) 14 | - [TODOS](#todos) 15 | 16 | ## Plugins 17 | 18 | ### Bundler 19 | 20 | The bundler plugin defines a **_Spinnaker Plugin_** and registers tasks to group and bundle related extensions. 21 | 22 | The bundle artifact can be stored wherever Spinnaker can access it for initiation. E.g. S3, GCS, artifactory, etc. 23 | 24 | #### Tasks 25 | 26 | | Task | Description | Dependencies | 27 | | ------------------- | ------------------------------------------------------------------------------------------------------------------- | ------------------------------ | 28 | | `collectPluginZips` | Copies the assembled extension zips into `build/distributions` directory of the bundle | `::assemblePlugin` | 29 | | `bundlePlugins` | Zips all the extension zips together into one zip. | `collectPluginZips` | 30 | | `checksumBundle` | Produces a checksum for the bundle | `bundlePlugins` | 31 | | `releaseBundle` | Makes sure that the bundle has been assembled and zipped and creates the `plugin-info.json` to describe the plugin. | `checksumBundle` | 32 | 33 | #### Project Extensions 34 | 35 | | Extension | Class | 36 | | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 37 | | `spinnakerBundle` | [SpinnakerBundleExtension](https://github.com/spinnaker/spinnaker-gradle-project/blob/68fd1accc037270e387fa889c394c32c90192289/spinnaker-extensions/src/main/kotlin/com/netflix/spinnaker/gradle/extension/extensions/SpinnakerBundleExtension.kt) | 38 | 39 | #### How to apply 40 | 41 | `apply plugin: "io.spinnaker.plugin.bundler"` 42 | 43 | #### Example 44 | 45 | ```groovy 46 | apply plugin: "io.spinnaker.plugin.bundler" 47 | 48 | spinnakerBundle { 49 | pluginId = "Example.Plugin" 50 | description = "An example plugin description." 51 | provider = "https://github.com/some-example-repo" 52 | version = rootProject.version 53 | } 54 | ``` 55 | 56 | ### UI Extension 57 | 58 | The UI extension plugin registers tasks that will assemble a [Deck](https://github.com/spinnaker/deck) extension 59 | 60 | #### Tasks 61 | 62 | | Task | Description | Dependencies | 63 | | ---------------- | ------------------------------------ | ------------- | 64 | | `yarn` | Installs package dependencies | | 65 | | `yarnModules` | Runs the command `yarn modules` | | 66 | | `yarnBuild` | Runs the command `yarn build` | `yarnModules` | 67 | | `assemblePlugin` | Zips the distribution files together | `build` | 68 | 69 | _Note_: Adds a dependency to the built in `build` task of `yarnBuild` 70 | 71 | #### How to apply 72 | 73 | `apply plugin: "io.spinnaker.plugin.ui-extension"` 74 | 75 | #### Example 76 | 77 | ```groovy 78 | apply plugin: "io.spinnaker.plugin.ui-extension" 79 | ``` 80 | 81 | ### Service Extension 82 | 83 | The Service extension plugin registers tasks that assemble a backend service extension 84 | 85 | #### Tasks 86 | 87 | | Task | Description | Dependencies | 88 | | ------------------------- | ------------------------------------------------------------------------- | ------------ | 89 | | `assemblePlugin` | Zips plugin related files (dependency jars, class files, etc) | `jar` | 90 | | `addPluginDataToManifest` | Gathers plugin details from extensions and adds them to the manifest file | | 91 | 92 | #### Project Extensions 93 | 94 | | Extension | Class | 95 | | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 96 | | `spinnakerPlugin` | [SpinnakerPluginExtension](https://github.com/spinnaker/spinnaker-gradle-project/blob/68fd1accc037270e387fa889c394c32c90192289/spinnaker-extensions/src/main/kotlin/com/netflix/spinnaker/gradle/extension/extensions/SpinnakerPluginExtension.kt) | 97 | 98 | #### How to apply 99 | 100 | `apply plugin: "io.spinnaker.plugin.service-extension"` 101 | 102 | #### Example 103 | 104 | ```groovy 105 | apply plugin: "java" 106 | apply plugin: "io.spinnaker.plugin.service-extension" 107 | 108 | repositories { 109 | mavenCentral() 110 | } 111 | 112 | dependencies { 113 | annotationProcessor "org.pf4j:pf4j:3.2.0" 114 | compileOnly("io.spinnaker.kork:kork-plugins-api:$korkVersion") 115 | } 116 | 117 | spinnakerPlugin { 118 | serviceName = "orca" 119 | pluginClassName = "com.example.service.ExamplePlugin" 120 | requires = "orca>=7.0.0" // Will default to `serviceName>=0.0.0` if undefined 121 | } 122 | ``` 123 | 124 | ### Compatibility Test Runner 125 | 126 | The Compatibility Test Runner plugin accompanies the Service Extension plugin to create tasks for easily testing your 127 | plugins inside a set of top-level Spinnaker versions (e.g., `1.21.1`). 128 | 129 | The test runner dynamically creates Gradle source sets for each top-level Spinnaker version you declare and re-writes 130 | your test dependencies to depend on the service Gradle platform version (e.g., `io.spinnaker.orca:orca-bom`) 131 | that corresponds to those Spinnaker versions. It does _not_ alter your plugin's compile or runtime classpaths. Your 132 | plugin compiles against version `X`; the test runner runs your plugin inside Spinnaker `Y` and `Z`. 133 | 134 | #### Tasks 135 | 136 | | Task | Description | Dependencies | 137 | | ----------------------------------------------------- | ------------------------------------------------------------------------------ | ----------------------------------------------------- | 138 | | `compatibilityTest-${project.name}-${config.version}` | Tasks are created for each version specified in the bundle compatibility block | | 139 | | `compatibilityTest` | Created in the bundle project to run all extension compatibility tests | `compatibilityTest-${project.name}-${config.version}` | 140 | 141 | #### Project Extensions 142 | 143 | | Extension | Class | 144 | | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 145 | | `compatibility` | [SpinnakerCompatibilityExtension](https://github.com/spinnaker/spinnaker-gradle-project/blob/68fd1accc037270e387fa889c394c32c90192289/spinnaker-extensions/src/main/kotlin/com/netflix/spinnaker/gradle/extension/extensions/SpinnakerCompatibilityExtension.kt) | 146 | 147 | #### How to apply 148 | 149 | - In the bundle project, add a `compatibility` block to the `spinnakerBundle` extension. 150 | - In a service extension subproject, `apply plugin: "io.spinnaker.plugin.compatibility-test-runner"` 151 | 152 | _Note_: Instead of providing an explicit version, you can also supply any of the following aliases: 153 | 154 | - `latest`: the most recent Spinnaker release 155 | - `supported`: the last three Spinnaker releases 156 | - `nightly`: Spinnaker's nightly build 157 | 158 | #### Example 159 | 160 | ```groovy 161 | // bundle project 162 | spinnakerBundle { 163 | // ... 164 | compatibility { 165 | spinnaker = ["1.21.1", "1.22.0"] // Set of Spinnaker versions to test against. 166 | } 167 | } 168 | 169 | // service extension subproject 170 | apply plugin: "io.spinnaker.plugin.compatibility-test-runner" 171 | ``` 172 | 173 | #### Writing Tests 174 | 175 | Tests should rely on Spinnaker's exported Gradle platforms (`-bom`). This allows the `compatibility-test-runner` 176 | to orchestrate tests against multiple versions of Spinnaker. 177 | 178 | **_Example_** 179 | 180 | ```groovy 181 | dependencies { 182 | testImplementation(platform("io.spinnaker.orca:orca-bom:${orcaVersion}")) 183 | // ... 184 | testImplementation("io.spinnaker.orca:orca-api-tck") 185 | } 186 | ``` 187 | 188 | **_Recommendations_**: 189 | 190 | - It is recommended to write tests in a Spring Boot - style integration test using [service test fixtures](https://github.com/spinnaker/orca/blob/master/orca-api-tck/src/main/kotlin/com/netflix/spinnaker/orca/api/test/OrcaFixture.kt). 191 | An example of this can be seen in [spinnaker-plugin-examples: RandomWaitStageIntegrationTest](https://github.com/spinnaker-plugin-examples/pf4jStagePlugin/blob/master/random-wait-orca/src/test/kotlin/io/armory/plugin/stage/wait/random/RandomWaitStageIntegrationTest.kt) 192 | 193 | ## Project structure 194 | 195 | Although the gradle plugins are applied to individual projects they do require a particular project structure. 196 | 197 | ### Definitions 198 | 199 | - **_bundle project_**, a project with the [bundle plugin](#bundler-plugin) applied 200 | - **_extension project_**, a project with either the [UI extension](#ui-extension-plugin) or [Service extension](#service-extension-plugin) plugin applied. 201 | 202 | ### Rules 203 | 204 | 1. A parent **_bundle project_** is created with a `spinnakerBundle` extension applied 205 | 2. One or more **_extension projects_** are direct subprojects of the parent **_bundle project_** 206 | 3. Only one [UI extension](#ui-extension-plugin) can be under a **_bundle project_** 207 | 4. Only one [Service extension](#service-extension-plugin) can exist per service name 208 | 5. A **_bundle project_** can _NOT_ be nested under another **_bundle project_** 209 | 6. Multiple **_bundle projects_** are supported (Monorepo) as long as they don't break rule 3. 210 | 211 | ### TODOs 212 | 213 | - [ ] Deck artifacts zip with in the module and collect the same in the plugin bundle ? 214 | - [ ] Publish bundle to ?? 215 | - [ ] How to register it with spinnaker ?? 216 | -------------------------------------------------------------------------------- /spinnaker-extensions/build.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | plugins { 18 | // Apply the Kotlin JVM plugin to add support for Kotlin. 19 | id("org.jetbrains.kotlin.jvm").version("1.3.72") 20 | `kotlin-dsl` 21 | } 22 | 23 | dependencies { 24 | implementation("org.gradle.crypto.checksum:org.gradle.crypto.checksum.gradle.plugin:1.4.0") 25 | 26 | implementation(platform("com.fasterxml.jackson:jackson-bom:2.11.1")) 27 | implementation("com.fasterxml.jackson.dataformat:jackson-dataformat-yaml") 28 | implementation("com.fasterxml.jackson.module:jackson-module-kotlin") 29 | implementation("org.jetbrains.kotlin:kotlin-gradle-plugin-api") 30 | implementation("org.jetbrains.kotlin:kotlin-gradle-plugin") 31 | 32 | // Kotlin standard library. 33 | implementation("org.jetbrains.kotlin:kotlin-stdlib") 34 | 35 | // Kotlin test library. 36 | testImplementation("org.jetbrains.kotlin:kotlin-test") 37 | 38 | // Kotlin JUnit integration. 39 | testImplementation("org.jetbrains.kotlin:kotlin-test-junit5") 40 | 41 | testImplementation("org.assertj:assertj-core:3.24.2") 42 | 43 | testImplementation("org.junit.jupiter:junit-jupiter-params:5.0.0") 44 | 45 | testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.0.0") 46 | } 47 | 48 | tasks.test { 49 | useJUnitPlatform() 50 | } 51 | 52 | gradlePlugin { 53 | // Define the plugin 54 | plugins { 55 | create("serviceExtension") { 56 | id = "io.spinnaker.plugin.service-extension" 57 | implementationClass = "com.netflix.spinnaker.gradle.extension.SpinnakerServiceExtensionPlugin" 58 | } 59 | 60 | create("uiExtension") { 61 | id = "io.spinnaker.plugin.ui-extension" 62 | implementationClass = "com.netflix.spinnaker.gradle.extension.SpinnakerUIExtensionPlugin" 63 | } 64 | 65 | create("bundler") { 66 | id = "io.spinnaker.plugin.bundler" 67 | implementationClass = "com.netflix.spinnaker.gradle.extension.SpinnakerExtensionsBundlerPlugin" 68 | } 69 | 70 | create("compatibilityTestRunner") { 71 | id = "io.spinnaker.plugin.compatibility-test-runner" 72 | implementationClass = "com.netflix.spinnaker.gradle.extension.compatibility.SpinnakerCompatibilityTestRunnerPlugin" 73 | } 74 | } 75 | } 76 | 77 | pluginBundle { 78 | website = "https://spinnaker.io" 79 | vcsUrl = "https://github.com/spinnaker/spinnaker-gradle-project" 80 | description = "Spinnaker extension development plugins" 81 | tags = listOf("spinnaker") 82 | 83 | (plugins) { 84 | "serviceExtension" { 85 | displayName = "Spinnaker service extension development plugin" 86 | } 87 | 88 | "uiExtension" { 89 | displayName = "Spinnaker UI extension development plugin" 90 | } 91 | 92 | "bundler" { 93 | displayName = "Spinnaker extension bundler plugin" 94 | } 95 | 96 | "compatibilityTestRunner" { 97 | displayName = "Spinnaker compatibility test runner" 98 | } 99 | } 100 | } 101 | 102 | // Add a source set for the functional test suite 103 | val functionalTestSourceSet = sourceSets.create("functionaltest") { 104 | } 105 | 106 | gradlePlugin.testSourceSets(functionalTestSourceSet) 107 | configurations.getByName("functionaltestImplementation").extendsFrom(configurations.getByName("testImplementation")) 108 | configurations.getByName("functionaltestRuntimeOnly").extendsFrom(configurations.getByName("testRuntimeOnly")) 109 | 110 | // Add a task to run the functional tests 111 | val functionalTest by tasks.creating(Test::class) { 112 | testClassesDirs = functionalTestSourceSet.output.classesDirs 113 | classpath = functionalTestSourceSet.runtimeClasspath 114 | useJUnitPlatform() 115 | } 116 | 117 | val check by tasks.getting(Task::class) { 118 | // Run the functional tests as part of `check` 119 | dependsOn(functionalTest) 120 | } 121 | 122 | tasks.withType(AbstractCopyTask::class).all { 123 | duplicatesStrategy = DuplicatesStrategy.EXCLUDE 124 | } 125 | -------------------------------------------------------------------------------- /spinnaker-extensions/src/functionaltest/kotlin/com/netflix/spinnaker/gradle/extension/SpinnakerExtensionGradlePluginFunctionalTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.gradle.extension 18 | 19 | import java.io.File 20 | import org.assertj.core.api.Assertions.assertThat 21 | import org.gradle.testkit.runner.GradleRunner 22 | import org.gradle.testkit.runner.BuildResult 23 | import org.gradle.testkit.runner.BuildTask 24 | import org.gradle.testkit.runner.TaskOutcome 25 | import kotlin.test.BeforeTest 26 | import kotlin.test.assertTrue 27 | import org.junit.jupiter.params.ParameterizedTest 28 | import org.junit.jupiter.params.provider.MethodSource 29 | 30 | const val TEST_ROOT = "build/functionaltest" 31 | 32 | /** 33 | * Functional test for the 'com.netflix.spinnaker.gradle.extension' plugin. 34 | */ 35 | class SpinnakerExtensionGradlePluginFunctionalTest { 36 | 37 | /** The version of spinnaker to test against */ 38 | val compatibilityTestVersion = "1.27.0" 39 | 40 | /** The version of orca in the above spinnaker version */ 41 | val orcaVersion = "8.18.4" 42 | 43 | /** 44 | * Assert that the specified tasks happened (or were skipped) in the order they appear 45 | * @param buildResult the build result 46 | * @param taskPaths paths of tasks to 47 | */ 48 | fun assertTaskOrder(buildResult: BuildResult, vararg taskPath: String ) { 49 | val taskOrder = buildResult.tasks.mapIndexed { index: Int, buildTask: BuildTask? -> buildTask?.path } 50 | assert(taskPath.size > 1) 51 | var i = 0 52 | while(i < taskPath.size) { 53 | val firstTaskPath = taskPath[i] 54 | val secondTaskPath = taskPath[++i] 55 | i++ 56 | assert(taskOrder.indexOf(firstTaskPath) < taskOrder.indexOf(secondTaskPath)) 57 | } 58 | } 59 | 60 | companion object{ 61 | @JvmStatic 62 | fun gradleVersion() = listOf("7.6.1") 63 | } 64 | 65 | @BeforeTest 66 | fun cleanup() { 67 | File(TEST_ROOT).also { 68 | if (it.exists()) it.deleteRecursively() 69 | } 70 | } 71 | 72 | @ParameterizedTest(name = "can run task: gradle version = {0}") 73 | @MethodSource("gradleVersion") 74 | fun `can run task`(version: String) { 75 | // Setup the test build 76 | val projectDir = File(TEST_ROOT) 77 | projectDir.mkdirs() 78 | projectDir.resolve("settings.gradle").writeText("") 79 | projectDir.resolve("build.gradle").writeText(""" 80 | plugins { 81 | id('io.spinnaker.plugin.bundler') 82 | } 83 | """) 84 | 85 | // Run the build 86 | val runner = GradleRunner.create() 87 | runner.forwardOutput() 88 | runner.withPluginClasspath() 89 | runner.withArguments("collectPluginZips") 90 | runner.withGradleVersion(version) 91 | runner.withProjectDir(projectDir) 92 | val result = runner.build() 93 | 94 | // Verify the result 95 | assertTrue(result.output.contains("BUILD SUCCESSFUL")) 96 | } 97 | 98 | @ParameterizedTest(name = "can run release bundle task, excluding compatibility test: gradle version = {0}") 99 | @MethodSource("gradleVersion") 100 | fun `can run release bundle task, excluding compatibility test`(version: String) { 101 | // Setup the test build 102 | val projectDir = File(TEST_ROOT) 103 | TestPlugin.Builder() 104 | .withRootDir(TEST_ROOT) 105 | .withService("orca") 106 | .withRootBuildGradle(""" 107 | plugins { 108 | id("io.spinnaker.plugin.bundler") 109 | } 110 | spinnakerBundle { 111 | pluginId = "Armory.TestPlugin" 112 | version = "0.0.1" 113 | description = "A plugin used to demonstrate that the build works end-to-end" 114 | provider = "daniel.peach@armory.io" 115 | } 116 | """) 117 | .withSubprojectBuildGradle(""" 118 | plugins { 119 | id("org.jetbrains.kotlin.jvm") 120 | } 121 | 122 | apply plugin: "io.spinnaker.plugin.service-extension" 123 | 124 | repositories { 125 | mavenCentral() 126 | } 127 | 128 | spinnakerPlugin { 129 | serviceName = "{{ service }}" 130 | requires = "{{ service }}>=0.0.0" 131 | pluginClass = "{{ package }}.MyPlugin" 132 | } 133 | 134 | dependencies { 135 | compileOnly("org.pf4j:pf4j:3.2.0") 136 | 137 | testImplementation("org.jetbrains.kotlin:kotlin-test") 138 | testImplementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") 139 | testImplementation("org.jetbrains.kotlin:kotlin-test") 140 | testImplementation("org.jetbrains.kotlin:kotlin-test-junit") 141 | } 142 | """) 143 | .build() 144 | 145 | // Run the build 146 | val runner = GradleRunner.create() 147 | runner.forwardOutput() 148 | runner.withPluginClasspath() 149 | runner.withArguments("releaseBundle") 150 | runner.withGradleVersion(version) 151 | runner.withProjectDir(projectDir) 152 | val result = runner.build() 153 | 154 | // Verify the result 155 | assert(result.task(":releaseBundle")!!.outcome == TaskOutcome.SUCCESS) 156 | assert(!result.tasks.contains(":compatibilityTest")) 157 | assertTrue(projectDir.resolve("build/distributions").resolve("functionaltest.zip").exists()) 158 | } 159 | 160 | @ParameterizedTest(name = "can run an end-to-end build, including compatibility test: gradle version = {0}") 161 | @MethodSource("gradleVersion") 162 | fun `can run an end-to-end build, including compatibility test`(version: String) { 163 | TestPlugin.Builder() 164 | .withRootDir(TEST_ROOT) 165 | .withService("orca") 166 | .withCompatibilityTestVersion(compatibilityTestVersion) 167 | .build() 168 | 169 | val build = GradleRunner 170 | .create() 171 | .forwardOutput() 172 | .withPluginClasspath() 173 | .withArguments("compatibilityTest", "releaseBundle") 174 | .withGradleVersion(version) 175 | .withProjectDir(File(TEST_ROOT)) 176 | .build() 177 | 178 | assert(build.task(":compatibilityTest")!!.outcome == TaskOutcome.SUCCESS) 179 | assert(build.task(":releaseBundle")!!.outcome == TaskOutcome.SUCCESS) 180 | assertTaskOrder(build, ":compatibilityTest", ":releaseBundle") 181 | val distributions = File(TEST_ROOT).resolve("build/distributions") 182 | assertTrue(distributions.resolve("functionaltest.zip").exists()) 183 | val pluginInfo = distributions.resolve("plugin-info.json").readText() 184 | listOf( 185 | """ 186 | "id": "Armory.TestPlugin", 187 | """.trimIndent(), 188 | """ 189 | "compatibility": [ 190 | { 191 | "service": "orca", 192 | "result": "SUCCESS", 193 | "platformVersion": "${compatibilityTestVersion}", 194 | "serviceVersion": "${orcaVersion}" 195 | } 196 | ] 197 | """ 198 | ).forEach { 199 | assertThat(pluginInfo).contains(it) 200 | } 201 | } 202 | 203 | @ParameterizedTest(name = "compatibility test task fails with failing test: gradle version = {0}") 204 | @MethodSource("gradleVersion") 205 | fun `compatibility test task fails with failing test`(version: String) { 206 | TestPlugin.Builder() 207 | .withRootDir(TEST_ROOT) 208 | .withService("orca") 209 | .withCompatibilityTestVersion(compatibilityTestVersion) 210 | .withTest("MyFailingTest.kt", """ 211 | package {{ package }} 212 | 213 | import kotlin.test.Test 214 | import kotlin.test.assertTrue 215 | 216 | class MyTest { 217 | @Test 218 | fun badAddition() { 219 | assertTrue(1 + 1 == 3) 220 | } 221 | } 222 | """) 223 | .build() 224 | 225 | val build = GradleRunner 226 | .create() 227 | .forwardOutput() 228 | .withPluginClasspath() 229 | .withArguments("compatibilityTest", "releaseBundle") 230 | .withGradleVersion(version) 231 | .withProjectDir(File(TEST_ROOT)) 232 | .buildAndFail() 233 | 234 | assert(build.task(":compatibilityTest")!!.outcome == TaskOutcome.FAILED) 235 | assert(!build.tasks.contains(":releaseBundle")) 236 | } 237 | 238 | @ParameterizedTest(name = "compatibility test task succeeds if failing test is not required: gradle version {0}") 239 | @MethodSource("gradleVersion") 240 | fun `compatibility test task succeeds if failing test is not required`(version: String) { 241 | TestPlugin.Builder() 242 | .withService("orca") 243 | .withCompatibilityTestVersion(compatibilityTestVersion) 244 | .withRootBuildGradle(""" 245 | plugins { 246 | id("io.spinnaker.plugin.bundler") 247 | } 248 | 249 | spinnakerBundle { 250 | pluginId = "Armory.TestPlugin" 251 | version = "0.0.1" 252 | description = "A plugin used to demonstrate that the build works end-to-end" 253 | provider = "daniel.peach@armory.io" 254 | compatibility { 255 | spinnaker { 256 | test(version: "{{ version }}", required: false) 257 | } 258 | } 259 | } 260 | """) 261 | .withTest("MyFailingTest.kt", """ 262 | package {{ package }} 263 | 264 | import kotlin.test.Test 265 | import kotlin.test.assertTrue 266 | 267 | class MyTest { 268 | @Test 269 | fun badAddition() { 270 | assertTrue(1 + 1 == 3) 271 | } 272 | } 273 | """) 274 | .build() 275 | 276 | val build = GradleRunner 277 | .create() 278 | .forwardOutput() 279 | .withPluginClasspath() 280 | .withArguments("compatibilityTest", "releaseBundle") 281 | .withGradleVersion(version) 282 | .withProjectDir(File(TEST_ROOT)) 283 | .build() 284 | 285 | assert(build.task(":compatibilityTest")!!.outcome == TaskOutcome.SUCCESS) 286 | assert(build.task(":releaseBundle")!!.outcome == TaskOutcome.SUCCESS) 287 | assertTaskOrder(build, ":compatibilityTest", ":releaseBundle") 288 | val pluginInfo = File(TEST_ROOT).resolve("build/distributions/plugin-info.json").readText() 289 | listOf( 290 | """ 291 | "id": "Armory.TestPlugin", 292 | """.trimIndent(), 293 | """ 294 | "compatibility": [ 295 | { 296 | "service": "orca", 297 | "result": "FAILURE", 298 | "platformVersion": "${compatibilityTestVersion}", 299 | "serviceVersion": "${orcaVersion}" 300 | } 301 | ] 302 | """ 303 | ).forEach { 304 | assertThat(pluginInfo).contains(it) 305 | } 306 | } 307 | } 308 | -------------------------------------------------------------------------------- /spinnaker-extensions/src/functionaltest/kotlin/com/netflix/spinnaker/gradle/extension/fixture.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Armory, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.gradle.extension 18 | 19 | import java.io.File 20 | 21 | internal class TestPlugin { 22 | var pluginPackage = "io.armory.plugins" 23 | var service = "orca" 24 | var rootDir = TEST_ROOT 25 | var compatibilityTestVersion = "1.28.0" 26 | 27 | var settingsGradle = """ 28 | pluginManagement { 29 | repositories { 30 | gradlePluginPortal() 31 | } 32 | } 33 | 34 | include "{{ service }}-plugin" 35 | """ 36 | 37 | var rootBuildGradle = """ 38 | plugins { 39 | id("io.spinnaker.plugin.bundler") 40 | } 41 | 42 | spinnakerBundle { 43 | pluginId = "Armory.TestPlugin" 44 | version = "0.0.1" 45 | description = "A plugin used to demonstrate that the build works end-to-end" 46 | provider = "daniel.peach@armory.io" 47 | compatibility { 48 | spinnaker { 49 | test "{{ version }}" 50 | } 51 | } 52 | } 53 | """ 54 | 55 | var subprojectBuildGradle = """ 56 | plugins { 57 | id("org.jetbrains.kotlin.jvm") 58 | } 59 | 60 | apply plugin: "io.spinnaker.plugin.service-extension" 61 | apply plugin: "io.spinnaker.plugin.compatibility-test-runner" 62 | 63 | repositories { 64 | mavenCentral() 65 | } 66 | 67 | spinnakerPlugin { 68 | serviceName = "{{ service }}" 69 | requires = "{{ service }}>=0.0.0" 70 | pluginClass = "{{ package }}.MyPlugin" 71 | } 72 | 73 | dependencies { 74 | compileOnly("org.pf4j:pf4j:3.2.0") 75 | 76 | testImplementation("org.jetbrains.kotlin:kotlin-test") 77 | testImplementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") 78 | testImplementation("org.jetbrains.kotlin:kotlin-test") 79 | testImplementation("org.jetbrains.kotlin:kotlin-test-junit") 80 | } 81 | """ 82 | 83 | var pluginName = "MyPlugin.java" 84 | var plugin = """ 85 | package {{ package }}; 86 | 87 | import org.pf4j.Plugin; 88 | import org.pf4j.PluginWrapper; 89 | 90 | public class MyPlugin extends Plugin { 91 | 92 | public MyPlugin(PluginWrapper wrapper) { 93 | super(wrapper); 94 | } 95 | } 96 | """ 97 | 98 | var testName = "MyTest.kt" 99 | var test = """ 100 | package {{ package }} 101 | 102 | import kotlin.test.Test 103 | import kotlin.test.assertTrue 104 | 105 | class MyTest { 106 | @Test 107 | fun addition() { 108 | assertTrue(1 + 1 == 2) 109 | } 110 | } 111 | """ 112 | 113 | class Builder { 114 | private val fixture = TestPlugin() 115 | 116 | fun withPackage(pluginPackage: String): Builder { 117 | fixture.apply { this.pluginPackage = pluginPackage } 118 | return this 119 | } 120 | 121 | fun withService(service: String): Builder { 122 | fixture.apply { this.service = service } 123 | return this 124 | } 125 | 126 | fun withRootDir(rootDir: String): Builder { 127 | fixture.apply { this.rootDir = rootDir } 128 | return this 129 | } 130 | 131 | fun withCompatibilityTestVersion(version: String): Builder { 132 | fixture.apply { compatibilityTestVersion = version } 133 | return this 134 | } 135 | 136 | fun withSettingsGradle(settingsGradle: String): Builder { 137 | fixture.apply { this.settingsGradle = settingsGradle } 138 | return this 139 | } 140 | 141 | fun withRootBuildGradle(rootBuildGradle: String): Builder { 142 | fixture.apply { this.rootBuildGradle = rootBuildGradle } 143 | return this 144 | } 145 | 146 | fun withSubprojectBuildGradle(subprojectBuildGradle: String): Builder { 147 | fixture.apply { this.subprojectBuildGradle = subprojectBuildGradle } 148 | return this 149 | } 150 | 151 | fun withPlugin(name: String, plugin: String): Builder { 152 | fixture.apply { pluginName = name; this.plugin = plugin } 153 | return this 154 | } 155 | 156 | fun withTest(name: String, test: String): Builder { 157 | fixture.apply { testName = name; this.test = test } 158 | return this 159 | } 160 | 161 | fun build(): TestPlugin { 162 | with(fixture) { 163 | directory(rootDir) { 164 | write("settings.gradle") { 165 | settingsGradle.interpolate() 166 | } 167 | write("build.gradle") { 168 | rootBuildGradle.interpolate() 169 | } 170 | subdirectory("$service-plugin") { 171 | write("build.gradle") { 172 | subprojectBuildGradle.interpolate() 173 | } 174 | subdirectory("src/main/java/${pluginPackage.replace(".", "/")}") { 175 | write(pluginName) { 176 | plugin.interpolate() 177 | } 178 | } 179 | subdirectory("src/test/kotlin/${pluginPackage.replace(".", "/")}") { 180 | write(testName) { 181 | test.interpolate() 182 | } 183 | } 184 | } 185 | } 186 | } 187 | return fixture 188 | } 189 | 190 | private fun String.interpolate(): String { 191 | var result = this 192 | listOf( 193 | "package" to fixture.pluginPackage, 194 | "service" to fixture.service, 195 | "version" to fixture.compatibilityTestVersion 196 | ).forEach { (k, v) -> 197 | result = result.replace("{{ $k }}", v) 198 | } 199 | return result 200 | } 201 | } 202 | } 203 | 204 | internal fun directory(path: String, dsl: DirectoryDsl.() -> Unit) { 205 | val dir = File(path) 206 | dir.mkdirs() 207 | 208 | object : DirectoryDsl { 209 | override fun subdirectory(path: String, dsl: DirectoryDsl.() -> Unit) { 210 | directory(dir.resolve(path).toString(), dsl) 211 | } 212 | 213 | override fun write(file: String, contents: () -> String) { 214 | dir.resolve(file).writeText(contents().trimIndent()) 215 | } 216 | }.dsl() 217 | } 218 | 219 | internal interface DirectoryDsl { 220 | fun subdirectory(path: String, dsl: DirectoryDsl.() -> Unit) 221 | fun write(file: String, contents: () -> String) 222 | } 223 | -------------------------------------------------------------------------------- /spinnaker-extensions/src/main/kotlin/com/netflix/spinnaker/gradle/extension/PluginObjectMapper.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Armory, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.netflix.spinnaker.gradle.extension 17 | 18 | import com.fasterxml.jackson.databind.DeserializationFeature 19 | import com.fasterxml.jackson.databind.ObjectMapper 20 | import com.fasterxml.jackson.module.kotlin.jacksonObjectMapper 21 | 22 | internal object PluginObjectMapper { 23 | val mapper: ObjectMapper = jacksonObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) 24 | } 25 | -------------------------------------------------------------------------------- /spinnaker-extensions/src/main/kotlin/com/netflix/spinnaker/gradle/extension/Plugins.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.netflix.spinnaker.gradle.extension 17 | 18 | import org.gradle.api.Project 19 | 20 | object Plugins { 21 | const val GROUP = "Spinnaker Plugins" 22 | 23 | const val BUNDLE_PLUGINS_TASK_NAME = "bundlePlugins" 24 | const val ASSEMBLE_PLUGIN_TASK_NAME = "assemblePlugin" 25 | const val RELEASE_BUNDLE_TASK_NAME = "releaseBundle" 26 | const val CHECKSUM_BUNDLE_TASK_NAME = "checksumBundle" 27 | const val COLLECT_PLUGIN_ZIPS_TASK_NAME = "collectPluginZips" 28 | const val ADD_PLUGIN_DATA_TO_MANIFEST = "addPluginDataToManifest" 29 | 30 | internal fun hasDeckPlugin(project: Project): Boolean = 31 | getParent(project).subprojects.any { it.plugins.hasPlugin(SpinnakerUIExtensionPlugin::class.java) } 32 | } 33 | -------------------------------------------------------------------------------- /spinnaker-extensions/src/main/kotlin/com/netflix/spinnaker/gradle/extension/ProjectHelper.kt: -------------------------------------------------------------------------------- 1 | package com.netflix.spinnaker.gradle.extension 2 | 3 | import org.gradle.api.Project 4 | 5 | fun getParent(project: Project): Project = project.parent ?: project.rootProject 6 | -------------------------------------------------------------------------------- /spinnaker-extensions/src/main/kotlin/com/netflix/spinnaker/gradle/extension/SpinnakerExtensionsBundlerPlugin.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.gradle.extension 18 | 19 | import com.netflix.spinnaker.gradle.extension.Plugins.ASSEMBLE_PLUGIN_TASK_NAME 20 | import com.netflix.spinnaker.gradle.extension.Plugins.BUNDLE_PLUGINS_TASK_NAME 21 | import com.netflix.spinnaker.gradle.extension.Plugins.CHECKSUM_BUNDLE_TASK_NAME 22 | import com.netflix.spinnaker.gradle.extension.Plugins.COLLECT_PLUGIN_ZIPS_TASK_NAME 23 | import com.netflix.spinnaker.gradle.extension.Plugins.RELEASE_BUNDLE_TASK_NAME 24 | import com.netflix.spinnaker.gradle.extension.extensions.SpinnakerBundleExtension 25 | import com.netflix.spinnaker.gradle.extension.tasks.CreatePluginInfoTask 26 | import org.gradle.api.Plugin 27 | import org.gradle.api.Project 28 | import org.gradle.api.Task 29 | import org.gradle.api.plugins.JavaPlugin 30 | import org.gradle.api.tasks.Copy 31 | import org.gradle.api.tasks.bundling.Zip 32 | import org.gradle.crypto.checksum.Checksum 33 | import org.gradle.crypto.checksum.ChecksumPlugin 34 | import java.io.File 35 | 36 | /** 37 | * Bundles all plugin artifacts into single zip. 38 | */ 39 | class SpinnakerExtensionsBundlerPlugin : Plugin { 40 | 41 | override fun apply(project: Project) { 42 | project.plugins.apply(JavaPlugin::class.java) 43 | project.plugins.apply(ChecksumPlugin::class.java) 44 | 45 | project.extensions.create("spinnakerBundle", SpinnakerBundleExtension::class.java) 46 | 47 | project.tasks.register(COLLECT_PLUGIN_ZIPS_TASK_NAME, Copy::class.java) { 48 | 49 | group = Plugins.GROUP 50 | 51 | // Look for assemblePlugin task. 52 | if (project.subprojects.isNotEmpty()) { // Safe guard this in case if project structure is not set correctly. 53 | val assemblePluginTasks: Set = project.getTasksByName(ASSEMBLE_PLUGIN_TASK_NAME, true) 54 | if (assemblePluginTasks.isNotEmpty()) { 55 | dependsOn(assemblePluginTasks) 56 | } 57 | } 58 | 59 | val distributions = project.subprojects 60 | .filter { subproject -> 61 | subproject.plugins.hasPlugin(SpinnakerServiceExtensionPlugin::class.java) || 62 | subproject.plugins.hasPlugin(SpinnakerUIExtensionPlugin::class.java) 63 | } 64 | .map { subproject -> project.file("${subproject.buildDir}/distributions") } 65 | 66 | from(distributions) 67 | .into("${project.buildDir}/zips") 68 | } 69 | 70 | project.tasks.register(BUNDLE_PLUGINS_TASK_NAME, Zip::class.java) { 71 | dependsOn(COLLECT_PLUGIN_ZIPS_TASK_NAME) 72 | group = Plugins.GROUP 73 | from("${project.buildDir}/zips") 74 | } 75 | 76 | project.tasks.register(CHECKSUM_BUNDLE_TASK_NAME, Checksum::class.java) { 77 | dependsOn(BUNDLE_PLUGINS_TASK_NAME) 78 | group = Plugins.GROUP 79 | 80 | files = project.tasks.getByName(BUNDLE_PLUGINS_TASK_NAME).outputs.files 81 | outputDir = File(project.buildDir, "checksums") 82 | algorithm = Checksum.Algorithm.SHA512 83 | } 84 | 85 | project.tasks.register(RELEASE_BUNDLE_TASK_NAME, CreatePluginInfoTask::class.java) { 86 | dependsOn(CHECKSUM_BUNDLE_TASK_NAME) 87 | group = Plugins.GROUP 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /spinnaker-extensions/src/main/kotlin/com/netflix/spinnaker/gradle/extension/SpinnakerServiceExtensionPlugin.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.gradle.extension 18 | 19 | import com.netflix.spinnaker.gradle.extension.Plugins.ASSEMBLE_PLUGIN_TASK_NAME 20 | import com.netflix.spinnaker.gradle.extension.extensions.SpinnakerBundleExtension 21 | import com.netflix.spinnaker.gradle.extension.extensions.SpinnakerPluginExtension 22 | import com.netflix.spinnaker.gradle.extension.tasks.AssembleJavaPluginZipTask 23 | import groovy.json.JsonOutput 24 | import org.gradle.api.Plugin 25 | import org.gradle.api.Project 26 | import org.gradle.api.plugins.JavaPlugin 27 | import org.gradle.api.plugins.JavaPluginExtension 28 | import org.gradle.api.tasks.bundling.Jar 29 | import org.gradle.kotlin.dsl.getByType 30 | import java.io.File 31 | import java.lang.IllegalStateException 32 | 33 | /** 34 | * Gradle plugin to support spinnaker service plugin bundling aspects. 35 | * 36 | * TODO(rz): Configure plugin manifest 37 | * TODO(rz): Auto-add `annotationProcessor "org.pf4j:pf4j:3.2.0"` 38 | */ 39 | class SpinnakerServiceExtensionPlugin : Plugin { 40 | 41 | override fun apply(project: Project) { 42 | project.plugins.apply(JavaPlugin::class.java) 43 | project.extensions.create("spinnakerPlugin", SpinnakerPluginExtension::class.java) 44 | project.tasks.register(ASSEMBLE_PLUGIN_TASK_NAME, AssembleJavaPluginZipTask::class.java) 45 | 46 | project.tasks.create(Plugins.ADD_PLUGIN_DATA_TO_MANIFEST) { 47 | doLast { 48 | (project.tasks.getByName("jar") as Jar).createPluginManifest(project) 49 | } 50 | } 51 | project.tasks.getByName("jar").dependsOn(Plugins.ADD_PLUGIN_DATA_TO_MANIFEST) 52 | } 53 | 54 | private fun Jar.createPluginManifest(project: Project) { 55 | val pluginExt = project.extensions.findByType(SpinnakerPluginExtension::class.java) 56 | ?: throw IllegalStateException("A 'spinnakerPlugin' configuration block is required") 57 | 58 | val bundleExt = getParent(project).extensions.findByType(SpinnakerBundleExtension::class.java) 59 | ?: throw IllegalStateException("A 'spinnakerBundle' configuration block is required") 60 | 61 | val attributes = mutableMapOf() 62 | 63 | val pluginVersion = removeTagPrefix(bundleExt.version, project) 64 | 65 | applyAttributeIfSet(attributes, "Plugin-Class", pluginExt.pluginClass) 66 | applyAttributeIfSet(attributes, "Plugin-Id", bundleExt.pluginId) 67 | applyAttributeIfSet(attributes, "Plugin-Version", pluginVersion) 68 | applyAttributeIfSet(attributes, "Plugin-Dependencies", pluginExt.dependencies) 69 | applyAttributeIfSet(attributes, "Plugin-Requires", pluginExt.requires) 70 | applyAttributeIfSet(attributes, "Plugin-Description", bundleExt.description) 71 | applyAttributeIfSet(attributes, "Plugin-Provider", bundleExt.provider) 72 | applyAttributeIfSet(attributes, "Plugin-License", bundleExt.license) 73 | 74 | manifest.attributes(attributes) 75 | 76 | //TODO: Generation of the plugin ref can be conditional and also make the location of the file configurable. 77 | createPluginRef(project, "${bundleExt.pluginId}-${pluginExt.serviceName}", this.temporaryDir.absolutePath) 78 | 79 | } 80 | 81 | private fun createPluginRef(project: Project, pluginExtensionName: String?, manifestLocation: String) { 82 | val sourceSets = project.extensions.getByType().sourceSets 83 | 84 | val classesDirs: List = sourceSets.getByName("main").runtimeClasspath.files 85 | .filter { !it.absolutePath.endsWith(".jar") } 86 | .map { it.absolutePath } 87 | 88 | val libDirs: List = sourceSets.getByName("main").runtimeClasspath.files 89 | .filter { it.absolutePath.endsWith(".jar") } 90 | .map { it.parent } 91 | val pluginRelInfo = mapOf( 92 | "pluginPath" to manifestLocation, 93 | "classesDirs" to classesDirs, 94 | "libsDirs" to listOf("${project.buildDir}/lib", *(libDirs.toTypedArray())) 95 | ) 96 | 97 | File(project.buildDir, "${pluginExtensionName ?: project.name}.plugin-ref").writeText( 98 | JsonOutput.prettyPrint(JsonOutput.toJson(pluginRelInfo)) 99 | ) 100 | } 101 | 102 | private fun applyAttributeIfSet(attributes: MutableMap, key: String, value: String?) { 103 | if (value != null) { 104 | attributes[key] = value 105 | } 106 | } 107 | 108 | //the plugin version is supplied with a v from tag, but fails when update manager compares versions 109 | private fun removeTagPrefix(bundleVersion: String, project: Project): String { 110 | val version = if (isVersionSpecified(bundleVersion)) bundleVersion else getParent(project).version.toString() 111 | return version.removePrefix("v") 112 | } 113 | 114 | private fun isVersionSpecified(version: String): Boolean { 115 | return version.isNotBlank() && version != Project.DEFAULT_VERSION 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /spinnaker-extensions/src/main/kotlin/com/netflix/spinnaker/gradle/extension/SpinnakerUIExtensionPlugin.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.gradle.extension 18 | 19 | import com.netflix.spinnaker.gradle.extension.Plugins.ASSEMBLE_PLUGIN_TASK_NAME 20 | import com.netflix.spinnaker.gradle.extension.tasks.AssembleUIPluginTask 21 | import org.gradle.api.Plugin 22 | import org.gradle.api.Project 23 | import org.gradle.api.plugins.JavaPlugin 24 | import org.gradle.api.tasks.Exec 25 | import org.gradle.internal.os.OperatingSystem; 26 | 27 | /** 28 | * Gradle plugin to support spinnaker UI plugin bundling aspects. 29 | * 30 | * TODO(rz): Need a setup command for scaffolding out all of the various node/rollup/yarn/npm/whatever tool configs 31 | */ 32 | class SpinnakerUIExtensionPlugin : Plugin { 33 | 34 | override fun apply(project: Project) { 35 | // Include JavaPlugin as a hack to get the Zip tasks to actually work. For whatever reason, when the JavaPlugin 36 | // is not applied, Zip tasks fail to bind their configurations, or something. 37 | project.pluginManager.apply(JavaPlugin::class.java) 38 | 39 | project.tasks.create(ASSEMBLE_PLUGIN_TASK_NAME, AssembleUIPluginTask::class.java) 40 | 41 | // Calling "yarn" on Windows doesn't work, need to use "yarn.cmd" 42 | val yarnCmd = if (OperatingSystem.current().isWindows) "yarn.cmd" else "yarn" 43 | 44 | project.tasks.create("yarn", Exec::class.java) { 45 | group = Plugins.GROUP 46 | workingDir = project.projectDir 47 | commandLine = listOf(yarnCmd) 48 | } 49 | 50 | project.tasks.create("yarnBuild", Exec::class.java) { 51 | group = Plugins.GROUP 52 | workingDir = project.projectDir 53 | commandLine = listOf(yarnCmd, "build") 54 | } 55 | 56 | project.afterEvaluate { 57 | project.tasks.getByName("build") { 58 | dependsOn("yarn", "yarnBuild") 59 | } 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /spinnaker-extensions/src/main/kotlin/com/netflix/spinnaker/gradle/extension/compatibility/CompatibilityTestResult.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Armory, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.gradle.extension.compatibility 18 | 19 | import org.gradle.api.tasks.testing.TestResult 20 | 21 | data class CompatibilityTestResult( 22 | val platformVersion: String, 23 | val serviceVersion: String, 24 | val service: String, 25 | val result: TestResult.ResultType 26 | ) 27 | -------------------------------------------------------------------------------- /spinnaker-extensions/src/main/kotlin/com/netflix/spinnaker/gradle/extension/compatibility/CompatibilityTestTask.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Armory, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.gradle.extension.compatibility 18 | 19 | import org.gradle.api.file.RegularFileProperty 20 | import org.gradle.api.tasks.OutputFile 21 | import org.gradle.api.tasks.testing.Test 22 | 23 | open class CompatibilityTestTask : Test() { 24 | 25 | @OutputFile 26 | val result: RegularFileProperty = project.objects.fileProperty() 27 | } 28 | -------------------------------------------------------------------------------- /spinnaker-extensions/src/main/kotlin/com/netflix/spinnaker/gradle/extension/compatibility/SpinnakerCompatibilityTestRunnerPlugin.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Armory, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.gradle.extension.compatibility 18 | 19 | import com.fasterxml.jackson.module.kotlin.readValue 20 | import com.netflix.spinnaker.gradle.extension.PluginObjectMapper 21 | import com.netflix.spinnaker.gradle.extension.extensions.SpinnakerBundleExtension 22 | import com.netflix.spinnaker.gradle.extension.extensions.SpinnakerPluginExtension 23 | import com.netflix.spinnaker.gradle.extension.extensions.VersionTestConfig 24 | import com.netflix.spinnaker.gradle.extension.getParent 25 | import com.netflix.spinnaker.gradle.extension.Plugins.RELEASE_BUNDLE_TASK_NAME 26 | import org.gradle.api.GradleException 27 | import org.gradle.api.Plugin 28 | import org.gradle.api.Project 29 | import org.gradle.api.artifacts.Dependency 30 | import org.gradle.api.file.SourceDirectorySet 31 | import org.gradle.api.plugins.JavaPlugin 32 | import org.gradle.api.plugins.JavaPluginExtension 33 | import org.gradle.api.tasks.SourceSet 34 | import org.gradle.api.tasks.SourceSetContainer 35 | import org.gradle.api.tasks.testing.Test 36 | import org.gradle.api.tasks.testing.TestDescriptor 37 | import org.gradle.api.tasks.testing.TestResult 38 | import org.gradle.kotlin.dsl.* 39 | import java.lang.IllegalStateException 40 | import org.jetbrains.kotlin.gradle.plugin.KotlinPluginWrapper 41 | import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet 42 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile 43 | 44 | class SpinnakerCompatibilityTestRunnerPlugin : Plugin { 45 | 46 | override fun apply(project: Project) { 47 | project.plugins.apply(JavaPlugin::class.java) 48 | project.plugins.apply(KotlinPluginWrapper::class.java) 49 | 50 | val bundle = getParent(project).extensions.getByType(SpinnakerBundleExtension::class) 51 | val spinnakerVersionsClient = DefaultSpinnakerVersionsClient(bundle.compatibility.halconfigBaseURL) 52 | 53 | val versionTestConfigs = spinnakerVersionsClient.resolveVersionAliases(bundle.compatibility.versionTestConfigs) 54 | versionTestConfigs.forEach { config -> 55 | val sourceSet = "compatibility-${config.version}" 56 | val runtimeConfiguration = "${sourceSet}RuntimeOnly" 57 | val implementationConfiguration = "${sourceSet}Implementation" 58 | 59 | project.configurations.create(runtimeConfiguration).extendsFrom(project.configurations.getByName("${SourceSet.TEST_SOURCE_SET_NAME}RuntimeOnly")) 60 | project.configurations.create(implementationConfiguration).extendsFrom(project.configurations.getByName("${SourceSet.TEST_SOURCE_SET_NAME}Implementation")) 61 | 62 | project.sourceSets.create(sourceSet) { 63 | compileClasspath += project.sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME).output 64 | runtimeClasspath += project.sourceSets.getByName(SourceSet.MAIN_SOURCE_SET_NAME).output 65 | 66 | project.sourceSets.getByName(SourceSet.TEST_SOURCE_SET_NAME).also { test -> 67 | java.srcDirs(test.java.srcDirs) 68 | kotlin.srcDirs(test.kotlin.srcDirs) 69 | resources.srcDirs(test.resources.srcDirs) 70 | } 71 | } 72 | 73 | val test = project.tasks.create("compatibilityTest-${project.name}-${config.version}") { 74 | description = "Runs compatibility tests for Spinnaker ${config.version}" 75 | group = GROUP 76 | testClassesDirs = project.sourceSets.getByName(sourceSet).output.classesDirs 77 | classpath = project.sourceSets.getByName(sourceSet).runtimeClasspath 78 | ignoreFailures = project.gradle.startParameter.taskNames.contains(TASK_NAME) 79 | result.set(project.layout.buildDirectory.file("compatibility/${config.version}.json")) 80 | } 81 | 82 | // Gradle hasn't seen the `SpinnakerPluginExtension` DSL values yet, 83 | // so push this last step into a lifecycle hook. 84 | project.afterEvaluate { 85 | val plugin = project.extensions.getByType(SpinnakerPluginExtension::class) 86 | val resolvedServiceVersion = spinnakerVersionsClient.getSpinnakerBOM(config.version).let { 87 | it.services[plugin.serviceName]?.version ?: throw IllegalStateException("Could not find version for service ${plugin.serviceName}") 88 | } 89 | 90 | project.dependencies.platform("io.spinnaker.${plugin.serviceName}:${plugin.serviceName}-bom:$resolvedServiceVersion").apply { 91 | force = true 92 | }.also { 93 | project.dependencies.add(runtimeConfiguration, it) 94 | } 95 | 96 | // Copy the kotlin test compilation options into the generated compile tasks. 97 | project.compileKotlinTask("compileTestKotlin")?.also { compileTestKt -> 98 | project.compileKotlinTask("compileCompatibility-${config.version}Kotlin")?.apply { 99 | kotlinOptions { 100 | languageVersion = compileTestKt.kotlinOptions.languageVersion 101 | jvmTarget = compileTestKt.kotlinOptions.jvmTarget 102 | } 103 | } ?: throw IllegalStateException("Could not find compileKotlin task for source set $sourceSet") 104 | } 105 | 106 | test.afterSuite { descriptor, result -> 107 | if (descriptor.parent == null) { 108 | test.result.asFile.get().writeText( 109 | PluginObjectMapper.mapper.writeValueAsString(CompatibilityTestResult( 110 | platformVersion = config.version, 111 | serviceVersion = resolvedServiceVersion, 112 | service = plugin.serviceName!!, 113 | result = result.resultType 114 | )) 115 | ) 116 | } 117 | } 118 | } 119 | } 120 | 121 | getParent(project).tasks.maybeCreate(TASK_NAME).apply { 122 | description = "Runs Spinnaker compatibility tests" 123 | group = GROUP 124 | dependsOn(versionTestConfigs.map { ":${project.name}:compatibilityTest-${project.name}-${it.version}" }) 125 | val releaseBundle = getParent(project).tasks.findByName(RELEASE_BUNDLE_TASK_NAME) 126 | if(releaseBundle != null) 127 | releaseBundle.mustRunAfter(TASK_NAME) 128 | doLast { 129 | val failedTests = getParent(project).subprojects 130 | .flatMap { it.tasks.withType(CompatibilityTestTask::class.java) } 131 | .map { PluginObjectMapper.mapper.readValue(it.result.asFile.get()) } 132 | .filter { it.result == TestResult.ResultType.FAILURE } 133 | 134 | // Only fail the top-level task if one of the tests is required. 135 | if (failedTests.any { result -> versionTestConfigs.findForResult(result).required }) { 136 | throw GradleException("Compatibility tests failed for Spinnaker ${failedTests.joinToString(", ") { it.platformVersion }}") 137 | } 138 | } 139 | } 140 | } 141 | 142 | companion object { 143 | const val TASK_NAME = "compatibilityTest" 144 | const val GROUP = "Spinnaker Compatibility" 145 | } 146 | } 147 | 148 | internal val Project.sourceSets: SourceSetContainer 149 | get() = project.extensions.getByType().sourceSets 150 | 151 | internal var Dependency.force: Boolean 152 | get() = withGroovyBuilder { getProperty("force") as Boolean } 153 | set(value) = withGroovyBuilder { "force"(value) } 154 | 155 | private val SourceSet.kotlin: SourceDirectorySet 156 | get() = withConvention(KotlinSourceSet::class) { kotlin } 157 | 158 | private fun Project.compileKotlinTask(task: String): KotlinCompile? = 159 | tasks.findByName(task) as KotlinCompile? 160 | 161 | private fun Test.afterSuite(cb: (desc: TestDescriptor, result: TestResult) -> Unit) = 162 | afterSuite(KotlinClosure2(cb)) 163 | 164 | private fun Collection.findForResult(result: CompatibilityTestResult): VersionTestConfig = 165 | find { it.version == result.platformVersion } 166 | // Should never happen. 167 | ?: throw IllegalStateException("Test result for Spinnaker ${result.platformVersion} has no test config") 168 | 169 | private fun SpinnakerVersionsClient.resolveVersionAliases(versions: List): Set { 170 | // Save a lookup if none of the versions are aliases. 171 | if (!versions.any { SpinnakerVersionAlias.isAlias(it.version) }) { 172 | return versions.toSet() 173 | } 174 | 175 | val versionsManifest = getVersionsManifest() 176 | return versions.flatMapTo(mutableSetOf()) { version -> 177 | when (version.alias) { 178 | SpinnakerVersionAlias.LATEST -> listOf(version.copy(version = versionsManifest.latestSpinnaker)) 179 | SpinnakerVersionAlias.SUPPORTED -> versionsManifest.versions.map { version.copy(version = it.version) } 180 | SpinnakerVersionAlias.NIGHTLY -> listOf(version.copy(version = "master-latest-validated")) 181 | else -> listOf(version) 182 | } 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /spinnaker-extensions/src/main/kotlin/com/netflix/spinnaker/gradle/extension/compatibility/SpinnakerVersionsClient.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Armory, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.gradle.extension.compatibility 18 | 19 | import com.fasterxml.jackson.databind.DeserializationFeature 20 | import com.fasterxml.jackson.databind.ObjectMapper 21 | import com.fasterxml.jackson.dataformat.yaml.YAMLFactory 22 | import com.fasterxml.jackson.module.kotlin.KotlinModule 23 | import com.fasterxml.jackson.module.kotlin.readValue 24 | import java.lang.IllegalArgumentException 25 | import java.net.URL 26 | 27 | class DefaultSpinnakerVersionsClient(private val baseURL: String) : SpinnakerVersionsClient { 28 | 29 | private val mapper = ObjectMapper(YAMLFactory()).registerModule(KotlinModule()).disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES) 30 | 31 | override fun getSpinnakerBOM(version: String): SpinnakerBOM = 32 | URL("$baseURL/bom/$version.yml").openStream().use { 33 | mapper.readValue(it) 34 | } 35 | 36 | override fun getVersionsManifest(): SpinnakerVersionsManifest = 37 | URL("$baseURL/versions.yml").openStream().use { 38 | mapper.readValue(it) 39 | } 40 | } 41 | 42 | interface SpinnakerVersionsClient { 43 | fun getSpinnakerBOM(version: String): SpinnakerBOM 44 | fun getVersionsManifest(): SpinnakerVersionsManifest 45 | } 46 | 47 | enum class SpinnakerVersionAlias { 48 | LATEST, SUPPORTED, NIGHTLY; 49 | 50 | companion object { 51 | fun from(str: String) = try { valueOf(str.toUpperCase()) } catch (iae: IllegalArgumentException) { null } 52 | fun isAlias(str: String) = values().any { from(str) != null } 53 | } 54 | } 55 | 56 | data class SpinnakerVersionsManifest(val latestSpinnaker: String, val versions: List) 57 | data class SpinnakerVersion(val version: String) 58 | 59 | data class SpinnakerBOM(val services: Map) 60 | data class ServiceVersion(val version: String?) 61 | -------------------------------------------------------------------------------- /spinnaker-extensions/src/main/kotlin/com/netflix/spinnaker/gradle/extension/extensions/SpinnakerBundleExtension.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.netflix.spinnaker.gradle.extension.extensions 17 | 18 | import org.gradle.api.Action 19 | import org.gradle.api.plugins.ExtensionAware 20 | import org.gradle.kotlin.dsl.create 21 | import java.lang.IllegalStateException 22 | 23 | /** 24 | * Configuration for the Spinnaker plugin bundle. 25 | */ 26 | open class SpinnakerBundleExtension { 27 | /** 28 | * The Spinnaker plugin ID. 29 | */ 30 | var pluginId: String 31 | set(value) { 32 | _pluginId = value 33 | } 34 | get() = requireValue("pluginId", _pluginId) 35 | 36 | private var _pluginId: String? = null 37 | 38 | /** 39 | * A description of the plugin functionality or behavior. 40 | */ 41 | var description: String 42 | set(value) { 43 | _description = value 44 | } 45 | get() = requireValue("description", _description) 46 | 47 | private var _description: String? = null 48 | 49 | /** 50 | * The version of the plugin bundle. 51 | * 52 | * If left unset, the value will be derived from a git tag. 53 | */ 54 | var version: String 55 | set(value) { 56 | _version = value 57 | } 58 | get() = requireValue("version", _version) 59 | 60 | private var _version: String? = null 61 | 62 | /** 63 | * The plugin author. 64 | */ 65 | var provider: String 66 | set(value) { 67 | _provider = value 68 | } 69 | get() = requireValue("provider", _provider) 70 | 71 | private var _provider: String? = null 72 | 73 | /** 74 | * The plugin license. 75 | */ 76 | var license: String? = null 77 | 78 | private fun requireValue(name: String, value: String?): String { 79 | if (value == null) { 80 | throw IllegalStateException("spinnakerBundle.$name must not be null") 81 | } 82 | return value 83 | } 84 | 85 | /** 86 | * An extension block that describes a plugin's compatibility. 87 | */ 88 | val compatibility 89 | get() = withExtensions { getByName(SpinnakerCompatibilityExtension.NAME) as SpinnakerCompatibilityExtension } 90 | 91 | // For Kotlin build scripts. 92 | fun compatibility(configure: SpinnakerCompatibilityExtension.() -> Unit) = 93 | withExtensions { 94 | create(SpinnakerCompatibilityExtension.NAME) 95 | configure(SpinnakerCompatibilityExtension.NAME, configure) 96 | } 97 | 98 | // For Groovy build scripts. 99 | fun compatibility(configure: Action) = 100 | withExtensions { 101 | create(SpinnakerCompatibilityExtension.NAME) 102 | configure(SpinnakerCompatibilityExtension.NAME, configure) 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /spinnaker-extensions/src/main/kotlin/com/netflix/spinnaker/gradle/extension/extensions/SpinnakerCompatibilityExtension.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Armory, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.gradle.extension.extensions 18 | 19 | import com.fasterxml.jackson.module.kotlin.convertValue 20 | import com.netflix.spinnaker.gradle.extension.PluginObjectMapper 21 | import com.netflix.spinnaker.gradle.extension.compatibility.SpinnakerVersionAlias 22 | import org.gradle.api.Action 23 | import org.gradle.kotlin.dsl.create 24 | 25 | /** 26 | * Configuration for plugin compatibility tests. 27 | * */ 28 | open class SpinnakerCompatibilityExtension { 29 | /** 30 | * A list of top-level Spinnaker versions (e.g., 1.21.0, 1.22.0) that this plugin should be tested against. 31 | * 32 | * e.g., 33 | * spinnakerBundle { 34 | * compatibility { 35 | * spinnaker = ["1.21.0", "1.22.0"] 36 | * } 37 | * } 38 | */ 39 | var spinnaker: List = emptyList() 40 | 41 | /** 42 | * The following "spinnaker" methods provide a DSL for more complex test configuration options. 43 | * Currently you can only define if a test is "required" or not for a given Spinnaker version; i.e., 44 | * should the top-level compatibilityTest task fail should if a test fails? 45 | * */ 46 | 47 | /** 48 | * For Kotlin build scripts. 49 | * e.g., 50 | * spinnakerBundle { 51 | * compatibility { 52 | * spinnaker { 53 | * test("1.21.1", required = false) 54 | * test("1.22.0") 55 | * } 56 | * } 57 | * } 58 | * */ 59 | fun spinnaker(configure: VersionTestConfigExtension.() -> Unit) { 60 | withExtensions { 61 | create(VersionTestConfigExtension.NAME) 62 | configure(VersionTestConfigExtension.NAME, configure) 63 | } 64 | } 65 | 66 | /** 67 | * For Groovy build scripts. 68 | * e.g., 69 | * spinnakerBundle { 70 | * compatibility { 71 | * spinnaker { 72 | * test(version: "1.21.1", required: false) 73 | * test "1.22.0" 74 | * } 75 | * } 76 | * } 77 | * */ 78 | fun spinnaker(configure: Action) { 79 | withExtensions { 80 | create(VersionTestConfigExtension.NAME) 81 | configure(VersionTestConfigExtension.NAME, configure) 82 | } 83 | } 84 | 85 | internal val versionTestConfigs 86 | get() = withExtensions { 87 | val extension = findByName(VersionTestConfigExtension.NAME) as? VersionTestConfigExtension 88 | spinnaker.map { VersionTestConfig(it) } + (extension?.configs ?: emptyList()) 89 | } 90 | 91 | var halconfigBaseURL: String = "https://storage.googleapis.com/halconfig" 92 | 93 | companion object { 94 | const val NAME = "compatibility" 95 | } 96 | } 97 | 98 | open class VersionTestConfigExtension { 99 | 100 | internal var configs = emptyList() 101 | 102 | // For Kotlin DSLs: 103 | // test("1.21.1", required = true) 104 | fun test(version: String, required: Boolean = true) { 105 | configs = configs + VersionTestConfig(version, required) 106 | } 107 | 108 | // For Groovy DSLs (Groovy can't handle default parameters): 109 | // test "1.21.1" 110 | fun test(version: String) { 111 | configs = configs + VersionTestConfig(version) 112 | } 113 | 114 | // For Groovy DSLs: 115 | // test(version: "1.21.1", required: false) 116 | fun test(config: Map) { 117 | configs = configs + PluginObjectMapper.mapper.convertValue(config) 118 | } 119 | 120 | companion object { 121 | const val NAME = "spinnaker" 122 | } 123 | } 124 | 125 | data class VersionTestConfig(val version: String, val required: Boolean = true) { 126 | val alias: SpinnakerVersionAlias? = if (SpinnakerVersionAlias.isAlias(version)) SpinnakerVersionAlias.from(version) else null 127 | } 128 | -------------------------------------------------------------------------------- /spinnaker-extensions/src/main/kotlin/com/netflix/spinnaker/gradle/extension/extensions/SpinnakerPluginExtension.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.netflix.spinnaker.gradle.extension.extensions 17 | 18 | /** 19 | * Configuration for backend service modules. 20 | */ 21 | open class SpinnakerPluginExtension { 22 | 23 | /** 24 | * The Spinnaker service name that this plugin is for. 25 | */ 26 | var serviceName: String? 27 | set(value) { 28 | _serviceName = value 29 | } 30 | get() = requireValue("serviceName", _serviceName) 31 | 32 | private var _serviceName: String? = null 33 | 34 | /** 35 | * The fully qualified plugin class name. 36 | */ 37 | var pluginClass: String? 38 | set(value) { 39 | _pluginClass = value 40 | } 41 | get() = requireValue("pluginClass", _pluginClass) 42 | 43 | private var _pluginClass: String? = null 44 | 45 | /** 46 | * Service version requirements for the plugin, e.g. "orca>=7.0.0". 47 | * 48 | * If this value remains unset, the value will be `$serviceName>=0.0.0`, effectively matching any version of the 49 | * target Spinnaker service. 50 | */ 51 | var requires: String? 52 | set(value) { 53 | _requires = value 54 | } 55 | get() { 56 | return _requires ?: "$serviceName>=0.0.0" 57 | } 58 | 59 | private var _requires: String? = null 60 | 61 | /** 62 | * Not yet supported. 63 | */ 64 | var dependencies: String? = null 65 | 66 | private fun requireValue(name: String, value: String?): String { 67 | if (value == null) { 68 | throw IllegalStateException("spinnakerPlugin.$name must not be null") 69 | } 70 | return value 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /spinnaker-extensions/src/main/kotlin/com/netflix/spinnaker/gradle/extension/extensions/dsl.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Armory, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.gradle.extension.extensions 18 | 19 | import org.gradle.api.plugins.ExtensionAware 20 | import org.gradle.api.plugins.ExtensionContainer 21 | 22 | internal fun Any.withExtensions(cb: ExtensionContainer.() -> T) = (this as ExtensionAware).extensions.let(cb) 23 | -------------------------------------------------------------------------------- /spinnaker-extensions/src/main/kotlin/com/netflix/spinnaker/gradle/extension/tasks/AssembleJavaPluginZipTask.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.netflix.spinnaker.gradle.extension.tasks 17 | 18 | import com.netflix.spinnaker.gradle.extension.Plugins 19 | import com.netflix.spinnaker.gradle.extension.extensions.SpinnakerPluginExtension 20 | import org.gradle.api.plugins.JavaPlugin 21 | import org.gradle.api.plugins.JavaPluginExtension 22 | import org.gradle.api.tasks.Internal 23 | import org.gradle.api.tasks.bundling.Zip 24 | import org.gradle.kotlin.dsl.getByType 25 | import java.io.File 26 | import java.lang.IllegalStateException 27 | 28 | /** 29 | * Task to assemble plugin related files(dependency jars, class files etc) into a zip. 30 | */ 31 | open class AssembleJavaPluginZipTask : Zip() { 32 | 33 | @Internal 34 | override fun getGroup(): String = Plugins.GROUP 35 | 36 | init { 37 | val ext = project.extensions.findByType(SpinnakerPluginExtension::class.java) 38 | ?: throw IllegalStateException("A 'spinnakerPlugin' configuration block is required") 39 | 40 | this.archiveBaseName.set(ext.serviceName) 41 | this.archiveVersion.set("") 42 | this.archiveExtension.set("zip") 43 | 44 | val sourceSets = project.extensions.getByType().sourceSets 45 | 46 | val configs = listOf("implementation", "runtimeOnly").map { project.configurations.getByName(it).copy() } 47 | configs.forEach { it.isCanBeResolved = true } 48 | val copySpecs = configs.map { 49 | project.copySpec().from(it) 50 | .into("lib/") 51 | } 52 | this.with( 53 | *(copySpecs.toTypedArray()), 54 | project.copySpec() 55 | .from(sourceSets.getByName("main").runtimeClasspath.files.filter { !it.absolutePath.endsWith(".jar") }) 56 | .from(sourceSets.getByName("main").resources) 57 | .into("classes/"), 58 | project.copySpec().from(File(project.buildDir, "tmp/jar")) 59 | .into("classes/META-INF/") 60 | ) 61 | 62 | this.dependsOn(JavaPlugin.JAR_TASK_NAME) 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /spinnaker-extensions/src/main/kotlin/com/netflix/spinnaker/gradle/extension/tasks/AssembleUIPluginTask.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.netflix.spinnaker.gradle.extension.tasks 17 | 18 | import com.netflix.spinnaker.gradle.extension.Plugins 19 | import org.gradle.api.tasks.Internal 20 | import org.gradle.api.tasks.bundling.Zip 21 | 22 | open class AssembleUIPluginTask : Zip() { 23 | 24 | @Internal 25 | override fun getGroup(): String = Plugins.GROUP 26 | 27 | init { 28 | this.archiveBaseName.set("deck") 29 | this.archiveVersion.set("") 30 | this.archiveExtension.set("zip") 31 | 32 | this.from(project.files("${project.buildDir}/dist")).into("/") 33 | 34 | project.afterEvaluate { 35 | dependsOn(project.tasks.getByName("build")) 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /spinnaker-extensions/src/main/kotlin/com/netflix/spinnaker/gradle/extension/tasks/BundlePluginsTask.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.netflix.spinnaker.gradle.extension.tasks 17 | 18 | import com.netflix.spinnaker.gradle.extension.Plugins 19 | import com.netflix.spinnaker.gradle.extension.Plugins.ASSEMBLE_PLUGIN_TASK_NAME 20 | import com.netflix.spinnaker.gradle.extension.SpinnakerServiceExtensionPlugin 21 | import com.netflix.spinnaker.gradle.extension.SpinnakerUIExtensionPlugin 22 | import org.gradle.api.tasks.Internal 23 | import org.gradle.api.tasks.TaskAction 24 | import org.gradle.api.tasks.bundling.Zip 25 | 26 | open class BundlePluginsTask : Zip() { 27 | 28 | @Internal 29 | override fun getGroup(): String = Plugins.GROUP 30 | 31 | init { 32 | project.afterEvaluate { 33 | dependsOn(project.getTasksByName(ASSEMBLE_PLUGIN_TASK_NAME, true)) 34 | 35 | } 36 | } 37 | 38 | @TaskAction 39 | fun doAction() { 40 | val distributions = project.subprojects 41 | .filter { 42 | it.plugins.hasPlugin(SpinnakerServiceExtensionPlugin::class.java) || 43 | it.plugins.hasPlugin(SpinnakerUIExtensionPlugin::class.java) 44 | } 45 | .map { project.file("${it.buildDir}/distributions") } 46 | 47 | from(distributions).into("/") 48 | include("*") 49 | 50 | archiveFileName.set("${project.name}-${project.version}.zip") 51 | 52 | // TODO(rz): Compute checksum 53 | // See: https://github.com/gradle/gradle-checksum 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /spinnaker-extensions/src/main/kotlin/com/netflix/spinnaker/gradle/extension/tasks/CreatePluginInfoTask.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.netflix.spinnaker.gradle.extension.tasks 17 | 18 | import com.fasterxml.jackson.module.kotlin.readValue 19 | import com.netflix.spinnaker.gradle.extension.PluginObjectMapper 20 | import com.netflix.spinnaker.gradle.extension.Plugins 21 | import com.netflix.spinnaker.gradle.extension.Plugins.CHECKSUM_BUNDLE_TASK_NAME 22 | import com.netflix.spinnaker.gradle.extension.compatibility.CompatibilityTestResult 23 | import com.netflix.spinnaker.gradle.extension.compatibility.CompatibilityTestTask 24 | import com.netflix.spinnaker.gradle.extension.extensions.SpinnakerBundleExtension 25 | import com.netflix.spinnaker.gradle.extension.extensions.SpinnakerPluginExtension 26 | import com.netflix.spinnaker.gradle.extension.getParent 27 | import groovy.json.JsonOutput 28 | import org.gradle.api.DefaultTask 29 | import org.gradle.api.Project 30 | import org.gradle.api.tasks.Internal 31 | import org.gradle.api.tasks.TaskAction 32 | import java.io.File 33 | import java.lang.IllegalStateException 34 | import java.time.Instant 35 | 36 | /** 37 | * TODO(rz): Need to expose release state to the world. 38 | */ 39 | open class CreatePluginInfoTask : DefaultTask() { 40 | 41 | @Internal 42 | override fun getGroup(): String = Plugins.GROUP 43 | 44 | @Internal 45 | val rootProjectVersion: String = project.version.toString() 46 | 47 | @TaskAction 48 | fun doAction() { 49 | val allPluginExts = project 50 | .subprojects 51 | .mapNotNull { it.extensions.findByType(SpinnakerPluginExtension::class.java) } 52 | .toMutableList() 53 | 54 | val bundleExt = project.extensions.findByType(SpinnakerBundleExtension::class.java) 55 | ?: throw IllegalStateException("A 'spinnakerBundle' configuration block is required") 56 | 57 | val requires = allPluginExts.map { it.requires ?: "${it.serviceName}>=0.0.0" } 58 | .let { 59 | if (Plugins.hasDeckPlugin(project)) { 60 | it + "deck>=0.0.0" 61 | } else { 62 | it 63 | } 64 | } 65 | .joinToString(",") 66 | 67 | val compatibility = project 68 | .subprojects 69 | .flatMap { it.tasks.withType(CompatibilityTestTask::class.java) } 70 | .map { it.result.get().asFile } 71 | .filter { it.exists() } 72 | .map { PluginObjectMapper.mapper.readValue(it.readBytes()) } 73 | 74 | val pluginInfo = mapOf( 75 | "id" to bundleExt.pluginId, 76 | "description" to bundleExt.description, 77 | "provider" to bundleExt.provider, 78 | "releases" to listOf( 79 | mapOf( 80 | "version" to if (isVersionSpecified(bundleExt.version)) bundleExt.version else rootProjectVersion, 81 | "date" to Instant.now().toString(), 82 | "requires" to requires, 83 | "sha512sum" to getChecksum(), 84 | "preferred" to false, 85 | "compatibility" to compatibility 86 | ) 87 | ) 88 | ) 89 | 90 | // TODO(rz): Is it bad to put the plugin-info into the distributions build dir? 91 | File(project.buildDir, "distributions/plugin-info.json").writeText( 92 | JsonOutput.prettyPrint(JsonOutput.toJson(pluginInfo)) 93 | ) 94 | } 95 | 96 | private fun isVersionSpecified(version: String): Boolean { 97 | return version.isNotBlank() && version != Project.DEFAULT_VERSION 98 | } 99 | 100 | private fun getChecksum(): String { 101 | return project.tasks 102 | .getByName(CHECKSUM_BUNDLE_TASK_NAME) 103 | .outputs 104 | .files 105 | .files 106 | .first() 107 | .listFiles() 108 | .first() 109 | .readText() 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /spinnaker-extensions/src/main/kotlin/com/netflix/spinnaker/gradle/extension/tasks/RegistrationTask.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.gradle.extension.tasks 18 | 19 | import com.netflix.spinnaker.gradle.extension.Plugins 20 | import org.gradle.api.DefaultTask 21 | import org.gradle.api.logging.LogLevel 22 | import org.gradle.api.tasks.Internal 23 | import org.gradle.api.tasks.TaskAction 24 | 25 | /** 26 | * Task to register a spinnaker plugin. 27 | * This task will invoke a spinnaker API to register 'spinnaker plugin' metadata with Front50. 28 | */ 29 | open class RegistrationTask : DefaultTask() { 30 | 31 | @Internal 32 | override fun getGroup(): String = Plugins.GROUP 33 | 34 | @TaskAction 35 | fun doAction() { 36 | project.logger.log(LogLevel.INFO, "Registration with spinnaker is not complete!!") 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /spinnaker-extensions/src/main/resources/META-INF/gradle-plugins/io.spinnaker.plugin.bundler.properties: -------------------------------------------------------------------------------- 1 | implementation-class=com.netflix.spinnaker.gradle.extension.SpinnakerExtensionsBundlerPlugin -------------------------------------------------------------------------------- /spinnaker-extensions/src/main/resources/META-INF/gradle-plugins/io.spinnaker.plugin.compatibility-test-runner.properties: -------------------------------------------------------------------------------- 1 | implementation-class=com.netflix.spinnaker.gradle.extension.compatibility.SpinnakerCompatibilityTestRunnerPlugin 2 | -------------------------------------------------------------------------------- /spinnaker-extensions/src/main/resources/META-INF/gradle-plugins/io.spinnaker.plugin.service-extension.properties: -------------------------------------------------------------------------------- 1 | implementation-class=com.netflix.spinnaker.gradle.extension.SpinnakerServiceExtensionPlugin -------------------------------------------------------------------------------- /spinnaker-extensions/src/main/resources/META-INF/gradle-plugins/io.spinnaker.plugin.ui-extension.properties: -------------------------------------------------------------------------------- 1 | implementation-class=com.netflix.spinnaker.gradle.extension.SpinnakerUIExtensionPlugin -------------------------------------------------------------------------------- /spinnaker-extensions/src/test/kotlin/com/netflix/spinnaker/gradle/extension/SpinnakerCompatibilityTestRunnerPluginTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 Armory, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.netflix.spinnaker.gradle.extension 17 | 18 | import com.netflix.spinnaker.gradle.extension.compatibility.* 19 | import com.netflix.spinnaker.gradle.extension.extensions.SpinnakerBundleExtension 20 | import com.netflix.spinnaker.gradle.extension.extensions.SpinnakerPluginExtension 21 | import com.sun.net.httpserver.HttpServer 22 | import org.gradle.api.Project 23 | import org.gradle.kotlin.dsl.withGroovyBuilder 24 | import org.gradle.testfixtures.ProjectBuilder 25 | import java.lang.IllegalStateException 26 | import java.net.InetSocketAddress 27 | import kotlin.test.assertEquals 28 | import kotlin.test.assertNotNull 29 | import kotlin.test.Test 30 | 31 | /** 32 | * Unit tests for the compatibility test runner plugin. 33 | * */ 34 | class SpinnakerCompatibilityTestRunnerPluginTest { 35 | 36 | @Test 37 | fun `compatibility-test-runner plugin registers tasks, source sets, and configurations`() { 38 | val service = "orca" 39 | val compatibility = listOf("1.21.1", "1.22.0") 40 | 41 | val (rootProject, subproject) = withProjects { 42 | rootProject { 43 | plugins.apply("io.spinnaker.plugin.bundler") 44 | spinnakerBundle { 45 | compatibility { 46 | spinnaker { 47 | compatibility.forEach { 48 | test(it) 49 | } 50 | } 51 | } 52 | } 53 | } 54 | subproject("$service-plugin") { 55 | plugins.apply("io.spinnaker.plugin.compatibility-test-runner") 56 | plugins.apply("io.spinnaker.plugin.service-extension") 57 | spinnakerPlugin { 58 | serviceName = service 59 | } 60 | } 61 | } 62 | 63 | // Verify tasks, source sets, and configurations exist. 64 | assertNotNull(rootProject.tasks.findByName(SpinnakerCompatibilityTestRunnerPlugin.TASK_NAME)) 65 | compatibility.forEach { 66 | subproject.tasks.findByName("${SpinnakerCompatibilityTestRunnerPlugin.TASK_NAME}-$service-plugin-$it").also { task -> 67 | require(task is CompatibilityTestTask) 68 | assertEquals(task.result.get().asFile.path, subproject.buildDir.toPath().resolve("compatibility/$it.json").toString()) 69 | } 70 | assertNotNull(subproject.sourceSets.findByName("compatibility-$it")) 71 | assertNotNull(subproject.configurations.findByName("compatibility-${it}Implementation")) 72 | assertNotNull(subproject.configurations.findByName("compatibility-${it}RuntimeOnly")) 73 | } 74 | } 75 | 76 | @Test 77 | fun `compatibility-test-runner plugin registers tasks, source sets, and configurations (simple DSL style)`() { 78 | val service = "orca" 79 | val compatibility = listOf("1.21.1", "1.22.0") 80 | 81 | val (rootProject, subproject) = withProjects { 82 | rootProject { 83 | plugins.apply("io.spinnaker.plugin.bundler") 84 | spinnakerBundle { 85 | compatibility { 86 | spinnaker = compatibility 87 | } 88 | } 89 | } 90 | subproject("$service-plugin") { 91 | plugins.apply("io.spinnaker.plugin.compatibility-test-runner") 92 | plugins.apply("io.spinnaker.plugin.service-extension") 93 | spinnakerPlugin { 94 | serviceName = service 95 | } 96 | } 97 | } 98 | 99 | // Verify tasks, source sets, and configurations exist. 100 | assertNotNull(rootProject.tasks.findByName(SpinnakerCompatibilityTestRunnerPlugin.TASK_NAME)) 101 | compatibility.forEach { 102 | subproject.tasks.findByName("${SpinnakerCompatibilityTestRunnerPlugin.TASK_NAME}-$service-plugin-$it").also { task -> 103 | require(task is CompatibilityTestTask) 104 | assertEquals(task.result.get().asFile.path, subproject.buildDir.toPath().resolve("compatibility/$it.json").toString()) 105 | } 106 | assertNotNull(subproject.sourceSets.findByName("compatibility-$it")) 107 | assertNotNull(subproject.configurations.findByName("compatibility-${it}Implementation")) 108 | assertNotNull(subproject.configurations.findByName("compatibility-${it}RuntimeOnly")) 109 | } 110 | } 111 | 112 | @Test 113 | fun `compatibility-test-runner sets service Gradle BOM version using Halyard BOM`() { 114 | val service = "orca" 115 | val compatibility = listOf("1.21.1", "1.22.0") 116 | 117 | // Set up bom server. 118 | val halconfigServer = HttpServer.create(InetSocketAddress(0), 0).apply { 119 | compatibility.forEach { version -> 120 | createContext("/bom/${version}.yml") { 121 | it.responseHeaders.set("Content-Type", "application/x-yaml") 122 | it.sendResponseHeaders(200, 0) 123 | it.responseBody.write(""" 124 | version: "$version" 125 | services: 126 | ${service}: 127 | version: "google-service-version-${version}" 128 | """.trimIndent().toByteArray()) 129 | it.responseBody.close() 130 | } 131 | } 132 | start() 133 | } 134 | 135 | val (_, subproject) = withProjects { 136 | rootProject { 137 | plugins.apply("io.spinnaker.plugin.bundler") 138 | spinnakerBundle { 139 | compatibility { 140 | spinnaker { 141 | compatibility.forEach { 142 | test(it) 143 | } 144 | } 145 | halconfigBaseURL = "http://localhost:${halconfigServer.address.port}" 146 | } 147 | } 148 | } 149 | subproject("$service-plugin") { 150 | plugins.apply("io.spinnaker.plugin.compatibility-test-runner") 151 | plugins.apply("io.spinnaker.plugin.service-extension") 152 | spinnakerPlugin { 153 | serviceName = service 154 | } 155 | } 156 | } 157 | 158 | // Trigger lifecycle hooks. 159 | subproject.evaluate() 160 | 161 | compatibility.forEach { version -> 162 | val runtime = subproject.configurations.findByName("compatibility-${version}RuntimeOnly") 163 | assertNotNull(runtime) 164 | 165 | val bom = runtime.dependencies.find { dependency -> 166 | dependency.group == "io.spinnaker.${service}" && dependency.name == "$service-bom" 167 | } 168 | assertNotNull(bom) 169 | assertEquals("google-service-version-${version}", bom.version) 170 | assert(bom.force) { "expected gradle BOM dependency version to be forced" } 171 | } 172 | } 173 | 174 | @Test 175 | fun `compatibility-test-runner resolves version aliases`() { 176 | val service = "orca" 177 | val compatibility = listOf("supported", "latest", "nightly", "1.22.0") 178 | 179 | // Set up Spinnaker versions server. 180 | val halconfigServer = HttpServer.create(InetSocketAddress(0), 0).apply { 181 | createContext("/versions.yml") { 182 | it.responseHeaders.set("Content-Type", "application/x-yaml") 183 | it.sendResponseHeaders(200, 0) 184 | it.responseBody.write(""" 185 | latestSpinnaker: "1.22.1" 186 | versions: 187 | - version: 1.22.1 188 | - version: 1.21.4 189 | - version: 1.20.7 190 | """.trimIndent().toByteArray()) 191 | it.responseBody.close() 192 | } 193 | start() 194 | } 195 | 196 | val (_, subproject) = withProjects { 197 | rootProject { 198 | plugins.apply("io.spinnaker.plugin.bundler") 199 | spinnakerBundle { 200 | compatibility { 201 | spinnaker { 202 | compatibility.forEach { 203 | test(it) 204 | } 205 | } 206 | halconfigBaseURL = "http://localhost:${halconfigServer.address.port}" 207 | } 208 | } 209 | } 210 | subproject("$service-plugin") { 211 | plugins.apply("io.spinnaker.plugin.compatibility-test-runner") 212 | plugins.apply("io.spinnaker.plugin.service-extension") 213 | spinnakerPlugin { 214 | serviceName = service 215 | } 216 | } 217 | } 218 | 219 | listOf( 220 | "1.22.1", // "latest", but also included in "supported" 221 | "1.22.0", // explicit 222 | "1.21.4", // included in "supported" 223 | "1.20.7", // included in "supported" 224 | "master-latest-validated" // "nightly" 225 | ).forEach { 226 | assertNotNull(subproject.tasks.findByName("${SpinnakerCompatibilityTestRunnerPlugin.TASK_NAME}-$service-plugin-$it")) 227 | } 228 | } 229 | } 230 | 231 | private fun Project.evaluate() = withGroovyBuilder { "evaluate"() } 232 | 233 | private fun withProjects(dsl: ProjectsDsl.() -> Unit): Pair { 234 | val rootProject = ProjectBuilder.builder().build() 235 | val subprojectBuilder = ProjectBuilder.builder().withParent(rootProject) 236 | var subproject: Project? = null 237 | 238 | object : ProjectsDsl { 239 | override fun rootProject(dsl: Project.() -> Unit) { 240 | rootProject.dsl() 241 | } 242 | override fun subproject(name: String, dsl: Project.() -> Unit) { 243 | subproject = subprojectBuilder.withName(name).build().also { it.dsl() } 244 | } 245 | }.dsl() 246 | 247 | return if (subproject != null) Pair(rootProject, subproject!!) else throw IllegalStateException("Must configure subproject!") 248 | } 249 | 250 | private interface ProjectsDsl { 251 | fun rootProject(dsl: Project.() -> Unit) 252 | fun subproject(name: String, dsl: Project.() -> Unit) 253 | } 254 | 255 | private fun Project.spinnakerBundle(dsl: SpinnakerBundleExtension.() -> Unit) = 256 | extensions.getByType(SpinnakerBundleExtension::class.java).dsl() 257 | 258 | private fun Project.spinnakerPlugin(dsl: SpinnakerPluginExtension.() -> Unit) = 259 | extensions.getByType(SpinnakerPluginExtension::class.java).dsl() 260 | 261 | -------------------------------------------------------------------------------- /spinnaker-extensions/src/test/kotlin/com/netflix/spinnaker/gradle/extension/SpinnakerExtensionGradlePluginTest.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.netflix.spinnaker.gradle.extension 17 | 18 | import com.netflix.spinnaker.gradle.extension.extensions.SpinnakerBundleExtension 19 | import com.netflix.spinnaker.gradle.extension.extensions.SpinnakerPluginExtension 20 | import org.gradle.testfixtures.ProjectBuilder 21 | import kotlin.test.Test 22 | import kotlin.test.assertNotNull 23 | 24 | /** 25 | * Unit tests to verify task existence in each spinnaker extension plugin. 26 | */ 27 | class SpinnakerExtensionGradlePluginTest { 28 | 29 | @Test 30 | fun `spinnakerserviceextension plugin registers task`() { 31 | // Create a test project and apply the plugin 32 | val project = ProjectBuilder.builder().build() 33 | 34 | project.plugins.apply("io.spinnaker.plugin.service-extension") 35 | project.plugins.apply("java") 36 | 37 | val extension: SpinnakerPluginExtension? = project.extensions.findByType(SpinnakerPluginExtension::class.java) 38 | assertNotNull(extension) 39 | 40 | extension.apply { 41 | this.serviceName = "test" 42 | this.pluginClass = "com.netflix.spinnaker.TestPlugin" 43 | } 44 | 45 | // Verify tasks exist 46 | assertNotNull(project.tasks.findByName(Plugins.ASSEMBLE_PLUGIN_TASK_NAME)) 47 | } 48 | 49 | @Test 50 | fun `spinnakeruiextension plugin registers task`() { 51 | // Create a test project and apply the plugin 52 | val project = ProjectBuilder.builder().build() 53 | project.plugins.apply("io.spinnaker.plugin.ui-extension") 54 | 55 | // Verify tasks exist 56 | assertNotNull(project.tasks.findByName(Plugins.ASSEMBLE_PLUGIN_TASK_NAME)) 57 | } 58 | 59 | @Test 60 | fun `spinnakerextensionbundler plugin registers task`() { 61 | // Create a test project and apply the plugin 62 | val project = ProjectBuilder.builder().build() 63 | project.plugins.apply("io.spinnaker.plugin.bundler") 64 | 65 | val extension: SpinnakerBundleExtension? = project.extensions.findByType(SpinnakerBundleExtension::class.java) 66 | assertNotNull(extension) 67 | 68 | extension.apply { 69 | this.provider = "test" 70 | this.pluginId = "Test" 71 | this.description = "Test Plugin" 72 | this.version = "1.0.0" 73 | } 74 | 75 | // Verify tasks exist 76 | assertNotNull(project.tasks.findByName(Plugins.RELEASE_BUNDLE_TASK_NAME)) 77 | assertNotNull(project.tasks.findByName(Plugins.CHECKSUM_BUNDLE_TASK_NAME)) 78 | assertNotNull(project.tasks.findByName(Plugins.BUNDLE_PLUGINS_TASK_NAME)) 79 | assertNotNull(project.tasks.findByName(Plugins.COLLECT_PLUGIN_ZIPS_TASK_NAME)) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /spinnaker-project-plugin/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'groovy' 2 | 3 | dependencies { 4 | implementation 'com.github.jk1:gradle-license-report:1.8' 5 | implementation 'org.owasp:dependency-check-gradle:5.1.0' 6 | implementation "com.diffplug.spotless:spotless-plugin-gradle:6.23.2" 7 | implementation 'org.eclipse.jgit:org.eclipse.jgit:5.4.0.201906121030-r' 8 | implementation 'com.netflix.nebula:gradle-java-cross-compile-plugin:8.0.0' 9 | implementation 'com.netflix.nebula:gradle-ospackage-plugin:8.4.1' 10 | implementation 'gradle.plugin.com.google.cloud.artifactregistry:artifactregistry-gradle-plugin:2.1.1' 11 | implementation platform('com.google.cloud:libraries-bom:26.1.4') 12 | 13 | implementation 'com.google.cloud:google-cloud-storage' 14 | implementation 'com.google.apis:google-api-services-artifactregistry:v1beta2-rev20221022-2.0.0' 15 | implementation 'com.google.api-client:google-api-client:2.0.0' 16 | implementation 'org.jetbrains.dokka:dokka-gradle-plugin:0.10.1' 17 | implementation 'io.github.gradle-nexus:publish-plugin:1.1.0' 18 | } 19 | 20 | gradlePlugin { 21 | plugins { 22 | spinnakerProject { 23 | id = 'io.spinnaker.project' 24 | implementationClass = 'com.netflix.spinnaker.gradle.project.SpinnakerProjectPlugin' 25 | } 26 | spinnakerPackage { 27 | id = 'io.spinnaker.package' 28 | implementationClass = 'com.netflix.spinnaker.gradle.application.SpinnakerPackagePlugin' 29 | } 30 | spinnakerArtifactRegistryPublish { 31 | id = 'io.spinnaker.artifactregistry-publish' 32 | implementationClass = 'com.netflix.spinnaker.gradle.publishing.artifactregistry.ArtifactRegistryPublishPlugin' 33 | } 34 | } 35 | } 36 | 37 | pluginBundle { 38 | website = 'https://spinnaker.io' 39 | vcsUrl = 'https://github.com/spinnaker/spinnaker-gradle-project' 40 | description = 'Plugins for Spinnaker services' 41 | tags = ['spinnaker'] 42 | 43 | plugins { 44 | spinnakerProject { 45 | displayName = 'Build configuration for Spinnaker projects' 46 | } 47 | spinnakerPackage { 48 | displayName = 'OS Packaging for Spinnaker applications' 49 | } 50 | spinnakerArtifactRegistryPublish { 51 | displayName = 'Google Artifact Registry publishing for spinnaker packages' 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /spinnaker-project-plugin/src/main/groovy/com/netflix/spinnaker/gradle/Flags.groovy: -------------------------------------------------------------------------------- 1 | package com.netflix.spinnaker.gradle 2 | 3 | import org.gradle.api.Project 4 | 5 | class Flags { 6 | 7 | /** 8 | * Whether or not the {@code targetJava17} property was set. 9 | */ 10 | static boolean targetJava17(Project project) { 11 | return Boolean.valueOf(project.findProperty("targetJava17")?.toString()) 12 | } 13 | 14 | /** 15 | * Whether cross-compilation should be enabled. 16 | * 17 | * Determined by the project property 'enableCrossCompilerPlugin', and 18 | * disabled by default. 19 | * 20 | * @param project the project from which to read the property 21 | * @return whether cross-compilation should be enabled 22 | */ 23 | static boolean shouldEnableCrossCompilation(Project project) { 24 | return Boolean.valueOf(project.findProperty("enableCrossCompilerPlugin")?.toString()) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /spinnaker-project-plugin/src/main/groovy/com/netflix/spinnaker/gradle/application/SpinnakerApplicationPlugin.groovy: -------------------------------------------------------------------------------- 1 | package com.netflix.spinnaker.gradle.application 2 | 3 | import org.gradle.api.Plugin 4 | import org.gradle.api.Project 5 | import org.gradle.api.plugins.ApplicationPlugin 6 | import org.gradle.api.plugins.ApplicationPluginConvention 7 | import org.gradle.api.plugins.JavaBasePlugin 8 | import org.gradle.api.plugins.JavaPluginConvention 9 | import org.gradle.api.tasks.application.CreateStartScripts 10 | import org.gradle.jvm.tasks.Jar 11 | 12 | class SpinnakerApplicationPlugin implements Plugin { 13 | @Override 14 | void apply(Project project) { 15 | project.plugins.apply(ApplicationPlugin) 16 | String appName = project.rootProject.name 17 | def appConvention = project.convention.getPlugin(ApplicationPluginConvention) 18 | appConvention.applicationName = appName 19 | appConvention.applicationDistribution.from(project.file("config/${appName}.yml")) { 20 | into('config') 21 | } 22 | appConvention.applicationDefaultJvmArgs << "-Djava.security.egd=file:/dev/./urandom" 23 | 24 | project.tasks.withType(CreateStartScripts) { 25 | it.defaultJvmOpts = appConvention.applicationDefaultJvmArgs + ["-Dspring.config.import=optional:/opt/spinnaker/config/"] 26 | it.doLast { 27 | unixScript.text = unixScript.text.replace('DEFAULT_JVM_OPTS=', '''\ 28 | if [ -f /etc/default/spinnaker ]; then 29 | set -a 30 | . /etc/default/spinnaker 31 | set +a 32 | fi 33 | DEFAULT_JVM_OPTS='''.stripIndent()) 34 | unixScript.text = unixScript.text.replace('CLASSPATH=$APP_HOME', 'CLASSPATH=$APP_HOME/config:$APP_HOME') 35 | windowsScript.text = windowsScript.text.replace('set CLASSPATH=', 'set CLASSPATH=%APP_HOME%\\config;') 36 | } 37 | } 38 | project.plugins.withType(JavaBasePlugin) { 39 | def java = project.convention.getPlugin(JavaPluginConvention) 40 | def mainSrc = java.sourceSets.getByName('main') 41 | mainSrc.resources.srcDir('src/main/resources') 42 | mainSrc.resources.srcDir('config') 43 | } 44 | 45 | project.tasks.withType(Jar) { 46 | it.exclude("${appName}.yml") 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /spinnaker-project-plugin/src/main/groovy/com/netflix/spinnaker/gradle/application/SpinnakerPackagePlugin.groovy: -------------------------------------------------------------------------------- 1 | package com.netflix.spinnaker.gradle.application 2 | 3 | import com.netflix.gradle.plugins.application.OspackageApplicationPlugin 4 | import com.netflix.gradle.plugins.packaging.ProjectPackagingExtension 5 | import org.gradle.api.Plugin 6 | import org.gradle.api.Project 7 | import org.redline_rpm.header.Os 8 | import org.redline_rpm.payload.Directive 9 | 10 | class SpinnakerPackagePlugin implements Plugin { 11 | 12 | @Override 13 | void apply(Project project) { 14 | project.plugins.apply(SpinnakerApplicationPlugin) 15 | project.plugins.apply(OspackageApplicationPlugin) 16 | ProjectPackagingExtension extension = project.extensions.getByType(ProjectPackagingExtension) 17 | extension.setOs(Os.LINUX) 18 | project.afterEvaluate { 19 | String projVer = project.version.toString() 20 | int idx = projVer.indexOf('-') 21 | if (project.hasProperty("ospackageRelease")) { 22 | extension.release = project.property("ospackageRelease") 23 | } else if (idx != -1) { 24 | extension.release = '1' 25 | } 26 | 27 | if (idx != -1 && !projVer.contains('-rc.')) { 28 | extension.setVersion(projVer.substring(0, idx)) 29 | } else { 30 | extension.setVersion(projVer) 31 | } 32 | } 33 | 34 | String appName = project.rootProject.name 35 | extension.setPackageName('spinnaker-' + appName) 36 | def postInstall = project.file('pkg_scripts/postInstall.sh') 37 | if (postInstall.exists()) { 38 | extension.postInstall(postInstall) 39 | } 40 | def postUninstall = project.file('pkg_scripts/postUninstall.sh') 41 | if (postUninstall.exists()) { 42 | extension.postUninstall(postUninstall) 43 | } 44 | 45 | def upstartConf = project.file("etc/init/${appName}.conf") 46 | if (upstartConf.exists()) { 47 | extension.from(upstartConf) { 48 | into('/etc/init') 49 | setUser('root') 50 | setPermissionGroup('root') 51 | setFileType(new Directive(Directive.RPMFILE_CONFIG | Directive.RPMFILE_NOREPLACE)) 52 | } 53 | } 54 | 55 | def systemdService = project.file("lib/systemd/system/${appName}.service") 56 | if (systemdService.exists()) { 57 | extension.from(systemdService) { 58 | into('/lib/systemd/system') 59 | setUser('root') 60 | setPermissionGroup('root') 61 | setFileType(new Directive(Directive.RPMFILE_CONFIG | Directive.RPMFILE_NOREPLACE)) 62 | } 63 | } 64 | 65 | def logrotateConf = project.file("etc/logrotate.d/${appName}") 66 | if (logrotateConf.exists()) { 67 | extension.from(logrotateConf) { 68 | into('/etc/logrotate.d') 69 | setUser('root') 70 | setPermissionGroup('root') 71 | setFileMode(0644) 72 | setFileType(new Directive(Directive.RPMFILE_CONFIG | Directive.RPMFILE_NOREPLACE)) 73 | } 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /spinnaker-project-plugin/src/main/groovy/com/netflix/spinnaker/gradle/baseproject/SpinnakerBaseProjectConventionsPlugin.groovy: -------------------------------------------------------------------------------- 1 | package com.netflix.spinnaker.gradle.baseproject 2 | 3 | import com.netflix.spinnaker.gradle.Flags 4 | import groovy.transform.CompileStatic 5 | import org.gradle.api.JavaVersion 6 | import org.gradle.api.Plugin 7 | import org.gradle.api.Project 8 | import org.gradle.api.java.archives.Manifest 9 | import org.gradle.api.plugins.BasePlugin 10 | import org.gradle.api.plugins.JavaBasePlugin 11 | import org.gradle.api.plugins.JavaLibraryPlugin 12 | import org.gradle.api.plugins.JavaPluginConvention 13 | import org.gradle.api.publish.maven.plugins.MavenPublishPlugin 14 | import org.gradle.api.tasks.Delete 15 | import org.gradle.api.tasks.javadoc.Javadoc 16 | import org.gradle.jvm.tasks.Jar 17 | 18 | @CompileStatic 19 | class SpinnakerBaseProjectConventionsPlugin implements Plugin { 20 | @Override 21 | void apply(Project project) { 22 | def javaVersion = Flags.targetJava17(project) ? JavaVersion.VERSION_17 : JavaVersion.VERSION_11 23 | project.repositories.mavenCentral() 24 | project.plugins.withType(JavaBasePlugin) { 25 | project.plugins.apply(MavenPublishPlugin) 26 | JavaPluginConvention convention = project.convention.getPlugin(JavaPluginConvention) 27 | convention.sourceCompatibility = javaVersion 28 | convention.targetCompatibility = javaVersion 29 | } 30 | project.plugins.withType(JavaLibraryPlugin) { 31 | JavaPluginConvention convention = project.convention.getPlugin(JavaPluginConvention) 32 | def sourceJar = project.tasks.create("sourceJar", Jar) 33 | sourceJar.dependsOn("classes") 34 | sourceJar.archiveClassifier.set('sources') 35 | sourceJar.from(convention.sourceSets.getByName('main').allSource) 36 | project.artifacts.add('archives', sourceJar) 37 | } 38 | // Nebula insists on building Javadoc, but we don't do anything with it 39 | // and it seems to cause lots of errors. 40 | project.tasks.withType(Javadoc) { (it as Javadoc).setFailOnError(false) } 41 | project.tasks.withType(Jar) { setImplementationVersion((it as Jar), project) } 42 | 43 | project.plugins.withType(BasePlugin) { 44 | Delete clean = project.getTasks().getByName(BasePlugin.CLEAN_TASK_NAME) as Delete 45 | clean.delete("${project.projectDir}/plugins") 46 | } 47 | } 48 | 49 | private static void setImplementationVersion(Jar jar, Project project) { 50 | def version = project.findProperty("ossVersion") ?: project.getVersion() 51 | if (version != Project.DEFAULT_VERSION) { 52 | jar.manifest { 53 | (it as Manifest).attributes(["Implementation-Version": version.toString()]) 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /spinnaker-project-plugin/src/main/groovy/com/netflix/spinnaker/gradle/baseproject/SpinnakerBaseProjectPlugin.groovy: -------------------------------------------------------------------------------- 1 | package com.netflix.spinnaker.gradle.baseproject 2 | 3 | import com.netflix.spinnaker.gradle.Flags 4 | import com.netflix.spinnaker.gradle.codestyle.SpinnakerCodeStylePlugin 5 | import com.netflix.spinnaker.gradle.idea.SpinnakerIdeaConfigPlugin 6 | import com.netflix.spinnaker.gradle.idea.SpinnakerNewIdeaProjectPlugin 7 | import com.netflix.spinnaker.gradle.license.SpinnakerLicenseReportPlugin 8 | import nebula.plugin.compile.JavaCrossCompilePlugin 9 | import org.gradle.api.Plugin 10 | import org.gradle.api.Project 11 | import org.owasp.dependencycheck.gradle.DependencyCheckPlugin 12 | 13 | class SpinnakerBaseProjectPlugin implements Plugin { 14 | @Override 15 | void apply(Project project) { 16 | project.plugins.apply(SpinnakerBaseProjectConventionsPlugin) 17 | project.plugins.apply(SpinnakerIdeaConfigPlugin) 18 | project.plugins.apply(SpinnakerNewIdeaProjectPlugin) 19 | project.plugins.apply(SpinnakerLicenseReportPlugin) 20 | project.plugins.apply(DependencyCheckPlugin) 21 | project.plugins.apply(SpinnakerCodeStylePlugin) 22 | if (Flags.shouldEnableCrossCompilation(project)) { 23 | project.plugins.apply(JavaCrossCompilePlugin) 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /spinnaker-project-plugin/src/main/groovy/com/netflix/spinnaker/gradle/codestyle/SpinnakerCodeStyle.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.netflix.spinnaker.gradle.codestyle 17 | 18 | class SpinnakerCodeStyle { 19 | boolean enabled = true 20 | List disabledProjects = [] 21 | } 22 | -------------------------------------------------------------------------------- /spinnaker-project-plugin/src/main/groovy/com/netflix/spinnaker/gradle/codestyle/SpinnakerCodeStylePlugin.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.netflix.spinnaker.gradle.codestyle 17 | 18 | import com.diffplug.gradle.spotless.FormatExtension 19 | import com.diffplug.gradle.spotless.JavaExtension 20 | import com.diffplug.gradle.spotless.KotlinExtension 21 | import com.diffplug.gradle.spotless.KotlinGradleExtension 22 | import com.diffplug.gradle.spotless.SpotlessExtension 23 | import com.diffplug.gradle.spotless.SpotlessPlugin 24 | import org.gradle.api.Action 25 | import org.gradle.api.Plugin 26 | import org.gradle.api.Project 27 | import org.gradle.api.plugins.JavaBasePlugin 28 | 29 | class SpinnakerCodeStylePlugin implements Plugin { 30 | 31 | @Override 32 | void apply(Project project) { 33 | def extension = project.extensions.create("spinnakerCodeStyle", SpinnakerCodeStyle) 34 | 35 | project.afterEvaluate { 36 | if (!extension.enabled) { 37 | project.logger.warn("${project.name} has disabled codestyle enforcement!") 38 | return 39 | } 40 | 41 | project.rootProject.file(".git/hooks").mkdirs() 42 | project.rootProject.file(".git/hooks/pre-commit").write(getClass().getResource("/pre-commit").text) 43 | project.rootProject.file(".git/hooks/pre-commit").executable = true 44 | 45 | project.plugins.apply(SpotlessPlugin) 46 | project.spotless { SpotlessExtension spotless -> 47 | 48 | // Instead of performing `spotlessCheck` on `check`, let's just `spotlessApply` instead, since devs will be 49 | // required to make the changes anyway. But don't do this if we're running in a CI build. 50 | if (!isRunningUnderContinuousIntegration()) { 51 | spotless.enforceCheck = false 52 | project.getTasks() 53 | .matching { it.name == JavaBasePlugin.CHECK_TASK_NAME } 54 | .all { it.dependsOn("spotlessApply") } 55 | } 56 | 57 | spotless.java(new Action() { 58 | @Override 59 | void execute(JavaExtension javaExtension) { 60 | javaExtension.target("src/**/*.java") 61 | javaExtension.googleJavaFormat("1.11.0") 62 | javaExtension.removeUnusedImports() 63 | javaExtension.trimTrailingWhitespace() 64 | javaExtension.endWithNewline() 65 | } 66 | }) 67 | 68 | if (hasKotlin(project)) { 69 | 70 | def ktlintData = [ 71 | indent_size : '2', 72 | continuation_indent_size: '2', 73 | // import ordering now defaults to IntelliJ-style, with java.* 74 | // and kotlin.* imports at the bottom. This is different than 75 | // google-java-format, so let's keep it consistent. 76 | kotlin_imports_layout : 'ascii' 77 | ] 78 | 79 | spotless.kotlin(new Action() { 80 | @Override 81 | void execute(KotlinExtension kotlinExtension) { 82 | kotlinExtension.ktlint("0.42.1").userData(ktlintData) 83 | kotlinExtension.trimTrailingWhitespace() 84 | kotlinExtension.endWithNewline() 85 | } 86 | }) 87 | 88 | spotless.kotlinGradle(new Action() { 89 | @Override 90 | void execute(KotlinGradleExtension kotlinGradleExtension) { 91 | kotlinGradleExtension.target("*.gradle.kts", "**/*.gradle.kts") 92 | kotlinGradleExtension.ktlint("0.42.1").userData(ktlintData) 93 | kotlinGradleExtension.trimTrailingWhitespace() 94 | kotlinGradleExtension.endWithNewline() 95 | } 96 | }) 97 | } 98 | 99 | spotless.format( 100 | 'misc', 101 | new Action() { 102 | @Override 103 | void execute(FormatExtension formatExtension) { 104 | formatExtension.target('**/.gitignore', 'src/**/*.json', 'src/**/*.yml', 'src/**/*.yaml', 'config/*.yml', 'halconfig/*.yml', '**/*.gradle') 105 | formatExtension.trimTrailingWhitespace() 106 | formatExtension.indentWithSpaces(2) 107 | formatExtension.endWithNewline() 108 | } 109 | } 110 | ) 111 | } 112 | } 113 | } 114 | 115 | private boolean hasKotlin(Project project) { 116 | Class kotlin 117 | try { 118 | kotlin = Class.forName("org.jetbrains.kotlin.gradle.plugin.KotlinPlatformJvmPlugin") 119 | } catch (ClassNotFoundException e) { 120 | return false 121 | } 122 | return !project.plugins.withType(kotlin).empty 123 | } 124 | 125 | private static boolean isRunningUnderContinuousIntegration() { 126 | return System.getenv().containsKey("GITHUB_WORKFLOW") 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /spinnaker-project-plugin/src/main/groovy/com/netflix/spinnaker/gradle/idea/SpinnakerIdeaConfigPlugin.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.gradle.idea 18 | 19 | import org.gradle.api.Plugin 20 | import org.gradle.api.Project 21 | import org.gradle.api.XmlProvider 22 | import org.gradle.api.plugins.JavaBasePlugin 23 | import org.gradle.api.plugins.JavaPluginConvention 24 | import org.gradle.plugins.ide.idea.IdeaPlugin 25 | 26 | class SpinnakerIdeaConfigPlugin implements Plugin { 27 | 28 | @Override 29 | void apply(Project project) { 30 | project.plugins.apply(IdeaPlugin) 31 | project.plugins.withType(IdeaPlugin) { IdeaPlugin idea -> 32 | if (project.rootProject == project) { 33 | project.plugins.withType(JavaBasePlugin) { 34 | JavaPluginConvention convention = project.convention.getPlugin(JavaPluginConvention) 35 | idea.model.project.jdkName = convention.sourceCompatibility 36 | idea.model.project.languageLevel = convention.targetCompatibility 37 | idea.model.project.targetBytecodeVersion = convention.targetCompatibility 38 | } 39 | idea.model.project.vcs = 'Git' 40 | idea.model.project.ipr.withXml { XmlProvider xp -> 41 | def projectNode = xp.asNode() 42 | (projectNode.component.find { it.@name == 'GradleSettings' } ?: 43 | projectNode.appendNode("component", [name: 'GradleSettings'])).replaceNode { 44 | component(name: 'GradleSettings') { 45 | option(name: 'linkedExternalProjectsSettings') { 46 | GradleProjectSettings() { 47 | option(name: 'distributionType', value: 'DEFAULT_WRAPPED') 48 | option(name: 'externalProjectPath', value: '$PROJECT_DIR$') 49 | option(name: 'useAutoImport', value: 'true') 50 | } 51 | } 52 | } 53 | } 54 | 55 | (projectNode.component.find { it.@name == 'CopyrightManager' } ?: 56 | projectNode.appendNode("component", [name: 'CopyrightManager'])).replaceNode { 57 | component(name: 'CopyrightManager', 'default': 'ASL2') { 58 | copyright() { 59 | option(name: 'notice', value: SpinnakerNewIdeaProjectPlugin.getCopyrightText(project)) 60 | option(name: 'keyword', value: 'Copyright') 61 | option(name: 'allowReplaceKeyword', value: '') 62 | option(name: 'myName', value: 'ASL2') 63 | option(name: 'myLocal', value: 'true') 64 | } 65 | } 66 | } 67 | } 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /spinnaker-project-plugin/src/main/groovy/com/netflix/spinnaker/gradle/idea/SpinnakerNewIdeaProjectPlugin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 Google, LLC 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.gradle.idea; 18 | 19 | import com.google.common.collect.ImmutableSet; 20 | import java.io.IOException; 21 | import java.io.OutputStream; 22 | import java.nio.file.Files; 23 | import java.nio.file.Path; 24 | import java.nio.file.Paths; 25 | import java.util.Objects; 26 | import java.util.stream.Stream; 27 | import javax.xml.parsers.DocumentBuilder; 28 | import javax.xml.parsers.DocumentBuilderFactory; 29 | import javax.xml.parsers.ParserConfigurationException; 30 | import javax.xml.transform.TransformerException; 31 | import javax.xml.transform.TransformerFactory; 32 | import javax.xml.transform.dom.DOMSource; 33 | import javax.xml.transform.stream.StreamResult; 34 | import org.eclipse.jgit.dircache.DirCache; 35 | import org.eclipse.jgit.dircache.DirCacheEntry; 36 | import org.eclipse.jgit.lib.Repository; 37 | import org.eclipse.jgit.storage.file.FileRepositoryBuilder; 38 | import org.gradle.api.Plugin; 39 | import org.gradle.api.Project; 40 | import org.w3c.dom.Document; 41 | import org.w3c.dom.Element; 42 | 43 | public class SpinnakerNewIdeaProjectPlugin implements Plugin { 44 | 45 | private static final Path LICENSE_FILE = Paths.get("copyright", "ALS2.xml"); 46 | private static final ImmutableSet COMMITTED_IDEA_FILES = ImmutableSet.of( 47 | Paths.get("compiler.xml"), 48 | LICENSE_FILE, 49 | Paths.get("copyright", "profiles_settings.xml"), 50 | Paths.get("google-java-format.xml"), 51 | Paths.get("gradle.xml"), 52 | Paths.get("vcs.xml") 53 | ); 54 | 55 | private static final String COPYRIGHT_ORG_PROPERTY = "io.spinnaker.copyright"; 56 | private static final String DEFAULT_COPYRIGHT_OWNER = "Netflix, Inc."; 57 | 58 | @Override 59 | public void apply(Project project) { 60 | project.afterEvaluate(unused -> { 61 | if (project != project.getRootProject()) { 62 | return; 63 | } 64 | 65 | updateGitIndex(project); 66 | updateCopyrightText(project); 67 | }); 68 | } 69 | 70 | // Runs the equivalent of 71 | // git update-index --assume-unchanged $FILE 72 | // for each file in COMMITED_IDEA_FILES. After opening the project, IntelliJ will modify some of 73 | // these files. Doing this tells git never to consider these modifications for adding to the 74 | // index. If you want to commit modifications to these files you must first undo this operation. 75 | private static void updateGitIndex(Project project) { 76 | try { 77 | Repository repository = new FileRepositoryBuilder().setMustExist(true).setWorkTree(project.getRootDir()).build(); 78 | DirCache index = repository.readDirCache(); 79 | if (!needsUpdate(index)) { 80 | return; 81 | } 82 | 83 | if (!index.lock()) { 84 | throw new IOException("Couldn't get lock for git repository."); 85 | } 86 | entryStream(index).forEach(entry -> entry.setAssumeValid(true)); 87 | index.write(); 88 | if (!index.commit()) { 89 | throw new IOException("Couldn't commit changes to git repository."); 90 | } 91 | } catch (IOException e) { 92 | System.out.println("Error configuring git repository for idea files: " + e.getMessage()); 93 | } 94 | } 95 | 96 | private static boolean needsUpdate(DirCache index) { 97 | return entryStream(index).anyMatch(entry -> !entry.isAssumeValid()); 98 | } 99 | 100 | private static Stream entryStream(DirCache index) { 101 | return COMMITTED_IDEA_FILES.stream().map(path -> Paths.get(".idea").resolve(path)).map(Path::toString).map( 102 | index::getEntry 103 | ).filter(Objects::nonNull); 104 | } 105 | 106 | private static void updateCopyrightText(Project project) { 107 | 108 | Path licensePath = project.getRootDir().toPath().resolve(".idea").resolve(LICENSE_FILE); 109 | 110 | if (!licensePath.toFile().exists()) { 111 | return; 112 | } 113 | 114 | try { 115 | Document document = generateCopyrightXml(project); 116 | 117 | try (OutputStream out = Files.newOutputStream(licensePath)) { 118 | TransformerFactory.newInstance().newTransformer().transform(new DOMSource(document), new StreamResult(out)); 119 | } 120 | } catch (IOException | ParserConfigurationException | TransformerException e) { 121 | System.out.println("Error updating license: " + e.getMessage()); 122 | } 123 | } 124 | 125 | private static Document generateCopyrightXml(Project project) throws ParserConfigurationException { 126 | 127 | DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); 128 | DocumentBuilder db = dbf.newDocumentBuilder(); 129 | 130 | Document document = db.newDocument(); 131 | 132 | Element component = document.createElement("component"); 133 | component.setAttribute("name", "CopyrightManager"); 134 | document.appendChild(component); 135 | 136 | Element copyright = document.createElement("copyright"); 137 | component.appendChild(copyright); 138 | 139 | Element notice = document.createElement("option"); 140 | notice.setAttribute("name", "notice"); 141 | notice.setAttribute("value", getCopyrightText(project)); 142 | copyright.appendChild(notice); 143 | 144 | Element name = document.createElement("option"); 145 | name.setAttribute("name", "myName"); 146 | name.setAttribute("value", "ALS2"); 147 | copyright.appendChild(name); 148 | 149 | return document; 150 | } 151 | 152 | static String getCopyrightText(Project project) { 153 | String copyrightOrg = project.hasProperty(COPYRIGHT_ORG_PROPERTY) ? (String) project.property( 154 | COPYRIGHT_ORG_PROPERTY 155 | ) : DEFAULT_COPYRIGHT_OWNER; 156 | return "Copyright $today.year " + copyrightOrg + "\n\n" 157 | + "Licensed under the Apache License, Version 2.0 (the \"License\");\n" 158 | + "you may not use this file except in compliance with the License.\n" 159 | + "You may obtain a copy of the License at\n" + "\n" + " http://www.apache.org/licenses/LICENSE-2.0\n" + "\n" 160 | + "Unless required by applicable law or agreed to in writing, software\n" 161 | + "distributed under the License is distributed on an \"AS IS\" BASIS,\n" 162 | + "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n" 163 | + "See the License for the specific language governing permissions and\n" + "limitations under the License."; 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /spinnaker-project-plugin/src/main/groovy/com/netflix/spinnaker/gradle/license/SpinnakerLicenseReportPlugin.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Google, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License") 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.gradle.license 18 | 19 | import com.github.jk1.license.LicenseReportExtension 20 | import com.github.jk1.license.LicenseReportPlugin 21 | import com.github.jk1.license.filter.LicenseBundleNormalizer 22 | import com.github.jk1.license.render.CsvReportRenderer 23 | import com.github.jk1.license.render.InventoryHtmlReportRenderer 24 | import com.github.jk1.license.render.JsonReportRenderer 25 | 26 | import groovy.json.JsonSlurper 27 | import org.gradle.api.Plugin 28 | import org.gradle.api.Project 29 | import org.gradle.api.plugins.ProjectReportsPlugin 30 | 31 | import static com.github.jk1.license.filter.LicenseBundleNormalizer.* 32 | 33 | class SpinnakerLicenseReportPlugin implements Plugin { 34 | @Override 35 | void apply(Project project) { 36 | project.plugins.apply(ProjectReportsPlugin) 37 | project.plugins.apply(LicenseReportPlugin) 38 | 39 | LicenseReportExtension pluginConfig = project.extensions.getByType(LicenseReportExtension) 40 | pluginConfig.configurations = LicenseReportExtension.ALL 41 | pluginConfig.renderers = [ 42 | new InventoryHtmlReportRenderer(), 43 | new CsvReportRenderer(), 44 | new JsonReportRenderer() ] 45 | 46 | def licenseBundleNormalizer = new LicenseBundleNormalizer() 47 | addSpinnakerNormalizerBundle(licenseBundleNormalizer.normalizerConfig) 48 | pluginConfig.filters = [ licenseBundleNormalizer ] 49 | } 50 | 51 | def addSpinnakerNormalizerBundle(LicenseBundleNormalizerConfig config) { 52 | def additionalConfig = new JsonSlurper().parse(getClass().getResourceAsStream("/license-normalizer-bundle.json")) 53 | 54 | additionalConfig.bundles.each { Map bundle -> 55 | config.bundles.add(new NormalizerLicenseBundle(bundle)) 56 | } 57 | 58 | additionalConfig.transformationRules.each { Map transformationRule -> 59 | config.transformationRules.add(new NormalizerTransformationRule(transformationRule)) 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /spinnaker-project-plugin/src/main/groovy/com/netflix/spinnaker/gradle/project/SpinnakerProjectPlugin.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Netflix, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.netflix.spinnaker.gradle.project 18 | 19 | import com.netflix.spinnaker.gradle.baseproject.SpinnakerBaseProjectPlugin 20 | import com.netflix.spinnaker.gradle.publishing.artifactregistry.ArtifactRegistryPublishPlugin 21 | import com.netflix.spinnaker.gradle.publishing.PublishingPlugin 22 | import com.netflix.spinnaker.gradle.publishing.nexus.NexusPublishPlugin 23 | import org.gradle.api.Plugin 24 | import org.gradle.api.Project 25 | import org.gradle.api.file.DuplicatesStrategy 26 | import org.gradle.api.tasks.AbstractCopyTask 27 | 28 | class SpinnakerProjectPlugin implements Plugin { 29 | 30 | @Override 31 | void apply(Project project) { 32 | project.plugins.apply(SpinnakerBaseProjectPlugin) 33 | project.plugins.apply(PublishingPlugin) 34 | project.plugins.apply(ArtifactRegistryPublishPlugin) 35 | project.plugins.apply(NexusPublishPlugin) 36 | project.tasks.withType(AbstractCopyTask) { 37 | it.configure {setProperty("duplicatesStrategy",DuplicatesStrategy.EXCLUDE)} 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /spinnaker-project-plugin/src/main/groovy/com/netflix/spinnaker/gradle/publishing/PublishingPlugin.groovy: -------------------------------------------------------------------------------- 1 | package com.netflix.spinnaker.gradle.publishing 2 | 3 | import org.gradle.api.Action 4 | import org.gradle.api.Plugin 5 | import org.gradle.api.Project 6 | import org.gradle.api.plugins.JavaLibraryPlugin 7 | import org.gradle.api.plugins.JavaPlatformPlugin 8 | import org.gradle.api.publish.PublishingExtension 9 | import org.gradle.api.publish.maven.MavenPom 10 | import org.gradle.api.publish.maven.MavenPomLicense 11 | import org.gradle.api.publish.maven.MavenPomLicenseSpec 12 | import org.gradle.api.publish.maven.MavenPublication 13 | import org.gradle.api.publish.maven.plugins.MavenPublishPlugin 14 | import org.gradle.api.publish.tasks.GenerateModuleMetadata 15 | 16 | class PublishingPlugin implements Plugin { 17 | 18 | public static final String PUBLICATION_NAME = "spinnaker" 19 | 20 | @Override 21 | void apply(Project project) { 22 | project.plugins.withType(JavaLibraryPlugin) { 23 | project.plugins.apply(MavenPublishPlugin) 24 | project.logger.info "adding maven publication for java library in $project.name" 25 | project.extensions.configure(PublishingExtension) { publishingExtension -> 26 | publishingExtension.publications.create(PUBLICATION_NAME, MavenPublication) { pub -> 27 | pub.from(project.components.getByName("java")) 28 | project.tasks.matching { it.name == 'sourceJar' }.configureEach { 29 | pub.artifact(it) 30 | } 31 | addLicenseMetadata(pub) 32 | } 33 | // Presence of a module metadata file causes IntelliJ to not associate -sources jars with libraries. 34 | // removing for now, can revisit if there is a good reason to have .modules 35 | project.tasks.named("generateMetadataFileFor${PUBLICATION_NAME.capitalize()}Publication", GenerateModuleMetadata) { 36 | it.enabled = false 37 | } 38 | } 39 | } 40 | 41 | project.plugins.withType(JavaPlatformPlugin) { 42 | project.plugins.apply(MavenPublishPlugin) 43 | project.logger.info "adding maven publication for java platform in $project.name" 44 | project.extensions.configure(PublishingExtension) { publishingExtension -> 45 | publishingExtension.publications.create(PUBLICATION_NAME, MavenPublication) { pub -> 46 | pub.from(project.components.getByName("javaPlatform")) 47 | addLicenseMetadata(pub) 48 | } 49 | } 50 | // Presence of a module metadata file causes some weird failures in kork spinnaker-dependencies and we don't 51 | // really need them, so just disabling for now for javaPlatform modules 52 | project.tasks.named("generateMetadataFileFor${PUBLICATION_NAME.capitalize()}Publication", GenerateModuleMetadata) { 53 | it.enabled = false 54 | } 55 | } 56 | 57 | // Provide a shim to the previous entrypoint for publishing so that buildscripts can work against 7.x and 8.+ 58 | // versions of the gradle project. 59 | // 60 | // We can remove this once we aren't maintaining release branches on the 7.x gradle plugin 61 | project.plugins.withType(org.gradle.api.publish.plugins.PublishingPlugin) { 62 | if (Boolean.valueOf(project.findProperty("enablePublishing") as String)) { 63 | for (shimTaskName in ["candidate", "final"]) { 64 | project.tasks.register(shimTaskName) { 65 | it.dependsOn(org.gradle.api.publish.plugins.PublishingPlugin.PUBLISH_LIFECYCLE_TASK_NAME) 66 | } 67 | } 68 | } 69 | } 70 | } 71 | 72 | static void addLicenseMetadata(MavenPublication pub) { 73 | pub.pom(new Action() { 74 | void execute(MavenPom mavenPom) { 75 | mavenPom.licenses(new Action() { 76 | void execute(MavenPomLicenseSpec mavenPomLicenseSpec) { 77 | mavenPomLicenseSpec.license(new Action() { 78 | void execute(MavenPomLicense mavenPomLicense) { 79 | mavenPomLicense.name.set('Apache License, Version 2.0') 80 | mavenPomLicense.url.set('http://www.apache.org/licenses/LICENSE-2.0.txt') 81 | mavenPomLicense.distribution.set('repo') 82 | } 83 | }) 84 | } 85 | }) 86 | } 87 | }) 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /spinnaker-project-plugin/src/main/groovy/com/netflix/spinnaker/gradle/publishing/artifactregistry/ArtifactRegistryDebPublishTask.java: -------------------------------------------------------------------------------- 1 | package com.netflix.spinnaker.gradle.publishing.artifactregistry; 2 | 3 | import com.google.api.client.googleapis.javanet.GoogleNetHttpTransport; 4 | import com.google.api.client.json.jackson2.JacksonFactory; 5 | import com.google.api.services.artifactregistry.v1beta2.ArtifactRegistryScopes; 6 | import com.google.api.services.artifactregistry.v1beta2.model.ImportAptArtifactsGcsSource; 7 | import com.google.api.services.artifactregistry.v1beta2.model.ImportAptArtifactsRequest; 8 | import com.google.api.services.artifactregistry.v1beta2.model.Operation; 9 | import com.google.api.services.artifactregistry.v1beta2.ArtifactRegistry; 10 | import com.google.auth.Credentials; 11 | import com.google.auth.http.HttpCredentialsAdapter; 12 | import com.google.auth.oauth2.GoogleCredentials; 13 | import com.google.cloud.artifactregistry.auth.DefaultCredentialProvider; 14 | import com.google.cloud.storage.*; 15 | import com.google.common.base.Stopwatch; 16 | import com.google.common.base.Strings; 17 | import com.google.common.io.ByteStreams; 18 | import org.apache.tools.ant.filters.StringInputStream; 19 | import org.gradle.api.DefaultTask; 20 | import org.gradle.api.file.RegularFile; 21 | import org.gradle.api.provider.Provider; 22 | import org.gradle.api.tasks.Input; 23 | import org.gradle.api.tasks.InputFile; 24 | import org.gradle.api.tasks.TaskAction; 25 | 26 | import javax.inject.Inject; 27 | import java.io.IOException; 28 | import java.nio.channels.ByteChannel; 29 | import java.nio.channels.WritableByteChannel; 30 | import java.nio.file.Files; 31 | import java.security.GeneralSecurityException; 32 | import java.util.List; 33 | import java.util.concurrent.TimeUnit; 34 | 35 | class ArtifactRegistryDebPublishTask extends DefaultTask { 36 | 37 | private static final String GOOGLE_SERVICE_ACCT_JSON_ENV_VAR = "GAR_JSON_KEY"; 38 | 39 | private Provider uploadBucket; 40 | private Provider repoProject; 41 | private Provider location; 42 | private Provider repository; 43 | private Provider archiveFile; 44 | private Provider aptImportTimeoutSeconds; 45 | 46 | @Inject 47 | public ArtifactRegistryDebPublishTask() {} 48 | 49 | @Input 50 | public Provider getUploadBucket() { 51 | return uploadBucket; 52 | } 53 | 54 | public void setUploadBucket(Provider uploadBucket) { 55 | this.uploadBucket = uploadBucket; 56 | } 57 | 58 | @Input 59 | public Provider getRepoProject() { 60 | return repoProject; 61 | } 62 | 63 | public void setRepoProject(Provider repoProject) { 64 | this.repoProject = repoProject; 65 | } 66 | 67 | @Input 68 | public Provider getLocation() { 69 | return location; 70 | } 71 | 72 | public void setLocation(Provider location) { 73 | this.location = location; 74 | } 75 | 76 | @Input 77 | public Provider getRepository() { 78 | return repository; 79 | } 80 | 81 | public void setRepository(Provider repository) { 82 | this.repository = repository; 83 | } 84 | 85 | @Input 86 | public Provider getAptImportTimeoutSeconds() { 87 | return aptImportTimeoutSeconds; 88 | } 89 | 90 | public void setAptImportTimeoutSeconds(Provider aptImportTimeout) { 91 | this.aptImportTimeoutSeconds = aptImportTimeout; 92 | } 93 | 94 | @InputFile 95 | public Provider getArchiveFile() { 96 | return archiveFile; 97 | } 98 | 99 | public void setArchiveFile(Provider archiveFile) { 100 | this.archiveFile = archiveFile; 101 | } 102 | 103 | @TaskAction 104 | void publishDeb() throws GeneralSecurityException, InterruptedException, IOException { 105 | Storage storage = StorageOptions.newBuilder().setCredentials(resolveCredentials()).build().getService(); 106 | BlobId blobId = uploadDebToGcs(storage); 107 | Operation importOperation = importDebToArtifactRegistry(blobId); 108 | 109 | deleteDebFromGcs(storage, blobId); 110 | 111 | if (!operationIsDone(importOperation)) { 112 | throw new IOException("Operation timed out importing debian package to Artifact Registry."); 113 | } else if (getErrors(importOperation) != null) { 114 | throw new IOException( 115 | "Received an error importing debian package to Artifact Registry: " + getErrors(importOperation) 116 | ); 117 | } 118 | } 119 | 120 | private void deleteDebFromGcs(Storage storage, BlobId blobId) { 121 | try { 122 | storage.delete(blobId); 123 | } catch (StorageException e) { 124 | getProject().getLogger().warn("Error deleting deb from temp GCS storage", e); 125 | } 126 | } 127 | 128 | private Credentials resolveCredentials() throws IOException { 129 | String fromEnvironmentVar = System.getenv(GOOGLE_SERVICE_ACCT_JSON_ENV_VAR); 130 | 131 | if (!Strings.isNullOrEmpty(fromEnvironmentVar)) { 132 | return GoogleCredentials.fromStream(new StringInputStream(fromEnvironmentVar)).createScoped( 133 | ArtifactRegistryScopes.CLOUD_PLATFORM 134 | ); 135 | } 136 | 137 | return new DefaultCredentialProvider().getCredential(); 138 | } 139 | 140 | /** 141 | * Import the blob into Artifact Registry and return an Operation representing the import. 142 | * 143 | *

144 | * If the Operation is not done, that means we timed out before finishing the import. The operation 145 | * should also be checked for errors. 146 | */ 147 | private Operation importDebToArtifactRegistry(BlobId blobId) throws GeneralSecurityException, IOException, 148 | InterruptedException { 149 | 150 | ArtifactRegistry artifactRegistryClient = new ArtifactRegistry( 151 | GoogleNetHttpTransport.newTrustedTransport(), JacksonFactory.getDefaultInstance(), new HttpCredentialsAdapter( 152 | resolveCredentials() 153 | ) 154 | ); 155 | 156 | String parent = String.format( 157 | "projects/%s/locations/%s/repositories/%s", 158 | repoProject.get(), 159 | location.get(), 160 | repository.get() 161 | ); 162 | ImportAptArtifactsGcsSource gcsSource = new ImportAptArtifactsGcsSource(); 163 | gcsSource.setUris(List.of(String.format("gs://%s/%s", blobId.getBucket(), blobId.getName()))); 164 | 165 | ImportAptArtifactsRequest content = new ImportAptArtifactsRequest(); 166 | content.setGcsSource(gcsSource); 167 | 168 | ArtifactRegistry.Projects.Locations.Repositories.AptArtifacts.ArtifactRegistryImport request = 169 | artifactRegistryClient.projects().locations().repositories().aptArtifacts().artifactregistryImport( 170 | parent, 171 | content 172 | ); 173 | 174 | Operation operation = request.execute(); 175 | 176 | Stopwatch timer = Stopwatch.createStarted(); 177 | while (!operationIsDone(operation) && !operationTimedOut(timer)) { 178 | Thread.sleep(30000); 179 | operation = artifactRegistryClient.projects().locations().operations().get(operation.getName()).execute(); 180 | } 181 | 182 | return operation; 183 | } 184 | 185 | private BlobId uploadDebToGcs(Storage storage) throws IOException { 186 | BlobId blobId = BlobId.of(uploadBucket.get(), archiveFile.get().getAsFile().getName()); 187 | BlobInfo blobInfo = BlobInfo.newBuilder(blobId).build(); 188 | try (ByteChannel fileChannel = Files.newByteChannel(archiveFile.get().getAsFile().toPath()); 189 | WritableByteChannel gcsChannel = storage.writer(blobInfo)) { 190 | ByteStreams.copy(fileChannel, gcsChannel); 191 | } 192 | return blobId; 193 | } 194 | 195 | /** Checks for done, correctly handling a null result. */ 196 | private boolean operationIsDone(Operation operation) { 197 | return Boolean.TRUE.equals(operation.getDone()); 198 | } 199 | 200 | private boolean operationTimedOut(Stopwatch timer) { 201 | return timer.elapsed(TimeUnit.SECONDS) > aptImportTimeoutSeconds.get(); 202 | } 203 | 204 | private Object getErrors(Operation operation) { 205 | return operation.getResponse().get("errors"); 206 | } 207 | } 208 | -------------------------------------------------------------------------------- /spinnaker-project-plugin/src/main/groovy/com/netflix/spinnaker/gradle/publishing/artifactregistry/ArtifactRegistryPublishExtension.groovy: -------------------------------------------------------------------------------- 1 | package com.netflix.spinnaker.gradle.publishing.artifactregistry 2 | 3 | import org.gradle.api.Project 4 | import org.gradle.api.model.ObjectFactory 5 | import org.gradle.api.provider.Property 6 | import org.gradle.api.provider.Provider 7 | 8 | class ArtifactRegistryPublishExtension { 9 | 10 | protected final Project project 11 | 12 | final Property enabled 13 | 14 | final Property mavenEnabled 15 | final Property aptEnabled 16 | 17 | final Property mavenProject 18 | final Property mavenLocation 19 | final Property mavenRepository 20 | 21 | final Property aptProject 22 | final Property aptLocation 23 | final Property aptRepository 24 | 25 | final Property aptTempGcsBucket 26 | 27 | final Property aptImportTimeoutSeconds; 28 | 29 | ArtifactRegistryPublishExtension(Project project) { 30 | this.project = project 31 | ObjectFactory props = project.objects 32 | enabled = props.property(Boolean).convention(false) 33 | mavenEnabled = props.property(Boolean).convention(false) 34 | aptEnabled = props.property(Boolean).convention(true) 35 | mavenProject = props.property(String).convention("spinnaker-community") 36 | mavenLocation = props.property(String).convention("us") 37 | mavenRepository = props.property(String).convention("maven") 38 | aptProject = props.property(String).convention("spinnaker-community") 39 | aptLocation = props.property(String).convention("us") 40 | aptRepository = props.property(String).convention("apt") 41 | aptTempGcsBucket = props.property(String).convention("spinnaker-deb-temp-uploads") 42 | aptImportTimeoutSeconds = props.property(Integer).convention(3600) 43 | } 44 | 45 | Provider enabled() { 46 | return withSysProp(enabled, Boolean, "artifactRegistryPublishEnabled") 47 | } 48 | 49 | Provider mavenEnabled() { 50 | return withSysProp(mavenEnabled, Boolean, "artifactRegistryPublishMavenEnabled") 51 | } 52 | 53 | Provider aptEnabled() { 54 | return withSysProp(aptEnabled, Boolean, "artifactRegistryPublishAptEnabled") 55 | } 56 | 57 | Provider mavenProject() { 58 | return withSysProp(mavenProject, String, "artifactRegistryMavenProject") 59 | } 60 | 61 | Provider mavenLocation() { 62 | return withSysProp(mavenLocation, String, "artifactRegistryMavenLocation") 63 | } 64 | 65 | Provider mavenRepository() { 66 | return withSysProp(mavenRepository, String, "artifactRegistryMavenRepository") 67 | } 68 | 69 | Provider mavenUrl() { 70 | return mavenProject().flatMap { String project -> 71 | mavenLocation().flatMap { String location -> 72 | mavenRepository().map { String repository -> 73 | "artifactregistry://$location-maven.pkg.dev/$project/$repository".toString() 74 | } 75 | } 76 | } 77 | } 78 | 79 | Provider aptProject() { 80 | return withSysProp(aptProject, String, "artifactRegistryAptProject") 81 | } 82 | 83 | Provider aptLocation() { 84 | return withSysProp(aptLocation, String, "artifactRegistryAptLocation") 85 | } 86 | 87 | Provider aptRepository() { 88 | return withSysProp(aptRepository, String, "artifactRegistryAptRepository") 89 | } 90 | 91 | Provider aptTempGcsBucket() { 92 | return withSysProp(aptTempGcsBucket, String, "artifactRegistryAptTempGcsBucket") 93 | } 94 | 95 | Provider aptImportTimeoutSeconds() { 96 | return withSysProp(aptImportTimeoutSeconds, Integer, "artifactRegistryAptImportTimeoutSeconds") 97 | } 98 | 99 | //------------------------------------------------------------------------ 100 | // 101 | // Note the following utility methods are protected rather than private 102 | // because the Gradle lazy properties generate some dynamic subclass that 103 | // needs visibility into these methods. 104 | // 105 | //------------------------------------------------------------------------ 106 | protected Provider withSysProp(Property property, Class type, String projectPropertyName) { 107 | return property.map { T value -> 108 | return projectProperty(type, projectPropertyName, value) 109 | } 110 | } 111 | 112 | protected T projectProperty(Class type, String projectPropertyName, T defaultValue) { 113 | if (project.hasProperty(projectPropertyName)) { 114 | if (type == Boolean) { 115 | return Boolean.valueOf(project.property(projectPropertyName).toString()) as T 116 | } 117 | return project.property(projectPropertyName).asType(type) 118 | } 119 | return defaultValue 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /spinnaker-project-plugin/src/main/groovy/com/netflix/spinnaker/gradle/publishing/artifactregistry/ArtifactRegistryPublishPlugin.groovy: -------------------------------------------------------------------------------- 1 | package com.netflix.spinnaker.gradle.publishing.artifactregistry 2 | 3 | import com.google.cloud.artifactregistry.gradle.plugin.ArtifactRegistryGradlePlugin 4 | import com.netflix.gradle.plugins.deb.Deb 5 | import com.netflix.gradle.plugins.packaging.SystemPackagingPlugin 6 | import org.gradle.api.Plugin 7 | import org.gradle.api.Project 8 | import org.gradle.api.Task 9 | import org.gradle.api.plugins.JavaLibraryPlugin 10 | import org.gradle.api.plugins.JavaPlatformPlugin 11 | import org.gradle.api.publish.PublishingExtension 12 | import org.gradle.api.publish.maven.plugins.MavenPublishPlugin 13 | import org.gradle.api.tasks.TaskProvider 14 | 15 | class ArtifactRegistryPublishPlugin implements Plugin { 16 | @Override 17 | void apply(Project project) { 18 | 19 | def extension = project.extensions.create("artifactRegistrySpinnaker", ArtifactRegistryPublishExtension, project) 20 | 21 | if (!extension.enabled().get()) { 22 | return 23 | } 24 | 25 | if (extension.mavenEnabled().get()) { 26 | project.plugins.withType(JavaLibraryPlugin) { 27 | project.plugins.apply(MavenPublishPlugin) 28 | project.plugins.apply(ArtifactRegistryGradlePlugin) 29 | project.extensions.configure(PublishingExtension) { publishingExtension -> 30 | publishingExtension.repositories.maven { 31 | it.name = 'artifactRegistry' 32 | it.url = extension.mavenUrl().get() 33 | } 34 | } 35 | } 36 | 37 | project.plugins.withType(JavaPlatformPlugin) { 38 | project.plugins.apply(MavenPublishPlugin) 39 | project.plugins.apply(ArtifactRegistryGradlePlugin) 40 | project.extensions.configure(PublishingExtension) { publishingExtension -> 41 | publishingExtension.repositories.maven { 42 | it.name = 'artifactRegistry' 43 | it.url = extension.mavenUrl().get() 44 | } 45 | } 46 | } 47 | } 48 | 49 | if (extension.aptEnabled().get()) { 50 | project.plugins.withType(SystemPackagingPlugin) { 51 | TaskProvider debTask = project.tasks.named("buildDeb", Deb) 52 | TaskProvider publishDeb = project.tasks.register("publishDebToArtifactRegistry", ArtifactRegistryDebPublishTask) { 53 | it.archiveFile = debTask.flatMap { it.archiveFile } 54 | it.uploadBucket = extension.aptTempGcsBucket() 55 | it.repoProject = extension.aptProject() 56 | it.location = extension.aptLocation() 57 | it.repository = extension.aptRepository() 58 | it.aptImportTimeoutSeconds = extension.aptImportTimeoutSeconds() 59 | it.dependsOn(debTask) 60 | it.onlyIf { extension.enabled().get() } 61 | it.onlyIf { extension.aptEnabled().get() } 62 | it.onlyIf { project.version.toString() != Project.DEFAULT_VERSION } 63 | } 64 | 65 | project.tasks.matching { it.name == "publish" }.configureEach { 66 | it.dependsOn(publishDeb) 67 | } 68 | } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /spinnaker-project-plugin/src/main/groovy/com/netflix/spinnaker/gradle/publishing/nexus/NexusPublishExtension.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Armory, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package com.netflix.spinnaker.gradle.publishing.nexus 19 | 20 | import org.gradle.api.Project 21 | import org.gradle.api.model.ObjectFactory 22 | import org.gradle.api.provider.Property 23 | import org.gradle.api.provider.Provider; 24 | 25 | class NexusPublishExtension { 26 | protected Project project 27 | 28 | final Property enabled 29 | final Property nexusStagingUrl 30 | final Property nexusSnapshotUrl 31 | final Property nexusStagingProfileId 32 | 33 | NexusPublishExtension(Project project) { 34 | this.project = project 35 | ObjectFactory props = project.objects 36 | enabled = props.property(Boolean).convention(false) 37 | nexusStagingUrl = props.property(String).convention("https://s01.oss.sonatype.org/service/local/") 38 | nexusSnapshotUrl = props.property(String).convention("https://s01.oss.sonatype.org/content/repositories/snapshots/") 39 | nexusStagingProfileId = props.property(String).convention("b6b58aed9c738") 40 | } 41 | 42 | Provider enabled() { 43 | return withSysProp(enabled, Boolean, "nexusPublishEnabled") 44 | } 45 | 46 | Provider nexusStagingUrl() { 47 | return withSysProp(nexusStagingUrl, String, "nexusStagingUrl") 48 | } 49 | 50 | Provider nexusSnapshotUrl() { 51 | return withSysProp(nexusSnapshotUrl, String, "nexusSnapshotUrl") 52 | } 53 | 54 | Provider nexusStagingProfileId() { 55 | return withSysProp(nexusStagingProfileId, String, "nexusStagingProfileId") 56 | } 57 | 58 | String pgpSigningKey() { 59 | return projectProperty(String, "nexusPgpSigningKey") 60 | } 61 | 62 | String pgpSigningPassword() { 63 | return projectProperty(String, "nexusPgpSigningPassword") 64 | } 65 | 66 | String nexusUsername() { 67 | return projectProperty(String, "nexusUsername") 68 | } 69 | 70 | String nexusPassword() { 71 | return projectProperty(String, "nexusPassword") 72 | } 73 | 74 | protected Provider withSysProp(Property property, Class type, String projectPropertyName) { 75 | return property.map { T value -> 76 | return projectProperty(type, projectPropertyName, value) 77 | } 78 | } 79 | 80 | protected T projectProperty(Class type, String projectPropertyName) { 81 | return projectProperty(type, projectPropertyName, null) 82 | } 83 | 84 | protected T projectProperty(Class type, String projectPropertyName, T defaultValue) { 85 | if (project.hasProperty(projectPropertyName)) { 86 | if (type == Boolean) { 87 | return Boolean.valueOf(project.property(projectPropertyName).toString()) as T 88 | } 89 | return project.property(projectPropertyName).asType(type) 90 | } 91 | return defaultValue 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /spinnaker-project-plugin/src/main/groovy/com/netflix/spinnaker/gradle/publishing/nexus/NexusPublishPlugin.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 Armory, Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | * 16 | */ 17 | 18 | package com.netflix.spinnaker.gradle.publishing.nexus 19 | 20 | import com.netflix.spinnaker.gradle.publishing.PublishingPlugin 21 | import io.github.gradlenexus.publishplugin.NexusPublishPlugin as BaseNexusPublishPlugin 22 | import io.github.gradlenexus.publishplugin.NexusPublishExtension as BaseNexusPublishExtension 23 | import org.gradle.api.Plugin 24 | import org.gradle.api.Project 25 | import org.gradle.api.plugins.JavaLibraryPlugin 26 | import org.gradle.api.plugins.JavaPlatformPlugin 27 | import org.gradle.api.plugins.JavaPlugin 28 | import org.gradle.api.publish.Publication 29 | import org.gradle.api.publish.PublishingExtension 30 | import org.gradle.api.publish.maven.plugins.MavenPublishPlugin 31 | import org.gradle.jvm.tasks.Jar 32 | import org.gradle.plugins.signing.SigningExtension 33 | import org.gradle.plugins.signing.SigningPlugin 34 | import org.jetbrains.dokka.gradle.DokkaPlugin 35 | 36 | class NexusPublishPlugin implements Plugin { 37 | 38 | @Override 39 | void apply(Project project) { 40 | def nexusExtension = project.extensions.create("nexusSpinnaker", NexusPublishExtension, project) 41 | if (!nexusExtension.enabled().get()) { 42 | return 43 | } 44 | 45 | if (project == project.rootProject) { 46 | configureBaseNexusPlugin(project, nexusExtension) 47 | } 48 | 49 | project.plugins.withType(JavaLibraryPlugin) { 50 | project.plugins.apply(MavenPublishPlugin) 51 | project.plugins.apply(SigningPlugin) 52 | 53 | configureSpinnakerPublicationForNexus(project, nexusExtension) 54 | } 55 | project.plugins.withType(JavaPlatformPlugin) { 56 | project.plugins.apply(MavenPublishPlugin) 57 | project.plugins.apply(SigningPlugin) 58 | 59 | configureSpinnakerPublicationForNexus(project, nexusExtension) 60 | } 61 | } 62 | 63 | private void configureBaseNexusPlugin(Project project, NexusPublishExtension nexusExtension) { 64 | project.plugins.apply(BaseNexusPublishPlugin) 65 | project.extensions.configure(BaseNexusPublishExtension) { nexus -> 66 | nexus.repositories.create("nexus") { 67 | nexusUrl = new URI(nexusExtension.nexusStagingUrl().get()) 68 | snapshotRepositoryUrl = new URI(nexusExtension.nexusSnapshotUrl().get()) 69 | username = nexusExtension.nexusUsername() 70 | password = nexusExtension.nexusPassword() 71 | stagingProfileId = nexusExtension.nexusStagingProfileId() 72 | } 73 | } 74 | } 75 | 76 | private void configureSpinnakerPublicationForNexus(Project project, NexusPublishExtension nexusExtension) { 77 | project.extensions.configure(PublishingExtension) { publishingExtension -> 78 | def spinnakerPublication = publishingExtension.publications.getByName(PublishingPlugin.PUBLICATION_NAME) 79 | 80 | configurePom(project, spinnakerPublication) 81 | 82 | project.extensions.configure(SigningExtension) { signingExtension -> 83 | signingExtension.useInMemoryPgpKeys(nexusExtension.pgpSigningKey(), nexusExtension.pgpSigningPassword()) 84 | signingExtension.sign(spinnakerPublication) 85 | } 86 | 87 | if (project.plugins.hasPlugin(DokkaPlugin)) { 88 | def javadocTask = project.tasks.findByName("dokkaJavadoc") 89 | def dokkaJar = project.task(type: Jar, "dokkaJar") { 90 | archiveClassifier.set("javadoc") 91 | from(javadocTask) 92 | } 93 | spinnakerPublication.artifact(dokkaJar) 94 | } else if (project.plugins.hasPlugin(JavaPlugin)) { 95 | def javadocTask = project.tasks.findByName(JavaPlugin.JAVADOC_TASK_NAME) 96 | def javadocJar = project.task(type: Jar, "javadocJar") { 97 | archiveClassifier.set("javadoc") 98 | from(javadocTask) 99 | } 100 | spinnakerPublication.artifact(javadocJar) 101 | } 102 | } 103 | } 104 | 105 | void configurePom(Project project, Publication publication) { 106 | def service = project.rootProject.name 107 | publication.pom { 108 | name = service 109 | description = "Spinnaker ${service.capitalize()}" 110 | url = "https://github.com/spinnaker/$service" 111 | licenses { 112 | license { 113 | name = "The Apache License, Version 2.0" 114 | url = "http://www.apache.org/licenses/LICENSE-2.0.txt" 115 | } 116 | } 117 | developers { 118 | developer { 119 | id = "toc" 120 | name = "Technical Oversight Committee" 121 | email = "toc@spinnaker.io" 122 | } 123 | } 124 | inceptionYear = "2014" 125 | scm { 126 | connection = "scm:git:git://github.com/spinnaker/${service}.git" 127 | developerConnection = "scm:git:ssh://github.com/spinnaker/${service}.git" 128 | url = "http://github.com/spinnaker/${service}/" 129 | } 130 | issueManagement { 131 | system = "GitHub Issues" 132 | url = "https://github.com/spinnaker/spinnaker/issues" 133 | } 134 | } 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /spinnaker-project-plugin/src/main/groovy/com/netflix/spinnaker/gradle/publishing/nexus/README.md: -------------------------------------------------------------------------------- 1 | # Publishing to Maven Central 2 | 3 | As of 3/2021, Spinnaker projects push jars to [Maven 4 | Central](https://repo.maven.apache.org/maven2/io/spinnaker/). 5 | 6 | Sonatype hosts instances of Nexus for OSS projects. Releases published through 7 | Nexus are synced with Maven Central. The sync takes about 10 minutes to 8 | complete. 9 | 10 | The jars are signed with a PGP key owned by `toc@spinnaker.io` with the 11 | fingerprint `9C88 1F6B 9595 3116 4FE3 CCD2 6A3E 0DDE A960 2C12`. 12 | 13 | Our Nexus instance is hosted at [s01.oss.sonatype.org](https://s01.oss.sonatype.org). 14 | 15 | ## Secrets 16 | 17 | There are four org-level GitHub secrets available to publish to Nexus: 18 | `NEXUS_USERNAME`, `NEXUS_PASSWORD`, `NEXUS_PGP_SIGNING_KEY`, and 19 | `NEXUS_PGP_SIGNING_PASSWORD`. 20 | 21 | If you need to sign in to the Nexus UI, ask the Spinnaker TOC for the root 22 | username and password credentials. 23 | 24 | ## Publishing with Gradle 25 | 26 | You can publish to Nexus without releasing to Maven Central: 27 | 28 | ```shell 29 | ./gradlew -P nexusPublishEnabled=true publishToNexus 30 | ``` 31 | 32 | This stages a release in an `Open` state. The Nexus UI provides a staging URL 33 | for staged artifacts if you need to test them out. 34 | 35 | From the UI, you can either `Close` a release, which will start the sync 36 | process, or `Drop` it, which will delete it. 37 | 38 | You can also publish and close a release programmatically: 39 | 40 | ```shell 41 | ./gradlew -P nexusPublishEnabled=true publishToNexus closeAndReleaseNexusStagingRepository 42 | ``` 43 | -------------------------------------------------------------------------------- /spinnaker-project-plugin/src/main/resources/license-normalizer-bundle.json: -------------------------------------------------------------------------------- 1 | { 2 | "bundles" : [ 3 | { "bundleName" : "apache1", "licenseName" : "Apache Software License, Version 1.1", "licenseUrl" : "http://www.apache.org/licenses/LICENSE-1.1" }, 4 | { "bundleName" : "apache2", "licenseName" : "Apache License, Version 2.0", "licenseUrl" : "http://www.apache.org/licenses/LICENSE-2.0" }, 5 | { "bundleName" : "cddl1", "licenseName" : "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE Version 1.0 (CDDL-1.0)", "licenseUrl" : "http://opensource.org/licenses/CDDL-1.0" }, 6 | { "bundleName" : "cddl+gpl", "licenseName" : "CDDL+GPL License", "licenseUrl" : "http://glassfish.java.net/public/CDDL+GPL_1_1.html" }, 7 | { "bundleName" : "mit", "licenseName" : "The MIT License (MIT)", "licenseUrl" : "http://opensource.org/licenses/MIT" }, 8 | { "bundleName" : "epl", "licenseName" : "Eclipse Public License - Version 1.0", "licenseUrl" : "http://www.eclipse.org/org/documents/epl-v10.php" }, 9 | { "bundleName" : "lgpl2.1", "licenseName" : "LGPL 2.1", "licenseUrl" : "http://www.gnu.org/licenses/lgpl-2.1.html" }, 10 | { "bundleName" : "bsd", "licenseName" : "BSD", "licenseUrl" : "http://www.opensource.org/licenses/bsd-license.php" } 11 | ], 12 | "transformationRules" : [ 13 | { "bundleName" : "apache2", "licenseNamePattern" : ".*The Apache Software License, Version 2.0.*" }, 14 | { "bundleName" : "apache2", "licenseNamePattern" : "Apache 2" }, 15 | { "bundleName" : "apache2", "licenseNamePattern" : "Apache License 2.0" }, 16 | { "bundleName" : "apache2", "licenseUrlPattern" : "http://www.apache.org/licenses/LICENSE-2.0" }, 17 | { "bundleName" : "apache2", "licenseNamePattern" : "Special Apache", "transformUrl" : false }, 18 | { "bundleName" : "apache2", "licenseNamePattern" : "Keep this name", "transformName" : false }, 19 | { "bundleName" : "mit", "licenseNamePattern" : ".*MIT.*", "transformUrl" : false }, 20 | { "bundleName" : "cddl+gpl", "licenseUrlPattern" : ".*CDDL+GPL.*", "transformUrl" : false }, 21 | { "bundleName" : "cddl+gpl", "licenseNamePattern" : "CDDL 1.1", "transformUrl" : false }, 22 | { "bundleName" : "cddl+gpl", "licenseNamePattern" : "GPLv2+CE", "transformUrl" : false }, 23 | { "bundleName" : "epl", "licenseUrlPattern" : "http://www.eclipse.org/org/documents/epl-v10.*"}, 24 | { "bundleName" : "epl", "licenseNamePattern" : ".*Eclipse Public License.*"}, 25 | { "bundleName" : "lgpl2.1", "licenseNamePattern" : "LGPL.*2.1", "transformUrl" : false}, 26 | { "bundleName" : "bsd", "licenseUrlPattern" : "http://www.opensource.org/licenses/bsd-license.*", "transformUrl" : false}, 27 | { "bundleName" : "bsd", "licenseNamePattern" : ".*BSD.*", "transformUrl" : false} 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /spinnaker-project-plugin/src/main/resources/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | if [[ "$SPOTLESS_SKIP" -eq "1" ]]; then 4 | echo '[git hook] $SPOTLESS_SKIP set: Skipping spotlessApply' 5 | exit 0 6 | fi 7 | 8 | echo '[git hook] executing gradle spotlessCheck before commit' 9 | 10 | STAGED=$(git diff --name-only --staged) 11 | ./gradlew spotlessApply 12 | echo ${STAGED} | xargs git add 13 | 14 | exit 0 15 | --------------------------------------------------------------------------------