├── .drone.yml ├── .github ├── issue_template.md ├── pull_request_template.md └── settings.yml ├── .gitignore ├── .harness ├── eventPR.yaml ├── eventPush.yaml ├── eventTag.yaml └── harness.yaml ├── LICENSE ├── README.md ├── docker ├── Dockerfile.linux.amd64 ├── Dockerfile.linux.arm64 ├── Dockerfile.windows.1809 ├── Dockerfile.windows.ltsc2022 └── manifest.tmpl ├── git-hooks ├── .gitleaksignore ├── README.md ├── hooks │ ├── git-leaks-pre-commit.sh │ ├── git-leaks.sh │ ├── pre-commit │ └── pre-push └── install.sh ├── go.mod ├── go.sum ├── main.go ├── plugin.go ├── plugin.yml ├── plugin_unix_test.go ├── plugin_windows_test.go ├── renovate.json └── stringmap.go /.drone.yml: -------------------------------------------------------------------------------- 1 | kind: pipeline 2 | type: vm 3 | name: testing 4 | platform: 5 | os: linux 6 | arch: amd64 7 | pool: 8 | use: ubuntu 9 | 10 | steps: 11 | - name: lint 12 | image: golang:1.22.4 13 | pull: always 14 | commands: 15 | - go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.51.2 16 | - golangci-lint version 17 | - golangci-lint run --timeout=300s 18 | volumes: 19 | - name: gopath 20 | path: "/go" 21 | - name: test 22 | image: golang:1.22.4 23 | commands: 24 | - go test -cover ./... 25 | volumes: 26 | - name: gopath 27 | path: "/go" 28 | volumes: 29 | - name: gopath 30 | temp: {} 31 | trigger: 32 | ref: 33 | - refs/heads/master 34 | - refs/tags/** 35 | - refs/pull/** 36 | 37 | --- 38 | kind: pipeline 39 | type: vm 40 | name: linux-amd64 41 | platform: 42 | os: linux 43 | arch: amd64 44 | pool: 45 | use: ubuntu 46 | 47 | steps: 48 | - name: environment 49 | image: golang:1.22.4 50 | pull: always 51 | environment: 52 | CGO_ENABLED: "0" 53 | commands: 54 | - go version 55 | - go env 56 | - name: build 57 | image: golang:1.22.4 58 | environment: 59 | CGO_ENABLED: "0" 60 | commands: 61 | - go build -v -ldflags "-X main.version=" -a -tags netgo -o release/linux/amd64/drone-s3 . 62 | - name: docker 63 | image: plugins/docker 64 | settings: 65 | dockerfile: docker/Dockerfile.linux.amd64 66 | repo: plugins/s3 67 | username: 68 | from_secret: docker_username 69 | password: 70 | from_secret: docker_password 71 | auto_tag: true 72 | auto_tag_suffix: linux-amd64 73 | depends_on: 74 | - testing 75 | trigger: 76 | ref: 77 | - refs/heads/master 78 | - refs/tags/** 79 | - refs/pull/** 80 | 81 | --- 82 | kind: pipeline 83 | type: vm 84 | name: linux-arm64 85 | platform: 86 | os: linux 87 | arch: arm64 88 | pool: 89 | use: ubuntu_arm64 90 | 91 | steps: 92 | - name: environment 93 | image: golang:1.22.4 94 | pull: always 95 | environment: 96 | CGO_ENABLED: "0" 97 | commands: 98 | - go version 99 | - go env 100 | - name: build 101 | image: golang:1.22.4 102 | environment: 103 | CGO_ENABLED: "0" 104 | commands: 105 | - go build -v -ldflags "-X main.version=" -a -tags netgo -o release/linux/arm64/drone-s3 . 106 | - name: docker 107 | image: plugins/docker 108 | settings: 109 | dockerfile: docker/Dockerfile.linux.arm64 110 | repo: plugins/s3 111 | username: 112 | from_secret: docker_username 113 | password: 114 | from_secret: docker_password 115 | auto_tag: true 116 | auto_tag_suffix: linux-arm64 117 | depends_on: 118 | - testing 119 | trigger: 120 | ref: 121 | - refs/heads/master 122 | - refs/tags/** 123 | - refs/pull/** 124 | 125 | --- 126 | kind: pipeline 127 | type: vm 128 | name: windows-1809 129 | platform: 130 | os: windows 131 | arch: amd64 132 | pool: 133 | use: windows 134 | 135 | steps: 136 | - name: environment 137 | image: golang:1.22.4 138 | pull: always 139 | environment: 140 | CGO_ENABLED: "0" 141 | commands: 142 | - go version 143 | - go env 144 | - name: build 145 | image: golang:1.22.4 146 | environment: 147 | CGO_ENABLED: "0" 148 | commands: 149 | - go build -v -ldflags "-X main.version=" -a -tags netgo -o release/windows/amd64/drone-s3.exe . 150 | - name: docker 151 | image: plugins/docker 152 | settings: 153 | dockerfile: docker/Dockerfile.windows.1809 154 | repo: plugins/s3 155 | username: 156 | from_secret: docker_username 157 | password: 158 | from_secret: docker_password 159 | auto_tag: true 160 | auto_tag_suffix: windows-1809-amd64 161 | daemon_off: true 162 | purge: false 163 | when: 164 | ref: 165 | - refs/heads/master 166 | - refs/tags/** 167 | depends_on: 168 | - testing 169 | trigger: 170 | ref: 171 | - refs/heads/master 172 | - refs/tags/** 173 | - refs/pull/** 174 | 175 | --- 176 | kind: pipeline 177 | type: vm 178 | name: windows-ltsc2022 179 | platform: 180 | os: windows 181 | arch: amd64 182 | pool: 183 | use: windows-2022 184 | 185 | steps: 186 | - name: environment 187 | image: golang:1.22.4 188 | pull: always 189 | environment: 190 | CGO_ENABLED: "0" 191 | commands: 192 | - go version 193 | - go env 194 | - name: build 195 | image: golang:1.22.4 196 | environment: 197 | CGO_ENABLED: "0" 198 | commands: 199 | - go build -v -ldflags "-X main.version=" -a -tags netgo -o release/windows/amd64/drone-s3.exe . 200 | - name: docker 201 | image: plugins/docker 202 | settings: 203 | dockerfile: docker/Dockerfile.windows.ltsc2022 204 | repo: plugins/s3 205 | username: 206 | from_secret: docker_username 207 | password: 208 | from_secret: docker_password 209 | auto_tag: true 210 | auto_tag_suffix: windows-ltsc2022-amd64 211 | daemon_off: true 212 | purge: false 213 | when: 214 | ref: 215 | - refs/heads/master 216 | - refs/tags/** 217 | depends_on: 218 | - testing 219 | trigger: 220 | ref: 221 | - refs/heads/master 222 | - refs/tags/** 223 | - refs/pull/** 224 | 225 | --- 226 | kind: pipeline 227 | type: vm 228 | name: manifest 229 | platform: 230 | os: linux 231 | arch: amd64 232 | pool: 233 | use: ubuntu 234 | 235 | steps: 236 | - name: manifest 237 | image: plugins/manifest 238 | settings: 239 | auto_tag: "true" 240 | username: 241 | from_secret: docker_username 242 | password: 243 | from_secret: docker_password 244 | spec: docker/manifest.tmpl 245 | ignore_missing: true 246 | depends_on: 247 | - linux-amd64 248 | - linux-arm64 249 | - windows-1809 250 | - windows-ltsc2022 251 | trigger: 252 | ref: 253 | - refs/heads/master 254 | - refs/tags/** 255 | 256 | --- 257 | 258 | kind: pipeline 259 | type: vm 260 | 261 | pool: 262 | use: ubuntu 263 | 264 | steps: 265 | - name: build 266 | pull: always 267 | image: golang:1.22.4 268 | commands: 269 | - GOOS=linux GOARCH=amd64 go build -ldflags "-s -w" -a -tags netgo -o release/drone-s3-linux-amd64 270 | - GOOS=linux GOARCH=arm64 go build -ldflags "-s -w" -a -tags netgo -o release/drone-s3-linux-arm64 271 | - GOOS=darwin GOARCH=amd64 go build -ldflags "-s -w" -a -tags netgo -o release/drone-s3-darwin-amd64 272 | - GOOS=darwin GOARCH=arm64 go build -ldflags "-s -w" -a -tags netgo -o release/drone-s3-darwin-arm64 273 | - GOOS=windows GOARCH=amd64 go build -ldflags "-s -w" -a -tags netgo -o release/drone-s3-windows-amd64 274 | 275 | environment: 276 | CGO_ENABLED: 0 277 | GO111MODULE: on 278 | 279 | - name: zstd-compress 280 | commands: 281 | - sudo apt-get update -y 282 | - sudo apt-get install -y zstd 283 | - zstd release/drone-s3-linux-amd64 284 | - zstd release/drone-s3-linux-arm64 285 | - zstd release/drone-s3-darwin-arm64 286 | - zstd release/drone-s3-darwin-amd64 287 | - zstd release/drone-s3-windows-amd64 288 | 289 | - name: release 290 | image: plugins/github-release 291 | settings: 292 | files: 293 | - release/drone-s3-linux-amd64.zst 294 | - release/drone-s3-linux-arm64.zst 295 | - release/drone-s3-darwin-arm64.zst 296 | - release/drone-s3-darwin-amd64.zst 297 | - release/drone-s3-windows-amd64.zst 298 | api_key: 299 | from_secret: cireporunner_github_token 300 | when: 301 | event: 302 | - tag 303 | depends_on: 304 | - linux-amd64 305 | - linux-arm64 306 | - windows-1809 307 | - windows-ltsc2022 308 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | 10 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drone-plugins/drone-s3/6655f98917ccd146743cca30c7bd2e2249b1c99e/.github/pull_request_template.md -------------------------------------------------------------------------------- /.github/settings.yml: -------------------------------------------------------------------------------- 1 | repository: 2 | name: drone-s3 3 | description: Drone plugin for publishing artifacts to Amazon S3 4 | homepage: http://plugins.drone.io/drone-plugins/drone-s3 5 | topics: drone, drone-plugin 6 | 7 | private: false 8 | has_issues: true 9 | has_wiki: false 10 | has_downloads: false 11 | 12 | default_branch: master 13 | 14 | allow_squash_merge: true 15 | allow_merge_commit: true 16 | allow_rebase_merge: true 17 | 18 | labels: 19 | - name: bug 20 | color: d73a4a 21 | description: Something isn't working 22 | - name: duplicate 23 | color: cfd3d7 24 | description: This issue or pull request already exists 25 | - name: enhancement 26 | color: a2eeef 27 | description: New feature or request 28 | - name: good first issue 29 | color: 7057ff 30 | description: Good for newcomers 31 | - name: help wanted 32 | color: 008672 33 | description: Extra attention is needed 34 | - name: invalid 35 | color: e4e669 36 | description: This doesn't seem right 37 | - name: question 38 | color: d876e3 39 | description: Further information is requested 40 | - name: renovate 41 | color: e99695 42 | description: Automated action from Renovate 43 | - name: wontfix 44 | color: ffffff 45 | description: This will not be worked on 46 | 47 | teams: 48 | - name: Admins 49 | permission: admin 50 | - name: Captain 51 | permission: admin 52 | - name: Maintainers 53 | permission: push 54 | 55 | branches: 56 | - name: master 57 | protection: 58 | required_pull_request_reviews: 59 | required_approving_review_count: 1 60 | dismiss_stale_reviews: false 61 | require_code_owner_reviews: false 62 | dismissal_restrictions: 63 | teams: 64 | - Admins 65 | - Captain 66 | required_status_checks: 67 | strict: true 68 | contexts: 69 | - continuous-integration/drone/pr 70 | enforce_admins: false 71 | restrictions: 72 | users: [] 73 | teams: [] 74 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files, Static and Dynamic libs (Shared Objects) 2 | *.o 3 | *.a 4 | *.so 5 | 6 | # Folders 7 | _obj 8 | _test 9 | 10 | # Architecture specific extensions/prefixes 11 | *.[568vq] 12 | [568vq].out 13 | 14 | *.cgo1.go 15 | *.cgo2.c 16 | _cgo_defun.c 17 | _cgo_gotypes.go 18 | _cgo_export.* 19 | 20 | _testmain.go 21 | 22 | *.exe 23 | *.test 24 | *.prof 25 | 26 | release/ 27 | vendor/ 28 | 29 | coverage.out 30 | drone-s3 31 | 32 | update_script.sh -------------------------------------------------------------------------------- /.harness/eventPR.yaml: -------------------------------------------------------------------------------- 1 | inputSet: 2 | name: event-PR 3 | identifier: eventPR 4 | orgIdentifier: default 5 | projectIdentifier: Drone_Plugins 6 | pipeline: 7 | identifier: drones3harness 8 | properties: 9 | ci: 10 | codebase: 11 | build: 12 | type: PR 13 | spec: 14 | number: <+trigger.prNumber> 15 | -------------------------------------------------------------------------------- /.harness/eventPush.yaml: -------------------------------------------------------------------------------- 1 | inputSet: 2 | name: event-Push 3 | identifier: eventPush 4 | orgIdentifier: default 5 | projectIdentifier: Drone_Plugins 6 | pipeline: 7 | identifier: drones3harness 8 | properties: 9 | ci: 10 | codebase: 11 | build: 12 | type: branch 13 | spec: 14 | branch: <+trigger.branch> 15 | -------------------------------------------------------------------------------- /.harness/eventTag.yaml: -------------------------------------------------------------------------------- 1 | inputSet: 2 | name: event-Tag 3 | identifier: eventTag 4 | orgIdentifier: default 5 | projectIdentifier: Drone_Plugins 6 | pipeline: 7 | identifier: drones3harness 8 | properties: 9 | ci: 10 | codebase: 11 | build: 12 | type: tag 13 | spec: 14 | tag: <+trigger.tag> 15 | -------------------------------------------------------------------------------- /.harness/harness.yaml: -------------------------------------------------------------------------------- 1 | pipeline: 2 | name: drone-s3-harness 3 | identifier: drones3harness 4 | projectIdentifier: Drone_Plugins 5 | orgIdentifier: default 6 | tags: {} 7 | properties: 8 | ci: 9 | codebase: 10 | connectorRef: GitHub_Drone_Plugins_Org 11 | repoName: drone-s3 12 | build: <+input> 13 | sparseCheckout: [] 14 | stages: 15 | - stage: 16 | name: Test 17 | identifier: Test 18 | description: "" 19 | type: CI 20 | spec: 21 | cloneCodebase: true 22 | caching: 23 | enabled: false 24 | paths: [] 25 | platform: 26 | os: Linux 27 | arch: Amd64 28 | runtime: 29 | type: Cloud 30 | spec: {} 31 | execution: 32 | steps: 33 | - step: 34 | type: Run 35 | name: lint 36 | identifier: lint 37 | spec: 38 | connectorRef: Plugins_Docker_Hub_Connector 39 | image: golang:1.22.7 40 | shell: Sh 41 | command: |- 42 | go install github.com/golangci/golangci-lint/cmd/golangci-lint@v1.51.2 43 | golangci-lint version 44 | golangci-lint run --timeout=300s 45 | - step: 46 | type: Run 47 | name: test 48 | identifier: Run_1 49 | spec: 50 | connectorRef: Plugins_Docker_Hub_Connector 51 | image: golang:1.22.7 52 | shell: Sh 53 | command: go test -cover ./... 54 | - parallel: 55 | - stage: 56 | name: linux-amd64 57 | identifier: linuxamd64 58 | description: "" 59 | type: CI 60 | spec: 61 | cloneCodebase: true 62 | caching: 63 | enabled: false 64 | paths: [] 65 | platform: 66 | os: Linux 67 | arch: Amd64 68 | runtime: 69 | type: Cloud 70 | spec: {} 71 | execution: 72 | steps: 73 | - step: 74 | type: Run 75 | name: Build Binary 76 | identifier: Run_1 77 | spec: 78 | connectorRef: Plugins_Docker_Hub_Connector 79 | image: golang:1.22.7 80 | shell: Sh 81 | command: |- 82 | # force go modules 83 | export GOPATH="" 84 | 85 | # disable cgo 86 | export CGO_ENABLED=0 87 | 88 | set -e 89 | set -x 90 | 91 | # linux 92 | export GOOS=linux GOARCH=amd64 93 | 94 | go build -v -ldflags "-X main.version=" -a -tags netgo -o release/linux/amd64/drone-s3 . 95 | envVariables: 96 | CGO_ENABLED: "0" 97 | - step: 98 | type: Plugin 99 | name: Build and Push on Tag 100 | identifier: Plugin_1 101 | spec: 102 | connectorRef: Plugins_Docker_Hub_Connector 103 | image: plugins/docker 104 | settings: 105 | username: drone 106 | password: <+secrets.getValue("Plugins_Docker_Hub_Pat")> 107 | repo: plugins/s3 108 | dockerfile: docker/Dockerfile.linux.amd64 109 | auto_tag: "true" 110 | auto_tag_suffix: linux-amd64 111 | when: 112 | stageStatus: Success 113 | condition: <+codebase.build.type> == "tag" 114 | - step: 115 | type: BuildAndPushDockerRegistry 116 | name: Build And Push on Branch 117 | identifier: BuildAndPushDockerRegistry_1 118 | spec: 119 | connectorRef: Plugins_Docker_Hub_Connector 120 | repo: plugins/s3 121 | tags: 122 | - linux-amd64 123 | caching: false 124 | dockerfile: docker/Dockerfile.linux.amd64 125 | when: 126 | stageStatus: Success 127 | condition: | 128 | <+codebase.build.type> == "branch" 129 | - stage: 130 | identifier: linarm64 131 | type: CI 132 | name: linux-arm64 133 | description: "" 134 | spec: 135 | cloneCodebase: true 136 | caching: 137 | enabled: false 138 | paths: [] 139 | platform: 140 | os: Linux 141 | arch: Arm64 142 | runtime: 143 | type: Cloud 144 | spec: {} 145 | execution: 146 | steps: 147 | - step: 148 | identifier: buildpush 149 | type: Run 150 | name: Build Binary 151 | spec: 152 | connectorRef: account.harnessImage 153 | image: golang:1.22.7 154 | shell: Sh 155 | command: |- 156 | # force go modules 157 | export GOPATH="" 158 | 159 | # disable cgo 160 | export CGO_ENABLED=0 161 | 162 | set -e 163 | set -x 164 | 165 | # linux 166 | export GOOS=linux GOARCH=arm64 167 | 168 | go build -v -ldflags "-X main.version=" -a -tags netgo -o release/linux/arm64/drone-s3 . 169 | - step: 170 | type: Plugin 171 | name: "Build and Push on Tag " 172 | identifier: Plugin_1 173 | spec: 174 | connectorRef: Plugins_Docker_Hub_Connector 175 | image: plugins/docker 176 | settings: 177 | username: drone 178 | password: <+secrets.getValue("Plugins_Docker_Hub_Pat")> 179 | repo: plugins/s3 180 | dockerfile: docker/Dockerfile.linux.arm64 181 | auto_tag: "true" 182 | auto_tag_suffix: linux-arm64 183 | when: 184 | stageStatus: Success 185 | condition: <+codebase.build.type> == "tag" 186 | - step: 187 | type: BuildAndPushDockerRegistry 188 | name: Build And Push On Branch 189 | identifier: BuildAndPushDockerRegistry_1 190 | spec: 191 | connectorRef: Plugins_Docker_Hub_Connector 192 | repo: plugins/s3 193 | tags: 194 | - linux-arm64 195 | caching: false 196 | dockerfile: docker/Dockerfile.linux.arm64 197 | when: 198 | stageStatus: Success 199 | condition: <+codebase.build.type> == "branch" 200 | - stage: 201 | identifier: winamd64 202 | type: CI 203 | name: windows-ltsc2022-amd64 204 | description: "" 205 | spec: 206 | cloneCodebase: true 207 | caching: 208 | enabled: false 209 | paths: [] 210 | platform: 211 | os: Windows 212 | arch: Amd64 213 | runtime: 214 | type: Cloud 215 | spec: {} 216 | execution: 217 | steps: 218 | - step: 219 | identifier: build_amd64ltsc2022 220 | type: Run 221 | name: Build Binary 222 | spec: 223 | connectorRef: Plugins_Docker_Hub_Connector 224 | image: golang:1.22.7 225 | shell: Sh 226 | command: |- 227 | # force go modules 228 | export GOPATH="" 229 | 230 | # disable cgo 231 | export CGO_ENABLED=0 232 | 233 | set -e 234 | set -x 235 | 236 | # linux 237 | GOOS=windows 238 | 239 | go build -v -ldflags "-X main.version=" -a -tags netgo -o release/windows/amd64/drone-s3.exe . 240 | - step: 241 | type: Plugin 242 | name: Build and Push on Tag 243 | identifier: Plugin_1 244 | spec: 245 | connectorRef: Plugins_Docker_Hub_Connector 246 | image: plugins/docker 247 | settings: 248 | username: drone 249 | password: <+secrets.getValue("Plugins_Docker_Hub_Pat")> 250 | repo: plugins/s3 251 | dockerfile: docker/Dockerfile.windows.ltsc2022 252 | auto_tag: "true" 253 | auto_tag_suffix: windows-ltsc2022-amd64 254 | when: 255 | stageStatus: Success 256 | condition: <+codebase.build.type> == "tag" 257 | - step: 258 | type: BuildAndPushDockerRegistry 259 | name: Build And Push on Branch 260 | identifier: BuildAndPushDockerRegistry_1 261 | spec: 262 | connectorRef: Plugins_Docker_Hub_Connector 263 | repo: plugins/s3 264 | tags: 265 | - windows-ltsc2022-amd64 266 | caching: false 267 | dockerfile: docker/Dockerfile.windows.ltsc2022 268 | when: 269 | stageStatus: Success 270 | condition: <+codebase.build.type> == "branch" 271 | - stage: 272 | identifier: win1809amd64 273 | type: CI 274 | name: windows-1809-amd64 275 | description: "" 276 | spec: 277 | cloneCodebase: true 278 | caching: 279 | enabled: false 280 | paths: [] 281 | execution: 282 | steps: 283 | - step: 284 | identifier: build_amd64ltsc2022 285 | type: Run 286 | name: Build Binary 287 | spec: 288 | connectorRef: Plugins_Docker_Hub_Connector 289 | image: golang:1.22.7 290 | shell: Sh 291 | command: |- 292 | # force go modules 293 | export GOPATH="" 294 | 295 | # disable cgo 296 | export CGO_ENABLED=0 297 | 298 | set -e 299 | set -x 300 | 301 | # linux 302 | GOOS=windows 303 | 304 | go build -v -ldflags "-X main.version=" -a -tags netgo -o release/windows/amd64/drone-s3.exe . 305 | - step: 306 | type: Plugin 307 | name: Build and Push on Tag 308 | identifier: Plugin_1 309 | spec: 310 | connectorRef: Plugins_Docker_Hub_Connector 311 | image: plugins/docker 312 | settings: 313 | username: drone 314 | password: <+secrets.getValue("Plugins_Docker_Hub_Pat")> 315 | repo: plugins/s3 316 | dockerfile: docker/Dockerfile.windows.1809 317 | auto_tag: "true" 318 | auto_tag_suffix: windows-1809-amd64 319 | when: 320 | stageStatus: Success 321 | condition: <+codebase.build.type> == "tag" 322 | - step: 323 | type: BuildAndPushDockerRegistry 324 | name: Build And Push on Branch 325 | identifier: BuildAndPushDockerRegistry_1 326 | spec: 327 | connectorRef: Plugins_Docker_Hub_Connector 328 | repo: plugins/s3 329 | tags: 330 | - windows-1809-amd64 331 | caching: false 332 | dockerfile: docker/Dockerfile.windows.1809 333 | when: 334 | stageStatus: Success 335 | condition: <+codebase.build.type> == "branch" 336 | infrastructure: 337 | type: VM 338 | spec: 339 | type: Pool 340 | spec: 341 | poolName: windows-2019 342 | os: Windows 343 | delegateSelectors: 344 | - windows-vm 345 | - stage: 346 | name: Manifest and Release 347 | identifier: Manifest_and_Release 348 | description: "" 349 | type: CI 350 | spec: 351 | cloneCodebase: true 352 | caching: 353 | enabled: false 354 | paths: [] 355 | platform: 356 | os: Linux 357 | arch: Amd64 358 | runtime: 359 | type: Cloud 360 | spec: {} 361 | execution: 362 | steps: 363 | - step: 364 | type: Plugin 365 | name: Manifest 366 | identifier: Manifest 367 | spec: 368 | connectorRef: Plugins_Docker_Hub_Connector 369 | image: plugins/manifest 370 | settings: 371 | username: drone 372 | password: <+secrets.getValue("Plugins_Docker_Hub_Pat")> 373 | auto_tag: "true" 374 | ignore_missing: "true" 375 | spec: docker/manifest.tmpl 376 | when: 377 | stageStatus: Success 378 | condition: | 379 | <+codebase.build.type> == "tag" || "branch" 380 | - step: 381 | type: Run 382 | name: build binaries 383 | identifier: Run_1 384 | spec: 385 | connectorRef: Plugins_Docker_Hub_Connector 386 | image: golang:1.22.7 387 | shell: Sh 388 | command: |- 389 | GOOS=linux GOARCH=amd64 go build -ldflags "-s -w" -a -tags netgo -o release/drone-s3-linux-amd64 390 | GOOS=linux GOARCH=arm64 go build -ldflags "-s -w" -a -tags netgo -o release/drone-s3-linux-arm64 391 | GOOS=darwin GOARCH=amd64 go build -ldflags "-s -w" -a -tags netgo -o release/drone-s3-darwin-amd64 392 | GOOS=darwin GOARCH=arm64 go build -ldflags "-s -w" -a -tags netgo -o release/drone-s3-darwin-arm64 393 | GOOS=windows GOARCH=amd64 go build -ldflags "-s -w" -a -tags netgo -o release/drone-s3-windows-amd64 394 | 395 | apt-get update -y 396 | apt-get install -y zstd 397 | zstd release/drone-s3-linux-amd64 398 | zstd release/drone-s3-linux-arm64 399 | zstd release/drone-s3-darwin-arm64 400 | zstd release/drone-s3-darwin-amd64 401 | zstd release/drone-s3-windows-amd64 402 | envVariables: 403 | CGO_ENABLED: "0" 404 | GO111MODULE: "on" 405 | when: 406 | stageStatus: Success 407 | condition: | 408 | <+codebase.build.type> == "tag" 409 | - step: 410 | type: Plugin 411 | name: Release 412 | identifier: Release 413 | spec: 414 | connectorRef: Plugins_Docker_Hub_Connector 415 | image: plugins/github-release 416 | settings: 417 | api_key: <+secrets.getValue("ci_job_github_pat")> 418 | title: <+trigger.tag> 419 | files: release/drone-s3-*.zst 420 | when: 421 | stageStatus: Success 422 | condition: <+codebase.build.type> == "tag" 423 | - step: 424 | name: Upload_Binaries_To_GCS 425 | identifier: Upload_Binaries_To_GCS 426 | template: 427 | templateRef: Upload_Binary_GCS_TI 428 | versionLabel: v1 429 | templateInputs: 430 | type: GCSUpload 431 | spec: 432 | sourcePath: /harness/release 433 | target: drone-s3/<+trigger.tag>/ 434 | when: 435 | condition: <+codebase.build.type> == "tag" 436 | variables: 437 | - name: DRONE_REPO_OWNER 438 | type: String 439 | description: "" 440 | required: false 441 | value: drone-plugins 442 | allowStageExecutions: true 443 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # drone-s3 2 | 3 | [![Build Status](http://cloud.drone.io/api/badges/drone-plugins/drone-s3/status.svg)](http://cloud.drone.io/drone-plugins/drone-s3) 4 | [![Gitter chat](https://badges.gitter.im/drone/drone.png)](https://gitter.im/drone/drone) 5 | [![Join the discussion at https://discourse.drone.io](https://img.shields.io/badge/discourse-forum-orange.svg)](https://discourse.drone.io) 6 | [![Drone questions at https://stackoverflow.com](https://img.shields.io/badge/drone-stackoverflow-orange.svg)](https://stackoverflow.com/questions/tagged/drone.io) 7 | [![](https://images.microbadger.com/badges/image/plugins/s3.svg)](https://microbadger.com/images/plugins/s3 "Get your own image badge on microbadger.com") 8 | [![Go Doc](https://godoc.org/github.com/drone-plugins/drone-s3?status.svg)](http://godoc.org/github.com/drone-plugins/drone-s3) 9 | [![Go Report](https://goreportcard.com/badge/github.com/drone-plugins/drone-s3)](https://goreportcard.com/report/github.com/drone-plugins/drone-s3) 10 | 11 | Drone plugin to publish files and artifacts to Amazon S3 or Minio. For the 12 | usage information and a listing of the available options please take a look at 13 | [the docs](http://plugins.drone.io/drone-plugins/drone-s3/). 14 | 15 | Run the following script to install git-leaks support to this repo. 16 | ``` 17 | chmod +x ./git-hooks/install.sh 18 | ./git-hooks/install.sh 19 | ``` 20 | 21 | ## Build 22 | 23 | Build the binary with the following commands: 24 | 25 | ``` 26 | go build 27 | go test 28 | ``` 29 | 30 | ## Docker 31 | 32 | Build the Docker image with the following commands: 33 | 34 | ``` 35 | CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -a -tags netgo -o release/linux/amd64/drone-s3 36 | docker build --rm=true -t plugins/s3 . 37 | ``` 38 | 39 | Please note incorrectly building the image for the correct x64 linux and with 40 | CGO disabled will result in an error when running the Docker image: 41 | 42 | ``` 43 | docker: Error response from daemon: Container command 44 | '/bin/drone-s3' not found or does not exist.. 45 | ``` 46 | 47 | ## Usage 48 | 49 | Execute from the working directory: 50 | 51 | * For Upload 52 | ``` 53 | docker run --rm \ 54 | -e PLUGIN_SOURCE= \ 55 | -e PLUGIN_TARGET= \ 56 | -e PLUGIN_BUCKET= \ 57 | -e AWS_ACCESS_KEY_ID= \ 58 | -e AWS_SECRET_ACCESS_KEY= \ 59 | -v $(pwd):$(pwd) \ 60 | -w $(pwd) \ 61 | plugins/s3 --dry-run 62 | ``` 63 | 64 | * For Download 65 | ``` 66 | docker run --rm \ 67 | -e PLUGIN_SOURCE= \ 68 | -e PLUGIN_BUCKET= \ 69 | -e AWS_ACCESS_KEY_ID= \ 70 | -e AWS_SECRET_ACCESS_KEY= \ 71 | -e PLUGIN_REGION= \ 72 | -e PLUGIN_DOWNLOAD="true" \ 73 | -v $(pwd):$(pwd) \ 74 | -w $(pwd) \ 75 | plugins/s3 --dry-run 76 | ``` 77 | 78 | ## Configuration Variables for Secondary Role Assumption with External ID 79 | 80 | The following environment variables enable the plugin to assume a secondary IAM role using IRSA, with an External ID if required by the role’s trust policy. 81 | 82 | ### Variables 83 | 84 | #### `PLUGIN_USER_ROLE_ARN` 85 | 86 | - **Type**: String 87 | - **Required**: No 88 | - **Description**: Specifies the secondary IAM role to be assumed by the plugin, allowing it to inherit permissions associated with this role and access specific AWS resources. 89 | 90 | #### `PLUGIN_USER_ROLE_EXTERNAL_ID` 91 | 92 | - **Type**: String 93 | - **Required**: No 94 | - **Description**: Provide the External ID necessary for the role assumption process if the secondary role’s trust policy mandates it. This is often required for added security, ensuring that only authorized entities assume the role. 95 | 96 | ### Usage Notes 97 | 98 | - If the role secondary role (`PLUGIN_USER_ROLE_ARN`) requires an External ID then pass it through `PLUGIN_USER_ROLE_EXTERNAL_ID`. -------------------------------------------------------------------------------- /docker/Dockerfile.linux.amd64: -------------------------------------------------------------------------------- 1 | FROM alpine:3.17 as alpine 2 | RUN apk add -U --no-cache ca-certificates 3 | 4 | FROM scratch 5 | 6 | LABEL maintainer="Drone.IO Community " \ 7 | org.label-schema.name="Drone S3" \ 8 | org.label-schema.vendor="Drone.IO Community" \ 9 | org.label-schema.schema-version="1.0" 10 | 11 | ADD release/linux/amd64/drone-s3 /bin/ 12 | COPY --from=alpine /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ 13 | ENTRYPOINT ["/bin/drone-s3"] 14 | -------------------------------------------------------------------------------- /docker/Dockerfile.linux.arm64: -------------------------------------------------------------------------------- 1 | FROM plugins/base:multiarch 2 | 3 | LABEL maintainer="Drone.IO Community " \ 4 | org.label-schema.name="Drone S3" \ 5 | org.label-schema.vendor="Drone.IO Community" \ 6 | org.label-schema.schema-version="1.0" 7 | 8 | ADD release/linux/arm64/drone-s3 /bin/ 9 | ENTRYPOINT ["/bin/drone-s3"] -------------------------------------------------------------------------------- /docker/Dockerfile.windows.1809: -------------------------------------------------------------------------------- 1 | # escape=` 2 | 3 | FROM mcr.microsoft.com/windows/servercore:ltsc2019 as core 4 | 5 | 6 | FROM mcr.microsoft.com/windows/nanoserver:1809 7 | USER ContainerAdministrator 8 | 9 | ENV GODEBUG=netdns=go 10 | COPY --from=core /windows/system32/netapi32.dll /windows/system32/netapi32.dll 11 | 12 | LABEL maintainer="Drone.IO Community " ` 13 | org.label-schema.name="Drone S3" ` 14 | org.label-schema.vendor="Drone.IO Community" ` 15 | org.label-schema.schema-version="1.0" 16 | 17 | ADD release/windows/amd64/drone-s3.exe C:/drone-s3.exe 18 | ENTRYPOINT [ "C:\\drone-s3.exe" ] -------------------------------------------------------------------------------- /docker/Dockerfile.windows.ltsc2022: -------------------------------------------------------------------------------- 1 | # escape=` 2 | 3 | FROM mcr.microsoft.com/windows/servercore:ltsc2022 as core 4 | 5 | 6 | FROM plugins/base:windows-ltsc2022-amd64 7 | USER ContainerAdministrator 8 | 9 | ENV GODEBUG=netdns=go 10 | COPY --from=core /windows/system32/netapi32.dll /windows/system32/netapi32.dll 11 | 12 | LABEL maintainer="Drone.IO Community " ` 13 | org.label-schema.name="Drone S3" ` 14 | org.label-schema.vendor="Drone.IO Community" ` 15 | org.label-schema.schema-version="1.0" 16 | 17 | ADD release/windows/amd64/drone-s3.exe C:/drone-s3.exe 18 | ENTRYPOINT [ "C:\\drone-s3.exe" ] -------------------------------------------------------------------------------- /docker/manifest.tmpl: -------------------------------------------------------------------------------- 1 | image: plugins/s3:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}} 2 | {{#if build.tags}} 3 | tags: 4 | {{#each build.tags}} 5 | - {{this}} 6 | {{/each}} 7 | {{/if}} 8 | manifests: 9 | - 10 | image: plugins/s3:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-amd64 11 | platform: 12 | architecture: amd64 13 | os: linux 14 | - 15 | image: plugins/s3:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}linux-arm64 16 | platform: 17 | architecture: arm64 18 | os: linux 19 | variant: v8 20 | - 21 | image: plugins/s3:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}windows-1809-amd64 22 | platform: 23 | architecture: amd64 24 | os: windows 25 | version: 1809 26 | - 27 | image: plugins/s3:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}windows-ltsc2022-amd64 28 | platform: 29 | architecture: amd64 30 | os: windows 31 | version: ltsc2022 -------------------------------------------------------------------------------- /git-hooks/.gitleaksignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drone-plugins/drone-s3/6655f98917ccd146743cca30c7bd2e2249b1c99e/git-hooks/.gitleaksignore -------------------------------------------------------------------------------- /git-hooks/README.md: -------------------------------------------------------------------------------- 1 | This document explains on how to install certain git hooks globally for all repositories in your machine. 2 | 3 | Step 1: git clone https://github.com/drone-plugins/drone-s3.git 4 | Step 2: cd git-hooks 5 | Step 3: Run install.sh 6 | 7 | "install.sh" script will create .git_template in the user directory and will put the git hook and its dependent scripts in it. Along with the .git_template folder, it will add 2 sections "init" and "hooks boolean" in the .gitconfig file in the same user's root directory. 8 | After running "install.sh" if you create/clone a new git repository then all the hooks will get install automatically for the git repository. In case of existing git repository copy the contents of ~/.git_template/hooks into the .git/hooks directory of existing git repository. -------------------------------------------------------------------------------- /git-hooks/hooks/git-leaks-pre-commit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #Helper script to be used as a pre-commit hook. 4 | 5 | echo "This hook checks for any secrets getting pushed as part of commit. If you feel that scan is false positive. \ 6 | Then add the exclusion in .gitleaksignore file. For more info visit: https://github.com/zricethezav/gitleaks" 7 | 8 | GIT_LEAKS_PRE_COMMIT=s$(git config --bool hook.pre-commit.gitleak) 9 | 10 | echo "INFO: Scanning Commits information for any GIT LEAKS" 11 | gitleaks protect --staged -v --exit-code=100 12 | STATUS=$? 13 | if [ $STATUS = 100 ]; then 14 | echo "WARNING: GIT LEAKS has detected sensitive information in your changes. Please remove them or add them (IF NON-SENSITIVE) in .gitleaksignore file." 15 | else 16 | exit 0 17 | fi -------------------------------------------------------------------------------- /git-hooks/hooks/git-leaks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #Helper script to be used as a pre-commit hook. 4 | 5 | echo "This hook checks for any secrets getting pushed as part of commit. If you feel that scan is false positive. \ 6 | Then add the exclusion in .gitleaksignore file. For more info visit: https://github.com/zricethezav/gitleaks" 7 | 8 | GIT_LEAKS=$(git config --bool hook.pre-push.gitleaks) 9 | 10 | echo "INFO: Scanning Commits information for any GIT LEAKS" 11 | gitleaks detect -s ./ --log-level=debug --log-opts=-1 -v 12 | STATUS=$? 13 | if [ $STATUS != 0 ]; then 14 | echo "WARNING: GIT LEAKS has detected sensitive information in your changes. Please remove them or add them (IF NON-SENSITIVE) in .gitleaksignore file." 15 | exit $STATUS 16 | else 17 | exit 0 18 | fi -------------------------------------------------------------------------------- /git-hooks/hooks/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | GL_SCRIPT_PATH="$HOME/.git_template/hooks/git-leaks-pre-commit.sh" 4 | 5 | pushd `dirname $0` > /dev/null && cd ../.. && BASEDIR=$(pwd -L) && popd > /dev/null 6 | BASENAME=`basename $0` 7 | 8 | if git rev-parse --verify HEAD >/dev/null 2>&1 9 | then 10 | against=HEAD 11 | else 12 | #Initial commit : diff against an empty tree object 13 | against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 14 | fi 15 | 16 | GIT_LEAKS_PRE_COMMIT=hook.pre-commit.gitleaks 17 | if [ "`git config $GIT_LEAKS_PRE_COMMIT`" == "false" ] 18 | then 19 | echo -e '\033[0;31m' checking git leaks is disabled - to enable: '\033[0;37m'git config --unset $GIT_LEAKS_PRE_COMMIT '\033[0m' 20 | echo -e '\033[0;34m' checking git leaks ... to enable: '\033[0;37m'git config --add $GIT_LEAKS_PRE_COMMIT true '\033[0m' 21 | else 22 | echo -e '\033[0;34m' checking for git leaks... 23 | [ -f "${GL_SCRIPT_PATH}" ] && . ${GL_SCRIPT_PATH} || echo "ERROR: Hook Script Not Found..." && exit 404 24 | fi -------------------------------------------------------------------------------- /git-hooks/hooks/pre-push: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | GL_SCRIPT_PATH="$HOME/.git_template/hooks/git-leaks.sh" 4 | 5 | pushd `dirname $0` > /dev/null && cd ../.. && BASEDIR=$(pwd -L) && popd > /dev/null 6 | BASENAME=`basename $0` 7 | 8 | if git rev-parse --verify HEAD >/dev/null 2>&1 9 | then 10 | against=HEAD 11 | else 12 | #Initial commit : diff against an empty tree object 13 | against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 14 | fi 15 | 16 | GIT_LEAKS=hook.pre-push.gitleaks 17 | if [ "`git config $GIT_LEAKS`" == "false" ] 18 | then 19 | echo -e '\033[0;31m' checking git leaks is disabled - to enable: '\033[0;37m'git config --unset $GIT_LEAKS '\033[0m' 20 | echo -e '\033[0;34m' checking git leaks ... to enable: '\033[0;37m'git config --add $GIT_LEAKS true '\033[0m' 21 | else 22 | echo -e '\033[0;34m' checking for git leaks... 23 | [ -f "${GL_SCRIPT_PATH}" ] && . ${GL_SCRIPT_PATH} || echo "ERROR: Hook Script Not Found..." && exit 404 24 | fi -------------------------------------------------------------------------------- /git-hooks/install.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | #Function to check if package is installed or not 4 | #args: $1: Name of the Package 5 | function check_package_installed() { 6 | LOCAL_PACKAGE_NAME=$1 7 | echo "Checking if $LOCAL_PACKAGE_NAME is installed or not..." 8 | brew list $LOCAL_PACKAGE_NAME 9 | if [ "$?" -eq 1 ];then 10 | echo "Installing $LOCAL_PACKAGE_NAME package..." 11 | brew install $LOCAL_PACKAGE_NAME 12 | fi 13 | } 14 | 15 | function create_git_template() { 16 | cd $BASEDIR 17 | mkdir -p ~/.git_template/hooks 18 | git config --global init.templatedir ${GIT_TEMPLATE} 19 | git config --global --add $GIT_LEAKS true 20 | git config --global --add $GIT_LEAKS_PRE_COMMIT true 21 | find hooks/ -type f -exec cp "{}" ~/.git_template/hooks \; 22 | #cp -f hooks/* ~/.git_template/hooks 23 | cat ~/.gitconfig 24 | } 25 | 26 | GIT_TEMPLATE="~/.git_template" 27 | GIT_LEAKS=hook.pre-push.gitleaks 28 | GIT_LEAKS_PRE_COMMIT=hook.pre-commit.gitleaks 29 | 30 | pushd `dirname $0` && BASEDIR=$(pwd -L) && popd 31 | 32 | echo This script will install hooks that run scripts that could be updated without notice. 33 | 34 | while true; do 35 | read -p "Do you wish to install these hooks?" yn 36 | case $yn in 37 | [Yy]* ) check_package_installed "gitleaks"; 38 | break;; 39 | [Nn]* ) exit;; 40 | * ) echo "Please answer yes or no.";; 41 | esac 42 | done 43 | 44 | create_git_template -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/drone-plugins/drone-s3 2 | 3 | go 1.22.7 4 | 5 | require ( 6 | github.com/aws/aws-sdk-go v1.48.0 7 | github.com/joho/godotenv v1.4.0 8 | github.com/mattn/go-zglob v0.0.4 9 | github.com/sirupsen/logrus v1.9.0 10 | github.com/urfave/cli v1.22.10 11 | ) 12 | 13 | require ( 14 | github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect 15 | github.com/jmespath/go-jmespath v0.4.0 // indirect 16 | github.com/pkg/errors v0.9.1 17 | github.com/russross/blackfriday/v2 v2.1.0 // indirect 18 | golang.org/x/sys v0.1.0 // indirect 19 | ) 20 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= 2 | github.com/aws/aws-sdk-go v1.48.0 h1:1SeJ8agckRDQvnSCt1dGZYAwUaoD2Ixj6IaXB4LCv8Q= 3 | github.com/aws/aws-sdk-go v1.48.0/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= 4 | github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= 5 | github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU= 6 | github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 7 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 9 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 10 | github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= 11 | github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= 12 | github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= 13 | github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= 14 | github.com/joho/godotenv v1.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= 15 | github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 16 | github.com/mattn/go-zglob v0.0.4 h1:LQi2iOm0/fGgu80AioIJ/1j9w9Oh+9DZ39J4VAGzHQM= 17 | github.com/mattn/go-zglob v0.0.4/go.mod h1:MxxjyoXXnMxfIpxTK2GAkw1w8glPsQILx3N5wrKakiY= 18 | github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= 19 | github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 20 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 21 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 22 | github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 23 | github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= 24 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 25 | github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= 26 | github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= 27 | github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 28 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 29 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 30 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 31 | github.com/urfave/cli v1.22.10 h1:p8Fspmz3iTctJstry1PYS3HVdllxnEzTEsgIgtxTrCk= 32 | github.com/urfave/cli v1.22.10/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= 33 | golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= 34 | golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= 35 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 36 | golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U= 37 | golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 38 | golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= 39 | golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= 40 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 41 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 42 | gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= 43 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 44 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= 45 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 46 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | 7 | "github.com/joho/godotenv" 8 | log "github.com/sirupsen/logrus" 9 | "github.com/urfave/cli" 10 | ) 11 | 12 | var ( 13 | version = "0.0.0" 14 | build = "0" 15 | ) 16 | 17 | func main() { 18 | app := cli.NewApp() 19 | app.Name = "s3 plugin" 20 | app.Usage = "s3 plugin" 21 | app.Version = fmt.Sprintf("%s+%s", version, build) 22 | app.Action = run 23 | app.Flags = []cli.Flag{ 24 | cli.StringFlag{ 25 | Name: "endpoint", 26 | Usage: "endpoint for the s3 connection", 27 | EnvVar: "PLUGIN_ENDPOINT,S3_ENDPOINT", 28 | }, 29 | cli.StringFlag{ 30 | Name: "access-key", 31 | Usage: "aws access key", 32 | EnvVar: "PLUGIN_ACCESS_KEY,AWS_ACCESS_KEY_ID", 33 | }, 34 | cli.StringFlag{ 35 | Name: "secret-key", 36 | Usage: "aws secret key", 37 | EnvVar: "PLUGIN_SECRET_KEY,AWS_SECRET_ACCESS_KEY", 38 | }, 39 | cli.StringFlag{ 40 | Name: "assume-role", 41 | Usage: "aws iam role to assume", 42 | EnvVar: "PLUGIN_ASSUME_ROLE,ASSUME_ROLE", 43 | }, 44 | cli.StringFlag{ 45 | Name: "assume-role-session-name", 46 | Usage: "aws iam role session name to assume", 47 | Value: "drone-s3", 48 | EnvVar: "PLUGIN_ASSUME_ROLE_SESSION_NAME,ASSUME_ROLE_SESSION_NAME", 49 | }, 50 | cli.StringFlag{ 51 | Name: "user-role-arn", 52 | Usage: "AWS user role", 53 | EnvVar: "PLUGIN_USER_ROLE_ARN,AWS_USER_ROLE_ARN", 54 | }, 55 | cli.StringFlag{ 56 | Name: "user-role-external-id", 57 | Usage: "external ID to use when assuming secondary role", 58 | EnvVar: "PLUGIN_USER_ROLE_EXTERNAL_ID", 59 | }, 60 | cli.StringFlag{ 61 | Name: "bucket", 62 | Usage: "aws bucket", 63 | Value: "us-east-1", 64 | EnvVar: "PLUGIN_BUCKET,S3_BUCKET", 65 | }, 66 | cli.StringFlag{ 67 | Name: "region", 68 | Usage: "aws region", 69 | Value: "us-east-1", 70 | EnvVar: "PLUGIN_REGION,S3_REGION", 71 | }, 72 | cli.StringFlag{ 73 | Name: "acl", 74 | Usage: "upload files with acl", 75 | EnvVar: "PLUGIN_ACL", 76 | }, 77 | cli.StringFlag{ 78 | Name: "source", 79 | Usage: "upload files from source folder", 80 | EnvVar: "PLUGIN_SOURCE", 81 | }, 82 | cli.StringFlag{ 83 | Name: "target", 84 | Usage: "upload files to target folder", 85 | EnvVar: "PLUGIN_TARGET", 86 | }, 87 | cli.StringFlag{ 88 | Name: "strip-prefix", 89 | Usage: "used to add or remove a prefix from the source/target path", 90 | EnvVar: "PLUGIN_STRIP_PREFIX", 91 | }, 92 | cli.StringSliceFlag{ 93 | Name: "exclude", 94 | Usage: "ignore files matching exclude pattern", 95 | EnvVar: "PLUGIN_EXCLUDE", 96 | }, 97 | cli.StringFlag{ 98 | Name: "encryption", 99 | Usage: "server-side encryption algorithm, defaults to none", 100 | EnvVar: "PLUGIN_ENCRYPTION", 101 | }, 102 | cli.BoolFlag{ 103 | Name: "download", 104 | Usage: "switch to download mode, which will fetch `source`'s files from s3 bucket", 105 | EnvVar: "PLUGIN_DOWNLOAD", 106 | }, 107 | cli.BoolFlag{ 108 | Name: "dry-run", 109 | Usage: "dry run for debug purposes", 110 | EnvVar: "PLUGIN_DRY_RUN", 111 | }, 112 | cli.BoolFlag{ 113 | Name: "path-style", 114 | Usage: "use path style for bucket paths", 115 | EnvVar: "PLUGIN_PATH_STYLE", 116 | }, 117 | cli.GenericFlag{ 118 | Name: "content-type", 119 | Usage: "set content type header for uploaded objects", 120 | EnvVar: "PLUGIN_CONTENT_TYPE", 121 | Value: &StringMapFlag{}, 122 | }, 123 | cli.GenericFlag{ 124 | Name: "content-encoding", 125 | Usage: "set content encoding header for uploaded objects", 126 | EnvVar: "PLUGIN_CONTENT_ENCODING", 127 | Value: &StringMapFlag{}, 128 | }, 129 | cli.GenericFlag{ 130 | Name: "cache-control", 131 | Usage: "set cache-control header for uploaded objects", 132 | EnvVar: "PLUGIN_CACHE_CONTROL", 133 | Value: &StringMapFlag{}, 134 | }, 135 | cli.StringFlag{ 136 | Name: "storage-class", 137 | Usage: "set storage class to choose the best backend", 138 | EnvVar: "PLUGIN_STORAGE_CLASS", 139 | }, 140 | cli.StringFlag{ 141 | Name: "env-file", 142 | Usage: "source env file", 143 | }, 144 | cli.StringFlag{ 145 | Name: "external-id", 146 | Usage: "external ID to use when assuming role", 147 | EnvVar: "PLUGIN_EXTERNAL_ID", 148 | }, 149 | cli.StringFlag{ 150 | Name: "oidc-token-id", 151 | Usage: "OIDC token for assuming role via web identity", 152 | EnvVar: "PLUGIN_OIDC_TOKEN_ID", 153 | }, 154 | } 155 | 156 | if err := app.Run(os.Args); err != nil { 157 | log.Fatal(err) 158 | } 159 | } 160 | 161 | func run(c *cli.Context) error { 162 | if c.String("env-file") != "" { 163 | _ = godotenv.Load(c.String("env-file")) 164 | } 165 | 166 | 167 | plugin := Plugin{ 168 | Endpoint: c.String("endpoint"), 169 | Key: c.String("access-key"), 170 | Secret: c.String("secret-key"), 171 | AssumeRole: c.String("assume-role"), 172 | AssumeRoleSessionName: c.String("assume-role-session-name"), 173 | Bucket: c.String("bucket"), 174 | UserRoleArn: c.String("user-role-arn"), 175 | UserRoleExternalID: c.String("user-role-external-id"), 176 | Region: c.String("region"), 177 | Access: c.String("acl"), 178 | Source: c.String("source"), 179 | Target: c.String("target"), 180 | StripPrefix: c.String("strip-prefix"), 181 | Exclude: c.StringSlice("exclude"), 182 | Encryption: c.String("encryption"), 183 | ContentType: c.Generic("content-type").(*StringMapFlag).Get(), 184 | Download: c.Bool("download"), 185 | ContentEncoding: c.Generic("content-encoding").(*StringMapFlag).Get(), 186 | CacheControl: c.Generic("cache-control").(*StringMapFlag).Get(), 187 | StorageClass: c.String("storage-class"), 188 | PathStyle: c.Bool("path-style"), 189 | DryRun: c.Bool("dry-run"), 190 | ExternalID: c.String("external-id"), 191 | IdToken: c.String("oidc-token-id"), 192 | } 193 | 194 | return plugin.Exec() 195 | } 196 | 197 | -------------------------------------------------------------------------------- /plugin.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io" 5 | "mime" 6 | "os" 7 | "path/filepath" 8 | "regexp" 9 | "strings" 10 | "time" 11 | 12 | "github.com/aws/aws-sdk-go/aws" 13 | "github.com/aws/aws-sdk-go/aws/credentials" 14 | "github.com/aws/aws-sdk-go/aws/credentials/stscreds" 15 | "github.com/aws/aws-sdk-go/aws/session" 16 | "github.com/aws/aws-sdk-go/service/s3" 17 | "github.com/aws/aws-sdk-go/service/sts" 18 | "github.com/mattn/go-zglob" 19 | "github.com/pkg/errors" 20 | log "github.com/sirupsen/logrus" 21 | ) 22 | 23 | // Plugin defines the S3 plugin parameters. 24 | type Plugin struct { 25 | Endpoint string 26 | Key string 27 | Secret string 28 | AssumeRole string 29 | AssumeRoleSessionName string 30 | Bucket string 31 | UserRoleArn string 32 | UserRoleExternalID string 33 | 34 | // if not "", enable server-side encryption 35 | // valid values are: 36 | // AES256 37 | // aws:kms 38 | Encryption string 39 | 40 | // us-east-1 41 | // us-west-1 42 | // us-west-2 43 | // eu-west-1 44 | // ap-southeast-1 45 | // ap-southeast-2 46 | // ap-northeast-1 47 | // sa-east-1 48 | Region string 49 | 50 | // if true, plugin is set to download mode, which means `source` from the bucket will be downloaded 51 | Download bool 52 | 53 | // Indicates the files ACL, which should be one 54 | // of the following: 55 | // private 56 | // public-read 57 | // public-read-write 58 | // authenticated-read 59 | // bucket-owner-read 60 | // bucket-owner-full-control 61 | Access string 62 | 63 | // Sets the content type on each uploaded object based on a extension map 64 | ContentType map[string]string 65 | 66 | // Sets the content encoding on each uploaded object based on a extension map 67 | ContentEncoding map[string]string 68 | 69 | // Sets the Cache-Control header on each uploaded object based on a extension map 70 | CacheControl map[string]string 71 | 72 | // Sets the storage class, affects the storage backend costs 73 | StorageClass string 74 | 75 | // Copies the files from the specified directory. 76 | // Regexp matching will apply to match multiple 77 | // files 78 | // 79 | // Examples: 80 | // /path/to/file 81 | // /path/to/*.txt 82 | // /path/to/*/*.txt 83 | // /path/to/** 84 | Source string 85 | Target string 86 | 87 | // Strip the prefix from the target path 88 | StripPrefix string 89 | 90 | // Exclude files matching this pattern. 91 | Exclude []string 92 | 93 | // Use path style instead of domain style. 94 | // 95 | // Should be true for minio and false for AWS. 96 | PathStyle bool 97 | // Dry run without uploading/ 98 | DryRun bool 99 | 100 | // set externalID for assume role 101 | ExternalID string 102 | 103 | // set OIDC ID Token to retrieve temporary credentials 104 | IdToken string 105 | } 106 | 107 | // Exec runs the plugin 108 | func (p *Plugin) Exec() error { 109 | if p.Download { 110 | p.Source = normalizePath(p.Source) 111 | p.Target = normalizePath(p.Target) 112 | } else { 113 | p.Target = strings.TrimPrefix(p.Target, "/") 114 | } 115 | 116 | // create the client 117 | client := p.createS3Client() 118 | 119 | // If in download mode, call the downloadS3Objects method 120 | if p.Download { 121 | sourceDir := normalizePath(p.Source) 122 | 123 | return p.downloadS3Objects(client, sourceDir) 124 | } 125 | 126 | // find the bucket 127 | log.WithFields(log.Fields{ 128 | "region": p.Region, 129 | "endpoint": p.Endpoint, 130 | "bucket": p.Bucket, 131 | }).Info("Attempting to upload") 132 | 133 | matches, err := matches(p.Source, p.Exclude) 134 | if err != nil { 135 | log.WithFields(log.Fields{ 136 | "error": err, 137 | }).Error("Could not match files") 138 | return err 139 | } 140 | 141 | for _, match := range matches { 142 | // skip directories 143 | if isDir(match, matches) { 144 | continue 145 | } 146 | 147 | target := resolveKey(p.Target, match, p.StripPrefix) 148 | 149 | contentType := matchExtension(match, p.ContentType) 150 | contentEncoding := matchExtension(match, p.ContentEncoding) 151 | cacheControl := matchExtension(match, p.CacheControl) 152 | 153 | if contentType == "" { 154 | contentType = mime.TypeByExtension(filepath.Ext(match)) 155 | 156 | if contentType == "" { 157 | contentType = "application/octet-stream" 158 | } 159 | } 160 | 161 | // log file for debug purposes. 162 | log.WithFields(log.Fields{ 163 | "name": match, 164 | "bucket": p.Bucket, 165 | "target": target, 166 | }).Info("Uploading file") 167 | 168 | // when executing a dry-run we exit because we don't actually want to 169 | // upload the file to S3. 170 | if p.DryRun { 171 | continue 172 | } 173 | 174 | f, err := os.Open(match) 175 | if err != nil { 176 | log.WithFields(log.Fields{ 177 | "error": err, 178 | "file": match, 179 | }).Error("Problem opening file") 180 | return err 181 | } 182 | defer f.Close() 183 | 184 | putObjectInput := &s3.PutObjectInput{ 185 | Body: f, 186 | Bucket: &(p.Bucket), 187 | Key: &target, 188 | } 189 | 190 | if contentType != "" { 191 | putObjectInput.ContentType = aws.String(contentType) 192 | } 193 | 194 | if contentEncoding != "" { 195 | putObjectInput.ContentEncoding = aws.String(contentEncoding) 196 | } 197 | 198 | if cacheControl != "" { 199 | putObjectInput.CacheControl = aws.String(cacheControl) 200 | } 201 | 202 | if p.Encryption != "" { 203 | putObjectInput.ServerSideEncryption = aws.String(p.Encryption) 204 | } 205 | 206 | if p.StorageClass != "" { 207 | putObjectInput.StorageClass = &(p.StorageClass) 208 | } 209 | 210 | if p.Access != "" { 211 | putObjectInput.ACL = &(p.Access) 212 | } 213 | 214 | _, err = client.PutObject(putObjectInput) 215 | 216 | if err != nil { 217 | log.WithFields(log.Fields{ 218 | "name": match, 219 | "bucket": p.Bucket, 220 | "target": target, 221 | "error": err, 222 | }).Error("Could not upload file") 223 | 224 | return err 225 | } 226 | f.Close() 227 | } 228 | 229 | return nil 230 | } 231 | 232 | // matches is a helper function that returns a list of all files matching the 233 | // included Glob pattern, while excluding all files that matche the exclusion 234 | // Glob pattners. 235 | func matches(include string, exclude []string) ([]string, error) { 236 | matches, err := zglob.Glob(include) 237 | if err != nil { 238 | return nil, err 239 | } 240 | if len(exclude) == 0 { 241 | return matches, nil 242 | } 243 | 244 | // find all files that are excluded and load into a map. we can verify 245 | // each file in the list is not a member of the exclusion list. 246 | excludem := map[string]bool{} 247 | for _, pattern := range exclude { 248 | excludes, err := zglob.Glob(pattern) 249 | if err != nil { 250 | return nil, err 251 | } 252 | for _, match := range excludes { 253 | excludem[match] = true 254 | } 255 | } 256 | 257 | var included []string 258 | for _, include := range matches { 259 | _, ok := excludem[include] 260 | if ok { 261 | continue 262 | } 263 | included = append(included, include) 264 | } 265 | return included, nil 266 | } 267 | 268 | func matchExtension(match string, stringMap map[string]string) string { 269 | for pattern := range stringMap { 270 | matched, err := regexp.MatchString(pattern, match) 271 | 272 | if err != nil { 273 | panic(err) 274 | } 275 | 276 | if matched { 277 | return stringMap[pattern] 278 | } 279 | } 280 | 281 | return "" 282 | } 283 | 284 | func assumeRole(roleArn, roleSessionName, externalID string) *credentials.Credentials { 285 | 286 | sess, _ := session.NewSession() 287 | client := sts.New(sess) 288 | duration := time.Hour * 1 289 | stsProvider := &stscreds.AssumeRoleProvider{ 290 | Client: client, 291 | Duration: duration, 292 | RoleARN: roleArn, 293 | RoleSessionName: roleSessionName, 294 | } 295 | 296 | if externalID != "" { 297 | stsProvider.ExternalID = &externalID 298 | } 299 | 300 | creds := credentials.NewCredentials(stsProvider) 301 | 302 | return creds 303 | } 304 | 305 | // resolveKey is a helper function that returns s3 object key where file present at srcPath is uploaded to. 306 | // srcPath is assumed to be in forward slash format 307 | func resolveKey(target, srcPath, stripPrefix string) string { 308 | key := filepath.Join(target, strings.TrimPrefix(srcPath, filepath.ToSlash(stripPrefix))) 309 | key = filepath.ToSlash(key) 310 | if !strings.HasPrefix(key, "/") { 311 | key = "/" + key 312 | } 313 | return key 314 | } 315 | 316 | func resolveSource(sourceDir, source, stripPrefix string) string { 317 | // Remove the leading sourceDir from the source path 318 | path := strings.TrimPrefix(strings.TrimPrefix(source, sourceDir), "/") 319 | 320 | // Add the specified stripPrefix to the resulting path 321 | return stripPrefix + path 322 | } 323 | 324 | // checks if the source path is a dir 325 | func isDir(source string, matches []string) bool { 326 | stat, err := os.Stat(source) 327 | if err != nil { 328 | return true // should never happen 329 | } 330 | if stat.IsDir() { 331 | count := 0 332 | for _, match := range matches { 333 | if strings.HasPrefix(match, source) { 334 | count++ 335 | } 336 | } 337 | if count <= 1 { 338 | log.Warnf("Skipping '%s' since it is a directory. Please use correct glob expression if this is unexpected.", source) 339 | } 340 | return true 341 | } 342 | return false 343 | } 344 | 345 | // normalizePath converts the path to a forward slash format and trims the prefix. 346 | func normalizePath(path string) string { 347 | return strings.TrimPrefix(filepath.ToSlash(path), "/") 348 | } 349 | 350 | // downloadS3Object downloads a single object from S3 351 | func (p *Plugin) downloadS3Object(client *s3.S3, sourceDir, key, target string) error { 352 | log.WithFields(log.Fields{ 353 | "bucket": p.Bucket, 354 | "key": key, 355 | }).Info("Getting S3 object") 356 | 357 | obj, err := client.GetObject(&s3.GetObjectInput{ 358 | Bucket: &p.Bucket, 359 | Key: &key, 360 | }) 361 | if err != nil { 362 | log.WithFields(log.Fields{ 363 | "error": err, 364 | "bucket": p.Bucket, 365 | "key": key, 366 | }).Error("Cannot get S3 object") 367 | return err 368 | } 369 | defer obj.Body.Close() 370 | 371 | // Create the destination file path 372 | destination := filepath.Join(p.Target, target) 373 | log.Println("Destination: ", destination) 374 | 375 | // Extract the directory from the destination path 376 | dir := filepath.Dir(destination) 377 | 378 | // Create the directory and any necessary parent directories 379 | if err := os.MkdirAll(dir, os.ModePerm); err != nil { 380 | return errors.Wrap(err, "error creating directories") 381 | } 382 | 383 | f, err := os.Create(destination) 384 | if err != nil { 385 | log.WithFields(log.Fields{ 386 | "error": err, 387 | "file": destination, 388 | }).Error("Failed to create file") 389 | return err 390 | } 391 | defer f.Close() 392 | 393 | _, err = io.Copy(f, obj.Body) 394 | if err != nil { 395 | log.WithFields(log.Fields{ 396 | "error": err, 397 | "file": destination, 398 | }).Error("Failed to write file") 399 | return err 400 | } 401 | 402 | return nil 403 | } 404 | 405 | // downloadS3Objects downloads all objects in the specified S3 bucket path 406 | func (p *Plugin) downloadS3Objects(client *s3.S3, sourceDir string) error { 407 | log.WithFields(log.Fields{ 408 | "bucket": p.Bucket, 409 | "dir": sourceDir, 410 | }).Info("Listing S3 directory") 411 | 412 | list, err := client.ListObjectsV2(&s3.ListObjectsV2Input{ 413 | Bucket: &p.Bucket, 414 | Prefix: &sourceDir, 415 | }) 416 | if err != nil { 417 | log.WithFields(log.Fields{ 418 | "error": err, 419 | "bucket": p.Bucket, 420 | "dir": sourceDir, 421 | }).Error("Cannot list S3 directory") 422 | return err 423 | } 424 | 425 | for _, item := range list.Contents { 426 | // resolveSource takes a source directory, a source path, and a prefix to strip, 427 | // and returns a resolved target path by removing the sourceDir from the source 428 | // and appending the stripPrefix. 429 | target := resolveSource(sourceDir, *item.Key, p.StripPrefix) 430 | 431 | if err := p.downloadS3Object(client, sourceDir, *item.Key, target); err != nil { 432 | return err 433 | } 434 | } 435 | 436 | return nil 437 | } 438 | 439 | // createS3Client creates and returns an S3 client based on the plugin configuration 440 | func (p *Plugin) createS3Client() *s3.S3 { 441 | 442 | conf := &aws.Config{ 443 | Region: aws.String(p.Region), 444 | Endpoint: &p.Endpoint, 445 | DisableSSL: aws.Bool(strings.HasPrefix(p.Endpoint, "http://")), 446 | S3ForcePathStyle: aws.Bool(p.PathStyle), 447 | } 448 | 449 | // Create initial session 450 | sess, err := session.NewSession(conf) 451 | if err != nil { 452 | log.Fatalf("failed to create AWS session: %v", err) 453 | } 454 | 455 | if p.Key != "" && p.Secret != "" { 456 | conf.Credentials = credentials.NewStaticCredentials(p.Key, p.Secret, "") 457 | } else if p.IdToken != "" && p.AssumeRole != "" { 458 | creds, err := assumeRoleWithWebIdentity(sess, p.AssumeRole, p.AssumeRoleSessionName, p.IdToken) 459 | if err != nil { 460 | log.Fatalf("failed to assume role with web identity: %v", err) 461 | } 462 | conf.Credentials = creds 463 | } else if p.AssumeRole != "" { 464 | conf.Credentials = assumeRole(p.AssumeRole, p.AssumeRoleSessionName, p.ExternalID) 465 | } else { 466 | log.Warn("AWS Key and/or Secret not provided (falling back to ec2 instance profile)") 467 | } 468 | 469 | 470 | // Create session with primary credentials 471 | sess, err = session.NewSession(conf) 472 | if err != nil { 473 | log.Fatalf("failed to create AWS session: %v", err) 474 | } 475 | 476 | // Initialize client with the session 477 | client := s3.New(sess) 478 | 479 | // Handle secondary role assumption if UserRoleArn is provided 480 | if len(p.UserRoleArn) > 0 { 481 | log.WithField("UserRoleArn", p.UserRoleArn).Info("Using user role ARN") 482 | 483 | // Create credentials using the existing session for role assumption 484 | // by assuming the UserRoleArn (with ExternalID when provided) 485 | creds := stscreds.NewCredentials(sess, p.UserRoleArn, func(provider *stscreds.AssumeRoleProvider) { 486 | if p.UserRoleExternalID != "" { 487 | provider.ExternalID = aws.String(p.UserRoleExternalID) 488 | } 489 | }) 490 | 491 | // Create new client with same config but updated credentials 492 | client = s3.New(sess, &aws.Config{Credentials: creds}) 493 | } 494 | 495 | return client 496 | } 497 | 498 | func assumeRoleWithWebIdentity(sess *session.Session, roleArn, roleSessionName, idToken string) (*credentials.Credentials, error) { 499 | svc := sts.New(sess) 500 | input := &sts.AssumeRoleWithWebIdentityInput{ 501 | RoleArn: aws.String(roleArn), 502 | RoleSessionName: aws.String(roleSessionName), 503 | WebIdentityToken: aws.String(idToken), 504 | } 505 | result, err := svc.AssumeRoleWithWebIdentity(input) 506 | if err != nil { 507 | log.Fatalf("failed to assume role with web identity: %v", err) 508 | } 509 | return credentials.NewStaticCredentials(*result.Credentials.AccessKeyId, *result.Credentials.SecretAccessKey, *result.Credentials.SessionToken), nil 510 | } 511 | -------------------------------------------------------------------------------- /plugin.yml: -------------------------------------------------------------------------------- 1 | run: 2 | binary: 3 | source: https://github.com/drone-plugins/drone-s3/releases/download/{{ release }}/drone-s3-{{ os }}-{{ arch }}.zst 4 | fallback_source: https://app.harness.io/storage/harness-download/harness-ti/drone-s3/{{ release }}/drone-s3-{{ os }}-{{ arch }}.zst -------------------------------------------------------------------------------- /plugin_unix_test.go: -------------------------------------------------------------------------------- 1 | //go:build aix || darwin || dragonfly || freebsd || (js && wasm) || linux || netbsd || openbsd || solaris 2 | 3 | package main 4 | 5 | import ( 6 | "testing" 7 | ) 8 | 9 | func TestResolveUnixKey(t *testing.T) { 10 | tests := []struct { 11 | name string 12 | target string 13 | srcPath string 14 | stripPrefix string 15 | expected string 16 | }{ 17 | { 18 | name: "target not set", 19 | target: "", 20 | srcPath: "/foo/bar", 21 | stripPrefix: "/foo", 22 | expected: "/bar", 23 | }, 24 | { 25 | name: "strip prefix not set", 26 | target: "/hello", 27 | srcPath: "/foo/bar", 28 | stripPrefix: "", 29 | expected: "/hello/foo/bar", 30 | }, 31 | { 32 | name: "everything set", 33 | target: "hello", 34 | srcPath: "/foo/bar", 35 | stripPrefix: "/foo", 36 | expected: "/hello/bar", 37 | }, 38 | } 39 | 40 | for _, tc := range tests { 41 | got := resolveKey(tc.target, tc.srcPath, tc.stripPrefix) 42 | if tc.expected != got { 43 | t.Fatalf("%s: expected error: %v, got: %v", tc.name, tc.expected, got) 44 | } 45 | } 46 | } 47 | 48 | func TestNormalizePath(t *testing.T) { 49 | tests := []struct { 50 | input string 51 | expected string 52 | }{ 53 | { 54 | input: "/path/to/file.txt", 55 | expected: "path/to/file.txt", 56 | }, 57 | { 58 | input: "C:\\Users\\username\\Documents\\file.doc", 59 | expected: "C:\\Users\\username\\Documents\\file.doc", 60 | }, 61 | { 62 | input: "relative/path/to/file", 63 | expected: "relative/path/to/file", 64 | }, 65 | { 66 | input: "file.txt", 67 | expected: "file.txt", 68 | }, 69 | { 70 | input: "/root/directory/", 71 | expected: "root/directory/", 72 | }, 73 | { 74 | input: "no_slash", 75 | expected: "no_slash", 76 | }, 77 | } 78 | 79 | for _, tc := range tests { 80 | result := normalizePath(tc.input) 81 | if result != tc.expected { 82 | t.Errorf("Expected: %s, Got: %s", tc.expected, result) 83 | } 84 | } 85 | } 86 | 87 | func TestResolveSource(t *testing.T) { 88 | tests := []struct { 89 | sourceDir string 90 | source string 91 | stripPrefix string 92 | expected string 93 | }{ 94 | // Test case 1 95 | { 96 | sourceDir: "/home/user/documents", 97 | source: "/home/user/documents/file.txt", 98 | stripPrefix: "output-", 99 | expected: "output-file.txt", 100 | }, 101 | // Test case 2 102 | { 103 | sourceDir: "assets", 104 | source: "assets/images/logo.png", 105 | stripPrefix: "", 106 | expected: "images/logo.png", 107 | }, 108 | // Test case 3 109 | { 110 | sourceDir: "/var/www/html", 111 | source: "/var/www/html/pages/index.html", 112 | stripPrefix: "web", 113 | expected: "webpages/index.html", 114 | }, 115 | // Test case 4 116 | { 117 | sourceDir: "dist", 118 | source: "dist/js/app.js", 119 | stripPrefix: "public", 120 | expected: "publicjs/app.js", 121 | }, 122 | } 123 | 124 | for _, tc := range tests { 125 | result := resolveSource(tc.sourceDir, tc.source, tc.stripPrefix) 126 | if result != tc.expected { 127 | t.Errorf("Expected: %s, Got: %s", tc.expected, result) 128 | } 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /plugin_windows_test.go: -------------------------------------------------------------------------------- 1 | //go:build windows 2 | 3 | package main 4 | 5 | import ( 6 | "testing" 7 | ) 8 | 9 | func TestResolveWinKey(t *testing.T) { 10 | tests := []struct { 11 | name string 12 | target string 13 | srcPath string 14 | stripPrefix string 15 | expected string 16 | }{ 17 | { 18 | name: "target not set", 19 | target: "", 20 | srcPath: "/foo/bar", 21 | stripPrefix: "/foo", 22 | expected: "/bar", 23 | }, 24 | { 25 | name: "strip prefix not set", 26 | target: "/hello", 27 | srcPath: "/foo/bar", 28 | stripPrefix: "", 29 | expected: "/hello/foo/bar", 30 | }, 31 | { 32 | name: "everything set", 33 | target: "hello", 34 | srcPath: "/foo/bar", 35 | stripPrefix: "/foo", 36 | expected: "/hello/bar", 37 | }, 38 | { 39 | name: "backslash strip prefix", 40 | target: "hello", 41 | srcPath: `foo/bar/world`, 42 | stripPrefix: `foo\bar`, 43 | expected: "/hello/world", 44 | }, 45 | { 46 | name: "forward slash strip prefix", 47 | target: "hello", 48 | srcPath: "foo/bar/world", 49 | stripPrefix: `foo/bar`, 50 | expected: "/hello/world", 51 | }, 52 | } 53 | 54 | for _, tc := range tests { 55 | got := resolveKey(tc.target, tc.srcPath, tc.stripPrefix) 56 | if tc.expected != got { 57 | t.Fatalf("%s: expected error: %v, got: %v", tc.name, tc.expected, got) 58 | } 59 | } 60 | } 61 | 62 | func TestNormalizePath(t *testing.T) { 63 | tests := []struct { 64 | input string 65 | expected string 66 | }{ 67 | { 68 | input: "/path/to/file.txt", 69 | expected: "path/to/file.txt", 70 | }, 71 | { 72 | input: "C:\\Users\\username\\Documents\\file.doc", 73 | expected: "C:\\Users\\username\\Documents\\file.doc", 74 | }, 75 | { 76 | input: "relative/path/to/file", 77 | expected: "relative/path/to/file", 78 | }, 79 | { 80 | input: "file.txt", 81 | expected: "file.txt", 82 | }, 83 | { 84 | input: "/root/directory/", 85 | expected: "root/directory/", 86 | }, 87 | { 88 | input: "no_slash", 89 | expected: "no_slash", 90 | }, 91 | } 92 | 93 | for _, tc := range tests { 94 | result := normalizePath(tc.input) 95 | if result != tc.expected { 96 | t.Errorf("Expected: %s, Got: %s", tc.expected, result) 97 | } 98 | } 99 | } 100 | 101 | func TestResolveSource(t *testing.T) { 102 | tests := []struct { 103 | sourceDir string 104 | source string 105 | stripPrefix string 106 | expected string 107 | }{ 108 | // Test case 1 109 | { 110 | sourceDir: "/home/user/documents", 111 | source: "/home/user/documents/file.txt", 112 | stripPrefix: "output-", 113 | expected: "output-file.txt", 114 | }, 115 | // Test case 2 116 | { 117 | sourceDir: "assets", 118 | source: "assets/images/logo.png", 119 | stripPrefix: "", 120 | expected: "images/logo.png", 121 | }, 122 | // Test case 3 123 | { 124 | sourceDir: "/var/www/html", 125 | source: "/var/www/html/pages/index.html", 126 | stripPrefix: "web", 127 | expected: "webpages/index.html", 128 | }, 129 | // Test case 4 130 | { 131 | sourceDir: "dist", 132 | source: "dist/js/app.js", 133 | stripPrefix: "public", 134 | expected: "publicjs/app.js", 135 | }, 136 | } 137 | 138 | for _, tc := range tests { 139 | result := resolveSource(tc.sourceDir, tc.source, tc.stripPrefix) 140 | if result != tc.expected { 141 | t.Errorf("Expected: %s, Got: %s", tc.expected, result) 142 | } 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ], 5 | "docker": { 6 | "fileMatch": [ 7 | "/docker/Dockerfile" 8 | ] 9 | }, 10 | "labels": [ 11 | "renovate" 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /stringmap.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "encoding/json" 5 | ) 6 | 7 | type StringMapFlag struct { 8 | parts map[string]string 9 | } 10 | 11 | func (s *StringMapFlag) String() string { 12 | return "" 13 | } 14 | 15 | func (s *StringMapFlag) Get() map[string]string { 16 | return s.parts 17 | } 18 | 19 | func (s *StringMapFlag) Set(value string) error { 20 | s.parts = map[string]string{} 21 | 22 | if err := json.Unmarshal([]byte(value), &s.parts); err != nil { 23 | s.parts[".*"] = value 24 | } 25 | 26 | return nil 27 | } 28 | --------------------------------------------------------------------------------