From daf58bb4ca61893a5434197d121f6e7e55160fb4 Mon Sep 17 00:00:00 2001 From: Markus Pesch Date: Mon, 27 Jun 2022 21:42:59 +0200 Subject: [PATCH] Initial Commit --- .dockerignore | 1 + .drone.yml | 697 +++++++++++++++++++++++++++++++++++++++ .gitignore | 5 + .golangci.yml | 42 +++ .markdownlint.yaml | 144 ++++++++ Dockerfile | 26 ++ LICENSE | 20 ++ Makefile | 118 +++++++ README.md | 126 +++++++ cmd/cmd.go | 583 ++++++++++++++++++++++++++++++++ cmd/completion.go | 67 ++++ go.mod | 28 ++ go.sum | 483 +++++++++++++++++++++++++++ main.go | 11 + manifest.tmpl | 26 ++ pkg/domain/author.go | 7 + pkg/domain/build.go | 33 ++ pkg/domain/commit.go | 10 + pkg/domain/job.go | 8 + pkg/domain/prev.go | 6 + pkg/domain/prevBuild.go | 6 + pkg/domain/prevCommit.go | 5 + pkg/domain/remote.go | 5 + pkg/domain/repo.go | 13 + pkg/domain/smtp.go | 13 + pkg/domain/yaml.go | 6 + pkg/flags/flags.go | 56 ++++ pkg/mail/assets/mail.txt | 278 ++++++++++++++++ pkg/mail/mail.go | 180 ++++++++++ renovate.json | 37 +++ 30 files changed, 3040 insertions(+) create mode 100644 .dockerignore create mode 100644 .drone.yml create mode 100644 .gitignore create mode 100644 .golangci.yml create mode 100644 .markdownlint.yaml create mode 100644 Dockerfile create mode 100644 LICENSE create mode 100644 Makefile create mode 100644 README.md create mode 100644 cmd/cmd.go create mode 100644 cmd/completion.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go create mode 100644 manifest.tmpl create mode 100644 pkg/domain/author.go create mode 100644 pkg/domain/build.go create mode 100644 pkg/domain/commit.go create mode 100644 pkg/domain/job.go create mode 100644 pkg/domain/prev.go create mode 100644 pkg/domain/prevBuild.go create mode 100644 pkg/domain/prevCommit.go create mode 100644 pkg/domain/remote.go create mode 100644 pkg/domain/repo.go create mode 100644 pkg/domain/smtp.go create mode 100644 pkg/domain/yaml.go create mode 100644 pkg/flags/flags.go create mode 100644 pkg/mail/assets/mail.txt create mode 100644 pkg/mail/mail.go create mode 100644 renovate.json diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..daaa79a --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +drone-email \ No newline at end of file diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..06c1b21 --- /dev/null +++ b/.drone.yml @@ -0,0 +1,697 @@ +--- +kind: pipeline +type: kubernetes +name: linter + +platform: + os: linux + +steps: +- name: markdown lint + commands: + - markdownlint *.md + image: docker.io/volkerraschek/markdownlint:0.32.0 + resources: + limits: + cpu: 150 + memory: 150M + +- name: email-notification + environment: + SMTP_FROM_ADDRESS: + from_secret: smtp_from_address + SMTP_FROM_NAME: + from_secret: smtp_from_name + SMTP_HOST: + from_secret: smtp_host + SMTP_USERNAME: + from_secret: smtp_username + SMTP_PASSWORD: + from_secret: smtp_password + image: docker.io/volkerraschek/drone-email:0.1.0 + resources: + limits: + cpu: 150 + memory: 150M + when: + status: + - changed + - failure + +trigger: + event: + exclude: + - tag + +--- +kind: pipeline +type: docker +name: dry-run-amd64 + +depends_on: +- linter + +platform: + os: linux + arch: amd64 + +steps: +- name: build + image: docker.io/plugins/docker:latest + settings: + auto_tag: false + dockerfile: Dockerfile + dry_run: true + force_tag: true + no_cache: true + purge: true + repo: volkerraschek/drone-email + tags: latest-amd64 + username: + from_secret: container_image_registry_user + password: + from_secret: container_image_registry_password + volumes: + - name: docker_socket + path: /var/run/docker.sock + +- name: notify + image: docker.io/drillster/drone-email:latest + environment: + PLUGIN_HOST: + from_secret: smtp_host + PLUGIN_USERNAME: + from_secret: smtp_username + PLUGIN_PASSWORD: + from_secret: smtp_password + PLUGIN_FROM: + from_secret: smtp_mail_address + when: + status: + - changed + - failure + +trigger: + branch: + exclude: + - master + event: + - pull_request + - push + repo: + - volker.raschek/docker-email + +volumes: +- name: docker_socket + host: + path: /var/run/docker.sock + +--- +kind: pipeline +type: docker +name: dry-run-arm-v7 + +depends_on: +- linter + +platform: + os: linux + arch: arm + +steps: +- name: build + image: docker.io/plugins/docker:latest + settings: + auto_tag: false + dockerfile: Dockerfile + dry_run: true + force_tag: true + no_cache: true + purge: true + repo: volkerraschek/drone-email + tags: latest-arm-v7 + username: + from_secret: container_image_registry_user + password: + from_secret: container_image_registry_password + +- name: notify + image: docker.io/drillster/drone-email:latest + environment: + PLUGIN_HOST: + from_secret: smtp_host + PLUGIN_USERNAME: + from_secret: smtp_username + PLUGIN_PASSWORD: + from_secret: smtp_password + PLUGIN_FROM: + from_secret: smtp_mail_address + volumes: + - name: docker_socket + path: /var/run/docker.sock + when: + status: + - changed + - failure + +trigger: + branch: + exclude: + - master + event: + - pull_request + - push + repo: + - volker.raschek/docker-email + +volumes: +- name: docker_socket + host: + path: /var/run/docker.sock + +--- +kind: pipeline +type: docker +name: dry-run-arm64-v8 + +depends_on: +- linter + +platform: + os: linux + arch: arm64 + +steps: +- name: build + image: docker.io/plugins/docker:latest + settings: + auto_tag: false + dockerfile: Dockerfile + dry_run: true + force_tag: true + no_cache: true + purge: true + repo: volkerraschek/drone-email + tags: latest-arm64-v8 + username: + from_secret: container_image_registry_user + password: + from_secret: container_image_registry_password + +- name: notify + image: docker.io/drillster/drone-email:latest + environment: + PLUGIN_HOST: + from_secret: smtp_host + PLUGIN_USERNAME: + from_secret: smtp_username + PLUGIN_PASSWORD: + from_secret: smtp_password + PLUGIN_FROM: + from_secret: smtp_mail_address + volumes: + - name: docker_socket + path: /var/run/docker.sock + when: + status: + - changed + - failure + +trigger: + branch: + exclude: + - master + event: + - pull_request + - push + repo: + - volker.raschek/docker-email + +volumes: +- name: docker_socket + host: + path: /var/run/docker.sock + +--- +kind: pipeline +type: docker +name: latest-amd64 + +depends_on: +- linter + +platform: + os: linux + arch: amd64 + +steps: +- name: build + image: docker.io/plugins/docker:latest + settings: + auto_tag: false + dockerfile: Dockerfile + force_tag: true + no_cache: true + purge: true + repo: volkerraschek/drone-email + tags: latest-amd64 + username: + from_secret: container_image_registry_user + password: + from_secret: container_image_registry_password + volumes: + - name: docker_socket + path: /var/run/docker.sock + +- name: email-notification + environment: + SMTP_FROM_ADDRESS: + from_secret: smtp_from_address + SMTP_FROM_NAME: + from_secret: smtp_from_name + SMTP_HOST: + from_secret: smtp_host + SMTP_USERNAME: + from_secret: smtp_username + SMTP_PASSWORD: + from_secret: smtp_password + image: docker.io/volkerraschek/drone-email:0.1.0 + when: + status: + - changed + - failure + +trigger: + branch: + - master + event: + - cron + - push + repo: + - volker.raschek/drone-email + +volumes: +- name: docker_socket + host: + path: /var/run/docker.sock + +--- +kind: pipeline +type: docker +name: latest-arm-v7 + +depends_on: +- linter + +platform: + os: linux + arch: arm + +steps: +- name: build + image: docker.io/plugins/docker:latest + settings: + auto_tag: false + dockerfile: Dockerfile + force_tag: true + no_cache: true + purge: true + repo: volkerraschek/drone-email + tags: latest-arm-v7 + username: + from_secret: container_image_registry_user + password: + from_secret: container_image_registry_password + +- name: email-notification + environment: + SMTP_FROM_ADDRESS: + from_secret: smtp_from_address + SMTP_FROM_NAME: + from_secret: smtp_from_name + SMTP_HOST: + from_secret: smtp_host + SMTP_USERNAME: + from_secret: smtp_username + SMTP_PASSWORD: + from_secret: smtp_password + image: docker.io/volkerraschek/drone-email:0.1.0 + when: + status: + - changed + - failure + +trigger: + branch: + - master + event: + - cron + - push + repo: + - volker.raschek/drone-email + +volumes: +- name: docker_socket + host: + path: /var/run/docker.sock + +--- +kind: pipeline +type: docker +name: latest-arm64-v8 + +depends_on: +- linter + +platform: + os: linux + arch: arm64 + +steps: +- name: build + image: docker.io/plugins/docker:latest + settings: + auto_tag: false + dockerfile: Dockerfile + force_tag: true + no_cache: true + purge: true + repo: volkerraschek/drone-email + tags: latest-arm64-v8 + username: + from_secret: container_image_registry_user + password: + from_secret: container_image_registry_password + +- name: email-notification + environment: + SMTP_FROM_ADDRESS: + from_secret: smtp_from_address + SMTP_FROM_NAME: + from_secret: smtp_from_name + SMTP_HOST: + from_secret: smtp_host + SMTP_USERNAME: + from_secret: smtp_username + SMTP_PASSWORD: + from_secret: smtp_password + image: docker.io/volkerraschek/drone-email:0.1.0 + when: + status: + - changed + - failure + +trigger: + branch: + - master + event: + - cron + - push + repo: + - volker.raschek/drone-email + +volumes: +- name: docker_socket + host: + path: /var/run/docker.sock + +--- +kind: pipeline +type: kubernetes +name: latest-manifest + +depends_on: +- latest-amd64 +- latest-arm-v7 +- latest-arm64-v8 + +steps: +- name: build-manifest + image: docker.io/plugins/manifest:latest + settings: + auto_tag: false + ignore_missing: true + spec: manifest.tmpl + username: + from_secret: container_image_registry_user + password: + from_secret: container_image_registry_password + +- name: email-notification + environment: + SMTP_FROM_ADDRESS: + from_secret: smtp_from_address + SMTP_FROM_NAME: + from_secret: smtp_from_name + SMTP_HOST: + from_secret: smtp_host + SMTP_USERNAME: + from_secret: smtp_username + SMTP_PASSWORD: + from_secret: smtp_password + image: docker.io/volkerraschek/drone-email:0.1.0 + resources: + limits: + cpu: 150 + memory: 150M + when: + status: + - changed + - failure + +trigger: + branch: + - master + event: + - cron + - push + repo: + - volker.raschek/drone-email + +--- +kind: pipeline +type: docker +name: tagged-amd64 + +platform: + os: linux + arch: amd64 + +steps: +- name: build + image: docker.io/plugins/docker:latest + settings: + auto_tag_suffix: amd64 + auto_tag: true + dockerfile: Dockerfile + force_tag: true + no_cache: true + purge: true + repo: volkerraschek/drone-email + username: + from_secret: container_image_registry_user + password: + from_secret: container_image_registry_password + build_args: + - VERSION=${DRONE_TAG} + volumes: + - name: docker_socket + path: /var/run/docker.sock + +- name: email-notification + environment: + SMTP_FROM_ADDRESS: + from_secret: smtp_from_address + SMTP_FROM_NAME: + from_secret: smtp_from_name + SMTP_HOST: + from_secret: smtp_host + SMTP_USERNAME: + from_secret: smtp_username + SMTP_PASSWORD: + from_secret: smtp_password + image: docker.io/volkerraschek/drone-email:0.1.0 + when: + status: + - changed + - failure + +trigger: + event: + - tag + repo: + - volker.raschek/drone-email + +volumes: +- name: docker_socket + host: + path: /var/run/docker.sock + +--- +kind: pipeline +type: docker +name: tagged-arm-v7 + +platform: + os: linux + arch: arm + +steps: +- name: build + image: docker.io/plugins/docker:latest + settings: + auto_tag_suffix: arm-v7 + auto_tag: true + dockerfile: Dockerfile + force_tag: true + no_cache: true + purge: true + repo: volkerraschek/drone-email + username: + from_secret: container_image_registry_user + password: + from_secret: container_image_registry_password + build_args: + - VERSION=${DRONE_TAG} + volumes: + - name: docker_socket + path: /var/run/docker.sock + +- name: email-notification + environment: + SMTP_FROM_ADDRESS: + from_secret: smtp_from_address + SMTP_FROM_NAME: + from_secret: smtp_from_name + SMTP_HOST: + from_secret: smtp_host + SMTP_USERNAME: + from_secret: smtp_username + SMTP_PASSWORD: + from_secret: smtp_password + image: docker.io/volkerraschek/drone-email:0.1.0 + when: + status: + - changed + - failure + +trigger: + event: + - tag + repo: + - volker.raschek/drone-email + +volumes: +- name: docker_socket + host: + path: /var/run/docker.sock + +--- +kind: pipeline +type: docker +name: tagged-arm64-v8 + +platform: + os: linux + arch: arm64 + +steps: +- name: build + image: docker.io/plugins/docker:latest + settings: + auto_tag_suffix: arm64-v8 + auto_tag: true + dockerfile: Dockerfile + force_tag: true + no_cache: true + purge: true + repo: volkerraschek/drone-email + username: + from_secret: container_image_registry_user + password: + from_secret: container_image_registry_password + build_args: + - VERSION=${DRONE_TAG} + volumes: + - name: docker_socket + path: /var/run/docker.sock + +- name: email-notification + environment: + SMTP_FROM_ADDRESS: + from_secret: smtp_from_address + SMTP_FROM_NAME: + from_secret: smtp_from_name + SMTP_HOST: + from_secret: smtp_host + SMTP_USERNAME: + from_secret: smtp_username + SMTP_PASSWORD: + from_secret: smtp_password + image: docker.io/volkerraschek/drone-email:0.1.0 + + when: + status: + - changed + - failure + +trigger: + event: + - tag + repo: + - volker.raschek/drone-email + +volumes: +- name: docker_socket + host: + path: /var/run/docker.sock + +--- +kind: pipeline +type: kubernetes +name: tagged-manifest + +depends_on: +- tagged-amd64 +- tagged-arm-v7 +- tagged-arm64-v8 + +steps: +- name: build-manifest + image: docker.io/plugins/manifest:latest + settings: + auto_tag: true + ignore_missing: true + spec: manifest.tmpl + username: + from_secret: container_image_registry_user + password: + from_secret: container_image_registry_password + +- name: email-notification + environment: + SMTP_FROM_ADDRESS: + from_secret: smtp_from_address + SMTP_FROM_NAME: + from_secret: smtp_from_name + SMTP_HOST: + from_secret: smtp_host + SMTP_USERNAME: + from_secret: smtp_username + SMTP_PASSWORD: + from_secret: smtp_password + image: docker.io/volkerraschek/drone-email:0.1.0 + resources: + limits: + cpu: 150 + memory: 150M + when: + status: + - changed + - failure + +trigger: + event: + - tag + repo: + - volker.raschek/drone-email diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fe9a469 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +drone-email* +config.yaml +container-test.sh +coverage.txt +variables.yaml \ No newline at end of file diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..65eab79 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,42 @@ +run: + skip-dirs: + - it + timeout: 10m + tests: true + +linters: + disable-all: true + enable: + # Default + - deadcode + - errcheck + - gosimple + - govet + - ineffassign + - staticcheck + - structcheck + - typecheck + - unused + - varcheck + + # Additionally linters + - containedctx + - decorder + - errname + - gosec + - ireturn + - makezero + - misspell + - unparam + - whitespace + - wrapcheck + +linters-settings: + # https://golangci-lint.run/usage/linters/#ireturn + ireturn: + allow: + - anon + - error + - empty + - stdlib + - github.com\/dedalus-cis4u\/proxy\/t1k\/test\/scheme\.Database diff --git a/.markdownlint.yaml b/.markdownlint.yaml new file mode 100644 index 0000000..4a86362 --- /dev/null +++ b/.markdownlint.yaml @@ -0,0 +1,144 @@ +# markdownlint YAML configuration +# https://github.com/DavidAnson/markdownlint/blob/main/schema/.markdownlint.yaml + +# Default state for all rules +default: true + +# Path to configuration file to extend +extends: null + +# MD003/heading-style/header-style - Heading style +MD003: + # Heading style + style: "atx" + +# MD004/ul-style - Unordered list style +MD004: + style: "dash" + +# MD007/ul-indent - Unordered list indentation +MD007: + # Spaces for indent + indent: 2 + # Whether to indent the first level of the list + start_indented: false + +# MD009/no-trailing-spaces - Trailing spaces +MD009: + # Spaces for line break + br_spaces: 2 + # Allow spaces for empty lines in list items + list_item_empty_lines: false + # Include unnecessary breaks + strict: false + +# MD010/no-hard-tabs - Hard tabs +MD010: + # Include code blocks + code_blocks: true + +# MD012/no-multiple-blanks - Multiple consecutive blank lines +MD012: + # Consecutive blank lines + maximum: 1 + +# MD013/line-length - Line length +MD013: + # Number of characters + line_length: 80 + # Number of characters for headings + heading_line_length: 80 + # Number of characters for code blocks + code_block_line_length: 80 + # Include code blocks + code_blocks: false + # Include tables + tables: false + # Include headings + headings: true + # Include headings + headers: true + # Strict length checking + strict: false + # Stern length checking + stern: false + +# MD022/blanks-around-headings/blanks-around-headers - Headings should be surrounded by blank lines +MD022: + # Blank lines above heading + lines_above: 1 + # Blank lines below heading + lines_below: 1 + +# MD024/no-duplicate-heading/no-duplicate-header - Multiple headings with the same content +MD024: + # Only check sibling headings + allow_different_nesting: true + +# MD025/single-title/single-h1 - Multiple top-level headings in the same document +MD025: + # Heading level + level: 1 + # RegExp for matching title in front matter + front_matter_title: "^\\s*title\\s*[:=]" + +# MD026/no-trailing-punctuation - Trailing punctuation in heading +MD026: + # Punctuation characters + punctuation: ".,;:!。,;:!" + +# MD029/ol-prefix - Ordered list item prefix +MD029: + # List style + style: "one_or_ordered" + +# MD030/list-marker-space - Spaces after list markers +MD030: + # Spaces for single-line unordered list items + ul_single: 1 + # Spaces for single-line ordered list items + ol_single: 1 + # Spaces for multi-line unordered list items + ul_multi: 1 + # Spaces for multi-line ordered list items + ol_multi: 1 + +# MD033/no-inline-html - Inline HTML +MD033: + # Allowed elements + allowed_elements: [] + +# MD035/hr-style - Horizontal rule style +MD035: + # Horizontal rule style + style: "---" + +# MD036/no-emphasis-as-heading/no-emphasis-as-header - Emphasis used instead of a heading +MD036: + # Punctuation characters + punctuation: ".,;:!?。,;:!?" + +# MD041/first-line-heading/first-line-h1 - First line in a file should be a top-level heading +MD041: + # Heading level + level: 1 + # RegExp for matching title in front matter + front_matter_title: "^\\s*title\\s*[:=]" + +# MD044/proper-names - Proper names should have the correct capitalization +MD044: + # List of proper names + names: + - Gitea + # Include code blocks + code_blocks: false + +# MD046/code-block-style - Code block style +MD046: + # Block style + style: "fenced" + +# MD048/code-fence-style - Code fence style +MD048: + # Code fence syle + style: "backtick" \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..26b3fb4 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,26 @@ +FROM docker.io/library/golang:1.18.3-alpine3.16 AS build + +ARG GONOPROXY +ARG GONOSUMDB +ARG GOPRIVATE +ARG GOPROXY +ARG VERSION + +COPY . /workspace + +WORKDIR /workspace + +RUN set -ex && \ + apk update && \ + apk add git make && \ + make install DESTDIR=/drone-email PREFIX=/usr VERSION=${VERSION} + +############################################################################### + +FROM docker.io/library/alpine:3.16 + +RUN apk add --no-cache bash bash-completion ca-certificates tzdata + +COPY --from=build /drone-email / + +ENTRYPOINT [ "/usr/bin/drone-email" ] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8b29665 --- /dev/null +++ b/LICENSE @@ -0,0 +1,20 @@ +Copyright (c) 2022 Markus Pesch + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c37d828 --- /dev/null +++ b/Makefile @@ -0,0 +1,118 @@ +VERSION?=$(shell git describe --abbrev=0)+$(shell date +'%Y%m%d_%H%M%S') + +EXECUTABLE:=drone-email + +DESTDIR?= +PREFIX?=/usr/local + +# CONTAINER_RUNTIME +CONTAINER_RUNTIME?=$(shell which podman) + +# DRONEEMAIL_IMAGE +DRONEEMAIL_IMAGE_REGISTRY_HOST?=docker.io +DRONEEMAIL_IMAGE_REPOSITORY=volkerraschek/${EXECUTABLE} +DRONEEMAIL_IMAGE_VERSION?=latest +DRONEEMAIL_IMAGE_FULLY_QUALIFIED=${DRONEEMAIL_IMAGE_REGISTRY_HOST}/${DRONEEMAIL_IMAGE_REPOSITORY}:${DRONEEMAIL_IMAGE_VERSION} +DRONEEMAIL_IMAGE_UNQUALIFIED=${DRONEEMAIL_IMAGE_REPOSITORY}:${DRONEEMAIL_IMAGE_VERSION} + +# BINARIES +# ============================================================================== +EXECUTABLES := ${EXECUTABLE} +EXECUTABLES += $(addsuffix .sh, ${EXECUTABLE}) +EXECUTABLES += $(addsuffix .fish, ${EXECUTABLE}) +EXECUTABLES += $(addsuffix .zsh, ${EXECUTABLE}) + +all: ${EXECUTABLES} + +${EXECUTABLE}: + CGO_ENABLED=0 \ + GONOPROXY=$(shell go env GONOPROXY) \ + GONOSUMDB=$(shell go env GONOSUMDB) \ + GOPRIVATE=$(shell go env GOPRIVATE) \ + GOPROXY=$(shell go env GOPROXY) \ + go build -ldflags "-X main.version=${VERSION:v%=%}" -o ${@} + +${EXECUTABLE}.sh: ${EXECUTABLE} + ./${EXECUTABLE} completion bash > ${EXECUTABLE}.sh + +${EXECUTABLE}.fish: ${EXECUTABLE} + ./${EXECUTABLE} completion fish > ${EXECUTABLE}.fish + +${EXECUTABLE}.zsh: ${EXECUTABLE} + ./${EXECUTABLE} completion zsh > ${EXECUTABLE}.zsh + +# UN/INSTALL +# ============================================================================== +PHONY+=install +install: all + install --directory ${DESTDIR}${PREFIX}/bin + install --mode 755 ${EXECUTABLE} ${DESTDIR}${PREFIX}/bin/${EXECUTABLE} + + install --directory ${DESTDIR}/etc/bash_completion.d + install --mode 644 ${EXECUTABLE}.sh ${DESTDIR}/etc/bash_completion.d/${EXECUTABLE}.sh + + install --directory ${DESTDIR}${PREFIX}/share/fish/vendor_completions.d + install --mode 644 ${EXECUTABLE}.fish ${DESTDIR}${PREFIX}/share/fish/vendor_completions.d/${EXECUTABLE}.fish + + install --directory ${DESTDIR}${PREFIX}/share/zsh/site-functions + install --mode 644 ${EXECUTABLE}.zsh ${DESTDIR}${PREFIX}/share/zsh/site-functions/_${EXECUTABLE}.zsh + + install --directory ${DESTDIR}${PREFIX}/licenses/${EXECUTABLE} + install --mode 644 LICENSE ${DESTDIR}${PREFIX}/licenses/${EXECUTABLE}/LICENSE + +PHONY+=uninstall +uninstall: + -rm --recursive --force \ + ${DESTDIR}${PREFIX}/bin/${EXECUTABLE} \ + ${DESTDIR}/etc/bash_completion.d/${EXECUTABLE}.sh \ + ${DESTDIR}${PREFIX}/share/fish/vendor_completions.d/${EXECUTABLE}.fish \ + ${DESTDIR}${PREFIX}/share/zsh/site-functions/_${EXECUTABLE}.zsh \ + ${DESTDIR}${PREFIX}/licenses/${EXECUTABLE}/LICENSE + +# CLEAN +# ============================================================================== +PHONY+=clean +clean: + -rm -rf ${EXECUTABLE}* + +# TEST +# ============================================================================== +PHONY+=test/unit +test/unit: + go test -v -race -coverprofile=coverage.txt -covermode=atomic -timeout 600s -count=1 ./... + +PHONY+=test/coverage +test/coverage: test/unit + go tool cover -html=coverage.txt + +# GOLANGCI-LINT +# ============================================================================== +PHONY+=golangci-lint +golangci-lint: + golangci-lint run --concurrency=$(shell nproc) + +# CONTAINER-IMAGE +# ============================================================================== +PHONY+=container-image/build +container-image/build: + ${CONTAINER_RUNTIME} build \ + --build-arg GONOPROXY=${GOPROXY} \ + --build-arg GONOSUMDB=${GONOSUMDB} \ + --build-arg GOPRIVATE=${GOPRIVATE} \ + --build-arg GOPROXY=${GOPROXY} \ + --build-arg VERSION=${VERSION} \ + --file ./Dockerfile \ + --no-cache \ + --tag ${DRONEEMAIL_IMAGE_UNQUALIFIED} \ + --tag ${DRONEEMAIL_IMAGE_FULLY_QUALIFIED} \ + . + +PHONY+=container-image/push +container-image/push: container-image/build + ${CONTAINER_RUNTIME} push ${DRONEEMAIL_IMAGE_FULLY_QUALIFIED} + +# PHONY +# ============================================================================== +# Declare the contents of the PHONY variable as phony. We keep that information +# in a variable so we can use it in if_changed. +.PHONY: ${PHONY} \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..63ef964 --- /dev/null +++ b/README.md @@ -0,0 +1,126 @@ +# drone-email + +[![Build Status](https://drone.cryptic.systems/api/badges/volker.raschek/drone-email/status.svg)](https://drone.cryptic.systems/volker.raschek/drone-email) + +A Drone CI/CD plugin to send build status notifications via email. The plugin is +currently available for the following architectures: + +- x86_64 / amd64 +- aarch64 / arm64 +- armv7 / arm + +## Compile or install the binary locally + +Checkout the source code of the project and use `make` to compile or install the +binary locally. + +```bash +make all # compile all targets, including shell completions +make drone-email # compile only the binary +make install # install the binary with completions locally +``` + +## Usage + +All params can be defined via cli flags. A list of all provided cli-flags will +be written to `stdout` via `drone-email --help`. + +Alternatively can be the flags defined via environment variables or a config file. + +### Environment variables + +| name | description | +| ------------------------------- | ----------------------------------------------- | +| `DRONE_BUILD_CREATED` | Unix timestamp when the build has been created | +| `DRONE_BUILD_EVENT` | Drone event which triggered the build | +| `DRONE_BUILD_FINISHED` | Unix timestamp when the build has been finished | +| `DRONE_BUILD_LINK` | URL to the build pipeline | +| `DRONE_BUILD_NUMBER` | Build number | +| `DRONE_BUILD_STARTED` | Unix timestamp when the build has been started | +| `DRONE_BUILD_STATUS` | Build status | +| `DRONE_COMMIT_AUTHOR_NAME` | Name of the commit author | +| `DRONE_COMMIT_AUTHOR_AVATAR` | Avatar of the commit author | +| `DRONE_COMMIT_AUTHOR_EMAIL` | EMail of the commit author | +| `DRONE_COMMIT_BRANCH` | Commit branch | +| `DRONE_COMMIT_LINK` | Link to the commit | +| `DRONE_COMMIT_MESSAGE` | Commit message | +| `DRONE_COMMIT_REF` | Commit reference | +| `DRONE_COMMIT_SHA` | Commit sha sum | +| `DRONE_DEPLOY_TO` | Deploy target | +| `DRONE_JOB_EXIT_CODE` | Job exit code | +| `DRONE_JOB_FINISHED` | Unix timestamp when the job has been created | +| `DRONE_JOB_NUMBER` | Job number | +| `DRONE_JOB_STARTED` | Unix timestamp when the job has been started | +| `DRONE_JOB_STATUS` | Job status | +| `DRONE_PREV_BUILD_NUMBER` | Previous build number | +| `DRONE_PREV_BUILD_STATUS` | Previous build status | +| `DRONE_PREV_COMMIT_SHA` | Previous commit sha sum | +| `DRONE_PULL_REQUEST` | Number of pull-requests | +| `DRONE_REMOTE_URL` | Clone URL of the repository | +| `DRONE_REPO` | Name of the repository, including org/owner | +| `DRONE_REPO_AVATAR` | Avatar of the repository | +| `DRONE_REPO_BRANCH` | Branch of the repository | +| `DRONE_REPO_LINK` | URL of the repository | +| `DRONE_REPO_NAME` | Name of the repository, without org/owner | +| `DRONE_REPO_OWNER` | Org/Owner of the repository | +| `DRONE_REPO_PRIVATE` | Private repository | +| `DRONE_REPO_SCM` | SCM of the repository | +| `DRONE_REPO_TRUSTED` | Trusted repository | +| `DRONE_TAG` | Tag | +| `DRONE_YAML_SIGNED` | Yaml is singed | +| `DRONE_YAML_VERIFIED` | Yaml is trusted | +| `SMTP_FROM_ADDRESS` | SMTP-From Address | +| `SMTP_FROM_NAME` | SMTP-From Name | +| `SMTP_HELO` | SMTP-HELO\EHLO | +| `SMTP_HOST` | SMTP-Host | +| `SMTP_MAIL_SUBJECT` | Overwrite default mail subject template | +| `SMTP_PASSWORD` | SMTP-Password | +| `SMTP_PORT` | SMTP-Port | +| `SMTP_START_TLS` | SMTP-Start-TLS | +| `SMTP_TLS_INSECURE_SKIP_VERIFY` | Trust insecure TLS certificate | +| `SMTP_TO_ADDRESSES` | SMTP-To Addresses | +| `SMTP_USERNAME` | SMTP-Username | + +### Config file + +Instead of environment variables, a `config.yaml` can be places in +`/etc/drone-email` or next to the binary. + +The yaml should contain the same parameters as the cli flags. For example: + +```yaml +drone-build-link: https://drone.example.local/max.mustermann/drone-email/1 +drone-build-number: 1 +drone-build-status: success +drone-build-started: 1656354006 +drone-commit-author-email: max@example.local +drone-commit-author-name: Max Mustermann +drone-commit-branch: master +drone-commit-sha: 06b44cbfa054f146881e7234f1773008f006a756 +drone-repo: max.mustermann/drone-email +drone-repo-link: https://git.example.local/max.mustermann/drone-email +smtp-from-address: noreply@example.local +smtp-from-name: noreply +smtp-helo: hostname.example.local +smtp-host: smtp1.example.local +smtp-password: my-password +smtp-username: noreply@example.local +``` + +## Known issues + +### Multiple success emails despite failed ci step + +The [drone-runner-kube](https://github.com/drone-runners/drone-runner-kube) does +not define the environment variable `DRONE_PREV_BUILD_STATUS` like the +[drone-runner-docker](https://github.com/drone-runners/drone-runner-docker). +This make it impossible to use the correct email template based on the build +state of the previous step. + +Furthermore, the environment variable `DRONE_BUILD_STATUS` is always defined as +`success`, even if the build has failed. + +Related issues: + +- [Drillster/drone-email](https://github.com/Drillster/drone-email/issues/69) +- [stackoverflow - drone-ci: get status of previous step](https://stackoverflow.com/questions/73096709/drone-ci-get-status-of-previous-step) diff --git a/cmd/cmd.go b/cmd/cmd.go new file mode 100644 index 0000000..3a652d2 --- /dev/null +++ b/cmd/cmd.go @@ -0,0 +1,583 @@ +package cmd + +import ( + "fmt" + "os" + "strings" + + "git.cryptic.systems/volker.raschek/drone-email-docker/pkg/domain" + "git.cryptic.systems/volker.raschek/drone-email-docker/pkg/flags" + "git.cryptic.systems/volker.raschek/drone-email-docker/pkg/mail" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "github.com/spf13/viper" +) + +const ( + // The name of our config file, without the file extension because viper + // supports many different config file languages. + defaultConfigFilename = "config" + defaultConfigExtension = "yaml" + + // The environment variable prefix of all environment variables bound to our command line flags. + // For example, --number is bound to STING_NUMBER. + envPrefix = "" +) + +func Execute(version string) error { + rootCmd := &cobra.Command{ + Use: "drone-email", + PersistentPreRunE: func(cmd *cobra.Command, args []string) error { + return initializeConfig(cmd) + }, + RunE: func(cmd *cobra.Command, args []string) error { + vars, err := newHTMLTemplateVarsByCommand(cmd) + if err != nil { + return fmt.Errorf("failed to initialize new html template vars: %w", err) + } + + smtpSettings, err := newSMTPSettingsByCommand(cmd) + if err != nil { + return fmt.Errorf("failed to initialize new config vars: %w", err) + } + + recipients, err := cmd.Flags().GetStringArray(flags.SMTP_TO_ADDRESSES) + if err != nil { + return fmt.Errorf("failed to detect value of %s: %w", flags.SMTP_TO_ADDRESSES, err) + } + + err = mail.NewPlugin(smtpSettings).Exec(cmd.Context(), recipients, vars) + if err != nil { + return fmt.Errorf("failed to execute mail plugin: %w", err) + } + + _, err = fmt.Fprint(os.Stdout, "E-Mails successfully sent") + if err != nil { + return fmt.Errorf("failed to write message on stdout: %w", err) + } + + return nil + }, + SilenceUsage: true, + Version: version, + } + + hostname, err := os.Hostname() + if err != nil { + return fmt.Errorf("failed to detect hostname: %w", err) + } + + // Drone environment variables/flags + // Flags which receive their values from environment variables of the drone + // CI/CD. + // + // The names of the FLags must match the environment variables, otherwise the + // environment variables will not be bound correctly to the flags. + rootCmd.Flags().Int64(flags.DRONE_BUILD_CREATED, 0, "Build created") + rootCmd.Flags().Int64(flags.DRONE_BUILD_FINISHED, 0, "Build finished") + rootCmd.Flags().Int64(flags.DRONE_BUILD_STARTED, 0, "Build stared") + rootCmd.Flags().String(flags.DRONE_BUILD_EVENT, "push", "Build event") + rootCmd.Flags().String(flags.DRONE_BUILD_LINK, "", "Build link") + rootCmd.Flags().Int(flags.DRONE_BUILD_NUMBER, 0, "Build number") + rootCmd.Flags().String(flags.DRONE_BUILD_STATUS, "success", "Build status") + + rootCmd.Flags().String(flags.DRONE_COMMIT_SHA, "", "SHA sum of the commit") + rootCmd.Flags().String(flags.DRONE_COMMIT_REF, "refs/heads/master", "Commit reference") + rootCmd.Flags().String(flags.DRONE_COMMIT_BRANCH, "master", "Commit branch") + rootCmd.Flags().String(flags.DRONE_COMMIT_LINK, "", "Link to the commit") + rootCmd.Flags().String(flags.DRONE_COMMIT_MESSAGE, "", "Commit message") + rootCmd.Flags().String(flags.DRONE_COMMIT_AUTHOR_NAME, "", "Name of the commit author") + rootCmd.Flags().String(flags.DRONE_COMMIT_AUTHOR_EMAIL, "", "E-Mail of the commit author") + rootCmd.Flags().String(flags.DRONE_COMMIT_AUTHOR_AVATAR, "", "Avatar of the commit author") + + rootCmd.Flags().String(flags.DRONE_DEPLOY_TO, "", "Deploy target") + + rootCmd.Flags().String(flags.DRONE_JOB_NUMBER, "", "Job number") + rootCmd.Flags().String(flags.DRONE_JOB_STATUS, "", "Job status") + rootCmd.Flags().Int(flags.DRONE_JOB_EXIT_CODE, 0, "Job exit code") + rootCmd.Flags().Int(flags.DRONE_JOB_STARTED, 0, "Job started") + rootCmd.Flags().Int(flags.DRONE_JOB_FINISHED, 0, "Job finished") + + rootCmd.Flags().String(flags.DRONE_PREV_BUILD_STATUS, "", "Previous build status") + rootCmd.Flags().Int(flags.DRONE_PREV_BUILD_NUMBER, 0, "Previous build number") + rootCmd.Flags().String(flags.DRONE_PREV_COMMIT_SHA, "", "Previous commit sha sum") + + rootCmd.Flags().Int(flags.DRONE_PULL_REQUEST, 0, "Number of pull-request") + + rootCmd.Flags().String(flags.DRONE_REMOTE_URL, "", "Clone URL of the repository") + + rootCmd.Flags().Bool(flags.DRONE_REPO_PRIVATE, true, "Repository is private") + rootCmd.Flags().Bool(flags.DRONE_REPO_TRUSTED, false, "Repository is trusted") + rootCmd.Flags().String(flags.DRONE_REPO_AVATAR, "", "Avatar URL of the repository") + rootCmd.Flags().String(flags.DRONE_REPO_BRANCH, "master", "Branch of the repository") + rootCmd.Flags().String(flags.DRONE_REPO_LINK, "", "URL to the repository") + rootCmd.Flags().String(flags.DRONE_REPO_NAME, "", "Name of the repository") + rootCmd.Flags().String(flags.DRONE_REPO_OWNER, "", "Name of the repository owner") + rootCmd.Flags().String(flags.DRONE_REPO_SCM, "git", "Source code management provider") + rootCmd.Flags().String(flags.DRONE_REPO, "", "Full name of the repository") + + rootCmd.Flags().String(flags.DRONE_TAG, "", "Tag") + + rootCmd.Flags().Bool(flags.DRONE_YAML_SIGNED, false, "YAML is signed") + rootCmd.Flags().Bool(flags.DRONE_YAML_VERIFIED, false, "YAML is verified") + + // MAIL SETTINGS + rootCmd.Flags().Bool(flags.SMTP_START_TLS, mail.DefaultSMTPStartTLS, "Use StartTLS instead of SSL") + rootCmd.Flags().Bool(flags.SMTP_TLS_INSECURE_SKIP_VERIFY, mail.DefaultSMTPTLSInsecureSkipVerify, "Trust insecure TLS certificates") + rootCmd.Flags().Int(flags.SMTP_PORT, mail.DefaultSMTPPort, "SMTP-Port") + rootCmd.Flags().String(flags.SMTP_FROM_ADDRESS, mail.DefaultSMTPFromAddress, "SMTP-From Address") + rootCmd.Flags().String(flags.SMTP_FROM_NAME, mail.DefaultSMTPFromName, "SMTP-From Name") + rootCmd.Flags().String(flags.SMTP_HELO, hostname, "SMTP-HELO/EHLO") + rootCmd.Flags().String(flags.SMTP_HOST, mail.DefaultSMTPHost, "SMTP-Host") + rootCmd.Flags().String(flags.SMTP_PASSWORD, "", "SMTP-Password") + rootCmd.Flags().String(flags.SMTP_USERNAME, "", "SMTP-User") + rootCmd.Flags().StringArray(flags.SMTP_TO_ADDRESSES, []string{}, "List of recipients") + + rootCmd.AddCommand(completionCmd) + + err = rootCmd.Execute() + if err != nil { + return fmt.Errorf("failed to execute root cmd: %w", err) + } + + return nil +} + +func initializeConfig(cmd *cobra.Command) error { + v := viper.New() + + // Set the base name of the config file, without the file extension. + v.SetConfigName(defaultConfigFilename) + v.SetConfigType(defaultConfigExtension) + + // Set as many paths as you like where viper should look for the + // config file. We are only looking in the current working directory. + v.AddConfigPath(".") + v.AddConfigPath("$HOME/.config/drone-email/") + v.AddConfigPath("/etc/drone-email/") + + // Attempt to read the config file, gracefully ignoring errors + // caused by a config file not being found. Return an error + // if we cannot parse the config file. + if err := v.ReadInConfig(); err != nil { + // It's okay if there isn't a config file + if _, ok := err.(viper.ConfigFileNotFoundError); !ok { + return fmt.Errorf("failed to read config: %w", err) + } + } + + // Bind to environment variables + // Works great for simple config names, but needs help for names + // like --favorite-color which we fix in the bindFlags function + v.AutomaticEnv() + + // Bind the current command's flags to viper + bindFlags(cmd, v) + + return nil +} + +// Bind each cobra flag to its associated viper configuration (config file and environment variable) +func bindFlags(cmd *cobra.Command, v *viper.Viper) { + cmd.Flags().VisitAll(func(f *pflag.Flag) { + // Environment variables can't have dashes in them, so bind them to their equivalent + // keys with underscores, e.g. --favorite-color to STING_FAVORITE_COLOR + if strings.Contains(f.Name, "-") { + envVarSuffix := strings.ToUpper(strings.ReplaceAll(f.Name, "-", "_")) + if len(envPrefix) <= 0 { + _ = v.BindEnv(f.Name, envVarSuffix) + } else { + _ = v.BindEnv(f.Name, fmt.Sprintf("%s_%s", envPrefix, envVarSuffix)) + } + } + + // Apply the viper config value to the flag when the flag is not set and viper has a value + if !f.Changed && v.IsSet(f.Name) { + val := v.Get(f.Name) + _ = cmd.Flags().Set(f.Name, fmt.Sprintf("%v", val)) + } + }) +} + +func newHTMLTemplateVarsByCommand(cmd *cobra.Command) (*mail.CIVars, error) { + build, err := newBuildByCommand(cmd) + if err != nil { + return nil, fmt.Errorf("failed to initialize new build struct: %w", err) + } + + commit, err := newCommitByCommand(cmd) + if err != nil { + return nil, fmt.Errorf("failed to initialize new commit struct: %w", err) + } + + deployTo, err := cmd.Flags().GetString(flags.DRONE_DEPLOY_TO) + if err != nil { + return nil, fmt.Errorf("failed to detect value of %s: %w", flags.DRONE_DEPLOY_TO, err) + } + + job, err := newJobByCommand(cmd) + if err != nil { + return nil, fmt.Errorf("failed to initialize new job struct: %w", err) + } + + prev, err := newPrevByCommand(cmd) + if err != nil { + return nil, fmt.Errorf("failed to initialize new prev struct: %w", err) + } + + pullRequests, err := cmd.Flags().GetInt(flags.DRONE_PULL_REQUEST) + if err != nil { + return nil, fmt.Errorf("failed to detect value of %s: %w", flags.DRONE_PULL_REQUEST, err) + } + + remote, err := newRemoteByCommand(cmd) + if err != nil { + return nil, fmt.Errorf("failed to initialize new remote struct: %w", err) + } + + repo, err := newRepoByCommand(cmd) + if err != nil { + return nil, fmt.Errorf("failed to initialize new repo struct: %w", err) + } + + tag, err := cmd.Flags().GetString(flags.DRONE_TAG) + if err != nil { + return nil, fmt.Errorf("failed to detect value of %s: %w", flags.DRONE_TAG, err) + } + + yaml, err := newYAMLByCommand(cmd) + if err != nil { + return nil, fmt.Errorf("failed to initialize new yaml struct: %w", err) + } + + return &mail.CIVars{ + Build: build, + Commit: commit, + DeployTo: deployTo, + Job: job, + Prev: prev, + PullRequest: pullRequests, + Remote: remote, + Repo: repo, + Tag: tag, + Yaml: yaml, + }, nil +} + +func newBuildByCommand(cmd *cobra.Command) (*domain.Build, error) { + buildCreated, err := cmd.Flags().GetInt64(flags.DRONE_BUILD_CREATED) + if err != nil { + return nil, fmt.Errorf("failed to detect value of %s: %w", flags.DRONE_BUILD_CREATED, err) + } + + buildEvent, err := cmd.Flags().GetString(flags.DRONE_BUILD_EVENT) + if err != nil { + return nil, fmt.Errorf("failed to detect value of %s: %w", flags.DRONE_BUILD_EVENT, err) + } + + buildFinished, err := cmd.Flags().GetInt64(flags.DRONE_BUILD_FINISHED) + if err != nil { + return nil, fmt.Errorf("failed to detect value of %s: %w", flags.DRONE_BUILD_FINISHED, err) + } + + buildLink, err := cmd.Flags().GetString(flags.DRONE_BUILD_LINK) + if err != nil { + return nil, fmt.Errorf("failed to detect value of %s: %w", flags.DRONE_BUILD_LINK, err) + } + + buildNumber, err := cmd.Flags().GetInt(flags.DRONE_BUILD_NUMBER) + if err != nil { + return nil, fmt.Errorf("failed to detect value of %s: %w", flags.DRONE_BUILD_NUMBER, err) + } + + buildStared, err := cmd.Flags().GetInt64(flags.DRONE_BUILD_STARTED) + if err != nil { + return nil, fmt.Errorf("failed to detect value of %s: %w", flags.DRONE_BUILD_STARTED, err) + } + + buildStatus, err := cmd.Flags().GetString(flags.DRONE_BUILD_STATUS) + if err != nil { + return nil, fmt.Errorf("failed to detect value of %s: %w", flags.DRONE_BUILD_STATUS, err) + } + + build := &domain.Build{ + Created: buildCreated, + Event: buildEvent, + Finished: buildFinished, + Link: buildLink, + Number: buildNumber, + Started: buildStared, + Status: buildStatus, + } + + return build, nil +} + +func newCommitByCommand(cmd *cobra.Command) (*domain.Commit, error) { + authorAvatar, err := cmd.Flags().GetString(flags.DRONE_COMMIT_AUTHOR_AVATAR) + if err != nil { + return nil, fmt.Errorf("failed to detect value of %s: %w", flags.DRONE_COMMIT_AUTHOR_AVATAR, err) + } + + authorEmail, err := cmd.Flags().GetString(flags.DRONE_COMMIT_AUTHOR_EMAIL) + if err != nil { + return nil, fmt.Errorf("failed to detect value of %s: %w", flags.DRONE_COMMIT_AUTHOR_EMAIL, err) + } + + authorName, err := cmd.Flags().GetString(flags.DRONE_COMMIT_AUTHOR_NAME) + if err != nil { + return nil, fmt.Errorf("failed to detect value of %s: %w", flags.DRONE_COMMIT_AUTHOR_NAME, err) + } + + branch, err := cmd.Flags().GetString(flags.DRONE_COMMIT_BRANCH) + if err != nil { + return nil, fmt.Errorf("failed to detect value of %s: %w", flags.DRONE_COMMIT_BRANCH, err) + } + + link, err := cmd.Flags().GetString(flags.DRONE_COMMIT_LINK) + if err != nil { + return nil, fmt.Errorf("failed to detect value of %s: %w", flags.DRONE_COMMIT_LINK, err) + } + + message, err := cmd.Flags().GetString(flags.DRONE_COMMIT_MESSAGE) + if err != nil { + return nil, fmt.Errorf("failed to detect value of %s: %w", flags.DRONE_COMMIT_MESSAGE, err) + } + + ref, err := cmd.Flags().GetString(flags.DRONE_COMMIT_REF) + if err != nil { + return nil, fmt.Errorf("failed to detect value of %s: %w", flags.DRONE_COMMIT_REF, err) + } + + sha, err := cmd.Flags().GetString(flags.DRONE_COMMIT_SHA) + if err != nil { + return nil, fmt.Errorf("failed to detect value of %s: %w", flags.DRONE_COMMIT_SHA, err) + } + + commit := &domain.Commit{ + Author: &domain.Author{ + Avatar: authorAvatar, + Email: authorEmail, + Name: authorName, + }, + Branch: branch, + Link: link, + Message: message, + Ref: ref, + Sha: sha, + } + + return commit, nil +} + +func newJobByCommand(cmd *cobra.Command) (*domain.Job, error) { + exitCode, err := cmd.Flags().GetInt(flags.DRONE_JOB_EXIT_CODE) + if err != nil { + return nil, fmt.Errorf("failed to detect value of %s: %w", flags.DRONE_JOB_EXIT_CODE, err) + } + + finished, err := cmd.Flags().GetInt64(flags.DRONE_BUILD_FINISHED) + if err != nil { + return nil, fmt.Errorf("failed to detect value of %s: %w", flags.DRONE_BUILD_FINISHED, err) + } + + started, err := cmd.Flags().GetInt64(flags.DRONE_BUILD_STARTED) + if err != nil { + return nil, fmt.Errorf("failed to detect value of %s: %w", flags.DRONE_BUILD_STARTED, err) + } + + status, err := cmd.Flags().GetString(flags.DRONE_JOB_STATUS) + if err != nil { + return nil, fmt.Errorf("failed to detect value of %s: %w", flags.DRONE_JOB_STATUS, err) + } + + job := &domain.Job{ + ExitCode: exitCode, + Finished: finished, + Started: started, + Status: status, + } + + return job, nil +} + +func newPrevByCommand(cmd *cobra.Command) (*domain.Prev, error) { + prevBuildNumber, err := cmd.Flags().GetInt(flags.DRONE_PREV_BUILD_NUMBER) + if err != nil { + return nil, fmt.Errorf("failed to detect value of %s: %w", flags.DRONE_PREV_BUILD_NUMBER, err) + } + + prevBuildStatus, err := cmd.Flags().GetString(flags.DRONE_PREV_BUILD_STATUS) + if err != nil { + return nil, fmt.Errorf("failed to detect value of %s: %w", flags.DRONE_PREV_BUILD_STATUS, err) + } + + prevCommitSha, err := cmd.Flags().GetString(flags.DRONE_PREV_COMMIT_SHA) + if err != nil { + return nil, fmt.Errorf("failed to detect value of %s: %w", flags.DRONE_PREV_COMMIT_SHA, err) + } + + prev := &domain.Prev{ + Build: &domain.PrevBuild{ + Number: prevBuildNumber, + Status: prevBuildStatus, + }, + Commit: &domain.PrevCommit{ + Sha: prevCommitSha, + }, + } + + return prev, nil +} + +func newRemoteByCommand(cmd *cobra.Command) (*domain.Remote, error) { + remoteURL, err := cmd.Flags().GetString(flags.DRONE_REMOTE_URL) + if err != nil { + return nil, fmt.Errorf("failed to detect value of %s: %w", flags.DRONE_REMOTE_URL, err) + } + + remote := &domain.Remote{ + URL: remoteURL, + } + + return remote, nil +} + +func newRepoByCommand(cmd *cobra.Command) (*domain.Repo, error) { + avatar, err := cmd.Flags().GetString(flags.DRONE_REPO_AVATAR) + if err != nil { + return nil, fmt.Errorf("failed to detect value of %s: %w", flags.DRONE_REPO_AVATAR, err) + } + + branch, err := cmd.Flags().GetString(flags.DRONE_REPO_BRANCH) + if err != nil { + return nil, fmt.Errorf("failed to detect value of %s: %w", flags.DRONE_REPO_BRANCH, err) + } + + fullName, err := cmd.Flags().GetString(flags.DRONE_REPO_NAME) + if err != nil { + return nil, fmt.Errorf("failed to detect value of %s: %w", flags.DRONE_REPO_NAME, err) + } + + link, err := cmd.Flags().GetString(flags.DRONE_REPO_LINK) + if err != nil { + return nil, fmt.Errorf("failed to detect value of %s: %w", flags.DRONE_REPO_LINK, err) + } + + name, err := cmd.Flags().GetString(flags.DRONE_REPO) + if err != nil { + return nil, fmt.Errorf("failed to detect value of %s: %w", flags.DRONE_REPO, err) + } + + owner, err := cmd.Flags().GetString(flags.DRONE_REPO_OWNER) + if err != nil { + return nil, fmt.Errorf("failed to detect value of %s: %w", flags.DRONE_REPO_OWNER, err) + } + + private, err := cmd.Flags().GetBool(flags.DRONE_REPO_PRIVATE) + if err != nil { + return nil, fmt.Errorf("failed to detect value of %s: %w", flags.DRONE_REPO_PRIVATE, err) + } + + scm, err := cmd.Flags().GetString(flags.DRONE_REPO_SCM) + if err != nil { + return nil, fmt.Errorf("failed to detect value of %s: %w", flags.DRONE_REPO_SCM, err) + } + + trusted, err := cmd.Flags().GetBool(flags.DRONE_REPO_TRUSTED) + if err != nil { + return nil, fmt.Errorf("failed to detect value of %s: %w", flags.DRONE_REPO_TRUSTED, err) + } + + remote := &domain.Repo{ + Avatar: avatar, + Branch: branch, + FullName: fullName, + Link: link, + Name: name, + Owner: owner, + Private: private, + SCM: scm, + Trusted: trusted, + } + + return remote, nil +} + +func newYAMLByCommand(cmd *cobra.Command) (*domain.Yaml, error) { + signed, err := cmd.Flags().GetBool(flags.DRONE_YAML_SIGNED) + if err != nil { + return nil, fmt.Errorf("failed to detect value of %s: %w", flags.DRONE_YAML_SIGNED, err) + } + + verified, err := cmd.Flags().GetBool(flags.DRONE_YAML_VERIFIED) + if err != nil { + return nil, fmt.Errorf("failed to detect value of %s: %w", flags.DRONE_YAML_VERIFIED, err) + } + + yaml := &domain.Yaml{ + Signed: signed, + Verified: verified, + } + + return yaml, nil +} + +func newSMTPSettingsByCommand(cmd *cobra.Command) (*domain.SMTPSettings, error) { + smtpStartTLS, err := cmd.Flags().GetBool(flags.SMTP_START_TLS) + if err != nil { + return nil, fmt.Errorf("failed to detect value of %s: %w", flags.SMTP_START_TLS, err) + } + + smtpFromAddress, err := cmd.Flags().GetString(flags.SMTP_FROM_ADDRESS) + if err != nil { + return nil, fmt.Errorf("failed to detect value of %s: %w", flags.SMTP_FROM_ADDRESS, err) + } + + smtpFromName, err := cmd.Flags().GetString(flags.SMTP_FROM_NAME) + if err != nil { + return nil, fmt.Errorf("failed to detect value of %s: %w", flags.SMTP_FROM_NAME, err) + } + + smtpHELOName, err := cmd.Flags().GetString(flags.SMTP_HELO) + if err != nil { + return nil, fmt.Errorf("failed to detect value of %s: %w", flags.SMTP_HELO, err) + } + + smtpHost, err := cmd.Flags().GetString(flags.SMTP_HOST) + if err != nil { + return nil, fmt.Errorf("failed to detect value of %s: %w", flags.SMTP_HOST, err) + } + + smtpPassword, err := cmd.Flags().GetString(flags.SMTP_PASSWORD) + if err != nil { + return nil, fmt.Errorf("failed to detect value of %s: %w", flags.SMTP_PASSWORD, err) + } + + smtpPort, err := cmd.Flags().GetInt(flags.SMTP_PORT) + if err != nil { + return nil, fmt.Errorf("failed to detect value of %s: %w", flags.SMTP_PORT, err) + } + + smtpTLSInsecureSkipVerify, err := cmd.Flags().GetBool(flags.SMTP_TLS_INSECURE_SKIP_VERIFY) + if err != nil { + return nil, fmt.Errorf("failed to detect value of %s: %w", flags.SMTP_TLS_INSECURE_SKIP_VERIFY, err) + } + + smtpUsername, err := cmd.Flags().GetString(flags.SMTP_USERNAME) + if err != nil { + return nil, fmt.Errorf("failed to detect value of %s: %w", flags.SMTP_USERNAME, err) + } + + return &domain.SMTPSettings{ + FromAddress: smtpFromAddress, + FromName: smtpFromName, + HELOName: smtpHELOName, + Host: smtpHost, + Password: smtpPassword, + Port: smtpPort, + StartTLS: smtpStartTLS, + TLSInsecureSkipVerify: smtpTLSInsecureSkipVerify, + Username: smtpUsername, + }, nil +} diff --git a/cmd/completion.go b/cmd/completion.go new file mode 100644 index 0000000..e6a4e78 --- /dev/null +++ b/cmd/completion.go @@ -0,0 +1,67 @@ +package cmd + +import ( + "os" + + "github.com/spf13/cobra" +) + +var completionCmd = &cobra.Command{ + Use: "completion [bash|zsh|fish|powershell]", + Short: "Generate completion script", + Long: `To load completions: + +Bash: + + $ source <(drone-email completion bash) + + # To load completions for each session, execute once: + # Linux: + $ drone-email completion bash > /etc/bash_completion.d/drone-email + # macOS: + $ drone-email completion bash > $(brew --prefix)/etc/bash_completion.d/drone-email + +Zsh: + + # If shell completion is not already enabled in your environment, + # you will need to enable it. You can execute the following once: + + $ echo "autoload -U compinit; compinit" >> ~/.zshrc + + # To load completions for each session, execute once: + $ drone-email completion zsh > "${fpath[1]}/_drone-email" + + # You will need to start a new shell for this setup to take effect. + +fish: + + $ drone-email completion fish | source + + # To load completions for each session, execute once: + $ drone-email completion fish > ~/.config/fish/completions/drone-email.fish + +PowerShell: + + PS> drone-email completion powershell | Out-String | Invoke-Expression + + # To load completions for every new session, run: + PS> drone-email completion powershell > drone-email.ps1 + # and source this file from your PowerShell profile. +`, + DisableFlagsInUseLine: true, + ValidArgs: []string{"bash", "zsh", "fish", "powershell"}, + Args: cobra.ExactValidArgs(1), + Hidden: true, + Run: func(cmd *cobra.Command, args []string) { + switch args[0] { + case "bash": + _ = cmd.Root().GenBashCompletion(os.Stdout) + case "zsh": + _ = cmd.Root().GenZshCompletion(os.Stdout) + case "fish": + _ = cmd.Root().GenFishCompletion(os.Stdout, true) + case "powershell": + _ = cmd.Root().GenPowerShellCompletionWithDesc(os.Stdout) + } + }, +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..bb18b53 --- /dev/null +++ b/go.mod @@ -0,0 +1,28 @@ +module git.cryptic.systems/volker.raschek/drone-email-docker + +go 1.18 + +require ( + github.com/spf13/cobra v1.5.0 + github.com/spf13/pflag v1.0.5 + github.com/spf13/viper v1.12.0 +) + +require ( + github.com/fsnotify/fsnotify v1.5.4 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/magiconair/properties v1.8.6 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/pelletier/go-toml v1.9.5 // indirect + github.com/pelletier/go-toml/v2 v2.0.2 // indirect + github.com/spf13/afero v1.8.2 // indirect + github.com/spf13/cast v1.5.0 // indirect + github.com/spf13/jwalterweatherman v1.1.0 // indirect + github.com/subosito/gotenv v1.4.0 // indirect + golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b // indirect + golang.org/x/text v0.3.7 // indirect + gopkg.in/ini.v1 v1.66.6 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..0c886a5 --- /dev/null +++ b/go.sum @@ -0,0 +1,483 @@ +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.44.3/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 v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +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/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +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/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +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= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +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/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/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/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/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +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/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +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/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/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +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/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.4.1/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.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +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/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.2/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/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +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 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +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/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/magiconair/properties v1.8.6 h1:5ibWZ6iY0NctNGWo87LalDlEZ6R41TqbbDamhfG/Qzo= +github.com/magiconair/properties v1.8.6/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml/v2 v2.0.2 h1:+jQXlF3scKIcSEKkdHzXhCTDLPFi5r1wnK6yPS+49Gw= +github.com/pelletier/go-toml/v2 v2.0.2/go.mod h1:MovirKjgVRESsAvNZlAjtFwV867yGuwRkXbG66OzopI= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= +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/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/spf13/afero v1.8.2 h1:xehSyVa0YnHWsJ49JFljMpg1HX19V6NDZ1fkm1Xznbo= +github.com/spf13/afero v1.8.2/go.mod h1:CtAatgMJh6bJEIs48Ay/FOnkljP3WeGUG0MC1RfAqwo= +github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w= +github.com/spf13/cast v1.5.0/go.mod h1:SpXXQ5YoyJw6s3/6cMTQuxvgRl3PCJiyaX9p6b155UU= +github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= +github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= +github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk= +github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.12.0 h1:CZ7eSOd3kZoaYDLbXnmzgQI5RlciuXBMA+18HwHRfZQ= +github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/subosito/gotenv v1.4.0 h1:yAzM1+SmVcz5R4tXGsNMu1jUl2aOJXoiWUCEwwnGrvs= +github.com/subosito/gotenv v1.4.0/go.mod h1:mZd6rFysKEcUhUHXJk0C/08wAgyDBFuwEYL7vWWGaGo= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +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/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +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-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-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +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/lint v0.0.0-20201208152925-83fdc39ff7b5/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/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/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-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-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/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-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-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +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/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +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-20200317015054-43a5402ce75a/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-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-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-20190606165138-5da285871e9c/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-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/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-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-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/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-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/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-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b h1:2n253B2r0pYSmEV+UNCQoPfU/FiaizQEK5Gu4Bq4JE8= +golang.org/x/sys v0.0.0-20220627191245-f75cf1eec38b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.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/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +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/tools v0.0.0-20180917221912-90fa682c2a6e/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-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-20190621195816-6e04913cbbac/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-20200227222343-706bc42d1f0d/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-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +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.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.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +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/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +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-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-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-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +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.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +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.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +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= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/ini.v1 v1.66.6 h1:LATuAqN/shcYAOkv3wl2L4rkaKqkcgTBQjOyYDvcPKI= +gopkg.in/ini.v1 v1.66.6/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +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= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +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= diff --git a/main.go b/main.go new file mode 100644 index 0000000..0e507dd --- /dev/null +++ b/main.go @@ -0,0 +1,11 @@ +package main + +import ( + "git.cryptic.systems/volker.raschek/drone-email-docker/cmd" +) + +var version string + +func main() { + _ = cmd.Execute(version) +} diff --git a/manifest.tmpl b/manifest.tmpl new file mode 100644 index 0000000..a2a1eef --- /dev/null +++ b/manifest.tmpl @@ -0,0 +1,26 @@ +image: volkerraschek/drone-email:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}} +{{#if build.tags}} +tags: +{{#each build.tags}} + - {{this}} +{{/each}} + - "latest" +{{/if}} +manifests: + - + image: volkerraschek/drone-email:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}}-amd64 + platform: + architecture: amd64 + os: linux + - + image: volkerraschek/drone-email:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}}-arm-v7 + platform: + architecture: arm + os: linux + variant: v7 + - + image: volkerraschek/drone-email:{{#if build.tag}}{{trimPrefix "v" build.tag}}{{else}}latest{{/if}}-arm64-v8 + platform: + architecture: arm64 + os: linux + variant: v8 \ No newline at end of file diff --git a/pkg/domain/author.go b/pkg/domain/author.go new file mode 100644 index 0000000..e74edbb --- /dev/null +++ b/pkg/domain/author.go @@ -0,0 +1,7 @@ +package domain + +type Author struct { + Avatar string + Email string + Name string +} diff --git a/pkg/domain/build.go b/pkg/domain/build.go new file mode 100644 index 0000000..ac5e9a0 --- /dev/null +++ b/pkg/domain/build.go @@ -0,0 +1,33 @@ +package domain + +import "time" + +type Build struct { + Created int64 + Event string + Finished int64 + Link string + Number int + Started int64 + Status string +} + +func (b *Build) CreatedToTimeFormat(format string) string { + return time.Unix(b.Created, 0).Format(format) +} + +func (b *Build) FinishedToTimeFormat(format string) string { + return time.Unix(b.Finished, 0).Format(format) +} + +func (b *Build) IsEvent(expectedEvent string) bool { + return expectedEvent == b.Event +} + +func (b *Build) IsStatus(expectedStatus string) bool { + return expectedStatus == b.Status +} + +func (b *Build) StartedToTimeFormat(format string) string { + return time.Unix(b.Started, 0).Format(format) +} diff --git a/pkg/domain/commit.go b/pkg/domain/commit.go new file mode 100644 index 0000000..1f2a59f --- /dev/null +++ b/pkg/domain/commit.go @@ -0,0 +1,10 @@ +package domain + +type Commit struct { + Author *Author + Branch string + Link string + Message string + Ref string + Sha string +} diff --git a/pkg/domain/job.go b/pkg/domain/job.go new file mode 100644 index 0000000..426a19a --- /dev/null +++ b/pkg/domain/job.go @@ -0,0 +1,8 @@ +package domain + +type Job struct { + ExitCode int + Finished int64 + Started int64 + Status string +} diff --git a/pkg/domain/prev.go b/pkg/domain/prev.go new file mode 100644 index 0000000..dbf8c54 --- /dev/null +++ b/pkg/domain/prev.go @@ -0,0 +1,6 @@ +package domain + +type Prev struct { + Build *PrevBuild + Commit *PrevCommit +} diff --git a/pkg/domain/prevBuild.go b/pkg/domain/prevBuild.go new file mode 100644 index 0000000..48d623f --- /dev/null +++ b/pkg/domain/prevBuild.go @@ -0,0 +1,6 @@ +package domain + +type PrevBuild struct { + Number int + Status string +} diff --git a/pkg/domain/prevCommit.go b/pkg/domain/prevCommit.go new file mode 100644 index 0000000..fbdc1df --- /dev/null +++ b/pkg/domain/prevCommit.go @@ -0,0 +1,5 @@ +package domain + +type PrevCommit struct { + Sha string +} diff --git a/pkg/domain/remote.go b/pkg/domain/remote.go new file mode 100644 index 0000000..05ffb80 --- /dev/null +++ b/pkg/domain/remote.go @@ -0,0 +1,5 @@ +package domain + +type Remote struct { + URL string +} diff --git a/pkg/domain/repo.go b/pkg/domain/repo.go new file mode 100644 index 0000000..1de3c86 --- /dev/null +++ b/pkg/domain/repo.go @@ -0,0 +1,13 @@ +package domain + +type Repo struct { + Avatar string + Branch string + FullName string + Link string + Name string + Owner string + Private bool + SCM string + Trusted bool +} diff --git a/pkg/domain/smtp.go b/pkg/domain/smtp.go new file mode 100644 index 0000000..25dc11e --- /dev/null +++ b/pkg/domain/smtp.go @@ -0,0 +1,13 @@ +package domain + +type SMTPSettings struct { + FromAddress string + FromName string + HELOName string + Host string + Password string + Port int + StartTLS bool + TLSInsecureSkipVerify bool + Username string +} diff --git a/pkg/domain/yaml.go b/pkg/domain/yaml.go new file mode 100644 index 0000000..5837df6 --- /dev/null +++ b/pkg/domain/yaml.go @@ -0,0 +1,6 @@ +package domain + +type Yaml struct { + Signed bool + Verified bool +} diff --git a/pkg/flags/flags.go b/pkg/flags/flags.go new file mode 100644 index 0000000..2688ef8 --- /dev/null +++ b/pkg/flags/flags.go @@ -0,0 +1,56 @@ +package flags + +const ( + DRONE_BUILD_CREATED string = "drone-build-created" + DRONE_BUILD_EVENT string = "drone-build-event" + DRONE_BUILD_FINISHED string = "drone-build-finished" + DRONE_BUILD_LINK string = "drone-build-link" + DRONE_BUILD_NUMBER string = "drone-build-number" + DRONE_BUILD_STARTED string = "drone-build-started" + DRONE_BUILD_STATUS string = "drone-build-status" + DRONE_COMMIT_AUTHOR_NAME string = "drone-commit-author-name" + DRONE_COMMIT_AUTHOR_AVATAR string = "drone-commit-author-avatar" + DRONE_COMMIT_AUTHOR_EMAIL string = "drone-commit-author-email" + DRONE_COMMIT_BRANCH string = "drone-commit-branch" + DRONE_COMMIT_LINK string = "drone-commit-link" + DRONE_COMMIT_MESSAGE string = "drone-commit-message" + DRONE_COMMIT_REF string = "drone-commit-ref" + DRONE_COMMIT_SHA string = "drone-commit-sha" + DRONE_DEPLOY_TO string = "drone-deploy-to" + DRONE_JOB_EXIT_CODE string = "drone-job-exit-code" + DRONE_JOB_FINISHED string = "drone-job-finished" + DRONE_JOB_NUMBER string = "drone-job-number" + DRONE_JOB_STARTED string = "drone-job-started" + DRONE_JOB_STATUS string = "drone-job-status" + DRONE_PREV_BUILD_NUMBER string = "drone-prev-build-number" + DRONE_PREV_BUILD_STATUS string = "drone-prev-build-status" + DRONE_PREV_COMMIT_SHA string = "drone-prev-commit-sha" + DRONE_PULL_REQUEST string = "drone-pull-request" + DRONE_REMOTE_URL string = "drone-remote-url" + DRONE_REPO string = "drone-repo" + DRONE_REPO_AVATAR string = "drone-repo-avatar" + DRONE_REPO_BRANCH string = "drone-repo-branch" + DRONE_REPO_LINK string = "drone-repo-link" + DRONE_REPO_NAME string = "drone-repo-name" + DRONE_REPO_OWNER string = "drone-repo-owner" + DRONE_REPO_PRIVATE string = "drone-repo-private" + DRONE_REPO_SCM string = "drone-repo-scm" + DRONE_REPO_TRUSTED string = "drone-repo-trusted" + DRONE_TAG string = "drone-tag" + DRONE_YAML_SIGNED string = "drone-yaml-signed" + DRONE_YAML_VERIFIED string = "drone-yaml-verified" +) + +const ( + SMTP_FROM_ADDRESS string = "smtp-from-address" + SMTP_FROM_NAME string = "smtp-from-name" + SMTP_HELO string = "smtp-helo" + SMTP_HOST string = "smtp-host" + SMTP_MAIL_SUBJECT string = "smtp-mail-subject" + SMTP_PASSWORD string = "smtp-password" + SMTP_PORT string = "smtp-port" + SMTP_START_TLS string = "smtp-no-start-tls" + SMTP_TLS_INSECURE_SKIP_VERIFY string = "smtp-tls-insecure" + SMTP_TO_ADDRESSES string = "smtp-to-addresses" + SMTP_USERNAME string = "smtp-username" +) diff --git a/pkg/mail/assets/mail.txt b/pkg/mail/assets/mail.txt new file mode 100644 index 0000000..264cd89 --- /dev/null +++ b/pkg/mail/assets/mail.txt @@ -0,0 +1,278 @@ +Date: {{ .TimeNowFormat "Mon, 02 Jan 2006 15:04:05" }} +From: {{ .SMTPSettings.FromName }} <{{ .SMTPSettings.FromAddress }}> +To: {{ .Recipient }} +Subject: [{{ .CIVars.Build.Status }}] {{ .CIVars.Repo.Name }} ({{ .CIVars.Commit.Branch }} - {{ .CIVars.Commit.Sha }}) +Content-Type: multipart/alternative; + boundary=3399d59dca7fb53c0236f440e2a402d670fc0abe57faa6f0233e85338b03 + +--3399d59dca7fb53c0236f440e2a402d670fc0abe57faa6f0233e85338b03 +Content-Transfer-Encoding: quoted-printable +Content-Type: text/plain; charset=UTF-8 + +{{- if .CIVars.Build.IsStatus "success" -}} +Success build #{{ .CIVars.Build.Number }} +{{- else -}} +Failed build #{{ .CIVars.Build.Number }} +{{- end }} + +Name: {{ .CIVars.Repo.Name }} +Author: {{ .CIVars.Commit.Author.Name }} <{{ .CIVars.Commit.Author.Email }}> +Branch: {{ .CIVars.Repo.Branch }} +Commit: {{ .CIVars.Commit.Sha }} +Started At: {{ .CIVars.Build.StartedToTimeFormat "2006-02-01 15:04:05" }} +Link: {{ .CIVars.Build.Link }} + +--3399d59dca7fb53c0236f440e2a402d670fc0abe57faa6f0233e85338b03 +Content-Transfer-Encoding: quoted-printable +Content-Type: text/html; charset=UTF-8 + + + + + + + + + + + + + + + +
+
+ + + {{ if .CIVars.Build.IsStatus "success" }} + + {{ else }} + + {{ end }} + + + + +
+ + Successful build #{{ .CIVars.Build.Number }} + + + + Failed build #{{ .CIVars.Build.Number }} + +
+ + + + + + + + + + + + + + + + + + + + + +
+ Repo: + + {{ .CIVars.Repo.Name }} +
+ Author: + + {{ .CIVars.Commit.Author.Name }} <{{ .CIVars.Commit.Author.Email }}> +
+ Branch: + + {{ .CIVars.Commit.Branch }} +
+ Commit: + + {{ .CIVars.Commit.Sha }} +
+ Started at: + + {{ .CIVars.Build.StartedToTimeFormat "2006-02-01 15:04:05" }} +
+
+ + + + +
+ {{ .CIVars.Commit.Message }} +
+
+
+
+ + +--3399d59dca7fb53c0236f440e2a402d670fc0abe57faa6f0233e85338b03-- \ No newline at end of file diff --git a/pkg/mail/mail.go b/pkg/mail/mail.go new file mode 100644 index 0000000..e4d12af --- /dev/null +++ b/pkg/mail/mail.go @@ -0,0 +1,180 @@ +package mail + +import ( + "bytes" + "context" + "crypto/tls" + "fmt" + "io" + "net" + "net/smtp" + "text/template" + "time" + + "git.cryptic.systems/volker.raschek/drone-email-docker/pkg/domain" + + _ "embed" +) + +const ( + DefaultSMTPFromAddress = "root@localhost" + DefaultSMTPFromName = "root" + DefaultSMTPHost = "localhost" + DefaultSMTPPort = 587 + DefaultSMTPStartTLS = true + DefaultSMTPTLSInsecureSkipVerify = false + DefaultSMTPToAddress = "root@localhost" +) + +//go:embed assets/mail.txt +var mailTemplate string + +type CIVars struct { + Build *domain.Build + Commit *domain.Commit + DeployTo string + Job *domain.Job + Prev *domain.Prev + PullRequest int + Remote *domain.Remote + Repo *domain.Repo + Tag string + Yaml *domain.Yaml +} + +type templateVars struct { + CIVars *CIVars + Recipient string + SMTPSettings *domain.SMTPSettings +} + +func (t *templateVars) TimeNowFormat(layout string) string { + return time.Now().Format(layout) +} + +type Plugin struct { + smtpSettings *domain.SMTPSettings +} + +// Exec will send emails over SMTP +func (p *Plugin) Exec(ctx context.Context, recipients []string, ciVars *CIVars) error { + exists := false + for _, recipient := range recipients { + if recipient == ciVars.Commit.Author.Email { + exists = true + break + } + } + + if !exists { + recipients = append(recipients, ciVars.Commit.Author.Email) + } + + tpl, err := template.New("mail").Parse(mailTemplate) + if err != nil { + return fmt.Errorf("failed to parse template: %w", err) + } + + buf := make([]byte, 0) + buffer := bytes.NewBuffer(buf) + + for _, recipient := range recipients { + err = tpl.Execute(buffer, &templateVars{ + CIVars: ciVars, + Recipient: recipient, + SMTPSettings: p.smtpSettings, + }) + if err != nil { + return fmt.Errorf("failed to generate template: %w", err) + } + + err := p.sendMail(recipient, buffer) + if err != nil { + return fmt.Errorf("failed to send mail: %w", err) + } + + buffer.Reset() + } + + return nil +} + +func (p *Plugin) sendMail(recipient string, r io.Reader) error { + // log.Printf("FROM_ADDRESS: %s", p.smtpSettings.FromAddress) + // log.Printf("FROM_NAME: %s", p.smtpSettings.FromName) + // log.Printf("HELO: %s", p.smtpSettings.HELOName) + // log.Printf("HOST: %s", p.smtpSettings.Host) + // log.Printf("PASSWORD: %s", p.smtpSettings.Password) + // log.Printf("USERNAME: %s", p.smtpSettings.Username) + // log.Printf("PORT: %v", p.smtpSettings.Port) + // log.Printf("START_TLS: %v", p.smtpSettings.StartTLS) + // log.Printf("INSECURE: %v", p.smtpSettings.TLSInsecureSkipVerify) + + address := fmt.Sprintf("%s:%d", p.smtpSettings.Host, p.smtpSettings.Port) + tcpConn, err := net.Dial("tcp", address) + if err != nil { + return fmt.Errorf("failed to dial a connection to %s: %w", address, err) + } + defer func() { _ = tcpConn.Close() }() + + smtpClient, err := smtp.NewClient(tcpConn, p.smtpSettings.Host) + if err != nil { + return fmt.Errorf("failed to initialize a new smtp client: %w", err) + } + defer func() { _ = smtpClient.Close() }() + + err = smtpClient.Hello(p.smtpSettings.HELOName) + if err != nil { + return fmt.Errorf("failed to send helo command: %w", err) + } + + // #nosec G402 + err = smtpClient.StartTLS(&tls.Config{ + InsecureSkipVerify: p.smtpSettings.TLSInsecureSkipVerify, + MinVersion: tls.VersionTLS12, + ServerName: p.smtpSettings.Host, + }) + if err != nil { + return fmt.Errorf("failed initialize starttls session: %w", err) + } + + smtpAuth := smtp.PlainAuth(p.smtpSettings.FromAddress, p.smtpSettings.FromAddress, p.smtpSettings.Password, p.smtpSettings.Host) + err = smtpClient.Auth(smtpAuth) + if err != nil { + return fmt.Errorf("failed to authenticate client: %w", err) + } + + err = smtpClient.Mail(p.smtpSettings.FromAddress) + if err != nil { + return fmt.Errorf("failed to sent mail command: %w", err) + } + + err = smtpClient.Rcpt(recipient) + if err != nil { + return fmt.Errorf("failed to sent rcpt command for %s: %w", recipient, err) + } + + wc, err := smtpClient.Data() + if err != nil { + return fmt.Errorf("failed to send data command: %w", err) + } + defer func() { _ = wc.Close() }() + + _, err = io.Copy(wc, r) + if err != nil { + return fmt.Errorf("failed to copy input from passed reader to smtp writer: %w", err) + } + + err = smtpClient.Quit() + if err != nil { + return fmt.Errorf("failed to send quit command: %w", err) + } + + return nil +} + +func NewPlugin(config *domain.SMTPSettings) *Plugin { + return &Plugin{ + smtpSettings: config, + } +} diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..b9e4ee1 --- /dev/null +++ b/renovate.json @@ -0,0 +1,37 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "assignees": [ "volker.raschek" ], + "automergeStrategy": "merge-commit", + "automergeType": "pr", + "labels": [ "renovate" ], + "packageRules": [ + { + "description": "Automatically update minor and patch versions of used drone-ci images", + "addLabels": [ "renovate/droneci", "renovate/automerge" ], + "automerge": true, + "matchManagers": "droneci", + "matchUpdateTypes": [ "minor", "patch"] + }, + { + "description": "Automatically update minor and patch versions of go modules", + "addLabels": [ "renovate/gomod", "renovate/automerge" ], + "automerge": true, + "matchBaseBranches": [ "master" ], + "matchManagers": [ "gomod" ], + "matchUpdateTypes": [ "minor", "patch" ] + }, + { + "description": "Prepare MR for major update minor of go modules", + "addLabels": [ "renovate/gomod" ], + "automerge": false, + "matchBaseBranches": [ "master" ], + "matchManagers": [ "gomod" ], + "matchUpdateTypes": [ "major" ] + } + ], + "postUpdateOptions": [ + "gomodTidy" + ], + "rebaseLabel": "renovate/rebase", + "rebaseWhen": "behind-base-branch" +}