diff --git a/.ecrc b/.ecrc new file mode 100644 index 0000000..ed9f777 --- /dev/null +++ b/.ecrc @@ -0,0 +1,11 @@ +{ + "Exclude": [ + ".git", + "go.mod", "go.sum", + "vendor", + "LICENSE", + "node_modules", + "_test.go", + "Makefile" + ] +} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..34f6015 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,19 @@ +root = true + +[*] +indent_style = space +indent_size = 2 +tab_width = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.go] +indent_style = tab + +[*.md] +trim_trailing_whitespace = false + +[Makefile] +indent_style = tab diff --git a/.gitea/ISSUE_TEMPLATE.yml b/.gitea/ISSUE_TEMPLATE.yml new file mode 100644 index 0000000..6d80569 --- /dev/null +++ b/.gitea/ISSUE_TEMPLATE.yml @@ -0,0 +1,16 @@ +name: Default +about: Use this template if nothing seems to work. +title: '' +body: + - type: markdown + attributes: + value: | + > **Note**: Thanks for taking the time to fill out this bug report! + - type: textarea + id: content + attributes: + label: ❔ What happened + description: Simply ask your question here. + placeholder: Tell us what you want to know... + validations: + required: true diff --git a/.gitea/ISSUE_TEMPLATE/Question.yml b/.gitea/ISSUE_TEMPLATE/Question.yml new file mode 100644 index 0000000..d68a07f --- /dev/null +++ b/.gitea/ISSUE_TEMPLATE/Question.yml @@ -0,0 +1,11 @@ +name: ❓ Question +about: Ask a question +title: "" +labels: + - question +body: + - type: textarea + id: content + attributes: + label: Question content + diff --git a/.gitea/ISSUE_TEMPLATE/bug_report.yml b/.gitea/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..6a1fbbe --- /dev/null +++ b/.gitea/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,71 @@ +name: "\U0001F41E Bug report" +about: Report an issue with the plugin +title: "" +labels: + - bug +body: + - type: markdown + attributes: + value: | + Thanks for taking the time to fill out this bug report! + - type: dropdown + id: component + attributes: + label: Component + description: Which component of Woodpecker is affected by the issue? + multiple: true + options: + - server + - agent + - cli + - web-ui + - other + validations: + required: true + - type: textarea + id: bug-description + attributes: + label: Describe the bug + description: A clear and concise description of what the bug is. If you intend to submit a PR for this issue, tell us in the description. Thanks! + placeholder: Bug description + validations: + required: true + - type: textarea + id: system-info + attributes: + label: System Info + description: Output of `https:///version` + render: shell + placeholder: Version info, docker-compose config, Kubernetes manifests + validations: + required: true + - type: textarea + id: additional-context + attributes: + label: Additional context + description: | + Logs? Screenshots? Anything that will give us more context about the issue you are encountering! + Sometimes a picture is worth a thousand words, but please try not to insert an image of logs / text + and copy paste the text instead. + + Tip: You can attach images by clicking this area to highlight it and then dragging files in. + validations: + required: false + - type: checkboxes + id: checkboxes + attributes: + label: Validations + description: Before submitting the issue, please make sure you do the following + options: + # - label: Follow our [Code of Conduct](https://github.com/woodpecker-ci/woodpecker/blob/main/CODE_OF_CONDUCT.md) + # required: true + - label: Read the [Contributing Guidelines](https://github.com/woodpecker-ci/woodpecker/blob/main/CONTRIBUTING.md). + required: true + - label: Read the [docs](https://woodpecker-ci.org/docs/intro). + required: true + - label: Check that there isn't [already an issue](https://github.com/woodpecker-ci/woodpecker/issues) that reports the same bug to avoid creating a duplicate. + required: true + - label: Checked that the bug isn't fixed in the `next` version already [https://woodpecker-ci.org/faq#which-version-of-woodpecker-should-i-use] + required: true + - label: Check that this is a concrete bug. For Q&A join our [Discord Chat Server](https://discord.gg/fcMQqSMXJy) or the [Matrix room](https://matrix.to/#/#woodpecker:matrix.org). + required: true diff --git a/.gitea/ISSUE_TEMPLATE/config.yml b/.gitea/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..ee41641 --- /dev/null +++ b/.gitea/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: true +contact_links: + - name: Ask a question in our Matrix room + about: If you prefer a chat-like conversation or in need for quick help, this might be an alternative to opening an issue. + url: https://matrix.to/#/#woodpecker:matrix.org + - name: Frequently Asked Questions + url: https://woodpecker-ci.org/faq + about: Check the FAQs for common questions. diff --git a/.gitea/ISSUE_TEMPLATE/feature_request.yml b/.gitea/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..2203dce --- /dev/null +++ b/.gitea/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,33 @@ +name: "\U0001F680 New feature proposal" +description: Propose a new feature to be added +title: "" +labels: ["feature"] +body: + - type: markdown + attributes: + value: | + Thanks for your interest in the project and taking the time to fill out this feature report! + - type: textarea + id: feature-description + attributes: + label: Clear and concise description of the problem + description: "As a user of Woodpecker I want [goal / wish] so that [benefit]. If you intend to submit a PR for this issue, tell us in the description." + validations: + required: true + - type: textarea + id: suggested-solution + attributes: + label: Suggested solution + description: "In web-ui / config we could provide following functionality..." + validations: + required: true + - type: textarea + id: alternative + attributes: + label: Alternative + description: Clear and concise description of any alternative solutions or features you've considered. + - type: textarea + id: additional-context + attributes: + label: Additional context + description: Any other context or screenshots about the feature request here. diff --git a/.gitea/PULL_REQUEST_TEMPLATE.yml b/.gitea/PULL_REQUEST_TEMPLATE.yml new file mode 100644 index 0000000..15e71da --- /dev/null +++ b/.gitea/PULL_REQUEST_TEMPLATE.yml @@ -0,0 +1,30 @@ +name: Pull Request +about: General pull request +title: "" +body: + - type: markdown + attributes: + value: | + Thanks for contributing to this project with your pull request! + - type: textarea + id: summary + attributes: + label: 📖 Summary + description: Provide a concise summary of the changes. Ideally broken down to multiple bullet points. Please add details and longer text blocks to "Details" below. + validations: + required: true + - type: dropdown + id: build_pr_images + attributes: + label: 📑 Build PR Images? + description: Should docker images be built for this PR and uploaded to Dockerhub? If so, a maintainer will add the `build_pr_images` label to this PR if this option is selected. + options: + - PR images are not needed + - ✅ Yes, please build PR images + validations: + required: true + - type: textarea + id: details + attributes: + label: 💬 Details + description: Add additional information here. diff --git a/.gitignore b/.gitignore index afd7d10..3389027 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ plugin-docker-buildx coverage.out CHANGELOG.md +debug.test* diff --git a/.markdownlint.yml b/.markdownlint.yml index b59a114..53d3923 100644 --- a/.markdownlint.yml +++ b/.markdownlint.yml @@ -4,3 +4,7 @@ MD013: False MD041: False MD004: style: dash + +MD033: + # Allowed elements + allowed_elements: [details, summary, img, a, br, p] diff --git a/.woodpecker.yml b/.woodpecker.yml index 0170411..ee4b55f 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -3,15 +3,29 @@ when: - event: push branch: - ${CI_REPO_DEFAULT_BRANCH} + - renovate/* variables: - - &golang 'golang:1.21' - - &build_plugin 'woodpeckerci/plugin-docker-buildx:2.1.0' - - base_settings: &base_buildx_settings - platforms: 'linux/amd64,linux/arm64' - dockerfile: Dockerfile.multiarch - auto_tag: true - repo: woodpeckerci/plugin-docker-buildx,codeberg.org/woodpecker-plugins/docker-buildx + - &golang "golang:1.22" + - &build_plugin "woodpeckerci/plugin-docker-buildx:3.0.1" + - base_settings: &base_buildx_settings + platforms: "linux/amd64,linux/arm64" + dockerfile: Dockerfile.multiarch + auto_tag: true + repo: woodpeckerci/plugin-docker-buildx,codeberg.org/woodpecker-plugins/docker-buildx + - &login_setting + # Default DockerHub login + - registry: https://index.docker.io/v1/ + username: + from_secret: docker_username + password: + from_secret: docker_password + # Additional Codeberg login + - registry: https://codeberg.org + username: + from_secret: cb_username + password: + from_secret: cb_password steps: vendor: @@ -20,37 +34,67 @@ steps: test: image: *golang + depends_on: vendor commands: go test -cover ./... + lint-editorconfig: + image: docker.io/mstruebing/editorconfig-checker:2.7.2 + when: + event: pull_request + + lint-format: + image: *golang + depends_on: vendor + commands: make formatcheck + when: + event: pull_request + publish-dryrun: image: *build_plugin + depends_on: test pull: true settings: <<: *base_buildx_settings repo: test - dry_run: true + dry-run: true when: + evaluate: 'not (CI_COMMIT_PULL_REQUEST_LABELS contains "build_pr_images")' event: pull_request - branch: main + branch: + - ${CI_REPO_DEFAULT_BRANCH} + - renovate/* publish: image: *build_plugin + depends_on: test settings: <<: *base_buildx_settings - logins: - # Default DockerHub login - - registry: https://index.docker.io/v1/ - username: - from_secret: docker_username - password: - from_secret: docker_password - # Additional Codeberg login - - registry: https://codeberg.org - username: - from_secret: cb_username - password: - from_secret: cb_password - + logins: *login_setting when: event: [push, tag, cron] branch: ${CI_REPO_DEFAULT_BRANCH} + + publish_pr_image: + image: *build_plugin + depends_on: test + settings: + <<: *base_buildx_settings + tag: pull_${CI_COMMIT_PULL_REQUEST} + logins: *login_setting + when: + evaluate: 'CI_COMMIT_PULL_REQUEST_LABELS contains "build_pr_images"' + event: pull_request + + # TODO: replace by plugin-ready-release-go once it supports gitea + gitea-release: + image: "woodpeckerci/plugin-gitea-release:0.3.1" + depends_on: test + settings: + base_url: https://codeberg.org + title: ${CI_COMMIT_TAG} + api_key: + from_secret: gitea_token + target: main + when: + event: [tag] + branch: ${CI_REPO_DEFAULT_BRANCH} diff --git a/Dockerfile.multiarch b/Dockerfile.multiarch index f9d9dd0..ffb321b 100644 --- a/Dockerfile.multiarch +++ b/Dockerfile.multiarch @@ -1,6 +1,6 @@ -ARG BUILDX_VERSION=0.11.2@sha256:e7f00cf9fc3754de699190b215d383e57bd654179b31b28eefadf978a362e647 -ARG DOCKER_VERSION=20.10-dind -ARG GOLANG_VERSION=1.21@sha256:24a09375a6216764a3eda6a25490a88ac178b5fcb9511d59d0da5ebf9e496474 +ARG BUILDX_VERSION=0.12.1 +ARG DOCKER_VERSION=25.0.3-dind +ARG GOLANG_VERSION=1.22 FROM --platform=$BUILDPLATFORM golang:${GOLANG_VERSION} as build @@ -15,7 +15,7 @@ RUN --mount=type=cache,target=/root/.cache/go-build \ FROM docker/buildx-bin:${BUILDX_VERSION} as buildx-bin FROM docker:${DOCKER_VERSION} -RUN apk --update --no-cache add coredns +RUN apk --update --no-cache add coredns git COPY --from=build /src/Corefile /etc/coredns/Corefile COPY --from=buildx-bin /buildx /usr/libexec/docker/cli-plugins/docker-buildx diff --git a/Makefile b/Makefile index 1af273c..f7351c3 100644 --- a/Makefile +++ b/Makefile @@ -2,5 +2,17 @@ TARGETOS ?= linux TARGETARCH ?= amd64 LDFLAGS := -s -w -extldflags "-static" +.PHONY: build build: CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -ldflags '${LDFLAGS}' -v -a -tags netgo -o plugin-docker-buildx ./cmd/docker-buildx + +format: install-tools + gofumpt -extra -w . + +formatcheck: install-tools + @([ -z "$(shell gofumpt -d . | head)" ]) || (echo "Source is unformatted"; exit 1) + +install-tools: ## Install development tools + @hash gofumpt > /dev/null 2>&1; if [ $$? -ne 0 ]; then \ + go install mvdan.cc/gofumpt@latest; \ + fi diff --git a/README.md b/README.md index de410c6..bd89efd 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,29 @@ # plugin-docker-buildx - - Get it on Codeberg +
+

+ + status-badge + + Latest release + + + Matrix space + + + Docker pulls + + + License: Apache-2.0 + +

+
Woodpecker CI plugin to build multiarch Docker images with [buildx](https://duckduckgo.com/?q=docker+buildx&ia=web). This plugin was initially a fork of [thegeeklab/drone-docker-buildx](https://github.com/thegeeklab/drone-docker-buildx/) (now archived in favor of this plugin) which itself was a fork of [drone-plugins/drone-docker](https://github.com/drone-plugins/drone-docker). +I also contains the ability to publish to AWS ECR which was previously provided by [drone-plugins/drone-ecr](https://github.com/drone-plugins/drone-docker/tree/master/cmd/drone-ecr). You can find the full documentation at [woodpecker-ci.org](https://woodpecker-ci.org/plugins/Docker%20Buildx) ([docs.md](./docs.md)). ## Images diff --git a/cmd/docker-buildx/config.go b/cmd/docker-buildx/config.go index 3b42772..05705f3 100644 --- a/cmd/docker-buildx/config.go +++ b/cmd/docker-buildx/config.go @@ -99,6 +99,18 @@ func settingsFlags(settings *plugin.Settings) []cli.Flag { Usage: "sets content of the docker buildkit json config", Destination: &settings.Daemon.BuildkitConfig, }, + &cli.BoolFlag{ + Name: "daemon.buildkit-debug", + EnvVars: []string{"PLUGIN_BUILDKIT_DEBUG"}, + Usage: "enables buildkit debug", + Destination: &settings.Daemon.BuildkitDebug, + }, + &cli.StringSliceFlag{ + Name: "daemon.buildkit-driveropt", + EnvVars: []string{"PLUGIN_BUILDKIT_DRIVEROPT"}, + Usage: "adds optional driver-ops args like 'env.http_proxy'", + Destination: &settings.Daemon.BuildkitDriverOpt, + }, &cli.StringFlag{ Name: "dockerfile", EnvVars: []string{"PLUGIN_DOCKERFILE"}, @@ -183,18 +195,24 @@ func settingsFlags(settings *plugin.Settings) []cli.Flag { Usage: "sets the build target to use", Destination: &settings.Build.Target, }, - &cli.StringSliceFlag{ + &cli.StringFlag{ Name: "cache-from", EnvVars: []string{"PLUGIN_CACHE_FROM"}, Usage: "sets images to consider as cache sources", Destination: &settings.Build.CacheFrom, }, - &cli.StringSliceFlag{ + &cli.StringFlag{ Name: "cache-to", EnvVars: []string{"PLUGIN_CACHE_TO"}, Usage: "cache destination for the build cache", Destination: &settings.Build.CacheTo, }, + &cli.StringSliceFlag{ + Name: "cache-images", + EnvVars: []string{"PLUGIN_CACHE_IMAGES"}, + Usage: "list of images to use for build cache. applies both to and from flags for each image", + Destination: &settings.Build.CacheImages, + }, &cli.BoolFlag{ Name: "pull-image", EnvVars: []string{"PLUGIN_PULL_IMAGE"}, @@ -283,5 +301,47 @@ func settingsFlags(settings *plugin.Settings) []cli.Flag { Usage: "sets build output type and destination configuration", Destination: &settings.Build.Output, }, + &cli.StringFlag{ + Name: "ecr.aws_access_key_id", + EnvVars: []string{"PLUGIN_AWS_ACCESS_KEY_ID"}, + Usage: "Access Key ID for AWS", + Destination: &settings.AwsAccessKeyId, + }, + &cli.StringFlag{ + Name: "ecr.aws_secret_access_key_id", + EnvVars: []string{"PLUGIN_AWS_SECRET_ACCESS_KEY"}, + Usage: "Secret Access Key for AWS", + Destination: &settings.AwsSecretAccessKey, + }, + &cli.StringFlag{ + Name: "ecr.aws_region", + EnvVars: []string{"PLUGIN_AWS_REGION"}, + Usage: "AWS region to use", + Destination: &settings.AwsRegion, + }, + &cli.BoolFlag{ + Name: "ecr.create_repository", + EnvVars: []string{"PLUGIN_ECR_CREATE_REPOSITORY"}, + Usage: "creates the ECR repository if it does not exist", + Destination: &settings.EcrCreateRepository, + }, + &cli.StringFlag{ + Name: "ecr.lifecycle_policy", + EnvVars: []string{"PLUGIN_ECR_LIFECYCLE_POLICY"}, + Usage: "AWS ECR lifecycle policy", + Destination: &settings.EcrLifecyclePolicy, + }, + &cli.StringFlag{ + Name: "ecr.repository_policy", + EnvVars: []string{"PLUGIN_ECR_REPOSITORY_POLICY"}, + Usage: "AWS ECR repository policy", + Destination: &settings.EcrRepositoryPolicy, + }, + &cli.BoolFlag{ + Name: "ecr.scan_on_push", + EnvVars: []string{"PLUGIN_ECR_SCAN_ON_PUSH"}, + Usage: "AWS: whether to enable image scanning on push", + Destination: &settings.EcrScanOnPush, + }, } } diff --git a/cmd/docker-buildx/main.go b/cmd/docker-buildx/main.go index 5531f44..cedfdc2 100644 --- a/cmd/docker-buildx/main.go +++ b/cmd/docker-buildx/main.go @@ -14,12 +14,14 @@ import ( var version = "unknown" func main() { - settings := &plugin.Settings{} + settings := &plugin.Settings{ + CustomCertStore: "/etc/docker/certs.d/", + } if _, err := os.Stat("/run/drone/env"); err == nil { godotenv.Overload("/run/drone/env") } - + if envFile, set := os.LookupEnv("PLUGIN_ENV_FILE"); set { godotenv.Overload(envFile) } diff --git a/docker.svg b/docker.svg new file mode 100644 index 0000000..383626b --- /dev/null +++ b/docker.svg @@ -0,0 +1,14 @@ + + + + + + diff --git a/docs.md b/docs.md index 14898d8..3f8ce32 100644 --- a/docs.md +++ b/docs.md @@ -1,14 +1,13 @@ --- name: Docker Buildx -icon: https://woodpecker-ci.org/img/logo.svg +icon: https://codeberg.org/woodpecker-plugins/docker-buildx/raw/branch/main/docker.svg description: plugin to build multiarch Docker images with buildx -authors: Woodpecker Authors +author: Woodpecker Authors tags: [docker, image, container, build] containerImage: woodpeckerci/plugin-docker-buildx containerImageUrl: https://hub.docker.com/r/woodpeckerci/plugin-docker-buildx url: https://codeberg.org/woodpecker-plugins/docker-buildx --- - Woodpecker CI plugin to build multiarch Docker images with buildx. This plugin is a fork of [thegeeklab/drone-docker-buildx](https://github.com/thegeeklab/drone-docker-buildx/) which itself is a fork of [drone-plugins/drone-docker](https://github.com/drone-plugins/drone-docker). ## Features @@ -28,17 +27,22 @@ It will automatically generate buildkit configuration to use custom CA certifica ## Settings -| Settings Name | Default | Description -| --------------------------| ----------------- | -------------------------------------------- -| `dry-run` | `false` | disables docker push -| `repo` | *none* | sets repository name for the image (can be a list) -| `username` | *none* | sets username to authenticates with -| `password` | *none* | sets password / token to authenticates with -| `email` | *none* | sets email address to authenticates with -| `registry` | `https://index.docker.io/v1/` | sets docker registry to authenticate with -| `dockerfile` | `Dockerfile` | sets dockerfile to use for the image build -| `tag`/`tags` | *none* | sets repository tags to use for the image -| `platforms` | *none* | sets target platform for build + +| Settings Name | Default | Description | +| ------------------------- | ------------------------------- | ---------------------------------------------------- | +| `dry-run` | `false` | disables docker push | +| `repo` | _none_ | sets repository name for the image (can be a list) | +| `username` | _none_ | sets username to authenticates with | +| `password` | _none_ | sets password / token to authenticates with | +| `aws_access_key_id` | _none_ | sets AWS_ACCESS_KEY_ID for AWS ECR auth | +| `aws_secret_access_key` | _none_ | sets AWS_SECRET_ACCESS_KEY for AWS ECR auth | +| `aws_region` | `us-east-1` | sets AWS_DEFAULT_REGION for AWS ECR auth | +| `password` | _none_ | sets password / token to authenticates with | +| `email` | _none_ | sets email address to authenticates with | +| `registry` | `https://index.docker.io/v1/` | sets docker registry to authenticate with | +| `dockerfile` | `Dockerfile` | sets dockerfile to use for the image build | +| `tag`/`tags` | _none_ | sets repository tags to use for the image | +| `platforms` | _none_ | sets target platform for build | ## auto_tag @@ -49,80 +53,89 @@ If it's not a tag event, and no default branch, automated tags are skipped. ## Examples ```yaml - publish-next-agent: - image: woodpeckerci/plugin-docker-buildx - secrets: [docker_username, docker_password] - settings: - repo: woodpeckerci/woodpecker-agent - dockerfile: docker/Dockerfile.agent.multiarch - platforms: windows/amd64,darwin/amd64,darwin/arm64,freebsd/amd64,linux/amd64,linux/arm64/v8 - tag: next - when: - branch: ${CI_REPO_DEFAULT_BRANCH} - event: push +publish-next-agent: + image: woodpeckerci/plugin-docker-buildx + secrets: [docker_username, docker_password] + settings: + repo: woodpeckerci/woodpecker-agent + dockerfile: docker/Dockerfile.agent.multiarch + platforms: windows/amd64,darwin/amd64,darwin/arm64,freebsd/amd64,linux/amd64,linux/arm64/v8 + tag: next + when: + branch: ${CI_REPO_DEFAULT_BRANCH} + event: push ``` ```yaml - publish: - image: woodpeckerci/plugin-docker-buildx - settings: - platforms: linux/386,linux/amd64,linux/arm/v6,linux/arm64/v8,linux/ppc64le,linux/riscv64,linux/s390x - repo: codeberg.org/${CI_REPO_OWNER}/hello - registry: codeberg.org - tags: latest - username: ${CI_REPO_OWNER} - password: - from_secret: cb_token +publish: + image: woodpeckerci/plugin-docker-buildx + settings: + platforms: linux/386,linux/amd64,linux/arm/v6,linux/arm64/v8,linux/ppc64le,linux/riscv64,linux/s390x + repo: codeberg.org/${CI_REPO_OWNER}/hello + registry: codeberg.org + tags: latest + username: ${CI_REPO_OWNER} + password: + from_secret: cb_token ``` ```yaml - docker-build: - image: woodpeckerci/plugin-docker-buildx - settings: - repo: codeberg.org/${CI_REPO_OWNER}/hello - registry: codeberg.org - dry_run: true - output: type=oci,dest=${CI_REPO_OWNER}-hello.tar +docker-build: + image: woodpeckerci/plugin-docker-buildx + settings: + repo: codeberg.org/${CI_REPO_OWNER}/hello + registry: codeberg.org + dry-run: true + output: type=oci,dest=${CI_REPO_OWNER}-hello.tar ``` ## Advanced Settings -| Settings Name | Default | Description -| --------------------------| ----------------- | -------------------------------------------- -| `mirror` | *none* | sets a registry mirror to pull images -| `storage_driver` | *none* | sets the docker daemon storage driver -| `storage_path` | `/var/lib/docker` | sets the docker daemon storage path -| `bip` | *none* | allows the docker daemon to bride ip address -| `mtu` | *none* | sets docker daemon custom mtu setting -| `custom_dns` | *none* | sets custom docker daemon dns server -| `custom_dns_search` | *none* | sets custom docker daemon dns search domain -| `insecure` | `false` | allows the docker daemon to use insecure registries -| `ipv6` | `false` | enables docker daemon IPv6 support -| `experimental` | `false` | enables docker daemon experimental mode -| `debug` | `false` | enables verbose debug mode for the docker daemon -| `daemon_off` | `false` | disables the startup of the docker daemon -| `buildkit_config` | *none* | sets content of the docker [buildkit TOML config](https://github.com/moby/buildkit/blob/master/docs/buildkitd.toml.md) -| `tags_file` | *none* | overrides the `tags` option with values in a file named `.tags`; multiple tags can be specified separated by a comma -| `context` | `.` | sets the path of the build context to use -| `auto_tag` | `false` | generates tag names automatically based on git branch and git tag, tags supplied via `tags` are additionally added to the auto_tags without suffix -| `default_suffix"`/`auto_tag_suffix`| *none* | generates tag names with the given suffix -| `default_tag` | `latest` | overrides the default tag name used when generating with `auto_tag` enabled -| `label`/`labels` | *none* | sets labels to use for the image in format `=` -| `default_labels`/`auto_labels` | `true` | sets docker image labels based on git information -| `build_args` | *none* | sets custom build arguments for the build -| `build_args_from_env` | *none* | forwards environment variables as custom arguments to the build -| `quiet` | `false` | enables suppression of the build output -| `target` | *none* | sets the build target to use -| `cache_from` | *none* | sets images to consider as cache sources -| `pull_image` | `true` | enforces to pull base image at build time -| `compress` | `false` | enables compression of the build context using gzip -| `config` | *none* | sets content of the docker daemon json config -| `purge` | `true` | enables cleanup of the docker environment at the end of a build -| `no_cache` | `false` | disables the usage of cached intermediate containers -| `add_host` | *none* | sets additional host:ip mapping -| `output` | *none* | sets build output in format `type=[,=]` -| `logins` | *none* | option to log into multiple registries -| `env_file` | *none* | load env vars from specified file + +| Settings Name | Default | Description | +| ------------------------------------- | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | +| `mirror` | _none_ | sets a registry mirror to pull images | +| `storage_driver` | _none_ | sets the docker daemon storage driver | +| `storage_path` | `/var/lib/docker` | sets the docker daemon storage path | +| `bip` | _none_ | allows the docker daemon to bride ip address | +| `mtu` | _none_ | sets docker daemon custom mtu setting | +| `custom_dns` | _none_ | sets custom docker daemon dns server | +| `custom_dns_search` | _none_ | sets custom docker daemon dns search domain | +| `insecure` | `false` | allows the docker daemon to use insecure registries | +| `ipv6` | `false` | enables docker daemon IPv6 support | +| `experimental` | `false` | enables docker daemon experimental mode | +| `debug` | `false` | enables verbose debug mode for the docker daemon | +| `daemon_off` | `false` | disables the startup of the docker daemon | +| `buildkit_debug` | `false` | enables debug output of buildkit | +| `buildkit_config` | _none_ | sets content of the docker[buildkit TOML config](https://github.com/moby/buildkit/blob/master/docs/buildkitd.toml.md) | +| `buildkit_driveropt` | _none_ | adds one or multiple`--driver-opt` buildx arguments for the default buildkit builder instance | +| `tags_file` | _none_ | overrides the`tags` option with values in a file named `.tags`; multiple tags can be specified separated by a newline | +| `context` | `.` | sets the path of the build context to use | +| `auto_tag` | `false` | generates tag names automatically based on git branch and git tag, tags supplied via`tags` are additionally added to the auto_tags without suffix | +| `default_suffix"`/`auto_tag_suffix` | _none_ | generates tag names with the given suffix | +| `default_tag` | `latest` | overrides the default tag name used when generating with`auto_tag` enabled | +| `label`/`labels` | _none_ | sets labels to use for the image in format`=` | +| `default_labels`/`auto_labels` | `true` | sets docker image labels based on git information | +| `build_args` | _none_ | sets custom build arguments for the build | +| `build_args_from_env` | _none_ | forwards environment variables as custom arguments to the build | +| `quiet` | `false` | enables suppression of the build output | +| `target` | _none_ | sets the build target to use | +| `cache_from` | _none_ | sets configuration for cache source | +| `cache_to` | _none_ | sets configuration for cache export | +| `cache_images` | _none_ | a list of images to use as cache. | +| `pull_image` | `true` | enforces to pull base image at build time | +| `compress` | `false` | enables compression of the build context using gzip | +| `config` | _none_ | sets content of the docker daemon json config | +| `purge` | `true` | enables cleanup of the docker environment at the end of a build | +| `no_cache` | `false` | disables the usage of cached intermediate containers | +| `add_host` | _none_ | sets additional host:ip mapping | +| `output` | _none_ | sets build output in format`type=[,=]` | +| `logins` | _none_ | option to log into multiple registries | +| `env_file` | _none_ | load env vars from specified file | +| `ecr_create_repository` | `false` | creates the ECR repository if it does not exist | +| `ecr_lifecycle_policy` | _none_ | AWS ECR lifecycle policy | +| `ecr_repository_policy` | _none_ | AWS ECR repository policy | +| `ecr_scan_on_push` | _none_ | AWS: whether to enable image scanning on push | ## Multi registry push example @@ -137,8 +150,110 @@ settings: username: a6543 password: from_secret: docker_token + mirrors: + - "my-docker-mirror-host.local" - registry: https://codeberg.org username: "6543" password: from_secret: cb_token + - registry: https://.dkr.ecr..amazonaws.com + aws_region: + aws_access_key_id: + from_secret: aws_access_key_id + aws_secret_access_key: + from_secret: aws_secret_access_key +``` + +## Using `plugin-docker-buildx` behind a proxy + +When performing a docker build behind a corporate proxy one needs to pass through the proxy settings to the plugin. + +```yaml +variables: + # proxy config + - proxy_conf: &proxy_conf + - http_proxy: "http://X.Y.Z.Z:3128" + - https_proxy: "http://X.Y.Z.Z:3128" + - no_proxy: ".my-subdomain.com" + # deployment targets + - &publish_repos "codeberg.org/test" + # logins for deployment targets + - publish_logins: &publish_logins + - registry: https://codeberg.org + username: + from_secret: CODEBERG_USER + password: + from_secret: CODEBERG_TOKEN + +steps: + test: + image: woodpeckerci/plugin-docker-buildx:2 + environment: + # adding proxy in env for the plugin runtime itself. + - <<: *proxy_conf + privileged: true + settings: + dry-run: true + repo: *publish_repos + dockerfile: Dockerfile.multi + platforms: linux/amd64 + auto_tag: true + logins: *publish_logins + # Adding custom dns server to lookup internal Docker Hub mirror. + # custom_dns: + # - 192.168.55.31 + # - 192.168.55.32 + # Adding an optional Docker Hub mirror for the nested dockerd. + # mirror: https://my-mirror.example.com + build_args: + # passthrough proxy config to the build process and Dockerfile CMDs itself. + - <<: *proxy_conf + # add driver-opt http config to tell buildkit + buildx to resolve external checksums through a proxy. + buildkit_driveropt: + - "env.http_proxy=http://X.Y.Z.Z:3128" + - "env.https_proxy=http://X.Y.Z.Z:3128" + - "env.no_proxy=.my-subdomain.com" +``` + +## Using cache images + +You can provide a list of images to use for cache. +These cache images are built with mode=max, image-manifest=true, and oci-mediatypes=true. +This is to provide better usage of cache and better compatibility with image stores like Harbor. + +```yaml +steps: + build: + image: woodpeckerci/plugin-docker-buildx + settings: + repo: hari/radiant + cache_images: + - hari/radiant:cache + - harbor.example.com/hari/radiant:cache + logins: + - registry: https://index.docker.io/v1/ + username: hari + password: + from_secret: docker_password + - registry: https://harbor.example.com + username: hari + password: + from_secret: harbor_password +``` + +## Using other cache types + +You can specify cache_to and cache_from to use specific settings. +For example you can configure an s3 object as cache. + +More details can be found [in the docker docs](https://docs.docker.com/build/cache/backends/). + +```yaml +steps: + build: + image: woodpeckerci/plugin-docker-buildx + settings: + repo: hari/radiant + cache_to: type=s3,region=east,bucket=mystuff,name=radiant-cache + cache_from: type=s3,region=east,bucket=mystuff,name=radiant-cache ``` diff --git a/go.mod b/go.mod index f87ce9d..c53b166 100644 --- a/go.mod +++ b/go.mod @@ -3,13 +3,15 @@ module codeberg.org/woodpecker-plugins/plugin-docker-buildx go 1.20 require ( - codeberg.org/6543/go-yaml2json v0.3.0 + codeberg.org/6543/go-yaml2json v1.0.0 github.com/6543/go-version v1.3.1 - github.com/drone-plugins/drone-plugin-lib v0.4.0 + github.com/aws/aws-sdk-go v1.50.13 + github.com/drone-plugins/drone-plugin-lib v0.4.2 github.com/joho/godotenv v1.5.1 - github.com/sirupsen/logrus v1.9.0 - github.com/stretchr/testify v1.8.0 - github.com/urfave/cli/v2 v2.25.7 + github.com/pelletier/go-toml/v2 v2.1.1 + github.com/sirupsen/logrus v1.9.3 + github.com/stretchr/testify v1.8.4 + github.com/urfave/cli/v2 v2.27.1 golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 honnef.co/go/tools v0.4.6 ) @@ -18,12 +20,13 @@ require ( github.com/BurntSushi/toml v1.3.2 // indirect github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a // indirect golang.org/x/mod v0.12.0 // indirect - golang.org/x/sys v0.11.0 // indirect + golang.org/x/sys v0.12.0 // indirect golang.org/x/tools v0.12.1-0.20230825192346-2191a27a6dc5 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index 68ccc5c..4c12125 100644 --- a/go.sum +++ b/go.sum @@ -1,91 +1,79 @@ -codeberg.org/6543/go-yaml2json v0.3.0 h1:BlvjmY0Gous8P+rr8aBdgPYnIfUAqFepF8q7Tp0R5t8= -codeberg.org/6543/go-yaml2json v0.3.0/go.mod h1:mz61q14LWF4ZABrgMEDMmk3t9dPi6zgR1uBh2VKV2RQ= +codeberg.org/6543/go-yaml2json v1.0.0 h1:heGqo9VEi7gY2yNqjj7X4ADs5nzlFIbGsJtgYDLrnig= +codeberg.org/6543/go-yaml2json v1.0.0/go.mod h1:mz61q14LWF4ZABrgMEDMmk3t9dPi6zgR1uBh2VKV2RQ= github.com/6543/go-version v1.3.1 h1:HvOp+Telns7HWJ2Xo/05YXQSB2bE0WmVgbHqwMPZT4U= github.com/6543/go-version v1.3.1/go.mod h1:oqFAHCwtLVUTLdhQmVZWYvaHXTdsbB4SY85at64SQEo= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v1.1.0 h1:ksErzDEI1khOiGPgpwuI7x2ebx/uXQNw7xJpn9Eq1+I= -github.com/BurntSushi/toml v1.1.0/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/aws/aws-sdk-go v1.49.17 h1:Cc+7LgPjKeJkF2SdNo1IkpQ5Dfl9HCZEVw9OP3CPuEI= +github.com/aws/aws-sdk-go v1.49.17/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go v1.49.22 h1:r01+cQJ3cORQI1PJxG8af0jzrZpUOL9L+/3kU2x1geU= +github.com/aws/aws-sdk-go v1.49.22/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go v1.50.5 h1:H2Aadcgwr7a2aqS6ZwcE+l1mA6ZrTseYCvjw2QLmxIA= +github.com/aws/aws-sdk-go v1.50.5/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= +github.com/aws/aws-sdk-go v1.50.13 h1:yeXram2g7q8uKkQkAEeZyk9FmPzxI4UpGwAZGZtEGmM= +github.com/aws/aws-sdk-go v1.50.13/go.mod h1:LF8svs817+Nz+DmiMQKTO3ubZ/6IaTpq3TjupRn3Eqk= github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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.4.0 h1:3l4+N6zfMWnkbPEXKng2o2/MR5mSwTrBih4ZEkkz1lg= -github.com/joho/godotenv v1.4.0/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/drone-plugins/drone-plugin-lib v0.4.2 h1:EiJ3Kco6ypP5noBQqVt1bBbuO1eUAumtPvLTX/NVAYg= +github.com/drone-plugins/drone-plugin-lib v0.4.2/go.mod h1:KwCu92jFjHV3xv2hu5Qg/8zBNvGwbhoJDQw/EwnTvoM= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= -github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= +github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= 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/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/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= -github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ= -github.com/urfave/cli/v2 v2.16.3 h1:gHoFIwpPjoyIMbJp/VFd+/vuD0dAgFK4B6DpEMFJfQk= -github.com/urfave/cli/v2 v2.16.3/go.mod h1:1CNUng3PtjQMtRzJO4FMXBQvkGtuYRxxiR9xMa7jMwI= -github.com/urfave/cli/v2 v2.25.7 h1:VAzn5oq403l5pHjc4OhD54+XGO9cdKVL/7lDjF+iKUs= -github.com/urfave/cli/v2 v2.25.7/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/urfave/cli/v2 v2.27.1 h1:8xSQ6szndafKVRmfyeUMxkNUJQMjL1F2zmsZ+qHpfho= +github.com/urfave/cli/v2 v2.27.1/go.mod h1:8qnjx1vcq5s2/wpsqoZFndg2CE5tNFyrTvS6SinrnYQ= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e h1:qyrTQ++p1afMkO4DPEeLGq/3oTsdlvdH4vqZUBWzUKM= -golang.org/x/exp/typeparams v0.0.0-20220218215828-6cf2b201936e/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a h1:Jw5wfR+h9mnIYH+OtGT2im5wV1YGGDora5vTv/aa5bE= golang.org/x/exp/typeparams v0.0.0-20221208152030-732eee02a75a/go.mod h1:AbB0pIl9nAr9wVwH+Z2ZpaocVmF5I4GyWCDIsVjR0bk= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug= golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= -golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM= -golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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= golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.1.11-0.20220513221640-090b14e8501f h1:OKYpQQVE3DKSc3r3zHVzq46vq5YH7x8xpR3/k9ixmUg= -golang.org/x/tools v0.1.11-0.20220513221640-090b14e8501f/go.mod h1:SgwaegtQh8clINPpECJMqnxLv9I09HLqnW3RMqW0CA4= golang.org/x/tools v0.12.1-0.20230825192346-2191a27a6dc5 h1:Vk4mysSz+GqQK2eqgWbo4zEO89wkeAjJiFIr9bpqa8k= golang.org/x/tools v0.12.1-0.20230825192346-2191a27a6dc5/go.mod h1:Sc0INKfu04TlqNoRA1hgpFZbhYXHPr4V5DzpSBTPqQM= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 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/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= -honnef.co/go/tools v0.3.3 h1:oDx7VAwstgpYpb3wv0oxiZlxY+foCpRAwY7Vk6XpAgA= -honnef.co/go/tools v0.3.3/go.mod h1:jzwdWgg7Jdq75wlfblQxO4neNaFFSvgc1tD5Wv8U0Yw= honnef.co/go/tools v0.4.6 h1:oFEHCKeID7to/3autwsWfnuv69j3NsfcXbvJKuIcep8= honnef.co/go/tools v0.4.6/go.mod h1:+rnGS1THNh8zMwnd2oVOTL9QF6vmfyG6ZXBULae2uc0= diff --git a/plugin/daemon.go b/plugin/daemon.go index 8906b0c..13996b6 100644 --- a/plugin/daemon.go +++ b/plugin/daemon.go @@ -6,11 +6,10 @@ import ( ) const ( - dockerExe = "/usr/local/bin/docker" - dockerdExe = "/usr/local/bin/dockerd" - dockerHome = "/root/.docker/" - buildkitConfig = "/tmp/buildkit.toml" - buildkitConfigTemplate = "[registry.\"%s\"]\n ca=[\"%s\"]\n" + dockerExe = "/usr/local/bin/docker" + dockerdExe = "/usr/local/bin/dockerd" + dockerHome = "/root/.docker/" + buildkitConfig = "/tmp/buildkit.toml" ) func (p Plugin) startDaemon() { diff --git a/plugin/docker.go b/plugin/docker.go index d914692..e966b37 100644 --- a/plugin/docker.go +++ b/plugin/docker.go @@ -41,6 +41,10 @@ func commandBuilder(daemon Daemon) *exec.Cmd { args = append(args, "--config", buildkitConfig) } + for _, driveropt := range daemon.BuildkitDriverOpt.Value() { + args = append(args, "--driver-opt", driveropt) + } + return exec.Command(dockerExe, args...) } @@ -74,11 +78,15 @@ func commandBuild(build Build, dryrun bool) *exec.Cmd { if build.NoCache { args = append(args, "--no-cache") } - for _, arg := range build.CacheFrom.Value() { - args = append(args, "--cache-from", arg) + if build.CacheFrom != "" { + args = append(args, "--cache-from", build.CacheFrom) } - for _, arg := range build.CacheTo.Value() { - args = append(args, "--cache-to", arg) + if build.CacheTo != "" { + args = append(args, "--cache-to", build.CacheTo) + } + for _, arg := range build.CacheImages.Value() { + args = append(args, "--cache-from", arg) + args = append(args, string("--cache-to=type=registry,ref="+arg+",mode=max,image-manifest=true,oci-mediatypes=true")) } for _, arg := range build.ArgsEnv.Value() { addProxyValue(&build, arg) @@ -175,9 +183,6 @@ func commandDaemon(daemon Daemon) *exec.Cmd { 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) } diff --git a/plugin/docker_test.go b/plugin/docker_test.go new file mode 100644 index 0000000..f2b13d1 --- /dev/null +++ b/plugin/docker_test.go @@ -0,0 +1,69 @@ +package plugin + +import ( + "fmt" + "os" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/urfave/cli/v2" +) + +func TestCommandBuilder(t *testing.T) { + tests := []struct { + Name string + Daemon Daemon + Input string + WantedLen int + Skip bool + Excuse string + }{ + { + Name: "Single driver-opt value", + Daemon: Daemon{}, + Input: "no_proxy=*.mydomain", + WantedLen: 1, + }, + { + Name: "Single driver-opt value with comma", + Input: "no_proxy=.mydomain,.sub.domain.com", + WantedLen: 1, + Skip: true, + Excuse: "Can be enabled whenever #94 is fixed.", + }, + } + + for _, test := range tests { + t.Run(test.Name, func(t *testing.T) { + if test.Skip { + t.Skip(fmt.Printf("%v skipped. %v", test.Name, test.Excuse)) + } + // prepare test values to mock plugin call with settings + os.Setenv("PLUGIN_BUILDKIT_DRIVEROPT", test.Input) + + // create dummy cli app to reproduce the issue + app := &cli.App{ + Name: "dummy App", + Usage: "testing inputs", + Version: "0.0.1", + Flags: []cli.Flag{ + &cli.StringSliceFlag{ + Name: "daemon.buildkit-driveropt", + EnvVars: []string{"PLUGIN_BUILDKIT_DRIVEROPT"}, + Usage: "adds optional driver-ops args like 'env.http_proxy'", + Destination: &test.Daemon.BuildkitDriverOpt, + }, + }, + Action: nil, + } + + // need to run the app to resolve the flags + _ = app.Run(nil) + + // call the commandBuilder to prepare the cmd with its args + _ = commandBuilder(test.Daemon) + + assert.Len(t, test.Daemon.BuildkitDriverOpt.Value(), test.WantedLen) + }) + } +} diff --git a/plugin/ecr.go b/plugin/ecr.go new file mode 100644 index 0000000..5409898 --- /dev/null +++ b/plugin/ecr.go @@ -0,0 +1,226 @@ +// Source: https://github.com/drone-plugins/drone-docker/tree/939591f01828eceae54f5768dc7ce08ad0ad0bba/cmd/drone-ecr +package plugin + +import ( + "encoding/base64" + "fmt" + "log" + "os" + "strings" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/aws/credentials/stscreds" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/ecr" +) + +const DefaultRegion = "us-east-1" + +var ( + repo string + assumeRole string + externalID string + ecr_login Login + aws_region string +) + +func (p *Plugin) EcrInit() { + // create a standalone Login object to account for single repo and multi-repo case + if len(p.settings.Logins) >= 1 { + for _, login := range p.settings.Logins { + if strings.Contains(login.Registry, "amazonaws.com") { + ecr_login = login + aws_region = login.Aws_region + + // filter repo containing ecr registry + substrings := make([]string, 0) + for _, repo := range p.settings.Build.Repo.Value() { + substrings = append(substrings, strings.Split(repo, ",")...) + } + filtered := make([]string, 0) + for _, s := range substrings { + if strings.Contains(s, "amazonaws.com") { + filtered = append(filtered, s) + } + } + + // Join the filtered substrings into a comma-separated string + repo = strings.Join(filtered, ",") + + // set the region + if aws_region == "" { + aws_region = DefaultRegion + } + + os.Setenv("AWS_REGION", aws_region) + os.Setenv("AWS_ACCESS_KEY_ID", ecr_login.Aws_access_key_id) + os.Setenv("AWS_SECRET_ACCESS_KEY", ecr_login.Aws_secret_access_key) + + } + } + } else { + ecr_login.Aws_access_key_id = p.settings.AwsAccessKeyId + ecr_login.Aws_secret_access_key = p.settings.AwsSecretAccessKey + aws_region = p.settings.AwsRegion + repo = p.settings.Build.Repo.Value()[0] + + // set the region + if aws_region == "" { + aws_region = DefaultRegion + } + + os.Setenv("AWS_REGION", p.settings.AwsRegion) + os.Setenv("AWS_ACCESS_KEY_ID", p.settings.AwsAccessKeyId) + os.Setenv("AWS_SECRET_ACCESS_KEY", p.settings.AwsSecretAccessKey) + } + // here the env vars are used for authentication + sess, err := session.NewSession(&aws.Config{Region: &aws_region}) + if err != nil { + log.Fatalf("error creating aws session: %v", err) + } + + svc := getECRClient(sess, assumeRole, externalID) + username, password, registry, err := getAuthInfo(svc) + if err != nil { + log.Fatalf("error getting ECR auth: %v", err) + } + + if !strings.HasPrefix(repo, registry) { + repo = fmt.Sprintf("%s/%s", registry, repo) + } + + if p.settings.EcrCreateRepository { + err = ensureRepoExists(svc, trimHostname(repo, registry), p.settings.EcrScanOnPush) + if err != nil { + log.Fatalf("error creating ECR repo: %v", err) + } + err = updateImageScannningConfig(svc, trimHostname(repo, registry), p.settings.EcrScanOnPush) + if err != nil { + log.Fatalf("error updating scan on push for ECR repo: %v", err) + } + } + + if p.settings.EcrLifecyclePolicy != "" { + p, err := os.ReadFile(p.settings.EcrLifecyclePolicy) + if err != nil { + log.Fatal(err) + } + if err := uploadLifeCyclePolicy(svc, string(p), trimHostname(repo, registry)); err != nil { + log.Fatalf("error uploading ECR lifecycle policy: %v", err) + } + } + + if p.settings.EcrRepositoryPolicy != "" { + p, err := os.ReadFile(p.settings.EcrRepositoryPolicy) + if err != nil { + log.Fatal(err) + } + if err := uploadRepositoryPolicy(svc, string(p), trimHostname(repo, registry)); err != nil { + log.Fatalf("error uploading ECR repository policy. %v", err) + } + } + + // set Username and Password for all Login which contain an AWS key + if len(p.settings.Logins) >= 1 { + for i, login := range p.settings.Logins { + if login.Aws_secret_access_key != "" && login.Aws_access_key_id != "" { + p.settings.Logins[i].Username = username + p.settings.Logins[i].Password = password + p.settings.Logins[i].Registry = registry + } + } + } else { + p.settings.DefaultLogin.Username = username + p.settings.DefaultLogin.Password = password + p.settings.DefaultLogin.Registry = registry + } +} + +func trimHostname(repo, registry string) string { + repo = strings.TrimPrefix(repo, registry) + repo = strings.TrimLeft(repo, "/") + return repo +} + +func ensureRepoExists(svc *ecr.ECR, name string, scanOnPush bool) (err error) { + input := &ecr.CreateRepositoryInput{} + input.SetRepositoryName(name) + input.SetImageScanningConfiguration(&ecr.ImageScanningConfiguration{ScanOnPush: &scanOnPush}) + _, err = svc.CreateRepository(input) + if err != nil { + if aerr, ok := err.(awserr.Error); ok && aerr.Code() == ecr.ErrCodeRepositoryAlreadyExistsException { + // eat it, we skip checking for existing to save two requests + err = nil + } + } + + return +} + +func updateImageScannningConfig(svc *ecr.ECR, name string, scanOnPush bool) (err error) { + input := &ecr.PutImageScanningConfigurationInput{} + input.SetRepositoryName(name) + input.SetImageScanningConfiguration(&ecr.ImageScanningConfiguration{ScanOnPush: &scanOnPush}) + _, err = svc.PutImageScanningConfiguration(input) + + return err +} + +func uploadLifeCyclePolicy(svc *ecr.ECR, lifecyclePolicy, name string) (err error) { + input := &ecr.PutLifecyclePolicyInput{} + input.SetLifecyclePolicyText(lifecyclePolicy) + input.SetRepositoryName(name) + _, err = svc.PutLifecyclePolicy(input) + + return err +} + +func uploadRepositoryPolicy(svc *ecr.ECR, repositoryPolicy, name string) (err error) { + input := &ecr.SetRepositoryPolicyInput{} + input.SetPolicyText(repositoryPolicy) + input.SetRepositoryName(name) + _, err = svc.SetRepositoryPolicy(input) + + return err +} + +func getAuthInfo(svc *ecr.ECR) (username, password, registry string, err error) { + var result *ecr.GetAuthorizationTokenOutput + var decoded []byte + + result, err = svc.GetAuthorizationToken(&ecr.GetAuthorizationTokenInput{}) + if err != nil { + return + } + + auth := result.AuthorizationData[0] + token := *auth.AuthorizationToken + decoded, err = base64.StdEncoding.DecodeString(token) + if err != nil { + return + } + + registry = strings.TrimPrefix(*auth.ProxyEndpoint, "https://") + creds := strings.Split(string(decoded), ":") + username = creds[0] + password = creds[1] + return +} + +func getECRClient(sess *session.Session, role, externalId string) *ecr.ECR { + if role == "" { + return ecr.New(sess) + } + if externalId != "" { + return ecr.New(sess, &aws.Config{ + Credentials: stscreds.NewCredentials(sess, role, func(p *stscreds.AssumeRoleProvider) { + p.ExternalID = &externalId + }), + }) + } else { + return ecr.New(sess, &aws.Config{ + Credentials: stscreds.NewCredentials(sess, role), + }) + } +} diff --git a/plugin/impl.go b/plugin/impl.go index d6e16c8..563d03d 100644 --- a/plugin/impl.go +++ b/plugin/impl.go @@ -10,6 +10,7 @@ import ( "strings" "time" + "github.com/pelletier/go-toml/v2" "github.com/sirupsen/logrus" "github.com/urfave/cli/v2" @@ -18,29 +19,37 @@ import ( // 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 - BuildkitConfig string // Docker buildkit config + 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 + BuildkitConfig string // Docker buildkit config + BuildkitDriverOpt cli.StringSlice // Docker buildkit driveropt args + BuildkitDebug bool // Docker buildkit debug setting } // 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 + // Generic + 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 + Mirrors []string // Docker registry mirrors + // ECR + Aws_access_key_id string `json:"aws_access_key_id"` // AWS access key id + Aws_secret_access_key string `json:"aws_secret_access_key"` // AWS secret access key + Aws_region string `json:"aws_region"` // AWS region } // Build defines Docker build parameters. @@ -63,8 +72,9 @@ type Build struct { Target string // Docker build target Output string // Docker build output Pull bool // Docker build pull - CacheFrom cli.StringSlice // Docker build cache-from - CacheTo cli.StringSlice // Docker build cache-to + CacheFrom string // Docker build cache-from + CacheTo string // Docker build cache-to + CacheImages cli.StringSlice // Docker build cache images Compress bool // Docker build compress Repo cli.StringSlice // Docker build repository NoCache bool // Docker build no-cache @@ -74,13 +84,24 @@ type Build struct { // Settings for the Plugin. type Settings struct { - Daemon Daemon - Logins []Login - LoginsRaw string - DefaultLogin Login - Build Build - Dryrun bool - Cleanup bool + // ECR + AwsRegion string `json:"aws_region"` // AWS region + EcrScanOnPush bool `json:"ecr_scan_on_push"` // ECR scan on push + EcrRepositoryPolicy string `json:"ecr_repository_policy"` // ECR repository policy + EcrLifecyclePolicy string `json:"ecr_lifecycle_policy"` // ECR lifecycle policy + EcrCreateRepository bool `json:"ecr_create_repository"` // ECR create repository + AwsAccessKeyId string `json:"aws_access_key_id"` // AWS access key id + AwsSecretAccessKey string `json:"aws_secret_access_key"` // AWS secret access key + + // Generic + Daemon Daemon + Logins []Login + LoginsRaw string + DefaultLogin Login + Build Build + Dryrun bool + Cleanup bool + CustomCertStore string // e.g. for "/etc/docker/certs.d//ca.crt" } func (l Login) anonymous() bool { @@ -98,6 +119,21 @@ func (p *Plugin) InitSettings() error { p.settings.Build.Branch = p.pipeline.Repo.Branch p.settings.Build.Ref = p.pipeline.Commit.Ref + // check if any Login struct contains AWS credentials + for _, login := range p.settings.Logins { + if strings.Contains(login.Registry, "amazonaws.com") { + p.EcrInit() + } + } + + if p.settings.AwsAccessKeyId != "" && p.settings.AwsSecretAccessKey != "" { + p.EcrInit() + } + + if p.settings.DefaultLogin.Registry != "" && p.settings.Daemon.Mirror != "" { + p.settings.DefaultLogin.Mirrors = []string{p.settings.Daemon.Mirror} + } + if len(p.settings.Logins) == 0 { p.settings.Logins = []Login{p.settings.DefaultLogin} } else if !p.settings.DefaultLogin.anonymous() { @@ -174,6 +210,10 @@ func (p *Plugin) Validate() error { p.settings.Build.Labels = *cli.NewStringSlice(p.Labels()...) } + if err := p.generateBuildkitConfig(); err != nil { + return err + } + return nil } @@ -189,31 +229,83 @@ func (p *Plugin) sanitizedUserTags() []string { return tags } -func (p *Plugin) writeBuildkitConfig() error { - // no buildkit config, automatically generate buildkit configuration to use a custom CA certificate for each registry - if p.settings.Daemon.BuildkitConfig == "" && p.settings.Daemon.Registry != "" { - for _, login := range p.settings.Logins { - if registry := login.Registry; registry != "" { - u, err := url.Parse(registry) - if err != nil { - return fmt.Errorf("could not parse registry address: %s: %v", registry, err) - } - if u.Host != "" { - registry = u.Host - } +type BuildkitConfigTOML struct { + Debug bool `toml:"debug,omitempty"` // needs to be public for toml lib to use + Registry map[string]*RegistryInfo `toml:"registry,omitempty"` +} - caPath := fmt.Sprintf("/etc/docker/certs.d/%s/ca.crt", registry) - ca, err := os.Open(caPath) - if err != nil && !os.IsNotExist(err) { - logrus.Warnf("error reading %s: %v", caPath, err) - } else if err == nil { - ca.Close() - p.settings.Daemon.BuildkitConfig += fmt.Sprintf(buildkitConfigTemplate, registry, caPath) +type RegistryInfo struct { + Mirrors []string `toml:"mirrors,omitempty"` + CA []string `toml:"ca,omitempty"` +} + +func (p *Plugin) generateBuildkitConfig() error { + // no buildkit config, automatically generate buildkit configuration + if p.settings.Daemon.BuildkitConfig == "" { + + cfg := BuildkitConfigTOML{} + cfg.Registry = make(map[string]*RegistryInfo) + + if p.settings.Daemon.BuildkitDebug { + cfg.Debug = p.settings.Daemon.BuildkitDebug + logrus.Println("buildkit debug enabled") + } + + // use a custom CA certificate for each registry + if p.settings.Daemon.Registry != "" { + for _, login := range p.settings.Logins { + if registry := login.Registry; registry != "" { + u, err := url.Parse(registry) + if err != nil { + return fmt.Errorf("could not parse registry address: %s: %v", registry, err) + } + if u.Host != "" { + registry = u.Host + } + + // docker hub fix + if registry == "index.docker.io" { + registry = "docker.io" + } + + caPath := fmt.Sprintf("%s/%s/ca.crt", p.settings.CustomCertStore, registry) + ca, err := os.Open(caPath) + if err != nil && !os.IsNotExist(err) { + logrus.Warnf("error reading %s: %v", caPath, err) + } else if err == nil { + ca.Close() + logrus.Infof("found ca file for '%s' registry", registry) + // add registry and ca path to buildkit.toml + if cfg.Registry[registry] == nil { + cfg.Registry[registry] = new(RegistryInfo) + } + cfg.Registry[registry].CA = []string{caPath} + } + + if len(login.Mirrors) != 0 { + if cfg.Registry[registry] == nil { + cfg.Registry[registry] = new(RegistryInfo) + } + cfg.Registry[registry].Mirrors = login.Mirrors + } } } } + + if cfg.Debug || len(cfg.Registry) > 0 { + tomlData, err := toml.Marshal(cfg) + if err != nil { + return fmt.Errorf("error marshaling buildkit.toml: %s", err) + } else { + p.settings.Daemon.BuildkitConfig = string(tomlData) + } + } } + return nil +} + +func (p *Plugin) writeBuildkitConfig() error { // save buildkit config as described if p.settings.Daemon.BuildkitConfig != "" { err := os.WriteFile(buildkitConfig, []byte(p.settings.Daemon.BuildkitConfig), 0o600) diff --git a/plugin/impl_test.go b/plugin/impl_test.go index ebbbb47..4232af0 100644 --- a/plugin/impl_test.go +++ b/plugin/impl_test.go @@ -1,6 +1,8 @@ package plugin import ( + "fmt" + "os" "testing" "codeberg.org/6543/go-yaml2json" @@ -8,7 +10,7 @@ import ( "github.com/urfave/cli/v2" ) -var defaultSettings = Settings{ +var defaultTestSettings = Settings{ Daemon: Daemon{ StoragePath: "/var/lib/docker", }, @@ -22,15 +24,16 @@ var defaultSettings = Settings{ DefaultLogin: Login{ Registry: "https://index.docker.io/v1/", }, - LoginsRaw: "[]", - Cleanup: true, + LoginsRaw: "[]", + Cleanup: true, + CustomCertStore: "/etc/docker/certs.d/", } func TestDefaultLogin(t *testing.T) { - s := defaultSettings + s := defaultTestSettings assert.NoError(t, newSettingsOnly(&s).Validate()) if assert.Len(t, s.Logins, 1) { - assert.EqualValues(t, defaultSettings.DefaultLogin.Registry, s.Logins[0].Registry) + assert.EqualValues(t, defaultTestSettings.DefaultLogin.Registry, s.Logins[0].Registry) } // only use login to auth to registrys @@ -45,11 +48,11 @@ func TestDefaultLogin(t *testing.T) { s.LoginsRaw = string(loginsRaw) assert.NoError(t, newSettingsOnly(&s).Validate()) if assert.Len(t, s.Logins, 2) { - assert.EqualValues(t, defaultSettings.DefaultLogin.Registry, s.Logins[0].Registry) + assert.EqualValues(t, defaultTestSettings.DefaultLogin.Registry, s.Logins[0].Registry) } // mixed login settings ('logins' and 'username', 'password' are used) - s = defaultSettings + s = defaultTestSettings loginsRaw, err = yaml2json.Convert([]byte(` - registry: https://codeberg.org username: cb_username @@ -60,11 +63,11 @@ func TestDefaultLogin(t *testing.T) { s.DefaultLogin.Password = "docker_password" assert.NoError(t, newSettingsOnly(&s).Validate()) if assert.Len(t, s.Logins, 2) { - assert.EqualValues(t, defaultSettings.DefaultLogin.Registry, s.Logins[0].Registry) + assert.EqualValues(t, defaultTestSettings.DefaultLogin.Registry, s.Logins[0].Registry) } // ignore default registry - s = defaultSettings + s = defaultTestSettings loginsRaw, err = yaml2json.Convert([]byte(` - registry: https://codeberg.org username: cb_username @@ -76,3 +79,33 @@ func TestDefaultLogin(t *testing.T) { assert.EqualValues(t, "https://codeberg.org", s.Logins[0].Registry) } } + +func TestWriteBuildkitConfig(t *testing.T) { + settings := defaultTestSettings + assert.NoError(t, newSettingsOnly(&settings).Validate()) + assert.EqualValues(t, "", settings.Daemon.BuildkitConfig) + + settings = defaultTestSettings + settings.Daemon.BuildkitDebug = true + assert.NoError(t, newSettingsOnly(&settings).Validate()) + assert.EqualValues(t, "debug = true\n", settings.Daemon.BuildkitConfig) + + settings = defaultTestSettings + settings.Daemon.Mirror = "mirror.example.com" + assert.NoError(t, newSettingsOnly(&settings).Validate()) + assert.EqualValues(t, "[registry]\n[registry.'docker.io']\nmirrors = ['mirror.example.com']\n", settings.Daemon.BuildkitConfig) + + settings = defaultTestSettings + settings.DefaultLogin.Registry = "codeberg.org" + tmpDir, err := os.MkdirTemp("", "go-test-*") + assert.NoError(t, err) + settings.CustomCertStore = tmpDir + defer os.RemoveAll(tmpDir) + assert.NoError(t, os.Mkdir(tmpDir+"/codeberg.org", os.ModePerm)) + caFile, err := os.Create(tmpDir + "/codeberg.org/" + "ca.crt") + assert.NoError(t, err) + assert.NoError(t, caFile.Close()) + + assert.NoError(t, newSettingsOnly(&settings).Validate()) + assert.EqualValues(t, fmt.Sprintf("[registry]\n[registry.'codeberg.org']\nca = ['%s/codeberg.org/ca.crt']\n", tmpDir), settings.Daemon.BuildkitConfig) +} diff --git a/renovate.json b/renovate.json index bf73d46..b3e1b63 100644 --- a/renovate.json +++ b/renovate.json @@ -1,4 +1,4 @@ { "$schema": "https://docs.renovatebot.com/renovate-schema.json", - "extends": ["github>thegeeklab/renovate-presets"] + "extends": ["local>woodpecker-plugins/renovate-config"] }