├── .dockerignore ├── .drone.yml ├── .github ├── issue_template.md ├── pull_request_template.md └── settings.yml ├── .gitignore ├── LICENSE ├── README.md ├── docker ├── Dockerfile.linux.amd64 ├── Dockerfile.linux.arm64 ├── Dockerfile.windows.1809 ├── Dockerfile.windows.ltsc2022 └── manifest.tmpl ├── go.mod ├── go.sum ├── main.go └── plugin ├── impl.go ├── impl_test.go ├── plugin.go ├── plugin_test.go └── test_params.env /.dockerignore: -------------------------------------------------------------------------------- 1 | * 2 | !release/ 3 | -------------------------------------------------------------------------------- /.drone.yml: -------------------------------------------------------------------------------- 1 | kind: pipeline 2 | type: vm 3 | name: testing 4 | platform: 5 | os: linux 6 | arch: amd64 7 | pool: 8 | use: ubuntu 9 | 10 | steps: 11 | - name: lint 12 | image: golang:1.20 13 | commands: 14 | - go install github.com/golangci/golangci-lint/cmd/golangci-lint@latest 15 | - golangci-lint version 16 | - golangci-lint run 17 | volumes: 18 | - name: gopath 19 | path: "/go" 20 | - name: test 21 | image: golang:1.20 22 | commands: 23 | - go test -cover ./... 24 | volumes: 25 | - name: gopath 26 | path: "/go" 27 | volumes: 28 | - name: gopath 29 | temp: {} 30 | trigger: 31 | ref: 32 | - refs/heads/master 33 | - refs/tags/** 34 | - refs/pull/** 35 | 36 | --- 37 | kind: pipeline 38 | type: vm 39 | name: linux-amd64 40 | platform: 41 | os: linux 42 | arch: amd64 43 | pool: 44 | use: ubuntu 45 | 46 | steps: 47 | - name: environment 48 | image: golang:1.20 49 | environment: 50 | CGO_ENABLED: "0" 51 | commands: 52 | - go version 53 | - go env 54 | - name: build 55 | image: golang:1.20 56 | environment: 57 | CGO_ENABLED: "0" 58 | commands: 59 | - go build -v -ldflags "-X main.version=" -a -tags netgo -o release/linux/amd64/drone-downstream . 60 | - name: docker 61 | image: plugins/docker 62 | settings: 63 | dockerfile: docker/Dockerfile.linux.amd64 64 | repo: plugins/downstream 65 | username: 66 | from_secret: docker_username 67 | password: 68 | from_secret: docker_password 69 | auto_tag: true 70 | auto_tag_suffix: linux-amd64 71 | depends_on: 72 | - testing 73 | trigger: 74 | ref: 75 | - refs/heads/master 76 | - refs/tags/** 77 | - refs/pull/** 78 | 79 | --- 80 | kind: pipeline 81 | type: vm 82 | name: linux-arm64 83 | platform: 84 | os: linux 85 | arch: arm64 86 | pool: 87 | use: ubuntu_arm64 88 | 89 | steps: 90 | - name: environment 91 | image: golang:1.20 92 | environment: 93 | CGO_ENABLED: "0" 94 | commands: 95 | - go version 96 | - go env 97 | - name: build 98 | image: golang:1.20 99 | environment: 100 | CGO_ENABLED: "0" 101 | commands: 102 | - go build -v -ldflags "-X main.version=" -a -tags netgo -o release/linux/arm64/drone-downstream . 103 | - name: docker 104 | image: plugins/docker 105 | settings: 106 | dockerfile: docker/Dockerfile.linux.arm64 107 | repo: plugins/downstream 108 | username: 109 | from_secret: docker_username 110 | password: 111 | from_secret: docker_password 112 | auto_tag: true 113 | auto_tag_suffix: linux-arm64 114 | depends_on: 115 | - testing 116 | trigger: 117 | ref: 118 | - refs/heads/master 119 | - refs/tags/** 120 | - refs/pull/** 121 | 122 | --- 123 | kind: pipeline 124 | type: vm 125 | name: windows-1809 126 | platform: 127 | os: windows 128 | arch: amd64 129 | pool: 130 | use: windows 131 | 132 | steps: 133 | - name: environment 134 | image: golang:1.20 135 | environment: 136 | CGO_ENABLED: "0" 137 | commands: 138 | - go version 139 | - go env 140 | - name: build 141 | image: golang:1.20 142 | environment: 143 | CGO_ENABLED: "0" 144 | commands: 145 | - go build -v -ldflags "-X main.version=" -a -tags netgo -o release/windows/amd64/drone-downstream.exe . 146 | - name: docker 147 | image: plugins/docker 148 | settings: 149 | dockerfile: docker/Dockerfile.windows.1809 150 | repo: plugins/downstream 151 | username: 152 | from_secret: docker_username 153 | password: 154 | from_secret: docker_password 155 | auto_tag: true 156 | auto_tag_suffix: windows-1809-amd64 157 | daemon_off: true 158 | purge: false 159 | when: 160 | ref: 161 | - refs/heads/master 162 | - refs/tags/** 163 | depends_on: 164 | - testing 165 | trigger: 166 | ref: 167 | - refs/heads/master 168 | - refs/tags/** 169 | - refs/pull/** 170 | 171 | --- 172 | kind: pipeline 173 | type: vm 174 | name: windows-ltsc2022 175 | platform: 176 | os: windows 177 | arch: amd64 178 | pool: 179 | use: windows-2022 180 | 181 | steps: 182 | - name: environment 183 | image: golang:1.20 184 | environment: 185 | CGO_ENABLED: "0" 186 | commands: 187 | - go version 188 | - go env 189 | - name: build 190 | image: golang:1.20 191 | environment: 192 | CGO_ENABLED: "0" 193 | commands: 194 | - go build -v -ldflags "-X main.version=" -a -tags netgo -o release/windows/amd64/drone-downstream.exe . 195 | - name: docker 196 | image: plugins/docker 197 | settings: 198 | dockerfile: docker/Dockerfile.windows.ltsc2022 199 | repo: plugins/downstream 200 | username: 201 | from_secret: docker_username 202 | password: 203 | from_secret: docker_password 204 | auto_tag: true 205 | auto_tag_suffix: windows-ltsc2022-amd64 206 | daemon_off: true 207 | purge: false 208 | when: 209 | ref: 210 | - refs/heads/master 211 | - refs/tags/** 212 | depends_on: 213 | - testing 214 | trigger: 215 | ref: 216 | - refs/heads/master 217 | - refs/tags/** 218 | - refs/pull/** 219 | 220 | --- 221 | kind: pipeline 222 | type: vm 223 | name: manifest 224 | platform: 225 | os: linux 226 | arch: amd64 227 | pool: 228 | use: ubuntu 229 | 230 | steps: 231 | - name: manifest 232 | image: plugins/manifest 233 | settings: 234 | auto_tag: "true" 235 | username: 236 | from_secret: docker_username 237 | password: 238 | from_secret: docker_password 239 | spec: docker/manifest.tmpl 240 | ignore_missing: true 241 | depends_on: 242 | - linux-amd64 243 | - linux-arm64 244 | - windows-1809 245 | - windows-ltsc2022 246 | trigger: 247 | ref: 248 | - refs/heads/master 249 | - refs/tags/** 250 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drone-plugins/drone-downstream/b7c1356ce0218ca6d74845f58265bc2d26bd27e1/.github/pull_request_template.md -------------------------------------------------------------------------------- /.github/settings.yml: -------------------------------------------------------------------------------- 1 | repository: 2 | name: drone-downstream 3 | description: Drone plugin to trigger downstream repositories to build 4 | homepage: http://plugins.drone.io/drone-plugins/drone-downstream 5 | topics: drone, drone-plugin 6 | 7 | private: false 8 | has_issues: true 9 | has_wiki: false 10 | has_downloads: false 11 | 12 | default_branch: master 13 | 14 | allow_squash_merge: true 15 | allow_merge_commit: true 16 | allow_rebase_merge: true 17 | 18 | labels: 19 | - name: bug 20 | color: d73a4a 21 | description: Something isn't working 22 | - name: duplicate 23 | color: cfd3d7 24 | description: This issue or pull request already exists 25 | - name: enhancement 26 | color: a2eeef 27 | description: New feature or request 28 | - name: good first issue 29 | color: 7057ff 30 | description: Good for newcomers 31 | - name: help wanted 32 | color: 008672 33 | description: Extra attention is needed 34 | - name: invalid 35 | color: e4e669 36 | description: This doesn't seem right 37 | - name: question 38 | color: d876e3 39 | description: Further information is requested 40 | - name: renovate 41 | color: e99695 42 | description: Automated action from Renovate 43 | - name: wontfix 44 | color: ffffff 45 | description: This will not be worked on 46 | 47 | teams: 48 | - name: Admins 49 | permission: admin 50 | - name: Captain 51 | permission: admin 52 | - name: Maintainers 53 | permission: push 54 | 55 | branches: 56 | - name: master 57 | protection: 58 | required_pull_request_reviews: 59 | required_approving_review_count: 1 60 | dismiss_stale_reviews: false 61 | require_code_owner_reviews: false 62 | dismissal_restrictions: 63 | teams: 64 | - Admins 65 | - Captain 66 | required_status_checks: 67 | strict: true 68 | contexts: 69 | - continuous-integration/drone/pr 70 | enforce_admins: false 71 | restrictions: 72 | users: [] 73 | teams: [] 74 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /release/ 2 | /drone-downstream* 3 | 4 | coverage.out 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # drone-downstream 2 | 3 | [![Build Status](http://cloud.drone.io/api/badges/drone-plugins/drone-downstream/status.svg)](http://cloud.drone.io/drone-plugins/drone-downstream) 4 | [![Gitter chat](https://badges.gitter.im/drone/drone.png)](https://gitter.im/drone/drone) 5 | [![Join the discussion at https://community.harness.io/](https://img.shields.io/badge/discourse-forum-orange.svg)](https://community.harness.io/) 6 | [![Drone questions at https://stackoverflow.com](https://img.shields.io/badge/drone-stackoverflow-orange.svg)](https://stackoverflow.com/questions/tagged/drone.io) 7 | [![](https://images.microbadger.com/badges/image/plugins/downstream.svg)](https://microbadger.com/images/plugins/downstream "Get your own image badge on microbadger.com") 8 | [![Go Doc](https://godoc.org/github.com/drone-plugins/drone-downstream?status.svg)](http://godoc.org/github.com/drone-plugins/drone-downstream) 9 | [![Go Report](https://goreportcard.com/badge/github.com/drone-plugins/drone-downstream)](https://goreportcard.com/report/github.com/drone-plugins/drone-downstream) 10 | 11 | Drone plugin to trigger downstream repository builds. For the usage information and a listing of the available options please take a look at [the docs](http://plugins.drone.io/drone-plugins/drone-downstream/). 12 | 13 | ## Build 14 | 15 | Build the binary with the following command: 16 | 17 | ```console 18 | export GOOS=linux 19 | export GOARCH=amd64 20 | export CGO_ENABLED=0 21 | export GO111MODULE=on 22 | 23 | go build -v -a -tags netgo -o release/linux/amd64/drone-downstream 24 | ``` 25 | 26 | ## Docker 27 | 28 | Build the Docker image with the following command: 29 | 30 | ```console 31 | docker build \ 32 | --label org.label-schema.build-date=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \ 33 | --label org.label-schema.vcs-ref=$(git rev-parse --short HEAD) \ 34 | --file docker/Dockerfile.linux.amd64 --tag plugins/downstream . 35 | ``` 36 | 37 | ## Usage 38 | 39 | ```console 40 | docker run --rm \ 41 | -e PLUGIN_REPOSITORIES=octocat/Hello-World \ 42 | -e PLUGIN_TOKEN=eyJhbFciHiJISzI1EiIsUnR5cCW6IkpXQCJ9.ezH0ZXh0LjoidGJvZXJnZXIiLCJ0eXBlIjoidXNlciJ9.1m_3QFA6eA7h4wrBby2aIRFAEhQWPrlj4dsO_Gfchtc \ 43 | -v $(pwd):$(pwd) \ 44 | -w $(pwd) \ 45 | plugins/downstream 46 | ``` 47 | -------------------------------------------------------------------------------- /docker/Dockerfile.linux.amd64: -------------------------------------------------------------------------------- 1 | FROM plugins/base:multiarch 2 | 3 | LABEL maintainer="Drone.IO Community " \ 4 | org.label-schema.name="Drone Downstream" \ 5 | org.label-schema.vendor="Drone.IO Community" \ 6 | org.label-schema.schema-version="1.0" 7 | 8 | ADD release/linux/amd64/drone-downstream /bin/ 9 | ENTRYPOINT [ "/bin/drone-downstream" ] 10 | -------------------------------------------------------------------------------- /docker/Dockerfile.linux.arm64: -------------------------------------------------------------------------------- 1 | FROM plugins/base:multiarch 2 | 3 | LABEL maintainer="Drone.IO Community " \ 4 | org.label-schema.name="Drone Downstream" \ 5 | org.label-schema.vendor="Drone.IO Community" \ 6 | org.label-schema.schema-version="1.0" 7 | 8 | ADD release/linux/arm64/drone-downstream /bin/ 9 | ENTRYPOINT [ "/bin/drone-downstream" ] 10 | -------------------------------------------------------------------------------- /docker/Dockerfile.windows.1809: -------------------------------------------------------------------------------- 1 | # escape=` 2 | FROM plugins/base:windows-1809-amd64 3 | 4 | LABEL maintainer="Drone.IO Community " ` 5 | org.label-schema.name="Drone Downstream" ` 6 | org.label-schema.vendor="Drone.IO Community" ` 7 | org.label-schema.schema-version="1.0" 8 | 9 | ADD release/windows/amd64/drone-downstream.exe C:/bin/drone-downstream.exe 10 | ENTRYPOINT [ "C:\\bin\\drone-downstream.exe" ] 11 | -------------------------------------------------------------------------------- /docker/Dockerfile.windows.ltsc2022: -------------------------------------------------------------------------------- 1 | # escape=` 2 | FROM plugins/base:windows-ltsc2022-amd64@sha256:0f90d5bceb432f1ee6f93cf44eed6a38c322834edd55df8a6648c9e6f15131f4 3 | 4 | LABEL maintainer="Drone.IO Community " ` 5 | org.label-schema.name="Drone Downstream" ` 6 | org.label-schema.vendor="Drone.IO Community" ` 7 | org.label-schema.schema-version="1.0" 8 | 9 | ADD release/windows/amd64/drone-downstream.exe C:/bin/drone-downstream.exe 10 | ENTRYPOINT [ "C:\\bin\\drone-downstream.exe" ] 11 | -------------------------------------------------------------------------------- /docker/manifest.tmpl: -------------------------------------------------------------------------------- 1 | image: plugins/downstream:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}} 2 | 3 | {{#if build.tags}} 4 | tags: 5 | {{#each build.tags}} 6 | - {{this}} 7 | {{/each}} 8 | {{/if}} 9 | 10 | manifests: 11 | - image: plugins/downstream:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-amd64 12 | platform: 13 | architecture: amd64 14 | os: linux 15 | - image: plugins/downstream:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm64 16 | platform: 17 | architecture: arm64 18 | os: linux 19 | variant: v8 20 | - image: plugins/downstream:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}windows-1809-amd64 21 | platform: 22 | architecture: amd64 23 | os: windows 24 | version: 1809 25 | - image: plugins/downstream:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}windows-1809-ltsc2022-amd64 26 | platform: 27 | architecture: amd64 28 | os: windows 29 | version: ltsc2022 30 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/drone-plugins/drone-downstream 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/drone-plugins/drone-plugin-lib v0.4.1 7 | github.com/drone/drone-go v1.7.1 8 | github.com/joho/godotenv v1.5.1 9 | github.com/urfave/cli/v2 v2.25.7 10 | golang.org/x/oauth2 v0.12.0 11 | ) 12 | 13 | require ( 14 | github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect 15 | github.com/golang/protobuf v1.5.3 // indirect 16 | github.com/russross/blackfriday/v2 v2.1.0 // indirect 17 | github.com/sirupsen/logrus v1.9.3 // indirect 18 | github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect 19 | golang.org/x/sys v0.12.0 // indirect 20 | google.golang.org/appengine v1.6.8 // indirect 21 | google.golang.org/protobuf v1.31.0 // indirect 22 | ) 23 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/99designs/httpsignatures-go v0.0.0-20170731043157-88528bf4ca7e/go.mod h1:Xa6lInWHNQnuWoF0YPSsx+INFA9qk7/7pTjwb3PInkY= 2 | github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= 3 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 4 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 5 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 6 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 7 | github.com/drone-plugins/drone-plugin-lib v0.4.1 h1:47rZlmcMpr1hSp+6Gl+1Z4t+efi/gMQU3lxukC1Yg64= 8 | github.com/drone-plugins/drone-plugin-lib v0.4.1/go.mod h1:KwCu92jFjHV3xv2hu5Qg/8zBNvGwbhoJDQw/EwnTvoM= 9 | github.com/drone/drone-go v1.7.1 h1:ZX+3Rs8YHUSUQ5mkuMLmm1zr1ttiiE2YGNxF3AnyDKw= 10 | github.com/drone/drone-go v1.7.1/go.mod h1:fxCf9jAnXDZV1yDr0ckTuWd1intvcQwfJmTRpTZ1mXg= 11 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= 12 | github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 13 | github.com/golang/protobuf v1.5.3 h1:KhyjKVUg7Usr/dYsdSqoFveMYd5ko72D+zANwlG1mmg= 14 | github.com/golang/protobuf v1.5.3/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= 15 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 16 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= 17 | github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 18 | github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= 19 | github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 20 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 21 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 22 | github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= 23 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 24 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 25 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 26 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 27 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 28 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 29 | github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs= 30 | github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= 31 | github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= 32 | github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= 33 | github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= 34 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= 35 | golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= 36 | golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= 37 | golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= 38 | golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= 39 | golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= 40 | golang.org/x/oauth2 v0.12.0 h1:smVPGxink+n1ZI5pkQa8y6fZT0RW0MgCO5bFpepy4B4= 41 | golang.org/x/oauth2 v0.12.0/go.mod h1:A74bZ3aGXgCY0qaIC9Ahg6Lglin4AMAco8cIv9baba4= 42 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 43 | golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= 44 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 45 | golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= 46 | golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 47 | golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 48 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 49 | golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 50 | golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= 51 | golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 52 | golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= 53 | golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= 54 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 55 | golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= 56 | golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= 57 | golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= 58 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 59 | golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= 60 | golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= 61 | golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 62 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 63 | google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM= 64 | google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds= 65 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= 66 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 67 | google.golang.org/protobuf v1.31.0 h1:g0LDEJHgrBl9N9r17Ru3sqWhkIx2NB67okBHPwC7hs8= 68 | google.golang.org/protobuf v1.31.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= 69 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 70 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 71 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 72 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, the Drone Plugins project authors. 2 | // Please see the AUTHORS file for details. All rights reserved. 3 | // Use of this source code is governed by an Apache 2.0 license that can be 4 | // found in the LICENSE file. 5 | 6 | // DO NOT MODIFY THIS FILE DIRECTLY 7 | 8 | package main 9 | 10 | import ( 11 | "os" 12 | "time" 13 | 14 | "github.com/drone-plugins/drone-downstream/plugin" 15 | "github.com/drone-plugins/drone-plugin-lib/errors" 16 | "github.com/drone-plugins/drone-plugin-lib/urfave" 17 | "github.com/joho/godotenv" 18 | "github.com/urfave/cli/v2" 19 | ) 20 | 21 | var version = "unknown" 22 | 23 | func main() { 24 | settings := &plugin.Settings{} 25 | 26 | if _, err := os.Stat("/run/drone/env"); err == nil { 27 | _ = godotenv.Overload("/run/drone/env") 28 | } 29 | 30 | app := &cli.App{ 31 | Name: "drone-downstream", 32 | Usage: "trigger a downstream drone build", 33 | Version: version, 34 | Flags: append(settingsFlags(settings), urfave.Flags()...), 35 | Action: run(settings), 36 | } 37 | 38 | if err := app.Run(os.Args); err != nil { 39 | errors.HandleExit(err) 40 | } 41 | } 42 | 43 | func run(settings *plugin.Settings) cli.ActionFunc { 44 | return func(ctx *cli.Context) error { 45 | urfave.LoggingFromContext(ctx) 46 | 47 | plugin := plugin.New( 48 | *settings, 49 | urfave.PipelineFromContext(ctx), 50 | urfave.NetworkFromContext(ctx), 51 | ) 52 | 53 | if err := plugin.Validate(); err != nil { 54 | if e, ok := err.(errors.ExitCoder); ok { 55 | return e 56 | } 57 | 58 | return errors.ExitMessagef("validation failed: %w", err) 59 | } 60 | 61 | if err := plugin.Execute(); err != nil { 62 | if e, ok := err.(errors.ExitCoder); ok { 63 | return e 64 | } 65 | 66 | return errors.ExitMessagef("execution failed: %w", err) 67 | } 68 | 69 | return nil 70 | } 71 | } 72 | 73 | // settingsFlags has the cli.Flags for the plugin.Settings. 74 | func settingsFlags(settings *plugin.Settings) []cli.Flag { 75 | return []cli.Flag{ 76 | &cli.StringSliceFlag{ 77 | Name: "repositories", 78 | Usage: "List of repositories to trigger", 79 | EnvVars: []string{"PLUGIN_REPOSITORIES"}, 80 | Destination: &settings.Repos, 81 | }, 82 | &cli.StringFlag{ 83 | Name: "server", 84 | Usage: "Trigger a drone build on a custom server", 85 | EnvVars: []string{"PLUGIN_SERVER", "DOWNSTREAM_SERVER"}, 86 | Destination: &settings.Server, 87 | }, 88 | &cli.StringFlag{ 89 | Name: "token", 90 | Usage: "Drone API token from your user settings", 91 | EnvVars: []string{"PLUGIN_TOKEN", "DRONE_TOKEN", "DOWNSTREAM_TOKEN"}, 92 | Destination: &settings.Token, 93 | }, 94 | &cli.BoolFlag{ 95 | Name: "wait", 96 | Usage: "Wait for any currently running builds to finish", 97 | EnvVars: []string{"PLUGIN_WAIT"}, 98 | Destination: &settings.Wait, 99 | }, 100 | &cli.DurationFlag{ 101 | Name: "timeout", 102 | Value: time.Duration(60) * time.Second, 103 | Usage: "How long to wait on any currently running builds", 104 | EnvVars: []string{"PLUGIN_WAIT_TIMEOUT"}, 105 | Destination: &settings.Timeout, 106 | }, 107 | &cli.BoolFlag{ 108 | Name: "last-successful", 109 | Usage: "Trigger last successful build", 110 | EnvVars: []string{"PLUGIN_LAST_SUCCESSFUL"}, 111 | Destination: &settings.LastSuccessful, 112 | }, 113 | &cli.StringSliceFlag{ 114 | Name: "params", 115 | Usage: "List of params (key=value or file paths of params) to pass to triggered builds", 116 | EnvVars: []string{"PLUGIN_PARAMS"}, 117 | Destination: &settings.Params, 118 | }, 119 | &cli.StringSliceFlag{ 120 | Name: "params-from-env", 121 | Usage: "List of environment variables to pass to triggered builds", 122 | EnvVars: []string{"PLUGIN_PARAMS_FROM_ENV"}, 123 | Destination: &settings.ParamsEnv, 124 | }, 125 | &cli.StringFlag{ 126 | Name: "deploy", 127 | Usage: "Environment to trigger deploy for the respective build", 128 | EnvVars: []string{"PLUGIN_DEPLOY"}, 129 | Destination: &settings.Deploy, 130 | }, 131 | &cli.BoolFlag{ 132 | Name: "block", 133 | Usage: "Block until the triggered build is finished, makes this build fail if triggered build fails", 134 | EnvVars: []string{"PLUGIN_BLOCK"}, 135 | Destination: &settings.Block, 136 | }, 137 | &cli.DurationFlag{ 138 | Name: "blockTimeout", 139 | Value: time.Duration(60) * time.Minute, 140 | Usage: "How long to block until the triggered build is finished", 141 | EnvVars: []string{"PLUGIN_BLOCK_TIMEOUT"}, 142 | Destination: &settings.BlockTimeout, 143 | }, 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /plugin/impl.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, the Drone Plugins project authors. 2 | // Please see the AUTHORS file for details. All rights reserved. 3 | // Use of this source code is governed by an Apache 2.0 license that can be 4 | // found in the LICENSE file. 5 | 6 | package plugin 7 | 8 | import ( 9 | "context" 10 | "fmt" 11 | "os" 12 | "os/signal" 13 | "strconv" 14 | "strings" 15 | "syscall" 16 | "time" 17 | 18 | "github.com/drone/drone-go/drone" 19 | "github.com/joho/godotenv" 20 | "github.com/urfave/cli/v2" 21 | "golang.org/x/oauth2" 22 | ) 23 | 24 | // Settings for the plugin. 25 | type Settings struct { 26 | Repos cli.StringSlice 27 | Server string 28 | Token string 29 | Wait bool 30 | Timeout time.Duration 31 | LastSuccessful bool 32 | Params cli.StringSlice 33 | ParamsEnv cli.StringSlice 34 | Deploy string 35 | Block bool 36 | BlockTimeout time.Duration 37 | 38 | server string 39 | params map[string]string 40 | } 41 | 42 | var ( 43 | errBuildNotFound = fmt.Errorf("build not found") 44 | ) 45 | 46 | // Validate handles the settings validation of the plugin. 47 | func (p *Plugin) Validate() error { 48 | if len(p.settings.Token) == 0 { 49 | return fmt.Errorf("you must provide your drone access token") 50 | } 51 | 52 | p.settings.server = getServerWithDefaults(p.settings.Server, p.pipeline.System.Host, p.pipeline.System.Proto) 53 | if len(p.settings.server) == 0 { 54 | return fmt.Errorf("you must provide your drone server") 55 | } 56 | 57 | if p.settings.Wait && p.settings.LastSuccessful { 58 | return fmt.Errorf("only one of wait and last_successful can be true; choose one") 59 | } 60 | 61 | var err error 62 | p.settings.params, err = parseParams(p.settings.Params.Value()) 63 | if err != nil { 64 | return fmt.Errorf("unable to parse params: %s", err) 65 | } 66 | 67 | upstreamBuildNumber, ok := os.LookupEnv("DRONE_BUILD_NUMBER") 68 | if ok { 69 | p.settings.params["DRONE_UPSTREAM_BUILD_NUMBER"] = upstreamBuildNumber 70 | } 71 | 72 | for _, k := range p.settings.ParamsEnv.Value() { 73 | v, exists := os.LookupEnv(k) 74 | if !exists { 75 | return fmt.Errorf("param_from_env %s is not set", k) 76 | } 77 | 78 | p.settings.params[k] = v 79 | } 80 | 81 | return nil 82 | } 83 | 84 | func findFirstBuild(client drone.Client, owner, name string, cond func(*drone.Build) bool) (*drone.Build, error) { 85 | const pageSize = 50 86 | 87 | for page := 0; ; page++ { 88 | 89 | builds, err := client.BuildList(owner, name, drone.ListOptions{ 90 | Page: page, 91 | Size: pageSize, 92 | }) 93 | 94 | if err != nil { 95 | return nil, fmt.Errorf("unable to get build list: %w", err) 96 | } 97 | 98 | for _, b := range builds { 99 | if cond(b) { 100 | return b, nil 101 | } 102 | } 103 | 104 | if len(builds) < pageSize { 105 | // we received less items than asked, it means there are no more builds 106 | break 107 | } 108 | } 109 | return nil, errBuildNotFound 110 | } 111 | 112 | // Execute provides the implementation of the plugin. 113 | func (p *Plugin) Execute() error { 114 | config := new(oauth2.Config) 115 | 116 | auther := config.Client( 117 | context.WithValue(context.Background(), oauth2.HTTPClient, p.network.Client), 118 | &oauth2.Token{ 119 | AccessToken: p.settings.Token, 120 | }, 121 | ) 122 | 123 | client := drone.NewClient(p.settings.server, auther) 124 | 125 | for _, entry := range p.settings.Repos.Value() { 126 | 127 | // parses the repository name in owner/name@branch format 128 | owner, name, branch := parseRepoBranch(entry) 129 | if len(owner) == 0 || len(name) == 0 { 130 | return fmt.Errorf("unable to parse repository name %s", entry) 131 | } 132 | 133 | // check for mandatory build no during deploy trigger 134 | if len(p.settings.Deploy) != 0 { 135 | if branch == "" { 136 | return fmt.Errorf("build no or branch must be mentioned for deploy, format repository@build/branch") 137 | } 138 | if _, err := strconv.Atoi(branch); err != nil && !p.settings.LastSuccessful { 139 | return fmt.Errorf("for deploy build no must be numeric only " + 140 | " or for branch deploy last_successful should be true," + 141 | " format repository@build/branch") 142 | } 143 | } 144 | 145 | waiting := false 146 | 147 | timeout := time.After(p.settings.Timeout) 148 | 149 | tick := time.Tick(1 * time.Second) //nolint:all 150 | 151 | var err error 152 | 153 | // Keep trying until we're timed out, successful or got an error 154 | // Tagged with "I" due to break nested in select 155 | I: 156 | for { 157 | select { 158 | // Got a timeout! fail with a timeout error 159 | case <-timeout: 160 | return fmt.Errorf("timed out waiting on a build for %s", entry) 161 | // Got a tick, we should check on the build status 162 | case <-tick: 163 | // first handle the deploy trigger 164 | if len(p.settings.Deploy) != 0 { 165 | var build *drone.Build 166 | if p.settings.LastSuccessful { 167 | // Get the last successful build of branch 168 | build, err = findFirstBuild(client, owner, name, func(b *drone.Build) bool { 169 | return b.Source == branch && b.Status == drone.StatusPassing && b.Event == "push" 170 | }) 171 | 172 | if err != nil { 173 | return fmt.Errorf("unable to get last successful build for %s: %w", entry, err) 174 | } 175 | } else { 176 | // Get build by number 177 | buildNumber, _ := strconv.Atoi(branch) 178 | build, err = client.Build(owner, name, buildNumber) 179 | if err != nil { 180 | return fmt.Errorf("unable to get requested build %v for deploy for %s", buildNumber, entry) 181 | } 182 | } 183 | if p.settings.Wait && !waiting && (build.Status == drone.StatusRunning || build.Status == drone.StatusPending) { 184 | fmt.Printf("BuildLast for repository: %s, returned build number: %v with a status of %s. Will retry for %v.\n", entry, build.Number, build.Status, p.settings.Timeout) 185 | waiting = true 186 | continue 187 | } 188 | if (build.Status != drone.StatusRunning && build.Status != drone.StatusPending) || !p.settings.Wait { 189 | // start a new deploy 190 | var newBuild *drone.Build 191 | newBuild, err = client.Promote(owner, name, int(build.Number), p.settings.Deploy, p.settings.params) 192 | if err != nil { 193 | if waiting { 194 | continue 195 | } 196 | return fmt.Errorf("unable to trigger deploy for %s - err %v", entry, err) 197 | } 198 | fmt.Printf("starting deploy for %s/%s env - %s build - %d\n", owner, name, p.settings.Deploy, build.Number) 199 | logParams(p.settings.params, p.settings.ParamsEnv.Value()) 200 | 201 | if p.settings.Block { 202 | err = blockUntilBuildIsFinished(p, client, owner, name, int(newBuild.Number)) 203 | if err != nil { 204 | return err 205 | } 206 | } 207 | 208 | break I 209 | } 210 | } 211 | 212 | // get the latest build for the specified repository 213 | build, err := client.BuildLast(owner, name, branch) 214 | if err != nil { 215 | if waiting { 216 | continue 217 | } 218 | return fmt.Errorf("unable to get latest build for %s: %s", entry, err) 219 | } 220 | if p.settings.Wait && !waiting && (build.Status == drone.StatusRunning || build.Status == drone.StatusPending) { 221 | fmt.Printf("BuildLast for repository: %s, returned build number: %v with a status of %s. Will retry for %v.\n", entry, build.Number, build.Status, p.settings.Timeout) 222 | waiting = true 223 | continue 224 | } else if p.settings.LastSuccessful && build.Status != drone.StatusPassing { 225 | 226 | build, err = findFirstBuild(client, owner, name, func(b *drone.Build) bool { 227 | return b.Source == branch && b.Status == drone.StatusPassing && b.Event == "push" 228 | }) 229 | 230 | if err != nil { 231 | return fmt.Errorf("unable to get last successful build for %s: %w", entry, err) 232 | } 233 | } 234 | 235 | if (build.Status != drone.StatusRunning && build.Status != drone.StatusPending) || !p.settings.Wait { 236 | // rebuild the latest build 237 | var newBuild *drone.Build 238 | newBuild, err = client.BuildRestart(owner, name, int(build.Number), p.settings.params) 239 | if err != nil { 240 | if waiting { 241 | continue 242 | } 243 | return fmt.Errorf("unable to trigger build for %s", entry) 244 | } 245 | fmt.Printf("Restarting build %d for %s\n", build.Number, entry) 246 | logParams(p.settings.params, p.settings.ParamsEnv.Value()) 247 | 248 | if p.settings.Block { 249 | err = blockUntilBuildIsFinished(p, client, owner, name, int(newBuild.Number)) 250 | if err != nil { 251 | return err 252 | } 253 | } 254 | 255 | break I 256 | } 257 | } 258 | } 259 | } 260 | 261 | return nil 262 | } 263 | 264 | func parseRepoBranch(repo string) (string, string, string) { 265 | var ( 266 | owner string 267 | name string 268 | branch string 269 | ) 270 | 271 | parts := strings.Split(repo, "@") 272 | if len(parts) == 2 { 273 | branch = parts[1] 274 | repo = parts[0] 275 | } 276 | 277 | parts = strings.Split(repo, "/") 278 | if len(parts) == 2 { 279 | owner = parts[0] 280 | name = parts[1] 281 | } 282 | return owner, name, branch 283 | } 284 | 285 | func parseParams(paramList []string) (map[string]string, error) { 286 | params := make(map[string]string) 287 | for _, p := range paramList { 288 | parts := strings.SplitN(p, "=", 2) 289 | if len(parts) == 2 { 290 | params[parts[0]] = parts[1] 291 | } else if _, err := os.Stat(parts[0]); os.IsNotExist(err) { 292 | return nil, fmt.Errorf( 293 | "invalid param '%s'; must be KEY=VALUE or file path", 294 | parts[0], 295 | ) 296 | } else { 297 | fileParams, err := godotenv.Read(parts[0]) 298 | if err != nil { 299 | return nil, err 300 | } 301 | 302 | for k, v := range fileParams { 303 | params[k] = v 304 | } 305 | } 306 | } 307 | 308 | return params, nil 309 | } 310 | 311 | func logParams(params map[string]string, paramsEnv []string) { 312 | if len(params) > 0 { 313 | fmt.Println(" with params:") 314 | for k, v := range params { 315 | fromEnv := false 316 | for _, e := range paramsEnv { 317 | if k == e { 318 | fromEnv = true 319 | break 320 | } 321 | } 322 | if fromEnv { 323 | v = "[from-environment]" 324 | } 325 | fmt.Printf(" - %s: %s\n", k, v) 326 | } 327 | } 328 | } 329 | 330 | func getServerWithDefaults(server string, host string, protocol string) string { 331 | if len(server) != 0 { 332 | return server 333 | } 334 | 335 | if len(host) == 0 || len(protocol) == 0 { 336 | return "" 337 | } 338 | 339 | return fmt.Sprintf("%s://%s", protocol, host) 340 | } 341 | 342 | func blockUntilBuildIsFinished(p *Plugin, client drone.Client, namespace, name string, buildNumber int) error { 343 | fmt.Printf("\nblocking until triggered build is finished\n") 344 | 345 | timeout := time.After(p.settings.BlockTimeout) 346 | 347 | tick := time.Tick(10 * time.Second) //nolint:all 348 | 349 | // listen for SIGINT and SIGTERM to cancel downstream build when stopping this executable 350 | // this does not work in drone because drone uses SIGKILL to terminate its containers 351 | // but when running the plugin locally during development, it's very handy 352 | sigs := make(chan os.Signal, 1) 353 | signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM) 354 | defer close(sigs) 355 | 356 | for { 357 | select { 358 | case <-sigs: 359 | err := client.BuildCancel(namespace, name, int(buildNumber)) 360 | if err != nil { 361 | return fmt.Errorf("could not cancel downstream job %d", buildNumber) 362 | } 363 | 364 | fmt.Printf("canceled downstream job %d\n", buildNumber) 365 | 366 | return nil 367 | 368 | // Got a timeout! fail with a timeout error 369 | case <-timeout: 370 | return fmt.Errorf("timed out waiting for %d", buildNumber) 371 | 372 | // Got a tick, we should check on the build status 373 | case <-tick: 374 | build, err := client.Build(namespace, name, buildNumber) 375 | if err != nil { 376 | return err 377 | } 378 | 379 | switch build.Status { 380 | case drone.StatusError, drone.StatusKilled, drone.StatusFailing, drone.StatusDeclined, drone.StatusSkipped: 381 | return fmt.Errorf( 382 | "build %d did not succeed: %s", 383 | buildNumber, 384 | build.Status, 385 | ) 386 | case drone.StatusPassing: 387 | return nil 388 | default: 389 | fmt.Printf("Waiting for build %d in status %s\n", buildNumber, build.Status) 390 | } 391 | } 392 | } 393 | } 394 | -------------------------------------------------------------------------------- /plugin/impl_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, the Drone Plugins project authors. 2 | // Please see the AUTHORS file for details. All rights reserved. 3 | // Use of this source code is governed by an Apache 2.0 license that can be 4 | // found in the LICENSE file. 5 | 6 | package plugin 7 | 8 | import ( 9 | "reflect" 10 | "testing" 11 | ) 12 | 13 | func Test_parseRepoBranch(t *testing.T) { 14 | 15 | var tests = []struct { 16 | Repo string 17 | Owner string 18 | Name string 19 | Branch string 20 | }{ 21 | {"octocat/hello-world", "octocat", "hello-world", ""}, 22 | {"octocat/hello-world@master", "octocat", "hello-world", "master"}, 23 | } 24 | 25 | for _, test := range tests { 26 | 27 | owner, name, branch := parseRepoBranch(test.Repo) 28 | if owner != test.Owner { 29 | t.Errorf("wanted repository owner %s, got %s", test.Owner, owner) 30 | } 31 | if name != test.Name { 32 | t.Errorf("wanted repository name %s, got %s", test.Name, name) 33 | } 34 | if branch != test.Branch { 35 | t.Errorf("wanted repository branch %s, got %s", test.Branch, branch) 36 | } 37 | } 38 | } 39 | 40 | func Test_parseParams_invalid(t *testing.T) { 41 | out, err := parseParams([]string{"invalid"}) 42 | if err == nil { 43 | t.Errorf("expected error, got %v", out) 44 | } 45 | } 46 | 47 | func Test_parseParams(t *testing.T) { 48 | var tests = []struct { 49 | Input []string 50 | Output map[string]string 51 | }{ 52 | {[]string{}, map[string]string{}}, 53 | { 54 | []string{"where=far", "who=you"}, 55 | map[string]string{"where": "far", "who": "you"}, 56 | }, 57 | { 58 | []string{"where=very=far"}, 59 | map[string]string{"where": "very=far"}}, 60 | { 61 | []string{"test_params.env"}, 62 | map[string]string{ 63 | "SOME_VAR": "someval", 64 | "FOO": "BAR", 65 | "BAR": "BAZ", 66 | "foo": "bar", 67 | "bar": "baz", 68 | }, 69 | }, 70 | { 71 | []string{"test_params.env", "where=far", "who=you"}, 72 | map[string]string{ 73 | "SOME_VAR": "someval", 74 | "FOO": "BAR", 75 | "BAR": "BAZ", 76 | "foo": "bar", 77 | "bar": "baz", 78 | "where": "far", 79 | "who": "you", 80 | }, 81 | }, 82 | } 83 | 84 | for _, test := range tests { 85 | out, err := parseParams(test.Input) 86 | if err != nil { 87 | t.Errorf("unable to parse params: %s", err) 88 | 89 | break 90 | } 91 | 92 | if !reflect.DeepEqual(out, test.Output) { 93 | t.Errorf("wanted params %+v, got %+v", test.Output, out) 94 | } 95 | } 96 | } 97 | 98 | func Test_getServerWithDefaults(t *testing.T) { 99 | var tests = []struct { 100 | Server string 101 | Host string 102 | Proto string 103 | Result string 104 | }{ 105 | {"", "drone.example.com", "http", "http://drone.example.com"}, 106 | {"", "drone.example.com:8000", "http", "http://drone.example.com:8000"}, 107 | {"", "drone.example.com", "https", "https://drone.example.com"}, 108 | {"", "drone.example.com:8888", "https", "https://drone.example.com:8888"}, 109 | {"https://drone.example.com", "drone.example.com:8888", "https", "https://drone.example.com"}, 110 | } 111 | 112 | for _, test := range tests { 113 | server := getServerWithDefaults(test.Server, test.Host, test.Proto) 114 | 115 | if server != test.Result { 116 | t.Errorf("wanted server url %s, got %s", test.Result, server) 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /plugin/plugin.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, the Drone Plugins project authors. 2 | // Please see the AUTHORS file for details. All rights reserved. 3 | // Use of this source code is governed by an Apache 2.0 license that can be 4 | // found in the LICENSE file. 5 | 6 | package plugin 7 | 8 | import ( 9 | "github.com/drone-plugins/drone-plugin-lib/drone" 10 | ) 11 | 12 | // Plugin implements drone.Plugin to provide the plugin implementation. 13 | type Plugin struct { 14 | settings Settings 15 | pipeline drone.Pipeline 16 | network drone.Network 17 | } 18 | 19 | // New initializes a plugin from the given Settings, Pipeline, and Network. 20 | func New(settings Settings, pipeline drone.Pipeline, network drone.Network) drone.Plugin { 21 | return &Plugin{ 22 | settings: settings, 23 | pipeline: pipeline, 24 | network: network, 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /plugin/plugin_test.go: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020, the Drone Plugins project authors. 2 | // Please see the AUTHORS file for details. All rights reserved. 3 | // Use of this source code is governed by an Apache 2.0 license that can be 4 | // found in the LICENSE file. 5 | 6 | package plugin 7 | 8 | import ( 9 | "testing" 10 | ) 11 | 12 | func TestPlugin(t *testing.T) { 13 | t.Skip() 14 | } 15 | -------------------------------------------------------------------------------- /plugin/test_params.env: -------------------------------------------------------------------------------- 1 | # from https://github.com/joho/godotenv#usage 2 | 3 | # I am a comment and that is OK 4 | SOME_VAR=someval 5 | FOO=BAR # comments at line end are OK too 6 | export BAR=BAZ 7 | 8 | # Or finally you can do YAML(ish) style 9 | foo: bar 10 | bar: baz 11 | --------------------------------------------------------------------------------