diff --git a/.dockerignore b/.dockerignore index 3548355e..156fe02d 100644 --- a/.dockerignore +++ b/.dockerignore @@ -3,7 +3,6 @@ docker-gen dist examples -LICENSE Makefile README.md templates diff --git a/.github/dependabot.yml b/.github/dependabot.yml index d8f468d9..10dc3c3b 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -1,14 +1,25 @@ version: 2 updates: - # Maintain dependencies for Go modules - package-ecosystem: "gomod" directory: "/" schedule: interval: "daily" + commit-message: + prefix: "build" # Maintain dependencies for Docker - package-ecosystem: "docker" directory: "/" schedule: interval: "daily" + commit-message: + prefix: "build" + + # Maintain GitHub Actions + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + commit-message: + prefix: "ci" diff --git a/.github/workflows/assets.yml b/.github/workflows/assets.yml new file mode 100644 index 00000000..6d60f2d5 --- /dev/null +++ b/.github/workflows/assets.yml @@ -0,0 +1,33 @@ +name: Release assets + +on: + push: + tags: + - "*.*.*" + +jobs: + assets: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + + - name: Build release assets + run: make release + + - name: Upload release assets + uses: alexellis/upload-assets@0.4.1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + asset_paths: '["./docker-gen-*.tar.gz"]' + + - name: Cleanup release assets + run: make dist-clean diff --git a/.github/workflows/build-publish-dispatch.yml b/.github/workflows/build-publish-dispatch.yml new file mode 100644 index 00000000..686163a1 --- /dev/null +++ b/.github/workflows/build-publish-dispatch.yml @@ -0,0 +1,79 @@ +name: Build and publish Docker images on demand + +on: + workflow_dispatch: + inputs: + image_tag: + description: "Image tag" + type: string + required: true + +jobs: + multiarch-build: + name: Build and publish ${{ matrix.base }} image with tag ${{ inputs.image_tag }} + strategy: + matrix: + base: [alpine, debian] + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Retrieve version + id: docker-gen_version + run: echo "VERSION=$(git describe --tags)" >> "$GITHUB_OUTPUT" + + - name: Get Docker tags + id: docker_meta + uses: docker/metadata-action@v5 + with: + images: | + nginxproxy/docker-gen + tags: | + type=raw,value=${{ inputs.image_tag }},enable=${{ matrix.base == 'alpine' }} + type=raw,value=${{ inputs.image_tag }},suffix=-debian,enable=${{ matrix.base == 'debian' }} + labels: | + org.opencontainers.image.authors=Nicolas Duchon (@buchdag), Jason Wilder + org.opencontainers.image.version=${{ steps.docker-gen_version.outputs.VERSION }} + flavor: | + latest=false + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to DockerHub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push the image + id: docker_build + uses: docker/build-push-action@v6 + with: + context: . + build-args: DOCKER_GEN_VERSION=${{ steps.docker-gen_version.outputs.VERSION }} + platforms: linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/ppc64le,linux/s390x + file: Dockerfile.${{ matrix.base }} + sbom: true + push: true + provenance: mode=max + tags: ${{ steps.docker_meta.outputs.tags }} + labels: ${{ steps.docker_meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Docker image digest + run: echo ${{ steps.docker_build.outputs.digest }} diff --git a/.github/workflows/build-publish.yml b/.github/workflows/build-publish.yml new file mode 100644 index 00000000..90cad8d6 --- /dev/null +++ b/.github/workflows/build-publish.yml @@ -0,0 +1,96 @@ +name: Build and publish Docker images + +on: + workflow_dispatch: + schedule: + - cron: "0 0 * * 1" + push: + branches: + - main + tags: + - "*.*.*" + paths: + - ".dockerignore" + - ".github/workflows/build-publish.yml" + - "Dockerfile.alpine" + - "Dockerfile.debian" + - "go.mod" + - "go.sum" + - "**.go" + +jobs: + multiarch-build: + name: Build and publish image + strategy: + matrix: + base: [alpine, debian] + runs-on: ubuntu-latest + if: (github.event_name == 'schedule' && github.repository == 'nginx-proxy/docker-gen') || (github.event_name != 'schedule') + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Retrieve version + id: docker-gen_version + run: echo "VERSION=$(git describe --tags)" >> "$GITHUB_OUTPUT" + + - name: Get Docker tags + id: docker_meta + uses: docker/metadata-action@v5 + with: + images: | + ghcr.io/nginx-proxy/docker-gen + nginxproxy/docker-gen + jwilder/docker-gen + tags: | + type=semver,pattern={{version}},enable=${{ matrix.base == 'alpine' }} + type=semver,pattern={{major}}.{{minor}},enable=${{ matrix.base == 'alpine' }} + type=semver,suffix=-debian,pattern={{version}},enable=${{ matrix.base == 'debian' }} + type=semver,suffix=-debian,pattern={{major}}.{{minor}},enable=${{ matrix.base == 'debian' }} + type=raw,value=latest,enable=${{ github.ref == 'refs/heads/main' && matrix.base == 'alpine' }} + type=raw,value=debian,enable=${{ github.ref == 'refs/heads/main' && matrix.base == 'debian' }} + labels: | + org.opencontainers.image.authors=Nicolas Duchon (@buchdag), Jason Wilder + org.opencontainers.image.version=${{ steps.docker-gen_version.outputs.VERSION }} + flavor: | + latest=false + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to DockerHub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Build and push the image + id: docker_build + uses: docker/build-push-action@v6 + with: + context: . + build-args: DOCKER_GEN_VERSION=${{ steps.docker-gen_version.outputs.VERSION }} + platforms: linux/386,linux/amd64,linux/arm/v6,linux/arm/v7,linux/arm64,linux/ppc64le,linux/s390x + file: Dockerfile.${{ matrix.base }} + sbom: true + push: true + provenance: mode=max + tags: ${{ steps.docker_meta.outputs.tags }} + labels: ${{ steps.docker_meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + + - name: Docker image digest + run: echo ${{ steps.docker_build.outputs.digest }} diff --git a/.github/workflows/dockerhub-description.yml b/.github/workflows/dockerhub-description.yml new file mode 100644 index 00000000..a983869d --- /dev/null +++ b/.github/workflows/dockerhub-description.yml @@ -0,0 +1,27 @@ +name: Update Docker Hub Description + +on: + push: + branches: + - main + paths: + - README.md + - .github/workflows/dockerhub-description.yml + +jobs: + dockerHubDescription: + name: Update Docker Hub Description + runs-on: ubuntu-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Docker Hub Description + uses: peter-evans/dockerhub-description@v4 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN_RWD }} + repository: nginxproxy/docker-gen + short-description: ${{ github.event.repository.description }} + enable-url-completion: true diff --git a/.github/workflows/dockerhub.yml b/.github/workflows/dockerhub.yml deleted file mode 100644 index 225f6d22..00000000 --- a/.github/workflows/dockerhub.yml +++ /dev/null @@ -1,104 +0,0 @@ -name: DockerHub - -on: - workflow_dispatch: - schedule: - - cron: '0 0 * * 1' - push: - branches: - - main - tags: - - '*.*.*' - paths: - - '.dockerignore' - - '.github/workflows/dockerhub.yml' - - 'Dockerfile' - - 'go.mod' - - 'go.sum' - - '**.go' - -jobs: - multiarch-build: - strategy: - matrix: - base: [alpine, debian] - runs-on: ubuntu-latest - - steps: - - name: Checkout - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - - name: Get Docker tags for Alpine based image - if: ${{ matrix.base == 'alpine' }} - id: docker_meta_alpine - uses: crazy-max/ghaction-docker-meta@v2 - with: - images: | - nginxproxy/docker-gen - jwilder/docker-gen - tags: | - type=semver,pattern={{version}} - type=semver,pattern={{major}}.{{minor}} - type=raw,value=latest,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }} - - - name: Get Docker tags for Debian based image - if: ${{ matrix.base == 'debian' }} - id: docker_meta_debian - uses: crazy-max/ghaction-docker-meta@v2 - with: - images: | - nginxproxy/docker-gen - jwilder/docker-gen - tags: | - type=semver,suffix=-debian,pattern={{version}} - type=semver,suffix=-debian,pattern={{major}}.{{minor}} - type=raw,value=debian,enable=${{ endsWith(github.ref, github.event.repository.default_branch) }} - flavor: latest=false - - - name: Retrieve version - run: echo "VERSION=$(git describe --tags)" >> $GITHUB_ENV - - - name: Set up QEMU - uses: docker/setup-qemu-action@v1 - - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v1 - - - name: Login to DockerHub - uses: docker/login-action@v1 - with: - username: ${{ secrets.DOCKERHUB_USERNAME }} - password: ${{ secrets.DOCKERHUB_TOKEN }} - - - name: Build and push the Alpine based image - if: ${{ matrix.base == 'alpine' }} - id: docker_build_alpine - uses: docker/build-push-action@v2 - with: - build-args: VERSION=${{ env.VERSION }} - platforms: linux/amd64,linux/arm64,linux/arm/v7 - push: true - tags: ${{ steps.docker_meta_alpine.outputs.tags }} - labels: ${{ steps.docker_meta_alpine.outputs.labels }} - - - name: Build and push the Debian based image - if: ${{ matrix.base == 'debian' }} - id: docker_build_debian - uses: docker/build-push-action@v2 - with: - build-args: VERSION=${{ env.VERSION }} - file: Dockerfile.debian - platforms: linux/amd64,linux/arm64,linux/arm/v7 - push: true - tags: ${{ steps.docker_meta_debian.outputs.tags }} - labels: ${{ steps.docker_meta_debian.outputs.labels }} - - - name: Alpine based image digest - if: ${{ matrix.base == 'alpine' }} - run: echo ${{ steps.docker_build_alpine.outputs.digest }} - - - name: Debian based image digest - if: ${{ matrix.base == 'debian' }} - run: echo ${{ steps.docker_build_debian.outputs.digest }} \ No newline at end of file diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 3a726ec4..b9ca177e 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -2,25 +2,39 @@ name: Tests on: push: - paths-ignore: - - 'LICENSE' - - '**.md' - - 'examples/*' - - 'templates/*' + branches: + - main pull_request: paths-ignore: - - 'LICENSE' - - '**.md' - - 'examples/*' - - 'templates/*' + - "LICENSE" + - "**.md" + - "examples/*" + - "templates/*" jobs: unit: - name: Unit Tests - runs-on: ubuntu-latest + strategy: + matrix: + go-version: + - "1.23" + - "1.24" + os: + - macos + - ubuntu + - windows + fail-fast: false + + name: Unit Tests (${{ matrix.os }}/go-${{ matrix.go-version }}) + runs-on: ${{ matrix.os }}-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: actions/setup-go@v5 + with: + go-version: ${{ matrix.go-version }} - name: Install dependencies run: make get-deps @@ -29,7 +43,8 @@ jobs: run: make docker-gen - name: Check code formatting + if: runner.os != 'Windows' run: make check-gofmt - name: Run tests - run: go test -v ./internal/... + run: make test diff --git a/Dockerfile b/Dockerfile deleted file mode 100644 index a06b5b1f..00000000 --- a/Dockerfile +++ /dev/null @@ -1,27 +0,0 @@ -# Build docker-gen from scratch -FROM golang:1.17.5-alpine as go-builder - -ARG VERSION=main - -WORKDIR /build - -# Install the dependencies -COPY . . -RUN go mod download - -# Build the docker-gen executable -RUN GOOS=linux CGO_ENABLED=0 go build -ldflags "-X main.buildVersion=${VERSION}" -o docker-gen ./cmd/docker-gen - -FROM alpine:3.13 - -LABEL maintainer="Jason Wilder " - -ENV DOCKER_HOST unix:///tmp/docker.sock - -# Install packages required by the image -RUN apk add --no-cache --virtual .bin-deps openssl - -# Install docker-gen from build stage -COPY --from=go-builder /build/docker-gen /usr/local/bin/docker-gen - -ENTRYPOINT ["/usr/local/bin/docker-gen"] \ No newline at end of file diff --git a/Dockerfile.alpine b/Dockerfile.alpine new file mode 100644 index 00000000..6c492165 --- /dev/null +++ b/Dockerfile.alpine @@ -0,0 +1,45 @@ +ARG DOCKER_GEN_VERSION=main + +# Build docker-gen from scratch +FROM --platform=$BUILDPLATFORM golang:1.24.5-alpine AS go-builder + +ENV CGO_ENABLED=0 + +ARG DOCKER_GEN_VERSION TARGETOS TARGETARCH TARGETVARIANT +ENV GOOS=$TARGETOS GOARCH=$TARGETARCH VARIANT=$TARGETVARIANT + +WORKDIR /build + +# Install the dependencies +COPY . . +RUN go mod download + +# Build the docker-gen executable +RUN set -eux; \ + case "$GOARCH" in \ + arm) export GOARM="${VARIANT#v}" ;; \ + amd64) export GOAMD64="$VARIANT" ;; \ + *) [ -z "$VARIANT" ] ;; \ + esac; \ + go env | grep -E 'OS=|ARCH=|ARM=|AMD64='; \ + go build -ldflags "-X main.buildVersion=${DOCKER_GEN_VERSION}" -o docker-gen ./cmd/docker-gen + +FROM alpine:3.22.1 + +ARG DOCKER_GEN_VERSION +ENV DOCKER_GEN_VERSION=${DOCKER_GEN_VERSION} \ + DOCKER_HOST=unix:///tmp/docker.sock + +# Install packages required by the image +RUN apk add --no-cache --virtual .bin-deps openssl + +# Copy the entrypoint script +COPY /app/docker-entrypoint.sh /app/docker-entrypoint.sh + +# Install docker-gen from build stage +COPY --from=go-builder /build/docker-gen /usr/local/bin/docker-gen + +# Copy the license +COPY LICENSE /usr/local/share/doc/docker-gen/ + +ENTRYPOINT ["/app/docker-entrypoint.sh"] diff --git a/Dockerfile.debian b/Dockerfile.debian index 3ecd522b..3173171f 100644 --- a/Dockerfile.debian +++ b/Dockerfile.debian @@ -1,7 +1,12 @@ +ARG DOCKER_GEN_VERSION=main + # Build docker-gen from scratch -FROM golang:1.17.5 as go-builder +FROM --platform=$BUILDPLATFORM golang:1.24.5 AS go-builder + +ENV CGO_ENABLED=0 -ARG VERSION=main +ARG DOCKER_GEN_VERSION TARGETOS TARGETARCH TARGETVARIANT +ENV GOOS=$TARGETOS GOARCH=$TARGETARCH VARIANT=$TARGETVARIANT WORKDIR /build @@ -10,13 +15,20 @@ COPY . . RUN go mod download # Build the docker-gen executable -RUN GOOS=linux go build -ldflags "-X main.buildVersion=${VERSION}" -o docker-gen ./cmd/docker-gen - -FROM debian:11-slim +RUN set -eux; \ + case "$GOARCH" in \ + arm) export GOARM="${VARIANT#v}" ;; \ + amd64) export GOAMD64="$VARIANT" ;; \ + *) [ -z "$VARIANT" ] ;; \ + esac; \ + go env | grep -E 'OS=|ARCH=|ARM=|AMD64='; \ + go build -ldflags "-X main.buildVersion=${DOCKER_GEN_VERSION}" -o docker-gen ./cmd/docker-gen -LABEL maintainer="Jason Wilder " +FROM debian:12.11-slim -ENV DOCKER_HOST unix:///tmp/docker.sock +ARG DOCKER_GEN_VERSION +ENV DOCKER_GEN_VERSION=${DOCKER_GEN_VERSION} \ + DOCKER_HOST=unix:///tmp/docker.sock # Install packages required by the image RUN apt-get update \ @@ -24,7 +36,13 @@ RUN apt-get update \ && apt-get clean \ && rm -r /var/lib/apt/lists/* +# Copy the entrypoint script +COPY /app/docker-entrypoint.sh /app/docker-entrypoint.sh + # Install docker-gen from build stage COPY --from=go-builder /build/docker-gen /usr/local/bin/docker-gen -ENTRYPOINT ["/usr/local/bin/docker-gen"] \ No newline at end of file +# Copy the license +COPY LICENSE /usr/local/share/doc/docker-gen/ + +ENTRYPOINT ["/app/docker-entrypoint.sh"] diff --git a/LICENSE b/LICENSE index 3d15abf4..f697bed0 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,7 @@ The MIT License (MIT) -Copyright (c) 2014 Jason Wilder +Copyright (c) 2014-2021 Jason Wilder +Copyright (c) 2021-2022 Nicolas Duchon Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Makefile b/Makefile index 307d94c9..f130219d 100644 --- a/Makefile +++ b/Makefile @@ -15,6 +15,7 @@ dist-clean: rm -f docker-gen-alpine-linux-*.tar.gz rm -f docker-gen-linux-*.tar.gz rm -f docker-gen-darwin-*.tar.gz + rm -f docker-gen-windows-*.tar.gz dist: dist-clean mkdir -p dist/alpine-linux/amd64 && GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -ldflags "$(LDFLAGS)" -a -tags netgo -installsuffix netgo -o dist/alpine-linux/amd64/docker-gen ./cmd/docker-gen @@ -26,7 +27,9 @@ dist: dist-clean mkdir -p dist/linux/armel && GOOS=linux GOARCH=arm GOARM=5 go build -ldflags "$(LDFLAGS)" -o dist/linux/armel/docker-gen ./cmd/docker-gen mkdir -p dist/linux/armhf && GOOS=linux GOARCH=arm GOARM=6 go build -ldflags "$(LDFLAGS)" -o dist/linux/armhf/docker-gen ./cmd/docker-gen mkdir -p dist/darwin/amd64 && GOOS=darwin GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o dist/darwin/amd64/docker-gen ./cmd/docker-gen - + mkdir -p dist/darwin/arm64 && GOOS=darwin GOARCH=arm64 go build -ldflags "$(LDFLAGS)" -o dist/darwin/arm64/docker-gen ./cmd/docker-gen + mkdir -p dist/windows/i386 && GOOS=windows GOARCH=386 go build -ldflags "$(LDFLAGS)" -o dist/windows/i386/docker-gen ./cmd/docker-gen + mkdir -p dist/windows/amd64 && GOOS=windows GOARCH=amd64 go build -ldflags "$(LDFLAGS)" -o dist/windows/amd64/docker-gen ./cmd/docker-gen release: dist go mod tidy @@ -39,6 +42,9 @@ release: dist tar -cvzf docker-gen-linux-armel-$(TAG).tar.gz -C dist/linux/armel docker-gen tar -cvzf docker-gen-linux-armhf-$(TAG).tar.gz -C dist/linux/armhf docker-gen tar -cvzf docker-gen-darwin-amd64-$(TAG).tar.gz -C dist/darwin/amd64 docker-gen + tar -cvzf docker-gen-darwin-arm64-$(TAG).tar.gz -C dist/darwin/arm64 docker-gen + tar -cvzf docker-gen-windows-amd64-$(TAG).tar.gz -C dist/windows/amd64 docker-gen + tar -cvzf docker-gen-windows-i386-$(TAG).tar.gz -C dist/windows/i386 docker-gen get-deps: go mod download @@ -51,9 +57,9 @@ check-gofmt: fi if [ -n "$(shell go fmt ./internal/...)" ]; then \ echo 1>&2 'The following files need to be formatted:'; \ - gofmt -l ./internal/dockergen; \ + gofmt -l ./internal; \ exit 1; \ fi test: - go test ./internal/... + go test -v ./internal/... diff --git a/README.md b/README.md index cfecfa1c..14e2c926 100644 --- a/README.md +++ b/README.md @@ -1,45 +1,38 @@ -docker-gen -===== - +# docker-gen [![Tests](https://github.com/nginx-proxy/docker-gen/actions/workflows/tests.yml/badge.svg)](https://github.com/nginx-proxy/docker-gen/actions/workflows/tests.yml) [![GitHub release](https://img.shields.io/github/v/release/nginx-proxy/docker-gen)](https://github.com/nginx-proxy/docker-gen/releases) [![Docker Image Size](https://img.shields.io/docker/image-size/nginxproxy/docker-gen?sort=semver)](https://hub.docker.com/r/nginxproxy/docker-gen "Click to view the image on Docker Hub") -[![Docker stars](https://img.shields.io/docker/stars/nginxproxy/docker-gen.svg)](https://hub.docker.com/r/nginxproxy/docker-gen 'DockerHub') -[![Docker pulls](https://img.shields.io/docker/pulls/nginxproxy/docker-gen.svg)](https://hub.docker.com/r/nginxproxy/docker-gen 'DockerHub') +[![Docker stars](https://img.shields.io/docker/stars/nginxproxy/docker-gen.svg)](https://hub.docker.com/r/nginxproxy/docker-gen "DockerHub") +[![Docker pulls](https://img.shields.io/docker/pulls/nginxproxy/docker-gen.svg)](https://hub.docker.com/r/nginxproxy/docker-gen "DockerHub") `docker-gen` is a file generator that renders templates using docker container meta-data. It can be used to generate various kinds of files for: - * **Centralized logging** - [fluentd](https://github.com/nginx-proxy/docker-gen/blob/main/templates/fluentd.conf.tmpl), logstash or other centralized logging tools that tail the containers JSON log file or files within the container. - * **Log Rotation** - [logrotate](https://github.com/nginx-proxy/docker-gen/blob/main/templates/logrotate.tmpl) files to rotate container JSON log files - * **Reverse Proxy Configs** - [nginx](https://github.com/nginx-proxy/docker-gen/blob/main/templates/nginx.tmpl), [haproxy](https://github.com/jwilder/docker-discover), etc. reverse proxy configs to route requests from the host to containers - * **Service Discovery** - Scripts (python, bash, etc..) to register containers within [etcd](https://github.com/jwilder/docker-register), hipache, etc.. +- **Centralized logging** - [fluentd](https://github.com/nginx-proxy/docker-gen/blob/main/templates/fluentd.conf.tmpl), logstash or other centralized logging tools that tail the containers JSON log file or files within the container. +- **Log Rotation** - [logrotate](https://github.com/nginx-proxy/docker-gen/blob/main/templates/logrotate.tmpl) files to rotate container JSON log files +- **Reverse Proxy Configs** - [nginx](https://github.com/nginx-proxy/docker-gen/blob/main/templates/nginx.tmpl), [haproxy](https://github.com/jwilder/docker-discover), etc. reverse proxy configs to route requests from the host to containers +- **Service Discovery** - Scripts (python, bash, etc..) to register containers within [etcd](https://github.com/jwilder/docker-register), hipache, etc.. -=== +--- ### Installation There are three common ways to run docker-gen: -* on the host -* bundled in a container with another application -* separate standalone containers - -#### Host Install -Linux/OSX binaries for release [0.7.6](https://github.com/nginx-proxy/docker-gen/releases) +- on the host +- bundled in a container with another application +- separate standalone containers -* [amd64](https://github.com/nginx-proxy/docker-gen/releases/download/0.7.6/docker-gen-linux-amd64-0.7.6.tar.gz) -* [i386](https://github.com/nginx-proxy/docker-gen/releases/download/0.7.6/docker-gen-linux-i386-0.7.6.tar.gz) -* [alpine-linux](https://github.com/nginx-proxy/docker-gen/releases/download/0.7.6/docker-gen-alpine-linux-amd64-0.7.6.tar.gz) +#### Host Install Download the version you need, untar, and install to your PATH. -``` -$ wget https://github.com/nginx-proxy/docker-gen/releases/download/0.7.6/docker-gen-linux-amd64-0.7.6.tar.gz -$ tar xvzf docker-gen-linux-amd64-0.7.6.tar.gz -$ ./docker-gen +```console +wget https://github.com/nginx-proxy/docker-gen/releases/download/0.12.0/docker-gen-linux-amd64-0.12.0.tar.gz +tar xvzf docker-gen-linux-amd64-0.12.0.tar.gz +./docker-gen ``` #### Bundled Container Install @@ -62,23 +55,31 @@ this to prevent having the docker socket bound to a publicly exposed container s Start nginx with a shared volume: -``` -$ docker run -d -p 80:80 --name nginx -v /tmp/nginx:/etc/nginx/conf.d -t nginx +```console +docker run -d -p 80:80 --name nginx -v /tmp/nginx:/etc/nginx/conf.d -t nginx ``` Fetch the template and start the docker-gen container with the shared volume: -``` -$ mkdir -p /tmp/templates && cd /tmp/templates -$ curl -o nginx.tmpl https://raw.githubusercontent.com/nginx-proxy/docker-gen/main/templates/nginx.tmpl -$ docker run -d --name nginx-gen --volumes-from nginx \ + +```console +mkdir -p /tmp/templates && cd /tmp/templates +curl -o nginx.tmpl https://raw.githubusercontent.com/nginx-proxy/docker-gen/main/templates/nginx.tmpl +docker run -d --name nginx-gen --volumes-from nginx \ -v /var/run/docker.sock:/tmp/docker.sock:rw \ -v /tmp/templates:/etc/docker-gen/templates \ -t nginxproxy/docker-gen -notify-sighup nginx -watch -only-exposed /etc/docker-gen/templates/nginx.tmpl /etc/nginx/conf.d/default.conf ``` -=== +Start a container, taking note of any Environment variables a container expects. See the top of a template for details. + +```console +docker run --env VIRTUAL_HOST='example.com' --env VIRTUAL_PORT=80 ... +``` + +--- ### Usage + ``` $ docker-gen Usage: docker-gen [options] template [dest] @@ -90,6 +91,11 @@ Options: config files with template directives. Config files will be merged if this option is specified multiple times. (default []) -endpoint string docker api endpoint (tcp|unix://..). Default unix:///var/run/docker.sock + -event-filter value + additional filter for event watched by docker-gen (e.g -event-filter event=connect -event-filter event=disconnect). + You can pass this option multiple times to combine filters. + By default docker-gen listen for container events start, stop, die and health_status. + https://docs.docker.com/engine/reference/commandline/events/#filtering-events -interval int notify command interval (secs) -keep-blank-lines @@ -98,13 +104,21 @@ Options: run command after template is regenerated (e.g restart xyz) -notify-output log the output(stdout/stderr) of notify command + -notify-sighup container-ID + send HUP signal to container. + Equivalent to 'docker kill -s HUP container-ID', or `-notify-container container-ID -notify-signal 1`. + You can pass this option multiple times to send HUP to multiple containers. -notify-container container-ID - container to send a signal to + send -notify-signal signal (defaults to 1 / HUP) to container. + You can pass this option multiple times to notify multiple containers. + -notify-filter key=value + container filter for notification (e.g -notify-filter name=foo). + You can pass this option multiple times to combine filters with AND. + https://docs.docker.com/engine/reference/commandline/ps/#filter -notify-signal signal - signal to send to the -notify-container. -1 to call docker restart. Defaults to 1 aka. HUP. - All available signals available on the [dockerclient](https://github.com/fsouza/go-dockerclient/blob/01804dec8a84d0a77e63611f2b62d33e9bb2b64a/signal.go) - -notify-sighup container-ID - send HUP signal to container. Equivalent to 'docker kill -s HUP container-ID', or `-notify-container container-ID -notify-signal 1` + signal to send to the -notify-container and -notify-filter. -1 to call docker restart. Defaults to 1 aka. HUP. + All available signals available on the dockerclient + https://github.com/fsouza/go-dockerclient/blob/main/signal.go -only-exposed only include containers with exposed ports -only-published @@ -112,11 +126,11 @@ Options: -include-stopped include stopped containers -tlscacert string - path to TLS CA certificate file (default "/Users/jason/.docker/machine/machines/default/ca.pem") + path to TLS CA certificate file (default "~/.docker/machine/machines/default/ca.pem") -tlscert string - path to TLS client certificate file (default "/Users/jason/.docker/machine/machines/default/cert.pem") + path to TLS client certificate file (default "~/.docker/machine/machines/default/cert.pem") -tlskey string - path to TLS client key file (default "/Users/jason/.docker/machine/machines/default/key.pem") + path to TLS client key file (default "~/.docker/machine/machines/default/key.pem") -tlsverify verify docker daemon's TLS certicate (default true) -version @@ -138,7 +152,6 @@ Environment Variables: If no `` file is specified, the output is sent to stdout. Mainly useful for debugging. - ### Configuration file Using the -config flag from above you can tell docker-gen to use the specified config file instead of command-line options. Multiple templates can be defined and they will be executed in the order that they appear in the config file. @@ -146,40 +159,43 @@ Using the -config flag from above you can tell docker-gen to use the specified c An example configuration file, **docker-gen.cfg** can be found in the examples folder. #### Configuration File Syntax -``` + +```ini [[config]] -Starts a configuration section +# Starts a configuration section dest = "path/to/a/file" -path to write the template. If not specfied, STDOUT is used +# path to write the template. If not specfied, STDOUT is used notifycmd = "/etc/init.d/foo reload" -run command after template is regenerated (e.g restart xyz) +# run command after template is regenerated (e.g restart xyz) onlyexposed = true -only include containers with exposed ports +# only include containers with exposed ports template = "/path/to/a/template/file.tmpl" -path to a template to generate +# path to a template to generate watch = true -watch for container changes +# watch for container changes wait = "500ms:2s" -debounce changes with a min:max duration. Only applicable if watch = true +# debounce changes with a min:max duration. Only applicable if watch = true [config.NotifyContainers] -Starts a notify container section +# Starts a notify container section containername = 1 -container name followed by the signal to send +# container name followed by the signal to send container_id = 1 -or the container id can be used followed by the signal to send +# or the container id can be used followed by the signal to send ``` + Putting it all together here is an example configuration file. -``` + +```ini [[config]] template = "/etc/nginx/nginx.conf.tmpl" dest = "/etc/nginx/sites-available/default" @@ -202,11 +218,21 @@ nginx = 1 # 1 is a signal number to be sent; here SIGHUP e75a60548dc9 = 1 # a key can be either container name (nginx) or ID ``` -=== +--- ### Templating -The templates used by docker-gen are written using the Go [text/template](http://golang.org/pkg/text/template/) language. In addition to the [built-in functions](http://golang.org/pkg/text/template/#hdr-Functions) supplied by Go, docker-gen provides a number of additional functions to make it simpler (or possible) to generate your desired output. +The templates used by docker-gen are written using the Go [text/template](http://golang.org/pkg/text/template/) language. In addition to the [built-in functions](http://golang.org/pkg/text/template/#hdr-Functions) supplied by Go, docker-gen uses [sprig](https://masterminds.github.io/sprig/) and some additional functions to make it simpler (or possible) to generate your desired output. Some templates rely on environment variables within the container to make decisions on what to generate from the template. + +Several templates may be parsed at once by using a semicolon (`;`) to delimit the `template` value. This can be used as a proxy for Golang's nested template functionality. In all cases, the main rendered template should go first. + +``` +[[config]] +template = "/etc/docker-gen/templates/nginx.tmpl;/etc/docker-gen/templates/header.tmpl" +dest = "/etc/nginx/conf.d/default.conf" +watch = true +wait = "500ms:2s" +``` #### Emit Structure @@ -215,6 +241,7 @@ Within the templates, the object emitted by docker-gen will be a structure consi ```go type RuntimeContainer struct { ID string + Created time.Time Addresses []Address Networks []Network Gateway string @@ -252,6 +279,7 @@ type Network struct { MacAddress string GlobalIPv6PrefixLen int IPPrefixLen int + Internal bool } type DockerImage struct { @@ -282,7 +310,12 @@ type SwarmNode struct { } type State struct { - Running bool + Running bool + Health Health +} + +type Health struct { + Status string } // Accessible from the root in templates as .Docker @@ -299,110 +332,126 @@ type Docker struct { } // Host environment variables accessible from root in templates as .Env - ``` For example, this is a JSON version of an emitted RuntimeContainer struct: ```json { - "ID":"71e9768075836eb38557adcfc71a207386a0c597dbeda240cf905df79b18cebf", - "Addresses":[ - { - "IP":"172.17.0.4", - "Port":"22", - "Proto":"tcp", - "HostIP":"192.168.10.24", - "HostPort":"2222" - } - ], - "Gateway":"172.17.42.1", - "Node": { - "ID":"I2VY:P7PF:TZD5:PGWB:QTI7:QDSP:C5UD:DYKR:XKKK:TRG2:M2BL:DFUN", - "Name":"docker-test", - "Address": { - "IP":"192.168.10.24" - } - }, - "Labels": { - "operatingsystem":"Ubuntu 14.04.2 LTS", - "storagedriver":"devicemapper", - "anything_foo":"something_bar" - }, - "IP":"172.17.0.4", - "Name":"docker_register", - "Hostname":"71e976807583", - "Image":{ - "Registry":"jwilder", - "Repository":"docker-register" - }, - "Env":{ - "ETCD_HOST":"172.17.42.1:4001", - "PATH":"/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", - "DOCKER_HOST":"unix:///var/run/docker.sock", - "HOST_IP":"172.17.42.1" - }, - "Volumes":{ - "/mnt":{ - "Path":"/mnt", - "HostPath":"/Users/joebob/tmp", - "ReadWrite":true - } - } + "ID": "71e9768075836eb38557adcfc71a207386a0c597dbeda240cf905df79b18cebf", + "Addresses": [ + { + "IP": "172.17.0.4", + "Port": "22", + "Proto": "tcp", + "HostIP": "192.168.10.24", + "HostPort": "2222" + } + ], + "Gateway": "172.17.42.1", + "Node": { + "ID": "I2VY:P7PF:TZD5:PGWB:QTI7:QDSP:C5UD:DYKR:XKKK:TRG2:M2BL:DFUN", + "Name": "docker-test", + "Address": { + "IP": "192.168.10.24" + } + }, + "Labels": { + "operatingsystem": "Ubuntu 14.04.2 LTS", + "storagedriver": "devicemapper", + "anything_foo": "something_bar" + }, + "IP": "172.17.0.4", + "Name": "docker_register", + "Hostname": "71e976807583", + "Image": { + "Registry": "jwilder", + "Repository": "docker-register" + }, + "Env": { + "ETCD_HOST": "172.17.42.1:4001", + "PATH": "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "DOCKER_HOST": "unix:///var/run/docker.sock", + "HOST_IP": "172.17.42.1" + }, + "Volumes": { + "/mnt": { + "Path": "/mnt", + "HostPath": "/Users/joebob/tmp", + "ReadWrite": true + } + } } ``` #### Functions -* *`closest $array $value`*: Returns the longest matching substring in `$array` that matches `$value` -* *`coalesce ...`*: Returns the first non-nil argument. -* *`contains $map $key`*: Returns `true` if `$map` contains `$key`. Takes maps from `string` to any type. -* *`dict $key $value ...`*: Creates a map from a list of pairs. Each `$key` value must be a `string`, but the `$value` can be any type (or `nil`). Useful for passing more than one value as a pipeline context to subtemplates. -* *`dir $path`*: Returns an array of filenames in the specified `$path`. -* *`exists $path`*: Returns `true` if `$path` refers to an existing file or directory. Takes a string. -* *`first $array`*: Returns the first value of an array or nil if the arry is nil or empty. -* *`groupBy $containers $fieldPath`*: Groups an array of `RuntimeContainer` instances based on the values of a field path expression `$fieldPath`. A field path expression is a dot-delimited list of map keys or struct member names specifying the path from container to a nested value, which must be a string. Returns a map from the value of the field path expression to an array of containers having that value. Containers that do not have a value for the field path in question are omitted. -* *`groupByKeys $containers $fieldPath`*: Returns the same as `groupBy` but only returns the keys of the map. -* *`groupByMulti $containers $fieldPath $sep`*: Like `groupBy`, but the string value specified by `$fieldPath` is first split by `$sep` into a list of strings. A container whose `$fieldPath` value contains a list of strings will show up in the map output under each of those strings. -* *`groupByLabel $containers $label`*: Returns the same as `groupBy` but grouping by the given label's value. -* *`hasPrefix $prefix $string`*: Returns whether `$prefix` is a prefix of `$string`. -* *`hasSuffix $suffix $string`*: Returns whether `$suffix` is a suffix of `$string`. -* *`intersect $slice1 $slice2`*: Returns the strings that exist in both string slices. -* *`json $value`*: Returns the JSON representation of `$value` as a `string`. -* *`keys $map`*: Returns the keys from `$map`. If `$map` is `nil`, a `nil` is returned. If `$map` is not a `map`, an error will be thrown. -* *`last $array`*: Returns the last value of an array. -* *`parseBool $string`*: parseBool returns the boolean value represented by the string. It accepts 1, t, T, TRUE, true, True, 0, f, F, FALSE, false, False. Any other value returns an error. Alias for [`strconv.ParseBool`](http://golang.org/pkg/strconv/#ParseBool) -* *`replace $string $old $new $count`*: Replaces up to `$count` occurences of `$old` with `$new` in `$string`. Alias for [`strings.Replace`](http://golang.org/pkg/strings/#Replace) -* *`sha1 $string`*: Returns the hexadecimal representation of the SHA1 hash of `$string`. -* *`split $string $sep`*: Splits `$string` into a slice of substrings delimited by `$sep`. Alias for [`strings.Split`](http://golang.org/pkg/strings/#Split) -* *`splitN $string $sep $count`*: Splits `$string` into a slice of substrings delimited by `$sep`, with number of substrings returned determined by `$count`. Alias for [`strings.SplitN`](https://golang.org/pkg/strings/#SplitN) -* *`sortStringsAsc $strings`: Returns a slice of strings `$strings` sorted in ascending order. -* *`sortStringsDesc $strings`: Returns a slice of strings `$strings` sorted in descending (reverse) order. -* *`sortObjectsByKeysAsc $objects $fieldPath`: Returns the array `$objects`, sorted in ascending order based on the values of a field path expression `$fieldPath`. -* *`sortObjectsByKeysDesc $objects $fieldPath`: Returns the array `$objects`, sorted in descending (reverse) order based on the values of a field path expression `$fieldPath`. -* *`trimPrefix $prefix $string`*: If `$prefix` is a prefix of `$string`, return `$string` with `$prefix` trimmed from the beginning. Otherwise, return `$string` unchanged. -* *`trimSuffix $suffix $string`*: If `$suffix` is a suffix of `$string`, return `$string` with `$suffix` trimmed from the end. Otherwise, return `$string` unchanged. -* *`trim $string`*: Removes whitespace from both sides of `$string`. -* *`toLower $string`*: Replace capital letters in `$string` to lowercase. -* *`toUpper $string`*: Replace lowercase letters in `$string` to uppercase. -* *`when $condition $trueValue $falseValue`*: Returns the `$trueValue` when the `$condition` is `true` and the `$falseValue` otherwise -* *`where $items $fieldPath $value`*: Filters an array or slice based on the values of a field path expression `$fieldPath`. A field path expression is a dot-delimited list of map keys or struct member names specifying the path from container to a nested value. Returns an array of items having that value. -* *`whereNot $items $fieldPath $value`*: Filters an array or slice based on the values of a field path expression `$fieldPath`. A field path expression is a dot-delimited list of map keys or struct member names specifying the path from container to a nested value. Returns an array of items **not** having that value. -* *`whereExist $items $fieldPath`*: Like `where`, but returns only items where `$fieldPath` exists (is not nil). -* *`whereNotExist $items $fieldPath`*: Like `where`, but returns only items where `$fieldPath` does not exist (is nil). -* *`whereAny $items $fieldPath $sep $values`*: Like `where`, but the string value specified by `$fieldPath` is first split by `$sep` into a list of strings. The comparison value is a string slice with possible matches. Returns items which OR intersect these values. -* *`whereAll $items $fieldPath $sep $values`*: Like `whereAny`, except all `$values` must exist in the `$fieldPath`. -* *`whereLabelExists $containers $label`*: Filters a slice of containers based on the existence of the label `$label`. -* *`whereLabelDoesNotExist $containers $label`*: Filters a slice of containers based on the non-existence of the label `$label`. -* *`whereLabelValueMatches $containers $label $pattern`*: Filters a slice of containers based on the existence of the label `$label` with values matching the regular expression `$pattern`. - -=== +- [Functions from Go](https://pkg.go.dev/text/template#hdr-Functions) +- [Functions from Sprig v3](https://masterminds.github.io/sprig/), except for those that have the same name as one of the following functions. +- _`closest $array $value`_: Returns the longest matching substring in `$array` that matches `$value` +- _`coalesce ...`_: Returns the first non-nil argument. +- _`comment $delimiter $string`_: Returns `$string` with each line prefixed by `$delimiter` (helpful for debugging combined with Sprig `toPrettyJson`: `{{ toPrettyJson $ | comment "#" }}`). +- _`contains $map $key`_: Returns `true` if `$map` contains `$key`. Takes maps from `string` to any type. +- _`dir $path`_: Returns an array of filenames in the specified `$path`. +- _`exists $path`_: Returns `true` if `$path` refers to an existing file or directory. Takes a string. +- _`eval $templateName [$data]`_: Evaluates the named template like Go's built-in `template` action, but instead of writing out the result it returns the result as a string so that it can be post-processed. The `$data` argument may be omitted, which is equivalent to passing `nil`. +- _`groupBy $containers $fieldPath`_: Groups an array of `RuntimeContainer` instances based on the values of a field path expression `$fieldPath`. A field path expression is a dot-delimited list of map keys or struct member names specifying the path from container to a nested value, which must be a string. Returns a map from the value of the field path expression to an array of containers having that value. Containers that do not have a value for the field path in question are omitted. +- _`groupByWithDefault $containers $fieldPath $defaultValue`_: Returns the same as `groupBy`, but containers that do not have a value for the field path are instead included in the map under the `$defaultValue` key. +- _`groupByKeys $containers $fieldPath`_: Returns the same as `groupBy` but only returns the keys of the map. +- _`groupByMulti $containers $fieldPath $sep`_: Like `groupBy`, but the string value specified by `$fieldPath` is first split by `$sep` into a list of strings. A container whose `$fieldPath` value contains a list of strings will show up in the map output under each of those strings. +- _`groupByLabel $containers $label`_: Returns the same as `groupBy` but grouping by the given label's value. Containers that do not have the `$label` set are omitted. +- _`groupByLabelWithDefault $containers $label $defaultValue`_: Returns the same as `groupBy` but grouping by the given label's value. Containers that do not have the `$label` set are included in the map under the `$defaultValue` key. +- _`include $file`_: Returns content of `$file`, and empty string if file reading error. +- _`intersect $slice1 $slice2`_: Returns the strings that exist in both string slices. +- _`fromYaml $string` / `mustFromYaml $string`_: Similar to [Sprig's `fromJson` / `mustFromJson`](https://github.com/Masterminds/sprig/blob/master/docs/defaults.md#fromjson-mustfromjson), but for YAML. +- _`toYaml $dict` / `mustToYaml $dict`_: Similar to [Sprig's `toJson` / `mustToJson`](https://github.com/Masterminds/sprig/blob/master/docs/defaults.md#tojson-musttojson), but for YAML. +- _`keys $map`_: Returns the keys from `$map`. If `$map` is `nil`, a `nil` is returned. If `$map` is not a `map`, an error will be thrown. +- _`sortStringsAsc $strings`_: Returns a slice of strings `$strings` sorted in ascending order. +- _`sortStringsDesc $strings`_: Returns a slice of strings `$strings` sorted in descending (reverse) order. +- _`sortObjectsByKeysAsc $objects $fieldPath`_: Returns the array `$objects`, sorted in ascending order based on the values of a field path expression `$fieldPath`. +- _`sortObjectsByKeysDesc $objects $fieldPath`_: Returns the array `$objects`, sorted in descending (reverse) order based on the values of a field path expression `$fieldPath`. +- _`when $condition $trueValue $falseValue`_: Returns the `$trueValue` when the `$condition` is `true` and the `$falseValue` otherwise +- _`where $items $fieldPath $value`_: Filters an array or slice based on the values of a field path expression `$fieldPath`. A field path expression is a dot-delimited list of map keys or struct member names specifying the path from container to a nested value. Returns an array of items having that value. +- _`whereNot $items $fieldPath $value`_: Filters an array or slice based on the values of a field path expression `$fieldPath`. A field path expression is a dot-delimited list of map keys or struct member names specifying the path from container to a nested value. Returns an array of items **not** having that value. +- _`whereExist $items $fieldPath`_: Like `where`, but returns only items where `$fieldPath` exists (is not nil). +- _`whereNotExist $items $fieldPath`_: Like `where`, but returns only items where `$fieldPath` does not exist (is nil). +- _`whereAny $items $fieldPath $sep $values`_: Like `where`, but the string value specified by `$fieldPath` is first split by `$sep` into a list of strings. The comparison value is a string slice with possible matches. Returns items which OR intersect these values. +- _`whereAll $items $fieldPath $sep $values`_: Like `whereAny`, except all `$values` must exist in the `$fieldPath`. +- _`whereLabelExists $containers $label`_: Filters a slice of containers based on the existence of the label `$label`. +- _`whereLabelDoesNotExist $containers $label`_: Filters a slice of containers based on the non-existence of the label `$label`. +- _`whereLabelValueMatches $containers $label $pattern`_: Filters a slice of containers based on the existence of the label `$label` with values matching the regular expression `$pattern`. + +Sprig functions that have the same name as docker-gen function (but different behaviour) are made available with the `sprig` prefix: + +- _`sprigCoalesce ...`_: Alias for Sprig's [`coalesce`](https://masterminds.github.io/sprig/defaults.html). +- _`sprigContains $string $string`_: Alias for Sprig's [`contains`](https://masterminds.github.io/sprig/strings.html). +- _`sprigDir $path`_: Alias for Sprig's [`dir`](https://masterminds.github.io/sprig/paths.html). +- _`sprigReplace $old $new $string`_: Alias for Sprig's [`replace`](https://masterminds.github.io/sprig/strings.html). +- _`sprigSplit $sep $string`_: Alias for Sprig's [`split`](https://masterminds.github.io/sprig/string_slice.html). +- _`sprigSplitn $sep $count $string"`_: Alias for Sprig's [`splitn`](https://masterminds.github.io/sprig/string_slice.html). + +Some functions are aliases for Go's [`strings`](https://pkg.go.dev/strings) package functions: + +- _`parseBool $string`_: Alias for [`strconv.ParseBool`](http://golang.org/pkg/strconv/#ParseBool). Returns the boolean value represented by `$string`. It accepts 1, t, T, TRUE, true, True, 0, f, F, FALSE, false, False. Any other value returns an error. +- _`replace $string $old $new $count`_: Alias for [`strings.Replace`](http://golang.org/pkg/strings/#Replace). Replaces up to `$count` occurences of `$old` with `$new` in `$string`. +- _`split $string $sep`_: Alias for [`strings.Split`](http://golang.org/pkg/strings/#Split). Splits `$string` into a slice of substrings delimited by `$sep`. +- _`splitN $string $sep $count`_: Alias for [`strings.SplitN`](https://golang.org/pkg/strings/#SplitN). Splits `$string` into a slice of substrings delimited by `$sep`, with number of substrings returned determined by `$count`. +- _`toLower $string`_: Alias for [`strings.ToLower`](https://pkg.go.dev/strings#ToLower). Replace capital letters in `$string` to lowercase. +- _`toUpper $string`_: Alias for [`strings.ToUpper`](https://pkg.go.dev/strings#ToUpper). Replace lowercase letters in `$string` to uppercase. + +Those have been aliased to Sprig functions with the same behaviour as the original docker-gen function: + +- _`json $value`_: Alias for Sprig's [`mustToJson`](https://masterminds.github.io/sprig/defaults.html) +- _`parseJson $string`_: Alias for Sprig's [`mustFromJson`](https://masterminds.github.io/sprig/defaults.html). +- _`sha1 $string`_: Alias for Sprig's [`sha1sum`](https://masterminds.github.io/sprig/crypto.html). + +--- ### Examples -* [Automated Nginx Reverse Proxy for Docker](http://jasonwilder.com/blog/2014/03/25/automated-nginx-reverse-proxy-for-docker/) -* [Docker Log Management With Fluentd](http://jasonwilder.com/blog/2014/03/17/docker-log-management-using-fluentd/) -* [Docker Service Discovery Using Etcd and Haproxy](http://jasonwilder.com/blog/2014/07/15/docker-service-discovery/) +- [Automated Nginx Reverse Proxy for Docker](http://jasonwilder.com/blog/2014/03/25/automated-nginx-reverse-proxy-for-docker/) +- [Docker Log Management With Fluentd](http://jasonwilder.com/blog/2014/03/17/docker-log-management-using-fluentd/) +- [Docker Service Discovery Using Etcd and Haproxy](http://jasonwilder.com/blog/2014/07/15/docker-service-discovery/) #### NGINX Reverse Proxy Config @@ -410,20 +459,20 @@ For example, this is a JSON version of an emitted RuntimeContainer struct: Start nginx-proxy: -``` -$ docker run -d -p 80:80 -v /var/run/docker.sock:/tmp/docker.sock -t nginxproxy/nginx-proxy +```console +docker run -d -p 80:80 -v /var/run/docker.sock:/tmp/docker.sock -t nginxproxy/nginx-proxy ``` -Then start containers with a VIRTUAL_HOST env variable: +Then start containers with a VIRTUAL_HOST (and the VIRTUAL_PORT if more than one port is exposed) env variable: -``` -$ docker run -e VIRTUAL_HOST=foo.bar.com -t ... +```console +docker run -e VIRTUAL_HOST=foo.bar.com -e VIRTUAL_PORT=80 -t ... ``` If you wanted to run docker-gen directly on the host, you could do it with: -``` -$ docker-gen -only-published -watch -notify "/etc/init.d/nginx reload" templates/nginx.tmpl /etc/nginx/sites-enabled/default +```console +docker-gen -only-published -watch -notify "/etc/init.d/nginx reload" templates/nginx.tmpl /etc/nginx/sites-enabled/default ``` #### Fluentd Log Management @@ -431,41 +480,34 @@ $ docker-gen -only-published -watch -notify "/etc/init.d/nginx reload" templates This template generate a fluentd.conf file used by fluentd. It would then ship log files off the host. -``` -$ docker-gen -watch -notify "restart fluentd" templates/fluentd.tmpl /etc/fluent/fluent.conf +```console +docker-gen -watch -notify "restart fluentd" templates/fluentd.tmpl /etc/fluent/fluent.conf ``` #### Service Discovery in Etcd - This template is an example of generating a script that is then executed. This template generates a python script that is then executed which register containers in Etcd using its HTTP API. -``` -$ docker-gen -notify "/bin/bash /tmp/etcd.sh" -interval 10 templates/etcd.tmpl /tmp/etcd.sh +```console +docker-gen -notify "/bin/bash /tmp/etcd.sh" -interval 10 templates/etcd.tmpl /tmp/etcd.sh ``` - ### Development This project uses [Go Modules](https://golang.org/ref/mod) for managing 3rd party dependencies. -This means that at least `go 1.11` is required. +This means that at least `go 1.11` is required. -For `go 1.11` and `go 1.12` it is additionally required to manually enable support by setting `GO111MODULE=on`. -For later versions, this is not required. +For `go 1.11` and `go 1.12` it is additionally required to manually enable support by setting `GO111MODULE=on`. +For later versions, this is not required. +```console +git clone +cd +make get-deps +make ``` -$ git clone -$ cd -$ make get-deps -$ make -``` - -### TODO - - * Add event status for handling start and stop events differently - -### License -MIT +### Powered by +[![GoLand logo](https://resources.jetbrains.com/storage/products/company/brand/logos/GoLand_icon.svg)](https://www.jetbrains.com/go/) diff --git a/app/docker-entrypoint.sh b/app/docker-entrypoint.sh new file mode 100755 index 00000000..0f8667c5 --- /dev/null +++ b/app/docker-entrypoint.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +set -eu + +# run container's CMD if it is an executable in PATH +command -v -- "$1" >/dev/null 2>&1 || set -- docker-gen "$@" + +exec "$@" diff --git a/cmd/docker-gen/main.go b/cmd/docker-gen/main.go index a28deeac..1fc351a7 100644 --- a/cmd/docker-gen/main.go +++ b/cmd/docker-gen/main.go @@ -7,6 +7,8 @@ import ( "os" "os/signal" "path/filepath" + "slices" + "strings" "syscall" "github.com/BurntSushi/toml" @@ -16,6 +18,7 @@ import ( ) type stringslice []string +type mapstringslice map[string][]string var ( buildVersion string @@ -24,13 +27,16 @@ var ( wait string notifyCmd string notifyOutput bool - notifyContainerID string + sighupContainerID stringslice + notifyContainerID stringslice notifyContainerSignal int + notifyContainerFilter mapstringslice = make(mapstringslice) onlyExposed bool onlyPublished bool includeStopped bool configFiles stringslice configs config.ConfigFile + eventFilter mapstringslice = mapstringslice{"event": {"start", "stop", "die", "health_status"}} interval int keepBlankLines bool endpoint string @@ -45,11 +51,22 @@ func (strings *stringslice) String() string { } func (strings *stringslice) Set(value string) error { - // TODO: Throw an error for duplicate `dest` *strings = append(*strings, value) return nil } +func (filter *mapstringslice) String() string { + return "[string][]string" +} + +func (filter *mapstringslice) Set(value string) error { + name, value, found := strings.Cut(value, "=") + if found { + (*filter)[name] = append((*filter)[name], value) + } + return nil +} + func usage() { println(`Usage: docker-gen [options] template [dest] @@ -96,12 +113,14 @@ func initFlags() { flag.BoolVar(&includeStopped, "include-stopped", false, "include stopped containers") flag.BoolVar(¬ifyOutput, "notify-output", false, "log the output(stdout/stderr) of notify command") flag.StringVar(¬ifyCmd, "notify", "", "run command after template is regenerated (e.g `restart xyz`)") - flag.StringVar(¬ifyContainerID, "notify-sighup", "", - "send HUP signal to container. Equivalent to docker kill -s HUP `container-ID`") - flag.StringVar(¬ifyContainerID, "notify-container", "", - "container to send a signal to") + flag.Var(&sighupContainerID, "notify-sighup", + "send HUP signal to container. Equivalent to docker kill -s HUP `container-ID`. You can pass this option multiple times to send HUP to multiple containers.") + flag.Var(¬ifyContainerID, "notify-container", + "send -notify-signal signal (defaults to 1 / HUP) to container. You can pass this option multiple times to notify multiple containers.") + flag.Var(¬ifyContainerFilter, "notify-filter", + "container filter for notification (e.g -notify-filter name=foo). You can pass this option multiple times to combine filters with AND. https://docs.docker.com/engine/reference/commandline/ps/#filter") flag.IntVar(¬ifyContainerSignal, "notify-signal", int(docker.SIGHUP), - "signal to send to the notify-container. Defaults to SIGHUP") + "signal to send to the notify-container and notify-filter. Defaults to SIGHUP") flag.Var(&configFiles, "config", "config files with template directives. Config files will be merged if this option is specified multiple times.") flag.IntVar(&interval, "interval", 0, "notify command interval (secs)") flag.BoolVar(&keepBlankLines, "keep-blank-lines", false, "keep blank lines in the output file") @@ -111,6 +130,9 @@ func initFlags() { flag.StringVar(&tlsCaCert, "tlscacert", filepath.Join(certPath, "ca.pem"), "path to TLS CA certificate file") flag.BoolVar(&tlsVerify, "tlsverify", os.Getenv("DOCKER_TLS_VERIFY") != "", "verify docker daemon's TLS certicate") + flag.Var(&eventFilter, "event-filter", + "additional filter for event watched by docker-gen (e.g -event-filter event=connect -event-filter event=disconnect). You can pass this option multiple times to combine filters. By default docker-gen listen for container events start, stop, die and health_status. https://docs.docker.com/engine/reference/commandline/events/#filtering-events") + flag.Usage = usage flag.Parse() } @@ -132,6 +154,9 @@ func main() { os.Exit(1) } + slices.Sort(configFiles) + configFiles = slices.Compact(configFiles) + if len(configFiles) > 0 { for _, configFile := range configFiles { err := loadConfig(configFile) @@ -158,14 +183,22 @@ func main() { Interval: interval, KeepBlankLines: keepBlankLines, } - if notifyContainerID != "" { - cfg.NotifyContainers[notifyContainerID] = notifyContainerSignal + for _, id := range sighupContainerID { + cfg.NotifyContainers[id] = int(syscall.SIGHUP) + } + for _, id := range notifyContainerID { + cfg.NotifyContainers[id] = notifyContainerSignal + } + if len(notifyContainerFilter) > 0 { + cfg.NotifyContainersFilter = notifyContainerFilter + cfg.NotifyContainersSignal = notifyContainerSignal } configs = config.ConfigFile{ - Config: []config.Config{cfg}} + Config: []config.Config{cfg}, + } } - all := true + all := false for _, config := range configs.Config { if config.IncludeStopped { all = true @@ -173,13 +206,14 @@ func main() { } generator, err := generator.NewGenerator(generator.GeneratorConfig{ - Endpoint: endpoint, - TLSKey: tlsKey, - TLSCert: tlsCert, - TLSCACert: tlsCaCert, - TLSVerify: tlsVerify, - All: all, - ConfigFile: configs, + Endpoint: endpoint, + TLSKey: tlsKey, + TLSCert: tlsCert, + TLSCACert: tlsCaCert, + TLSVerify: tlsVerify, + All: all, + EventFilter: eventFilter, + ConfigFile: configs, }) if err != nil { diff --git a/go.mod b/go.mod index 6a141b4c..4fbad338 100644 --- a/go.mod +++ b/go.mod @@ -1,37 +1,49 @@ module github.com/nginx-proxy/docker-gen -go 1.17 +go 1.23.0 require ( - github.com/BurntSushi/toml v0.4.1 - github.com/fsouza/go-dockerclient v1.7.6 - github.com/stretchr/testify v1.7.0 + github.com/BurntSushi/toml v1.3.2 + github.com/Masterminds/sprig/v3 v3.3.0 + github.com/fsouza/go-dockerclient v1.12.1 + github.com/stretchr/testify v1.10.0 + gopkg.in/yaml.v3 v3.0.1 ) require ( + dario.cat/mergo v1.0.1 // indirect github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect - github.com/Microsoft/go-winio v0.5.1 // indirect - github.com/Microsoft/hcsshim v0.8.23 // indirect - github.com/containerd/cgroups v1.0.1 // indirect - github.com/containerd/containerd v1.5.8 // indirect + github.com/Masterminds/goutils v1.1.1 // indirect + github.com/Masterminds/semver/v3 v3.3.0 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/containerd/log v0.1.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/docker/docker v20.10.12+incompatible // indirect + github.com/docker/docker v27.5.1+incompatible // indirect github.com/docker/go-connections v0.4.0 // indirect - github.com/docker/go-units v0.4.0 // indirect + github.com/docker/go-units v0.5.0 // indirect github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect - github.com/gorilla/mux v1.8.0 // indirect - github.com/moby/sys/mount v0.2.0 // indirect - github.com/moby/sys/mountinfo v0.4.1 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/gorilla/mux v1.8.1 // indirect + github.com/huandu/xstrings v1.5.0 // indirect + github.com/klauspost/compress v1.15.9 // indirect + github.com/mitchellh/copystructure v1.2.0 // indirect + github.com/mitchellh/reflectwalk v1.0.2 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/moby/patternmatcher v0.6.0 // indirect + github.com/moby/sys/sequential v0.5.0 // indirect + github.com/moby/sys/user v0.1.0 // indirect + github.com/moby/sys/userns v0.1.0 // indirect github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 // indirect github.com/morikuni/aec v1.0.0 // indirect + github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect github.com/opencontainers/go-digest v1.0.0 // indirect - github.com/opencontainers/image-spec v1.0.2 // indirect - github.com/opencontainers/runc v1.0.3 // indirect + github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - github.com/sirupsen/logrus v1.8.1 // indirect - go.opencensus.io v0.22.3 // indirect - golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 // indirect - gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect + github.com/shopspring/decimal v1.4.0 // indirect + github.com/sirupsen/logrus v1.9.3 // indirect + github.com/spf13/cast v1.7.0 // indirect + golang.org/x/crypto v0.35.0 // indirect + golang.org/x/sys v0.30.0 // indirect + gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect ) diff --git a/go.sum b/go.sum index 012759e4..4a70e652 100644 --- a/go.sum +++ b/go.sum @@ -1,969 +1,143 @@ -bazil.org/fuse v0.0.0-20160811212531-371fbbdaa898/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= -cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= -cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= -cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= -cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= -cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= -cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= -cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= -cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= -cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= -cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= -cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= -cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= -cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= -cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= -cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= -cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= -cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= -cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= -cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= -dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= -github.com/Azure/azure-sdk-for-go v16.2.1+incompatible/go.mod h1:9XXNKU+eRnpl9moKnB4QOLf1HestfXbmab5FXxiDBjc= -github.com/Azure/go-ansiterm v0.0.0-20170929234023-d6e3b3328b78/go.mod h1:LmzpDX56iTiv29bbRTIsUNlaFfuhWRQBWjQdVyAevI8= +dario.cat/mergo v1.0.1 h1:Ra4+bf83h2ztPIQYNP99R6m+Y7KfnARDfID+a+vLl4s= +dario.cat/mergo v1.0.1/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20210715213245-6c3934b029d8 h1:V8krnnfGj4pV65YLUm3C0/8bl7V5Nry2Pwvy3ru/wLc= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20210715213245-6c3934b029d8/go.mod h1:CzsSbkDixRphAF5hS6wbMKq0eI6ccJRb7/A0M6JBnwg= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 h1:UQHMgLO+TxOElx5B5HZ4hJQsoJ/PvUvKRhJHDQXO8P8= github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/Azure/go-autorest v10.8.1+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest/autorest v0.11.1/go.mod h1:JFgpikqFJ/MleTTxwepExTKnFUKKszPS8UavbQYUMuw= -github.com/Azure/go-autorest/autorest/adal v0.9.0/go.mod h1:/c022QCutn2P7uY+/oQWWNcK9YU+MH96NgK+jErpbcg= -github.com/Azure/go-autorest/autorest/adal v0.9.5/go.mod h1:B7KF7jKIeC9Mct5spmyCB/A8CG/sEz1vwIRGv/bbw7A= -github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= -github.com/Azure/go-autorest/autorest/mocks v0.4.0/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= -github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= -github.com/Azure/go-autorest/logger v0.2.0/go.mod h1:T9E3cAhj2VqvPOtCYAvby9aBXkZmbF5NWuPV8+WeEW8= -github.com/Azure/go-autorest/tracing v0.6.0/go.mod h1:+vhtPC754Xsa23ID7GlGsrdKBpUA79WCAKPPZVC2DeU= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/BurntSushi/toml v0.4.1 h1:GaI7EiDXDRfa8VshkTj7Fym7ha+y8/XxIgD2okUIjLw= -github.com/BurntSushi/toml v0.4.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= -github.com/Microsoft/go-winio v0.4.11/go.mod h1:VhR8bwka0BXejwEJY73c50VrPtXAaKcyvVC4A4RozmA= -github.com/Microsoft/go-winio v0.4.14/go.mod h1:qXqCSQ3Xa7+6tgxaGTIe4Kpcdsi+P8jBhyzoq1bpyYA= -github.com/Microsoft/go-winio v0.4.15-0.20190919025122-fc70bd9a86b5/go.mod h1:tTuCMEN+UleMWgg9dVx4Hu52b1bJo+59jBh3ajtinzw= -github.com/Microsoft/go-winio v0.4.16-0.20201130162521-d1ffc52c7331/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= -github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= -github.com/Microsoft/go-winio v0.4.17-0.20210211115548-6eac466e5fa3/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= -github.com/Microsoft/go-winio v0.4.17-0.20210324224401-5516f17a5958/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= -github.com/Microsoft/go-winio v0.4.17/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= -github.com/Microsoft/go-winio v0.5.1 h1:aPJp2QD7OOrhO5tQXqQoGSJc+DjDtWTGLOmNyAm6FgY= -github.com/Microsoft/go-winio v0.5.1/go.mod h1:JPGBdM1cNvN/6ISo+n8V5iA4v8pBzdOpzfwIujj1a84= -github.com/Microsoft/hcsshim v0.8.6/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= -github.com/Microsoft/hcsshim v0.8.7-0.20190325164909-8abdbb8205e4/go.mod h1:Op3hHsoHPAvb6lceZHDtd9OkTew38wNoXnJs8iY7rUg= -github.com/Microsoft/hcsshim v0.8.7/go.mod h1:OHd7sQqRFrYd3RmSgbgji+ctCwkbq2wbEYNSzOYtcBQ= -github.com/Microsoft/hcsshim v0.8.9/go.mod h1:5692vkUqntj1idxauYlpoINNKeqCiG6Sg38RRsjT5y8= -github.com/Microsoft/hcsshim v0.8.14/go.mod h1:NtVKoYxQuTLx6gEq0L96c9Ju4JbRJ4nY2ow3VK6a9Lg= -github.com/Microsoft/hcsshim v0.8.15/go.mod h1:x38A4YbHbdxJtc0sF6oIz+RG0npwSCAvn69iY6URG00= -github.com/Microsoft/hcsshim v0.8.16/go.mod h1:o5/SZqmR7x9JNKsW3pu+nqHm0MF8vbA+VxGOoXdC600= -github.com/Microsoft/hcsshim v0.8.23 h1:47MSwtKGXet80aIn+7h4YI6fwPmwIghAnsx2aOUrG2M= -github.com/Microsoft/hcsshim v0.8.23/go.mod h1:4zegtUJth7lAvFyc6cH2gGQ5B3OFQim01nnU2M8jKDg= -github.com/Microsoft/hcsshim/test v0.0.0-20201218223536-d3e5debf77da/go.mod h1:5hlzMzRKMLyo42nCZ9oml8AdTlq/0cvIaBv6tK1RehU= -github.com/Microsoft/hcsshim/test v0.0.0-20210227013316-43a75bb4edd3/go.mod h1:mw7qgWloBUl75W/gVH3cQszUg1+gUITj7D6NY7ywVnY= -github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= -github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= -github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= -github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= -github.com/Shopify/logrus-bugsnag v0.0.0-20171204204709-577dee27f20d/go.mod h1:HI8ITrYtUY+O+ZhtlqUnD8+KwNPOyugEhfP9fdUIaEQ= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY= -github.com/aws/aws-sdk-go v1.15.11/go.mod h1:mFuSZ37Z9YOHbQEwBWztmVzqXrEkub65tZoCYDt7FT0= -github.com/beorn7/perks v0.0.0-20160804104726-4c0e84591b9a/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/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/bitly/go-simplejson v0.5.0/go.mod h1:cXHtHw4XUPsvGaxgjIAn8PhEWG9NfngEKAMDJEczWVA= -github.com/bits-and-blooms/bitset v1.2.0/go.mod h1:gIdJ4wp64HaoK2YrL1Q5/N7Y16edYb8uY+O0FJTyyDA= -github.com/blang/semver v3.1.0+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= -github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= -github.com/bmizerany/assert v0.0.0-20160611221934-b7ed37b82869/go.mod h1:Ekp36dRnpXw/yCqJaO+ZrUyxD+3VXMFFr56k5XYrpB4= -github.com/bshuster-repo/logrus-logstash-hook v0.4.1/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= -github.com/buger/jsonparser v0.0.0-20180808090653-f4dd9f5a6b44/go.mod h1:bbYlZJ7hK1yFx9hf58LP0zeX7UjIGs20ufpu3evjr+s= -github.com/bugsnag/bugsnag-go v0.0.0-20141110184014-b1d153021fcd/go.mod h1:2oa8nejYd4cQ/b0hMIopN0lCRxU0bueqREvZLWFrtK8= -github.com/bugsnag/osext v0.0.0-20130617224835-0dd3f918b21b/go.mod h1:obH5gd0BsqsP2LwDJ9aOkm/6J86V6lyAXCoQWGw3K50= -github.com/bugsnag/panicwrap v0.0.0-20151223152923-e2c28503fcd0/go.mod h1:D/8v3kj0zr8ZAKg1AQ6crr+5VwKN5eIywRkfhyM/+dE= -github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/checkpoint-restore/go-criu/v4 v4.1.0/go.mod h1:xUQBLp4RLc5zJtWY++yjOoMoB5lihDt7fai+75m+rGw= -github.com/checkpoint-restore/go-criu/v5 v5.0.0/go.mod h1:cfwC0EG7HMUenopBsUf9d89JlCLQIfgVcNsNN0t6T2M= -github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= -github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= -github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= -github.com/cilium/ebpf v0.0.0-20200110133405-4032b1d8aae3/go.mod h1:MA5e5Lr8slmEg9bt0VpxxWqJlO4iwu3FBdHUzV7wQVg= -github.com/cilium/ebpf v0.0.0-20200702112145-1c8d4c9ef775/go.mod h1:7cR51M8ViRLIdUjrmSXlK9pkrsDlLHbO8jiB8X8JnOc= -github.com/cilium/ebpf v0.2.0/go.mod h1:To2CFviqOWL/M0gIMsvSMlqe7em/l1ALkX1PyjrX2Qs= -github.com/cilium/ebpf v0.4.0/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= -github.com/cilium/ebpf v0.6.2/go.mod h1:4tRaxcgiL706VnOzHOdBlY8IEAIdxINsQBcU4xJJXRs= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= -github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= -github.com/containerd/aufs v0.0.0-20200908144142-dab0cbea06f4/go.mod h1:nukgQABAEopAHvB6j7cnP5zJ+/3aVcE7hCYqvIwAHyE= -github.com/containerd/aufs v0.0.0-20201003224125-76a6863f2989/go.mod h1:AkGGQs9NM2vtYHaUen+NljV0/baGCAPELGm2q9ZXpWU= -github.com/containerd/aufs v0.0.0-20210316121734-20793ff83c97/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= -github.com/containerd/aufs v1.0.0/go.mod h1:kL5kd6KM5TzQjR79jljyi4olc1Vrx6XBlcyj3gNv2PU= -github.com/containerd/btrfs v0.0.0-20201111183144-404b9149801e/go.mod h1:jg2QkJcsabfHugurUvvPhS3E08Oxiuh5W/g1ybB4e0E= -github.com/containerd/btrfs v0.0.0-20210316141732-918d888fb676/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= -github.com/containerd/btrfs v1.0.0/go.mod h1:zMcX3qkXTAi9GI50+0HOeuV8LU2ryCE/V2vG/ZBiTss= -github.com/containerd/cgroups v0.0.0-20190717030353-c4b9ac5c7601/go.mod h1:X9rLEHIqSf/wfK8NsPqxJmeZgW4pcfzdXITDrUSJ6uI= -github.com/containerd/cgroups v0.0.0-20190919134610-bf292b21730f/go.mod h1:OApqhQ4XNSNC13gXIwDjhOQxjWa/NxkwZXJ1EvqT0ko= -github.com/containerd/cgroups v0.0.0-20200531161412-0dbf7f05ba59/go.mod h1:pA0z1pT8KYB3TCXK/ocprsh7MAkoW8bZVzPdih9snmM= -github.com/containerd/cgroups v0.0.0-20200710171044-318312a37340/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= -github.com/containerd/cgroups v0.0.0-20200824123100-0b889c03f102/go.mod h1:s5q4SojHctfxANBDvMeIaIovkq29IP48TKAxnhYRxvo= -github.com/containerd/cgroups v0.0.0-20210114181951-8a68de567b68/go.mod h1:ZJeTFisyysqgcCdecO57Dj79RfL0LNeGiFUqLYQRYLE= -github.com/containerd/cgroups v1.0.1 h1:iJnMvco9XGvKUvNQkv88bE4uJXxRQH18efbKo9w5vHQ= -github.com/containerd/cgroups v1.0.1/go.mod h1:0SJrPIenamHDcZhEcJMNBB85rHcUsw4f25ZfBiPYRkU= -github.com/containerd/console v0.0.0-20180822173158-c12b1e7919c1/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= -github.com/containerd/console v0.0.0-20181022165439-0650fd9eeb50/go.mod h1:Tj/on1eG8kiEhd0+fhSDzsPAFESxzBBvdyEgyryXffw= -github.com/containerd/console v0.0.0-20191206165004-02ecf6a7291e/go.mod h1:8Pf4gM6VEbTNRIT26AyyU7hxdQU3MvAvxVI0sc00XBE= -github.com/containerd/console v1.0.1/go.mod h1:XUsP6YE/mKtz6bxc+I8UiKKTP04qjQL4qcS3XoQ5xkw= -github.com/containerd/console v1.0.2/go.mod h1:ytZPjGgY2oeTkAONYafi2kSj0aYggsf8acV1PGKCbzQ= -github.com/containerd/containerd v1.2.10/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.3.0-beta.2.0.20190828155532-0293cbd26c69/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.3.0/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.3.1-0.20191213020239-082f7e3aed57/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.3.2/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.4.0-beta.2.0.20200729163537-40b22ef07410/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.4.1/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.4.3/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.4.9/go.mod h1:bC6axHOhabU15QhwfG7w5PipXdVtMXFTttgp+kVtyUA= -github.com/containerd/containerd v1.5.0-beta.1/go.mod h1:5HfvG1V2FsKesEGQ17k5/T7V960Tmcumvqn8Mc+pCYQ= -github.com/containerd/containerd v1.5.0-beta.3/go.mod h1:/wr9AVtEM7x9c+n0+stptlo/uBBoBORwEx6ardVcmKU= -github.com/containerd/containerd v1.5.0-beta.4/go.mod h1:GmdgZd2zA2GYIBZ0w09ZvgqEq8EfBp/m3lcVZIvPHhI= -github.com/containerd/containerd v1.5.0-rc.0/go.mod h1:V/IXoMqNGgBlabz3tHD2TWDoTJseu1FGOKuoA4nNb2s= -github.com/containerd/containerd v1.5.8 h1:NmkCC1/QxyZFBny8JogwLpOy2f+VEbO/f6bV2Mqtwuw= -github.com/containerd/containerd v1.5.8/go.mod h1:YdFSv5bTFLpG2HIYmfqDpSYYTDX+mc5qtSuYx1YUb/s= -github.com/containerd/continuity v0.0.0-20190426062206-aaeac12a7ffc/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= -github.com/containerd/continuity v0.0.0-20190815185530-f2a389ac0a02/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= -github.com/containerd/continuity v0.0.0-20191127005431-f65d91d395eb/go.mod h1:GL3xCUCBDV3CZiTSEKksMWbLE66hEyuu9qyDOOqM47Y= -github.com/containerd/continuity v0.0.0-20200710164510-efbc4488d8fe/go.mod h1:cECdGN1O8G9bgKTlLhuPJimka6Xb/Gg7vYzCTNVxhvo= -github.com/containerd/continuity v0.0.0-20201208142359-180525291bb7/go.mod h1:kR3BEg7bDFaEddKm54WSmrol1fKWDU1nKYkgrcgZT7Y= -github.com/containerd/continuity v0.0.0-20210208174643-50096c924a4e/go.mod h1:EXlVlkqNba9rJe3j7w3Xa924itAMLgZH4UD/Q4PExuQ= -github.com/containerd/continuity v0.1.0 h1:UFRRY5JemiAhPZrr/uE0n8fMTLcZsUvySPr1+D7pgr8= -github.com/containerd/continuity v0.1.0/go.mod h1:ICJu0PwR54nI0yPEnJ6jcS+J7CZAUXrLh8lPo2knzsM= -github.com/containerd/fifo v0.0.0-20180307165137-3d5202aec260/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= -github.com/containerd/fifo v0.0.0-20190226154929-a9fb20d87448/go.mod h1:ODA38xgv3Kuk8dQz2ZQXpnv/UZZUHUCL7pnLehbXgQI= -github.com/containerd/fifo v0.0.0-20200410184934-f15a3290365b/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= -github.com/containerd/fifo v0.0.0-20201026212402-0724c46b320c/go.mod h1:jPQ2IAeZRCYxpS/Cm1495vGFww6ecHmMk1YJH2Q5ln0= -github.com/containerd/fifo v0.0.0-20210316144830-115abcc95a1d/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= -github.com/containerd/fifo v1.0.0/go.mod h1:ocF/ME1SX5b1AOlWi9r677YJmCPSwwWnQ9O123vzpE4= -github.com/containerd/go-cni v1.0.1/go.mod h1:+vUpYxKvAF72G9i1WoDOiPGRtQpqsNW/ZHtSlv++smU= -github.com/containerd/go-cni v1.0.2/go.mod h1:nrNABBHzu0ZwCug9Ije8hL2xBCYh/pjfMb1aZGrrohk= -github.com/containerd/go-runc v0.0.0-20180907222934-5a6d9f37cfa3/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= -github.com/containerd/go-runc v0.0.0-20190911050354-e029b79d8cda/go.mod h1:IV7qH3hrUgRmyYrtgEeGWJfWbgcHL9CSRruz2Vqcph0= -github.com/containerd/go-runc v0.0.0-20200220073739-7016d3ce2328/go.mod h1:PpyHrqVs8FTi9vpyHwPwiNEGaACDxT/N/pLcvMSRA9g= -github.com/containerd/go-runc v0.0.0-20201020171139-16b287bc67d0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= -github.com/containerd/go-runc v1.0.0/go.mod h1:cNU0ZbCgCQVZK4lgG3P+9tn9/PaJNmoDXPpoJhDR+Ok= -github.com/containerd/imgcrypt v1.0.1/go.mod h1:mdd8cEPW7TPgNG4FpuP3sGBiQ7Yi/zak9TYCG3juvb0= -github.com/containerd/imgcrypt v1.0.4-0.20210301171431-0ae5c75f59ba/go.mod h1:6TNsg0ctmizkrOgXRNQjAPFWpMYRWuiB6dSF4Pfa5SA= -github.com/containerd/imgcrypt v1.1.1-0.20210312161619-7ed62a527887/go.mod h1:5AZJNI6sLHJljKuI9IHnw1pWqo/F0nGDOuR9zgTs7ow= -github.com/containerd/imgcrypt v1.1.1/go.mod h1:xpLnwiQmEUJPvQoAapeb2SNCxz7Xr6PJrXQb0Dpc4ms= -github.com/containerd/nri v0.0.0-20201007170849-eb1350a75164/go.mod h1:+2wGSDGFYfE5+So4M5syatU0N0f0LbWpuqyMi4/BE8c= -github.com/containerd/nri v0.0.0-20210316161719-dbaa18c31c14/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= -github.com/containerd/nri v0.1.0/go.mod h1:lmxnXF6oMkbqs39FiCt1s0R2HSMhcLel9vNL3m4AaeY= -github.com/containerd/ttrpc v0.0.0-20190828154514-0e0f228740de/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= -github.com/containerd/ttrpc v0.0.0-20190828172938-92c8520ef9f8/go.mod h1:PvCDdDGpgqzQIzDW1TphrGLssLDZp2GuS+X5DkEJB8o= -github.com/containerd/ttrpc v0.0.0-20191028202541-4f1b8fe65a5c/go.mod h1:LPm1u0xBw8r8NOKoOdNMeVHSawSsltak+Ihv+etqsE8= -github.com/containerd/ttrpc v1.0.1/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= -github.com/containerd/ttrpc v1.0.2/go.mod h1:UAxOpgT9ziI0gJrmKvgcZivgxOp8iFPSk8httJEt98Y= -github.com/containerd/ttrpc v1.1.0/go.mod h1:XX4ZTnoOId4HklF4edwc4DcqskFZuvXB1Evzy5KFQpQ= -github.com/containerd/typeurl v0.0.0-20180627222232-a93fcdb778cd/go.mod h1:Cm3kwCdlkCfMSHURc+r6fwoGH6/F1hH3S4sg0rLFWPc= -github.com/containerd/typeurl v0.0.0-20190911142611-5eb25027c9fd/go.mod h1:GeKYzf2pQcqv7tJ0AoCuuhtnqhva5LNU3U+OyKxxJpk= -github.com/containerd/typeurl v1.0.1/go.mod h1:TB1hUtrpaiO88KEK56ijojHS1+NeF0izUACaJW2mdXg= -github.com/containerd/typeurl v1.0.2/go.mod h1:9trJWW2sRlGub4wZJRTW83VtbOLS6hwcDZXTn6oPz9s= -github.com/containerd/zfs v0.0.0-20200918131355-0a33824f23a2/go.mod h1:8IgZOBdv8fAgXddBT4dBXJPtxyRsejFIpXoklgxgEjw= -github.com/containerd/zfs v0.0.0-20210301145711-11e8f1707f62/go.mod h1:A9zfAbMlQwE+/is6hi0Xw8ktpL+6glmqZYtevJgaB8Y= -github.com/containerd/zfs v0.0.0-20210315114300-dde8f0fda960/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= -github.com/containerd/zfs v0.0.0-20210324211415-d5c4544f0433/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= -github.com/containerd/zfs v1.0.0/go.mod h1:m+m51S1DvAP6r3FcmYCp54bQ34pyOwTieQDNRIRHsFY= -github.com/containernetworking/cni v0.7.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= -github.com/containernetworking/cni v0.8.0/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= -github.com/containernetworking/cni v0.8.1/go.mod h1:LGwApLUm2FpoOfxTDEeq8T9ipbpZ61X79hmU3w8FmsY= -github.com/containernetworking/plugins v0.8.6/go.mod h1:qnw5mN19D8fIwkqW7oHHYDHVlzhJpcY6TQxn/fUyDDM= -github.com/containernetworking/plugins v0.9.1/go.mod h1:xP/idU2ldlzN6m4p5LmGiwRDjeJr6FLK6vuiUwoH7P8= -github.com/containers/ocicrypt v1.0.1/go.mod h1:MeJDzk1RJHv89LjsH0Sp5KTY3ZYkjXO/C+bKAeWFIrc= -github.com/containers/ocicrypt v1.1.0/go.mod h1:b8AOe0YR67uU8OqfVNcznfFpAzu3rdgUV4GP9qXPfu4= -github.com/containers/ocicrypt v1.1.1/go.mod h1:Dm55fwWm1YZAjYRaJ94z2mfZikIyIN4B0oB3dj3jFxY= -github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk= -github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-iptables v0.4.5/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= -github.com/coreos/go-iptables v0.5.0/go.mod h1:/mVI274lEDI2ns62jHCDnCyBF9Iwsmekav8Dbxlm1MU= -github.com/coreos/go-oidc v2.1.0+incompatible/go.mod h1:CgnwVTmzoESiwO9qyAFEMiHoZ1nMCKZlZ9V6mm3/LKc= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20161114122254-48702e0da86b/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/go-systemd/v22 v22.0.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= -github.com/coreos/go-systemd/v22 v22.1.0/go.mod h1:xO0FLkIi5MaZafQlIrOotqXZ90ih+1atmu1JpKERPPk= -github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= +github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= +github.com/Masterminds/semver/v3 v3.3.0 h1:B8LGeaivUe71a5qox1ICM/JLl0NqZSW5CHyL+hmvYS0= +github.com/Masterminds/semver/v3 v3.3.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= +github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= github.com/creack/pty v1.1.11 h1:07n33Z8lZxZ2qwegKbObQohDhXDQxiMMz1NOUGYlesw= github.com/creack/pty v1.1.11/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= -github.com/cyphar/filepath-securejoin v0.2.2/go.mod h1:FpkQEhXnPnOthhzymB7CGsFk2G9VLXONKD9G7QGMM+4= -github.com/d2g/dhcp4 v0.0.0-20170904100407-a1d1b6c41b1c/go.mod h1:Ct2BUK8SB0YC1SMSibvLzxjeJLnrYEVLULFNiHY9YfQ= -github.com/d2g/dhcp4client v1.0.0/go.mod h1:j0hNfjhrt2SxUOw55nL0ATM/z4Yt3t2Kd1mW34z5W5s= -github.com/d2g/dhcp4server v0.0.0-20181031114812-7d4a0a7f59a5/go.mod h1:Eo87+Kg/IX2hfWJfwxMzLyuSZyxSoAug2nGa1G2QAi8= -github.com/d2g/hardwareaddr v0.0.0-20190221164911-e7d9fbe030e4/go.mod h1:bMl4RjIciD2oAxI7DmWRx6gbeqrkoLqv3MV0vzNad+I= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba/go.mod h1:dV8lFg6daOBZbT6/BDGIz6Y3WFGn8juu6G+CQ6LHtl0= -github.com/dgrijalva/jwt-go v0.0.0-20170104182250-a601269ab70c/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/dnaeon/go-vcr v1.0.1/go.mod h1:aBB1+wY4s93YsC3HHjMBMrwTj2R9FHDzUr9KyGc8n1E= -github.com/docker/distribution v0.0.0-20190905152932-14b96e55d84c/go.mod h1:0+TTO4EOBfRPhZXAeF1Vu+W3hHZ8eLp8PgKVZlcvtFY= -github.com/docker/distribution v2.7.1-0.20190205005809-0d3efadf0154+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/distribution v2.7.1+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v20.10.12+incompatible h1:CEeNmFM0QZIsJCZKMkZx0ZcahTiewkrgiwfYD+dfl1U= -github.com/docker/docker v20.10.12+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker v27.5.1+incompatible h1:4PYU5dnBYqRQi0294d1FBECqT9ECWeQAIfE8q4YnPY8= +github.com/docker/docker v27.5.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/go-connections v0.4.0 h1:El9xVISelRB7BuFusrZozjnkIM5YnzCViNKohAFqRJQ= github.com/docker/go-connections v0.4.0/go.mod h1:Gbd7IOopHjR8Iph03tsViu4nIes5XhDvyHbTtUxmeec= -github.com/docker/go-events v0.0.0-20170721190031-9461782956ad/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= -github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= -github.com/docker/go-metrics v0.0.0-20180209012529-399ea8c73916/go.mod h1:/u0gXw0Gay3ceNrsHubL3BtdOL2fHf93USgMTe0W5dI= -github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= -github.com/docker/go-units v0.4.0 h1:3uh0PgVws3nIA0Q+MwDC8yjEPf9zjRfZZWXZYDct3Tw= -github.com/docker/go-units v0.4.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= -github.com/docker/libtrust v0.0.0-20150114040149-fa567046d9b1/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= -github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= -github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE= -github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= -github.com/elazarl/goproxy v0.0.0-20180725130230-947c36da3153/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= -github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= -github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= -github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k= -github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= -github.com/fsouza/go-dockerclient v1.7.6 h1:seCREhafoiy3VGdbNE05yo7pivR0DkPPCijEkqd2Uw0= -github.com/fsouza/go-dockerclient v1.7.6/go.mod h1:t5djjfegW9TqPgRe5SULtxxlavONYep6EjpSswR9ONg= -github.com/fullsailor/pkcs7 v0.0.0-20190404230743-d7302db945fa/go.mod h1:KnogPXtdwXqoenmZCw6S+25EAm2MkxbG0deNDu4cbSA= -github.com/garyburd/redigo v0.0.0-20150301180006-535138d7bcd7/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= -github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= -github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= -github.com/go-ini/ini v1.25.4/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= -github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -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-logr/logr v0.1.0/go.mod h1:ixOQHD9gLJUVQQ2ZOR7zLEifBX6tGkNJF4QyIY7sIas= -github.com/go-logr/logr v0.2.0/go.mod h1:z6/tIYblkpsD+a4lm/fGIIU9mZ+XfAiaFtq7xTgseGU= -github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= -github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= -github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= -github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL98+wF9xc8zWvFonSJ8= -github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= -github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= -github.com/godbus/dbus v0.0.0-20151105175453-c7fdd8b5cd55/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= -github.com/godbus/dbus v0.0.0-20180201030542-885f9cc04c9c/go.mod h1:/YcGZj5zSblfDWMMoOzV4fas9FZnQYTkDnsGvmh2Grw= -github.com/godbus/dbus v0.0.0-20190422162347-ade71ed3457e/go.mod h1:bBOAhwG1umN6/6ZUMtDFBMQR8jRg9O75tm9K00oMsK4= -github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gogo/googleapis v1.2.0/go.mod h1:Njal3psf3qN6dwBtQfUmBZh2ybovJ0tlu3o/AC7HYjU= -github.com/gogo/googleapis v1.4.0/go.mod h1:5YRNX2z1oM5gXdAkurHa942MDgEJyk02w4OecKY87+c= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/gogo/protobuf v1.2.2-0.20190723190241-65acae22fc9d/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/gogo/protobuf v1.3.0/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= -github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsouza/go-dockerclient v1.12.1 h1:FMoLq+Zhv9Oz/rFmu6JWkImfr6CBgZOPcL+bHW4gS0o= +github.com/fsouza/go-dockerclient v1.12.1/go.mod h1:OqsgJJcpCwqyM3JED7TdfM9QVWS5O7jSYwXxYKmOooY= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20190129154638-5b532d6fd5ef/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= -github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= -github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= -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/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= -github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= -github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= -github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= -github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= -github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= -github.com/golang/protobuf v1.5.0 h1:LUVKkCeviFUMKqHa4tXIIij/lbhnMbP7Fn5wKdKkRh4= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 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.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.2/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.5.6 h1:BKbKCqvP6I+rmFHt06ZmyQtvB8xAkWdhFyr0ZUNZcxQ= -github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= -github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= -github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= -github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= -github.com/googleapis/gnostic v0.4.1/go.mod h1:LRhVm6pbyptWbWbuZ38d1eyptfvIytN3ir6b65WBswg= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/handlers v0.0.0-20150720190736-60c7bfde3e33/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= -github.com/gorilla/mux v1.7.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.8.0 h1:i40aqfkR1h2SlN9hojwV5ZA91wcXFOvkdNIeFDP5koI= -github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= -github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= -github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/hashicorp/errwrap v0.0.0-20141028054710-7554cd9344ce/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-multierror v0.0.0-20161216184304-ed905158d874/go.mod h1:JMRHfdO9jKNzS/+BTlxCjKNQHg/jZAft8U7LloJvN7I= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= -github.com/imdario/mergo v0.3.5/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.8/go.mod h1:2EnlNZ0deacrJVfApfmtdGgDfMuh/nq6Ok1EcJh5FfA= -github.com/imdario/mergo v0.3.10/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/imdario/mergo v0.3.11/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/j-keck/arping v0.0.0-20160618110441-2cf9dc699c56/go.mod h1:ymszkNOg6tORTn+6F6j+Jc8TOr5osrynvN6ivFWZ2GA= -github.com/jmespath/go-jmespath v0.0.0-20160202185014-0b12d6b521d8/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jmespath/go-jmespath v0.0.0-20160803190731-bd40a432e4c7/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= -github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= -github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= -github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +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/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/huandu/xstrings v1.5.0 h1:2ag3IFq9ZDANvthTwTiqSSZLjDc+BedvHPAp5tJy2TI= +github.com/huandu/xstrings v1.5.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= 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/klauspost/compress v1.11.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.11.13/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -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.2/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.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= -github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI= -github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/klauspost/compress v1.15.9 h1:wKRjX6JRtDdrE9qwa4b/Cip7ACOshUI4smpCQanqjSY= +github.com/klauspost/compress v1.15.9/go.mod h1:PhcZ0MbTNciWF3rruxRgKxI5NkcHHrHUDtV4Yw2GlzU= +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.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= -github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= -github.com/mailru/easyjson v0.7.0/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= -github.com/marstr/guid v1.1.0/go.mod h1:74gB1z2wpxxInTG6yaqA7KrtM0NZ+RbrcqDvYHefzho= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= -github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= -github.com/mattn/go-shellwords v1.0.3/go.mod h1:3xCvwCdWdlDJUrvuMn7Wuy9eWs4pE8vqg+NOMyg4B2o= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= -github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= -github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= -github.com/mistifyio/go-zfs v2.1.2-0.20190413222219-f784269be439+incompatible/go.mod h1:8AuVvqP/mXw1px98n46wfvcGfQ4ci2FwoAjKYxuo3Z4= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/osext v0.0.0-20151018003038-5e2d6d41470f/go.mod h1:OkQIRizQZAeMln+1tSwduZz7+Af5oFlKirV/MSYes2A= -github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= -github.com/moby/sys/mount v0.2.0 h1:WhCW5B355jtxndN5ovugJlMFJawbUODuW8fSnEH6SSM= -github.com/moby/sys/mount v0.2.0/go.mod h1:aAivFE2LB3W4bACsUXChRHQ0qKWsetY4Y9V7sxOougM= -github.com/moby/sys/mountinfo v0.4.0/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= -github.com/moby/sys/mountinfo v0.4.1 h1:1O+1cHA1aujwEwwVMa2Xm2l+gIpUHyd3+D+d7LZh1kM= -github.com/moby/sys/mountinfo v0.4.1/go.mod h1:rEr8tzG/lsIZHBtN/JjGG+LMYx9eXgW2JI+6q0qou+A= -github.com/moby/sys/symlink v0.1.0/go.mod h1:GGDODQmbFOjFsXvfLVn3+ZRxkch54RkSiGqsZeMYowQ= -github.com/moby/term v0.0.0-20200312100748-672ec06f55cd/go.mod h1:DdlQx2hp0Ss5/fLikoLlEeIYiATotOjgB//nb973jeo= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= +github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= +github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= +github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= +github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/sys/sequential v0.5.0 h1:OPvI35Lzn9K04PBbCLW0g4LcFAJgHsvXsRyewg5lXtc= +github.com/moby/sys/sequential v0.5.0/go.mod h1:tH2cOOs5V9MlPiXcQzRC+eEyab644PWKGRYaaV5ZZlo= +github.com/moby/sys/user v0.1.0 h1:WmZ93f5Ux6het5iituh9x2zAG7NFY9Aqi49jjE1PaQg= +github.com/moby/sys/user v0.1.0/go.mod h1:fKJhFOnsCN6xZ5gSfbM6zaHGgDJMrqt9/reuj4T7MmU= +github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= +github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6 h1:dcztxKSvZ4Id8iPpHERQBbIJfabdt4wUm5qy3wOL2Zc= github.com/moby/term v0.0.0-20210619224110-3f7ff695adc6/go.mod h1:E2VnQOmVuvZB6UYnnDB0qG5Nq/1tD9acaOpo6xmt0Kw= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= -github.com/mrunalp/fileutils v0.5.0/go.mod h1:M1WthSahJixYnrXQl/DFQuteStB1weuxD2QJNHXfbSQ= -github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= -github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= -github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= -github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U= -github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= -github.com/onsi/ginkgo v0.0.0-20151202141238-7f8ab55aaf3b/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.10.3/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= -github.com/onsi/gomega v0.0.0-20151007035656-2152b45fa28a/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= -github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= -github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc= -github.com/opencontainers/go-digest v0.0.0-20170106003457-a6d0ee40d420/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= -github.com/opencontainers/go-digest v0.0.0-20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= -github.com/opencontainers/go-digest v1.0.0-rc1/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= -github.com/opencontainers/go-digest v1.0.0-rc1.0.20180430190053-c9281466c8b2/go.mod h1:cMLVZDEM3+U2I4VmLI6N8jQYUd2OVphdqWwCJHrFt2s= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/image-spec v1.0.0/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/image-spec v1.0.1/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/image-spec v1.0.2 h1:9yCKha/T5XdGtO0q9Q9a6T5NUCsTn/DrBg0D7ufOcFM= -github.com/opencontainers/image-spec v1.0.2/go.mod h1:BtxoFyWECRxE4U/7sNtV5W15zMzWCbyJoFRP3s7yZA0= -github.com/opencontainers/runc v0.0.0-20190115041553-12f6a991201f/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v0.1.1/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v1.0.0-rc8.0.20190926000215-3e425f80a8c9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v1.0.0-rc9/go.mod h1:qT5XzbpPznkRYVz/mWwUaVBUv2rmF59PVA73FjuZG0U= -github.com/opencontainers/runc v1.0.0-rc93/go.mod h1:3NOsor4w32B2tC0Zbl8Knk4Wg84SM2ImC1fxBuqJ/H0= -github.com/opencontainers/runc v1.0.2/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0= -github.com/opencontainers/runc v1.0.3 h1:1hbqejyQWCJBvtKAfdO0b1FmaEf2z/bxnjqbARass5k= -github.com/opencontainers/runc v1.0.3/go.mod h1:aTaHFFwQXuA71CiyxOdFFIorAoemI04suvGRQFzWTD0= -github.com/opencontainers/runtime-spec v0.1.2-0.20190507144316-5b71a03e2700/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.0.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.0.2-0.20190207185410-29686dbc5559/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.0.2/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.0.3-0.20200929063507-e6143ca7d51d/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-spec v1.0.3-0.20210326190908-1c3f411f0417/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= -github.com/opencontainers/runtime-tools v0.0.0-20181011054405-1d69bd0f9c39/go.mod h1:r3f7wjNzSs2extwzU3Y+6pKfobzPh+kKFJ3ofN+3nfs= -github.com/opencontainers/selinux v1.6.0/go.mod h1:VVGKuOLlE7v4PJyT6h7mNWvq1rzqiriPsEqVhc+svHE= -github.com/opencontainers/selinux v1.8.0/go.mod h1:RScLhm78qiWa2gbVCcGkC7tCGdgk3ogry1nUQF8Evvo= -github.com/opencontainers/selinux v1.8.2/go.mod h1:MUIHuUEvKB1wtJjQdOyYRgOnLD2xAPP8dBsCoU0KuF8= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= -github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc= -github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1-0.20171018195549-f15c970de5b7/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b h1:YWuSjZCQAPM8UUBLkYUk1e+rZcvWHJmFb6i6rM44Xs8= +github.com/opencontainers/image-spec v1.1.0-rc2.0.20221005185240-3a7f492d3f1b/go.mod h1:3OVijpioIKYWTqjiG0zfF6wvoJ4fAXGbjdZuI2NgsRQ= 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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pquerna/cachecontrol v0.0.0-20171018203845-0dec1b30a021/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= -github.com/prometheus/client_golang v0.0.0-20180209125602-c332b6f63c06/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3/go.mod h1:/TN21ttK/J9q6uSwhBd54HahCDft0ttaMvbicHlPoso= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g= -github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= -github.com/prometheus/client_model v0.0.0-20171117100541-99fa1f4be8e5/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-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.0.0-20180110214958-89604d197083/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.0.0-20181113130724-41aa239b4cce/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.4.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc= -github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= -github.com/prometheus/procfs v0.0.0-20180125133057-cb4147076ac7/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.0-20190522114515-bc1a522cf7b1/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -github.com/prometheus/procfs v0.0.5/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ= -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.2.0/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= -github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= -github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= -github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/safchain/ethtool v0.0.0-20190326074333-42ed695e3de8/go.mod h1:Z0q5wiBQGYcxhMZ6gUqHn6pYNLypFAvaL3UvgZLR0U4= -github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= -github.com/seccomp/libseccomp-golang v0.9.1/go.mod h1:GbW5+tmTXfcxTToHLXlScSlAvWlF4P2Ca7zGrPiEpWo= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.0.4-0.20170822132746-89742aefa4b2/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= -github.com/sirupsen/logrus v1.0.6/go.mod h1:pMByvHTf9Beacp5x1UXfOR9xyW/9antXMhjMPG0dEzc= -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/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= -github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE= -github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= -github.com/spf13/cobra v0.0.2-0.20171109065643-2da4a54c5cee/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v1.0.0/go.mod h1:/6GTrnGXV9HjY+aR4k0oJ5tcvakLuG6EuKReYlHNrgE= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.1-0.20171106142849-4c012f6dcd95/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= +github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/viper v1.4.0/go.mod h1:PTJ7Z/lr49W6bUbkmS1V3by4uWynFiR9p7+dSq/yZzE= -github.com/stefanberger/go-pkcs11uri v0.0.0-20201008174630-78d3cae3a980/go.mod h1:AO3tvPzVZ/ayst6UlUKUv6rcPQInYe3IknH3jYhAKu8= -github.com/stretchr/objx v0.0.0-20180129172003-8a3f7159479f/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.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= -github.com/stretchr/testify v0.0.0-20180303142811-b89eecf5ca5d/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.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= -github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= -github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= -github.com/tchap/go-patricia v2.2.6+incompatible/go.mod h1:bmLyhP68RS6kStMGxByiQ23RP/odRBOTVjwp2cDyi6I= -github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= -github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc= -github.com/urfave/cli v0.0.0-20171014202726-7bc6a0acffa5/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= -github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= -github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/urfave/cli v1.22.2/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= -github.com/vishvananda/netlink v0.0.0-20181108222139-023a6dafdcdf/go.mod h1:+SR5DhBJrl6ZM7CoCKvpw5BKroDKQ+PJqOg65H/2ktk= -github.com/vishvananda/netlink v1.1.0/go.mod h1:cTgwzPIzzgDAYoQrMm0EdrjRUBkTqKYppBueQtXaqoE= -github.com/vishvananda/netlink v1.1.1-0.20201029203352-d40f9887b852/go.mod h1:twkDnbuQxJYemMlGd4JFIcuhgX83tXhKS2B/PRMpOho= -github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= -github.com/vishvananda/netns v0.0.0-20191106174202-0a2b9b5464df/go.mod h1:JP3t17pCcGlemwknint6hfoeCVQrEMVwxRLRjXpq+BU= -github.com/vishvananda/netns v0.0.0-20200728191858-db3c7e526aae/go.mod h1:DD4vA1DwXk04H54A1oHXtwZmA0grkVMdPxx/VGLCah0= -github.com/willf/bitset v1.1.11-0.20200630133818-d5bec3311243/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= -github.com/willf/bitset v1.1.11/go.mod h1:83CECat5yLh5zVOf4P1ErAgKA5UDvKtgyUABdr3+MjI= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= -github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= -github.com/xeipuuv/gojsonschema v0.0.0-20180618132009-1d523034197f/go.mod h1:5yf86TLmAcydyeJq5YvxkGPE2fm/u4myDekKRoLuqhs= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= -github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= -github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= -go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= -go.etcd.io/etcd v0.5.0-alpha.5.0.20200910180754-dd1b699fc489/go.mod h1:yVHk9ub3CSBatqGNg7GRmsnfLWtoW60w4eDYfh7vHDg= -go.mozilla.org/pkcs7 v0.0.0-20200128120323-432b2356ecb1/go.mod h1:SNgMg+EgDFwmvSmLRTNKC5fegJjB7v23qTQ0XLGUNHk= -go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= -go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8= -go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -golang.org/x/crypto v0.0.0-20171113213409-9f005a07e0d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181009213950-7c1a557ab941/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-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= 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.0.0-20200728195943-123391ffb6de/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20201002170205-7f63de1d35b0/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= -golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= -golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= -golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= -golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= -golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= -golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= -golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= -golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= -golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= -golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= -golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/crypto v0.35.0 h1:b15kiHdrGCHrP6LvwaQ3c03kgNhhiMgvlhxHQhmg2Xs= +golang.org/x/crypto v0.35.0/go.mod h1:dy7dXNW32cAb/6/PRuTNsix8T+vJAqvuIy5Bli/x0YQ= 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/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181011144130-49bb7cea24b1/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-20181220203305-927f97764cc3/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-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190619014844-b5b0513f8c1b/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-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191004110552-13f9640d40b9/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/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-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= -golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= -golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/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-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/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/sys v0.0.0-20180830151530-49385e6e1522/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-20180909124046-d0be0721c37e/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-20181116152217-5ac8a444bdc5/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-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190514135907-3a4b5fb9f71f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190522044717-8097e1b27ff5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190602015325-4c4f7f33c9ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190606203320-7fc4e5ec1444/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190812073006-9eafafc0a87e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191022100944-742c48ecaeb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191115151921-52ab43148777/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200120151820-655fe14d7479/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200217220822-9197077df867/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/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-20200622214017-ed371f2e16b4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200728102440-3e129f6d46b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200817155316-9781c653f443/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200909081042-eff7692f9009/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200916030750-2334cc1a136f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200922070232-aee5d888a860/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201112073958-5cba982894dd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201117170446-d9b008d0a637/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210426230700-d19ff857e887/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22 h1:RqytpXGR1iVNX7psjB3ff8y7sNFinVFvkx1c8SjBkio= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b h1:9zKuko04nR4gjZ4+DNjHqRlAJqbJETHwiNKDqTfOjfE= -golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= -golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.30.0 h1:QjkSwP/36a20jFYWkSue1YwXzLmsV5Gfq7Eiy72C1uc= +golang.org/x/sys v0.30.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/term v0.29.0 h1:L6pJp37ocefwRRtYPKSWOWzOtWSxVajvz2ldH/xi3iU= +golang.org/x/term v0.29.0/go.mod h1:6bl4lRlvVuDgSf3179VpIxBF0o10JUpXWOnI7nErv7s= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190624222133-a101b041ded4/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= -golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= 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/api v0.0.0-20160322025152-9bf6e6e569ff/go.mod h1:4mhQ8q/RsB7i+udVvVy5NUi08OU8ZlA0gRVgrF7VFY0= -google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= -google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= -google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= -google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= -google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= -google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= -google.golang.org/cloud v0.0.0-20151119220103-975617b05ea8/go.mod h1:0H1ncTHf11KCFhTc/+EFRbzSCOZx+VUbRMk55Yv5MYk= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190522204451-c2c4e71fbf69/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= -google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= -google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200117163144-32f20d992d24/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= -google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= -google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= -google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= -google.golang.org/genproto v0.0.0-20201110150050-8816d57aaa9a/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= -google.golang.org/grpc v0.0.0-20160317175043-d3ddb4469d5a/go.mod h1:yo6s7OP7yaDglbqo1J04qKzAhqBH6lvTonzMVmEdcZw= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.24.0/go.mod h1:XDChyiUovWa60DnaeDeZmSW86xtLtjtZbwvSiRnRtcA= -google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= -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.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= -google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= -google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -gopkg.in/airbrake/gobrake.v2 v2.0.9/go.mod h1:/h5ZAUhDkGaJfjzjKLSjv6zCL6O0LLBxU4K+aSYdM/U= -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 v1.0.0-20141024133853-64131543e789/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-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= -gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= -gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= -gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/square/go-jose.v2 v2.2.2/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/square/go-jose.v2 v2.3.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/square/go-jose.v2 v2.5.1/go.mod h1:M9dMgbHiYLoDGQrXy7OpJDJWiKiU//h+vD76mk0e1AI= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= -gopkg.in/yaml.v2 v2.2.1/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= -gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo= -gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk= -gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0= -gotest.tools/v3 v3.0.3/go.mod h1:Z7Lb0S5l+klDB31fvDQX8ss/FlKDxtlFlw3Oa8Ymbl8= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= -honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= -k8s.io/api v0.20.1/go.mod h1:KqwcCVogGxQY3nBlRpwt+wpAMF/KjaCc7RpywacvqUo= -k8s.io/api v0.20.4/go.mod h1:++lNL1AJMkDymriNniQsWRkMDzRaX2Y/POTUi8yvqYQ= -k8s.io/api v0.20.6/go.mod h1:X9e8Qag6JV/bL5G6bU8sdVRltWKmdHsFUGS3eVndqE8= -k8s.io/apimachinery v0.20.1/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= -k8s.io/apimachinery v0.20.4/go.mod h1:WlLqWAHZGg07AeltaI0MV5uk1Omp8xaN0JGLY6gkRpU= -k8s.io/apimachinery v0.20.6/go.mod h1:ejZXtW1Ra6V1O5H8xPBGz+T3+4gfkTCeExAHKU57MAc= -k8s.io/apiserver v0.20.1/go.mod h1:ro5QHeQkgMS7ZGpvf4tSMx6bBOgPfE+f52KwvXfScaU= -k8s.io/apiserver v0.20.4/go.mod h1:Mc80thBKOyy7tbvFtB4kJv1kbdD0eIH8k8vianJcbFM= -k8s.io/apiserver v0.20.6/go.mod h1:QIJXNt6i6JB+0YQRNcS0hdRHJlMhflFmsBDeSgT1r8Q= -k8s.io/client-go v0.20.1/go.mod h1:/zcHdt1TeWSd5HoUe6elJmHSQ6uLLgp4bIJHVEuy+/Y= -k8s.io/client-go v0.20.4/go.mod h1:LiMv25ND1gLUdBeYxBIwKpkSC5IsozMMmOOeSJboP+k= -k8s.io/client-go v0.20.6/go.mod h1:nNQMnOvEUEsOzRRFIIkdmYOjAZrC8bgq0ExboWSU1I0= -k8s.io/component-base v0.20.1/go.mod h1:guxkoJnNoh8LNrbtiQOlyp2Y2XFCZQmrcg2n/DeYNLk= -k8s.io/component-base v0.20.4/go.mod h1:t4p9EdiagbVCJKrQ1RsA5/V4rFQNDfRlevJajlGwgjI= -k8s.io/component-base v0.20.6/go.mod h1:6f1MPBAeI+mvuts3sIdtpjljHWBQ2cIy38oBIWMYnrM= -k8s.io/cri-api v0.17.3/go.mod h1:X1sbHmuXhwaHs9xxYffLqJogVsnI+f6cPRcgPel7ywM= -k8s.io/cri-api v0.20.1/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= -k8s.io/cri-api v0.20.4/go.mod h1:2JRbKt+BFLTjtrILYVqQK5jqhI+XNdF6UiGMgczeBCI= -k8s.io/cri-api v0.20.6/go.mod h1:ew44AjNXwyn1s0U4xCKGodU7J1HzBeZ1MpGrpa5r8Yc= -k8s.io/gengo v0.0.0-20200413195148-3a45101e95ac/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= -k8s.io/klog/v2 v2.0.0/go.mod h1:PBfzABfn139FHAV07az/IF9Wp1bkk3vpT2XSJ76fSDE= -k8s.io/klog/v2 v2.4.0/go.mod h1:Od+F08eJP+W3HUb4pSrPpgp9DGU4GzlpG/TmITuYh/Y= -k8s.io/kube-openapi v0.0.0-20201113171705-d219536bb9fd/go.mod h1:WOJ3KddDSol4tAGcJo0Tvi+dK12EcqSLqcWsryKMpfM= -k8s.io/kubernetes v1.13.0/go.mod h1:ocZa8+6APFNC2tX1DZASIbocyYT5jHzqFVsY5aoB7Jk= -k8s.io/utils v0.0.0-20201110183641-67b214c5f920/go.mod h1:jPW/WVKK9YHAvNhRxK0md/EJ228hCsBRufyofKtW8HA= -rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= -rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= -rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.14/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.0.15/go.mod h1:LEScyzhFmoF5pso/YSeBstl57mOzx9xlU9n85RGrDQg= -sigs.k8s.io/structured-merge-diff/v4 v4.0.2/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/structured-merge-diff/v4 v4.0.3/go.mod h1:bJZC9H9iH24zzfZ/41RGcq60oK1F7G282QMXDPYydCw= -sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= -sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= +gotest.tools/v3 v3.5.0 h1:Ljk6PdHdOhAb5aDMWXjDLMMhph+BpztA4v1QdqEW2eY= +gotest.tools/v3 v3.5.0/go.mod h1:isy3WKz7GK6uNw/sbHzfKBLvlvXwUyV06n6brMxxopU= diff --git a/internal/config/config.go b/internal/config/config.go index 03403022..38ac223c 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -7,18 +7,20 @@ import ( ) type Config struct { - Template string - Dest string - Watch bool - Wait *Wait - NotifyCmd string - NotifyOutput bool - NotifyContainers map[string]int - OnlyExposed bool - OnlyPublished bool - IncludeStopped bool - Interval int - KeepBlankLines bool + Template string + Dest string + Watch bool + Wait *Wait + NotifyCmd string + NotifyOutput bool + NotifyContainers map[string]int + NotifyContainersFilter map[string][]string + NotifyContainersSignal int + OnlyExposed bool + OnlyPublished bool + IncludeStopped bool + Interval int + KeepBlankLines bool } type ConfigFile struct { diff --git a/internal/context/address.go b/internal/context/address.go new file mode 100644 index 00000000..48368d12 --- /dev/null +++ b/internal/context/address.go @@ -0,0 +1,50 @@ +package context + +import ( + docker "github.com/fsouza/go-dockerclient" +) + +type Address struct { + IP string + IP6LinkLocal string + IP6Global string + Port string + HostPort string + Proto string + HostIP string +} + +func renderAddress(container *docker.Container, port docker.Port) Address { + return Address{ + IP: container.NetworkSettings.IPAddress, + IP6LinkLocal: container.NetworkSettings.LinkLocalIPv6Address, + IP6Global: container.NetworkSettings.GlobalIPv6Address, + Port: port.Port(), + Proto: port.Proto(), + } +} + +func GetContainerAddresses(container *docker.Container) []Address { + addresses := []Address{} + + for port, bindings := range container.NetworkSettings.Ports { + address := renderAddress(container, port) + + if len(bindings) > 0 { + address.HostPort = bindings[0].HostPort + address.HostIP = bindings[0].HostIP + } + + addresses = append(addresses, address) + } + + if len(addresses) == 0 { + // internal docker network has empty 'container.NetworkSettings.Ports' + for port := range container.Config.ExposedPorts { + address := renderAddress(container, port) + addresses = append(addresses, address) + } + } + + return addresses +} diff --git a/internal/context/address_test.go b/internal/context/address_test.go new file mode 100644 index 00000000..bfbb76b9 --- /dev/null +++ b/internal/context/address_test.go @@ -0,0 +1,116 @@ +package context + +import ( + "testing" + + docker "github.com/fsouza/go-dockerclient" + "github.com/stretchr/testify/assert" +) + +type FakePortBinding struct{} + +var httpPort = docker.Port("80/tcp") +var httpPortBinding = docker.PortBinding{ + HostIP: "100.100.100.100", + HostPort: "8080", +} + +var httpsPort = docker.Port("443/tcp") + +var httpTestPort = docker.Port("8080/tcp") +var httpsTestPort = docker.Port("8443/tcp") + +func TestGenerateContainerAddresses(t *testing.T) { + testContainer := &docker.Container{ + Config: &docker.Config{ + ExposedPorts: map[docker.Port]struct{}{}, + }, + NetworkSettings: &docker.NetworkSettings{ + IPAddress: "10.0.0.10", + LinkLocalIPv6Address: "24", + GlobalIPv6Address: "10.0.0.1", + Ports: map[docker.Port][]docker.PortBinding{}, + }, + } + testContainer.NetworkSettings.Ports[httpPort] = []docker.PortBinding{httpPortBinding} + testContainer.NetworkSettings.Ports[httpsPort] = []docker.PortBinding{} + + addresses := GetContainerAddresses(testContainer) + assert.Len(t, addresses, len(testContainer.NetworkSettings.Ports)) + assert.Contains(t, addresses, Address{ + IP: "10.0.0.10", + IP6LinkLocal: "24", + IP6Global: "10.0.0.1", + Port: "80", + Proto: "tcp", + HostIP: "100.100.100.100", + HostPort: "8080", + }) + assert.Contains(t, addresses, Address{ + IP: "10.0.0.10", + IP6LinkLocal: "24", + IP6Global: "10.0.0.1", + Port: "443", + Proto: "tcp", + HostIP: "", + HostPort: "", + }) +} + +func TestGenerateContainerAddressesWithExposedPorts(t *testing.T) { + testContainer := &docker.Container{ + Config: &docker.Config{ + ExposedPorts: map[docker.Port]struct{}{}, + }, + NetworkSettings: &docker.NetworkSettings{ + IPAddress: "10.0.0.10", + LinkLocalIPv6Address: "24", + GlobalIPv6Address: "10.0.0.1", + Ports: map[docker.Port][]docker.PortBinding{}, + }, + } + testContainer.NetworkSettings.Ports[httpPort] = []docker.PortBinding{} + testContainer.NetworkSettings.Ports[httpsPort] = []docker.PortBinding{} + testContainer.Config.ExposedPorts[httpPort] = struct{}{} + testContainer.Config.ExposedPorts[httpsPort] = struct{}{} + testContainer.Config.ExposedPorts[httpTestPort] = struct{}{} + + assert.Len(t, GetContainerAddresses(testContainer), 2) +} + +func TestGenerateContainerAddressesWithNoPorts(t *testing.T) { + testContainer := &docker.Container{ + Config: &docker.Config{ + ExposedPorts: map[docker.Port]struct{}{}, + }, + NetworkSettings: &docker.NetworkSettings{ + IPAddress: "10.0.0.10", + LinkLocalIPv6Address: "24", + GlobalIPv6Address: "10.0.0.1", + Ports: map[docker.Port][]docker.PortBinding{}, + }, + } + testContainer.Config.ExposedPorts[httpTestPort] = FakePortBinding{} + testContainer.Config.ExposedPorts[httpsTestPort] = FakePortBinding{} + + addresses := GetContainerAddresses(testContainer) + assert.Len(t, addresses, len(testContainer.Config.ExposedPorts)) + assert.Contains(t, addresses, Address{ + IP: "10.0.0.10", + IP6LinkLocal: "24", + IP6Global: "10.0.0.1", + Port: "8080", + Proto: "tcp", + HostIP: "", + HostPort: "", + }) + assert.Contains(t, addresses, Address{ + IP: "10.0.0.10", + IP6LinkLocal: "24", + IP6Global: "10.0.0.1", + Port: "8443", + Proto: "tcp", + HostIP: "", + HostPort: "", + }) +} diff --git a/internal/context/context.go b/internal/context/context.go index 1d5e75a8..2bb49275 100644 --- a/internal/context/context.go +++ b/internal/context/context.go @@ -6,6 +6,7 @@ import ( "os" "regexp" "sync" + "time" docker "github.com/fsouza/go-dockerclient" "github.com/nginx-proxy/docker-gen/internal/utils" @@ -51,16 +52,6 @@ func SetDockerEnv(d *docker.Env) { dockerEnv = d } -type Address struct { - IP string - IP6LinkLocal string - IP6Global string - Port string - HostPort string - Proto string - HostIP string -} - type Network struct { IP string Name string @@ -71,6 +62,7 @@ type Network struct { MacAddress string GlobalIPv6PrefixLen int IPPrefixLen int + Internal bool } type Volume struct { @@ -81,15 +73,22 @@ type Volume struct { type State struct { Running bool + Health Health +} + +type Health struct { + Status string } type RuntimeContainer struct { ID string + Created time.Time Addresses []Address Networks []Network Gateway string Name string Hostname string + NetworkMode string Image DockerImage Env map[string]string Volumes map[string]Volume diff --git a/internal/context/context_test.go b/internal/context/context_test.go index e34098ec..f571f665 100644 --- a/internal/context/context_test.go +++ b/internal/context/context_test.go @@ -2,7 +2,6 @@ package context import ( "fmt" - "io/ioutil" "os" "testing" @@ -74,7 +73,7 @@ func TestGetCurrentContainerID(t *testing.T) { var filepaths []string // Create temporary files with test content for _, key := range fileKeys { - file, err := ioutil.TempFile("", key) + file, err := os.CreateTemp("", key) if err != nil { t.Fatal(err) } @@ -114,7 +113,7 @@ func TestGetCurrentContainerIDMountInfo(t *testing.T) { var filepaths []string // Create temporary files with test content for _, key := range fileKeys { - file, err := ioutil.TempFile("", key) + file, err := os.CreateTemp("", key) if err != nil { t.Fatal(err) } diff --git a/internal/dockerclient/docker_cli_test.go b/internal/dockerclient/docker_cli_test.go index 24de3508..716e6f7e 100644 --- a/internal/dockerclient/docker_cli_test.go +++ b/internal/dockerclient/docker_cli_test.go @@ -2,7 +2,6 @@ package dockerclient import ( "fmt" - "io/ioutil" "os" "testing" @@ -236,7 +235,7 @@ func TestTlsEnabled(t *testing.T) { } // Create temporary files for key := range filepaths { - file, err := ioutil.TempFile("", key) + file, err := os.CreateTemp("", key) if err != nil { t.Fatal(err) } diff --git a/internal/generator/generator.go b/internal/generator/generator.go index 6f0d08df..e845fb01 100644 --- a/internal/generator/generator.go +++ b/internal/generator/generator.go @@ -26,6 +26,7 @@ type generator struct { TLSVerify bool TLSCert, TLSCaCert, TLSKey string All bool + EventFilter map[string][]string wg sync.WaitGroup retry bool @@ -40,6 +41,8 @@ type GeneratorConfig struct { TLSVerify bool All bool + EventFilter map[string][]string + ConfigFile config.ConfigFile } @@ -63,15 +66,16 @@ func NewGenerator(gc GeneratorConfig) (*generator, error) { context.SetDockerEnv(apiVersion) return &generator{ - Client: client, - Endpoint: gc.Endpoint, - TLSVerify: gc.TLSVerify, - TLSCert: gc.TLSCert, - TLSCaCert: gc.TLSCACert, - TLSKey: gc.TLSKey, - All: gc.All, - Configs: gc.ConfigFile, - retry: true, + Client: client, + Endpoint: gc.Endpoint, + TLSVerify: gc.TLSVerify, + TLSCert: gc.TLSCert, + TLSCaCert: gc.TLSCACert, + TLSKey: gc.TLSKey, + All: gc.All, + EventFilter: gc.EventFilter, + Configs: gc.ConfigFile, + retry: true, }, nil } @@ -103,14 +107,15 @@ func (g *generator) generateFromSignals() { go func() { defer g.wg.Done() - sigChan := newSignalChannel() + sigChan, cleanup := newSignalChannel() + defer cleanup() for { sig := <-sigChan log.Printf("Received signal: %s\n", sig) switch sig { case syscall.SIGHUP: g.generateFromContainers() - case syscall.SIGQUIT, syscall.SIGKILL, syscall.SIGTERM, syscall.SIGINT: + case syscall.SIGTERM, syscall.SIGINT: // exit when context is done return } @@ -131,7 +136,8 @@ func (g *generator) generateFromContainers() { continue } g.runNotifyCmd(config) - g.sendSignalToContainer(config) + g.sendSignalToContainers(config) + g.sendSignalToFilteredContainers(config) } } @@ -148,7 +154,8 @@ func (g *generator) generateAtInterval() { go func(cfg config.Config) { defer g.wg.Done() - sigChan := newSignalChannel() + sigChan, cleanup := newSignalChannel() + defer cleanup() for { select { case <-ticker.C: @@ -160,11 +167,12 @@ func (g *generator) generateAtInterval() { // ignore changed return value. always run notify command template.GenerateFile(cfg, containers) g.runNotifyCmd(cfg) - g.sendSignalToContainer(cfg) + g.sendSignalToContainers(cfg) + g.sendSignalToFilteredContainers(cfg) case sig := <-sigChan: log.Printf("Received signal: %s\n", sig) switch sig { - case syscall.SIGQUIT, syscall.SIGKILL, syscall.SIGTERM, syscall.SIGINT: + case syscall.SIGTERM, syscall.SIGINT: ticker.Stop() return } @@ -190,11 +198,11 @@ func (g *generator) generateFromEvents() { } g.wg.Add(1) + watcher := make(chan *docker.APIEvents, 100) + watchers = append(watchers, watcher) - go func(cfg config.Config, watcher chan *docker.APIEvents) { + go func(cfg config.Config) { defer g.wg.Done() - watchers = append(watchers, watcher) - debouncedChan := newDebounceChannel(watcher, cfg.Wait) for range debouncedChan { containers, err := g.getContainers() @@ -208,16 +216,18 @@ func (g *generator) generateFromEvents() { continue } g.runNotifyCmd(cfg) - g.sendSignalToContainer(cfg) + g.sendSignalToContainers(cfg) + g.sendSignalToFilteredContainers(cfg) } - }(cfg, make(chan *docker.APIEvents, 100)) + }(cfg) } // maintains docker client connection and passes events to watchers go func() { // channel will be closed by go-dockerclient eventChan := make(chan *docker.APIEvents, 100) - sigChan := newSignalChannel() + sigChan, cleanup := newSignalChannel() + defer cleanup() for { watching := false @@ -243,7 +253,11 @@ func (g *generator) generateFromEvents() { break } if !watching { - err := client.AddEventListener(eventChan) + options := docker.EventsOptions{ + Filters: g.EventFilter, + } + + err := client.AddEventListenerWithOptions(options, eventChan) if err != nil && err != docker.ErrListenerAlreadyExists { log.Printf("Error registering docker event listener: %s", err) time.Sleep(10 * time.Second) @@ -275,12 +289,11 @@ func (g *generator) generateFromEvents() { time.Sleep(10 * time.Second) break } - if event.Status == "start" || event.Status == "stop" || event.Status == "die" { - log.Printf("Received event %s for container %s", event.Status, event.ID[:12]) - // fanout event to all watchers - for _, watcher := range watchers { - watcher <- event - } + + log.Printf("Received event %s for %s %s", event.Action, event.Type, event.Actor.ID[:12]) + // fanout event to all watchers + for _, watcher := range watchers { + watcher <- event } case <-time.After(10 * time.Second): // check for docker liveness @@ -296,7 +309,7 @@ func (g *generator) generateFromEvents() { case sig := <-sigChan: log.Printf("Received signal: %s\n", sig) switch sig { - case syscall.SIGQUIT, syscall.SIGKILL, syscall.SIGTERM, syscall.SIGINT: + case syscall.SIGTERM, syscall.SIGINT: // close all watchers and exit for _, watcher := range watchers { close(watcher) @@ -329,28 +342,50 @@ func (g *generator) runNotifyCmd(config config.Config) { } } -func (g *generator) sendSignalToContainer(config config.Config) { +func (g *generator) sendSignalToContainer(container string, signal int) { + log.Printf("Sending container '%s' signal '%v'", container, signal) + + if signal == -1 { + if err := g.Client.RestartContainer(container, 10); err != nil { + log.Printf("Error sending restarting container: %s", err) + } + return + } + + killOpts := docker.KillContainerOptions{ + ID: container, + Signal: docker.Signal(signal), + } + if err := g.Client.KillContainer(killOpts); err != nil { + log.Printf("Error sending signal to container: %s", err) + } +} + +func (g *generator) sendSignalToContainers(config config.Config) { if len(config.NotifyContainers) < 1 { return } for container, signal := range config.NotifyContainers { - log.Printf("Sending container '%s' signal '%v'", container, signal) + g.sendSignalToContainer(container, signal) + } +} - if signal == -1 { - if err := g.Client.RestartContainer(container, 10); err != nil { - log.Printf("Error sending restarting container: %s", err) - } - return - } +func (g *generator) sendSignalToFilteredContainers(config config.Config) { + if len(config.NotifyContainersFilter) < 1 { + return + } - killOpts := docker.KillContainerOptions{ - ID: container, - Signal: docker.Signal(signal), - } - if err := g.Client.KillContainer(killOpts); err != nil { - log.Printf("Error sending signal to container: %s", err) - } + containers, err := g.Client.ListContainers(docker.ListContainersOptions{ + Filters: config.NotifyContainersFilter, + }) + if err != nil { + log.Printf("Error getting containers: %s", err) + return + } + + for _, container := range containers { + g.sendSignalToContainer(container.ID, config.NotifyContainersSignal) } } @@ -370,6 +405,15 @@ func (g *generator) getContainers() ([]*context.RuntimeContainer, error) { return nil, err } + apiNetworks, err := g.Client.ListNetworks() + if err != nil { + return nil, err + } + networks := make(map[string]docker.Network) + for _, apiNetwork := range apiNetworks { + networks[apiNetwork.Name] = apiNetwork + } + containers := []*context.RuntimeContainer{} for _, apiContainer := range apiContainers { opts := docker.InspectContainerOptions{ID: apiContainer.ID} @@ -381,7 +425,8 @@ func (g *generator) getContainers() ([]*context.RuntimeContainer, error) { registry, repository, tag := dockerclient.SplitDockerImage(container.Config.Image) runtimeContainer := &context.RuntimeContainer{ - ID: container.ID, + ID: container.ID, + Created: container.Created, Image: context.DockerImage{ Registry: registry, Repository: repository, @@ -389,10 +434,14 @@ func (g *generator) getContainers() ([]*context.RuntimeContainer, error) { }, State: context.State{ Running: container.State.Running, + Health: context.Health{ + Status: container.State.Health.Status, + }, }, Name: strings.TrimLeft(container.Name, "/"), Hostname: container.Config.Hostname, Gateway: container.NetworkSettings.Gateway, + NetworkMode: container.HostConfig.NetworkMode, Addresses: []context.Address{}, Networks: []context.Network{}, Env: make(map[string]string), @@ -403,22 +452,10 @@ func (g *generator) getContainers() ([]*context.RuntimeContainer, error) { IP6LinkLocal: container.NetworkSettings.LinkLocalIPv6Address, IP6Global: container.NetworkSettings.GlobalIPv6Address, } - for k, v := range container.NetworkSettings.Ports { - address := context.Address{ - IP: container.NetworkSettings.IPAddress, - IP6LinkLocal: container.NetworkSettings.LinkLocalIPv6Address, - IP6Global: container.NetworkSettings.GlobalIPv6Address, - Port: k.Port(), - Proto: k.Proto(), - } - if len(v) > 0 { - address.HostPort = v[0].HostPort - address.HostIP = v[0].HostIP - } - runtimeContainer.Addresses = append(runtimeContainer.Addresses, - address) - } + adresses := context.GetContainerAddresses(container) + runtimeContainer.Addresses = append(runtimeContainer.Addresses, adresses...) + for k, v := range container.NetworkSettings.Networks { network := context.Network{ IP: v.IPAddress, @@ -430,11 +467,13 @@ func (g *generator) getContainers() ([]*context.RuntimeContainer, error) { MacAddress: v.MacAddress, GlobalIPv6PrefixLen: v.GlobalIPv6PrefixLen, IPPrefixLen: v.IPPrefixLen, + Internal: networks[k].Internal, } runtimeContainer.Networks = append(runtimeContainer.Networks, network) } + for k, v := range container.Volumes { runtimeContainer.Volumes[k] = context.Volume{ Path: k, @@ -469,11 +508,10 @@ func (g *generator) getContainers() ([]*context.RuntimeContainer, error) { } -func newSignalChannel() <-chan os.Signal { +func newSignalChannel() (<-chan os.Signal, func()) { sig := make(chan os.Signal, 1) - signal.Notify(sig, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT) - - return sig + signal.Notify(sig, syscall.SIGHUP, syscall.SIGINT, syscall.SIGTERM) + return sig, func() { signal.Stop(sig) } } func newDebounceChannel(input chan *docker.APIEvents, wait *config.Wait) chan *docker.APIEvents { diff --git a/internal/generator/generator_test.go b/internal/generator/generator_test.go index f82b43fd..bbf5c814 100644 --- a/internal/generator/generator_test.go +++ b/internal/generator/generator_test.go @@ -4,11 +4,12 @@ import ( "bufio" "encoding/json" "fmt" - "io/ioutil" + "io" "log" "net/http" "os" "strings" + "sync/atomic" "testing" "time" @@ -20,17 +21,16 @@ import ( ) func TestGenerateFromEvents(t *testing.T) { - log.SetOutput(ioutil.Discard) + log.SetOutput(io.Discard) containerID := "8dfafdbc3a40" - counter := 0 + var counter atomic.Int32 eventsResponse := ` -{"status":"start","id":"8dfafdbc3a40","from":"base:latest","time":1374067924} -{"status":"stop","id":"8dfafdbc3a40","from":"base:latest","time":1374067966} -{"status":"start","id":"8dfafdbc3a40","from":"base:latest","time":1374067970} -{"status":"destroy","id":"8dfafdbc3a40","from":"base:latest","time":1374067990}` - infoResponse := `{"Containers":1,"Images":1,"Debug":0,"NFd":11,"NGoroutines":21,"MemoryLimit":1,"SwapLimit":0}` - versionResponse := `{"Version":"1.8.0","Os":"Linux","KernelVersion":"3.18.5-tinycore64","GoVersion":"go1.4.1","GitCommit":"a8a31ef","Arch":"amd64","ApiVersion":"1.19"}` +{"Type":"container","Action":"start","Actor": {"ID":"8dfafdbc3a40"},"Time":1374067924} +{"Type":"container","Action":"stop","Actor": {"ID":"8dfafdbc3a40"},"Time":1374067966} +{"Type":"container","Action":"start","Actor": {"ID":"8dfafdbc3a40"},"Time":1374067970}` + infoResponse := `{"Containers":1,"Images":1,"Debug":false,"NFd":11,"NGoroutines":21,"MemoryLimit":true,"SwapLimit":false}` + versionResponse := `{"Version":"19.03.12","Os":"Linux","KernelVersion":"4.19.76-linuxkit","GoVersion":"go1.13.14","GitCommit":"48a66213fe","Arch":"amd64","ApiVersion":"1.40"}` server, _ := dockertest.NewServer("127.0.0.1:0", nil, nil) server.CustomHandler("/events", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -38,7 +38,7 @@ func TestGenerateFromEvents(t *testing.T) { for rsc.Scan() { w.Write([]byte(rsc.Text())) w.(http.Flusher).Flush() - time.Sleep(15 * time.Millisecond) + time.Sleep(150 * time.Millisecond) } time.Sleep(500 * time.Millisecond) })) @@ -67,7 +67,7 @@ func TestGenerateFromEvents(t *testing.T) { json.NewEncoder(w).Encode(result) })) server.CustomHandler(fmt.Sprintf("/containers/%s/json", containerID), http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - counter++ + counter := counter.Add(1) container := docker.Container{ Name: "docker-gen-test", ID: containerID, @@ -82,11 +82,19 @@ func TestGenerateFromEvents(t *testing.T) { Cmd: []string{"/bin/sh"}, Image: "base:latest", }, + HostConfig: &docker.HostConfig{ + NetworkMode: "container:d246e2c9e3d465d96359c942e91de493f6d51a01ba33900d865180d64c34ee91", + }, State: docker.State{ Running: true, Pid: 400, ExitCode: 0, StartedAt: time.Now(), + Health: docker.Health{ + Status: "healthy", + FailingStreak: 5, + Log: []docker.HealthCheck{}, + }, }, Image: "0ff407d5a7d9ed36acdf3e75de8cc127afecc9af234d05486be2981cdc01a38d", NetworkSettings: &docker.NetworkSettings{ @@ -111,7 +119,7 @@ func TestGenerateFromEvents(t *testing.T) { } client.SkipServerVersionCheck = true - tmplFile, err := ioutil.TempFile(os.TempDir(), "docker-gen-tmpl") + tmplFile, err := os.CreateTemp(os.TempDir(), "docker-gen-tmpl") if err != nil { t.Errorf("Failed to create temp file: %v\n", err) } @@ -119,14 +127,14 @@ func TestGenerateFromEvents(t *testing.T) { tmplFile.Close() os.Remove(tmplFile.Name()) }() - err = ioutil.WriteFile(tmplFile.Name(), []byte("{{range $key, $value := .}}{{$value.ID}}.{{$value.Env.COUNTER}}{{end}}"), 0644) + err = os.WriteFile(tmplFile.Name(), []byte("{{range $key, $value := .}}{{$value.ID}}.{{$value.Env.COUNTER}}{{end}}"), 0644) if err != nil { t.Errorf("Failed to write to temp file: %v\n", err) } var destFiles []*os.File for i := 0; i < 4; i++ { - destFile, err := ioutil.TempFile(os.TempDir(), "docker-gen-out") + destFile, err := os.CreateTemp(os.TempDir(), "docker-gen-out") if err != nil { t.Errorf("Failed to create temp file: %v\n", err) } @@ -165,13 +173,13 @@ func TestGenerateFromEvents(t *testing.T) { Template: tmplFile.Name(), Dest: destFiles[2].Name(), Watch: true, - Wait: &config.Wait{Min: 20 * time.Millisecond, Max: 25 * time.Millisecond}, + Wait: &config.Wait{Min: 200 * time.Millisecond, Max: 250 * time.Millisecond}, }, { Template: tmplFile.Name(), Dest: destFiles[3].Name(), Watch: true, - Wait: &config.Wait{Min: 25 * time.Millisecond, Max: 100 * time.Millisecond}, + Wait: &config.Wait{Min: 250 * time.Millisecond, Max: 1 * time.Second}, }, }, }, @@ -188,12 +196,12 @@ func TestGenerateFromEvents(t *testing.T) { // The counter is incremented in each output file in the following sequence: // - // init 0ms 5ms 10ms 15ms 20ms 25ms 30ms 35ms 40ms 45ms 50ms 55ms + // init 150ms 200ms 250ms 300ms 350ms 400ms 450ms 500ms 550ms 600ms 650ms 700ms // ├──────╫──────┼──────┼──────╫──────┼──────┼──────╫──────┼──────┼──────┼──────┼──────┤ // File0 ├─ 1 ║ ║ ║ // File1 ├─ 1 ╟─ 2 ╟─ 3 ╟─ 5 - // File2 ├─ 1 ╟───── max (25ms) ───║───────────> 4 ╟─────── min (20ms) ──────> 6 - // File3 └─ 1 ╟──────────────────> ╟──────────────────> ╟─────────── min (25ms) ─────────> 7 + // File2 ├─ 1 ╟───── max (250ms) ──║───────────> 4 ╟─────── min (200ms) ─────> 6 + // File3 └─ 1 ╟──────────────────> ╟──────────────────> ╟─────────── min (250ms) ────────> 7 // ┌───╨───┐ ┌───╨──┐ ┌───╨───┐ // │ start │ │ stop │ │ start │ // └───────┘ └──────┘ └───────┘ @@ -201,7 +209,7 @@ func TestGenerateFromEvents(t *testing.T) { expectedCounters := []int{1, 5, 6, 7} for i, counter := range expectedCounters { - value, _ = ioutil.ReadFile(destFiles[i].Name()) + value, _ = os.ReadFile(destFiles[i].Name()) expected = fmt.Sprintf("%s.%d", containerID, counter) if string(value) != expected { t.Errorf("expected: %s. got: %s", expected, value) diff --git a/internal/template/functions.go b/internal/template/functions.go index 4cc11721..191846d9 100644 --- a/internal/template/functions.go +++ b/internal/template/functions.go @@ -1,28 +1,14 @@ package template import ( - "bytes" - "crypto/sha1" - "encoding/json" - "errors" "fmt" - "io" - "io/ioutil" "log" + "os" "reflect" + "regexp" "strings" ) -// hasPrefix returns whether a given string is a prefix of another string -func hasPrefix(prefix, s string) bool { - return strings.HasPrefix(s, prefix) -} - -// hasSuffix returns whether a given string is a suffix of another string -func hasSuffix(suffix, s string) bool { - return strings.HasSuffix(s, suffix) -} - func keys(input interface{}) (interface{}, error) { if input == nil { return nil, nil @@ -42,6 +28,14 @@ func keys(input interface{}) (interface{}, error) { return k, nil } +func include(file string) string { + data, err := os.ReadFile(file) + if err != nil { + return "" + } + return string(data) +} + func intersect(l1, l2 []string) []string { m := make(map[string]bool) m2 := make(map[string]bool) @@ -60,6 +54,12 @@ func intersect(l1, l2 []string) []string { return keys } +// comment prefix each line of the source string with the provided comment delimiter string +func comment(delimiter string, source string) string { + regexPattern := regexp.MustCompile(`(?m)^`) + return regexPattern.ReplaceAllString(source, delimiter) +} + func contains(input interface{}, key interface{}) bool { if input == nil { return false @@ -77,66 +77,6 @@ func contains(input interface{}, key interface{}) bool { return false } -func dict(values ...interface{}) (map[string]interface{}, error) { - if len(values)%2 != 0 { - return nil, errors.New("invalid dict call") - } - dict := make(map[string]interface{}, len(values)/2) - for i := 0; i < len(values); i += 2 { - key, ok := values[i].(string) - if !ok { - return nil, errors.New("dict keys must be strings") - } - dict[key] = values[i+1] - } - return dict, nil -} - -func hashSha1(input string) string { - h := sha1.New() - io.WriteString(h, input) - return fmt.Sprintf("%x", h.Sum(nil)) -} - -func marshalJson(input interface{}) (string, error) { - var buf bytes.Buffer - enc := json.NewEncoder(&buf) - if err := enc.Encode(input); err != nil { - return "", err - } - return strings.TrimSuffix(buf.String(), "\n"), nil -} - -func unmarshalJson(input string) (interface{}, error) { - var v interface{} - if err := json.Unmarshal([]byte(input), &v); err != nil { - return nil, err - } - return v, nil -} - -// arrayFirst returns first item in the array or nil if the -// input is nil or empty -func arrayFirst(input interface{}) interface{} { - if input == nil { - return nil - } - - arr := reflect.ValueOf(input) - - if arr.Len() == 0 { - return nil - } - - return arr.Index(0).Interface() -} - -// arrayLast returns last item in the array -func arrayLast(input interface{}) interface{} { - arr := reflect.ValueOf(input) - return arr.Index(arr.Len() - 1).Interface() -} - // arrayClosest find the longest matching substring in values // that matches input func arrayClosest(values []string, input string) string { @@ -152,7 +92,7 @@ func arrayClosest(values []string, input string) string { // dirList returns a list of files in the specified path func dirList(path string) ([]string, error) { names := []string{} - files, err := ioutil.ReadDir(path) + files, err := os.ReadDir(path) if err != nil { log.Printf("Template error: %v", err) return names, nil @@ -173,31 +113,6 @@ func coalesce(input ...interface{}) interface{} { return nil } -// trimPrefix returns a string without the prefix, if present -func trimPrefix(prefix, s string) string { - return strings.TrimPrefix(s, prefix) -} - -// trimSuffix returns a string without the suffix, if present -func trimSuffix(suffix, s string) string { - return strings.TrimSuffix(s, suffix) -} - -// trim returns the string without leading or trailing whitespace -func trim(s string) string { - return strings.TrimSpace(s) -} - -// toLower return the string in lower case -func toLower(s string) string { - return strings.ToLower(s) -} - -// toUpper return the string in upper case -func toUpper(s string) string { - return strings.ToUpper(s) -} - // when returns the trueValue when the condition is true and the falseValue otherwise func when(condition bool, trueValue, falseValue interface{}) interface{} { if condition { diff --git a/internal/template/functions_test.go b/internal/template/functions_test.go index 7ad3a8fa..04e16561 100644 --- a/internal/template/functions_test.go +++ b/internal/template/functions_test.go @@ -1,18 +1,32 @@ package template import ( - "bytes" - "encoding/json" - "io/ioutil" "os" - "path" + "path/filepath" "reflect" "testing" - "github.com/nginx-proxy/docker-gen/internal/context" "github.com/stretchr/testify/assert" ) +func TestComment(t *testing.T) { + env := map[string]string{ + "bar": "baz", + "foo": "test", + } + + expected := `# { +# "bar": "baz", +# "foo": "test" +# }` + + tests := templateTestList{ + {`{{toPrettyJson . | comment "# "}}`, env, expected}, + } + + tests.run(t) +} + func TestContainsString(t *testing.T) { env := map[string]string{ "PORT": "1234", @@ -47,7 +61,7 @@ func TestKeys(t *testing.T) { {`{{range (keys $)}}{{.}}{{end}}`, env, `VIRTUAL_HOST`}, } - tests.run(t, "keys") + tests.run(t) } func TestKeysEmpty(t *testing.T) { @@ -78,6 +92,34 @@ func TestKeysNil(t *testing.T) { } } +func TestInclude(t *testing.T) { + data := include("some_random_file") + assert.Equal(t, "", data) + + f, err := os.CreateTemp("", "docker-gen-test-temp-file") + if err != nil { + t.Fatal(err) + } + + defer func() { + f.Close() + os.Remove(f.Name()) + }() + + err = f.Chmod(0o644) + if err != nil { + t.Fatal(err) + } + + _, err = f.WriteString("some string") + if err != nil { + t.Fatal(err) + } + + data = include(f.Name()) + assert.Equal(t, "some string", data) +} + func TestIntersect(t *testing.T) { i := intersect([]string{"foo.fo.com", "bar.com"}, []string{"foo.bar.com"}) assert.Len(t, i, 0, "Expected no match") @@ -92,22 +134,6 @@ func TestIntersect(t *testing.T) { assert.Len(t, i, 2, "Expected exactly two matches") } -func TestHasPrefix(t *testing.T) { - const prefix = "tcp://" - const str = "tcp://127.0.0.1:2375" - if !hasPrefix(prefix, str) { - t.Fatalf("expected %s to have prefix %s", str, prefix) - } -} - -func TestHasSuffix(t *testing.T) { - const suffix = ".local" - const str = "myhost.local" - if !hasSuffix(suffix, str) { - t.Fatalf("expected %s to have suffix %s", str, suffix) - } -} - func TestSplitN(t *testing.T) { tests := templateTestList{ {`{{index (splitN . "/" 2) 0}}`, "example.com/path", `example.com`}, @@ -116,128 +142,7 @@ func TestSplitN(t *testing.T) { {`{{len (splitN . "/" 2)}}`, "example.com", `1`}, } - tests.run(t, "splitN") -} - -func TestTrimPrefix(t *testing.T) { - const prefix = "tcp://" - const str = "tcp://127.0.0.1:2375" - const trimmed = "127.0.0.1:2375" - got := trimPrefix(prefix, str) - if got != trimmed { - t.Fatalf("expected trimPrefix(%s,%s) to be %s, got %s", prefix, str, trimmed, got) - } -} - -func TestTrimSuffix(t *testing.T) { - const suffix = ".local" - const str = "myhost.local" - const trimmed = "myhost" - got := trimSuffix(suffix, str) - if got != trimmed { - t.Fatalf("expected trimSuffix(%s,%s) to be %s, got %s", suffix, str, trimmed, got) - } -} - -func TestTrim(t *testing.T) { - const str = " myhost.local " - const trimmed = "myhost.local" - got := trim(str) - if got != trimmed { - t.Fatalf("expected trim(%s) to be %s, got %s", str, trimmed, got) - } -} - -func TestToLower(t *testing.T) { - const str = ".RaNd0m StrinG_" - const lowered = ".rand0m string_" - assert.Equal(t, lowered, toLower(str), "Unexpected value from toLower()") -} - -func TestToUpper(t *testing.T) { - const str = ".RaNd0m StrinG_" - const uppered = ".RAND0M STRING_" - assert.Equal(t, uppered, toUpper(str), "Unexpected value from toUpper()") -} - -func TestDict(t *testing.T) { - containers := []*context.RuntimeContainer{ - { - Env: map[string]string{ - "VIRTUAL_HOST": "demo1.localhost", - }, - ID: "1", - }, - { - Env: map[string]string{ - "VIRTUAL_HOST": "demo1.localhost,demo3.localhost", - }, - ID: "2", - }, - { - Env: map[string]string{ - "VIRTUAL_HOST": "demo2.localhost", - }, - ID: "3", - }, - } - d, err := dict("/", containers) - if err != nil { - t.Fatal(err) - } - if d["/"] == nil { - t.Fatalf("did not find containers in dict: %s", d) - } - if d["MISSING"] != nil { - t.Fail() - } -} - -func TestSha1(t *testing.T) { - sum := hashSha1("/path") - if sum != "4f26609ad3f5185faaa9edf1e93aa131e2131352" { - t.Fatal("Incorrect SHA1 sum") - } -} - -func TestJson(t *testing.T) { - containers := []*context.RuntimeContainer{ - { - Env: map[string]string{ - "VIRTUAL_HOST": "demo1.localhost", - }, - ID: "1", - }, - { - Env: map[string]string{ - "VIRTUAL_HOST": "demo1.localhost,demo3.localhost", - }, - ID: "2", - }, - { - Env: map[string]string{ - "VIRTUAL_HOST": "demo2.localhost", - }, - ID: "3", - }, - } - output, err := marshalJson(containers) - if err != nil { - t.Fatal(err) - } - - buf := bytes.NewBufferString(output) - dec := json.NewDecoder(buf) - if err != nil { - t.Fatal(err) - } - var decoded []*context.RuntimeContainer - if err := dec.Decode(&decoded); err != nil { - t.Fatal(err) - } - if len(decoded) != len(containers) { - t.Fatalf("Incorrect unmarshaled container count. Expected %d, got %d.", len(containers), len(decoded)) - } + tests.run(t) } func TestParseJson(t *testing.T) { @@ -250,7 +155,7 @@ func TestParseJson(t *testing.T) { {`{{index (parseJson . | first) "enabled"}}`, `[{"enabled":true}]`, `true`}, } - tests.run(t, "parseJson") + tests.run(t) } func TestQueryEscape(t *testing.T) { @@ -261,7 +166,7 @@ func TestQueryEscape(t *testing.T) { {`{{queryEscape .}}`, `~^example\.com(\..*\.xip\.io)?$`, `~%5Eexample%5C.com%28%5C..%2A%5C.xip%5C.io%29%3F%24`}, } - tests.run(t, "queryEscape") + tests.run(t) } func TestArrayClosestExact(t *testing.T) { @@ -299,7 +204,7 @@ func TestWhen(t *testing.T) { {`{{ when (not (eq .StringValue "foo")) "first" "second" | print }}`, context, `second`}, } - tests.run(t, "when") + tests.run(t) } func TestWhenTrue(t *testing.T) { @@ -316,7 +221,7 @@ func TestWhenFalse(t *testing.T) { } func TestDirList(t *testing.T) { - dir, err := ioutil.TempDir("", "dirList") + dir, err := os.MkdirTemp("", "dirList") if err != nil { t.Fatal(err) } @@ -329,7 +234,7 @@ func TestDirList(t *testing.T) { } // Create temporary files for key := range files { - file, err := ioutil.TempFile(dir, key) + file, err := os.CreateTemp(dir, key) if err != nil { t.Fatal(err) } @@ -338,9 +243,9 @@ func TestDirList(t *testing.T) { } expected := []string{ - path.Base(files["aaa"]), - path.Base(files["bbb"]), - path.Base(files["ccc"]), + filepath.Base(files["aaa"]), + filepath.Base(files["bbb"]), + filepath.Base(files["ccc"]), } filesList, _ := dirList(dir) diff --git a/internal/template/groupby.go b/internal/template/groupby.go index f8eae67d..6b4f988d 100644 --- a/internal/template/groupby.go +++ b/internal/template/groupby.go @@ -2,7 +2,6 @@ package template import ( "fmt" - "reflect" "strings" "github.com/nginx-proxy/docker-gen/internal/context" @@ -18,7 +17,7 @@ func generalizedGroupBy(funcName string, entries interface{}, getValue func(inte groups := make(map[string][]interface{}) for i := 0; i < entriesVal.Len(); i++ { - v := reflect.Indirect(entriesVal.Index(i)).Interface() + v := entriesVal.Index(i).Interface() value, err := getValue(v) if err != nil { return nil, err @@ -53,6 +52,20 @@ func groupBy(entries interface{}, key string) (map[string][]interface{}, error) }) } +// groupByWithDefault is the same as groupBy but allows a default value to be set +func groupByWithDefault(entries interface{}, key string, defaultValue string) (map[string][]interface{}, error) { + getValueWithDefault := func(v interface{}) (interface{}, error) { + value := deepGet(v, key) + if value == nil { + return defaultValue, nil + } + return value, nil + } + return generalizedGroupBy("groupByWithDefault", entries, getValueWithDefault, func(groups map[string][]interface{}, value interface{}, v interface{}) { + groups[value.(string)] = append(groups[value.(string)], v) + }) +} + // groupByKeys is the same as groupBy but only returns a list of keys func groupByKeys(entries interface{}, key string) ([]string, error) { keys, err := generalizedGroupByKey("groupByKeys", entries, key, func(groups map[string][]interface{}, value interface{}, v interface{}) { @@ -73,15 +86,31 @@ func groupByKeys(entries interface{}, key string) ([]string, error) { // groupByLabel is the same as groupBy but over a given label func groupByLabel(entries interface{}, label string) (map[string][]interface{}, error) { getLabel := func(v interface{}) (interface{}, error) { - if container, ok := v.(context.RuntimeContainer); ok { + if container, ok := v.(*context.RuntimeContainer); ok { if value, ok := container.Labels[label]; ok { return value, nil } return nil, nil } - return nil, fmt.Errorf("must pass an array or slice of RuntimeContainer to 'groupByLabel'; received %v", v) + return nil, fmt.Errorf("must pass an array or slice of *RuntimeContainer to 'groupByLabel'; received %v", v) } return generalizedGroupBy("groupByLabel", entries, getLabel, func(groups map[string][]interface{}, value interface{}, v interface{}) { groups[value.(string)] = append(groups[value.(string)], v) }) } + +// groupByLabelWithDefault is the same as groupByLabel but allows a default value to be set +func groupByLabelWithDefault(entries interface{}, label string, defaultValue string) (map[string][]interface{}, error) { + getLabel := func(v interface{}) (interface{}, error) { + if container, ok := v.(*context.RuntimeContainer); ok { + if value, ok := container.Labels[label]; ok { + return value, nil + } + return defaultValue, nil + } + return nil, fmt.Errorf("must pass an array or slice of *RuntimeContainer to 'groupByLabel'; received %v", v) + } + return generalizedGroupBy("groupByLabelWithDefault", entries, getLabel, func(groups map[string][]interface{}, value interface{}, v interface{}) { + groups[value.(string)] = append(groups[value.(string)], v) + }) +} diff --git a/internal/template/groupby_test.go b/internal/template/groupby_test.go index 6a2acf6a..f591e0e4 100644 --- a/internal/template/groupby_test.go +++ b/internal/template/groupby_test.go @@ -7,100 +7,73 @@ import ( "github.com/stretchr/testify/assert" ) -func TestGroupByExistingKey(t *testing.T) { - containers := []*context.RuntimeContainer{ - { - Env: map[string]string{ - "VIRTUAL_HOST": "demo1.localhost", - }, - ID: "1", - }, - { - Env: map[string]string{ - "VIRTUAL_HOST": "demo1.localhost", - }, - ID: "2", - }, - { - Env: map[string]string{ - "VIRTUAL_HOST": "demo2.localhost", - }, - ID: "3", - }, - } +var groupByContainers = []*context.RuntimeContainer{ + { + Env: map[string]string{ + "VIRTUAL_HOST": "demo1.localhost", + "EXTERNAL": "true", + }, + ID: "1", + }, + { + Env: map[string]string{ + "VIRTUAL_HOST": "demo1.localhost", + }, + ID: "2", + }, + { + Env: map[string]string{ + "VIRTUAL_HOST": "demo2.localhost", + "EXTERNAL": "true", + }, + ID: "3", + }, + { + Env: map[string]string{}, + ID: "4", + }, +} - groups, err := groupBy(containers, "Env.VIRTUAL_HOST") +func TestGroupByExistingKey(t *testing.T) { + groups, err := groupBy(groupByContainers, "Env.VIRTUAL_HOST") assert.NoError(t, err) assert.Len(t, groups, 2) assert.Len(t, groups["demo1.localhost"], 2) assert.Len(t, groups["demo2.localhost"], 1) - assert.Equal(t, "3", groups["demo2.localhost"][0].(context.RuntimeContainer).ID) + assert.Equal(t, "3", groups["demo2.localhost"][0].(*context.RuntimeContainer).ID) } func TestGroupByAfterWhere(t *testing.T) { - containers := []*context.RuntimeContainer{ - { - Env: map[string]string{ - "VIRTUAL_HOST": "demo1.localhost", - "EXTERNAL": "true", - }, - ID: "1", - }, - { - Env: map[string]string{ - "VIRTUAL_HOST": "demo1.localhost", - }, - ID: "2", - }, - { - Env: map[string]string{ - "VIRTUAL_HOST": "demo2.localhost", - "EXTERNAL": "true", - }, - ID: "3", - }, - } - - filtered, _ := where(containers, "Env.EXTERNAL", "true") + filtered, _ := where(groupByContainers, "Env.EXTERNAL", "true") groups, err := groupBy(filtered, "Env.VIRTUAL_HOST") assert.NoError(t, err) assert.Len(t, groups, 2) assert.Len(t, groups["demo1.localhost"], 1) assert.Len(t, groups["demo2.localhost"], 1) - assert.Equal(t, "3", groups["demo2.localhost"][0].(context.RuntimeContainer).ID) + assert.Equal(t, "3", groups["demo2.localhost"][0].(*context.RuntimeContainer).ID) } -func TestGroupByKeys(t *testing.T) { - containers := []*context.RuntimeContainer{ - { - Env: map[string]string{ - "VIRTUAL_HOST": "demo1.localhost", - }, - ID: "1", - }, - { - Env: map[string]string{ - "VIRTUAL_HOST": "demo1.localhost", - }, - ID: "2", - }, - { - Env: map[string]string{ - "VIRTUAL_HOST": "demo2.localhost", - }, - ID: "3", - }, - } +func TestGroupByWithDefault(t *testing.T) { + groups, err := groupByWithDefault(groupByContainers, "Env.VIRTUAL_HOST", "default.localhost") + assert.NoError(t, err) + assert.Len(t, groups, 3) + assert.Len(t, groups["demo1.localhost"], 2) + assert.Len(t, groups["demo2.localhost"], 1) + assert.Len(t, groups["default.localhost"], 1) + assert.Equal(t, "4", groups["default.localhost"][0].(*context.RuntimeContainer).ID) +} + +func TestGroupByKeys(t *testing.T) { expected := []string{"demo1.localhost", "demo2.localhost"} - groups, err := groupByKeys(containers, "Env.VIRTUAL_HOST") + groups, err := groupByKeys(groupByContainers, "Env.VIRTUAL_HOST") assert.NoError(t, err) assert.ElementsMatch(t, expected, groups) - expected = []string{"1", "2", "3"} - groups, err = groupByKeys(containers, "ID") + expected = []string{"1", "2", "3", "4"} + groups, err = groupByKeys(groupByContainers, "ID") assert.NoError(t, err) assert.ElementsMatch(t, expected, groups) } @@ -111,45 +84,45 @@ func TestGeneralizedGroupByError(t *testing.T) { assert.Nil(t, groups) } -func TestGroupByLabel(t *testing.T) { - containers := []*context.RuntimeContainer{ - { - Labels: map[string]string{ - "com.docker.compose.project": "one", - }, - ID: "1", - }, - { - Labels: map[string]string{ - "com.docker.compose.project": "two", - }, - ID: "2", - }, - { - Labels: map[string]string{ - "com.docker.compose.project": "one", - }, - ID: "3", - }, - { - ID: "4", - }, - { - Labels: map[string]string{ - "com.docker.compose.project": "", - }, - ID: "5", - }, - } +var groupByLabelContainers = []*context.RuntimeContainer{ + { + Labels: map[string]string{ + "com.docker.compose.project": "one", + }, + ID: "1", + }, + { + Labels: map[string]string{ + "com.docker.compose.project": "two", + }, + ID: "2", + }, + { + Labels: map[string]string{ + "com.docker.compose.project": "one", + }, + ID: "3", + }, + { + ID: "4", + }, + { + Labels: map[string]string{ + "com.docker.compose.project": "", + }, + ID: "5", + }, +} - groups, err := groupByLabel(containers, "com.docker.compose.project") +func TestGroupByLabel(t *testing.T) { + groups, err := groupByLabel(groupByLabelContainers, "com.docker.compose.project") assert.NoError(t, err) assert.Len(t, groups, 3) assert.Len(t, groups["one"], 2) assert.Len(t, groups[""], 1) assert.Len(t, groups["two"], 1) - assert.Equal(t, "2", groups["two"][0].(context.RuntimeContainer).ID) + assert.Equal(t, "2", groups["two"][0].(*context.RuntimeContainer).ID) } func TestGroupByLabelError(t *testing.T) { @@ -159,6 +132,25 @@ func TestGroupByLabelError(t *testing.T) { assert.Nil(t, groups) } +func TestGroupByLabelWithDefault(t *testing.T) { + groups, err := groupByLabelWithDefault(groupByLabelContainers, "com.docker.compose.project", "default") + + assert.NoError(t, err) + assert.Len(t, groups, 4) + assert.Len(t, groups["one"], 2) + assert.Len(t, groups["two"], 1) + assert.Len(t, groups[""], 1) + assert.Len(t, groups["default"], 1) + assert.Equal(t, "4", groups["default"][0].(*context.RuntimeContainer).ID) +} + +func TestGroupByLabelWithDefaultError(t *testing.T) { + strings := []string{"foo", "bar", "baz"} + groups, err := groupByLabelWithDefault(strings, "", "") + assert.Error(t, err) + assert.Nil(t, groups) +} + func TestGroupByMulti(t *testing.T) { containers := []*context.RuntimeContainer{ { @@ -179,6 +171,10 @@ func TestGroupByMulti(t *testing.T) { }, ID: "3", }, + { + Env: map[string]string{}, + ID: "4", + }, } groups, _ := groupByMulti(containers, "Env.VIRTUAL_HOST", ",") @@ -193,13 +189,13 @@ func TestGroupByMulti(t *testing.T) { if len(groups["demo2.localhost"]) != 1 { t.Fatalf("expected 1 got %d", len(groups["demo2.localhost"])) } - if groups["demo2.localhost"][0].(context.RuntimeContainer).ID != "3" { - t.Fatalf("expected 2 got %s", groups["demo2.localhost"][0].(context.RuntimeContainer).ID) + if groups["demo2.localhost"][0].(*context.RuntimeContainer).ID != "3" { + t.Fatalf("expected 2 got %s", groups["demo2.localhost"][0].(*context.RuntimeContainer).ID) } if len(groups["demo3.localhost"]) != 1 { t.Fatalf("expect 1 got %d", len(groups["demo3.localhost"])) } - if groups["demo3.localhost"][0].(context.RuntimeContainer).ID != "2" { - t.Fatalf("expected 2 got %s", groups["demo3.localhost"][0].(context.RuntimeContainer).ID) + if groups["demo3.localhost"][0].(*context.RuntimeContainer).ID != "2" { + t.Fatalf("expected 2 got %s", groups["demo3.localhost"][0].(*context.RuntimeContainer).ID) } } diff --git a/internal/template/reflect.go b/internal/template/reflect.go index dfb465bc..47e76eed 100644 --- a/internal/template/reflect.go +++ b/internal/template/reflect.go @@ -1,49 +1,90 @@ package template import ( + "fmt" "log" + "math" "reflect" + "strconv" "strings" ) -func stripPrefix(s, prefix string) string { - path := s - for { - if strings.HasPrefix(path, ".") { - path = path[1:] - continue - } - break +func parseAllocateInt(desired string) (int, error) { + parsed, err := strconv.ParseInt(desired, 10, 32) + if err != nil { + return int(0), err + } + if parsed < 0 { + return int(0), fmt.Errorf("non-negative decimal number required for array/slice index, got %#v", desired) + } + if parsed <= math.MaxInt32 { + return int(parsed), nil } - return path + return math.MaxInt32, nil } -func deepGet(item interface{}, path string) interface{} { - if path == "" { - return item +func deepGetImpl(v reflect.Value, path []string) interface{} { + if !v.IsValid() { + return nil } + if len(path) == 0 { + return v.Interface() + } + if v.Kind() == reflect.Pointer { + v = v.Elem() + } + if v.Kind() == reflect.Pointer { + log.Printf("unable to descend into pointer of a pointer\n") + return nil + } + switch v.Kind() { + case reflect.Struct: + return deepGetImpl(v.FieldByName(path[0]), path[1:]) + case reflect.Map: + // If the first part of the path is a key in the map, we use it directly + if mapValue := v.MapIndex(reflect.ValueOf(path[0])); mapValue.IsValid() { + return deepGetImpl(mapValue, path[1:]) + } - path = stripPrefix(path, ".") - parts := strings.Split(path, ".") - itemValue := reflect.ValueOf(item) - - if len(parts) > 0 { - switch itemValue.Kind() { - case reflect.Struct: - fieldValue := itemValue.FieldByName(parts[0]) - if fieldValue.IsValid() { - return deepGet(fieldValue.Interface(), strings.Join(parts[1:], ".")) + // If the first part of the path is not a key in the map, we try to find a valid key by joining the path parts + var builder strings.Builder + for i, pathPart := range path { + if i > 0 { + builder.WriteString(".") } - case reflect.Map: - mapValue := itemValue.MapIndex(reflect.ValueOf(parts[0])) - if mapValue.IsValid() { - return deepGet(mapValue.Interface(), strings.Join(parts[1:], ".")) + builder.WriteString(pathPart) + joinedPath := builder.String() + + if mapValue := v.MapIndex(reflect.ValueOf(joinedPath)); mapValue.IsValid() { + if i == len(path) { + return mapValue.Interface() + } + return deepGetImpl(mapValue, path[i+1:]) } - default: - log.Printf("Can't group by %s (value %v, kind %s)\n", path, itemValue, itemValue.Kind()) } + + return nil + case reflect.Slice, reflect.Array: + i, err := parseAllocateInt(path[0]) + if err != nil { + log.Println(err.Error()) + return nil + } + if i >= v.Len() { + log.Printf("index %v out of bounds", i) + return nil + } + return deepGetImpl(v.Index(i), path[1:]) + default: + log.Printf("unable to index by %s (value %v, kind %s)\n", path[0], v, v.Kind()) return nil } +} - return itemValue.Interface() +func deepGet(item interface{}, path string) interface{} { + var parts []string + if path != "" { + parts = strings.Split(strings.TrimPrefix(path, "."), ".") + } + return deepGetImpl(reflect.ValueOf(item), parts) } diff --git a/internal/template/reflect_test.go b/internal/template/reflect_test.go index 0211c5e9..dac9c4ee 100644 --- a/internal/template/reflect_test.go +++ b/internal/template/reflect_test.go @@ -34,7 +34,7 @@ func TestDeepGetSimpleDotPrefix(t *testing.T) { item := context.RuntimeContainer{ ID: "expected", } - value := deepGet(item, "...ID") + value := deepGet(item, ".ID") assert.IsType(t, "", value) assert.Equal(t, "expected", value) @@ -51,3 +51,75 @@ func TestDeepGetMap(t *testing.T) { assert.Equal(t, "value", value) } + +func TestDeepGet(t *testing.T) { + s := struct{ X string }{"foo"} + sp := &s + + for _, tc := range []struct { + desc string + item interface{} + path string + want interface{} + }{ + { + "map of string", + map[string]string{ + "Env": "quux", + }, + "Env", + "quux", + }, + { + "map key empty string", + map[string]map[string]map[string]string{ + "": { + "": { + "": "foo", + }, + }, + }, + "...", + "foo", + }, + { + "map with dot in key", + map[string]map[string]string{ + "Env": { + "foo.bar.baz.qux": "quux", + }, + }, + "Env.foo.bar.baz.qux", + "quux", + }, + { + "nested maps with dot in keys", + map[string]map[string]map[string]string{ + "Env": { + "foo.bar": { + "baz.qux": "quux", + }, + }, + }, + "Env.foo.bar.baz.qux", + "quux", + }, + {"struct", s, "X", "foo"}, + {"pointer to struct", sp, "X", "foo"}, + {"double pointer to struct", &sp, ".X", nil}, + {"slice index", []string{"foo", "bar"}, "1", "bar"}, + {"slice index out of bounds", []string{}, "0", nil}, + {"slice index negative", []string{}, "-1", nil}, + {"slice index nonnumber", []string{}, "foo", nil}, + {"array index", [2]string{"foo", "bar"}, "1", "bar"}, + {"array index out of bounds", [1]string{"foo"}, "1", nil}, + {"array index negative", [1]string{"foo"}, "-1", nil}, + {"array index nonnumber", [1]string{"foo"}, "foo", nil}, + } { + t.Run(tc.desc, func(t *testing.T) { + got := deepGet(tc.item, tc.path) + assert.IsType(t, tc.want, got) + assert.Equal(t, tc.want, got) + }) + } +} diff --git a/internal/template/sort.go b/internal/template/sort.go index e27f5658..b4efd4c9 100644 --- a/internal/template/sort.go +++ b/internal/template/sort.go @@ -3,6 +3,8 @@ package template import ( "reflect" "sort" + "strconv" + "time" ) // sortStrings returns a sorted array of strings in increasing order @@ -47,20 +49,56 @@ func (s *sortableByKey) set(funcName string, entries interface{}) (err error) { } s.data = make([]interface{}, entriesVal.Len()) for i := 0; i < entriesVal.Len(); i++ { - s.data[i] = reflect.Indirect(entriesVal.Index(i)).Interface() + s.data[i] = entriesVal.Index(i).Interface() } return } +func getFieldAsString(item interface{}, path string) string { + // Mostly inspired by https://stackoverflow.com/a/47739620 + e := deepGet(item, path) + r := reflect.ValueOf(e) + + if r.Kind() == reflect.Invalid { + return "" + } + + fieldValue := r.Interface() + + switch v := fieldValue.(type) { + case int: + return strconv.FormatInt(int64(v), 10) + case int32: + return strconv.FormatInt(int64(v), 10) + case int64: + return strconv.FormatInt(v, 10) + case string: + return v + case bool: + if v { + return "true" + } + return "false" + case time.Time: + return v.String() + default: + return "" + } +} + // method required to implement sort.Interface func (s sortableByKey) Less(i, j int) bool { - values := map[int]string{i: "", j: ""} - for k := range values { - if v := reflect.ValueOf(deepGet(s.data[k], s.key)); v.Kind() != reflect.Invalid { - values[k] = v.Interface().(string) + dataI := getFieldAsString(s.data[i], s.key) + dataJ := getFieldAsString(s.data[j], s.key) + + if intI, err := strconv.ParseInt(dataI, 10, 64); err == nil { + if intJ, err := strconv.ParseInt(dataJ, 10, 64); err == nil { + // If both are integers, compare as integers + return intI < intJ } } - return values[i] < values[j] + + return dataI < dataJ } // Generalized SortBy function diff --git a/internal/template/sort_test.go b/internal/template/sort_test.go index 1a003713..b541fcdf 100644 --- a/internal/template/sort_test.go +++ b/internal/template/sort_test.go @@ -2,6 +2,7 @@ package template import ( "testing" + "time" "github.com/nginx-proxy/docker-gen/internal/context" "github.com/stretchr/testify/assert" @@ -19,84 +20,95 @@ func TestSortStringsDesc(t *testing.T) { assert.Equal(t, expected, sortStringsDesc(strings)) } -func TestSortObjectsByKeysAsc(t *testing.T) { - containers := []*context.RuntimeContainer{ - { - Env: map[string]string{ - "VIRTUAL_HOST": "bar.localhost", - }, - ID: "9", - }, - { - Env: map[string]string{ - "VIRTUAL_HOST": "foo.localhost", - }, - ID: "1", - }, - { - Env: map[string]string{ - "VIRTUAL_HOST": "baz.localhost", - }, - ID: "3", - }, - { - Env: map[string]string{}, - ID: "8", - }, +func TestGetFieldAsString(t *testing.T) { + testStruct := struct { + String string + BoolT bool + BoolF bool + Int int + Int32 int32 + Int64 int64 + Time time.Time + }{ + String: "foo", + BoolT: true, + BoolF: false, + Int: 42, + Int32: 43, + Int64: 44, + Time: time.Date(2023, 12, 19, 0, 0, 0, 0, time.UTC), } - sorted, err := sortObjectsByKeysAsc(containers, "ID") - - assert.NoError(t, err) - assert.Len(t, sorted, 4) - assert.Equal(t, "foo.localhost", sorted[0].(context.RuntimeContainer).Env["VIRTUAL_HOST"]) - assert.Equal(t, "9", sorted[3].(context.RuntimeContainer).ID) - - sorted, err = sortObjectsByKeysAsc(sorted, "Env.VIRTUAL_HOST") - - assert.NoError(t, err) - assert.Len(t, sorted, 4) - assert.Equal(t, "foo.localhost", sorted[3].(context.RuntimeContainer).Env["VIRTUAL_HOST"]) - assert.Equal(t, "8", sorted[0].(context.RuntimeContainer).ID) + assert.Equal(t, "foo", getFieldAsString(testStruct, "String")) + assert.Equal(t, "true", getFieldAsString(testStruct, "BoolT")) + assert.Equal(t, "false", getFieldAsString(testStruct, "BoolF")) + assert.Equal(t, "42", getFieldAsString(testStruct, "Int")) + assert.Equal(t, "43", getFieldAsString(testStruct, "Int32")) + assert.Equal(t, "44", getFieldAsString(testStruct, "Int64")) + assert.Equal(t, "2023-12-19 00:00:00 +0000 UTC", getFieldAsString(testStruct, "Time")) + assert.Equal(t, "", getFieldAsString(testStruct, "InvalidField")) } -func TestSortObjectsByKeysDesc(t *testing.T) { - containers := []*context.RuntimeContainer{ - { - Env: map[string]string{ - "VIRTUAL_HOST": "bar.localhost", - }, - ID: "9", +func TestSortObjectsByKeys(t *testing.T) { + o0 := &context.RuntimeContainer{ + Created: time.Date(2020, 1, 2, 0, 0, 0, 0, time.UTC), + Env: map[string]string{ + "VIRTUAL_HOST": "bar.localhost", + }, + Labels: map[string]string{ + "com.docker.compose.container_number": "1", }, - { - Env: map[string]string{ - "VIRTUAL_HOST": "foo.localhost", - }, - ID: "1", + ID: "11", + } + o1 := &context.RuntimeContainer{ + Created: time.Date(2021, 1, 2, 0, 0, 10, 0, time.UTC), + Env: map[string]string{ + "VIRTUAL_HOST": "foo.localhost", }, - { - Env: map[string]string{ - "VIRTUAL_HOST": "baz.localhost", - }, - ID: "3", + Labels: map[string]string{ + "com.docker.compose.container_number": "11", }, - { - Env: map[string]string{}, - ID: "8", + ID: "1", + } + o2 := &context.RuntimeContainer{ + Created: time.Date(2021, 1, 2, 0, 0, 0, 0, time.UTC), + Env: map[string]string{ + "VIRTUAL_HOST": "baz.localhost", }, + Labels: map[string]string{}, + ID: "3", } + o3 := &context.RuntimeContainer{ + Created: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), + Env: map[string]string{}, + Labels: map[string]string{ + "com.docker.compose.container_number": "2", + }, + ID: "8", + } + containers := []*context.RuntimeContainer{o0, o1, o2, o3} - sorted, err := sortObjectsByKeysDesc(containers, "ID") - - assert.NoError(t, err) - assert.Len(t, sorted, 4) - assert.Equal(t, "bar.localhost", sorted[0].(context.RuntimeContainer).Env["VIRTUAL_HOST"]) - assert.Equal(t, "1", sorted[3].(context.RuntimeContainer).ID) - - sorted, err = sortObjectsByKeysDesc(sorted, "Env.VIRTUAL_HOST") - - assert.NoError(t, err) - assert.Len(t, sorted, 4) - assert.Equal(t, "", sorted[3].(context.RuntimeContainer).Env["VIRTUAL_HOST"]) - assert.Equal(t, "1", sorted[0].(context.RuntimeContainer).ID) + for _, tc := range []struct { + desc string + fn func(interface{}, string) ([]interface{}, error) + key string + want []interface{} + }{ + {"Asc simple", sortObjectsByKeysAsc, "ID", []interface{}{o1, o2, o3, o0}}, + {"Desc simple", sortObjectsByKeysDesc, "ID", []interface{}{o0, o3, o2, o1}}, + {"Asc complex", sortObjectsByKeysAsc, "Env.VIRTUAL_HOST", []interface{}{o3, o0, o2, o1}}, + {"Desc complex", sortObjectsByKeysDesc, "Env.VIRTUAL_HOST", []interface{}{o1, o2, o0, o3}}, + {"Asc complex w/ dots in key name", sortObjectsByKeysAsc, "Labels.com.docker.compose.container_number", []interface{}{o2, o0, o3, o1}}, + {"Desc complex w/ dots in key name", sortObjectsByKeysDesc, "Labels.com.docker.compose.container_number", []interface{}{o1, o3, o0, o2}}, + {"Asc time", sortObjectsByKeysAsc, "Created", []interface{}{o3, o0, o2, o1}}, + {"Desc time", sortObjectsByKeysDesc, "Created", []interface{}{o1, o2, o0, o3}}, + } { + t.Run(tc.desc, func(t *testing.T) { + got, err := tc.fn(containers, tc.key) + assert.NoError(t, err) + // The function should return a sorted copy of the slice, not modify the original. + assert.Equal(t, []*context.RuntimeContainer{o0, o1, o2, o3}, containers) + assert.Equal(t, tc.want, got) + }) + } } diff --git a/internal/template/template.go b/internal/template/template.go index a6af7e15..b6ab00b3 100644 --- a/internal/template/template.go +++ b/internal/template/template.go @@ -3,9 +3,10 @@ package template import ( "bufio" "bytes" + "errors" "fmt" "io" - "io/ioutil" + "io/fs" "log" "net/url" "os" @@ -13,10 +14,10 @@ import ( "reflect" "strconv" "strings" - "syscall" "text/template" "unicode" + sprig "github.com/Masterminds/sprig/v3" "github.com/nginx-proxy/docker-gen/internal/config" "github.com/nginx-proxy/docker-gen/internal/context" "github.com/nginx-proxy/docker-gen/internal/utils" @@ -28,7 +29,7 @@ func getArrayValues(funcName string, entries interface{}) (*reflect.Value, error kind := entriesVal.Kind() if kind == reflect.Ptr { - entriesVal = reflect.Indirect(entriesVal) + entriesVal = entriesVal.Elem() kind = entriesVal.Kind() } @@ -42,51 +43,81 @@ func getArrayValues(funcName string, entries interface{}) (*reflect.Value, error } func newTemplate(name string) *template.Template { - tmpl := template.New(name).Funcs(template.FuncMap{ - "closest": arrayClosest, - "coalesce": coalesce, - "contains": contains, - "dict": dict, - "dir": dirList, - "exists": utils.PathExists, - "first": arrayFirst, - "groupBy": groupBy, - "groupByKeys": groupByKeys, - "groupByMulti": groupByMulti, - "groupByLabel": groupByLabel, - "hasPrefix": hasPrefix, - "hasSuffix": hasSuffix, - "json": marshalJson, - "intersect": intersect, - "keys": keys, - "last": arrayLast, - "replace": strings.Replace, - "parseBool": strconv.ParseBool, - "parseJson": unmarshalJson, - "queryEscape": url.QueryEscape, - "sha1": hashSha1, - "split": strings.Split, - "splitN": strings.SplitN, - "sortStringsAsc": sortStringsAsc, - "sortStringsDesc": sortStringsDesc, - "sortObjectsByKeysAsc": sortObjectsByKeysAsc, - "sortObjectsByKeysDesc": sortObjectsByKeysDesc, - "trimPrefix": trimPrefix, - "trimSuffix": trimSuffix, - "trim": trim, - "toLower": toLower, - "toUpper": toUpper, - "when": when, - "where": where, - "whereNot": whereNot, - "whereExist": whereExist, - "whereNotExist": whereNotExist, - "whereAny": whereAny, - "whereAll": whereAll, - "whereLabelExists": whereLabelExists, - "whereLabelDoesNotExist": whereLabelDoesNotExist, - "whereLabelValueMatches": whereLabelValueMatches, + tmpl := template.New(name) + // The eval function is defined here because it must be a closure around tmpl. + eval := func(name string, args ...any) (string, error) { + buf := bytes.NewBuffer(nil) + data := any(nil) + if len(args) == 1 { + data = args[0] + } else if len(args) > 1 { + return "", errors.New("too many arguments") + } + if err := tmpl.ExecuteTemplate(buf, name, data); err != nil { + return "", err + } + return buf.String(), nil + } + + sprigFuncMap := sprig.TxtFuncMap() + + tmpl.Funcs(sprigFuncMap).Funcs(template.FuncMap{ + "closest": arrayClosest, + "coalesce": coalesce, + "comment": comment, + "contains": contains, + "dir": dirList, + "eval": eval, + "exists": utils.PathExists, + "groupBy": groupBy, + "groupByWithDefault": groupByWithDefault, + "groupByKeys": groupByKeys, + "groupByMulti": groupByMulti, + "groupByLabel": groupByLabel, + "groupByLabelWithDefault": groupByLabelWithDefault, + "include": include, + "intersect": intersect, + "keys": keys, + "replace": strings.Replace, + "parseBool": strconv.ParseBool, + "fromYaml": fromYaml, + "toYaml": toYaml, + "mustFromYaml": mustFromYaml, + "mustToYaml": mustToYaml, + "queryEscape": url.QueryEscape, + "split": strings.Split, + "splitN": strings.SplitN, + "sortStringsAsc": sortStringsAsc, + "sortStringsDesc": sortStringsDesc, + "sortObjectsByKeysAsc": sortObjectsByKeysAsc, + "sortObjectsByKeysDesc": sortObjectsByKeysDesc, + "toLower": strings.ToLower, + "toUpper": strings.ToUpper, + "when": when, + "where": where, + "whereNot": whereNot, + "whereExist": whereExist, + "whereNotExist": whereNotExist, + "whereAny": whereAny, + "whereAll": whereAll, + "whereLabelExists": whereLabelExists, + "whereLabelDoesNotExist": whereLabelDoesNotExist, + "whereLabelValueMatches": whereLabelValueMatches, + + // legacy docker-gen template function aliased to their Sprig clone + "json": sprigFuncMap["mustToJson"], + "parseJson": sprigFuncMap["mustFromJson"], + "sha1": sprigFuncMap["sha1sum"], + + // aliases to sprig template functions masked by docker-gen functions with the same name + "sprigCoalesce": sprigFuncMap["coalesce"], + "sprigContains": sprigFuncMap["contains"], + "sprigDir": sprigFuncMap["dir"], + "sprigReplace": sprigFuncMap["replace"], + "sprigSplit": sprigFuncMap["split"], + "sprigSplitn": sprigFuncMap["splitn"], }) + return tmpl } @@ -160,47 +191,17 @@ func GenerateFile(config config.Config, containers context.Context) bool { } if config.Dest != "" { - dest, err := ioutil.TempFile(filepath.Dir(config.Dest), "docker-gen") - defer func() { - dest.Close() - os.Remove(dest.Name()) - }() - if err != nil { - log.Fatalf("Unable to create temp file: %s\n", err) - } - - if n, err := dest.Write(contents); n != len(contents) || err != nil { - log.Fatalf("Failed to write to temp file: wrote %d, exp %d, err=%v", n, len(contents), err) - } - - oldContents := []byte{} - if fi, err := os.Stat(config.Dest); err == nil || os.IsNotExist(err) { - if err != nil && os.IsNotExist(err) { - emptyFile, err := os.Create(config.Dest) - if err != nil { - log.Fatalf("Unable to create empty destination file: %s\n", err) - } else { - emptyFile.Close() - fi, _ = os.Stat(config.Dest) - } - } - if err := dest.Chmod(fi.Mode()); err != nil { - log.Fatalf("Unable to chmod temp file: %s\n", err) - } - if err := dest.Chown(int(fi.Sys().(*syscall.Stat_t).Uid), int(fi.Sys().(*syscall.Stat_t).Gid)); err != nil { - log.Fatalf("Unable to chown temp file: %s\n", err) - } - oldContents, err = ioutil.ReadFile(config.Dest) - if err != nil { - log.Fatalf("Unable to compare current file contents: %s: %s\n", config.Dest, err) - } + oldContents, err := os.ReadFile(config.Dest) + if err != nil && !errors.Is(err, fs.ErrNotExist) { + log.Fatalf("Unable to compare current file contents: %s: %s\n", config.Dest, err) } if !bytes.Equal(oldContents, contents) { - err = os.Rename(dest.Name(), config.Dest) + err := os.WriteFile(config.Dest, contents, 0644) if err != nil { - log.Fatalf("Unable to create dest file %s: %s\n", config.Dest, err) + log.Fatalf("Unable to write to dest file %s: %s\n", config.Dest, err) } + log.Printf("Generated '%s' from %d containers", config.Dest, len(filteredContainers)) return true } @@ -212,13 +213,14 @@ func GenerateFile(config config.Config, containers context.Context) bool { } func executeTemplate(templatePath string, containers context.Context) []byte { - tmpl, err := newTemplate(filepath.Base(templatePath)).ParseFiles(templatePath) + templatePathList := strings.Split(templatePath, ";") + tmpl, err := newTemplate(filepath.Base(templatePath)).ParseFiles(templatePathList...) if err != nil { log.Fatalf("Unable to parse template: %s", err) } buf := new(bytes.Buffer) - err = tmpl.ExecuteTemplate(buf, filepath.Base(templatePath), &containers) + err = tmpl.ExecuteTemplate(buf, filepath.Base(templatePathList[0]), &containers) if err != nil { log.Fatalf("Template error: %s\n", err) } diff --git a/internal/template/template_test.go b/internal/template/template_test.go index b78935f0..3fc2805c 100644 --- a/internal/template/template_test.go +++ b/internal/template/template_test.go @@ -2,11 +2,11 @@ package template import ( "bytes" - "fmt" + "errors" "reflect" + "strconv" "strings" "testing" - "text/template" "github.com/stretchr/testify/assert" ) @@ -14,24 +14,39 @@ import ( type templateTestList []struct { tmpl string context interface{} - expected string + expected interface{} } -func (tests templateTestList) run(t *testing.T, prefix string) { +func (tests templateTestList) run(t *testing.T) { for n, test := range tests { - tmplName := fmt.Sprintf("%s-test-%d", prefix, n) - tmpl := template.Must(newTemplate(tmplName).Parse(test.tmpl)) - - var b bytes.Buffer - err := tmpl.ExecuteTemplate(&b, tmplName, test.context) - if err != nil { - t.Fatalf("Error executing template: %v (test %s)", err, tmplName) - } - - got := b.String() - if test.expected != got { - t.Fatalf("Incorrect output found; expected %s, got %s (test %s)", test.expected, got, tmplName) - } + test := test + t.Run(strconv.Itoa(n), func(t *testing.T) { + t.Parallel() + wantErr, _ := test.expected.(error) + want, ok := test.expected.(string) + if !ok && wantErr == nil { + t.Fatalf("test bug: want a string or error for .expected, got %v", test.expected) + } + tmpl, err := newTemplate("testTemplate").Parse(test.tmpl) + if err != nil { + t.Fatalf("Template parse failed: %v", err) + } + + var b bytes.Buffer + err = tmpl.ExecuteTemplate(&b, "testTemplate", test.context) + got := b.String() + if err != nil { + if wantErr != nil { + return + } + t.Fatalf("Error executing template: %v", err) + } else if wantErr != nil { + t.Fatalf("Expected error, got %v", got) + } + if want != got { + t.Fatalf("Incorrect output found; want %#v, got %#v", want, got) + } + }) } } @@ -108,3 +123,73 @@ func TestRemoveBlankLines(t *testing.T) { } } } + +// TestSprig ensures that the migration to sprig to provide certain functions did not break +// compatibility with existing templates. +func TestSprig(t *testing.T) { + for _, tc := range []struct { + desc string + tts templateTestList + }{ + {"dict", templateTestList{ + {`{{ $d := dict "a" "b" }}{{ if eq (index $d "a") "b" }}ok{{ end }}`, nil, `ok`}, + {`{{ $d := dict "a" "b" }}{{ if eq (index $d "x") nil }}ok{{ end }}`, nil, `ok`}, + {`{{ $d := dict "a" "b" "c" (dict "d" "e") }}{{ if eq (index $d "c" "d") "e" }}ok{{ end }}`, nil, `ok`}, + }}, + {"first", templateTestList{ + {`{{ if eq (first $) "a"}}ok{{ end }}`, []string{"a", "b"}, `ok`}, + {`{{ if eq (first $) "a"}}ok{{ end }}`, [2]string{"a", "b"}, `ok`}, + }}, + {"hasPrefix", templateTestList{ + {`{{ if hasPrefix "tcp://" "tcp://127.0.0.1:2375" }}ok{{ end }}`, nil, `ok`}, + {`{{ if not (hasPrefix "udp://" "tcp://127.0.0.1:2375") }}ok{{ end }}`, nil, `ok`}, + }}, + {"hasSuffix", templateTestList{ + {`{{ if hasSuffix ".local" "myhost.local" }}ok{{ end }}`, nil, `ok`}, + {`{{ if not (hasSuffix ".example" "myhost.local") }}ok{{ end }}`, nil, `ok`}, + }}, + {"last", templateTestList{ + {`{{ if eq (last $) "b"}}ok{{ end }}`, []string{"a", "b"}, `ok`}, + {`{{ if eq (last $) "b"}}ok{{ end }}`, [2]string{"a", "b"}, `ok`}, + }}, + {"trim", templateTestList{ + {`{{ if eq (trim " myhost.local ") "myhost.local" }}ok{{ end }}`, nil, `ok`}, + }}, + } { + t.Run(tc.desc, func(t *testing.T) { + tc.tts.run(t) + }) + } +} + +func TestEval(t *testing.T) { + for _, tc := range []struct { + desc string + tts templateTestList + }{ + {"undefined", templateTestList{ + {`{{eval "missing"}}`, nil, errors.New("")}, + {`{{eval "missing" nil}}`, nil, errors.New("")}, + {`{{eval "missing" "abc"}}`, nil, errors.New("")}, + {`{{eval "missing" "abc" "def"}}`, nil, errors.New("")}, + }}, + // The purpose of the "ctx" context is to assert that $ and . inside the template is the + // eval argument, not the global context. + {"noArg", templateTestList{ + {`{{define "T"}}{{$}}{{.}}{{end}}{{eval "T"}}`, "ctx", ""}, + }}, + {"nilArg", templateTestList{ + {`{{define "T"}}{{$}}{{.}}{{end}}{{eval "T" nil}}`, "ctx", ""}, + }}, + {"oneArg", templateTestList{ + {`{{define "T"}}{{$}}{{.}}{{end}}{{eval "T" "arg"}}`, "ctx", "argarg"}, + }}, + {"moreThanOneArg", templateTestList{ + {`{{define "T"}}{{$}}{{.}}{{end}}{{eval "T" "a" "b"}}`, "ctx", errors.New("")}, + }}, + } { + t.Run(tc.desc, func(t *testing.T) { + tc.tts.run(t) + }) + } +} diff --git a/internal/template/where.go b/internal/template/where.go index 2ca6bdd3..ac95fc0f 100644 --- a/internal/template/where.go +++ b/internal/template/where.go @@ -18,7 +18,7 @@ func generalizedWhere(funcName string, entries interface{}, key string, test fun selection := make([]interface{}, 0) for i := 0; i < entriesVal.Len(); i++ { - v := reflect.Indirect(entriesVal.Index(i)).Interface() + v := entriesVal.Index(i).Interface() value := deepGet(v, key) if test(value) { diff --git a/internal/template/where_test.go b/internal/template/where_test.go index 095b577f..88dbb291 100644 --- a/internal/template/where_test.go +++ b/internal/template/where_test.go @@ -68,7 +68,7 @@ func TestWhere(t *testing.T) { }, } - tests.run(t, "where") + tests.run(t) } func TestWhereNot(t *testing.T) { @@ -133,7 +133,7 @@ func TestWhereNot(t *testing.T) { }, } - tests.run(t, "whereNot") + tests.run(t) } func TestWhereExist(t *testing.T) { @@ -173,7 +173,7 @@ func TestWhereExist(t *testing.T) { {`{{whereExist . "Env.VIRTUAL_PROTO" | len}}`, containers, `1`}, } - tests.run(t, "whereExist") + tests.run(t) } func TestWhereNotExist(t *testing.T) { @@ -213,7 +213,7 @@ func TestWhereNotExist(t *testing.T) { {`{{whereNotExist . "Env.VIRTUAL_PROTO" | len}}`, containers, `3`}, } - tests.run(t, "whereNotExist") + tests.run(t) } func TestWhereSomeMatch(t *testing.T) { @@ -251,7 +251,7 @@ func TestWhereSomeMatch(t *testing.T) { {`{{whereAny . "Env.NOEXIST" "," (split "demo3.localhost" ",") | len}}`, containers, `0`}, } - tests.run(t, "whereAny") + tests.run(t) } func TestWhereRequires(t *testing.T) { @@ -289,7 +289,7 @@ func TestWhereRequires(t *testing.T) { {`{{whereAll . "Env.NOEXIST" "," (split "demo3.localhost" ",") | len}}`, containers, `0`}, } - tests.run(t, "whereAll") + tests.run(t) } func TestWhereLabelExists(t *testing.T) { @@ -315,7 +315,7 @@ func TestWhereLabelExists(t *testing.T) { {`{{whereLabelExists . "com.example.baz" | len}}`, containers, `0`}, } - tests.run(t, "whereLabelExists") + tests.run(t) } func TestWhereLabelDoesNotExist(t *testing.T) { @@ -341,7 +341,7 @@ func TestWhereLabelDoesNotExist(t *testing.T) { {`{{whereLabelDoesNotExist . "com.example.baz" | len}}`, containers, `2`}, } - tests.run(t, "whereLabelDoesNotExist") + tests.run(t) } func TestWhereLabelValueMatches(t *testing.T) { @@ -370,5 +370,5 @@ func TestWhereLabelValueMatches(t *testing.T) { {`{{whereLabelValueMatches . "com.example.baz" ".*" | len}}`, containers, `0`}, } - tests.run(t, "whereLabelValueMatches") + tests.run(t) } diff --git a/internal/template/yaml.go b/internal/template/yaml.go new file mode 100644 index 00000000..f6fcfcf0 --- /dev/null +++ b/internal/template/yaml.go @@ -0,0 +1,31 @@ +package template + +import "gopkg.in/yaml.v3" + +// fromYaml decodes YAML into a structured value, ignoring errors. +func fromYaml(v string) interface{} { + output, _ := mustFromYaml(v) + return output +} + +// mustFromYaml decodes YAML into a structured value, returning errors. +func mustFromYaml(v string) (interface{}, error) { + var output interface{} + err := yaml.Unmarshal([]byte(v), &output) + return output, err +} + +// toYaml encodes an item into a YAML string +func toYaml(v interface{}) string { + output, _ := mustToYaml(v) + return string(output) +} + +// toYaml encodes an item into a YAML string, returning errors +func mustToYaml(v interface{}) (string, error) { + output, err := yaml.Marshal(v) + if err != nil { + return "", err + } + return string(output), nil +} diff --git a/internal/template/yaml_test.go b/internal/template/yaml_test.go new file mode 100644 index 00000000..f8e47aed --- /dev/null +++ b/internal/template/yaml_test.go @@ -0,0 +1,36 @@ +package template + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +var testYaml = `bool: true +list: + - foo + - bar +number: 42 +string: test +` + +var testJson = `{"bool":true,"list":["foo","bar"],"number":42,"string":"test"}` + +var testDict = map[string]interface{}{ + "bool": true, + "number": 42, + "string": "test", + "list": []interface{}{ + "foo", + "bar", + }, +} + +func TestFromYaml(t *testing.T) { + assert.Equal(t, testDict, fromYaml(testYaml)) + assert.Equal(t, testDict, fromYaml(testJson)) +} + +func TestToYaml(t *testing.T) { + assert.Equal(t, testYaml, toYaml(testDict)) +} diff --git a/internal/utils/utils.go b/internal/utils/utils.go index eb514456..2d4fb89b 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -1,6 +1,8 @@ package utils import ( + "errors" + "io/fs" "os" "strings" ) @@ -27,7 +29,7 @@ func PathExists(path string) (bool, error) { if err == nil { return true, nil } - if os.IsNotExist(err) { + if errors.Is(err, fs.ErrNotExist) { return false, nil } return false, err diff --git a/internal/utils/utils_test.go b/internal/utils/utils_test.go index 9cd3339e..b8cf7ac3 100644 --- a/internal/utils/utils_test.go +++ b/internal/utils/utils_test.go @@ -1,7 +1,6 @@ package utils import ( - "io/ioutil" "os" "testing" @@ -29,7 +28,7 @@ func TestSplitKeyValueSlice(t *testing.T) { } func TestPathExists(t *testing.T) { - file, err := ioutil.TempFile("", "test") + file, err := os.CreateTemp("", "test") if err != nil { t.Fatal(err) } diff --git a/templates/dnsmasq.hosts.conf.tmpl b/templates/dnsmasq.hosts.conf.tmpl index a8de9be0..12a8e18e 100644 --- a/templates/dnsmasq.hosts.conf.tmpl +++ b/templates/dnsmasq.hosts.conf.tmpl @@ -1,3 +1,6 @@ +{{/* Simple dnsmasq template generating host entries */}} +{{/* Domains are hard-coded, replace 'docker.comany.com' below */}} + {{$domain := "docker.company.com"}} {{range $key, $value := .}} # {{ $value.Name }} ({{$value.ID}} from {{$value.Image.Repository}}) diff --git a/templates/etcd.tmpl b/templates/etcd.tmpl index fce6ea21..9ccdf528 100644 --- a/templates/etcd.tmpl +++ b/templates/etcd.tmpl @@ -1,3 +1,4 @@ +{{/* etcd template to generate registration script */}} #!/bin/bash diff --git a/templates/fluentd.conf.tmpl b/templates/fluentd.conf.tmpl index 92149b27..9a666c46 100644 --- a/templates/fluentd.conf.tmpl +++ b/templates/fluentd.conf.tmpl @@ -1,3 +1,4 @@ +{{/* Generates fluentd configuration entries */}} ## File input ## read docker logs with tag=docker.container diff --git a/templates/logrotate.tmpl b/templates/logrotate.tmpl index fe2a5dd5..b1b6e7b9 100644 --- a/templates/logrotate.tmpl +++ b/templates/logrotate.tmpl @@ -1,3 +1,7 @@ +{{/* Generate logrotate snippets for logrotate based on files listed in */}} +{{/* the comma separated environment variable LOG_FILES */}} +{{/* e.g. docker run --env='/var/log/messages,/var/log/lastlog' ... */}} + {{ range $index, $value := $ }} {{ $logs := $value.Env.LOG_FILES }} {{ if $logs }} diff --git a/templates/nginx.tmpl b/templates/nginx.tmpl index f4aa32e0..25dc0bc5 100644 --- a/templates/nginx.tmpl +++ b/templates/nginx.tmpl @@ -1,3 +1,8 @@ +{{/* default nginx configuration template */}} +{{/* Generate a configuration file based on the containers mandatory */}} +{{/* VIRTUAL_HOST environment variable and the exposed ports. If multiple */}} +{{/* ports are exposed, the first one is used, unless set with VIRTUAL_PORT */}} + server { listen 80 default_server; server_name _; # This is just an invalid value which will never trigger on a real hostname. @@ -13,6 +18,12 @@ upstream {{ $host }} { {{ $addrLen := len $value.Addresses }} {{ $network := index $value.Networks 0 }} + + {{ if $value.State.Health.Status }} + {{ if ne $value.State.Health.Status "healthy" }} + {{ continue }} + {{ end }} + {{ end }} {{/* If only 1 port exposed, use that */}} {{ if eq $addrLen 1 }} @@ -62,4 +73,4 @@ server { proxy_set_header Connection ""; } } -{{ end }} \ No newline at end of file +{{ end }}