From f9e5a49b33456655c53c140b8125273e4b201005 Mon Sep 17 00:00:00 2001 From: Robert Kaussow Date: Sun, 17 Jan 2021 13:06:23 +0100 Subject: [PATCH] fork: initial commit --- .drone.jsonnet | 322 +++++++++++++++++++++++++++++++ .drone.yml | 382 +++++++++++++++++++++++++++++++++++++ .github/settings.yml | 55 ++++++ .gitignore | 4 + LICENSE | 202 ++++++++++++++++++++ README.md | 56 ++++++ cmd/drone-docker/config.go | 236 +++++++++++++++++++++++ cmd/drone-docker/main.go | 64 +++++++ docker/Dockerfile.amd64 | 23 +++ docker/Dockerfile.arm | 23 +++ docker/Dockerfile.arm64 | 23 +++ docker/manifest-quay.tmpl | 24 +++ docker/manifest.tmpl | 24 +++ go.mod | 16 ++ go.sum | 58 ++++++ plugin/daemon.go | 25 +++ plugin/docker.go | 217 +++++++++++++++++++++ plugin/docker_test.go | 1 + plugin/impl.go | 207 ++++++++++++++++++++ plugin/plugin.go | 21 ++ plugin/tags.go | 93 +++++++++ plugin/tags_test.go | 197 +++++++++++++++++++ renovate.json | 4 + 23 files changed, 2277 insertions(+) create mode 100644 .drone.jsonnet create mode 100644 .drone.yml create mode 100644 .github/settings.yml create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 cmd/drone-docker/config.go create mode 100644 cmd/drone-docker/main.go create mode 100644 docker/Dockerfile.amd64 create mode 100644 docker/Dockerfile.arm create mode 100644 docker/Dockerfile.arm64 create mode 100644 docker/manifest-quay.tmpl create mode 100644 docker/manifest.tmpl create mode 100644 go.mod create mode 100644 go.sum create mode 100644 plugin/daemon.go create mode 100644 plugin/docker.go create mode 100644 plugin/docker_test.go create mode 100644 plugin/impl.go create mode 100644 plugin/plugin.go create mode 100644 plugin/tags.go create mode 100644 plugin/tags_test.go create mode 100644 renovate.json diff --git a/.drone.jsonnet b/.drone.jsonnet new file mode 100644 index 0000000..77cfb84 --- /dev/null +++ b/.drone.jsonnet @@ -0,0 +1,322 @@ +local PipelineTest = { + kind: 'pipeline', + image_pull_secrets: ['docker_config'], + name: 'test', + platform: { + os: 'linux', + arch: 'amd64', + }, + steps: [ + { + name: 'staticcheck', + image: 'golang:1.15', + commands: [ + 'go run honnef.co/go/tools/cmd/staticcheck ./...', + ], + volumes: [ + { + name: 'gopath', + path: '/go', + }, + ], + }, + { + name: 'lint', + image: 'golang:1.15', + commands: [ + 'go run golang.org/x/lint/golint -set_exit_status ./...', + ], + volumes: [ + { + name: 'gopath', + path: '/go', + }, + ], + }, + { + name: 'vet', + image: 'golang:1.15', + commands: [ + 'go vet ./...', + ], + volumes: [ + { + name: 'gopath', + path: '/go', + }, + ], + }, + { + name: 'test', + image: 'golang:1.15', + commands: [ + 'go test -cover ./...', + ], + volumes: [ + { + name: 'gopath', + path: '/go', + }, + ], + }, + ], + volumes: [ + { + name: 'gopath', + temp: {}, + }, + ], + trigger: { + ref: ['refs/heads/main', 'refs/tags/**', 'refs/pull/**'], + }, +}; + + +local PipelineBuildBinaries = { + kind: 'pipeline', + image_pull_secrets: ['docker_config'], + name: 'build-binaries', + platform: { + os: 'linux', + arch: 'amd64', + }, + steps: [ + { + name: 'build', + image: 'techknowlogick/xgo:go-1.15.x', + commands: [ + '[ -z "${DRONE_TAG}" ] && BUILD_VERSION=${DRONE_COMMIT_SHA:0:8} || BUILD_VERSION=${DRONE_TAG##v}', + 'mkdir -p release/', + "cd cmd/drone-docker && xgo -ldflags \"-s -w -X main.version=$BUILD_VERSION\" -tags netgo -targets 'linux/amd64,linux/arm-6,linux/arm64' -out drone-docker .", + 'mv /build/* /drone/src/release/', + ], + }, + { + name: 'executable', + image: 'alpine', + commands: [ + '$(find release/ -executable -type f | grep drone-docker-linux-amd64) --help', + ], + }, + { + name: 'compress', + image: 'alpine', + commands: [ + 'apk add upx', + 'find release/ -maxdepth 1 -executable -type f -exec upx {} \\;', + 'ls -lh release/', + ], + }, + { + name: 'checksum', + image: 'alpine', + commands: [ + 'cd release/ && sha256sum * > sha256sum.txt', + ], + }, + { + name: 'publish', + image: 'plugins/github-release', + settings: { + overwrite: true, + api_key: { + from_secret: 'github_token', + }, + files: ['release/*'], + title: '${DRONE_TAG}', + note: 'CHANGELOG.md', + }, + when: { + ref: [ + 'refs/tags/**', + ], + }, + }, + ], + depends_on: [ + 'test', + ], + trigger: { + ref: ['refs/heads/main', 'refs/tags/**', 'refs/pull/**'], + }, +}; + +local PipelineBuildContainer(arch='amd64') = { + kind: 'pipeline', + image_pull_secrets: ['docker_config'], + name: 'build-container-' + arch, + platform: { + os: 'linux', + arch: arch, + }, + steps: [ + { + name: 'build', + image: 'golang:1.15', + commands: [ + '[ -z "${DRONE_TAG}" ] && BUILD_VERSION=${DRONE_COMMIT_SHA:0:8} || BUILD_VERSION=${DRONE_TAG##v}', + 'go build -v -ldflags "-X main.version=$BUILD_VERSION" -a -tags netgo -o release/' + arch + '/drone-docker ./cmd/drone-docker', + ], + }, + { + name: 'dryrun', + image: 'plugins/docker:19', + settings: { + config: { from_secret: 'docker_config' }, + dry_run: true, + dockerfile: 'docker/Dockerfile.' + arch, + repo: 'thegeeklab/${DRONE_REPO_NAME}', + username: { from_secret: 'docker_username' }, + password: { from_secret: 'docker_password' }, + }, + depends_on: ['build'], + when: { + ref: ['refs/pull/**'], + }, + }, + { + name: 'publish-dockerhub', + image: 'plugins/docker:19', + settings: { + config: { from_secret: 'docker_config' }, + auto_tag: true, + auto_tag_suffix: arch, + dockerfile: 'docker/Dockerfile.' + arch, + repo: 'thegeeklab/${DRONE_REPO_NAME}', + username: { from_secret: 'docker_username' }, + password: { from_secret: 'docker_password' }, + }, + when: { + ref: ['refs/heads/main', 'refs/tags/**'], + }, + depends_on: ['dryrun'], + }, + { + name: 'publish-quay', + image: 'plugins/docker:19', + settings: { + config: { from_secret: 'docker_config' }, + auto_tag: true, + auto_tag_suffix: arch, + dockerfile: 'docker/Dockerfile.' + arch, + registry: 'quay.io', + repo: 'quay.io/thegeeklab/${DRONE_REPO_NAME}', + username: { from_secret: 'quay_username' }, + password: { from_secret: 'quay_password' }, + }, + when: { + ref: ['refs/heads/main', 'refs/tags/**'], + }, + depends_on: ['dryrun'], + }, + ], + depends_on: [ + 'test', + ], + trigger: { + ref: ['refs/heads/main', 'refs/tags/**', 'refs/pull/**'], + }, +}; + +local PipelineNotifications = { + kind: 'pipeline', + image_pull_secrets: ['docker_config'], + name: 'notifications', + platform: { + os: 'linux', + arch: 'amd64', + }, + steps: [ + { + image: 'plugins/manifest', + name: 'manifest-dockerhub', + settings: { + ignore_missing: true, + auto_tag: true, + username: { from_secret: 'docker_username' }, + password: { from_secret: 'docker_password' }, + spec: 'docker/manifest.tmpl', + }, + when: { + status: ['success'], + }, + }, + { + image: 'plugins/manifest', + name: 'manifest-quay', + settings: { + ignore_missing: true, + auto_tag: true, + username: { from_secret: 'quay_username' }, + password: { from_secret: 'quay_password' }, + spec: 'docker/manifest-quay.tmpl', + }, + when: { + status: ['success'], + }, + }, + { + name: 'pushrm-dockerhub', + image: 'chko/docker-pushrm:1', + environment: { + DOCKER_PASS: { + from_secret: 'docker_password', + }, + DOCKER_USER: { + from_secret: 'docker_username', + }, + PUSHRM_FILE: 'README.md', + PUSHRM_SHORT: 'Drone plugin to build multiarch Docker images', + PUSHRM_TARGET: 'thegeeklab/${DRONE_REPO_NAME}', + }, + when: { + status: ['success'], + }, + }, + { + name: 'pushrm-quay', + image: 'chko/docker-pushrm:1', + environment: { + APIKEY__QUAY_IO: { + from_secret: 'quay_token', + }, + PUSHRM_FILE: 'README.md', + PUSHRM_TARGET: 'quay.io/thegeeklab/${DRONE_REPO_NAME}', + }, + when: { + status: ['success'], + }, + }, + { + name: 'matrix', + image: 'plugins/matrix', + settings: { + homeserver: { from_secret: 'matrix_homeserver' }, + roomid: { from_secret: 'matrix_roomid' }, + template: 'Status: **{{ build.status }}**
Build: [{{ repo.Owner }}/{{ repo.Name }}]({{ build.link }}) ({{ build.branch }}) by {{ build.author }}
Message: {{ build.message }}', + username: { from_secret: 'matrix_username' }, + password: { from_secret: 'matrix_password' }, + }, + when: { + status: ['success', 'failure'], + }, + }, + ], + depends_on: [ + 'build-binaries', + 'build-container-amd64', + 'build-container-arm64', + ], + trigger: { + ref: ['refs/heads/main', 'refs/tags/**'], + status: ['success', 'failure'], + }, +}; + +[ + PipelineTest, + PipelineBuildBinaries, + PipelineBuildContainer(arch='amd64'), + PipelineBuildContainer(arch='arm64'), + PipelineNotifications, +] diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..7fba963 --- /dev/null +++ b/.drone.yml @@ -0,0 +1,382 @@ +--- +kind: pipeline +name: test + +platform: + os: linux + arch: amd64 + +steps: +- name: staticcheck + image: golang:1.15 + commands: + - go run honnef.co/go/tools/cmd/staticcheck ./... + volumes: + - name: gopath + path: /go + +- name: lint + image: golang:1.15 + commands: + - go run golang.org/x/lint/golint -set_exit_status ./... + volumes: + - name: gopath + path: /go + +- name: vet + image: golang:1.15 + commands: + - go vet ./... + volumes: + - name: gopath + path: /go + +- name: test + image: golang:1.15 + commands: + - go test -cover ./... + volumes: + - name: gopath + path: /go + +volumes: +- name: gopath + temp: {} + +image_pull_secrets: +- docker_config + +trigger: + ref: + - refs/heads/main + - refs/tags/** + - refs/pull/** + +--- +kind: pipeline +name: build-binaries + +platform: + os: linux + arch: amd64 + +steps: +- name: build + image: techknowlogick/xgo:go-1.15.x + commands: + - "[ -z \"${DRONE_TAG}\" ] && BUILD_VERSION=${DRONE_COMMIT_SHA:0:8} || BUILD_VERSION=${DRONE_TAG##v}" + - mkdir -p release/ + - cd cmd/drone-docker && xgo -ldflags "-s -w -X main.version=$BUILD_VERSION" -tags netgo -targets 'linux/amd64,linux/arm-6,linux/arm64' -out drone-docker . + - mv /build/* /drone/src/release/ + +- name: executable + image: alpine + commands: + - $(find release/ -executable -type f | grep drone-docker-linux-amd64) --help + +- name: compress + image: alpine + commands: + - apk add upx + - find release/ -maxdepth 1 -executable -type f -exec upx {} \; + - ls -lh release/ + +- name: checksum + image: alpine + commands: + - cd release/ && sha256sum * > sha256sum.txt + +- name: publish + image: plugins/github-release + settings: + api_key: + from_secret: github_token + files: + - release/* + note: CHANGELOG.md + overwrite: true + title: ${DRONE_TAG} + when: + ref: + - refs/tags/** + +image_pull_secrets: +- docker_config + +trigger: + ref: + - refs/heads/main + - refs/tags/** + - refs/pull/** + +depends_on: +- test + +--- +kind: pipeline +name: build-container-amd64 + +platform: + os: linux + arch: amd64 + +steps: +- name: build + image: golang:1.15 + commands: + - "[ -z \"${DRONE_TAG}\" ] && BUILD_VERSION=${DRONE_COMMIT_SHA:0:8} || BUILD_VERSION=${DRONE_TAG##v}" + - go build -v -ldflags "-X main.version=$BUILD_VERSION" -a -tags netgo -o release/amd64/drone-docker ./cmd/drone-docker + +- name: dryrun + image: plugins/docker:19 + settings: + config: + from_secret: docker_config + dockerfile: docker/Dockerfile.amd64 + dry_run: true + password: + from_secret: docker_password + repo: thegeeklab/${DRONE_REPO_NAME} + username: + from_secret: docker_username + when: + ref: + - refs/pull/** + depends_on: + - build + +- name: publish-dockerhub + image: plugins/docker:19 + settings: + auto_tag: true + auto_tag_suffix: amd64 + config: + from_secret: docker_config + dockerfile: docker/Dockerfile.amd64 + password: + from_secret: docker_password + repo: thegeeklab/${DRONE_REPO_NAME} + username: + from_secret: docker_username + when: + ref: + - refs/heads/main + - refs/tags/** + depends_on: + - dryrun + +- name: publish-quay + image: plugins/docker:19 + settings: + auto_tag: true + auto_tag_suffix: amd64 + config: + from_secret: docker_config + dockerfile: docker/Dockerfile.amd64 + password: + from_secret: quay_password + registry: quay.io + repo: quay.io/thegeeklab/${DRONE_REPO_NAME} + username: + from_secret: quay_username + when: + ref: + - refs/heads/main + - refs/tags/** + depends_on: + - dryrun + +image_pull_secrets: +- docker_config + +trigger: + ref: + - refs/heads/main + - refs/tags/** + - refs/pull/** + +depends_on: +- test + +--- +kind: pipeline +name: build-container-arm64 + +platform: + os: linux + arch: arm64 + +steps: +- name: build + image: golang:1.15 + commands: + - "[ -z \"${DRONE_TAG}\" ] && BUILD_VERSION=${DRONE_COMMIT_SHA:0:8} || BUILD_VERSION=${DRONE_TAG##v}" + - go build -v -ldflags "-X main.version=$BUILD_VERSION" -a -tags netgo -o release/arm64/drone-docker ./cmd/drone-docker + +- name: dryrun + image: plugins/docker:19 + settings: + config: + from_secret: docker_config + dockerfile: docker/Dockerfile.arm64 + dry_run: true + password: + from_secret: docker_password + repo: thegeeklab/${DRONE_REPO_NAME} + username: + from_secret: docker_username + when: + ref: + - refs/pull/** + depends_on: + - build + +- name: publish-dockerhub + image: plugins/docker:19 + settings: + auto_tag: true + auto_tag_suffix: arm64 + config: + from_secret: docker_config + dockerfile: docker/Dockerfile.arm64 + password: + from_secret: docker_password + repo: thegeeklab/${DRONE_REPO_NAME} + username: + from_secret: docker_username + when: + ref: + - refs/heads/main + - refs/tags/** + depends_on: + - dryrun + +- name: publish-quay + image: plugins/docker:19 + settings: + auto_tag: true + auto_tag_suffix: arm64 + config: + from_secret: docker_config + dockerfile: docker/Dockerfile.arm64 + password: + from_secret: quay_password + registry: quay.io + repo: quay.io/thegeeklab/${DRONE_REPO_NAME} + username: + from_secret: quay_username + when: + ref: + - refs/heads/main + - refs/tags/** + depends_on: + - dryrun + +image_pull_secrets: +- docker_config + +trigger: + ref: + - refs/heads/main + - refs/tags/** + - refs/pull/** + +depends_on: +- test + +--- +kind: pipeline +name: notifications + +platform: + os: linux + arch: amd64 + +steps: +- name: manifest-dockerhub + image: plugins/manifest + settings: + auto_tag: true + ignore_missing: true + password: + from_secret: docker_password + spec: docker/manifest.tmpl + username: + from_secret: docker_username + when: + status: + - success + +- name: manifest-quay + image: plugins/manifest + settings: + auto_tag: true + ignore_missing: true + password: + from_secret: quay_password + spec: docker/manifest-quay.tmpl + username: + from_secret: quay_username + when: + status: + - success + +- name: pushrm-dockerhub + image: chko/docker-pushrm:1 + environment: + DOCKER_PASS: + from_secret: docker_password + DOCKER_USER: + from_secret: docker_username + PUSHRM_FILE: README.md + PUSHRM_SHORT: Drone plugin to build multiarch Docker images + PUSHRM_TARGET: thegeeklab/${DRONE_REPO_NAME} + when: + status: + - success + +- name: pushrm-quay + image: chko/docker-pushrm:1 + environment: + APIKEY__QUAY_IO: + from_secret: quay_token + PUSHRM_FILE: README.md + PUSHRM_TARGET: quay.io/thegeeklab/${DRONE_REPO_NAME} + when: + status: + - success + +- name: matrix + image: plugins/matrix + settings: + homeserver: + from_secret: matrix_homeserver + password: + from_secret: matrix_password + roomid: + from_secret: matrix_roomid + template: "Status: **{{ build.status }}**
Build: [{{ repo.Owner }}/{{ repo.Name }}]({{ build.link }}) ({{ build.branch }}) by {{ build.author }}
Message: {{ build.message }}" + username: + from_secret: matrix_username + when: + status: + - success + - failure + +image_pull_secrets: +- docker_config + +trigger: + ref: + - refs/heads/main + - refs/tags/** + status: + - success + - failure + +depends_on: +- build-binaries +- build-container-amd64 +- build-container-arm64 + +... diff --git a/.github/settings.yml b/.github/settings.yml new file mode 100644 index 0000000..dc8702f --- /dev/null +++ b/.github/settings.yml @@ -0,0 +1,55 @@ +repository: + name: drone-docker + description: Drone plugin to build multiarch Docker images + topics: drone, drone-plugin + + private: false + has_issues: true + has_wiki: false + has_downloads: true + + default_branch: master + + allow_squash_merge: true + allow_merge_commit: true + allow_rebase_merge: true + +labels: + - name: bug + color: d73a4a + description: Something isn't working + - name: documentation + color: 0075ca + description: Improvements or additions to documentation + - name: duplicate + color: cfd3d7 + description: This issue or pull request already exists + - name: enhancement + color: a2eeef + description: New feature or request + - name: good first issue + color: 7057ff + description: Good for newcomers + - name: help wanted + color: 008672 + description: Extra attention is needed + - name: invalid + color: e4e669 + description: This doesn't seem right + - name: question + color: d876e3 + description: Further information is requested + - name: wontfix + color: ffffff + description: This will not be worked on + +branches: + - name: master + protection: + required_pull_request_reviews: null + required_status_checks: + strict: true + contexts: + - continuous-integration/drone/pr + enforce_admins: null + restrictions: null diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5fd54ad --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +/release/ +/drone-docker* + +coverage.out diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e07ea97 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2021 Robert Kaussow + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..9f82b59 --- /dev/null +++ b/README.md @@ -0,0 +1,56 @@ +# drone-docker + +Drone plugin to build multiarch Docker images + +[![Build Status](https://img.shields.io/drone/build/thegeeklab/drone-docker?logo=drone)](https://cloud.drone.io/thegeeklab/drone-docker) +[![Docker Hub](https://img.shields.io/badge/dockerhub-latest-blue.svg?logo=docker&logoColor=white)](https://hub.docker.com/r/thegeeklab/drone-docker) +[![Quay.io](https://img.shields.io/badge/quay-latest-blue.svg?logo=docker&logoColor=white)](https://quay.io/repository/thegeeklab/drone-docker) +[![Go Report Card](https://goreportcard.com/badge/github.com/thegeeklab/drone-docker)](https://goreportcard.com/report/github.com/thegeeklab/drone-docker) +[![GitHub contributors](https://img.shields.io/github/contributors/thegeeklab/drone-docker)](https://github.com/thegeeklab/drone-docker/graphs/contributors) +[![Source: GitHub](https://img.shields.io/badge/source-github-blue.svg?logo=github&logoColor=white)](https://github.com/thegeeklab/drone-docker) +[![License: MIT](https://img.shields.io/github/license/thegeeklab/drone-docker)](https://github.com/thegeeklab/drone-docker/blob/main/LICENSE) + +Drone plugin to build multiarch Docker images. + +## Build + +Build the binary with the following command: + +```Shell +export GOOS=linux +export GOARCH=amd64 +export CGO_ENABLED=0 +export GO111MODULE=on + +go build -v -a -tags netgo -o release/drone-docker +``` + +Build the Docker image with the following command: + +```Shell +docker build --file docker/Dockerfile.amd64 --tag thegeeklab/drone-docker . +``` + +## Usage + +> Notice: Be aware that the tis plugin requires privileged capabilities, otherwise the integrated Docker daemon is not able to start. + +```console +docker run --rm \ + -e PLUGIN_TAG=latest \ + -e PLUGIN_REPO=octocat/hello-world \ + -e DRONE_COMMIT_SHA=00000000 \ + -v $(pwd):$(pwd) \ + -w $(pwd) \ + --privileged \ + thegeeklab/drone-docker --dry-run +``` + +## Contributors + +Special thanks goes to all [contributors](https://github.com/thegeeklab/drone-docker/graphs/contributors). If you would like to contribute, +please see the [instructions](https://github.com/thegeeklab/drone-docker/blob/main/CONTRIBUTING.md). + +## License + +This project is licensed under the MIT License - see the [LICENSE](https://github.com/thegeeklab/drone-docker/blob/main/LICENSE) file for details. diff --git a/cmd/drone-docker/config.go b/cmd/drone-docker/config.go new file mode 100644 index 0000000..8baceb1 --- /dev/null +++ b/cmd/drone-docker/config.go @@ -0,0 +1,236 @@ +package main + +import ( + "github.com/thegeeklab/drone-docker/plugin" + "github.com/urfave/cli/v2" +) + +// settingsFlags has the cli.Flags for the plugin.Settings. +func settingsFlags(settings *plugin.Settings) []cli.Flag { + return []cli.Flag{ + &cli.BoolFlag{ + Name: "dry-run", + Usage: "dry run disables docker push", + EnvVars: []string{"PLUGIN_DRY_RUN"}, + Destination: &settings.Dryrun, + }, + &cli.StringFlag{ + Name: "remote.url", + Usage: "git remote url", + EnvVars: []string{"DRONE_REMOTE_URL"}, + Destination: &settings.Build.Remote, + }, + &cli.StringFlag{ + Name: "daemon.mirror", + Usage: "docker daemon registry mirror", + EnvVars: []string{"PLUGIN_MIRROR", "DOCKER_PLUGIN_MIRROR"}, + Destination: &settings.Daemon.Mirror, + }, + &cli.StringFlag{ + Name: "daemon.storage-driver", + Usage: "docker daemon storage driver", + EnvVars: []string{"PLUGIN_STORAGE_DRIVER"}, + Destination: &settings.Daemon.StorageDriver, + }, + &cli.StringFlag{ + Name: "daemon.storage-path", + Usage: "docker daemon storage path", + Value: "/var/lib/docker", + EnvVars: []string{"PLUGIN_STORAGE_PATH"}, + Destination: &settings.Daemon.StoragePath, + }, + &cli.StringFlag{ + Name: "daemon.bip", + Usage: "docker daemon bride ip address", + EnvVars: []string{"PLUGIN_BIP"}, + Destination: &settings.Daemon.Bip, + }, + &cli.StringFlag{ + Name: "daemon.mtu", + Usage: "docker daemon custom mtu setting", + EnvVars: []string{"PLUGIN_MTU"}, + Destination: &settings.Daemon.MTU, + }, + &cli.StringSliceFlag{ + Name: "daemon.dns", + Usage: "docker daemon dns server", + EnvVars: []string{"PLUGIN_CUSTOM_DNS"}, + Destination: &settings.Daemon.DNS, + }, + &cli.StringSliceFlag{ + Name: "daemon.dns-search", + Usage: "docker daemon dns search domains", + EnvVars: []string{"PLUGIN_CUSTOM_DNS_SEARCH"}, + Destination: &settings.Daemon.DNSSearch, + }, + &cli.BoolFlag{ + Name: "daemon.insecure", + Usage: "docker daemon allows insecure registries", + EnvVars: []string{"PLUGIN_INSECURE"}, + Destination: &settings.Daemon.Insecure, + }, + &cli.BoolFlag{ + Name: "daemon.ipv6", + Usage: "docker daemon IPv6 networking", + EnvVars: []string{"PLUGIN_IPV6"}, + Destination: &settings.Daemon.IPv6, + }, + &cli.BoolFlag{ + Name: "daemon.experimental", + Usage: "docker daemon Experimental mode", + EnvVars: []string{"PLUGIN_EXPERIMENTAL"}, + Destination: &settings.Daemon.Experimental, + }, + &cli.BoolFlag{ + Name: "daemon.debug", + Usage: "docker daemon executes in debug mode", + EnvVars: []string{"PLUGIN_DEBUG", "DOCKER_LAUNCH_DEBUG"}, + Destination: &settings.Daemon.Debug, + }, + &cli.BoolFlag{ + Name: "daemon.off", + Usage: "don't start the docker daemon", + EnvVars: []string{"PLUGIN_DAEMON_OFF"}, + Destination: &settings.Daemon.Disabled, + }, + &cli.StringFlag{ + Name: "dockerfile", + Usage: "build dockerfile", + Value: "Dockerfile", + EnvVars: []string{"PLUGIN_DOCKERFILE"}, + Destination: &settings.Build.Dockerfile, + }, + &cli.StringFlag{ + Name: "context", + Usage: "build context", + Value: ".", + EnvVars: []string{"PLUGIN_CONTEXT"}, + Destination: &settings.Build.Context, + }, + &cli.StringSliceFlag{ + Name: "tags", + Usage: "build tags", + Value: cli.NewStringSlice([]string{"latest"}...), + EnvVars: []string{"PLUGIN_TAG", "PLUGIN_TAGS"}, + FilePath: ".tags", + Destination: &settings.Build.Tags, + }, + &cli.BoolFlag{ + Name: "tags.auto", + Usage: "default build tags", + EnvVars: []string{"PLUGIN_DEFAULT_TAGS", "PLUGIN_AUTO_TAG"}, + Destination: &settings.Build.TagsAuto, + }, + &cli.StringFlag{ + Name: "tags.suffix", + Usage: "default build tags with suffix", + EnvVars: []string{"PLUGIN_DEFAULT_SUFFIX", "PLUGIN_AUTO_TAG_SUFFIX"}, + Destination: &settings.Build.TagsSuffix, + }, + &cli.StringSliceFlag{ + Name: "args", + Usage: "build args", + EnvVars: []string{"PLUGIN_BUILD_ARGS"}, + Destination: &settings.Build.Args, + }, + &cli.StringSliceFlag{ + Name: "args-from-env", + Usage: "build args", + EnvVars: []string{"PLUGIN_BUILD_ARGS_FROM_ENV"}, + Destination: &settings.Build.ArgsEnv, + }, + &cli.BoolFlag{ + Name: "quiet", + Usage: "quiet docker build", + EnvVars: []string{"PLUGIN_QUIET"}, + Destination: &settings.Build.Quiet, + }, + &cli.StringFlag{ + Name: "target", + Usage: "build target", + EnvVars: []string{"PLUGIN_TARGET"}, + Destination: &settings.Build.Target, + }, + &cli.StringSliceFlag{ + Name: "cache-from", + Usage: "images to consider as cache sources", + EnvVars: []string{"PLUGIN_CACHE_FROM"}, + Destination: &settings.Build.CacheFrom, + }, + &cli.BoolFlag{ + Name: "squash", + Usage: "squash the layers at build time", + EnvVars: []string{"PLUGIN_SQUASH"}, + Destination: &settings.Build.Squash, + }, + &cli.BoolFlag{ + Name: "pull-image", + Usage: "force pull base image at build time", + EnvVars: []string{"PLUGIN_PULL_IMAGE"}, + Value: true, + Destination: &settings.Build.Pull, + }, + &cli.BoolFlag{ + Name: "compress", + Usage: "compress the build context using gzip", + EnvVars: []string{"PLUGIN_COMPRESS"}, + Destination: &settings.Build.Compress, + }, + &cli.StringFlag{ + Name: "repo", + Usage: "docker repository", + EnvVars: []string{"PLUGIN_REPO"}, + Destination: &settings.Build.Repo, + }, + &cli.StringFlag{ + Name: "docker.registry", + Usage: "docker registry", + Value: "https://index.docker.io/v1/", + EnvVars: []string{"PLUGIN_REGISTRY", "DOCKER_REGISTRY"}, + Destination: &settings.Login.Registry, + }, + &cli.StringFlag{ + Name: "docker.username", + Usage: "docker username", + EnvVars: []string{"PLUGIN_USERNAME", "DOCKER_USERNAME"}, + Destination: &settings.Login.Username, + }, + &cli.StringFlag{ + Name: "docker.password", + Usage: "docker password", + EnvVars: []string{"PLUGIN_PASSWORD", "DOCKER_PASSWORD"}, + Destination: &settings.Login.Password, + }, + &cli.StringFlag{ + Name: "docker.email", + Usage: "docker email", + EnvVars: []string{"PLUGIN_EMAIL", "DOCKER_EMAIL"}, + Destination: &settings.Login.Email, + }, + &cli.StringFlag{ + Name: "docker.config", + Usage: "docker json dockerconfig content", + EnvVars: []string{"PLUGIN_CONFIG", "DOCKER_PLUGIN_CONFIG"}, + Destination: &settings.Login.Config, + }, + &cli.BoolFlag{ + Name: "docker.purge", + Usage: "docker should cleanup images", + EnvVars: []string{"PLUGIN_PURGE"}, + Value: true, + Destination: &settings.Cleanup, + }, + &cli.BoolFlag{ + Name: "no-cache", + Usage: "do not use cached intermediate containers", + EnvVars: []string{"PLUGIN_NO_CACHE"}, + Destination: &settings.Build.NoCache, + }, + &cli.StringSliceFlag{ + Name: "add-host", + Usage: "additional host:IP mapping", + EnvVars: []string{"PLUGIN_ADD_HOST"}, + Destination: &settings.Build.AddHost, + }, + } +} diff --git a/cmd/drone-docker/main.go b/cmd/drone-docker/main.go new file mode 100644 index 0000000..d2d4594 --- /dev/null +++ b/cmd/drone-docker/main.go @@ -0,0 +1,64 @@ +package main + +import ( + "os" + + "github.com/joho/godotenv" + "github.com/thegeeklab/drone-docker/plugin" + "github.com/urfave/cli/v2" + + "github.com/drone-plugins/drone-plugin-lib/errors" + "github.com/drone-plugins/drone-plugin-lib/urfave" +) + +var version = "unknown" + +func main() { + settings := &plugin.Settings{} + + if _, err := os.Stat("/run/drone/env"); err == nil { + godotenv.Overload("/run/drone/env") + } + + app := &cli.App{ + Name: "drone-docker", + Usage: "build docker container with DinD", + Version: version, + Flags: append(settingsFlags(settings), urfave.Flags()...), + Action: run(settings), + } + + if err := app.Run(os.Args); err != nil { + errors.HandleExit(err) + } +} + +func run(settings *plugin.Settings) cli.ActionFunc { + return func(ctx *cli.Context) error { + urfave.LoggingFromContext(ctx) + + plugin := plugin.New( + *settings, + urfave.PipelineFromContext(ctx), + urfave.NetworkFromContext(ctx), + ) + + if err := plugin.Validate(); err != nil { + if e, ok := err.(errors.ExitCoder); ok { + return e + } + + return errors.ExitMessagef("validation failed: %w", err) + } + + if err := plugin.Execute(); err != nil { + if e, ok := err.(errors.ExitCoder); ok { + return e + } + + return errors.ExitMessagef("execution failed: %w", err) + } + + return nil + } +} diff --git a/docker/Dockerfile.amd64 b/docker/Dockerfile.amd64 new file mode 100644 index 0000000..d93d4c3 --- /dev/null +++ b/docker/Dockerfile.amd64 @@ -0,0 +1,23 @@ +FROM amd64/docker:19.03-dind + +LABEL maintainer="Robert Kaussow " +LABEL org.opencontainers.image.authors="Robert Kaussow " +LABEL org.opencontainers.image.title="drone-docker" +LABEL org.opencontainers.image.url="https://github.com/thegeeklab/drone-docker" +LABEL org.opencontainers.image.source="https://github.com/thegeeklab/drone-docker" +LABEL org.opencontainers.image.documentation="https://github.com/thegeeklab/drone-docker" + +ENV DOCKER_HOST=unix:///var/run/docker.sock + +RUN apk --update add --virtual .build-deps curl && \ + mkdir -p /etc/docker/ && \ + curl -SsL -o /etc/docker/default.json https://github.com/moby/moby/blob/19.03/profiles/seccomp/default.json && \ + sed -i 's/SCMP_ACT_ERRNO/SCMP_ACT_TRACE/g' /etc/docker/default.json && \ + chmod 600 /etc/docker/default.json && \ + apk del .build-deps && \ + rm -rf /var/cache/apk/* && \ + rm -rf /tmp/* + +ADD release/amd64/drone-docker /bin/ + +ENTRYPOINT ["/usr/local/bin/dockerd-entrypoint.sh", "drone-docker"] diff --git a/docker/Dockerfile.arm b/docker/Dockerfile.arm new file mode 100644 index 0000000..f7dc034 --- /dev/null +++ b/docker/Dockerfile.arm @@ -0,0 +1,23 @@ +FROM arm32v7/docker:19.03-dind + +LABEL maintainer="Robert Kaussow " +LABEL org.opencontainers.image.authors="Robert Kaussow " +LABEL org.opencontainers.image.title="drone-docker" +LABEL org.opencontainers.image.url="https://github.com/thegeeklab/drone-docker" +LABEL org.opencontainers.image.source="https://github.com/thegeeklab/drone-docker" +LABEL org.opencontainers.image.documentation="https://github.com/thegeeklab/drone-docker" + +ENV DOCKER_HOST=unix:///var/run/docker.sock + +RUN apk --update add --virtual .build-deps curl && \ + mkdir -p /etc/docker/ && \ + curl -SsL -o /etc/docker/default.json https://github.com/moby/moby/blob/19.03/profiles/seccomp/default.json && \ + sed -i 's/SCMP_ACT_ERRNO/SCMP_ACT_TRACE/g' /etc/docker/default.json && \ + chmod 600 /etc/docker/default.json && \ + apk del .build-deps && \ + rm -rf /var/cache/apk/* && \ + rm -rf /tmp/* + +ADD release/arm/drone-docker /bin/ + +ENTRYPOINT ["/usr/local/bin/dockerd-entrypoint.sh", "drone-docker"] diff --git a/docker/Dockerfile.arm64 b/docker/Dockerfile.arm64 new file mode 100644 index 0000000..0adb9de --- /dev/null +++ b/docker/Dockerfile.arm64 @@ -0,0 +1,23 @@ +FROM arm64v8/docker:19.03-dind + +LABEL maintainer="Robert Kaussow " +LABEL org.opencontainers.image.authors="Robert Kaussow " +LABEL org.opencontainers.image.title="drone-docker" +LABEL org.opencontainers.image.url="https://github.com/thegeeklab/drone-docker" +LABEL org.opencontainers.image.source="https://github.com/thegeeklab/drone-docker" +LABEL org.opencontainers.image.documentation="https://github.com/thegeeklab/drone-docker" + +ENV DOCKER_HOST=unix:///var/run/docker.sock + +RUN apk --update add --virtual .build-deps curl && \ + mkdir -p /etc/docker/ && \ + curl -SsL -o /etc/docker/default.json https://github.com/moby/moby/blob/19.03/profiles/seccomp/default.json && \ + sed -i 's/SCMP_ACT_ERRNO/SCMP_ACT_TRACE/g' /etc/docker/default.json && \ + chmod 600 /etc/docker/default.json && \ + apk del .build-deps && \ + rm -rf /var/cache/apk/* && \ + rm -rf /tmp/* + +ADD release/arm64/drone-docker /bin/ + +ENTRYPOINT ["/usr/local/bin/dockerd-entrypoint.sh", "drone-docker"] diff --git a/docker/manifest-quay.tmpl b/docker/manifest-quay.tmpl new file mode 100644 index 0000000..211bd26 --- /dev/null +++ b/docker/manifest-quay.tmpl @@ -0,0 +1,24 @@ +image: quay.io/thegeeklab/drone-docker:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}} +{{#if build.tags}} +tags: +{{#each build.tags}} + - {{this}} +{{/each}} +{{/if}} +manifests: + - image: quay.io/thegeeklab/drone-docker:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}amd64 + platform: + architecture: amd64 + os: linux + + - image: quay.io/thegeeklab/drone-docker:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}arm64 + platform: + architecture: arm64 + os: linux + variant: v8 + + - image: quay.io/thegeeklab/drone-docker:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}arm + platform: + architecture: arm + os: linux + variant: v7 diff --git a/docker/manifest.tmpl b/docker/manifest.tmpl new file mode 100644 index 0000000..38b6aec --- /dev/null +++ b/docker/manifest.tmpl @@ -0,0 +1,24 @@ +image: thegeeklab/drone-docker:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}} +{{#if build.tags}} +tags: +{{#each build.tags}} + - {{this}} +{{/each}} +{{/if}} +manifests: + - image: thegeeklab/drone-docker:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}amd64 + platform: + architecture: amd64 + os: linux + + - image: thegeeklab/drone-docker:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}arm64 + platform: + architecture: arm64 + os: linux + variant: v8 + + - image: thegeeklab/drone-docker:{{#if build.tag}}{{trimPrefix "v" build.tag}}-{{/if}}arm + platform: + architecture: arm + os: linux + variant: v7 diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..4ba3fca --- /dev/null +++ b/go.mod @@ -0,0 +1,16 @@ +module github.com/thegeeklab/drone-docker + +require ( + github.com/coreos/go-semver v0.3.0 + github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect + github.com/drone-plugins/drone-plugin-lib v0.4.0 + github.com/joho/godotenv v1.3.0 + github.com/kr/pretty v0.1.0 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/sirupsen/logrus v1.7.0 + github.com/urfave/cli/v2 v2.3.0 + golang.org/x/sys v0.0.0-20210110051926-789bb1bd4061 // indirect + gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect +) + +go 1.13 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..019dd47 --- /dev/null +++ b/go.sum @@ -0,0 +1,58 @@ +github.com/BurntSushi/toml v0.3.1 h1:WXkYYl6Yr3qBf1K79EBnL4mak0OimBfB0XUf9Vl28OQ= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/coreos/go-semver v0.3.0 h1:wkHLiw0WNATZnSG7epLsujiMCgPAc9xhjJ4tgnAxmfM= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM= +github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/drone-plugins/drone-plugin-lib v0.4.0 h1:qywEYGhquUuid6zNLmKia8CWY1TUa8jPQQ/G9ozfAmc= +github.com/drone-plugins/drone-plugin-lib v0.4.0/go.mod h1:EgqogX38GoJFtckeSQyhBJYX8P+KWBPhdprAVvyRxF8= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= +github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.7.0 h1:ShrD1U9pZB12TX0cVy0DtePoCH97K8EtX+mg7ZARUtM= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= +github.com/urfave/cli/v2 v2.3.0 h1:qph92Y649prgesehzOrQjdWyxFOp/QVM+6imKHad91M= +github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210110051926-789bb1bd4061 h1:DQmQoKxQWtyybCtX/3dIuDBcAhFszqq8YiNeS6sNu1c= +golang.org/x/sys v0.0.0-20210110051926-789bb1bd4061/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3 h1:fvjTMHxHEw/mxHbtzPi3JCcKXQRAnQTBRo6YCJSVHKI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= diff --git a/plugin/daemon.go b/plugin/daemon.go new file mode 100644 index 0000000..0705fb5 --- /dev/null +++ b/plugin/daemon.go @@ -0,0 +1,25 @@ +package plugin + +import ( + "io/ioutil" + "os" +) + +const dockerExe = "/usr/local/bin/docker" +const dockerdExe = "/usr/local/bin/dockerd" +const dockerHome = "/root/.docker/" + +func (p Plugin) startDaemon() { + cmd := commandDaemon(p.settings.Daemon) + if p.settings.Daemon.Debug { + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + } else { + cmd.Stdout = ioutil.Discard + cmd.Stderr = ioutil.Discard + } + go func() { + trace(cmd) + cmd.Run() + }() +} diff --git a/plugin/docker.go b/plugin/docker.go new file mode 100644 index 0000000..97a6986 --- /dev/null +++ b/plugin/docker.go @@ -0,0 +1,217 @@ +package plugin + +import ( + "fmt" + "os" + "os/exec" + "strings" + + "github.com/urfave/cli/v2" +) + +// helper function to create the docker login command. +func commandLogin(login Login) *exec.Cmd { + if login.Email != "" { + return commandLoginEmail(login) + } + return exec.Command( + dockerExe, "login", + "-u", login.Username, + "-p", login.Password, + login.Registry, + ) +} + +// helper to check if args match "docker pull " +func isCommandPull(args []string) bool { + return len(args) > 2 && args[1] == "pull" +} + +func commandPull(repo string) *exec.Cmd { + return exec.Command(dockerExe, "pull", repo) +} + +func commandLoginEmail(login Login) *exec.Cmd { + return exec.Command( + dockerExe, "login", + "-u", login.Username, + "-p", login.Password, + "-e", login.Email, + login.Registry, + ) +} + +// helper function to create the docker info command. +func commandVersion() *exec.Cmd { + return exec.Command(dockerExe, "version") +} + +// helper function to create the docker info command. +func commandInfo() *exec.Cmd { + return exec.Command(dockerExe, "info") +} + +// helper function to create the docker build command. +func commandBuild(build Build) *exec.Cmd { + args := []string{ + "build", + "--rm=true", + "-f", build.Dockerfile, + "-t", build.Name, + } + + args = append(args, build.Context) + if build.Squash { + args = append(args, "--squash") + } + if build.Compress { + args = append(args, "--compress") + } + if build.Pull { + args = append(args, "--pull=true") + } + if build.NoCache { + args = append(args, "--no-cache") + } + for _, arg := range build.CacheFrom.Value() { + args = append(args, "--cache-from", arg) + } + for _, arg := range build.ArgsEnv.Value() { + addProxyValue(&build, arg) + } + for _, arg := range build.Args.Value() { + args = append(args, "--build-arg", arg) + } + for _, host := range build.AddHost.Value() { + args = append(args, "--add-host", host) + } + if build.Target != "" { + args = append(args, "--target", build.Target) + } + if build.Quiet { + args = append(args, "--quiet") + } + + return exec.Command(dockerExe, args...) +} + +// helper function to add proxy values from the environment +func addProxyBuildArgs(build *Build) { + addProxyValue(build, "http_proxy") + addProxyValue(build, "https_proxy") + addProxyValue(build, "no_proxy") +} + +// helper function to add the upper and lower case version of a proxy value. +func addProxyValue(build *Build, key string) { + value := getProxyValue(key) + + if len(value) > 0 && !hasProxyBuildArg(build, key) { + build.Args = *cli.NewStringSlice(append(build.Args.Value(), fmt.Sprintf("%s=%s", key, value))...) + build.Args = *cli.NewStringSlice(append(build.Args.Value(), fmt.Sprintf("%s=%s", strings.ToUpper(key), value))...) + } +} + +// helper function to get a proxy value from the environment. +// +// assumes that the upper and lower case versions of are the same. +func getProxyValue(key string) string { + value := os.Getenv(key) + + if len(value) > 0 { + return value + } + + return os.Getenv(strings.ToUpper(key)) +} + +// helper function that looks to see if a proxy value was set in the build args. +func hasProxyBuildArg(build *Build, key string) bool { + keyUpper := strings.ToUpper(key) + + for _, s := range build.Args.Value() { + if strings.HasPrefix(s, key) || strings.HasPrefix(s, keyUpper) { + return true + } + } + + return false +} + +// helper function to create the docker tag command. +func commandTag(build Build, tag string) *exec.Cmd { + var ( + source = build.Name + target = fmt.Sprintf("%s:%s", build.Repo, tag) + ) + return exec.Command( + dockerExe, "tag", source, target, + ) +} + +// helper function to create the docker push command. +func commandPush(build Build, tag string) *exec.Cmd { + target := fmt.Sprintf("%s:%s", build.Repo, tag) + return exec.Command(dockerExe, "push", target) +} + +// helper function to create the docker daemon command. +func commandDaemon(daemon Daemon) *exec.Cmd { + args := []string{ + "--data-root", daemon.StoragePath, + "--host=unix:///var/run/docker.sock", + } + + if daemon.StorageDriver != "" { + args = append(args, "-s", daemon.StorageDriver) + } + if daemon.Insecure && daemon.Registry != "" { + args = append(args, "--insecure-registry", daemon.Registry) + } + if daemon.IPv6 { + args = append(args, "--ipv6") + } + if len(daemon.Mirror) != 0 { + args = append(args, "--registry-mirror", daemon.Mirror) + } + if len(daemon.Bip) != 0 { + args = append(args, "--bip", daemon.Bip) + } + for _, dns := range daemon.DNS.Value() { + args = append(args, "--dns", dns) + } + for _, dnsSearch := range daemon.DNSSearch.Value() { + args = append(args, "--dns-search", dnsSearch) + } + if len(daemon.MTU) != 0 { + args = append(args, "--mtu", daemon.MTU) + } + if daemon.Experimental { + args = append(args, "--experimental") + } + return exec.Command(dockerdExe, args...) +} + +// helper to check if args match "docker prune" +func isCommandPrune(args []string) bool { + return len(args) > 3 && args[2] == "prune" +} + +func commandPrune() *exec.Cmd { + return exec.Command(dockerExe, "system", "prune", "-f") +} + +// helper to check if args match "docker rmi" +func isCommandRmi(args []string) bool { + return len(args) > 2 && args[1] == "rmi" +} + +func commandRmi(tag string) *exec.Cmd { + return exec.Command(dockerExe, "rmi", tag) +} + +// trace writes each command to stdout with the command wrapped in an xml +// tag so that it can be extracted and displayed in the logs. +func trace(cmd *exec.Cmd) { + fmt.Fprintf(os.Stdout, "+ %s\n", strings.Join(cmd.Args, " ")) +} diff --git a/plugin/docker_test.go b/plugin/docker_test.go new file mode 100644 index 0000000..b0736c3 --- /dev/null +++ b/plugin/docker_test.go @@ -0,0 +1 @@ +package plugin diff --git a/plugin/impl.go b/plugin/impl.go new file mode 100644 index 0000000..39ca6cd --- /dev/null +++ b/plugin/impl.go @@ -0,0 +1,207 @@ +package plugin + +import ( + "fmt" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "time" + + "github.com/sirupsen/logrus" + "github.com/urfave/cli/v2" +) + +// Daemon defines Docker daemon parameters. +type Daemon struct { + Registry string // Docker registry + Mirror string // Docker registry mirror + Insecure bool // Docker daemon enable insecure registries + StorageDriver string // Docker daemon storage driver + StoragePath string // Docker daemon storage path + Disabled bool // DOcker daemon is disabled (already running) + Debug bool // Docker daemon started in debug mode + Bip string // Docker daemon network bridge IP address + DNS cli.StringSlice // Docker daemon dns server + DNSSearch cli.StringSlice // Docker daemon dns search domain + MTU string // Docker daemon mtu setting + IPv6 bool // Docker daemon IPv6 networking + Experimental bool // Docker daemon enable experimental mode +} + +// Login defines Docker login parameters. +type Login struct { + Registry string // Docker registry address + Username string // Docker registry username + Password string // Docker registry password + Email string // Docker registry email + Config string // Docker Auth Config +} + +// Build defines Docker build parameters. +type Build struct { + Remote string // Git remote URL + Name string // Git commit sha used as docker default named tag + Ref string // Git commit ref + Branch string // Git repository branch + Dockerfile string // Docker build Dockerfile + Context string // Docker build context + TagsAuto bool // Docker build auto tag + TagsSuffix string // Docker build tags with suffix + Tags cli.StringSlice // Docker build tags + Args cli.StringSlice // Docker build args + ArgsEnv cli.StringSlice // Docker build args from env + Target string // Docker build target + Squash bool // Docker build squash + Pull bool // Docker build pull + CacheFrom cli.StringSlice // Docker build cache-from + Compress bool // Docker build compress + Repo string // Docker build repository + NoCache bool // Docker build no-cache + AddHost cli.StringSlice // Docker build add-host + Quiet bool // Docker build quiet +} + +// Settings for the Plugin. +type Settings struct { + Daemon Daemon + Login Login + Build Build + Dryrun bool + Cleanup bool +} + +// Validate handles the settings validation of the plugin. +func (p *Plugin) Validate() error { + p.settings.Build.Name = "00000000" + if p.pipeline.Commit.SHA != "" { + p.settings.Build.Name = p.pipeline.Commit.SHA + } + + p.settings.Build.Branch = p.pipeline.Repo.Branch + p.settings.Build.Ref = p.pipeline.Commit.Ref + p.settings.Daemon.Registry = p.settings.Login.Registry + + if p.settings.Build.TagsAuto { + // return true if tag event or default branch + if UseDefaultTag( + p.settings.Build.Ref, + p.settings.Build.Branch, + ) { + tag, err := DefaultTagSuffix( + p.settings.Build.Ref, + p.settings.Build.TagsSuffix, + ) + if err != nil { + logrus.Printf("cannot build docker image for %s, invalid semantic version", p.settings.Build.Ref) + return err + } + p.settings.Build.Tags = *cli.NewStringSlice(tag...) + } else { + logrus.Printf("skipping automated docker build for %s", p.settings.Build.Ref) + return nil + } + } + + return nil +} + +// Execute provides the implementation of the plugin. +func (p *Plugin) Execute() error { + // start the Docker daemon server + if !p.settings.Daemon.Disabled { + p.startDaemon() + } + + // poll the docker daemon until it is started. This ensures the daemon is + // ready to accept connections before we proceed. + for i := 0; i < 15; i++ { + cmd := commandInfo() + err := cmd.Run() + if err == nil { + break + } + time.Sleep(time.Second * 1) + } + + // Create Auth Config File + if p.settings.Login.Config != "" { + os.MkdirAll(dockerHome, 0600) + + path := filepath.Join(dockerHome, "config.json") + err := ioutil.WriteFile(path, []byte(p.settings.Login.Config), 0600) + if err != nil { + return fmt.Errorf("error writing config.json: %s", err) + } + } + + // login to the Docker registry + if p.settings.Login.Password != "" { + cmd := commandLogin(p.settings.Login) + err := cmd.Run() + if err != nil { + return fmt.Errorf("error authenticating: %s", err) + } + } + + switch { + case p.settings.Login.Password != "": + fmt.Println("Detected registry credentials") + case p.settings.Login.Config != "": + fmt.Println("Detected registry credentials file") + default: + fmt.Println("Registry credentials or Docker config not provided. Guest mode enabled.") + } + + if p.settings.Build.Squash && !p.settings.Daemon.Experimental { + fmt.Println("Squash build flag is only available when Docker deamon is started with experimental flag. Ignoring...") + p.settings.Build.Squash = false + } + + // add proxy build args + addProxyBuildArgs(&p.settings.Build) + + var cmds []*exec.Cmd + cmds = append(cmds, commandVersion()) // docker version + cmds = append(cmds, commandInfo()) // docker info + + // pre-pull cache images + for _, img := range p.settings.Build.CacheFrom.Value() { + cmds = append(cmds, commandPull(img)) + } + + cmds = append(cmds, commandBuild(p.settings.Build)) // docker build + + for _, tag := range p.settings.Build.Tags.Value() { + cmds = append(cmds, commandTag(p.settings.Build, tag)) // docker tag + + if !p.settings.Dryrun { + cmds = append(cmds, commandPush(p.settings.Build, tag)) // docker push + } + } + + if p.settings.Cleanup { + cmds = append(cmds, commandRmi(p.settings.Build.Name)) // docker rmi + cmds = append(cmds, commandPrune()) // docker system prune -f + } + + // execute all commands in batch mode. + for _, cmd := range cmds { + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + trace(cmd) + + err := cmd.Run() + if err != nil && isCommandPull(cmd.Args) { + fmt.Printf("Could not pull cache-from image %s. Ignoring...\n", cmd.Args[2]) + } else if err != nil && isCommandPrune(cmd.Args) { + fmt.Printf("Could not prune system containers. Ignoring...\n") + } else if err != nil && isCommandRmi(cmd.Args) { + fmt.Printf("Could not remove image %s. Ignoring...\n", cmd.Args[2]) + } else if err != nil { + return err + } + } + + return nil +} diff --git a/plugin/plugin.go b/plugin/plugin.go new file mode 100644 index 0000000..527b881 --- /dev/null +++ b/plugin/plugin.go @@ -0,0 +1,21 @@ +package plugin + +import ( + "github.com/drone-plugins/drone-plugin-lib/drone" +) + +// Plugin implements drone.Plugin to provide the plugin implementation. +type Plugin struct { + settings Settings + pipeline drone.Pipeline + network drone.Network +} + +// New initializes a plugin from the given Settings, Pipeline, and Network. +func New(settings Settings, pipeline drone.Pipeline, network drone.Network) drone.Plugin { + return &Plugin{ + settings: settings, + pipeline: pipeline, + network: network, + } +} diff --git a/plugin/tags.go b/plugin/tags.go new file mode 100644 index 0000000..76f7b56 --- /dev/null +++ b/plugin/tags.go @@ -0,0 +1,93 @@ +package plugin + +import ( + "fmt" + "strings" + + "github.com/coreos/go-semver/semver" +) + +// DefaultTagSuffix returns a set of default suggested tags +// based on the commit ref with an attached suffix. +func DefaultTagSuffix(ref, suffix string) ([]string, error) { + tags, err := DefaultTags(ref) + if err != nil { + return nil, err + } + if len(suffix) == 0 { + return tags, nil + } + for i, tag := range tags { + if tag == "latest" { + tags[i] = suffix + } else { + tags[i] = fmt.Sprintf("%s-%s", tag, suffix) + } + } + return tags, nil +} + +func splitOff(input string, delim string) string { + parts := strings.SplitN(input, delim, 2) + + if len(parts) == 2 { + return parts[0] + } + + return input +} + +// DefaultTags returns a set of default suggested tags based on +// the commit ref. +func DefaultTags(ref string) ([]string, error) { + if !strings.HasPrefix(ref, "refs/tags/") { + return []string{"latest"}, nil + } + v := stripTagPrefix(ref) + version, err := semver.NewVersion(v) + if err != nil { + return []string{"latest"}, err + } + if version.PreRelease != "" || version.Metadata != "" { + return []string{ + version.String(), + }, nil + } + + v = stripTagPrefix(ref) + v = splitOff(splitOff(v, "+"), "-") + dotParts := strings.SplitN(v, ".", 3) + + if version.Major == 0 { + return []string{ + fmt.Sprintf("%0*d.%0*d", len(dotParts[0]), version.Major, len(dotParts[1]), version.Minor), + fmt.Sprintf("%0*d.%0*d.%0*d", len(dotParts[0]), version.Major, len(dotParts[1]), version.Minor, len(dotParts[2]), version.Patch), + }, nil + } + return []string{ + fmt.Sprintf("%0*d", len(dotParts[0]), version.Major), + fmt.Sprintf("%0*d.%0*d", len(dotParts[0]), version.Major, len(dotParts[1]), version.Minor), + fmt.Sprintf("%0*d.%0*d.%0*d", len(dotParts[0]), version.Major, len(dotParts[1]), version.Minor, len(dotParts[2]), version.Patch), + }, nil +} + +// UseDefaultTag for keep only default branch for latest tag +func UseDefaultTag(ref, defaultBranch string) bool { + if strings.HasPrefix(ref, "refs/tags/") { + return true + } + if stripHeadPrefix(ref) == defaultBranch { + return true + } + return false +} + +func stripHeadPrefix(ref string) string { + return strings.TrimPrefix(ref, "refs/heads/") +} + +func stripTagPrefix(ref string) string { + ref = strings.TrimPrefix(ref, "refs/tags/") + ref = strings.TrimPrefix(ref, "v") + return ref +} diff --git a/plugin/tags_test.go b/plugin/tags_test.go new file mode 100644 index 0000000..89b65da --- /dev/null +++ b/plugin/tags_test.go @@ -0,0 +1,197 @@ +package plugin + +import ( + "reflect" + "testing" +) + +func Test_stripTagPrefix(t *testing.T) { + var tests = []struct { + Before string + After string + }{ + {"refs/tags/1.0.0", "1.0.0"}, + {"refs/tags/v1.0.0", "1.0.0"}, + {"v1.0.0", "1.0.0"}, + } + + for _, test := range tests { + got, want := stripTagPrefix(test.Before), test.After + if got != want { + t.Errorf("Got tag %s, want %s", got, want) + } + } +} + +func TestDefaultTags(t *testing.T) { + var tests = []struct { + Before string + After []string + }{ + {"", []string{"latest"}}, + {"refs/heads/master", []string{"latest"}}, + {"refs/tags/0.9.0", []string{"0.9", "0.9.0"}}, + {"refs/tags/1.0.0", []string{"1", "1.0", "1.0.0"}}, + {"refs/tags/v1.0.0", []string{"1", "1.0", "1.0.0"}}, + {"refs/tags/v1.0.0-alpha.1", []string{"1.0.0-alpha.1"}}, + } + + for _, test := range tests { + tags, err := DefaultTags(test.Before) + if err != nil { + t.Error(err) + continue + } + got, want := tags, test.After + if !reflect.DeepEqual(got, want) { + t.Errorf("Got tag %v, want %v", got, want) + } + } +} + +func TestDefaultTagsError(t *testing.T) { + var tests = []string{ + "refs/tags/x1.0.0", + "refs/tags/20190203", + } + + for _, test := range tests { + _, err := DefaultTags(test) + if err == nil { + t.Errorf("Expect tag error for %s", test) + } + } +} + +func TestDefaultTagSuffix(t *testing.T) { + var tests = []struct { + Before string + Suffix string + After []string + }{ + // without suffix + { + After: []string{"latest"}, + }, + { + Before: "refs/tags/v1.0.0", + After: []string{ + "1", + "1.0", + "1.0.0", + }, + }, + // with suffix + { + Suffix: "linux-amd64", + After: []string{"linux-amd64"}, + }, + { + Before: "refs/tags/v1.0.0", + Suffix: "linux-amd64", + After: []string{ + "1-linux-amd64", + "1.0-linux-amd64", + "1.0.0-linux-amd64", + }, + }, + { + Suffix: "nanoserver", + After: []string{"nanoserver"}, + }, + { + Before: "refs/tags/v1.9.2", + Suffix: "nanoserver", + After: []string{ + "1-nanoserver", + "1.9-nanoserver", + "1.9.2-nanoserver", + }, + }, + { + Before: "refs/tags/v18.06.0", + Suffix: "nanoserver", + After: []string{ + "18-nanoserver", + "18.06-nanoserver", + "18.06.0-nanoserver", + }, + }, + } + + for _, test := range tests { + tag, err := DefaultTagSuffix(test.Before, test.Suffix) + if err != nil { + t.Error(err) + continue + } + got, want := tag, test.After + if !reflect.DeepEqual(got, want) { + t.Errorf("Got tag %v, want %v", got, want) + } + } +} + +func Test_stripHeadPrefix(t *testing.T) { + type args struct { + ref string + } + tests := []struct { + args args + want string + }{ + { + args: args{ + ref: "refs/heads/master", + }, + want: "master", + }, + } + for _, tt := range tests { + if got := stripHeadPrefix(tt.args.ref); got != tt.want { + t.Errorf("stripHeadPrefix() = %v, want %v", got, tt.want) + } + } +} + +func TestUseDefaultTag(t *testing.T) { + type args struct { + ref string + defaultBranch string + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "latest tag for default branch", + args: args{ + ref: "refs/heads/master", + defaultBranch: "master", + }, + want: true, + }, + { + name: "build from tags", + args: args{ + ref: "refs/tags/v1.0.0", + defaultBranch: "master", + }, + want: true, + }, + { + name: "skip build for not default branch", + args: args{ + ref: "refs/heads/develop", + defaultBranch: "master", + }, + want: false, + }, + } + for _, tt := range tests { + if got := UseDefaultTag(tt.args.ref, tt.args.defaultBranch); got != tt.want { + t.Errorf("%q. UseDefaultTag() = %v, want %v", tt.name, got, tt.want) + } + } +} diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..bf73d46 --- /dev/null +++ b/renovate.json @@ -0,0 +1,4 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": ["github>thegeeklab/renovate-presets"] +}