├── .drone.yml
├── .github
├── issue_template.md
├── pull_request_template.md
└── security.md
├── .github_changelog_generator
├── .gitignore
├── BUILDING.md
├── CHANGELOG.md
├── HISTORY.md
├── LICENSE.md
├── README.md
├── command
├── command.go
├── compile.go
├── copy.go
├── daemon
│ ├── config.go
│ ├── daemon.go
│ └── process.go
├── exec.go
└── internal
│ └── flags.go
├── docker
├── Dockerfile.linux.amd64
├── Dockerfile.linux.arm64
├── Dockerfile.linux.ppc64le
├── Dockerfile.windows.1809
├── Dockerfile.windows.ltsc2022
└── manifest.tmpl
├── engine
├── compiler
│ ├── clone.go
│ ├── clone_test.go
│ ├── compiler.go
│ ├── compiler_test.go
│ ├── script.go
│ ├── script_test.go
│ ├── shell
│ │ ├── powershell
│ │ │ ├── powershell.go
│ │ │ └── powershell_test.go
│ │ ├── shell.go
│ │ └── shell_test.go
│ ├── step.go
│ ├── testdata
│ │ ├── graph.json
│ │ ├── graph.yml
│ │ ├── match.json
│ │ ├── match.yml
│ │ ├── noclone_graph.json
│ │ ├── noclone_graph.yml
│ │ ├── noclone_serial.json
│ │ ├── noclone_serial.yml
│ │ ├── run_always.json
│ │ ├── run_always.yml
│ │ ├── run_failure.json
│ │ ├── run_failure.yml
│ │ ├── secret.yml
│ │ ├── serial.json
│ │ ├── serial.yml
│ │ └── steps.yml
│ ├── util.go
│ ├── util_test.go
│ ├── workspace.go
│ └── workspace_test.go
├── const.go
├── const_test.go
├── convert.go
├── engine.go
├── linter
│ ├── linter.go
│ ├── linter_test.go
│ └── testdata
│ │ ├── duplicate_name.yml
│ │ ├── duplicate_step.yml
│ │ ├── duplicate_step_service.yml
│ │ ├── invalid_arch.yml
│ │ ├── invalid_os.yml
│ │ ├── missing_build_image.yml
│ │ ├── missing_dep.yml
│ │ ├── missing_image.yml
│ │ ├── missing_name.yml
│ │ ├── pipeline_device.yml
│ │ ├── pipeline_dns.yml
│ │ ├── pipeline_dns_search.yml
│ │ ├── pipeline_extra_hosts.yml
│ │ ├── pipeline_network_mode.yml
│ │ ├── pipeline_port_host.yml
│ │ ├── pipeline_privileged.yml
│ │ ├── pipeline_volume_invalid_name.yml
│ │ ├── service_device.yml
│ │ ├── service_port_host.yml
│ │ ├── simple.yml
│ │ ├── volume_empty_dir.yml
│ │ ├── volume_empty_dir_memory.yml
│ │ ├── volume_host_path.yml
│ │ ├── volume_invalid_name.yml
│ │ ├── volume_missing_name.yml
│ │ └── volume_restricted.yml
├── resource
│ ├── linter.go
│ ├── lookup.go
│ ├── lookup_test.go
│ ├── parser.go
│ ├── parser_test.go
│ ├── pipeline.go
│ ├── pipeline_test.go
│ └── testdata
│ │ ├── linterr.yml
│ │ ├── malformed.yml
│ │ ├── manifest.yml
│ │ ├── nilstep.yml
│ │ └── nomatch.yml
├── spec.go
├── testdata
│ ├── network_bridge.yml
│ ├── network_default.yml
│ ├── network_host.yml
│ ├── status_failure.yml
│ ├── status_success.yml
│ ├── volume_host.yml
│ ├── volume_mem.yml
│ ├── volume_temp.yml
│ ├── workspace_custom.yml
│ ├── workspace_default.yml
│ └── workspace_legacy.yml
├── util.go
└── util_test.go
├── go.mod
├── go.sum
├── internal
├── docker
│ ├── docker.go
│ ├── errors
│ │ ├── errors.go
│ │ └── errors_test.go
│ ├── image
│ │ ├── image.go
│ │ └── image_test.go
│ ├── jsonmessage
│ │ ├── jsonmessage.go
│ │ └── testdata
│ │ │ └── alpine.json
│ └── stdcopy
│ │ └── stdcopy.go
├── encoder
│ ├── encoder.go
│ └── encoder_test.go
├── internal.go
└── match
│ ├── match.go
│ └── match_test.go
├── licenses
├── Polyform-Free-Trial.md
└── Polyform-Small-Business.md
├── main.go
└── scripts
└── build.sh
/.drone.yml:
--------------------------------------------------------------------------------
1 | kind: pipeline
2 | type: vm
3 | name: testing
4 |
5 | pool:
6 | use: ubuntu
7 |
8 | platform:
9 | os: linux
10 | arch: amd64
11 |
12 | steps:
13 | - name: test
14 | image: golang:1.16
15 | commands:
16 | - go test -cover ./...
17 | volumes:
18 | - name: go
19 | path: /go
20 |
21 | - name: build
22 | image: golang:1.16
23 | commands:
24 | - sh scripts/build.sh
25 | volumes:
26 | - name: go
27 | path: /go
28 |
29 | volumes:
30 | - name: go
31 | temp: {}
32 |
33 | ---
34 |
35 | kind: pipeline
36 | type: vm
37 | name: linux-amd64
38 | platform:
39 | os: linux
40 | arch: amd64
41 | pool:
42 | use: ubuntu
43 |
44 | steps:
45 | - name: environment
46 | image: golang:1.16
47 | pull: always
48 | environment:
49 | CGO_ENABLED: "0"
50 | commands:
51 | - go version
52 | - go env
53 | - name: build
54 | image: golang:1.16
55 | environment:
56 | CGO_ENABLED: "0"
57 | commands:
58 | - go build -o release/linux/amd64/drone-runner-docker
59 | - name: publish
60 | image: plugins/docker
61 | pull: if-not-exists
62 | settings:
63 | repo: drone/drone-runner-docker
64 | auto_tag: true
65 | auto_tag_suffix: linux-amd64
66 | dockerfile: docker/Dockerfile.linux.amd64
67 | username:
68 | from_secret: docker_username
69 | password:
70 | from_secret: docker_password
71 | when:
72 | ref:
73 | - refs/heads/master
74 | - refs/tags/*
75 |
76 | depends_on:
77 | - testing
78 |
79 | trigger:
80 | ref:
81 | - refs/heads/master
82 | - refs/tags/**
83 | - refs/pull/**
84 |
85 | ---
86 |
87 |
88 | kind: pipeline
89 | type: vm
90 | name: linux-arm64
91 | platform:
92 | os: linux
93 | arch: arm64
94 | pool:
95 | use: ubuntu_arm64
96 |
97 | steps:
98 | - name: environment
99 | image: golang:1.16
100 | pull: always
101 | environment:
102 | CGO_ENABLED: "0"
103 | commands:
104 | - go version
105 | - go env
106 | - name: build
107 | image: golang:1.16
108 | environment:
109 | CGO_ENABLED: "0"
110 | commands:
111 | - go build -o release/linux/arm64/drone-runner-docker
112 | - name: publish
113 | image: plugins/docker
114 | pull: if-not-exists
115 | settings:
116 | repo: drone/drone-runner-docker
117 | auto_tag: true
118 | auto_tag_suffix: linux-arm64
119 | dockerfile: docker/Dockerfile.linux.arm64
120 | username:
121 | from_secret: docker_username
122 | password:
123 | from_secret: docker_password
124 | when:
125 | ref:
126 | - refs/heads/master
127 | - refs/tags/*
128 |
129 | depends_on:
130 | - testing
131 |
132 | trigger:
133 | ref:
134 | - refs/heads/master
135 | - refs/tags/**
136 | - refs/pull/**
137 |
138 | ---
139 | kind: pipeline
140 | type: vm
141 | name: windows-1809
142 |
143 | platform:
144 | os: windows
145 | arch: amd64
146 |
147 | pool:
148 | use: windows
149 |
150 | steps:
151 | - name: environment
152 | image: golang:1.16
153 | pull: always
154 | environment:
155 | CGO_ENABLED: "0"
156 | commands:
157 | - go version
158 | - go env
159 | - name: build
160 | image: golang:1.16
161 | environment:
162 | CGO_ENABLED: "0"
163 | commands:
164 | - go build -o release/windows/amd64/drone-runner-docker.exe
165 | - name: docker
166 | image: plugins/docker
167 | settings:
168 | dockerfile: docker/Dockerfile.windows.1809
169 | repo: drone/drone-runner-docker
170 | username:
171 | from_secret: docker_username
172 | password:
173 | from_secret: docker_password
174 | auto_tag: true
175 | auto_tag_suffix: windows-1809-amd64
176 | daemon_off: true
177 | purge: false
178 | when:
179 | ref:
180 | - refs/heads/master
181 | - refs/tags/**
182 |
183 | depends_on:
184 | - testing
185 |
186 | trigger:
187 | ref:
188 | - refs/heads/master
189 | - refs/tags/**
190 | - refs/pull/**
191 |
192 | ---
193 | kind: pipeline
194 | type: vm
195 | name: windows-ltsc2022
196 |
197 | platform:
198 | os: windows
199 | arch: amd64
200 |
201 | pool:
202 | use: windows-2022
203 |
204 | steps:
205 | - name: environment
206 | image: golang:1.16
207 | pull: always
208 | environment:
209 | CGO_ENABLED: "0"
210 | commands:
211 | - go version
212 | - go env
213 | - name: build
214 | image: golang:1.16
215 | environment:
216 | CGO_ENABLED: "0"
217 | commands:
218 | - go build -o release/windows/amd64/drone-runner-docker.exe
219 | - name: docker
220 | image: plugins/docker
221 | settings:
222 | dockerfile: docker/Dockerfile.windows.ltsc2022
223 | repo: drone/drone-runner-docker
224 | username:
225 | from_secret: docker_username
226 | password:
227 | from_secret: docker_password
228 | auto_tag: true
229 | auto_tag_suffix: windows-ltsc2022-amd64
230 | daemon_off: true
231 | purge: false
232 | when:
233 | ref:
234 | - refs/heads/master
235 | - refs/tags/**
236 |
237 | depends_on:
238 | - testing
239 |
240 | trigger:
241 | ref:
242 | - refs/heads/master
243 | - refs/tags/**
244 | - refs/pull/**
245 |
246 | ---
247 |
248 | kind: pipeline
249 | type: docker
250 | name: manifest
251 |
252 | steps:
253 | - name: manifest
254 | image: plugins/manifest
255 | settings:
256 | spec: docker/manifest.tmpl
257 | auto_tag: true
258 | ignore_missing: true
259 | password:
260 | from_secret: docker_password
261 | username:
262 | from_secret: docker_username
263 |
264 | depends_on:
265 | - linux-amd64
266 | - linux-arm64
267 | - windows-1809
268 | - windows-ltsc2022
269 |
270 | trigger:
271 | ref:
272 | - refs/heads/master
273 | - refs/tags/*
274 |
--------------------------------------------------------------------------------
/.github/issue_template.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drone-runners/drone-runner-docker/9ea626aa421794087058fdea6199257a88c12af8/.github/issue_template.md
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drone-runners/drone-runner-docker/9ea626aa421794087058fdea6199257a88c12af8/.github/pull_request_template.md
--------------------------------------------------------------------------------
/.github/security.md:
--------------------------------------------------------------------------------
1 | # Security Policies and Procedures
2 |
3 | This document outlines security procedures and general policies for this project.
4 |
5 | * [Reporting a Bug](#reporting-a-bug)
6 | * [Disclosure Policy](#disclosure-policy)
7 | * [Comments on this Policy](#comments-on-this-policy)
8 |
9 | ## Reporting a Bug
10 |
11 | Report security bugs by emailing the lead maintainer at security@drone.io.
12 |
13 | The lead maintainer will acknowledge your email within 48 hours, and will send a
14 | more detailed response within 48 hours indicating the next steps in handling
15 | your report. After the initial reply to your report, the security team will
16 | endeavor to keep you informed of the progress towards a fix and full
17 | announcement, and may ask for additional information or guidance.
18 |
19 | Report security bugs in third-party packages to the person or team maintaining
20 | the module.
21 |
22 | ## Disclosure Policy
23 |
24 | When the security team receives a security bug report, they will assign it to a
25 | primary handler. This person will coordinate the fix and release process,
26 | involving the following steps:
27 |
28 | * Confirm the problem and determine the affected versions.
29 | * Audit code to find any potential similar problems.
30 | * Prepare fixes for all releases still under maintenance. These fixes will be
31 | released as fast as possible.
32 |
33 | ## Comments on this Policy
34 |
35 | If you have suggestions on how this process could be improved please submit a
36 | pull request.
--------------------------------------------------------------------------------
/.github_changelog_generator:
--------------------------------------------------------------------------------
1 | since-tag=v1.6.3
2 |
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | drone-runner-docker
2 | drone-runner-docker.exe
3 | release/*
4 | .docker
5 | .env
6 | NOTES*
7 |
--------------------------------------------------------------------------------
/BUILDING.md:
--------------------------------------------------------------------------------
1 | 1. Install go 1.13 or higher
2 | 2. Test
3 |
4 | go test ./...
5 |
6 | 3. Build binaries
7 |
8 | sh scripts/build_all.sh
9 |
10 | 4. Build images
11 |
12 | docker build -t drone/drone-runner-docker:latest-linux-amd64 -f docker/Dockerfile.linux.amd64 .
13 | docker build -t drone/drone-runner-docker:latest-linux-arm64 -f docker/Dockerfile.linux.arm64 .
14 | docker build -t drone/drone-runner-docker:latest-linux-arm -f docker/Dockerfile.linux.arm .
15 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## [v1.8.3](https://github.com/drone-runners/drone-runner-docker/tree/v1.8.3) (2023-01-06)
4 |
5 | [Full Changelog](https://github.com/drone-runners/drone-runner-docker/compare/v1.8.2...v1.8.3)
6 |
7 | **Fixed bugs:**
8 |
9 | - \(dron-434\) set platform in docker build step [\#57](https://github.com/drone-runners/drone-runner-docker/pull/57) ([tphoney](https://github.com/tphoney))
10 | - Revert "Dockerfile: Add target architecture to Docker images" [\#56](https://github.com/drone-runners/drone-runner-docker/pull/56) ([tphoney](https://github.com/tphoney))
11 | - Dockerfile: Add target architecture to Docker images [\#54](https://github.com/drone-runners/drone-runner-docker/pull/54) ([jnohlgard](https://github.com/jnohlgard))
12 |
13 | **Merged pull requests:**
14 |
15 | - \(maint\) move to harness.drone.io [\#59](https://github.com/drone-runners/drone-runner-docker/pull/59) ([tphoney](https://github.com/tphoney))
16 |
17 | ## [v1.8.2](https://github.com/drone-runners/drone-runner-docker/tree/v1.8.2) (2022-06-14)
18 |
19 | [Full Changelog](https://github.com/drone-runners/drone-runner-docker/compare/v1.8.1...v1.8.2)
20 |
21 | **Implemented enhancements:**
22 |
23 | - Add retries option to the clone step [\#44](https://github.com/drone-runners/drone-runner-docker/pull/44) ([julienduchesne](https://github.com/julienduchesne))
24 |
25 | **Merged pull requests:**
26 |
27 | - release prep for v1.8.2 [\#49](https://github.com/drone-runners/drone-runner-docker/pull/49) ([eoinmcafee00](https://github.com/eoinmcafee00))
28 |
29 | ## [v1.8.1](https://github.com/drone-runners/drone-runner-docker/tree/v1.8.1) (2022-04-19)
30 |
31 | [Full Changelog](https://github.com/drone-runners/drone-runner-docker/compare/v1.8.0...v1.8.1)
32 |
33 | **Fixed bugs:**
34 |
35 | - \(dron-254\) handle wait for log transfer [\#46](https://github.com/drone-runners/drone-runner-docker/pull/46) ([tphoney](https://github.com/tphoney))
36 | - Fix 3 doc typos in compiler.go [\#45](https://github.com/drone-runners/drone-runner-docker/pull/45) ([mach6](https://github.com/mach6))
37 | - feat\(engine\): Add debug logs for Docker.Destroy errors [\#20](https://github.com/drone-runners/drone-runner-docker/pull/20) ([jvrplmlmn](https://github.com/jvrplmlmn))
38 |
39 | **Merged pull requests:**
40 |
41 | - \(maint\) release prep v1.8.1 [\#47](https://github.com/drone-runners/drone-runner-docker/pull/47) ([tphoney](https://github.com/tphoney))
42 |
43 | ## [v1.8.0](https://github.com/drone-runners/drone-runner-docker/tree/v1.8.0) (2021-11-18)
44 |
45 | [Full Changelog](https://github.com/drone-runners/drone-runner-docker/compare/v1.7.0...v1.8.0)
46 |
47 | **Implemented enhancements:**
48 |
49 | - create and store card data [\#41](https://github.com/drone-runners/drone-runner-docker/pull/41) ([eoinmcafee00](https://github.com/eoinmcafee00))
50 |
51 | **Merged pull requests:**
52 |
53 | - release prep for v1.8.0 [\#43](https://github.com/drone-runners/drone-runner-docker/pull/43) ([eoinmcafee00](https://github.com/eoinmcafee00))
54 |
55 | ## [v1.7.0](https://github.com/drone-runners/drone-runner-docker/tree/v1.7.0) (2021-11-02)
56 |
57 | [Full Changelog](https://github.com/drone-runners/drone-runner-docker/compare/v1.6.3...v1.7.0)
58 |
59 | **Implemented enhancements:**
60 |
61 | - \(maint\) prep for v1.7.0 [\#42](https://github.com/drone-runners/drone-runner-docker/pull/42) ([tphoney](https://github.com/tphoney))
62 | - Expose the authorized keys tmate feature [\#18](https://github.com/drone-runners/drone-runner-docker/pull/18) ([julienduchesne](https://github.com/julienduchesne))
63 | - Support ppc64le [\#17](https://github.com/drone-runners/drone-runner-docker/pull/17) ([isuruf](https://github.com/isuruf))
64 | - \(feat\) adding image field to step [\#14](https://github.com/drone-runners/drone-runner-docker/pull/14) ([tphoney](https://github.com/tphoney))
65 | - check privileged image whitelist in service section [\#9](https://github.com/drone-runners/drone-runner-docker/pull/9) ([divialth](https://github.com/divialth))
66 |
67 | **Fixed bugs:**
68 |
69 | - \(maint\) bump go version in build to match go.mod [\#40](https://github.com/drone-runners/drone-runner-docker/pull/40) ([tphoney](https://github.com/tphoney))
70 | - bump drone/runner-go to include trigger env var [\#32](https://github.com/drone-runners/drone-runner-docker/pull/32) ([willejs](https://github.com/willejs))
71 | - Use correct name for the root depends\_on yaml key. [\#28](https://github.com/drone-runners/drone-runner-docker/pull/28) ([staffanselander](https://github.com/staffanselander))
72 | - \(DRON-82\) update envsubst to prevent compiler panics [\#24](https://github.com/drone-runners/drone-runner-docker/pull/24) ([tphoney](https://github.com/tphoney))
73 |
74 | **Merged pull requests:**
75 |
76 | - Use IsRestrictedVolume from runner-go [\#26](https://github.com/drone-runners/drone-runner-docker/pull/26) ([marko-gacesa](https://github.com/marko-gacesa))
77 | - chore: update runner-go to 1.8.0 [\#25](https://github.com/drone-runners/drone-runner-docker/pull/25) ([fgierlinger](https://github.com/fgierlinger))
78 | - Test that inverse matches work for `DRONE_LIMIT_REPOS` [\#19](https://github.com/drone-runners/drone-runner-docker/pull/19) ([jvrplmlmn](https://github.com/jvrplmlmn))
79 |
80 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
81 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
82 |
83 | ## 1.6.3
84 | ### Fixed
85 | - use path prefix when evaluating restricted volume mounts. See [#ea74fa2](https://github.com/drone-runners/drone-runner-docker/commit/ea74fa2ba442eacb0812ad5983c305a16b6763bc).
86 |
87 | ## 1.6.2
88 | ### Added
89 | - support for self-hosted tmate instances
90 |
91 | ## 1.6.1
92 | ### Changed
93 | - restrict temporary volumes used with docker plugins
94 | - restrict environment variables used with docker plugins
95 |
96 | ## 1.6.0
97 | ### Added
98 | - experimental support for remote debugging with tmate, disabled by default
99 |
100 | ### Fixed
101 | - exit code 78 not properly exiting early when pipeline has services (from runner-go)
102 |
103 | ## 1.5.3
104 | ### Fixed
105 | - unexpected http code from server must always fail pipeline (from runner-go)
106 |
107 | ## 1.5.2
108 | ### Added
109 | - trace logging for semaphore acquisition and release
110 |
111 | ### Fixed
112 | - failure to acquire semaphore due to error should fail the pipeline
113 | - failure to acquire semaphore due to context deadline should cancel the pipeline
114 |
115 | ## 1.5.1
116 | ### Fixed
117 | - cancel a build should result in cancel status, not error status
118 |
119 | ## 1.5.0
120 | ### Added
121 | - option to disable netrc for non-clone steps
122 | - option to customize docker bridge networks
123 |
124 | ### Changed
125 | - upgrade docker client
126 |
127 | ## 1.4.0
128 | ### Added
129 | - support for windows 1909
130 | - support for nomad runner execution
131 |
132 | ## 1.3.0
133 | ### Added
134 | - support for setting default container shmsize
135 |
136 | ### Changed
137 | - update environment extension protocol to version 2
138 | - registry credentials stored in repository secrets take precedence over globals
139 |
140 | ### Fixed
141 | - ignoring global memory limit and memory swap limit
142 |
143 | ### Added
144 | - support for environment extension variable masking
145 | - support for username/password in config.json files
146 |
147 | ## 1.2.1
148 | ### Added
149 | - deployment id environment variable
150 | - support for multi-line secret masking
151 | - trace logging prints external registry details
152 |
153 | ### Fixed
154 | - do not override user defined mem_limit and memswap_limit
155 | - remove scheme when comparing image and registry hostnames
156 |
157 | ## 1.2.0
158 | ### Added
159 | - support for mem_limit and memswap_limit
160 |
161 | ## 1.1.0
162 | ### Changed
163 |
164 | - abstract polling and execution to runner-go library
165 | - use trace level logging for context errors
166 | - prefix docker resource names
167 |
168 | ### Fixed
169 | - initialize environment map to prevent nil pointer
170 |
171 | ### Added
172 | - support for global environment variable extension
173 |
174 | ## 1.0.1
175 | ### Fixed
176 |
177 | - handle pipelines with missing names
178 | - prevent mounting /run/drone directory
179 |
180 | ## 1.0.0
181 | ### Added
182 |
183 | - ported docker pipelines to runner-go framework
184 | - support for pipeline environment variables
185 | - support for shm_size
186 |
187 |
188 | \* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*
189 |
--------------------------------------------------------------------------------
/HISTORY.md:
--------------------------------------------------------------------------------
1 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
2 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
3 |
4 | ## 1.6.3
5 | ### Fixed
6 | - use path prefix when evaluating restricted volume mounts. See [#ea74fa2](https://github.com/drone-runners/drone-runner-docker/commit/ea74fa2ba442eacb0812ad5983c305a16b6763bc).
7 |
8 | ## 1.6.2
9 | ### Added
10 | - support for self-hosted tmate instances
11 |
12 | ## 1.6.1
13 | ### Changed
14 | - restrict temporary volumes used with docker plugins
15 | - restrict environment variables used with docker plugins
16 |
17 | ## 1.6.0
18 | ### Added
19 | - experimental support for remote debugging with tmate, disabled by default
20 |
21 | ### Fixed
22 | - exit code 78 not properly exiting early when pipeline has services (from runner-go)
23 |
24 | ## 1.5.3
25 | ### Fixed
26 | - unexpected http code from server must always fail pipeline (from runner-go)
27 |
28 | ## 1.5.2
29 | ### Added
30 | - trace logging for semaphore acquisition and release
31 |
32 | ### Fixed
33 | - failure to acquire semaphore due to error should fail the pipeline
34 | - failure to acquire semaphore due to context deadline should cancel the pipeline
35 |
36 | ## 1.5.1
37 | ### Fixed
38 | - cancel a build should result in cancel status, not error status
39 |
40 | ## 1.5.0
41 | ### Added
42 | - option to disable netrc for non-clone steps
43 | - option to customize docker bridge networks
44 |
45 | ### Changed
46 | - upgrade docker client
47 |
48 | ## 1.4.0
49 | ### Added
50 | - support for windows 1909
51 | - support for nomad runner execution
52 |
53 | ## 1.3.0
54 | ### Added
55 | - support for setting default container shmsize
56 |
57 | ### Changed
58 | - update environment extension protocol to version 2
59 | - registry credentials stored in repository secrets take precedence over globals
60 |
61 | ### Fixed
62 | - ignoring global memory limit and memory swap limit
63 |
64 | ### Added
65 | - support for environment extension variable masking
66 | - support for username/password in config.json files
67 |
68 | ## 1.2.1
69 | ### Added
70 | - deployment id environment variable
71 | - support for multi-line secret masking
72 | - trace logging prints external registry details
73 |
74 | ### Fixed
75 | - do not override user defined mem_limit and memswap_limit
76 | - remove scheme when comparing image and registry hostnames
77 |
78 | ## 1.2.0
79 | ### Added
80 | - support for mem_limit and memswap_limit
81 |
82 | ## 1.1.0
83 | ### Changed
84 |
85 | - abstract polling and execution to runner-go library
86 | - use trace level logging for context errors
87 | - prefix docker resource names
88 |
89 | ### Fixed
90 | - initialize environment map to prevent nil pointer
91 |
92 | ### Added
93 | - support for global environment variable extension
94 |
95 | ## 1.0.1
96 | ### Fixed
97 |
98 | - handle pipelines with missing names
99 | - prevent mounting /run/drone directory
100 |
101 | ## 1.0.0
102 | ### Added
103 |
104 | - ported docker pipelines to runner-go framework
105 | - support for pipeline environment variables
106 | - support for shm_size
107 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | [Polyform-Small-Business-1.0.0](https://polyformproject.org/licenses/small-business/1.0.0) OR
2 | [Polyform-Free-Trial-1.0.0](https://polyformproject.org/licenses/free-trial/1.0.0)
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # drone-runner-docker
2 |
3 | The `docker` runner executes pipelines inside Docker containers. This runner is intended for linux workloads that are suitable for execution inside containers. This requires Drone server `1.6.0` or higher.
4 |
5 | Documentation:
6 | https://docs.drone.io/runner/docker/overview/
7 |
8 | Technical Support:
9 | https://discourse.drone.io
10 |
11 | Issue Tracker and Roadmap:
12 | https://trello.com/b/ttae5E5o/drone
13 |
14 | ## Release procedure
15 |
16 | Run the changelog generator.
17 |
18 | ```BASH
19 | docker run -it --rm -v "$(pwd)":/usr/local/src/your-app githubchangeloggenerator/github-changelog-generator -u drone-runners -p drone-runner-docker -t
20 | ```
21 |
22 | You can generate a token by logging into your GitHub account and going to Settings -> Personal access tokens.
23 |
24 | Next we tag the PR's with the fixes or enhancements labels. If the PR does not fufil the requirements, do not add a label.
25 |
26 | **Before moving on make sure to update the version file `version/version.go && version/version_test.go`.**
27 |
28 | Run the changelog generator again with the future version according to semver.
29 |
30 | ```BASH
31 | docker run -it --rm -v "$(pwd)":/usr/local/src/your-app githubchangeloggenerator/github-changelog-generator -u drone-runners -p drone-runner-docker -t --future-release v1.0.0
32 | ```
33 |
34 | Create your pull request for the release. Get it merged then tag the release.
--------------------------------------------------------------------------------
/command/command.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Drone.IO Inc. All rights reserved.
2 | // Use of this source code is governed by the Polyform License
3 | // that can be found in the LICENSE file.
4 |
5 | package command
6 |
7 | import (
8 | "context"
9 | "os"
10 |
11 | "github.com/drone-runners/drone-runner-docker/command/daemon"
12 |
13 | "gopkg.in/alecthomas/kingpin.v2"
14 | )
15 |
16 | // program version
17 | var version = "0.0.0"
18 |
19 | // empty context
20 | var nocontext = context.Background()
21 |
22 | // Command parses the command line arguments and then executes a
23 | // subcommand program.
24 | func Command() {
25 | app := kingpin.New("drone", "drone docker runner")
26 | registerCompile(app)
27 | registerExec(app)
28 | registerCopy(app)
29 | daemon.Register(app)
30 |
31 | kingpin.Version(version)
32 | kingpin.MustParse(app.Parse(os.Args[1:]))
33 | }
34 |
--------------------------------------------------------------------------------
/command/compile.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Drone.IO Inc. All rights reserved.
2 | // Use of this source code is governed by the Polyform License
3 | // that can be found in the LICENSE file.
4 |
5 | package command
6 |
7 | import (
8 | "encoding/json"
9 | "fmt"
10 | "io/ioutil"
11 | "os"
12 | "strings"
13 |
14 | "github.com/drone-runners/drone-runner-docker/command/internal"
15 | "github.com/drone-runners/drone-runner-docker/engine/compiler"
16 | "github.com/drone-runners/drone-runner-docker/engine/linter"
17 | "github.com/drone-runners/drone-runner-docker/engine/resource"
18 | "github.com/drone/envsubst"
19 | "github.com/drone/runner-go/environ"
20 | "github.com/drone/runner-go/environ/provider"
21 | "github.com/drone/runner-go/manifest"
22 | "github.com/drone/runner-go/pipeline/runtime"
23 | "github.com/drone/runner-go/registry"
24 | "github.com/drone/runner-go/secret"
25 |
26 | "gopkg.in/alecthomas/kingpin.v2"
27 | )
28 |
29 | type compileCommand struct {
30 | *internal.Flags
31 |
32 | Source *os.File
33 | Privileged []string
34 | Networks []string
35 | Volumes map[string]string
36 | Environ map[string]string
37 | Labels map[string]string
38 | Secrets map[string]string
39 | Resources compiler.Resources
40 | Tmate compiler.Tmate
41 | Clone bool
42 | Config string
43 | }
44 |
45 | func (c *compileCommand) run(*kingpin.ParseContext) error {
46 | rawsource, err := ioutil.ReadAll(c.Source)
47 | if err != nil {
48 | return err
49 | }
50 |
51 | envs := environ.Combine(
52 | c.Environ,
53 | environ.System(c.System),
54 | environ.Repo(c.Repo),
55 | environ.Build(c.Build),
56 | environ.Stage(c.Stage),
57 | environ.Link(c.Repo, c.Build, c.System),
58 | c.Build.Params,
59 | )
60 |
61 | // string substitution function ensures that string
62 | // replacement variables are escaped and quoted if they
63 | // contain newlines.
64 | subf := func(k string) string {
65 | v := envs[k]
66 | if strings.Contains(v, "\n") {
67 | v = fmt.Sprintf("%q", v)
68 | }
69 | return v
70 | }
71 |
72 | // evaluates string replacement expressions and returns an
73 | // update configuration.
74 | config, err := envsubst.Eval(string(rawsource), subf)
75 | if err != nil {
76 | return err
77 | }
78 |
79 | // parse and lint the configuration
80 | manifest, err := manifest.ParseString(config)
81 | if err != nil {
82 | return err
83 | }
84 |
85 | // a configuration can contain multiple pipelines.
86 | // get a specific pipeline resource for execution.
87 | resource, err := resource.Lookup(c.Stage.Name, manifest)
88 | if err != nil {
89 | return err
90 | }
91 |
92 | // lint the pipeline and return an error if any
93 | // linting rules are broken
94 | lint := linter.New()
95 | err = lint.Lint(resource, c.Repo)
96 | if err != nil {
97 | return err
98 | }
99 |
100 | // compile the pipeline to an intermediate representation.
101 | comp := &compiler.Compiler{
102 | Environ: provider.Static(c.Environ),
103 | Labels: c.Labels,
104 | Resources: c.Resources,
105 | Tmate: c.Tmate,
106 | Privileged: append(c.Privileged, compiler.Privileged...),
107 | Networks: c.Networks,
108 | Volumes: c.Volumes,
109 | Secret: secret.StaticVars(c.Secrets),
110 | Registry: registry.Combine(
111 | registry.File(c.Config),
112 | ),
113 | }
114 |
115 | // when running a build locally cloning is always
116 | // disabled in favor of mounting the source code
117 | // from the current working directory.
118 | if c.Clone == false {
119 | comp.Mount, _ = os.Getwd()
120 | }
121 |
122 | args := runtime.CompilerArgs{
123 | Pipeline: resource,
124 | Manifest: manifest,
125 | Build: c.Build,
126 | Netrc: c.Netrc,
127 | Repo: c.Repo,
128 | Stage: c.Stage,
129 | System: c.System,
130 | Secret: secret.StaticVars(c.Secrets),
131 | }
132 | spec := comp.Compile(nocontext, args)
133 |
134 | // encode the pipeline in json format and print to the
135 | // console for inspection.
136 | enc := json.NewEncoder(os.Stdout)
137 | enc.SetIndent("", " ")
138 | enc.Encode(spec)
139 | return nil
140 | }
141 |
142 | func registerCompile(app *kingpin.Application) {
143 | c := new(compileCommand)
144 | c.Environ = map[string]string{}
145 | c.Secrets = map[string]string{}
146 | c.Labels = map[string]string{}
147 | c.Volumes = map[string]string{}
148 |
149 | cmd := app.Command("compile", "compile the yaml file").
150 | Action(c.run)
151 |
152 | cmd.Flag("source", "source file location").
153 | Default(".drone.yml").
154 | FileVar(&c.Source)
155 |
156 | cmd.Flag("clone", "enable cloning").
157 | BoolVar(&c.Clone)
158 |
159 | cmd.Flag("secrets", "secret parameters").
160 | StringMapVar(&c.Secrets)
161 |
162 | cmd.Flag("environ", "environment variables").
163 | StringMapVar(&c.Environ)
164 |
165 | cmd.Flag("labels", "container labels").
166 | StringMapVar(&c.Labels)
167 |
168 | cmd.Flag("networks", "container networks").
169 | StringsVar(&c.Networks)
170 |
171 | cmd.Flag("volumes", "container volumes").
172 | StringMapVar(&c.Volumes)
173 |
174 | cmd.Flag("privileged", "privileged docker images").
175 | StringsVar(&c.Privileged)
176 |
177 | cmd.Flag("cpu-period", "container cpu period").
178 | Int64Var(&c.Resources.CPUPeriod)
179 |
180 | cmd.Flag("cpu-quota", "container cpu quota").
181 | Int64Var(&c.Resources.CPUQuota)
182 |
183 | cmd.Flag("cpu-set", "container cpu set").
184 | StringsVar(&c.Resources.CPUSet)
185 |
186 | cmd.Flag("cpu-shares", "container cpu shares").
187 | Int64Var(&c.Resources.CPUShares)
188 |
189 | cmd.Flag("memory", "container memory limit").
190 | Int64Var(&c.Resources.Memory)
191 |
192 | cmd.Flag("memory-swap", "container memory swap limit").
193 | Int64Var(&c.Resources.MemorySwap)
194 |
195 | cmd.Flag("docker-config", "path to the docker config file").
196 | StringVar(&c.Config)
197 |
198 | cmd.Flag("tmate-image", "tmate docker image").
199 | Default("drone/drone-runner-docker:1").
200 | StringVar(&c.Tmate.Image)
201 |
202 | cmd.Flag("tmate-enabled", "tmate enabled").
203 | BoolVar(&c.Tmate.Enabled)
204 |
205 | cmd.Flag("tmate-server-host", "tmate server host").
206 | StringVar(&c.Tmate.Server)
207 |
208 | cmd.Flag("tmate-server-port", "tmate server port").
209 | StringVar(&c.Tmate.Port)
210 |
211 | cmd.Flag("tmate-server-rsa-fingerprint", "tmate server rsa fingerprint").
212 | StringVar(&c.Tmate.RSA)
213 |
214 | cmd.Flag("tmate-server-ed25519-fingerprint", "tmate server rsa fingerprint").
215 | StringVar(&c.Tmate.ED25519)
216 |
217 | cmd.Flag("tmate-authorized-keys", "tmate authorized keys").
218 | StringVar(&c.Tmate.AuthorizedKeys)
219 |
220 | // shared pipeline flags
221 | c.Flags = internal.ParseFlags(cmd)
222 | }
223 |
--------------------------------------------------------------------------------
/command/copy.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Drone.IO Inc. All rights reserved.
2 | // Use of this source code is governed by the Polyform License
3 | // that can be found in the LICENSE file.
4 |
5 | package command
6 |
7 | import (
8 | "io"
9 | "os"
10 |
11 | "gopkg.in/alecthomas/kingpin.v2"
12 | )
13 |
14 | type copyCommand struct {
15 | source string
16 | target string
17 | }
18 |
19 | func (c *copyCommand) run(*kingpin.ParseContext) error {
20 | return Copy(c.source, c.target)
21 | }
22 |
23 | func Copy(src, dst string) error {
24 | in, err := os.Open(src)
25 | if err != nil {
26 | return err
27 | }
28 | defer in.Close()
29 |
30 | out, err := os.Create(dst)
31 | if err != nil {
32 | return err
33 | }
34 | defer out.Close()
35 |
36 | _, err = io.Copy(out, in)
37 | if err != nil {
38 | return err
39 | }
40 |
41 | err = out.Sync()
42 | if err != nil {
43 | return err
44 | }
45 |
46 | info, err := os.Stat(src)
47 | if err != nil {
48 | return err
49 | }
50 |
51 | err = os.Chmod(dst, info.Mode())
52 | if err != nil {
53 | return err
54 | }
55 |
56 | return out.Close()
57 | }
58 |
59 | // Register registers the copy command.
60 | func registerCopy(app *kingpin.Application) {
61 | c := new(copyCommand)
62 |
63 | cmd := app.Command("copy", "entrypoint copy").
64 | Hidden().
65 | Action(c.run)
66 |
67 | cmd.Flag("source", "source binary path").
68 | Default("/bin/tmate").
69 | StringVar(&c.source)
70 |
71 | cmd.Flag("target", "target binary path").
72 | Default("/usr/drone/bin/tmate").
73 | StringVar(&c.target)
74 | }
75 |
--------------------------------------------------------------------------------
/command/daemon/config.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Drone.IO Inc. All rights reserved.
2 | // Use of this source code is governed by the Polyform License
3 | // that can be found in the LICENSE file.
4 |
5 | package daemon
6 |
7 | import (
8 | "fmt"
9 | "os"
10 |
11 | "github.com/joho/godotenv"
12 | "github.com/kelseyhightower/envconfig"
13 | )
14 |
15 | // Config stores the system configuration.
16 | type Config struct {
17 | Debug bool `envconfig:"DRONE_DEBUG"`
18 | Trace bool `envconfig:"DRONE_TRACE"`
19 |
20 | Client struct {
21 | Address string `ignored:"true"`
22 | Proto string `envconfig:"DRONE_RPC_PROTO" default:"http"`
23 | Host string `envconfig:"DRONE_RPC_HOST" required:"true"`
24 | Secret string `envconfig:"DRONE_RPC_SECRET" required:"true"`
25 | SkipVerify bool `envconfig:"DRONE_RPC_SKIP_VERIFY"`
26 | Dump bool `envconfig:"DRONE_RPC_DUMP_HTTP"`
27 | DumpBody bool `envconfig:"DRONE_RPC_DUMP_HTTP_BODY"`
28 | }
29 |
30 | Dashboard struct {
31 | Disabled bool `envconfig:"DRONE_UI_DISABLE"`
32 | Username string `envconfig:"DRONE_UI_USERNAME"`
33 | Password string `envconfig:"DRONE_UI_PASSWORD"`
34 | Realm string `envconfig:"DRONE_UI_REALM" default:"MyRealm"`
35 | }
36 |
37 | Server struct {
38 | Port string `envconfig:"DRONE_HTTP_BIND" default:":3000"`
39 | Proto string `envconfig:"DRONE_HTTP_PROTO"`
40 | Host string `envconfig:"DRONE_HTTP_HOST"`
41 | Acme bool `envconfig:"DRONE_HTTP_ACME"`
42 | }
43 |
44 | Runner struct {
45 | Name string `envconfig:"DRONE_RUNNER_NAME"`
46 | Capacity int `envconfig:"DRONE_RUNNER_CAPACITY" default:"2"`
47 | Procs int64 `envconfig:"DRONE_RUNNER_MAX_PROCS"`
48 | Environ map[string]string `envconfig:"DRONE_RUNNER_ENVIRON"`
49 | EnvFile string `envconfig:"DRONE_RUNNER_ENV_FILE"`
50 | Secrets map[string]string `envconfig:"DRONE_RUNNER_SECRETS"`
51 | Labels map[string]string `envconfig:"DRONE_RUNNER_LABELS"`
52 | Volumes map[string]string `envconfig:"DRONE_RUNNER_VOLUMES"`
53 | Devices []string `envconfig:"DRONE_RUNNER_DEVICES"`
54 | Networks []string `envconfig:"DRONE_RUNNER_NETWORKS"`
55 | NetworkOpts map[string]string `envconfig:"DRONE_RUNNER_NETWORK_OPTS"`
56 | Privileged []string `envconfig:"DRONE_RUNNER_PRIVILEGED_IMAGES"`
57 | Clone string `envconfig:"DRONE_RUNNER_CLONE_IMAGE"`
58 | }
59 |
60 | Platform struct {
61 | OS string `envconfig:"DRONE_PLATFORM_OS" default:"linux"`
62 | Arch string `envconfig:"DRONE_PLATFORM_ARCH" default:"amd64"`
63 | Kernel string `envconfig:"DRONE_PLATFORM_KERNEL"`
64 | Variant string `envconfig:"DRONE_PLATFORM_VARIANT"`
65 | }
66 |
67 | Limit struct {
68 | Repos []string `envconfig:"DRONE_LIMIT_REPOS"`
69 | Events []string `envconfig:"DRONE_LIMIT_EVENTS"`
70 | Trusted bool `envconfig:"DRONE_LIMIT_TRUSTED"`
71 | }
72 |
73 | Resources struct {
74 | Memory int64 `envconfig:"DRONE_MEMORY_LIMIT"`
75 | MemorySwap int64 `envconfig:"DRONE_MEMORY_SWAP_LIMIT"`
76 | CPUQuota int64 `envconfig:"DRONE_CPU_QUOTA"`
77 | CPUPeriod int64 `envconfig:"DRONE_CPU_PERIOD"`
78 | CPUShares int64 `envconfig:"DRONE_CPU_SHARES"`
79 | CPUSet []string `envconfig:"DRONE_CPU_SET"`
80 | ShmSize int64 `envconfig:"DRONE_SHM_SIZE"`
81 | }
82 |
83 | Environ struct {
84 | Endpoint string `envconfig:"DRONE_ENV_PLUGIN_ENDPOINT"`
85 | Token string `envconfig:"DRONE_ENV_PLUGIN_TOKEN"`
86 | SkipVerify bool `envconfig:"DRONE_ENV_PLUGIN_SKIP_VERIFY"`
87 | }
88 |
89 | Secret struct {
90 | Endpoint string `envconfig:"DRONE_SECRET_PLUGIN_ENDPOINT"`
91 | Token string `envconfig:"DRONE_SECRET_PLUGIN_TOKEN"`
92 | SkipVerify bool `envconfig:"DRONE_SECRET_PLUGIN_SKIP_VERIFY"`
93 | }
94 |
95 | Netrc struct {
96 | CloneOnly bool `envconfig:"DRONE_NETRC_CLONE_ONLY"`
97 | }
98 |
99 | Registry struct {
100 | Endpoint string `envconfig:"DRONE_REGISTRY_PLUGIN_ENDPOINT"`
101 | Token string `envconfig:"DRONE_REGISTRY_PLUGIN_TOKEN"`
102 | SkipVerify bool `envconfig:"DRONE_REGISTRY_PLUGIN_SKIP_VERIFY"`
103 | }
104 |
105 | Docker struct {
106 | Config string `envconfig:"DRONE_DOCKER_CONFIG"`
107 | Stream bool `envconfig:"DRONE_DOCKER_STREAM_PULL" default:"true"`
108 | }
109 |
110 | Tmate struct {
111 | Enabled bool `envconfig:"DRONE_TMATE_ENABLED" default:"false"`
112 | Image string `envconfig:"DRONE_TMATE_IMAGE" default:"drone/drone-runner-docker:1"`
113 | Server string `envconfig:"DRONE_TMATE_HOST"`
114 | Port string `envconfig:"DRONE_TMATE_PORT"`
115 | RSA string `envconfig:"DRONE_TMATE_FINGERPRINT_RSA"`
116 | ED25519 string `envconfig:"DRONE_TMATE_FINGERPRINT_ED25519"`
117 | AuthorizedKeys string `envconfig:"DRONE_TMATE_AUTHORIZED_KEYS"`
118 | }
119 | }
120 |
121 | // legacy environment variables. the key is the legacy
122 | // variable name, and the value is the new variable name.
123 | var legacy = map[string]string{
124 | "DRONE_MACHINE": "DRONE_RUNNER_NAME",
125 | "DRONE_RUNNER_OS": "DRONE_PLATFORM_OS",
126 | "DRONE_RUNNER_ARCH": "DRONE_PLATFORM_ARCH",
127 | "DRONE_RUNNER_KERNEL": "DRONE_PLATFORM_KERNEL",
128 | "DRONE_RUNNER_VARIANT": "DRONE_PLATFORM_VARIANT",
129 | "DRONE_REGISTRY_ENDPOINT": "DRONE_REGISTRY_PLUGIN_ENDPOINT",
130 | "DRONE_REGISTRY_SECRET": "DRONE_REGISTRY_PLUGIN_TOKEN",
131 | "DRONE_REGISTRY_PLUGIN_SECRET": "DRONE_REGISTRY_PLUGIN_TOKEN",
132 | "DRONE_REGISTRY_SKIP_VERIFY": "DRONE_REGISTRY_PLUGIN_SKIP_VERIFY",
133 | "DRONE_SECRET_ENDPOINT": "DRONE_SECRET_PLUGIN_ENDPOINT",
134 | "DRONE_SECRET_SECRET": "DRONE_SECRET_PLUGIN_TOKEN",
135 | "DRONE_SECRET_SKIP_VERIFY": "DRONE_SECRET_PLUGIN_SKIP_VERIFY",
136 | "DRONE_LIMIT_MEM_SWAP": "DRONE_MEMORY_SWAP_LIMIT",
137 | "DRONE_LIMIT_MEM": "DRONE_MEMORY_LIMIT",
138 | "DRONE_LIMIT_CPU_QUOTA": "DRONE_CPU_QUOTA",
139 | "DRONE_LIMIT_CPU_SHARES": "DRONE_CPU_SHARES",
140 | "DRONE_LIMIT_CPU_SET": "DRONE_CPU_SET",
141 | "DRONE_LOGS_DEBUG": "DRONE_DEBUG",
142 | "DRONE_LOGS_TRACE": "DRONE_TRACE",
143 | "DRONE_SERVER_PROTO": "DRONE_HTTP_PROTO",
144 | "DRONE_SERVER_HOST": "DRONE_HTTP_HOST",
145 | "DRONE_SERVER_PORT": "DRONE_HTTP_BIND",
146 | "DRONE_SERVER_ACME": "DRONE_HTTP_ACME",
147 | }
148 |
149 | func fromEnviron() (Config, error) {
150 | // loop through legacy environment variable and, if set
151 | // rewrite to the new variable name.
152 | for k, v := range legacy {
153 | if s, ok := os.LookupEnv(k); ok {
154 | os.Setenv(v, s)
155 | }
156 | }
157 |
158 | var config Config
159 | err := envconfig.Process("", &config)
160 | if err != nil {
161 | return config, err
162 | }
163 | if config.Runner.Environ == nil {
164 | config.Runner.Environ = map[string]string{}
165 | }
166 | if config.Runner.Name == "" {
167 | config.Runner.Name, _ = os.Hostname()
168 | }
169 | if config.Dashboard.Password == "" {
170 | config.Dashboard.Disabled = true
171 | }
172 | config.Client.Address = fmt.Sprintf(
173 | "%s://%s",
174 | config.Client.Proto,
175 | config.Client.Host,
176 | )
177 |
178 | // environment variables can be sourced from a separate
179 | // file. These variables are loaded and appended to the
180 | // environment list.
181 | if file := config.Runner.EnvFile; file != "" {
182 | envs, err := godotenv.Read(file)
183 | if err != nil {
184 | return config, err
185 | }
186 | for k, v := range envs {
187 | config.Runner.Environ[k] = v
188 | }
189 | }
190 |
191 | return config, nil
192 | }
193 |
--------------------------------------------------------------------------------
/command/daemon/daemon.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Drone.IO Inc. All rights reserved.
2 | // Use of this source code is governed by the Polyform License
3 | // that can be found in the LICENSE file.
4 |
5 | package daemon
6 |
7 | import (
8 | "context"
9 | "time"
10 |
11 | "github.com/drone-runners/drone-runner-docker/engine"
12 | "github.com/drone-runners/drone-runner-docker/engine/compiler"
13 | "github.com/drone-runners/drone-runner-docker/engine/linter"
14 | "github.com/drone-runners/drone-runner-docker/engine/resource"
15 | "github.com/drone-runners/drone-runner-docker/internal/match"
16 |
17 | "github.com/drone/runner-go/client"
18 | "github.com/drone/runner-go/environ/provider"
19 | "github.com/drone/runner-go/handler/router"
20 | "github.com/drone/runner-go/logger"
21 | loghistory "github.com/drone/runner-go/logger/history"
22 | "github.com/drone/runner-go/pipeline/reporter/history"
23 | "github.com/drone/runner-go/pipeline/reporter/remote"
24 | "github.com/drone/runner-go/pipeline/runtime"
25 | "github.com/drone/runner-go/pipeline/uploader"
26 | "github.com/drone/runner-go/poller"
27 | "github.com/drone/runner-go/registry"
28 | "github.com/drone/runner-go/secret"
29 | "github.com/drone/runner-go/server"
30 | "github.com/drone/signal"
31 |
32 | "github.com/joho/godotenv"
33 | "github.com/sirupsen/logrus"
34 | "golang.org/x/sync/errgroup"
35 | "gopkg.in/alecthomas/kingpin.v2"
36 | )
37 |
38 | // empty context.
39 | var nocontext = context.Background()
40 |
41 | type daemonCommand struct {
42 | envfile string
43 | }
44 |
45 | func (c *daemonCommand) run(*kingpin.ParseContext) error {
46 | // load environment variables from file.
47 | godotenv.Load(c.envfile)
48 |
49 | // load the configuration from the environment
50 | config, err := fromEnviron()
51 | if err != nil {
52 | return err
53 | }
54 |
55 | // setup the global logrus logger.
56 | setupLogger(config)
57 |
58 | ctx, cancel := context.WithCancel(nocontext)
59 | defer cancel()
60 |
61 | // listen for termination signals to gracefully shutdown
62 | // the runner daemon.
63 | ctx = signal.WithContextFunc(ctx, func() {
64 | println("received signal, terminating process")
65 | cancel()
66 | })
67 |
68 | cli := client.New(
69 | config.Client.Address,
70 | config.Client.Secret,
71 | config.Client.SkipVerify,
72 | )
73 | if config.Client.Dump {
74 | cli.Dumper = logger.StandardDumper(
75 | config.Client.DumpBody,
76 | )
77 | }
78 | cli.Logger = logger.Logrus(
79 | logrus.NewEntry(
80 | logrus.StandardLogger(),
81 | ),
82 | )
83 |
84 | opts := engine.Opts{
85 | HidePull: !config.Docker.Stream,
86 | }
87 | engine, err := engine.NewEnv(opts)
88 | if err != nil {
89 | logrus.WithError(err).
90 | Fatalln("cannot load the docker engine")
91 | }
92 | for {
93 | err := engine.Ping(ctx)
94 | if err == context.Canceled {
95 | break
96 | }
97 | select {
98 | case <-ctx.Done():
99 | return ctx.Err()
100 | default:
101 | }
102 | if err != nil {
103 | logrus.WithError(err).
104 | Errorln("cannot ping the docker daemon")
105 | time.Sleep(time.Second)
106 | } else {
107 | logrus.Debugln("successfully pinged the docker daemon")
108 | break
109 | }
110 | }
111 |
112 | remote := remote.New(cli)
113 | upload := uploader.New(cli)
114 | tracer := history.New(remote)
115 | hook := loghistory.New()
116 | logrus.AddHook(hook)
117 |
118 | runner := &runtime.Runner{
119 | Client: cli,
120 | Machine: config.Runner.Name,
121 | Environ: config.Runner.Environ,
122 | Reporter: tracer,
123 | Lookup: resource.Lookup,
124 | Lint: linter.New().Lint,
125 | Match: match.Func(
126 | config.Limit.Repos,
127 | config.Limit.Events,
128 | config.Limit.Trusted,
129 | ),
130 | Compiler: &compiler.Compiler{
131 | Clone: config.Runner.Clone,
132 | Privileged: append(config.Runner.Privileged, compiler.Privileged...),
133 | Networks: config.Runner.Networks,
134 | NetworkOpts: config.Runner.NetworkOpts,
135 | NetrcCloneOnly: config.Netrc.CloneOnly,
136 | Volumes: config.Runner.Volumes,
137 | Resources: compiler.Resources{
138 | Memory: config.Resources.Memory,
139 | MemorySwap: config.Resources.MemorySwap,
140 | CPUQuota: config.Resources.CPUQuota,
141 | CPUPeriod: config.Resources.CPUPeriod,
142 | CPUShares: config.Resources.CPUShares,
143 | CPUSet: config.Resources.CPUSet,
144 | ShmSize: config.Resources.ShmSize,
145 | },
146 | Tmate: compiler.Tmate{
147 | Image: config.Tmate.Image,
148 | Enabled: config.Tmate.Enabled,
149 | Server: config.Tmate.Server,
150 | Port: config.Tmate.Port,
151 | RSA: config.Tmate.RSA,
152 | ED25519: config.Tmate.ED25519,
153 | AuthorizedKeys: config.Tmate.AuthorizedKeys,
154 | },
155 | Environ: provider.Combine(
156 | provider.Static(config.Runner.Environ),
157 | provider.External(
158 | config.Environ.Endpoint,
159 | config.Environ.Token,
160 | config.Environ.SkipVerify,
161 | ),
162 | ),
163 | Registry: registry.Combine(
164 | registry.File(
165 | config.Docker.Config,
166 | ),
167 | registry.External(
168 | config.Registry.Endpoint,
169 | config.Registry.Token,
170 | config.Registry.SkipVerify,
171 | ),
172 | ),
173 | Secret: secret.Combine(
174 | secret.StaticVars(
175 | config.Runner.Secrets,
176 | ),
177 | secret.External(
178 | config.Secret.Endpoint,
179 | config.Secret.Token,
180 | config.Secret.SkipVerify,
181 | ),
182 | ),
183 | },
184 | Exec: runtime.NewExecer(
185 | tracer,
186 | remote,
187 | upload,
188 | engine,
189 | config.Runner.Procs,
190 | ).Exec,
191 | }
192 |
193 | poller := &poller.Poller{
194 | Client: cli,
195 | Dispatch: runner.Run,
196 | Filter: &client.Filter{
197 | Kind: resource.Kind,
198 | Type: resource.Type,
199 | OS: config.Platform.OS,
200 | Arch: config.Platform.Arch,
201 | Variant: config.Platform.Variant,
202 | Kernel: config.Platform.Kernel,
203 | Labels: config.Runner.Labels,
204 | },
205 | }
206 |
207 | var g errgroup.Group
208 | server := server.Server{
209 | Addr: config.Server.Port,
210 | Handler: router.New(tracer, hook, router.Config{
211 | Username: config.Dashboard.Username,
212 | Password: config.Dashboard.Password,
213 | Realm: config.Dashboard.Realm,
214 | }),
215 | }
216 |
217 | logrus.WithField("addr", config.Server.Port).
218 | Infoln("starting the server")
219 |
220 | g.Go(func() error {
221 | return server.ListenAndServe(ctx)
222 | })
223 |
224 | // Ping the server and block until a successful connection
225 | // to the server has been established.
226 | for {
227 | err := cli.Ping(ctx, config.Runner.Name)
228 | select {
229 | case <-ctx.Done():
230 | return nil
231 | default:
232 | }
233 | if ctx.Err() != nil {
234 | break
235 | }
236 | if err != nil {
237 | logrus.WithError(err).
238 | Errorln("cannot ping the remote server")
239 | time.Sleep(time.Second)
240 | } else {
241 | logrus.Infoln("successfully pinged the remote server")
242 | break
243 | }
244 | }
245 |
246 | g.Go(func() error {
247 | logrus.WithField("capacity", config.Runner.Capacity).
248 | WithField("endpoint", config.Client.Address).
249 | WithField("kind", resource.Kind).
250 | WithField("type", resource.Type).
251 | WithField("os", config.Platform.OS).
252 | WithField("arch", config.Platform.Arch).
253 | Infoln("polling the remote server")
254 |
255 | poller.Poll(ctx, config.Runner.Capacity)
256 | return nil
257 | })
258 |
259 | err = g.Wait()
260 | if err != nil {
261 | logrus.WithError(err).
262 | Errorln("shutting down the server")
263 | }
264 | return err
265 | }
266 |
267 | // helper function configures the global logger from
268 | // the loaded configuration.
269 | func setupLogger(config Config) {
270 | logger.Default = logger.Logrus(
271 | logrus.NewEntry(
272 | logrus.StandardLogger(),
273 | ),
274 | )
275 | if config.Debug {
276 | logrus.SetLevel(logrus.DebugLevel)
277 | }
278 | if config.Trace {
279 | logrus.SetLevel(logrus.TraceLevel)
280 | }
281 | }
282 |
283 | // Register the daemon command.
284 | func Register(app *kingpin.Application) {
285 | registerDaemon(app)
286 | registerProcess(app)
287 | }
288 |
289 | func registerDaemon(app *kingpin.Application) {
290 | c := new(daemonCommand)
291 |
292 | cmd := app.Command("daemon", "starts the runner daemon").
293 | Default().
294 | Action(c.run)
295 |
296 | cmd.Arg("envfile", "load the environment variable file").
297 | Default("").
298 | StringVar(&c.envfile)
299 | }
300 |
--------------------------------------------------------------------------------
/command/daemon/process.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Drone.IO Inc. All rights reserved.
2 | // Use of this source code is governed by the Polyform License
3 | // that can be found in the LICENSE file.
4 |
5 | package daemon
6 |
7 | import (
8 | "github.com/drone-runners/drone-runner-docker/engine"
9 | "github.com/drone-runners/drone-runner-docker/engine/compiler"
10 | "github.com/drone-runners/drone-runner-docker/engine/linter"
11 | "github.com/drone-runners/drone-runner-docker/engine/resource"
12 | "github.com/drone/runner-go/pipeline/uploader"
13 |
14 | "github.com/drone/runner-go/client"
15 | "github.com/drone/runner-go/environ/provider"
16 | "github.com/drone/runner-go/logger"
17 | "github.com/drone/runner-go/pipeline/reporter/remote"
18 | "github.com/drone/runner-go/pipeline/runtime"
19 | "github.com/drone/runner-go/registry"
20 | "github.com/drone/runner-go/secret"
21 |
22 | "github.com/sirupsen/logrus"
23 | "gopkg.in/alecthomas/kingpin.v2"
24 | )
25 |
26 | type processCommand struct {
27 | stage int64
28 | }
29 |
30 | func (c *processCommand) run(*kingpin.ParseContext) error {
31 | // load the configuration from the environment
32 | config, err := fromEnviron()
33 | if err != nil {
34 | return err
35 | }
36 |
37 | // setup the global logrus logger.
38 | setupLogger(config)
39 |
40 | cli := client.New(
41 | config.Client.Address,
42 | config.Client.Secret,
43 | config.Client.SkipVerify,
44 | )
45 | if config.Client.Dump {
46 | cli.Dumper = logger.StandardDumper(
47 | config.Client.DumpBody,
48 | )
49 | }
50 | cli.Logger = logger.Logrus(
51 | logrus.NewEntry(
52 | logrus.StandardLogger(),
53 | ),
54 | )
55 |
56 | opts := engine.Opts{
57 | HidePull: !config.Docker.Stream,
58 | }
59 | engine, err := engine.NewEnv(opts)
60 | if err != nil {
61 | logrus.WithError(err).
62 | Fatalln("cannot load the docker engine")
63 | }
64 |
65 | remote := remote.New(cli)
66 | upload := uploader.New(cli)
67 |
68 | runner := &runtime.Runner{
69 | Client: cli,
70 | Machine: config.Runner.Name,
71 | Environ: config.Runner.Environ,
72 | Reporter: remote,
73 | Lookup: resource.Lookup,
74 | Lint: linter.New().Lint,
75 | Match: nil,
76 | Compiler: &compiler.Compiler{
77 | Clone: config.Runner.Clone,
78 | Privileged: append(config.Runner.Privileged, compiler.Privileged...),
79 | Networks: config.Runner.Networks,
80 | NetrcCloneOnly: config.Netrc.CloneOnly,
81 | Volumes: config.Runner.Volumes,
82 | Resources: compiler.Resources{
83 | Memory: config.Resources.Memory,
84 | MemorySwap: config.Resources.MemorySwap,
85 | CPUQuota: config.Resources.CPUQuota,
86 | CPUPeriod: config.Resources.CPUPeriod,
87 | CPUShares: config.Resources.CPUShares,
88 | CPUSet: config.Resources.CPUSet,
89 | ShmSize: config.Resources.ShmSize,
90 | },
91 | Environ: provider.Combine(
92 | provider.Static(config.Runner.Environ),
93 | provider.External(
94 | config.Environ.Endpoint,
95 | config.Environ.Token,
96 | config.Environ.SkipVerify,
97 | ),
98 | ),
99 | Registry: registry.Combine(
100 | registry.File(
101 | config.Docker.Config,
102 | ),
103 | registry.External(
104 | config.Registry.Endpoint,
105 | config.Registry.Token,
106 | config.Registry.SkipVerify,
107 | ),
108 | ),
109 | Secret: secret.Combine(
110 | secret.StaticVars(
111 | config.Runner.Secrets,
112 | ),
113 | secret.External(
114 | config.Secret.Endpoint,
115 | config.Secret.Token,
116 | config.Secret.SkipVerify,
117 | ),
118 | ),
119 | },
120 | Exec: runtime.NewExecer(
121 | remote,
122 | remote,
123 | upload,
124 | engine,
125 | config.Runner.Procs,
126 | ).Exec,
127 | }
128 |
129 | err = runner.RunAccepted(nocontext, c.stage)
130 | if err != nil {
131 | // TODO should this return an error and fail the
132 | // command? How does this impact Nomad?
133 | logrus.WithError(err).
134 | Errorln("pipeline execution failed")
135 | }
136 | return nil
137 | }
138 |
139 | func registerProcess(app *kingpin.Application) {
140 | c := new(processCommand)
141 |
142 | cmd := app.Command("process", "processes a pipeline by id").
143 | Action(c.run).
144 | Hidden()
145 |
146 | cmd.Arg("id", "pipeline id").
147 | Required().
148 | Int64Var(&c.stage)
149 | }
150 |
--------------------------------------------------------------------------------
/command/internal/flags.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Drone.IO Inc. All rights reserved.
2 | // Use of this source code is governed by the Polyform License
3 | // that can be found in the LICENSE file.
4 |
5 | package internal
6 |
7 | import (
8 | "fmt"
9 | "time"
10 |
11 | "github.com/drone/drone-go/drone"
12 |
13 | "gopkg.in/alecthomas/kingpin.v2"
14 | )
15 |
16 | // Flags maps
17 | type Flags struct {
18 | Build *drone.Build
19 | Netrc *drone.Netrc
20 | Repo *drone.Repo
21 | Stage *drone.Stage
22 | System *drone.System
23 | }
24 |
25 | // ParseFlags parses the flags from the command args.
26 | func ParseFlags(cmd *kingpin.CmdClause) *Flags {
27 | f := &Flags{
28 | Build: &drone.Build{},
29 | Netrc: &drone.Netrc{},
30 | Repo: &drone.Repo{},
31 | Stage: &drone.Stage{},
32 | System: &drone.System{},
33 | }
34 |
35 | now := fmt.Sprint(
36 | time.Now().Unix(),
37 | )
38 |
39 | cmd.Flag("repo-id", "repo id").Default("1").Int64Var(&f.Repo.ID)
40 | cmd.Flag("repo-namespace", "repo namespace").Default("").StringVar(&f.Repo.Namespace)
41 | cmd.Flag("repo-name", "repo name").Default("").StringVar(&f.Repo.Name)
42 | cmd.Flag("repo-slug", "repo slug").Default("").StringVar(&f.Repo.Slug)
43 | cmd.Flag("repo-http", "repo http clone url").Default("").StringVar(&f.Repo.HTTPURL)
44 | cmd.Flag("repo-ssh", "repo ssh clone url").Default("").StringVar(&f.Repo.SSHURL)
45 | cmd.Flag("repo-link", "repo link").Default("").StringVar(&f.Repo.Link)
46 | cmd.Flag("repo-branch", "repo branch").Default("").StringVar(&f.Repo.Branch)
47 | cmd.Flag("repo-private", "repo private").Default("false").BoolVar(&f.Repo.Private)
48 | cmd.Flag("repo-visibility", "repo visibility").Default("").StringVar(&f.Repo.Visibility)
49 | cmd.Flag("repo-trusted", "repo trusted").Default("false").BoolVar(&f.Repo.Trusted)
50 | cmd.Flag("repo-protected", "repo protected").Default("false").BoolVar(&f.Repo.Protected)
51 | cmd.Flag("repo-timeout", "repo timeout in minutes").Default("60").Int64Var(&f.Repo.Timeout)
52 | cmd.Flag("repo-created", "repo created").Default(now).Int64Var(&f.Repo.Created)
53 | cmd.Flag("repo-updated", "repo updated").Default(now).Int64Var(&f.Repo.Updated)
54 |
55 | cmd.Flag("build-id", "build id").Default("1").Int64Var(&f.Build.ID)
56 | cmd.Flag("build-number", "build number").Default("1").Int64Var(&f.Build.Number)
57 | cmd.Flag("build-parent", "build parent").Default("0").Int64Var(&f.Build.Parent)
58 | cmd.Flag("build-event", "build event").Default("push").StringVar(&f.Build.Event)
59 | cmd.Flag("build-action", "build action").Default("").StringVar(&f.Build.Action)
60 | cmd.Flag("build-cron", "build cron trigger").Default("").StringVar(&f.Build.Cron)
61 | cmd.Flag("build-target", "build deploy target").Default("").StringVar(&f.Build.Deploy)
62 | cmd.Flag("build-debug", "build debug").Default("false").BoolVar(&f.Build.Debug)
63 | cmd.Flag("build-created", "build created").Default(now).Int64Var(&f.Build.Created)
64 | cmd.Flag("build-updated", "build updated").Default(now).Int64Var(&f.Build.Updated)
65 |
66 | cmd.Flag("commit-sender", "commit sender").Default("").StringVar(&f.Build.Sender)
67 | cmd.Flag("commit-link", "commit link").Default("").StringVar(&f.Build.Link)
68 | cmd.Flag("commit-title", "commit title").Default("").StringVar(&f.Build.Title)
69 | cmd.Flag("commit-message", "commit message").Default("").StringVar(&f.Build.Message)
70 | cmd.Flag("commit-before", "commit before").Default("").StringVar(&f.Build.Before)
71 | cmd.Flag("commit-after", "commit after").Default("").StringVar(&f.Build.After)
72 | cmd.Flag("commit-ref", "commit ref").Default("").StringVar(&f.Build.Ref)
73 | cmd.Flag("commit-fork", "commit fork").Default("").StringVar(&f.Build.Fork)
74 | cmd.Flag("commit-source", "commit source branch").Default("").StringVar(&f.Build.Source)
75 | cmd.Flag("commit-target", "commit target branch").Default("").StringVar(&f.Build.Target)
76 |
77 | cmd.Flag("author-login", "commit author login").Default("").StringVar(&f.Build.Author)
78 | cmd.Flag("author-name", "commit author name").Default("").StringVar(&f.Build.AuthorName)
79 | cmd.Flag("author-email", "commit author email").Default("").StringVar(&f.Build.AuthorEmail)
80 | cmd.Flag("author-avatar", "commit author avatar").Default("").StringVar(&f.Build.AuthorAvatar)
81 |
82 | cmd.Flag("stage-id", "stage id").Default("1").Int64Var(&f.Stage.ID)
83 | cmd.Flag("stage-number", "stage number").Default("1").IntVar(&f.Stage.Number)
84 | cmd.Flag("stage-kind", "stage kind").Default("").StringVar(&f.Stage.Kind)
85 | cmd.Flag("stage-type", "stage type").Default("").StringVar(&f.Stage.Type)
86 | cmd.Flag("stage-name", "stage name").Default("default").StringVar(&f.Stage.Name)
87 | cmd.Flag("stage-os", "stage os").Default("").StringVar(&f.Stage.OS)
88 | cmd.Flag("stage-arch", "stage arch").Default("").StringVar(&f.Stage.Arch)
89 | cmd.Flag("stage-variant", "stage variant").Default("").StringVar(&f.Stage.Variant)
90 | cmd.Flag("stage-kernel", "stage kernel").Default("").StringVar(&f.Stage.Kernel)
91 | cmd.Flag("stage-created", "stage created").Default(now).Int64Var(&f.Stage.Created)
92 | cmd.Flag("stage-updated", "stage updated").Default(now).Int64Var(&f.Stage.Updated)
93 |
94 | cmd.Flag("netrc-username", "netrc username").Default("").StringVar(&f.Netrc.Login)
95 | cmd.Flag("netrc-password", "netrc password").Default("").StringVar(&f.Netrc.Password)
96 | cmd.Flag("netrc-machine", "netrc machine").Default("").StringVar(&f.Netrc.Machine)
97 |
98 | cmd.Flag("system-host", "server host").Default("").StringVar(&f.System.Host)
99 | cmd.Flag("system-proto", "server proto").Default("").StringVar(&f.System.Proto)
100 | cmd.Flag("system-link", "server link").Default("").StringVar(&f.System.Link)
101 | cmd.Flag("system-version", "server version").Default("").StringVar(&f.System.Version)
102 |
103 | return f
104 | }
105 |
--------------------------------------------------------------------------------
/docker/Dockerfile.linux.amd64:
--------------------------------------------------------------------------------
1 | FROM alpine:3 as alpine
2 | RUN apk add -U --no-cache ca-certificates
3 |
4 | RUN wget https://github.com/tmate-io/tmate/releases/download/2.4.0/tmate-2.4.0-static-linux-amd64.tar.xz
5 | RUN tar -xf tmate-2.4.0-static-linux-amd64.tar.xz
6 | RUN mv tmate-2.4.0-static-linux-amd64/tmate /bin/
7 | RUN chmod +x /bin/tmate
8 |
9 | FROM alpine:3
10 | EXPOSE 3000
11 |
12 | ENV GODEBUG netdns=go
13 | ENV DRONE_PLATFORM_OS linux
14 | ENV DRONE_PLATFORM_ARCH amd64
15 |
16 | COPY --from=alpine /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
17 | COPY --from=alpine /bin/tmate /bin/
18 |
19 | LABEL com.centurylinklabs.watchtower.stop-signal="SIGINT"
20 |
21 | ADD release/linux/amd64/drone-runner-docker /bin/
22 | ENTRYPOINT ["/bin/drone-runner-docker"]
23 |
--------------------------------------------------------------------------------
/docker/Dockerfile.linux.arm64:
--------------------------------------------------------------------------------
1 | FROM alpine:3 as alpine
2 | RUN apk add -U --no-cache ca-certificates
3 |
4 | RUN wget https://github.com/tmate-io/tmate/releases/download/2.4.0/tmate-2.4.0-static-linux-arm64v8.tar.xz
5 | RUN tar -xf tmate-2.4.0-static-linux-arm64v8.tar.xz
6 | RUN mv tmate-2.4.0-static-linux-arm64v8/tmate /bin/
7 | RUN chmod +x /bin/tmate
8 |
9 | FROM scratch
10 | EXPOSE 3000
11 |
12 | ENV GODEBUG netdns=go
13 | ENV DRONE_PLATFORM_OS linux
14 | ENV DRONE_PLATFORM_ARCH arm64
15 |
16 | COPY --from=alpine /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
17 | COPY --from=alpine /bin/tmate /bin/
18 |
19 | LABEL com.centurylinklabs.watchtower.stop-signal="SIGINT"
20 |
21 | ADD release/linux/arm64/drone-runner-docker /bin/
22 | ENTRYPOINT ["/bin/drone-runner-docker"]
23 |
--------------------------------------------------------------------------------
/docker/Dockerfile.linux.ppc64le:
--------------------------------------------------------------------------------
1 | FROM ppc64le/alpine:3 as alpine
2 | RUN apk add -U --no-cache ca-certificates
3 |
4 | RUN wget https://github.com/isuruf/tmate/releases/download/2.4.0/tmate-2.4.0-static-linux-ppc64le.tar.xz
5 | RUN tar -xf tmate-2.4.0-static-linux-ppc64le.tar.xz
6 | RUN mv tmate-2.4.0-static-linux-ppc64le/tmate /bin/
7 | RUN chmod +x /bin/tmate
8 |
9 | FROM scratch
10 | EXPOSE 3000
11 |
12 | ENV GODEBUG netdns=go
13 | ENV DRONE_PLATFORM_OS linux
14 | ENV DRONE_PLATFORM_ARCH ppc64le
15 |
16 | COPY --from=alpine /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
17 | COPY --from=alpine /bin/tmate /bin/
18 |
19 | LABEL com.centurylinklabs.watchtower.stop-signal="SIGINT"
20 |
21 | ADD release/linux/ppc64le/drone-runner-docker /bin/
22 | ENTRYPOINT ["/bin/drone-runner-docker"]
23 |
--------------------------------------------------------------------------------
/docker/Dockerfile.windows.1809:
--------------------------------------------------------------------------------
1 | # escape=`
2 | FROM mcr.microsoft.com/windows/nanoserver:1809
3 | USER ContainerAdministrator
4 |
5 | EXPOSE 3000
6 | ENV GODEBUG=netdns=go
7 | ENV DRONE_PLATFORM_OS windows
8 | ENV DRONE_PLATFORM_ARCH amd64
9 | ENV DRONE_PLATFORM_KERNEL 1809
10 |
11 | ADD release/windows/amd64/drone-runner-docker.exe C:/drone-runner-docker.exe
12 | ENTRYPOINT [ "C:\\drone-runner-docker.exe" ]
13 |
--------------------------------------------------------------------------------
/docker/Dockerfile.windows.ltsc2022:
--------------------------------------------------------------------------------
1 | # escape=`
2 | FROM mcr.microsoft.com/windows/nanoserver:ltsc2022
3 | USER ContainerAdministrator
4 |
5 | EXPOSE 3000
6 | ENV GODEBUG=netdns=go
7 | ENV DRONE_PLATFORM_OS windows
8 | ENV DRONE_PLATFORM_ARCH amd64
9 | ENV DRONE_PLATFORM_KERNEL ltsc2022
10 |
11 | ADD release/windows/amd64/drone-runner-docker.exe C:/drone-runner-docker.exe
12 | ENTRYPOINT [ "C:\\drone-runner-docker.exe" ]
13 |
--------------------------------------------------------------------------------
/docker/manifest.tmpl:
--------------------------------------------------------------------------------
1 | image: drone/drone-runner-docker:{{#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/drone-runner-docker:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-amd64
11 | platform:
12 | architecture: amd64
13 | os: linux
14 | -
15 | image: drone/drone-runner-docker:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm64
16 | platform:
17 | variant: v8
18 | architecture: arm64
19 | os: linux
20 | -
21 | image: drone/drone-runner-docker:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-ppc64le
22 | platform:
23 | architecture: ppc64le
24 | os: linux
25 | -
26 | image: drone/drone-runner-docker:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}windows-1809-amd64
27 | platform:
28 | architecture: amd64
29 | os: windows
30 | version: 1809
31 | -
32 | image: drone/drone-runner-docker:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}windows-ltsc2022-amd64
33 | platform:
34 | architecture: amd64
35 | os: windows
36 | version: ltsc2022
--------------------------------------------------------------------------------
/engine/compiler/clone.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Drone.IO Inc. All rights reserved.
2 | // Use of this source code is governed by the Polyform License
3 | // that can be found in the LICENSE file.
4 |
5 | package compiler
6 |
7 | import (
8 | "strconv"
9 |
10 | "github.com/drone-runners/drone-runner-docker/engine"
11 | "github.com/drone-runners/drone-runner-docker/engine/resource"
12 | "github.com/drone/runner-go/manifest"
13 | "github.com/drone/runner-go/pipeline/runtime"
14 | )
15 |
16 | // default name of the clone step.
17 | const cloneStepName = "clone"
18 |
19 | // helper function returns the clone image based on the
20 | // target operating system.
21 | func cloneImage(platform manifest.Platform) string {
22 | switch platform.OS {
23 | case "windows":
24 | return "drone/git:latest"
25 | default:
26 | return "drone/git:latest"
27 | }
28 | }
29 |
30 | // helper function configures the clone depth parameter,
31 | // specific to the clone plugin.
32 | func cloneParams(src manifest.Clone) map[string]string {
33 | dst := map[string]string{}
34 | if depth := src.Depth; depth > 0 {
35 | dst["PLUGIN_DEPTH"] = strconv.Itoa(depth)
36 | }
37 | if retries := src.Retries; retries > 0 {
38 | dst["PLUGIN_RETRIES"] = strconv.Itoa(retries)
39 | }
40 | if skipVerify := src.SkipVerify; skipVerify {
41 | dst["GIT_SSL_NO_VERIFY"] = "true"
42 | dst["PLUGIN_SKIP_VERIFY"] = "true"
43 | }
44 | return dst
45 | }
46 |
47 | // helper function creates a default container configuration
48 | // for the clone stage. The clone stage is automatically
49 | // added to each pipeline.
50 | func createClone(src *resource.Pipeline) *engine.Step {
51 | return &engine.Step{
52 | Name: cloneStepName,
53 | Image: cloneImage(src.Platform),
54 | RunPolicy: runtime.RunAlways,
55 | Envs: cloneParams(src.Clone),
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/engine/compiler/clone_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Drone.IO Inc. All rights reserved.
2 | // Use of this source code is governed by the Polyform License
3 | // that can be found in the LICENSE file.
4 |
5 | package compiler
6 |
7 | import (
8 | "testing"
9 |
10 | "github.com/dchest/uniuri"
11 | "github.com/drone-runners/drone-runner-docker/engine"
12 | "github.com/drone-runners/drone-runner-docker/engine/resource"
13 | "github.com/drone/drone-go/drone"
14 | "github.com/drone/runner-go/environ/provider"
15 | "github.com/drone/runner-go/manifest"
16 | "github.com/drone/runner-go/pipeline/runtime"
17 | "github.com/drone/runner-go/registry"
18 | "github.com/drone/runner-go/secret"
19 |
20 | "github.com/google/go-cmp/cmp"
21 | "github.com/google/go-cmp/cmp/cmpopts"
22 | )
23 |
24 | func TestClone(t *testing.T) {
25 | random = notRandom
26 | defer func() {
27 | random = uniuri.New
28 | }()
29 |
30 | c := &Compiler{
31 | Registry: registry.Static(nil),
32 | Secret: secret.Static(nil),
33 | Environ: provider.Static(nil),
34 | }
35 | args := runtime.CompilerArgs{
36 | Repo: &drone.Repo{},
37 | Build: &drone.Build{},
38 | Stage: &drone.Stage{},
39 | System: &drone.System{},
40 | Netrc: &drone.Netrc{},
41 | Manifest: &manifest.Manifest{},
42 | Pipeline: &resource.Pipeline{},
43 | }
44 | want := []*engine.Step{
45 | {
46 | ID: "random",
47 | Image: "drone/git:latest",
48 | Name: "clone",
49 | Pull: engine.PullIfNotExists,
50 | RunPolicy: runtime.RunAlways,
51 | WorkingDir: "/drone/src",
52 | Volumes: []*engine.VolumeMount{
53 | &engine.VolumeMount{
54 | Name: "_workspace",
55 | Path: "/drone/src",
56 | },
57 | },
58 | },
59 | }
60 | got := c.Compile(nocontext, args).(*engine.Spec)
61 | ignore := cmpopts.IgnoreFields(engine.Step{}, "Envs", "Labels")
62 | if diff := cmp.Diff(got.Steps, want, ignore); len(diff) != 0 {
63 | t.Errorf(diff)
64 | }
65 | }
66 |
67 | func TestCloneDisable(t *testing.T) {
68 | c := &Compiler{
69 | Environ: provider.Static(nil),
70 | Registry: registry.Static(nil),
71 | Secret: secret.Static(nil),
72 | }
73 | args := runtime.CompilerArgs{
74 | Repo: &drone.Repo{},
75 | Build: &drone.Build{},
76 | Stage: &drone.Stage{},
77 | System: &drone.System{},
78 | Netrc: &drone.Netrc{},
79 | Manifest: &manifest.Manifest{},
80 | Pipeline: &resource.Pipeline{Clone: manifest.Clone{Disable: true}},
81 | }
82 | got := c.Compile(nocontext, args).(*engine.Spec)
83 | if len(got.Steps) != 0 {
84 | t.Errorf("Expect no clone step added when disabled")
85 | }
86 | }
87 |
88 | func TestCloneCreate(t *testing.T) {
89 | want := &engine.Step{
90 | Name: "clone",
91 | Image: "drone/git:latest",
92 | RunPolicy: runtime.RunAlways,
93 | Envs: map[string]string{"PLUGIN_DEPTH": "50"},
94 | }
95 | src := &resource.Pipeline{Clone: manifest.Clone{Depth: 50}}
96 | got := createClone(src)
97 | if diff := cmp.Diff(got, want); len(diff) != 0 {
98 | t.Errorf(diff)
99 | }
100 | }
101 |
102 | func TestCloneImage(t *testing.T) {
103 | tests := []struct {
104 | in manifest.Platform
105 | out string
106 | }{
107 | {
108 | in: manifest.Platform{},
109 | out: "drone/git:latest",
110 | },
111 | {
112 | in: manifest.Platform{OS: "linux"},
113 | out: "drone/git:latest",
114 | },
115 | {
116 | in: manifest.Platform{OS: "windows"},
117 | out: "drone/git:latest",
118 | },
119 | }
120 | for _, test := range tests {
121 | got, want := cloneImage(test.in), test.out
122 | if got != want {
123 | t.Errorf("Want clone image %q, got %q", want, got)
124 | }
125 | }
126 | }
127 |
128 | func TestCloneParams(t *testing.T) {
129 | params := cloneParams(manifest.Clone{})
130 | if len(params) != 0 {
131 | t.Errorf("Expect empty clone parameters")
132 | }
133 | params = cloneParams(manifest.Clone{Depth: 0})
134 | if len(params) != 0 {
135 | t.Errorf("Expect zero depth ignored")
136 | }
137 | params = cloneParams(manifest.Clone{Retries: 0})
138 | if len(params) != 0 {
139 | t.Errorf("Expect zero retries ignored")
140 | }
141 | params = cloneParams(manifest.Clone{Depth: 50, SkipVerify: true, Retries: 4})
142 | if params["PLUGIN_DEPTH"] != "50" {
143 | t.Errorf("Expect clone depth 50")
144 | }
145 | if params["PLUGIN_RETRIES"] != "4" {
146 | t.Errorf("Expect clone retries 4")
147 | }
148 | if params["GIT_SSL_NO_VERIFY"] != "true" {
149 | t.Errorf("Expect GIT_SSL_NO_VERIFY is true")
150 | }
151 | if params["PLUGIN_SKIP_VERIFY"] != "true" {
152 | t.Errorf("Expect PLUGIN_SKIP_VERIFY is true")
153 | }
154 | }
155 |
--------------------------------------------------------------------------------
/engine/compiler/script.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Drone.IO Inc. All rights reserved.
2 | // Use of this source code is governed by the Polyform License
3 | // that can be found in the LICENSE file.
4 |
5 | package compiler
6 |
7 | import (
8 | "github.com/drone-runners/drone-runner-docker/engine"
9 | "github.com/drone-runners/drone-runner-docker/engine/compiler/shell"
10 | "github.com/drone-runners/drone-runner-docker/engine/compiler/shell/powershell"
11 | "github.com/drone-runners/drone-runner-docker/engine/resource"
12 | )
13 |
14 | // helper function configures the pipeline script for the
15 | // target operating system.
16 | func setupScript(src *resource.Step, dst *engine.Step, os string) {
17 | if len(src.Commands) > 0 {
18 | switch os {
19 | case "windows":
20 | setupScriptWindows(src, dst)
21 | default:
22 | setupScriptPosix(src, dst)
23 | }
24 | }
25 | }
26 |
27 | // helper function configures the pipeline script for the
28 | // windows operating system.
29 | func setupScriptWindows(src *resource.Step, dst *engine.Step) {
30 | dst.Entrypoint = []string{"powershell", "-noprofile", "-noninteractive", "-command"}
31 | dst.Command = []string{"echo $Env:DRONE_SCRIPT | iex"}
32 | dst.Envs["DRONE_SCRIPT"] = powershell.Script(src.Commands)
33 | dst.Envs["SHELL"] = "powershell.exe"
34 | }
35 |
36 | // helper function configures the pipeline script for the
37 | // linux operating system.
38 | func setupScriptPosix(src *resource.Step, dst *engine.Step) {
39 | dst.Entrypoint = []string{"/bin/sh", "-c"}
40 | dst.Command = []string{`echo "$DRONE_SCRIPT" | /bin/sh`}
41 | dst.Envs["DRONE_SCRIPT"] = shell.Script(src.Commands)
42 | }
43 |
--------------------------------------------------------------------------------
/engine/compiler/script_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Drone.IO Inc. All rights reserved.
2 | // Use of this source code is governed by the Polyform License
3 | // that can be found in the LICENSE file.
4 |
5 | package compiler
6 |
--------------------------------------------------------------------------------
/engine/compiler/shell/powershell/powershell.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Drone.IO Inc. All rights reserved.
2 | // Use of this source code is governed by the Polyform License
3 | // that can be found in the LICENSE file.
4 |
5 | // Package powershell provides functions for converting shell
6 | // commands to powershell scripts.
7 | package powershell
8 |
9 | import (
10 | "bytes"
11 | "fmt"
12 | "strings"
13 | )
14 |
15 | // Script converts a slice of individual shell commands to
16 | // a powershell script.
17 | func Script(commands []string) string {
18 | buf := new(bytes.Buffer)
19 | fmt.Fprintln(buf)
20 | fmt.Fprintf(buf, optionScript)
21 | fmt.Fprintln(buf)
22 | for _, command := range commands {
23 | escaped := fmt.Sprintf("%q", "+ "+command)
24 | escaped = strings.Replace(escaped, "$", "`$", -1)
25 | buf.WriteString(fmt.Sprintf(
26 | traceScript,
27 | escaped,
28 | command,
29 | ))
30 | }
31 | return buf.String()
32 | }
33 |
34 | // optionScript is a helper script this is added to the build
35 | // to set shell options, in this case, to exit on error.
36 | const optionScript = `
37 | if ($Env:DRONE_NETRC_MACHINE) {
38 | @"
39 | machine $Env:DRONE_NETRC_MACHINE
40 | login $Env:DRONE_NETRC_USERNAME
41 | password $Env:DRONE_NETRC_PASSWORD
42 | "@ > (Join-Path $Env:USERPROFILE '_netrc');
43 | }
44 | [Environment]::SetEnvironmentVariable("DRONE_NETRC_USERNAME", $null);
45 | [Environment]::SetEnvironmentVariable("DRONE_NETRC_PASSWORD", $null);
46 | [Environment]::SetEnvironmentVariable("DRONE_NETRC_USERNAME", $null);
47 | [Environment]::SetEnvironmentVariable("DRONE_NETRC_PASSWORD", $null);
48 | [Environment]::SetEnvironmentVariable("DRONE_SCRIPT", $null);
49 |
50 | $erroractionpreference = "stop"
51 | `
52 |
53 | // traceScript is a helper script that is added to
54 | // the build script to trace a command.
55 | const traceScript = `
56 | echo %s
57 | %s
58 | if ($LastExitCode -gt 0) { exit $LastExitCode }
59 | `
60 |
--------------------------------------------------------------------------------
/engine/compiler/shell/powershell/powershell_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Drone.IO Inc. All rights reserved.
2 | // Use of this source code is governed by the Polyform License
3 | // that can be found in the LICENSE file.
4 |
5 | package powershell
6 |
--------------------------------------------------------------------------------
/engine/compiler/shell/shell.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Drone.IO Inc. All rights reserved.
2 | // Use of this source code is governed by the Polyform License
3 | // that can be found in the LICENSE file.
4 |
5 | // Package shell provides functions for converting shell commands
6 | // to posix shell scripts.
7 | package shell
8 |
9 | import (
10 | "bytes"
11 | "fmt"
12 | "strings"
13 | )
14 |
15 | // Script converts a slice of individual shell commands to
16 | // a posix-compliant shell script.
17 | func Script(commands []string) string {
18 | buf := new(bytes.Buffer)
19 | fmt.Fprintln(buf)
20 | fmt.Fprintf(buf, optionScript)
21 | fmt.Fprintf(buf, tmateScript)
22 | fmt.Fprintln(buf)
23 | for _, command := range commands {
24 | escaped := fmt.Sprintf("%q", command)
25 | escaped = strings.Replace(escaped, "$", `\$`, -1)
26 | buf.WriteString(fmt.Sprintf(
27 | traceScript,
28 | escaped,
29 | command,
30 | ))
31 | }
32 | return buf.String()
33 | }
34 |
35 | // optionScript is a helper script this is added to the build
36 | // to set shell options, in this case, to exit on error.
37 | const optionScript = `
38 | if [ ! -z "${DRONE_NETRC_FILE}" ]; then
39 | echo $DRONE_NETRC_FILE > $HOME/.netrc
40 | chmod 600 $HOME/.netrc
41 | fi
42 |
43 | unset DRONE_SCRIPT
44 | unset DRONE_NETRC_MACHINE
45 | unset DRONE_NETRC_USERNAME
46 | unset DRONE_NETRC_PASSWORD
47 | unset DRONE_NETRC_FILE
48 |
49 | set -e
50 | `
51 |
52 | // traceScript is a helper script that is added to
53 | // the build script to trace a command.
54 | const traceScript = `
55 | echo + %s
56 | %s
57 | `
58 |
59 | const tmateScript = `
60 | remote_debug() {
61 | if [ "$?" -ne "0" ]; then
62 | /usr/drone/bin/tmate -F
63 | fi
64 | }
65 |
66 | if [ "${DRONE_BUILD_DEBUG}" = "true" ]; then
67 | if [ ! -z "${DRONE_TMATE_HOST}" ]; then
68 | echo "set -g tmate-server-host $DRONE_TMATE_HOST" >> $HOME/.tmate.conf
69 | echo "set -g tmate-server-port $DRONE_TMATE_PORT" >> $HOME/.tmate.conf
70 | echo "set -g tmate-server-rsa-fingerprint $DRONE_TMATE_FINGERPRINT_RSA" >> $HOME/.tmate.conf
71 | echo "set -g tmate-server-ed25519-fingerprint $DRONE_TMATE_FINGERPRINT_ED25519" >> $HOME/.tmate.conf
72 |
73 | if [ ! -z "${DRONE_TMATE_AUTHORIZED_KEYS}" ]; then
74 | echo "$DRONE_TMATE_AUTHORIZED_KEYS" > $HOME/.tmate.authorized_keys
75 | echo "set -g tmate-authorized-keys \"$HOME/.tmate.authorized_keys\"" >> $HOME/.tmate.conf
76 | fi
77 | fi
78 | trap remote_debug EXIT
79 | fi
80 | `
81 |
--------------------------------------------------------------------------------
/engine/compiler/shell/shell_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Drone.IO Inc. All rights reserved.
2 | // Use of this source code is governed by the Polyform License
3 | // that can be found in the LICENSE file.
4 |
5 | package shell
6 |
--------------------------------------------------------------------------------
/engine/compiler/step.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Drone.IO Inc. All rights reserved.
2 | // Use of this source code is governed by the Polyform License
3 | // that can be found in the LICENSE file.
4 |
5 | package compiler
6 |
7 | import (
8 | "strings"
9 |
10 | "github.com/drone-runners/drone-runner-docker/engine"
11 | "github.com/drone-runners/drone-runner-docker/engine/resource"
12 | "github.com/drone-runners/drone-runner-docker/internal/docker/image"
13 | "github.com/drone-runners/drone-runner-docker/internal/encoder"
14 |
15 | "github.com/drone/runner-go/pipeline/runtime"
16 | )
17 |
18 | func createStep(spec *resource.Pipeline, src *resource.Step) *engine.Step {
19 | dst := &engine.Step{
20 | ID: random(),
21 | Name: src.Name,
22 | Image: image.Expand(src.Image),
23 | Command: src.Command,
24 | Entrypoint: src.Entrypoint,
25 | Detach: src.Detach,
26 | DependsOn: src.DependsOn,
27 | DNS: src.DNS,
28 | DNSSearch: src.DNSSearch,
29 | Envs: convertStaticEnv(src.Environment),
30 | ExtraHosts: src.ExtraHosts,
31 | IgnoreStderr: false,
32 | IgnoreStdout: false,
33 | Network: src.Network,
34 | Privileged: src.Privileged,
35 | Pull: convertPullPolicy(src.Pull),
36 | User: src.User,
37 | Secrets: convertSecretEnv(src.Environment),
38 | ShmSize: int64(src.ShmSize),
39 | WorkingDir: src.WorkingDir,
40 |
41 | //
42 | //
43 | //
44 |
45 | Networks: nil, // set in compiler.go
46 | Volumes: nil, // set below
47 | Devices: nil, // see below
48 | // Resources: toResources(src), // TODO
49 | }
50 |
51 | // set container limits
52 | if v := int64(src.MemLimit); v > 0 {
53 | dst.MemLimit = v
54 | }
55 | if v := int64(src.MemSwapLimit); v > 0 {
56 | dst.MemSwapLimit = v
57 | }
58 |
59 | // appends the volumes to the container def.
60 | for _, vol := range src.Volumes {
61 | dst.Volumes = append(dst.Volumes, &engine.VolumeMount{
62 | Name: vol.Name,
63 | Path: vol.MountPath,
64 | })
65 | }
66 |
67 | // appends the devices to the container def.
68 | for _, vol := range src.Devices {
69 | dst.Devices = append(dst.Devices, &engine.VolumeDevice{
70 | Name: vol.Name,
71 | DevicePath: vol.DevicePath,
72 | })
73 | }
74 |
75 | // appends the settings variables to the
76 | // container definition.
77 | for key, value := range src.Settings {
78 | // fix https://github.com/drone/drone-yaml/issues/13
79 | if value == nil {
80 | continue
81 | }
82 | // all settings are passed to the plugin env
83 | // variables, prefixed with PLUGIN_
84 | key = "PLUGIN_" + strings.ToUpper(key)
85 |
86 | // if the setting parameter is sources from the
87 | // secret we create a secret enviornment variable.
88 | if value.Secret != "" {
89 | dst.Secrets = append(dst.Secrets, &engine.Secret{
90 | Name: value.Secret,
91 | Mask: true,
92 | Env: key,
93 | })
94 | } else {
95 | // else if the setting parameter is opaque
96 | // we inject as a string-encoded environment
97 | // variable.
98 | dst.Envs[key] = encoder.Encode(value.Value)
99 | }
100 | }
101 |
102 | // set the pipeline step run policy. steps run on
103 | // success by default, but may be optionally configured
104 | // to run on failure.
105 | if isRunAlways(src) {
106 | dst.RunPolicy = runtime.RunAlways
107 | } else if isRunOnFailure(src) {
108 | dst.RunPolicy = runtime.RunOnFailure
109 | }
110 |
111 | // set the pipeline failure policy. steps can choose
112 | // to ignore the failure, or fail fast.
113 | switch src.Failure {
114 | case "ignore":
115 | dst.ErrPolicy = runtime.ErrIgnore
116 | case "fast", "fast-fail", "fail-fast":
117 | dst.ErrPolicy = runtime.ErrFailFast
118 | }
119 |
120 | return dst
121 | }
122 |
--------------------------------------------------------------------------------
/engine/compiler/testdata/graph.json:
--------------------------------------------------------------------------------
1 | {
2 | "platform": {},
3 | "steps": [
4 | {
5 | "id": "random",
6 | "environment": {},
7 | "image": "drone/git:latest",
8 | "labels": {},
9 | "name": "clone",
10 | "pull": "if-not-exists",
11 | "run_policy": "always",
12 | "volumes": [
13 | {
14 | "name": "_workspace",
15 | "path": "/drone/src"
16 | }
17 | ],
18 | "working_dir": "/drone/src"
19 | },
20 | {
21 | "id": "random",
22 | "args": [
23 | "echo \"$DRONE_SCRIPT\" | /bin/sh"
24 | ],
25 | "depends_on": [
26 | "clone"
27 | ],
28 | "entrypoint": [
29 | "/bin/sh",
30 | "-c"
31 | ],
32 | "environment": {},
33 | "labels": {},
34 | "name": "build",
35 | "image": "docker.io/library/golang:latest",
36 | "volumes": [
37 | {
38 | "name": "_workspace",
39 | "path": "/drone/src"
40 | }
41 | ],
42 | "working_dir": "/drone/src"
43 | },
44 | {
45 | "id": "random",
46 | "args": [
47 | "echo \"$DRONE_SCRIPT\" | /bin/sh"
48 | ],
49 | "depends_on": [
50 | "build"
51 | ],
52 | "entrypoint": [
53 | "/bin/sh",
54 | "-c"
55 | ],
56 | "environment": {},
57 | "labels": {},
58 | "name": "test",
59 | "image": "docker.io/library/golang:latest",
60 | "volumes": [
61 | {
62 | "name": "_workspace",
63 | "path": "/drone/src"
64 | }
65 | ],
66 | "working_dir": "/drone/src"
67 | }
68 | ],
69 | "volumes": [
70 | {
71 | "temp": {
72 | "id": "random",
73 | "name": "_workspace",
74 | "labels": {}
75 | }
76 | }
77 | ],
78 | "network": {
79 | "id": "random",
80 | "labels": {}
81 | }
82 | }
--------------------------------------------------------------------------------
/engine/compiler/testdata/graph.yml:
--------------------------------------------------------------------------------
1 | kind: pipeline
2 | type: docker
3 | name: default
4 |
5 | steps:
6 | - name: build
7 | image: golang
8 | commands:
9 | - go build
10 |
11 | - name: test
12 | image: golang
13 | commands:
14 | - go test
15 | depends_on: [ build ]
16 |
--------------------------------------------------------------------------------
/engine/compiler/testdata/match.json:
--------------------------------------------------------------------------------
1 | {
2 | "platform": {},
3 | "steps": [
4 | {
5 | "id": "random",
6 | "args": [
7 | "echo \"$DRONE_SCRIPT\" | /bin/sh"
8 | ],
9 | "entrypoint": [
10 | "/bin/sh",
11 | "-c"
12 | ],
13 | "environment": {},
14 | "labels": {},
15 | "name": "build",
16 | "image": "docker.io/library/golang:latest",
17 | "volumes": [
18 | {
19 | "name": "_workspace",
20 | "path": "/drone/src"
21 | }
22 | ],
23 | "working_dir": "/drone/src"
24 | },
25 | {
26 | "id": "random",
27 | "args": [
28 | "echo \"$DRONE_SCRIPT\" | /bin/sh"
29 | ],
30 | "depends_on": [
31 | "build"
32 | ],
33 | "entrypoint": [
34 | "/bin/sh",
35 | "-c"
36 | ],
37 | "environment": {},
38 | "labels": {},
39 | "name": "test",
40 | "image": "docker.io/library/golang:latest",
41 | "run_policy": "never",
42 | "volumes": [
43 | {
44 | "name": "_workspace",
45 | "path": "/drone/src"
46 | }
47 | ],
48 | "working_dir": "/drone/src"
49 | }
50 | ],
51 | "volumes": [
52 | {
53 | "temp": {
54 | "id": "random",
55 | "name": "_workspace",
56 | "labels": {}
57 | }
58 | }
59 | ],
60 | "network": {
61 | "id": "random",
62 | "labels": {}
63 | }
64 | }
--------------------------------------------------------------------------------
/engine/compiler/testdata/match.yml:
--------------------------------------------------------------------------------
1 | kind: pipeline
2 | type: docker
3 | name: default
4 |
5 | clone:
6 | disable: true
7 |
8 | steps:
9 | - name: build
10 | image: golang
11 | commands:
12 | - go build
13 | when:
14 | branch: [ master ]
15 |
16 | - name: test
17 | image: golang
18 | commands:
19 | - go test
20 | when:
21 | branch: [ develop ]
22 |
--------------------------------------------------------------------------------
/engine/compiler/testdata/noclone_graph.json:
--------------------------------------------------------------------------------
1 | {
2 | "platform": {},
3 | "steps": [
4 | {
5 | "id": "random",
6 | "args": [
7 | "echo \"$DRONE_SCRIPT\" | /bin/sh"
8 | ],
9 | "entrypoint": [
10 | "/bin/sh",
11 | "-c"
12 | ],
13 | "environment": {},
14 | "labels": {},
15 | "name": "build",
16 | "image": "docker.io/library/golang:latest",
17 | "volumes": [
18 | {
19 | "name": "_workspace",
20 | "path": "/drone/src"
21 | }
22 | ],
23 | "working_dir": "/drone/src"
24 | },
25 | {
26 | "id": "random",
27 | "args": [
28 | "echo \"$DRONE_SCRIPT\" | /bin/sh"
29 | ],
30 | "depends_on": [
31 | "build"
32 | ],
33 | "entrypoint": [
34 | "/bin/sh",
35 | "-c"
36 | ],
37 | "environment": {},
38 | "labels": {},
39 | "name": "test",
40 | "image": "docker.io/library/golang:latest",
41 | "volumes": [
42 | {
43 | "name": "_workspace",
44 | "path": "/drone/src"
45 | }
46 | ],
47 | "working_dir": "/drone/src"
48 | }
49 | ],
50 | "volumes": [
51 | {
52 | "temp": {
53 | "id": "random",
54 | "name": "_workspace",
55 | "labels": {}
56 | }
57 | }
58 | ],
59 | "network": {
60 | "id": "random",
61 | "labels": {}
62 | }
63 | }
--------------------------------------------------------------------------------
/engine/compiler/testdata/noclone_graph.yml:
--------------------------------------------------------------------------------
1 | kind: pipeline
2 | type: docker
3 | name: default
4 |
5 | clone:
6 | disable: true
7 |
8 | steps:
9 | - name: build
10 | image: golang
11 | commands:
12 | - go build
13 |
14 | - name: test
15 | image: golang
16 | commands:
17 | - go test
18 | depends_on: [ build ]
--------------------------------------------------------------------------------
/engine/compiler/testdata/noclone_serial.json:
--------------------------------------------------------------------------------
1 | {
2 | "platform": {},
3 | "steps": [
4 | {
5 | "id": "random",
6 | "args": [
7 | "echo \"$DRONE_SCRIPT\" | /bin/sh"
8 | ],
9 | "entrypoint": [
10 | "/bin/sh",
11 | "-c"
12 | ],
13 | "environment": {},
14 | "labels": {},
15 | "name": "build",
16 | "image": "docker.io/library/golang:latest",
17 | "volumes": [
18 | {
19 | "name": "_workspace",
20 | "path": "/drone/src"
21 | }
22 | ],
23 | "working_dir": "/drone/src"
24 | }
25 | ],
26 | "volumes": [
27 | {
28 | "temp": {
29 | "id": "random",
30 | "name": "_workspace",
31 | "labels": {}
32 | }
33 | }
34 | ],
35 | "network": {
36 | "id": "random",
37 | "labels": {}
38 | }
39 | }
--------------------------------------------------------------------------------
/engine/compiler/testdata/noclone_serial.yml:
--------------------------------------------------------------------------------
1 | kind: pipeline
2 | type: docker
3 | name: default
4 |
5 | clone:
6 | disable: true
7 |
8 | steps:
9 | - name: build
10 | image: golang
11 | commands:
12 | - go build
13 | - go test
14 |
--------------------------------------------------------------------------------
/engine/compiler/testdata/run_always.json:
--------------------------------------------------------------------------------
1 | {
2 | "platform": {},
3 | "steps": [
4 | {
5 | "id": "random",
6 | "args": [
7 | "echo \"$DRONE_SCRIPT\" | /bin/sh"
8 | ],
9 | "entrypoint": [
10 | "/bin/sh",
11 | "-c"
12 | ],
13 | "environment": {},
14 | "labels": {},
15 | "name": "build",
16 | "image": "docker.io/library/golang:latest",
17 | "run_policy": "always",
18 | "volumes": [
19 | {
20 | "name": "_workspace",
21 | "path": "/drone/src"
22 | }
23 | ],
24 | "working_dir": "/drone/src"
25 | }
26 | ],
27 | "volumes": [
28 | {
29 | "temp": {
30 | "id": "random",
31 | "name": "_workspace",
32 | "labels": {}
33 | }
34 | }
35 | ],
36 | "network": {
37 | "id": "random",
38 | "labels": {}
39 | }
40 | }
--------------------------------------------------------------------------------
/engine/compiler/testdata/run_always.yml:
--------------------------------------------------------------------------------
1 | kind: pipeline
2 | type: docker
3 | name: default
4 |
5 | clone:
6 | disable: true
7 |
8 | steps:
9 | - name: build
10 | image: golang
11 | commands:
12 | - go build
13 | when:
14 | status: [ success, failure ]
15 |
--------------------------------------------------------------------------------
/engine/compiler/testdata/run_failure.json:
--------------------------------------------------------------------------------
1 | {
2 | "platform": {},
3 | "steps": [
4 | {
5 | "id": "random",
6 | "args": [
7 | "echo \"$DRONE_SCRIPT\" | /bin/sh"
8 | ],
9 | "entrypoint": [
10 | "/bin/sh",
11 | "-c"
12 | ],
13 | "environment": {},
14 | "labels": {},
15 | "name": "build",
16 | "image": "docker.io/library/golang:latest",
17 | "run_policy": "on-failure",
18 | "volumes": [
19 | {
20 | "name": "_workspace",
21 | "path": "/drone/src"
22 | }
23 | ],
24 | "working_dir": "/drone/src"
25 | }
26 | ],
27 | "volumes": [
28 | {
29 | "temp": {
30 | "id": "random",
31 | "name": "_workspace",
32 | "labels": { }
33 | }
34 | }
35 | ],
36 | "network": {
37 | "id": "random",
38 | "labels": {}
39 | }
40 | }
--------------------------------------------------------------------------------
/engine/compiler/testdata/run_failure.yml:
--------------------------------------------------------------------------------
1 | kind: pipeline
2 | type: docker
3 | name: default
4 |
5 | clone:
6 | disable: true
7 |
8 | steps:
9 | - name: build
10 | image: golang
11 | commands:
12 | - go build
13 | when:
14 | status: [ failure ]
15 |
--------------------------------------------------------------------------------
/engine/compiler/testdata/secret.yml:
--------------------------------------------------------------------------------
1 | kind: pipeline
2 | type: docker
3 | name: default
4 |
5 | clone:
6 | disable: true
7 |
8 | steps:
9 | - name: build
10 | environment:
11 | PASSWORD:
12 | from_secret: my_password
13 | USERNAME:
14 | from_secret: my_username
15 | commands:
16 | - go build
17 | - go test
18 |
--------------------------------------------------------------------------------
/engine/compiler/testdata/serial.json:
--------------------------------------------------------------------------------
1 | {
2 | "platform": {},
3 | "steps": [
4 | {
5 | "id": "random",
6 | "environment": {},
7 | "image": "drone/git:latest",
8 | "labels": {},
9 | "name": "clone",
10 | "run_policy": "always",
11 | "pull": "if-not-exists",
12 | "volumes": [
13 | {
14 | "name": "_workspace",
15 | "path": "/drone/src"
16 | }
17 | ],
18 | "working_dir": "/drone/src"
19 | },
20 | {
21 | "id": "random",
22 | "args": [
23 | "echo \"$DRONE_SCRIPT\" | /bin/sh"
24 | ],
25 | "depends_on": [
26 | "clone"
27 | ],
28 | "entrypoint": [
29 | "/bin/sh",
30 | "-c"
31 | ],
32 | "environment": {},
33 | "labels": {},
34 | "name": "build",
35 | "image": "docker.io/library/golang:latest",
36 | "volumes": [
37 | {
38 | "name": "_workspace",
39 | "path": "/drone/src"
40 | }
41 | ],
42 | "working_dir": "/drone/src"
43 | },
44 | {
45 | "id": "random",
46 | "args": [
47 | "echo \"$DRONE_SCRIPT\" | /bin/sh"
48 | ],
49 | "depends_on": [
50 | "build"
51 | ],
52 | "entrypoint": [
53 | "/bin/sh",
54 | "-c"
55 | ],
56 | "environment": {},
57 | "labels": {},
58 | "name": "test",
59 | "image": "docker.io/library/golang:latest",
60 | "volumes": [
61 | {
62 | "name": "_workspace",
63 | "path": "/drone/src"
64 | }
65 | ],
66 | "working_dir": "/drone/src"
67 | }
68 | ],
69 | "volumes": [
70 | {
71 | "temp": {
72 | "id": "random",
73 | "name": "_workspace",
74 | "labels": {}
75 | }
76 | }
77 | ],
78 | "network": {
79 | "id": "random",
80 | "labels": {}
81 | }
82 | }
--------------------------------------------------------------------------------
/engine/compiler/testdata/serial.yml:
--------------------------------------------------------------------------------
1 | kind: pipeline
2 | type: docker
3 | name: default
4 |
5 | steps:
6 | - name: build
7 | image: golang
8 | commands:
9 | - go build
10 |
11 | - name: test
12 | image: golang
13 | commands:
14 | - go test
15 |
--------------------------------------------------------------------------------
/engine/compiler/testdata/steps.yml:
--------------------------------------------------------------------------------
1 | kind: pipeline
2 | type: docker
3 | name: default
4 |
5 | clone:
6 | disable: true
7 |
8 | steps:
9 | - name: build
10 | image: golang
11 | commands:
12 | - go build
13 | - name: test
14 | image: golang
15 | commands:
16 | - go test
17 |
--------------------------------------------------------------------------------
/engine/compiler/util.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Drone.IO Inc. All rights reserved.
2 | // Use of this source code is governed by the Polyform License
3 | // that can be found in the LICENSE file.
4 |
5 | package compiler
6 |
7 | import (
8 | "strings"
9 |
10 | "github.com/drone-runners/drone-runner-docker/engine"
11 | "github.com/drone-runners/drone-runner-docker/engine/resource"
12 |
13 | "github.com/drone/drone-go/drone"
14 | "github.com/drone/runner-go/manifest"
15 | )
16 |
17 | // helper function returns true if the step is configured to
18 | // always run regardless of status.
19 | func isRunAlways(step *resource.Step) bool {
20 | if len(step.When.Status.Include) == 0 &&
21 | len(step.When.Status.Exclude) == 0 {
22 | return false
23 | }
24 | return step.When.Status.Match(drone.StatusFailing) &&
25 | step.When.Status.Match(drone.StatusPassing)
26 | }
27 |
28 | // helper function returns true if the step is configured to
29 | // only run on failure.
30 | func isRunOnFailure(step *resource.Step) bool {
31 | if len(step.When.Status.Include) == 0 &&
32 | len(step.When.Status.Exclude) == 0 {
33 | return false
34 | }
35 | return step.When.Status.Match(drone.StatusFailing)
36 | }
37 |
38 | // helper function returns true if the pipeline specification
39 | // manually defines an execution graph.
40 | func isGraph(spec *engine.Spec) bool {
41 | for _, step := range spec.Steps {
42 | if len(step.DependsOn) > 0 {
43 | return true
44 | }
45 | }
46 | return false
47 | }
48 |
49 | // helper function creates the dependency graph for serial
50 | // pipeline execution.
51 | func configureSerial(spec *engine.Spec) {
52 | var prev *engine.Step
53 | for _, step := range spec.Steps {
54 | if prev != nil {
55 | step.DependsOn = []string{prev.Name}
56 | }
57 | prev = step
58 | }
59 | }
60 |
61 | // helper function converts the environment variables to a map,
62 | // returning only inline environment variables not derived from
63 | // a secret.
64 | func convertStaticEnv(src map[string]*manifest.Variable) map[string]string {
65 | dst := map[string]string{}
66 | for k, v := range src {
67 | if v == nil {
68 | continue
69 | }
70 | if strings.TrimSpace(v.Secret) == "" {
71 | dst[k] = v.Value
72 | }
73 | }
74 | return dst
75 | }
76 |
77 | // helper function converts the environment variables to a map,
78 | // returning only inline environment variables not derived from
79 | // a secret.
80 | func convertSecretEnv(src map[string]*manifest.Variable) []*engine.Secret {
81 | dst := []*engine.Secret{}
82 | for k, v := range src {
83 | if v == nil {
84 | continue
85 | }
86 | if strings.TrimSpace(v.Secret) != "" {
87 | dst = append(dst, &engine.Secret{
88 | Name: v.Secret,
89 | Mask: true,
90 | Env: k,
91 | })
92 | }
93 | }
94 | return dst
95 | }
96 |
97 | // helper function modifies the pipeline dependency graph to
98 | // account for the clone step.
99 | func configureCloneDeps(spec *engine.Spec) {
100 | for _, step := range spec.Steps {
101 | if step.Name == "clone" {
102 | continue
103 | }
104 | if len(step.DependsOn) == 0 {
105 | step.DependsOn = []string{"clone"}
106 | }
107 | }
108 | }
109 |
110 | // helper function modifies the pipeline dependency graph to
111 | // account for a disabled clone step.
112 | func removeCloneDeps(spec *engine.Spec) {
113 | for _, step := range spec.Steps {
114 | if step.Name == "clone" {
115 | return
116 | }
117 | }
118 | for _, step := range spec.Steps {
119 | if len(step.DependsOn) == 1 &&
120 | step.DependsOn[0] == "clone" {
121 | step.DependsOn = []string{}
122 | }
123 | }
124 | }
125 |
126 | // helper function modifies the pipeline dependency graph to
127 | // account for the clone step.
128 | func convertPullPolicy(s string) engine.PullPolicy {
129 | switch strings.ToLower(s) {
130 | case "always":
131 | return engine.PullAlways
132 | case "if-not-exists":
133 | return engine.PullIfNotExists
134 | case "never":
135 | return engine.PullNever
136 | default:
137 | return engine.PullDefault
138 | }
139 | }
140 |
141 | // helper function returns true if the environment variable
142 | // is restricted for internal-use only.
143 | func isRestrictedVariable(env map[string]*manifest.Variable) bool {
144 | for _, name := range restrictedVars {
145 | if _, ok := env[name]; ok {
146 | return true
147 | }
148 | }
149 | return false
150 | }
151 |
152 | // list of restricted variables
153 | var restrictedVars = []string{
154 | "XDG_RUNTIME_DIR",
155 | "DOCKER_OPTS",
156 | "DOCKER_HOST",
157 | "PATH",
158 | "HOME",
159 | }
160 |
--------------------------------------------------------------------------------
/engine/compiler/util_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Drone.IO Inc. All rights reserved.
2 | // Use of this source code is governed by the Polyform License
3 | // that can be found in the LICENSE file.
4 |
5 | package compiler
6 |
7 | import (
8 | "testing"
9 |
10 | "github.com/drone-runners/drone-runner-docker/engine"
11 | "github.com/drone-runners/drone-runner-docker/engine/resource"
12 |
13 | "github.com/drone/runner-go/manifest"
14 |
15 | "github.com/google/go-cmp/cmp"
16 | "github.com/google/go-cmp/cmp/cmpopts"
17 | )
18 |
19 | func Test_isRunAlways(t *testing.T) {
20 | step := new(resource.Step)
21 | if isRunAlways(step) == true {
22 | t.Errorf("Want always run false if empty when clause")
23 | }
24 | step.When.Status.Include = []string{"success"}
25 | if isRunAlways(step) == true {
26 | t.Errorf("Want always run false if when success")
27 | }
28 | step.When.Status.Include = []string{"failure"}
29 | if isRunAlways(step) == true {
30 | t.Errorf("Want always run false if when faiure")
31 | }
32 | step.When.Status.Include = []string{"success", "failure"}
33 | if isRunAlways(step) == false {
34 | t.Errorf("Want always run true if when success, failure")
35 | }
36 | }
37 |
38 | func Test_isRunOnFailure(t *testing.T) {
39 | step := new(resource.Step)
40 | if isRunOnFailure(step) == true {
41 | t.Errorf("Want run on failure false if empty when clause")
42 | }
43 | step.When.Status.Include = []string{"success"}
44 | if isRunOnFailure(step) == true {
45 | t.Errorf("Want run on failure false if when success")
46 | }
47 | step.When.Status.Include = []string{"failure"}
48 | if isRunOnFailure(step) == false {
49 | t.Errorf("Want run on failure true if when faiure")
50 | }
51 | step.When.Status.Include = []string{"success", "failure"}
52 | if isRunOnFailure(step) == false {
53 | t.Errorf("Want run on failure true if when success, failure")
54 | }
55 | }
56 |
57 | func Test_isGraph(t *testing.T) {
58 | spec := new(engine.Spec)
59 | spec.Steps = []*engine.Step{
60 | {DependsOn: []string{}},
61 | }
62 | if isGraph(spec) == true {
63 | t.Errorf("Expect is graph false if deps not exist")
64 | }
65 | spec.Steps[0].DependsOn = []string{"clone"}
66 | if isGraph(spec) == false {
67 | t.Errorf("Expect is graph true if deps exist")
68 | }
69 | }
70 |
71 | func Test_configureSerial(t *testing.T) {
72 | before := new(engine.Spec)
73 | before.Steps = []*engine.Step{
74 | {Name: "build"},
75 | {Name: "test"},
76 | {Name: "deploy"},
77 | }
78 |
79 | after := new(engine.Spec)
80 | after.Steps = []*engine.Step{
81 | {Name: "build"},
82 | {Name: "test", DependsOn: []string{"build"}},
83 | {Name: "deploy", DependsOn: []string{"test"}},
84 | }
85 | configureSerial(before)
86 |
87 | opts := cmpopts.IgnoreUnexported(engine.Spec{})
88 | if diff := cmp.Diff(before, after, opts); diff != "" {
89 | t.Errorf("Unexpected serial configuration")
90 | t.Log(diff)
91 | }
92 | }
93 |
94 | func Test_convertStaticEnv(t *testing.T) {
95 | vars := map[string]*manifest.Variable{
96 | "username": &manifest.Variable{Value: "octocat"},
97 | "password": &manifest.Variable{Secret: "password"},
98 | }
99 | envs := convertStaticEnv(vars)
100 | want := map[string]string{"username": "octocat"}
101 | if diff := cmp.Diff(envs, want); diff != "" {
102 | t.Errorf("Unexpected environment variable set")
103 | t.Log(diff)
104 | }
105 | }
106 |
107 | func Test_convertSecretEnv(t *testing.T) {
108 | vars := map[string]*manifest.Variable{
109 | "USERNAME": &manifest.Variable{Value: "octocat"},
110 | "PASSWORD": &manifest.Variable{Secret: "password"},
111 | }
112 | envs := convertSecretEnv(vars)
113 | want := []*engine.Secret{
114 | {
115 | Name: "password",
116 | Env: "PASSWORD",
117 | Mask: true,
118 | },
119 | }
120 | if diff := cmp.Diff(envs, want); diff != "" {
121 | t.Errorf("Unexpected secret list")
122 | t.Log(diff)
123 | }
124 | }
125 |
126 | func Test_configureCloneDeps(t *testing.T) {
127 | before := new(engine.Spec)
128 | before.Steps = []*engine.Step{
129 | {Name: "clone"},
130 | {Name: "backend"},
131 | {Name: "frontend"},
132 | {Name: "deploy", DependsOn: []string{
133 | "backend", "frontend",
134 | }},
135 | }
136 |
137 | after := new(engine.Spec)
138 | after.Steps = []*engine.Step{
139 | {Name: "clone"},
140 | {Name: "backend", DependsOn: []string{"clone"}},
141 | {Name: "frontend", DependsOn: []string{"clone"}},
142 | {Name: "deploy", DependsOn: []string{
143 | "backend", "frontend",
144 | }},
145 | }
146 | configureCloneDeps(before)
147 |
148 | opts := cmpopts.IgnoreUnexported(engine.Spec{})
149 | if diff := cmp.Diff(before, after, opts); diff != "" {
150 | t.Errorf("Unexpected dependency adjustment")
151 | t.Log(diff)
152 | }
153 | }
154 |
155 | func Test_removeCloneDeps(t *testing.T) {
156 | before := new(engine.Spec)
157 | before.Steps = []*engine.Step{
158 | {Name: "backend", DependsOn: []string{"clone"}},
159 | {Name: "frontend", DependsOn: []string{"clone"}},
160 | {Name: "deploy", DependsOn: []string{
161 | "backend", "frontend",
162 | }},
163 | }
164 |
165 | after := new(engine.Spec)
166 | after.Steps = []*engine.Step{
167 | {Name: "backend", DependsOn: []string{}},
168 | {Name: "frontend", DependsOn: []string{}},
169 | {Name: "deploy", DependsOn: []string{
170 | "backend", "frontend",
171 | }},
172 | }
173 | removeCloneDeps(before)
174 |
175 | opts := cmpopts.IgnoreUnexported(engine.Spec{})
176 | if diff := cmp.Diff(before, after, opts); diff != "" {
177 | t.Errorf("Unexpected result after removing clone deps")
178 | t.Log(diff)
179 | }
180 | }
181 |
182 | func Test_removeCloneDeps_CloneEnabled(t *testing.T) {
183 | before := new(engine.Spec)
184 | before.Steps = []*engine.Step{
185 | {Name: "clone"},
186 | {Name: "test", DependsOn: []string{"clone"}},
187 | }
188 |
189 | after := new(engine.Spec)
190 | after.Steps = []*engine.Step{
191 | {Name: "clone"},
192 | {Name: "test", DependsOn: []string{"clone"}},
193 | }
194 | removeCloneDeps(before)
195 |
196 | opts := cmpopts.IgnoreUnexported(engine.Spec{})
197 | if diff := cmp.Diff(before, after, opts); diff != "" {
198 | t.Errorf("Expect clone dependencies not removed")
199 | t.Log(diff)
200 | }
201 | }
202 |
--------------------------------------------------------------------------------
/engine/compiler/workspace.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Drone.IO Inc. All rights reserved.
2 | // Use of this source code is governed by the Polyform License
3 | // that can be found in the LICENSE file.
4 |
5 | package compiler
6 |
7 | import (
8 | stdpath "path"
9 | "strings"
10 |
11 | "github.com/drone-runners/drone-runner-docker/engine"
12 | "github.com/drone-runners/drone-runner-docker/engine/resource"
13 | )
14 |
15 | const (
16 | workspacePath = "/drone/src"
17 | workspaceName = "workspace"
18 | workspaceHostName = "host"
19 | )
20 |
21 | func createWorkspace(from *resource.Pipeline) (base, path, full string) {
22 | base = from.Workspace.Base
23 | path = from.Workspace.Path
24 | if base == "" {
25 | if strings.HasPrefix(path, "/") {
26 | base = path
27 | path = ""
28 | } else {
29 | base = workspacePath
30 | }
31 | }
32 | full = stdpath.Join(base, path)
33 |
34 | if from.Platform.OS == "windows" {
35 | base = toWindowsDrive(base)
36 | path = toWindowsPath(path)
37 | full = toWindowsDrive(full)
38 | }
39 | return base, path, full
40 | }
41 |
42 | func setupWorkdir(src *resource.Step, dst *engine.Step, path string) {
43 | // if the working directory is already set
44 | // do not alter.
45 | if dst.WorkingDir != "" {
46 | return
47 | }
48 | // if the user is running the container as a
49 | // service (detached mode) with no commands, we
50 | // should use the default working directory.
51 | if dst.Detach && len(src.Commands) == 0 {
52 | return
53 | }
54 | // else set the working directory.
55 | dst.WorkingDir = path
56 | }
57 |
58 | // helper function appends the workspace base and
59 | // path to the step's list of environment variables.
60 | func setupWorkspaceEnv(step *engine.Step, base, path, full string) {
61 | step.Envs["DRONE_WORKSPACE_BASE"] = base
62 | step.Envs["DRONE_WORKSPACE_PATH"] = path
63 | step.Envs["DRONE_WORKSPACE"] = full
64 | step.Envs["CI_WORKSPACE_BASE"] = base
65 | step.Envs["CI_WORKSPACE_PATH"] = path
66 | step.Envs["CI_WORKSPACE"] = full
67 | }
68 |
69 | // helper function converts the path to a valid windows
70 | // path, including the default C drive.
71 | func toWindowsDrive(s string) string {
72 | return "c:" + toWindowsPath(s)
73 | }
74 |
75 | // helper function converts the path to a valid windows
76 | // path, replacing backslashes with forward slashes.
77 | func toWindowsPath(s string) string {
78 | return strings.Replace(s, "/", "\\", -1)
79 | }
80 |
--------------------------------------------------------------------------------
/engine/compiler/workspace_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Drone.IO Inc. All rights reserved.
2 | // Use of this source code is governed by the Polyform License
3 | // that can be found in the LICENSE file.
4 |
5 | package compiler
6 |
7 | import (
8 | "testing"
9 |
10 | "github.com/drone-runners/drone-runner-docker/engine"
11 | "github.com/drone-runners/drone-runner-docker/engine/resource"
12 | "github.com/drone/runner-go/manifest"
13 | )
14 |
15 | func TestSetupWorkspace(t *testing.T) {
16 | tests := []struct {
17 | path string
18 | src *resource.Step
19 | dst *engine.Step
20 | want string
21 | }{
22 | {
23 | path: "/drone/src",
24 | src: &resource.Step{},
25 | dst: &engine.Step{},
26 | want: "/drone/src",
27 | },
28 | // do not override the user-defined working dir.
29 | {
30 | path: "/drone/src",
31 | src: &resource.Step{},
32 | dst: &engine.Step{WorkingDir: "/foo"},
33 | want: "/foo",
34 | },
35 | // do not override the default working directory
36 | // for service containers with no commands.
37 | {
38 | path: "/drone/src",
39 | src: &resource.Step{},
40 | dst: &engine.Step{Detach: true},
41 | want: "",
42 | },
43 | // overrides the default working directory
44 | // for service containers with commands.
45 | {
46 | path: "/drone/src",
47 | src: &resource.Step{Commands: []string{"whoami"}},
48 | dst: &engine.Step{Detach: true},
49 | want: "/drone/src",
50 | },
51 | }
52 | for _, test := range tests {
53 | setupWorkdir(test.src, test.dst, test.path)
54 | if got, want := test.dst.WorkingDir, test.want; got != want {
55 | t.Errorf("Want working_dir %s, got %s", want, got)
56 | }
57 | }
58 | }
59 |
60 | func TestToWindows(t *testing.T) {
61 | got := toWindowsDrive("/go/src/github.com/octocat/hello-world")
62 | want := "c:\\go\\src\\github.com\\octocat\\hello-world"
63 | if got != want {
64 | t.Errorf("Want windows drive %q, got %q", want, got)
65 | }
66 | }
67 |
68 | func TestCreateWorkspace(t *testing.T) {
69 | tests := []struct {
70 | from *resource.Pipeline
71 | base string
72 | path string
73 | full string
74 | }{
75 | {
76 | from: &resource.Pipeline{
77 | Workspace: resource.Workspace{
78 | Base: "",
79 | Path: "",
80 | },
81 | },
82 | base: "/drone/src",
83 | path: "",
84 | full: "/drone/src",
85 | },
86 | {
87 | from: &resource.Pipeline{
88 | Workspace: resource.Workspace{
89 | Base: "",
90 | Path: "",
91 | },
92 | Platform: manifest.Platform{
93 | OS: "windows",
94 | },
95 | },
96 | base: "c:\\drone\\src",
97 | path: "",
98 | full: "c:\\drone\\src",
99 | },
100 | {
101 | from: &resource.Pipeline{
102 | Workspace: resource.Workspace{
103 | Base: "/drone",
104 | Path: "src",
105 | },
106 | },
107 | base: "/drone",
108 | path: "src",
109 | full: "/drone/src",
110 | },
111 | {
112 | from: &resource.Pipeline{
113 | Workspace: resource.Workspace{
114 | Base: "/drone",
115 | Path: "src",
116 | },
117 | Platform: manifest.Platform{
118 | OS: "windows",
119 | },
120 | },
121 | base: "c:\\drone",
122 | path: "src",
123 | full: "c:\\drone\\src",
124 | },
125 | {
126 | from: &resource.Pipeline{
127 | Workspace: resource.Workspace{
128 | Base: "/foo",
129 | Path: "bar",
130 | },
131 | },
132 | base: "/foo",
133 | path: "bar",
134 | full: "/foo/bar",
135 | },
136 | }
137 | for _, test := range tests {
138 | base, path, full := createWorkspace(test.from)
139 | if got, want := test.base, base; got != want {
140 | t.Errorf("Want workspace base %s, got %s", want, got)
141 | }
142 | if got, want := test.path, path; got != want {
143 | t.Errorf("Want workspace path %s, got %s", want, got)
144 | }
145 | if got, want := test.full, full; got != want {
146 | t.Errorf("Want workspace %s, got %s", want, got)
147 | }
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/engine/const.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Drone.IO Inc. All rights reserved.
2 | // Use of this source code is governed by the Polyform License
3 | // that can be found in the LICENSE file.
4 |
5 | package engine
6 |
7 | import (
8 | "bytes"
9 | "encoding/json"
10 | )
11 |
12 | // PullPolicy defines the container image pull policy.
13 | type PullPolicy int
14 |
15 | // PullPolicy enumeration.
16 | const (
17 | PullDefault PullPolicy = iota
18 | PullAlways
19 | PullIfNotExists
20 | PullNever
21 | )
22 |
23 | func (p PullPolicy) String() string {
24 | return pullPolicyID[p]
25 | }
26 |
27 | var pullPolicyID = map[PullPolicy]string{
28 | PullDefault: "default",
29 | PullAlways: "always",
30 | PullIfNotExists: "if-not-exists",
31 | PullNever: "never",
32 | }
33 |
34 | var pullPolicyName = map[string]PullPolicy{
35 | "": PullDefault,
36 | "default": PullDefault,
37 | "always": PullAlways,
38 | "if-not-exists": PullIfNotExists,
39 | "never": PullNever,
40 | }
41 |
42 | // MarshalJSON marshals the string representation of the
43 | // pull type to JSON.
44 | func (p *PullPolicy) MarshalJSON() ([]byte, error) {
45 | buffer := bytes.NewBufferString(`"`)
46 | buffer.WriteString(pullPolicyID[*p])
47 | buffer.WriteString(`"`)
48 | return buffer.Bytes(), nil
49 | }
50 |
51 | // UnmarshalJSON unmarshals the json representation of the
52 | // pull type from a string value.
53 | func (p *PullPolicy) UnmarshalJSON(b []byte) error {
54 | // unmarshal as string
55 | var s string
56 | err := json.Unmarshal(b, &s)
57 | if err != nil {
58 | return err
59 | }
60 | // lookup value
61 | *p = pullPolicyName[s]
62 | return nil
63 | }
64 |
--------------------------------------------------------------------------------
/engine/const_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Drone.IO Inc. All rights reserved.
2 | // Use of this source code is governed by the Polyform License
3 | // that can be found in the LICENSE file.
4 |
5 | package engine
6 |
7 | import (
8 | "bytes"
9 | "encoding/json"
10 | "testing"
11 | )
12 |
13 | func TestPullPolicy_Marshal(t *testing.T) {
14 | tests := []struct {
15 | policy PullPolicy
16 | data string
17 | }{
18 | {
19 | policy: PullAlways,
20 | data: `"always"`,
21 | },
22 | {
23 | policy: PullDefault,
24 | data: `"default"`,
25 | },
26 | {
27 | policy: PullIfNotExists,
28 | data: `"if-not-exists"`,
29 | },
30 | {
31 | policy: PullNever,
32 | data: `"never"`,
33 | },
34 | }
35 | for _, test := range tests {
36 | data, err := json.Marshal(&test.policy)
37 | if err != nil {
38 | t.Error(err)
39 | return
40 | }
41 | if bytes.Equal([]byte(test.data), data) == false {
42 | t.Errorf("Failed to marshal policy %s", test.policy)
43 | }
44 | }
45 | }
46 |
47 | func TestPullPolicy_Unmarshal(t *testing.T) {
48 | tests := []struct {
49 | policy PullPolicy
50 | data string
51 | }{
52 | {
53 | policy: PullAlways,
54 | data: `"always"`,
55 | },
56 | {
57 | policy: PullDefault,
58 | data: `"default"`,
59 | },
60 | {
61 | policy: PullIfNotExists,
62 | data: `"if-not-exists"`,
63 | },
64 | {
65 | policy: PullNever,
66 | data: `"never"`,
67 | },
68 | {
69 | // no policy should default to on-success
70 | policy: PullDefault,
71 | data: `""`,
72 | },
73 | }
74 | for _, test := range tests {
75 | var policy PullPolicy
76 | err := json.Unmarshal([]byte(test.data), &policy)
77 | if err != nil {
78 | t.Error(err)
79 | return
80 | }
81 | if got, want := policy, test.policy; got != want {
82 | t.Errorf("Want policy %q, got %q", want, got)
83 | }
84 | }
85 | }
86 |
87 | func TestPullPolicy_UnmarshalTypeError(t *testing.T) {
88 | var policy PullPolicy
89 | err := json.Unmarshal([]byte("[]"), &policy)
90 | if _, ok := err.(*json.UnmarshalTypeError); !ok {
91 | t.Errorf("Expect unmarshal error return when JSON invalid")
92 | }
93 | }
94 |
95 | func TestPullPolicy_String(t *testing.T) {
96 | tests := []struct {
97 | policy PullPolicy
98 | value string
99 | }{
100 | {
101 | policy: PullAlways,
102 | value: "always",
103 | },
104 | {
105 | policy: PullDefault,
106 | value: "default",
107 | },
108 | {
109 | policy: PullIfNotExists,
110 | value: "if-not-exists",
111 | },
112 | {
113 | policy: PullNever,
114 | value: "never",
115 | },
116 | }
117 | for _, test := range tests {
118 | if got, want := test.policy.String(), test.value; got != want {
119 | t.Errorf("Want policy string %q, got %q", want, got)
120 | }
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/engine/convert.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Drone.IO Inc. All rights reserved.
2 | // Use of this source code is governed by the Polyform License
3 | // that can be found in the LICENSE file.
4 |
5 | package engine
6 |
7 | import (
8 | "strings"
9 |
10 | "github.com/docker/docker/api/types/container"
11 | "github.com/docker/docker/api/types/mount"
12 | "github.com/docker/docker/api/types/network"
13 | )
14 |
15 | // returns a container configuration.
16 | func toConfig(spec *Spec, step *Step) *container.Config {
17 | config := &container.Config{
18 | Image: step.Image,
19 | Labels: step.Labels,
20 | WorkingDir: step.WorkingDir,
21 | User: step.User,
22 | AttachStdin: false,
23 | AttachStdout: true,
24 | AttachStderr: true,
25 | Tty: false,
26 | OpenStdin: false,
27 | StdinOnce: false,
28 | ArgsEscaped: false,
29 | }
30 |
31 | if len(step.Envs) != 0 {
32 | config.Env = toEnv(step.Envs)
33 | }
34 | for _, sec := range step.Secrets {
35 | if sec.Env != "" {
36 | config.Env = append(config.Env, sec.Env+"="+string(sec.Data))
37 | }
38 | }
39 |
40 | if len(step.Entrypoint) != 0 {
41 | config.Entrypoint = step.Entrypoint
42 | }
43 | if len(step.Command) != 0 {
44 | config.Cmd = step.Command
45 | }
46 | if len(step.Volumes) != 0 {
47 | config.Volumes = toVolumeSet(spec, step)
48 | }
49 | return config
50 | }
51 |
52 | // returns a container host configuration.
53 | func toHostConfig(spec *Spec, step *Step) *container.HostConfig {
54 | config := &container.HostConfig{
55 | LogConfig: container.LogConfig{
56 | Type: "json-file",
57 | },
58 | Privileged: step.Privileged,
59 | ShmSize: step.ShmSize,
60 | }
61 | // windows does not support privileged so we hard-code
62 | // this value to false.
63 | if spec.Platform.OS == "windows" {
64 | config.Privileged = false
65 | }
66 | if len(step.Network) > 0 {
67 | config.NetworkMode = container.NetworkMode(step.Network)
68 | }
69 | if len(step.DNS) > 0 {
70 | config.DNS = step.DNS
71 | }
72 | if len(step.DNSSearch) > 0 {
73 | config.DNSSearch = step.DNSSearch
74 | }
75 | if len(step.ExtraHosts) > 0 {
76 | config.ExtraHosts = step.ExtraHosts
77 | }
78 | if isUnlimited(step) == false {
79 | config.Resources = container.Resources{
80 | CPUPeriod: step.CPUPeriod,
81 | CPUQuota: step.CPUQuota,
82 | CpusetCpus: strings.Join(step.CPUSet, ","),
83 | CPUShares: step.CPUShares,
84 | Memory: step.MemLimit,
85 | MemorySwap: step.MemSwapLimit,
86 | }
87 | }
88 |
89 | if len(step.Volumes) != 0 {
90 | config.Devices = toDeviceSlice(spec, step)
91 | config.Binds = toVolumeSlice(spec, step)
92 | config.Mounts = toVolumeMounts(spec, step)
93 | }
94 | return config
95 | }
96 |
97 | // helper function returns the container network configuration.
98 | func toNetConfig(spec *Spec, proc *Step) *network.NetworkingConfig {
99 | // if the user overrides the default network we do not
100 | // attach to the user-defined network.
101 | if proc.Network != "" {
102 | return &network.NetworkingConfig{}
103 | }
104 | endpoints := map[string]*network.EndpointSettings{}
105 | endpoints[spec.Network.ID] = &network.EndpointSettings{
106 | NetworkID: spec.Network.ID,
107 | Aliases: []string{proc.Name},
108 | }
109 | return &network.NetworkingConfig{
110 | EndpointsConfig: endpoints,
111 | }
112 | }
113 |
114 | // helper function that converts a slice of device paths to a slice of
115 | // container.DeviceMapping.
116 | func toDeviceSlice(spec *Spec, step *Step) []container.DeviceMapping {
117 | var to []container.DeviceMapping
118 | for _, mount := range step.Devices {
119 | device, ok := lookupVolume(spec, mount.Name)
120 | if !ok {
121 | continue
122 | }
123 | if isDevice(device) == false {
124 | continue
125 | }
126 | to = append(to, container.DeviceMapping{
127 | PathOnHost: device.HostPath.Path,
128 | PathInContainer: mount.DevicePath,
129 | CgroupPermissions: "rwm",
130 | })
131 | }
132 | if len(to) == 0 {
133 | return nil
134 | }
135 | return to
136 | }
137 |
138 | // helper function that converts a slice of volume paths to a set
139 | // of unique volume names.
140 | func toVolumeSet(spec *Spec, step *Step) map[string]struct{} {
141 | set := map[string]struct{}{}
142 | for _, mount := range step.Volumes {
143 | volume, ok := lookupVolume(spec, mount.Name)
144 | if !ok {
145 | continue
146 | }
147 | if isDevice(volume) {
148 | continue
149 | }
150 | if isNamedPipe(volume) {
151 | continue
152 | }
153 | if isBindMount(volume) == false {
154 | continue
155 | }
156 | set[mount.Path] = struct{}{}
157 | }
158 | return set
159 | }
160 |
161 | // helper function returns a slice of volume mounts.
162 | func toVolumeSlice(spec *Spec, step *Step) []string {
163 | // this entire function should be deprecated in
164 | // favor of toVolumeMounts, however, I am unable
165 | // to get it working with data volumes.
166 | var to []string
167 | for _, mount := range step.Volumes {
168 | volume, ok := lookupVolume(spec, mount.Name)
169 | if !ok {
170 | continue
171 | }
172 | if isDevice(volume) {
173 | continue
174 | }
175 | if isDataVolume(volume) {
176 | path := volume.EmptyDir.ID + ":" + mount.Path
177 | to = append(to, path)
178 | }
179 | if isBindMount(volume) {
180 | path := volume.HostPath.Path + ":" + mount.Path
181 | to = append(to, path)
182 | }
183 | }
184 | return to
185 | }
186 |
187 | // helper function returns a slice of docker mount
188 | // configurations.
189 | func toVolumeMounts(spec *Spec, step *Step) []mount.Mount {
190 | var mounts []mount.Mount
191 | for _, target := range step.Volumes {
192 | source, ok := lookupVolume(spec, target.Name)
193 | if !ok {
194 | continue
195 | }
196 |
197 | if isBindMount(source) && !isDevice(source) {
198 | continue
199 | }
200 |
201 | // HACK: this condition can be removed once
202 | // toVolumeSlice has been fully replaced. at this
203 | // time, I cannot figure out how to get mounts
204 | // working with data volumes :(
205 | if isDataVolume(source) {
206 | continue
207 | }
208 | mounts = append(mounts, toMount(source, target))
209 | }
210 | if len(mounts) == 0 {
211 | return nil
212 | }
213 | return mounts
214 | }
215 |
216 | // helper function converts the volume declaration to a
217 | // docker mount structure.
218 | func toMount(source *Volume, target *VolumeMount) mount.Mount {
219 | to := mount.Mount{
220 | Target: target.Path,
221 | Type: toVolumeType(source),
222 | }
223 | if isBindMount(source) || isNamedPipe(source) {
224 | to.Source = source.HostPath.Path
225 | to.ReadOnly = source.HostPath.ReadOnly
226 | }
227 | if isTempfs(source) {
228 | to.TmpfsOptions = &mount.TmpfsOptions{
229 | SizeBytes: source.EmptyDir.SizeLimit,
230 | Mode: 0700,
231 | }
232 | }
233 | return to
234 | }
235 |
236 | // helper function returns the docker volume enumeration
237 | // for the given volume.
238 | func toVolumeType(from *Volume) mount.Type {
239 | switch {
240 | case isDataVolume(from):
241 | return mount.TypeVolume
242 | case isTempfs(from):
243 | return mount.TypeTmpfs
244 | case isNamedPipe(from):
245 | return mount.TypeNamedPipe
246 | default:
247 | return mount.TypeBind
248 | }
249 | }
250 |
251 | // helper function that converts a key value map of
252 | // environment variables to a string slice in key=value
253 | // format.
254 | func toEnv(env map[string]string) []string {
255 | var envs []string
256 | for k, v := range env {
257 | if v != "" {
258 | envs = append(envs, k+"="+v)
259 | }
260 | }
261 | return envs
262 | }
263 |
264 | // returns true if the container has no resource limits.
265 | func isUnlimited(res *Step) bool {
266 | return len(res.CPUSet) == 0 &&
267 | res.CPUPeriod == 0 &&
268 | res.CPUQuota == 0 &&
269 | res.CPUShares == 0 &&
270 | res.MemLimit == 0 &&
271 | res.MemSwapLimit == 0
272 | }
273 |
274 | // returns true if the volume is a bind mount.
275 | func isBindMount(volume *Volume) bool {
276 | return volume.HostPath != nil
277 | }
278 |
279 | // returns true if the volume is in-memory.
280 | func isTempfs(volume *Volume) bool {
281 | return volume.EmptyDir != nil && volume.EmptyDir.Medium == "memory"
282 | }
283 |
284 | // returns true if the volume is a data-volume.
285 | func isDataVolume(volume *Volume) bool {
286 | return volume.EmptyDir != nil && volume.EmptyDir.Medium != "memory"
287 | }
288 |
289 | // returns true if the volume is a device
290 | func isDevice(volume *Volume) bool {
291 | return volume.HostPath != nil && strings.HasPrefix(volume.HostPath.Path, "/dev/")
292 | }
293 |
294 | // returns true if the volume is a named pipe.
295 | func isNamedPipe(volume *Volume) bool {
296 | return volume.HostPath != nil &&
297 | strings.HasPrefix(volume.HostPath.Path, `\\.\pipe\`)
298 | }
299 |
300 | // helper function returns the named volume.
301 | func lookupVolume(spec *Spec, name string) (*Volume, bool) {
302 | for _, v := range spec.Volumes {
303 | if v.HostPath != nil && v.HostPath.Name == name {
304 | return v, true
305 | }
306 | if v.EmptyDir != nil && v.EmptyDir.Name == name {
307 | return v, true
308 | }
309 | }
310 | return nil, false
311 | }
312 |
--------------------------------------------------------------------------------
/engine/linter/linter.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Drone.IO Inc. All rights reserved.
2 | // Use of this source code is governed by the Polyform License
3 | // that can be found in the LICENSE file.
4 |
5 | package linter
6 |
7 | import (
8 | "errors"
9 | "fmt"
10 | "path/filepath"
11 | "strings"
12 |
13 | "github.com/drone-runners/drone-runner-docker/engine/resource"
14 | "github.com/drone/drone-go/drone"
15 | "github.com/drone/runner-go/manifest"
16 | )
17 |
18 | // ErrDuplicateStepName is returned when two Pipeline steps
19 | // have the same name.
20 | var ErrDuplicateStepName = errors.New("linter: duplicate step names")
21 |
22 | // Opts provides linting options.
23 | type Opts struct {
24 | Trusted bool
25 | }
26 |
27 | // Linter evaluates the pipeline against a set of
28 | // rules and returns an error if one or more of the
29 | // rules are broken.
30 | type Linter struct{}
31 |
32 | // New returns a new Linter.
33 | func New() *Linter {
34 | return new(Linter)
35 | }
36 |
37 | // Lint executes the linting rules for the pipeline
38 | // configuration.
39 | func (l *Linter) Lint(pipeline manifest.Resource, repo *drone.Repo) error {
40 | return checkPipeline(pipeline.(*resource.Pipeline), repo.Trusted)
41 | }
42 |
43 | func checkPipeline(pipeline *resource.Pipeline, trusted bool) error {
44 | // if err := checkNames(pipeline); err != nil {
45 | // return err
46 | // }
47 | if err := checkSteps(pipeline, trusted); err != nil {
48 | return err
49 | }
50 | if err := checkVolumes(pipeline, trusted); err != nil {
51 | return err
52 | }
53 | return nil
54 | }
55 |
56 | // func checkNames(pipeline *resource.Pipeline) error {
57 | // names := map[string]struct{}{}
58 | // if !pipeline.Clone.Disable {
59 | // names["clone"] = struct{}{}
60 | // }
61 | // steps := append(pipeline.Services, pipeline.Steps...)
62 | // for _, step := range steps {
63 | // _, ok := names[step.Name]
64 | // if ok {
65 | // return ErrDuplicateStepName
66 | // }
67 | // names[step.Name] = struct{}{}
68 | // }
69 | // return nil
70 | // }
71 |
72 | func checkSteps(pipeline *resource.Pipeline, trusted bool) error {
73 | steps := append(pipeline.Services, pipeline.Steps...)
74 | names := map[string]struct{}{}
75 | if !pipeline.Clone.Disable {
76 | names["clone"] = struct{}{}
77 | }
78 | for _, step := range steps {
79 | if step == nil {
80 | return errors.New("linter: nil step")
81 | }
82 |
83 | // unique list of names
84 | _, ok := names[step.Name]
85 | if ok {
86 | return ErrDuplicateStepName
87 | }
88 | names[step.Name] = struct{}{}
89 |
90 | if err := checkStep(step, trusted); err != nil {
91 | return err
92 | }
93 | if err := checkDeps(step, names); err != nil {
94 | return err
95 | }
96 | }
97 | return nil
98 | }
99 |
100 | func checkStep(step *resource.Step, trusted bool) error {
101 | if step.Image == "" {
102 | return errors.New("linter: invalid or missing image")
103 | }
104 | // if step.Name == "" {
105 | // return errors.New("linter: invalid or missing name")
106 | // }
107 | // if len(step.Name) > 100 {
108 | // return errors.New("linter: name exceeds maximum length")
109 | // }
110 | if trusted == false && step.Privileged {
111 | return errors.New("linter: untrusted repositories cannot enable privileged mode")
112 | }
113 | if trusted == false && len(step.Devices) > 0 {
114 | return errors.New("linter: untrusted repositories cannot mount devices")
115 | }
116 | if trusted == false && len(step.DNS) > 0 {
117 | return errors.New("linter: untrusted repositories cannot configure dns")
118 | }
119 | if trusted == false && len(step.DNSSearch) > 0 {
120 | return errors.New("linter: untrusted repositories cannot configure dns_search")
121 | }
122 | if trusted == false && len(step.ExtraHosts) > 0 {
123 | return errors.New("linter: untrusted repositories cannot configure extra_hosts")
124 | }
125 | if trusted == false && len(step.Network) > 0 {
126 | return errors.New("linter: untrusted repositories cannot configure network_mode")
127 | }
128 | if trusted == false && int(step.ShmSize) > 0 {
129 | return errors.New("linter: untrusted repositories cannot configure shm_size")
130 | }
131 | for _, mount := range step.Volumes {
132 | switch mount.Name {
133 | case "workspace", "_workspace", "_docker_socket":
134 | return fmt.Errorf("linter: invalid volume name: %s", mount.Name)
135 | }
136 | if strings.HasPrefix(filepath.Clean(mount.MountPath), "/run/drone") {
137 | return fmt.Errorf("linter: cannot mount volume at /run/drone")
138 | }
139 | }
140 | return nil
141 | }
142 |
143 | func checkVolumes(pipeline *resource.Pipeline, trusted bool) error {
144 | for _, volume := range pipeline.Volumes {
145 | if volume.EmptyDir != nil {
146 | err := checkEmptyDirVolume(volume.EmptyDir, trusted)
147 | if err != nil {
148 | return err
149 | }
150 | }
151 | if volume.HostPath != nil {
152 | err := checkHostPathVolume(volume.HostPath, trusted)
153 | if err != nil {
154 | return err
155 | }
156 | }
157 | switch volume.Name {
158 | case "":
159 | return fmt.Errorf("linter: missing volume name")
160 | case "workspace", "_workspace", "_docker_socket":
161 | return fmt.Errorf("linter: invalid volume name: %s", volume.Name)
162 | }
163 | }
164 | return nil
165 | }
166 |
167 | func checkHostPathVolume(volume *resource.VolumeHostPath, trusted bool) error {
168 | if trusted == false {
169 | return errors.New("linter: untrusted repositories cannot mount host volumes")
170 | }
171 | return nil
172 | }
173 |
174 | func checkEmptyDirVolume(volume *resource.VolumeEmptyDir, trusted bool) error {
175 | if trusted == false && volume.Medium == "memory" {
176 | return errors.New("linter: untrusted repositories cannot mount in-memory volumes")
177 | }
178 | return nil
179 | }
180 |
181 | func checkDeps(step *resource.Step, deps map[string]struct{}) error {
182 | for _, dep := range step.DependsOn {
183 | _, ok := deps[dep]
184 | if !ok {
185 | return fmt.Errorf("linter: unknown step dependency detected: %s references %s", step.Name, dep)
186 | }
187 | if step.Name == dep {
188 | return fmt.Errorf("linter: cyclical step dependency detected: %s", dep)
189 | }
190 | }
191 | return nil
192 | }
193 |
--------------------------------------------------------------------------------
/engine/linter/linter_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Drone.IO Inc. All rights reserved.
2 | // Use of this source code is governed by the Polyform License
3 | // that can be found in the LICENSE file.
4 |
5 | package linter
6 |
7 | import (
8 | "path"
9 | "testing"
10 |
11 | "github.com/drone-runners/drone-runner-docker/engine/resource"
12 | "github.com/drone/drone-go/drone"
13 | "github.com/drone/runner-go/manifest"
14 | )
15 |
16 | func TestLint(t *testing.T) {
17 | tests := []struct {
18 | path string
19 | trusted bool
20 | invalid bool
21 | message string
22 | }{
23 | {
24 | path: "testdata/simple.yml",
25 | trusted: false,
26 | invalid: false,
27 | },
28 | {
29 | path: "testdata/missing_image.yml",
30 | invalid: true,
31 | message: "linter: invalid or missing image",
32 | },
33 | // user should not use reserved volume names.
34 | {
35 | path: "testdata/volume_missing_name.yml",
36 | trusted: false,
37 | invalid: true,
38 | message: "linter: missing volume name",
39 | },
40 | {
41 | path: "testdata/volume_invalid_name.yml",
42 | trusted: false,
43 | invalid: true,
44 | message: "linter: invalid volume name: _workspace",
45 | },
46 | {
47 | path: "testdata/pipeline_volume_invalid_name.yml",
48 | trusted: false,
49 | invalid: true,
50 | message: "linter: invalid volume name: _docker_socket",
51 | },
52 | // user should not be trying to mount internal or restricted
53 | // volume paths.
54 | {
55 | path: "testdata/volume_restricted.yml",
56 | trusted: false,
57 | invalid: true,
58 | message: "linter: cannot mount volume at /run/drone",
59 | },
60 | // user should not be able to mount host path
61 | // volumes unless the repository is trusted.
62 | {
63 | path: "testdata/volume_host_path.yml",
64 | trusted: false,
65 | invalid: true,
66 | message: "linter: untrusted repositories cannot mount host volumes",
67 | },
68 | {
69 | path: "testdata/volume_host_path.yml",
70 | trusted: true,
71 | invalid: false,
72 | },
73 | // user should be able to mount emptyDir volumes
74 | // where no medium is specified.
75 | {
76 | path: "testdata/volume_empty_dir.yml",
77 | trusted: false,
78 | invalid: false,
79 | },
80 | // user should not be able to mount in-memory
81 | // emptyDir volumes unless the repository is
82 | // trusted.
83 | {
84 | path: "testdata/volume_empty_dir_memory.yml",
85 | trusted: false,
86 | invalid: true,
87 | message: "linter: untrusted repositories cannot mount in-memory volumes",
88 | },
89 | {
90 | path: "testdata/volume_empty_dir_memory.yml",
91 | trusted: true,
92 | invalid: false,
93 | },
94 | // user should not be able to mount devices unless
95 | // the repository is trusted.
96 | {
97 | path: "testdata/service_device.yml",
98 | trusted: false,
99 | invalid: true,
100 | message: "linter: untrusted repositories cannot mount devices",
101 | },
102 | {
103 | path: "testdata/service_device.yml",
104 | trusted: true,
105 | invalid: false,
106 | },
107 | {
108 | path: "testdata/pipeline_device.yml",
109 | trusted: false,
110 | invalid: true,
111 | message: "linter: untrusted repositories cannot mount devices",
112 | },
113 | {
114 | path: "testdata/pipeline_device.yml",
115 | trusted: true,
116 | invalid: false,
117 | },
118 | // user should not be able to set the securityContext
119 | // unless the repository is trusted.
120 | {
121 | path: "testdata/pipeline_privileged.yml",
122 | trusted: false,
123 | invalid: true,
124 | message: "linter: untrusted repositories cannot enable privileged mode",
125 | },
126 | {
127 | path: "testdata/pipeline_privileged.yml",
128 | trusted: true,
129 | invalid: false,
130 | },
131 | // user should not be able to set dns, dns_search or
132 | // extra_hosts unless the repository is trusted.
133 | {
134 | path: "testdata/pipeline_dns.yml",
135 | trusted: false,
136 | invalid: true,
137 | message: "linter: untrusted repositories cannot configure dns",
138 | },
139 | {
140 | path: "testdata/pipeline_dns.yml",
141 | trusted: true,
142 | invalid: false,
143 | },
144 | {
145 | path: "testdata/pipeline_dns_search.yml",
146 | trusted: false,
147 | invalid: true,
148 | message: "linter: untrusted repositories cannot configure dns_search",
149 | },
150 | {
151 | path: "testdata/pipeline_dns_search.yml",
152 | trusted: true,
153 | invalid: false,
154 | },
155 | {
156 | path: "testdata/pipeline_extra_hosts.yml",
157 | trusted: false,
158 | invalid: true,
159 | message: "linter: untrusted repositories cannot configure extra_hosts",
160 | },
161 | {
162 | path: "testdata/pipeline_extra_hosts.yml",
163 | trusted: true,
164 | invalid: false,
165 | },
166 | {
167 | path: "testdata/pipeline_network_mode.yml",
168 | trusted: false,
169 | invalid: true,
170 | message: "linter: untrusted repositories cannot configure network_mode",
171 | },
172 | {
173 | path: "testdata/pipeline_network_mode.yml",
174 | trusted: true,
175 | invalid: false,
176 | },
177 |
178 | //
179 | // The below checks were moved to the parser, however, we
180 | // should decide where we want this logic to live.
181 | //
182 |
183 | // // user should not be able to use duplicate names
184 | // // for steps or services.
185 | // {
186 | // path: "testdata/duplicate_step.yml",
187 | // invalid: true,
188 | // message: "linter: duplicate step names",
189 | // },
190 | // {
191 | // path: "testdata/duplicate_step_service.yml",
192 | // invalid: true,
193 | // message: "linter: duplicate step names",
194 | // },
195 | // {
196 | // path: "testdata/missing_name.yml",
197 | // invalid: true,
198 | // message: "linter: invalid or missing name",
199 | // },
200 |
201 | {
202 | path: "testdata/missing_dep.yml",
203 | invalid: true,
204 | message: "linter: unknown step dependency detected: test references foo",
205 | },
206 | }
207 | for _, test := range tests {
208 | name := path.Base(test.path)
209 | if test.trusted {
210 | name = name + "/trusted"
211 | }
212 | t.Run(name, func(t *testing.T) {
213 | resources, err := manifest.ParseFile(test.path)
214 | if err != nil {
215 | t.Logf("yaml: %s", test.path)
216 | t.Logf("trusted: %v", test.trusted)
217 | t.Error(err)
218 | return
219 | }
220 |
221 | lint := New()
222 | opts := &drone.Repo{Trusted: test.trusted}
223 | err = lint.Lint(resources.Resources[0].(*resource.Pipeline), opts)
224 | if err == nil && test.invalid == true {
225 | t.Logf("yaml: %s", test.path)
226 | t.Logf("trusted: %v", test.trusted)
227 | t.Errorf("Expect lint error")
228 | return
229 | }
230 |
231 | if err != nil && test.invalid == false {
232 | t.Logf("yaml: %s", test.path)
233 | t.Logf("trusted: %v", test.trusted)
234 | t.Errorf("Expect lint error is nil, got %s", err)
235 | return
236 | }
237 |
238 | if err == nil {
239 | return
240 | }
241 |
242 | if got, want := err.Error(), test.message; got != want {
243 | t.Logf("yaml: %s", test.path)
244 | t.Logf("trusted: %v", test.trusted)
245 | t.Errorf("Want message %q, got %q", want, got)
246 | return
247 | }
248 | })
249 | }
250 | }
251 |
--------------------------------------------------------------------------------
/engine/linter/testdata/duplicate_name.yml:
--------------------------------------------------------------------------------
1 | ---
2 | kind: pipeline
3 | type: docker
4 | name: default
5 |
6 | steps:
7 | - name: build
8 | image: golang
9 | commands:
10 | - go build
11 | - go test
12 |
13 | ---
14 | kind: pipeline
15 | name: default
16 |
17 | steps:
18 | - name: build
19 | image: golang
20 | commands:
21 | - go build
22 | - go test
23 |
24 | ...
--------------------------------------------------------------------------------
/engine/linter/testdata/duplicate_step.yml:
--------------------------------------------------------------------------------
1 | ---
2 | kind: pipeline
3 | type: docker
4 | name: default
5 |
6 | steps:
7 | - name: build
8 | image: golang
9 | commands:
10 | - go build
11 | - go test
12 |
13 | - name: build
14 | image: golang
15 | commands:
16 | - go build
17 | - go test
--------------------------------------------------------------------------------
/engine/linter/testdata/duplicate_step_service.yml:
--------------------------------------------------------------------------------
1 | ---
2 | kind: pipeline
3 | type: docker
4 | name: default
5 |
6 | steps:
7 | - name: test
8 | image: golang
9 | commands:
10 | - go build
11 | - go test
12 |
13 | services:
14 | - name: test
15 | image: redis
16 |
--------------------------------------------------------------------------------
/engine/linter/testdata/invalid_arch.yml:
--------------------------------------------------------------------------------
1 | ---
2 | kind: pipeline
3 | type: docker
4 | name: linux
5 |
6 | platform:
7 | os: linux
8 | arch: s390x
9 |
10 | steps:
11 | - name: build
12 | image: golang
13 | commands:
14 | - go build
15 | - go test
16 |
--------------------------------------------------------------------------------
/engine/linter/testdata/invalid_os.yml:
--------------------------------------------------------------------------------
1 | ---
2 | kind: pipeline
3 | type: docker
4 | name: linux
5 |
6 | platform:
7 | os: openbsd
8 |
9 | steps:
10 | - name: build
11 | image: golang
12 | commands:
13 | - go build
14 | - go test
15 |
--------------------------------------------------------------------------------
/engine/linter/testdata/missing_build_image.yml:
--------------------------------------------------------------------------------
1 | ---
2 | kind: pipeline
3 | type: docker
4 | name: linux
5 |
6 | steps:
7 | - name: test
8 | build: {}
9 |
10 |
--------------------------------------------------------------------------------
/engine/linter/testdata/missing_dep.yml:
--------------------------------------------------------------------------------
1 | ---
2 | kind: pipeline
3 | type: docker
4 | name: default
5 |
6 | steps:
7 | - name: build
8 | image: golang
9 | commands:
10 | - go build
11 |
12 | - name: test
13 | image: golang
14 | commands:
15 | - go build
16 | - go test
17 | depends_on:
18 | - foo
--------------------------------------------------------------------------------
/engine/linter/testdata/missing_image.yml:
--------------------------------------------------------------------------------
1 | ---
2 | kind: pipeline
3 | type: docker
4 | name: linux
5 |
6 | steps:
7 | - name: test
8 | commands:
9 | - go build
10 | - go test
11 |
--------------------------------------------------------------------------------
/engine/linter/testdata/missing_name.yml:
--------------------------------------------------------------------------------
1 | ---
2 | kind: pipeline
3 | type: docker
4 | name: linux
5 |
6 | steps:
7 | - image: golang
8 | commands:
9 | - go build
10 | - go test
11 |
--------------------------------------------------------------------------------
/engine/linter/testdata/pipeline_device.yml:
--------------------------------------------------------------------------------
1 | ---
2 | kind: pipeline
3 | type: docker
4 | name: linux
5 |
6 | steps:
7 | - name: test
8 | image: golang
9 | commands:
10 | - go build
11 | - go test
12 | devices:
13 | - name: data
14 | path: /dev/xvda
15 |
16 | services:
17 | - name: database
18 | image: redis
19 | ports:
20 | - 6379
21 |
--------------------------------------------------------------------------------
/engine/linter/testdata/pipeline_dns.yml:
--------------------------------------------------------------------------------
1 | ---
2 | kind: pipeline
3 | type: docker
4 | name: linux
5 |
6 | steps:
7 | - name: test
8 | image: golang
9 | commands:
10 | - go build
11 | - go test
12 | dns:
13 | - 8.8.8.8
14 |
--------------------------------------------------------------------------------
/engine/linter/testdata/pipeline_dns_search.yml:
--------------------------------------------------------------------------------
1 | ---
2 | kind: pipeline
3 | type: docker
4 | name: linux
5 |
6 | steps:
7 | - name: test
8 | image: golang
9 | commands:
10 | - go build
11 | - go test
12 | dns_search:
13 | - dc1.example.com
14 | - dc2.example.com
15 |
--------------------------------------------------------------------------------
/engine/linter/testdata/pipeline_extra_hosts.yml:
--------------------------------------------------------------------------------
1 | ---
2 | kind: pipeline
3 | type: docker
4 | name: linux
5 |
6 | steps:
7 | - name: test
8 | image: golang
9 | commands:
10 | - go build
11 | - go test
12 | extra_hosts:
13 | - "somehost:162.242.195.82"
14 | - "otherhost:50.31.209.229"
15 |
--------------------------------------------------------------------------------
/engine/linter/testdata/pipeline_network_mode.yml:
--------------------------------------------------------------------------------
1 | ---
2 | kind: pipeline
3 | type: docker
4 | name: linux
5 |
6 | steps:
7 | - name: test
8 | image: golang
9 | commands:
10 | - go build
11 | - go test
12 | network_mode: host
13 |
--------------------------------------------------------------------------------
/engine/linter/testdata/pipeline_port_host.yml:
--------------------------------------------------------------------------------
1 | ---
2 | kind: pipeline
3 | type: docker
4 | name: linux
5 |
6 | steps:
7 | - name: database
8 | image: redis
9 | detach: true
10 | ports:
11 | - port: 6379
12 | host: 6379
13 |
14 | - name: test
15 | image: golang
16 | commands:
17 | - go build
18 | - go test
19 |
--------------------------------------------------------------------------------
/engine/linter/testdata/pipeline_privileged.yml:
--------------------------------------------------------------------------------
1 | ---
2 | kind: pipeline
3 | type: docker
4 | name: linux
5 |
6 | steps:
7 | - name: test
8 | image: golang
9 | commands:
10 | - go build
11 | - go test
12 | privileged: true
13 |
14 | services:
15 | - name: database
16 | image: redis
17 | ports:
18 | - 6379
19 |
--------------------------------------------------------------------------------
/engine/linter/testdata/pipeline_volume_invalid_name.yml:
--------------------------------------------------------------------------------
1 | ---
2 | kind: pipeline
3 | type: docker
4 | name: linux
5 |
6 | steps:
7 | - name: test
8 | image: docker
9 | volumes:
10 | - name: _docker_socket
11 | path: /var/run/docker.sock
12 | commands:
13 | - docker system prune
14 |
15 |
--------------------------------------------------------------------------------
/engine/linter/testdata/service_device.yml:
--------------------------------------------------------------------------------
1 | ---
2 | kind: pipeline
3 | type: docker
4 | name: linux
5 |
6 | steps:
7 | - name: test
8 | image: golang
9 | commands:
10 | - go build
11 | - go test
12 |
13 | services:
14 | - name: database
15 | image: redis
16 | ports:
17 | - 6379
18 | devices:
19 | - name: data
20 | path: /dev/xvda
21 |
--------------------------------------------------------------------------------
/engine/linter/testdata/service_port_host.yml:
--------------------------------------------------------------------------------
1 | ---
2 | kind: pipeline
3 | type: docker
4 | name: linux
5 |
6 | steps:
7 | - name: test
8 | image: golang
9 | commands:
10 | - go build
11 | - go test
12 |
13 | services:
14 | - name: database
15 | image: redis
16 | ports:
17 | - port: 6379
18 | host: 6379
19 |
--------------------------------------------------------------------------------
/engine/linter/testdata/simple.yml:
--------------------------------------------------------------------------------
1 | ---
2 | kind: pipeline
3 | type: docker
4 | name: amd64
5 |
6 | steps:
7 | - name: build
8 | image: golang
9 | commands:
10 | - go build
11 |
12 | - name: test
13 | image: golang
14 | commands:
15 | - go test
16 |
17 | services:
18 | - name: database
19 | image: redis
20 | ports:
21 | - 6379
22 |
23 | ---
24 | kind: pipeline
25 | name: arm
26 |
27 | platform:
28 | arch: arm
29 |
30 | steps:
31 | - name: test
32 | image: golang
33 | commands:
34 | - go build
35 | - go test
36 |
37 | depends_on:
38 | - amd64
39 | ...
--------------------------------------------------------------------------------
/engine/linter/testdata/volume_empty_dir.yml:
--------------------------------------------------------------------------------
1 | ---
2 | kind: pipeline
3 | type: docker
4 | name: linux
5 |
6 | steps:
7 | - name: test
8 | image: golang
9 | commands:
10 | - go build
11 | - go test
12 |
13 | services:
14 | - name: database
15 | image: redis
16 | ports:
17 | - 6379
18 |
19 | volumes:
20 | - name: vol
21 | temp: {}
22 |
--------------------------------------------------------------------------------
/engine/linter/testdata/volume_empty_dir_memory.yml:
--------------------------------------------------------------------------------
1 | ---
2 | kind: pipeline
3 | type: docker
4 | name: linux
5 |
6 | steps:
7 | - name: test
8 | image: golang
9 | commands:
10 | - go build
11 | - go test
12 |
13 | services:
14 | - name: database
15 | image: redis
16 | ports:
17 | - 6379
18 |
19 | volumes:
20 | - name: vol
21 | temp:
22 | medium: memory
23 |
--------------------------------------------------------------------------------
/engine/linter/testdata/volume_host_path.yml:
--------------------------------------------------------------------------------
1 | ---
2 | kind: pipeline
3 | type: docker
4 | name: linux
5 |
6 | steps:
7 | - name: test
8 | image: golang
9 | commands:
10 | - go build
11 | - go test
12 |
13 | services:
14 | - name: database
15 | image: redis
16 | ports:
17 | - 6379
18 |
19 | volumes:
20 | - name: vol
21 | host:
22 | path: /any/path/it/will/be/replaced
23 |
--------------------------------------------------------------------------------
/engine/linter/testdata/volume_invalid_name.yml:
--------------------------------------------------------------------------------
1 | ---
2 | kind: pipeline
3 | type: docker
4 | name: linux
5 |
6 | steps:
7 | - name: test
8 | image: golang
9 | commands:
10 | - go build
11 | - go test
12 |
13 | services:
14 | - name: database
15 | image: redis
16 | ports:
17 | - 6379
18 |
19 | volumes:
20 | - name: _workspace
21 | temp: {}
22 |
--------------------------------------------------------------------------------
/engine/linter/testdata/volume_missing_name.yml:
--------------------------------------------------------------------------------
1 | ---
2 | kind: pipeline
3 | type: docker
4 | name: linux
5 |
6 | steps:
7 | - name: test
8 | image: golang
9 | commands:
10 | - go build
11 | - go test
12 |
13 | services:
14 | - name: database
15 | image: redis
16 |
17 | volumes:
18 | - temp: {}
19 |
--------------------------------------------------------------------------------
/engine/linter/testdata/volume_restricted.yml:
--------------------------------------------------------------------------------
1 | ---
2 | kind: pipeline
3 | type: docker
4 | name: linux
5 |
6 | steps:
7 | - name: test
8 | image: golang
9 | commands:
10 | - go build
11 | - go test
12 | volumes:
13 | - name: vol
14 | path: /run/drone/env
15 |
16 | volumes:
17 | - name: vol
18 | temp: {}
--------------------------------------------------------------------------------
/engine/resource/linter.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Drone.IO Inc. All rights reserved.
2 | // Use of this source code is governed by the Polyform License
3 | // that can be found in the LICENSE file.
4 |
5 | package resource
6 |
--------------------------------------------------------------------------------
/engine/resource/lookup.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Drone.IO Inc. All rights reserved.
2 | // Use of this source code is governed by the Polyform License
3 | // that can be found in the LICENSE file.
4 |
5 | package resource
6 |
7 | import (
8 | "errors"
9 |
10 | "github.com/drone/runner-go/manifest"
11 | )
12 |
13 | // Lookup returns the named pipeline from the Manifest.
14 | func Lookup(name string, manifest *manifest.Manifest) (manifest.Resource, error) {
15 | for _, resource := range manifest.Resources {
16 | if !isNameMatch(resource.GetName(), name) {
17 | continue
18 | }
19 | if pipeline, ok := resource.(*Pipeline); ok {
20 | return pipeline, nil
21 | }
22 | }
23 | return nil, errors.New("resource not found")
24 | }
25 |
26 | // helper function returns true if the name matches.
27 | func isNameMatch(a, b string) bool {
28 | return a == b ||
29 | (a == "" && b == "default") ||
30 | (b == "" && a == "default")
31 | }
32 |
--------------------------------------------------------------------------------
/engine/resource/lookup_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Drone.IO Inc. All rights reserved.
2 | // Use of this source code is governed by the Polyform License
3 | // that can be found in the LICENSE file.
4 |
5 | package resource
6 |
7 | import (
8 | "testing"
9 |
10 | "github.com/drone/runner-go/manifest"
11 | )
12 |
13 | func TestLookup(t *testing.T) {
14 | want := &Pipeline{Name: "default"}
15 | m := &manifest.Manifest{
16 | Resources: []manifest.Resource{want},
17 | }
18 | got, err := Lookup("default", m)
19 | if err != nil {
20 | t.Error(err)
21 | }
22 | if got != want {
23 | t.Errorf("Expect resource not found error")
24 | }
25 | }
26 |
27 | func TestLookupNotFound(t *testing.T) {
28 | m := &manifest.Manifest{
29 | Resources: []manifest.Resource{
30 | &manifest.Secret{
31 | Kind: "secret",
32 | Name: "password",
33 | },
34 | // matches name, but is not of kind pipeline
35 | &manifest.Secret{
36 | Kind: "secret",
37 | Name: "default",
38 | },
39 | },
40 | }
41 | _, err := Lookup("default", m)
42 | if err == nil {
43 | t.Errorf("Expect resource not found error")
44 | }
45 | }
46 |
47 | func TestNameMatch(t *testing.T) {
48 | tests := []struct {
49 | a, b string
50 | match bool
51 | }{
52 | {"a", "b", false},
53 | {"a", "a", true},
54 | {"", "default", true},
55 | }
56 | for _, test := range tests {
57 | got, want := isNameMatch(test.a, test.b), test.match
58 | if got != want {
59 | t.Errorf("Expect %q and %q match is %v", test.a, test.b, want)
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/engine/resource/parser.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Drone.IO Inc. All rights reserved.
2 | // Use of this source code is governed by the Polyform License
3 | // that can be found in the LICENSE file.
4 |
5 | package resource
6 |
7 | import (
8 | "errors"
9 |
10 | "github.com/drone/runner-go/manifest"
11 |
12 | "github.com/buildkite/yaml"
13 | )
14 |
15 | func init() {
16 | manifest.Register(parse)
17 | }
18 |
19 | // parse parses the raw resource and returns an Exec pipeline.
20 | func parse(r *manifest.RawResource) (manifest.Resource, bool, error) {
21 | if !match(r) {
22 | return nil, false, nil
23 | }
24 | out := new(Pipeline)
25 | err := yaml.Unmarshal(r.Data, out)
26 | if err != nil {
27 | return out, true, err
28 | }
29 | err = lint(out)
30 | return out, true, err
31 | }
32 |
33 | // match returns true if the resource matches the kind and type.
34 | func match(r *manifest.RawResource) bool {
35 | return (r.Kind == Kind && r.Type == Type) ||
36 | (r.Kind == Kind && r.Type == "")
37 | }
38 |
39 | func lint(pipeline *Pipeline) error {
40 | // ensure pipeline steps are not unique.
41 | names := map[string]struct{}{}
42 | for _, step := range pipeline.Steps {
43 | if step == nil {
44 | return errors.New("Linter: detected nil step")
45 | }
46 | if step.Name == "" {
47 | return errors.New("Linter: invalid or missing step name")
48 | }
49 | if len(step.Name) > 100 {
50 | return errors.New("Linter: step name cannot exceed 100 characters")
51 | }
52 | if _, ok := names[step.Name]; ok {
53 | return errors.New("Linter: duplicate step name")
54 | }
55 | names[step.Name] = struct{}{}
56 | }
57 | return nil
58 | }
59 |
--------------------------------------------------------------------------------
/engine/resource/parser_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Drone.IO Inc. All rights reserved.
2 | // Use of this source code is governed by the Polyform License
3 | // that can be found in the LICENSE file.
4 |
5 | package resource
6 |
7 | import (
8 | "testing"
9 |
10 | "github.com/drone/runner-go/manifest"
11 |
12 | "github.com/google/go-cmp/cmp"
13 | )
14 |
15 | func TestParse(t *testing.T) {
16 | got, err := manifest.ParseFile("testdata/manifest.yml")
17 | if err != nil {
18 | t.Error(err)
19 | return
20 | }
21 |
22 | want := []manifest.Resource{
23 | &manifest.Signature{
24 | Kind: "signature",
25 | Hmac: "a8842634682b78946a2",
26 | },
27 | &manifest.Secret{
28 | Kind: "secret",
29 | Type: "encrypted",
30 | Name: "token",
31 | Data: "f0e4c2f76c58916ec25",
32 | },
33 | &Pipeline{
34 | Kind: "pipeline",
35 | Type: "docker",
36 | Name: "default",
37 | Version: "1",
38 | Environment: map[string]string{
39 | "NODE_ENV": "development",
40 | },
41 | Workspace: Workspace{
42 | Path: "/drone/src",
43 | },
44 | Platform: manifest.Platform{
45 | OS: "linux",
46 | Arch: "arm64",
47 | },
48 | Clone: manifest.Clone{
49 | Depth: 50,
50 | },
51 | Deps: []string{"dependency"},
52 | PullSecrets: []string{"dockerconfigjson"},
53 | Trigger: manifest.Conditions{
54 | Branch: manifest.Condition{
55 | Include: []string{"master"},
56 | },
57 | },
58 | Services: []*Step{
59 | {
60 | Name: "redis",
61 | Image: "redis:latest",
62 | Entrypoint: []string{"/bin/redis-server"},
63 | Command: []string{"--debug"},
64 | },
65 | },
66 | Steps: []*Step{
67 | {
68 | Name: "build",
69 | Image: "golang",
70 | Detach: false,
71 | DependsOn: []string{"clone"},
72 | Commands: []string{
73 | "go build",
74 | "go test",
75 | },
76 | Environment: map[string]*manifest.Variable{
77 | "GOOS": &manifest.Variable{Value: "linux"},
78 | "GOARCH": &manifest.Variable{Value: "arm64"},
79 | },
80 | MemLimit: manifest.BytesSize(1073741824),
81 | MemSwapLimit: manifest.BytesSize(2147483648),
82 | Failure: "ignore",
83 | When: manifest.Conditions{
84 | Event: manifest.Condition{
85 | Include: []string{"push"},
86 | },
87 | },
88 | },
89 | },
90 | },
91 | }
92 |
93 | if diff := cmp.Diff(got.Resources, want); diff != "" {
94 | t.Errorf("Unexpected manifest")
95 | t.Log(diff)
96 | }
97 | }
98 |
99 | func TestParseErr(t *testing.T) {
100 | _, err := manifest.ParseFile("testdata/malformed.yml")
101 | if err == nil {
102 | t.Errorf("Expect error when malformed yaml")
103 | }
104 | }
105 |
106 | func TestParseLintErr(t *testing.T) {
107 | _, err := manifest.ParseFile("testdata/linterr.yml")
108 | if err == nil {
109 | t.Errorf("Expect linter returns error")
110 | return
111 | }
112 | }
113 |
114 | func TestParseLintNilStep(t *testing.T) {
115 | _, err := manifest.ParseFile("testdata/nilstep.yml")
116 | if err == nil {
117 | t.Errorf("Expect linter returns error")
118 | return
119 | }
120 | }
121 |
122 | func TestParseNoMatch(t *testing.T) {
123 | r := &manifest.RawResource{Kind: "pipeline", Type: "exec"}
124 | _, match, _ := parse(r)
125 | if match {
126 | t.Errorf("Expect no match")
127 | }
128 | }
129 |
130 | func TestMatch(t *testing.T) {
131 | r := &manifest.RawResource{
132 | Kind: "pipeline",
133 | Type: "docker",
134 | }
135 | if match(r) == false {
136 | t.Errorf("Expect match, got false")
137 | }
138 |
139 | r = &manifest.RawResource{
140 | Kind: "approval",
141 | Type: "docker",
142 | }
143 | if match(r) == true {
144 | t.Errorf("Expect kind mismatch, got true")
145 | }
146 |
147 | r = &manifest.RawResource{
148 | Kind: "pipeline",
149 | Type: "dummy",
150 | }
151 | if match(r) == true {
152 | t.Errorf("Expect type mismatch, got true")
153 | }
154 |
155 | }
156 |
157 | func TestLint(t *testing.T) {
158 | p := new(Pipeline)
159 | p.Steps = []*Step{{Name: "build"}, {Name: "test"}}
160 | if err := lint(p); err != nil {
161 | t.Errorf("Expect no lint error, got %s", err)
162 | }
163 |
164 | p.Steps = []*Step{{Name: "build"}, {Name: "build"}}
165 | if err := lint(p); err == nil {
166 | t.Errorf("Expect error when duplicate name")
167 | }
168 |
169 | p.Steps = []*Step{{Name: "build"}, {Name: ""}}
170 | if err := lint(p); err == nil {
171 | t.Errorf("Expect error when empty name")
172 | }
173 | }
174 |
--------------------------------------------------------------------------------
/engine/resource/pipeline.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Drone.IO Inc. All rights reserved.
2 | // Use of this source code is governed by the Polyform License
3 | // that can be found in the LICENSE file.
4 |
5 | package resource
6 |
7 | import "github.com/drone/runner-go/manifest"
8 |
9 | var (
10 | _ manifest.Resource = (*Pipeline)(nil)
11 | _ manifest.TriggeredResource = (*Pipeline)(nil)
12 | _ manifest.DependantResource = (*Pipeline)(nil)
13 | _ manifest.PlatformResource = (*Pipeline)(nil)
14 | )
15 |
16 | // Defines the Resource Kind and Type.
17 | const (
18 | Kind = "pipeline"
19 | Type = "docker"
20 | )
21 |
22 | // Pipeline is a pipeline resource that executes pipelines
23 | // on the host machine without any virtualization.
24 | type Pipeline struct {
25 | Version string `json:"version,omitempty"`
26 | Kind string `json:"kind,omitempty"`
27 | Type string `json:"type,omitempty"`
28 | Name string `json:"name,omitempty"`
29 | Deps []string `json:"depends_on,omitempty" yaml:"depends_on"`
30 |
31 | Clone manifest.Clone `json:"clone,omitempty"`
32 | Concurrency manifest.Concurrency `json:"concurrency,omitempty"`
33 | Node map[string]string `json:"node,omitempty"`
34 | Platform manifest.Platform `json:"platform,omitempty"`
35 | Trigger manifest.Conditions `json:"conditions,omitempty"`
36 |
37 | Environment map[string]string `json:"environment,omitempty"`
38 | Services []*Step `json:"services,omitempty"`
39 | Steps []*Step `json:"steps,omitempty"`
40 | Volumes []*Volume `json:"volumes,omitempty"`
41 | PullSecrets []string `json:"image_pull_secrets,omitempty" yaml:"image_pull_secrets"`
42 | Workspace Workspace `json:"workspace,omitempty"`
43 | }
44 |
45 | // GetVersion returns the resource version.
46 | func (p *Pipeline) GetVersion() string { return p.Version }
47 |
48 | // GetKind returns the resource kind.
49 | func (p *Pipeline) GetKind() string { return p.Kind }
50 |
51 | // GetType returns the resource type.
52 | func (p *Pipeline) GetType() string { return p.Type }
53 |
54 | // GetName returns the resource name.
55 | func (p *Pipeline) GetName() string { return p.Name }
56 |
57 | // GetDependsOn returns the resource dependencies.
58 | func (p *Pipeline) GetDependsOn() []string { return p.Deps }
59 |
60 | // GetTrigger returns the resource triggers.
61 | func (p *Pipeline) GetTrigger() manifest.Conditions { return p.Trigger }
62 |
63 | // GetNodes returns the resource node labels.
64 | func (p *Pipeline) GetNodes() map[string]string { return p.Node }
65 |
66 | // GetPlatform returns the resource platform.
67 | func (p *Pipeline) GetPlatform() manifest.Platform { return p.Platform }
68 |
69 | // GetConcurrency returns the resource concurrency limits.
70 | func (p *Pipeline) GetConcurrency() manifest.Concurrency { return p.Concurrency }
71 |
72 | // GetStep returns the named step. If no step exists with the
73 | // given name, a nil value is returned.
74 | func (p *Pipeline) GetStep(name string) *Step {
75 | for _, step := range p.Steps {
76 | if step.Name == name {
77 | return step
78 | }
79 | }
80 | return nil
81 | }
82 |
83 | type (
84 | // Step defines a Pipeline step.
85 | Step struct {
86 | Command []string `json:"command,omitempty"`
87 | Commands []string `json:"commands,omitempty"`
88 | Detach bool `json:"detach,omitempty"`
89 | DependsOn []string `json:"depends_on,omitempty" yaml:"depends_on"`
90 | Devices []*VolumeDevice `json:"devices,omitempty"`
91 | DNS []string `json:"dns,omitempty"`
92 | DNSSearch []string `json:"dns_search,omitempty" yaml:"dns_search"`
93 | Entrypoint []string `json:"entrypoint,omitempty"`
94 | Environment map[string]*manifest.Variable `json:"environment,omitempty"`
95 | ExtraHosts []string `json:"extra_hosts,omitempty" yaml:"extra_hosts"`
96 | Failure string `json:"failure,omitempty"`
97 | Image string `json:"image,omitempty"`
98 | MemLimit manifest.BytesSize `json:"mem_limit,omitempty" yaml:"mem_limit"`
99 | MemSwapLimit manifest.BytesSize `json:"memswap_limit,omitempty" yaml:"memswap_limit"`
100 | Network string `json:"network_mode,omitempty" yaml:"network_mode"`
101 | Name string `json:"name,omitempty"`
102 | Privileged bool `json:"privileged,omitempty"`
103 | Pull string `json:"pull,omitempty"`
104 | Settings map[string]*manifest.Parameter `json:"settings,omitempty"`
105 | Shell string `json:"shell,omitempty"`
106 | ShmSize manifest.BytesSize `json:"shm_size,omitempty" yaml:"shm_size"`
107 | User string `json:"user,omitempty"`
108 | Volumes []*VolumeMount `json:"volumes,omitempty"`
109 | When manifest.Conditions `json:"when,omitempty"`
110 | WorkingDir string `json:"working_dir,omitempty" yaml:"working_dir"`
111 | }
112 |
113 | // Volume that can be mounted by containers.
114 | Volume struct {
115 | Name string `json:"name,omitempty"`
116 | EmptyDir *VolumeEmptyDir `json:"temp,omitempty" yaml:"temp"`
117 | HostPath *VolumeHostPath `json:"host,omitempty" yaml:"host"`
118 | }
119 |
120 | // VolumeDevice describes a mapping of a raw block
121 | // device within a container.
122 | VolumeDevice struct {
123 | Name string `json:"name,omitempty"`
124 | DevicePath string `json:"path,omitempty" yaml:"path"`
125 | }
126 |
127 | // VolumeMount describes a mounting of a Volume
128 | // within a container.
129 | VolumeMount struct {
130 | Name string `json:"name,omitempty"`
131 | MountPath string `json:"path,omitempty" yaml:"path"`
132 | }
133 |
134 | // VolumeEmptyDir mounts a temporary directory from the
135 | // host node's filesystem into the container. This can
136 | // be used as a shared scratch space.
137 | VolumeEmptyDir struct {
138 | Medium string `json:"medium,omitempty"`
139 | SizeLimit manifest.BytesSize `json:"size_limit,omitempty" yaml:"size_limit"`
140 | }
141 |
142 | // VolumeHostPath mounts a file or directory from the
143 | // host node's filesystem into your container.
144 | VolumeHostPath struct {
145 | Path string `json:"path,omitempty"`
146 | }
147 |
148 | // Workspace represents the pipeline workspace configuration.
149 | Workspace struct {
150 | Base string `json:"base,omitempty"`
151 | Path string `json:"path,omitempty"`
152 | }
153 | )
154 |
--------------------------------------------------------------------------------
/engine/resource/pipeline_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Drone.IO Inc. All rights reserved.
2 | // Use of this source code is governed by the Polyform License
3 | // that can be found in the LICENSE file.
4 |
5 | package resource
6 |
7 | import (
8 | "testing"
9 |
10 | "github.com/drone/runner-go/manifest"
11 |
12 | "github.com/google/go-cmp/cmp"
13 | )
14 |
15 | func TestGetStep(t *testing.T) {
16 | step1 := &Step{Name: "build"}
17 | step2 := &Step{Name: "test"}
18 | pipeline := &Pipeline{
19 | Steps: []*Step{step1, step2},
20 | }
21 | if pipeline.GetStep("build") != step1 {
22 | t.Errorf("Expected named step")
23 | }
24 | if pipeline.GetStep("deploy") != nil {
25 | t.Errorf("Expected nil step")
26 | }
27 | }
28 |
29 | func TestGetters(t *testing.T) {
30 | platform := manifest.Platform{
31 | OS: "linux",
32 | Arch: "amd64",
33 | }
34 | trigger := manifest.Conditions{
35 | Branch: manifest.Condition{
36 | Include: []string{"master"},
37 | },
38 | }
39 | pipeline := &Pipeline{
40 | Version: "1.0.0",
41 | Kind: "pipeline",
42 | Type: "docker",
43 | Name: "default",
44 | Deps: []string{"before"},
45 | Platform: platform,
46 | Trigger: trigger,
47 | }
48 | if got, want := pipeline.GetVersion(), pipeline.Version; got != want {
49 | t.Errorf("Want Version %s, got %s", want, got)
50 | }
51 | if got, want := pipeline.GetKind(), pipeline.Kind; got != want {
52 | t.Errorf("Want Kind %s, got %s", want, got)
53 | }
54 | if got, want := pipeline.GetType(), pipeline.Type; got != want {
55 | t.Errorf("Want Type %s, got %s", want, got)
56 | }
57 | if got, want := pipeline.GetName(), pipeline.Name; got != want {
58 | t.Errorf("Want Name %s, got %s", want, got)
59 | }
60 | if diff := cmp.Diff(pipeline.GetDependsOn(), pipeline.Deps); diff != "" {
61 | t.Errorf("Unexpected Deps")
62 | t.Log(diff)
63 | }
64 | if diff := cmp.Diff(pipeline.GetTrigger(), pipeline.Trigger); diff != "" {
65 | t.Errorf("Unexpected Trigger")
66 | t.Log(diff)
67 | }
68 | if got, want := pipeline.GetPlatform(), pipeline.Platform; got != want {
69 | t.Errorf("Want Platform %s, got %s", want, got)
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/engine/resource/testdata/linterr.yml:
--------------------------------------------------------------------------------
1 | ---
2 | kind: pipeline
3 | type: docker
4 |
5 | server:
6 | image: docker-18-04
7 | region: nyc1
8 | size: s-1vcpu-1gb
9 |
10 | steps:
11 | - commands:
12 | - go build
13 | - go test
14 |
15 | ...
--------------------------------------------------------------------------------
/engine/resource/testdata/malformed.yml:
--------------------------------------------------------------------------------
1 | ---
2 | kind: pipeline
3 | type: docker
4 |
5 | steps:
6 | foo: bar
7 |
8 | ...
--------------------------------------------------------------------------------
/engine/resource/testdata/manifest.yml:
--------------------------------------------------------------------------------
1 | ---
2 | kind: signature
3 | hmac: a8842634682b78946a2
4 |
5 | ---
6 | kind: secret
7 | type: encrypted
8 | name: token
9 | data: f0e4c2f76c58916ec25
10 |
11 | ---
12 | kind: pipeline
13 | type: docker
14 | name: default
15 | version: 1
16 |
17 | depends_on:
18 | - dependency
19 |
20 | platform:
21 | os: linux
22 | arch: arm64
23 |
24 | workspace:
25 | path: /drone/src
26 |
27 | clone:
28 | depth: 50
29 |
30 | environment:
31 | NODE_ENV: development
32 |
33 | steps:
34 | - name: build
35 | image: golang
36 | detach: false
37 | failure: ignore
38 | mem_limit: 1GiB
39 | memswap_limit: 2GiB
40 | commands:
41 | - go build
42 | - go test
43 | environment:
44 | GOOS: linux
45 | GOARCH: arm64
46 | depends_on: [ clone ]
47 | when:
48 | event: [ push ]
49 |
50 | services:
51 | - name: redis
52 | image: redis:latest
53 | entrypoint: [ "/bin/redis-server" ]
54 | command: [ "--debug" ]
55 |
56 | image_pull_secrets:
57 | - dockerconfigjson
58 |
59 | trigger:
60 | branch: [ master ]
61 |
62 | ...
63 |
--------------------------------------------------------------------------------
/engine/resource/testdata/nilstep.yml:
--------------------------------------------------------------------------------
1 | ---
2 | kind: pipeline
3 | type: docker
4 | name: test
5 |
6 | steps:
7 | - ~
8 |
9 | ...
--------------------------------------------------------------------------------
/engine/resource/testdata/nomatch.yml:
--------------------------------------------------------------------------------
1 | ---
2 | kind: pipeline
3 | type: docker
4 |
5 | ...
--------------------------------------------------------------------------------
/engine/spec.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Drone.IO Inc. All rights reserved.
2 | // Use of this source code is governed by the Polyform License
3 | // that can be found in the LICENSE file.
4 |
5 | package engine
6 |
7 | import (
8 | "github.com/drone/runner-go/environ"
9 | "github.com/drone/runner-go/pipeline/runtime"
10 | )
11 |
12 | type (
13 |
14 | // Spec provides the pipeline spec. This provides the
15 | // required instructions for reproducible pipeline
16 | // execution.
17 | Spec struct {
18 | Platform Platform `json:"platform,omitempty"`
19 | Steps []*Step `json:"steps,omitempty"`
20 | Internal []*Step `json:"internal,omitempty"`
21 | Volumes []*Volume `json:"volumes,omitempty"`
22 | Network Network `json:"network"`
23 | }
24 |
25 | // Step defines a pipeline step.
26 | Step struct {
27 | ID string `json:"id,omitempty"`
28 | Auth *Auth `json:"auth,omitempty"`
29 | Command []string `json:"args,omitempty"`
30 | CPUPeriod int64 `json:"cpu_period,omitempty"`
31 | CPUQuota int64 `json:"cpu_quota,omitempty"`
32 | CPUShares int64 `json:"cpu_shares,omitempty"`
33 | CPUSet []string `json:"cpu_set,omitempty"`
34 | Detach bool `json:"detach,omitempty"`
35 | DependsOn []string `json:"depends_on,omitempty"`
36 | Devices []*VolumeDevice `json:"devices,omitempty"`
37 | DNS []string `json:"dns,omitempty"`
38 | DNSSearch []string `json:"dns_search,omitempty"`
39 | Entrypoint []string `json:"entrypoint,omitempty"`
40 | Envs map[string]string `json:"environment,omitempty"`
41 | ErrPolicy runtime.ErrPolicy `json:"err_policy,omitempty"`
42 | ExtraHosts []string `json:"extra_hosts,omitempty"`
43 | IgnoreStdout bool `json:"ignore_stderr,omitempty"`
44 | IgnoreStderr bool `json:"ignore_stdout,omitempty"`
45 | Image string `json:"image,omitempty"`
46 | Labels map[string]string `json:"labels,omitempty"`
47 | MemSwapLimit int64 `json:"memswap_limit,omitempty"`
48 | MemLimit int64 `json:"mem_limit,omitempty"`
49 | Name string `json:"name,omitempty"`
50 | Network string `json:"network,omitempty"`
51 | Networks []string `json:"networks,omitempty"`
52 | Privileged bool `json:"privileged,omitempty"`
53 | Pull PullPolicy `json:"pull,omitempty"`
54 | RunPolicy runtime.RunPolicy `json:"run_policy,omitempty"`
55 | Secrets []*Secret `json:"secrets,omitempty"`
56 | ShmSize int64 `json:"shm_size,omitempty"`
57 | User string `json:"user,omitempty"`
58 | Volumes []*VolumeMount `json:"volumes,omitempty"`
59 | WorkingDir string `json:"working_dir,omitempty"`
60 | }
61 |
62 | // Secret represents a secret variable.
63 | Secret struct {
64 | Name string `json:"name,omitempty"`
65 | Env string `json:"env,omitempty"`
66 | Data []byte `json:"data,omitempty"`
67 | Mask bool `json:"mask,omitempty"`
68 | }
69 |
70 | // Platform defines the target platform.
71 | Platform struct {
72 | OS string `json:"os,omitempty"`
73 | Arch string `json:"arch,omitempty"`
74 | Variant string `json:"variant,omitempty"`
75 | Version string `json:"version,omitempty"`
76 | }
77 |
78 | // Volume that can be mounted by containers.
79 | Volume struct {
80 | EmptyDir *VolumeEmptyDir `json:"temp,omitempty"`
81 | HostPath *VolumeHostPath `json:"host,omitempty"`
82 | }
83 |
84 | // VolumeMount describes a mounting of a Volume
85 | // within a container.
86 | VolumeMount struct {
87 | Name string `json:"name,omitempty"`
88 | Path string `json:"path,omitempty"`
89 | }
90 |
91 | // VolumeEmptyDir mounts a temporary directory from the
92 | // host node's filesystem into the container. This can
93 | // be used as a shared scratch space.
94 | VolumeEmptyDir struct {
95 | ID string `json:"id,omitempty"`
96 | Name string `json:"name,omitempty"`
97 | Medium string `json:"medium,omitempty"`
98 | SizeLimit int64 `json:"size_limit,omitempty"`
99 | Labels map[string]string `json:"labels,omitempty"`
100 | }
101 |
102 | // VolumeHostPath mounts a file or directory from the
103 | // host node's filesystem into your container.
104 | VolumeHostPath struct {
105 | ID string `json:"id,omitempty"`
106 | Name string `json:"name,omitempty"`
107 | Path string `json:"path,omitempty"`
108 | Labels map[string]string `json:"labels,omitempty"`
109 | ReadOnly bool `json:"read_only,omitempty"`
110 | }
111 |
112 | // VolumeDevice describes a mapping of a raw block
113 | // device within a container.
114 | VolumeDevice struct {
115 | Name string `json:"name,omitempty"`
116 | DevicePath string `json:"path,omitempty"`
117 | }
118 |
119 | // Network that is created and attached to containers
120 | Network struct {
121 | ID string `json:"id,omitempty"`
122 | Labels map[string]string `json:"labels,omitempty"`
123 | Options map[string]string `json:"options,omitempty"`
124 | }
125 |
126 | // Auth defines dockerhub authentication credentials.
127 | Auth struct {
128 | Address string `json:"address,omitempty"`
129 | Username string `json:"username,omitempty"`
130 | Password string `json:"password,omitempty"`
131 | }
132 | )
133 |
134 | //
135 | // implements the Spec interface
136 | //
137 |
138 | func (s *Spec) StepLen() int { return len(s.Steps) }
139 | func (s *Spec) StepAt(i int) runtime.Step { return s.Steps[i] }
140 |
141 | //
142 | // implements the Secret interface
143 | //
144 |
145 | func (s *Secret) GetName() string { return s.Name }
146 | func (s *Secret) GetValue() string { return string(s.Data) }
147 | func (s *Secret) IsMasked() bool { return s.Mask }
148 |
149 | //
150 | // implements the Step interface
151 | //
152 |
153 | func (s *Step) GetName() string { return s.Name }
154 | func (s *Step) GetDependencies() []string { return s.DependsOn }
155 | func (s *Step) GetEnviron() map[string]string { return s.Envs }
156 | func (s *Step) SetEnviron(env map[string]string) { s.Envs = env }
157 | func (s *Step) GetErrPolicy() runtime.ErrPolicy { return s.ErrPolicy }
158 | func (s *Step) GetRunPolicy() runtime.RunPolicy { return s.RunPolicy }
159 | func (s *Step) GetSecretAt(i int) runtime.Secret { return s.Secrets[i] }
160 | func (s *Step) GetSecretLen() int { return len(s.Secrets) }
161 | func (s *Step) IsDetached() bool { return s.Detach }
162 | func (s *Step) GetImage() string { return s.Image }
163 | func (s *Step) Clone() runtime.Step {
164 | dst := new(Step)
165 | *dst = *s
166 | dst.Envs = environ.Combine(s.Envs)
167 | return dst
168 | }
169 |
--------------------------------------------------------------------------------
/engine/testdata/network_bridge.yml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drone-runners/drone-runner-docker/9ea626aa421794087058fdea6199257a88c12af8/engine/testdata/network_bridge.yml
--------------------------------------------------------------------------------
/engine/testdata/network_default.yml:
--------------------------------------------------------------------------------
1 | kind: pipeline
2 | type: docker
3 | name: default
4 |
5 | clone:
6 | disable: true
7 |
8 | steps:
9 | - name: test
10 | pull: if-not-exists
11 | image: redis
12 | commands:
13 | - sleep 5
14 | - redis-cli -h redis ping
15 | - redis-cli -h redis set FOO bar
16 | - redis-cli -h redis get FOO
17 |
18 | services:
19 | - name: redis
20 | pull: if-not-exists
21 | image: redis
22 |
--------------------------------------------------------------------------------
/engine/testdata/network_host.yml:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/drone-runners/drone-runner-docker/9ea626aa421794087058fdea6199257a88c12af8/engine/testdata/network_host.yml
--------------------------------------------------------------------------------
/engine/testdata/status_failure.yml:
--------------------------------------------------------------------------------
1 | kind: pipeline
2 | type: docker
3 | name: default
4 |
5 | clone:
6 | disable: true
7 |
8 | steps:
9 | - name: test
10 | pull: if-not-exists
11 | image: alpine
12 | commands:
13 | - echo hello
14 | - echo world
15 | - exit 1
16 |
--------------------------------------------------------------------------------
/engine/testdata/status_success.yml:
--------------------------------------------------------------------------------
1 | kind: pipeline
2 | type: docker
3 | name: default
4 |
5 | clone:
6 | disable: true
7 |
8 | steps:
9 | - name: test
10 | pull: if-not-exists
11 | image: alpine
12 | commands:
13 | - echo hello
14 | - echo world
15 | - exit 0
16 |
--------------------------------------------------------------------------------
/engine/testdata/volume_host.yml:
--------------------------------------------------------------------------------
1 | kind: pipeline
2 | type: docker
3 | name: default
4 |
5 | clone:
6 | disable: true
7 |
8 | steps:
9 | - name: write
10 | pull: if-not-exists
11 | image: alpine
12 | volumes:
13 | - name: test
14 | path: /tmp
15 | commands:
16 | - pwd
17 | - echo "hello" > /tmp/greetings.txt
18 |
19 | - name: read
20 | pull: if-not-exists
21 | image: alpine
22 | volumes:
23 | - name: test
24 | path: /tmp
25 | commands:
26 | - pwd
27 | - cat /tmp/greetings.txt
28 |
29 | volumes:
30 | - name: test
31 | host:
32 | path: /tmp/drone/test
--------------------------------------------------------------------------------
/engine/testdata/volume_mem.yml:
--------------------------------------------------------------------------------
1 | kind: pipeline
2 | type: docker
3 | name: default
4 |
5 | clone:
6 | disable: true
7 |
8 | steps:
9 | - name: write
10 | pull: if-not-exists
11 | image: alpine
12 | volumes:
13 | - name: test
14 | path: /tmp/memory
15 | commands:
16 | - ls -la /tmp
17 | - ls -la /tmp/memory
18 | - touch /tmp/memory/hello.txt
19 | - df -T /tmp/memory
20 |
21 | volumes:
22 | - name: test
23 | temp:
24 | medium: memory
25 |
--------------------------------------------------------------------------------
/engine/testdata/volume_temp.yml:
--------------------------------------------------------------------------------
1 | kind: pipeline
2 | type: docker
3 | name: default
4 |
5 | clone:
6 | disable: true
7 |
8 | steps:
9 | - name: write
10 | pull: if-not-exists
11 | image: alpine
12 | volumes:
13 | - name: test
14 | path: /tmp
15 | commands:
16 | - pwd
17 | - echo "hello" > /tmp/greetings.txt
18 |
19 | - name: read
20 | pull: if-not-exists
21 | image: alpine
22 | volumes:
23 | - name: test
24 | path: /tmp
25 | commands:
26 | - pwd
27 | - cat /tmp/greetings.txt
28 |
29 | volumes:
30 | - name: test
31 | temp: {}
32 |
--------------------------------------------------------------------------------
/engine/testdata/workspace_custom.yml:
--------------------------------------------------------------------------------
1 | kind: pipeline
2 | type: docker
3 | name: default
4 |
5 | clone:
6 | disable: true
7 |
8 | workspace:
9 | path: /drone/custom/path
10 |
11 | steps:
12 | - name: write
13 | pull: if-not-exists
14 | image: alpine
15 | commands:
16 | - pwd
17 | - echo "hello" > greetings.txt
18 |
19 | - name: read
20 | pull: if-not-exists
21 | image: alpine
22 | commands:
23 | - pwd
24 | - cat greetings.txt
25 |
--------------------------------------------------------------------------------
/engine/testdata/workspace_default.yml:
--------------------------------------------------------------------------------
1 | kind: pipeline
2 | type: docker
3 | name: default
4 |
5 | clone:
6 | disable: true
7 |
8 | steps:
9 | - name: write
10 | pull: if-not-exists
11 | image: alpine
12 | commands:
13 | - echo "hello" > greetings.txt
14 | - df -T /drone/src
15 |
16 | - name: read
17 | pull: if-not-exists
18 | image: alpine
19 | commands:
20 | - pwd
21 | - cat greetings.txt
22 |
--------------------------------------------------------------------------------
/engine/testdata/workspace_legacy.yml:
--------------------------------------------------------------------------------
1 | kind: pipeline
2 | type: docker
3 | name: default
4 |
5 | clone:
6 | disable: true
7 |
8 | workspace:
9 | base: /tmp
10 | path: /drone
11 |
12 | steps:
13 | - name: write
14 | pull: if-not-exists
15 | image: alpine
16 | commands:
17 | - pwd
18 | - echo "hello" > /tmp/greetings.txt
19 |
20 | - name: read
21 | pull: if-not-exists
22 | image: alpine
23 | commands:
24 | - pwd
25 | - cat /tmp/greetings.txt
26 |
--------------------------------------------------------------------------------
/engine/util.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Drone.IO Inc. All rights reserved.
2 | // Use of this source code is governed by the Polyform License
3 | // that can be found in the LICENSE file.
4 |
5 | package engine
6 |
--------------------------------------------------------------------------------
/engine/util_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Drone.IO Inc. All rights reserved.
2 | // Use of this source code is governed by the Polyform License
3 | // that can be found in the LICENSE file.
4 |
5 | package engine
6 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/drone-runners/drone-runner-docker
2 |
3 | go 1.16
4 |
5 | replace github.com/docker/docker => github.com/docker/engine v17.12.0-ce-rc1.0.20200309214505-aa6a9891b09c+incompatible
6 |
7 | require (
8 | github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78 // indirect
9 | github.com/Microsoft/go-winio v0.4.11 // indirect
10 | github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc // indirect
11 | github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf // indirect
12 | github.com/buildkite/yaml v2.1.0+incompatible
13 | github.com/containerd/containerd v1.3.4 // indirect
14 | github.com/dchest/uniuri v0.0.0-20160212164326-8902c56451e9
15 | github.com/docker/distribution v2.7.1+incompatible
16 | github.com/docker/docker v0.0.0-00010101000000-000000000000
17 | github.com/docker/go-connections v0.3.0 // indirect
18 | github.com/drone/drone-go v1.7.1
19 | github.com/drone/envsubst v1.0.3
20 | github.com/drone/runner-go v1.12.0
21 | github.com/drone/signal v1.0.0
22 | github.com/ghodss/yaml v1.0.0
23 | github.com/gogo/protobuf v0.0.0-20170307180453-100ba4e88506 // indirect
24 | github.com/google/go-cmp v0.3.0
25 | github.com/gorilla/mux v1.7.4 // indirect
26 | github.com/joho/godotenv v1.3.0
27 | github.com/kelseyhightower/envconfig v1.4.0
28 | github.com/kr/pretty v0.1.0 // indirect
29 | github.com/mattn/go-isatty v0.0.8
30 | github.com/morikuni/aec v1.0.0 // indirect
31 | github.com/opencontainers/go-digest v1.0.0-rc1 // indirect
32 | github.com/opencontainers/image-spec v1.0.1 // indirect
33 | github.com/pkg/errors v0.8.1 // indirect
34 | github.com/sirupsen/logrus v1.4.2
35 | github.com/stretchr/testify v1.3.0 // indirect
36 | golang.org/x/sync v0.0.0-20190423024810-112230192c58
37 | golang.org/x/time v0.0.0-20181108054448-85acf8d2951c // indirect
38 | google.golang.org/grpc v1.29.1 // indirect
39 | gopkg.in/alecthomas/kingpin.v2 v2.2.6
40 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect
41 | gopkg.in/yaml.v2 v2.2.2 // indirect
42 | gotest.tools v2.2.0+incompatible // indirect
43 | )
44 |
--------------------------------------------------------------------------------
/internal/docker/docker.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Drone.IO Inc. All rights reserved.
2 | // Use of this source code is governed by the Polyform License
3 | // that can be found in the LICENSE file.
4 |
5 | package docker
6 |
--------------------------------------------------------------------------------
/internal/docker/errors/errors.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Drone.IO Inc. All rights reserved.
2 | // Use of this source code is governed by the Polyform License
3 | // that can be found in the LICENSE file.
4 |
5 | package errors
6 |
7 | import (
8 | "errors"
9 | "strings"
10 | )
11 |
12 | // TrimExtraInfo is a helper function that trims extra information
13 | // from a Docker error. Specifically, on Windows, this can expose
14 | // environment variables and other sensitive data.
15 | func TrimExtraInfo(err error) error {
16 | if err == nil {
17 | return nil
18 | }
19 | s := err.Error()
20 | i := strings.Index(s, "extra info:")
21 | if i > 0 {
22 | s = s[:i]
23 | s = strings.TrimSpace(s)
24 | s = strings.TrimSuffix(s, "(0x2)")
25 | s = strings.TrimSpace(s)
26 | return errors.New(s)
27 | }
28 | return err
29 | }
30 |
--------------------------------------------------------------------------------
/internal/docker/errors/errors_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Drone.IO Inc. All rights reserved.
2 | // Use of this source code is governed by the Polyform License
3 | // that can be found in the LICENSE file.
4 |
5 | package errors
6 |
7 | import (
8 | "errors"
9 | "testing"
10 | )
11 |
12 | func TestTrimExtraInfo(t *testing.T) {
13 | const (
14 | before = `Error response from daemon: container encountered an error during CreateProcess: failure in a Windows system call: The system cannot find the file specified. (0x2) extra info: { "User":"ContainerUser" }`
15 | after = `Error response from daemon: container encountered an error during CreateProcess: failure in a Windows system call: The system cannot find the file specified.`
16 | )
17 | errBefore := errors.New(before)
18 | errAfter := TrimExtraInfo(errBefore)
19 | if errAfter.Error() != after {
20 | t.Errorf("Expect trimmed image")
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/internal/docker/image/image.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Drone.IO Inc. All rights reserved.
2 | // Use of this source code is governed by the Polyform License
3 | // that can be found in the LICENSE file.
4 |
5 | package image
6 |
7 | import (
8 | "net/url"
9 | "strings"
10 |
11 | "github.com/docker/distribution/reference"
12 | )
13 |
14 | // Trim returns the short image name without tag.
15 | func Trim(name string) string {
16 | ref, err := reference.ParseAnyReference(name)
17 | if err != nil {
18 | return name
19 | }
20 | named, err := reference.ParseNamed(ref.String())
21 | if err != nil {
22 | return name
23 | }
24 | named = reference.TrimNamed(named)
25 | return reference.FamiliarName(named)
26 | }
27 |
28 | // Expand returns the fully qualified image name.
29 | func Expand(name string) string {
30 | ref, err := reference.ParseAnyReference(name)
31 | if err != nil {
32 | return name
33 | }
34 | named, err := reference.ParseNamed(ref.String())
35 | if err != nil {
36 | return name
37 | }
38 | named = reference.TagNameOnly(named)
39 | return named.String()
40 | }
41 |
42 | // Match returns true if the image name matches
43 | // an image in the list. Note the image tag is not used
44 | // in the matching logic.
45 | func Match(from string, to ...string) bool {
46 | from = Trim(from)
47 | for _, match := range to {
48 | if from == Trim(match) {
49 | return true
50 | }
51 | }
52 | return false
53 | }
54 |
55 | // MatchTag returns true if the image name matches
56 | // an image in the list, including the tag.
57 | func MatchTag(a, b string) bool {
58 | return Expand(a) == Expand(b)
59 | }
60 |
61 | // MatchHostname returns true if the image hostname
62 | // matches the specified hostname.
63 | func MatchHostname(image, hostname string) bool {
64 | ref, err := reference.ParseAnyReference(image)
65 | if err != nil {
66 | return false
67 | }
68 | named, err := reference.ParseNamed(ref.String())
69 | if err != nil {
70 | return false
71 | }
72 | if hostname == "index.docker.io" {
73 | hostname = "docker.io"
74 | }
75 | // the auth address could be a fully qualified
76 | // url in which case, we should parse so we can
77 | // extract the domain name.
78 | if strings.HasPrefix(hostname, "http://") ||
79 | strings.HasPrefix(hostname, "https://") {
80 | parsed, err := url.Parse(hostname)
81 | if err == nil {
82 | hostname = parsed.Host
83 | }
84 | }
85 | return reference.Domain(named) == hostname
86 | }
87 |
88 | // IsLatest parses the image and returns true if
89 | // the image uses the :latest tag.
90 | func IsLatest(s string) bool {
91 | return strings.HasSuffix(Expand(s), ":latest")
92 | }
93 |
--------------------------------------------------------------------------------
/internal/docker/image/image_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Drone.IO Inc. All rights reserved.
2 | // Use of this source code is governed by the Polyform License
3 | // that can be found in the LICENSE file.
4 |
5 | package image
6 |
7 | import "testing"
8 |
9 | func Test_trimImage(t *testing.T) {
10 | testdata := []struct {
11 | from string
12 | want string
13 | }{
14 | {
15 | from: "golang",
16 | want: "golang",
17 | },
18 | {
19 | from: "golang:latest",
20 | want: "golang",
21 | },
22 | {
23 | from: "golang:1.0.0",
24 | want: "golang",
25 | },
26 | {
27 | from: "library/golang",
28 | want: "golang",
29 | },
30 | {
31 | from: "library/golang:latest",
32 | want: "golang",
33 | },
34 | {
35 | from: "library/golang:1.0.0",
36 | want: "golang",
37 | },
38 | {
39 | from: "index.docker.io/library/golang:1.0.0",
40 | want: "golang",
41 | },
42 | {
43 | from: "docker.io/library/golang:1.0.0",
44 | want: "golang",
45 | },
46 | {
47 | from: "gcr.io/library/golang:1.0.0",
48 | want: "gcr.io/library/golang",
49 | },
50 | // error cases, return input unmodified
51 | {
52 | from: "foo/bar?baz:boo",
53 | want: "foo/bar?baz:boo",
54 | },
55 | }
56 | for _, test := range testdata {
57 | got, want := Trim(test.from), test.want
58 | if got != want {
59 | t.Errorf("Want image %q trimmed to %q, got %q", test.from, want, got)
60 | }
61 | }
62 | }
63 |
64 | func Test_expandImage(t *testing.T) {
65 | testdata := []struct {
66 | from string
67 | want string
68 | }{
69 | {
70 | from: "golang",
71 | want: "docker.io/library/golang:latest",
72 | },
73 | {
74 | from: "golang:latest",
75 | want: "docker.io/library/golang:latest",
76 | },
77 | {
78 | from: "golang:1.0.0",
79 | want: "docker.io/library/golang:1.0.0",
80 | },
81 | {
82 | from: "library/golang",
83 | want: "docker.io/library/golang:latest",
84 | },
85 | {
86 | from: "library/golang:latest",
87 | want: "docker.io/library/golang:latest",
88 | },
89 | {
90 | from: "library/golang:1.0.0",
91 | want: "docker.io/library/golang:1.0.0",
92 | },
93 | {
94 | from: "index.docker.io/library/golang:1.0.0",
95 | want: "docker.io/library/golang:1.0.0",
96 | },
97 | {
98 | from: "gcr.io/golang",
99 | want: "gcr.io/golang:latest",
100 | },
101 | {
102 | from: "gcr.io/golang:1.0.0",
103 | want: "gcr.io/golang:1.0.0",
104 | },
105 | // error cases, return input unmodified
106 | {
107 | from: "foo/bar?baz:boo",
108 | want: "foo/bar?baz:boo",
109 | },
110 | }
111 | for _, test := range testdata {
112 | got, want := Expand(test.from), test.want
113 | if got != want {
114 | t.Errorf("Want image %q expanded to %q, got %q", test.from, want, got)
115 | }
116 | }
117 | }
118 |
119 | func Test_matchImage(t *testing.T) {
120 | testdata := []struct {
121 | from, to string
122 | want bool
123 | }{
124 | {
125 | from: "golang",
126 | to: "golang",
127 | want: true,
128 | },
129 | {
130 | from: "golang:latest",
131 | to: "golang",
132 | want: true,
133 | },
134 | {
135 | from: "library/golang:latest",
136 | to: "golang",
137 | want: true,
138 | },
139 | {
140 | from: "index.docker.io/library/golang:1.0.0",
141 | to: "golang",
142 | want: true,
143 | },
144 | {
145 | from: "golang",
146 | to: "golang:latest",
147 | want: true,
148 | },
149 | {
150 | from: "library/golang:latest",
151 | to: "library/golang",
152 | want: true,
153 | },
154 | {
155 | from: "gcr.io/golang",
156 | to: "gcr.io/golang",
157 | want: true,
158 | },
159 | {
160 | from: "gcr.io/golang:1.0.0",
161 | to: "gcr.io/golang",
162 | want: true,
163 | },
164 | {
165 | from: "gcr.io/golang:latest",
166 | to: "gcr.io/golang",
167 | want: true,
168 | },
169 | {
170 | from: "gcr.io/golang",
171 | to: "gcr.io/golang:latest",
172 | want: true,
173 | },
174 | {
175 | from: "golang",
176 | to: "library/golang",
177 | want: true,
178 | },
179 | {
180 | from: "golang",
181 | to: "gcr.io/project/golang",
182 | want: false,
183 | },
184 | {
185 | from: "golang",
186 | to: "gcr.io/library/golang",
187 | want: false,
188 | },
189 | {
190 | from: "golang",
191 | to: "gcr.io/golang",
192 | want: false,
193 | },
194 | }
195 | for _, test := range testdata {
196 | got, want := Match(test.from, test.to), test.want
197 | if got != want {
198 | t.Errorf("Want image %q matching %q is %v", test.from, test.to, want)
199 | }
200 | }
201 | }
202 |
203 | func Test_matchHostname(t *testing.T) {
204 | testdata := []struct {
205 | image, hostname string
206 | want bool
207 | }{
208 | {
209 | image: "golang",
210 | hostname: "docker.io",
211 | want: true,
212 | },
213 | {
214 | image: "golang:latest",
215 | hostname: "docker.io",
216 | want: true,
217 | },
218 | {
219 | image: "golang:latest",
220 | hostname: "index.docker.io",
221 | want: true,
222 | },
223 | {
224 | image: "library/golang:latest",
225 | hostname: "docker.io",
226 | want: true,
227 | },
228 | {
229 | image: "docker.io/library/golang:1.0.0",
230 | hostname: "docker.io",
231 | want: true,
232 | },
233 | {
234 | image: "gcr.io/golang",
235 | hostname: "docker.io",
236 | want: false,
237 | },
238 | {
239 | image: "gcr.io/golang:1.0.0",
240 | hostname: "gcr.io",
241 | want: true,
242 | },
243 | {
244 | image: "gcr.io/golang:1.0.0",
245 | hostname: "gcr.io",
246 | want: true,
247 | },
248 | {
249 | image: "012345678910.dkr.ecr.us-east-1.amazonaws.com/foo:latest",
250 | hostname: "012345678910.dkr.ecr.us-east-1.amazonaws.com",
251 | want: true,
252 | },
253 | {
254 | image: "012345678910.dkr.ecr.us-east-1.amazonaws.com/foo:latest",
255 | hostname: "https://012345678910.dkr.ecr.us-east-1.amazonaws.com",
256 | want: true,
257 | },
258 | {
259 | image: "*&^%",
260 | hostname: "1.2.3.4:8000",
261 | want: false,
262 | },
263 | }
264 | for _, test := range testdata {
265 | got, want := MatchHostname(test.image, test.hostname), test.want
266 | if got != want {
267 | t.Errorf("Want image %q matching hostname %q is %v", test.image, test.hostname, want)
268 | }
269 | }
270 | }
271 |
272 | func Test_matchTag(t *testing.T) {
273 | testdata := []struct {
274 | a, b string
275 | want bool
276 | }{
277 | {
278 | a: "golang:1.0",
279 | b: "golang:1.0",
280 | want: true,
281 | },
282 | {
283 | a: "golang",
284 | b: "golang:latest",
285 | want: true,
286 | },
287 | {
288 | a: "docker.io/library/golang",
289 | b: "golang:latest",
290 | want: true,
291 | },
292 | {
293 | a: "golang",
294 | b: "golang:1.0",
295 | want: false,
296 | },
297 | {
298 | a: "golang:1.0",
299 | b: "golang:2.0",
300 | want: false,
301 | },
302 | }
303 | for _, test := range testdata {
304 | got, want := MatchTag(test.a, test.b), test.want
305 | if got != want {
306 | t.Errorf("Want image %q matching image tag %q is %v", test.a, test.b, want)
307 | }
308 | }
309 | }
310 |
311 | func Test_isLatest(t *testing.T) {
312 | testdata := []struct {
313 | name string
314 | want bool
315 | }{
316 | {
317 | name: "golang:1",
318 | want: false,
319 | },
320 | {
321 | name: "golang",
322 | want: true,
323 | },
324 | {
325 | name: "golang:latest",
326 | want: true,
327 | },
328 | {
329 | name: "docker.io/library/golang",
330 | want: true,
331 | },
332 | {
333 | name: "docker.io/library/golang:latest",
334 | want: true,
335 | },
336 | {
337 | name: "docker.io/library/golang:1",
338 | want: false,
339 | },
340 | }
341 | for _, test := range testdata {
342 | got, want := IsLatest(test.name), test.want
343 | if got != want {
344 | t.Errorf("Want image %q isLatest %v", test.name, want)
345 | }
346 | }
347 | }
348 |
--------------------------------------------------------------------------------
/internal/docker/jsonmessage/jsonmessage.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Drone.IO Inc. All rights reserved.
2 | // Use of this source code is governed by the Polyform License
3 | // that can be found in the LICENSE file.
4 |
5 | package jsonmessage
6 |
7 | import (
8 | "encoding/json"
9 | "fmt"
10 | "io"
11 | )
12 |
13 | type jsonError struct {
14 | Code int `json:"code"`
15 | Message string `json:"message"`
16 | }
17 |
18 | func (e *jsonError) Error() string {
19 | return e.Message
20 | }
21 |
22 | type jsonMessage struct {
23 | ID string `json:"id"`
24 | Status string `json:"status"`
25 | Error *jsonError `json:"errorDetail"`
26 | Progress *jsonProgress `json:"progressDetail"`
27 | }
28 |
29 | type jsonProgress struct {
30 | }
31 |
32 | // Copy copies a json message string to the io.Writer.
33 | func Copy(in io.Reader, out io.Writer) error {
34 | dec := json.NewDecoder(in)
35 | for {
36 | var jm jsonMessage
37 | if err := dec.Decode(&jm); err != nil {
38 | if err == io.EOF {
39 | break
40 | }
41 | return err
42 | }
43 |
44 | if jm.Error != nil {
45 | if jm.Error.Code == 401 {
46 | return fmt.Errorf("authentication is required")
47 | }
48 | return jm.Error
49 | }
50 |
51 | if jm.Progress != nil {
52 | continue
53 | }
54 | if jm.ID == "" {
55 | fmt.Fprintf(out, "%s\n", jm.Status)
56 | } else {
57 | fmt.Fprintf(out, "%s: %s\n", jm.ID, jm.Status)
58 | }
59 | }
60 | return nil
61 | }
62 |
--------------------------------------------------------------------------------
/internal/docker/jsonmessage/testdata/alpine.json:
--------------------------------------------------------------------------------
1 | {"status":"Pulling from library/alpine","id":"3.6"}
2 | {"status":"Pulling fs layer","progressDetail":{},"id":"5a3ea8efae5d"}
3 | {"status":"Downloading","progressDetail":{"current":21019,"total":2017774},"progress":"[\u003e ] 21.02kB/2.018MB","id":"5a3ea8efae5d"}
4 | {"status":"Downloading","progressDetail":{"current":1194721,"total":2017774},"progress":"[=============================\u003e ] 1.195MB/2.018MB","id":"5a3ea8efae5d"}
5 | {"status":"Verifying Checksum","progressDetail":{},"id":"5a3ea8efae5d"}
6 | {"status":"Download complete","progressDetail":{},"id":"5a3ea8efae5d"}
7 | {"status":"Extracting","progressDetail":{"current":32768,"total":2017774},"progress":"[\u003e ] 32.77kB/2.018MB","id":"5a3ea8efae5d"}
8 | {"status":"Extracting","progressDetail":{"current":786432,"total":2017774},"progress":"[===================\u003e ] 786.4kB/2.018MB","id":"5a3ea8efae5d"}
9 | {"status":"Extracting","progressDetail":{"current":2017774,"total":2017774},"progress":"[==================================================\u003e] 2.018MB/2.018MB","id":"5a3ea8efae5d"}
10 | {"status":"Pull complete","progressDetail":{},"id":"5a3ea8efae5d"}
11 | {"status":"Digest: sha256:66790a2b79e1ea3e1dabac43990c54aca5d1ddf268d9a5a0285e4167c8b24475"}
12 | {"status":"Status: Downloaded newer image for alpine:3.6"}
--------------------------------------------------------------------------------
/internal/docker/stdcopy/stdcopy.go:
--------------------------------------------------------------------------------
1 | // Copyright 2018 Docker, Inc.
2 | //
3 | // Licensed under the Apache License, Version 2.0 (the "License");
4 | // you may not use this file except in compliance with the License.
5 | // You may obtain a copy of the License at
6 | //
7 | // http://www.apache.org/licenses/LICENSE-2.0
8 | //
9 | // Unless required by applicable law or agreed to in writing, software
10 | // distributed under the License is distributed on an "AS IS" BASIS,
11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | // See the License for the specific language governing permissions and
13 | // limitations under the License.
14 |
15 | package stdcopy
16 |
17 | import (
18 | "bytes"
19 | "encoding/binary"
20 | "errors"
21 | "fmt"
22 | "io"
23 | "sync"
24 | )
25 |
26 | // StdType is the type of standard stream
27 | // a writer can multiplex to.
28 | type StdType byte
29 |
30 | const (
31 | // Stdin represents standard input stream type.
32 | Stdin StdType = iota
33 | // Stdout represents standard output stream type.
34 | Stdout
35 | // Stderr represents standard error steam type.
36 | Stderr
37 |
38 | stdWriterPrefixLen = 8
39 | stdWriterFdIndex = 0
40 | stdWriterSizeIndex = 4
41 |
42 | startingBufLen = 32*1024 + stdWriterPrefixLen + 1
43 | )
44 |
45 | var bufPool = &sync.Pool{New: func() interface{} { return bytes.NewBuffer(nil) }}
46 |
47 | // stdWriter is wrapper of io.Writer with extra customized info.
48 | type stdWriter struct {
49 | io.Writer
50 | prefix byte
51 | }
52 |
53 | // Write sends the buffer to the underneath writer.
54 | // It inserts the prefix header before the buffer,
55 | // so stdcopy.StdCopy knows where to multiplex the output.
56 | // It makes stdWriter to implement io.Writer.
57 | func (w *stdWriter) Write(p []byte) (n int, err error) {
58 | if w == nil || w.Writer == nil {
59 | return 0, errors.New("Writer not instantiated")
60 | }
61 | if p == nil {
62 | return 0, nil
63 | }
64 |
65 | header := [stdWriterPrefixLen]byte{stdWriterFdIndex: w.prefix}
66 | binary.BigEndian.PutUint32(header[stdWriterSizeIndex:], uint32(len(p)))
67 | buf := bufPool.Get().(*bytes.Buffer)
68 | buf.Write(header[:])
69 | buf.Write(p)
70 |
71 | n, err = w.Writer.Write(buf.Bytes())
72 | n -= stdWriterPrefixLen
73 | if n < 0 {
74 | n = 0
75 | }
76 |
77 | buf.Reset()
78 | bufPool.Put(buf)
79 | return
80 | }
81 |
82 | // NewStdWriter instantiates a new Writer.
83 | // Everything written to it will be encapsulated using a custom format,
84 | // and written to the underlying `w` stream.
85 | // This allows multiple write streams (e.g. stdout and stderr) to be muxed into a single connection.
86 | // `t` indicates the id of the stream to encapsulate.
87 | // It can be stdcopy.Stdin, stdcopy.Stdout, stdcopy.Stderr.
88 | func NewStdWriter(w io.Writer, t StdType) io.Writer {
89 | return &stdWriter{
90 | Writer: w,
91 | prefix: byte(t),
92 | }
93 | }
94 |
95 | // StdCopy is a modified version of io.Copy.
96 | //
97 | // StdCopy will demultiplex `src`, assuming that it contains two streams,
98 | // previously multiplexed together using a StdWriter instance.
99 | // As it reads from `src`, StdCopy will write to `dstout` and `dsterr`.
100 | //
101 | // StdCopy will read until it hits EOF on `src`. It will then return a nil error.
102 | // In other words: if `err` is non nil, it indicates a real underlying error.
103 | //
104 | // `written` will hold the total number of bytes written to `dstout` and `dsterr`.
105 | func StdCopy(dstout, dsterr io.Writer, src io.Reader) (written int64, err error) {
106 | var (
107 | buf = make([]byte, startingBufLen)
108 | bufLen = len(buf)
109 | nr, nw int
110 | er, ew error
111 | out io.Writer
112 | frameSize int
113 | )
114 |
115 | for {
116 | // Make sure we have at least a full header
117 | for nr < stdWriterPrefixLen {
118 | var nr2 int
119 | nr2, er = src.Read(buf[nr:])
120 | nr += nr2
121 | if er == io.EOF {
122 | if nr < stdWriterPrefixLen {
123 | return written, nil
124 | }
125 | break
126 | }
127 | if er != nil {
128 | return 0, er
129 | }
130 | }
131 |
132 | // Check the first byte to know where to write
133 | switch StdType(buf[stdWriterFdIndex]) {
134 | case Stdin:
135 | fallthrough
136 | case Stdout:
137 | // Write on stdout
138 | out = dstout
139 | case Stderr:
140 | // Write on stderr
141 | out = dsterr
142 | default:
143 | return 0, fmt.Errorf("Unrecognized input header: %d", buf[stdWriterFdIndex])
144 | }
145 |
146 | // Retrieve the size of the frame
147 | frameSize = int(binary.BigEndian.Uint32(buf[stdWriterSizeIndex : stdWriterSizeIndex+4]))
148 |
149 | // Check if the buffer is big enough to read the frame.
150 | // Extend it if necessary.
151 | if frameSize+stdWriterPrefixLen > bufLen {
152 | buf = append(buf, make([]byte, frameSize+stdWriterPrefixLen-bufLen+1)...)
153 | bufLen = len(buf)
154 | }
155 |
156 | // While the amount of bytes read is less than the size of the frame + header, we keep reading
157 | for nr < frameSize+stdWriterPrefixLen {
158 | var nr2 int
159 | nr2, er = src.Read(buf[nr:])
160 | nr += nr2
161 | if er == io.EOF {
162 | if nr < frameSize+stdWriterPrefixLen {
163 | return written, nil
164 | }
165 | break
166 | }
167 | if er != nil {
168 | return 0, er
169 | }
170 | }
171 |
172 | // Write the retrieved frame (without header)
173 | nw, ew = out.Write(buf[stdWriterPrefixLen : frameSize+stdWriterPrefixLen])
174 | if ew != nil {
175 | return 0, ew
176 | }
177 | // If the frame has not been fully written: error
178 | if nw != frameSize {
179 | return 0, io.ErrShortWrite
180 | }
181 | written += int64(nw)
182 |
183 | // Move the rest of the buffer to the beginning
184 | copy(buf, buf[frameSize+stdWriterPrefixLen:])
185 | // Move the index
186 | nr -= frameSize + stdWriterPrefixLen
187 | }
188 | }
189 |
--------------------------------------------------------------------------------
/internal/encoder/encoder.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Drone.IO Inc. All rights reserved.
2 | // Use of this source code is governed by the Polyform License
3 | // that can be found in the LICENSE file.
4 |
5 | package encoder
6 |
7 | import (
8 | "encoding/base64"
9 | "strconv"
10 | "strings"
11 |
12 | "github.com/buildkite/yaml"
13 | json "github.com/ghodss/yaml"
14 | )
15 |
16 | // Encode encodes an interface value as a string. This function
17 | // assumes all types were unmarshaled by the yaml.v2 library.
18 | // The yaml.v2 package only supports a subset of primitive types.
19 | func Encode(v interface{}) string {
20 | switch v := v.(type) {
21 | case string:
22 | return v
23 | case bool:
24 | return strconv.FormatBool(v)
25 | case int:
26 | return strconv.Itoa(v)
27 | case float64:
28 | return strconv.FormatFloat(v, 'g', -1, 64)
29 | case []byte:
30 | return base64.StdEncoding.EncodeToString(v)
31 | case []interface{}:
32 | return encodeSlice(v)
33 | default:
34 | return encodeMap(v)
35 | }
36 | }
37 |
38 | // helper function encodes a parameter in map format.
39 | func encodeMap(v interface{}) string {
40 | yml, _ := yaml.Marshal(v)
41 | out, _ := json.YAMLToJSON(yml)
42 | return string(out)
43 | }
44 |
45 | // helper function encodes a parameter in slice format.
46 | func encodeSlice(v interface{}) string {
47 | out, _ := yaml.Marshal(v)
48 |
49 | in := []string{}
50 | err := yaml.Unmarshal(out, &in)
51 | if err == nil {
52 | return strings.Join(in, ",")
53 | }
54 | out, _ = json.YAMLToJSON(out)
55 | return string(out)
56 | }
57 |
--------------------------------------------------------------------------------
/internal/encoder/encoder_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Drone.IO Inc. All rights reserved.
2 | // Use of this source code is governed by the Polyform License
3 | // that can be found in the LICENSE file.
4 |
5 | package encoder
6 |
7 | import "testing"
8 |
9 | func TestEncode(t *testing.T) {
10 | testdatum := []struct {
11 | data interface{}
12 | text string
13 | }{
14 | {
15 | data: "foo",
16 | text: "foo",
17 | },
18 | {
19 | data: true,
20 | text: "true",
21 | },
22 | {
23 | data: 42,
24 | text: "42",
25 | },
26 | {
27 | data: float64(42.424242),
28 | text: "42.424242",
29 | },
30 | {
31 | data: []interface{}{"foo", "bar", "baz"},
32 | text: "foo,bar,baz",
33 | },
34 | {
35 | data: []interface{}{1, 1, 2, 3, 5, 8},
36 | text: "1,1,2,3,5,8",
37 | },
38 | {
39 | data: []byte("foo"),
40 | text: "Zm9v",
41 | },
42 | {
43 | data: []interface{}{
44 | struct {
45 | Name string `json:"name"`
46 | }{
47 | Name: "john",
48 | },
49 | },
50 | text: `[{"name":"john"}]`,
51 | },
52 | {
53 | data: map[interface{}]interface{}{"foo": "bar"},
54 | text: `{"foo":"bar"}`,
55 | },
56 | }
57 |
58 | for _, testdata := range testdatum {
59 | if got, want := Encode(testdata.data), testdata.text; got != want {
60 | t.Errorf("Want interface{} encoded to %q, got %q", want, got)
61 | }
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/internal/internal.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Drone.IO Inc. All rights reserved.
2 | // Use of this source code is governed by the Polyform License
3 | // that can be found in the LICENSE file.
4 |
5 | // Package internal contains runner internals.
6 | package internal
7 |
--------------------------------------------------------------------------------
/internal/match/match.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Drone.IO Inc. All rights reserved.
2 | // Use of this source code is governed by the Polyform License
3 | // that can be found in the LICENSE file.
4 |
5 | package match
6 |
7 | import (
8 | "path/filepath"
9 |
10 | "github.com/drone/drone-go/drone"
11 | )
12 |
13 | // NOTE most runners do not require match capabilities. This is
14 | // provided as a defense in depth mechanism given the sensitive
15 | // nature of this runner executing code directly on the host.
16 | // The matching function is a last line of defence to prevent
17 | // unauthorized code from running on the host machine.
18 |
19 | // Func returns a new match function that returns true if the
20 | // repository and build do not match the allowd repository names
21 | // and build events.
22 | func Func(repos, events []string, trusted bool) func(*drone.Repo, *drone.Build) bool {
23 | return func(repo *drone.Repo, build *drone.Build) bool {
24 | // if trusted mode is enabled, only match repositories
25 | // that are trusted.
26 | if trusted && repo.Trusted == false {
27 | return false
28 | }
29 | if match(repo.Slug, repos) == false {
30 | return false
31 | }
32 | if match(build.Event, events) == false {
33 | return false
34 | }
35 | return true
36 | }
37 | }
38 |
39 | func match(s string, patterns []string) bool {
40 | // if no matching patterns are defined the string
41 | // is always considered a match.
42 | if len(patterns) == 0 {
43 | return true
44 | }
45 | for _, pattern := range patterns {
46 | if match, _ := filepath.Match(pattern, s); match {
47 | return true
48 | }
49 | }
50 | return false
51 | }
52 |
--------------------------------------------------------------------------------
/internal/match/match_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Drone.IO Inc. All rights reserved.
2 | // Use of this source code is governed by the Polyform License
3 | // that can be found in the LICENSE file.
4 |
5 | package match
6 |
7 | import (
8 | "testing"
9 |
10 | "github.com/drone/drone-go/drone"
11 | )
12 |
13 | func TestFunc(t *testing.T) {
14 | tests := []struct {
15 | repo string
16 | event string
17 | trusted bool
18 | match bool
19 | matcher func(*drone.Repo, *drone.Build) bool
20 | }{
21 | //
22 | // Expect match true
23 | //
24 |
25 | // repository, event and trusted flag matching
26 | {
27 | repo: "octocat/hello-world",
28 | event: "push",
29 | trusted: true,
30 | match: true,
31 | matcher: Func([]string{"spaceghost/*", "octocat/*"}, []string{"push"}, true),
32 | },
33 | // repository matching
34 | {
35 | repo: "octocat/hello-world",
36 | event: "pull_request",
37 | trusted: false,
38 | match: true,
39 | matcher: Func([]string{"spaceghost/*", "octocat/*"}, []string{}, false),
40 | },
41 | // repository matching, skipping an org
42 | {
43 | repo: "octocat/hello-world",
44 | event: "pull_request",
45 | trusted: false,
46 | match: true,
47 | matcher: Func([]string{"!spaceghost/*", "octocat/*"}, []string{}, false),
48 | },
49 | // event matching
50 | {
51 | repo: "octocat/hello-world",
52 | event: "pull_request",
53 | trusted: false,
54 | match: true,
55 | matcher: Func([]string{}, []string{"pull_request"}, false),
56 | },
57 | // trusted flag matching
58 | {
59 | repo: "octocat/hello-world",
60 | event: "pull_request",
61 | trusted: true,
62 | match: true,
63 | matcher: Func([]string{}, []string{}, true),
64 | },
65 |
66 | //
67 | // Expect match false
68 | //
69 |
70 | // repository matching
71 | {
72 | repo: "spaceghost/hello-world",
73 | event: "pull_request",
74 | trusted: false,
75 | match: false,
76 | matcher: Func([]string{"octocat/*"}, []string{}, false),
77 | },
78 | // repository matching, skip all repos in the org
79 | {
80 | repo: "spaceghost/hello-world",
81 | event: "pull_request",
82 | trusted: false,
83 | match: false,
84 | matcher: Func([]string{"!spaceghost/*"}, []string{}, false),
85 | },
86 | // repository matching, skip a concrete repo
87 | {
88 | repo: "spaceghost/hello-world",
89 | event: "pull_request",
90 | trusted: false,
91 | match: false,
92 | matcher: Func([]string{"!spaceghost/hello-world"}, []string{}, false),
93 | },
94 | // event matching
95 | {
96 | repo: "octocat/hello-world",
97 | event: "pull_request",
98 | trusted: false,
99 | match: false,
100 | matcher: Func([]string{}, []string{"push"}, false),
101 | },
102 | // trusted flag matching
103 | {
104 | repo: "octocat/hello-world",
105 | event: "pull_request",
106 | trusted: false,
107 | match: false,
108 | matcher: Func([]string{}, []string{}, true),
109 | },
110 | // does not match repository
111 | {
112 | repo: "foo/hello-world",
113 | event: "push",
114 | trusted: true,
115 | match: false,
116 | matcher: Func([]string{"spaceghost/*", "octocat/*"}, []string{"push"}, true),
117 | },
118 | // does not match event
119 | {
120 | repo: "octocat/hello-world",
121 | event: "pull_request",
122 | trusted: true,
123 | match: false,
124 | matcher: Func([]string{"spaceghost/*", "octocat/*"}, []string{"push"}, true),
125 | },
126 | // does not match trusted flag
127 | {
128 | repo: "octocat/hello-world",
129 | event: "push",
130 | trusted: false,
131 | match: false,
132 | matcher: Func([]string{"spaceghost/*", "octocat/*"}, []string{"push"}, true),
133 | },
134 | }
135 |
136 | for i, test := range tests {
137 | repo := &drone.Repo{
138 | Slug: test.repo,
139 | Trusted: test.trusted,
140 | }
141 | build := &drone.Build{
142 | Event: test.event,
143 | }
144 | match := test.matcher(repo, build)
145 | if match != test.match {
146 | t.Errorf("Expect match %v at index %d", test.match, i)
147 | }
148 | }
149 | }
150 |
--------------------------------------------------------------------------------
/licenses/Polyform-Free-Trial.md:
--------------------------------------------------------------------------------
1 | # Polyform Free Trial License 1.0.0
2 |
3 |
4 |
5 | ## Acceptance
6 |
7 | In order to get any license under these terms, you must agree
8 | to them as both strict obligations and conditions to all
9 | your licenses.
10 |
11 | ## Copyright License
12 |
13 | The licensor grants you a copyright license for the software
14 | to do everything you might do with the software that would
15 | otherwise infringe the licensor's copyright in it for any
16 | permitted purpose. However, you may only make changes or
17 | new works based on the software according to [Changes and New
18 | Works License](#changes-and-new-works-license), and you may
19 | not distribute copies of the software.
20 |
21 | ## Changes and New Works License
22 |
23 | The licensor grants you an additional copyright license to
24 | make changes and new works based on the software for any
25 | permitted purpose.
26 |
27 | ## Patent License
28 |
29 | The licensor grants you a patent license for the software that
30 | covers patent claims the licensor can license, or becomes able
31 | to license, that you would infringe by using the software.
32 |
33 | ## Fair Use
34 |
35 | You may have "fair use" rights for the software under the
36 | law. These terms do not limit them.
37 |
38 | ## Free Trial
39 |
40 | Use to evaluate whether the software suits a particular
41 | application for less than 32 consecutive calendar days, on
42 | behalf of you or your company, is use for a permitted purpose.
43 |
44 | ## No Other Rights
45 |
46 | These terms do not allow you to sublicense or transfer any of
47 | your licenses to anyone else, or prevent the licensor from
48 | granting licenses to anyone else. These terms do not imply
49 | any other licenses.
50 |
51 | ## Patent Defense
52 |
53 | If you make any written claim that the software infringes or
54 | contributes to infringement of any patent, your patent license
55 | for the software granted under these terms ends immediately. If
56 | your company makes such a claim, your patent license ends
57 | immediately for work on behalf of your company.
58 |
59 | ## Violations
60 |
61 | If you violate any of these terms, or do anything with the
62 | software not covered by your licenses, all your licenses
63 | end immediately.
64 |
65 | ## No Liability
66 |
67 | ***As far as the law allows, the software comes as is, without
68 | any warranty or condition, and the licensor will not be liable
69 | to you for any damages arising out of these terms or the use
70 | or nature of the software, under any kind of legal claim.***
71 |
72 | ## Definitions
73 |
74 | The **licensor** is the individual or entity offering these
75 | terms, and the **software** is the software the licensor makes
76 | available under these terms.
77 |
78 | **You** refers to the individual or entity agreeing to these
79 | terms.
80 |
81 | **Your company** is any legal entity, sole proprietorship,
82 | or other kind of organization that you work for, plus all
83 | organizations that have control over, are under the control of,
84 | or are under common control with that organization. **Control**
85 | means ownership of substantially all the assets of an entity,
86 | or the power to direct its management and policies by vote,
87 | contract, or otherwise. Control can be direct or indirect.
88 |
89 | **Your licenses** are all the licenses granted to you for the
90 | software under these terms.
91 |
92 | **Use** means anything you do with the software requiring one
93 | of your licenses.
--------------------------------------------------------------------------------
/licenses/Polyform-Small-Business.md:
--------------------------------------------------------------------------------
1 | # Polyform Small Business License 1.0.0
2 |
3 |
4 |
5 | ## Acceptance
6 |
7 | In order to get any license under these terms, you must agree
8 | to them as both strict obligations and conditions to all
9 | your licenses.
10 |
11 | ## Copyright License
12 |
13 | The licensor grants you a copyright license for the
14 | software to do everything you might do with the software
15 | that would otherwise infringe the licensor's copyright
16 | in it for any permitted purpose. However, you may
17 | only distribute the software according to [Distribution
18 | License](#distribution-license) and make changes or new works
19 | based on the software according to [Changes and New Works
20 | License](#changes-and-new-works-license).
21 |
22 | ## Distribution License
23 |
24 | The licensor grants you an additional copyright license
25 | to distribute copies of the software. Your license
26 | to distribute covers distributing the software with
27 | changes and new works permitted by [Changes and New Works
28 | License](#changes-and-new-works-license).
29 |
30 | ## Notices
31 |
32 | You must ensure that anyone who gets a copy of any part of
33 | the software from you also gets a copy of these terms or the
34 | URL for them above, as well as copies of any plain-text lines
35 | beginning with `Required Notice:` that the licensor provided
36 | with the software. For example:
37 |
38 | > Required Notice: Copyright Yoyodyne, Inc. (http://example.com)
39 |
40 | ## Changes and New Works License
41 |
42 | The licensor grants you an additional copyright license to
43 | make changes and new works based on the software for any
44 | permitted purpose.
45 |
46 | ## Patent License
47 |
48 | The licensor grants you a patent license for the software that
49 | covers patent claims the licensor can license, or becomes able
50 | to license, that you would infringe by using the software.
51 |
52 | ## Fair Use
53 |
54 | You may have "fair use" rights for the software under the
55 | law. These terms do not limit them.
56 |
57 | ## Small Business
58 |
59 | Use of the software for the benefit of your company is use for
60 | a permitted purpose if your company has fewer than 100 total
61 | individuals working as employees and independent contractors,
62 | and less than 1,000,000 USD (2019) total revenue in the prior
63 | tax year. Adjust this revenue threshold for inflation according
64 | to the United States Bureau of Labor Statistics' consumer price
65 | index for all urban consumers, U.S. city average, for all items,
66 | not seasonally adjusted, with 1982–1984=100 reference base.
67 |
68 | ## No Other Rights
69 |
70 | These terms do not allow you to sublicense or transfer any of
71 | your licenses to anyone else, or prevent the licensor from
72 | granting licenses to anyone else. These terms do not imply
73 | any other licenses.
74 |
75 | ## Patent Defense
76 |
77 | If you make any written claim that the software infringes or
78 | contributes to infringement of any patent, your patent license
79 | for the software granted under these terms ends immediately. If
80 | your company makes such a claim, your patent license ends
81 | immediately for work on behalf of your company.
82 |
83 | ## Violations
84 |
85 | The first time you are notified in writing that you have
86 | violated any of these terms, or done anything with the software
87 | not covered by your licenses, your licenses can nonetheless
88 | continue if you come into full compliance with these terms,
89 | and take practical steps to correct past violations, within
90 | 32 days of receiving notice. Otherwise, all your licenses
91 | end immediately.
92 |
93 | ## No Liability
94 |
95 | ***As far as the law allows, the software comes as is, without
96 | any warranty or condition, and the licensor will not be liable
97 | to you for any damages arising out of these terms or the use
98 | or nature of the software, under any kind of legal claim.***
99 |
100 | ## Definitions
101 |
102 | The **licensor** is the individual or entity offering these
103 | terms, and the **software** is the software the licensor makes
104 | available under these terms.
105 |
106 | **You** refers to the individual or entity agreeing to these
107 | terms.
108 |
109 | **Your company** is any legal entity, sole proprietorship,
110 | or other kind of organization that you work for, plus all
111 | organizations that have control over, are under the control of,
112 | or are under common control with that organization. **Control**
113 | means ownership of substantially all the assets of an entity,
114 | or the power to direct its management and policies by vote,
115 | contract, or otherwise. Control can be direct or indirect.
116 |
117 | **Your licenses** are all the licenses granted to you for the
118 | software under these terms.
119 |
120 | **Use** means anything you do with the software requiring one
121 | of your licenses.
122 |
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Drone.IO Inc. All rights reserved.
2 | // Use of this source code is governed by the Polyform License
3 | // that can be found in the LICENSE file.
4 |
5 | package main
6 |
7 | import (
8 | "github.com/drone-runners/drone-runner-docker/command"
9 | _ "github.com/joho/godotenv/autoload"
10 | )
11 |
12 | func main() {
13 | command.Command()
14 | }
15 |
--------------------------------------------------------------------------------
/scripts/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # disable go modules
4 | export GOPATH=""
5 |
6 | # disable cgo
7 | export CGO_ENABLED=0
8 |
9 | set -e
10 | set -x
11 |
12 | # linux
13 | GOOS=linux GOARCH=amd64 go build -o release/linux/amd64/drone-runner-docker
14 | GOOS=linux GOARCH=arm64 go build -o release/linux/arm64/drone-runner-docker
15 | GOOS=linux GOARCH=arm go build -o release/linux/arm/drone-runner-docker
16 | GOOS=linux GOARCH=ppc64le go build -o release/linux/ppc64le/drone-runner-docker
17 |
18 | # windows
19 | GOOS=windows go build -o release/windows/amd64/drone-runner-docker.exe
20 |
--------------------------------------------------------------------------------