├── .dockerignore
├── .drone.jsonnet
├── .drone.windows.jsonnet
├── .drone.windows.yml
├── .drone.yml
├── .github
├── issue_template.md
├── pull_request_template.md
└── settings.yml
├── .gitignore
├── README.md
├── client
├── client.go
├── interface.go
└── types.go
├── cmd_cobertura.go
├── cmd_gocov.go
├── cmd_lcov.go
├── cmd_publish.go
├── cmd_publish_test.go
├── coverage
├── cobertura
│ ├── cobertura.go
│ └── cobertura_test.go
├── coverage.go
├── coverage_test.go
├── gocov
│ ├── gocov.go
│ └── gocov_test.go
├── jacoco
│ └── jacoco.go
├── lcov
│ ├── lcov.go
│ └── lcov_test.go
├── path.go
└── path_test.go
├── docker
├── Dockerfile.linux.amd64
├── Dockerfile.linux.arm
├── Dockerfile.linux.arm64
├── Dockerfile.windows.1803
├── Dockerfile.windows.1809
└── manifest.tmpl
├── go.mod
├── go.sum
├── main.go
├── merge.go
└── pipeline.libsonnet
/.dockerignore:
--------------------------------------------------------------------------------
1 | *
2 | !release/
3 |
--------------------------------------------------------------------------------
/.drone.jsonnet:
--------------------------------------------------------------------------------
1 | local pipeline = import 'pipeline.libsonnet';
2 | local name = 'drone-coverage';
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.windows.jsonnet:
--------------------------------------------------------------------------------
1 | local pipeline = import 'pipeline.libsonnet';
2 | local name = 'drone-coverage';
3 |
4 | [
5 | pipeline.test('windows', 'amd64', '1803'),
6 | pipeline.build(name, 'windows', 'amd64', '1803'),
7 | pipeline.build(name, 'windows', 'amd64', '1809'),
8 | pipeline.notifications('windows', 'amd64', '1809', ['windows-1803', 'windows-1809']),
9 | ]
10 |
--------------------------------------------------------------------------------
/.drone.windows.yml:
--------------------------------------------------------------------------------
1 | ---
2 | kind: pipeline
3 | name: testing
4 |
5 | platform:
6 | os: windows
7 | arch: amd64
8 | version: 1803
9 |
10 | steps:
11 | - name: vet
12 | pull: always
13 | image: golang:1.11-windowsservercore-1803
14 | commands:
15 | - go vet ./...
16 | environment:
17 | GO111MODULE: on
18 | volumes:
19 | - name: gopath
20 | path: C:\\gopath
21 |
22 | - name: test
23 | pull: always
24 | image: golang:1.11-windowsservercore-1803
25 | commands:
26 | - go test -cover ./...
27 | environment:
28 | GO111MODULE: on
29 | volumes:
30 | - name: gopath
31 | path: C:\\gopath
32 |
33 | volumes:
34 | - name: gopath
35 | temp: {}
36 |
37 | trigger:
38 | ref:
39 | - refs/heads/master
40 | - "refs/tags/**"
41 | - "refs/pull/**"
42 |
43 | ---
44 | kind: pipeline
45 | name: windows-1803
46 |
47 | platform:
48 | os: windows
49 | arch: amd64
50 | version: 1803
51 |
52 | steps:
53 | - name: build-push
54 | pull: always
55 | image: golang:1.11-windowsservercore-1803
56 | commands:
57 | - "go build -v -ldflags \"-X main.version=${DRONE_COMMIT_SHA:0:8}\" -a -tags netgo -o release/windows/amd64/drone-coverage.exe"
58 | environment:
59 | CGO_ENABLED: 0
60 | GO111MODULE: on
61 | when:
62 | event:
63 | exclude:
64 | - tag
65 |
66 | - name: build-tag
67 | pull: always
68 | image: golang:1.11-windowsservercore-1803
69 | commands:
70 | - "go build -v -ldflags \"-X main.version=${DRONE_TAG##v}\" -a -tags netgo -o release/windows/amd64/drone-coverage.exe"
71 | environment:
72 | CGO_ENABLED: 0
73 | GO111MODULE: on
74 | when:
75 | event:
76 | - tag
77 |
78 | - name: executable
79 | pull: always
80 | image: golang:1.11-windowsservercore-1803
81 | commands:
82 | - ./release/windows/amd64/drone-coverage.exe --help
83 |
84 | - name: dryrun
85 | pull: always
86 | image: plugins/docker:windows-1803
87 | settings:
88 | daemon_off: true
89 | dockerfile: docker/Dockerfile.windows.1803
90 | dry_run: true
91 | password:
92 | from_secret: docker_password
93 | repo: plugins/coverage
94 | tags: windows-1803
95 | username:
96 | from_secret: docker_username
97 | volumes:
98 | - name: docker_pipe
99 | path: \\\\.\\pipe\\docker_engine
100 | when:
101 | event:
102 | - pull_request
103 |
104 | - name: publish
105 | pull: always
106 | image: plugins/docker:windows-1803
107 | settings:
108 | auto_tag: true
109 | auto_tag_suffix: windows-1803
110 | daemon_off: true
111 | dockerfile: docker/Dockerfile.windows.1803
112 | password:
113 | from_secret: docker_password
114 | repo: plugins/coverage
115 | username:
116 | from_secret: docker_username
117 | volumes:
118 | - name: docker_pipe
119 | path: \\\\.\\pipe\\docker_engine
120 | when:
121 | event:
122 | exclude:
123 | - pull_request
124 |
125 | volumes:
126 | - name: docker_pipe
127 | host:
128 | path: \\\\.\\pipe\\docker_engine
129 |
130 | trigger:
131 | ref:
132 | - refs/heads/master
133 | - "refs/tags/**"
134 | - "refs/pull/**"
135 |
136 | depends_on:
137 | - testing
138 |
139 | ---
140 | kind: pipeline
141 | name: windows-1809
142 |
143 | platform:
144 | os: windows
145 | arch: amd64
146 | version: 1809
147 |
148 | steps:
149 | - name: build-push
150 | pull: always
151 | image: golang:1.11-windowsservercore-1809
152 | commands:
153 | - "go build -v -ldflags \"-X main.version=${DRONE_COMMIT_SHA:0:8}\" -a -tags netgo -o release/windows/amd64/drone-coverage.exe"
154 | environment:
155 | CGO_ENABLED: 0
156 | GO111MODULE: on
157 | when:
158 | event:
159 | exclude:
160 | - tag
161 |
162 | - name: build-tag
163 | pull: always
164 | image: golang:1.11-windowsservercore-1809
165 | commands:
166 | - "go build -v -ldflags \"-X main.version=${DRONE_TAG##v}\" -a -tags netgo -o release/windows/amd64/drone-coverage.exe"
167 | environment:
168 | CGO_ENABLED: 0
169 | GO111MODULE: on
170 | when:
171 | event:
172 | - tag
173 |
174 | - name: executable
175 | pull: always
176 | image: golang:1.11-windowsservercore-1809
177 | commands:
178 | - ./release/windows/amd64/drone-coverage.exe --help
179 |
180 | - name: dryrun
181 | pull: always
182 | image: plugins/docker:windows-1809
183 | settings:
184 | daemon_off: true
185 | dockerfile: docker/Dockerfile.windows.1809
186 | dry_run: true
187 | password:
188 | from_secret: docker_password
189 | repo: plugins/coverage
190 | tags: windows-1809
191 | username:
192 | from_secret: docker_username
193 | volumes:
194 | - name: docker_pipe
195 | path: \\\\.\\pipe\\docker_engine
196 | when:
197 | event:
198 | - pull_request
199 |
200 | - name: publish
201 | pull: always
202 | image: plugins/docker:windows-1809
203 | settings:
204 | auto_tag: true
205 | auto_tag_suffix: windows-1809
206 | daemon_off: true
207 | dockerfile: docker/Dockerfile.windows.1809
208 | password:
209 | from_secret: docker_password
210 | repo: plugins/coverage
211 | username:
212 | from_secret: docker_username
213 | volumes:
214 | - name: docker_pipe
215 | path: \\\\.\\pipe\\docker_engine
216 | when:
217 | event:
218 | exclude:
219 | - pull_request
220 |
221 | volumes:
222 | - name: docker_pipe
223 | host:
224 | path: \\\\.\\pipe\\docker_engine
225 |
226 | trigger:
227 | ref:
228 | - refs/heads/master
229 | - "refs/tags/**"
230 | - "refs/pull/**"
231 |
232 | depends_on:
233 | - testing
234 |
235 | ---
236 | kind: pipeline
237 | name: notifications
238 |
239 | platform:
240 | os: windows
241 | arch: amd64
242 | version: 1809
243 |
244 | steps:
245 | - name: manifest
246 | pull: always
247 | image: plugins/manifest
248 | settings:
249 | auto_tag: true
250 | ignore_missing: true
251 | password:
252 | from_secret: docker_password
253 | spec: docker/manifest.tmpl
254 | username:
255 | from_secret: docker_username
256 |
257 | - name: microbadger
258 | pull: always
259 | image: plugins/webhook
260 | settings:
261 | urls:
262 | from_secret: microbadger_url
263 |
264 | trigger:
265 | ref:
266 | - refs/heads/master
267 | - "refs/tags/**"
268 |
269 | depends_on:
270 | - windows-1803
271 | - windows-1809
272 |
273 | ...
274 |
--------------------------------------------------------------------------------
/.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.11
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.11
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-push
52 | pull: always
53 | image: golang:1.11
54 | commands:
55 | - "go build -v -ldflags \"-X main.version=${DRONE_COMMIT_SHA:0:8}\" -a -tags netgo -o release/linux/amd64/drone-coverage"
56 | environment:
57 | CGO_ENABLED: 0
58 | GO111MODULE: on
59 | when:
60 | event:
61 | exclude:
62 | - tag
63 |
64 | - name: build-tag
65 | pull: always
66 | image: golang:1.11
67 | commands:
68 | - "go build -v -ldflags \"-X main.version=${DRONE_TAG##v}\" -a -tags netgo -o release/linux/amd64/drone-coverage"
69 | environment:
70 | CGO_ENABLED: 0
71 | GO111MODULE: on
72 | when:
73 | event:
74 | - tag
75 |
76 | - name: executable
77 | pull: always
78 | image: golang:1.11
79 | commands:
80 | - ./release/linux/amd64/drone-coverage --help
81 |
82 | - name: dryrun
83 | pull: always
84 | image: plugins/docker:linux-amd64
85 | settings:
86 | daemon_off: false
87 | dockerfile: docker/Dockerfile.linux.amd64
88 | dry_run: true
89 | password:
90 | from_secret: docker_password
91 | repo: plugins/coverage
92 | tags: linux-amd64
93 | username:
94 | from_secret: docker_username
95 | when:
96 | event:
97 | - pull_request
98 |
99 | - name: publish
100 | pull: always
101 | image: plugins/docker:linux-amd64
102 | settings:
103 | auto_tag: true
104 | auto_tag_suffix: linux-amd64
105 | daemon_off: false
106 | dockerfile: docker/Dockerfile.linux.amd64
107 | password:
108 | from_secret: docker_password
109 | repo: plugins/coverage
110 | username:
111 | from_secret: docker_username
112 | when:
113 | event:
114 | exclude:
115 | - pull_request
116 |
117 | trigger:
118 | ref:
119 | - refs/heads/master
120 | - "refs/tags/**"
121 | - "refs/pull/**"
122 |
123 | depends_on:
124 | - testing
125 |
126 | ---
127 | kind: pipeline
128 | name: linux-arm64
129 |
130 | platform:
131 | os: linux
132 | arch: arm64
133 |
134 | steps:
135 | - name: build-push
136 | pull: always
137 | image: golang:1.11
138 | commands:
139 | - "go build -v -ldflags \"-X main.version=${DRONE_COMMIT_SHA:0:8}\" -a -tags netgo -o release/linux/arm64/drone-coverage"
140 | environment:
141 | CGO_ENABLED: 0
142 | GO111MODULE: on
143 | when:
144 | event:
145 | exclude:
146 | - tag
147 |
148 | - name: build-tag
149 | pull: always
150 | image: golang:1.11
151 | commands:
152 | - "go build -v -ldflags \"-X main.version=${DRONE_TAG##v}\" -a -tags netgo -o release/linux/arm64/drone-coverage"
153 | environment:
154 | CGO_ENABLED: 0
155 | GO111MODULE: on
156 | when:
157 | event:
158 | - tag
159 |
160 | - name: executable
161 | pull: always
162 | image: golang:1.11
163 | commands:
164 | - ./release/linux/arm64/drone-coverage --help
165 |
166 | - name: dryrun
167 | pull: always
168 | image: plugins/docker:linux-arm64
169 | settings:
170 | daemon_off: false
171 | dockerfile: docker/Dockerfile.linux.arm64
172 | dry_run: true
173 | password:
174 | from_secret: docker_password
175 | repo: plugins/coverage
176 | tags: linux-arm64
177 | username:
178 | from_secret: docker_username
179 | when:
180 | event:
181 | - pull_request
182 |
183 | - name: publish
184 | pull: always
185 | image: plugins/docker:linux-arm64
186 | settings:
187 | auto_tag: true
188 | auto_tag_suffix: linux-arm64
189 | daemon_off: false
190 | dockerfile: docker/Dockerfile.linux.arm64
191 | password:
192 | from_secret: docker_password
193 | repo: plugins/coverage
194 | username:
195 | from_secret: docker_username
196 | when:
197 | event:
198 | exclude:
199 | - pull_request
200 |
201 | trigger:
202 | ref:
203 | - refs/heads/master
204 | - "refs/tags/**"
205 | - "refs/pull/**"
206 |
207 | depends_on:
208 | - testing
209 |
210 | ---
211 | kind: pipeline
212 | name: linux-arm
213 |
214 | platform:
215 | os: linux
216 | arch: arm
217 |
218 | steps:
219 | - name: build-push
220 | pull: always
221 | image: golang:1.11
222 | commands:
223 | - "go build -v -ldflags \"-X main.version=${DRONE_COMMIT_SHA:0:8}\" -a -tags netgo -o release/linux/arm/drone-coverage"
224 | environment:
225 | CGO_ENABLED: 0
226 | GO111MODULE: on
227 | when:
228 | event:
229 | exclude:
230 | - tag
231 |
232 | - name: build-tag
233 | pull: always
234 | image: golang:1.11
235 | commands:
236 | - "go build -v -ldflags \"-X main.version=${DRONE_TAG##v}\" -a -tags netgo -o release/linux/arm/drone-coverage"
237 | environment:
238 | CGO_ENABLED: 0
239 | GO111MODULE: on
240 | when:
241 | event:
242 | - tag
243 |
244 | - name: executable
245 | pull: always
246 | image: golang:1.11
247 | commands:
248 | - ./release/linux/arm/drone-coverage --help
249 |
250 | - name: dryrun
251 | pull: always
252 | image: plugins/docker:linux-arm
253 | settings:
254 | daemon_off: false
255 | dockerfile: docker/Dockerfile.linux.arm
256 | dry_run: true
257 | password:
258 | from_secret: docker_password
259 | repo: plugins/coverage
260 | tags: linux-arm
261 | username:
262 | from_secret: docker_username
263 | when:
264 | event:
265 | - pull_request
266 |
267 | - name: publish
268 | pull: always
269 | image: plugins/docker:linux-arm
270 | settings:
271 | auto_tag: true
272 | auto_tag_suffix: linux-arm
273 | daemon_off: false
274 | dockerfile: docker/Dockerfile.linux.arm
275 | password:
276 | from_secret: docker_password
277 | repo: plugins/coverage
278 | username:
279 | from_secret: docker_username
280 | when:
281 | event:
282 | exclude:
283 | - pull_request
284 |
285 | trigger:
286 | ref:
287 | - refs/heads/master
288 | - "refs/tags/**"
289 | - "refs/pull/**"
290 |
291 | depends_on:
292 | - testing
293 |
294 | ---
295 | kind: pipeline
296 | name: notifications
297 |
298 | platform:
299 | os: linux
300 | arch: amd64
301 |
302 | steps:
303 | - name: manifest
304 | pull: always
305 | image: plugins/manifest
306 | settings:
307 | auto_tag: true
308 | ignore_missing: true
309 | password:
310 | from_secret: docker_password
311 | spec: docker/manifest.tmpl
312 | username:
313 | from_secret: docker_username
314 |
315 | - name: microbadger
316 | pull: always
317 | image: plugins/webhook
318 | settings:
319 | urls:
320 | from_secret: microbadger_url
321 |
322 | trigger:
323 | ref:
324 | - refs/heads/master
325 | - "refs/tags/**"
326 |
327 | depends_on:
328 | - linux-amd64
329 | - linux-arm64
330 | - linux-arm
331 |
332 | ...
333 |
--------------------------------------------------------------------------------
/.github/issue_template.md:
--------------------------------------------------------------------------------
1 |
10 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drone-plugins/drone-coverage/56d79b9f3913c306413d8a3bbefe6d816d333e49/.github/pull_request_template.md
--------------------------------------------------------------------------------
/.github/settings.yml:
--------------------------------------------------------------------------------
1 | repository:
2 | name: drone-coverage
3 | description: Plugin for publishing coverage reports
4 | homepage: http://plugins.drone.io/drone-plugins/drone-coverage
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 | # 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 | drone-coverage
31 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # drone-coverage
2 |
3 | [](http://cloud.drone.io/drone-plugins/drone-coverage)
4 | [](https://gitter.im/drone/drone)
5 | [](https://discourse.drone.io)
6 | [](https://stackoverflow.com/questions/tagged/drone.io)
7 | [](https://microbadger.com/images/plugins/coverage "Get your own image badge on microbadger.com")
8 | [](http://godoc.org/github.com/drone-plugins/drone-coverage)
9 | [](https://goreportcard.com/report/github.com/drone-plugins/drone-coverage)
10 |
11 | > Warning: This plugin has not been migrated to Drone >= 0.5 yet, you are not able to use it with a current Drone version until somebody volunteers to update the plugin structure to the new format.
12 |
13 | Drone plugin for publishing coverage reports. 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-coverage/).
14 |
15 | ## Build
16 |
17 | Build the binary with the following command:
18 |
19 | ```console
20 | export GOOS=linux
21 | export GOARCH=amd64
22 | export CGO_ENABLED=0
23 | export GO111MODULE=on
24 |
25 | go build -v -a -tags netgo -o release/linux/amd64/drone-coverage
26 | ```
27 |
28 | ## Docker
29 |
30 | Build the Docker image with the following command:
31 |
32 | ```console
33 | docker build \
34 | --label org.label-schema.build-date=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \
35 | --label org.label-schema.vcs-ref=$(git rev-parse --short HEAD) \
36 | --file docker/Dockerfile.linux.amd64 --tag plugins/coverage .
37 | ```
38 |
39 | ## Usage
40 |
41 | ```sh
42 | docker run --rm \
43 | -e DRONE_REPO=octocat/hello-world \
44 | -e DRONE_COMMIT_SHA=7fd1a60b01f91b314f59955a4e4d4e80d8edf11d \
45 | -e DRONE_COMMIT_REF=refs/heads/master \
46 | -e DRONE_COMMIT_BRANCH=master \
47 | -e DRONE_COMMIT_AUTHOR=octocat \
48 | -e DRONE_BUILD_NUMBER=1 \
49 | -e DRONE_BUILD_EVENT=push \
50 | -e DRONE_BUILD_STATUS=success \
51 | -e DRONE_BUILD_LINK=http://github.com/octocat/hello-world \
52 | -e PLUGIN_PATTERN="path/to/lcov.info" \
53 | -e PLUGIN_SERVER="http://coverage.server.com" \
54 | -e GITHUB_TOKEN=3da541559918a808c2402b \
55 | -v $(pwd):$(pwd) \
56 | -w $(pwd) \
57 | plugins/coverage
58 | ```
59 |
--------------------------------------------------------------------------------
/client/client.go:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | import (
4 | "bytes"
5 | "crypto/tls"
6 | "encoding/json"
7 | "fmt"
8 | "io"
9 | "io/ioutil"
10 | "net/http"
11 | "net/url"
12 |
13 | "golang.org/x/oauth2"
14 | )
15 |
16 | const (
17 | pathLogin = "%s/login?access_token=%s"
18 | pathUser = "%s/api/user"
19 | pathRepos = "%s/api/user/repos"
20 | pathRepo = "%s/api/repos/%s"
21 | pathBuilds = "%s/api/repos/%s/builds"
22 | )
23 |
24 | type client struct {
25 | client *http.Client
26 | base string // base url
27 | }
28 |
29 | // NewClient returns a client at the specified url.
30 | func NewClient(uri string) Client {
31 | return &client{http.DefaultClient, uri}
32 | }
33 |
34 | func NewClientTLS(uri string, tlsConfig *tls.Config) Client {
35 | httpClient := http.DefaultClient
36 | if tlsConfig != nil {
37 | transport := &http.Transport{TLSClientConfig: tlsConfig}
38 | httpClient = &http.Client{Transport: transport}
39 | }
40 | return &client{httpClient, uri}
41 | }
42 |
43 | // NewClientToken returns a client at the specified url that
44 | // authenticates all outbound requests with the given token.
45 | func NewClientToken(uri, token string) Client {
46 | config := new(oauth2.Config)
47 | auther := config.Client(oauth2.NoContext, &oauth2.Token{AccessToken: token})
48 | return &client{auther, uri}
49 | }
50 |
51 | // NewClientTokenTLS returns a client at the specified url that
52 | // authenticates all outbound requests with the given token and
53 | // tls.Config if provided.
54 | func NewClientTokenTLS(uri, token string, tlsConfig *tls.Config) Client {
55 | config := new(oauth2.Config)
56 | auther := config.Client(oauth2.NoContext, &oauth2.Token{AccessToken: token})
57 | if tlsConfig != nil {
58 | auther.Transport.(*oauth2.Transport).Base = &http.Transport{TLSClientConfig: tlsConfig}
59 | }
60 | return &client{auther, uri}
61 | }
62 |
63 | func (c *client) Token(token string) (*Token, error) {
64 | out := new(Token)
65 | uri := fmt.Sprintf(pathLogin, c.base, token)
66 | err := c.post(uri, nil, out)
67 | return out, err
68 | }
69 |
70 | func (c *client) Repo(repo string) (*Repo, error) {
71 | out := new(Repo)
72 | uri := fmt.Sprintf(pathRepo, c.base, repo)
73 | err := c.get(uri, out)
74 | return out, err
75 | }
76 |
77 | func (c *client) Activate(repo string) (*Repo, error) {
78 | out := new(Repo)
79 | uri := fmt.Sprintf(pathRepo, c.base, repo)
80 | err := c.post(uri, nil, out)
81 | return out, err
82 | }
83 |
84 | func (c *client) Deactivate(repo string) error {
85 | uri := fmt.Sprintf(pathRepo, c.base, repo)
86 | err := c.delete(uri)
87 | return err
88 | }
89 |
90 | func (c *client) Submit(repo string, build *Build, report *Report) (*Build, error) {
91 | in := struct {
92 | Report *Report `json:"report"`
93 | Build *Build `json:"build"`
94 | }{report, build}
95 |
96 | out := new(Build)
97 | uri := fmt.Sprintf(pathBuilds, c.base, repo)
98 | err := c.post(uri, &in, out)
99 | return out, err
100 | }
101 |
102 | //
103 | // http request helper functions
104 | //
105 |
106 | // helper function for making an http GET request.
107 | func (c *client) get(rawurl string, out interface{}) error {
108 | return c.do(rawurl, "GET", nil, out)
109 | }
110 |
111 | // helper function for making an http POST request.
112 | func (c *client) post(rawurl string, in, out interface{}) error {
113 | return c.do(rawurl, "POST", in, out)
114 | }
115 |
116 | // helper function for making an http PUT request.
117 | func (c *client) put(rawurl string, in, out interface{}) error {
118 | return c.do(rawurl, "PUT", in, out)
119 | }
120 |
121 | // helper function for making an http PATCH request.
122 | func (c *client) patch(rawurl string, in, out interface{}) error {
123 | return c.do(rawurl, "PATCH", in, out)
124 | }
125 |
126 | // helper function for making an http DELETE request.
127 | func (c *client) delete(rawurl string) error {
128 | return c.do(rawurl, "DELETE", nil, nil)
129 | }
130 |
131 | // helper function to make an http request
132 | func (c *client) do(rawurl, method string, in, out interface{}) error {
133 | // executes the http request and returns the body as
134 | // and io.ReadCloser
135 | body, err := c.stream(rawurl, method, in, out)
136 | if err != nil {
137 | return err
138 | }
139 | defer body.Close()
140 |
141 | // if a json response is expected, parse and return
142 | // the json response.
143 | if out != nil {
144 | return json.NewDecoder(body).Decode(out)
145 | }
146 | return nil
147 | }
148 |
149 | // helper function to stream an http request
150 | func (c *client) stream(rawurl, method string, in, out interface{}) (io.ReadCloser, error) {
151 | uri, err := url.Parse(rawurl)
152 | if err != nil {
153 | return nil, err
154 | }
155 |
156 | // if we are posting or putting data, we need to
157 | // write it to the body of the request.
158 | var buf io.ReadWriter
159 | if in != nil {
160 | buf = new(bytes.Buffer)
161 | err := json.NewEncoder(buf).Encode(in)
162 | if err != nil {
163 | return nil, err
164 | }
165 | }
166 |
167 | // creates a new http request to bitbucket.
168 | req, err := http.NewRequest(method, uri.String(), buf)
169 | if err != nil {
170 | return nil, err
171 | }
172 | if in != nil {
173 | req.Header.Set("Content-Type", "application/json")
174 | }
175 |
176 | resp, err := c.client.Do(req)
177 | if err != nil {
178 | return nil, err
179 | }
180 | if resp.StatusCode > http.StatusPartialContent {
181 | defer resp.Body.Close()
182 | out, _ := ioutil.ReadAll(resp.Body)
183 | return nil, fmt.Errorf(string(out))
184 | }
185 | return resp.Body, nil
186 | }
187 |
--------------------------------------------------------------------------------
/client/interface.go:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | // Client to access the remote APIs.
4 | type Client interface {
5 | Token(string) (*Token, error)
6 | Repo(string) (*Repo, error)
7 | Activate(string) (*Repo, error)
8 | Deactivate(string) error
9 | Submit(string, *Build, *Report) (*Build, error)
10 | }
11 |
--------------------------------------------------------------------------------
/client/types.go:
--------------------------------------------------------------------------------
1 | package client
2 |
3 | // Token represents a user oauth2 token.
4 | type Token struct {
5 | Access string `json:"access_token"`
6 | Refresh string `json:"refresh_token"`
7 | Expires int64 `json:"expires_in"`
8 | }
9 |
10 | // User represents a registered user.
11 | type User struct {
12 | Login string `json:"login"`
13 | Avatar string `json:"avatar_url"`
14 | }
15 |
16 | // Repo represents a remote version control repository
17 | // for which coverage data has been collected.
18 | type Repo struct {
19 | Owner string `json:"owner"`
20 | Name string `json:"name"`
21 | Slug string `json:"slug"`
22 | Link string `json:"link"`
23 | Branch string `json:"branch"`
24 | Avatar string `json:"avatar_url"`
25 | Private bool `json:"private"`
26 | Coverage float64 `json:"coverage_percent"`
27 | Delta float64 `json:"coverage_changed"`
28 | Covered int64 `json:"lines_covered"`
29 | Lines int64 `json:"lines_total"`
30 | }
31 |
32 | // Build represents a build in an external continuous integration
33 | // server for which coverage data has been collected.
34 | type Build struct {
35 | Number int `json:"number"`
36 | Event string `json:"event"`
37 | Commit string `json:"commit"`
38 | Branch string `json:"branch"`
39 | Ref string `json:"ref"`
40 | Refspec string `json:"refspec"`
41 | Message string `json:"message"`
42 | Author string `json:"author"`
43 | Avatar string `json:"author_avatar"`
44 | Timestamp int64 `json:"timestamp"`
45 | Link string `json:"link_url"`
46 | Coverage float64 `json:"coverage_percent"`
47 | Changed float64 `json:"coverage_changed"`
48 | Covered int64 `json:"lines_covered"`
49 | Lines int64 `json:"lines_total"`
50 | }
51 |
52 | // File represents a source file from your repository that
53 | // includes coverage data per line.
54 | type File struct {
55 | FileName string `json:"filename"`
56 | Coverage float64 `json:"coverage_percent"`
57 | Changed float64 `json:"coverage_changed"`
58 | Covered int64 `json:"lines_covered"`
59 | Lines int64 `json:"lines_total"`
60 | Mode string `json:"coverage_mode"`
61 |
62 | Blocks []*Block `json:"blocks"`
63 | }
64 |
65 | // Block represents a block of code in a source code file
66 | // and includes coverage details.
67 | type Block struct {
68 | StartLine int `json:"start_line"`
69 | StartCol int `json:"start_col"`
70 | EndLine int `json:"end_line"`
71 | EndCol int `json:"end_col"`
72 | NumStmt int `json:"num_stmt"`
73 | Count int `json:"count"`
74 | }
75 |
76 | // Report represents a code coverage report.
77 | type Report struct {
78 | Coverage float64 `json:"coverage_percent"`
79 | Changed float64 `json:"coverage_changed"`
80 | Covered int64 `json:"lines_covered"`
81 | Lines int64 `json:"lines_total"`
82 | Files []*File `json:"files"`
83 | }
84 |
--------------------------------------------------------------------------------
/cmd_cobertura.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "os"
7 |
8 | "github.com/drone-plugins/drone-coverage/coverage/cobertura"
9 | "github.com/mattn/go-zglob"
10 | "github.com/urfave/cli"
11 | "golang.org/x/tools/cover"
12 | )
13 |
14 | // CoberturaCmd is the exported command for converting Cobertura files.
15 | var CoberturaCmd = cli.Command{
16 | Name: "cobertura",
17 | Usage: "parse cobertura files",
18 | Flags: []cli.Flag{},
19 | Action: func(c *cli.Context) error {
20 | return parseCobertura(c)
21 | },
22 | }
23 |
24 | func parseCobertura(c *cli.Context) error {
25 |
26 | pattern := c.Args().First()
27 | if pattern == "" {
28 | pattern = "**/coverage.xml"
29 | }
30 |
31 | matches, err := zglob.Glob(pattern)
32 | if err != nil {
33 | return err
34 | }
35 |
36 | parser := cobertura.New()
37 | var profiles []*cover.Profile
38 | for _, match := range matches {
39 | parsed, err := parser.ReadFile(match)
40 | if err != nil {
41 | return err
42 | }
43 | for _, p := range parsed {
44 | profiles = addProfile(profiles, p)
45 | }
46 | }
47 |
48 | // create the coverage payload that gets sent to the
49 | // coverage reporting server.
50 | report := profileToReport(profiles)
51 |
52 | out, err := json.MarshalIndent(report, " ", " ")
53 | if err != nil {
54 | return err
55 | }
56 | fmt.Fprintf(os.Stdout, "%s", out)
57 | return nil
58 | }
59 |
--------------------------------------------------------------------------------
/cmd_gocov.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "os"
7 |
8 | "github.com/drone-plugins/drone-coverage/coverage/gocov"
9 | "github.com/mattn/go-zglob"
10 | "github.com/urfave/cli"
11 | "golang.org/x/tools/cover"
12 | )
13 |
14 | // GocovCmd is the exported command for converting Go coverage files.
15 | var GocovCmd = cli.Command{
16 | Name: "gocov",
17 | Usage: "parse gocov files",
18 | Flags: []cli.Flag{},
19 | Action: func(c *cli.Context) error {
20 | return parseGocov(c)
21 | },
22 | }
23 |
24 | func parseGocov(c *cli.Context) error {
25 |
26 | pattern := c.Args().First()
27 | if pattern == "" {
28 | pattern = "**/*.out"
29 | }
30 |
31 | matches, err := zglob.Glob(pattern)
32 | if err != nil {
33 | return err
34 | }
35 |
36 | parser := gocov.New()
37 | var profiles []*cover.Profile
38 | for _, match := range matches {
39 | parsed, err := parser.ReadFile(match)
40 | if err != nil {
41 | return err
42 | }
43 | for _, p := range parsed {
44 | profiles = addProfile(profiles, p)
45 | }
46 | }
47 |
48 | // create the coverage payload that gets sent to the
49 | // coverage reporting server.
50 | report := profileToReport(profiles)
51 |
52 | out, err := json.MarshalIndent(report, " ", " ")
53 | if err != nil {
54 | return err
55 | }
56 | fmt.Fprintf(os.Stdout, "%s", out)
57 | return nil
58 | }
59 |
--------------------------------------------------------------------------------
/cmd_lcov.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "encoding/json"
5 | "fmt"
6 | "os"
7 |
8 | "github.com/drone-plugins/drone-coverage/coverage/lcov"
9 | "github.com/mattn/go-zglob"
10 | "github.com/urfave/cli"
11 | "golang.org/x/tools/cover"
12 | )
13 |
14 | // LcovCmd is the exported command for converting LCOV files.
15 | var LcovCmd = cli.Command{
16 | Name: "lcov",
17 | Usage: "parse lcov files",
18 | Flags: []cli.Flag{},
19 | Action: func(c *cli.Context) error {
20 | return parseLcov(c)
21 | },
22 | }
23 |
24 | func parseLcov(c *cli.Context) error {
25 |
26 | pattern := c.Args().First()
27 | if pattern == "" {
28 | pattern = "**/lcov.info"
29 | }
30 |
31 | matches, err := zglob.Glob(pattern)
32 | if err != nil {
33 | return err
34 | }
35 |
36 | parser := lcov.New()
37 | var profiles []*cover.Profile
38 | for _, match := range matches {
39 | parsed, err := parser.ReadFile(match)
40 | if err != nil {
41 | return err
42 | }
43 | for _, p := range parsed {
44 | profiles = addProfile(profiles, p)
45 | }
46 | }
47 |
48 | // create the coverage payload that gets sent to the
49 | // coverage reporting server.
50 | report := profileToReport(profiles)
51 |
52 | out, err := json.MarshalIndent(report, " ", " ")
53 | if err != nil {
54 | return err
55 | }
56 | fmt.Fprintf(os.Stdout, "%s", out)
57 | return nil
58 | }
59 |
--------------------------------------------------------------------------------
/cmd_publish.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "crypto/tls"
5 | "crypto/x509"
6 | "fmt"
7 | "io/ioutil"
8 | "os"
9 | "path"
10 | "strings"
11 | "time"
12 |
13 | "github.com/drone-plugins/drone-coverage/client"
14 | "github.com/drone-plugins/drone-coverage/coverage"
15 | "github.com/mattn/go-zglob"
16 | "github.com/sirupsen/logrus"
17 | "github.com/urfave/cli"
18 | "golang.org/x/tools/cover"
19 | )
20 |
21 | // PublishCmd is the exported command for publishing coverage files.
22 | var PublishCmd = cli.Command{
23 | Name: "publish",
24 | Usage: "publish coverage report",
25 | Flags: []cli.Flag{
26 | cli.StringFlag{
27 | Name: "repo.fullname",
28 | Usage: "repository full name",
29 | EnvVar: "DRONE_REPO",
30 | },
31 | cli.StringFlag{
32 | Name: "build.event",
33 | Value: "push",
34 | Usage: "build event",
35 | EnvVar: "DRONE_BUILD_EVENT",
36 | },
37 | cli.IntFlag{
38 | Name: "build.number",
39 | Usage: "build number",
40 | EnvVar: "DRONE_BUILD_NUMBER",
41 | },
42 | cli.StringFlag{
43 | Name: "build.link",
44 | Usage: "build link",
45 | EnvVar: "DRONE_BUILD_LINK",
46 | },
47 | cli.StringFlag{
48 | Name: "commit.sha",
49 | Usage: "git commit sha",
50 | EnvVar: "DRONE_COMMIT_SHA",
51 | },
52 | cli.StringFlag{
53 | Name: "commit.ref",
54 | Value: "refs/heads/master",
55 | Usage: "git commit ref",
56 | EnvVar: "DRONE_COMMIT_REF",
57 | },
58 | cli.StringFlag{
59 | Name: "commit.branch",
60 | Value: "master",
61 | Usage: "git commit branch",
62 | EnvVar: "DRONE_COMMIT_BRANCH",
63 | },
64 | cli.StringFlag{
65 | Name: "commit.message",
66 | Usage: "git commit message",
67 | EnvVar: "DRONE_COMMIT_MESSAGE",
68 | },
69 | cli.StringFlag{
70 | Name: "commit.author.name",
71 | Usage: "git author name",
72 | EnvVar: "DRONE_COMMIT_AUTHOR",
73 | },
74 | cli.StringFlag{
75 | Name: "commit.author.avatar",
76 | Usage: "git author avatar",
77 | EnvVar: "DRONE_COMMIT_AUTHOR_AVATAR",
78 | },
79 | cli.StringFlag{
80 | Name: "pattern",
81 | Usage: "coverage file pattern",
82 | Value: "**/*.*",
83 | EnvVar: "PLUGIN_PATTERN",
84 | },
85 | cli.StringFlag{
86 | Name: "server",
87 | Usage: "coverage server",
88 | EnvVar: "PLUGIN_SERVER",
89 | },
90 | cli.Float64Flag{
91 | Name: "threshold",
92 | Usage: "coverage threshold",
93 | EnvVar: "PLUGIN_THRESHOLD",
94 | },
95 | cli.BoolFlag{
96 | Name: "increase",
97 | Usage: "coverage must increase",
98 | EnvVar: "PLUGIN_MUST_INCREASE",
99 | },
100 | cli.StringFlag{
101 | Name: "cert",
102 | Usage: "coverage cert",
103 | EnvVar: "COVERAGE_CERT",
104 | },
105 | cli.StringFlag{
106 | Name: "token",
107 | Usage: "github token",
108 | EnvVar: "PLUGIN_TOKEN,GITHUB_TOKEN",
109 | },
110 | cli.StringFlag{
111 | Name: "env-file",
112 | Usage: "source env file",
113 | },
114 | cli.StringFlag{
115 | Name: "trim-prefix",
116 | Usage: "trim prefix from coverage files",
117 | EnvVar: "PLUGIN_TRIM_PREFIX",
118 | },
119 | },
120 | Action: func(c *cli.Context) error {
121 | return publish(c)
122 | },
123 | }
124 |
125 | func publish(c *cli.Context) error {
126 | logrus.Debugf("finding coverage files that match %s", c.String("pattern"))
127 |
128 | matches, err := zglob.Glob(c.String("pattern"))
129 | if err != nil {
130 | return err
131 | }
132 |
133 | var profiles []*cover.Profile
134 | for _, match := range matches {
135 | ok, reader := coverage.FromFile(match)
136 | if !ok {
137 | continue
138 | }
139 |
140 | logrus.Debugf("found coverage file %s", match)
141 |
142 | parsed, rerr := reader.ReadFile(match)
143 | if rerr != nil {
144 | return err
145 | }
146 |
147 | for _, p := range parsed {
148 | profiles = addProfile(profiles, p)
149 | }
150 | }
151 |
152 | // create the coverage payload that gets sent to the
153 | // coverage reporting server.
154 | report := profileToReport(profiles)
155 |
156 | build := client.Build{
157 | Number: c.Int("build.number"),
158 | Event: c.String("build.event"),
159 | Commit: c.String("commit.sha"),
160 | Branch: c.String("commit.branch"),
161 | Ref: c.String("commit.ref"),
162 | Message: c.String("commit.message"),
163 | Author: c.String("commit.author.name"),
164 | Avatar: c.String("commit.author.avatar"),
165 | Link: c.String("build.link"),
166 | Timestamp: time.Now().UTC().Unix(),
167 | }
168 |
169 | // get the base directory
170 | base := c.String("trim.prefix")
171 |
172 | if len(base) == 0 {
173 | base, err = os.Getwd()
174 |
175 | if err != nil {
176 | return err
177 | }
178 |
179 | logrus.Debug("Using current working directory")
180 | }
181 |
182 | logrus.Debugf("Base directory is %s", base)
183 |
184 | findFileReferences(report, base)
185 |
186 | var (
187 | repo = c.String("repo.fullname")
188 | server = c.String("server")
189 | secret = c.String("token")
190 | cert = c.String("cert")
191 | )
192 |
193 | cli := newClient(server, cert, "")
194 | token, err := cli.Token(secret)
195 | if err != nil {
196 | return err
197 | }
198 | cli = newClient(server, cert, token.Access)
199 |
200 | // check and see if the repository exists. if not, activate
201 | if _, err := cli.Repo(repo); err != nil {
202 | if _, err := cli.Activate(repo); err != nil {
203 | return err
204 | }
205 | }
206 |
207 | resp, err := cli.Submit(repo, &build, report)
208 | if err != nil {
209 | return err
210 | }
211 |
212 | switch {
213 | case resp.Changed > 0:
214 | fmt.Printf("Code coverage increased %.1f%% to %.1f%%\n", resp.Changed, resp.Coverage)
215 | case resp.Changed < 0:
216 | fmt.Printf("Code coverage dropped %.1f%% to %.1f%%\n", resp.Changed, resp.Coverage)
217 | default:
218 | fmt.Printf("Code coverage unchanged, %.1f%%\n", resp.Coverage)
219 | }
220 |
221 | if c.Float64("threshold") < resp.Coverage && c.Float64("threshold") != 0 {
222 | return fmt.Errorf("Failing build. Coverage threshold may not fall below %.1f%%\n", c.Float64("threshold"))
223 | }
224 | if resp.Changed < 0 && c.Bool("increase") {
225 | return fmt.Errorf("Failing build. Coverage may not decrease")
226 | }
227 |
228 | return nil
229 | }
230 |
231 | func findFileReferences(report *client.Report, base string) {
232 | var files []*client.File
233 |
234 | // normalize the file path based on the working directory
235 | // also ignore any files outside of the directory
236 | for _, file := range report.Files {
237 | fileName := file.FileName
238 | var prefix string
239 |
240 | if path.IsAbs(fileName) {
241 | if !strings.HasPrefix(fileName, base) {
242 | logrus.Warningf("File referenced in coverage not found at %s", fileName)
243 | continue
244 | }
245 |
246 | prefix = base
247 | } else if _, err := os.Stat(fileName); err == nil {
248 | logrus.Debugf("File found at relative path %s", fileName)
249 |
250 | prefix = ""
251 | } else {
252 | var err error
253 | prefix, err = coverage.PathPrefix(fileName, base)
254 |
255 | if err != nil {
256 | // See if file is on disk
257 | logrus.Warningf("File referenced in coverage not found at %s", fileName)
258 | continue
259 | }
260 |
261 | logrus.Debugf("Found common path at %s", prefix)
262 | }
263 |
264 | // Add the file to the report
265 | file.FileName = strings.TrimPrefix(fileName, prefix)
266 | files = append(files, file)
267 | }
268 |
269 | report.Files = files
270 | }
271 |
272 | // profileToReport is a helper function that converts the merged coverage
273 | // report to the Report JSON format expected by the coverage server.
274 | func profileToReport(profiles []*cover.Profile) *client.Report {
275 | report := client.Report{}
276 | report.Files = make([]*client.File, len(profiles), len(profiles))
277 |
278 | for i, profile := range profiles {
279 | file := client.File{
280 | Mode: profile.Mode,
281 | FileName: profile.FileName,
282 | }
283 |
284 | file.Blocks = make([]*client.Block, len(profile.Blocks), len(profile.Blocks))
285 | for ii, block := range profile.Blocks {
286 | file.Blocks[ii] = &client.Block{
287 | StartLine: block.StartLine,
288 | StartCol: block.StartCol,
289 | EndLine: block.EndLine,
290 | EndCol: block.EndCol,
291 | NumStmt: block.NumStmt,
292 | Count: block.Count,
293 | }
294 | }
295 |
296 | covered, total, percent := percentCovered(profile)
297 | file.Lines = total
298 | file.Covered = covered
299 | file.Coverage = percent
300 |
301 | report.Files[i] = &file
302 | report.Lines += file.Lines
303 | report.Covered += file.Covered
304 | }
305 | if report.Lines != 0 {
306 | report.Coverage = float64(report.Covered) / float64(report.Lines) * float64(100)
307 | }
308 | return &report
309 | }
310 |
311 | // percentCovered is a helper fucntion that calculate the percent
312 | // coverage for coverage profile.
313 | func percentCovered(p *cover.Profile) (int64, int64, float64) {
314 | var total, covered int64
315 | for _, b := range p.Blocks {
316 | total += int64(b.NumStmt)
317 | if b.Count > 0 {
318 | covered += int64(b.NumStmt)
319 | }
320 | }
321 | var percent float64
322 | if total != 0 {
323 | percent = float64(covered) / float64(total) * float64(100)
324 | }
325 | return covered, total, percent
326 | }
327 |
328 | // newClient returns a new coverage server client.
329 | func newClient(server, cert, token string) client.Client {
330 | pool, err := x509.SystemCertPool()
331 | if err != nil {
332 | pool = x509.NewCertPool()
333 | }
334 | conf := &tls.Config{RootCAs: pool}
335 | pem, _ := ioutil.ReadFile(cert)
336 | if len(pem) != 0 {
337 | pool.AppendCertsFromPEM(pem)
338 | }
339 | if len(token) == 0 {
340 | return client.NewClientTLS(server, conf)
341 | }
342 | return client.NewClientTokenTLS(server, token, conf)
343 | }
344 |
--------------------------------------------------------------------------------
/cmd_publish_test.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "os"
5 | "testing"
6 |
7 | "github.com/drone-plugins/drone-coverage/client"
8 | "github.com/sirupsen/logrus"
9 | )
10 |
11 | func createReport(names ...string) *client.Report {
12 | report := &client.Report{}
13 | var files []*client.File
14 |
15 | for _, name := range names {
16 | file := &client.File{
17 | FileName: name,
18 | }
19 |
20 | files = append(files, file)
21 | }
22 |
23 | report.Files = files
24 | return report
25 | }
26 |
27 | func TestPathModification(t *testing.T) {
28 | logrus.SetLevel(logrus.DebugLevel)
29 | logrus.SetOutput(os.Stderr)
30 |
31 | // test modified
32 | report := createReport("a/b.go")
33 | findFileReferences(report, "/a/")
34 |
35 | fileName := report.Files[0].FileName
36 | if fileName != "b.go" {
37 | t.Errorf("Expected filename to be b.go not %s", fileName)
38 | }
39 |
40 | // test removed
41 | report = createReport("a/b.go", "b/c.go")
42 | findFileReferences(report, "/a/")
43 |
44 | if len(report.Files) != 1 {
45 | t.Errorf("Expected a file to be removed")
46 | } else {
47 | fileName = report.Files[0].FileName
48 | if fileName != "b.go" {
49 | t.Errorf("Expected filename to be b.go not %s", fileName)
50 | }
51 | }
52 |
53 | // test file present
54 | f, err := os.Create(".a.go")
55 |
56 | if err != nil {
57 | t.Errorf("Could not create file for test")
58 | }
59 | defer os.Remove(f.Name())
60 |
61 | report = createReport(".a.go")
62 | findFileReferences(report, "/a/")
63 |
64 | if len(report.Files) != 1 {
65 | t.Errorf("Expected file to be present")
66 | } else {
67 | fileName = report.Files[0].FileName
68 | if fileName != ".a.go" {
69 | t.Errorf("Expected filename to be .a.go not %s", fileName)
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/coverage/cobertura/cobertura.go:
--------------------------------------------------------------------------------
1 | package cobertura
2 |
3 | import (
4 | "bufio"
5 | "encoding/xml"
6 | "io"
7 | "io/ioutil"
8 | "os"
9 |
10 | "github.com/drone-plugins/drone-coverage/coverage"
11 | "golang.org/x/tools/cover"
12 | )
13 |
14 | func init() {
15 | coverage.Register(`
16 |
18 |
20 |
189 |
190 |
191 |
192 | C:/local/mvn-coverage-example/src/main/java
193 | --source
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
244 |
245 |
246 |
247 |
248 |
249 |
250 |
251 |
252 |
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
261 |
262 |
263 |
264 |
265 |
266 |
267 |
268 |
269 |
270 |
271 |
272 |
273 |
274 |
275 |
276 |
277 |
278 |
279 |
280 |
281 |
282 |
283 |
284 |
285 |
286 |
287 |
288 |
289 |
290 |
291 |
292 |
293 |
294 |
295 |
296 |
297 |
298 |
299 |
300 |
301 |
302 |
303 |
304 |
305 |
306 |
307 |
308 |
309 |
310 |
311 |
312 |
313 |
314 |
315 |
316 |
317 |
318 |
319 |
320 |
321 |
322 |
323 |
324 |
325 |
326 |
327 |
328 |
329 |
330 |
331 |
332 |
333 |
334 |
335 |
336 |
337 |
338 |
339 |
340 |
341 |
342 |
343 |
344 |
345 |
346 |
347 |
348 |
349 |
350 |
351 |
352 |
353 |
354 |
355 |
356 |
357 |
358 |
359 |
360 |
361 |
362 |
363 |
364 |
365 | `)
366 |
367 | var sampleJunitCoverageProfile = []*cover.Profile{
368 | {
369 | FileName: "Main.java",
370 | Mode: "set",
371 | Blocks: []cover.ProfileBlock{
372 | {Count: 3, StartLine: 10, EndLine: 10, NumStmt: 1},
373 | {Count: 3, StartLine: 16, EndLine: 16, NumStmt: 1},
374 | {Count: 3, StartLine: 17, EndLine: 17, NumStmt: 1},
375 | {Count: 3, StartLine: 18, EndLine: 18, NumStmt: 1},
376 | {Count: 3, StartLine: 19, EndLine: 19, NumStmt: 1},
377 | {Count: 3, StartLine: 23, EndLine: 23, NumStmt: 1},
378 | {Count: 3, StartLine: 25, EndLine: 25, NumStmt: 1},
379 | {Count: 3, StartLine: 26, EndLine: 26, NumStmt: 1},
380 | {Count: 3, StartLine: 28, EndLine: 28, NumStmt: 1},
381 | {Count: 3, StartLine: 29, EndLine: 29, NumStmt: 1},
382 | {Count: 3, StartLine: 30, EndLine: 30, NumStmt: 1},
383 | },
384 | },
385 | {
386 | FileName: "search/BinarySearch.java",
387 | Mode: "set",
388 | Blocks: []cover.ProfileBlock{
389 | {Count: 3, StartLine: 12, EndLine: 12, NumStmt: 1},
390 | {Count: 3, StartLine: 16, EndLine: 16, NumStmt: 1},
391 | {Count: 12, StartLine: 18, EndLine: 18, NumStmt: 1},
392 | {Count: 9, StartLine: 20, EndLine: 20, NumStmt: 1},
393 | {Count: 9, StartLine: 21, EndLine: 21, NumStmt: 1},
394 | {Count: 9, StartLine: 23, EndLine: 23, NumStmt: 1},
395 | {Count: 0, StartLine: 24, EndLine: 24, NumStmt: 1},
396 | {Count: 9, StartLine: 25, EndLine: 25, NumStmt: 1},
397 | {Count: 6, StartLine: 26, EndLine: 26, NumStmt: 1},
398 | {Count: 3, StartLine: 28, EndLine: 28, NumStmt: 1},
399 | {Count: 9, StartLine: 29, EndLine: 29, NumStmt: 1},
400 | {Count: 3, StartLine: 31, EndLine: 31, NumStmt: 1},
401 | },
402 | },
403 | {
404 | FileName: "search/ISortedArraySearch.java",
405 | Mode: "set",
406 | },
407 | {
408 | FileName: "search/LinearSearch.java",
409 | Mode: "set",
410 | Blocks: []cover.ProfileBlock{
411 | {Count: 3, StartLine: 9, EndLine: 9, NumStmt: 1},
412 | {Count: 9, StartLine: 13, EndLine: 13, NumStmt: 1},
413 | {Count: 9, StartLine: 15, EndLine: 15, NumStmt: 1},
414 | {Count: 3, StartLine: 16, EndLine: 16, NumStmt: 1},
415 | {Count: 6, StartLine: 17, EndLine: 17, NumStmt: 1},
416 | {Count: 0, StartLine: 19, EndLine: 19, NumStmt: 1},
417 | {Count: 0, StartLine: 24, EndLine: 24, NumStmt: 1},
418 | },
419 | },
420 | }
421 |
--------------------------------------------------------------------------------
/coverage/coverage.go:
--------------------------------------------------------------------------------
1 | package coverage
2 |
3 | import (
4 | "bytes"
5 | "io"
6 | "io/ioutil"
7 | "os"
8 | "regexp"
9 |
10 | "golang.org/x/tools/cover"
11 | )
12 |
13 | // the algorithm uses at most sniffLen bytes to make its decision.
14 | const sniffLen = 128
15 |
16 | // the list of registered sniffers.
17 | var sniffers []*sniffer
18 |
19 | type sniffer struct {
20 | reader Reader
21 | sig []byte
22 | }
23 |
24 | // regular expression that can be used to match common filepath patterns
25 | // as a first check to determine if something is a coverage report.
26 | var includes = regexp.MustCompile(".+\\.out|.+clover.xml|.+coverage.json|.+cobertura-coverage.xml|.+lcov.+")
27 |
28 | // Reader reads and parses a coverage file.
29 | type Reader interface {
30 |
31 | // Read reads a coverage report from the bytes.
32 | Read(src []byte) ([]*cover.Profile, error)
33 |
34 | // ReadFile reads a coverage report from the file path.
35 | ReadFile(string) ([]*cover.Profile, error)
36 |
37 | // ReadProfiles reads a coverage report from the io.Reader.
38 | ReadProfiles(io.Reader) ([]*cover.Profile, error)
39 | }
40 |
41 | // Register registers a reader associated with the sniff pattern.
42 | func Register(sig string, r Reader) {
43 | sniffers = append(sniffers, &sniffer{r, []byte(sig)})
44 | }
45 |
46 | // FromBytes reads the first 512 bytes of slice and returns a coverage
47 | // report reader that is capable of parsing the coverage data.
48 | func FromBytes(data []byte) (bool, Reader) {
49 | if len(data) > sniffLen {
50 | data = data[:sniffLen]
51 | }
52 | firstNonWS := 0
53 | for ; firstNonWS < len(data) && isWS(data[firstNonWS]); firstNonWS++ {
54 | }
55 | if firstNonWS >= len(data) {
56 | return false, nil
57 | }
58 | data = data[firstNonWS:]
59 | for _, sniffer := range sniffers {
60 | if bytes.HasPrefix(data, sniffer.sig) {
61 | return true, sniffer.reader
62 | }
63 | }
64 | return false, nil
65 | }
66 |
67 | // FromFile reads the first 512 bytes of the file and returns a coverage
68 | // report reader that is capable of parsing the coverage file.
69 | func FromFile(path string) (bool, Reader) {
70 | f, err := os.Open(path)
71 | if err != nil {
72 | return false, nil
73 | }
74 | defer f.Close()
75 | r := io.LimitReader(f, sniffLen)
76 | b, err := ioutil.ReadAll(r)
77 | if err != nil {
78 | return false, nil
79 | }
80 | return FromBytes(b)
81 | }
82 |
83 | // IsMatch returns true if the coverage file path matches a regular expression
84 | // of known patterns for coverage files.
85 | func IsMatch(path string) bool {
86 | return includes.MatchString(path)
87 | }
88 |
89 | // helper function returns true if the byte is whitespace
90 | func isWS(b byte) bool {
91 | switch b {
92 | case '\t', '\n', '\x0c', '\r', ' ':
93 | return true
94 | }
95 | return false
96 | }
97 |
--------------------------------------------------------------------------------
/coverage/coverage_test.go:
--------------------------------------------------------------------------------
1 | package coverage
2 |
3 | import "testing"
4 |
5 | func TestFromBytes(t *testing.T) {
6 | Register("mode:", nil)
7 |
8 | // test empty string
9 | ok, _ := FromBytes([]byte(""))
10 | if ok {
11 | t.Errorf("Expect bytes to not match a reader")
12 | }
13 |
14 | // test whitespace that equals the pattern length
15 | ok, _ = FromBytes([]byte(" "))
16 | if ok {
17 | t.Errorf("Expect bytes to not match a reader")
18 | }
19 |
20 | // test whitespace greater than the pattern length
21 | ok, _ = FromBytes([]byte(" "))
22 | if ok {
23 | t.Errorf("Expect bytes to not match a reader")
24 | }
25 |
26 | // test a matching pattern
27 | ok, _ = FromBytes([]byte("mode:"))
28 | if !ok {
29 | t.Errorf("Expect bytes to match a reader")
30 | }
31 |
32 | // test whitespace prefixing a matching pattern
33 | ok, _ = FromBytes([]byte(" mode:"))
34 | if !ok {
35 | t.Errorf("Expect bytes to match a reader even with whitespace")
36 | }
37 | }
38 |
39 | func TestIsMatch(t *testing.T) {
40 | var tests = map[string]bool{
41 | "/drone/src/github.com/octocat/hello-world/coverage.out": true,
42 | "/drone/src/github.com/octocat/hello-world/coverage.stdout": false,
43 | "/drone/src/github.com/octocat/hello-world/clover.xml": true,
44 | "/drone/src/github.com/octocat/hello-world/coverage.json": true,
45 | "/drone/src/github.com/octocat/hello-world/cobertura-coverage.xml": true,
46 | "/drone/src/github.com/octocat/hello-world/lcov.info": true,
47 | "/drone/src/github.com/octocat/hello-world/lcov.txt": true,
48 | }
49 |
50 | for path, match := range tests {
51 | if IsMatch(path) != match {
52 | t.Errorf("Expected filepath match %v for %s", match, path)
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/coverage/gocov/gocov.go:
--------------------------------------------------------------------------------
1 | package gocov
2 |
3 | import (
4 | "bytes"
5 | "io"
6 | "io/ioutil"
7 | "os"
8 |
9 | "github.com/drone-plugins/drone-coverage/coverage"
10 | "golang.org/x/tools/cover"
11 | )
12 |
13 | func init() {
14 | coverage.Register("mode:", New())
15 | }
16 |
17 | type reader struct{}
18 |
19 | // New returns a new Reader for reading and parsing a Go coverage report.
20 | func New() coverage.Reader {
21 | return new(reader)
22 | }
23 |
24 | func (r *reader) Read(src []byte) ([]*cover.Profile, error) {
25 | buf := bytes.NewBuffer(src)
26 | return r.ReadProfiles(buf)
27 | }
28 |
29 | func (r *reader) ReadFile(path string) ([]*cover.Profile, error) {
30 | return cover.ParseProfiles(path)
31 | }
32 |
33 | func (r *reader) ReadProfiles(src io.Reader) ([]*cover.Profile, error) {
34 | file, err := ioutil.TempFile(os.TempDir(), "cover_file_")
35 | if err != nil {
36 | return nil, err
37 | }
38 | defer os.Remove(file.Name())
39 | if _, err := io.Copy(file, src); err != nil {
40 | return nil, err
41 | }
42 | return r.ReadFile(file.Name())
43 | }
44 |
--------------------------------------------------------------------------------
/coverage/gocov/gocov_test.go:
--------------------------------------------------------------------------------
1 | package gocov
2 |
3 | import (
4 | "reflect"
5 | "testing"
6 |
7 | "github.com/drone-plugins/drone-coverage/coverage"
8 | "golang.org/x/tools/cover"
9 | )
10 |
11 | func TestParse(t *testing.T) {
12 | got, err := New().Read(sampleFile)
13 | if err != nil {
14 | t.Errorf("Expected Go coverage profile parsed successfully, got error %s", err)
15 | }
16 |
17 | if !reflect.DeepEqual(got, sampleProfiles) {
18 | t.Errorf("Expected Go coverage profile matches the test fixture")
19 | }
20 | }
21 |
22 | func TestSniff(t *testing.T) {
23 | ok, _ := coverage.FromBytes([]byte("foo:"))
24 | if ok {
25 | t.Errorf("Expect sniffer does not find match")
26 | }
27 |
28 | ok, r := coverage.FromBytes(sampleFile)
29 | if !ok {
30 | t.Errorf("Expect sniffer to find a match")
31 | }
32 | if _, ok := r.(*reader); !ok {
33 | t.Errorf("Expect sniffer to return a reader")
34 | }
35 | }
36 |
37 | var sampleProfiles = []*cover.Profile{
38 | {
39 | FileName: "github.com/drone-plugins/drone-coverage/coverage/lcov/lcov.go",
40 | Mode: "set",
41 | Blocks: []cover.ProfileBlock{
42 | {Count: 1, StartLine: 14, StartCol: 50, EndLine: 17, EndCol: 2, NumStmt: 2},
43 | {Count: 1, StartLine: 20, StartCol: 52, EndLine: 26, EndCol: 15, NumStmt: 5},
44 | {Count: 1, StartLine: 26, StartCol: 15, EndLine: 29, EndCol: 10, NumStmt: 2},
45 | {Count: 1, StartLine: 30, StartCol: 3, EndLine: 34, EndCol: 40, NumStmt: 4},
46 | {Count: 1, StartLine: 35, StartCol: 3, EndLine: 50, EndCol: 50, NumStmt: 11},
47 | {Count: 1, StartLine: 51, StartCol: 3, EndLine: 52, EndCol: 12, NumStmt: 1},
48 | {Count: 1, StartLine: 56, StartCol: 2, EndLine: 56, EndCol: 22, NumStmt: 1},
49 | },
50 | },
51 | }
52 |
53 | var sampleFile = []byte(`mode: set
54 | github.com/drone-plugins/drone-coverage/coverage/lcov/lcov.go:14.50,17.2 2 1
55 | github.com/drone-plugins/drone-coverage/coverage/lcov/lcov.go:20.52,26.15 5 1
56 | github.com/drone-plugins/drone-coverage/coverage/lcov/lcov.go:56.2,56.22 1 1
57 | github.com/drone-plugins/drone-coverage/coverage/lcov/lcov.go:26.15,29.10 2 1
58 | github.com/drone-plugins/drone-coverage/coverage/lcov/lcov.go:30.3,34.40 4 1
59 | github.com/drone-plugins/drone-coverage/coverage/lcov/lcov.go:35.3,50.50 11 1
60 | github.com/drone-plugins/drone-coverage/coverage/lcov/lcov.go:51.3,52.12 1 1`)
61 |
--------------------------------------------------------------------------------
/coverage/jacoco/jacoco.go:
--------------------------------------------------------------------------------
1 | package jacoco
2 |
3 | import (
4 | "io"
5 |
6 | "github.com/drone-plugins/drone-coverage/coverage"
7 | "golang.org/x/tools/cover"
8 | )
9 |
10 | func init() {
11 | coverage.Register(`
12 | 0 {
72 | count = 1
73 | }
74 |
75 | block := cover.ProfileBlock{}
76 | block.NumStmt = 1
77 | block.StartLine = line
78 | block.EndLine = line
79 | block.Count = count
80 |
81 | if len(cols) != 0 && line > 0 {
82 | block.StartCol = 1
83 | block.EndCol = cols[line-1]
84 | }
85 |
86 | profile.Blocks = append(profile.Blocks, block)
87 | default:
88 | continue
89 | }
90 | }
91 |
92 | return profiles, nil
93 | }
94 |
95 | // this is a helper function that is able to calculate the
96 | func calculateCols(profile *cover.Profile) []int {
97 | var lines []int
98 |
99 | f, err := os.Open(profile.FileName)
100 | if err != nil {
101 | return lines
102 | }
103 | defer f.Close()
104 |
105 | scanner := bufio.NewScanner(f)
106 | for scanner.Scan() {
107 | cols := len(scanner.Bytes())
108 | lines = append(lines, cols)
109 | }
110 | return lines
111 | }
112 |
--------------------------------------------------------------------------------
/coverage/lcov/lcov_test.go:
--------------------------------------------------------------------------------
1 | package lcov
2 |
3 | import (
4 | "reflect"
5 | "testing"
6 |
7 | "github.com/drone-plugins/drone-coverage/coverage"
8 | "golang.org/x/tools/cover"
9 | )
10 |
11 | func TestRead(t *testing.T) {
12 | got, err := New().Read(sampleFile)
13 | if err != nil {
14 | t.Errorf("Expected LCOV parsed successfully, got error %s", err)
15 | }
16 |
17 | if !reflect.DeepEqual(got, sampleProfiles) {
18 | t.Errorf("Expected LCOV parsed file equals test fixture")
19 | }
20 | }
21 |
22 | func TestSniff(t *testing.T) {
23 | ok, _ := coverage.FromBytes([]byte("foo:"))
24 | if ok {
25 | t.Errorf("Expect sniffer does not find match")
26 | }
27 |
28 | ok, r := coverage.FromBytes(sampleFile)
29 | if !ok {
30 | t.Errorf("Expect sniffer to find a match")
31 | }
32 | if _, ok := r.(*reader); !ok {
33 | t.Errorf("Expect sniffer to return an LCOV reader")
34 | }
35 | }
36 |
37 | func TestRelativePathsReplacement(t *testing.T) {
38 | got, err := New().Read(sampleFileWithRelativePaths)
39 | if err != nil {
40 | t.Errorf("Expected LCOV parsed successfully, got error %s", err)
41 | }
42 |
43 | if !reflect.DeepEqual(got, sampleProfilesWithAbsolutePaths) {
44 | t.Errorf("Expected LCOV parsed file equals test fixture")
45 | }
46 | }
47 |
48 | var sampleProfilesWithAbsolutePaths = []*cover.Profile{
49 | {
50 | FileName: "/drone/src/github.com/donny-dont/dogma-codegen/lib/src/codegen/function_generator.dart",
51 | Mode: "set",
52 | Blocks: []cover.ProfileBlock{
53 | {NumStmt: 1, StartLine: 30, EndLine: 30, Count: 1},
54 | },
55 | },
56 | }
57 |
58 | var sampleProfiles = []*cover.Profile{
59 | {
60 | FileName: "/drone/src/github.com/donny-dont/dogma-codegen/lib/src/codegen/function_generator.dart",
61 | Mode: "set",
62 | Blocks: []cover.ProfileBlock{
63 | {NumStmt: 1, StartLine: 30, EndLine: 30, Count: 1},
64 | {NumStmt: 1, StartLine: 31, EndLine: 31, Count: 1},
65 | {NumStmt: 1, StartLine: 32, EndLine: 32, Count: 1},
66 | {NumStmt: 1, StartLine: 34, EndLine: 34, Count: 1},
67 | {NumStmt: 1, StartLine: 42, EndLine: 42, Count: 0},
68 | {NumStmt: 1, StartLine: 45, EndLine: 45, Count: 1},
69 | {NumStmt: 1, StartLine: 48, EndLine: 48, Count: 1},
70 | {NumStmt: 1, StartLine: 52, EndLine: 52, Count: 1},
71 | {NumStmt: 1, StartLine: 54, EndLine: 54, Count: 1},
72 | {NumStmt: 1, StartLine: 58, EndLine: 58, Count: 1},
73 | {NumStmt: 1, StartLine: 62, EndLine: 62, Count: 1},
74 | },
75 | },
76 | {
77 | FileName: "/drone/src/github.com/donny-dont/dogma-codegen/lib/src/codegen/annotated_metadata_generator.dart",
78 | Mode: "set",
79 | Blocks: []cover.ProfileBlock{
80 | {NumStmt: 1, StartLine: 33, EndLine: 33, Count: 1},
81 | {NumStmt: 1, StartLine: 36, EndLine: 36, Count: 1},
82 | {NumStmt: 1, StartLine: 37, EndLine: 37, Count: 1},
83 | {NumStmt: 1, StartLine: 38, EndLine: 38, Count: 1},
84 | },
85 | },
86 | }
87 |
88 | var sampleFileWithRelativePaths = []byte(`
89 | SF:./drone/src/github.com/donny-dont/dogma-codegen/lib/src/codegen/function_generator.dart
90 | DA:30,84
91 | end_of_record
92 | `)
93 |
94 | var sampleFile = []byte(`
95 | SF:/drone/src/github.com/donny-dont/dogma-codegen/lib/src/codegen/function_generator.dart
96 | DA:30,84
97 | DA:31,28
98 | DA:32,56
99 | DA:34,56
100 | DA:42,0
101 | DA:45,28
102 | DA:48,28
103 | DA:52,12
104 | DA:54,16
105 | DA:58,28
106 | DA:62,16
107 | end_of_record
108 | SF:/drone/src/github.com/donny-dont/dogma-codegen/lib/src/codegen/annotated_metadata_generator.dart
109 | DA:33,230
110 | DA:36,479
111 | DA:37,236
112 | DA:38,51
113 | end_of_record
114 | `)
115 |
--------------------------------------------------------------------------------
/coverage/path.go:
--------------------------------------------------------------------------------
1 | package coverage
2 |
3 | import "fmt"
4 |
5 | // PathPrefix finds the prefix relative to the base with the curr value.
6 | // It will search the base to find the commonality or return an error if there
7 | // is none.
8 | func PathPrefix(curr string, base string) (string, error) {
9 | cBytes := []byte(curr)
10 | bBytes := []byte(base)
11 | count := len(bBytes)
12 |
13 | // Search for the string
14 | for i, _ := range bBytes {
15 | for j, c := range cBytes {
16 | a := i + j
17 |
18 | if a == count {
19 | return curr[:j], nil
20 | }
21 |
22 | if c != bBytes[a] {
23 | break
24 | }
25 | }
26 | }
27 |
28 | return "", fmt.Errorf("Path %s not found in %s", curr, base)
29 | }
30 |
--------------------------------------------------------------------------------
/coverage/path_test.go:
--------------------------------------------------------------------------------
1 | package coverage
2 |
3 | import "testing"
4 |
5 | func TestErrors(t *testing.T) {
6 | _, err := PathPrefix("/d/e/f", "/a/b/c")
7 | if err == nil {
8 | t.Errorf("Expect error with two different abolute paths")
9 | }
10 |
11 | _, err = PathPrefix("d/e/f", "/a/b/c")
12 | if err == nil {
13 | t.Errorf("Expect error with a relative path not in absolute")
14 | }
15 | }
16 |
17 | func TestPrefix(t *testing.T) {
18 | abs, err := PathPrefix("/a/b/c/d", "/a/b/c/")
19 | if err != nil && abs != "/a/b/c/" {
20 | t.Errorf("Expect prefix from two absolute paths %s", abs)
21 | }
22 |
23 | rel, err := PathPrefix("b/c/d/e", "/a/b/c/")
24 | if err != nil && rel != "b/c/" {
25 | t.Errorf("Expect prefix from a relative path %s", rel)
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/docker/Dockerfile.linux.amd64:
--------------------------------------------------------------------------------
1 | FROM plugins/base:multiarch
2 |
3 | LABEL maintainer="Drone.IO Community " \
4 | org.label-schema.name="Drone Coverage" \
5 | org.label-schema.vendor="Drone.IO Community" \
6 | org.label-schema.schema-version="1.0"
7 |
8 | ADD release/linux/amd64/drone-coverage /bin/
9 | ENTRYPOINT ["/bin/drone-coverage"]
10 | CMD ["publish"]
11 |
--------------------------------------------------------------------------------
/docker/Dockerfile.linux.arm:
--------------------------------------------------------------------------------
1 | FROM plugins/base:multiarch
2 |
3 | LABEL maintainer="Drone.IO Community " \
4 | org.label-schema.name="Drone Coverage" \
5 | org.label-schema.vendor="Drone.IO Community" \
6 | org.label-schema.schema-version="1.0"
7 |
8 | ADD release/linux/arm/drone-coverage /bin/
9 | ENTRYPOINT ["/bin/drone-coverage"]
10 | CMD ["publish"]
11 |
--------------------------------------------------------------------------------
/docker/Dockerfile.linux.arm64:
--------------------------------------------------------------------------------
1 | FROM plugins/base:multiarch
2 |
3 | LABEL maintainer="Drone.IO Community " \
4 | org.label-schema.name="Drone Coverage" \
5 | org.label-schema.vendor="Drone.IO Community" \
6 | org.label-schema.schema-version="1.0"
7 |
8 | ADD release/linux/arm64/drone-coverage /bin/
9 | ENTRYPOINT ["/bin/drone-coverage"]
10 | CMD ["publish"]
11 |
--------------------------------------------------------------------------------
/docker/Dockerfile.windows.1803:
--------------------------------------------------------------------------------
1 | # escape=`
2 | FROM plugins/base:windows-1803
3 |
4 | LABEL maintainer="Drone.IO Community " `
5 | org.label-schema.name="Drone Coverage" `
6 | org.label-schema.vendor="Drone.IO Community" `
7 | org.label-schema.schema-version="1.0"
8 |
9 | ADD release/windows/amd64/drone-coverage.exe C:/bin/drone-coverage.exe
10 | ENTRYPOINT [ "C:\\bin\\drone-coverage.exe" ]
11 | CMD ["publish"]
12 |
--------------------------------------------------------------------------------
/docker/Dockerfile.windows.1809:
--------------------------------------------------------------------------------
1 | # escape=`
2 | FROM plugins/base:windows-1809
3 |
4 | LABEL maintainer="Drone.IO Community " `
5 | org.label-schema.name="Drone Coverage" `
6 | org.label-schema.vendor="Drone.IO Community" `
7 | org.label-schema.schema-version="1.0"
8 |
9 | ADD release/windows/amd64/drone-coverage.exe C:/bin/drone-coverage.exe
10 | ENTRYPOINT [ "C:\\bin\\drone-coverage.exe" ]
11 | CMD ["publish"]
12 |
--------------------------------------------------------------------------------
/docker/manifest.tmpl:
--------------------------------------------------------------------------------
1 | image: plugins/coverage:{{#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: plugins/coverage:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-amd64
11 | platform:
12 | architecture: amd64
13 | os: linux
14 | -
15 | image: plugins/coverage:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm64
16 | platform:
17 | architecture: arm64
18 | os: linux
19 | variant: v8
20 | -
21 | image: plugins/coverage:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm
22 | platform:
23 | architecture: arm
24 | os: linux
25 | variant: v7
26 | -
27 | image: plugins/coverage:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}windows-1803
28 | platform:
29 | architecture: amd64
30 | os: windows
31 | version: 1803
32 | -
33 | image: plugins/coverage:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}windows-1809
34 | platform:
35 | architecture: amd64
36 | os: windows
37 | version: 1809
38 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/drone-plugins/drone-coverage
2 |
3 | require (
4 | github.com/mattn/go-zglob v0.0.1
5 | github.com/sirupsen/logrus v1.3.0
6 | github.com/urfave/cli v1.20.0
7 | golang.org/x/oauth2 v0.0.0-20190130075453-99b60b757ec1
8 | golang.org/x/tools v0.0.0-20190211082357-3744606dbb67
9 | )
10 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
2 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
3 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4 | github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
5 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
6 | github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
7 | github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
8 | github.com/mattn/go-zglob v0.0.1 h1:xsEx/XUoVlI6yXjqBK062zYhRTZltCNmYPx6v+8DNaY=
9 | github.com/mattn/go-zglob v0.0.1/go.mod h1:9fxibJccNxU2cnpIKLRRFA7zX7qhkJIQWBb449FYHOo=
10 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
11 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
12 | github.com/sirupsen/logrus v1.3.0 h1:hI/7Q+DtNZ2kINb6qt/lS+IyXnHQe9e90POfeewL/ME=
13 | github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
14 | github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
15 | github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
16 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
17 | github.com/urfave/cli v1.20.0 h1:fDqGv3UG/4jbVl/QkFwEdddtEDjh/5Ov6X+0B/3bPaw=
18 | github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
19 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 h1:u+LnwYTOOW7Ukr/fppxEb1Nwz0AtPflrblfvUudpo+I=
20 | golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
21 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
22 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e h1:bRhVy7zSSasaqNksaRZiA5EEI+Ei4I1nO5Jh72wfHlg=
23 | golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
24 | golang.org/x/oauth2 v0.0.0-20190111185915-36a7019397c4 h1:Xi5aaGtyrfSB/gXS4Kal2NNpB7uzffL3yzWi2kByI18=
25 | golang.org/x/oauth2 v0.0.0-20190111185915-36a7019397c4/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
26 | golang.org/x/oauth2 v0.0.0-20190123100215-5dab4167f31c/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
27 | golang.org/x/oauth2 v0.0.0-20190130075453-99b60b757ec1 h1:nptVV7hxzzMT+cw8qQ7SoPwc3jCHsGzcOmyy8UKWlFI=
28 | golang.org/x/oauth2 v0.0.0-20190130075453-99b60b757ec1/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
29 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4 h1:YUO/7uOKsKeq9UokNS62b8FYywz3ker1l1vDZRCRefw=
30 | golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
31 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 h1:I6FyU15t786LL7oL/hn43zqTuEGr4PN7F4XJ1p4E3Y8=
32 | golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
33 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
34 | golang.org/x/tools v0.0.0-20190111214448-fc1d57b08d7b h1:Z5QW7z0ycYrOVRYv3z4FeSZbRNvVwUfXHKQSZKb5A6w=
35 | golang.org/x/tools v0.0.0-20190111214448-fc1d57b08d7b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
36 | golang.org/x/tools v0.0.0-20190123130514-9279ec27fd88/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
37 | golang.org/x/tools v0.0.0-20190124014748-78ee07aa9465/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
38 | golang.org/x/tools v0.0.0-20190211082357-3744606dbb67 h1:EPh/O3OZ46PfYCH54ZszS/MiEsjLhwJJkUDIy6gTeJY=
39 | golang.org/x/tools v0.0.0-20190211082357-3744606dbb67/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
40 | google.golang.org/appengine v1.4.0 h1:/wp5JvzpHIxhs/dumFmF7BXTf3Z+dd4uXta4kVyO508=
41 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
42 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "os"
5 |
6 | "github.com/sirupsen/logrus"
7 | "github.com/urfave/cli"
8 | )
9 |
10 | var (
11 | version = "unknown"
12 | )
13 |
14 | func main() {
15 | app := cli.NewApp()
16 | app.Name = "coverage generator"
17 | app.Usage = "generate and publish coverage reports"
18 | app.Version = version
19 | app.Before = setup
20 | app.Flags = []cli.Flag{
21 | cli.StringFlag{
22 | Name: "ci",
23 | Usage: "continuous integration mode",
24 | EnvVar: "CI",
25 | },
26 | cli.BoolTFlag{
27 | Name: "debug",
28 | Usage: "debug mode",
29 | EnvVar: "DEBUG",
30 | },
31 | }
32 | app.Commands = []cli.Command{
33 | LcovCmd,
34 | GocovCmd,
35 | PublishCmd,
36 | }
37 |
38 | err := app.Run(os.Args)
39 | if err != nil {
40 | logrus.Fatal(err)
41 | }
42 | }
43 |
44 | func setup(c *cli.Context) error {
45 | logrus.SetOutput(os.Stderr)
46 |
47 | if c.GlobalBool("debug") {
48 | logrus.SetLevel(logrus.DebugLevel)
49 | } else {
50 | logrus.SetLevel(logrus.WarnLevel)
51 | }
52 |
53 | return nil
54 | }
55 |
--------------------------------------------------------------------------------
/merge.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "log"
5 | "sort"
6 |
7 | "golang.org/x/tools/cover"
8 | )
9 |
10 | func mergeProfiles(p *cover.Profile, merge *cover.Profile) {
11 | if p.Mode != merge.Mode {
12 | log.Fatalf("cannot merge profiles with different modes")
13 | }
14 | // Since the blocks are sorted, we can keep track of where the last block
15 | // was inserted and only look at the blocks after that as targets for merge
16 | startIndex := 0
17 | for _, b := range merge.Blocks {
18 | startIndex = mergeProfileBlock(p, b, startIndex)
19 | }
20 | }
21 |
22 | func mergeProfileBlock(p *cover.Profile, pb cover.ProfileBlock, startIndex int) int {
23 | sortFunc := func(i int) bool {
24 | pi := p.Blocks[i+startIndex]
25 | return pi.StartLine >= pb.StartLine && (pi.StartLine != pb.StartLine || pi.StartCol >= pb.StartCol)
26 | }
27 |
28 | i := 0
29 | if sortFunc(i) != true {
30 | i = sort.Search(len(p.Blocks)-startIndex, sortFunc)
31 | }
32 | i += startIndex
33 | if i < len(p.Blocks) && p.Blocks[i].StartLine == pb.StartLine && p.Blocks[i].StartCol == pb.StartCol {
34 | if p.Blocks[i].EndLine != pb.EndLine || p.Blocks[i].EndCol != pb.EndCol {
35 | log.Fatalf("OVERLAP MERGE: %v %v %v", p.FileName, p.Blocks[i], pb)
36 | }
37 | p.Blocks[i].Count |= pb.Count
38 | } else {
39 | if i > 0 {
40 | pa := p.Blocks[i-1]
41 | if pa.EndLine >= pb.EndLine && (pa.EndLine != pb.EndLine || pa.EndCol > pb.EndCol) {
42 | log.Fatalf("OVERLAP BEFORE: %v %v %v", p.FileName, pa, pb)
43 | }
44 | }
45 | if i < len(p.Blocks)-1 {
46 | pa := p.Blocks[i+1]
47 | if pa.StartLine <= pb.StartLine && (pa.StartLine != pb.StartLine || pa.StartCol < pb.StartCol) {
48 | log.Fatalf("OVERLAP AFTER: %v %v %v", p.FileName, pa, pb)
49 | }
50 | }
51 | p.Blocks = append(p.Blocks, cover.ProfileBlock{})
52 | copy(p.Blocks[i+1:], p.Blocks[i:])
53 | p.Blocks[i] = pb
54 | }
55 | return i + 1
56 | }
57 |
58 | func addProfile(profiles []*cover.Profile, p *cover.Profile) []*cover.Profile {
59 | i := sort.Search(len(profiles), func(i int) bool { return profiles[i].FileName >= p.FileName })
60 | if i < len(profiles) && profiles[i].FileName == p.FileName {
61 | mergeProfiles(profiles[i], p)
62 | } else {
63 | profiles = append(profiles, nil)
64 | copy(profiles[i+1:], profiles[i:])
65 | profiles[i] = p
66 | }
67 | return profiles
68 | }
69 |
--------------------------------------------------------------------------------
/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.11' + 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 plugin_repo = 'plugins/' + 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-push',
78 | image: golang,
79 | pull: 'always',
80 | environment: {
81 | CGO_ENABLED: '0',
82 | GO111MODULE: 'on',
83 | },
84 | commands: [
85 | 'go build -v -ldflags "-X main.version=${DRONE_COMMIT_SHA:0:8}" -a -tags netgo -o release/' + os + '/' + arch + '/' + name + extension,
86 | ],
87 | when: {
88 | event: {
89 | exclude: ['tag'],
90 | },
91 | },
92 | },
93 | {
94 | name: 'build-tag',
95 | image: golang,
96 | pull: 'always',
97 | environment: {
98 | CGO_ENABLED: '0',
99 | GO111MODULE: 'on',
100 | },
101 | commands: [
102 | 'go build -v -ldflags "-X main.version=${DRONE_TAG##v}" -a -tags netgo -o release/' + os + '/' + arch + '/' + name + extension,
103 | ],
104 | when: {
105 | event: ['tag'],
106 | },
107 | },
108 | {
109 | name: 'executable',
110 | image: golang,
111 | pull: 'always',
112 | commands: [
113 | './release/' + os + '/' + arch + '/' + name + extension + ' --help',
114 | ],
115 | },
116 | {
117 | name: 'dryrun',
118 | image: 'plugins/docker:' + tag,
119 | pull: 'always',
120 | settings: {
121 | dry_run: true,
122 | tags: tag,
123 | dockerfile: 'docker/Dockerfile.' + file_suffix,
124 | daemon_off: if is_windows then 'true' else 'false',
125 | repo: plugin_repo,
126 | username: { from_secret: 'docker_username' },
127 | password: { from_secret: 'docker_password' },
128 | },
129 | volumes: if std.length(volumes) > 0 then volumes,
130 | when: {
131 | event: ['pull_request'],
132 | },
133 | },
134 | {
135 | name: 'publish',
136 | image: 'plugins/docker:' + tag,
137 | pull: 'always',
138 | settings: {
139 | auto_tag: true,
140 | auto_tag_suffix: tag,
141 | daemon_off: if is_windows then 'true' else 'false',
142 | dockerfile: 'docker/Dockerfile.' + file_suffix,
143 | repo: plugin_repo,
144 | username: { from_secret: 'docker_username' },
145 | password: { from_secret: 'docker_password' },
146 | },
147 | volumes: if std.length(volumes) > 0 then volumes,
148 | when: {
149 | event: {
150 | exclude: ['pull_request'],
151 | },
152 | },
153 | },
154 | ],
155 | trigger: {
156 | ref: [
157 | 'refs/heads/master',
158 | 'refs/tags/**',
159 | 'refs/pull/**',
160 | ],
161 | },
162 | depends_on: [test_pipeline_name],
163 | volumes: if is_windows then [{ name: windows_pipe_volume, host: { path: windows_pipe } }],
164 | },
165 |
166 | notifications(os='linux', arch='amd64', version='', depends_on=[])::
167 | {
168 | kind: 'pipeline',
169 | name: 'notifications',
170 | platform: {
171 | os: os,
172 | arch: arch,
173 | version: if std.length(version) > 0 then version,
174 | },
175 | steps: [
176 | {
177 | name: 'manifest',
178 | image: 'plugins/manifest',
179 | pull: 'always',
180 | settings: {
181 | username: { from_secret: 'docker_username' },
182 | password: { from_secret: 'docker_password' },
183 | spec: 'docker/manifest.tmpl',
184 | ignore_missing: true,
185 | auto_tag: true,
186 | },
187 | },
188 | {
189 | name: 'microbadger',
190 | image: 'plugins/webhook',
191 | pull: 'always',
192 | settings: {
193 | urls: { from_secret: 'microbadger_url' },
194 | },
195 | },
196 | ],
197 | trigger: {
198 | ref: [
199 | 'refs/heads/master',
200 | 'refs/tags/**',
201 | ],
202 | },
203 | depends_on: depends_on,
204 | },
205 | }
206 |
--------------------------------------------------------------------------------