├── .dockerignore ├── .drone.jsonnet ├── .drone.yml ├── .gitignore ├── LICENSE ├── README.md ├── cmd └── drone-kubernetes-secrets │ └── main.go ├── docker ├── Dockerfile.linux.amd64 ├── Dockerfile.linux.arm ├── Dockerfile.linux.arm64 └── manifest.tmpl ├── go.mod ├── go.sum ├── pipeline.libsonnet └── plugin ├── match.go ├── match_test.go ├── plugin.go ├── plugin_test.go ├── testdata ├── error.protobuf └── secret.protobuf ├── util.go └── util_test.go /.dockerignore: -------------------------------------------------------------------------------- 1 | * 2 | !release/ 3 | -------------------------------------------------------------------------------- /.drone.jsonnet: -------------------------------------------------------------------------------- 1 | local pipeline = import 'pipeline.libsonnet'; 2 | local name = 'drone-kubernetes-secrets'; 3 | 4 | [ 5 | pipeline.test('linux', 'amd64'), 6 | pipeline.build(name, 'linux', 'amd64'), 7 | pipeline.build(name, 'linux', 'arm64'), 8 | pipeline.build(name, 'linux', 'arm'), 9 | pipeline.notifications(depends_on=[ 10 | 'linux-amd64', 11 | 'linux-arm64', 12 | 'linux-arm', 13 | ]), 14 | ] 15 | -------------------------------------------------------------------------------- /.drone.yml: -------------------------------------------------------------------------------- 1 | --- 2 | kind: pipeline 3 | name: testing 4 | 5 | platform: 6 | os: linux 7 | arch: amd64 8 | 9 | steps: 10 | - name: vet 11 | pull: always 12 | image: golang:1.13 13 | commands: 14 | - go vet ./... 15 | environment: 16 | GO111MODULE: on 17 | volumes: 18 | - name: gopath 19 | path: /go 20 | 21 | - name: test 22 | pull: always 23 | image: golang:1.13 24 | commands: 25 | - go test -cover ./... 26 | environment: 27 | GO111MODULE: on 28 | volumes: 29 | - name: gopath 30 | path: /go 31 | 32 | volumes: 33 | - name: gopath 34 | temp: {} 35 | 36 | trigger: 37 | ref: 38 | - refs/heads/master 39 | - "refs/tags/**" 40 | - "refs/pull/**" 41 | 42 | --- 43 | kind: pipeline 44 | name: linux-amd64 45 | 46 | platform: 47 | os: linux 48 | arch: amd64 49 | 50 | steps: 51 | - name: build 52 | pull: always 53 | image: golang:1.13 54 | commands: 55 | - go build -v -a -tags netgo -o release/linux/amd64/drone-kubernetes-secrets ./cmd/drone-kubernetes-secrets 56 | environment: 57 | CGO_ENABLED: 0 58 | GO111MODULE: on 59 | 60 | - name: dryrun 61 | pull: always 62 | image: plugins/docker:linux-amd64 63 | settings: 64 | daemon_off: false 65 | dockerfile: docker/Dockerfile.linux.amd64 66 | dry_run: true 67 | password: 68 | from_secret: docker_password 69 | repo: drone/kubernetes-secrets 70 | tags: linux-amd64 71 | username: 72 | from_secret: docker_username 73 | when: 74 | event: 75 | - pull_request 76 | 77 | - name: publish 78 | pull: always 79 | image: plugins/docker:linux-amd64 80 | settings: 81 | auto_tag: true 82 | auto_tag_suffix: linux-amd64 83 | daemon_off: false 84 | dockerfile: docker/Dockerfile.linux.amd64 85 | password: 86 | from_secret: docker_password 87 | repo: drone/kubernetes-secrets 88 | username: 89 | from_secret: docker_username 90 | when: 91 | event: 92 | exclude: 93 | - pull_request 94 | 95 | - name: tarball 96 | pull: always 97 | image: golang:1.13 98 | commands: 99 | - tar -cvzf release/drone-kubernetes-secrets_linux_amd64.tar.gz -C release/linux/amd64 drone-kubernetes-secrets 100 | - "sha256sum release/drone-kubernetes-secrets_linux_amd64.tar.gz > release/drone-kubernetes-secrets_linux_amd64.tar.gz.sha256" 101 | when: 102 | event: 103 | - tag 104 | 105 | - name: gpgsign 106 | pull: always 107 | image: plugins/gpgsign 108 | settings: 109 | files: 110 | - "release/*.tar.gz" 111 | - "release/*.tar.gz.sha256" 112 | key: 113 | from_secret: gpgsign_key 114 | passphrase: 115 | from_secret: gpgkey_passphrase 116 | when: 117 | event: 118 | - tag 119 | 120 | - name: github 121 | pull: always 122 | image: plugins/github-release 123 | settings: 124 | files: 125 | - "release/*.tar.gz" 126 | - "release/*.tar.gz.sha256" 127 | - "release/*.tar.gz.asc" 128 | token: 129 | from_secret: github_token 130 | when: 131 | event: 132 | - tag 133 | 134 | trigger: 135 | ref: 136 | - refs/heads/master 137 | - "refs/tags/**" 138 | - "refs/pull/**" 139 | 140 | depends_on: 141 | - testing 142 | 143 | --- 144 | kind: pipeline 145 | name: linux-arm64 146 | 147 | platform: 148 | os: linux 149 | arch: arm64 150 | 151 | steps: 152 | - name: build 153 | pull: always 154 | image: golang:1.13 155 | commands: 156 | - go build -v -a -tags netgo -o release/linux/arm64/drone-kubernetes-secrets ./cmd/drone-kubernetes-secrets 157 | environment: 158 | CGO_ENABLED: 0 159 | GO111MODULE: on 160 | 161 | - name: dryrun 162 | pull: always 163 | image: plugins/docker:linux-arm64 164 | settings: 165 | daemon_off: false 166 | dockerfile: docker/Dockerfile.linux.arm64 167 | dry_run: true 168 | password: 169 | from_secret: docker_password 170 | repo: drone/kubernetes-secrets 171 | tags: linux-arm64 172 | username: 173 | from_secret: docker_username 174 | when: 175 | event: 176 | - pull_request 177 | 178 | - name: publish 179 | pull: always 180 | image: plugins/docker:linux-arm64 181 | settings: 182 | auto_tag: true 183 | auto_tag_suffix: linux-arm64 184 | daemon_off: false 185 | dockerfile: docker/Dockerfile.linux.arm64 186 | password: 187 | from_secret: docker_password 188 | repo: drone/kubernetes-secrets 189 | username: 190 | from_secret: docker_username 191 | when: 192 | event: 193 | exclude: 194 | - pull_request 195 | 196 | - name: tarball 197 | pull: always 198 | image: golang:1.13 199 | commands: 200 | - tar -cvzf release/drone-kubernetes-secrets_linux_arm64.tar.gz -C release/linux/arm64 drone-kubernetes-secrets 201 | - "sha256sum release/drone-kubernetes-secrets_linux_arm64.tar.gz > release/drone-kubernetes-secrets_linux_arm64.tar.gz.sha256" 202 | when: 203 | event: 204 | - tag 205 | 206 | - name: gpgsign 207 | pull: always 208 | image: plugins/gpgsign 209 | settings: 210 | files: 211 | - "release/*.tar.gz" 212 | - "release/*.tar.gz.sha256" 213 | key: 214 | from_secret: gpgsign_key 215 | passphrase: 216 | from_secret: gpgkey_passphrase 217 | when: 218 | event: 219 | - tag 220 | 221 | - name: github 222 | pull: always 223 | image: plugins/github-release 224 | settings: 225 | files: 226 | - "release/*.tar.gz" 227 | - "release/*.tar.gz.sha256" 228 | - "release/*.tar.gz.asc" 229 | token: 230 | from_secret: github_token 231 | when: 232 | event: 233 | - tag 234 | 235 | trigger: 236 | ref: 237 | - refs/heads/master 238 | - "refs/tags/**" 239 | - "refs/pull/**" 240 | 241 | depends_on: 242 | - testing 243 | 244 | --- 245 | kind: pipeline 246 | name: linux-arm 247 | 248 | platform: 249 | os: linux 250 | arch: arm 251 | 252 | steps: 253 | - name: build 254 | pull: always 255 | image: golang:1.13 256 | commands: 257 | - go build -v -a -tags netgo -o release/linux/arm/drone-kubernetes-secrets ./cmd/drone-kubernetes-secrets 258 | environment: 259 | CGO_ENABLED: 0 260 | GO111MODULE: on 261 | 262 | - name: dryrun 263 | pull: always 264 | image: plugins/docker:linux-arm 265 | settings: 266 | daemon_off: false 267 | dockerfile: docker/Dockerfile.linux.arm 268 | dry_run: true 269 | password: 270 | from_secret: docker_password 271 | repo: drone/kubernetes-secrets 272 | tags: linux-arm 273 | username: 274 | from_secret: docker_username 275 | when: 276 | event: 277 | - pull_request 278 | 279 | - name: publish 280 | pull: always 281 | image: plugins/docker:linux-arm 282 | settings: 283 | auto_tag: true 284 | auto_tag_suffix: linux-arm 285 | daemon_off: false 286 | dockerfile: docker/Dockerfile.linux.arm 287 | password: 288 | from_secret: docker_password 289 | repo: drone/kubernetes-secrets 290 | username: 291 | from_secret: docker_username 292 | when: 293 | event: 294 | exclude: 295 | - pull_request 296 | 297 | - name: tarball 298 | pull: always 299 | image: golang:1.13 300 | commands: 301 | - tar -cvzf release/drone-kubernetes-secrets_linux_arm.tar.gz -C release/linux/arm drone-kubernetes-secrets 302 | - "sha256sum release/drone-kubernetes-secrets_linux_arm.tar.gz > release/drone-kubernetes-secrets_linux_arm.tar.gz.sha256" 303 | when: 304 | event: 305 | - tag 306 | 307 | - name: gpgsign 308 | pull: always 309 | image: plugins/gpgsign 310 | settings: 311 | files: 312 | - "release/*.tar.gz" 313 | - "release/*.tar.gz.sha256" 314 | key: 315 | from_secret: gpgsign_key 316 | passphrase: 317 | from_secret: gpgkey_passphrase 318 | when: 319 | event: 320 | - tag 321 | 322 | - name: github 323 | pull: always 324 | image: plugins/github-release 325 | settings: 326 | files: 327 | - "release/*.tar.gz" 328 | - "release/*.tar.gz.sha256" 329 | - "release/*.tar.gz.asc" 330 | token: 331 | from_secret: github_token 332 | when: 333 | event: 334 | - tag 335 | 336 | trigger: 337 | ref: 338 | - refs/heads/master 339 | - "refs/tags/**" 340 | - "refs/pull/**" 341 | 342 | depends_on: 343 | - testing 344 | 345 | --- 346 | kind: pipeline 347 | name: notifications 348 | 349 | platform: 350 | os: linux 351 | arch: amd64 352 | 353 | steps: 354 | - name: manifest 355 | pull: always 356 | image: plugins/manifest 357 | settings: 358 | ignore_missing: true 359 | password: 360 | from_secret: docker_password 361 | spec: docker/manifest.tmpl 362 | username: 363 | from_secret: docker_username 364 | 365 | trigger: 366 | ref: 367 | - refs/heads/master 368 | - "refs/tags/**" 369 | 370 | depends_on: 371 | - linux-amd64 372 | - linux-arm64 373 | - linux-arm 374 | 375 | ... 376 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | 26 | release/ 27 | vendor/ 28 | 29 | coverage.out 30 | .env 31 | drone-kubernetes-secrets 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019 Drone.IO, Inc. 2 | 3 | Source code in this repository is variously licensed under the 4 | Apache License Version 2.0, an Apache compatible license, or the 5 | Drone Non-Commercial License. Source code in a given file is 6 | licensed under the Drone Non-Commercial License unless otherwise 7 | noted at the beginning of the file. 8 | 9 | ----------------------------------------------------------------- 10 | 11 | Drone Non-Commercial License 12 | 13 | Contributor: Drone.IO, Inc. 14 | 15 | Source Code: https://github.com/drone/drone 16 | 17 | This license lets you use and share this software for free, 18 | with a trial-length time limit on commercial use. Specifically: 19 | 20 | If you follow the rules below, you may do everything with this 21 | software that would otherwise infringe either the contributor's 22 | copyright in it, any patent claim the contributor can license 23 | that covers this software as of the contributor's latest 24 | contribution, or both. 25 | 26 | 1. You must limit use of this software in any manner primarily 27 | intended for or directed toward commercial advantage or 28 | private monetary compensation to a trial period of 32 29 | consecutive calendar days. This limit does not apply to use in 30 | developing feedback, modifications, or extensions that you 31 | contribute back to those giving this license. 32 | 33 | 2. Ensure everyone who gets a copy of this software from you, in 34 | source code or any other form, gets the text of this license 35 | and the contributor and source code lines above. 36 | 37 | 3. Do not make any legal claim against anyone for infringing any 38 | patent claim they would infringe by using this software alone, 39 | accusing this software, with or without changes, alone or as 40 | part of a larger application. 41 | 42 | You are excused for unknowingly breaking rule 1 if you stop 43 | doing anything requiring this license within 30 days of 44 | learning you broke the rule. 45 | 46 | **This software comes as is, without any warranty at all. As far 47 | as the law allows, the contributor will not be liable for any 48 | damages related to this software or this license, for any kind of 49 | legal claim.** 50 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # drone-kubernetes-secrets 2 | 3 | [![Build Status](http://cloud.drone.io/api/badges/drone/drone-kubernetes-secrets/status.svg)](http://cloud.drone.io/drone/drone-kubernetes-secrets) 4 | [![Gitter chat](https://badges.gitter.im/drone/drone.png)](https://gitter.im/drone/drone) 5 | [![Join the discussion at https://discourse.drone.io](https://img.shields.io/badge/discourse-forum-orange.svg)](https://discourse.drone.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/drone/kubernetes-secrets.svg)](https://microbadger.com/images/drone/kubernetes-secrets "Get your own image badge on microbadger.com") 8 | [![Go Doc](https://godoc.org/github.com/drone/drone-kubernetes-secrets?status.svg)](http://godoc.org/github.com/drone/drone-kubernetes-secrets) 9 | [![Go Report](https://goreportcard.com/badge/github.com/drone/drone-kubernetes-secrets)](https://goreportcard.com/report/github.com/drone/drone-kubernetes-secrets) 10 | 11 | TBD 12 | -------------------------------------------------------------------------------- /cmd/drone-kubernetes-secrets/main.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Drone.IO Inc. All rights reserved. 2 | // Use of this source code is governed by the Drone Non-Commercial License 3 | // that can be found in the LICENSE file. 4 | 5 | package main 6 | 7 | import ( 8 | "io/ioutil" 9 | "net/http" 10 | 11 | "github.com/drone/drone-go/plugin/secret" 12 | "github.com/drone/drone-kubernetes-secrets/plugin" 13 | 14 | "github.com/ericchiang/k8s" 15 | "github.com/ghodss/yaml" 16 | "github.com/kelseyhightower/envconfig" 17 | "github.com/sirupsen/logrus" 18 | 19 | _ "github.com/joho/godotenv/autoload" 20 | ) 21 | 22 | type config struct { 23 | Debug bool `envconfig:"DEBUG"` 24 | Address string `envconfig:"SERVER_ADDRESS"` 25 | Secret string `envconfig:"SECRET_KEY"` 26 | Config string `envconfig:"KUBERNETES_CONFIG"` 27 | Namespace string `envconfig:"KUBERNETES_NAMESPACE"` 28 | } 29 | 30 | func main() { 31 | spec := new(config) 32 | err := envconfig.Process("", spec) 33 | if err != nil { 34 | logrus.Fatal(err) 35 | } 36 | 37 | if spec.Debug { 38 | logrus.SetLevel(logrus.DebugLevel) 39 | } 40 | if spec.Secret == "" { 41 | logrus.Fatalln("missing secret key") 42 | } 43 | if spec.Address == "" { 44 | spec.Address = ":3000" 45 | } 46 | if spec.Namespace == "" { 47 | spec.Namespace = "default" 48 | } 49 | 50 | client, err := createClient(spec.Config) 51 | if err != nil { 52 | logrus.Fatal(err) 53 | } 54 | 55 | handler := secret.Handler( 56 | spec.Secret, 57 | plugin.New(client, spec.Namespace), 58 | logrus.StandardLogger(), 59 | ) 60 | 61 | logrus.Infof("server listening on address %s", spec.Address) 62 | 63 | http.Handle("/", handler) 64 | logrus.Fatal(http.ListenAndServe(spec.Address, nil)) 65 | } 66 | 67 | func createClient(path string) (*k8s.Client, error) { 68 | if path == "" { 69 | return k8s.NewInClusterClient() 70 | } 71 | 72 | data, err := ioutil.ReadFile(path) 73 | if err != nil { 74 | return nil, err 75 | } 76 | 77 | var config k8s.Config 78 | if err := yaml.Unmarshal(data, &config); err != nil { 79 | return nil, err 80 | } 81 | return k8s.NewClient(&config) 82 | } 83 | -------------------------------------------------------------------------------- /docker/Dockerfile.linux.amd64: -------------------------------------------------------------------------------- 1 | FROM alpine:3.9 2 | 3 | LABEL maintainer="Drone.IO Community " \ 4 | org.label-schema.name="Drone Kubernetes Secrets" \ 5 | org.label-schema.vendor="Drone.IO Community" \ 6 | org.label-schema.schema-version="1.0" 7 | 8 | EXPOSE 3000 9 | ENV GODEBUG netdns=go 10 | 11 | RUN apk add --no-cache ca-certificates 12 | 13 | ADD release/linux/amd64/drone-kubernetes-secrets /bin/ 14 | ENTRYPOINT ["/bin/drone-kubernetes-secrets"] 15 | -------------------------------------------------------------------------------- /docker/Dockerfile.linux.arm: -------------------------------------------------------------------------------- 1 | FROM arm32v6/alpine:3.9 2 | 3 | LABEL maintainer="Drone.IO Community " \ 4 | org.label-schema.name="Drone Kubernetes Secrets" \ 5 | org.label-schema.vendor="Drone.IO Community" \ 6 | org.label-schema.schema-version="1.0" 7 | 8 | EXPOSE 3000 9 | ENV GODEBUG netdns=go 10 | 11 | RUN apk add --no-cache ca-certificates 12 | 13 | ADD release/linux/arm/drone-kubernetes-secrets /bin/ 14 | ENTRYPOINT ["/bin/drone-kubernetes-secrets"] 15 | -------------------------------------------------------------------------------- /docker/Dockerfile.linux.arm64: -------------------------------------------------------------------------------- 1 | FROM arm64v8/alpine:3.9 2 | 3 | LABEL maintainer="Drone.IO Community " \ 4 | org.label-schema.name="Drone Kubernetes Secrets" \ 5 | org.label-schema.vendor="Drone.IO Community" \ 6 | org.label-schema.schema-version="1.0" 7 | 8 | EXPOSE 3000 9 | ENV GODEBUG netdns=go 10 | 11 | RUN apk add --no-cache ca-certificates 12 | 13 | ADD release/linux/arm64/drone-kubernetes-secrets /bin/ 14 | ENTRYPOINT ["/bin/drone-kubernetes-secrets"] 15 | -------------------------------------------------------------------------------- /docker/manifest.tmpl: -------------------------------------------------------------------------------- 1 | image: drone/kubernetes-secrets:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}} 2 | {{#if build.tags}} 3 | tags: 4 | {{#each build.tags}} 5 | - {{this}} 6 | {{/each}} 7 | {{/if}} 8 | manifests: 9 | - 10 | image: drone/kubernetes-secrets:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-amd64 11 | platform: 12 | architecture: amd64 13 | os: linux 14 | - 15 | image: drone/kubernetes-secrets:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm64 16 | platform: 17 | architecture: arm64 18 | os: linux 19 | - 20 | image: drone/kubernetes-secrets:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm 21 | platform: 22 | architecture: arm 23 | os: linux 24 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/drone/drone-kubernetes-secrets 2 | 3 | require ( 4 | github.com/99designs/httpsignatures-go v0.0.0-20170731043157-88528bf4ca7e 5 | github.com/drone/drone-go v1.0.0 6 | github.com/ericchiang/k8s v1.1.0 7 | github.com/ghodss/yaml v1.0.0 8 | github.com/golang/protobuf v1.1.0 9 | github.com/google/go-cmp v0.2.0 10 | github.com/h2non/gock v1.0.9 11 | github.com/joho/godotenv v1.2.0 12 | github.com/kelseyhightower/envconfig v1.3.0 13 | github.com/sirupsen/logrus v1.0.6 14 | golang.org/x/crypto v0.0.0-20180808211826-de0752318171 15 | golang.org/x/net v0.0.0-20180811021610-c39426892332 16 | golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0 17 | golang.org/x/text v0.3.0 18 | gopkg.in/yaml.v2 v2.2.1 19 | ) 20 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/99designs/httpsignatures-go v0.0.0-20170731043157-88528bf4ca7e h1:rl2Aq4ZODqTDkeSqQBy+fzpZPamacO1Srp8zq7jf2Sc= 2 | github.com/99designs/httpsignatures-go v0.0.0-20170731043157-88528bf4ca7e/go.mod h1:Xa6lInWHNQnuWoF0YPSsx+INFA9qk7/7pTjwb3PInkY= 3 | github.com/drone/drone-go v0.0.0-20180812181519-1c015eaecb1c h1:BZiTtYbh/xH+nomMaHPu/o+cjoasj9WuDn2qUi4hV90= 4 | github.com/drone/drone-go v0.0.0-20180812181519-1c015eaecb1c/go.mod h1:qVb1k1w9X5jgoGyLtbnfWNnd4XZfAwokxBmiutbpGqw= 5 | github.com/drone/drone-go v1.0.0 h1:8UF1A+2wGab4VCI0kUCdgfYvDUACZWDziPxr8ZeOd5g= 6 | github.com/drone/drone-go v1.0.0/go.mod h1:GxyeGClYohaKNYJv/ZpsmVHtMJ7WhoT+uDaJNcDIrk4= 7 | github.com/ericchiang/k8s v1.1.0 h1:XjBbrZhlvos0PtQrvvSIPAeinnrYM4c/QKB0CWfnoJU= 8 | github.com/ericchiang/k8s v1.1.0/go.mod h1:/OmBgSq2cd9IANnsGHGlEz27nwMZV2YxlpXuQtU3Bz4= 9 | github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk= 10 | github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= 11 | github.com/golang/protobuf v1.1.0 h1:0iH4Ffd/meGoXqF2lSAhZHt8X+cPgkfn/cb6Cce5Vpc= 12 | github.com/golang/protobuf v1.1.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= 13 | github.com/google/go-cmp v0.2.0 h1:+dTQ8DZQJz0Mb/HjFlkptS1FeQ4cWSnN941F8aEG4SQ= 14 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 15 | github.com/h2non/gock v1.0.9 h1:17gCehSo8ZOgEsFKpQgqHiR7VLyjxdAG3lkhVvO9QZU= 16 | github.com/h2non/gock v1.0.9/go.mod h1:CZMcB0Lg5IWnr9bF79pPMg9WeV6WumxQiUJ1UvdO1iE= 17 | github.com/joho/godotenv v1.2.0 h1:vGTvz69FzUFp+X4/bAkb0j5BoLC+9bpqTWY8mjhA9pc= 18 | github.com/joho/godotenv v1.2.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= 19 | github.com/kelseyhightower/envconfig v1.3.0 h1:IvRS4f2VcIQy6j4ORGIf9145T/AsUB+oY8LyvN8BXNM= 20 | github.com/kelseyhightower/envconfig v1.3.0/go.mod h1:cccZRl6mQpaq41TPp5QxidR+Sa3axMbJDNb//FQX6Gg= 21 | github.com/sirupsen/logrus v1.0.6 h1:hcP1GmhGigz/O7h1WVUM5KklBp1JoNS9FggWKdj/j3s= 22 | github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= 23 | golang.org/x/crypto v0.0.0-20180808211826-de0752318171 h1:vYogbvSFj2YXcjQxFHu/rASSOt9sLytpCaSkiwQ135I= 24 | golang.org/x/crypto v0.0.0-20180808211826-de0752318171/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= 25 | golang.org/x/net v0.0.0-20180811021610-c39426892332 h1:efGso+ep0DjyCBJPjvoz0HI6UldX4Md2F1rZFe1ir0E= 26 | golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= 27 | golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0 h1:8H8QZJ30plJyIVj60H3lr8TZGIq2Fh3Cyrs/ZNg1foU= 28 | golang.org/x/sys v0.0.0-20180810173357-98c5dad5d1a0/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 29 | golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= 30 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 31 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 32 | gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= 33 | gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 34 | -------------------------------------------------------------------------------- /pipeline.libsonnet: -------------------------------------------------------------------------------- 1 | local windows_pipe = '\\\\\\\\.\\\\pipe\\\\docker_engine'; 2 | local windows_pipe_volume = 'docker_pipe'; 3 | local test_pipeline_name = 'testing'; 4 | 5 | local windows(os) = os == 'windows'; 6 | 7 | local golang_image(os, version) = 8 | 'golang:' + '1.13' + if windows(os) then '-windowsservercore-' + version else ''; 9 | 10 | { 11 | test(os='linux', arch='amd64', version=''):: 12 | local is_windows = windows(os); 13 | local golang = golang_image(os, version); 14 | local volumes = if is_windows then [{name: 'gopath', path: 'C:\\\\gopath'}] else [{name: 'gopath', path: '/go',}]; 15 | { 16 | kind: 'pipeline', 17 | name: test_pipeline_name, 18 | platform: { 19 | os: os, 20 | arch: arch, 21 | version: if std.length(version) > 0 then version, 22 | }, 23 | steps: [ 24 | { 25 | name: 'vet', 26 | image: golang, 27 | pull: 'always', 28 | environment: { 29 | GO111MODULE: 'on', 30 | }, 31 | commands: [ 32 | 'go vet ./...', 33 | ], 34 | volumes: volumes, 35 | }, 36 | { 37 | name: 'test', 38 | image: golang, 39 | pull: 'always', 40 | environment: { 41 | GO111MODULE: 'on', 42 | }, 43 | commands: [ 44 | 'go test -cover ./...', 45 | ], 46 | volumes: volumes, 47 | }, 48 | ], 49 | trigger: { 50 | ref: [ 51 | 'refs/heads/master', 52 | 'refs/tags/**', 53 | 'refs/pull/**', 54 | ], 55 | }, 56 | volumes: [{name: 'gopath', temp: {}}] 57 | }, 58 | 59 | build(name, os='linux', arch='amd64', version=''):: 60 | local is_windows = windows(os); 61 | local tag = if is_windows then os + '-' + version else os + '-' + arch; 62 | local file_suffix = std.strReplace(tag, '-', '.'); 63 | local volumes = if is_windows then [{ name: windows_pipe_volume, path: windows_pipe }] else []; 64 | local golang = golang_image(os, version); 65 | local docker_name = 'drone/' + std.splitLimit(name, '-', 1)[1]; 66 | local extension = if is_windows then '.exe' else ''; 67 | { 68 | kind: 'pipeline', 69 | name: tag, 70 | platform: { 71 | os: os, 72 | arch: arch, 73 | version: if std.length(version) > 0 then version, 74 | }, 75 | steps: [ 76 | { 77 | name: 'build', 78 | image: golang, 79 | pull: 'always', 80 | environment: { 81 | CGO_ENABLED: '0', 82 | GO111MODULE: 'on', 83 | }, 84 | commands: [ 85 | 'go build -v -a -tags netgo -o release/' + os + '/' + arch + '/' + name + extension + ' ./cmd/' + name, 86 | ], 87 | }, 88 | { 89 | name: 'dryrun', 90 | image: 'plugins/docker:' + tag, 91 | pull: 'always', 92 | settings: { 93 | dry_run: true, 94 | tags: tag, 95 | dockerfile: 'docker/Dockerfile.' + file_suffix, 96 | daemon_off: if is_windows then 'true' else 'false', 97 | repo: docker_name, 98 | username: { from_secret: 'docker_username' }, 99 | password: { from_secret: 'docker_password' }, 100 | }, 101 | volumes: if std.length(volumes) > 0 then volumes, 102 | when: { 103 | event: ['pull_request'], 104 | }, 105 | }, 106 | { 107 | name: 'publish', 108 | image: 'plugins/docker:' + tag, 109 | pull: 'always', 110 | settings: { 111 | auto_tag: true, 112 | auto_tag_suffix: tag, 113 | daemon_off: if is_windows then 'true' else 'false', 114 | dockerfile: 'docker/Dockerfile.' + file_suffix, 115 | repo: docker_name, 116 | username: { from_secret: 'docker_username' }, 117 | password: { from_secret: 'docker_password' }, 118 | }, 119 | volumes: if std.length(volumes) > 0 then volumes, 120 | when: { 121 | event: { 122 | exclude: ['pull_request'], 123 | }, 124 | }, 125 | }, 126 | { 127 | name: 'tarball', 128 | image: golang, 129 | pull: 'always', 130 | commands: [ 131 | 'tar -cvzf release/' + name + '_' + os + '_' + arch + '.tar.gz -C release/' + os + '/' + arch + ' ' + name, 132 | 'sha256sum release/' + name + '_' + os + '_' + arch + '.tar.gz > release/' + name + '_' + os + '_' + arch + '.tar.gz.sha256' 133 | ], 134 | when: { 135 | event: ['tag'], 136 | }, 137 | }, 138 | { 139 | name: 'gpgsign', 140 | image: 'plugins/gpgsign', 141 | pull: 'always', 142 | settings: { 143 | files: [ 144 | 'release/*.tar.gz', 145 | 'release/*.tar.gz.sha256', 146 | ], 147 | key: { from_secret: 'gpgsign_key' }, 148 | passphrase: { from_secret: 'gpgkey_passphrase' }, 149 | }, 150 | when: { 151 | event: ['tag'], 152 | }, 153 | }, 154 | { 155 | name: 'github', 156 | image: 'plugins/github-release', 157 | pull: 'always', 158 | settings: { 159 | files: [ 160 | 'release/*.tar.gz', 161 | 'release/*.tar.gz.sha256', 162 | 'release/*.tar.gz.asc', 163 | ], 164 | token: { from_secret: 'github_token' }, 165 | }, 166 | when: { 167 | event: ['tag'], 168 | }, 169 | }, 170 | ], 171 | trigger: { 172 | ref: [ 173 | 'refs/heads/master', 174 | 'refs/tags/**', 175 | 'refs/pull/**', 176 | ], 177 | }, 178 | depends_on: [test_pipeline_name], 179 | volumes: if is_windows then [{ name: windows_pipe_volume, host: { path: windows_pipe } }], 180 | }, 181 | 182 | notifications(os='linux', arch='amd64', version='', depends_on=[]):: 183 | { 184 | kind: 'pipeline', 185 | name: 'notifications', 186 | platform: { 187 | os: os, 188 | arch: arch, 189 | version: if std.length(version) > 0 then version, 190 | }, 191 | steps: [ 192 | { 193 | name: 'manifest', 194 | image: 'plugins/manifest', 195 | pull: 'always', 196 | settings: { 197 | username: { from_secret: 'docker_username' }, 198 | password: { from_secret: 'docker_password' }, 199 | spec: 'docker/manifest.tmpl', 200 | ignore_missing: true, 201 | }, 202 | }, 203 | ], 204 | trigger: { 205 | ref: [ 206 | 'refs/heads/master', 207 | 'refs/tags/**', 208 | ], 209 | }, 210 | depends_on: depends_on, 211 | }, 212 | } 213 | -------------------------------------------------------------------------------- /plugin/match.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Drone.IO Inc. All rights reserved. 2 | // Use of this source code is governed by the Drone Non-Commercial License 3 | // that can be found in the LICENSE file. 4 | 5 | package plugin 6 | 7 | import ( 8 | "path" 9 | "strings" 10 | ) 11 | 12 | func match(name string, patterns []string) bool { 13 | if len(patterns) == 0 { 14 | return true 15 | } 16 | name = strings.ToLower(name) 17 | for _, pattern := range patterns { 18 | pattern = strings.ToLower(pattern) 19 | match, _ := path.Match(pattern, name) 20 | if match { 21 | return true 22 | } 23 | } 24 | return false 25 | } 26 | -------------------------------------------------------------------------------- /plugin/match_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Drone.IO Inc. All rights reserved. 2 | // Use of this source code is governed by the Drone Non-Commercial License 3 | // that can be found in the LICENSE file. 4 | 5 | package plugin 6 | 7 | import "testing" 8 | 9 | func TestMatch(t *testing.T) { 10 | tests := []struct { 11 | name string 12 | patterns []string 13 | match bool 14 | }{ 15 | // direct match 16 | { 17 | name: "octocat/Spoon-Fork", 18 | patterns: []string{"octocat/Spoon-Fork"}, 19 | match: true, 20 | }, 21 | // wildcard match 22 | { 23 | name: "octocat/Spoon-Fork", 24 | patterns: []string{"octocat/*"}, 25 | match: true, 26 | }, 27 | // wildcard match 28 | { 29 | name: "octocat/Spoon-Fork", 30 | patterns: []string{"github/*", "octocat/*"}, 31 | match: true, 32 | }, 33 | // wildcard match, case-insensitive 34 | { 35 | name: "OCTOCAT/HELLO-WORLD", 36 | patterns: []string{"octocat/HELLO-world"}, 37 | match: true, 38 | }, 39 | // match when no filter 40 | { 41 | name: "octocat/Spoon-Fork", 42 | patterns: []string{}, 43 | match: true, 44 | }, 45 | // no wildcard match 46 | { 47 | name: "octocat/Spoon-Fork", 48 | patterns: []string{"github/*"}, 49 | match: false, 50 | }, 51 | // no direct match 52 | { 53 | name: "octocat/Spoon-Fork", 54 | patterns: []string{"octocat/Hello-World"}, 55 | match: false, 56 | }, 57 | } 58 | 59 | for _, test := range tests { 60 | got, want := match(test.name, test.patterns), test.match 61 | if got != want { 62 | t.Errorf("Want matched %v, got %v", want, got) 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /plugin/plugin.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Drone.IO Inc. All rights reserved. 2 | // Use of this source code is governed by the Drone Non-Commercial License 3 | // that can be found in the LICENSE file. 4 | 5 | package plugin 6 | 7 | import ( 8 | "context" 9 | "errors" 10 | 11 | "github.com/drone/drone-go/drone" 12 | "github.com/drone/drone-go/plugin/secret" 13 | 14 | "github.com/ericchiang/k8s" 15 | "github.com/ericchiang/k8s/apis/core/v1" 16 | ) 17 | 18 | // New returns a new secret plugin that sources secrets 19 | // from the Kubernetes secrets manager. 20 | func New(client *k8s.Client, namespace string) secret.Plugin { 21 | return &plugin{ 22 | namespace: namespace, 23 | client: client, 24 | } 25 | } 26 | 27 | type plugin struct { 28 | client *k8s.Client 29 | namespace string 30 | } 31 | 32 | func (p *plugin) Find(ctx context.Context, req *secret.Request) (*drone.Secret, error) { 33 | if req.Path == "" { 34 | return nil, errors.New("invalid or missing secret path") 35 | } 36 | if req.Name == "" { 37 | return nil, errors.New("invalid or missing secret name") 38 | } 39 | 40 | path := req.Path 41 | name := req.Name 42 | 43 | // makes an api call to the kubernetes secrets manager and 44 | // attempts to retrieve the secret at the requested path. 45 | var secret v1.Secret 46 | err := p.client.Get(ctx, p.namespace, path, &secret) 47 | if err != nil { 48 | return nil, err 49 | } 50 | data, ok := secret.Data[name] 51 | if !ok { 52 | return nil, errors.New("secret not found") 53 | } 54 | 55 | // the user can filter out requets based on event type 56 | // using the X-Drone-Events secret key. Check for this 57 | // user-defined filter logic. 58 | events := extractEvents(secret.Metadata.Annotations) 59 | if !match(req.Build.Event, events) { 60 | return nil, errors.New("access denied: event does not match") 61 | } 62 | 63 | // the user can filter out requets based on repository 64 | // using the X-Drone-Repos secret key. Check for this 65 | // user-defined filter logic. 66 | repos := extractRepos(secret.Metadata.Annotations) 67 | if !match(req.Repo.Slug, repos) { 68 | return nil, errors.New("access denied: repository does not match") 69 | } 70 | 71 | return &drone.Secret{ 72 | Name: name, 73 | Data: string(data), 74 | Pull: true, // always true. use X-Drone-Events to prevent pull requests. 75 | Fork: true, // always true. use X-Drone-Events to prevent pull requests. 76 | }, nil 77 | } 78 | -------------------------------------------------------------------------------- /plugin/plugin_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Drone.IO Inc. All rights reserved. 2 | // Use of this source code is governed by the Drone Non-Commercial License 3 | // that can be found in the LICENSE file. 4 | 5 | package plugin 6 | 7 | import ( 8 | "context" 9 | "testing" 10 | 11 | "github.com/drone/drone-go/drone" 12 | "github.com/drone/drone-go/plugin/secret" 13 | 14 | "github.com/ericchiang/k8s" 15 | 16 | "github.com/google/go-cmp/cmp" 17 | "github.com/h2non/gock" 18 | ) 19 | 20 | var noContext = context.Background() 21 | 22 | func TestPlugin(t *testing.T) { 23 | defer gock.Off() 24 | 25 | client := &k8s.Client{ 26 | Endpoint: "http://localhost", 27 | Namespace: "default", 28 | } 29 | 30 | gock.New("http://localhost"). 31 | Reply(200). 32 | AddHeader("Content-Type", "application/vnd.kubernetes.protobuf"). 33 | File("testdata/secret.protobuf") 34 | 35 | req := &secret.Request{ 36 | Name: "username", 37 | Path: "docker", 38 | Build: drone.Build{ 39 | Event: "push", 40 | }, 41 | Repo: drone.Repo{ 42 | Slug: "octocat/hello-world", 43 | }, 44 | } 45 | plugin := New(client, client.Namespace) 46 | got, err := plugin.Find(noContext, req) 47 | if err != nil { 48 | t.Error(err) 49 | return 50 | } 51 | 52 | want := &drone.Secret{ 53 | Name: "username", 54 | Data: "admin", 55 | Pull: true, 56 | Fork: true, 57 | } 58 | if diff := cmp.Diff(got, want); diff != "" { 59 | t.Errorf(diff) 60 | return 61 | } 62 | 63 | if gock.IsPending() { 64 | t.Errorf("Unfinished requests") 65 | return 66 | } 67 | } 68 | 69 | func TestPlugin_FilterRepo(t *testing.T) { 70 | defer gock.Off() 71 | 72 | client := &k8s.Client{ 73 | Endpoint: "http://localhost", 74 | Namespace: "default", 75 | } 76 | 77 | gock.New("http://localhost"). 78 | Reply(200). 79 | AddHeader("Content-Type", "application/vnd.kubernetes.protobuf"). 80 | File("testdata/secret.protobuf") 81 | 82 | req := &secret.Request{ 83 | Name: "username", 84 | Path: "docker", 85 | Build: drone.Build{ 86 | Event: "push", 87 | }, 88 | Repo: drone.Repo{ 89 | Slug: "spaceghost/hello-world", 90 | }, 91 | } 92 | plugin := New(client, client.Namespace) 93 | _, err := plugin.Find(noContext, req) 94 | if err == nil { 95 | t.Errorf("Expect error") 96 | return 97 | } 98 | if want, got := err.Error(), "access denied: repository does not match"; got != want { 99 | t.Errorf("Want error %q, got %q", want, got) 100 | return 101 | } 102 | 103 | if gock.IsPending() { 104 | t.Errorf("Unfinished requests") 105 | return 106 | } 107 | } 108 | 109 | func TestPlugin_FilterEvent(t *testing.T) { 110 | defer gock.Off() 111 | 112 | client := &k8s.Client{ 113 | Endpoint: "http://localhost", 114 | Namespace: "default", 115 | } 116 | 117 | gock.New("http://localhost"). 118 | Reply(200). 119 | AddHeader("Content-Type", "application/vnd.kubernetes.protobuf"). 120 | File("testdata/secret.protobuf") 121 | 122 | req := &secret.Request{ 123 | Name: "username", 124 | Path: "docker", 125 | Build: drone.Build{ 126 | Event: "pull_request", 127 | }, 128 | Repo: drone.Repo{ 129 | Slug: "octocat/hello-world", 130 | }, 131 | } 132 | plugin := New(client, client.Namespace) 133 | _, err := plugin.Find(noContext, req) 134 | if err == nil { 135 | t.Errorf("Expect error") 136 | return 137 | } 138 | if want, got := err.Error(), "access denied: event does not match"; got != want { 139 | t.Errorf("Want error %q, got %q", want, got) 140 | return 141 | } 142 | 143 | if gock.IsPending() { 144 | t.Errorf("Unfinished requests") 145 | return 146 | } 147 | } 148 | 149 | func TestPlugin_MissingPath(t *testing.T) { 150 | req := &secret.Request{ 151 | Name: "password", 152 | } 153 | _, err := New(nil, "default").Find(noContext, req) 154 | if err == nil { 155 | t.Errorf("Expect invalid path error") 156 | return 157 | } 158 | if got, want := err.Error(), "invalid or missing secret path"; got != want { 159 | t.Errorf("Want error message %s, got %s", want, got) 160 | } 161 | } 162 | 163 | func TestPlugin_MissingName(t *testing.T) { 164 | req := &secret.Request{ 165 | Path: "docker", 166 | } 167 | _, err := New(nil, "default").Find(noContext, req) 168 | if err == nil { 169 | t.Errorf("Expect invalid path error") 170 | return 171 | } 172 | if got, want := err.Error(), "invalid or missing secret name"; got != want { 173 | t.Errorf("Want error message %s, got %s", want, got) 174 | } 175 | } 176 | 177 | func TestPlugin_NotFound(t *testing.T) { 178 | defer gock.Off() 179 | 180 | client := &k8s.Client{ 181 | Endpoint: "http://localhost", 182 | Namespace: "default", 183 | } 184 | 185 | gock.New("http://localhost"). 186 | Reply(404). 187 | AddHeader("Content-Type", "application/vnd.kubernetes.protobuf"). 188 | File("testdata/error.protobuf") 189 | 190 | req := &secret.Request{ 191 | Name: "username", 192 | Path: "docker", 193 | Build: drone.Build{ 194 | Event: "push", 195 | }, 196 | Repo: drone.Repo{ 197 | Slug: "octocat/hello-world", 198 | }, 199 | } 200 | plugin := New(client, client.Namespace) 201 | _, err := plugin.Find(noContext, req) 202 | if _, ok := err.(*k8s.APIError); !ok { 203 | t.Errorf("Expect APIError") 204 | return 205 | } 206 | 207 | if gock.IsPending() { 208 | t.Errorf("Unfinished requests") 209 | return 210 | } 211 | } 212 | 213 | func TestPlugin_InvalidAttribute(t *testing.T) { 214 | defer gock.Off() 215 | 216 | client := &k8s.Client{ 217 | Endpoint: "http://localhost", 218 | Namespace: "default", 219 | } 220 | 221 | gock.New("http://localhost"). 222 | Reply(200). 223 | AddHeader("Content-Type", "application/vnd.kubernetes.protobuf"). 224 | File("testdata/secret.protobuf") 225 | 226 | req := &secret.Request{ 227 | Name: "token", 228 | Path: "docker", 229 | Build: drone.Build{ 230 | Event: "push", 231 | }, 232 | Repo: drone.Repo{ 233 | Slug: "octocat/hello-world", 234 | }, 235 | } 236 | plugin := New(client, client.Namespace) 237 | _, err := plugin.Find(noContext, req) 238 | if err == nil { 239 | t.Errorf("Expect secret not found error") 240 | return 241 | } 242 | if got, want := err.Error(), "secret not found"; got != want { 243 | t.Errorf("Want error message %s, got %s", want, got) 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /plugin/testdata/error.protobuf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harness/drone-kubernetes-secrets/efe5d614f68a12cc8c44b7a9e5d48d3f42f0a2d1/plugin/testdata/error.protobuf -------------------------------------------------------------------------------- /plugin/testdata/secret.protobuf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/harness/drone-kubernetes-secrets/efe5d614f68a12cc8c44b7a9e5d48d3f42f0a2d1/plugin/testdata/secret.protobuf -------------------------------------------------------------------------------- /plugin/util.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Drone.IO Inc. All rights reserved. 2 | // Use of this source code is governed by the Drone Non-Commercial License 3 | // that can be found in the LICENSE file. 4 | 5 | package plugin 6 | 7 | import "strings" 8 | 9 | // helper function extracts the repository filters from the 10 | // secret payload in key value format. 11 | func extractRepos(params map[string]string) []string { 12 | for key, value := range params { 13 | if strings.EqualFold(key, "X-Drone-Repos") { 14 | return parseCommaSeparated(value) 15 | } 16 | } 17 | return nil 18 | } 19 | 20 | // helper function extracts the event filters from the 21 | // secret payload in key value format. 22 | func extractEvents(params map[string]string) []string { 23 | for key, value := range params { 24 | if strings.EqualFold(key, "X-Drone-Events") { 25 | return parseCommaSeparated(value) 26 | } 27 | } 28 | return nil 29 | } 30 | 31 | func parseCommaSeparated(s string) []string { 32 | parts := strings.Split(s, ",") 33 | if len(parts) == 1 && parts[0] == "" { 34 | return nil 35 | } 36 | return parts 37 | } 38 | -------------------------------------------------------------------------------- /plugin/util_test.go: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Drone.IO Inc. All rights reserved. 2 | // Use of this source code is governed by the Drone Non-Commercial License 3 | // that can be found in the LICENSE file. 4 | 5 | package plugin 6 | 7 | import ( 8 | "reflect" 9 | "testing" 10 | ) 11 | 12 | func TestExtractRepos(t *testing.T) { 13 | tests := []struct { 14 | params map[string]string 15 | patterns []string 16 | }{ 17 | { 18 | params: map[string]string{"X-Drone-Repos": ""}, 19 | patterns: nil, 20 | }, 21 | { 22 | params: map[string]string{"X-Drone-Repos": "octocat/Spoon-Fork"}, 23 | patterns: []string{"octocat/Spoon-Fork"}, 24 | }, 25 | { 26 | params: map[string]string{"X-Drone-Repos": "octocat/Spoon-Fork,octocat/Hello-World"}, 27 | patterns: []string{"octocat/Spoon-Fork", "octocat/Hello-World"}, 28 | }, 29 | { 30 | params: map[string]string{"x-drone-repos": "octocat/Spoon-Fork,octocat/Hello-World"}, 31 | patterns: []string{"octocat/Spoon-Fork", "octocat/Hello-World"}, 32 | }, 33 | { 34 | params: map[string]string{"foo": "bar"}, 35 | patterns: nil, 36 | }, 37 | } 38 | 39 | for i, test := range tests { 40 | got, want := extractRepos(test.params), test.patterns 41 | if !reflect.DeepEqual(got, want) { 42 | t.Errorf("Unexpected results at %d", i) 43 | } 44 | } 45 | } 46 | 47 | func TestExtractEvents(t *testing.T) { 48 | tests := []struct { 49 | params map[string]string 50 | patterns []string 51 | }{ 52 | { 53 | params: map[string]string{"X-Drone-Events": ""}, 54 | patterns: nil, 55 | }, 56 | { 57 | params: map[string]string{"X-Drone-Events": "push"}, 58 | patterns: []string{"push"}, 59 | }, 60 | { 61 | params: map[string]string{"X-Drone-Events": "push,tag"}, 62 | patterns: []string{"push", "tag"}, 63 | }, 64 | { 65 | params: map[string]string{"x-drone-events": "push,tag"}, 66 | patterns: []string{"push", "tag"}, 67 | }, 68 | { 69 | params: map[string]string{"foo": "bar"}, 70 | patterns: nil, 71 | }, 72 | } 73 | 74 | for i, test := range tests { 75 | got, want := extractEvents(test.params), test.patterns 76 | if !reflect.DeepEqual(got, want) { 77 | t.Errorf("Unexpected results at %d", i) 78 | } 79 | } 80 | } 81 | --------------------------------------------------------------------------------