Compare commits

..

1 Commits

Author SHA1 Message Date
Fernandez Ludovic
00c219cb6d WIP 2019-04-28 20:06:56 +02:00
71 changed files with 947 additions and 2442 deletions

View File

@ -6,5 +6,5 @@ dumpcerts.sh
acme.json acme.json
acme-backup.json acme-backup.json
traefik-certs-dumper traefik-certs-dumper
build-docker.sh
manifest.json manifest.json
*.Dockerfile

4
.github/FUNDING.yml vendored
View File

@ -1,4 +0,0 @@
github: ldez
ko_fi: ldez_oss
liberapay: ldez
thanks_dev: u/gh/ldez

View File

@ -1,40 +0,0 @@
name: Go Matrix
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
cross:
name: Go
runs-on: ${{ matrix.os }}
env:
CGO_ENABLED: 0
strategy:
matrix:
go-version: [ stable ]
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
# https://github.com/marketplace/actions/checkout
- name: Checkout code
uses: actions/checkout@v4
# https://github.com/marketplace/actions/setup-go-environment
- name: Set up Go ${{ matrix.go-version }}
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
- name: Test
run: go test -v -cover ./...
- name: Build
run: go build -v -ldflags "-s -w" -trimpath

View File

@ -1,41 +0,0 @@
name: Main
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
main:
name: Main Process
runs-on: ubuntu-latest
env:
GO_VERSION: stable
GOLANGCI_LINT_VERSION: v2.0.1
CGO_ENABLED: 0
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
- name: Check and get dependencies
run: |
go mod download
go mod tidy
git diff --exit-code go.mod
git diff --exit-code go.sum
# https://golangci-lint.run/usage/install#other-ci
- name: Install golangci-lint ${{ env.GOLANGCI_LINT_VERSION }}
run: |
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh | sh -s -- -b $(go env GOPATH)/bin ${GOLANGCI_LINT_VERSION}
golangci-lint --version
- name: Make
run: make

View File

@ -1,70 +0,0 @@
name: "Release a tag"
on:
push:
tags:
- v*
jobs:
release:
name: Release Process
runs-on: ubuntu-latest
env:
GO_VERSION: stable
CGO_ENABLED: 0
steps:
# temporary workaround for an error in free disk space action
# https://github.com/jlumbroso/free-disk-space/issues/14
- name: Update Package List and Remove Dotnet
run: |
sudo apt-get update
sudo apt-get remove -y '^dotnet-.*'
# https://github.com/marketplace/actions/free-disk-space-ubuntu
- name: Free Disk Space
uses: jlumbroso/free-disk-space@main
with:
# this might remove tools that are actually needed
tool-cache: false
# all of these default to true
android: true
dotnet: true
haskell: true
large-packages: true
docker-images: true
swap-storage: false
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-go@v5
with:
go-version: ${{ env.GO_VERSION }}
- name: dockerhub-login
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: ghcr-login
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
- name: Run GoReleaser
uses: goreleaser/goreleaser-action@v6
with:
version: latest
args: release -p 1 --clean --timeout=90m
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

2
.gitignore vendored
View File

@ -7,4 +7,4 @@ acme.json
acme-backup.json acme-backup.json
traefik-certs-dumper traefik-certs-dumper
manifest.json manifest.json
/linux-*.Dockerfile /*.Dockerfile

44
.golangci.toml Normal file
View File

@ -0,0 +1,44 @@
[run]
deadline = "2m"
skip-files = []
[linters-settings]
[linters-settings.govet]
check-shadowing = true
[linters-settings.gocyclo]
min-complexity = 12.0
[linters-settings.maligned]
suggest-new = true
[linters-settings.goconst]
min-len = 3.0
min-occurrences = 3.0
[linters-settings.misspell]
locale = "US"
[linters]
enable-all = true
disable = [
"maligned",
"lll",
"gas",
"dupl",
"prealloc",
"scopelint",
]
[issues]
exclude-use-default = false
max-per-linter = 0
max-same-issues = 0
exclude = []
[[issues.exclude-rules]]
path = "version.go"
text = "`(version|commit|date)` is a global variable"
[[issues.exclude-rules]]
path = "cmd/"
linters = ["gochecknoglobals", "gochecknoinits"]

View File

@ -1,108 +0,0 @@
version: "2"
formatters:
enable:
- gci
- gofumpt
settings:
gofumpt:
extra-rules: true
linters:
default: all
disable:
- cyclop # duplicate of gocyclo
- dupl
- err113
- exhaustive
- exhaustruct
- lll
- mnd
- nilnil
- nlreturn
- paralleltest
- prealloc
- rowserrcheck # not relevant (SQL)
- sqlclosecheck # not relevant (SQL)
- testpackage
- tparallel
- varnamelen
- wrapcheck
- wsl
settings:
depguard:
rules:
main:
deny:
- pkg: github.com/instana/testify
desc: not allowed
- pkg: github.com/pkg/errors
desc: Should be replaced by standard lib errors package
forbidigo:
forbid:
- pattern: ^print(ln)?$
- pattern: ^spew\.Print(f|ln)?$
- pattern: ^spew\.Dump$
funlen:
lines: -1
statements: 40
goconst:
min-len: 3
min-occurrences: 3
gocritic:
disabled-checks:
- sloppyReassign
- rangeValCopy
- octalLiteral
- paramTypeCombine # already handle by gofumpt.extra-rules
enabled-tags:
- diagnostic
- style
- performance
settings:
hugeParam:
sizeThreshold: 100
gocyclo:
min-complexity: 12
godox:
keywords:
- FIXME
gomoddirectives:
replace-allow-list:
- github.com/abbot/go-http-auth
- github.com/go-check/check
- github.com/gorilla/mux
- github.com/mailgun/minheap
- github.com/mailgun/multibuf
- github.com/jaguilar/vt100
gosec:
excludes:
- G204 # Subprocess launched with a potential tainted input or cmd arguments
- G301 # Expect directory permissions to be 0750 or less
- G306 # Expect WriteFile permissions to be 0600 or less
govet:
disable:
- fieldalignment
enable-all: true
misspell:
locale: US
exclusions:
presets:
- comments
rules:
- linters:
- gochecknoglobals
- gochecknoinits
path: cmd/
- linters:
- tagalign
path: internal/traefikv[1-3]/
- path: (.+)\.go$
text: 'ST1000: at least one file in a package should have a package comment'
- path: (.+)\.go$
text: 'package-comments: should have a package comment'
issues:
max-issues-per-linter: 0
max-same-issues: 0

View File

@ -1,4 +1,3 @@
version: 2
project_name: traefik-certs-dumper project_name: traefik-certs-dumper
builds: builds:
@ -6,7 +5,7 @@ builds:
ldflags: ldflags:
- -s -w -X github.com/ldez/traefik-certs-dumper/cmd.version={{.Version}} -X github.com/ldez/traefik-certs-dumper/cmd.commit={{.ShortCommit}} -X github.com/ldez/traefik-certs-dumper/cmd.date={{.Date}} - -s -w -X github.com/ldez/traefik-certs-dumper/cmd.version={{.Version}} -X github.com/ldez/traefik-certs-dumper/cmd.commit={{.ShortCommit}} -X github.com/ldez/traefik-certs-dumper/cmd.date={{.Date}}
env: env:
- CGO_ENABLED=0 - GO111MODULE=on
goos: goos:
- linux - linux
- darwin - darwin
@ -34,189 +33,17 @@ changelog:
- '^docs:' - '^docs:'
- '^doc:' - '^doc:'
- '^chore:' - '^chore:'
- '^chore\(deps\):'
- '^test:' - '^test:'
- '^tests:' - '^tests:'
archives: archive:
- id: tcd name_template: '{{ .ProjectName }}_v{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}'
name_template: '{{ .ProjectName }}_v{{ .Version }}_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}' format: tar.gz
formats: [ 'tar.gz' ] format_overrides:
format_overrides: - goos: windows
- goos: windows format: zip
formats: [ 'zip' ] files:
files: - LICENSE
- LICENSE
docker_manifests: #release:
- name_template: 'ldez/traefik-certs-dumper:{{ .Tag }}' # disable: true
image_templates:
- 'ldez/traefik-certs-dumper:{{ .Tag }}-amd64'
- 'ldez/traefik-certs-dumper:{{ .Tag }}-arm64'
- 'ldez/traefik-certs-dumper:{{ .Tag }}-armv7'
- 'ldez/traefik-certs-dumper:{{ .Tag }}-armv6'
- 'ldez/traefik-certs-dumper:{{ .Tag }}-386'
- name_template: 'ldez/traefik-certs-dumper:latest'
image_templates:
- 'ldez/traefik-certs-dumper:{{ .Tag }}-amd64'
- 'ldez/traefik-certs-dumper:{{ .Tag }}-arm64'
- 'ldez/traefik-certs-dumper:{{ .Tag }}-armv7'
- 'ldez/traefik-certs-dumper:{{ .Tag }}-armv6'
- 'ldez/traefik-certs-dumper:{{ .Tag }}-386'
- name_template: 'ldez/traefik-certs-dumper:v{{ .Major }}.{{ .Minor }}'
image_templates:
- 'ldez/traefik-certs-dumper:v{{ .Major }}.{{ .Minor }}-amd64'
- 'ldez/traefik-certs-dumper:v{{ .Major }}.{{ .Minor }}-arm64'
- 'ldez/traefik-certs-dumper:v{{ .Major }}.{{ .Minor }}-armv7'
- 'ldez/traefik-certs-dumper:v{{ .Major }}.{{ .Minor }}-armv6'
- 'ldez/traefik-certs-dumper:v{{ .Major }}.{{ .Minor }}-386'
- name_template: 'ghcr.io/ldez/traefik-certs-dumper:{{ .Tag }}'
image_templates:
- 'ghcr.io/ldez/traefik-certs-dumper:{{ .Tag }}-amd64'
- 'ghcr.io/ldez/traefik-certs-dumper:{{ .Tag }}-arm64'
- 'ghcr.io/ldez/traefik-certs-dumper:{{ .Tag }}-armv7'
- 'ghcr.io/ldez/traefik-certs-dumper:{{ .Tag }}-armv6'
- 'ghcr.io/ldez/traefik-certs-dumper:{{ .Tag }}-386'
- name_template: 'ghcr.io/ldez/traefik-certs-dumper:latest'
image_templates:
- 'ghcr.io/ldez/traefik-certs-dumper:{{ .Tag }}-amd64'
- 'ghcr.io/ldez/traefik-certs-dumper:{{ .Tag }}-arm64'
- 'ghcr.io/ldez/traefik-certs-dumper:{{ .Tag }}-armv7'
- 'ghcr.io/ldez/traefik-certs-dumper:{{ .Tag }}-armv6'
- 'ghcr.io/ldez/traefik-certs-dumper:{{ .Tag }}-386'
- name_template: 'ghcr.io/ldez/traefik-certs-dumper:v{{ .Major }}.{{ .Minor }}'
image_templates:
- 'ghcr.io/ldez/traefik-certs-dumper:v{{ .Major }}.{{ .Minor }}-amd64'
- 'ghcr.io/ldez/traefik-certs-dumper:v{{ .Major }}.{{ .Minor }}-arm64'
- 'ghcr.io/ldez/traefik-certs-dumper:v{{ .Major }}.{{ .Minor }}-armv7'
- 'ghcr.io/ldez/traefik-certs-dumper:v{{ .Major }}.{{ .Minor }}-armv6'
- 'ghcr.io/ldez/traefik-certs-dumper:v{{ .Major }}.{{ .Minor }}-386'
dockers:
- use: buildx
goos: linux
goarch: amd64
dockerfile: buildx.Dockerfile
image_templates:
- 'ldez/traefik-certs-dumper:latest-amd64'
- 'ldez/traefik-certs-dumper:{{ .Tag }}-amd64'
- 'ldez/traefik-certs-dumper:v{{ .Major }}.{{ .Minor }}-amd64'
- 'ghcr.io/ldez/traefik-certs-dumper:latest-amd64'
- 'ghcr.io/ldez/traefik-certs-dumper:{{ .Tag }}-amd64'
- 'ghcr.io/ldez/traefik-certs-dumper:v{{ .Major }}.{{ .Minor }}-amd64'
build_flag_templates:
- '--pull'
# https://github.com/opencontainers/image-spec/blob/main/annotations.md#pre-defined-annotation-keys
- '--label=org.opencontainers.image.title={{.ProjectName}}'
- '--label=org.opencontainers.image.description=Dump ACME data from Traefik to certificates'
- '--label=org.opencontainers.image.source={{.GitURL}}'
- '--label=org.opencontainers.image.url={{.GitURL}}'
- '--label=org.opencontainers.image.documentation=https://github.com/ldez/traefik-certs-dumper'
- '--label=org.opencontainers.image.created={{.Date}}'
- '--label=org.opencontainers.image.revision={{.FullCommit}}'
- '--label=org.opencontainers.image.version={{.Version}}'
- '--platform=linux/amd64'
- use: buildx
goos: linux
goarch: arm64
dockerfile: buildx.Dockerfile
image_templates:
- 'ldez/traefik-certs-dumper:latest-arm64'
- 'ldez/traefik-certs-dumper:latest-arm.v8' # only for compatibility with Seihon
- 'ldez/traefik-certs-dumper:{{ .Tag }}-arm64'
- 'ldez/traefik-certs-dumper:{{ .Tag }}-arm.v8' # only for compatibility with Seihon
- 'ldez/traefik-certs-dumper:v{{ .Major }}.{{ .Minor }}-arm64'
- 'ghcr.io/ldez/traefik-certs-dumper:latest-arm64'
- 'ghcr.io/ldez/traefik-certs-dumper:{{ .Tag }}-arm64'
- 'ghcr.io/ldez/traefik-certs-dumper:v{{ .Major }}.{{ .Minor }}-arm64'
build_flag_templates:
- '--pull'
# https://github.com/opencontainers/image-spec/blob/main/annotations.md#pre-defined-annotation-keys
- '--label=org.opencontainers.image.title={{.ProjectName}}'
- '--label=org.opencontainers.image.description=Dump ACME data from Traefik to certificates'
- '--label=org.opencontainers.image.source={{.GitURL}}'
- '--label=org.opencontainers.image.url={{.GitURL}}'
- '--label=org.opencontainers.image.documentation=https://github.com/ldez/traefik-certs-dumper'
- '--label=org.opencontainers.image.created={{.Date}}'
- '--label=org.opencontainers.image.revision={{.FullCommit}}'
- '--label=org.opencontainers.image.version={{.Version}}'
- '--platform=linux/arm64'
- use: buildx
goos: linux
goarch: arm
goarm: '7'
dockerfile: buildx.Dockerfile
image_templates:
- 'ldez/traefik-certs-dumper:latest-armv7'
- 'ldez/traefik-certs-dumper:latest-arm.v7' # only for compatibility with Seihon
- 'ldez/traefik-certs-dumper:{{ .Tag }}-armv7'
- 'ldez/traefik-certs-dumper:{{ .Tag }}-arm.v7' # only for compatibility with Seihon
- 'ldez/traefik-certs-dumper:v{{ .Major }}.{{ .Minor }}-armv7'
- 'ghcr.io/ldez/traefik-certs-dumper:latest-armv7'
- 'ghcr.io/ldez/traefik-certs-dumper:{{ .Tag }}-armv7'
- 'ghcr.io/ldez/traefik-certs-dumper:v{{ .Major }}.{{ .Minor }}-armv7'
build_flag_templates:
- '--pull'
# https://github.com/opencontainers/image-spec/blob/main/annotations.md#pre-defined-annotation-keys
- '--label=org.opencontainers.image.title={{.ProjectName}}'
- '--label=org.opencontainers.image.description=Dump ACME data from Traefik to certificates'
- '--label=org.opencontainers.image.source={{.GitURL}}'
- '--label=org.opencontainers.image.url={{.GitURL}}'
- '--label=org.opencontainers.image.documentation=https://github.com/ldez/traefik-certs-dumper'
- '--label=org.opencontainers.image.created={{.Date}}'
- '--label=org.opencontainers.image.revision={{.FullCommit}}'
- '--label=org.opencontainers.image.version={{.Version}}'
- '--platform=linux/arm/v7'
- use: buildx
goos: linux
goarch: arm
goarm: '6'
dockerfile: buildx.Dockerfile
image_templates:
- 'ldez/traefik-certs-dumper:latest-armv6'
- 'ldez/traefik-certs-dumper:latest-arm.v6' # only for compatibility with Seihon
- 'ldez/traefik-certs-dumper:{{ .Tag }}-armv6'
- 'ldez/traefik-certs-dumper:{{ .Tag }}-arm.v6' # only for compatibility with Seihon
- 'ldez/traefik-certs-dumper:v{{ .Major }}.{{ .Minor }}-armv6'
- 'ghcr.io/ldez/traefik-certs-dumper:latest-armv6'
- 'ghcr.io/ldez/traefik-certs-dumper:{{ .Tag }}-armv6'
- 'ghcr.io/ldez/traefik-certs-dumper:v{{ .Major }}.{{ .Minor }}-armv6'
build_flag_templates:
- '--pull'
# https://github.com/opencontainers/image-spec/blob/main/annotations.md#pre-defined-annotation-keys
- '--label=org.opencontainers.image.title={{.ProjectName}}'
- '--label=org.opencontainers.image.description=Dump ACME data from Traefik to certificates'
- '--label=org.opencontainers.image.source={{.GitURL}}'
- '--label=org.opencontainers.image.url={{.GitURL}}'
- '--label=org.opencontainers.image.documentation=https://github.com/ldez/traefik-certs-dumper'
- '--label=org.opencontainers.image.created={{.Date}}'
- '--label=org.opencontainers.image.revision={{.FullCommit}}'
- '--label=org.opencontainers.image.version={{.Version}}'
- '--platform=linux/arm/v6'
- use: buildx
goos: linux
goarch: '386'
dockerfile: buildx.Dockerfile
image_templates:
- 'ldez/traefik-certs-dumper:latest-386'
- 'ldez/traefik-certs-dumper:{{ .Tag }}-386'
- 'ldez/traefik-certs-dumper:v{{ .Major }}.{{ .Minor }}-386'
- 'ghcr.io/ldez/traefik-certs-dumper:latest-386'
- 'ghcr.io/ldez/traefik-certs-dumper:{{ .Tag }}-386'
- 'ghcr.io/ldez/traefik-certs-dumper:v{{ .Major }}.{{ .Minor }}-386'
build_flag_templates:
- '--pull'
# https://github.com/opencontainers/image-spec/blob/main/annotations.md#pre-defined-annotation-keys
- '--label=org.opencontainers.image.title={{.ProjectName}}'
- '--label=org.opencontainers.image.description=Dump ACME data from Traefik to certificates'
- '--label=org.opencontainers.image.source={{.GitURL}}'
- '--label=org.opencontainers.image.url={{.GitURL}}'
- '--label=org.opencontainers.image.documentation=https://github.com/ldez/traefik-certs-dumper'
- '--label=org.opencontainers.image.created={{.Date}}'
- '--label=org.opencontainers.image.revision={{.FullCommit}}'
- '--label=org.opencontainers.image.version={{.Version}}'
- '--platform=linux/386'

48
.travis.yml Normal file
View File

@ -0,0 +1,48 @@
dist: xenial
language: go
go:
- 1.11.x
- 1.x
services:
- docker
env:
- GO111MODULE=on
notifications:
email:
on_success: never
on_failure: change
before_install:
# Install linters and misspell
- curl -sfL https://install.goreleaser.com/github.com/golangci/golangci-lint.sh | bash -s -- -b $GOPATH/bin v1.15.0
- golangci-lint --version
install:
- echo "TRAVIS_GO_VERSION=$TRAVIS_GO_VERSION"
- go mod download
before_deploy:
- >
if ! [ "$BEFORE_DEPLOY_RUN" ]; then
export BEFORE_DEPLOY_RUN=1;
echo "${DOCKER_PASSWORD}" | docker login -u "${DOCKER_USERNAME}" --password-stdin
fi
deploy:
- provider: script
skip_cleanup: true
script: curl -sL https://git.io/goreleaser | bash
on:
tags: true
condition: $TRAVIS_GO_VERSION =~ ^1\.x$
- provider: script
skip_cleanup: true
script: make publish-images
on:
tags: true
condition: $TRAVIS_GO_VERSION =~ ^1\.x$

26
Dockerfile Normal file
View File

@ -0,0 +1,26 @@
FROM golang:1-alpine as builder
ARG RUNTIME_HASH
ARG GOARCH
ARG GOARM
ARG GOOS
RUN apk --update upgrade \
&& apk --no-cache --no-progress add git make gcc musl-dev
WORKDIR /go/src/github.com/ldez/traefik-certs-dumper
ENV GO111MODULE on
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN GOARCH=${GOARCH} GOARM=${GOARM} GOOS=${GOOS} make build
FROM alpine:3.9${RUNTIME_HASH}
RUN apk --update upgrade \
&& apk --no-cache --no-progress add ca-certificates
COPY --from=builder /go/src/github.com/ldez/traefik-certs-dumper/traefik-certs-dumper /usr/bin/traefik-certs-dumper
ENTRYPOINT ["/usr/bin/traefik-certs-dumper"]

View File

@ -1,4 +1,4 @@
Copyright 2019-2024 Fernandez Ludovic Copyright 2019 Fernandez Ludovic
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.

View File

@ -1,7 +1,5 @@
.PHONY: default clean checks test build .PHONY: default clean checks test build
export GO111MODULE=on
TAG_NAME := $(shell git tag -l --contains HEAD) TAG_NAME := $(shell git tag -l --contains HEAD)
SHA := $(shell git rev-parse --short HEAD) SHA := $(shell git rev-parse --short HEAD)
VERSION := $(if $(TAG_NAME),$(TAG_NAME),$(SHA)) VERSION := $(if $(TAG_NAME),$(TAG_NAME),$(SHA))
@ -23,5 +21,8 @@ build: clean
checks: checks:
golangci-lint run golangci-lint run
doc: publish-images:
go run . doc go run ./internal/multiarch.go --version="$(TAG_NAME)"
go run ./internal/multiarch.go --version="latest"
# VERSION=$(TAG_NAME) ./build-docker.sh
# VERSION="latest" ./build-docker.sh

88
build-docker.sh Executable file
View File

@ -0,0 +1,88 @@
#!/usr/bin/env bash
set -o errexit
set -o pipefail
VERSION=v666
# safe guard
#if [ -n "$TRAVIS_TAG" ] && [ -n "$VERSION" ]; then
# echo "Deploying..."
#else
# echo "Skipping deploy"
# exit 0
#fi
# base docker image name
IMAGE_NAME="ldez/traefik-certs-dumper"
# only linux for now
OS=linux
# target platforms in docker manifest notation
declare -a PLATFORMS=( "amd64" "arm.v6" "arm.v7" "arm64")
# images from Dockerfile
FROM_IMAGE=$(grep "{RUNTIME_HASH}" < Dockerfile | sed "s/FROM //" | sed 's/\$.*//')
# manifest cache file
MANIFEST_FILE=/tmp/tcd-manifest.${FROM_IMAGE}.json
# get platform image hash from docker manifest
function platformHash () {
local ARCHITECTURE VARIANT HASH
read -r ARCHITECTURE VARIANT <<< "$@"
if [ -z "$VARIANT" ]; then
HASH=$(jq -r ".manifests[] | select(.platform.architecture == \"$ARCHITECTURE\") | .digest" < "$MANIFEST_FILE")
else
HASH=$(jq -r ".manifests[] | select(.platform.architecture == \"$ARCHITECTURE\" and .platform.variant == \"$VARIANT\") | .digest" < "$MANIFEST_FILE")
fi
echo "$HASH"
}
# get manifest
if [ ! -f "$MANIFEST_FILE" ]; then
docker pull "$FROM_IMAGE"
echo "docker manifest inspect $FROM_IMAGE"
DOCKER_CLI_EXPERIMENTAL=enabled docker manifest inspect "$FROM_IMAGE" > "$MANIFEST_FILE"
fi
# create and push images
for platform in "${PLATFORMS[@]}"; do
# split architecture.version
IFS='.' read -r ARCHITECTURE VARIANT <<< "$platform"
# add xargs to trim whitespace
RUNTIME_HASH=$(platformHash "$ARCHITECTURE" "$VARIANT")
# arm architectures flavors, strip "v" prefix
GOARM=${VARIANT:1}
# build for target runtime image and architecture
# echo "docker build --build-arg=RUNTIME_HASH=@${RUNTIME_HASH} --build-arg=GOARCH=${ARCHITECTURE} --build-arg=GOARM=${GOARM} -t $IMAGE_NAME:${VERSION}-$platform" .
docker build --build-arg="RUNTIME_HASH=@${RUNTIME_HASH}" --build-arg="GOARCH=${ARCHITECTURE}" --build-arg="GOARM=${GOARM}" -t "$IMAGE_NAME:${VERSION}-$platform" .
# push images
echo "docker push $IMAGE_NAME:${VERSION}-$platform"
# docker push "$IMAGE_NAME:${VERSION}-$platform"
done
# create manifest
TAG_LIST=$(printf "$IMAGE_NAME:${VERSION}-%s " "${PLATFORMS[@]}")
# shellcheck disable=SC2086
echo "docker manifest create --amend $IMAGE_NAME:${VERSION} $TAG_LIST"
#DOCKER_CLI_EXPERIMENTAL=enabled docker manifest create --amend "$IMAGE_NAME:${VERSION}" $TAG_LIST
for platform in "${PLATFORMS[@]}"; do
# split architecture.version
IFS='.' read -r ARCHITECTURE VARIANT <<< "$platform"
echo "docker manifest annotate $IMAGE_NAME:${VERSION} $IMAGE_NAME:${VERSION}-$platform --os $OS --arch $ARCHITECTURE --variant $VARIANT"
# DOCKER_CLI_EXPERIMENTAL=enabled docker manifest annotate "$IMAGE_NAME:${VERSION}" "$IMAGE_NAME:${VERSION}-$platform" --os "$OS" --arch "$ARCHITECTURE" --variant "$VARIANT"
done
# push manifest
echo "docker manifest push $IMAGE_NAME:${VERSION}"
#DOCKER_CLI_EXPERIMENTAL=enabled docker manifest push "$IMAGE_NAME:${VERSION}"

View File

@ -1,9 +0,0 @@
# syntax=docker/dockerfile:1.4
FROM alpine:3
RUN apk --no-cache --no-progress add git ca-certificates tzdata jq \
&& rm -rf /var/cache/apk/*
COPY traefik-certs-dumper /usr/bin/traefik-certs-dumper
ENTRYPOINT ["/usr/bin/traefik-certs-dumper"]

View File

@ -1,16 +1,14 @@
package cmd package cmd
import ( import (
"context" "github.com/abronan/valkeyrie/store"
"time" "github.com/abronan/valkeyrie/store/boltdb"
"github.com/kvtools/boltdb"
"github.com/ldez/traefik-certs-dumper/v2/dumper" "github.com/ldez/traefik-certs-dumper/v2/dumper"
"github.com/ldez/traefik-certs-dumper/v2/dumper/kv" "github.com/ldez/traefik-certs-dumper/v2/dumper/kv"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
// boltdbCmd represents the boltdb command. // boltdbCmd represents the boltdb command
var boltdbCmd = &cobra.Command{ var boltdbCmd = &cobra.Command{
Use: "boltdb", Use: "boltdb",
Short: "Dump the content of BoltDB.", Short: "Dump the content of BoltDB.",
@ -31,20 +29,11 @@ func boltdbRun(baseConfig *dumper.BaseConfig, cmd *cobra.Command) error {
return err return err
} }
connectionTimeout, err := cmd.Flags().GetInt("connection-timeout") config.Options.Bucket = cmd.Flag("bucket").Value.String()
if err != nil { config.Options.PersistConnection, _ = cmd.Flags().GetBool("persist-connection")
return err
}
persistConnection, _ := cmd.Flags().GetBool("persist-connection") config.Backend = store.BOLTDB
boltdb.Register()
config.Options = &boltdb.Config{ return kv.Dump(config, baseConfig)
Bucket: cmd.Flag("bucket").Value.String(),
PersistConnection: persistConnection,
ConnectionTimeout: time.Duration(connectionTimeout) * time.Second,
}
config.StoreName = boltdb.StoreName
return kv.Dump(context.Background(), config, baseConfig)
} }

View File

@ -1,16 +1,14 @@
package cmd package cmd
import ( import (
"context" "github.com/abronan/valkeyrie/store"
"time" "github.com/abronan/valkeyrie/store/consul"
"github.com/kvtools/consul"
"github.com/ldez/traefik-certs-dumper/v2/dumper" "github.com/ldez/traefik-certs-dumper/v2/dumper"
"github.com/ldez/traefik-certs-dumper/v2/dumper/kv" "github.com/ldez/traefik-certs-dumper/v2/dumper/kv"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
// consulCmd represents the consul command. // consulCmd represents the consul command
var consulCmd = &cobra.Command{ var consulCmd = &cobra.Command{
Use: "consul", Use: "consul",
Short: "Dump the content of Consul.", Short: "Dump the content of Consul.",
@ -30,24 +28,10 @@ func consulRun(baseConfig *dumper.BaseConfig, cmd *cobra.Command) error {
return err return err
} }
tlsConfig, err := createTLSConfig(cmd) config.Options.Token = cmd.Flag("token").Value.String()
if err != nil {
return err
}
connectionTimeout, err := cmd.Flags().GetInt("connection-timeout") config.Backend = store.CONSUL
if err != nil { consul.Register()
return err
}
config.Options = &consul.Config{ return kv.Dump(config, baseConfig)
TLS: tlsConfig,
ConnectionTimeout: time.Duration(connectionTimeout) * time.Second,
Token: cmd.Flag("token").Value.String(),
Namespace: "",
}
config.StoreName = consul.StoreName
return kv.Dump(context.Background(), config, baseConfig)
} }

View File

@ -5,12 +5,12 @@ import (
"github.com/spf13/cobra/doc" "github.com/spf13/cobra/doc"
) )
// docCmd represents the doc command. // docCmd represents the doc command
var docCmd = &cobra.Command{ var docCmd = &cobra.Command{
Use: "doc", Use: "doc",
Short: "Generate documentation", Short: "Generate documentation",
Hidden: true, Hidden: true,
RunE: func(_ *cobra.Command, _ []string) error { RunE: func(cmd *cobra.Command, args []string) error {
return doc.GenMarkdownTree(rootCmd, "./docs") return doc.GenMarkdownTree(rootCmd, "./docs")
}, },
} }

View File

@ -1,17 +1,16 @@
package cmd package cmd
import ( import (
"context"
"time" "time"
"github.com/kvtools/etcdv2" "github.com/abronan/valkeyrie/store"
"github.com/kvtools/etcdv3" "github.com/abronan/valkeyrie/store/etcd/v2"
"github.com/ldez/traefik-certs-dumper/v2/dumper" "github.com/ldez/traefik-certs-dumper/v2/dumper"
"github.com/ldez/traefik-certs-dumper/v2/dumper/kv" "github.com/ldez/traefik-certs-dumper/v2/dumper/kv"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
// etcdCmd represents the etcd command. // etcdCmd represents the etcd command
var etcdCmd = &cobra.Command{ var etcdCmd = &cobra.Command{
Use: "etcd", Use: "etcd",
Short: "Dump the content of etcd.", Short: "Dump the content of etcd.",
@ -23,7 +22,6 @@ func init() {
kvCmd.AddCommand(etcdCmd) kvCmd.AddCommand(etcdCmd)
etcdCmd.Flags().Int("sync-period", 0, "Sync period for etcd in seconds.") etcdCmd.Flags().Int("sync-period", 0, "Sync period for etcd in seconds.")
etcdCmd.Flags().String("etcd-version", "etcd", "The etcd version can be: 'etcd' or 'etcdv3'.")
} }
func etcdRun(baseConfig *dumper.BaseConfig, cmd *cobra.Command) error { func etcdRun(baseConfig *dumper.BaseConfig, cmd *cobra.Command) error {
@ -32,47 +30,14 @@ func etcdRun(baseConfig *dumper.BaseConfig, cmd *cobra.Command) error {
return err return err
} }
backend, err := cmd.Flags().GetString("etcd-version")
if err != nil {
return err
}
tlsConfig, err := createTLSConfig(cmd)
if err != nil {
return err
}
synPeriod, err := cmd.Flags().GetInt("sync-period") synPeriod, err := cmd.Flags().GetInt("sync-period")
if err != nil { if err != nil {
return err return err
} }
config.Options.SyncPeriod = time.Duration(synPeriod) * time.Second
connectionTimeout, err := cmd.Flags().GetInt("connection-timeout") config.Backend = store.ETCD
if err != nil { etcd.Register()
return err
}
switch backend { return kv.Dump(config, baseConfig)
case "etcdv3":
config.Options = &etcdv3.Config{
TLS: tlsConfig,
ConnectionTimeout: time.Duration(connectionTimeout) * time.Second,
SyncPeriod: time.Duration(synPeriod) * time.Second,
Username: cmd.Flag("password").Value.String(),
Password: cmd.Flag("username").Value.String(),
}
config.StoreName = etcdv3.StoreName
default:
config.Options = &etcdv2.Config{
TLS: tlsConfig,
ConnectionTimeout: time.Duration(connectionTimeout) * time.Second,
SyncPeriod: time.Duration(synPeriod) * time.Second,
Username: cmd.Flag("password").Value.String(),
Password: cmd.Flag("username").Value.String(),
}
config.StoreName = etcdv2.StoreName
}
return kv.Dump(context.Background(), config, baseConfig)
} }

View File

@ -1,13 +1,12 @@
package cmd package cmd
import ( import (
"context"
"github.com/ldez/traefik-certs-dumper/v2/dumper" "github.com/ldez/traefik-certs-dumper/v2/dumper"
"github.com/ldez/traefik-certs-dumper/v2/dumper/file" "github.com/ldez/traefik-certs-dumper/v2/dumper/file"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
// fileCmd represents the file command
var fileCmd = &cobra.Command{ var fileCmd = &cobra.Command{
Use: "file", Use: "file",
Short: `Dump the content of the "acme.json" file.`, Short: `Dump the content of the "acme.json" file.`,
@ -15,9 +14,7 @@ var fileCmd = &cobra.Command{
RunE: runE(func(baseConfig *dumper.BaseConfig, cmd *cobra.Command) error { RunE: runE(func(baseConfig *dumper.BaseConfig, cmd *cobra.Command) error {
acmeFile := cmd.Flag("source").Value.String() acmeFile := cmd.Flag("source").Value.String()
baseConfig.Version = cmd.Flag("version").Value.String() return file.Dump(acmeFile, baseConfig)
return file.Dump(context.Background(), acmeFile, baseConfig)
}), }),
} }
@ -25,5 +22,4 @@ func init() {
rootCmd.AddCommand(fileCmd) rootCmd.AddCommand(fileCmd)
fileCmd.Flags().String("source", "./acme.json", "Path to 'acme.json' file.") fileCmd.Flags().String("source", "./acme.json", "Path to 'acme.json' file.")
fileCmd.Flags().String("version", "", "Traefik version. If empty use v1. Possible values: 'v2', 'v3'.")
} }

View File

@ -3,16 +3,17 @@ package cmd
import ( import (
"crypto/tls" "crypto/tls"
"crypto/x509" "crypto/x509"
"errors"
"fmt" "fmt"
"io/ioutil"
"os" "os"
"path/filepath" "time"
"github.com/abronan/valkeyrie/store"
"github.com/ldez/traefik-certs-dumper/v2/dumper/kv" "github.com/ldez/traefik-certs-dumper/v2/dumper/kv"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
// kvCmd represents the kv command. // kvCmd represents the kv command
var kvCmd = &cobra.Command{ var kvCmd = &cobra.Command{
Use: "kv", Use: "kv",
Short: `Dump the content of a KV store.`, Short: `Dump the content of a KV store.`,
@ -25,7 +26,6 @@ func init() {
kvCmd.PersistentFlags().StringSlice("endpoints", []string{"localhost:8500"}, "List of endpoints.") kvCmd.PersistentFlags().StringSlice("endpoints", []string{"localhost:8500"}, "List of endpoints.")
kvCmd.PersistentFlags().Int("connection-timeout", 0, "Connection timeout in seconds.") kvCmd.PersistentFlags().Int("connection-timeout", 0, "Connection timeout in seconds.")
kvCmd.PersistentFlags().String("prefix", "traefik", "Prefix used for KV store.") kvCmd.PersistentFlags().String("prefix", "traefik", "Prefix used for KV store.")
kvCmd.PersistentFlags().String("suffix", kv.DefaultStoreKeySuffix, "Suffix/Storage used for KV store.")
kvCmd.PersistentFlags().String("password", "", "Password for connection.") kvCmd.PersistentFlags().String("password", "", "Password for connection.")
kvCmd.PersistentFlags().String("username", "", "Username for connection.") kvCmd.PersistentFlags().String("username", "", "Username for connection.")
@ -43,10 +43,25 @@ func getKvConfig(cmd *cobra.Command) (*kv.Config, error) {
return nil, err return nil, err
} }
connectionTimeout, err := cmd.Flags().GetInt("connection-timeout")
if err != nil {
return nil, err
}
tlsConfig, err := createTLSConfig(cmd)
if err != nil {
return nil, err
}
return &kv.Config{ return &kv.Config{
Endpoints: endpoints, Endpoints: endpoints,
Prefix: cmd.Flag("prefix").Value.String(), Prefix: cmd.Flag("prefix").Value.String(),
Suffix: cmd.Flag("suffix").Value.String(), Options: &store.Config{
ConnectionTimeout: time.Duration(connectionTimeout) * time.Second,
Username: cmd.Flag("password").Value.String(),
Password: cmd.Flag("username").Value.String(),
TLS: tlsConfig,
},
}, nil }, nil
} }
@ -69,19 +84,19 @@ func createTLSConfig(cmd *cobra.Command) (*tls.Config, error) {
privateKey := cmd.Flag("tls.key").Value.String() privateKey := cmd.Flag("tls.key").Value.String()
certContent := cmd.Flag("tls.cert").Value.String() certContent := cmd.Flag("tls.cert").Value.String()
if !insecureSkipVerify && (certContent == "" || privateKey == "") { if !insecureSkipVerify && (len(certContent) == 0 || len(privateKey) == 0) {
return nil, errors.New("TLS Certificate or Key file must be set when TLS configuration is created") return nil, fmt.Errorf("TLS Certificate or Key file must be set when TLS configuration is created")
} }
cert, err := getCertificate(privateKey, certContent) cert, err := getCertificate(privateKey, certContent)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to load TLS keypair: %w", err) return nil, fmt.Errorf("failed to load TLS keypair: %s", err)
} }
return &tls.Config{ return &tls.Config{
Certificates: []tls.Certificate{cert}, Certificates: []tls.Certificate{cert},
RootCAs: caPool, RootCAs: caPool,
InsecureSkipVerify: insecureSkipVerify, //nolint:gosec // it's a CLI option. InsecureSkipVerify: insecureSkipVerify,
ClientAuth: clientAuth, ClientAuth: clientAuth,
}, nil }, nil
} }
@ -92,11 +107,11 @@ func getCertPool(ca string) (*x509.CertPool, error) {
if ca != "" { if ca != "" {
caContent, err := getCAContent(ca) caContent, err := getCAContent(ca)
if err != nil { if err != nil {
return nil, fmt.Errorf("failed to read CA. %w", err) return nil, fmt.Errorf("failed to read CA. %s", err)
} }
if !caPool.AppendCertsFromPEM(caContent) { if !caPool.AppendCertsFromPEM(caContent) {
return nil, errors.New("failed to parse CA") return nil, fmt.Errorf("failed to parse CA")
} }
} }
@ -104,14 +119,11 @@ func getCertPool(ca string) (*x509.CertPool, error) {
} }
func getCAContent(ca string) ([]byte, error) { func getCAContent(ca string) ([]byte, error) {
if _, err := os.Stat(ca); err != nil { if _, errCA := os.Stat(ca); errCA != nil {
if os.IsNotExist(err) { return []byte(ca), nil
return []byte(ca), nil
}
return nil, err
} }
caContent, err := os.ReadFile(filepath.Clean(ca)) caContent, err := ioutil.ReadFile(ca)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -138,11 +150,11 @@ func getCertificate(privateKey, certContent string) (tls.Certificate, error) {
_, errCertIsFile := os.Stat(certContent) _, errCertIsFile := os.Stat(certContent)
if errCertIsFile == nil && os.IsNotExist(errKeyIsFile) { if errCertIsFile == nil && os.IsNotExist(errKeyIsFile) {
return tls.Certificate{}, errors.New("tls cert is a file, but tls key is not") return tls.Certificate{}, fmt.Errorf("tls cert is a file, but tls key is not")
} }
if os.IsNotExist(errCertIsFile) && errKeyIsFile == nil { if os.IsNotExist(errCertIsFile) && errKeyIsFile == nil {
return tls.Certificate{}, errors.New("TLS key is a file, but tls cert is not") return tls.Certificate{}, fmt.Errorf("TLS key is a file, but tls cert is not")
} }
// string // string

View File

@ -1,30 +1,27 @@
package cmd package cmd
import ( import (
"crypto/rand"
"fmt" "fmt"
"io/ioutil"
"log" "log"
"math/big"
"os" "os"
"path/filepath" "path/filepath"
"strconv" "strconv"
"time"
"github.com/charmbracelet/lipgloss"
"github.com/ldez/traefik-certs-dumper/v2/dumper" "github.com/ldez/traefik-certs-dumper/v2/dumper"
"github.com/mitchellh/go-homedir" homedir "github.com/mitchellh/go-homedir"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
var cfgFile string var cfgFile string
// rootCmd represents the base command when called without any subcommands. // rootCmd represents the base command when called without any subcommands
var rootCmd = &cobra.Command{ var rootCmd = &cobra.Command{
Use: "traefik-certs-dumper", Use: "traefik-certs-dumper",
Short: "Dump Let's Encrypt certificates from Traefik.", Short: "Dump Let's Encrypt certificates from Traefik.",
Long: `Dump Let's Encrypt certificates from Traefik.`, Long: `Dump Let's Encrypt certificates from Traefik.`,
PersistentPreRunE: func(cmd *cobra.Command, _ []string) error { PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
if cmd.Name() == "version" { if cmd.Name() == "version" {
return nil return nil
} }
@ -38,7 +35,6 @@ var rootCmd = &cobra.Command{
return fmt.Errorf("--crt-ext (%q) and --key-ext (%q) are identical, in this case --domain-subdir is required", crtExt, keyExt) return fmt.Errorf("--crt-ext (%q) and --key-ext (%q) are identical, in this case --domain-subdir is required", crtExt, keyExt)
} }
} }
return nil return nil
}, },
} }
@ -50,8 +46,6 @@ func Execute() {
log.Println(err) log.Println(err)
os.Exit(1) os.Exit(1)
} }
help()
} }
func init() { func init() {
@ -115,7 +109,7 @@ func runE(apply func(*dumper.BaseConfig, *cobra.Command) error) func(*cobra.Comm
func tree(root, indent string) error { func tree(root, indent string) error {
fi, err := os.Stat(root) fi, err := os.Stat(root)
if err != nil { if err != nil {
return fmt.Errorf("could not stat %s: %w", root, err) return fmt.Errorf("could not stat %s: %v", root, err)
} }
fmt.Println(fi.Name()) fmt.Println(fi.Name())
@ -123,9 +117,9 @@ func tree(root, indent string) error {
return nil return nil
} }
fis, err := os.ReadDir(root) fis, err := ioutil.ReadDir(root)
if err != nil { if err != nil {
return fmt.Errorf("could not read dir %s: %w", root, err) return fmt.Errorf("could not read dir %s: %v", root, err)
} }
var names []string var names []string
@ -138,10 +132,10 @@ func tree(root, indent string) error {
for i, name := range names { for i, name := range names {
add := "│ " add := "│ "
if i == len(names)-1 { if i == len(names)-1 {
fmt.Print(indent + "└──") fmt.Printf(indent + "└──")
add = " " add = " "
} else { } else {
fmt.Print(indent + "├──") fmt.Printf(indent + "├──")
} }
if err := tree(filepath.Join(root, name), indent+add); err != nil { if err := tree(filepath.Join(root, name), indent+add); err != nil {
@ -184,34 +178,3 @@ func getBaseConfig(cmd *cobra.Command) (*dumper.BaseConfig, error) {
Hook: cmd.Flag("post-hook").Value.String(), Hook: cmd.Flag("post-hook").Value.String(),
}, nil }, nil
} }
func help() {
var maxInt int64 = 2 // -> 50%
if time.Now().Month() == time.December {
maxInt = 1 // -> 100%
}
n, _ := rand.Int(rand.Reader, big.NewInt(maxInt))
if n.Cmp(big.NewInt(0)) != 0 {
return
}
log.SetFlags(0)
pStyle := lipgloss.NewStyle().
Padding(1).
BorderStyle(lipgloss.RoundedBorder()).
BorderForeground(lipgloss.Color("161")).
Align(lipgloss.Center)
hStyle := lipgloss.NewStyle().Bold(true)
s := fmt.Sprintln(hStyle.Render("Request for Donation."))
s += `
I need your help!
Donations fund the maintenance and development of traefik-certs-dumper.
Click on this link to donate: https://donate.ldez.dev`
log.Println(pStyle.Render(s))
log.SetFlags(log.LstdFlags | log.Lshortfile)
}

View File

@ -13,11 +13,11 @@ var (
date = "I don't remember exactly" date = "I don't remember exactly"
) )
// versionCmd represents the version command. // versionCmd represents the version command
var versionCmd = &cobra.Command{ var versionCmd = &cobra.Command{
Use: "version", Use: "version",
Short: "Display version", Short: "Display version",
Run: func(_ *cobra.Command, _ []string) { Run: func(cmd *cobra.Command, args []string) {
displayVersion(rootCmd.Name()) displayVersion(rootCmd.Name())
}, },
} }

View File

@ -1,16 +1,14 @@
package cmd package cmd
import ( import (
"context" "github.com/abronan/valkeyrie/store"
"time" "github.com/abronan/valkeyrie/store/zookeeper"
"github.com/kvtools/zookeeper"
"github.com/ldez/traefik-certs-dumper/v2/dumper" "github.com/ldez/traefik-certs-dumper/v2/dumper"
"github.com/ldez/traefik-certs-dumper/v2/dumper/kv" "github.com/ldez/traefik-certs-dumper/v2/dumper/kv"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
// zookeeperCmd represents the zookeeper command. // zookeeperCmd represents the zookeeper command
var zookeeperCmd = &cobra.Command{ var zookeeperCmd = &cobra.Command{
Use: "zookeeper", Use: "zookeeper",
Short: "Dump the content of zookeeper.", Short: "Dump the content of zookeeper.",
@ -28,19 +26,8 @@ func zookeeperRun(baseConfig *dumper.BaseConfig, cmd *cobra.Command) error {
return err return err
} }
connectionTimeout, err := cmd.Flags().GetInt("connection-timeout") config.Backend = store.ZK
if err != nil { zookeeper.Register()
return err
}
config.Options = &zookeeper.Config{ return kv.Dump(config, baseConfig)
ConnectionTimeout: time.Duration(connectionTimeout) * time.Second,
Username: cmd.Flag("password").Value.String(),
Password: cmd.Flag("username").Value.String(),
MaxBufferSize: 0,
}
config.StoreName = zookeeper.StoreName
return kv.Dump(context.Background(), config, baseConfig)
} }

View File

@ -1 +0,0 @@
This directory content external contributions that are not maintain by @ldez.

View File

@ -1,40 +0,0 @@
[Unit]
Description=traefik certs dumper
; If you do not start traefik via systemd, choose network.target or docker.target
After=traefik.target
Wants=network-online.target systemd-networkd-wait-online.service
[Service]
Restart=on-abnormal
User=root
ExecStart=/usr/local/bin/traefik-certs-dumper file --version v2 --source /etc/traefik/acme/acme.json --dest /etc/ssl --watch
RestartSec=30
TimeoutSec=30
;WatchdogSec=30
; Limit the number of file descriptors; see `man systemd.exec` for more limit settings.
; LimitNOFILE=1048576
; Limit number of processes in this unit
LimitNPROC=1
; Use private /tmp and /var/tmp, which are discarded after traefik stops.
PrivateTmp=true
; Use a minimal /dev (May bring additional security if switched to 'true', but it may not work on Raspberry Pis or other devices)
PrivateDevices=true
; Hide /home, /root, and /run/user. Nobody will steal your SSH-keys.
ProtectHome=true
; Make cgroups /sys/fs/cgroup read-only
ProtectControlGroups=true
; Make kernel settings (procfs and sysfs) read-only
ProtectKernelTunables=true
; Make /usr, /boot, /etc and possibly some more folders read-only.
ProtectSystem=full
; This merely retains r/w access rights, it does not add any new. Must still be writable on the host!
ReadWriteDirectories=/etc/ssl
ReadOnlyPaths=/etc/traefik/acme/acme.json
; The following additional security directives only work with systemd v229 or later.
NoNewPrivileges=true
[Install]
WantedBy=multi-user.target

View File

@ -1,42 +0,0 @@
services:
traefik:
image: traefik:v1.7
command:
- --logLevel=INFO
- --defaultEntryPoints=web,websecure
- "--entryPoints=Name:web Address::80 Redirect.EntryPoint:websecure"
- "--entryPoints=Name:websecure Address::443 TLS"
- --docker
- --docker.exposedByDefault=false
- --acme
- --acme.email=email@example.com
- --acme.caServer=https://acme-staging-v02.api.letsencrypt.org/directory
- --acme.entrypoint=websecure
- --acme.storage=/letsencrypt/acme.json
- --acme.onHostRule
- --acme.tlsChallenge
ports:
- 80:80
- 443:443
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./letsencrypt:/letsencrypt
traefik-certs-dumper:
image: ldez/traefik-certs-dumper:v2.9.3
entrypoint: sh -c '
while ! [ -e /data/acme.json ]
|| ! [ `jq ".Certificates | length" /data/acme.json` != 0 ]; do
sleep 1
; done
&& traefik-certs-dumper file --watch
--source /data/acme.json --dest /data/certs'
volumes:
- ./letsencrypt:/data
network_mode: "none"
whoami:
image: traefik/whoami:v1.8.1
labels:
traefik.enable: true
traefik.frontend.rule: Host:example.com

View File

@ -1,44 +0,0 @@
services:
traefik:
image: traefik:v2.11.3
command:
- --log.level=INFO
- --entrypoints.web.address=:80
- --entrypoints.web.http.redirections.entrypoint.to=websecure
- --entrypoints.web.http.redirections.entrypoint.scheme=https
- --entrypoints.websecure.address=:443
- --entrypoints.websecure.http.tls=true
- --entrypoints.websecure.http.tls.certResolver=le
- --providers.docker.exposedbydefault=false
- --certificatesresolvers.le.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory
- --certificatesresolvers.le.acme.email=email@example.com
- --certificatesresolvers.le.acme.storage=/letsencrypt/acme.json
- --certificatesresolvers.le.acme.tlsChallenge=true
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- ./letsencrypt/:/letsencrypt
traefik-certs-dumper:
image: ldez/traefik-certs-dumper:v2.9.3
entrypoint: sh -c '
while ! [ -e /data/acme.json ]
|| ! [ `jq ".[] | .Certificates | length" /data/acme.json | jq -s "add" ` != 0 ]; do
sleep 1
; done
&& traefik-certs-dumper file --version v2 --watch
--source /data/acme.json --dest /data/certs'
volumes:
- ./letsencrypt:/data
network_mode: "none"
whoami:
image: traefik/whoami:v1.8.1
labels:
traefik.enable: 'true'
traefik.http.routers.app.rule: Host(`example.com`)
traefik.http.routers.app.entrypoints: websecure

View File

@ -24,9 +24,8 @@ Dump Let's Encrypt certificates from Traefik.
### SEE ALSO ### SEE ALSO
* [traefik-certs-dumper completion](traefik-certs-dumper_completion.md) - Generate the autocompletion script for the specified shell
* [traefik-certs-dumper file](traefik-certs-dumper_file.md) - Dump the content of the "acme.json" file. * [traefik-certs-dumper file](traefik-certs-dumper_file.md) - Dump the content of the "acme.json" file.
* [traefik-certs-dumper kv](traefik-certs-dumper_kv.md) - Dump the content of a KV store. * [traefik-certs-dumper kv](traefik-certs-dumper_kv.md) - Dump the content of a KV store.
* [traefik-certs-dumper version](traefik-certs-dumper_version.md) - Display version * [traefik-certs-dumper version](traefik-certs-dumper_version.md) - Display version
###### Auto generated by spf13/cobra on 21-Feb-2025 ###### Auto generated by spf13/cobra on 25-Apr-2019

View File

@ -1,40 +0,0 @@
## traefik-certs-dumper completion
Generate the autocompletion script for the specified shell
### Synopsis
Generate the autocompletion script for traefik-certs-dumper for the specified shell.
See each sub-command's help for details on how to use the generated script.
### Options
```
-h, --help help for completion
```
### Options inherited from parent commands
```
--clean Clean destination folder before dumping content. (default true)
--config string config file (default is $HOME/.traefik-certs-dumper.yaml)
--crt-ext string The file extension of the generated certificates. (default ".crt")
--crt-name string The file name (without extension) of the generated certificates. (default "certificate")
--dest string Path to store the dump content. (default "./dump")
--domain-subdir Use domain as sub-directory.
--key-ext string The file extension of the generated private keys. (default ".key")
--key-name string The file name (without extension) of the generated private keys. (default "privatekey")
--post-hook string Execute a command only if changes occurs on the data source. (works only with the watch mode)
--watch Enable watching changes.
```
### SEE ALSO
* [traefik-certs-dumper](traefik-certs-dumper.md) - Dump Let's Encrypt certificates from Traefik.
* [traefik-certs-dumper completion bash](traefik-certs-dumper_completion_bash.md) - Generate the autocompletion script for bash
* [traefik-certs-dumper completion fish](traefik-certs-dumper_completion_fish.md) - Generate the autocompletion script for fish
* [traefik-certs-dumper completion powershell](traefik-certs-dumper_completion_powershell.md) - Generate the autocompletion script for powershell
* [traefik-certs-dumper completion zsh](traefik-certs-dumper_completion_zsh.md) - Generate the autocompletion script for zsh
###### Auto generated by spf13/cobra on 21-Feb-2025

View File

@ -1,59 +0,0 @@
## traefik-certs-dumper completion bash
Generate the autocompletion script for bash
### Synopsis
Generate the autocompletion script for the bash shell.
This script depends on the 'bash-completion' package.
If it is not installed already, you can install it via your OS's package manager.
To load completions in your current shell session:
source <(traefik-certs-dumper completion bash)
To load completions for every new session, execute once:
#### Linux:
traefik-certs-dumper completion bash > /etc/bash_completion.d/traefik-certs-dumper
#### macOS:
traefik-certs-dumper completion bash > $(brew --prefix)/etc/bash_completion.d/traefik-certs-dumper
You will need to start a new shell for this setup to take effect.
```
traefik-certs-dumper completion bash
```
### Options
```
-h, --help help for bash
--no-descriptions disable completion descriptions
```
### Options inherited from parent commands
```
--clean Clean destination folder before dumping content. (default true)
--config string config file (default is $HOME/.traefik-certs-dumper.yaml)
--crt-ext string The file extension of the generated certificates. (default ".crt")
--crt-name string The file name (without extension) of the generated certificates. (default "certificate")
--dest string Path to store the dump content. (default "./dump")
--domain-subdir Use domain as sub-directory.
--key-ext string The file extension of the generated private keys. (default ".key")
--key-name string The file name (without extension) of the generated private keys. (default "privatekey")
--post-hook string Execute a command only if changes occurs on the data source. (works only with the watch mode)
--watch Enable watching changes.
```
### SEE ALSO
* [traefik-certs-dumper completion](traefik-certs-dumper_completion.md) - Generate the autocompletion script for the specified shell
###### Auto generated by spf13/cobra on 21-Feb-2025

View File

@ -1,50 +0,0 @@
## traefik-certs-dumper completion fish
Generate the autocompletion script for fish
### Synopsis
Generate the autocompletion script for the fish shell.
To load completions in your current shell session:
traefik-certs-dumper completion fish | source
To load completions for every new session, execute once:
traefik-certs-dumper completion fish > ~/.config/fish/completions/traefik-certs-dumper.fish
You will need to start a new shell for this setup to take effect.
```
traefik-certs-dumper completion fish [flags]
```
### Options
```
-h, --help help for fish
--no-descriptions disable completion descriptions
```
### Options inherited from parent commands
```
--clean Clean destination folder before dumping content. (default true)
--config string config file (default is $HOME/.traefik-certs-dumper.yaml)
--crt-ext string The file extension of the generated certificates. (default ".crt")
--crt-name string The file name (without extension) of the generated certificates. (default "certificate")
--dest string Path to store the dump content. (default "./dump")
--domain-subdir Use domain as sub-directory.
--key-ext string The file extension of the generated private keys. (default ".key")
--key-name string The file name (without extension) of the generated private keys. (default "privatekey")
--post-hook string Execute a command only if changes occurs on the data source. (works only with the watch mode)
--watch Enable watching changes.
```
### SEE ALSO
* [traefik-certs-dumper completion](traefik-certs-dumper_completion.md) - Generate the autocompletion script for the specified shell
###### Auto generated by spf13/cobra on 21-Feb-2025

View File

@ -1,47 +0,0 @@
## traefik-certs-dumper completion powershell
Generate the autocompletion script for powershell
### Synopsis
Generate the autocompletion script for powershell.
To load completions in your current shell session:
traefik-certs-dumper completion powershell | Out-String | Invoke-Expression
To load completions for every new session, add the output of the above command
to your powershell profile.
```
traefik-certs-dumper completion powershell [flags]
```
### Options
```
-h, --help help for powershell
--no-descriptions disable completion descriptions
```
### Options inherited from parent commands
```
--clean Clean destination folder before dumping content. (default true)
--config string config file (default is $HOME/.traefik-certs-dumper.yaml)
--crt-ext string The file extension of the generated certificates. (default ".crt")
--crt-name string The file name (without extension) of the generated certificates. (default "certificate")
--dest string Path to store the dump content. (default "./dump")
--domain-subdir Use domain as sub-directory.
--key-ext string The file extension of the generated private keys. (default ".key")
--key-name string The file name (without extension) of the generated private keys. (default "privatekey")
--post-hook string Execute a command only if changes occurs on the data source. (works only with the watch mode)
--watch Enable watching changes.
```
### SEE ALSO
* [traefik-certs-dumper completion](traefik-certs-dumper_completion.md) - Generate the autocompletion script for the specified shell
###### Auto generated by spf13/cobra on 21-Feb-2025

View File

@ -1,61 +0,0 @@
## traefik-certs-dumper completion zsh
Generate the autocompletion script for zsh
### Synopsis
Generate the autocompletion script for the zsh shell.
If shell completion is not already enabled in your environment you will need
to enable it. You can execute the following once:
echo "autoload -U compinit; compinit" >> ~/.zshrc
To load completions in your current shell session:
source <(traefik-certs-dumper completion zsh)
To load completions for every new session, execute once:
#### Linux:
traefik-certs-dumper completion zsh > "${fpath[1]}/_traefik-certs-dumper"
#### macOS:
traefik-certs-dumper completion zsh > $(brew --prefix)/share/zsh/site-functions/_traefik-certs-dumper
You will need to start a new shell for this setup to take effect.
```
traefik-certs-dumper completion zsh [flags]
```
### Options
```
-h, --help help for zsh
--no-descriptions disable completion descriptions
```
### Options inherited from parent commands
```
--clean Clean destination folder before dumping content. (default true)
--config string config file (default is $HOME/.traefik-certs-dumper.yaml)
--crt-ext string The file extension of the generated certificates. (default ".crt")
--crt-name string The file name (without extension) of the generated certificates. (default "certificate")
--dest string Path to store the dump content. (default "./dump")
--domain-subdir Use domain as sub-directory.
--key-ext string The file extension of the generated private keys. (default ".key")
--key-name string The file name (without extension) of the generated private keys. (default "privatekey")
--post-hook string Execute a command only if changes occurs on the data source. (works only with the watch mode)
--watch Enable watching changes.
```
### SEE ALSO
* [traefik-certs-dumper completion](traefik-certs-dumper_completion.md) - Generate the autocompletion script for the specified shell
###### Auto generated by spf13/cobra on 21-Feb-2025

View File

@ -13,9 +13,8 @@ traefik-certs-dumper file [flags]
### Options ### Options
``` ```
-h, --help help for file -h, --help help for file
--source string Path to 'acme.json' file. (default "./acme.json") --source string Path to 'acme.json' file. (default "./acme.json")
--version string Traefik version. If empty use v1. Possible values: 'v2', 'v3'.
``` ```
### Options inherited from parent commands ### Options inherited from parent commands
@ -37,4 +36,4 @@ traefik-certs-dumper file [flags]
* [traefik-certs-dumper](traefik-certs-dumper.md) - Dump Let's Encrypt certificates from Traefik. * [traefik-certs-dumper](traefik-certs-dumper.md) - Dump Let's Encrypt certificates from Traefik.
###### Auto generated by spf13/cobra on 21-Feb-2025 ###### Auto generated by spf13/cobra on 25-Apr-2019

View File

@ -14,7 +14,6 @@ Dump the content of a KV store.
-h, --help help for kv -h, --help help for kv
--password string Password for connection. --password string Password for connection.
--prefix string Prefix used for KV store. (default "traefik") --prefix string Prefix used for KV store. (default "traefik")
--suffix string Suffix/Storage used for KV store. (default "/acme/account/object")
--tls Enable TLS encryption. --tls Enable TLS encryption.
--tls.ca string Root CA for certificate verification if TLS is enabled --tls.ca string Root CA for certificate verification if TLS is enabled
--tls.ca.optional --tls.ca.optional
@ -47,4 +46,4 @@ Dump the content of a KV store.
* [traefik-certs-dumper kv etcd](traefik-certs-dumper_kv_etcd.md) - Dump the content of etcd. * [traefik-certs-dumper kv etcd](traefik-certs-dumper_kv_etcd.md) - Dump the content of etcd.
* [traefik-certs-dumper kv zookeeper](traefik-certs-dumper_kv_zookeeper.md) - Dump the content of zookeeper. * [traefik-certs-dumper kv zookeeper](traefik-certs-dumper_kv_zookeeper.md) - Dump the content of zookeeper.
###### Auto generated by spf13/cobra on 21-Feb-2025 ###### Auto generated by spf13/cobra on 25-Apr-2019

View File

@ -34,7 +34,6 @@ traefik-certs-dumper kv boltdb [flags]
--password string Password for connection. --password string Password for connection.
--post-hook string Execute a command only if changes occurs on the data source. (works only with the watch mode) --post-hook string Execute a command only if changes occurs on the data source. (works only with the watch mode)
--prefix string Prefix used for KV store. (default "traefik") --prefix string Prefix used for KV store. (default "traefik")
--suffix string Suffix/Storage used for KV store. (default "/acme/account/object")
--tls Enable TLS encryption. --tls Enable TLS encryption.
--tls.ca string Root CA for certificate verification if TLS is enabled --tls.ca string Root CA for certificate verification if TLS is enabled
--tls.ca.optional --tls.ca.optional
@ -49,4 +48,4 @@ traefik-certs-dumper kv boltdb [flags]
* [traefik-certs-dumper kv](traefik-certs-dumper_kv.md) - Dump the content of a KV store. * [traefik-certs-dumper kv](traefik-certs-dumper_kv.md) - Dump the content of a KV store.
###### Auto generated by spf13/cobra on 21-Feb-2025 ###### Auto generated by spf13/cobra on 25-Apr-2019

View File

@ -33,7 +33,6 @@ traefik-certs-dumper kv consul [flags]
--password string Password for connection. --password string Password for connection.
--post-hook string Execute a command only if changes occurs on the data source. (works only with the watch mode) --post-hook string Execute a command only if changes occurs on the data source. (works only with the watch mode)
--prefix string Prefix used for KV store. (default "traefik") --prefix string Prefix used for KV store. (default "traefik")
--suffix string Suffix/Storage used for KV store. (default "/acme/account/object")
--tls Enable TLS encryption. --tls Enable TLS encryption.
--tls.ca string Root CA for certificate verification if TLS is enabled --tls.ca string Root CA for certificate verification if TLS is enabled
--tls.ca.optional --tls.ca.optional
@ -48,4 +47,4 @@ traefik-certs-dumper kv consul [flags]
* [traefik-certs-dumper kv](traefik-certs-dumper_kv.md) - Dump the content of a KV store. * [traefik-certs-dumper kv](traefik-certs-dumper_kv.md) - Dump the content of a KV store.
###### Auto generated by spf13/cobra on 21-Feb-2025 ###### Auto generated by spf13/cobra on 25-Apr-2019

View File

@ -13,9 +13,8 @@ traefik-certs-dumper kv etcd [flags]
### Options ### Options
``` ```
--etcd-version string The etcd version can be: 'etcd' or 'etcdv3'. (default "etcd") -h, --help help for etcd
-h, --help help for etcd --sync-period int Sync period for etcd in seconds.
--sync-period int Sync period for etcd in seconds.
``` ```
### Options inherited from parent commands ### Options inherited from parent commands
@ -34,7 +33,6 @@ traefik-certs-dumper kv etcd [flags]
--password string Password for connection. --password string Password for connection.
--post-hook string Execute a command only if changes occurs on the data source. (works only with the watch mode) --post-hook string Execute a command only if changes occurs on the data source. (works only with the watch mode)
--prefix string Prefix used for KV store. (default "traefik") --prefix string Prefix used for KV store. (default "traefik")
--suffix string Suffix/Storage used for KV store. (default "/acme/account/object")
--tls Enable TLS encryption. --tls Enable TLS encryption.
--tls.ca string Root CA for certificate verification if TLS is enabled --tls.ca string Root CA for certificate verification if TLS is enabled
--tls.ca.optional --tls.ca.optional
@ -49,4 +47,4 @@ traefik-certs-dumper kv etcd [flags]
* [traefik-certs-dumper kv](traefik-certs-dumper_kv.md) - Dump the content of a KV store. * [traefik-certs-dumper kv](traefik-certs-dumper_kv.md) - Dump the content of a KV store.
###### Auto generated by spf13/cobra on 21-Feb-2025 ###### Auto generated by spf13/cobra on 25-Apr-2019

View File

@ -32,7 +32,6 @@ traefik-certs-dumper kv zookeeper [flags]
--password string Password for connection. --password string Password for connection.
--post-hook string Execute a command only if changes occurs on the data source. (works only with the watch mode) --post-hook string Execute a command only if changes occurs on the data source. (works only with the watch mode)
--prefix string Prefix used for KV store. (default "traefik") --prefix string Prefix used for KV store. (default "traefik")
--suffix string Suffix/Storage used for KV store. (default "/acme/account/object")
--tls Enable TLS encryption. --tls Enable TLS encryption.
--tls.ca string Root CA for certificate verification if TLS is enabled --tls.ca string Root CA for certificate verification if TLS is enabled
--tls.ca.optional --tls.ca.optional
@ -47,4 +46,4 @@ traefik-certs-dumper kv zookeeper [flags]
* [traefik-certs-dumper kv](traefik-certs-dumper_kv.md) - Dump the content of a KV store. * [traefik-certs-dumper kv](traefik-certs-dumper_kv.md) - Dump the content of a KV store.
###### Auto generated by spf13/cobra on 21-Feb-2025 ###### Auto generated by spf13/cobra on 25-Apr-2019

View File

@ -2,6 +2,10 @@
Display version Display version
### Synopsis
Display version
``` ```
traefik-certs-dumper version [flags] traefik-certs-dumper version [flags]
``` ```
@ -31,4 +35,4 @@ traefik-certs-dumper version [flags]
* [traefik-certs-dumper](traefik-certs-dumper.md) - Dump Let's Encrypt certificates from Traefik. * [traefik-certs-dumper](traefik-certs-dumper.md) - Dump Let's Encrypt certificates from Traefik.
###### Auto generated by spf13/cobra on 21-Feb-2025 ###### Auto generated by spf13/cobra on 25-Apr-2019

View File

@ -9,11 +9,4 @@ type BaseConfig struct {
Clean bool Clean bool
Watch bool Watch bool
Hook string Hook string
Version string
}
// FileInfo File information.
type FileInfo struct {
Name string
Ext string
} }

View File

@ -1,14 +1,12 @@
package v1 package dumper
import ( import (
"encoding/pem" "encoding/pem"
"fmt" "io/ioutil"
"os" "os"
"path/filepath" "path/filepath"
"github.com/go-acme/lego/v4/certcrypto" "github.com/go-acme/lego/certcrypto"
"github.com/ldez/traefik-certs-dumper/v2/dumper"
"github.com/ldez/traefik-certs-dumper/v2/internal/traefikv1"
) )
const ( const (
@ -16,70 +14,77 @@ const (
keysSubDir = "private" keysSubDir = "private"
) )
// FileInfo File information.
type FileInfo struct {
Name string
Ext string
}
// Dump Dumps data to certificates. // Dump Dumps data to certificates.
func Dump(data *traefikv1.StoredData, baseConfig *dumper.BaseConfig) error { func Dump(data *StoredData, baseConfig *BaseConfig) error {
if baseConfig.Clean { if baseConfig.Clean {
err := cleanDir(baseConfig.DumpPath) err := cleanDir(baseConfig.DumpPath)
if err != nil { if err != nil {
return fmt.Errorf("folder cleaning failed: %w", err) return err
} }
} }
if !baseConfig.DomainSubDir { if !baseConfig.DomainSubDir {
if err := os.MkdirAll(filepath.Join(baseConfig.DumpPath, certsSubDir), 0o755); err != nil { if err := os.MkdirAll(filepath.Join(baseConfig.DumpPath, certsSubDir), 0755); err != nil {
return fmt.Errorf("certs folder creation failure: %w", err) return err
} }
} }
if err := os.MkdirAll(filepath.Join(baseConfig.DumpPath, keysSubDir), 0o755); err != nil { if err := os.MkdirAll(filepath.Join(baseConfig.DumpPath, keysSubDir), 0755); err != nil {
return fmt.Errorf("keys folder creation failure: %w", err) return err
}
privateKeyPem := extractPEMPrivateKey(data.Account)
err := ioutil.WriteFile(filepath.Join(baseConfig.DumpPath, keysSubDir, "letsencrypt"+baseConfig.KeyInfo.Ext), privateKeyPem, 0666)
if err != nil {
return err
} }
for _, cert := range data.Certificates { for _, cert := range data.Certificates {
err := writeCert(baseConfig.DumpPath, cert, baseConfig.CrtInfo, baseConfig.DomainSubDir) err := writeCert(baseConfig.DumpPath, cert, baseConfig.CrtInfo, baseConfig.DomainSubDir)
if err != nil { if err != nil {
return fmt.Errorf("failed to write certificates: %w", err) return err
} }
err = writeKey(baseConfig.DumpPath, cert, baseConfig.KeyInfo, baseConfig.DomainSubDir) err = writeKey(baseConfig.DumpPath, cert, baseConfig.KeyInfo, baseConfig.DomainSubDir)
if err != nil { if err != nil {
return fmt.Errorf("failed to write certificate keys: %w", err) return err
} }
} }
if data.Account == nil { return nil
return nil
}
privateKeyPem := extractPEMPrivateKey(data.Account)
return os.WriteFile(filepath.Join(baseConfig.DumpPath, keysSubDir, "letsencrypt"+baseConfig.KeyInfo.Ext), privateKeyPem, 0o600)
} }
func writeCert(dumpPath string, cert *traefikv1.Certificate, info dumper.FileInfo, domainSubDir bool) error { func writeCert(dumpPath string, cert *Certificate, info FileInfo, domainSubDir bool) error {
certPath := filepath.Join(dumpPath, certsSubDir, safeName(cert.Domain.Main+info.Ext)) certPath := filepath.Join(dumpPath, certsSubDir, safeName(cert.Domain.Main+info.Ext))
if domainSubDir { if domainSubDir {
certPath = filepath.Join(dumpPath, safeName(cert.Domain.Main), info.Name+info.Ext) certPath = filepath.Join(dumpPath, safeName(cert.Domain.Main), info.Name+info.Ext)
if err := os.MkdirAll(filepath.Join(dumpPath, safeName(cert.Domain.Main)), 0o755); err != nil { if err := os.MkdirAll(filepath.Join(dumpPath, safeName(cert.Domain.Main)), 0755); err != nil {
return err return err
} }
} }
return os.WriteFile(certPath, cert.Certificate, 0o666) return ioutil.WriteFile(certPath, cert.Certificate, 0666)
} }
func writeKey(dumpPath string, cert *traefikv1.Certificate, info dumper.FileInfo, domainSubDir bool) error { func writeKey(dumpPath string, cert *Certificate, info FileInfo, domainSubDir bool) error {
keyPath := filepath.Join(dumpPath, keysSubDir, safeName(cert.Domain.Main+info.Ext)) keyPath := filepath.Join(dumpPath, keysSubDir, safeName(cert.Domain.Main+info.Ext))
if domainSubDir { if domainSubDir {
keyPath = filepath.Join(dumpPath, safeName(cert.Domain.Main), info.Name+info.Ext) keyPath = filepath.Join(dumpPath, safeName(cert.Domain.Main), info.Name+info.Ext)
if err := os.MkdirAll(filepath.Join(dumpPath, safeName(cert.Domain.Main)), 0o755); err != nil { if err := os.MkdirAll(filepath.Join(dumpPath, safeName(cert.Domain.Main)), 0755); err != nil {
return err return err
} }
} }
return os.WriteFile(keyPath, cert.Key, 0o600) return ioutil.WriteFile(keyPath, cert.Key, 0666)
} }
func extractPEMPrivateKey(account *traefikv1.Account) []byte { func extractPEMPrivateKey(account *Account) []byte {
var block *pem.Block var block *pem.Block
switch account.KeyType { switch account.KeyType {
case certcrypto.RSA2048, certcrypto.RSA4096, certcrypto.RSA8192: case certcrypto.RSA2048, certcrypto.RSA4096, certcrypto.RSA8192:
@ -93,7 +98,7 @@ func extractPEMPrivateKey(account *traefikv1.Account) []byte {
Bytes: account.PrivateKey, Bytes: account.PrivateKey,
} }
default: default:
panic(fmt.Sprintf("unsupported key type: '%v'", account.KeyType)) panic("unsupported key type")
} }
return pem.EncodeToMemory(block) return pem.EncodeToMemory(block)
@ -109,7 +114,7 @@ func cleanDir(dumpPath string) error {
return errExists return errExists
} }
dir, err := os.ReadDir(dumpPath) dir, err := ioutil.ReadDir(dumpPath)
if err != nil { if err != nil {
return err return err
} }

View File

@ -2,133 +2,58 @@ package file
import ( import (
"bytes" "bytes"
"context" "crypto/md5"
"crypto/sha256"
"encoding/json" "encoding/json"
"errors"
"fmt"
"io" "io"
"log" "log"
"os" "os"
"path/filepath"
"strings" "strings"
"github.com/fsnotify/fsnotify" "github.com/fsnotify/fsnotify"
"github.com/ldez/traefik-certs-dumper/v2/dumper" "github.com/ldez/traefik-certs-dumper/v2/dumper"
dumperv1 "github.com/ldez/traefik-certs-dumper/v2/dumper/v1"
dumperv2 "github.com/ldez/traefik-certs-dumper/v2/dumper/v2"
dumperv3 "github.com/ldez/traefik-certs-dumper/v2/dumper/v3"
"github.com/ldez/traefik-certs-dumper/v2/hook" "github.com/ldez/traefik-certs-dumper/v2/hook"
"github.com/ldez/traefik-certs-dumper/v2/internal/traefikv1"
"github.com/ldez/traefik-certs-dumper/v2/internal/traefikv2"
"github.com/ldez/traefik-certs-dumper/v2/internal/traefikv3"
) )
// Dump Dumps "acme.json" file to certificates. // Dump Dumps "acme.json" file to certificates.
func Dump(ctx context.Context, acmeFile string, baseConfig *dumper.BaseConfig) error { func Dump(acmeFile string, baseConfig *dumper.BaseConfig) error {
err := dump(acmeFile, baseConfig) err := dump(acmeFile, baseConfig)
if err != nil { if err != nil {
return err return err
} }
if baseConfig.Watch { if baseConfig.Watch {
hook.Exec(ctx, baseConfig.Hook) return watch(acmeFile, baseConfig)
return watch(ctx, acmeFile, baseConfig)
} }
return nil return nil
} }
func dump(acmeFile string, baseConfig *dumper.BaseConfig) error { func dump(acmeFile string, baseConfig *dumper.BaseConfig) error {
switch baseConfig.Version { data, err := readFile(acmeFile)
case "v3":
err := dumpV3(acmeFile, baseConfig)
if err != nil {
return fmt.Errorf("v3: dump failed: %w", err)
}
return nil
case "v2":
err := dumpV2(acmeFile, baseConfig)
if err != nil {
return fmt.Errorf("v2: dump failed: %w", err)
}
return nil
case "v1":
err := dumpV1(acmeFile, baseConfig)
if err != nil {
return fmt.Errorf("v1: dump failed: %w", err)
}
return nil
default:
err := dumpV1(acmeFile, baseConfig)
if err != nil {
return fmt.Errorf("v1: dump failed: %w", err)
}
return nil
}
}
func dumpV1(acmeFile string, baseConfig *dumper.BaseConfig) error {
data := &traefikv1.StoredData{}
err := readJSONFile(acmeFile, data)
if err != nil { if err != nil {
return err return err
} }
return dumperv1.Dump(data, baseConfig) return dumper.Dump(data, baseConfig)
} }
func dumpV2(acmeFile string, baseConfig *dumper.BaseConfig) error { func readFile(acmeFile string) (*dumper.StoredData, error) {
data := map[string]*traefikv2.StoredData{} source, err := os.Open(acmeFile)
err := readJSONFile(acmeFile, &data)
if err != nil { if err != nil {
return err return nil, err
} }
return dumperv2.Dump(data, baseConfig) data := &dumper.StoredData{}
if err = json.NewDecoder(source).Decode(data); err != nil {
return nil, err
}
return data, nil
} }
func dumpV3(acmeFile string, baseConfig *dumper.BaseConfig) error { func watch(acmeFile string, baseConfig *dumper.BaseConfig) error {
data := map[string]*traefikv3.StoredData{}
err := readJSONFile(acmeFile, &data)
if err != nil {
return err
}
return dumperv3.Dump(data, baseConfig)
}
func readJSONFile(acmeFile string, data interface{}) error {
source, err := os.Open(filepath.Clean(acmeFile))
if err != nil {
return fmt.Errorf("failed to open file %q: %w", acmeFile, err)
}
defer func() { _ = source.Close() }()
err = json.NewDecoder(source).Decode(data)
if errors.Is(err, io.EOF) {
log.Printf("warn: file %q may not be ready: %v", acmeFile, err)
return nil
}
if err != nil {
return fmt.Errorf("failed to unmarshal file %q: %w", acmeFile, err)
}
return nil
}
func watch(ctx context.Context, acmeFile string, baseConfig *dumper.BaseConfig) error {
watcher, err := fsnotify.NewWatcher() watcher, err := fsnotify.NewWatcher()
if err != nil { if err != nil {
return fmt.Errorf("failed to create new watcher: %w", err) return err
} }
defer func() { _ = watcher.Close() }() defer func() { _ = watcher.Close() }()
@ -148,7 +73,7 @@ func watch(ctx context.Context, acmeFile string, baseConfig *dumper.BaseConfig)
log.Println("event:", event) log.Println("event:", event)
} }
hash, errW := manageEvent(ctx, watcher, event, acmeFile, previousHash, baseConfig) hash, errW := manageEvent(watcher, event, acmeFile, previousHash, baseConfig)
if errW != nil { if errW != nil {
log.Println("error:", errW) log.Println("error:", errW)
done <- true done <- true
@ -171,7 +96,7 @@ func watch(ctx context.Context, acmeFile string, baseConfig *dumper.BaseConfig)
err = watcher.Add(acmeFile) err = watcher.Add(acmeFile)
if err != nil { if err != nil {
return fmt.Errorf("failed to add a new watcher: %w", err) return err
} }
<-done <-done
@ -179,15 +104,15 @@ func watch(ctx context.Context, acmeFile string, baseConfig *dumper.BaseConfig)
return nil return nil
} }
func manageEvent(ctx context.Context, watcher *fsnotify.Watcher, event fsnotify.Event, acmeFile string, previousHash []byte, baseConfig *dumper.BaseConfig) ([]byte, error) { func manageEvent(watcher *fsnotify.Watcher, event fsnotify.Event, acmeFile string, previousHash []byte, baseConfig *dumper.BaseConfig) ([]byte, error) {
err := manageRename(watcher, event, acmeFile) err := manageRename(watcher, event, acmeFile)
if err != nil { if err != nil {
return nil, fmt.Errorf("watcher renewal failed: %w", err) return nil, err
} }
hash, err := calculateHash(acmeFile) hash, err := calculateHash(acmeFile)
if err != nil { if err != nil {
return nil, fmt.Errorf("file hash calculation failed: %w", err) return nil, err
} }
if !bytes.Equal(previousHash, hash) { if !bytes.Equal(previousHash, hash) {
@ -203,7 +128,7 @@ func manageEvent(ctx context.Context, watcher *fsnotify.Watcher, event fsnotify.
log.Println("Dumped new certificate data.") log.Println("Dumped new certificate data.")
} }
hook.Exec(ctx, baseConfig.Hook) hook.Exec(baseConfig.Hook)
} }
return hash, nil return hash, nil
@ -222,13 +147,13 @@ func manageRename(watcher *fsnotify.Watcher, event fsnotify.Event, acmeFile stri
} }
func calculateHash(acmeFile string) ([]byte, error) { func calculateHash(acmeFile string) ([]byte, error) {
file, err := os.Open(filepath.Clean(acmeFile)) file, err := os.Open(acmeFile)
if err != nil { if err != nil {
return nil, err return nil, err
} }
defer func() { _ = file.Close() }() defer func() { _ = file.Close() }()
h := sha256.New() h := md5.New()
_, err = io.Copy(h, file) _, err = io.Copy(h, file)
if err != nil { if err != nil {
return nil, err return nil, err

View File

@ -1,60 +0,0 @@
package file
import (
"testing"
"github.com/ldez/traefik-certs-dumper/v2/dumper"
"github.com/stretchr/testify/require"
)
func TestDump(t *testing.T) {
testCases := []struct {
desc string
acmeFile string
version string
}{
{
desc: "should skip EOF error",
acmeFile: "./fixtures/acme-empty.json",
},
{
desc: "should dump traefik v1 file content",
acmeFile: "./fixtures/acme-v1.json",
},
{
desc: "should dump traefik v2 file content",
acmeFile: "./fixtures/acme-v2.json",
version: "v2",
},
{
desc: "should dump traefik v3 file content",
acmeFile: "./fixtures/acme-v3.json",
version: "v3",
},
}
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
t.Parallel()
dir := t.TempDir()
cfg := &dumper.BaseConfig{
DumpPath: dir,
CrtInfo: dumper.FileInfo{
Name: "certificate",
Ext: ".crt",
},
KeyInfo: dumper.FileInfo{
Name: "privatekey",
Ext: ".key",
},
Clean: true,
Version: test.version,
}
err := Dump(t.Context(), test.acmeFile, cfg)
require.NoError(t, err)
})
}
}

View File

@ -1,12 +0,0 @@
{
"Certificates": [
{
"domain": {
"main": "test.example.com"
},
"certificate": "Q2VydGlmaWNhdGU=",
"key": "Q2VydGlmaWNhdGUgS2V5",
"Store": "default"
}
]
}

View File

@ -1,36 +0,0 @@
{
"default": {
"Account": {
"Email": "test@email.com",
"Registration": {
"body": {
"status": "valid",
"contact": [
"mailto:test@email.com"
]
},
"uri": "https://acme-v02.api.letsencrypt.org/acme/acct/12345678"
},
"PrivateKey": "Q2VydGlmaWNhdGUgS2V5",
"KeyType": "4096"
},
"Certificates": [
{
"domain": {
"main": "my.domain.com"
},
"certificate": "Q2VydGlmaWNhdGU=",
"key": "Q2VydGlmaWNhdGUgS2V5",
"Store": "default"
},
{
"domain": {
"main": "my.domain2.com"
},
"certificate": "Q2VydGlmaWNhdGU=",
"key": "Q2VydGlmaWNhdGUgS2V5",
"Store": "default"
}
]
}
}

View File

@ -1,36 +0,0 @@
{
"default": {
"Account": {
"Email": "test@email.com",
"Registration": {
"body": {
"status": "valid",
"contact": [
"mailto:test@email.com"
]
},
"uri": "https://acme-v02.api.letsencrypt.org/acme/acct/12345678"
},
"PrivateKey": "Q2VydGlmaWNhdGUgS2V5",
"KeyType": "4096"
},
"Certificates": [
{
"domain": {
"main": "my.domain.com"
},
"certificate": "Q2VydGlmaWNhdGU=",
"key": "Q2VydGlmaWNhdGUgS2V5",
"Store": "default"
},
{
"domain": {
"main": "my.domain2.com"
},
"certificate": "Q2VydGlmaWNhdGU=",
"key": "Q2VydGlmaWNhdGUgS2V5",
"Store": "default"
}
]
}
}

View File

@ -1,7 +1,6 @@
//go:build !windows
// +build !windows // +build !windows
package v1 package dumper
func safeName(filename string) string { func safeName(filename string) string {
return filename return filename

View File

@ -1,6 +1,6 @@
// +build windows // +build windows
package v2 package dumper
import "strings" import "strings"

View File

@ -1,11 +1,11 @@
package traefikv1 package dumper
import ( import (
"github.com/go-acme/lego/v4/certcrypto" "github.com/go-acme/lego/certcrypto"
"github.com/go-acme/lego/v4/registration" "github.com/go-acme/lego/registration"
) )
// StoredData represents the data managed by the Store. // StoredData represents the data managed by the Store
type StoredData struct { type StoredData struct {
Account *Account Account *Account
Certificates []*Certificate Certificates []*Certificate
@ -13,20 +13,20 @@ type StoredData struct {
TLSChallenges map[string]*Certificate TLSChallenges map[string]*Certificate
} }
// Certificate is a struct which contains all data needed from an ACME certificate. // Certificate is a struct which contains all data needed from an ACME certificate
type Certificate struct { type Certificate struct {
Domain Domain Domain Domain
Certificate []byte Certificate []byte
Key []byte Key []byte
} }
// Domain holds a domain name with SANs. // Domain holds a domain name with SANs
type Domain struct { type Domain struct {
Main string Main string
SANs []string SANs []string
} }
// Account is used to store lets encrypt registration info. // Account is used to store lets encrypt registration info
type Account struct { type Account struct {
Email string Email string
Registration *registration.Resource Registration *registration.Resource

View File

@ -1,14 +1,11 @@
package kv package kv
import ( import "github.com/abronan/valkeyrie/store"
"github.com/kvtools/valkeyrie"
)
// Config KV configuration. // Config KV configuration.
type Config struct { type Config struct {
StoreName string Backend store.Backend
Prefix string Prefix string
Suffix string
Endpoints []string Endpoints []string
Options valkeyrie.Config Options *store.Config
} }

View File

@ -1,13 +1,13 @@
package kv package kv
import ( import (
"github.com/go-acme/lego/v4/certcrypto" "github.com/go-acme/lego/certcrypto"
"github.com/go-acme/lego/v4/registration" "github.com/go-acme/lego/registration"
"github.com/ldez/traefik-certs-dumper/v2/internal/traefikv1" "github.com/ldez/traefik-certs-dumper/v2/dumper"
) )
// CertificateOld is used to store certificate info. // CertificateV1 is used to store certificate info
type CertificateOld struct { type CertificateV1 struct {
Domain string Domain string
CertURL string CertURL string
CertStableURL string CertStableURL string
@ -15,8 +15,8 @@ type CertificateOld struct {
Certificate []byte Certificate []byte
} }
// AccountOld is used to store lets encrypt registration info. // AccountV1 is used to store lets encrypt registration info
type AccountOld struct { type AccountV1 struct {
Email string Email string
Registration *registration.Resource Registration *registration.Resource
PrivateKey []byte PrivateKey []byte
@ -26,43 +26,40 @@ type AccountOld struct {
HTTPChallenge map[string]map[string][]byte HTTPChallenge map[string]map[string][]byte
} }
// DomainsCertificates stores a certificate for multiple domains. // DomainsCertificates stores a certificate for multiple domains
type DomainsCertificates struct { type DomainsCertificates struct {
Certs []*DomainsCertificate Certs []*DomainsCertificate
} }
// ChallengeCert stores a challenge certificate. // ChallengeCert stores a challenge certificate
type ChallengeCert struct { type ChallengeCert struct {
Certificate []byte Certificate []byte
PrivateKey []byte PrivateKey []byte
} }
// DomainsCertificate contains a certificate for multiple domains. // DomainsCertificate contains a certificate for multiple domains
type DomainsCertificate struct { type DomainsCertificate struct {
Domains traefikv1.Domain Domains dumper.Domain
Certificate *CertificateOld Certificate *CertificateV1
} }
// convertOldAccount converts account information from old account format. // convertAccountV1ToV2 converts account information from version 1 to 2
func convertOldAccount(account *AccountOld) *traefikv1.StoredData { func convertAccountV1ToV2(account *AccountV1) *dumper.StoredData {
storedData := &traefikv1.StoredData{ storedData := &dumper.StoredData{}
Account: &traefikv1.Account{ storedData.Account = &dumper.Account{
PrivateKey: account.PrivateKey, PrivateKey: account.PrivateKey,
Registration: account.Registration, Registration: account.Registration,
Email: account.Email, Email: account.Email,
KeyType: account.KeyType, KeyType: account.KeyType,
},
} }
var certs []*dumper.Certificate
var certs []*traefikv1.Certificate
for _, oldCert := range account.DomainsCertificate.Certs { for _, oldCert := range account.DomainsCertificate.Certs {
certs = append(certs, &traefikv1.Certificate{ certs = append(certs, &dumper.Certificate{
Certificate: oldCert.Certificate.Certificate, Certificate: oldCert.Certificate.Certificate,
Domain: oldCert.Domains, Domain: oldCert.Domains,
Key: oldCert.Certificate.PrivateKey, Key: oldCert.Certificate.PrivateKey,
}) })
} }
storedData.Certificates = certs storedData.Certificates = certs
return storedData return storedData
} }

View File

@ -3,48 +3,46 @@ package kv
import ( import (
"bytes" "bytes"
"compress/gzip" "compress/gzip"
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io/ioutil"
"log" "log"
"os" "os"
"strings" "strings"
"github.com/kvtools/valkeyrie" "github.com/abronan/valkeyrie"
"github.com/kvtools/valkeyrie/store" "github.com/abronan/valkeyrie/store"
"github.com/ldez/traefik-certs-dumper/v2/dumper" "github.com/ldez/traefik-certs-dumper/v2/dumper"
v1 "github.com/ldez/traefik-certs-dumper/v2/dumper/v1"
"github.com/ldez/traefik-certs-dumper/v2/hook" "github.com/ldez/traefik-certs-dumper/v2/hook"
"github.com/ldez/traefik-certs-dumper/v2/internal/traefikv1"
) )
// DefaultStoreKeySuffix is the default suffix/storage. const storeKeySuffix = "/acme/account/object"
const DefaultStoreKeySuffix = "/acme/account/object"
// Dump Dumps KV content to certificates. // Dump Dumps KV content to certificates.
func Dump(ctx context.Context, config *Config, baseConfig *dumper.BaseConfig) error { func Dump(config *Config, baseConfig *dumper.BaseConfig) error {
kvStore, err := valkeyrie.NewStore(ctx, config.StoreName, config.Endpoints, config.Options) kvStore, err := valkeyrie.NewStore(config.Backend, config.Endpoints, config.Options)
if err != nil { if err != nil {
return fmt.Errorf("unable to create client of the store: %w", err) return fmt.Errorf("unable to create client of the store: %v", err)
} }
storeKey := config.Prefix + config.Suffix storeKey := config.Prefix + storeKeySuffix
if baseConfig.Watch { if baseConfig.Watch {
return watch(ctx, kvStore, storeKey, baseConfig) return watch(kvStore, storeKey, baseConfig)
} }
pair, err := kvStore.Get(ctx, storeKey, nil) pair, err := kvStore.Get(storeKey, nil)
if err != nil { if err != nil {
return fmt.Errorf("unable to retrieve %s value: %w", storeKey, err) return fmt.Errorf("unable to retrieve %s value: %v", storeKey, err)
} }
return dumpPair(pair, baseConfig) return dumpPair(pair, baseConfig)
} }
func watch(ctx context.Context, kvStore store.Store, storeKey string, baseConfig *dumper.BaseConfig) error { func watch(kvStore store.Store, storeKey string, baseConfig *dumper.BaseConfig) error {
pairs, err := kvStore.Watch(ctx, storeKey, nil) stopCh := make(<-chan struct{})
pairs, err := kvStore.Watch(storeKey, stopCh, nil)
if err != nil { if err != nil {
return err return err
} }
@ -64,7 +62,7 @@ func watch(ctx context.Context, kvStore store.Store, storeKey string, baseConfig
log.Println("Dumped new certificate data.") log.Println("Dumped new certificate data.")
} }
hook.Exec(ctx, baseConfig.Hook) hook.Exec(baseConfig.Hook)
} }
} }
@ -74,27 +72,26 @@ func dumpPair(pair *store.KVPair, baseConfig *dumper.BaseConfig) error {
return err return err
} }
return v1.Dump(data, baseConfig) return dumper.Dump(data, baseConfig)
} }
func getStoredDataFromGzip(pair *store.KVPair) (*traefikv1.StoredData, error) { func getStoredDataFromGzip(pair *store.KVPair) (*dumper.StoredData, error) {
reader, err := gzip.NewReader(bytes.NewBuffer(pair.Value)) reader, err := gzip.NewReader(bytes.NewBuffer(pair.Value))
if err != nil { if err != nil {
return nil, fmt.Errorf("fail to create GZip reader: %w", err) return nil, fmt.Errorf("fail to create GZip reader: %v", err)
} }
acmeData, err := io.ReadAll(reader) acmeData, err := ioutil.ReadAll(reader)
if err != nil { if err != nil {
return nil, fmt.Errorf("unable to read the pair content: %w", err) return nil, fmt.Errorf("unable to read the pair content: %v", err)
} }
account := &AccountOld{} account := &AccountV1{}
//nolint:musttag // old format
if err := json.Unmarshal(acmeData, &account); err != nil { if err := json.Unmarshal(acmeData, &account); err != nil {
return nil, fmt.Errorf("unable marshal AccountOld: %w", err) return nil, fmt.Errorf("unable marshal AccountV1: %v", err)
} }
return convertOldAccount(account), nil return convertAccountV1ToV2(account), nil
} }
func isDebug() bool { func isDebug() bool {

View File

@ -1,9 +0,0 @@
// +build windows
package v1
import "strings"
func safeName(filename string) string {
return strings.ReplaceAll(filename, "*", "_")
}

View File

@ -1,132 +0,0 @@
package v2
import (
"encoding/pem"
"fmt"
"os"
"path/filepath"
"github.com/go-acme/lego/v4/certcrypto"
"github.com/ldez/traefik-certs-dumper/v2/dumper"
"github.com/ldez/traefik-certs-dumper/v2/internal/traefikv2"
)
const (
certsSubDir = "certs"
keysSubDir = "private"
)
// Dump Dumps data to certificates.
func Dump(data map[string]*traefikv2.StoredData, baseConfig *dumper.BaseConfig) error {
if baseConfig.Clean {
err := cleanDir(baseConfig.DumpPath)
if err != nil {
return fmt.Errorf("folder cleaning failed: %w", err)
}
}
if !baseConfig.DomainSubDir {
if err := os.MkdirAll(filepath.Join(baseConfig.DumpPath, certsSubDir), 0o755); err != nil {
return fmt.Errorf("certs folder creation failure: %w", err)
}
}
if err := os.MkdirAll(filepath.Join(baseConfig.DumpPath, keysSubDir), 0o755); err != nil {
return fmt.Errorf("keys folder creation failure: %w", err)
}
for _, store := range data {
for _, cert := range store.Certificates {
err := writeCert(baseConfig.DumpPath, cert.Certificate, baseConfig.CrtInfo, baseConfig.DomainSubDir)
if err != nil {
return fmt.Errorf("failed to write certificates: %w", err)
}
err = writeKey(baseConfig.DumpPath, cert.Certificate, baseConfig.KeyInfo, baseConfig.DomainSubDir)
if err != nil {
return fmt.Errorf("failed to write certificate keys: %w", err)
}
}
if store.Account == nil {
continue
}
privateKeyPem := extractPEMPrivateKey(store.Account)
err := os.WriteFile(filepath.Join(baseConfig.DumpPath, keysSubDir, "letsencrypt"+baseConfig.KeyInfo.Ext), privateKeyPem, 0o600)
if err != nil {
return fmt.Errorf("failed to write private key: %w", err)
}
}
return nil
}
func writeCert(dumpPath string, cert traefikv2.Certificate, info dumper.FileInfo, domainSubDir bool) error {
certPath := filepath.Join(dumpPath, certsSubDir, safeName(cert.Domain.Main+info.Ext))
if domainSubDir {
certPath = filepath.Join(dumpPath, safeName(cert.Domain.Main), info.Name+info.Ext)
if err := os.MkdirAll(filepath.Join(dumpPath, safeName(cert.Domain.Main)), 0o755); err != nil {
return err
}
}
return os.WriteFile(certPath, cert.Certificate, 0o666)
}
func writeKey(dumpPath string, cert traefikv2.Certificate, info dumper.FileInfo, domainSubDir bool) error {
keyPath := filepath.Join(dumpPath, keysSubDir, safeName(cert.Domain.Main+info.Ext))
if domainSubDir {
keyPath = filepath.Join(dumpPath, safeName(cert.Domain.Main), info.Name+info.Ext)
if err := os.MkdirAll(filepath.Join(dumpPath, safeName(cert.Domain.Main)), 0o755); err != nil {
return err
}
}
return os.WriteFile(keyPath, cert.Key, 0o600)
}
func extractPEMPrivateKey(account *traefikv2.Account) []byte {
var block *pem.Block
switch account.KeyType {
case certcrypto.RSA2048, certcrypto.RSA4096, certcrypto.RSA8192:
block = &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: account.PrivateKey,
}
case certcrypto.EC256, certcrypto.EC384:
block = &pem.Block{
Type: "EC PRIVATE KEY",
Bytes: account.PrivateKey,
}
default:
panic(fmt.Sprintf("unsupported key type: '%v'", account.KeyType))
}
return pem.EncodeToMemory(block)
}
func cleanDir(dumpPath string) error {
_, errExists := os.Stat(dumpPath)
if os.IsNotExist(errExists) {
return nil
}
if errExists != nil {
return errExists
}
dir, err := os.ReadDir(dumpPath)
if err != nil {
return err
}
for _, f := range dir {
if err := os.RemoveAll(filepath.Join(dumpPath, f.Name())); err != nil {
return err
}
}
return nil
}

View File

@ -1,8 +0,0 @@
//go:build !windows
// +build !windows
package v2
func safeName(filename string) string {
return filename
}

View File

@ -1,132 +0,0 @@
package v3
import (
"encoding/pem"
"fmt"
"os"
"path/filepath"
"github.com/go-acme/lego/v4/certcrypto"
"github.com/ldez/traefik-certs-dumper/v2/dumper"
"github.com/ldez/traefik-certs-dumper/v2/internal/traefikv3"
)
const (
certsSubDir = "certs"
keysSubDir = "private"
)
// Dump Dumps data to certificates.
func Dump(data map[string]*traefikv3.StoredData, baseConfig *dumper.BaseConfig) error {
if baseConfig.Clean {
err := cleanDir(baseConfig.DumpPath)
if err != nil {
return fmt.Errorf("folder cleaning failed: %w", err)
}
}
if !baseConfig.DomainSubDir {
if err := os.MkdirAll(filepath.Join(baseConfig.DumpPath, certsSubDir), 0o755); err != nil {
return fmt.Errorf("certs folder creation failure: %w", err)
}
}
if err := os.MkdirAll(filepath.Join(baseConfig.DumpPath, keysSubDir), 0o755); err != nil {
return fmt.Errorf("keys folder creation failure: %w", err)
}
for _, store := range data {
for _, cert := range store.Certificates {
err := writeCert(baseConfig.DumpPath, cert.Certificate, baseConfig.CrtInfo, baseConfig.DomainSubDir)
if err != nil {
return fmt.Errorf("failed to write certificates: %w", err)
}
err = writeKey(baseConfig.DumpPath, cert.Certificate, baseConfig.KeyInfo, baseConfig.DomainSubDir)
if err != nil {
return fmt.Errorf("failed to write certificate keys: %w", err)
}
}
if store.Account == nil {
continue
}
privateKeyPem := extractPEMPrivateKey(store.Account)
err := os.WriteFile(filepath.Join(baseConfig.DumpPath, keysSubDir, "letsencrypt"+baseConfig.KeyInfo.Ext), privateKeyPem, 0o600)
if err != nil {
return fmt.Errorf("failed to write private key: %w", err)
}
}
return nil
}
func writeCert(dumpPath string, cert traefikv3.Certificate, info dumper.FileInfo, domainSubDir bool) error {
certPath := filepath.Join(dumpPath, certsSubDir, safeName(cert.Domain.Main+info.Ext))
if domainSubDir {
certPath = filepath.Join(dumpPath, safeName(cert.Domain.Main), info.Name+info.Ext)
if err := os.MkdirAll(filepath.Join(dumpPath, safeName(cert.Domain.Main)), 0o755); err != nil {
return err
}
}
return os.WriteFile(certPath, cert.Certificate, 0o666)
}
func writeKey(dumpPath string, cert traefikv3.Certificate, info dumper.FileInfo, domainSubDir bool) error {
keyPath := filepath.Join(dumpPath, keysSubDir, safeName(cert.Domain.Main+info.Ext))
if domainSubDir {
keyPath = filepath.Join(dumpPath, safeName(cert.Domain.Main), info.Name+info.Ext)
if err := os.MkdirAll(filepath.Join(dumpPath, safeName(cert.Domain.Main)), 0o755); err != nil {
return err
}
}
return os.WriteFile(keyPath, cert.Key, 0o600)
}
func extractPEMPrivateKey(account *traefikv3.Account) []byte {
var block *pem.Block
switch account.KeyType {
case certcrypto.RSA2048, certcrypto.RSA4096, certcrypto.RSA8192:
block = &pem.Block{
Type: "RSA PRIVATE KEY",
Bytes: account.PrivateKey,
}
case certcrypto.EC256, certcrypto.EC384:
block = &pem.Block{
Type: "EC PRIVATE KEY",
Bytes: account.PrivateKey,
}
default:
panic(fmt.Sprintf("unsupported key type: '%v'", account.KeyType))
}
return pem.EncodeToMemory(block)
}
func cleanDir(dumpPath string) error {
_, errExists := os.Stat(dumpPath)
if os.IsNotExist(errExists) {
return nil
}
if errExists != nil {
return errExists
}
dir, err := os.ReadDir(dumpPath)
if err != nil {
return err
}
for _, f := range dir {
if err := os.RemoveAll(filepath.Join(dumpPath, f.Name())); err != nil {
return err
}
}
return nil
}

View File

@ -1,8 +0,0 @@
//go:build !windows
// +build !windows
package v3
func safeName(filename string) string {
return filename
}

View File

@ -1,10 +0,0 @@
//go:build windows
// +build windows
package v3
import "strings"
func safeName(filename string) string {
return strings.ReplaceAll(filename, "*", "_")
}

125
go.mod
View File

@ -1,90 +1,51 @@
module github.com/ldez/traefik-certs-dumper/v2 module github.com/ldez/traefik-certs-dumper/v2
go 1.24.0 go 1.12
require ( require (
github.com/charmbracelet/lipgloss v1.0.0 github.com/abronan/valkeyrie v0.0.0-20190419181538-ccf7df650fe4
github.com/fsnotify/fsnotify v1.9.0 github.com/cenkalti/backoff v2.1.1+incompatible // indirect
github.com/go-acme/lego/v4 v4.25.2 github.com/coreos/bbolt v1.3.2 // indirect
github.com/kvtools/boltdb v1.0.2 github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e // indirect
github.com/kvtools/consul v1.0.2 github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f // indirect
github.com/kvtools/etcdv2 v1.0.2 github.com/cpuguy83/go-md2man v1.0.10 // indirect
github.com/kvtools/etcdv3 v1.0.2 github.com/dgrijalva/jwt-go v3.2.0+incompatible // indirect
github.com/kvtools/valkeyrie v1.0.0 github.com/docker/distribution v2.7.1+incompatible // indirect
github.com/kvtools/zookeeper v1.0.2 github.com/fsnotify/fsnotify v1.4.7
github.com/go-acme/lego v2.5.0+incompatible
github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef // indirect
github.com/google/btree v1.0.0 // indirect
github.com/gorilla/websocket v1.4.0 // indirect
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0 // indirect
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect
github.com/grpc-ecosystem/grpc-gateway v1.8.5 // indirect
github.com/hashicorp/go-msgpack v0.5.4 // indirect
github.com/hashicorp/go-uuid v1.0.1 // indirect
github.com/hashicorp/memberlist v0.1.3 // indirect
github.com/inconshreveable/mousetrap v1.0.0 // indirect
github.com/jonboulle/clockwork v0.1.0 // indirect
github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/go-homedir v1.1.0
github.com/spf13/cobra v1.9.1 github.com/mitchellh/go-testing-interface v1.0.0 // indirect
github.com/spf13/viper v1.19.0 github.com/opencontainers/go-digest v1.0.0-rc1 // indirect
github.com/stretchr/testify v1.10.0 github.com/opencontainers/image-spec v1.0.1 // indirect
github.com/pascaldekloe/goe v0.1.0 // indirect
github.com/pkg/errors v0.8.1 // indirect
github.com/prometheus/client_golang v0.9.2 // indirect
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90 // indirect
github.com/sirupsen/logrus v1.4.1 // indirect
github.com/soheilhy/cmux v0.1.4 // indirect
github.com/spf13/cobra v0.0.3
github.com/spf13/viper v1.3.2
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5 // indirect
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2 // indirect
go.uber.org/atomic v1.3.2 // indirect
go.uber.org/multierr v1.1.0 // indirect
go.uber.org/zap v1.9.1 // indirect
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4 // indirect
gopkg.in/square/go-jose.v2 v2.3.1 // indirect
) )
require ( replace (
github.com/armon/go-metrics v0.4.1 // indirect github.com/ugorji/go => github.com/ugorji/go v1.1.2-0.20181022190402-e5e69e061d4f
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8 => github.com/ugorji/go/codec v1.1.2-0.20181022190402-e5e69e061d4f
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
github.com/charmbracelet/x/ansi v0.4.2 // indirect
github.com/coreos/go-semver v0.3.1 // indirect
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/fatih/color v1.18.0 // indirect
github.com/go-jose/go-jose/v4 v4.1.1 // indirect
github.com/go-zookeeper/zk v1.0.4 // indirect
github.com/gogo/protobuf v1.3.2 // indirect
github.com/golang/protobuf v1.5.4 // indirect
github.com/hashicorp/consul/api v1.28.2 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.2 // indirect
github.com/hashicorp/go-hclog v1.6.3 // indirect
github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
github.com/hashicorp/go-metrics v0.5.4 // indirect
github.com/hashicorp/go-multierror v1.1.1 // indirect
github.com/hashicorp/go-rootcerts v1.0.2 // indirect
github.com/hashicorp/golang-lru v1.0.2 // indirect
github.com/hashicorp/hcl v1.0.1-vault-5 // indirect
github.com/hashicorp/serf v0.10.2 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
github.com/magiconair/properties v1.8.9 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.16 // indirect
github.com/mitchellh/mapstructure v1.5.0 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/muesli/termenv v0.15.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/rogpeppe/go-internal v1.12.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/sagikazarmark/locafero v0.4.0 // indirect
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
github.com/sourcegraph/conc v0.3.0 // indirect
github.com/spf13/afero v1.11.0 // indirect
github.com/spf13/cast v1.7.1 // indirect
github.com/spf13/pflag v1.0.6 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
go.etcd.io/bbolt v1.3.6 // indirect
go.etcd.io/etcd/api/v3 v3.5.14 // indirect
go.etcd.io/etcd/client/pkg/v3 v3.5.14 // indirect
go.etcd.io/etcd/client/v2 v2.305.12 // indirect
go.etcd.io/etcd/client/v3 v3.5.14 // indirect
go.uber.org/multierr v1.11.0 // indirect
go.uber.org/zap v1.27.0 // indirect
golang.org/x/crypto v0.40.0 // indirect
golang.org/x/exp v0.0.0-20241210194714-1829a127f884 // indirect
golang.org/x/net v0.42.0 // indirect
golang.org/x/sys v0.34.0 // indirect
golang.org/x/text v0.27.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 // indirect
google.golang.org/grpc v1.73.0 // indirect
google.golang.org/protobuf v1.36.6 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/ini.v1 v1.67.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
) )
exclude github.com/tencentcloud/tencentcloud-sdk-go v3.0.83+incompatible

504
go.sum
View File

@ -1,393 +1,177 @@
cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/abronan/valkeyrie v0.0.0-20190419181538-ccf7df650fe4 h1:DrTAEU8rVfy2tRZObh8Hdjs819By7XfFhoOKh8xqX7Y=
github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/abronan/valkeyrie v0.0.0-20190419181538-ccf7df650fe4/go.mod h1:NOvlKBjVll/vPwdjPHGLNhKk7VrnLzLGU/VGOVPLiog=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY=
github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= github.com/aws/aws-sdk-go v1.16.23/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo=
github.com/armon/go-metrics v0.4.1 h1:hR91U9KYmb6bLBYLQjyM+3j+rcd/UhE+G78SFnF8gJA=
github.com/armon/go-metrics v0.4.1/go.mod h1:E6amYzXo6aW1tqzoZGT755KkbgrJsSdpwZ+3JqfkOG4=
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= github.com/cenkalti/backoff v2.1.1+incompatible h1:tKJnvO2kl0zmb/jA5UKAt4VoEVw1qxKWjE/Bpp46npY=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/cenkalti/backoff v2.1.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM=
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/charmbracelet/lipgloss v1.0.0 h1:O7VkGDvqEdGi93X+DeqsQ7PKHDgtQfF8j8/O2qFMQNg= github.com/coreos/etcd v3.3.11+incompatible h1:0gCnqKsq7XxMi69JsnbmMc1o+RJH3XH64sV9aiTTYko=
github.com/charmbracelet/lipgloss v1.0.0/go.mod h1:U5fy9Z+C38obMs+T+tJqst9VGzlOYGj4ri9reL3qUlo= github.com/coreos/etcd v3.3.11+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/charmbracelet/x/ansi v0.4.2 h1:0JM6Aj/g/KC154/gOP4vfxun0ff6itogDYk41kof+qk= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
github.com/charmbracelet/x/ansi v0.4.2/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw= github.com/coreos/go-semver v0.2.0 h1:3Jm3tLmsgAYcjC+4Up7hJrFBPr+n7rAqYeSw/SZazuY=
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk=
github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk=
github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo=
github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/docker/distribution v2.7.1+incompatible h1:a5mlkVzth6W5A4fOsS3D2EO5BUmsJpcB+cRlLU7cSug=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk= github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w=
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/go-acme/lego v2.5.0+incompatible h1:5fNN9yRQfv8ymH3DSsxla+4aYeQt2IgfZqHKVnK8f0s=
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= github.com/go-acme/lego v2.5.0+incompatible/go.mod h1:yzMNe9CasVUhkquNvti5nAtPmG94USbYxYrZfTkIn0M=
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/gogo/protobuf v1.2.0 h1:xU6/SpYbvkNYiptHJYEDRseDLvYE7wSqhYYNy0QSUzI=
github.com/go-acme/lego/v4 v4.25.2 h1:+D1Q+VnZrD+WJdlkgUEGHFFTcDrwGlE7q24IFtMmHDI= github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/go-acme/lego/v4 v4.25.2/go.mod h1:OORYyVNZPaNdIdVYCGSBNRNZDIjhQbPuFxwGDgWj/yM= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/go-jose/go-jose/v4 v4.1.1 h1:JYhSgy4mXXzAdF3nUx3ygx347LRXJRrpgyU3adRmkAI= github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/go-jose/go-jose/v4 v4.1.1/go.mod h1:BdsZGqgdO3b6tTc6LSE56wcDbMMLuPsw5d4ZD5f94kA= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/golang/protobuf v1.2.0 h1:P3YflyNX/ehuJFLhxviNdFxQPkGK5cDcApsge1SqnvM=
github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A=
github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-zookeeper/zk v1.0.4 h1:DPzxraQx7OrPyXq2phlGlNSIyWEsAox0RJmjTseMV6I=
github.com/go-zookeeper/zk v1.0.4/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw=
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/grpc-ecosystem/grpc-gateway v1.8.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= github.com/hashicorp/consul v1.4.0 h1:PQTW4xCuAExEiSbhrsFsikzbW5gVBoi74BjUvYFyKHw=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/hashicorp/consul v1.4.0/go.mod h1:mFrjN1mfidgJfYP1xrJCF+AfRhr6Eaqhb2+sfyn/OOI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
github.com/google/btree v1.1.2 h1:xf4v41cLI2Z6FxbKm+8Bu+m8ifhj15JuZ9sa0jZCMUU=
github.com/google/btree v1.1.2/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/consul/api v1.28.2 h1:mXfkRHrpHN4YY3RqL09nXU1eHKLNiuAN4kHvDQ16k/8=
github.com/hashicorp/consul/api v1.28.2/go.mod h1:KyzqzgMEya+IZPcD65YFoOVAgPpbfERu4I/tzG6/ueE=
github.com/hashicorp/consul/sdk v0.16.0 h1:SE9m0W6DEfgIVCJX7xU+iv/hUl4m/nxqMTnCdMxDpJ8=
github.com/hashicorp/consul/sdk v0.16.0/go.mod h1:7pxqqhqoaPqnBnzXD1StKed62LqJeClzVsUEy85Zr0A=
github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/go-cleanhttp v0.5.0 h1:wvCrVc9TjDls6+YGAF2hAifE1E5U1+b4tH6KdvN3Gig=
github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80=
github.com/hashicorp/go-cleanhttp v0.5.2 h1:035FKYIWjmULyFRBKPs8TBQoi0x6d9G4xc9neXJWAZQ=
github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48=
github.com/hashicorp/go-hclog v1.6.3 h1:Qr2kF+eVWjTiYmU7Y31tYlP1h0q/X3Nl3tPGdaB11/k=
github.com/hashicorp/go-hclog v1.6.3/go.mod h1:W4Qnvbt70Wk/zYJryRzDRU/4r0kIg0PVHBcfoyhpF5M=
github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc= github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= github.com/hashicorp/go-msgpack v0.5.4/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM=
github.com/hashicorp/go-metrics v0.5.4 h1:8mmPiIJkTPPEbAiV97IxdAGNdRdaWwVap1BU6elejKY= github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk=
github.com/hashicorp/go-metrics v0.5.4/go.mod h1:CG5yz4NZ/AI/aQt9Ucm/vdBnbh7fvmv4lxZ350i+QQI= github.com/hashicorp/go-rootcerts v1.0.0 h1:Rqb66Oo1X/eSV1x66xbDccZjhJigjg0+e82kpwzSwCI=
github.com/hashicorp/go-msgpack v0.5.5 h1:i9R9JSrqIz0QVLz3sz+i3YJdT7TTSLcfLLzJi9aZTuI= github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU=
github.com/hashicorp/go-msgpack/v2 v2.1.2 h1:4Ee8FTp834e+ewB71RDrQ0VKpyFdrKOjvYtnQ/ltVj0= github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU=
github.com/hashicorp/go-msgpack/v2 v2.1.2/go.mod h1:upybraOAblm4S7rx0+jeNy+CWWhzywQsSRV5033mMu4=
github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs=
github.com/hashicorp/go-rootcerts v1.0.2 h1:jzhAVGtqPKbwpyCPELlgNWhE1znq+qwJtW5Oi2viEzc=
github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8=
github.com/hashicorp/go-sockaddr v1.0.5 h1:dvk7TIXCZpmfOlM+9mlcrWmWjw/wlKT+VDq2wMvfPJU=
github.com/hashicorp/go-sockaddr v1.0.5/go.mod h1:uoUUmtwU7n9Dv3O4SNLeFvg0SxQ3lyjsj6+CCykpaxI=
github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/hcl v1.0.1-vault-5 h1:kI3hhbbyzr4dldA8UdTb7ZlVVlI2DACdCfz31RPDgJM= github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I=
github.com/hashicorp/hcl v1.0.1-vault-5/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM= github.com/hashicorp/serf v0.8.1 h1:mYs6SMzu72+90OcPa5wr3nfznA4Dw9UyR791ZFNOIf4=
github.com/hashicorp/memberlist v0.5.2 h1:rJoNPWZ0juJBgqn48gjy59K5H4rNgvUoM1kUD7bXiuI= github.com/hashicorp/serf v0.8.1/go.mod h1:h/Ru6tmZazX7WO/GDmwdpS975F019L4t5ng5IgwbNrE=
github.com/hashicorp/memberlist v0.5.2/go.mod h1:Ri9p/tRShbjYnpNf4FFPXG7wxEGY4Nrcn6E7jrVa//4= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/hashicorp/serf v0.10.2 h1:m5IORhuNSjaxeljg5DeQVDlQyVkhRIjJDimbkCa8aAc= github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k=
github.com/hashicorp/serf v0.10.2/go.mod h1:T1CmSGfSeGfnfNy/w0odXQUR1rfECGd2Qdsp84DjOiY= github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM=
github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM=
github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/magiconair/properties v1.8.0 h1:LLgXmsheXeRoUOBOjtwPQCWIYqM/LU1ayDtDePerRcY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/kvtools/boltdb v1.0.2 h1:GZaUZ3AOw33mdK8r7p5bRqfhe504/BcwSVj6efQzLNs=
github.com/kvtools/boltdb v1.0.2/go.mod h1:ERqvkjiHCFTLy7WcHitvgpDH0y14pT3r+ZwkfqJ6tQM=
github.com/kvtools/consul v1.0.2 h1:ltPgs4Ld09Xaa7zrOJ/TewBYKAsr11/LRFpErdkb8AA=
github.com/kvtools/consul v1.0.2/go.mod h1:bFnzfGJ5ZIRRXCBGBmwhJlLdEWOlrjOcS1WjyAQzaJA=
github.com/kvtools/etcdv2 v1.0.2 h1:Bigfbm2f4uZveD0Av/5Vxs7MdqnWDVnAFpSF4QEL1c8=
github.com/kvtools/etcdv2 v1.0.2/go.mod h1:Ye5IwvG5KxUdcP14Yag6Pc3E9bzxy1j7zlvLK8eI/bU=
github.com/kvtools/etcdv3 v1.0.2 h1:EB0mAtzqe1folE7m7Q6wnCXcGwaOmrYmsVmF3hNsTKI=
github.com/kvtools/etcdv3 v1.0.2/go.mod h1:Xr6DbwqjuCEcXAIWmXxw0DX+N5BhuvablXgN90XeqMM=
github.com/kvtools/valkeyrie v1.0.0 h1:LAITop2wPoYCMitR24GZZsW0b57hmI+ePD18VRTtOf0=
github.com/kvtools/valkeyrie v1.0.0/go.mod h1:bDi/OdhJCSbGPMsCgUQl881yuEweKCSItAtTBI+ZjpU=
github.com/kvtools/zookeeper v1.0.2 h1:uK0CzQa+mtKGxDDH+DeqXo2HC1Kx4hWXZ7pX/zS4aTo=
github.com/kvtools/zookeeper v1.0.2/go.mod h1:6TfxUwJ7IuBk5srgnoe528W0ftanNECHgOiShx/t0Aw=
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM=
github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/dns v1.1.67 h1:kg0EHj0G4bfT5/oOys6HhZw4vmMlnoZ+gDu8tJ/AlI0= github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.67/go.mod h1:fujopn7TB3Pu3JM69XaawiU0wqjpL9/8xGop5UrTPps= github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI=
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/mitchellh/mapstructure v1.1.2 h1:fmNYVwqnSfB9mZU6OS2O6GsXM+wcskZDuKQzvN1EDeE=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/opencontainers/go-digest v1.0.0-rc1 h1:WzifXhOVOEOuFYOJAW6aQqW0TooG2iki3E3Ii+WN7gQ=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/opencontainers/image-spec v1.0.1 h1:JMemWkRwHx4Zj+fVxWoMCFm/8sYGGrUVojFA6h/TRcI=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/pascaldekloe/goe v0.1.0 h1:cBOtyMzM9HTpWjXfbbunk26uA6nG3a8n06Wieeh0MwY=
github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= github.com/pelletier/go-toml v1.2.0 h1:T5zMGML61Wp+FlcbWjRDT7yAxhJNAiPPLOFECq181zc=
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/prometheus/client_golang v0.9.2/go.mod h1:OsXs2jCmiKlQ1lTBmv21f2mNfw4xf/QclQDMrYNZzcM=
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU=
github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M=
github.com/prometheus/client_golang v1.11.1/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo=
github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/samuel/go-zookeeper v0.0.0-20180130194729-c4fab1ac1bec h1:6ncX5ko6B9LntYM0YBRXkiSaZMmLYeZ/NWcmeB43mMY=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/samuel/go-zookeeper v0.0.0-20180130194729-c4fab1ac1bec/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E=
github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A=
github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU=
github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8=
github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4=
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/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ=
github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4=
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 h1:nn5Wsu0esKSJiIVhscUtVbo7ada43DJhG55ua/hjS5I=
github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= github.com/spf13/afero v1.1.2 h1:m8/z1t7/fwjysjQRYbP0RD+bUIF/8tJwPdEZsI83ACI=
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= github.com/spf13/cast v1.3.0 h1:oget//CVOEoFewqQxwr0Ej5yjygnqGkvggSE/gB35Q8=
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= github.com/spf13/cobra v0.0.3 h1:ZlrZ4XsMRm04Fr5pSFxBgfND2EBVa1nLpiy1stUsX/8=
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/jwalterweatherman v1.0.0 h1:XHEdyB+EcvlqZamSM4ZOMGlc93t6AcsBEu9Gc1vn7yk=
github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.3.2 h1:VUFqw5KcqRf7i70GOzW7N+Q7+gxVBkSSqiXB12+JQ4M=
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= github.com/ugorji/go v1.1.2-0.20181022190402-e5e69e061d4f h1:E6ip3gLExd3v9o1iiZMMxOaC/XiWk3mPbDTOPLL0eWw=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/ugorji/go v1.1.2-0.20181022190402-e5e69e061d4f/go.mod h1:hnLbHMwcvSihnDhEfx2/BzKp2xb0Y+ErdfYcrs9tkJQ=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/ugorji/go/codec v1.1.2-0.20181022190402-e5e69e061d4f h1:CZG9W9a8rpiPXPmkGcyXoD9sLF+JfLh/x+BpYHGhK+o=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/ugorji/go/codec v1.1.2-0.20181022190402-e5e69e061d4f/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.etcd.io/bbolt v1.3.1-etcd.8 h1:6J7QAKqfFBGnU80KRnuQxfjjeE5xAGE/qB810I3FQHQ=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= go.etcd.io/bbolt v1.3.1-etcd.8/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU=
go.etcd.io/bbolt v1.3.6 h1:/ecaJf0sk1l4l6V4awd65v2C3ILy7MSj+s/x1ADCIMU= go.etcd.io/etcd v3.3.11+incompatible h1:AVwRXu9VIzZcvVe1nSirTVkNv7WT3/hwdMRrDVFsf3A=
go.etcd.io/bbolt v1.3.6/go.mod h1:qXsaaIqmgQH0T+OPdb99Bf+PKfBBQVAdyD6TY9G8XM4= go.etcd.io/etcd v3.3.11+incompatible/go.mod h1:yaeTdrJi5lOmYerz05bd8+V7KubZs8YSFZfzsF9A6aI=
go.etcd.io/etcd/api/v3 v3.5.14 h1:vHObSCxyB9zlF60w7qzAdTcGaglbJOpSj1Xj9+WGxq0= go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE=
go.etcd.io/etcd/api/v3 v3.5.14/go.mod h1:BmtWcRlQvwa1h3G2jvKYwIQy4PkHlDej5t7uLMUdJUU= go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0=
go.etcd.io/etcd/client/pkg/v3 v3.5.14 h1:SaNH6Y+rVEdxfpA2Jr5wkEvN6Zykme5+YnbCkxvuWxQ= go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q=
go.etcd.io/etcd/client/pkg/v3 v3.5.14/go.mod h1:8uMgAokyG1czCtIdsq+AGyYQMvpIKnSvPjFMunkgeZI= golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
go.etcd.io/etcd/client/v2 v2.305.12 h1:0m4ovXYo1CHaA/Mp3X/Fak5sRNIWf01wk/X1/G3sGKI= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9 h1:mKdxBk7AujPs8kU4m80U72y/zjbZ3UcXC7dClwKbUI0=
go.etcd.io/etcd/client/v2 v2.305.12/go.mod h1:aQ/yhsxMu+Oht1FOupSr60oBvcS9cKXHrzBpDsPTf9E= golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
go.etcd.io/etcd/client/v3 v3.5.14 h1:CWfRs4FDaDoSz81giL7zPpZH2Z35tbOrAJkkjMqOupg= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
go.etcd.io/etcd/client/v3 v3.5.14/go.mod h1:k3XfdV/VIHy/97rqWjoUzrj9tk7GgJGH9J8L4dNXmAk= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
go.opentelemetry.io/otel v1.36.0 h1:UumtzIklRBY6cI/lllNZlALOF5nNIzJVb16APdvgTXg= golang.org/x/net v0.0.0-20181220203305-927f97764cc3 h1:eH6Eip3UpmR+yM/qI9Ijluzb1bNv/cAU/n+6l8tRSis=
go.opentelemetry.io/otel v1.36.0/go.mod h1:/TcFMXYjyRNh8khOAO9ybYkqaDBb/70aVwkNML4pP8E= golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
go.opentelemetry.io/otel/metric v1.36.0 h1:MoWPKVhQvJ+eeXWHFBOPoBOi20jh6Iq2CcCREuTYufE= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
go.opentelemetry.io/otel/metric v1.36.0/go.mod h1:zC7Ks+yeyJt4xig9DEw9kuUFe5C3zLbVjV2PzT6qzbs= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
go.opentelemetry.io/otel/sdk v1.35.0 h1:iPctf8iprVySXSKJffSS79eOjl9pvxV9ZqOWT0QejKY=
go.opentelemetry.io/otel/sdk v1.35.0/go.mod h1:+ga1bZliga3DxJ3CQGg3updiaAJoNECOgJREo9KHGQg=
go.opentelemetry.io/otel/sdk/metric v1.35.0 h1:1RriWBmCKgkeHEhM7a2uMjMUfP7MsOF5JpUCaEqEI9o=
go.opentelemetry.io/otel/sdk/metric v1.35.0/go.mod h1:is6XYCUMpcKi+ZsOvfluY5YstFnhW0BidkR+gL+qN+w=
go.opentelemetry.io/otel/trace v1.36.0 h1:ahxWNuqZjpdiFAyrIoQ4GIiAIhxAunQR6MUoKrsNd4w=
go.opentelemetry.io/otel/trace v1.36.0/go.mod h1:gQ+OnDZzrybY4k4seLzPAWNwVBBVlF2szhehOBB/tGA=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
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/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
golang.org/x/exp v0.0.0-20241210194714-1829a127f884 h1:Y/Mj/94zIQQGHVSv1tTtQBDaQaJe62U9bkDZKKyhPCU=
golang.org/x/exp v0.0.0-20241210194714-1829a127f884/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.25.0 h1:n7a+ZbQKQA/Ysbyb0/6IbB1H/X41mKgbhfv7AfG/44w=
golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs=
golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/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-20181205085412-a5c9d58dba9a h1:1n5lsVfiQW3yfsRGu98756EH1YthsFqr/5mxHduZW2A=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200923182605-d9f96fdee20d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220503163025-988cb79eb6c6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8 h1:Nw54tB0rB7hY/N0NQvRW8DG4Yk3Q6T9cu9RcFQDu1tc=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= google.golang.org/grpc v1.18.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs=
golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= google.golang.org/grpc v1.19.0 h1:cfg4PD8YEdSFnm7qLV4++93WcmhH2nIUhMjhdCvl3j8=
golang.org/x/tools v0.34.0 h1:qIpSLOxeCYGg9TrcJokLBG4KFA6d795g0xkBkiESGlo= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
golang.org/x/tools v0.34.0/go.mod h1:pAP9OwEaY1CAW3HOmg3hLZC5Z0CCmzjAF2UQMSqNARg=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7 h1:FiusG7LWj+4byqhbvmB+Q93B/mOxJLN2DTozDuZm4EU=
google.golang.org/genproto/googleapis/api v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:kXqgZtrWaf6qS3jZOCnCH7WYfrvFjkC51bM8fz3RsCA=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7 h1:pFyd6EwwL2TqFf8emdthzeX+gZE1ElRq3iM8pui4KBY=
google.golang.org/genproto/googleapis/rpc v0.0.0-20250707201910-8d1bb00bc6a7/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A=
google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok=
google.golang.org/grpc v1.73.0/go.mod h1:50sbHOUqWoCQGI8V2HQLJM0B+LMlIUjNSZmow7EVBQc=
google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.36.6 h1:z1NpPI8ku2WgiWnf+t9wTPsn6eP1L7ksHUlkfLvd9xY=
google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/redis.v5 v5.2.9/go.mod h1:6gtv0/+A4iM08kdRfocWYB3bLX2tebpNtfKlFT6H4mY=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo=
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/square/go-jose.v2 v2.3.1 h1:SK5KegNXmKmqE342YYN2qPHEnUYeoMiXXl1poUlI+o4=
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74=
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@ -4,37 +4,36 @@ import (
"context" "context"
"errors" "errors"
"fmt" "fmt"
"os"
"os/exec" "os/exec"
"strings" "strings"
"time" "time"
) )
// Exec Execute a command on a go routine. // Exec Execute a command on a go routine.
func Exec(ctx context.Context, command string) { func Exec(command string) {
if command == "" { if command == "" {
return return
} }
go func() { go func() {
errH := execute(ctx, command) errH := execute(command)
if errH != nil { if errH != nil {
panic(errH) panic(errH)
} }
}() }()
} }
func execute(ctx context.Context, command string) error { func execute(command string) error {
ctxCmd, cancel := context.WithTimeout(ctx, 30*time.Second) ctxCmd, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel() defer cancel()
parts := strings.Fields(os.ExpandEnv(command)) parts := strings.Fields(command)
output, err := exec.CommandContext(ctxCmd, parts[0], parts[1:]...).CombinedOutput() output, err := exec.CommandContext(ctxCmd, parts[0], parts[1:]...).CombinedOutput()
if len(output) > 0 { if len(output) > 0 {
fmt.Println(string(output)) fmt.Println(string(output))
} }
if errors.Is(ctxCmd.Err(), context.DeadlineExceeded) { if ctxCmd.Err() == context.DeadlineExceeded {
return errors.New("hook timed out") return errors.New("hook timed out")
} }

View File

@ -1,30 +0,0 @@
package hook
import (
"testing"
)
func Test_execute(t *testing.T) {
testCases := []struct {
desc string
command string
}{
{
desc: "expand env vars",
command: `echo "${GOPATH} ${GOARCH}"`,
},
{
desc: "simple",
command: `echo 'hello'`,
},
}
for _, test := range testCases {
t.Run(test.desc, func(t *testing.T) {
err := execute(t.Context(), test.command)
if err != nil {
t.Fatal(err)
}
})
}
}

View File

@ -3,17 +3,16 @@ package main
import ( import (
"bytes" "bytes"
"compress/gzip" "compress/gzip"
"context" "io/ioutil"
"log" "log"
"os"
"path/filepath"
"time" "time"
"github.com/kvtools/boltdb" "github.com/abronan/valkeyrie"
"github.com/kvtools/consul" "github.com/abronan/valkeyrie/store"
"github.com/kvtools/etcdv3" "github.com/abronan/valkeyrie/store/boltdb"
"github.com/kvtools/valkeyrie" "github.com/abronan/valkeyrie/store/consul"
"github.com/kvtools/zookeeper" etcdv3 "github.com/abronan/valkeyrie/store/etcd/v3"
"github.com/abronan/valkeyrie/store/zookeeper"
) )
const storeKey = "traefik/acme/account/object" const storeKey = "traefik/acme/account/object"
@ -22,43 +21,38 @@ func main() {
log.SetFlags(log.Lshortfile) log.SetFlags(log.Lshortfile)
source := "./acme.json" source := "./acme.json"
err := loadData(context.Background(), source) err := loadData(source)
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
} }
func loadData(ctx context.Context, source string) error { func loadData(source string) error {
content, err := readFile(source) content, err := readFile(source)
if err != nil { if err != nil {
return err return err
} }
// Consul // Consul
err = putData(store.CONSUL, []string{"localhost:8500"}, content)
err = putData(ctx, consul.StoreName, []string{"localhost:8500"},
&consul.Config{ConnectionTimeout: 3 * time.Second}, content)
if err != nil { if err != nil {
return err return err
} }
// ETCD v3 // ETCD v3
err = putData(ctx, etcdv3.StoreName, []string{"localhost:2379"}, err = putData(store.ETCDV3, []string{"localhost:2379"}, content)
&etcdv3.Config{ConnectionTimeout: 3 * time.Second}, content)
if err != nil { if err != nil {
return err return err
} }
// Zookeeper // Zookeeper
err = putData(ctx, zookeeper.StoreName, []string{"localhost:2181"}, err = putData(store.ZK, []string{"localhost:2181"}, content)
&zookeeper.Config{ConnectionTimeout: 3 * time.Second}, content)
if err != nil { if err != nil {
return err return err
} }
// BoltDB // BoltDB
err = putData(ctx, boltdb.StoreName, []string{"/tmp/test-traefik-certs-dumper.db"}, err = putData(store.BOLTDB, []string{"/tmp/test-traefik-certs-dumper.db"}, content)
&boltdb.Config{ConnectionTimeout: 3 * time.Second, Bucket: "traefik"}, content)
if err != nil { if err != nil {
return err return err
} }
@ -66,13 +60,29 @@ func loadData(ctx context.Context, source string) error {
return nil return nil
} }
func putData(ctx context.Context, backend string, addrs []string, options valkeyrie.Config, content []byte) error { func putData(backend store.Backend, addrs []string, content []byte) error {
kvStore, err := valkeyrie.NewStore(ctx, backend, addrs, options) storeConfig := &store.Config{
ConnectionTimeout: 3 * time.Second,
Bucket: "traefik",
}
switch backend {
case store.CONSUL:
consul.Register()
case store.ETCDV3:
etcdv3.Register()
case store.ZK:
zookeeper.Register()
case store.BOLTDB:
boltdb.Register()
}
kvStore, err := valkeyrie.NewStore(backend, addrs, storeConfig)
if err != nil { if err != nil {
return err return err
} }
if err := kvStore.Put(ctx, storeKey, content, nil); err != nil { if err := kvStore.Put(storeKey, content, nil); err != nil {
return err return err
} }
@ -81,7 +91,7 @@ func putData(ctx context.Context, backend string, addrs []string, options valkey
} }
func readFile(source string) ([]byte, error) { func readFile(source string) ([]byte, error) {
content, err := os.ReadFile(filepath.Clean(source)) content, err := ioutil.ReadFile(source)
if err != nil { if err != nil {
return nil, err return nil, err
} }

255
internal/multiarch.go Normal file
View File

@ -0,0 +1,255 @@
package main
import (
"encoding/json"
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"os/exec"
"strings"
"text/template"
"github.com/docker/distribution/manifest/manifestlist"
)
type buildOption struct {
OS string
GoARM string
GoARCH string
Variant string
}
type actions struct {
Builds [][]string
Push [][]string
ManifestAnnotate [][]string
ManifestCreate []string
ManifestPush []string
}
func main() {
log.SetFlags(log.Lshortfile)
imageName := flag.String("image-name", "ldez/traefik-certs-dumper", "")
version := flag.String("version", "", "")
baseImageName := flag.String("base-image-name", "alpine:3.9", "")
dryRun := flag.Bool("dry-run", true, "")
flag.Parse()
require("image-name", imageName)
require("version", version)
require("base-image-name", baseImageName)
// FIXME
// _, travisTag := os.LookupEnv("TRAVIS_TAG")
// if !travisTag {
// log.Println("Skipping deploy")
// os.Exit(0)
// }
targets := []string{"arm.v6", "arm.v7", "arm.v8", "amd64", "386"}
actions, err := buildActions(*imageName, *version, *baseImageName, targets)
if err != nil {
log.Fatal(err)
}
err = execute(actions, *dryRun)
if err != nil {
log.Fatal(err)
}
}
func require(fieldName string, field *string) {
if field == nil || *field == "" {
log.Fatalf("%s is required", fieldName)
}
}
func buildActions(imageName, version, baseImageName string, targets []string) (actions, error) {
manifest, err := getManifest(baseImageName)
if err != nil {
return actions{}, err
}
buildOptions := map[string]buildOption{
"arm.v5": {OS: "linux", GoARM: "5", GoARCH: "arm", Variant: "v5"},
"arm.v6": {OS: "linux", GoARM: "6", GoARCH: "arm", Variant: "v6"},
"arm.v7": {OS: "linux", GoARM: "7", GoARCH: "arm", Variant: "v7"},
"arm.v8": {OS: "linux", GoARCH: "arm64", Variant: "v8"},
"amd64": {OS: "linux", GoARCH: "amd64"},
"386": {OS: "linux", GoARCH: "386"},
}
actions := actions{}
for _, target := range targets {
buildOption := buildOptions[target]
descriptor, err := findManifestDescriptor(buildOption, manifest.Manifests)
if err != nil {
log.Fatal(err)
}
dockerfile := fmt.Sprintf("%s-%s-%s.Dockerfile", buildOption.OS, buildOption.GoARCH, buildOption.GoARM)
actions.Builds = append(actions.Builds, []string{
"build",
"-t", fmt.Sprintf("%s:%s-%s", imageName, version, target),
"-f", dockerfile,
".",
})
err = createDockerfile(dockerfile, buildOption, descriptor, baseImageName)
if err != nil {
log.Fatal(err)
}
actions.Push = append(actions.Push, []string{"push", fmt.Sprintf(`%s:%s-%s`, imageName, version, target)})
ma := []string{
"manifest", "annotate",
fmt.Sprintf(`"%s:%s"`, imageName, version),
fmt.Sprintf(`"%s:%s-%s"`, imageName, version, target),
fmt.Sprintf(`--os="%s"`, buildOption.OS),
fmt.Sprintf(`--arch="%s"`, buildOption.GoARCH),
}
if buildOption.Variant != "" {
ma = append(ma, fmt.Sprintf(`--variant="%s"`, buildOption.Variant))
}
actions.ManifestAnnotate = append(actions.ManifestAnnotate, ma)
}
actions.ManifestCreate = []string{
"manifest", "create", "--amend",
fmt.Sprintf("%s:%s", imageName, version),
}
for _, target := range targets {
actions.ManifestCreate = append(actions.ManifestCreate, fmt.Sprintf(`"%s:%s-%s"`, imageName, version, target))
}
actions.ManifestPush = []string{
"manifest", "push", fmt.Sprintf("%s:%s", imageName, version),
}
return actions, nil
}
func createDockerfile(dockerfile string, buildOption buildOption, descriptor manifestlist.ManifestDescriptor, baseImageName string) error {
base := template.New("tmpl.Dockerfile")
parse, err := base.ParseFiles("./internal/tmpl.Dockerfile")
if err != nil {
return err
}
data := map[string]interface{}{
"GoOS": buildOption.OS,
"GoARCH": buildOption.GoARCH,
"GoARM": buildOption.GoARM,
"RuntimeImage": fmt.Sprintf("%s@%s", baseImageName, descriptor.Digest),
}
file, err := os.Create(dockerfile)
if err != nil {
return err
}
return parse.Execute(file, data)
}
func getManifest(baseImageName string) (*manifestlist.ManifestList, error) {
manifestPath := "./manifest.json"
if _, errExist := os.Stat(manifestPath); os.IsNotExist(errExist) {
cmd := exec.Command("docker", "manifest", "inspect", baseImageName)
cmd.Env = append(cmd.Env, "DOCKER_CLI_EXPERIMENTAL=enabled")
output, err := cmd.CombinedOutput()
if err != nil {
return nil, err
}
err = ioutil.WriteFile(manifestPath, output, 777)
if err != nil {
return nil, err
}
} else if errExist != nil {
return nil, errExist
}
bytes, err := ioutil.ReadFile(manifestPath)
if err != nil {
return nil, err
}
manifest := &manifestlist.ManifestList{}
err = json.Unmarshal(bytes, manifest)
if err != nil {
return nil, err
}
return manifest, nil
}
func findManifestDescriptor(criterion buildOption, descriptors []manifestlist.ManifestDescriptor) (manifestlist.ManifestDescriptor, error) {
for _, descriptor := range descriptors {
if descriptor.Platform.OS == criterion.OS &&
descriptor.Platform.Architecture == criterion.GoARCH &&
descriptor.Platform.Variant == criterion.Variant {
return descriptor, nil
}
}
return manifestlist.ManifestDescriptor{}, fmt.Errorf("not supported: %v", criterion)
}
func execute(actions actions, dryRun bool) error {
for _, args := range actions.Builds {
if err := execDocker(args, dryRun); err != nil {
return err
}
}
return nil
// for _, args := range actions.Push {
// if err := execDocker(args, dryRun); err != nil {
// return err
// }
// }
//
// if err := execDocker(actions.ManifestCreate, dryRun); err != nil {
// return err
// }
//
// for _, args := range actions.ManifestAnnotate {
// if err := execDocker(args, dryRun); err != nil {
// return err
// }
// }
//
// return execDocker(actions.ManifestPush, dryRun)
}
func execDocker(args []string, dryRun bool) error {
if dryRun {
fmt.Println("docker", strings.Join(args, " "))
return nil
}
cmd := exec.Command("docker", args...)
cmd.Env = append(cmd.Env, "DOCKER_CLI_EXPERIMENTAL=enabled")
output, err := cmd.CombinedOutput()
log.Println(string(output))
if err != nil {
return err
}
return nil
}

24
internal/tmpl.Dockerfile Normal file
View File

@ -0,0 +1,24 @@
FROM golang:1-alpine as builder
RUN apk --update upgrade \
&& apk --no-cache --no-progress add git make gcc musl-dev ca-certificates tzdata
WORKDIR /go/src/github.com/ldez/traefik-certs-dumper
ENV GO111MODULE on
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN GOARCH={{ .GoARCH }} GOARM={{ .GoARM }} make build
FROM {{ .RuntimeImage }}
# Not supported for multi-arch without Buildkit or QEMU
#RUN apk --update upgrade \
# && apk --no-cache --no-progress add ca-certificates
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=builder /go/src/github.com/ldez/traefik-certs-dumper/traefik-certs-dumper /usr/bin/traefik-certs-dumper
ENTRYPOINT ["/usr/bin/traefik-certs-dumper"]

View File

@ -1,101 +0,0 @@
package traefikv2
import (
"crypto"
"crypto/x509"
"github.com/go-acme/lego/v4/certcrypto"
"github.com/go-acme/lego/v4/registration"
)
// StoredData represents the data managed by Store.
type StoredData struct {
Account *Account
Certificates []*CertAndStore
}
// Account is used to store lets encrypt registration info.
type Account struct {
Email string
Registration *registration.Resource
PrivateKey []byte
KeyType certcrypto.KeyType
}
// GetEmail returns email.
func (a *Account) GetEmail() string {
return a.Email
}
// GetRegistration returns lets encrypt registration resource.
func (a *Account) GetRegistration() *registration.Resource {
return a.Registration
}
// GetPrivateKey returns private key.
func (a *Account) GetPrivateKey() crypto.PrivateKey {
privateKey, err := x509.ParsePKCS1PrivateKey(a.PrivateKey)
if err != nil {
return nil
}
return privateKey
}
// CertAndStore allows mapping a TLS certificate to a TLS store.
type CertAndStore struct {
Certificate
Store string
}
// Certificate is a struct which contains all data needed from an ACME certificate.
type Certificate struct {
Domain Domain `json:"domain,omitempty" toml:"domain,omitempty" yaml:"domain,omitempty"`
Certificate []byte `json:"certificate,omitempty" toml:"certificate,omitempty" yaml:"certificate,omitempty"`
Key []byte `json:"key,omitempty" toml:"key,omitempty" yaml:"key,omitempty"`
}
// Domain holds a domain name with SANs.
type Domain struct {
// Main defines the main domain name.
Main string `description:"Default subject name." json:"main,omitempty" toml:"main,omitempty" yaml:"main,omitempty"`
// SANs defines the subject alternative domain names.
SANs []string `description:"Subject alternative names." json:"sans,omitempty" toml:"sans,omitempty" yaml:"sans,omitempty"`
}
// ToStrArray convert a domain into an array of strings.
func (d *Domain) ToStrArray() []string {
var domains []string
if d.Main != "" {
domains = []string{d.Main}
}
return append(domains, d.SANs...)
}
// Set sets a domains from an array of strings.
func (d *Domain) Set(domains []string) {
if len(domains) > 0 {
d.Main = domains[0]
d.SANs = domains[1:]
}
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (d *Domain) DeepCopyInto(out *Domain) {
*out = *d
if d.SANs != nil {
in, out := &d.SANs, &out.SANs
*out = make([]string, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Domain.
func (d *Domain) DeepCopy() *Domain {
if d == nil {
return nil
}
out := new(Domain)
d.DeepCopyInto(out)
return out
}

View File

@ -1,101 +0,0 @@
package traefikv3
import (
"crypto"
"crypto/x509"
"github.com/go-acme/lego/v4/certcrypto"
"github.com/go-acme/lego/v4/registration"
)
// StoredData represents the data managed by Store.
type StoredData struct {
Account *Account
Certificates []*CertAndStore
}
// Account is used to store lets encrypt registration info.
type Account struct {
Email string
Registration *registration.Resource
PrivateKey []byte
KeyType certcrypto.KeyType
}
// GetEmail returns email.
func (a *Account) GetEmail() string {
return a.Email
}
// GetRegistration returns lets encrypt registration resource.
func (a *Account) GetRegistration() *registration.Resource {
return a.Registration
}
// GetPrivateKey returns private key.
func (a *Account) GetPrivateKey() crypto.PrivateKey {
privateKey, err := x509.ParsePKCS1PrivateKey(a.PrivateKey)
if err != nil {
return nil
}
return privateKey
}
// CertAndStore allows mapping a TLS certificate to a TLS store.
type CertAndStore struct {
Certificate
Store string
}
// Certificate is a struct which contains all data needed from an ACME certificate.
type Certificate struct {
Domain Domain `json:"domain,omitempty" toml:"domain,omitempty" yaml:"domain,omitempty"`
Certificate []byte `json:"certificate,omitempty" toml:"certificate,omitempty" yaml:"certificate,omitempty"`
Key []byte `json:"key,omitempty" toml:"key,omitempty" yaml:"key,omitempty"`
}
// Domain holds a domain name with SANs.
type Domain struct {
// Main defines the main domain name.
Main string `description:"Default subject name." json:"main,omitempty" toml:"main,omitempty" yaml:"main,omitempty"`
// SANs defines the subject alternative domain names.
SANs []string `description:"Subject alternative names." json:"sans,omitempty" toml:"sans,omitempty" yaml:"sans,omitempty"`
}
// ToStrArray convert a domain into an array of strings.
func (d *Domain) ToStrArray() []string {
var domains []string
if d.Main != "" {
domains = []string{d.Main}
}
return append(domains, d.SANs...)
}
// Set sets a domains from an array of strings.
func (d *Domain) Set(domains []string) {
if len(domains) > 0 {
d.Main = domains[0]
d.SANs = domains[1:]
}
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (d *Domain) DeepCopyInto(out *Domain) {
*out = *d
if d.SANs != nil {
in, out := &d.SANs, &out.SANs
*out = make([]string, len(*in))
copy(*out, *in)
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Domain.
func (d *Domain) DeepCopy() *Domain {
if d == nil {
return nil
}
out := new(Domain)
d.DeepCopyInto(out)
return out
}

View File

@ -1,13 +1,13 @@
# traefik-certs-dumper # traefik-certs-dumper
[![GitHub release](https://img.shields.io/github/release/ldez/traefik-certs-dumper.svg)](https://github.com/ldez/traefik-certs-dumper/releases/latest) [![GitHub release](https://img.shields.io/github/release/ldez/traefik-certs-dumper.svg)](https://github.com/ldez/traefik-certs-dumper/releases/latest)
[![Build Status](https://github.com/ldez/traefik-certs-dumper/workflows/Main/badge.svg?branch=master)](https://github.com/ldez/traefik-certs-dumper/actions) [![Build Status](https://travis-ci.org/ldez/traefik-certs-dumper.svg?branch=master)](https://travis-ci.org/ldez/traefik-certs-dumper)
[![Docker Image Version (latest semver)](https://img.shields.io/docker/v/ldez/traefik-certs-dumper)](https://hub.docker.com/r/ldez/traefik-certs-dumper/) [![Docker Information](https://images.microbadger.com/badges/image/ldez/traefik-certs-dumper.svg)](https://hub.docker.com/r/ldez/traefik-certs-dumper/)
[![Go Report Card](https://goreportcard.com/badge/github.com/ldez/traefik-certs-dumper)](https://goreportcard.com/report/github.com/ldez/traefik-certs-dumper) [![Go Report Card](https://goreportcard.com/badge/github.com/ldez/traefik-certs-dumper)](https://goreportcard.com/report/github.com/ldez/traefik-certs-dumper)
If you appreciate this project: If you appreciate this project:
[![Sponsor](https://img.shields.io/badge/Sponsor%20me-%E2%9D%A4%EF%B8%8F-pink)](https://github.com/sponsors/ldez) [![Say Thanks!](https://img.shields.io/badge/Say%20Thanks-!-1EAEDB.svg?style=for-the-badge)](https://saythanks.io/to/ldez)
## Features ## Features
@ -18,17 +18,16 @@ If you appreciate this project:
- from file ("acme.json") - from file ("acme.json")
- from KV stores (Consul, Etcd, Zookeeper) - from KV stores (Consul, Etcd, Zookeeper)
- Output formats: - Output formats:
- use domain as subdirectory (allow custom names and extensions) - use domain as sub-directory (allow custom names and extensions)
- flat (domain as filename) - flat (domain as filename)
- Hook (only with watch mode and if the data source changes) - Hook (only with watch mode and if the data source changes)
- Support Traefik v1, v2, and v3.
## Installation ## Installation
### Download / CI Integration ### Download / CI Integration
```bash ```bash
curl -sfL https://raw.githubusercontent.com/ldez/traefik-certs-dumper/master/godownloader.sh | bash -s -- -b $(go env GOPATH)/bin v2.9.3 curl -sfL https://raw.githubusercontent.com/ldez/traefik-certs-dumper/master/godownloader.sh | bash -s -- -b $GOPATH/bin v1.5.0
``` ```
<!-- <!--
@ -57,12 +56,6 @@ You can use pre-compiled binaries:
docker run ldez/traefik-certs-dumper:<tag_name> docker run ldez/traefik-certs-dumper:<tag_name>
``` ```
Examples:
- Traefik v1: [docker-compose](docs/docker-compose-traefik-v1.yml)
- Traefik v2: [docker-compose](docs/docker-compose-traefik-v2.yml)
- Traefik v3: TODO
## Usage ## Usage
- [traefik-certs-dumper](docs/traefik-certs-dumper.md) - [traefik-certs-dumper](docs/traefik-certs-dumper.md)
@ -74,7 +67,7 @@ Examples:
### Simple Dump ### Simple Dump
```console ```console
$ traefik-certs-dumper file --version v3 $ traefik-certs-dumper file
dump dump
├──certs ├──certs
│ └──my.domain.com.key │ └──my.domain.com.key
@ -86,7 +79,7 @@ dump
### Change source and destination ### Change source and destination
```console ```console
$ traefik-certs-dumper file --version v3 --source ./acme.json --dest ./dump/test $ traefik-certs-dumper file --source ./acme.json --dest ./dump/test
test test
├──certs ├──certs
│ └──my.domain.com.key │ └──my.domain.com.key
@ -98,7 +91,7 @@ test
### Use domain as sub-directory ### Use domain as sub-directory
```console ```console
$ traefik-certs-dumper file --version v3 --domain-subdir=true $ traefik-certs-dumper file --domain-subdir=true
dump dump
├──my.domain.com ├──my.domain.com
│ ├──certificate.crt │ ├──certificate.crt
@ -110,7 +103,7 @@ dump
#### Change file extension #### Change file extension
```console ```console
$ traefik-certs-dumper file --version v3 --domain-subdir --crt-ext=.pem --key-ext=.pem $ traefik-certs-dumper file --domain-subdir --crt-ext=.pem --key-ext=.pem
dump dump
├──my.domain.com ├──my.domain.com
│ ├──certificate.pem │ ├──certificate.pem
@ -122,7 +115,7 @@ dump
#### Change file name #### Change file name
```console ```console
$ traefik-certs-dumper file --version v3 --domain-subdir --crt-name=fullchain --key-name=privkey $ traefik-certs-dumper file --domain-subdir --crt-name=fullchain --key-name=privkey
dump dump
├──my.domain.com ├──my.domain.com
│ ├──fullchain.crt │ ├──fullchain.crt
@ -131,37 +124,6 @@ dump
└──letsencrypt.key └──letsencrypt.key
``` ```
## Hook
Hook can be a one-liner passed as a string, or a file for more complex post-hook scenarios.
For the former, create a file (ex: `hook.sh`) and mount it, then pass `sh hooksh` as a parameter to `--post-hook`.
Here is a docker-compose example:
```yml
services:
# ...
traefik-certs-dumper:
image: ldez/traefik-certs-dumper:v2.9.3
container_name: traefik-certs-dumper
entrypoint: sh -c '
while ! [ -e /data/acme.json ]
|| ! [ `jq ".[] | .Certificates | length" /data/acme.json | jq -s "add" ` != 0 ]; do
sleep 1
; done
&& traefik-certs-dumper file --version v2 --watch
--source /data/acme.json --dest /data/certs
--post-hook "sh /hook.sh"'
labels:
traefik.enable: false
volumes:
- ./letsencrypt:/data
- ./hook.sh:/hook.sh
# ...
```
### KV store ### KV store
#### Consul #### Consul
@ -187,3 +149,5 @@ $ traefik-certs-dumper kv boltdb --endpoints /the/path/to/mydb.db
```console ```console
$ traefik-certs-dumper kv zookeeper --endpoints localhost:2181 $ traefik-certs-dumper kv zookeeper --endpoints localhost:2181
``` ```