├── .chglog ├── CHANGELOG.tpl.md └── config.yml ├── .dictionary ├── .github └── settings.yml ├── .gitignore ├── .golangci.yml ├── .markdownlint.yml ├── .prettierignore ├── CONTRIBUTING.md ├── Corefile ├── Dockerfile.multiarch ├── LICENSE ├── Makefile ├── README.md ├── _docs ├── content │ └── _index.md └── data │ └── data.yaml ├── cmd └── drone-docker-buildx │ ├── config.go │ └── main.go ├── go.mod ├── go.sum └── plugin ├── coredns.go ├── daemon.go ├── docker.go ├── impl.go ├── plugin.go ├── tags.go └── tags_test.go /.chglog/CHANGELOG.tpl.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | {{ range .Versions -}} 4 | ## {{ if .Tag.Previous }}[{{ .Tag.Name }}]({{ $.Info.RepositoryURL }}/compare/{{ .Tag.Previous.Name }}...{{ .Tag.Name }}){{ else }}{{ .Tag.Name }}{{ end }} ({{ datetime "2006-01-02" .Tag.Date }}) 5 | 6 | {{ range .CommitGroups -}} 7 | ### {{ .Title }} 8 | 9 | {{ range .Commits -}} 10 | - {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ (regexReplaceAll "(.*)/issues/(.*)" (regexReplaceAll "(Co-\\w*-by.*)" .Subject "") "${1}/pull/${2}") | trim }} 11 | {{ end }} 12 | {{- end -}} 13 | 14 | {{- if .NoteGroups -}} 15 | {{ range .NoteGroups -}} 16 | ### {{ .Title }} 17 | 18 | {{ range .Notes }} 19 | {{ .Body }} 20 | {{ end }} 21 | {{ end -}} 22 | {{ end -}} 23 | {{ end -}} 24 | -------------------------------------------------------------------------------- /.chglog/config.yml: -------------------------------------------------------------------------------- 1 | style: github 2 | template: CHANGELOG.tpl.md 3 | info: 4 | title: CHANGELOG 5 | repository_url: https://github.com/thegeeklab/drone-docker-buildx 6 | options: 7 | commit_groups: 8 | title_maps: 9 | feat: Features 10 | fix: Bug Fixes 11 | perf: Performance Improvements 12 | refactor: Code Refactoring 13 | chore: Others 14 | test: Testing 15 | ci: CI Pipeline 16 | docs: Documentation 17 | header: 18 | pattern: "^(\\w*)(?:\\(([\\w\\$\\.\\-\\*\\s]*)\\))?\\:\\s(.*)$" 19 | pattern_maps: 20 | - Type 21 | - Scope 22 | - Subject 23 | notes: 24 | keywords: 25 | - BREAKING CHANGE 26 | -------------------------------------------------------------------------------- /.dictionary: -------------------------------------------------------------------------------- 1 | url 2 | ip 3 | mtu 4 | dns 5 | ipv6 6 | buildkit 7 | json 8 | config 9 | dockerfile 10 | og 11 | gzip 12 | toml 13 | config 14 | host:ip 15 | drone-docker-buildx 16 | multiarch 17 | buildx 18 | DockerHub 19 | ECR 20 | GHCR 21 | -------------------------------------------------------------------------------- /.github/settings.yml: -------------------------------------------------------------------------------- 1 | repository: 2 | name: drone-docker-buildx 3 | description: Drone plugin to build multiarch Docker images with buildx 4 | homepage: https://drone-plugin-index.geekdocs.de/plugins/drone-docker-buildx 5 | topics: drone, drone-plugin 6 | 7 | private: false 8 | has_issues: true 9 | has_wiki: false 10 | has_downloads: true 11 | 12 | default_branch: main 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: documentation 23 | color: 0075ca 24 | description: Improvements or additions to documentation 25 | - name: duplicate 26 | color: cfd3d7 27 | description: This issue or pull request already exists 28 | - name: enhancement 29 | color: a2eeef 30 | description: New feature or request 31 | - name: good first issue 32 | color: 7057ff 33 | description: Good for newcomers 34 | - name: help wanted 35 | color: 008672 36 | description: Extra attention is needed 37 | - name: invalid 38 | color: e4e669 39 | description: This doesn't seem right 40 | - name: question 41 | color: d876e3 42 | description: Further information is requested 43 | - name: wontfix 44 | color: ffffff 45 | description: This will not be worked on 46 | 47 | branches: 48 | - name: main 49 | protection: 50 | required_pull_request_reviews: null 51 | required_status_checks: 52 | strict: false 53 | contexts: 54 | - continuous-integration/drone/pr 55 | enforce_admins: true 56 | required_linear_history: true 57 | restrictions: null 58 | - name: docs 59 | protection: 60 | required_pull_request_reviews: null 61 | required_status_checks: null 62 | enforce_admins: true 63 | required_linear_history: true 64 | restrictions: 65 | apps: [] 66 | users: [] 67 | teams: 68 | - bot 69 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /dist/ 2 | /release/ 3 | /drone-docker-buildx* 4 | 5 | coverage.out 6 | CHANGELOG.md 7 | -------------------------------------------------------------------------------- /.golangci.yml: -------------------------------------------------------------------------------- 1 | linters: 2 | enable-all: false 3 | disable-all: true 4 | enable: 5 | - errcheck 6 | - gosimple 7 | - govet 8 | - ineffassign 9 | - staticcheck 10 | - typecheck 11 | - unused 12 | - asasalint 13 | - asciicheck 14 | - bidichk 15 | - bodyclose 16 | - containedctx 17 | - contextcheck 18 | - decorder 19 | - dogsled 20 | - dupl 21 | - dupword 22 | - durationcheck 23 | - errchkjson 24 | - errname 25 | - errorlint 26 | - execinquery 27 | - exhaustive 28 | - exportloopref 29 | - forcetypeassert 30 | - ginkgolinter 31 | - gocheckcompilerdirectives 32 | - gochecknoglobals 33 | - gochecknoinits 34 | - gocognit 35 | - goconst 36 | - gocritic 37 | - gocyclo 38 | - godot 39 | - godox 40 | - goerr113 41 | - gofmt 42 | - gofumpt 43 | - goheader 44 | - goimports 45 | - gomnd 46 | - gomoddirectives 47 | - gomodguard 48 | - goprintffuncname 49 | - gosec 50 | - grouper 51 | - importas 52 | - interfacebloat 53 | - ireturn 54 | - lll 55 | - loggercheck 56 | - maintidx 57 | - makezero 58 | - misspell 59 | - musttag 60 | - nakedret 61 | - nestif 62 | - nilerr 63 | - nilnil 64 | - nlreturn 65 | - noctx 66 | - nolintlint 67 | - nonamedreturns 68 | - nosprintfhostport 69 | - prealloc 70 | - predeclared 71 | - promlinter 72 | - reassign 73 | - revive 74 | # - rowserrcheck 75 | # - sqlclosecheck 76 | # - structcheck 77 | - stylecheck 78 | - tagliatelle 79 | - tenv 80 | - testableexamples 81 | - thelper 82 | - tparallel 83 | - unconvert 84 | - unparam 85 | - usestdlibvars 86 | # - wastedassign 87 | - whitespace 88 | - wsl 89 | fast: false 90 | 91 | run: 92 | timeout: 3m 93 | 94 | linters-settings: 95 | gofumpt: 96 | extra-rules: true 97 | lang-version: "1.20" 98 | -------------------------------------------------------------------------------- /.markdownlint.yml: -------------------------------------------------------------------------------- 1 | --- 2 | default: True 3 | MD013: False 4 | MD041: False 5 | MD004: 6 | style: dash 7 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .drone.yml 2 | *.tpl.md 3 | LICENSE 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | ## Security 4 | 5 | If you think you have found a **security issue**, please do not mention it in this repository. 6 | Instead, send an email to `security@thegeeklab.de` with as many details as possible so it can be handled confidential. 7 | 8 | ## Bug Reports and Feature Requests 9 | 10 | If you have found a **bug** or have a **feature request** please use the search first in case a similar issue already exists. 11 | If not, please create an issue in this repository 12 | 13 | ## Code 14 | 15 | If you would like to fix a bug or implement a feature, please fork the repository and create a Pull Request. 16 | 17 | Before you start any Pull Request, it is recommended that you create an issue to discuss first if you have any 18 | doubts about requirement or implementation. That way you can be sure that the maintainer(s) agree on what to change and how, 19 | and you can hopefully get a quick merge afterwards. 20 | 21 | Pull Requests can only be merged once all status checks are green. 22 | 23 | ## Do not force push to your Pull Request branch 24 | 25 | Please do not force push to your Pull Requests branch after you have created your Pull Request, as doing so makes it harder for us to review your work. 26 | Pull Requests will always be squashed by us when we merge your work. Commit as many times as you need in your Pull Request branch. 27 | 28 | ## Re-requesting a review 29 | 30 | Please do not ping your reviewer(s) by mentioning them in a new comment. Instead, use the re-request review functionality. 31 | Read more about this in the [GitHub docs, Re-requesting a review](https://docs.github.com/en/free-pro-team@latest/github/collaborating-with-issues-and-pull-requests/incorporating-feedback-in-your-pull-request#re-requesting-a-review). 32 | -------------------------------------------------------------------------------- /Corefile: -------------------------------------------------------------------------------- 1 | .:53 { 2 | forward . /etc/resolv.conf 3 | } 4 | -------------------------------------------------------------------------------- /Dockerfile.multiarch: -------------------------------------------------------------------------------- 1 | FROM --platform=$BUILDPLATFORM golang:1.20@sha256:741d6f9bcab778441efe05c8e4369d4f8ff56c9a635a97d77f55d8b0ec62f907 as build 2 | 3 | ARG TARGETOS 4 | ARG TARGETARCH 5 | 6 | ADD . /src 7 | WORKDIR /src 8 | 9 | RUN make build 10 | 11 | FROM docker:24.0-dind@sha256:020562d22f11c27997e00da910ed6b580d93094bc25841cb87aacab4ced4a882 12 | 13 | LABEL maintainer="Robert Kaussow " 14 | LABEL org.opencontainers.image.authors="Robert Kaussow " 15 | LABEL org.opencontainers.image.title="drone-docker-buildx" 16 | LABEL org.opencontainers.image.url="https://github.com/thegeeklab/drone-docker-buildx" 17 | LABEL org.opencontainers.image.source="https://github.com/thegeeklab/drone-docker-buildx" 18 | LABEL org.opencontainers.image.documentation="https://github.com/thegeeklab/drone-docker-buildx" 19 | 20 | ARG TARGETOS 21 | ARG TARGETARCH 22 | ARG BUILDX_VERSION 23 | 24 | # renovate: datasource=github-releases depName=docker/buildx 25 | ENV BUILDX_VERSION="${BUILDX_VERSION:-v0.11.2}" 26 | 27 | ENV DOCKER_HOST=unix:///var/run/docker.sock 28 | 29 | RUN apk --update add --virtual .build-deps curl && \ 30 | apk --update add --no-cache git coredns && \ 31 | mkdir -p /usr/lib/docker/cli-plugins/ && \ 32 | curl -SsL -o /usr/lib/docker/cli-plugins/docker-buildx \ 33 | "https://github.com/docker/buildx/releases/download/v${BUILDX_VERSION##v}/buildx-v${BUILDX_VERSION##v}.${TARGETOS:-linux}-${TARGETARCH:-amd64}" && \ 34 | chmod 755 /usr/lib/docker/cli-plugins/docker-buildx && \ 35 | apk del .build-deps && \ 36 | rm -rf /var/cache/apk/* && \ 37 | rm -rf /tmp/* 38 | 39 | COPY --from=build /src/Corefile /etc/coredns/Corefile 40 | COPY --from=build /src/dist/drone-docker-buildx /bin/drone-docker-buildx 41 | ENTRYPOINT ["/usr/local/bin/dockerd-entrypoint.sh", "drone-docker-buildx"] 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2022 Robert Kaussow 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # renovate: datasource=github-releases depName=mvdan/gofumpt 2 | GOFUMPT_PACKAGE_VERSION := v0.5.0 3 | # renovate: datasource=github-releases depName=golangci/golangci-lint 4 | GOLANGCI_LINT_PACKAGE_VERSION := v1.54.2 5 | 6 | EXECUTABLE := drone-docker-buildx 7 | 8 | DIST := dist 9 | DIST_DIRS := $(DIST) 10 | IMPORT := github.com/thegeeklab/$(EXECUTABLE) 11 | 12 | GO ?= go 13 | CWD ?= $(shell pwd) 14 | PACKAGES ?= $(shell go list ./...) 15 | SOURCES ?= $(shell find . -name "*.go" -type f) 16 | 17 | GOFUMPT_PACKAGE ?= mvdan.cc/gofumpt@$(GOFUMPT_PACKAGE_VERSION) 18 | GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/cmd/golangci-lint@$(GOLANGCI_LINT_PACKAGE_VERSION) 19 | XGO_PACKAGE ?= src.techknowlogick.com/xgo@latest 20 | 21 | GENERATE ?= 22 | XGO_VERSION := go-1.20.x 23 | XGO_TARGETS ?= linux/amd64,linux/arm64 24 | 25 | TARGETOS ?= linux 26 | TARGETARCH ?= amd64 27 | ifneq ("$(TARGETVARIANT)","") 28 | GOARM ?= $(subst v,,$(TARGETVARIANT)) 29 | endif 30 | TAGS ?= netgo 31 | 32 | ifndef VERSION 33 | ifneq ($(DRONE_TAG),) 34 | VERSION ?= $(subst v,,$(DRONE_TAG)) 35 | else 36 | VERSION ?= $(shell git rev-parse --short HEAD) 37 | endif 38 | endif 39 | 40 | ifndef DATE 41 | DATE := $(shell date -u +"%Y-%m-%dT%H:%M:%S%z") 42 | endif 43 | 44 | LDFLAGS += -s -w -X "main.BuildVersion=$(VERSION)" -X "main.BuildDate=$(DATE)" 45 | 46 | .PHONY: all 47 | all: clean build 48 | 49 | .PHONY: clean 50 | clean: 51 | $(GO) clean -i ./... 52 | rm -rf $(DIST_DIRS) 53 | 54 | .PHONY: fmt 55 | fmt: 56 | $(GO) run $(GOFUMPT_PACKAGE) -extra -w $(SOURCES) 57 | 58 | .PHONY: golangci-lint 59 | golangci-lint: 60 | $(GO) run $(GOLANGCI_LINT_PACKAGE) run 61 | 62 | .PHONY: lint 63 | lint: golangci-lint 64 | 65 | .PHONY: generate 66 | generate: 67 | $(GO) generate $(GENERATE) 68 | 69 | .PHONY: test 70 | test: 71 | $(GO) test -v -coverprofile coverage.out $(PACKAGES) 72 | 73 | .PHONY: build 74 | build: $(DIST)/$(EXECUTABLE) 75 | 76 | $(DIST)/$(EXECUTABLE): $(SOURCES) 77 | GOOS=$(TARGETOS) GOARCH=$(TARGETARCH) GOARM=$(GOARM) $(GO) build -v -tags '$(TAGS)' -ldflags '-extldflags "-static" $(LDFLAGS)' -o $@ ./cmd/$(EXECUTABLE) 78 | 79 | $(DIST_DIRS): 80 | mkdir -p $(DIST_DIRS) 81 | 82 | .PHONY: xgo 83 | xgo: | $(DIST_DIRS) 84 | $(GO) run $(XGO_PACKAGE) -go $(XGO_VERSION) -v -ldflags '-extldflags "-static" $(LDFLAGS)' -tags '$(TAGS)' -targets '$(XGO_TARGETS)' -out $(EXECUTABLE) --pkg cmd/$(EXECUTABLE) . 85 | cp /build/* $(CWD)/$(DIST) 86 | ls -l $(CWD)/$(DIST) 87 | 88 | .PHONY: checksum 89 | checksum: 90 | cd $(DIST); $(foreach file,$(wildcard $(DIST)/$(EXECUTABLE)-*),sha256sum $(notdir $(file)) > $(notdir $(file)).sha256;) 91 | ls -l $(CWD)/$(DIST) 92 | 93 | .PHONY: release 94 | release: xgo checksum 95 | 96 | .PHONY: deps 97 | deps: 98 | $(GO) mod download 99 | $(GO) install $(GOFUMPT_PACKAGE) 100 | $(GO) install $(GOLANGCI_LINT_PACKAGE) 101 | $(GO) install $(XGO_PACKAGE) 102 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # drone-docker-buildx 2 | 3 | DISCONTINUED: Drone plugin to build multiarch Docker images with buildx 4 | 5 | [![Build Status](https://img.shields.io/drone/build/thegeeklab/drone-docker-buildx?logo=drone&server=https%3A%2F%2Fdrone.thegeeklab.de)](https://drone.thegeeklab.de/thegeeklab/drone-docker-buildx) 6 | [![Docker Hub](https://img.shields.io/badge/dockerhub-latest-blue.svg?logo=docker&logoColor=white)](https://hub.docker.com/r/thegeeklab/drone-docker-buildx) 7 | [![Quay.io](https://img.shields.io/badge/quay-latest-blue.svg?logo=docker&logoColor=white)](https://quay.io/repository/thegeeklab/drone-docker-buildx) 8 | [![Go Report Card](https://goreportcard.com/badge/github.com/thegeeklab/drone-docker-buildx)](https://goreportcard.com/report/github.com/thegeeklab/drone-docker-buildx) 9 | [![GitHub contributors](https://img.shields.io/github/contributors/thegeeklab/drone-docker-buildx)](https://github.com/thegeeklab/drone-docker-buildx/graphs/contributors) 10 | [![Source: GitHub](https://img.shields.io/badge/source-github-blue.svg?logo=github&logoColor=white)](https://github.com/thegeeklab/drone-docker-buildx) 11 | [![License: Apache-2.0](https://img.shields.io/github/license/thegeeklab/drone-docker-buildx)](https://github.com/thegeeklab/drone-docker-buildx/blob/main/LICENSE) 12 | 13 | > **DISCONTINUED:** As I don't use Drone CI anymore, this project is unmaintained. If you are interested in a free and open source CI system check out [Woodpecker CI](https://woodpecker-ci.org/). 14 | 15 | Drone plugin to build multiarch Docker images with buildx. This plugin is a fork of [drone-plugins/drone-docker](https://github.com/drone-plugins/drone-docker). You can find the full documentation at [https://drone-plugin-index.geekdocs.de](https://drone-plugin-index.geekdocs.de/plugins/drone-docker-buildx). 16 | 17 | ## Versioning 18 | 19 | The tags follow the major version of Docker, e.g. `20`, and the minor and patch parts reflect the `version` of the plugin. A full example would be `20.12.5`. Minor versions can introduce breaking changes, while patch versions can be considered non-breaking. 20 | 21 | ## Community 22 | 23 | 24 | 25 | 26 | - [BitProcessor/drone-docker-buildx-ecr](https://github.com/BitProcessor/drone-docker-buildx-ecr) by [@BitProcessor](https://github.com/BitProcessor) 27 | 28 | 29 | 30 | 31 | ## Contributors 32 | 33 | Special thanks to all [contributors](https://github.com/thegeeklab/drone-docker-buildx/graphs/contributors). If you would like to contribute, please see the [instructions](https://github.com/thegeeklab/drone-docker-buildx/blob/main/CONTRIBUTING.md). 34 | 35 | ## License 36 | 37 | This project is licensed under the Apache-2.0 License - see the [LICENSE](https://github.com/thegeeklab/drone-docker-buildx/blob/main/LICENSE) file for details. 38 | -------------------------------------------------------------------------------- /_docs/content/_index.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: drone-docker-buildx 3 | --- 4 | 5 | [![Build Status](https://img.shields.io/drone/build/thegeeklab/drone-docker-buildx?logo=drone&server=https%3A%2F%2Fdrone.thegeeklab.de)](https://drone.thegeeklab.de/thegeeklab/drone-docker-buildx) 6 | [![Docker Hub](https://img.shields.io/badge/dockerhub-latest-blue.svg?logo=docker&logoColor=white)](https://hub.docker.com/r/thegeeklab/drone-docker-buildx) 7 | [![Quay.io](https://img.shields.io/badge/quay-latest-blue.svg?logo=docker&logoColor=white)](https://quay.io/repository/thegeeklab/drone-docker-buildx) 8 | [![GitHub contributors](https://img.shields.io/github/contributors/thegeeklab/drone-docker-buildx)](https://github.com/thegeeklab/drone-docker-buildx/graphs/contributors) 9 | [![Source: GitHub](https://img.shields.io/badge/source-github-blue.svg?logo=github&logoColor=white)](https://github.com/thegeeklab/drone-docker-buildx) 10 | [![License: MIT](https://img.shields.io/github/license/thegeeklab/drone-docker-buildx)](https://github.com/thegeeklab/drone-docker-buildx/blob/main/LICENSE) 11 | 12 | Drone plugin to build and publish multiarch Docker images with buildx. 13 | 14 | 15 | 16 | {{< toc >}} 17 | 18 | 19 | 20 | ## Versioning 21 | 22 | The tags follow the major version of Docker, e.g. `20`, and the minor and patch parts reflect the `version` of the plugin. A full example would be `20.12.5`. Minor versions can introduce breaking changes, while patch versions can be considered non-breaking. 23 | 24 | ## Usage 25 | 26 | {{< hint type=important >}} 27 | Be aware that the this plugin requires [privileged](https://docs.drone.io/pipeline/docker/syntax/steps/#privileged-mode) capabilities, otherwise the integrated Docker daemon is not able to start. 28 | {{< /hint >}} 29 | 30 | ```yaml 31 | kind: pipeline 32 | name: default 33 | 34 | steps: 35 | - name: docker 36 | image: thegeeklab/drone-docker-buildx:23 37 | privileged: true 38 | settings: 39 | username: octocat 40 | password: secure 41 | repo: octocat/example 42 | tags: latest 43 | ``` 44 | 45 | ### Parameters 46 | 47 | 48 | 49 | {{< propertylist name=drone-docker-buildx.data sort=name >}} 50 | 51 | 52 | 53 | ### Examples 54 | 55 | #### Push to other registries than DockerHub 56 | 57 | If the created image is to be pushed to registries other than the default DockerHub, it is necessary to set `registry` and `repo` as fully-qualified name. 58 | 59 | **GHCR:** 60 | 61 | ```yaml 62 | kind: pipeline 63 | name: default 64 | 65 | steps: 66 | - name: docker 67 | image: thegeeklab/drone-docker-buildx:23 68 | privileged: true 69 | settings: 70 | registry: ghcr.io 71 | username: octocat 72 | password: secret-access-token 73 | repo: ghcr.io/octocat/example 74 | tags: latest 75 | ``` 76 | 77 | **AWS ECR:** 78 | 79 | ```yaml 80 | kind: pipeline 81 | name: default 82 | 83 | steps: 84 | - name: docker 85 | image: thegeeklab/drone-docker-buildx:23 86 | privileged: true 87 | environment: 88 | AWS_ACCESS_KEY_ID: 89 | from_secret: aws_access_key_id 90 | AWS_SECRET_ACCESS_KEY: 91 | from_secret: aws_secret_access_key 92 | settings: 93 | registry: .dkr.ecr..amazonaws.com 94 | repo: .dkr.ecr..amazonaws.com/octocat/example 95 | tags: latest 96 | ``` 97 | 98 | ## Build 99 | 100 | Build the binary with the following command: 101 | 102 | ```shell 103 | export GOOS=linux 104 | export GOARCH=amd64 105 | export CGO_ENABLED=0 106 | export GO111MODULE=on 107 | 108 | make build 109 | ``` 110 | 111 | Build the Docker image with the following command: 112 | 113 | ```shell 114 | docker build --file docker/Dockerfile.amd64 --tag thegeeklab/drone-docker-buildx . 115 | ``` 116 | 117 | ## Test 118 | 119 | ```shell 120 | docker run --rm \ 121 | -e PLUGIN_TAG=latest \ 122 | -e PLUGIN_REPO=octocat/hello-world \ 123 | -e DRONE_COMMIT_SHA=00000000 \ 124 | -v $(pwd):$(pwd) \ 125 | -w $(pwd) \ 126 | --privileged \ 127 | thegeeklab/drone-docker-buildx --dry-run 128 | ``` 129 | -------------------------------------------------------------------------------- /_docs/data/data.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | properties: 3 | - name: dry_run 4 | description: Disable docker push. 5 | type: bool 6 | required: false 7 | 8 | - name: mirror 9 | description: Use a registry mirror to pull images. 10 | type: string 11 | required: false 12 | 13 | - name: storage_driver 14 | description: The docker daemon storage driver. 15 | type: string 16 | required: false 17 | 18 | - name: storage_path 19 | description: The docker daemon storage path. 20 | defaultValue: /var/lib/docker 21 | type: string 22 | required: false 23 | 24 | - name: bip 25 | description: Allows the docker daemon to bride IP address. 26 | type: string 27 | required: false 28 | 29 | - name: mtu 30 | description: A docker daemon custom MTU. 31 | type: string 32 | required: false 33 | 34 | - name: custom_dns 35 | description: Custom docker daemon DNS server. 36 | type: list 37 | required: false 38 | 39 | - name: custom_dns_search 40 | description: Custom docker daemon DNS search domain. 41 | type: list 42 | required: false 43 | 44 | - name: insecure 45 | description: Enable the usage of insecure registries. 46 | type: bool 47 | defaultValue: false 48 | required: false 49 | 50 | - name: ipv6 51 | description: Enable docker daemon IPv6 support. 52 | type: bool 53 | defaultValue: false 54 | required: false 55 | 56 | - name: experimental 57 | description: Enable docker daemon experimental mode. 58 | type: bool 59 | defaultValue: false 60 | required: false 61 | 62 | - name: debug 63 | description: Enable verbose debug mode for the docker daemon. 64 | type: string 65 | defaultValue: false 66 | required: false 67 | 68 | - name: daemon_off 69 | description: Disable the startup of the docker daemon. 70 | type: string 71 | defaultValue: false 72 | required: false 73 | 74 | - name: buildkit_config 75 | description: | 76 | Content of the docker buildkit toml [config](https://github.com/moby/buildkit/blob/master/docs/buildkitd.toml.md). Example: 77 | 78 | ```yaml 79 | steps: 80 | - name: Build 81 | image: thegeeklab/drone-docker-buildx:23 82 | settings: 83 | repo: example/repo 84 | buildkit_config: | 85 | [registry."registry.local:30081"] 86 | http = true 87 | insecure = true 88 | ``` 89 | type: string 90 | defaultValue: false 91 | required: false 92 | 93 | - name: dockerfile 94 | description: Set dockerfile to use for the image build. 95 | defaultValue: Dockerfile 96 | type: string 97 | required: false 98 | 99 | - name: context 100 | description: Set the path of the build context to use. 101 | defaultValue: . 102 | type: string 103 | required: false 104 | 105 | - name: named_context 106 | description: Set additional named [build contexts](https://docs.docker.com/engine/reference/commandline/buildx_build/#build-context) (e.g., name=path). 107 | type: list 108 | required: false 109 | 110 | - name: tags 111 | description: Set repository tags to use for the image. Tags can also be loaded from a `.tags` file. 112 | defaultValue: latest 113 | type: list 114 | required: false 115 | 116 | - name: auto_tag 117 | description: | 118 | Generate tag names automatically based on git branch and git tag. When this feature is enabled and the event type is `tag`, 119 | the plugin will automatically tag the image using the standard semVer convention. For example: 120 | - `1.0.0` produces docker tags `1`, `1.0`, `1.0.0` 121 | - `1.0.0-rc.1` produces docker tags `1.0.0-rc.1` 122 | When the event type is `push` and the target branch is your default branch, the plugin will automatically tag the image 123 | as `latest`. All other event types and branches are ignored. 124 | defaultValue: false 125 | type: bool 126 | required: false 127 | 128 | - name: auto_tag_suffix 129 | description: Generate tag names with the given suffix. 130 | type: string 131 | required: false 132 | 133 | - name: extra_tags 134 | description: | 135 | Set additional tags to be used for the image. Additional tags can also be loaded from an `.extratags` file. This function can be used 136 | to push images to multiple registries at once. Therefore, it is necessary to use the `config` flag to provide a configuration file 137 | that contains the authentication information for all used registries. 138 | type: list 139 | required: false 140 | 141 | - name: build_args 142 | description: Custom build arguments to pass to the build. 143 | type: list 144 | required: false 145 | 146 | - name: build_args_from_env 147 | description: Forward environment variables as custom arguments to the build. 148 | type: list 149 | required: false 150 | 151 | - name: quiet 152 | description: Enable suppression of the build output. 153 | defaultValue: false 154 | type: bool 155 | required: false 156 | 157 | - name: target 158 | description: The docker build target. 159 | type: string 160 | required: false 161 | 162 | - name: cache_from 163 | description: | 164 | Images to consider as [cache sources](https://docs.docker.com/engine/reference/commandline/buildx_build/#cache-from). To properly work, 165 | commas used in the cache source entries need to be escaped: 166 | 167 | ```yaml 168 | steps: 169 | - name: Build 170 | image: thegeeklab/drone-docker-buildx:23 171 | settings: 172 | repo: example/repo 173 | cache_from: 174 | # while using quotes, double-escaping is required 175 | - "type=registry\\\\,ref=example" 176 | - 'type=foo\\,ref=bar' 177 | ``` 178 | type: list 179 | required: false 180 | 181 | - name: cache_to 182 | description: | 183 | [Cache destination](https://docs.docker.com/engine/reference/commandline/buildx_build/#cache-to) for the build cache. 184 | type: string 185 | required: false 186 | 187 | - name: pull_image 188 | description: Enforce to pull the base image at build time. 189 | defaultValue: true 190 | type: bool 191 | required: false 192 | 193 | - name: compress 194 | description: Enable compression of the build context using gzip. 195 | defaultValue: false 196 | type: bool 197 | required: false 198 | 199 | - name: output 200 | description: | 201 | [Export action](https://docs.docker.com/engine/reference/commandline/buildx_build/#output) for the build result 202 | (format: `path` or `type=TYPE[,KEY=VALUE]`). 203 | defaultValue: false 204 | type: bool 205 | required: false 206 | 207 | - name: repo 208 | description: | 209 | Repository name for the image. If the image is to be pushed to registries other than the default DockerHub, 210 | it is necessary to set `repo` as fully-qualified name. 211 | type: string 212 | required: false 213 | 214 | - name: registry 215 | description: Docker registry to upload images. 216 | defaultValue: https://index.docker.io/v1/ 217 | type: string 218 | required: false 219 | 220 | - name: username 221 | description: Username for authentication with the registry. 222 | type: string 223 | required: false 224 | 225 | - name: password 226 | description: Password for authentication with the registry. 227 | type: string 228 | required: false 229 | 230 | - name: email 231 | description: E-Mail address for authentication with the registry. 232 | type: string 233 | required: false 234 | 235 | - name: config 236 | description: Content of the docker daemon json config. 237 | type: string 238 | required: false 239 | 240 | - name: no_cache 241 | description: Disable the usage of cached intermediate containers. 242 | defaultValue: false 243 | type: string 244 | required: false 245 | 246 | - name: add_host 247 | description: Additional `host:ip` mapping. 248 | type: list 249 | required: false 250 | 251 | - name: platforms 252 | description: Target platforms for build. 253 | type: list 254 | required: false 255 | 256 | - name: labels 257 | description: Labels to add to the image. 258 | type: list 259 | required: false 260 | 261 | - name: provenance 262 | description: Generate [provenance](https://docs.docker.com/build/attestations/slsa-provenance/) attestation for the build (shorthand for `--attest=type=provenance`). 263 | type: string 264 | required: false 265 | 266 | - name: sbom 267 | description: Generate [sbom](https://docs.docker.com/build/attestations/sbom/) attestation for the build (shorthand for `--attest type=sbom`). 268 | type: string 269 | required: false 270 | 271 | - name: secrets 272 | description: | 273 | Exposes [secrets](https://docs.docker.com/engine/reference/commandline/buildx_build/#secret) to the build. 274 | The secrets can be used by the build using `RUN --mount=type=secret` mount. 275 | 276 | ```yaml 277 | steps: 278 | - name: Build 279 | image: thegeeklab/drone-docker-buildx:23 280 | privileged: true 281 | environment: 282 | SECURE_TOKEN: 283 | from_secret: secure_token 284 | settings: 285 | secrets: 286 | # while using quotes, double-escaping is required 287 | - "id=raw_file_secret\\\\,src=file.txt" 288 | - 'id=other_raw_file_secret\\,src=other_file.txt' 289 | - "id=SECRET_TOKEN" 290 | ``` 291 | 292 | To use secrets from files a [host volume](https://docs.drone.io/pipeline/docker/syntax/volumes/host/) is required. 293 | This should be used with caution and avoided whenever possible. 294 | type: list 295 | required: false 296 | -------------------------------------------------------------------------------- /cmd/drone-docker-buildx/config.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/thegeeklab/drone-docker-buildx/plugin" 5 | "github.com/thegeeklab/drone-plugin-lib/v2/drone" 6 | "github.com/urfave/cli/v2" 7 | ) 8 | 9 | // settingsFlags has the cli.Flags for the plugin.Settings. 10 | // 11 | //nolint:maintidx 12 | func settingsFlags(settings *plugin.Settings, category string) []cli.Flag { 13 | return []cli.Flag{ 14 | &cli.BoolFlag{ 15 | Name: "dry-run", 16 | EnvVars: []string{"PLUGIN_DRY_RUN"}, 17 | Usage: "disable docker push", 18 | Destination: &settings.Dryrun, 19 | Category: category, 20 | }, 21 | &cli.StringFlag{ 22 | Name: "daemon.mirror", 23 | EnvVars: []string{"PLUGIN_MIRROR", "DOCKER_PLUGIN_MIRROR"}, 24 | Usage: "registry mirror to pull images", 25 | Destination: &settings.Daemon.Mirror, 26 | Category: category, 27 | }, 28 | &cli.StringFlag{ 29 | Name: "daemon.storage-driver", 30 | EnvVars: []string{"PLUGIN_STORAGE_DRIVER"}, 31 | Usage: "docker daemon storage driver", 32 | Destination: &settings.Daemon.StorageDriver, 33 | Category: category, 34 | }, 35 | &cli.StringFlag{ 36 | Name: "daemon.storage-path", 37 | EnvVars: []string{"PLUGIN_STORAGE_PATH"}, 38 | Usage: "docker daemon storage path", 39 | Value: "/var/lib/docker", 40 | Destination: &settings.Daemon.StoragePath, 41 | Category: category, 42 | }, 43 | &cli.StringFlag{ 44 | Name: "daemon.bip", 45 | EnvVars: []string{"PLUGIN_BIP"}, 46 | Usage: "allow the docker daemon to bride ip address", 47 | Destination: &settings.Daemon.Bip, 48 | Category: category, 49 | }, 50 | &cli.StringFlag{ 51 | Name: "daemon.mtu", 52 | EnvVars: []string{"PLUGIN_MTU"}, 53 | Usage: "docker daemon custom mtu setting", 54 | Destination: &settings.Daemon.MTU, 55 | Category: category, 56 | }, 57 | &cli.StringSliceFlag{ 58 | Name: "daemon.dns", 59 | EnvVars: []string{"PLUGIN_CUSTOM_DNS"}, 60 | Usage: "custom docker daemon dns server", 61 | Destination: &settings.Daemon.DNS, 62 | Category: category, 63 | }, 64 | &cli.StringSliceFlag{ 65 | Name: "daemon.dns-search", 66 | EnvVars: []string{"PLUGIN_CUSTOM_DNS_SEARCH"}, 67 | Usage: "custom docker daemon dns search domain", 68 | Destination: &settings.Daemon.DNSSearch, 69 | Category: category, 70 | }, 71 | &cli.BoolFlag{ 72 | Name: "daemon.insecure", 73 | EnvVars: []string{"PLUGIN_INSECURE"}, 74 | Usage: "allow the docker daemon to use insecure registries", 75 | Value: false, 76 | Destination: &settings.Daemon.Insecure, 77 | Category: category, 78 | }, 79 | &cli.BoolFlag{ 80 | Name: "daemon.ipv6", 81 | EnvVars: []string{"PLUGIN_IPV6"}, 82 | Usage: "enable docker daemon IPv6 support", 83 | Value: false, 84 | Destination: &settings.Daemon.IPv6, 85 | Category: category, 86 | }, 87 | &cli.BoolFlag{ 88 | Name: "daemon.experimental", 89 | EnvVars: []string{"PLUGIN_EXPERIMENTAL"}, 90 | Usage: "enable docker daemon experimental mode", 91 | Value: false, 92 | Destination: &settings.Daemon.Experimental, 93 | Category: category, 94 | }, 95 | &cli.BoolFlag{ 96 | Name: "daemon.debug", 97 | EnvVars: []string{"PLUGIN_DEBUG"}, 98 | Usage: "enable verbose debug mode for the docker daemon", 99 | Value: false, 100 | Destination: &settings.Daemon.Debug, 101 | Category: category, 102 | }, 103 | &cli.BoolFlag{ 104 | Name: "daemon.off", 105 | EnvVars: []string{"PLUGIN_DAEMON_OFF"}, 106 | Usage: "disable the startup of the docker daemon", 107 | Value: false, 108 | Destination: &settings.Daemon.Disabled, 109 | Category: category, 110 | }, 111 | &cli.StringFlag{ 112 | Name: "daemon.buildkit-config", 113 | EnvVars: []string{"PLUGIN_BUILDKIT_CONFIG"}, 114 | Usage: "content of the docker buildkit toml config", 115 | Destination: &settings.Daemon.BuildkitConfig, 116 | Category: category, 117 | }, 118 | &cli.StringFlag{ 119 | Name: "dockerfile", 120 | EnvVars: []string{"PLUGIN_DOCKERFILE"}, 121 | Usage: "dockerfile to use for the image build", 122 | Value: "Dockerfile", 123 | Destination: &settings.Build.Dockerfile, 124 | Category: category, 125 | }, 126 | &cli.StringFlag{ 127 | Name: "context", 128 | EnvVars: []string{"PLUGIN_CONTEXT"}, 129 | Usage: "path of the build context", 130 | Value: ".", 131 | Destination: &settings.Build.Context, 132 | Category: category, 133 | }, 134 | &cli.StringSliceFlag{ 135 | Name: "named-context", 136 | EnvVars: []string{"PLUGIN_NAMED_CONTEXT"}, 137 | Usage: "additional named build context", 138 | Destination: &settings.Build.NamedContext, 139 | Category: category, 140 | }, 141 | &cli.StringSliceFlag{ 142 | Name: "tags", 143 | EnvVars: []string{"PLUGIN_TAG", "PLUGIN_TAGS"}, 144 | Usage: "repository tags to use for the image", 145 | FilePath: ".tags", 146 | Destination: &settings.Build.Tags, 147 | Category: category, 148 | }, 149 | &cli.BoolFlag{ 150 | Name: "tags.auto", 151 | EnvVars: []string{"PLUGIN_DEFAULT_TAGS", "PLUGIN_AUTO_TAG"}, 152 | Usage: "generate tag names automatically based on git branch and git tag", 153 | Value: false, 154 | Destination: &settings.Build.TagsAuto, 155 | Category: category, 156 | }, 157 | &cli.StringFlag{ 158 | Name: "tags.suffix", 159 | EnvVars: []string{"PLUGIN_DEFAULT_SUFFIX", "PLUGIN_AUTO_TAG_SUFFIX"}, 160 | Usage: "generate tag names with the given suffix", 161 | Destination: &settings.Build.TagsSuffix, 162 | Category: category, 163 | }, 164 | &cli.StringSliceFlag{ 165 | Name: "extra.tags", 166 | EnvVars: []string{"PLUGIN_EXTRA_TAGS"}, 167 | Usage: "additional tags to use for the image including registry", 168 | FilePath: ".extratags", 169 | Destination: &settings.Build.ExtraTags, 170 | Category: category, 171 | }, 172 | &cli.StringSliceFlag{ 173 | Name: "args", 174 | EnvVars: []string{"PLUGIN_BUILD_ARGS"}, 175 | Usage: "custom build arguments for the build", 176 | Destination: &settings.Build.Args, 177 | Category: category, 178 | }, 179 | &cli.StringSliceFlag{ 180 | Name: "args-from-env", 181 | EnvVars: []string{"PLUGIN_BUILD_ARGS_FROM_ENV"}, 182 | Usage: "forward environment variables as custom arguments to the build", 183 | Destination: &settings.Build.ArgsEnv, 184 | Category: category, 185 | }, 186 | &cli.BoolFlag{ 187 | Name: "quiet", 188 | EnvVars: []string{"PLUGIN_QUIET"}, 189 | Usage: "enable suppression of the build output", 190 | Value: false, 191 | Destination: &settings.Build.Quiet, 192 | Category: category, 193 | }, 194 | &cli.StringFlag{ 195 | Name: "output", 196 | EnvVars: []string{"PLUGIN_OUTPUT"}, 197 | Usage: "export action for the build result", 198 | Destination: &settings.Build.Output, 199 | Category: category, 200 | }, 201 | &cli.StringFlag{ 202 | Name: "target", 203 | EnvVars: []string{"PLUGIN_TARGET"}, 204 | Usage: "build target to use", 205 | Destination: &settings.Build.Target, 206 | Category: category, 207 | }, 208 | &cli.GenericFlag{ 209 | Name: "cache-from", 210 | EnvVars: []string{"PLUGIN_CACHE_FROM"}, 211 | Usage: "images to consider as cache sources", 212 | Value: &drone.StringSliceFlag{}, 213 | Category: category, 214 | }, 215 | &cli.StringFlag{ 216 | Name: "cache-to", 217 | EnvVars: []string{"PLUGIN_CACHE_TO"}, 218 | Usage: "cache destination for the build cache", 219 | Destination: &settings.Build.CacheTo, 220 | Category: category, 221 | }, 222 | &cli.BoolFlag{ 223 | Name: "pull-image", 224 | EnvVars: []string{"PLUGIN_PULL_IMAGE"}, 225 | Usage: "enforce to pull base image at build time", 226 | Value: true, 227 | Destination: &settings.Build.Pull, 228 | Category: category, 229 | }, 230 | &cli.BoolFlag{ 231 | Name: "compress", 232 | EnvVars: []string{"PLUGIN_COMPRESS"}, 233 | Usage: "enable compression of the build context using gzip", 234 | Value: false, 235 | Destination: &settings.Build.Compress, 236 | Category: category, 237 | }, 238 | &cli.StringFlag{ 239 | Name: "repo", 240 | EnvVars: []string{"PLUGIN_REPO"}, 241 | Usage: "repository name for the image", 242 | Destination: &settings.Build.Repo, 243 | Category: category, 244 | }, 245 | &cli.StringFlag{ 246 | Name: "docker.registry", 247 | EnvVars: []string{"PLUGIN_REGISTRY", "DOCKER_REGISTRY"}, 248 | Usage: "docker registry to authenticate with", 249 | Value: "https://index.docker.io/v1/", 250 | Destination: &settings.Login.Registry, 251 | Category: category, 252 | }, 253 | &cli.StringFlag{ 254 | Name: "docker.username", 255 | EnvVars: []string{"PLUGIN_USERNAME", "DOCKER_USERNAME"}, 256 | Usage: "username for registry authentication", 257 | Destination: &settings.Login.Username, 258 | Category: category, 259 | }, 260 | &cli.StringFlag{ 261 | Name: "docker.password", 262 | EnvVars: []string{"PLUGIN_PASSWORD", "DOCKER_PASSWORD"}, 263 | Usage: "password for registry authentication", 264 | Destination: &settings.Login.Password, 265 | Category: category, 266 | }, 267 | &cli.StringFlag{ 268 | Name: "docker.email", 269 | EnvVars: []string{"PLUGIN_EMAIL", "DOCKER_EMAIL"}, 270 | Usage: "email address for registry authentication", 271 | Destination: &settings.Login.Email, 272 | Category: category, 273 | }, 274 | &cli.StringFlag{ 275 | Name: "docker.config", 276 | EnvVars: []string{"PLUGIN_CONFIG", "DOCKER_PLUGIN_CONFIG"}, 277 | Usage: "content of the docker daemon json config", 278 | Destination: &settings.Login.Config, 279 | Category: category, 280 | }, 281 | &cli.BoolFlag{ 282 | Name: "no-cache", 283 | EnvVars: []string{"PLUGIN_NO_CACHE"}, 284 | Usage: "disable the usage of cached intermediate containers", 285 | Value: false, 286 | Destination: &settings.Build.NoCache, 287 | Category: category, 288 | }, 289 | &cli.StringSliceFlag{ 290 | Name: "add-host", 291 | EnvVars: []string{"PLUGIN_ADD_HOST"}, 292 | Usage: "additional host:ip mapping", 293 | Destination: &settings.Build.AddHost, 294 | Category: category, 295 | }, 296 | &cli.StringSliceFlag{ 297 | Name: "platforms", 298 | EnvVars: []string{"PLUGIN_PLATFORMS"}, 299 | Usage: "target platform for build", 300 | Destination: &settings.Build.Platforms, 301 | Category: category, 302 | }, 303 | &cli.StringSliceFlag{ 304 | Name: "labels", 305 | EnvVars: []string{"PLUGIN_LABELS"}, 306 | Usage: "labels to add to image", 307 | Destination: &settings.Build.Labels, 308 | Category: category, 309 | }, 310 | &cli.StringFlag{ 311 | Name: "provenance", 312 | EnvVars: []string{"PLUGIN_PROVENANCE"}, 313 | Usage: "generates provenance attestation for the build", 314 | Destination: &settings.Build.Provenance, 315 | Category: category, 316 | }, 317 | &cli.StringFlag{ 318 | Name: "sbom", 319 | EnvVars: []string{"PLUGIN_SBOM"}, 320 | Usage: "generates sbom attestation for the build", 321 | Destination: &settings.Build.SBOM, 322 | Category: category, 323 | }, 324 | &cli.GenericFlag{ 325 | Name: "secrets", 326 | EnvVars: []string{"PLUGIN_SECRETS"}, 327 | Usage: "exposes secrets to the build", 328 | Value: &drone.StringSliceFlag{}, 329 | Category: category, 330 | }, 331 | } 332 | } 333 | -------------------------------------------------------------------------------- /cmd/drone-docker-buildx/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "os" 7 | 8 | "github.com/joho/godotenv" 9 | "github.com/sirupsen/logrus" 10 | "github.com/thegeeklab/drone-docker-buildx/plugin" 11 | "github.com/urfave/cli/v2" 12 | 13 | "github.com/thegeeklab/drone-plugin-lib/v2/drone" 14 | "github.com/thegeeklab/drone-plugin-lib/v2/urfave" 15 | ) 16 | 17 | //nolint:gochecknoglobals 18 | var ( 19 | BuildVersion = "devel" 20 | BuildDate = "00000000" 21 | ) 22 | 23 | var ErrTypeAssertionFailed = errors.New("type assertion failed") 24 | 25 | func main() { 26 | settings := &plugin.Settings{} 27 | 28 | if _, err := os.Stat("/run/drone/env"); err == nil { 29 | _ = godotenv.Overload("/run/drone/env") 30 | } 31 | 32 | cli.VersionPrinter = func(c *cli.Context) { 33 | fmt.Printf("%s version=%s date=%s\n", c.App.Name, c.App.Version, BuildDate) 34 | } 35 | 36 | app := &cli.App{ 37 | Name: "drone-docker-buildx", 38 | Usage: "build docker container with DinD and buildx", 39 | Version: BuildVersion, 40 | Flags: append(settingsFlags(settings, urfave.FlagsPluginCategory), urfave.Flags()...), 41 | Action: run(settings), 42 | } 43 | 44 | if err := app.Run(os.Args); err != nil { 45 | logrus.Fatal(err) 46 | } 47 | } 48 | 49 | func run(settings *plugin.Settings) cli.ActionFunc { 50 | return func(ctx *cli.Context) error { 51 | urfave.LoggingFromContext(ctx) 52 | 53 | cacheFrom, ok := ctx.Generic("cache-from").(*drone.StringSliceFlag) 54 | if !ok { 55 | return fmt.Errorf("%w: failed to read cache-from input", ErrTypeAssertionFailed) 56 | } 57 | 58 | settings.Build.CacheFrom = cacheFrom.Get() 59 | 60 | secrets, ok := ctx.Generic("secrets").(*drone.StringSliceFlag) 61 | if !ok { 62 | return fmt.Errorf("%w: failed to read secrets input", ErrTypeAssertionFailed) 63 | } 64 | 65 | settings.Build.Secrets = secrets.Get() 66 | 67 | plugin := plugin.New( 68 | *settings, 69 | urfave.PipelineFromContext(ctx), 70 | urfave.NetworkFromContext(ctx), 71 | ) 72 | 73 | if err := plugin.Validate(); err != nil { 74 | return fmt.Errorf("validation failed: %w", err) 75 | } 76 | 77 | if err := plugin.Execute(); err != nil { 78 | return fmt.Errorf("execution failed: %w", err) 79 | } 80 | 81 | return nil 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/thegeeklab/drone-docker-buildx 2 | 3 | go 1.20 4 | 5 | require ( 6 | github.com/coreos/go-semver v0.3.1 7 | github.com/joho/godotenv v1.5.1 8 | github.com/sirupsen/logrus v1.9.3 9 | github.com/thegeeklab/drone-plugin-lib/v2 v2.3.4 10 | github.com/urfave/cli/v2 v2.25.5 11 | golang.org/x/sys v0.11.0 12 | ) 13 | 14 | require ( 15 | github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect 16 | github.com/russross/blackfriday/v2 v2.1.0 // indirect 17 | github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect 18 | ) 19 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= 2 | github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= 3 | github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= 4 | github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= 5 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 6 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= 7 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= 9 | github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= 10 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 11 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 12 | github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= 13 | github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= 14 | github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= 15 | github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= 16 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 17 | github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= 18 | github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 19 | github.com/thegeeklab/drone-plugin-lib/v2 v2.3.4 h1:Quzrike/xRAR0izxQ0d+ocJyIUm4h1497Oyo9grcRzg= 20 | github.com/thegeeklab/drone-plugin-lib/v2 v2.3.4/go.mod h1:qWVUZCmwL0Ntwa/hvyqM03EeIr1ReBR2XJsmIc7MGus= 21 | github.com/urfave/cli/v2 v2.25.5 h1:d0NIAyhh5shGscroL7ek/Ya9QYQE0KNabJgiUinIQkc= 22 | github.com/urfave/cli/v2 v2.25.5/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc= 23 | github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= 24 | github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= 25 | golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 26 | golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= 27 | golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 28 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 29 | gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 30 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= 31 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= 32 | -------------------------------------------------------------------------------- /plugin/coredns.go: -------------------------------------------------------------------------------- 1 | package plugin 2 | 3 | import ( 4 | "io" 5 | "net" 6 | "os" 7 | "os/exec" 8 | ) 9 | 10 | func (p Plugin) startCoredns() { 11 | cmd := exec.Command("coredns", "-conf", "/etc/coredns/Corefile") 12 | if p.settings.Daemon.Debug { 13 | cmd.Stdout = os.Stdout 14 | cmd.Stderr = os.Stderr 15 | } else { 16 | cmd.Stdout = io.Discard 17 | cmd.Stderr = io.Discard 18 | } 19 | 20 | go func() { 21 | trace(cmd) 22 | _ = cmd.Run() 23 | }() 24 | } 25 | 26 | func getContainerIP() (string, error) { 27 | netInterfaceAddrList, err := net.InterfaceAddrs() 28 | if err != nil { 29 | return "", err 30 | } 31 | 32 | for _, netInterfaceAddr := range netInterfaceAddrList { 33 | netIP, ok := netInterfaceAddr.(*net.IPNet) 34 | if ok && !netIP.IP.IsLoopback() && netIP.IP.To4() != nil { 35 | return netIP.IP.String(), nil 36 | } 37 | } 38 | 39 | return "", nil 40 | } 41 | -------------------------------------------------------------------------------- /plugin/daemon.go: -------------------------------------------------------------------------------- 1 | package plugin 2 | 3 | import ( 4 | "io" 5 | "os" 6 | ) 7 | 8 | const ( 9 | dockerBin = "/usr/local/bin/docker" 10 | dockerdBin = "/usr/local/bin/dockerd" 11 | dockerHome = "/root/.docker/" 12 | buildkitConfig = "/tmp/buildkit.toml" 13 | ) 14 | 15 | func (p Plugin) startDaemon() { 16 | cmd := commandDaemon(p.settings.Daemon) 17 | if p.settings.Daemon.Debug { 18 | cmd.Stdout = os.Stdout 19 | cmd.Stderr = os.Stderr 20 | } else { 21 | cmd.Stdout = io.Discard 22 | cmd.Stderr = io.Discard 23 | } 24 | 25 | go func() { 26 | trace(cmd) 27 | _ = cmd.Run() 28 | }() 29 | } 30 | -------------------------------------------------------------------------------- /plugin/docker.go: -------------------------------------------------------------------------------- 1 | package plugin 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "strings" 7 | "time" 8 | 9 | "github.com/urfave/cli/v2" 10 | "golang.org/x/sys/execabs" 11 | ) 12 | 13 | // helper function to create the docker login command. 14 | func commandLogin(login Login) *execabs.Cmd { 15 | if login.Email != "" { 16 | return commandLoginEmail(login) 17 | } 18 | 19 | args := []string{ 20 | "login", 21 | "-u", login.Username, 22 | "-p", login.Password, 23 | login.Registry, 24 | } 25 | 26 | return execabs.Command( 27 | dockerBin, args..., 28 | ) 29 | } 30 | 31 | func commandLoginEmail(login Login) *execabs.Cmd { 32 | args := []string{ 33 | "login", 34 | "-u", login.Username, 35 | "-p", login.Password, 36 | "-e", login.Email, 37 | login.Registry, 38 | } 39 | 40 | return execabs.Command( 41 | dockerBin, args..., 42 | ) 43 | } 44 | 45 | // helper function to create the docker info command. 46 | func commandVersion() *execabs.Cmd { 47 | return execabs.Command(dockerBin, "version") 48 | } 49 | 50 | // helper function to create the docker info command. 51 | func commandInfo() *execabs.Cmd { 52 | return execabs.Command(dockerBin, "info") 53 | } 54 | 55 | func commandBuilder(daemon Daemon) *execabs.Cmd { 56 | args := []string{ 57 | "buildx", 58 | "create", 59 | "--use", 60 | } 61 | 62 | if daemon.BuildkitConfig != "" { 63 | args = append(args, "--config", buildkitConfig) 64 | } 65 | 66 | return execabs.Command(dockerBin, args...) 67 | } 68 | 69 | func commandBuildx() *execabs.Cmd { 70 | return execabs.Command(dockerBin, "buildx", "ls") 71 | } 72 | 73 | // helper function to create the docker build command. 74 | func commandBuild(build Build, dryrun bool) *execabs.Cmd { 75 | args := []string{ 76 | "buildx", 77 | "build", 78 | "--rm=true", 79 | "-f", build.Dockerfile, 80 | } 81 | 82 | defaultBuildArgs := []string{ 83 | fmt.Sprintf("DOCKER_IMAGE_CREATED=%s", time.Now().Format(time.RFC3339)), 84 | } 85 | 86 | args = append(args, build.Context) 87 | if !dryrun && build.Output == "" && len(build.Tags.Value()) > 0 { 88 | args = append(args, "--push") 89 | } 90 | 91 | if build.Compress { 92 | args = append(args, "--compress") 93 | } 94 | 95 | if build.Pull { 96 | args = append(args, "--pull=true") 97 | } 98 | 99 | if build.NoCache { 100 | args = append(args, "--no-cache") 101 | } 102 | 103 | for _, arg := range build.CacheFrom { 104 | args = append(args, "--cache-from", arg) 105 | } 106 | 107 | if build.CacheTo != "" { 108 | args = append(args, "--cache-to", build.CacheTo) 109 | } 110 | 111 | for _, arg := range build.ArgsEnv.Value() { 112 | addProxyValue(&build, arg) 113 | } 114 | 115 | for _, arg := range append(defaultBuildArgs, build.Args.Value()...) { 116 | args = append(args, "--build-arg", arg) 117 | } 118 | 119 | for _, host := range build.AddHost.Value() { 120 | args = append(args, "--add-host", host) 121 | } 122 | 123 | if build.Target != "" { 124 | args = append(args, "--target", build.Target) 125 | } 126 | 127 | if build.Quiet { 128 | args = append(args, "--quiet") 129 | } 130 | 131 | if build.Output != "" { 132 | args = append(args, "--output", build.Output) 133 | } 134 | 135 | for _, arg := range build.NamedContext.Value() { 136 | args = append(args, "--build-context", arg) 137 | } 138 | 139 | if len(build.Platforms.Value()) > 0 { 140 | args = append(args, "--platform", strings.Join(build.Platforms.Value(), ",")) 141 | } 142 | 143 | for _, arg := range build.Tags.Value() { 144 | args = append(args, "-t", fmt.Sprintf("%s:%s", build.Repo, arg)) 145 | } 146 | 147 | for _, arg := range build.ExtraTags.Value() { 148 | args = append(args, "-t", arg) 149 | } 150 | 151 | for _, arg := range build.Labels.Value() { 152 | args = append(args, "--label", arg) 153 | } 154 | 155 | if build.Provenance != "" { 156 | args = append(args, "--provenance", build.Provenance) 157 | } 158 | 159 | if build.SBOM != "" { 160 | args = append(args, "--sbom", build.SBOM) 161 | } 162 | 163 | for _, secret := range build.Secrets { 164 | args = append(args, "--secret", secret) 165 | } 166 | 167 | return execabs.Command(dockerBin, args...) 168 | } 169 | 170 | // helper function to add proxy values from the environment. 171 | func addProxyBuildArgs(build *Build) { 172 | addProxyValue(build, "http_proxy") 173 | addProxyValue(build, "https_proxy") 174 | addProxyValue(build, "no_proxy") 175 | } 176 | 177 | // helper function to add the upper and lower case version of a proxy value. 178 | func addProxyValue(build *Build, key string) { 179 | value := getProxyValue(key) 180 | 181 | if len(value) > 0 && !hasProxyBuildArg(build, key) { 182 | build.Args = *cli.NewStringSlice(append(build.Args.Value(), fmt.Sprintf("%s=%s", key, value))...) 183 | build.Args = *cli.NewStringSlice(append(build.Args.Value(), fmt.Sprintf("%s=%s", strings.ToUpper(key), value))...) 184 | } 185 | } 186 | 187 | // helper function to get a proxy value from the environment. 188 | // 189 | // assumes that the upper and lower case versions of are the same. 190 | func getProxyValue(key string) string { 191 | value := os.Getenv(key) 192 | 193 | if len(value) > 0 { 194 | return value 195 | } 196 | 197 | return os.Getenv(strings.ToUpper(key)) 198 | } 199 | 200 | // helper function that looks to see if a proxy value was set in the build args. 201 | func hasProxyBuildArg(build *Build, key string) bool { 202 | keyUpper := strings.ToUpper(key) 203 | 204 | for _, s := range build.Args.Value() { 205 | if strings.HasPrefix(s, key) || strings.HasPrefix(s, keyUpper) { 206 | return true 207 | } 208 | } 209 | 210 | return false 211 | } 212 | 213 | // helper function to create the docker daemon command. 214 | func commandDaemon(daemon Daemon) *execabs.Cmd { 215 | args := []string{ 216 | "--data-root", daemon.StoragePath, 217 | "--host=unix:///var/run/docker.sock", 218 | } 219 | 220 | if daemon.StorageDriver != "" { 221 | args = append(args, "-s", daemon.StorageDriver) 222 | } 223 | 224 | if daemon.Insecure && daemon.Registry != "" { 225 | args = append(args, "--insecure-registry", daemon.Registry) 226 | } 227 | 228 | if daemon.IPv6 { 229 | args = append(args, "--ipv6") 230 | } 231 | 232 | if len(daemon.Mirror) != 0 { 233 | args = append(args, "--registry-mirror", daemon.Mirror) 234 | } 235 | 236 | if len(daemon.Bip) != 0 { 237 | args = append(args, "--bip", daemon.Bip) 238 | } 239 | 240 | for _, dns := range daemon.DNS.Value() { 241 | args = append(args, "--dns", dns) 242 | } 243 | 244 | for _, dnsSearch := range daemon.DNSSearch.Value() { 245 | args = append(args, "--dns-search", dnsSearch) 246 | } 247 | 248 | if len(daemon.MTU) != 0 { 249 | args = append(args, "--mtu", daemon.MTU) 250 | } 251 | 252 | if daemon.Experimental { 253 | args = append(args, "--experimental") 254 | } 255 | 256 | return execabs.Command(dockerdBin, args...) 257 | } 258 | 259 | // trace writes each command to stdout with the command wrapped in an xml 260 | // tag so that it can be extracted and displayed in the logs. 261 | func trace(cmd *execabs.Cmd) { 262 | fmt.Fprintf(os.Stdout, "+ %s\n", strings.Join(cmd.Args, " ")) 263 | } 264 | -------------------------------------------------------------------------------- /plugin/impl.go: -------------------------------------------------------------------------------- 1 | package plugin 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | "time" 8 | 9 | "github.com/sirupsen/logrus" 10 | "github.com/urfave/cli/v2" 11 | "golang.org/x/sys/execabs" 12 | ) 13 | 14 | // Daemon defines Docker daemon parameters. 15 | type Daemon struct { 16 | Registry string // Docker registry 17 | Mirror string // Docker registry mirror 18 | Insecure bool // Docker daemon enable insecure registries 19 | StorageDriver string // Docker daemon storage driver 20 | StoragePath string // Docker daemon storage path 21 | Disabled bool // DOcker daemon is disabled (already running) 22 | Debug bool // Docker daemon started in debug mode 23 | Bip string // Docker daemon network bridge IP address 24 | DNS cli.StringSlice // Docker daemon dns server 25 | DNSSearch cli.StringSlice // Docker daemon dns search domain 26 | MTU string // Docker daemon mtu setting 27 | IPv6 bool // Docker daemon IPv6 networking 28 | Experimental bool // Docker daemon enable experimental mode 29 | BuildkitConfig string // Docker buildkit config 30 | } 31 | 32 | // Login defines Docker login parameters. 33 | type Login struct { 34 | Registry string // Docker registry address 35 | Username string // Docker registry username 36 | Password string // Docker registry password 37 | Email string // Docker registry email 38 | Config string // Docker Auth Config 39 | } 40 | 41 | // Build defines Docker build parameters. 42 | type Build struct { 43 | Ref string // Git commit ref 44 | Branch string // Git repository branch 45 | Dockerfile string // Docker build Dockerfile 46 | Context string // Docker build context 47 | TagsAuto bool // Docker build auto tag 48 | TagsSuffix string // Docker build tags with suffix 49 | Tags cli.StringSlice // Docker build tags 50 | ExtraTags cli.StringSlice // Docker build tags including registry 51 | Platforms cli.StringSlice // Docker build target platforms 52 | Args cli.StringSlice // Docker build args 53 | ArgsEnv cli.StringSlice // Docker build args from env 54 | Target string // Docker build target 55 | Pull bool // Docker build pull 56 | CacheFrom []string // Docker build cache-from 57 | CacheTo string // Docker build cache-to 58 | Compress bool // Docker build compress 59 | Repo string // Docker build repository 60 | NoCache bool // Docker build no-cache 61 | AddHost cli.StringSlice // Docker build add-host 62 | Quiet bool // Docker build quiet 63 | Output string // Docker build output folder 64 | NamedContext cli.StringSlice // Docker build named context 65 | Labels cli.StringSlice // Docker build labels 66 | Provenance string // Docker build provenance attestation 67 | SBOM string // Docker build sbom attestation 68 | Secrets []string // Docker build secrets 69 | } 70 | 71 | // Settings for the Plugin. 72 | type Settings struct { 73 | Daemon Daemon 74 | Login Login 75 | Build Build 76 | Dryrun bool 77 | } 78 | 79 | const strictFilePerm = 0o600 80 | 81 | // Validate handles the settings validation of the plugin. 82 | func (p *Plugin) Validate() error { 83 | p.settings.Build.Branch = p.pipeline.Repo.Branch 84 | p.settings.Build.Ref = p.pipeline.Commit.Ref 85 | p.settings.Daemon.Registry = p.settings.Login.Registry 86 | 87 | if p.settings.Build.TagsAuto { 88 | // return true if tag event or default branch 89 | if UseDefaultTag( 90 | p.settings.Build.Ref, 91 | p.settings.Build.Branch, 92 | ) { 93 | tag, err := DefaultTagSuffix( 94 | p.settings.Build.Ref, 95 | p.settings.Build.TagsSuffix, 96 | ) 97 | if err != nil { 98 | logrus.Infof("cannot generate tags from %s, invalid semantic version", p.settings.Build.Ref) 99 | 100 | return err 101 | } 102 | 103 | p.settings.Build.Tags = *cli.NewStringSlice(tag...) 104 | } else { 105 | logrus.Infof("skip auto-tagging for %s, not on default branch or tag", p.settings.Build.Ref) 106 | 107 | return nil 108 | } 109 | } 110 | 111 | return nil 112 | } 113 | 114 | // Execute provides the implementation of the plugin. 115 | // 116 | //nolint:gocognit 117 | func (p *Plugin) Execute() error { 118 | // start the Docker daemon server 119 | //nolint: nestif 120 | if !p.settings.Daemon.Disabled { 121 | // If no custom DNS value set start internal DNS server 122 | if len(p.settings.Daemon.DNS.Value()) == 0 { 123 | ip, err := getContainerIP() 124 | if err != nil { 125 | logrus.Warnf("error detecting IP address: %v", err) 126 | } 127 | 128 | if ip != "" { 129 | logrus.Debugf("discovered IP address: %v", ip) 130 | p.startCoredns() 131 | 132 | if err := p.settings.Daemon.DNS.Set(ip); err != nil { 133 | return fmt.Errorf("error setting daemon dns: %w", err) 134 | } 135 | } 136 | } 137 | 138 | p.startDaemon() 139 | } 140 | 141 | // poll the docker daemon until it is started. This ensures the daemon is 142 | // ready to accept connections before we proceed. 143 | for i := 0; i < 15; i++ { 144 | cmd := commandInfo() 145 | 146 | err := cmd.Run() 147 | if err == nil { 148 | break 149 | } 150 | 151 | time.Sleep(time.Second * 1) 152 | } 153 | 154 | // Create Auth Config File 155 | if p.settings.Login.Config != "" { 156 | if err := os.MkdirAll(dockerHome, strictFilePerm); err != nil { 157 | return fmt.Errorf("failed to create docker home: %w", err) 158 | } 159 | 160 | path := filepath.Join(dockerHome, "config.json") 161 | 162 | err := os.WriteFile(path, []byte(p.settings.Login.Config), strictFilePerm) 163 | if err != nil { 164 | return fmt.Errorf("error writing config.json: %w", err) 165 | } 166 | } 167 | 168 | // login to the Docker registry 169 | if p.settings.Login.Password != "" { 170 | cmd := commandLogin(p.settings.Login) 171 | 172 | err := cmd.Run() 173 | if err != nil { 174 | return fmt.Errorf("error authenticating: %w", err) 175 | } 176 | } 177 | 178 | if p.settings.Daemon.BuildkitConfig != "" { 179 | err := os.WriteFile(buildkitConfig, []byte(p.settings.Daemon.BuildkitConfig), strictFilePerm) 180 | if err != nil { 181 | return fmt.Errorf("error writing buildkit.toml: %w", err) 182 | } 183 | } 184 | 185 | switch { 186 | case p.settings.Login.Password != "": 187 | logrus.Info("Detected registry credentials") 188 | case p.settings.Login.Config != "": 189 | logrus.Info("Detected registry credentials file") 190 | default: 191 | logrus.Info("Registry credentials or Docker config not provided. Guest mode enabled.") 192 | } 193 | 194 | // add proxy build args 195 | addProxyBuildArgs(&p.settings.Build) 196 | 197 | var cmds []*execabs.Cmd 198 | cmds = append(cmds, commandVersion()) // docker version 199 | cmds = append(cmds, commandInfo()) // docker info 200 | cmds = append(cmds, commandBuilder(p.settings.Daemon)) 201 | cmds = append(cmds, commandBuildx()) 202 | 203 | cmds = append(cmds, commandBuild(p.settings.Build, p.settings.Dryrun)) // docker build 204 | 205 | // execute all commands in batch mode. 206 | for _, cmd := range cmds { 207 | cmd.Stdout = os.Stdout 208 | cmd.Stderr = os.Stderr 209 | trace(cmd) 210 | 211 | err := cmd.Run() 212 | if err != nil { 213 | return err 214 | } 215 | } 216 | 217 | return nil 218 | } 219 | -------------------------------------------------------------------------------- /plugin/plugin.go: -------------------------------------------------------------------------------- 1 | package plugin 2 | 3 | import ( 4 | "github.com/thegeeklab/drone-plugin-lib/v2/drone" 5 | ) 6 | 7 | // Plugin implements drone.Plugin to provide the plugin implementation. 8 | type Plugin struct { 9 | settings Settings 10 | pipeline drone.Pipeline 11 | network drone.Network 12 | } 13 | 14 | // New initializes a plugin from the given Settings, Pipeline, and Network. 15 | func New(settings Settings, pipeline drone.Pipeline, network drone.Network) *Plugin { 16 | return &Plugin{ 17 | settings: settings, 18 | pipeline: pipeline, 19 | network: network, 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /plugin/tags.go: -------------------------------------------------------------------------------- 1 | package plugin 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/coreos/go-semver/semver" 8 | ) 9 | 10 | // DefaultTagSuffix returns a set of default suggested tags 11 | // based on the commit ref with an attached suffix. 12 | func DefaultTagSuffix(ref, suffix string) ([]string, error) { 13 | tags, err := DefaultTags(ref) 14 | if err != nil { 15 | return nil, err 16 | } 17 | 18 | if len(suffix) == 0 { 19 | return tags, nil 20 | } 21 | 22 | for i, tag := range tags { 23 | if tag == "latest" { 24 | tags[i] = suffix 25 | } else { 26 | tags[i] = fmt.Sprintf("%s-%s", tag, suffix) 27 | } 28 | } 29 | 30 | return tags, nil 31 | } 32 | 33 | func splitOff(input, delim string) string { 34 | const splits = 2 35 | parts := strings.SplitN(input, delim, splits) 36 | 37 | if len(parts) == splits { 38 | return parts[0] 39 | } 40 | 41 | return input 42 | } 43 | 44 | // DefaultTags returns a set of default suggested tags based on 45 | // the commit ref. 46 | func DefaultTags(ref string) ([]string, error) { 47 | if !strings.HasPrefix(ref, "refs/tags/") { 48 | return []string{"latest"}, nil 49 | } 50 | 51 | rawVersion := stripTagPrefix(ref) 52 | 53 | version, err := semver.NewVersion(rawVersion) 54 | if err != nil { 55 | return []string{"latest"}, err 56 | } 57 | 58 | if version.PreRelease != "" || version.Metadata != "" { 59 | return []string{ 60 | version.String(), 61 | }, nil 62 | } 63 | 64 | rawVersion = stripTagPrefix(ref) 65 | rawVersion = splitOff(splitOff(rawVersion, "+"), "-") 66 | //nolint:gomnd 67 | dotParts := strings.SplitN(rawVersion, ".", 3) 68 | 69 | if version.Major == 0 { 70 | return []string{ 71 | fmt.Sprintf("%0*d.%0*d", len(dotParts[0]), version.Major, len(dotParts[1]), version.Minor), 72 | fmt.Sprintf( 73 | "%0*d.%0*d.%0*d", 74 | len(dotParts[0]), 75 | version.Major, 76 | len(dotParts[1]), 77 | version.Minor, 78 | len(dotParts[2]), 79 | version.Patch, 80 | ), 81 | }, nil 82 | } 83 | 84 | return []string{ 85 | fmt.Sprintf("%0*d", len(dotParts[0]), version.Major), 86 | fmt.Sprintf("%0*d.%0*d", len(dotParts[0]), version.Major, len(dotParts[1]), version.Minor), 87 | fmt.Sprintf( 88 | "%0*d.%0*d.%0*d", 89 | len(dotParts[0]), 90 | version.Major, 91 | len(dotParts[1]), 92 | version.Minor, 93 | len(dotParts[2]), 94 | version.Patch, 95 | ), 96 | }, nil 97 | } 98 | 99 | // UseDefaultTag to keep only default branch for latest tag. 100 | func UseDefaultTag(ref, defaultBranch string) bool { 101 | if strings.HasPrefix(ref, "refs/tags/") { 102 | return true 103 | } 104 | 105 | if stripHeadPrefix(ref) == defaultBranch { 106 | return true 107 | } 108 | 109 | return false 110 | } 111 | 112 | func stripHeadPrefix(ref string) string { 113 | return strings.TrimPrefix(ref, "refs/heads/") 114 | } 115 | 116 | func stripTagPrefix(ref string) string { 117 | ref = strings.TrimPrefix(ref, "refs/tags/") 118 | ref = strings.TrimPrefix(ref, "v") 119 | 120 | return ref 121 | } 122 | -------------------------------------------------------------------------------- /plugin/tags_test.go: -------------------------------------------------------------------------------- 1 | package plugin 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | ) 7 | 8 | func Test_stripTagPrefix(t *testing.T) { 9 | tests := []struct { 10 | Before string 11 | After string 12 | }{ 13 | {"refs/tags/1.0.0", "1.0.0"}, 14 | {"refs/tags/v1.0.0", "1.0.0"}, 15 | {"v1.0.0", "1.0.0"}, 16 | } 17 | 18 | for _, test := range tests { 19 | got, want := stripTagPrefix(test.Before), test.After 20 | if got != want { 21 | t.Errorf("Got tag %s, want %s", got, want) 22 | } 23 | } 24 | } 25 | 26 | func TestDefaultTags(t *testing.T) { 27 | tests := []struct { 28 | Before string 29 | After []string 30 | }{ 31 | {"", []string{"latest"}}, 32 | {"refs/heads/main", []string{"latest"}}, 33 | {"refs/tags/0.9.0", []string{"0.9", "0.9.0"}}, 34 | {"refs/tags/1.0.0", []string{"1", "1.0", "1.0.0"}}, 35 | {"refs/tags/v1.0.0", []string{"1", "1.0", "1.0.0"}}, 36 | {"refs/tags/v1.0.0-alpha.1", []string{"1.0.0-alpha.1"}}, 37 | } 38 | 39 | for _, test := range tests { 40 | tags, err := DefaultTags(test.Before) 41 | if err != nil { 42 | t.Error(err) 43 | 44 | continue 45 | } 46 | 47 | got, want := tags, test.After 48 | if !reflect.DeepEqual(got, want) { 49 | t.Errorf("Got tag %v, want %v", got, want) 50 | } 51 | } 52 | } 53 | 54 | func TestDefaultTagsError(t *testing.T) { 55 | tests := []string{ 56 | "refs/tags/x1.0.0", 57 | "refs/tags/20190203", 58 | } 59 | 60 | for _, test := range tests { 61 | _, err := DefaultTags(test) 62 | if err == nil { 63 | t.Errorf("Expect tag error for %s", test) 64 | } 65 | } 66 | } 67 | 68 | func TestDefaultTagSuffix(t *testing.T) { 69 | tests := []struct { 70 | Before string 71 | Suffix string 72 | After []string 73 | }{ 74 | // without suffix 75 | { 76 | After: []string{"latest"}, 77 | }, 78 | { 79 | Before: "refs/tags/v1.0.0", 80 | After: []string{ 81 | "1", 82 | "1.0", 83 | "1.0.0", 84 | }, 85 | }, 86 | // with suffix 87 | { 88 | Suffix: "linux-amd64", 89 | After: []string{"linux-amd64"}, 90 | }, 91 | { 92 | Before: "refs/tags/v1.0.0", 93 | Suffix: "linux-amd64", 94 | After: []string{ 95 | "1-linux-amd64", 96 | "1.0-linux-amd64", 97 | "1.0.0-linux-amd64", 98 | }, 99 | }, 100 | { 101 | Suffix: "nanoserver", 102 | After: []string{"nanoserver"}, 103 | }, 104 | { 105 | Before: "refs/tags/v1.9.2", 106 | Suffix: "nanoserver", 107 | After: []string{ 108 | "1-nanoserver", 109 | "1.9-nanoserver", 110 | "1.9.2-nanoserver", 111 | }, 112 | }, 113 | { 114 | Before: "refs/tags/v18.06.0", 115 | Suffix: "nanoserver", 116 | After: []string{ 117 | "18-nanoserver", 118 | "18.06-nanoserver", 119 | "18.06.0-nanoserver", 120 | }, 121 | }, 122 | } 123 | 124 | for _, test := range tests { 125 | tag, err := DefaultTagSuffix(test.Before, test.Suffix) 126 | if err != nil { 127 | t.Error(err) 128 | 129 | continue 130 | } 131 | 132 | got, want := tag, test.After 133 | if !reflect.DeepEqual(got, want) { 134 | t.Errorf("Got tag %v, want %v", got, want) 135 | } 136 | } 137 | } 138 | 139 | func Test_stripHeadPrefix(t *testing.T) { 140 | type args struct { 141 | ref string 142 | } 143 | 144 | tests := []struct { 145 | args args 146 | want string 147 | }{ 148 | { 149 | args: args{ 150 | ref: "refs/heads/main", 151 | }, 152 | want: "main", 153 | }, 154 | } 155 | 156 | for _, tt := range tests { 157 | if got := stripHeadPrefix(tt.args.ref); got != tt.want { 158 | t.Errorf("stripHeadPrefix() = %v, want %v", got, tt.want) 159 | } 160 | } 161 | } 162 | 163 | func TestUseDefaultTag(t *testing.T) { 164 | type args struct { 165 | ref string 166 | defaultBranch string 167 | } 168 | 169 | tests := []struct { 170 | name string 171 | args args 172 | want bool 173 | }{ 174 | { 175 | name: "latest tag for default branch", 176 | args: args{ 177 | ref: "refs/heads/main", 178 | defaultBranch: "main", 179 | }, 180 | want: true, 181 | }, 182 | { 183 | name: "build from tags", 184 | args: args{ 185 | ref: "refs/tags/v1.0.0", 186 | defaultBranch: "main", 187 | }, 188 | want: true, 189 | }, 190 | { 191 | name: "skip build for not default branch", 192 | args: args{ 193 | ref: "refs/heads/develop", 194 | defaultBranch: "main", 195 | }, 196 | want: false, 197 | }, 198 | } 199 | 200 | for _, tt := range tests { 201 | if got := UseDefaultTag(tt.args.ref, tt.args.defaultBranch); got != tt.want { 202 | t.Errorf("%q. UseDefaultTag() = %v, want %v", tt.name, got, tt.want) 203 | } 204 | } 205 | } 206 | --------------------------------------------------------------------------------