├── .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 | --------------------------------------------------------------------------------